@vzn/vx 0.0.589 → 0.0.590
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.590",
|
|
4
4
|
"description": "The fastest task runner and build cache for JavaScript monorepos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"node": ">=18"
|
|
22
22
|
},
|
|
23
23
|
"optionalDependencies": {
|
|
24
|
-
"@vzn/vx-linux-x64": "0.0.
|
|
25
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
26
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
27
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
24
|
+
"@vzn/vx-linux-x64": "0.0.590",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.590",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.590",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.590"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cli/select.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
claimedAffected,
|
|
32
32
|
fingerprintClaims,
|
|
33
33
|
gitOfDiscovery,
|
|
34
|
+
hasHook,
|
|
34
35
|
isDefaultBuild,
|
|
35
36
|
keepDiscoveryGraph,
|
|
36
37
|
} from '../orchestrator/index.js'
|
|
@@ -300,6 +301,15 @@ export async function resolveFilters(
|
|
|
300
301
|
if (f.raw === affected) {
|
|
301
302
|
changes = await affectedChanges(args)
|
|
302
303
|
names = changes.projects
|
|
304
|
+
// A `graph` plugin may tie any task to a changed path (an edge, an
|
|
305
|
+
// input), and the run alone builds that graph: every project is a
|
|
306
|
+
// candidate, and the run keeps the tasks its final graph reaches.
|
|
307
|
+
if (
|
|
308
|
+
changes.changed.length > 0 &&
|
|
309
|
+
hasHook((await loadCliWorkspace(root)).plugins, 'graph')
|
|
310
|
+
) {
|
|
311
|
+
names = new Set(projects.map((p) => p.name))
|
|
312
|
+
}
|
|
303
313
|
} else {
|
|
304
314
|
names = await affectedProjects(args)
|
|
305
315
|
}
|
|
@@ -88,8 +88,13 @@ export function affectedRoots(
|
|
|
88
88
|
return false
|
|
89
89
|
}
|
|
90
90
|
const seeded = (n: TaskNode): boolean => {
|
|
91
|
-
if (!changes.projects.has(n.projectName)) return false
|
|
92
91
|
const cache = n.config.cache
|
|
92
|
+
// Asked of every node, not only the changed projects' (the
|
|
93
|
+
// `workspaceFiles` owners): a `graph` hook may have given the glob.
|
|
94
|
+
if (cache !== undefined && changes.changed.some((rel) => declaresInput(cache, null, rel))) {
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
if (!changes.projects.has(n.projectName)) return false
|
|
93
98
|
// A group runs nothing; only the default `build` (projects.ts) is keyed.
|
|
94
99
|
// An uncached task reads its project: a root file another task of it
|
|
95
100
|
// declares (`workspaceFiles`) is no change there.
|
|
@@ -97,10 +102,7 @@ export function affectedRoots(
|
|
|
97
102
|
return !isGroupTask(n) && (whole.has(n.projectName) || changes.paths.has(n.projectName))
|
|
98
103
|
}
|
|
99
104
|
if (whole.has(n.projectName)) return true
|
|
100
|
-
|
|
101
|
-
return true
|
|
102
|
-
}
|
|
103
|
-
return changes.changed.some((rel) => declaresInput(cache, null, rel))
|
|
105
|
+
return (changes.paths.get(n.projectName) ?? []).some((rel) => declaresInput(cache, rel, null))
|
|
104
106
|
}
|
|
105
107
|
const reached = new Map<string, boolean>()
|
|
106
108
|
const reaches = (id: string): boolean => {
|
|
@@ -294,7 +294,11 @@ export interface ProjectHookContext extends BaseContext {
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
export interface GraphHookContext extends BaseContext {
|
|
297
|
-
/**
|
|
297
|
+
/**
|
|
298
|
+
* Task ids the user asked for (the rest were pulled in by `dependsOn`).
|
|
299
|
+
* Under `--affected` this is every candidate: the selection reads the
|
|
300
|
+
* graph the hooks leave and cuts it down afterwards.
|
|
301
|
+
*/
|
|
298
302
|
readonly requested: readonly string[]
|
|
299
303
|
}
|
|
300
304
|
|
|
@@ -521,9 +521,43 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
521
521
|
: {}),
|
|
522
522
|
})
|
|
523
523
|
let nodes = graphOf(requested)
|
|
524
|
+
const hasGraphHook = hasHook(plugins, 'graph')
|
|
525
|
+
const graphStage = async (): Promise<void> => {
|
|
526
|
+
// A hook edits each node's config in place, as `project` does, so the
|
|
527
|
+
// edit is held to what the loader accepts from a user: a misspelled
|
|
528
|
+
// `exec` field ran as an empty command and failed with no reason.
|
|
529
|
+
const configPaths = new Map(projectMetas.map((m) => [m.name, m.configPath]))
|
|
530
|
+
await applyGraphHooks(
|
|
531
|
+
plugins,
|
|
532
|
+
nodes,
|
|
533
|
+
{
|
|
534
|
+
workspaceRoot,
|
|
535
|
+
cacheDir,
|
|
536
|
+
warn: (m) => log.status(m),
|
|
537
|
+
requested: [...nodes.values()].filter((n) => n.requested).map((n) => n.id),
|
|
538
|
+
},
|
|
539
|
+
(plugin) => {
|
|
540
|
+
for (const n of nodes.values()) {
|
|
541
|
+
if (isDefaultBuild(n.config)) continue
|
|
542
|
+
const where = configPaths.get(n.projectName) ?? `${n.projectName} (no config file)`
|
|
543
|
+
validateProjectConfig(
|
|
544
|
+
{ tasks: { [n.taskName]: n.config } },
|
|
545
|
+
`${where} (after plugin '${plugin.name}')`,
|
|
546
|
+
)
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
workspaceConfig?.rules,
|
|
550
|
+
)
|
|
551
|
+
}
|
|
552
|
+
// `--affected` selects over the FINAL graph: an edge or an input a
|
|
553
|
+
// `graph` hook adds moves a key, so the hooks run first, over every
|
|
554
|
+
// candidate, and the selection prunes what they left.
|
|
555
|
+
const hooksFirst = hasGraphHook && options.affected !== undefined
|
|
524
556
|
// `--affected` keeps a bare request only when the diff reaches its
|
|
525
557
|
// closure; a `pkg#task` the user named runs as it always does.
|
|
526
558
|
if (options.affected !== undefined) {
|
|
559
|
+
const before = hooksFirst ? new Set(nodes.keys()) : null
|
|
560
|
+
if (hooksFirst) await graphStage()
|
|
527
561
|
const named = new Set(tasks.filter((t) => t.includes('#')))
|
|
528
562
|
const outright = new Set(options.selectedOutright)
|
|
529
563
|
const ids = requested.map((r) => `${r.project}#${r.task}`)
|
|
@@ -532,7 +566,20 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
532
566
|
(r, i) => named.has(ids[i]!) || outright.has(r.project) || reached.has(ids[i]!),
|
|
533
567
|
)
|
|
534
568
|
if (kept.length === 0) return emptyRun('none-affected')
|
|
535
|
-
if (
|
|
569
|
+
if (before !== null) {
|
|
570
|
+
const asked = new Set(ids)
|
|
571
|
+
// What a hook added or asked for is the plugin's choice, kept as it
|
|
572
|
+
// would be on a run without the diff.
|
|
573
|
+
const roots = [
|
|
574
|
+
...kept.map((r) => `${r.project}#${r.task}`),
|
|
575
|
+
...[...nodes.values()]
|
|
576
|
+
.filter((n) => !before.has(n.id) || (n.requested && !asked.has(n.id)))
|
|
577
|
+
.map((n) => n.id),
|
|
578
|
+
]
|
|
579
|
+
pruneToClosure(nodes, new Set(roots))
|
|
580
|
+
for (let i = unproven.length - 1; i >= 0; i--)
|
|
581
|
+
if (!nodes.has(unproven[i]![0])) unproven.splice(i, 1)
|
|
582
|
+
} else if (kept.length < requested.length) {
|
|
536
583
|
unproven.length = 0
|
|
537
584
|
nodes = graphOf(kept)
|
|
538
585
|
}
|
|
@@ -605,33 +652,7 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
605
652
|
localCache,
|
|
606
653
|
)
|
|
607
654
|
mark('git enumeration')
|
|
608
|
-
if (
|
|
609
|
-
// A hook edits each node's config in place, as `project` does, so the
|
|
610
|
-
// edit is held to what the loader accepts from a user: a misspelled
|
|
611
|
-
// `exec` field ran as an empty command and failed with no reason.
|
|
612
|
-
const configPaths = new Map(projectMetas.map((m) => [m.name, m.configPath]))
|
|
613
|
-
await applyGraphHooks(
|
|
614
|
-
plugins,
|
|
615
|
-
nodes,
|
|
616
|
-
{
|
|
617
|
-
workspaceRoot,
|
|
618
|
-
cacheDir,
|
|
619
|
-
warn: (m) => log.status(m),
|
|
620
|
-
requested: [...nodes.values()].filter((n) => n.requested).map((n) => n.id),
|
|
621
|
-
},
|
|
622
|
-
(plugin) => {
|
|
623
|
-
for (const n of nodes.values()) {
|
|
624
|
-
if (isDefaultBuild(n.config)) continue
|
|
625
|
-
const where = configPaths.get(n.projectName) ?? `${n.projectName} (no config file)`
|
|
626
|
-
validateProjectConfig(
|
|
627
|
-
{ tasks: { [n.taskName]: n.config } },
|
|
628
|
-
`${where} (after plugin '${plugin.name}')`,
|
|
629
|
-
)
|
|
630
|
-
}
|
|
631
|
-
},
|
|
632
|
-
workspaceConfig?.rules,
|
|
633
|
-
)
|
|
634
|
-
}
|
|
655
|
+
if (hasGraphHook && !hooksFirst) await graphStage()
|
|
635
656
|
// Args after `--` go to requested commands only; with none among them
|
|
636
657
|
// they reached nothing and the run passed, the `--watch` unheard.
|
|
637
658
|
if ((options.forwardArgs?.length ?? 0) > 0) {
|
|
@@ -767,3 +788,23 @@ async function refuseUndeclaredDeps(
|
|
|
767
788
|
if (!declared.has(name)) throw undeclaredDepsError(id, name)
|
|
768
789
|
}
|
|
769
790
|
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Drops every node outside the `dependsOn` closure of `roots`, and demotes a
|
|
794
|
+
* requested one only a kept task pulls in: what a rebuild from the kept
|
|
795
|
+
* requests would hold, with the `graph` hooks' edits kept.
|
|
796
|
+
*/
|
|
797
|
+
function pruneToClosure(nodes: Map<string, TaskNode>, roots: ReadonlySet<string>): void {
|
|
798
|
+
const keep = new Set<string>()
|
|
799
|
+
const stack = [...roots]
|
|
800
|
+
while (stack.length > 0) {
|
|
801
|
+
const id = stack.pop()!
|
|
802
|
+
if (keep.has(id)) continue
|
|
803
|
+
keep.add(id)
|
|
804
|
+
stack.push(...nodes.get(id)!.deps)
|
|
805
|
+
}
|
|
806
|
+
for (const [id, n] of nodes) {
|
|
807
|
+
if (!keep.has(id)) nodes.delete(id)
|
|
808
|
+
else if (!roots.has(id)) n.requested = false
|
|
809
|
+
}
|
|
810
|
+
}
|