@visulima/task-runner 1.0.0-alpha.7 → 1.0.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +3 -1
  3. package/dist/index.d.ts +3093 -49
  4. package/dist/index.js +29 -19
  5. package/dist/packem_chunks/index.js +5593 -0
  6. package/dist/packem_shared/{Cache-CWaX_c8U.js → Cache-CbhoA268.js} +151 -10
  7. package/dist/packem_shared/{FileAccessTracker-CQ5Ot7Hd.js → FileAccessTracker-D8zIURPU.js} +1 -1
  8. package/dist/packem_shared/{FingerprintManager-CV7U4f4f.js → FingerprintManager-78DjwWQ4.js} +1 -1
  9. package/dist/packem_shared/HttpRemoteCache-BTXUBH7t.js +290 -0
  10. package/dist/packem_shared/INPUT_URI_SCHEMES-DRm76YI5.js +69 -0
  11. package/dist/packem_shared/{IncrementalFileHasher-BRS76-mb.js → IncrementalFileHasher-BBhVK491.js} +1 -1
  12. package/dist/packem_shared/ReapiRemoteCache-vgRxDMmu.js +1012 -0
  13. package/dist/packem_shared/{TaskOrchestrator-rf45vW5c.js → TaskOrchestrator-CdRaQhTO.js} +100 -11
  14. package/dist/packem_shared/{TrackedTaskExecutor-CFPpQfXF.js → TrackedTaskExecutor-CWSMfHAW.js} +2 -2
  15. package/dist/packem_shared/V2_ROOT-DKBLxKo4.js +14 -0
  16. package/dist/packem_shared/actionDigestForTaskHash-BRE-9MT6.js +121 -0
  17. package/dist/packem_shared/archive-CnggHWb-.js +152 -0
  18. package/dist/packem_shared/{buildForwardDependencyMap-DLPgKEto.js → buildForwardDependencyMap-0BJFMMPv.js} +1 -2
  19. package/dist/packem_shared/{collectFiles-ClXHnHhg.js → collectFiles-cc1gokGU.js} +2 -1
  20. package/dist/packem_shared/{computeTaskHash-DYqfrDGq.js → computeTaskHash-DHoBJ_-V.js} +10 -4
  21. package/dist/packem_shared/containsBlob-CwGB0a_q.js +125 -0
  22. package/dist/packem_shared/{createTaskGraph-B7nH0kY_.js → createTaskGraph-Bwl4hwAf.js} +23 -2
  23. package/dist/packem_shared/{defaultTaskRunner-Cp7jCmIl.js → defaultTaskRunner-BaX4ZbFv.js} +58 -15
  24. package/dist/packem_shared/{detectFrameworks-CeFzKE6J.js → detectFrameworks-D7nyTc-o.js} +1 -1
  25. package/dist/packem_shared/{detectScriptShell-CR-xXKA4.js → detectScriptShell-CzxCM9-t.js} +1 -1
  26. package/dist/packem_shared/digestBuffer-CPdI2E1d.js +48 -0
  27. package/dist/packem_shared/{expandArguments-0AwD2BIA.js → expandArguments-Ba-hHYff.js} +2 -1
  28. package/dist/packem_shared/expandTokensInString-Bb7nYehP.js +47 -0
  29. package/dist/packem_shared/{extractPackageName-BllKetnz.js → extractPackageName-CMHjqGj_.js} +2 -3
  30. package/dist/packem_shared/{generateRunSummary-BE1jnQ3H.js → generateRunSummary-Bah7CFay.js} +1 -1
  31. package/dist/packem_shared/getCurrentBranch-DVNikt0P.js +156 -0
  32. package/dist/packem_shared/getMainWorktreeRoot-iBqToQJ4.js +114 -0
  33. package/dist/packem_shared/{parseCommands-D-IgF8Zh.js → parseCommands-DDdIxaH5.js} +8 -3
  34. package/dist/packem_shared/resolveCacheMode-CsmHT_0o.js +21 -0
  35. package/dist/packem_shared/{runConcurrently-CmfC4r-f.js → runConcurrently-BCGQ9fJl.js} +1 -1
  36. package/dist/packem_shared/shell-quote-DWJJbt21.js +3 -0
  37. package/dist/packem_shared/{utils-zO0ZRgtf.js → utils-Bmnj-H2J.js} +4 -1
  38. package/index.js +556 -723
  39. package/package.json +26 -13
  40. package/dist/affected.d.ts +0 -82
  41. package/dist/archive.d.ts +0 -38
  42. package/dist/cache.d.ts +0 -138
  43. package/dist/chrome-trace.d.ts +0 -53
  44. package/dist/command-parser/expand-arguments.d.ts +0 -11
  45. package/dist/command-parser/expand-shortcut.d.ts +0 -15
  46. package/dist/command-parser/expand-wildcard.d.ts +0 -13
  47. package/dist/command-parser/index.d.ts +0 -18
  48. package/dist/command-parser/strip-quotes.d.ts +0 -6
  49. package/dist/concurrent-fallback.d.ts +0 -16
  50. package/dist/concurrent.d.ts +0 -23
  51. package/dist/default-task-runner.d.ts +0 -44
  52. package/dist/detect-shell.d.ts +0 -19
  53. package/dist/file-access-tracker.d.ts +0 -59
  54. package/dist/fingerprint.d.ts +0 -54
  55. package/dist/flow-controllers/index.d.ts +0 -7
  56. package/dist/flow-controllers/input-handler.d.ts +0 -44
  57. package/dist/flow-controllers/log-timings.d.ts +0 -18
  58. package/dist/flow-controllers/restart-process.d.ts +0 -21
  59. package/dist/flow-controllers/teardown.d.ts +0 -22
  60. package/dist/framework-inference.d.ts +0 -35
  61. package/dist/graph-visualizer.d.ts +0 -74
  62. package/dist/incremental-hasher.d.ts +0 -76
  63. package/dist/life-cycle.d.ts +0 -38
  64. package/dist/lockfile-hasher.d.ts +0 -73
  65. package/dist/log-reporter.d.ts +0 -34
  66. package/dist/native-binding.d.ts +0 -106
  67. package/dist/output-resolver.d.ts +0 -20
  68. package/dist/packem_shared/RemoteCache-DSU3lc87.js +0 -219
  69. package/dist/packem_shared/archive-UQHAnZUa.js +0 -102
  70. package/dist/project-constraints.d.ts +0 -9
  71. package/dist/remote-cache.d.ts +0 -100
  72. package/dist/run-summary.d.ts +0 -111
  73. package/dist/task-graph-utils.d.ts +0 -39
  74. package/dist/task-graph.d.ts +0 -22
  75. package/dist/task-hasher.d.ts +0 -104
  76. package/dist/task-orchestrator.d.ts +0 -38
  77. package/dist/task-scheduler.d.ts +0 -41
  78. package/dist/terminal-buffer.d.ts +0 -29
  79. package/dist/tracked-executor.d.ts +0 -46
  80. package/dist/types.d.ts +0 -757
  81. package/dist/utils.d.ts +0 -39
@@ -1,9 +1,11 @@
1
- import { d as resolveTaskCwd, a as createFailureResult, e as createXxh3Hasher } from './utils-zO0ZRgtf.js';
1
+ import { d as resolveTaskCwd, a as createFailureResult, e as createXxh3Hasher } from './utils-Bmnj-H2J.js';
2
2
  import { resolve, join } from '@visulima/path';
3
- import { FingerprintManager } from './FingerprintManager-CV7U4f4f.js';
4
- import { generateRunSummary, writeLastRunSummary, writeRunSummary } from './generateRunSummary-BE1jnQ3H.js';
5
- import { computeTaskHash } from './computeTaskHash-DYqfrDGq.js';
6
- import { TrackedTaskExecutor } from './TrackedTaskExecutor-CFPpQfXF.js';
3
+ import { retrieveByTaskHash, storeByTaskHash } from './actionDigestForTaskHash-BRE-9MT6.js';
4
+ import { FingerprintManager } from './FingerprintManager-78DjwWQ4.js';
5
+ import { generateRunSummary, writeLastRunSummary, writeRunSummary } from './generateRunSummary-Bah7CFay.js';
6
+ import { computeTaskHash } from './computeTaskHash-DHoBJ_-V.js';
7
+ import { TrackedTaskExecutor } from './TrackedTaskExecutor-CWSMfHAW.js';
8
+ import { getCurrentBranch, evaluateWhen, explainWhen } from './getCurrentBranch-DVNikt0P.js';
7
9
 
8
10
  const hashFingerprint = (fingerprint) => {
9
11
  const hash = createXxh3Hasher();
@@ -25,6 +27,20 @@ const hashFingerprint = (fingerprint) => {
25
27
  }
26
28
  return hash.digest();
27
29
  };
30
+ const detectWarnings = (patterns, output) => {
31
+ if (!patterns || patterns.length === 0 || !output) {
32
+ return false;
33
+ }
34
+ for (const source of patterns) {
35
+ try {
36
+ if (new RegExp(source).test(output)) {
37
+ return true;
38
+ }
39
+ } catch {
40
+ }
41
+ }
42
+ return false;
43
+ };
28
44
  const createDeferred = () => {
29
45
  let resolve2;
30
46
  const promise = new Promise((r) => {
@@ -49,11 +65,14 @@ class TaskOrchestrator {
49
65
  #cacheDiagnostics;
50
66
  #resolveCommand;
51
67
  #remoteCache;
68
+ #onRemoteUploadError;
52
69
  #dryRun;
53
70
  #summarize;
54
71
  #taskGraph;
55
72
  #results = /* @__PURE__ */ new Map();
56
73
  #startTime;
74
+ #alwaysTasks;
75
+ #whenContext;
57
76
  /** Tracks in-flight task promises so the execution loop can await them */
58
77
  #inFlightTasks = /* @__PURE__ */ new Map();
59
78
  /** Deferred that gets resolved whenever a task completes, waking the loop */
@@ -74,10 +93,15 @@ class TaskOrchestrator {
74
93
  this.#cacheDiagnostics = options.cacheDiagnostics ?? false;
75
94
  this.#resolveCommand = options.resolveCommand ?? void 0;
76
95
  this.#remoteCache = options.remoteCache ?? void 0;
96
+ this.#onRemoteUploadError = options.onRemoteUploadError ?? void 0;
77
97
  this.#dryRun = options.dryRun ?? false;
78
98
  this.#summarize = options.summarize ?? false;
79
99
  this.#taskGraph = options.taskGraph ?? void 0;
80
100
  this.#startTime = Date.now();
101
+ this.#alwaysTasks = options.alwaysTasks ?? [];
102
+ this.#whenContext = options.whenContext ?? {
103
+ branch: getCurrentBranch(options.workspaceRoot)
104
+ };
81
105
  if (this.#autoFingerprint) {
82
106
  this.#fingerprintManager = new FingerprintManager(options.workspaceRoot);
83
107
  this.#trackedExecutor = new TrackedTaskExecutor(options.workspaceRoot);
@@ -96,6 +120,9 @@ class TaskOrchestrator {
96
120
  process.on("SIGTERM", signalHandler);
97
121
  try {
98
122
  await this.#executionLoop();
123
+ if (this.#alwaysTasks.length > 0 && !this.#aborted) {
124
+ await this.#runAlwaysTasks();
125
+ }
99
126
  } finally {
100
127
  process.removeListener("SIGINT", signalHandler);
101
128
  process.removeListener("SIGTERM", signalHandler);
@@ -110,6 +137,40 @@ class TaskOrchestrator {
110
137
  }
111
138
  return this.#results;
112
139
  }
140
+ /**
141
+ * Runs the configured `always: true` tasks sequentially after the
142
+ * main task graph completes. Each task's `when` clause is still
143
+ * honoured. Failures are recorded but never propagate — the whole
144
+ * point of an always-task is to fire regardless of upstream state.
145
+ *
146
+ * Always-tasks deliberately bypass `#processTask` /
147
+ * `#processTaskWithFingerprint`, which means **no cache lookup
148
+ * and no fingerprint check**. Cleanup / teardown / notification
149
+ * tasks are expected to run every invocation; serving them from
150
+ * cache would defeat the purpose.
151
+ */
152
+ async #runAlwaysTasks() {
153
+ for (const task of this.#alwaysTasks) {
154
+ this.#lifeCycle.scheduleTask?.(task);
155
+ this.#lifeCycle.startTasks?.([task]);
156
+ let result;
157
+ if (this.#shouldSkipForWhen(task)) {
158
+ result = this.#whenSkipResult(task);
159
+ } else {
160
+ const startTime = Date.now();
161
+ try {
162
+ result = await this.#executeTask(task, startTime);
163
+ } catch (error) {
164
+ result = createFailureResult(task, error, startTime);
165
+ this.#results.set(task.id, result);
166
+ }
167
+ }
168
+ this.#lifeCycle.endTasks?.([result]);
169
+ if (result.terminalOutput) {
170
+ this.#lifeCycle.printTaskTerminalOutput?.(result.task, result.status, result.terminalOutput);
171
+ }
172
+ }
173
+ }
113
174
  async #executionLoop() {
114
175
  while (!this.#scheduler.isComplete() && !this.#aborted) {
115
176
  const batch = this.#scheduler.getNextBatch();
@@ -130,7 +191,8 @@ class TaskOrchestrator {
130
191
  }
131
192
  this.#lifeCycle.startTasks?.(batch);
132
193
  for (const task of batch) {
133
- const taskPromise = (this.#autoFingerprint ? this.#processTaskWithFingerprint(task) : this.#processTask(task)).catch((error) => {
194
+ const startPromise = this.#shouldSkipForWhen(task) ? Promise.resolve(this.#whenSkipResult(task)) : this.#autoFingerprint ? this.#processTaskWithFingerprint(task) : this.#processTask(task);
195
+ const taskPromise = startPromise.catch((error) => {
134
196
  const errorResult = createFailureResult(task, error, Date.now());
135
197
  this.#results.set(task.id, errorResult);
136
198
  return errorResult;
@@ -169,7 +231,7 @@ class TaskOrchestrator {
169
231
  return this.#applyCachedResult(task, cachedResult, startTime);
170
232
  }
171
233
  if (this.#remoteCache) {
172
- const retrieved = await this.#remoteCache.retrieve(hash, this.#cache.cacheDirectory);
234
+ const retrieved = await retrieveByTaskHash(this.#remoteCache, hash, this.#cache.cacheDirectory);
173
235
  if (retrieved) {
174
236
  const remoteCached = await this.#cache.get(hash);
175
237
  if (remoteCached) {
@@ -182,7 +244,7 @@ class TaskOrchestrator {
182
244
  }
183
245
  const result = await this.#executeTask(task, startTime);
184
246
  if (result.code === 0 && task.cache !== false && task.hash && this.#remoteCache) {
185
- this.#remoteCache.store(task.hash, this.#cache.cacheDirectory).catch(() => {
247
+ storeByTaskHash(this.#remoteCache, task.hash, this.#cache.cacheDirectory, this.#onRemoteUploadError).catch(() => {
186
248
  });
187
249
  }
188
250
  return result;
@@ -217,7 +279,7 @@ class TaskOrchestrator {
217
279
  return this.#executeTaskWithTracking(task, startTime);
218
280
  }
219
281
  async #applyCachedResult(task, cachedResult, startTime) {
220
- const restored = await this.#cache.restoreOutputs(cachedResult.hash, task.outputs);
282
+ const restored = await this.#cache.restoreOutputs(cachedResult.hash, task.outputs, task.cacheRestore);
221
283
  const status = restored ? "local-cache" : "local-cache-kept-existing";
222
284
  const result = {
223
285
  code: cachedResult.code,
@@ -236,16 +298,19 @@ class TaskOrchestrator {
236
298
  captureOutput: this.#captureOutput,
237
299
  cwd: resolveTaskCwd(this.#workspaceRoot, task)
238
300
  });
301
+ const hadWarnings = code === 0 && detectWarnings(task.warningPattern, terminalOutput);
239
302
  const result = {
240
303
  code,
241
304
  endTime: Date.now(),
305
+ hadWarnings: hadWarnings || void 0,
242
306
  startTime,
243
307
  status: code === 0 ? "success" : "failure",
244
308
  task,
245
309
  terminalOutput
246
310
  };
247
311
  this.#results.set(task.id, result);
248
- if (code === 0 && task.cache !== false && task.hash) {
312
+ const skipCacheOnWarning = hadWarnings && task.cacheOnWarning === false;
313
+ if (code === 0 && task.cache !== false && task.hash && !skipCacheOnWarning) {
249
314
  const modified = await this.#detectSelfModifiedInputs(task);
250
315
  if (modified.length > 0) {
251
316
  result.selfModified = true;
@@ -336,16 +401,19 @@ class TaskOrchestrator {
336
401
  this.#untrackedEnvVars
337
402
  );
338
403
  }
404
+ const hadWarnings = code === 0 && detectWarnings(task.warningPattern, terminalOutput);
339
405
  const result = {
340
406
  code,
341
407
  endTime: Date.now(),
408
+ hadWarnings: hadWarnings || void 0,
342
409
  startTime,
343
410
  status: code === 0 ? "success" : "failure",
344
411
  task,
345
412
  terminalOutput
346
413
  };
347
414
  this.#results.set(task.id, result);
348
- if (code === 0 && task.cache !== false && fingerprint) {
415
+ const skipCacheOnWarning = hadWarnings && task.cacheOnWarning === false;
416
+ if (code === 0 && task.cache !== false && fingerprint && !skipCacheOnWarning) {
349
417
  const modified = this.#detectSelfModifiedFingerprint(fingerprint);
350
418
  const emptyFingerprintReason = this.#describeEmptyFingerprint(fingerprint, usedRealTracker, trackerAccessCount);
351
419
  if (modified.length > 0) {
@@ -413,6 +481,27 @@ class TaskOrchestrator {
413
481
  this.#results.set(task.id, result);
414
482
  return result;
415
483
  }
484
+ #shouldSkipForWhen(task) {
485
+ if (!task.when) {
486
+ return false;
487
+ }
488
+ return !evaluateWhen(task.when, this.#whenContext);
489
+ }
490
+ #whenSkipResult(task) {
491
+ const reason = explainWhen(task.when, this.#whenContext);
492
+ const startTime = Date.now();
493
+ this.#lifeCycle.printWhenSkip?.(task, reason);
494
+ const result = {
495
+ code: 0,
496
+ endTime: startTime,
497
+ startTime,
498
+ status: "skipped",
499
+ task,
500
+ terminalOutput: reason ? `Skipped: ${reason}` : "Skipped by when clause"
501
+ };
502
+ this.#results.set(task.id, result);
503
+ return result;
504
+ }
416
505
  }
417
506
 
418
507
  export { TaskOrchestrator };
@@ -27,8 +27,8 @@ const {
27
27
  rm
28
28
  } = __cjs_getBuiltinModule("node:fs/promises");
29
29
  import { join } from '@visulima/path';
30
- import { FileAccessTracker, generatePreloadScript } from './FileAccessTracker-CQ5Ot7Hd.js';
31
- import { u as uniqueId } from './utils-zO0ZRgtf.js';
30
+ import { FileAccessTracker, generatePreloadScript } from './FileAccessTracker-D8zIURPU.js';
31
+ import { u as uniqueId } from './utils-Bmnj-H2J.js';
32
32
 
33
33
  class TrackedTaskExecutor {
34
34
  #tracker;
@@ -0,0 +1,14 @@
1
+ import { join } from '@visulima/path';
2
+
3
+ const V2_ROOT = "v2";
4
+ const V2_CAS = "cas";
5
+ const V2_AC = "ac";
6
+ const V2_INDEX = "task-hash-index";
7
+ const V2_TMP = "tmp";
8
+ const shard = (hash) => hash.slice(0, 2);
9
+ const casBlobPath = (root, hash) => join(root, V2_ROOT, V2_CAS, shard(hash), hash);
10
+ const acEntryPath = (root, actionHash) => join(root, V2_ROOT, V2_AC, shard(actionHash), `${actionHash}.json`);
11
+ const taskHashIndexPath = (root, taskHash) => join(root, V2_ROOT, V2_INDEX, shard(taskHash), taskHash);
12
+ const tmpDirectory = (root) => join(root, V2_ROOT, V2_TMP);
13
+
14
+ export { V2_AC, V2_CAS, V2_INDEX, V2_ROOT, V2_TMP, acEntryPath, casBlobPath, shard, taskHashIndexPath, tmpDirectory };
@@ -0,0 +1,121 @@
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
+ createHash,
22
+ randomUUID
23
+ } = __cjs_getBuiltinModule("node:crypto");
24
+ const {
25
+ createReadStream
26
+ } = __cjs_getBuiltinModule("node:fs");
27
+ const {
28
+ mkdir,
29
+ rm,
30
+ stat
31
+ } = __cjs_getBuiltinModule("node:fs/promises");
32
+ import { join } from '@visulima/path';
33
+ import { e as extractTarGz, c as createTarGz } from './archive-CnggHWb-.js';
34
+
35
+ const TARBALL_OUTPUT_PATH = "vis-entry.tar.gz";
36
+ const actionDigestForTaskHash = (taskHash) => {
37
+ const key = `vis-task:${taskHash}`;
38
+ return {
39
+ hash: createHash("sha256").update(Buffer.from(key, "utf8")).digest("hex"),
40
+ sizeBytes: 0
41
+ };
42
+ };
43
+ const containsByTaskHash = async (backend, taskHash) => {
44
+ try {
45
+ return await backend.containsAction(actionDigestForTaskHash(taskHash));
46
+ } catch {
47
+ return false;
48
+ }
49
+ };
50
+ const retrieveByTaskHash = async (backend, taskHash, localCacheDirectory) => {
51
+ const action = await backend.retrieveAction(actionDigestForTaskHash(taskHash));
52
+ if (action === null || action.outputFiles.length === 0) {
53
+ return false;
54
+ }
55
+ const tarballEntry = action.outputFiles[0];
56
+ if (!tarballEntry) {
57
+ return false;
58
+ }
59
+ const entryDirectory = join(localCacheDirectory, taskHash);
60
+ const archivePath = join(localCacheDirectory, `.download-${taskHash}-${randomUUID()}.tar.gz`);
61
+ try {
62
+ await mkdir(localCacheDirectory, { recursive: true });
63
+ const fetched = await backend.fetchBlob(tarballEntry.digest, archivePath);
64
+ if (!fetched) {
65
+ return false;
66
+ }
67
+ await mkdir(entryDirectory, { recursive: true });
68
+ await extractTarGz(archivePath, entryDirectory);
69
+ return true;
70
+ } catch {
71
+ await rm(entryDirectory, { force: true, recursive: true }).catch(() => {
72
+ });
73
+ return false;
74
+ } finally {
75
+ await rm(archivePath, { force: true }).catch(() => {
76
+ });
77
+ }
78
+ };
79
+ const storeByTaskHash = async (backend, taskHash, localCacheDirectory, onUploadError) => {
80
+ const entryDirectory = join(localCacheDirectory, taskHash);
81
+ const archivePath = join(localCacheDirectory, `.upload-${taskHash}-${randomUUID()}.tar.gz`);
82
+ try {
83
+ try {
84
+ await stat(join(entryDirectory, ".commit"));
85
+ } catch {
86
+ return false;
87
+ }
88
+ try {
89
+ await createTarGz(entryDirectory, archivePath);
90
+ const blobDigest = await digestFile(archivePath);
91
+ const result = {
92
+ exitCode: 0,
93
+ outputDirectories: [],
94
+ outputFiles: [{ digest: blobDigest, isExecutable: false, path: TARBALL_OUTPUT_PATH }]
95
+ };
96
+ const blob = {
97
+ digest: blobDigest,
98
+ open: () => Promise.resolve(createReadStream(archivePath))
99
+ };
100
+ return await backend.storeAction(actionDigestForTaskHash(taskHash), result, [blob]);
101
+ } catch (error) {
102
+ onUploadError?.(taskHash, error);
103
+ return false;
104
+ }
105
+ } finally {
106
+ await rm(archivePath, { force: true }).catch(() => {
107
+ });
108
+ }
109
+ };
110
+ const digestFile = async (path) => {
111
+ const hash = createHash("sha256");
112
+ let sizeBytes = 0;
113
+ for await (const chunk of createReadStream(path)) {
114
+ const buffer = chunk;
115
+ hash.update(buffer);
116
+ sizeBytes += buffer.byteLength;
117
+ }
118
+ return { hash: hash.digest("hex"), sizeBytes };
119
+ };
120
+
121
+ export { actionDigestForTaskHash, containsByTaskHash, retrieveByTaskHash, storeByTaskHash };
@@ -0,0 +1,152 @@
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
+ mkdir,
22
+ writeFile,
23
+ utimes,
24
+ readdir,
25
+ readFile,
26
+ stat
27
+ } = __cjs_getBuiltinModule("node:fs/promises");
28
+ const {
29
+ Readable
30
+ } = __cjs_getBuiltinModule("node:stream");
31
+ const {
32
+ pipeline
33
+ } = __cjs_getBuiltinModule("node:stream/promises");
34
+ const {
35
+ createGunzip,
36
+ createGzip,
37
+ createBrotliDecompress,
38
+ createBrotliCompress,
39
+ constants
40
+ } = __cjs_getBuiltinModule("node:zlib");
41
+ import { join, resolve, sep } from '@visulima/path';
42
+ import { parseTar, createTar } from 'nanotar';
43
+
44
+ const BROTLI_COMPRESS_OPTIONS = {
45
+ params: {
46
+ [constants.BROTLI_PARAM_MODE]: constants.BROTLI_MODE_TEXT,
47
+ [constants.BROTLI_PARAM_QUALITY]: 4
48
+ }
49
+ };
50
+ const collectEntries = async (sourceDirectory) => {
51
+ const entries = [];
52
+ const root = resolve(sourceDirectory);
53
+ const walk = async (absolute, relative) => {
54
+ const items = await readdir(absolute, { withFileTypes: true });
55
+ items.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
56
+ for (const item of items) {
57
+ const childAbsolute = join(absolute, item.name);
58
+ const childRelative = relative === "" ? item.name : `${relative}/${item.name}`;
59
+ if (item.isDirectory()) {
60
+ await walk(childAbsolute, childRelative);
61
+ continue;
62
+ }
63
+ if (!item.isFile()) {
64
+ continue;
65
+ }
66
+ const [data, info] = await Promise.all([readFile(childAbsolute), stat(childAbsolute)]);
67
+ entries.push({
68
+ attrs: {
69
+ // eslint-disable-next-line no-bitwise -- POSIX file modes encode the rwx triplet in the low 12 bits; bitmask is the canonical extraction
70
+ mode: (info.mode & 4095).toString(8),
71
+ mtime: Math.floor(info.mtimeMs)
72
+ },
73
+ data,
74
+ name: childRelative
75
+ });
76
+ }
77
+ };
78
+ await walk(root, "");
79
+ return entries;
80
+ };
81
+ const safeJoinForExtract = (destinationDirectory, entryName) => {
82
+ if (entryName === "" || entryName === "." || entryName === "./") {
83
+ return null;
84
+ }
85
+ if (entryName.startsWith("/") || entryName.startsWith("\\") || /^[a-z]:[\\/]/i.test(entryName)) {
86
+ return null;
87
+ }
88
+ const resolvedDestination = resolve(destinationDirectory);
89
+ const resolvedEntry = resolve(resolvedDestination, entryName);
90
+ const prefix = resolvedDestination.endsWith(sep) ? resolvedDestination : `${resolvedDestination}${sep}`;
91
+ if (resolvedEntry !== resolvedDestination && !resolvedEntry.startsWith(prefix)) {
92
+ return null;
93
+ }
94
+ return resolvedEntry;
95
+ };
96
+ const writeTarEntries = async (entries, destinationDirectory, options = {}) => {
97
+ const preserveMtime = options.preserveMtime ?? true;
98
+ const preservePerms = options.preservePerms ?? true;
99
+ await mkdir(destinationDirectory, { recursive: true });
100
+ for (const entry of entries) {
101
+ if (entry.data === void 0) {
102
+ continue;
103
+ }
104
+ const writePath = safeJoinForExtract(destinationDirectory, entry.name);
105
+ if (writePath === null) {
106
+ throw new Error(`[task-runner] refusing to extract tar entry with unsafe path: ${entry.name}`);
107
+ }
108
+ await mkdir(join(writePath, ".."), { recursive: true });
109
+ const mode = preservePerms && entry.attrs?.mode !== void 0 ? Number.parseInt(entry.attrs.mode, 8) : void 0;
110
+ await writeFile(writePath, entry.data, mode === void 0 ? void 0 : { mode });
111
+ if (preserveMtime && entry.attrs?.mtime !== void 0) {
112
+ const mtime = new Date(entry.attrs.mtime * 1e3);
113
+ await utimes(writePath, mtime, mtime);
114
+ }
115
+ }
116
+ };
117
+ const compressBuffer = async (buffer, transform, outputPath) => {
118
+ const { createWriteStream } = await import('node:fs');
119
+ await pipeline(Readable.from(Buffer.from(buffer)), transform, createWriteStream(outputPath));
120
+ };
121
+ const decompressBuffer = async (sourcePath, transform) => {
122
+ const { createReadStream } = await import('node:fs');
123
+ const chunks = [];
124
+ await pipeline(createReadStream(sourcePath), transform, async (source) => {
125
+ for await (const chunk of source) {
126
+ chunks.push(chunk);
127
+ }
128
+ });
129
+ return Buffer.concat(chunks);
130
+ };
131
+ const createTarGz = async (sourceDirectory, outputPath) => {
132
+ const entries = await collectEntries(sourceDirectory);
133
+ const tarBytes = createTar(entries);
134
+ await compressBuffer(tarBytes, createGzip(), outputPath);
135
+ };
136
+ const extractTarGz = async (archivePath, destinationDirectory, options) => {
137
+ const tarBuffer = await decompressBuffer(archivePath, createGunzip());
138
+ const entries = parseTar(tarBuffer);
139
+ await writeTarEntries(entries, destinationDirectory, options);
140
+ };
141
+ const createTarBrotli = async (sourceDirectory, outputPath) => {
142
+ const entries = await collectEntries(sourceDirectory);
143
+ const tarBytes = createTar(entries);
144
+ await compressBuffer(tarBytes, createBrotliCompress(BROTLI_COMPRESS_OPTIONS), outputPath);
145
+ };
146
+ const extractTarBrotli = async (archivePath, destinationDirectory, options) => {
147
+ const tarBuffer = await decompressBuffer(archivePath, createBrotliDecompress());
148
+ const entries = parseTar(tarBuffer);
149
+ await writeTarEntries(entries, destinationDirectory, options);
150
+ };
151
+
152
+ export { extractTarBrotli as a, createTarBrotli as b, createTarGz as c, extractTarGz as e };
@@ -33,8 +33,7 @@ const findProjectForFile = (filePath, projects) => {
33
33
  let bestLength = 0;
34
34
  for (const [name, config] of Object.entries(projects)) {
35
35
  const { root } = config;
36
- if ((filePath.startsWith(`${root}/`) || filePath === root) && // Prefer the most specific (longest) match
37
- root.length > bestLength) {
36
+ if ((filePath.startsWith(`${root}/`) || filePath === root) && root.length > bestLength) {
38
37
  bestMatch = name;
39
38
  bestLength = root.length;
40
39
  }
@@ -18,5 +18,6 @@ const __cjs_getBuiltinModule = (module) => {
18
18
  };
19
19
 
20
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';
21
+
22
+ 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-Bmnj-H2J.js';
22
23
  import '@visulima/path';
@@ -24,11 +24,12 @@ const {
24
24
  stat,
25
25
  readFile
26
26
  } = __cjs_getBuiltinModule("node:fs/promises");
27
- import { b as hashStrings, s as sortObjectKeys, c as collectFiles, x as xxh3Hash, e as createXxh3Hasher } from './utils-zO0ZRgtf.js';
27
+ import { b as hashStrings, s as sortObjectKeys, c as collectFiles, x as xxh3Hash, e as createXxh3Hasher } from './utils-Bmnj-H2J.js';
28
28
  import { join, resolve, relative } from '@visulima/path';
29
- import { getFrameworkEnvVariables } from './detectFrameworks-CeFzKE6J.js';
30
- import { LockfileHasher } from './extractPackageName-BllKetnz.js';
29
+ import { getFrameworkEnvVariables } from './detectFrameworks-D7nyTc-o.js';
30
+ import { LockfileHasher } from './extractPackageName-CMHjqGj_.js';
31
31
  import { loadNativeBindings } from './isNativeAvailable-BpD28A6Z.js';
32
+ import { looksLikeInputUri, parseInputUri } from './INPUT_URI_SCHEMES-DRm76YI5.js';
32
33
 
33
34
  const DEFAULT_GLOBAL_INPUTS = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "tsconfig.base.json", "tsconfig.json", ".env"];
34
35
  const IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "coverage", "dist", "node_modules"]);
@@ -254,7 +255,12 @@ class InProcessTaskHasher {
254
255
  const seen = /* @__PURE__ */ new Set();
255
256
  for (const input of inputs) {
256
257
  if (typeof input === "string") {
257
- if (input.startsWith("{") || input.startsWith("!{")) {
258
+ if (looksLikeInputUri(input)) {
259
+ const parsed = parseInputUri(input);
260
+ if (parsed) {
261
+ result.push(parsed);
262
+ }
263
+ } else if (input.startsWith("{") || input.startsWith("!{")) {
258
264
  result.push({ fileset: input });
259
265
  } else if (input.startsWith("^")) {
260
266
  continue;