nx 23.2.0-beta.0 → 23.2.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/bin/nx.js +1 -1
- package/dist/src/analytics/analytics.d.ts +6 -0
- package/dist/src/analytics/analytics.js +7 -1
- package/dist/src/analytics/index.d.ts +1 -1
- package/dist/src/analytics/index.js +2 -1
- package/dist/src/command-line/init/ai-agent-prompts.d.ts +1 -1
- package/dist/src/command-line/init/ai-agent-prompts.js +7 -6
- package/dist/src/command-line/init/command-object.js +10 -2
- package/dist/src/command-line/init/implementation/angular/integrated-workspace.js +2 -5
- package/dist/src/command-line/init/implementation/angular/legacy-angular-versions.js +3 -0
- package/dist/src/command-line/init/implementation/dot-nx/add-nx-scripts.js +4 -1
- package/dist/src/command-line/migrate/execute-migration.d.ts +72 -0
- package/dist/src/command-line/migrate/execute-migration.js +389 -0
- package/dist/src/command-line/migrate/migrate-ui-api.js +17 -12
- package/dist/src/command-line/migrate/migrate.d.ts +1 -45
- package/dist/src/command-line/migrate/migrate.js +17 -375
- package/dist/src/command-line/nx-cloud/connect/view-logs.js +2 -0
- package/dist/src/command-line/release/utils/release-graph.d.ts +2 -2
- package/dist/src/command-line/release/utils/shared.d.ts +2 -2
- package/dist/src/command-line/show/show-target/inputs.js +50 -153
- package/dist/src/command-line/show/show-target/outputs.js +48 -173
- package/dist/src/command-line/show/show-target/utils.d.ts +28 -0
- package/dist/src/command-line/show/show-target/utils.js +83 -3
- package/dist/src/command-line/yargs-utils/shared-options.d.ts +1 -1
- package/dist/src/commands-runner/create-command-graph.js +5 -0
- package/dist/src/config/nx-json.d.ts +1 -1
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/daemon/server/handle-tasks-execution-hooks.d.ts +1 -1
- package/dist/src/daemon/server/server.js +7 -4
- package/dist/src/devkit-internals.d.ts +1 -0
- package/dist/src/devkit-internals.js +5 -2
- package/dist/src/generators/utils/nx-json.js +13 -3
- package/dist/src/hasher/check-task-files.d.ts +86 -0
- package/dist/src/hasher/check-task-files.js +378 -0
- package/dist/src/native/index.d.ts +25 -2
- package/dist/src/native/native-bindings.js +2 -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/plugins/js/lock-file/utils/pnpm-normalizer.js +3 -0
- package/dist/src/project-graph/build-project-graph.js +7 -1
- package/dist/src/project-graph/file-utils.js +6 -1
- package/dist/src/project-graph/plugins/loaded-nx-plugin.js +1 -0
- package/dist/src/project-graph/project-graph.js +3 -0
- package/dist/src/tasks-runner/life-cycle.d.ts +2 -2
- package/dist/src/tasks-runner/life-cycle.js +2 -2
- package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +9 -4
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +5 -4
- package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +16 -8
- package/dist/src/tasks-runner/life-cycles/performance-report.js +55 -27
- package/dist/src/tasks-runner/pseudo-terminal.d.ts +1 -1
- package/dist/src/tasks-runner/pseudo-terminal.js +2 -2
- package/dist/src/tasks-runner/task-orchestrator.d.ts +4 -1
- package/dist/src/tasks-runner/task-orchestrator.js +23 -6
- package/dist/src/utils/acknowledge-build-scripts.js +7 -5
- package/dist/src/utils/command-line-utils.js +30 -20
- package/dist/src/utils/git-revision.d.ts +12 -0
- package/dist/src/utils/git-revision.js +25 -0
- package/dist/src/utils/package-manager.js +6 -2
- package/package.json +11 -11
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { PostTasksExecutionContext, PreTasksExecutionContext } from '../../project-graph/plugins/public-api';
|
|
2
2
|
export declare function handleRunPreTasksExecution(context: PreTasksExecutionContext): Promise<{
|
|
3
|
-
error?: undefined;
|
|
4
3
|
response: NodeJS.ProcessEnv[];
|
|
5
4
|
description: string;
|
|
5
|
+
error?: undefined;
|
|
6
6
|
} | {
|
|
7
7
|
response?: undefined;
|
|
8
8
|
error: any;
|
|
@@ -360,6 +360,13 @@ const handleOutputsChanges = async (err, changeEvents) => {
|
|
|
360
360
|
}
|
|
361
361
|
};
|
|
362
362
|
async function startServer() {
|
|
363
|
+
// Watch before scan: a file written during boot must be visible to the
|
|
364
|
+
// watcher or the scan below. Scan-first left a blind window where such
|
|
365
|
+
// files stayed invisible to both until an unrelated change arrived.
|
|
366
|
+
if (!(0, shutdown_utils_1.getWatcherInstance)()) {
|
|
367
|
+
(0, shutdown_utils_1.storeWatcherInstance)(await (0, watcher_1.watchWorkspace)(server, handleWorkspaceChanges));
|
|
368
|
+
logger_1.serverLogger.watcherLog(`Subscribed to changes within: ${workspace_root_1.workspaceRoot} (native)`);
|
|
369
|
+
}
|
|
363
370
|
(0, workspace_context_1.setupWorkspaceContext)(workspace_root_1.workspaceRoot);
|
|
364
371
|
// Initialize analytics for daemon process
|
|
365
372
|
await (0, analytics_1.startAnalytics)();
|
|
@@ -418,10 +425,6 @@ async function startServer() {
|
|
|
418
425
|
logger_1.serverLogger.log(`Started listening on: ${socketPath}`);
|
|
419
426
|
// this triggers the storage of the lock file hash
|
|
420
427
|
daemonIsOutdated();
|
|
421
|
-
if (!(0, shutdown_utils_1.getWatcherInstance)()) {
|
|
422
|
-
(0, shutdown_utils_1.storeWatcherInstance)(await (0, watcher_1.watchWorkspace)(server, handleWorkspaceChanges));
|
|
423
|
-
logger_1.serverLogger.watcherLog(`Subscribed to changes within: ${workspace_root_1.workspaceRoot} (native)`);
|
|
424
|
-
}
|
|
425
428
|
if (!(0, shutdown_utils_1.getOutputWatcherInstance)()) {
|
|
426
429
|
(0, shutdown_utils_1.storeOutputWatcherInstance)(await (0, watcher_1.watchOutputFiles)(server, handleOutputsChanges));
|
|
427
430
|
}
|
|
@@ -38,5 +38,6 @@ export { PluginCache, safeWriteFileCache } from './utils/plugin-cache-utils';
|
|
|
38
38
|
export { emitPluginWorkerLog } from './project-graph/plugins/isolation/worker-streaming';
|
|
39
39
|
export { resolveImplementation, resolveSchema, ImplementationResolutionError, SchemaResolutionError, } from './config/schema-utils';
|
|
40
40
|
export { resolvePrompt, PromptResolutionError, } from './command-line/migrate/prompt-files';
|
|
41
|
+
export { checkFilesAreInputs, checkFilesAreOutputs, } from './hasher/check-task-files';
|
|
41
42
|
export { getCatalogManager, getCatalogDependenciesFromPackageJson, } from './utils/catalog';
|
|
42
43
|
export { acknowledgeBuildScripts } from './utils/acknowledge-build-scripts';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.acknowledgeBuildScripts = void 0;
|
|
3
|
+
exports.checkFilesAreOutputs = exports.checkFilesAreInputs = exports.PromptResolutionError = exports.resolvePrompt = exports.SchemaResolutionError = exports.ImplementationResolutionError = exports.resolveSchema = exports.resolveImplementation = exports.emitPluginWorkerLog = exports.safeWriteFileCache = exports.PluginCache = exports.handleImport = exports.signalToCode = exports.globalSpinner = exports.readYamlFile = exports.isUsingPrettierInTree = exports.isCI = exports.interpolate = exports.requireWithTsconfigFallback = exports.forceRegisterEsmLoader = exports.loadTsFile = exports.registerTsProject = exports.LoadedNxPlugin = exports.retrieveProjectConfigurations = exports.findProjectForPath = exports.createProjectRootMappings = exports.createProjectRootMappingsFromProjectConfigurations = exports.hashMultiGlobWithWorkspaceContext = exports.hashWithWorkspaceContext = exports.hashObject = exports.splitByColons = exports.installPackageToTmpAsync = exports.installPackageToTmp = exports.readModulePackageJson = exports.stripIndent = exports.sortObjectByKeys = exports.combineOptionsForExecutor = exports.splitTarget = exports.getIgnoreObjectForTree = exports.readTargetDefaultsForTarget = exports.findMatchingProjects = exports.findMatchingConfigFiles = exports.readProjectConfigurationsFromRootMap = exports.mergeTargetConfigurations = exports.retrieveProjectConfigurationsWithAngularProjects = exports.calculateDefaultProjectName = exports.readNxJsonFromDisk = exports.parseExecutor = exports.getExecutorInformation = exports.createTempNpmDirectory = void 0;
|
|
4
|
+
exports.acknowledgeBuildScripts = exports.getCatalogDependenciesFromPackageJson = exports.getCatalogManager = void 0;
|
|
5
5
|
const tslib_1 = require("tslib");
|
|
6
6
|
/**
|
|
7
7
|
* Note to developers: STOP! These exports are available via requireNx in @nx/devkit.
|
|
@@ -91,6 +91,9 @@ Object.defineProperty(exports, "SchemaResolutionError", { enumerable: true, get:
|
|
|
91
91
|
var prompt_files_1 = require("./command-line/migrate/prompt-files");
|
|
92
92
|
Object.defineProperty(exports, "resolvePrompt", { enumerable: true, get: function () { return prompt_files_1.resolvePrompt; } });
|
|
93
93
|
Object.defineProperty(exports, "PromptResolutionError", { enumerable: true, get: function () { return prompt_files_1.PromptResolutionError; } });
|
|
94
|
+
var check_task_files_1 = require("./hasher/check-task-files");
|
|
95
|
+
Object.defineProperty(exports, "checkFilesAreInputs", { enumerable: true, get: function () { return check_task_files_1.checkFilesAreInputs; } });
|
|
96
|
+
Object.defineProperty(exports, "checkFilesAreOutputs", { enumerable: true, get: function () { return check_task_files_1.checkFilesAreOutputs; } });
|
|
94
97
|
var catalog_1 = require("./utils/catalog");
|
|
95
98
|
Object.defineProperty(exports, "getCatalogManager", { enumerable: true, get: function () { return catalog_1.getCatalogManager; } });
|
|
96
99
|
Object.defineProperty(exports, "getCatalogDependenciesFromPackageJson", { enumerable: true, get: function () { return catalog_1.getCatalogDependenciesFromPackageJson; } });
|
|
@@ -42,9 +42,19 @@ function updateNxJson(tree, nxJson) {
|
|
|
42
42
|
}
|
|
43
43
|
function readNxJsonExtends(tree, extendsPath) {
|
|
44
44
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
let resolvedExtendsPath;
|
|
46
|
+
try {
|
|
47
|
+
resolvedExtendsPath = require.resolve(extendsPath, {
|
|
48
|
+
paths: [tree.root],
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Tree roots without a node_modules folder (e.g. the in-memory trees
|
|
53
|
+
// used in tests) can't anchor module resolution; fall back to
|
|
54
|
+
// resolving from the running nx package.
|
|
55
|
+
resolvedExtendsPath = require.resolve(extendsPath);
|
|
56
|
+
}
|
|
57
|
+
return (0, json_1.readJson)(tree, (0, path_1.relative)(tree.root, resolvedExtendsPath));
|
|
48
58
|
}
|
|
49
59
|
catch (e) {
|
|
50
60
|
throw new Error(`Unable to resolve nx.json extends. Error: ${e.message}`);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type NxJsonConfiguration } from '../config/nx-json';
|
|
2
|
+
import type { ProjectGraph } from '../config/project-graph';
|
|
3
|
+
import type { HashInputs } from '../native';
|
|
4
|
+
/**
|
|
5
|
+
* A project graph the caller has already built. `nx show target` resolves one to
|
|
6
|
+
* find the target in the first place, and `createProjectGraphAsync` is not
|
|
7
|
+
* memoized, so without this the CLI would build the graph a second time.
|
|
8
|
+
*/
|
|
9
|
+
export interface TaskFileCheckSeed {
|
|
10
|
+
projectGraph: ProjectGraph;
|
|
11
|
+
nxJson: NxJsonConfiguration;
|
|
12
|
+
}
|
|
13
|
+
/** The rule that made a value an input for a task. */
|
|
14
|
+
export type InputCategory = 'files' | 'depOutputs' | 'dependentTasksOutputFiles' | 'runtime' | 'environment' | 'external';
|
|
15
|
+
export interface InputCandidate {
|
|
16
|
+
/** The value as supplied — matched verbatim against environment/runtime/external. */
|
|
17
|
+
value: string;
|
|
18
|
+
/** Workspace-relative path form of `value` — matched against the path categories. */
|
|
19
|
+
path: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Check which values are legitimate inputs for the given task. A value matches
|
|
23
|
+
* when it is:
|
|
24
|
+
* - a declared environment variable, runtime input, or external dependency;
|
|
25
|
+
* - a file in the task's declared input file list;
|
|
26
|
+
* - a file in the task's materialized `depOutputs` (upstream has run);
|
|
27
|
+
* - a file matching a `dependentTasksOutputFiles` glob declared on the task
|
|
28
|
+
* that lies inside the declared outputs of an upstream task in the task
|
|
29
|
+
* graph (static — works even when upstream tasks have not yet run).
|
|
30
|
+
*
|
|
31
|
+
* `categories` records the rule each matched value satisfied. Paths may be
|
|
32
|
+
* workspace-relative or absolute; absolute ones are relativized against the
|
|
33
|
+
* workspace root, and a path outside the workspace simply matches nothing. A
|
|
34
|
+
* caller resolving paths against a cwd passes an {@link InputCandidate} so that
|
|
35
|
+
* names are still matched verbatim.
|
|
36
|
+
*/
|
|
37
|
+
export declare function checkFilesAreInputs(taskId: string, files: Array<string | InputCandidate>): Promise<{
|
|
38
|
+
matched: string[];
|
|
39
|
+
unmatched: string[];
|
|
40
|
+
categories: Map<string, InputCategory>;
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Check which files match the output globs declared for the given task.
|
|
44
|
+
* Uses the same path-matching logic as the task runner (directory containment
|
|
45
|
+
* + glob matching through the native `globset` engine), including negated
|
|
46
|
+
* (`!`-prefixed) patterns acting as exclusions over the whole pattern set.
|
|
47
|
+
*
|
|
48
|
+
* Paths may be workspace-relative or absolute; absolute ones are relativized
|
|
49
|
+
* against the workspace root. An output pattern whose `{options.*}` token has no
|
|
50
|
+
* value resolves to nothing — exactly as the task runner drops it — so a file it
|
|
51
|
+
* would have covered is reported `unmatched`, like any other non-output.
|
|
52
|
+
*
|
|
53
|
+
* That last case makes `unmatched` two answers in one: "not an output" and
|
|
54
|
+
* "the outputs could not be determined". A consumer judging sandbox violations
|
|
55
|
+
* cannot tell them apart, and would call the second one illegal. `getTaskOutputs`
|
|
56
|
+
* already computes the `unresolved` list this would need; surfacing it here is
|
|
57
|
+
* deliberately deferred until a consumer's contract asks for the distinction.
|
|
58
|
+
*/
|
|
59
|
+
export declare function checkFilesAreOutputs(taskId: string, files: string[]): Promise<{
|
|
60
|
+
matched: string[];
|
|
61
|
+
unmatched: string[];
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Returns the full hash inputs for a task (files + runtime + env + depOutputs
|
|
65
|
+
* + external). Used internally by the `nx show target --inputs` renderer.
|
|
66
|
+
*/
|
|
67
|
+
export declare function getTaskRawInputs(taskId: string, seed?: TaskFileCheckSeed): Promise<HashInputs | null>;
|
|
68
|
+
export interface TaskOutputs {
|
|
69
|
+
/** Output patterns after token substitution — what the task runner will cache. */
|
|
70
|
+
resolved: string[];
|
|
71
|
+
/** `resolved`, expanded against the files currently on disk. */
|
|
72
|
+
expanded: string[];
|
|
73
|
+
/** Configured outputs left out of `resolved` because an option had no value. */
|
|
74
|
+
unresolved: string[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Returns the outputs declared for a task, resolved against its effective
|
|
78
|
+
* configuration. Used internally by the `nx show target --outputs` renderer.
|
|
79
|
+
*/
|
|
80
|
+
export declare function getTaskOutputs(taskId: string, seed?: TaskFileCheckSeed): Promise<TaskOutputs>;
|
|
81
|
+
/**
|
|
82
|
+
* Resets all module-level caches. Call this in `beforeEach` when testing so
|
|
83
|
+
* each test gets a fresh context load. Not part of the public API.
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
export declare function _resetContextForTesting(): void;
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkFilesAreInputs = checkFilesAreInputs;
|
|
4
|
+
exports.checkFilesAreOutputs = checkFilesAreOutputs;
|
|
5
|
+
exports.getTaskRawInputs = getTaskRawInputs;
|
|
6
|
+
exports.getTaskOutputs = getTaskOutputs;
|
|
7
|
+
exports._resetContextForTesting = _resetContextForTesting;
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const nx_json_1 = require("../config/nx-json");
|
|
10
|
+
const native_1 = require("../native");
|
|
11
|
+
const project_graph_1 = require("../project-graph/project-graph");
|
|
12
|
+
const create_task_graph_1 = require("../tasks-runner/create-task-graph");
|
|
13
|
+
const utils_1 = require("../tasks-runner/utils");
|
|
14
|
+
const path_2 = require("../utils/path");
|
|
15
|
+
const project_graph_utils_1 = require("../utils/project-graph-utils");
|
|
16
|
+
const split_target_1 = require("../utils/split-target");
|
|
17
|
+
const workspace_root_1 = require("../utils/workspace-root");
|
|
18
|
+
const hash_plan_inspector_1 = require("./hash-plan-inspector");
|
|
19
|
+
const task_hasher_1 = require("./task-hasher");
|
|
20
|
+
let cachedContext = null;
|
|
21
|
+
function getContext(seed) {
|
|
22
|
+
// Only a fulfilled context is cached — caching a rejected promise would
|
|
23
|
+
// poison the resolver for the rest of the process after one transient failure.
|
|
24
|
+
return (cachedContext ??= loadContext(seed).catch((e) => {
|
|
25
|
+
cachedContext = null;
|
|
26
|
+
throw e;
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
async function loadContext(seed) {
|
|
30
|
+
const projectGraph = seed?.projectGraph ?? (await (0, project_graph_1.createProjectGraphAsync)());
|
|
31
|
+
const nxJson = seed?.nxJson ?? (0, nx_json_1.readNxJson)(workspace_root_1.workspaceRoot) ?? {};
|
|
32
|
+
let inspector = null;
|
|
33
|
+
const getInspector = () =>
|
|
34
|
+
// As with the context itself, a rejection is not cached.
|
|
35
|
+
(inspector ??= initInspector(projectGraph, nxJson).catch((e) => {
|
|
36
|
+
inspector = null;
|
|
37
|
+
throw e;
|
|
38
|
+
}));
|
|
39
|
+
return { projectGraph, nxJson, getInspector };
|
|
40
|
+
}
|
|
41
|
+
async function initInspector(projectGraph, nxJson) {
|
|
42
|
+
const inspector = new hash_plan_inspector_1.HashPlanInspector(projectGraph, workspace_root_1.workspaceRoot, nxJson);
|
|
43
|
+
await inspector.init();
|
|
44
|
+
return inspector;
|
|
45
|
+
}
|
|
46
|
+
const identityCache = new Map();
|
|
47
|
+
const hashInputsCache = new Map();
|
|
48
|
+
const outputsCache = new Map();
|
|
49
|
+
const taskGraphCache = new Map();
|
|
50
|
+
const depsOutputsCache = new Map();
|
|
51
|
+
// ── Internal resolution helpers ──────────────────────────────────────────────
|
|
52
|
+
function resolveIdentity(taskId, projectGraph) {
|
|
53
|
+
const cached = identityCache.get(taskId);
|
|
54
|
+
if (cached)
|
|
55
|
+
return cached;
|
|
56
|
+
const [project, target, configuration] = (0, split_target_1.splitTarget)(taskId, projectGraph);
|
|
57
|
+
if (!project || !target) {
|
|
58
|
+
throw new Error(`Invalid taskId "${taskId}" — expected "project:target[:configuration]"`);
|
|
59
|
+
}
|
|
60
|
+
const projectNode = projectGraph.nodes[project];
|
|
61
|
+
if (!projectNode) {
|
|
62
|
+
throw new Error(`Invalid taskId "${taskId}" — project "${project}" does not exist in the project graph.`);
|
|
63
|
+
}
|
|
64
|
+
const targetConfig = projectNode.data?.targets?.[target];
|
|
65
|
+
if (!targetConfig) {
|
|
66
|
+
throw new Error(`Invalid taskId "${taskId}" — project "${project}" has no target "${target}".`);
|
|
67
|
+
}
|
|
68
|
+
// Substituting defaultConfiguration for a configuration that does not exist
|
|
69
|
+
// would answer confidently about a *different* task — and configurations
|
|
70
|
+
// routinely change `outputPath`, so the answer could be wrong in either
|
|
71
|
+
// direction. `nx run` errors here; so do we.
|
|
72
|
+
if (configuration &&
|
|
73
|
+
!(0, project_graph_utils_1.projectHasTargetAndConfiguration)(projectNode, target, configuration)) {
|
|
74
|
+
const available = Object.keys(targetConfig.configurations ?? {});
|
|
75
|
+
throw new Error(`Invalid taskId "${taskId}" — target "${target}" of project "${project}" has no configuration "${configuration}".` +
|
|
76
|
+
(available.length
|
|
77
|
+
? ` Available configurations: ${available.join(', ')}.`
|
|
78
|
+
: ' It has no configurations.'));
|
|
79
|
+
}
|
|
80
|
+
const effectiveConfiguration = configuration ?? targetConfig.defaultConfiguration;
|
|
81
|
+
const identity = {
|
|
82
|
+
project,
|
|
83
|
+
target,
|
|
84
|
+
configuration: effectiveConfiguration,
|
|
85
|
+
canonicalTaskId: (0, utils_1.createTaskId)(project, target, effectiveConfiguration),
|
|
86
|
+
projectNode,
|
|
87
|
+
};
|
|
88
|
+
identityCache.set(taskId, identity);
|
|
89
|
+
return identity;
|
|
90
|
+
}
|
|
91
|
+
async function getRawInputs(taskId, { projectGraph, getInspector }) {
|
|
92
|
+
if (hashInputsCache.has(taskId)) {
|
|
93
|
+
return hashInputsCache.get(taskId) ?? null;
|
|
94
|
+
}
|
|
95
|
+
const { project, target, configuration, canonicalTaskId } = resolveIdentity(taskId, projectGraph);
|
|
96
|
+
const inspector = await getInspector();
|
|
97
|
+
// `null` means "this task is absent from the hash plan" — any other failure
|
|
98
|
+
// is a real error and propagates to the caller.
|
|
99
|
+
const planResult = inspector.inspectTaskInputs({
|
|
100
|
+
project,
|
|
101
|
+
target,
|
|
102
|
+
configuration,
|
|
103
|
+
});
|
|
104
|
+
const result = planResult[canonicalTaskId] ?? null;
|
|
105
|
+
hashInputsCache.set(taskId, result);
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
function getOutputs(taskId, projectGraph) {
|
|
109
|
+
const cached = outputsCache.get(taskId);
|
|
110
|
+
if (cached !== undefined)
|
|
111
|
+
return cached;
|
|
112
|
+
const { project, target, configuration, projectNode } = resolveIdentity(taskId, projectGraph);
|
|
113
|
+
const outputs = (0, utils_1.getOutputsForTargetAndConfiguration)({ project, target, configuration }, {}, projectNode).map(path_2.normalizePath);
|
|
114
|
+
outputsCache.set(taskId, outputs);
|
|
115
|
+
return outputs;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Configured outputs that `getOutputsForTargetAndConfiguration` dropped because
|
|
119
|
+
* an `{options.x}` token had no value to interpolate. The resolver discards them
|
|
120
|
+
* silently, so they have to be recovered from the target configuration.
|
|
121
|
+
*/
|
|
122
|
+
function getUnresolvedOutputs(taskId, projectGraph) {
|
|
123
|
+
const { target, configuration, projectNode } = resolveIdentity(taskId, projectGraph);
|
|
124
|
+
const targetConfig = projectNode.data.targets[target];
|
|
125
|
+
const options = {
|
|
126
|
+
...targetConfig.options,
|
|
127
|
+
...(configuration
|
|
128
|
+
? targetConfig.configurations?.[configuration]
|
|
129
|
+
: undefined),
|
|
130
|
+
};
|
|
131
|
+
return (targetConfig.outputs ?? []).filter((output) => [...output.matchAll(/\{options\.([^}]+)\}/g)].some(([, key]) => {
|
|
132
|
+
const value = key.split('.').reduce((acc, k) => acc?.[k], options);
|
|
133
|
+
return value === undefined;
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
function getTaskGraph(taskId, projectGraph) {
|
|
137
|
+
const cached = taskGraphCache.get(taskId);
|
|
138
|
+
if (cached)
|
|
139
|
+
return cached;
|
|
140
|
+
const { project, target, configuration } = resolveIdentity(taskId, projectGraph);
|
|
141
|
+
const taskGraph = (0, create_task_graph_1.createTaskGraph)(projectGraph, {}, [project], [target], configuration, {}, false);
|
|
142
|
+
taskGraphCache.set(taskId, taskGraph);
|
|
143
|
+
return taskGraph;
|
|
144
|
+
}
|
|
145
|
+
function getDepsOutputs(taskId, { projectGraph, nxJson }) {
|
|
146
|
+
if (depsOutputsCache.has(taskId))
|
|
147
|
+
return depsOutputsCache.get(taskId);
|
|
148
|
+
const { project, target } = resolveIdentity(taskId, projectGraph);
|
|
149
|
+
const result = (0, task_hasher_1.getInputs)({ target: { project, target } }, projectGraph, nxJson)
|
|
150
|
+
.depsOutputs ?? [];
|
|
151
|
+
depsOutputsCache.set(taskId, result);
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
function collectUpstreamTaskIds(taskGraph, rootTaskId, transitive) {
|
|
155
|
+
const direct = taskGraph.dependencies[rootTaskId] ?? [];
|
|
156
|
+
if (!transitive)
|
|
157
|
+
return [...direct];
|
|
158
|
+
const collected = new Set();
|
|
159
|
+
const walk = (id) => {
|
|
160
|
+
for (const dep of taskGraph.dependencies[id] ?? []) {
|
|
161
|
+
if (collected.has(dep))
|
|
162
|
+
continue;
|
|
163
|
+
collected.add(dep);
|
|
164
|
+
walk(dep);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
walk(rootTaskId);
|
|
168
|
+
return [...collected];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Matches a single path against a task's whole output pattern list using the
|
|
172
|
+
* native glob engine (`globset`) that the task runner's expand_outputs also
|
|
173
|
+
* builds on: non-glob patterns match themselves and anything nested under them,
|
|
174
|
+
* and negated (`!`-prefixed) patterns act as exclusions over the full set.
|
|
175
|
+
*/
|
|
176
|
+
function isOutput(taskId, path, projectGraph) {
|
|
177
|
+
const patterns = getOutputs(taskId, projectGraph);
|
|
178
|
+
return (0, native_1.matchOutputPaths)(patterns, [(0, path_2.normalizePath)(path)])[0];
|
|
179
|
+
}
|
|
180
|
+
function matchesDependentTaskOutputs(taskId, path, ctx) {
|
|
181
|
+
const normalized = (0, path_2.normalizePath)(path);
|
|
182
|
+
const depsOutputs = getDepsOutputs(taskId, ctx);
|
|
183
|
+
if (depsOutputs.length === 0)
|
|
184
|
+
return false;
|
|
185
|
+
const taskGraph = getTaskGraph(taskId, ctx.projectGraph);
|
|
186
|
+
const { canonicalTaskId } = resolveIdentity(taskId, ctx.projectGraph);
|
|
187
|
+
if (!taskGraph.tasks[canonicalTaskId])
|
|
188
|
+
return false;
|
|
189
|
+
for (const { dependentTasksOutputFiles, transitive } of depsOutputs) {
|
|
190
|
+
const glob = (0, path_2.normalizePath)(dependentTasksOutputFiles);
|
|
191
|
+
if (!(0, native_1.matchGlobPaths)([glob], [normalized])[0])
|
|
192
|
+
continue;
|
|
193
|
+
const upstreamIds = collectUpstreamTaskIds(taskGraph, canonicalTaskId, !!transitive);
|
|
194
|
+
for (const upstreamId of upstreamIds) {
|
|
195
|
+
if (isOutput(upstreamId, normalized, ctx.projectGraph))
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Coerces a caller-supplied path to the workspace-relative, forward-slashed
|
|
203
|
+
* form the hash plan and output patterns are expressed in. A path outside the
|
|
204
|
+
* workspace stays outside (`../…`) and simply matches nothing — it cannot be a
|
|
205
|
+
* declared input or output, so "unmatched" is the true answer rather than an
|
|
206
|
+
* error.
|
|
207
|
+
*/
|
|
208
|
+
function toWorkspaceRelativePath(candidatePath) {
|
|
209
|
+
// Backslash separators must be split *before* anchoring: on POSIX a backslash
|
|
210
|
+
// is an ordinary filename character, so `dep\..\..` would ride through
|
|
211
|
+
// join/relative as one opaque segment and only become a live `..` traversal
|
|
212
|
+
// after the swap. Swapped directly rather than via normalizePath, which also
|
|
213
|
+
// strips a Windows drive letter: isAbsolute accepts the drive-less form too,
|
|
214
|
+
// but relative() then resolves it against the cwd's drive, so a path on
|
|
215
|
+
// another drive (`D:\…`) could be relativized to *inside* the workspace — a
|
|
216
|
+
// Windows-only fail-open no POSIX test can catch.
|
|
217
|
+
const forwardSlashed = candidatePath.replace(/\\/g, '/');
|
|
218
|
+
// Anchoring a relative path to the workspace root before relativizing it back
|
|
219
|
+
// resolves any `..` segments. Left in, they would traverse *through* a pattern
|
|
220
|
+
// that globset had already matched — `dist/libs/dep/../../../secrets.env`
|
|
221
|
+
// matching an output of `dist/libs/dep`.
|
|
222
|
+
const absolute = (0, path_1.isAbsolute)(forwardSlashed)
|
|
223
|
+
? forwardSlashed
|
|
224
|
+
: (0, path_1.join)(workspace_root_1.workspaceRoot, forwardSlashed);
|
|
225
|
+
return (0, path_2.normalizePath)((0, path_1.relative)(workspace_root_1.workspaceRoot, absolute));
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* The task's hash inputs, or an error. A task missing from its own hash plan is
|
|
229
|
+
* a failure to *determine* the inputs — reporting every file as unmatched would
|
|
230
|
+
* tell a sandbox-violation consumer that all of them are illegal.
|
|
231
|
+
*/
|
|
232
|
+
async function requireRawInputs(taskId, ctx) {
|
|
233
|
+
const raw = await getRawInputs(taskId, ctx);
|
|
234
|
+
if (!raw) {
|
|
235
|
+
throw new Error(`Could not determine the inputs of task "${taskId}" — it is not present in its own hash plan.`);
|
|
236
|
+
}
|
|
237
|
+
return raw;
|
|
238
|
+
}
|
|
239
|
+
function classifyInput(taskId, candidate, raw, ctx) {
|
|
240
|
+
// `environment`, `runtime` and `external` hold names rather than paths, so
|
|
241
|
+
// they are matched against the value exactly as the caller supplied it.
|
|
242
|
+
if (raw.environment.includes(candidate.value))
|
|
243
|
+
return 'environment';
|
|
244
|
+
if (raw.runtime.includes(candidate.value))
|
|
245
|
+
return 'runtime';
|
|
246
|
+
if (raw.external.includes(candidate.value))
|
|
247
|
+
return 'external';
|
|
248
|
+
const path = toWorkspaceRelativePath(candidate.path);
|
|
249
|
+
if (raw.files.includes(path))
|
|
250
|
+
return 'files';
|
|
251
|
+
if (raw.depOutputs.includes(path))
|
|
252
|
+
return 'depOutputs';
|
|
253
|
+
return matchesDependentTaskOutputs(taskId, path, ctx)
|
|
254
|
+
? 'dependentTasksOutputFiles'
|
|
255
|
+
: null;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Check which values are legitimate inputs for the given task. A value matches
|
|
259
|
+
* when it is:
|
|
260
|
+
* - a declared environment variable, runtime input, or external dependency;
|
|
261
|
+
* - a file in the task's declared input file list;
|
|
262
|
+
* - a file in the task's materialized `depOutputs` (upstream has run);
|
|
263
|
+
* - a file matching a `dependentTasksOutputFiles` glob declared on the task
|
|
264
|
+
* that lies inside the declared outputs of an upstream task in the task
|
|
265
|
+
* graph (static — works even when upstream tasks have not yet run).
|
|
266
|
+
*
|
|
267
|
+
* `categories` records the rule each matched value satisfied. Paths may be
|
|
268
|
+
* workspace-relative or absolute; absolute ones are relativized against the
|
|
269
|
+
* workspace root, and a path outside the workspace simply matches nothing. A
|
|
270
|
+
* caller resolving paths against a cwd passes an {@link InputCandidate} so that
|
|
271
|
+
* names are still matched verbatim.
|
|
272
|
+
*/
|
|
273
|
+
async function checkFilesAreInputs(taskId, files) {
|
|
274
|
+
const ctx = await getContext();
|
|
275
|
+
// Resolve the task and its hash plan eagerly, so an unknown task or an
|
|
276
|
+
// undeterminable plan errors even when the file list is empty — rather than
|
|
277
|
+
// being reported as "none of these files are inputs".
|
|
278
|
+
resolveIdentity(taskId, ctx.projectGraph);
|
|
279
|
+
const raw = await requireRawInputs(taskId, ctx);
|
|
280
|
+
const matched = [];
|
|
281
|
+
const unmatched = [];
|
|
282
|
+
const categories = new Map();
|
|
283
|
+
// Results are keyed by value, so a value given two path forms could land in
|
|
284
|
+
// both matched and unmatched at once. Exact duplicates are collapsed instead.
|
|
285
|
+
const seenPaths = new Map();
|
|
286
|
+
for (const file of files) {
|
|
287
|
+
const candidate = typeof file === 'string' ? { value: file, path: file } : file;
|
|
288
|
+
const seenPath = seenPaths.get(candidate.value);
|
|
289
|
+
if (seenPath !== undefined) {
|
|
290
|
+
if (seenPath !== candidate.path) {
|
|
291
|
+
throw new Error(`Value "${candidate.value}" was given conflicting path forms "${seenPath}" and "${candidate.path}".`);
|
|
292
|
+
}
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
seenPaths.set(candidate.value, candidate.path);
|
|
296
|
+
const category = classifyInput(taskId, candidate, raw, ctx);
|
|
297
|
+
if (category) {
|
|
298
|
+
matched.push(candidate.value);
|
|
299
|
+
categories.set(candidate.value, category);
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
unmatched.push(candidate.value);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return { matched, unmatched, categories };
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Check which files match the output globs declared for the given task.
|
|
309
|
+
* Uses the same path-matching logic as the task runner (directory containment
|
|
310
|
+
* + glob matching through the native `globset` engine), including negated
|
|
311
|
+
* (`!`-prefixed) patterns acting as exclusions over the whole pattern set.
|
|
312
|
+
*
|
|
313
|
+
* Paths may be workspace-relative or absolute; absolute ones are relativized
|
|
314
|
+
* against the workspace root. An output pattern whose `{options.*}` token has no
|
|
315
|
+
* value resolves to nothing — exactly as the task runner drops it — so a file it
|
|
316
|
+
* would have covered is reported `unmatched`, like any other non-output.
|
|
317
|
+
*
|
|
318
|
+
* That last case makes `unmatched` two answers in one: "not an output" and
|
|
319
|
+
* "the outputs could not be determined". A consumer judging sandbox violations
|
|
320
|
+
* cannot tell them apart, and would call the second one illegal. `getTaskOutputs`
|
|
321
|
+
* already computes the `unresolved` list this would need; surfacing it here is
|
|
322
|
+
* deliberately deferred until a consumer's contract asks for the distinction.
|
|
323
|
+
*/
|
|
324
|
+
async function checkFilesAreOutputs(taskId, files) {
|
|
325
|
+
const ctx = await getContext();
|
|
326
|
+
// Validate taskId eagerly so callers always get an error for an unknown or
|
|
327
|
+
// malformed task, even when the file list is empty.
|
|
328
|
+
resolveIdentity(taskId, ctx.projectGraph);
|
|
329
|
+
const patterns = getOutputs(taskId, ctx.projectGraph);
|
|
330
|
+
const results = (0, native_1.matchOutputPaths)(patterns, files.map(toWorkspaceRelativePath));
|
|
331
|
+
const matched = [];
|
|
332
|
+
const unmatched = [];
|
|
333
|
+
files.forEach((file, i) => {
|
|
334
|
+
if (results[i]) {
|
|
335
|
+
matched.push(file);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
unmatched.push(file);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
return { matched, unmatched };
|
|
342
|
+
}
|
|
343
|
+
// ── Renderer helpers (used by `nx show target`) ──────────────────────────────
|
|
344
|
+
/**
|
|
345
|
+
* Returns the full hash inputs for a task (files + runtime + env + depOutputs
|
|
346
|
+
* + external). Used internally by the `nx show target --inputs` renderer.
|
|
347
|
+
*/
|
|
348
|
+
async function getTaskRawInputs(taskId, seed) {
|
|
349
|
+
const ctx = await getContext(seed);
|
|
350
|
+
return getRawInputs(taskId, ctx);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Returns the outputs declared for a task, resolved against its effective
|
|
354
|
+
* configuration. Used internally by the `nx show target --outputs` renderer.
|
|
355
|
+
*/
|
|
356
|
+
async function getTaskOutputs(taskId, seed) {
|
|
357
|
+
const ctx = await getContext(seed);
|
|
358
|
+
const resolved = getOutputs(taskId, ctx.projectGraph);
|
|
359
|
+
return {
|
|
360
|
+
resolved,
|
|
361
|
+
expanded: (0, native_1.expandOutputs)(workspace_root_1.workspaceRoot, resolved),
|
|
362
|
+
unresolved: getUnresolvedOutputs(taskId, ctx.projectGraph),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
// ── Test utilities ───────────────────────────────────────────────────────────
|
|
366
|
+
/**
|
|
367
|
+
* Resets all module-level caches. Call this in `beforeEach` when testing so
|
|
368
|
+
* each test gets a fresh context load. Not part of the public API.
|
|
369
|
+
* @internal
|
|
370
|
+
*/
|
|
371
|
+
function _resetContextForTesting() {
|
|
372
|
+
cachedContext = null;
|
|
373
|
+
identityCache.clear();
|
|
374
|
+
hashInputsCache.clear();
|
|
375
|
+
outputsCache.clear();
|
|
376
|
+
taskGraphCache.clear();
|
|
377
|
+
depsOutputsCache.clear();
|
|
378
|
+
}
|
|
@@ -17,6 +17,10 @@ interface WriterArc {
|
|
|
17
17
|
readonly __brand: unique symbol;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
interface MasterArc {
|
|
21
|
+
readonly __brand: unique symbol;
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
interface HashInstruction {
|
|
21
25
|
readonly __brand: unique symbol;
|
|
22
26
|
}
|
|
@@ -36,7 +40,7 @@ export declare class AppLifeCycle {
|
|
|
36
40
|
endTasks(taskResults: Array<TaskResult>, metadata: object): void
|
|
37
41
|
endCommand(summary?: PerformanceSummaryPayload | undefined | null): void
|
|
38
42
|
__init(doneCallback: (() => unknown)): void
|
|
39
|
-
registerRunningTask(taskId: string,
|
|
43
|
+
registerRunningTask(taskId: string, ptyHandles: ExternalObject<[ParserArc, WriterArc, MasterArc]>): void
|
|
40
44
|
registerRunningTaskWithEmptyParser(taskId: string): void
|
|
41
45
|
appendTaskOutput(taskId: string, output: string, isPtyOutput: boolean): void
|
|
42
46
|
setTaskStatus(taskId: string, status: TaskStatus): void
|
|
@@ -56,7 +60,7 @@ export declare class AppLifeCycle {
|
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
export declare class ChildProcess {
|
|
59
|
-
|
|
63
|
+
getPtyHandles(): ExternalObject<[ParserArc, WriterArc, MasterArc]>
|
|
60
64
|
getPid(): number
|
|
61
65
|
kill(signal?: NodeJS.Signals | number): void
|
|
62
66
|
onExit(callback: (message: string) => void): void
|
|
@@ -326,6 +330,7 @@ export interface EventDimensions {
|
|
|
326
330
|
packageName: string
|
|
327
331
|
packageVersion: string
|
|
328
332
|
duration: string
|
|
333
|
+
sampleRate: string
|
|
329
334
|
taskCount: string
|
|
330
335
|
projectCount: string
|
|
331
336
|
cachedTaskCount: string
|
|
@@ -557,6 +562,24 @@ export interface Link {
|
|
|
557
562
|
|
|
558
563
|
export declare function logDebug(message: string): void
|
|
559
564
|
|
|
565
|
+
/**
|
|
566
|
+
* Checks which `paths` match the given `globs`, using the same glob engine
|
|
567
|
+
* as the task hasher (`build_glob_set`). Used to statically match
|
|
568
|
+
* `dependentTasksOutputFiles` globs against candidate paths.
|
|
569
|
+
*/
|
|
570
|
+
export declare function matchGlobPaths(globs: Array<string>, paths: Array<string>): Array<boolean>
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Statically checks which `paths` would be captured by the given output
|
|
574
|
+
* `entries`, without touching the file system. Mirrors `expand_outputs`
|
|
575
|
+
* semantics: entries match themselves and anything nested under them (so a
|
|
576
|
+
* directory entry captures its contents), negated (`!`-prefixed) entries
|
|
577
|
+
* exclude matches from the whole entry set, and a non-empty list with only
|
|
578
|
+
* negated entries matches everything not excluded. An empty list matches
|
|
579
|
+
* nothing.
|
|
580
|
+
*/
|
|
581
|
+
export declare function matchOutputPaths(entries: Array<string>, paths: Array<string>): Array<boolean>
|
|
582
|
+
|
|
560
583
|
/** Combined metadata for groups and processes */
|
|
561
584
|
export interface Metadata {
|
|
562
585
|
/** Group-level metadata */
|
|
@@ -625,6 +625,8 @@ module.exports.isEditorInstalled = nativeBinding.isEditorInstalled
|
|
|
625
625
|
module.exports.killProcessTree = nativeBinding.killProcessTree
|
|
626
626
|
module.exports.killProcessTreeGraceful = nativeBinding.killProcessTreeGraceful
|
|
627
627
|
module.exports.logDebug = nativeBinding.logDebug
|
|
628
|
+
module.exports.matchGlobPaths = nativeBinding.matchGlobPaths
|
|
629
|
+
module.exports.matchOutputPaths = nativeBinding.matchOutputPaths
|
|
628
630
|
module.exports.parseTaskStatus = nativeBinding.parseTaskStatus
|
|
629
631
|
module.exports.remove = nativeBinding.remove
|
|
630
632
|
module.exports.restoreTerminal = nativeBinding.restoreTerminal
|
|
Binary file
|
|
Binary file
|
|
@@ -54,6 +54,9 @@ const YAML_DOCUMENT_SEPARATOR = '\n---\n';
|
|
|
54
54
|
// always read the workspace document.
|
|
55
55
|
// https://github.com/pnpm/pnpm/blob/main/lockfile/fs/src/yamlDocuments.ts
|
|
56
56
|
function extractMainLockfileDocument(content) {
|
|
57
|
+
// Lockfiles written on Windows may use CRLF line endings, which would never
|
|
58
|
+
// match the LF-only document markers.
|
|
59
|
+
content = content.replace(/\r\n/g, '\n');
|
|
57
60
|
if (!content.startsWith(YAML_DOCUMENT_START)) {
|
|
58
61
|
return content;
|
|
59
62
|
}
|