diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 098d74c2..80313d71 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,5 @@ # Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..5fc5feac --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,11 @@ + diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml new file mode 100644 index 00000000..2334065a --- /dev/null +++ b/.github/workflows/build-lint-test.yml @@ -0,0 +1,117 @@ +name: Build, Lint, and Test + +on: + workflow_call: + +jobs: + prepare: + name: Prepare + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + steps: + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v1 + with: + is-high-risk-environment: false + node-version: ${{ matrix.node-version }} + cache-node-modules: ${{ matrix.node-version == '22.x' }} + + build: + name: Build + needs: prepare + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [22.x] + steps: + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v1 + with: + is-high-risk-environment: false + node-version: ${{ matrix.node-version }} + - run: yarn build + - name: Require clean working directory + shell: bash + run: | + if ! git diff --exit-code; then + echo "Working tree dirty at end of job" + exit 1 + fi + + lint: + name: Lint + needs: prepare + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [22.x] + steps: + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v1 + with: + is-high-risk-environment: false + node-version: ${{ matrix.node-version }} + - run: yarn lint + - name: Validate RC changelog + if: ${{ startsWith(github.head_ref, 'release/') }} + run: yarn lint:changelog --rc + - name: Validate changelog + if: ${{ !startsWith(github.head_ref, 'release/') }} + run: yarn lint:changelog + - name: Require clean working directory + shell: bash + run: | + if ! git diff --exit-code; then + echo "Working tree dirty at end of job" + exit 1 + fi + + test: + name: Test + needs: prepare + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + steps: + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v1 + with: + is-high-risk-environment: false + node-version: ${{ matrix.node-version }} + - run: yarn test + - name: Require clean working directory + shell: bash + run: | + if ! git diff --exit-code; then + echo "Working tree dirty at end of job" + exit 1 + fi + + compatibility-test: + name: Compatibility test + needs: prepare + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + steps: + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v1 + with: + is-high-risk-environment: false + node-version: ${{ matrix.node-version }} + - name: Install dependencies via Yarn + run: rm yarn.lock && YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn + - run: yarn test + - name: Restore lockfile + run: git restore yarn.lock + - name: Require clean working directory + shell: bash + run: | + if ! git diff --exit-code; then + echo "Working tree dirty at end of job" + exit 1 + fi diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml index 730e1b65..5197d088 100644 --- a/.github/workflows/create-release-pr.yml +++ b/.github/workflows/create-release-pr.yml @@ -8,7 +8,7 @@ on: default: 'main' required: true release-type: - description: 'A SemVer version diff, i.e. major, minor, patch, prerelease etc. Mutually exclusive with "release-version".' + description: 'A SemVer version diff, i.e. major, minor, or patch. Mutually exclusive with "release-version".' required: false release-version: description: 'A specific version to bump to. Mutually exclusive with "release-type".' @@ -21,20 +21,22 @@ jobs: contents: write pull-requests: write steps: - - uses: actions/checkout@v4 + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v1 with: - # This is to guarantee that the most recent tag is fetched. - # This can be configured to a more reasonable value by consumers. + is-high-risk-environment: true + + # This is to guarantee that the most recent tag is fetched. This can + # be configured to a more reasonable value by consumers. fetch-depth: 0 + # We check out the specified branch, which will be used as the base # branch for all git operations and the release PR. ref: ${{ github.event.inputs.base-branch }} - - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - - uses: MetaMask/action-create-release-pr@v3 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: MetaMask/action-create-release-pr@v4 with: release-type: ${{ github.event.inputs.release-type }} release-version: ${{ github.event.inputs.release-version }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..21493f84 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,93 @@ +name: Main + +on: + push: + branches: [main] + pull_request: + +jobs: + check-workflows: + name: Check workflows + runs-on: ubuntu-latest + steps: + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v1 + with: + is-high-risk-environment: false + - name: Download actionlint + id: download-actionlint + run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/7fdc9630cc360ea1a469eed64ac6d78caeda1234/scripts/download-actionlint.bash) 1.6.23 + shell: bash + - name: Check workflow files + run: ${{ steps.download-actionlint.outputs.executable }} -color + shell: bash + + analyse-code: + name: Code scanner + needs: check-workflows + uses: ./.github/workflows/security-code-scanner.yml + permissions: + actions: read + contents: read + security-events: write + secrets: + SECURITY_SCAN_METRICS_TOKEN: ${{ secrets.SECURITY_SCAN_METRICS_TOKEN }} + APPSEC_BOT_SLACK_WEBHOOK: ${{ secrets.APPSEC_BOT_SLACK_WEBHOOK }} + + build-lint-test: + name: Build, lint, and test + uses: ./.github/workflows/build-lint-test.yml + + all-jobs-completed: + name: All jobs completed + runs-on: ubuntu-latest + needs: + - check-workflows + - analyse-code + - build-lint-test + outputs: + PASSED: ${{ steps.set-output.outputs.PASSED }} + steps: + - name: Set PASSED output + id: set-output + run: echo "PASSED=true" >> "$GITHUB_OUTPUT" + + all-jobs-pass: + name: All jobs pass + if: ${{ always() }} + runs-on: ubuntu-latest + needs: all-jobs-completed + steps: + - name: Check that all jobs have passed + run: | + passed="${{ needs.all-jobs-completed.outputs.PASSED }}" + if [[ $passed != "true" ]]; then + exit 1 + fi + + is-release: + # Filtering by `push` events ensures that we only release from the `main` branch, which is a + # requirement for our npm publishing environment. + # The commit author should always be 'github-actions' for releases created by the + # 'create-release-pr' workflow, so we filter by that as well to prevent accidentally + # triggering a release. + if: github.event_name == 'push' && startsWith(github.event.head_commit.author.name, 'github-actions') + needs: all-jobs-pass + outputs: + IS_RELEASE: ${{ steps.is-release.outputs.IS_RELEASE }} + runs-on: ubuntu-latest + steps: + - uses: MetaMask/action-is-release@v1 + id: is-release + + publish-release: + needs: is-release + if: needs.is-release.outputs.IS_RELEASE == 'true' + name: Publish release + permissions: + contents: write + uses: ./.github/workflows/publish-release.yml + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + PUBLISH_DOCS_TOKEN: ${{ secrets.PUBLISH_DOCS_TOKEN }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml new file mode 100644 index 00000000..5e66e52c --- /dev/null +++ b/.github/workflows/publish-docs.yml @@ -0,0 +1,37 @@ +name: Publish docs to GitHub Pages + +on: + workflow_call: + inputs: + destination_dir: + required: true + type: string + secrets: + PUBLISH_DOCS_TOKEN: + required: true + +jobs: + publish-docs-to-gh-pages: + name: Publish docs to GitHub Pages + runs-on: ubuntu-latest + environment: github-pages + permissions: + contents: write + steps: + - name: Ensure `destination_dir` is not empty + if: ${{ inputs.destination_dir == '' }} + run: exit 1 + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v1 + with: + is-high-risk-environment: true + - name: Run build script + run: yarn build:docs + - name: Deploy to `${{ inputs.destination_dir }}` directory of `gh-pages` branch + uses: peaceiris/actions-gh-pages@de7ea6f8efb354206b205ef54722213d99067935 + with: + # This `PUBLISH_DOCS_TOKEN` needs to be manually set per-repository. + # Look in the repository settings under "Environments", and set this token in the `github-pages` environment. + personal_token: ${{ secrets.PUBLISH_DOCS_TOKEN }} + publish_dir: ./docs + destination_dir: ${{ inputs.destination_dir }} diff --git a/.github/workflows/publish-main-docs.yml b/.github/workflows/publish-main-docs.yml new file mode 100644 index 00000000..b9bbbefc --- /dev/null +++ b/.github/workflows/publish-main-docs.yml @@ -0,0 +1,16 @@ +name: Publish main branch docs to GitHub Pages + +on: + push: + branches: main + +jobs: + publish-to-gh-pages: + name: Publish docs to `staging` directory of `gh-pages` branch + permissions: + contents: write + uses: ./.github/workflows/publish-docs.yml + with: + destination_dir: staging + secrets: + PUBLISH_DOCS_TOKEN: ${{ secrets.PUBLISH_DOCS_TOKEN }} diff --git a/.github/workflows/publish-rc-docs.yml b/.github/workflows/publish-rc-docs.yml new file mode 100644 index 00000000..802cf6bc --- /dev/null +++ b/.github/workflows/publish-rc-docs.yml @@ -0,0 +1,28 @@ +name: Publish release candidate docs to GitHub Pages + +on: + push: + branches: 'release/**' + +jobs: + get-release-version: + name: Get release version + runs-on: ubuntu-latest + outputs: + release-version: ${{ steps.release-name.outputs.RELEASE_VERSION }} + steps: + - name: Extract release version from branch name + id: release-name + run: | + BRANCH_NAME='${{ github.ref_name }}' + echo "RELEASE_VERSION=v${BRANCH_NAME#release/}" >> "$GITHUB_OUTPUT" + publish-to-gh-pages: + name: Publish docs to `rc-${{ needs.get-release-version.outputs.release-version }}` directory of `gh-pages` branch + permissions: + contents: write + uses: ./.github/workflows/publish-docs.yml + needs: get-release-version + with: + destination_dir: rc-${{ needs.get-release-version.outputs.release-version }} + secrets: + PUBLISH_DOCS_TOKEN: ${{ secrets.PUBLISH_DOCS_TOKEN }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 06f24438..b4872262 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -1,32 +1,115 @@ name: Publish Release on: - pull_request: - types: [closed] - + workflow_call: + secrets: + NPM_TOKEN: + required: true + SLACK_WEBHOOK_URL: + required: true + PUBLISH_DOCS_TOKEN: + required: true jobs: publish-release: permissions: contents: write - if: | - github.event.pull_request.merged == true && - startsWith(github.event.pull_request.head.ref, 'release/') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v1 with: + is-high-risk-environment: true ref: ${{ github.sha }} - - name: Install Corepack via Node - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - - name: Install Yarn - run: corepack enable - - name: Restore Yarn cache - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - uses: MetaMask/action-publish-release@v1 + - uses: MetaMask/action-publish-release@v3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: yarn build + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: publish-release-artifacts-${{ github.sha }} + retention-days: 4 + include-hidden-files: true + path: | + ./dist + ./node_modules/.yarn-state.yml + + publish-npm-dry-run: + needs: publish-release + runs-on: ubuntu-latest + steps: + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v1 + with: + is-high-risk-environment: true + ref: ${{ github.sha }} + - name: Restore build artifacts + uses: actions/download-artifact@v4 + with: + name: publish-release-artifacts-${{ github.sha }} + - name: Dry Run Publish + # omit npm-token token to perform dry run publish + uses: MetaMask/action-npm-publish@v5 + with: + slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} + subteam: S042S7RE4AE # @metamask-npm-publishers + env: + SKIP_PREPACK: true + + publish-npm: + needs: publish-npm-dry-run + runs-on: ubuntu-latest + environment: npm-publish + steps: + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v1 + with: + is-high-risk-environment: true + ref: ${{ github.sha }} + - name: Restore build artifacts + uses: actions/download-artifact@v4 + with: + name: publish-release-artifacts-${{ github.sha }} + - name: Publish + uses: MetaMask/action-npm-publish@v5 + with: + # This `NPM_TOKEN` needs to be manually set per-repository. + # Look in the repository settings under "Environments", and set this token in the `npm-publish` environment. + npm-token: ${{ secrets.NPM_TOKEN }} + env: + SKIP_PREPACK: true + + get-release-version: + needs: publish-npm + runs-on: ubuntu-latest + outputs: + RELEASE_VERSION: ${{ steps.get-release-version.outputs.RELEASE_VERSION }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + - id: get-release-version + shell: bash + run: ./scripts/get.sh ".version" "RELEASE_VERSION" + + publish-release-to-gh-pages: + name: Publish docs to `${{ needs.get-release-version.outputs.RELEASE_VERSION }}` directory of `gh-pages` branch + needs: get-release-version + permissions: + contents: write + uses: ./.github/workflows/publish-docs.yml + with: + destination_dir: ${{ needs.get-release-version.outputs.RELEASE_VERSION }} + secrets: + PUBLISH_DOCS_TOKEN: ${{ secrets.PUBLISH_DOCS_TOKEN }} + + publish-release-to-latest-gh-pages: + name: Publish docs to `latest` directory of `gh-pages` branch + needs: publish-npm + permissions: + contents: write + uses: ./.github/workflows/publish-docs.yml + with: + destination_dir: latest + secrets: + PUBLISH_DOCS_TOKEN: ${{ secrets.PUBLISH_DOCS_TOKEN }} diff --git a/.github/workflows/security-code-scanner.yml b/.github/workflows/security-code-scanner.yml index 9a32836b..ababbbeb 100644 --- a/.github/workflows/security-code-scanner.yml +++ b/.github/workflows/security-code-scanner.yml @@ -1,23 +1,24 @@ name: MetaMask Security Code Scanner on: - push: - branches: - - main - pull_request: - branches: - - main + workflow_call: + secrets: + SECURITY_SCAN_METRICS_TOKEN: + required: false + APPSEC_BOT_SLACK_WEBHOOK: + required: false workflow_dispatch: jobs: run-security-scan: + name: Run security scan runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write steps: - - name: MetaMask Security Code Scanner + - name: Analyse code uses: MetaMask/action-security-code-scanner@v1 with: repo: ${{ github.repository }} diff --git a/scripts/get.sh b/scripts/get.sh new file mode 100755 index 00000000..9c988bb8 --- /dev/null +++ b/scripts/get.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +if [[ ${RUNNER_DEBUG:-0} == 1 ]]; then + set -x +fi + +KEY="${1}" +OUTPUT="${2}" + +if [[ -z $KEY ]]; then + echo "Error: KEY not specified." + exit 1 +fi + +if [[ -z $OUTPUT ]]; then + echo "Error: OUTPUT not specified." + exit 1 +fi + +echo "$OUTPUT=$(jq --raw-output "$KEY" package.json)" >> "$GITHUB_OUTPUT"