Skip to content

fix(core): support aliased workspace package dependencies - #36686

Draft
leosvelperez wants to merge 9 commits into
masterfrom
gh-36630
Draft

fix(core): support aliased workspace package dependencies#36686
leosvelperez wants to merge 9 commits into
masterfrom
gh-36630

Conversation

@leosvelperez

Copy link
Copy Markdown
Member

Current Behavior

pnpm workspace aliases ("alias": "workspace:pkg@range") and npm aliases targeting workspace packages ("alias": "npm:pkg@range") are not resolved anywhere in Nx:

  • The project graph resolves package.json dependencies by manifest key only, so aliased entries produce no project dependency edges (or edges to the wrong project when the alias key matches another package's name). Everything downstream of the graph inherits the miss: affected, task pipelines, release ordering.
  • nx release never sees aliased entries: dependents are not version-bumped, and with preserveLocalDependencyProtocols: false an entry whose key names package A but whose value aliases package B is overwritten with A's version, silently retargeting the dependency to the registry package A.
  • Generated package.json files (generatePackageJson, createPackageJson) drop or corrupt aliased entries.
  • The @nx/dependency-checks lint rule reports aliased dependencies as missing and "fixes" them into duplicates.
  • Pruned lock file generation fails for aliased entries: the pruner throws and createLockFile silently falls back to the stale root lock file; the pnpm/npm stringifiers throw or drop the entries; the prune-lockfile executor rewrites aliases to a workspace_modules dir that never exists; copy-workspace-modules never copies the aliased targets.

Expected Behavior

Aliased entries resolve to the workspace package they reference, everywhere a plain entry does:

  • Graph edges point at the aliased target (npm aliases match under range satisfaction, mirroring package manager semantics), and an alias targeting a nonexistent workspace package fails with a scoped project-graph error that suggests close names.
  • nx release reads the inner range of aliased entries (so version.versionPrefix: "auto" preserves each entry's own prefix), updates every entry referencing a bumped dependency while keeping alias keys and targets intact, preserves workspace: aliases by default, and converts them to registry-compatible npm:pkg@version aliases when preserveLocalDependencyProtocols is false.
  • Generated package.json files emit aliased entries as "alias": "npm:target@version" under their original keys.
  • @nx/dependency-checks validates aliased entries against the target package and inner range.
  • Pruned lock files and deploy outputs agree for pnpm and npm: manifests keep the alias key pointing at file:./workspace_modules/<target>, lock files link the alias key to the target's module dir (nesting under the consumer when an alias key collides with a canonical package name; name-keyed nodes for v1/v2 dependencies sections), and copy-workspace-modules copies the aliased targets.

Related Issue(s)

Fixes #36630

Implementation Details

A single specifier parser (parseDependencySpecifier) and matching policy (matchDependencyToWorkspacePackage, resolveWorkspaceDependencyTarget) back every consumer: graph resolution, release version actions, dependency-checks, generated manifests, lock file pruning/stringification, and the deploy executors. The graph attaches per-project manifest descriptors (metadata.js.packageDependencies) so consumers see alias targets without re-reading manifests.

Notes and boundaries:

  • Graph metadata is recomputed on every graph build and the plugin file cache never persists the new field, so no cache version bump is needed. The only cross-version reuse path is NX_FORCE_REUSE_CACHED_GRAPH, where an older cached graph simply lacks the field and behaves like pre-fix Nx.
  • Measured overhead of descriptor collection: 3-6ms per 1000 manifests (11 deps each), with no additional manifest reads.
  • Yarn pruned lock files list no workspace-module entries for plain workspace dependencies today; aliased entries now behave identically to plain ones there, and yarn installs regenerate the entries. Revisit if yarn deploys move to --frozen-lockfile semantics.
  • Bun deploys only write package.json (no lock file generation, pre-existing), which now carries the rewritten alias entries.
  • Rangeless aliases (npm:pkg) keep floating: release never pins them, and they match workspace packages regardless of version.

View Polygraph session ↗

@netlify

netlify Bot commented Aug 17, 2026

Copy link
Copy Markdown

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit d1f8236
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/6a86f00cf1e1070008add75e
😎 Deploy Preview https://deploy-preview-36686--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.

@netlify

netlify Bot commented Aug 17, 2026

Copy link
Copy Markdown

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit d1f8236
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/6a86f00c5d0c820008698ad2
😎 Deploy Preview https://deploy-preview-36686--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.

@nx-cloud

nx-cloud Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit d1f8236

Command Status Duration Result
nx affected --targets=lint,oxlint,test,build,e2... ✅ Succeeded 13m 23s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 4s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 1m 4s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 21s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 13s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-20 12:34:39 UTC

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

@nx-cloud nx-cloud Bot left a comment

Copy link
Copy Markdown
Contributor

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

leosvelperez and others added 9 commits August 20, 2026 14:15
…oject metadata

Parse package.json dependency specifiers (plain, workspace: and npm:
aliases) and record the entries that resolve to workspace packages as
metadata.js.packageDependencies descriptors, in both package-json
plugins. The descriptors are recomputed on every run and attached
outside the per-file plugin cache entries, so target package changes
are reflected even when the source manifest is served from the cache.

Groundwork for resolving workspace and npm package aliases in the
project graph and Nx Release.
…jects

"alias": "workspace:pkg@range" entries created no graph edge (or an edge
to the project matching the manifest key), and npm:pkg@range aliases
never resolved to workspace projects. The locator now parses the
specifier first: a target-bearing workspace alias always links the
requested package when it exists (pnpm links even on a range mismatch),
and an npm alias links it when the workspace version satisfies the
range; plain and file: entries behave as before.

A workspace alias naming no workspace package is rejected by every
package manager at install time; graph construction now fails it
per-file with the dependency key, requested package, a close-name
suggestion, and a repair hint that preserves registry intent (replace
workspace: with npm:). Only manifests the package manager installs (the
root or workspace-globs matches) are validated, and packages outside
them never satisfy an alias. Task commands surface these messages
without --verbose, matching the handleErrors rendering.

Pruned lock file creation threw "package was not found in the root lock
file" for aliased entries; it now keeps the alias key and traverses the
requested workspace target, dispatching workspace-protocol entries
before external lookups so a same-named external node cannot shadow the
local target.

parseDependencySpecifier and the ProjectPackageDependencies type are
exposed through @nx/devkit/internal for first-party consumers.
Generated package.json files for buildable projects dropped internal
dependencies referenced through workspace: or npm: aliases, or emitted
them under the target's canonical package name instead of the alias key
the consuming code imports. The buildable-deps path now emits every
manifest key referencing the target, expressing aliases in the
registry-installable npm:<target>@<version> form using the built
output's version.
An internal dependency referenced through a workspace: or npm: alias
made the rule report the target's canonical package name as missing and
the alias key as obsolete. Expected dependency names now use the
manifest identity, so aliased entries are neither missing nor obsolete
while genuinely unused aliases are still reported.

Version mismatch checks compare an npm: alias against its inner range,
and the fixer only nests a root manifest value inside npm:<target>@...
when it is a plain registry range, falling back to the installed version
otherwise. A key reused across dependency collections resolves to the
production entry, matching how the graph builds edges.
…uring versioning

Nx Release located manifest dependency entries only by the target package
name, so entries like "alias": "workspace:pkg@^1.0.0" or "alias":
"npm:pkg@^1.0.0" were invisible: their versions were never bumped when the
target package was versioned, VersionData reported no version spec for
them, and an entry whose key names one workspace package while aliasing
another was treated as belonging to the wrong package and could be
rewritten to a plain version, silently retargeting it to a registry
package of the same name.

Dependency entries are now located by parsing each specifier with the
shared dependency specifier parser: an entry references a package when its
key or its aliased target names it. Reads return the alias inner range so
the versionPrefix logic operates on the actual version, and writes update
every referencing entry, preserving the alias key and target and
rewriting only the inner range. Workspace aliases are left untouched when
preserveLocalDependencyProtocols is enabled (the default) and are
converted to registry-compatible npm aliases otherwise.

Two related defects surfaced by the change are also fixed: the
dependency-update counter is now tracked per manifest so a preserved
entry in one manifest no longer aborts processing of later manifests
(previously it could also skip catalog updates and go negative when a
dependency appeared in several collections), and versionPrefix "auto" now
preserves each declaration's own prefix instead of stamping the prefix of
the first located entry onto all of them.
…iles

Aliased manifest entries ("alias": "workspace:pkg@range" or "npm:pkg@range")
broke pruned lock file generation. The pruner only resolved workspace:
aliases, so npm: aliases targeting workspace packages failed the external
lookup and createLockFile silently fell back to the stale root lock file.
The pnpm and npm stringifiers matched workspace packages by the raw
manifest key, so aliased entries threw, were dropped from the output, or
were pointed at a module dir named after the alias instead of the target
package.

Entry resolution now routes through a shared
resolveWorkspaceDependencyTarget helper (exported via @nx/devkit/internal):
aliasing specifiers resolve by their requested target under the same
range-satisfaction policy the graph uses, other entries keep the key-based
behavior. The pruner keeps npm-alias-to-workspace entries and traverses the
target. The pnpm stringifier links alias keys to the target's module dir in
the root snapshot and resolves aliased entries in the transitive importer
walks. The npm stringifier links alias keys per referencing entry, nesting
a link under its consumer when an alias key collides with a canonical
package name, no longer mutates the shared manifest object, and emits
name-keyed v1 dependency nodes instead of v3 path keys for lockfileVersion
1 and 2 outputs.
The prune-lockfile executor rewrote aliased workspace dependencies to
file:./workspace_modules/<alias-key>, a directory that never exists
(modules are copied under their package names), and left npm: aliases
pointing at the registry. The copy-workspace-modules executor matched
workspace packages by the raw manifest key, so aliased dependencies were
never copied and nested aliased entries kept their workspace: specifiers
in the copied manifests.

Both executors now resolve entries through
resolveWorkspaceDependencyTarget: alias keys are kept and pointed at the
target package's module dir, and the target modules are copied.
Co-authored-by: leosvelperez <leosvelperez@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support workspace package aliases consistently in the project graph and Nx Release

1 participant