Skip to content

feat(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android#394

Open
rafa-thayto wants to merge 6 commits into
mainfrom
rafa-thayto/aie-1009-clerk-init
Open

feat(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android#394
rafa-thayto wants to merge 6 commits into
mainfrom
rafa-thayto/aie-1009-clerk-init

Conversation

@rafa-thayto

Copy link
Copy Markdown
Contributor

Summary

  • Expo: wrap the expo-router root layout with <ClerkProvider> + secure token cache (existing layouts are modified in place, missing ones created), and add an Expo starter to the bootstrap registry so clerk init --starter --framework expo scaffolds a new app via create-expo-app
  • Express / Fastify: wire clerkMiddleware() / clerkPlugin into the server entry file (ESM and CJS, including inline-require and type-annotated forms) via a shared node-server.ts scaffolder; TypeScript Express apps also get the @clerk/express/env request type augmentation
  • iOS / Android: detect native platforms without a package.json (*.xcodeproj/*.xcworkspace directory bundles, AndroidManifest.xml), accept --framework ios|android, skip the npm bootstrap/install paths, and print the exact SDK quickstart steps (validated against the official Clerk docs, including the SwiftUI .environment(Clerk.shared) step)
  • Hardening (second commit): comment/string masking so quoted or commented-out creation statements can't hijack insertions, ClerkProvider wrapping scoped to the default export (expo-router ErrorBoundary exports are no longer wrapped), multi-line-import-safe insertion, directory-only wildcard markers, and style-consistent generated imports — every fix has a regression test written first

Test Plan

  • bun run format:check / bun run lint / bun run typecheck / bun run test — 2201 tests pass (28 new regression tests on this branch)
  • Hands-on verification: real fixture apps scaffolded by the actual code, dependencies installed, servers started and curled — Express ESM/CJS and Fastify respond 200 with x-clerk-auth-status headers stamped by the live middleware
  • Real create-expo-app run with the registry's exact flags, chained into the scaffolder; tsc --noEmit delta vs pristine template = zero; expo export --platform web bundles and renders with clerk-js booting in-browser
  • Real CLI runs for iOS/Android fixtures in an isolated unauthenticated environment: correct detection, zero file writes, correct guidance, exit 0; negative fixtures (bare Package.swift, build.gradle.kts, stray .xcodeproj file) correctly not detected
  • Scaffold output diffed against the official Clerk quickstarts for all five platforms (matches; the one gap found — the iOS SwiftUI environment step — is fixed here)

Notes

No releasing changes; changeset included (minor). Native platforms are instruction-only by design — their SDKs (SPM/Gradle) can't be safely installed by a JS package manager, so init links the app, pulls keys, and prints the documented setup steps instead.

…astify, ios, and android

- Expo: wrap the expo-router root layout with ClerkProvider + token cache,
  and add an Expo starter to the bootstrap registry (create-expo-app)
- Express/Fastify: wire clerkMiddleware()/clerkPlugin into the server entry
  (ESM and CJS) via a shared node-server scaffolder, plus the @clerk/express
  request type augmentation for TypeScript projects
- iOS/Android: detect native platforms via marker files (*.xcodeproj,
  AndroidManifest.xml), accept --framework ios|android, skip the npm SDK
  install, and print the exact SDK quickstart steps
Fixes found by adversarial review and hands-on verification of the
framework scaffolding, each with a regression test written first:

- mask comments/strings so commented-out or quoted app-creation
  statements can't hijack the Express/Fastify middleware insertion
- scope the Expo ClerkProvider wrap to the default export's body
  (sibling exports like ErrorBoundary are no longer wrapped) and wrap
  the last single-line return instead of the first (guards untouched)
- insertAfterLastImport spans full multi-line import statements
  instead of splicing into them
- skip wiring only when the framework's own SDK is present — an
  unrelated @clerk/* import no longer false-skips
- wildcard native markers (*.xcodeproj/*.xcworkspace) must be
  directories; a stray plain file no longer misdetects iOS
- creation regexes accept type-annotated declarations
  (const app: Express = express())
- emit the quickstart post-instruction when an entry file exists but
  no creation call is found, matching the README
- align added imports with the codebase style (brace spacing, order)
- iOS instructions include the official .environment(Clerk.shared)
  SwiftUI injection step
@changeset-bot

changeset-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 827f498

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
clerk Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6af63d91-b0ce-493b-9cfa-b9be5f324734

📥 Commits

Reviewing files that changed from the base of the PR and between 53b59b6 and 827f498.

📒 Files selected for processing (9)
  • packages/cli-core/src/commands/init/frameworks/expo.ts
  • packages/cli-core/src/commands/init/frameworks/express.ts
  • packages/cli-core/src/commands/init/frameworks/fastify.ts
  • packages/cli-core/src/commands/init/frameworks/node-server.test.ts
  • packages/cli-core/src/commands/init/frameworks/node-server.ts
  • packages/cli-core/src/commands/init/frameworks/source-scan.test.ts
  • packages/cli-core/src/commands/init/frameworks/source-scan.ts
  • packages/cli-core/src/commands/init/frameworks/transformations.test.ts
  • packages/cli-core/src/commands/init/frameworks/transformations.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli-core/src/commands/init/frameworks/node-server.test.ts
  • packages/cli-core/src/commands/init/frameworks/expo.ts

📝 Walkthrough

Walkthrough

The clerk init framework catalog now detects native iOS and Android projects and distinguishes npm-based frameworks from native ecosystems. Scaffolding adds Expo layout setup, Express and Fastify server wiring, TypeScript request augmentation, and native-platform quickstart instructions. Initialization skips npm SDK installation and bootstrap paths for native projects. Expo keyless bootstrapping, framework documentation, tests, and release metadata were also updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested reviewers: wyattjoh

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding init scaffolding support for Expo, Express, Fastify, iOS, and Android.
Description check ✅ Passed The description is directly related to the changeset and accurately outlines the added framework scaffolding and related hardening work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli-core/src/commands/init/frameworks/node-server.ts`:
- Around line 100-102: Update isCommonJs and the “already configured” check in
the initialization flow to inspect the existing masked content rather than raw
content, preventing comments and strings from triggering detection. Compute or
retain masked once before these checks, pass it into isCommonJs, and reuse the
same masked value for the creation-pattern match.

In `@packages/cli-core/src/commands/init/frameworks/transformations.ts`:
- Around line 41-78: Update maskCommentsAndStrings to parse template literals
with nested ${...} substitutions instead of treating every backtick as a simple
string delimiter. Track template-expression brace depth and recursively handle
strings, comments, and nested templates within substitutions, while preserving
masking and source-length alignment so downstream callers such as
insertAfterLastImport, scaffoldServerEntry, wrapLastReturnWithProvider, and
findDefaultExportBody continue matching code after nested templates.

In `@packages/cli-core/src/commands/init/README.md`:
- Around line 90-97: Update the native mobile platform detection introduction in
the README to avoid asserting that native repositories lack package.json files.
State that they may not have one, or focus on marker-based fallback detection
when no npm framework matches, while preserving the existing marker and SDK
installation guidance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 37840c8f-a5a3-4e0c-960b-42e9b98493f0

📥 Commits

Reviewing files that changed from the base of the PR and between 7fb79a6 and 83c0304.

📒 Files selected for processing (23)
  • .changeset/init-framework-scaffolding-gaps.md
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/bootstrap-registry.ts
  • packages/cli-core/src/commands/init/bootstrap.test.ts
  • packages/cli-core/src/commands/init/frameworks/android.test.ts
  • packages/cli-core/src/commands/init/frameworks/android.ts
  • packages/cli-core/src/commands/init/frameworks/expo.test.ts
  • packages/cli-core/src/commands/init/frameworks/expo.ts
  • packages/cli-core/src/commands/init/frameworks/express.test.ts
  • packages/cli-core/src/commands/init/frameworks/express.ts
  • packages/cli-core/src/commands/init/frameworks/fastify.test.ts
  • packages/cli-core/src/commands/init/frameworks/fastify.ts
  • packages/cli-core/src/commands/init/frameworks/ios.test.ts
  • packages/cli-core/src/commands/init/frameworks/ios.ts
  • packages/cli-core/src/commands/init/frameworks/node-server.test.ts
  • packages/cli-core/src/commands/init/frameworks/node-server.ts
  • packages/cli-core/src/commands/init/frameworks/transformations.test.ts
  • packages/cli-core/src/commands/init/frameworks/transformations.ts
  • packages/cli-core/src/commands/init/index.test.ts
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/scaffold.ts
  • packages/cli-core/src/lib/framework.test.ts
  • packages/cli-core/src/lib/framework.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)

Comment thread packages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment thread packages/cli-core/src/commands/init/frameworks/transformations.ts Outdated
Comment thread packages/cli-core/src/commands/init/README.md Outdated
…for CJS detection

- maskCommentsAndStrings now recursively scans `${...}` substitutions
  as real code instead of a naive backtick-to-backtick match, so a
  nested template literal can no longer desynchronize delimiter
  matching and corrupt masking for the rest of the file.
- scaffoldServerEntry's isCommonJs check now runs against masked
  content so a commented-out `require(...)` can't mis-classify a
  server entry file as CommonJS.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli-core/src/commands/init/frameworks/transformations.test.ts`:
- Around line 51-84: Strengthen the maskCommentsAndStrings parameterized
fixtures by adding code-like text after each embedded delimiter: a fake
require(...) after the nested template and identifiers following the
string/comment braces inside ${...}. Update the corresponding mustNotContain or
mustContain assertions so text still inside the template is masked while
REAL_CODE outside it remains preserved, protecting CommonJS detection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 197ec69c-fe1f-43bc-b523-119d06f2b5a2

📥 Commits

Reviewing files that changed from the base of the PR and between 83c0304 and 4df6f57.

📒 Files selected for processing (4)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/frameworks/node-server.ts
  • packages/cli-core/src/commands/init/frameworks/transformations.test.ts
  • packages/cli-core/src/commands/init/frameworks/transformations.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/cli-core/src/commands/init/frameworks/node-server.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/frameworks/transformations.ts

Comment on lines +51 to +84
describe("maskCommentsAndStrings", () => {
test.each([
{
name: "blanks the literal text inside a nested template substitution",
source: "const a = `outer ${`inner`} end`;\nconst REAL_CODE = 1;\n",
mustNotContain: "inner",
mustContain: "REAL_CODE",
},
{
name: "keeps an object literal's braces from ending the substitution early",
source: "const a = `x ${({ b: 1 }).b} y`;\nconst REAL_CODE = 1;\n",
mustNotContain: "x ${",
mustContain: "({ b: 1 }).b",
},
{
name: "handles a string literal inside a substitution",
source: 'const a = `x ${"}"} y`;\nconst REAL_CODE = 1;\n',
mustNotContain: '"}"',
mustContain: "REAL_CODE",
},
{
name: "handles a comment inside a substitution",
source: "const a = `x ${/* } */ 1} y`;\nconst REAL_CODE = 1;\n",
mustNotContain: "} */",
mustContain: "REAL_CODE",
},
])("$name", ({ source, mustNotContain, mustContain }) => {
const masked = maskCommentsAndStrings(source);

expect(masked).not.toContain(mustNotContain);
expect(masked).toContain(mustContain);
expect(masked.length).toBe(source.length);
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that code-like text after embedded delimiters remains masked.

These fixtures mostly check that the delimiter-containing literal/comment is absent and that REAL_CODE after the template survives. A broken scanner could still terminate early and leak text from the remainder of the template while passing these assertions. Add a fake require(...) after the nested template, plus identifiers after the embedded string/comment braces inside ${...}, and assert those are masked/preserved as appropriate. This directly protects the CommonJS-detection hardening described in the PR.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli-core/src/commands/init/frameworks/transformations.test.ts`
around lines 51 - 84, Strengthen the maskCommentsAndStrings parameterized
fixtures by adding code-like text after each embedded delimiter: a fake
require(...) after the nested template and identifiers following the
string/comment braces inside ${...}. Update the corresponding mustNotContain or
mustContain assertions so text still inside the template is masked while
REAL_CODE outside it remains preserved, protecting CommonJS detection.

An apostrophe in ordinary JSX text (`Don't`) or a quote inside a regex
literal (`/"/g`) opened a "string" that never closed, hiding the rest of
the file from the scaffolder. Real JS string literals never span lines,
so a quote with no partner on the same line is punctuation, not a
delimiter; template literals keep their multi-line handling.

Symptoms this fixes, each covered by a regression test:
- an Expo layout containing an apostrophe silently got no ClerkProvider
- with semicolon-less code plus an apostrophe in a trailing comment, the
  Express/Fastify middleware was inserted after `app.listen()`, so auth
  never applied to any route

Both hand-written scanners (findMatchingDelimiter, findStatementEnd) now
reuse the hardened mask instead of duplicating their own quote tracking.
The stock React Router root.tsx ends with a multi-line
`{ Links, Meta, Outlet, Scripts, ScrollRestoration }` import. The old
insertAfterLastImport spliced `export const middleware` into the middle
of that brace block, producing invalid syntax — and the corruption
cascaded, since every later magicast parse then failed and silently fell
back to prepending more imports.

The hardened helper already fixes this; this test fails against the old
implementation and pins the behavior so it can't regress.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/cli-core/src/commands/init/frameworks/transformations.test.ts (2)

106-109: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Close the ${...} substitution in this fixture.

The } in "}" is inside the quoted string, so this source has no substitution-closing brace. The masker therefore consumes the outer backtick and masks REAL_CODE, causing this test to fail instead of testing a string inside a valid substitution.

Proposed fix
-      source: 'const a = `x ${"}" y`;\nconst REAL_CODE = 1;\n',
+      source: 'const a = `x ${"}"} y`;\nconst REAL_CODE = 1;\n',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli-core/src/commands/init/frameworks/transformations.test.ts`
around lines 106 - 109, Update the “handles a string literal inside a
substitution” fixture so the template literal contains a valid closing brace
after the quoted string, while keeping the quoted “}” inside the substitution.
Preserve the existing REAL_CODE and mustNotContain assertions so the test
specifically exercises masking a string literal within a closed substitution.

79-88: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that insertion follows the real import.

This assertion also passes if SNIPPET is prepended to the file—or omitted (indexOf returns -1). Assert the exact expected output or compare against the end of the actual import line.

Proposed fix
-    expect(result.indexOf("SNIPPET")).toBeLessThan(result.indexOf("// import"));
+    expect(result).toBe(`import { a } from "a";
+SNIPPET
+// import { b } from "b"; (removed)
+const x = 1;
+`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli-core/src/commands/init/frameworks/transformations.test.ts`
around lines 79 - 88, Strengthen the test for insertAfterLastImport by asserting
the exact expected output, or verifying that SNIPPET starts immediately after
the real `import { a } from "a";` line and before the comment. Avoid relying
only on index ordering, which also permits prepending or omitting the snippet.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/cli-core/src/commands/init/frameworks/transformations.test.ts`:
- Around line 106-109: Update the “handles a string literal inside a
substitution” fixture so the template literal contains a valid closing brace
after the quoted string, while keeping the quoted “}” inside the substitution.
Preserve the existing REAL_CODE and mustNotContain assertions so the test
specifically exercises masking a string literal within a closed substitution.
- Around line 79-88: Strengthen the test for insertAfterLastImport by asserting
the exact expected output, or verifying that SNIPPET starts immediately after
the real `import { a } from "a";` line and before the comment. Avoid relying
only on index ordering, which also permits prepending or omitting the snippet.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c8101054-00a5-4beb-831d-df9fa450f6d5

📥 Commits

Reviewing files that changed from the base of the PR and between 4df6f57 and d0996ef.

📒 Files selected for processing (7)
  • packages/cli-core/src/commands/init/frameworks/expo.test.ts
  • packages/cli-core/src/commands/init/frameworks/expo.ts
  • packages/cli-core/src/commands/init/frameworks/express.test.ts
  • packages/cli-core/src/commands/init/frameworks/node-server.test.ts
  • packages/cli-core/src/commands/init/frameworks/node-server.ts
  • packages/cli-core/src/commands/init/frameworks/transformations.test.ts
  • packages/cli-core/src/commands/init/frameworks/transformations.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/cli-core/src/commands/init/frameworks/node-server.test.ts
  • packages/cli-core/src/commands/init/frameworks/node-server.ts
  • packages/cli-core/src/commands/init/frameworks/expo.ts
  • packages/cli-core/src/commands/init/frameworks/express.test.ts
  • packages/cli-core/src/commands/init/frameworks/transformations.ts
  • packages/cli-core/src/commands/init/frameworks/expo.test.ts

…r scaffolders

Quality pass over the scaffolding changes on this branch.

- extract maskCommentsAndStrings + findMatchingDelimiter into source-scan.ts.
  The masker is the subtlest code here — it carries an index-alignment
  invariant three modules depend on — and it was sitting in a grab-bag file
  next to Tailwind header markup. expo.ts also kept a private copy of the
  delimiter scanner; there is now one implementation.
- replace needsManualWiring, which drove control flow by matching the prose
  of a skip reason ("Could not find…"), with an explicit `wired` flag
  returned by scaffoldServerEntry. Rewording a user-facing message can no
  longer silently drop a setup instruction.
- move plan assembly into scaffoldServerFramework so express.ts and
  fastify.ts stop duplicating it. The quickstart URL and manual-wiring
  sentence are now config, so fastify.ts is pure configuration and
  express.ts only adds its types/globals.d.ts action.
- findStatementEnd takes masked source like the other scanners instead of
  re-masking content its caller had already masked.

No behavior change: 2217 tests pass, and the real create-expo-app project
still scaffolds to identical output.
@rafa-thayto
rafa-thayto requested a review from wyattjoh July 24, 2026 21:32
Sign up for free to 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.

1 participant