import fs from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { createHash } from 'node:crypto'; import { execFileSync } from 'node:child_process'; import { assertPiNodeVersion } from '../src/runtime-policy.mjs'; // A local portable build. Never copy a profile, credential, project or request journal. const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const [binaryArg, destinationArg, nodeArg = process.execPath, licenseArg] = process.argv.slice(2); if (!binaryArg || !destinationArg) throw Error('指定已验证的原生程序与新的输出目录'); const destination = path.resolve(destinationArg), binary = path.resolve(binaryArg), node = path.resolve(nodeArg); if (destination === root || destination.startsWith(root + path.sep) || !path.isAbsolute(binary)) throw Error('输出目录必须独立于源码'); const version = execFileSync(node, ['--version'], { encoding: 'utf8', windowsHide: true }).trim(); assertPiNodeVersion(version); await fs.mkdir(path.dirname(destination), { recursive: true }); await fs.mkdir(destination); // Refuse to overwrite an existing client or user data. await fs.copyFile(binary, path.join(destination, 'photocraft.exe')); const agent = path.join(destination, 'agent'); await fs.mkdir(agent); await fs.copyFile(node, path.join(agent, 'node.exe')); const nodeLicense = licenseArg ? path.resolve(licenseArg) : path.join(path.dirname(node), 'LICENSE'); await fs.copyFile(nodeLicense, path.join(agent, 'LICENSE-Node.txt')); for (const file of ['package.json', 'package-lock.json']) await fs.copyFile(path.join(root, file), path.join(agent, file)); for (const directory of ['src', 'node_modules']) await fs.cp(path.join(root, directory), path.join(agent, directory), { recursive: true, dereference: true }); const native = path.resolve(root, '../detail-photocraft'); for (const file of ['LICENSE-MIT', 'LICENSE-APACHE', 'NOTICE', 'ATTRIBUTION.md']) await fs.copyFile(path.join(native, file), path.join(destination, file)); await fs.writeFile(path.join(destination, '启动客户端.cmd'), '@echo off\r\nstart "" "%~dp0photocraft.exe"\r\n', 'utf8'); await fs.writeFile(path.join(destination, '本地构建说明.txt'), `由壹点详情页本地开发构建\r\n双击“启动客户端.cmd”。Agent运行组件已随包提供,无需另装Node。\r\n账户、个人Key、项目与会话保存在当前Windows用户的独立配置和凭据库,不在本目录内。\r\n本包未签名,尚不是正式安装程序。Node ${version};Pi SDK 1.3.0。\r\n`, 'utf8'); const files = []; async function inventory(dir) { for (const entry of await fs.readdir(dir, { withFileTypes: true })) { const file = path.join(dir, entry.name); if (entry.isDirectory()) await inventory(file); else { const bytes = await fs.readFile(file); files.push({ path: path.relative(destination, file).replaceAll('\\', '/'), bytes: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex') }); } } } await inventory(destination); const manifest = { at: new Date().toISOString(), kind: 'unsigned-local-development-portable', node: version, pi: '1.3.0', bytes: files.reduce((sum, file) => sum + file.bytes, 0), files }; await fs.writeFile(path.join(destination, 'build-manifest.json'), JSON.stringify(manifest, null, 2)); console.log(JSON.stringify({ destination, files: files.length, bytes: manifest.bytes, node: version, pi: manifest.pi }));