@visulima/task-runner 1.0.0-alpha.2 → 1.0.0-alpha.20
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/CHANGELOG.md +310 -0
- package/README.md +198 -52
- package/dist/index.d.ts +3795 -34
- package/dist/index.js +1 -20
- package/dist/packem_chunks/index.js +31 -0
- package/dist/packem_shared/Cache-C8FfeXpg.js +2 -0
- package/dist/packem_shared/CompositeLifeCycle-C6aee9GK.js +1 -0
- package/dist/packem_shared/FileAccessTracker-DBz_w4wl.js +50 -0
- package/dist/packem_shared/FingerprintManager-CYW2EwLc.js +2 -0
- package/dist/packem_shared/HttpRemoteCache-CpPl6lzE.js +1 -0
- package/dist/packem_shared/INPUT_URI_SCHEMES-Csrd0tlg.js +1 -0
- package/dist/packem_shared/IncrementalFileHasher-B-V3i2x-.js +1 -0
- package/dist/packem_shared/LogReporter-3R3oWj-Q.js +13 -0
- package/dist/packem_shared/ReapiRemoteCache-BXJip5wH.js +251 -0
- package/dist/packem_shared/TaskOrchestrator-CYj5MLwz.js +6 -0
- package/dist/packem_shared/TerminalBuffer-BtZy7TpT.js +3 -0
- package/dist/packem_shared/TrackedTaskExecutor-CtYLL3vS.js +1 -0
- package/dist/packem_shared/V2_ROOT-injxWBrl.js +1 -0
- package/dist/packem_shared/actionDigestForTaskHash-BOL4fZ9v.js +1 -0
- package/dist/packem_shared/archive-CDfGy5Lm.js +1 -0
- package/dist/packem_shared/buildForwardDependencyMap-DudUDFze.js +3 -0
- package/dist/packem_shared/collectFiles-W4bnBRpb.js +1 -0
- package/dist/packem_shared/collectNodeModulesBinDirs-CD-eDrtO.js +1 -0
- package/dist/packem_shared/computeTaskHash-CaPdG1BA.js +1 -0
- package/dist/packem_shared/containsBlob-DAU8R7GH.js +1 -0
- package/dist/packem_shared/createInputHandler-CkDCpPYy.js +1 -0
- package/dist/packem_shared/createTaskGraph-D8Jn_Dn9.js +1 -0
- package/dist/packem_shared/defaultTaskRunner-DMHpavzm.js +2 -0
- package/dist/packem_shared/detectFrameworks-WVZJOPgN.js +1 -0
- package/dist/packem_shared/detectScriptShell-CaTDk5cW.js +1 -0
- package/dist/packem_shared/digestBuffer-g11aCaDx.js +1 -0
- package/dist/packem_shared/enforceProjectConstraints-dNc1SwRi.js +1 -0
- package/dist/packem_shared/expandArguments-D7qvc6Rp.js +1 -0
- package/dist/packem_shared/expandShortcut-BErNHNXZ.js +1 -0
- package/dist/packem_shared/expandTokensInString-DVSFEdWu.js +1 -0
- package/dist/packem_shared/expandWildcard-DE0dOOZF.js +1 -0
- package/dist/packem_shared/extractPackageName-BeL6Gc3a.js +1 -0
- package/dist/packem_shared/findCycle-BY8-jmzB.js +1 -0
- package/dist/packem_shared/formatTimingTable-CP3rsDwf.js +7 -0
- package/dist/packem_shared/generateRunSummary-L9Z2NfWn.js +1 -0
- package/dist/packem_shared/getCurrentBranch-D-qoZByx.js +1 -0
- package/dist/packem_shared/getMainWorktreeRoot-DB9P2wDL.js +1 -0
- package/dist/packem_shared/isNativeAvailable-CkTjxb7P.js +1 -0
- package/dist/packem_shared/parseCommands-BHsXoUCd.js +1 -0
- package/dist/packem_shared/parsePartition-Bt1jBjZH.js +1 -0
- package/dist/packem_shared/projectGraphToDot-FN6oHDGH.js +250 -0
- package/dist/packem_shared/resolveCacheMode--4y60ODd.js +1 -0
- package/dist/packem_shared/resolveOutputs-CzGGEbcP.js +1 -0
- package/dist/packem_shared/runConcurrentFallback-BhJCT2LA.js +3 -0
- package/dist/packem_shared/runConcurrently-D1Ytsjaj.js +1 -0
- package/dist/packem_shared/runTeardown-DAn1xFWJ.js +1 -0
- package/dist/packem_shared/shell-quote-BhmqDUL1.js +1 -0
- package/dist/packem_shared/stripQuotes-jkZb0CL9.js +1 -0
- package/dist/packem_shared/toChromeTrace-DxN5NQIU.js +1 -0
- package/dist/packem_shared/tracked-executor-B90U4Um3.js +3 -0
- package/dist/packem_shared/utils-BH2W5Wml.js +1 -0
- package/dist/packem_shared/withRestart-DKtEGsQA.js +1 -0
- package/index.js +603 -0
- package/package.json +31 -19
- package/binding.js +0 -204
- package/dist/affected.d.ts +0 -48
- package/dist/cache.d.ts +0 -103
- package/dist/default-task-runner.d.ts +0 -44
- package/dist/file-access-tracker.d.ts +0 -53
- package/dist/fingerprint.d.ts +0 -45
- package/dist/framework-inference.d.ts +0 -35
- package/dist/graph-visualizer.d.ts +0 -74
- package/dist/incremental-hasher.d.ts +0 -58
- package/dist/life-cycle.d.ts +0 -36
- package/dist/lockfile-hasher.d.ts +0 -73
- package/dist/native-binding.d.ts +0 -64
- package/dist/packem_shared/Cache-IYpTYVUC.js +0 -298
- package/dist/packem_shared/CompositeLifeCycle-7AtYw1dv.js +0 -112
- package/dist/packem_shared/FileAccessTracker-CrtBAt5D.js +0 -239
- package/dist/packem_shared/FingerprintManager-D6Y0erg-.js +0 -227
- package/dist/packem_shared/IncrementalFileHasher-Ds3J6dgb.js +0 -151
- package/dist/packem_shared/RemoteCache-BDqrnDEi.js +0 -179
- package/dist/packem_shared/TaskOrchestrator-BvYs3ONw.js +0 -342
- package/dist/packem_shared/TaskScheduler-CJilHDta.js +0 -111
- package/dist/packem_shared/TrackedTaskExecutor-BGUKFE-7.js +0 -164
- package/dist/packem_shared/collectFiles-ClXHnHhg.js +0 -22
- package/dist/packem_shared/computeTaskHash-BoCnnvIJ.js +0 -356
- package/dist/packem_shared/createTaskGraph-CcsFaSrz.js +0 -164
- package/dist/packem_shared/defaultTaskRunner-CrW4v5Ye.js +0 -79
- package/dist/packem_shared/detectFrameworks-CeFzKE6J.js +0 -101
- package/dist/packem_shared/extractPackageName-CbVNW-dr.js +0 -189
- package/dist/packem_shared/filterAffectedTasks-I-18zPg6.js +0 -135
- package/dist/packem_shared/findCycle-DF4_BRdO.js +0 -212
- package/dist/packem_shared/generateRunSummary-qn-_jKwt.js +0 -134
- package/dist/packem_shared/isNativeAvailable-BWhnZ4ES.js +0 -19
- package/dist/packem_shared/projectGraphToDot-VdTjHcVp.js +0 -202
- package/dist/packem_shared/utils-zO0ZRgtf.js +0 -390
- package/dist/remote-cache.d.ts +0 -55
- package/dist/run-summary.d.ts +0 -89
- package/dist/task-graph-utils.d.ts +0 -39
- package/dist/task-graph.d.ts +0 -22
- package/dist/task-hasher.d.ts +0 -67
- package/dist/task-orchestrator.d.ts +0 -38
- package/dist/task-scheduler.d.ts +0 -18
- package/dist/tracked-executor.d.ts +0 -46
- package/dist/types.d.ts +0 -385
- package/dist/utils.d.ts +0 -39
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { createRequire as __cjs_createRequire } from "node:module";
|
|
2
|
-
|
|
3
|
-
const __cjs_require = __cjs_createRequire(import.meta.url);
|
|
4
|
-
|
|
5
|
-
const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
|
|
6
|
-
|
|
7
|
-
const __cjs_getBuiltinModule = (module) => {
|
|
8
|
-
// Check if we're in Node.js and version supports getBuiltinModule
|
|
9
|
-
if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
|
|
10
|
-
const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
|
|
11
|
-
// Node.js 20.16.0+ and 22.3.0+
|
|
12
|
-
if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
|
|
13
|
-
return __cjs_getProcess.getBuiltinModule(module);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
// Fallback to createRequire
|
|
17
|
-
return __cjs_require(module);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const {
|
|
21
|
-
exec
|
|
22
|
-
} = __cjs_getBuiltinModule("node:child_process");
|
|
23
|
-
const {
|
|
24
|
-
mkdir,
|
|
25
|
-
writeFile,
|
|
26
|
-
readFile,
|
|
27
|
-
rm
|
|
28
|
-
} = __cjs_getBuiltinModule("node:fs/promises");
|
|
29
|
-
import { join } from '@visulima/path';
|
|
30
|
-
import { FileAccessTracker, generatePreloadScript } from './FileAccessTracker-CrtBAt5D.js';
|
|
31
|
-
import { u as uniqueId } from './utils-zO0ZRgtf.js';
|
|
32
|
-
|
|
33
|
-
class TrackedTaskExecutor {
|
|
34
|
-
#tracker;
|
|
35
|
-
#workspaceRoot;
|
|
36
|
-
/** Tracks active child processes for cleanup on abort */
|
|
37
|
-
#activeProcesses = /* @__PURE__ */ new Set();
|
|
38
|
-
constructor(workspaceRoot) {
|
|
39
|
-
this.#workspaceRoot = workspaceRoot;
|
|
40
|
-
this.#tracker = new FileAccessTracker(workspaceRoot);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Returns true if file access tracking is supported on the current platform.
|
|
44
|
-
* strace tracking (Linux) or preload script (any Node.js process).
|
|
45
|
-
*/
|
|
46
|
-
// eslint-disable-next-line class-methods-use-this
|
|
47
|
-
get isTrackingSupported() {
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Returns true if the platform supports full syscall-level tracking (strace).
|
|
52
|
-
*/
|
|
53
|
-
get isStraceSupported() {
|
|
54
|
-
return this.#tracker.isSupported();
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Executes a task command and tracks all file system accesses.
|
|
58
|
-
*
|
|
59
|
-
* On Linux, uses strace for comprehensive tracking.
|
|
60
|
-
* On other platforms, uses a Node.js preload script (for Node processes).
|
|
61
|
-
*/
|
|
62
|
-
async execute(task, options, command) {
|
|
63
|
-
const cwd = options.cwd ?? (task.projectRoot ? join(this.#workspaceRoot, task.projectRoot) : this.#workspaceRoot);
|
|
64
|
-
if (this.#tracker.isSupported()) {
|
|
65
|
-
const trackingResult = await this.#tracker.track(command, {
|
|
66
|
-
cwd,
|
|
67
|
-
env: options.env
|
|
68
|
-
});
|
|
69
|
-
return {
|
|
70
|
-
accesses: trackingResult.accesses,
|
|
71
|
-
code: trackingResult.code,
|
|
72
|
-
terminalOutput: trackingResult.output
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
return this.#executeWithPreload(command, cwd, options.env);
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Executes a command with a Node.js preload script to track fs accesses.
|
|
79
|
-
* Works on macOS, Windows, and Linux for Node.js-based commands.
|
|
80
|
-
*/
|
|
81
|
-
async #executeWithPreload(command, cwd, env) {
|
|
82
|
-
const cacheDirectory = join(this.#workspaceRoot, "node_modules", ".cache", "task-runner");
|
|
83
|
-
await mkdir(cacheDirectory, { recursive: true });
|
|
84
|
-
const id = uniqueId();
|
|
85
|
-
const logFile = join(cacheDirectory, `preload-${id}.log`);
|
|
86
|
-
const preloadFile = join(cacheDirectory, `preload-${id}.mjs`);
|
|
87
|
-
const scriptContent = generatePreloadScript(logFile);
|
|
88
|
-
await writeFile(preloadFile, scriptContent);
|
|
89
|
-
return new Promise((resolve) => {
|
|
90
|
-
const child = exec(
|
|
91
|
-
command,
|
|
92
|
-
{
|
|
93
|
-
cwd,
|
|
94
|
-
env: {
|
|
95
|
-
...process.env,
|
|
96
|
-
...env,
|
|
97
|
-
NODE_OPTIONS: `${process.env["NODE_OPTIONS"] ?? ""} --import ${preloadFile}`.trim()
|
|
98
|
-
},
|
|
99
|
-
maxBuffer: 50 * 1024 * 1024
|
|
100
|
-
},
|
|
101
|
-
async (_error, stdout, stderr) => {
|
|
102
|
-
this.#activeProcesses.delete(child);
|
|
103
|
-
let accesses = [];
|
|
104
|
-
try {
|
|
105
|
-
const logContent = await readFile(logFile, "utf8");
|
|
106
|
-
accesses = this.#parsePreloadLog(logContent);
|
|
107
|
-
} catch {
|
|
108
|
-
}
|
|
109
|
-
await rm(logFile, { force: true }).catch(() => {
|
|
110
|
-
});
|
|
111
|
-
await rm(preloadFile, { force: true }).catch(() => {
|
|
112
|
-
});
|
|
113
|
-
resolve({
|
|
114
|
-
accesses,
|
|
115
|
-
code: child.exitCode ?? 1,
|
|
116
|
-
terminalOutput: stdout + stderr
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
);
|
|
120
|
-
this.#activeProcesses.add(child);
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Kills all active child processes. Called on abort/signal to prevent orphans.
|
|
125
|
-
*/
|
|
126
|
-
killAll() {
|
|
127
|
-
this.#tracker.killAll();
|
|
128
|
-
for (const child of this.#activeProcesses) {
|
|
129
|
-
try {
|
|
130
|
-
child.kill("SIGTERM");
|
|
131
|
-
} catch {
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
this.#activeProcesses.clear();
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Parses the JSON-lines log produced by the preload script.
|
|
138
|
-
*/
|
|
139
|
-
#parsePreloadLog(content) {
|
|
140
|
-
const accesses = [];
|
|
141
|
-
const seen = /* @__PURE__ */ new Set();
|
|
142
|
-
for (const line of content.split("\n")) {
|
|
143
|
-
if (!line.trim()) {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
try {
|
|
147
|
-
const entry = JSON.parse(line);
|
|
148
|
-
if (entry.path && !seen.has(entry.path)) {
|
|
149
|
-
seen.add(entry.path);
|
|
150
|
-
if (entry.path.startsWith(this.#workspaceRoot)) {
|
|
151
|
-
accesses.push({
|
|
152
|
-
path: entry.path,
|
|
153
|
-
type: entry.type
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
} catch {
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return accesses;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export { TrackedTaskExecutor };
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { createRequire as __cjs_createRequire } from "node:module";
|
|
2
|
-
|
|
3
|
-
const __cjs_require = __cjs_createRequire(import.meta.url);
|
|
4
|
-
|
|
5
|
-
const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
|
|
6
|
-
|
|
7
|
-
const __cjs_getBuiltinModule = (module) => {
|
|
8
|
-
// Check if we're in Node.js and version supports getBuiltinModule
|
|
9
|
-
if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
|
|
10
|
-
const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
|
|
11
|
-
// Node.js 20.16.0+ and 22.3.0+
|
|
12
|
-
if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
|
|
13
|
-
return __cjs_getProcess.getBuiltinModule(module);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
// Fallback to createRequire
|
|
17
|
-
return __cjs_require(module);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export { c as collectFiles, a as createFailureResult, h as hashFile, b as hashStrings, r as readPackageDeps, d as resolveTaskCwd, s as sortObjectKeys, u as uniqueId } from './utils-zO0ZRgtf.js';
|
|
22
|
-
import '@visulima/path';
|
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
import { createRequire as __cjs_createRequire } from "node:module";
|
|
2
|
-
|
|
3
|
-
const __cjs_require = __cjs_createRequire(import.meta.url);
|
|
4
|
-
|
|
5
|
-
const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
|
|
6
|
-
|
|
7
|
-
const __cjs_getBuiltinModule = (module) => {
|
|
8
|
-
// Check if we're in Node.js and version supports getBuiltinModule
|
|
9
|
-
if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
|
|
10
|
-
const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
|
|
11
|
-
// Node.js 20.16.0+ and 22.3.0+
|
|
12
|
-
if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
|
|
13
|
-
return __cjs_getProcess.getBuiltinModule(module);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
// Fallback to createRequire
|
|
17
|
-
return __cjs_require(module);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const {
|
|
21
|
-
execFile
|
|
22
|
-
} = __cjs_getBuiltinModule("node:child_process");
|
|
23
|
-
const {
|
|
24
|
-
readFile
|
|
25
|
-
} = __cjs_getBuiltinModule("node:fs/promises");
|
|
26
|
-
import { b as hashStrings, s as sortObjectKeys, c as collectFiles, x as xxh3Hash, e as createXxh3Hasher } from './utils-zO0ZRgtf.js';
|
|
27
|
-
import { join, resolve, relative } from '@visulima/path';
|
|
28
|
-
import { getFrameworkEnvVariables } from './detectFrameworks-CeFzKE6J.js';
|
|
29
|
-
import { LockfileHasher } from './extractPackageName-CbVNW-dr.js';
|
|
30
|
-
import { loadNativeBindings } from './isNativeAvailable-BWhnZ4ES.js';
|
|
31
|
-
|
|
32
|
-
const DEFAULT_GLOBAL_INPUTS = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "tsconfig.base.json", "tsconfig.json", ".env"];
|
|
33
|
-
const IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "coverage", "dist", "node_modules"]);
|
|
34
|
-
const LOCKFILE_NAMES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]);
|
|
35
|
-
let cachedNative;
|
|
36
|
-
const getNativeBindings = () => {
|
|
37
|
-
if (cachedNative === void 0) {
|
|
38
|
-
cachedNative = loadNativeBindings();
|
|
39
|
-
}
|
|
40
|
-
return cachedNative;
|
|
41
|
-
};
|
|
42
|
-
const hashSortedEntries = (hash, record, prefix) => {
|
|
43
|
-
for (const key of Object.keys(record).toSorted()) {
|
|
44
|
-
{
|
|
45
|
-
hash.update(`${key}\0`);
|
|
46
|
-
}
|
|
47
|
-
hash.update(record[key]);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const executeRuntimeCommand = (runtime) => {
|
|
51
|
-
if (runtime === "node -v" || runtime === "node --version") {
|
|
52
|
-
return Promise.resolve(process.version);
|
|
53
|
-
}
|
|
54
|
-
const parts = runtime.split(/\s+/);
|
|
55
|
-
const command = parts[0];
|
|
56
|
-
const args = parts.slice(1);
|
|
57
|
-
return new Promise((resolve2) => {
|
|
58
|
-
execFile(command, args, { timeout: 1e4 }, (error, stdout) => {
|
|
59
|
-
if (error) {
|
|
60
|
-
resolve2(`__runtime_error__:${runtime}`);
|
|
61
|
-
} else {
|
|
62
|
-
resolve2(stdout.trim());
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
};
|
|
67
|
-
const runtimeCache = /* @__PURE__ */ new Map();
|
|
68
|
-
const hashRuntimeValue = async (runtime) => {
|
|
69
|
-
let output = runtimeCache.get(runtime);
|
|
70
|
-
if (output === void 0) {
|
|
71
|
-
output = await executeRuntimeCommand(runtime);
|
|
72
|
-
runtimeCache.set(runtime, output);
|
|
73
|
-
}
|
|
74
|
-
return hashStrings(runtime, output);
|
|
75
|
-
};
|
|
76
|
-
const isFileSetInput = (input) => "fileset" in input;
|
|
77
|
-
const isRuntimeInput = (input) => "runtime" in input;
|
|
78
|
-
const isEnvironmentInput = (input) => "env" in input;
|
|
79
|
-
const isExternalDependencyInput = (input) => "externalDependencies" in input;
|
|
80
|
-
class InProcessTaskHasher {
|
|
81
|
-
#workspaceRoot;
|
|
82
|
-
#projects;
|
|
83
|
-
#namedInputs;
|
|
84
|
-
#targetDefaults;
|
|
85
|
-
#envVars;
|
|
86
|
-
#globalInputs;
|
|
87
|
-
#globalEnv;
|
|
88
|
-
#fileHashCache = /* @__PURE__ */ new Map();
|
|
89
|
-
#native;
|
|
90
|
-
#smartLockfileHashing;
|
|
91
|
-
#lockfileHasher;
|
|
92
|
-
#frameworkInference;
|
|
93
|
-
#globalHash = void 0;
|
|
94
|
-
constructor(options) {
|
|
95
|
-
this.#workspaceRoot = options.workspaceRoot;
|
|
96
|
-
this.#projects = options.projects;
|
|
97
|
-
this.#namedInputs = options.namedInputs ?? {};
|
|
98
|
-
this.#targetDefaults = options.targetDefaults ?? {};
|
|
99
|
-
this.#envVars = options.envVars ?? [];
|
|
100
|
-
this.#globalInputs = options.globalInputs ?? DEFAULT_GLOBAL_INPUTS;
|
|
101
|
-
this.#globalEnv = options.globalEnv ?? [];
|
|
102
|
-
this.#native = getNativeBindings();
|
|
103
|
-
this.#smartLockfileHashing = options.smartLockfileHashing ?? false;
|
|
104
|
-
this.#lockfileHasher = this.#smartLockfileHashing ? new LockfileHasher(options.workspaceRoot) : void 0;
|
|
105
|
-
this.#frameworkInference = options.frameworkInference ?? false;
|
|
106
|
-
}
|
|
107
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
108
|
-
async hashTask(task) {
|
|
109
|
-
const commandHash = this.#hashCommand(task);
|
|
110
|
-
const nodes = {};
|
|
111
|
-
const implicitDeps = {};
|
|
112
|
-
const runtime = {};
|
|
113
|
-
const globalHash = await this.#computeGlobalHash();
|
|
114
|
-
if (globalHash) {
|
|
115
|
-
implicitDeps["__global__"] = globalHash;
|
|
116
|
-
}
|
|
117
|
-
const inputs = this.#resolveInputs(task);
|
|
118
|
-
const negationPatterns = this.#collectNegationPatterns(inputs, task.target.project);
|
|
119
|
-
for (const input of inputs) {
|
|
120
|
-
if (isFileSetInput(input)) {
|
|
121
|
-
const fileHashes = await this.#hashFileSet(task, input.fileset, negationPatterns);
|
|
122
|
-
for (const [filePath, hash] of Object.entries(fileHashes)) {
|
|
123
|
-
nodes[filePath] = hash;
|
|
124
|
-
}
|
|
125
|
-
} else if (isRuntimeInput(input)) {
|
|
126
|
-
runtime[input.runtime] = await hashRuntimeValue(input.runtime);
|
|
127
|
-
} else if (isEnvironmentInput(input)) {
|
|
128
|
-
runtime[`env:${input.env}`] = hashStrings(input.env, process.env[input.env] ?? "");
|
|
129
|
-
} else if (isExternalDependencyInput(input)) {
|
|
130
|
-
const depHashes = await Promise.all(input.externalDependencies.map(async (dep) => [dep, await this.#hashExternalDependency(dep)]));
|
|
131
|
-
for (const [dep, hash] of depHashes) {
|
|
132
|
-
implicitDeps[dep] = hash;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
for (const envVariable of this.#envVars) {
|
|
137
|
-
runtime[`env:${envVariable}`] = hashStrings(envVariable, process.env[envVariable] ?? "");
|
|
138
|
-
}
|
|
139
|
-
const project = this.#projects[task.target.project];
|
|
140
|
-
if (project) {
|
|
141
|
-
if (this.#lockfileHasher) {
|
|
142
|
-
const lockfileHash = await this.#lockfileHasher.hashForPackage(join(project.root, "package.json"));
|
|
143
|
-
if (lockfileHash) {
|
|
144
|
-
implicitDeps["__lockfile__"] = lockfileHash.hash;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
if (this.#frameworkInference) {
|
|
148
|
-
const packageJsonPath = resolve(this.#workspaceRoot, project.root, "package.json");
|
|
149
|
-
const frameworkEnvVariables = await getFrameworkEnvVariables(packageJsonPath);
|
|
150
|
-
for (const envName of Object.keys(frameworkEnvVariables)) {
|
|
151
|
-
runtime[`framework-env:${envName}`] = hashStrings(envName, process.env[envName] ?? "");
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
command: commandHash,
|
|
157
|
-
implicitDeps: Object.keys(implicitDeps).length > 0 ? implicitDeps : void 0,
|
|
158
|
-
nodes,
|
|
159
|
-
runtime: Object.keys(runtime).length > 0 ? runtime : void 0
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Hashes the command identity for a task using xxh3-128.
|
|
164
|
-
* Uses native Rust implementation when available, otherwise pure TS xxh3-ts.
|
|
165
|
-
* Both produce identical xxh3-128 output.
|
|
166
|
-
*/
|
|
167
|
-
#hashCommand(task) {
|
|
168
|
-
const overridesJson = JSON.stringify(sortObjectKeys(task.overrides));
|
|
169
|
-
if (this.#native) {
|
|
170
|
-
return this.#native.hashCommand(task.target.project, task.target.target, task.target.configuration ?? void 0, overridesJson);
|
|
171
|
-
}
|
|
172
|
-
const hash = createXxh3Hasher();
|
|
173
|
-
hash.update(task.target.project);
|
|
174
|
-
hash.update(task.target.target);
|
|
175
|
-
if (task.target.configuration) {
|
|
176
|
-
hash.update(task.target.configuration);
|
|
177
|
-
}
|
|
178
|
-
hash.update(overridesJson);
|
|
179
|
-
return hash.digest();
|
|
180
|
-
}
|
|
181
|
-
#resolveInputs(task) {
|
|
182
|
-
const project = this.#projects[task.target.project];
|
|
183
|
-
const targetConfig = project?.targets?.[task.target.target];
|
|
184
|
-
const defaultConfig = this.#targetDefaults[task.target.target];
|
|
185
|
-
const rawInputs = targetConfig?.inputs ?? defaultConfig?.inputs;
|
|
186
|
-
if (!rawInputs) {
|
|
187
|
-
return [{ fileset: "{projectRoot}/**/*" }];
|
|
188
|
-
}
|
|
189
|
-
return this.#expandInputs(rawInputs, task.target.project);
|
|
190
|
-
}
|
|
191
|
-
#expandInputs(inputs, projectName) {
|
|
192
|
-
const result = [];
|
|
193
|
-
const seen = /* @__PURE__ */ new Set();
|
|
194
|
-
for (const input of inputs) {
|
|
195
|
-
if (typeof input === "string") {
|
|
196
|
-
if (input.startsWith("{") || input.startsWith("!{")) {
|
|
197
|
-
result.push({ fileset: input });
|
|
198
|
-
} else if (input.startsWith("^")) {
|
|
199
|
-
continue;
|
|
200
|
-
} else if (this.#namedInputs[input] && !seen.has(input)) {
|
|
201
|
-
seen.add(input);
|
|
202
|
-
result.push(...this.#expandInputs(this.#namedInputs[input], projectName));
|
|
203
|
-
} else {
|
|
204
|
-
result.push({ fileset: input });
|
|
205
|
-
}
|
|
206
|
-
} else {
|
|
207
|
-
result.push(input);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return result;
|
|
211
|
-
}
|
|
212
|
-
#collectNegationPatterns(inputs, projectName) {
|
|
213
|
-
const project = this.#projects[projectName];
|
|
214
|
-
const projectRoot = project?.root ?? "";
|
|
215
|
-
const patterns = [];
|
|
216
|
-
for (const input of inputs) {
|
|
217
|
-
if (!isFileSetInput(input)) {
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
const resolved = input.fileset.replace("{projectRoot}", projectRoot).replace("{workspaceRoot}", ".");
|
|
221
|
-
if (resolved.startsWith("!")) {
|
|
222
|
-
patterns.push(
|
|
223
|
-
resolved.slice(1).replace(/\/\*\*\/\*$/, "").replace(/\/\*$/, "")
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return patterns;
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Hashes all files matching a fileset pattern.
|
|
231
|
-
* Uses native Rust parallel hashing (via rayon) when available.
|
|
232
|
-
*/
|
|
233
|
-
async #hashFileSet(task, pattern, negationPatterns = []) {
|
|
234
|
-
const project = this.#projects[task.target.project];
|
|
235
|
-
const projectRoot = project?.root ?? "";
|
|
236
|
-
const resolvedPattern = pattern.replace("{projectRoot}", projectRoot).replace("{workspaceRoot}", ".");
|
|
237
|
-
if (resolvedPattern.startsWith("!")) {
|
|
238
|
-
return {};
|
|
239
|
-
}
|
|
240
|
-
const absoluteBase = resolve(this.#workspaceRoot, resolvedPattern.replace(/\/\*\*\/\*$/, "").replace(/\/\*$/, ""));
|
|
241
|
-
const absoluteNegations = negationPatterns.map((p) => resolve(this.#workspaceRoot, p));
|
|
242
|
-
const result = {};
|
|
243
|
-
const isExcluded = (filePath) => absoluteNegations.some((neg) => filePath.startsWith(`${neg}/`) || filePath === neg);
|
|
244
|
-
try {
|
|
245
|
-
if (this.#native) {
|
|
246
|
-
const fileHashes = this.#native.hashFilesInDirectory(absoluteBase, this.#workspaceRoot);
|
|
247
|
-
for (const { hash, path } of fileHashes) {
|
|
248
|
-
const absPath = resolve(this.#workspaceRoot, path);
|
|
249
|
-
if (!isExcluded(absPath)) {
|
|
250
|
-
result[path] = hash;
|
|
251
|
-
this.#fileHashCache.set(absPath, hash);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return result;
|
|
255
|
-
}
|
|
256
|
-
const files = await collectFiles(absoluteBase, IGNORED_DIRS);
|
|
257
|
-
const hashPromises = files.map(async (filePath) => {
|
|
258
|
-
if (isExcluded(filePath)) {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
const hash = await this.#hashFile(filePath);
|
|
262
|
-
if (hash) {
|
|
263
|
-
result[relative(this.#workspaceRoot, filePath)] = hash;
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
await Promise.all(hashPromises);
|
|
267
|
-
} catch {
|
|
268
|
-
}
|
|
269
|
-
return result;
|
|
270
|
-
}
|
|
271
|
-
async #hashFile(filePath) {
|
|
272
|
-
const cached = this.#fileHashCache.get(filePath);
|
|
273
|
-
if (cached) {
|
|
274
|
-
return cached;
|
|
275
|
-
}
|
|
276
|
-
try {
|
|
277
|
-
const content = await readFile(filePath);
|
|
278
|
-
const hash = xxh3Hash(content);
|
|
279
|
-
this.#fileHashCache.set(filePath, hash);
|
|
280
|
-
return hash;
|
|
281
|
-
} catch {
|
|
282
|
-
return void 0;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
async #hashExternalDependency(depName) {
|
|
286
|
-
try {
|
|
287
|
-
const packageJsonPath = join(this.#workspaceRoot, "node_modules", depName, "package.json");
|
|
288
|
-
const packageJsonContent = await readFile(packageJsonPath, "utf8");
|
|
289
|
-
const packageJson = JSON.parse(packageJsonContent);
|
|
290
|
-
return hashStrings(depName, packageJson.version ?? "unknown");
|
|
291
|
-
} catch {
|
|
292
|
-
return hashStrings(depName, "not-installed");
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Computes a combined hash of all global inputs and global env vars.
|
|
297
|
-
* Cached after first computation.
|
|
298
|
-
*/
|
|
299
|
-
async #computeGlobalHash() {
|
|
300
|
-
if (this.#globalHash !== void 0) {
|
|
301
|
-
return this.#globalHash;
|
|
302
|
-
}
|
|
303
|
-
const hash = createXxh3Hasher();
|
|
304
|
-
let hasContent = false;
|
|
305
|
-
const globalFileHashes = await Promise.all(
|
|
306
|
-
this.#globalInputs.filter((input) => !(this.#smartLockfileHashing && LOCKFILE_NAMES.has(input))).map(async (globalInput) => {
|
|
307
|
-
const fileHash = await this.#hashFile(join(this.#workspaceRoot, globalInput));
|
|
308
|
-
return fileHash ? { hash: fileHash, name: globalInput } : void 0;
|
|
309
|
-
})
|
|
310
|
-
);
|
|
311
|
-
for (const entry of globalFileHashes) {
|
|
312
|
-
if (entry) {
|
|
313
|
-
hash.update(entry.name);
|
|
314
|
-
hash.update(entry.hash);
|
|
315
|
-
hasContent = true;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
for (const envName of this.#globalEnv) {
|
|
319
|
-
hash.update(`globalEnv:${envName}=${process.env[envName] ?? ""}`);
|
|
320
|
-
hasContent = true;
|
|
321
|
-
}
|
|
322
|
-
this.#globalHash = hasContent ? hash.digest() : "";
|
|
323
|
-
return this.#globalHash || void 0;
|
|
324
|
-
}
|
|
325
|
-
clearCache() {
|
|
326
|
-
this.#fileHashCache.clear();
|
|
327
|
-
this.#globalHash = void 0;
|
|
328
|
-
this.#lockfileHasher?.clearCache();
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
const computeTaskHash = (hashDetails) => {
|
|
332
|
-
const native = getNativeBindings();
|
|
333
|
-
if (native) {
|
|
334
|
-
const nodes = Object.keys(hashDetails.nodes).toSorted().map((key) => [key, hashDetails.nodes[key]]);
|
|
335
|
-
const implicitDeps = hashDetails.implicitDeps ? Object.keys(hashDetails.implicitDeps).toSorted().map((key) => [key, hashDetails.implicitDeps[key]]) : void 0;
|
|
336
|
-
const runtime = hashDetails.runtime ? Object.keys(hashDetails.runtime).toSorted().map((key) => [key, hashDetails.runtime[key]]) : void 0;
|
|
337
|
-
return native.computeTaskHash({
|
|
338
|
-
command: hashDetails.command,
|
|
339
|
-
implicit_deps: implicitDeps,
|
|
340
|
-
nodes,
|
|
341
|
-
runtime
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
const hash = createXxh3Hasher();
|
|
345
|
-
hash.update(hashDetails.command);
|
|
346
|
-
hashSortedEntries(hash, hashDetails.nodes);
|
|
347
|
-
if (hashDetails.implicitDeps) {
|
|
348
|
-
hashSortedEntries(hash, hashDetails.implicitDeps);
|
|
349
|
-
}
|
|
350
|
-
if (hashDetails.runtime) {
|
|
351
|
-
hashSortedEntries(hash, hashDetails.runtime);
|
|
352
|
-
}
|
|
353
|
-
return hash.digest();
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
export { InProcessTaskHasher, computeTaskHash };
|