diff --git a/__tests__/commands/phpmyadmin.ts b/__tests__/commands/phpmyadmin.ts index c4beebaa5..98383aa19 100644 --- a/__tests__/commands/phpmyadmin.ts +++ b/__tests__/commands/phpmyadmin.ts @@ -63,7 +63,7 @@ describe( 'commands/PhpMyAdminCommand', () => { const app = { id: 123 }; const env = { id: 456, jobs: [] }; const tracker = jest.fn() as CommandTracker; - const cmd = new PhpMyAdminCommand( app, env, tracker ); + const cmd = new PhpMyAdminCommand( app, env, tracker, true ); const openUrl = jest.spyOn( cmd, 'openUrl' ); beforeEach( () => { @@ -91,5 +91,19 @@ describe( 'commands/PhpMyAdminCommand', () => { } ); expect( openUrl ).toHaveBeenCalledWith( 'http://test-url.com' ); } ); + + it( 'should print the URL to stdout instead of opening browser when print option is set', async () => { + const printCmd = new PhpMyAdminCommand( app, env, tracker, true ); + const printOpenUrl = jest.spyOn( printCmd, 'openUrl' ); + printOpenUrl.mockReset(); + const consoleSpy = jest.spyOn( console, 'log' ); + try { + await printCmd.run( { print: true } ); + expect( printOpenUrl ).not.toHaveBeenCalled(); + expect( consoleSpy ).toHaveBeenCalledWith( 'http://test-url.com' ); + } finally { + consoleSpy.mockRestore(); + } + } ); } ); } ); diff --git a/src/bin/vip-db-phpmyadmin.ts b/src/bin/vip-db-phpmyadmin.ts index c273190a6..d52dd9b99 100755 --- a/src/bin/vip-db-phpmyadmin.ts +++ b/src/bin/vip-db-phpmyadmin.ts @@ -18,6 +18,10 @@ const examples = [ description: "Generate access to a read-only phpMyAdmin web interface for the environment's database.", }, + { + usage: 'vip @example-app.develop db phpmyadmin --print', + description: 'Print the phpMyAdmin URL to stdout instead of opening it in a browser.', + }, ]; const appQuery = ` @@ -40,16 +44,29 @@ void command( { requiredArgs: 0, usage: 'vip db phpmyadmin', } ) + .option( 'print', 'Print the phpMyAdmin URL to stdout instead of opening it in a browser.' ) + .option( 'silent', 'Do not print any output to the console.' ) .examples( examples ) - .argv( process.argv, async ( arg: string[], { app, env }: { app: App; env: AppEnvironment } ) => { - const trackerFn = makeCommandTracker( 'phpmyadmin', { - app: app.id, - env: env.uniqueLabel, - } ); - await trackerFn( 'execute' ); + .argv( + process.argv, + async ( + arg: string[], + { + app, + env, + print: printUrl, + silent, + }: { app: App; env: AppEnvironment; print: boolean; silent: boolean } + ) => { + const trackerFn = makeCommandTracker( 'phpmyadmin', { + app: app.id, + env: env.uniqueLabel, + } ); + await trackerFn( 'execute' ); - const cmd = new PhpMyAdminCommand( app, env, trackerFn ); - await cmd.run(); + const cmd = new PhpMyAdminCommand( app, env, trackerFn, silent ); + await cmd.run( { print: printUrl } ); - await trackerFn( 'success' ); - } ); + await trackerFn( 'success' ); + } + ); diff --git a/src/commands/phpmyadmin.ts b/src/commands/phpmyadmin.ts index 8b63d2a61..e0684f1f7 100644 --- a/src/commands/phpmyadmin.ts +++ b/src/commands/phpmyadmin.ts @@ -4,6 +4,7 @@ import { ApolloClient } from '@apollo/client/core'; import chalk from 'chalk'; import gql from 'graphql-tag'; +import { setTimeout } from 'node:timers/promises'; /** * Internal dependencies @@ -21,7 +22,7 @@ import API, { enableGlobalGraphQLErrorHandling, } from '../lib/api'; import * as exit from '../lib/cli/exit'; -import { ProgressTracker } from '../lib/cli/progress'; +import { ProgressTracker, StepConstructorParam } from '../lib/cli/progress'; import { CommandTracker } from '../lib/tracker'; import { pollUntil } from '../lib/utils'; @@ -116,30 +117,83 @@ async function getPhpMyAdminStatus( appId: number, envId: number ): Promise< str return resp.data?.app?.environments?.[ 0 ]?.phpMyAdminStatus?.status ?? ''; } +class MyProgressTracker extends ProgressTracker { + public constructor( + steps: StepConstructorParam[], + private silent: boolean + ) { + super( steps ); + } + + public setSilent( silent: boolean ): void { + this.silent = silent; + } + + public print(): void { + if ( ! this.silent ) { + super.print(); + } + } + + public startPrinting( prePrintCallback?: () => unknown ): void { + if ( ! this.silent ) { + super.startPrinting( prePrintCallback ); + } + } + + public stopPrinting(): void { + if ( ! this.silent ) { + super.stopPrinting(); + } + } + + public stepRunning( stepId: string, additionalInfo?: string | string[] ): void { + if ( ! this.silent ) { + super.stepRunning( stepId, additionalInfo ); + } + } + + public stepSuccess( stepId: string, additionalInfo?: string | string[] ): void { + if ( ! this.silent ) { + super.stepSuccess( stepId, additionalInfo ); + } + } + + public stepFailed( stepId: string, additionalInfo?: string | string[] ): void { + if ( ! this.silent ) { + super.stepFailed( stepId, additionalInfo ); + } + } +} + export class PhpMyAdminCommand { private silent?: boolean; private readonly steps = { ENABLE: 'enable', GENERATE: 'generate', }; - private readonly progressTracker: ProgressTracker; + private readonly progressTracker: MyProgressTracker; constructor( private readonly app: App, private readonly env: AppEnvironment, - private readonly track: CommandTracker = async () => {} + private readonly track: CommandTracker = async () => {}, + silent = false ) { - this.progressTracker = new ProgressTracker( [ - { id: this.steps.ENABLE, name: 'Enabling PHPMyAdmin for this environment' }, - { id: this.steps.GENERATE, name: 'Generating access link' }, - ] ); + this.silent = silent; + this.progressTracker = new MyProgressTracker( + [ + { id: this.steps.ENABLE, name: 'Enabling phpMyAdmin for this environment' }, + { id: this.steps.GENERATE, name: 'Generating access link' }, + ], + silent + ); } private log( msg: string ): void { - if ( this.silent ) { - return; + if ( ! this.silent ) { + console.log( msg ); } - console.log( msg ); } private stopProgressTracker(): void { @@ -163,13 +217,11 @@ export class PhpMyAdminCommand { await pollUntil( this.getStatus.bind( this ), 1000, ( sts: string ) => sts === 'running' ); // Additional 30s for LB routing to be updated - await new Promise( resolve => setTimeout( resolve, 30000 ) ); + await setTimeout( 30_000 ); } } - public async run( silent = false ): Promise< void > { - this.silent = silent; - + public async run( { print = false } = {} ): Promise< void > { if ( ! this.app.id ) { exit.withError( 'No app was specified' ); } @@ -178,9 +230,11 @@ export class PhpMyAdminCommand { exit.withError( 'No environment was specified' ); } - const message = - 'Note: PHPMyAdmin sessions are read-only. If you run a query that writes to DB, it will fail.'; - console.log( chalk.yellow( message ) ); + if ( ! this.silent ) { + const message = + 'Note: PHPMyAdmin sessions are read-only. If you run a query that writes to DB, it will fail.'; + console.log( chalk.yellow( message ) ); + } this.progressTracker.startPrinting(); try { @@ -206,7 +260,7 @@ export class PhpMyAdminCommand { } exit.withError( - 'Failed to enable PhpMyAdmin. Please try again. If the problem persists, please contact support.' + 'Failed to enable phpMyAdmin. Please try again. If the problem persists, please contact support.' ); } @@ -226,11 +280,17 @@ export class PhpMyAdminCommand { stack: error.stack, } ); this.stopProgressTracker(); - exit.withError( `Failed to generate PhpMyAdmin URL: ${ error.message }` ); + exit.withError( `Failed to generate phpMyAdmin URL: ${ error.message }` ); } - void this.openUrl( url ); this.stopProgressTracker(); - this.log( 'PhpMyAdmin is opened in your default browser.' ); + + if ( print ) { + // Output only the URL to stdout for scripting/automation use + console.log( url ); + } else { + void this.openUrl( url ); + this.log( 'phpMyAdmin is opened in your default browser.' ); + } } }