diff --git a/.changeset/happy-keys-rhyme.md b/.changeset/happy-keys-rhyme.md new file mode 100644 index 000000000000..adc235984085 --- /dev/null +++ b/.changeset/happy-keys-rhyme.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +Adds support for Hyperdrive, via `wrangler hyperdrive`. diff --git a/packages/wrangler/src/__tests__/hyperdrive.test.ts b/packages/wrangler/src/__tests__/hyperdrive.test.ts new file mode 100644 index 000000000000..9647ce6fc7b9 --- /dev/null +++ b/packages/wrangler/src/__tests__/hyperdrive.test.ts @@ -0,0 +1,270 @@ +import { rest } from "msw"; +import { endEventLoop } from "./helpers/end-event-loop"; +import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; +import { mockConsoleMethods } from "./helpers/mock-console"; +import { clearDialogs } from "./helpers/mock-dialogs"; +import { useMockIsTTY } from "./helpers/mock-istty"; +import { createFetchResult, msw } from "./helpers/msw"; +import { runInTempDir } from "./helpers/run-in-tmp"; +import { runWrangler } from "./helpers/run-wrangler"; + +describe("hyperdrive help", () => { + const std = mockConsoleMethods(); + runInTempDir(); + + it("should show help text when no arguments are passed", async () => { + await runWrangler("hyperdrive"); + await endEventLoop(); + + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.out).toMatchInlineSnapshot(` + "wrangler hyperdrive + + šŸš€ Configure Hyperdrive databases + + Commands: + wrangler hyperdrive create Create a Hyperdrive config + wrangler hyperdrive delete Delete a Hyperdrive config + wrangler hyperdrive get Get a Hyperdrive config + wrangler hyperdrive list List Hyperdrive configs + wrangler hyperdrive update Update a Hyperdrive config + + Flags: + -j, --experimental-json-config Experimental: Support wrangler.json [boolean] + -c, --config Path to .toml configuration file [string] + -e, --env Environment to use for operations and .env files [string] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] + + -------------------- + šŸ“£ Hyperdrive is currently in open beta + šŸ“£ Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose + šŸ“£ To give feedback, visit https://discord.gg/cloudflaredev + --------------------" + `); + }); + + it("should show help when an invalid argument is pased", async () => { + await expect(() => runWrangler("hyperdrive qwer")).rejects.toThrow( + "Unknown argument: qwer" + ); + + expect(std.err).toMatchInlineSnapshot(` + "X [ERROR] Unknown argument: qwer + + " + `); + expect(std.out).toMatchInlineSnapshot(` + " + wrangler hyperdrive + + šŸš€ Configure Hyperdrive databases + + Commands: + wrangler hyperdrive create Create a Hyperdrive config + wrangler hyperdrive delete Delete a Hyperdrive config + wrangler hyperdrive get Get a Hyperdrive config + wrangler hyperdrive list List Hyperdrive configs + wrangler hyperdrive update Update a Hyperdrive config + + Flags: + -j, --experimental-json-config Experimental: Support wrangler.json [boolean] + -c, --config Path to .toml configuration file [string] + -e, --env Environment to use for operations and .env files [string] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] + + -------------------- + šŸ“£ Hyperdrive is currently in open beta + šŸ“£ Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose + šŸ“£ To give feedback, visit https://discord.gg/cloudflaredev + --------------------" + `); + }); +}); + +describe("hyperdrive commands", () => { + mockAccountId(); + mockApiToken(); + runInTempDir(); + const { setIsTTY } = useMockIsTTY(); + + const std = mockConsoleMethods(); + + beforeEach(() => { + // @ts-expect-error we're using a very simple setTimeout mock here + jest.spyOn(global, "setTimeout").mockImplementation((fn, _period) => { + setImmediate(fn); + }); + setIsTTY(true); + }); + + afterEach(() => { + clearDialogs(); + }); + + it("should handle creating a hyperdrive config", async () => { + mockHyperdriveRequest(); + await runWrangler( + "hyperdrive create test123 --connection-string='postgresql://test:password@foo.us-east-2.aws.neon.tech:5432/neondb'" + ); + expect(std.out).toMatchInlineSnapshot(` + "šŸš§ Creating 'test123' + āœ… Created new Hyperdrive config + { + \\"id\\": \\"7a040c1eee714e91a30ea6707a2d125c\\", + \\"name\\": \\"test123\\", + \\"origin\\": { + \\"host\\": \\"foo.us-east-2.aws.neon.tech\\", + \\"port\\": 5432, + \\"database\\": \\"neondb\\", + \\"user\\": \\"test\\" + }, + \\"caching\\": { + \\"disabled\\": false + } + }" + `); + }); + + it("should handle listing configs", async () => { + mockHyperdriveRequest(); + await runWrangler("hyperdrive list"); + expect(std.out).toMatchInlineSnapshot(` + "šŸ“‹ Listing Hyperdrive configs + ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” + ā”‚ id ā”‚ name ā”‚ user ā”‚ host ā”‚ port ā”‚ database ā”‚ + ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ + ā”‚ fb94f15a95ce4afa803bb21794b2802c ā”‚ new-db ā”‚ dbuser ā”‚ database.server.com ā”‚ 3211 ā”‚ mydb ā”‚ + ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ + ā”‚ 7a040c1eee714e91a30ea6707a2d125c ā”‚ test123 ā”‚ test ā”‚ foo.us-east-2.aws.neon.tech ā”‚ 5432 ā”‚ neondb ā”‚ + ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜" + `); + }); + + it("should handle displaying a config", async () => { + mockHyperdriveRequest(); + await runWrangler("hyperdrive get 7a040c1eee714e91a30ea6707a2d125c"); + expect(std.out).toMatchInlineSnapshot(` + "{ + \\"id\\": \\"7a040c1eee714e91a30ea6707a2d125c\\", + \\"name\\": \\"test123\\", + \\"origin\\": { + \\"host\\": \\"foo.us-east-2.aws.neon.tech\\", + \\"port\\": 5432, + \\"database\\": \\"neondb\\", + \\"user\\": \\"test\\" + }, + \\"caching\\": { + \\"disabled\\": false + } + }" + `); + }); + + it("should handle deleting a config", async () => { + mockHyperdriveRequest(); + await runWrangler("hyperdrive delete 7a040c1eee714e91a30ea6707a2d125c"); + expect(std.out).toMatchInlineSnapshot(` + "šŸ—‘ļø Deleting Hyperdrive database config 7a040c1eee714e91a30ea6707a2d125c + āœ… Deleted" + `); + }); +}); + +/** Create a mock handler for Hyperdrive API */ +function mockHyperdriveRequest() { + msw.use( + rest.get( + "*/accounts/:accountId/hyperdrive/configs/7a040c1eee714e91a30ea6707a2d125c", + (req, res, ctx) => { + return res.once( + ctx.json( + createFetchResult( + { + id: "7a040c1eee714e91a30ea6707a2d125c", + name: "test123", + origin: { + host: "foo.us-east-2.aws.neon.tech", + port: 5432, + database: "neondb", + user: "test", + }, + caching: { + disabled: false, + }, + }, + + true + ) + ) + ); + } + ), + rest.post("*/accounts/:accountId/hyperdrive/configs", (req, res, ctx) => { + return res.once( + ctx.json( + createFetchResult( + { + id: "7a040c1eee714e91a30ea6707a2d125c", + name: "test123", + origin: { + host: "foo.us-east-2.aws.neon.tech", + port: 5432, + database: "neondb", + user: "test", + }, + caching: { + disabled: false, + }, + }, + true + ) + ) + ); + }), + rest.delete( + "*/accounts/:accountId/hyperdrive/configs/7a040c1eee714e91a30ea6707a2d125c", + (req, res, ctx) => { + return res.once(ctx.json(createFetchResult(null, true))); + } + ), + rest.get("*/accounts/:accountId/hyperdrive/configs", (req, res, ctx) => { + return res.once( + ctx.json( + createFetchResult( + [ + { + id: "fb94f15a95ce4afa803bb21794b2802c", + name: "new-db", + origin: { + host: "database.server.com", + port: 3211, + database: "mydb", + user: "dbuser", + }, + caching: { + disabled: false, + }, + }, + { + id: "7a040c1eee714e91a30ea6707a2d125c", + name: "test123", + origin: { + host: "foo.us-east-2.aws.neon.tech", + port: 5432, + database: "neondb", + user: "test", + }, + caching: { + disabled: false, + }, + }, + ], + true + ) + ) + ); + }) + ); +} diff --git a/packages/wrangler/src/__tests__/index.test.ts b/packages/wrangler/src/__tests__/index.test.ts index 1e1d9e649869..5ec6e0860aff 100644 --- a/packages/wrangler/src/__tests__/index.test.ts +++ b/packages/wrangler/src/__tests__/index.test.ts @@ -49,6 +49,7 @@ describe("wrangler", () => { wrangler r2 šŸ“¦ Interact with an R2 store wrangler dispatch-namespace šŸ“¦ Interact with a dispatch namespace wrangler d1 šŸ—„ Interact with a D1 database + wrangler hyperdrive šŸš€ Configure Hyperdrive databases wrangler constellation šŸ¤– Interact with Constellation models wrangler pubsub šŸ“® Interact and manage Pub/Sub Brokers wrangler mtls-certificate šŸŖŖ Manage certificates used for mTLS connections @@ -101,6 +102,7 @@ describe("wrangler", () => { wrangler r2 šŸ“¦ Interact with an R2 store wrangler dispatch-namespace šŸ“¦ Interact with a dispatch namespace wrangler d1 šŸ—„ Interact with a D1 database + wrangler hyperdrive šŸš€ Configure Hyperdrive databases wrangler constellation šŸ¤– Interact with Constellation models wrangler pubsub šŸ“® Interact and manage Pub/Sub Brokers wrangler mtls-certificate šŸŖŖ Manage certificates used for mTLS connections diff --git a/packages/wrangler/src/hyperdrive/client.ts b/packages/wrangler/src/hyperdrive/client.ts new file mode 100644 index 000000000000..59982e2802d9 --- /dev/null +++ b/packages/wrangler/src/hyperdrive/client.ts @@ -0,0 +1,76 @@ +import { fetchResult } from "../cfetch"; +import { requireAuth } from "../user"; +import type { Config } from "../config"; + +export type HyperdriveConfig = { + id: string; + name: string; + origin: PublicOrigin; +}; + +export type Origin = { + host?: string; + port?: number; +}; + +export type PublicOrigin = Origin & { + scheme?: string; + database?: string; + user?: string; +}; + +export type OriginWithPassword = PublicOrigin & { + password?: string; +}; + +export type CreateUpdateHyperdriveBody = { + name?: string; + origin: OriginWithPassword; +}; + +export async function createConfig( + config: Config, + body: CreateUpdateHyperdriveBody +): Promise { + const accountId = await requireAuth(config); + return await fetchResult(`/accounts/${accountId}/hyperdrive/configs`, { + method: "POST", + body: JSON.stringify(body), + }); +} + +export async function deleteConfig(config: Config, id: string): Promise { + const accountId = await requireAuth(config); + return await fetchResult(`/accounts/${accountId}/hyperdrive/configs/${id}`, { + method: "DELETE", + }); +} + +export async function getConfig( + config: Config, + id: string +): Promise { + const accountId = await requireAuth(config); + return await fetchResult(`/accounts/${accountId}/hyperdrive/configs/${id}`, { + method: "GET", + }); +} + +export async function listConfigs(config: Config): Promise { + const accountId = await requireAuth(config); + return await fetchResult(`/accounts/${accountId}/hyperdrive/configs`, { + method: "GET", + }); +} + +export async function updateConfig( + config: Config, + id: string, + body: CreateUpdateHyperdriveBody +): Promise { + const accountId = await requireAuth(config); + return await fetchResult(`/accounts/${accountId}/hyperdrive/configs/${id}`, { + method: "PUT", + body: JSON.stringify(body), + }); +} diff --git a/packages/wrangler/src/hyperdrive/common.ts b/packages/wrangler/src/hyperdrive/common.ts new file mode 100644 index 000000000000..b60d1a6ee7ed --- /dev/null +++ b/packages/wrangler/src/hyperdrive/common.ts @@ -0,0 +1,2 @@ +export const hyperdriveBetaWarning = + "--------------------\nšŸ“£ Hyperdrive is currently in open beta\nšŸ“£ Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose\nšŸ“£ To give feedback, visit https://discord.gg/cloudflaredev\n--------------------\n"; diff --git a/packages/wrangler/src/hyperdrive/create.ts b/packages/wrangler/src/hyperdrive/create.ts new file mode 100644 index 000000000000..3d87e5fac591 --- /dev/null +++ b/packages/wrangler/src/hyperdrive/create.ts @@ -0,0 +1,83 @@ +import { readConfig } from "../config"; +import { logger } from "../logger"; +import { createConfig } from "./client"; +import { hyperdriveBetaWarning } from "./common"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; + +export function options(yargs: CommonYargsArgv) { + return yargs + .positional("name", { + type: "string", + demandOption: true, + description: "The name of the Hyperdrive config", + }) + .options({ + "connection-string": { + type: "string", + demandOption: true, + describe: + "The connection string for the database you want Hyperdrive to connect to - ex: protocol://user:password@host:port/database", + }, + }) + .epilogue(hyperdriveBetaWarning); +} + +export async function handler( + args: StrictYargsOptionsToInterface +) { + const config = readConfig(args.config, args); + + const url = new URL(args.connectionString); + + if (url.protocol === "") { + logger.log("You must specify the database protocol - e.g. 'postgresql:'."); + } else if ( + url.protocol !== "postgresql:" && + url.protocol !== "postgres:" && + url.protocol !== "" + ) { + logger.log( + "Only PostgreSQL or PostgreSQL compatible databases are currently supported." + ); + } else if (url.host === "") { + logger.log( + "You must provide a hostname or IP address in your connection string - e.g. 'user:password@database-hostname.example.com:5432/databasename" + ); + } else if (url.port === "") { + logger.log( + "You must provide a port number - e.g. 'user:password@database.example.com:port/databasename" + ); + } else if (url.pathname === "") { + logger.log( + "You must provide a database name as the path component - e.g. /postgres" + ); + } else if (url.username === "") { + logger.log( + "You must provide a username - e.g. 'user:password@database.example.com:port/databasename'" + ); + } else if (url.password === "") { + logger.log( + "You must provide a password - e.g. 'user:password@database.example.com:port/databasename' " + ); + } else { + logger.log(`šŸš§ Creating '${args.name}'`); + const database = await createConfig(config, { + name: args.name, + origin: { + host: url.hostname, + port: parseInt(url.port), + scheme: url.protocol, + database: url.pathname.replace("/", ""), + user: url.username, + password: url.password, + }, + }); + logger.log( + `āœ… Created new Hyperdrive config\n`, + JSON.stringify(database, null, 2) + ); + } +} diff --git a/packages/wrangler/src/hyperdrive/delete.ts b/packages/wrangler/src/hyperdrive/delete.ts new file mode 100644 index 000000000000..9aef85ab913f --- /dev/null +++ b/packages/wrangler/src/hyperdrive/delete.ts @@ -0,0 +1,28 @@ +import { readConfig } from "../config"; +import { logger } from "../logger"; +import { deleteConfig } from "./client"; +import { hyperdriveBetaWarning } from "./common"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; + +export function options(yargs: CommonYargsArgv) { + return yargs + .positional("id", { + type: "string", + demandOption: true, + description: "The ID of the Hyperdrive config", + }) + .epilogue(hyperdriveBetaWarning); +} + +export async function handler( + args: StrictYargsOptionsToInterface +) { + const config = readConfig(args.config, args); + + logger.log(`šŸ—‘ļø Deleting Hyperdrive database config ${args.id}`); + await deleteConfig(config, args.id); + logger.log(`āœ… Deleted`); +} diff --git a/packages/wrangler/src/hyperdrive/get.ts b/packages/wrangler/src/hyperdrive/get.ts new file mode 100644 index 000000000000..ef409913cfac --- /dev/null +++ b/packages/wrangler/src/hyperdrive/get.ts @@ -0,0 +1,27 @@ +import { readConfig } from "../config"; +import { logger } from "../logger"; +import { getConfig } from "./client"; +import { hyperdriveBetaWarning } from "./common"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; + +export function options(yargs: CommonYargsArgv) { + return yargs + .positional("id", { + type: "string", + demandOption: true, + description: "The ID of the Hyperdrive config", + }) + .epilogue(hyperdriveBetaWarning); +} + +export async function handler( + args: StrictYargsOptionsToInterface +) { + const config = readConfig(args.config, args); + + const database = await getConfig(config, args.id); + logger.log(JSON.stringify(database, null, 2)); +} diff --git a/packages/wrangler/src/hyperdrive/index.ts b/packages/wrangler/src/hyperdrive/index.ts new file mode 100644 index 000000000000..8490e4798079 --- /dev/null +++ b/packages/wrangler/src/hyperdrive/index.ts @@ -0,0 +1,32 @@ +import { hyperdriveBetaWarning } from "./common"; +import { options as createOptions, handler as createHandler } from "./create"; +import { options as deleteOptions, handler as deleteHandler } from "./delete"; +import { options as getOptions, handler as getHandler } from "./get"; +import { options as listOptions, handler as listHandler } from "./list"; +import { options as updateOptions, handler as updateHandler } from "./update"; +import type { CommonYargsArgv } from "../yargs-types"; + +export function hyperdrive(yargs: CommonYargsArgv) { + return yargs + .command( + "create ", + "Create a Hyperdrive config", + createOptions, + createHandler + ) + .command( + "delete ", + "Delete a Hyperdrive config", + deleteOptions, + deleteHandler + ) + .command("get ", "Get a Hyperdrive config", getOptions, getHandler) + .command("list", "List Hyperdrive configs", listOptions, listHandler) + .command( + "update ", + "Update a Hyperdrive config", + updateOptions, + updateHandler + ) + .epilogue(hyperdriveBetaWarning); +} diff --git a/packages/wrangler/src/hyperdrive/list.ts b/packages/wrangler/src/hyperdrive/list.ts new file mode 100644 index 000000000000..04c630b85590 --- /dev/null +++ b/packages/wrangler/src/hyperdrive/list.ts @@ -0,0 +1,31 @@ +import { readConfig } from "../config"; +import { logger } from "../logger"; +import { listConfigs } from "./client"; +import { hyperdriveBetaWarning } from "./common"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; + +export function options(yargs: CommonYargsArgv) { + return yargs.epilogue(hyperdriveBetaWarning); +} + +export async function handler( + args: StrictYargsOptionsToInterface +) { + const config = readConfig(args.config, args); + + logger.log(`šŸ“‹ Listing Hyperdrive configs`); + const databases = await listConfigs(config); + logger.table( + databases.map((database) => ({ + id: database.id, + name: database.name, + user: database.origin.user ?? "", + host: database.origin.host ?? "", + port: database.origin.port?.toString() ?? "", + database: database.origin.database ?? "", + })) + ); +} diff --git a/packages/wrangler/src/hyperdrive/update.ts b/packages/wrangler/src/hyperdrive/update.ts new file mode 100644 index 000000000000..a640b344089e --- /dev/null +++ b/packages/wrangler/src/hyperdrive/update.ts @@ -0,0 +1,82 @@ +import { readConfig } from "../config"; +import { logger } from "../logger"; +import { getConfig, updateConfig } from "./client"; +import { hyperdriveBetaWarning } from "./common"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; +import type { CreateUpdateHyperdriveBody } from "./client"; + +export function options(yargs: CommonYargsArgv) { + return yargs + .positional("id", { + type: "string", + demandOption: true, + description: "The ID of the Hyperdrive config", + }) + .options({ + "origin-host": { + type: "string", + describe: "The host of the origin database", + }, + "origin-port": { + type: "number", + describe: "The port number of the origin database", + }, + "origin-scheme": { + type: "string", + describe: + "The scheme used to connect to the origin database - e.g. postgresql or postgres", + }, + database: { + type: "string", + describe: "The name of the database within the origin database", + }, + "origin-user": { + type: "string", + describe: "The username used to connect to the origin database", + }, + "origin-password": { + type: "string", + describe: "The password used to connect to the origin database", + }, + }) + .epilogue(hyperdriveBetaWarning); +} + +export async function handler( + args: StrictYargsOptionsToInterface +) { + const config = readConfig(args.config, args); + + logger.log(`šŸš§ Updating '${args.id}'`); + const database = (await getConfig( + config, + args.id + )) as CreateUpdateHyperdriveBody; + if (args.originHost) { + database.origin.host = args.originHost; + } + if (args.originPort) { + database.origin.port = args.originPort; + } + if (args.originScheme) { + database.origin.scheme = args.originScheme; + } + if (args.database) { + database.origin.database = args.database; + } + if (args.originUser) { + database.origin.user = args.originUser; + } + if (args.originPassword) { + database.origin.password = args.originPassword; + } + + const updated = await updateConfig(config, args.id, database); + logger.log( + `āœ… Updated ${updated.id} Hyperdrive config\n`, + JSON.stringify(updated, null, 2) + ); +} diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index a7a0db21309b..d2ff2613ad94 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -33,6 +33,7 @@ import { devHandler, devOptions } from "./dev"; import { workerNamespaceCommands } from "./dispatch-namespace"; import { docsHandler, docsOptions } from "./docs"; import { generateHandler, generateOptions } from "./generate"; +import { hyperdrive } from "./hyperdrive/index"; import { initHandler, initOptions } from "./init"; import { kvNamespace, kvKey, kvBulk } from "./kv"; import { logBuildFailure, logger } from "./logger"; @@ -450,6 +451,15 @@ export function createCLIParser(argv: string[]) { return d1(d1Yargs.command(subHelp)); }); + // hyperdrive + wrangler.command( + "hyperdrive", + "šŸš€ Configure Hyperdrive databases", + (hyperdriveYargs) => { + return hyperdrive(hyperdriveYargs.command(subHelp)); + } + ); + // ai wrangler.command( "constellation",