@vzn/vx 0.0.646 → 0.0.649
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/README.md +29 -22
- package/launcher.cjs +9 -2
- package/launcher.sh +2 -2
- package/package.json +5 -5
- package/schemas/cache.json +5 -5
- package/schemas/error.json +1 -1
- package/schemas/info.json +16 -2
- package/schemas/last.json +112 -5
- package/schemas/plan.json +28 -2
- package/schemas/show.json +10 -1
- package/skills/vx/SKILL.md +18 -2
- package/src/bin.ts +53 -12
- package/src/cache/archive.ts +324 -23
- package/src/cache/cache.ts +268 -53
- package/src/cache/git-inputs.ts +219 -87
- package/src/cache/index.ts +3 -2
- package/src/cache/inputs.ts +460 -78
- package/src/cache/key-fold.ts +15 -6
- package/src/cache/layer.ts +49 -9
- package/src/cache/layered-cache.ts +123 -58
- package/src/cache/linked-inputs.ts +1 -1
- package/src/cache/output-index.ts +19 -3
- package/src/cache/plugin-cache.ts +49 -23
- package/src/cache/remote-hits.ts +72 -0
- package/src/cache/remote-lane.ts +90 -14
- package/src/cache/run-history.ts +70 -23
- package/src/cache/run-store.ts +11 -10
- package/src/cache/schema.ts +25 -6
- package/src/cache/tar-stream.ts +5 -0
- package/src/cache/umask.ts +1 -2
- package/src/cli/cache.ts +58 -27
- package/src/cli/completions.ts +12 -5
- package/src/cli/docs.ts +42 -16
- package/src/cli/help.ts +58 -19
- package/src/cli/index.ts +79 -33
- package/src/cli/info.ts +12 -6
- package/src/cli/init.ts +186 -31
- package/src/cli/last.ts +439 -45
- package/src/cli/lock.ts +197 -52
- package/src/cli/plan-format.ts +91 -49
- package/src/cli/plugin-commands.ts +70 -14
- package/src/cli/plugin-templates.ts +13 -13
- package/src/cli/registry.ts +209 -0
- package/src/cli/run.ts +109 -34
- package/src/cli/select.ts +143 -46
- package/src/cli/show.ts +59 -16
- package/src/cli/task-verb.ts +16 -6
- package/src/cli/upgrade.ts +151 -49
- package/src/cli/watch-cycle.ts +10 -5
- package/src/cli/watch-filter.ts +71 -3
- package/src/cli/watch-fs.ts +22 -6
- package/src/cli/watch-judge.ts +48 -6
- package/src/cli/watch.ts +163 -42
- package/src/cli/why.ts +211 -73
- package/src/cli/workspace-config.ts +54 -15
- package/src/config.ts +13 -1
- package/src/exec/env.ts +7 -2
- package/src/exec/executor.ts +154 -10
- package/src/exec/index.ts +6 -2
- package/src/exec/kill-tree.ts +15 -0
- package/src/exec/local-executor.ts +1 -1
- package/src/exec/proc-sample.ts +3 -1
- package/src/exec/runner.ts +253 -54
- package/src/exec/sandbox-binds.ts +7 -3
- package/src/exec/sandbox-paths.ts +1 -1
- package/src/exec/sandbox-runtime.ts +211 -24
- package/src/exec/sandbox-violations.ts +52 -20
- package/src/graph/critical-path.ts +79 -0
- package/src/graph/default-build.ts +19 -0
- package/src/graph/definition.ts +38 -0
- package/src/graph/folds.ts +38 -5
- package/src/graph/index.ts +4 -1
- package/src/graph/priorities.ts +74 -8
- package/src/graph/scheduler.ts +270 -60
- package/src/graph/task-graph.ts +228 -35
- package/src/handoff.ts +97 -9
- package/src/history/failure-mode.ts +6 -3
- package/src/history/history.ts +167 -61
- package/src/history/index.ts +2 -0
- package/src/history/metrics.ts +90 -30
- package/src/index.ts +30 -13
- package/src/orchestrator/admission.ts +10 -6
- package/src/orchestrator/affected-tasks.ts +11 -6
- package/src/orchestrator/deferred-outputs.ts +2 -0
- package/src/orchestrator/doctor.ts +134 -42
- package/src/orchestrator/execute-args.ts +4 -1
- package/src/orchestrator/execute-task.ts +130 -38
- package/src/orchestrator/fingerprint-watch.ts +1 -1
- package/src/orchestrator/graph-memo.ts +140 -0
- package/src/orchestrator/graph-stage.ts +18 -1
- package/src/orchestrator/held-servers.ts +1 -1
- package/src/orchestrator/hit-restore.ts +217 -119
- package/src/orchestrator/index.ts +4 -3
- package/src/orchestrator/local-floor.ts +119 -25
- package/src/orchestrator/local-shortcircuit.ts +28 -4
- package/src/orchestrator/lockfile-claim.ts +96 -16
- package/src/orchestrator/miss-save.ts +42 -8
- package/src/orchestrator/options.ts +8 -6
- package/src/orchestrator/output-host.ts +43 -14
- package/src/orchestrator/output-log.ts +90 -10
- package/src/orchestrator/persistent-task.ts +84 -29
- package/src/orchestrator/persistent.ts +16 -4
- package/src/orchestrator/placement.ts +185 -17
- package/src/orchestrator/plan.ts +64 -7
- package/src/orchestrator/plugin-host.ts +214 -42
- package/src/orchestrator/plugin.ts +113 -37
- package/src/orchestrator/prepare.ts +168 -30
- package/src/orchestrator/projects.ts +65 -40
- package/src/orchestrator/remote-prefetch.ts +119 -25
- package/src/orchestrator/run-artifacts.ts +95 -32
- package/src/orchestrator/run-context.ts +39 -41
- package/src/orchestrator/run-failures.ts +65 -23
- package/src/orchestrator/run-id.ts +6 -3
- package/src/orchestrator/run-keys.ts +149 -13
- package/src/orchestrator/run-live.ts +294 -0
- package/src/orchestrator/run-lock.ts +13 -7
- package/src/orchestrator/run-records.ts +35 -29
- package/src/orchestrator/run-report.ts +3 -3
- package/src/orchestrator/run-steps.ts +58 -22
- package/src/orchestrator/run-stop.ts +22 -5
- package/src/orchestrator/run.ts +331 -88
- package/src/orchestrator/sandbox-fix.ts +7 -5
- package/src/orchestrator/sandbox-request.ts +70 -20
- package/src/orchestrator/shell-verdict.ts +5 -5
- package/src/orchestrator/signals.ts +26 -1
- package/src/orchestrator/stable-keys.ts +92 -8
- package/src/orchestrator/steps.ts +2 -2
- package/src/orchestrator/task-artifacts.ts +90 -26
- package/src/orchestrator/task-hash.ts +78 -13
- package/src/orchestrator/task-log-buffer.ts +72 -36
- package/src/orchestrator/task-process.ts +7 -1
- package/src/orchestrator/telemetry-host.ts +22 -2
- package/src/orchestrator/telemetry.ts +53 -21
- package/src/orchestrator/upstream.ts +15 -0
- package/src/report/events.ts +32 -13
- package/src/report/forecast.ts +31 -9
- package/src/report/framed-output.ts +141 -50
- package/src/report/index.ts +1 -0
- package/src/report/logger.ts +170 -179
- package/src/report/path-links.ts +12 -4
- package/src/report/plain-output.ts +13 -5
- package/src/report/status-line.ts +81 -53
- package/src/report/summary.ts +114 -32
- package/src/report/terminal-signals.ts +13 -2
- package/src/settings.ts +7 -6
- package/src/util/bun-version.ts +4 -1
- package/src/util/colors.ts +4 -2
- package/src/util/config-frame.ts +41 -4
- package/src/{graph → util}/dependency-spec.ts +22 -13
- package/src/util/edit-distance.ts +13 -1
- package/src/util/env-flag.ts +9 -0
- package/src/util/errors.ts +59 -3
- package/src/util/essential-env.ts +57 -0
- package/src/util/git-ignored.ts +1 -1
- package/src/util/hash.ts +35 -0
- package/src/util/hide-env.ts +45 -5
- package/src/util/index.ts +21 -5
- package/src/util/num.ts +8 -0
- package/src/util/paths.ts +91 -21
- package/src/util/report.ts +6 -1
- package/src/util/secret-mask.ts +62 -1
- package/src/util/secrets.ts +7 -2
- package/src/util/size.ts +11 -3
- package/src/util/tail.ts +6 -1
- package/src/util/timing.ts +13 -1
- package/src/util/ui.ts +61 -3
- package/src/util/which.ts +1 -1
- package/src/util/write-file.ts +67 -0
- package/src/workspace/affected.ts +710 -107
- package/src/workspace/bracket-literal.ts +84 -0
- package/src/workspace/canonical-dir.ts +37 -0
- package/src/workspace/config-bytecode.ts +17 -11
- package/src/workspace/config-eval.ts +29 -8
- package/src/workspace/config-imports.ts +199 -58
- package/src/workspace/config-schema.ts +242 -39
- package/src/workspace/filter.ts +37 -8
- package/src/workspace/index.ts +11 -1
- package/src/workspace/json-data.ts +3 -4
- package/src/workspace/lockfile.ts +57 -8
- package/src/workspace/migration.ts +69 -18
- package/src/workspace/package-graph.ts +5 -4
- package/src/workspace/project-loader.ts +226 -37
- package/src/workspace/repo-id.ts +1 -1
- package/src/workspace/workspace.ts +176 -33
|
@@ -20,7 +20,16 @@ import {
|
|
|
20
20
|
import { PLUGIN_PACKAGE, type ProjectConfig, type WorkspaceConfig } from '../config.js'
|
|
21
21
|
import type { TaskExecutor } from '../exec/index.js'
|
|
22
22
|
import type { TaskNode, TaskOutcome, TaskStatus } from '../graph/index.js'
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
errorCode,
|
|
25
|
+
maskedLine,
|
|
26
|
+
nearest,
|
|
27
|
+
type PluginSecrets,
|
|
28
|
+
SecretBroker,
|
|
29
|
+
thrownMessage,
|
|
30
|
+
ui,
|
|
31
|
+
UserError,
|
|
32
|
+
} from '../util/index.js'
|
|
24
33
|
import type { ProjectMeta } from '../workspace/index.js'
|
|
25
34
|
import type { EventBus, Logger, RunStartInfo } from '../report/index.js'
|
|
26
35
|
import type { CiContext } from './run-context.js'
|
|
@@ -60,7 +69,8 @@ export interface VxPlugin {
|
|
|
60
69
|
|
|
61
70
|
/**
|
|
62
71
|
* The workspace config, before anything is derived from it (concurrency,
|
|
63
|
-
* cacheDir, timeout, …). `plugins` is already fixed by the time this runs
|
|
72
|
+
* cacheDir, timeout, …). `plugins` is already fixed by the time this runs:
|
|
73
|
+
* an edit to it is refused.
|
|
64
74
|
* Core re-validates after each plugin, as it does a user's file.
|
|
65
75
|
* `ctx.setBranch` names what the CI knows of the branch: its cache scope
|
|
66
76
|
* and the commit it is compared with.
|
|
@@ -78,7 +88,7 @@ export interface VxPlugin {
|
|
|
78
88
|
* (with no config; named by `name`, else its path). A project named by
|
|
79
89
|
* its path takes `name`; one its config names keeps that name, and a
|
|
80
90
|
* different name from an earlier plugin is refused, as is a directory
|
|
81
|
-
* outside the root. Two projects of one name are refused once the stage
|
|
91
|
+
* outside the root and the name `//` away from it. Two projects of one name are refused once the stage
|
|
82
92
|
* ends. `deps` and `reach` from every plugin union then; a target that is
|
|
83
93
|
* no project is refused.
|
|
84
94
|
*/
|
|
@@ -93,7 +103,7 @@ export interface VxPlugin {
|
|
|
93
103
|
* applied on top of it live. A project with NO config file (a
|
|
94
104
|
* `discover` plugin added it) is loaded too, as `{ tasks: {} }`, so a
|
|
95
105
|
* plugin can give it tasks; with no `project` plugin such a project is
|
|
96
|
-
* never visited.
|
|
106
|
+
* never visited. Its `name` is fixed by then: a change is refused.
|
|
97
107
|
*/
|
|
98
108
|
project?(config: ProjectConfig, ctx: ProjectHookContext): void | Promise<void>
|
|
99
109
|
|
|
@@ -154,13 +164,13 @@ export interface VxPlugin {
|
|
|
154
164
|
* on this machine, with the tasks running here right now. Return `false`
|
|
155
165
|
* to hold it until something finishes (it is asked again then). Asked
|
|
156
166
|
* many times per run, so it must be cheap and synchronous; a throw, or
|
|
157
|
-
* an answer that is a Promise, is reported once and the plugin admits
|
|
167
|
+
* an answer that is a Promise or not a boolean or undefined, is reported once and the plugin admits
|
|
158
168
|
* from then on — a policy never
|
|
159
169
|
* breaks a run. Restore-tier hits, tasks on an executor pool and groups
|
|
160
170
|
* hold no local resources: never asked, never in `ctx.running`. When
|
|
161
171
|
* several plugins answer, all must admit. Core keeps no notion of what a task needs: a plugin that
|
|
162
172
|
* packs memory or CPU learns or declares the numbers itself
|
|
163
|
-
* (`@vzn/vx-
|
|
173
|
+
* (`@vzn/vx-history` packs what past executions used).
|
|
164
174
|
*/
|
|
165
175
|
admit?(task: TaskNode, ctx: AdmitContext): boolean
|
|
166
176
|
|
|
@@ -223,8 +233,10 @@ export interface VxPlugin {
|
|
|
223
233
|
* (the contract `run({ log })` takes), or `undefined` to leave core's
|
|
224
234
|
* renderer in place. The first plugin, in declaration order, that
|
|
225
235
|
* returns one renders the run; the rest are not asked. Not consulted
|
|
226
|
-
* when an embedder passed its own `log`. A logger that throws
|
|
227
|
-
* rest of the run back to core's renderer with one warning
|
|
236
|
+
* when an embedder passed its own `log`. A logger that throws or rejects
|
|
237
|
+
* hands the rest of the run back to core's renderer with one warning; a
|
|
238
|
+
* hook that does not answer within the teardown bound (3 s) is passed
|
|
239
|
+
* over with one.
|
|
228
240
|
*/
|
|
229
241
|
output?(ctx: OutputContext): Logger | undefined | Promise<Logger | undefined>
|
|
230
242
|
|
|
@@ -240,8 +252,10 @@ export interface VxPlugin {
|
|
|
240
252
|
setup?(ctx: PluginSetupContext): void | Promise<void>
|
|
241
253
|
|
|
242
254
|
/**
|
|
243
|
-
* Optional teardown at end-of-run (flush a sink, close a socket)
|
|
244
|
-
*
|
|
255
|
+
* Optional teardown at end-of-run (flush a sink, close a socket), in
|
|
256
|
+
* declaration order, a plugin with `cache` after every other: the
|
|
257
|
+
* others may still write through its remote. Errors are logged, never
|
|
258
|
+
* thrown. A run that ran to its end passes its
|
|
245
259
|
* `RunReport`; every other exit (a refused setup, a stop) passes none.
|
|
246
260
|
*/
|
|
247
261
|
teardown?(run?: RunReport): void | Promise<void>
|
|
@@ -358,17 +372,22 @@ export interface WorkspaceHookContext {
|
|
|
358
372
|
}
|
|
359
373
|
|
|
360
374
|
/**
|
|
361
|
-
* What a CI knows of the run's branch (
|
|
375
|
+
* What a CI knows of the run's branch (2026-10-17): `scope`, the
|
|
362
376
|
* cache scope (`'vznjs/vx:feat-x'`; `VX_CACHE_SCOPE` beats it, and it
|
|
363
377
|
* beats the one git names), and `base`, the commit the branch is compared
|
|
364
378
|
* with (a pull request's base), for a bare `--affected`. `trusted`: the
|
|
365
|
-
* run has no scope, whatever git names (a merge queue's run,
|
|
366
|
-
* 2026-10-18). The scope keys plugin entries only.
|
|
379
|
+
* run has no scope, whatever git names (a merge queue's run,
|
|
380
|
+
* 2026-10-18). The scope keys plugin entries only. `refs`: the refs a
|
|
381
|
+
* remote reads, in order (`CacheContext.refs`; 2026-10-25: a pull
|
|
382
|
+
* request its head branch, then its base, then the default branch; a
|
|
383
|
+
* merge queue or train only its base); unset, git's. A later plugin's
|
|
384
|
+
* field wins.
|
|
367
385
|
*/
|
|
368
386
|
export interface BranchFacts {
|
|
369
387
|
scope?: string
|
|
370
388
|
base?: string
|
|
371
389
|
trusted?: boolean
|
|
390
|
+
refs?: readonly string[]
|
|
372
391
|
}
|
|
373
392
|
|
|
374
393
|
export interface ConfigContext extends WorkspaceHookContext {
|
|
@@ -452,7 +471,7 @@ export interface FingerprintClaim {
|
|
|
452
471
|
* by `--affected` with the file's bytes at the base ref and in the
|
|
453
472
|
* working tree (`null` where it does not exist). Return `undefined` to
|
|
454
473
|
* select every project — the answer a claimant gives when it cannot
|
|
455
|
-
* tell.
|
|
474
|
+
* tell. A name that is not one of `ctx.projects` is refused.
|
|
456
475
|
*/
|
|
457
476
|
affected(
|
|
458
477
|
change: FingerprintChange,
|
|
@@ -464,9 +483,14 @@ export interface FingerprintClaim {
|
|
|
464
483
|
* path and hashes nowhere, or a `file:` dependency's directory. A change
|
|
465
484
|
* to one, or under one, asks `affected` about the file naming it, with
|
|
466
485
|
* `change.extras`. Without it, a `bun.lock` patch edit selects every
|
|
467
|
-
* project.
|
|
486
|
+
* project. Asked on every `--affected` diff, so a claimant that memoises
|
|
487
|
+
* its parse answers from `ctx.cacheDir`.
|
|
468
488
|
*/
|
|
469
|
-
extraFiles?(
|
|
489
|
+
extraFiles?(
|
|
490
|
+
file: string,
|
|
491
|
+
bytes: Uint8Array,
|
|
492
|
+
ctx: FingerprintContext,
|
|
493
|
+
): readonly string[] | Promise<readonly string[]>
|
|
470
494
|
}
|
|
471
495
|
|
|
472
496
|
export interface FingerprintChange {
|
|
@@ -507,14 +531,26 @@ export interface ScheduleHookContext extends BaseContext {
|
|
|
507
531
|
readonly localCache: Cache
|
|
508
532
|
/** The run's branch (`currentBranch`), the timings a forecast reads first: pass it to `LocalHistoryProvider`. */
|
|
509
533
|
readonly branch: string | null
|
|
534
|
+
/** The refs the run reads, in order (`CacheContext.refs`): a remote's timings are read by them too. */
|
|
535
|
+
readonly refs: readonly string[]
|
|
510
536
|
/** This plugin's entries in the workspace's cache, as `setup` has them. */
|
|
511
537
|
readonly cache: PluginCache
|
|
512
538
|
/**
|
|
513
539
|
* Replace this plugin's weights while the run goes on: the tasks not yet
|
|
514
540
|
* started re-rank at once. For weights that arrive late (timings fetched
|
|
515
541
|
* from a remote). Ignored in a plan and once the run's tasks are done.
|
|
542
|
+
* Given a promise, the weights it resolves to, if any, when it does:
|
|
543
|
+
* `refining()` waits for it meanwhile.
|
|
544
|
+
*/
|
|
545
|
+
refine(
|
|
546
|
+
weights: ReadonlyMap<string, number> | Promise<ReadonlyMap<string, number> | undefined>,
|
|
547
|
+
): void
|
|
548
|
+
/**
|
|
549
|
+
* Settles once every refine promised so far (a promise to `refine`) has
|
|
550
|
+
* landed or failed, its listeners called: a plugin that sizes the run by
|
|
551
|
+
* the fetched data waits for it here rather than guessing without it.
|
|
516
552
|
*/
|
|
517
|
-
|
|
553
|
+
refining(): Promise<void>
|
|
518
554
|
/**
|
|
519
555
|
* Calls `listener` each time another plugin `refine`s: the late data it
|
|
520
556
|
* waited for (timings fetched from a remote) has landed in history, so a
|
|
@@ -606,7 +642,7 @@ export interface PluginHookHandlers {
|
|
|
606
642
|
}
|
|
607
643
|
|
|
608
644
|
export interface Plugin {
|
|
609
|
-
/**
|
|
645
|
+
/** The plugin's package name (`definePlugin` sets it), logged on errors. */
|
|
610
646
|
readonly name: string
|
|
611
647
|
/**
|
|
612
648
|
* Called once at the start of every run. Register hooks via
|
|
@@ -635,10 +671,15 @@ export interface PluginOrigin {
|
|
|
635
671
|
|
|
636
672
|
const packageNameByDir = new Map<string, string>()
|
|
637
673
|
|
|
638
|
-
/** The name of the nearest `package.json` above `dir` — the one that owns it. */
|
|
674
|
+
/** The name of the nearest named `package.json` above `dir` — the one that owns it. */
|
|
639
675
|
function pluginPackageName(dir: string): string {
|
|
640
676
|
const memo = packageNameByDir.get(dir)
|
|
641
677
|
if (memo !== undefined) return memo
|
|
678
|
+
// A package.json with no `name` below the package root is no package:
|
|
679
|
+
// dual builds (tshy) write `{ "type": "module" }` into `dist/esm/`. The
|
|
680
|
+
// walk passes it, as Vite's package lookup does; with no named one
|
|
681
|
+
// above, the nearest is refused.
|
|
682
|
+
let nameless: string | undefined
|
|
642
683
|
for (let d = dir; ;) {
|
|
643
684
|
let text: string | undefined
|
|
644
685
|
try {
|
|
@@ -659,18 +700,22 @@ function pluginPackageName(dir: string): string {
|
|
|
659
700
|
)
|
|
660
701
|
}
|
|
661
702
|
const name = pkg?.name
|
|
662
|
-
if (
|
|
703
|
+
if (name === undefined) nameless ??= path.join(d, 'package.json')
|
|
704
|
+
else if (typeof name !== 'string' || name.length === 0) {
|
|
663
705
|
throw new UserError(
|
|
664
706
|
`definePlugin: ${path.join(d, 'package.json')} has no name — a plugin is a package, and its name is the package's`,
|
|
665
707
|
)
|
|
708
|
+
} else {
|
|
709
|
+
packageNameByDir.set(dir, name)
|
|
710
|
+
return name
|
|
666
711
|
}
|
|
667
|
-
packageNameByDir.set(dir, name)
|
|
668
|
-
return name
|
|
669
712
|
}
|
|
670
713
|
const parent = path.dirname(d)
|
|
671
714
|
if (parent === d) {
|
|
672
715
|
throw new UserError(
|
|
673
|
-
|
|
716
|
+
nameless === undefined
|
|
717
|
+
? `definePlugin: no package.json above ${dir} — a plugin is a package, and its name is the package's`
|
|
718
|
+
: `definePlugin: ${nameless} has no name — a plugin is a package, and its name is the package's`,
|
|
674
719
|
)
|
|
675
720
|
}
|
|
676
721
|
d = parent
|
|
@@ -680,10 +725,24 @@ function pluginPackageName(dir: string): string {
|
|
|
680
725
|
/**
|
|
681
726
|
* The one way to make a plugin: `definePlugin(import.meta, { ...hooks })`.
|
|
682
727
|
* The name is read from the package the calling module belongs to — the
|
|
683
|
-
* nearest `package.json` above it — and stamped where the workspace loader
|
|
728
|
+
* nearest `package.json` above it that has a name — and stamped where the workspace loader
|
|
684
729
|
* checks for it, so a plugin cannot be named anything but its package.
|
|
685
730
|
*/
|
|
686
731
|
export function definePlugin(origin: PluginOrigin, hooks: PluginHooks): VxPlugin {
|
|
732
|
+
// The hooks are copied by spread, which takes own fields only: a class
|
|
733
|
+
// instance's methods stayed on its prototype and the plugin loaded
|
|
734
|
+
// without them, saying nothing.
|
|
735
|
+
const proto: unknown =
|
|
736
|
+
hooks !== null && typeof hooks === 'object' ? Object.getPrototypeOf(hooks) : undefined
|
|
737
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
738
|
+
const got =
|
|
739
|
+
hooks === null || typeof hooks !== 'object'
|
|
740
|
+
? String(hooks)
|
|
741
|
+
: `a ${(hooks as object).constructor?.name || 'class instance'}`
|
|
742
|
+
throw new UserError(
|
|
743
|
+
`definePlugin: the hooks must be a plain object ({ setup() { … } }), got ${got}`,
|
|
744
|
+
)
|
|
745
|
+
}
|
|
687
746
|
if ('name' in hooks) {
|
|
688
747
|
throw new UserError(`definePlugin: a plugin's name is its package name — drop the 'name' field`)
|
|
689
748
|
}
|
|
@@ -705,9 +764,11 @@ type OptionKind<V> = [V] extends [string]
|
|
|
705
764
|
? 'boolean'
|
|
706
765
|
: [V] extends [(...args: never[]) => unknown]
|
|
707
766
|
? 'function'
|
|
708
|
-
: [V] extends [
|
|
709
|
-
? '
|
|
710
|
-
:
|
|
767
|
+
: [V] extends [readonly unknown[]]
|
|
768
|
+
? 'array'
|
|
769
|
+
: [V] extends [object]
|
|
770
|
+
? 'object'
|
|
771
|
+
: 'any'
|
|
711
772
|
|
|
712
773
|
/**
|
|
713
774
|
* Every option a factory takes, each with the one kind its type allows
|
|
@@ -739,7 +800,8 @@ export function refuseUnknownOptions<T>(
|
|
|
739
800
|
const known = kinds as Readonly<Record<string, string>>
|
|
740
801
|
const names = Object.keys(known)
|
|
741
802
|
for (const [key, value] of Object.entries(options)) {
|
|
742
|
-
|
|
803
|
+
// Own keys only: the record's prototype answers `constructor`.
|
|
804
|
+
const kind = Object.hasOwn(known, key) ? known[key] : undefined
|
|
743
805
|
if (kind === undefined) {
|
|
744
806
|
const near = nearest(key, names)
|
|
745
807
|
throw new UserError(
|
|
@@ -751,12 +813,14 @@ export function refuseUnknownOptions<T>(
|
|
|
751
813
|
const ok =
|
|
752
814
|
kind === 'object'
|
|
753
815
|
? value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
754
|
-
:
|
|
816
|
+
: kind === 'array'
|
|
817
|
+
? Array.isArray(value)
|
|
818
|
+
: typeof value === kind
|
|
755
819
|
if (!ok) {
|
|
756
820
|
const got =
|
|
757
821
|
typeof value === 'function' ? 'a function' : (JSON.stringify(value) ?? String(value))
|
|
758
822
|
throw new UserError(
|
|
759
|
-
`${factory} option "${key}" must be ${kind === 'object' ? 'an' : 'a'} ${kind}, got ${got}`,
|
|
823
|
+
`${factory} option "${key}" must be ${kind === 'object' || kind === 'array' ? 'an' : 'a'} ${kind}, got ${got}`,
|
|
760
824
|
)
|
|
761
825
|
}
|
|
762
826
|
}
|
|
@@ -768,7 +832,7 @@ export interface InstallPluginsArgs {
|
|
|
768
832
|
cacheDir: string
|
|
769
833
|
bus: EventBus
|
|
770
834
|
/**
|
|
771
|
-
* Where plugin-throw warnings go. Defaults to
|
|
835
|
+
* Where plugin-throw warnings go. Defaults to `ui.say` on stderr; passing
|
|
772
836
|
* a callback lets the orchestrator funnel into the framed-output
|
|
773
837
|
* `run:status` channel.
|
|
774
838
|
*/
|
|
@@ -794,8 +858,9 @@ export class PluginSetupError extends UserError {
|
|
|
794
858
|
constructor(
|
|
795
859
|
readonly plugin: Plugin,
|
|
796
860
|
message: string,
|
|
861
|
+
code = 'VX_E_REFUSED',
|
|
797
862
|
) {
|
|
798
|
-
super(message)
|
|
863
|
+
super(message, code)
|
|
799
864
|
}
|
|
800
865
|
}
|
|
801
866
|
|
|
@@ -811,7 +876,7 @@ export class PluginSetupError extends UserError {
|
|
|
811
876
|
*/
|
|
812
877
|
export async function installPlugins(args: InstallPluginsArgs): Promise<() => void> {
|
|
813
878
|
const { plugins, bus, workspaceRoot, cacheDir } = args
|
|
814
|
-
const warn = args.warn ?? ((m) =>
|
|
879
|
+
const warn = args.warn ?? ((m) => ui.say('warn', m))
|
|
815
880
|
const secrets = args.secrets ?? new SecretBroker(args.ci ?? false, warn)
|
|
816
881
|
const disposers: Array<() => void> = []
|
|
817
882
|
// By object, not name: one package can export two plugins.
|
|
@@ -833,7 +898,7 @@ export async function installPlugins(args: InstallPluginsArgs): Promise<() => vo
|
|
|
833
898
|
if (disabled.has(plugin)) return
|
|
834
899
|
disabled.add(plugin)
|
|
835
900
|
warn(
|
|
836
|
-
`[vx] plugin '${plugin.name}' threw in ${where}; disabled for this run: ${
|
|
901
|
+
`[vx] plugin '${plugin.name}' threw in ${where}; disabled for this run: ${thrownMessage(err)}`,
|
|
837
902
|
)
|
|
838
903
|
}
|
|
839
904
|
const ctx: PluginContext = {
|
|
@@ -850,6 +915,11 @@ export async function installPlugins(args: InstallPluginsArgs): Promise<() => vo
|
|
|
850
915
|
bus: {
|
|
851
916
|
emit: (event) => bus.emit(event),
|
|
852
917
|
subscribe(subscriber) {
|
|
918
|
+
if (typeof subscriber !== 'function') {
|
|
919
|
+
throw new Error(
|
|
920
|
+
`ctx.bus.subscribe: the subscriber is ${subscriber === null ? 'null' : typeof subscriber}, not a function`,
|
|
921
|
+
)
|
|
922
|
+
}
|
|
853
923
|
// Isolated like an `on` handler: the bus itself swallows a throw.
|
|
854
924
|
const dispose = bus.subscribe((event) => {
|
|
855
925
|
if (disabled.has(plugin)) return
|
|
@@ -874,6 +944,12 @@ export async function installPlugins(args: InstallPluginsArgs): Promise<() => vo
|
|
|
874
944
|
`ctx.on: unknown hook '${String(hook)}' (one of ${Object.keys(HOOK_NAMES).join(', ')})`,
|
|
875
945
|
)
|
|
876
946
|
}
|
|
947
|
+
// Taken, it threw at the first event and switched the whole plugin off.
|
|
948
|
+
if (typeof handler !== 'function') {
|
|
949
|
+
throw new Error(
|
|
950
|
+
`ctx.on: the handler for '${hook}' is ${handler === null ? 'null' : typeof handler}, not a function`,
|
|
951
|
+
)
|
|
952
|
+
}
|
|
877
953
|
const onFail = (err: unknown): void => fail(hook, err)
|
|
878
954
|
const dispose = bus.subscribe((event) => {
|
|
879
955
|
if (disabled.has(plugin)) return
|
|
@@ -930,9 +1006,9 @@ export async function installPlugins(args: InstallPluginsArgs): Promise<() => vo
|
|
|
930
1006
|
throw new PluginSetupError(
|
|
931
1007
|
plugin,
|
|
932
1008
|
// Masked at the source, as plugin-host.ts's `safe()` does (L-11).
|
|
933
|
-
maskedLine(
|
|
934
|
-
|
|
935
|
-
),
|
|
1009
|
+
maskedLine(`plugin '${plugin.name}' failed in setup: ${thrownMessage(err)}`),
|
|
1010
|
+
// The plugin's own code survives, as `safe()` keeps it.
|
|
1011
|
+
errorCode(err) === 'VX_E_INTERNAL' ? 'VX_E_REFUSED' : errorCode(err),
|
|
936
1012
|
)
|
|
937
1013
|
}
|
|
938
1014
|
}
|