diff --git a/.github/actions/configure-node/action.yml b/.github/actions/configure-node/action.yml new file mode 100644 index 00000000..bf37d0f2 --- /dev/null +++ b/.github/actions/configure-node/action.yml @@ -0,0 +1,20 @@ +name: configure-node +description: Shareable action to configure Node.js for project environment +outputs: + node-version: + description: Node.js version + value: ${{ steps.setup-node.outputs.node-version }} +runs: + using: 'composite' + steps: + - uses: actions/setup-node@v3 + id: setup-node + with: + node-version-file: '.node-version' + - uses: actions/cache@v3 + id: cache + with: + path: '~/.pnpm-store' + key: ${{ runner.os }}-${{ steps.setup-node.outputs.node-version }}-${{ hashFiles('pnpm-lock.yaml') }} + - run: corepack enable + shell: bash diff --git a/.github/actions/get-semver-label-from-pr/action.yml b/.github/actions/get-semver-label-from-pr/action.yml new file mode 100644 index 00000000..921aabc7 --- /dev/null +++ b/.github/actions/get-semver-label-from-pr/action.yml @@ -0,0 +1,34 @@ +# used to extract the version from a semver label +name: get-semver-label-from-pr +description: Gets semver label from PR event +outputs: + semver-label: + description: Semver label name + value: ${{ steps.get-label.outputs.result }} + semver-bump: + description: Semver bump type + value: ${{ steps.get-bump.outputs.result }} +runs: + using: 'composite' + steps: + - uses: actions/github-script@v6 + id: get-label + with: + script: | + const labels = context.payload.pull_request.labels + const semverLabels = labels.filter(label => label.name.startsWith('semver:')) + if (semverLabels.length === 0) { + return core.setFailed('No semver label found') + } + if (semverLabels.length > 1) { + return core.setFailed('Cannot have multiple semver labels') + } + return semverLabels[0].name + - uses: actions/github-script@v6 + id: get-bump + if: ${{ success() }} + env: + LABEL_NAME: ${{ steps.get-label.outputs.result }} + with: + script: | + return process.env.LABEL_NAME.replace('semver: ', '') diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf1a2fca..3a66d9c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,3 +85,9 @@ jobs: VITE_HOST: ${{ secrets.VITE_HOST }} VITE_NEXTAUTH_URL: ${{ secrets.VITE_NEXTAUTH_URL }} VITE_DISCORD_GUILD_ID: ${{ secrets.VITE_DISCORD_GUILD_ID }} + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: apps/discord-bot-frontend/playwright-report/ + retention-days: 30 diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml deleted file mode 100644 index 96c2edbd..00000000 --- a/.github/workflows/create-release.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: create-release -on: - workflow_dispatch: - inputs: - type: - type: choice - description: 'Type of release' - options: # put in ascending order to not accidentally cut a "major" release PR - - prerelease - - prepatch - - preminor - - premajor - - patch - - minor - - major - required: true -jobs: - create-release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - persist-credentials: true - - uses: pnpm/action-setup@v2 - - name: set git config for pnpm version, git push - run: | - git config --global user.name "GitHub Actions" - git config --global user.email noreply@github.com - - name: set-version - id: version - run: | - if [[ ${{ github.event.inputs.type }} != pre* ]] - then - version=$(pnpm version ${{ github.event.inputs.type }} --no-git-tag-version) - else - version=$(pnpm version ${{ github.event.inputs.type }} --preid next --no-git-tag-version) - fi - echo "version=$version" >> $GITHUB_OUTPUT - - run: echo "::debug::version is ${{ steps.version.outputs.version }}" - - run: echo "::notice title="releasing"::${{ steps.version.outputs.version }}" - - if: ${{ steps.version.outputs.version == '' }} - run: echo "::error ::Invalid version" && exit 1 - # - uses: actions/setup-node@v3 # is this needed??? - # with: - # node-version-file: '.nvmrc' - # cache: 'pnpm' - - id: branch - run: echo "name=release/${{ steps.version.outputs.version }}" >> $GITHUB_OUTPUT - - name: Create release branch - run: git checkout -b ${{ steps.branch.outputs.name }} - # this is not required when pnpm is creating the git tag - - name: add and commit changes - run: | - git add package.json - git commit -m '[automated] release: ${{ steps.version.outputs.version }}' - - run: git push origin ${{ steps.branch.outputs.name }} - - id: pr - run: | - url=$(gh pr create --title "release: ${{ steps.version.outputs.version }}" --body "Automated PR created to release a new version of the project") - echo "link=$url" >> $GITHUB_OUTPUT - - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: echo '${{ steps.pr.outputs.link }}' - - run: echo ::notice title="created"::${{ steps.pr.outputs.link }} - - run: echo ::notice title="type"::${{ github.event.inputs.type }} diff --git a/.github/workflows/failed-release-notification.yml b/.github/workflows/failed-release-notification.yml new file mode 100644 index 00000000..6cfe5f21 --- /dev/null +++ b/.github/workflows/failed-release-notification.yml @@ -0,0 +1,20 @@ +name: failed-release-notification +on: + deployment_status: +env: + NEEDS_TRIAGE_LABEL: 'needs: triage' +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: create an issue + uses: actions/script@v6 + with: + script: | + github.rest.issues.create({ + title: "Release failed" + owner: context.repo.owner, + repo: context.repo.repo, + labels: ["${{ env.NEEDS_TRIAGE_LABEL }}"] + }) + # @TODO: message to Slack? diff --git a/.github/workflows/issues-pending-author.yml b/.github/workflows/issues-pending-author.yml index aa66110e..c88f9097 100644 --- a/.github/workflows/issues-pending-author.yml +++ b/.github/workflows/issues-pending-author.yml @@ -6,12 +6,12 @@ env: PENDING_RESPONSE_LABEL: 'pending: author' jobs: issue_commented: + runs-on: ubuntu-latest if: | !github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'pending: author') - runs-on: ubuntu-latest steps: - uses: siegerts/pending-author-response@v1 with: github-token: ${{ secrets.GITHUB_TOKEN }} - pending-response-label: ${{ env.PENDING_RESPONSE_LABEL }} \ No newline at end of file + pending-response-label: ${{ env.PENDING_RESPONSE_LABEL }} diff --git a/.github/workflows/pr-has-semver-label.yml b/.github/workflows/pr-has-semver-label.yml new file mode 100644 index 00000000..c0a244ed --- /dev/null +++ b/.github/workflows/pr-has-semver-label.yml @@ -0,0 +1,31 @@ +name: pr-has-semver-label +on: + pull_request_target: + branches: + - main + types: + - opened + - labeled + - unlabeled + - ready_for_review +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + pr-has-semver-label: + if: github.event.pull_request.draft == false + name: pr-has-semver-label + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + sparse-checkout: | + .github + - name: Does the PR have a single semver label? + id: get-semver-label + uses: ./.github/actions/get-semver-label-from-pr + - name: update GitHub Action summary + id: set-semver-label-output + if: ${{ success() }} + run: echo "PR merge will cause a ${{ steps.get-semver-label.outputs.semver-bump }} version bump" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release-env.yml b/.github/workflows/release-env.yml index 221f03d4..29bd477e 100644 --- a/.github/workflows/release-env.yml +++ b/.github/workflows/release-env.yml @@ -7,13 +7,9 @@ on: type: string required: true version: - description: 'Version of the release' + description: 'New version to deploy' type: string required: true - is-prerelease: - description: 'Determines if the release is a prerelease' - type: boolean - required: true aws-region: description: 'AWS Region to deploy to' type: string @@ -32,14 +28,12 @@ jobs: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} role-session-name: ${{ secrets.AWS_ROLE_SESSION_NAME }} aws-region: ${{ inputs.aws-region }} - - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 with: - node-version-file: '.nvmrc' - cache: 'pnpm' - - run: pnpm install --frozen-lockfile --silent - - run: pnpm build:lib + persist-credentials: false + - uses: ./.github/actions/configure-node + - run: pnpm install + - run: pnpm build - name: cdk synth run: | pnpm --filter ./cdk run synth \ @@ -47,27 +41,28 @@ jobs: --context version=${{ inputs.version }} \ --quiet - name: cdk deploy + timeout-minutes: 20 run: | pnpm --filter ./cdk run deploy \ + --require-approval never \ --context env=${{ inputs.env }} \ --context version=${{ inputs.version }} env: VITE_DISCORD_GUILD_ID: ${{ secrets.VITE_DISCORD_GUILD_ID }} VITE_NEXTAUTH_URL: ${{ secrets.VITE_NEXTAUTH_URL }} VITE_HOST: ${{ secrets.VITE_HOST }} - - name: release - run: | - # exit early when performing a sequential release (next -> main) - if [ ${{ inputs.env }} != main ]; then exit 0; fi - # otherwise proceed with creating GitHub release and git tag - if [ ${{ inputs.is-prerelease }} == true ] - then - url=$(gh release create ${{ inputs.version }} --generate-notes --prerelease) - else - url=$(gh release create ${{ inputs.version }} --generate-notes) - fi - tag=$(echo "${url/releases\/tag/tree}") - echo ::notice title="release"::$url - echo ::notice title="tag"::$tag + test-env: + runs-on: ubuntu-latest + needs: [release-env] + environment: ${{ inputs.env }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: ./.github/actions/configure-node + - run: pnpm install + # run tests on live environment + - run: pnpm run test env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TEST_HOST: ${{ secrets.VITE_HOST }} + DISCORD_GUILD_ID: ${{ secrets.VITE_DISCORD_GUILD_ID }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 81555b69..1ffca426 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,9 @@ name: release +concurrency: + group: release + # do not cancel previous deployment, this may cause a CloudFormation rollback which can cause the subsequent deployment to fail waiting for the rollback to complete + # @TODO: can CDK wait for the rollback to complete before proceeding? this would allow us to cancel in-progress runs + cancel-in-progress: false on: pull_request: branches: @@ -14,33 +19,81 @@ jobs: # 3. PR branch name begins with "release/" if: | github.event.pull_request.head.repo.full_name == github.repository && - github.event.pull_request.merged == true && - startsWith(github.event.pull_request.head.ref, 'release/') + github.event.pull_request.merged == true outputs: - version: ${{ steps.version.outputs.result }} - is-prerelease: ${{ contains(steps.version.outputs.result, 'next') }} + version-bump: ${{ steps.get-semver.outputs.semver-bump }} steps: - - name: extract-version - id: version - uses: actions/github-script@0.2.0 + - uses: actions/checkout@v4 + with: + persist-credentials: false + sparse-checkout: | + .github + - uses: ./.github/actions/get-semver-label-from-pr + id: get-semver + # @TODO: verify whether this commit changes the env deployment commit (where it should show the PR's commit) + increase-version: + runs-on: ubuntu-latest + needs: [verify-run] + outputs: + new-version: ${{ steps.version.outputs.result }} + steps: + - uses: actions/checkout@v4 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - return context.payload.pull_request.title.replace(/release\: /, ''); - prerelease: - needs: verify-run + persist-credentials: true + sparse-checkout: | + package.json + - name: configure git credentials + run: | + git config --global user.name "GitHub Actions" + git config --global user.email noreply@github.com + - name: bump version + id: version + run: pnpm version ${{ needs.verify-run.outputs.version-bump }} --no-git-tag-version + - name: push version changes + run: | + git add package.json + git commit -m "[automated] ${{ steps.version.outputs.result }}" + # push back to PR base -- i.e. the "main" branch + git push origin ${{ github.event.pull_request.base.ref }} + # release to "next" environment + release-to-next: + needs: + - verify-run + - increase-version uses: ./.github/workflows/release-env.yml secrets: inherit with: env: next - version: ${{ needs.verify-run.outputs.version }} - is-prerelease: true - release: - needs: [verify-run, prerelease] - if: ${{ fromJSON(needs.verify-run.outputs.is-prerelease) == false }} + version: ${{ needs.increase-version.outputs.new-version }} + # release to "main" environment + release-to-main: + needs: + - verify-run + - increase-version + - release-to-next uses: ./.github/workflows/release-env.yml secrets: inherit with: env: main - version: ${{ needs.verify-run.outputs.version }} - is-prerelease: false + version: ${{ needs.increase-version.outputs.new-version }} + # release on GitHub + create-github-release: + runs-on: ubuntu-latest + needs: + - verify-run + - increase-version + - release-to-next + - release-to-main + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: create + run: | + # otherwise proceed with creating GitHub release and git tag + url=$(gh release create ${{ needs.increase-version.outputs.new-version }} --generate-notes) + tag=$(echo "${url/releases\/tag/tree}") + echo ::notice title="release"::$url + echo ::notice title="tag"::$tag + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}