From 19163c54cf8f257e93f732bc8a3d6c59233ecfee Mon Sep 17 00:00:00 2001 From: Erika Wallace Date: Thu, 8 Jan 2026 15:56:43 -0500 Subject: [PATCH 1/5] uncomment and update commands --- .../pg/connection-pooling/attach.ts | 13 ++++++------- .../src/oldCommands/pg/credentials/create.ts | 15 +++++++-------- .../src/oldCommands/pg/credentials/destroy.ts | 19 ++++++++----------- .../pg/credentials/repair-default.ts | 17 ++++++++--------- .../src/oldCommands/pg/credentials/rotate.ts | 15 ++++++--------- .../cli/src/oldCommands/pg/credentials/url.ts | 17 ++++++++--------- 6 files changed, 43 insertions(+), 53 deletions(-) diff --git a/packages/cli/src/oldCommands/pg/connection-pooling/attach.ts b/packages/cli/src/oldCommands/pg/connection-pooling/attach.ts index 5141140499..134ff021fe 100644 --- a/packages/cli/src/oldCommands/pg/connection-pooling/attach.ts +++ b/packages/cli/src/oldCommands/pg/connection-pooling/attach.ts @@ -1,12 +1,12 @@ -/* -import color from '@heroku-cli/color' +import {color} from '@heroku-cli/color' import {Command, flags} from '@heroku-cli/command' import {Args, ux} from '@oclif/core' import * as Heroku from '@heroku-cli/schema' -import heredoc from 'tsheredoc' -import {essentialPlan} from '../../../lib/pg/util' +import tsheredoc from 'tsheredoc' import {utils} from '@heroku/heroku-cli-util' -import {nls} from '../../../nls' +import {nls} from '../../../nls.js' + +const heredoc = tsheredoc.default export default class Attach extends Command { static topic = 'pg' @@ -31,7 +31,7 @@ export default class Attach extends Command { const dbResolver = new utils.pg.DatabaseResolver(this.heroku) const {addon: db} = await dbResolver.getAttachment(app, args.database) - if (essentialPlan(db)) + if (utils.pg.isEssentialDatabase(db)) ux.error('You can’t perform this operation on Essential-tier databases.') ux.action.start(`Enabling Connection Pooling on ${color.yellow(db.name)} to ${color.magenta(app)}`) @@ -48,4 +48,3 @@ export default class Attach extends Command { ux.action.stop(`done, v${releases[0].version}`) } } -*/ diff --git a/packages/cli/src/oldCommands/pg/credentials/create.ts b/packages/cli/src/oldCommands/pg/credentials/create.ts index 4427b02f1b..ebc5ddea14 100644 --- a/packages/cli/src/oldCommands/pg/credentials/create.ts +++ b/packages/cli/src/oldCommands/pg/credentials/create.ts @@ -1,11 +1,11 @@ -/* -import color from '@heroku-cli/color' +import {color} from '@heroku-cli/color' import {Command, flags} from '@heroku-cli/command' import {Args, ux} from '@oclif/core' -import heredoc from 'tsheredoc' +import tsheredoc from 'tsheredoc' import {utils} from '@heroku/heroku-cli-util' -import {essentialPlan} from '../../../lib/pg/util' -import {nls} from '../../../nls' +import {nls} from '../../../nls.js' + +const heredoc = tsheredoc.default export default class Create extends Command { static topic = 'pg' @@ -25,7 +25,7 @@ export default class Create extends Command { const {app, name} = flags const dbResolver = new utils.pg.DatabaseResolver(this.heroku) const {addon: db} = await dbResolver.getAttachment(app, args.database) - if (essentialPlan(db)) { + if (utils.pg.isEssentialDatabase(db)) { throw new Error("You can't create a custom credential on Essential-tier databases.") } @@ -37,10 +37,9 @@ export default class Create extends Command { const attachCmd = `heroku addons:attach ${db.name} --credential ${name} -a ${app}` const psqlCmd = `heroku pg:psql ${db.name} -a ${app}` - ux.log(heredoc(` + ux.stdout(heredoc(` Please attach the credential to the apps you want to use it in by running ${color.cyan.bold(attachCmd)}. Please define the new grants for the credential within Postgres: ${color.cyan.bold(psqlCmd)}.`)) } } -*/ diff --git a/packages/cli/src/oldCommands/pg/credentials/destroy.ts b/packages/cli/src/oldCommands/pg/credentials/destroy.ts index 38d3757510..9761a83b11 100644 --- a/packages/cli/src/oldCommands/pg/credentials/destroy.ts +++ b/packages/cli/src/oldCommands/pg/credentials/destroy.ts @@ -1,12 +1,10 @@ -/* -import color from '@heroku-cli/color' +import {color} from '@heroku-cli/color' import {Command, flags} from '@heroku-cli/command' import {Args, ux} from '@oclif/core' import * as Heroku from '@heroku-cli/schema' -import {essentialPlan} from '../../../lib/pg/util' import {utils} from '@heroku/heroku-cli-util' -import confirmCommand from '../../../lib/confirmCommand' -import {nls} from '../../../nls' +import ConfirmCommand from '../../../lib/confirmCommand.js' +import {nls} from '../../../nls.js' export default class Destroy extends Command { static topic = 'pg'; @@ -14,7 +12,7 @@ export default class Destroy extends Command { static example = '$ heroku pg:credentials:destroy postgresql-transparent-56874 --name cred-name -a woodstock-production'; static flags = { name: flags.string({char: 'n', required: true, description: 'unique identifier for the credential'}), - confirm: flags.string({char: 'c'}), + confirm: flags.string({char: 'c', hidden: true}), app: flags.app({required: true}), remote: flags.remote(), }; @@ -33,7 +31,7 @@ export default class Destroy extends Command { const dbResolver = new utils.pg.DatabaseResolver(this.heroku) const {addon: db} = await dbResolver.getAttachment(app, database) - if (essentialPlan(db)) { + if (utils.pg.isEssentialDatabase(db)) { throw new Error("You can't destroy the default credential on Essential-tier databases.") } @@ -44,12 +42,11 @@ export default class Destroy extends Command { throw new Error(`Credential ${name} must be detached from the app${credAttachmentApps.length > 1 ? 's' : ''} ${credAttachmentApps.map(appName => color.app(appName || '')) .join(', ')} before destroying.`) - await confirmCommand(app, confirm) + await new ConfirmCommand().confirm(app, confirm) ux.action.start(`Destroying credential ${color.cyan.bold(name)}`) await this.heroku.delete(`/postgres/v0/databases/${db.name}/credentials/${encodeURIComponent(name)}`, {hostname: utils.pg.host()}) ux.action.stop() - ux.log(`The credential has been destroyed within ${db.name}.`) - ux.log(`Database objects owned by ${name} will be assigned to the default credential.`) + ux.stdout(`The credential has been destroyed within ${db.name}.`) + ux.stdout(`Database objects owned by ${name} will be assigned to the default credential.`) } } -*/ diff --git a/packages/cli/src/oldCommands/pg/credentials/repair-default.ts b/packages/cli/src/oldCommands/pg/credentials/repair-default.ts index 842b46b303..9e8bab6654 100644 --- a/packages/cli/src/oldCommands/pg/credentials/repair-default.ts +++ b/packages/cli/src/oldCommands/pg/credentials/repair-default.ts @@ -1,18 +1,18 @@ -/* import {Command, flags} from '@heroku-cli/command' import {Args, ux} from '@oclif/core' import {utils} from '@heroku/heroku-cli-util' -import {essentialPlan} from '../../../lib/pg/util' -import confirmCommand from '../../../lib/confirmCommand' -import heredoc from 'tsheredoc' -import {nls} from '../../../nls' +import ConfirmCommand from '../../../lib/confirmCommand.js' +import tsheredoc from 'tsheredoc' +import {nls} from '../../../nls.js' + +const heredoc = tsheredoc.default export default class RepairDefault extends Command { static topic = 'pg'; static description = 'repair the permissions of the default credential within database'; static example = '$ heroku pg:credentials:repair-default postgresql-something-12345'; static flags = { - confirm: flags.string({char: 'c'}), + confirm: flags.string({char: 'c', hidden: true}), app: flags.app({required: true}), remote: flags.remote(), }; @@ -27,9 +27,9 @@ export default class RepairDefault extends Command { const {database} = args const dbResolver = new utils.pg.DatabaseResolver(this.heroku) const {addon: db} = await dbResolver.getAttachment(app, database) - if (essentialPlan(db)) + if (utils.pg.isEssentialDatabase(db)) throw new Error("You can't perform this operation on Essential-tier databases.") - await confirmCommand(app, confirm, heredoc(` + await new ConfirmCommand().confirm(app, confirm, heredoc(` Destructive Action Ownership of all database objects owned by additional credentials will be transferred to the default credential. This command will also grant the default credential admin option for all additional credentials. @@ -39,4 +39,3 @@ export default class RepairDefault extends Command { ux.action.stop() } } -*/ diff --git a/packages/cli/src/oldCommands/pg/credentials/rotate.ts b/packages/cli/src/oldCommands/pg/credentials/rotate.ts index 039dee2396..df0080abba 100644 --- a/packages/cli/src/oldCommands/pg/credentials/rotate.ts +++ b/packages/cli/src/oldCommands/pg/credentials/rotate.ts @@ -1,13 +1,11 @@ -/* -import color from '@heroku-cli/color' +import {color} from '@heroku-cli/color' import {Command, flags} from '@heroku-cli/command' -import {APIClient} from '@heroku-cli/command/lib/api-client' +import {APIClient} from '@heroku-cli/command' import type {AddOnAttachment} from '@heroku-cli/schema' import {Args, ux} from '@oclif/core' -import confirmCommand from '../../../lib/confirmCommand' +import ConfirmCommand from '../../../lib/confirmCommand.js' import {utils} from '@heroku/heroku-cli-util' -import {legacyEssentialPlan} from '../../../lib/pg/util' -import {nls} from '../../../nls' +import {nls} from '../../../nls.js' export default class Rotate extends Command { static topic = 'pg' @@ -39,7 +37,7 @@ export default class Rotate extends Command { throw new Error('cannot pass both --all and --name') } - if (legacyEssentialPlan(db) && cred !== 'default') { + if (utils.pg.isLegacyEssentialDatabase(db) && cred !== 'default') { throw new Error('Legacy Essential-tier databases do not support named credentials.') } @@ -74,7 +72,7 @@ export default class Rotate extends Command { warnings.push(`This command will affect the app${(attachments.length > 1) ? 's' : ''} ${uniqueAttachments}.`) } - await confirmCommand(app, confirm, `Destructive Action\n${warnings.join('\n')}`) + await new ConfirmCommand().confirm(app, confirm, `Destructive Action\n${warnings.join('\n')}`) const options: APIClient.Options = { hostname: utils.pg.host(), body: {forced: force ?? undefined}, @@ -93,4 +91,3 @@ export default class Rotate extends Command { ux.action.stop() } } -*/ diff --git a/packages/cli/src/oldCommands/pg/credentials/url.ts b/packages/cli/src/oldCommands/pg/credentials/url.ts index 75b6c26dc6..96889b7a24 100644 --- a/packages/cli/src/oldCommands/pg/credentials/url.ts +++ b/packages/cli/src/oldCommands/pg/credentials/url.ts @@ -1,13 +1,13 @@ -/* -import color from '@heroku-cli/color' +import {color} from '@heroku-cli/color' import {Command, flags} from '@heroku-cli/command' import {Args, ux} from '@oclif/core' -import {legacyEssentialPlan} from '../../../lib/pg/util' import {utils} from '@heroku/heroku-cli-util' import {URL} from 'url' -import type {CredentialInfo} from '../../../lib/pg/types' -import heredoc from 'tsheredoc' -import {nls} from '../../../nls' +import type {CredentialInfo} from '../../../lib/pg/types.js' +import tsheredoc from 'tsheredoc' +import {nls} from '../../../nls.js' + +const heredoc = tsheredoc.default export default class Url extends Command { static topic = 'pg' @@ -32,7 +32,7 @@ export default class Url extends Command { const {database} = args const dbResolver = new utils.pg.DatabaseResolver(this.heroku) const {addon: db} = await dbResolver.getAttachment(app, database) - if (legacyEssentialPlan(db) && name !== 'default') { + if (utils.pg.isLegacyEssentialDatabase(db) && name !== 'default') { ux.error('Legacy Essential-tier databases do not support named credentials.') } @@ -62,7 +62,7 @@ export default class Url extends Command { connUrl.password = creds.password } - ux.log(heredoc(` + ux.stdout(heredoc(` Connection information for ${color.yellow(name)} credential. Connection info string: "dbname=${creds.database} host=${creds.host} port=${creds.port} user=${creds.user} password=${creds.password} sslmode=require" @@ -71,4 +71,3 @@ export default class Url extends Command { `)) } } -*/ From 2a19435bef75bc5a3fdc69d37bbf9656d60d46d7 Mon Sep 17 00:00:00 2001 From: Erika Wallace Date: Fri, 9 Jan 2026 15:33:27 -0500 Subject: [PATCH 2/5] check for essential tier and legacy essential tier databases --- .../{oldCommands => commands}/pg/connection-pooling/attach.ts | 2 +- .../cli/src/{oldCommands => commands}/pg/credentials/create.ts | 2 +- .../cli/src/{oldCommands => commands}/pg/credentials/destroy.ts | 2 +- .../{oldCommands => commands}/pg/credentials/repair-default.ts | 2 +- .../cli/src/{oldCommands => commands}/pg/credentials/rotate.ts | 0 .../cli/src/{oldCommands => commands}/pg/credentials/url.ts | 0 6 files changed, 4 insertions(+), 4 deletions(-) rename packages/cli/src/{oldCommands => commands}/pg/connection-pooling/attach.ts (95%) rename packages/cli/src/{oldCommands => commands}/pg/credentials/create.ts (95%) rename packages/cli/src/{oldCommands => commands}/pg/credentials/destroy.ts (96%) rename packages/cli/src/{oldCommands => commands}/pg/credentials/repair-default.ts (95%) rename packages/cli/src/{oldCommands => commands}/pg/credentials/rotate.ts (100%) rename packages/cli/src/{oldCommands => commands}/pg/credentials/url.ts (100%) diff --git a/packages/cli/src/oldCommands/pg/connection-pooling/attach.ts b/packages/cli/src/commands/pg/connection-pooling/attach.ts similarity index 95% rename from packages/cli/src/oldCommands/pg/connection-pooling/attach.ts rename to packages/cli/src/commands/pg/connection-pooling/attach.ts index 134ff021fe..00c7413643 100644 --- a/packages/cli/src/oldCommands/pg/connection-pooling/attach.ts +++ b/packages/cli/src/commands/pg/connection-pooling/attach.ts @@ -31,7 +31,7 @@ export default class Attach extends Command { const dbResolver = new utils.pg.DatabaseResolver(this.heroku) const {addon: db} = await dbResolver.getAttachment(app, args.database) - if (utils.pg.isEssentialDatabase(db)) + if (utils.pg.isEssentialDatabase(db) || utils.pg.isLegacyEssentialDatabase(db)) ux.error('You can’t perform this operation on Essential-tier databases.') ux.action.start(`Enabling Connection Pooling on ${color.yellow(db.name)} to ${color.magenta(app)}`) diff --git a/packages/cli/src/oldCommands/pg/credentials/create.ts b/packages/cli/src/commands/pg/credentials/create.ts similarity index 95% rename from packages/cli/src/oldCommands/pg/credentials/create.ts rename to packages/cli/src/commands/pg/credentials/create.ts index ebc5ddea14..05fe007fd3 100644 --- a/packages/cli/src/oldCommands/pg/credentials/create.ts +++ b/packages/cli/src/commands/pg/credentials/create.ts @@ -25,7 +25,7 @@ export default class Create extends Command { const {app, name} = flags const dbResolver = new utils.pg.DatabaseResolver(this.heroku) const {addon: db} = await dbResolver.getAttachment(app, args.database) - if (utils.pg.isEssentialDatabase(db)) { + if (utils.pg.isEssentialDatabase(db) || utils.pg.isLegacyEssentialDatabase(db)) { throw new Error("You can't create a custom credential on Essential-tier databases.") } diff --git a/packages/cli/src/oldCommands/pg/credentials/destroy.ts b/packages/cli/src/commands/pg/credentials/destroy.ts similarity index 96% rename from packages/cli/src/oldCommands/pg/credentials/destroy.ts rename to packages/cli/src/commands/pg/credentials/destroy.ts index 9761a83b11..2fbb52e5d3 100644 --- a/packages/cli/src/oldCommands/pg/credentials/destroy.ts +++ b/packages/cli/src/commands/pg/credentials/destroy.ts @@ -31,7 +31,7 @@ export default class Destroy extends Command { const dbResolver = new utils.pg.DatabaseResolver(this.heroku) const {addon: db} = await dbResolver.getAttachment(app, database) - if (utils.pg.isEssentialDatabase(db)) { + if (utils.pg.isEssentialDatabase(db) || utils.pg.isLegacyEssentialDatabase(db)) { throw new Error("You can't destroy the default credential on Essential-tier databases.") } diff --git a/packages/cli/src/oldCommands/pg/credentials/repair-default.ts b/packages/cli/src/commands/pg/credentials/repair-default.ts similarity index 95% rename from packages/cli/src/oldCommands/pg/credentials/repair-default.ts rename to packages/cli/src/commands/pg/credentials/repair-default.ts index 9e8bab6654..5f1fbb8841 100644 --- a/packages/cli/src/oldCommands/pg/credentials/repair-default.ts +++ b/packages/cli/src/commands/pg/credentials/repair-default.ts @@ -27,7 +27,7 @@ export default class RepairDefault extends Command { const {database} = args const dbResolver = new utils.pg.DatabaseResolver(this.heroku) const {addon: db} = await dbResolver.getAttachment(app, database) - if (utils.pg.isEssentialDatabase(db)) + if (utils.pg.isEssentialDatabase(db) || utils.pg.isLegacyEssentialDatabase(db)) throw new Error("You can't perform this operation on Essential-tier databases.") await new ConfirmCommand().confirm(app, confirm, heredoc(` Destructive Action diff --git a/packages/cli/src/oldCommands/pg/credentials/rotate.ts b/packages/cli/src/commands/pg/credentials/rotate.ts similarity index 100% rename from packages/cli/src/oldCommands/pg/credentials/rotate.ts rename to packages/cli/src/commands/pg/credentials/rotate.ts diff --git a/packages/cli/src/oldCommands/pg/credentials/url.ts b/packages/cli/src/commands/pg/credentials/url.ts similarity index 100% rename from packages/cli/src/oldCommands/pg/credentials/url.ts rename to packages/cli/src/commands/pg/credentials/url.ts From 45afeb459aa8bf26e7da92fd9fdd956aa07ceaa5 Mon Sep 17 00:00:00 2001 From: Erika Wallace Date: Fri, 9 Jan 2026 16:04:57 -0500 Subject: [PATCH 3/5] revert to using util function --- .../commands/pg/connection-pooling/attach.ts | 71 ++++++++++--------- .../cli/src/commands/pg/credentials/create.ts | 55 +++++++------- .../src/commands/pg/credentials/destroy.ts | 13 ++-- .../commands/pg/credentials/repair-default.ts | 13 ++-- packages/cli/src/lib/pg/util.ts | 7 +- 5 files changed, 83 insertions(+), 76 deletions(-) diff --git a/packages/cli/src/commands/pg/connection-pooling/attach.ts b/packages/cli/src/commands/pg/connection-pooling/attach.ts index 00c7413643..dcaf63904d 100644 --- a/packages/cli/src/commands/pg/connection-pooling/attach.ts +++ b/packages/cli/src/commands/pg/connection-pooling/attach.ts @@ -3,48 +3,49 @@ import {Command, flags} from '@heroku-cli/command' import {Args, ux} from '@oclif/core' import * as Heroku from '@heroku-cli/schema' import tsheredoc from 'tsheredoc' +import {essentialPlan} from '../../../lib/pg/util.js' import {utils} from '@heroku/heroku-cli-util' import {nls} from '../../../nls.js' const heredoc = tsheredoc.default export default class Attach extends Command { - static topic = 'pg' - static description = 'add an attachment to a database using connection pooling' - static examples = [heredoc` + static topic = 'pg' + static description = 'add an attachment to a database using connection pooling' + static examples = [heredoc` $ heroku pg:connection-pooling:attach postgresql-something-12345 `] - static flags = { - as: flags.string({description: 'name for add-on attachment'}), - app: flags.app({required: true}), - remote: flags.remote(), - } - - static args = { - database: Args.string({description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`}), - } - - public async run(): Promise { - const {flags, args} = await this.parse(Attach) - const {app} = flags - const dbResolver = new utils.pg.DatabaseResolver(this.heroku) - const {addon: db} = await dbResolver.getAttachment(app, args.database) - - if (utils.pg.isEssentialDatabase(db) || utils.pg.isLegacyEssentialDatabase(db)) - ux.error('You can’t perform this operation on Essential-tier databases.') - - ux.action.start(`Enabling Connection Pooling on ${color.yellow(db.name)} to ${color.magenta(app)}`) - const {body: attachment} = await this.heroku.post>(`/client/v11/databases/${encodeURIComponent(db.name)}/connection-pooling`, { - body: {name: flags.as, credential: 'default', app: app}, hostname: utils.pg.host(), - }) - ux.action.stop() - - ux.action.start(`Setting ${color.cyan(attachment.name)} config vars and restarting ${color.magenta(app)}`) - const {body: releases} = await this.heroku.get[]>( - `/apps/${app}/releases`, - {partial: true, headers: {Range: 'version ..; max=1, order=desc'}}, - ) - ux.action.stop(`done, v${releases[0].version}`) - } + static flags = { + as: flags.string({description: 'name for add-on attachment'}), + app: flags.app({required: true}), + remote: flags.remote(), + } + + static args = { + database: Args.string({description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`}), + } + + public async run(): Promise { + const {flags, args} = await this.parse(Attach) + const {app} = flags + const dbResolver = new utils.pg.DatabaseResolver(this.heroku) + const {addon: db} = await dbResolver.getAttachment(app, args.database) + + if (essentialPlan(db)) + ux.error('You can’t perform this operation on Essential-tier databases.') + + ux.action.start(`Enabling Connection Pooling on ${color.yellow(db.name)} to ${color.magenta(app)}`) + const {body: attachment} = await this.heroku.post>(`/client/v11/databases/${encodeURIComponent(db.name)}/connection-pooling`, { + body: {name: flags.as, credential: 'default', app}, hostname: utils.pg.host(), + }) + ux.action.stop() + + ux.action.start(`Setting ${color.cyan(attachment.name)} config vars and restarting ${color.magenta(app)}`) + const {body: releases} = await this.heroku.get[]>( + `/apps/${app}/releases`, + {partial: true, headers: {Range: 'version ..; max=1, order=desc'}}, + ) + ux.action.stop(`done, v${releases[0].version}`) + } } diff --git a/packages/cli/src/commands/pg/credentials/create.ts b/packages/cli/src/commands/pg/credentials/create.ts index 05fe007fd3..2803aa395b 100644 --- a/packages/cli/src/commands/pg/credentials/create.ts +++ b/packages/cli/src/commands/pg/credentials/create.ts @@ -3,43 +3,44 @@ import {Command, flags} from '@heroku-cli/command' import {Args, ux} from '@oclif/core' import tsheredoc from 'tsheredoc' import {utils} from '@heroku/heroku-cli-util' +import {essentialPlan} from '../../../lib/pg/util.js' import {nls} from '../../../nls.js' const heredoc = tsheredoc.default export default class Create extends Command { - static topic = 'pg' - static description = 'create credential within database\nExample:\n\n heroku pg:credentials:create postgresql-something-12345 --name new-cred-name\n' - static flags = { - name: flags.string({char: 'n', required: true, description: 'name of the new credential within the database'}), - app: flags.app({required: true}), - remote: flags.remote(), + static topic = 'pg' + static description = 'create credential within database\nExample:\n\n heroku pg:credentials:create postgresql-something-12345 --name new-cred-name\n' + static flags = { + name: flags.string({char: 'n', required: true, description: 'name of the new credential within the database'}), + app: flags.app({required: true}), + remote: flags.remote(), + } + + static args = { + database: Args.string({description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`}), + } + + public async run(): Promise { + const {flags, args} = await this.parse(Create) + const {app, name} = flags + const dbResolver = new utils.pg.DatabaseResolver(this.heroku) + const {addon: db} = await dbResolver.getAttachment(app, args.database) + if (essentialPlan(db)) { + throw new Error("You can't create a custom credential on Essential-tier databases.") } - static args = { - database: Args.string({description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`}), - } - - public async run(): Promise { - const {flags, args} = await this.parse(Create) - const {app, name} = flags - const dbResolver = new utils.pg.DatabaseResolver(this.heroku) - const {addon: db} = await dbResolver.getAttachment(app, args.database) - if (utils.pg.isEssentialDatabase(db) || utils.pg.isLegacyEssentialDatabase(db)) { - throw new Error("You can't create a custom credential on Essential-tier databases.") - } + const data = {name} + ux.action.start(`Creating credential ${color.cyan.bold(name)}`) - const data = {name} - ux.action.start(`Creating credential ${color.cyan.bold(name)}`) + await this.heroku.post(`/postgres/v0/databases/${db.name}/credentials`, {hostname: utils.pg.host(), body: data}) + ux.action.stop() - await this.heroku.post(`/postgres/v0/databases/${db.name}/credentials`, {hostname: utils.pg.host(), body: data}) - ux.action.stop() - - const attachCmd = `heroku addons:attach ${db.name} --credential ${name} -a ${app}` - const psqlCmd = `heroku pg:psql ${db.name} -a ${app}` - ux.stdout(heredoc(` + const attachCmd = `heroku addons:attach ${db.name} --credential ${name} -a ${app}` + const psqlCmd = `heroku pg:psql ${db.name} -a ${app}` + ux.stdout(heredoc(` Please attach the credential to the apps you want to use it in by running ${color.cyan.bold(attachCmd)}. Please define the new grants for the credential within Postgres: ${color.cyan.bold(psqlCmd)}.`)) - } + } } diff --git a/packages/cli/src/commands/pg/credentials/destroy.ts b/packages/cli/src/commands/pg/credentials/destroy.ts index 2fbb52e5d3..2381773292 100644 --- a/packages/cli/src/commands/pg/credentials/destroy.ts +++ b/packages/cli/src/commands/pg/credentials/destroy.ts @@ -2,24 +2,25 @@ import {color} from '@heroku-cli/color' import {Command, flags} from '@heroku-cli/command' import {Args, ux} from '@oclif/core' import * as Heroku from '@heroku-cli/schema' +import {essentialPlan} from '../../../lib/pg/util.js' import {utils} from '@heroku/heroku-cli-util' import ConfirmCommand from '../../../lib/confirmCommand.js' import {nls} from '../../../nls.js' export default class Destroy extends Command { - static topic = 'pg'; - static description = 'destroy credential within database'; - static example = '$ heroku pg:credentials:destroy postgresql-transparent-56874 --name cred-name -a woodstock-production'; + static topic = 'pg' + static description = 'destroy credential within database' + static example = '$ heroku pg:credentials:destroy postgresql-transparent-56874 --name cred-name -a woodstock-production' static flags = { name: flags.string({char: 'n', required: true, description: 'unique identifier for the credential'}), confirm: flags.string({char: 'c', hidden: true}), app: flags.app({required: true}), remote: flags.remote(), - }; + } static args = { database: Args.string({description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`}), - }; + } public async run(): Promise { const {flags, args} = await this.parse(Destroy) @@ -31,7 +32,7 @@ export default class Destroy extends Command { const dbResolver = new utils.pg.DatabaseResolver(this.heroku) const {addon: db} = await dbResolver.getAttachment(app, database) - if (utils.pg.isEssentialDatabase(db) || utils.pg.isLegacyEssentialDatabase(db)) { + if (essentialPlan(db)) { throw new Error("You can't destroy the default credential on Essential-tier databases.") } diff --git a/packages/cli/src/commands/pg/credentials/repair-default.ts b/packages/cli/src/commands/pg/credentials/repair-default.ts index 5f1fbb8841..7a4b56d247 100644 --- a/packages/cli/src/commands/pg/credentials/repair-default.ts +++ b/packages/cli/src/commands/pg/credentials/repair-default.ts @@ -1,6 +1,7 @@ import {Command, flags} from '@heroku-cli/command' import {Args, ux} from '@oclif/core' import {utils} from '@heroku/heroku-cli-util' +import {essentialPlan} from '../../../lib/pg/util.js' import ConfirmCommand from '../../../lib/confirmCommand.js' import tsheredoc from 'tsheredoc' import {nls} from '../../../nls.js' @@ -8,18 +9,18 @@ import {nls} from '../../../nls.js' const heredoc = tsheredoc.default export default class RepairDefault extends Command { - static topic = 'pg'; - static description = 'repair the permissions of the default credential within database'; - static example = '$ heroku pg:credentials:repair-default postgresql-something-12345'; + static topic = 'pg' + static description = 'repair the permissions of the default credential within database' + static example = '$ heroku pg:credentials:repair-default postgresql-something-12345' static flags = { confirm: flags.string({char: 'c', hidden: true}), app: flags.app({required: true}), remote: flags.remote(), - }; + } static args = { database: Args.string({description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`}), - }; + } public async run(): Promise { const {flags, args} = await this.parse(RepairDefault) @@ -27,7 +28,7 @@ export default class RepairDefault extends Command { const {database} = args const dbResolver = new utils.pg.DatabaseResolver(this.heroku) const {addon: db} = await dbResolver.getAttachment(app, database) - if (utils.pg.isEssentialDatabase(db) || utils.pg.isLegacyEssentialDatabase(db)) + if (essentialPlan(db)) throw new Error("You can't perform this operation on Essential-tier databases.") await new ConfirmCommand().confirm(app, confirm, heredoc(` Destructive Action diff --git a/packages/cli/src/lib/pg/util.ts b/packages/cli/src/lib/pg/util.ts index 21949a57a6..70f7977810 100644 --- a/packages/cli/src/lib/pg/util.ts +++ b/packages/cli/src/lib/pg/util.ts @@ -1,10 +1,13 @@ import {color} from '@heroku-cli/color' import type {AddOnAttachment} from '@heroku-cli/schema' -import {hux} from '@heroku/heroku-cli-util' +import {hux, utils, pg} from '@heroku/heroku-cli-util' import {renderAttachment} from '../../commands/addons/index.js' import {multiSortCompareFn} from '../utils/multisort.js' import type {CredentialsInfo} from './types.js' -import {utils} from '@heroku/heroku-cli-util' + +export function essentialPlan(addon: pg.ExtendedAddonAttachment['addon'] | pg.ExtendedAddon) { + return utils.pg.isEssentialDatabase(addon) || utils.pg.isLegacyEssentialDatabase(addon) +} export function formatResponseWithCommands(response: string): string { return response.replace(/`(.*?)`/g, (_, word) => color.cmd(word)) From 44c4074a7c14eb16aa8db44a415d382e3393ea8d Mon Sep 17 00:00:00 2001 From: Erika Wallace Date: Fri, 9 Jan 2026 16:33:32 -0500 Subject: [PATCH 4/5] uncomment and update tests (removed use of hobby-dev) --- .../pg/connection-pooling/attach.unit.test.ts | 7 ++---- .../pg/credentials/create.unit.test.ts | 7 ++---- .../pg/credentials/destroy.unit.test.ts | 12 +++++----- .../credentials/repair-default.unit.test.ts | 12 +++++----- .../pg/credentials/rotate.unit.test.ts | 22 +++++++------------ .../commands/pg/credentials/url.unit.test.ts | 9 ++++---- 6 files changed, 26 insertions(+), 43 deletions(-) diff --git a/packages/cli/test/unit/commands/pg/connection-pooling/attach.unit.test.ts b/packages/cli/test/unit/commands/pg/connection-pooling/attach.unit.test.ts index 928bbe09bc..f20b72cfca 100644 --- a/packages/cli/test/unit/commands/pg/connection-pooling/attach.unit.test.ts +++ b/packages/cli/test/unit/commands/pg/connection-pooling/attach.unit.test.ts @@ -1,10 +1,9 @@ -/* import {stdout, stderr} from 'stdout-stderr' import runCommand from '../../../../helpers/runCommand.js' import {expect} from 'chai' import nock from 'nock' -import Cmd from '../../../../../src/commands/pg/connection-pooling/attach' -import {resolvedAttachments} from '../../../../fixtures/addons/fixtures' +import Cmd from '../../../../../src/commands/pg/connection-pooling/attach.js' +import {resolvedAttachments} from '../../../../fixtures/addons/fixtures.js' describe('pg:connection-pooling:attach', function () { const addon = { @@ -76,5 +75,3 @@ describe('pg:connection-pooling:attach', function () { }) }) }) - -*/ diff --git a/packages/cli/test/unit/commands/pg/credentials/create.unit.test.ts b/packages/cli/test/unit/commands/pg/credentials/create.unit.test.ts index aa86254fec..01206fa2de 100644 --- a/packages/cli/test/unit/commands/pg/credentials/create.unit.test.ts +++ b/packages/cli/test/unit/commands/pg/credentials/create.unit.test.ts @@ -3,9 +3,8 @@ import runCommand from '../../../../helpers/runCommand.js' import {expect} from 'chai' import nock from 'nock' -// import Cmd from '../../../../../src/commands/pg/credentials/create' +import Cmd from '../../../../../src/commands/pg/credentials/create.js' -/* describe('pg:credentials:create', function () { let api: nock.Scope let pg: nock.Scope @@ -40,7 +39,7 @@ describe('pg:credentials:create', function () { 'credname', ]) expect(stdout.output).to.equal('\nPlease attach the credential to the apps you want to use it in by running heroku addons:attach postgres-1 --credential credname -a myapp.\nPlease define the new grants for the credential within Postgres: heroku pg:psql postgres-1 -a myapp.\n') - return expect(stderr.output).to.equal('Creating credential credname...\nCreating credential credname... done\n') + return expect(stderr.output).to.equal('Creating credential credname... done\n') }) it('throws an error when the db is numbered essential plan', async function () { @@ -82,5 +81,3 @@ describe('pg:credentials:create', function () { ]).catch(error => expect(error.message).to.contain(err)) }) }) - -*/ diff --git a/packages/cli/test/unit/commands/pg/credentials/destroy.unit.test.ts b/packages/cli/test/unit/commands/pg/credentials/destroy.unit.test.ts index 8e84e69925..ea431b41d9 100644 --- a/packages/cli/test/unit/commands/pg/credentials/destroy.unit.test.ts +++ b/packages/cli/test/unit/commands/pg/credentials/destroy.unit.test.ts @@ -1,13 +1,14 @@ import {stderr, stdout} from 'stdout-stderr' -// import Cmd from '../../../../../src/commands/pg/credentials/destroy' +import Cmd from '../../../../../src/commands/pg/credentials/destroy.js' import runCommand from '../../../../helpers/runCommand.js' import nock from 'nock' import expectOutput from '../../../../helpers/utils/expectOutput.js' import {expect} from 'chai' -import heredoc from 'tsheredoc' +import tsheredoc from 'tsheredoc' import stripAnsi from 'strip-ansi' -/* +const heredoc = tsheredoc.default + describe('pg:credentials:destroy', function () { const addon = { name: 'postgres-1', plan: {name: 'heroku-postgresql:standard-0'}, @@ -42,7 +43,6 @@ describe('pg:credentials:destroy', function () { 'myapp', ]) expectOutput(stderr.output, heredoc(` - Destroying credential credname... Destroying credential credname... done `)) expectOutput(stdout.output, heredoc(` @@ -53,7 +53,7 @@ describe('pg:credentials:destroy', function () { it('throws an error when the db is starter plan', async function () { const hobbyAddon = { - name: 'postgres-1', plan: {name: 'heroku-postgresql:hobby-dev'}, + name: 'postgres-1', plan: {name: 'heroku-postgresql:mini'}, } nock('https://api.heroku.com') .post('/actions/addon-attachments/resolve') @@ -142,5 +142,3 @@ describe('pg:credentials:destroy', function () { }) }) }) - -*/ diff --git a/packages/cli/test/unit/commands/pg/credentials/repair-default.unit.test.ts b/packages/cli/test/unit/commands/pg/credentials/repair-default.unit.test.ts index c5d008ed62..7265c6c85f 100644 --- a/packages/cli/test/unit/commands/pg/credentials/repair-default.unit.test.ts +++ b/packages/cli/test/unit/commands/pg/credentials/repair-default.unit.test.ts @@ -1,12 +1,13 @@ import {stderr, stdout} from 'stdout-stderr' -// import Cmd from '../../../../../src/commands/pg/credentials/repair-default' +import Cmd from '../../../../../src/commands/pg/credentials/repair-default.js' import runCommand from '../../../../helpers/runCommand.js' import nock from 'nock' -import heredoc from 'tsheredoc' +import tsheredoc from 'tsheredoc' import {expect} from 'chai' import expectOutput from '../../../../helpers/utils/expectOutput.js' -/* +const heredoc = tsheredoc.default + describe('pg:credentials:repair-default', function () { const addon = { name: 'postgres-1', plan: {name: 'heroku-postgresql:standard-0'}, @@ -31,14 +32,13 @@ describe('pg:credentials:repair-default', function () { ]) expectOutput(stdout.output, '') expectOutput(stderr.output, heredoc(` - Resetting permissions and object ownership for default role to factory settings... Resetting permissions and object ownership for default role to factory settings... done `)) }) it('throws an error when the db is essential plan', async function () { const hobbyAddon = { - name: 'postgres-1', plan: {name: 'heroku-postgresql:hobby-dev'}, + name: 'postgres-1', plan: {name: 'heroku-postgresql:mini'}, } nock('https://api.heroku.com') @@ -53,5 +53,3 @@ describe('pg:credentials:repair-default', function () { ]).catch((error: Error) => expect(error.message).to.equal(err)) }) }) - -*/ diff --git a/packages/cli/test/unit/commands/pg/credentials/rotate.unit.test.ts b/packages/cli/test/unit/commands/pg/credentials/rotate.unit.test.ts index bbfa52cb59..e89b42a2a3 100644 --- a/packages/cli/test/unit/commands/pg/credentials/rotate.unit.test.ts +++ b/packages/cli/test/unit/commands/pg/credentials/rotate.unit.test.ts @@ -2,11 +2,14 @@ import {ux} from '@oclif/core' import {expect} from 'chai' import nock from 'nock' import {stdout, stderr} from 'stdout-stderr' -import heredoc from 'tsheredoc' -// import Cmd from '../../../../../src/commands/pg/credentials/rotate' +import tsheredoc from 'tsheredoc' +import Cmd from '../../../../../src/commands/pg/credentials/rotate.js' import runCommand from '../../../../helpers/runCommand.js' import * as sinon from 'sinon' import stripAnsi from 'strip-ansi' +import {hux} from '@heroku/heroku-cli-util' + +const heredoc = tsheredoc.default const addon = { id: 1, name: 'postgres-1', plan: {name: 'heroku-postgresql:standard-0'}, @@ -22,7 +25,6 @@ const attachments = [ }, ] -/* describe('pg:credentials:rotate', function () { let api: nock.Scope let pg: nock.Scope @@ -31,7 +33,7 @@ describe('pg:credentials:rotate', function () { before(function () { uxWarnStub = sinon.stub(ux, 'warn') - uxPromptStub = sinon.stub(ux, 'prompt').resolves('myapp') + uxPromptStub = sinon.stub(hux, 'prompt').resolves('myapp') }) beforeEach(async function () { @@ -75,7 +77,6 @@ describe('pg:credentials:rotate', function () { ]) expect(stdout.output).to.equal('') expect(stderr.output).to.equal(heredoc(` - Rotating my_role on postgres-1... Rotating my_role on postgres-1... done `)) }) @@ -92,7 +93,6 @@ describe('pg:credentials:rotate', function () { ]) expect(stdout.output).to.equal('') expect(stderr.output).to.equal(heredoc(` - Rotating all credentials on postgres-1... Rotating all credentials on postgres-1... done `)) }) @@ -111,7 +111,6 @@ describe('pg:credentials:rotate', function () { ]) expect(stdout.output).to.equal('') expect(stderr.output).to.equal(heredoc(` - Rotating my_role on postgres-1... Rotating my_role on postgres-1... done `)) }) @@ -252,14 +251,13 @@ describe('pg:credentials:rotate', function () { ]) expect(stdout.output).to.equal('') expect(stderr.output).to.equal(heredoc(` - Rotating lucy on postgres-1... Rotating lucy on postgres-1... done `)) }) it('rotates credentials with no --name with starter plan', async function () { const hobbyAddon = { - name: 'postgres-1', plan: {name: 'heroku-postgresql:hobby-dev'}, + name: 'postgres-1', plan: {name: 'heroku-postgresql:mini'}, } api.post('/actions/addon-attachments/resolve', { @@ -278,14 +276,13 @@ describe('pg:credentials:rotate', function () { ]) expect(stdout.output).to.equal('') expect(stderr.output).to.equal(heredoc(` - Rotating default on postgres-1... Rotating default on postgres-1... done `)) }) it('rotates credentials with --all with starter plan', async function () { const hobbyAddon = { - name: 'postgres-1', plan: {name: 'heroku-postgresql:hobby-dev'}, + name: 'postgres-1', plan: {name: 'heroku-postgresql:mini'}, } api.post('/actions/addon-attachments/resolve', { @@ -305,10 +302,7 @@ describe('pg:credentials:rotate', function () { ]) expect(stdout.output).to.equal('') expect(stderr.output).to.equal(heredoc(` - Rotating all credentials on postgres-1... Rotating all credentials on postgres-1... done `)) }) }) - -*/ diff --git a/packages/cli/test/unit/commands/pg/credentials/url.unit.test.ts b/packages/cli/test/unit/commands/pg/credentials/url.unit.test.ts index 6b8582b3b1..09f29a05f1 100644 --- a/packages/cli/test/unit/commands/pg/credentials/url.unit.test.ts +++ b/packages/cli/test/unit/commands/pg/credentials/url.unit.test.ts @@ -1,13 +1,14 @@ import {stdout} from 'stdout-stderr' -// import Cmd from '../../../../../src/commands/pg/credentials/url' +import Cmd from '../../../../../src/commands/pg/credentials/url.js' import runCommand from '../../../../helpers/runCommand.js' import nock from 'nock' import expectOutput from '../../../../helpers/utils/expectOutput.js' import {expect} from 'chai' -import heredoc from 'tsheredoc' +import tsheredoc from 'tsheredoc' import * as fixtures from '../../../../fixtures/addons/fixtures.js' -/* +const heredoc = tsheredoc.default + describe('pg:credentials:url', function () { const addon = fixtures.addons['dwh-db'] const attachments = [fixtures.attachments['acme-inc-dwh::DATABASE']] @@ -129,5 +130,3 @@ describe('pg:credentials:url', function () { `)) }) }) - -*/ From c0891573e6b7ad52b138c23dab6feb055a56d34c Mon Sep 17 00:00:00 2001 From: Erika Wallace Date: Wed, 14 Jan 2026 16:13:29 -0500 Subject: [PATCH 5/5] update confirm flag descriptions --- packages/cli/src/commands/pg/credentials/destroy.ts | 2 +- packages/cli/src/commands/pg/credentials/repair-default.ts | 2 +- packages/cli/src/commands/pg/credentials/rotate.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/commands/pg/credentials/destroy.ts b/packages/cli/src/commands/pg/credentials/destroy.ts index 2381773292..20758f4bfa 100644 --- a/packages/cli/src/commands/pg/credentials/destroy.ts +++ b/packages/cli/src/commands/pg/credentials/destroy.ts @@ -13,7 +13,7 @@ export default class Destroy extends Command { static example = '$ heroku pg:credentials:destroy postgresql-transparent-56874 --name cred-name -a woodstock-production' static flags = { name: flags.string({char: 'n', required: true, description: 'unique identifier for the credential'}), - confirm: flags.string({char: 'c', hidden: true}), + confirm: flags.string({char: 'c', description: 'set to app name to bypass confirm prompt'}), app: flags.app({required: true}), remote: flags.remote(), } diff --git a/packages/cli/src/commands/pg/credentials/repair-default.ts b/packages/cli/src/commands/pg/credentials/repair-default.ts index 7a4b56d247..4c4e0f17a8 100644 --- a/packages/cli/src/commands/pg/credentials/repair-default.ts +++ b/packages/cli/src/commands/pg/credentials/repair-default.ts @@ -13,7 +13,7 @@ export default class RepairDefault extends Command { static description = 'repair the permissions of the default credential within database' static example = '$ heroku pg:credentials:repair-default postgresql-something-12345' static flags = { - confirm: flags.string({char: 'c', hidden: true}), + confirm: flags.string({char: 'c', description: 'set to app name to bypass confirm prompt'}), app: flags.app({required: true}), remote: flags.remote(), } diff --git a/packages/cli/src/commands/pg/credentials/rotate.ts b/packages/cli/src/commands/pg/credentials/rotate.ts index df0080abba..f0136d72d1 100644 --- a/packages/cli/src/commands/pg/credentials/rotate.ts +++ b/packages/cli/src/commands/pg/credentials/rotate.ts @@ -16,7 +16,7 @@ export default class Rotate extends Command { description: 'which credential to rotate (default credentials if not specified and --all is not used)', }), all: flags.boolean({description: 'rotate all credentials', exclusive: ['name']}), - confirm: flags.string({char: 'c'}), + confirm: flags.string({char: 'c', description: 'set to app name to bypass confirm prompt'}), force: flags.boolean({description: 'forces rotating the targeted credentials'}), app: flags.app({required: true}), remote: flags.remote(),