apple signing: App Group association fails early under automatic signing too (API capability + newest profile, steps and deep link, exit before the archive); console plan/verify apple-app-group
This commit is contained in:
parent
d978bb7a8b
commit
304407e523
20 changed files with 590 additions and 60 deletions
|
|
@ -2,6 +2,7 @@
|
|||
// storebound console plan lists the forms
|
||||
// storebound console plan <form> [--json] [--lookup]
|
||||
// storebound console plan play-app-content --all [--json] every App content card's steps inline, one owner list
|
||||
// storebound console plan apple-app-group [--bundle-id <id>] [--groups group.a,group.b] [--lookup] [--json] the developer portal App Group assignment the API cannot do
|
||||
// Read-only. Offline by default: the start URL, the ordered steps, the answer sheet (each answer certain or "ask
|
||||
// owner", with the console's own labels), the never-press list, the known UI facts and how to verify. Deep links use
|
||||
// apple.appId / play.developerId / play.appId, the ids cached by earlier runs (.storebound/console-app-ids.json,
|
||||
|
|
@ -12,8 +13,31 @@ import type { Ctx } from '../lib/ctx';
|
|||
import { formMeta, isForm, isPlanForm, PLAN_FORM_IDS } from '../lib/console-answers';
|
||||
import { reviewDemoAccount } from '../lib/credentials';
|
||||
import { consolePlan, type PlanIds, planText } from '../lib/console-plan';
|
||||
import type { ShipConfig } from '../lib/config';
|
||||
import { printJson } from '../lib/log';
|
||||
|
||||
/** apple-app-group: the targets whose entitlements (or apple.signing.extensions) list App Groups, narrowed by --bundle-id;
|
||||
* --groups g1,g2 names the groups of a bundle id the project does not show; --lookup reads the bundle id's resource id (a GET). */
|
||||
async function appGroupTargets(ctx: Ctx, cfg: ShipConfig, bundleId: string | undefined, groups: string | undefined, lookup: boolean): Promise<NonNullable<PlanIds['appGroupTargets']>> {
|
||||
const { discoverTargets } = await import('../apple/signing');
|
||||
const scan = discoverTargets(ctx.loaded.root, cfg);
|
||||
const all = [...(scan.main ? [scan.main] : []), ...scan.extensions];
|
||||
const listed = groups ? groups.split(',').map(g => g.trim()).filter(Boolean) : [];
|
||||
for (const g of listed) if (!/^group.[A-Za-z0-9.-]+$/.test(g)) throw new Error(`console plan apple-app-group: --groups "${g}" is not an App Group id (group.<reverse domain>)`);
|
||||
let found = all.filter(t => t.appGroups.length && (!bundleId || t.bundleId === bundleId)).map(t => ({ bundleId: t.bundleId, name: t.name, groups: listed.length ? listed : t.appGroups, source: t.entitlements ? `entitlements ${t.entitlements}` : 'apple.signing.extensions' }));
|
||||
if (bundleId && !found.length) {
|
||||
if (!listed.length) throw new Error(`console plan apple-app-group: no target "${bundleId}" with App Groups in the project (targets with groups: ${all.filter(t => t.appGroups.length).map(t => t.bundleId).join(', ') || 'none'}); pass --groups group.a,group.b`);
|
||||
found = [{ bundleId, name: bundleId, groups: listed, source: '--groups' }];
|
||||
}
|
||||
if (!lookup || !found.length) return found;
|
||||
const bundle = cfg.apple?.bundleId;
|
||||
if (!bundle) throw new Error('console plan: apple.bundleId is not set (needed for --lookup)');
|
||||
const { ascClient } = await import('../apple/asc');
|
||||
const { findBundleId } = await import('../apple/signing');
|
||||
const c = await ascClient(ctx.loaded.root, bundle, cfg.apple?.appId);
|
||||
return Promise.all(found.map(async t => ({ ...t, resourceId: (await findBundleId(c, t.bundleId))?.id ?? null })));
|
||||
}
|
||||
|
||||
export async function run(ctx: Ctx): Promise<void> {
|
||||
const { config, root } = ctx.loaded;
|
||||
const args = ctx.args, json = args.has('json');
|
||||
|
|
@ -21,7 +45,7 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
if (!form) {
|
||||
const forms = PLAN_FORM_IDS.map(id => {
|
||||
if (isForm(id)) { const m = formMeta(id, config); return { id, store: m.store, title: m.title, fillable: m.fillable, scripted: m.fillable }; }
|
||||
return { id, store: 'play' as const, title: consolePlan(id, config).title, fillable: true, scripted: false };
|
||||
return { id, store: id.startsWith('apple-') ? 'apple' as const : 'play' as const, title: consolePlan(id, config).title, fillable: true, scripted: false };
|
||||
});
|
||||
if (json) return printJson({ forms });
|
||||
console.log('storebound console plan <form> [--json] [--lookup] (then drive the page yourself; `storebound console verify <form>` checks it)\n');
|
||||
|
|
@ -53,6 +77,7 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
ids.readyChanges = await readyFromApi(root, cfg);
|
||||
} catch { /* not read: the plan says so */ }
|
||||
}
|
||||
if (form === 'apple-app-group') ids.appGroupTargets = await appGroupTargets(ctx, cfg, args.get('bundle-id'), args.get('groups'), args.has('lookup'));
|
||||
if (args.has('all') && form !== 'play-app-content') throw new Error('console plan: --all is for play-app-content (every App content card inline)');
|
||||
const plan = consolePlan(form, cfg, ids, { all: args.has('all') });
|
||||
if (json) return printJson(plan);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// storebound console verify <form> [--from <file|->] [--from-text <file|->] [--json]
|
||||
// --from takes the JSON of the plan's verify.capture snippet (input states: aria-checked, checked, aria-selected) or page text.
|
||||
// storebound console verify play-app-record --url <url after Create app> [--write]
|
||||
// storebound console verify apple-app-group [--bundle-id <id>] (API: the App Groups on the App ID's newest readable profile)
|
||||
// apple-app-privacy: the page text of the App Privacy page (the Data Types section's per-type summaries; the App Store
|
||||
// Connect API has no App Privacy endpoint). play-app-record: the JSON of the plan's capture snippet (input states) or
|
||||
// the page text. play-content-rating, play-target-audience: the summary page text. play-app-content: the plan's
|
||||
|
|
@ -43,6 +44,21 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
const { readyFromApi } = await import('../play/publish-ready');
|
||||
const { verifyPublishChangesApi } = await import('../lib/console-verify');
|
||||
r = verifyPublishChangesApi(await readyFromApi(root, config));
|
||||
} else if (form === 'apple-app-group') {
|
||||
const { discoverTargets, readGroupEvidence } = await import('../apple/signing');
|
||||
const { appGroupVerdicts } = await import('../lib/targets');
|
||||
const { verifyAppGroupsApi } = await import('../lib/console-verify');
|
||||
const want = args.get('bundle-id');
|
||||
const scan = discoverTargets(root, config);
|
||||
const targets = [...(scan.main ? [scan.main] : []), ...scan.extensions].filter(t => t.appGroups.length && (!want || t.bundleId === want));
|
||||
if (!targets.length) throw new Error(`console verify apple-app-group: no target${want ? ` "${want}"` : ''} with App Groups in the project's entitlements or apple.signing.extensions`);
|
||||
const bundleId = config.apple?.bundleId;
|
||||
if (!bundleId) throw new Error('console verify: apple.bundleId is not set');
|
||||
const { ascClient } = await import('../apple/asc');
|
||||
const c = await ascClient(root, bundleId, config.apple?.appId);
|
||||
const style = config.apple?.signing?.style ?? 'automatic';
|
||||
const evidence = await readGroupEvidence(c, targets, style, config.apple?.signing?.profileType ?? 'IOS_APP_STORE');
|
||||
r = verifyAppGroupsApi(targets, appGroupVerdicts(targets, evidence));
|
||||
} else if (form === 'apple-app-record' || form === 'apple-age-rating') {
|
||||
const bundleId = config.apple?.bundleId;
|
||||
if (!bundleId) throw new Error('console verify: apple.bundleId is not set');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue