@nx/nest 24.0.6 → 24.1.0-canary.20261111-edd9940

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ const init_1 = require("../init/init");
8
8
  const lib_1 = require("./lib");
9
9
  const assert_supported_nestjs_version_1 = require("../../utils/assert-supported-nestjs-version");
10
10
  const assert_vitest_supported_1 = require("../../utils/assert-vitest-supported");
11
+ const unit_test_runner_1 = require("../../utils/unit-test-runner");
11
12
  const ensure_dependencies_1 = require("../../utils/ensure-dependencies");
12
13
  async function applicationGenerator(tree, rawOptions) {
13
14
  return await applicationGeneratorInternal(tree, {
@@ -18,6 +19,9 @@ async function applicationGenerator(tree, rawOptions) {
18
19
  async function applicationGeneratorInternal(tree, rawOptions) {
19
20
  (0, assert_supported_nestjs_version_1.assertSupportedNestJsVersion)(tree);
20
21
  const options = await (0, lib_1.normalizeOptions)(tree, rawOptions);
22
+ if (options.unitTestRunner === 'jest') {
23
+ (0, unit_test_runner_1.assertJestSupported)(tree);
24
+ }
21
25
  if (options.unitTestRunner === 'vitest') {
22
26
  (0, assert_vitest_supported_1.assertVitestSupported)(tree);
23
27
  }
@@ -29,6 +33,9 @@ async function applicationGeneratorInternal(tree, rawOptions) {
29
33
  tasks.push(initTask);
30
34
  const nodeApplicationTask = await (0, node_1.applicationGenerator)(tree, (0, lib_1.toNodeApplicationGeneratorOptions)(options));
31
35
  tasks.push(nodeApplicationTask);
36
+ if (options.unitTestRunner === 'jest' && (0, unit_test_runner_1.usesEsmOnlyNestJs)(tree)) {
37
+ tasks.push((0, unit_test_runner_1.configureJestForEsmOnlyNestJs)(tree, options.appProjectName));
38
+ }
32
39
  (0, lib_1.createFiles)(tree, options);
33
40
  (0, lib_1.updateTsConfig)(tree, options);
34
41
  if (!options.skipPackageJson) {
@@ -4,24 +4,26 @@ exports.normalizeOptions = normalizeOptions;
4
4
  exports.toNodeApplicationGeneratorOptions = toNodeApplicationGeneratorOptions;
5
5
  const internal_1 = require("@nx/devkit/internal");
6
6
  const internal_2 = require("@nx/js/internal");
7
+ const unit_test_runner_1 = require("../../../utils/unit-test-runner");
7
8
  async function normalizeOptions(tree, options) {
8
9
  await (0, internal_1.ensureRootProjectName)(options, 'application');
9
- const { projectName: appProjectName, projectRoot: appProjectRoot } = await (0, internal_1.determineProjectNameAndRootOptions)(tree, {
10
+ const { projectName, projectRoot: appProjectRoot, importPath, } = await (0, internal_1.determineProjectNameAndRootOptions)(tree, {
10
11
  name: options.name,
11
12
  projectType: 'application',
12
13
  directory: options.directory,
13
14
  rootProject: options.rootProject,
14
15
  });
15
16
  options.rootProject = appProjectRoot === '.';
17
+ const isUsingTsSolutionConfig = (0, internal_2.isUsingTsSolutionSetup)(tree);
16
18
  return {
17
19
  ...options,
18
20
  strict: options.strict ?? false,
19
- appProjectName,
21
+ appProjectName: isUsingTsSolutionConfig && !options.name ? importPath : projectName,
20
22
  appProjectRoot,
21
23
  linter: await (0, internal_2.normalizeLinterOption)(tree, options.linter),
22
- unitTestRunner: options.unitTestRunner ?? 'jest',
24
+ unitTestRunner: options.unitTestRunner ?? (0, unit_test_runner_1.getDefaultUnitTestRunner)(tree),
23
25
  e2eTestRunner: options.e2eTestRunner ?? 'jest',
24
- useProjectJson: options.useProjectJson ?? !(0, internal_2.isUsingTsSolutionSetup)(tree),
26
+ useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig,
25
27
  };
26
28
  }
27
29
  function toNodeApplicationGeneratorOptions(options) {
@@ -45,7 +45,7 @@
45
45
  "enum": ["none", "prettier", "oxfmt"]
46
46
  },
47
47
  "unitTestRunner": {
48
- "description": "Test runner to use for unit tests.",
48
+ "description": "Test runner to use for unit tests. With Nest 12, which is ESM only, `jest` needs Node.js 24.9 or later, and Nx sets it up with Jest 30.5 and `--experimental-vm-modules`.",
49
49
  "type": "string",
50
50
  "enum": ["jest", "vitest", "none"],
51
51
  "default": "none",
@@ -2,10 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.addDependencies = addDependencies;
4
4
  const devkit_1 = require("@nx/devkit");
5
+ const ensure_typescript_for_schematics_1 = require("../../../utils/ensure-typescript-for-schematics");
5
6
  const versions_1 = require("../../../utils/versions");
6
7
  function addDependencies(tree, options) {
7
- return (0, devkit_1.addDependenciesToPackageJson)(tree, {}, {
8
+ return (0, devkit_1.runTasksInSerial)((0, devkit_1.addDependenciesToPackageJson)(tree, {}, {
8
9
  '@nestjs/schematics': (0, versions_1.versions)(tree).nestJsSchematicsVersion,
9
10
  '@nx/nest': versions_1.nxVersion,
10
- }, undefined, options.keepExistingVersions ?? true);
11
+ }, undefined, options.keepExistingVersions ?? true), (0, ensure_typescript_for_schematics_1.ensureTypescriptForNestSchematics)(tree));
11
12
  }
@@ -4,6 +4,7 @@ exports.normalizeOptions = normalizeOptions;
4
4
  exports.toJsLibraryGeneratorOptions = toJsLibraryGeneratorOptions;
5
5
  const internal_1 = require("@nx/devkit/internal");
6
6
  const internal_2 = require("@nx/js/internal");
7
+ const unit_test_runner_1 = require("../../../utils/unit-test-runner");
7
8
  async function normalizeOptions(tree, options) {
8
9
  await (0, internal_1.ensureRootProjectName)(options, 'library');
9
10
  const { projectName, names: projectNames, projectRoot, importPath, } = await (0, internal_1.determineProjectNameAndRootOptions)(tree, {
@@ -35,7 +36,7 @@ async function normalizeOptions(tree, options) {
35
36
  service: options.service ?? false,
36
37
  target: options.target ?? 'es6',
37
38
  testEnvironment: options.testEnvironment ?? 'node',
38
- unitTestRunner: options.unitTestRunner ?? 'jest',
39
+ unitTestRunner: options.unitTestRunner ?? (0, unit_test_runner_1.getDefaultUnitTestRunner)(tree),
39
40
  isUsingTsSolutionsConfig,
40
41
  useProjectJson: options.useProjectJson ?? !isUsingTsSolutionsConfig,
41
42
  };
@@ -8,6 +8,7 @@ const devkit_1 = require("@nx/devkit");
8
8
  const js_1 = require("@nx/js");
9
9
  const assert_supported_nestjs_version_1 = require("../../utils/assert-supported-nestjs-version");
10
10
  const assert_vitest_supported_1 = require("../../utils/assert-vitest-supported");
11
+ const unit_test_runner_1 = require("../../utils/unit-test-runner");
11
12
  const ensure_dependencies_1 = require("../../utils/ensure-dependencies");
12
13
  const init_1 = tslib_1.__importDefault(require("../init/init"));
13
14
  const lib_1 = require("./lib");
@@ -20,6 +21,9 @@ async function libraryGenerator(tree, rawOptions) {
20
21
  async function libraryGeneratorInternal(tree, rawOptions) {
21
22
  (0, assert_supported_nestjs_version_1.assertSupportedNestJsVersion)(tree);
22
23
  const options = await (0, lib_1.normalizeOptions)(tree, rawOptions);
24
+ if (options.unitTestRunner === 'jest') {
25
+ (0, unit_test_runner_1.assertJestSupported)(tree);
26
+ }
23
27
  if (options.unitTestRunner === 'vitest') {
24
28
  (0, assert_vitest_supported_1.assertVitestSupported)(tree);
25
29
  }
@@ -32,6 +36,9 @@ async function libraryGeneratorInternal(tree, rawOptions) {
32
36
  (0, lib_1.addExportsToBarrelFile)(tree, options);
33
37
  (0, lib_1.updateTsConfig)(tree, options);
34
38
  (0, lib_1.addProject)(tree, options);
39
+ const jestTask = options.unitTestRunner === 'jest' && (0, unit_test_runner_1.usesEsmOnlyNestJs)(tree)
40
+ ? (0, unit_test_runner_1.configureJestForEsmOnlyNestJs)(tree, options.projectName)
41
+ : () => { };
35
42
  if (!options.skipFormat) {
36
43
  await (0, devkit_1.formatFiles)(tree);
37
44
  }
@@ -39,6 +46,7 @@ async function libraryGeneratorInternal(tree, rawOptions) {
39
46
  jsLibraryTask,
40
47
  initTask,
41
48
  depsTask,
49
+ jestTask,
42
50
  () => {
43
51
  (0, internal_1.logShowProjectCommand)(options.projectName);
44
52
  },
@@ -34,7 +34,7 @@
34
34
  "x-priority": "important"
35
35
  },
36
36
  "unitTestRunner": {
37
- "description": "Test runner to use for unit tests.",
37
+ "description": "Test runner to use for unit tests. With Nest 12, which is ESM only, `jest` needs Node.js 24.9 or later, and Nx sets it up with Jest 30.5 and `--experimental-vm-modules`.",
38
38
  "type": "string",
39
39
  "enum": ["jest", "vitest", "none"],
40
40
  "default": "none",
@@ -0,0 +1,69 @@
1
+ # NestJS 11 -> 12 Migration Instructions for LLM
2
+
3
+ ## Overview
4
+
5
+ The deterministic migration bumped the declared `@nestjs/*` core packages to `^12.0.1`, bumped the declared `@nestjs/*` add-ons (such as `@nestjs/swagger`, `@nestjs/config` and `@nestjs/graphql`) to their first release that accepts NestJS 12, and, when the workspace was on TypeScript 5, upgraded `typescript` to 6 because `@nestjs/schematics` 12 requires it. NestJS 12 ships every core package as ESM only. A CommonJS app loads them through `require(esm)`, which needs Node.js 20.19+ or 22.12+ to run the app. Jest loads them only on Node.js 24.9 or later, with Jest 30.5+ and `--experimental-vm-modules`, so for each project that tests Nest code with Jest the migration upgraded `jest` to `^30.5.0` and set `NODE_OPTIONS=--experimental-vm-modules` in the `env` of the project's Jest target. The migration checked the Node.js version it ran on, `.nvmrc`, `.node-version` and `engines.node`, but it cannot see the Node.js version CI uses. This runbook covers that check and the source changes that need judgment. Do not change Nx target configuration or the webpack build setup.
6
+
7
+ ## Pre-Migration Checklist
8
+
9
+ 1. Confirm `@nestjs/core` in `package.json` resolves to 12. If it does not, the migration kept NestJS 11 on purpose; make no changes and stop.
10
+ 2. When the step's `nx_migrate_prompt` block has `impl.agentContext`, read it. It lists the packages the migration bumped and the projects that test Nest code with Jest.
11
+
12
+ ## Step 1: Check the Node.js version every Jest run uses
13
+
14
+ Skip this step when `impl.agentContext` says no project tests Nest code with Jest.
15
+
16
+ For each Jest-tested project listed in `impl.agentContext`, find every place its tests run and the Node.js version there:
17
+
18
+ - CI workflow files (`.github/workflows/*.yml`, `.gitlab-ci.yml`, `azure-pipelines.yml`, `.circleci/config.yml`, `bitbucket-pipelines.yml`, `Jenkinsfile`): `node-version`, `setup-node`, container `image:` tags such as `node:22`.
19
+ - Dockerfiles and dev container configs that run tests.
20
+ - Version manager files the migration does not read, such as `.tool-versions` and `mise.toml`.
21
+
22
+ Also check how those places run the tests. A job that sets `NODE_OPTIONS` itself, or runs `jest` directly instead of through the Nx target, does not get `--experimental-vm-modules`. Add the flag to that job's `NODE_OPTIONS` (keeping its existing value), or run the tests through `nx test`.
23
+
24
+ If any of them runs these tests on a Node.js below 24.9, the bump leaves the workspace broken: the tests fail there with `SyntaxError: Unexpected token 'export'` or a "could not be executed as CommonJS" error. In that case, **revert to NestJS 11**:
25
+
26
+ 1. Set every `@nestjs/*` package the migration bumped back to the version it had before the migration, in `package.json` (or its catalog entry).
27
+ 2. Remove the `NODE_OPTIONS` entry the migration added to each Jest target's `env`. Leave the `jest` and `typescript` upgrades in place; both work with NestJS 11.
28
+ 3. Install, and report to the user which CI job or file runs Node.js below 24.9, and that they can move those tests to Vitest (`nx g @nx/vitest:configuration <project>`, then replace `jest.*` calls with `vi.*`) or raise that Node.js version, then re-run the migration.
29
+
30
+ Stop after reverting; skip the remaining steps.
31
+
32
+ If every place runs Node.js 24.9 or later, keep NestJS 12 and continue.
33
+
34
+ ## Step 2: Review custom pipes
35
+
36
+ `ArgumentMetadata` is generic in NestJS 12. Custom pipes that implement `PipeTransform` and type the `metadata` parameter may fail to typecheck. Run typecheck on the Nest projects and fix the pipe signatures it reports.
37
+
38
+ ## Step 3: Review lifecycle hook ordering
39
+
40
+ Lifecycle hooks (`onModuleInit`, `onApplicationBootstrap`, `onModuleDestroy`, `beforeApplicationShutdown`, `onApplicationShutdown`) now run in module hierarchy order. Search for hooks that depend on another module's hook having run first, such as a provider that reads a connection another module opens in `onModuleInit`. Do not reorder code unless such a dependency exists; report each one you find.
41
+
42
+ ## Step 4: Review `@Optional()` on subclasses
43
+
44
+ `@Optional()` is no longer inherited by subclasses. Search for classes that extend an injectable whose constructor parameters use `@Optional()` and that do not redeclare the constructor. Add `@Optional()` to the subclass constructor where the parameter can still be missing.
45
+
46
+ ## Step 5: Opt-in packages
47
+
48
+ Check `package.json` for each package below and follow the NestJS 12 migration guide (https://docs.nestjs.com/migration-guide) for the ones present:
49
+
50
+ - `nats`: replaced by `@nats-io/transport-node`; the client options and message payloads change.
51
+ - `subscriptions-transport-ws`: removed; use `graphql-ws`. Clients must move to the `graphql-ws` protocol too, so report it rather than changing client code.
52
+ - `@nestjs/graphql` with `playground` config: GraphiQL is the default IDE; move `playground` options to `graphiql`.
53
+ - `@nestjs/config` with `validationSchema`: validation uses Standard Schema; Joi needs v18+, and Joi options move under `validationOptions.libraryOptions`.
54
+ - `@nestjs/terminus`: `HealthIndicator` and `HealthCheckError` are removed; use `HealthIndicatorService`.
55
+
56
+ `ConsoleLogger` now defaults `structuredParams` to `true`, which changes log output. Do not change it; mention it to the user if the workspace parses its logs.
57
+
58
+ ## Post-Migration Validation
59
+
60
+ 1. Install dependencies. A peer dependency conflict here usually comes from an add-on the migration bumped that also raised a third-party peer, such as `@nestjs/apollo` 14 requiring `@apollo/server` 5 or `@nestjs/serve-static` 12 requiring `@fastify/static` 10. Upgrade that peer to the range the add-on asks for, and report any `@nestjs/*` package that still has no release accepting NestJS 12.
61
+ 2. Run typecheck on the Nest projects (`npx nx run-many -t typecheck -p <projects>`).
62
+ 3. Run their tests (`npx nx run-many -t test -p <projects>`). A Jest run that fails with `Unexpected token 'export'` means it runs on Node.js below 24.9 or without `--experimental-vm-modules`; go back to Step 1.
63
+ 4. Build and serve one app (`npx nx build <app>`, `npx nx serve <app>`) to confirm it starts on the workspace's Node.js.
64
+ 5. Fix failures caused by this migration and re-run until green.
65
+
66
+ ## Nx-Specific Notes
67
+
68
+ - New Nest projects generated on NestJS 12 use Vitest by default. `--unitTestRunner=jest` works on Node.js 24.9+ and sets up Jest the same way this migration does.
69
+ - Keep the webpack build and `@nx/js:node` serve targets as they are; a CommonJS bundle loads NestJS 12 through `require(esm)`.
@@ -0,0 +1,8 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ type MigrationResult = {
3
+ nextSteps: string[];
4
+ agentContext: string[];
5
+ skipAgentic?: boolean;
6
+ };
7
+ export default function upgradeToNest12(tree: Tree): Promise<MigrationResult>;
8
+ export {};
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = upgradeToNest12;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const path_1 = require("path");
6
+ const semver_1 = require("semver");
7
+ const ensure_typescript_for_schematics_1 = require("../../utils/ensure-typescript-for-schematics");
8
+ const unit_test_runner_1 = require("../../utils/unit-test-runner");
9
+ const versions_1 = require("../../utils/versions");
10
+ // Keep these versions pinned; versions.ts advances to later majors.
11
+ // Core packages start at 12.0.1 because 12.0.0 shipped with its peers still on ^11.
12
+ const NEST_12_VERSIONS = {
13
+ '@nestjs/common': '^12.0.1',
14
+ '@nestjs/core': '^12.0.1',
15
+ '@nestjs/microservices': '^12.0.1',
16
+ '@nestjs/platform-express': '^12.0.1',
17
+ '@nestjs/platform-fastify': '^12.0.1',
18
+ '@nestjs/platform-socket.io': '^12.0.1',
19
+ '@nestjs/platform-ws': '^12.0.1',
20
+ '@nestjs/schematics': '^12.0.1',
21
+ '@nestjs/testing': '^12.0.1',
22
+ '@nestjs/websockets': '^12.0.1',
23
+ // Add-ons: the first release whose peer range accepts Nest 12.
24
+ '@nestjs/apollo': '^14.0.0',
25
+ '@nestjs/axios': '^12.0.0',
26
+ '@nestjs/bull': '^12.0.0',
27
+ '@nestjs/bullmq': '^12.0.0',
28
+ '@nestjs/cache-manager': '^12.0.0',
29
+ '@nestjs/config': '^12.0.0',
30
+ '@nestjs/cqrs': '^12.0.0',
31
+ '@nestjs/elasticsearch': '^12.0.0',
32
+ '@nestjs/event-emitter': '^12.0.0',
33
+ '@nestjs/graphql': '^14.0.0',
34
+ '@nestjs/jwt': '^12.0.0',
35
+ '@nestjs/mapped-types': '^12.0.0',
36
+ '@nestjs/mercurius': '^14.0.0',
37
+ '@nestjs/mongoose': '^12.0.0',
38
+ '@nestjs/passport': '^12.0.0',
39
+ '@nestjs/schedule': '^12.0.0',
40
+ '@nestjs/sequelize': '^12.0.0',
41
+ '@nestjs/serve-static': '^12.0.0',
42
+ '@nestjs/swagger': '^12.0.0',
43
+ '@nestjs/terminus': '^12.0.0',
44
+ '@nestjs/throttler': '^6.6.0',
45
+ '@nestjs/typeorm': '^12.0.0',
46
+ };
47
+ const JEST_CONFIG_FILES = [
48
+ 'jest.config.ts',
49
+ 'jest.config.cts',
50
+ 'jest.config.mts',
51
+ 'jest.config.js',
52
+ 'jest.config.cjs',
53
+ 'jest.config.mjs',
54
+ ];
55
+ const SOURCE_FILE = /\.[cm]?[jt]sx?$/;
56
+ const NEST_IMPORT = /(?:from\s+|require\(\s*|import\(\s*)['"]@nestjs\//;
57
+ const RERUN_COMMAND = `npx nx migrate @nx/nest@${versions_1.nxVersion} --from=@nx/nest@24.0.0 --include=all && npx nx migrate --run-migration=@nx/nest:upgrade-to-nest-12`;
58
+ async function upgradeToNest12(tree) {
59
+ const jestProjects = findJestTestedNestProjects(tree);
60
+ const oldNode = findNodeVersionsBelowJestFloor(tree);
61
+ if (jestProjects.length > 0 && oldNode.length > 0) {
62
+ return {
63
+ nextSteps: [
64
+ `Kept NestJS 11: Jest loads NestJS 12 only on Node.js ${unit_test_runner_1.jestNodeFloor} or later, and ${oldNode.join(', ')}. These projects test Nest code with Jest: ${jestProjects.join(', ')}. Move their tests to Vitest, or run them on Node.js ${unit_test_runner_1.jestNodeFloor}+, then run \`${RERUN_COMMAND}\` to move to NestJS 12.`,
65
+ ],
66
+ agentContext: [],
67
+ skipAgentic: true,
68
+ };
69
+ }
70
+ const declared = (0, devkit_1.readJson)(tree, 'package.json');
71
+ const bumped = Object.fromEntries(Object.entries(NEST_12_VERSIONS).filter(([name]) => declared.devDependencies?.[name] || declared.dependencies?.[name]));
72
+ (0, devkit_1.addDependenciesToPackageJson)(tree, {}, bumped);
73
+ (0, ensure_typescript_for_schematics_1.ensureTypescriptForNestSchematics)(tree);
74
+ for (const project of jestProjects) {
75
+ (0, unit_test_runner_1.configureJestForEsmOnlyNestJs)(tree, project);
76
+ }
77
+ await (0, devkit_1.formatFiles)(tree);
78
+ return {
79
+ nextSteps: [],
80
+ agentContext: [
81
+ `Bumped ${Object.entries(bumped)
82
+ .map(([name, version]) => `\`${name}\` to \`${version}\``)
83
+ .join(', ')} in package.json`,
84
+ jestProjects.length > 0
85
+ ? `These projects test Nest code with Jest, which needs Node.js ${unit_test_runner_1.jestNodeFloor}+ on NestJS 12; their Jest target now runs with NODE_OPTIONS=--experimental-vm-modules and Jest is on ^30.5.0: ${jestProjects.join(', ')}`
86
+ : 'No project tests Nest code with Jest',
87
+ ],
88
+ };
89
+ }
90
+ function findJestTestedNestProjects(tree) {
91
+ const projects = [];
92
+ for (const [name, project] of (0, devkit_1.getProjects)(tree)) {
93
+ const usesJest = JEST_CONFIG_FILES.some((file) => tree.exists((0, path_1.join)(project.root, file))) ||
94
+ Object.values(project.targets ?? {}).some((target) => target.executor === '@nx/jest:jest');
95
+ if (usesJest && importsNest(tree, project.root)) {
96
+ projects.push(name);
97
+ }
98
+ }
99
+ return projects.sort();
100
+ }
101
+ function importsNest(tree, root) {
102
+ let found = false;
103
+ (0, devkit_1.visitNotIgnoredFiles)(tree, root, (path) => {
104
+ if (!found && SOURCE_FILE.test(path)) {
105
+ found = NEST_IMPORT.test(tree.read(path, 'utf-8') ?? '');
106
+ }
107
+ });
108
+ return found;
109
+ }
110
+ // Each Node.js version the workspace can be seen to run that is below the
111
+ // Jest floor, described for the next steps. CI configuration is left to the
112
+ // AI instructions.
113
+ function findNodeVersionsBelowJestFloor(tree) {
114
+ const found = [];
115
+ const current = process.versions.node;
116
+ if ((0, semver_1.lt)(current, unit_test_runner_1.jestNodeFloor)) {
117
+ found.push(`this migration runs on Node.js ${current}`);
118
+ }
119
+ // Version managers resolve a partial version like `24` to the latest match.
120
+ for (const file of ['.nvmrc', '.node-version']) {
121
+ const pinned = tree.exists(file) ? tree.read(file, 'utf-8').trim() : '';
122
+ if ((0, semver_1.validRange)(pinned) &&
123
+ !(0, semver_1.intersects)(pinned, `>=${unit_test_runner_1.jestNodeFloor}`, { includePrerelease: true })) {
124
+ found.push(`\`${file}\` pins Node.js ${pinned}`);
125
+ }
126
+ }
127
+ const engines = (0, devkit_1.readJson)(tree, 'package.json').engines?.node;
128
+ const enginesFloor = typeof engines === 'string' && (0, semver_1.validRange)(engines)
129
+ ? (0, semver_1.minVersion)(engines)
130
+ : null;
131
+ if (enginesFloor && (0, semver_1.lt)(enginesFloor, unit_test_runner_1.jestNodeFloor)) {
132
+ found.push(`\`engines.node\` allows Node.js ${enginesFloor.version}`);
133
+ }
134
+ return found;
135
+ }
@@ -0,0 +1,41 @@
1
+ #### Upgrade NestJS 11 to 12
2
+
3
+ Bumps the declared `@nestjs/*` core packages (`common`, `core`, `microservices`, `platform-*`, `schematics`, `testing`, `websockets`) to 12, and the declared add-ons such as `@nestjs/swagger`, `@nestjs/config` and `@nestjs/graphql` to their first release that accepts NestJS 12; a package manager catalog entry is bumped in the catalog. `@nestjs/schematics` 12 requires TypeScript 6, so a workspace on TypeScript 5 also gets `typescript` upgraded to the version Nx installs by default.
4
+
5
+ NestJS 12 ships its core packages as ESM only. A CommonJS app loads them through `require(esm)`, which needs Node.js 20.19+ or 22.12+. Jest can load them only on Node.js 24.9 or later, with Jest 30.5+ and `--experimental-vm-modules`. For each project that tests Nest code with Jest, the migration upgrades `jest` to 30.5 and runs the project's Jest target with `NODE_OPTIONS=--experimental-vm-modules`. When the migration runs on a Node.js below 24.9, or `.nvmrc`, `.node-version` or `engines.node` allows one, the workspace stays on NestJS 11 instead, and the migration reports the projects and the command to run once their tests move to Vitest or run on Node.js 24.9+. The paired AI instructions check the Node.js version in CI, revert to NestJS 11 if a Jest run there would break, and walk through the changes that need judgment: generic `ArgumentMetadata` in custom pipes, lifecycle hook ordering, `@Optional()` on subclasses, and the opt-in NATS, GraphQL, `@nestjs/config` and Terminus changes. Read more in the [NestJS 12 migration guide](https://docs.nestjs.com/migration-guide).
6
+
7
+ #### Sample Code Changes
8
+
9
+ ##### Before
10
+
11
+ ```json title="package.json"
12
+ {
13
+ "dependencies": {
14
+ "@nestjs/common": "^11.0.0",
15
+ "@nestjs/core": "^11.0.0",
16
+ "@nestjs/platform-express": "^11.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "@nestjs/schematics": "^11.0.0",
20
+ "@nestjs/testing": "^11.0.0",
21
+ "typescript": "~5.9.2"
22
+ }
23
+ }
24
+ ```
25
+
26
+ ##### After
27
+
28
+ ```json title="package.json"
29
+ {
30
+ "dependencies": {
31
+ "@nestjs/common": "^12.0.1",
32
+ "@nestjs/core": "^12.0.1",
33
+ "@nestjs/platform-express": "^12.0.1"
34
+ },
35
+ "devDependencies": {
36
+ "@nestjs/schematics": "^12.0.1",
37
+ "@nestjs/testing": "^12.0.1",
38
+ "typescript": "~6.0.3"
39
+ }
40
+ }
41
+ ```
@@ -0,0 +1,6 @@
1
+ import type { GeneratorCallback, Tree } from '@nx/devkit';
2
+ /**
3
+ * `@nestjs/schematics` 12 peers `typescript >=6.0.0`, and npm refuses to
4
+ * install it next to TypeScript 5, so the declared `typescript` is upgraded.
5
+ */
6
+ export declare function ensureTypescriptForNestSchematics(tree: Tree): GeneratorCallback;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureTypescriptForNestSchematics = ensureTypescriptForNestSchematics;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const internal_1 = require("@nx/js/internal");
6
+ const semver_1 = require("semver");
7
+ /**
8
+ * `@nestjs/schematics` 12 peers `typescript >=6.0.0`, and npm refuses to
9
+ * install it next to TypeScript 5, so the declared `typescript` is upgraded.
10
+ */
11
+ function ensureTypescriptForNestSchematics(tree) {
12
+ const schematics = (0, devkit_1.getDependencyVersionFromPackageJson)(tree, '@nestjs/schematics');
13
+ const schematicsMajor = schematics ? (0, semver_1.coerce)(schematics)?.major : undefined;
14
+ if (schematicsMajor === undefined ||
15
+ schematicsMajor < 12 ||
16
+ (0, internal_1.isTypescriptVersionAtLeast)(tree, '6.0.0')) {
17
+ return () => { };
18
+ }
19
+ return (0, devkit_1.addDependenciesToPackageJson)(tree, {}, { typescript: internal_1.typescriptVersion }, undefined, false);
20
+ }
@@ -0,0 +1,11 @@
1
+ import { type GeneratorCallback, type Tree } from '@nx/devkit';
2
+ export declare const jestNodeFloor = "24.9.0";
3
+ export declare const jestEsmVersion = "^30.5.0";
4
+ export declare function usesEsmOnlyNestJs(tree: Tree): boolean;
5
+ export declare function getDefaultUnitTestRunner(tree: Tree): 'jest' | 'vitest';
6
+ export declare function assertJestSupported(tree: Tree): void;
7
+ /**
8
+ * Lets a project's Jest target load Nest 12: upgrades Jest to 30.5 and runs
9
+ * the target with `--experimental-vm-modules`, keeping the inferred env.
10
+ */
11
+ export declare function configureJestForEsmOnlyNestJs(tree: Tree, projectName: string): GeneratorCallback;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.jestEsmVersion = exports.jestNodeFloor = void 0;
4
+ exports.usesEsmOnlyNestJs = usesEsmOnlyNestJs;
5
+ exports.getDefaultUnitTestRunner = getDefaultUnitTestRunner;
6
+ exports.assertJestSupported = assertJestSupported;
7
+ exports.configureJestForEsmOnlyNestJs = configureJestForEsmOnlyNestJs;
8
+ const devkit_1 = require("@nx/devkit");
9
+ const semver_1 = require("semver");
10
+ const versions_1 = require("./versions");
11
+ const firstEsmOnlyNestJsMajor = 12;
12
+ // Jest loads the ESM-only Nest 12 packages through its require(esm) support,
13
+ // which needs Jest 30.5, Node.js 24.9 and `--experimental-vm-modules`.
14
+ exports.jestNodeFloor = '24.9.0';
15
+ exports.jestEsmVersion = '^30.5.0';
16
+ const jestNodeOptions = '--experimental-vm-modules';
17
+ // A missing or unresolvable version (e.g. `workspace:`) falls back to the
18
+ // latest major, like `versions()` does.
19
+ function getNestJsMajor(tree) {
20
+ return (0, semver_1.major)((0, versions_1.getInstalledNestJsVersion)(tree) ?? (0, semver_1.coerce)(versions_1.nestJsVersion).version);
21
+ }
22
+ function usesEsmOnlyNestJs(tree) {
23
+ return getNestJsMajor(tree) >= firstEsmOnlyNestJsMajor;
24
+ }
25
+ function getDefaultUnitTestRunner(tree) {
26
+ return usesEsmOnlyNestJs(tree) ? 'vitest' : 'jest';
27
+ }
28
+ function assertJestSupported(tree) {
29
+ const nodeVersion = process.versions.node;
30
+ if (!usesEsmOnlyNestJs(tree) || !(0, semver_1.lt)(nodeVersion, exports.jestNodeFloor)) {
31
+ return;
32
+ }
33
+ throw new Error(`Using jest with Nest ${firstEsmOnlyNestJsMajor} requires Node.js ${exports.jestNodeFloor} or later, but this runs on Node.js ${nodeVersion}.\n` +
34
+ `Nest ${firstEsmOnlyNestJsMajor} is ESM only, and Jest loads it only on Node.js ${exports.jestNodeFloor}+ with ${jestNodeOptions}, which Nx sets up for you.\n` +
35
+ `Upgrade Node.js to ${exports.jestNodeFloor} or later, generate with "--unitTestRunner=vitest", or add "@nestjs/core@^${firstEsmOnlyNestJsMajor - 1}.0.0" to the workspace first to keep Nest ${firstEsmOnlyNestJsMajor - 1}.`);
36
+ }
37
+ /**
38
+ * Lets a project's Jest target load Nest 12: upgrades Jest to 30.5 and runs
39
+ * the target with `--experimental-vm-modules`, keeping the inferred env.
40
+ */
41
+ function configureJestForEsmOnlyNestJs(tree, projectName) {
42
+ const targetName = getJestTargetName(tree);
43
+ const project = (0, devkit_1.readProjectConfiguration)(tree, projectName);
44
+ project.targets ??= {};
45
+ const target = (project.targets[targetName] ??= {});
46
+ target.options = {
47
+ ...target.options,
48
+ env: {
49
+ '...': true,
50
+ ...target.options?.env,
51
+ NODE_OPTIONS: jestNodeOptions,
52
+ },
53
+ };
54
+ (0, devkit_1.updateProjectConfiguration)(tree, projectName, project);
55
+ const { dependencies, devDependencies } = (0, devkit_1.readJson)(tree, 'package.json');
56
+ const declared = { ...dependencies, ...devDependencies };
57
+ return (0, devkit_1.addDependenciesToPackageJson)(tree, {}, {
58
+ jest: exports.jestEsmVersion,
59
+ ...(declared['jest-environment-node']
60
+ ? { 'jest-environment-node': exports.jestEsmVersion }
61
+ : {}),
62
+ }, undefined, false);
63
+ }
64
+ function getJestTargetName(tree) {
65
+ for (const plugin of (0, devkit_1.readNxJson)(tree)?.plugins ?? []) {
66
+ if (typeof plugin !== 'string' && plugin.plugin === '@nx/jest/plugin') {
67
+ return plugin.options?.targetName ?? 'test';
68
+ }
69
+ }
70
+ return 'test';
71
+ }
@@ -2,8 +2,8 @@ import { type Tree } from '@nx/devkit';
2
2
  export declare const nxVersion: any;
3
3
  export declare const tsLibVersion = "^2.3.0";
4
4
  export declare const minSupportedNestJsVersion = "10.0.0";
5
- export declare const nestJsVersion = "^11.0.0";
6
- export declare const nestJsSchematicsVersion = "^11.0.0";
5
+ export declare const nestJsVersion = "^12.0.0";
6
+ export declare const nestJsSchematicsVersion = "^12.0.0";
7
7
  export declare const rxjsVersion = "^7.8.0";
8
8
  export declare const reflectMetadataVersion = "^0.2.0";
9
9
  type NestJsVersions = {
@@ -9,12 +9,12 @@ const path_1 = require("path");
9
9
  exports.nxVersion = require((0, path_1.join)('@nx/nest', 'package.json')).version;
10
10
  exports.tsLibVersion = '^2.3.0';
11
11
  // NestJS has no public LTS table; cadence is roughly one major per year.
12
- // Support window: v10 + v11 (Rule 2 — N & N-1 — since v10.4.x still receives
13
- // upstream patches).
12
+ // Support window: v11 + v12 (Rule 2 — N & N-1), with v10 kept while v10.4.x
13
+ // still receives upstream patches.
14
14
  exports.minSupportedNestJsVersion = '10.0.0';
15
15
  // Fresh-install constants — latest supported major.
16
- exports.nestJsVersion = '^11.0.0';
17
- exports.nestJsSchematicsVersion = '^11.0.0';
16
+ exports.nestJsVersion = '^12.0.0';
17
+ exports.nestJsSchematicsVersion = '^12.0.0';
18
18
  exports.rxjsVersion = '^7.8.0';
19
19
  exports.reflectMetadataVersion = '^0.2.0';
20
20
  const latestVersions = {
@@ -30,6 +30,12 @@ const versionMap = {
30
30
  rxjsVersion: '^7.8.0',
31
31
  reflectMetadataVersion: '^0.1.13',
32
32
  },
33
+ 11: {
34
+ nestJsVersion: '^11.0.0',
35
+ nestJsSchematicsVersion: '^11.0.0',
36
+ rxjsVersion: '^7.8.0',
37
+ reflectMetadataVersion: '^0.2.0',
38
+ },
33
39
  };
34
40
  function versions(tree) {
35
41
  const installed = getInstalledNestJsVersion(tree);
package/migrations.json CHANGED
@@ -1,5 +1,17 @@
1
1
  {
2
- "generators": {},
2
+ "generators": {
3
+ "upgrade-to-nest-12": {
4
+ "version": "24.1.0-beta.0",
5
+ "requires": {
6
+ "@nestjs/core": ">=11.0.0 <12.0.0"
7
+ },
8
+ "optionalUpdate": true,
9
+ "description": "Moves a NestJS 11 workspace to NestJS 12: bumps the declared `@nestjs/*` core and add-on packages, upgrades TypeScript 5 to 6 for `@nestjs/schematics` 12, and sets up Jest 30.5 with `--experimental-vm-modules` for projects that test Nest code with Jest, unless such a project runs on a Node.js below 24.9, where Jest cannot load NestJS 12; an AI prompt then checks CI and the remaining changes.",
10
+ "implementation": "./dist/src/migrations/update-24-1-0/upgrade-to-nest-12",
11
+ "prompt": "./dist/src/migrations/update-24-1-0/ai-instructions-for-nest-12.md",
12
+ "documentation": "./dist/src/migrations/update-24-1-0/upgrade-to-nest-12.md"
13
+ }
14
+ },
3
15
  "packageJsonUpdates": {
4
16
  "21.2.0-beta.2": {
5
17
  "version": "21.2.0-beta.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/nest",
3
- "version": "24.0.6",
3
+ "version": "24.1.0-canary.20261111-edd9940",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "files": [
@@ -59,16 +59,16 @@
59
59
  }
60
60
  },
61
61
  "dependencies": {
62
- "@nestjs/schematics": "^11.0.0",
63
- "@nx/devkit": "24.0.6",
64
- "@nx/js": "24.0.6",
65
- "@nx/node": "24.0.6",
62
+ "@nestjs/schematics": ">=11.0.0 <13.0.0",
63
+ "@nx/devkit": "24.1.0-canary.20261111-edd9940",
64
+ "@nx/js": "24.1.0-canary.20261111-edd9940",
65
+ "@nx/node": "24.1.0-canary.20261111-edd9940",
66
66
  "semver": "^7.6.3",
67
67
  "tslib": "^2.3.0"
68
68
  },
69
69
  "peerDependencies": {
70
- "@nestjs/core": ">=10.0.0 <12.0.0",
71
- "@nestjs/common": ">=10.0.0 <12.0.0",
70
+ "@nestjs/core": ">=10.0.0 <13.0.0",
71
+ "@nestjs/common": ">=10.0.0 <13.0.0",
72
72
  "reflect-metadata": ">=0.1.0 <0.3.0",
73
73
  "rxjs": "^7.0.0"
74
74
  },