nx 23.1.0 → 23.1.1
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 +2 -1
- package/dist/src/ai/configure-ai-agents-disclaimer.d.ts +5 -0
- package/dist/src/ai/configure-ai-agents-disclaimer.js +31 -0
- package/dist/src/analytics/analytics.d.ts +6 -0
- package/dist/src/analytics/analytics.js +16 -8
- package/dist/src/analytics/index.d.ts +1 -1
- package/dist/src/analytics/index.js +2 -1
- package/dist/src/command-line/add/add.js +8 -1
- package/dist/src/command-line/graph/graph.d.ts +1 -0
- package/dist/src/command-line/graph/graph.js +15 -6
- package/dist/src/command-line/import/import.js +1 -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/configure-plugins.js +3 -2
- package/dist/src/command-line/init/implementation/add-nx-to-monorepo.js +4 -2
- package/dist/src/command-line/init/implementation/add-nx-to-nest.js +4 -3
- package/dist/src/command-line/init/implementation/add-nx-to-npm-repo.js +4 -3
- package/dist/src/command-line/init/implementation/add-nx-to-turborepo.js +4 -3
- package/dist/src/command-line/init/implementation/angular/index.js +4 -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/init/implementation/utils.d.ts +3 -3
- package/dist/src/command-line/init/implementation/utils.js +22 -3
- 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 +33 -376
- package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +1 -1
- package/dist/src/command-line/nx-cloud/connect/view-logs.js +2 -0
- package/dist/src/command-line/release/utils/git.js +6 -2
- package/dist/src/command-line/release/utils/release-graph.d.ts +2 -2
- package/dist/src/command-line/release/utils/remote-release-clients/github.js +1 -1
- package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +1 -1
- package/dist/src/command-line/release/utils/shared.d.ts +2 -2
- package/dist/src/command-line/show/show-target/info.js +11 -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 +5 -3
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/daemon/client/client.d.ts +0 -2
- package/dist/src/daemon/client/client.js +17 -19
- 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 +3 -0
- package/dist/src/devkit-internals.js +10 -1
- 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 +21 -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/npm-parser.js +71 -27
- package/dist/src/plugins/js/lock-file/pnpm-parser.js +50 -17
- package/dist/src/plugins/js/lock-file/project-graph-pruning.js +1 -1
- package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.js +3 -0
- package/dist/src/plugins/js/lock-file/yarn-parser.js +32 -5
- package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +12 -4
- package/dist/src/plugins/js/utils/register.d.ts +18 -0
- package/dist/src/plugins/js/utils/register.js +81 -0
- package/dist/src/plugins/js/utils/typescript.d.ts +9 -0
- package/dist/src/plugins/js/utils/typescript.js +15 -0
- package/dist/src/project-graph/build-project-graph.js +7 -1
- package/dist/src/project-graph/error-types.js +41 -1
- package/dist/src/project-graph/file-utils.js +6 -1
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +4 -0
- package/dist/src/project-graph/plugins/loaded-nx-plugin.js +1 -0
- package/dist/src/project-graph/plugins/transpiler.js +4 -0
- package/dist/src/project-graph/project-graph.js +3 -0
- package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +10 -12
- package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +93 -63
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +5 -8
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +11 -14
- package/dist/src/project-graph/utils/project-configuration/source-maps.d.ts +12 -5
- package/dist/src/project-graph/utils/project-configuration/source-maps.js +21 -7
- package/dist/src/project-graph/utils/project-configuration/target-defaults.d.ts +1 -1
- package/dist/src/project-graph/utils/project-configuration/target-defaults.js +26 -27
- package/dist/src/project-graph/utils/project-configuration/target-merging.js +50 -8
- package/dist/src/project-graph/utils/project-configuration/target-normalization.js +16 -3
- package/dist/src/project-graph/utils/project-configuration/utils.d.ts +15 -0
- package/dist/src/project-graph/utils/project-configuration/utils.js +42 -6
- package/dist/src/project-graph/utils/project-configuration-utils.d.ts +15 -12
- package/dist/src/project-graph/utils/project-configuration-utils.js +79 -101
- package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +11 -4
- package/dist/src/tasks-runner/life-cycles/performance-analysis.js +15 -6
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +5 -4
- package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +18 -8
- package/dist/src/tasks-runner/life-cycles/performance-report.js +66 -29
- package/dist/src/tasks-runner/run-command.js +4 -2
- package/dist/src/tasks-runner/task-env.d.ts +9 -0
- package/dist/src/tasks-runner/task-env.js +26 -2
- package/dist/src/tasks-runner/task-orchestrator.d.ts +4 -1
- package/dist/src/tasks-runner/task-orchestrator.js +23 -10
- package/dist/src/utils/acknowledge-build-scripts.d.ts +13 -0
- package/dist/src/utils/acknowledge-build-scripts.js +102 -0
- package/dist/src/utils/analytics-prompt.d.ts +0 -6
- package/dist/src/utils/analytics-prompt.js +0 -51
- package/dist/src/utils/catalog/manager-utils.d.ts +1 -2
- package/dist/src/utils/catalog/manager-utils.js +22 -5
- package/dist/src/utils/catalog/pnpm-manager.d.ts +1 -0
- package/dist/src/utils/catalog/pnpm-manager.js +3 -15
- package/dist/src/utils/catalog/yarn-manager.d.ts +1 -0
- package/dist/src/utils/catalog/yarn-manager.js +3 -15
- 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/git-utils.d.ts +16 -3
- package/dist/src/utils/git-utils.js +146 -52
- package/dist/src/utils/min-release-age/behavior/pnpm.js +9 -7
- package/dist/src/utils/package-json.js +11 -7
- package/dist/src/utils/package-manager.d.ts +3 -1
- package/dist/src/utils/package-manager.js +28 -4
- package/dist/src/utils/provenance.js +12 -2
- package/dist/src/utils/workspace-id.d.ts +20 -0
- package/dist/src/utils/workspace-id.js +54 -0
- package/migrations.json +7 -1
- package/package.json +22 -17
- package/dist/src/migrations/update-17-3-0/nx-release-path.d.ts +0 -3
- package/dist/src/migrations/update-17-3-0/nx-release-path.js +0 -47
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.daemonClient = exports.DaemonClient = void 0;
|
|
4
4
|
exports.isDaemonEnabled = isDaemonEnabled;
|
|
5
5
|
const child_process_1 = require("child_process");
|
|
6
|
-
const promises_1 = require("fs/promises");
|
|
7
6
|
const net_1 = require("net");
|
|
8
7
|
const node_fs_1 = require("node:fs");
|
|
9
8
|
const path_1 = require("path");
|
|
@@ -12,6 +11,7 @@ const configuration_1 = require("../../config/configuration");
|
|
|
12
11
|
const nx_json_1 = require("../../config/nx-json");
|
|
13
12
|
const native_1 = require("../../native");
|
|
14
13
|
const error_types_1 = require("../../project-graph/error-types");
|
|
14
|
+
const typescript_1 = require("../../plugins/js/utils/typescript");
|
|
15
15
|
const project_graph_1 = require("../../project-graph/project-graph");
|
|
16
16
|
const consume_messages_from_socket_1 = require("../../utils/consume-messages-from-socket");
|
|
17
17
|
const delayed_spinner_1 = require("../../utils/delayed-spinner");
|
|
@@ -62,8 +62,6 @@ class DaemonClient {
|
|
|
62
62
|
this._daemonStatus = DaemonStatus.DISCONNECTED;
|
|
63
63
|
this._waitForDaemonReady = null;
|
|
64
64
|
this._daemonReady = null;
|
|
65
|
-
this._out = null;
|
|
66
|
-
this._err = null;
|
|
67
65
|
this.fileWatcherReconnecting = false;
|
|
68
66
|
this.fileWatcherCallbacks = new Map();
|
|
69
67
|
this.fileWatcherConfigs = new Map();
|
|
@@ -127,10 +125,6 @@ class DaemonClient {
|
|
|
127
125
|
this.currentResolve = null;
|
|
128
126
|
this.currentReject = null;
|
|
129
127
|
this._enabled = undefined;
|
|
130
|
-
this._out?.close();
|
|
131
|
-
this._err?.close();
|
|
132
|
-
this._out = null;
|
|
133
|
-
this._err = null;
|
|
134
128
|
// Clean up file watcher and project graph listener connections
|
|
135
129
|
this.fileWatcherMessenger?.close();
|
|
136
130
|
this.fileWatcherMessenger = undefined;
|
|
@@ -949,25 +943,29 @@ class DaemonClient {
|
|
|
949
943
|
if (!(0, node_fs_1.existsSync)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE)) {
|
|
950
944
|
(0, node_fs_1.writeFileSync)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE, '');
|
|
951
945
|
}
|
|
952
|
-
//
|
|
953
|
-
//
|
|
954
|
-
//
|
|
955
|
-
//
|
|
956
|
-
const
|
|
957
|
-
|
|
958
|
-
(0, promises_1.open)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE, 'a'),
|
|
959
|
-
]);
|
|
960
|
-
this._out = out;
|
|
961
|
-
this._err = err;
|
|
946
|
+
// Redirect the detached daemon's stdout/stderr into the log file. The
|
|
947
|
+
// child dup's these descriptors at spawn, so we close ours right after
|
|
948
|
+
// instead of holding them for the life of this process (Node >=26 turns a
|
|
949
|
+
// file descriptor closed during garbage collection into a fatal error).
|
|
950
|
+
const outFd = (0, node_fs_1.openSync)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE, 'a');
|
|
951
|
+
const errFd = (0, node_fs_1.openSync)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE, 'a');
|
|
962
952
|
logger_1.clientLogger.log(`[Client] Starting new daemon server in background`);
|
|
963
|
-
const backgroundProcess = (0, child_process_1.spawn)(process.execPath, [
|
|
953
|
+
const backgroundProcess = (0, child_process_1.spawn)(process.execPath, [
|
|
954
|
+
// Spawn with the same resolve conditions Nx uses for plugin entries so a
|
|
955
|
+
// source-loaded plugin's transitive workspace imports resolve to source.
|
|
956
|
+
...(0, typescript_1.getPluginResolveConditionNodeArgs)(),
|
|
957
|
+
(0, path_1.join)(__dirname, `../server/start.js`),
|
|
958
|
+
], {
|
|
964
959
|
cwd: workspace_root_1.workspaceRoot,
|
|
965
|
-
stdio: ['ignore',
|
|
960
|
+
stdio: ['ignore', outFd, errFd],
|
|
966
961
|
detached: true,
|
|
967
962
|
windowsHide: true,
|
|
968
963
|
shell: false,
|
|
969
964
|
env: (0, daemon_environment_1.getDaemonEnv)(),
|
|
970
965
|
});
|
|
966
|
+
// The child now owns dup'd copies of the descriptors, so release ours.
|
|
967
|
+
(0, node_fs_1.closeSync)(outFd);
|
|
968
|
+
(0, node_fs_1.closeSync)(errFd);
|
|
971
969
|
// if this process is the process that spawned the daemon,
|
|
972
970
|
// the daemon env is already up to date
|
|
973
971
|
this.envReflectionSent = true;
|
|
@@ -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,3 +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';
|
|
42
|
+
export { getCatalogManager, getCatalogDependenciesFromPackageJson, } from './utils/catalog';
|
|
43
|
+
export { acknowledgeBuildScripts } from './utils/acknowledge-build-scripts';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
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;
|
|
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;
|
|
4
5
|
const tslib_1 = require("tslib");
|
|
5
6
|
/**
|
|
6
7
|
* Note to developers: STOP! These exports are available via requireNx in @nx/devkit.
|
|
@@ -90,3 +91,11 @@ Object.defineProperty(exports, "SchemaResolutionError", { enumerable: true, get:
|
|
|
90
91
|
var prompt_files_1 = require("./command-line/migrate/prompt-files");
|
|
91
92
|
Object.defineProperty(exports, "resolvePrompt", { enumerable: true, get: function () { return prompt_files_1.resolvePrompt; } });
|
|
92
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; } });
|
|
97
|
+
var catalog_1 = require("./utils/catalog");
|
|
98
|
+
Object.defineProperty(exports, "getCatalogManager", { enumerable: true, get: function () { return catalog_1.getCatalogManager; } });
|
|
99
|
+
Object.defineProperty(exports, "getCatalogDependenciesFromPackageJson", { enumerable: true, get: function () { return catalog_1.getCatalogDependenciesFromPackageJson; } });
|
|
100
|
+
var acknowledge_build_scripts_1 = require("./utils/acknowledge-build-scripts");
|
|
101
|
+
Object.defineProperty(exports, "acknowledgeBuildScripts", { enumerable: true, get: function () { return acknowledge_build_scripts_1.acknowledgeBuildScripts; } });
|
|
@@ -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
|
+
}
|