Formal releases with iOS runtime changes now bump MARKETING_VERSION, archive the tag via Xcode Cloud, wait until App Store Connect marks the build Valid, and submit App Review. GitHub publication stays independent of iOS outcome.
152 lines
4.3 KiB
JavaScript
152 lines
4.3 KiB
JavaScript
import { spawnSync } from "node:child_process";
|
|
import { resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const DEFAULT_REPOSITORY = "tianma-if/edgeever";
|
|
const PLATFORMS = new Set(["android", "ios", "both"]);
|
|
const ANDROID_TRACKS = new Set(["internal", "alpha", "beta", "production"]);
|
|
|
|
const usage = `Usage:
|
|
bun run publish:stores -- --release vX.Y.Z [options]
|
|
|
|
Options:
|
|
--release <tag> Required formal GitHub Release tag
|
|
--platform <target> android, ios, or both (default: both)
|
|
--android-track <track> production, alpha, beta, or internal (default: production)
|
|
--recover-play-apk Skip Play upload and recover its already signed APK
|
|
--ios-build-number <number> Submit an existing App Store Connect build; omit to start Xcode Cloud
|
|
--repository <owner/name> GitHub repository (default: ${DEFAULT_REPOSITORY})
|
|
--dry-run Print the workflow dispatch plan
|
|
--help Show this help
|
|
`;
|
|
|
|
export const parseStoreDeliveryArgs = (argv) => {
|
|
const options = {
|
|
releaseTag: "",
|
|
platform: "both",
|
|
androidTrack: "production",
|
|
repository: DEFAULT_REPOSITORY,
|
|
dryRun: false,
|
|
recoverPlayApk: false,
|
|
iosBuildNumber: "",
|
|
help: false,
|
|
};
|
|
const valueOptions = new Map([
|
|
["--release", "releaseTag"],
|
|
["--platform", "platform"],
|
|
["--android-track", "androidTrack"],
|
|
["--ios-build-number", "iosBuildNumber"],
|
|
["--repository", "repository"],
|
|
]);
|
|
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const argument = argv[index];
|
|
if (argument === "--dry-run") {
|
|
options.dryRun = true;
|
|
continue;
|
|
}
|
|
if (argument === "--recover-play-apk") {
|
|
options.recoverPlayApk = true;
|
|
continue;
|
|
}
|
|
if (argument === "--help") {
|
|
options.help = true;
|
|
continue;
|
|
}
|
|
const key = valueOptions.get(argument);
|
|
if (!key) {
|
|
throw new Error(`Unknown option: ${argument}`);
|
|
}
|
|
const value = argv[index + 1]?.trim();
|
|
if (!value || value.startsWith("--")) {
|
|
throw new Error(`${argument} requires a value.`);
|
|
}
|
|
options[key] = value;
|
|
index += 1;
|
|
}
|
|
|
|
if (options.help) {
|
|
return options;
|
|
}
|
|
if (!/^v\d+\.\d+\.\d+$/.test(options.releaseTag)) {
|
|
throw new Error("--release must use stable vX.Y.Z format.");
|
|
}
|
|
if (!PLATFORMS.has(options.platform)) {
|
|
throw new Error("--platform must be android, ios, or both.");
|
|
}
|
|
if (!ANDROID_TRACKS.has(options.androidTrack)) {
|
|
throw new Error(
|
|
"--android-track must be internal, alpha, beta, or production.",
|
|
);
|
|
}
|
|
if (!/^[^/\s]+\/[^/\s]+$/.test(options.repository)) {
|
|
throw new Error("--repository must use owner/name format.");
|
|
}
|
|
if (options.iosBuildNumber && !/^[1-9]\d*$/.test(options.iosBuildNumber)) {
|
|
throw new Error("--ios-build-number must be a positive integer.");
|
|
}
|
|
if (options.iosBuildNumber && options.platform === "android") {
|
|
throw new Error("--ios-build-number requires --platform ios or both.");
|
|
}
|
|
return options;
|
|
};
|
|
|
|
const run = (options) => {
|
|
const args = [
|
|
"workflow",
|
|
"run",
|
|
"store-delivery.yml",
|
|
"--repo",
|
|
options.repository,
|
|
"--ref",
|
|
"main",
|
|
"-f",
|
|
`release_tag=${options.releaseTag}`,
|
|
"-f",
|
|
`platform=${options.platform}`,
|
|
"-f",
|
|
`android_track=${options.androidTrack}`,
|
|
"-f",
|
|
`recover_play_apk=${options.recoverPlayApk}`,
|
|
"-f",
|
|
`ios_build_number=${options.iosBuildNumber}`,
|
|
];
|
|
if (options.dryRun) {
|
|
console.log(`gh ${args.join(" ")}`);
|
|
return;
|
|
}
|
|
|
|
const result = spawnSync("gh", args, {
|
|
cwd: resolve("."),
|
|
encoding: "utf8",
|
|
env: process.env,
|
|
stdio: ["ignore", "pipe", "inherit"],
|
|
});
|
|
if (result.status !== 0) {
|
|
throw new Error(
|
|
`GitHub workflow dispatch failed with status ${result.status ?? 1}.`,
|
|
);
|
|
}
|
|
const output = String(result.stdout ?? "").trim();
|
|
console.log(output || "Store delivery workflow dispatched.");
|
|
};
|
|
|
|
if (
|
|
process.argv[1] &&
|
|
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
) {
|
|
try {
|
|
const options = parseStoreDeliveryArgs(process.argv.slice(2));
|
|
if (options.help) {
|
|
console.log(usage);
|
|
} else {
|
|
run(options);
|
|
}
|
|
} catch (error) {
|
|
console.error(
|
|
`[store-delivery] failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
}
|