nx 23.3.0 → 24.0.0-beta.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/README.md +3 -6
- package/dist/nx-cloud-internals.d.ts +1 -0
- package/dist/nx-cloud-internals.js +7 -1
- package/dist/src/adapter/ngcli-adapter.js +1 -0
- package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +0 -12
- package/dist/src/command-line/graph/graph.js +3 -5
- package/dist/src/command-line/init/command-object.js +0 -12
- package/dist/src/command-line/init/init-v2.js +0 -2
- package/dist/src/command-line/migrate/agentic/close-agent-session.d.ts +1 -1
- package/dist/src/command-line/migrate/agentic/close-agent-session.js +4 -3
- package/dist/src/command-line/migrate/agentic/master/spawn-master.js +76 -15
- package/dist/src/command-line/migrate/execute-migration.d.ts +2 -2
- package/dist/src/command-line/migrate/execute-migration.js +18 -10
- package/dist/src/command-line/migrate/migrate-commits.d.ts +4 -1
- package/dist/src/command-line/migrate/migrate-commits.js +10 -3
- package/dist/src/command-line/migrate/migrate.js +43 -6
- package/dist/src/command-line/migrate/run/agent-output.d.ts +1 -0
- package/dist/src/command-line/migrate/run/agent-output.js +5 -0
- package/dist/src/command-line/migrate/run/broker.d.ts +14 -5
- package/dist/src/command-line/migrate/run/broker.js +68 -20
- package/dist/src/command-line/migrate/run/index.d.ts +4 -3
- package/dist/src/command-line/migrate/run/index.js +8 -1
- package/dist/src/command-line/migrate/run/orchestrator.js +72 -13
- package/dist/src/command-line/migrate/run/runbook.js +19 -0
- package/dist/src/command-line/migrate/run/state-machine.js +4 -1
- package/dist/src/command-line/migrate/run/util.d.ts +3 -0
- package/dist/src/command-line/migrate/run/util.js +7 -1
- package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +17 -12
- package/dist/src/command-line/release/utils/remote-release-clients/github.js +5 -6
- package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +5 -6
- package/dist/src/command-line/run/executor-utils.js +1 -0
- package/dist/src/command-line/run/run-one.d.ts +20 -1
- package/dist/src/command-line/run/run-one.js +153 -5
- package/dist/src/command-line/show/show-target/info.js +1 -1
- package/dist/src/command-line/show/show-target/utils.js +1 -0
- package/dist/src/command-line/watch/command-object.js +3 -22
- package/dist/src/command-line/watch/watch.d.ts +0 -7
- package/dist/src/command-line/watch/watch.js +1 -1
- package/dist/src/config/misc-interfaces.d.ts +12 -0
- package/dist/src/config/task-graph.d.ts +1 -1
- package/dist/src/config/workspace-json-project-json.d.ts +65 -0
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/daemon/client/client.d.ts +9 -0
- package/dist/src/daemon/client/client.js +89 -37
- package/dist/src/daemon/server/shutdown-utils.js +0 -3
- package/dist/src/daemon/socket-utils.d.ts +9 -4
- package/dist/src/daemon/socket-utils.js +12 -49
- package/dist/src/daemon/tmp-dir.d.ts +19 -14
- package/dist/src/daemon/tmp-dir.js +142 -354
- package/dist/src/devkit-exports.d.ts +1 -1
- package/dist/src/devkit-internals.d.ts +1 -1
- package/dist/src/devkit-internals.js +1 -0
- package/dist/src/hasher/check-task-files.js +4 -4
- package/dist/src/hasher/file-hasher.js +9 -7
- package/dist/src/hasher/hash-task.js +4 -0
- package/dist/src/hasher/native-task-hasher-impl.js +3 -1
- package/dist/src/migrations/update-24-0-0/find-custom-hashers.d.ts +3 -0
- package/dist/src/migrations/update-24-0-0/find-custom-hashers.js +100 -0
- package/dist/src/migrations/update-24-0-0/find-custom-hashers.md +63 -0
- package/dist/src/migrations/update-24-0-0/move-custom-hashers-to-inputs.md +145 -0
- package/dist/src/native/index.d.ts +180 -12
- package/dist/src/native/native-bindings.js +8 -0
- package/dist/src/native/nx.wasi-browser.js +1 -0
- package/dist/src/native/nx.wasi.cjs +1 -0
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/nx-cloud/update-manager.d.ts +7 -0
- package/dist/src/nx-cloud/update-manager.js +237 -50
- package/dist/src/project-graph/affected/affected-tasks.js +1 -0
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +7 -0
- package/dist/src/tasks-runner/create-task-graph.d.ts +8 -7
- package/dist/src/tasks-runner/create-task-graph.js +47 -26
- package/dist/src/tasks-runner/init-tasks-runner.js +13 -0
- package/dist/src/tasks-runner/life-cycle.d.ts +4 -1
- package/dist/src/tasks-runner/life-cycle.js +7 -0
- package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +5 -2
- package/dist/src/tasks-runner/life-cycles/performance-analysis.js +13 -7
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.d.ts +4 -3
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +9 -2
- package/dist/src/tasks-runner/readiness/probes.d.ts +9 -0
- package/dist/src/tasks-runner/readiness/probes.js +62 -0
- package/dist/src/tasks-runner/readiness/ready-when.d.ts +38 -0
- package/dist/src/tasks-runner/readiness/ready-when.js +190 -0
- package/dist/src/tasks-runner/run-command.js +5 -4
- package/dist/src/tasks-runner/running-tasks/node-child-process.js +5 -4
- package/dist/src/tasks-runner/task-env.d.ts +10 -0
- package/dist/src/tasks-runner/task-env.js +26 -5
- package/dist/src/tasks-runner/task-graph-utils.d.ts +9 -6
- package/dist/src/tasks-runner/task-graph-utils.js +17 -12
- package/dist/src/tasks-runner/task-orchestrator.d.ts +37 -8
- package/dist/src/tasks-runner/task-orchestrator.js +460 -76
- package/dist/src/tasks-runner/tasks-schedule.d.ts +18 -1
- package/dist/src/tasks-runner/tasks-schedule.js +64 -6
- package/dist/src/tasks-runner/utils.d.ts +20 -5
- package/dist/src/tasks-runner/utils.js +70 -35
- package/dist/src/ultracache/overrides.js +4 -0
- package/dist/src/utils/child-process.js +1 -0
- package/dist/src/utils/git-utils.d.ts +6 -1
- package/dist/src/utils/git-utils.js +26 -5
- package/dist/src/utils/owned-private-dir.d.ts +1 -17
- package/dist/src/utils/owned-private-dir.js +10 -62
- package/dist/src/utils/perf-hooks.d.ts +1 -0
- package/dist/src/utils/plugins/core-plugins.js +1 -1
- package/dist/src/utils/spawn-without-terminal.d.ts +27 -0
- package/dist/src/utils/spawn-without-terminal.js +110 -0
- package/dist/src/utils/string-similarity.d.ts +38 -0
- package/dist/src/utils/string-similarity.js +108 -0
- package/migrations.json +7 -0
- package/package.json +11 -20
- package/readme-template.md +3 -3
- package/schemas/nx-schema.json +70 -0
- package/schemas/project-schema.json +67 -0
- package/dist/src/tasks-runner/legacy-depends-on-warning.d.ts +0 -18
- package/dist/src/tasks-runner/legacy-depends-on-warning.js +0 -109
|
@@ -2,16 +2,23 @@ import { DefaultTasksRunnerOptions } from './default-tasks-runner';
|
|
|
2
2
|
import { Task, TaskGraph } from '../config/task-graph';
|
|
3
3
|
import { ProjectGraph } from '../config/project-graph';
|
|
4
4
|
import { ProjectConfiguration } from '../config/workspace-json-project-json';
|
|
5
|
+
import { TaskReadiness } from '../native';
|
|
5
6
|
export interface Batch {
|
|
6
7
|
id: string;
|
|
7
8
|
executorName: string;
|
|
8
9
|
taskGraph: TaskGraph;
|
|
9
10
|
}
|
|
11
|
+
export interface TasksScheduleHooks {
|
|
12
|
+
readinessElsewhere?: (producerId: string) => TaskReadiness | null;
|
|
13
|
+
onReadinessHold?: (producerId: string) => void;
|
|
14
|
+
}
|
|
10
15
|
export declare class TasksSchedule {
|
|
11
16
|
private readonly projectGraph;
|
|
12
17
|
private readonly projects;
|
|
13
18
|
private readonly taskGraph;
|
|
14
19
|
private readonly options;
|
|
20
|
+
private readonly fullTaskGraph;
|
|
21
|
+
private readonly hooks;
|
|
15
22
|
private notScheduledTaskGraph;
|
|
16
23
|
private reverseTaskDeps;
|
|
17
24
|
private reverseProjectGraph;
|
|
@@ -19,14 +26,21 @@ export declare class TasksSchedule {
|
|
|
19
26
|
private scheduledBatches;
|
|
20
27
|
private scheduledTasks;
|
|
21
28
|
private runningTasks;
|
|
29
|
+
private pendingStart;
|
|
22
30
|
private completedTasks;
|
|
31
|
+
private readiness;
|
|
32
|
+
private probedProducers;
|
|
23
33
|
private scheduleRequestsExecutionChain;
|
|
24
34
|
private estimatedTaskTimings;
|
|
25
35
|
private projectDependencies;
|
|
26
36
|
private batchCounters;
|
|
27
|
-
constructor(projectGraph: ProjectGraph, projects: Record<string, ProjectConfiguration>, taskGraph: TaskGraph, options: DefaultTasksRunnerOptions);
|
|
37
|
+
constructor(projectGraph: ProjectGraph, projects: Record<string, ProjectConfiguration>, taskGraph: TaskGraph, options: DefaultTasksRunnerOptions, fullTaskGraph?: TaskGraph, hooks?: TasksScheduleHooks);
|
|
28
38
|
init(): Promise<void>;
|
|
29
39
|
scheduleNextTasks(): Promise<void>;
|
|
40
|
+
markContinuousTaskStarted(taskId: string): void;
|
|
41
|
+
markReady(taskId: string): void;
|
|
42
|
+
markReadinessFailed(taskId: string): void;
|
|
43
|
+
markReadinessPending(taskId: string): void;
|
|
30
44
|
hasTasks(): boolean;
|
|
31
45
|
complete(taskIds: string[]): void;
|
|
32
46
|
getAllScheduledTasks(): {
|
|
@@ -42,7 +56,10 @@ export declare class TasksSchedule {
|
|
|
42
56
|
private scheduleBatches;
|
|
43
57
|
private scheduleBatch;
|
|
44
58
|
private processTaskForBatches;
|
|
59
|
+
private probedProducersOf;
|
|
60
|
+
private isProducerReady;
|
|
45
61
|
private canBatchTaskBeScheduled;
|
|
62
|
+
private areContinuousDependenciesReady;
|
|
46
63
|
private canBeScheduled;
|
|
47
64
|
getEstimatedTaskTimings(): Record<string, number>;
|
|
48
65
|
}
|
|
@@ -5,12 +5,15 @@ const utils_1 = require("./utils");
|
|
|
5
5
|
const project_graph_utils_1 = require("../utils/project-graph-utils");
|
|
6
6
|
const operators_1 = require("../project-graph/operators");
|
|
7
7
|
const task_history_1 = require("../utils/task-history");
|
|
8
|
+
const ready_when_1 = require("./readiness/ready-when");
|
|
8
9
|
class TasksSchedule {
|
|
9
|
-
constructor(projectGraph, projects, taskGraph, options) {
|
|
10
|
+
constructor(projectGraph, projects, taskGraph, options, fullTaskGraph = taskGraph, hooks = {}) {
|
|
10
11
|
this.projectGraph = projectGraph;
|
|
11
12
|
this.projects = projects;
|
|
12
13
|
this.taskGraph = taskGraph;
|
|
13
14
|
this.options = options;
|
|
15
|
+
this.fullTaskGraph = fullTaskGraph;
|
|
16
|
+
this.hooks = hooks;
|
|
14
17
|
this.notScheduledTaskGraph = this.taskGraph;
|
|
15
18
|
this.reverseTaskDeps = (0, utils_1.calculateReverseDeps)(this.taskGraph);
|
|
16
19
|
this.reverseProjectGraph = (0, operators_1.reverse)(this.projectGraph);
|
|
@@ -18,7 +21,13 @@ class TasksSchedule {
|
|
|
18
21
|
this.scheduledBatches = [];
|
|
19
22
|
this.scheduledTasks = [];
|
|
20
23
|
this.runningTasks = new Set();
|
|
24
|
+
// Continuous tasks with ready edges, queued but not spawned; their
|
|
25
|
+
// dependents stay unreleased
|
|
26
|
+
this.pendingStart = new Set();
|
|
21
27
|
this.completedTasks = new Set();
|
|
28
|
+
this.readiness = new Map();
|
|
29
|
+
// Read from the full graph: an agent worker's own graph has no edges
|
|
30
|
+
this.probedProducers = (0, ready_when_1.getReadyDependencies)(this.fullTaskGraph, this.projectGraph);
|
|
22
31
|
this.scheduleRequestsExecutionChain = Promise.resolve();
|
|
23
32
|
this.estimatedTaskTimings = {};
|
|
24
33
|
this.projectDependencies = {};
|
|
@@ -38,6 +47,18 @@ class TasksSchedule {
|
|
|
38
47
|
this.scheduleRequestsExecutionChain.then(() => this.scheduleTasks());
|
|
39
48
|
await this.scheduleRequestsExecutionChain;
|
|
40
49
|
}
|
|
50
|
+
markContinuousTaskStarted(taskId) {
|
|
51
|
+
this.pendingStart.delete(taskId);
|
|
52
|
+
}
|
|
53
|
+
markReady(taskId) {
|
|
54
|
+
this.readiness.set(taskId, 'ready');
|
|
55
|
+
}
|
|
56
|
+
markReadinessFailed(taskId) {
|
|
57
|
+
this.readiness.set(taskId, 'failed');
|
|
58
|
+
}
|
|
59
|
+
markReadinessPending(taskId) {
|
|
60
|
+
this.readiness.delete(taskId);
|
|
61
|
+
}
|
|
41
62
|
hasTasks() {
|
|
42
63
|
return (this.scheduledBatches.length +
|
|
43
64
|
this.scheduledTasks.length +
|
|
@@ -116,6 +137,10 @@ class TasksSchedule {
|
|
|
116
137
|
for (const taskId of taskIds) {
|
|
117
138
|
this.scheduledTasks.push(taskId);
|
|
118
139
|
this.runningTasks.add(taskId);
|
|
140
|
+
const task = this.taskGraph.tasks[taskId];
|
|
141
|
+
if (task.continuous && this.probedProducersOf(task).length > 0) {
|
|
142
|
+
this.pendingStart.add(taskId);
|
|
143
|
+
}
|
|
119
144
|
}
|
|
120
145
|
this.sortScheduledTasks();
|
|
121
146
|
}
|
|
@@ -182,6 +207,15 @@ class TasksSchedule {
|
|
|
182
207
|
if (!this.canBatchTaskBeScheduled(task, batches[rootExecutorName])) {
|
|
183
208
|
return;
|
|
184
209
|
}
|
|
210
|
+
// A batch never waits, so a task joins one once its probed producers are
|
|
211
|
+
// ready. A continuous task that something waits on, or that declares a
|
|
212
|
+
// probe, runs alone: only that path records its start and readiness
|
|
213
|
+
if ((task.continuous &&
|
|
214
|
+
(this.reverseTaskDeps[task.id].length > 0 ||
|
|
215
|
+
(0, ready_when_1.getReadyWhenConfig)(task, this.projectGraph) != null)) ||
|
|
216
|
+
this.probedProducersOf(task).some((id) => !this.isProducerReady(id))) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
185
219
|
const { batchImplementationFactory, preferBatch } = (0, utils_1.getExecutorForTask)(task, this.projects);
|
|
186
220
|
const executorName = (0, utils_1.getExecutorNameForTask)(task, this.projectGraph);
|
|
187
221
|
if (rootExecutorName !== executorName) {
|
|
@@ -222,17 +256,41 @@ class TasksSchedule {
|
|
|
222
256
|
await this.processTaskForBatches(batches, depTask, rootExecutorName, false, visitedInBatch);
|
|
223
257
|
}
|
|
224
258
|
}
|
|
259
|
+
probedProducersOf(task) {
|
|
260
|
+
return this.probedProducers[task.id] ?? [];
|
|
261
|
+
}
|
|
262
|
+
isProducerReady(producerId) {
|
|
263
|
+
if (this.taskGraph.tasks[producerId]) {
|
|
264
|
+
return this.readiness.get(producerId) === 'ready';
|
|
265
|
+
}
|
|
266
|
+
return this.hooks.readinessElsewhere?.(producerId) === 1 /* TaskReadiness.Ready */;
|
|
267
|
+
}
|
|
225
268
|
canBatchTaskBeScheduled(task, batchTaskGraph) {
|
|
226
269
|
// task self needs to support parallelism (undefined defaults to parallel)
|
|
227
|
-
// all deps have either completed or belong to the same batch
|
|
270
|
+
// all deps have either completed or belong to the same batch, and every
|
|
271
|
+
// continuous dep has started, and is ready where the edge asks for it
|
|
228
272
|
return (task.parallelism !== false &&
|
|
229
|
-
this.taskGraph.dependencies[task.id].every((id) => this.completedTasks.has(id) || !!batchTaskGraph?.tasks[id])
|
|
273
|
+
this.taskGraph.dependencies[task.id].every(({ id }) => this.completedTasks.has(id) || !!batchTaskGraph?.tasks[id]) &&
|
|
274
|
+
this.areContinuousDependenciesReady(task.id));
|
|
275
|
+
}
|
|
276
|
+
// A producer outside this task graph is polled by the task itself
|
|
277
|
+
areContinuousDependenciesReady(taskId) {
|
|
278
|
+
const started = this.taskGraph.continuousDependencies[taskId].every(({ id }) => this.runningTasks.has(id) && !this.pendingStart.has(id));
|
|
279
|
+
if (!started) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
const pendingProducer = this.probedProducersOf(this.taskGraph.tasks[taskId]).find((id) => this.taskGraph.tasks[id] && !this.readiness.has(id));
|
|
283
|
+
if (pendingProducer) {
|
|
284
|
+
this.hooks.onReadinessHold?.(pendingProducer);
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
return true;
|
|
230
288
|
}
|
|
231
289
|
canBeScheduled(taskId) {
|
|
232
|
-
const hasDependenciesCompleted = this.taskGraph.dependencies[taskId].every((id) => this.completedTasks.has(id));
|
|
233
|
-
const
|
|
290
|
+
const hasDependenciesCompleted = this.taskGraph.dependencies[taskId].every(({ id }) => this.completedTasks.has(id));
|
|
291
|
+
const continuousDependenciesReady = this.areContinuousDependenciesReady(taskId);
|
|
234
292
|
// if dependencies have not completed, cannot schedule
|
|
235
|
-
if (!hasDependenciesCompleted || !
|
|
293
|
+
if (!hasDependenciesCompleted || !continuousDependenciesReady) {
|
|
236
294
|
return false;
|
|
237
295
|
}
|
|
238
296
|
// if there are no running tasks, can schedule anything
|
|
@@ -2,7 +2,6 @@ import { CustomHasher, ExecutorConfig } from '../config/misc-interfaces';
|
|
|
2
2
|
import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph';
|
|
3
3
|
import { Task, TaskGraph } from '../config/task-graph';
|
|
4
4
|
import { ProjectConfiguration, TargetConfiguration, TargetDependencyConfig } from '../config/workspace-json-project-json';
|
|
5
|
-
import { LegacyDependsOnLocation } from './legacy-depends-on-warning';
|
|
6
5
|
export type NormalizedTargetDependencyConfig = TargetDependencyConfig & {
|
|
7
6
|
projects: string[];
|
|
8
7
|
};
|
|
@@ -10,9 +9,8 @@ export declare function getDependencyConfigs({ project, target }: {
|
|
|
10
9
|
project: string;
|
|
11
10
|
target: string;
|
|
12
11
|
}, extraTargetDependencies: Record<string, (TargetDependencyConfig | string)[]>, projectGraph: ProjectGraph, allTargetNames: string[]): NormalizedTargetDependencyConfig[] | undefined;
|
|
13
|
-
export
|
|
14
|
-
export declare function
|
|
15
|
-
export declare function normalizeDependencyConfigProjects(dependencyConfig: TargetDependencyConfig, currentProject: string, graph: ProjectGraph, location?: DependsOnEntryLocation): NormalizedTargetDependencyConfig;
|
|
12
|
+
export declare function normalizeDependencyConfigDefinition(definition: string | TargetDependencyConfig, currentProject: string, graph: ProjectGraph, allTargetNames: string[]): NormalizedTargetDependencyConfig[];
|
|
13
|
+
export declare function normalizeDependencyConfigProjects(dependencyConfig: TargetDependencyConfig, currentProject: string, graph: ProjectGraph): NormalizedTargetDependencyConfig;
|
|
16
14
|
export declare function expandDependencyConfigSyntaxSugar(dependencyConfigString: string | TargetDependencyConfig, graph: ProjectGraph, currentProject?: string): TargetDependencyConfig;
|
|
17
15
|
export declare function expandWildcardTargetConfiguration(dependencyConfig: NormalizedTargetDependencyConfig, allTargetNames: string[]): NormalizedTargetDependencyConfig[];
|
|
18
16
|
export declare function readProjectAndTargetFromTargetString(targetString: string, projects: Record<string, ProjectGraphProjectNode>, currentProject?: string): {
|
|
@@ -20,7 +18,7 @@ export declare function readProjectAndTargetFromTargetString(targetString: strin
|
|
|
20
18
|
target: string;
|
|
21
19
|
};
|
|
22
20
|
export declare function getOutputs(p: Record<string, ProjectGraphProjectNode>, target: Task['target'], overrides: Task['overrides']): string[];
|
|
23
|
-
export declare function normalizeTargetDependencyWithStringProjects(dependencyConfig: TargetDependencyConfig
|
|
21
|
+
export declare function normalizeTargetDependencyWithStringProjects(dependencyConfig: TargetDependencyConfig): Omit<TargetDependencyConfig, 'projects'> & {
|
|
24
22
|
projects?: string[];
|
|
25
23
|
};
|
|
26
24
|
export declare function validateOutputs(outputs: string[]): void;
|
|
@@ -49,6 +47,8 @@ export declare function getExecutorForTask(task: Task, projects: Record<string,
|
|
|
49
47
|
isNxExecutor: boolean;
|
|
50
48
|
};
|
|
51
49
|
export declare function getCustomHasher(task: Task, projects: Record<string, ProjectConfiguration>): CustomHasher | null;
|
|
50
|
+
/** Test-only: forget which executors have been warned about. */
|
|
51
|
+
export declare function resetCustomHasherDeprecationWarnings(): void;
|
|
52
52
|
/**
|
|
53
53
|
* `taskGraph` pruned to `required`, which must already hold its dependencies.
|
|
54
54
|
* Unknown ids are ignored: a sync generator can remove a task after selection.
|
|
@@ -64,3 +64,18 @@ export declare function shouldStreamOutput(task: Task, initiatingProject: string
|
|
|
64
64
|
export declare function isCacheableTask(task: Task): boolean;
|
|
65
65
|
export declare function unparse(options: Object): string[];
|
|
66
66
|
export declare function createTaskId(project: string, target: string, configuration: string | undefined): string;
|
|
67
|
+
/**
|
|
68
|
+
* Distinguishes two invocations of the same task for the recursive invocation
|
|
69
|
+
* detector. The task id alone would report `nx run app:build --flag` invoked
|
|
70
|
+
* from within `app:build` as a loop, so the detector pairs the id with this
|
|
71
|
+
* hash and only treats an exact repeat as an immediate loop.
|
|
72
|
+
*
|
|
73
|
+
* The hash cannot decide recursion on its own. Arguments that accumulate
|
|
74
|
+
* through forwarding (`app:build` -> `app:build hello` -> `app:build hello
|
|
75
|
+
* hello`) hash differently on every hop, so the tracker also bounds how many
|
|
76
|
+
* ancestors may be running one task id.
|
|
77
|
+
*
|
|
78
|
+
* `__overrides_unparsed__` is dropped, matching how the task hasher treats it:
|
|
79
|
+
* it restates the other keys as raw argv, down to flag order and spelling.
|
|
80
|
+
*/
|
|
81
|
+
export declare function hashTaskOverrides(task: Task): string;
|
|
@@ -17,6 +17,7 @@ exports.getExecutorNameForTask = getExecutorNameForTask;
|
|
|
17
17
|
exports.expandInitiatingTasksThroughNoop = expandInitiatingTasksThroughNoop;
|
|
18
18
|
exports.getExecutorForTask = getExecutorForTask;
|
|
19
19
|
exports.getCustomHasher = getCustomHasher;
|
|
20
|
+
exports.resetCustomHasherDeprecationWarnings = resetCustomHasherDeprecationWarnings;
|
|
20
21
|
exports.pruneToSelectedTasks = pruneToSelectedTasks;
|
|
21
22
|
exports.removeTasksFromTaskGraph = removeTasksFromTaskGraph;
|
|
22
23
|
exports.calculateReverseDeps = calculateReverseDeps;
|
|
@@ -28,37 +29,35 @@ exports.shouldStreamOutput = shouldStreamOutput;
|
|
|
28
29
|
exports.isCacheableTask = isCacheableTask;
|
|
29
30
|
exports.unparse = unparse;
|
|
30
31
|
exports.createTaskId = createTaskId;
|
|
32
|
+
exports.hashTaskOverrides = hashTaskOverrides;
|
|
31
33
|
const minimatch_1 = require("minimatch");
|
|
32
34
|
const node_path_1 = require("node:path");
|
|
33
35
|
const posix_1 = require("node:path/posix");
|
|
34
36
|
const executor_utils_1 = require("../command-line/run/executor-utils");
|
|
37
|
+
const file_hasher_1 = require("../hasher/file-hasher");
|
|
35
38
|
const native_1 = require("../native");
|
|
36
39
|
const fileutils_1 = require("../utils/fileutils");
|
|
37
40
|
const find_matching_projects_1 = require("../utils/find-matching-projects");
|
|
38
|
-
const legacy_depends_on_warning_1 = require("./legacy-depends-on-warning");
|
|
39
41
|
const globs_1 = require("../utils/globs");
|
|
40
42
|
const long_running_target_1 = require("../utils/long-running-target");
|
|
43
|
+
const output_1 = require("../utils/output");
|
|
41
44
|
const path_1 = require("../utils/path");
|
|
42
45
|
const serialize_overrides_into_command_line_1 = require("../utils/serialize-overrides-into-command-line");
|
|
43
46
|
const split_target_1 = require("../utils/split-target");
|
|
44
47
|
const workspace_root_1 = require("../utils/workspace-root");
|
|
45
48
|
const is_tui_enabled_1 = require("./is-tui-enabled");
|
|
46
49
|
function getDependencyConfigs({ project, target }, extraTargetDependencies, projectGraph, allTargetNames) {
|
|
47
|
-
|
|
48
|
-
const dependencyConfigs = (projectGraph.nodes[project].data?.targets[target]?.dependsOn ??
|
|
50
|
+
return (projectGraph.nodes[project].data?.targets[target]?.dependsOn ??
|
|
49
51
|
// This is passed into `run-command` from programmatic invocations
|
|
50
52
|
extraTargetDependencies[target] ??
|
|
51
|
-
[]).flatMap((config
|
|
52
|
-
if (legacyViolations.length) {
|
|
53
|
-
(0, legacy_depends_on_warning_1.flushLegacyDependsOnViolations)(project, target, legacyViolations, projectGraph.nodes[project]?.data?.root);
|
|
54
|
-
}
|
|
55
|
-
return dependencyConfigs;
|
|
53
|
+
[]).flatMap((config) => normalizeDependencyConfigDefinition(config, project, projectGraph, allTargetNames));
|
|
56
54
|
}
|
|
57
|
-
function normalizeDependencyConfigDefinition(definition, currentProject, graph, allTargetNames
|
|
58
|
-
return expandWildcardTargetConfiguration(normalizeDependencyConfigProjects(expandDependencyConfigSyntaxSugar(definition, graph, currentProject), currentProject, graph
|
|
55
|
+
function normalizeDependencyConfigDefinition(definition, currentProject, graph, allTargetNames) {
|
|
56
|
+
return expandWildcardTargetConfiguration(normalizeDependencyConfigProjects(expandDependencyConfigSyntaxSugar(definition, graph, currentProject), currentProject, graph), allTargetNames);
|
|
59
57
|
}
|
|
60
|
-
function normalizeDependencyConfigProjects(dependencyConfig, currentProject, graph
|
|
61
|
-
|
|
58
|
+
function normalizeDependencyConfigProjects(dependencyConfig, currentProject, graph) {
|
|
59
|
+
assertNotLegacyProjectsString(dependencyConfig, currentProject, graph);
|
|
60
|
+
const noStringConfig = normalizeTargetDependencyWithStringProjects(dependencyConfig);
|
|
62
61
|
if (noStringConfig.projects) {
|
|
63
62
|
dependencyConfig.projects = (0, find_matching_projects_1.findMatchingProjects)(noStringConfig.projects, graph.nodes);
|
|
64
63
|
}
|
|
@@ -67,6 +66,13 @@ function normalizeDependencyConfigProjects(dependencyConfig, currentProject, gra
|
|
|
67
66
|
}
|
|
68
67
|
return dependencyConfig;
|
|
69
68
|
}
|
|
69
|
+
function assertNotLegacyProjectsString(dependencyConfig, currentProject, graph) {
|
|
70
|
+
const projects = dependencyConfig.projects;
|
|
71
|
+
if ((projects === 'self' || projects === 'dependencies') &&
|
|
72
|
+
!graph.nodes[projects]) {
|
|
73
|
+
throw new Error(`${currentProject} has a dependsOn entry using projects: '${projects}', which is no longer supported: ${JSON.stringify(dependencyConfig)}\nPlease run "nx repair" to repair your configuration`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
70
76
|
function expandDependencyConfigSyntaxSugar(dependencyConfigString, graph, currentProject) {
|
|
71
77
|
if (typeof dependencyConfigString !== 'string') {
|
|
72
78
|
return dependencyConfigString;
|
|
@@ -133,23 +139,9 @@ function readProjectAndTargetFromTargetString(targetString, projects, currentPro
|
|
|
133
139
|
function getOutputs(p, target, overrides) {
|
|
134
140
|
return getOutputsForTargetAndConfiguration(target, overrides, p[target.project]);
|
|
135
141
|
}
|
|
136
|
-
function normalizeTargetDependencyWithStringProjects(dependencyConfig
|
|
142
|
+
function normalizeTargetDependencyWithStringProjects(dependencyConfig) {
|
|
137
143
|
if (typeof dependencyConfig.projects === 'string') {
|
|
138
|
-
|
|
139
|
-
// The v16 `update-depends-on-to-tokens` migration already rewrites
|
|
140
|
-
// these to the modern shape, and `nx repair` will re-run it on demand.
|
|
141
|
-
if (dependencyConfig.projects === 'self') {
|
|
142
|
-
(0, legacy_depends_on_warning_1.warnLegacyDependsOnMagicString)(currentProject, dependencyConfig, location);
|
|
143
|
-
delete dependencyConfig.projects;
|
|
144
|
-
}
|
|
145
|
-
else if (dependencyConfig.projects === 'dependencies') {
|
|
146
|
-
(0, legacy_depends_on_warning_1.warnLegacyDependsOnMagicString)(currentProject, dependencyConfig, location);
|
|
147
|
-
dependencyConfig.dependencies = true;
|
|
148
|
-
delete dependencyConfig.projects;
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
dependencyConfig.projects = [dependencyConfig.projects];
|
|
152
|
-
}
|
|
144
|
+
dependencyConfig.projects = [dependencyConfig.projects];
|
|
153
145
|
}
|
|
154
146
|
return dependencyConfig;
|
|
155
147
|
}
|
|
@@ -324,10 +316,10 @@ function expandInitiatingTasksThroughNoop(initiatingTasks, taskGraph, projectGra
|
|
|
324
316
|
continue;
|
|
325
317
|
if (getExecutorNameForTask(task, projectGraph) === 'nx:noop') {
|
|
326
318
|
for (const dep of taskGraph.dependencies[taskId] ?? []) {
|
|
327
|
-
queue.push(dep);
|
|
319
|
+
queue.push(dep.id);
|
|
328
320
|
}
|
|
329
321
|
for (const dep of taskGraph.continuousDependencies[taskId] ?? []) {
|
|
330
|
-
queue.push(dep);
|
|
322
|
+
queue.push(dep.id);
|
|
331
323
|
}
|
|
332
324
|
}
|
|
333
325
|
else {
|
|
@@ -341,9 +333,33 @@ function getExecutorForTask(task, projects) {
|
|
|
341
333
|
const [nodeModule, executorName] = (0, executor_utils_1.parseExecutor)(executor);
|
|
342
334
|
return (0, executor_utils_1.getExecutorInformation)(nodeModule, executorName, workspace_root_1.workspaceRoot, projects);
|
|
343
335
|
}
|
|
336
|
+
// TODO(v25): remove custom hasher support.
|
|
344
337
|
function getCustomHasher(task, projects) {
|
|
345
338
|
const factory = getExecutorForTask(task, projects).hasherFactory;
|
|
346
|
-
|
|
339
|
+
if (!factory) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
warnCustomHasherDeprecation(projects[task.target.project].targets[task.target.target].executor);
|
|
343
|
+
return factory();
|
|
344
|
+
}
|
|
345
|
+
const customHasherDeprecationWarned = new Set();
|
|
346
|
+
function warnCustomHasherDeprecation(executor) {
|
|
347
|
+
if (customHasherDeprecationWarned.has(executor)) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
customHasherDeprecationWarned.add(executor);
|
|
351
|
+
output_1.output.warn({
|
|
352
|
+
title: `The "${executor}" executor uses a custom hasher, which is deprecated and will be removed in Nx 25.`,
|
|
353
|
+
bodyLines: [
|
|
354
|
+
'Replace the custom hasher with target inputs, following the migration guide:',
|
|
355
|
+
'https://nx.dev/docs/kb/local-executors#replace-a-custom-hasher-with-inputs',
|
|
356
|
+
'If the executor comes from a third-party plugin, report this to its maintainers.',
|
|
357
|
+
],
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
/** Test-only: forget which executors have been warned about. */
|
|
361
|
+
function resetCustomHasherDeprecationWarnings() {
|
|
362
|
+
customHasherDeprecationWarned.clear();
|
|
347
363
|
}
|
|
348
364
|
/**
|
|
349
365
|
* `taskGraph` pruned to `required`, which must already hold its dependencies.
|
|
@@ -370,8 +386,8 @@ function removeIdsFromTaskGraph(graph, ids, mapWithIds) {
|
|
|
370
386
|
for (let id of Object.keys(mapWithIds)) {
|
|
371
387
|
if (!removedSet.has(id)) {
|
|
372
388
|
filteredMapWithIds[id] = mapWithIds[id];
|
|
373
|
-
dependencies[id] = graph.dependencies[id].filter((
|
|
374
|
-
continuousDependencies[id] = graph.continuousDependencies[id].filter((
|
|
389
|
+
dependencies[id] = graph.dependencies[id].filter((dep) => !removedSet.has(dep.id));
|
|
390
|
+
continuousDependencies[id] = graph.continuousDependencies[id].filter((dep) => !removedSet.has(dep.id));
|
|
375
391
|
}
|
|
376
392
|
}
|
|
377
393
|
return {
|
|
@@ -388,12 +404,12 @@ function calculateReverseDeps(taskGraph) {
|
|
|
388
404
|
});
|
|
389
405
|
Object.keys(taskGraph.dependencies).forEach((taskId) => {
|
|
390
406
|
taskGraph.dependencies[taskId].forEach((d) => {
|
|
391
|
-
reverseTaskDeps[d].push(taskId);
|
|
407
|
+
reverseTaskDeps[d.id].push(taskId);
|
|
392
408
|
});
|
|
393
409
|
});
|
|
394
410
|
Object.keys(taskGraph.continuousDependencies).forEach((taskId) => {
|
|
395
411
|
taskGraph.continuousDependencies[taskId].forEach((d) => {
|
|
396
|
-
reverseTaskDeps[d].push(taskId);
|
|
412
|
+
reverseTaskDeps[d.id].push(taskId);
|
|
397
413
|
});
|
|
398
414
|
});
|
|
399
415
|
return reverseTaskDeps;
|
|
@@ -457,3 +473,22 @@ function createTaskId(project, target, configuration) {
|
|
|
457
473
|
}
|
|
458
474
|
return id;
|
|
459
475
|
}
|
|
476
|
+
/**
|
|
477
|
+
* Distinguishes two invocations of the same task for the recursive invocation
|
|
478
|
+
* detector. The task id alone would report `nx run app:build --flag` invoked
|
|
479
|
+
* from within `app:build` as a loop, so the detector pairs the id with this
|
|
480
|
+
* hash and only treats an exact repeat as an immediate loop.
|
|
481
|
+
*
|
|
482
|
+
* The hash cannot decide recursion on its own. Arguments that accumulate
|
|
483
|
+
* through forwarding (`app:build` -> `app:build hello` -> `app:build hello
|
|
484
|
+
* hello`) hash differently on every hop, so the tracker also bounds how many
|
|
485
|
+
* ancestors may be running one task id.
|
|
486
|
+
*
|
|
487
|
+
* `__overrides_unparsed__` is dropped, matching how the task hasher treats it:
|
|
488
|
+
* it restates the other keys as raw argv, down to flag order and spelling.
|
|
489
|
+
*/
|
|
490
|
+
function hashTaskOverrides(task) {
|
|
491
|
+
const overrides = { ...task.overrides };
|
|
492
|
+
delete overrides['__overrides_unparsed__'];
|
|
493
|
+
return (0, file_hasher_1.hashObject)(overrides);
|
|
494
|
+
}
|
|
@@ -19,6 +19,7 @@ function customHasherTaskIds(projectGraph, taskGraph) {
|
|
|
19
19
|
return memoized;
|
|
20
20
|
}
|
|
21
21
|
const projects = (0, project_graph_1.readProjectsConfigurationFromProjectGraph)(projectGraph).projects;
|
|
22
|
+
// TODO(v25): remove custom hasher support.
|
|
22
23
|
const ids = Object.values(taskGraph.tasks)
|
|
23
24
|
.filter((task) => {
|
|
24
25
|
try {
|
|
@@ -48,6 +49,9 @@ function getUltracacheSettings(taskGraph) {
|
|
|
48
49
|
function ultracacheEligibilityOptions(projectGraph, taskGraph) {
|
|
49
50
|
return {
|
|
50
51
|
customHasherTaskIds: customHasherTaskIds(projectGraph, taskGraph),
|
|
52
|
+
continuousTaskIds: Object.values(taskGraph.tasks)
|
|
53
|
+
.filter((task) => task.continuous)
|
|
54
|
+
.map((task) => task.id),
|
|
51
55
|
};
|
|
52
56
|
}
|
|
53
57
|
/**
|
|
@@ -128,6 +128,7 @@ function runNxArgvSync(argv, options) {
|
|
|
128
128
|
if (result.status !== 0) {
|
|
129
129
|
const error = new Error(`Command failed: nx ${argv.join(' ')} (exit code ${result.status})`);
|
|
130
130
|
error.status = result.status ?? 1;
|
|
131
|
+
error.signal = result.signal;
|
|
131
132
|
throw error;
|
|
132
133
|
}
|
|
133
134
|
}
|
|
@@ -93,8 +93,13 @@ export declare function tryCommitChanges(commitMessage: string, directory: strin
|
|
|
93
93
|
/**
|
|
94
94
|
* `tryCommitChanges` for a process that has to stay responsive while git
|
|
95
95
|
* runs: a signing prompt or a hook can hold the commit for minutes.
|
|
96
|
+
* `withoutTerminal` is for a caller sharing the terminal with another program:
|
|
97
|
+
* git runs without it (see `spawnWithoutTerminal`), so such a prompt fails the
|
|
98
|
+
* commit instead.
|
|
96
99
|
*/
|
|
97
|
-
export declare function tryCommitChangesAsync(commitMessage: string, directory: string, excludePaths?: string[]
|
|
100
|
+
export declare function tryCommitChangesAsync(commitMessage: string, directory: string, excludePaths?: string[], { withoutTerminal }?: {
|
|
101
|
+
withoutTerminal?: boolean;
|
|
102
|
+
}): Promise<string | null>;
|
|
98
103
|
/**
|
|
99
104
|
* `git reset --hard <ref>` then `git clean -fd`, keeping `keepPaths` (git
|
|
100
105
|
* exclusion patterns, repo-relative) out of the clean. Throws with git's
|
|
@@ -30,6 +30,7 @@ const crypto = tslib_1.__importStar(require("crypto"));
|
|
|
30
30
|
const fs = tslib_1.__importStar(require("fs"));
|
|
31
31
|
const path_1 = require("path");
|
|
32
32
|
const owned_private_dir_1 = require("./owned-private-dir");
|
|
33
|
+
const spawn_without_terminal_1 = require("./spawn-without-terminal");
|
|
33
34
|
function execFileAsync(file, args, execOptions) {
|
|
34
35
|
return new Promise((res, rej) => {
|
|
35
36
|
(0, child_process_1.execFile)(file, args, { ...execOptions, windowsHide: true }, (err, stdout) => {
|
|
@@ -870,11 +871,14 @@ function tryCommitChanges(commitMessage, directory, excludePaths = []) {
|
|
|
870
871
|
/**
|
|
871
872
|
* `tryCommitChanges` for a process that has to stay responsive while git
|
|
872
873
|
* runs: a signing prompt or a hook can hold the commit for minutes.
|
|
874
|
+
* `withoutTerminal` is for a caller sharing the terminal with another program:
|
|
875
|
+
* git runs without it (see `spawnWithoutTerminal`), so such a prompt fails the
|
|
876
|
+
* commit instead.
|
|
873
877
|
*/
|
|
874
|
-
async function tryCommitChangesAsync(commitMessage, directory, excludePaths = []) {
|
|
878
|
+
async function tryCommitChangesAsync(commitMessage, directory, excludePaths = [], { withoutTerminal = false } = {}) {
|
|
875
879
|
try {
|
|
876
880
|
for (const { command, input } of commitCommands(commitMessage, excludePaths)) {
|
|
877
|
-
await execAsync(command, directory, input);
|
|
881
|
+
await execAsync(command, directory, input, withoutTerminal);
|
|
878
882
|
}
|
|
879
883
|
}
|
|
880
884
|
catch (err) {
|
|
@@ -908,12 +912,14 @@ function commitFailure(err) {
|
|
|
908
912
|
// for callers to inspect; otherwise only stderr/stdout text survives.
|
|
909
913
|
return new Error(detail || (err instanceof Error ? err.message : String(err)), { cause: err });
|
|
910
914
|
}
|
|
911
|
-
function execAsync(command, cwd, input) {
|
|
915
|
+
function execAsync(command, cwd, input, withoutTerminal) {
|
|
912
916
|
return new Promise((res, rej) => {
|
|
913
|
-
const child = (0, child_process_1.exec)(command, { encoding: 'utf8', cwd, windowsHide: true },
|
|
914
917
|
// The streams ride on the error as `execSync` reports them, so
|
|
915
918
|
// `commitFailure` shapes both the same way.
|
|
916
|
-
(err, stdout, stderr) => err ? rej(Object.assign(err, { stdout, stderr })) : res()
|
|
919
|
+
const settle = (err, stdout, stderr) => err ? rej(Object.assign(err, { stdout, stderr })) : res();
|
|
920
|
+
const child = withoutTerminal
|
|
921
|
+
? collectOutput((0, spawn_without_terminal_1.spawnWithoutTerminal)(command, { cwd }), command, settle)
|
|
922
|
+
: (0, child_process_1.exec)(command, { encoding: 'utf8', cwd, windowsHide: true }, settle);
|
|
917
923
|
// Closed for every command, since `git commit -F -` reads to end of
|
|
918
924
|
// input. A child that exits before reading raises EPIPE here, which the
|
|
919
925
|
// exit callback already reports.
|
|
@@ -921,6 +927,21 @@ function execAsync(command, cwd, input) {
|
|
|
921
927
|
child.stdin?.end(input ?? '');
|
|
922
928
|
});
|
|
923
929
|
}
|
|
930
|
+
// What `exec` reports through its callback; `exec` itself ignores `detached`.
|
|
931
|
+
function collectOutput(child, command, done) {
|
|
932
|
+
let stdout = '';
|
|
933
|
+
let stderr = '';
|
|
934
|
+
child.stdout?.setEncoding('utf8').on('data', (chunk) => (stdout += chunk));
|
|
935
|
+
child.stderr?.setEncoding('utf8').on('data', (chunk) => (stderr += chunk));
|
|
936
|
+
child.once('error', (err) => done(err, stdout, stderr));
|
|
937
|
+
child.once('close', (code, signal) => done(code === 0
|
|
938
|
+
? null
|
|
939
|
+
: Object.assign(new Error(`Command failed: ${command}`), {
|
|
940
|
+
code,
|
|
941
|
+
signal,
|
|
942
|
+
}), stdout, stderr));
|
|
943
|
+
return child;
|
|
944
|
+
}
|
|
924
945
|
/**
|
|
925
946
|
* `git reset --hard <ref>` then `git clean -fd`, keeping `keepPaths` (git
|
|
926
947
|
* exclusion patterns, repo-relative) out of the clean. Throws with git's
|
|
@@ -25,7 +25,7 @@ export type OwnedRealDir = string & {
|
|
|
25
25
|
export type OwnedPrivateDir = string & {
|
|
26
26
|
readonly [ownedPrivateDirBrand]: true;
|
|
27
27
|
};
|
|
28
|
-
/** Why a guard refused a directory. Data, not a sentence
|
|
28
|
+
/** Why a guard refused a directory. Data, not a sentence. */
|
|
29
29
|
export type DirRefusal = {
|
|
30
30
|
kind: 'not-created';
|
|
31
31
|
dir: string;
|
|
@@ -65,8 +65,6 @@ export type GuardResult<T> = {
|
|
|
65
65
|
};
|
|
66
66
|
/** The user-facing sentence for a refusal. The only place wording is decided. */
|
|
67
67
|
export declare function describeRefusal(r: DirRefusal): string;
|
|
68
|
-
/** What the user can do about a refusal, or `undefined` when there is nothing. */
|
|
69
|
-
export declare function remedyFor(r: DirRefusal): string | undefined;
|
|
70
68
|
/** One refusal as an `Error`, so several can travel in an `AggregateError`. */
|
|
71
69
|
export declare class DirectoryRefusedError extends Error {
|
|
72
70
|
readonly refusal: DirRefusal;
|
|
@@ -98,20 +96,6 @@ export declare function canonicalDir(dir: string): string;
|
|
|
98
96
|
* verified private directory beneath it.
|
|
99
97
|
*/
|
|
100
98
|
export declare function isSafeSharedRoot(dir: string): GuardResult<SafeSharedRoot>;
|
|
101
|
-
/**
|
|
102
|
-
* Whether other users on this machine can write into `dir`. Gates whether a
|
|
103
|
-
* refusal message may cite other users, so it must not over-report: `false` on
|
|
104
|
-
* Windows, where libuv synthesizes `st_mode` from the READONLY attribute and
|
|
105
|
-
* reports an ordinary per-account directory as `0666`, and `false` on a path
|
|
106
|
-
* that cannot be inspected.
|
|
107
|
-
*
|
|
108
|
-
* `statSync`, not `lstatSync`: the question is about the directory that will be
|
|
109
|
-
* used, not the link pointing at it. Link modes mislead in both directions —
|
|
110
|
-
* Linux creates symlinks `0777`, macOS applies the umask. Nothing here decides
|
|
111
|
-
* whether a path is accepted, so following the link costs nothing the guards
|
|
112
|
-
* do not already re-check.
|
|
113
|
-
*/
|
|
114
|
-
export declare function isPeerWritable(dir: string): boolean;
|
|
115
99
|
/**
|
|
116
100
|
* Create a shared container as sticky + world-writable if it does not exist,
|
|
117
101
|
* and report whether the resulting path is safe for the current user.
|