mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-21 15:01:03 +00:00
160 lines
5.2 KiB
TypeScript
160 lines
5.2 KiB
TypeScript
#!/usr/bin/env bun
|
|
|
|
import { $ } from "bun";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
const androidDir = join(scriptDir, "..");
|
|
const buildGradlePath = join(androidDir, "app", "build.gradle.kts");
|
|
const releaseOutputDir = join(androidDir, "build", "release-bundles");
|
|
|
|
const releaseVariants = [
|
|
{
|
|
flavorName: "play",
|
|
gradleTask: ":app:bundlePlayRelease",
|
|
bundlePath: join(androidDir, "app", "build", "outputs", "bundle", "playRelease", "app-play-release.aab"),
|
|
},
|
|
{
|
|
flavorName: "third-party",
|
|
gradleTask: ":app:bundleThirdPartyRelease",
|
|
bundlePath: join(
|
|
androidDir,
|
|
"app",
|
|
"build",
|
|
"outputs",
|
|
"bundle",
|
|
"thirdPartyRelease",
|
|
"app-thirdParty-release.aab",
|
|
),
|
|
},
|
|
] as const;
|
|
|
|
type VersionState = {
|
|
versionName: string;
|
|
versionCode: number;
|
|
};
|
|
|
|
type ParsedVersionMatches = {
|
|
versionNameMatch: RegExpMatchArray;
|
|
versionCodeMatch: RegExpMatchArray;
|
|
};
|
|
|
|
function formatVersionName(date: Date): string {
|
|
const year = date.getFullYear();
|
|
const month = date.getMonth() + 1;
|
|
const day = date.getDate();
|
|
return `${year}.${month}.${day}`;
|
|
}
|
|
|
|
function formatVersionCodePrefix(date: Date): string {
|
|
const year = date.getFullYear().toString();
|
|
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
const day = date.getDate().toString().padStart(2, "0");
|
|
return `${year}${month}${day}`;
|
|
}
|
|
|
|
function parseVersionMatches(buildGradleText: string): ParsedVersionMatches {
|
|
const versionCodeMatch = buildGradleText.match(/versionCode = (\d+)/);
|
|
const versionNameMatch = buildGradleText.match(/versionName = "([^"]+)"/);
|
|
if (!versionCodeMatch || !versionNameMatch) {
|
|
throw new Error(`Couldn't parse versionName/versionCode from ${buildGradlePath}`);
|
|
}
|
|
return { versionCodeMatch, versionNameMatch };
|
|
}
|
|
|
|
function resolveNextVersionCode(currentVersionCode: number, todayPrefix: string): number {
|
|
const currentRaw = currentVersionCode.toString();
|
|
let nextSuffix = 0;
|
|
|
|
if (currentRaw.startsWith(todayPrefix)) {
|
|
const suffixRaw = currentRaw.slice(todayPrefix.length);
|
|
nextSuffix = (suffixRaw ? Number.parseInt(suffixRaw, 10) : 0) + 1;
|
|
}
|
|
|
|
if (!Number.isInteger(nextSuffix) || nextSuffix < 0 || nextSuffix > 99) {
|
|
throw new Error(
|
|
`Can't auto-bump Android versionCode for ${todayPrefix}: next suffix ${nextSuffix} is invalid`,
|
|
);
|
|
}
|
|
|
|
return Number.parseInt(`${todayPrefix}${nextSuffix.toString().padStart(2, "0")}`, 10);
|
|
}
|
|
|
|
function resolveNextVersion(buildGradleText: string, date: Date): VersionState {
|
|
const { versionCodeMatch } = parseVersionMatches(buildGradleText);
|
|
const currentVersionCode = Number.parseInt(versionCodeMatch[1] ?? "", 10);
|
|
if (!Number.isInteger(currentVersionCode)) {
|
|
throw new Error(`Invalid Android versionCode in ${buildGradlePath}`);
|
|
}
|
|
|
|
const versionName = formatVersionName(date);
|
|
const versionCode = resolveNextVersionCode(currentVersionCode, formatVersionCodePrefix(date));
|
|
return { versionName, versionCode };
|
|
}
|
|
|
|
function updateBuildGradleVersions(buildGradleText: string, nextVersion: VersionState): string {
|
|
return buildGradleText
|
|
.replace(/versionCode = \d+/, `versionCode = ${nextVersion.versionCode}`)
|
|
.replace(/versionName = "[^"]+"/, `versionName = "${nextVersion.versionName}"`);
|
|
}
|
|
|
|
async function sha256Hex(path: string): Promise<string> {
|
|
const buffer = await Bun.file(path).arrayBuffer();
|
|
const digest = await crypto.subtle.digest("SHA-256", buffer);
|
|
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
}
|
|
|
|
async function verifyBundleSignature(path: string): Promise<void> {
|
|
await $`jarsigner -verify ${path}`.quiet();
|
|
}
|
|
|
|
async function copyBundle(sourcePath: string, destinationPath: string): Promise<void> {
|
|
const sourceFile = Bun.file(sourcePath);
|
|
if (!(await sourceFile.exists())) {
|
|
throw new Error(`Signed bundle missing at ${sourcePath}`);
|
|
}
|
|
|
|
await Bun.write(destinationPath, sourceFile);
|
|
}
|
|
|
|
async function main() {
|
|
const buildGradleFile = Bun.file(buildGradlePath);
|
|
const originalText = await buildGradleFile.text();
|
|
const nextVersion = resolveNextVersion(originalText, new Date());
|
|
const updatedText = updateBuildGradleVersions(originalText, nextVersion);
|
|
|
|
if (updatedText === originalText) {
|
|
throw new Error("Android version bump produced no change");
|
|
}
|
|
|
|
console.log(`Android versionName -> ${nextVersion.versionName}`);
|
|
console.log(`Android versionCode -> ${nextVersion.versionCode}`);
|
|
|
|
await Bun.write(buildGradlePath, updatedText);
|
|
await $`mkdir -p ${releaseOutputDir}`;
|
|
|
|
try {
|
|
await $`./gradlew ${releaseVariants[0].gradleTask} ${releaseVariants[1].gradleTask}`.cwd(androidDir);
|
|
} catch (error) {
|
|
await Bun.write(buildGradlePath, originalText);
|
|
throw error;
|
|
}
|
|
|
|
for (const variant of releaseVariants) {
|
|
const outputPath = join(
|
|
releaseOutputDir,
|
|
`openclaw-${nextVersion.versionName}-${variant.flavorName}-release.aab`,
|
|
);
|
|
|
|
await copyBundle(variant.bundlePath, outputPath);
|
|
await verifyBundleSignature(outputPath);
|
|
const hash = await sha256Hex(outputPath);
|
|
|
|
console.log(`Signed AAB (${variant.flavorName}): ${outputPath}`);
|
|
console.log(`SHA-256 (${variant.flavorName}): ${hash}`);
|
|
}
|
|
}
|
|
|
|
await main();
|