mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-27 09:21:35 +07:00
build: harden local release verification
This commit is contained in:
@@ -164,12 +164,24 @@ OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke
|
||||
`scripts/package-mac-dist.sh` to build, sign, notarize, and package the app;
|
||||
manual GitHub release asset upload; then `scripts/make_appcast.sh` plus the
|
||||
`appcast.xml` commit to `main`.
|
||||
- `scripts/package-mac-dist.sh` now fails closed for release builds if the
|
||||
bundled app comes out with a debug bundle id, an empty Sparkle feed URL, or a
|
||||
`CFBundleVersion` below the canonical Sparkle build floor for that short
|
||||
version. For correction tags, set a higher explicit `APP_BUILD`.
|
||||
- `scripts/make_appcast.sh` first uses `generate_appcast` from `PATH`, then
|
||||
falls back to the SwiftPM Sparkle tool output under `apps/macos/.build`.
|
||||
- For stable tags, the local fallback may update the shared production
|
||||
`appcast.xml`.
|
||||
- For beta tags, the local fallback still publishes the mac assets but must not
|
||||
update the shared production `appcast.xml` unless a separate beta feed exists.
|
||||
- Treat the local workflow as fallback only. Prefer the CI/CD publish workflow
|
||||
when it is working.
|
||||
- After any stable mac publish, verify all of the following before you call the
|
||||
release finished:
|
||||
- the GitHub release has `.zip`, `.dmg`, and `.dSYM.zip` assets
|
||||
- `appcast.xml` on `main` points at the new stable zip
|
||||
- the packaged app reports the expected short version and a numeric
|
||||
`CFBundleVersion` at or above the canonical Sparkle build floor
|
||||
|
||||
## Run the release sequence
|
||||
|
||||
|
||||
@@ -34,17 +34,27 @@ OpenClaw has three public release lanes:
|
||||
|
||||
## Release preflight
|
||||
|
||||
- Run `pnpm build` before `pnpm release:check` so the expected `dist/*` release
|
||||
artifacts exist for the pack validation step
|
||||
- Run `pnpm release:check` before every tagged release
|
||||
- Run `RELEASE_TAG=vYYYY.M.D node --import tsx scripts/openclaw-npm-release-check.ts`
|
||||
(or the matching beta/correction tag) before approval
|
||||
- npm release preflight fails closed unless the tarball includes both
|
||||
`dist/control-ui/index.html` and a non-empty `dist/control-ui/assets/` payload
|
||||
so we do not ship an empty browser dashboard again
|
||||
- Stable macOS release readiness also includes the updater surfaces:
|
||||
- the GitHub release must end up with the packaged `.zip`, `.dmg`, and `.dSYM.zip`
|
||||
- `appcast.xml` on `main` must point at the new stable zip after publish
|
||||
- the packaged app must keep a non-debug bundle id, a non-empty Sparkle feed
|
||||
URL, and a `CFBundleVersion` at or above the canonical Sparkle build floor
|
||||
for that release version
|
||||
|
||||
## Public references
|
||||
|
||||
- [`.github/workflows/openclaw-npm-release.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/openclaw-npm-release.yml)
|
||||
- [`scripts/openclaw-npm-release-check.ts`](https://github.com/openclaw/openclaw/blob/main/scripts/openclaw-npm-release-check.ts)
|
||||
- [`scripts/package-mac-dist.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/package-mac-dist.sh)
|
||||
- [`scripts/make_appcast.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/make_appcast.sh)
|
||||
|
||||
Maintainers use the private release docs in
|
||||
[`openclaw/maintainers/release/README.md`](https://github.com/openclaw/maintainers/blob/main/release/README.md)
|
||||
|
||||
@@ -5,6 +5,16 @@ ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
ZIP=${1:?"Usage: $0 OpenClaw-<ver>.zip"}
|
||||
FEED_URL=${2:-"https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml"}
|
||||
PRIVATE_KEY_FILE=${SPARKLE_PRIVATE_KEY_FILE:-}
|
||||
|
||||
find_generate_appcast() {
|
||||
if command -v generate_appcast >/dev/null 2>&1; then
|
||||
command -v generate_appcast
|
||||
return 0
|
||||
fi
|
||||
|
||||
find "$ROOT/apps/macos/.build" -type f -path "*/artifacts/sparkle/Sparkle/bin/generate_appcast" -print -quit 2>/dev/null
|
||||
}
|
||||
|
||||
if [[ -z "$PRIVATE_KEY_FILE" ]]; then
|
||||
echo "Set SPARKLE_PRIVATE_KEY_FILE to your ed25519 private key (Sparkle)." >&2
|
||||
exit 1
|
||||
@@ -52,13 +62,13 @@ cp -f "$NOTES_HTML" "$TMP_DIR/${ZIP_BASE}.html"
|
||||
|
||||
DOWNLOAD_URL_PREFIX=${SPARKLE_DOWNLOAD_URL_PREFIX:-"https://github.com/openclaw/openclaw/releases/download/v${VERSION}/"}
|
||||
|
||||
export PATH="$ROOT/apps/macos/.build/artifacts/sparkle/Sparkle/bin:$PATH"
|
||||
if ! command -v generate_appcast >/dev/null; then
|
||||
echo "generate_appcast not found in PATH. Build Sparkle tools via SwiftPM." >&2
|
||||
GENERATE_APPCAST="$(find_generate_appcast)"
|
||||
if [[ -z "$GENERATE_APPCAST" ]]; then
|
||||
echo "generate_appcast not found. Install Sparkle tooling or build the mac app first so SwiftPM emits the Sparkle binaries." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
generate_appcast \
|
||||
"$GENERATE_APPCAST" \
|
||||
--ed-key-file "$PRIVATE_KEY_FILE" \
|
||||
--download-url-prefix "$DOWNLOAD_URL_PREFIX" \
|
||||
--embed-release-notes \
|
||||
|
||||
@@ -12,14 +12,29 @@ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BUILD_ROOT="$ROOT_DIR/apps/macos/.build"
|
||||
PRODUCT="OpenClaw"
|
||||
BUILD_CONFIG="${BUILD_CONFIG:-release}"
|
||||
APP_VERSION_INPUT="${APP_VERSION:-$(cd "$ROOT_DIR" && node -p "require('./package.json').version" 2>/dev/null || echo "0.0.0")}"
|
||||
|
||||
# Default to universal binary for distribution builds (supports both Apple Silicon and Intel Macs)
|
||||
export BUILD_ARCHS="${BUILD_ARCHS:-all}"
|
||||
export BUILD_CONFIG
|
||||
|
||||
# Use release bundle ID (not .debug) so Sparkle auto-update works.
|
||||
# The .debug suffix in package-mac-app.sh blanks SUFeedURL intentionally for dev builds.
|
||||
export BUNDLE_ID="${BUNDLE_ID:-ai.openclaw.mac}"
|
||||
|
||||
canonical_sparkle_build() {
|
||||
node --import tsx "$ROOT_DIR/scripts/sparkle-build.ts" canonical-build "$1"
|
||||
}
|
||||
|
||||
# Local fallback releases must not silently fall back to a git-rev-count build number.
|
||||
# For correction tags, pass a higher explicit APP_BUILD than the canonical floor.
|
||||
if [[ -z "${APP_BUILD:-}" && "$BUILD_CONFIG" == "release" ]]; then
|
||||
CANONICAL_APP_BUILD="$(canonical_sparkle_build "$APP_VERSION_INPUT" 2>/dev/null || true)"
|
||||
if [[ "$CANONICAL_APP_BUILD" =~ ^[0-9]+$ ]]; then
|
||||
export APP_BUILD="$CANONICAL_APP_BUILD"
|
||||
fi
|
||||
fi
|
||||
|
||||
"$ROOT_DIR/scripts/package-mac-app.sh"
|
||||
|
||||
APP="$ROOT_DIR/dist/OpenClaw.app"
|
||||
@@ -29,6 +44,9 @@ if [[ ! -d "$APP" ]]; then
|
||||
fi
|
||||
|
||||
VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "$APP/Contents/Info.plist" 2>/dev/null || echo "0.0.0")
|
||||
BUNDLE_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$APP/Contents/Info.plist" 2>/dev/null || echo "")
|
||||
ACTUAL_BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" "$APP/Contents/Info.plist" 2>/dev/null || echo "")
|
||||
ACTUAL_FEED_URL=$(/usr/libexec/PlistBuddy -c "Print SUFeedURL" "$APP/Contents/Info.plist" 2>/dev/null || echo "")
|
||||
ZIP="$ROOT_DIR/dist/OpenClaw-$VERSION.zip"
|
||||
DMG="$ROOT_DIR/dist/OpenClaw-$VERSION.dmg"
|
||||
NOTARY_ZIP="$ROOT_DIR/dist/OpenClaw-$VERSION.notary.zip"
|
||||
@@ -42,6 +60,31 @@ if [[ "$SKIP_NOTARIZE" == "1" ]]; then
|
||||
NOTARIZE=0
|
||||
fi
|
||||
|
||||
if [[ "$BUILD_CONFIG" == "release" ]]; then
|
||||
if [[ "$ACTUAL_BUNDLE_ID" == *.debug ]]; then
|
||||
echo "Error: release packaging produced debug bundle id '$ACTUAL_BUNDLE_ID'." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$ACTUAL_FEED_URL" ]]; then
|
||||
echo "Error: release packaging produced an empty SUFeedURL." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CANONICAL_APP_BUILD="$(canonical_sparkle_build "$VERSION" 2>/dev/null || true)"
|
||||
if [[ "$CANONICAL_APP_BUILD" =~ ^[0-9]+$ ]]; then
|
||||
if [[ ! "$BUNDLE_VERSION" =~ ^[0-9]+$ ]]; then
|
||||
echo "Error: release packaging produced non-numeric CFBundleVersion '$BUNDLE_VERSION'." >&2
|
||||
exit 1
|
||||
fi
|
||||
if (( BUNDLE_VERSION < CANONICAL_APP_BUILD )); then
|
||||
echo "Error: CFBundleVersion '$BUNDLE_VERSION' is below the canonical Sparkle floor '$CANONICAL_APP_BUILD' for '$VERSION'." >&2
|
||||
echo "Set APP_BUILD explicitly only when you need a higher correction build." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$NOTARIZE" == "1" ]]; then
|
||||
echo "📦 Notary zip: $NOTARY_ZIP"
|
||||
rm -f "$NOTARY_ZIP"
|
||||
|
||||
@@ -329,6 +329,18 @@ async function main() {
|
||||
for (const path of missing) {
|
||||
console.error(` - ${path}`);
|
||||
}
|
||||
if (
|
||||
missing.some(
|
||||
(path) =>
|
||||
path === "dist/build-info.json" ||
|
||||
path === "dist/control-ui/index.html" ||
|
||||
path.startsWith("dist/"),
|
||||
)
|
||||
) {
|
||||
console.error(
|
||||
"release-check: build artifacts are missing. Run `pnpm build` before `pnpm release:check`.",
|
||||
);
|
||||
}
|
||||
}
|
||||
if (forbidden.length > 0) {
|
||||
console.error("release-check: forbidden files in npm pack:");
|
||||
|
||||
Reference in New Issue
Block a user