@vzn/vx 0.0.286 → 0.0.288

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.286",
3
+ "version": "0.0.288",
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.286",
21
- "@vzn/vx-linux-arm64": "0.0.286",
22
- "@vzn/vx-darwin-x64": "0.0.286",
23
- "@vzn/vx-darwin-arm64": "0.0.286"
20
+ "@vzn/vx-linux-x64": "0.0.288",
21
+ "@vzn/vx-linux-arm64": "0.0.288",
22
+ "@vzn/vx-darwin-x64": "0.0.288",
23
+ "@vzn/vx-darwin-arm64": "0.0.288"
24
24
  },
25
25
  "dependencies": {
26
26
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
package/src/cli/init.ts CHANGED
@@ -99,7 +99,10 @@ export async function initCmd(args: readonly string[]): Promise<number> {
99
99
  return applyMigration({
100
100
  root,
101
101
  metas,
102
- plan: migrateScripts(metas),
102
+ plan: migrateScripts(
103
+ metas,
104
+ metas.some((m) => m.dir === root) ? undefined : await readRootManifest(root),
105
+ ),
103
106
  source: 'package.json scripts',
104
107
  verb: 'vx init',
105
108
  dry: parsed.dry,
@@ -343,3 +346,13 @@ function firstTask(file: string, runner: 'turbo' | 'nx'): string {
343
346
  const first = new RegExp(`"${field}"\\s*:\\s*\\{\\s*"([^"]+)"`).exec(text)?.[1]
344
347
  return first ?? 'build'
345
348
  }
349
+
350
+ /** The root package.json as an object, `{}` when unreadable or not one. */
351
+ async function readRootManifest(root: string): Promise<Record<string, unknown>> {
352
+ const pj: unknown = await Bun.file(path.join(root, 'package.json'))
353
+ .json()
354
+ .catch(() => ({}))
355
+ return typeof pj === 'object' && pj !== null && !Array.isArray(pj)
356
+ ? (pj as Record<string, unknown>)
357
+ : {}
358
+ }
@@ -242,7 +242,15 @@ function upstreamBuildOnWorker(tasks: GeneratedTask[]): void {
242
242
  if (!at.todos.includes(todo)) at.todos.push(todo)
243
243
  }
244
244
 
245
- export function migrateScripts(metas: readonly ProjectMeta[]): MigrationPlan {
245
+ /**
246
+ * `outside`: the root manifest when the root is no member (pnpm's and
247
+ * Yarn's default), whose own scripts (`lint: eslint .`) went unmapped
248
+ * without a word.
249
+ */
250
+ export function migrateScripts(
251
+ metas: readonly ProjectMeta[],
252
+ outside?: Readonly<Record<string, unknown>>,
253
+ ): MigrationPlan {
246
254
  const projects: GeneratedProject[] = []
247
255
  const hookMemo = new Map<string, string | null>()
248
256
  // The workspace root among members: its scripts run the workspace
@@ -252,9 +260,21 @@ export function migrateScripts(metas: readonly ProjectMeta[]): MigrationPlan {
252
260
  // replaced it so (D-45). A lone package is its repo's project and maps.
253
261
  const root = metas.length > 1 ? workspaceRootOf(metas) : undefined
254
262
  const notes: string[] = []
255
- if (root !== undefined) {
263
+ const outsideScripts = outside?.['scripts']
264
+ const outsideRuns =
265
+ typeof outsideScripts === 'object' &&
266
+ outsideScripts !== null &&
267
+ Object.values(outsideScripts).some((v) => typeof v === 'string' && v !== '')
268
+ const rootName =
269
+ root?.name ??
270
+ (outsideRuns
271
+ ? typeof outside?.['name'] === 'string'
272
+ ? outside['name']
273
+ : 'package.json'
274
+ : undefined)
275
+ if (rootName !== undefined) {
256
276
  notes.push(
257
- `${root.name} (the workspace root) not mapped: its scripts run the workspace; declare its own tasks in its vx.config by hand`,
277
+ `${rootName} (the workspace root) not mapped: its scripts run the workspace; declare its own tasks in its vx.config by hand`,
258
278
  )
259
279
  }
260
280
  for (const meta of metas) {
@@ -558,8 +558,7 @@ export async function listProjects(workspace: Workspace): Promise<ProjectMeta[]>
558
558
  }),
559
559
  )
560
560
 
561
- const projects: ProjectMeta[] = []
562
- const seenName = new Map<string, string>()
561
+ const byName = new Map<string, NonNullable<(typeof loaded)[number]>[]>()
563
562
  for (const entry of loaded) {
564
563
  if (entry === null) continue
565
564
  const { dir, pkg, configPath } = entry
@@ -575,22 +574,41 @@ export async function listProjects(workspace: Workspace): Promise<ProjectMeta[]>
575
574
  }
576
575
  continue
577
576
  }
578
- const previous = seenName.get(pkg.name)
579
- if (previous) {
580
- // pnpm accepts two manifests of one name (sveltejs/kit's test apps,
581
- // the first-five-minutes walk, 2026-09-28); vx cannot, since a
582
- // project is addressed by it. Say which two, short, and the way on.
577
+ const group = byName.get(pkg.name)
578
+ if (group === undefined) byName.set(pkg.name, [entry])
579
+ else group.push(entry)
580
+ }
581
+ const projects: ProjectMeta[] = []
582
+ const shared: string[] = []
583
+ for (const [name, group] of byName) {
584
+ if (group.length === 1) {
585
+ const { dir, pkg, configPath } = group[0]!
586
+ projects.push({ name, dir, packageJson: pkg, configPath })
587
+ continue
588
+ }
589
+ const dirs = group.map((e) => relPosix(workspace.root, e.dir)).sort()
590
+ // pnpm accepts two manifests of one name (vite's playground, sveltejs/kit's
591
+ // test apps); vx cannot, since a project is addressed by it. Like a
592
+ // nameless one, a pair that declares no vx tasks is left out, so the
593
+ // rest of the workspace runs (vite was refused whole, 2026-10-01); one
594
+ // with a vx config was meant to run, and is refused with the way on.
595
+ if (group.some((e) => e.configPath !== null)) {
583
596
  throw new UserError(
584
- `Duplicate package name "${pkg.name}" in workspace: ${[previous, dir]
585
- .map((d) => relPosix(workspace.root, d))
586
- .sort()
587
- .join(' and ')}; ` +
597
+ `Duplicate package name "${name}" in workspace: ${dirs.join(' and ')}; ` +
588
598
  'vx names a project by its package name — rename one, or leave one out with a `!` pattern in the workspace globs',
589
599
  )
590
600
  }
591
- seenName.set(pkg.name, dir)
592
- projects.push({ name: pkg.name, dir, packageJson: pkg, configPath })
601
+ shared.push(`${name} (${dirs.join(', ')})`)
602
+ }
603
+ if (shared.length > 0) {
604
+ shared.sort()
605
+ const shown =
606
+ shared.slice(0, 2).join('; ') + (shared.length > 2 ? `; and ${shared.length - 2} more` : '')
607
+ process.stderr.write(
608
+ `vx: left out, as no project can be addressed by a name several manifests share (none has a vx config): ${shown}\n`,
609
+ )
593
610
  }
611
+
594
612
  // Code-unit order, not `localeCompare`: ICU collation cost 28 ms of a
595
613
  // 300 ms warm run at 1000 projects, and a stable deterministic order is
596
614
  // all any consumer needs.