Skip to content

chore(devkit): build devkit to local dist and use nodenext#34946

Merged
FrozenPandaz merged 16 commits into
masterfrom
devkit-dist
May 2, 2026
Merged

chore(devkit): build devkit to local dist and use nodenext#34946
FrozenPandaz merged 16 commits into
masterfrom
devkit-dist

Conversation

@FrozenPandaz
Copy link
Copy Markdown
Contributor

@FrozenPandaz FrozenPandaz commented Mar 21, 2026

Current Behavior

@nx/devkit builds to <workspaceRoot>/dist/packages/devkit/ — outside its own package directory. Other packages reach into that shared workspace dist/ to consume devkit, and consumers using moduleResolution: nodenext need a custom Module._resolveFilename hack to find it.

This is inconsistent with nx itself, which already builds to packages/nx/dist/ (#34111).

Expected Behavior

@nx/devkit builds to packages/devkit/dist/ and is consumed via a standard exports map. Workspace consumers resolve through normal pnpm symlinks; the @nx/nx-source condition still lets in-repo code resolve to .ts source during dev.

This unblocks the rest of the workspace from migrating in the same direction (a dist-build-migration Claude skill is included as a per-package playbook).

How to review

The PR has 307 files but only 3 categories of work. Most of the diff is mechanical.

1. The actual migration — review carefully (~10 files)

Devkit package config and the @nx/nx-source condition wiring:

  • packages/devkit/package.jsonexports map, typesVersions, files, type: commonjs, main/types repointed to dist/
  • packages/devkit/tsconfig.lib.jsonoutDir: dist, nodenext module/resolution
  • packages/devkit/project.jsonbuild-base outputs, nx-release-publish.packageRoot, manifestRootsToUpdate
  • packages/devkit/internal.ts — re-exports src/utils/* and src/generators/* symbols (replaces the @nx/devkit/src/* deep-import pattern)
  • packages/devkit/eslint.config.mjs — ignore dist
  • packages/devkit/{README.md → readme-template.md} — README is now generated, gitignored
  • .gitignore — ignore the generated packages/devkit/README.md
  • scripts/nx-release.ts — devkit's package.json now lives at packages/devkit/package.json, not dist/packages/devkit/package.json
  • scripts/patched-jest-resolver.js — adds @nx/nx-source condition

2. Mass import rewrites — already marked viewed (~205 files)

Every internal import of @nx/devkit/src/utils/<x> or @nx/devkit/src/generators/<x> was rewritten to @nx/devkit/internal:

-import { foo } from '@nx/devkit/src/utils/bar';
+import { foo } from '@nx/devkit/internal';

I marked these files as viewed in the GitHub review UI to clear them from the unread queue. They're across nearly every plugin (angular, react, next, vite, webpack, rspack, expo, jest, etc.).

3. Follow-on cleanups (~10 files)

These appear in their own commits and are easy to review individually:

  • cleanup(angular-rspack) — removes the patchDevkitRequestPath runtime hack from 14 example configs; devkit now resolves through standard node_modules (the MF patch stays — module-federation isn't migrated yet).
  • cleanup(devkit) typedoc — drops dead path mappings + redundant include manipulation in astro-docs/src/plugins/utils/typedoc/typedoc.ts (verified docs build is byte-identical with/without the removed config).
  • cleanup(devkit) eslint ignores — drops '**/*.d.ts' from packages/devkit/eslint.config.mjs since .d.ts files only emit to dist/ (already ignored).
  • fix(angular) eslint quote — quote-agnostic regex in e2e/angular/src/projects-linting.test.ts so the test still disables prefer-standalone after the angular-eslint generator switched to double quotes (latent bug surfaced by CI).
  • fix(testing) jest migration — removes a stray unused @nx/devkit/internal import.
  • e2e test falloute2e/{angular,next}/src/*.test.ts lose access to @nx/devkit/src/utils/string-utils (e2e tests can't use /internal); inline equivalents using public names() API. e2e/nx-build/src/nx-build.test.ts updates the expected output path.

Local verification

  • pnpm nx run-many -t test,build,lint -p devkit
  • pnpm nx run astro-docs:build ✓ — devkit reference pages still generate (148 markdown files; identical to a build with the path mappings re-added as a control)
  • pnpm nx run-many -t build -p examples-angular-rspack-csr-css,examples-angular-rspack-ssr-css,examples-angular-rspack-zoneless-csr-css,examples-angular-rspack-mf-host,examples-angular-rspack-mf-remote --skip-nx-cache ✓ — examples build without the devkit patch

Known follow-up (not in this PR)

  • scripts/nx-release.ts still has hackFixForDevkitPeerDependencies() (a band-aid from fix(devkit): restore peer dep range to 2 majors #32406 that re-adds <= to devkit's nx peer-dep range after nx release version strips it). The proper fix is to set preserveMatchingDependencyRanges: true in packages/devkit/project.json and delete the hack — that needs a release dry-run to verify, so it's queued separately.

Related Issue(s)

Follow-up to #34111.

@netlify
Copy link
Copy Markdown

netlify Bot commented Mar 21, 2026

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit b8afee4
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/69f52ff4243dda00094f3b58
😎 Deploy Preview https://deploy-preview-34946--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 Mar 21, 2026

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit b8afee4
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/69f52ff4be87a500075b2311
😎 Deploy Preview https://deploy-preview-34946--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 Mar 21, 2026

View your CI Pipeline Execution ↗ for commit b8afee4

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 36m 48s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 6s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 17s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 26s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 26s View ↗
nx affected -t e2e-macos-local --parallel=1 --b... ✅ Succeeded 35m 53s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-01 23:43:17 UTC

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

@FrozenPandaz FrozenPandaz force-pushed the devkit-dist branch 3 times, most recently from 4413af3 to 712c14f Compare March 27, 2026 19:31
nx-cloud[bot]

This comment was marked as outdated.

@FrozenPandaz FrozenPandaz force-pushed the devkit-dist branch 2 times, most recently from d61c9ee to 5f5f694 Compare March 27, 2026 21:43
nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

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 ↗

🔔 Heads up, your workspace has pending recommendations ↗ to auto-apply fixes for similar failures.


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

@FrozenPandaz FrozenPandaz force-pushed the devkit-dist branch 3 times, most recently from 1281623 to e0ac70a Compare April 21, 2026 03:41
@FrozenPandaz FrozenPandaz force-pushed the devkit-dist branch 2 times, most recently from eb51156 to 8fe5520 Compare May 1, 2026 18:10
FrozenPandaz and others added 16 commits May 1, 2026 18:56
Mirrors the nx package change from #34111. The devkit package now builds
to packages/devkit/dist/ instead of dist/packages/devkit/.

Key changes:
- tsconfig.lib.json: outDir to dist, nodenext module/moduleResolution
- package.json: added type:commonjs, exports map with @nx/nx-source condition
- project.json: updated build targets for local dist paths
- Added .npmignore to exclude source from publish
- Renamed README.md to readme-template.md (generated README is gitignored)
- Updated all external references (nx-release.ts, angular-rspack patch)

chore(core): use npm-published nx packages for workspace-plugin

Remove workspace:* links to @nx/devkit, @nx/js, @nx/plugin, nx, and
@nx/conformance from workspace-plugin. These are already installed as
npm packages in root devDependencies, so workspace-plugin resolves them
via hoisting. This breaks the circular dependency where workspace-plugin
needed devkit built before it could load.

Also added @nx/plugin to root devDependencies and disabled
dependency-checks lint rule for workspace-plugin.

fix(devkit): add explicit exports for directory-based modules

The wildcard export pattern `./src/utils/*` resolves to
`./dist/src/utils/catalog.js` but the actual file is at
`./dist/src/utils/catalog/index.js`. Added explicit entries for
catalog, async-iterable, and plugin-migrations directories.

fix(devkit): update astro-docs devkit entry point paths

The d.ts files now live in packages/devkit/ instead of
dist/packages/devkit/ after the local dist migration.

fix(angular): disable prefer-standalone lint rule for lib in e2e test

The test only disabled @angular-eslint/prefer-standalone for app1 but
not lib1, causing lint failures when angular-eslint enables the rule
by default.

fix(devkit): simplify exports map to avoid subpath resolution issues

fix(devkit): add explicit exports for directory modules and nested paths

cleanup(devkit): move src subpath imports to devkit internal

All internal imports like @nx/devkit/src/utils/catalog are now
re-exported from @nx/devkit/internal. This removes the need for
src subpath exports in package.json, which caused issues with
Node.js exports map resolution for nested paths.

fix(devkit): update remaining e2e test imports from devkit src to internal

fix(core): add nx-source condition to shared jest resolver

fix(misc): inline string utils in e2e tests instead of importing from devkit internal

chore(repo): remove main
…eration

The devkit tsconfig.lib.json excludes dist/ and only includes *.ts and
src/**/*.ts. Since TypeDoc copies this tsconfig to a temp directory, the
dist/index.d.ts entry point wasn't in scope. Add an absolute path include
for devkit's dist .d.ts files and remove 'dist' from the exclude list.
Remove dead path mappings and the in-memory include manipulation.
TypeDoc receives absolute entryPoints (packages/devkit/dist/*.d.ts)
and resolves nx subpaths via workspace-linked node_modules + nx's
exports map, so neither is needed.
Without this, TypeDoc errors with 'entry point is not referenced by
the files or include option in your tsconfig' and generates zero
devkit reference pages.
The @angular-eslint generator now emits double-quoted rule names, so
the existing single-quote string replacement no-ops. Use a regex that
matches either quote style and apply it to both app1 and lib1.
devkit now builds to packages/devkit/dist with a proper exports map and
is reachable through workspace-linked node_modules. require('@nx/devkit')
resolves correctly from inside @nx/angular-rspack without the manual
Module._resolveFilename interception.

The patchModuleFederationRequestPath helper stays for now — module
federation hasn't been migrated to local dist yet.
Declaration files only emit to dist/ (declarationDir in tsconfig.lib.json),
which is already ignored. The wildcard pattern is dead.
findPluginForConfigFile was added but never referenced.
Comment on lines +82 to +86
"./internal": {
"@nx/nx-source": "./internal.ts",
"types": "./dist/internal.d.ts",
"default": "./dist/internal.js"
},
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.

We are gonna break a ton of community plugins if we dont leave the export path for the project name and root utils (and maybe the create nodes hash utils) as exported just as an FYI

@FrozenPandaz FrozenPandaz merged commit 59e2e51 into master May 2, 2026
25 checks passed
@FrozenPandaz FrozenPandaz deleted the devkit-dist branch May 2, 2026 02:42
FrozenPandaz added a commit that referenced this pull request May 2, 2026
## Current Behavior

`@nx/devkit`'s `build-base` target overrode `outputs` in `project.json`:

```json
"outputs": [
  "{projectRoot}/dist/**/*.{js,cjs,mjs,d.ts}",
  "{projectRoot}/*.d.ts",
  "{projectRoot}/src/**/*.d.ts"
]
```

This override is missing `tsconfig.tsbuildinfo`. Any downstream task
whose inputs include `dependentTasksOutputFiles:
"**/*.{d.ts,d.cts,d.mts,tsbuildinfo}"` (e.g. `docker:build-base`) trips
a sandbox violation: when `tsc --build` walks project references it
reads devkit's tsbuildinfo, but Nx never registered that file as a dep
output, so the read isn't covered by any declared input.

The same gap exists in the `dist-build-migration` Claude skill, so every
package migrated with that playbook would reproduce the violation.

Sandbox report that surfaced this:
https://staging.nx.app/runs/HNBpzgdeNi/task/docker%3Abuild-base/sandbox-report-raw?sandboxReportId=10b903d8-b860-41c7-80b2-756b0541e690

## Expected Behavior

Drop the override entirely. The `@nx/js/typescript` plugin already reads
`outDir` and `tsBuildInfoFile` from `tsconfig.lib.json` and infers a
strictly more complete set of outputs:

```
{projectRoot}/dist/**/*.{js,cjs,mjs,jsx,json,d.ts,d.cts,d.mts}{,.map}
{projectRoot}/dist/tsconfig.tsbuildinfo
```

The inferred set picks up the tsbuildinfo plus
`.cjs`/`.mjs`/`.json`/`.d.cts`/`.d.mts`/`.map` that the manual override
was missing. Verified the violation is resolved:

```
$ pnpm nx show target inputs docker:build-base --check packages/devkit/dist/tsconfig.tsbuildinfo
✓ packages/devkit/dist/tsconfig.tsbuildinfo is an input for docker:build-base (depOutputs)
```

The `dist-build-migration` skill is updated to tell future migrations to
leave `build-base.outputs` to the plugin.

## Related Issue(s)

Follow-up to #34946.
FrozenPandaz added a commit that referenced this pull request May 4, 2026
## Current Behavior

After #34946, `@nx/devkit` ships a strict `exports` map. Deep imports
like `@nx/devkit/src/utils/...` and `@nx/devkit/src/generators/...` are
no longer reachable through Node module resolution. Workspaces upgrading
to `23.x` that reference those paths break with module-resolution errors
at runtime / type-check time.

## Expected Behavior

`nx migrate` runs an automated rewrite that covers the realistic shapes
of these deep imports.

The migration walks every `.ts`/`.tsx`/`.cts`/`.mts` file in the
workspace and:

1. **Buckets named imports by symbol.** Each `@nx/devkit/src/...` import
declaration is parsed via the TypeScript compiler API (lazy-loaded with
`ensurePackage`). Specifiers in the `internal.ts` re-export list go to
`@nx/devkit/internal`; all others go to `@nx/devkit`. Mixed imports
split into two declarations.
2. **Falls back for non-named shapes.** Default imports, namespace
imports, side-effect imports, `require(...)` calls, and dynamic
`import(...)` get the specifier swapped to `@nx/devkit/internal` (the
safe default — it re-exports every previously deep-importable symbol).
3. **Collapses duplicate imports.** A second AST pass groups `import {
... } from '@nx/devkit'` and `import { ... } from '@nx/devkit/internal'`
declarations by `(specifier, isTypeOnly)`, merging each 2+ group into
one declaration with deduplicated specifiers. This handles both the
duplicates the rewrite produced and any pre-existing devkit imports the
user already had.

Edits are stacked via `applyChangesToString` (devkit's offset-tracking
text-mutation helper), then `formatFiles` normalizes formatting.

### Example

Before:
```ts
import { Tree } from '@nx/devkit';
import { dasherize, names } from '@nx/devkit/src/utils/string-utils';
import { addPlugin } from '@nx/devkit/src/utils/add-plugin';
```

After:
```ts
import { Tree, names } from '@nx/devkit';
import { dasherize, addPlugin } from '@nx/devkit/internal';
```

### Tests

29 unit tests cover: single-bucket and mixed-bucket rewrites, `as`
aliases, `import type` and inline `type` modifiers, multi-line imports,
side-effect / default / namespace fallbacks, `require()` and dynamic
`import()` fallbacks, quote-style preservation, pre-existing-import
merge, public/internal independence, value-vs-type segregation,
specifier deduplication, and a sanity test that every name in
`DEVKIT_INTERNAL_SYMBOLS` is bucketed as internal.

A user-facing `update-deep-imports.md` lives next to the implementation;
the astro-docs build picks it up via the existing
`packages/*/src/migrations/**/*.md` input glob.

## Related Issue(s)

Follow-up to #34946.
FrozenPandaz added a commit that referenced this pull request May 6, 2026
)

## Current Behavior

After running `nx-release` locally, `packages/devkit/package.json` is
left modified in the working tree.

The `nx-release.ts` script snapshots and restores the source
`package.json` for every package whose source folder is the publish root
(so the temporary edits made during `nx release version` — real version,
expanded `workspace:*` deps, etc. — don't leak into the working tree).
The list of those packages lives in `packagesToReset` near the top of
`scripts/nx-release.ts`.

When `@nx/devkit` was migrated to publish from its source folder in
#34946, it was not added to that list, so its source `package.json` is
no longer restored.

## Expected Behavior

`packages/devkit/package.json` is restored to its committed contents
after `nx-release` finishes (or is interrupted), matching the behavior
already in place for `nx`, `dotnet`, `maven`, `angular-rspack`, and
`angular-rspack-compiler`.

## Related Issue(s)

N/A — internal release-tooling fix.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

This pull request has already been merged/closed. If you experience issues related to these changes, please open a new issue referencing this pull request.

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators May 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants