apple iap-submit: DEVELOPER_REJECTED IAP / subscription / group versions (left by a canceled submission) are submitted as they are, plan says 'add (developer rejected, re-submitted as is)', --new-version only for APPROVED / REJECTED, empty draft reused; fake ASC models cancel and re-add. play build / check: warn before Gradle when defaultConfig does not read -PversionCode / -PversionName, with the exact Groovy / Kotlin line (built with AGP 8.13); android.injected.version.code does not override bundleRelease; post-build versionCode mismatch is a hard stop. play-production-release plan: 'Do you want to restart your review?' dialog, Restart review is never-press. Pitfalls 31-33
This commit is contained in:
parent
41d2ffcc24
commit
0b1451800c
18 changed files with 399 additions and 26 deletions
|
|
@ -52,8 +52,11 @@ export function pickGradle(root: string, cfg: PlayBuildConfig = {}): GradleTarge
|
|||
return { framework: fw, dir, module, task: `:${module}:bundle${flavor}Release`, notes };
|
||||
}
|
||||
|
||||
/** Command line of the release bundle build. Version code / name go in as injected properties (the same ones Android
|
||||
* Studio uses) and as -PversionCode / -PversionName for build files that read those. */
|
||||
/** Command line of the release bundle build. Version code / name go in as -PversionCode / -PversionName (the build
|
||||
* file must read them: versionSourceIssues) and as the injected properties Android Studio uses. Those do NOT
|
||||
* override defaultConfig for bundleRelease: checked with AGP 8.13 (2026-10-12), `-Pandroid.injected.version.code=42`
|
||||
* on a build file with `versionCode 1` gives an AAB with versionCode 1; seen live the same day with a Capacitor app.
|
||||
* They stay on the command line for older plugins / IDE parity only; the AAB check after the build is the stop. */
|
||||
export function gradleArgs(t: GradleTarget, o: { versionCode?: number; versionName?: string; flavor?: string; hasWrapper: boolean }): string[] {
|
||||
if (t.framework === 'flutter')
|
||||
return ['flutter', 'build', 'appbundle', '--release', ...(o.flavor ? ['--flavor', o.flavor] : []), ...(o.versionCode ? ['--build-number', String(o.versionCode)] : []), ...(o.versionName ? ['--build-name', o.versionName] : [])];
|
||||
|
|
@ -62,6 +65,90 @@ export function gradleArgs(t: GradleTarget, o: { versionCode?: number; versionNa
|
|||
...(o.versionName ? [`-Pandroid.injected.version.name=${o.versionName}`, `-PversionName=${o.versionName}`] : [])];
|
||||
}
|
||||
|
||||
// ---- where the build file takes versionCode / versionName from ----
|
||||
|
||||
export interface VersionSourceIssue { field: 'versionCode' | 'versionName'; file: string; line: number | null; text: string; kind: 'literal' | 'other' | 'missing'; current: string; fix: string }
|
||||
|
||||
/** The application module's build file (build.gradle.kts first, then build.gradle), or null. */
|
||||
export function appBuildFile(t: Pick<GradleTarget, 'dir' | 'module'>): string | null {
|
||||
const base = join(t.dir, ...t.module.split(':'));
|
||||
return ['build.gradle.kts', 'build.gradle'].map(f => join(base, f)).find(f => existsSync(f)) ?? null;
|
||||
}
|
||||
|
||||
/** The line that reads the -P property storebound passes, keeping `current` as the default. Pure. */
|
||||
export function versionFixLine(field: 'versionCode' | 'versionName', current: string, kotlin: boolean): string {
|
||||
// Groovy: `versionCode (x) as Integer` parses as versionCode(x) as Integer, which hands Gradle a String (AGP 8.13:
|
||||
// "Value is null"); the cast must sit inside the parentheses
|
||||
if (kotlin) return field === 'versionCode' ? `versionCode = (project.findProperty("versionCode") as String?)?.toInt() ?: ${current}`
|
||||
: `versionName = project.findProperty("versionName") as String? ?: "${current}"`;
|
||||
return field === 'versionCode' ? `versionCode((project.findProperty('versionCode') ?: ${current}) as Integer)` : `versionName project.findProperty('versionName') ?: '${current}'`;
|
||||
}
|
||||
|
||||
/** The block body after `name {` (brace matched, strings skipped), with the line number it starts on. Pure. */
|
||||
function block(src: string, name: string): { body: string; line: number } | null {
|
||||
const m = new RegExp(`\\b${name}\\s*\\{`).exec(src);
|
||||
if (!m) return null;
|
||||
let depth = 0, q = '';
|
||||
const start = m.index + m[0].length;
|
||||
for (let i = start; i < src.length; i++) {
|
||||
const c = src[i]!;
|
||||
if (q) { if (c === '\\') i++; else if (c === q) q = ''; continue; }
|
||||
if (c === '"' || c === "'") q = c;
|
||||
else if (c === '{') depth++;
|
||||
else if (c === '}') { if (depth === 0) return { body: src.slice(start, i), line: src.slice(0, start).split('\n').length }; depth--; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether defaultConfig of the build file reads versionCode / versionName from the project properties storebound
|
||||
* passes (-PversionCode / -PversionName). A literal (`versionCode 1`), an environment variable or anything else keeps
|
||||
* the AAB at that value whatever `play build` passes (seen live: a Capacitor app read versionCode from an env var and
|
||||
* the AAB stayed at 1). Each issue carries the exact line to use. Pure apart from reading the file.
|
||||
*/
|
||||
export function versionSourceIssues(file: string, src = readFileSync(file, 'utf8')): VersionSourceIssue[] {
|
||||
const kotlin = file.endsWith('.kts');
|
||||
// comments out first (block and line; a `//` right after a colon is a URL), keeping the line numbers
|
||||
const code = src.replace(/\/\*[\s\S]*?\*\//g, m => m.replace(/[^\n]/g, ' ')).replace(/(^|[^:])\/\/.*$/gm, '$1');
|
||||
const dc = block(code, 'defaultConfig');
|
||||
if (!dc) return [];
|
||||
const out: VersionSourceIssue[] = [];
|
||||
const lines = dc.body.split('\n');
|
||||
for (const field of ['versionCode', 'versionName'] as const) {
|
||||
const fallback = field === 'versionCode' ? '1' : '1.0';
|
||||
let hit: { line: number; text: string; value: string } | null = null;
|
||||
lines.forEach((l, i) => {
|
||||
for (const stmt of l.split(';')) {
|
||||
const m = new RegExp(`^\\s*(?:${field}|set${field[0]!.toUpperCase()}${field.slice(1)})\\b\\s*(?:=\\s*)?(.*)$`).exec(stmt);
|
||||
if (m && !hit) hit = { line: dc.line + i, text: l.trim(), value: m[1]!.trim() };
|
||||
}
|
||||
});
|
||||
if (!hit) {
|
||||
out.push({ field, file, line: null, text: '', kind: 'missing', current: fallback, fix: versionFixLine(field, fallback, kotlin) });
|
||||
continue;
|
||||
}
|
||||
const h = hit as { line: number; text: string; value: string };
|
||||
const reads = new RegExp(`(?:findProperty|property|hasProperty|getProperty|gradleProperty)\\s*\\(\\s*['"]${field}['"]|properties\\s*\\[\\s*['"]${field}['"]`).test(h.value);
|
||||
if (reads) continue;
|
||||
const lit = /^\(?\s*(\d+|"[^"]*"|'[^']*')\s*\)?$/.exec(h.value);
|
||||
const current = lit ? lit[1]!.replace(/^['"]|['"]$/g, '')
|
||||
: field === 'versionCode' ? (/(?:\?:|\|\||,)\s*['"]?(\d+)['"]?/.exec(h.value)?.[1] ?? /\b(\d+)\b/.exec(h.value)?.[1] ?? fallback)
|
||||
: (/['"](\d[\w.+-]*)['"]/.exec(h.value)?.[1] ?? fallback);
|
||||
out.push({ field, file, line: h.line, text: h.text, kind: lit ? 'literal' : 'other', current, fix: versionFixLine(field, current, kotlin) });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** One warning per issue: where, why, and the line to use. Pure. */
|
||||
export function versionSourceMessage(i: VersionSourceIssue, rel: string, framework?: AndroidFramework): string {
|
||||
const where = i.line ? `${rel}:${i.line}` : rel;
|
||||
const flag = i.field === 'versionCode' ? '-PversionCode' : '-PversionName';
|
||||
const what = i.kind === 'missing' ? `defaultConfig sets no ${i.field}`
|
||||
: i.kind === 'literal' ? `${i.field} is a literal (${i.text})` : `${i.field} does not come from the project property ${i.field} (${i.text})`;
|
||||
const unity = framework === 'unity' ? ' (Unity export: the file is rewritten on every export; set Bundle Version Code / Version in Player Settings or edit it after each export)' : '';
|
||||
return `${where}: ${what}, so ${flag} from play build is ignored and the AAB keeps ${i.kind === 'missing' ? 'whatever the manifest says' : i.current}; android.injected.version.* does not override it for bundleRelease. Use in defaultConfig: ${i.fix}${unity}`;
|
||||
}
|
||||
|
||||
/** Environment that hands the upload key to the Android Gradle plugin (injected signing properties). */
|
||||
export function signingEnv(s: AndroidSigning | null): Record<string, string> {
|
||||
if (!s) return {};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue