Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9671e4f
feat: add release infrastructure with cross-compiled binaries
wyattjoh Mar 13, 2026
458ad76
feat: add canary and snapshot release channels
wyattjoh Mar 13, 2026
f3560b2
fix: add .ts extension to config imports in test files
wyattjoh Mar 13, 2026
d4ebb77
fix: normalize all relative imports to use .ts extensions
wyattjoh Mar 13, 2026
5f2b957
fix: isolate test files to work around Bun mock.module leak
wyattjoh Mar 13, 2026
e9268b3
fix: harden workflows and clean up code issues
wyattjoh Mar 13, 2026
dc801a0
fix: align release flow with JS monorepo conventions
wyattjoh Mar 13, 2026
d6da7fe
docs: update release docs and add musl detection references
wyattjoh Mar 13, 2026
88ddd53
fix: address code review findings
wyattjoh Mar 13, 2026
31b83b7
fix: add snapshot smoke test, guard signal forwarding, fix docs
wyattjoh Mar 13, 2026
bffd158
feat: migrate npm publishing to OIDC trusted publishing
wyattjoh Mar 13, 2026
a14084c
refactor: simplify shim by using spawnSync instead of async spawn
wyattjoh Mar 13, 2026
a187670
chore: update license year to 2026
wyattjoh Mar 13, 2026
45abd2b
refactor: use static import for dev version and add npm provenance
wyattjoh Mar 13, 2026
2967063
fix: address code review findings from release infrastructure
wyattjoh Mar 13, 2026
7ffc7f3
fix: address code review findings from release and packaging
wyattjoh Mar 13, 2026
1b238a9
refactor: extract smoke tests into reusable workflow and simplify tsc…
wyattjoh Mar 13, 2026
3f52d84
fix: move typescript from peerDependencies to devDependencies
wyattjoh Mar 13, 2026
975ab13
fix: add job timeouts, set tag format, and reduce artifact retention
wyattjoh Mar 13, 2026
1a82814
docs: update CLAUDE.md paths for monorepo structure
wyattjoh Mar 13, 2026
9b0508d
fix: harden release scripts and update CLAUDE.md documentation
wyattjoh Mar 13, 2026
27df13e
fix: update bun.lock and fix errorSpy reference after rebase
wyattjoh Mar 16, 2026
8749cac
refactor: rename npm wrapper package from @clerk/cli to clerk
wyattjoh Mar 17, 2026
d799450
fix: remove unused errorSpy variable in link test
wyattjoh Mar 19, 2026
0599b1e
ci: narrow workflow permissions and fix multiline output
wyattjoh Mar 19, 2026
fa6d7f9
fix(ci): remove unintended leading spaces in snapshot failure comment
wyattjoh Mar 19, 2026
cebde2c
refactor: add bunTarget and ext fields to target definitions
wyattjoh Mar 19, 2026
109556f
feat: add check-release script for stable release detection
wyattjoh Mar 19, 2026
f482ff0
feat: add changesets configuration
wyattjoh Mar 19, 2026
4199333
feat: add cross-compile build script
wyattjoh Mar 19, 2026
507f311
feat: add canary versioning script
wyattjoh Mar 19, 2026
e89d2b0
feat: add snapshot versioning script
wyattjoh Mar 19, 2026
7d4e38f
feat: add git tag and GitHub Release creation to releaser
wyattjoh Mar 19, 2026
6490b26
feat: add release, version, and build scripts to root package.json
wyattjoh Mar 19, 2026
58efb32
refactor: consolidate build-binaries into single sequential job
wyattjoh Mar 19, 2026
edc36cb
feat: replace release-please with changesets/action in release workflow
wyattjoh Mar 19, 2026
55ee209
refactor: use changeset snapshot script and fix failure comment inden…
wyattjoh Mar 19, 2026
2d76c86
chore: remove release-please configuration files
wyattjoh Mar 19, 2026
f30646c
docs: update releasing.md for changesets migration
wyattjoh Mar 19, 2026
077275b
fix(ci): replace heredocs with echo+body-file in snapshot.yml comments
wyattjoh Mar 19, 2026
2d01b44
fix: address review findings — error handling and cleanup
wyattjoh Mar 19, 2026
66c394c
refactor: consolidate canary.ts into snapshot.ts
wyattjoh Mar 19, 2026
fd316a6
refactor: extract shared isPublished helper, inline readVersion, remo…
wyattjoh Mar 19, 2026
0847f26
refactor: standardize arg parsing with node:util parseArgs, co-locate…
wyattjoh Mar 19, 2026
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
11 changes: 11 additions & 0 deletions .changeset/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# Changesets

This directory is used by [Changesets](https://github.com/changesets/changesets) to manage versioning and changelogs.

To add a changeset, run:

```sh
bunx changeset
```

This will prompt you to select the packages that changed and the type of change (patch, minor, major). A markdown file will be created in this directory describing the change — commit it with your PR.
14 changes: 14 additions & 0 deletions .changeset/config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "public",
"baseBranch": "origin/main",
"updateInternalDependencies": "patch",
"ignore": ["@clerk/cli-core"],
"snapshot": {
"useCalculatedVersion": true,
"prereleaseTemplate": "{tag}.v{datetime}"
}
}
79 changes: 79 additions & 0 deletions .github/workflows/build-binaries.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
name: Build Binaries

on:
workflow_call:
inputs:
version:
required: true
type: string
ref:
required: true
type: string
artifact-prefix:
required: true
type: string
description: "Prefix for artifact names (e.g. 'clerk', 'clerk-canary', 'clerk-snapshot')"

jobs:

@brkalowbrkalowMar 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a practical benefit to parallelizing these? For a larger project I could see the argument, but these builds should be really quick.

I expect the overhead of the GH actions setup here to build / upload / download the artifacts might end up taking longer than doing everything inline.

build:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Build all targets
env:
CLI_VERSION: ${{ inputs.version }}
run: bun run scripts/build.ts --version="${CLI_VERSION}"
Comment on lines +26 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the build script
find . -name "build.ts" -o -name "build.js"| head -20

Repository: clerk/cli-new

Length of output: 75


🏁 Script executed:

# Check the workflow file structure to understand how targets are built
cat -n .github/workflows/build-binaries.yml | head -50

Repository: clerk/cli-new

Length of output: 1722


🏁 Script executed:

# Find and examine the cli-core package.json
find . -path "*/packages/cli-core/package.json"

Repository: clerk/cli-new

Length of output: 89


🏁 Script executed:

cat -n ./scripts/build.ts

Repository: clerk/cli-new

Length of output: 2701


🏁 Script executed:

cat -n ./packages/cli-core/package.json | head -40

Repository: clerk/cli-new

Length of output: 1105


🏁 Script executed:

cat -n ./scripts/releaser/targets.ts

Repository: clerk/cli-new

Length of output: 2532


🏁 Script executed:

grep -r "@napi-rs/keyring" ./packages/cli-core/src/ --include="*.ts" -n

Repository: clerk/cli-new

Length of output: 799


🌐 Web query:

@napi-rs/keyring package structure platform-specific

💡 Result:

@napi-rs/keyring follows the common napi-rs “meta package + per-platform binary packages” layout:

  • Main (cross-platform) package:@napi-rs/keyring

    • Ships JS/TS files (index.js, index.d.ts, etc.).
    • Declares platform-specific packages under optionalDependencies, including (example list from v1.1.3):
      @napi-rs/keyring-darwin-x64, @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-win32-x64-msvc, @napi-rs/keyring-win32-ia32-msvc, @napi-rs/keyring-win32-arm64-msvc, @napi-rs/keyring-linux-x64-gnu, @napi-rs/keyring-linux-x64-musl, @napi-rs/keyring-linux-arm64-gnu, @napi-rs/keyring-linux-arm64-musl, @napi-rs/keyring-linux-arm-gnueabihf, @napi-rs/keyring-freebsd-x64. (app.unpkg.com)
  • Runtime loader (platform selection):index.js detects process.platform + process.arch, and on Linux additionally detects musl vs glibc (using process.report when available, otherwise inspecting ldd). It then loads either a local .node file (if present) or requires the matching platform package such as @napi-rs/keyring-linux-x64-gnu / @napi-rs/keyring-linux-x64-musl. (app.unpkg.com)

  • Per-platform packages: each @napi-rs/keyring-<platform> package is essentially “just the native binary for that target” (e.g. @napi-rs/keyring-linux-x64-gnu is the x86_64-unknown-linux-gnu binary). (socket.dev)

  • Install behavior: because those binaries are in optionalDependencies, package managers will typically only install the one that matches the current OS/CPU (and libc variant on Linux). (napi.rs)

Citations:


🌐 Web query:

Bun build --compile --target cross-compilation native modules

💡 Result:

To cross-compile a single-file executable with Bun, you use bun build --compile plus a Bun target triple:

bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp
# examples: bun-linux-arm64, bun-windows-x64, bun-darwin-arm64, etc.

Bun documents these --target values and shows cross-compiling from one OS/arch to another. [1]

Native modules (.node / N-API addons) with --compile

  • Bun can embed .node files into the produced executable, but it’s essentially embedding/packaging: you still need a .node binary built for the same OS/arch as the executable target. [2]
  • Make sure the addon is directly required, e.g.:
    constaddon=require("./addon.node");
    Bun warns that if the addon is loaded indirectly via tools like @mapbox/node-pre-gyp, it may not bundle correctly unless the .node is directly required. [2]

Practical cross-compilation workflow for native addons

  1. Build/obtain the addon for the target (e.g., Linux x64):
    • Prefer packages that publish prebuilt binaries for your target.
    • Or build the addon in a target environment (common: Linux Docker for bun-linux-x64, Windows runner for bun-windows-x64, etc.).
  2. Ensure your dependency install selects the right platform artifacts when needed:
    bun install --os=linux --cpu=x64
    This tells Bun to select packages for that platform (useful for cross-platform builds). [3]
  3. Run the compile step with the same target:
    bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp

Key point:--target cross-compiles the Bun executable, but your native addon must already be built for that target (and ideally be directly required so Bun can embed it). [1][2]

Sources:
[1] Bun docs: single-file executable + cross-compilation targets (--compile, --target=...) (bun.sh)
[2] Bun docs: “Embed N-API Addons” / embedding .node files + node-pre-gyp caveat (bun.sh)
[3] Bun docs: bun install --cpu/--os for platform-specific package selection (bun.sh)

Citations:


🏁 Script executed:

grep -r "credential-store\|from.*lib" ./packages/cli-core/src/cli.ts -n | head -20

Repository: clerk/cli-new

Length of output: 103


🏁 Script executed:

grep -r "credential-store" ./packages/cli-core/src/ --include="*.ts"| grep -v "\.test\.ts" -c

Repository: clerk/cli-new

Length of output: 58


🏁 Script executed:

rg "credential-store" ./packages/cli-core/src/ --type ts --files-with-matches | grep -v test

Repository: clerk/cli-new

Length of output: 335


🏁 Script executed:

grep -A 2 "credential-store" ./packages/cli-core/src/commands/auth/login.ts | head -10

Repository: clerk/cli-new

Length of output: 255


🏁 Script executed:

head -30 ./packages/cli-core/src/commands/auth/login.ts | grep -A 2 "credential-store"

Repository: clerk/cli-new

Length of output: 255


Build each target from a matching install and runner.

The workflow runs bun install --frozen-lockfile once on a Linux host (line 26), then cross-compiles all 8 targets (darwin-arm64, darwin-x64, linux-, win32-) from that single install (line 31). @napi-rs/keyring declares platform-specific binaries as optionalDependencies (e.g., @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-linux-x64-gnu); during bun install on Linux, only Linux variants are installed. When bun build --compile --target=bun-darwin-arm64 cross-compiles, the binary lacks the correct native keyring addon, and at runtime credential-store falls back to plaintext file storage instead. The current file/--version checks pass because they verify only binary format, not functionality. Build each target on its matching runner before publishing to ensure native addons are available for embedding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/build-binaries.yml around lines 26 - 31, The workflow
currently runs a single bun install on Linux and then cross-compiles all targets
with bun run scripts/build.ts (using CLI_VERSION), causing platform-specific
optionalDependencies like `@napi-rs/keyring-`* to be missing for non-Linux
targets; change the job to install and build each target on a matching runner
(create a matrix of targets mapped to appropriate runner OS/arch), run bun
install --frozen-lockfile on that runner before invoking bun run
scripts/build.ts --version="${CLI_VERSION}"/--target=<target> so the
platform-specific native addons are present and embedded during build; ensure
the build step references the same CLI_VERSION env and that each matrix entry
runs its own install+build pair rather than reusing a single install.


- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-arm64
path: dist/artifacts/darwin-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-x64
path: dist/artifacts/darwin-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64
path: dist/artifacts/linux-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64-musl
path: dist/artifacts/linux-arm64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64
path: dist/artifacts/linux-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64-musl
path: dist/artifacts/linux-x64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-arm64
path: dist/artifacts/win32-arm64/clerk.exe
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-x64
path: dist/artifacts/win32-x64/clerk.exe
retention-days: 1
13 changes: 4 additions & 9 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,25 +7,20 @@ on:
jobs:
lint:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun run format:check
- run: bun run lint
- run: bun run build

test:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- name: Run tests
run: |
failed=0
for f in $(find src -name '*.test.ts' | sort); do
if ! bun test "$f"; then
failed=1
fi
done
exit $failed
- run: bun run --filter @clerk/cli-core test
188 changes: 188 additions & 0 deletions .github/workflows/release.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
name: Release

on:
push:
branches: [main]

concurrency:
group: release
cancel-in-progress: false

permissions:
contents: read

Comment thread
wyattjoh marked this conversation as resolved.
jobs:
versioning:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.check.outputs.release_created }}
version: ${{ steps.check.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- id: changesets
uses: changesets/action@v1
with:
version: bun run version-packages
commit: "ci(repo): version packages"
title: "ci(repo): Version Packages"
env:
GITHUB_TOKEN: ${{ github.token }}

- name: Check if release needed
id: check
run: bun run scripts/check-release.ts

# ─── Stable release ────────────────────────────────────────────────

build:
needs: versioning
if: ${{ needs.versioning.outputs.release_created == 'true' }}
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.versioning.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk

smoke-test:
needs: [versioning, build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.versioning.outputs.version }}
artifact-prefix: clerk
preset: stable

publish-npm:
needs: [versioning, build, smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Publish packages
run: bun run release
env:
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
GH_TOKEN: ${{ github.token }}

upload-github-assets:
needs: [versioning, build, smoke-test, publish-npm]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Upload binaries to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
tag="v${{ needs.versioning.outputs.version }}"
for dir in dist/artifacts/clerk-*/; do
target=${dir#dist/artifacts/clerk-} && target=${target%/}
ext=""; [[ "$target" == win32-* ]] && ext=".exe"
gh release upload "$tag" "${dir}clerk${ext}#clerk-${target}${ext}"
done

# ─── Canary release ────────────────────────────────────────────────

canary-version:
needs: versioning
if: ${{ needs.versioning.outputs.release_created != 'true' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Version packages for canary
id: version
run: |
bun run version-packages:canary
version=$(jq -r '.version' packages/cli/package.json)
echo "version=${version}" >> "$GITHUB_OUTPUT"

canary-build:
needs: canary-version
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.canary-version.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk-canary

canary-smoke-test:
needs: [canary-version, canary-build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.canary-version.outputs.version }}
artifact-prefix: clerk-canary
preset: canary

canary-publish:
needs: [canary-version, canary-build, canary-smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-canary-*
path: dist/artifacts

- name: Rename artifact directories
run: |
cd dist/artifacts
for dir in clerk-canary-*/; do
target=${dir#clerk-canary-} && target=${target%/}
mv "$dir" "clerk-${target}"
done

- name: Publish canary packages
run: bun run release:canary --version "$CANARY_VERSION"
env:
CANARY_VERSION: ${{ needs.canary-version.outputs.version }}
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9671e4f
feat: add release infrastructure with cross-compiled binaries
wyattjoh Mar 13, 2026
458ad76
feat: add canary and snapshot release channels
wyattjoh Mar 13, 2026
f3560b2
fix: add .ts extension to config imports in test files
wyattjoh Mar 13, 2026
d4ebb77
fix: normalize all relative imports to use .ts extensions
wyattjoh Mar 13, 2026
5f2b957
fix: isolate test files to work around Bun mock.module leak
wyattjoh Mar 13, 2026
e9268b3
fix: harden workflows and clean up code issues
wyattjoh Mar 13, 2026
dc801a0
fix: align release flow with JS monorepo conventions
wyattjoh Mar 13, 2026
d6da7fe
docs: update release docs and add musl detection references
wyattjoh Mar 13, 2026
88ddd53
fix: address code review findings
wyattjoh Mar 13, 2026
31b83b7
fix: add snapshot smoke test, guard signal forwarding, fix docs
wyattjoh Mar 13, 2026
bffd158
feat: migrate npm publishing to OIDC trusted publishing
wyattjoh Mar 13, 2026
a14084c
refactor: simplify shim by using spawnSync instead of async spawn
wyattjoh Mar 13, 2026
a187670
chore: update license year to 2026
wyattjoh Mar 13, 2026
45abd2b
refactor: use static import for dev version and add npm provenance
wyattjoh Mar 13, 2026
2967063
fix: address code review findings from release infrastructure
wyattjoh Mar 13, 2026
7ffc7f3
fix: address code review findings from release and packaging
wyattjoh Mar 13, 2026
1b238a9
refactor: extract smoke tests into reusable workflow and simplify tsc…
wyattjoh Mar 13, 2026
3f52d84
fix: move typescript from peerDependencies to devDependencies
wyattjoh Mar 13, 2026
975ab13
fix: add job timeouts, set tag format, and reduce artifact retention
wyattjoh Mar 13, 2026
1a82814
docs: update CLAUDE.md paths for monorepo structure
wyattjoh Mar 13, 2026
9b0508d
fix: harden release scripts and update CLAUDE.md documentation
wyattjoh Mar 13, 2026
27df13e
fix: update bun.lock and fix errorSpy reference after rebase
wyattjoh Mar 16, 2026
8749cac
refactor: rename npm wrapper package from @clerk/cli to clerk
wyattjoh Mar 17, 2026
d799450
fix: remove unused errorSpy variable in link test
wyattjoh Mar 19, 2026
0599b1e
ci: narrow workflow permissions and fix multiline output
wyattjoh Mar 19, 2026
fa6d7f9
fix(ci): remove unintended leading spaces in snapshot failure comment
wyattjoh Mar 19, 2026
cebde2c
refactor: add bunTarget and ext fields to target definitions
wyattjoh Mar 19, 2026
109556f
feat: add check-release script for stable release detection
wyattjoh Mar 19, 2026
f482ff0
feat: add changesets configuration
wyattjoh Mar 19, 2026
4199333
feat: add cross-compile build script
wyattjoh Mar 19, 2026
507f311
feat: add canary versioning script
wyattjoh Mar 19, 2026
e89d2b0
feat: add snapshot versioning script
wyattjoh Mar 19, 2026
7d4e38f
feat: add git tag and GitHub Release creation to releaser
wyattjoh Mar 19, 2026
6490b26
feat: add release, version, and build scripts to root package.json
wyattjoh Mar 19, 2026
58efb32
refactor: consolidate build-binaries into single sequential job
wyattjoh Mar 19, 2026
edc36cb
feat: replace release-please with changesets/action in release workflow
wyattjoh Mar 19, 2026
55ee209
refactor: use changeset snapshot script and fix failure comment inden…
wyattjoh Mar 19, 2026
2d76c86
chore: remove release-please configuration files
wyattjoh Mar 19, 2026
f30646c
docs: update releasing.md for changesets migration
wyattjoh Mar 19, 2026
077275b
fix(ci): replace heredocs with echo+body-file in snapshot.yml comments
wyattjoh Mar 19, 2026
2d01b44
fix: address review findings — error handling and cleanup
wyattjoh Mar 19, 2026
66c394c
refactor: consolidate canary.ts into snapshot.ts
wyattjoh Mar 19, 2026
fd316a6
refactor: extract shared isPublished helper, inline readVersion, remo…
wyattjoh Mar 19, 2026
0847f26
refactor: standardize arg parsing with node:util parseArgs, co-locate…
wyattjoh Mar 19, 2026
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
11 changes: 11 additions & 0 deletions .changeset/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# Changesets

This directory is used by [Changesets](https://github.com/changesets/changesets) to manage versioning and changelogs.

To add a changeset, run:

```sh
bunx changeset
```

This will prompt you to select the packages that changed and the type of change (patch, minor, major). A markdown file will be created in this directory describing the change — commit it with your PR.
14 changes: 14 additions & 0 deletions .changeset/config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "public",
"baseBranch": "origin/main",
"updateInternalDependencies": "patch",
"ignore": ["@clerk/cli-core"],
"snapshot": {
"useCalculatedVersion": true,
"prereleaseTemplate": "{tag}.v{datetime}"
}
}
79 changes: 79 additions & 0 deletions .github/workflows/build-binaries.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
name: Build Binaries

on:
workflow_call:
inputs:
version:
required: true
type: string
ref:
required: true
type: string
artifact-prefix:
required: true
type: string
description: "Prefix for artifact names (e.g. 'clerk', 'clerk-canary', 'clerk-snapshot')"

jobs:

@brkalowbrkalowMar 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a practical benefit to parallelizing these? For a larger project I could see the argument, but these builds should be really quick.

I expect the overhead of the GH actions setup here to build / upload / download the artifacts might end up taking longer than doing everything inline.

build:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Build all targets
env:
CLI_VERSION: ${{ inputs.version }}
run: bun run scripts/build.ts --version="${CLI_VERSION}"
Comment on lines +26 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the build script
find . -name "build.ts" -o -name "build.js"| head -20

Repository: clerk/cli-new

Length of output: 75


🏁 Script executed:

# Check the workflow file structure to understand how targets are built
cat -n .github/workflows/build-binaries.yml | head -50

Repository: clerk/cli-new

Length of output: 1722


🏁 Script executed:

# Find and examine the cli-core package.json
find . -path "*/packages/cli-core/package.json"

Repository: clerk/cli-new

Length of output: 89


🏁 Script executed:

cat -n ./scripts/build.ts

Repository: clerk/cli-new

Length of output: 2701


🏁 Script executed:

cat -n ./packages/cli-core/package.json | head -40

Repository: clerk/cli-new

Length of output: 1105


🏁 Script executed:

cat -n ./scripts/releaser/targets.ts

Repository: clerk/cli-new

Length of output: 2532


🏁 Script executed:

grep -r "@napi-rs/keyring" ./packages/cli-core/src/ --include="*.ts" -n

Repository: clerk/cli-new

Length of output: 799


🌐 Web query:

@napi-rs/keyring package structure platform-specific

💡 Result:

@napi-rs/keyring follows the common napi-rs “meta package + per-platform binary packages” layout:

  • Main (cross-platform) package:@napi-rs/keyring

    • Ships JS/TS files (index.js, index.d.ts, etc.).
    • Declares platform-specific packages under optionalDependencies, including (example list from v1.1.3):
      @napi-rs/keyring-darwin-x64, @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-win32-x64-msvc, @napi-rs/keyring-win32-ia32-msvc, @napi-rs/keyring-win32-arm64-msvc, @napi-rs/keyring-linux-x64-gnu, @napi-rs/keyring-linux-x64-musl, @napi-rs/keyring-linux-arm64-gnu, @napi-rs/keyring-linux-arm64-musl, @napi-rs/keyring-linux-arm-gnueabihf, @napi-rs/keyring-freebsd-x64. (app.unpkg.com)
  • Runtime loader (platform selection):index.js detects process.platform + process.arch, and on Linux additionally detects musl vs glibc (using process.report when available, otherwise inspecting ldd). It then loads either a local .node file (if present) or requires the matching platform package such as @napi-rs/keyring-linux-x64-gnu / @napi-rs/keyring-linux-x64-musl. (app.unpkg.com)

  • Per-platform packages: each @napi-rs/keyring-<platform> package is essentially “just the native binary for that target” (e.g. @napi-rs/keyring-linux-x64-gnu is the x86_64-unknown-linux-gnu binary). (socket.dev)

  • Install behavior: because those binaries are in optionalDependencies, package managers will typically only install the one that matches the current OS/CPU (and libc variant on Linux). (napi.rs)

Citations:


🌐 Web query:

Bun build --compile --target cross-compilation native modules

💡 Result:

To cross-compile a single-file executable with Bun, you use bun build --compile plus a Bun target triple:

bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp
# examples: bun-linux-arm64, bun-windows-x64, bun-darwin-arm64, etc.

Bun documents these --target values and shows cross-compiling from one OS/arch to another. [1]

Native modules (.node / N-API addons) with --compile

  • Bun can embed .node files into the produced executable, but it’s essentially embedding/packaging: you still need a .node binary built for the same OS/arch as the executable target. [2]
  • Make sure the addon is directly required, e.g.:
    constaddon=require("./addon.node");
    Bun warns that if the addon is loaded indirectly via tools like @mapbox/node-pre-gyp, it may not bundle correctly unless the .node is directly required. [2]

Practical cross-compilation workflow for native addons

  1. Build/obtain the addon for the target (e.g., Linux x64):
    • Prefer packages that publish prebuilt binaries for your target.
    • Or build the addon in a target environment (common: Linux Docker for bun-linux-x64, Windows runner for bun-windows-x64, etc.).
  2. Ensure your dependency install selects the right platform artifacts when needed:
    bun install --os=linux --cpu=x64
    This tells Bun to select packages for that platform (useful for cross-platform builds). [3]
  3. Run the compile step with the same target:
    bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp

Key point:--target cross-compiles the Bun executable, but your native addon must already be built for that target (and ideally be directly required so Bun can embed it). [1][2]

Sources:
[1] Bun docs: single-file executable + cross-compilation targets (--compile, --target=...) (bun.sh)
[2] Bun docs: “Embed N-API Addons” / embedding .node files + node-pre-gyp caveat (bun.sh)
[3] Bun docs: bun install --cpu/--os for platform-specific package selection (bun.sh)

Citations:


🏁 Script executed:

grep -r "credential-store\|from.*lib" ./packages/cli-core/src/cli.ts -n | head -20

Repository: clerk/cli-new

Length of output: 103


🏁 Script executed:

grep -r "credential-store" ./packages/cli-core/src/ --include="*.ts"| grep -v "\.test\.ts" -c

Repository: clerk/cli-new

Length of output: 58


🏁 Script executed:

rg "credential-store" ./packages/cli-core/src/ --type ts --files-with-matches | grep -v test

Repository: clerk/cli-new

Length of output: 335


🏁 Script executed:

grep -A 2 "credential-store" ./packages/cli-core/src/commands/auth/login.ts | head -10

Repository: clerk/cli-new

Length of output: 255


🏁 Script executed:

head -30 ./packages/cli-core/src/commands/auth/login.ts | grep -A 2 "credential-store"

Repository: clerk/cli-new

Length of output: 255


Build each target from a matching install and runner.

The workflow runs bun install --frozen-lockfile once on a Linux host (line 26), then cross-compiles all 8 targets (darwin-arm64, darwin-x64, linux-, win32-) from that single install (line 31). @napi-rs/keyring declares platform-specific binaries as optionalDependencies (e.g., @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-linux-x64-gnu); during bun install on Linux, only Linux variants are installed. When bun build --compile --target=bun-darwin-arm64 cross-compiles, the binary lacks the correct native keyring addon, and at runtime credential-store falls back to plaintext file storage instead. The current file/--version checks pass because they verify only binary format, not functionality. Build each target on its matching runner before publishing to ensure native addons are available for embedding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/build-binaries.yml around lines 26 - 31, The workflow
currently runs a single bun install on Linux and then cross-compiles all targets
with bun run scripts/build.ts (using CLI_VERSION), causing platform-specific
optionalDependencies like `@napi-rs/keyring-`* to be missing for non-Linux
targets; change the job to install and build each target on a matching runner
(create a matrix of targets mapped to appropriate runner OS/arch), run bun
install --frozen-lockfile on that runner before invoking bun run
scripts/build.ts --version="${CLI_VERSION}"/--target=<target> so the
platform-specific native addons are present and embedded during build; ensure
the build step references the same CLI_VERSION env and that each matrix entry
runs its own install+build pair rather than reusing a single install.


- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-arm64
path: dist/artifacts/darwin-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-x64
path: dist/artifacts/darwin-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64
path: dist/artifacts/linux-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64-musl
path: dist/artifacts/linux-arm64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64
path: dist/artifacts/linux-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64-musl
path: dist/artifacts/linux-x64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-arm64
path: dist/artifacts/win32-arm64/clerk.exe
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-x64
path: dist/artifacts/win32-x64/clerk.exe
retention-days: 1
13 changes: 4 additions & 9 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,25 +7,20 @@ on:
jobs:
lint:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun run format:check
- run: bun run lint
- run: bun run build

test:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- name: Run tests
run: |
failed=0
for f in $(find src -name '*.test.ts' | sort); do
if ! bun test "$f"; then
failed=1
fi
done
exit $failed
- run: bun run --filter @clerk/cli-core test
188 changes: 188 additions & 0 deletions .github/workflows/release.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
name: Release

on:
push:
branches: [main]

concurrency:
group: release
cancel-in-progress: false

permissions:
contents: read

Comment thread
wyattjoh marked this conversation as resolved.
jobs:
versioning:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.check.outputs.release_created }}
version: ${{ steps.check.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- id: changesets
uses: changesets/action@v1
with:
version: bun run version-packages
commit: "ci(repo): version packages"
title: "ci(repo): Version Packages"
env:
GITHUB_TOKEN: ${{ github.token }}

- name: Check if release needed
id: check
run: bun run scripts/check-release.ts

# ─── Stable release ────────────────────────────────────────────────

build:
needs: versioning
if: ${{ needs.versioning.outputs.release_created == 'true' }}
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.versioning.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk

smoke-test:
needs: [versioning, build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.versioning.outputs.version }}
artifact-prefix: clerk
preset: stable

publish-npm:
needs: [versioning, build, smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Publish packages
run: bun run release
env:
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
GH_TOKEN: ${{ github.token }}

upload-github-assets:
needs: [versioning, build, smoke-test, publish-npm]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Upload binaries to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
tag="v${{ needs.versioning.outputs.version }}"
for dir in dist/artifacts/clerk-*/; do
target=${dir#dist/artifacts/clerk-} && target=${target%/}
ext=""; [[ "$target" == win32-* ]] && ext=".exe"
gh release upload "$tag" "${dir}clerk${ext}#clerk-${target}${ext}"
done

# ─── Canary release ────────────────────────────────────────────────

canary-version:
needs: versioning
if: ${{ needs.versioning.outputs.release_created != 'true' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Version packages for canary
id: version
run: |
bun run version-packages:canary
version=$(jq -r '.version' packages/cli/package.json)
echo "version=${version}" >> "$GITHUB_OUTPUT"

canary-build:
needs: canary-version
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.canary-version.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk-canary

canary-smoke-test:
needs: [canary-version, canary-build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.canary-version.outputs.version }}
artifact-prefix: clerk-canary
preset: canary

canary-publish:
needs: [canary-version, canary-build, canary-smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-canary-*
path: dist/artifacts

- name: Rename artifact directories
run: |
cd dist/artifacts
for dir in clerk-canary-*/; do
target=${dir#clerk-canary-} && target=${target%/}
mv "$dir" "clerk-${target}"
done

- name: Publish canary packages
run: bun run release:canary --version "$CANARY_VERSION"
env:
CANARY_VERSION: ${{ needs.canary-version.outputs.version }}
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9671e4f
feat: add release infrastructure with cross-compiled binaries
wyattjoh Mar 13, 2026
458ad76
feat: add canary and snapshot release channels
wyattjoh Mar 13, 2026
f3560b2
fix: add .ts extension to config imports in test files
wyattjoh Mar 13, 2026
d4ebb77
fix: normalize all relative imports to use .ts extensions
wyattjoh Mar 13, 2026
5f2b957
fix: isolate test files to work around Bun mock.module leak
wyattjoh Mar 13, 2026
e9268b3
fix: harden workflows and clean up code issues
wyattjoh Mar 13, 2026
dc801a0
fix: align release flow with JS monorepo conventions
wyattjoh Mar 13, 2026
d6da7fe
docs: update release docs and add musl detection references
wyattjoh Mar 13, 2026
88ddd53
fix: address code review findings
wyattjoh Mar 13, 2026
31b83b7
fix: add snapshot smoke test, guard signal forwarding, fix docs
wyattjoh Mar 13, 2026
bffd158
feat: migrate npm publishing to OIDC trusted publishing
wyattjoh Mar 13, 2026
a14084c
refactor: simplify shim by using spawnSync instead of async spawn
wyattjoh Mar 13, 2026
a187670
chore: update license year to 2026
wyattjoh Mar 13, 2026
45abd2b
refactor: use static import for dev version and add npm provenance
wyattjoh Mar 13, 2026
2967063
fix: address code review findings from release infrastructure
wyattjoh Mar 13, 2026
7ffc7f3
fix: address code review findings from release and packaging
wyattjoh Mar 13, 2026
1b238a9
refactor: extract smoke tests into reusable workflow and simplify tsc…
wyattjoh Mar 13, 2026
3f52d84
fix: move typescript from peerDependencies to devDependencies
wyattjoh Mar 13, 2026
975ab13
fix: add job timeouts, set tag format, and reduce artifact retention
wyattjoh Mar 13, 2026
1a82814
docs: update CLAUDE.md paths for monorepo structure
wyattjoh Mar 13, 2026
9b0508d
fix: harden release scripts and update CLAUDE.md documentation
wyattjoh Mar 13, 2026
27df13e
fix: update bun.lock and fix errorSpy reference after rebase
wyattjoh Mar 16, 2026
8749cac
refactor: rename npm wrapper package from @clerk/cli to clerk
wyattjoh Mar 17, 2026
d799450
fix: remove unused errorSpy variable in link test
wyattjoh Mar 19, 2026
0599b1e
ci: narrow workflow permissions and fix multiline output
wyattjoh Mar 19, 2026
fa6d7f9
fix(ci): remove unintended leading spaces in snapshot failure comment
wyattjoh Mar 19, 2026
cebde2c
refactor: add bunTarget and ext fields to target definitions
wyattjoh Mar 19, 2026
109556f
feat: add check-release script for stable release detection
wyattjoh Mar 19, 2026
f482ff0
feat: add changesets configuration
wyattjoh Mar 19, 2026
4199333
feat: add cross-compile build script
wyattjoh Mar 19, 2026
507f311
feat: add canary versioning script
wyattjoh Mar 19, 2026
e89d2b0
feat: add snapshot versioning script
wyattjoh Mar 19, 2026
7d4e38f
feat: add git tag and GitHub Release creation to releaser
wyattjoh Mar 19, 2026
6490b26
feat: add release, version, and build scripts to root package.json
wyattjoh Mar 19, 2026
58efb32
refactor: consolidate build-binaries into single sequential job
wyattjoh Mar 19, 2026
edc36cb
feat: replace release-please with changesets/action in release workflow
wyattjoh Mar 19, 2026
55ee209
refactor: use changeset snapshot script and fix failure comment inden…
wyattjoh Mar 19, 2026
2d76c86
chore: remove release-please configuration files
wyattjoh Mar 19, 2026
f30646c
docs: update releasing.md for changesets migration
wyattjoh Mar 19, 2026
077275b
fix(ci): replace heredocs with echo+body-file in snapshot.yml comments
wyattjoh Mar 19, 2026
2d01b44
fix: address review findings — error handling and cleanup
wyattjoh Mar 19, 2026
66c394c
refactor: consolidate canary.ts into snapshot.ts
wyattjoh Mar 19, 2026
fd316a6
refactor: extract shared isPublished helper, inline readVersion, remo…
wyattjoh Mar 19, 2026
0847f26
refactor: standardize arg parsing with node:util parseArgs, co-locate…
wyattjoh Mar 19, 2026
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
11 changes: 11 additions & 0 deletions .changeset/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# Changesets

This directory is used by [Changesets](https://github.com/changesets/changesets) to manage versioning and changelogs.

To add a changeset, run:

```sh
bunx changeset
```

This will prompt you to select the packages that changed and the type of change (patch, minor, major). A markdown file will be created in this directory describing the change — commit it with your PR.
14 changes: 14 additions & 0 deletions .changeset/config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "public",
"baseBranch": "origin/main",
"updateInternalDependencies": "patch",
"ignore": ["@clerk/cli-core"],
"snapshot": {
"useCalculatedVersion": true,
"prereleaseTemplate": "{tag}.v{datetime}"
}
}
79 changes: 79 additions & 0 deletions .github/workflows/build-binaries.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
name: Build Binaries

on:
workflow_call:
inputs:
version:
required: true
type: string
ref:
required: true
type: string
artifact-prefix:
required: true
type: string
description: "Prefix for artifact names (e.g. 'clerk', 'clerk-canary', 'clerk-snapshot')"

jobs:

@brkalowbrkalowMar 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a practical benefit to parallelizing these? For a larger project I could see the argument, but these builds should be really quick.

I expect the overhead of the GH actions setup here to build / upload / download the artifacts might end up taking longer than doing everything inline.

build:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Build all targets
env:
CLI_VERSION: ${{ inputs.version }}
run: bun run scripts/build.ts --version="${CLI_VERSION}"
Comment on lines +26 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the build script
find . -name "build.ts" -o -name "build.js"| head -20

Repository: clerk/cli-new

Length of output: 75


🏁 Script executed:

# Check the workflow file structure to understand how targets are built
cat -n .github/workflows/build-binaries.yml | head -50

Repository: clerk/cli-new

Length of output: 1722


🏁 Script executed:

# Find and examine the cli-core package.json
find . -path "*/packages/cli-core/package.json"

Repository: clerk/cli-new

Length of output: 89


🏁 Script executed:

cat -n ./scripts/build.ts

Repository: clerk/cli-new

Length of output: 2701


🏁 Script executed:

cat -n ./packages/cli-core/package.json | head -40

Repository: clerk/cli-new

Length of output: 1105


🏁 Script executed:

cat -n ./scripts/releaser/targets.ts

Repository: clerk/cli-new

Length of output: 2532


🏁 Script executed:

grep -r "@napi-rs/keyring" ./packages/cli-core/src/ --include="*.ts" -n

Repository: clerk/cli-new

Length of output: 799


🌐 Web query:

@napi-rs/keyring package structure platform-specific

💡 Result:

@napi-rs/keyring follows the common napi-rs “meta package + per-platform binary packages” layout:

  • Main (cross-platform) package:@napi-rs/keyring

    • Ships JS/TS files (index.js, index.d.ts, etc.).
    • Declares platform-specific packages under optionalDependencies, including (example list from v1.1.3):
      @napi-rs/keyring-darwin-x64, @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-win32-x64-msvc, @napi-rs/keyring-win32-ia32-msvc, @napi-rs/keyring-win32-arm64-msvc, @napi-rs/keyring-linux-x64-gnu, @napi-rs/keyring-linux-x64-musl, @napi-rs/keyring-linux-arm64-gnu, @napi-rs/keyring-linux-arm64-musl, @napi-rs/keyring-linux-arm-gnueabihf, @napi-rs/keyring-freebsd-x64. (app.unpkg.com)
  • Runtime loader (platform selection):index.js detects process.platform + process.arch, and on Linux additionally detects musl vs glibc (using process.report when available, otherwise inspecting ldd). It then loads either a local .node file (if present) or requires the matching platform package such as @napi-rs/keyring-linux-x64-gnu / @napi-rs/keyring-linux-x64-musl. (app.unpkg.com)

  • Per-platform packages: each @napi-rs/keyring-<platform> package is essentially “just the native binary for that target” (e.g. @napi-rs/keyring-linux-x64-gnu is the x86_64-unknown-linux-gnu binary). (socket.dev)

  • Install behavior: because those binaries are in optionalDependencies, package managers will typically only install the one that matches the current OS/CPU (and libc variant on Linux). (napi.rs)

Citations:


🌐 Web query:

Bun build --compile --target cross-compilation native modules

💡 Result:

To cross-compile a single-file executable with Bun, you use bun build --compile plus a Bun target triple:

bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp
# examples: bun-linux-arm64, bun-windows-x64, bun-darwin-arm64, etc.

Bun documents these --target values and shows cross-compiling from one OS/arch to another. [1]

Native modules (.node / N-API addons) with --compile

  • Bun can embed .node files into the produced executable, but it’s essentially embedding/packaging: you still need a .node binary built for the same OS/arch as the executable target. [2]
  • Make sure the addon is directly required, e.g.:
    constaddon=require("./addon.node");
    Bun warns that if the addon is loaded indirectly via tools like @mapbox/node-pre-gyp, it may not bundle correctly unless the .node is directly required. [2]

Practical cross-compilation workflow for native addons

  1. Build/obtain the addon for the target (e.g., Linux x64):
    • Prefer packages that publish prebuilt binaries for your target.
    • Or build the addon in a target environment (common: Linux Docker for bun-linux-x64, Windows runner for bun-windows-x64, etc.).
  2. Ensure your dependency install selects the right platform artifacts when needed:
    bun install --os=linux --cpu=x64
    This tells Bun to select packages for that platform (useful for cross-platform builds). [3]
  3. Run the compile step with the same target:
    bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp

Key point:--target cross-compiles the Bun executable, but your native addon must already be built for that target (and ideally be directly required so Bun can embed it). [1][2]

Sources:
[1] Bun docs: single-file executable + cross-compilation targets (--compile, --target=...) (bun.sh)
[2] Bun docs: “Embed N-API Addons” / embedding .node files + node-pre-gyp caveat (bun.sh)
[3] Bun docs: bun install --cpu/--os for platform-specific package selection (bun.sh)

Citations:


🏁 Script executed:

grep -r "credential-store\|from.*lib" ./packages/cli-core/src/cli.ts -n | head -20

Repository: clerk/cli-new

Length of output: 103


🏁 Script executed:

grep -r "credential-store" ./packages/cli-core/src/ --include="*.ts"| grep -v "\.test\.ts" -c

Repository: clerk/cli-new

Length of output: 58


🏁 Script executed:

rg "credential-store" ./packages/cli-core/src/ --type ts --files-with-matches | grep -v test

Repository: clerk/cli-new

Length of output: 335


🏁 Script executed:

grep -A 2 "credential-store" ./packages/cli-core/src/commands/auth/login.ts | head -10

Repository: clerk/cli-new

Length of output: 255


🏁 Script executed:

head -30 ./packages/cli-core/src/commands/auth/login.ts | grep -A 2 "credential-store"

Repository: clerk/cli-new

Length of output: 255


Build each target from a matching install and runner.

The workflow runs bun install --frozen-lockfile once on a Linux host (line 26), then cross-compiles all 8 targets (darwin-arm64, darwin-x64, linux-, win32-) from that single install (line 31). @napi-rs/keyring declares platform-specific binaries as optionalDependencies (e.g., @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-linux-x64-gnu); during bun install on Linux, only Linux variants are installed. When bun build --compile --target=bun-darwin-arm64 cross-compiles, the binary lacks the correct native keyring addon, and at runtime credential-store falls back to plaintext file storage instead. The current file/--version checks pass because they verify only binary format, not functionality. Build each target on its matching runner before publishing to ensure native addons are available for embedding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/build-binaries.yml around lines 26 - 31, The workflow
currently runs a single bun install on Linux and then cross-compiles all targets
with bun run scripts/build.ts (using CLI_VERSION), causing platform-specific
optionalDependencies like `@napi-rs/keyring-`* to be missing for non-Linux
targets; change the job to install and build each target on a matching runner
(create a matrix of targets mapped to appropriate runner OS/arch), run bun
install --frozen-lockfile on that runner before invoking bun run
scripts/build.ts --version="${CLI_VERSION}"/--target=<target> so the
platform-specific native addons are present and embedded during build; ensure
the build step references the same CLI_VERSION env and that each matrix entry
runs its own install+build pair rather than reusing a single install.


- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-arm64
path: dist/artifacts/darwin-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-x64
path: dist/artifacts/darwin-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64
path: dist/artifacts/linux-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64-musl
path: dist/artifacts/linux-arm64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64
path: dist/artifacts/linux-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64-musl
path: dist/artifacts/linux-x64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-arm64
path: dist/artifacts/win32-arm64/clerk.exe
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-x64
path: dist/artifacts/win32-x64/clerk.exe
retention-days: 1
13 changes: 4 additions & 9 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,25 +7,20 @@ on:
jobs:
lint:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun run format:check
- run: bun run lint
- run: bun run build

test:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- name: Run tests
run: |
failed=0
for f in $(find src -name '*.test.ts' | sort); do
if ! bun test "$f"; then
failed=1
fi
done
exit $failed
- run: bun run --filter @clerk/cli-core test
188 changes: 188 additions & 0 deletions .github/workflows/release.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
name: Release

on:
push:
branches: [main]

concurrency:
group: release
cancel-in-progress: false

permissions:
contents: read

Comment thread
wyattjoh marked this conversation as resolved.
jobs:
versioning:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.check.outputs.release_created }}
version: ${{ steps.check.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- id: changesets
uses: changesets/action@v1
with:
version: bun run version-packages
commit: "ci(repo): version packages"
title: "ci(repo): Version Packages"
env:
GITHUB_TOKEN: ${{ github.token }}

- name: Check if release needed
id: check
run: bun run scripts/check-release.ts

# ─── Stable release ────────────────────────────────────────────────

build:
needs: versioning
if: ${{ needs.versioning.outputs.release_created == 'true' }}
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.versioning.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk

smoke-test:
needs: [versioning, build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.versioning.outputs.version }}
artifact-prefix: clerk
preset: stable

publish-npm:
needs: [versioning, build, smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Publish packages
run: bun run release
env:
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
GH_TOKEN: ${{ github.token }}

upload-github-assets:
needs: [versioning, build, smoke-test, publish-npm]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Upload binaries to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
tag="v${{ needs.versioning.outputs.version }}"
for dir in dist/artifacts/clerk-*/; do
target=${dir#dist/artifacts/clerk-} && target=${target%/}
ext=""; [[ "$target" == win32-* ]] && ext=".exe"
gh release upload "$tag" "${dir}clerk${ext}#clerk-${target}${ext}"
done

# ─── Canary release ────────────────────────────────────────────────

canary-version:
needs: versioning
if: ${{ needs.versioning.outputs.release_created != 'true' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Version packages for canary
id: version
run: |
bun run version-packages:canary
version=$(jq -r '.version' packages/cli/package.json)
echo "version=${version}" >> "$GITHUB_OUTPUT"

canary-build:
needs: canary-version
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.canary-version.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk-canary

canary-smoke-test:
needs: [canary-version, canary-build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.canary-version.outputs.version }}
artifact-prefix: clerk-canary
preset: canary

canary-publish:
needs: [canary-version, canary-build, canary-smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-canary-*
path: dist/artifacts

- name: Rename artifact directories
run: |
cd dist/artifacts
for dir in clerk-canary-*/; do
target=${dir#clerk-canary-} && target=${target%/}
mv "$dir" "clerk-${target}"
done

- name: Publish canary packages
run: bun run release:canary --version "$CANARY_VERSION"
env:
CANARY_VERSION: ${{ needs.canary-version.outputs.version }}
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9671e4f
feat: add release infrastructure with cross-compiled binaries
wyattjoh Mar 13, 2026
458ad76
feat: add canary and snapshot release channels
wyattjoh Mar 13, 2026
f3560b2
fix: add .ts extension to config imports in test files
wyattjoh Mar 13, 2026
d4ebb77
fix: normalize all relative imports to use .ts extensions
wyattjoh Mar 13, 2026
5f2b957
fix: isolate test files to work around Bun mock.module leak
wyattjoh Mar 13, 2026
e9268b3
fix: harden workflows and clean up code issues
wyattjoh Mar 13, 2026
dc801a0
fix: align release flow with JS monorepo conventions
wyattjoh Mar 13, 2026
d6da7fe
docs: update release docs and add musl detection references
wyattjoh Mar 13, 2026
88ddd53
fix: address code review findings
wyattjoh Mar 13, 2026
31b83b7
fix: add snapshot smoke test, guard signal forwarding, fix docs
wyattjoh Mar 13, 2026
bffd158
feat: migrate npm publishing to OIDC trusted publishing
wyattjoh Mar 13, 2026
a14084c
refactor: simplify shim by using spawnSync instead of async spawn
wyattjoh Mar 13, 2026
a187670
chore: update license year to 2026
wyattjoh Mar 13, 2026
45abd2b
refactor: use static import for dev version and add npm provenance
wyattjoh Mar 13, 2026
2967063
fix: address code review findings from release infrastructure
wyattjoh Mar 13, 2026
7ffc7f3
fix: address code review findings from release and packaging
wyattjoh Mar 13, 2026
1b238a9
refactor: extract smoke tests into reusable workflow and simplify tsc…
wyattjoh Mar 13, 2026
3f52d84
fix: move typescript from peerDependencies to devDependencies
wyattjoh Mar 13, 2026
975ab13
fix: add job timeouts, set tag format, and reduce artifact retention
wyattjoh Mar 13, 2026
1a82814
docs: update CLAUDE.md paths for monorepo structure
wyattjoh Mar 13, 2026
9b0508d
fix: harden release scripts and update CLAUDE.md documentation
wyattjoh Mar 13, 2026
27df13e
fix: update bun.lock and fix errorSpy reference after rebase
wyattjoh Mar 16, 2026
8749cac
refactor: rename npm wrapper package from @clerk/cli to clerk
wyattjoh Mar 17, 2026
d799450
fix: remove unused errorSpy variable in link test
wyattjoh Mar 19, 2026
0599b1e
ci: narrow workflow permissions and fix multiline output
wyattjoh Mar 19, 2026
fa6d7f9
fix(ci): remove unintended leading spaces in snapshot failure comment
wyattjoh Mar 19, 2026
cebde2c
refactor: add bunTarget and ext fields to target definitions
wyattjoh Mar 19, 2026
109556f
feat: add check-release script for stable release detection
wyattjoh Mar 19, 2026
f482ff0
feat: add changesets configuration
wyattjoh Mar 19, 2026
4199333
feat: add cross-compile build script
wyattjoh Mar 19, 2026
507f311
feat: add canary versioning script
wyattjoh Mar 19, 2026
e89d2b0
feat: add snapshot versioning script
wyattjoh Mar 19, 2026
7d4e38f
feat: add git tag and GitHub Release creation to releaser
wyattjoh Mar 19, 2026
6490b26
feat: add release, version, and build scripts to root package.json
wyattjoh Mar 19, 2026
58efb32
refactor: consolidate build-binaries into single sequential job
wyattjoh Mar 19, 2026
edc36cb
feat: replace release-please with changesets/action in release workflow
wyattjoh Mar 19, 2026
55ee209
refactor: use changeset snapshot script and fix failure comment inden…
wyattjoh Mar 19, 2026
2d76c86
chore: remove release-please configuration files
wyattjoh Mar 19, 2026
f30646c
docs: update releasing.md for changesets migration
wyattjoh Mar 19, 2026
077275b
fix(ci): replace heredocs with echo+body-file in snapshot.yml comments
wyattjoh Mar 19, 2026
2d01b44
fix: address review findings — error handling and cleanup
wyattjoh Mar 19, 2026
66c394c
refactor: consolidate canary.ts into snapshot.ts
wyattjoh Mar 19, 2026
fd316a6
refactor: extract shared isPublished helper, inline readVersion, remo…
wyattjoh Mar 19, 2026
0847f26
refactor: standardize arg parsing with node:util parseArgs, co-locate…
wyattjoh Mar 19, 2026
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
11 changes: 11 additions & 0 deletions .changeset/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# Changesets

This directory is used by [Changesets](https://github.com/changesets/changesets) to manage versioning and changelogs.

To add a changeset, run:

```sh
bunx changeset
```

This will prompt you to select the packages that changed and the type of change (patch, minor, major). A markdown file will be created in this directory describing the change — commit it with your PR.
14 changes: 14 additions & 0 deletions .changeset/config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "public",
"baseBranch": "origin/main",
"updateInternalDependencies": "patch",
"ignore": ["@clerk/cli-core"],
"snapshot": {
"useCalculatedVersion": true,
"prereleaseTemplate": "{tag}.v{datetime}"
}
}
79 changes: 79 additions & 0 deletions .github/workflows/build-binaries.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
name: Build Binaries

on:
workflow_call:
inputs:
version:
required: true
type: string
ref:
required: true
type: string
artifact-prefix:
required: true
type: string
description: "Prefix for artifact names (e.g. 'clerk', 'clerk-canary', 'clerk-snapshot')"

jobs:

@brkalowbrkalowMar 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a practical benefit to parallelizing these? For a larger project I could see the argument, but these builds should be really quick.

I expect the overhead of the GH actions setup here to build / upload / download the artifacts might end up taking longer than doing everything inline.

build:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Build all targets
env:
CLI_VERSION: ${{ inputs.version }}
run: bun run scripts/build.ts --version="${CLI_VERSION}"
Comment on lines +26 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the build script
find . -name "build.ts" -o -name "build.js"| head -20

Repository: clerk/cli-new

Length of output: 75


🏁 Script executed:

# Check the workflow file structure to understand how targets are built
cat -n .github/workflows/build-binaries.yml | head -50

Repository: clerk/cli-new

Length of output: 1722


🏁 Script executed:

# Find and examine the cli-core package.json
find . -path "*/packages/cli-core/package.json"

Repository: clerk/cli-new

Length of output: 89


🏁 Script executed:

cat -n ./scripts/build.ts

Repository: clerk/cli-new

Length of output: 2701


🏁 Script executed:

cat -n ./packages/cli-core/package.json | head -40

Repository: clerk/cli-new

Length of output: 1105


🏁 Script executed:

cat -n ./scripts/releaser/targets.ts

Repository: clerk/cli-new

Length of output: 2532


🏁 Script executed:

grep -r "@napi-rs/keyring" ./packages/cli-core/src/ --include="*.ts" -n

Repository: clerk/cli-new

Length of output: 799


🌐 Web query:

@napi-rs/keyring package structure platform-specific

💡 Result:

@napi-rs/keyring follows the common napi-rs “meta package + per-platform binary packages” layout:

  • Main (cross-platform) package:@napi-rs/keyring

    • Ships JS/TS files (index.js, index.d.ts, etc.).
    • Declares platform-specific packages under optionalDependencies, including (example list from v1.1.3):
      @napi-rs/keyring-darwin-x64, @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-win32-x64-msvc, @napi-rs/keyring-win32-ia32-msvc, @napi-rs/keyring-win32-arm64-msvc, @napi-rs/keyring-linux-x64-gnu, @napi-rs/keyring-linux-x64-musl, @napi-rs/keyring-linux-arm64-gnu, @napi-rs/keyring-linux-arm64-musl, @napi-rs/keyring-linux-arm-gnueabihf, @napi-rs/keyring-freebsd-x64. (app.unpkg.com)
  • Runtime loader (platform selection):index.js detects process.platform + process.arch, and on Linux additionally detects musl vs glibc (using process.report when available, otherwise inspecting ldd). It then loads either a local .node file (if present) or requires the matching platform package such as @napi-rs/keyring-linux-x64-gnu / @napi-rs/keyring-linux-x64-musl. (app.unpkg.com)

  • Per-platform packages: each @napi-rs/keyring-<platform> package is essentially “just the native binary for that target” (e.g. @napi-rs/keyring-linux-x64-gnu is the x86_64-unknown-linux-gnu binary). (socket.dev)

  • Install behavior: because those binaries are in optionalDependencies, package managers will typically only install the one that matches the current OS/CPU (and libc variant on Linux). (napi.rs)

Citations:


🌐 Web query:

Bun build --compile --target cross-compilation native modules

💡 Result:

To cross-compile a single-file executable with Bun, you use bun build --compile plus a Bun target triple:

bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp
# examples: bun-linux-arm64, bun-windows-x64, bun-darwin-arm64, etc.

Bun documents these --target values and shows cross-compiling from one OS/arch to another. [1]

Native modules (.node / N-API addons) with --compile

  • Bun can embed .node files into the produced executable, but it’s essentially embedding/packaging: you still need a .node binary built for the same OS/arch as the executable target. [2]
  • Make sure the addon is directly required, e.g.:
    constaddon=require("./addon.node");
    Bun warns that if the addon is loaded indirectly via tools like @mapbox/node-pre-gyp, it may not bundle correctly unless the .node is directly required. [2]

Practical cross-compilation workflow for native addons

  1. Build/obtain the addon for the target (e.g., Linux x64):
    • Prefer packages that publish prebuilt binaries for your target.
    • Or build the addon in a target environment (common: Linux Docker for bun-linux-x64, Windows runner for bun-windows-x64, etc.).
  2. Ensure your dependency install selects the right platform artifacts when needed:
    bun install --os=linux --cpu=x64
    This tells Bun to select packages for that platform (useful for cross-platform builds). [3]
  3. Run the compile step with the same target:
    bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp

Key point:--target cross-compiles the Bun executable, but your native addon must already be built for that target (and ideally be directly required so Bun can embed it). [1][2]

Sources:
[1] Bun docs: single-file executable + cross-compilation targets (--compile, --target=...) (bun.sh)
[2] Bun docs: “Embed N-API Addons” / embedding .node files + node-pre-gyp caveat (bun.sh)
[3] Bun docs: bun install --cpu/--os for platform-specific package selection (bun.sh)

Citations:


🏁 Script executed:

grep -r "credential-store\|from.*lib" ./packages/cli-core/src/cli.ts -n | head -20

Repository: clerk/cli-new

Length of output: 103


🏁 Script executed:

grep -r "credential-store" ./packages/cli-core/src/ --include="*.ts"| grep -v "\.test\.ts" -c

Repository: clerk/cli-new

Length of output: 58


🏁 Script executed:

rg "credential-store" ./packages/cli-core/src/ --type ts --files-with-matches | grep -v test

Repository: clerk/cli-new

Length of output: 335


🏁 Script executed:

grep -A 2 "credential-store" ./packages/cli-core/src/commands/auth/login.ts | head -10

Repository: clerk/cli-new

Length of output: 255


🏁 Script executed:

head -30 ./packages/cli-core/src/commands/auth/login.ts | grep -A 2 "credential-store"

Repository: clerk/cli-new

Length of output: 255


Build each target from a matching install and runner.

The workflow runs bun install --frozen-lockfile once on a Linux host (line 26), then cross-compiles all 8 targets (darwin-arm64, darwin-x64, linux-, win32-) from that single install (line 31). @napi-rs/keyring declares platform-specific binaries as optionalDependencies (e.g., @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-linux-x64-gnu); during bun install on Linux, only Linux variants are installed. When bun build --compile --target=bun-darwin-arm64 cross-compiles, the binary lacks the correct native keyring addon, and at runtime credential-store falls back to plaintext file storage instead. The current file/--version checks pass because they verify only binary format, not functionality. Build each target on its matching runner before publishing to ensure native addons are available for embedding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/build-binaries.yml around lines 26 - 31, The workflow
currently runs a single bun install on Linux and then cross-compiles all targets
with bun run scripts/build.ts (using CLI_VERSION), causing platform-specific
optionalDependencies like `@napi-rs/keyring-`* to be missing for non-Linux
targets; change the job to install and build each target on a matching runner
(create a matrix of targets mapped to appropriate runner OS/arch), run bun
install --frozen-lockfile on that runner before invoking bun run
scripts/build.ts --version="${CLI_VERSION}"/--target=<target> so the
platform-specific native addons are present and embedded during build; ensure
the build step references the same CLI_VERSION env and that each matrix entry
runs its own install+build pair rather than reusing a single install.


- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-arm64
path: dist/artifacts/darwin-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-x64
path: dist/artifacts/darwin-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64
path: dist/artifacts/linux-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64-musl
path: dist/artifacts/linux-arm64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64
path: dist/artifacts/linux-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64-musl
path: dist/artifacts/linux-x64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-arm64
path: dist/artifacts/win32-arm64/clerk.exe
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-x64
path: dist/artifacts/win32-x64/clerk.exe
retention-days: 1
13 changes: 4 additions & 9 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,25 +7,20 @@ on:
jobs:
lint:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun run format:check
- run: bun run lint
- run: bun run build

test:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- name: Run tests
run: |
failed=0
for f in $(find src -name '*.test.ts' | sort); do
if ! bun test "$f"; then
failed=1
fi
done
exit $failed
- run: bun run --filter @clerk/cli-core test
188 changes: 188 additions & 0 deletions .github/workflows/release.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
name: Release

on:
push:
branches: [main]

concurrency:
group: release
cancel-in-progress: false

permissions:
contents: read

Comment thread
wyattjoh marked this conversation as resolved.
jobs:
versioning:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.check.outputs.release_created }}
version: ${{ steps.check.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- id: changesets
uses: changesets/action@v1
with:
version: bun run version-packages
commit: "ci(repo): version packages"
title: "ci(repo): Version Packages"
env:
GITHUB_TOKEN: ${{ github.token }}

- name: Check if release needed
id: check
run: bun run scripts/check-release.ts

# ─── Stable release ────────────────────────────────────────────────

build:
needs: versioning
if: ${{ needs.versioning.outputs.release_created == 'true' }}
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.versioning.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk

smoke-test:
needs: [versioning, build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.versioning.outputs.version }}
artifact-prefix: clerk
preset: stable

publish-npm:
needs: [versioning, build, smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Publish packages
run: bun run release
env:
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
GH_TOKEN: ${{ github.token }}

upload-github-assets:
needs: [versioning, build, smoke-test, publish-npm]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Upload binaries to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
tag="v${{ needs.versioning.outputs.version }}"
for dir in dist/artifacts/clerk-*/; do
target=${dir#dist/artifacts/clerk-} && target=${target%/}
ext=""; [[ "$target" == win32-* ]] && ext=".exe"
gh release upload "$tag" "${dir}clerk${ext}#clerk-${target}${ext}"
done

# ─── Canary release ────────────────────────────────────────────────

canary-version:
needs: versioning
if: ${{ needs.versioning.outputs.release_created != 'true' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Version packages for canary
id: version
run: |
bun run version-packages:canary
version=$(jq -r '.version' packages/cli/package.json)
echo "version=${version}" >> "$GITHUB_OUTPUT"

canary-build:
needs: canary-version
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.canary-version.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk-canary

canary-smoke-test:
needs: [canary-version, canary-build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.canary-version.outputs.version }}
artifact-prefix: clerk-canary
preset: canary

canary-publish:
needs: [canary-version, canary-build, canary-smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-canary-*
path: dist/artifacts

- name: Rename artifact directories
run: |
cd dist/artifacts
for dir in clerk-canary-*/; do
target=${dir#clerk-canary-} && target=${target%/}
mv "$dir" "clerk-${target}"
done

- name: Publish canary packages
run: bun run release:canary --version "$CANARY_VERSION"
env:
CANARY_VERSION: ${{ needs.canary-version.outputs.version }}
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9671e4f
feat: add release infrastructure with cross-compiled binaries
wyattjoh Mar 13, 2026
458ad76
feat: add canary and snapshot release channels
wyattjoh Mar 13, 2026
f3560b2
fix: add .ts extension to config imports in test files
wyattjoh Mar 13, 2026
d4ebb77
fix: normalize all relative imports to use .ts extensions
wyattjoh Mar 13, 2026
5f2b957
fix: isolate test files to work around Bun mock.module leak
wyattjoh Mar 13, 2026
e9268b3
fix: harden workflows and clean up code issues
wyattjoh Mar 13, 2026
dc801a0
fix: align release flow with JS monorepo conventions
wyattjoh Mar 13, 2026
d6da7fe
docs: update release docs and add musl detection references
wyattjoh Mar 13, 2026
88ddd53
fix: address code review findings
wyattjoh Mar 13, 2026
31b83b7
fix: add snapshot smoke test, guard signal forwarding, fix docs
wyattjoh Mar 13, 2026
bffd158
feat: migrate npm publishing to OIDC trusted publishing
wyattjoh Mar 13, 2026
a14084c
refactor: simplify shim by using spawnSync instead of async spawn
wyattjoh Mar 13, 2026
a187670
chore: update license year to 2026
wyattjoh Mar 13, 2026
45abd2b
refactor: use static import for dev version and add npm provenance
wyattjoh Mar 13, 2026
2967063
fix: address code review findings from release infrastructure
wyattjoh Mar 13, 2026
7ffc7f3
fix: address code review findings from release and packaging
wyattjoh Mar 13, 2026
1b238a9
refactor: extract smoke tests into reusable workflow and simplify tsc…
wyattjoh Mar 13, 2026
3f52d84
fix: move typescript from peerDependencies to devDependencies
wyattjoh Mar 13, 2026
975ab13
fix: add job timeouts, set tag format, and reduce artifact retention
wyattjoh Mar 13, 2026
1a82814
docs: update CLAUDE.md paths for monorepo structure
wyattjoh Mar 13, 2026
9b0508d
fix: harden release scripts and update CLAUDE.md documentation
wyattjoh Mar 13, 2026
27df13e
fix: update bun.lock and fix errorSpy reference after rebase
wyattjoh Mar 16, 2026
8749cac
refactor: rename npm wrapper package from @clerk/cli to clerk
wyattjoh Mar 17, 2026
d799450
fix: remove unused errorSpy variable in link test
wyattjoh Mar 19, 2026
0599b1e
ci: narrow workflow permissions and fix multiline output
wyattjoh Mar 19, 2026
fa6d7f9
fix(ci): remove unintended leading spaces in snapshot failure comment
wyattjoh Mar 19, 2026
cebde2c
refactor: add bunTarget and ext fields to target definitions
wyattjoh Mar 19, 2026
109556f
feat: add check-release script for stable release detection
wyattjoh Mar 19, 2026
f482ff0
feat: add changesets configuration
wyattjoh Mar 19, 2026
4199333
feat: add cross-compile build script
wyattjoh Mar 19, 2026
507f311
feat: add canary versioning script
wyattjoh Mar 19, 2026
e89d2b0
feat: add snapshot versioning script
wyattjoh Mar 19, 2026
7d4e38f
feat: add git tag and GitHub Release creation to releaser
wyattjoh Mar 19, 2026
6490b26
feat: add release, version, and build scripts to root package.json
wyattjoh Mar 19, 2026
58efb32
refactor: consolidate build-binaries into single sequential job
wyattjoh Mar 19, 2026
edc36cb
feat: replace release-please with changesets/action in release workflow
wyattjoh Mar 19, 2026
55ee209
refactor: use changeset snapshot script and fix failure comment inden…
wyattjoh Mar 19, 2026
2d76c86
chore: remove release-please configuration files
wyattjoh Mar 19, 2026
f30646c
docs: update releasing.md for changesets migration
wyattjoh Mar 19, 2026
077275b
fix(ci): replace heredocs with echo+body-file in snapshot.yml comments
wyattjoh Mar 19, 2026
2d01b44
fix: address review findings — error handling and cleanup
wyattjoh Mar 19, 2026
66c394c
refactor: consolidate canary.ts into snapshot.ts
wyattjoh Mar 19, 2026
fd316a6
refactor: extract shared isPublished helper, inline readVersion, remo…
wyattjoh Mar 19, 2026
0847f26
refactor: standardize arg parsing with node:util parseArgs, co-locate…
wyattjoh Mar 19, 2026
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
11 changes: 11 additions & 0 deletions .changeset/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# Changesets

This directory is used by [Changesets](https://github.com/changesets/changesets) to manage versioning and changelogs.

To add a changeset, run:

```sh
bunx changeset
```

This will prompt you to select the packages that changed and the type of change (patch, minor, major). A markdown file will be created in this directory describing the change — commit it with your PR.
14 changes: 14 additions & 0 deletions .changeset/config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "public",
"baseBranch": "origin/main",
"updateInternalDependencies": "patch",
"ignore": ["@clerk/cli-core"],
"snapshot": {
"useCalculatedVersion": true,
"prereleaseTemplate": "{tag}.v{datetime}"
}
}
79 changes: 79 additions & 0 deletions .github/workflows/build-binaries.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
name: Build Binaries

on:
workflow_call:
inputs:
version:
required: true
type: string
ref:
required: true
type: string
artifact-prefix:
required: true
type: string
description: "Prefix for artifact names (e.g. 'clerk', 'clerk-canary', 'clerk-snapshot')"

jobs:

@brkalowbrkalowMar 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a practical benefit to parallelizing these? For a larger project I could see the argument, but these builds should be really quick.

I expect the overhead of the GH actions setup here to build / upload / download the artifacts might end up taking longer than doing everything inline.

build:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Build all targets
env:
CLI_VERSION: ${{ inputs.version }}
run: bun run scripts/build.ts --version="${CLI_VERSION}"
Comment on lines +26 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the build script
find . -name "build.ts" -o -name "build.js"| head -20

Repository: clerk/cli-new

Length of output: 75


🏁 Script executed:

# Check the workflow file structure to understand how targets are built
cat -n .github/workflows/build-binaries.yml | head -50

Repository: clerk/cli-new

Length of output: 1722


🏁 Script executed:

# Find and examine the cli-core package.json
find . -path "*/packages/cli-core/package.json"

Repository: clerk/cli-new

Length of output: 89


🏁 Script executed:

cat -n ./scripts/build.ts

Repository: clerk/cli-new

Length of output: 2701


🏁 Script executed:

cat -n ./packages/cli-core/package.json | head -40

Repository: clerk/cli-new

Length of output: 1105


🏁 Script executed:

cat -n ./scripts/releaser/targets.ts

Repository: clerk/cli-new

Length of output: 2532


🏁 Script executed:

grep -r "@napi-rs/keyring" ./packages/cli-core/src/ --include="*.ts" -n

Repository: clerk/cli-new

Length of output: 799


🌐 Web query:

@napi-rs/keyring package structure platform-specific

💡 Result:

@napi-rs/keyring follows the common napi-rs “meta package + per-platform binary packages” layout:

  • Main (cross-platform) package:@napi-rs/keyring

    • Ships JS/TS files (index.js, index.d.ts, etc.).
    • Declares platform-specific packages under optionalDependencies, including (example list from v1.1.3):
      @napi-rs/keyring-darwin-x64, @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-win32-x64-msvc, @napi-rs/keyring-win32-ia32-msvc, @napi-rs/keyring-win32-arm64-msvc, @napi-rs/keyring-linux-x64-gnu, @napi-rs/keyring-linux-x64-musl, @napi-rs/keyring-linux-arm64-gnu, @napi-rs/keyring-linux-arm64-musl, @napi-rs/keyring-linux-arm-gnueabihf, @napi-rs/keyring-freebsd-x64. (app.unpkg.com)
  • Runtime loader (platform selection):index.js detects process.platform + process.arch, and on Linux additionally detects musl vs glibc (using process.report when available, otherwise inspecting ldd). It then loads either a local .node file (if present) or requires the matching platform package such as @napi-rs/keyring-linux-x64-gnu / @napi-rs/keyring-linux-x64-musl. (app.unpkg.com)

  • Per-platform packages: each @napi-rs/keyring-<platform> package is essentially “just the native binary for that target” (e.g. @napi-rs/keyring-linux-x64-gnu is the x86_64-unknown-linux-gnu binary). (socket.dev)

  • Install behavior: because those binaries are in optionalDependencies, package managers will typically only install the one that matches the current OS/CPU (and libc variant on Linux). (napi.rs)

Citations:


🌐 Web query:

Bun build --compile --target cross-compilation native modules

💡 Result:

To cross-compile a single-file executable with Bun, you use bun build --compile plus a Bun target triple:

bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp
# examples: bun-linux-arm64, bun-windows-x64, bun-darwin-arm64, etc.

Bun documents these --target values and shows cross-compiling from one OS/arch to another. [1]

Native modules (.node / N-API addons) with --compile

  • Bun can embed .node files into the produced executable, but it’s essentially embedding/packaging: you still need a .node binary built for the same OS/arch as the executable target. [2]
  • Make sure the addon is directly required, e.g.:
    constaddon=require("./addon.node");
    Bun warns that if the addon is loaded indirectly via tools like @mapbox/node-pre-gyp, it may not bundle correctly unless the .node is directly required. [2]

Practical cross-compilation workflow for native addons

  1. Build/obtain the addon for the target (e.g., Linux x64):
    • Prefer packages that publish prebuilt binaries for your target.
    • Or build the addon in a target environment (common: Linux Docker for bun-linux-x64, Windows runner for bun-windows-x64, etc.).
  2. Ensure your dependency install selects the right platform artifacts when needed:
    bun install --os=linux --cpu=x64
    This tells Bun to select packages for that platform (useful for cross-platform builds). [3]
  3. Run the compile step with the same target:
    bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp

Key point:--target cross-compiles the Bun executable, but your native addon must already be built for that target (and ideally be directly required so Bun can embed it). [1][2]

Sources:
[1] Bun docs: single-file executable + cross-compilation targets (--compile, --target=...) (bun.sh)
[2] Bun docs: “Embed N-API Addons” / embedding .node files + node-pre-gyp caveat (bun.sh)
[3] Bun docs: bun install --cpu/--os for platform-specific package selection (bun.sh)

Citations:


🏁 Script executed:

grep -r "credential-store\|from.*lib" ./packages/cli-core/src/cli.ts -n | head -20

Repository: clerk/cli-new

Length of output: 103


🏁 Script executed:

grep -r "credential-store" ./packages/cli-core/src/ --include="*.ts"| grep -v "\.test\.ts" -c

Repository: clerk/cli-new

Length of output: 58


🏁 Script executed:

rg "credential-store" ./packages/cli-core/src/ --type ts --files-with-matches | grep -v test

Repository: clerk/cli-new

Length of output: 335


🏁 Script executed:

grep -A 2 "credential-store" ./packages/cli-core/src/commands/auth/login.ts | head -10

Repository: clerk/cli-new

Length of output: 255


🏁 Script executed:

head -30 ./packages/cli-core/src/commands/auth/login.ts | grep -A 2 "credential-store"

Repository: clerk/cli-new

Length of output: 255


Build each target from a matching install and runner.

The workflow runs bun install --frozen-lockfile once on a Linux host (line 26), then cross-compiles all 8 targets (darwin-arm64, darwin-x64, linux-, win32-) from that single install (line 31). @napi-rs/keyring declares platform-specific binaries as optionalDependencies (e.g., @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-linux-x64-gnu); during bun install on Linux, only Linux variants are installed. When bun build --compile --target=bun-darwin-arm64 cross-compiles, the binary lacks the correct native keyring addon, and at runtime credential-store falls back to plaintext file storage instead. The current file/--version checks pass because they verify only binary format, not functionality. Build each target on its matching runner before publishing to ensure native addons are available for embedding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/build-binaries.yml around lines 26 - 31, The workflow
currently runs a single bun install on Linux and then cross-compiles all targets
with bun run scripts/build.ts (using CLI_VERSION), causing platform-specific
optionalDependencies like `@napi-rs/keyring-`* to be missing for non-Linux
targets; change the job to install and build each target on a matching runner
(create a matrix of targets mapped to appropriate runner OS/arch), run bun
install --frozen-lockfile on that runner before invoking bun run
scripts/build.ts --version="${CLI_VERSION}"/--target=<target> so the
platform-specific native addons are present and embedded during build; ensure
the build step references the same CLI_VERSION env and that each matrix entry
runs its own install+build pair rather than reusing a single install.


- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-arm64
path: dist/artifacts/darwin-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-x64
path: dist/artifacts/darwin-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64
path: dist/artifacts/linux-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64-musl
path: dist/artifacts/linux-arm64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64
path: dist/artifacts/linux-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64-musl
path: dist/artifacts/linux-x64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-arm64
path: dist/artifacts/win32-arm64/clerk.exe
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-x64
path: dist/artifacts/win32-x64/clerk.exe
retention-days: 1
13 changes: 4 additions & 9 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,25 +7,20 @@ on:
jobs:
lint:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun run format:check
- run: bun run lint
- run: bun run build

test:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- name: Run tests
run: |
failed=0
for f in $(find src -name '*.test.ts' | sort); do
if ! bun test "$f"; then
failed=1
fi
done
exit $failed
- run: bun run --filter @clerk/cli-core test
188 changes: 188 additions & 0 deletions .github/workflows/release.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
name: Release

on:
push:
branches: [main]

concurrency:
group: release
cancel-in-progress: false

permissions:
contents: read

Comment thread
wyattjoh marked this conversation as resolved.
jobs:
versioning:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.check.outputs.release_created }}
version: ${{ steps.check.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- id: changesets
uses: changesets/action@v1
with:
version: bun run version-packages
commit: "ci(repo): version packages"
title: "ci(repo): Version Packages"
env:
GITHUB_TOKEN: ${{ github.token }}

- name: Check if release needed
id: check
run: bun run scripts/check-release.ts

# ─── Stable release ────────────────────────────────────────────────

build:
needs: versioning
if: ${{ needs.versioning.outputs.release_created == 'true' }}
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.versioning.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk

smoke-test:
needs: [versioning, build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.versioning.outputs.version }}
artifact-prefix: clerk
preset: stable

publish-npm:
needs: [versioning, build, smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Publish packages
run: bun run release
env:
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
GH_TOKEN: ${{ github.token }}

upload-github-assets:
needs: [versioning, build, smoke-test, publish-npm]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Upload binaries to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
tag="v${{ needs.versioning.outputs.version }}"
for dir in dist/artifacts/clerk-*/; do
target=${dir#dist/artifacts/clerk-} && target=${target%/}
ext=""; [[ "$target" == win32-* ]] && ext=".exe"
gh release upload "$tag" "${dir}clerk${ext}#clerk-${target}${ext}"
done

# ─── Canary release ────────────────────────────────────────────────

canary-version:
needs: versioning
if: ${{ needs.versioning.outputs.release_created != 'true' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Version packages for canary
id: version
run: |
bun run version-packages:canary
version=$(jq -r '.version' packages/cli/package.json)
echo "version=${version}" >> "$GITHUB_OUTPUT"

canary-build:
needs: canary-version
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.canary-version.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk-canary

canary-smoke-test:
needs: [canary-version, canary-build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.canary-version.outputs.version }}
artifact-prefix: clerk-canary
preset: canary

canary-publish:
needs: [canary-version, canary-build, canary-smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-canary-*
path: dist/artifacts

- name: Rename artifact directories
run: |
cd dist/artifacts
for dir in clerk-canary-*/; do
target=${dir#clerk-canary-} && target=${target%/}
mv "$dir" "clerk-${target}"
done

- name: Publish canary packages
run: bun run release:canary --version "$CANARY_VERSION"
env:
CANARY_VERSION: ${{ needs.canary-version.outputs.version }}
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9671e4f
feat: add release infrastructure with cross-compiled binaries
wyattjoh Mar 13, 2026
458ad76
feat: add canary and snapshot release channels
wyattjoh Mar 13, 2026
f3560b2
fix: add .ts extension to config imports in test files
wyattjoh Mar 13, 2026
d4ebb77
fix: normalize all relative imports to use .ts extensions
wyattjoh Mar 13, 2026
5f2b957
fix: isolate test files to work around Bun mock.module leak
wyattjoh Mar 13, 2026
e9268b3
fix: harden workflows and clean up code issues
wyattjoh Mar 13, 2026
dc801a0
fix: align release flow with JS monorepo conventions
wyattjoh Mar 13, 2026
d6da7fe
docs: update release docs and add musl detection references
wyattjoh Mar 13, 2026
88ddd53
fix: address code review findings
wyattjoh Mar 13, 2026
31b83b7
fix: add snapshot smoke test, guard signal forwarding, fix docs
wyattjoh Mar 13, 2026
bffd158
feat: migrate npm publishing to OIDC trusted publishing
wyattjoh Mar 13, 2026
a14084c
refactor: simplify shim by using spawnSync instead of async spawn
wyattjoh Mar 13, 2026
a187670
chore: update license year to 2026
wyattjoh Mar 13, 2026
45abd2b
refactor: use static import for dev version and add npm provenance
wyattjoh Mar 13, 2026
2967063
fix: address code review findings from release infrastructure
wyattjoh Mar 13, 2026
7ffc7f3
fix: address code review findings from release and packaging
wyattjoh Mar 13, 2026
1b238a9
refactor: extract smoke tests into reusable workflow and simplify tsc…
wyattjoh Mar 13, 2026
3f52d84
fix: move typescript from peerDependencies to devDependencies
wyattjoh Mar 13, 2026
975ab13
fix: add job timeouts, set tag format, and reduce artifact retention
wyattjoh Mar 13, 2026
1a82814
docs: update CLAUDE.md paths for monorepo structure
wyattjoh Mar 13, 2026
9b0508d
fix: harden release scripts and update CLAUDE.md documentation
wyattjoh Mar 13, 2026
27df13e
fix: update bun.lock and fix errorSpy reference after rebase
wyattjoh Mar 16, 2026
8749cac
refactor: rename npm wrapper package from @clerk/cli to clerk
wyattjoh Mar 17, 2026
d799450
fix: remove unused errorSpy variable in link test
wyattjoh Mar 19, 2026
0599b1e
ci: narrow workflow permissions and fix multiline output
wyattjoh Mar 19, 2026
fa6d7f9
fix(ci): remove unintended leading spaces in snapshot failure comment
wyattjoh Mar 19, 2026
cebde2c
refactor: add bunTarget and ext fields to target definitions
wyattjoh Mar 19, 2026
109556f
feat: add check-release script for stable release detection
wyattjoh Mar 19, 2026
f482ff0
feat: add changesets configuration
wyattjoh Mar 19, 2026
4199333
feat: add cross-compile build script
wyattjoh Mar 19, 2026
507f311
feat: add canary versioning script
wyattjoh Mar 19, 2026
e89d2b0
feat: add snapshot versioning script
wyattjoh Mar 19, 2026
7d4e38f
feat: add git tag and GitHub Release creation to releaser
wyattjoh Mar 19, 2026
6490b26
feat: add release, version, and build scripts to root package.json
wyattjoh Mar 19, 2026
58efb32
refactor: consolidate build-binaries into single sequential job
wyattjoh Mar 19, 2026
edc36cb
feat: replace release-please with changesets/action in release workflow
wyattjoh Mar 19, 2026
55ee209
refactor: use changeset snapshot script and fix failure comment inden…
wyattjoh Mar 19, 2026
2d76c86
chore: remove release-please configuration files
wyattjoh Mar 19, 2026
f30646c
docs: update releasing.md for changesets migration
wyattjoh Mar 19, 2026
077275b
fix(ci): replace heredocs with echo+body-file in snapshot.yml comments
wyattjoh Mar 19, 2026
2d01b44
fix: address review findings — error handling and cleanup
wyattjoh Mar 19, 2026
66c394c
refactor: consolidate canary.ts into snapshot.ts
wyattjoh Mar 19, 2026
fd316a6
refactor: extract shared isPublished helper, inline readVersion, remo…
wyattjoh Mar 19, 2026
0847f26
refactor: standardize arg parsing with node:util parseArgs, co-locate…
wyattjoh Mar 19, 2026
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
11 changes: 11 additions & 0 deletions .changeset/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# Changesets

This directory is used by [Changesets](https://github.com/changesets/changesets) to manage versioning and changelogs.

To add a changeset, run:

```sh
bunx changeset
```

This will prompt you to select the packages that changed and the type of change (patch, minor, major). A markdown file will be created in this directory describing the change — commit it with your PR.
14 changes: 14 additions & 0 deletions .changeset/config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "public",
"baseBranch": "origin/main",
"updateInternalDependencies": "patch",
"ignore": ["@clerk/cli-core"],
"snapshot": {
"useCalculatedVersion": true,
"prereleaseTemplate": "{tag}.v{datetime}"
}
}
79 changes: 79 additions & 0 deletions .github/workflows/build-binaries.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
name: Build Binaries

on:
workflow_call:
inputs:
version:
required: true
type: string
ref:
required: true
type: string
artifact-prefix:
required: true
type: string
description: "Prefix for artifact names (e.g. 'clerk', 'clerk-canary', 'clerk-snapshot')"

jobs:

@brkalowbrkalowMar 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a practical benefit to parallelizing these? For a larger project I could see the argument, but these builds should be really quick.

I expect the overhead of the GH actions setup here to build / upload / download the artifacts might end up taking longer than doing everything inline.

build:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Build all targets
env:
CLI_VERSION: ${{ inputs.version }}
run: bun run scripts/build.ts --version="${CLI_VERSION}"
Comment on lines +26 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the build script
find . -name "build.ts" -o -name "build.js"| head -20

Repository: clerk/cli-new

Length of output: 75


🏁 Script executed:

# Check the workflow file structure to understand how targets are built
cat -n .github/workflows/build-binaries.yml | head -50

Repository: clerk/cli-new

Length of output: 1722


🏁 Script executed:

# Find and examine the cli-core package.json
find . -path "*/packages/cli-core/package.json"

Repository: clerk/cli-new

Length of output: 89


🏁 Script executed:

cat -n ./scripts/build.ts

Repository: clerk/cli-new

Length of output: 2701


🏁 Script executed:

cat -n ./packages/cli-core/package.json | head -40

Repository: clerk/cli-new

Length of output: 1105


🏁 Script executed:

cat -n ./scripts/releaser/targets.ts

Repository: clerk/cli-new

Length of output: 2532


🏁 Script executed:

grep -r "@napi-rs/keyring" ./packages/cli-core/src/ --include="*.ts" -n

Repository: clerk/cli-new

Length of output: 799


🌐 Web query:

@napi-rs/keyring package structure platform-specific

💡 Result:

@napi-rs/keyring follows the common napi-rs “meta package + per-platform binary packages” layout:

  • Main (cross-platform) package:@napi-rs/keyring

    • Ships JS/TS files (index.js, index.d.ts, etc.).
    • Declares platform-specific packages under optionalDependencies, including (example list from v1.1.3):
      @napi-rs/keyring-darwin-x64, @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-win32-x64-msvc, @napi-rs/keyring-win32-ia32-msvc, @napi-rs/keyring-win32-arm64-msvc, @napi-rs/keyring-linux-x64-gnu, @napi-rs/keyring-linux-x64-musl, @napi-rs/keyring-linux-arm64-gnu, @napi-rs/keyring-linux-arm64-musl, @napi-rs/keyring-linux-arm-gnueabihf, @napi-rs/keyring-freebsd-x64. (app.unpkg.com)
  • Runtime loader (platform selection):index.js detects process.platform + process.arch, and on Linux additionally detects musl vs glibc (using process.report when available, otherwise inspecting ldd). It then loads either a local .node file (if present) or requires the matching platform package such as @napi-rs/keyring-linux-x64-gnu / @napi-rs/keyring-linux-x64-musl. (app.unpkg.com)

  • Per-platform packages: each @napi-rs/keyring-<platform> package is essentially “just the native binary for that target” (e.g. @napi-rs/keyring-linux-x64-gnu is the x86_64-unknown-linux-gnu binary). (socket.dev)

  • Install behavior: because those binaries are in optionalDependencies, package managers will typically only install the one that matches the current OS/CPU (and libc variant on Linux). (napi.rs)

Citations:


🌐 Web query:

Bun build --compile --target cross-compilation native modules

💡 Result:

To cross-compile a single-file executable with Bun, you use bun build --compile plus a Bun target triple:

bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp
# examples: bun-linux-arm64, bun-windows-x64, bun-darwin-arm64, etc.

Bun documents these --target values and shows cross-compiling from one OS/arch to another. [1]

Native modules (.node / N-API addons) with --compile

  • Bun can embed .node files into the produced executable, but it’s essentially embedding/packaging: you still need a .node binary built for the same OS/arch as the executable target. [2]
  • Make sure the addon is directly required, e.g.:
    constaddon=require("./addon.node");
    Bun warns that if the addon is loaded indirectly via tools like @mapbox/node-pre-gyp, it may not bundle correctly unless the .node is directly required. [2]

Practical cross-compilation workflow for native addons

  1. Build/obtain the addon for the target (e.g., Linux x64):
    • Prefer packages that publish prebuilt binaries for your target.
    • Or build the addon in a target environment (common: Linux Docker for bun-linux-x64, Windows runner for bun-windows-x64, etc.).
  2. Ensure your dependency install selects the right platform artifacts when needed:
    bun install --os=linux --cpu=x64
    This tells Bun to select packages for that platform (useful for cross-platform builds). [3]
  3. Run the compile step with the same target:
    bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp

Key point:--target cross-compiles the Bun executable, but your native addon must already be built for that target (and ideally be directly required so Bun can embed it). [1][2]

Sources:
[1] Bun docs: single-file executable + cross-compilation targets (--compile, --target=...) (bun.sh)
[2] Bun docs: “Embed N-API Addons” / embedding .node files + node-pre-gyp caveat (bun.sh)
[3] Bun docs: bun install --cpu/--os for platform-specific package selection (bun.sh)

Citations:


🏁 Script executed:

grep -r "credential-store\|from.*lib" ./packages/cli-core/src/cli.ts -n | head -20

Repository: clerk/cli-new

Length of output: 103


🏁 Script executed:

grep -r "credential-store" ./packages/cli-core/src/ --include="*.ts"| grep -v "\.test\.ts" -c

Repository: clerk/cli-new

Length of output: 58


🏁 Script executed:

rg "credential-store" ./packages/cli-core/src/ --type ts --files-with-matches | grep -v test

Repository: clerk/cli-new

Length of output: 335


🏁 Script executed:

grep -A 2 "credential-store" ./packages/cli-core/src/commands/auth/login.ts | head -10

Repository: clerk/cli-new

Length of output: 255


🏁 Script executed:

head -30 ./packages/cli-core/src/commands/auth/login.ts | grep -A 2 "credential-store"

Repository: clerk/cli-new

Length of output: 255


Build each target from a matching install and runner.

The workflow runs bun install --frozen-lockfile once on a Linux host (line 26), then cross-compiles all 8 targets (darwin-arm64, darwin-x64, linux-, win32-) from that single install (line 31). @napi-rs/keyring declares platform-specific binaries as optionalDependencies (e.g., @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-linux-x64-gnu); during bun install on Linux, only Linux variants are installed. When bun build --compile --target=bun-darwin-arm64 cross-compiles, the binary lacks the correct native keyring addon, and at runtime credential-store falls back to plaintext file storage instead. The current file/--version checks pass because they verify only binary format, not functionality. Build each target on its matching runner before publishing to ensure native addons are available for embedding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/build-binaries.yml around lines 26 - 31, The workflow
currently runs a single bun install on Linux and then cross-compiles all targets
with bun run scripts/build.ts (using CLI_VERSION), causing platform-specific
optionalDependencies like `@napi-rs/keyring-`* to be missing for non-Linux
targets; change the job to install and build each target on a matching runner
(create a matrix of targets mapped to appropriate runner OS/arch), run bun
install --frozen-lockfile on that runner before invoking bun run
scripts/build.ts --version="${CLI_VERSION}"/--target=<target> so the
platform-specific native addons are present and embedded during build; ensure
the build step references the same CLI_VERSION env and that each matrix entry
runs its own install+build pair rather than reusing a single install.


- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-arm64
path: dist/artifacts/darwin-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-x64
path: dist/artifacts/darwin-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64
path: dist/artifacts/linux-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64-musl
path: dist/artifacts/linux-arm64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64
path: dist/artifacts/linux-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64-musl
path: dist/artifacts/linux-x64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-arm64
path: dist/artifacts/win32-arm64/clerk.exe
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-x64
path: dist/artifacts/win32-x64/clerk.exe
retention-days: 1
13 changes: 4 additions & 9 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,25 +7,20 @@ on:
jobs:
lint:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun run format:check
- run: bun run lint
- run: bun run build

test:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- name: Run tests
run: |
failed=0
for f in $(find src -name '*.test.ts' | sort); do
if ! bun test "$f"; then
failed=1
fi
done
exit $failed
- run: bun run --filter @clerk/cli-core test
188 changes: 188 additions & 0 deletions .github/workflows/release.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
name: Release

on:
push:
branches: [main]

concurrency:
group: release
cancel-in-progress: false

permissions:
contents: read

Comment thread
wyattjoh marked this conversation as resolved.
jobs:
versioning:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.check.outputs.release_created }}
version: ${{ steps.check.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- id: changesets
uses: changesets/action@v1
with:
version: bun run version-packages
commit: "ci(repo): version packages"
title: "ci(repo): Version Packages"
env:
GITHUB_TOKEN: ${{ github.token }}

- name: Check if release needed
id: check
run: bun run scripts/check-release.ts

# ─── Stable release ────────────────────────────────────────────────

build:
needs: versioning
if: ${{ needs.versioning.outputs.release_created == 'true' }}
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.versioning.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk

smoke-test:
needs: [versioning, build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.versioning.outputs.version }}
artifact-prefix: clerk
preset: stable

publish-npm:
needs: [versioning, build, smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Publish packages
run: bun run release
env:
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
GH_TOKEN: ${{ github.token }}

upload-github-assets:
needs: [versioning, build, smoke-test, publish-npm]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Upload binaries to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
tag="v${{ needs.versioning.outputs.version }}"
for dir in dist/artifacts/clerk-*/; do
target=${dir#dist/artifacts/clerk-} && target=${target%/}
ext=""; [[ "$target" == win32-* ]] && ext=".exe"
gh release upload "$tag" "${dir}clerk${ext}#clerk-${target}${ext}"
done

# ─── Canary release ────────────────────────────────────────────────

canary-version:
needs: versioning
if: ${{ needs.versioning.outputs.release_created != 'true' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Version packages for canary
id: version
run: |
bun run version-packages:canary
version=$(jq -r '.version' packages/cli/package.json)
echo "version=${version}" >> "$GITHUB_OUTPUT"

canary-build:
needs: canary-version
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.canary-version.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk-canary

canary-smoke-test:
needs: [canary-version, canary-build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.canary-version.outputs.version }}
artifact-prefix: clerk-canary
preset: canary

canary-publish:
needs: [canary-version, canary-build, canary-smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-canary-*
path: dist/artifacts

- name: Rename artifact directories
run: |
cd dist/artifacts
for dir in clerk-canary-*/; do
target=${dir#clerk-canary-} && target=${target%/}
mv "$dir" "clerk-${target}"
done

- name: Publish canary packages
run: bun run release:canary --version "$CANARY_VERSION"
env:
CANARY_VERSION: ${{ needs.canary-version.outputs.version }}
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9671e4f
feat: add release infrastructure with cross-compiled binaries
wyattjoh Mar 13, 2026
458ad76
feat: add canary and snapshot release channels
wyattjoh Mar 13, 2026
f3560b2
fix: add .ts extension to config imports in test files
wyattjoh Mar 13, 2026
d4ebb77
fix: normalize all relative imports to use .ts extensions
wyattjoh Mar 13, 2026
5f2b957
fix: isolate test files to work around Bun mock.module leak
wyattjoh Mar 13, 2026
e9268b3
fix: harden workflows and clean up code issues
wyattjoh Mar 13, 2026
dc801a0
fix: align release flow with JS monorepo conventions
wyattjoh Mar 13, 2026
d6da7fe
docs: update release docs and add musl detection references
wyattjoh Mar 13, 2026
88ddd53
fix: address code review findings
wyattjoh Mar 13, 2026
31b83b7
fix: add snapshot smoke test, guard signal forwarding, fix docs
wyattjoh Mar 13, 2026
bffd158
feat: migrate npm publishing to OIDC trusted publishing
wyattjoh Mar 13, 2026
a14084c
refactor: simplify shim by using spawnSync instead of async spawn
wyattjoh Mar 13, 2026
a187670
chore: update license year to 2026
wyattjoh Mar 13, 2026
45abd2b
refactor: use static import for dev version and add npm provenance
wyattjoh Mar 13, 2026
2967063
fix: address code review findings from release infrastructure
wyattjoh Mar 13, 2026
7ffc7f3
fix: address code review findings from release and packaging
wyattjoh Mar 13, 2026
1b238a9
refactor: extract smoke tests into reusable workflow and simplify tsc…
wyattjoh Mar 13, 2026
3f52d84
fix: move typescript from peerDependencies to devDependencies
wyattjoh Mar 13, 2026
975ab13
fix: add job timeouts, set tag format, and reduce artifact retention
wyattjoh Mar 13, 2026
1a82814
docs: update CLAUDE.md paths for monorepo structure
wyattjoh Mar 13, 2026
9b0508d
fix: harden release scripts and update CLAUDE.md documentation
wyattjoh Mar 13, 2026
27df13e
fix: update bun.lock and fix errorSpy reference after rebase
wyattjoh Mar 16, 2026
8749cac
refactor: rename npm wrapper package from @clerk/cli to clerk
wyattjoh Mar 17, 2026
d799450
fix: remove unused errorSpy variable in link test
wyattjoh Mar 19, 2026
0599b1e
ci: narrow workflow permissions and fix multiline output
wyattjoh Mar 19, 2026
fa6d7f9
fix(ci): remove unintended leading spaces in snapshot failure comment
wyattjoh Mar 19, 2026
cebde2c
refactor: add bunTarget and ext fields to target definitions
wyattjoh Mar 19, 2026
109556f
feat: add check-release script for stable release detection
wyattjoh Mar 19, 2026
f482ff0
feat: add changesets configuration
wyattjoh Mar 19, 2026
4199333
feat: add cross-compile build script
wyattjoh Mar 19, 2026
507f311
feat: add canary versioning script
wyattjoh Mar 19, 2026
e89d2b0
feat: add snapshot versioning script
wyattjoh Mar 19, 2026
7d4e38f
feat: add git tag and GitHub Release creation to releaser
wyattjoh Mar 19, 2026
6490b26
feat: add release, version, and build scripts to root package.json
wyattjoh Mar 19, 2026
58efb32
refactor: consolidate build-binaries into single sequential job
wyattjoh Mar 19, 2026
edc36cb
feat: replace release-please with changesets/action in release workflow
wyattjoh Mar 19, 2026
55ee209
refactor: use changeset snapshot script and fix failure comment inden…
wyattjoh Mar 19, 2026
2d76c86
chore: remove release-please configuration files
wyattjoh Mar 19, 2026
f30646c
docs: update releasing.md for changesets migration
wyattjoh Mar 19, 2026
077275b
fix(ci): replace heredocs with echo+body-file in snapshot.yml comments
wyattjoh Mar 19, 2026
2d01b44
fix: address review findings — error handling and cleanup
wyattjoh Mar 19, 2026
66c394c
refactor: consolidate canary.ts into snapshot.ts
wyattjoh Mar 19, 2026
fd316a6
refactor: extract shared isPublished helper, inline readVersion, remo…
wyattjoh Mar 19, 2026
0847f26
refactor: standardize arg parsing with node:util parseArgs, co-locate…
wyattjoh Mar 19, 2026
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
11 changes: 11 additions & 0 deletions .changeset/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# Changesets

This directory is used by [Changesets](https://github.com/changesets/changesets) to manage versioning and changelogs.

To add a changeset, run:

```sh
bunx changeset
```

This will prompt you to select the packages that changed and the type of change (patch, minor, major). A markdown file will be created in this directory describing the change — commit it with your PR.
14 changes: 14 additions & 0 deletions .changeset/config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "public",
"baseBranch": "origin/main",
"updateInternalDependencies": "patch",
"ignore": ["@clerk/cli-core"],
"snapshot": {
"useCalculatedVersion": true,
"prereleaseTemplate": "{tag}.v{datetime}"
}
}
79 changes: 79 additions & 0 deletions .github/workflows/build-binaries.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
name: Build Binaries

on:
workflow_call:
inputs:
version:
required: true
type: string
ref:
required: true
type: string
artifact-prefix:
required: true
type: string
description: "Prefix for artifact names (e.g. 'clerk', 'clerk-canary', 'clerk-snapshot')"

jobs:

@brkalowbrkalowMar 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a practical benefit to parallelizing these? For a larger project I could see the argument, but these builds should be really quick.

I expect the overhead of the GH actions setup here to build / upload / download the artifacts might end up taking longer than doing everything inline.

build:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Build all targets
env:
CLI_VERSION: ${{ inputs.version }}
run: bun run scripts/build.ts --version="${CLI_VERSION}"
Comment on lines +26 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the build script
find . -name "build.ts" -o -name "build.js"| head -20

Repository: clerk/cli-new

Length of output: 75


🏁 Script executed:

# Check the workflow file structure to understand how targets are built
cat -n .github/workflows/build-binaries.yml | head -50

Repository: clerk/cli-new

Length of output: 1722


🏁 Script executed:

# Find and examine the cli-core package.json
find . -path "*/packages/cli-core/package.json"

Repository: clerk/cli-new

Length of output: 89


🏁 Script executed:

cat -n ./scripts/build.ts

Repository: clerk/cli-new

Length of output: 2701


🏁 Script executed:

cat -n ./packages/cli-core/package.json | head -40

Repository: clerk/cli-new

Length of output: 1105


🏁 Script executed:

cat -n ./scripts/releaser/targets.ts

Repository: clerk/cli-new

Length of output: 2532


🏁 Script executed:

grep -r "@napi-rs/keyring" ./packages/cli-core/src/ --include="*.ts" -n

Repository: clerk/cli-new

Length of output: 799


🌐 Web query:

@napi-rs/keyring package structure platform-specific

💡 Result:

@napi-rs/keyring follows the common napi-rs “meta package + per-platform binary packages” layout:

  • Main (cross-platform) package:@napi-rs/keyring

    • Ships JS/TS files (index.js, index.d.ts, etc.).
    • Declares platform-specific packages under optionalDependencies, including (example list from v1.1.3):
      @napi-rs/keyring-darwin-x64, @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-win32-x64-msvc, @napi-rs/keyring-win32-ia32-msvc, @napi-rs/keyring-win32-arm64-msvc, @napi-rs/keyring-linux-x64-gnu, @napi-rs/keyring-linux-x64-musl, @napi-rs/keyring-linux-arm64-gnu, @napi-rs/keyring-linux-arm64-musl, @napi-rs/keyring-linux-arm-gnueabihf, @napi-rs/keyring-freebsd-x64. (app.unpkg.com)
  • Runtime loader (platform selection):index.js detects process.platform + process.arch, and on Linux additionally detects musl vs glibc (using process.report when available, otherwise inspecting ldd). It then loads either a local .node file (if present) or requires the matching platform package such as @napi-rs/keyring-linux-x64-gnu / @napi-rs/keyring-linux-x64-musl. (app.unpkg.com)

  • Per-platform packages: each @napi-rs/keyring-<platform> package is essentially “just the native binary for that target” (e.g. @napi-rs/keyring-linux-x64-gnu is the x86_64-unknown-linux-gnu binary). (socket.dev)

  • Install behavior: because those binaries are in optionalDependencies, package managers will typically only install the one that matches the current OS/CPU (and libc variant on Linux). (napi.rs)

Citations:


🌐 Web query:

Bun build --compile --target cross-compilation native modules

💡 Result:

To cross-compile a single-file executable with Bun, you use bun build --compile plus a Bun target triple:

bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp
# examples: bun-linux-arm64, bun-windows-x64, bun-darwin-arm64, etc.

Bun documents these --target values and shows cross-compiling from one OS/arch to another. [1]

Native modules (.node / N-API addons) with --compile

  • Bun can embed .node files into the produced executable, but it’s essentially embedding/packaging: you still need a .node binary built for the same OS/arch as the executable target. [2]
  • Make sure the addon is directly required, e.g.:
    constaddon=require("./addon.node");
    Bun warns that if the addon is loaded indirectly via tools like @mapbox/node-pre-gyp, it may not bundle correctly unless the .node is directly required. [2]

Practical cross-compilation workflow for native addons

  1. Build/obtain the addon for the target (e.g., Linux x64):
    • Prefer packages that publish prebuilt binaries for your target.
    • Or build the addon in a target environment (common: Linux Docker for bun-linux-x64, Windows runner for bun-windows-x64, etc.).
  2. Ensure your dependency install selects the right platform artifacts when needed:
    bun install --os=linux --cpu=x64
    This tells Bun to select packages for that platform (useful for cross-platform builds). [3]
  3. Run the compile step with the same target:
    bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp

Key point:--target cross-compiles the Bun executable, but your native addon must already be built for that target (and ideally be directly required so Bun can embed it). [1][2]

Sources:
[1] Bun docs: single-file executable + cross-compilation targets (--compile, --target=...) (bun.sh)
[2] Bun docs: “Embed N-API Addons” / embedding .node files + node-pre-gyp caveat (bun.sh)
[3] Bun docs: bun install --cpu/--os for platform-specific package selection (bun.sh)

Citations:


🏁 Script executed:

grep -r "credential-store\|from.*lib" ./packages/cli-core/src/cli.ts -n | head -20

Repository: clerk/cli-new

Length of output: 103


🏁 Script executed:

grep -r "credential-store" ./packages/cli-core/src/ --include="*.ts"| grep -v "\.test\.ts" -c

Repository: clerk/cli-new

Length of output: 58


🏁 Script executed:

rg "credential-store" ./packages/cli-core/src/ --type ts --files-with-matches | grep -v test

Repository: clerk/cli-new

Length of output: 335


🏁 Script executed:

grep -A 2 "credential-store" ./packages/cli-core/src/commands/auth/login.ts | head -10

Repository: clerk/cli-new

Length of output: 255


🏁 Script executed:

head -30 ./packages/cli-core/src/commands/auth/login.ts | grep -A 2 "credential-store"

Repository: clerk/cli-new

Length of output: 255


Build each target from a matching install and runner.

The workflow runs bun install --frozen-lockfile once on a Linux host (line 26), then cross-compiles all 8 targets (darwin-arm64, darwin-x64, linux-, win32-) from that single install (line 31). @napi-rs/keyring declares platform-specific binaries as optionalDependencies (e.g., @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-linux-x64-gnu); during bun install on Linux, only Linux variants are installed. When bun build --compile --target=bun-darwin-arm64 cross-compiles, the binary lacks the correct native keyring addon, and at runtime credential-store falls back to plaintext file storage instead. The current file/--version checks pass because they verify only binary format, not functionality. Build each target on its matching runner before publishing to ensure native addons are available for embedding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/build-binaries.yml around lines 26 - 31, The workflow
currently runs a single bun install on Linux and then cross-compiles all targets
with bun run scripts/build.ts (using CLI_VERSION), causing platform-specific
optionalDependencies like `@napi-rs/keyring-`* to be missing for non-Linux
targets; change the job to install and build each target on a matching runner
(create a matrix of targets mapped to appropriate runner OS/arch), run bun
install --frozen-lockfile on that runner before invoking bun run
scripts/build.ts --version="${CLI_VERSION}"/--target=<target> so the
platform-specific native addons are present and embedded during build; ensure
the build step references the same CLI_VERSION env and that each matrix entry
runs its own install+build pair rather than reusing a single install.


- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-arm64
path: dist/artifacts/darwin-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-x64
path: dist/artifacts/darwin-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64
path: dist/artifacts/linux-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64-musl
path: dist/artifacts/linux-arm64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64
path: dist/artifacts/linux-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64-musl
path: dist/artifacts/linux-x64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-arm64
path: dist/artifacts/win32-arm64/clerk.exe
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-x64
path: dist/artifacts/win32-x64/clerk.exe
retention-days: 1
13 changes: 4 additions & 9 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,25 +7,20 @@ on:
jobs:
lint:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun run format:check
- run: bun run lint
- run: bun run build

test:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- name: Run tests
run: |
failed=0
for f in $(find src -name '*.test.ts' | sort); do
if ! bun test "$f"; then
failed=1
fi
done
exit $failed
- run: bun run --filter @clerk/cli-core test
188 changes: 188 additions & 0 deletions .github/workflows/release.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
name: Release

on:
push:
branches: [main]

concurrency:
group: release
cancel-in-progress: false

permissions:
contents: read

Comment thread
wyattjoh marked this conversation as resolved.
jobs:
versioning:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.check.outputs.release_created }}
version: ${{ steps.check.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- id: changesets
uses: changesets/action@v1
with:
version: bun run version-packages
commit: "ci(repo): version packages"
title: "ci(repo): Version Packages"
env:
GITHUB_TOKEN: ${{ github.token }}

- name: Check if release needed
id: check
run: bun run scripts/check-release.ts

# ─── Stable release ────────────────────────────────────────────────

build:
needs: versioning
if: ${{ needs.versioning.outputs.release_created == 'true' }}
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.versioning.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk

smoke-test:
needs: [versioning, build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.versioning.outputs.version }}
artifact-prefix: clerk
preset: stable

publish-npm:
needs: [versioning, build, smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Publish packages
run: bun run release
env:
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
GH_TOKEN: ${{ github.token }}

upload-github-assets:
needs: [versioning, build, smoke-test, publish-npm]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Upload binaries to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
tag="v${{ needs.versioning.outputs.version }}"
for dir in dist/artifacts/clerk-*/; do
target=${dir#dist/artifacts/clerk-} && target=${target%/}
ext=""; [[ "$target" == win32-* ]] && ext=".exe"
gh release upload "$tag" "${dir}clerk${ext}#clerk-${target}${ext}"
done

# ─── Canary release ────────────────────────────────────────────────

canary-version:
needs: versioning
if: ${{ needs.versioning.outputs.release_created != 'true' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Version packages for canary
id: version
run: |
bun run version-packages:canary
version=$(jq -r '.version' packages/cli/package.json)
echo "version=${version}" >> "$GITHUB_OUTPUT"

canary-build:
needs: canary-version
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.canary-version.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk-canary

canary-smoke-test:
needs: [canary-version, canary-build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.canary-version.outputs.version }}
artifact-prefix: clerk-canary
preset: canary

canary-publish:
needs: [canary-version, canary-build, canary-smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-canary-*
path: dist/artifacts

- name: Rename artifact directories
run: |
cd dist/artifacts
for dir in clerk-canary-*/; do
target=${dir#clerk-canary-} && target=${target%/}
mv "$dir" "clerk-${target}"
done

- name: Publish canary packages
run: bun run release:canary --version "$CANARY_VERSION"
env:
CANARY_VERSION: ${{ needs.canary-version.outputs.version }}
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9671e4f
feat: add release infrastructure with cross-compiled binaries
wyattjoh Mar 13, 2026
458ad76
feat: add canary and snapshot release channels
wyattjoh Mar 13, 2026
f3560b2
fix: add .ts extension to config imports in test files
wyattjoh Mar 13, 2026
d4ebb77
fix: normalize all relative imports to use .ts extensions
wyattjoh Mar 13, 2026
5f2b957
fix: isolate test files to work around Bun mock.module leak
wyattjoh Mar 13, 2026
e9268b3
fix: harden workflows and clean up code issues
wyattjoh Mar 13, 2026
dc801a0
fix: align release flow with JS monorepo conventions
wyattjoh Mar 13, 2026
d6da7fe
docs: update release docs and add musl detection references
wyattjoh Mar 13, 2026
88ddd53
fix: address code review findings
wyattjoh Mar 13, 2026
31b83b7
fix: add snapshot smoke test, guard signal forwarding, fix docs
wyattjoh Mar 13, 2026
bffd158
feat: migrate npm publishing to OIDC trusted publishing
wyattjoh Mar 13, 2026
a14084c
refactor: simplify shim by using spawnSync instead of async spawn
wyattjoh Mar 13, 2026
a187670
chore: update license year to 2026
wyattjoh Mar 13, 2026
45abd2b
refactor: use static import for dev version and add npm provenance
wyattjoh Mar 13, 2026
2967063
fix: address code review findings from release infrastructure
wyattjoh Mar 13, 2026
7ffc7f3
fix: address code review findings from release and packaging
wyattjoh Mar 13, 2026
1b238a9
refactor: extract smoke tests into reusable workflow and simplify tsc…
wyattjoh Mar 13, 2026
3f52d84
fix: move typescript from peerDependencies to devDependencies
wyattjoh Mar 13, 2026
975ab13
fix: add job timeouts, set tag format, and reduce artifact retention
wyattjoh Mar 13, 2026
1a82814
docs: update CLAUDE.md paths for monorepo structure
wyattjoh Mar 13, 2026
9b0508d
fix: harden release scripts and update CLAUDE.md documentation
wyattjoh Mar 13, 2026
27df13e
fix: update bun.lock and fix errorSpy reference after rebase
wyattjoh Mar 16, 2026
8749cac
refactor: rename npm wrapper package from @clerk/cli to clerk
wyattjoh Mar 17, 2026
d799450
fix: remove unused errorSpy variable in link test
wyattjoh Mar 19, 2026
0599b1e
ci: narrow workflow permissions and fix multiline output
wyattjoh Mar 19, 2026
fa6d7f9
fix(ci): remove unintended leading spaces in snapshot failure comment
wyattjoh Mar 19, 2026
cebde2c
refactor: add bunTarget and ext fields to target definitions
wyattjoh Mar 19, 2026
109556f
feat: add check-release script for stable release detection
wyattjoh Mar 19, 2026
f482ff0
feat: add changesets configuration
wyattjoh Mar 19, 2026
4199333
feat: add cross-compile build script
wyattjoh Mar 19, 2026
507f311
feat: add canary versioning script
wyattjoh Mar 19, 2026
e89d2b0
feat: add snapshot versioning script
wyattjoh Mar 19, 2026
7d4e38f
feat: add git tag and GitHub Release creation to releaser
wyattjoh Mar 19, 2026
6490b26
feat: add release, version, and build scripts to root package.json
wyattjoh Mar 19, 2026
58efb32
refactor: consolidate build-binaries into single sequential job
wyattjoh Mar 19, 2026
edc36cb
feat: replace release-please with changesets/action in release workflow
wyattjoh Mar 19, 2026
55ee209
refactor: use changeset snapshot script and fix failure comment inden…
wyattjoh Mar 19, 2026
2d76c86
chore: remove release-please configuration files
wyattjoh Mar 19, 2026
f30646c
docs: update releasing.md for changesets migration
wyattjoh Mar 19, 2026
077275b
fix(ci): replace heredocs with echo+body-file in snapshot.yml comments
wyattjoh Mar 19, 2026
2d01b44
fix: address review findings — error handling and cleanup
wyattjoh Mar 19, 2026
66c394c
refactor: consolidate canary.ts into snapshot.ts
wyattjoh Mar 19, 2026
fd316a6
refactor: extract shared isPublished helper, inline readVersion, remo…
wyattjoh Mar 19, 2026
0847f26
refactor: standardize arg parsing with node:util parseArgs, co-locate…
wyattjoh Mar 19, 2026
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
11 changes: 11 additions & 0 deletions .changeset/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# Changesets

This directory is used by [Changesets](https://github.com/changesets/changesets) to manage versioning and changelogs.

To add a changeset, run:

```sh
bunx changeset
```

This will prompt you to select the packages that changed and the type of change (patch, minor, major). A markdown file will be created in this directory describing the change — commit it with your PR.
14 changes: 14 additions & 0 deletions .changeset/config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "public",
"baseBranch": "origin/main",
"updateInternalDependencies": "patch",
"ignore": ["@clerk/cli-core"],
"snapshot": {
"useCalculatedVersion": true,
"prereleaseTemplate": "{tag}.v{datetime}"
}
}
79 changes: 79 additions & 0 deletions .github/workflows/build-binaries.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
name: Build Binaries

on:
workflow_call:
inputs:
version:
required: true
type: string
ref:
required: true
type: string
artifact-prefix:
required: true
type: string
description: "Prefix for artifact names (e.g. 'clerk', 'clerk-canary', 'clerk-snapshot')"

jobs:

@brkalowbrkalowMar 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a practical benefit to parallelizing these? For a larger project I could see the argument, but these builds should be really quick.

I expect the overhead of the GH actions setup here to build / upload / download the artifacts might end up taking longer than doing everything inline.

build:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Build all targets
env:
CLI_VERSION: ${{ inputs.version }}
run: bun run scripts/build.ts --version="${CLI_VERSION}"
Comment on lines +26 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the build script
find . -name "build.ts" -o -name "build.js"| head -20

Repository: clerk/cli-new

Length of output: 75


🏁 Script executed:

# Check the workflow file structure to understand how targets are built
cat -n .github/workflows/build-binaries.yml | head -50

Repository: clerk/cli-new

Length of output: 1722


🏁 Script executed:

# Find and examine the cli-core package.json
find . -path "*/packages/cli-core/package.json"

Repository: clerk/cli-new

Length of output: 89


🏁 Script executed:

cat -n ./scripts/build.ts

Repository: clerk/cli-new

Length of output: 2701


🏁 Script executed:

cat -n ./packages/cli-core/package.json | head -40

Repository: clerk/cli-new

Length of output: 1105


🏁 Script executed:

cat -n ./scripts/releaser/targets.ts

Repository: clerk/cli-new

Length of output: 2532


🏁 Script executed:

grep -r "@napi-rs/keyring" ./packages/cli-core/src/ --include="*.ts" -n

Repository: clerk/cli-new

Length of output: 799


🌐 Web query:

@napi-rs/keyring package structure platform-specific

💡 Result:

@napi-rs/keyring follows the common napi-rs “meta package + per-platform binary packages” layout:

  • Main (cross-platform) package:@napi-rs/keyring

    • Ships JS/TS files (index.js, index.d.ts, etc.).
    • Declares platform-specific packages under optionalDependencies, including (example list from v1.1.3):
      @napi-rs/keyring-darwin-x64, @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-win32-x64-msvc, @napi-rs/keyring-win32-ia32-msvc, @napi-rs/keyring-win32-arm64-msvc, @napi-rs/keyring-linux-x64-gnu, @napi-rs/keyring-linux-x64-musl, @napi-rs/keyring-linux-arm64-gnu, @napi-rs/keyring-linux-arm64-musl, @napi-rs/keyring-linux-arm-gnueabihf, @napi-rs/keyring-freebsd-x64. (app.unpkg.com)
  • Runtime loader (platform selection):index.js detects process.platform + process.arch, and on Linux additionally detects musl vs glibc (using process.report when available, otherwise inspecting ldd). It then loads either a local .node file (if present) or requires the matching platform package such as @napi-rs/keyring-linux-x64-gnu / @napi-rs/keyring-linux-x64-musl. (app.unpkg.com)

  • Per-platform packages: each @napi-rs/keyring-<platform> package is essentially “just the native binary for that target” (e.g. @napi-rs/keyring-linux-x64-gnu is the x86_64-unknown-linux-gnu binary). (socket.dev)

  • Install behavior: because those binaries are in optionalDependencies, package managers will typically only install the one that matches the current OS/CPU (and libc variant on Linux). (napi.rs)

Citations:


🌐 Web query:

Bun build --compile --target cross-compilation native modules

💡 Result:

To cross-compile a single-file executable with Bun, you use bun build --compile plus a Bun target triple:

bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp
# examples: bun-linux-arm64, bun-windows-x64, bun-darwin-arm64, etc.

Bun documents these --target values and shows cross-compiling from one OS/arch to another. [1]

Native modules (.node / N-API addons) with --compile

  • Bun can embed .node files into the produced executable, but it’s essentially embedding/packaging: you still need a .node binary built for the same OS/arch as the executable target. [2]
  • Make sure the addon is directly required, e.g.:
    constaddon=require("./addon.node");
    Bun warns that if the addon is loaded indirectly via tools like @mapbox/node-pre-gyp, it may not bundle correctly unless the .node is directly required. [2]

Practical cross-compilation workflow for native addons

  1. Build/obtain the addon for the target (e.g., Linux x64):
    • Prefer packages that publish prebuilt binaries for your target.
    • Or build the addon in a target environment (common: Linux Docker for bun-linux-x64, Windows runner for bun-windows-x64, etc.).
  2. Ensure your dependency install selects the right platform artifacts when needed:
    bun install --os=linux --cpu=x64
    This tells Bun to select packages for that platform (useful for cross-platform builds). [3]
  3. Run the compile step with the same target:
    bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp

Key point:--target cross-compiles the Bun executable, but your native addon must already be built for that target (and ideally be directly required so Bun can embed it). [1][2]

Sources:
[1] Bun docs: single-file executable + cross-compilation targets (--compile, --target=...) (bun.sh)
[2] Bun docs: “Embed N-API Addons” / embedding .node files + node-pre-gyp caveat (bun.sh)
[3] Bun docs: bun install --cpu/--os for platform-specific package selection (bun.sh)

Citations:


🏁 Script executed:

grep -r "credential-store\|from.*lib" ./packages/cli-core/src/cli.ts -n | head -20

Repository: clerk/cli-new

Length of output: 103


🏁 Script executed:

grep -r "credential-store" ./packages/cli-core/src/ --include="*.ts"| grep -v "\.test\.ts" -c

Repository: clerk/cli-new

Length of output: 58


🏁 Script executed:

rg "credential-store" ./packages/cli-core/src/ --type ts --files-with-matches | grep -v test

Repository: clerk/cli-new

Length of output: 335


🏁 Script executed:

grep -A 2 "credential-store" ./packages/cli-core/src/commands/auth/login.ts | head -10

Repository: clerk/cli-new

Length of output: 255


🏁 Script executed:

head -30 ./packages/cli-core/src/commands/auth/login.ts | grep -A 2 "credential-store"

Repository: clerk/cli-new

Length of output: 255


Build each target from a matching install and runner.

The workflow runs bun install --frozen-lockfile once on a Linux host (line 26), then cross-compiles all 8 targets (darwin-arm64, darwin-x64, linux-, win32-) from that single install (line 31). @napi-rs/keyring declares platform-specific binaries as optionalDependencies (e.g., @napi-rs/keyring-darwin-arm64, @napi-rs/keyring-linux-x64-gnu); during bun install on Linux, only Linux variants are installed. When bun build --compile --target=bun-darwin-arm64 cross-compiles, the binary lacks the correct native keyring addon, and at runtime credential-store falls back to plaintext file storage instead. The current file/--version checks pass because they verify only binary format, not functionality. Build each target on its matching runner before publishing to ensure native addons are available for embedding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/build-binaries.yml around lines 26 - 31, The workflow
currently runs a single bun install on Linux and then cross-compiles all targets
with bun run scripts/build.ts (using CLI_VERSION), causing platform-specific
optionalDependencies like `@napi-rs/keyring-`* to be missing for non-Linux
targets; change the job to install and build each target on a matching runner
(create a matrix of targets mapped to appropriate runner OS/arch), run bun
install --frozen-lockfile on that runner before invoking bun run
scripts/build.ts --version="${CLI_VERSION}"/--target=<target> so the
platform-specific native addons are present and embedded during build; ensure
the build step references the same CLI_VERSION env and that each matrix entry
runs its own install+build pair rather than reusing a single install.


- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-arm64
path: dist/artifacts/darwin-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-darwin-x64
path: dist/artifacts/darwin-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64
path: dist/artifacts/linux-arm64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-arm64-musl
path: dist/artifacts/linux-arm64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64
path: dist/artifacts/linux-x64/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-linux-x64-musl
path: dist/artifacts/linux-x64-musl/clerk
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-arm64
path: dist/artifacts/win32-arm64/clerk.exe
retention-days: 1

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}-win32-x64
path: dist/artifacts/win32-x64/clerk.exe
retention-days: 1
13 changes: 4 additions & 9 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,25 +7,20 @@ on:
jobs:
lint:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun run format:check
- run: bun run lint
- run: bun run build

test:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- name: Run tests
run: |
failed=0
for f in $(find src -name '*.test.ts' | sort); do
if ! bun test "$f"; then
failed=1
fi
done
exit $failed
- run: bun run --filter @clerk/cli-core test
188 changes: 188 additions & 0 deletions .github/workflows/release.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
name: Release

on:
push:
branches: [main]

concurrency:
group: release
cancel-in-progress: false

permissions:
contents: read

Comment thread
wyattjoh marked this conversation as resolved.
jobs:
versioning:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.check.outputs.release_created }}
version: ${{ steps.check.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- id: changesets
uses: changesets/action@v1
with:
version: bun run version-packages
commit: "ci(repo): version packages"
title: "ci(repo): Version Packages"
env:
GITHUB_TOKEN: ${{ github.token }}

- name: Check if release needed
id: check
run: bun run scripts/check-release.ts

# ─── Stable release ────────────────────────────────────────────────

build:
needs: versioning
if: ${{ needs.versioning.outputs.release_created == 'true' }}
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.versioning.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk

smoke-test:
needs: [versioning, build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.versioning.outputs.version }}
artifact-prefix: clerk
preset: stable

publish-npm:
needs: [versioning, build, smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Publish packages
run: bun run release
env:
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
GH_TOKEN: ${{ github.token }}

upload-github-assets:
needs: [versioning, build, smoke-test, publish-npm]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: clerk-*
path: dist/artifacts

- name: Upload binaries to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
tag="v${{ needs.versioning.outputs.version }}"
for dir in dist/artifacts/clerk-*/; do
target=${dir#dist/artifacts/clerk-} && target=${target%/}
ext=""; [[ "$target" == win32-* ]] && ext=".exe"
gh release upload "$tag" "${dir}clerk${ext}#clerk-${target}${ext}"
done

# ─── Canary release ────────────────────────────────────────────────

canary-version:
needs: versioning
if: ${{ needs.versioning.outputs.release_created != 'true' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

- name: Version packages for canary
id: version
run: |
bun run version-packages:canary
version=$(jq -r '.version' packages/cli/package.json)
echo "version=${version}" >> "$GITHUB_OUTPUT"

canary-build:
needs: canary-version
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.canary-version.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk-canary

canary-smoke-test:
needs: [canary-version, canary-build]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.canary-version.outputs.version }}
artifact-prefix: clerk-canary
preset: canary

canary-publish:
needs: [canary-version, canary-build, canary-smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing
run: npm install -g npm@11
- run: bun install --frozen-lockfile

- uses: actions/download-artifact@v4
with:
pattern: clerk-canary-*
path: dist/artifacts

- name: Rename artifact directories
run: |
cd dist/artifacts
for dir in clerk-canary-*/; do
target=${dir#clerk-canary-} && target=${target%/}
mv "$dir" "clerk-${target}"
done

- name: Publish canary packages
run: bun run release:canary --version "$CANARY_VERSION"
env:
CANARY_VERSION: ${{ needs.canary-version.outputs.version }}
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
Loading
Loading