Publish with curl
The Action is a convenience: every step is plain HTTP against Mocco's public API, so any CI system (or a script on your machine) can publish. You need a secret API key with the desktop:write scope, your app id (Setup), curl, jq and shasum.
The five calls
All calls take Authorization: Bearer <key>. Errors answer application/problem+json with a detail that says what to fix.
Declare the version and its files:
POST /v1/desktop/apps/<app id>/releaseswith the version, the notes, and per file its platform, kind (installerorupdater), file name, content type, size and SHA-256 (plus the.sigcontent for an updater). It answers201(or200when the version was declared before) with areleaseIdand, per file, an upload URL, ornullfor a file already verified.Upload each file with
PUTto its URL, with exactly the headers it came with, within 15 minutes.Finalize:
POST /v1/desktop/releases/<release id>/finalize. It answers202. If an upload is missing or incomplete, it answers400naming the file: declare again with the same body (fresh URLs), upload, finalize.Wait:
GET /v1/desktop/releases/<release id>untilstatusisdraft(verified) orfailed(failureReasonsays which file and why).Publish:
POST /v1/desktop/releases/<release id>/publishwith{"channel":"stable"}. It answers201(or200when the channel already serves it).
A published or withdrawn version can't be declared again (409 version_exists): bump the version.
The script
API=https://www.mocco.club/api/ext/v1
APP=$MOCCO_DESKTOP_APP_ID
AUTH="Authorization: Bearer $MOCCO_API_KEY"
V=0.2.0
DMG=release/App_${V}_aarch64.dmg
TGZ=release/App_${V}_aarch64.app.tar.gz
artifact() { # platform kind file content-type [signature-file]
jq -n --arg platform "$1" --arg kind "$2" --arg filename "$(basename "$3")" --arg contentType "$4" \
--argjson size "$(wc -c < "$3" | tr -d ' ')" --arg sha256 "$(shasum -a 256 "$3" | cut -d' ' -f1)" \
--arg signature "$( [ -n "${5:-}" ] && cat "$5" )" \
'{platform:$platform, kind:$kind, filename:$filename, contentType:$contentType, size:$size, sha256:$sha256}
+ (if $signature == "" then {} else {signature:$signature} end)'
}
BODY=$(jq -n --arg version "$V" --rawfile notes RELEASE_NOTES.md \
--argjson dmg "$(artifact darwin-aarch64 installer "$DMG" application/x-apple-diskimage)" \
--argjson tgz "$(artifact darwin-aarch64 updater "$TGZ" application/gzip "$TGZ.sig")" \
'{version:$version, notes:$notes, artifacts:[$dmg, $tgz]}')
# 1. Declare.
DECLARED=$(curl -fsS -X POST "$API/desktop/apps/$APP/releases" -H "$AUTH" -H 'content-type: application/json' -d "$BODY")
RELEASE=$(jq -r .releaseId <<<"$DECLARED")
# 2. Upload each file that has a URL (null: already verified).
put() { # platform kind file
TARGET=$(jq -c --arg p "$1" --arg k "$2" '.uploads[] | select(.platform == $p and .kind == $k) | .upload' <<<"$DECLARED")
[ "$TARGET" = null ] && return 0
HEADERS=(); while IFS= read -r h; do HEADERS+=(-H "$h"); done \
< <(jq -r '.headers | to_entries[] | "\(.key): \(.value)"' <<<"$TARGET")
curl -fsS -X PUT "${HEADERS[@]}" --upload-file "$3" "$(jq -r .url <<<"$TARGET")"
}
put darwin-aarch64 installer "$DMG"
put darwin-aarch64 updater "$TGZ"
# 3. Finalize, and 4. wait for draft or failed.
curl -fsS -X POST "$API/desktop/releases/$RELEASE/finalize" -H "$AUTH"
while :; do
STATUS=$(curl -fsS "$API/desktop/releases/$RELEASE" -H "$AUTH")
case $(jq -r .status <<<"$STATUS") in
draft) break ;;
failed) jq -r .failureReason <<<"$STATUS" >&2; exit 1 ;;
*) sleep 5 ;;
esac
done
# 5. Publish to stable.
curl -fsS -X POST "$API/desktop/releases/$RELEASE/publish" -H "$AUTH" -H 'content-type: application/json' \
-d '{"channel":"stable"}'The script runs on macOS and Linux runners. curl -f fails the step on an error; to see the problem's detail, drop -f and print the body. Running it again for the same version resumes: files already verified aren't uploaded again.
The full API, including notes and yank, is in the desktop releases reference.