Files
Michał Pierzchała 0dfd65f6a2 perf(ios): speed up deep snapshots and keep first taps reliable (#2414)
* 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
2026-09-09 18:24:15 +02:00

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 };
});
}