Skip to content

prerelease-link.sh: has_npm() is root-only, so a Maven- or pip-rooted repo skips npm entirely and link reports a false success #329

Description

@dmealing

Found while evaluating 0.24.0-rc.3 in an adopter project. Adjacent to #325 but a different code path — see "Not a duplicate of #325" below.

Summary

Ecosystem detection is root-only:

has_npm()   { [ -f "$PROJECT/package.json" ]; }

In a repo whose root is not a JS project — a Maven- or pip-rooted monorepo with JS apps in sub-directories — this returns false, so link skips npm entirely: no .npmrc, no repin, no lockfile handling, and npm is not listed in the detected ecosystems.

link then reports success. Every @metaobjectsdev/* dependency silently stays on the previously-released version, so the adopter believes they are testing the RC on the TS side when they are not — a false green, which is worse for a pre-release evaluation than a hard failure.

Observed

Repo root has pom.xml and no package.json; three independent JS install roots underneath, each with its own bun.lock, collectively depending on 16 @metaobjectsdev/* packages.

$ prerelease-link.sh link --version 0.24.0-rc.3
── linking <repo-root> → <registry> (owner <owner>) ──
  pinning vendor dependencies to 0.24.0-rc.3
  repinned com.metaobjects dependencies to 7.24.0-rc.3 (via <metaobjects.version>)
✓ linked. Install/restore, then iterate:
  maven  mvn -U compile

Only maven is listed. All 16 npm packages remain on the prior minor.

The tell

repin_npm() already handles this correctly — it does not use $PROJECT/package.json, it recurses:

find "$PROJECT" -name package.json -not -path '*/node_modules/*' ...

So the repin logic is nested-aware and only the gate in front of it is root-only. Running link --project <sub> against each JS root individually works perfectly and pins all 16.

Not a duplicate of #325

#325 is the case where npm is detected (root package.json exists) and sub-projects get repinned without receiving an .npmrc. Here npm is never detected at all, so nothing is repinned and no .npmrc is written anywhere — a silent no-op rather than a notarget install failure. Different cause (has_npm gate vs link_npm write scope), different symptom, and #325's fix (write an .npmrc per changed manifest) does not help if the gate never opens.

The two together suggest the underlying assumption worth revisiting: "the project has one root, and it is $PROJECT."

Suggested fix

Make detection match the recursion that already exists downstream — detect npm if any non-ignored package.json is found, using the same find and prune rules as repin_npm. The same argument applies to has_py (root-only pyproject.toml / requirements*.txt); has_nuget already recurses via find, so it is the odd one out in the right direction.

Workaround for adopters today

Run link once per JS root:

for p in <app> <console> <scripts/docs>; do
  prerelease-link.sh link --version 0.24.0-rc.3 --project "$p"
done

Each run correctly writes .npmrc, repins that manifest, and adds the local git exclude. Remember that unlink must then also be run once per root.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions