Update From FlareAuth Upstream #12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Update From FlareAuth Upstream | |
| on: | |
| schedule: | |
| - cron: "0 */6 * * *" | |
| workflow_dispatch: | |
| inputs: | |
| upstream_repository: | |
| description: "FlareAuth upstream repository to fetch from." | |
| required: true | |
| default: "saltbo/flareauth" | |
| target_ref: | |
| description: "Upstream branch, tag, or commit to merge. Empty uses latest semver tag." | |
| required: false | |
| default: "" | |
| allow_major_upgrade: | |
| description: "Allow manually requested semver tags from a different major." | |
| required: true | |
| default: "false" | |
| type: choice | |
| options: | |
| - "false" | |
| - "true" | |
| run_e2e: | |
| description: "Run Cucumber E2E before opening the PR." | |
| required: true | |
| default: "false" | |
| type: choice | |
| options: | |
| - "false" | |
| - "true" | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| concurrency: | |
| group: flareauth-upstream-update-${{ github.repository }} | |
| cancel-in-progress: false | |
| jobs: | |
| update: | |
| name: Update Deployment Repository | |
| if: github.repository != 'saltbo/flareauth' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout deployment repository | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup Node | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: 24.14.1 | |
| cache: npm | |
| - name: Merge upstream | |
| id: merge | |
| run: | | |
| set -euo pipefail | |
| upstream_repository="${{ inputs.upstream_repository || 'saltbo/flareauth' }}" | |
| requested_ref="${{ inputs.target_ref || '' }}" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git remote add flareauth-upstream "https://github.com/${upstream_repository}.git" 2>/dev/null || true | |
| git fetch origin main | |
| git fetch flareauth-upstream --tags | |
| current_version="$(node -p "require('./package.json').version")" | |
| current_major="${current_version%%.*}" | |
| if [ -z "$requested_ref" ]; then | |
| target_ref="$( | |
| git tag -l "v${current_major}.*.*" --sort=-v:refname | | |
| grep -E "^v${current_major}\.[0-9]+\.[0-9]+$" | | |
| head -n 1 || | |
| true | |
| )" | |
| if [ -z "$target_ref" ]; then | |
| echo "No compatible upstream semver tags found for current major ${current_major}." | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| echo "branch=flareauth-upgrade/latest" >> "$GITHUB_OUTPUT" | |
| echo "target_ref=none" >> "$GITHUB_OUTPUT" | |
| echo "update_mode=latest" >> "$GITHUB_OUTPUT" | |
| echo "run_e2e=${{ inputs.run_e2e || 'false' }}" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| branch="flareauth-upgrade/latest" | |
| update_mode="latest" | |
| else | |
| target_ref="$requested_ref" | |
| if [[ "$target_ref" =~ ^v([0-9]+)\.[0-9]+\.[0-9]+$ ]]; then | |
| target_major="${BASH_REMATCH[1]}" | |
| if [ "$target_major" != "$current_major" ] && [ "${{ inputs.allow_major_upgrade || 'false' }}" != "true" ]; then | |
| echo "Refusing major upgrade from ${current_version} to ${target_ref}." | |
| echo "Set allow_major_upgrade=true for an intentional major-version PR." | |
| exit 1 | |
| fi | |
| fi | |
| branch="flareauth-upgrade/${target_ref//[^A-Za-z0-9._-]/-}" | |
| update_mode="pinned" | |
| fi | |
| git checkout -B "$branch" | |
| if git rev-parse --verify --quiet "refs/remotes/flareauth-upstream/$target_ref" >/dev/null; then | |
| merge_ref="refs/remotes/flareauth-upstream/$target_ref" | |
| elif git rev-parse --verify --quiet "refs/tags/$target_ref" >/dev/null; then | |
| merge_ref="refs/tags/$target_ref" | |
| else | |
| merge_ref="$target_ref" | |
| fi | |
| if ! git merge --no-edit "$merge_ref"; then | |
| conflicted="$(git diff --name-only --diff-filter=U | sort | tr '\n' ' ')" | |
| if [ "$conflicted" != "package-lock.json package.json " ]; then | |
| echo "Automatic merge failed with unsupported conflicts: ${conflicted}" | |
| exit 1 | |
| fi | |
| node <<'NODE' | |
| const { execFileSync } = require('node:child_process') | |
| const { writeFileSync } = require('node:fs') | |
| const readStage = (stage, path) => | |
| JSON.parse(execFileSync('git', ['show', `:${stage}:${path}`], { encoding: 'utf8' })) | |
| const writeJson = (path, value) => writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`) | |
| const packageOurs = readStage(2, 'package.json') | |
| const packageTheirs = readStage(3, 'package.json') | |
| const mergedPackage = { | |
| ...packageTheirs, | |
| name: packageOurs.name, | |
| scripts: { | |
| ...packageTheirs.scripts, | |
| ...(packageOurs.scripts?.['deploy:prod'] ? { 'deploy:prod': packageOurs.scripts['deploy:prod'] } : {}), | |
| }, | |
| } | |
| writeJson('package.json', mergedPackage) | |
| const lockTheirs = readStage(3, 'package-lock.json') | |
| lockTheirs.name = mergedPackage.name | |
| lockTheirs.version = mergedPackage.version | |
| if (lockTheirs.packages?.['']) { | |
| lockTheirs.packages[''].name = mergedPackage.name | |
| lockTheirs.packages[''].version = mergedPackage.version | |
| } | |
| writeJson('package-lock.json', lockTheirs) | |
| NODE | |
| git add package.json package-lock.json | |
| git commit --no-edit | |
| fi | |
| if git diff --quiet origin/main...HEAD; then | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "branch=$branch" >> "$GITHUB_OUTPUT" | |
| echo "target_ref=$target_ref" >> "$GITHUB_OUTPUT" | |
| echo "update_mode=$update_mode" >> "$GITHUB_OUTPUT" | |
| echo "run_e2e=${{ inputs.run_e2e || 'false' }}" >> "$GITHUB_OUTPUT" | |
| - name: Install dependencies | |
| if: steps.merge.outputs.changed == 'true' | |
| run: npm ci | |
| - name: Install Restish | |
| if: steps.merge.outputs.changed == 'true' | |
| run: | | |
| version=0.21.2 | |
| archive="restish-${version}-linux-amd64.tar.gz" | |
| curl -fsSL "https://github.com/rest-sh/restish/releases/download/v${version}/${archive}" -o "$archive" | |
| curl -fsSL "https://github.com/rest-sh/restish/releases/download/v${version}/checksums.txt" -o checksums.txt | |
| grep " ${archive}$" checksums.txt | sha256sum --check - | |
| tar -xzf "$archive" | |
| sudo install restish /usr/local/bin/restish | |
| restish --version | |
| - name: Typecheck | |
| if: steps.merge.outputs.changed == 'true' | |
| run: npm run typecheck | |
| - name: Lint | |
| if: steps.merge.outputs.changed == 'true' | |
| run: npm run lint | |
| - name: Test | |
| if: steps.merge.outputs.changed == 'true' | |
| run: npm test | |
| - name: Build | |
| if: steps.merge.outputs.changed == 'true' | |
| run: npm run build | |
| - name: Cucumber E2E | |
| if: steps.merge.outputs.changed == 'true' && steps.merge.outputs.run_e2e == 'true' | |
| run: npm run test:e2e | |
| - name: Push upgrade branch | |
| if: steps.merge.outputs.changed == 'true' | |
| run: git push --force-with-lease origin "${{ steps.merge.outputs.branch }}" | |
| - name: Open upgrade PR | |
| if: steps.merge.outputs.changed == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.FLAREAUTH_UPGRADE_TOKEN || secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| branch="${{ steps.merge.outputs.branch }}" | |
| target_ref="${{ steps.merge.outputs.target_ref }}" | |
| update_mode="${{ steps.merge.outputs.update_mode }}" | |
| if [ "$update_mode" = "latest" ]; then | |
| title="chore: update FlareAuth to latest upstream ${target_ref}" | |
| else | |
| title="chore: update FlareAuth upstream to ${target_ref}" | |
| fi | |
| body="$(cat <<EOF | |
| Updates this deployment repository from \`${{ inputs.upstream_repository || 'saltbo/flareauth' }}\` at \`${target_ref}\`. | |
| Update mode: \`${update_mode}\` | |
| Validation run by this workflow: | |
| - \`npm run typecheck\` | |
| - \`npm run lint\` | |
| - \`npm test\` | |
| - \`npm run build\` | |
| E2E run: \`${{ steps.merge.outputs.run_e2e }}\` | |
| Review deployment-specific files such as \`wrangler.toml\`, \`wrangler.preview.toml\`, and \`package.json\` before merging. | |
| EOF | |
| )" | |
| existing="$(gh pr list --head "$branch" --base main --json number --jq '.[0].number // empty')" | |
| if [ -n "$existing" ]; then | |
| gh pr edit "$existing" --title "$title" --body "$body" | |
| else | |
| gh pr create --base main --head "$branch" --title "$title" --body "$body" | |
| fi | |
| - name: No upstream changes | |
| if: steps.merge.outputs.changed == 'false' | |
| run: echo "Deployment repository is already up to date with ${{ steps.merge.outputs.target_ref }}." | |
| upstream-disabled: | |
| name: Disabled In Upstream Repository | |
| if: github.repository == 'saltbo/flareauth' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - run: echo "This workflow is distributed by the upstream template and only runs in deployment repositories." |