From af9edc98e45c6a7336501ddaafa16e2d5d37f1f5 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Sat, 28 Feb 2026 09:54:08 +0530 Subject: [PATCH] fix(release): unify sparkle build policy and defaults --- docs/platforms/mac/release.md | 2 - scripts/package-mac-app.sh | 32 ++------------- scripts/release-check.ts | 54 ++----------------------- scripts/sparkle-build.ts | 76 +++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 80 deletions(-) create mode 100644 scripts/sparkle-build.ts diff --git a/docs/platforms/mac/release.md b/docs/platforms/mac/release.md index 31ee7819a82..696ef8ecb91 100644 --- a/docs/platforms/mac/release.md +++ b/docs/platforms/mac/release.md @@ -38,7 +38,6 @@ Notes: # Default is auto-derived from APP_VERSION when omitted. BUNDLE_ID=ai.openclaw.mac \ APP_VERSION=2026.2.27 \ -APP_BUILD="$(git rev-list --count HEAD)" \ BUILD_CONFIG=release \ SIGN_IDENTITY="Developer ID Application: ()" \ scripts/package-mac-app.sh @@ -56,7 +55,6 @@ scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.2.27.dmg NOTARIZE=1 NOTARYTOOL_PROFILE=openclaw-notary \ BUNDLE_ID=ai.openclaw.mac \ APP_VERSION=2026.2.27 \ -APP_BUILD="$(git rev-list --count HEAD)" \ BUILD_CONFIG=release \ SIGN_IDENTITY="Developer ID Application: ()" \ scripts/package-mac-dist.sh diff --git a/scripts/package-mac-app.sh b/scripts/package-mac-app.sh index 353eb42477e..182eb3f0188 100755 --- a/scripts/package-mac-app.sh +++ b/scripts/package-mac-app.sh @@ -14,6 +14,7 @@ BUILD_TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ") GIT_COMMIT=$(cd "$ROOT_DIR" && git rev-parse --short HEAD 2>/dev/null || echo "unknown") GIT_BUILD_NUMBER=$(cd "$ROOT_DIR" && git rev-list --count HEAD 2>/dev/null || echo "0") APP_VERSION="${APP_VERSION:-$PKG_VERSION}" +APP_BUILD="${APP_BUILD:-}" BUILD_CONFIG="${BUILD_CONFIG:-debug}" BUILD_ARCHS_VALUE="${BUILD_ARCHS:-$(uname -m)}" if [[ "${BUILD_ARCHS_VALUE}" == "all" ]]; then @@ -29,42 +30,17 @@ if [[ "$BUNDLE_ID" == *.debug ]]; then AUTO_CHECKS=false fi -canonical_build_from_version() { - local version="$1" - if [[ "$version" =~ ^([0-9]{4})\.([0-9]{1,2})\.([0-9]{1,2})([.-].*)?$ ]]; then - local year="${BASH_REMATCH[1]}" - local month="${BASH_REMATCH[2]}" - local day="${BASH_REMATCH[3]}" - local month_dec=$((10#$month)) - local day_dec=$((10#$day)) - local suffix="${BASH_REMATCH[4]:-}" - local lane=90 - # Keep stable releases above same-day prereleases so Sparkle can advance beta -> stable. - if [[ -n "$suffix" ]]; then - if [[ "$suffix" =~ ([0-9]+)$ ]]; then - lane=$((10#${BASH_REMATCH[1]})) - if (( lane > 89 )); then - lane=89 - fi - else - lane=1 - fi - fi - printf "%d%02d%02d%02d" "$year" "$month_dec" "$day_dec" "$lane" - return 0 - fi - return 1 +sparkle_canonical_build_from_version() { + node --import tsx "$ROOT_DIR/scripts/sparkle-build.ts" canonical-build "$1" } if [[ -z "${APP_BUILD:-}" ]]; then APP_BUILD="$GIT_BUILD_NUMBER" - if CANONICAL_BUILD="$(canonical_build_from_version "$APP_VERSION")"; then + if CANONICAL_BUILD="$(sparkle_canonical_build_from_version "$APP_VERSION" 2>/dev/null)"; then if [[ "$CANONICAL_BUILD" =~ ^[0-9]+$ ]] && (( CANONICAL_BUILD > APP_BUILD )); then APP_BUILD="$CANONICAL_BUILD" fi fi -else - APP_BUILD="${APP_BUILD}" fi if [[ "$AUTO_CHECKS" == "true" && ! "$APP_BUILD" =~ ^[0-9]+$ ]]; then diff --git a/scripts/release-check.ts b/scripts/release-check.ts index 4ff997690e5..3ed05b4c85f 100755 --- a/scripts/release-check.ts +++ b/scripts/release-check.ts @@ -3,6 +3,7 @@ import { execSync } from "node:child_process"; import { readdirSync, readFileSync } from "node:fs"; import { join, resolve } from "node:path"; +import { sparkleBuildFloorsFromShortVersion, type SparkleBuildFloors } from "./sparkle-build.ts"; type PackFile = { path: string }; type PackResult = { files?: PackFile[] }; @@ -22,12 +23,6 @@ type PackageJson = { version?: string; }; -type CalverSparkleFloors = { - dateKey: number; - legacyFloor: number; - laneFloor: number; -}; - function normalizePluginSyncVersion(version: string): string { const normalized = version.trim().replace(/^v/, ""); const base = /^([0-9]+\.[0-9]+\.[0-9]+)/.exec(normalized)?.[1]; @@ -94,45 +89,6 @@ function checkPluginVersions() { } } -function sparkleFloorsFromShortVersion(shortVersion: string): CalverSparkleFloors | null { - const match = /^([0-9]{4})\.([0-9]{1,2})\.([0-9]{1,2})([.-].*)?$/.exec(shortVersion.trim()); - if (!match) { - return null; - } - const year = Number(match[1]); - const month = Number(match[2]); - const day = Number(match[3]); - if ( - !Number.isInteger(year) || - !Number.isInteger(month) || - !Number.isInteger(day) || - month < 1 || - month > 12 || - day < 1 || - day > 31 - ) { - return null; - } - - const dateKey = Number(`${year}${String(month).padStart(2, "0")}${String(day).padStart(2, "0")}`); - const legacyFloor = Number(`${dateKey}0`); - - // Must stay aligned with canonical_build_from_version in scripts/package-mac-app.sh. - const suffix = match[4] ?? ""; - let lane = 90; - if (suffix.length > 0) { - const numericSuffix = /([0-9]+)$/.exec(suffix)?.[1]; - if (numericSuffix) { - lane = Math.min(Number.parseInt(numericSuffix, 10), 89); - } else { - lane = 1; - } - } - - const laneFloor = Number(`${dateKey}${String(lane).padStart(2, "0")}`); - return { dateKey, legacyFloor, laneFloor }; -} - function extractTag(item: string, tag: string): string | null { const escapedTag = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const regex = new RegExp(`<${escapedTag}>([^<]+)`); @@ -143,7 +99,7 @@ function checkAppcastSparkleVersions() { const xml = readFileSync(appcastPath, "utf8"); const itemMatches = [...xml.matchAll(/([\s\S]*?)<\/item>/g)]; const errors: string[] = []; - const calverItems: Array<{ title: string; sparkleBuild: number; floors: CalverSparkleFloors }> = + const calverItems: Array<{ title: string; sparkleBuild: number; floors: SparkleBuildFloors }> = []; if (itemMatches.length === 0) { @@ -167,13 +123,12 @@ function checkAppcastSparkleVersions() { if (!shortVersion) { continue; } - const floors = sparkleFloorsFromShortVersion(shortVersion); + const floors = sparkleBuildFloorsFromShortVersion(shortVersion); if (floors === null) { continue; } - const sparkleBuild = Number(sparkleVersion); - calverItems.push({ title, sparkleBuild, floors }); + calverItems.push({ title, sparkleBuild: Number(sparkleVersion), floors }); } const adoptionDateKey = calverItems @@ -186,7 +141,6 @@ function checkAppcastSparkleVersions() { item.sparkleBuild >= 1_000_000_000 || (typeof adoptionDateKey === "number" && item.floors.dateKey >= adoptionDateKey); const floor = expectLaneFloor ? item.floors.laneFloor : item.floors.legacyFloor; - if (item.sparkleBuild < floor) { const floorLabel = expectLaneFloor ? "lane floor" : "legacy floor"; errors.push( diff --git a/scripts/sparkle-build.ts b/scripts/sparkle-build.ts new file mode 100644 index 00000000000..0aa1f45a9b6 --- /dev/null +++ b/scripts/sparkle-build.ts @@ -0,0 +1,76 @@ +#!/usr/bin/env -S node --import tsx + +import { pathToFileURL } from "node:url"; + +export type SparkleBuildFloors = { + dateKey: number; + legacyFloor: number; + laneFloor: number; + lane: number; +}; + +const CALVER_REGEX = /^([0-9]{4})\.([0-9]{1,2})\.([0-9]{1,2})([.-].*)?$/; + +export function sparkleBuildFloorsFromShortVersion( + shortVersion: string, +): SparkleBuildFloors | null { + const match = CALVER_REGEX.exec(shortVersion.trim()); + if (!match) { + return null; + } + + const year = Number.parseInt(match[1], 10); + const month = Number.parseInt(match[2], 10); + const day = Number.parseInt(match[3], 10); + if ( + !Number.isInteger(year) || + !Number.isInteger(month) || + !Number.isInteger(day) || + month < 1 || + month > 12 || + day < 1 || + day > 31 + ) { + return null; + } + + const dateKey = Number(`${year}${String(month).padStart(2, "0")}${String(day).padStart(2, "0")}`); + const legacyFloor = Number(`${dateKey}0`); + + let lane = 90; + const suffix = match[4] ?? ""; + if (suffix.length > 0) { + const numericSuffix = /([0-9]+)$/.exec(suffix)?.[1]; + if (numericSuffix) { + lane = Math.min(Number.parseInt(numericSuffix, 10), 89); + } else { + lane = 1; + } + } + + const laneFloor = Number(`${dateKey}${String(lane).padStart(2, "0")}`); + return { dateKey, legacyFloor, laneFloor, lane }; +} + +export function canonicalSparkleBuildFromVersion(shortVersion: string): number | null { + return sparkleBuildFloorsFromShortVersion(shortVersion)?.laneFloor ?? null; +} + +function runCli(args: string[]): number { + const [command, version] = args; + if (command !== "canonical-build" || !version) { + return 1; + } + + const build = canonicalSparkleBuildFromVersion(version); + if (build === null) { + return 1; + } + + console.log(String(build)); + return 0; +} + +if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { + process.exit(runCli(process.argv.slice(2))); +}