// Android Pin Version script supports OpenClaw repository automation. import path from "node:path"; import { canonicalAndroidVersionCode, normalizeAndroidVersionCode, normalizePinnedAndroidVersion, resolveAndroidVersion, resolveGatewayVersionForAndroidRelease, syncAndroidVersioning, writeAndroidVersionManifest, } from "./lib/android-version.ts"; type CliOptions = { explicitVersion: string | null; explicitVersionCode: number | null; fromGateway: boolean; rootDir: string; sync: boolean; }; export type PinAndroidVersionResult = { previousVersion: string | null; previousVersionCode: number | null; nextVersion: string; nextVersionCode: number; packageVersion: string | null; versionFilePath: string; syncedPaths: string[]; }; function usage(): string { return [ "Usage: node --import tsx scripts/android-pin-version.ts (--from-gateway | --version ) [--version-code ] [--no-sync] [--root dir]", "", "Examples:", " node --import tsx scripts/android-pin-version.ts --from-gateway", " node --import tsx scripts/android-pin-version.ts --version 2026.6.5", " node --import tsx scripts/android-pin-version.ts --version 2026.6.5 --version-code 2026060502", ].join("\n"); } function parseExplicitVersionCode(raw: string): number { const text = raw.trim(); if (!/^[1-9]\d*$/u.test(text)) { throw new Error(`Invalid value for --version-code: ${raw}. Expected a positive integer.`); } const value = Number(text); if (!Number.isSafeInteger(value)) { throw new Error(`Invalid value for --version-code: ${raw}. Expected a safe integer.`); } return value; } export function parseArgs(argv: string[]): CliOptions { let explicitVersion: string | null = null; let explicitVersionCode: number | null = null; let fromGateway = false; let rootDir = path.resolve("."); let sync = true; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; switch (arg) { case "--from-gateway": { fromGateway = true; break; } case "--version": { explicitVersion = argv[index + 1] ?? null; index += 1; break; } case "--version-code": { const value = argv[index + 1]; if (!value) { throw new Error("Missing value for --version-code."); } explicitVersionCode = parseExplicitVersionCode(value); index += 1; break; } case "--no-sync": { sync = false; break; } case "--root": { const value = argv[index + 1]; if (!value) { throw new Error("Missing value for --root."); } rootDir = path.resolve(value); index += 1; break; } case "-h": case "--help": { console.log(`${usage()}\n`); process.exit(0); } default: { throw new Error(`Unknown argument: ${arg}`); } } } if (fromGateway === (explicitVersion !== null)) { throw new Error("Choose exactly one of --from-gateway or --version ."); } if (explicitVersion !== null && !explicitVersion.trim()) { throw new Error("Missing value for --version."); } return { explicitVersion, explicitVersionCode, fromGateway, rootDir, sync }; } export function pinAndroidVersion(params: CliOptions): PinAndroidVersionResult { const rootDir = path.resolve(params.rootDir); let previousVersion: string | null; let previousVersionCode: number | null; try { const currentVersion = resolveAndroidVersion(rootDir); previousVersion = currentVersion.canonicalVersion; previousVersionCode = currentVersion.versionCode; } catch { previousVersion = null; previousVersionCode = null; } const gatewayVersion = params.fromGateway ? resolveGatewayVersionForAndroidRelease(rootDir) : null; const packageVersion = gatewayVersion?.packageVersion ?? null; const nextVersion = gatewayVersion?.pinnedAndroidVersion ?? normalizePinnedAndroidVersion(params.explicitVersion ?? ""); const nextVersionCode = params.explicitVersionCode === null ? (gatewayVersion?.versionCode ?? canonicalAndroidVersionCode(nextVersion)) : normalizeAndroidVersionCode(params.explicitVersionCode, nextVersion); const versionFilePath = writeAndroidVersionManifest(nextVersion, nextVersionCode, rootDir); const syncedPaths = params.sync ? syncAndroidVersioning({ mode: "write", rootDir }).updatedPaths : []; return { previousVersion, previousVersionCode, nextVersion, nextVersionCode, packageVersion, versionFilePath, syncedPaths, }; } export async function main(argv: string[]): Promise { try { const options = parseArgs(argv); const result = pinAndroidVersion(options); const sourceText = result.packageVersion ? ` from gateway version ${result.packageVersion}` : ""; process.stdout.write( `Pinned Android version to ${result.nextVersion} (${result.nextVersionCode})${sourceText}.\n`, ); if ( result.previousVersion && (result.previousVersion !== result.nextVersion || result.previousVersionCode !== result.nextVersionCode) ) { process.stdout.write( `Previous pinned Android version: ${result.previousVersion} (${result.previousVersionCode}).\n`, ); } process.stdout.write( `Updated version manifest: ${path.relative(process.cwd(), result.versionFilePath)}\n`, ); if (options.sync) { if (result.syncedPaths.length === 0) { process.stdout.write("Android versioning artifacts already up to date.\n"); } else { process.stdout.write( `Updated Android versioning artifacts:\n- ${result.syncedPaths.map((filePath) => path.relative(process.cwd(), filePath)).join("\n- ")}\n`, ); } } return 0; } catch (error) { process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); return 1; } } if (import.meta.url === `file://${process.argv[1]}`) { const exitCode = await main(process.argv.slice(2)); if (exitCode !== 0) { process.exit(exitCode); } }