import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import { createHash } from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const appRoot = fileURLToPath(new URL('..', import.meta.url)); const repoRoot = path.resolve(appRoot, '../..'); const platforms = { arm64: 'aarch64-apple-darwin', x64: 'x86_64-apple-darwin', }; export function lockedMacPackage(lock, arch, version) { assert.ok(Object.hasOwn(platforms, arch), '未知 macOS 架构'); const alias = `@openai/codex-darwin-${arch}`; const entry = lock.packages?.[`node_modules/${alias}`]; assert.equal( entry?.version, `${version}-darwin-${arch}`, '原生依赖必须与应用锁定版本一致', ); assert.deepEqual(entry.os, ['darwin']); assert.deepEqual(entry.cpu, [arch]); const url = new URL(entry.resolved); assert.equal(url.protocol, 'https:'); assert.equal( url.hostname, 'registry.npmjs.org', '只下载锁定的官方 npm 原生包', ); assert.equal(url.username + url.password + url.search + url.hash, ''); assert.match(entry.integrity, /^sha512-[A-Za-z0-9+/]+={0,2}$/); return { alias, target: platforms[arch], ...entry }; } export function verifyPackageIntegrity(bytes, expected) { const actual = `sha512-${createHash('sha512').update(bytes).digest('base64')}`; assert.equal(actual, expected, 'Codex 下载包 lockfile integrity 不匹配'); } export function validateArchiveListing(listing) { const files = listing.trim().split(/\r?\n/u); assert.ok(files.length > 0); for (const file of files) { assert.ok(file.startsWith('package/'), '原生包必须只有 package 根目录'); assert.ok( !file.split('/').includes('..') && !file.includes('\\'), '压缩包路径不安全', ); } } export async function prepareMacosCodex() { assert.equal(process.platform, 'darwin', '该入口仅用于 macOS 构建机'); const lock = JSON.parse( fs.readFileSync(path.join(repoRoot, 'package-lock.json'), 'utf8'), ); const app = JSON.parse( fs.readFileSync(path.join(appRoot, 'package.json'), 'utf8'), ); const version = app.devDependencies['@openai/codex']; assert.match(version, /^\d+\.\d+\.\d+$/u, 'Codex 必须锁定精确版本'); const cache = path.join(appRoot, 'src-tauri/target/.macos-native-cache'); fs.mkdirSync(cache, { recursive: true }); for (const arch of Object.keys(platforms)) { const entry = lockedMacPackage(lock, arch, version); const archive = path.join(cache, `codex-${entry.version}.tgz`); if (!fs.existsSync(archive)) { const response = await fetch(entry.resolved, { signal: AbortSignal.timeout(300_000), }); assert.ok(response.ok, `原生包下载失败 HTTP ${response.status}`); const bytes = Buffer.from(await response.arrayBuffer()); verifyPackageIntegrity(bytes, entry.integrity); const partial = `${archive}.${process.pid}.tmp`; fs.writeFileSync(partial, bytes); fs.renameSync(partial, archive); } verifyPackageIntegrity(fs.readFileSync(archive), entry.integrity); validateArchiveListing( execFileSync('tar', ['-tzf', archive], { encoding: 'utf8' }), ); // 拒绝链接、设备及其它特殊条目,不能让 tar 在包目录之外写入。 const entries = execFileSync('tar', ['-tvzf', archive], { encoding: 'utf8', }); assert.ok( entries .trim() .split(/\r?\n/u) .every((line) => /^[-d]/u.test(line)), '原生包禁止链接或特殊文件', ); const parent = path.join(repoRoot, 'node_modules/@openai'); fs.mkdirSync(parent, { recursive: true }); const stage = fs.mkdtempSync(path.join(parent, '.mac-native-')); try { execFileSync( 'tar', ['-xzf', archive, '-C', stage, '--strip-components=1'], { stdio: 'pipe' }, ); const metadata = JSON.parse( fs.readFileSync( path.join(stage, 'vendor', entry.target, 'codex-package.json'), 'utf8', ), ); assert.equal(metadata.version, version); assert.equal(metadata.target, entry.target); assert.equal(metadata.entrypoint, 'bin/codex'); const destination = path.join(repoRoot, 'node_modules', entry.alias); assert.ok( !fs.existsSync(destination) || !fs.lstatSync(destination).isSymbolicLink(), '拒绝覆盖链接依赖', ); fs.rmSync(destination, { recursive: true, force: true }); fs.renameSync(stage, destination); } finally { fs.rmSync(stage, { recursive: true, force: true }); } console.log(`[macOS Codex] ${entry.version}: lockfile integrity 已验证`); } } if ( process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url) ) { await prepareMacosCodex(); }