@vzn/vx 0.0.96 → 0.0.98
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 +2 -1
- package/package.json +5 -5
- package/src/cli/help.ts +0 -1
- package/src/cli/index.ts +0 -3
- package/src/cli/info.ts +1 -1
- package/src/cli/last.ts +2 -1
- package/src/cli/run-id.ts +27 -0
- package/src/cli/run.ts +3 -1
- package/src/cli/select.ts +7 -2
- package/src/cli/why.ts +5 -1
- package/src/orchestrator/doctor.ts +1 -1
- package/src/orchestrator/index.ts +0 -1
- package/src/orchestrator/metrics.ts +1 -4
- package/src/util/index.ts +1 -1
- package/src/util/verbs.ts +2 -6
- package/src/workspace/config-schema.ts +2 -2
package/README.md
CHANGED
|
@@ -93,7 +93,8 @@ npx vx run build --all # restored from the cache, not rebuilt
|
|
|
93
93
|
Or copy [`examples/basic`](examples/basic), a two-package starter CI
|
|
94
94
|
runs on every commit. More: [Quickstart](https://vznjs.github.io/vx/quickstart/) ·
|
|
95
95
|
[Configure](https://vznjs.github.io/vx/guides/configure/) ·
|
|
96
|
-
[CLI](packages/vx/docs/cli.md) · [every config field](packages/vx/docs/schema.md)
|
|
96
|
+
[CLI](packages/vx/docs/cli.md) · [every config field](packages/vx/docs/schema.md) ·
|
|
97
|
+
[playground](https://vznjs.github.io/vx/playground/) (the planner, in your browser).
|
|
97
98
|
|
|
98
99
|
## Already on Turborepo or Nx?
|
|
99
100
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.98",
|
|
4
4
|
"description": "A fast, correct task runner and build cache for JavaScript monorepos.",
|
|
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.
|
|
21
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
22
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
23
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
20
|
+
"@vzn/vx-linux-x64": "0.0.98",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.98",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.98",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.98"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cli/help.ts
CHANGED
|
@@ -141,7 +141,6 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
141
141
|
' task counts, cache dir/entries/size, recent runs,',
|
|
142
142
|
' lock status.',
|
|
143
143
|
' --format <fmt> pretty (default) | json.',
|
|
144
|
-
' vx stats Deprecated: alias of vx info.',
|
|
145
144
|
" vx why <task> Why did this task re-run? Compares the task's latest",
|
|
146
145
|
' run (or --run <id>) against its previous run: names the',
|
|
147
146
|
' exact changed cache-key components (files / env / runtime',
|
package/src/cli/index.ts
CHANGED
|
@@ -61,9 +61,6 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
61
61
|
return await (await import('./upgrade.js')).upgradeCmd(rest)
|
|
62
62
|
case 'show':
|
|
63
63
|
return await (await import('./show.js')).showCmd(rest)
|
|
64
|
-
case 'stats': // deprecated alias — `vx info` absorbed `vx stats`
|
|
65
|
-
process.stderr.write('vx stats is deprecated; use vx info\n')
|
|
66
|
-
return await (await import('./info.js')).infoCmd(rest)
|
|
67
64
|
case 'info':
|
|
68
65
|
return await (await import('./info.js')).infoCmd(rest)
|
|
69
66
|
case 'why':
|
package/src/cli/info.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// `vx info` — workspace doctor printout. One screen of environment +
|
|
2
2
|
// workspace + cache facts for bug reports and quick sanity checks.
|
|
3
|
-
//
|
|
3
|
+
// The facts come
|
|
4
4
|
// from the orchestrator's `collectInfo` (doctor.ts), which `vx mcp` reads
|
|
5
5
|
// too; this file parses the flags and renders the rows.
|
|
6
6
|
|
package/src/cli/last.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { formatElapsed, UserError } from '../util/index.js'
|
|
18
18
|
import { findWorkspaceRoot } from '../workspace/index.js'
|
|
19
19
|
import { cliCacheDir, parseCacheDirFlag, warnToStderr } from './workspace-config.js'
|
|
20
|
+
import { resolveRunId } from './run-id.js'
|
|
20
21
|
|
|
21
22
|
interface LastArgs {
|
|
22
23
|
runId?: string
|
|
@@ -204,7 +205,7 @@ export async function lastCmd(args: readonly string[]): Promise<number> {
|
|
|
204
205
|
|
|
205
206
|
const inv =
|
|
206
207
|
parsed.runId !== undefined
|
|
207
|
-
? getInvocation(db, parsed.runId)
|
|
208
|
+
? getInvocation(db, resolveRunId(db, parsed.runId, 'vx last') ?? parsed.runId)
|
|
208
209
|
: (listInvocations(db, { limit: 1 })[0] ?? null)
|
|
209
210
|
if (inv === null || inv === undefined) {
|
|
210
211
|
throw new UserError(
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// A run id as a user types it: the whole id, or a prefix of one, as git
|
|
2
|
+
// takes a short sha. `vx last --list` prints 36-character ids; nobody
|
|
3
|
+
// retypes one whole.
|
|
4
|
+
|
|
5
|
+
import type { Database } from 'bun:sqlite'
|
|
6
|
+
import { UserError } from '../util/index.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The recorded run `raw` names: itself when recorded, else the one run
|
|
10
|
+
* whose id starts with it. Null when none does; a prefix several runs share
|
|
11
|
+
* is refused with those runs, since picking one would replay the wrong run.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveRunId(db: Database, raw: string, verb: string): string | null {
|
|
14
|
+
const exact = db.query('SELECT 1 FROM invocations WHERE run_id = ?').get(raw)
|
|
15
|
+
if (exact !== null) return raw
|
|
16
|
+
const rows = db
|
|
17
|
+
.query(
|
|
18
|
+
'SELECT run_id FROM invocations WHERE substr(run_id, 1, ?) = ? ORDER BY run_id DESC LIMIT 6',
|
|
19
|
+
)
|
|
20
|
+
.all(raw.length, raw) as Array<{ run_id: string }>
|
|
21
|
+
if (rows.length === 0) return null
|
|
22
|
+
if (rows.length === 1) return rows[0]!.run_id
|
|
23
|
+
const shown = rows.slice(0, 5).map((r) => ` ${r.run_id}`)
|
|
24
|
+
throw new UserError(
|
|
25
|
+
`${verb}: run id ${raw} is the start of ${rows.length > 5 ? 'more than 5' : rows.length} runs — type more of it:\n${shown.join('\n')}`,
|
|
26
|
+
)
|
|
27
|
+
}
|
package/src/cli/run.ts
CHANGED
|
@@ -547,7 +547,9 @@ export async function runCmd(args: readonly string[]): Promise<number> {
|
|
|
547
547
|
// explicit anchored positional.
|
|
548
548
|
if (tasks.length === 0) {
|
|
549
549
|
if (!process.stdin.isTTY) {
|
|
550
|
-
process.stderr.write(
|
|
550
|
+
process.stderr.write(
|
|
551
|
+
`vx run: missing task name (stdin is not a TTY, so no picker; vx run <task>, e.g. vx run build)${seeHelp('run')}\n`,
|
|
552
|
+
)
|
|
551
553
|
return 1
|
|
552
554
|
}
|
|
553
555
|
const picked = await pickTask(
|
package/src/cli/select.ts
CHANGED
|
@@ -305,7 +305,10 @@ export async function resolveFilters(
|
|
|
305
305
|
}
|
|
306
306
|
// Something matched, so the run proceeds; a pattern that matched nothing
|
|
307
307
|
// alongside it is still worth a line — it is probably a typo.
|
|
308
|
-
for (const f of unmatched)
|
|
308
|
+
for (const f of unmatched)
|
|
309
|
+
process.stderr.write(
|
|
310
|
+
`vx: filter "${f}" matched no projects${didYouMeanProject([f], projects)}\n`,
|
|
311
|
+
)
|
|
309
312
|
let staged: Map<string, ProjectEntry> | undefined
|
|
310
313
|
if (stagedPromise !== undefined) {
|
|
311
314
|
try {
|
|
@@ -398,7 +401,9 @@ export async function pickTask(
|
|
|
398
401
|
}
|
|
399
402
|
const n = Number(answer)
|
|
400
403
|
if (!Number.isInteger(n) || n < 1 || n > entries.length) {
|
|
401
|
-
process.stderr.write(
|
|
404
|
+
process.stderr.write(
|
|
405
|
+
`vx run: invalid selection: ${answer} (type a number from 1 to ${entries.length})\n`,
|
|
406
|
+
)
|
|
402
407
|
return null
|
|
403
408
|
}
|
|
404
409
|
return entries[n - 1] ?? null
|
package/src/cli/why.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { nearMatches, UserError } from '../util/index.js'
|
|
18
18
|
import { findWorkspaceRoot } from '../workspace/index.js'
|
|
19
19
|
import { cliCacheDir, parseCacheDirFlag, warnToStderr } from './workspace-config.js'
|
|
20
|
+
import { resolveRunId } from './run-id.js'
|
|
20
21
|
|
|
21
22
|
interface WhyArgs {
|
|
22
23
|
target?: string
|
|
@@ -166,7 +167,10 @@ export async function whyCmd(args: readonly string[]): Promise<number> {
|
|
|
166
167
|
try {
|
|
167
168
|
const db = cache.dbHandle()
|
|
168
169
|
const taskId = resolveTarget(cache, parsed.target)
|
|
169
|
-
const runId =
|
|
170
|
+
const runId =
|
|
171
|
+
parsed.runId !== undefined
|
|
172
|
+
? (resolveRunId(db, parsed.runId, 'vx why') ?? parsed.runId)
|
|
173
|
+
: latestRunId(db, taskId)
|
|
170
174
|
|
|
171
175
|
if (runId === null) {
|
|
172
176
|
// Runs exist (resolveTarget passed) but predate run ids — fall back to
|
|
@@ -243,7 +243,7 @@ async function sandboxFact(declared: number): Promise<InfoFacts['sandbox']> {
|
|
|
243
243
|
|
|
244
244
|
/**
|
|
245
245
|
* The doctor's output is pasted into bug reports and compared between
|
|
246
|
-
* invocations
|
|
246
|
+
* invocations, so a
|
|
247
247
|
* reason must not carry this process's id. The Linux runtime names its
|
|
248
248
|
* mux socket after the pid (`srt-mux-<pid>-<n>.sock`), and a listen that
|
|
249
249
|
* fails — a nested sandbox, a read-only tmpdir — quotes that path.
|
|
@@ -114,7 +114,7 @@ export function listRuns(db: Database, args: ListRunsArgs = {}): RunSummaryRow[]
|
|
|
114
114
|
/**
|
|
115
115
|
* One `vx run` invocation header — every `invocations` column, camelCased,
|
|
116
116
|
* `tags` parsed to a `Record<string,string>` and `dirty`/`ci`/`exitOk`
|
|
117
|
-
* surfaced as booleans.
|
|
117
|
+
* surfaced as booleans. The SPA's
|
|
118
118
|
* existing fields (runId / startedAt / endedAt / taskCount / failedCount /
|
|
119
119
|
* hitCount / totalDurationMs) are all present, so existing views keep working.
|
|
120
120
|
*/
|
|
@@ -147,9 +147,6 @@ export interface InvocationDetail {
|
|
|
147
147
|
tags: Record<string, string>
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
/** @deprecated kept as an alias of `InvocationDetail` for older callers. */
|
|
151
|
-
export type InvocationRow = InvocationDetail
|
|
152
|
-
|
|
153
150
|
interface InvocationRawRow {
|
|
154
151
|
runId: string
|
|
155
152
|
command: string
|
package/src/util/index.ts
CHANGED
|
@@ -45,7 +45,7 @@ export { appendTail, createTail, resetTail, tailText, type Tail } from './tail.j
|
|
|
45
45
|
export { ulid } from './ulid.js'
|
|
46
46
|
export { splitTaskId } from './task-id.js'
|
|
47
47
|
export { editDistance, nearMatches, nearest } from './edit-distance.js'
|
|
48
|
-
export { CORE_VERBS,
|
|
48
|
+
export { CORE_VERBS, MOVED_VERBS } from './verbs.js'
|
|
49
49
|
export { isUnsupportedBun, MIN_BUN, unsupportedBunMessage } from './bun-version.js'
|
|
50
50
|
export { executablePath } from './which.js'
|
|
51
51
|
export { procfsIsOwn } from './procfs.js'
|
package/src/util/verbs.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The verbs the CLI dispatcher (`cli/index.ts`) owns
|
|
3
|
-
* deprecated alias of `info` and stays out of hints but is still matched
|
|
4
|
-
* first. Lives in util rather than cli because the workspace VALIDATOR
|
|
2
|
+
* The verbs the CLI dispatcher (`cli/index.ts`) owns. Lives in util rather than cli because the workspace VALIDATOR
|
|
5
3
|
* refuses a plugin verb that names one of these — a verb core matches
|
|
6
4
|
* first could never run — and the workspace module cannot import cli.
|
|
7
5
|
*/
|
|
@@ -21,9 +19,6 @@ export const CORE_VERBS = [
|
|
|
21
19
|
'version',
|
|
22
20
|
] as const
|
|
23
21
|
|
|
24
|
-
/** Every word the dispatcher matches before consulting plugins. */
|
|
25
|
-
export const DISPATCHED_VERBS: readonly string[] = [...CORE_VERBS, 'stats']
|
|
26
|
-
|
|
27
22
|
/**
|
|
28
23
|
* Verbs core owned once and a package owns now. Not core verbs: a plugin
|
|
29
24
|
* may declare them, and the dispatcher prints the
|
|
@@ -36,4 +31,5 @@ export const MOVED_VERBS: Readonly<Record<string, string>> = {
|
|
|
36
31
|
prune:
|
|
37
32
|
'vx prune was removed (2026-09-11): the Docker-subset verb is not shipped; copy the ' +
|
|
38
33
|
'workspace and `--filter` the build instead',
|
|
34
|
+
stats: 'vx stats was removed (H-19): run `vx info`, which prints the same report',
|
|
39
35
|
}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
type WorkspaceConfig,
|
|
16
16
|
} from '../config.js'
|
|
17
17
|
import {
|
|
18
|
-
|
|
18
|
+
CORE_VERBS,
|
|
19
19
|
MAX_TIMEOUT_MS,
|
|
20
20
|
nearest,
|
|
21
21
|
normalizeGlob,
|
|
@@ -174,7 +174,7 @@ export function validateWorkspace(config: WorkspaceConfig, configPath: string):
|
|
|
174
174
|
// would run the first declared and hide the second. Here, in the
|
|
175
175
|
// schema, so every loader of the file — a run, a reading verb, the
|
|
176
176
|
// plugin-verb lookup — refuses it the same way.
|
|
177
|
-
if (
|
|
177
|
+
if ((CORE_VERBS as readonly string[]).includes(verb)) {
|
|
178
178
|
throw new UserError(
|
|
179
179
|
`${configPath}: plugin '${plug.name}' declares command '${verb}', a core verb — core verbs cannot be shadowed`,
|
|
180
180
|
)
|