Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
271474d
feat(migrate): add migration command suite
royanger Aug 6, 2026
f32b73f
test(migrate): support multiselect prompt stubs
royanger Aug 6, 2026
9a56d9f
docs(migrate): mention migration command
royanger Aug 6, 2026
080eb66
docs(changeset): add migrate changeset
royanger Aug 6, 2026
d94307e
refactor(migrate): keep migration state in the CLI config, not a cwd …
royanger Aug 6, 2026
2f22633
feat(migrate): add `clerk migrate settings`, and resolve migration en…
royanger Aug 6, 2026
194d8f6
feat(migrate): explain each setting in `migrate settings`, and fix th…
royanger Aug 6, 2026
909003b
feat(migrate): move the Supabase scope of `skip-unsupported-providers…
royanger Aug 6, 2026
922890a
feat(migrate): wrap the logs and transformers subcommands in the gutter
royanger Aug 6, 2026
05b6536
feat(migrate): follow the CLI's spinner, next-steps, pluralization an…
royanger Aug 6, 2026
68c7057
feat(prompts): advertise select-all in the multiselect footer
royanger Aug 6, 2026
e9a6692
feat(migrate): offer to fix the settings the readiness report flags
royanger Aug 6, 2026
18cef5e
feat(migrate): document `clerk migrate` rather than `clerk migrate run`
royanger Aug 6, 2026
85afd33
fix(migrate): read the Firebase hash parameters only when the transfo…
royanger Aug 6, 2026
a38470f
Merge branch 'main' into ra/integrate-migration-tool-into-cli
royanger Aug 17, 2026
3a667d7
chore: ignore local migration exports and service account keys
royanger Aug 18, 2026
829d979
fix(config): persist the migration entry to disk
royanger Aug 18, 2026
9a96503
refactor(migrate)!: rename `migrate run` to `migrate import`, drop --…
royanger Aug 18, 2026
35c2b09
feat(migrate): timestamp export filenames and ask where to save
royanger Aug 18, 2026
5a16189
feat(migrate): URL-encode credentials pasted into a connection string
royanger Aug 18, 2026
8f374a9
feat(migrate): choose the Clerk export source from a flat instance list
royanger Aug 18, 2026
f077786
feat(migrate): prompt for the Firebase service account key
royanger Aug 18, 2026
f0ba767
refactor(migrate): restyle `clerk migrate transformers list`
royanger Aug 18, 2026
aa62b62
refactor(migrate): name log files after the commands that write them
royanger Sep 8, 2026
b1fec90
refactor: move the redaction placeholder into constants
royanger Sep 8, 2026
f9c0b08
feat(migrate): accept Firebase's own variable names and restyle setti…
royanger Sep 8, 2026
bcce90a
Merge branch 'main' into ra/integrate-migration-tool-into-cli
royanger Sep 8, 2026
bf4d6ed
fix(cli): name the host when a request cannot connect
royanger Sep 11, 2026
1c029c8
feat(migrate): ask once where migration logs go, and remember it
royanger Sep 11, 2026
9900266
feat(migrate): clear one setting by name
royanger Sep 11, 2026
0f35744
fix(migrate): warn instead of refusing when an import may exceed the …
royanger Sep 11, 2026
0faebbc
feat(migrate): sign in and link before an import starts
royanger Sep 11, 2026
4bf8048
feat(migrate): accept libsql/Turso connection strings
royanger Sep 11, 2026
94b15e8
feat(migrate): ask again when a database connection fails
royanger Sep 14, 2026
563ff35
refactor(migrate): retry any rejected credential, not just a connecti…
royanger Sep 14, 2026
534894f
fix(migrate): keep the Firebase import command on one line
royanger Sep 14, 2026
9f3db3f
Merge branch 'main' into ra/integrate-migration-tool-into-cli
royanger Sep 16, 2026
37ec7ec
fix(migrate): mark promise-returning callbacks async
royanger Sep 16, 2026
8893e60
feat(migrate): add WorkOS as an export platform and transformer
royanger Sep 16, 2026
4e5c4bd
docs: sync the migrate description in the root README
royanger Sep 16, 2026
fe3beec
docs(migrate): describe the export source picker and counts as they are
royanger Sep 17, 2026
58f98c7
feat(migrate): make `-y` mean "do not prompt" on every export
royanger Sep 17, 2026
0541bb3
fix(migrate): let a named instance skip the export source picker
royanger Sep 17, 2026
24f2cb9
fix(migrate): require -y to clear every setting where nothing can be …
royanger Sep 17, 2026
e447998
build: drop the unused `build` script and compile in CI
royanger Sep 18, 2026
3030f79
chore: set bun.lock configVersion to 0
royanger Sep 18, 2026
13c37c0
Merge branch 'main' into ra/integrate-migration-tool-into-cli
royanger Sep 18, 2026
f46c0fc
feat(migrate): accept --no-with-identities and assume it under -y
royanger Sep 21, 2026
a2a5165
fix(migrate): keep the log directory an import saved at its start
royanger Sep 21, 2026
e4db578
feat(migrate): print the import command where an agent can see it
royanger Sep 21, 2026
95a0ca7
build: track the playwright-core patch against 1.63.0
royanger Sep 21, 2026
c33e9f8
test(migrate): compare the satellite host by hostname, not substring
royanger Sep 21, 2026
dbd907f
ci: match the Playwright image to the version installed
royanger Sep 22, 2026
9edb832
build: restore a build alias for main's CI
royanger Sep 22, 2026
c7e5452
build: hold Playwright at 1.60.0 until main's CI image moves
royanger Sep 22, 2026
d76b455
feat(migrate): remember what an export produced
royanger Sep 23, 2026
447432d
refactor(migrate)!: remove `clerk migrate settings` and the state it …
royanger Sep 29, 2026
8376389
feat(migrate): keep every run in a run store, and add `clerk migrate …
royanger Sep 29, 2026
560cf28
feat(migrate): add `clerk migrate undo <run-id>`
royanger Sep 29, 2026
e9e75b6
feat(migrate): exports write a self-describing file into their run
royanger Sep 29, 2026
5e24e5a
feat(migrate)!: rename transformers to sources, and say what each one…
royanger Sep 29, 2026
983b1f3
fix(migrate): correct what each source carries
royanger Sep 29, 2026
d56fa31
feat(migrate)!: check every import against the instance, continue re-…
royanger Sep 29, 2026
9fca06c
feat(migrate): print the target first on every command, and name the …
royanger Sep 29, 2026
4bf1cd3
docs(migrate): rewrite the README around the six commands, the five r…
royanger Sep 29, 2026
ea8fc9d
fix(migrate): keep banned users banned from Better Auth, Firebase and…
royanger Oct 1, 2026
459887a
fix(migrate): carry Auth0 blocked users and display names
royanger Oct 1, 2026
63886d4
feat(migrate): keep the WorkOS tenant's external_id in private metadata
royanger Oct 1, 2026
f6a3232
fix(migrate): warn when Firebase returns no hash for a password user
royanger Oct 1, 2026
0effeda
fix(migrate): add the leading + to Supabase phone numbers
royanger Oct 1, 2026
ebd8acb
feat(migrate): let CLERK_MIGRATE_DEV_USER_LIMIT override the dev user…
royanger Oct 1, 2026
9ab81bc
fix(migrate): check usernames against the instance's username rules
royanger Oct 1, 2026
2c38439
fix(migrate): drop emails and phones the instance has off before crea…
royanger Oct 1, 2026
63a38bb
fix(migrate): say passwords are stored, not dropped, on an instance w…
royanger Oct 1, 2026
069a1db
fix(migrate): create the user without a phone Clerk refuses
royanger Oct 1, 2026
7407d73
fix(migrate): skip anonymous Better Auth users
royanger Oct 1, 2026
6630765
fix(migrate): record each user as created as soon as POST /v1/users r…
royanger Oct 1, 2026
e8291f0
fix(migrate): drop usernames too when the instance has them off
royanger Oct 2, 2026
8cfda2e
fix(migrate): retry the instance lookup on 429, and never call an unk…
royanger Oct 2, 2026
1aea960
fix(migrate): import a one-word name as the first name
royanger Oct 2, 2026
e0960b1
fix(migrate): name the columns for a schema the export can't read, an…
royanger Oct 2, 2026
a33767f
fix(migrate): drop placeholder emails Clerk refuses, and reject users…
royanger Oct 2, 2026
2ff5ae7
fix(migrate): name the record kept when duplicates in the file collide
royanger Oct 2, 2026
f108dc0
fix(migrate): let undo find users whose create was in flight when the…
royanger Oct 2, 2026
3e01d66
fix(migrate): drop a first or last name Clerk refuses, instead of fai…
royanger Oct 2, 2026
4b7a604
fix(migrate): detect Supabase password hashers per user, and skip sof…
royanger Oct 2, 2026
fdfe548
docs(migrate): document the read-only Firebase roles, and name a miss…
royanger Oct 2, 2026
cea1167
docs(migrate): document both verified Firebase role setups
royanger Oct 2, 2026
3eec00b
fix(migrate): make resume and undo safe after an interrupted import
royanger Oct 2, 2026
6930163
docs: add stacked pr plan
royanger Oct 2, 2026
e642140
fix(migrate): stop creating users with more trust than the source gav…
royanger Oct 2, 2026
0365fb8
fix(migrate): fix the Medium review findings and the Standards behavi…
royanger Oct 2, 2026
0d0fb59
fix(migrate): fix the Low review findings and the Firebase temp-file …
royanger Oct 2, 2026
fdd4974
fix(migrate): offer no fix for a Supabase provider Clerk doesn't offer
royanger Oct 2, 2026
033a28c
docs(changeset): Add `clerk migrate` for moving users into Clerk from…
royanger Oct 2, 2026
b59cbbf
test(migrate): read each user's latest run line in the e2e test
royanger Oct 2, 2026
b070514
test(e2e): don't load .env files into the e2e run
royanger Oct 2, 2026
baba090
fix(migrate): read Better Auth schemas Drizzle generated
royanger Oct 2, 2026
d620193
fix(migrate): exit 130 when Ctrl-C stops an import or undo
royanger Oct 2, 2026
7a82040
Merge branch 'main' into ra/integrate-migration-tool-into-cli
royanger Oct 5, 2026
fc2a1da
docs: replace the stacked PR plan with the slice plan
royanger Oct 5, 2026
a87ac6e
docs: drop the agent baseline re-runs from the slice plan
royanger Oct 5, 2026
2e7dd4c
fix(migrate): keep Supabase last names from user metadata
royanger Oct 6, 2026
86fcc62
fix(migrate): reject a row with an unknown password hasher instead of…
royanger Oct 6, 2026
e6079dd
fix(migrate): let a row's own values win over source defaults
royanger Oct 6, 2026
3b1463e
test(migrate): expect an unknown hasher to be rejected, not abort the…
royanger Oct 6, 2026
5c28491
fix(migrate): leave a create answered without a user ID as unknown
royanger Oct 6, 2026
2ff094f
fix(migrate): say a literal --instance could not be verified when the…
royanger Oct 6, 2026
17bf7a9
test(migrate): restore the interactive tests' mode instead of forcing it
royanger Oct 6, 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
12 changes: 12 additions & 0 deletions .changeset/integrate-migration-tool-into-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"clerk": minor
---

Add `clerk migrate` for moving users into Clerk from Clerk, Auth0, Supabase, Auth.js, Better Auth, Firebase or WorkOS.

- `migrate export <source>` writes a self-describing file. `migrate import <file|export-run-id>` checks every user against the instance before writing (`--dry-run`, `--allow-partial`, `--skip-legal-checks`), asks before it writes, and continues where an interrupted or partial run stopped.
- `migrate runs` shows what every run did, `migrate undo <run-id>` deletes the users an import created, and `migrate sources` shows what each source carries, including sources you write yourself (`--source ./my-source.ts`).
- A request that cannot connect now names the host it could not reach.
- `clerk init` warns when the project uses WorkOS, and points to the migration guide.
- Multiselect prompts list `a: all` in their key legend.
- `clerk users` dry runs name the instance the key reaches and where the key came from, such as `--app` or the `CLERK_SECRET_KEY` env var.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: bun-${{ runner.os }}-
- run: bun install --frozen-lockfile
- run: bun run build
- run: bun run build:compile

lint:
name: Lint
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ coverage

# logs
logs
!packages/cli-core/src/commands/migrate/logs/
!packages/cli-core/src/commands/migrate/logs/**
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

Expand Down Expand Up @@ -44,3 +46,7 @@ test/e2e/.har

# Local planning/spec docs
docs/superpowers/

# Local migration exports and the credentials that produced them
exports/
*service-account*.json
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Default to using Bun instead of Node.js.
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
- `Bun.redis` for Redis. Don't use `ioredis`.
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
- `Bun.sql` for Postgres and MySQL. Don't use `pg`, `postgres.js`, or `mysql2`.
- `WebSocket` is built-in. Don't use `ws`.
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
- Bun.$`ls` instead of execa.
Expand All @@ -48,14 +48,16 @@ bun run test:e2e:op # Run E2E tests with secrets resolved from 1Password (prefe
bun run test:e2e # Run E2E tests with env vars already set (used by CI)
```

Locally, prefer `bun run test:e2e:op` so secrets are injected from 1Password in-memory and never written to disk. `bun run test:e2e` is for CI or for cases where the required env vars are already exported.
Locally, prefer `bun run test:e2e:op` so secrets are injected from 1Password in-memory and never written to disk. `bun run test:e2e` is for CI or for cases where the required env vars are already exported. Both run `bun test` with `--no-env-file`, so a `.env.local` pointing the CLI at a local `clerk_go` stack can't send the production test secrets there.

CI runs `bun run format:check` (fails if unformatted), `bun run lint`, `bun run test`, and `bun run test:e2e` on every PR to `main`. E2E tests only run for PRs from the same repository (not external forks) and target the production Clerk API with a dedicated test application.

When running multiple test files directly with `bun test`, always pass `--isolate` or `--parallel`. `--parallel` implies `--isolate`. Without isolation, Bun can share module mocks across files and produce order-dependent failures. Prefer `bun run test` for the full suite because it already passes `--parallel`.

These flags require Bun >= 1.3.13 — older versions silently ignore them and lose isolation. `bun run test` and `bun run test:e2e` run `scripts/check-bun-version.ts` first, which fails fast when the installed Bun is older than the `engines.bun` floor in package.json.

The same floor also covers `Bun.sql`'s MySQL adapter used by the DB-backed export commands: MySQL support landed in Bun 1.2.21, but binary columns (password hashes) only decoded correctly from 1.3.6. See the header of `scripts/check-bun-version.ts`.

## Versioning

The `CLI_VERSION` global is injected at compile time via `bun build --compile --define "CLI_VERSION=..."`. The CI release workflow injects the real version.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Commands:
init [options] Initialize Clerk in your project
link [options] Link this project to a Clerk application
mcp Manage the Clerk remote MCP server connection for AI editors and CLIs
migrate Migrate users into Clerk from another auth provider or another Clerk instance
open Open Clerk resources in your browser
telemetry Control CLI usage telemetry (status, disable, enable)
unlink [options] Unlink this project from its Clerk application
Expand Down
Loading
Loading