template compliance answers never reach a store: init writes unanswered (dataSafety, appPrivacy, reviewNotes, ageRating, accounts, reviewAccess), older configs are recognised by the template values; check / validate / scan block, play data-safety (also --dry), console plan / fill, apple review-details and appinfo refuse. onboard --declarations replaces them on a live app: name and copyright from the stores (a copyright with a private term is left out), the rows the code proves (scan proposal, sign-up methods from auth code and login SDKs), the rest as one owner question list. play data-safety --dry prints the answers that change (old -> new, account and deletion first) against the last uploaded CSV; pitfall 60; tests

This commit is contained in:
CRTkafa 2026-10-14 11:36:30 +08:00
parent 34c2fa22e5
commit 7219ded3e8
27 changed files with 659 additions and 19 deletions

View file

@ -367,6 +367,9 @@ export interface AccountsConfig {
signUp?: AccountSignUp[];
}
export type TemplateArea = 'dataSafety' | 'appPrivacy' | 'reviewNotes' | 'ageRating' | 'accounts' | 'reviewAccess';
export const TEMPLATE_AREAS: readonly TemplateArea[] = ['dataSafety', 'appPrivacy', 'reviewNotes', 'ageRating', 'accounts', 'reviewAccess'];
export type AccountSignUp = 'username' | 'username-password' | 'email-password' | 'email-link' | 'phone' | 'oauth' | 'other';
export const ACCOUNT_SIGN_UPS: readonly AccountSignUp[] = ['username', 'username-password', 'email-password', 'email-link', 'phone', 'oauth', 'other'];
@ -645,6 +648,15 @@ export interface ShipConfig {
/** App Store base territory of every price schedule (alpha-3). Default: the current one, else USA. */
baseTerritory?: string;
};
/**
* Compliance answers that are still the template's (`storebound init`) or not confirmed by the owner yet:
* 'dataSafety', 'appPrivacy', 'reviewNotes', 'ageRating', 'accounts', 'reviewAccess'. While an area is listed, check
* and validate report a blocker and nothing that sends it (play data-safety, apple review-details / appinfo, console
* plan / fill) runs. Remove an entry once its answers are the owner's (derived from the code: `storebound scan
* --write`, `storebound onboard --declarations`). `[]` = everything answered. Left out (configs before 0.8.4): the
* template answers are recognised by their values (cli/lib/template-answers.ts).
*/
unanswered?: TemplateArea[];
dataSafety?: DataSafety;
/** App Store App Privacy answers storebound cannot derive from dataSafety (App Privacy has no API: the owner enters
* them in App Store Connect). `storebound scan` proposes them from the detected SDKs and code. */
@ -1077,6 +1089,8 @@ export function validateConfig(c: unknown): string[] {
e.push(...validateCpp(cfg.apple.customProductPages, ids, Object.keys(cfg.shots?.variants ?? {})));
e.push(...validateEvents(cfg.apple.events, ids));
}
if (cfg.unanswered !== undefined && !(Array.isArray(cfg.unanswered) && cfg.unanswered.every(x => TEMPLATE_AREAS.includes(x))))
e.push(`unanswered: a list of ${TEMPLATE_AREAS.join(' | ')}`);
if (cfg.appPrivacy) {
if (cfg.appPrivacy.tracking !== undefined && typeof cfg.appPrivacy.tracking !== 'boolean') e.push('appPrivacy.tracking: true | false');
if (cfg.appPrivacy.trackingDomains && !(Array.isArray(cfg.appPrivacy.trackingDomains) && cfg.appPrivacy.trackingDomains.every(d => typeof d === 'string' && /^[a-z0-9.-]+$/i.test(d))))