Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/release-dispatch-allsettled.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
148 changes: 109 additions & 39 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ jobs:

- name: Trigger workflows on related repos
if: steps.changesets.outputs.published == 'true'
continue-on-error: true
uses: actions/github-script@v7
with:
result-encoding: string
Expand All @@ -87,33 +88,104 @@ jobs:
const clerkUiVersion = require('./packages/ui/package.json').version;
const nextjsVersion = require('./packages/nextjs/package.json').version;

const dispatches = [
github.rest.actions.createWorkflowDispatch({
owner: 'clerk',
repo: 'sdk-infra-workers',
workflow_id: 'update-pkg-versions.yml',
ref: 'main',
inputs: { clerkjsVersion, clerkUiVersion }
}),
github.rest.actions.createWorkflowDispatch({
owner: 'clerk',
repo: 'dashboard',
workflow_id: 'prepare-nextjs-sdk-update.yml',
ref: 'main',
inputs: { version: nextjsVersion }
}),
github.rest.actions.createWorkflowDispatch({
owner: 'clerk',
repo: 'clerk-docs',
workflow_id: 'typedoc.yml',
ref: 'main',
}),
const targets = [
{ repo: 'sdk-infra-workers', workflow_id: 'update-pkg-versions.yml', inputs: { clerkjsVersion, clerkUiVersion } },
{ repo: 'dashboard', workflow_id: 'prepare-nextjs-sdk-update.yml', inputs: { version: nextjsVersion } },
{ repo: 'clerk-docs', workflow_id: 'typedoc.yml' },
];
await Promise.all(dispatches);
const results = await Promise.allSettled(
targets.map(t => github.rest.actions.createWorkflowDispatch({ owner: 'clerk', ref: 'main', ...t }))
);
const failures = results
.map((r, i) => r.status === 'rejected' ? { target: targets[i], reason: r.reason } : null)
.filter(Boolean);
if (failures.length) {
failures.forEach(f => core.error(`Dispatch to ${f.target.repo}/${f.target.workflow_id} failed: ${f.reason?.message ?? f.reason}`));
core.setFailed(`${failures.length} downstream dispatch(es) failed`);
}
} else{
core.warning("Changeset in pre-mode should not prepare a ClerkJS production release")
}

# Recovery: if the changesets action published to npm but then failed
# (e.g. git push --follow-tags error), the `published` output is never
# set and downstream repos are not notified. This step detects that
# scenario by checking npm for the local package version and dispatches
# if the packages are already live.
- name: Recover downstream notifications
if: always() && steps.changesets.conclusion == 'failure'
continue-on-error: true
uses: actions/github-script@v7
with:
result-encoding: string
retries: 3
retry-exempt-status-codes: 400,401
github-token: ${{ secrets.CLERK_COOKIE_PAT }}
script: |
const { execSync } = require('child_process');

const clerkjsVersion = require('./packages/clerk-js/package.json').version;
const clerkUiVersion = require('./packages/ui/package.json').version;

// Only recover stable releases
const preReleases = [
clerkjsVersion.includes('-') && `@clerk/clerk-js@${clerkjsVersion}`,
clerkUiVersion.includes('-') && `@clerk/ui@${clerkUiVersion}`,
].filter(Boolean);
if (preReleases.length > 0) {
console.log(`Skipping recovery: ${preReleases.join(', ')} is a pre-release`);
return;
}

const preMode = require("fs").existsSync("./.changeset/pre.json");
if (preMode) {
core.warning("Changeset in pre-mode, skipping recovery dispatch");
return;
}

// Check if either version was actually published to npm
function isPublished(name, version) {
try {
return execSync(`npm view ${name}@${version} version`, { encoding: 'utf8' }).trim() === version;
} catch {
return false;
}
}
Comment on lines +147 to +153
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider logging suppressed errors in isPublished for debuggability.

The catch block silently returns false for any error. While this is reasonable for "check if published" semantics, transient npm registry errors would be indistinguishable from "package not published," potentially skipping recovery when it should have run.

♻️ Proposed improvement for debuggability
             function isPublished(name, version) {
               try {
                 return execSync(`npm view ${name}@${version} version`, { encoding: 'utf8' }).trim() === version;
-              } catch {
+              } catch (e) {
+                console.log(`npm view ${name}@${version} failed: ${e.message}`);
                 return false;
               }
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function isPublished(name, version) {
try {
return execSync(`npm view ${name}@${version} version`, { encoding: 'utf8' }).trim() === version;
} catch {
return false;
}
}
function isPublished(name, version) {
try {
return execSync(`npm view ${name}@${version} version`, { encoding: 'utf8' }).trim() === version;
} catch (e) {
console.log(`npm view ${name}@${version} failed: ${e.message}`);
return false;
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 147 - 153, The catch in
isPublished currently swallows all errors; change it to capture the error (e.g.,
catch (err)) and log it (for example using console.warn or core.warning) with
context including the package name and version before returning false so
transient registry or network errors are visible; update the isPublished
function around the execSync call to include the caught error in the log
message.


const clerkjsPublished = isPublished('@clerk/clerk-js', clerkjsVersion);
const clerkUiPublished = isPublished('@clerk/ui', clerkUiVersion);

if (!clerkjsPublished && !clerkUiPublished) {
console.log('Neither @clerk/clerk-js nor @clerk/ui were published to npm, no recovery needed');
return;
}

const published = [
clerkjsPublished && `@clerk/clerk-js@${clerkjsVersion}`,
clerkUiPublished && `@clerk/ui@${clerkUiVersion}`,
].filter(Boolean).join(', ');
core.warning(`Recovery: ${published} published to npm but downstream repos were not notified. Dispatching now.`);

const nextjsVersion = require('./packages/nextjs/package.json').version;

const targets = [
{ repo: 'sdk-infra-workers', workflow_id: 'update-pkg-versions.yml', inputs: { clerkjsVersion, clerkUiVersion } },
{ repo: 'dashboard', workflow_id: 'prepare-nextjs-sdk-update.yml', inputs: { version: nextjsVersion } },
{ repo: 'clerk-docs', workflow_id: 'typedoc.yml' },
];
Comment on lines +171 to +175
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Extract shared targets array to reduce duplication.

The targets array is duplicated verbatim from lines 91-95. If a downstream repository is added or removed, both locations must be updated, risking drift.

Consider extracting the targets definition to a shared location (e.g., a JSON file or a reusable composite action) or at minimum, add a comment linking the two locations to alert future maintainers.

♻️ Alternative: inline comment to link the two locations
+            // NOTE: Keep in sync with targets array in "Trigger workflows on related repos" step above
             const targets = [
               { repo: 'sdk-infra-workers', workflow_id: 'update-pkg-versions.yml', inputs: { clerkjsVersion, clerkUiVersion } },
               { repo: 'dashboard', workflow_id: 'prepare-nextjs-sdk-update.yml', inputs: { version: nextjsVersion } },
               { repo: 'clerk-docs', workflow_id: 'typedoc.yml' },
             ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const targets = [
{ repo: 'sdk-infra-workers', workflow_id: 'update-pkg-versions.yml', inputs: { clerkjsVersion, clerkUiVersion } },
{ repo: 'dashboard', workflow_id: 'prepare-nextjs-sdk-update.yml', inputs: { version: nextjsVersion } },
{ repo: 'clerk-docs', workflow_id: 'typedoc.yml' },
];
// NOTE: Keep in sync with targets array in "Trigger workflows on related repos" step above
const targets = [
{ repo: 'sdk-infra-workers', workflow_id: 'update-pkg-versions.yml', inputs: { clerkjsVersion, clerkUiVersion } },
{ repo: 'dashboard', workflow_id: 'prepare-nextjs-sdk-update.yml', inputs: { version: nextjsVersion } },
{ repo: 'clerk-docs', workflow_id: 'typedoc.yml' },
];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 171 - 175, The duplicated targets
array (variable name targets containing entries with repo:'sdk-infra-workers',
repo:'dashboard', repo:'clerk-docs' and workflow_id keys like
'update-pkg-versions.yml', 'prepare-nextjs-sdk-update.yml', 'typedoc.yml')
should be deduplicated: extract it into a single shared source (e.g., a
JSON/YAML file or a reusable composite action) and have this workflow
load/reference that single definition, or at minimum add a clear inline comment
in both places pointing to the other location to prevent drift; update the
release.yml to reference the shared definition (or include the linking comment)
and ensure inputs (clerkjsVersion, clerkUiVersion, version: nextjsVersion)
remain intact.

const results = await Promise.allSettled(
targets.map(t => github.rest.actions.createWorkflowDispatch({ owner: 'clerk', ref: 'main', ...t }))
);
const failures = results
.map((r, i) => r.status === 'rejected' ? { target: targets[i], reason: r.reason } : null)
.filter(Boolean);
if (failures.length) {
failures.forEach(f => core.error(`Recovery dispatch to ${f.target.repo}/${f.target.workflow_id} failed: ${f.reason?.message ?? f.reason}`));
core.setFailed(`${failures.length} recovery dispatch(es) failed`);
} else {
core.notice('Recovery dispatch completed successfully');
}

- name: Generate notification payload
id: notification
if: steps.changesets.outputs.published == 'true'
Expand Down Expand Up @@ -198,6 +270,7 @@ jobs:

- name: Trigger workflows on related repos
if: steps.publish.outcome == 'success'
continue-on-error: true
uses: actions/github-script@v7
with:
result-encoding: string
Expand All @@ -209,30 +282,27 @@ jobs:
const clerkUiVersion = require('./packages/ui/package.json').version;
const nextjsVersion = require('./packages/nextjs/package.json').version;

const dispatches = [
github.rest.actions.createWorkflowDispatch({
owner: 'clerk',
repo: 'sdk-infra-workers',
workflow_id: 'update-pkg-versions.yml',
ref: 'main',
inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha }
}),
const targets = [
{ repo: 'sdk-infra-workers', workflow_id: 'update-pkg-versions.yml', inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha } },
];

if (nextjsVersion.includes('canary')) {
console.log('clerk/nextjs changed, will notify clerk/accounts');
dispatches.push(
github.rest.actions.createWorkflowDispatch({
owner: 'clerk',
repo: 'accounts',
workflow_id: 'release-staging.yml',
ref: 'main',
inputs: { version: nextjsVersion }
}),
targets.push(
{ repo: 'accounts', workflow_id: 'release-staging.yml', inputs: { version: nextjsVersion } },
);
}

await Promise.all(dispatches);
const results = await Promise.allSettled(
targets.map(t => github.rest.actions.createWorkflowDispatch({ owner: 'clerk', ref: 'main', ...t }))
);
const failures = results
.map((r, i) => r.status === 'rejected' ? { target: targets[i], reason: r.reason } : null)
.filter(Boolean);
if (failures.length) {
failures.forEach(f => core.error(`Dispatch to ${f.target.repo}/${f.target.workflow_id} failed: ${f.reason?.message ?? f.reason}`));
core.setFailed(`${failures.length} downstream dispatch(es) failed`);
}

- name: Notify Slack on failure
if: ${{ always() && steps.publish.outcome == 'failure' }}
Expand Down
Loading