Skip to content

fix(testing): publish @nx/vitest, @nx/cypress, @nx/playwright, @nx/vite from local dist#35743

Merged
FrozenPandaz merged 15 commits into
masterfrom
feature/nxc-3581-m2-epic-migrate-nxvitest-to-local-dist
May 22, 2026
Merged

fix(testing): publish @nx/vitest, @nx/cypress, @nx/playwright, @nx/vite from local dist#35743
FrozenPandaz merged 15 commits into
masterfrom
feature/nxc-3581-m2-epic-migrate-nxvitest-to-local-dist

Conversation

@FrozenPandaz
Copy link
Copy Markdown
Contributor

Current Behavior

@nx/vitest, @nx/cypress, @nx/playwright, and @nx/vite all build to the workspace-root dist/packages/<name>/ directory and publish from there. They use the legacy module: commonjs / moduleResolution: node configuration, an exports map with a ./src/* wildcard (vitest/cypress), and a nx-release-publish.options.packageRoot that points at the workspace-root dist tree. This is the same shape @nx/devkit, @nx/nx, @nx/js, @nx/eslint, and @nx/jest already moved off — but the M2 group of testing/build packages was still on the old layout, blocking downstream packages (like @nx/web) from migrating.

Separately, the 23.0.0 rewrite-internal-subpath-imports codemods shipped with @nx/js, @nx/jest, @nx/eslint (and the new one in this PR for @nx/cypress) walk require(...) / dynamic import(...) / jest.mock(...) call expressions but leave typeof import('@nx/<name>/src/x') type queries untouched. The common typed-runtime-require idiom

const m = require('@nx/<name>/src/x') as typeof import('@nx/<name>/src/x');

would have its runtime arg rewritten to /internal while the type arg kept pointing at the now-removed ./src/* wildcard — leaving external consumers with a TS error after running the migration.

Expected Behavior

Each of the four packages now builds to packages/<name>/dist/ with module: nodenext + composite TypeScript, ships a files field in package.json listing what to publish, declares release.preserveLocalDependencyProtocols: true + manifestRootsToUpdate: ["packages/{projectName}"] in project.json, and publishes straight from the package directory via nx-release-publish.options.packageRoot: packages/{projectName}.

Per-package highlights:

  • @nx/vitest — straight structural migration. No @nx/vitest/src/* consumers, so no codemod migration shipped. assets.json updated to glob src/migrations/**/*.md so prompt-form migrations resolve from ./dist/src/....
  • @nx/cypress — drops the ./src/* wildcard from the exports map, ships a curated internal.ts re-export entry, and ships a 23.0.0-beta.17 symbol-aware codemod (rewrite-internal-subpath-imports) that routes @nx/cypress/src/* imports to either the public @nx/cypress entry (for configurationGenerator, componentConfigurationGenerator, cypressInitGenerator, migrateCypressProject) or @nx/cypress/internal (for everything else). 10 first-party consumers in @nx/angular / @nx/react / @nx/next / @nx/web are codemodded to match.
  • @nx/playwright — straight structural migration; existing exports already enumerated explicit subpaths, no ./src/* wildcard to drop.
  • @nx/vite — structural migration + three executor .impl.ts files switched from const schema = await import('./schema.json') to a top-level import schema from './schema.json' (required under nodenext). Kept ./plugins/nx-tsconfig-paths.plugin / ./plugins/nx-copy-assets.plugin / ./plugins/rollup-replace-files.plugin as explicit public entries because storybook / react-native templates bake them into generated user vite configs.

The fourth commit fixes the typeof import() blind spot across all four of the 23.0.0 subpath-rewrite codemods (@nx/js, @nx/jest, @nx/eslint, the new @nx/cypress one) by walking ImportTypeNode and rewriting the literal-type-node argument when it points into the package's src/. The cypress spec also adds explicit coverage for componentConfigurationGenerator as a public-routed symbol, default and default-plus-named imports, jest.mock(..., factory), and it.each(MOCK_HELPER_METHODS) for both the jest and vi mock families — drift between those hardcoded sets and the real public/mock surface was the most plausible future silent-regression path. The dist-build-migration skill is updated to document the ImportTypeNode handling and the expanded spec checklist.

Validation

  • pnpm nx run-many -t build-base -p angular,nuxt,remix,vue,web,react,vitest,cypress,playwright,vite,jest,js,eslint
  • pnpm nx affected -t build,lint --base=origin/master ✅ (53 projects, 103 tasks).
  • pnpm nx run-many -t test -p jest,js,eslint,cypress --testPathPatterns="rewrite-internal-subpath-imports" ✅ — 99 passing (32 cypress, 27 eslint, 20 jest, 20 js).

@nx/web is now unblocked on 4 of its 5 prior dist-build dependencies (@nx/vitest, @nx/cypress, @nx/playwright, @nx/vite); only @nx/webpack remains on the old layout.

Related Issue(s)

Linear: NXC-3581 — M2 epic, migrate testing/build packages to local dist.

Pre-create review (run before PR open)

Critical

  • (Fixed in this PR) Codemod skipped typeof import('@nx/<name>/src/...') type queries — backported the fix to @nx/js, @nx/jest, @nx/eslint, and the new @nx/cypress migration.
  • (Fixed in this PR) Spec didn't exercise componentConfigurationGenerator (the 4th public symbol), default imports, or jest.mock / vi.mock proper.

Important

  • @nx/vite / @nx/vitest / @nx/playwright ship no codemod despite dropping their ./src/* wildcards. No first-party consumers exist; external plugin authors using deep subpaths will hit breakage. Noted as a known breaking change for the upgrade guide rather than fixed in this PR — codemods can land in a follow-up if the surface turns out to matter.
  • Empty try { ... } catch {} in cypress-version.ts (pre-existing; diff only touched JSDoc). Tracked for a follow-up.

Suggestions

  • Vite executor schema imports are now eager at module load (theoretical risk; matches the jest precedent).
  • vite/project.json and playwright/project.json omit dependsOn: ["^build", "build-base"] while cypress/vitest include it (covered by nx.json targetDefaults, cosmetic).
  • cypress dropped legacy aliases ./generators / ./executors / ./migrations (no .json suffix) from the exports map — undocumented surface, low risk.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 20, 2026

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 78e2a49
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/6a108da75444980008a78872
😎 Deploy Preview https://deploy-preview-35743--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 20, 2026

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit 78e2a49
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/6a108da7984c7a00088f3e4b
😎 Deploy Preview https://deploy-preview-35743--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 20, 2026

View your CI Pipeline Execution ↗ for commit 249ae27

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 12m 15s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 5s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 15s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 20s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 12s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-22 17:24:49 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.

@FrozenPandaz FrozenPandaz force-pushed the feature/nxc-3581-m2-epic-migrate-nxvitest-to-local-dist branch from 323df49 to 8bcb368 Compare May 22, 2026 02:13
nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

@FrozenPandaz FrozenPandaz force-pushed the feature/nxc-3581-m2-epic-migrate-nxvitest-to-local-dist branch from 1dd6f2b to 4624c77 Compare May 22, 2026 13:54
@FrozenPandaz FrozenPandaz marked this pull request as ready for review May 22, 2026 15:05
@FrozenPandaz FrozenPandaz requested a review from a team as a code owner May 22, 2026 15:05
@FrozenPandaz FrozenPandaz requested a review from lourw May 22, 2026 15:05
nx-cloud[bot]

This comment was marked as outdated.

FrozenPandaz and others added 14 commits May 22, 2026 12:29
…ports

Switches the @nx/vitest package to the local-dist build layout used by
@nx/devkit, @nx/nx, @nx/js, @nx/eslint, and @nx/jest. The package builds
to packages/vitest/dist/ with nodenext module resolution and is published
straight from the package directory, not from the workspace-root
dist/packages/vitest.

The generator/executor/migration paths in generators.json,
executors.json and migrations.json are repointed to ./dist/src/...; the
prompt-migration .md files are copied into dist via assets.json so the
new paths resolve in published artifacts.
… exports

Switches the @nx/cypress package to the local-dist build layout used by
@nx/devkit, @nx/nx, @nx/js, @nx/eslint, and @nx/jest. The package builds
to packages/cypress/dist/ with nodenext module resolution and is
published straight from the package directory, not from the
workspace-root dist/packages/cypress.

Drops the './src/*' wildcard from the exports map and routes internal
consumers through a curated './internal' entry; a 23.0.0-beta.17
migration rewrites '@nx/cypress/src/*' imports to either the public
'@nx/cypress' entry (for re-exported symbols) or '@nx/cypress/internal'
(for everything else). Consumers in @nx/angular, @nx/react, @nx/next and
@nx/web are codemodded to match.
…denext exports

Switches the @nx/playwright package to the local-dist build layout used
by @nx/devkit, @nx/nx, @nx/js, @nx/eslint, and @nx/jest. The package
builds to packages/playwright/dist/ with nodenext module resolution and
is published straight from the package directory, not from the
workspace-root dist/packages/playwright.

The previous exports already referenced explicit subpaths only (no
'./src/*' wildcard) and no first-party consumer imports from
'@nx/playwright/src/*', so this migration ships without a codemod
migration step.
Switches the @nx/vite package to the local-dist build layout used by
@nx/devkit, @nx/nx, @nx/js, @nx/eslint, @nx/jest, @nx/vitest, @nx/cypress
and @nx/playwright. The package builds to packages/vite/dist/ with
nodenext module resolution and is published straight from the package
directory, not from the workspace-root dist/packages/vite.

The previous exports already enumerated explicit subpaths only (no
'./src/*' wildcard), and no first-party consumer imports from
'@nx/vite/src/*', so this migration ships without a codemod migration
step.
…ations

The @nx/js, @nx/jest, @nx/eslint, and @nx/cypress 23.0.0 codemods walk
`require`/dynamic `import`/`jest.mock` call expressions and rewrite
`@nx/<name>/src/*` arguments, but a `typeof import('@nx/<name>/src/x')`
type query parses as an `ImportTypeNode` — a separate AST branch — so
the previous handler left it untouched.

In practice that meant the common `const m = require('@nx/<name>/src/x')
as typeof import('@nx/<name>/src/x')` typed-runtime-require idiom would
have its runtime argument rewritten to `/internal` while the type
argument kept pointing at the now-removed `./src/*` wildcard, leaving
external consumers with a TS error after running the migration. Walk
`ImportTypeNode` explicitly and rewrite its literal-type-node argument
when the specifier starts with the package's `src/` prefix.

Adds:
- the `<typeof import()>require()` cast case in tandem to each spec.
- (for @nx/cypress) coverage for `componentConfigurationGenerator` as a
  public-routed symbol, default and default-plus-named imports, and
  every entry in `MOCK_HELPER_METHODS` via `it.each` — drift between
  these hardcoded sets and the real public/mock surface was the most
  plausible future silent-regression path.

Updates the dist-build-migration skill to document the `ImportTypeNode`
handling and the expanded spec checklist.
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
After the local-dist migration the compiled `dist/src/utils/versions.js`
sits two levels under `dist/`, so `require('../../package.json')`
resolves to `dist/package.json` — which doesn't exist; the real
package.json is at the package root. `@nx/vitest`'s own
`init` generator was failing at module-load time with
`Cannot find module '../../package.json'` when installed via the
local registry in e2e runs.

Switch to the self-reference pattern `require(join('@nx/vitest',
'package.json')).version` already used by the other migrated packages
(@nx/cypress, @nx/playwright, @nx/vite, @nx/jest, @nx/eslint, @nx/js)
and add the matching `@nx/vitest` ignoredDependencies entry so the
dependency-checks lint rule doesn't flag the self-import.
…pers

`assertValidMigrationPaths` already strips `dist/` from the
`implementation` path of a migration entry before `require`-ing the
source `.ts`, but did the existence check on the `prompt` path
verbatim. After the dist-build migration, prompt entries in
`migrations.json` point at `./dist/src/migrations/<dir>/<file>.md`
(the published copy that ships under the package's `dist/`); the spec
runs against the source tree, where the `.md` lives at
`./src/migrations/<dir>/<file>.md`. Result: every prompt-form
migration in @nx/vitest (and other packages that adopt the same shape
post-migration) failed the `should have valid path generator: ...`
assertion.

`assertGeneratorsEnforceVersionFloor` had the same blind spot for
`factory` paths in `generators.json` — `./dist/src/generators/…`
made `require(join(packageRoot, factoryRelative))` resolve to a
not-yet-built dist file rather than the source. @nx/playwright's
`all-generators-enforce-floor.spec.ts` was failing on all three
generators for this reason.

Both helpers now drop the leading `dist/` segment before resolving,
matching the existing implementation-path behaviour.
… under nodenext

After the local-dist migration, `@nx/vite` and `@nx/vitest` compile
with `module: nodenext`. Under that setting TypeScript no longer
downlevels `await import('@nx/eslint/internal')` into a synchronous
`Promise.resolve(require(...))` — it stays as a true ESM dynamic
import. ESM resolution walks up `node_modules` from the importing
file's location and ignores `Module._initPaths`, which is exactly the
mechanism `ensurePackage` uses to register the on-demand temp install.
Result: every generator path that goes through `ignoreViteTempFiles` /
`ignoreVitestTempFiles` (or the `@nx/vite:configuration` generator's
`includeVitest` branch) crashed with `Cannot find package
'@nx/eslint'` (or `'@nx/vitest'`) when run against the published
artifact in an e2e workspace. Same bug fired the
`migrations.spec.ts`-shape regressions in the `@nx/vite:setup-paths`
generator's downstream consumers in `@nx/web` and `@nx/react` e2es.

Switch the four affected call sites to a synchronous CommonJS
`require()` call after `ensurePackage`. `require()` honors
`Module._initPaths`, so the temp install resolves correctly. Type
information is preserved via `typeof import(...)` on the destructuring.

Also fixes `e2e/nx-build/src/nx-build.test.ts` which still expected
`@nx/playwright` to publish from the workspace-root
`dist/packages/playwright/index.js`; updates the expected paths for
`@nx/playwright`, `@nx/cypress`, `@nx/vite`, and `@nx/vitest` to
`packages/<name>/dist/index.js` matching the new layout.

Updates the dist-build-migration skill with a new step 15b documenting
the `ensurePackage` + `await import()` audit so future migrations
don't ship the same regression.
The 'should build all packages and produce correct output' assertion in
e2e/nx-build/src/nx-build.test.ts still expected @nx/playwright to ship
from the workspace-root dist/packages/playwright/index.js. Update the
expected paths for the four newly-migrated packages (@nx/playwright,
@nx/cypress, @nx/vite, @nx/vitest) to packages/<name>/dist/index.js
matching the new layout, and add the cypress/vite/vitest entries since
they weren't covered before.
…imumCypressVersion

These were marked @deprecated in v23 (commit 5feafd6) pointing at the
@nx/cypress/internal replacements. Not exported in the package exports
map and no in-repo callers remain.
@FrozenPandaz FrozenPandaz force-pushed the feature/nxc-3581-m2-epic-migrate-nxvitest-to-local-dist branch from 3c27bff to 249ae27 Compare May 22, 2026 16:32
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 b6fc86e into master May 22, 2026
18 checks passed
@FrozenPandaz FrozenPandaz deleted the feature/nxc-3581-m2-epic-migrate-nxvitest-to-local-dist branch May 22, 2026 20:13
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