build.prebuild and a Capacitor web-asset staleness guard for apple build / play build

apple build and play build run build.prebuild (or apple.prebuild / play.prebuild) first, once per flow run;
--no-prebuild skips it, --dry only prints it. A Capacitor project's copied public/index.html must then be newer
than the last source commit and <webDir>/index.html, else the build stops (--allow-stale-web overrides). check
warns when stale without a prebuild; the build output and dry plan show the web bundle's sha256 and mtime.
The Capacitor project is also found in a monorepo (apps/native etc.); several ask for play.build.dir /
apple.build.*. Pitfall 23.
This commit is contained in:
CRTkafa 2026-10-12 08:44:05 +08:00
parent f694af80eb
commit 5fbb9e63e3
14 changed files with 600 additions and 25 deletions

View file

@ -534,6 +534,8 @@ export interface ShipConfig {
reportsBucket?: string;
/** `storebound play build`: Gradle project folder, module, flavor (found automatically when unset). */
build?: PlayBuildConfig;
/** Shell command `play build` runs first in the project root; overrides build.prebuild for Play. */
prebuild?: string;
/** Play Console > App content > Target audience and content: the age groups you select. Any group under 13 puts
* the app under the Families policy (`storebound scan` checks ad SDKs, the advertising ID and location for it). */
targetAges?: PlayAgeGroup[];
@ -579,6 +581,8 @@ export interface ShipConfig {
signing?: AppleSigning;
/** `storebound apple build`: workspace / project, scheme, configuration, output folder (found automatically when unset). */
build?: AppleBuildConfig;
/** Shell command `apple build` runs first in the project root; overrides build.prebuild for the App Store. */
prebuild?: string;
/** Promoted in-app purchases on the product page, in order (`storebound apple promoted`; at most 20). */
promoted?: PromotedConfig;
/** Custom product pages (`storebound apple cpp`; at most 70). */
@ -620,6 +624,14 @@ export interface ShipConfig {
/** Owner answers for console forms that have no API (`storebound console fill`); only what the rest of the config
* cannot prove. Anything left out stays empty and highlighted in the page. */
console?: ConsoleConfig;
/** Native builds (`apple build`, `play build`). */
build?: {
/** Shell command run in the project root before the archive / bundle, e.g. "npm run build && npx cap sync" for a
* Capacitor app (the native build ships the web assets cap sync copied last). Printed, output streamed, a nonzero
* exit stops the build; `--no-prebuild` skips it; inside one `flow run` it runs once, not per store.
* apple.prebuild / play.prebuild override it per store. */
prebuild?: string;
};
}
// ---- growth extras ----
@ -700,7 +712,12 @@ const SHA256_RE = /^([0-9A-F]{2}:){31}[0-9A-F]{2}$/;
/** Problems of the growth-extras sections (web, revenuecat, prices.ppp, apple.teamId / adhoc). Pure. */
export function validateExtras(cfg: Partial<ShipConfig>): string[] {
const e: string[] = [];
if (cfg.apple?.teamId !== undefined && !/^[A-Z0-9]{10}$/.test(cfg.apple.teamId)) e.push('apple.teamId: the 10-character Team ID (Membership details)');
const badCmd = (v: unknown) => v !== undefined && (typeof v !== 'string' || !v.trim());
if (cfg.build !== undefined && (typeof cfg.build !== 'object' || cfg.build === null)) e.push('build: { prebuild?: "<shell command>" }');
if (badCmd(cfg.build?.prebuild)) e.push('build.prebuild: a shell command run in the project root before apple build / play build, e.g. "npm run build && npx cap sync"');
if (badCmd(cfg.apple?.prebuild)) e.push('apple.prebuild: a shell command (overrides build.prebuild for apple build)');
if (badCmd(cfg.play?.prebuild)) e.push('play.prebuild: a shell command (overrides build.prebuild for play build)');
if (cfg.apple?.teamId !== undefined &&!/^[A-Z0-9]{10}$/.test(cfg.apple.teamId)) e.push('apple.teamId: the 10-character Team ID (Membership details)');
if (cfg.play?.developerId !== undefined && !/^\d{1,25}$/.test(cfg.play.developerId)) e.push('play.developerId: the digits of play.google.com/console/u/0/developers/<id>');
if (cfg.play?.appId !== undefined && !/^\d{1,25}$/.test(cfg.play.appId)) e.push('play.appId: the digits of play.google.com/console/u/0/developers/<developerId>/app/<appId>/app-dashboard');
if (cfg.play?.developerAccount !== undefined && !(typeof cfg.play.developerAccount === 'string' && cfg.play.developerAccount.trim() && cfg.play.developerAccount.length <= 100))