diff --git a/README.md b/README.md index 3fd21f7..e0f8b79 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ Every command that returns account data outputs JSON, so you can pipe to `jq`. T upstash start-redis # free temporary DB, no account needed upstash redis list upstash redis create --name my-db --region us-east-1 +upstash redis create-branch --db-id $DB_ID --name staging # a copy of the data to test against upstash redis exec --db-url $URL --db-token $TOKEN GET key # Vector diff --git a/src/commands/redis/create-branch.ts b/src/commands/redis/create-branch.ts new file mode 100644 index 0000000..3b18521 --- /dev/null +++ b/src/commands/redis/create-branch.ts @@ -0,0 +1,20 @@ +import { Command } from "commander"; +import { resolveAuth } from "../../auth.js"; +import { request } from "../../client.js"; +import { printJSON } from "../../output.js"; +import type { Database } from "../../types.js"; + +export function registerCreateBranch(redis: Command): void { + redis + .command("create-branch") + .description( + "Create a branch: a point-in-time copy of a Redis database with its own endpoint and password, billed as pay-as-you-go. It starts in state creating; poll `redis get` until state is active.", + ) + .requiredOption("--db-id ", "ID of the database or branch to copy") + .requiredOption("--name ", "Branch name, unique among the branches of the database") + .action(async (flags: { dbId: string; name: string }, command: Command) => { + const auth = resolveAuth(command); + const branch = await request(auth, "POST", `/v2/redis/database/${flags.dbId}/branch`, { name: flags.name }); + printJSON(branch); + }); +} diff --git a/src/commands/redis/index.ts b/src/commands/redis/index.ts index c339b4a..fc79e5b 100644 --- a/src/commands/redis/index.ts +++ b/src/commands/redis/index.ts @@ -1,5 +1,6 @@ import { Command } from "commander"; import { registerCreate } from "./create.js"; +import { registerCreateBranch } from "./create-branch.js"; import { registerList } from "./list.js"; import { registerGet } from "./get.js"; import { registerDelete } from "./delete.js"; @@ -22,6 +23,7 @@ export function registerRedis(program: Command): void { const redis = program.command("redis").description("Manage Redis databases"); registerCreate(redis); + registerCreateBranch(redis); registerList(redis); registerGet(redis); registerDelete(redis); diff --git a/src/types.ts b/src/types.ts index 37ef0d6..49361c4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,6 +26,8 @@ export type Region = (typeof REGIONS)[number]; export interface Database { database_id: string; database_name: string; + root_database_id?: string; + source_database_id?: string; password?: string; endpoint?: string; port?: number; diff --git a/tests/unit/redis-branch.test.ts b/tests/unit/redis-branch.test.ts new file mode 100644 index 0000000..2e6a0a2 --- /dev/null +++ b/tests/unit/redis-branch.test.ts @@ -0,0 +1,29 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { createRedisProgram, runCommand } from "../helpers/program.js"; + +const originalEnv = { ...process.env }; + +beforeEach(() => { + process.env = { ...originalEnv, UPSTASH_EMAIL: "user@example.com", UPSTASH_API_KEY: "api-key" }; +}); + +afterEach(() => { + vi.restoreAllMocks(); + process.env = { ...originalEnv }; +}); + +describe("redis create-branch", () => { + it("posts the name to the source database's branch route and prints the branch", async () => { + const branch = { database_id: "branch_1", database_name: "staging", root_database_id: "db_123", state: "creating" }; + const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(Response.json(branch)); + const program = await createRedisProgram(); + + const output = await runCommand(program, ["redis", "create-branch", "--db-id", "db_123", "--name", "staging"]); + + const [url, init] = fetchSpy.mock.calls[0] as [string, RequestInit]; + expect(url).toBe("https://api.upstash.com/v2/redis/database/db_123/branch"); + expect(init.method).toBe("POST"); + expect(JSON.parse(init.body as string)).toEqual({ name: "staging" }); + expect(output).toEqual(branch); + }); +});