Skip to content

fix(core): use workspace root for package manager detection in script targets#35550

Merged
FrozenPandaz merged 6 commits intomasterfrom
fix/package-json-pm-detection
May 4, 2026
Merged

fix(core): use workspace root for package manager detection in script targets#35550
FrozenPandaz merged 6 commits intomasterfrom
fix/package-json-pm-detection

Conversation

@FrozenPandaz
Copy link
Copy Markdown
Contributor

Current Behavior

readTargetsFromPackageJson (in packages/nx/src/utils/package-json.ts) receives a workspaceRoot argument but never passes it to package manager detection:

for (const script of includedScripts) {
  packageManagerCommand ??= getPackageManagerCommand(); // ← no workspaceRoot
  res[script] = buildTargetFromScript(script, scripts, packageManagerCommand);
}

Two consequences:

  1. Wrong package managerdetectPackageManager() defaults dir = '', so the lockfile probe runs in the CWD, not the workspace. When that finds nothing it falls back to npm_config_user_agent, so the inferred runCommand (npm run X vs pnpm run X vs yarn X) on script targets ends up depending on whoever invoked the nx process rather than on the workspace's actual lockfile.

  2. Module-level cachelet packageManagerCommand (cleared with ??=) memoizes the first detection result across all subsequent calls in the process. So even if the first call had the right workspaceRoot, every later call inherits that detection regardless of its workspaceRoot. This is also why packages/nx/src/plugins/package-json/create-nodes.spec.ts had four pre-existing snapshot failures locally (pnpm run … instead of the expected npm run …) — the first test in any process locked detection to the host's PM.

This is a follow-up to #35116, which moved package manager detection into the createNodes callback for the inferred plugins but missed this code path.

Expected Behavior

  • Drop the module-level cache.
  • Thread workspaceRoot into both detectPackageManager and getPackageManagerCommand, so the lockfile probe runs in the right directory.
if (includedScripts.length > 0) {
  const packageManagerCommand = getPackageManagerCommand(
    detectPackageManager(workspaceRoot),
    workspaceRoot
  );
  for (const script of includedScripts) {
    res[script] = buildTargetFromScript(script, scripts, packageManagerCommand);
  }
}

The packages/nx/src/plugins/package-json/create-nodes.spec.ts fixture now seeds package-lock.json into memfs in a beforeEach, matching the pattern #35116 established for plugin specs. Without the lockfile the detector still falls back to the env var; with it, every test deterministically picks npm, matching the existing snapshots.

Verification

Before this PR: 4 failures in packages/nx/src/plugins/package-json/create-nodes.spec.ts:

✕ should build projects from package.json files
✕ should store js package metadata
✕ should add a script target if the sibling project.json file does not exist
✕ should add a script target if the sibling project.json exists but does not have a conflicting target

Tests: 4 failed, 7 passed, 11 total

After this PR:

Tests: 11 passed, 11 total

Related Issue(s)

Follow-up to #35116.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 3, 2026

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 7333e01
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/69f9027781492d0008ebc9ee
😎 Deploy Preview https://deploy-preview-35550--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 3, 2026

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit 7333e01
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/69f90277ae44b50008899596
😎 Deploy Preview https://deploy-preview-35550--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented May 3, 2026

View your CI Pipeline Execution ↗ for commit cbe8994

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 19m 51s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 3s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 16s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 23s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 8s View ↗
nx affected -t e2e-macos-local --parallel=1 --b... ✅ Succeeded 2m 33s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-04 20:56:36 UTC

@FrozenPandaz FrozenPandaz marked this pull request as ready for review May 4, 2026 13:25
@FrozenPandaz FrozenPandaz requested a review from a team as a code owner May 4, 2026 13:25
@FrozenPandaz FrozenPandaz requested a review from JamesHenry May 4, 2026 13:25
Copy link
Copy Markdown
Member

@AgentEnder AgentEnder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to push back if you want, but I think this'd be cleaner if we moved where the package manager command was fetched. Previously it was once per workspace, now it'd be once per project etc and it adds an if around the for loop that wouldn't otherwise be needed.

Its definitely pretty minor, but I'd like it changed.

Comment thread packages/nx/src/utils/package-json.ts Outdated
Comment on lines +233 to +236
const packageManagerCommand = getPackageManagerCommand(
detectPackageManager(workspaceRoot),
workspaceRoot
);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a minor degradation, we'll be detecting the package manager / getting the command set once per project instead of once per module lifetime. I'd prefer to move this outside of here, unless you see a good reason to re-detect when reading the targets per package.json...

If we don't want to have it at module level, I'd be fine with passing it inot the readTargetsFromPackageJson fn or something

FrozenPandaz added a commit that referenced this pull request May 4, 2026
compute packageManagerCommand once per workspace at the createNodesV2
entry point and thread it through createNodeFromPackageJson and
buildProjectConfigurationFromPackageJson into readTargetsFromPackageJson,
instead of detecting per package.json read.

addresses review feedback on #35550.
… targets

readTargetsFromPackageJson received a workspaceRoot argument but never
passed it to detectPackageManager / getPackageManagerCommand. Detection
fell back to npm_config_user_agent, so the inferred npm run command on
script targets depended on whoever invoked the process rather than on
the actual workspace lockfile. The result was visible to users running
nx in a workspace whose lockfile disagrees with their invoking shell's
package manager.

A module-level `let packageManagerCommand` cache also kept the first
detection result across all subsequent calls in the same process,
masking changes in workspaceRoot.

Drops the module-level cache and threads workspaceRoot through to
detection. The package-json create-nodes spec now seeds a
package-lock.json into memfs (matching #35116's pattern for plugin
specs) so the detector picks `npm` deterministically.

Follow-up to #35116.
compute packageManagerCommand once per workspace at the createNodesV2
entry point and thread it through createNodeFromPackageJson and
buildProjectConfigurationFromPackageJson into readTargetsFromPackageJson,
instead of detecting per package.json read.

addresses review feedback on #35550.
@FrozenPandaz FrozenPandaz force-pushed the fix/package-json-pm-detection branch from d369301 to 47a0c4e Compare May 4, 2026 18:47
…lities

getRunNxBaseCommand and preparePackageInstallation called
detectPackageManager() with no argument, defaulting to CWD-based probing.
Pass workspaceRoot so the lockfile probe runs in the right directory and
avoid env-var fallback. Also dedupe back-to-back getPackageManagerCommand
calls in preparePackageInstallation.
Inferred plugins were calling getLockFileName(detectPackageManager(...))
per project inside createNodesInternal. Hoist the detection to the
createNodes callback alongside the existing pmc computation, derive
both pmc and lockFileName from a single detectPackageManager call, and
thread lockFileName through.

Affects: angular, cypress, detox, expo, js/typescript, next, nuxt,
playwright, react-native, remix, rollup, rsbuild, rspack, storybook,
webpack.
nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

…n entry

the legacy nx-all-package-jsons-plugin entry point at
packages/nx/plugins/package-json.ts was missed in the earlier hoist;
update it to compute the packageManagerCommand once and pass it as the
required 5th argument to createNodeFromPackageJson, matching createNodesV2.

fixes the TS2554 error in nx:build-base on CI.
@FrozenPandaz FrozenPandaz enabled auto-merge (squash) May 4, 2026 19:24
Copy link
Copy Markdown
Contributor

@nx-cloud nx-cloud Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nx Cloud has identified a flaky task in your failed CI:

🔂 Since the failure was identified as flaky, we triggered a CI rerun by adding an empty commit to this branch.

Nx Cloud View detailed reasoning in Nx Cloud ↗


🎓 Learn more about Self-Healing CI on nx.dev

@FrozenPandaz FrozenPandaz merged commit ed44fb3 into master May 4, 2026
16 checks passed
@FrozenPandaz FrozenPandaz deleted the fix/package-json-pm-detection branch May 4, 2026 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants