@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,179 +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
|
-
createWriteStream
|
|
25
|
-
} = __cjs_getBuiltinModule("node:fs");
|
|
26
|
-
const {
|
|
27
|
-
mkdir,
|
|
28
|
-
rm,
|
|
29
|
-
stat,
|
|
30
|
-
readFile
|
|
31
|
-
} = __cjs_getBuiltinModule("node:fs/promises");
|
|
32
|
-
import { join } from '@visulima/path';
|
|
33
|
-
const {
|
|
34
|
-
pipeline
|
|
35
|
-
} = __cjs_getBuiltinModule("node:stream/promises");
|
|
36
|
-
|
|
37
|
-
const createTarGz = (sourceDirectory, outputPath) => new Promise((resolve, reject) => {
|
|
38
|
-
execFile("tar", ["-czf", outputPath, "-C", sourceDirectory, "."], (error) => {
|
|
39
|
-
if (error) {
|
|
40
|
-
reject(error);
|
|
41
|
-
} else {
|
|
42
|
-
resolve();
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
const extractTarGz = (archivePath, destinationDirectory) => new Promise((resolve, reject) => {
|
|
47
|
-
execFile("tar", ["-xzf", archivePath, "-C", destinationDirectory], (error) => {
|
|
48
|
-
if (error) {
|
|
49
|
-
reject(error);
|
|
50
|
-
} else {
|
|
51
|
-
resolve();
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
class RemoteCache {
|
|
56
|
-
#url;
|
|
57
|
-
#token;
|
|
58
|
-
#teamId;
|
|
59
|
-
#timeout;
|
|
60
|
-
#read;
|
|
61
|
-
#write;
|
|
62
|
-
#onUploadError;
|
|
63
|
-
constructor(options) {
|
|
64
|
-
this.#url = options.url.replace(/\/$/, "");
|
|
65
|
-
this.#token = options.token;
|
|
66
|
-
this.#teamId = options.teamId;
|
|
67
|
-
this.#timeout = options.timeout ?? 3e4;
|
|
68
|
-
this.#read = options.read ?? true;
|
|
69
|
-
this.#write = options.write ?? true;
|
|
70
|
-
this.#onUploadError = options.onUploadError;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Retrieves a cached artifact from the remote cache.
|
|
74
|
-
* Returns the local path to the extracted cache entry, or undefined if not found.
|
|
75
|
-
*/
|
|
76
|
-
async retrieve(hash, localCacheDirectory) {
|
|
77
|
-
if (!this.#read) {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
const entryDirectory = join(localCacheDirectory, hash);
|
|
81
|
-
const archivePath = join(localCacheDirectory, `.remote-${hash}.tar.gz`);
|
|
82
|
-
try {
|
|
83
|
-
const artifactUrl = this.#buildUrl(`/v8/artifacts/${hash}`);
|
|
84
|
-
const response = await fetch(artifactUrl, {
|
|
85
|
-
headers: this.#buildHeaders(),
|
|
86
|
-
method: "GET",
|
|
87
|
-
signal: AbortSignal.timeout(this.#timeout)
|
|
88
|
-
});
|
|
89
|
-
if (!response.ok) {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
await mkdir(localCacheDirectory, { recursive: true });
|
|
93
|
-
const { body } = response;
|
|
94
|
-
if (!body) {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
const fileStream = createWriteStream(archivePath);
|
|
98
|
-
await pipeline(body, fileStream);
|
|
99
|
-
await mkdir(entryDirectory, { recursive: true });
|
|
100
|
-
await extractTarGz(archivePath, entryDirectory);
|
|
101
|
-
await rm(archivePath, { force: true });
|
|
102
|
-
return true;
|
|
103
|
-
} catch {
|
|
104
|
-
await rm(archivePath, { force: true }).catch(() => {
|
|
105
|
-
});
|
|
106
|
-
await rm(entryDirectory, { force: true, recursive: true }).catch(() => {
|
|
107
|
-
});
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Stores a cache entry in the remote cache.
|
|
113
|
-
*/
|
|
114
|
-
async store(hash, localCacheDirectory) {
|
|
115
|
-
if (!this.#write) {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
const entryDirectory = join(localCacheDirectory, hash);
|
|
119
|
-
const archivePath = join(localCacheDirectory, `.upload-${hash}.tar.gz`);
|
|
120
|
-
try {
|
|
121
|
-
await stat(join(entryDirectory, ".commit"));
|
|
122
|
-
await createTarGz(entryDirectory, archivePath);
|
|
123
|
-
const artifactUrl = this.#buildUrl(`/v8/artifacts/${hash}`);
|
|
124
|
-
const archiveContent = await readFile(archivePath);
|
|
125
|
-
const response = await fetch(artifactUrl, {
|
|
126
|
-
body: archiveContent,
|
|
127
|
-
headers: {
|
|
128
|
-
...this.#buildHeaders(),
|
|
129
|
-
"Content-Length": String(archiveContent.length),
|
|
130
|
-
"Content-Type": "application/octet-stream"
|
|
131
|
-
},
|
|
132
|
-
method: "PUT",
|
|
133
|
-
signal: AbortSignal.timeout(this.#timeout)
|
|
134
|
-
});
|
|
135
|
-
await rm(archivePath, { force: true });
|
|
136
|
-
return response.ok;
|
|
137
|
-
} catch (error) {
|
|
138
|
-
await rm(archivePath, { force: true }).catch(() => {
|
|
139
|
-
});
|
|
140
|
-
this.#onUploadError?.(hash, error);
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Checks if an artifact exists in the remote cache without downloading it.
|
|
146
|
-
*/
|
|
147
|
-
async exists(hash) {
|
|
148
|
-
if (!this.#read) {
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
try {
|
|
152
|
-
const artifactUrl = this.#buildUrl(`/v8/artifacts/${hash}`);
|
|
153
|
-
const response = await fetch(artifactUrl, {
|
|
154
|
-
headers: this.#buildHeaders(),
|
|
155
|
-
method: "HEAD",
|
|
156
|
-
signal: AbortSignal.timeout(this.#timeout)
|
|
157
|
-
});
|
|
158
|
-
return response.ok;
|
|
159
|
-
} catch {
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
#buildUrl(path) {
|
|
164
|
-
const url = `${this.#url}${path}`;
|
|
165
|
-
if (this.#teamId) {
|
|
166
|
-
return `${url}?teamId=${encodeURIComponent(this.#teamId)}`;
|
|
167
|
-
}
|
|
168
|
-
return url;
|
|
169
|
-
}
|
|
170
|
-
#buildHeaders() {
|
|
171
|
-
const headers = {};
|
|
172
|
-
if (this.#token) {
|
|
173
|
-
headers["Authorization"] = `Bearer ${this.#token}`;
|
|
174
|
-
}
|
|
175
|
-
return headers;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export { RemoteCache };
|
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
import { d as resolveTaskCwd, a as createFailureResult, e as createXxh3Hasher } from './utils-zO0ZRgtf.js';
|
|
2
|
-
import { join } from '@visulima/path';
|
|
3
|
-
import { FingerprintManager } from './FingerprintManager-D6Y0erg-.js';
|
|
4
|
-
import { generateRunSummary, writeRunSummary } from './generateRunSummary-qn-_jKwt.js';
|
|
5
|
-
import { computeTaskHash } from './computeTaskHash-BoCnnvIJ.js';
|
|
6
|
-
import { TrackedTaskExecutor } from './TrackedTaskExecutor-BGUKFE-7.js';
|
|
7
|
-
|
|
8
|
-
const hashFingerprint = (fingerprint) => {
|
|
9
|
-
const hash = createXxh3Hasher();
|
|
10
|
-
hash.update(fingerprint.commandHash);
|
|
11
|
-
for (const key of Object.keys(fingerprint.fileHashes).toSorted()) {
|
|
12
|
-
hash.update(key);
|
|
13
|
-
hash.update(fingerprint.fileHashes[key]);
|
|
14
|
-
}
|
|
15
|
-
for (const path of fingerprint.missingFiles) {
|
|
16
|
-
hash.update(`missing:${path}`);
|
|
17
|
-
}
|
|
18
|
-
for (const key of Object.keys(fingerprint.directoryListings).toSorted()) {
|
|
19
|
-
hash.update(`dir:${key}`);
|
|
20
|
-
hash.update(JSON.stringify(fingerprint.directoryListings[key]));
|
|
21
|
-
}
|
|
22
|
-
for (const key of Object.keys(fingerprint.envHashes).toSorted()) {
|
|
23
|
-
hash.update(key);
|
|
24
|
-
hash.update(fingerprint.envHashes[key]);
|
|
25
|
-
}
|
|
26
|
-
return hash.digest();
|
|
27
|
-
};
|
|
28
|
-
const createDeferred = () => {
|
|
29
|
-
let resolve;
|
|
30
|
-
const promise = new Promise((r) => {
|
|
31
|
-
resolve = r;
|
|
32
|
-
});
|
|
33
|
-
return { promise, resolve };
|
|
34
|
-
};
|
|
35
|
-
class TaskOrchestrator {
|
|
36
|
-
#taskHasher;
|
|
37
|
-
#cache;
|
|
38
|
-
#scheduler;
|
|
39
|
-
#lifeCycle;
|
|
40
|
-
#taskExecutor;
|
|
41
|
-
#workspaceRoot;
|
|
42
|
-
#skipCache;
|
|
43
|
-
#captureOutput;
|
|
44
|
-
#autoFingerprint;
|
|
45
|
-
#fingerprintManager;
|
|
46
|
-
#trackedExecutor;
|
|
47
|
-
#fingerprintEnvPatterns;
|
|
48
|
-
#untrackedEnvVars;
|
|
49
|
-
#cacheDiagnostics;
|
|
50
|
-
#resolveCommand;
|
|
51
|
-
#remoteCache;
|
|
52
|
-
#dryRun;
|
|
53
|
-
#summarize;
|
|
54
|
-
#taskGraph;
|
|
55
|
-
#results = /* @__PURE__ */ new Map();
|
|
56
|
-
#startTime;
|
|
57
|
-
/** Tracks in-flight task promises so the execution loop can await them */
|
|
58
|
-
#inFlightTasks = /* @__PURE__ */ new Map();
|
|
59
|
-
/** Deferred that gets resolved whenever a task completes, waking the loop */
|
|
60
|
-
#taskCompletionSignal = createDeferred();
|
|
61
|
-
#aborted = false;
|
|
62
|
-
constructor(options) {
|
|
63
|
-
this.#taskHasher = options.taskHasher;
|
|
64
|
-
this.#cache = options.cache;
|
|
65
|
-
this.#scheduler = options.scheduler;
|
|
66
|
-
this.#lifeCycle = options.lifeCycle;
|
|
67
|
-
this.#taskExecutor = options.taskExecutor;
|
|
68
|
-
this.#workspaceRoot = options.workspaceRoot;
|
|
69
|
-
this.#skipCache = options.skipCache ?? false;
|
|
70
|
-
this.#captureOutput = options.captureOutput ?? true;
|
|
71
|
-
this.#autoFingerprint = options.autoFingerprint ?? false;
|
|
72
|
-
this.#fingerprintEnvPatterns = options.fingerprintEnvPatterns ?? [];
|
|
73
|
-
this.#untrackedEnvVars = options.untrackedEnvVars ?? [];
|
|
74
|
-
this.#cacheDiagnostics = options.cacheDiagnostics ?? false;
|
|
75
|
-
this.#resolveCommand = options.resolveCommand ?? void 0;
|
|
76
|
-
this.#remoteCache = options.remoteCache ?? void 0;
|
|
77
|
-
this.#dryRun = options.dryRun ?? false;
|
|
78
|
-
this.#summarize = options.summarize ?? false;
|
|
79
|
-
this.#taskGraph = options.taskGraph ?? void 0;
|
|
80
|
-
this.#startTime = Date.now();
|
|
81
|
-
if (this.#autoFingerprint) {
|
|
82
|
-
this.#fingerprintManager = new FingerprintManager(options.workspaceRoot);
|
|
83
|
-
this.#trackedExecutor = new TrackedTaskExecutor(options.workspaceRoot);
|
|
84
|
-
} else {
|
|
85
|
-
this.#fingerprintManager = void 0;
|
|
86
|
-
this.#trackedExecutor = void 0;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
async run() {
|
|
90
|
-
this.#lifeCycle.startCommand?.();
|
|
91
|
-
const signalHandler = () => {
|
|
92
|
-
this.#aborted = true;
|
|
93
|
-
this.#trackedExecutor?.killAll();
|
|
94
|
-
};
|
|
95
|
-
process.on("SIGINT", signalHandler);
|
|
96
|
-
process.on("SIGTERM", signalHandler);
|
|
97
|
-
try {
|
|
98
|
-
await this.#executionLoop();
|
|
99
|
-
} finally {
|
|
100
|
-
process.removeListener("SIGINT", signalHandler);
|
|
101
|
-
process.removeListener("SIGTERM", signalHandler);
|
|
102
|
-
this.#lifeCycle.endCommand?.();
|
|
103
|
-
}
|
|
104
|
-
if (this.#summarize && this.#taskGraph) {
|
|
105
|
-
const summary = generateRunSummary(this.#results, this.#taskGraph, this.#startTime);
|
|
106
|
-
await writeRunSummary(summary, this.#workspaceRoot);
|
|
107
|
-
}
|
|
108
|
-
return this.#results;
|
|
109
|
-
}
|
|
110
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
111
|
-
async #executionLoop() {
|
|
112
|
-
while (!this.#scheduler.isComplete() && !this.#aborted) {
|
|
113
|
-
const batch = this.#scheduler.getNextBatch();
|
|
114
|
-
if (batch.length === 0) {
|
|
115
|
-
if (this.#inFlightTasks.size > 0) {
|
|
116
|
-
await this.#taskCompletionSignal.promise;
|
|
117
|
-
this.#taskCompletionSignal = createDeferred();
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (this.#scheduler.remainingCount > 0) {
|
|
121
|
-
throw new Error("Deadlock detected: tasks remain but none can be scheduled. This may indicate a circular dependency.");
|
|
122
|
-
}
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
for (const task of batch) {
|
|
126
|
-
this.#lifeCycle.scheduleTask?.(task);
|
|
127
|
-
this.#scheduler.startTask(task.id);
|
|
128
|
-
}
|
|
129
|
-
this.#lifeCycle.startTasks?.(batch);
|
|
130
|
-
for (const task of batch) {
|
|
131
|
-
const taskPromise = (this.#autoFingerprint ? this.#processTaskWithFingerprint(task) : this.#processTask(task)).catch((error) => {
|
|
132
|
-
const errorResult = createFailureResult(task, error, Date.now());
|
|
133
|
-
this.#results.set(task.id, errorResult);
|
|
134
|
-
return errorResult;
|
|
135
|
-
}).then((result) => {
|
|
136
|
-
this.#inFlightTasks.delete(task.id);
|
|
137
|
-
this.#scheduler.completeTask(task.id);
|
|
138
|
-
this.#lifeCycle.endTasks?.([result]);
|
|
139
|
-
if (result.terminalOutput) {
|
|
140
|
-
this.#lifeCycle.printTaskTerminalOutput?.(result.task, result.status, result.terminalOutput);
|
|
141
|
-
}
|
|
142
|
-
this.#taskCompletionSignal.resolve();
|
|
143
|
-
return result;
|
|
144
|
-
});
|
|
145
|
-
this.#inFlightTasks.set(task.id, taskPromise);
|
|
146
|
-
}
|
|
147
|
-
if (this.#inFlightTasks.size > 0) {
|
|
148
|
-
await this.#taskCompletionSignal.promise;
|
|
149
|
-
this.#taskCompletionSignal = createDeferred();
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (this.#inFlightTasks.size > 0) {
|
|
153
|
-
await Promise.all(this.#inFlightTasks.values());
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
157
|
-
async #processTask(task) {
|
|
158
|
-
const startTime = Date.now();
|
|
159
|
-
const hashDetails = await this.#taskHasher.hashTask(task);
|
|
160
|
-
const hash = computeTaskHash(hashDetails);
|
|
161
|
-
Object.assign(task, { hash, hashDetails });
|
|
162
|
-
if (this.#dryRun) {
|
|
163
|
-
return this.#dryRunResult(task, startTime);
|
|
164
|
-
}
|
|
165
|
-
if (!this.#skipCache && task.cache !== false) {
|
|
166
|
-
const cachedResult = await this.#cache.get(hash);
|
|
167
|
-
if (cachedResult) {
|
|
168
|
-
return this.#applyCachedResult(task, cachedResult, startTime);
|
|
169
|
-
}
|
|
170
|
-
if (this.#remoteCache) {
|
|
171
|
-
const retrieved = await this.#remoteCache.retrieve(hash, this.#cache.cacheDirectory);
|
|
172
|
-
if (retrieved) {
|
|
173
|
-
const remoteCached = await this.#cache.get(hash);
|
|
174
|
-
if (remoteCached) {
|
|
175
|
-
const result2 = await this.#applyCachedResult(task, remoteCached, startTime);
|
|
176
|
-
result2.status = "remote-cache";
|
|
177
|
-
return result2;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
const result = await this.#executeTask(task, startTime);
|
|
183
|
-
if (result.code === 0 && task.cache !== false && task.hash && this.#remoteCache) {
|
|
184
|
-
this.#remoteCache.store(task.hash, this.#cache.cacheDirectory).catch(() => {
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
return result;
|
|
188
|
-
}
|
|
189
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
190
|
-
async #processTaskWithFingerprint(task) {
|
|
191
|
-
const startTime = Date.now();
|
|
192
|
-
if (!this.#skipCache && task.cache !== false) {
|
|
193
|
-
const cachedResult = await this.#cache.getByTaskId(task.id);
|
|
194
|
-
if (cachedResult?.fingerprint && this.#fingerprintManager) {
|
|
195
|
-
const commandMiss = this.#fingerprintManager.validateCommand(
|
|
196
|
-
cachedResult.fingerprint,
|
|
197
|
-
`${task.target.project}:${task.target.target}`,
|
|
198
|
-
task.overrides
|
|
199
|
-
);
|
|
200
|
-
if (commandMiss) {
|
|
201
|
-
if (this.#cacheDiagnostics) {
|
|
202
|
-
this.#lifeCycle.printCacheMiss?.(task, this.#fingerprintManager.formatMissReasons([commandMiss]));
|
|
203
|
-
}
|
|
204
|
-
} else {
|
|
205
|
-
const missReasons = await this.#fingerprintManager.validate(cachedResult.fingerprint);
|
|
206
|
-
if (!missReasons) {
|
|
207
|
-
return this.#applyCachedResult(task, cachedResult, startTime);
|
|
208
|
-
}
|
|
209
|
-
if (this.#cacheDiagnostics) {
|
|
210
|
-
this.#lifeCycle.printCacheMiss?.(task, this.#fingerprintManager.formatMissReasons(missReasons));
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
} else if (this.#cacheDiagnostics && !cachedResult) {
|
|
214
|
-
this.#lifeCycle.printCacheMiss?.(task, "Cache miss reasons:\n - No previous fingerprint found (first run)");
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return this.#executeTaskWithTracking(task, startTime);
|
|
218
|
-
}
|
|
219
|
-
async #applyCachedResult(task, cachedResult, startTime) {
|
|
220
|
-
const restored = await this.#cache.restoreOutputs(cachedResult.hash, task.outputs);
|
|
221
|
-
const status = restored ? "local-cache" : "local-cache-kept-existing";
|
|
222
|
-
const result = {
|
|
223
|
-
code: cachedResult.code,
|
|
224
|
-
endTime: Date.now(),
|
|
225
|
-
startTime,
|
|
226
|
-
status,
|
|
227
|
-
task,
|
|
228
|
-
terminalOutput: cachedResult.terminalOutput
|
|
229
|
-
};
|
|
230
|
-
this.#results.set(task.id, result);
|
|
231
|
-
return result;
|
|
232
|
-
}
|
|
233
|
-
async #executeTask(task, startTime) {
|
|
234
|
-
try {
|
|
235
|
-
const { code, terminalOutput } = await this.#taskExecutor(task, {
|
|
236
|
-
captureOutput: this.#captureOutput,
|
|
237
|
-
cwd: resolveTaskCwd(this.#workspaceRoot, task)
|
|
238
|
-
});
|
|
239
|
-
const result = {
|
|
240
|
-
code,
|
|
241
|
-
endTime: Date.now(),
|
|
242
|
-
startTime,
|
|
243
|
-
status: code === 0 ? "success" : "failure",
|
|
244
|
-
task,
|
|
245
|
-
terminalOutput
|
|
246
|
-
};
|
|
247
|
-
this.#results.set(task.id, result);
|
|
248
|
-
if (code === 0 && task.cache !== false && task.hash) {
|
|
249
|
-
await this.#cache.put(task.hash, terminalOutput, task.outputs, code);
|
|
250
|
-
}
|
|
251
|
-
return result;
|
|
252
|
-
} catch (error) {
|
|
253
|
-
const result = createFailureResult(task, error, startTime);
|
|
254
|
-
this.#results.set(task.id, result);
|
|
255
|
-
return result;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
async #executeTaskWithTracking(task, startTime) {
|
|
259
|
-
if (!this.#fingerprintManager) {
|
|
260
|
-
return this.#executeTask(task, startTime);
|
|
261
|
-
}
|
|
262
|
-
const taskCommand = `${task.target.project}:${task.target.target}`;
|
|
263
|
-
const cwd = resolveTaskCwd(this.#workspaceRoot, task);
|
|
264
|
-
try {
|
|
265
|
-
let code;
|
|
266
|
-
let terminalOutput;
|
|
267
|
-
let fingerprint;
|
|
268
|
-
const shellCommand = this.#resolveCommand?.(task);
|
|
269
|
-
const canTrack = shellCommand && this.#trackedExecutor?.isTrackingSupported;
|
|
270
|
-
if (canTrack && this.#trackedExecutor) {
|
|
271
|
-
const trackedResult = await this.#trackedExecutor.execute(task, { captureOutput: this.#captureOutput, cwd }, shellCommand);
|
|
272
|
-
code = trackedResult.code;
|
|
273
|
-
terminalOutput = trackedResult.terminalOutput;
|
|
274
|
-
fingerprint = await this.#fingerprintManager.createFingerprint(
|
|
275
|
-
trackedResult.accesses,
|
|
276
|
-
taskCommand,
|
|
277
|
-
task.overrides,
|
|
278
|
-
process.env,
|
|
279
|
-
this.#fingerprintEnvPatterns,
|
|
280
|
-
this.#untrackedEnvVars
|
|
281
|
-
);
|
|
282
|
-
} else {
|
|
283
|
-
const executionResult = await this.#taskExecutor(task, {
|
|
284
|
-
captureOutput: this.#captureOutput,
|
|
285
|
-
cwd
|
|
286
|
-
});
|
|
287
|
-
code = executionResult.code;
|
|
288
|
-
terminalOutput = executionResult.terminalOutput;
|
|
289
|
-
const hashDetails = await this.#taskHasher.hashTask(task);
|
|
290
|
-
const fileAccesses = Object.keys(hashDetails.nodes).map((filePath) => {
|
|
291
|
-
return {
|
|
292
|
-
path: join(this.#workspaceRoot, filePath),
|
|
293
|
-
type: "read"
|
|
294
|
-
};
|
|
295
|
-
});
|
|
296
|
-
fingerprint = await this.#fingerprintManager.createFingerprint(
|
|
297
|
-
fileAccesses,
|
|
298
|
-
taskCommand,
|
|
299
|
-
task.overrides,
|
|
300
|
-
process.env,
|
|
301
|
-
this.#fingerprintEnvPatterns,
|
|
302
|
-
this.#untrackedEnvVars
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
const result = {
|
|
306
|
-
code,
|
|
307
|
-
endTime: Date.now(),
|
|
308
|
-
startTime,
|
|
309
|
-
status: code === 0 ? "success" : "failure",
|
|
310
|
-
task,
|
|
311
|
-
terminalOutput
|
|
312
|
-
};
|
|
313
|
-
this.#results.set(task.id, result);
|
|
314
|
-
if (code === 0 && task.cache !== false && fingerprint) {
|
|
315
|
-
const hash = hashFingerprint(fingerprint);
|
|
316
|
-
Object.assign(task, { hash });
|
|
317
|
-
await this.#cache.put(hash, terminalOutput, task.outputs, code, fingerprint);
|
|
318
|
-
await this.#cache.setTaskIndex(task.id, hash);
|
|
319
|
-
}
|
|
320
|
-
return result;
|
|
321
|
-
} catch (error) {
|
|
322
|
-
const result = createFailureResult(task, error, startTime);
|
|
323
|
-
this.#results.set(task.id, result);
|
|
324
|
-
return result;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
#dryRunResult(task, startTime) {
|
|
328
|
-
const cacheStatus = task.hash ? `[hash: ${task.hash.slice(0, 12)}...]` : "[no hash]";
|
|
329
|
-
const result = {
|
|
330
|
-
code: 0,
|
|
331
|
-
endTime: Date.now(),
|
|
332
|
-
startTime,
|
|
333
|
-
status: "skipped",
|
|
334
|
-
task,
|
|
335
|
-
terminalOutput: `DRY RUN ${cacheStatus}`
|
|
336
|
-
};
|
|
337
|
-
this.#results.set(task.id, result);
|
|
338
|
-
return result;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
export { TaskOrchestrator };
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
const calculateProjectDepths = (projectGraph) => {
|
|
2
|
-
const depths = /* @__PURE__ */ new Map();
|
|
3
|
-
const visited = /* @__PURE__ */ new Set();
|
|
4
|
-
const calculateDepth = (projectName) => {
|
|
5
|
-
if (depths.has(projectName)) {
|
|
6
|
-
return depths.get(projectName);
|
|
7
|
-
}
|
|
8
|
-
if (visited.has(projectName)) {
|
|
9
|
-
return 0;
|
|
10
|
-
}
|
|
11
|
-
visited.add(projectName);
|
|
12
|
-
const deps = projectGraph.dependencies[projectName] ?? [];
|
|
13
|
-
let maxDepth = 0;
|
|
14
|
-
for (const dep of deps) {
|
|
15
|
-
maxDepth = Math.max(maxDepth, calculateDepth(dep.target) + 1);
|
|
16
|
-
}
|
|
17
|
-
depths.set(projectName, maxDepth);
|
|
18
|
-
visited.delete(projectName);
|
|
19
|
-
return maxDepth;
|
|
20
|
-
};
|
|
21
|
-
for (const projectName of Object.keys(projectGraph.nodes)) {
|
|
22
|
-
calculateDepth(projectName);
|
|
23
|
-
}
|
|
24
|
-
return depths;
|
|
25
|
-
};
|
|
26
|
-
class TaskScheduler {
|
|
27
|
-
#taskGraph;
|
|
28
|
-
#maxParallel;
|
|
29
|
-
#completedTasks = /* @__PURE__ */ new Set();
|
|
30
|
-
#runningTasks = /* @__PURE__ */ new Set();
|
|
31
|
-
#totalTasks;
|
|
32
|
-
#dependentCounts;
|
|
33
|
-
#projectDepths;
|
|
34
|
-
constructor(taskGraph, projectGraph, maxParallel = 3) {
|
|
35
|
-
this.#taskGraph = taskGraph;
|
|
36
|
-
this.#maxParallel = maxParallel;
|
|
37
|
-
this.#totalTasks = Object.keys(taskGraph.tasks).length;
|
|
38
|
-
this.#dependentCounts = this.#calculateDependentCounts();
|
|
39
|
-
this.#projectDepths = calculateProjectDepths(projectGraph);
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Returns the next batch of tasks that are ready to execute.
|
|
43
|
-
*/
|
|
44
|
-
getNextBatch() {
|
|
45
|
-
const availableSlots = this.#maxParallel - this.#runningTasks.size;
|
|
46
|
-
if (availableSlots <= 0) {
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
const readyTasks = this.#getReadyTasks();
|
|
50
|
-
const sortedTasks = this.#sortByPriority(readyTasks);
|
|
51
|
-
return sortedTasks.slice(0, availableSlots);
|
|
52
|
-
}
|
|
53
|
-
startTask(taskId) {
|
|
54
|
-
this.#runningTasks.add(taskId);
|
|
55
|
-
}
|
|
56
|
-
completeTask(taskId) {
|
|
57
|
-
this.#runningTasks.delete(taskId);
|
|
58
|
-
this.#completedTasks.add(taskId);
|
|
59
|
-
}
|
|
60
|
-
isComplete() {
|
|
61
|
-
return this.#completedTasks.size === this.#totalTasks;
|
|
62
|
-
}
|
|
63
|
-
get remainingCount() {
|
|
64
|
-
return this.#totalTasks - this.#completedTasks.size;
|
|
65
|
-
}
|
|
66
|
-
get runningCount() {
|
|
67
|
-
return this.#runningTasks.size;
|
|
68
|
-
}
|
|
69
|
-
#getReadyTasks() {
|
|
70
|
-
const ready = [];
|
|
71
|
-
for (const [taskId, task] of Object.entries(this.#taskGraph.tasks)) {
|
|
72
|
-
if (this.#completedTasks.has(taskId) || this.#runningTasks.has(taskId)) {
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
const deps = this.#taskGraph.dependencies[taskId] ?? [];
|
|
76
|
-
if (deps.every((dep) => this.#completedTasks.has(dep))) {
|
|
77
|
-
ready.push(task);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return ready;
|
|
81
|
-
}
|
|
82
|
-
#sortByPriority(tasks) {
|
|
83
|
-
return [...tasks].toSorted((a, b) => {
|
|
84
|
-
const aDeps = this.#dependentCounts.get(a.id) ?? 0;
|
|
85
|
-
const bDeps = this.#dependentCounts.get(b.id) ?? 0;
|
|
86
|
-
if (aDeps !== bDeps) {
|
|
87
|
-
return bDeps - aDeps;
|
|
88
|
-
}
|
|
89
|
-
const aDepth = this.#projectDepths.get(a.target.project) ?? 0;
|
|
90
|
-
const bDepth = this.#projectDepths.get(b.target.project) ?? 0;
|
|
91
|
-
if (aDepth !== bDepth) {
|
|
92
|
-
return bDepth - aDepth;
|
|
93
|
-
}
|
|
94
|
-
return a.id.localeCompare(b.id);
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
#calculateDependentCounts() {
|
|
98
|
-
const counts = /* @__PURE__ */ new Map();
|
|
99
|
-
for (const taskId of Object.keys(this.#taskGraph.tasks)) {
|
|
100
|
-
counts.set(taskId, 0);
|
|
101
|
-
}
|
|
102
|
-
for (const deps of Object.values(this.#taskGraph.dependencies)) {
|
|
103
|
-
for (const dep of deps) {
|
|
104
|
-
counts.set(dep, (counts.get(dep) ?? 0) + 1);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return counts;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export { TaskScheduler };
|