@vzn/vx 0.0.306 → 0.0.308
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/cli/init.ts +1 -0
- package/src/orchestrator/prepare.ts +25 -0
- package/src/orchestrator/run.ts +16 -3
- package/src/workspace/migrate-scripts.ts +58 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.308",
|
|
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.308",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.308",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.308",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.308"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cli/init.ts
CHANGED
|
@@ -102,6 +102,7 @@ export async function initCmd(args: readonly string[]): Promise<number> {
|
|
|
102
102
|
plan: migrateScripts(
|
|
103
103
|
metas,
|
|
104
104
|
metas.some((m) => m.dir === root) ? undefined : await readRootManifest(root),
|
|
105
|
+
root,
|
|
105
106
|
),
|
|
106
107
|
source: 'package.json scripts',
|
|
107
108
|
verb: 'vx init',
|
|
@@ -106,6 +106,11 @@ export interface PreparedRun {
|
|
|
106
106
|
* than run the remainder silently.
|
|
107
107
|
*/
|
|
108
108
|
unresolvedTasks: readonly string[]
|
|
109
|
+
/**
|
|
110
|
+
* The bare names in `unresolvedTasks` a project outside a scoped run's
|
|
111
|
+
* selection (the cwd's project, a `--filter`) declares: no typo, a scope.
|
|
112
|
+
*/
|
|
113
|
+
declaredElsewhere: readonly string[]
|
|
109
114
|
/** Every discovered project — the declared task names a typo is measured against. */
|
|
110
115
|
projects: ReadonlyMap<string, ProjectEntry>
|
|
111
116
|
/**
|
|
@@ -330,6 +335,24 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
330
335
|
)
|
|
331
336
|
}
|
|
332
337
|
}
|
|
338
|
+
// Run inside a project that lacks the task (`vx run typecheck` at a
|
|
339
|
+
// root whose members declare it), the run said no project declares it.
|
|
340
|
+
// Only a failing run pays for the rest of the workspace.
|
|
341
|
+
let declaredElsewhere: string[] = []
|
|
342
|
+
// A scope with no project to ask (a member with no vx config) leaves
|
|
343
|
+
// every requested name unjudged.
|
|
344
|
+
const bare = (candidateProjects.length === 0 ? tasks : unresolvedTasks).filter(
|
|
345
|
+
(t) => !t.includes('#'),
|
|
346
|
+
)
|
|
347
|
+
if (options.projects !== undefined && options.selectedByDiff !== true && bare.length > 0) {
|
|
348
|
+
let nowhere = undeclaredIn(bare, projects)
|
|
349
|
+
if (nowhere.length > 0 && projectsWithConfigs.some((m) => !projects.has(m.name))) {
|
|
350
|
+
nowhere = await declaredNowhere(nowhere, () =>
|
|
351
|
+
loadProjects({ ...loadArgs, seeds: 'all', closure: false, staged: projects }),
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
declaredElsewhere = bare.filter((t) => !nowhere.includes(t))
|
|
355
|
+
}
|
|
333
356
|
|
|
334
357
|
// Cache seam precedence: an EXPLICITLY injected remote layer
|
|
335
358
|
// (RunOptions.remoteCache — a distribution agent or daemon that already
|
|
@@ -413,6 +436,7 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
413
436
|
nodes: new Map(),
|
|
414
437
|
keyOnly: new Map(),
|
|
415
438
|
unresolvedTasks,
|
|
439
|
+
declaredElsewhere,
|
|
416
440
|
projects,
|
|
417
441
|
anyProjectConfig: projectsWithConfigs.length > 0,
|
|
418
442
|
workspaceFingerprint,
|
|
@@ -532,6 +556,7 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
532
556
|
nodes,
|
|
533
557
|
keyOnly,
|
|
534
558
|
unresolvedTasks,
|
|
559
|
+
declaredElsewhere,
|
|
535
560
|
projects,
|
|
536
561
|
anyProjectConfig: projectsWithConfigs.length > 0,
|
|
537
562
|
workspaceFingerprint,
|
package/src/orchestrator/run.ts
CHANGED
|
@@ -273,7 +273,7 @@ async function runOnBus(
|
|
|
273
273
|
// case too; the message is identical, so that branch stays below.
|
|
274
274
|
if (prepared.unresolvedTasks.length > 0) {
|
|
275
275
|
log.status(
|
|
276
|
-
`No projects declare task(s): ${prepared.unresolvedTasks.join(', ')}.${didYouMean(prepared.unresolvedTasks, prepared.projects)}${await initHint(prepared)}`,
|
|
276
|
+
`No projects declare task(s): ${prepared.unresolvedTasks.join(', ')}.${elsewhereHint(prepared.declaredElsewhere)}${didYouMean(prepared.unresolvedTasks, prepared.projects)}${await initHint(prepared)}`,
|
|
277
277
|
)
|
|
278
278
|
await teardown()
|
|
279
279
|
prepared.cache.close()
|
|
@@ -292,7 +292,7 @@ async function runOnBus(
|
|
|
292
292
|
// buildTaskGraph semantics but logged just in case.
|
|
293
293
|
const msg =
|
|
294
294
|
prepared.empty === 'no-tasks-declared'
|
|
295
|
-
? `No projects declare task(s): ${options.tasks.join(', ')}.${await initHint(prepared)}`
|
|
295
|
+
? `No projects declare task(s): ${options.tasks.join(', ')}.${elsewhereHint(prepared.declaredElsewhere)}${await initHint(prepared)}`
|
|
296
296
|
: 'No tasks to run.'
|
|
297
297
|
log.status(msg)
|
|
298
298
|
await teardown()
|
|
@@ -1210,7 +1210,14 @@ export async function planRun(options: RunOptions): Promise<RunPlan> {
|
|
|
1210
1210
|
return {
|
|
1211
1211
|
tasks: [],
|
|
1212
1212
|
unresolvedTasks: prepared.unresolvedTasks,
|
|
1213
|
-
unresolvedHint: `${didYouMean(prepared.unresolvedTasks, prepared.projects)}${await initHint(prepared)}`,
|
|
1213
|
+
unresolvedHint: `${elsewhereHint(prepared.declaredElsewhere)}${didYouMean(prepared.unresolvedTasks, prepared.projects)}${await initHint(prepared)}`,
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
if (prepared.empty === 'no-tasks-declared' && prepared.declaredElsewhere.length > 0) {
|
|
1217
|
+
return {
|
|
1218
|
+
tasks: [],
|
|
1219
|
+
unresolvedTasks: prepared.declaredElsewhere,
|
|
1220
|
+
unresolvedHint: elsewhereHint(prepared.declaredElsewhere),
|
|
1214
1221
|
}
|
|
1215
1222
|
}
|
|
1216
1223
|
if (prepared.empty !== null) return { tasks: [] }
|
|
@@ -1258,6 +1265,12 @@ export async function planRun(options: RunOptions): Promise<RunPlan> {
|
|
|
1258
1265
|
}
|
|
1259
1266
|
}
|
|
1260
1267
|
|
|
1268
|
+
/** The names a project outside the run's selection declares, and how to reach them. */
|
|
1269
|
+
function elsewhereHint(names: readonly string[]): string {
|
|
1270
|
+
if (names.length === 0) return ''
|
|
1271
|
+
return ` Only projects outside the selection declare ${names.join(', ')} — pass --all, or --filter to pick them.`
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1261
1274
|
/**
|
|
1262
1275
|
* The first-run case, told apart from a typo: no package in the workspace
|
|
1263
1276
|
* has a `vx.config.*` at all, so no name could have resolved. Reached only
|
|
@@ -244,24 +244,67 @@ function upstreamBuildOnWorker(tasks: GeneratedTask[]): void {
|
|
|
244
244
|
if (!at.todos.includes(todo)) at.todos.push(todo)
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
/**
|
|
248
|
+
* A root script that runs the members rather than checking the repo:
|
|
249
|
+
* pnpm's `-r` / `--filter` / `-C`, npm's and Yarn's workspace flags, Bun's
|
|
250
|
+
* `--filter`, and the other runners, vx itself included. Mapped, it ran
|
|
251
|
+
* every member's task again beside the member's own (D-45).
|
|
252
|
+
*/
|
|
253
|
+
const RUNS_MEMBERS =
|
|
254
|
+
/(^|[\s;&|(])(turbo|nx|lerna|ultra|wireit|nps|moon|rush|vx)(\s|$)|\s(-r|--recursive|--filter|-F|--workspaces|-ws|--workspace|-w|-C|--dir|--if-present|--parallel|--stream)(\s|=|$)|\bworkspaces?\s+(foreach|run)\b|\bcd\s/
|
|
255
|
+
|
|
247
256
|
/**
|
|
248
257
|
* `outside`: the root manifest when the root is no member (pnpm's and
|
|
249
258
|
* Yarn's default), whose own scripts (`lint: eslint .`) went unmapped
|
|
250
|
-
* without a word.
|
|
259
|
+
* without a word; `outsideDir` is where it sits.
|
|
251
260
|
*/
|
|
252
261
|
export function migrateScripts(
|
|
253
262
|
metas: readonly ProjectMeta[],
|
|
254
263
|
outside?: Readonly<Record<string, unknown>>,
|
|
264
|
+
outsideDir?: string,
|
|
255
265
|
): MigrationPlan {
|
|
256
266
|
const projects: GeneratedProject[] = []
|
|
257
267
|
const hookMemo = new Map<string, string | null>()
|
|
258
|
-
// The workspace root among members: its scripts run the
|
|
259
|
-
// (`npm run build --workspaces`, `pnpm -r build`), and a root
|
|
260
|
-
// of one ran every member's build again under `--all`. Since
|
|
261
|
-
// hand-written root config makes the root a project, and
|
|
262
|
-
// replaced it so (D-45). A lone package is its repo's project
|
|
268
|
+
// The workspace root among members: many of its scripts run the
|
|
269
|
+
// workspace (`npm run build --workspaces`, `pnpm -r build`), and a root
|
|
270
|
+
// task made of one ran every member's build again under `--all`. Since
|
|
271
|
+
// D-39 a hand-written root config makes the root a project, and
|
|
272
|
+
// `--force` replaced it so (D-45). A lone package is its repo's project
|
|
273
|
+
// and maps.
|
|
263
274
|
const root = metas.length > 1 ? workspaceRootOf(metas) : undefined
|
|
264
275
|
const notes: string[] = []
|
|
276
|
+
// The rest check the whole repo (`lint: oxlint .`, `test: vitest`):
|
|
277
|
+
// `vx run lint` found no project in remix, wagmi or element-plus. Such a
|
|
278
|
+
// script maps onto the root, when the root has a name (vx skips a
|
|
279
|
+
// nameless one's config), no config of its own, and no member declares
|
|
280
|
+
// the task, so `--all` never runs one check twice.
|
|
281
|
+
const outsideName =
|
|
282
|
+
typeof outside?.['name'] === 'string' && outside['name'] !== '' ? outside['name'] : undefined
|
|
283
|
+
const rootMeta: ProjectMeta | undefined =
|
|
284
|
+
root !== undefined
|
|
285
|
+
? root.configPath === null
|
|
286
|
+
? root
|
|
287
|
+
: undefined
|
|
288
|
+
: outsideName !== undefined && outsideDir !== undefined
|
|
289
|
+
? { name: outsideName, dir: outsideDir, packageJson: outside as never, configPath: null }
|
|
290
|
+
: undefined
|
|
291
|
+
const memberTasks = new Set(
|
|
292
|
+
metas
|
|
293
|
+
.filter((m) => m !== root)
|
|
294
|
+
.flatMap((m) => Object.entries(scriptsOf(m)))
|
|
295
|
+
.filter(([, v]) => typeof v === 'string' && v !== '')
|
|
296
|
+
.map(([n]) => n),
|
|
297
|
+
)
|
|
298
|
+
const rootScripts = (meta: ProjectMeta): Record<string, unknown> =>
|
|
299
|
+
Object.fromEntries(
|
|
300
|
+
Object.entries(scriptsOf(meta)).filter(
|
|
301
|
+
([n, v]) => typeof v === 'string' && !memberTasks.has(n) && !RUNS_MEMBERS.test(` ${v}`),
|
|
302
|
+
),
|
|
303
|
+
)
|
|
304
|
+
const rootMapped =
|
|
305
|
+
rootMeta === undefined
|
|
306
|
+
? 0
|
|
307
|
+
: Object.keys(rootScripts(rootMeta)).filter((n) => !LIFECYCLE.test(n)).length
|
|
265
308
|
const outsideScripts = outside?.['scripts']
|
|
266
309
|
const outsideRuns =
|
|
267
310
|
typeof outsideScripts === 'object' &&
|
|
@@ -274,7 +317,11 @@ export function migrateScripts(
|
|
|
274
317
|
? outside['name']
|
|
275
318
|
: 'package.json'
|
|
276
319
|
: undefined)
|
|
277
|
-
if (rootName !== undefined) {
|
|
320
|
+
if (rootName !== undefined && rootMapped > 0) {
|
|
321
|
+
notes.push(
|
|
322
|
+
`${rootName} (the workspace root): its scripts that check the whole repo are its tasks; those that run the members (\`pnpm -r\`, \`--filter\`, a runner) or share a member's task name are left out`,
|
|
323
|
+
)
|
|
324
|
+
} else if (rootName !== undefined) {
|
|
278
325
|
// A nameless root's vx.config is skipped (vx names projects by their
|
|
279
326
|
// manifest's name), so the hand-written one needs a name first (vuejs/core).
|
|
280
327
|
notes.push(
|
|
@@ -282,9 +329,10 @@ export function migrateScripts(
|
|
|
282
329
|
(rootName === 'package.json' ? ', after giving its package.json a "name"' : ''),
|
|
283
330
|
)
|
|
284
331
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
332
|
+
const mapped = metas.filter((m) => m !== root)
|
|
333
|
+
if (rootMeta !== undefined && rootMapped > 0) mapped.push(rootMeta)
|
|
334
|
+
for (const meta of mapped) {
|
|
335
|
+
const scripts = meta === rootMeta ? rootScripts(meta) : scriptsOf(meta)
|
|
288
336
|
const hooksBy = runsScriptHooks(meta.dir, hookMemo)
|
|
289
337
|
const runsHooks = hooksBy !== null
|
|
290
338
|
const runnable = Object.keys(scripts).filter(
|