@vzn/vx 0.0.276 → 0.0.278

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.276",
3
+ "version": "0.0.278",
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.276",
21
- "@vzn/vx-linux-arm64": "0.0.276",
22
- "@vzn/vx-darwin-x64": "0.0.276",
23
- "@vzn/vx-darwin-arm64": "0.0.276"
20
+ "@vzn/vx-linux-x64": "0.0.278",
21
+ "@vzn/vx-linux-arm64": "0.0.278",
22
+ "@vzn/vx-darwin-x64": "0.0.278",
23
+ "@vzn/vx-darwin-arm64": "0.0.278"
24
24
  },
25
25
  "dependencies": {
26
26
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
package/src/cli/show.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  // <task>`: one task. `<task>`: that task in every project declaring it.
7
7
  // Deliberately NOT the lock — vx-lock.json is already the frozen JSON.
8
8
 
9
+ import path from 'node:path'
9
10
  import type { ProjectConfig, TaskConfig } from '../config.js'
10
11
  import { declaredTask } from '../graph/index.js'
11
12
  import { flagHint, seeHelp } from './help.js'
@@ -65,6 +66,10 @@ export async function showCmd(args: readonly string[]): Promise<number> {
65
66
  const metas = await discoverCliProjects(await loadWorkspace(root, reads))
66
67
  const byName = new Map(metas.map((m) => [m.name, m]))
67
68
 
69
+ // `//#task` is Turbo's root package's task: the root project here (D-39).
70
+ const rootMeta = metas.find((m) => path.resolve(m.dir) === path.resolve(root))
71
+ if (parsed.target?.startsWith('//#') && rootMeta !== undefined)
72
+ parsed.target = `${rootMeta.name}#${parsed.target.slice(3)}`
68
73
  const hashAt = parsed.target?.indexOf('#') ?? -1
69
74
  const projectName =
70
75
  parsed.target === undefined
package/src/cli/why.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  // names the exact cache-key components that differ. Read-only over cache.db —
6
6
  // no config evaluation, no re-hash.
7
7
 
8
+ import path from 'node:path'
8
9
  import { Cache, noteSchemaReset } from '../cache/index.js'
9
10
  import { flagHint, seeHelp } from './help.js'
10
11
  import { splitTaskId } from '../graph/index.js'
@@ -159,6 +160,16 @@ function resolveTarget(cache: Cache, target: string): string {
159
160
 
160
161
  const fmtWhen = (ms: number): string => new Date(ms).toISOString()
161
162
 
163
+ /** `//#task`, Turbo's root package, as the root project's id: its package.json name (D-39). */
164
+ async function rootSpelled(root: string, target: string): Promise<string> {
165
+ if (!target.startsWith('//#')) return target
166
+ const name = await Bun.file(path.join(root, 'package.json'))
167
+ .json()
168
+ .then((j: { name?: unknown }) => j.name)
169
+ .catch(() => undefined)
170
+ return typeof name === 'string' && name !== '' ? `${name}#${target.slice(3)}` : target
171
+ }
172
+
162
173
  export async function whyCmd(args: readonly string[]): Promise<number> {
163
174
  const parsed = parseWhyArgs(args)
164
175
  if (parsed.error !== undefined) throw new UserError(`vx why: ${parsed.error}`)
@@ -171,7 +182,7 @@ export async function whyCmd(args: readonly string[]): Promise<number> {
171
182
  noteSchemaReset(cache, warnToStderr)
172
183
  try {
173
184
  const db = cache.dbHandle()
174
- const taskId = resolveTarget(cache, parsed.target)
185
+ const taskId = resolveTarget(cache, await rootSpelled(root, parsed.target))
175
186
  const runId =
176
187
  parsed.runId !== undefined
177
188
  ? (resolveRunId(db, parsed.runId, 'vx why') ?? parsed.runId)
@@ -479,6 +479,11 @@ export function buildTaskGraph(options: BuildGraphOptions): Map<string, TaskNode
479
479
  // Cross-project edge: pkg#task. Missing target is a hard error
480
480
  // because the user named the package + task explicitly.
481
481
  if (!visit(frame, spec.project, spec.task, false)) {
482
+ // The loader spells `//` as the root project's name when there is one.
483
+ if (spec.project === '//')
484
+ throw new UserError(
485
+ `Task ${id} depends on //#${spec.task}, Turbo's root package, but the workspace root is no project here: give it a package.json name and a vx.config with that task`,
486
+ )
482
487
  throw new UserError(
483
488
  `Task ${id} depends on ${taskId(spec.project, spec.task)} but no such project or task is declared`,
484
489
  )
@@ -4,6 +4,7 @@
4
4
  // plugin `project` stage, which can give tasks to a package that never
5
5
  // wrote a `vx.config.ts`.
6
6
 
7
+ import path from 'node:path'
7
8
  import type { ProjectConfig } from '../config.js'
8
9
  import {
9
10
  frozenProjectConfig,
@@ -198,6 +199,12 @@ export async function loadProjects(args: LoadProjectsArgs): Promise<LoadedProjec
198
199
  // deps by design), so its config has to be pulled in — and that config
199
200
  // may declare cross edges of its own. The common case (no cross deps)
200
201
  // is a single round, identical to loading the closure in one batch.
202
+ // Turbo spells the root package `//` (`dependsOn: ['//#lint']`); vx names
203
+ // the root project by its package.json name, so the spelling is rewritten
204
+ // to it once, here, for the closure, the graph and `cache.inputs.tasks`
205
+ // alike. With no root project it stays, and the graph's refusal says so.
206
+ const root = path.resolve(workspaceRoot)
207
+ const rootName = args.projectMetas.find((m) => path.resolve(m.dir) === root)?.name
201
208
  const projects = new Map<string, ProjectEntry>()
202
209
  while (pending.length > 0) {
203
210
  const round = pending.splice(0, pending.length)
@@ -248,8 +255,9 @@ export async function loadProjects(args: LoadProjectsArgs): Promise<LoadedProjec
248
255
  (plugin) => validateProjectConfig(config, `${where} (after plugin '${plugin.name}')`),
249
256
  )
250
257
  }
251
- projects.set(meta.name, { name: meta.name, dir: meta.dir, config })
252
- for (const name of crossDepProjects(config)) {
258
+ const named = rootName === undefined ? config : rootSpelled(config, rootName)
259
+ projects.set(meta.name, { name: meta.name, dir: meta.dir, config: named })
260
+ for (const name of crossDepProjects(named)) {
253
261
  if (args.closure) considerWithDeps(name)
254
262
  else consider(name)
255
263
  }
@@ -258,6 +266,33 @@ export async function loadProjects(args: LoadProjectsArgs): Promise<LoadedProjec
258
266
  return { projects, configured }
259
267
  }
260
268
 
269
+ /**
270
+ * `config` with Turbo's `//#task` (in `dependsOn` and `cache.inputs.tasks`)
271
+ * spelled `<root>#task`; the same object when it holds none. A copy, not
272
+ * an edit: a loaded config may be a cached or a frozen one.
273
+ */
274
+ function rootSpelled(config: ProjectConfig, rootName: string): ProjectConfig {
275
+ const fix = (list: readonly string[] | undefined): readonly string[] | undefined =>
276
+ list?.some((e) => e.startsWith('//#') || e.startsWith('!//#'))
277
+ ? list.map((e) => e.replace(/^(!?)\/\/#/, `$1${rootName}#`))
278
+ : list
279
+ let tasks: NonNullable<ProjectConfig['tasks']> | undefined
280
+ for (const [name, task] of Object.entries(config.tasks ?? {})) {
281
+ const dependsOn = fix(task.dependsOn)
282
+ const inputTasks = fix(task.cache?.inputs?.tasks)
283
+ if (dependsOn === task.dependsOn && inputTasks === task.cache?.inputs?.tasks) continue
284
+ tasks ??= { ...config.tasks }
285
+ tasks[name] = {
286
+ ...task,
287
+ ...(dependsOn !== undefined ? { dependsOn: [...dependsOn] } : {}),
288
+ ...(task.cache?.inputs !== undefined && inputTasks !== undefined
289
+ ? { cache: { ...task.cache, inputs: { ...task.cache.inputs, tasks: [...inputTasks] } } }
290
+ : {}),
291
+ }
292
+ }
293
+ return tasks === undefined ? config : { ...config, tasks }
294
+ }
295
+
261
296
  /** Project names named by a `pkg#task` dependsOn entry anywhere in `config`. */
262
297
  function crossDepProjects(config: ProjectConfig): string[] {
263
298
  const out: string[] = []