upload --pre-check/--evidence, shots copy translate, game-center --locale-map, play games API-disabled hint
This commit is contained in:
parent
29a9683ab7
commit
8ffa27d4f8
24 changed files with 598 additions and 27 deletions
72
cli/lib/pre-check.ts
Normal file
72
cli/lib/pre-check.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// The pre-upload check of `apple upload` and `play upload`: a shell command the project owns (a store-readiness gate,
|
||||
// a policy scan of the binary) runs before any byte is sent.
|
||||
// --pre-check "<cmd with {file}>" (config: upload.preCheck) {file} is replaced by the quoted IPA / AAB path
|
||||
// --evidence <path.json> (config: upload.evidence) what ran, written also when the check fails
|
||||
// A nonzero exit refuses the upload: ShipStepError PRE_CHECK_FAILED (exit 1 under the ship-step contract), nothing
|
||||
// is sent. The evidence ({file, sha256, size, command, exitCode, stdoutTail, timestamp, storeboundVersion}) goes to
|
||||
// --evidence and into the --json / --result-file result as `preCheck`.
|
||||
import { createHash } from 'node:crypto';
|
||||
import { mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
||||
import { basename, dirname } from 'node:path';
|
||||
import type { Args } from './args';
|
||||
import type { ShipConfig } from './config';
|
||||
import { log } from './log';
|
||||
import { pump } from './runner';
|
||||
import * as rt from './rt';
|
||||
import { SHIP_EXIT, ShipStepError, shipExtra, shipPhase } from './ship-step';
|
||||
|
||||
export interface PreCheckEvidence {
|
||||
file: string; sha256: string; size: number; command: string; exitCode: number;
|
||||
stdoutTail: string[]; timestamp: string; storeboundVersion: string;
|
||||
}
|
||||
|
||||
/** The command with {file} replaced by the POSIX-quoted path (sh -c); a command without {file} gets none. */
|
||||
export function preCheckCommand(template: string, file: string): string {
|
||||
const q = `'${file.replace(/\\/g, '/').replace(/'/g, `'\\''`)}'`;
|
||||
return template.split('{file}').join(q);
|
||||
}
|
||||
|
||||
function sha256File(path: string): string { return createHash('sha256').update(readFileSync(path)).digest('hex'); }
|
||||
|
||||
/**
|
||||
* Runs the pre-check for `file` when --pre-check / upload.preCheck is set (else returns null). Throws PRE_CHECK_FAILED
|
||||
* on a nonzero exit, after writing the evidence. `shown` is the file name for the evidence (not the absolute path).
|
||||
*/
|
||||
export async function runPreCheck(o: { args: Args; config: ShipConfig; root: string; resolve: (p: string) => string; file: string; shown: string; tag: string }): Promise<PreCheckEvidence | null> {
|
||||
const flag = o.args.get('pre-check');
|
||||
if (flag === 'true') throw new Error('--pre-check "<command with {file}>" needs a command');
|
||||
const template = (flag ?? o.config.upload?.preCheck)?.trim();
|
||||
const evFlag = o.args.get('evidence');
|
||||
if (evFlag === 'true') throw new Error('--evidence <path.json> needs a path');
|
||||
const evPath = evFlag ?? o.config.upload?.evidence;
|
||||
if (!template) {
|
||||
if (evFlag) throw new Error('--evidence needs --pre-check (or upload.preCheck in the config)');
|
||||
return null;
|
||||
}
|
||||
const command = preCheckCommand(template, o.file);
|
||||
shipPhase('pre-check', command);
|
||||
log(o.tag, `pre-check: $ ${command}`);
|
||||
const p = rt.spawn(['sh', '-c', command], { cwd: o.root, stdin: 'ignore', stdout: 'pipe', stderr: 'pipe', env: process.env });
|
||||
const tail: string[] = [];
|
||||
const each = (line: string, kind: 'stdout' | 'stderr'): void => {
|
||||
if (kind === 'stdout') { tail.push(line); if (tail.length > 20) tail.shift(); }
|
||||
console.error(` ${line}`);
|
||||
};
|
||||
await Promise.all([pump(p.stdout, 'stdout', each), pump(p.stderr, 'stderr', each)]);
|
||||
const exitCode = Number(await p.exited);
|
||||
const version = ((await import('../../package.json')) as { version: string }).version;
|
||||
const ev: PreCheckEvidence = {
|
||||
file: o.shown.split('\\').join('/'), sha256: sha256File(o.file), size: statSync(o.file).size, command, exitCode,
|
||||
stdoutTail: tail, timestamp: new Date().toISOString(), storeboundVersion: version,
|
||||
};
|
||||
if (evPath) {
|
||||
const abs = o.resolve(evPath);
|
||||
mkdirSync(dirname(abs), { recursive: true });
|
||||
writeFileSync(abs, JSON.stringify(ev, null, 2) + '\n');
|
||||
log(o.tag, `pre-check evidence: ${basename(abs)}`);
|
||||
}
|
||||
shipExtra({ preCheck: ev });
|
||||
if (exitCode !== 0) throw new ShipStepError(`pre-check failed (exit ${exitCode}), nothing was uploaded: ${command}${tail.length ? `\n${tail.slice(-5).join('\n')}` : ''}`, SHIP_EXIT.failure, 'PRE_CHECK_FAILED');
|
||||
log(o.tag, 'pre-check: ok');
|
||||
return ev;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue