@vzn/vx 0.0.525 → 0.0.527
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.
|
|
3
|
+
"version": "0.0.527",
|
|
4
4
|
"description": "The fastest task runner and build cache for JavaScript monorepos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"node": ">=18"
|
|
22
22
|
},
|
|
23
23
|
"optionalDependencies": {
|
|
24
|
-
"@vzn/vx-linux-x64": "0.0.
|
|
25
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
26
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
27
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
24
|
+
"@vzn/vx-linux-x64": "0.0.527",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.527",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.527",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.527"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/exec/runner.ts
CHANGED
|
@@ -158,13 +158,20 @@ function trailingCommentStart(command: string): number {
|
|
|
158
158
|
/**
|
|
159
159
|
* The command a task runs with the args after `--` appended, shell-quoted.
|
|
160
160
|
* They go before a trailing comment: appended after it, `echo args: # show`
|
|
161
|
-
* ran without them and said nothing (item 1060).
|
|
161
|
+
* ran without them and said nothing (item 1060). Trailing blanks go first.
|
|
162
162
|
*/
|
|
163
163
|
export function withForwardArgs(command: string, args: readonly string[] | undefined): string {
|
|
164
164
|
if (!args || args.length === 0) return command
|
|
165
165
|
const quoted = args.map(shellQuote).join(' ')
|
|
166
166
|
let comment = trailingCommentStart(command)
|
|
167
|
-
if (comment < 0)
|
|
167
|
+
if (comment < 0) {
|
|
168
|
+
// A command that ends in a newline (a template literal's closing line)
|
|
169
|
+
// ran the args as a command of their own: `--watch: not found`. Kept
|
|
170
|
+
// when an odd run of backslashes ends it: that escapes what follows.
|
|
171
|
+
const body = command.trimEnd()
|
|
172
|
+
const escaped = (/\\+$/.exec(body)?.[0].length ?? 0) % 2 === 1
|
|
173
|
+
return `${escaped ? command : body} ${quoted}`
|
|
174
|
+
}
|
|
168
175
|
// A comment-only last line after a commented line: the args go before
|
|
169
176
|
// the earliest comment, or they land inside it (`echo one # c\n# two`).
|
|
170
177
|
for (let c = comment; c >= 0; c = trailingCommentStart(command.slice(0, c).trimEnd())) comment = c
|
|
@@ -169,17 +169,21 @@ export async function applyProjectHooks(
|
|
|
169
169
|
* deleting each other's outputs). A violation is reported
|
|
170
170
|
* against the LAST plugin that ran: usually the one whose edit made it so,
|
|
171
171
|
* but an earlier plugin's edit that a later one left in place is blamed on
|
|
172
|
-
* the later one, since
|
|
172
|
+
* the later one, since the structure is not checked between plugins.
|
|
173
|
+
* Each node's task config is (`afterEach`), like the `project` stage's.
|
|
173
174
|
*/
|
|
174
175
|
export async function applyGraphHooks(
|
|
175
176
|
plugins: readonly VxPlugin[],
|
|
176
177
|
nodes: Map<string, TaskNode>,
|
|
177
178
|
ctx: GraphHookContext,
|
|
179
|
+
/** Runs after EACH plugin's edit, so a refused task config names the plugin that wrote it. */
|
|
180
|
+
afterEach?: (plugin: VxPlugin) => void,
|
|
178
181
|
): Promise<void> {
|
|
179
182
|
let last: VxPlugin | undefined
|
|
180
183
|
for (const plugin of plugins) {
|
|
181
184
|
if (plugin.graph === undefined) continue
|
|
182
185
|
await safe(plugin, 'graph', () => plugin.graph!(nodes, ctx))
|
|
186
|
+
afterEach?.(plugin)
|
|
183
187
|
last = plugin
|
|
184
188
|
}
|
|
185
189
|
if (last === undefined) return
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
resolveCacheDir,
|
|
40
40
|
resolveStoreRoot,
|
|
41
41
|
type ProjectEntry,
|
|
42
|
+
validateProjectConfig,
|
|
42
43
|
} from '../workspace/index.js'
|
|
43
44
|
import {
|
|
44
45
|
buildTaskGraph,
|
|
@@ -61,6 +62,7 @@ import {
|
|
|
61
62
|
discoverProjects,
|
|
62
63
|
gitOfDiscovery,
|
|
63
64
|
graphOfDiscovery,
|
|
65
|
+
isDefaultBuild,
|
|
64
66
|
loadProjects,
|
|
65
67
|
loadWorkspacePlugins,
|
|
66
68
|
type LoadedProjects,
|
|
@@ -597,12 +599,30 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
597
599
|
)
|
|
598
600
|
mark('git enumeration')
|
|
599
601
|
if (hasHook(plugins, 'graph')) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
602
|
+
// A hook edits each node's config in place, as `project` does, so the
|
|
603
|
+
// edit is held to what the loader accepts from a user: a misspelled
|
|
604
|
+
// `exec` field ran as an empty command and failed with no reason.
|
|
605
|
+
const configPaths = new Map(projectMetas.map((m) => [m.name, m.configPath]))
|
|
606
|
+
await applyGraphHooks(
|
|
607
|
+
plugins,
|
|
608
|
+
nodes,
|
|
609
|
+
{
|
|
610
|
+
workspaceRoot,
|
|
611
|
+
cacheDir,
|
|
612
|
+
warn: (m) => log.status(m),
|
|
613
|
+
requested: [...nodes.values()].filter((n) => n.requested).map((n) => n.id),
|
|
614
|
+
},
|
|
615
|
+
(plugin) => {
|
|
616
|
+
for (const n of nodes.values()) {
|
|
617
|
+
if (isDefaultBuild(n.config)) continue
|
|
618
|
+
const where = configPaths.get(n.projectName) ?? `${n.projectName} (no config file)`
|
|
619
|
+
validateProjectConfig(
|
|
620
|
+
{ tasks: { [n.taskName]: n.config } },
|
|
621
|
+
`${where} (after plugin '${plugin.name}')`,
|
|
622
|
+
)
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
)
|
|
606
626
|
}
|
|
607
627
|
if (hasHook(plugins, 'key')) {
|
|
608
628
|
await applyKeyHooks(plugins, nodes, { workspaceRoot, cacheDir, warn: (m) => log.status(m) })
|
|
@@ -74,7 +74,9 @@ const decoder = new TextDecoder()
|
|
|
74
74
|
* `^task` edges vanish, upstream hashes drop out of the cache key (stale
|
|
75
75
|
* hits), and a second cache dir appears under the member. Claiming is decided
|
|
76
76
|
* with the same globs `loadWorkspace` applies, so "the root that claims me"
|
|
77
|
-
* and "the root that lists me as a project" cannot diverge.
|
|
77
|
+
* and "the root that lists me as a project" cannot diverge. An outer root
|
|
78
|
+
* that lists both the claimer and the claimed member outranks the claimer:
|
|
79
|
+
* from the claimer's own directory the walk reaches the outer root too.
|
|
78
80
|
*
|
|
79
81
|
* When no candidate claims `start` — a standalone package, or a subdirectory
|
|
80
82
|
* of a single-project repo — the nearest candidate wins (the root itself IS
|
|
@@ -92,6 +94,9 @@ export async function findWorkspaceRoot(
|
|
|
92
94
|
// The nearest manifest with workspace globs of its own, once passed: an
|
|
93
95
|
// outer root claims the tree only by listing this directory itself.
|
|
94
96
|
let inner: string | null = null
|
|
97
|
+
// The root that claims `start`, and the members it claimed it through.
|
|
98
|
+
let claimed: string | null = null
|
|
99
|
+
let members: string[] = []
|
|
95
100
|
while (true) {
|
|
96
101
|
let globs: string[] | null
|
|
97
102
|
try {
|
|
@@ -105,12 +110,23 @@ export async function findWorkspaceRoot(
|
|
|
105
110
|
}
|
|
106
111
|
if (globs !== null) {
|
|
107
112
|
nearest ??= dir
|
|
108
|
-
if (
|
|
113
|
+
if (claimed !== null) {
|
|
114
|
+
// An outer root that lists both the claimer and the member owns
|
|
115
|
+
// them: `packages/**` holding `packages/inner` (with `workspaces:
|
|
116
|
+
// ['sub']`) and `packages/inner/sub` resolved `sub` to `inner` and
|
|
117
|
+
// `inner` to the outer root, two roots and two keys for one tree.
|
|
118
|
+
if (claimsMember(dir, [claimed], globs) && claimsMember(dir, members, globs)) claimed = dir
|
|
119
|
+
} else if (claimsMember(dir, inner === null ? below : [inner], globs)) {
|
|
120
|
+
claimed = dir
|
|
121
|
+
members = inner === null ? [...below] : [inner]
|
|
122
|
+
}
|
|
109
123
|
// pnpm takes the nearest `pnpm-workspace.yaml` as the root, listed or
|
|
110
124
|
// not. Walking past it, `apps/inner` resolved to the outer workspace
|
|
111
125
|
// while `apps/inner/pkgs/x` resolved to the inner one: two roots and
|
|
112
126
|
// two caches for one tree (item 990). Already read: `reads` holds it.
|
|
113
|
-
if ((await readOnce(reads, path.join(dir, 'pnpm-workspace.yaml'))) !== null)
|
|
127
|
+
if ((await readOnce(reads, path.join(dir, 'pnpm-workspace.yaml'))) !== null) {
|
|
128
|
+
return claimed ?? dir
|
|
129
|
+
}
|
|
114
130
|
}
|
|
115
131
|
const parent = path.dirname(dir)
|
|
116
132
|
if (parent === dir) break
|
|
@@ -126,6 +142,7 @@ export async function findWorkspaceRoot(
|
|
|
126
142
|
if (inner === null && globs !== null && globs.length > 0) inner = dir
|
|
127
143
|
dir = parent
|
|
128
144
|
}
|
|
145
|
+
if (claimed !== null) return claimed
|
|
129
146
|
if (nearest !== null) return nearest
|
|
130
147
|
throw new UserError(
|
|
131
148
|
`Could not find a workspace root in any parent of ${start} ` +
|