From ebe0112a54f046de26bca45e4fbada3c5e6e26ea Mon Sep 17 00:00:00 2001 From: heathweaver Date: Tue, 23 Jun 2026 19:48:20 +0000 Subject: [PATCH 1/2] fix(database): make `database link` usable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `database link` crashed unconditionally with `Unknown conflicting option "connectionString"`. The --hostname/--username/--password/--port options declared `conflicts: ["connectionString"]`, but `connectionString` is a positional argument, not an option, and cliffy's `conflicts` only accepts option names — so validation threw before any input was processed, breaking every invocation including `--help`. Remove the invalid `conflicts` (and the now-incorrect `required` on --hostname, which made the connection-string form impossible), and validate the connection-string-vs-flags mutual exclusion manually in the action. This surfaced a second bug: `port` was sent to the backend as a string (`expected number, received string`), so coerce it to a number. Verified `database link --dry-run` now succeeds against a real Postgres in both the --hostname/--port and connection-string forms. Fixes #104 Co-Authored-By: Claude Opus 4.8 (1M context) --- deploy/database.ts | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/deploy/database.ts b/deploy/database.ts index eb823b2..8942244 100644 --- a/deploy/database.ts +++ b/deploy/database.ts @@ -89,19 +89,10 @@ const databasesLinkCommand = new Command() "Test connection without linking", "link --dry-run my-db --hostname db.example.com", ) - .option("--hostname ", "The hostname to use for the database", { - required: true, - conflicts: ["connectionString"], - }) - .option("--username ", "The username to use for the database", { - conflicts: ["connectionString"], - }) - .option("--password ", "The password to use for the database", { - conflicts: ["connectionString"], - }) - .option("--port ", "The port to use for the database", { - conflicts: ["connectionString"], - }) + .option("--hostname ", "The hostname to use for the database") + .option("--username ", "The username to use for the database") + .option("--password ", "The password to use for the database") + .option("--port ", "The port to use for the database") .option("--cert ", "The SSL certificate to use for the database") .option( "--dry-run", @@ -111,6 +102,23 @@ const databasesLinkCommand = new Command() .action(actionHandler(async (config, options, name, connectionString) => { config.noCreate(); + // `connectionString` is a positional argument, so its mutual exclusion + // with --hostname/--port/--username/--password can't be expressed via + // cliffy's `conflicts` (which only accepts option names). Validate manually. + const hasIndividualFlags = options.hostname || options.port !== undefined || + options.username || options.password; + if (connectionString && hasIndividualFlags) { + throw new TypeError( + "Provide either a connection string or the individual " + + "--hostname/--port/--username/--password flags, not both.", + ); + } + if (!connectionString && !options.hostname) { + throw new TypeError( + "A connection string or --hostname is required.", + ); + } + const org = await getOrg(options, config, options.org); const trpcClient = createTrpcClient(options); @@ -152,7 +160,9 @@ const databasesLinkCommand = new Command() const connectionConfig = { hostname: hostname, - port: port || null, + // The backend expects a number; `--port ` and the parsed + // connection-string port both arrive as strings, so coerce here. + port: port != null ? Number(port) : null, username: username || null, password: password || null, certificate: options.cert || null, From 293595214609e89aa77387a8379187386c18b263 Mon Sep 17 00:00:00 2001 From: heathweaver Date: Tue, 23 Jun 2026 19:52:56 +0000 Subject: [PATCH 2/2] fix(database): send connection_config to createInstance The link path sent `connectionConfig` (camelCase) to `databases.createInstance`, while the dry-run path sends `connection_config` (snake_case) to `databases.testConnection`. The backend expects snake_case, so a real link failed with `connection_config: expected object, received undefined` even though --dry-run succeeded. Use the same snake_case key. Verified: `database link ` now returns "Successfully linked database" against a real Postgres. Co-Authored-By: Claude Opus 4.8 (1M context) --- deploy/database.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/database.ts b/deploy/database.ts index 8942244..10d55c5 100644 --- a/deploy/database.ts +++ b/deploy/database.ts @@ -180,7 +180,7 @@ const databasesLinkCommand = new Command() org: org, slug: name, engine, - connectionConfig, + connection_config: connectionConfig, }); console.log(`${green("✔")} Successfully linked database '${name}'.`); }