Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 65 additions & 36 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,16 @@ on:
branches: [main]
push:
branches: [main]
tags: ["v*"]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
# ============================================
# CI: Quality Checks (PR + main, skip tags)
# CI: Quality Checks
# ============================================
ci:
if: startsWith(github.ref, 'refs/tags/') == false
runs-on: veryfront-k8s-runners
strategy:
fail-fast: false
Expand All @@ -35,11 +33,10 @@ jobs:
esac

# ============================================
# CI: Tests (PR + main, skip tags)
# CI: Tests
# ============================================

tests:
if: startsWith(github.ref, 'refs/tags/') == false
runs-on: veryfront-k8s-runners
timeout-minutes: 15
strategy:
Expand All @@ -64,11 +61,10 @@ jobs:
fi

# ============================================
# CI: Compiled Binary E2E Tests (PR + main, skip tags)
# CI: Compiled Binary E2E Tests
# ============================================

tests-binary-e2e:
if: startsWith(github.ref, 'refs/tags/') == false
runs-on: veryfront-k8s-runners
timeout-minutes: 20
name: tests (binary e2e)
Expand All @@ -86,11 +82,11 @@ jobs:
VERYFRONT_BINARY_FRESH=1 deno task test:e2e:binary --no-lock

# ============================================
# CI: Split Mode Test (PR + main, skip tags)
# CI: Split Mode Test
# ============================================

tests-split-mode:
if: startsWith(github.ref, 'refs/tags/') == false && vars.RUN_SPLIT_MODE_TEST == 'true'
if: vars.RUN_SPLIT_MODE_TEST == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
name: tests (split mode)
Expand Down Expand Up @@ -144,12 +140,38 @@ jobs:
echo "Split mode startup test passed"

# ============================================
# CD: Build Binaries (main + tags)
# CD: Detect release type from deno.json
# X.Y.Z = stable (npm latest), anything else = RC
# ============================================

version-check:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
outputs:
version: ${{ steps.check.outputs.version }}
is_stable: ${{ steps.check.outputs.is_stable }}
steps:
- uses: actions/checkout@v4
- name: Detect release type
id: check
run: |
VERSION=$(jq -r '.version' deno.json)
echo "version=${VERSION}" >> $GITHUB_OUTPUT
if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "is_stable=true" >> $GITHUB_OUTPUT
echo "::notice::Stable version detected: $VERSION → will publish as latest"
else
echo "is_stable=false" >> $GITHUB_OUTPUT
echo "::notice::Pre-release version detected: $VERSION → will publish as rc"
fi

# ============================================
# CD: Build Binaries (main only)
# Version comes directly from deno.json
# ============================================

build-binaries:
if: (github.ref == 'refs/heads/main' && github.event_name == 'push') || startsWith(github.ref, 'refs/tags/v')
if: github.ref == 'refs/heads/main'
runs-on: ${{ matrix.os }}
continue-on-error: ${{ contains(matrix.os, 'windows') }}
strategy:
Expand Down Expand Up @@ -202,13 +224,14 @@ jobs:
retention-days: 7

# ============================================
# CD: Pre-release (main only)
# Publishes whatever version is in deno.json with --tag rc
# CD: Pre-release (RC versions only)
# Runs when deno.json version has a prerelease suffix (e.g. 0.1.14-rc)
# Appends .{run_number} and publishes with --tag rc
# ============================================

prerelease:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: [ci, tests, tests-binary-e2e, build-binaries]
if: needs.version-check.outputs.is_stable == 'false'
needs: [ci, tests, tests-binary-e2e, build-binaries, version-check]
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -228,11 +251,10 @@ jobs:
- name: Compute RC version
id: version
run: |
BASE=$(jq -r '.version' deno.json)
RC_VERSION="${BASE}-rc.${{ github.run_number }}"
echo "base=${BASE}" >> $GITHUB_OUTPUT
BASE=${{ needs.version-check.outputs.version }}
RC_VERSION="${BASE}.${{ github.run_number }}"
echo "version=${RC_VERSION}" >> $GITHUB_OUTPUT
echo "Publishing ${RC_VERSION}"
echo "Publishing RC: ${RC_VERSION}"

- name: Build and publish
env:
Expand Down Expand Up @@ -283,12 +305,14 @@ jobs:
client-payload: '{"version": "${{ steps.version.outputs.version }}"}'

# ============================================
# CD: Stable Release (tags only)
# CD: Stable Release (clean semver only)
# Runs when deno.json version is X.Y.Z (no suffix)
# Publishes with --tag latest, creates git tag, updates Homebrew
# ============================================

release:
if: startsWith(github.ref, 'refs/tags/v')
needs: [build-binaries]
if: needs.version-check.outputs.is_stable == 'true'
needs: [ci, tests, tests-binary-e2e, build-binaries, version-check]
runs-on: ubuntu-latest
environment: production
permissions:
Expand All @@ -297,18 +321,11 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Validate tag matches deno.json
run: |
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
DENO_VERSION=$(jq -r '.version' deno.json)
if [ "$TAG_VERSION" != "$DENO_VERSION" ]; then
echo "Error: Tag ($TAG_VERSION) doesn't match deno.json ($DENO_VERSION)"
exit 1
fi

- name: Read version
id: version
run: echo "version=$(jq -r '.version' deno.json)" >> $GITHUB_OUTPUT
run: |
echo "version=${{ needs.version-check.outputs.version }}" >> $GITHUB_OUTPUT
echo "Publishing stable: ${{ needs.version-check.outputs.version }}"

- uses: denoland/setup-deno@v2
with:
Expand All @@ -332,6 +349,19 @@ jobs:
with:
path: binaries

- name: Create git tag
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
TAG="v${VERSION}"
if git ls-remote --tags origin | grep -q "refs/tags/${TAG}$"; then
echo "Tag ${TAG} already exists, skipping"
else
git tag "${TAG}"
git push origin "${TAG}"
echo "Created tag ${TAG}"
fi

- name: Create GitHub releases
env:
VERSION: ${{ steps.version.outputs.version }}
Expand Down Expand Up @@ -384,12 +414,11 @@ jobs:
client-payload: '{"version": "${{ steps.version.outputs.version }}"}'

# ============================================
# CD: Update Homebrew (tags only)
# CD: Update Homebrew (stable releases only)
# ============================================

update-homebrew:
if: startsWith(github.ref, 'refs/tags/v')
needs: release
needs: [release, version-check]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -403,7 +432,7 @@ jobs:
max_attempts: 3
retry_wait_seconds: 10
command: |
VERSION="${GITHUB_REF#refs/tags/v}"
VERSION="${{ needs.version-check.outputs.version }}"

# Download binaries and calculate checksums
declare -A SHAS
Expand Down
5 changes: 5 additions & 0 deletions cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,8 @@ const args = getArgs();
const { parseCliArgs } = await import("./shared/args.ts");
const { routeCommand } = await import("./router.ts");
await routeCommand(parseCliArgs(args));

// Exit cleanly after one-shot commands. Long-running commands (dev, start, mcp)
// never return from routeCommand, so this only runs for commands like deploy, push, init, build.
const { exitProcess } = await import("./utils/index.ts");
exitProcess(0);