mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
Merge ci/macos-app-release: build, sign, notarize and attach Codeman.dmg on every release
release.yml and docs/releasing.md take the branch's text, which is master's text plus the macOS app additions (its first commit copied both files from master). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
31ce0c88cd
8 files changed
+718
-13
No files matched your search
@@ -0,0 +1,72 @@
|
||||
name: Desktop app
|
||||
|
||||
# Checks the native macOS app in packages/desktop on the pull requests and master
|
||||
# pushes that change it: formatting, clippy and the unit tests, on macOS. No
|
||||
# secret and a read-only token, because cargo runs the build script of every
|
||||
# crate in Cargo.lock. The release build, signing and notarization live in
|
||||
# desktop-dmg.yml.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'packages/desktop/**'
|
||||
- '.github/workflows/desktop-ci.yml'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'packages/desktop/**'
|
||||
- '.github/workflows/desktop-ci.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: desktop-ci-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# Keep in step with desktop-dmg.yml, which builds the release with it.
|
||||
RUST_TOOLCHAIN: '1.99.1'
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: fmt, clippy, test
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 45
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/desktop
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Rust toolchain
|
||||
run: |
|
||||
rustup toolchain install "$RUST_TOOLCHAIN" --profile minimal --component clippy --component rustfmt
|
||||
rustup default "$RUST_TOOLCHAIN"
|
||||
rustc --version
|
||||
cargo --version
|
||||
|
||||
- name: Format
|
||||
run: cargo fmt --check
|
||||
|
||||
# A test build bakes another installer in at compile time (option_env! in
|
||||
# setup.rs), so neither variable may reach cargo.
|
||||
- name: Clippy
|
||||
run: |
|
||||
unset CODEMAN_DESKTOP_INSTALLER_URL CODEMAN_DESKTOP_INSTALL_BRANCH
|
||||
cargo clippy --locked --all-targets -- -D warnings
|
||||
|
||||
- name: Unit tests
|
||||
run: |
|
||||
unset CODEMAN_DESKTOP_INSTALLER_URL CODEMAN_DESKTOP_INSTALL_BRANCH
|
||||
cargo test --locked
|
||||
|
||||
# /bin/bash on macOS is 3.2, the bash these scripts run under on a Mac.
|
||||
- name: Scripts parse on bash 3.2
|
||||
run: |
|
||||
for script in scripts/*.sh; do
|
||||
/bin/bash -n "$script"
|
||||
done
|
||||
@@ -0,0 +1,337 @@
|
||||
name: macOS app
|
||||
|
||||
# Builds Codeman.app (Apple Silicon + Intel), signs it with the Developer ID,
|
||||
# has Apple notarize the app and the DMG, and attaches Codeman.dmg and
|
||||
# Codeman.dmg.sha256 to a Codeman release. The asset name never changes, so
|
||||
# https://github.com/Ark0N/Codeman/releases/latest/download/Codeman.dmg always
|
||||
# serves the newest app.
|
||||
#
|
||||
# Release calls this when npm took a Codeman release, with tag codeman@<version>,
|
||||
# and it builds the release commit. By hand, once this file is on master:
|
||||
# gh workflow run desktop-dmg.yml # dry run of master: artifacts only
|
||||
# gh workflow run desktop-dmg.yml --ref <branch> # dry run of a branch that carries this file
|
||||
# gh workflow run desktop-dmg.yml -f tag=codeman@1.50.0 # rebuild that tag, replace its Codeman.dmg
|
||||
# Only a run on master (or Release itself) attaches anything; every run leaves
|
||||
# the signed Codeman.dmg as an artifact on its run page.
|
||||
#
|
||||
# Three jobs, so no job that holds a secret runs code from a crate:
|
||||
# - build runs cargo, and with it the build script of every crate in
|
||||
# Cargo.lock. It has a read-only token and nothing else: no secret, no cache.
|
||||
# - sign gets the built app as an artifact and runs only Apple's tools and this
|
||||
# repository's scripts on it. It alone holds the signing and notary secrets.
|
||||
# - attach has the one token that can write to a release, and no secret.
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
tag:
|
||||
description: Release to attach Codeman.dmg to (codeman@x.y.z); empty attaches nothing
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
secrets:
|
||||
MACOS_CERT_P12:
|
||||
description: The Developer ID Application certificate with its private key, a base64 .p12
|
||||
required: true
|
||||
MACOS_CERT_PASSWORD:
|
||||
description: The .p12's password
|
||||
required: true
|
||||
APPLE_ID:
|
||||
description: Apple ID of the developer team, for the notary service
|
||||
required: true
|
||||
APPLE_APP_PASSWORD:
|
||||
description: An app-specific password for that Apple ID
|
||||
required: true
|
||||
APPLE_TEAM_ID:
|
||||
description: The team ID the certificate belongs to
|
||||
required: true
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to rebuild and attach to, e.g. codeman@1.50.0. Empty: dry run, artifacts only'
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
# What the macos-26 image and the Mac mini that built every tested DMG both ran
|
||||
# on 2026-11-08. Move it deliberately, together with desktop-ci.yml; Cargo.toml's
|
||||
# rust-version is the floor.
|
||||
RUST_TOOLCHAIN: '1.99.1'
|
||||
# The commit to build: a rebuild by hand builds the tag it attaches to; Release
|
||||
# (a push to master) and a dry run build the commit they ran on.
|
||||
BUILD_REF: ${{ github.event_name == 'workflow_dispatch' && inputs.tag != '' && format('refs/tags/{0}', inputs.tag) || github.sha }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Codeman.app
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ env.BUILD_REF }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Rust toolchain
|
||||
run: |
|
||||
rustup toolchain install "$RUST_TOOLCHAIN" --profile minimal \
|
||||
--target aarch64-apple-darwin --target x86_64-apple-darwin
|
||||
rustup default "$RUST_TOOLCHAIN"
|
||||
rustc --version
|
||||
cargo --version
|
||||
|
||||
# A release installs Codeman from getcodeman.com/install and master. A test
|
||||
# build bakes another installer in at compile time (option_env! in setup.rs),
|
||||
# so neither variable may reach cargo here, and setup.rs's tests pin the default.
|
||||
- name: Unit tests
|
||||
working-directory: packages/desktop
|
||||
run: |
|
||||
unset CODEMAN_DESKTOP_INSTALLER_URL CODEMAN_DESKTOP_INSTALL_BRANCH
|
||||
cargo test --locked
|
||||
|
||||
- name: Bundle Codeman.app (signed ad hoc)
|
||||
working-directory: packages/desktop
|
||||
run: |
|
||||
unset CODEMAN_DESKTOP_INSTALLER_URL CODEMAN_DESKTOP_INSTALL_BRANCH SIGN_IDENTITY
|
||||
./scripts/bundle-macos.sh --universal
|
||||
|
||||
- name: Check the build
|
||||
working-directory: packages/desktop
|
||||
run: |
|
||||
BIN=dist/Codeman.app/Contents/MacOS/codeman-desktop
|
||||
lipo "$BIN" -verify_arch arm64 x86_64
|
||||
codesign --verify --strict dist/Codeman.app
|
||||
strings -a "$BIN" > "$RUNNER_TEMP/strings.txt"
|
||||
if grep -E 'raw\.githubusercontent\.com|test/desktop-' "$RUNNER_TEMP/strings.txt"; then
|
||||
echo "::error::A test installer URL or branch is baked into the app"
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'https://getcodeman.com/install' "$RUNNER_TEMP/strings.txt"
|
||||
|
||||
# ditto keeps the bundle's modes, symlinks and extended attributes; an
|
||||
# artifact of the bare folder would not (upload-artifact sets every file to
|
||||
# 644). The artifact is a zip around this zip: download-artifact unpacks the
|
||||
# outer one and leaves the ditto zip as it was.
|
||||
- name: Archive Codeman.app
|
||||
run: |
|
||||
mkdir -p "$RUNNER_TEMP/unsigned"
|
||||
ditto -c -k --sequesterRsrc --keepParent packages/desktop/dist/Codeman.app "$RUNNER_TEMP/unsigned/Codeman.app.zip"
|
||||
|
||||
- name: Upload the unsigned app
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: codeman-app-unsigned
|
||||
path: ${{ runner.temp }}/unsigned/Codeman.app.zip
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
overwrite: true
|
||||
|
||||
sign:
|
||||
name: Sign and notarize
|
||||
needs: build
|
||||
runs-on: macos-26
|
||||
# Two notary submissions; each usually takes minutes, and sign-macos.sh
|
||||
# gives each at most 45.
|
||||
timeout-minutes: 120
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
# For sign-macos.sh, make-dmg.sh and macos/entitlements.plist only. Nothing
|
||||
# here builds or runs code from the app or its crates.
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ env.BUILD_REF }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download the unsigned app
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: codeman-app-unsigned
|
||||
path: ${{ runner.temp }}/unsigned
|
||||
|
||||
# The build job ran every crate's build script, so its zip is checked before
|
||||
# this job, which holds the secrets, unpacks it: entries only under
|
||||
# Codeman.app/ (and the __MACOSX/ folder where ditto keeps attributes), no
|
||||
# "..", and no symlink, which the app never has and which could point an
|
||||
# unpack or a later write outside the app.
|
||||
- name: Unpack Codeman.app
|
||||
run: |
|
||||
ZIP="$RUNNER_TEMP/unsigned/Codeman.app.zip"
|
||||
ENTRIES=$(zipinfo -1 "$ZIP")
|
||||
LISTING=$(zipinfo "$ZIP")
|
||||
if printf '%s\n' "$ENTRIES" | grep -vE '^(Codeman\.app|__MACOSX)/' \
|
||||
|| printf '%s\n' "$ENTRIES" | grep -E '(^|/)\.\.(/|$)' \
|
||||
|| printf '%s\n' "$LISTING" | grep '^l'; then
|
||||
echo "::error::The build artifact holds an entry outside Codeman.app, a '..' or a symlink"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$RUNNER_TEMP/app"
|
||||
ditto -x -k "$ZIP" "$RUNNER_TEMP/app"
|
||||
if [ -n "$(find "$RUNNER_TEMP/app" -type l)" ]; then
|
||||
echo "::error::The unpacked app holds a symlink"
|
||||
exit 1
|
||||
fi
|
||||
test -x "$RUNNER_TEMP/app/Codeman.app/Contents/MacOS/codeman-desktop"
|
||||
|
||||
# A keychain of its own under RUNNER_TEMP, with a random password, holds the
|
||||
# Developer ID key for the length of this job. The last step deletes it and
|
||||
# the decoded .p12, whatever happened before. All five secrets are checked
|
||||
# here, so a missing one stops the job before any work.
|
||||
- name: Signing keychain
|
||||
env:
|
||||
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
|
||||
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
for name in MACOS_CERT_P12 MACOS_CERT_PASSWORD APPLE_ID APPLE_APP_PASSWORD APPLE_TEAM_ID; do
|
||||
if [ -z "${!name}" ]; then
|
||||
echo "::error::The repository secret $name is empty or not set"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
KEYCHAIN="$RUNNER_TEMP/codeman-signing.keychain-db"
|
||||
P12="$RUNNER_TEMP/codeman-signing.p12"
|
||||
trap 'rm -f "$P12"' EXIT
|
||||
KEYCHAIN_PASSWORD=$(openssl rand -hex 32)
|
||||
echo "::add-mask::$KEYCHAIN_PASSWORD"
|
||||
|
||||
# Line breaks out first: an encoder that wraps its output (GNU base64
|
||||
# without -w0) leaves some that macOS's base64 refuses.
|
||||
printf '%s' "$MACOS_CERT_P12" | tr -d '\r\n ' | base64 --decode > "$P12"
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
|
||||
# Locks after six hours, not after the default five idle minutes, which
|
||||
# a notarization wait can outlast.
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN"
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
|
||||
security import "$P12" -k "$KEYCHAIN" -f pkcs12 -P "$MACOS_CERT_PASSWORD" -T /usr/bin/codesign
|
||||
rm -f "$P12"
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN" > /dev/null
|
||||
# First on the search list, ahead of the runner's own keychains.
|
||||
SEARCH=()
|
||||
while IFS= read -r line; do
|
||||
line=${line#*\"}
|
||||
line=${line%\"*}
|
||||
if [ -n "$line" ]; then SEARCH+=("$line"); fi
|
||||
done < <(security list-keychains -d user)
|
||||
security list-keychains -d user -s "$KEYCHAIN" "${SEARCH[@]}"
|
||||
|
||||
# The identity by its SHA-1 hash, which no other certificate can match.
|
||||
IDENTITIES=$(security find-identity -v -p codesigning "$KEYCHAIN")
|
||||
MATCHES=$(printf '%s\n' "$IDENTITIES" | grep '"Developer ID Application: ' || true)
|
||||
COUNT=$(printf '%s' "$MATCHES" | grep -c . || true)
|
||||
if [ "$COUNT" != 1 ]; then
|
||||
printf '%s\n' "$IDENTITIES"
|
||||
echo "::error::MACOS_CERT_P12 holds $COUNT valid Developer ID Application identities; it needs exactly one"
|
||||
exit 1
|
||||
fi
|
||||
HASH=$(printf '%s\n' "$MATCHES" | awk '{ print $2 }')
|
||||
IDENTITY=$(printf '%s\n' "$MATCHES" | sed 's/^[^"]*"\(.*\)"$/\1/')
|
||||
case "$IDENTITY" in
|
||||
*"($APPLE_TEAM_ID)") ;;
|
||||
*)
|
||||
echo "::error::The certificate in MACOS_CERT_P12 ($IDENTITY) is not one of team APPLE_TEAM_ID"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
echo "Signing as $IDENTITY ($HASH)"
|
||||
echo "SIGN_IDENTITY=$HASH" >> "$GITHUB_ENV"
|
||||
|
||||
# The notary login goes to notarytool on its command line (sign-macos.sh's
|
||||
# APPLE_ID path), in this job only, which already holds the signing key.
|
||||
- name: Sign, notarize and staple
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: packages/desktop/scripts/sign-macos.sh "$RUNNER_TEMP/app/Codeman.app" "$RUNNER_TEMP/out"
|
||||
|
||||
# A dry run's result: codeman-dmg on the run page (gh run download <run-id>
|
||||
# -n codeman-dmg). attach takes it from here too.
|
||||
- name: Upload Codeman.dmg
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: codeman-dmg
|
||||
path: |
|
||||
${{ runner.temp }}/out/Codeman.dmg
|
||||
${{ runner.temp }}/out/Codeman.dmg.sha256
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
||||
- name: Remove the signing keychain
|
||||
if: always()
|
||||
run: |
|
||||
KEYCHAIN="$RUNNER_TEMP/codeman-signing.keychain-db"
|
||||
if [ -e "$KEYCHAIN" ]; then
|
||||
security delete-keychain "$KEYCHAIN" || true
|
||||
fi
|
||||
rm -f "$KEYCHAIN" "$RUNNER_TEMP/codeman-signing.p12"
|
||||
|
||||
attach:
|
||||
name: Attach to the release
|
||||
needs: sign
|
||||
# Release runs on pushes to master, so github.ref is master both there and for
|
||||
# a rebuild started by hand on master. A dry run from any other branch, or a
|
||||
# fork, attaches nothing.
|
||||
if: inputs.tag != '' && github.ref == 'refs/heads/master' && github.repository == 'Ark0N/Codeman'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download Codeman.dmg
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: codeman-dmg
|
||||
path: out
|
||||
|
||||
- name: Upload to the release
|
||||
id: upload
|
||||
working-directory: out
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REQUESTED_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
sha256sum -c Codeman.dmg.sha256
|
||||
TAG=$REQUESTED_TAG
|
||||
# Release's retag step ignores its own errors, so a release whose rename to
|
||||
# codeman@ failed still carries the tag changesets gave it.
|
||||
if ! gh release view "$TAG" -R "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
|
||||
case "$TAG" in
|
||||
codeman@*) TAG="aicodeman@${TAG#codeman@}" ;;
|
||||
esac
|
||||
gh release view "$TAG" -R "$GITHUB_REPOSITORY" > /dev/null
|
||||
echo "::warning::Attaching to $TAG: there is no release $REQUESTED_TAG"
|
||||
fi
|
||||
gh release upload "$TAG" Codeman.dmg Codeman.dmg.sha256 --clobber -R "$GITHUB_REPOSITORY"
|
||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check the download links
|
||||
working-directory: out
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ steps.upload.outputs.tag }}
|
||||
run: |
|
||||
EXPECTED=$(cut -d' ' -f1 Codeman.dmg.sha256)
|
||||
check() {
|
||||
curl -fsSL --retry 6 --retry-delay 10 --retry-all-errors -o "$RUNNER_TEMP/served.dmg" "$1"
|
||||
echo "$EXPECTED $RUNNER_TEMP/served.dmg" | sha256sum -c --quiet -
|
||||
echo "$1 serves this build"
|
||||
}
|
||||
check "https://github.com/$GITHUB_REPOSITORY/releases/download/$TAG/Codeman.dmg"
|
||||
LATEST=$(gh api "repos/$GITHUB_REPOSITORY/releases/latest" --jq .tag_name)
|
||||
if [ "$LATEST" = "$TAG" ]; then
|
||||
check "https://github.com/$GITHUB_REPOSITORY/releases/latest/download/Codeman.dmg"
|
||||
else
|
||||
echo "::warning::Latest is $LATEST, so releases/latest/download/Codeman.dmg does not serve $TAG"
|
||||
fi
|
||||
@@ -16,6 +16,9 @@ jobs:
|
||||
pull-requests: write
|
||||
outputs:
|
||||
published: ${{ steps.changesets.outputs.published }}
|
||||
# Set only when aicodeman itself was published: `published` is also true for
|
||||
# a release that moved only xterm-zerolag-input.
|
||||
codeman_version: ${{ steps.codeman.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
@@ -89,6 +92,38 @@ jobs:
|
||||
git tag -d "$OLD_TAG" 2>/dev/null || true
|
||||
git push origin "$NEW_TAG" ":refs/tags/$OLD_TAG" 2>/dev/null || true
|
||||
|
||||
# The macOS app job below runs only for a Codeman release, so it reads the
|
||||
# version from the list of packages this run published. Never fails the job:
|
||||
# npm has published by now, and a bad list only means no app build.
|
||||
- name: Codeman version published by this run
|
||||
id: codeman
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
env:
|
||||
PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }}
|
||||
run: |
|
||||
VERSION=$(printf '%s' "$PUBLISHED_PACKAGES" | jq -r '.[] | select(.name == "aicodeman") | .version' || true)
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "Codeman version published: ${VERSION:-none (no macOS app build)}"
|
||||
|
||||
# releases/latest/download/Codeman.dmg (the Mac app's stable link) serves
|
||||
# whichever release holds Latest. GitHub gives Latest to the release created
|
||||
# last, and a publish that moved only xterm-zerolag-input creates one the
|
||||
# retag above never touches, so Latest goes back to the newest Codeman
|
||||
# release after every publish. A failure warns and leaves npm, the tag and
|
||||
# the MCP publish alone.
|
||||
- name: Keep Latest on the Codeman release
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
RELEASE_ID=$(gh release view "codeman@${VERSION}" --json databaseId -q .databaseId 2>/dev/null || true)
|
||||
if [ -z "$RELEASE_ID" ]; then
|
||||
echo "::warning::No codeman@${VERSION} release to mark Latest"
|
||||
elif ! gh api -X PATCH "repos/${{ github.repository }}/releases/${RELEASE_ID}" -f make_latest=true > /dev/null; then
|
||||
echo "::warning::Could not mark codeman@${VERSION} Latest"
|
||||
fi
|
||||
|
||||
# Its own job: this one alone holds id-token, and a registry failure shows as itself
|
||||
# without touching the npm publish or the tag above.
|
||||
mcp-registry:
|
||||
@@ -99,3 +134,25 @@ jobs:
|
||||
contents: read
|
||||
id-token: write
|
||||
uses: ./.github/workflows/publish-mcp.yml
|
||||
|
||||
# Builds, signs and notarizes the Mac app and attaches Codeman.dmg to the
|
||||
# release (.github/workflows/desktop-dmg.yml), only when Codeman itself was
|
||||
# published. Its own job, so a failed DMG shows as itself and never touches
|
||||
# npm, the tag or the MCP publish; `gh workflow run desktop-dmg.yml -f
|
||||
# tag=codeman@<version>` reruns it. It gets the five secrets it uses and no
|
||||
# other: never `secrets: inherit`, which would hand it NPM_TOKEN.
|
||||
macos-app:
|
||||
name: macOS app
|
||||
needs: release
|
||||
if: needs.release.outputs.codeman_version != ''
|
||||
permissions:
|
||||
contents: write # the ceiling for the called jobs; only its attach job asks for write
|
||||
uses: ./.github/workflows/desktop-dmg.yml
|
||||
with:
|
||||
tag: codeman@${{ needs.release.outputs.codeman_version }}
|
||||
secrets:
|
||||
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
|
||||
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
Reference in new issue
Block a user