@vibesdotdev/plugins 0.0.0-stage → 1.0.0-rc.0
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/LICENSE +18 -0
- package/README.md +54 -2
- package/VIBES.md +165 -0
- package/dist/ai/index.d.ts +4 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +3 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/plugin.impl.cloud.d.ts +4 -0
- package/dist/ai/plugin.impl.cloud.d.ts.map +1 -0
- package/dist/ai/plugin.impl.cloud.js +32 -0
- package/dist/ai/plugin.impl.cloud.js.map +1 -0
- package/dist/ai/plugin.impl.consumer.d.ts +7 -0
- package/dist/ai/plugin.impl.consumer.d.ts.map +1 -0
- package/dist/ai/plugin.impl.consumer.js +222 -0
- package/dist/ai/plugin.impl.consumer.js.map +1 -0
- package/dist/auth/consumer.plugin.d.ts +3 -0
- package/dist/auth/consumer.plugin.d.ts.map +1 -0
- package/dist/auth/consumer.plugin.js +105 -0
- package/dist/auth/consumer.plugin.js.map +1 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/plugin.d.ts +4 -0
- package/dist/auth/plugin.d.ts.map +1 -0
- package/dist/auth/plugin.js +33 -0
- package/dist/auth/plugin.js.map +1 -0
- package/dist/automation/consumer.plugin.d.ts +3 -0
- package/dist/automation/consumer.plugin.d.ts.map +1 -0
- package/dist/automation/consumer.plugin.js +92 -0
- package/dist/automation/consumer.plugin.js.map +1 -0
- package/dist/automation/index.d.ts +2 -0
- package/dist/automation/index.d.ts.map +1 -0
- package/dist/automation/index.js +2 -0
- package/dist/automation/index.js.map +1 -0
- package/dist/automation/plugin.d.ts +4 -0
- package/dist/automation/plugin.d.ts.map +1 -0
- package/dist/automation/plugin.js +30 -0
- package/dist/automation/plugin.js.map +1 -0
- package/dist/bun/host.d.ts +44 -0
- package/dist/bun/host.d.ts.map +1 -0
- package/dist/bun/host.js +71 -0
- package/dist/bun/host.js.map +1 -0
- package/dist/cli/child-failure.d.ts +37 -0
- package/dist/cli/child-failure.d.ts.map +1 -0
- package/dist/cli/child-failure.js +86 -0
- package/dist/cli/child-failure.js.map +1 -0
- package/dist/cli/context.d.ts +12 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/context.js +11 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli/execution.d.ts +10 -0
- package/dist/cli/execution.d.ts.map +1 -0
- package/dist/cli/execution.js +20 -0
- package/dist/cli/execution.js.map +1 -0
- package/dist/cli/failure-facts.d.ts +16 -0
- package/dist/cli/failure-facts.d.ts.map +1 -0
- package/dist/cli/failure-facts.js +39 -0
- package/dist/cli/failure-facts.js.map +1 -0
- package/dist/cli/package-manager.consumer.d.ts +106 -0
- package/dist/cli/package-manager.consumer.d.ts.map +1 -0
- package/dist/cli/package-manager.consumer.js +258 -0
- package/dist/cli/package-manager.consumer.js.map +1 -0
- package/dist/cli/plugin.d.ts +9 -0
- package/dist/cli/plugin.d.ts.map +1 -0
- package/dist/cli/plugin.js +34 -0
- package/dist/cli/plugin.js.map +1 -0
- package/dist/cli/plugins.add.cli-command.descriptor.d.ts +36 -0
- package/dist/cli/plugins.add.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/plugins.add.cli-command.descriptor.js +31 -0
- package/dist/cli/plugins.add.cli-command.descriptor.js.map +1 -0
- package/dist/cli/plugins.add.cli-command.impl.consumer.d.ts +63 -0
- package/dist/cli/plugins.add.cli-command.impl.consumer.d.ts.map +1 -0
- package/dist/cli/plugins.add.cli-command.impl.consumer.js +206 -0
- package/dist/cli/plugins.add.cli-command.impl.consumer.js.map +1 -0
- package/dist/cli/plugins.cli-group.descriptor.d.ts +4 -0
- package/dist/cli/plugins.cli-group.descriptor.d.ts.map +1 -0
- package/dist/cli/plugins.cli-group.descriptor.js +10 -0
- package/dist/cli/plugins.cli-group.descriptor.js.map +1 -0
- package/dist/cli/plugins.host.cli-command.descriptor.d.ts +31 -0
- package/dist/cli/plugins.host.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/plugins.host.cli-command.descriptor.js +37 -0
- package/dist/cli/plugins.host.cli-command.descriptor.js.map +1 -0
- package/dist/cli/plugins.host.cli-command.impl.consumer.d.ts +29 -0
- package/dist/cli/plugins.host.cli-command.impl.consumer.d.ts.map +1 -0
- package/dist/cli/plugins.host.cli-command.impl.consumer.js +109 -0
- package/dist/cli/plugins.host.cli-command.impl.consumer.js.map +1 -0
- package/dist/cli/plugins.link.cli-command.descriptor.d.ts +35 -0
- package/dist/cli/plugins.link.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/plugins.link.cli-command.descriptor.js +32 -0
- package/dist/cli/plugins.link.cli-command.descriptor.js.map +1 -0
- package/dist/cli/plugins.link.cli-command.impl.consumer.d.ts +31 -0
- package/dist/cli/plugins.link.cli-command.impl.consumer.d.ts.map +1 -0
- package/dist/cli/plugins.link.cli-command.impl.consumer.js +104 -0
- package/dist/cli/plugins.link.cli-command.impl.consumer.js.map +1 -0
- package/dist/cli/plugins.list.cli-command.descriptor.d.ts +26 -0
- package/dist/cli/plugins.list.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/plugins.list.cli-command.descriptor.js +22 -0
- package/dist/cli/plugins.list.cli-command.descriptor.js.map +1 -0
- package/dist/cli/plugins.list.cli-command.impl.consumer.d.ts +116 -0
- package/dist/cli/plugins.list.cli-command.impl.consumer.d.ts.map +1 -0
- package/dist/cli/plugins.list.cli-command.impl.consumer.js +343 -0
- package/dist/cli/plugins.list.cli-command.impl.consumer.js.map +1 -0
- package/dist/cli/plugins.remove.cli-command.descriptor.d.ts +31 -0
- package/dist/cli/plugins.remove.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/plugins.remove.cli-command.descriptor.js +26 -0
- package/dist/cli/plugins.remove.cli-command.descriptor.js.map +1 -0
- package/dist/cli/plugins.remove.cli-command.impl.consumer.d.ts +30 -0
- package/dist/cli/plugins.remove.cli-command.impl.consumer.d.ts.map +1 -0
- package/dist/cli/plugins.remove.cli-command.impl.consumer.js +80 -0
- package/dist/cli/plugins.remove.cli-command.impl.consumer.js.map +1 -0
- package/dist/cli/plugins.unlink.cli-command.descriptor.d.ts +31 -0
- package/dist/cli/plugins.unlink.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/plugins.unlink.cli-command.descriptor.js +21 -0
- package/dist/cli/plugins.unlink.cli-command.descriptor.js.map +1 -0
- package/dist/cli/plugins.unlink.cli-command.impl.consumer.d.ts +76 -0
- package/dist/cli/plugins.unlink.cli-command.impl.consumer.d.ts.map +1 -0
- package/dist/cli/plugins.unlink.cli-command.impl.consumer.js +190 -0
- package/dist/cli/plugins.unlink.cli-command.impl.consumer.js.map +1 -0
- package/dist/cli/registry-sign-in.consumer.d.ts +80 -0
- package/dist/cli/registry-sign-in.consumer.d.ts.map +1 -0
- package/dist/cli/registry-sign-in.consumer.js +199 -0
- package/dist/cli/registry-sign-in.consumer.js.map +1 -0
- package/dist/cli/roll.d.ts +230 -0
- package/dist/cli/roll.d.ts.map +1 -0
- package/dist/cli/roll.js +569 -0
- package/dist/cli/roll.js.map +1 -0
- package/dist/cli/schemas/context.d.ts +10 -0
- package/dist/cli/schemas/context.d.ts.map +1 -0
- package/dist/cli/schemas/context.js +3 -0
- package/dist/cli/schemas/context.js.map +1 -0
- package/dist/cli/schemas/scopes.d.ts +37 -0
- package/dist/cli/schemas/scopes.d.ts.map +1 -0
- package/dist/cli/schemas/scopes.js +11 -0
- package/dist/cli/schemas/scopes.js.map +1 -0
- package/dist/cli/schemas/unlink.d.ts +18 -0
- package/dist/cli/schemas/unlink.d.ts.map +1 -0
- package/dist/cli/schemas/unlink.js +3 -0
- package/dist/cli/schemas/unlink.js.map +1 -0
- package/dist/cli/schemas/update.d.ts +77 -0
- package/dist/cli/schemas/update.d.ts.map +1 -0
- package/dist/cli/schemas/update.js +3 -0
- package/dist/cli/schemas/update.js.map +1 -0
- package/dist/cli/scopes.d.ts +50 -0
- package/dist/cli/scopes.d.ts.map +1 -0
- package/dist/cli/scopes.js +90 -0
- package/dist/cli/scopes.js.map +1 -0
- package/dist/cli/seed.d.ts +232 -0
- package/dist/cli/seed.d.ts.map +1 -0
- package/dist/cli/seed.js +590 -0
- package/dist/cli/seed.js.map +1 -0
- package/dist/cli/self-update.d.ts +120 -0
- package/dist/cli/self-update.d.ts.map +1 -0
- package/dist/cli/self-update.js +274 -0
- package/dist/cli/self-update.js.map +1 -0
- package/dist/cli/update.cli-command.descriptor.d.ts +62 -0
- package/dist/cli/update.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/update.cli-command.descriptor.js +49 -0
- package/dist/cli/update.cli-command.descriptor.js.map +1 -0
- package/dist/cli/update.cli-command.impl.consumer.d.ts +178 -0
- package/dist/cli/update.cli-command.impl.consumer.d.ts.map +1 -0
- package/dist/cli/update.cli-command.impl.consumer.js +750 -0
- package/dist/cli/update.cli-command.impl.consumer.js.map +1 -0
- package/dist/comms/consumer.plugin.d.ts +3 -0
- package/dist/comms/consumer.plugin.d.ts.map +1 -0
- package/dist/comms/consumer.plugin.js +108 -0
- package/dist/comms/consumer.plugin.js.map +1 -0
- package/dist/comms/index.d.ts +2 -0
- package/dist/comms/index.d.ts.map +1 -0
- package/dist/comms/index.js +2 -0
- package/dist/comms/index.js.map +1 -0
- package/dist/comms/plugin.d.ts +4 -0
- package/dist/comms/plugin.d.ts.map +1 -0
- package/dist/comms/plugin.js +30 -0
- package/dist/comms/plugin.js.map +1 -0
- package/dist/consumer.d.ts +3 -0
- package/dist/consumer.d.ts.map +1 -0
- package/dist/consumer.js +3 -0
- package/dist/consumer.js.map +1 -0
- package/dist/consumer.plugin.d.ts +63 -0
- package/dist/consumer.plugin.d.ts.map +1 -0
- package/dist/consumer.plugin.js +89 -0
- package/dist/consumer.plugin.js.map +1 -0
- package/dist/core/consumer.plugin.d.ts +4 -0
- package/dist/core/consumer.plugin.d.ts.map +1 -0
- package/dist/core/consumer.plugin.js +358 -0
- package/dist/core/consumer.plugin.js.map +1 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/optional-member.d.ts +11 -0
- package/dist/core/optional-member.d.ts.map +1 -0
- package/dist/core/optional-member.js +68 -0
- package/dist/core/optional-member.js.map +1 -0
- package/dist/core/optional-plugin.d.ts +40 -0
- package/dist/core/optional-plugin.d.ts.map +1 -0
- package/dist/core/optional-plugin.js +107 -0
- package/dist/core/optional-plugin.js.map +1 -0
- package/dist/core/plugin.d.ts +10 -0
- package/dist/core/plugin.d.ts.map +1 -0
- package/dist/core/plugin.js +64 -0
- package/dist/core/plugin.js.map +1 -0
- package/dist/core/setup-requirements.descriptors.d.ts +7 -0
- package/dist/core/setup-requirements.descriptors.d.ts.map +1 -0
- package/dist/core/setup-requirements.descriptors.js +117 -0
- package/dist/core/setup-requirements.descriptors.js.map +1 -0
- package/dist/data/consumer.plugin.d.ts +3 -0
- package/dist/data/consumer.plugin.d.ts.map +1 -0
- package/dist/data/consumer.plugin.js +99 -0
- package/dist/data/consumer.plugin.js.map +1 -0
- package/dist/data/index.d.ts +2 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +2 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/plugin.d.ts +4 -0
- package/dist/data/plugin.d.ts.map +1 -0
- package/dist/data/plugin.js +37 -0
- package/dist/data/plugin.js.map +1 -0
- package/dist/devtools/consumer.plugin.d.ts +3 -0
- package/dist/devtools/consumer.plugin.d.ts.map +1 -0
- package/dist/devtools/consumer.plugin.js +323 -0
- package/dist/devtools/consumer.plugin.js.map +1 -0
- package/dist/devtools/index.d.ts +2 -0
- package/dist/devtools/index.d.ts.map +1 -0
- package/dist/devtools/index.js +2 -0
- package/dist/devtools/index.js.map +1 -0
- package/dist/devtools/plugin.d.ts +4 -0
- package/dist/devtools/plugin.d.ts.map +1 -0
- package/dist/devtools/plugin.js +34 -0
- package/dist/devtools/plugin.js.map +1 -0
- package/dist/filesystem/index.d.ts +2 -0
- package/dist/filesystem/index.d.ts.map +1 -0
- package/dist/filesystem/index.js +2 -0
- package/dist/filesystem/index.js.map +1 -0
- package/dist/filesystem/plugin.d.ts +4 -0
- package/dist/filesystem/plugin.d.ts.map +1 -0
- package/dist/filesystem/plugin.js +21 -0
- package/dist/filesystem/plugin.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +16 -0
- package/dist/plugin.js.map +1 -0
- package/dist/schemas/ai.d.ts +119 -0
- package/dist/schemas/ai.d.ts.map +1 -0
- package/dist/schemas/ai.js +84 -0
- package/dist/schemas/ai.js.map +1 -0
- package/dist/schemas/auth.d.ts +15 -0
- package/dist/schemas/auth.d.ts.map +1 -0
- package/dist/schemas/auth.js +19 -0
- package/dist/schemas/auth.js.map +1 -0
- package/dist/schemas/automation.d.ts +66 -0
- package/dist/schemas/automation.d.ts.map +1 -0
- package/dist/schemas/automation.js +36 -0
- package/dist/schemas/automation.js.map +1 -0
- package/dist/schemas/comms.d.ts +13 -0
- package/dist/schemas/comms.d.ts.map +1 -0
- package/dist/schemas/comms.js +27 -0
- package/dist/schemas/comms.js.map +1 -0
- package/dist/schemas/core.d.ts +102 -0
- package/dist/schemas/core.d.ts.map +1 -0
- package/dist/schemas/core.js +124 -0
- package/dist/schemas/core.js.map +1 -0
- package/dist/schemas/data.d.ts +13 -0
- package/dist/schemas/data.d.ts.map +1 -0
- package/dist/schemas/data.js +25 -0
- package/dist/schemas/data.js.map +1 -0
- package/dist/schemas/devtools.d.ts +33 -0
- package/dist/schemas/devtools.d.ts.map +1 -0
- package/dist/schemas/devtools.js +56 -0
- package/dist/schemas/devtools.js.map +1 -0
- package/dist/schemas/family.d.ts +11 -0
- package/dist/schemas/family.d.ts.map +1 -0
- package/dist/schemas/family.js +21 -0
- package/dist/schemas/family.js.map +1 -0
- package/dist/schemas/filesystem.d.ts +25 -0
- package/dist/schemas/filesystem.d.ts.map +1 -0
- package/dist/schemas/filesystem.js +24 -0
- package/dist/schemas/filesystem.js.map +1 -0
- package/dist/schemas/host.d.ts +10 -0
- package/dist/schemas/host.d.ts.map +1 -0
- package/dist/schemas/host.js +2 -0
- package/dist/schemas/host.js.map +1 -0
- package/dist/schemas/optional-member.d.ts +67 -0
- package/dist/schemas/optional-member.d.ts.map +1 -0
- package/dist/schemas/optional-member.js +31 -0
- package/dist/schemas/optional-member.js.map +1 -0
- package/dist/schemas/roll.d.ts +47 -0
- package/dist/schemas/roll.d.ts.map +1 -0
- package/dist/schemas/roll.js +18 -0
- package/dist/schemas/roll.js.map +1 -0
- package/dist/schemas/tui.d.ts +33 -0
- package/dist/schemas/tui.d.ts.map +1 -0
- package/dist/schemas/tui.js +95 -0
- package/dist/schemas/tui.js.map +1 -0
- package/dist/schemas/ui.d.ts +15 -0
- package/dist/schemas/ui.d.ts.map +1 -0
- package/dist/schemas/ui.js +45 -0
- package/dist/schemas/ui.js.map +1 -0
- package/dist/tui/code-sessions-tui-plugin-shim.d.ts +7 -0
- package/dist/tui/code-sessions-tui-plugin-shim.d.ts.map +1 -0
- package/dist/tui/code-sessions-tui-plugin-shim.js +2 -0
- package/dist/tui/code-sessions-tui-plugin-shim.js.map +1 -0
- package/dist/tui/index.d.ts +8 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +8 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/plugin.d.ts +53 -0
- package/dist/tui/plugin.d.ts.map +1 -0
- package/dist/tui/plugin.js +350 -0
- package/dist/tui/plugin.js.map +1 -0
- package/dist/ui/consumer.plugin.d.ts +3 -0
- package/dist/ui/consumer.plugin.d.ts.map +1 -0
- package/dist/ui/consumer.plugin.js +75 -0
- package/dist/ui/consumer.plugin.js.map +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +2 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/plugin.d.ts +4 -0
- package/dist/ui/plugin.d.ts.map +1 -0
- package/dist/ui/plugin.js +35 -0
- package/dist/ui/plugin.js.map +1 -0
- package/host-start.mjs +39 -0
- package/host.mjs +24 -0
- package/package.json +437 -4
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
import { resolveVibesAppOriginForEnvironment } from '@vibesdotdev/config/environment/resolve-app-origin';
|
|
2
|
+
import { resolveFileSystemAdapter } from '@vibesdotdev/filesystem/client/resolve-adapter';
|
|
3
|
+
import { getPathRoots } from '@vibesdotdev/paths';
|
|
4
|
+
import { LoggedErrorSchema } from '@vibesdotdev/logging';
|
|
5
|
+
import { CapabilityError, CaughtFailureSchema, composeChildEnv, getRuntimeHostIdentity, getRuntimePackagePlugins, isCapabilityError } from '@vibesdotdev/kernel';
|
|
6
|
+
import { readWorkspacePluginSurfaces } from '@vibesdotdev/kernel/bootstrap';
|
|
7
|
+
import { redactedExcerpt } from '@vibesdotdev/shared/utils';
|
|
8
|
+
import { z } from 'zod/v4';
|
|
9
|
+
import { unreadableSelectionsRefusal } from "./plugins.unlink.cli-command.impl.consumer.js";
|
|
10
|
+
import { summarizeChildFailure } from "./child-failure.js";
|
|
11
|
+
import { PluginsCLICommand } from "./context.js";
|
|
12
|
+
import { resolveRuntimeExecution } from "./execution.js";
|
|
13
|
+
import { planBun, readScopeDeclarations, runBunOrThrow } from "./package-manager.consumer.js";
|
|
14
|
+
import { bodiesRunningReplacedExecutable, censusBodies, markReplacedExecutables, rollBodies } from "./roll.js";
|
|
15
|
+
import { resolveScopes } from "./scopes.js";
|
|
16
|
+
import { formatRepairReceipt, repairGlobalScope, seedGlobalScope, GLOBAL_MANIFEST_NAME, HOST_PACKAGE_NAME } from "./seed.js";
|
|
17
|
+
import { runSelfUpdate, selfInstallTarget } from "./self-update.js";
|
|
18
|
+
/**
|
|
19
|
+
* Prefix the release artifacts are published under, on the `assets` origin.
|
|
20
|
+
*
|
|
21
|
+
* MEASURED CORRECTION (WU-12, 2026-08-31): this used to read
|
|
22
|
+
* `https://vibes.dev/downloads/cli/latest.json`, which is not where anything
|
|
23
|
+
* is published. `vibes infra release cli` uploads to R2 bucket
|
|
24
|
+
* `vibes-public-downloads` under key prefix `downloads/cli/`, served from
|
|
25
|
+
* `https://assets.vibes.dev/`, and `home-web`'s `scripts/install.sh` has
|
|
26
|
+
* always downloaded from `${VIBES_MIRROR:-https://assets.vibes.dev/downloads/cli}`.
|
|
27
|
+
* `--self` pointing at a different origin than the installer is a split every
|
|
28
|
+
* mirror user would hit, so the ONE base is shared. The installer's
|
|
29
|
+
* `VIBES_MIRROR` is a shell variable; here the mirror is the command's own
|
|
30
|
+
* declared `--mirror <url>` option, never an environment read.
|
|
31
|
+
*/
|
|
32
|
+
const RELEASE_DOWNLOADS_PATH = 'downloads/cli';
|
|
33
|
+
/** The deadline on the shipping-path verification boot. */
|
|
34
|
+
const VERIFICATION_TIMEOUT_MS = 120_000;
|
|
35
|
+
/** Whether a scope declares anything bun could install: a dependency or a `workspaces` glob. A scope with no manifest declares nothing. */
|
|
36
|
+
function declaresAnything(declared) {
|
|
37
|
+
return declared !== null && (declared.dependencies.length > 0 || declared.workspaces.length > 0);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The published release base: the `assets` origin the production
|
|
41
|
+
* `config/environment` declares, under {@link RELEASE_DOWNLOADS_PATH}.
|
|
42
|
+
* Releases are published only to production, whatever environment this host
|
|
43
|
+
* calls (the desktop release reads its origin by the same rule), so this
|
|
44
|
+
* reads the production declaration rather than the active one; `--mirror`
|
|
45
|
+
* retargets. A runtime that declares no production `assets` origin refuses
|
|
46
|
+
* `release-origin-undeclared`.
|
|
47
|
+
*/
|
|
48
|
+
export async function publishedReleaseBase(runtime) {
|
|
49
|
+
const origin = await resolveVibesAppOriginForEnvironment('assets', 'production', runtime);
|
|
50
|
+
if (origin === null) {
|
|
51
|
+
throw new CapabilityError('No production assets origin is declared in this runtime.', 'NOT_FOUND', {
|
|
52
|
+
reason: 'release-origin-undeclared',
|
|
53
|
+
retryable: false,
|
|
54
|
+
recoveryHint: 'Pass --mirror <url> naming the release base that holds latest.json.'
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return `${origin}/${RELEASE_DOWNLOADS_PATH}`;
|
|
58
|
+
}
|
|
59
|
+
/** `latest.json` under a release base (the declared `--mirror`, else the published base), trailing slashes trimmed. */
|
|
60
|
+
export function latestManifestUrl(base) {
|
|
61
|
+
return `${base.replace(/\/+$/, '')}/latest.json`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Which scope reports make `vibes update` FAIL.
|
|
65
|
+
*
|
|
66
|
+
* A command that reports `NOT VERIFIED` and exits 0 is the absence-looks-like-
|
|
67
|
+
* success defect: the shipped draft did exactly that, so a scope whose
|
|
68
|
+
* verification boot could not compose, and a `$VIBES_HOME` the command refused
|
|
69
|
+
* to touch at all, both read as "updated" to CI, to a script, and to the WU-07
|
|
70
|
+
* harness. Pure and exported so the rule is testable without booting a child.
|
|
71
|
+
*
|
|
72
|
+
* Not a failure: a benign skip (`no workspace plugin list to update`, a home
|
|
73
|
+
* delegated to a host workspace) — nothing was asked for and nothing broke.
|
|
74
|
+
*
|
|
75
|
+
* `--dry-run` suppresses only the VERIFICATION half: nothing was installed, so
|
|
76
|
+
* nothing was booted, so an unverified scope is not a defect. A REFUSAL is not
|
|
77
|
+
* suppressed — a foreign or fossil `$VIBES_HOME`, or a link glob whose parent
|
|
78
|
+
* is gone, is a fact about the scope as it stands, not about the install that
|
|
79
|
+
* did not run, and `vibes update --dry-run` in CI has to be able to see it.
|
|
80
|
+
*/
|
|
81
|
+
/** A surface omission is verified because the kernel refused to import the declared export. */
|
|
82
|
+
export function isUnverifiedPluginRow(row) {
|
|
83
|
+
if (row.status === 'loaded' || row.status === 'deduped')
|
|
84
|
+
return false;
|
|
85
|
+
if (row.status === 'not-composed' && row.reason === 'surface-omitted')
|
|
86
|
+
return false;
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
export function collectUpdateFailures(reports, options) {
|
|
90
|
+
const failures = [];
|
|
91
|
+
for (const report of reports) {
|
|
92
|
+
// Deliberately BEFORE the dry-run short-circuit: a refusal happened
|
|
93
|
+
// whether or not anything was going to be installed.
|
|
94
|
+
if (report.failed === true) {
|
|
95
|
+
failures.push({
|
|
96
|
+
scope: report.scope,
|
|
97
|
+
root: report.root,
|
|
98
|
+
reason: report.skipped ?? 'refused'
|
|
99
|
+
});
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (report.skipped !== undefined)
|
|
103
|
+
continue;
|
|
104
|
+
// Nothing was installed, so nothing was booted: there is no verification
|
|
105
|
+
// to have failed.
|
|
106
|
+
if (options.dryRun)
|
|
107
|
+
continue;
|
|
108
|
+
if (!report.verified) {
|
|
109
|
+
failures.push({
|
|
110
|
+
scope: report.scope,
|
|
111
|
+
root: report.root,
|
|
112
|
+
reason: `NOT VERIFIED: ${report.verifyDetail ?? 'unknown'}`
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return failures;
|
|
117
|
+
}
|
|
118
|
+
/** The first 12 hex digits of `bun.lock`'s sha256 under `root`, or `null` when the scope has no lock. */
|
|
119
|
+
async function lockDigest(runtime, root) {
|
|
120
|
+
const filesystem = await resolveFileSystemAdapter(runtime, { binding: false });
|
|
121
|
+
const path = filesystem.join(root, 'bun.lock');
|
|
122
|
+
if (!(await filesystem.exists(path)))
|
|
123
|
+
return null;
|
|
124
|
+
const read = await filesystem.readFile(path);
|
|
125
|
+
const bytes = read instanceof Uint8Array ? read.slice() : new TextEncoder().encode(read);
|
|
126
|
+
const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));
|
|
127
|
+
return Array.from(digest, (byte) => byte.toString(16).padStart(2, '0')).join('').slice(0, 12);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The command's inputs, each read on its own: a missing flag is off, a missing
|
|
131
|
+
* or non-text mirror is empty. commander maps `--no-roll` to `roll: false`, so
|
|
132
|
+
* only an explicit `false` turns the roll off.
|
|
133
|
+
*/
|
|
134
|
+
const UpdateInputSchema = z.object({
|
|
135
|
+
dryRun: z.boolean().catch(false),
|
|
136
|
+
roll: z.boolean().catch(true),
|
|
137
|
+
latest: z.boolean().catch(false),
|
|
138
|
+
repair: z.boolean().catch(false),
|
|
139
|
+
self: z.boolean().catch(false),
|
|
140
|
+
allowDowngrade: z.boolean().catch(false),
|
|
141
|
+
mirror: z.string().trim().catch('')
|
|
142
|
+
});
|
|
143
|
+
/** The schema of the verification boot's `plugins list --json` envelope, each field on its own. */
|
|
144
|
+
const VerificationEnvelopeSchema = z.object({
|
|
145
|
+
plugins: z.array(z.object({ plugin: z.string(), status: z.string(), reason: z.literal('surface-omitted').optional() })).optional().catch(undefined),
|
|
146
|
+
resolutions: z
|
|
147
|
+
.array(z.object({ specifier: z.string(), linked: z.boolean().nullable(), realpath: z.string().nullable() }))
|
|
148
|
+
.optional()
|
|
149
|
+
.catch(undefined)
|
|
150
|
+
});
|
|
151
|
+
/** A failure as one bounded, credential-free line for a report. */
|
|
152
|
+
function reportLine(failure) {
|
|
153
|
+
return redactedExcerpt(LoggedErrorSchema.parse(failure).message) || 'unknown failure';
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* `vibes update` — seed, install, verify, roll (plan §5).
|
|
157
|
+
*
|
|
158
|
+
* ### Ordering: self FIRST, then the graph, then ONE roll
|
|
159
|
+
*
|
|
160
|
+
* `--self` runs before any scope work and the census roll runs exactly once at
|
|
161
|
+
* the end. Rolling before the binary is replaced re-stales every body just
|
|
162
|
+
* rolled — the executable freshness nerve fires on the new inode — so the
|
|
163
|
+
* draft's "roll, then swap" order produced two rolls and a `pm status` Build
|
|
164
|
+
* column reading STALE immediately after a successful update.
|
|
165
|
+
*
|
|
166
|
+
* ### What triggers the roll: THREE reasons, one nerve each
|
|
167
|
+
*
|
|
168
|
+
* 1. the module graph moved (a scope's `bun.lock` digest changed);
|
|
169
|
+
* 2. `--self` installed a new release over this executable;
|
|
170
|
+
* 3. a body in the census is **already** executing a replaced file.
|
|
171
|
+
*
|
|
172
|
+
* The third one is not redundant. A local `bun run build:install` swaps
|
|
173
|
+
* `~/.local/bin/vibes` by atomic rename: no lock moves and `--self` downloaded
|
|
174
|
+
* nothing, so on reasons 1–2 alone `vibes update` reported "nothing changed, so
|
|
175
|
+
* no body was rolled" in the same minute `vibes pm status` reported "⚠ PM
|
|
176
|
+
* daemon (pid …) runs a replaced vibes executable". Two surfaces, one fact,
|
|
177
|
+
* and the surface that could FIX it held the weaker predicate. The census now
|
|
178
|
+
* consumes the same executable nerve `pm status` and PM's
|
|
179
|
+
* `ExecutableFreshnessReconciler` consume (`markReplacedExecutables`), so a
|
|
180
|
+
* body running a replaced executable is rolled and the receipt says why.
|
|
181
|
+
*
|
|
182
|
+
* ### Per scope
|
|
183
|
+
*
|
|
184
|
+
* preflight (schema, glob parents, foreign-owner refusal, `workspace:*`
|
|
185
|
+
* declarations) → `bun install` → a SCOPED `bun update <enabled registry
|
|
186
|
+
* packages> @vibesdotdev/plugins`, never a fleet-wide `bun update` at a
|
|
187
|
+
* 232-member root → verification by spawning the SHIPPING path
|
|
188
|
+
* (`vibes plugins list --json --scope <kind>` with `VIBES_HOST_ROOT` bound to
|
|
189
|
+
* the scope root), where every row must read `loaded`.
|
|
190
|
+
*
|
|
191
|
+
* The verification deliberately re-enters the real executable rather than
|
|
192
|
+
* inspecting the manifest we just wrote. A manifest is a claim; a boot is a
|
|
193
|
+
* measurement, and it is the only thing that proves the scope this command
|
|
194
|
+
* just changed can still compose.
|
|
195
|
+
*
|
|
196
|
+
* ### Exit code
|
|
197
|
+
*
|
|
198
|
+
* A scope that did not verify, and a scope the command REFUSED to touch, both
|
|
199
|
+
* make `vibes update` exit non-zero — after the full report (including the
|
|
200
|
+
* `--json` envelope) has been printed. The draft printed `NOT VERIFIED` and
|
|
201
|
+
* exited 0, so a refused `$VIBES_HOME` and a scope that cannot compose read as
|
|
202
|
+
* success to CI, to a script and to the WU-07 harness. Benign skips (a
|
|
203
|
+
* workspace with no plugin list, a home delegated to a host workspace) are not
|
|
204
|
+
* failures. `--dry-run` never fails a VERIFICATION — it installs nothing, so
|
|
205
|
+
* it boots nothing — but a REFUSED scope still exits non-zero under it: the
|
|
206
|
+
* refusal describes the scope as it stands, not the install that did not run.
|
|
207
|
+
* The rule lives in `collectUpdateFailures`.
|
|
208
|
+
*
|
|
209
|
+
* ### Foreign / fossil manifests
|
|
210
|
+
*
|
|
211
|
+
* A `$VIBES_HOME/package.json` whose name is not `vibes-global` is somebody
|
|
212
|
+
* else's file (or the 24-April fossil). We refuse it by name and print
|
|
213
|
+
* `vibes update --repair --global`; `--repair` then backs it up as
|
|
214
|
+
* `*.fossil-<stamp>` and prints kept|dropped. Nothing is merged silently and
|
|
215
|
+
* nothing is deleted without a backup line.
|
|
216
|
+
*/
|
|
217
|
+
export default class UpdateCLICommand extends PluginsCLICommand {
|
|
218
|
+
constructor(descriptor, context) {
|
|
219
|
+
super(descriptor, context);
|
|
220
|
+
}
|
|
221
|
+
async execute(args, options) {
|
|
222
|
+
const flags = { ...args, ...options };
|
|
223
|
+
const { dryRun, roll, latest, repair, ...input } = UpdateInputSchema.parse(flags);
|
|
224
|
+
const paths = getPathRoots(this.runtime);
|
|
225
|
+
const self = input.self ? await this.updateSelf(dryRun, input.mirror, input.allowDowngrade) : null;
|
|
226
|
+
const scopes = await resolveScopes(this.runtime, flags, { fallback: 'both' });
|
|
227
|
+
const scoped = [];
|
|
228
|
+
for (const scope of scopes) {
|
|
229
|
+
scoped.push(await this.updateScope(scope, { dryRun, latest, repair }));
|
|
230
|
+
}
|
|
231
|
+
const reports = scoped.map((outcome) => outcome.report);
|
|
232
|
+
const graphChanged = reports.some((report) => report.changed);
|
|
233
|
+
let outcomes = [];
|
|
234
|
+
let censusNote;
|
|
235
|
+
if (!roll) {
|
|
236
|
+
censusNote = '--no-roll: running bodies keep the previous graph until they restart.';
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// The census is taken BEFORE the trigger is decided, because one of the
|
|
240
|
+
// three reasons to roll is a fact about the bodies themselves.
|
|
241
|
+
const bodies = await markReplacedExecutables(await censusBodies({
|
|
242
|
+
runtime: this.runtime,
|
|
243
|
+
userHome: paths.userHome,
|
|
244
|
+
vibesHome: paths.vibesHome,
|
|
245
|
+
includePmDaemon: true
|
|
246
|
+
}), this.runtime);
|
|
247
|
+
// THE THIRD REASON. `--self` only reports a release this command
|
|
248
|
+
// downloaded, and the lock digest only reports the module graph; a local
|
|
249
|
+
// `bun run build:install` replaces the executable by atomic rename and
|
|
250
|
+
// moves neither. Gating on those two alone made `vibes update` print
|
|
251
|
+
// "nothing changed, so no body was rolled" while `vibes pm status`
|
|
252
|
+
// printed "runs a replaced vibes executable" about the same daemon.
|
|
253
|
+
// Same nerve, one decision.
|
|
254
|
+
const stale = bodiesRunningReplacedExecutable(bodies);
|
|
255
|
+
if (graphChanged || self?.replaced || stale.length > 0 || dryRun) {
|
|
256
|
+
const notes = [];
|
|
257
|
+
if (stale.length > 0 && !graphChanged && !self?.replaced) {
|
|
258
|
+
notes.push(`the module graph is unchanged, but ${stale.length} running ` +
|
|
259
|
+
`${stale.length === 1 ? 'body executes' : 'bodies execute'} a replaced vibes ` +
|
|
260
|
+
`executable — ${stale.map((body) => `${body.id}: ${body.replacedReason}`).join('; ')}`);
|
|
261
|
+
}
|
|
262
|
+
if (!this.runtime.hasKind('hq/os-service')) {
|
|
263
|
+
notes.push('hq/os-service is not composed into this host, so OS services were enumerated from ' +
|
|
264
|
+
'the agent directory alone and cannot be restarted from here.');
|
|
265
|
+
}
|
|
266
|
+
if (notes.length)
|
|
267
|
+
censusNote = notes.join(' ');
|
|
268
|
+
outcomes = await rollBodies({ runtime: this.runtime, bodies, dryRun });
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
censusNote =
|
|
272
|
+
'nothing changed and no running body executes a replaced vibes executable, ' +
|
|
273
|
+
'so no body was rolled.';
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
this.emit({ self, reports, outcomes, censusNote, dryRun });
|
|
277
|
+
// The report is printed FIRST (including the whole `--json` envelope, so
|
|
278
|
+
// a machine caller still gets every row) and only then does the command
|
|
279
|
+
// fail. Silence plus exit 0 is what this replaces.
|
|
280
|
+
const failures = collectUpdateFailures(reports, { dryRun });
|
|
281
|
+
if (failures.length > 0)
|
|
282
|
+
throw updateIncomplete(failures, scoped);
|
|
283
|
+
}
|
|
284
|
+
/** Replace the running executable from the declared `--mirror`, else the published release base. */
|
|
285
|
+
async updateSelf(dryRun, mirror, allowDowngrade) {
|
|
286
|
+
const host = this.runtime.localEnvironment;
|
|
287
|
+
const platform = host.getPlatform?.();
|
|
288
|
+
const arch = host.getArch?.();
|
|
289
|
+
const home = host.accountHome?.();
|
|
290
|
+
if (!platform || !arch || !home) {
|
|
291
|
+
throw new CapabilityError('Host platform, architecture and account home are not captured.', 'UNAVAILABLE', {
|
|
292
|
+
recoveryHint: 'Run through a host that captures platform, architecture and account home (the vibes CLI host).'
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const target = selfInstallTarget(platform, { LOCALAPPDATA: this.runtime.getEnv('LOCALAPPDATA') }, home);
|
|
296
|
+
const manifestUrl = latestManifestUrl(mirror || (await publishedReleaseBase(this.runtime)));
|
|
297
|
+
const result = await runSelfUpdate({
|
|
298
|
+
runtime: this.runtime,
|
|
299
|
+
manifestUrl,
|
|
300
|
+
currentVersion: this.hostVersion(),
|
|
301
|
+
target,
|
|
302
|
+
platform,
|
|
303
|
+
arch,
|
|
304
|
+
dryRun,
|
|
305
|
+
allowDowngrade
|
|
306
|
+
});
|
|
307
|
+
const report = {
|
|
308
|
+
replaced: result.replaced,
|
|
309
|
+
fromVersion: result.fromVersion,
|
|
310
|
+
toVersion: result.toVersion,
|
|
311
|
+
target: result.target.label,
|
|
312
|
+
manifestUrl
|
|
313
|
+
};
|
|
314
|
+
// THE DIGEST IS THE POINT OF A DRY RUN. `runSelfUpdate` already
|
|
315
|
+
// resolved the asset and its sha256; dropping it here left
|
|
316
|
+
// `--self --dry-run` announcing a download whose verification the
|
|
317
|
+
// operator could not check against SHA256SUMS.txt before running it
|
|
318
|
+
// for real. Reported on both surfaces (WU-12).
|
|
319
|
+
if (result.sha256)
|
|
320
|
+
report.sha256 = result.sha256;
|
|
321
|
+
if (result.note)
|
|
322
|
+
report.note = result.note;
|
|
323
|
+
return report;
|
|
324
|
+
}
|
|
325
|
+
/** Version of the running host: the host's own version fact, the one `--version` reports. */
|
|
326
|
+
hostVersion() {
|
|
327
|
+
// The shim's channel version, else the version the executable host
|
|
328
|
+
// declared; the environment captured both once.
|
|
329
|
+
return this.runtime.localEnvironment.hostVersion?.()?.trim() || 'unknown';
|
|
330
|
+
}
|
|
331
|
+
async updateScope(scope, options) {
|
|
332
|
+
const filesystem = await resolveFileSystemAdapter(this.runtime, { binding: false });
|
|
333
|
+
const plugins = await getRuntimePackagePlugins(this.runtime);
|
|
334
|
+
const manifestPath = filesystem.join(scope.root, 'package.json');
|
|
335
|
+
const base = {
|
|
336
|
+
scope: scope.kind,
|
|
337
|
+
root: scope.root,
|
|
338
|
+
manifestPath,
|
|
339
|
+
lockBefore: await lockDigest(this.runtime, scope.root),
|
|
340
|
+
lockAfter: null,
|
|
341
|
+
changed: false,
|
|
342
|
+
packages: [],
|
|
343
|
+
commands: [],
|
|
344
|
+
verified: false,
|
|
345
|
+
plugins: []
|
|
346
|
+
};
|
|
347
|
+
if (scope.kind === 'global') {
|
|
348
|
+
const repairReport = await this.prepareGlobal(plugins, scope.root, options);
|
|
349
|
+
if (repairReport.refusal)
|
|
350
|
+
return { report: { ...base, skipped: repairReport.refusal, failed: true } };
|
|
351
|
+
if (repairReport.lines.length)
|
|
352
|
+
base.repair = repairReport.lines;
|
|
353
|
+
}
|
|
354
|
+
const selected = await this.selectionsFor(scope);
|
|
355
|
+
const selections = selected.plugins;
|
|
356
|
+
if (selected.surface)
|
|
357
|
+
base.surface = selected.surface;
|
|
358
|
+
if (selections.length === 0 && scope.kind === 'workspace') {
|
|
359
|
+
const skipped = selected.surface
|
|
360
|
+
? `${selected.surface} selects no plugins`
|
|
361
|
+
: `no workspace plugin list to update (looked for vibes.config.{ts,js,json}#plugins, then package.json#vibes.plugins in ${scope.root})`;
|
|
362
|
+
return { report: { ...base, skipped } };
|
|
363
|
+
}
|
|
364
|
+
// Preflight the on-disk rules before running bun: a glob whose parent is
|
|
365
|
+
// gone or a specifier nothing declares makes `bun install` succeed and the
|
|
366
|
+
// next boot fail, which is the worst possible ordering.
|
|
367
|
+
const preflight = this.preflight(plugins, scope);
|
|
368
|
+
if (preflight.issues.length) {
|
|
369
|
+
const skipped = { ...base, skipped: preflight.issues.join('; ') };
|
|
370
|
+
if (!preflight.benign)
|
|
371
|
+
skipped.failed = true;
|
|
372
|
+
return { report: skipped };
|
|
373
|
+
}
|
|
374
|
+
// The scope's install inputs, read once: an unreadable manifest refuses the scope (non-zero exit).
|
|
375
|
+
const declarations = await this.readDeclarations(scope.root);
|
|
376
|
+
if ('refused' in declarations) {
|
|
377
|
+
const refused = declarations.refused;
|
|
378
|
+
return { report: { ...base, skipped: `${refused.message} (${refused.reason})`, failed: true }, failure: refused };
|
|
379
|
+
}
|
|
380
|
+
const declared = declarations.declared;
|
|
381
|
+
// A scope that declares NOTHING — no plugins, no dependencies, no
|
|
382
|
+
// `workspaces` globs — has no install to run, and running one is not
|
|
383
|
+
// harmless: `bun install` on an empty manifest prints "No packages!
|
|
384
|
+
// Deleted empty lockfile" and still MATERIALIZES an empty
|
|
385
|
+
// `node_modules/` (measured, bun 1.4.0). Under `$VIBES_HOME` that
|
|
386
|
+
// directory is the one thing plan §8 item 3 says a delegated home must
|
|
387
|
+
// not have — an empty `node_modules` is where a later stray install
|
|
388
|
+
// silently lands and becomes a second graph. This is the exact shape
|
|
389
|
+
// `vibes update --repair --global` leaves behind on the migration path
|
|
390
|
+
// (plan §9 step 1 drops every fossil dependency and glob before step 2
|
|
391
|
+
// records `hostWorkspace`), so without this skip the documented
|
|
392
|
+
// migration cannot reach the documented end state.
|
|
393
|
+
if (selections.length === 0 && !declaresAnything(declared)) {
|
|
394
|
+
return { report: { ...base, skipped: 'nothing declared — no plugins, dependencies or workspaces globs to install' } };
|
|
395
|
+
}
|
|
396
|
+
const members = plugins.expandWorkspaceMembers(scope.root);
|
|
397
|
+
const registryPackages = [...new Set(selections.map((selection) => plugins.getPackageName(selection)))]
|
|
398
|
+
.filter((name) => !members.has(name))
|
|
399
|
+
.sort();
|
|
400
|
+
// The host package is always kept current; a scope that never names it
|
|
401
|
+
// still resolves its plugins through it.
|
|
402
|
+
const updateTargets = [...new Set([...registryPackages, HOST_PACKAGE_NAME])].filter((name) => declared?.dependencies.includes(name) === true);
|
|
403
|
+
base.packages = updateTargets;
|
|
404
|
+
const install = { action: 'install', cwd: scope.root };
|
|
405
|
+
const update = {
|
|
406
|
+
action: 'update',
|
|
407
|
+
cwd: scope.root,
|
|
408
|
+
args: [...updateTargets, ...(options.latest ? ['--latest'] : [])]
|
|
409
|
+
};
|
|
410
|
+
base.commands = [
|
|
411
|
+
await planBun({ runtime: this.runtime, ...install }),
|
|
412
|
+
...(updateTargets.length ? [await planBun({ runtime: this.runtime, ...update })] : [])
|
|
413
|
+
];
|
|
414
|
+
if (options.dryRun)
|
|
415
|
+
return { report: base };
|
|
416
|
+
// REGISTRY AUTH PREFLIGHT — the last thing checked before bun runs.
|
|
417
|
+
//
|
|
418
|
+
// Everything above this line preflights the on-disk shape. This one
|
|
419
|
+
// preflights the CREDENTIAL, because the failure it prevents is not a
|
|
420
|
+
// crash: packages.vibes.dev 404-hides a rejected or unresolved
|
|
421
|
+
// credential rather than returning 401, so `bun install` emits
|
|
422
|
+
// `error: GET https://packages.vibes.dev/@vibesdotdev%2fplugins - 404`
|
|
423
|
+
// and the user reasonably concludes the package was never published.
|
|
424
|
+
// Measured on a real machine 2026-08-31 — days were spent on a
|
|
425
|
+
// publication bug that did not exist. A refusal that NAMES the remedy
|
|
426
|
+
// is strictly better than a bare 404 trace.
|
|
427
|
+
// `runBunOrThrow` owns it (`registry-sign-in.consumer.ts`), so every
|
|
428
|
+
// install path — `update`, `plugins add|link|unlink|remove` — gets it.
|
|
429
|
+
await runBunOrThrow({ runtime: this.runtime, ...install });
|
|
430
|
+
// NEVER a bare `bun update` here: at a 232-member developer root that
|
|
431
|
+
// walks every workspace member and rewrites the whole lock.
|
|
432
|
+
if (updateTargets.length) {
|
|
433
|
+
await runBunOrThrow({ runtime: this.runtime, ...update });
|
|
434
|
+
}
|
|
435
|
+
base.lockAfter = await lockDigest(this.runtime, scope.root);
|
|
436
|
+
base.changed = base.lockAfter !== base.lockBefore;
|
|
437
|
+
const verification = await this.verifyScope(plugins, scope);
|
|
438
|
+
base.verified = verification.ok;
|
|
439
|
+
base.plugins = verification.rows;
|
|
440
|
+
if (!verification.ok)
|
|
441
|
+
base.verifyDetail = verification.detail;
|
|
442
|
+
return { report: base, failure: verification.failure };
|
|
443
|
+
}
|
|
444
|
+
/** Seed an absent home, repair a fossil one, or refuse a foreign one by name. */
|
|
445
|
+
async prepareGlobal(plugins, root, options) {
|
|
446
|
+
const scope = plugins.readGlobalPluginScope(root);
|
|
447
|
+
if (scope.absent) {
|
|
448
|
+
const seeded = await seedGlobalScope(this.runtime, { home: root, dryRun: options.dryRun });
|
|
449
|
+
const lines = [`seeded ${seeded.manifestPath}`];
|
|
450
|
+
if (seeded.wroteBunfig)
|
|
451
|
+
lines.push(`seeded ${seeded.bunfigPath}`);
|
|
452
|
+
if (seeded.migratedBunfig)
|
|
453
|
+
lines.push(`repaired the @vibesdotdev scope in ${seeded.bunfigPath} (backup beside it)`);
|
|
454
|
+
return { lines };
|
|
455
|
+
}
|
|
456
|
+
if (options.repair) {
|
|
457
|
+
const result = await repairGlobalScope(this.runtime, {
|
|
458
|
+
home: root,
|
|
459
|
+
dryRun: options.dryRun,
|
|
460
|
+
// The kernel is dependency-free by contract, so the glob expander
|
|
461
|
+
// is injected here: without it a `workspace:*` dependency whose
|
|
462
|
+
// supplying glob is gone would be KEPT and bun would fail the
|
|
463
|
+
// install again — the shape `vibes plugins unlink` used to leave
|
|
464
|
+
// behind, and the one `--repair` could not repair.
|
|
465
|
+
workspaceMembers: (scopeRoot, globs) => plugins.expandWorkspaceMembers(scopeRoot, [...globs]).keys()
|
|
466
|
+
});
|
|
467
|
+
return { lines: formatRepairReceipt(result) };
|
|
468
|
+
}
|
|
469
|
+
if (!scope.owned) {
|
|
470
|
+
return {
|
|
471
|
+
lines: [],
|
|
472
|
+
refusal: `${scope.manifestPath} is named '${scope.manifestName ?? '(unnamed)'}', not ` +
|
|
473
|
+
`'${GLOBAL_MANIFEST_NAME}'. vibes will not rewrite a manifest it does not own. ` +
|
|
474
|
+
'Run: vibes update --repair --global (it backs the file up first)'
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
return { lines: [] };
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* The specifiers a scope has ENABLED, read through the SAME resolution the
|
|
481
|
+
* bootstrap loader composes from.
|
|
482
|
+
*
|
|
483
|
+
* The workspace half used to call `resolveWorkspaceListTarget`, the WRITER's
|
|
484
|
+
* target resolver, which reports an empty list for a `vibes.config.ts`
|
|
485
|
+
* because it refuses to rewrite a module. `update --workspace` therefore saw
|
|
486
|
+
* nothing to update for plan §3's PRIMARY workspace surface — the file
|
|
487
|
+
* requirement #1 tells users to write — and skipped the scope with
|
|
488
|
+
* `no workspace plugin list to update`. It now reads Runtime's
|
|
489
|
+
* `readWorkspacePluginSurfaces` (`@vibesdotdev/kernel/bootstrap`, the
|
|
490
|
+
* owner of workspace composition formats) over the handed runtime's
|
|
491
|
+
* selected `runtime/loader`, the same reader the workspace composition and
|
|
492
|
+
* `plugins list` read: `vibes.config.{ts,js,json}#plugins` primary,
|
|
493
|
+
* `package.json#vibes.plugins` fallback.
|
|
494
|
+
*
|
|
495
|
+
* A SHADOWED fallback list is deliberately not included. `bun update` here
|
|
496
|
+
* targets the packages this scope composes; a specifier the effective surface
|
|
497
|
+
* overrides is not composed, and installing it would make `vibes update`
|
|
498
|
+
* disagree with the boot it uses to verify itself.
|
|
499
|
+
*/
|
|
500
|
+
async selectionsFor(scope) {
|
|
501
|
+
if (scope.kind === 'global') {
|
|
502
|
+
return {
|
|
503
|
+
plugins: (await getRuntimePackagePlugins(this.runtime)).readGlobalPluginScope(scope.root).plugins,
|
|
504
|
+
surface: 'package.json#vibes.plugins'
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
const loader = await this.runtime
|
|
508
|
+
.query('runtime/loader')
|
|
509
|
+
.enabled()
|
|
510
|
+
.resolve();
|
|
511
|
+
const listing = await readWorkspacePluginSurfaces(scope.root, { loader });
|
|
512
|
+
// A declaration that failed to load leaves the list unknown: refuse
|
|
513
|
+
// rather than update the manifest's list or nothing.
|
|
514
|
+
if (listing.failures.length > 0)
|
|
515
|
+
throw unreadableSelectionsRefusal(listing);
|
|
516
|
+
return {
|
|
517
|
+
plugins: listing.effective?.plugins ?? [],
|
|
518
|
+
surface: listing.effective?.surface ?? null
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* The scope's install inputs. An unreadable or malformed manifest is the
|
|
523
|
+
* typed `plugin-scope-manifest-unreadable` refusal, carried into the report
|
|
524
|
+
* as a refused scope (SPEC: a refused scope exits non-zero) and kept whole
|
|
525
|
+
* as the command's cause; every other failure propagates.
|
|
526
|
+
*/
|
|
527
|
+
async readDeclarations(root) {
|
|
528
|
+
try {
|
|
529
|
+
return { declared: await readScopeDeclarations(this.runtime, root) };
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
const failure = CaughtFailureSchema.parse(error);
|
|
533
|
+
if (isCapabilityError(failure) && failure.reason === 'plugin-scope-manifest-unreadable')
|
|
534
|
+
return { refused: failure };
|
|
535
|
+
throw failure;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* On-disk rules that decide whether bun may run at all.
|
|
540
|
+
*
|
|
541
|
+
* `benign` separates "there is nothing here to do" (a delegated home owns
|
|
542
|
+
* no packages — the update belongs to the host workspace) from "the request
|
|
543
|
+
* could not be carried out" (a link glob whose parent directory is gone).
|
|
544
|
+
* Only the second kind fails the command; conflating them would make
|
|
545
|
+
* `vibes update --global` on a delegated machine exit non-zero forever.
|
|
546
|
+
*/
|
|
547
|
+
preflight(plugins, scope) {
|
|
548
|
+
if (scope.kind === 'global') {
|
|
549
|
+
const global = plugins.readGlobalPluginScope(scope.root);
|
|
550
|
+
if (global.hostWorkspace) {
|
|
551
|
+
return {
|
|
552
|
+
issues: [
|
|
553
|
+
`delegated to ${global.hostWorkspace} — update that workspace instead ` +
|
|
554
|
+
'(the global home owns no packages)'
|
|
555
|
+
],
|
|
556
|
+
benign: true
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
return {
|
|
560
|
+
issues: plugins.validatePluginScopeOnDisk(global)
|
|
561
|
+
.filter((issue) => issue.code === 'workspace-parent-missing')
|
|
562
|
+
.map((issue) => `${issue.message}. Remedy: ${issue.remedy}`),
|
|
563
|
+
benign: false
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
return { issues: [], benign: true };
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Boot the SHIPPING path against the scope root and require every row to be
|
|
570
|
+
* composed. A manifest that parses is not a scope that composes.
|
|
571
|
+
*
|
|
572
|
+
* Two details that are easy to get wrong and were measured, not guessed:
|
|
573
|
+
*
|
|
574
|
+
* - The child's cwd is the scope root only for the WORKSPACE scope. Setting
|
|
575
|
+
* it to `$VIBES_HOME` for the global scope made bootstrap read
|
|
576
|
+
* `$VIBES_HOME/package.json#vibes.plugins` as the *workspace* fallback
|
|
577
|
+
* list (there is no config there, so the cwd IS the workspace root), so
|
|
578
|
+
* the workspace batch claimed every global specifier and the global batch
|
|
579
|
+
* reported `deduped` for all of them.
|
|
580
|
+
* - `deduped` is therefore accepted alongside `loaded`: it means the very
|
|
581
|
+
* same specifier was already selected by an earlier scope in that process,
|
|
582
|
+
* so the plugin IS composed. `missing` and `failed` are the real failures,
|
|
583
|
+
* and `shadowed` is reported as a warning detail because another plugin
|
|
584
|
+
* owns the id.
|
|
585
|
+
*/
|
|
586
|
+
async verifyScope(plugins, scope) {
|
|
587
|
+
let argv;
|
|
588
|
+
try {
|
|
589
|
+
argv = (await getRuntimeHostIdentity(this.runtime)).selfSpawnArgv('vibes update verification');
|
|
590
|
+
}
|
|
591
|
+
catch (error) {
|
|
592
|
+
const failure = CaughtFailureSchema.parse(error);
|
|
593
|
+
return { ok: false, rows: [], detail: reportLine(failure), failure };
|
|
594
|
+
}
|
|
595
|
+
const execution = await resolveRuntimeExecution(this.runtime, 'update verification boot');
|
|
596
|
+
const request = {
|
|
597
|
+
command: argv[0],
|
|
598
|
+
args: [...argv.slice(1), 'plugins', 'list', '--json', '--scope', scope.kind],
|
|
599
|
+
env: composeChildEnv(this.runtime, { env: { VIBES_HOST_ROOT: scope.root } }),
|
|
600
|
+
envMode: 'replace',
|
|
601
|
+
timeout: VERIFICATION_TIMEOUT_MS
|
|
602
|
+
};
|
|
603
|
+
if (scope.kind === 'workspace')
|
|
604
|
+
request.cwd = scope.root;
|
|
605
|
+
const result = await execution.execute(request);
|
|
606
|
+
if (result.exitCode !== 0 || result.timedOut) {
|
|
607
|
+
const summary = summarizeChildFailure({ stdout: result.stdout, stderr: result.stderr, max: 3 });
|
|
608
|
+
return {
|
|
609
|
+
ok: false,
|
|
610
|
+
rows: [],
|
|
611
|
+
detail: (summary && redactedExcerpt(summary, { maxChars: 1_000 })) ||
|
|
612
|
+
`verification exited ${result.timedOut ? 'by timeout' : String(result.exitCode)} with no output`
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
const start = result.stdout.indexOf('{');
|
|
616
|
+
if (start < 0)
|
|
617
|
+
return { ok: false, rows: [], detail: 'verification produced no JSON envelope' };
|
|
618
|
+
let json;
|
|
619
|
+
try {
|
|
620
|
+
json = JSON.parse(result.stdout.slice(start));
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
const failure = CaughtFailureSchema.parse(error);
|
|
624
|
+
return { ok: false, rows: [], detail: `verification produced malformed JSON: ${reportLine(failure)}`, failure };
|
|
625
|
+
}
|
|
626
|
+
const envelope = VerificationEnvelopeSchema.safeParse(json);
|
|
627
|
+
if (!envelope.success)
|
|
628
|
+
return { ok: false, rows: [], detail: 'verification produced a JSON envelope of the wrong shape' };
|
|
629
|
+
const linkage = new Map((envelope.data.resolutions ?? []).map((row) => [row.specifier, row]));
|
|
630
|
+
const rows = (envelope.data.plugins ?? []).map((row) => ({
|
|
631
|
+
plugin: row.plugin,
|
|
632
|
+
status: row.status,
|
|
633
|
+
reason: row.reason,
|
|
634
|
+
linked: linkage.get(row.plugin)?.linked ?? null,
|
|
635
|
+
realpath: linkage.get(row.plugin)?.realpath ?? null
|
|
636
|
+
}));
|
|
637
|
+
const bad = rows.filter((row) => isUnverifiedPluginRow(row));
|
|
638
|
+
if (bad.length) {
|
|
639
|
+
return { ok: false, rows, detail: bad.map((row) => `${row.plugin}: ${row.status}`).join(', ') };
|
|
640
|
+
}
|
|
641
|
+
return { ok: true, rows };
|
|
642
|
+
}
|
|
643
|
+
emit(payload) {
|
|
644
|
+
if (this.format === 'json') {
|
|
645
|
+
const roll = {
|
|
646
|
+
denominator: payload.outcomes.length,
|
|
647
|
+
rolled: payload.outcomes.filter((outcome) => outcome.rolled).length,
|
|
648
|
+
bodies: payload.outcomes.map(rollBodyRow)
|
|
649
|
+
};
|
|
650
|
+
if (payload.censusNote)
|
|
651
|
+
roll.note = payload.censusNote;
|
|
652
|
+
this.ui.json({ dryRun: payload.dryRun, self: payload.self, scopes: payload.reports, roll });
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
if (payload.self) {
|
|
656
|
+
const self = payload.self;
|
|
657
|
+
// The sha256 is printed on BOTH surfaces. A dry run whose whole purpose
|
|
658
|
+
// is "tell me what you would do" that withholds the digest it would
|
|
659
|
+
// verify leaves the operator unable to compare it with SHA256SUMS.txt
|
|
660
|
+
// before authorising the real run.
|
|
661
|
+
const digest = self.sha256 ? ` sha256 ${self.sha256}` : '';
|
|
662
|
+
this.ui.info(self.replaced
|
|
663
|
+
? `self ${self.fromVersion} → ${self.toVersion} installed at ${self.target}${digest}`
|
|
664
|
+
: `self ${self.fromVersion}: ${self.note ?? 'unchanged'}${digest}`);
|
|
665
|
+
this.ui.info(` manifest ${self.manifestUrl}`);
|
|
666
|
+
}
|
|
667
|
+
for (const report of payload.reports) {
|
|
668
|
+
for (const line of report.repair ?? [])
|
|
669
|
+
this.ui.info(` ${line}`);
|
|
670
|
+
if (report.skipped !== undefined) {
|
|
671
|
+
const line = `${report.scope} ${report.root}: ${report.skipped}`;
|
|
672
|
+
if (report.failed === true)
|
|
673
|
+
this.ui.error(line);
|
|
674
|
+
else
|
|
675
|
+
this.ui.warn(line);
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
const lock = report.changed
|
|
679
|
+
? `lock ${report.lockBefore ?? 'none'} → ${report.lockAfter ?? 'none'}`
|
|
680
|
+
: `lock ${report.lockAfter ?? report.lockBefore ?? 'none'} (unchanged)`;
|
|
681
|
+
const failed = !payload.dryRun && !report.verified;
|
|
682
|
+
const verdict = payload.dryRun
|
|
683
|
+
? 'dry run'
|
|
684
|
+
: report.verified
|
|
685
|
+
? 'verified'
|
|
686
|
+
: `NOT VERIFIED: ${report.verifyDetail ?? 'unknown'}`;
|
|
687
|
+
const line = `${report.scope} ${report.root} — ${lock} — ${verdict}`;
|
|
688
|
+
if (failed)
|
|
689
|
+
this.ui.error(line);
|
|
690
|
+
else
|
|
691
|
+
this.ui.info(line);
|
|
692
|
+
for (const command of payload.dryRun ? report.commands : []) {
|
|
693
|
+
this.ui.info(` would run: ${command.join(' ')}`);
|
|
694
|
+
}
|
|
695
|
+
for (const row of report.plugins) {
|
|
696
|
+
this.ui.info(` ${row.plugin} ${row.status} ${row.linked === null ? '' : row.linked ? 'linked' : 'installed'}`.trimEnd());
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
if (payload.censusNote)
|
|
700
|
+
this.ui.warn(payload.censusNote);
|
|
701
|
+
for (const outcome of payload.outcomes) {
|
|
702
|
+
if (outcome.rolled)
|
|
703
|
+
this.ui.success(outcome.line);
|
|
704
|
+
else
|
|
705
|
+
this.ui.warn(outcome.line);
|
|
706
|
+
}
|
|
707
|
+
if (payload.outcomes.length) {
|
|
708
|
+
this.ui.info(`${payload.outcomes.filter((outcome) => outcome.rolled).length} of ` +
|
|
709
|
+
`${payload.outcomes.length} known bodies rolled`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function rollBodyRow(outcome) {
|
|
714
|
+
const row = {
|
|
715
|
+
kind: outcome.body.kind,
|
|
716
|
+
id: outcome.body.id,
|
|
717
|
+
source: outcome.body.source,
|
|
718
|
+
shape: outcome.body.shape,
|
|
719
|
+
hqRegistered: outcome.body.hqRegistered,
|
|
720
|
+
// The census reports WHY a body was in scope, not only what happened to
|
|
721
|
+
// it: `replacedExecutable` is the nerve `vibes pm status` prints as
|
|
722
|
+
// `⚠ STALE`, and `null` means unanswerable.
|
|
723
|
+
replacedExecutable: outcome.body.replacedExecutable ?? null,
|
|
724
|
+
rolled: outcome.rolled
|
|
725
|
+
};
|
|
726
|
+
if (outcome.body.pid !== undefined)
|
|
727
|
+
row.runningPid = outcome.body.pid;
|
|
728
|
+
if (outcome.body.replacedReason)
|
|
729
|
+
row.replacedReason = outcome.body.replacedReason;
|
|
730
|
+
if (outcome.previousPid !== undefined)
|
|
731
|
+
row.previousPid = outcome.previousPid;
|
|
732
|
+
if (outcome.pid !== undefined)
|
|
733
|
+
row.pid = outcome.pid;
|
|
734
|
+
if (outcome.reason)
|
|
735
|
+
row.reason = outcome.reason;
|
|
736
|
+
return row;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* `vibes update` did not complete: one line per failing scope, and the failure
|
|
740
|
+
* raised behind each refused or unverified scope kept as the cause for `-v`.
|
|
741
|
+
*/
|
|
742
|
+
function updateIncomplete(failures, scoped) {
|
|
743
|
+
const message = `vibes update did not complete (${failures.length} of ${scoped.length} scope(s)):\n` +
|
|
744
|
+
failures.map((failure) => ` ${failure.scope} ${failure.root}: ${failure.reason}`).join('\n');
|
|
745
|
+
const causes = scoped.flatMap((outcome) => (outcome.failure === undefined ? [] : [outcome.failure]));
|
|
746
|
+
if (causes.length === 0)
|
|
747
|
+
return new Error(message);
|
|
748
|
+
return new Error(message, { cause: new AggregateError(causes, 'The failure behind each refused or unverified scope') });
|
|
749
|
+
}
|
|
750
|
+
//# sourceMappingURL=update.cli-command.impl.consumer.js.map
|