@vzn/vx 0.0.18 → 0.0.22

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 (149) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +107 -70
  3. package/package.json +10 -20
  4. package/src/bin.ts +64 -5
  5. package/src/cache/archive.ts +279 -28
  6. package/src/cache/cache.ts +952 -1584
  7. package/src/cache/chained-cache.ts +14 -10
  8. package/src/cache/config-evals.ts +112 -0
  9. package/src/cache/file-hashes.ts +290 -0
  10. package/src/cache/git-inputs.ts +966 -0
  11. package/src/cache/index.ts +23 -7
  12. package/src/cache/inputs.ts +553 -739
  13. package/src/cache/key-fold.ts +232 -0
  14. package/src/cache/layer.ts +674 -0
  15. package/src/cache/layered-cache.ts +211 -49
  16. package/src/cache/output-index.ts +323 -0
  17. package/src/cache/policy.ts +84 -0
  18. package/src/cache/run-history.ts +152 -0
  19. package/src/cache/zstd.ts +183 -0
  20. package/src/cli/cache.ts +88 -30
  21. package/src/cli/completions.ts +104 -0
  22. package/src/cli/core-alias.ts +25 -0
  23. package/src/cli/format.ts +4 -15
  24. package/src/cli/help.ts +76 -38
  25. package/src/cli/index.ts +82 -17
  26. package/src/cli/info.ts +162 -91
  27. package/src/cli/init.ts +87 -0
  28. package/src/cli/last.ts +130 -23
  29. package/src/cli/lock.ts +36 -18
  30. package/src/cli/plan-format.ts +18 -7
  31. package/src/cli/plugin-commands.ts +34 -12
  32. package/src/cli/run.ts +138 -313
  33. package/src/cli/select.ts +342 -0
  34. package/src/cli/show.ts +151 -71
  35. package/src/cli/upgrade.ts +136 -15
  36. package/src/cli/watch.ts +1175 -116
  37. package/src/cli/why.ts +51 -29
  38. package/src/cli/workspace-config.ts +138 -0
  39. package/src/config.ts +183 -126
  40. package/src/exec/env.ts +11 -0
  41. package/src/exec/executor.ts +62 -31
  42. package/src/exec/index.ts +16 -2
  43. package/src/exec/kill-tree.ts +281 -0
  44. package/src/exec/local-executor.ts +41 -0
  45. package/src/exec/runner.ts +361 -77
  46. package/src/exec/sandbox-binds.ts +211 -0
  47. package/src/exec/sandbox-paths.ts +70 -0
  48. package/src/exec/sandbox-runtime.ts +1152 -415
  49. package/src/exec/sandbox-violations.ts +293 -0
  50. package/src/graph/dependency-spec.ts +14 -14
  51. package/src/graph/index.ts +6 -4
  52. package/src/graph/priorities.ts +130 -0
  53. package/src/graph/scheduler.ts +332 -329
  54. package/src/graph/task-graph.ts +552 -188
  55. package/src/index.ts +111 -102
  56. package/src/orchestrator/admission.ts +161 -0
  57. package/src/orchestrator/colors.ts +8 -3
  58. package/src/orchestrator/deferred-outputs.ts +25 -7
  59. package/src/orchestrator/doctor.ts +363 -0
  60. package/src/orchestrator/events.ts +111 -8
  61. package/src/orchestrator/excluded-keys.ts +155 -0
  62. package/src/orchestrator/execute-task.ts +450 -565
  63. package/src/orchestrator/failure-mode.ts +169 -28
  64. package/src/orchestrator/failure-recap.ts +121 -0
  65. package/src/orchestrator/fingerprint-watch.ts +89 -0
  66. package/src/orchestrator/framed-output.ts +121 -49
  67. package/src/orchestrator/history.ts +123 -104
  68. package/src/orchestrator/hit-restore.ts +285 -0
  69. package/src/orchestrator/index.ts +42 -5
  70. package/src/orchestrator/keyed-projects.ts +89 -0
  71. package/src/orchestrator/local-shortcircuit.ts +83 -23
  72. package/src/orchestrator/lockfile-claim.ts +401 -0
  73. package/src/orchestrator/logger.ts +128 -41
  74. package/src/orchestrator/metrics.ts +201 -72
  75. package/src/orchestrator/miss-save.ts +232 -0
  76. package/src/orchestrator/options.ts +63 -34
  77. package/src/orchestrator/persistent.ts +99 -0
  78. package/src/orchestrator/placement.ts +212 -0
  79. package/src/orchestrator/plan.ts +24 -11
  80. package/src/orchestrator/plugin-host.ts +200 -49
  81. package/src/orchestrator/plugin.ts +190 -14
  82. package/src/orchestrator/prepare.ts +168 -137
  83. package/src/orchestrator/projects.ts +270 -0
  84. package/src/orchestrator/remote-prefetch.ts +22 -2
  85. package/src/orchestrator/run-artifacts.ts +34 -3
  86. package/src/orchestrator/run-context.ts +20 -5
  87. package/src/orchestrator/run-lock.ts +349 -0
  88. package/src/orchestrator/run-records.ts +132 -0
  89. package/src/orchestrator/run-report.ts +6 -4
  90. package/src/orchestrator/run.ts +467 -611
  91. package/src/orchestrator/sandbox-request.ts +569 -0
  92. package/src/orchestrator/save-lane.ts +44 -0
  93. package/src/orchestrator/shell-verdict.ts +117 -0
  94. package/src/orchestrator/signals.ts +167 -0
  95. package/src/orchestrator/stable-keys.ts +259 -10
  96. package/src/orchestrator/status-line.ts +35 -2
  97. package/src/orchestrator/summary.ts +115 -25
  98. package/src/orchestrator/task-hash.ts +103 -21
  99. package/src/orchestrator/task-log-buffer.ts +15 -18
  100. package/src/orchestrator/telemetry-host.ts +27 -8
  101. package/src/orchestrator/telemetry.ts +71 -44
  102. package/src/orchestrator/upstream.ts +91 -45
  103. package/src/util/bun-version.ts +43 -0
  104. package/src/util/cgroup.ts +160 -0
  105. package/src/util/edit-distance.ts +49 -0
  106. package/src/util/errors.ts +86 -0
  107. package/src/util/hash.ts +10 -1
  108. package/src/util/index.ts +42 -5
  109. package/src/util/paths.ts +168 -5
  110. package/src/util/procfs.ts +20 -0
  111. package/src/util/settle.ts +23 -2
  112. package/src/util/size.ts +33 -0
  113. package/src/util/task-id.ts +20 -0
  114. package/src/util/timing.ts +11 -1
  115. package/src/util/verbs.ts +39 -0
  116. package/src/util/which.ts +48 -0
  117. package/src/workspace/affected.ts +368 -60
  118. package/src/workspace/config-cache.ts +271 -32
  119. package/src/workspace/config-eval.ts +60 -16
  120. package/src/workspace/config-imports.ts +131 -9
  121. package/src/workspace/config-schema.ts +1033 -0
  122. package/src/workspace/filter.ts +69 -17
  123. package/src/workspace/fingerprint.ts +59 -7
  124. package/src/workspace/index.ts +37 -3
  125. package/src/workspace/json-data.ts +90 -0
  126. package/src/workspace/load-reads.ts +32 -0
  127. package/src/workspace/lockfile.ts +41 -14
  128. package/src/workspace/migrate-scripts.ts +187 -0
  129. package/src/workspace/migration.ts +353 -0
  130. package/src/workspace/package-graph.ts +239 -31
  131. package/src/workspace/project-loader.ts +295 -815
  132. package/src/workspace/workspace.ts +266 -31
  133. package/plugins/local-cache/index.ts +0 -2
  134. package/plugins/local-executor/index.ts +0 -2
  135. package/plugins/schedule-history/index.ts +0 -2
  136. package/src/cache/cas-backend.ts +0 -115
  137. package/src/cache/digest.ts +0 -32
  138. package/src/cli/migrate-emit.ts +0 -21
  139. package/src/cli/migrate-nx.ts +0 -473
  140. package/src/cli/migrate-scripts.ts +0 -127
  141. package/src/cli/migrate-turbo.ts +0 -401
  142. package/src/cli/migrate.ts +0 -305
  143. package/src/cli/prune.ts +0 -291
  144. package/src/orchestrator/missing-plugin.ts +0 -9
  145. package/src/orchestrator/resources.ts +0 -45
  146. package/src/orchestrator/verify.ts +0 -174
  147. package/src/plugins/local-cache/index.ts +0 -11
  148. package/src/plugins/local-executor/index.ts +0 -45
  149. package/src/plugins/schedule-history/index.ts +0 -109
@@ -19,17 +19,125 @@
19
19
  // - RELATIVE specifiers only. A bare specifier is a package; it moves when
20
20
  // the lockfile moves, which the workspace fingerprint already covers.
21
21
  // - Descend only through files owned by NO project. A config reaching into
22
- // another project (this repo's `apps/docs/vx.config.ts` imports
23
- // `../../src/index.ts`) records that edge and STOPS there — following it
24
- // would drag substantially all of core `src/` into the closure, and the
25
- // containment channel already selects the project that owns it.
22
+ // another project (say a site's `vx.config.ts` importing
23
+ // `../core/src/index.ts`) records that edge and STOPS there — following
24
+ // it would drag substantially all of that project's `src/` into the
25
+ // closure, and the containment channel already selects the project that
26
+ // owns it.
26
27
 
28
+ import { existsSync } from 'node:fs'
27
29
  import { realpath } from 'node:fs/promises'
30
+ import { builtinModules } from 'node:module'
28
31
  import path from 'node:path'
29
32
  import type { ProjectMeta } from './workspace.js'
30
33
 
34
+ const BUILTINS = new Set(builtinModules)
35
+
36
+ /**
37
+ * One transpiler per loader, made on first use: constructing one costs
38
+ * 42 µs against 12 µs for the scan itself (measured 2026-09-16), and a
39
+ * fresh one per config put 1000 cold evaluations 130 ms behind.
40
+ */
41
+ const transpilers: Partial<Record<'ts' | 'js', Bun.Transpiler>> = {}
42
+ function scanner(loader: 'ts' | 'js'): Bun.Transpiler {
43
+ return (transpilers[loader] ??= new Bun.Transpiler({ loader }))
44
+ }
45
+
46
+ /**
47
+ * Whether Bun's own parser finds an ESM `export` in `source` — the one
48
+ * syntax that makes Bun evaluate a file as a module whatever else it says.
49
+ * Without one, a file that touches `module`, `exports`, `require`, `this`
50
+ * or `__dirname` at the top level is CommonJS to Bun. False on source the
51
+ * parser refuses: the evaluation names that error.
52
+ */
53
+ export function hasEsmExport(source: string, loader: 'ts' | 'js'): boolean {
54
+ try {
55
+ return scanner(loader).scan(source).exports.length > 0
56
+ } catch {
57
+ return false
58
+ }
59
+ }
60
+
61
+ /** The package a bare specifier names: `@scope/name` or the first segment. */
62
+ function packageOf(spec: string): string {
63
+ const parts = spec.split('/')
64
+ return spec.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]!
65
+ }
66
+
67
+ const SPECIFIER =
68
+ /\b(?:from|import)\s*\(?\s*["']([^"'./][^"']*)["']|\brequire\s*\(\s*["']([^"'./][^"']*)["']/g
69
+
70
+ /** Whether a specifier is one the walk would have to look up at all. */
71
+ function needsLookup(spec: string): boolean {
72
+ if (spec.includes(':') || BUILTINS.has(spec)) return false
73
+ return !(spec === '@vzn/vx' || spec.startsWith('@vzn/vx/'))
74
+ }
75
+
76
+ /**
77
+ * A textual pass before the transpiler's: its scan costs ~50 µs per config
78
+ * in situ (1000 cold evaluations: 50 ms, measured 2026-09-16), and most
79
+ * configs import `@vzn/vx` and nothing else. Every static specifier is a
80
+ * quoted string after `from`, `import` or `require(`; a candidate found
81
+ * here is checked exactly by the scan (a comment or a type-only import is
82
+ * the scan's to drop), and a source with no candidate skips it.
83
+ */
84
+ function hasBareCandidate(source: string): boolean {
85
+ SPECIFIER.lastIndex = 0
86
+ for (let m = SPECIFIER.exec(source); m !== null; m = SPECIFIER.exec(source)) {
87
+ if (needsLookup(m[1] ?? m[2] ?? '')) return true
88
+ }
89
+ return false
90
+ }
91
+
92
+ /**
93
+ * The bare specifiers of `source` that no `node_modules/<package>` above
94
+ * `fromDir` provides — the imports Bun would AUTO-INSTALL from the npm
95
+ * registry rather than fail, when no `node_modules` exists anywhere above
96
+ * (measured 2026-09-16: sixteen registry connections and 150 ms before
97
+ * "cannot find"; with one present, 0 and 1 ms). A config is evaluated in
98
+ * the user's process with the user's network: a fresh clone before its
99
+ * install, or a typo, must be a refusal, never a download. Builtins
100
+ * (`node:fs`, `fs`, `bun:sqlite`) and `@vzn/vx` (served by the core alias
101
+ * inside the compiled binary) are never in the list; relative and absolute
102
+ * specifiers resolve by path and are the loader's to refuse.
103
+ */
104
+ export function unprovidedBareImports(
105
+ source: string,
106
+ fromDir: string,
107
+ loader: 'ts' | 'js',
108
+ ): string[] {
109
+ if (!hasBareCandidate(source)) return []
110
+ let specifiers: string[]
111
+ try {
112
+ specifiers = scanner(loader)
113
+ .scanImports(source)
114
+ .map((i) => i.path)
115
+ } catch {
116
+ return [] // unparseable: the evaluation names the syntax error
117
+ }
118
+ const out: string[] = []
119
+ for (const spec of specifiers) {
120
+ if (spec.startsWith('.') || spec.startsWith('/') || !needsLookup(spec)) continue
121
+ const pkg = packageOf(spec)
122
+ let dir = path.resolve(fromDir)
123
+ let provided = false
124
+ for (;;) {
125
+ if (existsSync(path.join(dir, 'node_modules', pkg))) {
126
+ provided = true
127
+ break
128
+ }
129
+ const up = path.dirname(dir)
130
+ if (up === dir) break
131
+ dir = up
132
+ }
133
+ if (!provided && !out.includes(spec)) out.push(spec)
134
+ }
135
+ return out
136
+ }
137
+
31
138
  /**
32
- * Absolute resolved targets of the RELATIVE specifiers in `source`.
139
+ * Absolute resolved targets of the RELATIVE specifiers in `source`; one that
140
+ * does not resolve contributes the paths it names.
33
141
  *
34
142
  * Paths come back realpath'd, because `Bun.resolveSync` realpaths them — see
35
143
  * the caller, which realpaths everything it compares against for exactly that
@@ -39,7 +147,9 @@ import type { ProjectMeta } from './workspace.js'
39
147
  function scanLocalImports(source: string, fromDir: string, loader: 'ts' | 'js'): string[] {
40
148
  let specifiers: string[]
41
149
  try {
42
- specifiers = new Bun.Transpiler({ loader }).scanImports(source).map((i) => i.path)
150
+ specifiers = scanner(loader)
151
+ .scanImports(source)
152
+ .map((i) => i.path)
43
153
  } catch {
44
154
  return [] // unparseable source contributes no edges
45
155
  }
@@ -49,9 +159,18 @@ function scanLocalImports(source: string, fromDir: string, loader: 'ts' | 'js'):
49
159
  try {
50
160
  out.push(Bun.resolveSync(spec, fromDir))
51
161
  } catch {
52
- // Unresolvable (deleted, typo, extensionless miss) — a config that will
53
- // not load cannot be shown to import anything, and failing selection
54
- // over it would break a working build.
162
+ // Unresolvable: most often the change itself deleted or renamed the
163
+ // target, and the config that imports it is exactly the project the
164
+ // change broke. The edge is the path the specifier names (and, bare
165
+ // of an extension, the files Bun would have tried), so the deleted
166
+ // path in the diff still reaches its importer (item 958). Selection
167
+ // may widen; it is never hashed.
168
+ const named = path.resolve(fromDir, spec)
169
+ out.push(named)
170
+ if (path.extname(spec) === '') {
171
+ for (const ext of RESOLVED_EXTENSIONS)
172
+ out.push(named + ext, path.join(named, 'index' + ext))
173
+ }
55
174
  }
56
175
  }
57
176
  return out
@@ -91,6 +210,9 @@ async function realDirIndex(projects: readonly ProjectMeta[]): Promise<Map<strin
91
210
 
92
211
  const TS_EXT = new Set(['.ts', '.mts', '.cts'])
93
212
 
213
+ /** The extensions Bun tries for a specifier that names none. */
214
+ const RESOLVED_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.json']
215
+
94
216
  /** The deepest project containing `file`, or undefined when none does. */
95
217
  function ownerOf(file: string, dirToName: ReadonlyMap<string, string>): string | undefined {
96
218
  let dir = path.dirname(file)