@visulima/task-runner 1.0.0-alpha.5 → 1.0.0-alpha.6
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 +31 -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 +12 -12
|
@@ -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;
|
package/dist/types.d.ts
CHANGED
|
@@ -12,6 +12,32 @@ export interface TaskTarget {
|
|
|
12
12
|
/**
|
|
13
13
|
* Represents a single task to be executed.
|
|
14
14
|
*/
|
|
15
|
+
/**
|
|
16
|
+
* Scheduling priority hint. The scheduler picks higher-priority tasks
|
|
17
|
+
* out of the ready-queue first; ties fall through to the graph-derived
|
|
18
|
+
* signals (dependent count, project depth).
|
|
19
|
+
*
|
|
20
|
+
* Use `"high"` for long-running leaves you want to kick off early
|
|
21
|
+
* (integration tests, e2e suites) so their wall-clock overlaps with
|
|
22
|
+
* the main build phase. Use `"low"` for work you don't mind deferring
|
|
23
|
+
* (linters, coverage uploads) when faster tasks contend for slots.
|
|
24
|
+
*/
|
|
25
|
+
export type TaskPriority = "high" | "low" | "normal";
|
|
26
|
+
/**
|
|
27
|
+
* A single entry in a task's `outputs` list.
|
|
28
|
+
*
|
|
29
|
+
* - `string` — a literal path *or* a glob pattern relative to the
|
|
30
|
+
* workspace root. Prefix with `!` to exclude files the positive
|
|
31
|
+
* patterns would otherwise include (e.g. `"!dist/cache/**"`).
|
|
32
|
+
* - `{ auto: true }` — use whatever files the task actually wrote
|
|
33
|
+
* during execution (resolved from the file-access tracker). Lets
|
|
34
|
+
* authors who don't know their exact output layout cache results
|
|
35
|
+
* without listing every path. Silently behaves as "no outputs" when
|
|
36
|
+
* tracking isn't active for this task.
|
|
37
|
+
*/
|
|
38
|
+
export type OutputSpec = string | {
|
|
39
|
+
auto: true;
|
|
40
|
+
};
|
|
15
41
|
export interface Task {
|
|
16
42
|
/** Whether this task is eligible for caching */
|
|
17
43
|
cache?: boolean;
|
|
@@ -21,12 +47,22 @@ export interface Task {
|
|
|
21
47
|
hashDetails?: TaskHashDetails;
|
|
22
48
|
/** Unique identifier for the task, typically "project:target:configuration" */
|
|
23
49
|
id: string;
|
|
24
|
-
/**
|
|
25
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Output patterns this task produces. Each entry is either a
|
|
52
|
+
* literal path, a glob (`"dist/**"`), a negative glob
|
|
53
|
+
* (`"!dist/cache/**"`), or `{ auto: true }` to pick up whatever
|
|
54
|
+
* files the task wrote during execution.
|
|
55
|
+
*/
|
|
56
|
+
outputs: OutputSpec[];
|
|
26
57
|
/** Overrides/extra options passed to the task */
|
|
27
58
|
overrides: Record<string, unknown>;
|
|
28
59
|
/** Whether this task supports parallel execution */
|
|
29
60
|
parallelism?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Explicit scheduling priority. Outranks the scheduler's
|
|
63
|
+
* graph-derived heuristics. Defaults to `"normal"` when absent.
|
|
64
|
+
*/
|
|
65
|
+
priority?: TaskPriority;
|
|
30
66
|
/** The project root directory */
|
|
31
67
|
projectRoot?: string;
|
|
32
68
|
/** The target this task executes */
|
|
@@ -66,8 +102,23 @@ export type TaskStatus = "success" | "failure" | "skipped" | "local-cache" | "lo
|
|
|
66
102
|
export interface TaskResult {
|
|
67
103
|
/** The exit code, if applicable */
|
|
68
104
|
code?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Set when auto-fingerprint tracking was attempted for this task but
|
|
107
|
+
* returned zero workspace accesses — usually a static binary on
|
|
108
|
+
* macOS/Windows, where the Node preload can't attach. Caching is
|
|
109
|
+
* skipped to avoid persisting an empty fingerprint that would
|
|
110
|
+
* produce false cache hits on every subsequent run.
|
|
111
|
+
*/
|
|
112
|
+
emptyFingerprint?: boolean;
|
|
69
113
|
/** The end time of the task */
|
|
70
114
|
endTime?: number;
|
|
115
|
+
/**
|
|
116
|
+
* Set when the task modified one or more of its own tracked input
|
|
117
|
+
* files during execution. Caching is skipped in this case — the
|
|
118
|
+
* fingerprint captured before the run would mismatch the post-run
|
|
119
|
+
* contents and produce false cache hits on subsequent runs.
|
|
120
|
+
*/
|
|
121
|
+
selfModified?: boolean;
|
|
71
122
|
/** The start time of the task */
|
|
72
123
|
startTime?: number;
|
|
73
124
|
status: TaskStatus;
|
|
@@ -145,11 +196,30 @@ export interface TargetDependencyConfig {
|
|
|
145
196
|
* Defines an input for cache invalidation.
|
|
146
197
|
*/
|
|
147
198
|
export type InputDefinition = FileSetInput | RuntimeInput | EnvironmentInput | ExternalDependencyInput;
|
|
199
|
+
/**
|
|
200
|
+
* Controls how a glob pattern is anchored.
|
|
201
|
+
* - "package" (default): pattern is resolved relative to the project root
|
|
202
|
+
* - "workspace": pattern is resolved relative to the workspace root
|
|
203
|
+
*/
|
|
204
|
+
export type FileSetBase = "package" | "workspace";
|
|
148
205
|
/**
|
|
149
206
|
* An input based on file patterns.
|
|
207
|
+
*
|
|
208
|
+
* `fileset` may be a bare glob string (package-root relative) or an object
|
|
209
|
+
* form `{ pattern, base }` to anchor explicitly to the workspace root.
|
|
210
|
+
* Negation (`!` prefix) works in both forms.
|
|
150
211
|
*/
|
|
151
212
|
export interface FileSetInput {
|
|
152
|
-
fileset: string;
|
|
213
|
+
fileset: FileSetPattern | string;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Object form of a fileset pattern, for anchoring to the workspace root.
|
|
217
|
+
*/
|
|
218
|
+
export interface FileSetPattern {
|
|
219
|
+
/** Anchor for the pattern. */
|
|
220
|
+
base: FileSetBase;
|
|
221
|
+
/** Glob pattern (may start with `!` for negation). */
|
|
222
|
+
pattern: string;
|
|
153
223
|
}
|
|
154
224
|
/**
|
|
155
225
|
* An input based on a runtime command.
|
|
@@ -311,6 +381,14 @@ export interface NamedInputs {
|
|
|
311
381
|
* Configuration for the task runner.
|
|
312
382
|
*/
|
|
313
383
|
export interface TaskRunnerOptions {
|
|
384
|
+
/**
|
|
385
|
+
* Scan each task's resolved command text for `$VAR`/`${VAR}`
|
|
386
|
+
* references and automatically fingerprint those env vars. Catches
|
|
387
|
+
* tasks like `curl ${VERCEL_URL}/api` where the user forgot to
|
|
388
|
+
* declare the reference in `envVars`/`globalEnv`.
|
|
389
|
+
* @default false
|
|
390
|
+
*/
|
|
391
|
+
autoEnvVars?: boolean;
|
|
314
392
|
/**
|
|
315
393
|
* Enable auto-fingerprinting mode (Vite Task-style).
|
|
316
394
|
* When enabled, the task runner automatically tracks which files
|
|
@@ -374,12 +452,37 @@ export interface TaskRunnerOptions {
|
|
|
374
452
|
* forcing a full rebuild. This matches Turborepo's `globalDependencies`.
|
|
375
453
|
*/
|
|
376
454
|
globalInputs?: string[];
|
|
455
|
+
/**
|
|
456
|
+
* When `true`, file hashes are cached in a persistent mtime+size
|
|
457
|
+
* indexed snapshot under `node_modules/.cache/task-runner/`. On
|
|
458
|
+
* subsequent runs, unchanged files skip content re-reads — cuts
|
|
459
|
+
* cold-cache fingerprint time dramatically on large workspaces.
|
|
460
|
+
*
|
|
461
|
+
* Changed or new files still get full content hashing. Safe to
|
|
462
|
+
* leave on by default; overhead when nothing matches is a single
|
|
463
|
+
* `stat` call per file.
|
|
464
|
+
* @default false
|
|
465
|
+
*/
|
|
466
|
+
incrementalFileHashing?: boolean;
|
|
377
467
|
/** Maximum age of cache entries in milliseconds */
|
|
378
468
|
maxCacheAge?: number;
|
|
379
469
|
/** Maximum cache size (e.g., "1GB") */
|
|
380
470
|
maxCacheSize?: string;
|
|
381
471
|
/** Named inputs for cache invalidation */
|
|
382
472
|
namedInputs?: NamedInputs;
|
|
473
|
+
/**
|
|
474
|
+
* When `true`, the cache directory is partitioned by a hash of
|
|
475
|
+
* the resolved `globalEnv` values. Changing a globalEnv var moves
|
|
476
|
+
* cache writes into a new namespace; rolling it back reuses the
|
|
477
|
+
* old namespace and its hits. Without this option, any globalEnv
|
|
478
|
+
* change silently busts every cached entry.
|
|
479
|
+
*
|
|
480
|
+
* Keep disabled when globalEnv is stable across runs — the extra
|
|
481
|
+
* path depth offers no value, and misconfigured namespaces can
|
|
482
|
+
* hide stale hits.
|
|
483
|
+
* @default false
|
|
484
|
+
*/
|
|
485
|
+
namespaceByGlobalEnv?: boolean;
|
|
383
486
|
/** Maximum number of parallel tasks */
|
|
384
487
|
parallel?: number | boolean;
|
|
385
488
|
/**
|
|
@@ -614,8 +717,39 @@ export interface ConcurrentRunResult {
|
|
|
614
717
|
export interface LifeCycleInterface {
|
|
615
718
|
endCommand?: () => void;
|
|
616
719
|
endTasks?: (taskResults: TaskResult[]) => void;
|
|
720
|
+
/**
|
|
721
|
+
* Called when a running task emits data on stderr, in the order the
|
|
722
|
+
* data arrives. Executors that support streaming output (`vis run`'s
|
|
723
|
+
* concurrent executor) forward chunks here verbatim. Executors that
|
|
724
|
+
* only buffer full output (e.g. the simple test executor) skip this
|
|
725
|
+
* hook — rely on `printTaskTerminalOutput` for the final dump.
|
|
726
|
+
*
|
|
727
|
+
* Handlers should be non-blocking; the orchestrator doesn't await.
|
|
728
|
+
*/
|
|
729
|
+
onTaskStderr?: (task: Task, chunk: string) => void;
|
|
730
|
+
/**
|
|
731
|
+
* Called when a running task emits data on stdout, in the order the
|
|
732
|
+
* data arrives. See {@link LifeCycleInterface.onTaskStderr} for
|
|
733
|
+
* semantics — same contract, different stream.
|
|
734
|
+
*/
|
|
735
|
+
onTaskStdout?: (task: Task, chunk: string) => void;
|
|
617
736
|
/** Called when a cache miss occurs with diagnostic information */
|
|
618
737
|
printCacheMiss?: (task: Task, reasons: string) => void;
|
|
738
|
+
/**
|
|
739
|
+
* Called when caching was skipped because auto-fingerprint tracking
|
|
740
|
+
* came back empty — a signal that the tracker (strace or Node
|
|
741
|
+
* preload) couldn't observe the task's file access, typically
|
|
742
|
+
* because it's a static binary on a platform without strace.
|
|
743
|
+
* `reason` is a short human-readable diagnostic.
|
|
744
|
+
*/
|
|
745
|
+
printEmptyFingerprintWarning?: (task: Task, reason: string) => void;
|
|
746
|
+
/**
|
|
747
|
+
* Called when caching is skipped because the task modified one or
|
|
748
|
+
* more of its own tracked inputs. `modifiedFiles` lists the
|
|
749
|
+
* workspace-relative paths that changed between pre- and
|
|
750
|
+
* post-execution hashes.
|
|
751
|
+
*/
|
|
752
|
+
printSelfModifyingSkip?: (task: Task, modifiedFiles: string[]) => void;
|
|
619
753
|
printTaskTerminalOutput?: (task: Task, status: TaskStatus, terminalOutput: string) => void;
|
|
620
754
|
scheduleTask?: (task: Task) => void;
|
|
621
755
|
startCommand?: () => void;
|