@@ -107,6 +107,85 @@ jobs:
107107 core.warning("Changeset in pre-mode should not prepare a ClerkJS production release")
108108 }
109109
110+ # Recovery: if the changesets action published to npm but then failed
111+ # (e.g. git push --follow-tags error), the `published` output is never
112+ # set and downstream repos are not notified. This step detects that
113+ # scenario by checking npm for the local package version and dispatches
114+ # if the packages are already live.
115+ - name : Recover downstream notifications
116+ if : always() && steps.changesets.conclusion == 'failure'
117+ continue-on-error : true
118+ uses : actions/github-script@v7
119+ with :
120+ result-encoding : string
121+ retries : 3
122+ retry-exempt-status-codes : 400,401
123+ github-token : ${{ secrets.CLERK_COOKIE_PAT }}
124+ script : |
125+ const { execSync } = require('child_process');
126+
127+ const clerkjsVersion = require('./packages/clerk-js/package.json').version;
128+ const clerkUiVersion = require('./packages/ui/package.json').version;
129+
130+ // Only recover stable releases
131+ const preReleases = [
132+ clerkjsVersion.includes('-') && `@clerk/clerk-js@${clerkjsVersion}`,
133+ clerkUiVersion.includes('-') && `@clerk/ui@${clerkUiVersion}`,
134+ ].filter(Boolean);
135+ if (preReleases.length > 0) {
136+ console.log(`Skipping recovery: ${preReleases.join(', ')} is a pre-release`);
137+ return;
138+ }
139+
140+ const preMode = require("fs").existsSync("./.changeset/pre.json");
141+ if (preMode) {
142+ core.warning("Changeset in pre-mode, skipping recovery dispatch");
143+ return;
144+ }
145+
146+ // Check if either version was actually published to npm
147+ function isPublished(name, version) {
148+ try {
149+ return execSync(`npm view ${name}@${version} version`, { encoding: 'utf8' }).trim() === version;
150+ } catch {
151+ return false;
152+ }
153+ }
154+
155+ const clerkjsPublished = isPublished('@clerk/clerk-js', clerkjsVersion);
156+ const clerkUiPublished = isPublished('@clerk/ui', clerkUiVersion);
157+
158+ if (!clerkjsPublished && !clerkUiPublished) {
159+ console.log('Neither @clerk/clerk-js nor @clerk/ui were published to npm, no recovery needed');
160+ return;
161+ }
162+
163+ const published = [
164+ clerkjsPublished && `@clerk/clerk-js@${clerkjsVersion}`,
165+ clerkUiPublished && `@clerk/ui@${clerkUiVersion}`,
166+ ].filter(Boolean).join(', ');
167+ core.warning(`Recovery: ${published} published to npm but downstream repos were not notified. Dispatching now.`);
168+
169+ const nextjsVersion = require('./packages/nextjs/package.json').version;
170+
171+ const targets = [
172+ { repo: 'sdk-infra-workers', workflow_id: 'update-pkg-versions.yml', inputs: { clerkjsVersion, clerkUiVersion } },
173+ { repo: 'dashboard', workflow_id: 'prepare-nextjs-sdk-update.yml', inputs: { version: nextjsVersion } },
174+ { repo: 'clerk-docs', workflow_id: 'typedoc.yml' },
175+ ];
176+ const results = await Promise.allSettled(
177+ targets.map(t => github.rest.actions.createWorkflowDispatch({ owner: 'clerk', ref: 'main', ...t }))
178+ );
179+ const failures = results
180+ .map((r, i) => r.status === 'rejected' ? { target: targets[i], reason: r.reason } : null)
181+ .filter(Boolean);
182+ if (failures.length) {
183+ failures.forEach(f => core.error(`Recovery dispatch to ${f.target.repo}/${f.target.workflow_id} failed: ${f.reason?.message ?? f.reason}`));
184+ core.setFailed(`${failures.length} recovery dispatch(es) failed`);
185+ } else {
186+ core.notice('Recovery dispatch completed successfully');
187+ }
188+
110189 - name : Generate notification payload
111190 id : notification
112191 if : steps.changesets.outputs.published == 'true'
0 commit comments