Tag-driven CI pipeline. Push a v0.X.Y tag → GitHub Actions takes over:
validate → build CLI + publish to npm → build + sign + notarize Tauri DMG
→ create GitHub Release with both artifacts attached.
Set these in repo settings → Secrets and variables → Actions → New repository secret. All five are required for a successful Mac release.
| Secret | Purpose |
|---|---|
APPLE_ID |
Your Apple Developer Apple ID (e.g. you@example.com) |
APPLE_APP_SPECIFIC_PASSWORD |
App-specific password from appleid.apple.com (used by notarytool) |
APPLE_TEAM_ID |
10-character team ID (from developer.apple.com → membership) |
CSC_LINK |
Base64-encoded .p12 of the Developer ID Application cert |
CSC_KEY_PASSWORD |
Password used when exporting the .p12 |
NPM_TOKEN |
npm access token with publish scope |
# In Keychain Access on the developer machine:
# select your "Developer ID Application: <Name> (TEAM_ID)" cert + key
# → Export → save as cert.p12 with a strong password.
base64 -i cert.p12 -o cert.p12.b64
# Paste the contents of cert.p12.b64 as the CSC_LINK secret value.The CI workflow imports this into a temporary keychain at build time,
signs the .app, then notarizes via Apple's notarytool (the
DEEPCODE_NOTARY keychain profile is created on the fly from the
Apple secrets).
appleid.apple.com → Sign-in security →
App-specific passwords → Generate. Save the 16-char password as
APPLE_APP_SPECIFIC_PASSWORD.
npmjs.com → account → Access Tokens → Generate
new token → Automation (CI-friendly) → save as NPM_TOKEN.
# 1. Make sure main is green and CHANGELOG.md has an entry for the new version.
# 2. Bump versions everywhere in lockstep:
# - apps/cli/package.json
# - apps/desktop/package.json
# - apps/desktop/src-tauri/tauri.conf.json
# - apps/desktop/src-tauri/Cargo.toml
# (The CI workflow also re-syncs these from the tag.)
# 3. Tag + push:
git tag v0.1.3
git push origin v0.1.3The release.yml workflow fires on any v* tag push and runs five jobs
serially:
- validate —
pnpm typecheck+pnpm test+pnpm build - publish-cli — bumps
apps/cli/package.jsonto the tag version,pnpm publishto npm registry. Beta / nightly tags get--tag <channel>solateststays on stable. - build-mac — macOS-14 runner, Rust + Tauri build, calls
scripts/sign-and-notarize.shend-to-end. OutputsDeepCode-<version>-arm64.dmg. - github-release — generates release notes via
scripts/gen-release-notes.ts(groups PRs by label), creates the GitHub Release, attaches the DMG.
Tag format determines the channel + publish target:
| Tag format | Channel | npm tag | GitHub release |
|---|---|---|---|
v0.2.1 |
stable |
latest |
not prerelease |
v0.3.0-beta.1 |
beta |
beta |
prerelease |
v0.3.0-nightly.20260605 |
nightly |
nightly |
prerelease |
v0.2.2+security.1 |
stable |
latest |
mandatory flag |
The +security.X suffix sets is_mandatory=true in the release output
so the Tauri updater can show a red "must update" banner.
tauri.conf.json already has plugins.updater.active: true with a committed
public key and an endpoint at the release's latest.json. Three pieces are
still missing, so in-app auto-update will not work until they're done (users
must download the DMG manually):
-
Tauri updater signing key.
tauri.conf.json#plugins.updater.pubkeyis the public half. The matching private key must exist and be added as a GitHub secret. If you have it, store it; if not, regenerate the pair (this changes the pubkey, so the first build after is a clean break for existing installs):pnpm --filter @deepcode/desktop exec tauri signer generate -w deepcode-updater.key # → paste the PUBLIC key into tauri.conf.json#plugins.updater.pubkey # → add the PRIVATE key file contents as the secret below (never commit it)
Add two secrets:
TAURI_SIGNING_PRIVATE_KEYandTAURI_SIGNING_PRIVATE_KEY_PASSWORD. -
Enable updater artifacts. Set
bundle.createUpdaterArtifacts: trueintauri.conf.json.⚠️ Only flip this together with step 1 —tauri buildwill fail ifcreateUpdaterArtifactsis true but no signing key is present in the env. (This is why it's left off today: the plain DMG build works without a key.) -
Generate + upload
latest.json. Add a step to.github/workflows/release.yml(build-mac job) that, after signing, writeslatest.jsonmatching Tauri v2's schema and uploads it to the release:{ "version": "<tag>", "notes": "...", "pub_date": "<ISO>", "platforms": { "darwin-aarch64": { "signature": "<contents of DeepCode_<v>_aarch64.dmg.sig>", "url": "https://github.com/oratis/deepcode/releases/download/v<v>/DeepCode_<v>_aarch64.dmg" } } }The
.sigis produced by the signed build (step 2). The build env must exportTAURI_SIGNING_PRIVATE_KEY+..._PASSWORDso the artifact is signed.
Until all three land, ship the DMG (notarized, works today) and tell users to download manually; the "Relaunch to update" flow lights up once the feed exists.
- Verify:
npm view deepcode-cli@<version>shows the new version - Verify:
https://github.com/oratis/deepcode/releases/tag/v<version>has the DMG attached - Optional: announce in the README / homepage
Before pushing the tag for a real release, the same flow runs locally:
# Bump versions everywhere first, then:
pnpm install
pnpm typecheck
pnpm test
pnpm build
bash scripts/sign-and-notarize.shThe DMG lands at
apps/desktop/src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/DeepCode_<version>_aarch64.dmg.
This is the same artifact CI would attach.
GitHub Releases are independent — delete a release (or mark prerelease) via the GitHub UI to hide it from users.
npm unpublish is more restricted: only the most recent version, and
only within 72h of publish. If a CLI version needs urgent rollback past
that window, publish a patched higher version instead and let users
upgrade.