mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-10-23 15:19:37 +00:00
Some checks are pending
Tests / test (26) (push) Waiting to run
Tests / test-platforms (macos-latest) (push) Waiting to run
Tests / test-platforms (windows-latest) (push) Waiting to run
Tests / desktop (push) Waiting to run
CI / semgrep (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
Tests / test (24) (push) Waiting to run
npm redacts UUID-shaped path segments to `***` in `npm ls --parseable`, so matching each line against the checkout's absolute <root>/node_modules/ prefix found nothing on any path containing a UUID and packaging aborted with the misleading "is the root npm installed?".
The top-level package name is now read by segment position: after the N-th /node_modules segment, N counted off the real root path, which the redaction renames around but cannot move.
Closure verified identical: 160 packages, same names and versions, same 55M, staged from a UUID worktree and from a UUID-free checkout of main.
Reproduced before and after: main aborts from the UUID path, the branch stages 160 packages from that same path.
A non-zero `npm ls` exit still yields its stdout, so an extraneous package in node_modules does not abort staging.
A missing npm_execpath now fails with its own message instead of degrading into the empty-closure one.
Review patch on the branch: the two hand-rolled path scanners collapse to one split('/node_modules/'), and the npm attribution is corrected from 12 to 11 and later, reproduced on npm 11.19.0.
Fixes #1466.
37 lines
2 KiB
JavaScript
37 lines
2 KiB
JavaScript
// Extract the top-level production package names from `npm ls --parseable`
|
|
// output without anchoring on the checkout's absolute path.
|
|
//
|
|
// npm redacts UUID-shaped path segments to `***` in that output, so on a
|
|
// checkout whose path contains a UUID (CI runners, scratch worktrees) any
|
|
// absolute-prefix match finds nothing and staging reports an empty dependency
|
|
// closure (#1466). `npm ls` runs with its working directory at the package
|
|
// root, so every line it prints belongs to this project's tree however the
|
|
// prefix was mangled. The root's own node_modules is identified by POSITION —
|
|
// at however many `/node_modules` segments the real root path contains, the
|
|
// top-level name sits right after — and the redaction renames segments but
|
|
// cannot move one.
|
|
|
|
/** The set of top-level package names (`name` or `@scope/name`) named by the
|
|
* parseable lines, deduped. Nested transitive paths map to their top-level
|
|
* ancestor, mirroring the layout the stage copies into build/cli/node_modules.
|
|
* `rootModulesPath` is the real (unredacted) `<root>/node_modules` path; only
|
|
* the COUNT of its `/node_modules` segments is read, never its text, so a
|
|
* redacted line resolves by position alone. */
|
|
export function topLevelPackagesFromNpmLs(listed, rootModulesPath) {
|
|
// `npm ls --parseable` prints native separators on Windows; normalize both
|
|
// sides before segmenting so Store builds read the same tree.
|
|
const depth = rootModulesPath.replaceAll('\\', '/').split('/node_modules').length - 1
|
|
const names = new Set()
|
|
for (const raw of listed.split('\n')) {
|
|
const segments = raw.trim().replaceAll('\\', '/').split('/node_modules/')
|
|
if (segments.length <= depth) continue
|
|
const match = segments[depth].match(/^(@[^/]+\/[^/]+|[^/]+)/)
|
|
if (!match) continue
|
|
// A truncated tail can leave a bare scope (`@ns`) with no package after
|
|
// it; that is not a package name, and join() below would copy the whole
|
|
// scope directory.
|
|
if (match[1].startsWith('@') && !match[1].includes('/')) continue
|
|
names.add(match[1])
|
|
}
|
|
return names
|
|
}
|