@vzn/vx 0.0.644 → 0.0.646
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/LICENSE +1 -1
- package/README.md +22 -48
- package/package.json +5 -5
- package/schemas/init.json +5 -0
- package/schemas/last.json +15 -0
- package/skills/vx/SKILL.md +1 -1
- package/src/bin.ts +1 -1
- package/src/cache/archive.ts +12 -76
- package/src/cache/cache.ts +72 -42
- package/src/cache/git-inputs.ts +55 -10
- package/src/cache/index.ts +5 -2
- package/src/cache/inputs.ts +61 -11
- package/src/cache/layer.ts +30 -8
- package/src/cache/layered-cache.ts +138 -44
- package/src/cache/output-index.ts +25 -11
- package/src/cache/plugin-cache.ts +9 -14
- package/src/cache/remote-lane.ts +60 -22
- package/src/cache/run-history.ts +13 -6
- package/src/cache/schema.ts +19 -2
- package/src/cache/scoped-cache.ts +3 -80
- package/src/cli/core-alias.ts +5 -2
- package/src/cli/help.ts +8 -10
- package/src/cli/index.ts +0 -12
- package/src/cli/init.ts +159 -25
- package/src/cli/last.ts +4 -2
- package/src/cli/plan-format.ts +6 -2
- package/src/cli/plugin-commands.ts +98 -6
- package/src/cli/plugin-templates.ts +2 -2
- package/src/cli/run.ts +2 -8
- package/src/cli/select.ts +5 -3
- package/src/cli/upgrade.ts +5 -6
- package/src/config.ts +2 -4
- package/src/exec/executor.ts +112 -9
- package/src/exec/index.ts +2 -0
- package/src/exec/proc-sample.ts +67 -5
- package/src/exec/runner.ts +4 -0
- package/src/exec/sandbox-runtime.ts +11 -4
- package/src/graph/scheduler.ts +59 -8
- package/src/history/history.ts +119 -7
- package/src/history/metrics.ts +53 -16
- package/src/index.ts +16 -1
- package/src/orchestrator/affected-tasks.ts +0 -18
- package/src/orchestrator/execute-args.ts +3 -0
- package/src/orchestrator/execute-task.ts +28 -7
- package/src/orchestrator/held-servers.ts +86 -0
- package/src/orchestrator/hit-restore.ts +11 -4
- package/src/orchestrator/index.ts +18 -1
- package/src/orchestrator/local-floor.ts +104 -0
- package/src/orchestrator/local-shortcircuit.ts +26 -7
- package/src/orchestrator/miss-save.ts +10 -4
- package/src/orchestrator/options.ts +0 -8
- package/src/orchestrator/persistent-task.ts +14 -0
- package/src/orchestrator/placement.ts +84 -9
- package/src/orchestrator/plan.ts +7 -1
- package/src/orchestrator/plugin-host.ts +22 -7
- package/src/orchestrator/plugin.ts +48 -7
- package/src/orchestrator/prepare.ts +14 -49
- package/src/orchestrator/run-context.ts +39 -1
- package/src/orchestrator/run-keys.ts +164 -0
- package/src/orchestrator/run-records.ts +6 -0
- package/src/orchestrator/run.ts +95 -6
- package/src/orchestrator/sandbox-request.ts +9 -3
- package/src/orchestrator/task-artifacts.ts +142 -0
- package/src/orchestrator/task-hash.ts +12 -4
- package/src/orchestrator/task-process.ts +16 -0
- package/src/orchestrator/telemetry-host.ts +2 -1
- package/src/orchestrator/telemetry.ts +60 -17
- package/src/report/forecast.ts +6 -27
- package/src/report/framed-output.ts +41 -5
- package/src/report/logger.ts +60 -20
- package/src/report/status-line.ts +38 -18
- package/src/report/summary.ts +75 -9
- package/src/settings.ts +8 -7
- package/src/util/colors.ts +16 -1
- package/src/util/index.ts +1 -0
- package/src/util/paths.ts +30 -0
- package/src/util/ui.ts +4 -1
- package/src/workspace/affected.ts +0 -39
- package/src/workspace/config-bytecode.ts +276 -0
- package/src/workspace/index.ts +0 -2
- package/src/workspace/migration.ts +4 -32
- package/src/workspace/project-loader.ts +92 -14
- package/src/cli/foreign-flags.ts +0 -727
package/src/cli/init.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// `vx init [--dry [--format json]] [--force] [--mjs] [--native|--keep]` — a workspace from nowhere.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// what it writes (`migrate`);
|
|
5
|
-
//
|
|
2
|
+
// It installs vx globally and, where a package.json is, as its devDependency
|
|
3
|
+
// (M, 2026-10-20). A Turbo or Nx repo is `@vzn/vx-migrate`'s, which maps it
|
|
4
|
+
// and installs what it writes (`migrate`); any other gets the workspace file
|
|
5
|
+
// and an empty root project, then the lock (`starter`).
|
|
6
6
|
// `vx init --plugin <seam>` writes a runnable plugin for one seam and its
|
|
7
7
|
// test instead (plugin-templates.ts, the examples the gate runs).
|
|
8
8
|
|
|
@@ -13,13 +13,19 @@ import { gitTop, workspaceFileWritten } from '../handoff.js'
|
|
|
13
13
|
import { flagHint, formatValue, refuse, seeHelp } from './help.js'
|
|
14
14
|
import { lockAfterMigration } from './lock.js'
|
|
15
15
|
import { PLUGIN_TEMPLATES } from './plugin-templates.js'
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
isCompiledBinary,
|
|
18
|
+
projectManager,
|
|
19
|
+
startedVersion,
|
|
20
|
+
targetVersion,
|
|
21
|
+
upgradeArgv,
|
|
22
|
+
type ManagedInstall,
|
|
23
|
+
} from './upgrade.js'
|
|
17
24
|
import { VERSION } from '../version.js'
|
|
18
25
|
import { DOT_VX_GITATTRIBUTES, DOT_VX_GITIGNORE, ui, UserError, sayNotice } from '../util/index.js'
|
|
19
26
|
import {
|
|
20
27
|
findWorkspaceRoot,
|
|
21
28
|
PROJECT_CONFIG_FILENAMES,
|
|
22
|
-
vxInvocation,
|
|
23
29
|
WORKSPACE_CONFIG_FILENAMES,
|
|
24
30
|
} from '../workspace/index.js'
|
|
25
31
|
|
|
@@ -107,13 +113,104 @@ export async function initCmd(args: readonly string[]): Promise<number> {
|
|
|
107
113
|
dirs.push(d)
|
|
108
114
|
if (d === top || path.dirname(d) === d) break
|
|
109
115
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
if (dirs.some((d) => TOOL_CONFIGS.some((f) => existsSync(path.join(d, f))))) {
|
|
117
|
+
return migrate(dirs, parsed)
|
|
118
|
+
}
|
|
119
|
+
// The topmost package.json: a monorepo's root, wherever below it init ran.
|
|
120
|
+
const pkgRoot = dirs.findLast((d) => existsSync(path.join(d, 'package.json'))) ?? null
|
|
121
|
+
return starter(marked ?? pkgRoot ?? cwd, pkgRoot, top === null, parsed)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** What makes a repo vx-migrate's to map: a Turbo or Nx config. */
|
|
125
|
+
const TOOL_CONFIGS = ['turbo.json', 'turbo.jsonc', 'nx.json']
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The installs `vx init` runs (M, 2026-10-20): vx at `version` globally,
|
|
129
|
+
* with the manager that started init, so a bare `vx` is on PATH, unless
|
|
130
|
+
* the global vx already answers `version`; and, where `pkgRoot` has a
|
|
131
|
+
* package.json, as its devDependency with the repo's own manager.
|
|
132
|
+
*/
|
|
133
|
+
export function initInstalls(
|
|
134
|
+
version: string,
|
|
135
|
+
userAgent: string | undefined,
|
|
136
|
+
pkgRoot: string | null,
|
|
137
|
+
globalAt: string | undefined,
|
|
138
|
+
execPath = process.execPath,
|
|
139
|
+
plugins: readonly string[] = [],
|
|
140
|
+
): { argv: string[]; cwd: string | null }[] {
|
|
141
|
+
const out: { argv: string[]; cwd: string | null }[] = []
|
|
142
|
+
if (globalAt !== version) {
|
|
143
|
+
const runner = /^(npm|pnpm|yarn|bun)\//.exec(userAgent ?? '')?.[1] ?? 'bun'
|
|
144
|
+
const manager = runner as ManagedInstall['manager']
|
|
145
|
+
out.push({ argv: upgradeArgv({ manager, scope: 'global', cwd: '' }, version), cwd: null })
|
|
146
|
+
}
|
|
147
|
+
if (pkgRoot !== null) {
|
|
148
|
+
const install: ManagedInstall = {
|
|
149
|
+
manager: projectManager(pkgRoot, execPath),
|
|
150
|
+
scope: 'project',
|
|
151
|
+
cwd: pkgRoot,
|
|
152
|
+
field: 'devDependencies',
|
|
153
|
+
plugins: [...plugins],
|
|
154
|
+
}
|
|
155
|
+
out.push({ argv: upgradeArgv(install, version), cwd: pkgRoot })
|
|
156
|
+
}
|
|
157
|
+
return out
|
|
113
158
|
}
|
|
114
159
|
|
|
115
|
-
/**
|
|
116
|
-
|
|
160
|
+
/** The version the global `vx` on PATH answers: not a runner's cache, not a project's bin. */
|
|
161
|
+
function globalVersion(): string | undefined {
|
|
162
|
+
for (const dir of (process.env['PATH'] ?? '').split(path.delimiter)) {
|
|
163
|
+
const p = dir.split(path.sep).join('/')
|
|
164
|
+
if (p.includes('/_npx/') || /\/(bunx|dlx)-/.test(p) || p.includes('/node_modules/.bin'))
|
|
165
|
+
continue
|
|
166
|
+
const bin = path.join(dir, 'vx')
|
|
167
|
+
if (existsSync(bin)) return startedVersion(bin)?.split(' ')[1]
|
|
168
|
+
}
|
|
169
|
+
return undefined
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Run `initInstalls`, in order. A source checkout (0.0.0, which npm never
|
|
174
|
+
* had) installs nothing: the release would replace the code under test.
|
|
175
|
+
*/
|
|
176
|
+
async function install(
|
|
177
|
+
pkgRoot: string | null,
|
|
178
|
+
dry: boolean,
|
|
179
|
+
plugins: readonly string[] = [],
|
|
180
|
+
): Promise<string[]> {
|
|
181
|
+
if (VERSION === '0.0.0') return []
|
|
182
|
+
const version = await targetVersion(undefined, 'vx init')
|
|
183
|
+
const steps = initInstalls(
|
|
184
|
+
version,
|
|
185
|
+
process.env['npm_config_user_agent'],
|
|
186
|
+
pkgRoot,
|
|
187
|
+
globalVersion(),
|
|
188
|
+
process.execPath,
|
|
189
|
+
plugins,
|
|
190
|
+
)
|
|
191
|
+
if (dry) return steps.map((s) => s.argv.join(' '))
|
|
192
|
+
for (const s of steps) {
|
|
193
|
+
const bin = Bun.which(s.argv[0]!, { PATH: process.env['PATH'] ?? '' })
|
|
194
|
+
if (bin === null) {
|
|
195
|
+
throw new UserError(
|
|
196
|
+
`vx init: ${s.argv[0]} is not on PATH; run it yourself: ${s.argv.join(' ')}`,
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
ui.print(
|
|
200
|
+
'out',
|
|
201
|
+
ui.text([ui.cmd(s.argv.join(' ')), ui.dim(s.cwd === null ? '' : ` in ${s.cwd}`)]),
|
|
202
|
+
)
|
|
203
|
+
const proc = Bun.spawnSync({
|
|
204
|
+
cmd: [bin, ...s.argv.slice(1)],
|
|
205
|
+
cwd: s.cwd ?? process.cwd(),
|
|
206
|
+
stdio: ['inherit', 'inherit', 'inherit'],
|
|
207
|
+
})
|
|
208
|
+
if (proc.exitCode !== 0) {
|
|
209
|
+
throw new UserError(`vx init: ${s.argv.join(' ')} failed (exit ${proc.exitCode})`)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return steps.map((s) => s.argv.join(' '))
|
|
213
|
+
}
|
|
117
214
|
|
|
118
215
|
/** Write `plugins/<seam>.ts` and its test under `root`: the workspace root, else the cwd. */
|
|
119
216
|
async function scaffoldPlugin(seam: string, root: string, args: InitArgs): Promise<number> {
|
|
@@ -145,30 +242,63 @@ async function scaffoldPlugin(seam: string, root: string, args: InitArgs): Promi
|
|
|
145
242
|
return 0
|
|
146
243
|
}
|
|
147
244
|
|
|
148
|
-
|
|
149
|
-
|
|
245
|
+
/**
|
|
246
|
+
* The `@vzn/vx-lockfile` plugin the lockfile at `pkgRoot` calls for: without
|
|
247
|
+
* it a JS workspace has no project edges, and the first `^build` a user
|
|
248
|
+
* writes is refused at `vx lock`.
|
|
249
|
+
*/
|
|
250
|
+
function lockfilePlugin(pkgRoot: string | null): 'pnpm' | 'bun' | 'yarn' | 'npm' | null {
|
|
251
|
+
if (pkgRoot === null) return null
|
|
252
|
+
const has = (f: string): boolean => existsSync(path.join(pkgRoot, f))
|
|
253
|
+
if (has('pnpm-lock.yaml')) return 'pnpm'
|
|
254
|
+
if (has('bun.lock') || has('bun.lockb')) return 'bun'
|
|
255
|
+
if (has('yarn.lock')) return 'yarn'
|
|
256
|
+
if (has('package-lock.json') || has('npm-shrinkwrap.json')) return 'npm'
|
|
257
|
+
return null
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function workspaceSource(plugin: string | null): string {
|
|
261
|
+
if (plugin === null)
|
|
262
|
+
return "import { defineWorkspace } from '@vzn/vx'\n\nexport default defineWorkspace({ plugins: [] })\n"
|
|
263
|
+
return `import { defineWorkspace } from '@vzn/vx'\nimport { ${plugin} } from '@vzn/vx-lockfile'\n\nexport default defineWorkspace({ plugins: [${plugin}()] })\n`
|
|
264
|
+
}
|
|
150
265
|
const PROJECT =
|
|
151
266
|
"import { defineProject } from '@vzn/vx'\n\nexport default defineProject({ tasks: {} })\n"
|
|
152
267
|
|
|
153
268
|
/**
|
|
154
|
-
* No
|
|
155
|
-
* `vx.config.ts` with no task, then the lock.
|
|
156
|
-
* running vx (core-alias.ts), so the two load
|
|
157
|
-
* A file already here is kept; `--force` replaces it.
|
|
269
|
+
* No Turbo or Nx config: the installs, then `.vx/workspace.ts` declaring
|
|
270
|
+
* no plugin and a root `vx.config.ts` with no task, then the lock.
|
|
271
|
+
* `@vzn/vx` resolves to the running vx (core-alias.ts), so the two load
|
|
272
|
+
* whatever got installed. A file already here is kept; `--force` replaces it.
|
|
158
273
|
*/
|
|
159
|
-
async function starter(
|
|
274
|
+
async function starter(
|
|
275
|
+
root: string,
|
|
276
|
+
pkgRoot: string | null,
|
|
277
|
+
noGit: boolean,
|
|
278
|
+
args: InitArgs,
|
|
279
|
+
): Promise<number> {
|
|
160
280
|
if (args.mode !== undefined) {
|
|
161
281
|
throw new UserError(
|
|
162
|
-
`vx init: --${args.mode} answers a Turbo or Nx repo's question, and there is no
|
|
282
|
+
`vx init: --${args.mode} answers a Turbo or Nx repo's question, and there is no turbo.json or nx.json here`,
|
|
163
283
|
)
|
|
164
284
|
}
|
|
285
|
+
// A source checkout installs nothing, so it declares the plugin only where one is.
|
|
286
|
+
const found = lockfilePlugin(pkgRoot)
|
|
287
|
+
const plugin =
|
|
288
|
+
found !== null &&
|
|
289
|
+
(VERSION !== '0.0.0' ||
|
|
290
|
+
existsSync(path.join(pkgRoot!, 'node_modules', '@vzn', 'vx-lockfile', 'package.json')))
|
|
291
|
+
? found
|
|
292
|
+
: null
|
|
293
|
+
const plugins = plugin === null ? [] : ['@vzn/vx-lockfile']
|
|
294
|
+
const installs = args.json === true || args.dry ? await install(pkgRoot, true, plugins) : []
|
|
165
295
|
const ext = args.mjs ? 'mjs' : 'ts'
|
|
166
296
|
const config = `vx.config.${ext}`
|
|
167
297
|
const files: { path: string; contents: string }[] = []
|
|
168
298
|
const kept: string[] = []
|
|
169
299
|
const replaced: string[] = []
|
|
170
300
|
for (const [names, name, contents] of [
|
|
171
|
-
[WORKSPACE_CONFIG_FILENAMES, `.vx/workspace.${ext}`,
|
|
301
|
+
[WORKSPACE_CONFIG_FILENAMES, `.vx/workspace.${ext}`, workspaceSource(plugin)],
|
|
172
302
|
[PROJECT_CONFIG_FILENAMES, config, PROJECT],
|
|
173
303
|
] as const) {
|
|
174
304
|
const existing = names.find((n) => existsSync(path.join(root, n)))
|
|
@@ -185,16 +315,16 @@ async function starter(root: string, noGit: boolean, args: InitArgs): Promise<nu
|
|
|
185
315
|
] as const) {
|
|
186
316
|
if (!existsSync(path.join(root, name))) files.push({ path: name, contents })
|
|
187
317
|
}
|
|
188
|
-
const
|
|
189
|
-
const next = `${noGit ? 'git init, ' : ''}declare a task in ${kept.find((f) => f.startsWith('vx.config.')) ?? config}, then ${vx} lock && ${vx} run <task>`
|
|
318
|
+
const next = `${noGit ? 'git init, ' : ''}declare a task in ${kept.find((f) => f.startsWith('vx.config.')) ?? config}, then vx lock && vx run <task>`
|
|
190
319
|
if (args.json === true) {
|
|
191
320
|
process.stdout.write(
|
|
192
|
-
`${JSON.stringify({ dry: true, source: 'no
|
|
321
|
+
`${JSON.stringify({ dry: true, source: 'no turbo.json or nx.json', files, kept, replaced, todos: [], notes: [], next, installs })}\n`,
|
|
193
322
|
)
|
|
194
323
|
return 0
|
|
195
324
|
}
|
|
196
|
-
const rows: ui.FieldRow[] = [['from', 'no
|
|
325
|
+
const rows: ui.FieldRow[] = [['from', 'no turbo.json or nx.json: one project, the root']]
|
|
197
326
|
if (args.dry) rows.push(['dry run', 'nothing written'])
|
|
327
|
+
if (installs.length > 0) rows.push(['install', ...installs])
|
|
198
328
|
if (files.length > 0) rows.push(['files', ...files.map((f) => f.path)])
|
|
199
329
|
if (kept.length > 0) rows.push(['kept', ...kept])
|
|
200
330
|
if (replaced.length > 0) rows.push(['replaced', ...replaced])
|
|
@@ -203,6 +333,8 @@ async function starter(root: string, noGit: boolean, args: InitArgs): Promise<nu
|
|
|
203
333
|
ui.print('out', ui.report(`vx init ${VERSION}`, [...rows, null, ['next', next]]))
|
|
204
334
|
return 0
|
|
205
335
|
}
|
|
336
|
+
const ran = await install(pkgRoot, false, plugins)
|
|
337
|
+
if (ran.length > 0) rows.push(['installed', ...ran])
|
|
206
338
|
for (const f of files) await Bun.write(path.join(root, f.path), f.contents)
|
|
207
339
|
for (const f of replaced) await unlink(path.join(root, f))
|
|
208
340
|
// The lock that follows loads the workspace file just written.
|
|
@@ -217,7 +349,7 @@ async function starter(root: string, noGit: boolean, args: InitArgs): Promise<nu
|
|
|
217
349
|
}
|
|
218
350
|
|
|
219
351
|
/**
|
|
220
|
-
* A
|
|
352
|
+
* A Turbo or Nx repo is `@vzn/vx-migrate`'s, after the global install, at the latest
|
|
221
353
|
* release: it installs @vzn/vx and every @vzn/vx-* plugin the root lists
|
|
222
354
|
* at its own version, so init always lands on latest with all of them
|
|
223
355
|
* together, as `vx upgrade` moves them (M, 2026-10-16: an installed 0.0.637
|
|
@@ -232,6 +364,8 @@ async function migrate(dirs: readonly string[], args: InitArgs): Promise<number>
|
|
|
232
364
|
args.mode !== undefined && `--${args.mode}`,
|
|
233
365
|
args.json === true && '--format=json',
|
|
234
366
|
].filter((f): f is string => typeof f === 'string')
|
|
367
|
+
// vx-migrate installs the project's own vx.
|
|
368
|
+
if (!args.dry) await install(null, false)
|
|
235
369
|
// An exact version: `bun x` may answer `@latest` from its cache.
|
|
236
370
|
const latest = VERSION === '0.0.0' ? 'latest' : await targetVersion(undefined, 'vx init')
|
|
237
371
|
return Bun.spawn(migrateCommand(dirs, flags, latest), {
|
package/src/cli/last.ts
CHANGED
|
@@ -365,10 +365,12 @@ export function formatTaskRows(tasks: readonly RunSummaryRow[]): string[] {
|
|
|
365
365
|
? [...hits].sort((a, b) => b.durationMs - a.durationMs).slice(0, HITS_SHOWN)
|
|
366
366
|
: hits
|
|
367
367
|
const ordered = [...failed, ...rest, ...shownHits]
|
|
368
|
-
const
|
|
368
|
+
const at = (t: (typeof ordered)[number]): string =>
|
|
369
|
+
t.placement !== null ? `@${t.placement}` : ''
|
|
370
|
+
const idW = Math.max(...ordered.map((t) => `${t.project}#${t.task}${at(t)}`.length), 4)
|
|
369
371
|
const lines = ['']
|
|
370
372
|
for (const t of ordered) {
|
|
371
|
-
const id = `${t.project}#${t.task}`
|
|
373
|
+
const id = `${t.project}#${t.task}${at(t)}`
|
|
372
374
|
// The terminal summary's own word for a task that runs every time
|
|
373
375
|
// by design; a reader must not take its row for a miss.
|
|
374
376
|
// A failure's row reads as the frame did — `failed (exit 137)` —
|
package/src/cli/plan-format.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Formatters for `--dry` (human / JSON).
|
|
2
2
|
|
|
3
3
|
import type { AffectedReason, CacheStatus, RunPlan } from '../orchestrator/index.js'
|
|
4
|
-
import { formatElapsed, maskedCommand } from '../util/index.js'
|
|
4
|
+
import { formatBytes, formatElapsed, maskedCommand } from '../util/index.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Human-readable preview. One line per real task (groups hidden, same
|
|
@@ -65,7 +65,11 @@ export function formatPlanText(plan: RunPlan): string {
|
|
|
65
65
|
|
|
66
66
|
const hitParts: string[] = []
|
|
67
67
|
if (local > 0) hitParts.push(`${local} local`)
|
|
68
|
-
if (remote > 0)
|
|
68
|
+
if (remote > 0) {
|
|
69
|
+
// The remote's sizes, summed: what restoring the hits would download.
|
|
70
|
+
const bytes = real.reduce((sum, t) => sum + (t.remoteBytes ?? 0), 0)
|
|
71
|
+
hitParts.push(bytes > 0 ? `${remote} remote, ${formatBytes(bytes)}` : `${remote} remote`)
|
|
72
|
+
}
|
|
69
73
|
const hits = local + remote
|
|
70
74
|
const summary: string[] = [`${real.length} task(s) planned`]
|
|
71
75
|
if (hits > 0) summary.push(`${hits} cache hit${hits === 1 ? '' : 's'} (${hitParts.join(', ')})`)
|
|
@@ -9,9 +9,16 @@
|
|
|
9
9
|
|
|
10
10
|
import type { CachePolicy, PluginCache, ResolvedRemote } from '../cache/index.js'
|
|
11
11
|
import type { WorkspaceConfig } from '../config.js'
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
BranchFacts,
|
|
14
|
+
VxPlugin,
|
|
15
|
+
PluginCommand,
|
|
16
|
+
CommandContext,
|
|
17
|
+
CacheFacts,
|
|
18
|
+
TaskArtifacts,
|
|
19
|
+
} from '../orchestrator/index.js'
|
|
13
20
|
import { configuredBaseBranch, findWorkspaceRoot } from '../workspace/index.js'
|
|
14
|
-
import { machineParallelism, maskedLine } from '../util/index.js'
|
|
21
|
+
import { machineParallelism, maskedLine, SecretBroker } from '../util/index.js'
|
|
15
22
|
import { resolveSetting } from '../settings.js'
|
|
16
23
|
|
|
17
24
|
export interface ResolvedPluginCommand {
|
|
@@ -111,6 +118,8 @@ export async function resolvePluginCommand(
|
|
|
111
118
|
: [process.execPath, Bun.main],
|
|
112
119
|
warn,
|
|
113
120
|
cache: lazyCache(() => verbCache(ws, plugin.name)),
|
|
121
|
+
artifacts: lazyArtifacts(() => verbArtifacts(ws)),
|
|
122
|
+
artifactsFor: (facts) => lazyArtifacts(() => verbArtifacts(ws, facts)),
|
|
114
123
|
},
|
|
115
124
|
}
|
|
116
125
|
}
|
|
@@ -138,10 +147,8 @@ async function verbCache(
|
|
|
138
147
|
ws: Exclude<Awaited<ReturnType<typeof workspacePlugins>>, UnresolvedPluginCommand | null>,
|
|
139
148
|
plugin: string,
|
|
140
149
|
): Promise<PluginCache> {
|
|
141
|
-
const [{ Cache, pluginCache }, { resolveCache, resolveCacheScope }] =
|
|
142
|
-
import('../cache/index.js'),
|
|
143
|
-
import('../orchestrator/index.js'),
|
|
144
|
-
])
|
|
150
|
+
const [{ Cache, pluginCache }, { cacheRefs, openRemotes, resolveCache, resolveCacheScope }] =
|
|
151
|
+
await Promise.all([import('../cache/index.js'), import('../orchestrator/index.js')])
|
|
145
152
|
const scope = resolveCacheScope(
|
|
146
153
|
ws.workspaceRoot,
|
|
147
154
|
ws.branch.trusted === true ? null : ws.branch.scope,
|
|
@@ -162,13 +169,98 @@ async function verbCache(
|
|
|
162
169
|
cacheDir: ws.cacheDir,
|
|
163
170
|
warn,
|
|
164
171
|
scope,
|
|
172
|
+
refs: cacheRefs(ws.workspaceRoot, configuredBaseBranch(ws.workspaceConfig)),
|
|
165
173
|
localCache,
|
|
166
174
|
policy: VERB_POLICY,
|
|
167
175
|
remotes,
|
|
168
176
|
})
|
|
177
|
+
await openVerbRemotes(remotes, openRemotes)
|
|
169
178
|
return pluginCache(plugin, { db: localCache.dbHandle(), policy: VERB_POLICY, remotes, scope })
|
|
170
179
|
}
|
|
171
180
|
|
|
181
|
+
/**
|
|
182
|
+
* The verb's `ctx.artifacts`: the workspace's cache as a run opens it, its
|
|
183
|
+
* policy and scope, a laptop writing no remote unless the policy names it.
|
|
184
|
+
* With `facts` (`ctx.artifactsFor`), under another run's refs and lent access.
|
|
185
|
+
*/
|
|
186
|
+
async function verbArtifacts(
|
|
187
|
+
ws: Exclude<Awaited<ReturnType<typeof workspacePlugins>>, UnresolvedPluginCommand | null>,
|
|
188
|
+
facts?: () => CacheFacts | undefined,
|
|
189
|
+
): Promise<TaskArtifacts> {
|
|
190
|
+
const [
|
|
191
|
+
{ Cache, FULL_CACHE_POLICY },
|
|
192
|
+
{
|
|
193
|
+
cacheRefs,
|
|
194
|
+
namesRemote,
|
|
195
|
+
openRemotes,
|
|
196
|
+
resolveCache,
|
|
197
|
+
resolveCacheScope,
|
|
198
|
+
taskArtifacts,
|
|
199
|
+
workspaceCachePolicy,
|
|
200
|
+
},
|
|
201
|
+
{ onCI },
|
|
202
|
+
] = await Promise.all([
|
|
203
|
+
import('../cache/index.js'),
|
|
204
|
+
import('../orchestrator/index.js'),
|
|
205
|
+
import('./run.js'),
|
|
206
|
+
])
|
|
207
|
+
const asked = workspaceCachePolicy(ws.workspaceConfig) ?? FULL_CACHE_POLICY
|
|
208
|
+
const policy: CachePolicy =
|
|
209
|
+
onCI(process.env['CI']) || namesRemote(ws.workspaceConfig?.cache?.policy)
|
|
210
|
+
? asked
|
|
211
|
+
: { ...asked, remoteWrite: false }
|
|
212
|
+
const scope = resolveCacheScope(
|
|
213
|
+
ws.workspaceRoot,
|
|
214
|
+
ws.branch.trusted === true ? null : ws.branch.scope,
|
|
215
|
+
ws.plugins.some((p) => p.cache !== undefined),
|
|
216
|
+
configuredBaseBranch(ws.workspaceConfig),
|
|
217
|
+
)
|
|
218
|
+
const localCache = new Cache(
|
|
219
|
+
ws.cacheDir,
|
|
220
|
+
{ read: policy.localRead, write: policy.localWrite },
|
|
221
|
+
ws.workspaceRoot,
|
|
222
|
+
undefined,
|
|
223
|
+
'open',
|
|
224
|
+
await ws.storeRoot,
|
|
225
|
+
)
|
|
226
|
+
const remotes: ResolvedRemote[] = []
|
|
227
|
+
const cache = await resolveCache(ws.plugins, {
|
|
228
|
+
workspaceRoot: ws.workspaceRoot,
|
|
229
|
+
cacheDir: ws.cacheDir,
|
|
230
|
+
warn,
|
|
231
|
+
scope,
|
|
232
|
+
refs: facts?.()?.refs ?? cacheRefs(ws.workspaceRoot, configuredBaseBranch(ws.workspaceConfig)),
|
|
233
|
+
localCache,
|
|
234
|
+
policy,
|
|
235
|
+
remotes,
|
|
236
|
+
...(facts === undefined ? {} : { lentBy: () => facts()?.lent }),
|
|
237
|
+
})
|
|
238
|
+
await openVerbRemotes(remotes, openRemotes)
|
|
239
|
+
return taskArtifacts(cache, ws.workspaceRoot)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Each remote's `open`, as a run's: a remote that takes its token there
|
|
244
|
+
* (turboCache's OIDC exchange, a lent token) had none, so an agent's
|
|
245
|
+
* uploads went nowhere and its run could not restore them.
|
|
246
|
+
*/
|
|
247
|
+
async function openVerbRemotes(
|
|
248
|
+
remotes: readonly ResolvedRemote[],
|
|
249
|
+
open: (typeof import('../orchestrator/index.js'))['openRemotes'],
|
|
250
|
+
): Promise<void> {
|
|
251
|
+
const ci = (await import('./run.js')).onCI(process.env['CI'])
|
|
252
|
+
await open(remotes, ci, new SecretBroker(ci, warn))
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function lazyArtifacts(open: () => Promise<TaskArtifacts>): TaskArtifacts {
|
|
256
|
+
let opened: Promise<TaskArtifacts> | undefined
|
|
257
|
+
const artifacts = (): Promise<TaskArtifacts> => (opened ??= open())
|
|
258
|
+
return {
|
|
259
|
+
save: async (key, task, run) => (await artifacts()).save(key, task, run),
|
|
260
|
+
restore: async (key, task) => (await artifacts()).restore(key, task),
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
172
264
|
function lazyCache(open: () => Promise<PluginCache>): PluginCache {
|
|
173
265
|
let opened: Promise<PluginCache> | undefined
|
|
174
266
|
const cache = (): Promise<PluginCache> => (opened ??= open())
|
|
@@ -10,13 +10,13 @@ export const PLUGIN_TEMPLATES: Readonly<Record<string, { plugin: string; test: s
|
|
|
10
10
|
},
|
|
11
11
|
cache: {
|
|
12
12
|
plugin:
|
|
13
|
-
"// `cache`: where artifacts live. A directory stands in for a remote store;\n// core puts it behind the local cache, so a remote error is a miss and a\n// warning, never a failed run.\nimport { link, mkdir, unlink } from 'node:fs/promises'\nimport path from 'node:path'\nimport { definePlugin, type RemoteCacheLayer, type VxPlugin } from '@vzn/vx'\n\nclass DirRemote implements RemoteCacheLayer {\n constructor(readonly endpoint: string) {}\n async has(hash: string): Promise<
|
|
13
|
+
"// `cache`: where artifacts live. A directory stands in for a remote store;\n// core puts it behind the local cache, so a remote error is a miss and a\n// warning, never a failed run.\nimport { link, mkdir, unlink } from 'node:fs/promises'\nimport path from 'node:path'\nimport { definePlugin, type ArtifactInfo, type RemoteCacheLayer, type VxPlugin } from '@vzn/vx'\n\nclass DirRemote implements RemoteCacheLayer {\n constructor(readonly endpoint: string) {}\n async has(hash: string): Promise<ArtifactInfo | null> {\n const file = Bun.file(path.join(this.endpoint, hash))\n return (await file.exists()) ? { size: file.size } : null\n }\n async get(hash: string) {\n const file = Bun.file(path.join(this.endpoint, hash))\n return (await file.exists()) ? { body: file, durationMs: undefined } : null\n }\n async put(hash: string, body: Blob): Promise<void> {\n await mkdir(this.endpoint, { recursive: true })\n // Written aside, then linked: on a shared directory a rename can swap\n // the file under a reader, while a link never replaces an existing one.\n // EEXIST is success: the same hash means the same bytes.\n const tmp = path.join(this.endpoint, `.${hash}.${process.pid}`)\n await Bun.write(tmp, body)\n try {\n await link(tmp, path.join(this.endpoint, hash))\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err\n } finally {\n await unlink(tmp)\n }\n }\n}\n\nexport function dirCache(dir: string): VxPlugin {\n return definePlugin(import.meta, {\n cache() {\n return new DirRemote(dir)\n },\n })\n}\n",
|
|
14
14
|
test: "// A test for the example `cache` plugin beside it: a throwaway workspace\n// declares the plugin and vx's own run() drives it. `bun test` runs it.\nimport { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { afterEach, beforeEach, expect, it } from 'bun:test'\nimport { run, type Logger } from '@vzn/vx'\n\nlet root: string\nbeforeEach(async () => {\n root = await mkdtemp(path.join(os.tmpdir(), 'vx-plugin-cache-'))\n await writeFile(path.join(root, 'package.json'), JSON.stringify({ name: 'ws', private: true }))\n await writeFile(path.join(root, 'pnpm-workspace.yaml'), 'packages:\\n - \"packages/*\"\\n')\n Bun.spawnSync({ cmd: ['git', 'init', '-q'], cwd: root })\n})\nafterEach(async () => {\n await rm(root, { recursive: true, force: true })\n})\n\n/** A package `name` whose `vx.config.mjs` is `config`. */\nasync function pkg(\n name: string,\n config: string,\n scripts?: Record<string, string>,\n): Promise<string> {\n const dir = path.join(root, 'packages', name)\n await mkdir(dir, { recursive: true })\n await writeFile(\n path.join(dir, 'package.json'),\n JSON.stringify({ name, version: '1.0.0', ...(scripts ? { scripts } : {}) }),\n )\n // Core reads no package.json: the config names the project.\n await writeFile(\n path.join(dir, 'vx.config.mjs'),\n config.replace('export default {', `export default { name: ${JSON.stringify(name)},`),\n )\n return dir\n}\n\n/**\n * A `.vx/workspace.mjs` declaring `call`, e.g. `dirCache()`, from the plugin file\n * beside this test, then `vx lock`: a run reads project configs only from the lock.\n */\nasync function workspace(call: string): Promise<void> {\n const from = JSON.stringify(path.join(import.meta.dir, 'cache.ts'))\n await Bun.write(\n path.join(root, '.vx/workspace.mjs'),\n `import { dirCache } from ${from}\\nexport default { plugins: [${call}] }\\n`,\n )\n const vx = path.join(path.dirname(new URL(import.meta.resolve('@vzn/vx')).pathname), 'bin.ts')\n const lock = Bun.spawnSync({ cmd: [process.execPath, vx, 'lock'], cwd: root })\n expect([lock.exitCode, lock.stderr.toString()]).toEqual([0, ''])\n}\n\nfunction log(order: string[] = []): Logger {\n return {\n status() {},\n taskStart(node) {\n order.push(`start ${node.id}`)\n },\n taskStdout() {},\n taskStderr() {},\n taskComplete(node) {\n order.push(`end ${node.id}`)\n },\n }\n}\n\nit('a run on an empty local cache restores from the directory', async () => {\n const dir = await pkg(\n 'a',\n `export default { tasks: { build: {\n exec: { command: 'mkdir -p dist && echo built > dist/out.txt' },\n inputs: { files: ['package.json'] }, cache: { outputs: { files: ['dist/**'] } },\n } } }`,\n )\n await workspace(`dirCache(${JSON.stringify(path.join(root, 'remote'))})`)\n const first = await run({ cwd: root, tasks: ['build'], log: log(), handleSignals: false })\n expect(first.outcomes.map((o) => o.status)).toEqual(['success'])\n await rm(path.join(root, '.vx', 'cache'), { recursive: true, force: true })\n await rm(path.join(dir, 'dist'), { recursive: true, force: true })\n const second = await run({ cwd: root, tasks: ['build'], log: log(), handleSignals: false })\n expect(second.outcomes.map((o) => o.status)).toEqual(['cache-hit-remote'])\n expect(await Bun.file(path.join(dir, 'dist', 'out.txt')).text()).toBe('built\\n')\n}, 20_000)\n",
|
|
15
15
|
},
|
|
16
16
|
commands: {
|
|
17
17
|
plugin:
|
|
18
18
|
"// `commands`: a CLI verb. `vx cache-dir` prints where this workspace's cache\n// lives. The verb's return value is the exit code.\nimport { definePlugin, type VxPlugin } from '@vzn/vx'\n\nexport function cacheDirVerb(): VxPlugin {\n return definePlugin(import.meta, {\n commands: {\n 'cache-dir': {\n description: \"print this workspace's cache directory\",\n run(argv, ctx) {\n if (argv.length > 0) {\n console.error(`vx cache-dir: takes no arguments, got ${argv.join(' ')}`)\n return 2\n }\n console.log(ctx.cacheDir)\n return 0\n },\n },\n },\n })\n}\n",
|
|
19
|
-
test: "// A test for the example `commands` plugin beside it: the verb is called\n// the way vx calls it, with the context vx hands it. `bun test` runs it.\nimport { expect, it, spyOn } from 'bun:test'\nimport { cacheDirVerb } from './commands.ts'\n\nconst verb = cacheDirVerb().commands!['cache-dir']!\nconst ctx = {\n workspaceRoot: '/ws',\n cacheDir: '/ws/.vx/cache',\n warn() {},\n concurrency: 1,\n vx: ['vx'],\n cache: {\n get: async () => null,\n put: async () => {},\n has: async () => null,\n hasMany: async () => null,\n getRun: async () => null,\n },\n}\n\nit('prints the cache directory; an argument is exit 2', async () => {\n const out = spyOn(console, 'log').mockImplementation(() => {})\n const err = spyOn(console, 'error').mockImplementation(() => {})\n try {\n expect(await verb.run([], ctx)).toBe(0)\n expect(out.mock.calls).toEqual([['/ws/.vx/cache']])\n expect(await verb.run(['x'], ctx)).toBe(2)\n expect(err.mock.calls).toEqual([['vx cache-dir: takes no arguments, got x']])\n } finally {\n out.mockRestore()\n err.mockRestore()\n }\n})\n",
|
|
19
|
+
test: "// A test for the example `commands` plugin beside it: the verb is called\n// the way vx calls it, with the context vx hands it. `bun test` runs it.\nimport { expect, it, spyOn } from 'bun:test'\nimport { cacheDirVerb } from './commands.ts'\n\nconst verb = cacheDirVerb().commands!['cache-dir']!\nconst ctx = {\n workspaceRoot: '/ws',\n cacheDir: '/ws/.vx/cache',\n warn() {},\n concurrency: 1,\n vx: ['vx'],\n cache: {\n get: async () => null,\n put: async () => {},\n has: async () => null,\n hasMany: async () => null,\n getRun: async () => null,\n },\n artifacts: { save: async () => {}, restore: async () => null },\n artifactsFor: () => ({ save: async () => {}, restore: async () => null }),\n}\n\nit('prints the cache directory; an argument is exit 2', async () => {\n const out = spyOn(console, 'log').mockImplementation(() => {})\n const err = spyOn(console, 'error').mockImplementation(() => {})\n try {\n expect(await verb.run([], ctx)).toBe(0)\n expect(out.mock.calls).toEqual([['/ws/.vx/cache']])\n expect(await verb.run(['x'], ctx)).toBe(2)\n expect(err.mock.calls).toEqual([['vx cache-dir: takes no arguments, got x']])\n } finally {\n out.mockRestore()\n err.mockRestore()\n }\n})\n",
|
|
20
20
|
},
|
|
21
21
|
executor: {
|
|
22
22
|
plugin:
|
package/src/cli/run.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { isatty } from 'node:tty'
|
|
2
2
|
import { findWorkspaceRoot } from '../workspace/index.js'
|
|
3
|
-
import { translateForeign } from './foreign-flags.js'
|
|
4
3
|
import { flagHint, refuse, seeHelp } from './help.js'
|
|
5
4
|
import {
|
|
6
5
|
planRun,
|
|
@@ -116,7 +115,7 @@ const SETTING_OPTIONS = [
|
|
|
116
115
|
|
|
117
116
|
/** `verb` is the one being parsed for: `vx watch` reads `vx run`'s flags, and
|
|
118
117
|
* its flag hints named `vx run` (M-58). The caller appends the help pointer. */
|
|
119
|
-
export function parseRunArgs(
|
|
118
|
+
export function parseRunArgs(args: readonly string[], verb: 'run' | 'watch' = 'run'): RunArgs {
|
|
120
119
|
const out: RunArgs = {
|
|
121
120
|
tasks: [],
|
|
122
121
|
filters: [],
|
|
@@ -132,10 +131,6 @@ export function parseRunArgs(rawArgs: readonly string[], verb: 'run' | 'watch' =
|
|
|
132
131
|
affected: undefined,
|
|
133
132
|
}
|
|
134
133
|
|
|
135
|
-
// A Turbo or Nx spelling becomes vx's, or a refusal naming vx's.
|
|
136
|
-
const translated = translateForeign(rawArgs)
|
|
137
|
-
if ('error' in translated) return { ...out, error: translated.error }
|
|
138
|
-
const args = translated
|
|
139
134
|
const sepIdx = args.indexOf('--')
|
|
140
135
|
const before = sepIdx === -1 ? args : args.slice(0, sepIdx)
|
|
141
136
|
out.forwardArgs = sepIdx === -1 ? [] : args.slice(sepIdx + 1)
|
|
@@ -518,7 +513,6 @@ export async function resolveRunOptions(
|
|
|
518
513
|
}
|
|
519
514
|
if (parsed.profile !== undefined) opts.profile = parsed.profile
|
|
520
515
|
// `--affected`'s base is the branch's for the cache scope too.
|
|
521
|
-
if (affectedFilter !== undefined) opts.scopeBase = affectedFilter.slice(4, -1)
|
|
522
516
|
|
|
523
517
|
return opts
|
|
524
518
|
}
|
|
@@ -664,6 +658,6 @@ export async function runCmd(args: readonly string[]): Promise<number> {
|
|
|
664
658
|
}
|
|
665
659
|
|
|
666
660
|
/** CI=0 and CI=false are "not CI": several vendors set CI=true. */
|
|
667
|
-
function onCI(v: string | undefined): boolean {
|
|
661
|
+
export function onCI(v: string | undefined): boolean {
|
|
668
662
|
return v !== undefined && v !== '' && v !== '0' && v !== 'false'
|
|
669
663
|
}
|
package/src/cli/select.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// here goes through the staged load, so the answer is the run's.
|
|
5
5
|
|
|
6
6
|
import path from 'node:path'
|
|
7
|
+
import { settingEnv } from '../settings.js'
|
|
7
8
|
import fs from 'node:fs'
|
|
8
9
|
import {
|
|
9
10
|
affectedChanges,
|
|
@@ -244,12 +245,13 @@ function refusalOf(err: unknown): Refusal {
|
|
|
244
245
|
|
|
245
246
|
/**
|
|
246
247
|
* The filter `--affected[=<base>]` stands for: `...[<base>]`, the changed
|
|
247
|
-
* projects and their dependents. An empty base is
|
|
248
|
-
* a pull request's base commit), else the
|
|
248
|
+
* projects and their dependents. An empty base is `VX_AFFECTED_BASE`'s,
|
|
249
|
+
* else a plugin's (`setBranch`: a pull request's base commit), else the
|
|
250
|
+
* merge base with `baseBranch`.
|
|
249
251
|
*/
|
|
250
252
|
export async function affectedFilterFor(cwd: string, affected: string): Promise<string | Refusal> {
|
|
251
253
|
const root = await findWorkspaceRoot(cwd)
|
|
252
|
-
let base = affected
|
|
254
|
+
let base = affected || ((settingEnv('affectedBase') as string | undefined) ?? '')
|
|
253
255
|
if (base === '') {
|
|
254
256
|
const ws = await loadCliWorkspace(root)
|
|
255
257
|
try {
|
package/src/cli/upgrade.ts
CHANGED
|
@@ -228,7 +228,7 @@ export function verifiedRelease(
|
|
|
228
228
|
const ours = signatures.filter((s) => s.keyid === key.id)
|
|
229
229
|
if (ours.length === 0) {
|
|
230
230
|
throw new UserError(
|
|
231
|
-
`vx upgrade: ${pkg}@${version} is signed with an npm key this vx does not know (${[...new Set(signatures.map((s) => s.keyid))].join(', ')}) — if npm rotated its signing key, this vx's installer needs updating: install the new release with
|
|
231
|
+
`vx upgrade: ${pkg}@${version} is signed with an npm key this vx does not know (${[...new Set(signatures.map((s) => s.keyid))].join(', ')}) — if npm rotated its signing key, this vx's installer needs updating: install the new release with bun add -g @vzn/vx@latest, or from https://github.com/vznjs/vx/releases. Nothing replaced`,
|
|
232
232
|
)
|
|
233
233
|
}
|
|
234
234
|
const publicKey = createPublicKey({
|
|
@@ -475,7 +475,7 @@ function installedVersion(dir: string, pkg: string): string | undefined {
|
|
|
475
475
|
}
|
|
476
476
|
|
|
477
477
|
/** The project's package manager: its lockfile, then `packageManager`, then the tree's shape. */
|
|
478
|
-
function projectManager(dir: string, execPath: string): ManagedInstall['manager'] {
|
|
478
|
+
export function projectManager(dir: string, execPath: string): ManagedInstall['manager'] {
|
|
479
479
|
for (let d = dir; ; d = path.dirname(d)) {
|
|
480
480
|
if (existsSync(path.join(d, 'pnpm-lock.yaml'))) return 'pnpm'
|
|
481
481
|
if (existsSync(path.join(d, 'bun.lock')) || existsSync(path.join(d, 'bun.lockb'))) return 'bun'
|
|
@@ -511,7 +511,7 @@ export function managedInstall(execPath: string, cwd: string): ManagedInstall |
|
|
|
511
511
|
const root = p.slice(0, `${p}/`.indexOf('/node_modules/'))
|
|
512
512
|
if (p.includes('/_npx/') || /\/bunx-[^/]*\//.test(p)) {
|
|
513
513
|
throw new UserError(
|
|
514
|
-
`vx upgrade: this vx runs from a package-manager cache (${execPath}) — nothing is installed to upgrade; ask for the version you want instead:
|
|
514
|
+
`vx upgrade: this vx runs from a package-manager cache (${execPath}) — nothing is installed to upgrade; ask for the version you want instead: bunx @vzn/vx@latest`,
|
|
515
515
|
)
|
|
516
516
|
}
|
|
517
517
|
if (p.includes('/.bun/install/global/')) return { manager: 'bun', scope: 'global', cwd: root }
|
|
@@ -733,7 +733,7 @@ export async function upgradeOnlyGlobal(
|
|
|
733
733
|
const install = managedInstall(self, path.dirname(self))
|
|
734
734
|
if (install?.scope === 'project') {
|
|
735
735
|
throw new UserError(
|
|
736
|
-
`vx upgrade -g: no global vx ran — ${self} is the project's own. Install one (
|
|
736
|
+
`vx upgrade -g: no global vx ran — ${self} is the project's own. Install one (bun add -g ${PACKAGE}), or run \`vx upgrade\` to move the project's`,
|
|
737
737
|
)
|
|
738
738
|
}
|
|
739
739
|
if (install === null) await upgradeBinary(self, version, current, 'global ')
|
|
@@ -771,8 +771,7 @@ export async function upgradeCmd(argv: readonly string[]): Promise<number> {
|
|
|
771
771
|
}
|
|
772
772
|
if (!isCompiledBinary()) {
|
|
773
773
|
throw new UserError(
|
|
774
|
-
'vx upgrade: only the compiled binary upgrades itself; this vx runs from source — use git pull
|
|
775
|
-
'(npm installs update with: npm install -g @vzn/vx@latest)',
|
|
774
|
+
'vx upgrade: only the compiled binary upgrades itself; this vx runs from source — use git pull',
|
|
776
775
|
)
|
|
777
776
|
}
|
|
778
777
|
const dest = process.execPath
|
package/src/config.ts
CHANGED
|
@@ -102,11 +102,9 @@ export interface CacheSettings {
|
|
|
102
102
|
* How the local cache keeps an entry. `embeddings` (default `'32KiB'`):
|
|
103
103
|
* an entry up to this size inside the index; `false`, none. `decompress` (default true): a larger one as its
|
|
104
104
|
* plain files, which a hit copies or clones out with no decode; off, as
|
|
105
|
-
* one `.tar.zst`.
|
|
106
|
-
* checks first, each file's size and mtime, every byte (`'hash'`), or
|
|
107
|
-
* nothing (`'none'`).
|
|
105
|
+
* one `.tar.zst`.
|
|
108
106
|
*/
|
|
109
|
-
store?: { embeddings?: false | string; decompress?: boolean
|
|
107
|
+
store?: { embeddings?: false | string; decompress?: boolean }
|
|
110
108
|
/**
|
|
111
109
|
* Cache transfers at once. `download` and `upload` (per remote) default
|
|
112
110
|
* to min(96, max(cores × 3, 64)); `restore`, local hits writing their
|