Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
2df236e
(wip) Started refactor, added CLI, started moving validators to their…
royanger Mar 2, 2024
ee654d0
(wip) Added Supabase validator
royanger Mar 2, 2024
58fe501
(wip) Check if the file exists
royanger Mar 2, 2024
bc86a10
(wip) Reading from .json, and from .csv into JSON
royanger Mar 2, 2024
335b642
(wip) Moved validator list to constant, generate CLI options on deman…
royanger Mar 2, 2024
5a28fc6
(wip) Moved validators into their own directory, added metadata to ea…
royanger Mar 2, 2024
a43625b
(wip) Minor improvements,some more typing
royanger Mar 2, 2024
bdad2f6
(wip) Refactored to transform incoming data to expected schema
royanger Mar 3, 2024
7a02123
(wip) Basic import is now working
royanger Mar 3, 2024
8367ee7
(wip) Cleanup and adding spinners + messaging
royanger Mar 3, 2024
398e9ec
(wip) Added logger, some cleanup
royanger Mar 3, 2024
3f400b2
(wip) Improved logger, creates directories as needed
royanger Mar 3, 2024
f2d6e6a
(wip) Improved logger significantly, removed blank/empty entries from…
royanger Mar 3, 2024
e959bc0
(wip) Added eslint
royanger Mar 3, 2024
34f3768
(wip)
royanger Mar 3, 2024
28b852e
(wip) Added prettier
royanger Mar 4, 2024
969c4a1
(wip) Removed spinner that was added just for testing.
royanger Mar 4, 2024
63fa0fd
(wip) Added Supabase JSON sample
royanger Mar 4, 2024
6d5f6a4
(wip) Added transform/validation to JSON and CSV
royanger Mar 4, 2024
242d0e7
Removed need for different handler filers, combined into one.
royanger Mar 4, 2024
4af9dab
Formatting
royanger Mar 4, 2024
c46c5e3
Bug fixes and minor updates
royanger Mar 4, 2024
cb11d76
Fixed types with any on transform functions and used a newer .hasOwnP…
JacobMGEvans Mar 4, 2024
dea3101
Updated Supabase handler, added sample, add code to add default field
royanger Mar 4, 2024
2a22ff4
Merge pull request #12 from clerk/jacob/cut-from-roy-refactor
royanger Mar 4, 2024
10fb7d9
improved Logger type and fixed updated type handling the undefined wi…
JacobMGEvans Mar 4, 2024
1852a79
Apply suggestions from code review
royanger Mar 4, 2024
08ab75c
Type passed loadUsersFromFile needs validation, handling with cast fo…
JacobMGEvans Mar 4, 2024
64b096e
Updated Auth0 map
royanger Mar 4, 2024
802f34c
handle merge conflict
JacobMGEvans Mar 4, 2024
4829cda
Merge pull request #13 from clerk/jacob/testing-refactor
JacobMGEvans Mar 5, 2024
b10b1cc
Added more tests for file types for loadUsers & added errorLogger test
JacobMGEvans Mar 6, 2024
31b7cce
Merge pull request #14 from clerk/jacob/testing
JacobMGEvans Mar 6, 2024
6f31f1f
Updated .gitignore
royanger May 26, 2024
f39d270
Modified handlers and code for Clerk exported JSON
royanger May 29, 2024
c834c91
Update the createUser to include more fields
royanger May 29, 2024
e5cf86f
wip: changes to files/exports and work to adapt to new JSON format fr…
royanger May 31, 2024
bb0a927
fix pathing for windows
jescalan Jul 26, 2024
6e31577
feat(validators): add additional password hashing algorithms to userS…
kduprey Jul 10, 2025
f675484
Merge pull request #29 from clerk/kenton/sup-806-update-list-of-suppo…
kduprey Jul 10, 2025
9b7eace
chore: Update deps, swap to @clerk/backend
royanger Jan 14, 2026
0f97f0a
refactor: Tests added, CLI refactored, code refactor, many changes
royanger Jan 19, 2026
238f280
Merge branch 'roy/refactor-script-for-multiple-validators' into roy/t…
royanger Jan 19, 2026
4ec88d4
Merge pull request #32 from clerk/roy/testing-clerk-json
royanger Jan 19, 2026
396cbe4
chore: Remove console logs
royanger Jan 19, 2026
b5c0e97
Merge branch 'main' into roy/refactor-script-for-multiple-validators
royanger Jan 19, 2026
44bd2e9
refactor: Updated default rate limits
royanger Jan 19, 2026
0cdf534
refactor: Updated list of hashers, CLI error if there is an invalid h…
royanger Jan 19, 2026
a3d62b7
chore: Clean up unused code
royanger Jan 20, 2026
5628555
refactor: Optionally enabled metadata, removed mfaEnabled
royanger Jan 20, 2026
4572d2e
feat: Add script to clean logs
royanger Jan 20, 2026
1e8774b
chore: Update tests, delete script, sample data
royanger Jan 20, 2026
0124a4c
chore: Split handlers
royanger Jan 20, 2026
27c3623
refactor: CLI updates and clean up
royanger Jan 20, 2026
1797b3d
refactor: Reorganized files and scripts
royanger Jan 20, 2026
aff0721
refactor: Updated README, updated delete script to only delete migrat…
royanger Jan 20, 2026
345531c
chore: Added JSDoc comments throughout code
royanger Jan 21, 2026
9e06d75
refactor: Implement tryCatch for all async operations
royanger Jan 21, 2026
6f3a96b
chore: Fix imports, update README
royanger Jan 21, 2026
6546524
chore: Update use of .email() in zod schema
royanger Jan 21, 2026
8a7d4bb
feat: Expanded sample data
royanger Jan 21, 2026
745da8a
chore: Upgraded deps
royanger Jan 21, 2026
729a197
chore: Added logging for 'delete', changed phone numbers, fixed tests
royanger Jan 21, 2026
acb7846
chore: Update esling and prettier, add pre-commit prettier hook
royanger Jan 22, 2026
fb52acb
chore: Project clean up
royanger Jan 22, 2026
1cf0ec0
refactor: Clean up, config changes, refactoring logger, updating docs…
royanger Jan 22, 2026
aeef48b
chore: Update eslint config
royanger Jan 22, 2026
6abc663
refactor: Fixed lint errors
royanger Jan 22, 2026
c6d6eb1
fix: Update sample data and added support for | separated emails and …
royanger Jan 22, 2026
7798a4c
docs: Updated README with validator and transformer info
royanger Jan 22, 2026
bdf6e49
chore: Updated Auth.js sample to match default user table schema/data
royanger Jan 22, 2026
9051304
refactor: Updated CLI to show info about each transformer
royanger Jan 22, 2026
d382107
fix: Fixed rate limints/concurrency
royanger Jan 22, 2026
1557051
refactor: Include information about users who failed validation in su…
royanger Jan 22, 2026
c3df1fa
Merge branch 'main' into roy/refactor-script-for-multiple-validators
royanger Jan 22, 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
47 changes: 40 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,43 @@
CLERK_SECRET_KEY=sk_live_
# ============================================================================
# REQUIRED: Clerk Secret Key
# ============================================================================
# Get your secret key from the Clerk Dashboard: https://dashboard.clerk.com
# Format: sk_test_... (development) or sk_live_... (production)
CLERK_SECRET_KEY=sk_

# By default you can not import users to your development instance
# Change this to 'true' if you want to import to a development instance
IMPORT_TO_DEV_INSTANCE=false
# ============================================================================
# OPTIONAL: Rate Limit Override
# ============================================================================
# Rate limit in requests per second for user creation
#
# Auto-configured based on your CLERK_SECRET_KEY:
# - Production (sk_live_*): 100 requests/second (Clerk limit: 1000 req/10s)
# - Development (sk_test_*): 10 requests/second (Clerk limit: 100 req/10s)
#
# Only set this if you need to reduce the rate for safety or testing, or if
# have a rate limit exception and can increase the speed
# Example: RATE_LIMIT=50
# RATE_LIMIT=

# Delay between createUser requests
# Clerk's normal rate limit is 1 request/second
DELAY=1050
# ============================================================================
# OPTIONAL: Concurrency Limit Override
# ============================================================================
# Number of concurrent API requests during migration/deletion
#
# Auto-calculated to achieve ~95% of rate limit (assumes 100ms API latency):
# - Production (100 req/s): 9 concurrent = ~90-95 req/s throughput
# - Development (10 req/s): 1 concurrent = ~9-10 req/s throughput
#
# Increase this value if:
# - Your API responses are slower (>100ms) and you want faster throughput
# - You want to process users faster (higher concurrency = faster, but may hit rate limits)
#
# Decrease this value if:
# - You're hitting rate limits (429 errors)
# - You want to be more conservative
#
# Examples:
# - CONCURRENCY_LIMIT=15 (faster, ~150 req/s, may hit some rate limits)
# - CONCURRENCY_LIMIT=5 (slower, ~50 req/s, very safe)
# CONCURRENCY_LIMIT=

7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
node_modules
.env
users.json
migration-log.json
bun.lockb
.settings
package-lock.json
yarn.lock
pnpm-lock.yaml
logs
testing/
.claude
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx lint-staged
10 changes: 10 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
logs/**
samples/**
testing/**
**/*.json
**/*.csv
**/*.log
node_modules
bun.lock


11 changes: 11 additions & 0 deletions .prettierrc.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default {
trailingComma: 'es5',
tabWidth: 2,
semi: true,
singleQuote: true,
printWidth: 80,
bracketSpacing: true,
arrowParens: 'always',
endOfLine: 'lf',
useTabs: true,
};
210 changes: 210 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

This is a CLI tool for migrating users from various authentication platforms (Clerk, Auth0, Supabase, AuthJS) to a Clerk instance. It handles rate limiting, validates user data with Zod schemas, and provides comprehensive logging of successes and failures.

## Common Commands

### Development Commands

- `bun migrate` - Start the migration process (interactive CLI)
- `bun delete` - Delete all migrated users (uses externalId to identify users)
- `bun clean-logs` - Remove all log files from the `./logs` folder
- `bun run test` - Run all tests with Vitest
- `bun lint` - Run ESLint
- `bun lint:fix` - Auto-fix ESLint issues
- `bun format` - Format code with Prettier
- `bun format:test` - Check formatting without making changes

### Testing

- `bun run test` - Run all test files
- `bun run test <filename>` - Run a specific test file (e.g., `bun run test validator.test.ts`)
- `bun run test --watch` - Run tests in watch mode

## Architecture

### Transformer System

The migration tool uses a **transformer pattern** to support different source platforms. Each transformer defines:

1. **Field Transformer**: Maps source platform fields to Clerk's schema
- Example: Auth0's `_id.$oid` → Clerk's `userId`
- Example: Supabase's `encrypted_password` → Clerk's `password`
- Handles nested field flattening (see `flattenObjectSelectively` in `src/migrate/functions.ts`)

2. **Optional Default Fields**: Applied to all users from that platform
- Example: Supabase defaults `passwordHasher` to `"bcrypt"`

3. **Optional Post-Transform**: Custom logic applied after field mapping
- Example: Auth0 converts metadata from string to objects

**Transformer locations**: `src/migrate/transformers/`

- `clerk.ts` - Clerk-to-Clerk migrations
- `auth0.ts` - Auth0 migrations
- `supabase.ts` - Supabase migrations
- `authjs.ts` - AuthJS migrations
- `index.ts` - Exports all transformers as array

**Adding a new transformer**:

1. Create a new file in `src/migrate/transformers/` with transformer config
2. Export it in `src/migrate/transformers/index.ts`
3. The CLI will automatically include it in the platform selection

### Data Flow

```
User File (CSV/JSON)
loadUsersFromFile (functions.ts)
↓ Parse file
↓ Apply transformer defaults
transformUsers (functions.ts)
↓ Transform field names via transformer
↓ Apply transformer postTransform
↓ Validate with Zod schema
↓ Log validation errors
importUsers (import-users.ts)
↓ Process sequentially with rate limiting
createUser (import-users.ts)
↓ Create user with primary email/phone
↓ Add additional emails/phones
↓ Handle errors and logging
```

### Schema Validation

User validation is centralized in `src/migrate/validator.ts`:

- Uses Zod for schema validation
- Enforces: at least one verified identifier (email or phone)
- Enforces: passwordHasher required when password is present
- Fields can be single values or arrays (e.g., `email: string | string[]`)
- All fields except `userId` are optional

**Adding a new field**: Edit `userSchema` in `src/migrate/validator.ts`

### Rate Limiting

Rate limits are auto-configured based on instance type (detected from `CLERK_SECRET_KEY`):

- **Production** (`sk_live_*`): 100 requests/second (Clerk's limit: 1000 req/10s)
- **Development** (`sk_test_*`): 10 requests/second (Clerk's limit: 100 req/10s)

Configuration in `src/envs-constants.ts`:

- `RATE_LIMIT` - Requests per second (auto-configured based on instance type)
- `CONCURRENCY_LIMIT` - Number of concurrent requests (defaults to ~95% of rate limit)
- Production: 9 concurrent (assumes 100ms API latency → ~90-95 req/s throughput)
- Development: 1 concurrent (assumes 100ms API latency → ~9-10 req/s throughput)
- Override defaults via `.env` file with `RATE_LIMIT` or `CONCURRENCY_LIMIT`

The script uses **p-limit for concurrency control** across all API calls:

- Limits the number of simultaneously executing API calls
- Formula: `CONCURRENCY_LIMIT = RATE_LIMIT * 0.095` (assumes 100ms latency)
- With X concurrent requests and 100ms latency: throughput ≈ X \* 10 req/s
- Shared limiter across ALL operations (user creation, email creation, phone creation)

**Performance**:

- Production: ~3,500 users in ~35 seconds (assuming 1 email per user)
- Development: ~3,500 users in ~350 seconds
- Users can increase `CONCURRENCY_LIMIT` for faster processing (may hit some rate limits)

**Retry logic**:

- If a 429 occurs, uses Retry-After value from API response
- Falls back to 10 second default if Retry-After not available
- Centralized in `getRetryDelay()` function in `src/utils.ts`
- The script automatically retries up to 5 times (configurable via MAX_RETRIES)

### Logging System

All operations create timestamped logs in `./logs/`:

- `{timestamp}-import.log` - Success/failure for each user
- `{timestamp}-import-errors.log` - Detailed error information
- `{timestamp}-delete.log` - User deletion results
- `{timestamp}-delete-errors.log` - Deletion errors

Logger functions in `src/logger.ts`:

- `importLogger()` - Log import attempt
- `errorLogger()` - Log creation errors
- `validationLogger()` - Log validation errors
- `deleteLogger()` - Log deletion attempt
- `deleteErrorLogger()` - Log deletion errors

### CLI Analysis Features

The CLI (in `src/migrate/cli.ts`) analyzes the import file before migration and provides:

1. **Identifier Analysis**: Shows which users have emails, phones, usernames
2. **Password Analysis**: Prompts whether to migrate users without passwords
3. **User Model Analysis**: Shows first/last name coverage
4. **Dashboard Configuration Guidance**: Tells user which fields to enable/require in Clerk Dashboard
5. **Instance Type Detection**: Prevents importing >500 users to dev instances

**Key CLI functions**:

- `runCLI()` - Main CLI orchestrator
- `analyzeFields()` - Analyzes user data for field coverage
- `displayIdentifierAnalysis()` - Shows identifier stats + Dashboard guidance
- `displayPasswordAnalysis()` - Shows password stats + prompts for skipPasswordRequirement
- `loadSettings()` / `saveSettings()` - Persists CLI choices in `.settings` file

### Error Handling

The codebase uses a consistent error handling pattern:

- `tryCatch()` utility (in `src/utils.ts`) - Returns `[result, error]` (error is null on success)
- Used extensively to make additional emails/phones non-fatal
- Rate limit errors (429) trigger automatic retry with delay
- Validation errors are logged but don't stop the migration

## Important Implementation Notes

### Clerk-to-Clerk Migrations

When migrating from Clerk to Clerk (`key === "clerk"`), the transformer consolidates email and phone arrays:

- Merges `email`, `emailAddresses`, `unverifiedEmailAddresses` into single array
- Merges `phone`, `phoneNumbers`, `unverifiedPhoneNumbers` into single array
- First item becomes primary, rest are added as additional identifiers
- See `transformUsers()` in `src/migrate/functions.ts` around line 129

### Password Hasher Validation

Invalid password hashers cause immediate failure:

- Valid hashers are defined in `PASSWORD_HASHERS` constant (`src/types.ts`)
- Detection logic in `transformUsers()` checks if hasher exists but is invalid
- Throws detailed error with user ID, row number, and list of valid hashers

### User Creation Multi-Step Process

Creating a user involves multiple API calls, all managed by the shared concurrency limiter:

1. Create user with primary email/phone + core fields (rate-limited)
2. Add additional emails (each rate-limited individually, non-fatal)
3. Add additional phones (each rate-limited individually, non-fatal)

This is necessary because Clerk's API only accepts one primary identifier per creation call. All API calls share the same concurrency pool, maximizing throughput across all operations.

### Environment Variable Detection

The script auto-detects instance type from `CLERK_SECRET_KEY`:

- Checks if key contains `"live"` → production
- Otherwise → development
- Used to set default delays and enforce user limits
- See `detectInstanceType()` and `createEnvSchema()` in `src/envs-constants.ts`
Loading