@vzn/vx 0.0.12 → 0.0.14
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 +106 -89
- package/launcher.mjs +36 -28
- package/package.json +5 -5
- package/src/cache/cas-backend.ts +2 -2
- package/src/cache/index.ts +1 -2
- package/src/cache/inputs.ts +6 -0
- package/src/cache/layered-cache.ts +22 -2
- package/src/cli/cache.ts +5 -10
- package/src/cli/help.ts +8 -2
- package/src/cli/info.ts +0 -3
- package/src/cli/run.ts +32 -10
- package/src/config.ts +40 -1
- package/src/graph/dependency-spec.ts +16 -0
- package/src/graph/index.ts +10 -1
- package/src/graph/scheduler.ts +230 -35
- package/src/graph/task-graph.ts +69 -17
- package/src/index.ts +29 -4
- package/src/orchestrator/execute-task.ts +66 -10
- package/src/orchestrator/index.ts +11 -0
- package/src/orchestrator/metrics.ts +351 -0
- package/src/orchestrator/options.ts +26 -5
- package/src/orchestrator/plugin-host.ts +5 -10
- package/src/orchestrator/prepare.ts +20 -11
- package/src/orchestrator/protocol.ts +16 -2
- package/src/orchestrator/resources.ts +59 -0
- package/src/orchestrator/run.ts +45 -0
- package/src/orchestrator/summary.ts +24 -0
- package/src/orchestrator/task-hash.ts +17 -2
- package/src/orchestrator/telemetry.ts +9 -1
- package/src/orchestrator/upstream.ts +34 -6
- package/src/orchestrator/verify.ts +34 -11
- package/src/util/index.ts +1 -0
- package/src/util/size.ts +16 -0
- package/src/workspace/project-loader.ts +43 -1
- package/src/cache/remote-cache.ts +0 -251
- package/src/orchestrator/remote-cache-setup.ts +0 -60
package/src/graph/task-graph.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { TaskConfig } from '../config.js'
|
|
2
2
|
import { UserError } from '../util/index.js'
|
|
3
3
|
import type { PackageGraph, ProjectEntry } from '../workspace/index.js'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
DependencySpecError,
|
|
6
|
+
compileTaskPattern,
|
|
7
|
+
isTaskPattern,
|
|
8
|
+
parseDependencySpec,
|
|
9
|
+
type DependencySpec,
|
|
10
|
+
} from './dependency-spec.js'
|
|
5
11
|
|
|
6
12
|
// Re-exported so existing importers keep working while the type's home
|
|
7
13
|
// moves to workspace (it's the joint product of discovery + loading).
|
|
@@ -204,28 +210,50 @@ export function buildTaskGraph(options: BuildGraphOptions): Map<string, TaskNode
|
|
|
204
210
|
}
|
|
205
211
|
|
|
206
212
|
// dependsOn is about which tasks to ADD to the graph, not which
|
|
207
|
-
// to filter.
|
|
208
|
-
// they're cache.inputs.tasks
|
|
213
|
+
// to filter. BARE wildcards ("*"/"^*" = "everything upstream") and
|
|
214
|
+
// negation aren't meaningful here — they're cache.inputs.tasks
|
|
215
|
+
// operations. PARTIAL patterns (`build.*`, `^build.*`) are legal:
|
|
216
|
+
// they name a namespace of tasks to add (Nx 19.5 parity).
|
|
209
217
|
if (spec.kind === 'wildcardSelf' || spec.kind === 'wildcardDeps') {
|
|
210
|
-
throw new UserError(`Task ${id}: dependsOn does not accept wildcards (got "${raw}")`)
|
|
218
|
+
throw new UserError(`Task ${id}: dependsOn does not accept bare wildcards (got "${raw}")`)
|
|
211
219
|
}
|
|
212
220
|
if (spec.negated) {
|
|
213
221
|
throw new UserError(`Task ${id}: dependsOn does not accept negation (got "${raw}")`)
|
|
214
222
|
}
|
|
223
|
+
if (spec.kind === 'cross' && (isTaskPattern(spec.task) || isTaskPattern(spec.project))) {
|
|
224
|
+
throw new UserError(
|
|
225
|
+
`Task ${id}: dependsOn patterns are not supported in the "pkg#task" form (got "${raw}")`,
|
|
226
|
+
)
|
|
227
|
+
}
|
|
215
228
|
// CLI `--excludeDependencies=name1,name2` drops edges whose target
|
|
216
229
|
// task name matches, regardless of bucket (self / deps / cross).
|
|
230
|
+
// Pattern specs re-apply the filter per EXPANDED name below.
|
|
217
231
|
if (skipNames?.has(spec.task)) continue
|
|
218
232
|
|
|
219
233
|
if (spec.kind === 'self') {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
)
|
|
234
|
+
if (isTaskPattern(spec.task)) {
|
|
235
|
+
// `build.*` — every OTHER same-project task matching the
|
|
236
|
+
// pattern (the declaring task never matches itself — that
|
|
237
|
+
// would be an instant self-cycle). Zero matches is legal: a
|
|
238
|
+
// preset-spread pattern needn't match in every project.
|
|
239
|
+
const re = compileTaskPattern(spec.task)
|
|
240
|
+
for (const name of Object.keys(project.config.tasks ?? {})) {
|
|
241
|
+
if (name === taskName || !re.test(name)) continue
|
|
242
|
+
if (skipNames?.has(name)) continue
|
|
243
|
+
const child = addNode(projectName, name, false)
|
|
244
|
+
if (child) node.deps.push(child.id)
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
// Missing target is a hard error — the user typed a name that
|
|
248
|
+
// doesn't resolve in this project.
|
|
249
|
+
const child = addNode(projectName, spec.task, false)
|
|
250
|
+
if (!child) {
|
|
251
|
+
throw new UserError(
|
|
252
|
+
`Task ${id} depends on ${taskId(projectName, spec.task)} but no such task is declared`,
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
node.deps.push(child.id)
|
|
227
256
|
}
|
|
228
|
-
node.deps.push(child.id)
|
|
229
257
|
} else if (spec.kind === 'deps') {
|
|
230
258
|
// Nearest-holder frontier (Turbo/Nx direct-deps parity +
|
|
231
259
|
// sparse bridging): walk the package dep graph from this
|
|
@@ -235,15 +263,36 @@ export function buildTaskGraph(options: BuildGraphOptions): Map<string, TaskNode
|
|
|
235
263
|
// through so a sparse dep doesn't break ordering to deeper
|
|
236
264
|
// holders. The visited set both dedupes shared subtrees and
|
|
237
265
|
// terminates on package-graph cycles (legal in PMs).
|
|
266
|
+
//
|
|
267
|
+
// With a pattern (`^build.*`), a holder is a package declaring
|
|
268
|
+
// AT LEAST ONE matching task and it receives edges to ALL its
|
|
269
|
+
// matches — holder-ness is about declaration, so a holder still
|
|
270
|
+
// stops the walk even when every match is --excludeDependencies'd.
|
|
271
|
+
const re = isTaskPattern(spec.task) ? compileTaskPattern(spec.task) : null
|
|
238
272
|
const visited = new Set<string>()
|
|
239
273
|
const frontier = [...packageGraph.directDeps(projectName)]
|
|
240
274
|
while (frontier.length > 0) {
|
|
241
275
|
const target = frontier.pop()!
|
|
242
276
|
if (visited.has(target)) continue
|
|
243
277
|
visited.add(target)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
278
|
+
if (re === null) {
|
|
279
|
+
const child = addNode(target, spec.task, false)
|
|
280
|
+
if (child) node.deps.push(child.id)
|
|
281
|
+
else frontier.push(...packageGraph.directDeps(target))
|
|
282
|
+
} else {
|
|
283
|
+
const names = Object.keys(projects.get(target)?.config.tasks ?? {}).filter((n) =>
|
|
284
|
+
re.test(n),
|
|
285
|
+
)
|
|
286
|
+
if (names.length > 0) {
|
|
287
|
+
for (const name of names) {
|
|
288
|
+
if (skipNames?.has(name)) continue
|
|
289
|
+
const child = addNode(target, name, false)
|
|
290
|
+
if (child) node.deps.push(child.id)
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
frontier.push(...packageGraph.directDeps(target))
|
|
294
|
+
}
|
|
295
|
+
}
|
|
247
296
|
}
|
|
248
297
|
} else {
|
|
249
298
|
// Cross-project edge: pkg#task. Missing target is a hard error
|
|
@@ -258,8 +307,11 @@ export function buildTaskGraph(options: BuildGraphOptions): Map<string, TaskNode
|
|
|
258
307
|
}
|
|
259
308
|
}
|
|
260
309
|
|
|
261
|
-
// Stable ordering for deterministic scheduling and cache keys
|
|
262
|
-
|
|
310
|
+
// Stable ordering for deterministic scheduling and cache keys — deduped:
|
|
311
|
+
// a target named twice (an exact entry + a pattern matching it, or a
|
|
312
|
+
// literal duplicate) must contribute ONE edge, not a double-folded
|
|
313
|
+
// upstream hash and a doubled DOT edge.
|
|
314
|
+
node.deps = [...new Set(node.deps)].sort()
|
|
263
315
|
return node
|
|
264
316
|
}
|
|
265
317
|
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type {
|
|
|
21
21
|
CacheConfig,
|
|
22
22
|
CacheInputs,
|
|
23
23
|
CacheOutputs,
|
|
24
|
+
ResourcesConfig,
|
|
24
25
|
SandboxConfig,
|
|
25
26
|
SandboxNetworkConfig,
|
|
26
27
|
} from './config.js'
|
|
@@ -52,7 +53,16 @@ export type {
|
|
|
52
53
|
} from './orchestrator/index.js'
|
|
53
54
|
export { defaultLogger, resolveOutputView } from './orchestrator/index.js'
|
|
54
55
|
export { buildTaskGraph, expandRequested, isGroupTask, markSurfacedDeps } from './graph/index.js'
|
|
55
|
-
export type {
|
|
56
|
+
export type {
|
|
57
|
+
OutputFingerprint,
|
|
58
|
+
TaskNode,
|
|
59
|
+
TaskOutcome,
|
|
60
|
+
TaskStatus,
|
|
61
|
+
VerifyVerdict,
|
|
62
|
+
} from './graph/index.js'
|
|
63
|
+
// The one output-tree diff implementation (verify verdicts + a serve's
|
|
64
|
+
// cross-machine fingerprint diff both name rels through it).
|
|
65
|
+
export { diffOutputTrees } from './orchestrator/index.js'
|
|
56
66
|
|
|
57
67
|
// Cache classes + the layer interface (the `cache` capability's currency)
|
|
58
68
|
// and input-output resolution. The blob-CAS/digest substrate
|
|
@@ -63,16 +73,29 @@ export type { TaskNode, TaskOutcome, TaskStatus, VerifyVerdict } from './graph/i
|
|
|
63
73
|
export {
|
|
64
74
|
Cache,
|
|
65
75
|
LayeredCache,
|
|
66
|
-
RemoteCache,
|
|
67
76
|
GitFilesCache,
|
|
68
77
|
cleanOutputs,
|
|
69
78
|
resolveInputs,
|
|
70
79
|
resolveOutputs,
|
|
71
80
|
} from './cache/index.js'
|
|
72
|
-
export type { CacheLayer, RunRecord, InvocationRecord } from './cache/index.js'
|
|
81
|
+
export type { CacheLayer, RemoteCacheLayer, RunRecord, InvocationRecord } from './cache/index.js'
|
|
73
82
|
|
|
74
|
-
// Workspace discovery
|
|
83
|
+
// Workspace discovery + the project/config catalog surface — an
|
|
84
|
+
// out-of-process service/CLI needs these. `readLockfile` is THE one reader
|
|
85
|
+
// of vx-lock.json (the format carries its own version sentinel; a second
|
|
86
|
+
// parser in a sibling package would drift), and the loader chain
|
|
87
|
+
// (`loadWorkspace` → `listProjectMetas` → `loadProjectConfig`) is the same
|
|
88
|
+
// one `vx show` uses. Workspace's `listProjects` re-exports as
|
|
89
|
+
// `listProjectMetas` — the bare name belongs to the metrics query below.
|
|
75
90
|
export { findWorkspaceRoot, loadWorkspaceConfig, resolveCacheDir } from './workspace/index.js'
|
|
91
|
+
export { readLockfile, LOCKFILE_NAME } from './workspace/index.js'
|
|
92
|
+
export type { Lockfile, LockfileEntry } from './workspace/index.js'
|
|
93
|
+
export {
|
|
94
|
+
loadWorkspace,
|
|
95
|
+
loadProjectConfig,
|
|
96
|
+
listProjects as listProjectMetas,
|
|
97
|
+
} from './workspace/index.js'
|
|
98
|
+
export type { ProjectMeta } from './workspace/index.js'
|
|
76
99
|
|
|
77
100
|
// Plugin API — the run-level extension points. Behavior capabilities
|
|
78
101
|
// (backend / cache) change WHAT/HOW work runs; the observe-only `telemetry`
|
|
@@ -181,8 +204,10 @@ export {
|
|
|
181
204
|
getHitRateSplit,
|
|
182
205
|
getInvocation,
|
|
183
206
|
getParallelismHistory,
|
|
207
|
+
getPeriodComparison,
|
|
184
208
|
getPrunableEntries,
|
|
185
209
|
getRecentFailures,
|
|
210
|
+
getRegressions,
|
|
186
211
|
getRun,
|
|
187
212
|
getRunHeatmap,
|
|
188
213
|
getRunTrends,
|
|
@@ -26,6 +26,7 @@ import { isGroupTask, type TaskNode, type TaskOutcome, type VerifyVerdict } from
|
|
|
26
26
|
import {
|
|
27
27
|
classifyDeterminism,
|
|
28
28
|
diffOutputTrees,
|
|
29
|
+
foldFingerprint,
|
|
29
30
|
hashOutputTree,
|
|
30
31
|
outputRefs,
|
|
31
32
|
undeclaredInputPaths,
|
|
@@ -83,13 +84,17 @@ export interface ExecuteArgs {
|
|
|
83
84
|
* save and its outputs are content-compared; a divergence flags the task
|
|
84
85
|
* non-hermetic. When `inputs` is set, an executed + cacheable task is
|
|
85
86
|
* forced through the declared-input baseline sandbox and any read outside
|
|
86
|
-
* those inputs flags the declared `cache.inputs` as incomplete.
|
|
87
|
-
*
|
|
88
|
-
*
|
|
87
|
+
* those inputs flags the declared `cache.inputs` as incomplete. When
|
|
88
|
+
* `fingerprint` is set, the executed task's output tree is fingerprinted
|
|
89
|
+
* and attached to the outcome (no re-run — the cross-machine diff feed).
|
|
90
|
+
* `allow` exempts known-nondeterministic task ids from failing the run.
|
|
91
|
+
* Pure side-channel — the re-run never saves, nothing is hashed.
|
|
92
|
+
* Undefined = off.
|
|
89
93
|
*/
|
|
90
94
|
verify?: {
|
|
91
95
|
determinism: boolean
|
|
92
96
|
inputs: boolean
|
|
97
|
+
fingerprint: boolean
|
|
93
98
|
allow: ReadonlySet<string>
|
|
94
99
|
}
|
|
95
100
|
/** Per-run memo for `git ls-files` (one entry per project dir). */
|
|
@@ -537,12 +542,23 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
|
|
|
537
542
|
stdout: result.stdout,
|
|
538
543
|
},
|
|
539
544
|
})
|
|
540
|
-
// --verify: fingerprint attempt 1's outputs by CONTENT (
|
|
541
|
-
//
|
|
542
|
-
//
|
|
543
|
-
|
|
545
|
+
// --verify: fingerprint attempt 1's outputs by CONTENT (raw bytes, never
|
|
546
|
+
// the mtime+size memo). The determinism re-run below compares against it,
|
|
547
|
+
// and the fingerprinting modes (`--verify`/`=all`/`=fingerprint`) fold it
|
|
548
|
+
// into the shipped `outputFp` — the cross-machine diff feed.
|
|
549
|
+
//
|
|
550
|
+
// Gate on DECLARED outputs (`outputs`/`wsOutputs` globs), not the
|
|
551
|
+
// RESOLVED file count: a task that declares `dist/**` but produces zero
|
|
552
|
+
// files this run must still ship the EMPTY tree, or the cross-machine
|
|
553
|
+
// diff can't pair it against a platform where the same glob DID match —
|
|
554
|
+
// a platform-conditional glob producing nothing on one OS is exactly
|
|
555
|
+
// the divergence Phase 4 exists to catch. The empty fold is well-defined
|
|
556
|
+
// (a fixed sentinel tree), so two both-empty platforms stay silent.
|
|
557
|
+
if (
|
|
558
|
+
(args.verify?.fingerprint || args.verify?.determinism) &&
|
|
559
|
+
outputs.length + wsOutputs.length > 0
|
|
560
|
+
) {
|
|
544
561
|
verifyFp1 = await hashOutputTree(
|
|
545
|
-
cache,
|
|
546
562
|
outputRefs(node.projectDir, outputFiles, args.workspaceRoot, wsOutputFiles),
|
|
547
563
|
)
|
|
548
564
|
}
|
|
@@ -618,7 +634,14 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
|
|
|
618
634
|
willWrite &&
|
|
619
635
|
!result.timedOut
|
|
620
636
|
) {
|
|
621
|
-
|
|
637
|
+
// Determinism verdict is keyed on RESOLVED outputs (unchanged): a task
|
|
638
|
+
// that declares outputs but produced none this run has nothing to
|
|
639
|
+
// content-compare, so it stays `no-outputs` with no re-run — even though
|
|
640
|
+
// `verifyFp1` is now the (empty) declared-outputs fingerprint that
|
|
641
|
+
// `--verify=fingerprint` ships for the cross-machine diff. `verifyFp1`
|
|
642
|
+
// is keyed by resolved output file, so an empty map means "declared but
|
|
643
|
+
// produced nothing" — the in-scope stand-in for the resolved-zero count.
|
|
644
|
+
if (verifyFp1 === undefined || verifyFp1.size === 0) {
|
|
622
645
|
verify = { kind: 'no-outputs' } // cacheable but nothing to replay (e.g. lint)
|
|
623
646
|
} else {
|
|
624
647
|
const rerun = await runAttempt()
|
|
@@ -626,7 +649,6 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
|
|
|
626
649
|
verify = { kind: 'rerun-failed', exitCode: rerun.exitCode }
|
|
627
650
|
} else {
|
|
628
651
|
const fp2 = await hashOutputTree(
|
|
629
|
-
cache,
|
|
630
652
|
outputRefs(
|
|
631
653
|
node.projectDir,
|
|
632
654
|
await resolveOutputs({
|
|
@@ -646,14 +668,47 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
|
|
|
646
668
|
args.verify.allow.has(node.id),
|
|
647
669
|
)
|
|
648
670
|
}
|
|
671
|
+
// Put attempt 1's saved bytes back so disk == the cached artifact
|
|
672
|
+
// REGARDLESS of the verdict. Mirror the restoreHit sequence exactly:
|
|
673
|
+
// clean the declared globs FIRST (a nondeterministic re-run may have
|
|
674
|
+
// written a stray filename the restore would never overwrite), then
|
|
675
|
+
// restore, then record the exact changed paths so a downstream
|
|
676
|
+
// same-project task's git snapshot can't go stale. fp1's keys ARE
|
|
677
|
+
// attempt 1's saved rels (bare project rels + workspace-outputs/-
|
|
678
|
+
// prefixed ws rels — the artifact namespace).
|
|
679
|
+
let cleanedRels: string[] = []
|
|
680
|
+
let cleanedWsRels: string[] = []
|
|
681
|
+
if (outputs.length > 0) cleanedRels = await cleanOutputs(cleanArgs)
|
|
682
|
+
if (wsOutputs.length > 0) cleanedWsRels = await cleanWorkspaceOutputs(wsCleanArgs)
|
|
649
683
|
await cache.restoreOutputs(
|
|
650
684
|
hash,
|
|
651
685
|
node.projectDir,
|
|
652
686
|
wsOutputs.length > 0 ? args.workspaceRoot : undefined,
|
|
653
687
|
)
|
|
688
|
+
const savedKeys = [...verifyFp1.keys()]
|
|
689
|
+
if (outputs.length > 0) {
|
|
690
|
+
args.gitFilesCache?.markOutputsChanged(node.projectDir, [
|
|
691
|
+
...cleanedRels,
|
|
692
|
+
...savedKeys.filter((k) => !k.startsWith(WORKSPACE_OUTPUT_PREFIX)),
|
|
693
|
+
])
|
|
694
|
+
}
|
|
695
|
+
if (wsOutputs.length > 0) {
|
|
696
|
+
args.gitFilesCache?.markWorkspaceOutputsChanged(args.workspaceRoot, [
|
|
697
|
+
...cleanedWsRels,
|
|
698
|
+
...savedKeys
|
|
699
|
+
.filter((k) => k.startsWith(WORKSPACE_OUTPUT_PREFIX))
|
|
700
|
+
.map((k) => k.slice(WORKSPACE_OUTPUT_PREFIX.length)),
|
|
701
|
+
])
|
|
702
|
+
}
|
|
654
703
|
}
|
|
655
704
|
}
|
|
656
705
|
|
|
706
|
+
// Cross-machine diff feed: ship attempt 1's fingerprint (the SAVED bytes —
|
|
707
|
+
// the determinism re-run above never clobbers verifyFp1) so a connected
|
|
708
|
+
// serve can pair it with other platforms' reports for the same cache key.
|
|
709
|
+
const outputFp =
|
|
710
|
+
args.verify?.fingerprint && verifyFp1 !== undefined ? foldFingerprint(verifyFp1) : undefined
|
|
711
|
+
|
|
657
712
|
return {
|
|
658
713
|
node,
|
|
659
714
|
status: effectiveExitCode === 0 ? 'success' : 'failed',
|
|
@@ -666,6 +721,7 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
|
|
|
666
721
|
wallclockStartNs,
|
|
667
722
|
wallclockEndNs,
|
|
668
723
|
...(verify !== undefined ? { verify } : {}),
|
|
724
|
+
...(outputFp !== undefined ? { outputFp } : {}),
|
|
669
725
|
...(finalViolations.length > 0
|
|
670
726
|
? {
|
|
671
727
|
sandboxViolations: finalViolations.length,
|
|
@@ -95,6 +95,9 @@ export type {
|
|
|
95
95
|
TelemetrySink,
|
|
96
96
|
TelemetrySource,
|
|
97
97
|
} from './telemetry.js'
|
|
98
|
+
// `diffOutputTrees` is the ONE tree-diff implementation — the verify verdict
|
|
99
|
+
// and a serve's cross-machine fingerprint diff must never drift on it.
|
|
100
|
+
export { diffOutputTrees } from './verify.js'
|
|
98
101
|
export { deriveStableKeys } from './stable-keys.js'
|
|
99
102
|
export type { DeriveStableKeysArgs, StableKey } from './stable-keys.js'
|
|
100
103
|
export { captureGitContext, captureWorkspaceIdentity, resolveCacheScope } from './run-context.js'
|
|
@@ -112,8 +115,10 @@ export {
|
|
|
112
115
|
getHitRateSplit,
|
|
113
116
|
getInvocation,
|
|
114
117
|
getParallelismHistory,
|
|
118
|
+
getPeriodComparison,
|
|
115
119
|
getPrunableEntries,
|
|
116
120
|
getRecentFailures,
|
|
121
|
+
getRegressions,
|
|
117
122
|
getRun,
|
|
118
123
|
getRunHeatmap,
|
|
119
124
|
getRunTrends,
|
|
@@ -149,8 +154,14 @@ export type {
|
|
|
149
154
|
ListInvocationsArgs,
|
|
150
155
|
ListRunsArgs,
|
|
151
156
|
ParallelismPoint,
|
|
157
|
+
PeriodComparison,
|
|
158
|
+
PeriodComparisonArgs,
|
|
159
|
+
PeriodStats,
|
|
152
160
|
PrunableEntry,
|
|
153
161
|
ProjectRollup,
|
|
162
|
+
RegressedTask,
|
|
163
|
+
RegressionArgs,
|
|
164
|
+
TaskMover,
|
|
154
165
|
RunDetail,
|
|
155
166
|
RunSummaryRow,
|
|
156
167
|
StoragePoint,
|