fix: harden release gate and page delivery
Memind CI / Test, build, and release guards (push) Failing after 12m3s
Memind CI / Test, build, and release guards (push) Failing after 12m3s
This commit is contained in:
@@ -5,6 +5,8 @@ import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { spawn } from 'node:child_process';
|
||||
|
||||
import { removeForbiddenPortalRuntimePaths } from '../release-gate/artifact.mjs';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const root = path.join(__dirname, '..');
|
||||
const runtimeRoot = path.join(root, '.runtime', 'portal');
|
||||
@@ -18,6 +20,17 @@ const skipNodeModules = process.argv.includes('--skip-node-modules');
|
||||
const runtimeNodeTarget = process.env.PORTAL_RUNTIME_NODE_TARGET || 'node24';
|
||||
const runtimeInstallMode = process.env.PORTAL_RUNTIME_INSTALL_MODE || 'bundle-node-modules';
|
||||
const linuxArm64ResvgPackage = '@resvg/resvg-js-linux-arm64-gnu';
|
||||
const linuxArm64Argon2Package = '@node-rs/argon2-linux-arm64-gnu';
|
||||
const linuxArm64NativePackages = [
|
||||
{
|
||||
hostPackage: '@resvg/resvg-js',
|
||||
targetPackage: linuxArm64ResvgPackage,
|
||||
},
|
||||
{
|
||||
hostPackage: '@node-rs/argon2',
|
||||
targetPackage: linuxArm64Argon2Package,
|
||||
},
|
||||
];
|
||||
|
||||
const externalPackages = [
|
||||
'@img/sharp-darwin-arm64',
|
||||
@@ -217,6 +230,8 @@ async function copyRuntimeAssets() {
|
||||
await copyDir(publicDir, path.join(runtimeRoot, 'public'));
|
||||
}
|
||||
|
||||
await removeForbiddenPortalRuntimePaths(runtimeRoot);
|
||||
|
||||
if (await exists(schemaFile)) {
|
||||
await fs.copyFile(schemaFile, path.join(runtimeRoot, 'schema.sql'));
|
||||
}
|
||||
@@ -237,41 +252,51 @@ async function copyNodeModules() {
|
||||
console.log('==> 拷贝生产运行依赖 node_modules');
|
||||
await copyDir(resolvedNodeModulesDir, path.join(runtimeRoot, 'node_modules'));
|
||||
await rewriteNodeModulesSymlinks(path.join(runtimeRoot, 'node_modules'), resolvedNodeModulesDir);
|
||||
await installLinuxArm64ResvgBinary();
|
||||
await installLinuxArm64NativePackages();
|
||||
}
|
||||
|
||||
async function readLinuxArm64NativePackageVersion({ hostPackage, targetPackage }) {
|
||||
const hostPackageJson = JSON.parse(
|
||||
await fs.readFile(path.join(nodeModulesDir, hostPackage, 'package.json'), 'utf8'),
|
||||
);
|
||||
const version = hostPackageJson.optionalDependencies?.[targetPackage];
|
||||
if (!version) {
|
||||
throw new Error(`无法从 ${hostPackage} 确定 ${targetPackage} 的版本`);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
// The runtime artifact is assembled on macOS but runs in a Linux ARM64 Colima VM.
|
||||
// pnpm therefore copies the macOS optional resvg binary only. Keep the target
|
||||
// native package alongside the copied node_modules so the dynamic require in
|
||||
// @resvg/resvg-js resolves in production.
|
||||
async function installLinuxArm64ResvgBinary() {
|
||||
const resvgPackageJson = JSON.parse(
|
||||
await fs.readFile(path.join(nodeModulesDir, '@resvg', 'resvg-js', 'package.json'), 'utf8'),
|
||||
);
|
||||
const version = resvgPackageJson.optionalDependencies?.[linuxArm64ResvgPackage];
|
||||
if (!version) {
|
||||
throw new Error(`无法确定 ${linuxArm64ResvgPackage} 的版本`);
|
||||
}
|
||||
|
||||
const stagingDir = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-resvg-linux-arm64-'));
|
||||
const targetDir = path.join(runtimeRoot, 'node_modules', '@resvg', 'resvg-js-linux-arm64-gnu');
|
||||
// pnpm copies the host optional binaries only, so explicitly materialize every
|
||||
// Linux ARM64 native package that an externalized runtime dependency loads.
|
||||
async function installLinuxArm64NativePackage(nativePackage) {
|
||||
const { targetPackage } = nativePackage;
|
||||
const version = await readLinuxArm64NativePackageVersion(nativePackage);
|
||||
const stagingDir = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-native-linux-arm64-'));
|
||||
const targetDir = path.join(runtimeRoot, 'node_modules', ...targetPackage.split('/'));
|
||||
try {
|
||||
console.log(`==> 补齐 Linux ARM64 resvg 原生依赖 (${version})`);
|
||||
await run('npm', ['pack', `${linuxArm64ResvgPackage}@${version}`, '--pack-destination', stagingDir]);
|
||||
console.log(`==> 补齐 Linux ARM64 原生依赖 ${targetPackage} (${version})`);
|
||||
await run('npm', ['pack', `${targetPackage}@${version}`, '--pack-destination', stagingDir]);
|
||||
const archive = (await fs.readdir(stagingDir)).find((name) => name.endsWith('.tgz'));
|
||||
if (!archive) throw new Error(`未生成 ${linuxArm64ResvgPackage} 安装包`);
|
||||
if (!archive) throw new Error(`未生成 ${targetPackage} 安装包`);
|
||||
await remove(targetDir);
|
||||
await fs.mkdir(targetDir, { recursive: true });
|
||||
await run('tar', ['-xzf', path.join(stagingDir, archive), '--strip-components=1', '-C', targetDir]);
|
||||
const files = await fs.readdir(targetDir);
|
||||
if (!files.some((name) => name.endsWith('.node'))) {
|
||||
throw new Error(`${linuxArm64ResvgPackage} 缺少原生 .node 文件`);
|
||||
throw new Error(`${targetPackage} 缺少原生 .node 文件`);
|
||||
}
|
||||
} finally {
|
||||
await remove(stagingDir);
|
||||
}
|
||||
}
|
||||
|
||||
async function installLinuxArm64NativePackages() {
|
||||
for (const nativePackage of linuxArm64NativePackages) {
|
||||
await installLinuxArm64NativePackage(nativePackage);
|
||||
}
|
||||
}
|
||||
|
||||
async function rewriteNodeModulesSymlinks(runtimeNodeModulesDir, sourceNodeModulesDir) {
|
||||
console.log('==> 重写 node_modules 顶层符号链接为 runtime 内相对路径');
|
||||
const localNodeModulesRoot = `${sourceNodeModulesDir}${path.sep}`;
|
||||
@@ -325,6 +350,14 @@ async function writeMetadata() {
|
||||
.filter((pkg) => packageJson.dependencies?.[pkg])
|
||||
.map((pkg) => [pkg, packageJson.dependencies[pkg]]),
|
||||
);
|
||||
const runtimeOptionalDependencies = Object.fromEntries(
|
||||
await Promise.all(
|
||||
linuxArm64NativePackages.map(async (nativePackage) => [
|
||||
nativePackage.targetPackage,
|
||||
await readLinuxArm64NativePackageVersion(nativePackage),
|
||||
]),
|
||||
),
|
||||
);
|
||||
const runtimePackageJson = {
|
||||
name: `${packageJson.name}-portal-runtime`,
|
||||
private: true,
|
||||
@@ -333,6 +366,7 @@ async function writeMetadata() {
|
||||
node: '>=22',
|
||||
},
|
||||
dependencies: runtimeDependencies,
|
||||
optionalDependencies: runtimeOptionalDependencies,
|
||||
};
|
||||
await writeFile(path.join(runtimeRoot, 'package.json'), `${JSON.stringify(runtimePackageJson, null, 2)}\n`);
|
||||
const tunnelScript = path.join(root, 'scripts', 'memind-portal-tunnel.sh');
|
||||
|
||||
Reference in New Issue
Block a user