Files
deepseek-harness/packages/boot/plugin-manager/tests/manager.spec.ts
T
Turtle a8bf3e7600 fix(plugin-manager): harden git allowBuilds key parsing
Capture git keys that contain spaces, skip keys whose git URL carries a
credential, and isolate Git configuration for the real pnpm test.
2026-10-22 15:38:14 +08:00

2059 lines
136 KiB
TypeScript

/** Persistent manager behavior through a real profile Include and Loader. */
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { realpath } from 'node:fs/promises'
import { once } from 'node:events'
import { createServer } from 'node:http'
import type { Socket } from 'node:net'
import { join, resolve } from 'node:path'
import { homedir, tmpdir } from 'node:os'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { execa } from 'execa'
import type { Context } from '@deepseek-ai/cordis'
import { afterAll, beforeAll, expect, it, onTestFinished, vi } from 'vitest'
import {
boot, composeEntries, initProfile, loadProfileDirectory, readProfilePatches, readProfileManifest,
reconcileProfilePatches, PluginPackages, readPluginMeta, getDshRuntimeVersion,
type ProfileContext, type RuntimeResolution,
} from '@deepseek-ai/dsh-app-boot'
import { OPTIONAL_BUNDLES, OFFICIAL_ON_DEMAND_CATALOG } from '@deepseek-ai/dsh-official-bundles'
import PluginManager, { type Config, type PluginChange, type PluginInstallLogChunk, type PluginInstallProgress, type PluginInstallRequestId } from '../src/index.ts'
import Hmr from '@deepseek-ai/dsh-hmr'
import Timer from '@deepseek-ai/cordis-plugin-timer'
import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
import { Group } from '@deepseek-ai/cordis-plugin-loader'
import * as operations from '../src/operations.ts'
import * as githubConnection from '../src/github-connection.ts'
import * as gitTransport from '../src/git-transport.ts'
import { parse, parseDocument } from 'yaml'
import { isolateGitCommandLineConfig } from './git-environment.ts'
// These cases install through real Git and pnpm, so the host's command-line configuration group must not reach their children.
let restoreGitCommandLineConfig: () => void
beforeAll(() => { restoreGitCommandLineConfig = isolateGitCommandLineConfig() })
afterAll(() => { restoreGitCommandLineConfig() })
async function fixture(reload: 'live' | 'startup' = 'live', overlay = false, prepare?: (ctx: Context) => void, config: Config = {}, packageManager?: ProfileContext['packageManager'], prepareFiles?: (dir: string) => void, sourceInstallation = false) {
const temporaryHome = mkdtempSync(join(tmpdir(), 'plugin-manager-'))
let owner: Context | undefined
onTestFinished(async () => { await owner?.fiber.dispose(); rmSync(temporaryHome, { recursive: true, force: true }) })
// pnpm resolves workspace roots through native realpath, including Windows 8.3 aliases.
const home = await realpath(temporaryHome)
const dir = join(home, 'profiles', 'test')
const anchor = sourceInstallation ? join(home, 'apps', 'cli', 'package.json') : join(home, 'package.json')
if (sourceInstallation) {
mkdirSync(join(home, 'apps', 'cli'), { recursive: true })
writeFileSync(join(home, 'package.json'), '{"name":"@deepseek-ai/dsh-root"}')
}
writeFileSync(anchor, JSON.stringify(sourceInstallation
? { name: '@deepseek-ai/dsh', dependencies: { '@deepseek-ai/dsh-app-boot': 'workspace:*' } }
: { name: 'installation', dependencies: {} }))
initProfile(dir, ['core', 'extra'])
const bundle = (name: string, rows: unknown[], packageRoot = dir) => {
const path = join(packageRoot, 'node_modules', name)
mkdirSync(path, { recursive: true })
writeFileSync(join(path, 'package.json'), JSON.stringify({ name, version: '1.0.0', dsh: { bundle: { patch: './cordis.patch.yml' } } }))
writeFileSync(join(path, 'cordis.patch.yml'), JSON.stringify([{ insert: rows }]))
writeFileSync(join(path, 'plugin.mjs'), 'export function apply(ctx, config) { if (config?.fail) throw new Error("test activation failed"); ctx.provide(config?.service ?? "managedProbe", true) }\n')
}
bundle('core', [{ id: 'manager', name: 'cordis:manager', config }])
bundle('extra', [{ id: 'managed', name: './plugin.mjs' }])
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { extra: '1.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
writeFileSync(join(dir, 'cordis.yml'), '[]\n')
prepareFiles?.(dir)
const overlays: PatchOptions[] = overlay ? [{ id: 'managed', disabled: true }] : []
const profile: ProfileContext = {
name: 'test',
...(packageManager === undefined ? {} : { packageManager }),
startedBundles: loadProfileDirectory('test', dir, anchor).layers.map(layer => layer.packageName),
dir, patchPath: join(dir, 'cordis.patch.yml'), installAnchor: anchor, cwd: home, home,
overlays, telemetryDisabledEnv: undefined,
}
const ctx = await boot('test', join(dir, 'cordis.yml'), readProfilePatches('test', profile), (ctx) => {
owner = ctx
ctx.provide('appReady', { onReady: (listener: () => void) => { listener(); return () => {} } })
prepare?.(ctx)
ctx.provide('profileContext', profile)
ctx.loader.builtins.manager = PluginManager
})
// What pnpm's own configuration names is read from pnpm before every registry plan; the fixture answers npm's own registry.
const registry = vi.spyOn(operations, 'readProfileRegistry').mockResolvedValue('https://registry.npmjs.org/')
const connection = vi.spyOn(githubConnection, 'checkGithubConnection').mockResolvedValue(undefined)
onTestFinished(() => { registry.mockRestore(); connection.mockRestore() })
let stopHmr = async () => {}
if (reload === 'live') {
await ctx.plugin(Timer)
const owner = await ctx.plugin(Hmr, { root: [], ignored: [], debounce: 0 })
stopHmr = () => owner.dispose()
await ctx.hmr.runExclusive(async () => {})
}
return { ctx, dir, manager: ctx.pluginManager, bundle, profile, stopHmr, overlays, connection }
}
it.each(['network', 'timeout'] as const)('stops a GitHub %s before pnpm and attributes it to the repository', async (kind) => {
const { manager, dir, connection } = await fixture()
const before = readFileSync(join(dir, 'package.json'), 'utf8')
const failure = { exitCode: 1, output: 'GitHub check failed', truncated: false, logPath: join(dir, 'git.log'), kind }
connection.mockResolvedValue(failure)
const pnpm = vi.spyOn(operations, 'runProfilePnpm')
onTestFinished(() => { pnpm.mockRestore() })
expect(await manager.installBundle('https://github.com/acme/dsh-plugin.git')).toMatchObject({
application: 'failed', changed: false, stage: 'install', failedAt: 'spec-host', packageResult: failure,
})
expect(pnpm).not.toHaveBeenCalled()
expect(connection).toHaveBeenCalledWith(expect.objectContaining({ host: 'github.com' }), dir, expect.objectContaining({ timeoutMs: 5000 }))
expect(readFileSync(join(dir, 'package.json'), 'utf8')).toBe(before)
})
it('leaves non-network GitHub errors to pnpm and forwards profile Git environment and deadline', async () => {
const env = { GIT_CONFIG_GLOBAL: '/application/git.config' }
const { manager, connection } = await fixture('live', false, undefined, { githubConnectionTimeoutMs: 8000 }, { command: 'pnpm', args: [], env })
connection.mockResolvedValue({ exitCode: 128, output: 'fatal: Authentication failed', truncated: false, logPath: 'git.log', kind: 'unknown' })
const failure = { exitCode: 1, output: 'pnpm owns authentication', truncated: false, logPath: 'pnpm.log', kind: 'unknown' as const }
const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockResolvedValue(failure)
onTestFinished(() => { pnpm.mockRestore() })
const result = await manager.installBundle('github:acme/private-plugin')
expect(result.application).toBe('failed')
expect(result.packageResult).toEqual(failure)
expect(pnpm).toHaveBeenCalledOnce()
expect(result.failedAt).toBeUndefined()
expect(connection).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.objectContaining({ timeoutMs: 8000, env }))
})
it('cancels an active GitHub check before starting pnpm', async () => {
const { manager, connection } = await fixture()
const entered = Promise.withResolvers<undefined>()
connection.mockImplementation(async (_spec, _dir, options) => {
entered.resolve(undefined)
await new Promise<void>((resolve) => { options.signal.addEventListener('abort', () => { resolve() }, { once: true }) })
return { exitCode: 1, output: 'cancelled', truncated: false, logPath: 'git.log', kind: 'unknown' }
})
const pnpm = vi.spyOn(operations, 'runProfilePnpm')
onTestFinished(() => { pnpm.mockRestore() })
const requestId = '824103ec-bc45-489d-bb85-5b4fe0aefc78' as PluginInstallRequestId
const installing = manager.installBundle('github:acme/dsh-plugin', { requestId })
await entered.promise
expect(await manager.cancelInstall(requestId)).toEqual({ status: 'cancelled' })
expect(await installing).toMatchObject({ application: 'cancelled', changed: false })
expect(pnpm).not.toHaveBeenCalled()
})
it('installs the spec the Git transport probe chooses with the profile Git environment and deadline', async () => {
const env = { GIT_CONFIG_GLOBAL: '/application/git.config' }
const { manager, dir } = await fixture('live', false, undefined, { gitTransportTimeoutMs: 7000 }, { command: 'pnpm', args: [], env })
const spec = 'git+https://git.example/team/sub/plugin.git#main'
const transport = vi.spyOn(gitTransport, 'chooseGitTransport').mockResolvedValue('git+ssh://[email protected]/team/sub/plugin.git#main')
const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockResolvedValue({ exitCode: 1, output: 'stop', truncated: false, logPath: 'pnpm.log', kind: 'unknown' })
onTestFinished(() => { transport.mockRestore(); pnpm.mockRestore() })
expect((await manager.installBundle(spec)).application).toBe('failed')
expect(transport).toHaveBeenCalledWith(expect.objectContaining({ kind: 'git', spec }), dir, expect.objectContaining({ timeoutMs: 7000, env }))
expect(pnpm.mock.calls[0]?.[1]).toEqual(['add', 'git+ssh://[email protected]/team/sub/plugin.git#main'])
})
it('attributes a failure to the host of the SSH spec the probe chose, which carries no HTTPS port', async () => {
const { manager } = await fixture()
const transport = vi.spyOn(gitTransport, 'chooseGitTransport').mockResolvedValue('git+ssh://[email protected]/team/plugin.git#main')
const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockResolvedValue({
exitCode: 1, output: 'ssh: Could not resolve hostname git.example: nodename nor servname provided', truncated: false, logPath: 'pnpm.log',
})
onTestFinished(() => { transport.mockRestore(); pnpm.mockRestore() })
expect(await manager.installBundle('git+https://git.example:8443/team/plugin.git#main')).toMatchObject({ application: 'failed', failedAt: 'spec-host' })
expect(pnpm).toHaveBeenCalledOnce()
})
it('cancels an active Git transport probe before starting pnpm', async () => {
const { manager } = await fixture()
const entered = Promise.withResolvers<undefined>()
const transport = vi.spyOn(gitTransport, 'chooseGitTransport').mockImplementation(async (spec, _dir, options) => {
entered.resolve(undefined)
await new Promise<void>((resolve) => { options.signal.addEventListener('abort', () => { resolve() }, { once: true }) })
return spec.spec
})
const pnpm = vi.spyOn(operations, 'runProfilePnpm')
onTestFinished(() => { transport.mockRestore(); pnpm.mockRestore() })
const requestId = '5f0c1f8e-3f5e-4a59-9a43-1d0c5c3f7a21' as PluginInstallRequestId
const installing = manager.installBundle('git+https://git.example/team/plugin.git', { requestId })
await entered.promise
expect(await manager.cancelInstall(requestId)).toEqual({ status: 'cancelled' })
expect(await installing).toMatchObject({ application: 'cancelled', changed: false })
expect(pnpm).not.toHaveBeenCalled()
})
it('waits for the GitHub check before installing and activating the bundle', async () => {
const { manager, dir, bundle, connection } = await fixture()
const entered = Promise.withResolvers<undefined>()
const release = Promise.withResolvers<undefined>()
connection.mockImplementation(async () => { entered.resolve(undefined); await release.promise; return undefined })
const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
bundle('addon', [])
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, addon: '1.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: 'installed', truncated: false, logPath: join(dir, 'pnpm.log') }
})
const installing = manager.installBundle('github:acme/addon')
onTestFinished(async () => { release.resolve(undefined); await installing; pnpm.mockRestore() })
await entered.promise
expect(pnpm).not.toHaveBeenCalled()
release.resolve(undefined)
expect(await installing).toMatchObject({ application: 'applied', changed: true, bundle: 'addon' })
expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toContain('addon')
})
it('disposal waits for an active GitHub check to stop', async () => {
const { ctx, manager, connection } = await fixture('startup')
const entered = Promise.withResolvers<undefined>()
const released = Promise.withResolvers<undefined>()
connection.mockImplementation(async (_spec, _dir, options) => {
entered.resolve(undefined)
await new Promise<void>((resolve) => { options.signal.addEventListener('abort', () => { resolve() }, { once: true }) })
released.resolve(undefined)
return { exitCode: 1, output: 'cancelled', truncated: false, logPath: 'git.log', kind: 'unknown' }
})
const pnpm = vi.spyOn(operations, 'runProfilePnpm')
onTestFinished(() => { pnpm.mockRestore() })
const installing = manager.installBundle('github:acme/addon')
await entered.promise
await ctx.fiber.dispose()
await released.promise
expect(await installing).toMatchObject({ application: 'cancelled', changed: false })
expect(pnpm).not.toHaveBeenCalled()
})
it.each(['github:acme/connected', 'https://github.com/acme/connected.git', 'git+ssh://[email protected]/acme/connected.git'])(
'installs %s through real Git and pnpm with private repository SSH fallback', async (spec) => {
const pnpm = fileURLToPath(new URL('../../../../apps/desktop/node_modules/pnpm/bin/pnpm.mjs', import.meta.url))
const env = { GIT_CONFIG_GLOBAL: '', GIT_CONFIG_NOSYSTEM: '1' }
const { manager, dir, connection } = await fixture('startup', false, undefined, {}, {
command: process.execPath, args: ['--expose-internals', pnpm], env,
})
connection.mockRestore()
const repository = join(dir, 'repository')
mkdirSync(repository)
env.GIT_CONFIG_GLOBAL = join(dir, 'git.config')
writeFileSync(env.GIT_CONFIG_GLOBAL, '')
const git = (args: string[]) => execa('git', args, { cwd: repository, env })
await git(['init', '--initial-branch=main'])
const name = '@test/github-connected'
writeFileSync(join(repository, 'package.json'), JSON.stringify({ name, version: '1.0.0', dsh: { bundle: { patch: './cordis.patch.yml' } } }))
writeFileSync(join(repository, 'cordis.patch.yml'), '[]\n')
await git(['add', '.'])
await git(['-c', 'user.name=Fixture', '-c', '[email protected]', '-c', 'commit.gpgsign=false', 'commit', '-m', 'fixture bundle'])
const requests: string[] = []
const sockets = new Set<Socket>()
const proxy = createServer((_request, response) => {
response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="fixture"' })
response.end()
})
proxy.on('connection', (socket) => {
sockets.add(socket)
socket.once('close', () => { sockets.delete(socket) })
})
proxy.on('connect', (request, socket) => {
requests.push(request.url ?? '')
socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n')
})
onTestFinished(async () => {
for (const socket of sockets) socket.destroy()
await new Promise<void>((resolve, reject) => { proxy.close((error) => { if (error) reject(error); else resolve() }) })
})
proxy.listen(0, '127.0.0.1')
await once(proxy, 'listening')
const address = proxy.address()
if (address === null || typeof address === 'string') throw new Error('proxy did not bind a TCP port')
const proxyUrl = `http://127.0.0.1:${String(address.port)}`
writeFileSync(env.GIT_CONFIG_GLOBAL, `[url "${pathToFileURL(repository).href}"]\n insteadOf = ssh://[email protected]/acme/connected.git\n insteadOf = [email protected]:acme/connected.git\n[url "${proxyUrl}/"]\n insteadOf = https://github.com/\n[http]\n proxy =\n`)
// pnpm probes HTTPS with Node before falling back to SSH, even for an SSH spec.
// Route that HTTP request to our proxy too; Git's URL rewrite alone cannot isolate it.
const manifest = readProfileManifest('test', dir)
delete manifest.dependencies
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
writeFileSync(join(dir, '.npmrc'), `store-dir=${join(dir, 'store').replaceAll('\\', '/')}\nhttps-proxy=${proxyUrl}\nproxy=${proxyUrl}\nnoproxy=\n`)
const result = await manager.installBundle(spec, { enabled: false })
expect(result.error).toBeUndefined()
expect(result).toMatchObject({ changed: true, bundle: name, packageResult: { exitCode: 0 } })
expect(readProfileManifest('test', dir).dependencies).toHaveProperty(name)
expect(requests).toContain('github.com:443')
},
)
it.each(['missing package', 'invalid manifest', 'not a bundle', 'missing patch', 'invalid patch'])(
'boots with %s, lists its error, and permits deselection without enabling the broken bundle', async (failure) => {
const { manager, dir, ctx } = await fixture('live', false, undefined, {}, undefined, (dir) => {
const path = join(dir, 'node_modules', 'extra')
if (failure === 'missing package') rmSync(path, { recursive: true })
if (failure === 'invalid manifest') writeFileSync(join(path, 'package.json'), '{')
if (failure === 'not a bundle') writeFileSync(join(path, 'package.json'), '{}')
if (failure === 'missing patch') rmSync(join(path, 'cordis.patch.yml'))
if (failure === 'invalid patch') writeFileSync(join(path, 'cordis.patch.yml'), '[invalid')
})
expect([...ctx.loader.entries()].some(entry => entry.id === 'include:managed')).toBe(false)
expect((await manager.listBundles()).find(row => row.name === 'extra')).toMatchObject({
enabled: true, source: '[email protected]', error: { code: failure === 'not a bundle' ? 'not-bundle' : 'operation-error' }, rows: [],
})
expect(await manager.setBundleEnabled('extra', false)).toMatchObject({ changed: true, application: 'applied' })
expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toEqual(['core'])
expect(await manager.setBundleEnabled('extra', true)).toMatchObject({ changed: false, application: 'failed' })
},
)
it.each(['missing files', 'missing declaration'])('keeps a running management bundle protected with %s', async (failure) => {
const { manager, dir } = await fixture()
if (failure === 'missing files') rmSync(join(dir, 'node_modules', 'core'), { recursive: true })
else writeFileSync(join(dir, 'node_modules', 'core', 'package.json'), '{}')
expect((await manager.listBundles()).find(row => row.name === 'core')).toMatchObject({
enabled: true, readOnlyReason: 'management-required', removable: false, error: { code: failure === 'missing files' ? 'operation-error' : 'not-bundle' },
})
expect(await manager.setBundleEnabled('core', false)).toMatchObject({
changed: false, application: 'failed', error: { code: 'management-required' },
})
})
it.each(['live', 'startup'] as const)('removes a bundle skipped at startup in a %s profile', async (mode) => {
const { manager, dir } = await fixture(mode, false, undefined, {}, undefined, (dir) => {
rmSync(join(dir, 'node_modules', 'extra'), { recursive: true })
})
const remove = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
const manifest = readProfileManifest('test', dir)
expect(manifest.dsh?.profile?.bundles).toEqual(['core'])
delete manifest.dependencies?.extra
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: 'removed', truncated: false, logPath: join(dir, 'pnpm.log') }
})
onTestFinished(() => { remove.mockRestore() })
expect(await manager.removeBundle('extra')).toMatchObject({ changed: true, application: mode === 'live' ? 'applied' : 'restart-required' })
expect((await manager.listBundles()).some(row => row.name === 'extra')).toBe(false)
})
it('recomposes the remaining layers while another bundle is broken and mounts it after repair', async () => {
const { manager, dir, bundle, ctx } = await fixture()
writeFileSync(join(dir, 'node_modules', 'extra', 'cordis.patch.yml'), '[invalid')
bundle('another', [])
expect(await manager.setBundleEnabled('another', true)).toMatchObject({ application: 'applied' })
expect([...ctx.loader.entries()].some(entry => entry.id === 'include:managed')).toBe(false)
bundle('extra', [{ id: 'managed', name: './plugin.mjs' }])
expect(await manager.setBundleEnabled('another', false)).toMatchObject({ application: 'applied' })
expect([...ctx.loader.entries()].find(entry => entry.id === 'include:managed')?.fiber?.state).toBe(2)
expect((await manager.listBundles()).find(row => row.name === 'extra')?.error).toBeUndefined()
})
it('lists bundle versions and current-profile plugin targets', async () => {
const { manager, dir } = await fixture()
const plugins = await manager.listPlugins()
expect(plugins.find(row => row.entryId === 'include:managed')).toMatchObject({ patchId: 'managed', enabled: true })
expect(plugins.find(row => row.entryId === 'include:manager')?.readOnlyReason).toBe('management-required')
expect((await manager.listBundles()).filter(bundle => bundle.installTarget === undefined)).toEqual([
{
name: 'core', version: '1.0.0', enabled: true, installed: false, official: false, availability: 'profile', optional: false, removable: false, updatable: false, readOnlyReason: 'management-required',
meta: { title: 'core' },
rows: [{ rowId: 'manager', moduleName: 'cordis:manager', entryId: 'include:manager' }], overrides: [],
},
{
name: 'extra', version: '1.0.0', source: '[email protected]', enabled: true, installed: true, official: false, availability: 'profile', optional: false, removable: true, updatable: true,
meta: { title: 'extra' },
rows: [{ rowId: 'managed', moduleName: pathToFileURL(join(dir, 'node_modules', 'extra', 'plugin.mjs')).href, entryId: 'include:managed' }], overrides: [],
},
])
})
it('describes a bundle by its manifest and patch: one-liner, rows without a live entry, and the built-in rows it changes', async () => {
const { manager, dir, bundle } = await fixture()
bundle('described', [{ id: 'described-row', name: './plugin.mjs' }])
writeFileSync(join(dir, 'node_modules', 'described', 'package.json'), JSON.stringify({
name: 'described', version: '2.0.0', description: 'Describes itself.', dsh: { bundle: { patch: './cordis.patch.yml' } },
}))
// An anonymous row is not addressable and is left out of the rows.
writeFileSync(join(dir, 'node_modules', 'described', 'cordis.patch.yml'), JSON.stringify([
{ insert: [{ id: 'described-row', name: './plugin.mjs' }, { name: './plugin.mjs' }] }, { id: 'managed', disabled: true }, { id: 'described-row', config: {} },
]))
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, described: '2.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
const moduleName = pathToFileURL(join(dir, 'node_modules', 'described', 'plugin.mjs')).href
expect((await manager.listBundles()).find(row => row.name === 'described')).toEqual({
name: 'described', version: '2.0.0', description: 'Describes itself.', source: '[email protected]', enabled: false, installed: true, official: false, availability: 'profile', optional: false, removable: true, updatable: true,
meta: { title: 'described', description: 'Describes itself.' },
rows: [{ rowId: 'described-row', moduleName }], overrides: ['managed'],
})
await manager.setBundleEnabled('described', true)
expect((await manager.listBundles()).find(row => row.name === 'described')?.rows).toEqual([
{ rowId: 'described-row', moduleName, entryId: 'include:described-row' },
])
// Off again, the rows lose their entries.
await manager.setBundleEnabled('described', false)
expect((await manager.listBundles()).find(row => row.name === 'described')?.rows).toEqual([{ rowId: 'described-row', moduleName }])
})
it('projects each bundle\'s default GUI listing without changing its selection, and reports invalid declarations', async () => {
const { manager, dir } = await fixture()
const path = join(dir, 'node_modules', 'extra', 'package.json')
const manifest = JSON.parse(readFileSync(path, 'utf8')) as { dsh: { bundle: { patch: string; listedByDefault?: boolean | string } } }
for (const listedByDefault of [false, true, undefined]) {
if (listedByDefault === undefined) delete manifest.dsh.bundle.listedByDefault
else manifest.dsh.bundle.listedByDefault = listedByDefault
writeFileSync(path, JSON.stringify(manifest))
const entry = (await manager.listBundles()).find(row => row.name === 'extra')
expect(entry?.listedByDefault).toBe(listedByDefault)
expect(entry?.enabled).toBe(true)
expect(entry?.error).toBeUndefined()
}
manifest.dsh.bundle.listedByDefault = 'hidden'
writeFileSync(path, JSON.stringify(manifest))
expect((await manager.listBundles()).find(row => row.name === 'extra')).toMatchObject({ enabled: true,
error: { diagnostic: 'dsh.bundle.listedByDefault must be a boolean' } })
})
it('names where each installed bundle comes from as a spec pnpm installs', async () => {
const { manager, dir, bundle, profile } = await fixture()
const recorded: Record<string, string> = {
extra: '^1.0.0', tagged: 'latest', aliased: 'npm:@acme/aliased@2', jsr: 'jsr:@acme/jsr@^1', github: 'github:someone/dsh-plugin#v1',
ssh: '[email protected]:someone/dsh-plugin.git', deploy: '[email protected]:team/dsh-plugin.git',
sshUrl: 'git+ssh://[email protected]/someone/dsh-plugin.git', email: 'git+http://[email protected]:[email protected]/repo.git',
tarball: 'https://cdn.example.com/dsh-x-1.0.0.tgz', token: 'https://[email protected]/dsh-x-1.0.0.tgz',
password: 'git+https://someone:[email protected]/someone/dsh-plugin.git#main',
relative: 'file:../plugins/relative', linked: 'link:/plugins/linked', home: 'file:~/plugins/home',
aliasLocal: 'file:../plugins/original', shadowed: '1.0.0',
}
for (const name of Object.keys(recorded)) bundle(name, [])
// Installed under a name other than its own, the package keeps that name only when the spec carries it.
writeFileSync(join(dir, 'node_modules', 'aliasLocal', 'package.json'), JSON.stringify({ name: 'original', version: '1.0.0', dsh: { bundle: { patch: './cordis.patch.yml' } } }))
const manifest = readProfileManifest('test', dir)
manifest.dependencies = recorded
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
// The installation's own copy of a bundle is the one that loads, so the profile's dependency on it names nothing.
writeFileSync(profile.installAnchor, JSON.stringify({ name: 'installation', dependencies: { shadowed: '1.0.0' } }))
expect(Object.fromEntries((await manager.listBundles())
.filter(row => row.installTarget === undefined).map(row => [row.name, row.source]))).toEqual({
core: undefined, extra: 'extra@^1.0.0', tagged: 'tagged@latest', aliased: 'aliased@npm:@acme/aliased@2', jsr: 'jsr@jsr:@acme/jsr@^1',
github: 'github:someone/dsh-plugin#v1', ssh: '[email protected]:someone/dsh-plugin.git', deploy: '[email protected]:team/dsh-plugin.git',
sshUrl: 'git+ssh://[email protected]/someone/dsh-plugin.git', email: 'git+http://git.example.com/repo.git',
tarball: 'https://cdn.example.com/dsh-x-1.0.0.tgz', token: 'https://cdn.example.com/dsh-x-1.0.0.tgz',
password: 'git+https://git.example.com/someone/dsh-plugin.git#main',
relative: `file:${resolve(dir, '../plugins/relative')}`, linked: `link:${resolve(dir, '/plugins/linked')}`,
home: `file:${resolve(homedir(), 'plugins/home')}`, aliasLocal: `aliasLocal@file:${resolve(dir, '../plugins/original')}`, shadowed: undefined,
})
})
it.each(['native', 'runtime'] as const)('reads a disabled bundle and its independent exported plugins without running them with %s resolution', async (mode) => {
const { ctx, manager, dir, bundle, profile } = await fixture('startup')
bundle('localized', [{ id: 'first', name: 'local-child/first' }, { id: 'second', name: 'local-child/second' }])
const root = join(dir, 'node_modules', 'localized')
const child = mode === 'runtime'
? join(profile.home, 'installation', 'node_modules', 'local-child')
: join(root, 'node_modules', 'local-child')
mkdirSync(join(root, 'locale'), { recursive: true })
mkdirSync(join(child, 'first-locale'), { recursive: true })
mkdirSync(join(child, 'second-locale'), { recursive: true })
writeFileSync(join(root, 'package.json'), JSON.stringify({ name: 'localized',
exports: { './locale/*.json': './locale/*.json' }, dsh: {
bundle: { patch: './cordis.patch.yml' },
} }))
writeFileSync(join(root, 'locale', 'en.json'), '{"meta":{"title":"Local bundle"}}')
writeFileSync(join(root, 'locale', 'zh.json'), '{"meta":{"title":"本地组合包"}}')
writeFileSync(join(child, 'package.json'), JSON.stringify({ name: 'local-child', exports: {
'./first': './index.js', './second': './index.js',
'./first/locale/*.json': './first-locale/*.json', './second/locale/*.json': './second-locale/*.json',
} }))
writeFileSync(join(child, 'index.js'), 'throw new Error("must not execute")\n')
writeFileSync(join(child, 'first-locale', 'en.json'), '{"meta":{"title":"First plugin"}}')
writeFileSync(join(child, 'second-locale', 'en.json'), '{"meta":{"title":"Second plugin"}}')
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, localized: '1.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
const parentURL = pathToFileURL(join(root, 'package.json')).href
const readChildren = () => ['first', 'second'].map(part => readPluginMeta(`local-child/${part}`, parentURL))
const expectUnlinked = () => {
expect(lstatSync(child).isDirectory()).toBe(true)
for (const path of [
join(root, 'node_modules'), join(dir, 'node_modules', 'local-child'),
join(profile.home, 'profiles', 'node_modules'), join(profile.home, 'node_modules'),
join(dir, '.dsh-module-fallback'),
]) expect(lstatSync(path, { throwIfNoEntry: false }), path).toBeUndefined()
}
const resolution: RuntimeResolution = {
profilesDir: join(profile.home, 'profiles'), profileDir: dir,
localPackageNames: Object.keys(manifest.dependencies),
linkedRoots: [],
entries: [{ name: 'local-child', packageDir: child, version: undefined,
declarer: join(child, 'package.json'), scope: 'installation' }],
}
if (mode === 'runtime') {
expectUnlinked()
expect(readChildren()).toEqual([undefined, undefined])
}
await ctx.plugin(PluginPackages, mode === 'runtime' ? { resolution } : {})
const result = (await manager.listBundles()).find(row => row.name === 'localized')
expect(result).toMatchObject({ enabled: false, meta: { title: { en: 'Local bundle', zh: '本地组合包' } }, rows: [
{ rowId: 'first', moduleName: 'local-child/first', meta: { title: { en: 'First plugin' } } },
{ rowId: 'second', moduleName: 'local-child/second', meta: { title: { en: 'Second plugin' } } },
] })
expect(result?.rows[0]?.entryId).toBeUndefined()
expect(readPluginMeta('local-child/private', parentURL)).toBeUndefined()
if (mode === 'runtime') {
expectUnlinked()
await ctx.fiber.dispose()
expect(readChildren()).toEqual([undefined, undefined])
expectUnlinked()
}
})
it.each([
{ resources: 'exported', exports: { './locale/*.json': './locale/*.json' }, expected: { meta: { title: { en: 'Nameless bundle' } } } },
{ resources: 'private', exports: {}, expected: {} },
])('lists a bundle without a manifest name with $resources locale resources', async ({ exports, expected }) => {
const { manager, dir, bundle } = await fixture('startup')
bundle('unnamed', [])
const root = join(dir, 'node_modules', 'unnamed')
mkdirSync(join(root, 'locale'))
writeFileSync(join(root, 'package.json'), JSON.stringify({
version: '1.0.0', exports, dsh: { bundle: { patch: './cordis.patch.yml' } },
}))
writeFileSync(join(root, 'locale', 'en.json'), '{"meta":{"title":"Nameless bundle"}}')
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, unnamed: '1.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
expect((await manager.listBundles()).find(row => row.name === 'unnamed')).toEqual({
name: 'unnamed', version: '1.0.0', source: '[email protected]', enabled: false, installed: true, official: false, availability: 'profile', optional: false, removable: true, updatable: true,
rows: [], overrides: [], ...expected,
})
})
it('turns a plugin off and on without leaving a patch override the bundle already implies', async () => {
const { manager, dir } = await fixture()
const id = (await manager.listPlugins()).find(row => row.patchId === 'managed')!.entryId
expect(await manager.setPluginEnabled(id, false)).toMatchObject({ changed: true, application: 'applied' })
expect((await manager.listPlugins()).find(row => row.entryId === id)?.enabled).toBe(false)
expect(await manager.setPluginEnabled(id, false)).toMatchObject({ changed: false, application: 'applied' })
expect(readFileSync(join(dir, 'cordis.patch.yml'), 'utf8').match(/id: managed/g)).toHaveLength(1)
expect(await manager.setPluginEnabled(id, true)).toMatchObject({ changed: true, application: 'applied' })
expect((await manager.listPlugins()).find(row => row.entryId === id)?.enabled).toBe(true)
expect(readFileSync(join(dir, 'cordis.patch.yml'), 'utf8')).not.toContain('id: managed')
})
it('retains installed dependencies when toggling a bundle and appends it when re-enabled', async () => {
const { manager, dir, bundle } = await fixture()
bundle('third', [])
await manager.setBundleEnabled('third', true)
expect(await manager.setBundleEnabled('extra', false)).toMatchObject({ changed: true, application: 'applied' })
expect(await manager.setBundleEnabled('extra', false)).toMatchObject({ changed: false, application: 'applied' })
expect(readProfileManifest('test', dir).dependencies).toEqual({ extra: '1.0.0' })
expect((await manager.listPlugins()).some(row => row.patchId === 'managed')).toBe(false)
await manager.setBundleEnabled('extra', true)
expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toEqual(['core', 'third', 'extra'])
})
it('reports an overlay overriding a saved plugin toggle', async () => {
const { manager, dir } = await fixture('live', true)
writeFileSync(join(dir, 'cordis.patch.yml'), '- id: managed\n disabled: true\n')
const id = (await manager.listPlugins()).find(row => row.patchId === 'managed')!.entryId
expect(await manager.setPluginEnabled(id, true)).toMatchObject({ changed: true, application: 'overridden' })
expect(await manager.setPluginEnabled(id, true)).toMatchObject({ changed: false, application: 'overridden' })
})
it('saves startup-only toggles and refuses removal of currently used packages', async () => {
const { manager } = await fixture('startup')
const id = (await manager.listPlugins()).find(row => row.patchId === 'managed')!.entryId
expect(await manager.setPluginEnabled(id, false)).toMatchObject({ application: 'restart-required' })
expect((await manager.listPlugins()).find(row => row.entryId === id)?.enabled).toBe(true)
await manager.setBundleEnabled('extra', false)
expect(await manager.removeBundle('extra')).toMatchObject({ changed: false, application: 'failed', error: { code: 'stop-profile' } })
})
it('refuses self-disable, unknown entries and removal of installation-owned bundles', async () => {
const { manager } = await fixture()
const id = (await manager.listPlugins()).find(row => row.entryId === 'include:manager')!.entryId
expect(await manager.setPluginEnabled(id, false)).toMatchObject({ changed: false, application: 'failed', error: { code: 'management-required' } })
expect(await manager.setPluginEnabled('missing' as typeof id, true)).toMatchObject({ changed: false, application: 'failed', error: { code: 'unknown-plugin' } })
expect(await manager.removeBundle('core')).toMatchObject({ changed: false, application: 'failed', error: { code: 'not-removable' } })
// A name no bundle directory answers to fails with the resolver's own diagnostic.
expect(await manager.setBundleEnabled('unknown', true)).toMatchObject({ changed: false, application: 'failed', error: { code: 'operation-error' } })
})
it('installs only valid bundle declarations and honors installation without activation', async () => {
const { manager, dir, bundle } = await fixture()
const initial = readProfileManifest('test', dir)
delete initial.dependencies
writeFileSync(join(dir, 'package.json'), JSON.stringify(initial))
const install = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async (_context, args) => {
const name = String(args[1])
bundle(name, [{ id: name, name: './plugin.mjs', config: { service: name } }])
if (name === 'another-bundle') {
writeFileSync(join(dir, 'node_modules', name, 'package.json'), JSON.stringify({ name, dsh: { bundle: { patch: './cordis.patch.yml' } } }))
}
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, [name]: '1.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: 'installed', truncated: false, logPath: join(dir, 'pnpm.log') }
})
onTestFinished(() => { install.mockRestore() })
expect(await manager.installBundle('new-bundle', { enabled: false })).toMatchObject({
changed: true, application: 'applied', stage: 'enable', target: 'new-bundle', bundle: 'new-bundle', version: '1.0.0', packageResult: { exitCode: 0 },
})
expect((await manager.listBundles()).find(row => row.name === 'new-bundle')?.enabled).toBe(false)
expect(await manager.setBundleEnabled('new-bundle', true)).toMatchObject({ application: 'applied' })
expect((await manager.listPlugins()).find(row => row.patchId === 'new-bundle')?.fiberPhase).toBe('active')
const another = await manager.installBundle('another-bundle')
expect(another).toMatchObject({ application: 'applied', bundle: 'another-bundle' })
expect(another).not.toHaveProperty('version')
expect((await manager.listBundles()).find(row => row.name === 'another-bundle')?.enabled).toBe(true)
})
it('reports blocked scripts after a failed installation and retries only after explicit profile build approval', async () => {
const { manager, dir, bundle } = await fixture()
const policy = join(dir, 'pnpm-workspace.yaml')
const run = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
const manifest = readProfileManifest('test', dir)
const completion = { exitCode: 0, output: '', truncated: false, logPath: join(dir, 'pnpm.log') }
manifest.dependencies = { ...manifest.dependencies, addon: '1.0.0' }
if (run.mock.calls.length === 1) {
writeFileSync(policy, 'allowBuilds:\n native: set this to true or false\n denied: false\n')
completion.exitCode = 1
completion.output = 'ERR_PNPM_IGNORED_BUILDS'
} else {
expect(parse(readFileSync(policy, 'utf8'))).toEqual({ allowBuilds: { native: true, denied: false } })
bundle('addon', [])
}
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return completion
})
onTestFinished(() => { run.mockRestore() })
// The failed run's manifest change is put back; the policy pnpm wrote stays, so its undecided names can be offered.
expect(await manager.installBundle('addon')).toMatchObject({ application: 'failed', pendingBuilds: ['native'] })
expect(readProfileManifest('test', dir).dependencies).not.toHaveProperty('addon')
expect(parse(readFileSync(policy, 'utf8'))).toMatchObject({ allowBuilds: { native: 'set this to true or false' } })
expect(await manager.installBundle('addon', { approvedBuilds: ['denied'] })).toMatchObject({ application: 'failed', changed: false, error: { code: 'stale-approval' } })
expect(run).toHaveBeenCalledTimes(1)
expect(await manager.installBundle('addon', { approvedBuilds: ['native'], enabled: false })).toMatchObject({
application: 'applied', changed: true, approvedBuilds: ['native'],
})
expect(readProfileManifest('test', dir).dsh?.profile?.bundles).not.toContain('addon')
})
it('retains approved policy and reports it as changed when the registry fails before adding a dependency', async () => {
const { manager, dir } = await fixture()
writeFileSync(join(dir, 'pnpm-workspace.yaml'), 'allowBuilds:\n native: set this to true or false\n')
const run = vi.spyOn(operations, 'runProfilePnpm').mockResolvedValue({ exitCode: 1, output: 'registry unavailable', truncated: false, logPath: '/log' })
onTestFinished(() => { run.mockRestore() })
expect(await manager.installBundle('addon', { approvedBuilds: ['native'] })).toMatchObject({
changed: true, application: 'failed', pendingBuilds: [], approvedBuilds: ['native'], error: { diagnostic: 'registry unavailable' },
})
expect(parse(readFileSync(join(dir, 'pnpm-workspace.yaml'), 'utf8'))).toEqual({ allowBuilds: { native: true } })
})
it('runs a real pnpm dependency script only after approval and retry', async () => {
const { manager, dir, profile } = await fixture('startup')
const addon = join(profile.cwd, 'addon')
mkdirSync(addon)
writeFileSync(join(addon, 'package.json'), JSON.stringify({ name: 'approval-fixture-addon', version: '1.0.0',
scripts: { install: 'node build.cjs' }, dsh: { bundle: { patch: './cordis.patch.yml' } } }))
writeFileSync(join(addon, 'build.cjs'), 'require("node:fs").writeFileSync("built.txt", "built")\n')
writeFileSync(join(addon, 'cordis.patch.yml'), '[]\n')
writeFileSync(join(dir, 'package.json'), '{"name":"approval-fixture","private":true}\n')
const policy = parseDocument(readFileSync(join(dir, 'pnpm-workspace.yaml'), 'utf8'))
policy.set('offline', true)
policy.set('storeDir', join(profile.cwd, 'store'))
writeFileSync(join(dir, 'pnpm-workspace.yaml'), String(policy))
const blocked = await manager.installBundle('file:./addon', { enabled: false })
expect(blocked, JSON.stringify(blocked)).toMatchObject({ application: 'failed', packageResult: { kind: 'build-blocked' } })
expect(blocked.pendingBuilds).toHaveLength(1)
const built = join(dir, 'node_modules', 'approval-fixture-addon', 'built.txt')
expect(existsSync(built)).toBe(false)
expect(readProfileManifest('test', dir).dependencies?.['approval-fixture-addon']).toBeUndefined()
const allowed = await manager.installBundle('file:./addon', { enabled: false, approvedBuilds: blocked.pendingBuilds! })
expect(allowed, JSON.stringify(allowed)).toMatchObject({ application: 'restart-required', packageResult: { exitCode: 0 } })
expect(readFileSync(built, 'utf8')).toBe('built')
})
it('offers approval for a real git-hosted package pnpm refused to prepare', async () => {
// Both the fixture's Git and the manager's pnpm child read only this empty configuration.
const env = { GIT_CONFIG_GLOBAL: '', GIT_CONFIG_NOSYSTEM: '1' }
const pnpm = fileURLToPath(new URL('../../../../apps/desktop/node_modules/pnpm/bin/pnpm.mjs', import.meta.url))
const { manager, dir, profile } = await fixture('startup', false, undefined, {}, { command: process.execPath, args: ['--expose-internals', pnpm], env })
const repository = join(profile.cwd, 'repository')
mkdirSync(repository)
env.GIT_CONFIG_GLOBAL = join(profile.cwd, 'git.config')
writeFileSync(env.GIT_CONFIG_GLOBAL, '')
const git = (args: string[]) => execa('git', args, { cwd: repository, env })
await git(['init', '--initial-branch=main'])
writeFileSync(join(repository, 'package.json'), JSON.stringify({ name: '@test/git-prepared', version: '1.0.0',
scripts: { prepare: 'node build.cjs' }, dsh: { bundle: { patch: './cordis.patch.yml' } } }))
writeFileSync(join(repository, 'build.cjs'), 'require("node:fs").writeFileSync("built.txt", "built")\n')
writeFileSync(join(repository, 'cordis.patch.yml'), '[]\n')
await git(['add', '.'])
await git(['-c', 'user.name=Fixture', '-c', '[email protected]', '-c', 'commit.gpgsign=false', 'commit', '-m', 'fixture bundle'])
writeFileSync(join(dir, 'package.json'), '{"name":"git-approval-fixture","private":true}\n')
const policy = parseDocument(readFileSync(join(dir, 'pnpm-workspace.yaml'), 'utf8'))
policy.set('storeDir', join(profile.cwd, 'store'))
writeFileSync(join(dir, 'pnpm-workspace.yaml'), String(policy))
const spec = `git+${pathToFileURL(repository).href}`
const blocked = await manager.installBundle(spec, { enabled: false })
expect(blocked, JSON.stringify(blocked)).toMatchObject({ application: 'failed', packageResult: { kind: 'build-blocked' } })
expect(blocked.pendingBuilds).toEqual([expect.stringMatching(/^@test\/git-prepared@git\+file:.*#[0-9a-f]{40}$/)])
const built = join(dir, 'node_modules', '@test', 'git-prepared', 'built.txt')
expect(existsSync(built)).toBe(false)
const allowed = await manager.installBundle(spec, { enabled: false, approvedBuilds: blocked.pendingBuilds! })
expect(allowed, JSON.stringify(allowed)).toMatchObject({ application: 'restart-required', packageResult: { exitCode: 0 } })
expect(readFileSync(built, 'utf8')).toBe('built')
})
it.each(['[', 'allowBuilds: false\n'])('preserves pnpm diagnostics when pending approvals cannot be read: %s', async (policy) => {
const { manager, dir } = await fixture()
writeFileSync(join(dir, 'pnpm-workspace.yaml'), policy)
const run = vi.spyOn(operations, 'runProfilePnpm').mockResolvedValue({ exitCode: 1, output: 'original pnpm failure', truncated: false, logPath: '/log' })
onTestFinished(() => { run.mockRestore() })
expect(await manager.installBundle('addon')).toMatchObject({ application: 'failed', error: { diagnostic: 'original pnpm failure' } })
})
it('unloads before removing packages and retries inactive dependencies whose files are missing', async () => {
const { manager, dir, ctx } = await fixture()
const remove = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
await ctx.hmr.runExclusive(async () => {
expect([...ctx.loader.entries()].some(row => row.id === 'include:managed')).toBe(false)
})
return { exitCode: 1, output: 'removal failed', truncated: false, logPath: join(dir, 'pnpm.log') }
})
onTestFinished(() => { remove.mockRestore() })
expect(await manager.removeBundle('extra')).toMatchObject({ changed: true, application: 'failed', packageResult: { exitCode: 1 } })
expect(readProfileManifest('test', dir).dependencies).toEqual({ extra: '1.0.0' })
expect((await manager.listBundles()).find(row => row.name === 'extra')?.enabled).toBe(false)
rmSync(join(dir, 'node_modules', 'extra'), { recursive: true })
remove.mockImplementationOnce(async () => {
const manifest = readProfileManifest('test', dir)
delete manifest.dependencies?.extra
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: 'removed', truncated: false, logPath: join(dir, 'pnpm.log') }
})
expect(await manager.removeBundle('extra')).toMatchObject({ changed: true, application: 'applied' })
expect((await manager.listBundles()).some(row => row.name === 'extra')).toBe(false)
})
it('treats a terminated run that trapped the signal and exited zero as a failure', async () => {
const { manager, dir } = await fixture()
const install = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
// pnpm rewrote the manifest before it trapped the manager's signal.
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, half: '1.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: 'installing\n', truncated: false, logPath: join(dir, 'pnpm.log'), timedOut: true }
})
onTestFinished(() => { install.mockRestore() })
const result = await manager.installBundle('half', { enabled: false })
expect(result).toMatchObject({ application: 'failed', packageResult: { exitCode: 0, timedOut: true, kind: 'timeout' } })
// The half-written dependency goes back, and nothing is activated.
expect(readProfileManifest('test', dir).dependencies).toEqual({ extra: '1.0.0' })
expect((await manager.listBundles()).some(row => row.name === 'half')).toBe(false)
})
it('reports a terminated removal as failed even when pnpm exited zero', async () => {
const { manager, dir } = await fixture()
const remove = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
// The unload already happened, so the removal is reported through its own run.
const manifest = readProfileManifest('test', dir)
delete manifest.dependencies?.extra
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: 'removing\n', truncated: false, logPath: join(dir, 'pnpm.log'), timedOut: true }
})
onTestFinished(() => { remove.mockRestore() })
expect(await manager.removeBundle('extra')).toMatchObject({
application: 'failed', packageResult: { exitCode: 0, timedOut: true, kind: 'timeout' },
})
})
it('restores the manifest and lockfile after a failed package run, classifying the failure', async () => {
const { manager, dir } = await fixture()
const lockPath = join(dir, 'pnpm-lock.yaml')
const install = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, partial: '1' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
writeFileSync(lockPath, 'partial lockfile\n')
return { exitCode: 42, output: 'ERR_PNPM_META_FETCH_FAIL GET https://registry/partial: ENOTFOUND', truncated: false, logPath: join(dir, 'pnpm.log') }
})
onTestFinished(() => { install.mockRestore() })
const before = readFileSync(join(dir, 'package.json'), 'utf8')
// A registry that does not answer sends the installation on to the configured fallback; the failure reported is the last one's.
expect(await manager.installBundle('partial')).toMatchObject({
changed: false, application: 'failed', stage: 'install', target: 'partial',
error: { code: 'operation-error', diagnostic: expect.stringContaining('ENOTFOUND') as string },
packageResult: { exitCode: 42, kind: 'network' }, registries: [null, MIRROR],
})
expect(readFileSync(join(dir, 'package.json'), 'utf8')).toBe(before)
// A lockfile the run created is removed; one that existed is put back.
expect(existsSync(lockPath)).toBe(false)
writeFileSync(lockPath, 'original lockfile\n')
expect(await manager.installBundle('partial')).toMatchObject({ changed: false, application: 'failed' })
expect(readFileSync(lockPath, 'utf8')).toBe('original lockfile\n')
expect((await manager.listBundles()).some(row => row.name === 'partial')).toBe(false)
expect(install).toHaveBeenCalledTimes(4)
})
it('keeps saved changes after activation failure and allows a corrected configuration to retry', async () => {
const { manager, dir } = await fixture()
writeFileSync(join(dir, 'cordis.patch.yml'), '- id: managed\n disabled: true\n config: { fail: true }\n')
const id = (await manager.listPlugins()).find(row => row.patchId === 'managed')!.entryId
expect(await manager.setPluginEnabled(id, true)).toMatchObject({ changed: true, application: 'failed' })
expect(readFileSync(join(dir, 'cordis.patch.yml'), 'utf8')).toContain('disabled: false')
writeFileSync(join(dir, 'cordis.patch.yml'), '- id: managed\n disabled: true\n config: { fail: false }\n')
const result = await manager.setPluginEnabled(id, true)
expect(result, JSON.stringify(result)).toMatchObject({ application: 'applied' })
})
it('reports a selected plain dependency as a problem, omits an unselected one, and reports missing versions', async () => {
const { manager, dir, profile } = await fixture()
writeFileSync(profile.installAnchor, '{}')
writeFileSync(join(dir, 'node_modules', 'extra', 'package.json'), '{"name":"extra"}')
expect((await manager.listBundles()).find(row => row.name === 'extra')).toMatchObject({ enabled: true, error: { code: 'not-bundle' } })
expect(await manager.setBundleEnabled('extra', false)).toMatchObject({ application: 'applied' })
// Switched off, a dependency without a bundle patch is a library the page has no business with.
expect((await manager.listBundles()).some(row => row.name === 'extra')).toBe(false)
expect(await manager.setBundleEnabled('extra', true)).toMatchObject({ changed: false, application: 'failed' })
writeFileSync(join(dir, 'node_modules', 'core', 'package.json'), '{"name":"core","dsh":{"bundle":{"patch":"./cordis.patch.yml"}}}')
expect((await manager.listBundles())[0]?.version).toBeUndefined()
writeFileSync(join(dir, 'package.json'), '{}')
expect((await manager.listBundles()).filter(bundle => bundle.installTarget === undefined)).toEqual([])
expect(await manager.setBundleEnabled('unknown', false)).toMatchObject({ application: 'failed' })
writeFileSync(profile.installAnchor, '{"dependencies":{"missing-builtin":"1"}}')
expect((await manager.listBundles()).filter(bundle => bundle.installTarget === undefined)).toEqual([])
})
it('refuses management bundle disablement and permits repeated bundle selections', async () => {
const { manager } = await fixture()
expect(await manager.setBundleEnabled('core', false)).toMatchObject({ application: 'failed', changed: false })
expect(await manager.setBundleEnabled('extra', true)).toMatchObject({ application: 'applied', changed: false })
})
it.each([
'@deepseek-ai/dsh-host-plugin-inventory',
'@deepseek-ai/dsh-typert-registry',
'@deepseek-ai/dsh-api-remotes',
'@deepseek-ai/dsh-api-remote-access',
])('protects the management dependency %s and its containing bundle', async (name) => {
const { ctx, manager, bundle, profile, dir } = await fixture('startup')
bundle('extra', [{ id: 'dependency', name, disabled: true }])
await reconcileProfilePatches(ctx, readProfilePatches('test', profile), 'test')
const entry = (await manager.listPlugins()).find(row => row.moduleName === name)!
expect(entry).toMatchObject({ readOnlyReason: 'management-required' })
const manifest = readFileSync(join(dir, 'package.json'), 'utf8')
const patch = readFileSync(profile.patchPath, 'utf8')
expect(await manager.setPluginEnabled(entry.entryId, false)).toMatchObject({
changed: false, application: 'failed', error: { code: 'management-required' },
})
expect((await manager.listBundles()).find(row => row.name === 'extra')).toMatchObject({
removable: false, readOnlyReason: 'management-required',
})
expect(await manager.setBundleEnabled('extra', false)).toMatchObject({
changed: false, application: 'failed', error: { code: 'management-required' },
})
expect(await manager.removeBundle('extra')).toMatchObject({
changed: false, application: 'failed', error: { code: 'not-removable' },
})
expect(readFileSync(join(dir, 'package.json'), 'utf8')).toBe(manifest)
expect(readFileSync(profile.patchPath, 'utf8')).toBe(patch)
})
it('addresses children inside profile groups and marks ambiguous ids read-only', async () => {
const { manager, bundle, profile } = await fixture('live', false, (ctx) => { ctx.loader.builtins.group = Group })
bundle('grouped', [{ id: 'group', name: 'cordis:group', group: true,
config: [{ id: 'child', name: './plugin.mjs', config: { service: 'child' } }] }])
expect(await manager.setBundleEnabled('grouped', true)).toMatchObject({ application: 'applied' })
expect((await manager.listPlugins()).find(row => row.patchId === 'child')).toBeDefined()
expect((await manager.listBundles()).find(item => item.name === 'grouped')?.rows.map(row => row.rowId)).toEqual(['child'])
const entries = composeEntries([readProfilePatches('test', profile)])
const duplicate = entries.find(row => row.id === 'managed')!
writeFileSync(profile.patchPath, JSON.stringify([{ insert: [duplicate] }]))
expect((await manager.listPlugins()).find(row => row.entryId === 'include:managed')?.readOnlyReason).toBe('unaddressable')
})
it.each(['', '-g'])('rejects an invalid installation spec before calling pnpm: %j', async (spec) => {
const { manager } = await fixture()
expect(await manager.installBundle(spec)).toMatchObject({ changed: false, application: 'failed', error: { code: 'invalid-spec' } })
})
it('restores the manifest when the package pnpm added declares no bundle', async () => {
const { manager, dir, bundle } = await fixture()
const install = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
bundle('plain', [])
writeFileSync(join(dir, 'node_modules', 'plain', 'package.json'), '{"name":"plain"}')
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, plain: '1' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: 'installed', truncated: false, logPath: join(dir, 'pnpm.log') }
})
onTestFinished(() => { install.mockRestore() })
expect(await manager.installBundle('plain')).toMatchObject({
changed: false, application: 'failed', stage: 'install', error: { code: 'not-bundle' }, packageResult: { exitCode: 0 },
})
// The manifest is put back rather than cleaned through another pnpm run.
expect(install).toHaveBeenCalledOnce()
expect(readProfileManifest('test', dir)).toMatchObject({ dependencies: { extra: '1.0.0' }, dsh: { profile: { bundles: ['core', 'extra'] } } })
expect((await manager.listBundles()).some(row => row.name === 'plain')).toBe(false)
})
it('refuses to install over an installed package, by name, version or local directory, without running pnpm', async () => {
const { manager, dir } = await fixture()
const install = vi.spyOn(operations, 'runProfilePnpm').mockResolvedValue({ exitCode: 0, output: '', truncated: false, logPath: '/operation.log' })
onTestFinished(() => { install.mockRestore() })
for (const spec of ['extra', 'extra@2', join(dir, 'node_modules', 'extra'), `link:${join(dir, 'node_modules', 'extra')}`]) {
expect(await manager.installBundle(spec)).toMatchObject({ application: 'failed', stage: 'install', changed: false, error: { code: 'already-installed' } })
}
// A tarball saved as a path relative to the profile, and a refused install's script approvals, write nothing.
const manifest = readProfileManifest('test', dir)
writeFileSync(join(dir, 'package.json'), JSON.stringify({ ...manifest, dependencies: { extra: 'file:../extra-1.0.0.tgz' } }))
const workspace = join(dir, 'pnpm-workspace.yaml')
const settings = existsSync(workspace) ? readFileSync(workspace, 'utf8') : undefined
expect(await manager.installBundle(join(dir, '..', 'extra-1.0.0.tgz'), { approvedBuilds: ['native-provider'] }))
.toMatchObject({ application: 'failed', changed: false, error: { code: 'already-installed' } })
expect(existsSync(workspace) ? readFileSync(workspace, 'utf8') : undefined).toBe(settings)
// A self-hosted HTTPS Git spec matches the SSH form an earlier installation saved.
writeFileSync(join(dir, 'package.json'), JSON.stringify({ ...manifest, dependencies: { extra: 'git+ssh://[email protected]/team/sub/extra.git#main' } }))
expect(await manager.installBundle('git+https://git.example/team/sub/extra.git#main'))
.toMatchObject({ application: 'failed', changed: false, error: { code: 'already-installed' } })
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
expect(install).not.toHaveBeenCalled()
expect(readProfileManifest('test', dir).dependencies).toEqual({ extra: '1.0.0' })
})
it('keeps a valid installed bundle when its subsequent activation fails', async () => {
const { manager, dir, bundle } = await fixture()
const install = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
bundle('broken', [{ id: 'broken', name: './plugin.mjs', config: { fail: true } }])
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, broken: '1' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: '', truncated: false, logPath: '/operation.log' }
})
onTestFinished(() => { install.mockRestore() })
const result = await manager.installBundle('broken')
expect(result).toMatchObject({ application: 'failed', stage: 'enable', target: 'broken', bundle: 'broken', packageResult: { exitCode: 0 } })
expect(install).toHaveBeenCalledOnce()
expect((await manager.listBundles()).find(row => row.name === 'broken')).toMatchObject({ enabled: true, removable: true })
})
it('returns unchanged failures as warnings while toggling and removing another bundle', async () => {
const { manager, dir, bundle, ctx } = await fixture()
bundle('broken', [
{ id: 'broken', name: './plugin.mjs', config: { fail: true } },
{ id: 'missing', name: './missing.mjs' },
{ id: 'pending', name: './pending.mjs' },
])
writeFileSync(join(dir, 'node_modules/broken/pending.mjs'), 'export const inject = ["unavailable"]; export function apply() {}')
expect(await manager.setBundleEnabled('broken', true)).toMatchObject({ application: 'failed' })
const brokenId = (await manager.listPlugins()).find(row => row.patchId === 'broken')!.entryId
expect(await manager.setPluginEnabled(brokenId, true)).toMatchObject({ application: 'failed' })
expect(await manager.setBundleEnabled('broken', true)).toMatchObject({ application: 'failed' })
const id = (await manager.listPlugins()).find(row => row.patchId === 'managed')!.entryId
const changed = await manager.setPluginEnabled(id, false)
expect(changed).toMatchObject({ application: 'applied' })
expect(changed.warnings).toHaveLength(3)
expect(await manager.setPluginEnabled(id, true)).toMatchObject({ application: 'applied' })
const remove = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
expect([...ctx.loader.entries()].some(row => row.id === 'include:managed')).toBe(false)
const manifest = readProfileManifest('test', dir)
delete manifest.dependencies?.extra
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: '', truncated: false, logPath: '/operation.log' }
})
onTestFinished(() => { remove.mockRestore() })
const removed = await manager.removeBundle('extra')
expect(removed.application).toBe('applied')
expect(removed.warnings).toHaveLength(3)
expect(remove).toHaveBeenCalledOnce()
})
it('recognizes a repeated installation without HMR that leaves the saved dependency as it was', async () => {
const { manager, dir } = await fixture('startup')
const install = vi.spyOn(operations, 'runProfilePnpm').mockResolvedValue({ exitCode: 0, output: '', truncated: false, logPath: join(dir, 'pnpm.log') })
onTestFinished(() => { install.mockRestore() })
expect(await manager.installBundle(`link:${join(dir, 'node_modules', 'extra')}`, { enabled: false }))
.toMatchObject({ changed: false, application: 'restart-required', bundle: 'extra', enabled: false })
})
it('reports ambiguous package changes as failures', async () => {
const { manager, dir } = await fixture()
const install = vi.spyOn(operations, 'runProfilePnpm').mockResolvedValue({ exitCode: 0, output: '', truncated: false, logPath: join(dir, 'pnpm.log') })
onTestFinished(() => { install.mockRestore() })
expect(await manager.installBundle('extra-long@1')).toMatchObject({ changed: false, application: 'failed', error: { code: 'ambiguous-install' } })
install.mockImplementationOnce(async () => {
writeFileSync(join(dir, 'package.json'), '{}')
return { exitCode: 0, output: '', truncated: false, logPath: join(dir, 'pnpm.log') }
})
expect(await manager.installBundle('unknown')).toMatchObject({ changed: false, application: 'failed', error: { code: 'ambiguous-install' } })
expect(readProfileManifest('test', dir).dependencies).toEqual({ extra: '1.0.0' })
})
it('removes a package with the registry it was installed from, so pnpm verifies its lockfile entries there', async () => {
const { manager, dir, bundle } = await fixture()
const corp = 'https://npm.corp.example/'
const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async (_context, args) => {
const name = String(args[1])
const manifest = readProfileManifest('test', dir)
if (args[0] === 'add') {
bundle(name, [{ id: name, name: './plugin.mjs', config: { service: name } }])
manifest.dependencies = { ...manifest.dependencies, [name]: '1.0.0' }
} else {
delete manifest.dependencies?.[name]
}
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: '', truncated: false, logPath: join(dir, 'pnpm.log') }
})
onTestFinished(() => { pnpm.mockRestore() })
expect(await manager.installBundle('mirrored', { enabled: false, registry: corp })).toMatchObject({ application: 'applied' })
expect(await manager.removeBundle('mirrored')).toMatchObject({ application: 'applied' })
expect(pnpm.mock.calls.at(-1)?.[1]).toEqual(['remove', 'mirrored', `--config.registry=${corp}`])
// A failed installation records no registry, and a failed removal keeps the recorded one for the retry.
pnpm.mockResolvedValueOnce({ exitCode: 1, output: 'failed', truncated: false, logPath: join(dir, 'pnpm.log') })
expect(await manager.installBundle('failing', { enabled: false, registry: corp })).toMatchObject({ application: 'failed' })
expect(await manager.installBundle('mirrored', { enabled: false, registry: corp })).toMatchObject({ application: 'applied' })
pnpm.mockResolvedValueOnce({ exitCode: 1, output: 'remove failed', truncated: false, logPath: join(dir, 'pnpm.log') })
expect(await manager.removeBundle('mirrored')).toMatchObject({ application: 'failed' })
expect(await manager.removeBundle('mirrored')).toMatchObject({ application: 'applied' })
expect(pnpm.mock.calls.at(-1)?.[1]).toEqual(['remove', 'mirrored', `--config.registry=${corp}`])
// A package this manager did not install is removed with the registry an installation asks first.
bundle('mirrored', [])
const manifest = readProfileManifest('test', dir)
writeFileSync(join(dir, 'package.json'), JSON.stringify({ ...manifest, dependencies: { ...manifest.dependencies, mirrored: '1.0.0' } }))
await manager.removeBundle('mirrored')
expect(pnpm.mock.calls.at(-1)?.[1]).toEqual(['remove', 'mirrored'])
})
it('streams pnpm output, reports the installation phases, and names the installed bundle', async () => {
const { ctx, manager, dir, bundle } = await fixture()
const chunks: PluginInstallLogChunk[] = []
const phases: PluginInstallProgress[] = []
const changes: PluginChange[] = []
ctx.on('plugin-manager/install-log', (chunk) => { chunks.push(chunk) })
ctx.on('plugin-manager/install-state', (progress) => { phases.push(progress) })
ctx.on('plugin-manager/changed', (change) => { changes.push(change) })
const install = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async (_context, args, options) => {
options.onOutput?.('Progress: resolved 1\n', 'stdout')
options.onOutput?.('warning\n', 'stderr')
const name = String(args[1])
bundle(name, [{ id: name, name: './plugin.mjs', config: { service: name } }])
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, [name]: '1.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: 'installed', truncated: false, logPath: join(dir, 'pnpm.log') }
})
onTestFinished(() => { install.mockRestore() })
const requestId = 'f2340b6d-40bb-46b7-8b94-217bdf5010bd' as PluginInstallRequestId
expect(await manager.installBundle('streamed', { enabled: false, requestId })).toMatchObject({ application: 'applied', changed: true, bundle: 'streamed' })
expect(install).toHaveBeenCalledWith(expect.objectContaining({ profile: 'test' }), ['add', 'streamed'],
expect.objectContaining({ command: 'pnpm', execution: 'service' }))
const jobId = chunks[0]?.jobId
expect(chunks).toEqual([
{ requestId, jobId, argv: ['pnpm', 'add', 'streamed'], cwd: dir, stream: 'stdout', text: 'Progress: resolved 1\n' },
{ requestId, jobId, argv: ['pnpm', 'add', 'streamed'], cwd: dir, stream: 'stderr', text: 'warning\n' },
{ requestId, jobId, argv: ['pnpm', 'add', 'streamed'], cwd: dir, stream: 'stdout', text: '', exitCode: 0 },
])
expect(phases).toEqual([{ requestId, phase: 'installing', attempt: { registry: null, index: 1, total: 2 } }, { requestId, phase: 'applying' }])
expect(changes).toEqual([{ reason: 'install' }])
// A run without a request id streams too, unidentified.
await manager.removeBundle('streamed')
expect(chunks.at(-1)).toMatchObject({ argv: ['pnpm', 'remove', 'streamed'], stream: 'stdout', exitCode: 0 })
expect(chunks.at(-1)).not.toHaveProperty('requestId')
expect(changes).toEqual([{ reason: 'install' }, { reason: 'remove' }])
})
it('stops a run on request, restores the files, and answers not-running or too-late otherwise', async () => {
const { ctx, manager, dir, bundle } = await fixture()
const phases: PluginInstallProgress[] = []
ctx.on('plugin-manager/install-state', (progress) => { phases.push(progress) })
const started = Promise.withResolvers<undefined>()
const install = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async (_context, _args, options) => {
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, slow: '1' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
started.resolve(undefined)
await new Promise<undefined>((resolve) => { options.signal?.addEventListener('abort', () => { resolve(undefined) }, { once: true }) })
return { exitCode: 1, output: 'killed', truncated: false, logPath: join(dir, 'pnpm.log') }
})
onTestFinished(() => { install.mockRestore() })
const requestId = 'f2340b6d-40bb-46b7-8b94-217bdf5010bd' as PluginInstallRequestId
const before = readFileSync(join(dir, 'package.json'), 'utf8')
const run = manager.installBundle('slow', { requestId })
await started.promise
const recoveredCancellation = manager.waitForInstall(requestId)
expect(await manager.waitForInstall('missing' as PluginInstallRequestId)).toBeNull()
expect(await manager.cancelInstall('00000000-0000-4000-8000-000000000000' as PluginInstallRequestId)).toEqual({ status: 'not-running' })
expect(await manager.cancelInstall(requestId)).toEqual({ status: 'cancelled' })
expect(readFileSync(join(dir, 'package.json'), 'utf8')).toBe(before)
const cancelled = await run
expect(await recoveredCancellation).toEqual(cancelled)
expect(await manager.waitForInstall(requestId)).toBeNull()
expect(cancelled).toMatchObject({ application: 'cancelled', changed: false, stage: 'install', packageResult: { exitCode: 1 } })
expect(cancelled.error).toBeUndefined()
expect(phases).toEqual([{ requestId, phase: 'installing', attempt: { registry: null, index: 1, total: 2 } }, { requestId, phase: 'cancelling' }])
expect(await manager.cancelInstall(requestId)).toEqual({ status: 'not-running' })
// Once pnpm has exited and the bundle is being applied, the run cannot be stopped.
install.mockImplementation(async (_context, args) => {
const name = String(args[1])
bundle(name, [{ id: name, name: './plugin.mjs', config: { service: name } }])
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, [name]: '1.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: 'installed', truncated: false, logPath: join(dir, 'pnpm.log') }
})
let tooLate: Promise<unknown> | undefined
let recoveredApplication: Promise<unknown> | undefined
ctx.on('plugin-manager/install-state', (progress) => {
if (progress.phase !== 'applying') return
tooLate = manager.cancelInstall(progress.requestId)
recoveredApplication = manager.waitForInstall(progress.requestId)
})
expect(await manager.installBundle('late', { requestId })).toMatchObject({ application: 'applied', bundle: 'late' })
expect(await tooLate).toEqual({ status: 'too-late' })
expect(await recoveredApplication).toMatchObject({ application: 'applied', bundle: 'late' })
// A request aborted before its turn under the lock never starts pnpm.
const early = manager.installBundle('never', { requestId })
const queued = manager.installBundle('after', { requestId: '11111111-1111-4111-8111-111111111111' as PluginInstallRequestId })
expect(await manager.cancelInstall('11111111-1111-4111-8111-111111111111' as PluginInstallRequestId)).toEqual({ status: 'cancelled' })
await early
expect(await queued).toMatchObject({ application: 'cancelled', changed: false })
})
it('reads what a spec names before installing it', async () => {
const { manager, dir, profile } = await fixture(undefined, false, undefined, { inspectTimeoutMs: 1000, pnpmCommand: 'pnpm-test', fallbackRegistries: [] })
const view = vi.spyOn(operations, 'viewProfilePackage')
onTestFinished(() => { view.mockRestore() })
const answers = (stdout: string) => view.mockResolvedValueOnce({ exitCode: 0, stdout, stderr: '', timedOut: false })
answers(JSON.stringify({ name: 'dsh-x', version: '1.4.2', description: 'A sidebar.', dsh: { bundle: { patch: './cordis.patch.yml' } } }))
expect(await manager.inspect('dsh-x')).toEqual({
status: 'accepted', kind: 'registry', name: 'dsh-x', version: '1.4.2', description: 'A sidebar.', bundle: true, registry: null,
})
expect(view).toHaveBeenCalledWith(dir, 'dsh-x', { command: 'pnpm-test', fields: ['name', 'version', 'description', 'dsh'], timeoutMs: 1000, registry: null })
const signal = AbortSignal.abort()
answers(JSON.stringify([{ name: 'dsh-lib', version: '1.0.0', dsh: { bundle: {} } }, { name: 'dsh-lib', version: '1.1.0', dsh: null }]))
expect(await manager.inspect('dsh-lib@^1', undefined, signal)).toEqual({ status: 'refused', problem: 'not-a-bundle', reason: 'dsh-lib declares no dsh.bundle', registries: [null] })
expect(view).toHaveBeenLastCalledWith(dir, 'dsh-lib@^1', { command: 'pnpm-test', fields: ['name', 'version', 'description', 'dsh'], timeoutMs: 1000, signal, registry: null })
// An answer that names no package keeps the name the spec gave; colour escapes around the JSON are dropped.
answers('\x1b[36m' + JSON.stringify({ version: '0.0.1', description: '', dsh: { bundle: { patch: './p.yml' } } }) + '\x1b[39m\n')
expect(await manager.inspect('dsh-bare')).toEqual({ status: 'accepted', kind: 'registry', name: 'dsh-bare', version: '0.0.1', bundle: true, registry: null })
const failure = (stderr: string, exitCode: number | null = 1, more: Partial<operations.PackageViewResult> = {}) =>
view.mockResolvedValueOnce({ exitCode, stdout: '', stderr, timedOut: false, ...more })
failure('npm error code E404\nnpm error 404 Not Found - GET https://registry/nope\n')
expect(await manager.inspect('nope')).toMatchObject({ status: 'refused', problem: 'not-found', reason: expect.stringContaining('E404') as string })
failure('ERR_PNPM_NO_MATCHING_VERSION No matching version found for old@9\n')
expect(await manager.inspect('old@9')).toMatchObject({ status: 'refused', problem: 'not-found' })
failure('ERR_PNPM_META_FETCH_FAIL request failed, reason: getaddrinfo ENOTFOUND registry\n')
expect(await manager.inspect('far')).toMatchObject({ status: 'refused', problem: 'network' })
view.mockResolvedValueOnce({ exitCode: 3, stdout: 'plain text\n', stderr: '', timedOut: false })
expect(await manager.inspect('odd')).toEqual({ status: 'refused', problem: 'unknown', reason: 'plain text', registries: [null] })
failure('', 4)
expect(await manager.inspect('quiet')).toEqual({ status: 'refused', problem: 'unknown', reason: 'pnpm view exited with 4', registries: [null] })
failure('', null, { timedOut: true })
// A registry that never answered within the bound is unreachable.
expect(await manager.inspect('slow')).toEqual({ status: 'refused', problem: 'network', reason: 'pnpm view timed out after 1000ms', registries: [null] })
failure('', null, { cause: Object.assign(new Error('spawn pnpm ENOENT'), { code: 'ENOENT' }) })
expect(await manager.inspect('gone')).toMatchObject({ status: 'refused', problem: 'unknown', reason: expect.stringContaining('ENOENT') as string })
answers('not json')
expect(await manager.inspect('garbled')).toMatchObject({ status: 'refused', problem: 'unknown', reason: expect.stringContaining('unreadable pnpm view output') as string })
answers('"just a string"')
expect(await manager.inspect('scalar')).toEqual({ status: 'refused', problem: 'unknown', reason: 'pnpm view answered no package', registries: [null] })
answers('')
expect(await manager.inspect('silent')).toEqual({ status: 'refused', problem: 'unknown', reason: 'pnpm view answered no package', registries: [null] })
// What is installed, or supplied by the installation, is refused before the registry is asked.
expect(await manager.inspect('extra')).toEqual({ status: 'refused', problem: 'already-installed', reason: 'extra is already installed' })
expect(await manager.inspect('./relative')).toEqual({ status: 'refused', problem: 'invalid-spec', reason: 'a local path must be absolute' })
expect(await manager.inspect('github:acme/dsh-remote')).toEqual({ status: 'accepted', kind: 'git', bundle: null, registry: null, host: 'github.com' })
const tarball = join(profile.home, 'pack.tgz')
expect(await manager.inspect(tarball)).toEqual({ status: 'refused', problem: 'not-a-package', reason: 'the tarball does not exist' })
writeFileSync(tarball, '')
expect(await manager.inspect(tarball)).toEqual({ status: 'accepted', kind: 'tarball', bundle: null, registry: null })
expect(await manager.inspect('https://cdn.example.com/x/y/z/dsh-x-1.0.0.tgz')).toEqual({ status: 'accepted', kind: 'tarball', bundle: null, registry: null, host: 'cdn.example.com' })
// A directory answers from its own manifest.
const local = join(profile.home, 'dev', 'dsh-local')
mkdirSync(local, { recursive: true })
expect(await manager.inspect(join(profile.home, 'dev', 'missing'))).toEqual({ status: 'refused', problem: 'not-a-package', reason: 'the path does not exist' })
expect(await manager.inspect(local)).toMatchObject({ status: 'refused', problem: 'not-a-package', reason: expect.stringContaining('no readable package.json') as string })
writeFileSync(join(local, 'package.json'), '{"version":"1.0.0"}')
expect(await manager.inspect(local)).toEqual({ status: 'refused', problem: 'not-a-package', reason: 'the package.json names no package' })
writeFileSync(join(local, 'package.json'), JSON.stringify({ name: 'dsh-local', version: '0.1.0', description: 'Local.' }))
expect(await manager.inspect(local)).toEqual({ status: 'refused', problem: 'not-a-bundle', reason: 'dsh-local declares no dsh.bundle' })
writeFileSync(join(local, 'package.json'), JSON.stringify({ name: 'dsh-local', version: '0.1.0', description: 'Local.', dsh: { bundle: { patch: './p.yml' } } }))
expect(await manager.inspect(`file:${local}`)).toEqual({ status: 'accepted', kind: 'path', name: 'dsh-local', version: '0.1.0', description: 'Local.', bundle: true, registry: null })
writeFileSync(join(local, 'package.json'), JSON.stringify({ name: 'core', dsh: { bundle: { patch: './p.yml' } } }))
expect(await manager.inspect(local)).toEqual({ status: 'refused', problem: 'already-installed', reason: 'core is already installed' })
// A profile and an installation that list nothing know nothing.
writeFileSync(join(dir, 'package.json'), '{}')
writeFileSync(profile.installAnchor, '{}')
expect(await manager.inspect(local)).toEqual({ status: 'accepted', kind: 'path', name: 'core', bundle: true, registry: null })
expect(view).toHaveBeenCalledTimes(13)
})
it('announces each manager operation as a change, and a patch generation applied outside it not at all', async () => {
const { ctx, manager, profile } = await fixture('startup')
const changes: PluginChange[] = []
ctx.on('plugin-manager/changed', (change) => { changes.push(change) })
await reconcileProfilePatches(ctx, readProfilePatches('test', profile), 'test')
expect(changes).toEqual([])
const id = (await manager.listPlugins()).find(row => row.patchId === 'managed')!.entryId
await manager.setPluginEnabled(id, false)
expect(changes).toEqual([{ reason: 'plugin' }])
await manager.setBundleEnabled('extra', false)
expect(changes).toEqual([{ reason: 'plugin' }, { reason: 'bundle' }])
})
it('handles missing patch files and retains non-Error package diagnostics', async () => {
const { manager, dir } = await fixture()
rmSync(join(dir, 'cordis.patch.yml'))
const id = (await manager.listPlugins()).find(row => row.patchId === 'managed')!.entryId
expect(await manager.setPluginEnabled(id, false)).toMatchObject({ changed: true, application: 'applied' })
const install = vi.spyOn(operations, 'runProfilePnpm').mockRejectedValueOnce('pnpm rejected operation')
onTestFinished(() => { install.mockRestore() })
expect(await manager.installBundle('new')).toMatchObject({ changed: false, application: 'failed', error: { code: 'operation-error', diagnostic: 'pnpm rejected operation' } })
rmSync(join(dir, 'cordis.patch.yml'))
mkdirSync(join(dir, 'cordis.patch.yml'))
await expect(manager.setPluginEnabled(id, true)).rejects.toThrow()
})
it('applies a manager change through the active HMR service', async () => {
const { manager } = await fixture()
const id = (await manager.listPlugins()).find(row => row.patchId === 'managed')!.entryId
expect(await manager.setPluginEnabled(id, false)).toMatchObject({ changed: true, application: 'applied' })
expect((await manager.listPlugins()).find(row => row.entryId === id)?.enabled).toBe(false)
})
it('refuses removal of a hot-installed bundle after HMR is disabled', async () => {
const { ctx, manager, dir, bundle, stopHmr } = await fixture()
bundle('later', [{ id: 'later', name: './plugin.mjs', config: { service: 'laterProbe' } }])
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, later: '1' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
expect(await manager.setBundleEnabled('later', true)).toMatchObject({ application: 'applied' })
await stopHmr()
expect(ctx.get('hmr')).toBeUndefined()
expect(await manager.setBundleEnabled('later', false)).toMatchObject({ application: 'restart-required' })
expect(ctx.get('laterProbe')).toBe(true)
expect(await manager.removeBundle('later')).toMatchObject({ changed: false, application: 'failed' })
})
it.each(OPTIONAL_BUNDLES)('offers %s switched off and never removable', async (offered) => {
const { manager, profile } = await fixture()
// The launcher names the bundles the installation ships; the fixture supplies one of them from the
// installation's own node_modules, which the resolver consults before the profile's and before the repository's.
const supplied = join(profile.home, 'node_modules', offered)
mkdirSync(supplied, { recursive: true })
writeFileSync(join(supplied, 'package.json'), JSON.stringify({
name: offered, version: '3.0.0', description: 'Package one-liner.', dsh: { bundle: { patch: './cordis.patch.yml' } },
}))
writeFileSync(join(supplied, 'cordis.patch.yml'), JSON.stringify([{ insert: [{ id: 'offered-row', name: './plugin.mjs', config: { service: 'offeredProbe' } }] }]))
writeFileSync(join(supplied, 'plugin.mjs'), 'export function apply(ctx, config) { ctx.provide(config?.service ?? "offeredProbe", true) }\n')
writeFileSync(profile.installAnchor, JSON.stringify({ name: 'installation', dependencies: { [offered]: '3.0.0' } }))
expect((await manager.listBundles()).find(row => row.name === offered)).toEqual({
name: offered, version: '3.0.0', description: 'Package one-liner.',
meta: { title: offered, description: 'Package one-liner.' },
enabled: false, installed: false, official: true, availability: 'installation', optional: true, removable: false, updatable: false,
rows: [{ rowId: 'offered-row', moduleName: pathToFileURL(join(supplied, 'plugin.mjs')).href }], overrides: [],
})
expect(await manager.setBundleEnabled(offered, true)).toMatchObject({ application: 'applied' })
expect((await manager.listBundles()).find(row => row.name === offered)).toMatchObject({ enabled: true, optional: true, removable: false })
expect(await manager.removeBundle(offered)).toMatchObject({ changed: false, application: 'failed' })
})
it('removes a selected bundle no dependency holds by deselecting it without pnpm', async () => {
const { manager, dir } = await fixture()
const manifest = readProfileManifest('test', dir)
writeFileSync(join(dir, 'package.json'), JSON.stringify({
...manifest, dsh: { profile: { bundles: [...manifest.dsh?.profile?.bundles ?? [], 'retired'] } },
}))
expect((await manager.listBundles()).find(row => row.name === 'retired')).toMatchObject({
enabled: true, installed: false, official: false, availability: 'missing', optional: false, removable: true, error: { code: 'operation-error' },
})
const pnpm = vi.spyOn(operations, 'runProfilePnpm')
onTestFinished(() => { pnpm.mockRestore() })
expect(await manager.removeBundle('retired')).toMatchObject({ changed: true, application: 'applied' })
expect(pnpm).not.toHaveBeenCalled()
expect(readProfileManifest('test', dir).dsh?.profile?.bundles).not.toContain('retired')
expect((await manager.listBundles()).some(row => row.name === 'retired')).toBe(false)
})
it('omits installation-owned plain packages from the bundle inventory', async () => {
const { manager, dir, profile, bundle } = await fixture()
bundle('installation-plain', [])
writeFileSync(join(dir, 'node_modules/installation-plain/package.json'), '{"name":"installation-plain"}')
writeFileSync(profile.installAnchor, '{"dependencies":{"installation-plain":"1"}}')
expect((await manager.listBundles()).some(row => row.name === 'installation-plain')).toBe(false)
})
it('does not delete a bundle retained by a higher-priority overlay', async () => {
const { ctx, manager, overlays, dir } = await fixture()
const entry = [...ctx.loader.entries()].find(row => row.id === 'include:managed')!
overlays.push({ insert: [{ ...entry.options }] })
const remove = vi.spyOn(operations, 'runProfilePnpm')
onTestFinished(() => { remove.mockRestore() })
expect(await manager.removeBundle('extra')).toMatchObject({ changed: true, application: 'failed', error: { code: 'bundle-in-use' } })
expect(await manager.removeBundle('extra')).toMatchObject({ changed: false, application: 'failed', error: { code: 'bundle-in-use' } })
expect(remove).not.toHaveBeenCalled()
expect(readProfileManifest('test', dir).dependencies).toEqual({ extra: '1.0.0' })
expect(ctx.get('managedProbe')).toBe(true)
})
it('applies watched configuration while pnpm installation is still running', async () => {
const { ctx, manager, dir, profile, bundle } = await fixture()
const entered = Promise.withResolvers<undefined>()
const release = Promise.withResolvers<undefined>()
const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
entered.resolve(undefined)
await release.promise
bundle('new-bundle', [])
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, 'new-bundle': '1.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: 'installed', truncated: false, logPath: join(dir, 'pnpm.log') }
})
const installing = manager.installBundle('new-bundle')
onTestFinished(async () => { release.resolve(undefined); await installing; pnpm.mockRestore() })
await entered.promise
writeFileSync(profile.patchPath, '- id: managed\n disabled: true\n')
await vi.waitFor(() => { expect(ctx.get('managedProbe')).toBeUndefined() }, { timeout: 10000 })
expect(pnpm).toHaveBeenCalledOnce()
release.resolve(undefined)
expect(await installing).toMatchObject({ application: 'applied', changed: true })
expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toEqual(['core', 'extra', 'new-bundle'])
expect(ctx.get('managedProbe')).toBeUndefined()
})
it('installs and removes with the bundled pnpm when PATH contains no pnpm', async () => {
const pnpm = fileURLToPath(new URL('../../../../apps/desktop/node_modules/pnpm/bin/pnpm.mjs', import.meta.url))
const { manager, dir } = await fixture('startup', false, undefined, { pnpmCommand: 'must-not-be-used' }, {
command: process.execPath, args: ['--expose-internals', pnpm], env: { PATH: '', ELECTRON_RUN_AS_NODE: '1' },
})
const target = join(dir, 'local-bundle')
mkdirSync(target)
writeFileSync(join(target, 'package.json'), JSON.stringify({ name: '@test/desktop-manager', version: '1.0.0',
dsh: { bundle: { patch: './cordis.patch.yml' } } }))
writeFileSync(join(target, 'cordis.patch.yml'), '[]\n')
// Fixture-only packages need no registry resolution during this local install.
const manifest = readProfileManifest('test', dir)
delete manifest.dependencies
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
const installed = await manager.installBundle(target)
expect(installed.error).toBeUndefined()
expect(installed.packageResult?.exitCode).toBe(0)
expect(readProfileManifest('test', dir).dependencies).toHaveProperty('@test/desktop-manager')
const removed = await manager.removeBundle('@test/desktop-manager')
expect(removed.error).toBeUndefined()
expect(removed.packageResult?.exitCode).toBe(0)
expect(readProfileManifest('test', dir).dependencies ?? {}).not.toHaveProperty('@test/desktop-manager')
})
const MIRROR = 'https://registry.npmmirror.com/'
const OFFICIAL = 'https://registry.npmjs.org/'
/** What pnpm's own configuration names in the profile, for the tests that need something other than npm's own registry. */
function pnpmNames(url: string | null = OFFICIAL) {
return vi.spyOn(operations, 'readProfileRegistry').mockResolvedValue(url)
}
it('asks the registries in turn while one is unreachable or stale, and names the one that answered', async () => {
const { manager, dir } = await fixture(undefined, false, undefined, { inspectTimeoutMs: 1000, fallbackRegistries: ['https://REGISTRY.npmmirror.com'] })
const read = pnpmNames()
const view = vi.spyOn(operations, 'viewProfilePackage')
onTestFinished(() => { view.mockRestore() })
const failure = (stderr: string, exitCode = 1) => view.mockResolvedValueOnce({ exitCode, stdout: '', stderr, timedOut: false })
const answers = (stdout: string) => view.mockResolvedValueOnce({ exitCode: 0, stdout, stderr: '', timedOut: false })
const asked = (from: number) => view.mock.calls.slice(from).map(call => call[2].registry)
const manifest = JSON.stringify({ name: 'dsh-x', version: '1.0.0', dsh: { bundle: { patch: './cordis.patch.yml' } } })
// pnpm's own registry is unreachable; the mirror answers and the install is told to start there.
failure('ERR_PNPM_META_FETCH_FAIL GET https://registry.npmjs.org/dsh-x: ETIMEDOUT\n')
answers(manifest)
expect(await manager.inspect('dsh-x')).toEqual({ status: 'accepted', kind: 'registry', name: 'dsh-x', version: '1.0.0', bundle: true, registry: MIRROR })
expect(asked(0)).toEqual([null, MIRROR])
expect(read).toHaveBeenCalledWith(dir, { command: 'pnpm', timeoutMs: 1000 })
expect(view).toHaveBeenLastCalledWith(expect.any(String), 'dsh-x', { command: 'pnpm', fields: ['name', 'version', 'description', 'dsh'], timeoutMs: 1000, registry: MIRROR })
// pnpm prints a refusal as JSON on stdout with nothing on stderr: it is read the same way, and the reason is its message.
view.mockResolvedValueOnce({ exitCode: 1, stdout: '{\n "error": {\n "code": "ERR_PNPM_META_FETCH_FAIL",\n "message": "GET https://registry.npmjs.org/dsh-x: fetch failed"\n }\n}\n', stderr: '', timedOut: false })
answers(manifest)
expect(await manager.inspect('dsh-x')).toMatchObject({ status: 'accepted', registry: MIRROR })
expect(asked(2)).toEqual([null, MIRROR])
// A requested registry goes first. A mirror's 404 may be a copy not yet synced, so the lookup goes on; every registry's 404 is not-found.
view.mockResolvedValueOnce({ exitCode: 1, stdout: '{"error":{"code":"ERR_PNPM_FETCH_404","message":"GET https://registry.npmmirror.com/dsh-x: Not Found - 404"}}', stderr: '', timedOut: false })
failure('ERR_PNPM_FETCH_404 GET https://registry.npmjs.org/dsh-x: Not Found - 404\n')
expect(await manager.inspect('dsh-x', { registry: MIRROR })).toMatchObject({
status: 'refused', problem: 'not-found', reason: 'ERR_PNPM_FETCH_404 GET https://registry.npmjs.org/dsh-x: Not Found - 404', registries: [MIRROR, null],
})
expect(asked(4)).toEqual([MIRROR, null])
// A registry outside the configured set is asked alone.
failure('ERR_PNPM_META_FETCH_FAIL GET https://npm.corp.example/dsh-x: ECONNREFUSED\n')
expect(await manager.inspect('dsh-x', { registry: 'https://npm.corp.example' })).toMatchObject({ status: 'refused', problem: 'network', registries: ['https://npm.corp.example/'] })
expect(asked(6)).toEqual(['https://npm.corp.example/'])
// A failure no registry changes ends the round at once.
failure('', 4)
expect(await manager.inspect('dsh-x')).toMatchObject({ status: 'refused', problem: 'unknown', registries: [null] })
expect(asked(7)).toEqual([null])
// A registry that never answered is unreachable, like one that refused the connection: the round goes on, and the
// refusal is a network one.
view.mockResolvedValueOnce({ exitCode: null, stdout: '', stderr: '', timedOut: true })
view.mockResolvedValueOnce({ exitCode: null, stdout: '', stderr: '', timedOut: true })
expect(await manager.inspect('dsh-x')).toEqual({ status: 'refused', problem: 'network', reason: 'pnpm view timed out after 1000ms', registries: [null, MIRROR] })
expect(asked(8)).toEqual([null, MIRROR])
// A lookup the caller dropped is not carried to the next registry.
const controller = new AbortController()
view.mockImplementationOnce(async () => { controller.abort(); return { exitCode: 1, stdout: '', stderr: 'ECONNRESET\n', timedOut: false } })
expect(await manager.inspect('dsh-x', {}, controller.signal)).toMatchObject({ status: 'refused', problem: 'network', registries: [null] })
expect(asked(10)).toEqual([null])
// The other forms ask no registry and carry the one the install starts with.
expect(await manager.inspect('github:acme/dsh-remote', { registry: MIRROR })).toEqual({ status: 'accepted', kind: 'git', bundle: null, registry: MIRROR, host: 'github.com' })
expect(await manager.inspect('github:acme/dsh-remote')).toEqual({ status: 'accepted', kind: 'git', bundle: null, registry: null, host: 'github.com' })
expect(view).toHaveBeenCalledTimes(11)
})
it('keeps pnpm\'s own registry alone when it names a private one, or cannot be read', async () => {
const { manager } = await fixture(undefined, false, undefined, { inspectTimeoutMs: 1000 })
const read = pnpmNames('https://npm.corp.example/')
const view = vi.spyOn(operations, 'viewProfilePackage')
onTestFinished(() => { view.mockRestore() })
view.mockResolvedValue({ exitCode: 1, stdout: '', stderr: 'ERR_PNPM_META_FETCH_FAIL GET https://npm.corp.example/dsh-x: ETIMEDOUT\n', timedOut: false })
expect(await manager.inspect('dsh-x')).toMatchObject({ status: 'refused', problem: 'network', registries: [null] })
expect(await manager.inspect('dsh-x', { registry: MIRROR })).toMatchObject({ status: 'refused', problem: 'network', registries: [MIRROR] })
read.mockResolvedValue(null)
expect(await manager.inspect('dsh-x')).toMatchObject({ status: 'refused', problem: 'network', registries: [null] })
expect(await manager.registries()).toEqual({ registry: null, fallbackRegistries: [MIRROR], resolved: null })
expect(view).toHaveBeenCalledTimes(3)
})
it('installs from the next registry after one is unreachable, restoring the files between attempts', async () => {
const { ctx, manager, dir, bundle } = await fixture()
const phases: PluginInstallProgress[] = []
const chunks: PluginInstallLogChunk[] = []
ctx.on('plugin-manager/install-state', (progress) => { phases.push(progress) })
ctx.on('plugin-manager/install-log', (chunk) => { chunks.push(chunk) })
const lockPath = join(dir, 'pnpm-lock.yaml')
const install = vi.spyOn(operations, 'runProfilePnpm')
.mockImplementationOnce(async () => {
writeFileSync(lockPath, 'partial lockfile\n')
return { exitCode: 1, output: 'ERR_PNPM_META_FETCH_FAIL GET https://registry.npmjs.org/fallen: ETIMEDOUT', truncated: false, logPath: join(dir, 'pnpm.log') }
})
.mockImplementationOnce(async (_context, args) => {
// The failed attempt's lockfile is gone before the next registry is asked.
expect(existsSync(lockPath)).toBe(false)
const name = String(args[1])
bundle(name, [{ id: name, name: './plugin.mjs', config: { service: name } }])
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, [name]: '1.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: 'installed', truncated: false, logPath: join(dir, 'pnpm.log') }
})
onTestFinished(() => { install.mockRestore() })
const requestId = 'f2340b6d-40bb-46b7-8b94-217bdf5010bd' as PluginInstallRequestId
const result = await manager.installBundle('fallen', { enabled: false, requestId })
expect(result).toMatchObject({ application: 'applied', bundle: 'fallen', registries: [null, MIRROR], packageResult: { exitCode: 0 } })
expect(result.failedAt).toBeUndefined()
expect(install.mock.calls.map(call => call[1])).toEqual([['add', 'fallen'], ['add', 'fallen', `--registry=${MIRROR}`]])
expect(phases).toEqual([
{ requestId, phase: 'installing', attempt: { registry: null, index: 1, total: 2 } },
{ requestId, phase: 'installing', attempt: { registry: MIRROR, index: 2, total: 2 } },
{ requestId, phase: 'applying' },
])
// Each attempt streams as its own run, the command line naming the registry it asked.
expect(new Set(chunks.map(chunk => chunk.jobId)).size).toBe(2)
expect(chunks.at(-1)).toMatchObject({ requestId, argv: ['pnpm', 'add', 'fallen', `--registry=${MIRROR}`], exitCode: 0 })
})
it('does not ask the next registry after the manager terminated a silent run', async () => {
const { manager } = await fixture()
const install = vi.spyOn(operations, 'runProfilePnpm').mockResolvedValue({
exitCode: 1, output: 'installing\ndsh: pnpm printed nothing for 600000ms and was terminated\n',
truncated: false, logPath: '/dev/null', timedOut: true,
})
onTestFinished(() => { install.mockRestore() })
const stalled = await manager.installBundle('silent')
expect(stalled).toMatchObject({ application: 'failed', registries: [null], packageResult: { kind: 'timeout', timedOut: true } })
expect(stalled.failedAt).toBeUndefined()
expect(install).toHaveBeenCalledTimes(1)
})
it('drops the earlier registry failure once a later attempt is terminated for silence', async () => {
const { manager } = await fixture()
const install = vi.spyOn(operations, 'runProfilePnpm')
.mockResolvedValueOnce({
exitCode: 1, output: 'ERR_PNPM_META_FETCH_FAIL GET https://registry.npmjs.org/flaky: ETIMEDOUT',
truncated: false, logPath: '/dev/null',
})
.mockResolvedValueOnce({
exitCode: 1, output: 'installing\ndsh: pnpm printed nothing for 600000ms and was terminated\n',
truncated: false, logPath: '/dev/null', timedOut: true,
})
onTestFinished(() => { install.mockRestore() })
const stalled = await manager.installBundle('flaky')
expect(stalled).toMatchObject({ application: 'failed', registries: [null, MIRROR], packageResult: { kind: 'timeout', timedOut: true } })
expect(stalled.failedAt).toBeUndefined()
expect(install).toHaveBeenCalledTimes(2)
})
it('bounds each package run with the configured silence timeout', async () => {
const { manager } = await fixture('live', false, undefined, { idleTimeoutMs: 1234 })
const install = vi.spyOn(operations, 'runProfilePnpm')
.mockResolvedValue({ exitCode: 1, output: 'plain failure', truncated: false, logPath: '/dev/null' })
onTestFinished(() => { install.mockRestore() })
await manager.installBundle('quiet')
expect(install.mock.calls[0]?.[2]).toMatchObject({ idleTimeoutMs: 1234 })
})
it('stops at a failure no registry changes, at a host the spec itself is fetched from, and after a registry outside the configured set', async () => {
const { manager } = await fixture()
const failing = (output: string) => ({ exitCode: 1, output, truncated: false, logPath: '/dev/null' })
const install = vi.spyOn(operations, 'runProfilePnpm').mockResolvedValue(failing('ERR_PNPM_IGNORED_BUILDS Ignored build scripts: native'))
onTestFinished(() => { install.mockRestore() })
const blocked = await manager.installBundle('blocked')
expect(blocked).toMatchObject({ application: 'failed', registries: [null], packageResult: { kind: 'build-blocked' } })
expect(blocked.failedAt).toBeUndefined()
expect(install).toHaveBeenCalledTimes(1)
install.mockResolvedValue(failing('fatal: unable to access \'https://github.com/acme/dsh-x/\': Could not resolve host: github.com'))
expect(await manager.installBundle('github:acme/dsh-x')).toMatchObject({ application: 'failed', registries: [null], failedAt: 'spec-host', packageResult: { kind: 'network' } })
expect(install).toHaveBeenCalledTimes(2)
install.mockResolvedValue(failing('ERR_PNPM_META_FETCH_FAIL GET https://npm.corp.example/dsh-x: ECONNREFUSED'))
expect(await manager.installBundle('dsh-x', { registry: 'https://npm.corp.example' })).toMatchObject({
application: 'failed', registries: ['https://npm.corp.example/'], failedAt: 'registry', packageResult: { kind: 'network' },
})
expect(install).toHaveBeenCalledTimes(3)
expect(install).toHaveBeenLastCalledWith(expect.anything(), ['add', 'dsh-x', '--registry=https://npm.corp.example/'], expect.anything())
// Every configured registry unreachable: the failure is the last attempt's, and all of them are named.
install.mockResolvedValue(failing('ERR_PNPM_META_FETCH_FAIL GET https://registry/dsh-x: ETIMEDOUT'))
expect(await manager.installBundle('dsh-x')).toMatchObject({ application: 'failed', registries: [null, MIRROR], failedAt: 'registry', packageResult: { kind: 'network' } })
expect(install).toHaveBeenCalledTimes(5)
})
it('reports a stop that lands before a run starts, while the registries are read, as cancelled', async () => {
const { manager } = await fixture()
const requestId = 'f2340b6d-40bb-46b7-8b94-217bdf5010bd' as PluginInstallRequestId
// The stop lands while the plan is being read: the run after it must not start on a dead signal.
const read = vi.spyOn(operations, 'readProfileRegistry').mockImplementation(async () => {
void manager.cancelInstall(requestId)
await Promise.resolve()
return OFFICIAL
})
const install = vi.spyOn(operations, 'runProfilePnpm')
onTestFinished(() => { read.mockRestore(); install.mockRestore() })
const result = await manager.installBundle('dsh-x', { requestId })
expect(result).toMatchObject({ application: 'cancelled', registries: [] })
expect(result.error).toBeUndefined()
expect(install).not.toHaveBeenCalled()
})
it('answers the configured registries in pnpm\'s comparison form with what pnpm names, and refuses one that is not an http(s) URL at load', async () => {
expect(await (await fixture()).manager.registries()).toEqual({ registry: null, fallbackRegistries: [MIRROR], resolved: OFFICIAL })
const { manager } = await fixture(undefined, false, undefined, { registry: 'https://NPM.corp.example', fallbackRegistries: [] })
expect(await manager.registries()).toEqual({ registry: 'https://npm.corp.example/', fallbackRegistries: [], resolved: OFFICIAL })
expect(() => PluginManager.Config({ registry: 'npm.corp.example' })).toThrow()
expect(() => PluginManager.Config({ fallbackRegistries: ['ftp://npm.corp.example/'] })).toThrow()
})
it.each(['live', 'startup'] as const)('requires exact risk acknowledgement and preserves exemptions in a %s profile', async (mode) => {
const { manager, dir, ctx } = await fixture(mode, false, undefined, {}, undefined, (dir) => {
const file = join(dir, 'node_modules', 'extra', 'package.json')
const metadata = JSON.parse(readFileSync(file, 'utf8')) as Record<string, unknown>
writeFileSync(file, JSON.stringify({ ...metadata, peerDependencies: { '@deepseek-ai/dsh': '999.0.0' } }))
})
const runtime = getDshRuntimeVersion()
const managed = () => [...ctx.loader.entries()].find(entry => entry.id === 'include:managed')
// The bundle's own peers are incompatible, so its whole layer is skipped and contributes no row.
expect(managed()).toBeUndefined()
expect((await manager.listBundles()).find(bundle => bundle.name === 'extra')?.error).toEqual({
code: 'incompatible-version',
incompatible: [{ name: 'extra', version: '1.0.0', runtimeVersion: runtime, requirements: { '@deepseek-ai/dsh': '999.0.0' } }],
})
expect(await manager.setBundleEnabled('extra', true)).toMatchObject({ changed: false, application: 'failed' })
expect(await manager.setVersionExemption('[email protected]', runtime, true)).toMatchObject({ changed: false, application: 'failed' })
expect(manager.listVersionExemptions()).toEqual({ exemptions: {}, warnings: [] })
expect(existsSync(join(dir, 'compatibility.json'))).toBe(false)
expect(await manager.setVersionExemption('[email protected]', runtime, true, true)).toMatchObject({
changed: true, application: mode === 'live' ? 'applied' : 'restart-required',
})
expect(manager.listVersionExemptions()).toEqual({ exemptions: { '[email protected]': [runtime] }, warnings: [] })
// The grant is persisted only in the profile's compatibility file, never in its package manifest.
expect(JSON.parse(readFileSync(join(dir, 'compatibility.json'), 'utf8'))).toEqual({ '[email protected]': [runtime] })
expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toEqual(['core', 'extra'])
// `setVersionExemption` reconciles the profile itself, so a live tree re-admits the bundle here;
// no manifest watch or Loader instrumentation participates. A startup-only profile keeps it out until restart.
if (mode === 'live') expect(managed()?.fiber?.state).toBe(2)
else expect(managed()).toBeUndefined()
expect(await manager.setVersionExemption('[email protected]', runtime, false)).toMatchObject({ changed: true })
expect(manager.listVersionExemptions()).toEqual({ exemptions: {}, warnings: [] })
expect(JSON.parse(readFileSync(join(dir, 'compatibility.json'), 'utf8'))).toEqual({})
if (mode === 'live') expect(managed()).toBeUndefined()
})
it('reports a package run refused for compatibility as a typed refusal', async () => {
const { manager } = await fixture()
const incompatible = [{ name: 'dsh-x', version: '2.0.0', runtimeVersion: getDshRuntimeVersion(), requirements: { '@deepseek-ai/dsh': '999.0.0' } }]
const install = vi.spyOn(operations, 'runProfilePnpm').mockResolvedValue({
exitCode: 1, output: 'dsh: installation rejected', truncated: false, logPath: 'pnpm.log', kind: 'unknown', incompatible,
})
onTestFinished(() => { install.mockRestore() })
const result = await manager.installBundle('dsh-x')
expect(result).toMatchObject({ application: 'failed', changed: false, error: { code: 'incompatible-version', incompatible } })
expect(install).toHaveBeenCalledTimes(1)
})
it.each([false, true])('rechecks installed bundle peers before accepting a disabled installation (exempted=%s)', async (exempted) => {
const { manager, dir, bundle } = await fixture()
if (exempted) await manager.setVersionExemption('[email protected]', getDshRuntimeVersion(), true, true)
const before = readFileSync(join(dir, 'package.json'), 'utf8')
const install = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
bundle('incompatible', [])
const file = join(dir, 'node_modules', 'incompatible', 'package.json')
const metadata = JSON.parse(readFileSync(file, 'utf8')) as Record<string, unknown>
writeFileSync(file, JSON.stringify({ ...metadata, peerDependencies: { '@deepseek-ai/dsh': '<0.0.0' } }))
const profile = readProfileManifest('test', dir)
profile.dependencies = { ...profile.dependencies, incompatible: '1.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(profile))
return { exitCode: 0, output: 'installed', truncated: false, logPath: join(dir, 'pnpm.log') }
})
onTestFinished(() => { install.mockRestore() })
const result = await manager.installBundle('incompatible', { enabled: false })
expect(result).toMatchObject({ application: exempted ? 'applied' : 'failed', changed: exempted })
if (!exempted) {
expect(result.error).toMatchObject({ code: 'incompatible-version', incompatible: [{ name: 'incompatible', version: '1.0.0' }] })
expect(readFileSync(join(dir, 'package.json'), 'utf8')).toBe(before)
}
expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toEqual(['core', 'extra'])
})
it('lists absent Official entries offline without resolving their packages or consulting registries', async () => {
const { manager } = await fixture()
const resolve = vi.spyOn(operations, 'bundleManifest')
const registry = vi.spyOn(operations, 'viewProfilePackage')
onTestFinished(() => { resolve.mockRestore(); registry.mockRestore() })
const bundles = await manager.listBundles()
for (const entry of OFFICIAL_ON_DEMAND_CATALOG) {
expect(bundles.filter(bundle => bundle.name === entry.packageName)).toEqual([{
name: entry.packageName, official: true, availability: 'missing', enabled: false, installed: false, optional: false,
removable: false, updatable: false, rows: [], overrides: [], meta: entry.meta,
installTarget: { spec: `${entry.packageName}@${getDshRuntimeVersion()}`, version: getDshRuntimeVersion() },
}])
expect(resolve.mock.calls.some(([name]) => name === entry.packageName)).toBe(false)
}
expect(registry).not.toHaveBeenCalled()
})
it('distinguishes an unreadable profile dependency from an absent catalog selection', async () => {
const { manager, dir, profile } = await fixture()
const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, [name]: '1.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
const broken = join(profile.home, 'node_modules', name)
mkdirSync(broken, { recursive: true })
writeFileSync(join(broken, 'package.json'), '{')
expect((await manager.listBundles()).find(bundle => bundle.name === name)).toMatchObject({
official: true, installed: true, availability: 'missing', enabled: false, removable: true,
source: `${name}@1.0.0`, error: { code: 'operation-error' },
})
})
it('uses the generic exact installer and keeps an Official entry through on, off, and removal', async () => {
const { manager, dir, bundle, profile } = await fixture()
const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName
const version = getDshRuntimeVersion()
const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async (_dir, args) => {
const manifest = readProfileManifest('test', dir)
if (args[0] === 'add') {
bundle(name, [{ id: 'official-probe', name: './plugin.mjs', config: { service: 'officialProbe' } }], profile.home)
const packagePath = join(profile.home, 'node_modules', name, 'package.json')
const packaged = JSON.parse(readFileSync(packagePath, 'utf8')) as Record<string, unknown>
writeFileSync(packagePath, JSON.stringify({ ...packaged, version }))
manifest.dependencies = { ...manifest.dependencies, [name]: version }
} else {
delete manifest.dependencies?.[name]
rmSync(join(profile.home, 'node_modules', name), { recursive: true, force: true })
}
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: '', truncated: false, logPath: '/install.log' }
})
onTestFinished(() => { pnpm.mockRestore() })
expect(await manager.installBundle(`${name}@${version}`, { saveExact: true })).toMatchObject({ application: 'applied', enabled: true })
expect(pnpm.mock.calls[0]?.[1]).toContain('--save-exact')
expect((await manager.listBundles()).find(item => item.name === name)).toMatchObject({ official: true, availability: 'profile', enabled: true, version })
expect(await manager.setBundleEnabled(name, false)).toMatchObject({ application: 'applied' })
expect(readProfileManifest('test', dir).dependencies?.[name]).toBe(version)
expect(await manager.removeBundle(name)).toMatchObject({ application: 'applied' })
expect((await manager.listBundles()).filter(item => item.name === name)).toHaveLength(1)
expect((await manager.listBundles()).find(item => item.name === name)).toMatchObject({ official: true, availability: 'missing', enabled: false, installed: false })
})
it('restores dependency and selection after an exact version is unavailable', async () => {
const { manager, dir, bundle, profile } = await fixture()
const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName
bundle(name, [], profile.home)
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, [name]: '1.0.0' }
manifest.dsh!.profile!.bundles!.push(name)
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
const before = readFileSync(join(dir, 'package.json'), 'utf8')
const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
writeFileSync(join(dir, 'package.json'), '{}')
return { exitCode: 1, output: 'No matching version', truncated: false, logPath: '/install.log', kind: 'no-matching-version' }
})
onTestFinished(() => { pnpm.mockRestore() })
expect(await manager.installBundle(`${name}@${getDshRuntimeVersion()}`, { saveExact: true })).toMatchObject({ application: 'failed', stage: 'install' })
expect(readFileSync(join(dir, 'package.json'), 'utf8')).toBe(before)
})
it('retains a successful installation and saved selection when Official activation fails', async () => {
const { manager, dir, bundle, profile } = await fixture()
const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName
const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
bundle(name, [{ id: 'official-probe', name: './plugin.mjs', config: { fail: true } }], profile.home)
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, [name]: getDshRuntimeVersion() }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: '', truncated: false, logPath: '/install.log' }
})
onTestFinished(() => { pnpm.mockRestore() })
expect(await manager.installBundle(`${name}@${getDshRuntimeVersion()}`, { saveExact: true })).toMatchObject({ application: 'failed', stage: 'enable' })
expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toContain(name)
expect(readProfileManifest('test', dir).dependencies?.[name]).toBe(getDshRuntimeVersion())
})
it('reports and removes a saved Official selection whose dependency is absent', async () => {
const { manager, dir } = await fixture()
const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName
const manifest = readProfileManifest('test', dir)
manifest.dsh!.profile!.bundles!.push(name)
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
expect((await manager.listBundles()).find(bundle => bundle.name === name)).toMatchObject({
official: true, installed: false, availability: 'missing', enabled: true, removable: true, error: { code: 'not-bundle' },
})
const pnpm = vi.spyOn(operations, 'runProfilePnpm')
onTestFinished(() => { pnpm.mockRestore() })
expect(await manager.removeBundle(name)).toMatchObject({ application: 'applied' })
expect(pnpm).not.toHaveBeenCalled()
expect((await manager.listBundles()).find(bundle => bundle.name === name)).toMatchObject({ availability: 'missing', enabled: false })
})
it('keeps a broken installation-carried catalog entry visible with offline metadata', async () => {
const { manager, profile } = await fixture()
const entry = OFFICIAL_ON_DEMAND_CATALOG[0]!
writeFileSync(profile.installAnchor, JSON.stringify({ name: 'installation', dependencies: { [entry.packageName]: '1.0.0' } }))
const broken = join(profile.home, 'node_modules', entry.packageName)
mkdirSync(broken, { recursive: true })
writeFileSync(join(broken, 'package.json'), '{')
expect((await manager.listBundles()).find(bundle => bundle.name === entry.packageName)).toMatchObject({
official: true, availability: 'missing', enabled: false, installed: false, optional: false, removable: false,
meta: entry.meta, installTarget: { version: getDshRuntimeVersion() }, error: { code: 'operation-error' },
})
})
it('offers checkout links beside stale registry and other-checkout dependencies at the same version', async () => {
const { manager, dir, bundle, profile } = await fixture('live', false, undefined, {}, undefined, undefined, true)
const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName
const version = getDshRuntimeVersion()
const source = join(profile.home, 'packages', 'native', 'provider')
mkdirSync(join(source, 'lib'), { recursive: true })
writeFileSync(join(source, 'package.json'), JSON.stringify({ name, version, main: './lib/index.js', dsh: { bundle: { patch: './cordis.patch.yml' } } }))
writeFileSync(join(source, 'lib', 'index.js'), 'export function apply() {}\n')
writeFileSync(join(source, 'cordis.patch.yml'), '[]\n')
bundle(name, [], profile.home)
const installedManifest = join(profile.home, 'node_modules', name, 'package.json')
writeFileSync(installedManifest, JSON.stringify({ ...JSON.parse(readFileSync(installedManifest, 'utf8')), version }))
const target = { spec: `link:${source}`, version }
expect((await manager.listBundles()).find(item => item.name === name)).toMatchObject({ availability: 'missing', installTarget: target })
for (const spec of [version, 'link:../other-checkout/native', `link:${source}`]) {
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, [name]: spec }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
const listed = (await manager.listBundles()).find(item => item.name === name)
expect(listed).toMatchObject({ version, official: true, availability: 'profile', installTarget: target })
expect(listed?.source === listed?.installTarget?.spec).toBe(spec === `link:${source}`)
}
})
it.each(['absent', 'bundle', 'not-bundle', 'unreadable'])('keeps a missing source bundle visible when its installed copy is %s', async (state) => {
const { manager, dir, bundle, profile } = await fixture('live', false, undefined, {}, undefined, undefined, true)
const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName
if (state !== 'absent') {
bundle(name, [], profile.home)
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, [name]: '1.0.0' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
const file = join(profile.home, 'node_modules', name, 'package.json')
if (state === 'not-bundle') writeFileSync(file, JSON.stringify({ name, version: '1.0.0' }))
if (state === 'unreadable') writeFileSync(file, '{')
}
expect((await manager.listBundles()).find(item => item.name === name)).toMatchObject({
official: true, error: { code: 'operation-error' },
})
expect((await manager.listBundles()).find(item => item.name === name)?.error?.diagnostic).toContain('restore the checkout')
expect((await manager.listBundles()).find(item => item.name === name)?.installTarget).toBeUndefined()
})
/** A `pnpm view` stand-in answering by spec and printed fields. */
function viewAnswers(
answer: (spec: string, fields: readonly string[], registry: string | null | undefined) => operations.PackageViewResult | object | string,
) {
const view = vi.spyOn(operations, 'viewProfilePackage').mockImplementation(async (_dir, spec, options) => {
const value = answer(spec, options.fields, options.registry)
if (typeof value === 'object' && 'exitCode' in value) return value
return { exitCode: 0, stdout: typeof value === 'string' ? value : JSON.stringify(value), stderr: '', timedOut: false }
})
onTestFinished(() => { view.mockRestore() })
return view
}
const BUNDLE = { bundle: { patch: './cordis.patch.yml' } }
/** The published `extra` versions the version tests offer; the installed copy is 1.0.0. */
function extraRegistry(spec: string, fields: readonly string[]): object {
if (fields.includes('versions')) {
return {
versions: ['0.9.0', '1.0.0', '1.1.0', '1.2.0-beta.1', '1.2.0', '2.0.0', 'not-semver'],
time: { '1.1.0': '2026-09-01T00:00:00.000Z', '2.0.0': '2026-10-10T00:00:00.000Z' },
'dist-tags': { latest: '2.0.0', next: '1.2.0-beta.1' },
}
}
const version = spec.slice('extra@'.length)
if (version === '2.0.0') return { name: 'extra', version, peerDependencies: { '@deepseek-ai/dsh': '>=999.0.0' }, dsh: BUNDLE }
if (version === '1.1.0') return { name: 'extra', version, deprecated: 'use 1.2.0', dsh: BUNDLE }
if (version === '1.2.0-beta.1') return { name: 'extra', version }
// An own `engines.dsh` decides compatibility alone, so its unmet peer range does not make this version incompatible.
if (version === '1.2.0') {
return { name: 'extra', version, engines: { dsh: '>=0.0.0-0' }, peerDependencies: { '@deepseek-ai/dsh': '>=999.0.0' }, dsh: BUNDLE }
}
return { name: 'extra', version, dsh: BUNDLE }
}
it('lists newer registry versions with tags, times, deprecation, bundle declaration, and DSH compatibility', async () => {
const config = { inspectTimeoutMs: 1000, versionListLimit: 3, versionLookupConcurrency: 2 }
const { manager, dir } = await fixture(undefined, false, undefined, config)
const view = viewAnswers(extraRegistry)
const listed = await manager.listVersions('extra')
expect(listed).toEqual({
status: 'listed', installed: '1.0.0', registry: null, attempts: [{ registry: null }], omitted: 1,
versions: [
{
version: '2.0.0', publishedAt: '2026-10-10T00:00:00.000Z', tags: ['latest'], prerelease: false, bundle: true,
incompatible: { name: 'extra', version: '2.0.0', runtimeVersion: getDshRuntimeVersion(), requirements: { '@deepseek-ai/dsh': '>=999.0.0' } },
},
{ version: '1.2.0', tags: [], prerelease: false, bundle: true },
{ version: '1.2.0-beta.1', tags: ['next'], prerelease: true, bundle: false },
],
})
// The list itself, then one lookup per listed version on the registry that answered.
expect(view.mock.calls.map(call => call[1])).toEqual(expect.arrayContaining(['extra', '[email protected]', '[email protected]', '[email protected]']))
expect(view).toHaveBeenCalledTimes(4)
expect(view).toHaveBeenCalledWith(dir, 'extra', expect.objectContaining({ fields: ['versions', 'time', 'dist-tags'], timeoutMs: 1000 }))
expect(view).toHaveBeenCalledWith(dir, '[email protected]', expect.objectContaining({ fields: ['name', 'version', 'engines', 'peerDependencies', 'deprecated', 'dsh'], registry: null }))
})
it('lists versions from the next registry while one is unreachable, and refuses what no registry answered', async () => {
const { manager } = await fixture(undefined, false, undefined, { inspectTimeoutMs: 1000 })
const unreachable = { exitCode: 1, stdout: '', stderr: 'ERR_PNPM_META_FETCH_FAIL GET https://registry.npmjs.org/extra: ETIMEDOUT\n', timedOut: false }
viewAnswers((spec, fields, registry) => registry === null ? unreachable
: fields.includes('versions') ? { versions: ['1.0.0', '1.1.0'], 'dist-tags': { latest: '1.1.0' } } : extraRegistry(spec, fields))
// A requested registry is asked first.
expect(await manager.listVersions('extra', { registry: MIRROR })).toMatchObject({ status: 'listed', registry: MIRROR, attempts: [{ registry: MIRROR }] })
expect(await manager.listVersions('extra')).toMatchObject({
status: 'listed', registry: MIRROR,
attempts: [{ registry: null, problem: 'network', reason: unreachable.stderr.trim() }, { registry: MIRROR }],
versions: [{ version: '1.1.0', deprecated: 'use 1.2.0', tags: ['latest'] }],
})
viewAnswers(() => unreachable)
expect(await manager.listVersions('extra')).toEqual({
status: 'refused', problem: 'network', reason: unreachable.stderr.trim(),
attempts: [{ registry: null, problem: 'network', reason: unreachable.stderr.trim() }, { registry: MIRROR, problem: 'network', reason: unreachable.stderr.trim() }],
})
// One version that cannot be read refuses the list, naming the registry that failed it.
viewAnswers((spec, fields) => spec === '[email protected]' ? { exitCode: 1, stdout: '', stderr: 'boom', timedOut: false } : extraRegistry(spec, fields))
expect(await manager.listVersions('extra')).toMatchObject({ status: 'refused', problem: 'unknown', reason: 'boom', attempts: [{ registry: null, problem: 'unknown' }] })
viewAnswers(() => ({ 'dist-tags': {} }))
expect(await manager.listVersions('extra')).toMatchObject({ status: 'refused', problem: 'unknown', reason: 'pnpm view answered no versions' })
viewAnswers((spec, fields) => fields.includes('versions') ? { versions: '1.0.0' } : extraRegistry(spec, fields))
expect(await manager.listVersions('extra')).toMatchObject({ status: 'listed', versions: [], omitted: 0 })
viewAnswers((_spec, fields) => fields.includes('versions') ? { versions: ['1.1.0'] } : { name: 'extra', version: '1.1.0', peerDependencies: { '@deepseek-ai/dsh': 3 } })
expect(await manager.listVersions('extra')).toMatchObject({ status: 'refused', problem: 'unknown', reason: expect.stringContaining('must be a string') as string })
})
it('refuses to list versions of a package not installed from a registry', async () => {
const { manager, dir, bundle } = await fixture()
const view = viewAnswers(extraRegistry)
const refusal = (reason: string) => ({ status: 'refused', problem: 'not-updatable', reason, attempts: [] })
expect(await manager.listVersions('core')).toEqual(refusal('core is not a third-party package installed in this profile'))
expect(await manager.listVersions('absent')).toEqual(refusal('absent is not a third-party package installed in this profile'))
bundle('linked', [])
const manifest = readProfileManifest('test', dir)
manifest.dependencies = { ...manifest.dependencies, linked: `link:${join(dir, 'node_modules', 'linked')}` }
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
expect(await manager.listVersions('linked')).toEqual(refusal('linked is not installed from a registry under its own name'))
// An alias saves another package under the dependency's name; its versions are not the alias's.
const aliased = readProfileManifest('test', dir)
aliased.dependencies = { ...aliased.dependencies, linked: 'npm:[email protected]' }
writeFileSync(join(dir, 'package.json'), JSON.stringify(aliased))
expect(await manager.listVersions('linked')).toEqual(refusal('linked is not installed from a registry under its own name'))
writeFileSync(join(dir, 'node_modules', 'extra', 'package.json'), JSON.stringify({ name: 'extra', version: 'nightly', dsh: BUNDLE }))
expect(await manager.listVersions('extra')).toEqual(refusal('extra declares no semver version'))
// A bundle carrying plugin management cannot be removed, so it is not offered for an update.
writeFileSync(join(dir, 'node_modules', 'extra', 'cordis.patch.yml'), JSON.stringify([{ insert: [{ id: 'pm', name: '@deepseek-ai/dsh-plugin-manager' }] }]))
expect((await manager.listBundles()).find(item => item.name === 'extra')).toMatchObject({ readOnlyReason: 'management-required', updatable: false })
expect(await manager.listVersions('extra')).toEqual(refusal('extra carries plugin management and cannot be removed'))
expect(view).not.toHaveBeenCalled()
})
/** A pnpm stand-in that removes `extra` and installs the version an `add` names, unless `fails` names it. */
function extraPnpm(dir: string, bundle: (name: string, rows: unknown[]) => void, fails: readonly string[] = []) {
const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async (_context, args) => {
const manifest = readProfileManifest('test', dir)
const spec = String(args[1])
if (args[0] === 'remove') {
delete manifest.dependencies?.extra
rmSync(join(dir, 'node_modules', 'extra'), { recursive: true, force: true })
} else if (fails.includes(spec)) {
return { exitCode: 1, output: `ERR_PNPM_FETCH_404 GET ${spec}: Not Found - 404`, truncated: false, logPath: '/install.log', kind: 'not-found' as const }
} else {
const version = spec.slice('extra@'.length)
bundle('extra', [{ id: 'managed', name: './plugin.mjs' }])
writeFileSync(join(dir, 'node_modules', 'extra', 'package.json'), JSON.stringify({ name: 'extra', version, dsh: BUNDLE }))
manifest.dependencies = { ...manifest.dependencies, extra: version }
}
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
return { exitCode: 0, output: '', truncated: false, logPath: '/install.log' }
})
onTestFinished(() => { pnpm.mockRestore() })
return pnpm
}
it('updates a registry bundle by removing it and installing the exact newer version with its selection', async () => {
const { ctx, manager, dir, bundle } = await fixture()
viewAnswers(extraRegistry)
const pnpm = extraPnpm(dir, bundle)
const steps: string[] = []
ctx.on('plugin-manager/update-state', ({ step }) => { steps.push(step) })
const requestId = 'c2b8f1a4-6f0e-4b52-9a51-0a8e5f0b7d11' as PluginInstallRequestId
const updated = await manager.updateBundle('extra', '1.2.0', { registry: MIRROR, requestId })
expect(updated).toMatchObject({ from: '1.0.0', to: '1.2.0', step: 'install', result: { application: 'applied', version: '1.2.0', enabled: true } })
expect(updated.restore).toBeUndefined()
expect(steps).toEqual(['checking', 'removing', 'installing'])
expect(pnpm.mock.calls.map(call => call[1])).toEqual([['remove', 'extra'], ['add', '[email protected]', '--save-exact', `--registry=${MIRROR}`]])
expect(readProfileManifest('test', dir).dependencies?.extra).toBe('1.2.0')
expect(ctx.get('managedProbe')).toBe(true)
})
it('installs the removed version again when the target fails to install', async () => {
const { manager, dir, bundle } = await fixture()
viewAnswers(extraRegistry)
const pnpm = extraPnpm(dir, bundle, ['[email protected]'])
// The removed copy came from the mirror, so it is installed again from there.
await manager.updateBundle('extra', '1.1.0', { registry: MIRROR })
pnpm.mockClear()
const updated = await manager.updateBundle('extra', '1.2.0')
expect(updated).toMatchObject({
from: '1.1.0', to: '1.2.0', step: 'install', result: { application: 'failed', packageResult: { kind: 'not-found' } },
restore: { application: 'applied', version: '1.1.0' },
})
expect(pnpm.mock.calls.map(call => call[1])).toEqual([
// The update starts at the registry the package came from; a 404 there may be a mirror not yet synced, so pnpm's
// own registry is asked too before the installation gives up.
['remove', 'extra', `--config.registry=${MIRROR}`], ['add', '[email protected]', '--save-exact', `--registry=${MIRROR}`], ['add', '[email protected]', '--save-exact'],
['add', '[email protected]', '--save-exact', `--registry=${MIRROR}`],
])
expect(readProfileManifest('test', dir).dependencies?.extra).toBe('1.1.0')
})
it.each([
['cancelled', 'cancelled'],
['rejected', 'rejected'],
] as const)('installs the removed version again when the target installation is %s', async (_name, outcome) => {
const { manager, dir, bundle } = await fixture()
viewAnswers(extraRegistry)
extraPnpm(dir, bundle)
const install = manager.installBundle.bind(manager)
const spy = vi.spyOn(manager, 'installBundle').mockImplementation(async (spec, options) => {
if (spec !== '[email protected]') return install(spec, options)
if (outcome === 'rejected') throw new Error('lock wait timed out')
return { changed: false, application: 'cancelled', stage: 'install', target: spec }
})
onTestFinished(() => { spy.mockRestore() })
const updated = await manager.updateBundle('extra', '1.2.0')
expect(updated.result.application).toBe(outcome === 'rejected' ? 'failed' : 'cancelled')
if (outcome === 'rejected') expect(updated.result.error).toEqual({ code: 'operation-error', diagnostic: 'lock wait timed out' })
expect(updated.restore).toMatchObject({ application: 'applied', version: '1.0.0' })
expect(readProfileManifest('test', dir).dependencies?.extra).toBe('1.0.0')
})
it('removes a target that installed but could not start before installing the removed version again', async () => {
const { manager, dir, bundle } = await fixture()
viewAnswers(extraRegistry)
const pnpm = extraPnpm(dir, bundle)
const install = manager.installBundle.bind(manager)
const spy = vi.spyOn(manager, 'installBundle').mockImplementation(async (spec, options) => {
const result = await install(spec, options)
// The target's files land, then its activation fails, as a plugin that throws on start does.
return spec === '[email protected]' ? { ...result, application: 'failed', stage: 'enable', error: { code: 'operation-error', diagnostic: 'cannot start' } } : result
})
onTestFinished(() => { spy.mockRestore() })
const updated = await manager.updateBundle('extra', '1.2.0')
expect(updated).toMatchObject({ result: { application: 'failed', stage: 'enable' }, restore: { application: 'applied', version: '1.0.0' } })
expect(pnpm.mock.calls.map(call => call[1].slice(0, 2))).toEqual([['remove', 'extra'], ['add', '[email protected]'], ['remove', 'extra'], ['add', '[email protected]']])
expect(readProfileManifest('test', dir).dependencies?.extra).toBe('1.0.0')
})
it('reports a removal that rejects as the update\'s last step', async () => {
const { manager, dir, bundle } = await fixture()
viewAnswers(extraRegistry)
const pnpm = extraPnpm(dir, bundle)
const spy = vi.spyOn(manager, 'removeBundle').mockRejectedValue(new Error('lock wait timed out'))
onTestFinished(() => { spy.mockRestore() })
expect(await manager.updateBundle('extra', '1.2.0')).toMatchObject({
step: 'remove', result: { application: 'failed', stage: 'remove', error: { code: 'operation-error', diagnostic: 'lock wait timed out' } },
})
expect(pnpm).not.toHaveBeenCalled()
})
it('reports a reinstallation that fails too, leaving the package uninstalled', async () => {
const { manager, dir, bundle } = await fixture()
viewAnswers(extraRegistry)
extraPnpm(dir, bundle, ['[email protected]', '[email protected]'])
const updated = await manager.updateBundle('extra', '1.2.0')
expect(updated).toMatchObject({ step: 'install', result: { application: 'failed' }, restore: { application: 'failed' } })
expect(readProfileManifest('test', dir).dependencies?.extra).toBeUndefined()
})
it('refuses an update before changing anything when the target is not a newer compatible bundle', async () => {
const { manager, dir, bundle } = await fixture()
viewAnswers((spec, fields) => spec === '[email protected]' ? { exitCode: 1, stdout: '', stderr: 'ERR_PNPM_NO_MATCHING_VERSION No matching version found for [email protected]', timedOut: false }
: spec === '[email protected]' ? { name: 'extra', version: '1.5.0', peerDependencies: { '@deepseek-ai/dsh': 7 }, dsh: BUNDLE }
: extraRegistry(spec, fields))
const pnpm = extraPnpm(dir, bundle)
const before = readFileSync(join(dir, 'package.json'), 'utf8')
const refused = async (name: string, version: string) => (await manager.updateBundle(name, version)).result.error
expect(await refused('extra', '0.9.0')).toEqual({ code: 'invalid-spec', diagnostic: '0.9.0 is not a version newer than the installed 1.0.0' })
expect(await refused('extra', 'latest')).toMatchObject({ code: 'invalid-spec' })
expect(await refused('core', '2.0.0')).toEqual({ code: 'invalid-spec', diagnostic: 'core is not a third-party package installed in this profile' })
expect(await refused('extra', '2.0.0')).toMatchObject({ code: 'incompatible-version', incompatible: [{ name: 'extra', version: '2.0.0' }] })
expect(await refused('extra', '1.2.0-beta.1')).toEqual({ code: 'not-bundle' })
expect(await refused('extra', '3.0.0')).toMatchObject({ code: 'operation-error', diagnostic: expect.stringContaining('No matching version') as string })
expect(await refused('extra', '1.5.0')).toMatchObject({ code: 'operation-error', diagnostic: expect.stringContaining('must be a string') as string })
expect(await manager.updateBundle('extra', '0.9.0')).toMatchObject({ from: '1.0.0', to: '0.9.0', step: 'check', result: { application: 'failed', changed: false, stage: 'install', target: 'extra' } })
expect(pnpm).not.toHaveBeenCalled()
expect(readFileSync(join(dir, 'package.json'), 'utf8')).toBe(before)
})
it('reports a removal the profile refuses as the update\'s last step', async () => {
const { manager, dir, bundle } = await fixture()
viewAnswers(extraRegistry)
const pnpm = extraPnpm(dir, bundle)
pnpm.mockResolvedValueOnce({ exitCode: 1, output: 'EBUSY', truncated: false, logPath: '/remove.log', kind: 'unknown' })
expect(await manager.updateBundle('extra', '1.2.0')).toMatchObject({ step: 'remove', result: { application: 'failed', stage: 'remove' } })
expect(pnpm).toHaveBeenCalledOnce()
})
it('installs over the running version without HMR and reports the restart', async () => {
const { ctx, manager, dir, bundle } = await fixture('startup')
viewAnswers(extraRegistry)
const pnpm = extraPnpm(dir, bundle)
const steps: string[] = []
ctx.on('plugin-manager/update-state', ({ step }) => { steps.push(step) })
const requestId = '5f7d1e9a-2b3c-4d5e-8f90-a1b2c3d4e5f6' as PluginInstallRequestId
expect(await manager.updateBundle('extra', '1.2.0', { requestId })).toMatchObject({
step: 'install', result: { application: 'restart-required', restartReason: 'no-hmr', version: '1.2.0' },
})
expect(steps).toEqual(['checking', 'installing'])
expect(pnpm.mock.calls.map(call => call[1])).toEqual([['add', '[email protected]', '--save-exact']])
})