Skip to content

ci: fix two release-pipeline bugs the first tag exposed (#247) #3

ci: fix two release-pipeline bugs the first tag exposed (#247)

ci: fix two release-pipeline bugs the first tag exposed (#247) #3

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write # for GitHub Releases
id-token: write # for npm provenance (if enabled)
jobs:
# ----------------------------------------------------------------------
# Validate — typecheck + tests + build all packages
# ----------------------------------------------------------------------
validate:
name: Validate before release
runs-on: ubuntu-latest
timeout-minutes: 25
outputs:
version: ${{ steps.version.outputs.version }}
channel: ${{ steps.version.outputs.channel }}
is_mandatory: ${{ steps.version.outputs.is_mandatory }}
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
# The release gate must validate in the same environment as CI. Without
# this, `pnpm test` here runs against a weaker box than ci.yml uses, so a
# green CI stops predicting a green release — which is exactly how the
# first v0.3.0 tag failed: the deny-all-net fallback test spawns `bwrap`,
# and this runner had no bubblewrap installed.
# Kept byte-for-byte in step with the "Install sandbox tools" step in
# ci.yml; change both together.
- name: Install sandbox tools
run: |
sudo apt-get update
sudo apt-get install -y bubblewrap slirp4netns curl
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=53 || true
- run: pnpm typecheck
- run: pnpm lint
- run: pnpm format:check
- name: Test
# Matches ci.yml: opts in the selective-allowlist integration test, which
# self-skips when bwrap/slirp4netns are absent.
env:
DC_SANDBOX_NET_TEST: '1'
run: pnpm test
- run: pnpm docs:check
- run: pnpm release:check
- name: Install Chromium
run: pnpm --filter @deepcode/desktop exec playwright install --with-deps chromium
- name: Exercise desktop protocol fixture
run: pnpm --filter @deepcode/desktop test:e2e
- name: Upload validation diagnostics
if: failure()
uses: actions/upload-artifact@v4
with:
name: release-validation-diagnostics
path: |
apps/vscode/dist/release-gate-report.json
apps/desktop/playwright-report
apps/desktop/test-results
if-no-files-found: ignore
retention-days: 14
- id: version
name: Parse version + channel from tag
run: |
TAG="${GITHUB_REF#refs/tags/}"
VERSION="${TAG#v}"
CHANNEL="stable"
IS_MANDATORY="false"
if [[ "$VERSION" == *-nightly.* ]]; then CHANNEL="nightly"; fi
if [[ "$VERSION" == *-beta.* ]]; then CHANNEL="beta"; fi
if [[ "$VERSION" == *+security.* ]]; then IS_MANDATORY="true"; fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "channel=$CHANNEL" >> $GITHUB_OUTPUT
echo "is_mandatory=$IS_MANDATORY" >> $GITHUB_OUTPUT
echo "Released as version=$VERSION channel=$CHANNEL mandatory=$IS_MANDATORY"
# ----------------------------------------------------------------------
# Publish CLI to npm
# ----------------------------------------------------------------------
publish-cli:
name: Publish deepcode-cli to npm
# Avoid a partial release: do not publish npm until both installable
# desktop/editor artifacts have built successfully.
needs: [validate, build-vscode, build-mac]
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: 'pnpm'
registry-url: https://registry.npmjs.org
- run: pnpm install --frozen-lockfile
# Before `pnpm build`: core's VERSION is compiled into dist, and it is what
# `deepcode --version`, `--help`, `/upgrade` and `/bug` print.
- name: Stamp core VERSION from tag
run: |
sed -i.bak -E "s/^export const VERSION = '.*';/export const VERSION = '${{ needs.validate.outputs.version }}';/" packages/core/src/index.ts
rm -f packages/core/src/index.ts.bak
grep -q "export const VERSION = '${{ needs.validate.outputs.version }}';" packages/core/src/index.ts
# Same workspace-protocol problem as build-vscode. This job had never run
# (it needs both build jobs), so the bug was latent rather than observed.
- name: Set CLI version from tag
run: |
cd apps/cli
node -e '
const fs = require("fs");
const p = "package.json";
const pkg = JSON.parse(fs.readFileSync(p, "utf8"));
pkg.version = process.argv[1];
fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + "\n");
' "${{ needs.validate.outputs.version }}"
node -e 'process.exit(require("./package.json").version === process.argv[1] ? 0 : 1)' "${{ needs.validate.outputs.version }}"
- run: pnpm build
- name: Publish
run: |
cd apps/cli
if [ "${{ needs.validate.outputs.channel }}" = "stable" ]; then
pnpm publish --no-git-checks --access public
else
pnpm publish --no-git-checks --access public --tag ${{ needs.validate.outputs.channel }}
fi
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# ----------------------------------------------------------------------
# Build installable VS Code extension
# ----------------------------------------------------------------------
build-vscode:
name: Build VS Code extension
needs: validate
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
# The extension bundles its own app-server, which bundles core.
- name: Stamp core VERSION from tag
run: |
sed -i.bak -E "s/^export const VERSION = '.*';/export const VERSION = '${{ needs.validate.outputs.version }}';/" packages/core/src/index.ts
rm -f packages/core/src/index.ts.bak
# Not `npm version`: this is a pnpm workspace, and npm rejects the
# `workspace:*` dependency protocol with EUNSUPPORTEDPROTOCOL before it
# ever writes the field. Editing the one key touches nothing else.
- name: Set extension version from tag
run: |
node -e '
const fs = require("fs");
const p = "package.json";
const pkg = JSON.parse(fs.readFileSync(p, "utf8"));
pkg.version = process.argv[1];
fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + "\n");
' "${{ needs.validate.outputs.version }}"
node -e 'process.exit(require("./package.json").version === process.argv[1] ? 0 : 1)' "${{ needs.validate.outputs.version }}"
working-directory: apps/vscode
- name: Package VSIX
run: |
mkdir -p release-artifacts
pnpm --dir apps/vscode package \
--out "../../release-artifacts/deepcode-${{ needs.validate.outputs.version }}.vsix"
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: vscode-release
path: release-artifacts/deepcode-*.vsix
# ----------------------------------------------------------------------
# Build + sign Mac client (.dmg) via Tauri
# ----------------------------------------------------------------------
build-mac:
name: Build + sign + notarize Mac client (Tauri)
needs: validate
runs-on: macos-14
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: 'pnpm'
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin
- name: Cache Cargo registry
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
apps/desktop/src-tauri/target
key: cargo-mac-${{ hashFiles('**/Cargo.lock') }}
- name: pnpm install
run: pnpm install --frozen-lockfile
- name: Prepare pinned Node sidecar runtime
env:
NODE_SIDECAR_VERSION: 22.23.1
# Verified against https://nodejs.org/dist/v22.23.1/SHASUMS256.txt and by
# hashing the downloaded artifact. The previous value never matched any
# published tarball, so this check had never once passed.
# Re-derive when bumping:
# curl -sS https://nodejs.org/dist/v<VER>/SHASUMS256.txt \
# | grep node-v<VER>-darwin-arm64.tar.xz
NODE_SIDECAR_SHA256: fb526811860f81dcac7dd8b2b55eca4accfc5d61c3b7c2508f2639faee8a738d
run: |
archive="node-v${NODE_SIDECAR_VERSION}-darwin-arm64.tar.xz"
curl --fail --location --retry 3 \
"https://nodejs.org/dist/v${NODE_SIDECAR_VERSION}/${archive}" \
--output "$RUNNER_TEMP/$archive"
echo "${NODE_SIDECAR_SHA256} $RUNNER_TEMP/$archive" | shasum -a 256 --check
tar -xJf "$RUNNER_TEMP/$archive" -C "$RUNNER_TEMP"
echo "DEEPCODE_NODE_RUNTIME=$RUNNER_TEMP/node-v${NODE_SIDECAR_VERSION}-darwin-arm64/bin/node" >> "$GITHUB_ENV"
- name: Set version
run: |
# core VERSION first — the sidecar it builds into reports it.
sed -i.bak -E "s/^export const VERSION = '.*';/export const VERSION = '${{ needs.validate.outputs.version }}';/" packages/core/src/index.ts
rm -f packages/core/src/index.ts.bak
cd apps/desktop
npm version "${{ needs.validate.outputs.version }}" --no-git-tag-version
# Sync the tauri.conf.json version too
node -e "
const fs=require('fs');
const p='src-tauri/tauri.conf.json';
const c=JSON.parse(fs.readFileSync(p,'utf8'));
c.version='${{ needs.validate.outputs.version }}';
fs.writeFileSync(p, JSON.stringify(c,null,2)+'\n');
"
# And Cargo.toml + the lockfile entry, which must agree or a --locked
# cargo invocation refuses to build.
sed -i.bak -E 's/^version = ".*"/version = "${{ needs.validate.outputs.version }}"/' src-tauri/Cargo.toml
rm -f src-tauri/Cargo.toml.bak
node -e "
const fs=require('fs');
const p='src-tauri/Cargo.lock';
const s=fs.readFileSync(p,'utf8');
fs.writeFileSync(p, s.replace(
/name = \"deepcode_desktop\"\nversion = \"[^\"]+\"/,
'name = \"deepcode_desktop\"\nversion = \"${{ needs.validate.outputs.version }}\"'
));
"
- name: Import Developer ID certificate
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
run: |
# CSC_LINK is base64-encoded .p12 of the Developer ID Application cert
echo "$CSC_LINK" | base64 --decode > /tmp/cert.p12
security create-keychain -p actions ci.keychain
security default-keychain -s ci.keychain
security unlock-keychain -p actions ci.keychain
security import /tmp/cert.p12 -k ci.keychain -P "$CSC_KEY_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k actions ci.keychain
rm -f /tmp/cert.p12
- name: Store notarization credentials
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
xcrun notarytool store-credentials "DEEPCODE_NOTARY" \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_APP_SPECIFIC_PASSWORD"
- name: Build + sign + notarize
env:
DEEPCODE_TARGET: aarch64-apple-darwin
DEEPCODE_NOTARY_PROFILE: DEEPCODE_NOTARY
run: bash scripts/sign-and-notarize.sh
- name: Stage release artifacts
run: |
mkdir -p release-artifacts
cp apps/desktop/src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/DeepCode_${{ needs.validate.outputs.version }}_aarch64.dmg \
release-artifacts/DeepCode-${{ needs.validate.outputs.version }}-arm64.dmg
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: mac-release
path: release-artifacts/DeepCode-*.dmg
# ----------------------------------------------------------------------
# GitHub Release — runs after Mac build so the DMG can be attached
# ----------------------------------------------------------------------
github-release:
name: Publish GitHub Release
needs: [validate, publish-cli, build-vscode, build-mac]
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Download Mac DMG
uses: actions/download-artifact@v8
with:
name: mac-release
path: release-artifacts/
- name: Download VS Code extension
uses: actions/download-artifact@v8
with:
name: vscode-release
path: release-artifacts/
- name: Generate release notes
id: notes
run: |
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -n "$PREV_TAG" ]; then
FROM="$PREV_TAG"
else
FROM=$(git rev-list --max-parents=0 HEAD)
fi
npx tsx scripts/gen-release-notes.ts "$FROM" HEAD > release-notes.md
cat release-notes.md
- name: Create GitHub Release
uses: softprops/action-gh-release@v3
with:
name: v${{ needs.validate.outputs.version }}
body_path: release-notes.md
prerelease: ${{ needs.validate.outputs.channel != 'stable' }}
generate_release_notes: false
files: |
release-artifacts/*.dmg
release-artifacts/*.vsix