import { lstatSync, readFileSync, writeFileSync, openSync, closeSync, fsyncSync, renameSync, unlinkSync } from 'node:fs'; import { dirname, join, resolve, basename } from 'node:path'; import { randomUUID } from 'node:crypto'; export const BUILTIN_DEPENDENCIES = Object.freeze({ 'dsh-preview-download': 'file:/usr/local/lib/dsh-mgr/dsh-preview-download-1.0.1.tgz', 'dsh-chat-manager': 'file:/usr/local/lib/dsh-mgr/dsh-chat-manager-1.5.16-dsh-mgr.1.tgz', 'dsh-mgr-workspace': 'file:/usr/local/lib/dsh-mgr/dsh-mgr-workspace-1.0.0.tgz', 'dsh-web-mobile': 'file:/usr/local/lib/dsh-mgr/dsh-web-mobile-3.1.0.tgz', 'dsh-free-search': 'file:/usr/local/lib/dsh-mgr/dsh-free-search-0.9.2.tgz', }); export const BUILTIN_OVERRIDES = Object.freeze({ 'dsh-chat-manager>clsx': 'file:/usr/local/lib/dsh-mgr/clsx-2.1.1.tgz', 'dsh-free-search>@deepseek-ai/schemastery': 'file:/usr/local/lib/dsh-mgr/schemastery-3.18.4.tgz', '@deepseek-ai/schemastery>@deepseek-ai/cosmokit': 'file:/usr/local/lib/dsh-mgr/cosmokit-1.8.5.tgz', '@deepseek-ai/schemastery>@standard-schema/spec': 'file:/usr/local/lib/dsh-mgr/spec-1.1.0.tgz', }); const sections = ['dependencies', 'devDependencies', 'optionalDependencies']; export const LOCKFILE_PENDING = '.dsh-lockfile-pending'; export function markLockfilePending(profile) { assertSafeProfile(profile); const fd = openSync(join(profile, LOCKFILE_PENDING), 'w', 0o600); try { writeFileSync(fd, 'builtin profile migration\n'); fsyncSync(fd); } finally { closeSync(fd); } const directoryFd = openSync(profile, 'r'); try { fsyncSync(directoryFd); } finally { closeSync(directoryFd); } } const overrideOwners = ['dsh-chat-manager', 'dsh-free-search', 'dsh-free-search', 'dsh-free-search']; const semver = '(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?'; function object(value, label) { if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`invalid ${label}`); } export function reconcileManifest(input) { object(input, 'manifest'); const manifest = structuredClone(input); const active = new Set(); const archives = new Set(); let changed = false; for (const section of sections) { if (manifest[section] === undefined) continue; object(manifest[section], section); for (const [name, target] of Object.entries(BUILTIN_DEPENDENCIES)) { const current = manifest[section][name]; if (typeof current !== 'string' || !new RegExp(`^file:/usr/local/lib/dsh-mgr/${name}-${semver}\\.tgz$`).test(current)) continue; active.add(name); archives.add(target); if (current !== target) { manifest[section][name] = target; changed = true; } } } if (manifest.pnpm !== undefined) { object(manifest.pnpm, 'pnpm'); if (manifest.pnpm.overrides !== undefined) object(manifest.pnpm.overrides, 'pnpm.overrides'); } Object.entries(BUILTIN_OVERRIDES).forEach(([key, target], index) => { if (!active.has(overrideOwners[index])) return; manifest.pnpm ??= {}; manifest.pnpm.overrides ??= {}; if (!Object.hasOwn(manifest.pnpm.overrides, key)) { manifest.pnpm.overrides[key] = target; changed = true; } if (manifest.pnpm.overrides[key] === target) archives.add(target); }); return { manifest, changed, archives: [...archives] }; } function statIfPresent(path) { try { return lstatSync(path); } catch (error) { if (error.code !== 'ENOENT') throw error; } } export function assertSafeProfile(profile) { for (let dir = resolve(profile); ; dir = dirname(dir)) { const stat = statIfPresent(dir); if (stat && (!stat.isDirectory() || stat.isSymbolicLink())) throw new Error(`unsafe profile directory: ${dir}`); if (dirname(dir) === dir) break; } for (const name of ['package.json', 'pnpm-workspace.yaml', 'pnpm-lock.yaml', LOCKFILE_PENDING]) { const path = join(profile, name); const stat = statIfPresent(path); if (stat && (!stat.isFile() || stat.isSymbolicLink())) throw new Error(`unsafe profile file: ${path}`); } } export function reconcileProfile(profile, { archiveDirectory = '/usr/local/lib/dsh-mgr' } = {}) { assertSafeProfile(profile); const path = join(profile, 'package.json'); if (!statIfPresent(path)) return { changed: false }; const original = readFileSync(path); const result = reconcileManifest(JSON.parse(original.toString('utf8'))); for (const reference of result.archives) { const archive = join(archiveDirectory, basename(reference.slice(5))); const stat = lstatSync(archive); if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`unsafe builtin archive: ${archive}`); } if (!result.changed) return { changed: false }; const backupPath = `${path}.backup-${randomUUID()}`; const temporaryPath = `${path}.tmp-${randomUUID()}`; let temporaryCreated = false; try { const backupFd = openSync(backupPath, 'wx', 0o600); try { writeFileSync(backupFd, original); fsyncSync(backupFd); } finally { closeSync(backupFd); } const syncDirectory = () => { const directoryFd = openSync(profile, 'r'); try { fsyncSync(directoryFd); } finally { closeSync(directoryFd); } }; // Make the recovery copy durable before replacing the active manifest. syncDirectory(); const fd = openSync(temporaryPath, 'wx', 0o600); temporaryCreated = true; try { writeFileSync(fd, JSON.stringify(result.manifest, null, 2) + '\n'); fsyncSync(fd); } finally { closeSync(fd); } assertSafeProfile(profile); // Persist before changing the manifest, including across failed installs/restarts. markLockfilePending(profile); renameSync(temporaryPath, path); temporaryCreated = false; syncDirectory(); } finally { if (temporaryCreated) unlinkSync(temporaryPath); } return { changed: true, backupPath }; }