@vzn/vx 0.0.645 → 0.0.648
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/README.md +39 -50
- package/launcher.cjs +9 -2
- package/launcher.sh +2 -2
- package/package.json +5 -5
- package/schemas/cache.json +5 -5
- package/schemas/error.json +1 -1
- package/schemas/info.json +16 -2
- package/schemas/last.json +127 -5
- package/schemas/plan.json +28 -2
- package/schemas/show.json +10 -1
- package/skills/vx/SKILL.md +19 -3
- package/src/bin.ts +54 -13
- package/src/cache/archive.ts +324 -23
- package/src/cache/cache.ts +330 -65
- package/src/cache/git-inputs.ts +274 -97
- package/src/cache/index.ts +7 -2
- package/src/cache/inputs.ts +515 -83
- package/src/cache/key-fold.ts +15 -6
- package/src/cache/layer.ts +74 -11
- package/src/cache/layered-cache.ts +154 -59
- package/src/cache/linked-inputs.ts +1 -1
- package/src/cache/output-index.ts +44 -14
- package/src/cache/plugin-cache.ts +49 -23
- package/src/cache/remote-hits.ts +72 -0
- package/src/cache/remote-lane.ts +100 -15
- package/src/cache/run-history.ts +77 -23
- package/src/cache/run-store.ts +11 -10
- package/src/cache/schema.ts +42 -6
- package/src/cache/tar-stream.ts +5 -0
- package/src/cache/umask.ts +1 -2
- package/src/cli/cache.ts +58 -27
- package/src/cli/completions.ts +12 -5
- package/src/cli/core-alias.ts +5 -2
- package/src/cli/docs.ts +42 -16
- package/src/cli/help.ts +62 -25
- package/src/cli/index.ts +79 -45
- package/src/cli/info.ts +12 -6
- package/src/cli/init.ts +222 -34
- package/src/cli/last.ts +443 -47
- package/src/cli/lock.ts +197 -52
- package/src/cli/plan-format.ts +91 -49
- package/src/cli/plugin-commands.ts +160 -11
- package/src/cli/plugin-templates.ts +13 -13
- package/src/cli/registry.ts +209 -0
- package/src/cli/run.ts +111 -41
- package/src/cli/select.ts +148 -49
- package/src/cli/show.ts +59 -16
- package/src/cli/task-verb.ts +16 -6
- package/src/cli/upgrade.ts +155 -54
- package/src/cli/watch-cycle.ts +10 -5
- package/src/cli/watch-filter.ts +71 -3
- package/src/cli/watch-fs.ts +22 -6
- package/src/cli/watch-judge.ts +48 -6
- package/src/cli/watch.ts +163 -42
- package/src/cli/why.ts +211 -73
- package/src/cli/workspace-config.ts +54 -15
- package/src/config.ts +13 -1
- package/src/exec/env.ts +7 -2
- package/src/exec/executor.ts +262 -15
- package/src/exec/index.ts +8 -2
- package/src/exec/kill-tree.ts +15 -0
- package/src/exec/local-executor.ts +1 -1
- package/src/exec/proc-sample.ts +70 -6
- package/src/exec/runner.ts +256 -53
- package/src/exec/sandbox-binds.ts +7 -3
- package/src/exec/sandbox-paths.ts +1 -1
- package/src/exec/sandbox-runtime.ts +222 -28
- package/src/exec/sandbox-violations.ts +52 -20
- package/src/graph/critical-path.ts +79 -0
- package/src/graph/default-build.ts +19 -0
- package/src/graph/definition.ts +38 -0
- package/src/graph/folds.ts +38 -5
- package/src/graph/index.ts +4 -1
- package/src/graph/priorities.ts +74 -8
- package/src/graph/scheduler.ts +326 -65
- package/src/graph/task-graph.ts +228 -35
- package/src/handoff.ts +97 -9
- package/src/history/failure-mode.ts +6 -3
- package/src/history/history.ts +268 -50
- package/src/history/index.ts +2 -0
- package/src/history/metrics.ts +142 -45
- package/src/index.ts +42 -10
- package/src/orchestrator/admission.ts +10 -6
- package/src/orchestrator/affected-tasks.ts +11 -6
- package/src/orchestrator/deferred-outputs.ts +2 -0
- package/src/orchestrator/doctor.ts +134 -42
- package/src/orchestrator/execute-args.ts +6 -0
- package/src/orchestrator/execute-task.ts +155 -42
- package/src/orchestrator/fingerprint-watch.ts +1 -1
- package/src/orchestrator/graph-memo.ts +140 -0
- package/src/orchestrator/graph-stage.ts +18 -1
- package/src/orchestrator/held-servers.ts +86 -0
- package/src/orchestrator/hit-restore.ts +227 -122
- package/src/orchestrator/index.ts +20 -3
- package/src/orchestrator/local-floor.ts +198 -0
- package/src/orchestrator/local-shortcircuit.ts +51 -8
- package/src/orchestrator/lockfile-claim.ts +96 -16
- package/src/orchestrator/miss-save.ts +52 -12
- package/src/orchestrator/options.ts +8 -6
- package/src/orchestrator/output-host.ts +43 -14
- package/src/orchestrator/output-log.ts +90 -10
- package/src/orchestrator/persistent-task.ts +97 -28
- package/src/orchestrator/persistent.ts +16 -4
- package/src/orchestrator/placement.ts +257 -14
- package/src/orchestrator/plan.ts +64 -7
- package/src/orchestrator/plugin-host.ts +230 -40
- package/src/orchestrator/plugin.ts +150 -37
- package/src/orchestrator/prepare.ts +178 -32
- package/src/orchestrator/projects.ts +65 -40
- package/src/orchestrator/remote-prefetch.ts +119 -25
- package/src/orchestrator/run-artifacts.ts +95 -32
- package/src/orchestrator/run-context.ts +39 -41
- package/src/orchestrator/run-failures.ts +65 -23
- package/src/orchestrator/run-id.ts +6 -3
- package/src/orchestrator/run-keys.ts +300 -0
- package/src/orchestrator/run-live.ts +294 -0
- package/src/orchestrator/run-lock.ts +13 -7
- package/src/orchestrator/run-records.ts +35 -23
- package/src/orchestrator/run-report.ts +3 -3
- package/src/orchestrator/run-steps.ts +58 -22
- package/src/orchestrator/run-stop.ts +22 -5
- package/src/orchestrator/run.ts +409 -79
- package/src/orchestrator/sandbox-fix.ts +7 -5
- package/src/orchestrator/sandbox-request.ts +78 -22
- package/src/orchestrator/shell-verdict.ts +5 -5
- package/src/orchestrator/signals.ts +26 -1
- package/src/orchestrator/stable-keys.ts +92 -8
- package/src/orchestrator/steps.ts +2 -2
- package/src/orchestrator/task-artifacts.ts +206 -0
- package/src/orchestrator/task-hash.ts +89 -14
- package/src/orchestrator/task-log-buffer.ts +72 -36
- package/src/orchestrator/task-process.ts +22 -0
- package/src/orchestrator/telemetry-host.ts +24 -3
- package/src/orchestrator/telemetry.ts +113 -38
- package/src/orchestrator/upstream.ts +15 -0
- package/src/report/events.ts +32 -13
- package/src/report/forecast.ts +36 -35
- package/src/report/framed-output.ts +173 -46
- package/src/report/index.ts +1 -0
- package/src/report/logger.ts +217 -186
- package/src/report/path-links.ts +12 -4
- package/src/report/plain-output.ts +13 -5
- package/src/report/status-line.ts +94 -46
- package/src/report/summary.ts +174 -38
- package/src/report/terminal-signals.ts +13 -2
- package/src/settings.ts +14 -6
- package/src/util/bun-version.ts +4 -1
- package/src/util/colors.ts +20 -3
- package/src/util/config-frame.ts +41 -4
- package/src/{graph → util}/dependency-spec.ts +22 -13
- package/src/util/edit-distance.ts +13 -1
- package/src/util/env-flag.ts +9 -0
- package/src/util/errors.ts +59 -3
- package/src/util/essential-env.ts +57 -0
- package/src/util/git-ignored.ts +1 -1
- package/src/util/hash.ts +35 -0
- package/src/util/hide-env.ts +45 -5
- package/src/util/index.ts +22 -5
- package/src/util/num.ts +8 -0
- package/src/util/paths.ts +121 -21
- package/src/util/report.ts +6 -1
- package/src/util/secret-mask.ts +62 -1
- package/src/util/secrets.ts +7 -2
- package/src/util/size.ts +11 -3
- package/src/util/tail.ts +6 -1
- package/src/util/timing.ts +13 -1
- package/src/util/ui.ts +65 -4
- package/src/util/which.ts +1 -1
- package/src/util/write-file.ts +67 -0
- package/src/workspace/affected.ts +710 -107
- package/src/workspace/bracket-literal.ts +84 -0
- package/src/workspace/canonical-dir.ts +37 -0
- package/src/workspace/config-bytecode.ts +282 -0
- package/src/workspace/config-eval.ts +29 -8
- package/src/workspace/config-imports.ts +199 -58
- package/src/workspace/config-schema.ts +242 -39
- package/src/workspace/filter.ts +37 -8
- package/src/workspace/index.ts +11 -2
- package/src/workspace/json-data.ts +3 -4
- package/src/workspace/lockfile.ts +57 -8
- package/src/workspace/migration.ts +72 -49
- package/src/workspace/package-graph.ts +5 -4
- package/src/workspace/project-loader.ts +314 -47
- package/src/workspace/repo-id.ts +1 -1
- package/src/workspace/workspace.ts +176 -33
- package/src/cli/foreign-flags.ts +0 -727
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// A server the run's local store cannot settle: some dependant missed here
|
|
2
|
+
// but may be a hit the remote holds. The scheduler holds such a server
|
|
3
|
+
// until the remote answers; when every dependant hit, the server never
|
|
4
|
+
// starts (2026-10-22), as it never starts behind local hits.
|
|
5
|
+
|
|
6
|
+
import type { TaskNode } from '../graph/index.js'
|
|
7
|
+
import type { CacheEntry, CacheLayer } from '../cache/index.js'
|
|
8
|
+
import type { ShortCircuit } from './local-shortcircuit.js'
|
|
9
|
+
import { getContext } from './remote-prefetch.js'
|
|
10
|
+
|
|
11
|
+
/** The persistent tasks nobody asked for whose every dependant is a local hit or a key the remote may hold. */
|
|
12
|
+
export function serversToHold(
|
|
13
|
+
nodes: ReadonlyMap<string, TaskNode>,
|
|
14
|
+
restoreTier: ReadonlySet<string>,
|
|
15
|
+
remoteAsk: ReadonlyMap<string, string>,
|
|
16
|
+
): Map<string, string[]> {
|
|
17
|
+
const out = new Map<string, string[]>()
|
|
18
|
+
if (remoteAsk.size === 0) return out
|
|
19
|
+
const dependents = new Map<string, string[]>()
|
|
20
|
+
for (const n of nodes.values())
|
|
21
|
+
for (const d of n.deps) {
|
|
22
|
+
const list = dependents.get(d)
|
|
23
|
+
if (list === undefined) dependents.set(d, [n.id])
|
|
24
|
+
else list.push(n.id)
|
|
25
|
+
}
|
|
26
|
+
for (const n of nodes.values()) {
|
|
27
|
+
if (n.config.exec?.persistent === undefined || n.requested || n.surfaced === true) continue
|
|
28
|
+
const ds = dependents.get(n.id)
|
|
29
|
+
if (
|
|
30
|
+
ds !== undefined &&
|
|
31
|
+
ds.every((d) => restoreTier.has(d) || remoteAsk.has(d)) &&
|
|
32
|
+
ds.some((d) => remoteAsk.has(d))
|
|
33
|
+
)
|
|
34
|
+
out.set(n.id, ds)
|
|
35
|
+
}
|
|
36
|
+
return out
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* For each held server, whether the remote holds every dependant: one
|
|
41
|
+
* `get` per dependant through the layered cache, which shares the
|
|
42
|
+
* prefetch's request. All hits join the restore tier at once, so the
|
|
43
|
+
* scheduler finds the server idle; the first miss answers at once, with
|
|
44
|
+
* nothing promoted, so the server starts without waiting on the rest.
|
|
45
|
+
*/
|
|
46
|
+
export function askRemote(
|
|
47
|
+
held: ReadonlyMap<string, readonly string[]>,
|
|
48
|
+
nodes: ReadonlyMap<string, TaskNode>,
|
|
49
|
+
shortCircuit: Pick<ShortCircuit, 'preProbed' | 'restoreTier' | 'remoteAsk'>,
|
|
50
|
+
cache: CacheLayer,
|
|
51
|
+
): Map<string, Promise<readonly string[]>> {
|
|
52
|
+
const asked = new Map<string, Promise<CacheEntry | null>>()
|
|
53
|
+
const ask = (id: string): Promise<CacheEntry | null> => {
|
|
54
|
+
let p = asked.get(id)
|
|
55
|
+
if (p === undefined) {
|
|
56
|
+
const node = nodes.get(id)!
|
|
57
|
+
p = cache
|
|
58
|
+
.get(shortCircuit.remoteAsk.get(id)!, getContext(node, node.config.exec?.command ?? ''))
|
|
59
|
+
.catch(() => null)
|
|
60
|
+
asked.set(id, p)
|
|
61
|
+
}
|
|
62
|
+
return p
|
|
63
|
+
}
|
|
64
|
+
const out = new Map<string, Promise<readonly string[]>>()
|
|
65
|
+
for (const [server, ds] of held) {
|
|
66
|
+
const remote = ds.filter((d) => shortCircuit.remoteAsk.has(d))
|
|
67
|
+
out.set(
|
|
68
|
+
server,
|
|
69
|
+
new Promise((resolve) => {
|
|
70
|
+
const hits = new Map<string, CacheEntry>()
|
|
71
|
+
for (const d of remote)
|
|
72
|
+
void ask(d).then((hit) => {
|
|
73
|
+
if (hit === null) return resolve([])
|
|
74
|
+
hits.set(d, hit)
|
|
75
|
+
if (hits.size < remote.length) return
|
|
76
|
+
for (const [id, entry] of hits) {
|
|
77
|
+
shortCircuit.preProbed.set(id, { hash: shortCircuit.remoteAsk.get(id)!, hit: entry })
|
|
78
|
+
shortCircuit.restoreTier.add(id)
|
|
79
|
+
}
|
|
80
|
+
resolve(remote)
|
|
81
|
+
})
|
|
82
|
+
}),
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
return out
|
|
86
|
+
}
|
|
@@ -14,8 +14,10 @@ import {
|
|
|
14
14
|
type CacheEntry,
|
|
15
15
|
cleanOutputPaths,
|
|
16
16
|
committedFiles,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
OUTPUT_DIRS_OVER_CAP,
|
|
18
|
+
outputWalkTakes,
|
|
19
|
+
resolveOutputTree,
|
|
20
|
+
resolveWorkspaceOutputTree,
|
|
19
21
|
sweepRestoreTemps,
|
|
20
22
|
WORKSPACE_OUTPUT_PREFIX,
|
|
21
23
|
} from '../cache/index.js'
|
|
@@ -27,11 +29,11 @@ import {
|
|
|
27
29
|
span,
|
|
28
30
|
splitNegations,
|
|
29
31
|
taskGlob,
|
|
30
|
-
|
|
32
|
+
outputDirPrefixes,
|
|
31
33
|
} from '../util/index.js'
|
|
32
34
|
import type { ExecuteArgs } from './execute-task.js'
|
|
33
35
|
import { entryHolds } from './miss-save.js'
|
|
34
|
-
import { decodeOutputLog } from './output-log.js'
|
|
36
|
+
import { decodeOutputLog, maskStreams } from './output-log.js'
|
|
35
37
|
|
|
36
38
|
export interface RestoreHitArgs {
|
|
37
39
|
args: ExecuteArgs
|
|
@@ -53,28 +55,33 @@ export interface RestoreHitArgs {
|
|
|
53
55
|
* schedule using the IDENTICAL logic — there is one restore path, not
|
|
54
56
|
* two that could drift.
|
|
55
57
|
*/
|
|
56
|
-
export async function restoreHit(
|
|
57
|
-
const { args, hash
|
|
58
|
-
const { node
|
|
58
|
+
export async function restoreHit(given: RestoreHitArgs): Promise<TaskOutcome> {
|
|
59
|
+
const { args, hash } = given
|
|
60
|
+
const { node } = args
|
|
59
61
|
const cacheCfg: CacheConfig | undefined = node.config.cache
|
|
60
62
|
const outputs = cacheCfg?.outputs.files ?? []
|
|
61
63
|
const wsOutputs = cacheCfg?.outputs.workspaceFiles ?? []
|
|
62
64
|
const anyOutputs = outputs.length > 0 || wsOutputs.length > 0
|
|
65
|
+
// A layer that handed no rows: one SELECT, read by both paths below.
|
|
66
|
+
const restore =
|
|
67
|
+
anyOutputs && given.hit.outputRows === undefined
|
|
68
|
+
? {
|
|
69
|
+
...given,
|
|
70
|
+
hit: {
|
|
71
|
+
...given.hit,
|
|
72
|
+
outputRows: args.cache.loadOutputFilesBatch([hash]).get(hash) ?? [],
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
: given
|
|
76
|
+
const { hit } = restore
|
|
77
|
+
const now = currentHit(restore)
|
|
78
|
+
if (now !== undefined) return now()
|
|
63
79
|
// The directories the positive globs cover whole. A `!` entry inside one
|
|
64
80
|
// leaves the snapshot sound: a file added or removed there still bumps a
|
|
65
81
|
// recorded directory, and the walk that follows applies the `!` (A-46).
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
outputs,
|
|
70
|
-
nestedProjectDirs: args.nestedProjectDirs,
|
|
71
|
-
committed: committedFiles(args.gitFilesCache, node.projectDir),
|
|
72
|
-
}
|
|
73
|
-
const wsCleanArgs = {
|
|
74
|
-
workspaceRoot: args.workspaceRoot,
|
|
75
|
-
outputs: wsOutputs,
|
|
76
|
-
committed: committedFiles(args.gitFilesCache, args.workspaceRoot),
|
|
77
|
-
}
|
|
82
|
+
// Asked only of a task that declares outputs: most hits declare none,
|
|
83
|
+
// and a warm run of 1,000 such hits paid this set-up for each.
|
|
84
|
+
const dirPrefixes = anyOutputs ? dirPrefixesOf(outputs) : null
|
|
78
85
|
// The two sides of an overlapping pair (item 588). An ADDITIVE task owns
|
|
79
86
|
// only its recorded rows: its glob also selects the upstream's files it
|
|
80
87
|
// adds beside, so "no strays" is not a claim it can make, and its clean
|
|
@@ -83,9 +90,10 @@ export async function restoreHit(restore: RestoreHitArgs): Promise<TaskOutcome>
|
|
|
83
90
|
// addition below is not a stray — while its glob clean still takes them:
|
|
84
91
|
// the dependant restores or runs after it, by the edge.
|
|
85
92
|
const additive = (node.addsToOutputsOf?.length ?? 0) > 0
|
|
86
|
-
const addedGlobs =
|
|
87
|
-
.
|
|
88
|
-
|
|
93
|
+
const addedGlobs =
|
|
94
|
+
anyOutputs && node.outputsAddedToBy !== undefined
|
|
95
|
+
? node.outputsAddedToBy.flatMap((g) => asTrees([g])).map((g) => taskGlob(g))
|
|
96
|
+
: []
|
|
89
97
|
const isAddition = (rel: string): boolean => addedGlobs.some((g) => g.match(rel))
|
|
90
98
|
|
|
91
99
|
// "Tree is already current" short-circuit — skip cleanOutputs
|
|
@@ -112,11 +120,9 @@ export async function restoreHit(restore: RestoreHitArgs): Promise<TaskOutcome>
|
|
|
112
120
|
// The walk that found the tree stale, handed to the clean when nothing
|
|
113
121
|
// was awaited between them: the clean's own walk would only repeat it
|
|
114
122
|
// (X-161).
|
|
115
|
-
let walked: { proj:
|
|
123
|
+
let walked: { proj: Tree; ws: Tree } | undefined
|
|
116
124
|
if (anyOutputs) {
|
|
117
|
-
const
|
|
118
|
-
const expected = hit.outputRows ?? args.cache.loadOutputFilesBatch([hash]).get(hash) ?? []
|
|
119
|
-
endRows()
|
|
125
|
+
const expected = hit.outputRows ?? []
|
|
120
126
|
if (expected.length > 0) {
|
|
121
127
|
// Two namespaces in the rows: bare rels are project outputs,
|
|
122
128
|
// `workspace-outputs/<rel>` rows anchor at the workspace root.
|
|
@@ -124,86 +130,92 @@ export async function restoreHit(restore: RestoreHitArgs): Promise<TaskOutcome>
|
|
|
124
130
|
const wsExpected = expected
|
|
125
131
|
.filter((e) => e.path.startsWith(WORKSPACE_OUTPUT_PREFIX))
|
|
126
132
|
.map((e) => ({ ...e, path: e.path.slice(WORKSPACE_OUTPUT_PREFIX.length) }))
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// each row to a path and back was ~5 ms per 1,000 warm hits.
|
|
153
|
-
treeMatches = wsExpected.length === 0
|
|
154
|
-
} else {
|
|
155
|
-
const endGlob = span('output glob')
|
|
156
|
-
const actualAbs = await resolveOutputs({
|
|
157
|
-
projectDir: node.projectDir,
|
|
158
|
-
outputs,
|
|
159
|
-
nestedProjectDirs: args.nestedProjectDirs,
|
|
160
|
-
})
|
|
161
|
-
const actualWsAbs = await resolveWorkspaceOutputs({
|
|
162
|
-
workspaceRoot: args.workspaceRoot,
|
|
163
|
-
outputs: wsOutputs,
|
|
164
|
-
})
|
|
165
|
-
endGlob()
|
|
166
|
-
const setsMatch = (
|
|
167
|
-
actual: readonly string[],
|
|
168
|
-
exp: ReadonlyArray<{ path: string }>,
|
|
169
|
-
): boolean => {
|
|
170
|
-
const expSet = new Set(exp.map((e) => e.path).filter((p) => !p.endsWith('/')))
|
|
171
|
-
return actual.length === expSet.size && actual.every((r) => expSet.has(r))
|
|
172
|
-
}
|
|
173
|
-
// A stray a dependant's glob could have added is not a stray; a path
|
|
174
|
-
// this entry recorded is never dropped, even where that glob covers
|
|
175
|
-
// the whole tree (strapi's shape: both on `dist`).
|
|
176
|
-
const expectedRels = new Set(projExpected.map((e) => e.path))
|
|
177
|
-
const expectedWsRels = new Set(wsExpected.map((e) => e.path))
|
|
178
|
-
const actualRels = actualAbs
|
|
179
|
-
.map((p) => relPosix(node.projectDir, p))
|
|
180
|
-
.filter((rel) => expectedRels.has(rel) || !isAddition(rel))
|
|
181
|
-
const actualWsRels = actualWsAbs
|
|
182
|
-
.map((p) => relPosix(args.workspaceRoot, p))
|
|
183
|
-
.filter((rel) => expectedWsRels.has(rel) || !isAddition(rel))
|
|
184
|
-
const rowsPresent = (
|
|
185
|
-
actual: readonly string[],
|
|
186
|
-
exp: ReadonlyArray<{ path: string }>,
|
|
187
|
-
): boolean => {
|
|
188
|
-
const have = new Set(actual)
|
|
189
|
-
return exp.every((e) => e.path.endsWith('/') || have.has(e.path))
|
|
133
|
+
const endGlob = span('output glob')
|
|
134
|
+
const projTree = await resolveOutputTree({
|
|
135
|
+
projectDir: node.projectDir,
|
|
136
|
+
outputs,
|
|
137
|
+
nestedProjectDirs: args.nestedProjectDirs,
|
|
138
|
+
})
|
|
139
|
+
const wsTree = await resolveWorkspaceOutputTree({
|
|
140
|
+
workspaceRoot: args.workspaceRoot,
|
|
141
|
+
outputs: wsOutputs,
|
|
142
|
+
})
|
|
143
|
+
endGlob()
|
|
144
|
+
const actualAbs = projTree.files
|
|
145
|
+
const actualWsAbs = wsTree.files
|
|
146
|
+
// An empty directory is an entry row (`dist/empty/`): one the entry
|
|
147
|
+
// does not hold is a stray, as a file is. A directory row with a row
|
|
148
|
+
// under it is one kept for its mode (`DIR_MODE`), and the walk lists
|
|
149
|
+
// no such directory: only the files and empty directories it holds.
|
|
150
|
+
const setsMatch = (
|
|
151
|
+
actual: readonly string[],
|
|
152
|
+
exp: ReadonlyArray<{ path: string }>,
|
|
153
|
+
): boolean => {
|
|
154
|
+
const held = new Set<string>()
|
|
155
|
+
for (const { path: p } of exp) {
|
|
156
|
+
for (let i = p.lastIndexOf('/', p.length - 2); i > 0; i = p.lastIndexOf('/', i - 1))
|
|
157
|
+
held.add(p.slice(0, i + 1))
|
|
190
158
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
159
|
+
const expSet = new Set(
|
|
160
|
+
exp.map((e) => e.path).filter((p) => !p.endsWith('/') || !held.has(p)),
|
|
161
|
+
)
|
|
162
|
+
return actual.length === expSet.size && actual.every((r) => expSet.has(r))
|
|
163
|
+
}
|
|
164
|
+
const dirRels = (root: string, dirs: readonly string[]): string[] =>
|
|
165
|
+
dirs.map((d) => `${relPosix(root, d)}/`)
|
|
166
|
+
// A stray a dependant's glob could have added is not a stray; a path
|
|
167
|
+
// this entry recorded is never dropped, even where that glob covers
|
|
168
|
+
// the whole tree (strapi's shape: both on `dist`).
|
|
169
|
+
const expectedRels = new Set(projExpected.map((e) => e.path))
|
|
170
|
+
const expectedWsRels = new Set(wsExpected.map((e) => e.path))
|
|
171
|
+
const actualRels = actualAbs
|
|
172
|
+
.map((p) => relPosix(node.projectDir, p))
|
|
173
|
+
.filter((rel) => expectedRels.has(rel) || !isAddition(rel))
|
|
174
|
+
const actualWsRels = actualWsAbs
|
|
175
|
+
.map((p) => relPosix(args.workspaceRoot, p))
|
|
176
|
+
.filter((rel) => expectedWsRels.has(rel) || !isAddition(rel))
|
|
177
|
+
const actualDirRels = dirRels(node.projectDir, projTree.emptyDirs).filter(
|
|
178
|
+
(rel) => expectedRels.has(rel) || !isAddition(rel.slice(0, -1)),
|
|
179
|
+
)
|
|
180
|
+
const actualWsDirRels = dirRels(args.workspaceRoot, wsTree.emptyDirs).filter(
|
|
181
|
+
(rel) => expectedWsRels.has(rel) || !isAddition(rel.slice(0, -1)),
|
|
182
|
+
)
|
|
183
|
+
const rowsPresent = (
|
|
184
|
+
actual: readonly string[],
|
|
185
|
+
exp: ReadonlyArray<{ path: string }>,
|
|
186
|
+
): boolean => {
|
|
187
|
+
const have = new Set(actual)
|
|
188
|
+
return exp.every((e) => e.path.endsWith('/') || have.has(e.path))
|
|
195
189
|
}
|
|
190
|
+
const treeMatches = additive
|
|
191
|
+
? rowsPresent(actualRels, projExpected) && rowsPresent(actualWsRels, wsExpected)
|
|
192
|
+
: setsMatch([...actualRels, ...actualDirRels], projExpected) &&
|
|
193
|
+
setsMatch([...actualWsRels, ...actualWsDirRels], wsExpected)
|
|
194
|
+
if (!treeMatches) walked = { proj: projTree, ws: wsTree }
|
|
196
195
|
if (treeMatches) {
|
|
197
196
|
const endStat = span('output stat')
|
|
198
197
|
skipRestore =
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
args.cache.isOutputsCurrent(node.projectDir, projExpected) &&
|
|
199
|
+
args.cache.isOutputsCurrent(args.workspaceRoot, wsExpected)
|
|
201
200
|
endStat()
|
|
202
201
|
// The walk ran and proved the tree current (rows absent, or a
|
|
203
202
|
// directory had moved — a benign touch): snapshot the directories
|
|
204
203
|
// so the next hit skips the walk.
|
|
205
|
-
if (
|
|
206
|
-
|
|
204
|
+
if (
|
|
205
|
+
skipRestore &&
|
|
206
|
+
dirPrefixes !== null &&
|
|
207
|
+
hit.outputDirRows?.some((r) => r.mtimeMs === OUTPUT_DIRS_OVER_CAP) !== true
|
|
208
|
+
) {
|
|
209
|
+
// Held to the rows like the run-end snapshot (item 1087): a stray
|
|
210
|
+
// written since the walk listed its directory, more than a racy
|
|
211
|
+
// window before this, is not the entry's.
|
|
212
|
+
const holds = entryHolds(
|
|
213
|
+
node,
|
|
214
|
+
outputWalkTakes(node.projectDir, outputs, args.nestedProjectDirs),
|
|
215
|
+
)
|
|
216
|
+
await args.cache.recordOutputDirs?.(hash, node.projectDir, dirPrefixes, (files) =>
|
|
217
|
+
holds(files, expected),
|
|
218
|
+
)
|
|
207
219
|
}
|
|
208
220
|
}
|
|
209
221
|
}
|
|
@@ -214,24 +226,37 @@ export async function restoreHit(restore: RestoreHitArgs): Promise<TaskOutcome>
|
|
|
214
226
|
// of the tar is spared. A stray under the glob still falls through to
|
|
215
227
|
// the clean, as strict ownership requires (item 589).
|
|
216
228
|
if (anyOutputs && !skipRestore) {
|
|
217
|
-
const expected = hit.outputRows ??
|
|
229
|
+
const expected = hit.outputRows ?? []
|
|
218
230
|
if (expected.length === 0) {
|
|
219
231
|
const endGlob = span('output glob')
|
|
220
|
-
const actual = await
|
|
232
|
+
const actual = await resolveOutputTree({
|
|
221
233
|
projectDir: node.projectDir,
|
|
222
234
|
outputs,
|
|
223
235
|
nestedProjectDirs: args.nestedProjectDirs,
|
|
224
236
|
})
|
|
225
|
-
const actualWs = await
|
|
237
|
+
const actualWs = await resolveWorkspaceOutputTree({
|
|
226
238
|
workspaceRoot: args.workspaceRoot,
|
|
227
239
|
outputs: wsOutputs,
|
|
228
240
|
})
|
|
229
241
|
endGlob()
|
|
230
|
-
skipRestore =
|
|
242
|
+
skipRestore =
|
|
243
|
+
actual.files.length + actual.emptyDirs.length + actualWs.files.length === 0 &&
|
|
244
|
+
actualWs.emptyDirs.length === 0
|
|
231
245
|
walked = { proj: actual, ws: actualWs }
|
|
232
246
|
}
|
|
233
247
|
}
|
|
234
248
|
if (!skipRestore) {
|
|
249
|
+
const cleanArgs = {
|
|
250
|
+
projectDir: node.projectDir,
|
|
251
|
+
outputs,
|
|
252
|
+
nestedProjectDirs: args.nestedProjectDirs,
|
|
253
|
+
committed: committedFiles(args.gitFilesCache, node.projectDir),
|
|
254
|
+
}
|
|
255
|
+
const wsCleanArgs = {
|
|
256
|
+
workspaceRoot: args.workspaceRoot,
|
|
257
|
+
outputs: wsOutputs,
|
|
258
|
+
committed: committedFiles(args.gitFilesCache, args.workspaceRoot),
|
|
259
|
+
}
|
|
235
260
|
const heldRels = hit.outputFiles.filter((p) => !p.startsWith(WORKSPACE_OUTPUT_PREFIX))
|
|
236
261
|
const heldWsRels = hit.outputFiles
|
|
237
262
|
.filter((p) => p.startsWith(WORKSPACE_OUTPUT_PREFIX))
|
|
@@ -248,34 +273,42 @@ export async function restoreHit(restore: RestoreHitArgs): Promise<TaskOutcome>
|
|
|
248
273
|
let foundWsRels: string[] = []
|
|
249
274
|
let staleRels: string[] = []
|
|
250
275
|
let staleWsRels: string[] = []
|
|
276
|
+
let staleDirs: string[] = []
|
|
277
|
+
let staleWsDirs: string[] = []
|
|
251
278
|
if (!additive) {
|
|
252
|
-
sweepRestoreTemps(node.projectDir, outputs)
|
|
253
|
-
sweepRestoreTemps(args.workspaceRoot, wsOutputs)
|
|
279
|
+
sweepRestoreTemps(node.projectDir, outputs, cleanArgs.committed)
|
|
280
|
+
sweepRestoreTemps(args.workspaceRoot, wsOutputs, wsCleanArgs.committed)
|
|
254
281
|
if (outputs.length > 0) {
|
|
255
|
-
const
|
|
256
|
-
foundRels =
|
|
282
|
+
const tree = walked?.proj ?? (await resolveOutputTree(cleanArgs))
|
|
283
|
+
foundRels = tree.files.map((f) => relPosix(node.projectDir, f))
|
|
257
284
|
const blocking = inTheWay(foundRels, heldRels)
|
|
258
285
|
staleRels = notHeld(foundRels, heldRels).filter((f) => !blocking.has(f))
|
|
259
|
-
|
|
286
|
+
const dirs = splitDirs(node.projectDir, tree.emptyDirs, heldRels)
|
|
287
|
+
staleDirs = dirs.stale
|
|
288
|
+
if (blocking.size > 0 || dirs.blocking.length > 0) {
|
|
260
289
|
await cleanOutputPaths({
|
|
261
290
|
projectDir: node.projectDir,
|
|
262
291
|
rels: [...blocking],
|
|
263
292
|
outputs,
|
|
264
293
|
committed: cleanArgs.committed,
|
|
294
|
+
emptyDirs: dirs.blocking,
|
|
265
295
|
})
|
|
266
296
|
}
|
|
267
297
|
}
|
|
268
298
|
if (wsOutputs.length > 0) {
|
|
269
|
-
const
|
|
270
|
-
foundWsRels =
|
|
299
|
+
const tree = walked?.ws ?? (await resolveWorkspaceOutputTree(wsCleanArgs))
|
|
300
|
+
foundWsRels = tree.files.map((f) => relPosix(args.workspaceRoot, f))
|
|
271
301
|
const blocking = inTheWay(foundWsRels, heldWsRels)
|
|
272
302
|
staleWsRels = notHeld(foundWsRels, heldWsRels).filter((f) => !blocking.has(f))
|
|
273
|
-
|
|
303
|
+
const dirs = splitDirs(args.workspaceRoot, tree.emptyDirs, heldWsRels)
|
|
304
|
+
staleWsDirs = dirs.stale
|
|
305
|
+
if (blocking.size > 0 || dirs.blocking.length > 0) {
|
|
274
306
|
await cleanOutputPaths({
|
|
275
307
|
projectDir: args.workspaceRoot,
|
|
276
308
|
rels: [...blocking],
|
|
277
309
|
outputs: wsOutputs,
|
|
278
310
|
committed: wsCleanArgs.committed,
|
|
311
|
+
emptyDirs: dirs.blocking,
|
|
279
312
|
})
|
|
280
313
|
}
|
|
281
314
|
}
|
|
@@ -284,10 +317,11 @@ export async function restoreHit(restore: RestoreHitArgs): Promise<TaskOutcome>
|
|
|
284
317
|
files: outputs,
|
|
285
318
|
workspaceFiles: wsOutputs,
|
|
286
319
|
})
|
|
287
|
-
if (staleRels.length > 0) {
|
|
320
|
+
if (staleRels.length > 0 || staleDirs.length > 0) {
|
|
288
321
|
await cleanOutputPaths({
|
|
289
322
|
projectDir: node.projectDir,
|
|
290
323
|
rels: staleRels,
|
|
324
|
+
emptyDirs: staleDirs,
|
|
291
325
|
outputs,
|
|
292
326
|
committed: cleanArgs.committed,
|
|
293
327
|
// A stale file in an output directory the entry holds empty takes
|
|
@@ -299,12 +333,18 @@ export async function restoreHit(restore: RestoreHitArgs): Promise<TaskOutcome>
|
|
|
299
333
|
),
|
|
300
334
|
})
|
|
301
335
|
}
|
|
302
|
-
if (staleWsRels.length > 0) {
|
|
336
|
+
if (staleWsRels.length > 0 || staleWsDirs.length > 0) {
|
|
303
337
|
await cleanOutputPaths({
|
|
304
338
|
projectDir: args.workspaceRoot,
|
|
305
339
|
rels: staleWsRels,
|
|
340
|
+
emptyDirs: staleWsDirs,
|
|
306
341
|
outputs: wsOutputs,
|
|
307
342
|
committed: wsCleanArgs.committed,
|
|
343
|
+
keep: new Set(
|
|
344
|
+
heldWsRels
|
|
345
|
+
.filter((r) => r.endsWith('/'))
|
|
346
|
+
.map((r) => path.join(args.workspaceRoot, r.slice(0, -1))),
|
|
347
|
+
),
|
|
308
348
|
})
|
|
309
349
|
}
|
|
310
350
|
// The tree now holds this entry's bytes: stamp them for the next hit
|
|
@@ -323,7 +363,7 @@ export async function restoreHit(restore: RestoreHitArgs): Promise<TaskOutcome>
|
|
|
323
363
|
hash,
|
|
324
364
|
projectDir: node.projectDir,
|
|
325
365
|
prefixes: dirPrefixes,
|
|
326
|
-
holds: entryHolds(node),
|
|
366
|
+
holds: entryHolds(node, outputWalkTakes(node.projectDir, outputs, args.nestedProjectDirs)),
|
|
327
367
|
})
|
|
328
368
|
}
|
|
329
369
|
// Restored outputs changed the project's tree — but on this
|
|
@@ -344,6 +384,47 @@ export async function restoreHit(restore: RestoreHitArgs): Promise<TaskOutcome>
|
|
|
344
384
|
])
|
|
345
385
|
}
|
|
346
386
|
}
|
|
387
|
+
return hitOutcome(restore, !skipRestore && anyOutputs)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* The hit's outcome now, when its tree is provably current without a walk
|
|
392
|
+
* or an await: it declares no outputs, or every directory the entry
|
|
393
|
+
* recorded and every file it holds are as the entry left them (the
|
|
394
|
+
* directory snapshot proves the set, the stamps the bytes). Undefined
|
|
395
|
+
* sends it down `restoreHit`'s full path. Nothing happens until the thunk
|
|
396
|
+
* runs, which replays the stored output: the scheduler starts a hit it
|
|
397
|
+
* settles in place first, so its output follows its start.
|
|
398
|
+
*/
|
|
399
|
+
export function currentHit(restore: RestoreHitArgs): (() => TaskOutcome) | undefined {
|
|
400
|
+
const { args, hash, hit } = restore
|
|
401
|
+
const { node } = args
|
|
402
|
+
const outputs = node.config.cache?.outputs.files ?? []
|
|
403
|
+
if ((node.config.cache?.outputs.workspaceFiles?.length ?? 0) > 0) return undefined
|
|
404
|
+
if (outputs.length > 0) {
|
|
405
|
+
const dirPrefixes = dirPrefixesOf(outputs)
|
|
406
|
+
if (dirPrefixes === null || args.cache.outputDirsCurrent === undefined) return undefined
|
|
407
|
+
const expected = hit.outputRows ?? args.cache.loadOutputFilesBatch([hash]).get(hash) ?? []
|
|
408
|
+
if (expected.length === 0) return undefined
|
|
409
|
+
// Loaded with the entry (batched by getMany, or with the lazy get).
|
|
410
|
+
const dirRows = hit.outputDirRows ?? []
|
|
411
|
+
// Rows are recorded for these same prefixes under this same key (the
|
|
412
|
+
// outputs are folded into it), so this cannot fail today (item 638).
|
|
413
|
+
// Kept: a row set that shrank would otherwise vouch for a tree it never
|
|
414
|
+
// saw, and the failure it would let through is a skipped restore.
|
|
415
|
+
if (!dirPrefixes.every((pre) => dirRows.some((r) => r.path === pre))) return undefined
|
|
416
|
+
if (!args.cache.outputDirsCurrent(node.projectDir, dirRows)) return undefined
|
|
417
|
+
// No workspace output is declared, so every row is the project's.
|
|
418
|
+
if (expected.some((e) => e.path.startsWith(WORKSPACE_OUTPUT_PREFIX))) return undefined
|
|
419
|
+
if (!args.cache.isOutputsCurrent(node.projectDir, expected)) return undefined
|
|
420
|
+
}
|
|
421
|
+
return () => hitOutcome(restore, false)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** Replay the stored output and build the hit's outcome. */
|
|
425
|
+
function hitOutcome(restore: RestoreHitArgs, restored: boolean): TaskOutcome {
|
|
426
|
+
const { args, hash, hit, cacheOpStart, taskStartNs } = restore
|
|
427
|
+
const { node, log } = args
|
|
347
428
|
if (hit.stdout) {
|
|
348
429
|
// An entry saved before masking, or by another machine, can hold a
|
|
349
430
|
// secret-named variable's value (L-11). Asked only of a hit that prints.
|
|
@@ -352,10 +433,9 @@ export async function restoreHit(restore: RestoreHitArgs): Promise<TaskOutcome>
|
|
|
352
433
|
node.config.exec?.env?.secret,
|
|
353
434
|
)
|
|
354
435
|
// The stored log holds both streams in the order the run printed them.
|
|
355
|
-
for (const c of decodeOutputLog(hit.stdout)) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
else log.taskStdout(node, text)
|
|
436
|
+
for (const c of maskStreams(decodeOutputLog(hit.stdout), secrets ?? undefined)) {
|
|
437
|
+
if (c.err) log.taskStderr(node, c.text)
|
|
438
|
+
else log.taskStdout(node, c.text)
|
|
359
439
|
}
|
|
360
440
|
}
|
|
361
441
|
const status =
|
|
@@ -363,10 +443,10 @@ export async function restoreHit(restore: RestoreHitArgs): Promise<TaskOutcome>
|
|
|
363
443
|
// `restored` distinguishes "we just wrote files to disk" from
|
|
364
444
|
// "disk already matched the cached snapshot". Drives the
|
|
365
445
|
// "up-to-date" vs "local-cache" / "remote-cache" label in the
|
|
366
|
-
// framed block.
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
|
|
446
|
+
// framed block. A hit the remote answered is never up to date: up to
|
|
447
|
+
// date is fresh here, and a remote hit waited on the network and
|
|
448
|
+
// downloaded its entry, outputs or none (2026-10-24: "Up to date is
|
|
449
|
+
// a fresh. It should not require any cache").
|
|
370
450
|
return {
|
|
371
451
|
node,
|
|
372
452
|
status,
|
|
@@ -380,7 +460,7 @@ export async function restoreHit(restore: RestoreHitArgs): Promise<TaskOutcome>
|
|
|
380
460
|
...(hit.sizeBytes !== undefined ? { artifactBytes: hit.sizeBytes } : {}),
|
|
381
461
|
...(hit.fetchMs !== undefined ? { fetchMs: hit.fetchMs } : {}),
|
|
382
462
|
hash,
|
|
383
|
-
restored,
|
|
463
|
+
restored: restored || hit.source === 'remote',
|
|
384
464
|
wallclockStartNs: taskStartNs,
|
|
385
465
|
wallclockEndNs: process.hrtime.bigint() - args.runStartHrTimeNs,
|
|
386
466
|
}
|
|
@@ -396,7 +476,7 @@ function dirPrefixesOf(outputs: readonly string[]): readonly string[] | null {
|
|
|
396
476
|
const key = JSON.stringify(outputs)
|
|
397
477
|
let prefixes = prefixMemo.get(key)
|
|
398
478
|
if (prefixes === undefined) {
|
|
399
|
-
prefixes =
|
|
479
|
+
prefixes = outputDirPrefixes(splitNegations(outputs).positive)
|
|
400
480
|
prefixMemo.set(key, prefixes)
|
|
401
481
|
}
|
|
402
482
|
return prefixes
|
|
@@ -439,6 +519,31 @@ function inTheWay(found: readonly string[], held: readonly string[]): Set<string
|
|
|
439
519
|
}
|
|
440
520
|
|
|
441
521
|
/** The found outputs the entry does not hold: the stale ones, once it has restored. */
|
|
522
|
+
type Tree = { files: string[]; emptyDirs: string[] }
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* The empty directories found under the outputs (absolute) that the entry
|
|
526
|
+
* does not hold, as rels: one where it holds a file blocks that file's
|
|
527
|
+
* rename and goes first, the rest go after the restore, as a miss's clean
|
|
528
|
+
* takes them.
|
|
529
|
+
*/
|
|
530
|
+
function splitDirs(
|
|
531
|
+
root: string,
|
|
532
|
+
emptyDirs: readonly string[],
|
|
533
|
+
held: readonly string[],
|
|
534
|
+
): { blocking: string[]; stale: string[] } {
|
|
535
|
+
const heldKeys = new Set(held.map(fold))
|
|
536
|
+
const blocking: string[] = []
|
|
537
|
+
const stale: string[] = []
|
|
538
|
+
for (const d of emptyDirs) {
|
|
539
|
+
const rel = relPosix(root, d)
|
|
540
|
+
if (heldKeys.has(fold(`${rel}/`))) continue
|
|
541
|
+
if (heldKeys.has(fold(rel))) blocking.push(rel)
|
|
542
|
+
else stale.push(rel)
|
|
543
|
+
}
|
|
544
|
+
return { blocking, stale }
|
|
545
|
+
}
|
|
546
|
+
|
|
442
547
|
function notHeld(found: readonly string[], held: readonly string[]): string[] {
|
|
443
548
|
const keep = new Set(held.map(fold))
|
|
444
549
|
return found.filter((f) => !keep.has(fold(f)))
|
|
@@ -2,14 +2,20 @@
|
|
|
2
2
|
// through here (enforced by tests/module-boundaries.test.ts).
|
|
3
3
|
|
|
4
4
|
export { run, planRun, nxProjectTarget, projectNamed } from './run.js'
|
|
5
|
-
export {
|
|
5
|
+
export { CYCLE_INTERRUPT } from './run-stop.js'
|
|
6
|
+
export {
|
|
7
|
+
namesRemote,
|
|
8
|
+
openRemotes,
|
|
9
|
+
prepareRun,
|
|
10
|
+
workspaceCachePolicy,
|
|
11
|
+
type PreparedRun,
|
|
12
|
+
} from './prepare.js'
|
|
6
13
|
export { affectedTaskProjects } from './affected-tasks.js'
|
|
7
14
|
export { applyGraphStage } from './graph-stage.js'
|
|
8
15
|
export type { AffectedReason } from './affected-tasks.js'
|
|
9
16
|
export {
|
|
10
17
|
discoverProjects,
|
|
11
18
|
gitOfDiscovery,
|
|
12
|
-
isDefaultBuild,
|
|
13
19
|
keepDiscoveryGraph,
|
|
14
20
|
loadProjects,
|
|
15
21
|
loadResolvedProjects,
|
|
@@ -36,7 +42,16 @@ export { exitSignal, shellQuote } from '../exec/index.js'
|
|
|
36
42
|
export { forwardedSignal } from './signals.js'
|
|
37
43
|
// `vx cache clean` waits for a run on the workspace before it evicts.
|
|
38
44
|
export { acquireRunLock } from './run-lock.js'
|
|
45
|
+
export { type LiveSnapshot, liveSocketPath } from './run-live.js'
|
|
39
46
|
export type { RunPlan, PlannedTask, PlanPrediction, CacheStatus } from './plan.js'
|
|
47
|
+
export {
|
|
48
|
+
cacheFacts,
|
|
49
|
+
taskArtifacts,
|
|
50
|
+
type ArtifactRun,
|
|
51
|
+
type ArtifactTask,
|
|
52
|
+
type CacheFacts,
|
|
53
|
+
type TaskArtifacts,
|
|
54
|
+
} from './task-artifacts.js'
|
|
40
55
|
export {
|
|
41
56
|
installPlugins,
|
|
42
57
|
type CacheContext,
|
|
@@ -131,6 +146,8 @@ export {
|
|
|
131
146
|
type TaskLog,
|
|
132
147
|
taskLog,
|
|
133
148
|
} from './run-failures.js'
|
|
134
|
-
export { type RunStep, runSteps } from './run-steps.js'
|
|
149
|
+
export { type RunStep, runSteps, runWarnings } from './run-steps.js'
|
|
135
150
|
export { resolveRunId, shortRunId } from './run-id.js'
|
|
136
151
|
export { collectInfo, type CollectInfoOptions, type InfoFacts } from './doctor.js'
|
|
152
|
+
export { localFloor, type LocalFloor } from './local-floor.js'
|
|
153
|
+
export { sandboxRequestFor, sweepPlaceholders, type SandboxRequest } from './sandbox-request.js'
|