@vzn/vx 0.0.27 → 0.0.28
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.28",
|
|
4
4
|
"description": "An open, extensible monorepo task runner.",
|
|
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.28",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.28",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.28",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.28"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/exec/executor.ts
CHANGED
|
@@ -254,15 +254,26 @@ export function assertExecuteResult(
|
|
|
254
254
|
* Place one task: the first executor, in declaration order, that may take
|
|
255
255
|
* it — a `remote` executor is skipped for a `pinnedLocal` task, then
|
|
256
256
|
* `accepts()` decides. Decided once per task before scheduling. The local
|
|
257
|
-
* executor accepts everything, so with it declared
|
|
257
|
+
* executor accepts everything, so with it declared the only throw is an
|
|
258
|
+
* `accepts()` that threw: refused as `label` names that executor, where it
|
|
259
|
+
* surfaced as a bare stack naming no plugin (item 1022).
|
|
258
260
|
*/
|
|
259
261
|
export function selectExecutor(
|
|
260
262
|
executors: readonly TaskExecutor[],
|
|
261
263
|
task: TaskPlacement,
|
|
264
|
+
label: (executor: TaskExecutor) => string = (e) => `executor '${e.name}'`,
|
|
262
265
|
): TaskExecutor {
|
|
263
266
|
for (const executor of executors) {
|
|
264
267
|
if (task.pinnedLocal && executor.remote === true) continue
|
|
265
|
-
if (executor.accepts === undefined
|
|
268
|
+
if (executor.accepts === undefined) return executor
|
|
269
|
+
let taken: boolean
|
|
270
|
+
try {
|
|
271
|
+
taken = executor.accepts(task)
|
|
272
|
+
} catch (err) {
|
|
273
|
+
const m = err instanceof Error ? err.message : String(err)
|
|
274
|
+
throw new UserError(`${label(executor)} failed in accepts for ${task.taskId}: ${m}`)
|
|
275
|
+
}
|
|
276
|
+
if (taken) return executor
|
|
266
277
|
}
|
|
267
278
|
throw new Error(
|
|
268
279
|
`no executor accepted ${task.taskId} (declared: ${executors.map((e) => e.name).join(', ')}). Core's local executor accepts every task, so this means it is missing from the list.`,
|
|
@@ -10,7 +10,7 @@ import { selectExecutor, type TaskExecutor } from '../exec/index.js'
|
|
|
10
10
|
import { isGroupTask, type TaskNode } from '../graph/index.js'
|
|
11
11
|
import { resolveDownloadModes } from './download-policy.js'
|
|
12
12
|
import type { Logger } from './logger.js'
|
|
13
|
-
import { resolveExecutors } from './plugin-host.js'
|
|
13
|
+
import { executorLabel, resolveExecutors } from './plugin-host.js'
|
|
14
14
|
import type { prepareRun } from './prepare.js'
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -100,14 +100,18 @@ export function placeTasks(
|
|
|
100
100
|
}
|
|
101
101
|
for (const node of nodes.values()) {
|
|
102
102
|
if (isGroupTask(node) || node.config.exec?.persistent !== undefined) continue
|
|
103
|
-
const executor = selectExecutor(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
103
|
+
const executor = selectExecutor(
|
|
104
|
+
executors,
|
|
105
|
+
{
|
|
106
|
+
taskId: node.id,
|
|
107
|
+
projectName: node.projectName,
|
|
108
|
+
projectDir: node.projectDir,
|
|
109
|
+
command: node.config.exec!.command,
|
|
110
|
+
pinnedLocal: pinned.has(node.id),
|
|
111
|
+
cacheable: node.config.cache !== undefined,
|
|
112
|
+
},
|
|
113
|
+
executorLabel,
|
|
114
|
+
)
|
|
111
115
|
placements.executors.set(node.id, executor)
|
|
112
116
|
if (node.config.exec?.remote === 'only') {
|
|
113
117
|
// A pinned 'only' task (it transitively depends on a persistent one)
|
|
@@ -364,6 +364,17 @@ export async function resolveCache(
|
|
|
364
364
|
})
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
/** The plugin each resolved executor came from, so a hook of it that throws is named. */
|
|
368
|
+
const executorPlugin = new WeakMap<TaskExecutor, string>()
|
|
369
|
+
|
|
370
|
+
/** How a message names `executor`: its plugin and its own name, or its name alone (the floor). */
|
|
371
|
+
export function executorLabel(executor: TaskExecutor): string {
|
|
372
|
+
const plugin = executorPlugin.get(executor)
|
|
373
|
+
return plugin === undefined
|
|
374
|
+
? `executor '${executor.name}'`
|
|
375
|
+
: `plugin '${plugin}' (executor '${executor.name}')`
|
|
376
|
+
}
|
|
377
|
+
|
|
367
378
|
/**
|
|
368
379
|
* Collect every plugin's `executor`, in declaration order, with core's
|
|
369
380
|
* own appended at the tail: per task, `selectExecutor` takes the first
|
|
@@ -383,6 +394,7 @@ export async function resolveExecutors(
|
|
|
383
394
|
if (typeof executor.name !== 'string' || executor.name.length === 0) {
|
|
384
395
|
throw new UserError(`plugin '${plugin.name}' returned an executor with no name`)
|
|
385
396
|
}
|
|
397
|
+
executorPlugin.set(executor, plugin.name)
|
|
386
398
|
executors.push(executor)
|
|
387
399
|
}
|
|
388
400
|
// Core's own executor is the TAIL of every list, so a plugin executor
|
package/src/orchestrator/run.ts
CHANGED
|
@@ -40,7 +40,7 @@ import { excludedTaint } from './excluded-keys.js'
|
|
|
40
40
|
import { keyUpstream } from './upstream.js'
|
|
41
41
|
import { busLogger, createEventBus, terminalSubscriber, type EventBus } from './events.js'
|
|
42
42
|
import { installPlugins, PluginSetupError } from './plugin.js'
|
|
43
|
-
import { buildAdmission, resolveExecutors, teardownPlugins } from './plugin-host.js'
|
|
43
|
+
import { buildAdmission, executorLabel, resolveExecutors, teardownPlugins } from './plugin-host.js'
|
|
44
44
|
import { subscribeTelemetry, type TelemetryHandle } from './telemetry-host.js'
|
|
45
45
|
import { assembleRunSummary, isPassStatus } from './telemetry.js'
|
|
46
46
|
import type { RunContextRecord } from './telemetry.js'
|
|
@@ -364,16 +364,26 @@ async function runOnBus(
|
|
|
364
364
|
remaining.add(id)
|
|
365
365
|
}
|
|
366
366
|
// The SAME set is handed back every time, narrowed in place: a consumer
|
|
367
|
-
// reading it later sees the truth rather than a stale snapshot.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
367
|
+
// reading it later sees the truth rather than a stale snapshot. A hint,
|
|
368
|
+
// as `admit` is: one that throws is named once and asked no more, where
|
|
369
|
+
// it surfaced as a bare stack, or mid-run from the completion path
|
|
370
|
+
// (item 1022).
|
|
371
|
+
const tellDemand = (executor: TaskExecutor, remaining: ReadonlySet<string>): void => {
|
|
372
|
+
try {
|
|
373
|
+
executor.demand!(remaining)
|
|
374
|
+
} catch (err) {
|
|
375
|
+
demandOf.delete(executor)
|
|
376
|
+
const m = err instanceof Error ? err.message : String(err)
|
|
377
|
+
log.status(`[vx] ${executorLabel(executor)} failed in demand: ${m}; not asked again this run`)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
for (const [executor, remaining] of demandOf) tellDemand(executor, remaining)
|
|
371
381
|
const narrowDemand =
|
|
372
382
|
demandOf.size === 0
|
|
373
383
|
? (): void => {}
|
|
374
384
|
: (id: string): void => {
|
|
375
385
|
for (const [executor, remaining] of demandOf) {
|
|
376
|
-
if (remaining.delete(id)) executor
|
|
386
|
+
if (remaining.delete(id)) tellDemand(executor, remaining)
|
|
377
387
|
}
|
|
378
388
|
}
|
|
379
389
|
|