Skip to content

feat: add create CLI with --rename-field/--rename-table scaffolding - #59

Open
mvanhorn wants to merge 2 commits into
convex:mainfrom
mvanhorn:feat/cli-create-rename-field
Open

mvanhorn wants to merge 2 commits into
convex:mainfrom
mvanhorn:feat/cli-create-rename-field

Conversation

@mvanhorn

@mvanhorn mvanhorn commented Jul 4, 2026

Copy link
Copy Markdown

Summary

Adds a create CLI to the migrations component, per @ianmacartney's suggestion on convex/convex-backend#477:

What do you think about making this a TUI under a CLI command for the migrations component? npx @convex-dev/migrations create [--rename-field] ... That seems like the more idiomatic place to put it, and it could then be used by agents similarly. The CLI could also help with scaffolding the component setup if you hadn't installed / added it to your app yet.

What it does

  • npx @convex-dev/migrations create [name] --rename-field users.oldName=newName scaffolds a field-rename migration (copy old to new, unset old) without prompting
  • --rename-table old=new scaffolds the documented table-rename pattern
  • No flags: interactive prompts (plain readline, zero new runtime dependencies)
  • Missing-component detection: if convex.config.ts has no migrations entry, the CLI prints the install/config steps instead of failing opaquely
  • --json prints a machine-readable success summary so agents can consume it

Testing

  • npm run build, npm run typecheck, npm run lint pass
  • Vitest unit tests cover both scaffold generators
  • Smoke-tested the built CLI: help, --rename-field scaffold generation into a bare project, setup detection, and --json output

Refs convex/convex-backend#477

Adds npx-runnable CLI (bin: convex-migrations) with a create command:
--rename-field table.old=new and --rename-table old=new scaffold the
documented migration patterns non-interactively; with no flags it prompts.
Detects a missing component setup (no migrations entry in convex.config.ts)
and prints the install/config steps instead of failing. --json emits an
agent-friendly summary. Suggested by @ianmacartney in
convex/convex-backend#477.
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a new convex-migrations CLI to the package. It registers a bin entry in package.json, adds a CLI entrypoint (src/cli.ts), a create command implementation (src/cli/main.ts) supporting flag-based and interactive migration scaffolding, and a code generator module (src/cli/generator.ts) that renders rename-field, rename-table, and custom migration definitions with accompanying tests. ESLint configuration is updated to add a Node-environment block for the new CLI files.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as cli.ts / main.ts
  participant Generator as generator.ts
  participant FS as convex/migrations.ts

  User->>CLI: convex-migrations create [--rename-field/--rename-table]
  CLI->>CLI: parseArgs / buildCreateRequest
  alt flags supplied
    CLI->>Generator: render*Migration(spec)
  else interactive prompt
    CLI->>User: prompt for kind, values, name
    User-->>CLI: input
    CLI->>Generator: render*Migration(spec)
  end
  Generator-->>CLI: GeneratedMigration { name, definition }
  CLI->>FS: detect convex.config, check existing exports
  CLI->>FS: create or append migrations.ts
  CLI-->>User: success message / JSON output or error
Loading

Compact metadata:

  • Lines changed: ~+723/-1 across 6 files
  • Estimated review effort: High

Related issues: None specified in provided summaries.

Related PRs: None specified in provided summaries.

Suggested labels: enhancement, cli

Suggested reviewers: None specified in provided summaries.

🐰 A rabbit taps a fresh new key,
"convex-migrations create" — hop with glee,
Fields renamed, tables swapped with care,
Prompts appear when flags aren't there,
A scaffold born from CLI decree.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a create CLI with rename-field and rename-table scaffolding.
Description check ✅ Passed The description is directly about the new migrations create CLI and matches the implemented scaffolding and JSON output features.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
src/cli/generator.test.ts (1)

1-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding coverage for renderCustomMigration, parse-error paths, and toExportName edge cases.

Current tests only cover the happy paths for renderRenameFieldMigration/renderRenameTableMigration. Missing coverage: renderCustomMigration, parseRenameFieldSpec/parseRenameTableSpec error cases (malformed input, same old/new value), and toExportName's reserved-word/non-identifier fallback branches — all of which contain non-trivial logic.

🤖 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 `@src/cli/generator.test.ts` around lines 1 - 69, Add tests to cover the
missing generator branches: use the existing generator helpers in
generator.test.ts to exercise renderCustomMigration, and add cases for
parseRenameFieldSpec and parseRenameTableSpec that verify malformed input and
same old/new value errors. Also add focused tests for toExportName covering
reserved words and invalid identifiers so the fallback naming path is validated
alongside the current happy-path rename migration coverage.
src/cli/generator.ts (1)

122-144: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Rename-table scaffold changes document IDs — worth calling out.

The generated migration inserts a new document and deletes the old one, so any other document that stores an Id<oldTable> (via v.id(...)) will silently point to a non-existent document after the migration runs. This matches Convex's documented workaround for the lack of native table renames — confirmed by Convex's own community: Renaming columns or tables requires a flow like add‑new‑field + migrate + remove‑old‑field which can get rough when using an entire new terminology over the system. Since this is inherent to the pattern (not a bug), consider emitting a comment in the generated scaffold warning that references to the old table's IDs will break, so first-time users of this CLI aren't caught off guard.

💡 Suggested comment addition
   const definition = `export const ${toExportName(name)} = migrations.define({
   table: ${quote(oldTable)},
+  // NOTE: this creates new documents with new _ids in ${quote(newTable)}.
+  // Any other documents referencing ${quote(oldTable)} ids will need to be updated separately.
   migrateOne: async (ctx, doc) => {
🤖 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 `@src/cli/generator.ts` around lines 122 - 144, The rename-table migration
scaffold in renderRenameTableMigration should warn that it replaces documents,
not just renames them. Add a generated comment near the migration definition
explaining that inserting into the new table and deleting from the old one will
invalidate any stored Id<oldTable> references (for example via v.id), and point
users to the need to update dependent fields before running the migration.
src/cli/main.ts (1)

105-134: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Inconsistent JSON output shape between success and error.

On success, JSON.stringify(result, null, 2) has no ok/success discriminator, but the error path returns { ok: false, error: ... }. A consumer of --json output parsing for machine-readable results has to rely on exit code alone to distinguish success/failure rather than a uniform schema — this undercuts the "machine-readable success summaries" goal called out in the PR.

♻️ Suggested fix
     if (parsed.json) {
-      writeLine(io.stdout, JSON.stringify(result, null, 2));
+      writeLine(io.stdout, JSON.stringify({ ok: true, ...result }, null, 2));
     } else {
🤖 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 `@src/cli/main.ts` around lines 105 - 134, The JSON output from the CLI is
inconsistent between success and failure in main(): success writes the raw
createMigration result, while the error path wraps the message in an { ok:
false, error: ... } object. Update the parsed.json branch in the try path to
emit a uniform machine-readable schema with an explicit success discriminator
(for example, matching the error shape with ok/success plus the result payload),
so consumers can reliably parse both outcomes without depending only on exit
code.
🤖 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.

Nitpick comments:
In `@src/cli/generator.test.ts`:
- Around line 1-69: Add tests to cover the missing generator branches: use the
existing generator helpers in generator.test.ts to exercise
renderCustomMigration, and add cases for parseRenameFieldSpec and
parseRenameTableSpec that verify malformed input and same old/new value errors.
Also add focused tests for toExportName covering reserved words and invalid
identifiers so the fallback naming path is validated alongside the current
happy-path rename migration coverage.

In `@src/cli/generator.ts`:
- Around line 122-144: The rename-table migration scaffold in
renderRenameTableMigration should warn that it replaces documents, not just
renames them. Add a generated comment near the migration definition explaining
that inserting into the new table and deleting from the old one will invalidate
any stored Id<oldTable> references (for example via v.id), and point users to
the need to update dependent fields before running the migration.

In `@src/cli/main.ts`:
- Around line 105-134: The JSON output from the CLI is inconsistent between
success and failure in main(): success writes the raw createMigration result,
while the error path wraps the message in an { ok: false, error: ... } object.
Update the parsed.json branch in the try path to emit a uniform machine-readable
schema with an explicit success discriminator (for example, matching the error
shape with ok/success plus the result payload), so consumers can reliably parse
both outcomes without depending only on exit code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: get-convex/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6ed1c79f-3b96-498d-a7ee-d9087199f5b3

📥 Commits

Reviewing files that changed from the base of the PR and between 2219852 and 244bf43.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • eslint.config.js
  • package.json
  • src/cli.ts
  • src/cli/generator.test.ts
  • src/cli/generator.ts
  • src/cli/main.ts

@pkg-pr-new

pkg-pr-new Bot commented Jul 20, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@convex-dev/migrations@59

commit: 244bf43

The generated migration copies each document into the new table, so it
gets a new _id and any v.id(oldTable) reference elsewhere goes stale.
Say so in the generated code.
@mvanhorn

Copy link
Copy Markdown
Author

Following up on the CodeRabbit note about rename-table changing document IDs: 5356804 puts that warning in the generated code. The scaffold now says that copying gives each document a new _id, and that v.id("<oldTable>") references in other tables need updating or they will point at deleted documents. The generator test covers the new lines; vitest, tsc --noEmit and eslint pass.

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