@vzn/vx 0.0.541 → 0.0.543

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.541",
3
+ "version": "0.0.543",
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.541",
25
- "@vzn/vx-linux-arm64": "0.0.541",
26
- "@vzn/vx-darwin-x64": "0.0.541",
27
- "@vzn/vx-darwin-arm64": "0.0.541"
24
+ "@vzn/vx-linux-x64": "0.0.543",
25
+ "@vzn/vx-linux-arm64": "0.0.543",
26
+ "@vzn/vx-darwin-x64": "0.0.543",
27
+ "@vzn/vx-darwin-arm64": "0.0.543"
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -636,7 +636,8 @@ export class Cache implements CacheLayer {
636
636
  * and the store's own schema is `store_meta.schema` (`matchStoreSchema`). Or
637
637
  * `null` for an index that holds its entries itself (a `cacheDir`).
638
638
  * Undefined follows the layout the index records: a reading verb, a
639
- * plugin's handle.
639
+ * plugin's handle. An `'inspect'` open reads it only where the index
640
+ * records none (deleted, or never written beside a run's store).
640
641
  */
641
642
  storeRoot?: string | null,
642
643
  ) {
@@ -802,11 +803,13 @@ export class Cache implements CacheLayer {
802
803
  )?.value,
803
804
  )
804
805
  let storeDir =
805
- mode === 'inspect' || storeRoot === undefined
806
- ? recorded
807
- : storeRoot === null
808
- ? undefined
809
- : storeRoot
806
+ mode === 'inspect'
807
+ ? (recorded ?? (typeof storeRoot === 'string' ? storeRoot : undefined))
808
+ : storeRoot === undefined
809
+ ? recorded
810
+ : storeRoot === null
811
+ ? undefined
812
+ : storeRoot
810
813
  if (storeDir !== undefined && mode === 'open' && this.writeBlocked === null) {
811
814
  const blocked = storeBlocked(storeDir, typeof storeRoot === 'string' ? storeRoot : null)
812
815
  if (blocked !== null) {
package/src/cli/cache.ts CHANGED
@@ -4,7 +4,12 @@ import { flagHint, refusedWord, seeHelp } from './help.js'
4
4
  import { nearest, parseDuration, parseSize } from '../util/index.js'
5
5
  import { acquireRunLock } from '../orchestrator/index.js'
6
6
  import { findWorkspaceRoot } from '../workspace/index.js'
7
- import { cliCacheDir, parseCacheDirFlag, warnToStderr } from './workspace-config.js'
7
+ import {
8
+ cliCacheDir,
9
+ loadCliWorkspace,
10
+ parseCacheDirFlag,
11
+ warnToStderr,
12
+ } from './workspace-config.js'
8
13
  import { formatBytes } from './format.js'
9
14
 
10
15
  // parseSize moved to `util` (the orchestrator's resource resolver needs it
@@ -90,7 +95,7 @@ export function parsePruneArgs(args: readonly string[]): PruneArgs {
90
95
  // the deliberate way to do it.
91
96
  if (ms === 0) {
92
97
  return {
93
- error: `--older-than 0 would evict every entry — delete the cache directory instead`,
98
+ error: `--older-than 0 would evict every entry — to clear the cache, delete the cache store \`vx info\` names`,
94
99
  }
95
100
  }
96
101
  out.olderThanMs = Date.now() - ms
@@ -103,7 +108,7 @@ export function parsePruneArgs(args: readonly string[]): PruneArgs {
103
108
  if (bytes === null) return { error: `invalid size: ${v} (e.g. 500M, 1G)` }
104
109
  if (bytes === 0) {
105
110
  return {
106
- error: `--max-size 0 would evict every entry — delete the cache directory instead`,
111
+ error: `--max-size 0 would evict every entry — to clear the cache, delete the cache store \`vx info\` names`,
107
112
  }
108
113
  }
109
114
  // A bare number is bytes to `parseSize` (a computed size needs that),
@@ -161,14 +166,20 @@ async function pruneCmd(args: readonly string[]): Promise<number> {
161
166
  // operate on the same directory or prune silently no-ops against the
162
167
  // wrong path.
163
168
  // A dry run only reads: it never resets the index (item 896).
164
- const dir = await cliCacheDir(root, parsed.cacheDir)
169
+ // The store `vx run` keeps entries in (`~/.vx/<id>/cache`): with the
170
+ // index deleted, a prune followed nothing and said 0 while every entry
171
+ // stayed restorable (X-17).
172
+ let dir: string
173
+ let storeRoot: string | null = null
174
+ if (parsed.cacheDir !== undefined) dir = await cliCacheDir(root, parsed.cacheDir)
175
+ else ({ cacheDir: dir, storeRoot } = await loadCliWorkspace(root))
165
176
  // A workspace that never ran has nothing to prune, and opening the cache
166
177
  // to find that out made it: `.vx/cache` with a database and a
167
178
  // `.gitignore`, where item 900 had held the dry run to making nothing
168
179
  // (and a read-only checkout was refused as unwritable instead).
169
180
  const dry = parsed.dryRun === true
170
181
  const json = parsed.format === 'json'
171
- if (!existsSync(dir)) {
182
+ if (!existsSync(dir) && (storeRoot === null || !existsSync(storeRoot))) {
172
183
  printPruned({ evicted: 0, bytesFreed: 0, orphans: 0, orphanBytes: 0 }, dry, json)
173
184
  return 0
174
185
  }
@@ -182,7 +193,10 @@ async function pruneCmd(args: readonly string[]): Promise<number> {
182
193
  return 0
183
194
  }
184
195
  }
185
- const cache = parsed.dryRun === true ? Cache.inspect(dir) : new Cache(dir)
196
+ const cache =
197
+ parsed.dryRun === true
198
+ ? new Cache(dir, undefined, undefined, undefined, 'inspect', storeRoot)
199
+ : new Cache(dir, undefined, root, undefined, 'open', storeRoot)
186
200
  // A prune deletes rows and artifacts; a cache this user cannot write is
187
201
  // refused up front with the directory named, as a run refuses it, rather
188
202
  // than dying in the first DELETE with SQLite's "readonly database" and a
package/src/cli/why.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  } from '../orchestrator/index.js'
19
19
  import { MASKED, nearMatches, printable, UserError } from '../util/index.js'
20
20
  import { findWorkspaceRoot } from '../workspace/index.js'
21
+ import { findCwdProject } from './select.js'
21
22
  import { cliCacheDir, parseCacheDirFlag } from './workspace-config.js'
22
23
 
23
24
  interface WhyArgs {
@@ -127,9 +128,14 @@ function suggest(query: string, ids: readonly string[]): string {
127
128
  /**
128
129
  * Resolve a positional target to a full `project#task` id against the runs
129
130
  * table. A `pkg#task` form is used as-is; a bare task name matches every
130
- * project that ran it — unique → resolved, several → error listing them.
131
+ * project that ran it — unique → resolved, several → the project the cwd
132
+ * sits in, as `vx run build` there picks (X-18), else an error listing them.
131
133
  */
132
- function resolveTarget(cache: Cache, target: string): string {
134
+ async function resolveTarget(
135
+ cache: Cache,
136
+ target: string,
137
+ cwdProject: () => Promise<string | null>,
138
+ ): Promise<string> {
133
139
  const db = cache.dbHandle()
134
140
  if (target.includes('#')) {
135
141
  // An exact id needs one row, and its newest is where a scan from the
@@ -151,6 +157,8 @@ function resolveTarget(cache: Cache, target: string): string {
151
157
  const matches = ids.filter((id) => id.endsWith(`#${target}`))
152
158
  if (matches.length === 1) return matches[0]!
153
159
  if (matches.length > 1) {
160
+ const here = await cwdProject()
161
+ if (here !== null && matches.includes(`${here}#${target}`)) return `${here}#${target}`
154
162
  throw new UserError(
155
163
  `vx why: "${target}" ran in ${matches.length} projects — pick one:\n` +
156
164
  matches.map((m) => ` ${m}`).join('\n'),
@@ -187,11 +195,19 @@ export async function whyCmd(args: readonly string[]): Promise<number> {
187
195
  const cache = Cache.inspect(await cliCacheDir(root, parsed.cacheDir))
188
196
  try {
189
197
  const db = cache.dbHandle()
190
- const taskId = resolveTarget(cache, await rootSpelled(root, parsed.target))
191
- const runId =
192
- parsed.runId !== undefined
193
- ? (resolveRunId(db, parsed.runId, 'vx why') ?? parsed.runId)
194
- : latestRunId(db, taskId)
198
+ const taskId = await resolveTarget(cache, await rootSpelled(root, parsed.target), () =>
199
+ findCwdProject(process.cwd()),
200
+ )
201
+ let runId: string | null
202
+ if (parsed.runId !== undefined) {
203
+ runId = resolveRunId(db, parsed.runId, 'vx why')
204
+ // An unknown id read as a known run missing the task (X-18).
205
+ if (runId === null) {
206
+ throw new UserError(
207
+ `vx why: no recorded run ${parsed.runId} (vx last --list shows recent runs)`,
208
+ )
209
+ }
210
+ } else runId = latestRunId(db, taskId)
195
211
 
196
212
  if (runId === null) {
197
213
  // Runs exist (resolveTarget passed) but predate run ids — fall back to
@@ -245,7 +261,10 @@ export async function whyCmd(args: readonly string[]): Promise<number> {
245
261
  // The key moved and the diff says by what: the verdict names it, so
246
262
  // an env-only change reads as one line, not "inputs differ" above a
247
263
  // table to scan.
248
- const moved = diff.entries.map((e) => `${e.kind} ${printable(e.name)}`)
264
+ // The config component is kind and name `config`: it read "config config".
265
+ const moved = diff.entries.map((e) =>
266
+ e.name === e.kind ? e.kind : `${e.kind} ${printable(e.name)}`,
267
+ )
249
268
  lines.push(
250
269
  ` verdict ${
251
270
  why.hashChanged === true && moved.length > 0
@@ -153,7 +153,7 @@ async function collectWorkspaceInfo(
153
153
  : path.resolve(cwd, opts.cacheDir)
154
154
  const metas = await discoverProjects(await loadWorkspace(root, reads), plugins, cacheDir, warn)
155
155
  const storeRoot = opts.cacheDir === undefined ? resolveStoreRoot(root, workspaceConfig) : null
156
- const cache = Cache.inspect(cacheDir)
156
+ const cache = new Cache(cacheDir, undefined, undefined, undefined, 'inspect', storeRoot)
157
157
  let stats
158
158
  let orphans
159
159
  let flaky: FlakyTask[]