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

Merged
rafa-thayto merged 12 commits into
mainfrom
rafa-thayto/aie-1009-clerk-init
Jul 31, 2026
Merged

feat(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android#394
rafa-thayto merged 12 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.

@changeset-bot

changeset-botBot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: edaef04

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

This PR includes changesets to release 1 package
NameType
clerkMinor

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

coderabbitaiBot 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
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: adding init scaffolding support for Expo, Express, Fastify, iOS, and Android.
Description check✅ PassedThe description is directly related to the changeset and accurately outlines the added framework scaffolding and related hardening work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot 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 threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/transformations.ts Outdated
Comment threadpackages/cli-core/src/commands/init/README.md Outdated

@coderabbitaicoderabbitaiBot 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.

@coderabbitaicoderabbitaiBot 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

…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
…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.
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.
…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-thaytoforce-pushed the rafa-thayto/aie-1009-clerk-init branch from 827f498 to d82d638CompareJuly 26, 2026 12:12

@wyattjohwyattjoh 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.

Nice work on the masking layer in source-scan.ts and the shared node-server.ts scaffolder — the test coverage on the Express/Fastify entry shapes is genuinely thorough. Two things I'd like to see addressed before merge: the ErrorBoundary fix doesn't hold for arrow-function default exports, and the iOS/Android path writes a live secret key into a repo that almost certainly doesn't gitignore it.

Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/lib/framework.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/express.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/ios.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/android.ts Outdated
Review feedback from @wyattjoh on #394.
- findDefaultExportBody only resolved `export default function` and a
reference to a function declaration. The common React Native shape — an
arrow function, inline or via `export default RootLayout` — resolved to
null, and the caller then fell back to scanning the whole file, so
<ClerkProvider> landed in the sibling ErrorBoundary export and the actual
layout shipped unwrapped. Arrow and function-expression bindings now
resolve, and an unresolvable default export fails closed with the
"unsupported shape" skip instead of wrapping whatever return came last.
- the single-line `return <Slot />;` branch matched raw content, so a
trailing comment defeated the greedy `<.*>` and skipped the layout. It
matches the masked slice now, reading the JSX back out of the real source
so string attributes survive, and splices only up to the `;` so the
comment itself is preserved.
- the "Already has ClerkProvider" check ran on raw content, so a leftover
`// TODO: wrap in <ClerkProvider>` skipped the file with no wiring and no
warning. It uses the mask like every other anchor search here.
- entryCandidates grouped every `src/` name ahead of every root name, so an
unrelated `src/app.ts` outranked a root `index.js` and the middleware
landed in a file that never runs. Ordering is by basename now.
- the `--env-file` instruction hardcoded `index.js` even though the entry
path was already resolved; it names the real file.
- Express projects get a post-instruction that types/globals.d.ts must be
covered by the tsconfig `include` — a config scoped to ["src"] never
loads it and the req.auth augmentation silently doesn't apply.
- env pull: skip writing CLERK_SECRET_KEY for non-npm ecosystems (iOS/Android
only ever need the publishable key, and their default .gitignore templates
don't cover .env, so the secret key had nowhere safe to land)
- init: gate the "Install agent skills?" prompt behind isNpmFramework so
native runs don't shell out to npx in a repo with no Node toolchain
- android: fix the lifecycle-viewmodel-compose quickstart step to use
<latest-version> instead of a hardcoded version, matching the
clerk-android-ui line above it
Addresses review feedback from wyattjoh on PR #394.
Extends the e2e suite to the frameworks added in this PR, at the depth each
one supports headless:
- Express / Fastify: hand-authored templates under test/e2e/templates/ (no
official scaffolder exists; the refresh script copies the template and pins
its `latest` specs). Beyond build+typecheck, a new runServerTests helper
starts the scaffolded server (`node --env-file` with type stripping) and
asserts a live request comes back with the x-clerk-auth-status header —
proof the middleware/plugin actually runs with the pulled keys.
- Expo: create-expo-app fixture; `expo export --platform web` as the build so
Metro bundles the ClerkProvider-wrapped layout (expo-secure-store is
pre-pinned since @clerk/expo/token-cache resolves it at bundle time).
No browser test — the export is an app shell, not a sign-in UI.
- iOS / Android: native-init.test.ts with marker-stub fixtures outside the
manifest. Asserts the whole native contract: detection, exit 0, keys pulled
into .env (publishable only — the secret-key question is still open in
review), zero project writes (git status must show exactly `?? .env`), and
the printed SDK quickstart steps.
gitInit/linkProject are now exported from fixture-setup for the native test;
templates are excluded from repo lint/typecheck like fixtures already were.
…heck
The SDK 57 template's CSS-module imports only typecheck through the
expo/types reference in expo-env.d.ts, which `expo start` generates and no
headless command does. Without it the pristine template fails tsc in CI.
Caught by the new expo e2e: the layout is a hoistable function declaration,
so TS won't narrow `string | undefined` through the module-level throw guard
into it, and ClerkProvider's publishableKey prop requires string. `?? ""`
types the const as string while the guard still throws on the empty fallback.
@rafa-thayto
rafa-thayto merged commit bc8b757 into mainJul 31, 2026
19 of 20 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/aie-1009-clerk-init branch July 31, 2026 14:50
@github-actionsgithub-actionsBot mentioned this pull request Jul 31, 2026
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@wyattjoh
, '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(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android - #394

Merged
rafa-thayto merged 12 commits into
mainfrom
rafa-thayto/aie-1009-clerk-init
Jul 31, 2026
Merged

feat(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android#394
rafa-thayto merged 12 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.

@changeset-bot

changeset-botBot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: edaef04

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

This PR includes changesets to release 1 package
NameType
clerkMinor

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

coderabbitaiBot 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
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: adding init scaffolding support for Expo, Express, Fastify, iOS, and Android.
Description check✅ PassedThe description is directly related to the changeset and accurately outlines the added framework scaffolding and related hardening work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot 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 threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/transformations.ts Outdated
Comment threadpackages/cli-core/src/commands/init/README.md Outdated

@coderabbitaicoderabbitaiBot 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.

@coderabbitaicoderabbitaiBot 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

…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
…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.
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.
…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-thaytoforce-pushed the rafa-thayto/aie-1009-clerk-init branch from 827f498 to d82d638CompareJuly 26, 2026 12:12

@wyattjohwyattjoh 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.

Nice work on the masking layer in source-scan.ts and the shared node-server.ts scaffolder — the test coverage on the Express/Fastify entry shapes is genuinely thorough. Two things I'd like to see addressed before merge: the ErrorBoundary fix doesn't hold for arrow-function default exports, and the iOS/Android path writes a live secret key into a repo that almost certainly doesn't gitignore it.

Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/lib/framework.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/express.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/ios.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/android.ts Outdated
Review feedback from @wyattjoh on #394.
- findDefaultExportBody only resolved `export default function` and a
reference to a function declaration. The common React Native shape — an
arrow function, inline or via `export default RootLayout` — resolved to
null, and the caller then fell back to scanning the whole file, so
<ClerkProvider> landed in the sibling ErrorBoundary export and the actual
layout shipped unwrapped. Arrow and function-expression bindings now
resolve, and an unresolvable default export fails closed with the
"unsupported shape" skip instead of wrapping whatever return came last.
- the single-line `return <Slot />;` branch matched raw content, so a
trailing comment defeated the greedy `<.*>` and skipped the layout. It
matches the masked slice now, reading the JSX back out of the real source
so string attributes survive, and splices only up to the `;` so the
comment itself is preserved.
- the "Already has ClerkProvider" check ran on raw content, so a leftover
`// TODO: wrap in <ClerkProvider>` skipped the file with no wiring and no
warning. It uses the mask like every other anchor search here.
- entryCandidates grouped every `src/` name ahead of every root name, so an
unrelated `src/app.ts` outranked a root `index.js` and the middleware
landed in a file that never runs. Ordering is by basename now.
- the `--env-file` instruction hardcoded `index.js` even though the entry
path was already resolved; it names the real file.
- Express projects get a post-instruction that types/globals.d.ts must be
covered by the tsconfig `include` — a config scoped to ["src"] never
loads it and the req.auth augmentation silently doesn't apply.
- env pull: skip writing CLERK_SECRET_KEY for non-npm ecosystems (iOS/Android
only ever need the publishable key, and their default .gitignore templates
don't cover .env, so the secret key had nowhere safe to land)
- init: gate the "Install agent skills?" prompt behind isNpmFramework so
native runs don't shell out to npx in a repo with no Node toolchain
- android: fix the lifecycle-viewmodel-compose quickstart step to use
<latest-version> instead of a hardcoded version, matching the
clerk-android-ui line above it
Addresses review feedback from wyattjoh on PR #394.
Extends the e2e suite to the frameworks added in this PR, at the depth each
one supports headless:
- Express / Fastify: hand-authored templates under test/e2e/templates/ (no
official scaffolder exists; the refresh script copies the template and pins
its `latest` specs). Beyond build+typecheck, a new runServerTests helper
starts the scaffolded server (`node --env-file` with type stripping) and
asserts a live request comes back with the x-clerk-auth-status header —
proof the middleware/plugin actually runs with the pulled keys.
- Expo: create-expo-app fixture; `expo export --platform web` as the build so
Metro bundles the ClerkProvider-wrapped layout (expo-secure-store is
pre-pinned since @clerk/expo/token-cache resolves it at bundle time).
No browser test — the export is an app shell, not a sign-in UI.
- iOS / Android: native-init.test.ts with marker-stub fixtures outside the
manifest. Asserts the whole native contract: detection, exit 0, keys pulled
into .env (publishable only — the secret-key question is still open in
review), zero project writes (git status must show exactly `?? .env`), and
the printed SDK quickstart steps.
gitInit/linkProject are now exported from fixture-setup for the native test;
templates are excluded from repo lint/typecheck like fixtures already were.
…heck
The SDK 57 template's CSS-module imports only typecheck through the
expo/types reference in expo-env.d.ts, which `expo start` generates and no
headless command does. Without it the pristine template fails tsc in CI.
Caught by the new expo e2e: the layout is a hoistable function declaration,
so TS won't narrow `string | undefined` through the module-level throw guard
into it, and ClerkProvider's publishableKey prop requires string. `?? ""`
types the const as string while the guard still throws on the empty fallback.
@rafa-thayto
rafa-thayto merged commit bc8b757 into mainJul 31, 2026
19 of 20 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/aie-1009-clerk-init branch July 31, 2026 14:50
@github-actionsgithub-actionsBot mentioned this pull request Jul 31, 2026
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@wyattjoh
, '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(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android - #394

Merged
rafa-thayto merged 12 commits into
mainfrom
rafa-thayto/aie-1009-clerk-init
Jul 31, 2026
Merged

feat(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android#394
rafa-thayto merged 12 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.

@changeset-bot

changeset-botBot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: edaef04

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

This PR includes changesets to release 1 package
NameType
clerkMinor

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

coderabbitaiBot 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
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: adding init scaffolding support for Expo, Express, Fastify, iOS, and Android.
Description check✅ PassedThe description is directly related to the changeset and accurately outlines the added framework scaffolding and related hardening work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot 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 threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/transformations.ts Outdated
Comment threadpackages/cli-core/src/commands/init/README.md Outdated

@coderabbitaicoderabbitaiBot 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.

@coderabbitaicoderabbitaiBot 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

…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
…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.
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.
…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-thaytoforce-pushed the rafa-thayto/aie-1009-clerk-init branch from 827f498 to d82d638CompareJuly 26, 2026 12:12

@wyattjohwyattjoh 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.

Nice work on the masking layer in source-scan.ts and the shared node-server.ts scaffolder — the test coverage on the Express/Fastify entry shapes is genuinely thorough. Two things I'd like to see addressed before merge: the ErrorBoundary fix doesn't hold for arrow-function default exports, and the iOS/Android path writes a live secret key into a repo that almost certainly doesn't gitignore it.

Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/lib/framework.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/express.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/ios.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/android.ts Outdated
Review feedback from @wyattjoh on #394.
- findDefaultExportBody only resolved `export default function` and a
reference to a function declaration. The common React Native shape — an
arrow function, inline or via `export default RootLayout` — resolved to
null, and the caller then fell back to scanning the whole file, so
<ClerkProvider> landed in the sibling ErrorBoundary export and the actual
layout shipped unwrapped. Arrow and function-expression bindings now
resolve, and an unresolvable default export fails closed with the
"unsupported shape" skip instead of wrapping whatever return came last.
- the single-line `return <Slot />;` branch matched raw content, so a
trailing comment defeated the greedy `<.*>` and skipped the layout. It
matches the masked slice now, reading the JSX back out of the real source
so string attributes survive, and splices only up to the `;` so the
comment itself is preserved.
- the "Already has ClerkProvider" check ran on raw content, so a leftover
`// TODO: wrap in <ClerkProvider>` skipped the file with no wiring and no
warning. It uses the mask like every other anchor search here.
- entryCandidates grouped every `src/` name ahead of every root name, so an
unrelated `src/app.ts` outranked a root `index.js` and the middleware
landed in a file that never runs. Ordering is by basename now.
- the `--env-file` instruction hardcoded `index.js` even though the entry
path was already resolved; it names the real file.
- Express projects get a post-instruction that types/globals.d.ts must be
covered by the tsconfig `include` — a config scoped to ["src"] never
loads it and the req.auth augmentation silently doesn't apply.
- env pull: skip writing CLERK_SECRET_KEY for non-npm ecosystems (iOS/Android
only ever need the publishable key, and their default .gitignore templates
don't cover .env, so the secret key had nowhere safe to land)
- init: gate the "Install agent skills?" prompt behind isNpmFramework so
native runs don't shell out to npx in a repo with no Node toolchain
- android: fix the lifecycle-viewmodel-compose quickstart step to use
<latest-version> instead of a hardcoded version, matching the
clerk-android-ui line above it
Addresses review feedback from wyattjoh on PR #394.
Extends the e2e suite to the frameworks added in this PR, at the depth each
one supports headless:
- Express / Fastify: hand-authored templates under test/e2e/templates/ (no
official scaffolder exists; the refresh script copies the template and pins
its `latest` specs). Beyond build+typecheck, a new runServerTests helper
starts the scaffolded server (`node --env-file` with type stripping) and
asserts a live request comes back with the x-clerk-auth-status header —
proof the middleware/plugin actually runs with the pulled keys.
- Expo: create-expo-app fixture; `expo export --platform web` as the build so
Metro bundles the ClerkProvider-wrapped layout (expo-secure-store is
pre-pinned since @clerk/expo/token-cache resolves it at bundle time).
No browser test — the export is an app shell, not a sign-in UI.
- iOS / Android: native-init.test.ts with marker-stub fixtures outside the
manifest. Asserts the whole native contract: detection, exit 0, keys pulled
into .env (publishable only — the secret-key question is still open in
review), zero project writes (git status must show exactly `?? .env`), and
the printed SDK quickstart steps.
gitInit/linkProject are now exported from fixture-setup for the native test;
templates are excluded from repo lint/typecheck like fixtures already were.
…heck
The SDK 57 template's CSS-module imports only typecheck through the
expo/types reference in expo-env.d.ts, which `expo start` generates and no
headless command does. Without it the pristine template fails tsc in CI.
Caught by the new expo e2e: the layout is a hoistable function declaration,
so TS won't narrow `string | undefined` through the module-level throw guard
into it, and ClerkProvider's publishableKey prop requires string. `?? ""`
types the const as string while the guard still throws on the empty fallback.
@rafa-thayto
rafa-thayto merged commit bc8b757 into mainJul 31, 2026
19 of 20 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/aie-1009-clerk-init branch July 31, 2026 14:50
@github-actionsgithub-actionsBot mentioned this pull request Jul 31, 2026
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@wyattjoh
, '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(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android - #394

Merged
rafa-thayto merged 12 commits into
mainfrom
rafa-thayto/aie-1009-clerk-init
Jul 31, 2026
Merged

feat(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android#394
rafa-thayto merged 12 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.

@changeset-bot

changeset-botBot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: edaef04

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

This PR includes changesets to release 1 package
NameType
clerkMinor

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

coderabbitaiBot 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
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: adding init scaffolding support for Expo, Express, Fastify, iOS, and Android.
Description check✅ PassedThe description is directly related to the changeset and accurately outlines the added framework scaffolding and related hardening work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot 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 threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/transformations.ts Outdated
Comment threadpackages/cli-core/src/commands/init/README.md Outdated

@coderabbitaicoderabbitaiBot 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.

@coderabbitaicoderabbitaiBot 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

…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
…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.
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.
…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-thaytoforce-pushed the rafa-thayto/aie-1009-clerk-init branch from 827f498 to d82d638CompareJuly 26, 2026 12:12

@wyattjohwyattjoh 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.

Nice work on the masking layer in source-scan.ts and the shared node-server.ts scaffolder — the test coverage on the Express/Fastify entry shapes is genuinely thorough. Two things I'd like to see addressed before merge: the ErrorBoundary fix doesn't hold for arrow-function default exports, and the iOS/Android path writes a live secret key into a repo that almost certainly doesn't gitignore it.

Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/lib/framework.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/express.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/ios.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/android.ts Outdated
Review feedback from @wyattjoh on #394.
- findDefaultExportBody only resolved `export default function` and a
reference to a function declaration. The common React Native shape — an
arrow function, inline or via `export default RootLayout` — resolved to
null, and the caller then fell back to scanning the whole file, so
<ClerkProvider> landed in the sibling ErrorBoundary export and the actual
layout shipped unwrapped. Arrow and function-expression bindings now
resolve, and an unresolvable default export fails closed with the
"unsupported shape" skip instead of wrapping whatever return came last.
- the single-line `return <Slot />;` branch matched raw content, so a
trailing comment defeated the greedy `<.*>` and skipped the layout. It
matches the masked slice now, reading the JSX back out of the real source
so string attributes survive, and splices only up to the `;` so the
comment itself is preserved.
- the "Already has ClerkProvider" check ran on raw content, so a leftover
`// TODO: wrap in <ClerkProvider>` skipped the file with no wiring and no
warning. It uses the mask like every other anchor search here.
- entryCandidates grouped every `src/` name ahead of every root name, so an
unrelated `src/app.ts` outranked a root `index.js` and the middleware
landed in a file that never runs. Ordering is by basename now.
- the `--env-file` instruction hardcoded `index.js` even though the entry
path was already resolved; it names the real file.
- Express projects get a post-instruction that types/globals.d.ts must be
covered by the tsconfig `include` — a config scoped to ["src"] never
loads it and the req.auth augmentation silently doesn't apply.
- env pull: skip writing CLERK_SECRET_KEY for non-npm ecosystems (iOS/Android
only ever need the publishable key, and their default .gitignore templates
don't cover .env, so the secret key had nowhere safe to land)
- init: gate the "Install agent skills?" prompt behind isNpmFramework so
native runs don't shell out to npx in a repo with no Node toolchain
- android: fix the lifecycle-viewmodel-compose quickstart step to use
<latest-version> instead of a hardcoded version, matching the
clerk-android-ui line above it
Addresses review feedback from wyattjoh on PR #394.
Extends the e2e suite to the frameworks added in this PR, at the depth each
one supports headless:
- Express / Fastify: hand-authored templates under test/e2e/templates/ (no
official scaffolder exists; the refresh script copies the template and pins
its `latest` specs). Beyond build+typecheck, a new runServerTests helper
starts the scaffolded server (`node --env-file` with type stripping) and
asserts a live request comes back with the x-clerk-auth-status header —
proof the middleware/plugin actually runs with the pulled keys.
- Expo: create-expo-app fixture; `expo export --platform web` as the build so
Metro bundles the ClerkProvider-wrapped layout (expo-secure-store is
pre-pinned since @clerk/expo/token-cache resolves it at bundle time).
No browser test — the export is an app shell, not a sign-in UI.
- iOS / Android: native-init.test.ts with marker-stub fixtures outside the
manifest. Asserts the whole native contract: detection, exit 0, keys pulled
into .env (publishable only — the secret-key question is still open in
review), zero project writes (git status must show exactly `?? .env`), and
the printed SDK quickstart steps.
gitInit/linkProject are now exported from fixture-setup for the native test;
templates are excluded from repo lint/typecheck like fixtures already were.
…heck
The SDK 57 template's CSS-module imports only typecheck through the
expo/types reference in expo-env.d.ts, which `expo start` generates and no
headless command does. Without it the pristine template fails tsc in CI.
Caught by the new expo e2e: the layout is a hoistable function declaration,
so TS won't narrow `string | undefined` through the module-level throw guard
into it, and ClerkProvider's publishableKey prop requires string. `?? ""`
types the const as string while the guard still throws on the empty fallback.
@rafa-thayto
rafa-thayto merged commit bc8b757 into mainJul 31, 2026
19 of 20 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/aie-1009-clerk-init branch July 31, 2026 14:50
@github-actionsgithub-actionsBot mentioned this pull request Jul 31, 2026
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@wyattjoh
, '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(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android - #394

Merged
rafa-thayto merged 12 commits into
mainfrom
rafa-thayto/aie-1009-clerk-init
Jul 31, 2026
Merged

feat(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android#394
rafa-thayto merged 12 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.

@changeset-bot

changeset-botBot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: edaef04

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

This PR includes changesets to release 1 package
NameType
clerkMinor

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

coderabbitaiBot 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
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: adding init scaffolding support for Expo, Express, Fastify, iOS, and Android.
Description check✅ PassedThe description is directly related to the changeset and accurately outlines the added framework scaffolding and related hardening work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot 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 threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/transformations.ts Outdated
Comment threadpackages/cli-core/src/commands/init/README.md Outdated

@coderabbitaicoderabbitaiBot 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.

@coderabbitaicoderabbitaiBot 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

…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
…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.
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.
…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-thaytoforce-pushed the rafa-thayto/aie-1009-clerk-init branch from 827f498 to d82d638CompareJuly 26, 2026 12:12

@wyattjohwyattjoh 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.

Nice work on the masking layer in source-scan.ts and the shared node-server.ts scaffolder — the test coverage on the Express/Fastify entry shapes is genuinely thorough. Two things I'd like to see addressed before merge: the ErrorBoundary fix doesn't hold for arrow-function default exports, and the iOS/Android path writes a live secret key into a repo that almost certainly doesn't gitignore it.

Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/lib/framework.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/express.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/ios.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/android.ts Outdated
Review feedback from @wyattjoh on #394.
- findDefaultExportBody only resolved `export default function` and a
reference to a function declaration. The common React Native shape — an
arrow function, inline or via `export default RootLayout` — resolved to
null, and the caller then fell back to scanning the whole file, so
<ClerkProvider> landed in the sibling ErrorBoundary export and the actual
layout shipped unwrapped. Arrow and function-expression bindings now
resolve, and an unresolvable default export fails closed with the
"unsupported shape" skip instead of wrapping whatever return came last.
- the single-line `return <Slot />;` branch matched raw content, so a
trailing comment defeated the greedy `<.*>` and skipped the layout. It
matches the masked slice now, reading the JSX back out of the real source
so string attributes survive, and splices only up to the `;` so the
comment itself is preserved.
- the "Already has ClerkProvider" check ran on raw content, so a leftover
`// TODO: wrap in <ClerkProvider>` skipped the file with no wiring and no
warning. It uses the mask like every other anchor search here.
- entryCandidates grouped every `src/` name ahead of every root name, so an
unrelated `src/app.ts` outranked a root `index.js` and the middleware
landed in a file that never runs. Ordering is by basename now.
- the `--env-file` instruction hardcoded `index.js` even though the entry
path was already resolved; it names the real file.
- Express projects get a post-instruction that types/globals.d.ts must be
covered by the tsconfig `include` — a config scoped to ["src"] never
loads it and the req.auth augmentation silently doesn't apply.
- env pull: skip writing CLERK_SECRET_KEY for non-npm ecosystems (iOS/Android
only ever need the publishable key, and their default .gitignore templates
don't cover .env, so the secret key had nowhere safe to land)
- init: gate the "Install agent skills?" prompt behind isNpmFramework so
native runs don't shell out to npx in a repo with no Node toolchain
- android: fix the lifecycle-viewmodel-compose quickstart step to use
<latest-version> instead of a hardcoded version, matching the
clerk-android-ui line above it
Addresses review feedback from wyattjoh on PR #394.
Extends the e2e suite to the frameworks added in this PR, at the depth each
one supports headless:
- Express / Fastify: hand-authored templates under test/e2e/templates/ (no
official scaffolder exists; the refresh script copies the template and pins
its `latest` specs). Beyond build+typecheck, a new runServerTests helper
starts the scaffolded server (`node --env-file` with type stripping) and
asserts a live request comes back with the x-clerk-auth-status header —
proof the middleware/plugin actually runs with the pulled keys.
- Expo: create-expo-app fixture; `expo export --platform web` as the build so
Metro bundles the ClerkProvider-wrapped layout (expo-secure-store is
pre-pinned since @clerk/expo/token-cache resolves it at bundle time).
No browser test — the export is an app shell, not a sign-in UI.
- iOS / Android: native-init.test.ts with marker-stub fixtures outside the
manifest. Asserts the whole native contract: detection, exit 0, keys pulled
into .env (publishable only — the secret-key question is still open in
review), zero project writes (git status must show exactly `?? .env`), and
the printed SDK quickstart steps.
gitInit/linkProject are now exported from fixture-setup for the native test;
templates are excluded from repo lint/typecheck like fixtures already were.
…heck
The SDK 57 template's CSS-module imports only typecheck through the
expo/types reference in expo-env.d.ts, which `expo start` generates and no
headless command does. Without it the pristine template fails tsc in CI.
Caught by the new expo e2e: the layout is a hoistable function declaration,
so TS won't narrow `string | undefined` through the module-level throw guard
into it, and ClerkProvider's publishableKey prop requires string. `?? ""`
types the const as string while the guard still throws on the empty fallback.
@rafa-thayto
rafa-thayto merged commit bc8b757 into mainJul 31, 2026
19 of 20 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/aie-1009-clerk-init branch July 31, 2026 14:50
@github-actionsgithub-actionsBot mentioned this pull request Jul 31, 2026
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@wyattjoh
, '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(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android - #394

Merged
rafa-thayto merged 12 commits into
mainfrom
rafa-thayto/aie-1009-clerk-init
Jul 31, 2026
Merged

feat(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android#394
rafa-thayto merged 12 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.

@changeset-bot

changeset-botBot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: edaef04

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

This PR includes changesets to release 1 package
NameType
clerkMinor

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

coderabbitaiBot 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
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: adding init scaffolding support for Expo, Express, Fastify, iOS, and Android.
Description check✅ PassedThe description is directly related to the changeset and accurately outlines the added framework scaffolding and related hardening work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot 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 threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/transformations.ts Outdated
Comment threadpackages/cli-core/src/commands/init/README.md Outdated

@coderabbitaicoderabbitaiBot 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.

@coderabbitaicoderabbitaiBot 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

…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
…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.
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.
…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-thaytoforce-pushed the rafa-thayto/aie-1009-clerk-init branch from 827f498 to d82d638CompareJuly 26, 2026 12:12

@wyattjohwyattjoh 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.

Nice work on the masking layer in source-scan.ts and the shared node-server.ts scaffolder — the test coverage on the Express/Fastify entry shapes is genuinely thorough. Two things I'd like to see addressed before merge: the ErrorBoundary fix doesn't hold for arrow-function default exports, and the iOS/Android path writes a live secret key into a repo that almost certainly doesn't gitignore it.

Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/lib/framework.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/express.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/ios.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/android.ts Outdated
Review feedback from @wyattjoh on #394.
- findDefaultExportBody only resolved `export default function` and a
reference to a function declaration. The common React Native shape — an
arrow function, inline or via `export default RootLayout` — resolved to
null, and the caller then fell back to scanning the whole file, so
<ClerkProvider> landed in the sibling ErrorBoundary export and the actual
layout shipped unwrapped. Arrow and function-expression bindings now
resolve, and an unresolvable default export fails closed with the
"unsupported shape" skip instead of wrapping whatever return came last.
- the single-line `return <Slot />;` branch matched raw content, so a
trailing comment defeated the greedy `<.*>` and skipped the layout. It
matches the masked slice now, reading the JSX back out of the real source
so string attributes survive, and splices only up to the `;` so the
comment itself is preserved.
- the "Already has ClerkProvider" check ran on raw content, so a leftover
`// TODO: wrap in <ClerkProvider>` skipped the file with no wiring and no
warning. It uses the mask like every other anchor search here.
- entryCandidates grouped every `src/` name ahead of every root name, so an
unrelated `src/app.ts` outranked a root `index.js` and the middleware
landed in a file that never runs. Ordering is by basename now.
- the `--env-file` instruction hardcoded `index.js` even though the entry
path was already resolved; it names the real file.
- Express projects get a post-instruction that types/globals.d.ts must be
covered by the tsconfig `include` — a config scoped to ["src"] never
loads it and the req.auth augmentation silently doesn't apply.
- env pull: skip writing CLERK_SECRET_KEY for non-npm ecosystems (iOS/Android
only ever need the publishable key, and their default .gitignore templates
don't cover .env, so the secret key had nowhere safe to land)
- init: gate the "Install agent skills?" prompt behind isNpmFramework so
native runs don't shell out to npx in a repo with no Node toolchain
- android: fix the lifecycle-viewmodel-compose quickstart step to use
<latest-version> instead of a hardcoded version, matching the
clerk-android-ui line above it
Addresses review feedback from wyattjoh on PR #394.
Extends the e2e suite to the frameworks added in this PR, at the depth each
one supports headless:
- Express / Fastify: hand-authored templates under test/e2e/templates/ (no
official scaffolder exists; the refresh script copies the template and pins
its `latest` specs). Beyond build+typecheck, a new runServerTests helper
starts the scaffolded server (`node --env-file` with type stripping) and
asserts a live request comes back with the x-clerk-auth-status header —
proof the middleware/plugin actually runs with the pulled keys.
- Expo: create-expo-app fixture; `expo export --platform web` as the build so
Metro bundles the ClerkProvider-wrapped layout (expo-secure-store is
pre-pinned since @clerk/expo/token-cache resolves it at bundle time).
No browser test — the export is an app shell, not a sign-in UI.
- iOS / Android: native-init.test.ts with marker-stub fixtures outside the
manifest. Asserts the whole native contract: detection, exit 0, keys pulled
into .env (publishable only — the secret-key question is still open in
review), zero project writes (git status must show exactly `?? .env`), and
the printed SDK quickstart steps.
gitInit/linkProject are now exported from fixture-setup for the native test;
templates are excluded from repo lint/typecheck like fixtures already were.
…heck
The SDK 57 template's CSS-module imports only typecheck through the
expo/types reference in expo-env.d.ts, which `expo start` generates and no
headless command does. Without it the pristine template fails tsc in CI.
Caught by the new expo e2e: the layout is a hoistable function declaration,
so TS won't narrow `string | undefined` through the module-level throw guard
into it, and ClerkProvider's publishableKey prop requires string. `?? ""`
types the const as string while the guard still throws on the empty fallback.
@rafa-thayto
rafa-thayto merged commit bc8b757 into mainJul 31, 2026
19 of 20 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/aie-1009-clerk-init branch July 31, 2026 14:50
@github-actionsgithub-actionsBot mentioned this pull request Jul 31, 2026
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@wyattjoh
, '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(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android - #394

Merged
rafa-thayto merged 12 commits into
mainfrom
rafa-thayto/aie-1009-clerk-init
Jul 31, 2026
Merged

feat(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android#394
rafa-thayto merged 12 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.

@changeset-bot

changeset-botBot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: edaef04

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

This PR includes changesets to release 1 package
NameType
clerkMinor

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

coderabbitaiBot 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
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: adding init scaffolding support for Expo, Express, Fastify, iOS, and Android.
Description check✅ PassedThe description is directly related to the changeset and accurately outlines the added framework scaffolding and related hardening work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot 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 threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/transformations.ts Outdated
Comment threadpackages/cli-core/src/commands/init/README.md Outdated

@coderabbitaicoderabbitaiBot 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.

@coderabbitaicoderabbitaiBot 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

…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
…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.
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.
…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-thaytoforce-pushed the rafa-thayto/aie-1009-clerk-init branch from 827f498 to d82d638CompareJuly 26, 2026 12:12

@wyattjohwyattjoh 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.

Nice work on the masking layer in source-scan.ts and the shared node-server.ts scaffolder — the test coverage on the Express/Fastify entry shapes is genuinely thorough. Two things I'd like to see addressed before merge: the ErrorBoundary fix doesn't hold for arrow-function default exports, and the iOS/Android path writes a live secret key into a repo that almost certainly doesn't gitignore it.

Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/lib/framework.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/express.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/ios.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/android.ts Outdated
Review feedback from @wyattjoh on #394.
- findDefaultExportBody only resolved `export default function` and a
reference to a function declaration. The common React Native shape — an
arrow function, inline or via `export default RootLayout` — resolved to
null, and the caller then fell back to scanning the whole file, so
<ClerkProvider> landed in the sibling ErrorBoundary export and the actual
layout shipped unwrapped. Arrow and function-expression bindings now
resolve, and an unresolvable default export fails closed with the
"unsupported shape" skip instead of wrapping whatever return came last.
- the single-line `return <Slot />;` branch matched raw content, so a
trailing comment defeated the greedy `<.*>` and skipped the layout. It
matches the masked slice now, reading the JSX back out of the real source
so string attributes survive, and splices only up to the `;` so the
comment itself is preserved.
- the "Already has ClerkProvider" check ran on raw content, so a leftover
`// TODO: wrap in <ClerkProvider>` skipped the file with no wiring and no
warning. It uses the mask like every other anchor search here.
- entryCandidates grouped every `src/` name ahead of every root name, so an
unrelated `src/app.ts` outranked a root `index.js` and the middleware
landed in a file that never runs. Ordering is by basename now.
- the `--env-file` instruction hardcoded `index.js` even though the entry
path was already resolved; it names the real file.
- Express projects get a post-instruction that types/globals.d.ts must be
covered by the tsconfig `include` — a config scoped to ["src"] never
loads it and the req.auth augmentation silently doesn't apply.
- env pull: skip writing CLERK_SECRET_KEY for non-npm ecosystems (iOS/Android
only ever need the publishable key, and their default .gitignore templates
don't cover .env, so the secret key had nowhere safe to land)
- init: gate the "Install agent skills?" prompt behind isNpmFramework so
native runs don't shell out to npx in a repo with no Node toolchain
- android: fix the lifecycle-viewmodel-compose quickstart step to use
<latest-version> instead of a hardcoded version, matching the
clerk-android-ui line above it
Addresses review feedback from wyattjoh on PR #394.
Extends the e2e suite to the frameworks added in this PR, at the depth each
one supports headless:
- Express / Fastify: hand-authored templates under test/e2e/templates/ (no
official scaffolder exists; the refresh script copies the template and pins
its `latest` specs). Beyond build+typecheck, a new runServerTests helper
starts the scaffolded server (`node --env-file` with type stripping) and
asserts a live request comes back with the x-clerk-auth-status header —
proof the middleware/plugin actually runs with the pulled keys.
- Expo: create-expo-app fixture; `expo export --platform web` as the build so
Metro bundles the ClerkProvider-wrapped layout (expo-secure-store is
pre-pinned since @clerk/expo/token-cache resolves it at bundle time).
No browser test — the export is an app shell, not a sign-in UI.
- iOS / Android: native-init.test.ts with marker-stub fixtures outside the
manifest. Asserts the whole native contract: detection, exit 0, keys pulled
into .env (publishable only — the secret-key question is still open in
review), zero project writes (git status must show exactly `?? .env`), and
the printed SDK quickstart steps.
gitInit/linkProject are now exported from fixture-setup for the native test;
templates are excluded from repo lint/typecheck like fixtures already were.
…heck
The SDK 57 template's CSS-module imports only typecheck through the
expo/types reference in expo-env.d.ts, which `expo start` generates and no
headless command does. Without it the pristine template fails tsc in CI.
Caught by the new expo e2e: the layout is a hoistable function declaration,
so TS won't narrow `string | undefined` through the module-level throw guard
into it, and ClerkProvider's publishableKey prop requires string. `?? ""`
types the const as string while the guard still throws on the empty fallback.
@rafa-thayto
rafa-thayto merged commit bc8b757 into mainJul 31, 2026
19 of 20 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/aie-1009-clerk-init branch July 31, 2026 14:50
@github-actionsgithub-actionsBot mentioned this pull request Jul 31, 2026
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@wyattjoh
, '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(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android - #394

Merged
rafa-thayto merged 12 commits into
mainfrom
rafa-thayto/aie-1009-clerk-init
Jul 31, 2026
Merged

feat(init): close the framework scaffolding gaps for expo, express, fastify, ios, and android#394
rafa-thayto merged 12 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.

@changeset-bot

changeset-botBot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: edaef04

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

This PR includes changesets to release 1 package
NameType
clerkMinor

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

coderabbitaiBot 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
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: adding init scaffolding support for Expo, Express, Fastify, iOS, and Android.
Description check✅ PassedThe description is directly related to the changeset and accurately outlines the added framework scaffolding and related hardening work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot 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 threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/transformations.ts Outdated
Comment threadpackages/cli-core/src/commands/init/README.md Outdated

@coderabbitaicoderabbitaiBot 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.

@coderabbitaicoderabbitaiBot 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

…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
…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.
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.
…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-thaytoforce-pushed the rafa-thayto/aie-1009-clerk-init branch from 827f498 to d82d638CompareJuly 26, 2026 12:12

@wyattjohwyattjoh 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.

Nice work on the masking layer in source-scan.ts and the shared node-server.ts scaffolder — the test coverage on the Express/Fastify entry shapes is genuinely thorough. Two things I'd like to see addressed before merge: the ErrorBoundary fix doesn't hold for arrow-function default exports, and the iOS/Android path writes a live secret key into a repo that almost certainly doesn't gitignore it.

Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/lib/framework.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/expo.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/node-server.ts Outdated
Comment threadpackages/cli-core/src/commands/init/frameworks/express.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/ios.ts
Comment threadpackages/cli-core/src/commands/init/frameworks/android.ts Outdated
Review feedback from @wyattjoh on #394.
- findDefaultExportBody only resolved `export default function` and a
reference to a function declaration. The common React Native shape — an
arrow function, inline or via `export default RootLayout` — resolved to
null, and the caller then fell back to scanning the whole file, so
<ClerkProvider> landed in the sibling ErrorBoundary export and the actual
layout shipped unwrapped. Arrow and function-expression bindings now
resolve, and an unresolvable default export fails closed with the
"unsupported shape" skip instead of wrapping whatever return came last.
- the single-line `return <Slot />;` branch matched raw content, so a
trailing comment defeated the greedy `<.*>` and skipped the layout. It
matches the masked slice now, reading the JSX back out of the real source
so string attributes survive, and splices only up to the `;` so the
comment itself is preserved.
- the "Already has ClerkProvider" check ran on raw content, so a leftover
`// TODO: wrap in <ClerkProvider>` skipped the file with no wiring and no
warning. It uses the mask like every other anchor search here.
- entryCandidates grouped every `src/` name ahead of every root name, so an
unrelated `src/app.ts` outranked a root `index.js` and the middleware
landed in a file that never runs. Ordering is by basename now.
- the `--env-file` instruction hardcoded `index.js` even though the entry
path was already resolved; it names the real file.
- Express projects get a post-instruction that types/globals.d.ts must be
covered by the tsconfig `include` — a config scoped to ["src"] never
loads it and the req.auth augmentation silently doesn't apply.
- env pull: skip writing CLERK_SECRET_KEY for non-npm ecosystems (iOS/Android
only ever need the publishable key, and their default .gitignore templates
don't cover .env, so the secret key had nowhere safe to land)
- init: gate the "Install agent skills?" prompt behind isNpmFramework so
native runs don't shell out to npx in a repo with no Node toolchain
- android: fix the lifecycle-viewmodel-compose quickstart step to use
<latest-version> instead of a hardcoded version, matching the
clerk-android-ui line above it
Addresses review feedback from wyattjoh on PR #394.
Extends the e2e suite to the frameworks added in this PR, at the depth each
one supports headless:
- Express / Fastify: hand-authored templates under test/e2e/templates/ (no
official scaffolder exists; the refresh script copies the template and pins
its `latest` specs). Beyond build+typecheck, a new runServerTests helper
starts the scaffolded server (`node --env-file` with type stripping) and
asserts a live request comes back with the x-clerk-auth-status header —
proof the middleware/plugin actually runs with the pulled keys.
- Expo: create-expo-app fixture; `expo export --platform web` as the build so
Metro bundles the ClerkProvider-wrapped layout (expo-secure-store is
pre-pinned since @clerk/expo/token-cache resolves it at bundle time).
No browser test — the export is an app shell, not a sign-in UI.
- iOS / Android: native-init.test.ts with marker-stub fixtures outside the
manifest. Asserts the whole native contract: detection, exit 0, keys pulled
into .env (publishable only — the secret-key question is still open in
review), zero project writes (git status must show exactly `?? .env`), and
the printed SDK quickstart steps.
gitInit/linkProject are now exported from fixture-setup for the native test;
templates are excluded from repo lint/typecheck like fixtures already were.
…heck
The SDK 57 template's CSS-module imports only typecheck through the
expo/types reference in expo-env.d.ts, which `expo start` generates and no
headless command does. Without it the pristine template fails tsc in CI.
Caught by the new expo e2e: the layout is a hoistable function declaration,
so TS won't narrow `string | undefined` through the module-level throw guard
into it, and ClerkProvider's publishableKey prop requires string. `?? ""`
types the const as string while the guard still throws on the empty fallback.
@rafa-thayto
rafa-thayto merged commit bc8b757 into mainJul 31, 2026
19 of 20 checks passed
@rafa-thayto
rafa-thayto deleted the rafa-thayto/aie-1009-clerk-init branch July 31, 2026 14:50
@github-actionsgithub-actionsBot mentioned this pull request Jul 31, 2026
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@wyattjoh