From 1e09df31544974d5ef4636bbbdac68d914e5b7db Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Wed, 10 Aug 2022 19:02:45 +0000 Subject: [PATCH] feat(ng-dev): create system for registering functions to be called on command completion Create a callback system to call functions after a command completes. --- ng-dev/auth/shared/ng-dev-token.ts | 4 +++ ng-dev/cli.ts | 42 ++++++++++++++++-------------- ng-dev/utils/logging.ts | 4 ++- ng-dev/utils/ng-dev-service.ts | 17 +++++++++--- ng-dev/utils/yargs.ts | 24 +++++++++++++++++ 5 files changed, 66 insertions(+), 25 deletions(-) create mode 100644 ng-dev/utils/yargs.ts diff --git a/ng-dev/auth/shared/ng-dev-token.ts b/ng-dev/auth/shared/ng-dev-token.ts index aa5513d4e..e2fc604e4 100644 --- a/ng-dev/auth/shared/ng-dev-token.ts +++ b/ng-dev/auth/shared/ng-dev-token.ts @@ -7,6 +7,7 @@ import {randomBytes, createCipheriv, createDecipheriv, createHash} from 'crypto' import {RawData, WebSocket} from 'ws'; import {AuthenticatedGitClient} from '../../utils/git/authenticated-git-client.js'; import {assertValidGithubConfig, getConfig} from '../../utils/config.js'; +import {registerCompletedFunction} from '../../utils/yargs.js'; /** Algorithm to use for encryption. */ const algorithm = 'aes-256-ctr'; @@ -159,6 +160,9 @@ export function configureAuthorizedGitClientWithTemporaryToken() { }, }); + // Close the socket whenever the command which established it is complete. + registerCompletedFunction(() => socket.close()); + // When the token is provided via the websocket message, use the token to set up // the AuthenticatedGitClient. The token is valid as long as the socket remains open, // with the server emposing a limit of 1 hour. diff --git a/ng-dev/cli.ts b/ng-dev/cli.ts index 10a16a143..91222a43e 100644 --- a/ng-dev/cli.ts +++ b/ng-dev/cli.ts @@ -7,7 +7,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import yargs from 'yargs'; +import {runParserWithCompletedFunctions} from './utils/yargs.js'; import {buildCaretakerParser} from './caretaker/cli.js'; import {buildCiParser} from './ci/cli.js'; @@ -21,23 +21,25 @@ import {buildReleaseParser} from './release/cli.js'; import {tsCircularDependenciesBuilder} from './ts-circular-dependencies/index.js'; import {captureLogOutputForCommand} from './utils/logging.js'; import {buildAuthParser} from './auth/cli.js'; +import {Argv} from 'yargs'; -yargs(process.argv.slice(2)) - .scriptName('ng-dev') - .middleware(captureLogOutputForCommand) - .demandCommand() - .recommendCommands() - .command('auth ', false, buildAuthParser) - .command('commit-message ', '', buildCommitMessageParser) - .command('format ', '', buildFormatParser) - .command('pr ', '', buildPrParser) - .command('pullapprove ', '', buildPullapproveParser) - .command('release ', '', buildReleaseParser) - .command('ts-circular-deps ', '', tsCircularDependenciesBuilder) - .command('caretaker ', '', buildCaretakerParser) - .command('misc ', '', buildMiscParser) - .command('ngbot ', false, buildNgbotParser) - .command('ci ', false, buildCiParser) - .wrap(120) - .strict() - .parse(); +runParserWithCompletedFunctions((yargs: Argv) => { + return yargs + .scriptName('ng-dev') + .middleware(captureLogOutputForCommand, true) + .demandCommand() + .recommendCommands() + .command('auth ', false, buildAuthParser) + .command('commit-message ', '', buildCommitMessageParser) + .command('format ', '', buildFormatParser) + .command('pr ', '', buildPrParser) + .command('pullapprove ', '', buildPullapproveParser) + .command('release ', '', buildReleaseParser) + .command('ts-circular-deps ', '', tsCircularDependenciesBuilder) + .command('caretaker ', '', buildCaretakerParser) + .command('misc ', '', buildMiscParser) + .command('ngbot ', false, buildNgbotParser) + .command('ci ', false, buildCiParser) + .wrap(120) + .strict(); +}); diff --git a/ng-dev/utils/logging.ts b/ng-dev/utils/logging.ts index 0c5f63eb2..6d52be5c8 100644 --- a/ng-dev/utils/logging.ts +++ b/ng-dev/utils/logging.ts @@ -133,8 +133,10 @@ const LOG_LEVEL_COLUMNS = 7; * response is executed. */ export async function captureLogOutputForCommand(argv: Arguments) { + // TODO(josephperrott): remove this guard against running multiple times after + // https://github.com/yargs/yargs/issues/2223 is fixed if (FILE_LOGGING_ENABLED) { - throw Error('`captureLogOutputForCommand` cannot be called multiple times'); + return; } const repoDir = determineRepoBaseDirFromCwd(); diff --git a/ng-dev/utils/ng-dev-service.ts b/ng-dev/utils/ng-dev-service.ts index d2acc5082..5dc79aa19 100644 --- a/ng-dev/utils/ng-dev-service.ts +++ b/ng-dev/utils/ng-dev-service.ts @@ -28,6 +28,9 @@ const firebaseConfig = { appId: '1:823469418460:web:009b51c93132b218761119', }; +/** Whether or not the middleware has already been run. */ +let ngDevServiceMiddlewareHasRun = false; + /** * Sets up middleware to ensure that configuration and setup is completed for commands which * require the ng-dev service @@ -54,8 +57,15 @@ export async function useNgDevService( }) .middleware( async (args: Arguments) => { - initializeApp(firebaseConfig); - await restoreNgTokenFromDiskIfValid(); + // TODO(josephperrott): remove this guard against running multiple times after + // https://github.com/yargs/yargs/issues/2223 is fixed + if (ngDevServiceMiddlewareHasRun) { + return; + } + ngDevServiceMiddlewareHasRun = true; + + initializeApp(firebaseConfig); + await restoreNgTokenFromDiskIfValid(); if (args.githubEscapeHatch === true) { Log.warn('This escape hatch should only be used if the service is erroring. Please'); @@ -81,7 +91,6 @@ export async function useNgDevService( Log.log('Log in by running the following command:'); Log.log(' yarn ng-dev auth login'); throw new Error('The user is not logged in'); - }, - ) + }, true) ); } diff --git a/ng-dev/utils/yargs.ts b/ng-dev/utils/yargs.ts new file mode 100644 index 000000000..a53b3649d --- /dev/null +++ b/ng-dev/utils/yargs.ts @@ -0,0 +1,24 @@ +import yargs, {Arguments, Argv} from 'yargs'; + +// A function to be called when the command completes. +type CompletedFn = (err: Error | null) => Promise | void; + +/** List of functions to be called upon command completion. */ +const completedFunctions: CompletedFn[] = []; + +/** Register a function to be called when the command completes. */ +export function registerCompletedFunction(fn: CompletedFn) { + completedFunctions.push(fn); +} + +/** + * Run the yargs process, as configured by the supplied function, calling a set of completion + * functions after the command completes. + */ +export function runParserWithCompletedFunctions(applyConfiguration: (argv: Argv) => Argv) { + applyConfiguration(yargs([])).parse(process.argv.slice(2), async (err: Error | null) => { + for (const completedFunc of completedFunctions) { + await completedFunc(err); + } + }); +}