48985d3447
补齐macOS universal双架构Codex资源与构建校验 统一发布清单和检查脚本支持universal目标 新增锁定原生依赖完整性校验与隔离构建smoke 新增Mac Jenkins Agent归档构建Job与本机构建接入规范
135 lines
4.8 KiB
JavaScript
135 lines
4.8 KiB
JavaScript
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();
|
|
}
|