Files
Genarrative/apps/mobile-shell/scripts/check-build-artifacts.mjs
T
kdletters 387a1c26e3 完成全量检查整改
清理已跟踪原始日志与个人本地配置
修复前后端有效门禁、测试和构建问题
补齐生产 Jenkins、SpacetimeDB 本地命令与文档约束
收紧图片编辑器状态、附件与媒体引用测试
排除已下线旧创作入口测试并清理 warning
2026-07-17 16:56:46 +08:00

106 lines
3.1 KiB
JavaScript

import {spawnSync} from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
const shellRoot = new URL('../', import.meta.url);
const repoRoot = path.resolve(shellRoot.pathname, '../..');
const androidArtifactPath = path.join(
repoRoot,
'build',
'native',
'mobile',
'genarrative-mobile-android.apk',
);
const iosSimulatorArtifactPath = path.join(
repoRoot,
'build',
'native',
'mobile',
'genarrative-mobile-ios-simulator.tar.gz',
);
function assertFile(filePath, label, minimumBytes) {
if (!fs.existsSync(filePath)) {
throw new Error(`${label} is missing: ${filePath}`);
}
const stat = fs.statSync(filePath);
if (!stat.isFile() || stat.size < minimumBytes) {
throw new Error(`${label} must be a real build artifact`);
}
}
function readHeader(filePath, length) {
const handle = fs.openSync(filePath, 'r');
try {
const header = Buffer.alloc(length);
fs.readSync(handle, header, 0, header.length, 0);
return header;
} finally {
fs.closeSync(handle);
}
}
function assertZipHeader(filePath, label) {
const header = readHeader(filePath, 4);
if (
header[0] !== 0x50 ||
header[1] !== 0x4b ||
header[2] !== 0x03 ||
header[3] !== 0x04
) {
throw new Error(`${label} must be a ZIP based artifact`);
}
}
function assertGzipHeader(filePath, label) {
const header = readHeader(filePath, 2);
if (header[0] !== 0x1f || header[1] !== 0x8b) {
throw new Error(`${label} must be a gzip compressed tar artifact`);
}
}
function run(command, args, label) {
const result = spawnSync(command, args, {
cwd: repoRoot,
encoding: 'utf8',
});
if (result.error) {
throw new Error(`${label} failed to start: ${result.error.message}`);
}
if ((result.status ?? 0) !== 0) {
throw new Error(`${label} failed: ${(result.stderr || result.stdout).trim()}`);
}
return result.stdout;
}
function assertIncludes(source, snippet, label) {
if (!source.includes(snippet)) {
throw new Error(`${label} missing ${snippet}`);
}
}
assertFile(androidArtifactPath, 'Android mobile shell APK', 1024 * 1024);
assertZipHeader(androidArtifactPath, 'Android mobile shell APK');
const androidListing = run('unzip', ['-l', androidArtifactPath], 'Android APK listing');
assertIncludes(androidListing, 'AndroidManifest.xml', 'Android mobile shell APK');
assertIncludes(androidListing, 'classes.dex', 'Android mobile shell APK');
assertIncludes(androidListing, 'assets/index.android.bundle', 'Android mobile shell APK');
assertFile(iosSimulatorArtifactPath, 'iOS simulator mobile shell archive', 1024 * 1024);
assertGzipHeader(iosSimulatorArtifactPath, 'iOS simulator mobile shell archive');
const iosListing = run(
'tar',
['-tzf', iosSimulatorArtifactPath],
'iOS simulator archive listing',
);
assertIncludes(iosListing, '.app/Info.plist', 'iOS simulator mobile shell archive');
assertIncludes(iosListing, '.app/Genarrative', 'iOS simulator mobile shell archive');
assertIncludes(
iosListing,
'.app/main.jsbundle',
'iOS simulator mobile shell archive',
);
console.log('[mobile-shell:build-artifacts] OK');