From 804dde7b4e0befacd1b69564f9e09eeee82a03ef Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Tue, 22 Sep 2026 12:18:21 -0500 Subject: [PATCH 1/3] ci(repo): Deprecate superseded packages after Core 2 releases Every Core 2 backport publishes new @clerk/clerk-react, @clerk/types and @clerk/clerk-expo versions that npm resolves to on a bare install with no deprecation warning, because npm skips deprecated versions when an undeprecated one exists. Deprecate the just-published versions of those packages as the last step of the release job so the warning stays in place. Co-Authored-By: Claude Fable 5.1 --- .changeset/deprecate-superseded-core-2.md | 2 + .github/workflows/release.yml | 6 ++ scripts/deprecate-superseded.mjs | 75 +++++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 .changeset/deprecate-superseded-core-2.md create mode 100644 scripts/deprecate-superseded.mjs diff --git a/.changeset/deprecate-superseded-core-2.md b/.changeset/deprecate-superseded-core-2.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/deprecate-superseded-core-2.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9abd7b80027..7e9e49fdf92 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -110,6 +110,12 @@ jobs: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CHANGELOG_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + - name: Deprecate superseded packages + if: steps.changesets.outputs.published == 'true' + run: node scripts/deprecate-superseded.mjs '${{ steps.changesets.outputs.publishedPackages }}' + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + # We're running the CI workflow (where node v20 modules are cached) in # merge_group and not on main, we need to explicitly cache node_modules here so # that follow-on branches can use the cached version of node_modules rather diff --git a/scripts/deprecate-superseded.mjs b/scripts/deprecate-superseded.mjs new file mode 100644 index 00000000000..cc257abe444 --- /dev/null +++ b/scripts/deprecate-superseded.mjs @@ -0,0 +1,75 @@ +import { execFileSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +const UPGRADE_GUIDE = 'https://clerk.com/docs/guides/development/upgrading/upgrade-guides/core-3'; + +const supersededPackages = { + '@clerk/clerk-react': `This package is no longer supported. Please use @clerk/react instead. See the upgrade guide for more info: ${UPGRADE_GUIDE}`, + '@clerk/types': `This package is no longer supported. Please import types from @clerk/shared/types instead. See the upgrade guide for more info: ${UPGRADE_GUIDE}`, + '@clerk/clerk-expo': `@clerk/clerk-expo is deprecated. Migrate to @clerk/expo by following the Core 3 upgrade guide ${UPGRADE_GUIDE}`, +}; + +const { NPM_TOKEN, DRY_RUN } = process.env; +const dryRun = DRY_RUN === '1' || DRY_RUN === 'true'; + +const run = () => { + const publishedPackages = JSON.parse(process.argv[2] ?? '[]'); + const targets = publishedPackages.filter(({ name }) => name in supersededPackages); + + if (targets.length === 0) { + console.log('No superseded packages were published, nothing to deprecate.'); + return; + } + + if (!dryRun && !NPM_TOKEN) { + throw new Error('NPM_TOKEN is required to deprecate packages.'); + } + + const npmrc = writeUserconfig(); + const failures = []; + + for (const { name, version } of targets) { + const spec = `${name}@${version}`; + const message = supersededPackages[name]; + + if (dryRun) { + console.log(`[dry-run] npm deprecate ${spec} ${JSON.stringify(message)}`); + continue; + } + + try { + npm(['deprecate', spec, message], npmrc); + const applied = npm(['view', spec, 'deprecated'], npmrc).trim(); + if (applied !== message) { + throw new Error(`Expected deprecation message was not applied. Registry returned: ${JSON.stringify(applied)}`); + } + console.log(`Deprecated ${spec}`); + } catch (error) { + failures.push(`${spec}: ${error.message}`); + } + } + + fs.rmSync(path.dirname(npmrc), { recursive: true, force: true }); + + if (failures.length > 0) { + throw new Error(`Failed to deprecate:\n${failures.join('\n')}`); + } +}; + +const writeUserconfig = () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'deprecate-superseded-')); + const file = path.join(dir, '.npmrc'); + fs.writeFileSync(file, `//registry.npmjs.org/:_authToken=${NPM_TOKEN ?? ''}\n`, { mode: 0o600 }); + return file; +}; + +const npm = (args, userconfig) => + execFileSync('npm', args, { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + env: { ...process.env, npm_config_userconfig: userconfig }, + }); + +run(); From 6edc6c9515ad3bac78a9f8c8b27852b686a75d06 Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Tue, 22 Sep 2026 12:31:25 -0500 Subject: [PATCH 2/3] ci(repo): Run the deprecate step even if the release notification fails An `if` without a status function gets an implicit `success()`, so a failed Slack notification would have skipped deprecating the versions that were already published. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7e9e49fdf92..0e496eb50b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -111,7 +111,7 @@ jobs: SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK - name: Deprecate superseded packages - if: steps.changesets.outputs.published == 'true' + if: ${{ !cancelled() && steps.changesets.outputs.published == 'true' }} run: node scripts/deprecate-superseded.mjs '${{ steps.changesets.outputs.publishedPackages }}' env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} From bfee285619eb744bc75e4030b94db1d0602781ec Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Wed, 30 Sep 2026 12:50:46 -0500 Subject: [PATCH 3/3] ci(repo): Trigger CI after leaving draft The CI jobs skip on draft PRs and do not re-run when a PR is marked ready, so they never ran against this branch. Co-Authored-By: Claude Fable 5.1