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:
parent
f694af80eb
commit
5fbb9e63e3
14 changed files with 600 additions and 25 deletions
|
|
@ -1,6 +1,8 @@
|
|||
// Archive and export an App Store IPA with xcodebuild, no Xcode UI.
|
||||
// storebound apple build [--version 1.2.0] [--build-number N] [--scheme S] [--workspace W | --project P]
|
||||
// [--configuration Release] [--out build/ios] [--dry] [--json]
|
||||
// [--configuration Release] [--out build/ios] [--no-prebuild] [--allow-stale-web] [--dry] [--json]
|
||||
// First the prebuild (build.prebuild / apple.prebuild: e.g. the web build + npx cap sync; once per flow run), then a
|
||||
// Capacitor project's copied web assets (ios/App/App/public) must be newer than the source, else it stops.
|
||||
// Finds the workspace / project and scheme (native, Capacitor ios/App, React Native ios/<Name>, Flutter ios/Runner,
|
||||
// Unity Unity-iPhone), takes the next safe build number from App Store Connect (apple next-build) unless
|
||||
// --build-number is given, writes <out>/ExportOptions.plist (method app-store-connect, team, signing style and the
|
||||
|
|
@ -10,7 +12,7 @@
|
|||
// Local only: nothing is sent to App Store Connect except reads (build numbers, team id, profiles).
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { homedir } from 'node:os';
|
||||
import { join, relative } from 'node:path';
|
||||
import { dirname, join, relative } from 'node:path';
|
||||
import { ascKeyFile } from '../lib/credentials';
|
||||
import type { Ctx } from '../lib/ctx';
|
||||
import { need } from '../lib/ctx';
|
||||
|
|
@ -19,6 +21,7 @@ import { pump } from '../lib/runner';
|
|||
import { parseMobileprovision, toXmlPlist } from '../lib/plist';
|
||||
import { nextBuildNumber } from '../lib/release';
|
||||
import { exportOptions, profileDirs } from '../lib/signing';
|
||||
import { capacitorProject, webStep } from '../lib/web-assets';
|
||||
import { archiveArgs, type AscAuth, exportArgs, flutterConfigArgs, ipaInfo, pickXcode, scanXcode, showCmd } from '../lib/xcode';
|
||||
import { ascClient } from './asc';
|
||||
import { findBundleId } from './signing';
|
||||
|
|
@ -69,7 +72,14 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
const style = signing.style ?? 'automatic';
|
||||
const dry = a.has('dry');
|
||||
const json = a.has('json');
|
||||
const target = pickXcode(scanXcode(root), { ...cfg, workspace: a.get('workspace') ?? cfg.workspace, project: a.get('project') ?? cfg.project, scheme: a.get('scheme') ?? cfg.scheme });
|
||||
const workspace = a.get('workspace') ?? cfg.workspace, project = a.get('project') ?? cfg.project;
|
||||
// Capacitor in a monorepo (apps/native/ios): the config's workspace / project wins, else the one Capacitor project found
|
||||
const explicit = workspace ?? project;
|
||||
const cap = capacitorProject(root, explicit ? dirname(explicit) : undefined);
|
||||
const sub = !explicit && cap?.dir ? cap.dir : '';
|
||||
const found = pickXcode(scanXcode(sub ? join(root, sub) : root), { ...cfg, workspace, project, scheme: a.get('scheme') ?? cfg.scheme });
|
||||
const target = sub ? { ...found, ...(found.workspace ? { workspace: `${sub}/${found.workspace}` } : {}), ...(found.project ? { project: `${sub}/${found.project}` } : {}) } : found;
|
||||
if (sub) target.notes.unshift(`Capacitor project: ${sub} (${sub}/${cap!.config}; the only one found; set apple.build.workspace to use another)`);
|
||||
const configuration = a.str('configuration', cfg.configuration ?? 'Release');
|
||||
const out = resolve(a.get('out') ?? cfg.out ?? 'build/ios');
|
||||
const version = a.get('version');
|
||||
|
|
@ -115,8 +125,10 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
];
|
||||
const plan = { framework: target.framework, workspace: target.workspace ?? null, project: target.project ?? null, scheme: target.scheme, configuration,
|
||||
version: version ?? '(project value)', build: buildNo, teamId, signingStyle: style, exportOptions: opts, out: relative(root, out) || '.', steps, notes: target.notes };
|
||||
const webOpts = { config, root, cap, store: 'apple' as const, tag: TAG, noPrebuild: a.has('no-prebuild'), allowStale: a.has('allow-stale-web') };
|
||||
if (dry) {
|
||||
if (json) { printJson(plan); return; }
|
||||
const web = await webStep({ ...webOpts, dry: true, quiet: json });
|
||||
if (json) { printJson({ ...plan, web }); return; }
|
||||
log(TAG, `${target.framework}: ${target.workspace ?? target.project} scheme ${target.scheme} (${configuration}), build ${buildNo}${version ? `, version ${version}` : ''}, signing ${style}`);
|
||||
for (const n of target.notes) log(TAG, n);
|
||||
for (const s of steps) log(TAG, `would run: ${s}`);
|
||||
|
|
@ -125,6 +137,7 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
return;
|
||||
}
|
||||
if (process.platform !== 'darwin' || !Bun.which('xcodebuild')) throw new Error('apple build needs macOS with Xcode (xcode-select --install is not enough: the full Xcode app)');
|
||||
const web = await webStep({ ...webOpts, dry: false });
|
||||
for (const n of target.notes) log(TAG, n);
|
||||
mkdirSync(out, { recursive: true });
|
||||
writeFileSync(join(out, 'ExportOptions.plist'), plist);
|
||||
|
|
@ -142,7 +155,7 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
const info = ipaInfo(join(exportPath, ipa));
|
||||
if (info.bundleId !== bundleId) warn(TAG, `the IPA's bundle id is ${info.bundleId}, the config says ${bundleId}`);
|
||||
if (info.build !== build) warn(TAG, `the IPA has build ${info.build}, not ${build}: the project sets CFBundleVersion itself (use $(CURRENT_PROJECT_VERSION) in Info.plist)`);
|
||||
const result = { ipa: join(exportPath, ipa), bundleId: info.bundleId, version: info.version, build: info.build };
|
||||
const result = { ipa: join(exportPath, ipa), bundleId: info.bundleId, version: info.version, build: info.build, web: web.bundle };
|
||||
if (json) printJson(result);
|
||||
done(TAG, `${relative(root, result.ipa)}: ${info.bundleId} ${info.version} (${info.build}). Next: storebound apple upload ${relative(root, result.ipa)} --dry`);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue