11name : Publish Packages
22
3+ # This workflow publishes packages to NPM when changes are merged to main branch or when manually triggered.
4+ # It runs automatically after successful tests or can be run manually for specific packages.
5+
36on :
47 workflow_run :
8+ # Only run after linting and tests have passed on main branch
59 workflows : ['Linting and Tests']
610 types : [completed]
11+ # For security reasons, this should never be set to anything but `main`
712 branches : [main]
813 workflow_dispatch :
914 inputs :
@@ -16,44 +21,40 @@ permissions:
1621 contents : read
1722
1823env :
24+ # Use the SHA from the workflow run that triggered this or the current SHA for manual runs
1925 COMMIT_SHA : ${{ github.event.workflow_run.head_sha || github.sha }}
2026
2127jobs :
22- detect -packages :
28+ prepare -packages :
2329 runs-on : ubuntu-latest
30+ # Only run if manually triggered or if the triggering workflow succeeded from a push event
31+ if : github.event_name == 'workflow_dispatch' || (
32+ github.event.workflow_run.conclusion == 'success' &&
33+ github.event.workflow_run.event == 'push' &&
34+ github.repository == 'nodejs/nodejs.org')
2435 outputs :
25- packages : ${{ steps.find-packages.outputs.packages }}
26- steps :
27- - name : Checkout repository
28- uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
29-
30- - name : Find packages
31- id : find-packages
32- env :
33- PACKAGE : ${{ github.event.inputs.package }}
34- run : |
35- if [ "$PACKAGE" != "" ]; then
36- echo "packages=[\"$PACKAGE\"]" >> $GITHUB_OUTPUT
37- else
38- PACKAGES=$(ls -d packages/* | xargs -n 1 basename | jq -R -s -c 'split("\n")[:-1]')
39- echo "packages=$PACKAGES" >> $GITHUB_OUTPUT
40- fi
41-
42- verify-commit :
43- runs-on : ubuntu-latest
44- if : github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push')
36+ # Output the matrix of packages to publish for use in the publish job
37+ matrix : ${{ steps.generate-matrix.outputs.matrix }}
4538 steps :
46- - name : Checkout repository
47- uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
39+ - name : Harden Runner
40+ uses : step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
41+ with :
42+ egress-policy : audit
4843
4944 - name : Verify commit authenticity
45+ # Skip verification for manual runs since they're initiated by trusted users
46+ if : github.event_name != 'workflow_dispatch'
5047 env :
5148 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
5249 run : |
50+ # Get commit data from GitHub API to verify its authenticity
5351 COMMIT_DATA=$(gh api repos/${{ github.repository }}/commits/$COMMIT_SHA)
52+ # Check if commit signature is verified (GPG signed)
5453 VERIFIED=$(echo "$COMMIT_DATA" | jq -r '.commit.verification.verified')
54+ # Check if commit was made through GitHub's web interface (merge queue)
5555 COMMITTER=$(echo "$COMMIT_DATA" | jq -r '.commit.committer.email')
5656
57+ # Security checks to ensure we only publish from verified and trusted sources
5758 if [[ "$VERIFIED" != "true" ]]; then
5859 echo "❌ Unverified commit! Aborting."
5960 exit 1
@@ -66,49 +67,85 @@ jobs:
6667
6768 echo "✅ Commit is verified and trusted."
6869
69- publish :
70- needs : [detect-packages, verify-commit]
71- runs-on : ubuntu-latest
72- strategy :
73- matrix :
74- package : ${{ fromJson(needs.detect-packages.outputs.packages) }}
75- fail-fast : false
76- steps :
7770 - name : Checkout repository
7871 uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
7972 with :
80- fetch-depth : 2
73+ fetch-depth : 2 # Need at least 2 commits to detect changes between commits
8174
82- - name : Check for package changes
83- if : github.event_name != 'workflow_dispatch'
84- id : check_changes
75+ - name : Generate package matrix
76+ id : generate-matrix
8577 env :
86- PACKAGE : ${{ matrix.package }}
78+ PACKAGE : ${{ github.event.inputs.package }}
79+ EVENT_NAME : ${{ github.event_name }}
8780 run : |
88- if git diff --quiet $COMMIT_SHA~1 $COMMIT_SHA -- "packages/$PACKAGE/"; then
89- echo "changed=false" >> $GITHUB_OUTPUT
81+ if [ -n "$PACKAGE" ]; then
82+ # If a specific package is requested via workflow_dispatch, just publish that one
83+ echo "matrix={\"package\":[\"$PACKAGE\"]}" >> $GITHUB_OUTPUT
9084 else
91- echo "changed=true" >> $GITHUB_OUTPUT
85+ # Otherwise, identify all packages with changes since the last commit
86+ CHANGED_PACKAGES=()
87+ for pkg in $(ls -d packages/*); do
88+ PKG_NAME=$(basename "$pkg")
89+ # For manual runs, include all packages. For automatic runs, only include packages with changes
90+ if [ "$EVENT_NAME" == "workflow_dispatch" ] || ! git diff --quiet $COMMIT_SHA~1 $COMMIT_SHA -- "$pkg/"; then
91+ CHANGED_PACKAGES+=("$PKG_NAME")
92+ fi
93+ done
94+
95+ # Format the output for GitHub Actions matrix using jq
96+ PACKAGES_JSON=$(printf '%s\n' "${CHANGED_PACKAGES[@]}" | jq -R . | jq -s .)
97+ echo "matrix={\"package\":$PACKAGES_JSON}" >> $GITHUB_OUTPUT
9298 fi
9399
100+ publish :
101+ needs : prepare-packages
102+ runs-on : ubuntu-latest
103+ # Use the dynamic matrix from prepare-packages job to create parallel jobs for each package
104+ strategy :
105+ matrix : ${{ fromJson(needs.prepare-packages.outputs.matrix) }}
106+ fail-fast : false # Continue publishing other packages even if one fails
107+ steps :
108+ - name : Harden Runner
109+ uses : step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
110+ with :
111+ egress-policy : audit
112+
113+ - name : Checkout repository
114+ uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
115+
94116 - name : Set up pnpm
95117 uses : pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
96118 with :
97119 cache : true
98120
99121 - name : Setup Node.js
100- if : github.event_name == 'workflow_dispatch' || steps.check_changes.outputs.changed == 'true'
101122 uses : actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
102123 with :
103124 node-version-file : ' .nvmrc'
104125 registry-url : ' https://registry.npmjs.org'
105126 cache : pnpm
106127
107128 - name : Publish
108- if : github.event_name == 'workflow_dispatch' || steps.check_changes.outputs.changed == 'true'
109129 working-directory : packages/${{ matrix.package }}
110130 env :
111131 NPM_TOKEN : ${{ secrets.NPM_TOKEN }}
112- run : >
132+ run : |
133+ # Create a unique version using the commit SHA as a prerelease identifier
134+ # This ensures we can publish multiple times from the same codebase with unique versions
113135 npm version --no-git-tag-version 0.0.0-$COMMIT_SHA
136+ # Publish the package to the npm registry with public access flag
114137 pnpm publish --access public
138+
139+ - name : Notify on Manual Release
140+ if : ${{ github.event_name == 'workflow_dispatch' }}
141+ uses : rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # 2.3.3
142+ env :
143+ SLACK_COLOR : ' #43853D'
144+ SLACK_ICON : https://github.com/nodejs.png?size=48
145+ SLACK_TITLE : ' :rocket: Package Published: ${{ matrix.package }}'
146+ SLACK_MESSAGE : |
147+ :package: *Package*: `${{ matrix.package }}` (<https://www.npmjs.com/package/${{ steps.package-info.outputs.name }}|View on npm>)
148+ :bust_in_silhouette: *Published by*: ${{ github.triggering_actor }}
149+ :octocat: *Commit*: <https://github.com/${{ github.repository }}/commit/${{ env.COMMIT_SHA }}|${{ env.COMMIT_SHA }}>
150+ SLACK_USERNAME : nodejs-bot
151+ SLACK_WEBHOOK : ${{ secrets.SLACK_WEBHOOK }}
0 commit comments