@vzn/vx 0.0.652 → 0.0.653
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 -2
- package/launcher.sh +2 -1
- package/package.json +5 -5
- package/schemas/error.json +1 -1
- package/schemas/show.json +1 -0
- package/skills/vx/SKILL.md +3 -3
- package/src/bin.ts +25 -3
- package/src/cache/archive.ts +196 -31
- package/src/cache/cache.ts +863 -225
- package/src/cache/checkpointer.ts +102 -0
- package/src/cache/file-hashes.ts +2 -1
- package/src/cache/fold-memo.ts +24 -4
- package/src/cache/fs-clock.ts +26 -0
- package/src/cache/git-inputs.ts +188 -22
- package/src/cache/history-stream.ts +22 -5
- package/src/cache/index.ts +10 -1
- package/src/cache/inputs.ts +573 -152
- package/src/cache/key-fold.ts +1 -1
- package/src/cache/layer.ts +23 -10
- package/src/cache/layered-cache.ts +28 -7
- package/src/cache/output-index.ts +106 -44
- package/src/cache/plugin-cache.ts +2 -2
- package/src/cache/policy.ts +6 -2
- package/src/cache/remote-lane.ts +3 -1
- package/src/cache/run-history.ts +50 -10
- package/src/cache/schema.ts +42 -9
- package/src/cli/cache.ts +147 -34
- package/src/cli/completions.ts +2 -2
- package/src/cli/core-alias.ts +9 -2
- package/src/cli/docs.ts +4 -2
- package/src/cli/help.ts +25 -2
- package/src/cli/index.ts +38 -7
- package/src/cli/info.ts +4 -3
- package/src/cli/init.ts +55 -20
- package/src/cli/last.ts +184 -41
- package/src/cli/lock.ts +187 -24
- package/src/cli/plan-format.ts +24 -21
- package/src/cli/plugin-commands.ts +6 -1
- package/src/cli/plugin-templates.ts +1 -1
- package/src/cli/run.ts +68 -34
- package/src/cli/select.ts +160 -64
- package/src/cli/show.ts +63 -18
- package/src/cli/task-verb.ts +12 -7
- package/src/cli/upgrade.ts +210 -76
- package/src/cli/watch-filter.ts +88 -15
- package/src/cli/watch-fs.ts +34 -47
- package/src/cli/watch-judge.ts +72 -8
- package/src/cli/watch-set.ts +6 -4
- package/src/cli/watch.ts +179 -25
- package/src/cli/why.ts +107 -33
- package/src/cli/workspace-config.ts +11 -1
- package/src/config.ts +9 -0
- package/src/exec/env.ts +3 -1
- package/src/exec/executor.ts +76 -15
- package/src/exec/kill-tree.ts +7 -7
- package/src/exec/runner.ts +270 -108
- package/src/exec/sandbox-binds.ts +109 -18
- package/src/exec/sandbox-runtime.ts +134 -37
- package/src/exec/sandbox-violations.ts +250 -46
- package/src/exec/spawn-off.ts +10 -5
- package/src/exec/warm-shell.ts +29 -7
- package/src/graph/folds.ts +89 -16
- package/src/graph/index.ts +8 -0
- package/src/graph/priorities.ts +8 -5
- package/src/graph/scheduler.ts +173 -38
- package/src/graph/stand-ins.ts +36 -0
- package/src/graph/task-graph.ts +327 -141
- package/src/graph/task-status.ts +5 -1
- package/src/handoff.ts +45 -10
- package/src/history/failure-mode.ts +15 -5
- package/src/history/history.ts +103 -30
- package/src/history/index.ts +1 -0
- package/src/history/metrics.ts +189 -69
- package/src/index.ts +2 -1
- package/src/orchestrator/admission.ts +13 -9
- package/src/orchestrator/affected-tasks.ts +35 -13
- package/src/orchestrator/command-windows.ts +8 -5
- package/src/orchestrator/deferred-outputs.ts +41 -12
- package/src/orchestrator/doctor.ts +21 -9
- package/src/orchestrator/download-policy.ts +3 -3
- package/src/orchestrator/excluded-keys.ts +14 -2
- package/src/orchestrator/execute-args.ts +9 -2
- package/src/orchestrator/execute-task.ts +564 -123
- package/src/orchestrator/graph-memo.ts +26 -4
- package/src/orchestrator/graph-stage.ts +27 -4
- package/src/orchestrator/held-servers.ts +36 -14
- package/src/orchestrator/hit-restore.ts +136 -87
- package/src/orchestrator/index.ts +4 -1
- package/src/orchestrator/keyed-projects.ts +25 -0
- package/src/orchestrator/local-floor.ts +21 -3
- package/src/orchestrator/lockfile-claim.ts +30 -4
- package/src/orchestrator/miss-save.ts +33 -53
- package/src/orchestrator/options.ts +13 -0
- package/src/orchestrator/output-host.ts +16 -4
- package/src/orchestrator/output-log.ts +1 -54
- package/src/orchestrator/persistent-task.ts +31 -17
- package/src/orchestrator/persistent.ts +152 -15
- package/src/orchestrator/placement.ts +81 -15
- package/src/orchestrator/plan.ts +37 -15
- package/src/orchestrator/plugin-host.ts +232 -108
- package/src/orchestrator/plugin.ts +53 -27
- package/src/orchestrator/prepare.ts +194 -38
- package/src/orchestrator/projects.ts +89 -10
- package/src/orchestrator/read-only.ts +115 -0
- package/src/orchestrator/run-context.ts +6 -2
- package/src/orchestrator/run-failures.ts +43 -14
- package/src/orchestrator/run-id.ts +13 -2
- package/src/orchestrator/run-keys.ts +87 -28
- package/src/orchestrator/run-live.ts +16 -13
- package/src/orchestrator/run-lock.ts +27 -4
- package/src/orchestrator/run-records.ts +12 -10
- package/src/orchestrator/run-report.ts +2 -1
- package/src/orchestrator/run-stop.ts +17 -9
- package/src/orchestrator/run.ts +443 -107
- package/src/orchestrator/sandbox-fix.ts +40 -12
- package/src/orchestrator/sandbox-request.ts +286 -59
- package/src/orchestrator/save-lane.ts +24 -13
- package/src/orchestrator/shell-verdict.ts +20 -1
- package/src/orchestrator/signals.ts +103 -8
- package/src/orchestrator/stable-keys.ts +17 -9
- package/src/orchestrator/task-artifacts.ts +15 -2
- package/src/orchestrator/task-hash.ts +187 -30
- package/src/orchestrator/telemetry-host.ts +25 -6
- package/src/orchestrator/telemetry.ts +80 -12
- package/src/orchestrator/upstream.ts +3 -1
- package/src/report/events.ts +20 -3
- package/src/report/forecast.ts +103 -37
- package/src/report/framed-output.ts +165 -157
- package/src/report/index.ts +2 -1
- package/src/report/logger.ts +107 -30
- package/src/report/plain-output.ts +13 -9
- package/src/report/status-line.ts +28 -55
- package/src/report/summary.ts +57 -14
- package/src/settings.ts +42 -6
- package/src/ui/components.ts +36 -42
- package/src/ui/index.ts +3 -1
- package/src/ui/live-region.ts +67 -8
- package/src/ui/terminal.ts +12 -0
- package/src/ui/theme.ts +16 -1
- package/src/util/config-frame.ts +17 -3
- package/src/util/edit-distance.ts +17 -1
- package/src/util/errors.ts +23 -7
- package/src/util/foreign-cache-dir.ts +60 -0
- package/src/util/git-ignored.ts +59 -5
- package/src/util/index.ts +15 -1
- package/src/util/paths.ts +50 -6
- package/src/util/plain-text.ts +19 -0
- package/src/util/secret-mask.ts +57 -1
- package/src/util/settle.ts +16 -0
- package/src/util/size.ts +12 -2
- package/src/util/write-file.ts +86 -9
- package/src/workspace/affected.ts +225 -27
- package/src/workspace/config-bytecode.ts +23 -4
- package/src/workspace/config-eval.ts +90 -16
- package/src/workspace/config-imports.ts +96 -41
- package/src/workspace/config-schema.ts +126 -25
- package/src/workspace/dependencies-lock.ts +7 -14
- package/src/workspace/filter.ts +10 -1
- package/src/workspace/index.ts +3 -1
- package/src/workspace/json-data.ts +34 -8
- package/src/workspace/lockfile.ts +86 -37
- package/src/workspace/migration.ts +46 -25
- package/src/workspace/module-state.ts +62 -0
- package/src/workspace/nested-dirs.ts +8 -0
- package/src/workspace/project-loader.ts +276 -32
- package/src/workspace/repo-id.ts +29 -6
- package/src/workspace/workspace.ts +190 -30
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
// whichever tool wrote it. Core knows no source format here: a mapper returns a
|
|
6
6
|
// `MigrationPlan` and this file does the rest.
|
|
7
7
|
|
|
8
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
8
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs'
|
|
9
9
|
import { unlink } from 'node:fs/promises'
|
|
10
10
|
import path from 'node:path'
|
|
11
11
|
import { workspaceFileWritten } from '../handoff.js'
|
|
12
12
|
import {
|
|
13
|
+
checkIgnoreSync,
|
|
13
14
|
DOT_VX_GITIGNORE,
|
|
14
15
|
executablePath,
|
|
15
16
|
gitEnv,
|
|
@@ -22,6 +23,31 @@ import * as ui from '../ui/index.js'
|
|
|
22
23
|
import { VERSION } from '../version.js'
|
|
23
24
|
import { WORKSPACE_CONFIG_FILENAMES } from './project-loader.js'
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* The nearest existing directory at or above `dir` (under `root`) and its
|
|
28
|
+
* real path when that path is outside `realRoot`; none when it is inside.
|
|
29
|
+
*/
|
|
30
|
+
function linkOut(
|
|
31
|
+
root: string,
|
|
32
|
+
realRoot: string,
|
|
33
|
+
dir: string,
|
|
34
|
+
): [link: string | undefined, real: string] {
|
|
35
|
+
for (let at = dir; ; at = path.dirname(at)) {
|
|
36
|
+
let real: string
|
|
37
|
+
try {
|
|
38
|
+
real = realpathSync(at)
|
|
39
|
+
} catch {
|
|
40
|
+
// quiet: not made yet; mkdir makes it a real directory under its parent
|
|
41
|
+
if (at === root) return [undefined, realRoot]
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
const rel = path.relative(realRoot, real)
|
|
45
|
+
return rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)
|
|
46
|
+
? [at, real]
|
|
47
|
+
: [undefined, real]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
25
51
|
/** The root config `vx init` writes outside a Turbo or Nx repo: no task yet. */
|
|
26
52
|
export function starterProject(format: MigrationFormat): string {
|
|
27
53
|
return format === 'ts'
|
|
@@ -246,8 +272,9 @@ export interface ApplyMigrationArgs {
|
|
|
246
272
|
* the migrated workspace has its .vx/projects.lock.json and its graph rules checked
|
|
247
273
|
* (2026-10-14). On success `said` is its line, shown as the
|
|
248
274
|
* report's `lock` row; a refusal (a graph rule, a plugin not installed
|
|
249
|
-
* yet) prints above the report
|
|
250
|
-
*
|
|
275
|
+
* yet) prints above the report, the files stay written, `vx lock` is the
|
|
276
|
+
* step to re-run, and the exit is 1: a run reads only the lock, so the
|
|
277
|
+
* migration is not done (as `vx init`'s own path exits).
|
|
251
278
|
*/
|
|
252
279
|
lock?: (root: string) => Promise<{ code: number; said: string }>
|
|
253
280
|
/** Rows of what the tool did before the write, e.g. `['ran', 'bun add -d …']`. */
|
|
@@ -352,11 +379,23 @@ export async function applyMigration(args: ApplyMigrationArgs): Promise<number>
|
|
|
352
379
|
|
|
353
380
|
// A plan is a mapper's reading of the repo's own files (an Nx node's
|
|
354
381
|
// `root`, a workspace glob): one that names `../x` wrote outside it.
|
|
382
|
+
// A member directory linked out of the root is spelled inside it, so the
|
|
383
|
+
// real directory is asked too: core's discovery never loads a config
|
|
384
|
+
// there. `.vx` is not a project: its files are refused outside the
|
|
385
|
+
// root at the write (`refuseSymlink`).
|
|
386
|
+
const realRoot = realpathSync(root)
|
|
355
387
|
for (const f of files) {
|
|
356
388
|
const rel = relPosix(root, f.abs)
|
|
357
389
|
if (rel === '..' || rel.startsWith('../') || path.isAbsolute(rel)) {
|
|
358
390
|
throw new UserError(`${f.relPath}: outside the workspace root`)
|
|
359
391
|
}
|
|
392
|
+
if (rel.startsWith('.vx/')) continue
|
|
393
|
+
const [link, real] = linkOut(root, realRoot, path.dirname(f.abs))
|
|
394
|
+
if (link !== undefined) {
|
|
395
|
+
throw new UserError(
|
|
396
|
+
`${f.relPath}: outside the workspace root (${relPosix(root, link)} resolves to ${real}); vx loads no project there`,
|
|
397
|
+
)
|
|
398
|
+
}
|
|
360
399
|
}
|
|
361
400
|
|
|
362
401
|
if (!dry && !force) {
|
|
@@ -396,8 +435,8 @@ export async function applyMigration(args: ApplyMigrationArgs): Promise<number>
|
|
|
396
435
|
ui.print('out', ui.excerpt(f.relPath, f.contents))
|
|
397
436
|
}
|
|
398
437
|
} else if (!dry) {
|
|
399
|
-
for (const f of files) refuseSymlink(f.abs)
|
|
400
|
-
for (const f of files) await writeNoFollow(f.abs, f.contents)
|
|
438
|
+
for (const f of files) refuseSymlink(f.abs, root)
|
|
439
|
+
for (const f of files) await writeNoFollow(f.abs, f.contents, root)
|
|
401
440
|
for (const f of replaced) await unlink(f)
|
|
402
441
|
// The lock that follows loads its plugins from the file just written.
|
|
403
442
|
workspaceFileWritten()
|
|
@@ -538,7 +577,7 @@ export async function applyMigration(args: ApplyMigrationArgs): Promise<number>
|
|
|
538
577
|
lock === undefined || lock.code === 0 ? next : `fix the above, then vx lock and ${next}`
|
|
539
578
|
rows.push(null, ['next', said])
|
|
540
579
|
ui.print('out', ui.report(`${verb} ${VERSION}`, rows))
|
|
541
|
-
return 0
|
|
580
|
+
return lock === undefined || lock.code === 0 ? 0 : 1
|
|
542
581
|
}
|
|
543
582
|
|
|
544
583
|
/**
|
|
@@ -590,25 +629,7 @@ function ignoreAdvice(root: string, ignored: readonly string[], dry: boolean): s
|
|
|
590
629
|
|
|
591
630
|
/** The paths (root-relative) git's ignore rules drop; none outside a work tree. */
|
|
592
631
|
function gitIgnored(root: string, relPaths: readonly string[]): string[] {
|
|
593
|
-
|
|
594
|
-
try {
|
|
595
|
-
const r = Bun.spawnSync({
|
|
596
|
-
cmd: [executablePath('git'), 'check-ignore', '--stdin', '-z'],
|
|
597
|
-
env: gitEnv(),
|
|
598
|
-
cwd: root,
|
|
599
|
-
stdin: Buffer.from(relPaths.map((p) => `${p}\0`).join('')),
|
|
600
|
-
stdout: 'pipe',
|
|
601
|
-
stderr: 'ignore',
|
|
602
|
-
})
|
|
603
|
-
// 0: some ignored; 1: none; anything else (no work tree): nothing to say.
|
|
604
|
-
if (r.exitCode !== 0) return []
|
|
605
|
-
return r.stdout
|
|
606
|
-
.toString()
|
|
607
|
-
.split('\0')
|
|
608
|
-
.filter((p) => p !== '')
|
|
609
|
-
} catch {
|
|
610
|
-
return []
|
|
611
|
-
}
|
|
632
|
+
return [...(checkIgnoreSync(root, relPaths) ?? [])]
|
|
612
633
|
}
|
|
613
634
|
|
|
614
635
|
function inGitWorkTree(root: string): boolean {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// What a module's exports hold, to tell whether a config changed a module
|
|
2
|
+
// other configs import. Both functions are self-contained: the config
|
|
3
|
+
// worker embeds them by their source (config-eval.ts), as it does
|
|
4
|
+
// `nonJsonPaths`, and a minified build renames only what each one holds.
|
|
5
|
+
|
|
6
|
+
/** One entry per export: every value reachable from it, in walk order, compared by identity. */
|
|
7
|
+
export type ExportState = Map<PropertyKey, unknown[]>
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Every value each export of `ns` reaches through own properties and Map
|
|
11
|
+
* and Set entries, without calling a getter. Functions and typed arrays
|
|
12
|
+
* are compared by identity only. State a module keeps unexported, behind a
|
|
13
|
+
* function, is not reached.
|
|
14
|
+
*/
|
|
15
|
+
export function exportState(ns: object): ExportState {
|
|
16
|
+
const ownKeys = Reflect.ownKeys
|
|
17
|
+
const describe = Object.getOwnPropertyDescriptor
|
|
18
|
+
const protoOf = Object.getPrototypeOf
|
|
19
|
+
const state: ExportState = new Map()
|
|
20
|
+
for (const name of ownKeys(ns)) {
|
|
21
|
+
const out: unknown[] = []
|
|
22
|
+
const seen = new Set<object>()
|
|
23
|
+
const walk = (v: unknown): void => {
|
|
24
|
+
out.push(v)
|
|
25
|
+
if (v === null || typeof v !== 'object' || seen.has(v) || ArrayBuffer.isView(v)) return
|
|
26
|
+
seen.add(v)
|
|
27
|
+
out.push(protoOf(v))
|
|
28
|
+
if (v instanceof Map)
|
|
29
|
+
for (const [k, x] of v) {
|
|
30
|
+
out.push(k)
|
|
31
|
+
walk(x)
|
|
32
|
+
}
|
|
33
|
+
else if (v instanceof Set) for (const x of v) walk(x)
|
|
34
|
+
for (const k of ownKeys(v)) {
|
|
35
|
+
const d = describe(v, k)!
|
|
36
|
+
out.push(k, d.get, d.set, d.writable)
|
|
37
|
+
if ('value' in d) walk(d.value)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
walk((ns as Record<PropertyKey, unknown>)[name])
|
|
42
|
+
} catch {
|
|
43
|
+
// A binding not yet initialized, or a Proxy's trap that throws.
|
|
44
|
+
out.push('\0threw')
|
|
45
|
+
}
|
|
46
|
+
state.set(name, out)
|
|
47
|
+
}
|
|
48
|
+
return state
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The exports whose state differs between `before` and `after`. */
|
|
52
|
+
export function changedExports(before: ExportState, after: ExportState): string[] {
|
|
53
|
+
const out: string[] = []
|
|
54
|
+
for (const name of new Set([...before.keys(), ...after.keys()])) {
|
|
55
|
+
const a = before.get(name)
|
|
56
|
+
const b = after.get(name)
|
|
57
|
+
let same = a !== undefined && b !== undefined && a.length === b.length
|
|
58
|
+
for (let i = 0; same && i < a!.length; i++) same = Object.is(a![i], b![i])
|
|
59
|
+
if (!same) out.push(String(name))
|
|
60
|
+
}
|
|
61
|
+
return out
|
|
62
|
+
}
|
|
@@ -19,9 +19,16 @@ import type { ProjectEntry } from './workspace.js'
|
|
|
19
19
|
* once we leave the `p.dir` block entirely. The inner loop is still bounded by
|
|
20
20
|
* that block (near-O(P log P) on real trees; interlopers are few), not the
|
|
21
21
|
* full tail.
|
|
22
|
+
*
|
|
23
|
+
* `workspaces`: the absolute roots of nested workspaces (each holds its own
|
|
24
|
+
* `.vx/workspace.*`), a boundary for outputs: each joins the dirs of every
|
|
25
|
+
* project it lies under. An outer output glob that ran into one let the
|
|
26
|
+
* clean before a run delete its files. Never passed for inputs: a task may
|
|
27
|
+
* read them, and an input left out of the key is a stale hit.
|
|
22
28
|
*/
|
|
23
29
|
export function computeNestedProjectDirs(
|
|
24
30
|
entries: Array<Pick<ProjectEntry, 'name' | 'dir'>>,
|
|
31
|
+
workspaces: readonly string[] = [],
|
|
25
32
|
): Map<string, string[]> {
|
|
26
33
|
const result = new Map<string, string[]>()
|
|
27
34
|
if (entries.length === 0) return result
|
|
@@ -42,6 +49,7 @@ export function computeNestedProjectDirs(
|
|
|
42
49
|
// else: an interloping sibling (`foo-utils` for `foo`) that shares the
|
|
43
50
|
// string prefix but isn't nested — skip it, descendants may follow.
|
|
44
51
|
}
|
|
52
|
+
for (const w of workspaces) if (w.startsWith(prefix)) nested.push(w)
|
|
45
53
|
result.set(p.name, nested)
|
|
46
54
|
}
|
|
47
55
|
return result
|