mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
refactor: centralize Oxlint worker bounds
This commit is contained in:
@@ -15,7 +15,7 @@ pre-commit:
|
|||||||
glob: '*.{ts,tsx,mts,cts,mjs}'
|
glob: '*.{ts,tsx,mts,cts,mjs}'
|
||||||
exclude:
|
exclude:
|
||||||
- 'vendor/*/src/**'
|
- 'vendor/*/src/**'
|
||||||
run: node_modules/.bin/oxlint --fix --no-error-on-unmatched-pattern {staged_files}
|
run: node_modules/.bin/tsx scripts/run-oxlint.ts --fix --no-error-on-unmatched-pattern {staged_files}
|
||||||
stage_fixed: true
|
stage_fixed: true
|
||||||
|
|
||||||
- name: whitespace (staged)
|
- name: whitespace (staged)
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
"clean": "tsx scripts/clean.ts",
|
"clean": "tsx scripts/clean.ts",
|
||||||
"change-scope": "tsx scripts/change-scope.ts",
|
"change-scope": "tsx scripts/change-scope.ts",
|
||||||
"typecheck": "tsc -b",
|
"typecheck": "tsc -b",
|
||||||
"lint": "oxlint .",
|
"lint": "tsx scripts/run-oxlint.ts .",
|
||||||
"lint:fix": "eslint --config eslint.format.config.mjs --fix . && oxlint . --fix",
|
"lint:fix": "eslint --config eslint.format.config.mjs --fix . && tsx scripts/run-oxlint.ts . --fix",
|
||||||
"duplication": "jscpd --config .jscpd.json packages scripts",
|
"duplication": "jscpd --config .jscpd.json packages scripts",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:coverage": "vitest run --coverage",
|
"test:coverage": "vitest run --coverage",
|
||||||
|
|||||||
@@ -121,24 +121,17 @@ describe('Oxlint gate', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('passes the configured worker bound to both Oxlint backends', () => {
|
it('surfaces the configured worker bound on the shared package script', () => {
|
||||||
const subject = withEnv('DSH_OXLINT_THREADS', '4', () =>
|
const subject = withEnv('DSH_OXLINT_THREADS', '4', () =>
|
||||||
withPnpmEntrypoint(() => gatesForMode('ci-lint')[0]))
|
withPnpmEntrypoint(() => gatesForMode('ci-lint')[0]))
|
||||||
|
|
||||||
expect(subject).toMatchObject({
|
expect(subject).toMatchObject({
|
||||||
id: 'lint',
|
id: 'lint',
|
||||||
displayCommand: 'pnpm exec oxlint . --threads=4',
|
displayCommand: 'DSH_OXLINT_THREADS=4 pnpm run lint',
|
||||||
command: process.execPath,
|
command: process.execPath,
|
||||||
args: ['/private/pnpm.cjs', 'exec', 'oxlint', '.', '--threads=4'],
|
args: ['/private/pnpm.cjs', 'run', 'lint'],
|
||||||
env: { GOMAXPROCS: '4' },
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('rejects a non-positive or non-integer worker bound', () => {
|
|
||||||
expect(() => withEnv('DSH_OXLINT_THREADS', 'auto', () =>
|
|
||||||
withPnpmEntrypoint(() => gatesForMode('ci-lint'))))
|
|
||||||
.toThrow('DSH_OXLINT_THREADS must be a positive integer')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Node 24 consumer graph', () => {
|
describe('Node 24 consumer graph', () => {
|
||||||
|
|||||||
@@ -377,24 +377,10 @@ function ciWindowsObservationalGates(): Gate[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function lintGate(): Gate {
|
function lintGate(): Gate {
|
||||||
const threadBound = oxlintThreadBound()
|
|
||||||
if (threadBound !== undefined) {
|
|
||||||
return pnpmExec('lint', ['oxlint', '.', `--threads=${threadBound}`], {
|
|
||||||
label: 'lint',
|
|
||||||
env: { GOMAXPROCS: threadBound },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return pnpmScript('lint', 'lint')
|
|
||||||
}
|
|
||||||
|
|
||||||
function oxlintThreadBound(): string | undefined {
|
|
||||||
const raw = process.env.DSH_OXLINT_THREADS
|
const raw = process.env.DSH_OXLINT_THREADS
|
||||||
if (raw === undefined || raw === '') return undefined
|
return pnpmScript('lint', 'lint', raw === undefined || raw === ''
|
||||||
const parsed = Number.parseInt(raw, 10)
|
? {}
|
||||||
if (!Number.isSafeInteger(parsed) || parsed < 1 || String(parsed) !== raw) {
|
: { displayCommand: `DSH_OXLINT_THREADS=${raw} pnpm run lint` })
|
||||||
throw new Error(`run-gates: DSH_OXLINT_THREADS must be a positive integer, got ${JSON.stringify(raw)}.`)
|
|
||||||
}
|
|
||||||
return raw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function coverageGate(): Gate {
|
function coverageGate(): Gate {
|
||||||
|
|||||||
28
scripts/run-oxlint.spec.ts
Normal file
28
scripts/run-oxlint.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { resolveOxlintInvocation } from './run-oxlint.ts'
|
||||||
|
|
||||||
|
describe('Oxlint invocation', () => {
|
||||||
|
it('preserves the ordinary default invocation', () => {
|
||||||
|
expect(resolveOxlintInvocation(['.'], { PATH: '/bin' })).toEqual({
|
||||||
|
args: ['.'],
|
||||||
|
env: { PATH: '/bin' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('bounds both worker pools from one setting', () => {
|
||||||
|
expect(resolveOxlintInvocation(['.', '--fix'], { DSH_OXLINT_THREADS: '4', GOMAXPROCS: '12' })).toEqual({
|
||||||
|
args: ['.', '--fix', '--threads=4'],
|
||||||
|
env: { DSH_OXLINT_THREADS: '4', GOMAXPROCS: '4' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it.each(['0', '-1', '1.5', 'auto'])('rejects invalid worker bound %s', (value) => {
|
||||||
|
expect(() => resolveOxlintInvocation(['.'], { DSH_OXLINT_THREADS: value }))
|
||||||
|
.toThrow('DSH_OXLINT_THREADS must be a positive integer')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects a competing direct worker bound', () => {
|
||||||
|
expect(() => resolveOxlintInvocation(['.', '--threads=2'], { DSH_OXLINT_THREADS: '4' }))
|
||||||
|
.toThrow('use DSH_OXLINT_THREADS instead')
|
||||||
|
})
|
||||||
|
})
|
||||||
46
scripts/run-oxlint.ts
Normal file
46
scripts/run-oxlint.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { spawnSync } from 'node:child_process'
|
||||||
|
import { resolve } from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
const oxlintCli = fileURLToPath(new URL('../node_modules/oxlint/bin/oxlint', import.meta.url))
|
||||||
|
|
||||||
|
/** Complete Oxlint child-process arguments and environment. */
|
||||||
|
export interface OxlintInvocation {
|
||||||
|
readonly args: readonly string[]
|
||||||
|
readonly env: NodeJS.ProcessEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the repository worker bound to both Oxlint backends.
|
||||||
|
* @param args - Oxlint CLI arguments requested by the caller.
|
||||||
|
* @param env - Environment inherited by the Oxlint process.
|
||||||
|
* @returns the complete CLI arguments and child environment.
|
||||||
|
*/
|
||||||
|
export function resolveOxlintInvocation(args: readonly string[], env: NodeJS.ProcessEnv): OxlintInvocation {
|
||||||
|
const raw = env.DSH_OXLINT_THREADS
|
||||||
|
if (raw === undefined || raw === '') return { args: [...args], env: { ...env } }
|
||||||
|
const parsed = Number.parseInt(raw, 10)
|
||||||
|
if (!Number.isSafeInteger(parsed) || parsed < 1 || String(parsed) !== raw) {
|
||||||
|
throw new Error(`run-oxlint: DSH_OXLINT_THREADS must be a positive integer, got ${JSON.stringify(raw)}.`)
|
||||||
|
}
|
||||||
|
if (args.some(arg => arg === '--threads' || arg.startsWith('--threads='))) {
|
||||||
|
throw new Error('run-oxlint: use DSH_OXLINT_THREADS instead of passing --threads directly.')
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
args: [...args, `--threads=${raw}`],
|
||||||
|
env: { ...env, GOMAXPROCS: raw },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function main(): void {
|
||||||
|
const invocation = resolveOxlintInvocation(process.argv.slice(2), process.env)
|
||||||
|
const result = spawnSync(process.execPath, [oxlintCli, ...invocation.args], {
|
||||||
|
env: invocation.env,
|
||||||
|
stdio: 'inherit',
|
||||||
|
})
|
||||||
|
if (result.error !== undefined) throw result.error
|
||||||
|
process.exitCode = result.status ?? 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const entrypoint = process.argv[1]
|
||||||
|
if (entrypoint !== undefined && resolve(entrypoint) === fileURLToPath(import.meta.url)) main()
|
||||||
Reference in New Issue
Block a user