Files
deepseek-harness/apps/cli/src/plugin.ts
T

108 lines
6.3 KiB
TypeScript

/** Profile package management and explicit, exact-version compatibility approvals. */
import { runPluginCommand, runProfilePnpm, setProfileVersionExemption, type PackageOperationOptions } from '@deepseek-ai/dsh-plugin-manager/operations'
import { INSTALL_ANCHOR } from './profile-boot.ts'
import { DEFAULT_PROFILE_BUNDLES, initProfile, PROFILE_TEMPLATES, readProfileCompatibility, resolveProfileDir, type ProfileContext } from '@deepseek-ai/dsh-app-boot'
import { withFileLock } from '@deepseek-ai/dsh-atomic-write'
import { existsSync } from 'node:fs'
import { mkdir } from 'node:fs/promises'
import { join } from 'node:path'
function requireDesktopProfile(dir: string): void {
if (!existsSync(join(dir, 'package.json'))) {
throw new Error('Open DeepSeek Harness Desktop once to initialize its profile, then fully quit it before running dsh plugin --profile desktop.')
}
}
/** Parse only DSH-owned commands; all other arguments remain pnpm's responsibility. */
async function versionCommand(profile: string, args: readonly string[]): Promise<number | undefined> {
const [command, ...rest] = args
if (command !== 'allow-version' && command !== 'revoke-version' && command !== 'version-exemptions') return undefined
try {
let packageVersion: string | undefined
let runtimeVersion: string | undefined
let acceptRisk = false
const argumentsIterator = rest.values()
for (const argument of argumentsIterator) {
if (argument === '--accept-risk' && command === 'allow-version' && !acceptRisk) acceptRisk = true
else if (argument === '--dsh-version' && runtimeVersion === undefined) runtimeVersion = argumentsIterator.next().value
else if (argument.startsWith('--dsh-version=') && runtimeVersion === undefined) runtimeVersion = argument.slice('--dsh-version='.length)
else if (!argument.startsWith('-') && packageVersion === undefined) packageVersion = argument
else throw new Error(`unexpected argument ${JSON.stringify(argument)}`)
}
if (command === 'version-exemptions' && rest.length > 0) throw new Error('usage: dsh plugin version-exemptions')
let request: { packageVersion: string; runtimeVersion: string } | undefined
if (command !== 'version-exemptions') {
if (packageVersion === undefined || runtimeVersion === undefined) {
throw new Error(`usage: dsh plugin ${command} <package@version> --dsh-version <exact>${command === 'allow-version' ? ' --accept-risk' : ''}`)
}
request = { packageVersion, runtimeVersion }
}
if (command === 'allow-version') {
process.stderr.write('dsh: warning: allowing incompatible plugin versions can break the application or corrupt data. Approval applies only to the exact package and DSH versions.\n')
}
const dir = resolveProfileDir(profile)
if (profile !== 'desktop') await mkdir(dir, { recursive: true })
await withFileLock(join(dir, 'package.json'), async () => {
if (profile === 'desktop') requireDesktopProfile(dir)
else if (!existsSync(join(dir, 'package.json'))) initProfile(dir, PROFILE_TEMPLATES[profile]?.bundles ?? DEFAULT_PROFILE_BUNDLES)
if (request === undefined) {
const { exemptions, warnings } = readProfileCompatibility(dir)
for (const warning of warnings) process.stderr.write(`dsh: warning: ${warning}\n`)
process.stdout.write(JSON.stringify(exemptions, undefined, 2) + '\n')
} else {
await setProfileVersionExemption(dir, request.packageVersion, request.runtimeVersion, command === 'allow-version', acceptRisk)
process.stdout.write(`dsh: ${command === 'allow-version' ? 'allowed' : 'revoked'} ${request.packageVersion} for DSH ${request.runtimeVersion}\n`)
}
}, { waitMs: 120000 })
return 0
} catch (error) {
process.stderr.write(`dsh: ${String(error)}\n`)
return 1
}
}
/** Run package management for a profile.
* @param profile Profile name; Desktop's reserved profile must already be initialized by the application.
* @param args DSH exemption command or pnpm arguments relative to the invoking directory.
* @param packageManager Installation-owned executable and environment for pnpm operations.
* @returns Zero on success; nonzero on invalid approval or package-manager failure.
*/
export async function runPlugin(profile: string, args: readonly string[], packageManager?: ProfileContext['packageManager']): Promise<number> {
if (profile === 'desktop') {
try { requireDesktopProfile(resolveProfileDir(profile)) } catch (error) {
process.stderr.write(`dsh: ${String(error)}\n`)
return 1
}
}
const versionResult = await versionCommand(profile, args)
if (versionResult !== undefined) return versionResult
const dir = resolveProfileDir(profile)
if (existsSync(join(dir, 'package.json'))) {
for (const warning of readProfileCompatibility(dir).warnings) process.stderr.write(`dsh: warning: ${warning}\n`)
}
const context = { profile, dir, installAnchor: INSTALL_ANCHOR, cwd: process.cwd() }
const options: PackageOperationOptions = {
...packageManager,
execution: 'cli',
outputBytes: 16384,
lockWaitMs: 120000,
lookupTimeoutMs: 120000,
onOutput: (text, stream) => { process[stream].write(text) },
}
const result = profile === 'desktop'
? await withFileLock(join(dir, 'package.json'), async () => {
requireDesktopProfile(dir)
return runProfilePnpm(context, args, options)
}, { waitMs: 120000 })
: await runPluginCommand(context, args, options)
if (result.exitCode === 127) process.stderr.write('dsh: pnpm was not found; install pnpm and make it available on PATH.\n')
for (const { name, version, runtimeVersion } of result.incompatible ?? []) {
process.stderr.write(`dsh: to accept the risk, run: dsh plugin --profile ${profile} allow-version ${name}@${version} --dsh-version ${runtimeVersion} --accept-risk\n`)
}
if (result.exitCode !== 0) process.stderr.write(`dsh: plugin command failed; diagnostics: ${result.logPath}\n`)
if (result.exitCode !== 0 && args.some(argument => /^git\+|^github:|\.git(?:#|$)/.test(argument))) {
process.stderr.write(`dsh: git-hosted plugins build on install via their prepare script, which pnpm blocks until allowed — add the exact key pnpm printed above under allowBuilds in ${join(resolveProfileDir(profile), 'pnpm-workspace.yaml')}, then re-run\n`)
}
return result.exitCode
}