Skip to content

CLI email template commands (2/2)#2633

Draft
drew-harris wants to merge 1 commit into
drewh/validate-senders-app-levelfrom
drewh/cli-email
Draft

CLI email template commands (2/2)#2633
drew-harris wants to merge 1 commit into
drewh/validate-senders-app-levelfrom
drewh/cli-email

Conversation

@drew-harris
Copy link
Copy Markdown
Contributor

@drew-harris drew-harris commented May 6, 2026

Adds instant-cli auth email commands to push and pull the email template to/from an instant.email.ts file.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b7646b67-bef5-47db-a47f-558684fce38d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

Adds CLI commands auth email push and auth email pull, local instant.email discovery and validation, client push/pull effects, CLI registration, and backend GET/POST handlers with updated authorization and sender selection.

Changes

Email Template Push/Pull Feature

Layer / File(s) Summary
Email Config Discovery
client/packages/cli/src/util/findConfigCandidates.ts
Adds getEnvEmailPathWithLogging(), getEmailReadCandidates() for instant.email candidates (env override, root/src/lib/app paths) and getEmailPathToWrite() for output selection.
Local Email File Loading
client/packages/cli/src/old.js
Adds readLocalEmailFile() to load first matching email config candidate via loadConfig({merge:false}), returning `{ path, email }
Email Schema & Public Types
client/packages/cli/src/commands/pull.ts, client/packages/cli/src/lib/email.ts
Defines EmailConfig and response schemas (PullEmailResponse and nullable helpers), adds NoEmailFileFound tagged error, and validation/decoding used by push/pull flows.
Push Effect (Core)
client/packages/cli/src/lib/email.ts
Implements pushEmail: reads/validates local config, builds authed client and POSTs magic-code template to /dash/apps/${appId}/email_templates, decodes response and logs saved fields.
Pull Implementation & Helpers
client/packages/cli/src/commands/pull.ts
Implements pullEmail(emailPath?): GETs /dash/apps/${appId}/email_templates, decodes union response, reads existing local file, prompts on overwrite, computes output path, generates escaped TypeScript module, writes file, and logs result.
CLI Command Implementations
client/packages/cli/src/commands/auth/email/push.ts, client/packages/cli/src/commands/auth/email/pull.ts
Adds authEmailPushCmd (yields pushEmail(opts.file)) and authEmailPullCmd (calls pullEmail(opts.file)), thin Effect-based wrappers accepting optional file.
CLI Command Registration
client/packages/cli/src/index.ts
Imports/registers auth email group with push and pull subcommands (each -a --app <app-id>, -f --file <path>, WithAppLayer configured with coerce:false/coerceAuth:false/allowAdminToken:true); updates infoDef description.
Tests
client/packages/cli/__tests__/authEmail.test.ts
Adds integration-style tests mocking local file IO and HTTP client; verifies POST payload mapping and GET/pull write behavior including missing-template skip.
Backend API Routes
server/src/instant/dash/routes.clj
Changes apps-get auth to req->app-accepting-superadmin-or-ref-token! :collaborator :apps/read; updates email-template-post auth to :admin :apps/write and uses authenticated user id or app creator for sender sync; adds email-template-get and wires GET /dash/apps/:app_id/email_templates.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • instantdb/instant#2536: Modifies server auth patterns with req->app-accepting-superadmin-or-ref-token! and adds reference-token scoped endpoints, similar authorization approach as this PR.

Suggested reviewers

  • stopachka
  • nezaj
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether the description relates to the changeset. Add a pull request description explaining the purpose of the changes, the new CLI commands added, and how they enable users to manage email templates.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately and concisely describes the main change: adding CLI commands for managing email templates (push and pull operations).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch drewh/cli-email

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


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 and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

View Vercel preview at instant-www-js-drewh-cli-email-jsv.vercel.app.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
server/src/instant/dash/routes.clj (1)

1357-1387: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

sender-user-id will be nil for org-owned apps, causing a NOT NULL constraint violation in sync-sender!

req->app-accepting-superadmin-or-ref-token! explicitly uses select-keys to return only {:app ...}, so user is always nil. The fallback (or (:id user) (:creator_id app)) therefore reduces to (:creator_id app), which is nil for org-owned apps (they use org-id but have no creator-id).

When a user provides sender-email for an org-owned app, sync-sender! calls put! with {:user-id nil ...}, which violates the NOT NULL constraint on the user_id column in app_email_senders (set by migration 21).

The old req->app-and-user! always produced a non-nil :user – this is a regression from switching to the new auth function.

🔧 One fix option: look up the authenticated user separately for the sender-user-id
-(let [{app :app user :user} (req->app-accepting-superadmin-or-ref-token! :admin :apps/write req)
+(let [{app :app} (req->app-accepting-superadmin-or-ref-token! :admin :apps/write req)
+      user (try (req->auth-user! req) (catch Exception _ nil))
🤖 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 `@server/src/instant/dash/routes.clj` around lines 1357 - 1387, The
sender-user-id can be nil because req->app-accepting-superadmin-or-ref-token!
only returns :app; update email-template-post to explicitly look up the
authenticated user for sender-user-id (e.g., call the existing request user
helper such as req->user! or the equivalent auth helper) and set sender-user-id
to (:id authenticated-user) or fallback to (:creator_id app); ensure you do not
pass nil into app-email-sender-model/sync-sender! (or else validate/raise if no
valid user-id can be determined) so sync-sender! is never invoked with a nil
:user-id.
🧹 Nitpick comments (3)
client/packages/cli/src/commands/auth/email/pull.ts (1)

7-19: 💤 Low value

Schema constants used before declaration — move them above authEmailPullCmd

PullEmailResponse, EmailTemplate, and NullableString (lines 21–40) are referenced inside the authEmailPullCmd generator body (line 13), but defined after it. This works at runtime because Effect's lazy evaluation means the generator body only executes after module initialization completes, but it hurts readability. Moving the schema definitions to the top of the file makes the data model visible before the function that consumes it.

🤖 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 `@client/packages/cli/src/commands/auth/email/pull.ts` around lines 7 - 19, The
schema constants PullEmailResponse, EmailTemplate, and NullableString are
declared after authEmailPullCmd but referenced inside its generator; move the
declarations for NullableString, EmailTemplate, and PullEmailResponse to appear
above the authEmailPullCmd definition so the data model is visible before use,
and leave the rest of the function (including CurrentApp and InstantHttpAuthed
usage) unchanged.
server/src/instant/dash/routes.clj (1)

1389-1393: 💤 Low value

email-template-get hardcodes "magic-code" — consider accepting email-type as a query param for future extensibility

The POST endpoint accepts any :email-type from the body, but this GET endpoint is hardcoded. If a second email type (e.g. "invite") is ever added, a new endpoint or a query parameter would be needed. Making it a ?email-type= query param now would mirror the POST design and avoid breaking the API later.

🤖 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 `@server/src/instant/dash/routes.clj` around lines 1389 - 1393, The GET handler
email-template-get currently hardcodes the email-type to "magic-code"; change it
to read an optional ?email-type= query parameter from the request (e.g. from
req's query-params) and pass that value into
app-email-template-model/get-by-app-id-and-email-type, defaulting to
"magic-code" when the query param is absent or empty so behavior stays backward
compatible; update email-template-get to extract the query param,
validate/normalize it if necessary, and use that variable instead of the string
literal.
client/packages/cli/src/commands/auth/email/push.ts (1)

4-6: 💤 Low value

authEmailPushCmd is a no-op wrapper — consider a direct re-export

The function body is just yield* pushEmail, adding no additional logic, error mapping, or context. A re-export would be simpler and consistent with how authEmailPullCmd exposes its logic directly:

-export const authEmailPushCmd = Effect.fn(function* () {
-  yield* pushEmail;
-});
+export { pushEmail as authEmailPushCmd } from '../../../lib/email.ts';
🤖 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 `@client/packages/cli/src/commands/auth/email/push.ts` around lines 4 - 6,
authEmailPushCmd is just a no-op wrapper around pushEmail (yield* pushEmail) and
should be exposed directly like authEmailPullCmd; remove the Effect.fn wrapper
and re-export pushEmail as authEmailPushCmd (or export { pushEmail as
authEmailPushCmd }) so the original Effect value and types are preserved and no
extra indirection remains.
🤖 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 `@client/packages/cli/src/commands/auth/email/pull.ts`:
- Around line 15-18: The current extraction uses a redundant union guard
('template' in emailState) and logs JSON.stringify(template) which prints "null"
with no user-facing message; simplify PullEmailResponse to always be { template:
EmailTemplate | null }, remove the union variants and the `'template' in
emailState` guard, access the template directly via emailState.template (or the
corresponding response field) in pull.ts, and replace the raw JSON.stringify log
with a friendly message when template is null (e.g., "No template configured")
otherwise pretty-print the template via Effect.log.

In `@client/packages/cli/src/commands/info.ts`:
- Around line 36-41: The block using Effect.serviceOption(CurrentApp) is
unreachable and wrong: either remove it or wire the info command to provide
CurrentApp (add WithAppLayer in index.ts), and if you keep it fix the HTTP call
to interpolate appId into the correct route (use GET /dash/apps/:app_id via
authedHttp.get with appId), ensure authedHttp exists before calling it, and
actually use the returned appInfo (log or return it) instead of leaving it
unused.

In `@client/packages/cli/src/lib/email.ts`:
- Around line 59-73: The request currently sets 'sender-email' to an empty
string via authEmail.fromAddress ?? '' which sends "" in JSON; change the body
construction passed to HttpClientRequest.bodyJson so it omits the 'sender-email'
property entirely when authEmail.fromAddress is null/undefined/empty (or
alternatively set it to null), e.g. build the payload from authEmail.subject,
authEmail.body, authEmail.from, and conditionally include 'sender-email' only
when authEmail.fromAddress has a value before calling HttpClientRequest.bodyJson
in the pipeline that uses withCommand('auth email push') and
.post(`/dash/apps/${appId}/email_templates`).

---

Outside diff comments:
In `@server/src/instant/dash/routes.clj`:
- Around line 1357-1387: The sender-user-id can be nil because
req->app-accepting-superadmin-or-ref-token! only returns :app; update
email-template-post to explicitly look up the authenticated user for
sender-user-id (e.g., call the existing request user helper such as req->user!
or the equivalent auth helper) and set sender-user-id to (:id
authenticated-user) or fallback to (:creator_id app); ensure you do not pass nil
into app-email-sender-model/sync-sender! (or else validate/raise if no valid
user-id can be determined) so sync-sender! is never invoked with a nil :user-id.

---

Nitpick comments:
In `@client/packages/cli/src/commands/auth/email/pull.ts`:
- Around line 7-19: The schema constants PullEmailResponse, EmailTemplate, and
NullableString are declared after authEmailPullCmd but referenced inside its
generator; move the declarations for NullableString, EmailTemplate, and
PullEmailResponse to appear above the authEmailPullCmd definition so the data
model is visible before use, and leave the rest of the function (including
CurrentApp and InstantHttpAuthed usage) unchanged.

In `@client/packages/cli/src/commands/auth/email/push.ts`:
- Around line 4-6: authEmailPushCmd is just a no-op wrapper around pushEmail
(yield* pushEmail) and should be exposed directly like authEmailPullCmd; remove
the Effect.fn wrapper and re-export pushEmail as authEmailPushCmd (or export {
pushEmail as authEmailPushCmd }) so the original Effect value and types are
preserved and no extra indirection remains.

In `@server/src/instant/dash/routes.clj`:
- Around line 1389-1393: The GET handler email-template-get currently hardcodes
the email-type to "magic-code"; change it to read an optional ?email-type= query
parameter from the request (e.g. from req's query-params) and pass that value
into app-email-template-model/get-by-app-id-and-email-type, defaulting to
"magic-code" when the query param is absent or empty so behavior stays backward
compatible; update email-template-get to extract the query param,
validate/normalize it if necessary, and use that variable instead of the string
literal.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 873d1a3d-d509-4019-8b76-dd669e31148a

📥 Commits

Reviewing files that changed from the base of the PR and between 271c4ea and faabd4c.

📒 Files selected for processing (8)
  • client/packages/cli/src/commands/auth/email/pull.ts
  • client/packages/cli/src/commands/auth/email/push.ts
  • client/packages/cli/src/commands/info.ts
  • client/packages/cli/src/index.ts
  • client/packages/cli/src/lib/email.ts
  • client/packages/cli/src/old.js
  • client/packages/cli/src/util/findConfigCandidates.ts
  • server/src/instant/dash/routes.clj

Comment thread client/packages/cli/src/commands/auth/email/pull.ts Outdated
Comment thread client/packages/cli/src/commands/info.ts Outdated
Comment thread client/packages/cli/src/lib/email.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
client/packages/cli/src/commands/info.ts (1)

36-41: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Incomplete implementation: wrong URL, unused variable, and dead code — CurrentApp is never provided to info

These issues were flagged in a previous review and remain unresolved:

  1. Wrong URL — Line 40 uses the hardcoded /dash/app/ path. The appId from line 39 is never interpolated.
  2. Unused variableappInfo is assigned but never consumed (logged or returned).
  3. Dead code — The info command is wired with AuthLayerLive, not WithAppLayer, so CurrentApp is never provided to the service context. Effect.serviceOption(CurrentApp) will always yield Option.none(), making this entire block unreachable.
🤖 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 `@client/packages/cli/src/commands/info.ts` around lines 36 - 41, The block
using Effect.serviceOption(CurrentApp) is dead because the info command is wired
with AuthLayerLive (not WithAppLayer), and it also misuses appId and appInfo;
either remove this unreachable branch or properly provide CurrentApp via
WithAppLayer to info; if you choose to keep it, change the HTTP call in the
authedHttp.get to interpolate the appId (e.g., `/dash/app/${appId}`), and
actually use appInfo (log or return it) instead of leaving it unused; ensure
consistency between the wiring (AuthLayerLive vs WithAppLayer), the service
lookup (CurrentApp), and the HTTP request/consumption (appId and appInfo).
🧹 Nitpick comments (1)
client/packages/cli/src/util/findConfigCandidates.ts (1)

87-95: ⚡ Quick win

Consider collapsing the repeated env-path / read-candidates / write-path boilerplate into parameterised helpers.

getEnvSchemaPathWithLogging, getEnvPermsPathWithLogging, and getEnvEmailPathWithLogging are structurally identical, differing only by env-var name. The same triplication applies to get*ReadCandidates (root candidate + src/lib scan + app prefix) and get*PathToWrite. Each time a new config type is introduced, three more copy-paste functions are needed. Extracting factory helpers now (while there are only three types) would keep future additions a one-liner.

♻️ Sketch of a factory approach
+function makeEnvPathLogger(envVar: string): () => string | undefined {
+  return function () {
+    const p = process.env[envVar];
+    if (p) console.log(`Using ${envVar}=${chalk.green(p)}`);
+    return p;
+  };
+}
+
+function makeReadCandidates(stem: string, envVar: string): () => ConfigCandidate[] {
+  return function () {
+    const existing = process.env[envVar];
+    if (existing) {
+      if (existing) console.log(`Using ${envVar}=${chalk.green(existing)}`);
+      return [{ files: existing, transform: transformImports }];
+    }
+    const extensions = ['ts', 'mts', 'cts', 'js', 'mjs', 'cjs'];
+    const candidates: ConfigCandidate[] = [
+      { files: stem, extensions, transform: transformImports },
+    ];
+    for (const p of findPathsRecursive('src', 3, stem))
+      candidates.push({ files: p, extensions, transform: transformImports });
+    for (const p of findPathsRecursive('lib', 2, stem))
+      candidates.push({ files: p, extensions, transform: transformImports });
+    candidates.push({ files: `app/${stem}`, extensions, transform: transformImports });
+    return candidates;
+  };
+}
+
+function makePathToWrite(envVar: string, defaultStem: string): () => string {
+  return function () {
+    if (process.env[envVar]) return process.env[envVar]!;
+    if (existsSync(join(process.cwd(), 'src'))) return join('src', `${defaultStem}.ts`);
+    return `${defaultStem}.ts`;
+  };
+}
+
-function getEnvEmailPathWithLogging() { ... }
-export function getEmailReadCandidates() { ... }
-export function getEmailPathToWrite() { ... }
+export const getEmailReadCandidates = makeReadCandidates('instant.email', 'INSTANT_EMAIL_FILE_PATH');
+export const getEmailPathToWrite    = makePathToWrite('INSTANT_EMAIL_FILE_PATH', 'instant.email');

Also applies to: 188-228, 252-260

🤖 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 `@client/packages/cli/src/util/findConfigCandidates.ts` around lines 87 - 95,
Several functions are duplicated (getEnvSchemaPathWithLogging,
getEnvPermsPathWithLogging, getEnvEmailPathWithLogging and the
get*ReadCandidates / get*PathToWrite families) — extract parameterized factory
helpers to eliminate copy/paste: create a single
makeEnvPathWithLogging(envVarName) that returns the env-path getter used for
schema/perms/email; create makeReadCandidates(baseNames, options?) to produce
the root/src/lib/app-prefixed candidate arrays and makePathToWrite(baseName) for
write-path logic, then replace the specific functions
(getEnvSchemaPathWithLogging, getEnvPermsPathWithLogging,
getEnvEmailPathWithLogging,
getSchemaReadCandidates/getPermsReadCandidates/getEmailReadCandidates,
getSchemaPathToWrite/getPermsPathToWrite/getEmailPathToWrite) with one-line
invocations of those factories so all behavior and logging remains the same but
future config types are a one-liner.
🤖 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.

Duplicate comments:
In `@client/packages/cli/src/commands/info.ts`:
- Around line 36-41: The block using Effect.serviceOption(CurrentApp) is dead
because the info command is wired with AuthLayerLive (not WithAppLayer), and it
also misuses appId and appInfo; either remove this unreachable branch or
properly provide CurrentApp via WithAppLayer to info; if you choose to keep it,
change the HTTP call in the authedHttp.get to interpolate the appId (e.g.,
`/dash/app/${appId}`), and actually use appInfo (log or return it) instead of
leaving it unused; ensure consistency between the wiring (AuthLayerLive vs
WithAppLayer), the service lookup (CurrentApp), and the HTTP request/consumption
(appId and appInfo).

---

Nitpick comments:
In `@client/packages/cli/src/util/findConfigCandidates.ts`:
- Around line 87-95: Several functions are duplicated
(getEnvSchemaPathWithLogging, getEnvPermsPathWithLogging,
getEnvEmailPathWithLogging and the get*ReadCandidates / get*PathToWrite
families) — extract parameterized factory helpers to eliminate copy/paste:
create a single makeEnvPathWithLogging(envVarName) that returns the env-path
getter used for schema/perms/email; create makeReadCandidates(baseNames,
options?) to produce the root/src/lib/app-prefixed candidate arrays and
makePathToWrite(baseName) for write-path logic, then replace the specific
functions (getEnvSchemaPathWithLogging, getEnvPermsPathWithLogging,
getEnvEmailPathWithLogging,
getSchemaReadCandidates/getPermsReadCandidates/getEmailReadCandidates,
getSchemaPathToWrite/getPermsPathToWrite/getEmailPathToWrite) with one-line
invocations of those factories so all behavior and logging remains the same but
future config types are a one-liner.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 790fcbae-a349-4dcf-89cb-dffdacb61dba

📥 Commits

Reviewing files that changed from the base of the PR and between faabd4c and d36040e.

📒 Files selected for processing (8)
  • client/packages/cli/src/commands/auth/email/pull.ts
  • client/packages/cli/src/commands/auth/email/push.ts
  • client/packages/cli/src/commands/info.ts
  • client/packages/cli/src/index.ts
  • client/packages/cli/src/lib/email.ts
  • client/packages/cli/src/old.js
  • client/packages/cli/src/util/findConfigCandidates.ts
  • server/src/instant/dash/routes.clj
✅ Files skipped from review due to trivial changes (1)
  • client/packages/cli/src/commands/auth/email/push.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • client/packages/cli/src/index.ts
  • client/packages/cli/src/lib/email.ts
  • client/packages/cli/src/commands/auth/email/pull.ts
  • server/src/instant/dash/routes.clj

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
client/packages/cli/src/commands/auth/email/pull.ts (2)

34-40: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Collapse PullEmailResponse to match the actual server contract.

Since the server always returns { template: EmailTemplate | null }, the two extra union arms (EmailTemplate and Schema.Null at the top level) are dead code and add noise. Simplifying also removes the need for the guard in the command body above.

✨ Suggested simplification
-const PullEmailResponse = Schema.Union(
-  Schema.Struct({
-    template: Schema.Union(EmailTemplate, Schema.Null),
-  }),
-  EmailTemplate,
-  Schema.Null,
-);
+const PullEmailResponse = Schema.Struct({
+  template: Schema.Union(EmailTemplate, Schema.Null),
+});
🤖 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 `@client/packages/cli/src/commands/auth/email/pull.ts` around lines 34 - 40,
PullEmailResponse is an overly broad union; simplify it to match the server
contract by replacing the top-level Union with a single Schema.Struct that has a
"template" field whose type is Schema.Union(EmailTemplate, Schema.Null) (or
Schema.Optional/Nullable equivalent used elsewhere) so the response shape is
exactly { template: EmailTemplate | null }, removing the extra EmailTemplate and
Schema.Null arms and the runtime guard in the command.

15-18: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

null template logs as "null" with no user-facing message; response extraction can be simplified.

The existing comment on this segment remains valid against the current code. The server-side email-template-get endpoint always returns { template: ... }, making the EmailTemplate and Schema.Null top-level union variants in PullEmailResponse unreachable, so the 'template' in emailState guard is always true. Additionally, when template is null, JSON.stringify(null, null, 2) prints the string "null" with no user-friendly explanation.

✨ Suggested simplification
-  const template =
-    emailState && 'template' in emailState ? emailState.template : emailState;
-
-  yield* Effect.log(JSON.stringify(template, null, 2));
+  const template = emailState.template;
+
+  if (!template) {
+    yield* Effect.log('No email template configured for this app.');
+  } else {
+    yield* Effect.log(JSON.stringify(template, null, 2));
+  }
🤖 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 `@client/packages/cli/src/commands/auth/email/pull.ts` around lines 15 - 18,
The guard and JSON.stringify call produce confusing output when template is null
and the union branch is unreachable; simplify by directly reading
emailState.template (since the server always returns { template: ... }) and
replace the raw JSON.stringify with a user-friendly log when template is null
(e.g., log "No template available" or similar) otherwise log the template
content; update the code paths referencing template and emailState in pull.ts
(the template variable extraction and the Effect.log call) accordingly.
🤖 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.

Duplicate comments:
In `@client/packages/cli/src/commands/auth/email/pull.ts`:
- Around line 34-40: PullEmailResponse is an overly broad union; simplify it to
match the server contract by replacing the top-level Union with a single
Schema.Struct that has a "template" field whose type is
Schema.Union(EmailTemplate, Schema.Null) (or Schema.Optional/Nullable equivalent
used elsewhere) so the response shape is exactly { template: EmailTemplate |
null }, removing the extra EmailTemplate and Schema.Null arms and the runtime
guard in the command.
- Around line 15-18: The guard and JSON.stringify call produce confusing output
when template is null and the union branch is unreachable; simplify by directly
reading emailState.template (since the server always returns { template: ... })
and replace the raw JSON.stringify with a user-friendly log when template is
null (e.g., log "No template available" or similar) otherwise log the template
content; update the code paths referencing template and emailState in pull.ts
(the template variable extraction and the Effect.log call) accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ffb8ec8b-fabd-4ab7-9c41-d62a33ad52e8

📥 Commits

Reviewing files that changed from the base of the PR and between d36040e and 211b5ab.

📒 Files selected for processing (3)
  • client/packages/cli/src/commands/auth/email/pull.ts
  • client/packages/cli/src/index.ts
  • server/src/instant/dash/routes.clj
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/packages/cli/src/index.ts
  • server/src/instant/dash/routes.clj

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
client/packages/cli/src/commands/auth/email/pull.ts (1)

8-9: ⚡ Quick win

Guard opts before reading file

Line 9 assumes an options object is always present. If a caller invokes this command with undefined, this will throw before pullEmail runs.

Suggested patch
-export const authEmailPullCmd = Effect.fn(function* (opts: AuthEmailPullOpts) {
-  yield* pullEmail(opts.file);
+export const authEmailPullCmd = Effect.fn(function* (
+  opts: AuthEmailPullOpts = {},
+) {
+  yield* pullEmail(opts.file);
 });
🤖 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 `@client/packages/cli/src/commands/auth/email/pull.ts` around lines 8 - 9,
authEmailPullCmd currently dereferences opts.file without validating opts; if
callers pass undefined this will throw. Update authEmailPullCmd to guard the
parameter (opts) before accessing file: check opts is truthy (or provide a
default object) and validate that opts.file exists (or handle missing file)
before calling pullEmail(opts.file), and if invalid, return or throw a clear
error. Reference the function authEmailPullCmd, the type AuthEmailPullOpts, and
the call to pullEmail to locate the change.
🤖 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 `@client/packages/cli/src/commands/pull.ts`:
- Around line 111-113: The code sets fromAddress by treating any falsy value the
same as undefined, which turns a legitimate empty-string sender ("") into
'undefined'; change the check in pull.ts so it only treats null/undefined as
missing: use a test against null/undefined (e.g., template.email !== null &&
template.email !== undefined or typeof template.email === 'string') and only
call JSON.stringify(template.email) in that case, otherwise set
'undefined'—update the assignment for fromAddress accordingly so empty-string
values are preserved.

---

Nitpick comments:
In `@client/packages/cli/src/commands/auth/email/pull.ts`:
- Around line 8-9: authEmailPullCmd currently dereferences opts.file without
validating opts; if callers pass undefined this will throw. Update
authEmailPullCmd to guard the parameter (opts) before accessing file: check opts
is truthy (or provide a default object) and validate that opts.file exists (or
handle missing file) before calling pullEmail(opts.file), and if invalid, return
or throw a clear error. Reference the function authEmailPullCmd, the type
AuthEmailPullOpts, and the call to pullEmail to locate the change.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: be550d82-1a98-41a2-aef1-c7025b73f12d

📥 Commits

Reviewing files that changed from the base of the PR and between 5d4cda3 and 118e77d.

📒 Files selected for processing (8)
  • client/packages/cli/__tests__/authEmail.test.ts
  • client/packages/cli/src/commands/auth/email/pull.ts
  • client/packages/cli/src/commands/auth/email/push.ts
  • client/packages/cli/src/commands/pull.ts
  • client/packages/cli/src/index.ts
  • client/packages/cli/src/lib/email.ts
  • client/packages/cli/src/old.js
  • client/packages/cli/src/util/findConfigCandidates.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • client/packages/cli/src/commands/auth/email/push.ts
  • client/packages/cli/src/index.ts
  • client/packages/cli/src/util/findConfigCandidates.ts
  • client/packages/cli/src/lib/email.ts

Comment thread client/packages/cli/src/commands/pull.ts Outdated
@drew-harris drew-harris changed the title CLI email template commands CLI email template commands (2/2) May 15, 2026
@drew-harris drew-harris force-pushed the drewh/validate-senders-app-level branch from 253b606 to 19eff43 Compare May 15, 2026 21:30
@drew-harris drew-harris force-pushed the drewh/validate-senders-app-level branch from 19eff43 to 68c7afb Compare May 15, 2026 21:30
@drew-harris drew-harris force-pushed the drewh/validate-senders-app-level branch from 68c7afb to 8e8d726 Compare May 16, 2026 00:17
@drew-harris drew-harris force-pushed the drewh/validate-senders-app-level branch 2 times, most recently from ad0aafd to a98e807 Compare May 18, 2026 23:39
@drew-harris drew-harris force-pushed the drewh/validate-senders-app-level branch from a98e807 to 1d01c82 Compare May 19, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant