scan, privacy answers and credentials from a second game's first release (lessons 35-37): review-prompt needs rating context, not a bare "rate"; account signals skip import lines and module paths, config-only accounts say "from config"; accounts.inApp without deletion code stays a medium 5.1.1(v) finding; privacy-manifest --propose builds NSPrivacyCollectedDataTypes from appPrivacy.types and check warns on an empty list; appPrivacy.types is authoritative in the App Privacy sheet (unlisted = not collected), ad / support / AR types default to not collected without tracking or ad SDKs; Play advertising ID from SDKs, the AD_ID permission, app-ads.txt or the flag, never from a Device ID alone; scan.ignore[].evidence and warnings for entries that match nothing; empty credentials template, placeholder values count as missing (check, doctor)
This commit is contained in:
parent
86cd79dd45
commit
1366e7e231
34 changed files with 881 additions and 71 deletions
|
|
@ -7,7 +7,7 @@ import { reviewDemoAccount } from '../credentials';
|
|||
import { proposeDeclarations, type Proposal } from './declare';
|
||||
import { detectProject, ECO_LABEL, type ProjectFacts, type Stack, where } from './project';
|
||||
import type { OriginalityReport } from '../originality';
|
||||
import { APPLE_GUIDELINES, type ScanFinding, scanRules, type Severity, sortScan } from './rules';
|
||||
import { APPLE_GUIDELINES, applyIgnore, type ScanFinding, scanRules, type Severity, sortScan, unusedIgnores } from './rules';
|
||||
import type { SdkCategory } from './sdks';
|
||||
|
||||
export interface ScanReport {
|
||||
|
|
@ -68,6 +68,8 @@ export async function runScan(dir: string, o: { config?: string } = {}): Promise
|
|||
else if (facts.android && facts.androidInfo.manifests.length) notes.push('Android: libraries add permissions when the manifest is merged and no local build output (build/intermediates) was found; check the merged manifest (Android Studio > Merged Manifest, or the AAB) too.');
|
||||
if (facts.ios && !facts.plistSources.length) notes.push('iOS: no Info.plist settings found (generated at build time?): purpose string, ATS and export compliance checks were skipped.');
|
||||
if (!facts.sources.length) notes.push('No app source files found: code checks (sign-in, deletion, restore, ATT prompt) could not run.');
|
||||
// scan.ignore entries that accept nothing (a renamed title, a typo, a fixed issue) are said, never silently dropped
|
||||
notes.push(...unusedIgnores(findings, config?.scan?.ignore ?? []));
|
||||
const report: ScanReport = {
|
||||
root: dir, config: configPath ? rel(configPath) : null, configError: error,
|
||||
stacks: facts.stacks, platforms: { ios: facts.ios, android: facts.android },
|
||||
|
|
@ -149,10 +151,9 @@ export function addOriginality(r: ScanReport, o: OriginalityReport, stores: { ap
|
|||
if (stores.apple) r.findings.push({ ...base, guideline: '4.3', store: 'apple', ref: APPLE_GUIDELINES });
|
||||
if (stores.play) r.findings.push({ ...base, guideline: 'Play: Repetitive content', store: 'play', ref: 'https://support.google.com/googleplay/android-developer/answer/9898783' });
|
||||
}
|
||||
for (const f of r.findings) if (f.rule === 'originality' && !f.accepted) {
|
||||
const hit = ignore.find(i => i.rule === f.rule && (!i.store || i.store === f.store) && (!i.match || f.title.toLowerCase().includes(i.match.toLowerCase())));
|
||||
if (hit) f.accepted = hit.reason;
|
||||
}
|
||||
applyIgnore(r.findings.filter(f => f.rule === 'originality'), ignore);
|
||||
// runScan skipped the originality entries (it cannot produce that rule); now they can be checked
|
||||
r.notes.push(...unusedIgnores(r.findings, ignore, []).filter(n => n.includes('(rule originality')));
|
||||
r.findings = sortScan(r.findings);
|
||||
r.counts = { blocker: 0, high: 0, medium: 0, low: 0, accepted: 0 };
|
||||
for (const f of r.findings) { if (f.accepted) r.counts.accepted++; else r.counts[f.severity]++; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue