@visulima/task-runner 1.0.0-alpha.5 → 1.0.0-alpha.7
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 +48 -0
- package/dist/archive.d.ts +38 -0
- package/dist/cache.d.ts +31 -3
- package/dist/chrome-trace.d.ts +53 -0
- package/dist/file-access-tracker.d.ts +7 -1
- package/dist/fingerprint.d.ts +9 -0
- package/dist/incremental-hasher.d.ts +18 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.js +22 -19
- package/dist/life-cycle.d.ts +2 -0
- package/dist/log-reporter.d.ts +34 -0
- package/dist/output-resolver.d.ts +20 -0
- package/dist/packem_shared/{Cache-iAjRMV2d.js → Cache-CWaX_c8U.js} +135 -45
- package/dist/packem_shared/{CompositeLifeCycle-7AtYw1dv.js → CompositeLifeCycle-CSVbRC_5.js} +10 -0
- package/dist/packem_shared/{FileAccessTracker-CrtBAt5D.js → FileAccessTracker-CQ5Ot7Hd.js} +68 -16
- package/dist/packem_shared/{FingerprintManager-Cu-ta9ee.js → FingerprintManager-CV7U4f4f.js} +22 -1
- package/dist/packem_shared/{IncrementalFileHasher-Cm_kJY5V.js → IncrementalFileHasher-BRS76-mb.js} +26 -0
- package/dist/packem_shared/LogReporter-BDt52HLu.js +44 -0
- package/dist/packem_shared/{RemoteCache-BFceSe4a.js → RemoteCache-DSU3lc87.js} +77 -37
- package/dist/packem_shared/{TaskOrchestrator-lLn-PH1m.js → TaskOrchestrator-rf45vW5c.js} +94 -15
- package/dist/packem_shared/{TerminalBuffer-CnPyFgPB.js → TerminalBuffer-qVJvbRQZ.js} +1 -1
- package/dist/packem_shared/{TrackedTaskExecutor-BGUKFE-7.js → TrackedTaskExecutor-CFPpQfXF.js} +1 -1
- package/dist/packem_shared/archive-UQHAnZUa.js +102 -0
- package/dist/packem_shared/{buildForwardDependencyMap-0BJFMMPv.js → buildForwardDependencyMap-DLPgKEto.js} +2 -1
- package/dist/packem_shared/{computeTaskHash-B2SVZqgp.js → computeTaskHash-DYqfrDGq.js} +122 -6
- package/dist/packem_shared/{createTaskGraph-CcsFaSrz.js → createTaskGraph-B7nH0kY_.js} +2 -2
- package/dist/packem_shared/{defaultTaskRunner-BdFTifsh.js → defaultTaskRunner-Cp7jCmIl.js} +28 -6
- package/dist/packem_shared/{extractPackageName-CbVNW-dr.js → extractPackageName-BllKetnz.js} +2 -1
- package/dist/packem_shared/{generateRunSummary-qn-_jKwt.js → generateRunSummary-BE1jnQ3H.js} +19 -1
- package/dist/packem_shared/{parsePartition-C4-P5RjK.js → parsePartition-BfLbHGAx.js} +18 -0
- package/dist/packem_shared/{projectGraphToDot-C8uYeaPo.js → projectGraphToDot-DU1lSe-c.js} +1 -1
- package/dist/packem_shared/resolveOutputs-n6MCKoTe.js +111 -0
- package/dist/packem_shared/{runConcurrentFallback-CGHz_f-Q.js → runConcurrentFallback-BTmgGV1H.js} +1 -1
- package/dist/packem_shared/{runConcurrently-qrkWyzXW.js → runConcurrently-CmfC4r-f.js} +1 -1
- package/dist/packem_shared/toChromeTrace-B2tZoJ-7.js +121 -0
- package/dist/remote-cache.d.ts +45 -0
- package/dist/run-summary.d.ts +26 -4
- package/dist/task-hasher.d.ts +37 -0
- package/dist/task-orchestrator.d.ts +2 -2
- package/dist/types.d.ts +137 -3
- package/index.js +52 -52
- package/package.json +13 -13
|
@@ -78,9 +78,9 @@ const getDependencyProjectTasks = (projectName, targetName, overrides, workspace
|
|
|
78
78
|
const resolveStringDependency = (task, dep, workspace, projectGraph, targetDefaults) => {
|
|
79
79
|
if (dep.startsWith("^")) {
|
|
80
80
|
const targetName = dep.slice(1);
|
|
81
|
-
return getDependencyProjectTasks(task.target.project, targetName,
|
|
81
|
+
return getDependencyProjectTasks(task.target.project, targetName, {}, workspace, projectGraph, targetDefaults);
|
|
82
82
|
}
|
|
83
|
-
return getSameProjectTask(task.target.project, dep,
|
|
83
|
+
return getSameProjectTask(task.target.project, dep, {}, workspace, targetDefaults);
|
|
84
84
|
};
|
|
85
85
|
const resolveConfigDependency = (task, dep, workspace, projectGraph, targetDefaults) => {
|
|
86
86
|
const tasks = [];
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { x as xxh3Hash } from './utils-zO0ZRgtf.js';
|
|
2
|
+
import { Cache } from './Cache-CWaX_c8U.js';
|
|
2
3
|
import { inferFrameworkEnvPatterns } from './detectFrameworks-CeFzKE6J.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { IncrementalFileHasher } from './IncrementalFileHasher-BRS76-mb.js';
|
|
5
|
+
import { RemoteCache } from './RemoteCache-DSU3lc87.js';
|
|
6
|
+
import { InProcessTaskHasher } from './computeTaskHash-DYqfrDGq.js';
|
|
7
|
+
import { TaskOrchestrator } from './TaskOrchestrator-rf45vW5c.js';
|
|
8
|
+
import { TaskScheduler } from './parsePartition-BfLbHGAx.js';
|
|
7
9
|
|
|
10
|
+
const computeGlobalEnvNamespace = (globalEnv) => {
|
|
11
|
+
if (!globalEnv || globalEnv.length === 0) {
|
|
12
|
+
return void 0;
|
|
13
|
+
}
|
|
14
|
+
const payload = [...globalEnv].sort().map((name) => `${name}=${process.env[name] ?? ""}`).join("\n");
|
|
15
|
+
return xxh3Hash(Buffer.from(payload)).slice(0, 16);
|
|
16
|
+
};
|
|
8
17
|
const resolveParallel = (parallel) => {
|
|
9
18
|
if (typeof parallel === "number") {
|
|
10
19
|
return Math.max(1, parallel);
|
|
@@ -16,8 +25,10 @@ const resolveParallel = (parallel) => {
|
|
|
16
25
|
};
|
|
17
26
|
const defaultTaskRunner = async (_tasks, options, context) => {
|
|
18
27
|
const { lifeCycle, projectGraph, taskExecutor, taskGraph, workspaceRoot } = context;
|
|
28
|
+
const cacheNamespace = options.namespaceByGlobalEnv ? computeGlobalEnvNamespace(options.globalEnv) : void 0;
|
|
19
29
|
const cache = new Cache({
|
|
20
30
|
cacheDirectory: options.cacheDirectory,
|
|
31
|
+
cacheNamespace,
|
|
21
32
|
maxCacheAge: options.maxCacheAge,
|
|
22
33
|
maxCacheSize: options.maxCacheSize,
|
|
23
34
|
workspaceRoot
|
|
@@ -28,11 +39,17 @@ const defaultTaskRunner = async (_tasks, options, context) => {
|
|
|
28
39
|
for (const [name, node] of Object.entries(projectGraph.nodes)) {
|
|
29
40
|
projects[name] = node.data;
|
|
30
41
|
}
|
|
42
|
+
const incrementalHasher = options.incrementalFileHashing ? new IncrementalFileHasher({ workspaceRoot }) : void 0;
|
|
43
|
+
if (incrementalHasher) {
|
|
44
|
+
await incrementalHasher.load();
|
|
45
|
+
}
|
|
31
46
|
const taskHasher = new InProcessTaskHasher({
|
|
47
|
+
autoEnvVars: options.autoEnvVars,
|
|
32
48
|
envVars: options.envVars,
|
|
33
49
|
frameworkInference: options.frameworkInference,
|
|
34
50
|
globalEnv: options.globalEnv,
|
|
35
51
|
globalInputs: options.globalInputs,
|
|
52
|
+
incrementalHasher,
|
|
36
53
|
namedInputs: options.namedInputs,
|
|
37
54
|
projects,
|
|
38
55
|
smartLockfileHashing: options.smartLockfileHashing,
|
|
@@ -72,7 +89,12 @@ const defaultTaskRunner = async (_tasks, options, context) => {
|
|
|
72
89
|
untrackedEnvVars: options.untrackedEnvVars,
|
|
73
90
|
workspaceRoot
|
|
74
91
|
});
|
|
75
|
-
|
|
92
|
+
const results = await orchestrator.run();
|
|
93
|
+
if (incrementalHasher) {
|
|
94
|
+
await incrementalHasher.save().catch(() => {
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return results;
|
|
76
98
|
};
|
|
77
99
|
|
|
78
100
|
export { defaultTaskRunner };
|
package/dist/packem_shared/{extractPackageName-CbVNW-dr.js → extractPackageName-BllKetnz.js}
RENAMED
|
@@ -44,7 +44,8 @@ const parseNpmLockfile = (content) => {
|
|
|
44
44
|
continue;
|
|
45
45
|
}
|
|
46
46
|
const name = extractPackageName(path);
|
|
47
|
-
if (name &&
|
|
47
|
+
if (name && // Use the first (top-level) occurrence
|
|
48
|
+
!versions.has(name)) {
|
|
48
49
|
versions.set(name, entry.version);
|
|
49
50
|
}
|
|
50
51
|
}
|
package/dist/packem_shared/{generateRunSummary-qn-_jKwt.js → generateRunSummary-BE1jnQ3H.js}
RENAMED
|
@@ -18,6 +18,7 @@ const __cjs_getBuiltinModule = (module) => {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
const {
|
|
21
|
+
readFile,
|
|
21
22
|
mkdir,
|
|
22
23
|
writeFile
|
|
23
24
|
} = __cjs_getBuiltinModule("node:fs/promises");
|
|
@@ -130,5 +131,22 @@ const writeRunSummary = async (summary, workspaceRoot) => {
|
|
|
130
131
|
await writeFile(filePath, JSON.stringify(summary, void 0, 2));
|
|
131
132
|
return filePath;
|
|
132
133
|
};
|
|
134
|
+
const LAST_SUMMARY_FILE = "last-summary.json";
|
|
135
|
+
const getLastRunSummaryPath = (workspaceRoot) => join(workspaceRoot, ".task-runner", LAST_SUMMARY_FILE);
|
|
136
|
+
const writeLastRunSummary = async (summary, workspaceRoot) => {
|
|
137
|
+
const cacheDirectory = join(workspaceRoot, ".task-runner");
|
|
138
|
+
await mkdir(cacheDirectory, { recursive: true });
|
|
139
|
+
const filePath = getLastRunSummaryPath(workspaceRoot);
|
|
140
|
+
await writeFile(filePath, JSON.stringify(summary, void 0, 2));
|
|
141
|
+
return filePath;
|
|
142
|
+
};
|
|
143
|
+
const readLastRunSummary = async (workspaceRoot) => {
|
|
144
|
+
try {
|
|
145
|
+
const content = await readFile(getLastRunSummaryPath(workspaceRoot), "utf8");
|
|
146
|
+
return JSON.parse(content);
|
|
147
|
+
} catch {
|
|
148
|
+
return void 0;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
133
151
|
|
|
134
|
-
export { generateRunSummary, writeRunSummary };
|
|
152
|
+
export { generateRunSummary, getLastRunSummaryPath, readLastRunSummary, writeLastRunSummary, writeRunSummary };
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
const taskPriorityWeight = (priority) => {
|
|
2
|
+
switch (priority) {
|
|
3
|
+
case "high": {
|
|
4
|
+
return 2;
|
|
5
|
+
}
|
|
6
|
+
case "low": {
|
|
7
|
+
return 0;
|
|
8
|
+
}
|
|
9
|
+
default: {
|
|
10
|
+
return 1;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|
|
1
14
|
const parsePartition = (value) => {
|
|
2
15
|
const raw = value ?? process.env.VIS_PARTITION;
|
|
3
16
|
if (!raw) {
|
|
@@ -124,6 +137,11 @@ class TaskScheduler {
|
|
|
124
137
|
}
|
|
125
138
|
#sortByPriority(tasks) {
|
|
126
139
|
return [...tasks].toSorted((a, b) => {
|
|
140
|
+
const aPriority = taskPriorityWeight(a.priority);
|
|
141
|
+
const bPriority = taskPriorityWeight(b.priority);
|
|
142
|
+
if (aPriority !== bPriority) {
|
|
143
|
+
return bPriority - aPriority;
|
|
144
|
+
}
|
|
127
145
|
const aDeps = this.#dependentCounts.get(a.id) ?? 0;
|
|
128
146
|
const bDeps = this.#dependentCounts.get(b.id) ?? 0;
|
|
129
147
|
if (aDeps !== bDeps) {
|
|
@@ -153,7 +153,7 @@ const toGraphHtml = (taskGraph, options = {}) => {
|
|
|
153
153
|
graphData.edges = graphData.edges.filter((e) => focused.has(e.source) && focused.has(e.target));
|
|
154
154
|
graphData.roots = graphData.roots.filter((r) => focused.has(r));
|
|
155
155
|
}
|
|
156
|
-
return String.raw(_a || (_a = __template(['<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<title>Task Graph</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: system-ui, -apple-system, sans-serif; background: #1a1a2e; color: #eee; }\n svg { width: 100vw; height: 100vh; }\n .node rect { rx: 6; ry: 6; stroke: #555; stroke-width: 1.5; cursor: pointer; }\n .node text { font-size: 11px; fill: #1a1a2e; font-weight: 600; pointer-events: none; }\n .node:hover rect { stroke: #fff; stroke-width: 2; }\n .edge { stroke: #444; stroke-width: 1.5; fill: none; marker-end: url(#arrow); }\n .label { font-size: 10px; fill: #888; }\n #info { position: fixed; top: 12px; right: 12px; background: #16213e; padding: 12px 16px; border-radius: 8px; font-size: 13px; }\n #info b { color: #e94560; }\n</style>\n</head>\n<body>\n<div id="info">\n <b>', "</b> tasks · <b>", "</b> dependencies · <b>", '</b> roots\n</div>\n<svg id="graph">\n <defs>\n <marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto">\n <path d="M 0 0 L 10 5 L 0 10 z" fill="#666"/>\n </marker>\n </defs>\n</svg>\n<script>\nconst data = ', ";\nconst svg = document.getElementById('graph');\nconst W = window.innerWidth, H = window.innerHeight;\nconst statusColors = {\n success: '#2ecc71', 'local-cache': '#3498db', 'remote-cache': '#9b59b6',\n failure: '#e74c3c', running: '#f39c12', skipped: '#95a5a6', pending: '#ecf0f1'\n};\nconst projectColors = {};\nconst palette = ['#e94560','#0f3460','#533483','#16c79a','#f39c12','#2ecc71','#3498db','#e67e22','#9b59b6','#1abc9c'];\nlet ci = 0;\ndata.nodes.forEach(n => {\n if (!projectColors[n.project]) projectColors[n.project] = palette[ci++ % palette.length];\n});\n\n// Simple force-directed layout\nconst nodes = data.nodes.map((n, i) => ({\n ...n, x: W/2 + (Math.random()-0.5)*400, y: H/2 + (Math.random()-0.5)*300, vx: 0, vy: 0\n}));\nconst nodeMap = new Map(nodes.map(n => [n.id, n]));\nconst edges = data.edges.map(e => ({ source: nodeMap.get(e.source), target: nodeMap.get(e.target) }));\n\nfunction simulate() {\n for (let iter = 0; iter < 300; iter++) {\n // Repulsion\n for (let i = 0; i < nodes.length; i++) {\n for (let j = i+1; j < nodes.length; j++) {\n let dx = nodes[j].x - nodes[i].x, dy = nodes[j].y - nodes[i].y;\n let d = Math.sqrt(dx*dx + dy*dy) || 1;\n let f = 8000 / (d * d);\n nodes[i].vx -= dx/d * f; nodes[i].vy -= dy/d * f;\n nodes[j].vx += dx/d * f; nodes[j].vy += dy/d * f;\n }\n }\n // Attraction (edges)\n edges.forEach(e => {\n if (!e.source || !e.target) {\n return;\n }\n let dx = e.target.x - e.source.x, dy = e.target.y - e.source.y;\n let d = Math.sqrt(dx*dx + dy*dy) || 1;\n let f = (d - 150) * 0.01;\n e.source.vx += dx/d * f; e.source.vy += dy/d * f;\n e.target.vx -= dx/d * f; e.target.vy -= dy/d * f;\n });\n // Gravity\n nodes.forEach(n => {\n n.vx += (W/2 - n.x) * 0.001;\n n.vy += (H/2 - n.y) * 0.001;\n n.x += n.vx * 0.3; n.y += n.vy * 0.3;\n n.vx *= 0.8; n.vy *= 0.8;\n n.x = Math.max(60, Math.min(W-60, n.x));\n n.y = Math.max(30, Math.min(H-30, n.y));\n });\n }\n}\nsimulate();\n\n// Render\nedges.forEach(e => {\n if (!e.source || !e.target) {\n return;\n }\n const line = document.createElementNS('http://www.w3.org/2000/svg','line');\n line.setAttribute('x1', e.source.x); line.setAttribute('y1', e.source.y);\n line.setAttribute('x2', e.target.x); line.setAttribute('y2', e.target.y);\n line.setAttribute('class','edge');\n svg.appendChild(line);\n});\nnodes.forEach(n => {\n const g = document.createElementNS('http://www.w3.org/2000/svg','g');\n g.setAttribute('class','node');\n g.setAttribute('transform','translate('+(n.x-50)+','+(n.y-14)+')');\n const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');\n rect.setAttribute('width','100'); rect.setAttribute('height','28');\n rect.setAttribute('fill', n.status ? (statusColors[n.status]||'#ecf0f1') : projectColors[n.project]);\n g.appendChild(rect);\n const text = document.createElementNS('http://www.w3.org/2000/svg','text');\n text.setAttribute('x','50'); text.setAttribute('y','18'); text.setAttribute('text-anchor','middle');\n text.textContent = n.id.length > 14 ? n.target : n.id;\n g.appendChild(text);\n g.addEventListener('click', () => {\n const deps = data.edges.filter(e => e.source === n.id).map(e => e.target);\n const rdeps = data.edges.filter(e => e.target === n.id).map(e => e.source);\n alert(n.id + '\n\nDepends on: ' + (deps.join(', ')||'none') + '\nRequired by: ' + (rdeps.join(', ')||'none'));\n });\n svg.appendChild(g);\n});\n<\/script>\n</body>\n</html>"], ['<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<title>Task Graph</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: system-ui, -apple-system, sans-serif; background: #1a1a2e; color: #eee; }\n svg { width: 100vw; height: 100vh; }\n .node rect { rx: 6; ry: 6; stroke: #555; stroke-width: 1.5; cursor: pointer; }\n .node text { font-size: 11px; fill: #1a1a2e; font-weight: 600; pointer-events: none; }\n .node:hover rect { stroke: #fff; stroke-width: 2; }\n .edge { stroke: #444; stroke-width: 1.5; fill: none; marker-end: url(#arrow); }\n .label { font-size: 10px; fill: #888; }\n #info { position: fixed; top: 12px; right: 12px; background: #16213e; padding: 12px 16px; border-radius: 8px; font-size: 13px; }\n #info b { color: #e94560; }\n</style>\n</head>\n<body>\n<div id="info">\n <b>', "</b> tasks · <b>", "</b> dependencies · <b>", '</b> roots\n</div>\n<svg id="graph">\n <defs>\n <marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto">\n <path d="M 0 0 L 10 5 L 0 10 z" fill="#666"/>\n </marker>\n </defs>\n</svg>\n<script>\nconst data = ', ";\nconst svg = document.getElementById('graph');\nconst W = window.innerWidth, H = window.innerHeight;\nconst statusColors = {\n success: '#2ecc71', 'local-cache': '#3498db', 'remote-cache': '#9b59b6',\n failure: '#e74c3c', running: '#f39c12', skipped: '#95a5a6', pending: '#ecf0f1'\n};\nconst projectColors = {};\nconst palette = ['#e94560','#0f3460','#533483','#16c79a','#f39c12','#2ecc71','#3498db','#e67e22','#9b59b6','#1abc9c'];\nlet ci = 0;\ndata.nodes.forEach(n => {\n if (!projectColors[n.project]) projectColors[n.project] = palette[ci++ % palette.length];\n});\n\n// Simple force-directed layout\nconst nodes = data.nodes.map((n, i) => ({\n ...n, x: W/2 + (Math.random()-0.5)*400, y: H/2 + (Math.random()-0.5)*300, vx: 0, vy: 0\n}));\nconst nodeMap = new Map(nodes.map(n => [n.id, n]));\nconst edges = data.edges.map(e => ({ source: nodeMap.get(e.source), target: nodeMap.get(e.target) }));\n\nfunction simulate() {\n for (let iter = 0; iter < 300; iter++) {\n // Repulsion\n for (let i = 0; i < nodes.length; i++) {\n for (let j = i+1; j < nodes.length; j++) {\n let dx = nodes[j].x - nodes[i].x, dy = nodes[j].y - nodes[i].y;\n let d = Math.sqrt(dx*dx + dy*dy) || 1;\n let f = 8000 / (d * d);\n nodes[i].vx -= dx/d * f; nodes[i].vy -= dy/d * f;\n nodes[j].vx += dx/d * f; nodes[j].vy += dy/d * f;\n }\n }\n // Attraction (edges)\n edges.forEach(e => {\n if (!e.source || !e.target) {\n return;\n }\n let dx = e.target.x - e.source.x, dy = e.target.y - e.source.y;\n let d = Math.sqrt(dx*dx + dy*dy) || 1;\n let f = (d - 150) * 0.01;\n e.source.vx += dx/d * f; e.source.vy += dy/d * f;\n e.target.vx -= dx/d * f; e.target.vy -= dy/d * f;\n });\n // Gravity\n nodes.forEach(n => {\n n.vx += (W/2 - n.x) * 0.001;\n n.vy += (H/2 - n.y) * 0.001;\n n.x += n.vx * 0.3; n.y += n.vy * 0.3;\n n.vx *= 0.8; n.vy *= 0.8;\n n.x = Math.max(60, Math.min(W-60, n.x));\n n.y = Math.max(30, Math.min(H-30, n.y));\n });\n }\n}\nsimulate();\n\n// Render\nedges.forEach(e => {\n if (!e.source || !e.target) {\n return;\n }\n const line = document.createElementNS('http://www.w3.org/2000/svg','line');\n line.setAttribute('x1', e.source.x); line.setAttribute('y1', e.source.y);\n line.setAttribute('x2', e.target.x); line.setAttribute('y2', e.target.y);\n line.setAttribute('class','edge');\n svg.appendChild(line);\n});\nnodes.forEach(n => {\n const g = document.createElementNS('http://www.w3.org/2000/svg','g');\n g.setAttribute('class','node');\n g.setAttribute('transform','translate('+(n.x-50)+','+(n.y-14)+')');\n const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');\n rect.setAttribute('width','100'); rect.setAttribute('height','28');\n rect.setAttribute('fill', n.status ? (statusColors[n.status]||'#ecf0f1') : projectColors[n.project]);\n g.appendChild(rect);\n const text = document.createElementNS('http://www.w3.org/2000/svg','text');\n text.setAttribute('x','50'); text.setAttribute('y','18'); text.setAttribute('text-anchor','middle');\n text.textContent = n.id.length > 14 ? n.target : n.id;\n g.appendChild(text);\n g.addEventListener('click', () => {\n const deps = data.edges.filter(e => e.source === n.id).map(e => e.target);\n const rdeps = data.edges.filter(e => e.target === n.id).map(e => e.source);\n alert(n.id + '\\n\\nDepends on: ' + (deps.join(', ')||'none') + '\\nRequired by: ' + (rdeps.join(', ')||'none'));\n });\n svg.appendChild(g);\n});\n<\/script>\n</body>\n</html>"])), graphData.nodes.length, graphData.edges.length, graphData.roots.length, JSON.stringify(graphData).replaceAll("</", String.raw`<\/`));
|
|
156
|
+
return String.raw(_a || (_a = __template(['<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<title>Task Graph</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: system-ui, -apple-system, sans-serif; background: #1a1a2e; color: #eee; }\n svg { width: 100vw; height: 100vh; }\n .node rect { rx: 6; ry: 6; stroke: #555; stroke-width: 1.5; cursor: pointer; }\n .node text { font-size: 11px; fill: #1a1a2e; font-weight: 600; pointer-events: none; }\n .node:hover rect { stroke: #fff; stroke-width: 2; }\n .edge { stroke: #444; stroke-width: 1.5; fill: none; marker-end: url(#arrow); }\n .label { font-size: 10px; fill: #888; }\n #info { position: fixed; top: 12px; right: 12px; background: #16213e; padding: 12px 16px; border-radius: 8px; font-size: 13px; }\n #info b { color: #e94560; }\n</style>\n</head>\n<body>\n<div id="info">\n <b>', "</b> tasks · <b>", "</b> dependencies · <b>", '</b> roots\n</div>\n<svg id="graph">\n <defs>\n <marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto">\n <path d="M 0 0 L 10 5 L 0 10 z" fill="#666"/>\n </marker>\n </defs>\n</svg>\n<script>\nconst data = ', ";\nconst svg = document.getElementById('graph');\nconst W = window.innerWidth, H = window.innerHeight;\nconst statusColors = {\n success: '#2ecc71', 'local-cache': '#3498db', 'remote-cache': '#9b59b6',\n failure: '#e74c3c', running: '#f39c12', skipped: '#95a5a6', pending: '#ecf0f1'\n};\nconst projectColors = {};\nconst palette = ['#e94560','#0f3460','#533483','#16c79a','#f39c12','#2ecc71','#3498db','#e67e22','#9b59b6','#1abc9c'];\nlet ci = 0;\ndata.nodes.forEach(n => {\n if (!projectColors[n.project]) {\n projectColors[n.project] = palette[ci++ % palette.length];\n }\n});\n\n// Simple force-directed layout\nconst nodes = data.nodes.map((n, i) => ({\n ...n, x: W/2 + (Math.random()-0.5)*400, y: H/2 + (Math.random()-0.5)*300, vx: 0, vy: 0\n}));\nconst nodeMap = new Map(nodes.map(n => [n.id, n]));\nconst edges = data.edges.map(e => ({ source: nodeMap.get(e.source), target: nodeMap.get(e.target) }));\n\nfunction simulate() {\n for (let iter = 0; iter < 300; iter++) {\n // Repulsion\n for (let i = 0; i < nodes.length; i++) {\n for (let j = i+1; j < nodes.length; j++) {\n let dx = nodes[j].x - nodes[i].x, dy = nodes[j].y - nodes[i].y;\n let d = Math.sqrt(dx*dx + dy*dy) || 1;\n let f = 8000 / (d * d);\n nodes[i].vx -= dx/d * f; nodes[i].vy -= dy/d * f;\n nodes[j].vx += dx/d * f; nodes[j].vy += dy/d * f;\n }\n }\n // Attraction (edges)\n edges.forEach(e => {\n if (!e.source || !e.target) {\n return;\n }\n let dx = e.target.x - e.source.x, dy = e.target.y - e.source.y;\n let d = Math.sqrt(dx*dx + dy*dy) || 1;\n let f = (d - 150) * 0.01;\n e.source.vx += dx/d * f; e.source.vy += dy/d * f;\n e.target.vx -= dx/d * f; e.target.vy -= dy/d * f;\n });\n // Gravity\n nodes.forEach(n => {\n n.vx += (W/2 - n.x) * 0.001;\n n.vy += (H/2 - n.y) * 0.001;\n n.x += n.vx * 0.3; n.y += n.vy * 0.3;\n n.vx *= 0.8; n.vy *= 0.8;\n n.x = Math.max(60, Math.min(W-60, n.x));\n n.y = Math.max(30, Math.min(H-30, n.y));\n });\n }\n}\nsimulate();\n\n// Render\nedges.forEach(e => {\n if (!e.source || !e.target) {\n return;\n }\n const line = document.createElementNS('http://www.w3.org/2000/svg','line');\n line.setAttribute('x1', e.source.x); line.setAttribute('y1', e.source.y);\n line.setAttribute('x2', e.target.x); line.setAttribute('y2', e.target.y);\n line.setAttribute('class','edge');\n svg.appendChild(line);\n});\nnodes.forEach(n => {\n const g = document.createElementNS('http://www.w3.org/2000/svg','g');\n g.setAttribute('class','node');\n g.setAttribute('transform','translate('+(n.x-50)+','+(n.y-14)+')');\n const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');\n rect.setAttribute('width','100'); rect.setAttribute('height','28');\n rect.setAttribute('fill', n.status ? (statusColors[n.status]||'#ecf0f1') : projectColors[n.project]);\n g.appendChild(rect);\n const text = document.createElementNS('http://www.w3.org/2000/svg','text');\n text.setAttribute('x','50'); text.setAttribute('y','18'); text.setAttribute('text-anchor','middle');\n text.textContent = n.id.length > 14 ? n.target : n.id;\n g.appendChild(text);\n g.addEventListener('click', () => {\n const deps = data.edges.filter(e => e.source === n.id).map(e => e.target);\n const rdeps = data.edges.filter(e => e.target === n.id).map(e => e.source);\n alert(n.id + '\n\nDepends on: ' + (deps.join(', ')||'none') + '\nRequired by: ' + (rdeps.join(', ')||'none'));\n });\n svg.appendChild(g);\n});\n<\/script>\n</body>\n</html>"], ['<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<title>Task Graph</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: system-ui, -apple-system, sans-serif; background: #1a1a2e; color: #eee; }\n svg { width: 100vw; height: 100vh; }\n .node rect { rx: 6; ry: 6; stroke: #555; stroke-width: 1.5; cursor: pointer; }\n .node text { font-size: 11px; fill: #1a1a2e; font-weight: 600; pointer-events: none; }\n .node:hover rect { stroke: #fff; stroke-width: 2; }\n .edge { stroke: #444; stroke-width: 1.5; fill: none; marker-end: url(#arrow); }\n .label { font-size: 10px; fill: #888; }\n #info { position: fixed; top: 12px; right: 12px; background: #16213e; padding: 12px 16px; border-radius: 8px; font-size: 13px; }\n #info b { color: #e94560; }\n</style>\n</head>\n<body>\n<div id="info">\n <b>', "</b> tasks · <b>", "</b> dependencies · <b>", '</b> roots\n</div>\n<svg id="graph">\n <defs>\n <marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto">\n <path d="M 0 0 L 10 5 L 0 10 z" fill="#666"/>\n </marker>\n </defs>\n</svg>\n<script>\nconst data = ', ";\nconst svg = document.getElementById('graph');\nconst W = window.innerWidth, H = window.innerHeight;\nconst statusColors = {\n success: '#2ecc71', 'local-cache': '#3498db', 'remote-cache': '#9b59b6',\n failure: '#e74c3c', running: '#f39c12', skipped: '#95a5a6', pending: '#ecf0f1'\n};\nconst projectColors = {};\nconst palette = ['#e94560','#0f3460','#533483','#16c79a','#f39c12','#2ecc71','#3498db','#e67e22','#9b59b6','#1abc9c'];\nlet ci = 0;\ndata.nodes.forEach(n => {\n if (!projectColors[n.project]) {\n projectColors[n.project] = palette[ci++ % palette.length];\n }\n});\n\n// Simple force-directed layout\nconst nodes = data.nodes.map((n, i) => ({\n ...n, x: W/2 + (Math.random()-0.5)*400, y: H/2 + (Math.random()-0.5)*300, vx: 0, vy: 0\n}));\nconst nodeMap = new Map(nodes.map(n => [n.id, n]));\nconst edges = data.edges.map(e => ({ source: nodeMap.get(e.source), target: nodeMap.get(e.target) }));\n\nfunction simulate() {\n for (let iter = 0; iter < 300; iter++) {\n // Repulsion\n for (let i = 0; i < nodes.length; i++) {\n for (let j = i+1; j < nodes.length; j++) {\n let dx = nodes[j].x - nodes[i].x, dy = nodes[j].y - nodes[i].y;\n let d = Math.sqrt(dx*dx + dy*dy) || 1;\n let f = 8000 / (d * d);\n nodes[i].vx -= dx/d * f; nodes[i].vy -= dy/d * f;\n nodes[j].vx += dx/d * f; nodes[j].vy += dy/d * f;\n }\n }\n // Attraction (edges)\n edges.forEach(e => {\n if (!e.source || !e.target) {\n return;\n }\n let dx = e.target.x - e.source.x, dy = e.target.y - e.source.y;\n let d = Math.sqrt(dx*dx + dy*dy) || 1;\n let f = (d - 150) * 0.01;\n e.source.vx += dx/d * f; e.source.vy += dy/d * f;\n e.target.vx -= dx/d * f; e.target.vy -= dy/d * f;\n });\n // Gravity\n nodes.forEach(n => {\n n.vx += (W/2 - n.x) * 0.001;\n n.vy += (H/2 - n.y) * 0.001;\n n.x += n.vx * 0.3; n.y += n.vy * 0.3;\n n.vx *= 0.8; n.vy *= 0.8;\n n.x = Math.max(60, Math.min(W-60, n.x));\n n.y = Math.max(30, Math.min(H-30, n.y));\n });\n }\n}\nsimulate();\n\n// Render\nedges.forEach(e => {\n if (!e.source || !e.target) {\n return;\n }\n const line = document.createElementNS('http://www.w3.org/2000/svg','line');\n line.setAttribute('x1', e.source.x); line.setAttribute('y1', e.source.y);\n line.setAttribute('x2', e.target.x); line.setAttribute('y2', e.target.y);\n line.setAttribute('class','edge');\n svg.appendChild(line);\n});\nnodes.forEach(n => {\n const g = document.createElementNS('http://www.w3.org/2000/svg','g');\n g.setAttribute('class','node');\n g.setAttribute('transform','translate('+(n.x-50)+','+(n.y-14)+')');\n const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');\n rect.setAttribute('width','100'); rect.setAttribute('height','28');\n rect.setAttribute('fill', n.status ? (statusColors[n.status]||'#ecf0f1') : projectColors[n.project]);\n g.appendChild(rect);\n const text = document.createElementNS('http://www.w3.org/2000/svg','text');\n text.setAttribute('x','50'); text.setAttribute('y','18'); text.setAttribute('text-anchor','middle');\n text.textContent = n.id.length > 14 ? n.target : n.id;\n g.appendChild(text);\n g.addEventListener('click', () => {\n const deps = data.edges.filter(e => e.source === n.id).map(e => e.target);\n const rdeps = data.edges.filter(e => e.target === n.id).map(e => e.source);\n alert(n.id + '\\n\\nDepends on: ' + (deps.join(', ')||'none') + '\\nRequired by: ' + (rdeps.join(', ')||'none'));\n });\n svg.appendChild(g);\n});\n<\/script>\n</body>\n</html>"])), graphData.nodes.length, graphData.edges.length, graphData.roots.length, JSON.stringify(graphData).replaceAll("</", String.raw`<\/`));
|
|
157
157
|
};
|
|
158
158
|
const toGraphAscii = (taskGraph, options = {}) => {
|
|
159
159
|
const { taskStatuses } = options;
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
stat,
|
|
22
|
+
glob
|
|
23
|
+
} = __cjs_getBuiltinModule("node:fs/promises");
|
|
24
|
+
const {
|
|
25
|
+
matchesGlob
|
|
26
|
+
} = __cjs_getBuiltinModule("node:path");
|
|
27
|
+
import { isAbsolute, resolve, relative, join } from '@visulima/path';
|
|
28
|
+
|
|
29
|
+
const GLOB_METACHARS = /[*?[\]{}]/;
|
|
30
|
+
const toWorkspaceRelative = (workspaceRoot, absolutePath) => {
|
|
31
|
+
const rel = relative(workspaceRoot, absolutePath);
|
|
32
|
+
if (rel.length === 0 || rel.startsWith("..")) {
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
return rel;
|
|
36
|
+
};
|
|
37
|
+
const resolveOutputs = async (workspaceRoot, outputs, autoWrites) => {
|
|
38
|
+
if (!outputs || outputs.length === 0) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
const positives = [];
|
|
42
|
+
const negatives = [];
|
|
43
|
+
let wantsAuto = false;
|
|
44
|
+
for (const entry of outputs) {
|
|
45
|
+
if (typeof entry !== "string") {
|
|
46
|
+
if (entry.auto) {
|
|
47
|
+
wantsAuto = true;
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (entry.length === 0) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (entry.startsWith("!")) {
|
|
55
|
+
const pattern = entry.slice(1);
|
|
56
|
+
if (pattern.length > 0) {
|
|
57
|
+
negatives.push(pattern);
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
positives.push(entry);
|
|
62
|
+
}
|
|
63
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
64
|
+
if (wantsAuto && autoWrites) {
|
|
65
|
+
for (const write of autoWrites) {
|
|
66
|
+
const absolute = isAbsolute(write) ? write : resolve(workspaceRoot, write);
|
|
67
|
+
const rel = toWorkspaceRelative(workspaceRoot, absolute);
|
|
68
|
+
if (rel) {
|
|
69
|
+
resolved.add(rel);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const patternResults = await Promise.all(positives.map((pattern) => expandPattern(workspaceRoot, pattern)));
|
|
74
|
+
for (const paths of patternResults) {
|
|
75
|
+
for (const rel of paths) {
|
|
76
|
+
resolved.add(rel);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (negatives.length === 0) {
|
|
80
|
+
return [...resolved].sort();
|
|
81
|
+
}
|
|
82
|
+
return [...resolved].filter((candidate) => !negatives.some((pattern) => matchesGlob(candidate, pattern))).sort();
|
|
83
|
+
};
|
|
84
|
+
const expandPattern = async (workspaceRoot, pattern) => {
|
|
85
|
+
if (!GLOB_METACHARS.test(pattern)) {
|
|
86
|
+
const absolute = resolve(workspaceRoot, pattern);
|
|
87
|
+
const rel = toWorkspaceRelative(workspaceRoot, absolute);
|
|
88
|
+
if (!rel) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
await stat(absolute);
|
|
93
|
+
return [rel];
|
|
94
|
+
} catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const paths = [];
|
|
99
|
+
for await (const entry of glob(pattern, { cwd: workspaceRoot, withFileTypes: true })) {
|
|
100
|
+
if (!entry.isFile()) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const rel = toWorkspaceRelative(workspaceRoot, join(entry.parentPath, entry.name));
|
|
104
|
+
if (rel) {
|
|
105
|
+
paths.push(rel);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return paths;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export { resolveOutputs };
|
package/dist/packem_shared/{runConcurrentFallback-CGHz_f-Q.js → runConcurrentFallback-BTmgGV1H.js}
RENAMED
|
@@ -226,7 +226,7 @@ const spawnCommandPty = (index, config, shellPath, onEvent, onComplete) => {
|
|
|
226
226
|
cwd: config.cwd ?? process.cwd(),
|
|
227
227
|
env: Object.fromEntries(
|
|
228
228
|
Object.entries({ ...process.env, ...config.env, FORCE_COLOR: process.env["FORCE_COLOR"] ?? "1", TERM: "xterm-256color" }).filter(
|
|
229
|
-
(entry) => entry[1]
|
|
229
|
+
(entry) => typeof entry[1] === "string"
|
|
230
230
|
)
|
|
231
231
|
),
|
|
232
232
|
name: "xterm-256color",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runConcurrentFallback } from './runConcurrentFallback-
|
|
1
|
+
import { runConcurrentFallback } from './runConcurrentFallback-BTmgGV1H.js';
|
|
2
2
|
import { detectScriptShell } from './detectScriptShell-CR-xXKA4.js';
|
|
3
3
|
import { logTimings } from './formatTimingTable-3qtCM552.js';
|
|
4
4
|
import { withRestart } from './withRestart-BREjRJa4.js';
|
|
@@ -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
|
+
writeFile
|
|
22
|
+
} = __cjs_getBuiltinModule("node:fs/promises");
|
|
23
|
+
|
|
24
|
+
const TASK_CATEGORY = "task";
|
|
25
|
+
const FLOW_CATEGORY = "dep";
|
|
26
|
+
const PID = 1;
|
|
27
|
+
const toChromeTrace = (summary) => {
|
|
28
|
+
const events = [];
|
|
29
|
+
const traceStartMs = Date.parse(summary.startTime);
|
|
30
|
+
const tasksByStart = [...summary.tasks].sort((a, b) => {
|
|
31
|
+
const aStart = a.startTime ? Date.parse(a.startTime) : Number.POSITIVE_INFINITY;
|
|
32
|
+
const bStart = b.startTime ? Date.parse(b.startTime) : Number.POSITIVE_INFINITY;
|
|
33
|
+
return aStart - bStart;
|
|
34
|
+
});
|
|
35
|
+
const lanes = [];
|
|
36
|
+
const taskLane = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const task of tasksByStart) {
|
|
38
|
+
if (!task.startTime || !task.endTime) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const startMs = Date.parse(task.startTime);
|
|
42
|
+
const endMs = Date.parse(task.endTime);
|
|
43
|
+
let lane = lanes.findIndex((laneEnd) => laneEnd <= startMs);
|
|
44
|
+
if (lane === -1) {
|
|
45
|
+
lane = lanes.length;
|
|
46
|
+
lanes.push(endMs);
|
|
47
|
+
} else {
|
|
48
|
+
lanes[lane] = endMs;
|
|
49
|
+
}
|
|
50
|
+
taskLane.set(task.taskId, lane);
|
|
51
|
+
events.push({
|
|
52
|
+
args: {
|
|
53
|
+
cacheStatus: task.cacheStatus,
|
|
54
|
+
exitCode: task.exitCode,
|
|
55
|
+
hash: task.hash,
|
|
56
|
+
project: task.target.project,
|
|
57
|
+
target: task.target.target
|
|
58
|
+
},
|
|
59
|
+
cat: TASK_CATEGORY,
|
|
60
|
+
dur: Math.max(0, (endMs - startMs) * 1e3),
|
|
61
|
+
name: task.taskId,
|
|
62
|
+
ph: "X",
|
|
63
|
+
pid: PID,
|
|
64
|
+
tid: lane,
|
|
65
|
+
ts: (startMs - traceStartMs) * 1e3
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
let flowId = 1;
|
|
69
|
+
for (const task of summary.tasks) {
|
|
70
|
+
if (!task.startTime || !task.endTime) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const dependents = task.dependencies ?? [];
|
|
74
|
+
for (const depId of dependents) {
|
|
75
|
+
const dep = summary.tasks.find((t) => t.taskId === depId);
|
|
76
|
+
if (!dep?.endTime || !task.startTime) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const depEndUs = (Date.parse(dep.endTime) - traceStartMs) * 1e3;
|
|
80
|
+
const taskStartUs = (Date.parse(task.startTime) - traceStartMs) * 1e3;
|
|
81
|
+
const id = flowId;
|
|
82
|
+
flowId += 1;
|
|
83
|
+
events.push(
|
|
84
|
+
{
|
|
85
|
+
cat: FLOW_CATEGORY,
|
|
86
|
+
id,
|
|
87
|
+
name: `${depId} → ${task.taskId}`,
|
|
88
|
+
ph: "s",
|
|
89
|
+
pid: PID,
|
|
90
|
+
tid: taskLane.get(depId) ?? 0,
|
|
91
|
+
ts: depEndUs
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
cat: FLOW_CATEGORY,
|
|
95
|
+
id,
|
|
96
|
+
name: `${depId} → ${task.taskId}`,
|
|
97
|
+
ph: "f",
|
|
98
|
+
pid: PID,
|
|
99
|
+
tid: taskLane.get(task.taskId) ?? 0,
|
|
100
|
+
ts: taskStartUs
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
events.unshift({
|
|
106
|
+
args: { name: `vis run (${summary.id})` },
|
|
107
|
+
cat: "__metadata",
|
|
108
|
+
name: "process_name",
|
|
109
|
+
ph: "M",
|
|
110
|
+
pid: PID,
|
|
111
|
+
tid: 0,
|
|
112
|
+
ts: 0
|
|
113
|
+
});
|
|
114
|
+
return events;
|
|
115
|
+
};
|
|
116
|
+
const writeChromeTrace = async (summary, outputPath) => {
|
|
117
|
+
const events = toChromeTrace(summary);
|
|
118
|
+
await writeFile(outputPath, JSON.stringify({ traceEvents: events }));
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export { toChromeTrace, writeChromeTrace };
|
package/dist/remote-cache.d.ts
CHANGED
|
@@ -1,7 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compression algorithm used for artifact tarballs on the wire.
|
|
3
|
+
* - `"gzip"` (default): tar+gzip, matches Turborepo's protocol format
|
|
4
|
+
* and stays interop-safe with existing remote cache servers.
|
|
5
|
+
* - `"brotli"`: tar + Node brotli (BROTLI_MODE_TEXT, quality 4) — a
|
|
6
|
+
* solid ratio/speed trade-off for source-tree tarballs. Both upload
|
|
7
|
+
* and download sides must agree; switching invalidates existing
|
|
8
|
+
* remote entries (they will simply re-populate on next run).
|
|
9
|
+
*/
|
|
10
|
+
export type RemoteCacheCompression = "brotli" | "gzip";
|
|
11
|
+
/**
|
|
12
|
+
* HMAC signing configuration for cache integrity.
|
|
13
|
+
*
|
|
14
|
+
* When set, every upload carries an `X-Artifact-Signature` header
|
|
15
|
+
* containing the HMAC-SHA256 digest of `hash | body`. On download,
|
|
16
|
+
* the client recomputes the HMAC and rejects any artifact whose
|
|
17
|
+
* signature doesn't match using a constant-time comparison.
|
|
18
|
+
*
|
|
19
|
+
* Prevents cache poisoning in shared team environments where a
|
|
20
|
+
* compromised cache server (or a MITM) could inject malicious
|
|
21
|
+
* artifacts. Unsigned entries (written before signing was enabled)
|
|
22
|
+
* are accepted only when `verifyOnDownload === false` — the default.
|
|
23
|
+
*/
|
|
24
|
+
export interface RemoteCacheSigning {
|
|
25
|
+
/** Shared secret. Must be at least 16 characters. */
|
|
26
|
+
secret: string;
|
|
27
|
+
/**
|
|
28
|
+
* Reject downloads whose signature doesn't match or is missing.
|
|
29
|
+
* Set to `true` once every upload on your server is signed.
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
verifyOnDownload?: boolean;
|
|
33
|
+
}
|
|
1
34
|
/**
|
|
2
35
|
* Options for the remote cache.
|
|
3
36
|
*/
|
|
4
37
|
interface RemoteCacheOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Compression format for artifact tarballs. Defaults to `"gzip"`
|
|
40
|
+
* for Turborepo protocol compatibility.
|
|
41
|
+
*/
|
|
42
|
+
compression?: RemoteCacheCompression;
|
|
5
43
|
/**
|
|
6
44
|
* Called when a fire-and-forget upload fails.
|
|
7
45
|
* Since uploads are non-blocking, errors are silently swallowed by default.
|
|
@@ -10,6 +48,13 @@ interface RemoteCacheOptions {
|
|
|
10
48
|
onUploadError?: (hash: string, error: unknown) => void;
|
|
11
49
|
/** Whether to enable remote cache reads */
|
|
12
50
|
read?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* HMAC-SHA256 signing for upload integrity. When set, every
|
|
53
|
+
* uploaded artifact carries an `X-Artifact-Signature` header;
|
|
54
|
+
* downloads with `verifyOnDownload: true` reject unsigned or
|
|
55
|
+
* tampered payloads.
|
|
56
|
+
*/
|
|
57
|
+
signing?: RemoteCacheSigning;
|
|
13
58
|
/** Team ID or namespace for cache isolation */
|
|
14
59
|
teamId?: string;
|
|
15
60
|
/** Request timeout in milliseconds (default: 30000) */
|
package/dist/run-summary.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TaskGraph, TaskHashDetails, TaskResults } from "./types.d.ts";
|
|
1
|
+
import type { OutputSpec, TaskGraph, TaskHashDetails, TaskResults } from "./types.d.ts";
|
|
2
2
|
/**
|
|
3
3
|
* Summary of a single task execution.
|
|
4
4
|
*/
|
|
@@ -19,8 +19,8 @@ interface TaskSummary {
|
|
|
19
19
|
hash: string | undefined;
|
|
20
20
|
/** Detailed hash information */
|
|
21
21
|
hashDetails: TaskHashDetails | undefined;
|
|
22
|
-
/** The task's declared outputs */
|
|
23
|
-
outputs:
|
|
22
|
+
/** The task's declared outputs (glob patterns, literals, or `{ auto: true }`). */
|
|
23
|
+
outputs: OutputSpec[];
|
|
24
24
|
/** Start time (ISO 8601) */
|
|
25
25
|
startTime: string | undefined;
|
|
26
26
|
/** The task target */
|
|
@@ -85,5 +85,27 @@ declare const generateRunSummary: (results: TaskResults, taskGraph: TaskGraph, s
|
|
|
85
85
|
* @returns The path to the written summary file
|
|
86
86
|
*/
|
|
87
87
|
declare const writeRunSummary: (summary: RunSummary, workspaceRoot: string) => Promise<string>;
|
|
88
|
+
/**
|
|
89
|
+
* Path where the most-recent run summary is persisted.
|
|
90
|
+
* Consumers (e.g. CLIs exposing `--last-details`) read this file
|
|
91
|
+
* to replay or render the previous run without re-executing.
|
|
92
|
+
*/
|
|
93
|
+
declare const getLastRunSummaryPath: (workspaceRoot: string) => string;
|
|
94
|
+
/**
|
|
95
|
+
* Persists `summary` as the most-recent run summary at
|
|
96
|
+
* `.task-runner/last-summary.json`, overwriting any previous entry.
|
|
97
|
+
*
|
|
98
|
+
* This is the companion to {@link readLastRunSummary} and powers
|
|
99
|
+
* CLI surfaces that display "last run" details without re-running tasks.
|
|
100
|
+
* @returns The path to the written summary file
|
|
101
|
+
*/
|
|
102
|
+
declare const writeLastRunSummary: (summary: RunSummary, workspaceRoot: string) => Promise<string>;
|
|
103
|
+
/**
|
|
104
|
+
* Reads the most-recent run summary written by {@link writeLastRunSummary}.
|
|
105
|
+
* Returns `undefined` when no previous run has been recorded or the file
|
|
106
|
+
* cannot be parsed — callers should render an informational message
|
|
107
|
+
* instead of treating this as an error.
|
|
108
|
+
*/
|
|
109
|
+
declare const readLastRunSummary: (workspaceRoot: string) => Promise<RunSummary | undefined>;
|
|
88
110
|
export type { RunSummary, TaskSummary };
|
|
89
|
-
export { generateRunSummary, writeRunSummary };
|
|
111
|
+
export { generateRunSummary, getLastRunSummaryPath, readLastRunSummary, writeLastRunSummary, writeRunSummary };
|
package/dist/task-hasher.d.ts
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
|
+
import type { IncrementalFileHasher } from "./incremental-hasher.d.ts";
|
|
1
2
|
import type { NamedInputs, ProjectConfiguration, TargetConfiguration, Task, TaskHashDetails } from "./types.d.ts";
|
|
2
3
|
/**
|
|
3
4
|
* Interface for task hashers.
|
|
4
5
|
*/
|
|
5
6
|
interface TaskHasher {
|
|
6
7
|
hashTask: (task: Task) => Promise<TaskHashDetails>;
|
|
8
|
+
/**
|
|
9
|
+
* Rehashes a single file bypassing any in-memory cache. Optional to keep
|
|
10
|
+
* external/custom hashers backward compatible; the orchestrator skips
|
|
11
|
+
* self-modifying-task detection when the implementation is absent.
|
|
12
|
+
*/
|
|
13
|
+
rehashFile?: (filePath: string) => Promise<string | undefined>;
|
|
7
14
|
}
|
|
8
15
|
/**
|
|
9
16
|
* Options for creating an InProcessTaskHasher.
|
|
10
17
|
*/
|
|
11
18
|
interface TaskHasherOptions {
|
|
19
|
+
/**
|
|
20
|
+
* When true, scan each task's resolved command for `$VAR`/`${VAR}`
|
|
21
|
+
* references and auto-fingerprint them. Catches the common case of
|
|
22
|
+
* a script reading `$VERCEL_URL` or `${NEXT_PUBLIC_API}` without
|
|
23
|
+
* the user remembering to declare it in `envVars`/`globalEnv`.
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
autoEnvVars?: boolean;
|
|
12
27
|
/** Additional environment variables to include in hash */
|
|
13
28
|
envVars?: string[];
|
|
14
29
|
/**
|
|
@@ -27,6 +42,17 @@ interface TaskHasherOptions {
|
|
|
27
42
|
* These are workspace-root-relative paths (e.g., "pnpm-lock.yaml").
|
|
28
43
|
*/
|
|
29
44
|
globalInputs?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Optional persistent mtime/size-indexed file snapshot. When set,
|
|
47
|
+
* `#hashFile` consults the snapshot first and only re-reads file
|
|
48
|
+
* contents when the file's mtime or size has changed since the
|
|
49
|
+
* previous run. Cuts cold-cache fingerprint time dramatically on
|
|
50
|
+
* large workspaces where most source files don't change run-to-run.
|
|
51
|
+
*
|
|
52
|
+
* The caller is responsible for `load()`ing the snapshot before
|
|
53
|
+
* using the hasher and `save()`ing it after the run completes.
|
|
54
|
+
*/
|
|
55
|
+
incrementalHasher?: IncrementalFileHasher;
|
|
30
56
|
/** Named input definitions */
|
|
31
57
|
namedInputs?: NamedInputs;
|
|
32
58
|
/** Project configurations keyed by project name */
|
|
@@ -55,6 +81,17 @@ declare class InProcessTaskHasher implements TaskHasher {
|
|
|
55
81
|
constructor(options: TaskHasherOptions);
|
|
56
82
|
hashTask(task: Task): Promise<TaskHashDetails>;
|
|
57
83
|
clearCache(): void;
|
|
84
|
+
/**
|
|
85
|
+
* Reads `filePath` fresh and returns its content hash, bypassing the
|
|
86
|
+
* in-memory cache used during the initial `hashTask` pass.
|
|
87
|
+
*
|
|
88
|
+
* Used to detect tasks that modify their own tracked inputs: compare
|
|
89
|
+
* a pre-execution hash (from `task.hashDetails.nodes`) against the
|
|
90
|
+
* post-execution result of this method.
|
|
91
|
+
* @param filePath Absolute path to the file.
|
|
92
|
+
* @returns The fresh xxh3 hash, or `undefined` if the file cannot be read.
|
|
93
|
+
*/
|
|
94
|
+
rehashFile(filePath: string): Promise<string | undefined>;
|
|
58
95
|
}
|
|
59
96
|
/**
|
|
60
97
|
* Computes the final hash for a task from its hash details.
|
|
@@ -2,7 +2,7 @@ import type { Cache } from "./cache.d.ts";
|
|
|
2
2
|
import type { RemoteCache } from "./remote-cache.d.ts";
|
|
3
3
|
import type { TaskHasher } from "./task-hasher.d.ts";
|
|
4
4
|
import type { TaskScheduler } from "./task-scheduler.d.ts";
|
|
5
|
-
import type { LifeCycleInterface, Task, TaskExecutor, TaskResults } from "./types.d.ts";
|
|
5
|
+
import type { LifeCycleInterface, Task, TaskExecutor, TaskGraph, TaskResults } from "./types.d.ts";
|
|
6
6
|
/**
|
|
7
7
|
* Options for the TaskOrchestrator.
|
|
8
8
|
*/
|
|
@@ -20,7 +20,7 @@ interface TaskOrchestratorOptions {
|
|
|
20
20
|
skipCache?: boolean;
|
|
21
21
|
summarize?: boolean;
|
|
22
22
|
taskExecutor: TaskExecutor;
|
|
23
|
-
taskGraph?:
|
|
23
|
+
taskGraph?: TaskGraph;
|
|
24
24
|
taskHasher: TaskHasher;
|
|
25
25
|
untrackedEnvVars?: string[];
|
|
26
26
|
workspaceRoot: string;
|