Console pages as absolute deep links: cli/lib/console-links.ts builds the URL of every App Store Connect / Play Console page from apple.appId, play.developerId and play.appId (app list / developer chooser when unknown); every plain 'App Store Connect > ...' / 'Play Console > ...' direction in cli/ and templates/ now carries it, validate / scan / declarations / release plan JSON carry url, the desktop shows an Open button; tests/console-links.test.ts guards it; AGENTS rule 11, pitfall 71
This commit is contained in:
parent
f898d642d0
commit
e500e88e40
50 changed files with 381 additions and 92 deletions
|
|
@ -33,6 +33,7 @@
|
|||
// error of Play Console (no policy page; developer.android.com/build/configure-app-module on choosing the id).
|
||||
// The other Google Play rules (sensitive permissions, prominent disclosure, Families, advertising ID, 16 KB pages,
|
||||
// testing for personal accounts, Play metadata, ads, payments, impersonation) are in play-rules.ts with their sources.
|
||||
import { consoleLink, consoleUrl } from '../console-links';
|
||||
import type { FixAction } from '../fix-actions';
|
||||
import { templateBlockers } from '../template-answers';
|
||||
import { BETA_WORDS, competitorRe, OTHER_PLATFORMS, PLACEHOLDER_MARKERS, PLACEHOLDER_TEXT, PRICE_CLAIMS } from '../listing-rules';
|
||||
|
|
@ -57,6 +58,8 @@ export interface ScanFinding {
|
|||
severity: Severity;
|
||||
title: string;
|
||||
evidence: Hit[];
|
||||
/** Absolute deep link to the console page the message points to (desktop / MCP render it as an Open link). */
|
||||
url?: string;
|
||||
fix: string;
|
||||
skill: string;
|
||||
/** Policy page. */
|
||||
|
|
@ -222,7 +225,7 @@ export function scanRules(inp: ScanInput): ScanFinding[] {
|
|||
}
|
||||
if (play && ra?.signInRequired && !inp.demoAccount)
|
||||
add({ rule: 'play-app-access', guideline: 'Play: App access', store: 'play', severity: 'high', title: 'Sign-in is required but there are no reviewer credentials for Play App access', evidence: ev,
|
||||
fix: 'Play Console > App content > App access: add the demo account and instructions (keep the credentials in .storebound/credentials.json only).', skill: 'review-access', ref: `${PLAY}9859455` });
|
||||
fix: `${consoleLink('play.app-access')}: add the demo account and instructions (keep the credentials in .storebound/credentials.json only).`, url: consoleUrl('play.app-access'), skill: 'review-access', ref: `${PLAY}9859455` });
|
||||
}
|
||||
if (creates.length) {
|
||||
if (apple && !deletionCode.length && deletionClaim.length)
|
||||
|
|
@ -264,7 +267,7 @@ export function scanRules(inp: ScanInput): ScanFinding[] {
|
|||
const termsUrl = c?.app.termsUrl;
|
||||
if (!(termsUrl && all.includes(termsUrl)) && !/terms of use|terms of service|\beula\b|license agreement/i.test(all))
|
||||
add({ rule: 'subscription-info', guideline: '3.1.2(c)', store: 'apple', severity: 'high', title: 'App Store description has no Terms of Use (EULA) link', evidence: desc.slice(0, 1),
|
||||
fix: 'Add a line "Terms of Use: <app.termsUrl>" (and the privacy policy) to the description of every locale, or set a custom EULA in App Store Connect > App Information.', skill: 'store-subscriptions' });
|
||||
fix: 'Add a line "Terms of Use: <app.termsUrl>" (and the privacy policy) to the description of every locale, or set a custom EULA in ' + consoleLink('asc.app-info') + '.', url: consoleUrl('asc.app-info'), skill: 'store-subscriptions' });
|
||||
if (!/renew|cancel/i.test(all))
|
||||
add({ rule: 'subscription-info', guideline: '3.1.2(c)', store: 'apple', severity: 'medium', title: 'App Store description does not say the subscription renews automatically or how to cancel', evidence: desc.slice(0, 1),
|
||||
fix: 'Describe what the subscription includes, the length and price per period, that it renews automatically unless cancelled at least 24 hours before the end of the period, and where to manage it.', skill: 'store-subscriptions' });
|
||||
|
|
@ -469,7 +472,7 @@ export function scanRules(inp: ScanInput): ScanFinding[] {
|
|||
ref: 'https://developer.android.com/google/play/requirements/target-sdk' });
|
||||
else if (!a.targetSdk && a.targetSdkFrom)
|
||||
add({ rule: 'play-target-sdk', guideline: 'Play: Target API level', store: 'play', severity: 'low', title: `targetSdk comes from ${a.targetSdkFrom}: check that the built AAB targets ${need} or higher`, evidence: a.manifests.slice(0, 1).map(f => ({ file: f, line: 1, text: 'AndroidManifest.xml' })),
|
||||
fix: `Check with \`aapt2 dump badging app.aab\` or Play Console > App bundle explorer; Play refuses uploads below ${need}.`, skill: 'play-first-release', ref: 'https://developer.android.com/google/play/requirements/target-sdk' });
|
||||
fix: `Check with \`aapt2 dump badging app.aab\` or ${consoleLink('play.bundle-explorer')}; Play refuses uploads below ${need}.`, skill: 'play-first-release', ref: 'https://developer.android.com/google/play/requirements/target-sdk' });
|
||||
const perms = new Map(a.permissions.map(x => [x.name.replace(/^android\.permission\./, ''), x] as const));
|
||||
const fgs = perms.get('FOREGROUND_SERVICE');
|
||||
if (fgs && (!a.targetSdk || a.targetSdk.value >= 34)) {
|
||||
|
|
@ -477,7 +480,7 @@ export function scanRules(inp: ScanInput): ScanFinding[] {
|
|||
const untyped = a.services.filter(s => !s.fgsType);
|
||||
if (untyped.length && !typed.length)
|
||||
add({ rule: 'play-fgs', guideline: 'Play: Foreground service types', store: 'play', severity: 'high', title: 'FOREGROUND_SERVICE is requested but no <service> declares android:foregroundServiceType (required when targeting Android 14+)', evidence: [{ ...fgs.at, text: 'FOREGROUND_SERVICE' }, ...untyped.slice(0, 2).map(s => ({ ...s.at, text: `service ${s.name}` }))],
|
||||
fix: 'Give each foreground service a type and its FOREGROUND_SERVICE_<TYPE> permission, then fill App content > Foreground service permissions (description, user impact, video).', skill: 'play-first-release', ref: `${PLAY}13392821` });
|
||||
fix: 'Give each foreground service a type and its FOREGROUND_SERVICE_<TYPE> permission, then fill ' + consoleLink('play.foreground-service') + ' (description, user impact, video).', url: consoleUrl('play.foreground-service'), skill: 'play-first-release', ref: `${PLAY}13392821` });
|
||||
for (const s of typed) {
|
||||
const types = s.fgsType!.split('|').map(x => x.trim()).filter(Boolean);
|
||||
const missing = types.filter(t => FGS_PERMISSION[t] && !perms.has(`FOREGROUND_SERVICE_${FGS_PERMISSION[t]}`));
|
||||
|
|
@ -487,7 +490,7 @@ export function scanRules(inp: ScanInput): ScanFinding[] {
|
|||
}
|
||||
if (typed.length)
|
||||
add({ rule: 'play-fgs', guideline: 'Play: Foreground service types', store: 'play', severity: 'medium', title: `Foreground service types need a Play Console declaration: ${[...new Set(typed.flatMap(s => s.fgsType!.split('|')))].join(', ')}`, evidence: typed.slice(0, 3).map(s => ({ ...s.at, text: `${s.name}: ${s.fgsType}` })),
|
||||
fix: 'App content > Foreground service permissions: describe each use, the user impact if it is deferred, and add a short video; shortService needs none.', skill: 'play-first-release', ref: `${PLAY}13392821` });
|
||||
fix: consoleLink('play.foreground-service') + ': describe each use, the user impact if it is deferred, and add a short video; shortService needs none.', skill: 'play-first-release', ref: `${PLAY}13392821` });
|
||||
}
|
||||
const exact = perms.get('USE_EXACT_ALARM');
|
||||
if (exact)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue