Skip to content

Commit bd9676f

Browse files
committed
chore(wheelhouse): cascade template@9cc162a2
Auto-applied by socket-wheelhouse sync-scaffolding into socket-lib. 2 file(s) touched: - scripts/fleet/ai-lint-fix/oxlint-json.mts - scripts/fleet/fix.mts
1 parent 15842b8 commit bd9676f

2 files changed

Lines changed: 55 additions & 26 deletions

File tree

scripts/fleet/ai-lint-fix/oxlint-json.mts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,18 @@ export async function runLintJson(
124124
if (!stdout.trim()) {
125125
return []
126126
}
127+
// The Socket Firewall (sfw) install wrapper prepends a "Protected by Socket
128+
// Firewall" banner (and may append a trailer) to stdout, so the captured
129+
// payload is not pure JSON. Extract the root object span — first `{` to last
130+
// `}` — before parsing. A no-banner environment slices to the whole string.
131+
const jsonStart = stdout.indexOf('{')
132+
const jsonEnd = stdout.lastIndexOf('}')
133+
if (jsonStart === -1 || jsonEnd <= jsonStart) {
134+
return []
135+
}
136+
const jsonText = stdout.slice(jsonStart, jsonEnd + 1)
127137
try {
128-
const parsed = JSON.parse(stdout) as OxlintJsonOutput
138+
const parsed = JSON.parse(jsonText) as OxlintJsonOutput
129139
if (!parsed || !Array.isArray(parsed.diagnostics)) {
130140
return []
131141
}

scripts/fleet/fix.mts

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@ import { spawn } from '@socketsecurity/lib-stable/process/spawn/child'
2525
const WIN32 = process.platform === 'win32'
2626
const logger = getDefaultLogger()
2727

28+
// Pull the numeric exit code out of a lib-spawn rejection. The rejection
29+
// carries `{ code }` — a number for a normal non-zero exit, a string (e.g.
30+
// 'ENOENT') for a spawn failure. Non-numeric → 1 (treat as a generic failure).
31+
function exitCodeOf(e: unknown): number {
32+
if (e && typeof e === 'object' && 'code' in e) {
33+
const { code } = e as { code: unknown }
34+
if (typeof code === 'number') {
35+
return code
36+
}
37+
}
38+
return 1
39+
}
40+
2841
async function run(
2942
cmd: string,
3043
args: string[],
@@ -38,37 +51,34 @@ async function run(
3851
shell: WIN32,
3952
stdio: 'inherit',
4053
})
41-
if (result.code !== 0 && required) {
42-
logger.error(`${label || cmd} failed (exit ${result.code})`)
43-
return result.code
44-
}
45-
if (result.code !== 0) {
46-
// Non-blocking: log warning and continue.
47-
logger.warn(`${label || cmd}: exited ${result.code} (non-blocking)`)
48-
}
49-
return 0
54+
return result.code ?? 0
5055
} catch (e) {
51-
const msg = e instanceof Error ? e.message : String(e)
52-
if (!required) {
53-
logger.warn(`${label || cmd}: ${msg} (non-blocking)`)
54-
return 0
56+
// The lib `spawn` REJECTS on a non-zero exit (carrying `{ code }`), so a
57+
// "failing" command lands here, not the resolved branch. Surface the real
58+
// exit code instead of throwing — the caller decides what non-zero means.
59+
// Throwing here would abort the pipeline and skip later steps (notably
60+
// ai-lint-fix after `lint --fix` exits non-zero with AI-fixable errors).
61+
const code = exitCodeOf(e)
62+
if (required) {
63+
logger.error(`${label || cmd} failed (exit ${code})`)
64+
} else {
65+
logger.warn(`${label || cmd}: exited ${code} (non-blocking)`)
5566
}
56-
throw e
67+
return code
5768
}
5869
}
5970

6071
async function main(): Promise<void> {
61-
// Step 1: Lint fix — delegates to scripts/fleet/lint.mts which runs both
62-
// oxfmt and oxlint. Forward extra argv so `--all` / `--staged` /
63-
// explicit file paths reach the lint runner unchanged.
64-
const lintExit = await run(
65-
'pnpm',
66-
['run', 'lint', '--fix', ...process.argv.slice(2)],
67-
{ label: 'lint --fix' },
68-
)
69-
if (lintExit) {
70-
process.exitCode = lintExit
71-
}
72+
// Lint fix (oxfmt + oxlint via scripts/fleet/lint.mts). Forward extra argv so
73+
// `--all` / `--staged` / explicit file paths reach the runner unchanged.
74+
// NON-required: oxlint can't autofix custom socket/* JS-plugin rules, so a
75+
// non-zero exit is the EXPECTED case — those violations are what ai-lint-fix
76+
// (below) handles, and gating the pipeline here would skip it when it's needed
77+
// most. The real pass/fail is the verify run at the end of this function.
78+
await run('pnpm', ['run', 'lint', '--fix', ...process.argv.slice(2)], {
79+
label: 'lint --fix',
80+
required: false,
81+
})
7282

7383
// Step 2: zizmor — fixes GitHub Actions workflow security issues.
7484
// Only runs if .github/ directory exists (some repos don't have workflows).
@@ -107,6 +117,15 @@ async function main(): Promise<void> {
107117
required: false,
108118
},
109119
)
120+
121+
// Verify: re-run lint (no --fix) to set the real exit code. `fix` succeeds
122+
// only when nothing remains after the deterministic + AI passes; a lingering
123+
// violation (or a genuine lint crash) surfaces here as a non-zero exit.
124+
process.exitCode = await run(
125+
'pnpm',
126+
['run', 'lint', ...process.argv.slice(2)],
127+
{ label: 'lint (verify)', required: false },
128+
)
110129
}
111130

112131
main().catch((e: unknown) => {

0 commit comments

Comments
 (0)