From f67383252f09d5bc2040d2c0cd5fa1593d9c75e3 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Fri, 23 May 2025 16:12:55 -0400 Subject: [PATCH 1/4] feat(meta): publish @node-core/* packages --- .github/workflows/publish-packages.yml | 114 +++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 .github/workflows/publish-packages.yml diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml new file mode 100644 index 0000000000000..05ef3a3b739d8 --- /dev/null +++ b/.github/workflows/publish-packages.yml @@ -0,0 +1,114 @@ +name: Publish Packages + +on: + workflow_run: + workflows: ['Linting and Tests'] + types: [completed] + branches: [main] + workflow_dispatch: + inputs: + package: + description: 'Specific package to publish (leave empty for all packages)' + required: false + type: string + +permissions: + contents: read + +env: + COMMIT_SHA: ${{ github.event.workflow_run.head_sha || github.sha }} + +jobs: + detect-packages: + runs-on: ubuntu-latest + outputs: + packages: ${{ steps.find-packages.outputs.packages }} + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Find packages + id: find-packages + env: + PACKAGE: ${{ github.event.inputs.package }} + run: | + if [ "$PACKAGE" != "" ]; then + echo "packages=[\"$PACKAGE\"]" >> $GITHUB_OUTPUT + else + PACKAGES=$(ls -d packages/* | xargs -n 1 basename | jq -R -s -c 'split("\n")[:-1]') + echo "packages=$PACKAGES" >> $GITHUB_OUTPUT + fi + + verify-commit: + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push') + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Verify commit authenticity + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + COMMIT_DATA=$(gh api repos/${{ github.repository }}/commits/$COMMIT_SHA) + VERIFIED=$(echo "$COMMIT_DATA" | jq -r '.commit.verification.verified') + COMMITTER=$(echo "$COMMIT_DATA" | jq -r '.commit.committer.email') + + if [[ "$VERIFIED" != "true" ]]; then + echo "❌ Unverified commit! Aborting." + exit 1 + fi + + if [[ "$COMMITTER" != "noreply@github.com" ]]; then + echo "❌ Not merged with the merge queue! Aborting." + exit 1 + fi + + echo "✅ Commit is verified and trusted." + + publish: + needs: [detect-packages, verify-commit] + runs-on: ubuntu-latest + strategy: + matrix: + package: ${{ fromJson(needs.detect-packages.outputs.packages) }} + fail-fast: false + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 2 + + - name: Check for package changes + if: github.event_name != 'workflow_dispatch' + id: check_changes + env: + PACKAGE: ${{ matrix.package }} + run: | + if git diff --quiet $COMMIT_SHA~1 $COMMIT_SHA -- "packages/$PACKAGE/"; then + echo "changed=false" >> $GITHUB_OUTPUT + else + echo "changed=true" >> $GITHUB_OUTPUT + fi + + - name: Set up pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + with: + cache: true + + - name: Setup Node.js + if: github.event_name == 'workflow_dispatch' || steps.check_changes.outputs.changed == 'true' + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version-file: '.nvmrc' + registry-url: 'https://registry.npmjs.org' + cache: pnpm + + - name: Publish + if: github.event_name == 'workflow_dispatch' || steps.check_changes.outputs.changed == 'true' + working-directory: packages/${{ matrix.package }} + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: > + npm version --no-git-tag-version 0.0.0-$COMMIT_SHA + pnpm publish --access public From a1163c58c60334998f12a576d5cbacb7d85ab16e Mon Sep 17 00:00:00 2001 From: avivkeller Date: Sat, 24 May 2025 09:08:14 -0400 Subject: [PATCH 2/4] add slack notif, add doc --- .github/workflows/publish-packages.yml | 121 ++++++++++++++++--------- COLLABORATOR_GUIDE.md | 13 +++ 2 files changed, 92 insertions(+), 42 deletions(-) diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index 05ef3a3b739d8..a695c3975d9da 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -1,9 +1,14 @@ name: Publish Packages +# This workflow publishes packages to NPM when changes are merged to main branch or when manually triggered. +# It runs automatically after successful tests or can be run manually for specific packages. + on: workflow_run: + # Only run after linting and tests have passed on main branch workflows: ['Linting and Tests'] types: [completed] + # For security reasons, this should never be set to anything but `main` branches: [main] workflow_dispatch: inputs: @@ -16,44 +21,40 @@ permissions: contents: read env: + # Use the SHA from the workflow run that triggered this or the current SHA for manual runs COMMIT_SHA: ${{ github.event.workflow_run.head_sha || github.sha }} jobs: - detect-packages: + prepare-packages: runs-on: ubuntu-latest + # Only run if manually triggered or if the triggering workflow succeeded from a push event + if: github.event_name == 'workflow_dispatch' || ( + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + github.repository == 'nodejs/nodejs.org') outputs: - packages: ${{ steps.find-packages.outputs.packages }} - steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - name: Find packages - id: find-packages - env: - PACKAGE: ${{ github.event.inputs.package }} - run: | - if [ "$PACKAGE" != "" ]; then - echo "packages=[\"$PACKAGE\"]" >> $GITHUB_OUTPUT - else - PACKAGES=$(ls -d packages/* | xargs -n 1 basename | jq -R -s -c 'split("\n")[:-1]') - echo "packages=$PACKAGES" >> $GITHUB_OUTPUT - fi - - verify-commit: - runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push') + # Output the matrix of packages to publish for use in the publish job + matrix: ${{ steps.generate-matrix.outputs.matrix }} steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Harden Runner + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit - name: Verify commit authenticity + # Skip verification for manual runs since they're initiated by trusted users + if: github.event_name != 'workflow_dispatch' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | + # Get commit data from GitHub API to verify its authenticity COMMIT_DATA=$(gh api repos/${{ github.repository }}/commits/$COMMIT_SHA) + # Check if commit signature is verified (GPG signed) VERIFIED=$(echo "$COMMIT_DATA" | jq -r '.commit.verification.verified') + # Check if commit was made through GitHub's web interface (merge queue) COMMITTER=$(echo "$COMMIT_DATA" | jq -r '.commit.committer.email') + # Security checks to ensure we only publish from verified and trusted sources if [[ "$VERIFIED" != "true" ]]; then echo "❌ Unverified commit! Aborting." exit 1 @@ -66,38 +67,58 @@ jobs: echo "✅ Commit is verified and trusted." - publish: - needs: [detect-packages, verify-commit] - runs-on: ubuntu-latest - strategy: - matrix: - package: ${{ fromJson(needs.detect-packages.outputs.packages) }} - fail-fast: false - steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - fetch-depth: 2 + fetch-depth: 2 # Need at least 2 commits to detect changes between commits - - name: Check for package changes - if: github.event_name != 'workflow_dispatch' - id: check_changes + - name: Generate package matrix + id: generate-matrix env: - PACKAGE: ${{ matrix.package }} + PACKAGE: ${{ github.event.inputs.package }} + EVENT_NAME: ${{ github.event_name }} run: | - if git diff --quiet $COMMIT_SHA~1 $COMMIT_SHA -- "packages/$PACKAGE/"; then - echo "changed=false" >> $GITHUB_OUTPUT + if [ -n "$PACKAGE" ]; then + # If a specific package is requested via workflow_dispatch, just publish that one + echo "matrix={\"package\":[\"$PACKAGE\"]}" >> $GITHUB_OUTPUT else - echo "changed=true" >> $GITHUB_OUTPUT + # Otherwise, identify all packages with changes since the last commit + CHANGED_PACKAGES=() + for pkg in $(ls -d packages/*); do + PKG_NAME=$(basename "$pkg") + # For manual runs, include all packages. For automatic runs, only include packages with changes + if [ "$EVENT_NAME" == "workflow_dispatch" ] || ! git diff --quiet $COMMIT_SHA~1 $COMMIT_SHA -- "$pkg/"; then + CHANGED_PACKAGES+=("$PKG_NAME") + fi + done + + # Format the output for GitHub Actions matrix using jq + PACKAGES_JSON=$(printf '%s\n' "${CHANGED_PACKAGES[@]}" | jq -R . | jq -s .) + echo "matrix={\"package\":$PACKAGES_JSON}" >> $GITHUB_OUTPUT fi + publish: + needs: prepare-packages + runs-on: ubuntu-latest + # Use the dynamic matrix from prepare-packages job to create parallel jobs for each package + strategy: + matrix: ${{ fromJson(needs.prepare-packages.outputs.matrix) }} + fail-fast: false # Continue publishing other packages even if one fails + steps: + - name: Harden Runner + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Set up pnpm uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 with: cache: true - name: Setup Node.js - if: github.event_name == 'workflow_dispatch' || steps.check_changes.outputs.changed == 'true' uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version-file: '.nvmrc' @@ -105,10 +126,26 @@ jobs: cache: pnpm - name: Publish - if: github.event_name == 'workflow_dispatch' || steps.check_changes.outputs.changed == 'true' working-directory: packages/${{ matrix.package }} env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - run: > + run: | + # Create a unique version using the commit SHA as a prerelease identifier + # This ensures we can publish multiple times from the same codebase with unique versions npm version --no-git-tag-version 0.0.0-$COMMIT_SHA + # Publish the package to the npm registry with public access flag pnpm publish --access public + + - name: Notify on Manual Release + if: ${{ github.event_name == 'workflow_dispatch' }} + uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # 2.3.3 + env: + SLACK_COLOR: '#43853D' + SLACK_ICON: https://github.com/nodejs.png?size=48 + SLACK_TITLE: ':rocket: Package Published: ${{ matrix.package }}' + SLACK_MESSAGE: | + :package: *Package*: `${{ matrix.package }}` () + :bust_in_silhouette: *Published by*: ${{ github.triggering_actor }} + :octocat: *Commit*: + SLACK_USERNAME: nodejs-bot + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} diff --git a/COLLABORATOR_GUIDE.md b/COLLABORATOR_GUIDE.md index 857efecd5c037..3a00478db6656 100644 --- a/COLLABORATOR_GUIDE.md +++ b/COLLABORATOR_GUIDE.md @@ -20,6 +20,7 @@ - [General Guidelines for Unit Tests](#general-guidelines-for-unit-tests) - [General Guidelines for Playwright E2E Tests](#general-guidelines-for-playwright-e2e-tests) - [General Guidelines for Storybooks](#general-guidelines-for-storybooks) +- [Publishing Packages](#publishing-packages) - [Remarks on Technologies used](#remarks-on-technologies-used) - [Seeking additional clarification](#seeking-additional-clarification) @@ -487,6 +488,18 @@ export default { component: NameOfComponent } as Meta; - We recommend reading previous Storybooks from the codebase for inspiration and code guidelines. - If you need to decorate/wrap your Component/Story with a Container/Provider, please use [Storybook Decorators](https://storybook.js.org/docs/react/writing-stories/decorators) +## Publishing Packages + +The Node.js Website uses a multi-package workspace architecture where individual packages are published to the npm registry. This section outlines the process for publishing packages and the best practices to follow. + +The package publishing process is automated through GitHub Actions and can be triggered in two ways: + +1. **Automatically after successful tests**: + When changes are merged to the main branch, the "Publish Packages" workflow runs after the "Linting and Tests" workflow completes successfully. Commits must come through GitHub's merge queue (committer must be verified from noreply@github.com) + +2. **Manually via workflow dispatch**: + You can manually trigger publishing for specific packages through the GitHub Actions interface. When manually triggering publishing, ensure the commit hasn't already been published and is safe to do so. In the event of a manual trigger, a Slack notification will be sent to `#nodejs-website`. + ## Remarks on Technologies Used The Node.js Website is a somewhat complex application and at times non-trivial solutions have been implemented to solve certain technical challenges. From a8235b41dbb195229e2339f66e6a3d07820948c7 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Sat, 24 May 2025 14:50:15 -0400 Subject: [PATCH 3/4] _always_ verify commit --- .github/workflows/publish-packages.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index a695c3975d9da..7e8d13df404cb 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -42,8 +42,6 @@ jobs: egress-policy: audit - name: Verify commit authenticity - # Skip verification for manual runs since they're initiated by trusted users - if: github.event_name != 'workflow_dispatch' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | From 8572965fe66ffa7fbe6a7887d837fe4b0633bf55 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Mon, 26 May 2025 09:45:38 -0400 Subject: [PATCH 4/4] fixup --- .github/workflows/publish-packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index 7e8d13df404cb..d16686d63d995 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -91,7 +91,7 @@ jobs: done # Format the output for GitHub Actions matrix using jq - PACKAGES_JSON=$(printf '%s\n' "${CHANGED_PACKAGES[@]}" | jq -R . | jq -s .) + PACKAGES_JSON=$(jq -n '$ARGS.positional' --args "${CHANGED_PACKAGES[@]}" -c) echo "matrix={\"package\":$PACKAGES_JSON}" >> $GITHUB_OUTPUT fi