nx 23.3.0-rc.1 → 24.0.0-beta.0
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/README.md +3 -6
- package/dist/src/adapter/ngcli-adapter.js +1 -0
- package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +0 -12
- package/dist/src/command-line/graph/graph.js +3 -5
- package/dist/src/command-line/init/command-object.js +0 -12
- package/dist/src/command-line/init/init-v2.js +0 -2
- package/dist/src/command-line/migrate/version-skew-guard.d.ts +1 -1
- package/dist/src/command-line/migrate/version-skew-guard.js +4 -7
- package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +17 -12
- package/dist/src/command-line/release/utils/remote-release-clients/github.js +5 -6
- package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +5 -6
- package/dist/src/command-line/run/executor-utils.js +1 -0
- package/dist/src/command-line/run/run-one.d.ts +20 -1
- package/dist/src/command-line/run/run-one.js +153 -5
- package/dist/src/command-line/show/show-target/utils.js +1 -0
- package/dist/src/command-line/watch/command-object.js +3 -22
- package/dist/src/command-line/watch/watch.d.ts +0 -7
- package/dist/src/command-line/watch/watch.js +1 -1
- package/dist/src/config/misc-interfaces.d.ts +12 -0
- package/dist/src/config/workspace-json-project-json.d.ts +5 -0
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/daemon/client/client.d.ts +9 -0
- package/dist/src/daemon/client/client.js +89 -37
- package/dist/src/daemon/server/shutdown-utils.js +0 -3
- package/dist/src/daemon/socket-utils.d.ts +9 -4
- package/dist/src/daemon/socket-utils.js +12 -49
- package/dist/src/daemon/tmp-dir.d.ts +19 -14
- package/dist/src/daemon/tmp-dir.js +142 -354
- package/dist/src/devkit-internals.d.ts +1 -1
- package/dist/src/devkit-internals.js +1 -0
- package/dist/src/hasher/file-hasher.js +9 -7
- package/dist/src/hasher/hash-task.js +4 -0
- package/dist/src/hasher/native-task-hasher-impl.js +1 -1
- package/dist/src/migrations/update-24-0-0/find-custom-hashers.d.ts +3 -0
- package/dist/src/migrations/update-24-0-0/find-custom-hashers.js +100 -0
- package/dist/src/migrations/update-24-0-0/find-custom-hashers.md +63 -0
- package/dist/src/migrations/update-24-0-0/move-custom-hashers-to-inputs.md +145 -0
- package/dist/src/native/index.d.ts +137 -11
- package/dist/src/native/native-bindings.js +5 -0
- package/dist/src/native/nx.wasi-browser.js +1 -0
- package/dist/src/native/nx.wasi.cjs +1 -0
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/nx-cloud/update-manager.d.ts +7 -0
- package/dist/src/nx-cloud/update-manager.js +237 -50
- package/dist/src/project-graph/affected/affected-tasks.js +1 -0
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +7 -0
- package/dist/src/tasks-runner/create-task-graph.d.ts +10 -0
- package/dist/src/tasks-runner/create-task-graph.js +37 -2
- package/dist/src/tasks-runner/task-env.d.ts +10 -0
- package/dist/src/tasks-runner/task-env.js +26 -5
- package/dist/src/tasks-runner/task-orchestrator.d.ts +10 -4
- package/dist/src/tasks-runner/task-orchestrator.js +87 -53
- package/dist/src/tasks-runner/utils.d.ts +20 -5
- package/dist/src/tasks-runner/utils.js +80 -29
- package/dist/src/ultracache/overrides.js +4 -0
- package/dist/src/ultracache/store.d.ts +4 -3
- package/dist/src/ultracache/store.js +5 -9
- package/dist/src/utils/owned-private-dir.d.ts +1 -17
- package/dist/src/utils/owned-private-dir.js +10 -62
- package/dist/src/utils/perf-hooks.d.ts +1 -0
- package/dist/src/utils/plugins/core-plugins.js +1 -1
- package/dist/src/utils/string-similarity.d.ts +38 -0
- package/dist/src/utils/string-similarity.js +108 -0
- package/migrations.json +7 -0
- package/package.json +11 -20
- package/readme-template.md +3 -3
- package/schemas/nx-schema.json +5 -0
- package/schemas/project-schema.json +5 -0
- package/dist/src/tasks-runner/legacy-depends-on-warning.d.ts +0 -18
- package/dist/src/tasks-runner/legacy-depends-on-warning.js +0 -109
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<p
|
|
1
|
+
<p align="center">
|
|
2
2
|
<picture>
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-
|
|
4
|
-
<img alt="Nx
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo-light.svg">
|
|
4
|
+
<img alt="Nx Logo" src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.svg" width="140">
|
|
5
5
|
</picture>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
@@ -60,6 +60,3 @@ npx nx@latest init
|
|
|
60
60
|
- [Official Nx YouTube Channel](https://www.youtube.com/@NxDevtools)
|
|
61
61
|
- [Blog Posts About Nx](https://nx.dev/blog)
|
|
62
62
|
|
|
63
|
-
<p style="text-align: center;"><a href="https://nx.dev/#learning-materials" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-courses-and-videos.svg"
|
|
64
|
-
width="100%" alt="Nx - Smart Monorepos · Fast Builds"></a></p>
|
|
65
|
-
|
|
@@ -991,6 +991,7 @@ async function getWrappedWorkspaceNodeModulesArchitectHost(workspace, root, proj
|
|
|
991
991
|
const batchImplementationFactory = executorConfig.batchImplementation
|
|
992
992
|
? this.getImplementationFactory(executorConfig.batchImplementation, executorsDir, nodeModule)
|
|
993
993
|
: null;
|
|
994
|
+
// TODO(v25): remove custom hasher support.
|
|
994
995
|
const hasherFactory = executorConfig.hasher
|
|
995
996
|
? this.getImplementationFactory(executorConfig.hasher, executorsDir, nodeModule)
|
|
996
997
|
: null;
|
|
@@ -64,18 +64,6 @@ async function configureAiAgentsHandler(args, inner = false) {
|
|
|
64
64
|
await resetDaemonAgentStatus();
|
|
65
65
|
}
|
|
66
66
|
async function configureAiAgentsHandlerImpl(options) {
|
|
67
|
-
// Node 24's stricter readline throws ERR_USE_AFTER_CLOSE when a prompt
|
|
68
|
-
// library operates on a closed interface. Added for enquirer, which nx no
|
|
69
|
-
// longer uses; verify against Node 24 before removing.
|
|
70
|
-
// TODO(v24): drop if @clack/prompts proves not to need it.
|
|
71
|
-
process.on('uncaughtException', (error) => {
|
|
72
|
-
if (error &&
|
|
73
|
-
typeof error === 'object' &&
|
|
74
|
-
'code' in error &&
|
|
75
|
-
error['code'] === 'ERR_USE_AFTER_CLOSE')
|
|
76
|
-
return;
|
|
77
|
-
throw error;
|
|
78
|
-
});
|
|
79
67
|
const normalizedOptions = normalizeOptions(options);
|
|
80
68
|
const { nonConfiguredAgents, partiallyConfiguredAgents, fullyConfiguredAgents, disabledAgents, } = await (0, utils_1.getAgentConfigurations)(normalizedOptions.agents, workspace_root_1.workspaceRoot);
|
|
81
69
|
if (disabledAgents.length > 0) {
|
|
@@ -372,11 +372,9 @@ async function generateGraph(args, affectedProjects) {
|
|
|
372
372
|
title: `Project graph started at ${url.toString()}`,
|
|
373
373
|
});
|
|
374
374
|
if (args.open) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
// Ignore errors when opening browser (e.g. no browser available)
|
|
379
|
-
});
|
|
375
|
+
// Best-effort: openUrl never throws, returning false if no opener could be
|
|
376
|
+
// spawned. The URL is printed above, so a failure is silent.
|
|
377
|
+
(0, native_1.openUrl)(url.toString());
|
|
380
378
|
}
|
|
381
379
|
return new Promise((res) => {
|
|
382
380
|
app.once('close', res);
|
|
@@ -17,18 +17,6 @@ exports.yargsInitCommand = {
|
|
|
17
17
|
return withInitOptions(yargs);
|
|
18
18
|
},
|
|
19
19
|
handler: async (args) => {
|
|
20
|
-
// Node 24's stricter readline throws ERR_USE_AFTER_CLOSE when a prompt
|
|
21
|
-
// library operates on a closed interface. Added for enquirer, which nx no
|
|
22
|
-
// longer uses; verify against Node 24 before removing.
|
|
23
|
-
// TODO(v24): drop if @clack/prompts proves not to need it.
|
|
24
|
-
process.on('uncaughtException', (error) => {
|
|
25
|
-
if (error &&
|
|
26
|
-
typeof error === 'object' &&
|
|
27
|
-
'code' in error &&
|
|
28
|
-
error['code'] === 'ERR_USE_AFTER_CLOSE')
|
|
29
|
-
return;
|
|
30
|
-
throw error;
|
|
31
|
-
});
|
|
32
20
|
const useV2 = await isInitV2();
|
|
33
21
|
if (useV2) {
|
|
34
22
|
await require('./init-v2').initHandler(args);
|
|
@@ -442,7 +442,6 @@ function getPluginReason(plugin) {
|
|
|
442
442
|
'@nx/next': 'next.js detected in dependencies',
|
|
443
443
|
'@nx/nuxt': 'nuxt detected in dependencies',
|
|
444
444
|
'@nx/react-native': 'react-native detected in dependencies',
|
|
445
|
-
'@nx/remix': '@remix-run/dev detected in dependencies',
|
|
446
445
|
'@nx/rsbuild': '@rsbuild/core detected in dependencies',
|
|
447
446
|
'@nx/react': '@react-router/dev detected in dependencies',
|
|
448
447
|
'@nx/gradle': 'gradlew detected in workspace',
|
|
@@ -493,7 +492,6 @@ const npmPackageToPluginMap = {
|
|
|
493
492
|
next: '@nx/next',
|
|
494
493
|
nuxt: '@nx/nuxt',
|
|
495
494
|
'react-native': '@nx/react-native',
|
|
496
|
-
'@remix-run/dev': '@nx/remix',
|
|
497
495
|
'@rsbuild/core': '@nx/rsbuild',
|
|
498
496
|
'@react-router/dev': '@nx/react',
|
|
499
497
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const NEW_MIGRATE_FLAGS_FLOOR = "23.2.0";
|
|
2
|
-
export declare const NEW_MIGRATE_FLAGS: readonly ['--run-migration', '--runMigration', '--run-id', '--runId', '--step-action', '--stepAction'
|
|
2
|
+
export declare const NEW_MIGRATE_FLAGS: readonly ['--run-migration', '--runMigration', '--run-id', '--runId', '--step-action', '--stepAction'];
|
|
3
3
|
export declare function findNewMigrateFlag(argv: string[]): string | undefined;
|
|
4
4
|
/**
|
|
5
5
|
* Whether the invocation names an existing orchestrated run. Such an
|
|
@@ -19,9 +19,10 @@ const version_utils_1 = require("./version-utils");
|
|
|
19
19
|
// pass this floor and route to a temp CLI that drops the new flags and runs
|
|
20
20
|
// the plan phase instead.
|
|
21
21
|
exports.NEW_MIGRATE_FLAGS_FLOOR = '23.2.0';
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
22
|
+
// List only flags that pick which migrate path runs: an older nx drops them
|
|
23
|
+
// and runs another one. Flags read only by --run-migrations do not belong.
|
|
24
|
+
// yargs accepts both spellings of each flag and the raw argv is forwarded
|
|
25
|
+
// across both migrate hops, so detection must catch every one.
|
|
25
26
|
exports.NEW_MIGRATE_FLAGS = [
|
|
26
27
|
'--run-migration',
|
|
27
28
|
'--runMigration',
|
|
@@ -29,10 +30,6 @@ exports.NEW_MIGRATE_FLAGS = [
|
|
|
29
30
|
'--runId',
|
|
30
31
|
'--step-action',
|
|
31
32
|
'--stepAction',
|
|
32
|
-
'--final-validation',
|
|
33
|
-
'--finalValidation',
|
|
34
|
-
'--no-final-validation',
|
|
35
|
-
'--no-finalValidation',
|
|
36
33
|
];
|
|
37
34
|
const RUN_ID_FLAGS = ['--run-id', '--runId'];
|
|
38
35
|
/**
|
|
@@ -155,23 +155,28 @@ async function runConnectToNxCloud(options, command) {
|
|
|
155
155
|
installationSource: command ?? installationSource,
|
|
156
156
|
});
|
|
157
157
|
const connectCloudUrl = await (0, url_shorten_1.createNxCloudOnboardingURL)('nx-connect', token, undefined, options?.generateToken === true);
|
|
158
|
+
const manualConnectNote = {
|
|
159
|
+
title: `Your Nx Cloud workspace is ready.`,
|
|
160
|
+
bodyLines: [
|
|
161
|
+
`To claim it, connect it to your Nx Cloud account:`,
|
|
162
|
+
`- Go to the following URL to connect your workspace to Nx Cloud:`,
|
|
163
|
+
'',
|
|
164
|
+
`${connectCloudUrl}`,
|
|
165
|
+
],
|
|
166
|
+
};
|
|
158
167
|
try {
|
|
159
168
|
const cloudConnectSpinner = ora(`Opening Nx Cloud ${connectCloudUrl} in your browser to connect your workspace.`).start();
|
|
160
169
|
await sleep(2000);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
170
|
+
if ((0, native_1.openUrl)(connectCloudUrl)) {
|
|
171
|
+
cloudConnectSpinner.succeed();
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
cloudConnectSpinner.fail();
|
|
175
|
+
output_1.output.note(manualConnectNote);
|
|
176
|
+
}
|
|
164
177
|
}
|
|
165
178
|
catch (e) {
|
|
166
|
-
output_1.output.note(
|
|
167
|
-
title: `Your Nx Cloud workspace is ready.`,
|
|
168
|
-
bodyLines: [
|
|
169
|
-
`To claim it, connect it to your Nx Cloud account:`,
|
|
170
|
-
`- Go to the following URL to connect your workspace to Nx Cloud:`,
|
|
171
|
-
'',
|
|
172
|
-
`${connectCloudUrl}`,
|
|
173
|
-
],
|
|
174
|
-
});
|
|
179
|
+
output_1.output.note(manualConnectNote);
|
|
175
180
|
}
|
|
176
181
|
return true;
|
|
177
182
|
}
|
|
@@ -5,6 +5,7 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const pc = tslib_1.__importStar(require("picocolors"));
|
|
6
6
|
const prompt_helpers_1 = require("../../../../utils/prompt-helpers");
|
|
7
7
|
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const native_1 = require("../../../../native");
|
|
8
9
|
const node_fs_1 = require("node:fs");
|
|
9
10
|
const node_os_1 = require("node:os");
|
|
10
11
|
const output_1 = require("../../../../utils/output");
|
|
@@ -238,18 +239,16 @@ class GithubRemoteReleaseClient extends remote_release_client_1.RemoteReleaseCli
|
|
|
238
239
|
if (!shouldContinueInGitHub) {
|
|
239
240
|
return;
|
|
240
241
|
}
|
|
241
|
-
|
|
242
|
-
await open(result.url)
|
|
243
|
-
.then(() => {
|
|
242
|
+
if ((0, native_1.openUrl)(result.url)) {
|
|
244
243
|
console.info(`\nFollow up in the browser to manually create the release:\n\n` +
|
|
245
244
|
pc.underline(pc.cyan(result.url)) +
|
|
246
245
|
`\n`);
|
|
247
|
-
}
|
|
248
|
-
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
249
248
|
console.info(`Open this link to manually create a release: \n` +
|
|
250
249
|
pc.underline(pc.cyan(result.url)) +
|
|
251
250
|
'\n');
|
|
252
|
-
}
|
|
251
|
+
}
|
|
253
252
|
}
|
|
254
253
|
async promptForContinueInGitHub() {
|
|
255
254
|
try {
|
|
@@ -5,6 +5,7 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const pc = tslib_1.__importStar(require("picocolors"));
|
|
6
6
|
const prompt_helpers_1 = require("../../../../utils/prompt-helpers");
|
|
7
7
|
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const native_1 = require("../../../../native");
|
|
8
9
|
const output_1 = require("../../../../utils/output");
|
|
9
10
|
const extract_repo_slug_1 = require("./extract-repo-slug");
|
|
10
11
|
const remote_release_client_1 = require("./remote-release-client");
|
|
@@ -157,18 +158,16 @@ class GitLabRemoteReleaseClient extends remote_release_client_1.RemoteReleaseCli
|
|
|
157
158
|
if (!shouldContinueInGitLab) {
|
|
158
159
|
return;
|
|
159
160
|
}
|
|
160
|
-
|
|
161
|
-
await open(result.url)
|
|
162
|
-
.then(() => {
|
|
161
|
+
if ((0, native_1.openUrl)(result.url)) {
|
|
163
162
|
console.info(`\nFollow up in the browser to manually create the release:\n\n` +
|
|
164
163
|
pc.underline(pc.cyan(result.url)) +
|
|
165
164
|
`\n`);
|
|
166
|
-
}
|
|
167
|
-
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
168
167
|
console.info(`Open this link to manually create a release: \n` +
|
|
169
168
|
pc.underline(pc.cyan(result.url)) +
|
|
170
169
|
'\n');
|
|
171
|
-
}
|
|
170
|
+
}
|
|
172
171
|
}
|
|
173
172
|
async promptForContinueInGitLab() {
|
|
174
173
|
try {
|
|
@@ -44,6 +44,7 @@ projects) {
|
|
|
44
44
|
const batchImplementationFactory = executorConfig.batchImplementation
|
|
45
45
|
? (0, schema_utils_1.getImplementationFactory)(executorConfig.batchImplementation, executorsDir, nodeModule, projects)
|
|
46
46
|
: null;
|
|
47
|
+
// TODO(v25): remove custom hasher support.
|
|
47
48
|
const hasherFactory = executorConfig.hasher
|
|
48
49
|
? (0, schema_utils_1.getImplementationFactory)(executorConfig.hasher, executorsDir, nodeModule, projects)
|
|
49
50
|
: null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NxJsonConfiguration } from '../../config/nx-json';
|
|
2
|
-
import { ProjectGraph } from '../../config/project-graph';
|
|
2
|
+
import { ProjectGraph, ProjectGraphProjectNode } from '../../config/project-graph';
|
|
3
3
|
import { TargetDependencyConfig } from '../../config/workspace-json-project-json';
|
|
4
4
|
export declare function runOne(cwd: string, args: {
|
|
5
5
|
[k: string]: any;
|
|
@@ -7,6 +7,25 @@ export declare function runOne(cwd: string, args: {
|
|
|
7
7
|
excludeTaskDependencies: boolean;
|
|
8
8
|
loadDotEnvFiles: boolean;
|
|
9
9
|
}): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Builds the error shown when the project itself can't be found. Matches the
|
|
12
|
+
* attempted `project:target[:configuration]` against every runnable task id so
|
|
13
|
+
* a project typo (e.g. `webpai:build`) still surfaces the intended task
|
|
14
|
+
* (`webapi:build`).
|
|
15
|
+
*/
|
|
16
|
+
export declare function getCannotFindProjectError(projectGraph: ProjectGraph, projectName: string, target: string, configuration?: string): {
|
|
17
|
+
title: string;
|
|
18
|
+
bodyLines: string[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Validates that `target` exists on the resolved project. When it does not,
|
|
22
|
+
* returns an error describing the available targets (and the closest match, if
|
|
23
|
+
* any) so the user can recover from a typo or discover what they can run.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getRunOneTargetError(project: ProjectGraphProjectNode, target: string, configuration?: string): {
|
|
26
|
+
title: string;
|
|
27
|
+
bodyLines: string[];
|
|
28
|
+
} | null;
|
|
10
29
|
export declare function parseRunOneOptions(cwd: string, parsedArgs: {
|
|
11
30
|
[k: string]: any;
|
|
12
31
|
}, projectGraph: ProjectGraph, nxJson: NxJsonConfiguration): {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runOne = runOne;
|
|
4
|
+
exports.getCannotFindProjectError = getCannotFindProjectError;
|
|
5
|
+
exports.getRunOneTargetError = getRunOneTargetError;
|
|
4
6
|
exports.parseRunOneOptions = parseRunOneOptions;
|
|
5
7
|
const calculate_default_project_name_1 = require("../../config/calculate-default-project-name");
|
|
6
8
|
const configuration_1 = require("../../config/configuration");
|
|
@@ -11,6 +13,7 @@ const command_line_utils_1 = require("../../utils/command-line-utils");
|
|
|
11
13
|
const find_matching_projects_1 = require("../../utils/find-matching-projects");
|
|
12
14
|
const output_1 = require("../../utils/output");
|
|
13
15
|
const split_target_1 = require("../../utils/split-target");
|
|
16
|
+
const string_similarity_1 = require("../../utils/string-similarity");
|
|
14
17
|
const workspace_root_1 = require("../../utils/workspace-root");
|
|
15
18
|
const graph_1 = require("../graph/graph");
|
|
16
19
|
const connect_to_nx_cloud_1 = require("../nx-cloud/connect/connect-to-nx-cloud");
|
|
@@ -28,7 +31,12 @@ async function runOne(cwd, args, extraTargetDependencies = {}, extraOptions = {
|
|
|
28
31
|
configuration: opts.configuration,
|
|
29
32
|
targets: [opts.target],
|
|
30
33
|
}, 'run-one', { printWarnings: args.graph !== 'stdout' }, nxJson);
|
|
31
|
-
const { projects, projectName } = getProjects(projectGraph, opts.project);
|
|
34
|
+
const { projects, projectName } = getProjects(projectGraph, opts.project, opts.target, opts.configuration);
|
|
35
|
+
const targetError = getRunOneTargetError(projects[0], opts.target, opts.configuration);
|
|
36
|
+
if (targetError) {
|
|
37
|
+
output_1.output.error(targetError);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
32
40
|
if (nxArgs.help) {
|
|
33
41
|
await (await (0, handle_import_1.handleImport)('./run.js', __dirname)).printTargetRunHelp({
|
|
34
42
|
...opts,
|
|
@@ -59,7 +67,7 @@ async function runOne(cwd, args, extraTargetDependencies = {}, extraOptions = {
|
|
|
59
67
|
process.exit(status);
|
|
60
68
|
}
|
|
61
69
|
}
|
|
62
|
-
function getProjects(projectGraph, projectName) {
|
|
70
|
+
function getProjects(projectGraph, projectName, target, configuration) {
|
|
63
71
|
if (projectGraph.nodes[projectName]) {
|
|
64
72
|
return {
|
|
65
73
|
projectName: projectName,
|
|
@@ -90,11 +98,151 @@ function getProjects(projectGraph, projectName) {
|
|
|
90
98
|
process.exit(1);
|
|
91
99
|
}
|
|
92
100
|
}
|
|
93
|
-
output_1.output.error(
|
|
94
|
-
title: `Cannot find project '${projectName}'`,
|
|
95
|
-
});
|
|
101
|
+
output_1.output.error(getCannotFindProjectError(projectGraph, projectName, target, configuration));
|
|
96
102
|
process.exit(1);
|
|
97
103
|
}
|
|
104
|
+
// Keep error output scannable: only show a handful of targets/suggestions.
|
|
105
|
+
const MAX_LISTED_TARGETS = 5;
|
|
106
|
+
const MAX_SUGGESTIONS = 3;
|
|
107
|
+
/**
|
|
108
|
+
* Builds the list of all runnable `project:target` task ids in the workspace,
|
|
109
|
+
* used to suggest the right invocation when a project or target can't be found.
|
|
110
|
+
*/
|
|
111
|
+
function getProjectTargetIds(projectGraph) {
|
|
112
|
+
const ids = [];
|
|
113
|
+
for (const projectName of Object.keys(projectGraph.nodes)) {
|
|
114
|
+
const targets = projectGraph.nodes[projectName].data.targets ?? {};
|
|
115
|
+
for (const targetName of Object.keys(targets)) {
|
|
116
|
+
ids.push(`${projectName}:${targetName}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return ids;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A target (and even a configuration) name can legally contain ':'. When the
|
|
123
|
+
* user typos such a name, `splitTarget` splits on every ':' and parses the
|
|
124
|
+
* trailing segment(s) as separate parts (e.g. `nx:zzcustom:variantt` becomes
|
|
125
|
+
* project `nx`, target `zzcustom`, configuration `variantt`).
|
|
126
|
+
*
|
|
127
|
+
* To match the real name we therefore score the fully-rejoined specifier and
|
|
128
|
+
* every progressively shorter form, and keep whichever produced the closest
|
|
129
|
+
* candidate. Rejoining wins for colon-containing names, while the shorter forms
|
|
130
|
+
* keep matching genuine `target` + `configuration` typos (where the real target
|
|
131
|
+
* name does not carry the configuration suffix). Ties go to the longer form,
|
|
132
|
+
* which is the one that reproduces the name the user actually typed.
|
|
133
|
+
*/
|
|
134
|
+
function findClosestSpecifier(parts, candidates, limit = 1) {
|
|
135
|
+
const present = parts.filter((part) => !!part);
|
|
136
|
+
let best = [];
|
|
137
|
+
for (let end = present.length; end >= 1; end--) {
|
|
138
|
+
const matches = (0, string_similarity_1.rankSuggestions)(present.slice(0, end).join(':'), candidates);
|
|
139
|
+
if (matches.length &&
|
|
140
|
+
(!best.length || matches[0].distance < best[0].distance)) {
|
|
141
|
+
best = matches;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return best.slice(0, limit).map(({ candidate }) => candidate);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Finds the closest available target to what the user typed, staying
|
|
148
|
+
* config-aware. When a configuration was parsed off the specifier, the rejoined
|
|
149
|
+
* `target:configuration` is scored too, so a colon-containing real target name
|
|
150
|
+
* (e.g. `zzcustom:variant`) still matches. Both forms are scored and the closest
|
|
151
|
+
* candidate wins, ties going to the rejoined form -- mirroring
|
|
152
|
+
* `findClosestSpecifier`'s preference order. The bare-`target` tier reuses the
|
|
153
|
+
* precomputed `ranked` list (its head, gated by the suggestion threshold)
|
|
154
|
+
* instead of recomputing distances.
|
|
155
|
+
*/
|
|
156
|
+
function findClosestTarget(target, configuration, availableTargets, ranked) {
|
|
157
|
+
let best;
|
|
158
|
+
if (configuration) {
|
|
159
|
+
[best] = (0, string_similarity_1.rankSuggestions)(`${target}:${configuration}`, availableTargets);
|
|
160
|
+
}
|
|
161
|
+
const nearest = ranked[0];
|
|
162
|
+
if (nearest &&
|
|
163
|
+
(0, string_similarity_1.isWithinSuggestionThreshold)(target, nearest.candidate, nearest.distance) &&
|
|
164
|
+
(!best || nearest.distance < best.distance)) {
|
|
165
|
+
best = nearest;
|
|
166
|
+
}
|
|
167
|
+
return best?.candidate;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Lists up to `MAX_LISTED_TARGETS` targets from a list already ranked by how
|
|
171
|
+
* closely they resemble the target the user typed (closest first, ties broken
|
|
172
|
+
* alphabetically) so the most likely intended targets surface first.
|
|
173
|
+
* `closestMatch` is dropped because it is already surfaced separately as the
|
|
174
|
+
* "Did you mean" suggestion. Appends a "...and N more" line when the project has
|
|
175
|
+
* more targets than we show, and returns no lines at all when the suggestion was
|
|
176
|
+
* the project's only target -- a bare "Available targets:" header reads as "this
|
|
177
|
+
* project has none".
|
|
178
|
+
*/
|
|
179
|
+
function formatAvailableTargets(ranked, closestMatch) {
|
|
180
|
+
const targets = ranked
|
|
181
|
+
.map(({ candidate }) => candidate)
|
|
182
|
+
.filter((candidate) => candidate !== closestMatch);
|
|
183
|
+
if (!targets.length) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
const shown = targets.slice(0, MAX_LISTED_TARGETS);
|
|
187
|
+
const lines = ['Available targets:', ...shown.map((t) => ` - ${t}`)];
|
|
188
|
+
if (targets.length > shown.length) {
|
|
189
|
+
lines.push(` ...and ${targets.length - shown.length} more`);
|
|
190
|
+
}
|
|
191
|
+
return lines;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Builds the error shown when the project itself can't be found. Matches the
|
|
195
|
+
* attempted `project:target[:configuration]` against every runnable task id so
|
|
196
|
+
* a project typo (e.g. `webpai:build`) still surfaces the intended task
|
|
197
|
+
* (`webapi:build`).
|
|
198
|
+
*/
|
|
199
|
+
function getCannotFindProjectError(projectGraph, projectName, target, configuration) {
|
|
200
|
+
const bodyLines = [];
|
|
201
|
+
const suggestions = findClosestSpecifier([projectName, target, configuration], getProjectTargetIds(projectGraph), MAX_SUGGESTIONS);
|
|
202
|
+
if (suggestions.length) {
|
|
203
|
+
bodyLines.push('Did you mean one of these?', ...suggestions.map((id) => ` - ${id}`));
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
title: `Cannot find project '${projectName}'`,
|
|
207
|
+
bodyLines,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Validates that `target` exists on the resolved project. When it does not,
|
|
212
|
+
* returns an error describing the available targets (and the closest match, if
|
|
213
|
+
* any) so the user can recover from a typo or discover what they can run.
|
|
214
|
+
*/
|
|
215
|
+
function getRunOneTargetError(project, target, configuration) {
|
|
216
|
+
const availableTargets = Object.keys(project.data.targets ?? {});
|
|
217
|
+
if (availableTargets.includes(target)) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const bodyLines = [];
|
|
221
|
+
// Rank the available targets by distance to the bare target ONCE. This drives
|
|
222
|
+
// the ordered "Available targets" block and doubles as the bare-target tier of
|
|
223
|
+
// the closest-match lookup, so distances are not computed twice.
|
|
224
|
+
const ranked = (0, string_similarity_1.rankByDistance)(target, availableTargets);
|
|
225
|
+
const closestMatch = findClosestTarget(target, configuration, availableTargets, ranked);
|
|
226
|
+
if (closestMatch) {
|
|
227
|
+
bodyLines.push(`Did you mean "${closestMatch}"?`);
|
|
228
|
+
}
|
|
229
|
+
if (!availableTargets.length) {
|
|
230
|
+
bodyLines.push(`The project "${project.name}" does not have any targets configured.`);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
const targetLines = formatAvailableTargets(ranked, closestMatch);
|
|
234
|
+
if (targetLines.length) {
|
|
235
|
+
if (bodyLines.length) {
|
|
236
|
+
bodyLines.push('');
|
|
237
|
+
}
|
|
238
|
+
bodyLines.push(...targetLines);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
title: `Cannot find target "${target}" for project "${project.name}"`,
|
|
243
|
+
bodyLines,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
98
246
|
const targetAliases = {
|
|
99
247
|
b: 'build',
|
|
100
248
|
e: 'e2e',
|
|
@@ -142,6 +142,7 @@ function validateConfiguration(projectName, targetName, configuration, targetCon
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
// ── Custom hasher detection ──────────────────────────────────────────
|
|
145
|
+
// TODO(v25): remove custom hasher support.
|
|
145
146
|
/**
|
|
146
147
|
* Checks whether a target's executor defines a custom hasher.
|
|
147
148
|
* Returns true if the executor has a hasherFactory — meaning the
|
|
@@ -13,7 +13,7 @@ exports.yargsWatchCommand = {
|
|
|
13
13
|
},
|
|
14
14
|
};
|
|
15
15
|
function withWatchOptions(yargs) {
|
|
16
|
-
return (
|
|
16
|
+
return (0, shared_options_1.withVerbose)(yargs)
|
|
17
17
|
.parserConfiguration({
|
|
18
18
|
'strip-dashed': true,
|
|
19
19
|
'populate--': true,
|
|
@@ -32,12 +32,6 @@ function withWatchOptions(yargs) {
|
|
|
32
32
|
type: 'boolean',
|
|
33
33
|
description: 'When watching selected projects, also include the projects they depend on.',
|
|
34
34
|
alias: 'd',
|
|
35
|
-
})
|
|
36
|
-
// TODO(v24): remove the deprecated --includeDependentProjects alias
|
|
37
|
-
.option('includeDependentProjects', {
|
|
38
|
-
type: 'boolean',
|
|
39
|
-
hidden: true,
|
|
40
|
-
describe: "Deprecated in favor of --includeDependencies; will be removed in Nx 24. The flag name was misleading since it includes the watched project's dependencies, not its dependents. The new flag is functionally identical.",
|
|
41
35
|
})
|
|
42
36
|
.option('includeGlobalWorkspaceFiles', {
|
|
43
37
|
type: 'boolean',
|
|
@@ -59,6 +53,7 @@ function withWatchOptions(yargs) {
|
|
|
59
53
|
.conflicts({
|
|
60
54
|
all: 'projects',
|
|
61
55
|
})
|
|
56
|
+
.strictOptions()
|
|
62
57
|
.check((args) => {
|
|
63
58
|
if (!args.all && !args.projects) {
|
|
64
59
|
throw Error('Please specify either --all or --projects');
|
|
@@ -73,19 +68,5 @@ function withWatchOptions(yargs) {
|
|
|
73
68
|
else {
|
|
74
69
|
throw Error('No command specified for watch mode.');
|
|
75
70
|
}
|
|
76
|
-
|
|
77
|
-
// Nx 23 because the original name was misleading: it includes the
|
|
78
|
-
// watched project's *dependencies*, not its dependents. The new flag
|
|
79
|
-
// is functionally identical — only the name changed. Map the legacy
|
|
80
|
-
// name through so existing scripts keep working during the
|
|
81
|
-
// deprecation window.
|
|
82
|
-
// TODO(v24): remove the legacy includeDependentProjects pass-through
|
|
83
|
-
const a = args;
|
|
84
|
-
if (a.includeDependentProjects !== undefined &&
|
|
85
|
-
a.includeDependencies === undefined) {
|
|
86
|
-
a.includeDependencies = a.includeDependentProjects;
|
|
87
|
-
// eslint-disable-next-line no-console
|
|
88
|
-
console.warn("--includeDependentProjects is deprecated in favor of --includeDependencies and will be removed in Nx 24. The flag name was misleading since it includes the watched project's dependencies, not its dependents. The new flag is functionally identical.");
|
|
89
|
-
}
|
|
90
|
-
}, true));
|
|
71
|
+
}, true);
|
|
91
72
|
}
|
|
@@ -3,13 +3,6 @@ export interface WatchArguments {
|
|
|
3
3
|
projects?: string[];
|
|
4
4
|
all?: boolean;
|
|
5
5
|
includeDependencies?: boolean;
|
|
6
|
-
/**
|
|
7
|
-
* @deprecated Renamed to {@link WatchArguments.includeDependencies}; will be
|
|
8
|
-
* removed in Nx 24. The original name was misleading: this flag includes
|
|
9
|
-
* the watched project's dependencies, not its dependents. The new property
|
|
10
|
-
* is functionally identical — only the name changed.
|
|
11
|
-
*/
|
|
12
|
-
includeDependentProjects?: boolean;
|
|
13
6
|
includeGlobalWorkspaceFiles?: boolean;
|
|
14
7
|
verbose?: boolean;
|
|
15
8
|
command?: string;
|
|
@@ -149,7 +149,7 @@ async function watch(args) {
|
|
|
149
149
|
}
|
|
150
150
|
await client_1.daemonClient.registerFileWatcher({
|
|
151
151
|
watchProjects: whatToWatch,
|
|
152
|
-
includeDependencies: args.includeDependencies
|
|
152
|
+
includeDependencies: args.includeDependencies,
|
|
153
153
|
includeGlobalWorkspaceFiles: args.includeGlobalWorkspaceFiles,
|
|
154
154
|
}, async (err, data) => {
|
|
155
155
|
if (err === 'reconnecting') {
|
|
@@ -32,6 +32,9 @@ export interface ExecutorJsonEntryConfig {
|
|
|
32
32
|
batchImplementation?: string;
|
|
33
33
|
preferBatch?: boolean;
|
|
34
34
|
description?: string;
|
|
35
|
+
/**
|
|
36
|
+
* @deprecated Use target inputs instead. This will be removed in Nx 25.
|
|
37
|
+
*/
|
|
35
38
|
hasher?: string;
|
|
36
39
|
}
|
|
37
40
|
export type ExecutorsJsonEntry = string | ExecutorJsonEntryConfig;
|
|
@@ -143,6 +146,9 @@ export interface ExecutorConfig {
|
|
|
143
146
|
outputCapture?: OutputCaptureMethod;
|
|
144
147
|
continuous?: boolean;
|
|
145
148
|
} & Schema;
|
|
149
|
+
/**
|
|
150
|
+
* @deprecated Use target inputs instead. This will be removed in Nx 25.
|
|
151
|
+
*/
|
|
146
152
|
hasherFactory?: () => CustomHasher;
|
|
147
153
|
implementationFactory: () => Executor;
|
|
148
154
|
batchImplementationFactory?: () => TaskGraphExecutor;
|
|
@@ -172,6 +178,9 @@ options: T, context: ExecutorContext) => AsyncIterableIterator<{
|
|
|
172
178
|
* Implementation of a target of a project
|
|
173
179
|
*/
|
|
174
180
|
export type Executor<T = any> = PromiseExecutor<T> | AsyncIteratorExecutor<T>;
|
|
181
|
+
/**
|
|
182
|
+
* @deprecated Use target inputs instead. This will be removed in Nx 25.
|
|
183
|
+
*/
|
|
175
184
|
export interface HasherContext {
|
|
176
185
|
hasher: TaskHasher;
|
|
177
186
|
projectGraph: ProjectGraph;
|
|
@@ -179,6 +188,9 @@ export interface HasherContext {
|
|
|
179
188
|
projectsConfigurations: ProjectsConfigurations;
|
|
180
189
|
nxJsonConfiguration: NxJsonConfiguration;
|
|
181
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* @deprecated Use target inputs instead. This will be removed in Nx 25.
|
|
193
|
+
*/
|
|
182
194
|
export type CustomHasher = (task: Task, context: HasherContext) => Promise<Hash>;
|
|
183
195
|
export type TaskResult = {
|
|
184
196
|
success: boolean;
|
|
@@ -187,6 +187,11 @@ export interface TargetDependencyConfig {
|
|
|
187
187
|
* Whether to forward task options to the dependency target.
|
|
188
188
|
*/
|
|
189
189
|
options?: 'ignore' | 'forward';
|
|
190
|
+
/**
|
|
191
|
+
* Set to `false` to keep a continuous dependency's inputs out of this
|
|
192
|
+
* target's hash. It still runs first. Has no effect on other dependencies.
|
|
193
|
+
*/
|
|
194
|
+
inputs?: boolean;
|
|
190
195
|
}
|
|
191
196
|
export type InputDefinition = {
|
|
192
197
|
input: string;
|