feat(plugin-inventory): offline bundle and pnpm plugin install for the desktop

Adds an install surface to the plugin-inventory gateway: availableBundles lists
the curated offline-installable optional bundles (AVAILABLE_BUNDLES); install
composes an offline bundle into the profile's dsh.profile.bundles, or for a
registry spec runs pnpm against the writable profile via the bundled Node and a
vendored pnpm (gated behind the dshAllowPluginInstall context flag, set only by
the desktop boot); uninstall removes a bundle layer. The reconcile logic from
`dsh plugin add` moves into app-boot as shared helpers. The desktop vendored
pnpm into the harness and sets the allow-install env; the plugin-list SPA gains
an installable-bundles section. Tests cover the guard, install helpers, and the
SPA section at 100% host coverage.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pine
2026-08-14 18:46:39 +08:00
parent 8f1c764614
commit 6cd7c5a590
25 changed files with 929 additions and 110 deletions
+11
View File
@@ -43,4 +43,15 @@ if (!existsSync(nodeBin)) throw new Error(`node executable not found: ${nodeBin}
cpSync(nodeBin, join(out, 'bin/node'))
chmodSync(join(out, 'bin/node'), 0o755)
// Vendor pnpm so the packaged app can install third-party plugins without pnpm
// on the target machine. npm ships with Node, so use it on the build machine
// (which has network); a failure only disables registry install, never the
// offline bundle install, so warn rather than abort.
try {
execFileSync('npm', ['install', '--prefix', join(out, 'pnpm'), 'pnpm@11.7.0'], { stdio: 'pipe' })
console.log('vendored pnpm into harness')
} catch {
console.warn('could not vendor pnpm; registry plugin install will be unavailable in the packaged app')
}
console.log(`assembled self-contained harness at ${out}`)
+2 -1
View File
@@ -177,7 +177,8 @@ function startSession(): void {
// Run the harness from its own root so relative path resolution is stable.
cwd: harness,
stdio: ['ignore', 'pipe', 'inherit'],
env: { ...process.env, DSH_TELEMETRY_DISABLED: '1' },
// The desktop is the trusted surface that opts into plugin install.
env: { ...process.env, DSH_TELEMETRY_DISABLED: '1', DSH_ALLOW_PLUGIN_INSTALL: '1' },
})
let settled = false
let buffered = ''