From 49b2a42c808229e66cf9fa181c673d3f55f1875a Mon Sep 17 00:00:00 2001 From: Branden Rodgers Date: Mon, 13 Jan 2025 18:14:30 -0500 Subject: [PATCH 1/2] chore: Port DevServerManager to TS (#1335) --- commands/project/dev.ts | 7 +-- commands/theme/preview.ts | 8 +-- lib/DevServerManager.ts | 116 +++++++++++++++++++++++++++++--------- lib/LocalDevManager.ts | 17 +++--- lib/projects/structure.ts | 92 +++++------------------------- types/Projects.ts | 71 +++++++++++++++++++++++ 6 files changed, 185 insertions(+), 126 deletions(-) diff --git a/commands/project/dev.ts b/commands/project/dev.ts index 762028634..b77ce22cf 100644 --- a/commands/project/dev.ts +++ b/commands/project/dev.ts @@ -30,11 +30,10 @@ const { isAppDeveloperAccount, } = require('../../lib/accountTypes'); const { getValidEnv } = require('@hubspot/local-dev-lib/environment'); - +const { ComponentTypes } = require('../../types/Projects'); const { findProjectComponents, getProjectComponentTypes, - COMPONENT_TYPES, } = require('../../lib/projects/structure'); const { confirmDefaultAccountIsTarget, @@ -82,8 +81,8 @@ exports.handler = async options => { const components = await findProjectComponents(projectDir); const runnableComponents = components.filter(component => component.runnable); const componentTypes = getProjectComponentTypes(runnableComponents); - const hasPrivateApps = !!componentTypes[COMPONENT_TYPES.privateApp]; - const hasPublicApps = !!componentTypes[COMPONENT_TYPES.publicApp]; + const hasPrivateApps = !!componentTypes[ComponentTypes.PrivateApp]; + const hasPublicApps = !!componentTypes[ComponentTypes.PublicApp]; if (runnableComponents.length === 0) { logger.error( diff --git a/commands/theme/preview.ts b/commands/theme/preview.ts index c82f6dd46..5bbfdf027 100644 --- a/commands/theme/preview.ts +++ b/commands/theme/preview.ts @@ -20,10 +20,8 @@ const { ApiErrorContext, logError } = require('../../lib/errorHandlers/index'); const { handleExit, handleKeypress } = require('../../lib/process'); const { getThemeJSONPath } = require('@hubspot/local-dev-lib/cms/themes'); const { getProjectConfig } = require('../../lib/projects'); -const { - findProjectComponents, - COMPONENT_TYPES, -} = require('../../lib/projects/structure'); +const { findProjectComponents } = require('../../lib/projects/structure'); +const { ComponentTypes } = require('../../types/Projects'); const { preview } = require('@hubspot/theme-preview-dev-server'); const { hasFeature } = require('../../lib/hasFeature'); const i18nKey = 'commands.theme.subcommands.preview'; @@ -85,7 +83,7 @@ const determineSrcAndDest = async options => { if (!themeJsonPath) { const projectComponents = await findProjectComponents(projectDir); const themeComponents = projectComponents.filter( - c => c.type === COMPONENT_TYPES.hublTheme + c => c.type === ComponentTypes.HublTheme ); if (themeComponents.length === 0) { logger.error(i18n(`${i18nKey}.errors.noThemeComponents`)); diff --git a/lib/DevServerManager.ts b/lib/DevServerManager.ts index 525cf5751..90644e676 100644 --- a/lib/DevServerManager.ts +++ b/lib/DevServerManager.ts @@ -1,48 +1,77 @@ -// @ts-nocheck -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { COMPONENT_TYPES } = require('./projects/structure'); -const { i18n } = require('./lang'); -const { promptUser } = require('./prompts/promptUtils'); -const { DevModeInterface } = require('@hubspot/ui-extensions-dev-server'); -const { +import { logger } from '@hubspot/local-dev-lib/logger'; +import { Environment } from '@hubspot/local-dev-lib/types/Config'; +import { i18n } from './lang'; +import { promptUser } from './prompts/promptUtils'; +import { DevModeInterface as UIEDevModeInterface } from '@hubspot/ui-extensions-dev-server'; +import { startPortManagerServer, stopPortManagerServer, requestPorts, -} = require('@hubspot/local-dev-lib/portManager'); -const { +} from '@hubspot/local-dev-lib/portManager'; +import { getHubSpotApiOrigin, getHubSpotWebsiteOrigin, -} = require('@hubspot/local-dev-lib/urls'); -const { getAccountConfig } = require('@hubspot/local-dev-lib/config'); +} from '@hubspot/local-dev-lib/urls'; +import { getAccountConfig } from '@hubspot/local-dev-lib/config'; +import { ProjectConfig, ComponentTypes, Component } from '../types/Projects'; const i18nKey = 'lib.DevServerManager'; const SERVER_KEYS = { privateApp: 'privateApp', publicApp: 'publicApp', +} as const; + +type ServerKey = keyof typeof SERVER_KEYS; + +type DevServerInterface = { + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + setup?: Function; + start?: (options: object) => Promise; + fileChange?: (filePath: string, event: string) => Promise; + cleanup?: () => Promise; +}; + +type DevServer = { + componentType: ComponentTypes; + serverInterface: DevServerInterface; +}; + +type ComponentsByType = { + [key in ComponentTypes]?: { [key: string]: Component }; }; class DevServerManager { + private initialized: boolean; + private started: boolean; + private componentsByType: ComponentsByType; + private devServers: { [key in ServerKey]: DevServer }; + constructor() { this.initialized = false; this.started = false; this.componentsByType = {}; - this.server = null; - this.path = null; this.devServers = { [SERVER_KEYS.privateApp]: { - componentType: COMPONENT_TYPES.privateApp, - serverInterface: DevModeInterface, + componentType: ComponentTypes.PrivateApp, + serverInterface: UIEDevModeInterface, }, [SERVER_KEYS.publicApp]: { - componentType: COMPONENT_TYPES.publicApp, - serverInterface: DevModeInterface, + componentType: ComponentTypes.PublicApp, + serverInterface: UIEDevModeInterface, }, }; } - async iterateDevServers(callback) { - const serverKeys = Object.keys(this.devServers); + async iterateDevServers( + callback: ( + serverInterface: DevServerInterface, + compatibleComponents: { + [key: string]: Component; + } + ) => Promise + ): Promise { + const serverKeys: ServerKey[] = Object.keys(this.devServers) as ServerKey[]; for (let i = 0; i < serverKeys.length; i++) { const serverKey = serverKeys[i]; @@ -59,21 +88,37 @@ class DevServerManager { } } - arrangeComponentsByType(components) { - return components.reduce((acc, component) => { + arrangeComponentsByType(components: Component[]): ComponentsByType { + return components.reduce((acc, component) => { if (!acc[component.type]) { acc[component.type] = {}; } - acc[component.type][component.config.name] = component; + if ('name' in component.config && component.config.name) { + acc[component.type]![component.config.name] = component; + } return acc; }, {}); } - async setup({ components, onUploadRequired, accountId, setActiveApp }) { + async setup({ + components, + onUploadRequired, + accountId, + setActiveApp, + }: { + components: Component[]; + onUploadRequired: () => void; + accountId: number; + setActiveApp: (appUid: string | undefined) => Promise; + }): Promise { this.componentsByType = this.arrangeComponentsByType(components); - const { env } = getAccountConfig(accountId); + let env: Environment; + const accountConfig = getAccountConfig(accountId); + if (accountConfig) { + env = accountConfig.env; + } await startPortManagerServer(); await this.iterateDevServers( async (serverInterface, compatibleComponents) => { @@ -96,7 +141,13 @@ class DevServerManager { this.initialized = true; } - async start({ accountId, projectConfig }) { + async start({ + accountId, + projectConfig, + }: { + accountId: number; + projectConfig: ProjectConfig; + }): Promise { if (this.initialized) { await this.iterateDevServers(async serverInterface => { if (serverInterface.start) { @@ -114,7 +165,13 @@ class DevServerManager { this.started = true; } - fileChange({ filePath, event }) { + async fileChange({ + filePath, + event, + }: { + filePath: string; + event: string; + }): Promise { if (this.started) { this.iterateDevServers(async serverInterface => { if (serverInterface.fileChange) { @@ -124,7 +181,7 @@ class DevServerManager { } } - async cleanup() { + async cleanup(): Promise { if (this.started) { await this.iterateDevServers(async serverInterface => { if (serverInterface.cleanup) { @@ -137,4 +194,7 @@ class DevServerManager { } } -module.exports = new DevServerManager(); +const Manager = new DevServerManager(); + +export default Manager; +module.exports = Manager; diff --git a/lib/LocalDevManager.ts b/lib/LocalDevManager.ts index 5503f6c10..18af42151 100644 --- a/lib/LocalDevManager.ts +++ b/lib/LocalDevManager.ts @@ -22,11 +22,8 @@ const DevServerManager = require('./DevServerManager'); const { EXIT_CODES } = require('./enums/exitCodes'); const { getProjectDetailUrl } = require('./projects/urls'); const { getAccountHomeUrl } = require('./localDev'); -const { - CONFIG_FILES, - COMPONENT_TYPES, - getAppCardConfigs, -} = require('./projects/structure'); +const { CONFIG_FILES, getAppCardConfigs } = require('./projects/structure'); +const { ComponentTypes } = require('../types/Projects'); const { UI_COLORS, uiCommandReference, @@ -92,7 +89,7 @@ class LocalDevManager { return component.config.uid === appUid; }); - if (this.activeApp.type === COMPONENT_TYPES.publicApp) { + if (this.activeApp.type === ComponentTypes.PublicApp) { try { await this.setActivePublicAppData(); await this.checkActivePublicAppInstalls(); @@ -212,7 +209,7 @@ class LocalDevManager { ) ); - if (this.activeApp.type === COMPONENT_TYPES.publicApp) { + if (this.activeApp.type === ComponentTypes.PublicApp) { logger.log( uiLink( i18n(`${i18nKey}.viewTestAccountLink`), @@ -319,7 +316,7 @@ class LocalDevManager { let warning = reason; if (!reason) { warning = - this.activeApp.type === COMPONENT_TYPES.publicApp && + this.activeApp.type === ComponentTypes.PublicApp && this.publicAppActiveInstalls > 0 ? i18n(`${i18nKey}.uploadWarning.defaultPublicAppWarning`, { installCount: this.publicAppActiveInstalls, @@ -383,7 +380,7 @@ class LocalDevManager { const missingComponents = []; this.runnableComponents.forEach(({ type, config, path }) => { - if (Object.values(COMPONENT_TYPES).includes(type)) { + if (Object.values(ComponentTypes).includes(type)) { const cardConfigs = getAppCardConfigs(config, path); if (!deployedComponentNames.includes(config.name)) { @@ -423,7 +420,7 @@ class LocalDevManager { }); const configPaths = this.runnableComponents - .filter(({ type }) => Object.values(COMPONENT_TYPES).includes(type)) + .filter(({ type }) => Object.values(ComponentTypes).includes(type)) .map(component => { const appConfigPath = path.join( component.path, diff --git a/lib/projects/structure.ts b/lib/projects/structure.ts index a36b10480..d6ad0a903 100644 --- a/lib/projects/structure.ts +++ b/lib/projects/structure.ts @@ -1,89 +1,23 @@ import * as fs from 'fs'; import * as path from 'path'; -import { ValueOf } from '@hubspot/local-dev-lib/types/Utils'; import { walk } from '@hubspot/local-dev-lib/fs'; import { logger } from '@hubspot/local-dev-lib/logger'; import { logError } from '../errorHandlers/index'; - -export type Component = { - type: ComponentTypes; - config: object; - runnable: boolean; - path: string; -}; - -type PrivateAppComponentConfig = { - name: string; - description: string; - uid: string; - scopes: Array; - public: boolean; - extensions?: { - crm: { - cards: Array<{ file: string }>; - }; - }; -}; - -type PublicAppComponentConfig = { - name: string; - uid: string; - description: string; - allowedUrls: Array; - auth: { - redirectUrls: Array; - requiredScopes: Array; - optionalScopes: Array; - conditionallyRequiredScopes: Array; - }; - support: { - supportEmail: string; - documentationUrl: string; - supportUrl: string; - supportPhone: string; - }; - extensions?: { - crm: { - cards: Array<{ file: string }>; - }; - }; - webhooks?: { - file: string; - }; -}; - -type AppCardComponentConfig = { - type: 'crm-card'; - data: { - title: string; - uid: string; - location: string; - module: { - file: string; - }; - objectTypes: Array<{ name: string }>; - }; -}; - -type GenericComponentConfig = - | PublicAppComponentConfig - | PrivateAppComponentConfig - | AppCardComponentConfig; - -export const COMPONENT_TYPES = { - privateApp: 'private-app', - publicApp: 'public-app', - hublTheme: 'hubl-theme', -} as const; - -type ComponentTypes = ValueOf; +import { + ComponentTypes, + Component, + GenericComponentConfig, + PublicAppComponentConfig, + PrivateAppComponentConfig, + AppCardComponentConfig, +} from '../../types/Projects'; export const CONFIG_FILES: { - [k in ValueOf]: string; + [k in ComponentTypes]: string; } = { - [COMPONENT_TYPES.privateApp]: 'app.json', - [COMPONENT_TYPES.publicApp]: 'public-app.json', - [COMPONENT_TYPES.hublTheme]: 'theme.json', + [ComponentTypes.PrivateApp]: 'app.json', + [ComponentTypes.PublicApp]: 'public-app.json', + [ComponentTypes.HublTheme]: 'theme.json', }; function getComponentTypeFromConfigFile( @@ -189,7 +123,7 @@ export async function findProjectComponents( if (parsedConfig) { const isLegacy = getIsLegacyApp(parsedConfig, dir); - const isHublTheme = base === CONFIG_FILES[COMPONENT_TYPES.hublTheme]; + const isHublTheme = base === CONFIG_FILES[ComponentTypes.HublTheme]; const componentType = getComponentTypeFromConfigFile(base); if (componentType) { diff --git a/types/Projects.ts b/types/Projects.ts index e6010faeb..0d7b9ac25 100644 --- a/types/Projects.ts +++ b/types/Projects.ts @@ -49,3 +49,74 @@ export type ProjectTemplateRepoConfig = { projects?: ProjectTemplate[]; components?: ComponentTemplate[]; }; + +export type PrivateAppComponentConfig = { + name: string; + description: string; + uid: string; + scopes: Array; + public: boolean; + extensions?: { + crm: { + cards: Array<{ file: string }>; + }; + }; +}; + +export type PublicAppComponentConfig = { + name: string; + uid: string; + description: string; + allowedUrls: Array; + auth: { + redirectUrls: Array; + requiredScopes: Array; + optionalScopes: Array; + conditionallyRequiredScopes: Array; + }; + support: { + supportEmail: string; + documentationUrl: string; + supportUrl: string; + supportPhone: string; + }; + extensions?: { + crm: { + cards: Array<{ file: string }>; + }; + }; + webhooks?: { + file: string; + }; +}; + +export type AppCardComponentConfig = { + type: 'crm-card'; + data: { + title: string; + uid: string; + location: string; + module: { + file: string; + }; + objectTypes: Array<{ name: string }>; + }; +}; + +export type GenericComponentConfig = + | PublicAppComponentConfig + | PrivateAppComponentConfig + | AppCardComponentConfig; + +export enum ComponentTypes { + PrivateApp = 'private-app', + PublicApp = 'public-app', + HublTheme = 'hubl-theme', +} + +export type Component = { + type: ComponentTypes; + config: GenericComponentConfig; + runnable: boolean; + path: string; +}; From 48e9d585fd83e7c451fcd7dd4b7562257858e377 Mon Sep 17 00:00:00 2001 From: Joe Yeager Date: Tue, 14 Jan 2025 10:03:51 -0800 Subject: [PATCH 2/2] fix: Revert regression to the debug logging for axios errors (#1338) --- lib/errorHandlers/index.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/errorHandlers/index.ts b/lib/errorHandlers/index.ts index 0a521d1c9..45f74b8e3 100644 --- a/lib/errorHandlers/index.ts +++ b/lib/errorHandlers/index.ts @@ -6,6 +6,7 @@ import { import { shouldSuppressError } from './suppressError'; import { i18n } from '../lang'; import util from 'util'; +import { isAxiosError } from 'axios'; const i18nKey = 'lib.errorHandlers.index'; @@ -50,10 +51,12 @@ export function debugError(error: unknown, context?: ApiErrorContext): void { logger.debug(i18n(`${i18nKey}.errorOccurred`, { error: String(error) })); } - if (error instanceof Error) { + if (error instanceof Error && error.cause) { logger.debug( i18n(`${i18nKey}.errorCause`, { - cause: util.inspect(error.cause, false, null, true), + cause: isAxiosError(error.cause) + ? `${error.cause}` + : util.inspect(error.cause, false, null, true), }) ); }