mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
0dfd65f6a2
* perf(ios): recover deep snapshots and isolate optional tap probes * chore(gates): enforce snapshot assets and optional probe lifecycle * fix(ios): preserve capture bounds and local probe recovery * chore(gates): validate base package assets with its own policy * chore(gates): verify recovery failures respect launch observation policy * fix(ios): fail closed on unknown snapshot frontier completeness
102 lines
3.4 KiB
JavaScript
102 lines
3.4 KiB
JavaScript
import { execFileSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
export const SNAPSHOT_BRIDGE_ASSET_PATHS = Object.freeze([
|
|
'apple/snapshot-bridge/SnapshotBridge.m',
|
|
'apple/snapshot-bridge/SnapshotBridgeRuntime.m',
|
|
'apple/snapshot-bridge/SnapshotBridgeRuntime.h',
|
|
'apple/snapshot-bridge/SnapshotBridgeCapture.h',
|
|
'apple/snapshot-bridge/SnapshotBridgeCapture.m',
|
|
]);
|
|
|
|
export function assertSnapshotBridgeAssets(presentPaths, context) {
|
|
const present = new Set(presentPaths);
|
|
const missing = SNAPSHOT_BRIDGE_ASSET_PATHS.filter((assetPath) => !present.has(assetPath));
|
|
if (missing.length > 0) {
|
|
throw new Error(`${context} is missing: ${missing.join(', ')}`);
|
|
}
|
|
}
|
|
|
|
export function collectNpmPack(root) {
|
|
const cachePath = path.join(root, '.tmp', 'npm-cache');
|
|
fs.mkdirSync(cachePath, { recursive: true });
|
|
const stdout = execFileSync(
|
|
'npm',
|
|
['pack', '--ignore-scripts', '--json', '--pack-destination', cachePath, '--cache', cachePath],
|
|
{ cwd: root, encoding: 'utf8' },
|
|
);
|
|
const pack = parseNpmPackOutput(stdout);
|
|
const entries = normalizeNpmPackEntries(pack);
|
|
assertPublishPackageContents(entries, {
|
|
requireSnapshotBridge: fs.existsSync(path.join(root, 'apple', 'snapshot-bridge')),
|
|
});
|
|
return {
|
|
filename: pack.filename,
|
|
tarballPath: path.join(cachePath, pack.filename),
|
|
tarballBytes: pack.size,
|
|
unpackedBytes: pack.unpackedSize,
|
|
files: entries.length,
|
|
entries,
|
|
};
|
|
}
|
|
|
|
export function assertPublishPackageContents(entries, options = {}) {
|
|
const paths = entries.map((entry) => entry.path);
|
|
const requiredAssets = [
|
|
{ directory: 'android/snapshot-helper/dist/', suffix: '.apk' },
|
|
{ directory: 'android/snapshot-helper/dist/', suffix: '.manifest.json' },
|
|
{ directory: 'android/ime-helper/dist/', suffix: '.apk' },
|
|
{ directory: 'android/ime-helper/dist/', suffix: '.manifest.json' },
|
|
];
|
|
if (
|
|
options.requireSnapshotBridge ??
|
|
paths.some((entryPath) => entryPath.startsWith('apple/snapshot-bridge/'))
|
|
) {
|
|
assertSnapshotBridgeAssets(
|
|
paths.filter((entryPath) => SNAPSHOT_BRIDGE_ASSET_PATHS.includes(entryPath)),
|
|
'npm pack snapshot bridge',
|
|
);
|
|
}
|
|
const missingAssets = requiredAssets.filter(
|
|
(asset) =>
|
|
!paths.some(
|
|
(entryPath) => entryPath.startsWith(asset.directory) && entryPath.endsWith(asset.suffix),
|
|
),
|
|
);
|
|
if (missingAssets.length > 0) {
|
|
throw new Error(
|
|
`npm pack is missing publish assets: ${missingAssets
|
|
.map((asset) => `${asset.directory}*${asset.suffix}`)
|
|
.join(', ')}`,
|
|
);
|
|
}
|
|
const forbiddenPaths = paths.filter(
|
|
(entryPath) => entryPath === 'scripts' || entryPath.startsWith('scripts/'),
|
|
);
|
|
if (forbiddenPaths.length > 0) {
|
|
throw new Error(`npm pack includes benchmark or build scripts: ${forbiddenPaths.join(', ')}`);
|
|
}
|
|
}
|
|
|
|
function parseNpmPackOutput(stdout) {
|
|
const parsed = JSON.parse(stdout);
|
|
return Array.isArray(parsed) ? parsed[0] : parsed;
|
|
}
|
|
|
|
function normalizeNpmPackEntries(pack) {
|
|
if (!Array.isArray(pack.files)) {
|
|
throw new Error('npm pack did not return per-file path/size data in files[]');
|
|
}
|
|
return pack.files.map((entry) => {
|
|
if (
|
|
!entry ||
|
|
typeof entry.path !== 'string' ||
|
|
!Number.isSafeInteger(entry.size) ||
|
|
entry.size < 0
|
|
) {
|
|
throw new Error(`npm pack returned an invalid file entry: ${JSON.stringify(entry)}`);
|
|
}
|
|
return { path: entry.path, size: entry.size };
|
|
});
|
|
}
|