Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
dd702c4
feat(migrate): `migrate resend` moves a Resend account and codebase t…
thedogwiththedataonit Oct 16, 2026
2cce8b0
docs: migrate resend in README, the brew-cli skill and CHANGELOG; 0.19.0
thedogwiththedataonit Oct 16, 2026
aba1026
fix(migrate): ask for the SDK when the declared one is older than SDK…
thedogwiththedataonit Oct 16, 2026
f96b7d6
Vendor brew-v2 #2145: import modes on an app email's source
thedogwiththedataonit Oct 17, 2026
969488d
fix(migrate): adapt to #2169: a key whose trigger is in use is manual
thedogwiththedataonit Oct 21, 2026
496ad98
chore(release): migrate is 0.21.0; its install-sdk step follows SDK_R…
thedogwiththedataonit Oct 25, 2026
607f7ea
fix(migrate): print the apply command through main's invocation seria…
thedogwiththedataonit Oct 31, 2026
101904f
chore(release): migrate is 0.22.0 after init's 0.21.0
thedogwiththedataonit Oct 31, 2026
486c0ed
docs(changelog): 0.22.0 bundles the #2169 head 5084a5202f spec
thedogwiththedataonit Nov 1, 2026
0efc9f2
fix(migrate): close the #61 review findings
thedogwiththedataonit Nov 1, 2026
c2fae0b
fix(migrate): every client's template map stays current; --domain res…
thedogwiththedataonit Nov 1, 2026
db431b7
fix(migrate): a JavaScript sender reads a JavaScript map; a later --d…
thedogwiththedataonit Nov 1, 2026
0ec6170
fix(migrate): a run without --domain resumes on the recorded domain
thedogwiththedataonit Nov 1, 2026
9e1c037
fix(migrate): a client also used for more than sends stays on Resend
thedogwiththedataonit Nov 1, 2026
f8395cc
fix(migrate): client uses come from code and resolved imports; re-run…
thedogwiththedataonit Nov 1, 2026
c0145d3
docs(changelog): 0.22.0 bundles the #2169 head b884e78157 spec
thedogwiththedataonit Nov 1, 2026
1ae2b52
fix(migrate): a setup whose answer was lost is finished on its own key
thedogwiththedataonit Nov 1, 2026
b226c23
fix(migrate): rewrite only an env lookup of the key, and no field it …
thedogwiththedataonit Nov 1, 2026
0234f85
fix(migrate): a JavaScript ESM template map is .mjs
thedogwiththedataonit Nov 1, 2026
70cb055
fix(migrate): contacts wait while the --domain cannot be resolved
thedogwiththedataonit Nov 1, 2026
e33a4bc
docs(changelog): 0.22.0 bundles the #2169 head 02a3946b18 spec
thedogwiththedataonit Nov 1, 2026
cf5bf6b
fix(migrate): a spread inside template after its id makes the id comp…
thedogwiththedataonit Nov 1, 2026
f08e42c
fix(migrate): a file that exports the Resend class stays on Resend
thedogwiththedataonit Nov 1, 2026
60fe3c6
fix(migrate): another Resend key reads contacts and suppressions again
thedogwiththedataonit Nov 1, 2026
359ada1
fix(migrate): a sender edited while the migration ran is left as is
thedogwiththedataonit Nov 1, 2026
1300b10
fix(migrate): a plan leaves an interrupted apply's progress alone
thedogwiththedataonit Nov 1, 2026
e3ad2d0
fix(migrate): a pass after refused rows sends new idempotency keys
thedogwiththedataonit Nov 1, 2026
9aee3ff
docs(changelog): 0.22.0 bundles the #2169 head 6e4accea4c spec
thedogwiththedataonit Nov 1, 2026
fe2a733
fix(migrate): a computed member on the client is a send or more than one
thedogwiththedataonit Nov 1, 2026
86f7831
fix(migrate): a second item on a kit key never suffixes into auth.*
thedogwiththedataonit Nov 1, 2026
52f18fc
fix(migrate): the send scan reads a file whose only send is in brackets
thedogwiththedataonit Nov 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,59 @@
# Changelog

## 0.22.0

Needs the same SDK release as 0.20.0, and the API from GetBrew/brew-v2#2169
(the import modes `props: "merge_tags"`, `placeholders: "resend"` and
`variables`, and the same modes on an app email's `source`); the bundled
spec is the #2169 head `6e4accea4c` (main's spec, brew-v2 `373e900a9a`,
plus the DX delta).

### Added

- **`brew-cli migrate resend`** moves a Resend account and the project
that sends through it to Brew. It plans by default: it reads Resend at
2 requests a second with a full-access key read from `RESEND_API_KEY`
or `--resend-key-stdin` (never sent to Brew, never printed or stored),
reads the Brew brand, scans the code, and writes `brew.migration.json`
(`--dry-run` writes nothing). `--apply` (destructive class) copies:
templates as app emails by key (imported with `placeholders: "resend"`
and their variables' fallbacks, published where they were), React Email
files as app emails with props as merge tags (`props: "merge_tags"`),
contacts (subscribed ones with an import consent record, unsubscribed
ones stay unsubscribed, contacts with no state are left out), segments
and topics as boolean contact fields with an audience each (topics read
each contact's choices, one request per contact), suppressions and
unsubscribed contacts into `--domain`'s unsubscribe list, and draft
broadcasts as Brew draft emails (nothing is sent). Domains, webhooks
and automations are reported. Every write carries an idempotency key,
and `brew.migration.json` keeps cursors, so a re-run resumes; it never
holds an address, a name or a key. A template whose key's trigger
already starts a published automation no setup made
(`APP_EMAIL_TRIGGER_IN_USE`) is left `manual` with the API's message;
the next run sets it up once that automation is unpublished. A setup
whose answer was lost is finished on the same key by the next run, not
set up again under another. A Resend domain that is also in
Brew gets the `DKIM_SELECTOR_IN_USE` advice and a subdomain to verify
instead. Each `resend.emails.send` / `resend.batch.send` call site is
reported with what carries over and a suggested
`getBrew().send(key, payload)`; `--rewrite-imports` moves a file to
`@brew.new/sdk/resend` (with `lib/brew-resend-map.ts`) only when every
send through it is a migrated template whose every field it can name,
and its client reads `RESEND_API_KEY` from the environment.
`--only <phases>`, `--key-prefix`, `--manifest`, `--cwd`. A sending-only
Resend key exits 3 (`RESEND_KEY_SENDING_ONLY`); a failure exits 1 with
the cursor saved.
Sources sit behind a `MigrationSource` interface; Resend is the only one.
- The `brew-migrate-from-resend` agent skill (embedded; `init` installs it
in Claude Code projects that use Resend).
- `app-emails upsert` takes the import modes on `source` through
`--input`, e.g.
`--source-file emails/order-shipped.tsx --input '{"source":{"props":"merge_tags"}}'`:
`--source-file` fills `format` and `content` and keeps the rest of
`--input`'s `source` (before, it replaced it), and takes the format from
`--input`'s `source` when it cannot infer one. `migrate resend` names
that command for a React Email file it leaves to move by hand.

## 0.21.0

Needs the same SDK release as 0.20.0 (`appEmails.setUpKit` and the auth
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,40 @@ stack exits 2 before any request. `types` adds a
`declare module '@brew.new/sdk'` block for keyed triggers; pass
`--no-registry` in a project that does not install the SDK.

## Move from Resend: `migrate resend`

```bash
printf %s "$RESEND_API_KEY" | brew-cli migrate resend --resend-key-stdin # plan: writes brew.migration.json, changes nothing
printf %s "$RESEND_API_KEY" | brew-cli migrate resend --resend-key-stdin --apply \
--domain news.example.com --yes # copy it into your Brew brand
```

The plan reads the Resend account (with a full-access key that stays on
this machine and is never sent to Brew) at 2 requests a second, reads your
Brew brand, and scans the project for `resend.emails.send` and
`resend.batch.send`. `--apply` (destructive class: exit 4 without `--yes`)
then copies:

| Resend | Brew |
| --- | --- |
| Templates (`{{{VAR}}}`) | App emails by key, imported with `placeholders: "resend"` |
| React Email files | App emails by key, props as merge tags (`props: "merge_tags"`) |
| Contacts | Contacts (subscribed with an import consent record; unsubscribed stay unsubscribed) |
| Segments, topics | A boolean contact field each, and an audience on it |
| Suppressions, unsubscribed contacts | `--domain`'s unsubscribe list |
| Draft broadcasts | Brew draft emails (nothing is sent) |
| Domains, webhooks, automations | Reported only |

Progress lives in `brew.migration.json` (ids, keys, counts and cursors,
never an address, a name or a key), so an interrupted apply resumes where
it stopped. Brew signs with the same DKIM selector as Resend
(`resend._domainkey`): the plan suggests a subdomain to verify in Brew
until cutover. Sends that do not carry over (raw `html`, `attachments`,
`cc`, …) become `edit-file` next steps with a suggested
`getBrew().send(key, payload)`; `--rewrite-imports` moves a file to
`@brew.new/sdk/resend` only when every send through it is a migrated
template.

## Connect your AI editor to Brew (MCP)

```bash
Expand Down
8 changes: 5 additions & 3 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ registry; never hand-write a lockfile entry), run a frozen install, the full
checks and `docs:commands:check`, and release the CLI.

The API side must be live first: GetBrew/brew-v2#2169 deployed (the by-key
fire, `/v1/app-emails`, the team sandbox), and the live spec
(`https://brew.new/openapi/public-api-v1.yaml`, what the spec-drift
sentinel reads) matching `openapi/public-api-v1.yaml`,
fire, `/v1/app-emails`, the team sandbox, the import modes on
`POST /v1/emails/import`, which `migrate resend` sends, and the same modes
on an app email's `source`, which `app-emails upsert --input` passes
through), and the live spec (`https://brew.new/openapi/public-api-v1.yaml`,
what the spec-drift sentinel reads) matching `openapi/public-api-v1.yaml`,
which is the #2169 head `6e4accea4c` (main's spec, brew-v2 `373e900a9a`,
plus the DX delta).

Expand Down
30 changes: 28 additions & 2 deletions docs/commands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<!-- GENERATED FILE — do not edit. Regenerate with `bun run docs:commands`. -->

156 commands. Classes: read (always safe), write
157 commands. Classes: read (always safe), write
(mutating, retry-safe), destructive (irreversible — the confirmation
protocol applies: interactive y/N on a TTY, exit 4 + JSON envelope with
a `confirmCommand` otherwise, `--yes` to proceed).
Expand Down Expand Up @@ -177,6 +177,7 @@ a `confirmCommand` otherwise, `--yes` to proceed).
| `brew-cli docs api` | read | `GET /v1/help` | Fetch the live machine-readable API catalog (GET /v1/help) |
| `brew-cli mcp install` | write | — | Add the Brew MCP server to your AI editors' config (Cursor, VS Code, Claude Code, Gemini CLI, Devin Desktop/Windsurf, Codex) |
| `brew-cli init` | destructive | — | Set up Brew app email in this project: sign in, starter kit, SDK, lib/brew.ts, .env.local, MCP, a sandbox test email |
| `brew-cli migrate resend` | destructive | — | Move a Resend account and codebase to Brew: plan by default (writes brew.migration.json), --apply to copy templates, contacts, segments, topics, suppressions and draft broadcasts |
| `brew-cli api` | destructive | — | Raw authenticated request against the Brew public API |

## Details
Expand Down Expand Up @@ -1663,7 +1664,7 @@ Set up one app email by key in one idempotent call (trigger, contract, design, a
- SDK: `brew.appEmails.upsert(...)`
- Argument `triggerKey` — Trigger key your code sends by: a kit key (auth.password-reset) or your own (order.paid)
- `--starter <id>` — Starter-kit template to use (a kit key alone uses its own starter)
- `--source-file <path>` — Your own design source (HTML, MJML or React Email JSX), or - for stdin
- `--source-file <path>` — Your own design source (HTML, MJML or React Email JSX), or - for stdin. Import modes (props, placeholders, variables) go in --input's source
- `--format <format>` — Source format: html | mjml | jsx (default: from the file extension)
- `--title <title>` — App email title in Brew
- `--subject <text>` — Subject line
Expand All @@ -1679,6 +1680,7 @@ brew-cli app-emails upsert auth.password-reset --yes
brew-cli app-emails upsert order.paid --source-file emails/order-paid.tsx --subject "Your order is confirmed" --yes
brew-cli app-emails upsert lifecycle.welcome --overwrite --yes
brew-cli app-emails upsert account.sign-in-code --source-file emails/sign-in-code.tsx --input '{"payloadSchema":{"fields":[{"key":"code","type":"string","required":true,"secret":true}]}}' --yes
brew-cli app-emails upsert order.shipped --source-file emails/order-shipped.tsx --input '{"source":{"props":"merge_tags"}}' --yes
```

### brew-cli app-emails kit
Expand Down Expand Up @@ -2863,6 +2865,30 @@ brew-cli init --app apps/web --domain mail.example.com --yes
brew-cli init --yes --apply-patches better-auth-config@3f9c2a7d1b04
```

### brew-cli migrate resend

Move a Resend account and codebase to Brew: plan by default (writes brew.migration.json), --apply to copy templates, contacts, segments, topics, suppressions and draft broadcasts

- Class: destructive
- Derived from `brew.brand.get(...)`, `brew.domains.list(...)`, `brew.appEmails.list(...)`, `brew.emails.import(...)`, `brew.appEmails.upsert(...)`, `brew.emails.delete(...)`, `brew.contacts.upsertMany(...)`, `brew.audiences.list(...)`, `brew.audiences.create(...)`, `brew.domains.unsubscribes.import(...)`
- `--apply` — Execute the plan: import templates as app emails, upsert contacts, create segment and topic audiences, import suppressions, copy draft broadcasts as Brew drafts (resumes from brew.migration.json)
- `--dry-run` — Plan and print the report without writing brew.migration.json
- `--only <phases...>` — Run only these phases: domains, templates, code, contacts, segments, suppressions, topics, broadcasts, webhooks, automations
- `--resend-key-stdin` — Read the Resend API key from stdin (else RESEND_API_KEY); it is never sent to Brew or printed
- `--cwd <dir>` — The project to scan and rewrite (default: the current directory)
- `--domain <domain>` — Brew domain (id or host) whose unsubscribe list takes Resend's suppressions and unsubscribed contacts
- `--manifest <path>` — Where the plan and progress live (default brew.migration.json)
- `--key-prefix <prefix>` — Prefix for the trigger keys made from template aliases and React Email file names (default none), e.g. resend.
- `--rewrite-imports` — With --apply: move files whose every send is a migrated template from 'resend' to '@brew.new/sdk/resend' and write lib/brew-resend-map.ts

```bash
RESEND_API_KEY=re_123 brew-cli migrate resend
printf %s "$RESEND_API_KEY" | brew-cli migrate resend --resend-key-stdin --json
brew-cli migrate resend --only templates code --dry-run
brew-cli migrate resend --apply --yes
brew-cli migrate resend --apply --rewrite-imports --domain news.example.com --yes
```

### brew-cli api

Raw authenticated request against the Brew public API
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@brew.new/cli",
"version": "0.21.0",
"version": "0.22.0",
"description": "Official agent-first command-line interface for the Brew public API.",
"license": "MIT",
"author": "Brew",
Expand Down
4 changes: 2 additions & 2 deletions packages/launchers/brew.new/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "brew.new",
"version": "0.21.0",
"version": "0.22.0",
"description": "Set up Brew email in your app with one command: npx brew.new init (auth and lifecycle email, the SDK, MCP for your editor).",
"license": "MIT",
"author": "Brew",
Expand Down Expand Up @@ -41,7 +41,7 @@
"node": ">=20"
},
"dependencies": {
"@brew.new/cli": "0.21.0"
"@brew.new/cli": "0.22.0"
},
"publishConfig": {
"access": "public"
Expand Down
4 changes: 2 additions & 2 deletions packages/launchers/create-brew/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-brew",
"version": "0.21.0",
"version": "0.22.0",
"description": "npm create brew: set up Brew email in your app (auth and lifecycle email, the SDK, MCP for your editor).",
"license": "MIT",
"author": "Brew",
Expand Down Expand Up @@ -41,7 +41,7 @@
"node": ">=20"
},
"dependencies": {
"@brew.new/cli": "0.21.0"
"@brew.new/cli": "0.22.0"
},
"publishConfig": {
"access": "public"
Expand Down
9 changes: 9 additions & 0 deletions skills/brew-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ replaces a kit key's design built from `--source-file` with the starter, and
any other key is refused (400).
`automations triggers create --key <key>` gives any trigger a key.

## Moving from Resend

`brew-cli migrate resend` plans by default (writes `brew.migration.json`,
changes nothing); `--apply --yes` copies templates, React Email files,
contacts, segments, topics, suppressions and draft broadcasts, and
resumes when re-run. Pass the Resend key on stdin with
`--resend-key-stdin`; it never goes to Brew. The
`brew-migrate-from-resend` skill has the full mapping.

## The contract you can rely on

- Exit codes: 0 ok · 1 API error · 2 usage · 3 auth · 4 confirmation
Expand Down
59 changes: 59 additions & 0 deletions skills/brew-migrate-from-resend/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
name: brew-migrate-from-resend
description: Move an app and its Resend account to Brew with `brew-cli migrate resend`, covering templates and React Email as app emails, contacts, segments, topics, suppressions and broadcast drafts, plus the code that calls resend.emails.send. Use when a project depends on `resend`, someone asks to switch from Resend to Brew, or a migration report (brew.migration.json) needs finishing.
---

# Migrate from Resend to Brew

Plan first, review with the person, then apply. Nothing in Resend is
changed or deleted, and the Resend key never leaves this machine for Brew.

```bash
# 1. Plan: reads Resend (2 requests a second) and Brew, scans the code,
# writes brew.migration.json. Changes nothing.
printf %s "$RESEND_API_KEY" | npx @brew.new/cli migrate resend --resend-key-stdin --json

# 2. Apply after the person reviewed the plan (destructive class: exit 4
# without --yes). Re-running resumes; done items are skipped.
printf %s "$RESEND_API_KEY" | npx @brew.new/cli migrate resend --resend-key-stdin --apply --domain <brew-marketing-domain> --yes --json
```

Ask the person for a **full-access** Resend key (a sending-only key exits 3
with `RESEND_KEY_SENDING_ONLY`). Pass it on stdin; never print it, write
it to a file, or put it in a command line that gets logged. Brew needs its
own key (`brew-cli login`, or `BREW_API_KEY`).

## What moves where

| Resend | Brew |
| --- | --- |
| Template (`{{{VAR}}}`) | App email by key (alias or name), published if it was; placeholders become `{{ trigger.VAR }}` |
| React Email file | App email by key (file name); props become merge tags |
| Contact | Contact; subscribed ones with an import consent record, unsubscribed stay unsubscribed; no state, left out |
| Segment | Boolean field `resend_segment_*` and an audience on it |
| Topic | Boolean field `resend_topic_*` (true = opted in) and an audience; one Resend request per contact |
| Suppression, unsubscribed contact | The `--domain`'s unsubscribe list |
| Draft broadcast | A Brew draft email (nothing is sent) |
| Domain, webhook, automation | Reported only |

Brew signs mail with the same DKIM selector as Resend (`resend._domainkey`),
so a domain cannot verify in both. Verify a subdomain in Brew (the plan
suggests one, e.g. `mail.acme.com`) and keep the Resend domain until
cutover.

## Finish the code

- Every remaining `resend.emails.send` / `resend.batch.send` is a blocking
`edit-file` next step with a suggested `getBrew().send(key, payload)`.
`attachments`, `cc`, `bcc`, `headers`, `scheduledAt` and raw `html` do
not carry over: say so to the person instead of dropping them silently.
- `--rewrite-imports` (with `--apply`) moves a file to
`@brew.new/sdk/resend` only when every send through it is a migrated
template, and writes `lib/brew-resend-map.ts`.
- Then send one migrated email to yourself (`brew-cli send <key> --to
<you> --yes`; before a domain verifies, Brew delivers to your team only)
and cut over: production gets `BREW_API_KEY`, then retire
`RESEND_API_KEY` and the `resend` package.

`brew.migration.json` holds ids, keys, counts and cursors, never an
address, a name or a key: it is safe to commit.
2 changes: 2 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ const GROUP_SUMMARIES: Readonly<Record<string, string>> = {
"Brew Insights: findings about the brand's email, plus its pulse, report, suggestions and memo",
integrations: 'Read the connected-integration catalog',
mcp: 'Connect AI editors and agents to the Brew MCP server',
migrate:
'Move to Brew from another email provider: plan, then apply (Resend)',
'inbox-placement-tests': 'Inbox placement (seed) test results',
notifications:
'What finished or failed: generations, sends, imports, domain checks',
Expand Down
12 changes: 9 additions & 3 deletions src/commands/app-emails/upsert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { UPSERT_APP_EMAIL_DEFAULT_TIMEOUT_MS } from '@brew.new/sdk'
import {
APP_EMAIL_SOURCE_FORMATS,
inlineInput,
inputSource,
oneOfFlag,
} from '../../lib/app-email-inputs'
import { defineCommand } from '../../lib/define-command'
Expand Down Expand Up @@ -52,7 +53,7 @@ export const appEmailsUpsertCommand = defineCommand({
{
flag: '--source-file <path>',
summary:
'Your own design source (HTML, MJML or React Email JSX), or - for stdin',
"Your own design source (HTML, MJML or React Email JSX), or - for stdin. Import modes (props, placeholders, variables) go in --input's source",
},
{
flag: '--format <format>',
Expand Down Expand Up @@ -89,6 +90,7 @@ export const appEmailsUpsertCommand = defineCommand({
'brew-cli app-emails upsert order.paid --source-file emails/order-paid.tsx --subject "Your order is confirmed" --yes',
'brew-cli app-emails upsert lifecycle.welcome --overwrite --yes',
`brew-cli app-emails upsert account.sign-in-code --source-file emails/sign-in-code.tsx --input '{"payloadSchema":{"fields":[{"key":"code","type":"string","required":true,"secret":true}]}}' --yes`,
`brew-cli app-emails upsert order.shipped --source-file emails/order-shipped.tsx --input '{"source":{"props":"merge_tags"}}' --yes`,
],
confirmSummary: ({ args, flags }) => {
if (flags.input === '-') {
Expand Down Expand Up @@ -129,10 +131,14 @@ export const appEmailsUpsertCommand = defineCommand({
const base = await readJsonFlag(ctx, flags.input, '--input')
const sourcePath = flagString(flags.sourceFile)
const content = await readTextFlag(ctx, sourcePath, '--source-file')
let source: { format: string; content: string } | undefined
let source: Readonly<Record<string, unknown>> | undefined
if (content !== undefined) {
// The file fills `format` and `content`; the rest of --input's
// `source` (the import modes) stays.
const baseSource = inputSource(base)
const format =
explicitFormat ??
flagString(baseSource?.format) ??
(sourcePath === undefined || sourcePath === '-'
? undefined
: FORMAT_BY_EXTENSION[extname(sourcePath).toLowerCase()])
Expand All @@ -141,7 +147,7 @@ export const appEmailsUpsertCommand = defineCommand({
'--format is required for this source: html | mjml | jsx.'
)
}
source = { format, content }
source = { ...baseSource, format, content }
} else if (explicitFormat !== undefined) {
throw new CliUsageError('--format applies to --source-file only.')
}
Expand Down
Loading