@vzn/vx 0.0.465 → 0.0.467
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 +5 -5
- package/src/cli/last.ts +14 -6
- package/src/orchestrator/doctor.ts +40 -16
- package/src/workspace/affected.ts +13 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.467",
|
|
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.467",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.467",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.467",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.467"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cli/last.ts
CHANGED
|
@@ -208,6 +208,10 @@ export async function lastCmd(args: readonly string[]): Promise<number> {
|
|
|
208
208
|
noteSchemaReset(cache, warnToStderr)
|
|
209
209
|
try {
|
|
210
210
|
const db = cache.dbHandle()
|
|
211
|
+
// Nothing recorded at all is its own answer: `--failed` said "no
|
|
212
|
+
// recorded run failed" and a run id pointed at a `--list` that lists
|
|
213
|
+
// nothing, while `--list --failed` past green runs said there were none.
|
|
214
|
+
const noRuns = (): boolean => listInvocations(db, { limit: 1 }).length === 0
|
|
211
215
|
|
|
212
216
|
if (parsed.list !== undefined) {
|
|
213
217
|
const invocations =
|
|
@@ -219,7 +223,9 @@ export async function lastCmd(args: readonly string[]): Promise<number> {
|
|
|
219
223
|
return 0
|
|
220
224
|
}
|
|
221
225
|
if (invocations.length === 0) {
|
|
222
|
-
process.stdout.write(
|
|
226
|
+
process.stdout.write(
|
|
227
|
+
parsed.failed === true && !noRuns() ? 'no recorded run failed\n' : 'no recorded runs\n',
|
|
228
|
+
)
|
|
223
229
|
return 0
|
|
224
230
|
}
|
|
225
231
|
for (const inv of invocations) {
|
|
@@ -241,11 +247,13 @@ export async function lastCmd(args: readonly string[]): Promise<number> {
|
|
|
241
247
|
: (listInvocations(db, { limit: 1 })[0] ?? null)
|
|
242
248
|
if (inv === null || inv === undefined) {
|
|
243
249
|
throw new UserError(
|
|
244
|
-
|
|
245
|
-
?
|
|
246
|
-
: parsed.
|
|
247
|
-
?
|
|
248
|
-
:
|
|
250
|
+
noRuns()
|
|
251
|
+
? 'vx last: no recorded runs yet — run something first'
|
|
252
|
+
: parsed.runId !== undefined
|
|
253
|
+
? `vx last: no recorded run ${parsed.runId} (vx last --list shows recent runs)`
|
|
254
|
+
: parsed.failed === true
|
|
255
|
+
? 'vx last: no recorded run failed'
|
|
256
|
+
: 'vx last: no recorded runs yet — run something first',
|
|
249
257
|
)
|
|
250
258
|
}
|
|
251
259
|
const detail = getRun(db, inv.runId)
|
|
@@ -114,8 +114,34 @@ export interface CollectInfoOptions {
|
|
|
114
114
|
export async function collectInfo(cwd: string, opts: CollectInfoOptions = {}): Promise<InfoFacts> {
|
|
115
115
|
const warn = opts.warn ?? warnToStderr
|
|
116
116
|
const reads: LoadReads = new Map()
|
|
117
|
+
// Both first: outside a workspace, or under a workspace config that does
|
|
118
|
+
// not load, the doctor refuses at once, with no probe (~180 ms) ahead.
|
|
117
119
|
const root = await findWorkspaceRoot(cwd, reads)
|
|
118
|
-
const
|
|
120
|
+
const workspace = await loadWorkspacePlugins(root, warn)
|
|
121
|
+
// The sandbox probe asks nothing of the workspace, so it runs under the
|
|
122
|
+
// loads below instead of after them. Awaited on every path: it resets
|
|
123
|
+
// the runtime it brought up, whose sockets would hold the process open.
|
|
124
|
+
const probing = sandboxProbe()
|
|
125
|
+
// Rejects only if the reset does; awaited below, never unhandled.
|
|
126
|
+
probing.catch(() => {})
|
|
127
|
+
try {
|
|
128
|
+
return await collectWorkspaceInfo(cwd, root, reads, workspace, opts, probing)
|
|
129
|
+
} catch (err) {
|
|
130
|
+
// The workspace's refusal is the error worth reporting.
|
|
131
|
+
await probing.catch(() => {})
|
|
132
|
+
throw err
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function collectWorkspaceInfo(
|
|
137
|
+
cwd: string,
|
|
138
|
+
root: string,
|
|
139
|
+
reads: LoadReads,
|
|
140
|
+
{ workspaceConfig, plugins }: Awaited<ReturnType<typeof loadWorkspacePlugins>>,
|
|
141
|
+
opts: CollectInfoOptions,
|
|
142
|
+
probing: Promise<Omit<InfoFacts['sandbox'], 'declared'>>,
|
|
143
|
+
): Promise<InfoFacts> {
|
|
144
|
+
const warn = opts.warn ?? warnToStderr
|
|
119
145
|
const cacheDir =
|
|
120
146
|
opts.cacheDir === undefined
|
|
121
147
|
? resolveCacheDir(root, workspaceConfig)
|
|
@@ -173,7 +199,10 @@ export async function collectInfo(cwd: string, opts: CollectInfoOptions = {}): P
|
|
|
173
199
|
}
|
|
174
200
|
|
|
175
201
|
const lockPresent = await Bun.file(lockfilePath(root)).exists()
|
|
176
|
-
|
|
202
|
+
// Asked while the probe runs: each is a synchronous spawn.
|
|
203
|
+
const git = gitVersion()
|
|
204
|
+
const statusCache = gitStatusCache(root)
|
|
205
|
+
const sandbox = { ...(await probing), declared: sandboxed }
|
|
177
206
|
return {
|
|
178
207
|
vx: VERSION,
|
|
179
208
|
// An unsupported Bun does not stop a run, it makes the run's ANSWERS
|
|
@@ -183,12 +212,12 @@ export async function collectInfo(cwd: string, opts: CollectInfoOptions = {}): P
|
|
|
183
212
|
// have, which is a property 19 tests hold on purpose.
|
|
184
213
|
bun: Bun.version,
|
|
185
214
|
bunSupported: !isUnsupportedBun(Bun.version),
|
|
186
|
-
git
|
|
215
|
+
git,
|
|
187
216
|
// The one `git status` walk per run is the warm path's critical path on
|
|
188
217
|
// a large tree (~55 ms at 1000 projects, measured 2026-09-02). git's
|
|
189
218
|
// own caches make it near-free after the first run, and they are OFF by
|
|
190
219
|
// default — say so, since nothing else in a run would.
|
|
191
|
-
gitStatusCache:
|
|
220
|
+
gitStatusCache: statusCache,
|
|
192
221
|
workspaceRoot: root,
|
|
193
222
|
projects: metas.length,
|
|
194
223
|
tasks: taskCount,
|
|
@@ -224,24 +253,19 @@ export async function collectInfo(cwd: string, opts: CollectInfoOptions = {}): P
|
|
|
224
253
|
}
|
|
225
254
|
|
|
226
255
|
/**
|
|
227
|
-
* The runtime probe's verdict — one sandboxed `true` (memoized)
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
256
|
+
* The runtime probe's verdict — one sandboxed `true` (memoized). The probe
|
|
257
|
+
* initializes the Linux runtime, whose proxy sockets would keep a
|
|
258
|
+
* standalone process alive; reset after asking, since the doctor runs
|
|
259
|
+
* nothing.
|
|
231
260
|
*/
|
|
232
|
-
async function
|
|
261
|
+
async function sandboxProbe(): Promise<Omit<InfoFacts['sandbox'], 'declared'>> {
|
|
233
262
|
try {
|
|
234
263
|
const verdict = await probeSandbox()
|
|
235
264
|
const untraced = verdict.available ? await untracedReason() : null
|
|
236
|
-
return {
|
|
237
|
-
available: verdict.available,
|
|
238
|
-
reason: stableSandboxReason(verdict.reason),
|
|
239
|
-
declared,
|
|
240
|
-
untraced,
|
|
241
|
-
}
|
|
265
|
+
return { available: verdict.available, reason: stableSandboxReason(verdict.reason), untraced }
|
|
242
266
|
} catch (err) {
|
|
243
267
|
const message = err instanceof Error ? err.message : String(err)
|
|
244
|
-
return { available: false, reason: stableSandboxReason(message),
|
|
268
|
+
return { available: false, reason: stableSandboxReason(message), untraced: null }
|
|
245
269
|
} finally {
|
|
246
270
|
await resetSandbox()
|
|
247
271
|
}
|
|
@@ -151,7 +151,6 @@ export async function affectedProjects(args: AffectedArgs): Promise<Set<string>>
|
|
|
151
151
|
`("${since.slice(0, range) || 'HEAD'}"); vx diffs it against the working tree.`,
|
|
152
152
|
)
|
|
153
153
|
}
|
|
154
|
-
await verifyRef(args.workspaceRoot, since)
|
|
155
154
|
// Diff from the MERGE BASE of `since` and HEAD, not from `since` itself:
|
|
156
155
|
// on a branch whose base has moved on, `git diff <base>` reports every
|
|
157
156
|
// file OTHER people changed on the base (over-selection that defeats a
|
|
@@ -160,7 +159,9 @@ export async function affectedProjects(args: AffectedArgs): Promise<Set<string>>
|
|
|
160
159
|
// when there is none (unrelated histories, a detached probe) the ref
|
|
161
160
|
// itself is the base, as before. Turbo's two-dot range `A..HEAD` is the
|
|
162
161
|
// one form that diffs from A itself.
|
|
163
|
-
const base = fromMergeBase
|
|
162
|
+
const base = fromMergeBase
|
|
163
|
+
? await mergeBase(args.workspaceRoot, since)
|
|
164
|
+
: (await verifyRef(args.workspaceRoot, since), since)
|
|
164
165
|
|
|
165
166
|
const [diffed, untracked] = await Promise.all([
|
|
166
167
|
// `--no-renames` is crucial for project-affected detection: with
|
|
@@ -693,7 +694,13 @@ const resolvedRefs = new Map<string, string>()
|
|
|
693
694
|
/** A ref that names an ancestor of HEAD by its own spelling (`HEAD~1`, `HEAD^`). */
|
|
694
695
|
const HEAD_ANCESTOR = /^HEAD(?:~\d*|\^\d*)+$/
|
|
695
696
|
|
|
696
|
-
/**
|
|
697
|
+
/**
|
|
698
|
+
* `git merge-base <ref> HEAD`, or `ref` itself when the two share no
|
|
699
|
+
* ancestor. A merge base proves the ref resolves, so `verifyRef`'s own
|
|
700
|
+
* spawn (synchronous, ~3.5 ms) runs only when there is none: to refuse a
|
|
701
|
+
* ref that does not resolve, or to pass one that does (exit 1, unrelated
|
|
702
|
+
* histories; a tree).
|
|
703
|
+
*/
|
|
697
704
|
async function mergeBase(workspaceRoot: string, ref: string): Promise<string> {
|
|
698
705
|
// HEAD's own ancestor is its merge base with HEAD: the commit it names,
|
|
699
706
|
// which the default base's search already resolved (`HEAD~1`).
|
|
@@ -702,7 +709,9 @@ async function mergeBase(workspaceRoot: string, ref: string): Promise<string> {
|
|
|
702
709
|
const proc = spawnGit(['merge-base', '--end-of-options', ref, 'HEAD'], workspaceRoot)
|
|
703
710
|
const [out, exit] = await Promise.all([new Response(proc.stdout).text(), proc.exited])
|
|
704
711
|
const sha = out.trim()
|
|
705
|
-
|
|
712
|
+
if (exit === 0 && sha.length > 0) return sha
|
|
713
|
+
await verifyRef(workspaceRoot, ref)
|
|
714
|
+
return ref
|
|
706
715
|
}
|
|
707
716
|
|
|
708
717
|
async function verifyRef(workspaceRoot: string, ref: string): Promise<void> {
|