3535 required : false
3636 type : string
3737
38- concurrency : ${{ github.workflow }}-${{ github.ref }}
38+ concurrency :
39+ group : ${{ github.workflow }}-${{ github.ref }}
40+ cancel-in-progress : true
3941
4042jobs :
41- # Changeset check - only runs on PRs
43+ # === DETECT CHANGES - determines which jobs should run ===
44+ detect-changes :
45+ name : Detect Changes
46+ runs-on : ubuntu-latest
47+ if : github.event_name != 'workflow_dispatch'
48+ outputs :
49+ mjs-changed : ${{ steps.changes.outputs.mjs-changed }}
50+ js-changed : ${{ steps.changes.outputs.js-changed }}
51+ package-changed : ${{ steps.changes.outputs.package-changed }}
52+ docs-changed : ${{ steps.changes.outputs.docs-changed }}
53+ workflow-changed : ${{ steps.changes.outputs.workflow-changed }}
54+ any-code-changed : ${{ steps.changes.outputs.any-code-changed }}
55+ steps :
56+ - uses : actions/checkout@v4
57+ with :
58+ fetch-depth : 0
59+
60+ - name : Detect changes
61+ id : changes
62+ working-directory : ./js
63+ env :
64+ GITHUB_EVENT_NAME : ${{ github.event_name }}
65+ GITHUB_BASE_SHA : ${{ github.event.pull_request.base.sha }}
66+ GITHUB_HEAD_SHA : ${{ github.event.pull_request.head.sha }}
67+ run : node scripts/detect-code-changes.mjs
68+
69+ # === VERSION CHANGE CHECK ===
70+ # Prohibit manual version changes in package.json - versions should only be changed by CI/CD
71+ version-check :
72+ name : Check for Manual Version Changes
73+ runs-on : ubuntu-latest
74+ if : github.event_name == 'pull_request'
75+ steps :
76+ - uses : actions/checkout@v4
77+ with :
78+ fetch-depth : 0
79+
80+ - name : Check for version changes in package.json
81+ working-directory : ./js
82+ env :
83+ GITHUB_HEAD_REF : ${{ github.head_ref }}
84+ GITHUB_BASE_REF : ${{ github.base_ref }}
85+ run : node scripts/check-version.mjs
86+
87+ # === CHANGESET CHECK - only runs on PRs with code changes ===
88+ # Docs-only PRs (./docs folder, markdown files) don't require changesets
4289 changeset-check :
4390 name : Check for Changesets
4491 runs-on : ubuntu-latest
45- if : github.event_name == 'pull_request'
92+ needs : [detect-changes]
93+ if : github.event_name == 'pull_request' && needs.detect-changes.outputs.any-code-changed == 'true'
4694 steps :
4795 - uses : actions/checkout@v4
4896 with :
@@ -59,6 +107,10 @@ jobs:
59107
60108 - name : Check for changesets
61109 working-directory : ./js
110+ env :
111+ GITHUB_BASE_REF : ${{ github.base_ref }}
112+ GITHUB_BASE_SHA : ${{ github.event.pull_request.base.sha }}
113+ GITHUB_HEAD_SHA : ${{ github.event.pull_request.head.sha }}
62114 run : |
63115 # Skip changeset check for automated version PRs
64116 if [[ "${{ github.head_ref }}" == "changeset-release/"* ]]; then
@@ -69,12 +121,22 @@ jobs:
69121 # Run changeset validation script
70122 node scripts/validate-changeset.mjs
71123
72- # Linting and formatting - runs after changeset check on PRs, immediately on main
124+ # === LINT AND FORMAT CHECK ===
125+ # Lint runs independently of changeset-check - it's a fast check that should always run
73126 lint :
74127 name : Lint and Format Check
75128 runs-on : ubuntu-latest
76- needs : [changeset-check]
77- if : always() && (github.event_name == 'push' || needs.changeset-check.result == 'success')
129+ needs : [detect-changes]
130+ if : |
131+ always() && !cancelled() && (
132+ github.event_name == 'push' ||
133+ github.event_name == 'workflow_dispatch' ||
134+ needs.detect-changes.outputs.mjs-changed == 'true' ||
135+ needs.detect-changes.outputs.js-changed == 'true' ||
136+ needs.detect-changes.outputs.docs-changed == 'true' ||
137+ needs.detect-changes.outputs.package-changed == 'true' ||
138+ needs.detect-changes.outputs.workflow-changed == 'true'
139+ )
78140 steps :
79141 - uses : actions/checkout@v4
80142
@@ -103,8 +165,9 @@ jobs:
103165 test :
104166 name : Test (Node.js on ${{ matrix.os }})
105167 runs-on : ${{ matrix.os }}
106- needs : [changeset-check]
107- if : always() && (github.event_name == 'push' || needs.changeset-check.result == 'success')
168+ needs : [detect-changes, changeset-check]
169+ # Run if: push event, OR changeset-check succeeded, OR changeset-check was skipped (docs-only PR)
170+ if : always() && !cancelled() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.changeset-check.result == 'success' || needs.changeset-check.result == 'skipped')
108171 strategy :
109172 fail-fast : false
110173 matrix :
@@ -135,7 +198,7 @@ jobs:
135198 needs : [lint, test]
136199 # Use always() to ensure this job runs even if changeset-check was skipped
137200 # This is needed because lint/test jobs have a transitive dependency on changeset-check
138- if : always() && github.ref == 'refs/heads/main' && github.event_name == 'push' && needs.lint.result == 'success' && needs.test.result == 'success'
201+ if : always() && !cancelled() && github.ref == 'refs/heads/main' && github.event_name == 'push' && needs.lint.result == 'success' && needs.test.result == 'success'
139202 runs-on : ubuntu-latest
140203 # Permissions required for npm OIDC trusted publishing
141204 permissions :
@@ -164,11 +227,14 @@ jobs:
164227 - name : Check for changesets
165228 id : check_changesets
166229 working-directory : ./js
230+ run : node scripts/check-changesets.mjs
231+
232+ - name : Merge multiple changesets
233+ if : steps.check_changesets.outputs.has_changesets == 'true' && steps.check_changesets.outputs.changeset_count > 1
234+ working-directory : ./js
167235 run : |
168- # Count changeset files (excluding README.md and config.json)
169- CHANGESET_COUNT=$(find .changeset -name "*.md" ! -name "README.md" | wc -l)
170- echo "Found $CHANGESET_COUNT changeset file(s)"
171- echo "has_changesets=$([[ $CHANGESET_COUNT -gt 0 ]] && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT
236+ echo "Multiple changesets detected, merging..."
237+ node scripts/merge-changesets.mjs
172238
173239 - name : Version packages and commit to main
174240 if : steps.check_changesets.outputs.has_changesets == 'true'
@@ -200,7 +266,14 @@ jobs:
200266 # Manual Instant Release - triggered via workflow_dispatch with instant mode
201267 instant-release :
202268 name : Instant Release
203- if : github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'instant'
269+ needs : [lint, test]
270+ # Note: always() is required to evaluate the condition when dependencies use always()
271+ if : |
272+ always() && !cancelled() &&
273+ github.event_name == 'workflow_dispatch' &&
274+ github.event.inputs.release_mode == 'instant' &&
275+ needs.lint.result == 'success' &&
276+ needs.test.result == 'success'
204277 runs-on : ubuntu-latest
205278 # Permissions required for npm OIDC trusted publishing
206279 permissions :
0 commit comments