apple build exports each build into build/ios/export/<version>-<build>/ (cleaned first), runs xcodebuild with Apple's tool folders first on PATH (Homebrew rsync 3.x broke Xcode's IPA step: exportArchive Copy failed), shows the IDEDistribution log's real error lines and exits non-zero on any export failure; apple upload fails with exit 1 when the IPA's build number is already on App Store Connect (stale IPA after a failed build), --allow-existing re-checks, --build N picks that build's IPA, release and beta flows use both; doctor flags a non-Apple rsync first on PATH; desktop publish calls rsync by absolute path without --chmod (publishSteps); site download notice per OS (macOS signed and notarized, Windows unsigned for now, Linux unsigned); game-report credential tests hermetic against a machine default profile (pitfalls 53, 54)

This commit is contained in:
CRTkafa 2026-10-13 09:45:48 +08:00
parent 8fd4ac741e
commit 0e4f579899
20 changed files with 338 additions and 48 deletions

View file

@ -648,6 +648,22 @@ export function releaseTogetherProblems(folderVersion: string, pkgVersion: strin
return folderVersion === pkgVersion ? [] : [`the folder is desktop ${folderVersion} but package.json (the npm CLI) is ${pkgVersion}: the CLI and the app ship with one version (bun tools/desktop-version.ts --set ${folderVersion}, or build the folder from the release commit)`];
}
/**
* The publish commands. rsync by absolute path, never from PATH: Apple's /usr/bin/rsync (openrsync) with only the
* options it supports (no --chmod; the modes are set on the server after the upload), else Homebrew's keg. A tool
* never needs a global rsync linked or installed (pitfall 54: a linked Homebrew rsync broke Xcode's exportArchive).
*/
export function publishSteps(o: { host: string; v: string; channel: string; out: string; files: string[] }, exists: (p: string) => boolean = existsSync): string[][] {
const rsync = ['/usr/bin/rsync', '/opt/homebrew/opt/rsync/bin/rsync', '/usr/local/opt/rsync/bin/rsync'].find(p => exists(p));
if (!rsync) throw new Error('publish needs rsync: /usr/bin/rsync (macOS, most Linux) or the Homebrew keg (brew install rsync; no link needed)');
const dir = `${REMOTE_INCOMING}/${o.v}`;
return [
['ssh', o.host, `mkdir -p ${dir} && chmod 700 ${REMOTE_INCOMING}`],
[rsync, '-a', '--delete', ...o.files.map(f => join(o.out, f)), `${o.host}:${dir}/`],
['ssh', o.host, `find ${dir} -type d -exec chmod 755 {} + && find ${dir} -type f -exec chmod 644 {} + && ${REMOTE_PUBLISH} ${o.v} ${o.channel}`],
];
}
function publish(argv: string[]): void {
const a = args(argv);
const out = resolve(a.get('out') ?? fail('--out <version folder> is required'));
@ -660,11 +676,7 @@ function publish(argv: string[]): void {
const together = releaseTogetherProblems(v, versions().version);
if (together.length) fail(together.join('; '));
const unsigned = Object.values(manifest.downloads ?? {}).filter(d => !d!.codeSigned && /\.(dmg|exe)$/.test(d!.file)).map(d => d!.file);
const steps = [
['ssh', host, `mkdir -p ${REMOTE_INCOMING}/${v} && chmod 700 ${REMOTE_INCOMING}`],
['rsync', '-a', '--chmod=F644,D755', '--delete', ...files.map(f => join(out, f)), `${host}:${REMOTE_INCOMING}/${v}/`],
['ssh', host, `${REMOTE_PUBLISH} ${v} ${channel}`],
];
const steps = publishSteps({ host, v, channel, out, files });
console.log(`\npublish storebound desktop ${v} to the ${channel} channel (${channel === 'stable' ? `${BASE_URL}/latest.json` : `${BASE_URL}/beta/latest.json`}):`);
for (const f of files) console.log(` ${f}`);
if (unsigned.length) console.log(` note: unsigned preview builds (${unsigned.join(', ')}); the downloads page says so.`);