diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index d1dd4f9f..ad2d9680 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -1,31 +1,45 @@ name: Build, Lint, and Test on: - push: - branches: [main] - pull_request: + workflow_call: jobs: build-lint-test: + name: Build, lint, and test (Node ${{ matrix.node-version }}) runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20.x, 22.x, 24.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} - run: corepack enable - run: yarn install --immutable - run: yarn build - - run: git diff --exit-code --name-status || { echo 'working directory dirty after "yarn build"'; exit 1; } + - 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 - run: yarn lint - run: yarn test - # tests to ensure get-release-packages.sh functions as expected + + # Tests that ensure get-release-packages.sh works against a real monorepo + # by running it over a pinned snapshot of MetaMask/snaps. updated-packages-test: + name: Updated packages test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: repository: MetaMask/snaps ref: v120.0.0 path: snaps - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: path: action-publish-release - run: corepack enable @@ -125,9 +139,3 @@ jobs: echo "modified RELEASE_PACKAGES has length '${LENGTH}', expected length '${EXPECTED_LENGTH}'" exit 1 fi; - - all-tests-pass: - runs-on: ubuntu-latest - needs: [build-lint-test, updated-packages-test] - steps: - - run: echo "Great success" diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml index ed48eb5a..5b753c9c 100644 --- a/.github/workflows/create-release-pr.yml +++ b/.github/workflows/create-release-pr.yml @@ -21,7 +21,7 @@ jobs: contents: write pull-requests: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: # This is to guarantee that the most recent tag is fetched. # This can be configured to a more reasonable value by consumers. @@ -29,15 +29,13 @@ jobs: # 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 }} - - name: Get Node.js version - id: nvm - run: echo "NODE_VERSION=$(cat .nvmrc)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-node@v3 + - name: Install Node.js + uses: actions/setup-node@v6 with: - node-version: ${{ steps.nvm.outputs.NODE_VERSION }} - - uses: MetaMask/action-create-release-pr@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + node-version-file: '.nvmrc' + - uses: MetaMask/action-create-release-pr@v5 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..e6cd0390 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,113 @@ +name: Main + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + check-workflows: + name: Check workflows + runs-on: ubuntu-latest + steps: + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v3 + with: + is-high-risk-environment: false + persist-credentials: false + - name: Download actionlint + id: download-actionlint + run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/914e7df21a07ef503a81201c76d2b11c789d3fca/scripts/download-actionlint.bash) 1.7.12 + shell: bash + - name: Check workflow files + env: + ACTIONLINT_EXECUTABLE: ${{ steps.download-actionlint.outputs.executable }} + run: | + echo "::add-matcher::.github/actionlint-matcher.json" + "$ACTIONLINT_EXECUTABLE" -color + shell: bash + + analyse-code: + name: Analyse code + needs: check-workflows + uses: MetaMask/action-security-code-scanner/.github/workflows/security-scan.yml@v2 + with: + scanner-ref: v2 + paths-ignored: | + **/__snapshots__/ + **/*.snap + **/*.test.js* + **/*.test.ts* + **/fixtures/ + **/jest.config.js + **/mocks/ + **/test*/ + node_modules/ + secrets: + project-metrics-token: ${{ secrets.SECURITY_SCAN_METRICS_TOKEN }} + slack-webhook: ${{ secrets.APPSEC_BOT_SLACK_WEBHOOK }} + permissions: + actions: read + contents: read + security-events: write + + 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 + permissions: {} + 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 + permissions: {} + steps: + - name: Check that all jobs have passed + env: + PASSED: ${{ needs.all-jobs-completed.outputs.PASSED }} + run: | + if [[ $PASSED != "true" ]]; then + exit 1 + fi + + is-release: + # Filtering by `push` events ensures that we only release from the `main` + # branch. 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@v2 + 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: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 1f5ad211..f7a4bb81 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -1,35 +1,88 @@ name: Publish Release on: - pull_request: - types: [closed] + workflow_call: + inputs: + slack-icon-url: + required: false + type: string + default: 'https://raw.githubusercontent.com/MetaMask/action-npm-publish/main/robo.png' + slack-subteam: + required: false + type: string + default: S042S7RE4AE # @metamask-npm-publishers + slack-username: + required: false + type: string + default: 'MetaMask bot' + secrets: + SLACK_WEBHOOK_URL: + required: true jobs: + announce-release: + name: Announce release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - id: name-hash + name: Get Slack name and hash + shell: bash + if: inputs.slack-subteam != '' + run: | + NAME_VERSION_TEXT=$(jq --raw-output '.name + "@" + .version' package.json ) + NAME_VERSION_TEXT_STRIPPED="${NAME_VERSION_TEXT#@}" + echo "NAME_VERSION=$NAME_VERSION_TEXT_STRIPPED" >> "$GITHUB_OUTPUT" + - id: final-text + name: Get Slack final text + shell: bash + if: inputs.slack-subteam != '' + run: | + DEFAULT_TEXT="\`${{ steps.name-hash.outputs.NAME_VERSION }}\` is awaiting deployment :rocket: \n " + SUBTEAM_TEXT="${{ inputs.slack-subteam }}" + FINAL_TEXT="$DEFAULT_TEXT" + if [[ ! "$SUBTEAM_TEXT" == "" ]]; then + FINAL_TEXT=" $DEFAULT_TEXT" + fi + echo "FINAL_TEXT=$FINAL_TEXT" >> "$GITHUB_OUTPUT" + - name: Post to a Slack channel + if: inputs.slack-subteam != '' + uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844 + with: + payload: | + { + "text": "${{ steps.final-text.outputs.FINAL_TEXT }}", + "icon_url": "${{ inputs.slack-icon-url }}", + "username": "${{ inputs.slack-username }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + continue-on-error: true + publish-release: + name: Publish release + environment: action-publish + needs: announce-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@v3 + - name: Checkout repository + uses: actions/checkout@v6 with: - # This is to guarantee that the most recent tag is fetched, - # which we need for updating the shorthand major version tag. + # This is to guarantee that the most recent tag is fetched, which we + # need for updating the shorthand major version tag. fetch-depth: 0 - # We check out the release pull request's base branch, which will be - # used as the base branch for all git operations. - ref: ${{ github.event.pull_request.base.ref }} - - name: Get Node.js version - id: nvm - run: echo "NODE_VERSION=$(cat .nvmrc)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-node@v3 - with: - node-version: ${{ steps.nvm.outputs.NODE_VERSION }} - - uses: ./ + ref: ${{ github.sha }} + - name: Publish release + # We can't use the action by its public name here because this repo + # *is* that action. Use the local checkout instead. + uses: ./ id: publish-release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Update shorthand major version tag - run: ./scripts/update-major-version-tag.sh ${{ steps.publish-release.outputs.release-version }} + run: | + ./.github/workflows/scripts/update-major-version-tag.sh \ + ${{ steps.publish-release.outputs.release-version }} diff --git a/scripts/update-major-version-tag.sh b/.github/workflows/scripts/update-major-version-tag.sh similarity index 79% rename from scripts/update-major-version-tag.sh rename to .github/workflows/scripts/update-major-version-tag.sh index d260dd7c..5a9e78ec 100755 --- a/scripts/update-major-version-tag.sh +++ b/.github/workflows/scripts/update-major-version-tag.sh @@ -17,11 +17,11 @@ git config user.name github-actions git config user.email github-actions@github.com if git show-ref --tags "$MAJOR_VERSION_TAG" --quiet; then - echo "Tag ${MAJOR_VERSION_TAG} exists, attempting to delete it." + echo "Tag \"${MAJOR_VERSION_TAG}\" exists, attempting to delete it." git tag --delete "$MAJOR_VERSION_TAG" git push --delete origin "$MAJOR_VERSION_TAG" -else - echo "Tag ${MAJOR_VERSION_TAG} does not exist, creating it from scratch." +else + echo "Tag \"${MAJOR_VERSION_TAG}\" does not exist, creating it from scratch." fi git tag "$MAJOR_VERSION_TAG" HEAD diff --git a/.github/workflows/security-code-scanner.yml b/.github/workflows/security-code-scanner.yml deleted file mode 100644 index 6bb460d2..00000000 --- a/.github/workflows/security-code-scanner.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: MetaMask Security Code Scanner - -on: - push: - branches: - - main - pull_request: - branches: - - main - workflow_dispatch: - -jobs: - run-security-scan: - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - steps: - - name: MetaMask Security Code Scanner - uses: MetaMask/Security-Code-Scanner@main - with: - repo: ${{ github.repository }} - paths_ignored: | - .storybook/ - '**/__snapshots__/' - '**/*.snap' - '**/*.stories.js' - '**/*.stories.tsx' - '**/*.test.browser.ts*' - '**/*.test.js*' - '**/*.test.ts*' - '**/fixtures/' - '**/jest.config.js' - '**/jest.environment.js' - '**/mocks/' - '**/test*/' - docs/ - e2e/ - merged-packages/ - node_modules - storybook/ - test*/ - rules_excluded: example - project_metrics_token: ${{ secrets.SECURITY_SCAN_METRICS_TOKEN }} - slack_webhook: ${{ secrets.APPSEC_BOT_SLACK_WEBHOOK }}