@vzn/vx 0.0.24 → 0.0.25

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.24",
3
+ "version": "0.0.25",
4
4
  "description": "An open, extensible monorepo task runner.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,10 +17,10 @@
17
17
  "node": ">=18"
18
18
  },
19
19
  "optionalDependencies": {
20
- "@vzn/vx-linux-x64": "0.0.24",
21
- "@vzn/vx-linux-arm64": "0.0.24",
22
- "@vzn/vx-darwin-x64": "0.0.24",
23
- "@vzn/vx-darwin-arm64": "0.0.24"
20
+ "@vzn/vx-linux-x64": "0.0.25",
21
+ "@vzn/vx-linux-arm64": "0.0.25",
22
+ "@vzn/vx-darwin-x64": "0.0.25",
23
+ "@vzn/vx-darwin-arm64": "0.0.25"
24
24
  },
25
25
  "dependencies": {
26
26
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -30,6 +30,13 @@ export interface TaskNode {
30
30
  config: TaskConfig
31
31
  /** Ids of tasks that must complete before this one runs. */
32
32
  deps: string[]
33
+ /**
34
+ * The members of `deps` that order the run and fold into no key:
35
+ * `--exclude-dependencies` dropped a task between this one and them
36
+ * (`excludeDependencies`). The dropped task's derived key already folds
37
+ * theirs, so folding them again would give a key no full run derives.
38
+ */
39
+ orderOnly?: string[]
33
40
  /**
34
41
  * True for the tasks the user actually asked for (via cwd, `--all`,
35
42
  * `--filter`, or `pkg#task`). False for deps pulled in by `dependsOn`
@@ -588,6 +595,27 @@ export function excludeDependencies(
588
595
  if (lost.length === 0) continue
589
596
  dropped.set(id, lost)
590
597
  node.deps = node.deps.filter(stays)
598
+ // A scheduled task reached through a dropped one still runs first: in
599
+ // `test → gen → build` with `gen` excluded and `build` requested, the
600
+ // edge to `gen` went and nothing ordered `test` after `build`, so the
601
+ // two ran at once (item 1019). The walk reads unscheduled nodes only,
602
+ // whose deps this loop leaves as they are.
603
+ const order = new Set<string>()
604
+ const seen = new Set<string>()
605
+ const stack = [...lost]
606
+ while (stack.length > 0) {
607
+ const dep = stack.pop()!
608
+ if (seen.has(dep)) continue
609
+ seen.add(dep)
610
+ if (scheduled.has(dep)) {
611
+ if (!node.deps.includes(dep)) order.add(dep)
612
+ continue
613
+ }
614
+ stack.push(...nodes.get(dep)!.deps)
615
+ }
616
+ if (order.size === 0) continue
617
+ node.orderOnly = [...order].sort()
618
+ node.deps = [...node.deps, ...node.orderOnly].sort()
591
619
  }
592
620
  for (const id of keyOnly.keys()) nodes.delete(id)
593
621
  return { keyOnly, dropped }
@@ -16,7 +16,7 @@
16
16
  import type { CacheLayer, GitFilesCache } from '../cache/index.js'
17
17
  import { isGroupTask, type TaskNode, type TaskOutcome } from '../graph/index.js'
18
18
  import { computeGroupHash, computeTaskHash, type HashCache } from './task-hash.js'
19
- import { filterUpstreamHashes } from './upstream.js'
19
+ import { filterUpstreamHashes, keyedDeps } from './upstream.js'
20
20
 
21
21
  export interface KeyExcludedArgs {
22
22
  /** The scheduled graph, after `excludeDependencies`. */
@@ -41,7 +41,7 @@ export interface KeyExcludedArgs {
41
41
  export async function keyExcludedDependencies(args: KeyExcludedArgs): Promise<void> {
42
42
  const nodeOf = (id: string): TaskNode => args.nodes.get(id) ?? args.keyOnly.get(id)!
43
43
  const fullDeps = (node: TaskNode): readonly string[] => [
44
- ...node.deps,
44
+ ...keyedDeps(node),
45
45
  ...(args.dropped.get(node.id) ?? []),
46
46
  ]
47
47
  // Each key's promise is made after its dependencies' promises, in a
@@ -17,7 +17,15 @@ import { UserError } from '../util/index.js'
17
17
  * a key at one of them and not the others.
18
18
  */
19
19
  export function keyUpstream(node: TaskNode, upstream: TaskOutcome[]): TaskOutcome[] {
20
- return node.excludedUpstream === undefined ? upstream : [...upstream, ...node.excludedUpstream]
20
+ const order = node.orderOnly
21
+ const keyed = order === undefined ? upstream : upstream.filter((o) => !order.includes(o.node.id))
22
+ return node.excludedUpstream === undefined ? keyed : [...keyed, ...node.excludedUpstream]
23
+ }
24
+
25
+ /** `node.deps` less its ordering-only edges: the dependencies a key may fold. */
26
+ export function keyedDeps(node: TaskNode): readonly string[] {
27
+ const order = node.orderOnly
28
+ return order === undefined ? node.deps : node.deps.filter((d) => !order.includes(d))
21
29
  }
22
30
 
23
31
  /**