android: derive release notes from changelog

This commit is contained in:
joshavant
2026-06-16 21:54:21 +02:00
parent 08e0b8cf6b
commit 529150868c
7 changed files with 198 additions and 0 deletions

View File

@@ -30,7 +30,9 @@ const PUBLIC_EXTENSION_CONTRACT_RE =
*/
export const RELEASE_METADATA_PATHS = new Set([
"CHANGELOG.md",
"apps/android/CHANGELOG.md",
"apps/android/Config/Version.properties",
"apps/android/fastlane/metadata/android/en-US/release_notes.txt",
"apps/android/version.json",
"apps/ios/CHANGELOG.md",
"apps/ios/Config/Version.xcconfig",

View File

@@ -46,7 +46,9 @@ const LINTABLE_CORE_PATH_RE = /^(?:src|ui|packages)\/.+\.[cm]?[jt]sx?$/u;
const CORE_LINT_OPTIMIZATION_NEUTRAL_PATH_RE =
/^(?:scripts|test\/scripts)\/|^\.github\/workflows\/ci\.yml$/u;
const ANDROID_VERSION_SYNC_PATHS = new Set([
"apps/android/CHANGELOG.md",
"apps/android/Config/Version.properties",
"apps/android/fastlane/metadata/android/en-US/release_notes.txt",
"apps/android/version.json",
]);
let corepackPnpmShimDir;

View File

@@ -4,7 +4,9 @@ import path from "node:path";
import { parseReleaseVersion } from "./npm-publish-plan.mjs";
const ANDROID_VERSION_FILE = "apps/android/version.json";
const ANDROID_CHANGELOG_FILE = "apps/android/CHANGELOG.md";
const ANDROID_VERSION_PROPERTIES_FILE = "apps/android/Config/Version.properties";
const ANDROID_RELEASE_NOTES_FILE = "apps/android/fastlane/metadata/android/en-US/release_notes.txt";
const ANDROID_VERSION_CODE_MAX = 2_100_000_000;
type AndroidVersionManifest = {
@@ -14,6 +16,8 @@ type AndroidVersionManifest = {
export type ResolvedAndroidVersion = {
canonicalVersion: string;
changelogPath: string;
releaseNotesPath: string;
versionCode: number;
versionFilePath: string;
versionPropertiesPath: string;
@@ -161,13 +165,17 @@ export function writeAndroidVersionManifest(
export function resolveAndroidVersion(rootDir = path.resolve(".")): ResolvedAndroidVersion {
const versionFilePath = path.join(rootDir, ANDROID_VERSION_FILE);
const changelogPath = path.join(rootDir, ANDROID_CHANGELOG_FILE);
const versionPropertiesPath = path.join(rootDir, ANDROID_VERSION_PROPERTIES_FILE);
const releaseNotesPath = path.join(rootDir, ANDROID_RELEASE_NOTES_FILE);
const manifest = readAndroidVersionManifest(rootDir);
const canonicalVersion = normalizePinnedAndroidVersion(manifest.version ?? "");
const versionCode = normalizeAndroidVersionCode(manifest.versionCode, canonicalVersion);
return {
canonicalVersion,
changelogPath,
releaseNotesPath,
versionCode,
versionFilePath,
versionPropertiesPath,
@@ -178,6 +186,51 @@ export function renderAndroidVersionProperties(version: ResolvedAndroidVersion):
return `# Shared Android version defaults.\n# Source of truth: apps/android/version.json\n# Generated by scripts/android-sync-versioning.ts.\n\nOPENCLAW_ANDROID_VERSION_NAME=${version.canonicalVersion}\nOPENCLAW_ANDROID_VERSION_CODE=${version.versionCode}\n`;
}
function matchChangelogHeading(line: string, heading: string): boolean {
const normalized = line.trim();
return normalized === `## ${heading}` || normalized.startsWith(`## ${heading} - `);
}
export function extractChangelogSection(content: string, heading: string): string | null {
const lines = content.split(/\r?\n/u);
const startIndex = lines.findIndex((line) => matchChangelogHeading(line, heading));
if (startIndex === -1) {
return null;
}
let endIndex = lines.length;
for (let index = startIndex + 1; index < lines.length; index += 1) {
if (lines[index]?.startsWith("## ")) {
endIndex = index;
break;
}
}
const body = lines
.slice(startIndex + 1, endIndex)
.join("\n")
.trim();
return body || null;
}
export function renderAndroidReleaseNotes(
version: ResolvedAndroidVersion,
changelogContent: string,
): string {
const candidateHeadings = [version.canonicalVersion, "Unreleased"];
for (const heading of candidateHeadings) {
const body = extractChangelogSection(changelogContent, heading);
if (body) {
return `${body}\n`;
}
}
throw new Error(
`Unable to find Android changelog notes for ${version.canonicalVersion}. Add a matching section to ${ANDROID_CHANGELOG_FILE}.`,
);
}
function syncFile(params: {
mode: SyncAndroidVersioningMode;
path: string;
@@ -207,7 +260,9 @@ export function syncAndroidVersioning(params?: {
const mode = params?.mode ?? "write";
const rootDir = path.resolve(params?.rootDir ?? ".");
const version = resolveAndroidVersion(rootDir);
const changelogContent = readFileSync(version.changelogPath, "utf8");
const nextVersionProperties = renderAndroidVersionProperties(version);
const nextReleaseNotes = renderAndroidReleaseNotes(version, changelogContent);
const updatedPaths: string[] = [];
if (
@@ -221,5 +276,16 @@ export function syncAndroidVersioning(params?: {
updatedPaths.push(version.versionPropertiesPath);
}
if (
syncFile({
mode,
path: version.releaseNotesPath,
nextContent: nextReleaseNotes,
label: "Android release notes",
})
) {
updatedPaths.push(version.releaseNotesPath);
}
return { updatedPaths };
}