@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 +5 -5
- package/src/cli/show.ts +5 -0
- package/src/cli/why.ts +12 -1
- package/src/graph/task-graph.ts +5 -0
- package/src/orchestrator/projects.ts +37 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
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.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)
|
package/src/graph/task-graph.ts
CHANGED
|
@@ -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
|
-
|
|
252
|
-
|
|
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[] = []
|