From dfc45fdd16a2518e8434e7ebae1ceb8efc7834d0 Mon Sep 17 00:00:00 2001 From: Ashley Li Date: Wed, 18 Mar 2026 02:12:04 -0700 Subject: [PATCH 1/9] Merge release and deploy-prod workflows --- .github/workflows/deploy-prod.yml | 162 +++++++++++++++++++++++++++--- .github/workflows/release.yml | 133 ------------------------ 2 files changed, 148 insertions(+), 147 deletions(-) delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 98d8e7d6..68d5e535 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -2,18 +2,123 @@ name: Deploy all resources to PROD run-name: PROD deploy - @${{ github.actor }} on: - release: - types: [created] - tags: - - 'v*' + push: + branches: + - 'main' + paths: + - 'package.json' jobs: + check-version: + 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@v2 + with: + app-id: ${{ vars.AUTOMATED_RELEASE_APP_ID }} + private-key: ${{ secrets.AUTOMATED_RELEASE_APP_KEY }} + + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 2 + 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 + git show HEAD~1:package.json > /tmp/old-package.json 2>/dev/null || echo '{"version":"0.0.0"}' > /tmp/old-package.json + PREVIOUS_VERSION=$(jq -r '.version' /tmp/old-package.json) + + 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 + run: | + CURRENT="${{ steps.version.outputs.current }}" + PREVIOUS="${{ steps.version.outputs.previous }}" + + # 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 [[ "$v1_pre" > "$v2_pre" ]]; then + echo "greater" + elif [[ "$v1_pre" < "$v2_pre" ]]; then + echo "lesser" + else + 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: @@ -47,6 +152,8 @@ jobs: runs-on: ubuntu-24.04-arm timeout-minutes: 15 name: Build Application + needs: + - check-version steps: - uses: actions/checkout@v6 env: @@ -66,17 +173,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 @@ -209,6 +312,40 @@ jobs: - name: Call the health check script run: make prod_health_check + make-release: + runs-on: ubuntu-latest + timeout-minutes: 30 + name: Make release tag + needs: [check-version, build, test-unit, test-e2e] + steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.AUTOMATED_RELEASE_APP_ID }} + private-key: ${{ secrets.AUTOMATED_RELEASE_APP_KEY }} + + - name: Create Release + uses: actions/github-script@v8 + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + const version = '${{ needs.check-version.outputs.version }}'; + const isPrerelease = ${{ needs.check-version.outputs.is-prerelease }}; + const tagName = `v${version}`; + + 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 @@ -216,10 +353,7 @@ jobs: permissions: id-token: write contents: read - needs: - - build - - test-unit - - test-e2e + needs: [build, test-unit, test-e2e, make-release] steps: - uses: actions/checkout@v6 - name: Set up Node @@ -240,7 +374,7 @@ jobs: run: cd dist/clients/typescript-fetch && npm publish --provenance --access public notify: - needs: [deploy-prod, publish-node-client, test-unit, build, test-e2e] + needs: [deploy-prod, publish-node-client, test-unit, build, test-e2e, make-release] runs-on: ubuntu-latest if: failure() steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 84c142fa..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,133 +0,0 @@ -# .github/workflows/release.yml -name: Auto Release - -on: - push: - branches: - - 'main' - paths: - - 'package.json' - -jobs: - release: - runs-on: ubuntu-latest - steps: - - name: Generate GitHub App token - id: app-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ vars.AUTOMATED_RELEASE_APP_ID }} - private-key: ${{ secrets.AUTOMATED_RELEASE_APP_KEY }} - - - name: Checkout code - uses: actions/checkout@v6 - with: - fetch-depth: 2 - 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 - git show HEAD~1:package.json > /tmp/old-package.json 2>/dev/null || echo '{"version":"0.0.0"}' > /tmp/old-package.json - PREVIOUS_VERSION=$(jq -r '.version' /tmp/old-package.json) - - echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT - echo "previous=$PREVIOUS_VERSION" >> $GITHUB_OUTPUT - - if [ "$CURRENT_VERSION" != "$PREVIOUS_VERSION" ]; then - echo "changed=true" >> $GITHUB_OUTPUT - else - echo "changed=false" >> $GITHUB_OUTPUT - 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 - if: steps.version.outputs.changed == 'true' - run: | - CURRENT="${{ steps.version.outputs.current }}" - PREVIOUS="${{ steps.version.outputs.previous }}" - - # 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 [[ "$v1_pre" > "$v2_pre" ]]; then - echo "greater" - elif [[ "$v1_pre" < "$v2_pre" ]]; then - echo "lesser" - else - 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" - - - name: Create Release - if: steps.version.outputs.changed == 'true' - uses: actions/github-script@v8 - with: - github-token: ${{ steps.app-token.outputs.token }} - script: | - const version = '${{ steps.version.outputs.current }}'; - const isPrerelease = ${{ steps.version.outputs.prerelease }}; - const tagName = `v${version}`; - - 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}`); From b6d8e03a9739b44fd88c486cc5fae80625543539 Mon Sep 17 00:00:00 2001 From: Ashley Li Date: Thu, 19 Mar 2026 15:26:38 -0700 Subject: [PATCH 2/9] Fix release workflow to deploy-prod before make-release before publish-node-client --- .github/workflows/deploy-prod.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 68d5e535..92cc7561 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -316,7 +316,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 name: Make release tag - needs: [check-version, build, test-unit, test-e2e] + needs: [check-version, deploy-prod] steps: - name: Generate GitHub App token id: app-token @@ -353,7 +353,8 @@ jobs: permissions: id-token: write contents: read - needs: [build, test-unit, test-e2e, make-release] + needs: + - make-release steps: - uses: actions/checkout@v6 - name: Set up Node From aa97c0cbf18d5aafe8aa900577b93cf5b909d73b Mon Sep 17 00:00:00 2001 From: Ashley Li Date: Thu, 19 Mar 2026 16:46:19 -0700 Subject: [PATCH 3/9] Compare current version against latest tag --- .github/workflows/deploy-prod.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 92cc7561..7b0b3b02 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -5,8 +5,6 @@ on: push: branches: - 'main' - paths: - - 'package.json' jobs: check-version: @@ -25,7 +23,7 @@ jobs: - name: Checkout code uses: actions/checkout@v6 with: - fetch-depth: 2 + fetch-tags: true token: ${{ steps.app-token.outputs.token }} - name: Check if version changed @@ -35,8 +33,9 @@ jobs: CURRENT_VERSION=$(jq -r '.version' package.json) # Get previous version - git show HEAD~1:package.json > /tmp/old-package.json 2>/dev/null || echo '{"version":"0.0.0"}' > /tmp/old-package.json - PREVIOUS_VERSION=$(jq -r '.version' /tmp/old-package.json) + LATEST_TAG=$(git tag -l 'v*' --sort=-version:refname | head -n 1) + PREVIOUS_VERSION=${LATEST_TAG#v} + PREVIOUS_VERSION=${PREVIOUS_VERSION:-0.0.0} echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT echo "previous=$PREVIOUS_VERSION" >> $GITHUB_OUTPUT From 63317fbf51f3e9e71cfa8b2308fb01f3289b2395 Mon Sep 17 00:00:00 2001 From: Ashley Li Date: Fri, 20 Mar 2026 09:32:20 -0700 Subject: [PATCH 4/9] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 226c6280..3231ef48 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "infra-core", - "version": "4.8.5", + "version": "4.8.6", "private": true, "type": "module", "workspaces": [ From ee5e5b19e3a96accf4f58f02e7ce398efd6bf2c0 Mon Sep 17 00:00:00 2001 From: Ashley Li Date: Fri, 3 Apr 2026 00:23:32 -0500 Subject: [PATCH 5/9] Fix permissions, version check, and release checking --- .github/workflows/deploy-prod.yml | 64 ++++++++++++++++++++++++++----- src/ui/util/revision.ts | 2 +- 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 5103ebb2..04d9b62e 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -8,6 +8,8 @@ on: jobs: check-version: + permissions: + contents: read runs-on: ubuntu-latest outputs: is-prerelease: ${{ steps.version.outputs.prerelease }} @@ -15,7 +17,7 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3 with: app-id: ${{ vars.AUTOMATED_RELEASE_APP_ID }} private-key: ${{ secrets.AUTOMATED_RELEASE_APP_KEY }} @@ -53,10 +55,10 @@ jobs: fi - name: Validate version increment + env: + CURRENT: ${{ steps.version.outputs.current }} + PREVIOUS: ${{ steps.version.outputs.previous }} run: | - CURRENT="${{ steps.version.outputs.current }}" - PREVIOUS="${{ steps.version.outputs.previous }}" - # Function to compare semver versions version_compare() { # Strip prerelease suffix for base comparison @@ -91,11 +93,35 @@ jobs: echo "greater" elif [[ -n "$v1_pre" && -z "$v2_pre" ]]; then echo "lesser" - elif [[ "$v1_pre" > "$v2_pre" ]]; then - echo "greater" - elif [[ "$v1_pre" < "$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 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 } @@ -312,6 +338,8 @@ jobs: run: make prod_health_check make-release: + permissions: + contents: read runs-on: ubuntu-latest timeout-minutes: 30 name: Make release tag @@ -326,13 +354,28 @@ jobs: - 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 = '${{ needs.check-version.outputs.version }}'; - const isPrerelease = ${{ needs.check-version.outputs.is-prerelease }}; + 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, @@ -374,6 +417,7 @@ jobs: run: cd dist/clients/typescript-fetch && npm publish --provenance --access public notify: + permissions: {} needs: [deploy-prod, publish-node-client, test-unit, build, test-e2e, make-release] runs-on: ubuntu-latest if: failure() diff --git a/src/ui/util/revision.ts b/src/ui/util/revision.ts index 6dbaeed2..5ee7ac44 100644 --- a/src/ui/util/revision.ts +++ b/src/ui/util/revision.ts @@ -1,5 +1,5 @@ export function getCurrentRevision() { return import.meta.env.VITE_BUILD_HASH - ? import.meta.env.VITE_BUILD_HASH.substring(0, 7) + ? import.meta.env.VITE_BUILD_HASH : "unknown"; } From 189a735ae8b1a7fbed45586ade1fc8ed21234cbe Mon Sep 17 00:00:00 2001 From: Ashley Li Date: Fri, 3 Apr 2026 01:36:52 -0500 Subject: [PATCH 6/9] Serialize workflow --- .github/workflows/deploy-prod.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 04d9b62e..5982d77d 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -1,6 +1,10 @@ name: Deploy all resources to PROD run-name: PROD deploy - @${{ github.actor }} +concurrency: + group: prod-release-${{ github.ref }} + cancel-in-progress: false + on: push: branches: From 58b659f9998412b6f4fc0fac31237c84ec76548a Mon Sep 17 00:00:00 2001 From: Ashley Li Date: Fri, 3 Apr 2026 12:44:53 -0500 Subject: [PATCH 7/9] Notify when version check fails --- .github/workflows/deploy-prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 5982d77d..b3c1a54d 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -422,7 +422,7 @@ jobs: notify: permissions: {} - needs: [deploy-prod, publish-node-client, test-unit, build, test-e2e, make-release] + needs: [check-version, deploy-prod, publish-node-client, test-unit, build, test-e2e, make-release] runs-on: ubuntu-latest if: failure() steps: From cae29da66fc9c676b1dc52ef2f48fb357f0b0b05 Mon Sep 17 00:00:00 2001 From: Ashley Li Date: Sat, 4 Apr 2026 00:55:18 -0500 Subject: [PATCH 8/9] Bump version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4f7d90c1..9cf90756 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "infra-core", - "version": "4.8.9", + "version": "4.8.10", "private": true, "type": "module", "workspaces": [ @@ -93,4 +93,4 @@ "pdfjs-dist": "^4.8.69", "form-data": "^4.0.4" } -} \ No newline at end of file +} From 5919fc5a354fff4b8f7d8347ee80b7e74448822b Mon Sep 17 00:00:00 2001 From: Ashley Li Date: Sat, 4 Apr 2026 01:11:58 -0500 Subject: [PATCH 9/9] Sort correctly on prereleases --- .github/workflows/deploy-prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index b3c1a54d..c67be406 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -39,7 +39,7 @@ jobs: CURRENT_VERSION=$(jq -r '.version' package.json) # Get previous version - LATEST_TAG=$(git tag -l 'v*' --sort=-version:refname | head -n 1) + 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}