* Add R2 dual-write for nightly appcast and DMGs Upload nightly DMGs and appcast to Cloudflare R2 (files.cmux.com) alongside the existing GitHub Release assets. R2 uses atomic PutObject so the appcast never 404s during replacement, fixing the transient SUDownloadError 2001 that occurs when GitHub Release assets are being overwritten. DMGs are uploaded before the appcast so the feed never references a file that doesn't exist yet. The GitHub Release upload is unchanged, so existing nightly users are unaffected. A follow-up PR will switch the Sparkle feed URL in the app bundle from GitHub Releases to R2 after manual verification. * Add continue-on-error to R2 steps R2 upload failures should not block the existing GitHub Release publish. This keeps the nightly pipeline safe while R2 is new. * Add R2 dual-write for stable release appcast and DMG Same pattern as nightly: upload DMG then appcast to R2 (files.cmux.com/stable/) alongside the GitHub Release. Both steps use continue-on-error so R2 failures can't block the release. * Address review feedback: cache headers, no double build, AWS CLI guard - Add Cache-Control headers: immutable versioned DMGs get max-age=1yr, mutable appcast.xml and latest DMG get no-cache to prevent stale CDN - Replace separate appcast generation step with sed URL replacement, avoiding a second Sparkle clone+build (signature is over DMG content, not the URL) - Add AWS CLI availability check with fallback brew install --------- Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
413 lines
19 KiB
YAML
413 lines
19 KiB
YAML
name: Release macOS app
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*"
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: write
|
|
attestations: write
|
|
id-token: write
|
|
|
|
env:
|
|
CREATE_DMG_VERSION: 8.0.0
|
|
|
|
jobs:
|
|
build-sign-notarize:
|
|
runs-on: warp-macos-26-arm64-6x
|
|
timeout-minutes: 20
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Guard immutable release assets
|
|
id: guard_release_assets
|
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
|
|
with:
|
|
script: |
|
|
const { evaluateReleaseAssetGuard } = require('./scripts/release_asset_guard');
|
|
const tag = context.ref.replace('refs/tags/', '');
|
|
core.setOutput('skip_all', 'false');
|
|
core.setOutput('skip_upload', 'false');
|
|
core.setOutput('release_state', 'clear');
|
|
try {
|
|
const release = await github.rest.repos.getReleaseByTag({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
tag,
|
|
});
|
|
const existingAssetNames = (release.data.assets || []).map((asset) => asset.name);
|
|
const {
|
|
conflicts,
|
|
missingImmutableAssets,
|
|
guardState,
|
|
hasPartialConflict,
|
|
shouldSkipBuildAndUpload,
|
|
} = evaluateReleaseAssetGuard({ existingAssetNames });
|
|
|
|
core.setOutput('release_state', guardState);
|
|
|
|
if (hasPartialConflict) {
|
|
core.setFailed(
|
|
`Release ${tag} has a partial immutable asset state. Existing immutable assets: ` +
|
|
`${conflicts.join(', ')}. Missing immutable assets: ${missingImmutableAssets.join(', ')}. ` +
|
|
'Resolve release assets manually before rerunning.'
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (shouldSkipBuildAndUpload) {
|
|
core.notice(
|
|
`Release ${tag} already contains immutable assets (${conflicts.join(', ')}). ` +
|
|
'Skipping build, notarization, and upload to preserve existing signed artifacts.'
|
|
);
|
|
core.setOutput('skip_all', 'true');
|
|
core.setOutput('skip_upload', 'true');
|
|
return;
|
|
}
|
|
|
|
core.notice(`Release ${tag} exists but has no immutable release assets yet; continuing.`);
|
|
} catch (error) {
|
|
if (error.status === 404) {
|
|
core.notice(`Release ${tag} does not exist yet; safe to build and publish assets.`);
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
- name: Select Xcode
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -d "/Applications/Xcode.app/Contents/Developer" ]; then
|
|
XCODE_DIR="/Applications/Xcode.app/Contents/Developer"
|
|
else
|
|
XCODE_APP="$(ls -d /Applications/Xcode*.app 2>/dev/null | head -n 1 || true)"
|
|
if [ -n "$XCODE_APP" ]; then
|
|
XCODE_DIR="$XCODE_APP/Contents/Developer"
|
|
else
|
|
echo "No Xcode.app found under /Applications" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
echo "DEVELOPER_DIR=$XCODE_DIR" >> "$GITHUB_ENV"
|
|
export DEVELOPER_DIR="$XCODE_DIR"
|
|
xcodebuild -version
|
|
xcrun --sdk macosx --show-sdk-path
|
|
|
|
- name: Install build deps
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
run: |
|
|
ZIG_REQUIRED="0.15.2"
|
|
if command -v zig >/dev/null 2>&1 && zig version 2>/dev/null | grep -q "^${ZIG_REQUIRED}"; then
|
|
echo "zig ${ZIG_REQUIRED} already installed"
|
|
else
|
|
echo "Installing zig ${ZIG_REQUIRED} from tarball"
|
|
curl -fSL "https://ziglang.org/download/${ZIG_REQUIRED}/zig-aarch64-macos-${ZIG_REQUIRED}.tar.xz" -o /tmp/zig.tar.xz
|
|
tar xf /tmp/zig.tar.xz -C /tmp
|
|
sudo mkdir -p /usr/local/bin /usr/local/lib
|
|
sudo cp -f /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/zig /usr/local/bin/zig
|
|
sudo cp -rf /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/lib /usr/local/lib/zig
|
|
export PATH="/usr/local/bin:$PATH"
|
|
zig version
|
|
fi
|
|
npm install --global "create-dmg@${CREATE_DMG_VERSION}"
|
|
|
|
- name: Download pre-built GhosttyKit.xcframework
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
run: |
|
|
./scripts/download-prebuilt-ghosttykit.sh
|
|
|
|
- name: Cache Swift packages
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
|
|
with:
|
|
path: .spm-cache
|
|
key: spm-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}
|
|
restore-keys: spm-
|
|
|
|
- name: Setup Go
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
|
with:
|
|
go-version-file: daemon/remote/go.mod
|
|
|
|
- name: Derive Sparkle public key from private key
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
env:
|
|
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
|
|
run: |
|
|
if [ -z "$SPARKLE_PRIVATE_KEY" ]; then
|
|
echo "Missing SPARKLE_PRIVATE_KEY secret" >&2
|
|
exit 1
|
|
fi
|
|
DERIVED_PUBLIC_KEY=$(swift scripts/derive_sparkle_public_key.swift "$SPARKLE_PRIVATE_KEY")
|
|
echo "Derived Sparkle public key: $DERIVED_PUBLIC_KEY"
|
|
echo "SPARKLE_PUBLIC_KEY=$DERIVED_PUBLIC_KEY" >> "$GITHUB_ENV"
|
|
|
|
- name: Build universal app (Release)
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
run: |
|
|
xcodebuild -scheme cmux -configuration Release -derivedDataPath build-universal \
|
|
-destination 'generic/platform=macOS' \
|
|
-clonedSourcePackagesDirPath .spm-cache \
|
|
ARCHS="arm64 x86_64" \
|
|
ONLY_ACTIVE_ARCH=NO \
|
|
CODE_SIGNING_ALLOWED=NO build
|
|
|
|
- name: Verify binary architectures
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
APP_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/MacOS/cmux"
|
|
CLI_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/cmux"
|
|
HELPER_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/ghostty"
|
|
APP_ARCHS="$(lipo -archs "$APP_BINARY")"
|
|
CLI_ARCHS="$(lipo -archs "$CLI_BINARY")"
|
|
HELPER_ARCHS="$(lipo -archs "$HELPER_BINARY")"
|
|
echo "App binary architectures: $APP_ARCHS"
|
|
echo "CLI binary architectures: $CLI_ARCHS"
|
|
echo "Ghostty helper architectures: $HELPER_ARCHS"
|
|
[[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]]
|
|
[[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]]
|
|
[[ "$HELPER_ARCHS" == *arm64* && "$HELPER_ARCHS" == *x86_64* ]]
|
|
|
|
- name: Build remote daemon release assets and inject manifest
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
APP_PLIST="build-universal/Build/Products/Release/cmux.app/Contents/Info.plist"
|
|
APP_VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$APP_PLIST")
|
|
./scripts/build_remote_daemon_release_assets.sh \
|
|
--version "$APP_VERSION" \
|
|
--release-tag "$GITHUB_REF_NAME" \
|
|
--repo "manaflow-ai/cmux" \
|
|
--output-dir "remote-daemon-assets"
|
|
MANIFEST_JSON="$(python3 -c 'import json,sys; print(json.dumps(json.load(open(sys.argv[1], encoding="utf-8")), separators=(",",":")))' remote-daemon-assets/cmuxd-remote-manifest.json)"
|
|
plutil -remove CMUXRemoteDaemonManifestJSON "$APP_PLIST" >/dev/null 2>&1 || true
|
|
plutil -insert CMUXRemoteDaemonManifestJSON -string "$MANIFEST_JSON" "$APP_PLIST"
|
|
|
|
- name: Run CLI version memory guard regression
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
CLI_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/cmux"
|
|
[ -x "$CLI_BINARY" ] || { echo "cmux CLI binary not found at $CLI_BINARY" >&2; exit 1; }
|
|
CMUX_CLI_BIN="$CLI_BINARY" python3 tests/test_cli_version_memory_guard.py
|
|
|
|
- name: Verify bundled Ghostty theme picker helper
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
HELPER_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/ghostty"
|
|
[ -x "$HELPER_BINARY" ] || { echo "Ghostty theme picker helper not found at $HELPER_BINARY" >&2; exit 1; }
|
|
|
|
- name: Inject Sparkle keys into Info.plist
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
run: |
|
|
APP_PLIST="build-universal/Build/Products/Release/cmux.app/Contents/Info.plist"
|
|
/usr/libexec/PlistBuddy -c "Delete :SUPublicEDKey" "$APP_PLIST" >/dev/null 2>&1 || true
|
|
/usr/libexec/PlistBuddy -c "Delete :SUFeedURL" "$APP_PLIST" >/dev/null 2>&1 || true
|
|
echo "Adding SUPublicEDKey to Info.plist..."
|
|
/usr/libexec/PlistBuddy -c "Add :SUPublicEDKey string ${SPARKLE_PUBLIC_KEY}" "$APP_PLIST"
|
|
echo "Adding SUFeedURL to Info.plist..."
|
|
/usr/libexec/PlistBuddy -c "Add :SUFeedURL string https://github.com/manaflow-ai/cmux/releases/latest/download/appcast.xml" "$APP_PLIST"
|
|
echo "Verifying:"
|
|
/usr/libexec/PlistBuddy -c "Print :SUPublicEDKey" "$APP_PLIST"
|
|
/usr/libexec/PlistBuddy -c "Print :SUFeedURL" "$APP_PLIST"
|
|
|
|
- name: Import signing cert
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
env:
|
|
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
run: |
|
|
if [ -z "$APPLE_CERTIFICATE_BASE64" ]; then
|
|
echo "Missing APPLE_CERTIFICATE_BASE64 secret" >&2
|
|
exit 1
|
|
fi
|
|
if [ -z "$APPLE_CERTIFICATE_PASSWORD" ]; then
|
|
echo "Missing APPLE_CERTIFICATE_PASSWORD secret" >&2
|
|
exit 1
|
|
fi
|
|
KEYCHAIN_PASSWORD="$(uuidgen)"
|
|
echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > /tmp/cert.p12
|
|
security delete-keychain build.keychain >/dev/null 2>&1 || true
|
|
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
|
security set-keychain-settings -lut 21600 build.keychain
|
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
|
security import /tmp/cert.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
|
|
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
|
security list-keychains -d user -s build.keychain
|
|
|
|
- name: Codesign app
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
env:
|
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
run: |
|
|
if [ -z "$APPLE_SIGNING_IDENTITY" ]; then
|
|
echo "Missing APPLE_SIGNING_IDENTITY secret" >&2
|
|
exit 1
|
|
fi
|
|
APP_PATH="build-universal/Build/Products/Release/cmux.app"
|
|
ENTITLEMENTS="cmux.entitlements"
|
|
CLI_PATH="$APP_PATH/Contents/Resources/bin/cmux"
|
|
HELPER_PATH="$APP_PATH/Contents/Resources/bin/ghostty"
|
|
if [ -f "$CLI_PATH" ]; then
|
|
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" "$CLI_PATH"
|
|
fi
|
|
if [ -f "$HELPER_PATH" ]; then
|
|
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" "$HELPER_PATH"
|
|
fi
|
|
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" --deep "$APP_PATH"
|
|
/usr/bin/codesign --verify --deep --strict --verbose=2 "$APP_PATH"
|
|
|
|
- name: Notarize app
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
env:
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
run: |
|
|
if [ -z "$APPLE_ID" ] || [ -z "$APPLE_APP_SPECIFIC_PASSWORD" ] || [ -z "$APPLE_TEAM_ID" ]; then
|
|
echo "Missing notarization secrets (APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID)" >&2
|
|
exit 1
|
|
fi
|
|
APP_PATH="build-universal/Build/Products/Release/cmux.app"
|
|
ZIP_SUBMIT="cmux-notary.zip"
|
|
DMG_RELEASE="cmux-macos.dmg"
|
|
ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "$ZIP_SUBMIT"
|
|
APP_SUBMIT_JSON="$(xcrun notarytool submit "$ZIP_SUBMIT" --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --wait --output-format json)"
|
|
APP_SUBMIT_ID="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"$APP_SUBMIT_JSON")"
|
|
APP_STATUS="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["status"])' <<<"$APP_SUBMIT_JSON")"
|
|
if [ "$APP_STATUS" != "Accepted" ]; then
|
|
echo "App notarization failed with status: $APP_STATUS" >&2
|
|
xcrun notarytool log "$APP_SUBMIT_ID" --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" || true
|
|
exit 1
|
|
fi
|
|
xcrun stapler staple "$APP_PATH"
|
|
xcrun stapler validate "$APP_PATH"
|
|
spctl -a -vv --type execute "$APP_PATH"
|
|
rm -f "$ZIP_SUBMIT"
|
|
# create-dmg generates a styled drag-to-install DMG
|
|
create-dmg \
|
|
--identity="$APPLE_SIGNING_IDENTITY" \
|
|
"$APP_PATH" \
|
|
./
|
|
mv ./cmux*.dmg "$DMG_RELEASE"
|
|
DMG_SUBMIT_JSON="$(xcrun notarytool submit "$DMG_RELEASE" --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --wait --output-format json)"
|
|
DMG_SUBMIT_ID="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"$DMG_SUBMIT_JSON")"
|
|
DMG_STATUS="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["status"])' <<<"$DMG_SUBMIT_JSON")"
|
|
if [ "$DMG_STATUS" != "Accepted" ]; then
|
|
echo "DMG notarization failed with status: $DMG_STATUS" >&2
|
|
xcrun notarytool log "$DMG_SUBMIT_ID" --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" || true
|
|
exit 1
|
|
fi
|
|
xcrun stapler staple "$DMG_RELEASE"
|
|
xcrun stapler validate "$DMG_RELEASE"
|
|
|
|
- name: Upload dSYMs to Sentry
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
env:
|
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
SENTRY_ORG: manaflow
|
|
SENTRY_PROJECT: cmuxterm-macos
|
|
run: |
|
|
if [ -z "$SENTRY_AUTH_TOKEN" ]; then
|
|
echo "SENTRY_AUTH_TOKEN not set, skipping dSYM upload"
|
|
exit 0
|
|
fi
|
|
brew install getsentry/tools/sentry-cli || true
|
|
sentry-cli debug-files upload --include-sources build-universal/Build/Products/Release/
|
|
|
|
- name: Generate Sparkle appcast
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
env:
|
|
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
|
|
run: |
|
|
if [ -z "$SPARKLE_PRIVATE_KEY" ]; then
|
|
echo "Missing SPARKLE_PRIVATE_KEY secret" >&2
|
|
exit 1
|
|
fi
|
|
./scripts/sparkle_generate_appcast.sh cmux-macos.dmg "$GITHUB_REF_NAME" appcast.xml
|
|
|
|
- name: Upload release assets to R2
|
|
if: steps.guard_release_assets.outputs.skip_upload != 'true' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
|
continue-on-error: true
|
|
env:
|
|
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
|
AWS_DEFAULT_REGION: auto
|
|
R2_ENDPOINT: "https://${{ secrets.CF_R2_ACCOUNT_ID }}.r2.cloudflarestorage.com"
|
|
run: |
|
|
set -euo pipefail
|
|
command -v aws >/dev/null 2>&1 || { echo "Installing AWS CLI..."; brew install awscli; }
|
|
BUCKET=cmux-binaries
|
|
|
|
# Derive R2 appcast by replacing the download URL prefix.
|
|
sed "s|https://github.com/manaflow-ai/cmux/releases/download/${GITHUB_REF_NAME}/|https://files.cmux.com/stable/|g" \
|
|
appcast.xml > appcast-r2.xml
|
|
|
|
# Upload DMG first so the appcast never references a missing file.
|
|
aws s3 cp cmux-macos.dmg \
|
|
"s3://${BUCKET}/stable/cmux-macos.dmg" \
|
|
--endpoint-url "$R2_ENDPOINT" \
|
|
--cache-control "no-cache, no-store, must-revalidate"
|
|
|
|
# Upload appcast last (atomic PutObject, no 404 window).
|
|
aws s3 cp appcast-r2.xml \
|
|
"s3://${BUCKET}/stable/appcast.xml" \
|
|
--endpoint-url "$R2_ENDPOINT" \
|
|
--cache-control "no-cache, no-store, must-revalidate"
|
|
|
|
echo "R2 upload complete: https://files.cmux.com/stable/appcast.xml"
|
|
|
|
- name: Attest remote daemon release assets
|
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
|
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
|
with:
|
|
subject-path: |
|
|
remote-daemon-assets/cmuxd-remote-darwin-arm64
|
|
remote-daemon-assets/cmuxd-remote-darwin-amd64
|
|
remote-daemon-assets/cmuxd-remote-linux-arm64
|
|
remote-daemon-assets/cmuxd-remote-linux-amd64
|
|
remote-daemon-assets/cmuxd-remote-checksums.txt
|
|
remote-daemon-assets/cmuxd-remote-manifest.json
|
|
|
|
- name: Upload build artifacts (dry-run)
|
|
if: steps.guard_release_assets.outputs.skip_upload != 'true' && github.event_name == 'workflow_dispatch'
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
with:
|
|
name: cmux-release-dry-run
|
|
path: |
|
|
cmux-macos.dmg
|
|
appcast.xml
|
|
remote-daemon-assets/cmuxd-remote-*
|
|
if-no-files-found: error
|
|
|
|
- name: Upload release asset
|
|
if: steps.guard_release_assets.outputs.skip_upload != 'true' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
|
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
|
|
with:
|
|
files: |
|
|
cmux-macos.dmg
|
|
appcast.xml
|
|
remote-daemon-assets/cmuxd-remote-darwin-arm64
|
|
remote-daemon-assets/cmuxd-remote-darwin-amd64
|
|
remote-daemon-assets/cmuxd-remote-linux-arm64
|
|
remote-daemon-assets/cmuxd-remote-linux-amd64
|
|
remote-daemon-assets/cmuxd-remote-checksums.txt
|
|
remote-daemon-assets/cmuxd-remote-manifest.json
|
|
generate_release_notes: true
|
|
overwrite_files: false
|
|
|
|
- name: Cleanup keychain
|
|
if: always()
|
|
run: |
|
|
security delete-keychain build.keychain >/dev/null 2>&1 || true
|
|
rm -f /tmp/cert.p12
|