import { createHash } from 'node:crypto'; import { mkdirSync, writeFileSync, readFileSync } from 'node:fs'; import { execFileSync } from 'node:child_process'; import { createRequire } from 'node:module'; const version = '3.2.1'; const integrity = '2XHbebwr78ujeJVt3wqffSp7/LEnLqUajawnL0TWNGG6LcAimAm8q8GJdh28c7gdRs6BYAGxfF/TatA5E6xZ+A=='; const response = await fetch('https://registry.npmjs.org/dsh-web-mobile/-/dsh-web-mobile-3.2.1.tgz', { signal: AbortSignal.timeout(120000) }); if (!response.ok) throw new Error('mobile plugin download failed'); const data = Buffer.from(await response.arrayBuffer()); if (createHash('sha512').update(data).digest('base64') !== integrity) throw new Error('mobile plugin integrity mismatch'); const destination = '/usr/local/lib/node_modules/@deepseek-ai/dsh/node_modules/dsh-web-mobile'; mkdirSync(destination, { recursive: true }); const archive = '/usr/local/lib/dsh-mgr/dsh-web-mobile-3.2.1.tgz'; writeFileSync(archive, data, { mode: 0o644 }); execFileSync('tar', ['-xzf', archive, '--strip-components=1', '--no-same-owner', '-C', destination]); // Published tar modes can be owner-only; runtime is UID 1000, not build root. execFileSync('chmod', ['-R', 'a+rX', destination]); const pkg = JSON.parse(readFileSync(destination + '/package.json')); if (pkg.version !== version || pkg.name !== 'dsh-web-mobile' || Object.keys(pkg.dependencies || {}).length) throw new Error('unexpected mobile package metadata'); const require = createRequire('/usr/local/lib/node_modules/@deepseek-ai/dsh/package.json'); const semver = createRequire('/usr/local/lib/node_modules/npm/package.json')('semver'); for (const [name, range] of Object.entries(pkg.peerDependencies || {})) { // React is provided by the browser shell, not a Node package in DSH. // Limit that exception to the upstream-declared, tested RC hosts. if (name === 'react') { const host = require('./package.json').version; if (host !== '0.2.1-rc.3') throw new Error('unverified browser React host: ' + host); continue; } let peer; try { peer = require(name + '/package.json'); } catch (error) { if (pkg.peerDependenciesMeta?.[name]?.optional) continue; throw new Error('missing required mobile peer: ' + name); } if (!(semver.satisfies(peer.version, range) || (peer.version === "0.2.1-rc.3" && name.startsWith("@deepseek-ai/dsh-") && semver.satisfies(peer.version, range, { includePrerelease: true })))) throw new Error('incompatible mobile peer: ' + name + '@' + peer.version); } // DSH runtime resolution traverses the installation manifest, not arbitrary // directories in node_modules. Declare the baked package in that graph. const hostManifest = '/usr/local/lib/node_modules/@deepseek-ai/dsh/package.json'; const host = JSON.parse(readFileSync(hostManifest)); host.dependencies = { ...host.dependencies, 'dsh-web-mobile': version }; writeFileSync(hostManifest, JSON.stringify(host, null, 2)); console.log('Installed dsh-web-mobile@3.2.1 (SHA-512 verified; no lifecycle scripts)');