From 39c687e84514872fa0fd13cb3d7684bdb1a819de Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Fri, 9 May 2025 11:56:13 -0400 Subject: [PATCH 1/4] Convert function ls command to TS --- commands/function.ts | 16 +++---- commands/function/list.ts | 88 ++++++++++++++++++++++++++------------- 2 files changed, 66 insertions(+), 38 deletions(-) diff --git a/commands/function.ts b/commands/function.ts index d99c59e43..e9ceba87a 100644 --- a/commands/function.ts +++ b/commands/function.ts @@ -1,16 +1,16 @@ -// @ts-nocheck -const { addGlobalOptions } = require('../lib/commonOpts'); -const list = require('./function/list'); +import { Argv } from 'yargs'; +import { addGlobalOptions } from '../lib/commonOpts'; +import list from './function/list'; const deploy = require('./function/deploy'); const server = require('./function/server'); -const { i18n } = require('../lib/lang'); +import { i18n } from '../lib/lang'; -exports.command = ['function', 'functions']; -exports.describe = i18n(`commands.function.describe`); +export const command = ['function', 'functions']; +export const describe = i18n(`commands.function.describe`); -exports.builder = yargs => { +export function builder(yargs: Argv): Argv { addGlobalOptions(yargs); yargs.command(list).command(deploy).command(server).demandCommand(1, ''); return yargs; -}; +} diff --git a/commands/function/list.ts b/commands/function/list.ts index 718204919..211816210 100644 --- a/commands/function/list.ts +++ b/commands/function/list.ts @@ -1,26 +1,36 @@ -// @ts-nocheck -const moment = require('moment'); -const { getRoutes } = require('@hubspot/local-dev-lib/api/functions'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { logError, ApiErrorContext } = require('../../lib/errorHandlers/index'); -const { getTableContents, getTableHeader } = require('../../lib/ui/table'); -const { - addConfigOptions, - addAccountOptions, - addUseEnvironmentOptions, -} = require('../../lib/commonOpts'); -const { trackCommandUsage } = require('../../lib/usageTracking'); -const { i18n } = require('../../lib/lang'); - -const { EXIT_CODES } = require('../../lib/enums/exitCodes'); - -exports.command = ['list', 'ls']; -exports.describe = i18n('commands.function.subcommands.list.describe'); - -exports.handler = async options => { - const { derivedAccountId } = options; - - trackCommandUsage('functions-list', null, derivedAccountId); +import { Argv, ArgumentsCamelCase } from 'yargs'; +import moment from 'moment'; +import { getRoutes } from '@hubspot/local-dev-lib/api/functions'; +import { logger } from '@hubspot/local-dev-lib/logger'; + +import { logError, ApiErrorContext } from '../../lib/errorHandlers/index'; +import { getTableContents, getTableHeader } from '../../lib/ui/table'; +import { trackCommandUsage } from '../../lib/usageTracking'; +import { i18n } from '../../lib/lang'; +import { EXIT_CODES } from '../../lib/enums/exitCodes'; +import { + CommonArgs, + ConfigArgs, + AccountArgs, + EnvironmentArgs, + YargsCommandModule, +} from '../../types/Yargs'; +import { makeYargsBuilder } from '../../lib/yargsUtils'; + +const command = ['list', 'ls']; +const describe = i18n('commands.function.subcommands.list.describe'); + +type FunctionListArgs = CommonArgs & + ConfigArgs & + AccountArgs & + EnvironmentArgs & { json?: boolean }; + +async function handler( + args: ArgumentsCamelCase +): Promise { + const { derivedAccountId } = args; + + trackCommandUsage('function-list', undefined, derivedAccountId); logger.debug( i18n('commands.function.subcommands.list.debug.gettingFunctions') @@ -39,7 +49,7 @@ exports.handler = async options => { ); } - if (options.json) { + if (args.json) { return logger.log(routesResp.objects); } @@ -58,13 +68,9 @@ exports.handler = async options => { getTableHeader(['Route', 'Method', 'Secrets', 'Created', 'Updated']) ); return logger.log(getTableContents(functionsAsArrays)); -}; - -exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); - addUseEnvironmentOptions(yargs); +} +function functionListBuilder(yargs: Argv): Argv { yargs.options({ json: { describe: i18n( @@ -73,4 +79,26 @@ exports.builder = yargs => { type: 'boolean', }, }); + + return yargs as Argv; +} + +const builder = makeYargsBuilder( + functionListBuilder, + command, + describe, + { + useConfigOptions: true, + useAccountOptions: true, + useEnvironmentOptions: true, + } +); + +const functionListCommand: YargsCommandModule = { + command, + describe, + handler, + builder, }; + +export default functionListCommand; From 0f5b4bf0bb2dd4848d1e406bed42e87f4c979d86 Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Fri, 9 May 2025 12:36:22 -0400 Subject: [PATCH 2/4] Correct typing command bucket --- commands/function.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/commands/function.ts b/commands/function.ts index e9ceba87a..340ad7120 100644 --- a/commands/function.ts +++ b/commands/function.ts @@ -4,13 +4,27 @@ import list from './function/list'; const deploy = require('./function/deploy'); const server = require('./function/server'); import { i18n } from '../lib/lang'; +import { makeYargsBuilder } from '../lib/yargsUtils'; export const command = ['function', 'functions']; export const describe = i18n(`commands.function.describe`); -export function builder(yargs: Argv): Argv { +function functionBuilder(yargs: Argv): Argv { addGlobalOptions(yargs); yargs.command(list).command(deploy).command(server).demandCommand(1, ''); - return yargs; } + +const builder = makeYargsBuilder(functionBuilder, command, describe); + +const functionCommand = { + command, + describe, + builder, + handler: () => {}, +}; + +export default functionCommand; + +// TODO Remove this legacy export once we've migrated all commands to TS +module.exports = functionCommand; From 303f8bf988d5f74d9f5b996d90604996096b5dc9 Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Fri, 9 May 2025 13:40:01 -0400 Subject: [PATCH 3/4] Convert function deploy to TS --- commands/function.ts | 5 +- commands/function/deploy.ts | 126 ++++++++++++++++++++++++------------ 2 files changed, 86 insertions(+), 45 deletions(-) diff --git a/commands/function.ts b/commands/function.ts index 340ad7120..04df77606 100644 --- a/commands/function.ts +++ b/commands/function.ts @@ -1,10 +1,11 @@ import { Argv } from 'yargs'; import { addGlobalOptions } from '../lib/commonOpts'; import list from './function/list'; -const deploy = require('./function/deploy'); +import deploy from './function/deploy'; const server = require('./function/server'); import { i18n } from '../lib/lang'; import { makeYargsBuilder } from '../lib/yargsUtils'; +import { YargsCommandModuleBucket } from '../types/Yargs'; export const command = ['function', 'functions']; export const describe = i18n(`commands.function.describe`); @@ -17,7 +18,7 @@ function functionBuilder(yargs: Argv): Argv { const builder = makeYargsBuilder(functionBuilder, command, describe); -const functionCommand = { +const functionCommand: YargsCommandModuleBucket = { command, describe, builder, diff --git a/commands/function/deploy.ts b/commands/function/deploy.ts index 1293a0402..1acfadc38 100644 --- a/commands/function/deploy.ts +++ b/commands/function/deploy.ts @@ -1,31 +1,42 @@ -// @ts-nocheck -const SpinniesManager = require('../../lib/ui/SpinniesManager'); -const { - addAccountOptions, - addConfigOptions, - addUseEnvironmentOptions, -} = require('../../lib/commonOpts'); -const { trackCommandUsage } = require('../../lib/usageTracking'); -const { logError, ApiErrorContext } = require('../../lib/errorHandlers/index'); -const { uiAccountDescription } = require('../../lib/ui'); -const { poll } = require('../../lib/polling'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { +import { Argv, ArgumentsCamelCase } from 'yargs'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { buildPackage, getBuildStatus, -} = require('@hubspot/local-dev-lib/api/functions'); -const { outputBuildLog } = require('../../lib/serverlessLogs'); -const { i18n } = require('../../lib/lang'); -const { isHubSpotHttpError } = require('@hubspot/local-dev-lib/errors/index'); +} from '@hubspot/local-dev-lib/api/functions'; +import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index'; -exports.command = 'deploy '; -exports.describe = false; +import SpinniesManager from '../../lib/ui/SpinniesManager'; +import { trackCommandUsage } from '../../lib/usageTracking'; +import { logError, ApiErrorContext } from '../../lib/errorHandlers/index'; +import { uiAccountDescription } from '../../lib/ui'; +import { poll } from '../../lib/polling'; +import { outputBuildLog } from '../../lib/serverlessLogs'; +import { i18n } from '../../lib/lang'; +import { + CommonArgs, + ConfigArgs, + AccountArgs, + EnvironmentArgs, + YargsCommandModule, +} from '../../types/Yargs'; +import { makeYargsBuilder } from '../../lib/yargsUtils'; -exports.handler = async options => { - const { path: functionPath, derivedAccountId } = options; +const command = 'deploy '; +const describe = undefined; + +type FunctionDeployArgs = CommonArgs & + ConfigArgs & + AccountArgs & + EnvironmentArgs & { path: string }; + +async function handler( + args: ArgumentsCamelCase +): Promise { + const { path: functionPath, derivedAccountId } = args; const splitFunctionPath = functionPath.split('.'); - trackCommandUsage('functions-deploy', null, derivedAccountId); + trackCommandUsage('function-deploy', undefined, derivedAccountId); if ( !splitFunctionPath.length || @@ -60,21 +71,27 @@ exports.handler = async options => { functionPath ); const successResp = await poll(() => - getBuildStatus(derivedAccountId, buildId) + getBuildStatus(derivedAccountId, Number(buildId)) ); - const buildTimeSeconds = (successResp.buildTime / 1000).toFixed(2); + if (successResp) { + const buildTimeSeconds = successResp.buildTime + ? (successResp.buildTime / 1000).toFixed(2) + : 0; - SpinniesManager.succeed('loading'); + SpinniesManager.succeed('loading'); - await outputBuildLog(successResp.cdnUrl); - logger.success( - i18n('commands.function.subcommands.deploy.success.deployed', { - accountId: derivedAccountId, - buildTimeSeconds, - functionPath, - }) - ); - } catch (e) { + if (successResp.cdnUrl) { + await outputBuildLog(successResp.cdnUrl); + } + logger.success( + i18n('commands.function.subcommands.deploy.success.deployed', { + accountId: derivedAccountId, + buildTimeSeconds, + functionPath, + }) + ); + } + } catch (e: unknown) { SpinniesManager.fail('loading', { text: i18n('commands.function.subcommands.deploy.loadingFailed', { account: uiAccountDescription(derivedAccountId), @@ -88,11 +105,18 @@ exports.handler = async options => { functionPath, }) ); - } else if (e.status === 'ERROR') { - await outputBuildLog(e.cdnUrl); + } else if ( + typeof e === 'object' && + e !== null && + 'status' in e && + e.status === 'ERROR' && + 'cdnUrl' in e && + 'errorReason' in e + ) { + await outputBuildLog(e.cdnUrl as string); logger.error( i18n('commands.function.subcommands.deploy.errors.buildError', { - details: e.errorReason, + details: String(e.errorReason), }) ); } else { @@ -105,9 +129,9 @@ exports.handler = async options => { ); } } -}; +} -exports.builder = yargs => { +function functionDeployBuilder(yargs: Argv): Argv { yargs.positional('path', { describe: i18n( 'commands.function.subcommands.deploy.positionals.path.describe' @@ -122,9 +146,25 @@ exports.builder = yargs => { ], ]); - addConfigOptions(yargs); - addAccountOptions(yargs); - addUseEnvironmentOptions(yargs); + return yargs as Argv; +} - return yargs; +const builder = makeYargsBuilder( + functionDeployBuilder, + command, + describe, + { + useConfigOptions: true, + useAccountOptions: true, + useEnvironmentOptions: true, + } +); + +const functionDeployCommand: YargsCommandModule = { + command, + describe, + handler, + builder, }; + +export default functionDeployCommand; From 1541add29ec2753d9a7aa8ebeb0be43fc4ed2378 Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Fri, 9 May 2025 14:09:02 -0400 Subject: [PATCH 4/4] Add error type guard --- commands/function/deploy.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/commands/function/deploy.ts b/commands/function/deploy.ts index 1acfadc38..3459a8133 100644 --- a/commands/function/deploy.ts +++ b/commands/function/deploy.ts @@ -22,6 +22,18 @@ import { } from '../../types/Yargs'; import { makeYargsBuilder } from '../../lib/yargsUtils'; +type FunctionBuildError = { + status: 'ERROR'; + errorReason: string; + cdnUrl: string; +}; + +function isFunctionBuildError(e: unknown): e is FunctionBuildError { + return ( + typeof e === 'object' && e !== null && 'status' in e && e.status === 'ERROR' + ); +} + const command = 'deploy '; const describe = undefined; @@ -105,15 +117,8 @@ async function handler( functionPath, }) ); - } else if ( - typeof e === 'object' && - e !== null && - 'status' in e && - e.status === 'ERROR' && - 'cdnUrl' in e && - 'errorReason' in e - ) { - await outputBuildLog(e.cdnUrl as string); + } else if (isFunctionBuildError(e)) { + await outputBuildLog(e.cdnUrl); logger.error( i18n('commands.function.subcommands.deploy.errors.buildError', { details: String(e.errorReason),