From a112d00d3f48b484ed65db2994da5e4d62fc8d33 Mon Sep 17 00:00:00 2001 From: indexzero Date: Mon, 16 Mar 2026 01:17:30 -0400 Subject: [PATCH 1/3] fix(test) resolve cyclonedx binary once instead of using npx per call Concurrent npx invocations race on the shared npx cache, causing ENOTEMPTY on directory renames. Instead, resolve the cyclonedx-npm binary once (via require.resolve or a one-time npm install into a temp prefix) and invoke it directly with node for all subsequent calls. Co-Authored-By: Claude Opus 4.6 --- test/support/parity.js | 55 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/test/support/parity.js b/test/support/parity.js index 643fbbc..7c7a219 100644 --- a/test/support/parity.js +++ b/test/support/parity.js @@ -98,6 +98,46 @@ async function setupAndInstall(packageName, version) { return tmpDir; } +/** + * Resolve the cyclonedx-npm binary once, installing if needed. + * Avoids repeated npx invocations that race on the shared npx cache. + * @returns {Promise} Absolute path to cyclonedx-npm-cli.js + */ +let _cyclonedxBin; +async function getCycloneDXBin() { + if (_cyclonedxBin) return _cyclonedxBin; + + // Try to resolve from node_modules first (e.g. if installed as devDep) + try { + const result = await x('node', [ + '-e', + 'console.log(require.resolve("@cyclonedx/cyclonedx-npm/bin/cyclonedx-npm-cli.js"))' + ]); + if (result.exitCode === 0 && result.stdout.trim()) { + _cyclonedxBin = result.stdout.trim(); + return _cyclonedxBin; + } + } catch { + // fall through to npx install + } + + // Install once into a temp prefix and resolve the binary + const prefix = join(tmpdir(), 'flatlock-cyclonedx'); + const install = await x('npm', ['install', '--prefix', prefix, '@cyclonedx/cyclonedx-npm']); + if (install.exitCode !== 0) { + throw new Error(`Failed to install @cyclonedx/cyclonedx-npm: ${install.stderr}`); + } + _cyclonedxBin = join( + prefix, + 'node_modules', + '@cyclonedx', + 'cyclonedx-npm', + 'bin', + 'cyclonedx-npm-cli.js' + ); + return _cyclonedxBin; +} + /** * Run CycloneDX on a directory * @param {string} dir @@ -105,20 +145,14 @@ async function setupAndInstall(packageName, version) { * @returns {Promise>} Set of name@version strings */ async function runCycloneDX(dir, { lockfileOnly = false } = {}) { - const args = [ - '@cyclonedx/cyclonedx-npm', - '--output-format', - 'JSON', - '--flatten-components', - '--omit', - 'dev' - ]; + const bin = await getCycloneDXBin(); + const args = [bin, '--output-format', 'JSON', '--flatten-components', '--omit', 'dev']; if (lockfileOnly) { args.push('--package-lock-only'); } - const result = await x('npx', args, { + const result = await x('node', args, { nodeOptions: { cwd: dir } }); @@ -217,7 +251,8 @@ export async function getParityResults(packageName, version) { const tmpDir = await setupAndInstall(packageName, version); try { - // Run both on the same lockfile + // Run all three in parallel — safe now that CycloneDX uses a + // pre-resolved binary instead of npx. const [cyclonedxLockfile, cyclonedxNodeModules, flatlock] = await Promise.all([ runCycloneDX(tmpDir, { lockfileOnly: true }), runCycloneDX(tmpDir, { lockfileOnly: false }), From d12ee10052642f1322cd73b181fc653b77c53b93 Mon Sep 17 00:00:00 2001 From: indexzero Date: Mon, 16 Mar 2026 01:21:26 -0400 Subject: [PATCH 2/3] fix(test) align test:coverage glob with test glob test:coverage was missing ./test/**/*.test.js, so verification and parser-specific tests were excluded from coverage runs. Co-Authored-By: Claude Opus 4.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 37e092c..2637677 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ ], "scripts": { "test": "node --test ./test/*.test.js ./test/**/*.test.js", - "test:coverage": "c8 node --test ./test/*.test.js", + "test:coverage": "c8 node --test ./test/*.test.js ./test/**/*.test.js", "build:types": "tsc", "lint": "biome lint src test", "lint:fix": "biome lint --write src test", From 257c4a8324bfd5a27be834d398a4de79dcd17128 Mon Sep 17 00:00:00 2001 From: indexzero Date: Mon, 16 Mar 2026 01:44:38 -0400 Subject: [PATCH 3/3] fix(test) decode fixtures before tests, add parsers to CI coverage Add pretest/pretest:coverage hooks to run fixture decoding so that test/parsers/*.test.js can find decoded fixtures in CI. Align test and test:coverage globs to include test/parsers/*.test.js. Add test:all for running the full suite including verification and monorepo tests locally. Co-Authored-By: Claude Opus 4.6 --- package.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2637677..88d0ddd 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,11 @@ "bin/flatcover.js" ], "scripts": { - "test": "node --test ./test/*.test.js ./test/**/*.test.js", - "test:coverage": "c8 node --test ./test/*.test.js ./test/**/*.test.js", + "pretest": "node test/fixtures/decode.js", + "pretest:coverage": "node test/fixtures/decode.js", + "test": "node --test ./test/*.test.js ./test/parsers/*.test.js", + "test:coverage": "c8 node --test ./test/*.test.js ./test/parsers/*.test.js", + "test:all": "node --test ./test/*.test.js ./test/**/*.test.js", "build:types": "tsc", "lint": "biome lint src test", "lint:fix": "biome lint --write src test",