Files
Genarrative/apps/mobile-shell/scripts/check-build-artifacts.mjs
T
kdletters 071faa482c 统一 Rust 与 TypeScript 格式化门禁
纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口

完成项目 TypeScript/Prettier 与 Rust 全量格式化

修复 Pingora expected executable 门禁的空白敏感误报

同步开发运维文档与 AGC skill pack 格式化忽略规则
2026-09-01 16:28:34 +08:00

135 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');