Skip to content
This repository has been archived by the owner on Aug 22, 2024. It is now read-only.

docs(readme): note project deprecation #6

docs(readme): note project deprecation

docs(readme): note project deprecation #6

name: build-test-deploy
# * Note: feel free to run this workflow in forks (e.g. for testing your PR)
# * without worrying about this workflow attempting to deploy a package 🚀
# ! If you want to use the CD pipeline, the following secrets must be defined:
# ! CODECOV_TOKEN (optional for public repos)
# ! GPG_PRIVATE_KEY
# ! GPG_PASSPHRASE
# ! GH_TOKEN
# ! NPM_TOKEN
on:
workflow_dispatch: # * Treated like a `push` event with a user-defined ref
push:
tags-ignore: ['**']
branches-ignore: ['dependabot/**', 'snyk-**', 'no-ci/**']
pull_request:
types: [opened, reopened, synchronize]
branches: [main, canary]
env:
# * The name and email used to author commits and interact with GitHub
# ! This should correspond to the identity associated with the GH_TOKEN secret
CI_COMMITTER_NAME: xunn-bot
CI_COMMITTER_EMAIL: [email protected]
# * Selectively enable debugger verbose output in the pipeline
# ? See also: https://www.npmjs.com/package/debug#wildcards
# DEBUG: 'projector-lens-next:*'
# * The version of node to load into each job. Must NOT be quoted!
NODE_CURRENT_VERSION: 15.x
# * Node versions to test against (NODE_CURRENT_VERSION included
# * automatically). Must be quoted!
NODE_TEST_VERSIONS: '"12.x", "14.x"'
# * Webpack versions to test against. Must be quoted!
WEBPACK_TEST_VERSIONS: '"5.x"'
# * Regular expressions (w/ proper escaping) for skipping CI/CD
# ! These also have to be updated in .changelogrc.js and cleanup.yml
CI_SKIP_REGEX: '\[skip ci\]|\[ci skip\]'
CD_SKIP_REGEX: '\[skip cd\]|\[cd skip\]'
# * A list of GitHub repo namespaces allowed to trigger the CD pipeline
# ? Add your GitHub user/org (github.repository_owner) to enable CD pipeline.
# ? Repo owner names are compared to the below in a case insensitive fashion
# ! (you'll need to provide your own secrets or the pipeline will error)
REPO_OWNER_WHITELIST: xunnamius, ergodark, nhscc
# * A list of exact GitHub logins whose passing PRs will be auto-merged
# ? Add your bot user's login name (github.actor) to enable auto-merge
# ! WARNING: unlike REPO_OWNER_WHITELIST, matching is case sensitive.
# ! WARNING: this allows 3rd party code to be merged and released without
# ! any human oversight. Only allow this for trusted actors, like dependabot!
AUTOMERGE_ACTOR_WHITELIST: dependabot[bot], xunn-bot
# * A list of exact GitHub logins whose auto-merge attempts should be retried
# * on failure even when the PR is not mergeable.
# ? Only add bots that actively synchronize their PRs, like dependabot!
# ! WARNING: listing a bot here that creates PRs that are never synchronized
# ! could waste hundreds or even thousands of Actions minutes and $$$!
AUTOMERGE_RETRY_WHITELIST: dependabot[bot]
# * Npm audit will fail upon encountering problems of at least this severity:
NPM_AUDIT_FAIL_LEVEL: high
# * Attempt to upload project coverage data to codecov
UPLOAD_CODE_COVERAGE: true
jobs:
metadata:
name: 'gather-metadata'
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
current-branch: ${{ steps.branch.outputs.current-branch }}
has-proper-owner: ${{ steps.owner.outputs.is-whitelisted }}
can-automerge:
${{ steps.automerge.outputs.allowed-to-automerge == 'true' }}
can-retry-automerge:
${{ steps.automerge-retry.outputs.allowed-to-retry-automerge == 'true'
}}
should-skip-ci: ${{ steps.skip.outputs.should-skip-ci == 'true' }}
should-skip-cd: ${{ steps.skip.outputs.should-skip-cd == 'true' }}
node-matrix: ${{ steps.set-matrix.outputs.node-matrix }}
webpack-matrix: ${{ steps.set-matrix.outputs.webpack-matrix }}
has-deploy: ${{ steps.data.outputs.has-deploy == 'true' }}
has-release-config: ${{ steps.data.outputs.has-release-config == 'true' }}
has-docs: ${{ steps.data.outputs.has-docs == 'true' }}
has-externals: ${{ steps.data.outputs.has-externals == 'true' }}
has-integration-node:
${{ steps.data.outputs.has-integration-node == 'true' }}
has-integration-externals:
${{ steps.data.outputs.has-integration-externals == 'true' }}
has-integration-client:
${{ steps.data.outputs.has-integration-client == 'true' }}
has-integration-webpack:
${{ steps.data.outputs.has-integration-webpack == 'true' }}
steps:
- name: Report DEBUG mode status
run: |
if ! [ -z "$DEBUG" ]; then
echo 'PIPELINE IS RUNNING IN DEBUG MODE!'
else
echo '(pipeline is not running in debug mode)'
fi
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 1 # ! We only skip if the tippy top commit says so!
- name: Determine should-skip
id: skip
run: |
set +e
LAST_COMMIT_MSG=$(git log -1 --pretty=format:"%s")
! [ -z "$DEBUG" ] && echo "LAST_COMMIT_MSG=$LAST_COMMIT_MSG" || true
echo "$LAST_COMMIT_MSG" | grep -qE "$CI_SKIP_REGEX"
[ $? -ne 0 ] && CI_SKIP=false || CI_SKIP=true
! [ -z "$DEBUG" ] && echo "CI_SKIP=$CI_SKIP" || true
[ "$CI_SKIP" = 'true' ] || echo "$LAST_COMMIT_MSG" | grep -qE "$CD_SKIP_REGEX"
[ $? -ne 0 ] && CD_SKIP=false || CD_SKIP=true
! [ -z "$DEBUG" ] && echo "CD_SKIP=$CD_SKIP" || true
echo "::set-output name=should-skip-ci::$CI_SKIP"
! [ -z "$DEBUG" ] && echo "set-output name=should-skip-ci::$CI_SKIP" || true
echo "::set-output name=should-skip-cd::$CD_SKIP"
! [ -z "$DEBUG" ] && echo "set-output name=should-skip-cd::$CD_SKIP" || true
- name: Determine matrixes
if: steps.skip.outputs.should-skip-ci != 'true'
id: set-matrix
run: |
echo "::set-output name=node-matrix::{\"node\":[$NODE_TEST_VERSIONS, \"$NODE_CURRENT_VERSION\"]}"
echo "::set-output name=webpack-matrix::{\"webpack\":[$WEBPACK_TEST_VERSIONS]}"
! [ -z "$DEBUG" ] && echo "set-output name=node-matrix::{\"node\":[$NODE_TEST_VERSIONS, \"$NODE_CURRENT_VERSION\"]}" || true
! [ -z "$DEBUG" ] && echo "set-output name=webpack-matrix::{\"webpack\":[$WEBPACK_TEST_VERSIONS]}" || true
- name: Gather branch metadata
if: steps.skip.outputs.should-skip-ci != 'true'
uses: nelonoel/[email protected]
- name: Determine current-branch
if: steps.skip.outputs.should-skip-ci != 'true'
id: branch
run: |
echo "::set-output name=current-branch::$BRANCH_NAME"
! [ -z "$DEBUG" ] && echo "set-output name=current-branch::$BRANCH_NAME" || true
- name: Verify repository owner against deploy whitelist
if: steps.skip.outputs.should-skip-ci != 'true'
id: owner
run: |
set +e
node -e "process.exit('${{ env.REPO_OWNER_WHITELIST }}'.split(',').some(o => o.trim().toLowerCase() == '${{ github.repository_owner }}'.toLowerCase()) ? 0 : 1)"
RESULT=`[ $? -eq 0 ] && echo 'true' || echo 'false'`
echo "::set-output name=is-whitelisted::$RESULT"
! [ -z "$DEBUG" ] && echo "set-output name=is-whitelisted::$RESULT" || true
- name: Verify workflow actor against auto-merge whitelist
if: steps.skip.outputs.should-skip-ci != 'true'
id: automerge
run: |
set +e
node -e "process.exit('${{ env.AUTOMERGE_ACTOR_WHITELIST }}'.split(',').some(o => o.trim() == '${{ github.actor }}') ? 0 : 1)"
RESULT=`[ $? -eq 0 ] && echo 'true' || echo 'false'`
echo "::set-output name=allowed-to-automerge::$RESULT"
! [ -z "$DEBUG" ] && echo "set-output name=allowed-to-automerge::$RESULT" || true
- name: Verify workflow actor against auto-merge retry whitelist
if: steps.skip.outputs.should-skip-ci != 'true'
id: automerge-retry
run: |
set +e
node -e "process.exit('${{ env.AUTOMERGE_RETRY_WHITELIST }}'.split(',').some(o => o.trim() == '${{ github.actor }}') ? 0 : 1)"
RESULT=`[ $? -eq 0 ] && echo 'true' || echo 'false'`
echo "::set-output name=allowed-to-retry-automerge::$RESULT"
! [ -z "$DEBUG" ] && echo "set-output name=allowed-to-retry-automerge::$RESULT" || true
- name: Use node ${{ env.NODE_CURRENT_VERSION }}
if: steps.skip.outputs.should-skip-ci != 'true'
uses: actions/[email protected]
with:
node-version: ${{ env.NODE_CURRENT_VERSION }}
- name: Gather remaining metadata
if: steps.skip.outputs.should-skip-ci != 'true'
id: data
run: |
set +e
NPMR=`npm run list-tasks`
CODE=$?
! [ -z "$DEBUG" ] && echo 'NPMR=' $NPMR || true
if [ $CODE -ne 0 ]; then
echo '::error::npm script "list-tasks" failed'
exit 1
fi
if [ -r release.config.js ]; then HAS_RCNF=true; else HAS_RCNF=false; fi
echo $NPMR | grep -qe '\sbuild-docs\s'
if [ $? -eq 0 ]; then HAS_DOCS=true; else HAS_DOCS=false; fi
echo $NPMR | grep -qe '\sbuild-externals\s'
if [ $? -eq 0 ]; then HAS_EXTS=true; else HAS_EXTS=false; fi
echo $NPMR | grep -qe '\stest-integration-node\s'
if [ $? -eq 0 ]; then HAS_NODE=true; else HAS_NODE=false; fi
echo $NPMR | grep -qe '\stest-integration-externals\s'
if [ $? -eq 0 ]; then HAS_IEXT=true; else HAS_IEXT=false; fi
echo $NPMR | grep -qe '\stest-integration-client\s'
if [ $? -eq 0 ]; then HAS_BWSR=true; else HAS_BWSR=false; fi
echo $NPMR | grep -qe '\stest-integration-webpack\s'
if [ $? -eq 0 ]; then HAS_WEBP=true; else HAS_WEBP=false; fi
echo $NPMR | grep -qe '\sdeploy\s'
if [ $? -eq 0 ]; then HAS_DPLY=true; else HAS_DPLY=false; fi
echo "::set-output name=has-release-config::$HAS_RCNF"
echo "::set-output name=has-docs::$HAS_DOCS"
echo "::set-output name=has-externals::$HAS_EXTS"
echo "::set-output name=has-integration-node::$HAS_NODE"
echo "::set-output name=has-integration-externals::$HAS_IEXT"
echo "::set-output name=has-integration-client::$HAS_BWSR"
echo "::set-output name=has-integration-webpack::$HAS_WEBP"
echo "::set-output name=has-deploy::$HAS_DPLY"
! [ -z "$DEBUG" ] && echo "set-output name=has-release-config::$HAS_RCNF" || true
! [ -z "$DEBUG" ] && echo "set-output name=has-docs::$HAS_DOCS" || true
! [ -z "$DEBUG" ] && echo "set-output name=has-externals::$HAS_EXTS" || true
! [ -z "$DEBUG" ] && echo "set-output name=has-integration-node::$HAS_NODE" || true
! [ -z "$DEBUG" ] && echo "set-output name=has-integration-externals::$HAS_IEXT" || true
! [ -z "$DEBUG" ] && echo "set-output name=has-integration-client::$HAS_BWSR" || true
! [ -z "$DEBUG" ] && echo "set-output name=has-integration-webpack::$HAS_WEBP" || true
! [ -z "$DEBUG" ] && echo "set-output name=has-deploy::$HAS_DPLY" || true
if [ "$HAS_EXTS" != "$HAS_IEXT" ]; then
echo '::error::expected both 1) `build-externals` and 2) `test-integration-externals` scripts to be defined in package.json'
exit 2
fi
if [ "$HAS_DOCS" = 'false' ]; then
echo '::warning::no `build-docs` script defined in package.json'
fi
check-audit:
name: '[CI] check-audit'
runs-on: ubuntu-latest
timeout-minutes: 10
needs: metadata
if: needs.metadata.outputs.should-skip-ci != 'true'
steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Reconfigure git auth
run:
git config --global url."https://${{ secrets.GH_TOKEN
}}@github.com/".insteadOf ssh://[email protected]/
- name: Use node ${{ env.NODE_CURRENT_VERSION }}
uses: actions/[email protected]
with:
node-version: ${{ env.NODE_CURRENT_VERSION }}
- name: Npm audit
run: npm audit --audit-level=${{ env.NPM_AUDIT_FAIL_LEVEL }}
lint:
name: '[CI] lint'
runs-on: ubuntu-latest
timeout-minutes: 10
needs: metadata
if: needs.metadata.outputs.should-skip-ci != 'true'
steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Reconfigure git auth
run:
git config --global url."https://${{ secrets.GH_TOKEN
}}@github.com/".insteadOf ssh://[email protected]/
- name: Use node ${{ env.NODE_CURRENT_VERSION }}
uses: actions/[email protected]
with:
node-version: ${{ env.NODE_CURRENT_VERSION }}
- name: Cache npm
uses: actions/[email protected]
id: cache-npm
with:
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
path: ~/.npm
restore-keys: npm-${{ runner.os }}-
- name: Install CI dependencies
run: |
npm ci
PEER_DEPS=$(node -e 'console.log(Object.entries(require("./package.json").peerDependencies || {}).map(([p,v]) => `${p}@${v}`).join(" "))')
! [ -z "$DEBUG" ] && echo "(install targets) PEER_DEPS='$PEER_DEPS'" || true
! [ -z "$PEER_DEPS" ] && npm install --no-save $PEER_DEPS || true
- name: Lint
run: npm run lint
test-unit:
name: '[CI] test-unit'
runs-on: ubuntu-latest
timeout-minutes: 10
needs: metadata
if: needs.metadata.outputs.should-skip-ci != 'true'
steps:
- name: Checkout
uses: actions/checkout@v2
with:
# ? codecov-action requires access to git history
fetch-depth: 0
persist-credentials: false
- name: Reconfigure git auth
run:
git config --global url."https://${{ secrets.GH_TOKEN
}}@github.com/".insteadOf ssh://[email protected]/
- name: Use node ${{ env.NODE_CURRENT_VERSION }}
uses: actions/[email protected]
with:
node-version: ${{ env.NODE_CURRENT_VERSION }}
- name: Cache npm
uses: actions/[email protected]
id: cache-npm
with:
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
path: ~/.npm
restore-keys: npm-${{ runner.os }}-
- name: Install CI dependencies
run: |
npm ci
PEER_DEPS=$(node -e 'console.log(Object.entries(require("./package.json").peerDependencies || {}).map(([p,v]) => `${p}@${v}`).join(" "))')
! [ -z "$DEBUG" ] && echo "(install targets) PEER_DEPS='$PEER_DEPS'" || true
! [ -z "$PEER_DEPS" ] && npm install --no-save $PEER_DEPS || true
- name: Test unit
run: npm run test-unit
- name: Attempt to upload coverage data to codecov
if: env.UPLOAD_CODE_COVERAGE == 'true'
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
fail_ci_if_error: false
- name: Issue any codecov-related warnings
if: env.UPLOAD_CODE_COVERAGE != 'true'
run: echo '::warning::no code coverage data uploaded for this run'
test-integration-node:
name: '[CI] test-integration-node'
runs-on: ubuntu-latest
timeout-minutes: 10
needs: metadata
if: |
needs.metadata.outputs.should-skip-ci != 'true'
&& needs.metadata.outputs.has-integration-node == 'true'
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.metadata.outputs.node-matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Reconfigure git auth
run:
git config --global url."https://${{ secrets.GH_TOKEN
}}@github.com/".insteadOf ssh://[email protected]/
- name: Use node ${{ matrix.node }}
uses: actions/[email protected]
with:
node-version: ${{ matrix.node }}
- name: Cache npm
uses: actions/[email protected]
id: cache-npm
with:
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
path: ~/.npm
restore-keys: npm-${{ runner.os }}-
- name: Install CI dependencies
run: |
npm ci
PEER_DEPS=$(node -e 'console.log(Object.entries(require("./package.json").peerDependencies || {}).map(([p,v]) => `${p}@${v}`).join(" "))')
! [ -z "$DEBUG" ] && echo "(install targets) PEER_DEPS='$PEER_DEPS'" || true
! [ -z "$PEER_DEPS" ] && npm install --no-save $PEER_DEPS || true
- name: Build distributables
run: npm run build-dist
- name: Test integration
env:
MATRIX_NODE_VERSION: ${{ matrix.node }}
run: npm run test-integration-node
test-integration-externals:
name: '[CI] test-integration-externals'
runs-on: ubuntu-latest
timeout-minutes: 10
needs: metadata
if: |
needs.metadata.outputs.should-skip-ci != 'true'
&& needs.metadata.outputs.has-integration-externals == 'true'
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.metadata.outputs.node-matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Reconfigure git auth
run:
git config --global url."https://${{ secrets.GH_TOKEN
}}@github.com/".insteadOf ssh://[email protected]/
- name: Use node ${{ matrix.node }}
uses: actions/[email protected]
with:
node-version: ${{ matrix.node }}
- name: Cache npm
uses: actions/[email protected]
id: cache-npm
with:
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
path: ~/.npm
restore-keys: npm-${{ runner.os }}-
- name: Install CI dependencies
run: |
npm ci
PEER_DEPS=$(node -e 'console.log(Object.entries(require("./package.json").peerDependencies || {}).map(([p,v]) => `${p}@${v}`).join(" "))')
! [ -z "$DEBUG" ] && echo "(install targets) PEER_DEPS='$PEER_DEPS'" || true
! [ -z "$PEER_DEPS" ] && npm install --no-save $PEER_DEPS || true
- name: Build externals
run: npm run build-externals
- name: Test integration
env:
MATRIX_NODE_VERSION: ${{ matrix.node }}
run: npm run test-integration-externals
test-integration-client:
name: '[CI] test-integration-client'
runs-on: ubuntu-latest
timeout-minutes: 10
needs: metadata
if: |
needs.metadata.outputs.should-skip-ci != 'true'
&& needs.metadata.outputs.has-integration-client == 'true'
steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Reconfigure git auth
run:
git config --global url."https://${{ secrets.GH_TOKEN
}}@github.com/".insteadOf ssh://[email protected]/
- name: Use node ${{ env.NODE_CURRENT_VERSION }}
uses: actions/[email protected]
with:
node-version: ${{ env.NODE_CURRENT_VERSION }}
- name: Cache npm
uses: actions/[email protected]
id: cache-npm
with:
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
path: ~/.npm
restore-keys: npm-${{ runner.os }}-
- name: Install CI dependencies
run: |
npm ci
PEER_DEPS=$(node -e 'console.log(Object.entries(require("./package.json").peerDependencies || {}).map(([p,v]) => `${p}@${v}`).join(" "))')
! [ -z "$DEBUG" ] && echo "(install targets) PEER_DEPS='$PEER_DEPS'" || true
! [ -z "$PEER_DEPS" ] && npm install --no-save $PEER_DEPS || true
- name: Build distributables
run: npm run build-dist
- name: Test integration
run: npm run test-integration-client
test-integration-webpack:
name: '[CI] test-integration-webpack'
runs-on: ubuntu-latest
timeout-minutes: 10
needs: metadata
if: |
needs.metadata.outputs.should-skip-ci != 'true'
&& needs.metadata.outputs.has-integration-webpack == 'true'
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.metadata.outputs.webpack-matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Reconfigure git auth
run:
git config --global url."https://${{ secrets.GH_TOKEN
}}@github.com/".insteadOf ssh://[email protected]/
- name: Use node ${{ env.NODE_CURRENT_VERSION }}
uses: actions/[email protected]
with:
node-version: ${{ env.NODE_CURRENT_VERSION }}
- name: Cache npm
uses: actions/[email protected]
id: cache-npm
with:
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
path: ~/.npm
restore-keys: npm-${{ runner.os }}-
- name: Install CI dependencies
run: |
npm ci
PEER_DEPS=$(node -e 'console.log(Object.entries(require("./package.json").peerDependencies || {}).map(([p,v]) => `${p}@${v}`).join(" "))')
! [ -z "$DEBUG" ] && echo "(install targets) PEER_DEPS='$PEER_DEPS'" || true
! [ -z "$PEER_DEPS" ] && npm install --no-save $PEER_DEPS || true
- name: Build distributables
run: npm run build-dist
- name: Test integration
env:
MATRIX_WEBPACK_VERSION: ${{ matrix.webpack }}
run: npm run test-integration-webpack
# * === The end of CI (build and test) and the beginning of CD (deploy) === *
# ? Skip this job:
# ? 1. If CI and/or CD are skipped globally
# ? 2. On PRs
# ? 3. In repo forks
# ? 4. If no semantic-release configuration
build:
name: '[CD] build'
runs-on: ubuntu-latest
timeout-minutes: 10
needs: metadata
if: |
needs.metadata.outputs.should-skip-cd != 'true'
&& github.event_name != 'pull_request'
&& needs.metadata.outputs.has-proper-owner == 'true'
&& needs.metadata.outputs.has-release-config == 'true'
steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Reconfigure git auth
run:
git config --global url."https://${{ secrets.GH_TOKEN
}}@github.com/".insteadOf ssh://[email protected]/
- name: Use node ${{ env.NODE_CURRENT_VERSION }}
uses: actions/[email protected]
with:
node-version: ${{ env.NODE_CURRENT_VERSION }}
- name: Cache npm
uses: actions/[email protected]
id: cache-npm
with:
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
path: ~/.npm
restore-keys: npm-${{ runner.os }}-
- name: Install CI dependencies
run: |
npm ci
PEER_DEPS=$(node -e 'console.log(Object.entries(require("./package.json").peerDependencies || {}).map(([p,v]) => `${p}@${v}`).join(" "))')
! [ -z "$DEBUG" ] && echo "(install targets) PEER_DEPS='$PEER_DEPS'" || true
! [ -z "$PEER_DEPS" ] && npm install --no-save $PEER_DEPS || true
- name: Pre source formatting
run: npm run format
- name: Build distributables
run: npm run build-dist
- name: Build documentation
if: needs.metadata.outputs.has-docs == 'true'
run: npm run build-docs
- name: Run formatting
run: npm run format
- name: Cache build
uses: actions/[email protected]
id: cache-build
with:
key: build-${{ runner.os }}-${{ github.sha }}
path: ./*
# ? This job always runs except:
# ? 1. If the "build" job failed or was skipped
# ? 2. If CI pipeline didn't complete successfully
release:
name: '[CD] release'
runs-on: ubuntu-latest
timeout-minutes: 10
needs:
- build
- check-audit
- lint
- test-unit
- metadata
- test-integration-node
- test-integration-externals
- test-integration-client
- test-integration-webpack
if: |
always()
&& needs.build.result == 'success'
&& needs.check-audit.result == 'success'
&& needs.lint.result == 'success'
&& needs.test-unit.result == 'success'
&& (needs.metadata.outputs.has-integration-node != 'true' || needs.test-integration-node.result == 'success')
&& (needs.metadata.outputs.has-integration-externals != 'true' || needs.test-integration-externals.result == 'success')
&& (needs.metadata.outputs.has-integration-client != 'true' || needs.test-integration-client.result == 'success')
&& (needs.metadata.outputs.has-integration-webpack != 'true' || needs.test-integration-webpack.result == 'success')
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
persist-credentials: false
- name: Reconfigure git auth
run:
git config --global url."https://${{ secrets.GH_TOKEN
}}@github.com/".insteadOf ssh://[email protected]/
- name: Restore build
uses: actions/[email protected]
id: restore-build
with:
key: build-${{ runner.os }}-${{ github.sha }}
path: ./*
- name: Fail if build not restored
if: steps.restore-build.outputs.cache-hit != 'true'
run: |
echo '::error::release job failed to restore build ${{ github.sha }}'
exit 1
- name: Use node ${{ env.NODE_CURRENT_VERSION }}
uses: actions/[email protected]
with:
node-version: ${{ env.NODE_CURRENT_VERSION }}
- name: Import gpg key
id: gpg
uses: crazy-max/ghaction-import-gpg@v3
with:
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
git-user-signingkey: true
git-commit-gpgsign: true
git-tag-gpgsign: true
git-committer-name: ${{ env.CI_COMMITTER_NAME }}
git-committer-email: ${{ env.CI_COMMITTER_EMAIL }}
- name: Perform semantic release
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
SHOULD_UPDATE_CHANGELOG:
${{ needs.metadata.outputs.current-branch == 'main' }}
SHOULD_DEPLOY: ${{ needs.metadata.outputs.has-deploy == 'true' }}
GIT_AUTHOR_NAME: ${{ env.CI_COMMITTER_NAME }}
GIT_AUTHOR_EMAIL: ${{ env.CI_COMMITTER_EMAIL }}
GIT_COMMITTER_NAME: ${{ env.CI_COMMITTER_NAME }}
GIT_COMMITTER_EMAIL: ${{ env.CI_COMMITTER_EMAIL }}
run: npx semantic-release
# ? This job always runs except:
# ? 1. If CI and/or CD are skipped globally
# ? 2. If the "build" or "release" jobs weren't skipped
# ? 3. If not a PR
# ? 4. If the PR is a draft
# ? 5. If CI pipeline didn't complete successfully
auto-merge:
name: '[CD] auto-merge'
runs-on: ubuntu-latest
timeout-minutes: 10
needs:
- build
- check-audit
- lint
- release
- test-unit
- metadata
- test-integration-node
- test-integration-externals
- test-integration-client
- test-integration-webpack
if: |
always()
&& needs.metadata.outputs.should-skip-cd != 'true'
&& needs.metadata.outputs.can-automerge == 'true'
&& github.event_name == 'pull_request'
&& github.event.pull_request.draft == false
&& needs.build.result == 'skipped'
&& needs.release.result == 'skipped'
&& needs.check-audit.result == 'success'
&& needs.lint.result == 'success'
&& needs.test-unit.result == 'success'
&& (needs.metadata.outputs.has-integration-node != 'true' || needs.test-integration-node.result == 'success')
&& (needs.metadata.outputs.has-integration-externals != 'true' || needs.test-integration-externals.result == 'success')
&& (needs.metadata.outputs.has-integration-client != 'true' || needs.test-integration-client.result == 'success')
&& (needs.metadata.outputs.has-integration-webpack != 'true' || needs.test-integration-webpack.result == 'success')
steps:
- name: 'Merge pull request'
uses: 'actions/[email protected]'
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const MAX_RETRIES = 5;
const MINIMUM_SECONDS = 10;
const JITTER_SECONDS = 10;
let success = false;
let errors = [];
let jitter = 0;
const pullRequest = context.payload.pull_request;
const repository = context.repo;
const debugging = !!'${{ env.DEBUG }}';
const canRetryMerge =
'${{ needs.metadata.outputs.can-retry-automerge }}' == 'true';
const delay = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const withErrorHandling = (promise, failData = {}) => {
return promise.catch((e) =>
Promise.resolve({
status: e.status || 0,
data: { message: e.message, ...failData }
})
);
};
if (debugging) {
console.log('MAX_RETRIES:', MAX_RETRIES);
console.log('MINIMUM_SECONDS:', MINIMUM_SECONDS);
console.log('JITTER_SECONDS:', JITTER_SECONDS);
console.log('repository:', repository);
console.log(
'canRetryMerge:',
canRetryMerge,
' ("${{ needs.metadata.outputs.can-retry-automerge }}")'
);
console.log('pullRequest.number:', pullRequest.number);
console.log(`head_ref (sha): ${pullRequest.head.sha}`);
}
for (let tries = 0; !success && tries < MAX_RETRIES; ++tries) {
const failSymbol = Symbol('fail');
try {
const latestPullRequest = await withErrorHandling(
github.pulls.get(
{
owner: repository.owner,
repo: repository.repo,
pull_number: pullRequest.number
},
{ failed: failSymbol }
)
);
if (debugging) {
console.log('latestPullRequest->status:', latestPullRequest.status);
console.log(
'latestPullRequest->message:',
latestPullRequest.data.message
);
console.log('latestPullRequest->failed:', latestPullRequest.data.failed);
console.log('latestPullRequest->state:', latestPullRequest.data.state);
console.log('latestPullRequest->merged:', latestPullRequest.data.merged);
console.log('latestPullRequest->draft:', latestPullRequest.data.draft);
}
if (latestPullRequest.status == 404) {
core.warning(
`Auto-merge skipped: PR #${pullRequest.number} no longer exists`
);
return;
} else if (latestPullRequest.data.failed == failSymbol) {
throw new Error(
latestPullRequest.data.message ||
`failed to get PR #${pullRequest.number}: status code ${status}`
);
} else if (latestPullRequest.data.merged) {
core.info(
`Auto-merge skipped: PR #${pullRequest.number} has already been merged`
);
return;
} else if (latestPullRequest.data.draft) {
core.warning(
`Auto-merge skipped: PR #${pullRequest.number} was marked as a draft`
);
return;
} else if (
latestPullRequest.status < 400 &&
latestPullRequest.data.state != 'open'
) {
core.warning(
`Auto-merge skipped: PR #${pullRequest.number} is no longer open`
);
return;
} // ? Mergeability check is the attempt to merge itself (below)
const {
status,
data: { merged, message }
} = await withErrorHandling(
github.pulls.merge({
owner: repository.owner,
repo: repository.repo,
pull_number: pullRequest.number,
sha: pullRequest.head.sha,
merge_method: 'merge'
}),
{ merged: false }
);
if (debugging) {
console.log('mergeAttempt->status:', status);
console.log('mergeAttempt->message:', message);
console.log('mergeAttempt->merged:', merged);
}
const defaultError =
message ||
`failed to merge PR #${pullRequest.number}: status code ${status}`;
if ([403, 404, 422].includes(status)) {
core.setFailed(`Auto-merge failed: ${message}`);
return;
} else if (status == 409) {
core.info(
`Auto-merge skipped: current HEAD is out of sync with PR #${pullRequest.number}`
);
} else if (status >= 400 && status < 500 && !canRetryMerge) {
core.setFailed(`Auto-merge failed: ${defaultError}`);
return;
} else if (!merged) {
throw new Error(defaultError);
}
success = true;
} catch (e) {
console.error(
'Auto-merge failed:',
e.message || e || 'an unknown error occurred'
);
if (tries + 1 < MAX_RETRIES) {
const nextRetryIn = 2 ** tries * MINIMUM_SECONDS * 1000 + jitter;
jitter += Math.random() * JITTER_SECONDS * 1000;
core.warning(
`Auto-merge attempt ${
tries + 1
}/${MAX_RETRIES} failed, next try in ${Math.round(
nextRetryIn / 1000
)} seconds`
);
await delay(nextRetryIn);
}
}
}
if (!success) core.setFailed(`Auto-merge failed after ${MAX_RETRIES} attempts`);