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.
Files changed (59) hide show
  1. package/dist/bin/nx.js +1 -1
  2. package/dist/src/analytics/analytics.d.ts +6 -0
  3. package/dist/src/analytics/analytics.js +7 -1
  4. package/dist/src/analytics/index.d.ts +1 -1
  5. package/dist/src/analytics/index.js +2 -1
  6. package/dist/src/command-line/init/ai-agent-prompts.d.ts +1 -1
  7. package/dist/src/command-line/init/ai-agent-prompts.js +7 -6
  8. package/dist/src/command-line/init/command-object.js +10 -2
  9. package/dist/src/command-line/init/implementation/angular/integrated-workspace.js +2 -5
  10. package/dist/src/command-line/init/implementation/angular/legacy-angular-versions.js +3 -0
  11. package/dist/src/command-line/init/implementation/dot-nx/add-nx-scripts.js +4 -1
  12. package/dist/src/command-line/migrate/execute-migration.d.ts +72 -0
  13. package/dist/src/command-line/migrate/execute-migration.js +389 -0
  14. package/dist/src/command-line/migrate/migrate-ui-api.js +17 -12
  15. package/dist/src/command-line/migrate/migrate.d.ts +1 -45
  16. package/dist/src/command-line/migrate/migrate.js +17 -375
  17. package/dist/src/command-line/nx-cloud/connect/view-logs.js +2 -0
  18. package/dist/src/command-line/release/utils/release-graph.d.ts +2 -2
  19. package/dist/src/command-line/release/utils/shared.d.ts +2 -2
  20. package/dist/src/command-line/show/show-target/inputs.js +50 -153
  21. package/dist/src/command-line/show/show-target/outputs.js +48 -173
  22. package/dist/src/command-line/show/show-target/utils.d.ts +28 -0
  23. package/dist/src/command-line/show/show-target/utils.js +83 -3
  24. package/dist/src/command-line/yargs-utils/shared-options.d.ts +1 -1
  25. package/dist/src/commands-runner/create-command-graph.js +5 -0
  26. package/dist/src/config/nx-json.d.ts +1 -1
  27. package/dist/src/core/graph/main.js +1 -1
  28. package/dist/src/daemon/server/handle-tasks-execution-hooks.d.ts +1 -1
  29. package/dist/src/daemon/server/server.js +7 -4
  30. package/dist/src/devkit-internals.d.ts +1 -0
  31. package/dist/src/devkit-internals.js +5 -2
  32. package/dist/src/generators/utils/nx-json.js +13 -3
  33. package/dist/src/hasher/check-task-files.d.ts +86 -0
  34. package/dist/src/hasher/check-task-files.js +378 -0
  35. package/dist/src/native/index.d.ts +25 -2
  36. package/dist/src/native/native-bindings.js +2 -0
  37. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  38. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  39. package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.js +3 -0
  40. package/dist/src/project-graph/build-project-graph.js +7 -1
  41. package/dist/src/project-graph/file-utils.js +6 -1
  42. package/dist/src/project-graph/plugins/loaded-nx-plugin.js +1 -0
  43. package/dist/src/project-graph/project-graph.js +3 -0
  44. package/dist/src/tasks-runner/life-cycle.d.ts +2 -2
  45. package/dist/src/tasks-runner/life-cycle.js +2 -2
  46. package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +9 -4
  47. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +5 -4
  48. package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +16 -8
  49. package/dist/src/tasks-runner/life-cycles/performance-report.js +55 -27
  50. package/dist/src/tasks-runner/pseudo-terminal.d.ts +1 -1
  51. package/dist/src/tasks-runner/pseudo-terminal.js +2 -2
  52. package/dist/src/tasks-runner/task-orchestrator.d.ts +4 -1
  53. package/dist/src/tasks-runner/task-orchestrator.js +23 -6
  54. package/dist/src/utils/acknowledge-build-scripts.js +7 -5
  55. package/dist/src/utils/command-line-utils.js +30 -20
  56. package/dist/src/utils/git-revision.d.ts +12 -0
  57. package/dist/src/utils/git-revision.js +25 -0
  58. package/dist/src/utils/package-manager.js +6 -2
  59. 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.getCatalogDependenciesFromPackageJson = exports.getCatalogManager = 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 = 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
- return (0, json_1.readJson)(tree, (0, path_1.relative)(tree.root, require.resolve(extendsPath, {
46
- paths: [tree.root],
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, parserAndWriter: ExternalObject<[ParserArc, WriterArc]>): void
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
- getParserAndWriter(): ExternalObject<[ParserArc, WriterArc]>
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
@@ -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
  }