8181
8282 - name : Trigger workflows on related repos
8383 if : steps.changesets.outputs.published == 'true'
84+ continue-on-error : true
8485 uses : actions/github-script@v7
8586 with :
8687 result-encoding : string
@@ -94,33 +95,107 @@ jobs:
9495 const clerkUiVersion = require('./packages/ui/package.json').version;
9596 const nextjsVersion = require('./packages/nextjs/package.json').version;
9697
97- const dispatches = [
98- github.rest.actions.createWorkflowDispatch({
99- owner: 'clerk',
100- repo: 'sdk-infra-workers',
101- workflow_id: 'update-pkg-versions.yml',
102- ref: 'main',
103- inputs: { clerkjsVersion, clerkUiVersion }
104- }),
105- github.rest.actions.createWorkflowDispatch({
106- owner: 'clerk',
107- repo: 'dashboard',
108- workflow_id: 'prepare-nextjs-sdk-update.yml',
109- ref: 'main',
110- inputs: { version: nextjsVersion }
111- }),
112- github.rest.actions.createWorkflowDispatch({
113- owner: 'clerk',
114- repo: 'clerk-docs',
115- workflow_id: 'typedoc.yml',
116- ref: 'main',
117- }),
98+ // NOTE: Keep in sync with the `targets` array in the "Recover downstream notifications" step below.
99+ const targets = [
100+ { repo: 'sdk-infra-workers', workflow_id: 'update-pkg-versions.yml', inputs: { clerkjsVersion, clerkUiVersion } },
101+ { repo: 'dashboard', workflow_id: 'prepare-nextjs-sdk-update.yml', inputs: { version: nextjsVersion } },
102+ { repo: 'clerk-docs', workflow_id: 'typedoc.yml' },
118103 ];
119- await Promise.all(dispatches);
104+ const results = await Promise.allSettled(
105+ targets.map(t => github.rest.actions.createWorkflowDispatch({ owner: 'clerk', ref: 'main', ...t }))
106+ );
107+ const failures = results
108+ .map((r, i) => r.status === 'rejected' ? { target: targets[i], reason: r.reason } : null)
109+ .filter(Boolean);
110+ if (failures.length) {
111+ failures.forEach(f => core.error(`Dispatch to ${f.target.repo}/${f.target.workflow_id} failed: ${f.reason?.message ?? f.reason}`));
112+ core.setFailed(`${failures.length} downstream dispatch(es) failed`);
113+ }
120114 } else{
121115 core.warning("Changeset in pre-mode should not prepare a ClerkJS production release")
122116 }
123117
118+ # Recovery: if the changesets action published to npm but then failed
119+ # (e.g. git push --follow-tags error), the `published` output is never
120+ # set and downstream repos are not notified. This step detects that
121+ # scenario by checking npm for the local package version and dispatches
122+ # if the packages are already live.
123+ - name : Recover downstream notifications
124+ if : always() && steps.changesets.conclusion == 'failure'
125+ continue-on-error : true
126+ uses : actions/github-script@v7
127+ with :
128+ result-encoding : string
129+ retries : 3
130+ retry-exempt-status-codes : 400,401
131+ github-token : ${{ secrets.CLERK_COOKIE_PAT }}
132+ script : |
133+ const { execSync } = require('child_process');
134+
135+ const clerkjsVersion = require('./packages/clerk-js/package.json').version;
136+ const clerkUiVersion = require('./packages/ui/package.json').version;
137+
138+ // Only recover stable releases
139+ const preReleases = [
140+ clerkjsVersion.includes('-') && `@clerk/clerk-js@${clerkjsVersion}`,
141+ clerkUiVersion.includes('-') && `@clerk/ui@${clerkUiVersion}`,
142+ ].filter(Boolean);
143+ if (preReleases.length > 0) {
144+ console.log(`Skipping recovery: ${preReleases.join(', ')} is a pre-release`);
145+ return;
146+ }
147+
148+ const preMode = require("fs").existsSync("./.changeset/pre.json");
149+ if (preMode) {
150+ core.warning("Changeset in pre-mode, skipping recovery dispatch");
151+ return;
152+ }
153+
154+ // Check if either version was actually published to npm
155+ function isPublished(name, version) {
156+ try {
157+ return execSync(`npm view ${name}@${version} version`, { encoding: 'utf8' }).trim() === version;
158+ } catch (e) {
159+ console.log(`npm view ${name}@${version} failed: ${e.message}`);
160+ return false;
161+ }
162+ }
163+
164+ const clerkjsPublished = isPublished('@clerk/clerk-js', clerkjsVersion);
165+ const clerkUiPublished = isPublished('@clerk/ui', clerkUiVersion);
166+
167+ if (!clerkjsPublished && !clerkUiPublished) {
168+ console.log('Neither @clerk/clerk-js nor @clerk/ui were published to npm, no recovery needed');
169+ return;
170+ }
171+
172+ const published = [
173+ clerkjsPublished && `@clerk/clerk-js@${clerkjsVersion}`,
174+ clerkUiPublished && `@clerk/ui@${clerkUiVersion}`,
175+ ].filter(Boolean).join(', ');
176+ core.warning(`Recovery: ${published} published to npm but downstream repos were not notified. Dispatching now.`);
177+
178+ const nextjsVersion = require('./packages/nextjs/package.json').version;
179+
180+ // NOTE: Keep in sync with the `targets` array in the "Trigger workflows on related repos" step above.
181+ const targets = [
182+ { repo: 'sdk-infra-workers', workflow_id: 'update-pkg-versions.yml', inputs: { clerkjsVersion, clerkUiVersion } },
183+ { repo: 'dashboard', workflow_id: 'prepare-nextjs-sdk-update.yml', inputs: { version: nextjsVersion } },
184+ { repo: 'clerk-docs', workflow_id: 'typedoc.yml' },
185+ ];
186+ const results = await Promise.allSettled(
187+ targets.map(t => github.rest.actions.createWorkflowDispatch({ owner: 'clerk', ref: 'main', ...t }))
188+ );
189+ const failures = results
190+ .map((r, i) => r.status === 'rejected' ? { target: targets[i], reason: r.reason } : null)
191+ .filter(Boolean);
192+ if (failures.length) {
193+ failures.forEach(f => core.error(`Recovery dispatch to ${f.target.repo}/${f.target.workflow_id} failed: ${f.reason?.message ?? f.reason}`));
194+ core.setFailed(`${failures.length} recovery dispatch(es) failed`);
195+ } else {
196+ core.notice('Recovery dispatch completed successfully');
197+ }
198+
124199 - name : Generate notification payload
125200 id : notification
126201 if : steps.changesets.outputs.published == 'true'
@@ -205,6 +280,7 @@ jobs:
205280
206281 - name : Trigger workflows on related repos
207282 if : steps.publish.outcome == 'success'
283+ continue-on-error : true
208284 uses : actions/github-script@v7
209285 with :
210286 result-encoding : string
@@ -216,30 +292,27 @@ jobs:
216292 const clerkUiVersion = require('./packages/ui/package.json').version;
217293 const nextjsVersion = require('./packages/nextjs/package.json').version;
218294
219- const dispatches = [
220- github.rest.actions.createWorkflowDispatch({
221- owner: 'clerk',
222- repo: 'sdk-infra-workers',
223- workflow_id: 'update-pkg-versions.yml',
224- ref: 'main',
225- inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha }
226- }),
295+ const targets = [
296+ { repo: 'sdk-infra-workers', workflow_id: 'update-pkg-versions.yml', inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha } },
227297 ];
228298
229299 if (nextjsVersion.includes('canary')) {
230300 console.log('clerk/nextjs changed, will notify clerk/accounts');
231- dispatches.push(
232- github.rest.actions.createWorkflowDispatch({
233- owner: 'clerk',
234- repo: 'accounts',
235- workflow_id: 'release-staging.yml',
236- ref: 'main',
237- inputs: { version: nextjsVersion }
238- }),
301+ targets.push(
302+ { repo: 'accounts', workflow_id: 'release-staging.yml', inputs: { version: nextjsVersion } },
239303 );
240304 }
241305
242- await Promise.all(dispatches);
306+ const results = await Promise.allSettled(
307+ targets.map(t => github.rest.actions.createWorkflowDispatch({ owner: 'clerk', ref: 'main', ...t }))
308+ );
309+ const failures = results
310+ .map((r, i) => r.status === 'rejected' ? { target: targets[i], reason: r.reason } : null)
311+ .filter(Boolean);
312+ if (failures.length) {
313+ failures.forEach(f => core.error(`Dispatch to ${f.target.repo}/${f.target.workflow_id} failed: ${f.reason?.message ?? f.reason}`));
314+ core.setFailed(`${failures.length} downstream dispatch(es) failed`);
315+ }
243316
244317 - name : Notify Slack on failure
245318 if : ${{ always() && steps.publish.outcome == 'failure' }}
0 commit comments