@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.
- package/dist/src/generators/application/application.js +80 -1
- package/dist/src/generators/application/files/express/src/main.ts__tmpl__ +5 -1
- package/dist/src/generators/application/files/fastify/src/app/app.spec.ts__tmpl__ +1 -1
- package/dist/src/generators/application/files/fastify/src/app/app.ts__tmpl__ +2 -2
- package/dist/src/generators/application/files/fastify/src/main.ts__tmpl__ +6 -1
- package/dist/src/generators/application/files/koa/src/main.ts__tmpl__ +5 -1
- package/dist/src/generators/application/lib/add-dependencies.js +1 -0
- package/dist/src/generators/application/lib/add-script-serve.d.ts +7 -0
- package/dist/src/generators/application/lib/add-script-serve.js +52 -0
- package/dist/src/generators/application/lib/add-tsc-build-plugin.d.ts +8 -0
- package/dist/src/generators/application/lib/add-tsc-build-plugin.js +44 -0
- package/dist/src/generators/application/lib/create-files.js +2 -0
- package/dist/src/generators/application/lib/create-project.js +20 -3
- package/dist/src/generators/application/lib/create-targets.d.ts +2 -0
- package/dist/src/generators/application/lib/create-targets.js +32 -4
- package/dist/src/generators/application/lib/index.d.ts +1 -0
- package/dist/src/generators/application/lib/index.js +1 -0
- package/dist/src/generators/application/lib/normalize-options.js +45 -3
- package/dist/src/generators/application/lib/normalized-schema.d.ts +3 -0
- package/dist/src/generators/application/schema.d.ts +2 -2
- package/dist/src/generators/application/schema.json +3 -4
- package/dist/src/generators/e2e-project/e2e-project.js +114 -19
- package/dist/src/generators/e2e-project/files/cli/jest.config.ts__tmpl__ +2 -2
- package/dist/src/generators/e2e-project/files/non-ts-solution/tsconfig.spec.json__tmpl__ +2 -2
- package/dist/src/generators/e2e-project/files/server/common/jest.config.ts__tmpl__ +2 -2
- package/dist/src/generators/e2e-project/files/ts-solution/tsconfig.json__tmpl__ +3 -2
- package/dist/src/generators/e2e-project/files/vitest/common/vitest.config.mts__tmpl__ +12 -0
- package/dist/src/generators/e2e-project/files/vitest/server/src/support/global-setup.ts__tmpl__ +15 -0
- package/dist/src/generators/e2e-project/files/vitest/server/src/support/test-setup.ts__tmpl__ +6 -0
- package/dist/src/generators/e2e-project/schema.d.ts +3 -0
- package/dist/src/generators/e2e-project/schema.json +6 -0
- package/dist/src/utils/versions.d.ts +5 -4
- package/dist/src/utils/versions.js +6 -5
- 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
|
+
});
|
|
@@ -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(
|
|
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(
|
|
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)(
|
|
53
|
+
...(0, create_targets_1.getPruneTargets)(options.buildTargetName, options.outputPath),
|
|
44
54
|
};
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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: [
|
|
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: [
|
|
169
|
+
dependsOn: [buildTarget],
|
|
142
170
|
cache: true,
|
|
143
171
|
outputs: [
|
|
144
172
|
`{workspaceRoot}/${(0, devkit_1.joinPathFragments)(outputPath, 'workspace_modules')}`,
|
|
@@ -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
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
+
});
|
package/dist/src/generators/e2e-project/files/vitest/server/src/support/global-setup.ts__tmpl__
ADDED
|
@@ -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
|
+
}
|
|
@@ -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.
|
|
31
|
-
export declare const fastifyAutoloadVersion = "~6.0
|
|
32
|
-
export declare const fastifySensibleVersion = "~6.0.
|
|
33
|
-
export declare const fastifyPluginVersion = "~
|
|
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.
|
|
96
|
-
exports.fastifyAutoloadVersion = '~6.0
|
|
97
|
-
exports.fastifySensibleVersion = '~6.0.
|
|
98
|
-
exports.fastifyPluginVersion = '~
|
|
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.
|
|
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.
|
|
74
|
-
"@nx/jest": "24.0.0-beta.
|
|
75
|
-
"@nx/js": "24.0.0-beta.
|
|
76
|
-
"@nx/eslint": "24.0.0-beta.
|
|
77
|
-
"@nx/docker": "24.0.0-beta.
|
|
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.
|
|
84
|
-
"@nx/
|
|
85
|
-
"@nx/
|
|
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",
|