@vzn/vx 0.0.522 → 0.0.523
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/cache/cache.ts +16 -41
- package/src/cache/index.ts +0 -1
- package/src/cli/cache.ts +1 -2
- package/src/cli/last.ts +2 -3
- package/src/cli/why.ts +2 -3
- package/src/cli/workspace-config.ts +1 -2
- package/src/orchestrator/doctor.ts +1 -2
- package/src/orchestrator/prepare.ts +0 -2
- package/src/workspace/migration.ts +11 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.523",
|
|
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.523",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.523",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.523",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.523"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cache/cache.ts
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
import { Database } from 'bun:sqlite'
|
|
35
35
|
import {
|
|
36
36
|
accessSync,
|
|
37
|
+
chmodSync,
|
|
37
38
|
closeSync,
|
|
38
39
|
constants,
|
|
39
40
|
existsSync,
|
|
@@ -169,38 +170,6 @@ function outOfFdsAtOpen(dbFile: string, err: unknown): UserError | undefined {
|
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
/** Say once, on the channel the opener has, that an upgrade emptied the index. */
|
|
173
|
-
export function noteSchemaReset(cache: Cache, warn: (message: string) => void): void {
|
|
174
|
-
if (cache.storeFallback !== null) {
|
|
175
|
-
warn(
|
|
176
|
-
`[vx] shared cache store ${cache.storeFallback} is not usable; entries stay in ${cache.storeDir ?? 'this workspace'}, where no other workspace hits them`,
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
if (cache.storeMoved !== null) {
|
|
180
|
-
const { from, to } = cache.storeMoved
|
|
181
|
-
warn(
|
|
182
|
-
`[vx] cache entries now live in the shared store ${to}; the ones in ${from} miss once, and \`vx cache prune\` reclaims their artifacts`,
|
|
183
|
-
)
|
|
184
|
-
}
|
|
185
|
-
if (cache.storeReset !== null) {
|
|
186
|
-
const { from, to } = cache.storeReset
|
|
187
|
-
warn(
|
|
188
|
-
`[vx] shared cache store ${cache.storeDir} re-indexed: schema ${from} → ${to} (vx version changed); its artifacts stay and are indexed again as tasks hit them`,
|
|
189
|
-
)
|
|
190
|
-
}
|
|
191
|
-
if (cache.formatChange !== null) {
|
|
192
|
-
const { from, to } = cache.formatChange
|
|
193
|
-
warn(
|
|
194
|
-
`[vx] cache format changed: ${from} → ${to} (vx version changed); every cached task misses once and re-saves, and the old entries, never read again, age out under \`vx cache prune --older-than\` or \`cacheRetention\``,
|
|
195
|
-
)
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
|
-
if (cache.schemaReset === null) return
|
|
199
|
-
const { from, to } = cache.schemaReset
|
|
200
|
-
warn(
|
|
201
|
-
`[vx] cache index reset: schema ${from} → ${to} (vx version changed); run history starts over, and each artifact is indexed again when its task next hits`,
|
|
202
|
-
)
|
|
203
|
-
}
|
|
204
173
|
// SCHEMA history (drop+recreate on mismatch; pre-alpha, no migrations):
|
|
205
174
|
// v20: file_hashes.content_hash (git blob OIDs).
|
|
206
175
|
// v21: dropped the unused outputs_hash column (pure-input hashing).
|
|
@@ -459,7 +428,16 @@ function ensurePrivateDir(dir: string): string | null {
|
|
|
459
428
|
if (!st.isDirectory()) return `${dir} is not a directory`
|
|
460
429
|
const uid = process.getuid?.()
|
|
461
430
|
if (uid !== undefined && st.uid !== uid) return `${dir} belongs to another user`
|
|
462
|
-
|
|
431
|
+
// The user's own directory open to others (a `~/.vx` made at the
|
|
432
|
+
// default umask, 0755) is vx's to close: refusing it left the store
|
|
433
|
+
// unused on a stock macOS home (owner, 2026-10-06: "you should own it").
|
|
434
|
+
if ((st.mode & 0o077) !== 0) {
|
|
435
|
+
try {
|
|
436
|
+
chmodSync(dir, 0o700)
|
|
437
|
+
} catch (err) {
|
|
438
|
+
return `${dir} is open to other users and chmod 700 failed (${errorText(err)})`
|
|
439
|
+
}
|
|
440
|
+
}
|
|
463
441
|
return null
|
|
464
442
|
}
|
|
465
443
|
|
|
@@ -550,8 +528,7 @@ export class Cache implements CacheLayer {
|
|
|
550
528
|
|
|
551
529
|
/**
|
|
552
530
|
* The store this open was asked for and could not use, with why: the
|
|
553
|
-
* entries went to a store inside `cacheDir` instead.
|
|
554
|
-
* `noteSchemaReset`.
|
|
531
|
+
* entries went to a store inside `cacheDir` instead.
|
|
555
532
|
*/
|
|
556
533
|
readonly storeFallback: string | null = null
|
|
557
534
|
|
|
@@ -561,24 +538,22 @@ export class Cache implements CacheLayer {
|
|
|
561
538
|
/**
|
|
562
539
|
* Set when THIS open found an index written by another `SCHEMA_VERSION`
|
|
563
540
|
* and dropped every table. The next open sees the current version and
|
|
564
|
-
* reports null
|
|
565
|
-
*
|
|
566
|
-
* channel. Without it an upgrade empties the cache and the history in
|
|
567
|
-
* silence, and the all-miss run that follows looks like a bug.
|
|
541
|
+
* reports null. vx prints nothing for it: the cache is vx's to keep
|
|
542
|
+
* (owner, 2026-10-06).
|
|
568
543
|
*/
|
|
569
544
|
readonly schemaReset: SchemaReset | null = null
|
|
570
545
|
|
|
571
546
|
/**
|
|
572
547
|
* Set when THIS open found entries written under another
|
|
573
548
|
* `CACHE_VERSION`: the index survives, but no old key is derived again,
|
|
574
|
-
* so every cached task misses once.
|
|
549
|
+
* so every cached task misses once. Not printed, like `schemaReset`.
|
|
575
550
|
*/
|
|
576
551
|
readonly formatChange: SchemaReset | null = null
|
|
577
552
|
|
|
578
553
|
/**
|
|
579
554
|
* Set when THIS open found the shared store written under another
|
|
580
555
|
* `SCHEMA_VERSION` and dropped its inventory; the artifacts stay and are
|
|
581
|
-
* indexed again as they hit.
|
|
556
|
+
* indexed again as they hit. Never printed: the cache is vx's to keep.
|
|
582
557
|
*/
|
|
583
558
|
readonly storeReset: SchemaReset | null = null
|
|
584
559
|
|
package/src/cache/index.ts
CHANGED
package/src/cli/cache.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
|
-
import { Cache
|
|
2
|
+
import { Cache } from '../cache/index.js'
|
|
3
3
|
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'
|
|
@@ -189,7 +189,6 @@ async function pruneCmd(args: readonly string[]): Promise<number> {
|
|
|
189
189
|
// stack (an unprivileged user on a root-owned `.vx`, 2026-09-16). A dry
|
|
190
190
|
// run only reads, and reads a read-only cache fine.
|
|
191
191
|
if (!parsed.dryRun) cache.assertWritable()
|
|
192
|
-
noteSchemaReset(cache, warnToStderr)
|
|
193
192
|
// A prune beside a run on this workspace evicts what the run has just
|
|
194
193
|
// probed as hits, whose `accessed_at` bumps it has not flushed yet; the
|
|
195
194
|
// run then re-runs those tasks (upstream survey, nx#36688). So a prune
|
package/src/cli/last.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Read-only — no config evaluation, no re-hash, no cache probe.
|
|
6
6
|
|
|
7
7
|
import type { Database } from 'bun:sqlite'
|
|
8
|
-
import { Cache
|
|
8
|
+
import { Cache } from '../cache/index.js'
|
|
9
9
|
import { formatBytes } from './format.js'
|
|
10
10
|
import { flagHint, seeHelp } from './help.js'
|
|
11
11
|
import {
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from '../orchestrator/index.js'
|
|
22
22
|
import { formatElapsed, UserError } from '../util/index.js'
|
|
23
23
|
import { findWorkspaceRoot } from '../workspace/index.js'
|
|
24
|
-
import { cliCacheDir, parseCacheDirFlag
|
|
24
|
+
import { cliCacheDir, parseCacheDirFlag } from './workspace-config.js'
|
|
25
25
|
|
|
26
26
|
interface LastArgs {
|
|
27
27
|
runId?: string
|
|
@@ -232,7 +232,6 @@ export async function lastCmd(args: readonly string[]): Promise<number> {
|
|
|
232
232
|
|
|
233
233
|
const root = await findWorkspaceRoot(process.cwd())
|
|
234
234
|
const cache = Cache.inspect(await cliCacheDir(root, parsed.cacheDir))
|
|
235
|
-
noteSchemaReset(cache, warnToStderr)
|
|
236
235
|
try {
|
|
237
236
|
const db = cache.dbHandle()
|
|
238
237
|
// Nothing recorded at all is its own answer: `--failed` said "no
|
package/src/cli/why.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// no config evaluation, no re-hash.
|
|
7
7
|
|
|
8
8
|
import path from 'node:path'
|
|
9
|
-
import { Cache
|
|
9
|
+
import { Cache } from '../cache/index.js'
|
|
10
10
|
import { flagHint, seeHelp } from './help.js'
|
|
11
11
|
import { splitTaskId } from '../graph/index.js'
|
|
12
12
|
import {
|
|
@@ -18,7 +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 { cliCacheDir, parseCacheDirFlag
|
|
21
|
+
import { cliCacheDir, parseCacheDirFlag } from './workspace-config.js'
|
|
22
22
|
|
|
23
23
|
interface WhyArgs {
|
|
24
24
|
target?: string
|
|
@@ -185,7 +185,6 @@ export async function whyCmd(args: readonly string[]): Promise<number> {
|
|
|
185
185
|
|
|
186
186
|
const root = await findWorkspaceRoot(process.cwd())
|
|
187
187
|
const cache = Cache.inspect(await cliCacheDir(root, parsed.cacheDir))
|
|
188
|
-
noteSchemaReset(cache, warnToStderr)
|
|
189
188
|
try {
|
|
190
189
|
const db = cache.dbHandle()
|
|
191
190
|
const taskId = resolveTarget(cache, await rootSpelled(root, parsed.target))
|
|
@@ -8,7 +8,7 @@ import { existsSync } from 'node:fs'
|
|
|
8
8
|
import path from 'node:path'
|
|
9
9
|
import type { WorkspaceConfig } from '../config.js'
|
|
10
10
|
import { UserError } from '../util/index.js'
|
|
11
|
-
import { Cache
|
|
11
|
+
import { Cache } from '../cache/index.js'
|
|
12
12
|
import { discoverProjects, loadProjects, loadWorkspacePlugins } from '../orchestrator/index.js'
|
|
13
13
|
import type { VxPlugin } from '../orchestrator/index.js'
|
|
14
14
|
import {
|
|
@@ -147,7 +147,6 @@ export async function loadCliProjects(
|
|
|
147
147
|
'open',
|
|
148
148
|
opts.cacheDir === undefined ? ws.storeRoot : null,
|
|
149
149
|
)
|
|
150
|
-
if (cache !== null) noteSchemaReset(cache, warnToStderr)
|
|
151
150
|
try {
|
|
152
151
|
const loaded = await loadProjects({
|
|
153
152
|
workspaceRoot,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import os from 'node:os'
|
|
9
9
|
import path from 'node:path'
|
|
10
|
-
import { Cache, CACHE_VERSION,
|
|
10
|
+
import { Cache, CACHE_VERSION, SCHEMA_VERSION } from '../cache/index.js'
|
|
11
11
|
import { PLUGIN_HOOKS } from '../config.js'
|
|
12
12
|
import {
|
|
13
13
|
cgroupCpuQuota,
|
|
@@ -154,7 +154,6 @@ async function collectWorkspaceInfo(
|
|
|
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
156
|
const cache = Cache.inspect(cacheDir)
|
|
157
|
-
noteSchemaReset(cache, warn)
|
|
158
157
|
let stats
|
|
159
158
|
let orphans
|
|
160
159
|
let flaky: FlakyTask[]
|
|
@@ -12,7 +12,6 @@ import type { WorkspaceConfig } from '../config.js'
|
|
|
12
12
|
import { beginRun, mark, nearest, UserError } from '../util/index.js'
|
|
13
13
|
import {
|
|
14
14
|
Cache,
|
|
15
|
-
noteSchemaReset,
|
|
16
15
|
type CacheLayer,
|
|
17
16
|
type CachePolicy,
|
|
18
17
|
FULL_CACHE_POLICY,
|
|
@@ -287,7 +286,6 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
287
286
|
storeRoot,
|
|
288
287
|
)
|
|
289
288
|
localCache.assertWritable()
|
|
290
|
-
noteSchemaReset(localCache, (m) => log.status(m))
|
|
291
289
|
// Two digests from one read: the config-evaluation cache keys on every
|
|
292
290
|
// file (a config may import a dependency), the task keys on the files no
|
|
293
291
|
// plugin claims (`VxPlugin.fingerprint`).
|
|
@@ -289,7 +289,7 @@ export async function applyMigration(args: ApplyMigrationArgs): Promise<number>
|
|
|
289
289
|
files.push({
|
|
290
290
|
relPath: relPosix(root, abs),
|
|
291
291
|
abs,
|
|
292
|
-
contents: renderConfigFile(
|
|
292
|
+
contents: renderConfigFile(p, format),
|
|
293
293
|
})
|
|
294
294
|
}
|
|
295
295
|
for (const f of plan.extraFiles) {
|
|
@@ -302,7 +302,8 @@ export async function applyMigration(args: ApplyMigrationArgs): Promise<number>
|
|
|
302
302
|
const hasWorkspaceFile = (
|
|
303
303
|
await Promise.all(WORKSPACE_CONFIG_FILENAMES.map((n) => Bun.file(path.join(root, n)).exists()))
|
|
304
304
|
).some(Boolean)
|
|
305
|
-
|
|
305
|
+
// A plan may bring its own (vx-migrate declares the plugins it detects).
|
|
306
|
+
if (!hasWorkspaceFile && !plan.extraFiles.some((f) => f.relPath === workspaceName)) {
|
|
306
307
|
const abs = path.join(root, workspaceName)
|
|
307
308
|
files.push({ relPath: relPosix(root, abs), abs, contents: workspaceFile(format) })
|
|
308
309
|
}
|
|
@@ -550,11 +551,8 @@ function exampleConfig(format: MigrationFormat): string {
|
|
|
550
551
|
// `import { defineWorkspace } from '@vzn/vx'` loads a SECOND copy of core
|
|
551
552
|
// into every run — measured 2026-09-09 at ~17 ms on a two-package
|
|
552
553
|
// workspace, a fifth of the whole run — for an identity function.
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
// complete workspace. Add a remote cache, a remote executor or telemetry
|
|
556
|
-
// as one entry each — those imports are the runtime ones.
|
|
557
|
-
export default { plugins: [] }`
|
|
554
|
+
// No explanatory comment: the file is the user's (owner, 2026-10-06).
|
|
555
|
+
const WORKSPACE_BODY = `export default { plugins: [] }`
|
|
558
556
|
|
|
559
557
|
function workspaceFile(format: MigrationFormat): string {
|
|
560
558
|
return format === 'ts'
|
|
@@ -586,22 +584,18 @@ function renderValue(v: unknown, indent: string): string {
|
|
|
586
584
|
return `{\n${body.join('\n')}\n${indent}}`
|
|
587
585
|
}
|
|
588
586
|
|
|
589
|
-
function renderConfigFile(
|
|
590
|
-
source: string,
|
|
591
|
-
p: GeneratedProject,
|
|
592
|
-
verb: string,
|
|
593
|
-
format: MigrationFormat,
|
|
594
|
-
): string {
|
|
587
|
+
function renderConfigFile(p: GeneratedProject, format: MigrationFormat): string {
|
|
595
588
|
// A type-only import: the editor type-checks against the installed
|
|
596
589
|
// package, and Bun erases it, so the config loads in a workspace that
|
|
597
590
|
// runs the vx binary without the package installed. `mjs` is the same
|
|
598
591
|
// object with nothing to erase.
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
]
|
|
592
|
+
// No "generated by" header: the file is the user's from here (owner,
|
|
593
|
+
// 2026-10-06), and each TODO(vx-migrate) sits on its own task.
|
|
594
|
+
const lines: string[] = []
|
|
602
595
|
if (format === 'ts') lines.push("import type { ProjectConfig } from '@vzn/vx/config'")
|
|
603
596
|
if (p.importLines.length > 0) lines.push(...p.importLines)
|
|
604
|
-
lines.push(''
|
|
597
|
+
if (lines.length > 0) lines.push('')
|
|
598
|
+
lines.push('export default {')
|
|
605
599
|
if (p.tags !== undefined && p.tags.length > 0) lines.push(` tags: ${renderValue(p.tags, ' ')},`)
|
|
606
600
|
lines.push(' tasks: {')
|
|
607
601
|
for (const t of p.tasks) {
|