@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.
Files changed (102) hide show
  1. package/CHANGELOG.md +310 -0
  2. package/README.md +198 -52
  3. package/dist/index.d.ts +3795 -34
  4. package/dist/index.js +1 -20
  5. package/dist/packem_chunks/index.js +31 -0
  6. package/dist/packem_shared/Cache-C8FfeXpg.js +2 -0
  7. package/dist/packem_shared/CompositeLifeCycle-C6aee9GK.js +1 -0
  8. package/dist/packem_shared/FileAccessTracker-DBz_w4wl.js +50 -0
  9. package/dist/packem_shared/FingerprintManager-CYW2EwLc.js +2 -0
  10. package/dist/packem_shared/HttpRemoteCache-CpPl6lzE.js +1 -0
  11. package/dist/packem_shared/INPUT_URI_SCHEMES-Csrd0tlg.js +1 -0
  12. package/dist/packem_shared/IncrementalFileHasher-B-V3i2x-.js +1 -0
  13. package/dist/packem_shared/LogReporter-3R3oWj-Q.js +13 -0
  14. package/dist/packem_shared/ReapiRemoteCache-BXJip5wH.js +251 -0
  15. package/dist/packem_shared/TaskOrchestrator-CYj5MLwz.js +6 -0
  16. package/dist/packem_shared/TerminalBuffer-BtZy7TpT.js +3 -0
  17. package/dist/packem_shared/TrackedTaskExecutor-CtYLL3vS.js +1 -0
  18. package/dist/packem_shared/V2_ROOT-injxWBrl.js +1 -0
  19. package/dist/packem_shared/actionDigestForTaskHash-BOL4fZ9v.js +1 -0
  20. package/dist/packem_shared/archive-CDfGy5Lm.js +1 -0
  21. package/dist/packem_shared/buildForwardDependencyMap-DudUDFze.js +3 -0
  22. package/dist/packem_shared/collectFiles-W4bnBRpb.js +1 -0
  23. package/dist/packem_shared/collectNodeModulesBinDirs-CD-eDrtO.js +1 -0
  24. package/dist/packem_shared/computeTaskHash-CaPdG1BA.js +1 -0
  25. package/dist/packem_shared/containsBlob-DAU8R7GH.js +1 -0
  26. package/dist/packem_shared/createInputHandler-CkDCpPYy.js +1 -0
  27. package/dist/packem_shared/createTaskGraph-D8Jn_Dn9.js +1 -0
  28. package/dist/packem_shared/defaultTaskRunner-DMHpavzm.js +2 -0
  29. package/dist/packem_shared/detectFrameworks-WVZJOPgN.js +1 -0
  30. package/dist/packem_shared/detectScriptShell-CaTDk5cW.js +1 -0
  31. package/dist/packem_shared/digestBuffer-g11aCaDx.js +1 -0
  32. package/dist/packem_shared/enforceProjectConstraints-dNc1SwRi.js +1 -0
  33. package/dist/packem_shared/expandArguments-D7qvc6Rp.js +1 -0
  34. package/dist/packem_shared/expandShortcut-BErNHNXZ.js +1 -0
  35. package/dist/packem_shared/expandTokensInString-DVSFEdWu.js +1 -0
  36. package/dist/packem_shared/expandWildcard-DE0dOOZF.js +1 -0
  37. package/dist/packem_shared/extractPackageName-BeL6Gc3a.js +1 -0
  38. package/dist/packem_shared/findCycle-BY8-jmzB.js +1 -0
  39. package/dist/packem_shared/formatTimingTable-CP3rsDwf.js +7 -0
  40. package/dist/packem_shared/generateRunSummary-L9Z2NfWn.js +1 -0
  41. package/dist/packem_shared/getCurrentBranch-D-qoZByx.js +1 -0
  42. package/dist/packem_shared/getMainWorktreeRoot-DB9P2wDL.js +1 -0
  43. package/dist/packem_shared/isNativeAvailable-CkTjxb7P.js +1 -0
  44. package/dist/packem_shared/parseCommands-BHsXoUCd.js +1 -0
  45. package/dist/packem_shared/parsePartition-Bt1jBjZH.js +1 -0
  46. package/dist/packem_shared/projectGraphToDot-FN6oHDGH.js +250 -0
  47. package/dist/packem_shared/resolveCacheMode--4y60ODd.js +1 -0
  48. package/dist/packem_shared/resolveOutputs-CzGGEbcP.js +1 -0
  49. package/dist/packem_shared/runConcurrentFallback-BhJCT2LA.js +3 -0
  50. package/dist/packem_shared/runConcurrently-D1Ytsjaj.js +1 -0
  51. package/dist/packem_shared/runTeardown-DAn1xFWJ.js +1 -0
  52. package/dist/packem_shared/shell-quote-BhmqDUL1.js +1 -0
  53. package/dist/packem_shared/stripQuotes-jkZb0CL9.js +1 -0
  54. package/dist/packem_shared/toChromeTrace-DxN5NQIU.js +1 -0
  55. package/dist/packem_shared/tracked-executor-B90U4Um3.js +3 -0
  56. package/dist/packem_shared/utils-BH2W5Wml.js +1 -0
  57. package/dist/packem_shared/withRestart-DKtEGsQA.js +1 -0
  58. package/index.js +603 -0
  59. package/package.json +31 -19
  60. package/binding.js +0 -204
  61. package/dist/affected.d.ts +0 -48
  62. package/dist/cache.d.ts +0 -103
  63. package/dist/default-task-runner.d.ts +0 -44
  64. package/dist/file-access-tracker.d.ts +0 -53
  65. package/dist/fingerprint.d.ts +0 -45
  66. package/dist/framework-inference.d.ts +0 -35
  67. package/dist/graph-visualizer.d.ts +0 -74
  68. package/dist/incremental-hasher.d.ts +0 -58
  69. package/dist/life-cycle.d.ts +0 -36
  70. package/dist/lockfile-hasher.d.ts +0 -73
  71. package/dist/native-binding.d.ts +0 -64
  72. package/dist/packem_shared/Cache-IYpTYVUC.js +0 -298
  73. package/dist/packem_shared/CompositeLifeCycle-7AtYw1dv.js +0 -112
  74. package/dist/packem_shared/FileAccessTracker-CrtBAt5D.js +0 -239
  75. package/dist/packem_shared/FingerprintManager-D6Y0erg-.js +0 -227
  76. package/dist/packem_shared/IncrementalFileHasher-Ds3J6dgb.js +0 -151
  77. package/dist/packem_shared/RemoteCache-BDqrnDEi.js +0 -179
  78. package/dist/packem_shared/TaskOrchestrator-BvYs3ONw.js +0 -342
  79. package/dist/packem_shared/TaskScheduler-CJilHDta.js +0 -111
  80. package/dist/packem_shared/TrackedTaskExecutor-BGUKFE-7.js +0 -164
  81. package/dist/packem_shared/collectFiles-ClXHnHhg.js +0 -22
  82. package/dist/packem_shared/computeTaskHash-BoCnnvIJ.js +0 -356
  83. package/dist/packem_shared/createTaskGraph-CcsFaSrz.js +0 -164
  84. package/dist/packem_shared/defaultTaskRunner-CrW4v5Ye.js +0 -79
  85. package/dist/packem_shared/detectFrameworks-CeFzKE6J.js +0 -101
  86. package/dist/packem_shared/extractPackageName-CbVNW-dr.js +0 -189
  87. package/dist/packem_shared/filterAffectedTasks-I-18zPg6.js +0 -135
  88. package/dist/packem_shared/findCycle-DF4_BRdO.js +0 -212
  89. package/dist/packem_shared/generateRunSummary-qn-_jKwt.js +0 -134
  90. package/dist/packem_shared/isNativeAvailable-BWhnZ4ES.js +0 -19
  91. package/dist/packem_shared/projectGraphToDot-VdTjHcVp.js +0 -202
  92. package/dist/packem_shared/utils-zO0ZRgtf.js +0 -390
  93. package/dist/remote-cache.d.ts +0 -55
  94. package/dist/run-summary.d.ts +0 -89
  95. package/dist/task-graph-utils.d.ts +0 -39
  96. package/dist/task-graph.d.ts +0 -22
  97. package/dist/task-hasher.d.ts +0 -67
  98. package/dist/task-orchestrator.d.ts +0 -38
  99. package/dist/task-scheduler.d.ts +0 -18
  100. package/dist/tracked-executor.d.ts +0 -46
  101. package/dist/types.d.ts +0 -385
  102. package/dist/utils.d.ts +0 -39
@@ -1,239 +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
- execFileSync
23
- } = __cjs_getBuiltinModule("node:child_process");
24
- const {
25
- mkdir,
26
- readFile,
27
- rm
28
- } = __cjs_getBuiltinModule("node:fs/promises");
29
- const {
30
- platform
31
- } = __cjs_getBuiltinModule("node:os");
32
- import { resolve, join } from '@visulima/path';
33
- import { u as uniqueId } from './utils-zO0ZRgtf.js';
34
-
35
- import __cjs_mod__ from "node:module"; // -- packem CommonJS require shim --
36
- const require = __cjs_mod__.createRequire(import.meta.url);
37
- let straceAvailable;
38
- const isStraceAvailable = () => {
39
- if (straceAvailable === void 0) {
40
- try {
41
- execFileSync("strace", ["-V"], { stdio: "ignore" });
42
- straceAvailable = true;
43
- } catch {
44
- straceAvailable = false;
45
- }
46
- }
47
- return straceAvailable;
48
- };
49
- const STRACE_PATTERNS = [
50
- { pattern: /openat\(AT_FDCWD,\s*"([^"]+)"/, type: "read" },
51
- { pattern: /^(?:\d+\s+)?open\("([^"]+)"/, type: "read" },
52
- { pattern: /(?:stat|lstat|newfstatat)\((?:AT_FDCWD,\s*)?"([^"]+)"/, type: "stat" },
53
- { pattern: /access\("([^"]+)"/, type: "stat" }
54
- ];
55
- class FileAccessTracker {
56
- #workspaceRoot;
57
- #excludePatterns;
58
- /** Tracks active child processes for cleanup on abort */
59
- #activeProcesses = /* @__PURE__ */ new Set();
60
- constructor(workspaceRoot, excludePatterns) {
61
- this.#workspaceRoot = resolve(workspaceRoot);
62
- this.#excludePatterns = excludePatterns ?? [
63
- /\/proc\//,
64
- /\/sys\//,
65
- /\/dev\//,
66
- /\/tmp\//,
67
- /\/etc\//,
68
- /\.so(\.\d+)*$/,
69
- /node_modules\/.package-lock\.json$/
70
- ];
71
- }
72
- /**
73
- * Returns true if file access tracking is supported on the current platform.
74
- */
75
- // eslint-disable-next-line class-methods-use-this
76
- isSupported() {
77
- return platform() === "linux" && isStraceAvailable();
78
- }
79
- /**
80
- * Runs a command and tracks all file system accesses.
81
- * On unsupported platforms, runs the command without tracking.
82
- */
83
- async track(command, options = {}) {
84
- if (!this.isSupported()) {
85
- return this.#runWithoutTracking(command, options);
86
- }
87
- return this.#runWithStrace(command, options);
88
- }
89
- /**
90
- * Runs a command wrapped with strace to capture file accesses.
91
- */
92
- async #runWithStrace(command, options) {
93
- const traceDirectory = join(this.#workspaceRoot, "node_modules", ".cache", "task-runner");
94
- await mkdir(traceDirectory, { recursive: true });
95
- const traceFile = join(traceDirectory, `strace-${uniqueId()}.log`);
96
- const straceCommand = `strace -f -qq -e trace=open,openat,stat,lstat,newfstatat,access,getdents,getdents64 -o ${traceFile} -- ${command}`;
97
- return new Promise((_resolve) => {
98
- const child = exec(
99
- straceCommand,
100
- {
101
- cwd: options.cwd ?? this.#workspaceRoot,
102
- env: { ...process.env, ...options.env },
103
- maxBuffer: 50 * 1024 * 1024
104
- // 50MB
105
- },
106
- async (_error, stdout, stderr) => {
107
- this.#activeProcesses.delete(child);
108
- let accesses = [];
109
- try {
110
- const traceContent = await readFile(traceFile, "utf8");
111
- accesses = this.#parseStraceOutput(traceContent, options.cwd ?? this.#workspaceRoot);
112
- } catch {
113
- }
114
- await rm(traceFile, { force: true }).catch(() => {
115
- });
116
- _resolve({
117
- accesses,
118
- code: child.exitCode ?? 1,
119
- output: stdout + stderr
120
- });
121
- }
122
- );
123
- this.#activeProcesses.add(child);
124
- });
125
- }
126
- /**
127
- * Parses strace output to extract file accesses.
128
- */
129
- #parseStraceOutput(traceContent, cwd) {
130
- const accesses = [];
131
- const seenPaths = /* @__PURE__ */ new Set();
132
- for (const line of traceContent.split("\n")) {
133
- const parsed = this.#parseStraceLine(line, cwd);
134
- if (parsed && !seenPaths.has(parsed.path)) {
135
- seenPaths.add(parsed.path);
136
- accesses.push(parsed);
137
- }
138
- }
139
- return accesses;
140
- }
141
- /**
142
- * Parses a single strace output line.
143
- * Each entry maps a regex to the file access type it represents.
144
- */
145
- #parseStraceLine(line, cwd) {
146
- const isMissing = line.includes("ENOENT");
147
- for (const { pattern, type } of STRACE_PATTERNS) {
148
- const match = pattern.exec(line);
149
- if (!match?.[1]) {
150
- continue;
151
- }
152
- let path = match[1];
153
- if (!path.startsWith("/")) {
154
- path = resolve(cwd, path);
155
- }
156
- if (this.#shouldExclude(path) || !path.startsWith(this.#workspaceRoot)) {
157
- return void 0;
158
- }
159
- return { path, type: isMissing ? "missing" : type };
160
- }
161
- return void 0;
162
- }
163
- /**
164
- * Checks if a path should be excluded from tracking.
165
- */
166
- #shouldExclude(filePath) {
167
- return this.#excludePatterns.some((pattern) => pattern.test(filePath));
168
- }
169
- /**
170
- * Kills all active child processes. Called on abort/signal to prevent orphans.
171
- */
172
- killAll() {
173
- for (const child of this.#activeProcesses) {
174
- try {
175
- child.kill("SIGTERM");
176
- } catch {
177
- }
178
- }
179
- this.#activeProcesses.clear();
180
- }
181
- /**
182
- * Runs a command without file access tracking.
183
- */
184
- async #runWithoutTracking(command, options) {
185
- return new Promise((_resolve) => {
186
- const child = exec(
187
- command,
188
- {
189
- cwd: options.cwd ?? this.#workspaceRoot,
190
- env: { ...process.env, ...options.env },
191
- maxBuffer: 50 * 1024 * 1024
192
- },
193
- (_error, stdout, stderr) => {
194
- this.#activeProcesses.delete(child);
195
- _resolve({
196
- accesses: [],
197
- code: child.exitCode ?? 1,
198
- output: stdout + stderr
199
- });
200
- }
201
- );
202
- this.#activeProcesses.add(child);
203
- });
204
- }
205
- }
206
- const generatePreloadScript = (outputPath) => String.raw`
207
- import { createWriteStream } from "node:fs";
208
-
209
- const fs = require("node:fs");
210
- const fsp = require("node:fs/promises");
211
- const logStream = createWriteStream(${JSON.stringify(outputPath)}, { flags: "a" });
212
- const log = (type, path) => { logStream.write(JSON.stringify({ type, path }) + "\n"); };
213
-
214
- // Patch each fs method: save original, replace with logged wrapper
215
- const patch = (obj, method, type) => {
216
- const orig = obj[method];
217
- obj[method] = function(...args) {
218
- log(type, args[0]?.toString());
219
- return orig.apply(this, args);
220
- };
221
- };
222
-
223
- // Sync
224
- patch(fs, "readFileSync", "read");
225
- patch(fs, "statSync", "stat");
226
- patch(fs, "readdirSync", "readdir");
227
- // Async (callback)
228
- patch(fs, "readFile", "read");
229
- patch(fs, "stat", "stat");
230
- patch(fs, "readdir", "readdir");
231
- // Async (promises)
232
- patch(fsp, "readFile", "read");
233
- patch(fsp, "stat", "stat");
234
- patch(fsp, "readdir", "readdir");
235
-
236
- process.on("beforeExit", () => { logStream.end(); });
237
- `;
238
-
239
- export { FileAccessTracker, generatePreloadScript };
@@ -1,227 +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
- readdir,
22
- stat
23
- } = __cjs_getBuiltinModule("node:fs/promises");
24
- import { resolve, relative } from '@visulima/path';
25
- import { b as hashStrings, s as sortObjectKeys, h as hashFile } from './utils-zO0ZRgtf.js';
26
-
27
- class FingerprintManager {
28
- #workspaceRoot;
29
- #fileHashCache = /* @__PURE__ */ new Map();
30
- constructor(workspaceRoot) {
31
- this.#workspaceRoot = resolve(workspaceRoot);
32
- }
33
- // eslint-disable-next-line sonarjs/cognitive-complexity
34
- async createFingerprint(accesses, command, args, envVariables, envPatterns = [], untrackedEnvVariables = []) {
35
- const fileHashes = {};
36
- const missingPaths = /* @__PURE__ */ new Set();
37
- const directoryListings = {};
38
- for (const access of accesses) {
39
- const relativePath = relative(this.#workspaceRoot, access.path);
40
- switch (access.type) {
41
- case "missing": {
42
- missingPaths.add(relativePath);
43
- break;
44
- }
45
- case "read":
46
- case "stat": {
47
- if (!fileHashes[relativePath]) {
48
- const hash = await this.#hashFileWithCache(access.path);
49
- if (hash) {
50
- fileHashes[relativePath] = hash;
51
- }
52
- }
53
- break;
54
- }
55
- case "readdir": {
56
- if (!directoryListings[relativePath]) {
57
- try {
58
- const entries = await readdir(access.path);
59
- directoryListings[relativePath] = entries.toSorted();
60
- } catch {
61
- directoryListings[relativePath] = [];
62
- }
63
- }
64
- break;
65
- }
66
- }
67
- }
68
- const commandHash = hashStrings(`${command}:${JSON.stringify(sortObjectKeys(args))}`);
69
- const envHashes = {};
70
- const matchedEnvVariables = FingerprintManager.#resolveEnvPatterns(envPatterns, envVariables);
71
- const untrackedSet = new Set(untrackedEnvVariables);
72
- for (const [key, value] of Object.entries(matchedEnvVariables)) {
73
- if (untrackedSet.has(key)) {
74
- continue;
75
- }
76
- envHashes[key] = hashStrings(`${key}=${value ?? ""}`);
77
- }
78
- const missingFiles = [...missingPaths].toSorted();
79
- return { commandHash, directoryListings, envHashes, fileHashes, missingFiles };
80
- }
81
- /**
82
- * Validates a stored fingerprint against the current state.
83
- * Returns null if valid (cache hit), or an array of reasons (cache miss).
84
- *
85
- * Does NOT use the file hash cache — validation must see current disk state.
86
- */
87
- async validate(fingerprint) {
88
- const reasons = [];
89
- const fileCheckPromises = Object.entries(fingerprint.fileHashes).map(async ([relativePath, previousHash]) => {
90
- const absolutePath = resolve(this.#workspaceRoot, relativePath);
91
- const currentHash = await hashFile(absolutePath);
92
- if (!currentHash) {
93
- return { detail: relativePath, previousHash, type: "file-deleted" };
94
- }
95
- if (currentHash !== previousHash) {
96
- return { currentHash, detail: relativePath, previousHash, type: "file-changed" };
97
- }
98
- return void 0;
99
- });
100
- const missingCheckPromises = fingerprint.missingFiles.map(async (relativePath) => {
101
- const absolutePath = resolve(this.#workspaceRoot, relativePath);
102
- try {
103
- await stat(absolutePath);
104
- return { detail: relativePath, type: "file-created" };
105
- } catch {
106
- return void 0;
107
- }
108
- });
109
- const directoryCheckPromises = Object.entries(fingerprint.directoryListings).map(async ([relativePath, previousEntries]) => {
110
- const absolutePath = resolve(this.#workspaceRoot, relativePath);
111
- try {
112
- const readdirResult = await readdir(absolutePath);
113
- const currentEntries = readdirResult.toSorted();
114
- if (JSON.stringify(previousEntries) !== JSON.stringify(currentEntries)) {
115
- return {
116
- currentHash: JSON.stringify(currentEntries),
117
- detail: relativePath,
118
- previousHash: JSON.stringify(previousEntries),
119
- type: "directory-changed"
120
- };
121
- }
122
- } catch {
123
- return { detail: relativePath, type: "directory-changed" };
124
- }
125
- return void 0;
126
- });
127
- const [fileResults, missingResults, directoryResults] = await Promise.all([
128
- Promise.all(fileCheckPromises),
129
- Promise.all(missingCheckPromises),
130
- Promise.all(directoryCheckPromises)
131
- ]);
132
- for (const r of [...fileResults, ...missingResults, ...directoryResults]) {
133
- if (r) {
134
- reasons.push(r);
135
- }
136
- }
137
- for (const [envName, previousHash] of Object.entries(fingerprint.envHashes)) {
138
- const currentHash = hashStrings(`${envName}=${process.env[envName] ?? ""}`);
139
- if (currentHash !== previousHash) {
140
- reasons.push({
141
- currentHash,
142
- detail: envName,
143
- previousHash,
144
- type: "env-changed"
145
- });
146
- }
147
- }
148
- return reasons.length > 0 ? reasons : void 0;
149
- }
150
- // eslint-disable-next-line class-methods-use-this
151
- validateCommand(fingerprint, command, args) {
152
- const currentHash = hashStrings(`${command}:${JSON.stringify(sortObjectKeys(args))}`);
153
- if (currentHash !== fingerprint.commandHash) {
154
- return {
155
- currentHash,
156
- detail: "command arguments",
157
- previousHash: fingerprint.commandHash,
158
- type: "args-changed"
159
- };
160
- }
161
- return void 0;
162
- }
163
- // eslint-disable-next-line class-methods-use-this
164
- formatMissReasons(reasons) {
165
- const lines = ["Cache miss reasons:"];
166
- for (const reason of reasons) {
167
- switch (reason.type) {
168
- case "args-changed": {
169
- lines.push(` - Command arguments changed`);
170
- break;
171
- }
172
- case "directory-changed": {
173
- lines.push(` - Directory contents changed: ${reason.detail}`);
174
- break;
175
- }
176
- case "env-changed": {
177
- lines.push(` - Environment variable changed: ${reason.detail}`);
178
- break;
179
- }
180
- case "file-changed": {
181
- lines.push(` - File modified: ${reason.detail}`);
182
- break;
183
- }
184
- case "file-created": {
185
- lines.push(` - File created (was missing): ${reason.detail}`);
186
- break;
187
- }
188
- case "file-deleted": {
189
- lines.push(` - File deleted: ${reason.detail}`);
190
- break;
191
- }
192
- case "no-fingerprint": {
193
- lines.push(` - No previous fingerprint found (first run)`);
194
- break;
195
- }
196
- }
197
- }
198
- return lines.join("\n");
199
- }
200
- static #resolveEnvPatterns(patterns, envVariables) {
201
- const result = {};
202
- for (const pattern of patterns) {
203
- if (pattern.includes("*")) {
204
- const prefix = pattern.replace("*", "");
205
- for (const [key, value] of Object.entries(envVariables)) {
206
- if (key.startsWith(prefix)) {
207
- result[key] = value;
208
- }
209
- }
210
- } else {
211
- result[pattern] = envVariables[pattern];
212
- }
213
- }
214
- return result;
215
- }
216
- async #hashFileWithCache(filePath) {
217
- const cached = this.#fileHashCache.get(filePath);
218
- if (cached !== void 0) {
219
- return cached;
220
- }
221
- const hash = await hashFile(filePath) ?? void 0;
222
- this.#fileHashCache.set(filePath, hash);
223
- return hash;
224
- }
225
- }
226
-
227
- export { FingerprintManager };
@@ -1,151 +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
- readFile,
22
- mkdir,
23
- writeFile,
24
- stat
25
- } = __cjs_getBuiltinModule("node:fs/promises");
26
- import { c as collectFiles, x as xxh3Hash } from './utils-zO0ZRgtf.js';
27
- import { join, dirname, relative } from '@visulima/path';
28
-
29
- const DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([".cache", ".git", ".task-runner-cache", "coverage", "dist", "node_modules"]);
30
- class IncrementalFileHasher {
31
- #workspaceRoot;
32
- #ignoredDirs;
33
- #snapshotPath;
34
- #snapshot = /* @__PURE__ */ new Map();
35
- #loaded = false;
36
- constructor(options) {
37
- this.#workspaceRoot = options.workspaceRoot;
38
- this.#ignoredDirs = options.ignoredDirs ?? DEFAULT_IGNORED_DIRS;
39
- this.#snapshotPath = options.snapshotPath ?? join(options.workspaceRoot, "node_modules", ".cache", "task-runner", "file-snapshot.json");
40
- }
41
- /**
42
- * Loads the snapshot from disk if available.
43
- * Called automatically on first use.
44
- */
45
- async load() {
46
- if (this.#loaded) {
47
- return;
48
- }
49
- this.#loaded = true;
50
- if (!this.#snapshotPath) {
51
- return;
52
- }
53
- try {
54
- const content = await readFile(this.#snapshotPath, "utf8");
55
- const data = JSON.parse(content);
56
- for (const [path, snap] of Object.entries(data)) {
57
- this.#snapshot.set(path, snap);
58
- }
59
- } catch {
60
- }
61
- }
62
- /**
63
- * Persists the current snapshot to disk for cross-run reuse.
64
- */
65
- async save() {
66
- if (!this.#snapshotPath) {
67
- return;
68
- }
69
- const directory = dirname(this.#snapshotPath);
70
- await mkdir(directory, { recursive: true });
71
- const data = {};
72
- for (const [path, snap] of this.#snapshot) {
73
- data[path] = snap;
74
- }
75
- await writeFile(this.#snapshotPath, JSON.stringify(data));
76
- }
77
- /**
78
- * Incrementally hashes all files in a directory.
79
- *
80
- * Only files whose mtime or size changed since the last snapshot
81
- * are re-read and re-hashed. Unchanged files reuse the cached hash.
82
- *
83
- * Returns a map of relative paths → hashes.
84
- */
85
- async hashDirectory(directoryPath) {
86
- await this.load();
87
- const result = {};
88
- const filePaths = await collectFiles(directoryPath, this.#ignoredDirs);
89
- const BATCH_SIZE = 64;
90
- const batches = [];
91
- for (let i = 0; i < filePaths.length; i += BATCH_SIZE) {
92
- batches.push(filePaths.slice(i, i + BATCH_SIZE));
93
- }
94
- for (const batch of batches) {
95
- const batchResults = await Promise.all(
96
- batch.map(async (filePath) => {
97
- const hash = await this.#hashFileIncremental(filePath);
98
- const relativePath = relative(this.#workspaceRoot, filePath);
99
- return { hash, relativePath };
100
- })
101
- );
102
- for (const { hash, relativePath } of batchResults) {
103
- if (hash) {
104
- result[relativePath] = hash;
105
- }
106
- }
107
- }
108
- return result;
109
- }
110
- /**
111
- * Hashes a single file incrementally.
112
- * Returns the cached hash if mtime + size haven't changed.
113
- */
114
- async #hashFileIncremental(filePath) {
115
- try {
116
- const fileStat = await stat(filePath);
117
- if (!fileStat.isFile()) {
118
- return void 0;
119
- }
120
- const cached = this.#snapshot.get(filePath);
121
- if (cached && cached.mtimeMs === fileStat.mtimeMs && cached.size === fileStat.size) {
122
- return cached.hash;
123
- }
124
- const content = await readFile(filePath);
125
- const hash = xxh3Hash(content);
126
- this.#snapshot.set(filePath, {
127
- hash,
128
- mtimeMs: fileStat.mtimeMs,
129
- size: fileStat.size
130
- });
131
- return hash;
132
- } catch {
133
- this.#snapshot.delete(filePath);
134
- return void 0;
135
- }
136
- }
137
- /**
138
- * Returns how many files are in the snapshot (for diagnostics).
139
- */
140
- get snapshotSize() {
141
- return this.#snapshot.size;
142
- }
143
- /**
144
- * Clears the in-memory snapshot.
145
- */
146
- clear() {
147
- this.#snapshot.clear();
148
- }
149
- }
150
-
151
- export { IncrementalFileHasher };