mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-27 09:21:35 +07:00
release: automate macOS publishing (#52853)
* release: automate macOS publishing * release: keep mac appcast in openclaw repo * release: add preflight-only release workflow runs * release: keep appcast updates manual * release: generate signed appcast as workflow artifact * release: require preflight before publish * release: require mac app for every release * docs: clarify every release ships mac app * release: document Sparkle feed and SHA rules * release: keep publish flow tag-based * release: stabilize mac appcast flow * release: document local mac fallback
This commit is contained in:
@@ -37,6 +37,15 @@ Use this skill for release and publish-time workflow. Keep ordinary development
|
||||
- For fallback correction tags like `vYYYY.M.D-N`, the repo version locations still stay at `YYYY.M.D`.
|
||||
- “Bump version everywhere” means all version locations above except `appcast.xml`.
|
||||
- Release signing and notary credentials live outside the repo in the private maintainer docs.
|
||||
- Every OpenClaw release ships the npm package and macOS app together.
|
||||
- The production Sparkle feed lives at `https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml`, and the canonical published file is `appcast.xml` on `main` in the `openclaw` repo.
|
||||
- That shared production Sparkle feed is stable-only. Beta mac releases may
|
||||
upload assets to the GitHub prerelease, but they must not replace the shared
|
||||
`appcast.xml` unless a separate beta feed exists.
|
||||
- For fallback correction tags like `vYYYY.M.D-N`, the repo version still stays
|
||||
at `YYYY.M.D`, but the mac release must use a strictly higher numeric
|
||||
`APP_BUILD` / Sparkle build than the original release so existing installs
|
||||
see it as newer.
|
||||
|
||||
## Build changelog-backed release notes
|
||||
|
||||
@@ -68,42 +77,101 @@ OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke
|
||||
|
||||
## Check all relevant release builds
|
||||
|
||||
- Always validate the core npm release path before creating the tag.
|
||||
- Default core release checks:
|
||||
- Always validate the OpenClaw npm release path before creating the tag.
|
||||
- Default release checks:
|
||||
- `pnpm check`
|
||||
- `pnpm build`
|
||||
- `node --import tsx scripts/release-check.ts`
|
||||
- `pnpm release:check`
|
||||
- `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke`
|
||||
- Check all release-related build surfaces touched by the release, not only the npm package.
|
||||
- Include mac release readiness in preflight:
|
||||
- if the release includes mac artifacts, run or inspect the mac packaging/notary/appcast flow
|
||||
- if the release does not include mac artifacts, explicitly confirm that exception before continuing
|
||||
- Include mac release readiness in preflight by running or inspecting the mac
|
||||
packaging, notarization, and appcast flow for every release.
|
||||
- Treat the `appcast.xml` update on `main` as part of mac release readiness, not an optional follow-up.
|
||||
- The workflows remain tag-based. The agent is responsible for making sure
|
||||
preflight runs complete successfully before any publish run starts.
|
||||
- Any fix after preflight means a new commit. Delete and recreate the tag and
|
||||
matching GitHub release from the fixed commit, then rerun preflight from
|
||||
scratch before publishing.
|
||||
- For stable mac releases, generate the signed `appcast.xml` before uploading
|
||||
public release assets so the updater feed cannot lag the published binaries.
|
||||
- Serialize stable appcast-producing runs across tags so two releases do not
|
||||
generate replacement `appcast.xml` files from the same stale seed.
|
||||
- For stable releases, confirm the latest beta already passed the broader release workflows before cutting stable.
|
||||
- If any required build, packaging step, or release workflow is red, do not say the release is ready.
|
||||
|
||||
## Use the right auth flow
|
||||
|
||||
- Core `openclaw` publish uses GitHub trusted publishing.
|
||||
- OpenClaw publish uses GitHub trusted publishing.
|
||||
- The publish run must be started manually with `workflow_dispatch`.
|
||||
- Both release workflows accept `preflight_only=true` to run CI
|
||||
validation/build steps without entering the gated publish job.
|
||||
- npm preflight and macOS preflight must both pass before any publish run
|
||||
starts.
|
||||
- The release workflows stay tag-based; rely on the documented release sequence
|
||||
rather than workflow-level SHA pinning.
|
||||
- The `npm-release` environment must be approved by `@openclaw/openclaw-release-managers` before publish continues.
|
||||
- Do not use `NPM_TOKEN` or the plugin OTP flow for core releases.
|
||||
- Mac publish uses `.github/workflows/macos-release.yml` for build, signing,
|
||||
notarization, stable-feed `appcast.xml` artifact generation, and release-asset
|
||||
upload.
|
||||
- The agent must download the signed `appcast.xml` artifact from a successful
|
||||
stable mac workflow and then update `appcast.xml` on `main`.
|
||||
- For beta mac releases, do not update the shared production `appcast.xml`
|
||||
unless a separate beta Sparkle feed exists.
|
||||
- `.github/workflows/macos-release.yml` still requires the `mac-release`
|
||||
environment approval.
|
||||
- Do not use `NPM_TOKEN` or the plugin OTP flow for OpenClaw releases.
|
||||
- `@openclaw/*` plugin publishes use a separate maintainer-only flow.
|
||||
- Only publish plugins that already exist on npm; bundled disk-tree-only plugins stay unpublished.
|
||||
|
||||
## Fallback local mac publish
|
||||
|
||||
- Keep the original local macOS publish workflow available as a fallback in case
|
||||
CI/CD mac publishing is unavailable or broken.
|
||||
- Preserve the existing maintainer workflow Peter uses: run it on a real Mac
|
||||
with local signing, notary, and Sparkle credentials already configured.
|
||||
- Follow the private maintainer macOS runbook for the local steps:
|
||||
`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`.
|
||||
- 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.
|
||||
|
||||
## Run the release sequence
|
||||
|
||||
1. Confirm the operator explicitly wants to cut a release.
|
||||
2. Choose the exact target version and git tag.
|
||||
3. Make every repo version location match that tag before creating it.
|
||||
4. Update `CHANGELOG.md` and assemble the matching GitHub release notes.
|
||||
5. Run the full preflight for all relevant release builds, including mac readiness when applicable.
|
||||
5. Run the full preflight for all relevant release builds, including mac readiness.
|
||||
6. Confirm the target npm version is not already published.
|
||||
7. Create and push the git tag.
|
||||
8. Create or refresh the matching GitHub release.
|
||||
9. Start `.github/workflows/openclaw-npm-release.yml` with `workflow_dispatch` and the same tag.
|
||||
10. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
|
||||
11. After publish, verify npm and any attached release artifacts.
|
||||
9. Start `.github/workflows/openclaw-npm-release.yml` with `preflight_only=true`
|
||||
and wait for it to pass.
|
||||
10. Start `.github/workflows/macos-release.yml` with `preflight_only=true` and
|
||||
wait for it to pass.
|
||||
11. If either preflight fails, fix the issue on a new commit, delete the tag
|
||||
and matching GitHub release, recreate them from the fixed commit, and rerun
|
||||
both preflights from scratch before continuing. Never reuse old preflight
|
||||
results after the commit changes.
|
||||
12. Start `.github/workflows/openclaw-npm-release.yml` with the same tag for
|
||||
the real publish.
|
||||
13. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
|
||||
14. Start `.github/workflows/macos-release.yml` for the real publish and wait
|
||||
for `mac-release` approval and success.
|
||||
15. For stable releases, let the mac workflow generate the signed
|
||||
`appcast.xml` artifact before it uploads the public mac assets, then
|
||||
download that artifact from the successful run, update `appcast.xml` on
|
||||
`main`, and verify the feed.
|
||||
16. For beta releases, publish the mac assets but expect no shared production
|
||||
`appcast.xml` artifact and do not update the shared production feed unless a
|
||||
separate beta feed exists.
|
||||
17. After publish, verify npm and any attached release artifacts.
|
||||
|
||||
## GHSA advisory work
|
||||
|
||||
|
||||
306
.github/workflows/macos-release.yml
vendored
Normal file
306
.github/workflows/macos-release.yml
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
name: macOS Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Existing release tag to build macOS artifacts for (for example v2026.3.22 or v2026.3.22-beta.1)
|
||||
required: true
|
||||
type: string
|
||||
preflight_only:
|
||||
description: Run validation/build only and skip the gated publish job
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: macos-release-${{ inputs.tag }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.23.0"
|
||||
SPARKLE_FEED_URL: https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml
|
||||
|
||||
jobs:
|
||||
preflight_macos_release:
|
||||
runs-on: macos-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Validate tag input format
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
|
||||
echo "Invalid release tag format: ${RELEASE_TAG}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout selected tag
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: refs/tags/${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Ensure matching GitHub release exists
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null
|
||||
|
||||
- name: Validate release tag and package metadata
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
RELEASE_MAIN_REF: origin/main
|
||||
run: |
|
||||
set -euo pipefail
|
||||
RELEASE_SHA=$(git rev-parse HEAD)
|
||||
export RELEASE_SHA RELEASE_TAG RELEASE_MAIN_REF
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
pnpm release:openclaw:npm:check
|
||||
|
||||
- name: Resolve package version
|
||||
id: package_version
|
||||
run: echo "value=$(node -p 'require(\"./package.json\").version')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check
|
||||
run: pnpm check
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Build Control UI
|
||||
run: node scripts/ui.js build
|
||||
|
||||
- name: Verify release contents
|
||||
run: pnpm release:check
|
||||
|
||||
- name: Swift build
|
||||
run: swift build --package-path apps/macos --configuration release
|
||||
|
||||
- name: Swift test
|
||||
run: swift test --package-path apps/macos --parallel
|
||||
|
||||
- name: Package macOS release with ad-hoc signing
|
||||
env:
|
||||
APP_VERSION: ${{ steps.package_version.outputs.value }}
|
||||
BUNDLE_ID: ai.openclaw.mac
|
||||
BUILD_CONFIG: release
|
||||
CODESIGN_TIMESTAMP: "off"
|
||||
SIGN_IDENTITY: "-"
|
||||
SKIP_NOTARIZE: "1"
|
||||
SKIP_PNPM_INSTALL: "1"
|
||||
SKIP_TSC: "1"
|
||||
SKIP_UI_BUILD: "1"
|
||||
SPARKLE_FEED_URL: ${{ env.SPARKLE_FEED_URL }}
|
||||
run: scripts/package-mac-dist.sh
|
||||
|
||||
publish_macos_release:
|
||||
needs: [preflight_macos_release]
|
||||
if: ${{ !inputs.preflight_only }}
|
||||
runs-on: macos-latest
|
||||
environment: mac-release
|
||||
concurrency:
|
||||
# Stable releases all derive the same shared appcast.xml; serialize those
|
||||
# runs so each artifact starts from the latest stable feed snapshot.
|
||||
group: macos-release-publish-${{ contains(inputs.tag, '-beta.') && inputs.tag || 'stable-feed' }}
|
||||
cancel-in-progress: false
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Validate tag input format
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
|
||||
echo "Invalid release tag format: ${RELEASE_TAG}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout selected tag
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: refs/tags/${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Ensure matching GitHub release exists
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null
|
||||
|
||||
- name: Resolve package version
|
||||
id: package_version
|
||||
run: echo "value=$(node -p 'require(\"./package.json\").version')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Determine release channel
|
||||
id: release_channel
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "$RELEASE_TAG" == *-beta.* ]]; then
|
||||
echo "is_beta=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "is_beta=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Import Developer ID certificate
|
||||
env:
|
||||
MACOS_DEVELOPER_ID_P12_BASE64: ${{ secrets.MACOS_DEVELOPER_ID_P12_BASE64 }}
|
||||
MACOS_DEVELOPER_ID_P12_PASSWORD: ${{ secrets.MACOS_DEVELOPER_ID_P12_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
CERT_PATH="$RUNNER_TEMP/openclaw-macos-release.p12"
|
||||
KEYCHAIN_PATH="$RUNNER_TEMP/openclaw-release.keychain-db"
|
||||
KEYCHAIN_PASSWORD="$(openssl rand -hex 32)"
|
||||
echo "::add-mask::$KEYCHAIN_PASSWORD"
|
||||
export CERT_PATH MACOS_DEVELOPER_ID_P12_BASE64
|
||||
python3 - <<'PY'
|
||||
import base64
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
Path(os.environ["CERT_PATH"]).write_bytes(
|
||||
base64.b64decode(os.environ["MACOS_DEVELOPER_ID_P12_BASE64"])
|
||||
)
|
||||
PY
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security import "$CERT_PATH" \
|
||||
-k "$KEYCHAIN_PATH" \
|
||||
-P "$MACOS_DEVELOPER_ID_P12_PASSWORD" \
|
||||
-T /usr/bin/codesign \
|
||||
-T /usr/bin/security
|
||||
EXISTING_KEYCHAINS="$(security list-keychains -d user | tr -d '"')"
|
||||
security list-keychains -d user -s "$KEYCHAIN_PATH" $EXISTING_KEYCHAINS
|
||||
security default-keychain -d user -s "$KEYCHAIN_PATH"
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Resolve signing identity
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SIGN_IDENTITY="$(security find-identity -p codesigning -v "$KEYCHAIN_PATH" 2>/dev/null | awk -F'\"' '/Developer ID Application/ { print $2; exit }')"
|
||||
if [[ -z "${SIGN_IDENTITY}" ]]; then
|
||||
echo "Developer ID Application identity not found in imported keychain." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "SIGN_IDENTITY=$SIGN_IDENTITY" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Write notary and Sparkle key files
|
||||
env:
|
||||
APP_STORE_CONNECT_API_KEY_P8: ${{ secrets.APP_STORE_CONNECT_API_KEY_P8 }}
|
||||
APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
|
||||
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
||||
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NOTARYTOOL_KEY_PATH="$RUNNER_TEMP/openclaw-notary.p8"
|
||||
SPARKLE_PRIVATE_KEY_PATH="$RUNNER_TEMP/openclaw-sparkle-ed25519.pem"
|
||||
export NOTARYTOOL_KEY_PATH SPARKLE_PRIVATE_KEY_PATH
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
def write_secret(path_env: str, value_env: str) -> None:
|
||||
value = os.environ[value_env].replace("\\n", "\n")
|
||||
Path(os.environ[path_env]).write_text(value, encoding="utf-8")
|
||||
|
||||
write_secret("NOTARYTOOL_KEY_PATH", "APP_STORE_CONNECT_API_KEY_P8")
|
||||
write_secret("SPARKLE_PRIVATE_KEY_PATH", "SPARKLE_PRIVATE_KEY")
|
||||
PY
|
||||
echo "NOTARYTOOL_KEY=$NOTARYTOOL_KEY_PATH" >> "$GITHUB_ENV"
|
||||
echo "NOTARYTOOL_KEY_ID=$APP_STORE_CONNECT_KEY_ID" >> "$GITHUB_ENV"
|
||||
echo "NOTARYTOOL_ISSUER=$APP_STORE_CONNECT_ISSUER_ID" >> "$GITHUB_ENV"
|
||||
echo "SPARKLE_PRIVATE_KEY_FILE=$SPARKLE_PRIVATE_KEY_PATH" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build, sign, notarize, and package macOS release
|
||||
env:
|
||||
APP_VERSION: ${{ steps.package_version.outputs.value }}
|
||||
BUNDLE_ID: ai.openclaw.mac
|
||||
BUILD_CONFIG: release
|
||||
SIGN_IDENTITY: ${{ env.SIGN_IDENTITY }}
|
||||
SKIP_PNPM_INSTALL: "1"
|
||||
SPARKLE_FEED_URL: ${{ env.SPARKLE_FEED_URL }}
|
||||
run: scripts/package-mac-dist.sh
|
||||
|
||||
- name: Checkout main branch for appcast seed
|
||||
if: ${{ steps.release_channel.outputs.is_beta != 'true' }}
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
path: openclaw-main
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Seed appcast from main
|
||||
if: ${{ steps.release_channel.outputs.is_beta != 'true' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
APPCAST_SOURCE="openclaw-main/appcast.xml"
|
||||
if [[ -f "$APPCAST_SOURCE" ]]; then
|
||||
cp "$APPCAST_SOURCE" appcast.xml
|
||||
else
|
||||
echo "No existing appcast at $APPCAST_SOURCE; generating a fresh feed."
|
||||
fi
|
||||
|
||||
- name: Generate signed appcast artifact
|
||||
if: ${{ steps.release_channel.outputs.is_beta != 'true' }}
|
||||
env:
|
||||
SPARKLE_DOWNLOAD_URL_PREFIX: https://github.com/openclaw/openclaw/releases/download/${{ inputs.tag }}/
|
||||
SPARKLE_RELEASE_VERSION: ${{ steps.package_version.outputs.value }}
|
||||
run: scripts/make_appcast.sh "dist/OpenClaw-${{ steps.package_version.outputs.value }}.zip" "${{ env.SPARKLE_FEED_URL }}"
|
||||
|
||||
- name: Upload stable appcast artifact
|
||||
if: ${{ steps.release_channel.outputs.is_beta != 'true' }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: macos-appcast-${{ inputs.tag }}
|
||||
path: appcast.xml
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Skip shared appcast for beta releases
|
||||
if: ${{ steps.release_channel.outputs.is_beta == 'true' }}
|
||||
run: echo "Beta release detected; skip shared production appcast artifact generation."
|
||||
|
||||
- name: Upload macOS assets to GitHub release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
VERSION: ${{ steps.package_version.outputs.value }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh release upload "$RELEASE_TAG" \
|
||||
"dist/OpenClaw-$VERSION.zip" \
|
||||
"dist/OpenClaw-$VERSION.dmg" \
|
||||
"dist/OpenClaw-$VERSION.dSYM.zip" \
|
||||
--clobber \
|
||||
--repo "$GITHUB_REPOSITORY"
|
||||
|
||||
- name: Clean up signing keychain
|
||||
if: always()
|
||||
run: |
|
||||
if [[ -n "${KEYCHAIN_PATH:-}" ]]; then
|
||||
security delete-keychain "$KEYCHAIN_PATH" >/dev/null 2>&1 || true
|
||||
fi
|
||||
69
.github/workflows/openclaw-npm-release.yml
vendored
69
.github/workflows/openclaw-npm-release.yml
vendored
@@ -7,6 +7,11 @@ on:
|
||||
description: Release tag to publish (for example v2026.3.22, v2026.3.22-beta.1, or fallback v2026.3.22-1)
|
||||
required: true
|
||||
type: string
|
||||
preflight_only:
|
||||
description: Run validation/build only and skip the gated publish job
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
|
||||
@@ -18,13 +23,10 @@ env:
|
||||
PNPM_VERSION: "10.23.0"
|
||||
|
||||
jobs:
|
||||
publish_openclaw_npm:
|
||||
# npm trusted publishing + provenance requires a GitHub-hosted runner.
|
||||
preflight_openclaw_npm:
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm-release
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Validate tag input format
|
||||
env:
|
||||
@@ -84,5 +86,64 @@ jobs:
|
||||
- name: Verify release contents
|
||||
run: pnpm release:check
|
||||
|
||||
publish_openclaw_npm:
|
||||
# npm trusted publishing + provenance requires a GitHub-hosted runner.
|
||||
needs: [preflight_openclaw_npm]
|
||||
if: ${{ !inputs.preflight_only }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm-release
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Validate tag input format
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
|
||||
echo "Invalid release tag format: ${RELEASE_TAG}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: refs/tags/${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Validate release tag and package metadata
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
RELEASE_MAIN_REF: origin/main
|
||||
run: |
|
||||
set -euo pipefail
|
||||
RELEASE_SHA=$(git rev-parse HEAD)
|
||||
export RELEASE_SHA RELEASE_TAG RELEASE_MAIN_REF
|
||||
# Fetch the full main ref so merge-base ancestry checks keep working
|
||||
# for older tagged commits that are still contained in main.
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
pnpm release:openclaw:npm:check
|
||||
|
||||
- name: Ensure version is not already published
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PACKAGE_VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
if npm view "openclaw@${PACKAGE_VERSION}" version >/dev/null 2>&1; then
|
||||
echo "openclaw@${PACKAGE_VERSION} is already published on npm."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Publishing openclaw@${PACKAGE_VERSION}"
|
||||
|
||||
- name: Publish
|
||||
run: bash scripts/openclaw-npm-publish.sh --publish
|
||||
|
||||
@@ -23,7 +23,7 @@ OpenClaw has three public release lanes:
|
||||
- Do not zero-pad month or day
|
||||
- `latest` means the current stable npm release
|
||||
- `beta` means the current prerelease npm release
|
||||
- Beta releases may ship before the macOS app catches up
|
||||
- Every OpenClaw release ships the npm package and macOS app together
|
||||
|
||||
## Release cadence
|
||||
|
||||
|
||||
@@ -114,8 +114,12 @@ merge_framework_machos() {
|
||||
done < <(find "$primary" -type f -print0)
|
||||
}
|
||||
|
||||
echo "📦 Ensuring deps (pnpm install)"
|
||||
(cd "$ROOT_DIR" && pnpm install --no-frozen-lockfile --config.node-linker=hoisted)
|
||||
if [[ "${SKIP_PNPM_INSTALL:-0}" != "1" ]]; then
|
||||
echo "📦 Ensuring deps (pnpm install)"
|
||||
(cd "$ROOT_DIR" && pnpm install --no-frozen-lockfile --config.node-linker=hoisted)
|
||||
else
|
||||
echo "📦 Skipping pnpm install (SKIP_PNPM_INSTALL=1)"
|
||||
fi
|
||||
|
||||
if [[ -z "${APP_BUILD:-}" ]]; then
|
||||
APP_BUILD="$GIT_BUILD_NUMBER"
|
||||
|
||||
Reference in New Issue
Block a user