feat: add doctor command for project health checks - #21

Merged
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command
Mar 11, 2026
Merged

feat: add doctor command for project health checks#21
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds clerk doctor command that runs 7 diagnostic checks covering authentication, project linkage, instance validity, environment variables, and CLI configuration
  • Supports --verbose, --json, --spotlight, and --fix options
  • Auto-fix flow prompts to run clerk auth login, clerk link, or clerk env pull for fixable issues, then re-verifies all checks
  • Includes comprehensive test coverage (30 tests, 117 assertions) for all checks and the context factory
  • Uses a defineCheck() builder pattern to eliminate duplicated result construction across check functions
  • Fix factories are lazy — passed as references and only invoked when a result actually needs a fix

Checks

CheckWhat it verifies
Logged inCredential store has a stored token
Authentication validToken is still valid via /oauth/userinfo
Project linkedCurrent git repo/worktree/directory is linked to a Clerk app
Application reachableLinked application ID exists and is accessible on Clerk
Instance IDsDev/prod instance IDs match the application
Environment variables.env.local or .env has Clerk keys
CLI configuration~/.clerk/config.json exists and parses

Test plan

  • Run bun test — all tests pass
  • Run clerk doctor in a linked project — all checks display correctly
  • Run clerk doctor --verbose — detail lines shown
  • Run clerk doctor --json — valid JSON output
  • Run clerk doctor --spotlight — only warnings/failures shown
  • Run clerk doctor --fix — prompts for fixable issues, re-verifies after
  • Run clerk doctor while logged out — authentication checks fail with remedy
  • Run clerk doctor in an unlinked directory — project linkage fails with remedy

Console

clerk doctor
image

clerk doctor --fix
image

@rafa-thayto

Copy link
Copy Markdown
ContributorAuthor

waiting for this lint fixes being merged: #22

Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Runs 8 diagnostic checks covering authentication, project linkage,
instances, git availability, environment variables, and CLI config.
Supports --verbose, --json, --spotlight, and --fix options.
Git is not required by any CLI command — the link command gracefully
falls back to directory-based profile keys when git is unavailable.
…prove tests
- Extract defineCheck() builder pattern to eliminate duplicated result
construction across all 7 check functions
- Make fix factories lazy (passed as references, invoked only when needed)
- Extract AUTH_ERROR_STATUS regex to named constant
- Add expectCheck() test helper and mockProfile fixture to reduce test boilerplate
- Replace non-null assertion with runtime guard in fix loop
- Rename "Linked app exists" check to "Linked application"
@@ -30,7 +30,7 @@ clerk doctor --fix # Offer to auto-fix issues
| Authentication token | Authentication | Credential store has a stored token |
| Token validity | Authentication | Token is still valid (calls `/oauth/userinfo`) |
| Project linkage | Project | Current directory is linked to a Clerk app |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I still find "Linked application" and "Project linkage" a little confusing. The definition of "Project linkage" doesn't use the term "project" but just uses "Clerk app".

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

let me think better on this

do you have any name suggestions?

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/doctor-command branch from d2a598e to 4cac338CompareMarch 11, 2026 16:45

@dmoernerdmoerner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Significantly clearer with the builder/factory, thank you!

I still find the project/application link distinction a little confusing. From the code I see it has to do with git or the directory, perhaps the description can be updated there.

Approved.

@rafa-thayto

rafa-thayto commented Mar 11, 2026

Copy link
Copy Markdown
ContributorAuthor

just updating the PR with the new patterns introduced on #24

Replace process.exit(1) calls with CliError throws to comply with
the unicorn/no-process-exit lint rule and the centralized error
handler in cli.ts. Import PlapiError from its canonical source
(errors.ts) and remove duplicate red export in color.ts.
Rename check titles to be user-focused rather than implementation-focused:
- "Authentication token" → "Logged in"
- "Token validity" → "Authentication valid"
- "Project linkage" → "Project linked"
- "Linked application" → "Application reachable"
- "Instances" → "Instance IDs"
Improve messages to be more explicit about what's happening:
- App reachability pass now includes app ID
- 404 fail says "not found on Clerk" with actionable remedy
- Network failures say "Could not reach Clerk"
- Stale instances say "mismatch ... not found in application"
- Remove redundant app ID from project link (shown in app check)
The check name isn't shown in terminal output, so the prefix is needed
for context.
@rafa-thayto
rafa-thayto merged commit 2d1cf72 into mainMar 11, 2026
2 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/doctor-command branch March 11, 2026 18:03
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
…mpts (#38)
* chore: add magicast dependency for AST-based code manipulation
Used by the init command's framework scaffolders to safely add imports
and modify config files without breaking existing code.
* refactor: fix framework SDK names and detection priority
- Fix @clerk/clerk-react → @clerk/react
- Fix @clerk/tanstack-start → @clerk/tanstack-react-start
- Remove standalone vite detection (covered by react)
- Reorder priority: scaffoldable frameworks first, then expo, react,
express, fastify
- Export readDeps for use by init context module
* feat(init): add project context gathering
Detects framework, TypeScript, src/ directory convention, package
manager, Next.js router variant, middleware filename, layout path,
and existing Clerk SDK installation.
* feat(init): add scaffold system with framework-specific scaffolders
Add idempotent scaffolders for Next.js (App + Pages Router), React,
React Router, Nuxt, TanStack Start, Astro, and Vue. Each scaffolder
generates framework-appropriate boilerplate (middleware, providers,
auth pages) while preserving existing user code.
Shared helpers handle AST-based import injection (magicast with string
fallback), middleware composition, and auth page creation.
* feat(init): add scan, format, preview, and agent prompt modules
- scan: detect competing auth libraries pre-scaffold and scan for
hardcoded keys/leftover imports post-scaffold
- format: run Prettier/Biome on generated files
- preview: show planned file changes and confirm before writing
- prompts: framework-specific agent mode prompts with exact code
snippets and file paths
* feat(init): rewrite init command with scaffold and scan flow
Replace the minimal init (login + link + install + env pull) with a
full orchestrator that also scaffolds framework boilerplate and scans
for issues. Skips login/link when already authenticated and linked.
* docs(init): update README with scaffolding docs and framework table
- Add Agent Mode section
- Add env var column to framework detection table
- Add Expo, Express, Fastify to detection table
- Document scaffolding actions for all 8 supported frameworks
- Note that Expo/Express/Fastify are detected but not scaffolded
* refactor(init): restructure agent prompts into markdown templates
Replace monolithic prompts.ts with individual .md template files per
framework, matching the clerk-docs structured format (sections, rules,
deprecated patterns, verification checklists).
- Move prompts to src/commands/init/prompts/*.md with {{PLACEHOLDER}}
interpolation
- Add prompts for expo, express, and fastify frameworks
- Merge DOCS_URLS + TEMPLATE_MAP into single FRAMEWORK_PROMPTS record
- Extract shared pmInstallCommand, remove duplicate pmAddCommand
- Fix formatter-escaped underscores (\_app, \_\_root) in template loader
- Add "After Setup" section to all prompts (matching clerk-docs)
* refactor(init): make FileAction a discriminated union and extend FrameworkScaffold
FileAction is now a proper discriminated union with a dedicated `skip`
variant that has no `content` field, enforced at the type level. The
FrameworkScaffold interface gains `dep`, `variant`, `minMajorVersion`,
`matches()`, and optional `enrichContext()` so each scaffolder is
self-describing. Enrichment fields (variant, layoutPath,
middlewareBasename) become optional on ProjectContext since they are
populated after gatherContext.
* refactor(init): extract Next.js context enrichment to frameworks/nextjs-context
Move parseNextMajorVersion, detectMiddlewareBasename, detectNextjsVariant,
and detectLayoutPath from context.ts into a dedicated nextjs-context.ts
module. This isolates framework-specific logic behind the enrichContext
hook on FrameworkScaffold rather than coupling it to gatherContext.
Parallelize dirExists calls with Promise.all in both files.
* refactor(init): replace scaffold key map with matches() dispatch and version check
Replace the Record<string, FrameworkScaffold> keyed map with an array
using satisfies and matches()-based lookup. Add enrichProjectContext()
that delegates to each scaffolder's enrichContext hook. Add minMajorVersion
guard that checks the framework dep version before scaffolding.
* refactor(init): add shared helpers and flatten scaffoldNextjsMiddleware
Add parseMajorVersion, insertAfterLastImport, wrapBodyWithProvider, and
resolveNextjsMiddlewareBasename to helpers. Use proper skip FileAction
in scaffoldAuthPage and scaffoldNextjsMiddleware. Flatten nested ifs
with early returns and store BunFile reference once. The middleware
fallback now resolves from the actual Next.js version in deps.
* refactor(init): update all scaffolders with skip actions, dep/matches, and minMajorVersion
Each scaffolder now exports dep, matches(), and minMajorVersion. All skip
cases use the proper FileAction skip variant. Flatten nested ifs with
early returns across scaffoldLayout, scaffoldApp, scaffoldMiddleware, and
scaffoldConfig. Use shared wrapBodyWithProvider (nextjs-app, tanstack-start),
insertAfterLastImport (react-router, vue), and extract wrapWithClerkProvider
in react.ts. Store BunFile references to avoid duplicate creation.
* refactor(init): remove findings cap, precompile regexes, and unexport internal types
Remove MAX_FINDINGS so no context is lost. Convert IGNORE_DIRS to a Set
for O(1) lookups. Precompile CODE_SCANS regexes once at module level.
Inline matchesFramework guard. Convert AuthLibraryScan and CodeScan from
exported interfaces to local types since they are not imported elsewhere.
* refactor(init): replace readFileSync with static text imports for compiled binaries
Use Bun's `import ... with { type: "text" }` to embed markdown prompt
templates at build time. This replaces the runtime readFileSync +
import.meta.dir approach that would break in compiled Bun binaries.
Remove the template cache since imports are already static.
* refactor(init): abstract formatter config into data-driven array
Replace inline if-checks for prettier/biome with a FormatterConfig type
and a FORMATTERS array. The runFormatters loop now iterates the config,
making it trivial to add new formatters.
* test(init): update tests for discriminated union, enrichContext, and version check
Update context tests to call enrichProjectContext separately from
gatherContext and import parseMajorVersion from helpers. Add tests for
scaffold version check (below minimum, meets minimum, Next.js 16 proxy).
Update nextjs-app tests to assert on the skip type instead of skipReason
field. Update scan test to verify all findings are returned without cap.
* chore: update bun.lock with magicast dependency
* refactor(init): extract shared auth and config scaffolding helpers
Add jsxAuthPageContent with type-safe JsxClerkPackage union,
scaffoldConfigFile generic for the find→check→modify pattern,
authComponentName, and inline capitalize. These shared helpers
eliminate duplication across framework scaffolders.
* refactor(init): use shared helpers in framework scaffolders
Replace duplicated authRouteContent/nextjsAuthPageContent with
jsxAuthPageContent, replace per-framework scaffoldConfig with
scaffoldConfigFile in astro, nuxt, and react-router. Remove
unused imports and add enableV8Middleware documentation.
* refactor(init): restructure preview, prompts, and scan modules
Update preview formatting to use switch on discriminated union,
restructure prompts/index.ts with typed template resolution and
variable building, and minor scan.ts cleanup.
* test(init): add framework scaffolder and scan tests
Add tests for nextjs-app (middleware composition, provider wrapping,
src/ paths, proxy basename), react-router (root modification, loader
merge), tanstack-start (base dir detection), and scan (no findings cap).
* chore(init): add markdown type declaration for static text imports
* feat(init): add framework lookup utility with aliases
Export FRAMEWORK_MAP and add lookupFramework() to resolve framework
names or aliases (e.g. "tanstack-start" → "@tanstack/react-start")
along with FRAMEWORK_NAMES for validation and display.
* feat(init): add i18nLocaleDir to ProjectContext type
Add optional i18nLocaleDir field to ProjectContext for frameworks
that detect locale-based routing directories (e.g. [locale], [lang]).
* refactor(init): extract previewPlan display function
Split preview logic into previewPlan() (display only) and
previewAndConfirm() (display + prompt) to support --yes mode
without duplicating the plan rendering code.
* feat(init): add shared scaffolding helpers for auth, env, and i18n
Add reusable helpers for all framework scaffolders:
- authFileSpecs() factory for sign-in/sign-up file generation
- scaffoldEnvVars() and SIGN_ROUTE_ENV_VARS for env file management
- htmlAuthComponentMarkup() and jsxAuthComponentMarkup() with
Tailwind/plain CSS variants
- hasTailwindStyles() for style detection
- i18n middleware detection, composition, and routing helpers
(detectI18nMiddlewareLib, composeWithI18nMiddleware, etc.)
- findFirstDirMatch() for generic directory scanning
* feat(init): detect i18n locale directory in Next.js context
Add detectI18nLocaleDir() to identify App Router locale directories
(e.g. [locale], [lang]) by checking for layout files inside dynamic
segments. Populate i18nLocaleDir in enrichNextjsContext() so
scaffolders can place auth pages inside locale-prefixed paths.
* feat(init): support framework override in context gathering
Accept optional frameworkOverride parameter in gatherContext() to
skip auto-detection when the user specifies --framework explicitly.
* feat(init): wire --framework, --yes, and --prompt CLI options
Register --framework <name>, -y/--yes, and --prompt options on the
init command. Wire them through to framework override resolution,
preview-only plan display, and agent prompt output respectively.
* refactor(init): migrate framework scaffolders to shared helpers
Update all six framework scaffolders (Astro, Next.js App/Pages,
Nuxt, React Router, TanStack Start) to use the shared helpers:
- authFileSpecs() factory for sign-in/sign-up generation
- scaffoldEnvVars() for .env file management
- hasTailwindStyles() for consistent style detection
- i18n-aware auth page placement using locale directory detection
- htmlAuthComponentMarkup()/jsxAuthComponentMarkup() for rendering
* test(init): add i18n detection and locale routing tests
Add tests for i18n support across frameworks:
- Context: locale directory detection ([locale], [lang]) with
layout file validation and src/ convention support
- Next.js App Router: i18n auth page placement, composed Clerk +
next-intl middleware, expression export handling, env var scaffolding
- React Router: ($locale) prefix detection for auth routes
- TanStack Start: {-$locale} directory detection for auth routes
* docs(init): document new CLI options and update help output
Add --framework, --yes, and --prompt options to init command README
with usage examples. Update root README help output with --verbose
flag documentation.
* refactor(init): address PR review comments
- Extract utility helpers from index.ts into heuristics.ts (jfoshee #18)
- Extract text transformations into transformations.ts (jfoshee #19)
- Fix wrapBodyWithProvider whitespace/formatting bug (jfoshee #20)
- Refactor tests to use semantic path lookups instead of positional
array indexing (jfoshee #13/#17)
- Add doc comment to helpers.ts clarifying shared usage (jfoshee #15)
- Update astro.md NEVER rules to start with "Never" (jfoshee #25)
- Remove section banner comments from init command files
* test(init): add missing framework scaffolder tests
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@dmoerner
, '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

feat: add doctor command for project health checks - #21

Merged
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command
Mar 11, 2026
Merged

feat: add doctor command for project health checks#21
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds clerk doctor command that runs 7 diagnostic checks covering authentication, project linkage, instance validity, environment variables, and CLI configuration
  • Supports --verbose, --json, --spotlight, and --fix options
  • Auto-fix flow prompts to run clerk auth login, clerk link, or clerk env pull for fixable issues, then re-verifies all checks
  • Includes comprehensive test coverage (30 tests, 117 assertions) for all checks and the context factory
  • Uses a defineCheck() builder pattern to eliminate duplicated result construction across check functions
  • Fix factories are lazy — passed as references and only invoked when a result actually needs a fix

Checks

CheckWhat it verifies
Logged inCredential store has a stored token
Authentication validToken is still valid via /oauth/userinfo
Project linkedCurrent git repo/worktree/directory is linked to a Clerk app
Application reachableLinked application ID exists and is accessible on Clerk
Instance IDsDev/prod instance IDs match the application
Environment variables.env.local or .env has Clerk keys
CLI configuration~/.clerk/config.json exists and parses

Test plan

  • Run bun test — all tests pass
  • Run clerk doctor in a linked project — all checks display correctly
  • Run clerk doctor --verbose — detail lines shown
  • Run clerk doctor --json — valid JSON output
  • Run clerk doctor --spotlight — only warnings/failures shown
  • Run clerk doctor --fix — prompts for fixable issues, re-verifies after
  • Run clerk doctor while logged out — authentication checks fail with remedy
  • Run clerk doctor in an unlinked directory — project linkage fails with remedy

Console

clerk doctor
image

clerk doctor --fix
image

@rafa-thayto

Copy link
Copy Markdown
ContributorAuthor

waiting for this lint fixes being merged: #22

Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Runs 8 diagnostic checks covering authentication, project linkage,
instances, git availability, environment variables, and CLI config.
Supports --verbose, --json, --spotlight, and --fix options.
Git is not required by any CLI command — the link command gracefully
falls back to directory-based profile keys when git is unavailable.
…prove tests
- Extract defineCheck() builder pattern to eliminate duplicated result
construction across all 7 check functions
- Make fix factories lazy (passed as references, invoked only when needed)
- Extract AUTH_ERROR_STATUS regex to named constant
- Add expectCheck() test helper and mockProfile fixture to reduce test boilerplate
- Replace non-null assertion with runtime guard in fix loop
- Rename "Linked app exists" check to "Linked application"
@@ -30,7 +30,7 @@ clerk doctor --fix # Offer to auto-fix issues
| Authentication token | Authentication | Credential store has a stored token |
| Token validity | Authentication | Token is still valid (calls `/oauth/userinfo`) |
| Project linkage | Project | Current directory is linked to a Clerk app |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I still find "Linked application" and "Project linkage" a little confusing. The definition of "Project linkage" doesn't use the term "project" but just uses "Clerk app".

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

let me think better on this

do you have any name suggestions?

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/doctor-command branch from d2a598e to 4cac338CompareMarch 11, 2026 16:45

@dmoernerdmoerner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Significantly clearer with the builder/factory, thank you!

I still find the project/application link distinction a little confusing. From the code I see it has to do with git or the directory, perhaps the description can be updated there.

Approved.

@rafa-thayto

rafa-thayto commented Mar 11, 2026

Copy link
Copy Markdown
ContributorAuthor

just updating the PR with the new patterns introduced on #24

Replace process.exit(1) calls with CliError throws to comply with
the unicorn/no-process-exit lint rule and the centralized error
handler in cli.ts. Import PlapiError from its canonical source
(errors.ts) and remove duplicate red export in color.ts.
Rename check titles to be user-focused rather than implementation-focused:
- "Authentication token" → "Logged in"
- "Token validity" → "Authentication valid"
- "Project linkage" → "Project linked"
- "Linked application" → "Application reachable"
- "Instances" → "Instance IDs"
Improve messages to be more explicit about what's happening:
- App reachability pass now includes app ID
- 404 fail says "not found on Clerk" with actionable remedy
- Network failures say "Could not reach Clerk"
- Stale instances say "mismatch ... not found in application"
- Remove redundant app ID from project link (shown in app check)
The check name isn't shown in terminal output, so the prefix is needed
for context.
@rafa-thayto
rafa-thayto merged commit 2d1cf72 into mainMar 11, 2026
2 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/doctor-command branch March 11, 2026 18:03
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
…mpts (#38)
* chore: add magicast dependency for AST-based code manipulation
Used by the init command's framework scaffolders to safely add imports
and modify config files without breaking existing code.
* refactor: fix framework SDK names and detection priority
- Fix @clerk/clerk-react → @clerk/react
- Fix @clerk/tanstack-start → @clerk/tanstack-react-start
- Remove standalone vite detection (covered by react)
- Reorder priority: scaffoldable frameworks first, then expo, react,
express, fastify
- Export readDeps for use by init context module
* feat(init): add project context gathering
Detects framework, TypeScript, src/ directory convention, package
manager, Next.js router variant, middleware filename, layout path,
and existing Clerk SDK installation.
* feat(init): add scaffold system with framework-specific scaffolders
Add idempotent scaffolders for Next.js (App + Pages Router), React,
React Router, Nuxt, TanStack Start, Astro, and Vue. Each scaffolder
generates framework-appropriate boilerplate (middleware, providers,
auth pages) while preserving existing user code.
Shared helpers handle AST-based import injection (magicast with string
fallback), middleware composition, and auth page creation.
* feat(init): add scan, format, preview, and agent prompt modules
- scan: detect competing auth libraries pre-scaffold and scan for
hardcoded keys/leftover imports post-scaffold
- format: run Prettier/Biome on generated files
- preview: show planned file changes and confirm before writing
- prompts: framework-specific agent mode prompts with exact code
snippets and file paths
* feat(init): rewrite init command with scaffold and scan flow
Replace the minimal init (login + link + install + env pull) with a
full orchestrator that also scaffolds framework boilerplate and scans
for issues. Skips login/link when already authenticated and linked.
* docs(init): update README with scaffolding docs and framework table
- Add Agent Mode section
- Add env var column to framework detection table
- Add Expo, Express, Fastify to detection table
- Document scaffolding actions for all 8 supported frameworks
- Note that Expo/Express/Fastify are detected but not scaffolded
* refactor(init): restructure agent prompts into markdown templates
Replace monolithic prompts.ts with individual .md template files per
framework, matching the clerk-docs structured format (sections, rules,
deprecated patterns, verification checklists).
- Move prompts to src/commands/init/prompts/*.md with {{PLACEHOLDER}}
interpolation
- Add prompts for expo, express, and fastify frameworks
- Merge DOCS_URLS + TEMPLATE_MAP into single FRAMEWORK_PROMPTS record
- Extract shared pmInstallCommand, remove duplicate pmAddCommand
- Fix formatter-escaped underscores (\_app, \_\_root) in template loader
- Add "After Setup" section to all prompts (matching clerk-docs)
* refactor(init): make FileAction a discriminated union and extend FrameworkScaffold
FileAction is now a proper discriminated union with a dedicated `skip`
variant that has no `content` field, enforced at the type level. The
FrameworkScaffold interface gains `dep`, `variant`, `minMajorVersion`,
`matches()`, and optional `enrichContext()` so each scaffolder is
self-describing. Enrichment fields (variant, layoutPath,
middlewareBasename) become optional on ProjectContext since they are
populated after gatherContext.
* refactor(init): extract Next.js context enrichment to frameworks/nextjs-context
Move parseNextMajorVersion, detectMiddlewareBasename, detectNextjsVariant,
and detectLayoutPath from context.ts into a dedicated nextjs-context.ts
module. This isolates framework-specific logic behind the enrichContext
hook on FrameworkScaffold rather than coupling it to gatherContext.
Parallelize dirExists calls with Promise.all in both files.
* refactor(init): replace scaffold key map with matches() dispatch and version check
Replace the Record<string, FrameworkScaffold> keyed map with an array
using satisfies and matches()-based lookup. Add enrichProjectContext()
that delegates to each scaffolder's enrichContext hook. Add minMajorVersion
guard that checks the framework dep version before scaffolding.
* refactor(init): add shared helpers and flatten scaffoldNextjsMiddleware
Add parseMajorVersion, insertAfterLastImport, wrapBodyWithProvider, and
resolveNextjsMiddlewareBasename to helpers. Use proper skip FileAction
in scaffoldAuthPage and scaffoldNextjsMiddleware. Flatten nested ifs
with early returns and store BunFile reference once. The middleware
fallback now resolves from the actual Next.js version in deps.
* refactor(init): update all scaffolders with skip actions, dep/matches, and minMajorVersion
Each scaffolder now exports dep, matches(), and minMajorVersion. All skip
cases use the proper FileAction skip variant. Flatten nested ifs with
early returns across scaffoldLayout, scaffoldApp, scaffoldMiddleware, and
scaffoldConfig. Use shared wrapBodyWithProvider (nextjs-app, tanstack-start),
insertAfterLastImport (react-router, vue), and extract wrapWithClerkProvider
in react.ts. Store BunFile references to avoid duplicate creation.
* refactor(init): remove findings cap, precompile regexes, and unexport internal types
Remove MAX_FINDINGS so no context is lost. Convert IGNORE_DIRS to a Set
for O(1) lookups. Precompile CODE_SCANS regexes once at module level.
Inline matchesFramework guard. Convert AuthLibraryScan and CodeScan from
exported interfaces to local types since they are not imported elsewhere.
* refactor(init): replace readFileSync with static text imports for compiled binaries
Use Bun's `import ... with { type: "text" }` to embed markdown prompt
templates at build time. This replaces the runtime readFileSync +
import.meta.dir approach that would break in compiled Bun binaries.
Remove the template cache since imports are already static.
* refactor(init): abstract formatter config into data-driven array
Replace inline if-checks for prettier/biome with a FormatterConfig type
and a FORMATTERS array. The runFormatters loop now iterates the config,
making it trivial to add new formatters.
* test(init): update tests for discriminated union, enrichContext, and version check
Update context tests to call enrichProjectContext separately from
gatherContext and import parseMajorVersion from helpers. Add tests for
scaffold version check (below minimum, meets minimum, Next.js 16 proxy).
Update nextjs-app tests to assert on the skip type instead of skipReason
field. Update scan test to verify all findings are returned without cap.
* chore: update bun.lock with magicast dependency
* refactor(init): extract shared auth and config scaffolding helpers
Add jsxAuthPageContent with type-safe JsxClerkPackage union,
scaffoldConfigFile generic for the find→check→modify pattern,
authComponentName, and inline capitalize. These shared helpers
eliminate duplication across framework scaffolders.
* refactor(init): use shared helpers in framework scaffolders
Replace duplicated authRouteContent/nextjsAuthPageContent with
jsxAuthPageContent, replace per-framework scaffoldConfig with
scaffoldConfigFile in astro, nuxt, and react-router. Remove
unused imports and add enableV8Middleware documentation.
* refactor(init): restructure preview, prompts, and scan modules
Update preview formatting to use switch on discriminated union,
restructure prompts/index.ts with typed template resolution and
variable building, and minor scan.ts cleanup.
* test(init): add framework scaffolder and scan tests
Add tests for nextjs-app (middleware composition, provider wrapping,
src/ paths, proxy basename), react-router (root modification, loader
merge), tanstack-start (base dir detection), and scan (no findings cap).
* chore(init): add markdown type declaration for static text imports
* feat(init): add framework lookup utility with aliases
Export FRAMEWORK_MAP and add lookupFramework() to resolve framework
names or aliases (e.g. "tanstack-start" → "@tanstack/react-start")
along with FRAMEWORK_NAMES for validation and display.
* feat(init): add i18nLocaleDir to ProjectContext type
Add optional i18nLocaleDir field to ProjectContext for frameworks
that detect locale-based routing directories (e.g. [locale], [lang]).
* refactor(init): extract previewPlan display function
Split preview logic into previewPlan() (display only) and
previewAndConfirm() (display + prompt) to support --yes mode
without duplicating the plan rendering code.
* feat(init): add shared scaffolding helpers for auth, env, and i18n
Add reusable helpers for all framework scaffolders:
- authFileSpecs() factory for sign-in/sign-up file generation
- scaffoldEnvVars() and SIGN_ROUTE_ENV_VARS for env file management
- htmlAuthComponentMarkup() and jsxAuthComponentMarkup() with
Tailwind/plain CSS variants
- hasTailwindStyles() for style detection
- i18n middleware detection, composition, and routing helpers
(detectI18nMiddlewareLib, composeWithI18nMiddleware, etc.)
- findFirstDirMatch() for generic directory scanning
* feat(init): detect i18n locale directory in Next.js context
Add detectI18nLocaleDir() to identify App Router locale directories
(e.g. [locale], [lang]) by checking for layout files inside dynamic
segments. Populate i18nLocaleDir in enrichNextjsContext() so
scaffolders can place auth pages inside locale-prefixed paths.
* feat(init): support framework override in context gathering
Accept optional frameworkOverride parameter in gatherContext() to
skip auto-detection when the user specifies --framework explicitly.
* feat(init): wire --framework, --yes, and --prompt CLI options
Register --framework <name>, -y/--yes, and --prompt options on the
init command. Wire them through to framework override resolution,
preview-only plan display, and agent prompt output respectively.
* refactor(init): migrate framework scaffolders to shared helpers
Update all six framework scaffolders (Astro, Next.js App/Pages,
Nuxt, React Router, TanStack Start) to use the shared helpers:
- authFileSpecs() factory for sign-in/sign-up generation
- scaffoldEnvVars() for .env file management
- hasTailwindStyles() for consistent style detection
- i18n-aware auth page placement using locale directory detection
- htmlAuthComponentMarkup()/jsxAuthComponentMarkup() for rendering
* test(init): add i18n detection and locale routing tests
Add tests for i18n support across frameworks:
- Context: locale directory detection ([locale], [lang]) with
layout file validation and src/ convention support
- Next.js App Router: i18n auth page placement, composed Clerk +
next-intl middleware, expression export handling, env var scaffolding
- React Router: ($locale) prefix detection for auth routes
- TanStack Start: {-$locale} directory detection for auth routes
* docs(init): document new CLI options and update help output
Add --framework, --yes, and --prompt options to init command README
with usage examples. Update root README help output with --verbose
flag documentation.
* refactor(init): address PR review comments
- Extract utility helpers from index.ts into heuristics.ts (jfoshee #18)
- Extract text transformations into transformations.ts (jfoshee #19)
- Fix wrapBodyWithProvider whitespace/formatting bug (jfoshee #20)
- Refactor tests to use semantic path lookups instead of positional
array indexing (jfoshee #13/#17)
- Add doc comment to helpers.ts clarifying shared usage (jfoshee #15)
- Update astro.md NEVER rules to start with "Never" (jfoshee #25)
- Remove section banner comments from init command files
* test(init): add missing framework scaffolder tests
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@dmoerner
, '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

feat: add doctor command for project health checks - #21

Merged
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command
Mar 11, 2026
Merged

feat: add doctor command for project health checks#21
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds clerk doctor command that runs 7 diagnostic checks covering authentication, project linkage, instance validity, environment variables, and CLI configuration
  • Supports --verbose, --json, --spotlight, and --fix options
  • Auto-fix flow prompts to run clerk auth login, clerk link, or clerk env pull for fixable issues, then re-verifies all checks
  • Includes comprehensive test coverage (30 tests, 117 assertions) for all checks and the context factory
  • Uses a defineCheck() builder pattern to eliminate duplicated result construction across check functions
  • Fix factories are lazy — passed as references and only invoked when a result actually needs a fix

Checks

CheckWhat it verifies
Logged inCredential store has a stored token
Authentication validToken is still valid via /oauth/userinfo
Project linkedCurrent git repo/worktree/directory is linked to a Clerk app
Application reachableLinked application ID exists and is accessible on Clerk
Instance IDsDev/prod instance IDs match the application
Environment variables.env.local or .env has Clerk keys
CLI configuration~/.clerk/config.json exists and parses

Test plan

  • Run bun test — all tests pass
  • Run clerk doctor in a linked project — all checks display correctly
  • Run clerk doctor --verbose — detail lines shown
  • Run clerk doctor --json — valid JSON output
  • Run clerk doctor --spotlight — only warnings/failures shown
  • Run clerk doctor --fix — prompts for fixable issues, re-verifies after
  • Run clerk doctor while logged out — authentication checks fail with remedy
  • Run clerk doctor in an unlinked directory — project linkage fails with remedy

Console

clerk doctor
image

clerk doctor --fix
image

@rafa-thayto

Copy link
Copy Markdown
ContributorAuthor

waiting for this lint fixes being merged: #22

Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Runs 8 diagnostic checks covering authentication, project linkage,
instances, git availability, environment variables, and CLI config.
Supports --verbose, --json, --spotlight, and --fix options.
Git is not required by any CLI command — the link command gracefully
falls back to directory-based profile keys when git is unavailable.
…prove tests
- Extract defineCheck() builder pattern to eliminate duplicated result
construction across all 7 check functions
- Make fix factories lazy (passed as references, invoked only when needed)
- Extract AUTH_ERROR_STATUS regex to named constant
- Add expectCheck() test helper and mockProfile fixture to reduce test boilerplate
- Replace non-null assertion with runtime guard in fix loop
- Rename "Linked app exists" check to "Linked application"
@@ -30,7 +30,7 @@ clerk doctor --fix # Offer to auto-fix issues
| Authentication token | Authentication | Credential store has a stored token |
| Token validity | Authentication | Token is still valid (calls `/oauth/userinfo`) |
| Project linkage | Project | Current directory is linked to a Clerk app |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I still find "Linked application" and "Project linkage" a little confusing. The definition of "Project linkage" doesn't use the term "project" but just uses "Clerk app".

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

let me think better on this

do you have any name suggestions?

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/doctor-command branch from d2a598e to 4cac338CompareMarch 11, 2026 16:45

@dmoernerdmoerner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Significantly clearer with the builder/factory, thank you!

I still find the project/application link distinction a little confusing. From the code I see it has to do with git or the directory, perhaps the description can be updated there.

Approved.

@rafa-thayto

rafa-thayto commented Mar 11, 2026

Copy link
Copy Markdown
ContributorAuthor

just updating the PR with the new patterns introduced on #24

Replace process.exit(1) calls with CliError throws to comply with
the unicorn/no-process-exit lint rule and the centralized error
handler in cli.ts. Import PlapiError from its canonical source
(errors.ts) and remove duplicate red export in color.ts.
Rename check titles to be user-focused rather than implementation-focused:
- "Authentication token" → "Logged in"
- "Token validity" → "Authentication valid"
- "Project linkage" → "Project linked"
- "Linked application" → "Application reachable"
- "Instances" → "Instance IDs"
Improve messages to be more explicit about what's happening:
- App reachability pass now includes app ID
- 404 fail says "not found on Clerk" with actionable remedy
- Network failures say "Could not reach Clerk"
- Stale instances say "mismatch ... not found in application"
- Remove redundant app ID from project link (shown in app check)
The check name isn't shown in terminal output, so the prefix is needed
for context.
@rafa-thayto
rafa-thayto merged commit 2d1cf72 into mainMar 11, 2026
2 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/doctor-command branch March 11, 2026 18:03
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
…mpts (#38)
* chore: add magicast dependency for AST-based code manipulation
Used by the init command's framework scaffolders to safely add imports
and modify config files without breaking existing code.
* refactor: fix framework SDK names and detection priority
- Fix @clerk/clerk-react → @clerk/react
- Fix @clerk/tanstack-start → @clerk/tanstack-react-start
- Remove standalone vite detection (covered by react)
- Reorder priority: scaffoldable frameworks first, then expo, react,
express, fastify
- Export readDeps for use by init context module
* feat(init): add project context gathering
Detects framework, TypeScript, src/ directory convention, package
manager, Next.js router variant, middleware filename, layout path,
and existing Clerk SDK installation.
* feat(init): add scaffold system with framework-specific scaffolders
Add idempotent scaffolders for Next.js (App + Pages Router), React,
React Router, Nuxt, TanStack Start, Astro, and Vue. Each scaffolder
generates framework-appropriate boilerplate (middleware, providers,
auth pages) while preserving existing user code.
Shared helpers handle AST-based import injection (magicast with string
fallback), middleware composition, and auth page creation.
* feat(init): add scan, format, preview, and agent prompt modules
- scan: detect competing auth libraries pre-scaffold and scan for
hardcoded keys/leftover imports post-scaffold
- format: run Prettier/Biome on generated files
- preview: show planned file changes and confirm before writing
- prompts: framework-specific agent mode prompts with exact code
snippets and file paths
* feat(init): rewrite init command with scaffold and scan flow
Replace the minimal init (login + link + install + env pull) with a
full orchestrator that also scaffolds framework boilerplate and scans
for issues. Skips login/link when already authenticated and linked.
* docs(init): update README with scaffolding docs and framework table
- Add Agent Mode section
- Add env var column to framework detection table
- Add Expo, Express, Fastify to detection table
- Document scaffolding actions for all 8 supported frameworks
- Note that Expo/Express/Fastify are detected but not scaffolded
* refactor(init): restructure agent prompts into markdown templates
Replace monolithic prompts.ts with individual .md template files per
framework, matching the clerk-docs structured format (sections, rules,
deprecated patterns, verification checklists).
- Move prompts to src/commands/init/prompts/*.md with {{PLACEHOLDER}}
interpolation
- Add prompts for expo, express, and fastify frameworks
- Merge DOCS_URLS + TEMPLATE_MAP into single FRAMEWORK_PROMPTS record
- Extract shared pmInstallCommand, remove duplicate pmAddCommand
- Fix formatter-escaped underscores (\_app, \_\_root) in template loader
- Add "After Setup" section to all prompts (matching clerk-docs)
* refactor(init): make FileAction a discriminated union and extend FrameworkScaffold
FileAction is now a proper discriminated union with a dedicated `skip`
variant that has no `content` field, enforced at the type level. The
FrameworkScaffold interface gains `dep`, `variant`, `minMajorVersion`,
`matches()`, and optional `enrichContext()` so each scaffolder is
self-describing. Enrichment fields (variant, layoutPath,
middlewareBasename) become optional on ProjectContext since they are
populated after gatherContext.
* refactor(init): extract Next.js context enrichment to frameworks/nextjs-context
Move parseNextMajorVersion, detectMiddlewareBasename, detectNextjsVariant,
and detectLayoutPath from context.ts into a dedicated nextjs-context.ts
module. This isolates framework-specific logic behind the enrichContext
hook on FrameworkScaffold rather than coupling it to gatherContext.
Parallelize dirExists calls with Promise.all in both files.
* refactor(init): replace scaffold key map with matches() dispatch and version check
Replace the Record<string, FrameworkScaffold> keyed map with an array
using satisfies and matches()-based lookup. Add enrichProjectContext()
that delegates to each scaffolder's enrichContext hook. Add minMajorVersion
guard that checks the framework dep version before scaffolding.
* refactor(init): add shared helpers and flatten scaffoldNextjsMiddleware
Add parseMajorVersion, insertAfterLastImport, wrapBodyWithProvider, and
resolveNextjsMiddlewareBasename to helpers. Use proper skip FileAction
in scaffoldAuthPage and scaffoldNextjsMiddleware. Flatten nested ifs
with early returns and store BunFile reference once. The middleware
fallback now resolves from the actual Next.js version in deps.
* refactor(init): update all scaffolders with skip actions, dep/matches, and minMajorVersion
Each scaffolder now exports dep, matches(), and minMajorVersion. All skip
cases use the proper FileAction skip variant. Flatten nested ifs with
early returns across scaffoldLayout, scaffoldApp, scaffoldMiddleware, and
scaffoldConfig. Use shared wrapBodyWithProvider (nextjs-app, tanstack-start),
insertAfterLastImport (react-router, vue), and extract wrapWithClerkProvider
in react.ts. Store BunFile references to avoid duplicate creation.
* refactor(init): remove findings cap, precompile regexes, and unexport internal types
Remove MAX_FINDINGS so no context is lost. Convert IGNORE_DIRS to a Set
for O(1) lookups. Precompile CODE_SCANS regexes once at module level.
Inline matchesFramework guard. Convert AuthLibraryScan and CodeScan from
exported interfaces to local types since they are not imported elsewhere.
* refactor(init): replace readFileSync with static text imports for compiled binaries
Use Bun's `import ... with { type: "text" }` to embed markdown prompt
templates at build time. This replaces the runtime readFileSync +
import.meta.dir approach that would break in compiled Bun binaries.
Remove the template cache since imports are already static.
* refactor(init): abstract formatter config into data-driven array
Replace inline if-checks for prettier/biome with a FormatterConfig type
and a FORMATTERS array. The runFormatters loop now iterates the config,
making it trivial to add new formatters.
* test(init): update tests for discriminated union, enrichContext, and version check
Update context tests to call enrichProjectContext separately from
gatherContext and import parseMajorVersion from helpers. Add tests for
scaffold version check (below minimum, meets minimum, Next.js 16 proxy).
Update nextjs-app tests to assert on the skip type instead of skipReason
field. Update scan test to verify all findings are returned without cap.
* chore: update bun.lock with magicast dependency
* refactor(init): extract shared auth and config scaffolding helpers
Add jsxAuthPageContent with type-safe JsxClerkPackage union,
scaffoldConfigFile generic for the find→check→modify pattern,
authComponentName, and inline capitalize. These shared helpers
eliminate duplication across framework scaffolders.
* refactor(init): use shared helpers in framework scaffolders
Replace duplicated authRouteContent/nextjsAuthPageContent with
jsxAuthPageContent, replace per-framework scaffoldConfig with
scaffoldConfigFile in astro, nuxt, and react-router. Remove
unused imports and add enableV8Middleware documentation.
* refactor(init): restructure preview, prompts, and scan modules
Update preview formatting to use switch on discriminated union,
restructure prompts/index.ts with typed template resolution and
variable building, and minor scan.ts cleanup.
* test(init): add framework scaffolder and scan tests
Add tests for nextjs-app (middleware composition, provider wrapping,
src/ paths, proxy basename), react-router (root modification, loader
merge), tanstack-start (base dir detection), and scan (no findings cap).
* chore(init): add markdown type declaration for static text imports
* feat(init): add framework lookup utility with aliases
Export FRAMEWORK_MAP and add lookupFramework() to resolve framework
names or aliases (e.g. "tanstack-start" → "@tanstack/react-start")
along with FRAMEWORK_NAMES for validation and display.
* feat(init): add i18nLocaleDir to ProjectContext type
Add optional i18nLocaleDir field to ProjectContext for frameworks
that detect locale-based routing directories (e.g. [locale], [lang]).
* refactor(init): extract previewPlan display function
Split preview logic into previewPlan() (display only) and
previewAndConfirm() (display + prompt) to support --yes mode
without duplicating the plan rendering code.
* feat(init): add shared scaffolding helpers for auth, env, and i18n
Add reusable helpers for all framework scaffolders:
- authFileSpecs() factory for sign-in/sign-up file generation
- scaffoldEnvVars() and SIGN_ROUTE_ENV_VARS for env file management
- htmlAuthComponentMarkup() and jsxAuthComponentMarkup() with
Tailwind/plain CSS variants
- hasTailwindStyles() for style detection
- i18n middleware detection, composition, and routing helpers
(detectI18nMiddlewareLib, composeWithI18nMiddleware, etc.)
- findFirstDirMatch() for generic directory scanning
* feat(init): detect i18n locale directory in Next.js context
Add detectI18nLocaleDir() to identify App Router locale directories
(e.g. [locale], [lang]) by checking for layout files inside dynamic
segments. Populate i18nLocaleDir in enrichNextjsContext() so
scaffolders can place auth pages inside locale-prefixed paths.
* feat(init): support framework override in context gathering
Accept optional frameworkOverride parameter in gatherContext() to
skip auto-detection when the user specifies --framework explicitly.
* feat(init): wire --framework, --yes, and --prompt CLI options
Register --framework <name>, -y/--yes, and --prompt options on the
init command. Wire them through to framework override resolution,
preview-only plan display, and agent prompt output respectively.
* refactor(init): migrate framework scaffolders to shared helpers
Update all six framework scaffolders (Astro, Next.js App/Pages,
Nuxt, React Router, TanStack Start) to use the shared helpers:
- authFileSpecs() factory for sign-in/sign-up generation
- scaffoldEnvVars() for .env file management
- hasTailwindStyles() for consistent style detection
- i18n-aware auth page placement using locale directory detection
- htmlAuthComponentMarkup()/jsxAuthComponentMarkup() for rendering
* test(init): add i18n detection and locale routing tests
Add tests for i18n support across frameworks:
- Context: locale directory detection ([locale], [lang]) with
layout file validation and src/ convention support
- Next.js App Router: i18n auth page placement, composed Clerk +
next-intl middleware, expression export handling, env var scaffolding
- React Router: ($locale) prefix detection for auth routes
- TanStack Start: {-$locale} directory detection for auth routes
* docs(init): document new CLI options and update help output
Add --framework, --yes, and --prompt options to init command README
with usage examples. Update root README help output with --verbose
flag documentation.
* refactor(init): address PR review comments
- Extract utility helpers from index.ts into heuristics.ts (jfoshee #18)
- Extract text transformations into transformations.ts (jfoshee #19)
- Fix wrapBodyWithProvider whitespace/formatting bug (jfoshee #20)
- Refactor tests to use semantic path lookups instead of positional
array indexing (jfoshee #13/#17)
- Add doc comment to helpers.ts clarifying shared usage (jfoshee #15)
- Update astro.md NEVER rules to start with "Never" (jfoshee #25)
- Remove section banner comments from init command files
* test(init): add missing framework scaffolder tests
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@dmoerner
, '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

feat: add doctor command for project health checks - #21

Merged
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command
Mar 11, 2026
Merged

feat: add doctor command for project health checks#21
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds clerk doctor command that runs 7 diagnostic checks covering authentication, project linkage, instance validity, environment variables, and CLI configuration
  • Supports --verbose, --json, --spotlight, and --fix options
  • Auto-fix flow prompts to run clerk auth login, clerk link, or clerk env pull for fixable issues, then re-verifies all checks
  • Includes comprehensive test coverage (30 tests, 117 assertions) for all checks and the context factory
  • Uses a defineCheck() builder pattern to eliminate duplicated result construction across check functions
  • Fix factories are lazy — passed as references and only invoked when a result actually needs a fix

Checks

CheckWhat it verifies
Logged inCredential store has a stored token
Authentication validToken is still valid via /oauth/userinfo
Project linkedCurrent git repo/worktree/directory is linked to a Clerk app
Application reachableLinked application ID exists and is accessible on Clerk
Instance IDsDev/prod instance IDs match the application
Environment variables.env.local or .env has Clerk keys
CLI configuration~/.clerk/config.json exists and parses

Test plan

  • Run bun test — all tests pass
  • Run clerk doctor in a linked project — all checks display correctly
  • Run clerk doctor --verbose — detail lines shown
  • Run clerk doctor --json — valid JSON output
  • Run clerk doctor --spotlight — only warnings/failures shown
  • Run clerk doctor --fix — prompts for fixable issues, re-verifies after
  • Run clerk doctor while logged out — authentication checks fail with remedy
  • Run clerk doctor in an unlinked directory — project linkage fails with remedy

Console

clerk doctor
image

clerk doctor --fix
image

@rafa-thayto

Copy link
Copy Markdown
ContributorAuthor

waiting for this lint fixes being merged: #22

Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Runs 8 diagnostic checks covering authentication, project linkage,
instances, git availability, environment variables, and CLI config.
Supports --verbose, --json, --spotlight, and --fix options.
Git is not required by any CLI command — the link command gracefully
falls back to directory-based profile keys when git is unavailable.
…prove tests
- Extract defineCheck() builder pattern to eliminate duplicated result
construction across all 7 check functions
- Make fix factories lazy (passed as references, invoked only when needed)
- Extract AUTH_ERROR_STATUS regex to named constant
- Add expectCheck() test helper and mockProfile fixture to reduce test boilerplate
- Replace non-null assertion with runtime guard in fix loop
- Rename "Linked app exists" check to "Linked application"
@@ -30,7 +30,7 @@ clerk doctor --fix # Offer to auto-fix issues
| Authentication token | Authentication | Credential store has a stored token |
| Token validity | Authentication | Token is still valid (calls `/oauth/userinfo`) |
| Project linkage | Project | Current directory is linked to a Clerk app |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I still find "Linked application" and "Project linkage" a little confusing. The definition of "Project linkage" doesn't use the term "project" but just uses "Clerk app".

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

let me think better on this

do you have any name suggestions?

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/doctor-command branch from d2a598e to 4cac338CompareMarch 11, 2026 16:45

@dmoernerdmoerner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Significantly clearer with the builder/factory, thank you!

I still find the project/application link distinction a little confusing. From the code I see it has to do with git or the directory, perhaps the description can be updated there.

Approved.

@rafa-thayto

rafa-thayto commented Mar 11, 2026

Copy link
Copy Markdown
ContributorAuthor

just updating the PR with the new patterns introduced on #24

Replace process.exit(1) calls with CliError throws to comply with
the unicorn/no-process-exit lint rule and the centralized error
handler in cli.ts. Import PlapiError from its canonical source
(errors.ts) and remove duplicate red export in color.ts.
Rename check titles to be user-focused rather than implementation-focused:
- "Authentication token" → "Logged in"
- "Token validity" → "Authentication valid"
- "Project linkage" → "Project linked"
- "Linked application" → "Application reachable"
- "Instances" → "Instance IDs"
Improve messages to be more explicit about what's happening:
- App reachability pass now includes app ID
- 404 fail says "not found on Clerk" with actionable remedy
- Network failures say "Could not reach Clerk"
- Stale instances say "mismatch ... not found in application"
- Remove redundant app ID from project link (shown in app check)
The check name isn't shown in terminal output, so the prefix is needed
for context.
@rafa-thayto
rafa-thayto merged commit 2d1cf72 into mainMar 11, 2026
2 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/doctor-command branch March 11, 2026 18:03
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
…mpts (#38)
* chore: add magicast dependency for AST-based code manipulation
Used by the init command's framework scaffolders to safely add imports
and modify config files without breaking existing code.
* refactor: fix framework SDK names and detection priority
- Fix @clerk/clerk-react → @clerk/react
- Fix @clerk/tanstack-start → @clerk/tanstack-react-start
- Remove standalone vite detection (covered by react)
- Reorder priority: scaffoldable frameworks first, then expo, react,
express, fastify
- Export readDeps for use by init context module
* feat(init): add project context gathering
Detects framework, TypeScript, src/ directory convention, package
manager, Next.js router variant, middleware filename, layout path,
and existing Clerk SDK installation.
* feat(init): add scaffold system with framework-specific scaffolders
Add idempotent scaffolders for Next.js (App + Pages Router), React,
React Router, Nuxt, TanStack Start, Astro, and Vue. Each scaffolder
generates framework-appropriate boilerplate (middleware, providers,
auth pages) while preserving existing user code.
Shared helpers handle AST-based import injection (magicast with string
fallback), middleware composition, and auth page creation.
* feat(init): add scan, format, preview, and agent prompt modules
- scan: detect competing auth libraries pre-scaffold and scan for
hardcoded keys/leftover imports post-scaffold
- format: run Prettier/Biome on generated files
- preview: show planned file changes and confirm before writing
- prompts: framework-specific agent mode prompts with exact code
snippets and file paths
* feat(init): rewrite init command with scaffold and scan flow
Replace the minimal init (login + link + install + env pull) with a
full orchestrator that also scaffolds framework boilerplate and scans
for issues. Skips login/link when already authenticated and linked.
* docs(init): update README with scaffolding docs and framework table
- Add Agent Mode section
- Add env var column to framework detection table
- Add Expo, Express, Fastify to detection table
- Document scaffolding actions for all 8 supported frameworks
- Note that Expo/Express/Fastify are detected but not scaffolded
* refactor(init): restructure agent prompts into markdown templates
Replace monolithic prompts.ts with individual .md template files per
framework, matching the clerk-docs structured format (sections, rules,
deprecated patterns, verification checklists).
- Move prompts to src/commands/init/prompts/*.md with {{PLACEHOLDER}}
interpolation
- Add prompts for expo, express, and fastify frameworks
- Merge DOCS_URLS + TEMPLATE_MAP into single FRAMEWORK_PROMPTS record
- Extract shared pmInstallCommand, remove duplicate pmAddCommand
- Fix formatter-escaped underscores (\_app, \_\_root) in template loader
- Add "After Setup" section to all prompts (matching clerk-docs)
* refactor(init): make FileAction a discriminated union and extend FrameworkScaffold
FileAction is now a proper discriminated union with a dedicated `skip`
variant that has no `content` field, enforced at the type level. The
FrameworkScaffold interface gains `dep`, `variant`, `minMajorVersion`,
`matches()`, and optional `enrichContext()` so each scaffolder is
self-describing. Enrichment fields (variant, layoutPath,
middlewareBasename) become optional on ProjectContext since they are
populated after gatherContext.
* refactor(init): extract Next.js context enrichment to frameworks/nextjs-context
Move parseNextMajorVersion, detectMiddlewareBasename, detectNextjsVariant,
and detectLayoutPath from context.ts into a dedicated nextjs-context.ts
module. This isolates framework-specific logic behind the enrichContext
hook on FrameworkScaffold rather than coupling it to gatherContext.
Parallelize dirExists calls with Promise.all in both files.
* refactor(init): replace scaffold key map with matches() dispatch and version check
Replace the Record<string, FrameworkScaffold> keyed map with an array
using satisfies and matches()-based lookup. Add enrichProjectContext()
that delegates to each scaffolder's enrichContext hook. Add minMajorVersion
guard that checks the framework dep version before scaffolding.
* refactor(init): add shared helpers and flatten scaffoldNextjsMiddleware
Add parseMajorVersion, insertAfterLastImport, wrapBodyWithProvider, and
resolveNextjsMiddlewareBasename to helpers. Use proper skip FileAction
in scaffoldAuthPage and scaffoldNextjsMiddleware. Flatten nested ifs
with early returns and store BunFile reference once. The middleware
fallback now resolves from the actual Next.js version in deps.
* refactor(init): update all scaffolders with skip actions, dep/matches, and minMajorVersion
Each scaffolder now exports dep, matches(), and minMajorVersion. All skip
cases use the proper FileAction skip variant. Flatten nested ifs with
early returns across scaffoldLayout, scaffoldApp, scaffoldMiddleware, and
scaffoldConfig. Use shared wrapBodyWithProvider (nextjs-app, tanstack-start),
insertAfterLastImport (react-router, vue), and extract wrapWithClerkProvider
in react.ts. Store BunFile references to avoid duplicate creation.
* refactor(init): remove findings cap, precompile regexes, and unexport internal types
Remove MAX_FINDINGS so no context is lost. Convert IGNORE_DIRS to a Set
for O(1) lookups. Precompile CODE_SCANS regexes once at module level.
Inline matchesFramework guard. Convert AuthLibraryScan and CodeScan from
exported interfaces to local types since they are not imported elsewhere.
* refactor(init): replace readFileSync with static text imports for compiled binaries
Use Bun's `import ... with { type: "text" }` to embed markdown prompt
templates at build time. This replaces the runtime readFileSync +
import.meta.dir approach that would break in compiled Bun binaries.
Remove the template cache since imports are already static.
* refactor(init): abstract formatter config into data-driven array
Replace inline if-checks for prettier/biome with a FormatterConfig type
and a FORMATTERS array. The runFormatters loop now iterates the config,
making it trivial to add new formatters.
* test(init): update tests for discriminated union, enrichContext, and version check
Update context tests to call enrichProjectContext separately from
gatherContext and import parseMajorVersion from helpers. Add tests for
scaffold version check (below minimum, meets minimum, Next.js 16 proxy).
Update nextjs-app tests to assert on the skip type instead of skipReason
field. Update scan test to verify all findings are returned without cap.
* chore: update bun.lock with magicast dependency
* refactor(init): extract shared auth and config scaffolding helpers
Add jsxAuthPageContent with type-safe JsxClerkPackage union,
scaffoldConfigFile generic for the find→check→modify pattern,
authComponentName, and inline capitalize. These shared helpers
eliminate duplication across framework scaffolders.
* refactor(init): use shared helpers in framework scaffolders
Replace duplicated authRouteContent/nextjsAuthPageContent with
jsxAuthPageContent, replace per-framework scaffoldConfig with
scaffoldConfigFile in astro, nuxt, and react-router. Remove
unused imports and add enableV8Middleware documentation.
* refactor(init): restructure preview, prompts, and scan modules
Update preview formatting to use switch on discriminated union,
restructure prompts/index.ts with typed template resolution and
variable building, and minor scan.ts cleanup.
* test(init): add framework scaffolder and scan tests
Add tests for nextjs-app (middleware composition, provider wrapping,
src/ paths, proxy basename), react-router (root modification, loader
merge), tanstack-start (base dir detection), and scan (no findings cap).
* chore(init): add markdown type declaration for static text imports
* feat(init): add framework lookup utility with aliases
Export FRAMEWORK_MAP and add lookupFramework() to resolve framework
names or aliases (e.g. "tanstack-start" → "@tanstack/react-start")
along with FRAMEWORK_NAMES for validation and display.
* feat(init): add i18nLocaleDir to ProjectContext type
Add optional i18nLocaleDir field to ProjectContext for frameworks
that detect locale-based routing directories (e.g. [locale], [lang]).
* refactor(init): extract previewPlan display function
Split preview logic into previewPlan() (display only) and
previewAndConfirm() (display + prompt) to support --yes mode
without duplicating the plan rendering code.
* feat(init): add shared scaffolding helpers for auth, env, and i18n
Add reusable helpers for all framework scaffolders:
- authFileSpecs() factory for sign-in/sign-up file generation
- scaffoldEnvVars() and SIGN_ROUTE_ENV_VARS for env file management
- htmlAuthComponentMarkup() and jsxAuthComponentMarkup() with
Tailwind/plain CSS variants
- hasTailwindStyles() for style detection
- i18n middleware detection, composition, and routing helpers
(detectI18nMiddlewareLib, composeWithI18nMiddleware, etc.)
- findFirstDirMatch() for generic directory scanning
* feat(init): detect i18n locale directory in Next.js context
Add detectI18nLocaleDir() to identify App Router locale directories
(e.g. [locale], [lang]) by checking for layout files inside dynamic
segments. Populate i18nLocaleDir in enrichNextjsContext() so
scaffolders can place auth pages inside locale-prefixed paths.
* feat(init): support framework override in context gathering
Accept optional frameworkOverride parameter in gatherContext() to
skip auto-detection when the user specifies --framework explicitly.
* feat(init): wire --framework, --yes, and --prompt CLI options
Register --framework <name>, -y/--yes, and --prompt options on the
init command. Wire them through to framework override resolution,
preview-only plan display, and agent prompt output respectively.
* refactor(init): migrate framework scaffolders to shared helpers
Update all six framework scaffolders (Astro, Next.js App/Pages,
Nuxt, React Router, TanStack Start) to use the shared helpers:
- authFileSpecs() factory for sign-in/sign-up generation
- scaffoldEnvVars() for .env file management
- hasTailwindStyles() for consistent style detection
- i18n-aware auth page placement using locale directory detection
- htmlAuthComponentMarkup()/jsxAuthComponentMarkup() for rendering
* test(init): add i18n detection and locale routing tests
Add tests for i18n support across frameworks:
- Context: locale directory detection ([locale], [lang]) with
layout file validation and src/ convention support
- Next.js App Router: i18n auth page placement, composed Clerk +
next-intl middleware, expression export handling, env var scaffolding
- React Router: ($locale) prefix detection for auth routes
- TanStack Start: {-$locale} directory detection for auth routes
* docs(init): document new CLI options and update help output
Add --framework, --yes, and --prompt options to init command README
with usage examples. Update root README help output with --verbose
flag documentation.
* refactor(init): address PR review comments
- Extract utility helpers from index.ts into heuristics.ts (jfoshee #18)
- Extract text transformations into transformations.ts (jfoshee #19)
- Fix wrapBodyWithProvider whitespace/formatting bug (jfoshee #20)
- Refactor tests to use semantic path lookups instead of positional
array indexing (jfoshee #13/#17)
- Add doc comment to helpers.ts clarifying shared usage (jfoshee #15)
- Update astro.md NEVER rules to start with "Never" (jfoshee #25)
- Remove section banner comments from init command files
* test(init): add missing framework scaffolder tests
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@dmoerner
, '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

feat: add doctor command for project health checks - #21

Merged
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command
Mar 11, 2026
Merged

feat: add doctor command for project health checks#21
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds clerk doctor command that runs 7 diagnostic checks covering authentication, project linkage, instance validity, environment variables, and CLI configuration
  • Supports --verbose, --json, --spotlight, and --fix options
  • Auto-fix flow prompts to run clerk auth login, clerk link, or clerk env pull for fixable issues, then re-verifies all checks
  • Includes comprehensive test coverage (30 tests, 117 assertions) for all checks and the context factory
  • Uses a defineCheck() builder pattern to eliminate duplicated result construction across check functions
  • Fix factories are lazy — passed as references and only invoked when a result actually needs a fix

Checks

CheckWhat it verifies
Logged inCredential store has a stored token
Authentication validToken is still valid via /oauth/userinfo
Project linkedCurrent git repo/worktree/directory is linked to a Clerk app
Application reachableLinked application ID exists and is accessible on Clerk
Instance IDsDev/prod instance IDs match the application
Environment variables.env.local or .env has Clerk keys
CLI configuration~/.clerk/config.json exists and parses

Test plan

  • Run bun test — all tests pass
  • Run clerk doctor in a linked project — all checks display correctly
  • Run clerk doctor --verbose — detail lines shown
  • Run clerk doctor --json — valid JSON output
  • Run clerk doctor --spotlight — only warnings/failures shown
  • Run clerk doctor --fix — prompts for fixable issues, re-verifies after
  • Run clerk doctor while logged out — authentication checks fail with remedy
  • Run clerk doctor in an unlinked directory — project linkage fails with remedy

Console

clerk doctor
image

clerk doctor --fix
image

@rafa-thayto

Copy link
Copy Markdown
ContributorAuthor

waiting for this lint fixes being merged: #22

Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Runs 8 diagnostic checks covering authentication, project linkage,
instances, git availability, environment variables, and CLI config.
Supports --verbose, --json, --spotlight, and --fix options.
Git is not required by any CLI command — the link command gracefully
falls back to directory-based profile keys when git is unavailable.
…prove tests
- Extract defineCheck() builder pattern to eliminate duplicated result
construction across all 7 check functions
- Make fix factories lazy (passed as references, invoked only when needed)
- Extract AUTH_ERROR_STATUS regex to named constant
- Add expectCheck() test helper and mockProfile fixture to reduce test boilerplate
- Replace non-null assertion with runtime guard in fix loop
- Rename "Linked app exists" check to "Linked application"
@@ -30,7 +30,7 @@ clerk doctor --fix # Offer to auto-fix issues
| Authentication token | Authentication | Credential store has a stored token |
| Token validity | Authentication | Token is still valid (calls `/oauth/userinfo`) |
| Project linkage | Project | Current directory is linked to a Clerk app |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I still find "Linked application" and "Project linkage" a little confusing. The definition of "Project linkage" doesn't use the term "project" but just uses "Clerk app".

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

let me think better on this

do you have any name suggestions?

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/doctor-command branch from d2a598e to 4cac338CompareMarch 11, 2026 16:45

@dmoernerdmoerner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Significantly clearer with the builder/factory, thank you!

I still find the project/application link distinction a little confusing. From the code I see it has to do with git or the directory, perhaps the description can be updated there.

Approved.

@rafa-thayto

rafa-thayto commented Mar 11, 2026

Copy link
Copy Markdown
ContributorAuthor

just updating the PR with the new patterns introduced on #24

Replace process.exit(1) calls with CliError throws to comply with
the unicorn/no-process-exit lint rule and the centralized error
handler in cli.ts. Import PlapiError from its canonical source
(errors.ts) and remove duplicate red export in color.ts.
Rename check titles to be user-focused rather than implementation-focused:
- "Authentication token" → "Logged in"
- "Token validity" → "Authentication valid"
- "Project linkage" → "Project linked"
- "Linked application" → "Application reachable"
- "Instances" → "Instance IDs"
Improve messages to be more explicit about what's happening:
- App reachability pass now includes app ID
- 404 fail says "not found on Clerk" with actionable remedy
- Network failures say "Could not reach Clerk"
- Stale instances say "mismatch ... not found in application"
- Remove redundant app ID from project link (shown in app check)
The check name isn't shown in terminal output, so the prefix is needed
for context.
@rafa-thayto
rafa-thayto merged commit 2d1cf72 into mainMar 11, 2026
2 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/doctor-command branch March 11, 2026 18:03
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
…mpts (#38)
* chore: add magicast dependency for AST-based code manipulation
Used by the init command's framework scaffolders to safely add imports
and modify config files without breaking existing code.
* refactor: fix framework SDK names and detection priority
- Fix @clerk/clerk-react → @clerk/react
- Fix @clerk/tanstack-start → @clerk/tanstack-react-start
- Remove standalone vite detection (covered by react)
- Reorder priority: scaffoldable frameworks first, then expo, react,
express, fastify
- Export readDeps for use by init context module
* feat(init): add project context gathering
Detects framework, TypeScript, src/ directory convention, package
manager, Next.js router variant, middleware filename, layout path,
and existing Clerk SDK installation.
* feat(init): add scaffold system with framework-specific scaffolders
Add idempotent scaffolders for Next.js (App + Pages Router), React,
React Router, Nuxt, TanStack Start, Astro, and Vue. Each scaffolder
generates framework-appropriate boilerplate (middleware, providers,
auth pages) while preserving existing user code.
Shared helpers handle AST-based import injection (magicast with string
fallback), middleware composition, and auth page creation.
* feat(init): add scan, format, preview, and agent prompt modules
- scan: detect competing auth libraries pre-scaffold and scan for
hardcoded keys/leftover imports post-scaffold
- format: run Prettier/Biome on generated files
- preview: show planned file changes and confirm before writing
- prompts: framework-specific agent mode prompts with exact code
snippets and file paths
* feat(init): rewrite init command with scaffold and scan flow
Replace the minimal init (login + link + install + env pull) with a
full orchestrator that also scaffolds framework boilerplate and scans
for issues. Skips login/link when already authenticated and linked.
* docs(init): update README with scaffolding docs and framework table
- Add Agent Mode section
- Add env var column to framework detection table
- Add Expo, Express, Fastify to detection table
- Document scaffolding actions for all 8 supported frameworks
- Note that Expo/Express/Fastify are detected but not scaffolded
* refactor(init): restructure agent prompts into markdown templates
Replace monolithic prompts.ts with individual .md template files per
framework, matching the clerk-docs structured format (sections, rules,
deprecated patterns, verification checklists).
- Move prompts to src/commands/init/prompts/*.md with {{PLACEHOLDER}}
interpolation
- Add prompts for expo, express, and fastify frameworks
- Merge DOCS_URLS + TEMPLATE_MAP into single FRAMEWORK_PROMPTS record
- Extract shared pmInstallCommand, remove duplicate pmAddCommand
- Fix formatter-escaped underscores (\_app, \_\_root) in template loader
- Add "After Setup" section to all prompts (matching clerk-docs)
* refactor(init): make FileAction a discriminated union and extend FrameworkScaffold
FileAction is now a proper discriminated union with a dedicated `skip`
variant that has no `content` field, enforced at the type level. The
FrameworkScaffold interface gains `dep`, `variant`, `minMajorVersion`,
`matches()`, and optional `enrichContext()` so each scaffolder is
self-describing. Enrichment fields (variant, layoutPath,
middlewareBasename) become optional on ProjectContext since they are
populated after gatherContext.
* refactor(init): extract Next.js context enrichment to frameworks/nextjs-context
Move parseNextMajorVersion, detectMiddlewareBasename, detectNextjsVariant,
and detectLayoutPath from context.ts into a dedicated nextjs-context.ts
module. This isolates framework-specific logic behind the enrichContext
hook on FrameworkScaffold rather than coupling it to gatherContext.
Parallelize dirExists calls with Promise.all in both files.
* refactor(init): replace scaffold key map with matches() dispatch and version check
Replace the Record<string, FrameworkScaffold> keyed map with an array
using satisfies and matches()-based lookup. Add enrichProjectContext()
that delegates to each scaffolder's enrichContext hook. Add minMajorVersion
guard that checks the framework dep version before scaffolding.
* refactor(init): add shared helpers and flatten scaffoldNextjsMiddleware
Add parseMajorVersion, insertAfterLastImport, wrapBodyWithProvider, and
resolveNextjsMiddlewareBasename to helpers. Use proper skip FileAction
in scaffoldAuthPage and scaffoldNextjsMiddleware. Flatten nested ifs
with early returns and store BunFile reference once. The middleware
fallback now resolves from the actual Next.js version in deps.
* refactor(init): update all scaffolders with skip actions, dep/matches, and minMajorVersion
Each scaffolder now exports dep, matches(), and minMajorVersion. All skip
cases use the proper FileAction skip variant. Flatten nested ifs with
early returns across scaffoldLayout, scaffoldApp, scaffoldMiddleware, and
scaffoldConfig. Use shared wrapBodyWithProvider (nextjs-app, tanstack-start),
insertAfterLastImport (react-router, vue), and extract wrapWithClerkProvider
in react.ts. Store BunFile references to avoid duplicate creation.
* refactor(init): remove findings cap, precompile regexes, and unexport internal types
Remove MAX_FINDINGS so no context is lost. Convert IGNORE_DIRS to a Set
for O(1) lookups. Precompile CODE_SCANS regexes once at module level.
Inline matchesFramework guard. Convert AuthLibraryScan and CodeScan from
exported interfaces to local types since they are not imported elsewhere.
* refactor(init): replace readFileSync with static text imports for compiled binaries
Use Bun's `import ... with { type: "text" }` to embed markdown prompt
templates at build time. This replaces the runtime readFileSync +
import.meta.dir approach that would break in compiled Bun binaries.
Remove the template cache since imports are already static.
* refactor(init): abstract formatter config into data-driven array
Replace inline if-checks for prettier/biome with a FormatterConfig type
and a FORMATTERS array. The runFormatters loop now iterates the config,
making it trivial to add new formatters.
* test(init): update tests for discriminated union, enrichContext, and version check
Update context tests to call enrichProjectContext separately from
gatherContext and import parseMajorVersion from helpers. Add tests for
scaffold version check (below minimum, meets minimum, Next.js 16 proxy).
Update nextjs-app tests to assert on the skip type instead of skipReason
field. Update scan test to verify all findings are returned without cap.
* chore: update bun.lock with magicast dependency
* refactor(init): extract shared auth and config scaffolding helpers
Add jsxAuthPageContent with type-safe JsxClerkPackage union,
scaffoldConfigFile generic for the find→check→modify pattern,
authComponentName, and inline capitalize. These shared helpers
eliminate duplication across framework scaffolders.
* refactor(init): use shared helpers in framework scaffolders
Replace duplicated authRouteContent/nextjsAuthPageContent with
jsxAuthPageContent, replace per-framework scaffoldConfig with
scaffoldConfigFile in astro, nuxt, and react-router. Remove
unused imports and add enableV8Middleware documentation.
* refactor(init): restructure preview, prompts, and scan modules
Update preview formatting to use switch on discriminated union,
restructure prompts/index.ts with typed template resolution and
variable building, and minor scan.ts cleanup.
* test(init): add framework scaffolder and scan tests
Add tests for nextjs-app (middleware composition, provider wrapping,
src/ paths, proxy basename), react-router (root modification, loader
merge), tanstack-start (base dir detection), and scan (no findings cap).
* chore(init): add markdown type declaration for static text imports
* feat(init): add framework lookup utility with aliases
Export FRAMEWORK_MAP and add lookupFramework() to resolve framework
names or aliases (e.g. "tanstack-start" → "@tanstack/react-start")
along with FRAMEWORK_NAMES for validation and display.
* feat(init): add i18nLocaleDir to ProjectContext type
Add optional i18nLocaleDir field to ProjectContext for frameworks
that detect locale-based routing directories (e.g. [locale], [lang]).
* refactor(init): extract previewPlan display function
Split preview logic into previewPlan() (display only) and
previewAndConfirm() (display + prompt) to support --yes mode
without duplicating the plan rendering code.
* feat(init): add shared scaffolding helpers for auth, env, and i18n
Add reusable helpers for all framework scaffolders:
- authFileSpecs() factory for sign-in/sign-up file generation
- scaffoldEnvVars() and SIGN_ROUTE_ENV_VARS for env file management
- htmlAuthComponentMarkup() and jsxAuthComponentMarkup() with
Tailwind/plain CSS variants
- hasTailwindStyles() for style detection
- i18n middleware detection, composition, and routing helpers
(detectI18nMiddlewareLib, composeWithI18nMiddleware, etc.)
- findFirstDirMatch() for generic directory scanning
* feat(init): detect i18n locale directory in Next.js context
Add detectI18nLocaleDir() to identify App Router locale directories
(e.g. [locale], [lang]) by checking for layout files inside dynamic
segments. Populate i18nLocaleDir in enrichNextjsContext() so
scaffolders can place auth pages inside locale-prefixed paths.
* feat(init): support framework override in context gathering
Accept optional frameworkOverride parameter in gatherContext() to
skip auto-detection when the user specifies --framework explicitly.
* feat(init): wire --framework, --yes, and --prompt CLI options
Register --framework <name>, -y/--yes, and --prompt options on the
init command. Wire them through to framework override resolution,
preview-only plan display, and agent prompt output respectively.
* refactor(init): migrate framework scaffolders to shared helpers
Update all six framework scaffolders (Astro, Next.js App/Pages,
Nuxt, React Router, TanStack Start) to use the shared helpers:
- authFileSpecs() factory for sign-in/sign-up generation
- scaffoldEnvVars() for .env file management
- hasTailwindStyles() for consistent style detection
- i18n-aware auth page placement using locale directory detection
- htmlAuthComponentMarkup()/jsxAuthComponentMarkup() for rendering
* test(init): add i18n detection and locale routing tests
Add tests for i18n support across frameworks:
- Context: locale directory detection ([locale], [lang]) with
layout file validation and src/ convention support
- Next.js App Router: i18n auth page placement, composed Clerk +
next-intl middleware, expression export handling, env var scaffolding
- React Router: ($locale) prefix detection for auth routes
- TanStack Start: {-$locale} directory detection for auth routes
* docs(init): document new CLI options and update help output
Add --framework, --yes, and --prompt options to init command README
with usage examples. Update root README help output with --verbose
flag documentation.
* refactor(init): address PR review comments
- Extract utility helpers from index.ts into heuristics.ts (jfoshee #18)
- Extract text transformations into transformations.ts (jfoshee #19)
- Fix wrapBodyWithProvider whitespace/formatting bug (jfoshee #20)
- Refactor tests to use semantic path lookups instead of positional
array indexing (jfoshee #13/#17)
- Add doc comment to helpers.ts clarifying shared usage (jfoshee #15)
- Update astro.md NEVER rules to start with "Never" (jfoshee #25)
- Remove section banner comments from init command files
* test(init): add missing framework scaffolder tests
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@dmoerner
, '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

feat: add doctor command for project health checks - #21

Merged
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command
Mar 11, 2026
Merged

feat: add doctor command for project health checks#21
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds clerk doctor command that runs 7 diagnostic checks covering authentication, project linkage, instance validity, environment variables, and CLI configuration
  • Supports --verbose, --json, --spotlight, and --fix options
  • Auto-fix flow prompts to run clerk auth login, clerk link, or clerk env pull for fixable issues, then re-verifies all checks
  • Includes comprehensive test coverage (30 tests, 117 assertions) for all checks and the context factory
  • Uses a defineCheck() builder pattern to eliminate duplicated result construction across check functions
  • Fix factories are lazy — passed as references and only invoked when a result actually needs a fix

Checks

CheckWhat it verifies
Logged inCredential store has a stored token
Authentication validToken is still valid via /oauth/userinfo
Project linkedCurrent git repo/worktree/directory is linked to a Clerk app
Application reachableLinked application ID exists and is accessible on Clerk
Instance IDsDev/prod instance IDs match the application
Environment variables.env.local or .env has Clerk keys
CLI configuration~/.clerk/config.json exists and parses

Test plan

  • Run bun test — all tests pass
  • Run clerk doctor in a linked project — all checks display correctly
  • Run clerk doctor --verbose — detail lines shown
  • Run clerk doctor --json — valid JSON output
  • Run clerk doctor --spotlight — only warnings/failures shown
  • Run clerk doctor --fix — prompts for fixable issues, re-verifies after
  • Run clerk doctor while logged out — authentication checks fail with remedy
  • Run clerk doctor in an unlinked directory — project linkage fails with remedy

Console

clerk doctor
image

clerk doctor --fix
image

@rafa-thayto

Copy link
Copy Markdown
ContributorAuthor

waiting for this lint fixes being merged: #22

Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Runs 8 diagnostic checks covering authentication, project linkage,
instances, git availability, environment variables, and CLI config.
Supports --verbose, --json, --spotlight, and --fix options.
Git is not required by any CLI command — the link command gracefully
falls back to directory-based profile keys when git is unavailable.
…prove tests
- Extract defineCheck() builder pattern to eliminate duplicated result
construction across all 7 check functions
- Make fix factories lazy (passed as references, invoked only when needed)
- Extract AUTH_ERROR_STATUS regex to named constant
- Add expectCheck() test helper and mockProfile fixture to reduce test boilerplate
- Replace non-null assertion with runtime guard in fix loop
- Rename "Linked app exists" check to "Linked application"
@@ -30,7 +30,7 @@ clerk doctor --fix # Offer to auto-fix issues
| Authentication token | Authentication | Credential store has a stored token |
| Token validity | Authentication | Token is still valid (calls `/oauth/userinfo`) |
| Project linkage | Project | Current directory is linked to a Clerk app |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I still find "Linked application" and "Project linkage" a little confusing. The definition of "Project linkage" doesn't use the term "project" but just uses "Clerk app".

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

let me think better on this

do you have any name suggestions?

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/doctor-command branch from d2a598e to 4cac338CompareMarch 11, 2026 16:45

@dmoernerdmoerner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Significantly clearer with the builder/factory, thank you!

I still find the project/application link distinction a little confusing. From the code I see it has to do with git or the directory, perhaps the description can be updated there.

Approved.

@rafa-thayto

rafa-thayto commented Mar 11, 2026

Copy link
Copy Markdown
ContributorAuthor

just updating the PR with the new patterns introduced on #24

Replace process.exit(1) calls with CliError throws to comply with
the unicorn/no-process-exit lint rule and the centralized error
handler in cli.ts. Import PlapiError from its canonical source
(errors.ts) and remove duplicate red export in color.ts.
Rename check titles to be user-focused rather than implementation-focused:
- "Authentication token" → "Logged in"
- "Token validity" → "Authentication valid"
- "Project linkage" → "Project linked"
- "Linked application" → "Application reachable"
- "Instances" → "Instance IDs"
Improve messages to be more explicit about what's happening:
- App reachability pass now includes app ID
- 404 fail says "not found on Clerk" with actionable remedy
- Network failures say "Could not reach Clerk"
- Stale instances say "mismatch ... not found in application"
- Remove redundant app ID from project link (shown in app check)
The check name isn't shown in terminal output, so the prefix is needed
for context.
@rafa-thayto
rafa-thayto merged commit 2d1cf72 into mainMar 11, 2026
2 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/doctor-command branch March 11, 2026 18:03
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
…mpts (#38)
* chore: add magicast dependency for AST-based code manipulation
Used by the init command's framework scaffolders to safely add imports
and modify config files without breaking existing code.
* refactor: fix framework SDK names and detection priority
- Fix @clerk/clerk-react → @clerk/react
- Fix @clerk/tanstack-start → @clerk/tanstack-react-start
- Remove standalone vite detection (covered by react)
- Reorder priority: scaffoldable frameworks first, then expo, react,
express, fastify
- Export readDeps for use by init context module
* feat(init): add project context gathering
Detects framework, TypeScript, src/ directory convention, package
manager, Next.js router variant, middleware filename, layout path,
and existing Clerk SDK installation.
* feat(init): add scaffold system with framework-specific scaffolders
Add idempotent scaffolders for Next.js (App + Pages Router), React,
React Router, Nuxt, TanStack Start, Astro, and Vue. Each scaffolder
generates framework-appropriate boilerplate (middleware, providers,
auth pages) while preserving existing user code.
Shared helpers handle AST-based import injection (magicast with string
fallback), middleware composition, and auth page creation.
* feat(init): add scan, format, preview, and agent prompt modules
- scan: detect competing auth libraries pre-scaffold and scan for
hardcoded keys/leftover imports post-scaffold
- format: run Prettier/Biome on generated files
- preview: show planned file changes and confirm before writing
- prompts: framework-specific agent mode prompts with exact code
snippets and file paths
* feat(init): rewrite init command with scaffold and scan flow
Replace the minimal init (login + link + install + env pull) with a
full orchestrator that also scaffolds framework boilerplate and scans
for issues. Skips login/link when already authenticated and linked.
* docs(init): update README with scaffolding docs and framework table
- Add Agent Mode section
- Add env var column to framework detection table
- Add Expo, Express, Fastify to detection table
- Document scaffolding actions for all 8 supported frameworks
- Note that Expo/Express/Fastify are detected but not scaffolded
* refactor(init): restructure agent prompts into markdown templates
Replace monolithic prompts.ts with individual .md template files per
framework, matching the clerk-docs structured format (sections, rules,
deprecated patterns, verification checklists).
- Move prompts to src/commands/init/prompts/*.md with {{PLACEHOLDER}}
interpolation
- Add prompts for expo, express, and fastify frameworks
- Merge DOCS_URLS + TEMPLATE_MAP into single FRAMEWORK_PROMPTS record
- Extract shared pmInstallCommand, remove duplicate pmAddCommand
- Fix formatter-escaped underscores (\_app, \_\_root) in template loader
- Add "After Setup" section to all prompts (matching clerk-docs)
* refactor(init): make FileAction a discriminated union and extend FrameworkScaffold
FileAction is now a proper discriminated union with a dedicated `skip`
variant that has no `content` field, enforced at the type level. The
FrameworkScaffold interface gains `dep`, `variant`, `minMajorVersion`,
`matches()`, and optional `enrichContext()` so each scaffolder is
self-describing. Enrichment fields (variant, layoutPath,
middlewareBasename) become optional on ProjectContext since they are
populated after gatherContext.
* refactor(init): extract Next.js context enrichment to frameworks/nextjs-context
Move parseNextMajorVersion, detectMiddlewareBasename, detectNextjsVariant,
and detectLayoutPath from context.ts into a dedicated nextjs-context.ts
module. This isolates framework-specific logic behind the enrichContext
hook on FrameworkScaffold rather than coupling it to gatherContext.
Parallelize dirExists calls with Promise.all in both files.
* refactor(init): replace scaffold key map with matches() dispatch and version check
Replace the Record<string, FrameworkScaffold> keyed map with an array
using satisfies and matches()-based lookup. Add enrichProjectContext()
that delegates to each scaffolder's enrichContext hook. Add minMajorVersion
guard that checks the framework dep version before scaffolding.
* refactor(init): add shared helpers and flatten scaffoldNextjsMiddleware
Add parseMajorVersion, insertAfterLastImport, wrapBodyWithProvider, and
resolveNextjsMiddlewareBasename to helpers. Use proper skip FileAction
in scaffoldAuthPage and scaffoldNextjsMiddleware. Flatten nested ifs
with early returns and store BunFile reference once. The middleware
fallback now resolves from the actual Next.js version in deps.
* refactor(init): update all scaffolders with skip actions, dep/matches, and minMajorVersion
Each scaffolder now exports dep, matches(), and minMajorVersion. All skip
cases use the proper FileAction skip variant. Flatten nested ifs with
early returns across scaffoldLayout, scaffoldApp, scaffoldMiddleware, and
scaffoldConfig. Use shared wrapBodyWithProvider (nextjs-app, tanstack-start),
insertAfterLastImport (react-router, vue), and extract wrapWithClerkProvider
in react.ts. Store BunFile references to avoid duplicate creation.
* refactor(init): remove findings cap, precompile regexes, and unexport internal types
Remove MAX_FINDINGS so no context is lost. Convert IGNORE_DIRS to a Set
for O(1) lookups. Precompile CODE_SCANS regexes once at module level.
Inline matchesFramework guard. Convert AuthLibraryScan and CodeScan from
exported interfaces to local types since they are not imported elsewhere.
* refactor(init): replace readFileSync with static text imports for compiled binaries
Use Bun's `import ... with { type: "text" }` to embed markdown prompt
templates at build time. This replaces the runtime readFileSync +
import.meta.dir approach that would break in compiled Bun binaries.
Remove the template cache since imports are already static.
* refactor(init): abstract formatter config into data-driven array
Replace inline if-checks for prettier/biome with a FormatterConfig type
and a FORMATTERS array. The runFormatters loop now iterates the config,
making it trivial to add new formatters.
* test(init): update tests for discriminated union, enrichContext, and version check
Update context tests to call enrichProjectContext separately from
gatherContext and import parseMajorVersion from helpers. Add tests for
scaffold version check (below minimum, meets minimum, Next.js 16 proxy).
Update nextjs-app tests to assert on the skip type instead of skipReason
field. Update scan test to verify all findings are returned without cap.
* chore: update bun.lock with magicast dependency
* refactor(init): extract shared auth and config scaffolding helpers
Add jsxAuthPageContent with type-safe JsxClerkPackage union,
scaffoldConfigFile generic for the find→check→modify pattern,
authComponentName, and inline capitalize. These shared helpers
eliminate duplication across framework scaffolders.
* refactor(init): use shared helpers in framework scaffolders
Replace duplicated authRouteContent/nextjsAuthPageContent with
jsxAuthPageContent, replace per-framework scaffoldConfig with
scaffoldConfigFile in astro, nuxt, and react-router. Remove
unused imports and add enableV8Middleware documentation.
* refactor(init): restructure preview, prompts, and scan modules
Update preview formatting to use switch on discriminated union,
restructure prompts/index.ts with typed template resolution and
variable building, and minor scan.ts cleanup.
* test(init): add framework scaffolder and scan tests
Add tests for nextjs-app (middleware composition, provider wrapping,
src/ paths, proxy basename), react-router (root modification, loader
merge), tanstack-start (base dir detection), and scan (no findings cap).
* chore(init): add markdown type declaration for static text imports
* feat(init): add framework lookup utility with aliases
Export FRAMEWORK_MAP and add lookupFramework() to resolve framework
names or aliases (e.g. "tanstack-start" → "@tanstack/react-start")
along with FRAMEWORK_NAMES for validation and display.
* feat(init): add i18nLocaleDir to ProjectContext type
Add optional i18nLocaleDir field to ProjectContext for frameworks
that detect locale-based routing directories (e.g. [locale], [lang]).
* refactor(init): extract previewPlan display function
Split preview logic into previewPlan() (display only) and
previewAndConfirm() (display + prompt) to support --yes mode
without duplicating the plan rendering code.
* feat(init): add shared scaffolding helpers for auth, env, and i18n
Add reusable helpers for all framework scaffolders:
- authFileSpecs() factory for sign-in/sign-up file generation
- scaffoldEnvVars() and SIGN_ROUTE_ENV_VARS for env file management
- htmlAuthComponentMarkup() and jsxAuthComponentMarkup() with
Tailwind/plain CSS variants
- hasTailwindStyles() for style detection
- i18n middleware detection, composition, and routing helpers
(detectI18nMiddlewareLib, composeWithI18nMiddleware, etc.)
- findFirstDirMatch() for generic directory scanning
* feat(init): detect i18n locale directory in Next.js context
Add detectI18nLocaleDir() to identify App Router locale directories
(e.g. [locale], [lang]) by checking for layout files inside dynamic
segments. Populate i18nLocaleDir in enrichNextjsContext() so
scaffolders can place auth pages inside locale-prefixed paths.
* feat(init): support framework override in context gathering
Accept optional frameworkOverride parameter in gatherContext() to
skip auto-detection when the user specifies --framework explicitly.
* feat(init): wire --framework, --yes, and --prompt CLI options
Register --framework <name>, -y/--yes, and --prompt options on the
init command. Wire them through to framework override resolution,
preview-only plan display, and agent prompt output respectively.
* refactor(init): migrate framework scaffolders to shared helpers
Update all six framework scaffolders (Astro, Next.js App/Pages,
Nuxt, React Router, TanStack Start) to use the shared helpers:
- authFileSpecs() factory for sign-in/sign-up generation
- scaffoldEnvVars() for .env file management
- hasTailwindStyles() for consistent style detection
- i18n-aware auth page placement using locale directory detection
- htmlAuthComponentMarkup()/jsxAuthComponentMarkup() for rendering
* test(init): add i18n detection and locale routing tests
Add tests for i18n support across frameworks:
- Context: locale directory detection ([locale], [lang]) with
layout file validation and src/ convention support
- Next.js App Router: i18n auth page placement, composed Clerk +
next-intl middleware, expression export handling, env var scaffolding
- React Router: ($locale) prefix detection for auth routes
- TanStack Start: {-$locale} directory detection for auth routes
* docs(init): document new CLI options and update help output
Add --framework, --yes, and --prompt options to init command README
with usage examples. Update root README help output with --verbose
flag documentation.
* refactor(init): address PR review comments
- Extract utility helpers from index.ts into heuristics.ts (jfoshee #18)
- Extract text transformations into transformations.ts (jfoshee #19)
- Fix wrapBodyWithProvider whitespace/formatting bug (jfoshee #20)
- Refactor tests to use semantic path lookups instead of positional
array indexing (jfoshee #13/#17)
- Add doc comment to helpers.ts clarifying shared usage (jfoshee #15)
- Update astro.md NEVER rules to start with "Never" (jfoshee #25)
- Remove section banner comments from init command files
* test(init): add missing framework scaffolder tests
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@dmoerner
, '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

feat: add doctor command for project health checks - #21

Merged
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command
Mar 11, 2026
Merged

feat: add doctor command for project health checks#21
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds clerk doctor command that runs 7 diagnostic checks covering authentication, project linkage, instance validity, environment variables, and CLI configuration
  • Supports --verbose, --json, --spotlight, and --fix options
  • Auto-fix flow prompts to run clerk auth login, clerk link, or clerk env pull for fixable issues, then re-verifies all checks
  • Includes comprehensive test coverage (30 tests, 117 assertions) for all checks and the context factory
  • Uses a defineCheck() builder pattern to eliminate duplicated result construction across check functions
  • Fix factories are lazy — passed as references and only invoked when a result actually needs a fix

Checks

CheckWhat it verifies
Logged inCredential store has a stored token
Authentication validToken is still valid via /oauth/userinfo
Project linkedCurrent git repo/worktree/directory is linked to a Clerk app
Application reachableLinked application ID exists and is accessible on Clerk
Instance IDsDev/prod instance IDs match the application
Environment variables.env.local or .env has Clerk keys
CLI configuration~/.clerk/config.json exists and parses

Test plan

  • Run bun test — all tests pass
  • Run clerk doctor in a linked project — all checks display correctly
  • Run clerk doctor --verbose — detail lines shown
  • Run clerk doctor --json — valid JSON output
  • Run clerk doctor --spotlight — only warnings/failures shown
  • Run clerk doctor --fix — prompts for fixable issues, re-verifies after
  • Run clerk doctor while logged out — authentication checks fail with remedy
  • Run clerk doctor in an unlinked directory — project linkage fails with remedy

Console

clerk doctor
image

clerk doctor --fix
image

@rafa-thayto

Copy link
Copy Markdown
ContributorAuthor

waiting for this lint fixes being merged: #22

Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Runs 8 diagnostic checks covering authentication, project linkage,
instances, git availability, environment variables, and CLI config.
Supports --verbose, --json, --spotlight, and --fix options.
Git is not required by any CLI command — the link command gracefully
falls back to directory-based profile keys when git is unavailable.
…prove tests
- Extract defineCheck() builder pattern to eliminate duplicated result
construction across all 7 check functions
- Make fix factories lazy (passed as references, invoked only when needed)
- Extract AUTH_ERROR_STATUS regex to named constant
- Add expectCheck() test helper and mockProfile fixture to reduce test boilerplate
- Replace non-null assertion with runtime guard in fix loop
- Rename "Linked app exists" check to "Linked application"
@@ -30,7 +30,7 @@ clerk doctor --fix # Offer to auto-fix issues
| Authentication token | Authentication | Credential store has a stored token |
| Token validity | Authentication | Token is still valid (calls `/oauth/userinfo`) |
| Project linkage | Project | Current directory is linked to a Clerk app |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I still find "Linked application" and "Project linkage" a little confusing. The definition of "Project linkage" doesn't use the term "project" but just uses "Clerk app".

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

let me think better on this

do you have any name suggestions?

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/doctor-command branch from d2a598e to 4cac338CompareMarch 11, 2026 16:45

@dmoernerdmoerner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Significantly clearer with the builder/factory, thank you!

I still find the project/application link distinction a little confusing. From the code I see it has to do with git or the directory, perhaps the description can be updated there.

Approved.

@rafa-thayto

rafa-thayto commented Mar 11, 2026

Copy link
Copy Markdown
ContributorAuthor

just updating the PR with the new patterns introduced on #24

Replace process.exit(1) calls with CliError throws to comply with
the unicorn/no-process-exit lint rule and the centralized error
handler in cli.ts. Import PlapiError from its canonical source
(errors.ts) and remove duplicate red export in color.ts.
Rename check titles to be user-focused rather than implementation-focused:
- "Authentication token" → "Logged in"
- "Token validity" → "Authentication valid"
- "Project linkage" → "Project linked"
- "Linked application" → "Application reachable"
- "Instances" → "Instance IDs"
Improve messages to be more explicit about what's happening:
- App reachability pass now includes app ID
- 404 fail says "not found on Clerk" with actionable remedy
- Network failures say "Could not reach Clerk"
- Stale instances say "mismatch ... not found in application"
- Remove redundant app ID from project link (shown in app check)
The check name isn't shown in terminal output, so the prefix is needed
for context.
@rafa-thayto
rafa-thayto merged commit 2d1cf72 into mainMar 11, 2026
2 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/doctor-command branch March 11, 2026 18:03
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
…mpts (#38)
* chore: add magicast dependency for AST-based code manipulation
Used by the init command's framework scaffolders to safely add imports
and modify config files without breaking existing code.
* refactor: fix framework SDK names and detection priority
- Fix @clerk/clerk-react → @clerk/react
- Fix @clerk/tanstack-start → @clerk/tanstack-react-start
- Remove standalone vite detection (covered by react)
- Reorder priority: scaffoldable frameworks first, then expo, react,
express, fastify
- Export readDeps for use by init context module
* feat(init): add project context gathering
Detects framework, TypeScript, src/ directory convention, package
manager, Next.js router variant, middleware filename, layout path,
and existing Clerk SDK installation.
* feat(init): add scaffold system with framework-specific scaffolders
Add idempotent scaffolders for Next.js (App + Pages Router), React,
React Router, Nuxt, TanStack Start, Astro, and Vue. Each scaffolder
generates framework-appropriate boilerplate (middleware, providers,
auth pages) while preserving existing user code.
Shared helpers handle AST-based import injection (magicast with string
fallback), middleware composition, and auth page creation.
* feat(init): add scan, format, preview, and agent prompt modules
- scan: detect competing auth libraries pre-scaffold and scan for
hardcoded keys/leftover imports post-scaffold
- format: run Prettier/Biome on generated files
- preview: show planned file changes and confirm before writing
- prompts: framework-specific agent mode prompts with exact code
snippets and file paths
* feat(init): rewrite init command with scaffold and scan flow
Replace the minimal init (login + link + install + env pull) with a
full orchestrator that also scaffolds framework boilerplate and scans
for issues. Skips login/link when already authenticated and linked.
* docs(init): update README with scaffolding docs and framework table
- Add Agent Mode section
- Add env var column to framework detection table
- Add Expo, Express, Fastify to detection table
- Document scaffolding actions for all 8 supported frameworks
- Note that Expo/Express/Fastify are detected but not scaffolded
* refactor(init): restructure agent prompts into markdown templates
Replace monolithic prompts.ts with individual .md template files per
framework, matching the clerk-docs structured format (sections, rules,
deprecated patterns, verification checklists).
- Move prompts to src/commands/init/prompts/*.md with {{PLACEHOLDER}}
interpolation
- Add prompts for expo, express, and fastify frameworks
- Merge DOCS_URLS + TEMPLATE_MAP into single FRAMEWORK_PROMPTS record
- Extract shared pmInstallCommand, remove duplicate pmAddCommand
- Fix formatter-escaped underscores (\_app, \_\_root) in template loader
- Add "After Setup" section to all prompts (matching clerk-docs)
* refactor(init): make FileAction a discriminated union and extend FrameworkScaffold
FileAction is now a proper discriminated union with a dedicated `skip`
variant that has no `content` field, enforced at the type level. The
FrameworkScaffold interface gains `dep`, `variant`, `minMajorVersion`,
`matches()`, and optional `enrichContext()` so each scaffolder is
self-describing. Enrichment fields (variant, layoutPath,
middlewareBasename) become optional on ProjectContext since they are
populated after gatherContext.
* refactor(init): extract Next.js context enrichment to frameworks/nextjs-context
Move parseNextMajorVersion, detectMiddlewareBasename, detectNextjsVariant,
and detectLayoutPath from context.ts into a dedicated nextjs-context.ts
module. This isolates framework-specific logic behind the enrichContext
hook on FrameworkScaffold rather than coupling it to gatherContext.
Parallelize dirExists calls with Promise.all in both files.
* refactor(init): replace scaffold key map with matches() dispatch and version check
Replace the Record<string, FrameworkScaffold> keyed map with an array
using satisfies and matches()-based lookup. Add enrichProjectContext()
that delegates to each scaffolder's enrichContext hook. Add minMajorVersion
guard that checks the framework dep version before scaffolding.
* refactor(init): add shared helpers and flatten scaffoldNextjsMiddleware
Add parseMajorVersion, insertAfterLastImport, wrapBodyWithProvider, and
resolveNextjsMiddlewareBasename to helpers. Use proper skip FileAction
in scaffoldAuthPage and scaffoldNextjsMiddleware. Flatten nested ifs
with early returns and store BunFile reference once. The middleware
fallback now resolves from the actual Next.js version in deps.
* refactor(init): update all scaffolders with skip actions, dep/matches, and minMajorVersion
Each scaffolder now exports dep, matches(), and minMajorVersion. All skip
cases use the proper FileAction skip variant. Flatten nested ifs with
early returns across scaffoldLayout, scaffoldApp, scaffoldMiddleware, and
scaffoldConfig. Use shared wrapBodyWithProvider (nextjs-app, tanstack-start),
insertAfterLastImport (react-router, vue), and extract wrapWithClerkProvider
in react.ts. Store BunFile references to avoid duplicate creation.
* refactor(init): remove findings cap, precompile regexes, and unexport internal types
Remove MAX_FINDINGS so no context is lost. Convert IGNORE_DIRS to a Set
for O(1) lookups. Precompile CODE_SCANS regexes once at module level.
Inline matchesFramework guard. Convert AuthLibraryScan and CodeScan from
exported interfaces to local types since they are not imported elsewhere.
* refactor(init): replace readFileSync with static text imports for compiled binaries
Use Bun's `import ... with { type: "text" }` to embed markdown prompt
templates at build time. This replaces the runtime readFileSync +
import.meta.dir approach that would break in compiled Bun binaries.
Remove the template cache since imports are already static.
* refactor(init): abstract formatter config into data-driven array
Replace inline if-checks for prettier/biome with a FormatterConfig type
and a FORMATTERS array. The runFormatters loop now iterates the config,
making it trivial to add new formatters.
* test(init): update tests for discriminated union, enrichContext, and version check
Update context tests to call enrichProjectContext separately from
gatherContext and import parseMajorVersion from helpers. Add tests for
scaffold version check (below minimum, meets minimum, Next.js 16 proxy).
Update nextjs-app tests to assert on the skip type instead of skipReason
field. Update scan test to verify all findings are returned without cap.
* chore: update bun.lock with magicast dependency
* refactor(init): extract shared auth and config scaffolding helpers
Add jsxAuthPageContent with type-safe JsxClerkPackage union,
scaffoldConfigFile generic for the find→check→modify pattern,
authComponentName, and inline capitalize. These shared helpers
eliminate duplication across framework scaffolders.
* refactor(init): use shared helpers in framework scaffolders
Replace duplicated authRouteContent/nextjsAuthPageContent with
jsxAuthPageContent, replace per-framework scaffoldConfig with
scaffoldConfigFile in astro, nuxt, and react-router. Remove
unused imports and add enableV8Middleware documentation.
* refactor(init): restructure preview, prompts, and scan modules
Update preview formatting to use switch on discriminated union,
restructure prompts/index.ts with typed template resolution and
variable building, and minor scan.ts cleanup.
* test(init): add framework scaffolder and scan tests
Add tests for nextjs-app (middleware composition, provider wrapping,
src/ paths, proxy basename), react-router (root modification, loader
merge), tanstack-start (base dir detection), and scan (no findings cap).
* chore(init): add markdown type declaration for static text imports
* feat(init): add framework lookup utility with aliases
Export FRAMEWORK_MAP and add lookupFramework() to resolve framework
names or aliases (e.g. "tanstack-start" → "@tanstack/react-start")
along with FRAMEWORK_NAMES for validation and display.
* feat(init): add i18nLocaleDir to ProjectContext type
Add optional i18nLocaleDir field to ProjectContext for frameworks
that detect locale-based routing directories (e.g. [locale], [lang]).
* refactor(init): extract previewPlan display function
Split preview logic into previewPlan() (display only) and
previewAndConfirm() (display + prompt) to support --yes mode
without duplicating the plan rendering code.
* feat(init): add shared scaffolding helpers for auth, env, and i18n
Add reusable helpers for all framework scaffolders:
- authFileSpecs() factory for sign-in/sign-up file generation
- scaffoldEnvVars() and SIGN_ROUTE_ENV_VARS for env file management
- htmlAuthComponentMarkup() and jsxAuthComponentMarkup() with
Tailwind/plain CSS variants
- hasTailwindStyles() for style detection
- i18n middleware detection, composition, and routing helpers
(detectI18nMiddlewareLib, composeWithI18nMiddleware, etc.)
- findFirstDirMatch() for generic directory scanning
* feat(init): detect i18n locale directory in Next.js context
Add detectI18nLocaleDir() to identify App Router locale directories
(e.g. [locale], [lang]) by checking for layout files inside dynamic
segments. Populate i18nLocaleDir in enrichNextjsContext() so
scaffolders can place auth pages inside locale-prefixed paths.
* feat(init): support framework override in context gathering
Accept optional frameworkOverride parameter in gatherContext() to
skip auto-detection when the user specifies --framework explicitly.
* feat(init): wire --framework, --yes, and --prompt CLI options
Register --framework <name>, -y/--yes, and --prompt options on the
init command. Wire them through to framework override resolution,
preview-only plan display, and agent prompt output respectively.
* refactor(init): migrate framework scaffolders to shared helpers
Update all six framework scaffolders (Astro, Next.js App/Pages,
Nuxt, React Router, TanStack Start) to use the shared helpers:
- authFileSpecs() factory for sign-in/sign-up generation
- scaffoldEnvVars() for .env file management
- hasTailwindStyles() for consistent style detection
- i18n-aware auth page placement using locale directory detection
- htmlAuthComponentMarkup()/jsxAuthComponentMarkup() for rendering
* test(init): add i18n detection and locale routing tests
Add tests for i18n support across frameworks:
- Context: locale directory detection ([locale], [lang]) with
layout file validation and src/ convention support
- Next.js App Router: i18n auth page placement, composed Clerk +
next-intl middleware, expression export handling, env var scaffolding
- React Router: ($locale) prefix detection for auth routes
- TanStack Start: {-$locale} directory detection for auth routes
* docs(init): document new CLI options and update help output
Add --framework, --yes, and --prompt options to init command README
with usage examples. Update root README help output with --verbose
flag documentation.
* refactor(init): address PR review comments
- Extract utility helpers from index.ts into heuristics.ts (jfoshee #18)
- Extract text transformations into transformations.ts (jfoshee #19)
- Fix wrapBodyWithProvider whitespace/formatting bug (jfoshee #20)
- Refactor tests to use semantic path lookups instead of positional
array indexing (jfoshee #13/#17)
- Add doc comment to helpers.ts clarifying shared usage (jfoshee #15)
- Update astro.md NEVER rules to start with "Never" (jfoshee #25)
- Remove section banner comments from init command files
* test(init): add missing framework scaffolder tests
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@dmoerner
, '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

feat: add doctor command for project health checks - #21

Merged
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command
Mar 11, 2026
Merged

feat: add doctor command for project health checks#21
rafa-thayto merged 9 commits into
mainfrom
rafa-thayto/doctor-command

Conversation

@rafa-thayto

@rafa-thaytorafa-thayto commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds clerk doctor command that runs 7 diagnostic checks covering authentication, project linkage, instance validity, environment variables, and CLI configuration
  • Supports --verbose, --json, --spotlight, and --fix options
  • Auto-fix flow prompts to run clerk auth login, clerk link, or clerk env pull for fixable issues, then re-verifies all checks
  • Includes comprehensive test coverage (30 tests, 117 assertions) for all checks and the context factory
  • Uses a defineCheck() builder pattern to eliminate duplicated result construction across check functions
  • Fix factories are lazy — passed as references and only invoked when a result actually needs a fix

Checks

CheckWhat it verifies
Logged inCredential store has a stored token
Authentication validToken is still valid via /oauth/userinfo
Project linkedCurrent git repo/worktree/directory is linked to a Clerk app
Application reachableLinked application ID exists and is accessible on Clerk
Instance IDsDev/prod instance IDs match the application
Environment variables.env.local or .env has Clerk keys
CLI configuration~/.clerk/config.json exists and parses

Test plan

  • Run bun test — all tests pass
  • Run clerk doctor in a linked project — all checks display correctly
  • Run clerk doctor --verbose — detail lines shown
  • Run clerk doctor --json — valid JSON output
  • Run clerk doctor --spotlight — only warnings/failures shown
  • Run clerk doctor --fix — prompts for fixable issues, re-verifies after
  • Run clerk doctor while logged out — authentication checks fail with remedy
  • Run clerk doctor in an unlinked directory — project linkage fails with remedy

Console

clerk doctor
image

clerk doctor --fix
image

@rafa-thayto

Copy link
Copy Markdown
ContributorAuthor

waiting for this lint fixes being merged: #22

Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Comment threadsrc/commands/doctor/checks.ts Outdated
Runs 8 diagnostic checks covering authentication, project linkage,
instances, git availability, environment variables, and CLI config.
Supports --verbose, --json, --spotlight, and --fix options.
Git is not required by any CLI command — the link command gracefully
falls back to directory-based profile keys when git is unavailable.
…prove tests
- Extract defineCheck() builder pattern to eliminate duplicated result
construction across all 7 check functions
- Make fix factories lazy (passed as references, invoked only when needed)
- Extract AUTH_ERROR_STATUS regex to named constant
- Add expectCheck() test helper and mockProfile fixture to reduce test boilerplate
- Replace non-null assertion with runtime guard in fix loop
- Rename "Linked app exists" check to "Linked application"
@@ -30,7 +30,7 @@ clerk doctor --fix # Offer to auto-fix issues
| Authentication token | Authentication | Credential store has a stored token |
| Token validity | Authentication | Token is still valid (calls `/oauth/userinfo`) |
| Project linkage | Project | Current directory is linked to a Clerk app |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I still find "Linked application" and "Project linkage" a little confusing. The definition of "Project linkage" doesn't use the term "project" but just uses "Clerk app".

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

let me think better on this

do you have any name suggestions?

@rafa-thayto
rafa-thaytoforce-pushed the rafa-thayto/doctor-command branch from d2a598e to 4cac338CompareMarch 11, 2026 16:45

@dmoernerdmoerner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Significantly clearer with the builder/factory, thank you!

I still find the project/application link distinction a little confusing. From the code I see it has to do with git or the directory, perhaps the description can be updated there.

Approved.

@rafa-thayto

rafa-thayto commented Mar 11, 2026

Copy link
Copy Markdown
ContributorAuthor

just updating the PR with the new patterns introduced on #24

Replace process.exit(1) calls with CliError throws to comply with
the unicorn/no-process-exit lint rule and the centralized error
handler in cli.ts. Import PlapiError from its canonical source
(errors.ts) and remove duplicate red export in color.ts.
Rename check titles to be user-focused rather than implementation-focused:
- "Authentication token" → "Logged in"
- "Token validity" → "Authentication valid"
- "Project linkage" → "Project linked"
- "Linked application" → "Application reachable"
- "Instances" → "Instance IDs"
Improve messages to be more explicit about what's happening:
- App reachability pass now includes app ID
- 404 fail says "not found on Clerk" with actionable remedy
- Network failures say "Could not reach Clerk"
- Stale instances say "mismatch ... not found in application"
- Remove redundant app ID from project link (shown in app check)
The check name isn't shown in terminal output, so the prefix is needed
for context.
@rafa-thayto
rafa-thayto merged commit 2d1cf72 into mainMar 11, 2026
2 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/doctor-command branch March 11, 2026 18:03
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
rafa-thayto added a commit that referenced this pull request Mar 24, 2026
…mpts (#38)
* chore: add magicast dependency for AST-based code manipulation
Used by the init command's framework scaffolders to safely add imports
and modify config files without breaking existing code.
* refactor: fix framework SDK names and detection priority
- Fix @clerk/clerk-react → @clerk/react
- Fix @clerk/tanstack-start → @clerk/tanstack-react-start
- Remove standalone vite detection (covered by react)
- Reorder priority: scaffoldable frameworks first, then expo, react,
express, fastify
- Export readDeps for use by init context module
* feat(init): add project context gathering
Detects framework, TypeScript, src/ directory convention, package
manager, Next.js router variant, middleware filename, layout path,
and existing Clerk SDK installation.
* feat(init): add scaffold system with framework-specific scaffolders
Add idempotent scaffolders for Next.js (App + Pages Router), React,
React Router, Nuxt, TanStack Start, Astro, and Vue. Each scaffolder
generates framework-appropriate boilerplate (middleware, providers,
auth pages) while preserving existing user code.
Shared helpers handle AST-based import injection (magicast with string
fallback), middleware composition, and auth page creation.
* feat(init): add scan, format, preview, and agent prompt modules
- scan: detect competing auth libraries pre-scaffold and scan for
hardcoded keys/leftover imports post-scaffold
- format: run Prettier/Biome on generated files
- preview: show planned file changes and confirm before writing
- prompts: framework-specific agent mode prompts with exact code
snippets and file paths
* feat(init): rewrite init command with scaffold and scan flow
Replace the minimal init (login + link + install + env pull) with a
full orchestrator that also scaffolds framework boilerplate and scans
for issues. Skips login/link when already authenticated and linked.
* docs(init): update README with scaffolding docs and framework table
- Add Agent Mode section
- Add env var column to framework detection table
- Add Expo, Express, Fastify to detection table
- Document scaffolding actions for all 8 supported frameworks
- Note that Expo/Express/Fastify are detected but not scaffolded
* refactor(init): restructure agent prompts into markdown templates
Replace monolithic prompts.ts with individual .md template files per
framework, matching the clerk-docs structured format (sections, rules,
deprecated patterns, verification checklists).
- Move prompts to src/commands/init/prompts/*.md with {{PLACEHOLDER}}
interpolation
- Add prompts for expo, express, and fastify frameworks
- Merge DOCS_URLS + TEMPLATE_MAP into single FRAMEWORK_PROMPTS record
- Extract shared pmInstallCommand, remove duplicate pmAddCommand
- Fix formatter-escaped underscores (\_app, \_\_root) in template loader
- Add "After Setup" section to all prompts (matching clerk-docs)
* refactor(init): make FileAction a discriminated union and extend FrameworkScaffold
FileAction is now a proper discriminated union with a dedicated `skip`
variant that has no `content` field, enforced at the type level. The
FrameworkScaffold interface gains `dep`, `variant`, `minMajorVersion`,
`matches()`, and optional `enrichContext()` so each scaffolder is
self-describing. Enrichment fields (variant, layoutPath,
middlewareBasename) become optional on ProjectContext since they are
populated after gatherContext.
* refactor(init): extract Next.js context enrichment to frameworks/nextjs-context
Move parseNextMajorVersion, detectMiddlewareBasename, detectNextjsVariant,
and detectLayoutPath from context.ts into a dedicated nextjs-context.ts
module. This isolates framework-specific logic behind the enrichContext
hook on FrameworkScaffold rather than coupling it to gatherContext.
Parallelize dirExists calls with Promise.all in both files.
* refactor(init): replace scaffold key map with matches() dispatch and version check
Replace the Record<string, FrameworkScaffold> keyed map with an array
using satisfies and matches()-based lookup. Add enrichProjectContext()
that delegates to each scaffolder's enrichContext hook. Add minMajorVersion
guard that checks the framework dep version before scaffolding.
* refactor(init): add shared helpers and flatten scaffoldNextjsMiddleware
Add parseMajorVersion, insertAfterLastImport, wrapBodyWithProvider, and
resolveNextjsMiddlewareBasename to helpers. Use proper skip FileAction
in scaffoldAuthPage and scaffoldNextjsMiddleware. Flatten nested ifs
with early returns and store BunFile reference once. The middleware
fallback now resolves from the actual Next.js version in deps.
* refactor(init): update all scaffolders with skip actions, dep/matches, and minMajorVersion
Each scaffolder now exports dep, matches(), and minMajorVersion. All skip
cases use the proper FileAction skip variant. Flatten nested ifs with
early returns across scaffoldLayout, scaffoldApp, scaffoldMiddleware, and
scaffoldConfig. Use shared wrapBodyWithProvider (nextjs-app, tanstack-start),
insertAfterLastImport (react-router, vue), and extract wrapWithClerkProvider
in react.ts. Store BunFile references to avoid duplicate creation.
* refactor(init): remove findings cap, precompile regexes, and unexport internal types
Remove MAX_FINDINGS so no context is lost. Convert IGNORE_DIRS to a Set
for O(1) lookups. Precompile CODE_SCANS regexes once at module level.
Inline matchesFramework guard. Convert AuthLibraryScan and CodeScan from
exported interfaces to local types since they are not imported elsewhere.
* refactor(init): replace readFileSync with static text imports for compiled binaries
Use Bun's `import ... with { type: "text" }` to embed markdown prompt
templates at build time. This replaces the runtime readFileSync +
import.meta.dir approach that would break in compiled Bun binaries.
Remove the template cache since imports are already static.
* refactor(init): abstract formatter config into data-driven array
Replace inline if-checks for prettier/biome with a FormatterConfig type
and a FORMATTERS array. The runFormatters loop now iterates the config,
making it trivial to add new formatters.
* test(init): update tests for discriminated union, enrichContext, and version check
Update context tests to call enrichProjectContext separately from
gatherContext and import parseMajorVersion from helpers. Add tests for
scaffold version check (below minimum, meets minimum, Next.js 16 proxy).
Update nextjs-app tests to assert on the skip type instead of skipReason
field. Update scan test to verify all findings are returned without cap.
* chore: update bun.lock with magicast dependency
* refactor(init): extract shared auth and config scaffolding helpers
Add jsxAuthPageContent with type-safe JsxClerkPackage union,
scaffoldConfigFile generic for the find→check→modify pattern,
authComponentName, and inline capitalize. These shared helpers
eliminate duplication across framework scaffolders.
* refactor(init): use shared helpers in framework scaffolders
Replace duplicated authRouteContent/nextjsAuthPageContent with
jsxAuthPageContent, replace per-framework scaffoldConfig with
scaffoldConfigFile in astro, nuxt, and react-router. Remove
unused imports and add enableV8Middleware documentation.
* refactor(init): restructure preview, prompts, and scan modules
Update preview formatting to use switch on discriminated union,
restructure prompts/index.ts with typed template resolution and
variable building, and minor scan.ts cleanup.
* test(init): add framework scaffolder and scan tests
Add tests for nextjs-app (middleware composition, provider wrapping,
src/ paths, proxy basename), react-router (root modification, loader
merge), tanstack-start (base dir detection), and scan (no findings cap).
* chore(init): add markdown type declaration for static text imports
* feat(init): add framework lookup utility with aliases
Export FRAMEWORK_MAP and add lookupFramework() to resolve framework
names or aliases (e.g. "tanstack-start" → "@tanstack/react-start")
along with FRAMEWORK_NAMES for validation and display.
* feat(init): add i18nLocaleDir to ProjectContext type
Add optional i18nLocaleDir field to ProjectContext for frameworks
that detect locale-based routing directories (e.g. [locale], [lang]).
* refactor(init): extract previewPlan display function
Split preview logic into previewPlan() (display only) and
previewAndConfirm() (display + prompt) to support --yes mode
without duplicating the plan rendering code.
* feat(init): add shared scaffolding helpers for auth, env, and i18n
Add reusable helpers for all framework scaffolders:
- authFileSpecs() factory for sign-in/sign-up file generation
- scaffoldEnvVars() and SIGN_ROUTE_ENV_VARS for env file management
- htmlAuthComponentMarkup() and jsxAuthComponentMarkup() with
Tailwind/plain CSS variants
- hasTailwindStyles() for style detection
- i18n middleware detection, composition, and routing helpers
(detectI18nMiddlewareLib, composeWithI18nMiddleware, etc.)
- findFirstDirMatch() for generic directory scanning
* feat(init): detect i18n locale directory in Next.js context
Add detectI18nLocaleDir() to identify App Router locale directories
(e.g. [locale], [lang]) by checking for layout files inside dynamic
segments. Populate i18nLocaleDir in enrichNextjsContext() so
scaffolders can place auth pages inside locale-prefixed paths.
* feat(init): support framework override in context gathering
Accept optional frameworkOverride parameter in gatherContext() to
skip auto-detection when the user specifies --framework explicitly.
* feat(init): wire --framework, --yes, and --prompt CLI options
Register --framework <name>, -y/--yes, and --prompt options on the
init command. Wire them through to framework override resolution,
preview-only plan display, and agent prompt output respectively.
* refactor(init): migrate framework scaffolders to shared helpers
Update all six framework scaffolders (Astro, Next.js App/Pages,
Nuxt, React Router, TanStack Start) to use the shared helpers:
- authFileSpecs() factory for sign-in/sign-up generation
- scaffoldEnvVars() for .env file management
- hasTailwindStyles() for consistent style detection
- i18n-aware auth page placement using locale directory detection
- htmlAuthComponentMarkup()/jsxAuthComponentMarkup() for rendering
* test(init): add i18n detection and locale routing tests
Add tests for i18n support across frameworks:
- Context: locale directory detection ([locale], [lang]) with
layout file validation and src/ convention support
- Next.js App Router: i18n auth page placement, composed Clerk +
next-intl middleware, expression export handling, env var scaffolding
- React Router: ($locale) prefix detection for auth routes
- TanStack Start: {-$locale} directory detection for auth routes
* docs(init): document new CLI options and update help output
Add --framework, --yes, and --prompt options to init command README
with usage examples. Update root README help output with --verbose
flag documentation.
* refactor(init): address PR review comments
- Extract utility helpers from index.ts into heuristics.ts (jfoshee #18)
- Extract text transformations into transformations.ts (jfoshee #19)
- Fix wrapBodyWithProvider whitespace/formatting bug (jfoshee #20)
- Refactor tests to use semantic path lookups instead of positional
array indexing (jfoshee #13/#17)
- Add doc comment to helpers.ts clarifying shared usage (jfoshee #15)
- Update astro.md NEVER rules to start with "Never" (jfoshee #25)
- Remove section banner comments from init command files
* test(init): add missing framework scaffolder tests
Add test coverage for the 5 framework scaffolders that were missing
tests (jfoshee #21/#22):
- astro.test.ts (10 tests)
- nuxt.test.ts (8 tests)
- vue.test.ts (6 tests)
- react.test.ts (7 tests)
- nextjs-pages.test.ts (10 tests)
All tests use semantic path-based lookups via findAction() helper.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@rafa-thayto@dmoerner