Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dfc45fd
Merge release and deploy-prod workflows
ashleyyli Mar 18, 2026
b6d8e03
Fix release workflow to deploy-prod before make-release before publis…
ashleyyli Mar 19, 2026
aa97c0c
Compare current version against latest tag
ashleyyli Mar 19, 2026
7da12d0
Fix merge conflicts
ashleyyli Mar 20, 2026
2d68c09
Auto-update feature branch with changes from the main branch
github-actions[bot] Mar 20, 2026
63317fb
Bump version
ashleyyli Mar 20, 2026
2dd8b46
Auto-update feature branch with changes from the main branch
github-actions[bot] Mar 29, 2026
8d1890e
Auto-update feature branch with changes from the main branch
github-actions[bot] Mar 30, 2026
c83e67b
Auto-update feature branch with changes from the main branch
github-actions[bot] Apr 1, 2026
9685b94
Auto-update feature branch with changes from the main branch
github-actions[bot] Apr 1, 2026
be9f252
Auto-update feature branch with changes from the main branch
github-actions[bot] Apr 2, 2026
c2d5fea
Auto-update feature branch with changes from the main branch
github-actions[bot] Apr 2, 2026
36ab2c2
Auto-update feature branch with changes from the main branch
github-actions[bot] Apr 3, 2026
ee5e5b1
Fix permissions, version check, and release checking
ashleyyli Apr 3, 2026
981eb11
Auto-update feature branch with changes from the main branch
github-actions[bot] Apr 3, 2026
189a735
Serialize workflow
ashleyyli Apr 3, 2026
58b659f
Notify when version check fails
ashleyyli Apr 3, 2026
925d635
Auto-update feature branch with changes from the main branch
github-actions[bot] Apr 4, 2026
cae29da
Bump version
ashleyyli Apr 4, 2026
5919fc5
Sort correctly on prereleases
ashleyyli Apr 4, 2026
f6c0e8b
Auto-update feature branch with changes from the main branch
github-actions[bot] Apr 5, 2026
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
208 changes: 195 additions & 13 deletions .github/workflows/deploy-prod.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,153 @@
name: Deploy all resources to PROD
run-name: PROD deploy - @${{ github.actor }}

concurrency:
group: prod-release-${{ github.ref }}
cancel-in-progress: false

on:
release:
types: [created]
tags:
- 'v*'
push:
branches:
- 'main'

jobs:
check-version:
permissions:
contents: read
runs-on: ubuntu-latest
outputs:
is-prerelease: ${{ steps.version.outputs.prerelease }}
version: ${{ steps.version.outputs.current }}
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.AUTOMATED_RELEASE_APP_ID }}
private-key: ${{ secrets.AUTOMATED_RELEASE_APP_KEY }}

- name: Checkout code
uses: actions/checkout@v6
with:
fetch-tags: true
token: ${{ steps.app-token.outputs.token }}

- name: Check if version changed
id: version
run: |
# Get current version
CURRENT_VERSION=$(jq -r '.version' package.json)

# Get previous version
LATEST_TAG=$(git -c 'versionsort.suffix=-' tag -l 'v*' --sort=-version:refname | head -n 1)
PREVIOUS_VERSION=${LATEST_TAG#v}
PREVIOUS_VERSION=${PREVIOUS_VERSION:-0.0.0}
Comment on lines +41 to +44
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
export HOME="$tmp/home"
mkdir -p "$HOME"
repo="$tmp/repo"
mkdir -p "$repo"
cd "$repo"

git init -q
git -c user.name=test -c user.email=test@example.com commit --allow-empty -m init -q
git tag v1.2.3
git tag v1.2.3-beta.10
git tag -l 'v*' --sort=-version:refname

Repository: acm-uiuc/core

Length of output: 157


Don't use Git's version sort for semver comparison.

git tag --sort=version:refname doesn't correctly order same-base prereleases vs. releases. Git sorts suffixes lexicographically by default, but SemVer requires release versions to outrank their prereleases. With tags v1.2.3 and v1.2.3-beta.10, Git's sort puts the prerelease first—so head -n 1 picks v1.2.3-beta.10 as the "latest" tag instead of v1.2.3. This breaks PREVIOUS_VERSION extraction and can allow downgrades past the later semver check (e.g., deploying 1.2.3-beta.11 when 1.2.3 already exists). Use the same semver comparator you implement below instead of relying on Git's sort.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-prod.yml around lines 41 - 44, The current logic
uses git's --sort=version:refname to set LATEST_TAG and PREVIOUS_VERSION which
misorders prereleases vs releases; replace that by enumerating all tags (the
output feeding LATEST_TAG), strip the leading "v" (as PREVIOUS_VERSION does),
and then determine the highest semver using the same semver comparator used
elsewhere in this workflow (not git's sort). Concretely: collect tags into a
list instead of relying on git --sort, normalize them by removing the "v" prefix
(the same normalization used to set PREVIOUS_VERSION), and pick the max with
your semver comparator function so LATEST_TAG/PREVIOUS_VERSION reflect true
semver ordering (release > prerelease).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Git's version:refname sorts by version number rather than lexicographically (--sort=refname)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ashleyyli, you're correct that --sort=version:refname performs numeric-aware version ordering (e.g., correctly placing 1.10.0 after 1.2.0), unlike --sort=refname which is purely lexicographic.

However, the concern about prerelease vs. release ordering still stands. Even when version sort is used in git-tag, tagnames with the same base version but different suffixes are still sorted lexicographically, resulting in prerelease tags appearing after the main release (e.g., "1.0-rc1" after "1.0"). This is the opposite of what SemVer requires — when major, minor, and patch are equal, a pre-release version has lower precedence than a normal version (e.g., 1.0.0-alpha < 1.0.0).

The practical impact: if both v1.2.3 and v1.2.3-beta.10 exist, --sort=-version:refname | head -n 1 returns v1.2.3-beta.10 (the prerelease sorts lexicographically higher due to the - suffix), not v1.2.3 (the true semver maximum). This would set PREVIOUS_VERSION to 1.2.3-beta.10, incorrectly blocking a deployment of 1.2.3.

The version:refname sort order can be affected by the versionsort.suffix configuration variable — and as shown in the Git source, you'd need to configure that with - as the suffix to make prereleases sort before their base release. Without it, the default behaviour misaligns with semver.


echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
echo "previous=$PREVIOUS_VERSION" >> $GITHUB_OUTPUT

if [ "$CURRENT_VERSION" == "$PREVIOUS_VERSION" ]; then
echo "No version change detected."
exit 1
fi

# Check if prerelease (contains hyphen)
if [[ "$CURRENT_VERSION" == *-* ]]; then
echo "prerelease=true" >> $GITHUB_OUTPUT
else
echo "prerelease=false" >> $GITHUB_OUTPUT
fi

- name: Validate version increment
env:
CURRENT: ${{ steps.version.outputs.current }}
PREVIOUS: ${{ steps.version.outputs.previous }}
run: |
# Function to compare semver versions
version_compare() {
# Strip prerelease suffix for base comparison
local v1_base="${1%%-*}"
local v2_base="${2%%-*}"
local v1_pre="${1#*-}"
local v2_pre="${2#*-}"

# If no prerelease, set to empty
[[ "$v1_base" == "$1" ]] && v1_pre=""
[[ "$v2_base" == "$2" ]] && v2_pre=""

# Compare major.minor.patch
IFS='.' read -ra V1 <<< "$v1_base"
IFS='.' read -ra V2 <<< "$v2_base"

for i in 0 1 2; do
local n1="${V1[$i]:-0}"
local n2="${V2[$i]:-0}"
if (( n1 > n2 )); then
echo "greater"
return
elif (( n1 < n2 )); then
echo "lesser"
return
fi
done

# Base versions equal, compare prerelease
# No prerelease > prerelease (1.0.0 > 1.0.0-beta)
if [[ -z "$v1_pre" && -n "$v2_pre" ]]; then
echo "greater"
elif [[ -n "$v1_pre" && -z "$v2_pre" ]]; then
echo "lesser"
elif [[ -z "$v1_pre" && -z "$v2_pre" ]]; then
echo "equal"
else
# Semver-compliant prerelease comparison (spec §11)
IFS='.' read -ra P1 <<< "$v1_pre"
IFS='.' read -ra P2 <<< "$v2_pre"
local len=${#P1[@]}
(( ${#P2[@]} > len )) && len=${#P2[@]}
for (( i=0; i<len; i++ )); do
local id1="${P1[$i]:-}"
local id2="${P2[$i]:-}"
# Shorter prerelease is lesser if all prior identifiers equal
if [[ -z "$id1" ]]; then echo "lesser"; return; fi
if [[ -z "$id2" ]]; then echo "greater"; return; fi
local is_num1=false is_num2=false
[[ "$id1" =~ ^[0-9]+$ ]] && is_num1=true
[[ "$id2" =~ ^[0-9]+$ ]] && is_num2=true
if $is_num1 && $is_num2; then
if (( 10#$id1 > 10#$id2 )); then echo "greater"; return; fi
if (( 10#$id1 < 10#$id2 )); then echo "lesser"; return; fi
elif $is_num1; then
echo "lesser"; return # numeric < non-numeric
elif $is_num2; then
echo "greater"; return # non-numeric > numeric
else
if [[ "$id1" > "$id2" ]]; then echo "greater"; return; fi
if [[ "$id1" < "$id2" ]]; then echo "lesser"; return; fi
fi
done
echo "equal"
fi
}

RESULT=$(version_compare "$CURRENT" "$PREVIOUS")

if [[ "$RESULT" == "lesser" ]]; then
echo "::error::Version downgrade detected: $PREVIOUS → $CURRENT"
echo "Version must be incremented, not decremented."
exit 1
fi

echo "✓ Version increment valid: $PREVIOUS → $CURRENT"

test-unit:
permissions:
contents: read
runs-on: ubuntu-latest
timeout-minutes: 15
name: Run Unit Tests
needs:
- check-version
steps:
- uses: actions/checkout@v6
env:
Expand Down Expand Up @@ -47,6 +181,8 @@ jobs:
runs-on: ubuntu-24.04-arm
timeout-minutes: 15
name: Build Application
needs:
- check-version
steps:
- uses: actions/checkout@v6
env:
Expand All @@ -66,17 +202,13 @@ jobs:
restore-keys: |
yarn-modules-${{ runner.arch }}-${{ runner.os }}-

- name: Extract version from tag
id: get_version
run: echo "VITE_BUILD_HASH=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_ENV"

- name: Run Prod build
run: make build
env:
HUSKY: "0"
VITE_RUN_ENVIRONMENT: prod
RunEnvironment: prod
VITE_BUILD_HASH: ${{ env.VITE_BUILD_HASH }}
VITE_BUILD_HASH: ${{ needs.check-version.outputs.version }}

- name: Upload Build files
uses: actions/upload-artifact@v7
Expand Down Expand Up @@ -209,6 +341,57 @@ jobs:
- name: Call the health check script
run: make prod_health_check

make-release:
permissions:
contents: read
runs-on: ubuntu-latest
timeout-minutes: 30
name: Make release tag
needs: [check-version, deploy-prod]
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.AUTOMATED_RELEASE_APP_ID }}
private-key: ${{ secrets.AUTOMATED_RELEASE_APP_KEY }}

- name: Create Release
uses: actions/github-script@v8
env:
VERSION: ${{ needs.check-version.outputs.version }}
IS_PRERELEASE: ${{ needs.check-version.outputs.is-prerelease }}
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const version = process.env.VERSION;
const isPrerelease = process.env.IS_PRERELEASE === 'true';
const tagName = `v${version}`;

try {
const existing = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag: tagName,
});
console.log(`Release already exists: ${existing.data.html_url}`);
return;
} catch (error) {
if (error.status !== 404) throw error;
}

const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tagName,
name: tagName,
target_commitish: context.sha,
generate_release_notes: true,
prerelease: isPrerelease
});

console.log(`Created ${isPrerelease ? 'prerelease' : 'release'}: ${release.data.html_url}`);

publish-node-client:
runs-on: ubuntu-latest
timeout-minutes: 30
Expand All @@ -217,9 +400,7 @@ jobs:
id-token: write
contents: read
needs:
- build
- test-unit
- test-e2e
- make-release
steps:
- uses: actions/checkout@v6
- name: Set up Node
Expand All @@ -240,7 +421,8 @@ jobs:
run: cd dist/clients/typescript-fetch && npm publish --provenance --access public

notify:
needs: [deploy-prod, publish-node-client, test-unit, build, test-e2e]
permissions: {}
needs: [check-version, deploy-prod, publish-node-client, test-unit, build, test-e2e, make-release]
runs-on: ubuntu-latest
if: failure()
steps:
Expand Down
133 changes: 0 additions & 133 deletions .github/workflows/release.yml

This file was deleted.

Loading
Loading