Skip to content

Commit 0980857

Browse files
committed
chore(lint): clear socket/* + eslint findings to current fleet ruleset
Resolve the full lint backlog (0 errors, 0 warnings repo-wide): - optional-explicit-undefined (14): add `| undefined` to optional property types across src/ (auth, github, go/import-finder, go/mod-parser, go/wasm-executor, decorations, parse-externals, purl-alerts manager) to pair with exactOptionalPropertyTypes. - no-underscore-dangle (2): justify-disable `_pendingEvent` in go/wasm-executor — the Go wasm ABI runtime reads/writes the field by that exact name; renaming breaks the syscall contract. - prefer-spawn-over-execsync (5): migrate scripts/{check,test,lint}.mts from execSync/execFileSync to spawnSync (array args, no shell) from @socketsecurity/lib-stable; check.mts adopts the current fleet shape. - prefer-ellipsis-char (2): `...` → `…` in lint.mts progress logs. - no-underscore-dangle (2 in paths.mts): inline path.dirname(fileURLToPath(import.meta.url)) instead of named __dirname/__filename. - prefer-node-modules-dot-cache (1): vitest exclude uses the `.{idea,git,cache,…}` brace form instead of a bare `.cache/` path. Assisted-by: Claude Code:opus-4-7
1 parent 5dc33ad commit 0980857

13 files changed

Lines changed: 202 additions & 167 deletions

File tree

.config/vitest.config.mts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @fileoverview Vitest configuration.
2+
* @file Vitest configuration.
33
*/
44
import process from 'node:process'
55

@@ -27,7 +27,7 @@ export default defineConfig({
2727
'**/node_modules/**',
2828
'**/dist/**',
2929
'**/out/**',
30-
'**/.cache/**',
30+
'**/.{idea,git,cache,output,temp}/**',
3131
'**/.claude/**',
3232
],
3333
reporters: ['default'],

scripts/check.mts

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,62 @@
11
/**
2-
* @fileoverview Unified check runner — delegates to lint + type +
3-
* path-hygiene.
4-
*
5-
* Forwards CLI scope flags to the lint script so `pnpm run check --all`
6-
* actually runs a full-scope lint (not the default modified-only scope).
7-
* `pnpm type` doesn't accept our scope flags, so it's always a full
8-
* check.
9-
*
10-
* Usage:
11-
* pnpm run check # lint in modified scope + full type
12-
* check + path-hygiene
13-
* pnpm run check --staged # lint staged + full type + paths
14-
* pnpm run check --all # full lint + full type + paths (CI)
15-
*
16-
* Byte-identical across every fleet repo. Sync-scaffolding flags drift.
2+
* @file Unified check runner — delegates to lint + type + path-hygiene.
3+
* Forwards CLI scope flags to the lint script so `pnpm run check --all`
4+
* actually runs a full-scope lint (not the default modified-only scope).
5+
* `pnpm type` doesn't accept our scope flags, so it's always a full check.
6+
* Usage: pnpm run check # lint in modified scope + full type check +
7+
* path-hygiene pnpm run check --staged # lint staged + full type + paths pnpm
8+
* run check --all # full lint + full type + paths (CI) Byte-identical across
9+
* every fleet repo. Sync-scaffolding flags drift.
1710
*/
1811

19-
import { execSync } from 'node:child_process'
12+
// prefer-async-spawn: sync-required — top-level CLI runner; entire
13+
// flow is sequential gate-running with exit-code aggregation.
14+
import { spawnSync } from '@socketsecurity/lib-stable/process/spawn/child'
2015
import process from 'node:process'
2116

2217
const args = process.argv.slice(2)
2318
const forwardedArgs = args.filter(
2419
a => a === '--all' || a === '--fix' || a === '--quiet' || a === '--staged',
2520
)
2621

27-
try {
28-
const lintArgs = forwardedArgs.length ? ' ' + forwardedArgs.join(' ') : ''
29-
execSync(`node scripts/lint.mts${lintArgs}`, { stdio: 'inherit' })
30-
execSync('pnpm exec tsgo --noEmit -p tsconfig.check.json', {
31-
stdio: 'inherit',
32-
})
22+
// spawnSync with array args — no shell interpolation, matches the
23+
// socket/prefer-spawn-over-execsync rule.
24+
export function run(cmd: string, cmdArgs: string[]): boolean {
25+
const r = spawnSync(cmd, cmdArgs, { stdio: 'inherit' })
26+
return r.status === 0
27+
}
28+
29+
const steps: Array<() => boolean> = [
30+
// Lint scope is forwarded; everything else is full-scope.
31+
() => run('node', ['scripts/lint.mts', ...forwardedArgs]),
32+
() => run('pnpm', ['exec', 'tsgo', '--noEmit', '-p', 'tsconfig.check.json']),
3333
// Path-hygiene check (1 path, 1 reference). Mantra-driven gate;
3434
// see .claude/skills/path-guard/ + .claude/hooks/path-guard/.
35-
execSync('node scripts/check-paths.mts --quiet', { stdio: 'inherit' })
36-
} catch {
37-
process.exitCode = 1
35+
() => run('node', ['scripts/check-paths.mts', '--quiet']),
36+
// Lock-step reference hygiene. Opt-in gate that exits clean when
37+
// .config/lock-step-refs.json is absent; for repos that ship
38+
// cross-language ports (acorn quadruplet, socket-btm mcp/*.cpp),
39+
// it validates every `Lock-step with <Lang>: <path>` comment resolves
40+
// to an existing file. Forms documented in
41+
// docs/claude.md/fleet/parser-comments.md §5–6.
42+
() => run('node', ['scripts/check-lock-step-refs.mts', '--quiet']),
43+
// Lock-step header byte-equality. Same opt-in. Where the path-refs
44+
// gate above catches stale REFERENCES, this one catches drift in the
45+
// top-of-file `BEGIN LOCK-STEP HEADER` / `END LOCK-STEP HEADER` block
46+
// — the intent tripwire across the quadruplet. Spec:
47+
// docs/claude.md/fleet/parser-comments.md §7.
48+
() => run('node', ['scripts/check-lock-step-header.mts', '--quiet']),
49+
// Soak-exclude date-annotation gate — pairs with
50+
// .claude/hooks/soak-exclude-date-annotation-guard/. Catches
51+
// pnpm-workspace.yaml `minimumReleaseAgeExclude` entries that landed
52+
// via non-Claude paths without the canonical
53+
// `# published: YYYY-MM-DD | removable: YYYY-MM-DD` annotation.
54+
() => run('node', ['scripts/check-soak-exclude-dates.mts']),
55+
]
56+
57+
for (let i = 0, { length } = steps; i < length; i += 1) {
58+
if (!steps[i]!()) {
59+
process.exitCode = 1
60+
break
61+
}
3862
}

scripts/lint.mts

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
* contract so pre-commit hooks and CI work identically across repos.
1414
*/
1515

16-
import { execFileSync, execSync } from 'node:child_process'
17-
import type { ExecSyncOptions } from 'node:child_process'
16+
import { spawnSync } from '@socketsecurity/lib-stable/process/spawn/child'
17+
import type { SpawnSyncOptions } from 'node:child_process'
1818
import { existsSync } from 'node:fs'
1919
import path from 'node:path'
2020
import process from 'node:process'
@@ -30,7 +30,7 @@ const mode: 'staged' | 'all' | 'modified' = args.includes('--all')
3030
: 'modified'
3131
const fix = args.includes('--fix')
3232
const quiet = args.includes('--quiet') || args.includes('--silent')
33-
const stdio: ExecSyncOptions['stdio'] = quiet ? 'pipe' : 'inherit'
33+
const stdio: SpawnSyncOptions['stdio'] = quiet ? 'pipe' : 'inherit'
3434

3535
const LINTABLE_EXTS = new Set(['.cjs', '.cts', '.js', '.mjs', '.mts', '.ts'])
3636

@@ -49,26 +49,29 @@ export function filterLintable(files: string[]): string[] {
4949
}
5050

5151
export function getModifiedFiles(): string[] {
52-
return gitFiles('git diff --name-only --diff-filter=ACMR HEAD')
52+
return gitFiles(['diff', '--name-only', '--diff-filter=ACMR', 'HEAD'])
5353
}
5454

5555
export function getStagedFiles(): string[] {
56-
return gitFiles('git diff --cached --name-only --diff-filter=ACMR')
56+
return gitFiles(['diff', '--cached', '--name-only', '--diff-filter=ACMR'])
5757
}
5858

59-
export function gitFiles(command: string): string[] {
60-
try {
61-
const out = execSync(command, {
62-
encoding: 'utf8',
63-
stdio: ['ignore', 'pipe', 'pipe'],
64-
})
65-
return out
66-
.split('\n')
67-
.map(s => s.trim())
68-
.filter(s => s.length > 0)
69-
} catch {
59+
// spawnSync with array args — no shell interpolation. Matches the
60+
// socket/prefer-spawn-over-execsync rule: a shell-string execSync makes
61+
// every interpolated value an injection vector; the array form can't
62+
// shell-expand its args.
63+
export function gitFiles(gitArgs: string[]): string[] {
64+
const r = spawnSync('git', gitArgs, {
65+
stdio: ['ignore', 'pipe', 'pipe'],
66+
stdioString: true,
67+
})
68+
if (r.status !== 0 || typeof r.stdout !== 'string') {
7069
return []
7170
}
71+
return r.stdout
72+
.split('\n')
73+
.map(s => s.trim())
74+
.filter(s => s.length > 0)
7275
}
7376

7477
export function log(msg: string): void {
@@ -78,22 +81,24 @@ export function log(msg: string): void {
7881
}
7982

8083
export function runAll(): number {
81-
log('Formatting all files...')
82-
try {
83-
execSync(
84-
`pnpm exec oxfmt -c .config/oxfmtrc.json ${fix ? '--write' : '--check'} .`,
85-
{ stdio },
86-
)
87-
} catch {
84+
log('Formatting all files…')
85+
const oxfmtArgs = [
86+
'exec',
87+
'oxfmt',
88+
'-c',
89+
'.config/oxfmtrc.json',
90+
fix ? '--write' : '--check',
91+
'.',
92+
]
93+
if (spawnSync('pnpm', oxfmtArgs, { stdio }).status !== 0) {
8894
return 1
8995
}
90-
log('Running oxlint on all files...')
91-
try {
92-
execSync(
93-
`pnpm exec oxlint -c .config/oxlintrc.json${fix ? ' --fix' : ''}`,
94-
{ stdio },
95-
)
96-
} catch {
96+
log('Running oxlint on all files…')
97+
const oxlintArgs = ['exec', 'oxlint', '-c', '.config/oxlintrc.json']
98+
if (fix) {
99+
oxlintArgs.push('--fix')
100+
}
101+
if (spawnSync('pnpm', oxlintArgs, { stdio }).status !== 0) {
97102
return 1
98103
}
99104
return 0
@@ -104,7 +109,7 @@ export function runFiles(files: string[]): number {
104109
log('No lintable files; skipping.')
105110
return 0
106111
}
107-
log(`Formatting ${files.length} file(s)...`)
112+
log(`Formatting ${files.length} file(s)`)
108113
const oxfmtArgs = [
109114
'exec',
110115
'oxfmt',
@@ -114,20 +119,16 @@ export function runFiles(files: string[]): number {
114119
'--no-error-on-unmatched-pattern',
115120
...files,
116121
]
117-
try {
118-
execFileSync('pnpm', oxfmtArgs, { stdio })
119-
} catch {
122+
if (spawnSync('pnpm', oxfmtArgs, { stdio }).status !== 0) {
120123
return 1
121124
}
122-
log(`Running oxlint on ${files.length} file(s)...`)
125+
log(`Running oxlint on ${files.length} file(s)`)
123126
const oxlintArgs = ['exec', 'oxlint', '-c', '.config/oxlintrc.json']
124127
if (fix) {
125128
oxlintArgs.push('--fix')
126129
}
127130
oxlintArgs.push(...files)
128-
try {
129-
execFileSync('pnpm', oxlintArgs, { stdio })
130-
} catch {
131+
if (spawnSync('pnpm', oxlintArgs, { stdio }).status !== 0) {
131132
return 1
132133
}
133134
return 0

scripts/paths.mts

Lines changed: 32 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,31 @@
11
/**
2-
* @fileoverview Centralized path resolution for vscode-socket-security.
3-
*
4-
* Source of truth for every build/test/runtime path. Per the fleet
5-
* `1 path, 1 reference` rule — every other module imports from here
6-
* instead of constructing paths inline.
7-
*
8-
* Layout follows the socket-btm canonical pattern:
9-
* build/<mode>/<platform-arch>/out/<artifact>
10-
*
11-
* For this repo specifically, the bundled output is platform-agnostic
12-
* (esbuild emits a single CommonJS file that runs in whatever Node
13-
* VSCode provides — the only platform-fork inputs are the embedded
14-
* WASM binaries which esbuild includes via --loader:.wasm=binary).
15-
* We still expose getBuildPaths(mode, platformArch) for fleet
16-
* compatibility; consumers that don't care about platform can call
17-
* it with a fixed sentinel like 'any'.
18-
*
19-
* VSCode marketplace publishing expects `main: ./out/main.js` per
20-
* package.json. The canonical build output therefore lands at
21-
* `./out/main.js` directly (no platform/mode subtree) — same place
22-
* as before this refactor — but `BUILD_ROOT` and `getBuildPaths`
23-
* are still defined so future build steps (e.g. per-platform sfw
24-
* shims, signed VSIX outputs) can branch the layout without
25-
* inventing path conventions ad-hoc.
2+
* @file Centralized path resolution for vscode-socket-security. Source of truth
3+
* for every build/test/runtime path. Per the fleet `1 path, 1 reference` rule
4+
* — every other module imports from here instead of constructing paths
5+
* inline. Layout follows the socket-btm canonical pattern:
6+
* build/<mode>/<platform-arch>/out/<artifact> For this repo specifically, the
7+
* bundled output is platform-agnostic (esbuild emits a single CommonJS file
8+
* that runs in whatever Node VSCode provides — the only platform-fork inputs
9+
* are the embedded WASM binaries which esbuild includes via
10+
* --loader:.wasm=binary). We still expose getBuildPaths(mode, platformArch)
11+
* for fleet compatibility; consumers that don't care about platform can call
12+
* it with a fixed sentinel like 'any'. VSCode marketplace publishing expects
13+
* `main: ./out/main.js` per package.json. The canonical build output
14+
* therefore lands at `./out/main.js` directly (no platform/mode subtree) —
15+
* same place as before this refactor — but `BUILD_ROOT` and `getBuildPaths`
16+
* are still defined so future build steps (e.g. per-platform sfw shims,
17+
* signed VSIX outputs) can branch the layout without inventing path
18+
* conventions ad-hoc.
2619
*/
2720

2821
import path from 'node:path'
2922
import { fileURLToPath } from 'node:url'
3023

31-
const __filename = fileURLToPath(import.meta.url)
32-
const __dirname = path.dirname(__filename)
33-
34-
// Package root: scripts/../
35-
export const PACKAGE_ROOT = path.resolve(__dirname, '..')
24+
// Package root: scripts/../ — derive from this module's own location.
25+
export const PACKAGE_ROOT = path.resolve(
26+
path.dirname(fileURLToPath(import.meta.url)),
27+
'..',
28+
)
3629

3730
// Build roots.
3831
//
@@ -60,19 +53,18 @@ export const EXTENSION_ENTRY = path.join(SRC_DIR, 'extension.ts')
6053
/**
6154
* Build paths for a specific (mode, platform-arch) tuple.
6255
*
63-
* @param buildMode 'dev' | 'prod' (determines minify, debug toggles)
64-
* @param platformArch e.g. 'darwin-arm64', 'linux-x64', 'win32-x64'.
65-
* Use 'any' when the artifact is platform-agnostic.
56+
* @param buildMode 'dev' | 'prod' (determines minify, debug toggles)
57+
* @param platformArch E.g. 'darwin-arm64', 'linux-x64', 'win32-x64'. Use 'any'
58+
* when the artifact is platform-agnostic.
6659
*
67-
* Returns an object whose keys mirror the socket-btm canonical:
68-
* buildDir build/<mode>/<platformArch>
69-
* outputFinalDir build/<mode>/<platformArch>/out/Final
70-
* outputFinalFile the bundled JS output
60+
* Returns an object whose keys mirror the socket-btm canonical: buildDir
61+
* build/<mode>/<platformArch> outputFinalDir
62+
* build/<mode>/<platformArch>/out/Final outputFinalFile the bundled JS
63+
* output.
7164
*
72-
* For vscode-socket-security the `out/Final/main.js` mirror is
73-
* documentation only — the real shipped path is OUT_DIR/main.js (so
74-
* vsce packaging finds it). Per-mode build trees are reserved for
75-
* future use.
65+
* For vscode-socket-security the `out/Final/main.js` mirror is documentation
66+
* only — the real shipped path is OUT_DIR/main.js (so vsce packaging finds
67+
* it). Per-mode build trees are reserved for future use.
7668
*/
7769
export function getBuildPaths(
7870
buildMode: 'dev' | 'prod',

0 commit comments

Comments
 (0)