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:
parent
34c2fa22e5
commit
7219ded3e8
27 changed files with 659 additions and 19 deletions
|
|
@ -1,5 +1,8 @@
|
|||
// Play Data safety form through applications.dataSafety (CSV import; replaces every earlier answer).
|
||||
// storebound play data-safety [--csv out.csv] [--dry] [--force]
|
||||
// --dry prints the answers that change (question [response]: old -> new, account and deletion answers first) against
|
||||
// the CSV uploaded last (Play cannot read the form back), then the whole CSV (or --csv writes it). Template answers
|
||||
// (cli/lib/template-answers.ts) are refused, in the dry run too.
|
||||
// Answers come from `dataSafety` (and accounts.deletionUrl) in storebound.config.ts. --dry only writes / prints the CSV.
|
||||
// Both --dry and a real run stop on a missing required row (dataSafetyIssues) before anything is sent.
|
||||
// Play cannot read the form back, so a run compares with what this project uploaded last: the sha256 of that CSV
|
||||
|
|
@ -9,7 +12,8 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|||
import { join } from 'node:path';
|
||||
import type { Ctx } from '../lib/ctx';
|
||||
import { need } from '../lib/ctx';
|
||||
import { dataSafetyCsv, dataSafetyProblem, dataSafetyRows } from '../lib/data-safety';
|
||||
import { type DataSafetyChange, dataSafetyCsv, dataSafetyDiff, dataSafetyProblem, dataSafetyRows } from '../lib/data-safety';
|
||||
import { refuseTemplate } from '../lib/template-answers';
|
||||
import { DATA_SAFETY_UPLOADED } from '../lib/declarations';
|
||||
import { done, log } from '../lib/log';
|
||||
import { playClient } from './client';
|
||||
|
|
@ -36,11 +40,24 @@ function rememberUpload(root: string, pkg: string, sha256: string): void {
|
|||
writeFileSync(f, JSON.stringify(all, null, 2) + '\n');
|
||||
}
|
||||
|
||||
const ACCOUNT_Q = /ACCOUNT|DELETION/;
|
||||
|
||||
/** The changed answers, account and deletion answers first, as `question [response]: old -> new` lines. Pure. */
|
||||
export function changeLines(changes: DataSafetyChange[]): string[] {
|
||||
const show = (v: string) => (v ? v : '(empty)');
|
||||
const line = (c: DataSafetyChange) => ` ${c.question}${c.response ? ` [${c.response}]` : ''}: ${show(c.from)} -> ${show(c.to)}${c.label ? ` (${c.label.slice(0, 70)})` : ''}`;
|
||||
const acc = changes.filter(c => ACCOUNT_Q.test(c.question)), rest = changes.filter(c => !ACCOUNT_Q.test(c.question));
|
||||
return [...(acc.length ? [' account and deletion answers:', ...acc.map(line)] : []), ...(rest.length ? [' data types and other answers:', ...rest.map(line)] : [])];
|
||||
}
|
||||
|
||||
export async function run(ctx: Ctx): Promise<void> {
|
||||
const { config, root, resolve } = ctx.loaded;
|
||||
const ds = need(config.dataSafety, 'dataSafety');
|
||||
// template answers are never sent, and the dry run refuses the same way (it shows what a real run would do)
|
||||
refuseTemplate(config, ['dataSafety', 'accounts'], `play data-safety${ctx.args.has('dry') ? ' --dry' : ''}`);
|
||||
const csv = dataSafetyCsv(ds, config.accounts);
|
||||
const problem = dataSafetyProblem(dataSafetyRows(ds, config.accounts));
|
||||
const rows = dataSafetyRows(ds, config.accounts);
|
||||
const problem = dataSafetyProblem(rows);
|
||||
const out = ctx.args.get('csv');
|
||||
if (out) { writeFileSync(resolve(out), csv); log('play', `wrote ${out}`); }
|
||||
const types = Object.keys(config.dataSafety!.data);
|
||||
|
|
@ -50,6 +67,11 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
const last = lastUpload(root, pkg);
|
||||
const same = last?.sha256 === hash;
|
||||
if (ctx.args.has('dry')) {
|
||||
const upFile = join(root, DATA_SAFETY_UPLOADED);
|
||||
const before = existsSync(upFile) ? readFileSync(upFile, 'utf8') : null;
|
||||
const changes = dataSafetyDiff(before, rows);
|
||||
log('play', `changes against ${before ? `the last uploaded CSV (${DATA_SAFETY_UPLOADED}; Play cannot read the form back)` : 'nothing (no upload recorded: every answer is new)'}: ${changes.length} answer(s)`);
|
||||
for (const l of changeLines(changes)) console.log(l);
|
||||
if (!out) process.stdout.write(csv);
|
||||
if (problem) throw new Error(problem);
|
||||
log('play', same ? `the same CSV was uploaded ${last!.at.slice(0, 16).replace('T', ' ')} UTC: a real run sends nothing (--force sends it again)` : 'a real run uploads it (it differs from the last upload, or none is recorded)');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue