Repository navigation
init + the brew.new / create-brew launchers (C4, 0.20.0) - #55
thedogwiththedataonit wants to merge 10 commits into
Conversation
| kit = await client.appEmails.setUpKit(body, { | ||
| idempotencyKey: idempotencyKey('kit', body), | ||
| }) |
There was a problem hiding this comment.
After a partial kit failure, the suggested rerun sends the same body with the same saved projectId, so it reuses the same Idempotency-Key. The API returns the original response for 24 hours, including the failed entries. The rerun therefore cannot finish those keys during that window. Keep the key for uncertain request retries, but use a new attempt key after receiving a completed response with failed entries.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 633-635
Comment:
**Recovery replays failed setup**
After a partial kit failure, the suggested rerun sends the same body with the same saved `projectId`, so it reuses the same `Idempotency-Key`. The API returns the original response for 24 hours, including the failed entries. The rerun therefore cannot finish those keys during that window. Keep the key for uncertain request retries, but use a new attempt key after receiving a completed response with failed entries.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| const passwordLines = next.flatMap((line, index) => | ||
| /^\s*emailAndPassword\s*:\s*\{\s*$/.test(line) ? [index] : [] | ||
| ) | ||
| const passwordLine = passwordLines[0] | ||
| if (passwordLines.length === 1 && passwordLine !== undefined) { |
There was a problem hiding this comment.
A valid Better Auth config with emailAndPassword: { enabled: true }, on one line passes the automatic-patch checks, but this regex never finds its password block. The patch still reports success without adding sendResetPassword, leaving password-reset email unwired. Handle the inline form or return a manual patch when the callback cannot be placed.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/patches.ts
Line: 206-210
Comment:
**Password reset stays unwired**
A valid Better Auth config with `emailAndPassword: { enabled: true },` on one line passes the automatic-patch checks, but this regex never finds its password block. The patch still reports success without adding `sendResetPassword`, leaving password-reset email unwired. Handle the inline form or return a manual patch when the callback cannot be placed.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| if (normalized.includes('[auth.hook.send_email]')) { | ||
| return normalized.includes('/functions/v1/send-email') | ||
| ? { patch: { ...meta, path, status: 'already-applied' } } |
There was a problem hiding this comment.
supabaseConfigPatch reports already-applied whenever the file contains the hook header and URL text. It does not check whether the section is commented out or has enabled = false. A disabled hook pointing at send-email therefore receives no patch or blocking next step. Check the active section and its enabled value before declaring it applied.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/patches.ts
Line: 311-313
Comment:
**Disabled hook looks ready**
`supabaseConfigPatch` reports `already-applied` whenever the file contains the hook header and URL text. It does not check whether the section is commented out or has `enabled = false`. A disabled hook pointing at `send-email` therefore receives no patch or blocking next step. Check the active section and its enabled value before declaring it applied.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| const separator = normalized.endsWith('\n') ? '\n' : '\n\n' | ||
| return computed( | ||
| path, | ||
| text, | ||
| `${normalized}${separator}${SUPABASE_CONFIG_BLOCK}\n`, |
There was a problem hiding this comment.
Supabase config becomes invalid
If supabase/config.toml already defines [functions.send-email] but has no send-email hook section, this patch appends another [functions.send-email] table. Repeating a TOML table makes the config invalid, so --apply-patches can break supabase start. Merge the existing function section or leave that part as a manual edit.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/patches.ts
Line: 323-327
Comment:
**Supabase config becomes invalid**
If `supabase/config.toml` already defines `[functions.send-email]` but has no send-email hook section, this patch appends another `[functions.send-email]` table. Repeating a TOML table makes the config invalid, so `--apply-patches` can break `supabase start`. Merge the existing function section or leave that part as a manual edit.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| const kept = files.filter((file) => file.action === 'kept-user-version') | ||
| steps.push({ | ||
| id: 'files', | ||
| status: dryRun ? 'planned' : 'done', | ||
| detail: | ||
| kept.length > 0 | ||
| ? `kept your version of ${kept.map((file) => file.path).join(', ')}` | ||
| : `${files.filter((file) => file.action === 'created' || file.action === 'updated').length} ${dryRun ? 'to write' : 'written'}`, |
There was a problem hiding this comment.
Required file writes can return action: 'refused', but the pipeline still marks the files and state steps as done. A helper directory linked outside the project can leave a generated helper missing; a refused brew.json write loses the saved project ID and hashes. Mark refused required writes as failed and provide a recovery step before applying patches that depend on them.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 796-803
Comment:
**Missing files look written**
Required file writes can return `action: 'refused'`, but the pipeline still marks the `files` and `state` steps as done. A helper directory linked outside the project can leave a generated helper missing; a refused `brew.json` write loses the saved project ID and hashes. Mark refused required writes as failed and provide a recovery step before applying patches that depend on them.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| const result = await sink.spawn(install.line.command, install.line.args, { | ||
| cwd: install.cwd, | ||
| signal: ctx.signal, | ||
| }) |
There was a problem hiding this comment.
The new install step passes bare npm, pnpm and yarn commands to defaultSpawn, which always uses shell: false. On Windows these commands normally resolve to .cmd shims, which cannot be launched this way. Setup therefore fails to install the SDK and exits with a failed install step. Add a Windows-safe way to launch these shims while preserving argument quoting.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 1158-1161
Comment:
**Windows SDK install fails**
The new install step passes bare `npm`, `pnpm` and `yarn` commands to `defaultSpawn`, which always uses `shell: false`. On Windows these commands normally resolve to `.cmd` shims, which cannot be launched this way. Setup therefore fails to install the SDK and exits with a failed install step. Add a Windows-safe way to launch these shims while preserving argument quoting.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| if (paths.auth !== null && project.auth === 'better-auth') { | ||
| computedPatches.push(betterAuthPatch(project, paths.auth, { lifecycle })) | ||
| } | ||
| if (paths.auth !== null && project.auth === 'authjs') { | ||
| computedPatches.push(authjsPatch(project, paths.auth)) |
There was a problem hiding this comment.
Lifecycle setup breaks auth email
On a fresh Better Auth or Auth.js project, --sets lifecycle creates only lifecycle emails, but the pipeline still generates and offers patches for auth callbacks. Applying those patches routes auth emails to keys the kit did not create. Track whether the auth set was requested, and skip auth adapters and patches when it was not.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 812-816
Comment:
**Lifecycle setup breaks auth email**
On a fresh Better Auth or Auth.js project, `--sets lifecycle` creates only lifecycle emails, but the pipeline still generates and offers patches for auth callbacks. Applying those patches routes auth emails to keys the kit did not create. Track whether the auth set was requested, and skip auth adapters and patches when it was not.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.9a731ff to
4c151ea
Compare
d028a47 to
7f77dd2
Compare
| '--app', | ||
| '--domain', | ||
| '--sets', | ||
| '--stack', | ||
| '--test-to', | ||
| ]) |
There was a problem hiding this comment.
VALUE_FLAGS omits --app-url and --apply-patches. For example, npm create brew -- --app-url https://example.com --yes treats the URL as a subcommand and does not insert init. The CLI then rejects the init-only flag instead of starting setup. Add both flags to the shared launcher and sync its copies.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/launchers/launcher.mjs
Line: 13-18
Comment:
**Launcher skips setup**
`VALUE_FLAGS` omits `--app-url` and `--apply-patches`. For example, `npm create brew -- --app-url https://example.com --yes` treats the URL as a subcommand and does not insert `init`. The CLI then rejects the init-only flag instead of starting setup. Add both flags to the shared launcher and sync its copies.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| next[at] = line.replace(/\bproviders\s*:\s*\[\s*/, (match) => | ||
| /\[\s*\]/.test(line) | ||
| ? `${match}brewEmailProvider` | ||
| : `${match}brewEmailProvider, ` |
There was a problem hiding this comment.
authjsPatch checks the whole line for [] when deciding whether to add a comma. A valid line such as providers: [GitHub({ checks: [] })], takes the empty-list branch and becomes providers: [brewEmailProviderGitHub({ checks: [] })],. Applying that patch breaks the auth config and can stop the app from building or starting. Check whether the providers list itself is empty, not whether any empty array appears on the line.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/patches.ts
Line: 305-308
Comment:
**Auth patch breaks startup**
`authjsPatch` checks the whole line for `[]` when deciding whether to add a comma. A valid line such as `providers: [GitHub({ checks: [] })],` takes the empty-list branch and becomes `providers: [brewEmailProviderGitHub({ checks: [] })],`. Applying that patch breaks the auth config and can stop the app from building or starting. Check whether the providers list itself is empty, not whether any empty array appears on the line.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| 'automations.triggers.getContract', | ||
| 'send', | ||
| ], | ||
| commandClass: 'destructive', |
There was a problem hiding this comment.
initCommand calls appEmails.setUpKit without declaring defaultTimeoutMs. The repository guide requires commands with long-running server work to declare the SDK's per-call timeout through lib/long-running.ts, which already lists this kit operation. Add the required timeout before merging so the whole setup has a default deadline.
Context Used: AGENTS.md (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/init.ts
Line: 109
Comment:
**Setup has no default deadline**
`initCommand` calls `appEmails.setUpKit` without declaring `defaultTimeoutMs`. The repository guide requires commands with long-running server work to declare the SDK's per-call timeout through `lib/long-running.ts`, which already lists this kit operation. Add the required timeout before merging so the whole setup has a default deadline.
**Context Used:** AGENTS.md ([source](https://github.com/getbrew/brew-cli/blob/main/AGENTS.md))
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
4c151ea to
2ff44ff
Compare
7f77dd2 to
f49bcc4
Compare
| ts | ||
| ? "import { type BrewClient, createBrewClient } from '@brew.new/sdk'" | ||
| : "import { createBrewClient } from '@brew.new/sdk'", |
There was a problem hiding this comment.
In a JavaScript server with "type": "commonjs", init writes brew.js and brew-auth.js with import and export statements. Node cannot load these files, so the generated email helpers fail before they can send anything. Generate CommonJS code for these projects, or use .mjs files with matching imports.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/templates.ts
Line: 150-152
Comment:
**CommonJS helpers cannot load**
In a JavaScript server with `"type": "commonjs"`, `init` writes `brew.js` and `brew-auth.js` with `import` and `export` statements. Node cannot load these files, so the generated email helpers fail before they can send anything. Generate CommonJS code for these projects, or use `.mjs` files with matching imports.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| const start = options.app === undefined ? cwd : resolve(cwd, options.app) | ||
| const resolved = resolveProjectRoot(start) | ||
| let root = resolved.root | ||
| let workspace = resolved.monorepo?.workspace ?? null | ||
| if ( | ||
| resolved.monorepo !== null && | ||
| workspace === null && | ||
| options.app === undefined |
There was a problem hiding this comment.
A missing or mistyped --app path can silently select a parent project. detectProject passes the path to resolveProjectRoot, which walks upward; supplying --app also skips the monorepo choice check. With --yes, setup can then install the SDK and write files in the parent app or monorepo root instead of the requested app. Reject an invalid explicit app path before falling back to a parent.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/detect.ts
Line: 380-387
Comment:
**Setup changes the wrong app**
A missing or mistyped `--app` path can silently select a parent project. `detectProject` passes the path to `resolveProjectRoot`, which walks upward; supplying `--app` also skips the monorepo choice check. With `--yes`, setup can then install the SDK and write files in the parent app or monorepo root instead of the requested app. Reject an invalid explicit app path before falling back to a parent.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.…ed send summary - RELEASING app email SDK gate and the 0.17.0 changelog name the SDK versions: #55 ships as 13.1.0 and #64 as 13.2.0, after the SDK's 13.0.0; the CLI takes `^13.2.0`. - `app-emails upsert` passes `--input` payloadSchema fields through as given, so `secret: true` on a one-time code or sign-in link reaches the API; a test pins it (typed against the vendored spec) and a new example shows it. - `send`'s human summary reads the fire response through the vendored spec's TriggerFireAccepted: `notStarted[].code` (DOMAIN_REQUIRED, from #2059) is in the CLI's spec but not in SDK 13.2.0's types, which leave #2059 out. A test pins the not-started line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…^13.2.0 The SDK numbering is final: #55 (`send`) ships as 13.1.0 and #64 (`appEmails`, the auth adapters) as 13.2.0, after main's 13.0.0. init's generated code imports the adapters (Better Auth, Auth.js, the Supabase hook), so SDK_RANGE is `^13.2.0`; `migrate resend` shares it for `@brew.new/sdk/resend`. The test email step reads the fire response through the vendored spec's TriggerFireAccepted: `notStarted[].code` (DOMAIN_REQUIRED, brew-v2#2059) is not in SDK 13.2.0's types, which leave #2059 out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f49bcc4 to
954239d
Compare
The install step skipped any app that already declared @brew.new/sdk 12.3 or later, but the generated code needs ^13.2.0 (the adapters and `appEmails`), so an app on 12.3 to 13.1 kept an SDK that cannot load the files init writes (Greptile on #55). The threshold now comes from SDK_RANGE itself; tests cover 12.3, 13.1, 13.2 and 14.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The install step skipped any app that already declared @brew.new/sdk 12.3 or later, but the generated code needs ^13.2.0 (the adapters and `appEmails`), so an app on 12.3 to 13.1 kept an SDK that cannot load the files init writes (Greptile on #55). `reachesSdkRange` (templates.ts, next to SDK_RANGE) takes the threshold from SDK_RANGE itself; tests cover 12.3, 13.1, 13.2 and 14.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5892de7 to
d50df52
Compare
…ted code - RELEASING app email SDK gate and the 0.17.0 changelog name the SDK versions: #55 ships as 13.1.0 and #64 as 13.2.0, after the SDK's 13.0.0; the CLI takes `^13.2.0`. - `app-emails upsert` passes `--input` payloadSchema fields through as given, so `secret: true` on a one-time code or sign-in link reaches the API; a test pins it (typed against the vendored spec) and a new example shows it. - A test pins `send`'s not-started line naming `DOMAIN_REQUIRED` (#2059), which SDK 13.2.0 types now that its spec includes #2059. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
282dd6c to
4f45e49
Compare
…^13.2.0 The SDK numbering is final: #55 (`send`) ships as 13.1.0 and #64 (`appEmails`, the auth adapters) as 13.2.0, after main's 13.0.0. init's generated code imports the adapters (Better Auth, Auth.js, the Supabase hook), so SDK_RANGE is `^13.2.0`; `migrate resend` shares it for `@brew.new/sdk/resend`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The install step skipped any app that already declared @brew.new/sdk 12.3 or later, but the generated code needs ^13.2.0 (the adapters and `appEmails`), so an app on 12.3 to 13.1 kept an SDK that cannot load the files init writes (Greptile on #55). `reachesSdkRange` (templates.ts, next to SDK_RANGE) takes the threshold from SDK_RANGE itself; tests cover 12.3, 13.1, 13.2 and 14.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d50df52 to
0aa3796
Compare
| : []), | ||
| ' */', | ||
| 'export const brewEmails = brewAuthEmails({', | ||
| ' client: getBrew,', |
There was a problem hiding this comment.
On a Hono Worker using Better Auth or Auth.js, authHelperFile passes getBrew directly to the email adapter. The generated Worker version requires the request's env, but neither adapter setup supplies it. TypeScript can reject the callback, and calling it without env fails at env.BREW_API_KEY, so auth emails cannot send.
Generate an adapter factory that takes the request's env and passes a zero-argument client callback. Both the Better Auth and Auth.js templates need this.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/templates.ts
Line: 342
Comment:
**Worker auth emails fail**
On a Hono Worker using Better Auth or Auth.js, `authHelperFile` passes `getBrew` directly to the email adapter. The generated Worker version requires the request's `env`, but neither adapter setup supplies it. TypeScript can reject the callback, and calling it without `env` fails at `env.BREW_API_KEY`, so auth emails cannot send.
Generate an adapter factory that takes the request's `env` and passes a zero-argument client callback. Both the Better Auth and Auth.js templates need this.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| if ( | ||
| has('vite') && | ||
| (has('react') || | ||
| has('@vitejs/plugin-react') || | ||
| has('@vitejs/plugin-react-swc')) | ||
| ) { | ||
| return { framework: 'vite-react', dependency: 'vite' } |
There was a problem hiding this comment.
If a TanStack Start app declares vite and react, this branch returns before the later @tanstack/react-start check. detectProject calls it a browser-only app, so init skips installing the SDK and writes no server client or auth helper, despite the app having a server.
Check known full-stack frameworks before the generic Vite/React branch.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/detect.ts
Line: 192-198
Comment:
**Server setup gets skipped**
If a TanStack Start app declares `vite` and `react`, this branch returns before the later `@tanstack/react-start` check. `detectProject` calls it a browser-only app, so `init` skips installing the SDK and writes no server client or auth helper, despite the app having a server.
Check known full-stack frameworks before the generic Vite/React branch.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.4f45e49 to
d4b5b60
Compare
…ted code - RELEASING app email SDK gate and the 0.17.0 changelog name the SDK versions: #55 ships as 13.1.0 and #64 as 13.2.0, after the SDK's 13.0.0; the CLI takes `^13.2.0`. - `app-emails upsert` passes `--input` payloadSchema fields through as given, so `secret: true` on a one-time code or sign-in link reaches the API; a test pins it (typed against the vendored spec) and a new example shows it. - A test pins `send`'s not-started line naming `DOMAIN_REQUIRED` (#2059), which SDK 13.2.0 types now that its spec includes #2059. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…^13.2.0 The SDK numbering is final: #55 (`send`) ships as 13.1.0 and #64 (`appEmails`, the auth adapters) as 13.2.0, after main's 13.0.0. init's generated code imports the adapters (Better Auth, Auth.js, the Supabase hook), so SDK_RANGE is `^13.2.0`; `migrate resend` shares it for `@brew.new/sdk/resend`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The install step skipped any app that already declared @brew.new/sdk 12.3 or later, but the generated code needs ^13.2.0 (the adapters and `appEmails`), so an app on 12.3 to 13.1 kept an SDK that cannot load the files init writes (Greptile on #55). `reachesSdkRange` (templates.ts, next to SDK_RANGE) takes the threshold from SDK_RANGE itself; tests cover 12.3, 13.1, 13.2 and 14.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0aa3796 to
638fb38
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ted code - RELEASING app email SDK gate and the 0.17.0 changelog name the SDK versions: #55 ships as 13.1.0 and #64 as 13.2.0, after the SDK's 13.0.0; the CLI takes `^13.2.0`. - `app-emails upsert` passes `--input` payloadSchema fields through as given, so `secret: true` on a one-time code or sign-in link reaches the API; a test pins it (typed against the vendored spec) and a new example shows it. - A test pins `send`'s not-started line naming `DOMAIN_REQUIRED` (#2059), which SDK 13.2.0 types now that its spec includes #2059. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d4b5b60 to
b3ce2c5
Compare
…^13.2.0 The SDK numbering is final: #55 (`send`) ships as 13.1.0 and #64 (`appEmails`, the auth adapters) as 13.2.0, after main's 13.0.0. init's generated code imports the adapters (Better Auth, Auth.js, the Supabase hook), so SDK_RANGE is `^13.2.0`; `migrate resend` shares it for `@brew.new/sdk/resend`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
638fb38 to
51e3333
Compare
The install step skipped any app that already declared @brew.new/sdk 12.3 or later, but the generated code needs ^13.2.0 (the adapters and `appEmails`), so an app on 12.3 to 13.1 kept an SDK that cannot load the files init writes (Greptile on #55). `reachesSdkRange` (templates.ts, next to SDK_RANGE) takes the threshold from SDK_RANGE itself; tests cover 12.3, 13.1, 13.2 and 14.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| const nextSteps: NextStep[] = [] | ||
| const sets: readonly AppEmailKitSet[] = options.sets ?? ['auth', 'lifecycle'] | ||
| const lifecycle = sets.includes('lifecycle') | ||
| const stack = options.stack ?? kitStackFor(project.auth) |
There was a problem hiding this comment.
Selected auth library stays unwired
--stack changes the kit request, but generated helpers and patches still follow project.auth. For example, --stack better-auth in an app detected as none creates the Better Auth kit but generates sendAuthEmail instead of brewEmails and offers no Better Auth patch. The user must wire the selected library by hand. Use the selected stack consistently for email helpers and patches, or reject conflicting selections.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 241
Comment:
**Selected auth library stays unwired**
`--stack` changes the kit request, but generated helpers and patches still follow `project.auth`. For example, `--stack better-auth` in an app detected as `none` creates the Better Auth kit but generates `sendAuthEmail` instead of `brewEmails` and offers no Better Auth patch. The user must wire the selected library by hand. Use the selected stack consistently for email helpers and patches, or reject conflicting selections.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| if (text.includes('brewEmails')) { | ||
| return { | ||
| patch: { ...meta, path: config.path, status: 'already-applied' }, |
There was a problem hiding this comment.
betterAuthPatch treats any mention of brewEmails as proof that all requested callbacks are wired. Run setup with --sets auth and apply its patch, then rerun with both sets: the kit adds lifecycle emails, but this check skips adding databaseHooks.user.create.after. The welcome email never runs, and setup leaves no blocking step to add it. Check the callbacks needed for the current sets and offer a patch or manual step for missing ones.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/patches.ts
Line: 165-167
Comment:
**Welcome emails stay unwired**
`betterAuthPatch` treats any mention of `brewEmails` as proof that all requested callbacks are wired. Run setup with `--sets auth` and apply its patch, then rerun with both sets: the kit adds lifecycle emails, but this check skips adding `databaseHooks.user.create.after`. The welcome email never runs, and setup leaves no blocking step to add it. Check the callbacks needed for the current sets and offer a patch or manual step for missing ones.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| id: 'send-test-email', | ||
| actor: 'agent', | ||
| kind: 'run-command', | ||
| command: `${input.prefix} send ${key} --to ${to} --yes`, |
There was a problem hiding this comment.
The test-email retry command drops the credentials and API settings used by setup. init can use an app key from .env.local, but send only reads flags, environment variables, or CLI config. If only the app key is saved locally, this retry exits with an auth error. If a different CLI key is saved, it targets the wrong account. Build a retry that preserves the setup context without printing the key, so users do not have to reconstruct those settings.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 1449
Comment:
**Test retry loses its account**
The test-email retry command drops the credentials and API settings used by setup. `init` can use an app key from `.env.local`, but `send` only reads flags, environment variables, or CLI config. If only the app key is saved locally, this retry exits with an auth error. If a different CLI key is saved, it targets the wrong account. Build a retry that preserves the setup context without printing the key, so users do not have to reconstruct those settings.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.`brew-cli init` (destructive; `--dry-run --json` plans, `--yes --json` runs): detect (package manager, monorepo, framework, auth library + Auth.js version, Stripe, existing email providers, editors, cloud shells) → sign in through the browser for a CLI key and an app key (`--no-wait` exits 3 with the link and code, `--resume` continues) → .env.local (app key only, 0600, git-ignored first) → brand → domain (sandbox report, `--domain` adds one and prints DNS records, never waits) → `appEmails.setUpKit` for the detected stack → install @brew.new/sdk with the project's package manager → create-only managed files (lib/brew.ts, lib/brew-auth.ts per auth library, lib/brew-contracts.ts with the key registry, the Supabase Send Email Hook function, .env.example, brew.json with hashes) → patches for the user's auth config (applied only with --apply-patches when the file still hashes to the plan) → MCP for detected editors → AGENTS.md block and, in Claude Code projects, the skills → a sandbox test email. One JSON report (schemas/init-report.json, schemaVersion 1) with steps, files, patches, nextSteps (actor agent|human) and agentInstructions; exit 0 complete/action_required, 1 failed, 2 usage/ambiguous monorepo, 3 sign-in required, 4 confirmation. Idempotency keys `init:<projectId>:<step>:<body hash>`, so identical re-runs replay. Keys are masked everywhere. Tests: detection matrix over 16 fixture projects; MSW end-to-end runs (exit 4, dry run with zero mutating requests, full run with exact request bodies, files, brew.json and schema, re-run unchanged with the same keys, exit 3 + resume, monorepo exit 2, per-stack runs); generated files type-checked against the installed SDK under a strict tsconfig (with a negative control); a secret canary on every run's stdout and stderr. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
packages/launchers/{brew.new,create-brew}: each runs @brew.new/cli in the
same process, pinned to exactly the CLI's version. The shared launcher
inserts `init` when no subcommand is given (create-brew also for a bare
--help), and sets BREW_CLI_INVOKED_AS (`npx -y brew.new@<version>`,
`npm create brew@latest --`) so every printed command re-enters through
the launcher.
- scripts/sync-launchers.ts [--check]: version, exact CLI pin, launcher
copy and LICENSE per package (CI + release gate).
- scripts/smoke-launchers.ts: npm pack the CLI and both launchers, install
into a scratch project (offline, or --registry after the CLI
publishes), run --version and --help.
- release.yml publishes both launchers after the CLI, skipping a version
npm already has. Nothing is published from this change.
- Unit tests for the argv rules, the invocation prefix round trip and
launch(); package metadata pinned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- package.json and both launchers go to 0.18.0 (`sync:launchers`), and
init's notes get their own 0.18.0 section.
- `types` (and init's `lib/brew-contracts.ts`) now read the platform's
TypeScript contract per trigger, so the init tests serve
`GET /v1/automations/triggers/{id}/contract` from the same trigger rows
(tests/helpers/contract-artifacts.ts).
- The agent sign-in tests pin the fake clock: the login test server's
session expires 10 minutes after a fixed instant, so on the real clock
they started failing once that date passed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of init against the plan and the platform: - release.yml: the launcher step runs after the CLI is published and never fails the release. Sync check, pack smoke and registry smoke moved into it; a version npm already has is skipped, and a failed check or publish (a name the token does not own yet) leaves a warning. CI still gates sync and pack smoke. - Generated files, the AGENTS.md block, the brew-app-emails skill, README and the report schema say `npx @brew.new/cli` (on npm today) instead of `npx brew.new`, whose name is not ours yet. - `--apply-patches <id>@<hash>,…`: the report's nextStep carries the hash of the file the diff was computed on, and a file edited since then is reported `stale` and left alone. Re-run commands drop a flag's value with the flag. - The Auth.js patch is manual when the config has no database adapter (Auth.js refuses an email provider without one, breaking all sign-in). - After a browser sign-in the run acts as the new app key, so the kit, contracts and test email land in the brand that key is bound to, even when an explicit key of another brand started the run. - lib/brew.ts reads the key through `$env/dynamic/private` (SvelteKit) and `import.meta.env` (Astro); Express, Hono and plain Node get a blocking step to load .env.local. SvelteKit and Astro join the typecheck matrix. - A test email that does not go out is a non-blocking retry step, and the dry-run names the key the real run would send. - `--app-url` is shown on the approval page so the right brand is picked. - create-brew pins its version in BREW_CLI_INVOKED_AS (`npm create brew@<version> --`), and the launcher smoke runs the packed launchers under Node, including an `init --dry-run --json` argv check. - The generated code installs `@brew.new/sdk@^12.5.0` (npm's 12.3.0 has no `send` or `appEmails`); the Better Auth patch no longer promises a reset hook it could not add; a git-tracked .env.local gets the `git rm --cached` hint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…^13.2.0 The SDK numbering is final: #55 (`send`) ships as 13.1.0 and #64 (`appEmails`, the auth adapters) as 13.2.0, after main's 13.0.0. init's generated code imports the adapters (Better Auth, Auth.js, the Supabase hook), so SDK_RANGE is `^13.2.0`; `migrate resend` shares it for `@brew.new/sdk/resend`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The install step skipped any app that already declared @brew.new/sdk 12.3 or later, but the generated code needs ^13.2.0 (the adapters and `appEmails`), so an app on 12.3 to 13.1 kept an SDK that cannot load the files init writes (Greptile on #55). `reachesSdkRange` (templates.ts, next to SDK_RANGE) takes the threshold from SDK_RANGE itself; tests cover 12.3, 13.1, 13.2 and 14.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…red pager main published 0.17.0, so #54 is 0.18.0 and this release (with the launchers, synced) is 0.19.0. `init` writes the contracts file through the `types` pager, which now walks main's cycle-safe `collectAll` with the command context, and passes the command's stop signal to the contract reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `--domain` on a domain that already publishes another DKIM key where Brew asks for its record (POST /v1/domains `warnings[]` DKIM_SELECTOR_IN_USE, read structurally since the published SDK's row type predates it): the report swaps the DNS-records step for a `dkim-selector-in-use` step naming the first suggested subdomain. - A kit key refused with APP_EMAIL_TRIGGER_IN_USE gets a human `trigger-in-use:<key>` review step (the server's message names the automations) ahead of the re-run step. - CHANGELOG names brew-v2 #2169 @ 6f8ef50544 as the API side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… ^13.6.0 npm has CLI 0.18.0 and SDK 13.1.0-13.3.0 without the DX features, so the DX numbers move up: this PR is 0.20.0 (after #54's 0.19.0) and SDK_RANGE is the adapters release, 13.6.0. A declared ^13.3.0 (published, no adapters) now installs ^13.6.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b3ce2c5 to
c00be72
Compare
…ted code - RELEASING app email SDK gate and the 0.17.0 changelog name the SDK versions: #55 ships as 13.1.0 and #64 as 13.2.0, after the SDK's 13.0.0; the CLI takes `^13.2.0`. - `app-emails upsert` passes `--input` payloadSchema fields through as given, so `secret: true` on a one-time code or sign-in link reaches the API; a test pins it (typed against the vendored spec) and a new example shows it. - A test pins `send`'s not-started line naming `DOMAIN_REQUIRED` (#2059), which SDK 13.2.0 types now that its spec includes #2059. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51e3333 to
01d5e0e
Compare
After main's published 0.18.0 the DX numbers move up one (#54 0.19.0, #55 0.20.0, #61 0.21.0, this PR 0.22.0), and the SDK's DX releases follow its published 13.3.0 and pending 13.4.0: adapters 13.6.0 (SDK_RANGE), connect 13.7.0. doctor --project's tests use those ranges. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| let selectorInUse: DkimSelectorInUse | undefined | ||
| if (options.domain !== undefined && target === undefined) { | ||
| if (dryRun) { | ||
| domain.name = options.domain | ||
| } else { | ||
| const body = { | ||
| name: options.domain, | ||
| sendingPurpose: 'transactional' as const, | ||
| } | ||
| target = await client.domains.add(body, { | ||
| idempotencyKey: idempotencyKey('domain', body), | ||
| }) | ||
| domain.added = true | ||
| selectorInUse = dkimSelectorInUse(target) |
There was a problem hiding this comment.
A rerun with the same --domain loses the DKIM_SELECTOR_IN_USE warning. selectorInUse is set only after domains.add, so finding the existing domain skips that check and produces an add-dns-records step. Following that step can replace another sender's DKIM key and stop its emails from passing authentication.
Keep the warning across reruns and withhold conflicting records from both report formats until they are safe to publish.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 559-572
Comment:
**Reruns forget DKIM conflicts**
A rerun with the same `--domain` loses the `DKIM_SELECTOR_IN_USE` warning. `selectorInUse` is set only after `domains.add`, so finding the existing domain skips that check and produces an `add-dns-records` step. Following that step can replace another sender's DKIM key and stop its emails from passing authentication.
Keep the warning across reruns and withhold conflicting records from both report formats until they are safe to publish.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| steps.push({ id: 'domain', status: 'planned', detail: afterSignIn }) | ||
| } else { | ||
| try { | ||
| const list = await client.domains.list({ limit: 100 }) |
There was a problem hiding this comment.
init reads only the first 100 domains before deciding whether --domain already exists. If the requested domain is on a later page, setup calls domains.add again. The API returns DOMAIN_ALREADY_EXISTS, so a valid rerun gets a failed domain step instead of the existing domain's records. Setup can also miss a verified sending domain on a later page.
Follow the remaining pages before choosing or adding a domain.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 546
Comment:
**Setup misses existing domains**
`init` reads only the first 100 domains before deciding whether `--domain` already exists. If the requested domain is on a later page, setup calls `domains.add` again. The API returns `DOMAIN_ALREADY_EXISTS`, so a valid rerun gets a failed domain step instead of the existing domain's records. Setup can also miss a verified sending domain on a later page.
Follow the remaining pages before choosing or adding a domain.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Stacked on #54 (→ #53 → #44 → main); #61 (migrate resend) stacks on this. CLI 0.18.0 (C4):
initand thebrew.new/create-brewlaunchers.Publish order (nothing here is published, merged or tagged)
send,fireByKey) and Release CLI 0.17.0 with registry SDK 13 #64 (appEmails, adapter subpaths) rebased onto SDK main (which already has the canvas revocation fix this CLI's main needs) and published. Numbers are final: init + the brew.new / create-brew launchers (C4, 0.20.0) #55 ships as 13.1.0 and Release CLI 0.17.0 with registry SDK 13 #64 as 13.2.0, after the canvas fix's 13.0.0; this CLI takes^13.2.0. Both SDK PRs are behind SDK main (init + the brew.new / create-brew launchers (C4, 0.20.0) #55 conflicts).bun add @brew.new/sdk@^<that version>(regeneratesbun.lockfrom the registry, replacing main's git pin), re-vendoropenapi/public-api-v1.yamlfrom brew-v2 main, frozen install + full checks, then CLI 0.17.0 → 0.18.0 → 0.19.0.brew.newandcreate-brewon npm (both still 404) for the token's account; until then the release step warns and skips them.brew-cli initDestructive class;
--dry-run --jsonplans (no writes, no sign-in, no mutating requests),--yes --jsonruns; exit 0 complete / action_required / planned, 1 failed, 2 usage or ambiguous monorepo, 3 auth_required (with--no-wait/--resume), 4 confirm. Pipeline: detect (framework, auth, billing, existing email, editors, cloud shells) → .gitignore check → browser sign-in (CLI key to config, app key only to.env.local, 0600) → brand → domain (sandbox until verified;--domainadds one and prints DNS) →appEmails.setUpKit→ install the SDK → create-only managed files (lib/brew.ts,lib/brew-auth.ts,lib/brew-contracts.ts, the Supabase hook function,.env.example,brew.json) → auth-config patches → MCP → AGENTS.md + skills (Claude Code projects) → sandbox test email → report (schemas/init-report.json) withnextSteps[](actor: agent|human).Launchers
packages/launchers/{launcher.mjs, brew.new/, create-brew/}: exact@brew.new/clipin = launcher version = CLI version (sync:launchers[:check]),initinserted when no subcommand is given,BREW_CLI_INVOKED_AS=npx -y brew.new@<v>/npm create brew@<v> --(pinned, so resume and confirm commands run the same CLI).Review round 2026-10-16
typesconflict (init now writeslib/brew-contracts.tsfrom main's contract artifacts), CHANGELOG gets its own## 0.18.0, version 0.18.0 + launchers synced. Init tests serveGET /v1/automations/triggers/{id}/contract; the agent sign-in tests pin the fake clock (they had started failing on the real date).--apply-patches <id>@<hash>,…: the report's nextStep carries the hash of the reviewed file; a file edited since is reportedstaleand left alone (new test). Re-run commands drop a flag's value with the flag.lib/brew.tsreads the key via$env/dynamic/private(SvelteKit) andimport.meta.env(Astro); Express, Hono and plain Node get a blocking load-.env.localstep; SvelteKit and Astro joined the typecheck matrix. A failed test email is a non-blocking retry step.--app-urlis shown on the approval page. The launcher smoke runs under Node with aninit --dry-run --jsonargv check.toggle_delivery(brew-v2 PR-K/PR-L not started; init prints them as human steps),integrations connect(no/v1/integrations/{provider}/connectionin any spec; SDK S4), brand creation from--app-url(sign-in keys are bound to a brand picked on the approval page), the Entri link,doctor --project.Checks (scratch SDK = SDK main + #64)
tsc✓ ·lint✓ ·test64 files / 906 tests ✓ (secret canary on every init run; generated files type-checked for 9 stacks + a negative control) ·docs:commands:check,generate:skills:check,sync:launchers:check✓ ·build+ bin smoke ✓ ·smoke:launchers(Node) ✓ ·smoke:signals✓.Needs live validation (after publish + deploy)
npx -y @brew.new/cli@0.18.0 init --dry-run --jsonthen--yes --jsonon each wave-1 example app (sign-in minting both keys, kit per stack, sandbox test email in a team inbox,DOMAIN_REQUIREDfor a non-member--test-to), the Better Auth patch on a real app, the Supabase hook deploy, the Workers secret path, and the launcher publish once the names are reserved.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Do not merge yet: setup still has unresolved auth, file-write, recovery, and launcher failures.
Fix with agent prompt
(2 more issues omitted due to length limits.)
Summary
Adds
initto detect an app, sign in, create starter emails, install the SDK, write helpers, and report remaining work.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Detect app] --> B{Dry run?} B -->|Yes| C[Report planned changes] B -->|No| D[Read app key or browser sign-in] D --> E[Check brand and domains] E --> F[Set up starter emails] F --> G[Install SDK and write helpers] G --> H[Offer reviewed auth patches] H --> I[Configure editors and save project state] I --> J[Send test email] J --> K[Report remaining steps]Reviews (7) · Last reviewed commit: "chore(release): init is 0.20.0; generate..." · Reviewed by Greptile