@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/archive.d.ts +38 -0
  3. package/dist/cache.d.ts +31 -3
  4. package/dist/chrome-trace.d.ts +53 -0
  5. package/dist/file-access-tracker.d.ts +7 -1
  6. package/dist/fingerprint.d.ts +9 -0
  7. package/dist/incremental-hasher.d.ts +18 -0
  8. package/dist/index.d.ts +8 -3
  9. package/dist/index.js +22 -19
  10. package/dist/life-cycle.d.ts +2 -0
  11. package/dist/log-reporter.d.ts +34 -0
  12. package/dist/output-resolver.d.ts +20 -0
  13. package/dist/packem_shared/{Cache-iAjRMV2d.js → Cache-CWaX_c8U.js} +135 -45
  14. package/dist/packem_shared/{CompositeLifeCycle-7AtYw1dv.js → CompositeLifeCycle-CSVbRC_5.js} +10 -0
  15. package/dist/packem_shared/{FileAccessTracker-CrtBAt5D.js → FileAccessTracker-CQ5Ot7Hd.js} +68 -16
  16. package/dist/packem_shared/{FingerprintManager-Cu-ta9ee.js → FingerprintManager-CV7U4f4f.js} +22 -1
  17. package/dist/packem_shared/{IncrementalFileHasher-Cm_kJY5V.js → IncrementalFileHasher-BRS76-mb.js} +26 -0
  18. package/dist/packem_shared/LogReporter-BDt52HLu.js +44 -0
  19. package/dist/packem_shared/{RemoteCache-BFceSe4a.js → RemoteCache-DSU3lc87.js} +77 -37
  20. package/dist/packem_shared/{TaskOrchestrator-lLn-PH1m.js → TaskOrchestrator-rf45vW5c.js} +94 -15
  21. package/dist/packem_shared/{TerminalBuffer-CnPyFgPB.js → TerminalBuffer-qVJvbRQZ.js} +1 -1
  22. package/dist/packem_shared/{TrackedTaskExecutor-BGUKFE-7.js → TrackedTaskExecutor-CFPpQfXF.js} +1 -1
  23. package/dist/packem_shared/archive-UQHAnZUa.js +102 -0
  24. package/dist/packem_shared/{buildForwardDependencyMap-0BJFMMPv.js → buildForwardDependencyMap-DLPgKEto.js} +2 -1
  25. package/dist/packem_shared/{computeTaskHash-B2SVZqgp.js → computeTaskHash-DYqfrDGq.js} +122 -6
  26. package/dist/packem_shared/{createTaskGraph-CcsFaSrz.js → createTaskGraph-B7nH0kY_.js} +2 -2
  27. package/dist/packem_shared/{defaultTaskRunner-BdFTifsh.js → defaultTaskRunner-Cp7jCmIl.js} +28 -6
  28. package/dist/packem_shared/{extractPackageName-CbVNW-dr.js → extractPackageName-BllKetnz.js} +2 -1
  29. package/dist/packem_shared/{generateRunSummary-qn-_jKwt.js → generateRunSummary-BE1jnQ3H.js} +19 -1
  30. package/dist/packem_shared/{parsePartition-C4-P5RjK.js → parsePartition-BfLbHGAx.js} +18 -0
  31. package/dist/packem_shared/{projectGraphToDot-C8uYeaPo.js → projectGraphToDot-DU1lSe-c.js} +1 -1
  32. package/dist/packem_shared/resolveOutputs-n6MCKoTe.js +111 -0
  33. package/dist/packem_shared/{runConcurrentFallback-CGHz_f-Q.js → runConcurrentFallback-BTmgGV1H.js} +1 -1
  34. package/dist/packem_shared/{runConcurrently-qrkWyzXW.js → runConcurrently-CmfC4r-f.js} +1 -1
  35. package/dist/packem_shared/toChromeTrace-B2tZoJ-7.js +121 -0
  36. package/dist/remote-cache.d.ts +45 -0
  37. package/dist/run-summary.d.ts +26 -4
  38. package/dist/task-hasher.d.ts +37 -0
  39. package/dist/task-orchestrator.d.ts +2 -2
  40. package/dist/types.d.ts +137 -3
  41. package/index.js +52 -52
  42. package/package.json +12 -12
package/CHANGELOG.md CHANGED
@@ -1,3 +1,34 @@
1
+ ## @visulima/task-runner [1.0.0-alpha.6](https://github.com/visulima/visulima/compare/@visulima/task-runner@1.0.0-alpha.5...@visulima/task-runner@1.0.0-alpha.6) (2026-04-21)
2
+
3
+ ### Features
4
+
5
+ * **task-runner:** output globs, auto-writes, parallel cache IO ([137f53f](https://github.com/visulima/visulima/commit/137f53f7f5a4d8c16df511c9d145b2c158025a32))
6
+ * **task-runner:** vite-task parity + plugin-ready lifecycle hooks ([cfc7360](https://github.com/visulima/visulima/commit/cfc7360abf00524fbfc37b60df27970c325f91e1)), closes [pkg#task](https://github.com/visulima/pkg/issues/task)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **task-runner:** resolve eslint errors in chrome-trace and task-hasher ([2cf6266](https://github.com/visulima/visulima/commit/2cf6266b8252bc24a6d900f49f97611d4d629ff3))
11
+
12
+ ### Miscellaneous Chores
13
+
14
+ * **api-platform:** apply pending lint and source updates ([3fb0043](https://github.com/visulima/visulima/commit/3fb0043a4cf35f752ca89a09a077100ae0142da8))
15
+ * bump engines.node to ^22.14.0 || >=24.10.0 ([c3d0931](https://github.com/visulima/visulima/commit/c3d0931d1504e4f21ebf50ea680cfa7ce4ba15ce))
16
+ * fixed jsr.json ([5d85e51](https://github.com/visulima/visulima/commit/5d85e5179de38e284ec433b14d77c71a1619c8d6))
17
+ * **task-runner:** apply formatter and lint fixes ([70b4641](https://github.com/visulima/visulima/commit/70b4641b394897ebfc021425b992db936e320d8a))
18
+ * **task-runner:** apply formatter and lint fixes ([aee664f](https://github.com/visulima/visulima/commit/aee664f0a03ed9c1eb8bf7a6a91e62f601a3d5ff))
19
+ * **task-runner:** apply pending changes ([9d92517](https://github.com/visulima/visulima/commit/9d92517f1a54bc28a19cb8cbdb937cdc234e152d))
20
+ * **task-runner:** apply pending lint and source updates ([c01eb39](https://github.com/visulima/visulima/commit/c01eb393f991d8fc96f8ed87bfc71d90902ee659))
21
+ * **task-runner:** enforce curly braces and apply lint fixes ([4fbd8ee](https://github.com/visulima/visulima/commit/4fbd8eefa1b56f428528b495b074d5c266fb6733))
22
+
23
+ ### Code Refactoring
24
+
25
+ * replace inline import() types with top-level imports ([4569a4c](https://github.com/visulima/visulima/commit/4569a4ca04723da069f985855dcfab292f7347e1))
26
+
27
+ ### Tests
28
+
29
+ * **task-runner:** remove native binding guard from tests ([90f0dff](https://github.com/visulima/visulima/commit/90f0dffe85f4b83e76905a26d53b917365116b45))
30
+ * **task-runner:** skip default excludes for tmpdir-backed tracker tests ([0db0620](https://github.com/visulima/visulima/commit/0db06206173f0799244fafcc578b9fb0be9d2fb6))
31
+
1
32
  ## @visulima/task-runner [1.0.0-alpha.5](https://github.com/visulima/visulima/compare/@visulima/task-runner@1.0.0-alpha.4...@visulima/task-runner@1.0.0-alpha.5) (2026-04-15)
2
33
 
3
34
  ### Features
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Shared tar + brotli archive helpers.
3
+ *
4
+ * Both the local cache (`cache.ts`) and remote cache (`remote-cache.ts`)
5
+ * archive file trees into compressed tarballs. This module consolidates
6
+ * the compression parameters and stream plumbing so both paths stay in
7
+ * sync — same brotli quality, same error-handling shape, one place to
8
+ * audit.
9
+ */
10
+ /**
11
+ * Brotli quality 4 hits a sweet spot for cache tarballs: ~15–20% smaller
12
+ * than gzip on typical source/dist payloads at comparable throughput.
13
+ * Higher qualities (8+) reach diminishing returns and noticeably slow
14
+ * down cache writes; lower qualities (1–3) give up ratio for speed we
15
+ * don't need on IO-bound workloads.
16
+ */
17
+ export declare const BROTLI_COMPRESS_OPTIONS: {
18
+ params: Record<number, number>;
19
+ };
20
+ /** Plain tar: `sourceDir` → `outputPath`, no compression. */
21
+ export declare const createTar: (sourceDirectory: string, outputPath: string) => Promise<void>;
22
+ /** Plain tar extract into `destinationDirectory`. */
23
+ export declare const extractTar: (archivePath: string, destinationDirectory: string) => Promise<void>;
24
+ /** tar + gzip (`-czf`). Used when Turborepo-protocol compatibility matters. */
25
+ export declare const createTarGz: (sourceDirectory: string, outputPath: string) => Promise<void>;
26
+ export declare const extractTarGz: (archivePath: string, destinationDirectory: string) => Promise<void>;
27
+ /**
28
+ * Creates an uncompressed tar, then streams it through brotli into
29
+ * `outputPath`. Two-step (tar-to-temp, brotli-stream) to avoid
30
+ * shelling out to an external `brotli` binary that may not exist.
31
+ * Cleans up the intermediate tar even when compression fails.
32
+ */
33
+ export declare const createTarBrotli: (sourceDirectory: string, outputPath: string) => Promise<void>;
34
+ /**
35
+ * Inverse of {@link createTarBrotli}: decompresses into a temp tar,
36
+ * then extracts. Temp is cleaned in `finally`.
37
+ */
38
+ export declare const extractTarBrotli: (archivePath: string, destinationDirectory: string) => Promise<void>;
package/dist/cache.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { TaskFingerprint } from "./fingerprint.d.ts";
2
+ import type { OutputSpec } from "./types.d.ts";
2
3
  /**
3
4
  * Represents a cached task result.
4
5
  */
@@ -18,6 +19,17 @@ interface CachedResult {
18
19
  interface CacheOptions {
19
20
  /** The cache directory (defaults to `{workspaceRoot}/.task-runner-cache`) */
20
21
  cacheDirectory?: string;
22
+ /**
23
+ * Optional isolation namespace appended to the cache directory
24
+ * as `&lt;cacheDir>/ns/&lt;namespace>`. When the caller derives the
25
+ * namespace from the resolved global-env fingerprint, flipping an
26
+ * env var sends writes into a new namespace while keeping the old
27
+ * namespace intact — rolling the env back restores the old hits.
28
+ *
29
+ * Filesystem-safe segment; callers are responsible for sanitising
30
+ * (slashes/colons would break path resolution).
31
+ */
32
+ cacheNamespace?: string;
21
33
  /** Maximum age of cache entries in milliseconds (default: 7 days) */
22
34
  maxCacheAge?: number;
23
35
  /** Maximum cache size (e.g., "500MB", "1GB") */
@@ -78,12 +90,28 @@ declare class Cache {
78
90
  *
79
91
  * Uses atomic write: builds the entry in a temporary directory,
80
92
  * then renames into place to avoid partial reads by concurrent processes.
93
+ *
94
+ * `outputs` accepts the richer `OutputSpec[]` shape — glob
95
+ * patterns, negative globs, and `{ auto: true }` entries. Pass
96
+ * `autoWrites` alongside `{ auto: true }` so the resolver knows
97
+ * which files the task actually wrote; otherwise auto entries
98
+ * contribute nothing.
81
99
  */
82
- put(hash: string, terminalOutput: string, outputs: string[], code: number, fingerprint?: TaskFingerprint): Promise<void>;
100
+ put(hash: string, terminalOutput: string, outputs: OutputSpec[], code: number, fingerprint?: TaskFingerprint, autoWrites?: ReadonlyArray<string>): Promise<void>;
83
101
  /**
84
- * Restores cached outputs to their original locations.
102
+ * Restores cached outputs from the compressed `outputs.tar.br`
103
+ * archive. Returns `true` either when the archive was extracted
104
+ * successfully OR when the entry simply has no outputs to restore.
105
+ *
106
+ * The restore flow stages into a temp directory, then swaps each
107
+ * top-level entry into place (see {@link restoreOutputsCompressed})
108
+ * so a mid-restore failure never destroys the user's working tree.
109
+ * The `outputs` parameter is no longer consulted at restore time —
110
+ * the archive is authoritative, and top-level entries in the
111
+ * extracted staging become the set of swap roots. Still accepted
112
+ * for backward compat.
85
113
  */
86
- restoreOutputs(hash: string, outputs: string[]): Promise<boolean>;
114
+ restoreOutputs(hash: string, _outputs?: OutputSpec[]): Promise<boolean>;
87
115
  /**
88
116
  * Retrieves the most recent cached result for a task by its ID.
89
117
  * Used in auto-fingerprint mode where the hash is derived from
@@ -0,0 +1,53 @@
1
+ import type { RunSummary } from "./run-summary.d.ts";
2
+ /**
3
+ * A single event in the Chrome Tracing JSON format. Chrome's
4
+ * chrome://tracing viewer and Perfetto both accept an array of these.
5
+ *
6
+ * Fields track the subset we actually emit:
7
+ * - `ph: "X"` — "complete" span (has duration)
8
+ * - `ph: "s"` / `ph: "f"` — flow start / finish (connects two spans)
9
+ *
10
+ * See the Chrome Trace Event Format spec on docs.google.com
11
+ * (document id: 1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU)
12
+ * for the full specification.
13
+ */
14
+ export interface ChromeTraceEvent {
15
+ args?: Record<string, unknown>;
16
+ /** Event category — grouped in the viewer's search/filter UI. */
17
+ cat: string;
18
+ /** Duration in microseconds — only set for `ph: "X"` events. */
19
+ dur?: number;
20
+ /** Flow ID — used to draw an arrow from flow-start to flow-finish. */
21
+ id?: number;
22
+ /** Human label shown on the timeline. */
23
+ name: string;
24
+ /**
25
+ * Event phase:
26
+ * - `"X"` — complete (span with duration)
27
+ * - `"s"` — flow start
28
+ * - `"f"` — flow finish
29
+ * - `"M"` — metadata
30
+ */
31
+ ph: "f" | "M" | "s" | "X";
32
+ pid: number;
33
+ tid: number;
34
+ /** Timestamp in microseconds. */
35
+ ts: number;
36
+ }
37
+ /**
38
+ * Converts a {@link RunSummary} into a Chrome Tracing event list that
39
+ * renders as a gantt chart in chrome://tracing or Perfetto.
40
+ *
41
+ * Each task becomes a `"X"` span; dependency edges become flow arrows
42
+ * from the dependency's finish to the dependent task's start.
43
+ * Parallel tasks are assigned synthetic `tid` values (lane 0, 1, 2, …)
44
+ * based on the smallest free lane at the task's start time, so the
45
+ * timeline clearly shows concurrency without requiring real thread IDs.
46
+ */
47
+ export declare const toChromeTrace: (summary: RunSummary) => ChromeTraceEvent[];
48
+ /**
49
+ * Writes a Chrome Tracing JSON file at `outputPath`. Consumers (e.g.
50
+ * a CLI `--profile out.json` flag) call this after the run completes
51
+ * with a RunSummary produced by the task orchestrator.
52
+ */
53
+ export declare const writeChromeTrace: (summary: RunSummary, outputPath: string) => Promise<void>;
@@ -1,11 +1,17 @@
1
1
  /**
2
2
  * Represents a file access recorded during task execution.
3
+ *
4
+ * Write-intent accesses (`"write"`) are emitted when a task opens a file
5
+ * with `O_WRONLY`/`O_RDWR`/`O_CREAT`/`O_TRUNC` flags (strace) or calls
6
+ * `writeFile`/`appendFile`/`unlink`/`rename` (preload script).
7
+ * The orchestrator uses the overlap of reads and writes to the same
8
+ * workspace path to detect self-modifying tasks and skip caching.
3
9
  */
4
10
  export interface FileAccess {
5
11
  /** The absolute path of the file */
6
12
  path: string;
7
13
  /** The type of access */
8
- type: "read" | "stat" | "readdir" | "missing";
14
+ type: "missing" | "read" | "readdir" | "stat" | "write";
9
15
  }
10
16
  /**
11
17
  * Result of tracking file accesses during a command execution.
@@ -13,6 +13,15 @@ export interface TaskFingerprint {
13
13
  fileHashes: Record<string, string>;
14
14
  /** Paths of files that were probed but didn't exist (ENOENT) */
15
15
  missingFiles: string[];
16
+ /**
17
+ * Workspace-relative paths that were both read **and** written
18
+ * during execution. Populated when the tracker emits
19
+ * {@link FileAccess} entries with `"write"` type. The orchestrator
20
+ * uses a non-empty value here to skip caching a self-modifying
21
+ * task, whose fingerprint would otherwise capture post-write state
22
+ * and trigger false cache hits.
23
+ */
24
+ modifiedInputs?: string[];
16
25
  }
17
26
  /**
18
27
  * Describes why a cache miss occurred.
@@ -53,6 +53,24 @@ declare class IncrementalFileHasher {
53
53
  * Clears the in-memory snapshot.
54
54
  */
55
55
  clear(): void;
56
+ /**
57
+ * Looks up the cached hash for `absolutePath` when its mtime and
58
+ * size match the snapshot; returns `undefined` otherwise. Callers
59
+ * that already have a `stat` result (e.g. `InProcessTaskHasher`)
60
+ * skip an extra syscall by passing it through directly.
61
+ *
62
+ * The snapshot is considered loaded on first call — lazy-load is
63
+ * synchronous-friendly here because the caller already performed
64
+ * an async `stat` before calling this method.
65
+ */
66
+ getSnapshotHash(absolutePath: string, mtimeMs: number, size: number): string | undefined;
67
+ /**
68
+ * Writes a fresh snapshot entry after the caller has computed the
69
+ * hash. Pairs with {@link IncrementalFileHasher.getSnapshotHash}
70
+ * — after a miss, the caller hashes the file and records the
71
+ * result here so the next run can reuse it.
72
+ */
73
+ recordSnapshot(absolutePath: string, hash: string, mtimeMs: number, size: number): void;
56
74
  }
57
75
  export type { FileSnapshot, IncrementalHasherOptions };
58
76
  export { IncrementalFileHasher };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export type { AffectedOptions, AffectedResult } from "./affected.d.ts";
2
2
  export { buildForwardDependencyMap, buildReverseDependencyMap, expandAffected, filterAffectedTasks, getAffectedProjects, getChangedFiles } from "./affected.d.ts";
3
3
  export type { CachedResult, CacheOptions } from "./cache.d.ts";
4
4
  export { Cache, DEFAULT_CACHE_DIRECTORY_NAME, formatCacheSize, parseCacheSize } from "./cache.d.ts";
5
+ export type { ChromeTraceEvent } from "./chrome-trace.d.ts";
6
+ export { toChromeTrace, writeChromeTrace } from "./chrome-trace.d.ts";
5
7
  export type { ParseCommandsOptions } from "./command-parser/index.d.ts";
6
8
  export { expandArguments, expandShortcut, expandWildcard, parseCommands, stripQuotes } from "./command-parser/index.d.ts";
7
9
  export { runConcurrently } from "./concurrent.d.ts";
@@ -23,12 +25,15 @@ export { IncrementalFileHasher } from "./incremental-hasher.d.ts";
23
25
  export { CompositeLifeCycle, ConsoleLifeCycle, EmptyLifeCycle } from "./life-cycle.d.ts";
24
26
  export type { PackageLockfileHash, ResolvedDependency } from "./lockfile-hasher.d.ts";
25
27
  export { extractPackageName, LockfileHasher, parseNpmLockfile, parsePnpmLockfile, parseYarnLockfile } from "./lockfile-hasher.d.ts";
28
+ export type { LogMode } from "./log-reporter.d.ts";
29
+ export { createLogReporter, LogReporter } from "./log-reporter.d.ts";
26
30
  export { isNativeAvailable, loadNativeBindings } from "./native-binding.d.ts";
31
+ export { resolveOutputs } from "./output-resolver.d.ts";
27
32
  export { enforceProjectConstraints } from "./project-constraints.d.ts";
28
- export type { RemoteCacheOptions } from "./remote-cache.d.ts";
33
+ export type { RemoteCacheCompression, RemoteCacheOptions, RemoteCacheSigning } from "./remote-cache.d.ts";
29
34
  export { RemoteCache } from "./remote-cache.d.ts";
30
35
  export type { RunSummary, TaskSummary } from "./run-summary.d.ts";
31
- export { generateRunSummary, writeRunSummary } from "./run-summary.d.ts";
36
+ export { generateRunSummary, getLastRunSummaryPath, readLastRunSummary, writeLastRunSummary, writeRunSummary } from "./run-summary.d.ts";
32
37
  export { createTaskGraph, getTaskId, parseTaskId } from "./task-graph.d.ts";
33
38
  export { findCycle, findCycles, getDependentTasks, getLeafTasks, getTransitiveDependencies, makeAcyclic, reverseTaskGraph, walkTaskGraph, } from "./task-graph-utils.d.ts";
34
39
  export type { TaskHasher, TaskHasherOptions } from "./task-hasher.d.ts";
@@ -40,5 +45,5 @@ export { parsePartition, TaskScheduler } from "./task-scheduler.d.ts";
40
45
  export { TerminalBuffer } from "./terminal-buffer.d.ts";
41
46
  export type { TrackedExecutionResult } from "./tracked-executor.d.ts";
42
47
  export { TrackedTaskExecutor } from "./tracked-executor.d.ts";
43
- export type { AffectedScope, ConcurrentCloseEvent, ConcurrentCommandConfig, ConcurrentCommandInput, ConcurrentRunnerOptions, ConcurrentRunResult, ConstraintsConfig, ConstraintViolation, DependencyKindRules, DependencyType, EnvironmentInput, ExternalDependencyInput, FileSetInput, InputDefinition, LifeCycleInterface, NamedInputs, ProcessEvent, ProjectConfiguration, ProjectGraph, ProjectGraphDependency, ProjectGraphProjectNode, RuntimeInput, TagRelationships, TargetConfiguration, TargetDependencyConfig, Task, TaskExecutionOptions, TaskExecutor, TaskGraph, TaskHashDetails, TaskResult, TaskResults, TaskRunnerContext, TaskRunnerOptions, TasksRunner, TaskStatus, TaskTarget, TypeBoundaries, WorkspaceConfiguration, } from "./types.d.ts";
48
+ export type { AffectedScope, ConcurrentCloseEvent, ConcurrentCommandConfig, ConcurrentCommandInput, ConcurrentRunnerOptions, ConcurrentRunResult, ConstraintsConfig, ConstraintViolation, DependencyKindRules, DependencyType, EnvironmentInput, ExternalDependencyInput, FileSetBase, FileSetInput, FileSetPattern, InputDefinition, LifeCycleInterface, NamedInputs, OutputSpec, ProcessEvent, ProjectConfiguration, ProjectGraph, ProjectGraphDependency, ProjectGraphProjectNode, RuntimeInput, TagRelationships, TargetConfiguration, TargetDependencyConfig, Task, TaskExecutionOptions, TaskExecutor, TaskGraph, TaskHashDetails, TaskPriority, TaskResult, TaskResults, TaskRunnerContext, TaskRunnerOptions, TasksRunner, TaskStatus, TaskTarget, TypeBoundaries, WorkspaceConfiguration, } from "./types.d.ts";
44
49
  export { collectFiles, createFailureResult, hashFile, hashStrings, readPackageDeps, resolveTaskCwd, sortObjectKeys, uniqueId } from "./utils.d.ts";
package/dist/index.js CHANGED
@@ -1,28 +1,31 @@
1
- export { buildForwardDependencyMap, buildReverseDependencyMap, expandAffected, filterAffectedTasks, getAffectedProjects, getChangedFiles } from './packem_shared/buildForwardDependencyMap-0BJFMMPv.js';
2
- export { Cache, DEFAULT_CACHE_DIRECTORY_NAME, formatCacheSize, parseCacheSize } from './packem_shared/Cache-iAjRMV2d.js';
1
+ export { buildForwardDependencyMap, buildReverseDependencyMap, expandAffected, filterAffectedTasks, getAffectedProjects, getChangedFiles } from './packem_shared/buildForwardDependencyMap-DLPgKEto.js';
2
+ export { Cache, DEFAULT_CACHE_DIRECTORY_NAME, formatCacheSize, parseCacheSize } from './packem_shared/Cache-CWaX_c8U.js';
3
+ export { toChromeTrace, writeChromeTrace } from './packem_shared/toChromeTrace-B2tZoJ-7.js';
3
4
  export { parseCommands } from './packem_shared/parseCommands-D-IgF8Zh.js';
4
- export { runConcurrently } from './packem_shared/runConcurrently-qrkWyzXW.js';
5
- export { runConcurrentFallback } from './packem_shared/runConcurrentFallback-CGHz_f-Q.js';
6
- export { defaultTaskRunner } from './packem_shared/defaultTaskRunner-BdFTifsh.js';
5
+ export { runConcurrently } from './packem_shared/runConcurrently-CmfC4r-f.js';
6
+ export { runConcurrentFallback } from './packem_shared/runConcurrentFallback-BTmgGV1H.js';
7
+ export { defaultTaskRunner } from './packem_shared/defaultTaskRunner-Cp7jCmIl.js';
7
8
  export { detectScriptShell } from './packem_shared/detectScriptShell-CR-xXKA4.js';
8
- export { FileAccessTracker, generatePreloadScript } from './packem_shared/FileAccessTracker-CrtBAt5D.js';
9
- export { FingerprintManager } from './packem_shared/FingerprintManager-Cu-ta9ee.js';
9
+ export { FileAccessTracker, generatePreloadScript } from './packem_shared/FileAccessTracker-CQ5Ot7Hd.js';
10
+ export { FingerprintManager } from './packem_shared/FingerprintManager-CV7U4f4f.js';
10
11
  export { detectFrameworks, getFrameworkEnvVariables, inferFrameworkEnvPatterns } from './packem_shared/detectFrameworks-CeFzKE6J.js';
11
- export { projectGraphToDot, toGraphAscii, toGraphHtml, toGraphJson, toGraphvizDot } from './packem_shared/projectGraphToDot-C8uYeaPo.js';
12
- export { IncrementalFileHasher } from './packem_shared/IncrementalFileHasher-Cm_kJY5V.js';
13
- export { CompositeLifeCycle, ConsoleLifeCycle, EmptyLifeCycle } from './packem_shared/CompositeLifeCycle-7AtYw1dv.js';
14
- export { LockfileHasher, extractPackageName, parseNpmLockfile, parsePnpmLockfile, parseYarnLockfile } from './packem_shared/extractPackageName-CbVNW-dr.js';
12
+ export { projectGraphToDot, toGraphAscii, toGraphHtml, toGraphJson, toGraphvizDot } from './packem_shared/projectGraphToDot-DU1lSe-c.js';
13
+ export { IncrementalFileHasher } from './packem_shared/IncrementalFileHasher-BRS76-mb.js';
14
+ export { CompositeLifeCycle, ConsoleLifeCycle, EmptyLifeCycle } from './packem_shared/CompositeLifeCycle-CSVbRC_5.js';
15
+ export { LockfileHasher, extractPackageName, parseNpmLockfile, parsePnpmLockfile, parseYarnLockfile } from './packem_shared/extractPackageName-BllKetnz.js';
16
+ export { LogReporter, createLogReporter } from './packem_shared/LogReporter-BDt52HLu.js';
15
17
  export { isNativeAvailable, loadNativeBindings } from './packem_shared/isNativeAvailable-BpD28A6Z.js';
18
+ export { resolveOutputs } from './packem_shared/resolveOutputs-n6MCKoTe.js';
16
19
  export { enforceProjectConstraints } from './packem_shared/enforceProjectConstraints-C5Jp_C3u.js';
17
- export { RemoteCache } from './packem_shared/RemoteCache-BFceSe4a.js';
18
- export { generateRunSummary, writeRunSummary } from './packem_shared/generateRunSummary-qn-_jKwt.js';
19
- export { createTaskGraph, getTaskId, parseTaskId } from './packem_shared/createTaskGraph-CcsFaSrz.js';
20
+ export { RemoteCache } from './packem_shared/RemoteCache-DSU3lc87.js';
21
+ export { generateRunSummary, getLastRunSummaryPath, readLastRunSummary, writeLastRunSummary, writeRunSummary } from './packem_shared/generateRunSummary-BE1jnQ3H.js';
22
+ export { createTaskGraph, getTaskId, parseTaskId } from './packem_shared/createTaskGraph-B7nH0kY_.js';
20
23
  export { findCycle, findCycles, getDependentTasks, getLeafTasks, getTransitiveDependencies, makeAcyclic, reverseTaskGraph, walkTaskGraph } from './packem_shared/findCycle-DefgNYhg.js';
21
- export { InProcessTaskHasher, computeTaskHash } from './packem_shared/computeTaskHash-B2SVZqgp.js';
22
- export { TaskOrchestrator } from './packem_shared/TaskOrchestrator-lLn-PH1m.js';
23
- export { TaskScheduler, parsePartition } from './packem_shared/parsePartition-C4-P5RjK.js';
24
- export { TerminalBuffer } from './packem_shared/TerminalBuffer-CnPyFgPB.js';
25
- export { TrackedTaskExecutor } from './packem_shared/TrackedTaskExecutor-BGUKFE-7.js';
24
+ export { InProcessTaskHasher, computeTaskHash } from './packem_shared/computeTaskHash-DYqfrDGq.js';
25
+ export { TaskOrchestrator } from './packem_shared/TaskOrchestrator-rf45vW5c.js';
26
+ export { TaskScheduler, parsePartition } from './packem_shared/parsePartition-BfLbHGAx.js';
27
+ export { TerminalBuffer } from './packem_shared/TerminalBuffer-qVJvbRQZ.js';
28
+ export { TrackedTaskExecutor } from './packem_shared/TrackedTaskExecutor-CFPpQfXF.js';
26
29
  export { c as collectFiles, a as createFailureResult, h as hashFile, b as hashStrings, r as readPackageDeps, d as resolveTaskCwd, s as sortObjectKeys, u as uniqueId } from './packem_shared/utils-zO0ZRgtf.js';
27
30
  export { createInputHandler } from './packem_shared/createInputHandler-DTfePcTG.js';
28
31
  export { expandArguments } from './packem_shared/expandArguments-0AwD2BIA.js';
@@ -13,6 +13,8 @@ declare class CompositeLifeCycle implements LifeCycleInterface {
13
13
  endTasks(taskResults: TaskResult[]): void;
14
14
  printTaskTerminalOutput(task: Task, status: TaskStatus, terminalOutput: string): void;
15
15
  printCacheMiss(task: Task, reasons: string): void;
16
+ onTaskStdout(task: Task, chunk: string): void;
17
+ onTaskStderr(task: Task, chunk: string): void;
16
18
  }
17
19
  /**
18
20
  * A lifecycle handler that logs task progress to the console.
@@ -0,0 +1,34 @@
1
+ import type { LifeCycleInterface, Task, TaskResult, TaskStatus } from "./types.d.ts";
2
+ /**
3
+ * Output formatting mode for task terminal output.
4
+ *
5
+ * - `interleaved` **(default)**: emit each task's buffered output as-is
6
+ * — lines from parallel tasks may intermix when streamed live.
7
+ * - `labeled`: prefix every line with `[project#target]` so parallel
8
+ * tasks remain distinguishable.
9
+ * - `grouped`: buffer each task's output and print it as a single block
10
+ * bracketed by `── project#target ──` header and blank-line footer.
11
+ *
12
+ * Matches the three modes exposed by vite-task's `--log` flag.
13
+ */
14
+ export type LogMode = "grouped" | "interleaved" | "labeled";
15
+ /**
16
+ * A lifecycle handler that renders task terminal output per {@link LogMode}.
17
+ *
18
+ * Operates on the buffered `printTaskTerminalOutput` signal the orchestrator
19
+ * emits at task-completion. Line-by-line streaming is the consumer's
20
+ * responsibility — a streaming reporter can wrap this one and emit buffered
21
+ * output at the end of each task regardless of streaming choices.
22
+ */
23
+ export declare class LogReporter implements LifeCycleInterface {
24
+ #private;
25
+ constructor(mode: LogMode, write?: (chunk: string) => void);
26
+ printTaskTerminalOutput(task: Task, _status: TaskStatus, terminalOutput: string): void;
27
+ endTasks(_taskResults: TaskResult[]): void;
28
+ }
29
+ /**
30
+ * Convenience factory matching vite-task's `createLogReporter(mode)` surface.
31
+ * Consumers that already compose their own lifecycle handlers can instantiate
32
+ * {@link LogReporter} directly.
33
+ */
34
+ export declare const createLogReporter: (mode: LogMode, write?: (chunk: string) => void) => LogReporter;
@@ -0,0 +1,20 @@
1
+ import type { OutputSpec } from "./types.d.ts";
2
+ /**
3
+ * Expands a task's `OutputSpec[]` into the concrete file list to
4
+ * archive:
5
+ *
6
+ * - literal paths → kept as-is (the archiver recursively copies
7
+ * directories, so `"dist"` captures its whole tree);
8
+ * - positive globs → expanded via `fs.glob`, filtered to files only;
9
+ * - negatives (`!pattern`) → applied to the combined result;
10
+ * - `{ auto: true }` → pulls in `autoWrites` entries that fall inside
11
+ * the workspace.
12
+ *
13
+ * Returns deduped, sorted workspace-relative paths so archives are
14
+ * byte-reproducible across invocations.
15
+ *
16
+ * Silent degradation: missing literal paths, empty glob matches, and
17
+ * `{ auto: true }` without tracked writes all contribute nothing
18
+ * rather than throwing.
19
+ */
20
+ export declare const resolveOutputs: (workspaceRoot: string, outputs: OutputSpec[] | undefined, autoWrites?: ReadonlyArray<string>) => Promise<string[]>;
@@ -22,17 +22,30 @@ const {
22
22
  mkdir,
23
23
  writeFile,
24
24
  rename,
25
- stat,
26
25
  rm,
27
- cp,
28
- readdir
26
+ readdir,
27
+ stat,
28
+ cp
29
29
  } = __cjs_getBuiltinModule("node:fs/promises");
30
30
  import { formatBytes, parseBytes } from '@visulima/humanizer';
31
31
  import { join, resolve, dirname } from '@visulima/path';
32
+ import { e as extractTarBrotli, c as createTarBrotli } from './archive-UQHAnZUa.js';
33
+ import { resolveOutputs } from './resolveOutputs-n6MCKoTe.js';
32
34
  import { u as uniqueId } from './utils-zO0ZRgtf.js';
33
35
 
34
36
  const DEFAULT_MAX_CACHE_AGE = 7 * 24 * 60 * 60 * 1e3;
35
37
  const DEFAULT_CACHE_DIRECTORY_NAME = ".task-runner-cache";
38
+ const assertSafeNamespace = (namespace) => {
39
+ if (namespace.includes("\0")) {
40
+ throw new Error("cacheNamespace: null bytes are not allowed.");
41
+ }
42
+ if (namespace.includes("/") || namespace.includes("\\")) {
43
+ throw new Error(`cacheNamespace: path separators are not allowed (received ${JSON.stringify(namespace)}).`);
44
+ }
45
+ if (namespace === "." || namespace === "..") {
46
+ throw new Error(`cacheNamespace: "${namespace}" would escape the cache subtree.`);
47
+ }
48
+ };
36
49
  const removeEntry = async (entryPath) => {
37
50
  try {
38
51
  await rm(entryPath, { force: true, recursive: true });
@@ -73,7 +86,11 @@ class Cache {
73
86
  #indexWriteQueue = Promise.resolve();
74
87
  constructor(options) {
75
88
  this.#workspaceRoot = options.workspaceRoot;
76
- this.#cacheDirectory = options.cacheDirectory ?? join(options.workspaceRoot, DEFAULT_CACHE_DIRECTORY_NAME);
89
+ const baseDirectory = options.cacheDirectory ?? join(options.workspaceRoot, DEFAULT_CACHE_DIRECTORY_NAME);
90
+ if (options.cacheNamespace !== void 0 && options.cacheNamespace.length > 0) {
91
+ assertSafeNamespace(options.cacheNamespace);
92
+ }
93
+ this.#cacheDirectory = options.cacheNamespace && options.cacheNamespace.length > 0 ? join(baseDirectory, "ns", options.cacheNamespace) : baseDirectory;
77
94
  this.#maxCacheAge = options.maxCacheAge ?? DEFAULT_MAX_CACHE_AGE;
78
95
  this.#maxCacheSize = options.maxCacheSize ? parseCacheSize(options.maxCacheSize) : void 0;
79
96
  }
@@ -117,8 +134,14 @@ class Cache {
117
134
  *
118
135
  * Uses atomic write: builds the entry in a temporary directory,
119
136
  * then renames into place to avoid partial reads by concurrent processes.
137
+ *
138
+ * `outputs` accepts the richer `OutputSpec[]` shape — glob
139
+ * patterns, negative globs, and `{ auto: true }` entries. Pass
140
+ * `autoWrites` alongside `{ auto: true }` so the resolver knows
141
+ * which files the task actually wrote; otherwise auto entries
142
+ * contribute nothing.
120
143
  */
121
- async put(hash, terminalOutput, outputs, code, fingerprint) {
144
+ async put(hash, terminalOutput, outputs, code, fingerprint, autoWrites) {
122
145
  const cacheEntryDirectory = join(this.#cacheDirectory, hash);
123
146
  const temporaryDirectory = join(this.#cacheDirectory, `.tmp-${hash}-${uniqueId()}`);
124
147
  try {
@@ -126,7 +149,7 @@ class Cache {
126
149
  const writes = [
127
150
  writeFile(join(temporaryDirectory, "code"), String(code)),
128
151
  writeFile(join(temporaryDirectory, "terminalOutput"), terminalOutput),
129
- this.#archiveOutputs(temporaryDirectory, outputs)
152
+ this.#archiveOutputs(temporaryDirectory, outputs, autoWrites)
130
153
  ];
131
154
  if (fingerprint) {
132
155
  writes.push(writeFile(join(temporaryDirectory, "fingerprint.json"), JSON.stringify(fingerprint)));
@@ -140,36 +163,21 @@ class Cache {
140
163
  }
141
164
  }
142
165
  /**
143
- * Restores cached outputs to their original locations.
166
+ * Restores cached outputs from the compressed `outputs.tar.br`
167
+ * archive. Returns `true` either when the archive was extracted
168
+ * successfully OR when the entry simply has no outputs to restore.
169
+ *
170
+ * The restore flow stages into a temp directory, then swaps each
171
+ * top-level entry into place (see {@link restoreOutputsCompressed})
172
+ * so a mid-restore failure never destroys the user's working tree.
173
+ * The `outputs` parameter is no longer consulted at restore time —
174
+ * the archive is authoritative, and top-level entries in the
175
+ * extracted staging become the set of swap roots. Still accepted
176
+ * for backward compat.
144
177
  */
145
- async restoreOutputs(hash, outputs) {
178
+ async restoreOutputs(hash, _outputs) {
146
179
  const cacheEntryDirectory = join(this.#cacheDirectory, hash);
147
- const outputsDirectory = join(cacheEntryDirectory, "outputs");
148
- try {
149
- await stat(outputsDirectory);
150
- } catch {
151
- return true;
152
- }
153
- try {
154
- await Promise.all(
155
- outputs.map(async (output) => {
156
- const absoluteOutput = resolve(this.#workspaceRoot, output);
157
- const cachedOutput = join(outputsDirectory, output);
158
- try {
159
- await stat(cachedOutput);
160
- } catch {
161
- return;
162
- }
163
- const parentDirectory = dirname(absoluteOutput);
164
- await mkdir(parentDirectory, { recursive: true });
165
- await rm(absoluteOutput, { force: true, recursive: true });
166
- await cp(cachedOutput, absoluteOutput, { recursive: true });
167
- })
168
- );
169
- return true;
170
- } catch {
171
- return false;
172
- }
180
+ return restoreOutputsCompressed(cacheEntryDirectory, this.#workspaceRoot);
173
181
  }
174
182
  /**
175
183
  * Retrieves the most recent cached result for a task by its ID.
@@ -276,23 +284,105 @@ class Cache {
276
284
  }
277
285
  }
278
286
  /**
279
- * Archives task output files into the cache.
287
+ * Archives task output files into the cache as a single
288
+ * brotli-compressed tarball (`outputs.tar.br`). See
289
+ * {@link archiveOutputsCompressed} for the staging + compression
290
+ * flow.
280
291
  */
281
- async #archiveOutputs(cacheEntryDirectory, outputs) {
282
- const outputsDirectory = join(cacheEntryDirectory, "outputs");
283
- await mkdir(outputsDirectory, { recursive: true });
284
- for (const output of outputs) {
285
- const absoluteOutput = resolve(this.#workspaceRoot, output);
286
- const cachedOutput = join(outputsDirectory, output);
292
+ async #archiveOutputs(cacheEntryDirectory, outputs, autoWrites) {
293
+ await archiveOutputsCompressed(this.#workspaceRoot, cacheEntryDirectory, outputs, autoWrites);
294
+ }
295
+ }
296
+ const OUTPUTS_ARCHIVE_FILENAME = "outputs.tar.br";
297
+ const archiveOutputsCompressed = async (workspaceRoot, cacheEntryDirectory, outputs, autoWrites) => {
298
+ if (outputs.length === 0) {
299
+ return;
300
+ }
301
+ const resolvedPaths = await resolveOutputs(workspaceRoot, outputs, autoWrites);
302
+ if (resolvedPaths.length === 0) {
303
+ return;
304
+ }
305
+ const stagingDirectory = join(cacheEntryDirectory, `.outputs-stage-${uniqueId()}`);
306
+ const finalPath = join(cacheEntryDirectory, OUTPUTS_ARCHIVE_FILENAME);
307
+ try {
308
+ await mkdir(stagingDirectory, { recursive: true });
309
+ const stagedFlags = await runBounded(STAGE_CONCURRENCY, resolvedPaths, async (relativePath) => {
310
+ const absoluteOutput = resolve(workspaceRoot, relativePath);
311
+ const stagedOutput = join(stagingDirectory, relativePath);
312
+ try {
313
+ await mkdir(dirname(stagedOutput), { recursive: true });
314
+ await cp(absoluteOutput, stagedOutput, { recursive: true });
315
+ return true;
316
+ } catch {
317
+ return false;
318
+ }
319
+ });
320
+ const stagedCount = stagedFlags.filter(Boolean).length;
321
+ if (stagedCount === 0) {
322
+ return;
323
+ }
324
+ await createTarBrotli(stagingDirectory, finalPath);
325
+ } finally {
326
+ await rm(stagingDirectory, { force: true, recursive: true }).catch(() => {
327
+ });
328
+ }
329
+ };
330
+ const STAGE_CONCURRENCY = 16;
331
+ const runBounded = async (limit, items, fn) => {
332
+ const results = Array.from({ length: items.length });
333
+ let cursor = 0;
334
+ const worker = async () => {
335
+ while (cursor < items.length) {
336
+ const index = cursor;
337
+ cursor += 1;
338
+ results[index] = await fn(items[index], index);
339
+ }
340
+ };
341
+ const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
342
+ await Promise.all(workers);
343
+ return results;
344
+ };
345
+ const restoreOutputsCompressed = async (cacheEntryDirectory, workspaceRoot) => {
346
+ const archivePath = join(cacheEntryDirectory, OUTPUTS_ARCHIVE_FILENAME);
347
+ try {
348
+ await stat(archivePath);
349
+ } catch {
350
+ return true;
351
+ }
352
+ const stagingDirectory = join(cacheEntryDirectory, `.restore-${uniqueId()}`);
353
+ const backups = [];
354
+ try {
355
+ await mkdir(stagingDirectory, { recursive: true });
356
+ await extractTarBrotli(archivePath, stagingDirectory);
357
+ const topLevel = await readdir(stagingDirectory);
358
+ await runBounded(STAGE_CONCURRENCY, topLevel, async (entry) => {
359
+ const absoluteOutput = resolve(workspaceRoot, entry);
360
+ const stagedOutput = join(stagingDirectory, entry);
361
+ await mkdir(dirname(absoluteOutput), { recursive: true });
287
362
  try {
288
363
  await stat(absoluteOutput);
289
- const cachedOutputParent = join(cachedOutput, "..");
290
- await mkdir(cachedOutputParent, { recursive: true });
291
- await cp(absoluteOutput, cachedOutput, { recursive: true });
364
+ const backup = `${absoluteOutput}.pre-restore-${uniqueId()}`;
365
+ await rename(absoluteOutput, backup);
366
+ backups.push({ backup, original: absoluteOutput });
292
367
  } catch {
293
368
  }
369
+ await cp(stagedOutput, absoluteOutput, { recursive: true });
370
+ });
371
+ await Promise.all(backups.map(({ backup }) => rm(backup, { force: true, recursive: true }).catch(() => {
372
+ })));
373
+ return true;
374
+ } catch {
375
+ for (const { backup, original } of backups) {
376
+ await rm(original, { force: true, recursive: true }).catch(() => {
377
+ });
378
+ await rename(backup, original).catch(() => {
379
+ });
294
380
  }
381
+ return false;
382
+ } finally {
383
+ await rm(stagingDirectory, { force: true, recursive: true }).catch(() => {
384
+ });
295
385
  }
296
- }
386
+ };
297
387
 
298
388
  export { Cache, DEFAULT_CACHE_DIRECTORY_NAME, formatCacheSize, parseCacheSize };