@nx/node 24.0.0-beta.0 → 24.0.0-beta.2

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.
Files changed (34) hide show
  1. package/dist/src/generators/application/application.js +80 -1
  2. package/dist/src/generators/application/files/express/src/main.ts__tmpl__ +5 -1
  3. package/dist/src/generators/application/files/fastify/src/app/app.spec.ts__tmpl__ +1 -1
  4. package/dist/src/generators/application/files/fastify/src/app/app.ts__tmpl__ +2 -2
  5. package/dist/src/generators/application/files/fastify/src/main.ts__tmpl__ +6 -1
  6. package/dist/src/generators/application/files/koa/src/main.ts__tmpl__ +5 -1
  7. package/dist/src/generators/application/lib/add-dependencies.js +1 -0
  8. package/dist/src/generators/application/lib/add-script-serve.d.ts +7 -0
  9. package/dist/src/generators/application/lib/add-script-serve.js +52 -0
  10. package/dist/src/generators/application/lib/add-tsc-build-plugin.d.ts +8 -0
  11. package/dist/src/generators/application/lib/add-tsc-build-plugin.js +44 -0
  12. package/dist/src/generators/application/lib/create-files.js +2 -0
  13. package/dist/src/generators/application/lib/create-project.js +20 -3
  14. package/dist/src/generators/application/lib/create-targets.d.ts +2 -0
  15. package/dist/src/generators/application/lib/create-targets.js +32 -4
  16. package/dist/src/generators/application/lib/index.d.ts +1 -0
  17. package/dist/src/generators/application/lib/index.js +1 -0
  18. package/dist/src/generators/application/lib/normalize-options.js +45 -3
  19. package/dist/src/generators/application/lib/normalized-schema.d.ts +3 -0
  20. package/dist/src/generators/application/schema.d.ts +2 -2
  21. package/dist/src/generators/application/schema.json +3 -4
  22. package/dist/src/generators/e2e-project/e2e-project.js +114 -19
  23. package/dist/src/generators/e2e-project/files/cli/jest.config.ts__tmpl__ +2 -2
  24. package/dist/src/generators/e2e-project/files/non-ts-solution/tsconfig.spec.json__tmpl__ +2 -2
  25. package/dist/src/generators/e2e-project/files/server/common/jest.config.ts__tmpl__ +2 -2
  26. package/dist/src/generators/e2e-project/files/ts-solution/tsconfig.json__tmpl__ +3 -2
  27. package/dist/src/generators/e2e-project/files/vitest/common/vitest.config.mts__tmpl__ +12 -0
  28. package/dist/src/generators/e2e-project/files/vitest/server/src/support/global-setup.ts__tmpl__ +15 -0
  29. package/dist/src/generators/e2e-project/files/vitest/server/src/support/test-setup.ts__tmpl__ +6 -0
  30. package/dist/src/generators/e2e-project/schema.d.ts +3 -0
  31. package/dist/src/generators/e2e-project/schema.json +6 -0
  32. package/dist/src/utils/versions.d.ts +5 -4
  33. package/dist/src/utils/versions.js +6 -5
  34. package/package.json +10 -9
@@ -13,6 +13,28 @@ const e2e_project_1 = require("../e2e-project/e2e-project");
13
13
  const init_1 = require("../init/init");
14
14
  const setup_docker_1 = require("../setup-docker/setup-docker");
15
15
  const lib_1 = require("./lib");
16
+ function getRolldownBuildTargetName(tree, projectRoot) {
17
+ const configFile = (0, devkit_1.joinPathFragments)(projectRoot, 'rolldown.config.ts');
18
+ const registration = (0, devkit_1.readNxJson)(tree)?.plugins?.find((p) => typeof p === 'string'
19
+ ? p === '@nx/rolldown/plugin'
20
+ : p.plugin === '@nx/rolldown/plugin' &&
21
+ (0, internal_1.findMatchingConfigFiles)([configFile], p.include, p.exclude).length > 0);
22
+ return ((typeof registration === 'object' &&
23
+ registration.options
24
+ ?.buildTargetName) ||
25
+ 'build');
26
+ }
27
+ // fastify.inject requires ESM-only `cookie` 2, which Jest cannot load untransformed.
28
+ function allowJestToTransformCookie(tree, projectRoot) {
29
+ const configFile = ['jest.config.cts', 'jest.config.ts', 'jest.config.js']
30
+ .map((f) => (0, devkit_1.joinPathFragments)(projectRoot, f))
31
+ .find((f) => tree.exists(f));
32
+ if (!configFile) {
33
+ return;
34
+ }
35
+ const config = tree.read(configFile, 'utf-8');
36
+ tree.write(configFile, config.replace(/^(\s*)moduleFileExtensions:/m, `$1transformIgnorePatterns: ['/node_modules/(?!(\\\\.pnpm/[^/]+/node_modules/)?cookie/)'],\n$1moduleFileExtensions:`));
37
+ }
16
38
  function updateTsConfigOptions(tree, options) {
17
39
  if (options.isUsingTsSolutionConfig) {
18
40
  return;
@@ -110,8 +132,40 @@ async function applicationGeneratorInternal(tree, schema) {
110
132
  }));
111
133
  }
112
134
  }
135
+ if (options.bundler === 'rolldown') {
136
+ (0, devkit_1.ensurePackage)('@nx/rolldown', versions_1.nxVersion);
137
+ // CommonJS `require` instead of dynamic ESM `import`: `ensurePackage`
138
+ // exposes the temp install via `Module._initPaths`, which ESM ignores.
139
+ const { rolldownInitGenerator, } = require('@nx/rolldown');
140
+ tasks.push(await rolldownInitGenerator(tree, {
141
+ addPlugin: options.addPlugin,
142
+ skipPackageJson: options.skipPackageJson,
143
+ skipFormat: true,
144
+ }));
145
+ // The registration may infer `bundle`/`rolldown:build` instead of `build`.
146
+ if (options.addPlugin) {
147
+ options.buildTargetName = getRolldownBuildTargetName(tree, options.appProjectRoot);
148
+ }
149
+ }
113
150
  (0, lib_1.addAppFiles)(tree, options);
151
+ if (options.serveRuntime) {
152
+ (0, lib_1.addScriptServe)(tree, options);
153
+ }
114
154
  (0, lib_1.addProject)(tree, options, frameworkDependencies);
155
+ if (options.bundler === 'rolldown') {
156
+ const { configurationGenerator: rolldownConfigurationGenerator, } = require('@nx/rolldown');
157
+ await rolldownConfigurationGenerator(tree, {
158
+ project: options.name,
159
+ main: (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src', options.js ? 'main.js' : 'main.ts'),
160
+ outputPath: options.outputPath,
161
+ // Matches the app's module type (see create-project).
162
+ format: options.serveRuntime === 'node' ? 'esm' : 'cjs',
163
+ platform: 'node',
164
+ addPlugin: options.addPlugin,
165
+ skipPackageJson: true,
166
+ skipFormat: true,
167
+ });
168
+ }
115
169
  // If we are using the new TS solution
116
170
  // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
117
171
  if (options.isUsingTsSolutionConfig) {
@@ -134,6 +188,9 @@ async function applicationGeneratorInternal(tree, schema) {
134
188
  skipFormat: true,
135
189
  });
136
190
  tasks.push(jestTask);
191
+ if (options.framework === 'fastify') {
192
+ allowJestToTransformCookie(tree, options.appProjectRoot);
193
+ }
137
194
  // There are no tests by default, so set `--passWithNoTests` to avoid test failure on new project.
138
195
  const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, options.name);
139
196
  projectConfig.targets ??= {};
@@ -170,13 +227,15 @@ async function applicationGeneratorInternal(tree, schema) {
170
227
  // No need for default spec file if unit testing is not setup.
171
228
  tree.delete((0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/app/app.spec.ts'));
172
229
  }
173
- if (options.e2eTestRunner === 'jest') {
230
+ if (options.e2eTestRunner === 'jest' || options.e2eTestRunner === 'vitest') {
174
231
  const e2eTask = await (0, e2e_project_1.e2eProjectGenerator)(tree, {
175
232
  ...options,
176
233
  projectType: options.framework === 'none' ? 'cli' : 'server',
177
234
  name: options.rootProject ? 'e2e' : `${options.name}-e2e`,
178
235
  directory: options.rootProject ? 'e2e' : `${options.appProjectRoot}-e2e`,
179
236
  project: options.name,
237
+ buildTarget: options.buildTargetName,
238
+ testRunner: options.e2eTestRunner,
180
239
  port: options.port,
181
240
  isNest: options.isNest,
182
241
  skipFormat: true,
@@ -193,6 +252,7 @@ async function applicationGeneratorInternal(tree, schema) {
193
252
  const dockerTask = await (0, setup_docker_1.setupDockerGenerator)(tree, {
194
253
  ...options,
195
254
  project: options.name,
255
+ buildTarget: options.buildTargetName,
196
256
  skipFormat: true,
197
257
  skipDockerPlugin: options.skipDockerPlugin ?? false,
198
258
  });
@@ -202,9 +262,28 @@ async function applicationGeneratorInternal(tree, schema) {
202
262
  (0, internal_2.updateTsconfigFiles)(tree, options.appProjectRoot, 'tsconfig.app.json', {
203
263
  module: 'nodenext',
204
264
  moduleResolution: 'nodenext',
265
+ // The TS solution base only emits declarations; the tsc build runs the app.
266
+ ...(options.bundler === 'tsc' && { emitDeclarationOnly: false }),
267
+ // tsconfig.app.json extends the base, not the tsconfig.json that has it.
268
+ ...(options.js && { allowJs: true }),
269
+ // Node runs the sources, so relative imports name `.ts` files.
270
+ ...(options.serveRuntime === 'node' && {
271
+ rewriteRelativeImportExtensions: true,
272
+ }),
205
273
  }, options.linter === 'eslint'
206
274
  ? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs']
207
275
  : undefined);
276
+ const specTsConfig = (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.spec.json');
277
+ if (options.serveRuntime === 'node' && tree.exists(specTsConfig)) {
278
+ // Specs import `./x.ts` too, but rewriting them is unsafe (TS2878) since
279
+ // the app is a referenced project with its own outDir.
280
+ (0, devkit_1.updateJson)(tree, specTsConfig, (json) => {
281
+ delete json.compilerOptions.rewriteRelativeImportExtensions;
282
+ delete json.compilerOptions.emitDeclarationOnly;
283
+ json.compilerOptions.allowImportingTsExtensions = true;
284
+ return json;
285
+ });
286
+ }
208
287
  }
209
288
  (0, internal_2.sortPackageJsonFields)(tree, options.appProjectRoot);
210
289
  if (!options.skipFormat) {
@@ -9,6 +9,10 @@ app.get('/', (req, res) => {
9
9
  res.send({ 'message': 'Hello API'});
10
10
  });
11
11
 
12
- app.listen(port, host, () => {
12
+ const server = app.listen(port, host, () => {
13
13
  console.log(`[ ready ] http://${host}:${port}`);
14
14
  });
15
+
16
+ process.on('SIGTERM', () => {
17
+ server.close(() => process.exit(0));
18
+ });
@@ -1,6 +1,6 @@
1
1
  import Fastify from 'fastify';
2
2
  import type { FastifyInstance } from 'fastify';
3
- import { app } from './app';
3
+ import { app } from './app<%= importExtension %>';
4
4
 
5
5
  describe('GET /', () => {
6
6
  let server: FastifyInstance;
@@ -14,14 +14,14 @@ export async function app(fastify: FastifyInstance, opts: AppOptions) {
14
14
  // those should be support plugins that are reused
15
15
  // through your application
16
16
  fastify.register(AutoLoad, {
17
- dir: path.join(__dirname, 'plugins'),
17
+ dir: path.join(<%= dirname %>, 'plugins'),
18
18
  options: { ...opts },
19
19
  });
20
20
 
21
21
  // This loads all plugins defined in routes
22
22
  // define your routes in one of these
23
23
  fastify.register(AutoLoad, {
24
- dir: path.join(__dirname, 'routes'),
24
+ dir: path.join(<%= dirname %>, 'routes'),
25
25
  options: { ...opts },
26
26
  });
27
27
  }
@@ -1,5 +1,5 @@
1
1
  import Fastify from 'fastify';
2
- import { app } from './app/app';
2
+ import { app } from './app/app<%= importExtension %>';
3
3
 
4
4
  const host = process.env.HOST ?? 'localhost';
5
5
  const port = process.env.PORT ? Number(process.env.PORT) : <%= port %>;
@@ -21,3 +21,8 @@ server.listen({ port, host }, (err) => {
21
21
  console.log(`[ ready ] http://${host}:${port}`);
22
22
  }
23
23
  });
24
+
25
+ process.on('SIGTERM', async () => {
26
+ await server.close();
27
+ process.exit(0);
28
+ });
@@ -9,6 +9,10 @@ app.use(async ctx =>{
9
9
  ctx.body = { 'message': 'Hello API'};
10
10
  });
11
11
 
12
- app.listen(port, host, () => {
12
+ const server = app.listen(port, host, () => {
13
13
  console.log(`[ ready ] http://${host}:${port}`);
14
14
  });
15
+
16
+ process.on('SIGTERM', () => {
17
+ server.close(() => process.exit(0));
18
+ });
@@ -57,6 +57,7 @@ function addProjectDependencies(tree, options) {
57
57
  ...frameworkDevDependencies[options.framework],
58
58
  ...bundlers[options.bundler],
59
59
  '@types/node': typesNodeVersion,
60
+ ...(options.serveRuntime === 'tsx' && { tsx: versions_1.tsxVersion }),
60
61
  }, undefined, options.keepExistingVersions ?? true),
61
62
  frameworkDependencies: frameworkDependencies[options.framework],
62
63
  };
@@ -0,0 +1,7 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ import type { NormalizedSchema } from './normalized-schema';
3
+ /**
4
+ * Prepends a shebang and `@nx` block to the main file so @nx/js/node or
5
+ * @nx/js/tsx infers `serve` as `node --watch` / `tsx watch` on it.
6
+ */
7
+ export declare function addScriptServe(tree: Tree, options: NormalizedSchema): void;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addScriptServe = addScriptServe;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const internal_1 = require("@nx/devkit/internal");
6
+ /**
7
+ * Prepends a shebang and `@nx` block to the main file so @nx/js/node or
8
+ * @nx/js/tsx infers `serve` as `node --watch` / `tsx watch` on it.
9
+ */
10
+ function addScriptServe(tree, options) {
11
+ const runtime = options.serveRuntime;
12
+ const mainFile = (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src', options.js ? 'main.js' : 'main.ts');
13
+ // `^build` builds workspace libraries whose exports point at their output.
14
+ const header = `#!/usr/bin/env ${runtime}
15
+ /**
16
+ * @nx
17
+ * name: serve
18
+ * continuous: true
19
+ * watch: true
20
+ * dependsOn: ["^build"]
21
+ */
22
+ `;
23
+ tree.write(mainFile, header + tree.read(mainFile, 'utf-8'));
24
+ includeAppInScriptPlugin(tree, `@nx/js/${runtime}`, options.appProjectRoot, mainFile,
25
+ // Written later by addProject.
26
+ (0, devkit_1.joinPathFragments)(options.appProjectRoot, options.useProjectJson ? 'project.json' : 'package.json'));
27
+ }
28
+ /**
29
+ * Scopes the plugin to each app's main file and project marker; unscoped, it
30
+ * would sniff every js/ts file in the workspace.
31
+ */
32
+ function includeAppInScriptPlugin(tree, plugin, projectRoot, mainFile, marker) {
33
+ const nxJson = (0, devkit_1.readNxJson)(tree) ?? {};
34
+ nxJson.plugins ??= [];
35
+ const files = '{package.json,project.json,src/main.{ts,js}}';
36
+ const pattern = projectRoot === '.' ? files : `${projectRoot}/${files}`;
37
+ const registrations = nxJson.plugins.filter((p) => (typeof p === 'string' ? p : p.plugin) === plugin);
38
+ const covers = (p) => typeof p === 'string' ||
39
+ (0, internal_1.findMatchingConfigFiles)([mainFile, marker], p.include, p.exclude).length ===
40
+ 2;
41
+ if (registrations.some(covers)) {
42
+ return;
43
+ }
44
+ const scoped = registrations.find((p) => typeof p !== 'string' && !!p.include && !p.exclude);
45
+ if (scoped) {
46
+ scoped.include.push(pattern);
47
+ }
48
+ else {
49
+ nxJson.plugins.push({ plugin, include: [pattern] });
50
+ }
51
+ (0, devkit_1.updateNxJson)(tree, nxJson);
52
+ }
@@ -0,0 +1,8 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ /**
3
+ * Infers a tsc `build` target for the app from its tsconfig.app.json. A shared
4
+ * registration scoped by `include` keeps other apps that have a
5
+ * tsconfig.app.json (vite, webpack, ...) from getting a tsc build too.
6
+ * Returns the name of the inferred build target.
7
+ */
8
+ export declare function addTscAppBuildPlugin(tree: Tree, projectRoot: string): string;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addTscAppBuildPlugin = addTscAppBuildPlugin;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const tsPlugin = '@nx/js/typescript';
6
+ const appBuildConfigName = 'tsconfig.app.json';
7
+ /**
8
+ * Infers a tsc `build` target for the app from its tsconfig.app.json. A shared
9
+ * registration scoped by `include` keeps other apps that have a
10
+ * tsconfig.app.json (vite, webpack, ...) from getting a tsc build too.
11
+ * Returns the name of the inferred build target.
12
+ */
13
+ function addTscAppBuildPlugin(tree, projectRoot) {
14
+ const nxJson = (0, devkit_1.readNxJson)(tree) ?? {};
15
+ nxJson.plugins ??= [];
16
+ // `./*` matches nothing for a root project.
17
+ const pattern = projectRoot === '.' ? '*' : `${projectRoot}/*`;
18
+ const registration = nxJson.plugins.find((p) => typeof p !== 'string' &&
19
+ p.plugin === tsPlugin &&
20
+ p.options?.build?.configName ===
21
+ appBuildConfigName);
22
+ if (!registration) {
23
+ nxJson.plugins.push({
24
+ plugin: tsPlugin,
25
+ include: [pattern],
26
+ options: {
27
+ typecheck: false,
28
+ build: {
29
+ targetName: 'build',
30
+ configName: appBuildConfigName,
31
+ buildDepsName: 'build-deps',
32
+ watchDepsName: 'watch-deps',
33
+ skipBuildCheck: true,
34
+ },
35
+ },
36
+ });
37
+ }
38
+ else if (registration.include && !registration.include.includes(pattern)) {
39
+ registration.include.push(pattern);
40
+ }
41
+ (0, devkit_1.updateNxJson)(tree, nxJson);
42
+ // A reused registration may name the target something other than `build`.
43
+ return (registration?.options?.build?.targetName ?? 'build');
44
+ }
@@ -37,6 +37,8 @@ function addAppFiles(tree, options) {
37
37
  root: options.appProjectRoot,
38
38
  offset: (0, devkit_1.offsetFromRoot)(options.appProjectRoot),
39
39
  rootTsConfigPath: (0, js_1.getRelativePathToRootTsConfig)(tree, options.appProjectRoot),
40
+ importExtension: options.serveRuntime === 'node' ? '.ts' : '',
41
+ dirname: options.serveRuntime === 'node' ? 'import.meta.dirname' : '__dirname',
40
42
  });
41
43
  }
42
44
  if (options.js) {
@@ -5,6 +5,7 @@ const internal_1 = require("@nx/devkit/internal");
5
5
  const devkit_1 = require("@nx/devkit");
6
6
  const internal_2 = require("@nx/js/internal");
7
7
  const has_webpack_plugin_1 = require("../../../utils/has-webpack-plugin");
8
+ const add_tsc_build_plugin_1 = require("./add-tsc-build-plugin");
8
9
  const create_targets_1 = require("./create-targets");
9
10
  function addProject(tree, options, frameworkDependencies) {
10
11
  const project = {
@@ -38,15 +39,28 @@ function addProject(tree, options, frameworkDependencies) {
38
39
  ensurePnpmDeployInputs(tree, options, project.targets.build);
39
40
  }
40
41
  }
42
+ else if (options.bundler === 'tsc') {
43
+ if (options.addPlugin) {
44
+ options.buildTargetName = (0, add_tsc_build_plugin_1.addTscAppBuildPlugin)(tree, options.appProjectRoot);
45
+ }
46
+ else {
47
+ project.targets.build = (0, create_targets_1.getTscBuildConfig)(tree, options);
48
+ }
49
+ }
50
+ // The rolldown build target comes from @nx/rolldown:configuration.
41
51
  project.targets = {
42
52
  ...project.targets,
43
- ...(0, create_targets_1.getPruneTargets)('build', options.outputPath),
53
+ ...(0, create_targets_1.getPruneTargets)(options.buildTargetName, options.outputPath),
44
54
  };
45
- project.targets.serve = (0, create_targets_1.getServeConfig)(options);
55
+ if (!options.serveRuntime) {
56
+ project.targets.serve = (0, create_targets_1.getServeConfig)(options);
57
+ }
46
58
  const packageJson = {
47
59
  name: options.importPath,
48
60
  version: '0.0.1',
49
61
  private: true,
62
+ // Node runs the TypeScript source as ESM; without this it warns on every start.
63
+ ...(options.serveRuntime === 'node' && { type: 'module' }),
50
64
  dependencies: { ...frameworkDependencies },
51
65
  };
52
66
  if (!options.useProjectJson) {
@@ -59,7 +73,10 @@ function addProject(tree, options, frameworkDependencies) {
59
73
  else {
60
74
  (0, devkit_1.addProjectConfiguration)(tree, options.name, project);
61
75
  }
62
- if (!options.useProjectJson || options.isUsingTsSolutionConfig) {
76
+ // The prune targets read the project package.json for the deps to deploy.
77
+ if (!options.useProjectJson ||
78
+ options.isUsingTsSolutionConfig ||
79
+ !options.rootProject) {
63
80
  (0, devkit_1.writeJson)(tree, (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'package.json'), packageJson);
64
81
  }
65
82
  }
@@ -1,6 +1,8 @@
1
1
  import { ProjectConfiguration, Tree, TargetConfiguration } from '@nx/devkit';
2
2
  import { NormalizedSchema } from './normalized-schema';
3
3
  export declare function getWebpackBuildConfig(tree: Tree, project: ProjectConfiguration, options: NormalizedSchema): TargetConfiguration;
4
+ /** Explicit form of the build target @nx/js/typescript infers from tsconfig.app.json. */
5
+ export declare function getTscBuildConfig(tree: Tree, options: NormalizedSchema): TargetConfiguration;
4
6
  export declare function getEsBuildConfig(tree: Tree, project: ProjectConfiguration, options: NormalizedSchema): TargetConfiguration;
5
7
  export declare function getServeConfig(options: NormalizedSchema): TargetConfiguration;
6
8
  export declare function getPruneTargets(buildTarget: string, outputPath: string): {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getWebpackBuildConfig = getWebpackBuildConfig;
4
+ exports.getTscBuildConfig = getTscBuildConfig;
4
5
  exports.getEsBuildConfig = getEsBuildConfig;
5
6
  exports.getServeConfig = getServeConfig;
6
7
  exports.getPruneTargets = getPruneTargets;
@@ -33,6 +34,21 @@ function getWebpackBuildConfig(tree, project, options) {
33
34
  },
34
35
  };
35
36
  }
37
+ /** Explicit form of the build target @nx/js/typescript infers from tsconfig.app.json. */
38
+ function getTscBuildConfig(tree, options) {
39
+ const namedInputs = (0, devkit_1.readNxJson)(tree)?.namedInputs ?? {};
40
+ return {
41
+ command: 'tsc --build tsconfig.app.json',
42
+ options: { cwd: options.appProjectRoot },
43
+ cache: true,
44
+ dependsOn: ['^build'],
45
+ inputs: 'production' in namedInputs
46
+ ? ['production', '^production']
47
+ : ['default', '^default'],
48
+ outputs: ['{projectRoot}/dist'],
49
+ syncGenerators: ['@nx/js:typescript-sync'],
50
+ };
51
+ }
36
52
  function getEsBuildConfig(tree, project, options) {
37
53
  const sourceRoot = (0, internal_1.getProjectSourceRoot)(project, tree);
38
54
  return {
@@ -42,8 +58,8 @@ function getEsBuildConfig(tree, project, options) {
42
58
  options: {
43
59
  platform: 'node',
44
60
  outputPath: options.outputPath,
45
- // Use CJS for Node apps for widest compatibility.
46
- format: ['cjs'],
61
+ // CJS for widest compatibility, unless the app is ESM (`"type": "module"`).
62
+ format: [options.serveRuntime === 'node' ? 'esm' : 'cjs'],
47
63
  bundle: false,
48
64
  main: (0, devkit_1.joinPathFragments)(sourceRoot, 'main' + (options.js ? '.js' : '.ts')),
49
65
  tsConfig: (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.app.json'),
@@ -69,6 +85,18 @@ function getEsBuildConfig(tree, project, options) {
69
85
  };
70
86
  }
71
87
  function getServeConfig(options) {
88
+ if (options.bundler === 'tsc' || options.bundler === 'rolldown') {
89
+ // These build targets have no development/production configurations.
90
+ return {
91
+ continuous: true,
92
+ executor: '@nx/js:node',
93
+ dependsOn: [options.buildTargetName],
94
+ options: {
95
+ buildTarget: `${options.name}:${options.buildTargetName}`,
96
+ runBuildTargetDependencies: false,
97
+ },
98
+ };
99
+ }
72
100
  return {
73
101
  continuous: true,
74
102
  executor: '@nx/js:node',
@@ -128,7 +156,7 @@ function getPruneTargets(buildTarget, outputPath) {
128
156
  }
129
157
  return {
130
158
  'prune-lockfile': {
131
- dependsOn: ['build'],
159
+ dependsOn: [buildTarget],
132
160
  cache: true,
133
161
  executor: '@nx/js:prune-lockfile',
134
162
  ...(pruneLockfileInputs ? { inputs: pruneLockfileInputs } : {}),
@@ -138,7 +166,7 @@ function getPruneTargets(buildTarget, outputPath) {
138
166
  },
139
167
  },
140
168
  'copy-workspace-modules': {
141
- dependsOn: ['build'],
169
+ dependsOn: [buildTarget],
142
170
  cache: true,
143
171
  outputs: [
144
172
  `{workspaceRoot}/${(0, devkit_1.joinPathFragments)(outputPath, 'workspace_modules')}`,
@@ -6,3 +6,4 @@ export * from './create-files';
6
6
  export * from './add-dependencies';
7
7
  export * from './add-linting';
8
8
  export * from './add-proxy';
9
+ export * from './add-script-serve';
@@ -9,3 +9,4 @@ tslib_1.__exportStar(require("./create-files"), exports);
9
9
  tslib_1.__exportStar(require("./add-dependencies"), exports);
10
10
  tslib_1.__exportStar(require("./add-linting"), exports);
11
11
  tslib_1.__exportStar(require("./add-proxy"), exports);
12
+ tslib_1.__exportStar(require("./add-script-serve"), exports);
@@ -4,6 +4,7 @@ exports.normalizeOptions = normalizeOptions;
4
4
  const devkit_1 = require("@nx/devkit");
5
5
  const internal_1 = require("@nx/devkit/internal");
6
6
  const internal_2 = require("@nx/js/internal");
7
+ const semver_1 = require("semver");
7
8
  async function normalizeOptions(host, options) {
8
9
  await (0, internal_1.ensureRootProjectName)(options, 'application');
9
10
  const { projectName, projectRoot: appProjectRoot, importPath, } = await (0, internal_1.determineProjectNameAndRootOptions)(host, {
@@ -13,8 +14,6 @@ async function normalizeOptions(host, options) {
13
14
  rootProject: options.rootProject,
14
15
  });
15
16
  options.rootProject = appProjectRoot === '.';
16
- options.bundler = options.bundler ?? 'esbuild';
17
- options.e2eTestRunner = options.e2eTestRunner ?? 'jest';
18
17
  const parsedTags = options.tags
19
18
  ? options.tags.split(',').map((s) => s.trim())
20
19
  : [];
@@ -23,7 +22,26 @@ async function normalizeOptions(host, options) {
23
22
  (process.env.NX_ADD_PLUGINS !== 'false' &&
24
23
  nxJson.useInferencePlugins !== false);
25
24
  const isUsingTsSolutionConfig = (0, internal_2.isUsingTsSolutionSetup)(host);
25
+ // Legacy workspaces resolve imports through tsconfig paths, which tsc does not
26
+ // rewrite in emitted JS; rolldown bundles them instead. Fastify autoloads
27
+ // plugins/ and routes/ from disk, so it needs unbundled output.
28
+ options.bundler ??= isUsingTsSolutionConfig
29
+ ? 'tsc'
30
+ : options.framework === 'fastify'
31
+ ? 'esbuild'
32
+ : 'rolldown';
33
+ if (options.bundler === 'tsc' && !isUsingTsSolutionConfig) {
34
+ throw new Error(`The "tsc" bundler requires a TypeScript solution workspace (project references). This workspace uses tsconfig path aliases, which tsc does not rewrite in emitted JavaScript. Use "--bundler=rolldown" or "--bundler=esbuild" instead.`);
35
+ }
36
+ if (options.bundler === 'rolldown' && options.framework === 'fastify') {
37
+ // The fastify template autoloads plugins/ and routes/ from disk at runtime,
38
+ // which a single-file bundle does not emit.
39
+ throw new Error(`The "rolldown" bundler does not support the "fastify" framework. Use "--bundler=tsc" or "--bundler=esbuild" instead.`);
40
+ }
26
41
  const swcJest = options.swcJest ?? isUsingTsSolutionConfig;
42
+ const serveRuntime = getServeRuntime(options, addPlugin, isUsingTsSolutionConfig);
43
+ // Jest loads the app's ESM sources as CommonJS.
44
+ const unitTestRunner = options.unitTestRunner ?? (serveRuntime === 'node' ? 'vitest' : 'jest');
27
45
  const appProjectName = !isUsingTsSolutionConfig || options.name ? projectName : importPath;
28
46
  const useProjectJson = options.useProjectJson ?? !isUsingTsSolutionConfig;
29
47
  return {
@@ -37,9 +55,13 @@ async function normalizeOptions(host, options) {
37
55
  importPath,
38
56
  parsedTags,
39
57
  linter: await (0, internal_2.normalizeLinterOption)(host, options.linter),
40
- unitTestRunner: options.unitTestRunner ?? 'jest',
58
+ unitTestRunner,
59
+ e2eTestRunner: options.e2eTestRunner ??
60
+ (unitTestRunner === 'vitest' ? 'vitest' : 'jest'),
41
61
  rootProject: options.rootProject ?? false,
42
62
  port: options.port ?? 3000,
63
+ buildTargetName: 'build',
64
+ serveRuntime,
43
65
  outputPath: isUsingTsSolutionConfig
44
66
  ? (0, devkit_1.joinPathFragments)(appProjectRoot, 'dist')
45
67
  : (0, devkit_1.joinPathFragments)('dist', options.rootProject ? appProjectName : appProjectRoot),
@@ -48,3 +70,23 @@ async function normalizeOptions(host, options) {
48
70
  useProjectJson,
49
71
  };
50
72
  }
73
+ function getServeRuntime(options, addPlugin, isUsingTsSolutionConfig) {
74
+ // Webpack apps keep the explicit @nx/js:node serve.
75
+ if (!addPlugin || options.bundler === 'webpack') {
76
+ return undefined;
77
+ }
78
+ // Node runs TypeScript natively but does not resolve tsconfig path aliases.
79
+ // Its ESM sources import `./x.ts`, which only tsc and rolldown rewrite on build,
80
+ // Jest loads ESM sources as CommonJS, so `--js` and jest apps stay CommonJS on tsx.
81
+ if (!isUsingTsSolutionConfig ||
82
+ (options.bundler !== 'tsc' && options.bundler !== 'rolldown') ||
83
+ options.js ||
84
+ options.unitTestRunner === 'jest') {
85
+ return 'tsx';
86
+ }
87
+ if (!(0, semver_1.satisfies)(process.versions.node, '^22.18.0 || >=23.6.0')) {
88
+ devkit_1.logger.warn(`Node.js ${process.versions.node} cannot run TypeScript natively, so "serve" uses tsx. Use Node.js 22.18 or later to serve with node.`);
89
+ return 'tsx';
90
+ }
91
+ return 'node';
92
+ }
@@ -3,6 +3,9 @@ export interface NormalizedSchema extends Omit<Schema, 'useTsSolution'> {
3
3
  appProjectRoot: string;
4
4
  parsedTags: string[];
5
5
  outputPath: string;
6
+ buildTargetName: string;
7
+ /** Runs the main file for an inferred `serve`; unset means an explicit @nx/js:node serve. */
8
+ serveRuntime?: 'node' | 'tsx';
6
9
  importPath: string;
7
10
  isUsingTsSolutionConfig: boolean;
8
11
  }
@@ -6,7 +6,7 @@ export interface Schema {
6
6
  skipFormat?: boolean;
7
7
  skipPackageJson?: boolean;
8
8
  unitTestRunner?: 'jest' | 'vitest' | 'none';
9
- e2eTestRunner?: 'jest' | 'none';
9
+ e2eTestRunner?: 'jest' | 'vitest' | 'none';
10
10
  linter?: LinterType;
11
11
  formatter?: 'none' | 'prettier' | 'oxfmt';
12
12
  tags?: string;
@@ -20,7 +20,7 @@ export interface Schema {
20
20
  * @deprecated Use `enableTypedLinting` instead. This option will be removed in Nx v24.
21
21
  */
22
22
  setParserOptionsProject?: boolean;
23
- bundler?: 'esbuild' | 'webpack';
23
+ bundler?: 'tsc' | 'rolldown' | 'esbuild' | 'webpack';
24
24
  framework?: NodeJsFrameWorks;
25
25
  port?: number;
26
26
  rootProject?: boolean;
@@ -54,7 +54,7 @@
54
54
  },
55
55
  "e2eTestRunner": {
56
56
  "type": "string",
57
- "enum": ["jest", "none"],
57
+ "enum": ["jest", "vitest", "none"],
58
58
  "description": "Test runner to use for end-to-end tests",
59
59
  "default": "none",
60
60
  "x-priority": "important",
@@ -97,10 +97,9 @@
97
97
  "x-deprecated": "Use `enableTypedLinting` instead. This option will be removed in Nx v24."
98
98
  },
99
99
  "bundler": {
100
- "description": "Bundler which is used to package the application",
100
+ "description": "Bundler which is used to package the application. Defaults to `tsc` in TypeScript solution workspaces, otherwise `rolldown`. `tsc` requires a TypeScript solution workspace.",
101
101
  "type": "string",
102
- "enum": ["esbuild", "webpack"],
103
- "default": "esbuild",
102
+ "enum": ["tsc", "rolldown", "esbuild", "webpack"],
104
103
  "x-priority": "important"
105
104
  },
106
105
  "framework": {
@@ -8,6 +8,7 @@ const devkit_1 = require("@nx/devkit");
8
8
  const eslint_1 = require("@nx/eslint");
9
9
  const internal_2 = require("@nx/js/internal");
10
10
  const internal_3 = require("@nx/eslint/internal");
11
+ const node_fs_1 = require("node:fs");
11
12
  const path = tslib_1.__importStar(require("path"));
12
13
  const versions_1 = require("../../utils/versions");
13
14
  const internal_4 = require("@nx/jest/internal");
@@ -27,6 +28,10 @@ async function e2eProjectGeneratorInternal(host, _options) {
27
28
  const jestMajorVersion = (0, internal_4.getInstalledJestMajorVersion)(host);
28
29
  const useCommonJsConfig = jestMajorVersion === null || jestMajorVersion >= 30;
29
30
  const jestConfigExt = useCommonJsConfig ? 'cts' : 'ts';
31
+ const dependsOn = [
32
+ `${options.project}:${options.buildTarget ?? 'build'}`,
33
+ `${options.project}:serve`,
34
+ ];
30
35
  // TODO(@ndcunningham): This is broken.. the outputs are wrong.. and this isn't using the jest generator
31
36
  const packageJson = {
32
37
  name: options.importPath,
@@ -40,15 +45,18 @@ async function e2eProjectGeneratorInternal(host, _options) {
40
45
  : undefined,
41
46
  implicitDependencies: [options.project],
42
47
  targets: {
43
- e2e: {
44
- executor: '@nx/jest:jest',
45
- outputs: ['{projectRoot}/test-output/jest/coverage'],
46
- options: {
47
- jestConfig: `${options.e2eProjectRoot}/jest.config.${jestConfigExt}`,
48
- passWithNoTests: true,
48
+ // @nx/vitest infers the rest of the vitest e2e target.
49
+ e2e: options.testRunner === 'vitest'
50
+ ? { dependsOn }
51
+ : {
52
+ executor: '@nx/jest:jest',
53
+ outputs: ['{projectRoot}/test-output/jest/coverage'],
54
+ options: {
55
+ jestConfig: `${options.e2eProjectRoot}/jest.config.${jestConfigExt}`,
56
+ passWithNoTests: true,
57
+ },
58
+ dependsOn,
49
59
  },
50
- dependsOn: [`${options.project}:build`, `${options.project}:serve`],
51
- },
52
60
  },
53
61
  };
54
62
  }
@@ -58,15 +66,17 @@ async function e2eProjectGeneratorInternal(host, _options) {
58
66
  implicitDependencies: [options.project],
59
67
  projectType: 'application',
60
68
  targets: {
61
- e2e: {
62
- executor: '@nx/jest:jest',
63
- outputs: ['{workspaceRoot}/coverage/{e2eProjectRoot}'],
64
- options: {
65
- jestConfig: `${options.e2eProjectRoot}/jest.config.${jestConfigExt}`,
66
- passWithNoTests: true,
69
+ e2e: options.testRunner === 'vitest'
70
+ ? { dependsOn }
71
+ : {
72
+ executor: '@nx/jest:jest',
73
+ outputs: ['{workspaceRoot}/coverage/{e2eProjectRoot}'],
74
+ options: {
75
+ jestConfig: `${options.e2eProjectRoot}/jest.config.${jestConfigExt}`,
76
+ passWithNoTests: true,
77
+ },
78
+ dependsOn,
67
79
  },
68
- dependsOn: [`${options.project}:build`, `${options.project}:serve`],
69
- },
70
80
  },
71
81
  });
72
82
  }
@@ -98,6 +108,10 @@ async function e2eProjectGeneratorInternal(host, _options) {
98
108
  };
99
109
  });
100
110
  }
111
+ // The app may use another unit test runner, leaving no Jest setup to share.
112
+ if (options.testRunner !== 'vitest' && !(0, internal_4.findRootJestPreset)(host)) {
113
+ tasks.push(await (0, internal_4.ensureRootJest)(host));
114
+ }
101
115
  const jestPreset = (0, internal_4.findRootJestPreset)(host) ?? 'jest.preset.js';
102
116
  const tsConfigFile = options.isUsingTsSolutionConfig
103
117
  ? 'tsconfig.json'
@@ -115,6 +129,7 @@ async function e2eProjectGeneratorInternal(host, _options) {
115
129
  offsetFromRoot: rootOffset,
116
130
  jestPreset,
117
131
  coverageDirectory,
132
+ js: useCommonJsConfig,
118
133
  tmpl: '',
119
134
  });
120
135
  if (options.isNest) {
@@ -128,7 +143,12 @@ async function e2eProjectGeneratorInternal(host, _options) {
128
143
  }
129
144
  }
130
145
  else if (options.projectType === 'cli') {
131
- const mainFile = appProject.targets.build?.options?.outputPath;
146
+ // Inferred builds (tsc, rolldown) have no outputPath option; use the
147
+ // output dir the app generator configures.
148
+ const mainFile = appProject.targets.build?.options?.outputPath ??
149
+ (0, devkit_1.joinPathFragments)(options.isUsingTsSolutionConfig
150
+ ? (0, devkit_1.joinPathFragments)(appProject.root, 'dist')
151
+ : (0, devkit_1.joinPathFragments)('dist', appProject.root === '.' ? options.project : appProject.root), 'main.js');
132
152
  (0, devkit_1.generateFiles)(host, path.join(__dirname, 'files/cli'), options.e2eProjectRoot, {
133
153
  ...options,
134
154
  ...(0, devkit_1.names)(options.rootProject ? 'cli' : projectSimpleName),
@@ -137,16 +157,40 @@ async function e2eProjectGeneratorInternal(host, _options) {
137
157
  offsetFromRoot: rootOffset,
138
158
  jestPreset,
139
159
  coverageDirectory,
160
+ js: useCommonJsConfig,
140
161
  tmpl: '',
141
162
  });
142
163
  }
164
+ if (options.testRunner === 'vitest') {
165
+ tasks.push(await addVitest(host, options.e2eProjectRoot, options.e2eProjectName, dependsOn));
166
+ host.delete((0, devkit_1.joinPathFragments)(options.e2eProjectRoot, 'jest.config.ts'));
167
+ for (const file of [
168
+ 'src/test-setup.ts',
169
+ 'src/support/global-teardown.ts',
170
+ ]) {
171
+ const filePath = (0, devkit_1.joinPathFragments)(options.e2eProjectRoot, file);
172
+ if (host.exists(filePath))
173
+ host.delete(filePath);
174
+ }
175
+ for (const dir of ['common', options.projectType]) {
176
+ const templates = path.join(__dirname, 'files/vitest', dir);
177
+ if ((0, node_fs_1.existsSync)(templates)) {
178
+ (0, devkit_1.generateFiles)(host, templates, options.e2eProjectRoot, {
179
+ ...options,
180
+ tmpl: '',
181
+ });
182
+ }
183
+ }
184
+ }
143
185
  // Rename jest.config.ts to jest.config.cts for Jest 30+
144
186
  const jestConfigPath = (0, devkit_1.joinPathFragments)(options.e2eProjectRoot, 'jest.config.ts');
145
187
  if (host.exists(jestConfigPath) && useCommonJsConfig) {
146
188
  host.rename(jestConfigPath, (0, devkit_1.joinPathFragments)(options.e2eProjectRoot, 'jest.config.cts'));
147
189
  }
148
190
  if (options.isUsingTsSolutionConfig) {
149
- (0, internal_2.addSwcTestConfig)(host, options.e2eProjectRoot, 'es6');
191
+ if (options.testRunner !== 'vitest') {
192
+ (0, internal_2.addSwcTestConfig)(host, options.e2eProjectRoot, 'es6');
193
+ }
150
194
  (0, devkit_1.generateFiles)(host, path.join(__dirname, 'files/ts-solution'), options.e2eProjectRoot, {
151
195
  ...options,
152
196
  relativeProjectReferencePath: (0, posix_1.relative)(options.e2eProjectRoot, appProject.root),
@@ -174,7 +218,7 @@ async function e2eProjectGeneratorInternal(host, _options) {
174
218
  addPlugin: options.addPlugin,
175
219
  // The generated e2e specs are Jest; this is what enables the linter's
176
220
  // Jest rules.
177
- unitTestRunner: 'jest',
221
+ unitTestRunner: options.testRunner,
178
222
  }));
179
223
  }
180
224
  if (options.linter === 'eslint') {
@@ -247,9 +291,60 @@ async function normalizeOptions(tree, options) {
247
291
  e2eProjectName,
248
292
  importPath,
249
293
  port: options.port ?? 3000,
294
+ testRunner: options.testRunner ?? 'jest',
250
295
  rootProject: !!options.rootProject,
251
296
  isUsingTsSolutionConfig,
252
297
  useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig,
253
298
  };
254
299
  }
300
+ async function addVitest(tree, e2eProjectRoot, e2eProjectName, dependsOn) {
301
+ (0, devkit_1.ensurePackage)('@nx/vitest', versions_1.nxVersion);
302
+ // CommonJS `require` instead of dynamic ESM `import`: `ensurePackage`
303
+ // exposes the temp install via `Module._initPaths`, which ESM ignores.
304
+ const { initGenerator, } = require('@nx/vitest/generators');
305
+ const task = await initGenerator(tree, {
306
+ addPlugin: false,
307
+ skipFormat: true,
308
+ });
309
+ // A scoped registration infers `e2e`; the others would add a `test` target.
310
+ const include = `${e2eProjectRoot}/**/*`;
311
+ const nxJson = (0, devkit_1.readNxJson)(tree);
312
+ // Split tasks get no `dependsOn` from `e2e`, so give them the app's serve here.
313
+ const ciTargetDefaults = nxJson.targetDefaults?.['e2e-ci--**/**'];
314
+ (0, devkit_1.updateNxJson)(tree, {
315
+ ...nxJson,
316
+ targetDefaults: {
317
+ ...nxJson.targetDefaults,
318
+ 'e2e-ci--**/**': [
319
+ ...(Array.isArray(ciTargetDefaults)
320
+ ? ciTargetDefaults
321
+ : ciTargetDefaults
322
+ ? [ciTargetDefaults]
323
+ : []),
324
+ { filter: { projects: [e2eProjectName] }, dependsOn },
325
+ ],
326
+ },
327
+ plugins: [
328
+ ...(nxJson.plugins ?? []).map((p) => {
329
+ const registration = typeof p === 'string' ? { plugin: p } : p;
330
+ return registration.plugin === '@nx/vitest'
331
+ ? {
332
+ ...registration,
333
+ exclude: [...(registration.exclude ?? []), include],
334
+ }
335
+ : p;
336
+ }),
337
+ {
338
+ plugin: '@nx/vitest',
339
+ include: [include],
340
+ options: {
341
+ testTargetName: 'e2e',
342
+ ciTargetName: 'e2e-ci',
343
+ testMode: 'run',
344
+ },
345
+ },
346
+ ],
347
+ });
348
+ return task;
349
+ }
255
350
  exports.default = e2eProjectGenerator;
@@ -1,6 +1,6 @@
1
1
  <%_ if (isUsingTsSolutionConfig) { _%>
2
2
  /* eslint-disable */
3
- import { readFileSync } from 'fs';
3
+ <% if (js) { %>const { readFileSync } = require('fs');<% } else { %>import { readFileSync } from 'fs';<% } %>
4
4
 
5
5
  // Reading the SWC compilation config for the spec files
6
6
  const swcJestConfig = JSON.parse(
@@ -11,7 +11,7 @@ const swcJestConfig = JSON.parse(
11
11
  swcJestConfig.swcrc = false;
12
12
 
13
13
  <%_ } _%>
14
- export default {
14
+ <% if (js) { %>module.exports =<% } else { %>export default<% } %> {
15
15
  displayName: '<%= e2eProjectName %>',
16
16
  preset: '<%= offsetFromRoot %><%= jestPreset %>',
17
17
  setupFiles: ['<rootDir>/src/test-setup.ts'],
@@ -3,10 +3,10 @@
3
3
  "compilerOptions": {
4
4
  "outDir": "<%= offsetFromRoot %>dist/out-tsc",
5
5
  "module": "commonjs",
6
- "types": ["jest", "node"]
6
+ "types": <%- testRunner === 'vitest' ? '["vitest/globals", "node"]' : '["jest", "node"]' %>
7
7
  },
8
8
  "include": [
9
- "jest.config.ts",
9
+ "<%= testRunner === 'vitest' ? 'vitest.config.mts' : 'jest.config.ts' %>",
10
10
  "src/**/*.ts"
11
11
  ]
12
12
  }
@@ -1,6 +1,6 @@
1
1
  <%_ if (isUsingTsSolutionConfig) { _%>
2
2
  /* eslint-disable */
3
- import { readFileSync } from 'fs';
3
+ <% if (js) { %>const { readFileSync } = require('fs');<% } else { %>import { readFileSync } from 'fs';<% } %>
4
4
 
5
5
  // Reading the SWC compilation config for the spec files
6
6
  const swcJestConfig = JSON.parse(
@@ -11,7 +11,7 @@ const swcJestConfig = JSON.parse(
11
11
  swcJestConfig.swcrc = false;
12
12
 
13
13
  <%_ } _%>
14
- export default {
14
+ <% if (js) { %>module.exports =<% } else { %>export default<% } %> {
15
15
  displayName: '<%= e2eProjectName %>',
16
16
  preset: '<%= offsetFromRoot %><%= jestPreset %>',
17
17
  globalSetup: '<rootDir>/src/support/global-setup.ts',
@@ -4,10 +4,11 @@
4
4
  "outDir": "out-tsc/<%= e2eProjectName %>",
5
5
  "esModuleInterop": true,
6
6
  "noUnusedLocals": false,
7
- "noImplicitAny": false
7
+ "noImplicitAny": false,
8
+ "types": <%- testRunner === 'vitest' ? '["vitest/globals", "node"]' : '["jest", "node"]' %>
8
9
  },
9
10
  "include": [
10
- "jest.config.ts",
11
+ "<%= testRunner === 'vitest' ? 'vitest.config.mts' : 'jest.config.ts' %>",
11
12
  "src/**/*.ts"
12
13
  ],
13
14
  "references": []
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ name: '<%= e2eProjectName %>',
6
+ globals: true,
7
+ environment: 'node',
8
+ include: ['src/**/*.spec.ts'],<% if (projectType === 'server') { %>
9
+ globalSetup: ['src/support/global-setup.ts'],
10
+ setupFiles: ['src/support/test-setup.ts'],<% } %>
11
+ },
12
+ });
@@ -0,0 +1,15 @@
1
+ import { waitForPortOpen } from '@nx/node/utils';
2
+
3
+ const host = process.env.HOST ?? 'localhost';
4
+ const port = process.env.PORT ? Number(process.env.PORT) : <%= port %>;
5
+
6
+ export async function setup() {
7
+ // Start services that the app needs to run (e.g. database, docker-compose, etc.).
8
+ console.log('\nSetting up...\n');
9
+ await waitForPortOpen(port, { host });
10
+ }
11
+
12
+ export async function teardown() {
13
+ // Put clean up logic here (e.g. stopping services, docker-compose, etc.).
14
+ console.log('\nTearing down...\n');
15
+ }
@@ -0,0 +1,6 @@
1
+ import axios from 'axios';
2
+
3
+ // Configure axios for tests to use.
4
+ const host = process.env.HOST ?? 'localhost';
5
+ const port = process.env.PORT ?? '<%= port %>';
6
+ axios.defaults.baseURL = `http://${host}:${port}`;
@@ -10,4 +10,7 @@ export interface Schema {
10
10
  skipFormat?: boolean;
11
11
  addPlugin?: boolean;
12
12
  useProjectJson?: boolean;
13
+ /** Name of the app's build target. Defaults to `build`. */
14
+ buildTarget?: string;
15
+ testRunner?: 'jest' | 'vitest';
13
16
  }
@@ -65,6 +65,12 @@
65
65
  "useProjectJson": {
66
66
  "type": "boolean",
67
67
  "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file."
68
+ },
69
+ "testRunner": {
70
+ "description": "Test runner to use for the E2E tests.",
71
+ "type": "string",
72
+ "enum": ["jest", "vitest"],
73
+ "default": "jest"
68
74
  }
69
75
  },
70
76
  "required": ["project"]
@@ -1,6 +1,7 @@
1
1
  import { type Tree } from '@nx/devkit';
2
2
  export declare const nxVersion: any;
3
3
  export declare const tslibVersion = "^2.3.0";
4
+ export declare const tsxVersion = "^4.20.5";
4
5
  export declare const minSupportedNodeTypesVersion = "22.0.0";
5
6
  export declare const typesNodeVersion = "^22.0.0";
6
7
  type NodeTypesVersions = {
@@ -27,10 +28,10 @@ type KoaVersions = {
27
28
  export declare function koaVersions(tree: Tree): KoaVersions;
28
29
  export declare function getInstalledKoaVersion(tree?: Tree): string | null;
29
30
  export declare const minSupportedFastifyVersion = "4.0.0";
30
- export declare const fastifyVersion = "~5.2.1";
31
- export declare const fastifyAutoloadVersion = "~6.0.3";
32
- export declare const fastifySensibleVersion = "~6.0.2";
33
- export declare const fastifyPluginVersion = "~5.0.1";
31
+ export declare const fastifyVersion = "~5.12.5";
32
+ export declare const fastifyAutoloadVersion = "~6.5.0";
33
+ export declare const fastifySensibleVersion = "~6.0.6";
34
+ export declare const fastifyPluginVersion = "~6.0.0";
34
35
  type FastifyVersions = {
35
36
  fastifyVersion: string;
36
37
  fastifyAutoloadVersion: string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.axiosVersion = exports.fastifyPluginVersion = exports.fastifySensibleVersion = exports.fastifyAutoloadVersion = exports.fastifyVersion = exports.minSupportedFastifyVersion = exports.koaTypingsVersion = exports.koaVersion = exports.minSupportedKoaVersion = exports.expressTypingsVersion = exports.expressVersion = exports.minSupportedExpressVersion = exports.typesNodeVersion = exports.minSupportedNodeTypesVersion = exports.tslibVersion = exports.nxVersion = void 0;
3
+ exports.axiosVersion = exports.fastifyPluginVersion = exports.fastifySensibleVersion = exports.fastifyAutoloadVersion = exports.fastifyVersion = exports.minSupportedFastifyVersion = exports.koaTypingsVersion = exports.koaVersion = exports.minSupportedKoaVersion = exports.expressTypingsVersion = exports.expressVersion = exports.minSupportedExpressVersion = exports.typesNodeVersion = exports.minSupportedNodeTypesVersion = exports.tsxVersion = exports.tslibVersion = exports.nxVersion = void 0;
4
4
  exports.nodeTypesVersions = nodeTypesVersions;
5
5
  exports.getInstalledNodeTypesVersion = getInstalledNodeTypesVersion;
6
6
  exports.expressVersions = expressVersions;
@@ -14,6 +14,7 @@ const semver_1 = require("semver");
14
14
  const path_1 = require("path");
15
15
  exports.nxVersion = require((0, path_1.join)('@nx/node', 'package.json')).version;
16
16
  exports.tslibVersion = '^2.3.0';
17
+ exports.tsxVersion = '^4.20.5';
17
18
  // `@types/node` — supported window covers the active LTS majors (currently v22
18
19
  // and v24). Fresh installs default to the active LTS (v22).
19
20
  exports.minSupportedNodeTypesVersion = '22.0.0';
@@ -92,10 +93,10 @@ function getInstalledKoaVersion(tree) {
92
93
  }
93
94
  // Fastify — upstream "last 2 majors" policy. Support window v4 + v5.
94
95
  exports.minSupportedFastifyVersion = '4.0.0';
95
- exports.fastifyVersion = '~5.2.1';
96
- exports.fastifyAutoloadVersion = '~6.0.3';
97
- exports.fastifySensibleVersion = '~6.0.2';
98
- exports.fastifyPluginVersion = '~5.0.1';
96
+ exports.fastifyVersion = '~5.12.5';
97
+ exports.fastifyAutoloadVersion = '~6.5.0';
98
+ exports.fastifySensibleVersion = '~6.0.6';
99
+ exports.fastifyPluginVersion = '~6.0.0';
99
100
  const fastifyLatest = {
100
101
  fastifyVersion: exports.fastifyVersion,
101
102
  fastifyAutoloadVersion: exports.fastifyAutoloadVersion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/node",
3
- "version": "24.0.0-beta.0",
3
+ "version": "24.0.0-beta.2",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "files": [
@@ -70,19 +70,20 @@
70
70
  },
71
71
  "dependencies": {
72
72
  "tslib": "^2.3.0",
73
- "@nx/devkit": "24.0.0-beta.0",
74
- "@nx/jest": "24.0.0-beta.0",
75
- "@nx/js": "24.0.0-beta.0",
76
- "@nx/eslint": "24.0.0-beta.0",
77
- "@nx/docker": "24.0.0-beta.0",
73
+ "@nx/devkit": "24.0.0-beta.2",
74
+ "@nx/jest": "24.0.0-beta.2",
75
+ "@nx/js": "24.0.0-beta.2",
76
+ "@nx/eslint": "24.0.0-beta.2",
77
+ "@nx/docker": "24.0.0-beta.2",
78
78
  "semver": "^7.6.3",
79
79
  "tcp-port-used": "^1.0.2",
80
80
  "kill-port": "^1.6.1"
81
81
  },
82
82
  "devDependencies": {
83
- "nx": "24.0.0-beta.0",
84
- "@nx/webpack": "24.0.0-beta.0",
85
- "@nx/vitest": "24.0.0-beta.0"
83
+ "nx": "24.0.0-beta.2",
84
+ "@nx/rolldown": "24.0.0-beta.2",
85
+ "@nx/webpack": "24.0.0-beta.2",
86
+ "@nx/vitest": "24.0.0-beta.2"
86
87
  },
87
88
  "peerDependencies": {
88
89
  "express": ">=4.0.0 <6.0.0",