apple.macos listing / review notes per platform + iOS-only claim warnings; apple upload of a .pkg: bounded pkgutil, macOS processing wait, heartbeats; release 409 pattern
This commit is contained in:
parent
53d57544bf
commit
1e47c2ee60
18 changed files with 417 additions and 63 deletions
|
|
@ -271,29 +271,51 @@ export function readAppInfo(appDir: string): Record<string, PlistValue> {
|
|||
return parsePlist(readFileSync(p)) as Record<string, PlistValue>;
|
||||
}
|
||||
|
||||
/** What bounds and reports the .pkg reading (tests inject a fake spawnSync and short limits). */
|
||||
export interface PkgInfoOptions { say?: (msg: string) => void; spawnSync?: typeof rt.spawnSync; checkMs?: number; expandMs?: number }
|
||||
/** pkgutil --check-signature evaluates the certificate chain (revocation lookups over the network): never unbounded. */
|
||||
export const PKG_CHECK_MS = 90_000;
|
||||
/** pkgutil --expand copies the whole archive to a temp folder: minutes for a big app, never unbounded. */
|
||||
export const PKG_EXPAND_MS = 5 * 60_000;
|
||||
|
||||
/** bundle id, version and build of a .pkg: pkgutil --expand on a Mac (PackageInfo), else the <pkg>.json next to it that
|
||||
* `apple build --platform macos` wrote. Refuses an unsigned or wrongly signed .pkg when pkgutil is there. */
|
||||
export function pkgInfo(pkg: string, expectBundleId?: string): { bundleId: string; version: string; build: string } {
|
||||
if (rt.which('pkgutil')) {
|
||||
const sig = parsePkgSignature(rt.spawnSync(['pkgutil', '--check-signature', pkg], { stdout: 'pipe', stderr: 'pipe' }).stdout.toString());
|
||||
const bad = pkgProblems(sig);
|
||||
if (bad.length) throw new Error(`${basename(pkg)}: not uploaded:\n${bad.map(x => ` ${x}`).join('\n')}`);
|
||||
const tmp = join(process.env.TMPDIR ?? '/tmp', `sb-pkg-${process.pid}-${Date.now()}`);
|
||||
try {
|
||||
if (rt.spawnSync(['pkgutil', '--expand', pkg, tmp], { stdout: 'pipe', stderr: 'pipe' }).exitCode === 0) {
|
||||
for (const d of readdirSync(tmp)) {
|
||||
const p = join(tmp, d, 'PackageInfo');
|
||||
const info = existsSync(p) ? parsePackageInfo(readFileSync(p, 'utf8')) : null;
|
||||
if (info && (!expectBundleId || info.bundleId === expectBundleId)) return info;
|
||||
}
|
||||
}
|
||||
} finally { try { rmSync(tmp, { recursive: true, force: true }); } catch { /* temp */ } }
|
||||
}
|
||||
* `apple build --platform macos` wrote. Refuses an unsigned or wrongly signed .pkg when pkgutil is there. Both pkgutil
|
||||
* calls have a timeout (no stdin, so no prompt) and say what they do first; one that times out falls back to the
|
||||
* .json file, else it fails with the reason (a silent hang over SSH, 10 min, was this read). */
|
||||
export function pkgInfo(pkg: string, expectBundleId?: string, o: PkgInfoOptions = {}): { bundleId: string; version: string; build: string } {
|
||||
const say = o.say ?? (() => {});
|
||||
const spawnSync = o.spawnSync ?? rt.spawnSync;
|
||||
const side = `${pkg}.json`;
|
||||
let timedOut = '';
|
||||
if (rt.which('pkgutil') || o.spawnSync) {
|
||||
const checkMs = o.checkMs ?? PKG_CHECK_MS, expandMs = o.expandMs ?? PKG_EXPAND_MS;
|
||||
say(`checking the signature of ${basename(pkg)} (pkgutil --check-signature, up to ${Math.round(checkMs / 1000)} s)`);
|
||||
const chk = spawnSync(['pkgutil', '--check-signature', pkg], { stdout: 'pipe', stderr: 'pipe', timeout: checkMs });
|
||||
if (chk.exitCode === null && chk.signalCode) timedOut = `pkgutil --check-signature gave no answer in ${Math.round(checkMs / 1000)} s (it checks the certificate chain over the network)`;
|
||||
else {
|
||||
const bad = pkgProblems(parsePkgSignature(chk.stdout.toString()));
|
||||
if (bad.length) throw new Error(`${basename(pkg)}: not uploaded:\n${bad.map(x => ` ${x}`).join('\n')}`);
|
||||
const tmp = join(process.env.TMPDIR ?? '/tmp', `sb-pkg-${process.pid}-${Date.now()}`);
|
||||
say(`reading bundle id, version and build (pkgutil --expand, up to ${Math.round(expandMs / 1000)} s; a big package takes a while)`);
|
||||
try {
|
||||
const ex = spawnSync(['pkgutil', '--expand', pkg, tmp], { stdout: 'pipe', stderr: 'pipe', timeout: expandMs });
|
||||
if (ex.exitCode === null && ex.signalCode) timedOut = `pkgutil --expand gave no answer in ${Math.round(expandMs / 1000)} s`;
|
||||
else if (ex.exitCode === 0) {
|
||||
for (const d of readdirSync(tmp)) {
|
||||
const p = join(tmp, d, 'PackageInfo');
|
||||
const info = existsSync(p) ? parsePackageInfo(readFileSync(p, 'utf8')) : null;
|
||||
if (info && (!expectBundleId || info.bundleId === expectBundleId)) return info;
|
||||
}
|
||||
}
|
||||
} finally { try { rmSync(tmp, { recursive: true, force: true }); } catch { /* temp */ } }
|
||||
}
|
||||
}
|
||||
if (timedOut) say(`${timedOut}: using ${basename(side)} (written by apple build) instead`);
|
||||
if (existsSync(side)) {
|
||||
const j = JSON.parse(readFileSync(side, 'utf8')) as { bundleId?: string; version?: string; build?: string; unsigned?: boolean };
|
||||
if (j.unsigned) throw new Error(`${basename(pkg)} was built with --unsigned: the Mac App Store refuses it`);
|
||||
if (j.bundleId && j.version && j.build) return { bundleId: j.bundleId, version: j.version, build: j.build };
|
||||
}
|
||||
if (timedOut) throw new Error(`${basename(pkg)}: ${timedOut}, and there is no ${basename(side)} file from apple build to read instead: run it in the Mac's logged-in GUI session, or check the Mac's network`);
|
||||
throw new Error(`${basename(pkg)}: cannot read bundle id / version / build (needs pkgutil on a Mac, or the ${basename(side)} file apple build wrote)`);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue