The sidebar Plugins page now drives `ctx.pluginManager` from #4182 and the settings Plugin list tab goes back to read-only: its bundle form and row checkboxes are removed, and the page joins `listBundles` with `listPlugins` into one view per bundle, decides availability from the inventory's `managementAvailable`, switches bundles and rows through `setBundleEnabled` and `setPluginEnabled`, and confirms uninstalls. The manager gains what the guided install dialog needs: `inspect(spec)` reads a spec through the registry or a directory before anything installs and refuses specs without a bundle; `installBundle` takes a request id, streams each pnpm run as `plugin-manager/install-log`, reports its phases as `plugin-manager/install-state`, classifies a failed run into `packageResult.kind`, names the bundle it added, and restores `package.json` and `pnpm-lock.yaml` when the run fails, is cancelled, or adds a package without a bundle patch; `cancelInstall(requestId)` answers only after pnpm exited and the files are back; `listBundles` carries titles, one-liners, declared rows with their live entries, and the built-in rows a bundle overrides; every completed operation and every patch generation applied outside the manager emits `plugin-manager/changed`. The config gains `pnpmCommand` and `inspectTimeoutMs`. App boot emits `profile/reconciled` after each applied patch generation and finds a root Include mounted under its pinned id by another copy of the module. Row diagnostics, dependency checks, and the non-bundle dependency group are not carried over; the web e2e scaffold mounts a launcher-style managed profile instead of the removed profile runtime.
44 lines
2.0 KiB
TypeScript
44 lines
2.0 KiB
TypeScript
/**
|
|
* What a failed pnpm run was, read off how it ended and what it printed:
|
|
* pnpm names its failures with stable `ERR_PNPM_*` codes and Node's errno
|
|
* names, which the run's captured tail carries whatever the locale.
|
|
* @module @deepseek-ai/dsh-plugin-manager/install-failure
|
|
*/
|
|
|
|
import type { PluginInstallFailureKind } from './types.ts'
|
|
|
|
/** How a run ended, beyond its exit code. */
|
|
export interface InstallFailureFacts {
|
|
/** The output tail the failure reports. */
|
|
readonly log: string
|
|
/** The spawn error, when the child never ran. */
|
|
readonly cause?: unknown
|
|
/** Whether the run outlived its bound. */
|
|
readonly timedOut?: boolean
|
|
}
|
|
|
|
/** Patterns in the order they decide: a specific code before the generic network family. */
|
|
const LOG_KINDS: readonly [PluginInstallFailureKind, RegExp][] = [
|
|
['build-blocked', /ERR_PNPM_IGNORED_BUILDS|Ignored build scripts/],
|
|
['not-found', /ERR_PNPM_FETCH_404|\bE404\b|404 Not Found|Not Found - GET/],
|
|
['no-matching-version', /ERR_PNPM_NO_MATCHING_VERSION|\bETARGET\b|No matching version/],
|
|
['disk-full', /\bENOSPC\b|no space left on device/i],
|
|
['permission', /\bEACCES\b|\bEPERM\b|permission denied/i],
|
|
['integrity', /ERR_PNPM_TARBALL_INTEGRITY|ERR_PNPM_BAD_TARBALL_SIZE|\bEINTEGRITY\b/],
|
|
['network', /\bENOTFOUND\b|\bECONNRESET\b|\bETIMEDOUT\b|\bECONNREFUSED\b|\bEAI_AGAIN\b|ERR_PNPM_META_FETCH_FAIL|ERR_PNPM_FETCH_5\d\d|ERR_PNPM_FETCH_TIMEOUT|socket hang up|Could not resolve host|unable to access/],
|
|
]
|
|
|
|
/**
|
|
* Classify a failed run.
|
|
* @param facts - how the run ended and what it printed.
|
|
* @returns the kind, `unknown` when nothing in the facts names one.
|
|
*/
|
|
export function classifyInstallFailure(facts: InstallFailureFacts): PluginInstallFailureKind {
|
|
if (facts.timedOut === true) return 'timeout'
|
|
if ((facts.cause as NodeJS.ErrnoException | undefined)?.code === 'ENOENT') return 'pnpm-missing'
|
|
for (const [kind, pattern] of LOG_KINDS) {
|
|
if (pattern.test(facts.log)) return kind
|
|
}
|
|
return 'unknown'
|
|
}
|