From fff4fa2b22aea57862b566163dd9da67785b2701 Mon Sep 17 00:00:00 2001 From: Camden Phalen Date: Wed, 31 Jul 2024 16:43:50 -0400 Subject: [PATCH 01/25] Start exporting types --- api/appsDev.ts | 5 +---- api/customObjects.ts | 20 +++++--------------- api/designManager.ts | 16 ++++------------ api/github.ts | 4 +--- api/localDevAuth.ts | 16 +--------------- api/projects.ts | 6 +----- api/secrets.ts | 5 +---- config/config_DEPRECATED.ts | 4 ---- types/Accounts.ts | 17 +++++++++++++++++ types/Apps.ts | 4 ++++ types/DesignManager.ts | 11 +++++++++++ types/Github.ts | 2 ++ types/Project.ts | 5 +++++ types/Schemas.ts | 14 ++++++++++++++ types/Secrets.ts | 3 +++ 15 files changed, 70 insertions(+), 62 deletions(-) create mode 100644 types/DesignManager.ts create mode 100644 types/Secrets.ts diff --git a/api/appsDev.ts b/api/appsDev.ts index ac87712b..346bf364 100644 --- a/api/appsDev.ts +++ b/api/appsDev.ts @@ -3,14 +3,11 @@ import { PublicApp, PublicApInstallCounts, PublicAppDeveloperTestAccountInstallData, + FetchPublicAppsForPortalResponse, } from '../types/Apps'; const APPS_DEV_API_PATH = 'apps-dev/external/public/v3'; -type FetchPublicAppsForPortalResponse = { - results: Array; -}; - export async function fetchPublicAppsForPortal( accountId: number ): Promise> { diff --git a/api/customObjects.ts b/api/customObjects.ts index 6d9cec0d..99bac912 100644 --- a/api/customObjects.ts +++ b/api/customObjects.ts @@ -1,23 +1,13 @@ import http from '../http'; -import { FetchSchemasResponse, Schema } from '../types/Schemas'; +import { + FetchSchemasResponse, + Schema, + CreateObjectsResponse, +} from '../types/Schemas'; const CUSTOM_OBJECTS_API_PATH = 'crm/v3/objects'; const SCHEMA_API_PATH = 'crm-object-schemas/v3/schemas'; -type CreateObjectsResponse = { - status: string; - startedAt: string; - completedAt: string; - results: Array<{ - id: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - properties: Array; - createdAt: string; - updatedAt: string; - archived: boolean; - }>; -}; - export async function batchCreateObjects( accountId: number, objectTypeId: string, diff --git a/api/designManager.ts b/api/designManager.ts index 0e53c7fd..23b32dc2 100644 --- a/api/designManager.ts +++ b/api/designManager.ts @@ -1,16 +1,12 @@ import http from '../http'; import { QueryParams } from '../types/Http'; +import { + FetchThemesResponse, + FetchBuiltinMappingResponse, +} from '../types/DesignManager'; const DESIGN_MANAGER_API_PATH = 'designmanager/v1'; -type FetchThemesResponse = { - objects: Array<{ - theme: { - path: string; - }; - }>; -}; - export async function fetchThemes( accountId: number, params: QueryParams = {} @@ -21,10 +17,6 @@ export async function fetchThemes( }); } -type FetchBuiltinMappingResponse = { - [key: string]: string; -}; - export async function fetchBuiltinMapping( accountId: number ): Promise { diff --git a/api/github.ts b/api/github.ts index 238e04c5..a04b3eac 100644 --- a/api/github.ts +++ b/api/github.ts @@ -1,6 +1,6 @@ import axios, { AxiosResponse } from 'axios'; import { getDefaultUserAgentHeader } from '../http/getAxiosConfig'; -import { GithubReleaseData, GithubRepoFile } from '../types/Github'; +import { GithubReleaseData, GithubRepoFile, RepoPath } from '../types/Github'; const GITHUB_REPOS_API = 'https://api.github.com/repos'; const GITHUB_RAW_CONTENT_API_PATH = 'https://raw.githubusercontent.com'; @@ -10,8 +10,6 @@ declare global { var githubToken: string; } -type RepoPath = `${string}/${string}`; - const GITHUB_AUTH_HEADERS = { authorization: global && global.githubToken ? `Bearer ${global.githubToken}` : null, diff --git a/api/localDevAuth.ts b/api/localDevAuth.ts index f52a3108..4b28a8d2 100644 --- a/api/localDevAuth.ts +++ b/api/localDevAuth.ts @@ -2,26 +2,12 @@ import { getAxiosConfig } from '../http/getAxiosConfig'; import http from '../http'; import { ENVIRONMENTS } from '../constants/environments'; import { Environment } from '../types/Config'; -import { ScopeData } from '../types/Accounts'; +import { ScopeData, AccessTokenResponse } from '../types/Accounts'; import axios from 'axios'; -import { HUBSPOT_ACCOUNT_TYPES } from '../constants/config'; -import { ValueOf } from '../types/Utils'; import { PublicAppInstallationData } from '../types/Apps'; const LOCALDEVAUTH_API_AUTH_PATH = 'localdevauth/v1/auth'; -type AccessTokenResponse = { - hubId: number; - userId: number; - oauthAccessToken: string; - expiresAtMillis: number; - enabledFeatures?: { [key: string]: number }; - scopeGroups: Array; - encodedOAuthRefreshToken: string; - hubName: string; - accountType: ValueOf; -}; - export async function fetchAccessToken( personalAccessKey: string, env: Environment = ENVIRONMENTS.PROD, diff --git a/api/projects.ts b/api/projects.ts index 801a0a42..b0699f90 100644 --- a/api/projects.ts +++ b/api/projects.ts @@ -6,6 +6,7 @@ import { FetchProjectResponse, UploadProjectResponse, ProjectSettings, + FetchPlatformVersionResponse, } from '../types/Project'; import { Build, FetchProjectBuildsResponse } from '../types/Build'; import { @@ -101,11 +102,6 @@ export async function deleteProject( }); } -type FetchPlatformVersionResponse = { - defaultPlatformVersion: string; - activePlatformVersions: Array; -}; - export async function fetchPlatformVersions( accountId: number ): Promise { diff --git a/api/secrets.ts b/api/secrets.ts index 14013517..b36998b4 100644 --- a/api/secrets.ts +++ b/api/secrets.ts @@ -1,11 +1,8 @@ import http from '../http'; +import { FetchSecretsResponse } from '../types/Secrets'; const SECRETS_API_PATH = 'cms/v3/functions/secrets'; -type FetchSecretsResponse = { - results: Array; -}; - export async function addSecret( accountId: number, key: string, diff --git a/config/config_DEPRECATED.ts b/config/config_DEPRECATED.ts index b3a6a1c3..1e146c9a 100644 --- a/config/config_DEPRECATED.ts +++ b/config/config_DEPRECATED.ts @@ -475,10 +475,6 @@ export function removeSandboxAccountFromConfig( return promptDefaultAccount; } -type UpdateAccountConfigOptions = Partial & { - environment?: Environment; -}; - /** * @throws {Error} */ diff --git a/types/Accounts.ts b/types/Accounts.ts index 103d6cbc..3839de5c 100644 --- a/types/Accounts.ts +++ b/types/Accounts.ts @@ -122,3 +122,20 @@ export type ScopeData = { portalScopesInGroup: Array; userScopesInGroup: Array; }; + +export type AccessTokenResponse = { + hubId: number; + userId: number; + oauthAccessToken: string; + expiresAtMillis: number; + enabledFeatures?: { [key: string]: number }; + scopeGroups: Array; + encodedOAuthRefreshToken: string; + hubName: string; + accountType: ValueOf; +}; + +export type UpdateAccountConfigOptions = + Partial & { + environment?: Environment; + }; diff --git a/types/Apps.ts b/types/Apps.ts index aedb55e8..bb01f807 100644 --- a/types/Apps.ts +++ b/types/Apps.ts @@ -66,3 +66,7 @@ export type PublicApp = { allowedExternalUrls: Array; preventProjectMigrations?: boolean; }; + +export type FetchPublicAppsForPortalResponse = { + results: Array; +}; diff --git a/types/DesignManager.ts b/types/DesignManager.ts new file mode 100644 index 00000000..990d4e7c --- /dev/null +++ b/types/DesignManager.ts @@ -0,0 +1,11 @@ +export type FetchThemesResponse = { + objects: Array<{ + theme: { + path: string; + }; + }>; +}; + +export type FetchBuiltinMappingResponse = { + [key: string]: string; +}; diff --git a/types/Github.ts b/types/Github.ts index 4bd182c2..ff3b2ddc 100644 --- a/types/Github.ts +++ b/types/Github.ts @@ -64,3 +64,5 @@ export interface GithubSourceData { repositoryName: string; source: string; } + +export type RepoPath = `${string}/${string}`; diff --git a/types/Project.ts b/types/Project.ts index 4c18b10c..e37c0371 100644 --- a/types/Project.ts +++ b/types/Project.ts @@ -40,3 +40,8 @@ export type UploadProjectResponse = { export type ProjectSettings = { isAutoDeployEnabled: boolean; }; + +export type FetchPlatformVersionResponse = { + defaultPlatformVersion: string; + activePlatformVersions: Array; +}; diff --git a/types/Schemas.ts b/types/Schemas.ts index fea5fab9..41de172f 100644 --- a/types/Schemas.ts +++ b/types/Schemas.ts @@ -26,3 +26,17 @@ export type Schema = { }; export type FetchSchemasResponse = { results: Array }; + +export type CreateObjectsResponse = { + status: string; + startedAt: string; + completedAt: string; + results: Array<{ + id: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + properties: Array; + createdAt: string; + updatedAt: string; + archived: boolean; + }>; +}; diff --git a/types/Secrets.ts b/types/Secrets.ts new file mode 100644 index 00000000..9e7ccad1 --- /dev/null +++ b/types/Secrets.ts @@ -0,0 +1,3 @@ +export type FetchSecretsResponse = { + results: Array; +}; From 948be017ff7449d804d899e2019ead3602e2cba9 Mon Sep 17 00:00:00 2001 From: Camden Phalen Date: Thu, 1 Aug 2024 12:54:51 -0400 Subject: [PATCH 02/25] Export some more types --- config/configUtils.ts | 26 ++++---------------------- config/environment.ts | 16 +++++----------- errors/errors_DEPRECATED.ts | 10 +++++----- lib/archive.ts | 12 +----------- types/Accounts.ts | 21 +++++++++++++++++++++ types/Archive.ts | 10 ++++++++++ types/Config.ts | 10 ++++++++++ types/Error.ts | 4 ++++ 8 files changed, 60 insertions(+), 49 deletions(-) create mode 100644 types/Archive.ts diff --git a/config/configUtils.ts b/config/configUtils.ts index 6319bada..93428cc2 100644 --- a/config/configUtils.ts +++ b/config/configUtils.ts @@ -4,13 +4,16 @@ import { OAUTH_AUTH_METHOD, PERSONAL_ACCESS_KEY_AUTH_METHOD, } from '../constants/auth'; -import { CLIConfig_NEW, Environment } from '../types/Config'; +import { CLIConfig_NEW } from '../types/Config'; import { AuthType, CLIAccount_NEW, APIKeyAccount_NEW, OAuthAccount_NEW, PersonalAccessKeyAccount_NEW, + PersonalAccessKeyOptions, + OAuthOptions, + APIKeyOptions, } from '../types/Accounts'; import { i18n } from '../utils/lang'; @@ -52,12 +55,6 @@ export function getOrderedConfig( }; } -type PersonalAccessKeyOptions = { - accountId: number; - personalAccessKey: string; - env: Environment; -}; - function generatePersonalAccessKeyAccountConfig({ accountId, personalAccessKey, @@ -71,15 +68,6 @@ function generatePersonalAccessKeyAccountConfig({ }; } -type OAuthOptions = { - accountId: number; - clientId: string; - clientSecret: string; - refreshToken: string; - scopes: Array; - env: Environment; -}; - function generateOauthAccountConfig({ accountId, clientId, @@ -103,12 +91,6 @@ function generateOauthAccountConfig({ }; } -type APIKeyOptions = { - accountId: number; - apiKey: string; - env: Environment; -}; - function generateApiKeyAccountConfig({ accountId, apiKey, diff --git a/config/environment.ts b/config/environment.ts index 1db88a47..6062261f 100644 --- a/config/environment.ts +++ b/config/environment.ts @@ -1,4 +1,8 @@ -import { CLIConfig_NEW, Environment } from '../types/Config'; +import { + CLIConfig_NEW, + Environment, + EnvironmentConfigVariables, +} from '../types/Config'; import { logger } from '../lib/logger'; import { ENVIRONMENT_VARIABLES } from '../constants/environments'; import { @@ -13,16 +17,6 @@ import { i18n } from '../utils/lang'; const i18nKey = 'config.environment'; -type EnvironmentConfigVariables = { - apiKey?: string; - clientId?: string; - clientSecret?: string; - personalAccessKey?: string; - accountId?: number; - refreshToken?: string; - env?: Environment; -}; - function getConfigVariablesFromEnv(): EnvironmentConfigVariables { const env = process.env; diff --git a/errors/errors_DEPRECATED.ts b/errors/errors_DEPRECATED.ts index 492bc529..6571aeea 100644 --- a/errors/errors_DEPRECATED.ts +++ b/errors/errors_DEPRECATED.ts @@ -1,8 +1,8 @@ -import { BaseError, FileSystemErrorContext } from '../types/Error'; - -type ErrorContext = { - accountId?: number; -}; +import { + BaseError, + FileSystemErrorContext, + ErrorContext, +} from '../types/Error'; function isSystemError(err: BaseError) { return err.errno != null && err.code != null && err.syscall != null; diff --git a/lib/archive.ts b/lib/archive.ts index 0d882ac2..9f73b9d3 100644 --- a/lib/archive.ts +++ b/lib/archive.ts @@ -7,14 +7,10 @@ import { throwFileSystemError } from '../errors/fileSystemErrors'; import { throwErrorWithMessage } from '../errors/standardErrors'; import { logger } from './logger'; import { i18n } from '../utils/lang'; +import { ZipData, CopySourceToDestOptions } from '../types/Archive'; const i18nKey = 'lib.archive'; -type ZipData = { - extractDir: string; - tmpDir: string; -}; - async function extractZip( name: string, zip: Buffer, @@ -59,12 +55,6 @@ async function extractZip( return result; } -type CopySourceToDestOptions = { - sourceDir?: string; - includesRootDir?: boolean; - hideLogs?: boolean; -}; - async function copySourceToDest( src: string, dest: string, diff --git a/types/Accounts.ts b/types/Accounts.ts index 3839de5c..201ff8f2 100644 --- a/types/Accounts.ts +++ b/types/Accounts.ts @@ -139,3 +139,24 @@ export type UpdateAccountConfigOptions = Partial & { environment?: Environment; }; + +export type PersonalAccessKeyOptions = { + accountId: number; + personalAccessKey: string; + env: Environment; +}; + +export type OAuthOptions = { + accountId: number; + clientId: string; + clientSecret: string; + refreshToken: string; + scopes: Array; + env: Environment; +}; + +export type APIKeyOptions = { + accountId: number; + apiKey: string; + env: Environment; +}; diff --git a/types/Archive.ts b/types/Archive.ts new file mode 100644 index 00000000..56764308 --- /dev/null +++ b/types/Archive.ts @@ -0,0 +1,10 @@ +export type ZipData = { + extractDir: string; + tmpDir: string; +}; + +export type CopySourceToDestOptions = { + sourceDir?: string; + includesRootDir?: boolean; + hideLogs?: boolean; +}; diff --git a/types/Config.ts b/types/Config.ts index 8c9a0972..0809ca58 100644 --- a/types/Config.ts +++ b/types/Config.ts @@ -25,3 +25,13 @@ export interface CLIConfig_DEPRECATED { export type CLIConfig = CLIConfig_NEW | CLIConfig_DEPRECATED; export type Environment = ValueOf | ''; + +export type EnvironmentConfigVariables = { + apiKey?: string; + clientId?: string; + clientSecret?: string; + personalAccessKey?: string; + accountId?: number; + refreshToken?: string; + env?: Environment; +}; diff --git a/types/Error.ts b/types/Error.ts index 555d553c..3be8806d 100644 --- a/types/Error.ts +++ b/types/Error.ts @@ -38,3 +38,7 @@ export type AxiosErrorContext = { }; export type OptionalError = BaseError | null | undefined; + +export type ErrorContext = { + accountId?: number; +}; From b743f3842fd41770645e253e14ac14b0a4b5ce0d Mon Sep 17 00:00:00 2001 From: Camden Phalen Date: Thu, 1 Aug 2024 14:07:19 -0400 Subject: [PATCH 03/25] Export some more types --- lib/cms/functions.ts | 46 ++++++++++------------------------------ lib/cms/modules.ts | 18 +++++----------- lib/fileManager.ts | 4 +--- lib/fileMapper.ts | 15 +------------ lib/github.ts | 23 ++++++-------------- lib/gitignore.ts | 7 +----- lib/personalAccessKey.ts | 13 +----------- lib/portManager.ts | 6 +----- types/Accounts.ts | 11 ++++++++++ types/Config.ts | 6 ++++++ types/FileManager.ts | 2 ++ types/Files.ts | 14 ++++++++++++ types/Functions.ts | 30 ++++++++++++++++++++++++++ types/Github.ts | 13 ++++++++++++ types/Modules.ts | 12 +++++++++++ 15 files changed, 116 insertions(+), 104 deletions(-) diff --git a/lib/cms/functions.ts b/lib/cms/functions.ts index ea39800d..88564654 100644 --- a/lib/cms/functions.ts +++ b/lib/cms/functions.ts @@ -8,21 +8,14 @@ import { throwErrorWithMessage } from '../../errors/standardErrors'; import { throwFileSystemError } from '../../errors/fileSystemErrors'; import { i18n } from '../../utils/lang'; +import { + FunctionConfig, + FunctionConfigInfo, + FunctionInfo, + FunctionOptions, +} from '../../types/Functions'; const i18nKey = 'lib.cms.functions'; -type Config = { - runtime: string; - version: string; - environment: object; - secrets: Array; - endpoints: { - [key: string]: { - method: string; - file: string; - }; - }; -}; - function isObjectOrFunction(value: object): boolean { const type = typeof value; return value != null && (type === 'object' || type === 'function'); @@ -38,17 +31,11 @@ function createEndpoint( }; } -type ConfigInfo = { - endpointPath: string; - endpointMethod: string; - functionFile: string; -}; - function createConfig({ endpointPath, endpointMethod, functionFile, -}: ConfigInfo): Config { +}: FunctionConfigInfo): FunctionConfig { return { runtime: 'nodejs18.x', version: '1.0', @@ -60,14 +47,14 @@ function createConfig({ }; } -function writeConfig(configFilePath: string, config: Config): void { +function writeConfig(configFilePath: string, config: FunctionConfig): void { const configJson = JSON.stringify(config, null, ' '); fs.writeFileSync(configFilePath, configJson); } function updateExistingConfig( configFilePath: string, - { endpointPath, endpointMethod, functionFile }: ConfigInfo + { endpointPath, endpointMethod, functionFile }: FunctionConfigInfo ): void { let configString!: string; try { @@ -84,9 +71,9 @@ function updateExistingConfig( }); } - let config!: Config; + let config!: FunctionConfig; try { - config = JSON.parse(configString) as Config; + config = JSON.parse(configString) as FunctionConfig; } catch (err) { logger.debug( i18n(`${i18nKey}.updateExistingConfig.invalidJSON`, { @@ -140,17 +127,6 @@ function updateExistingConfig( } } -type FunctionInfo = { - functionsFolder: string; - filename: string; - endpointPath: string; - endpointMethod: string; -}; - -type FunctionOptions = { - allowExistingFile?: boolean; -}; - export async function createFunction( functionInfo: FunctionInfo, dest: string, diff --git a/lib/cms/modules.ts b/lib/cms/modules.ts index ffb9b98c..bdfb7a37 100644 --- a/lib/cms/modules.ts +++ b/lib/cms/modules.ts @@ -10,7 +10,11 @@ import { isModuleFolder, isModuleFolderChild, } from '../../utils/cms/modules'; -import { PathInput } from '../../types/Modules'; +import { + PathInput, + ValidationResult, + ModuleDefinition, +} from '../../types/Modules'; import { i18n } from '../../utils/lang'; const i18nKey = 'lib.cms.modules'; @@ -24,11 +28,6 @@ export const ValidationIds = { MODULE_NESTING: 'MODULE_NESTING', }; -type ValidationResult = { - id: string; - message: string; -}; - const getValidationResult = ( id: string, message: string @@ -152,13 +151,6 @@ const updateFileContents = async ( } }; -type ModuleDefinition = { - contentTypes: Array; - moduleLabel: string; - reactType: boolean; - global: boolean; -}; - export async function createModule( moduleDefinition: ModuleDefinition, name: string, diff --git a/lib/fileManager.ts b/lib/fileManager.ts index d0df2cc2..090fe59a 100644 --- a/lib/fileManager.ts +++ b/lib/fileManager.ts @@ -28,11 +28,9 @@ import { } from '../errors/standardErrors'; import { throwFileSystemError } from '../errors/fileSystemErrors'; import { GenericError } from '../types/Error'; -import { File, Folder } from '../types/FileManager'; +import { File, SimplifiedFolder } from '../types/FileManager'; import { i18n } from '../utils/lang'; -type SimplifiedFolder = Partial & Pick; - const i18nKey = 'lib.fileManager'; export async function uploadFolder( diff --git a/lib/fileMapper.ts b/lib/fileMapper.ts index aec3b40c..5c45eea8 100644 --- a/lib/fileMapper.ts +++ b/lib/fileMapper.ts @@ -20,6 +20,7 @@ import { Mode, FileMapperOptions, FileMapperInputOptions, + PathTypeData, } from '../types/Files'; import { throwFileSystemError } from '../errors/fileSystemErrors'; import { isTimeoutError } from '../errors/apiErrors'; @@ -98,14 +99,6 @@ function validateFileMapperNode(node: FileMapperNode): void { }); } -type PathTypeData = { - isModule: boolean; - isHubspot: boolean; - isFile: boolean; - isRoot: boolean; - isFolder: boolean; -}; - export function getTypeDataFromPath(src: string): PathTypeData { const isModule = isPathToModule(src); const isHubspot = isPathToHubspot(src); @@ -121,12 +114,6 @@ export function getTypeDataFromPath(src: string): PathTypeData { }; } -type RecursiveFileMapperCallback = ( - node: FileMapperNode, - filepath?: string, - depth?: number -) => boolean; - export function recurseFolder( node: FileMapperNode, callback: RecursiveFileMapperCallback, diff --git a/lib/github.ts b/lib/github.ts index 9e594163..12678d46 100644 --- a/lib/github.ts +++ b/lib/github.ts @@ -5,7 +5,13 @@ import { throwError, throwErrorWithMessage } from '../errors/standardErrors'; import { extractZipArchive } from './archive'; import { logger } from './logger'; import { GenericError, BaseError } from '../types/Error'; -import { GithubReleaseData, GithubRepoFile } from '../types/Github'; +import { + GithubReleaseData, + GithubRepoFile, + RepoPath, + DownloadGithubRepoZipOptions, + CloneGithubRepoOptions, +} from '../types/Github'; import { fetchRepoFile, fetchRepoFileByDownloadUrl, @@ -17,8 +23,6 @@ import { i18n } from '../utils/lang'; const i18nKey = 'lib.github'; -type RepoPath = `${string}/${string}`; - export async function fetchFileFromRepository( repoPath: RepoPath, filePath: string, @@ -66,11 +70,6 @@ export async function fetchReleaseData( } } -type DownloadGithubRepoZipOptions = { - branch?: string; - tag?: string; -}; - async function downloadGithubRepoZip( repoPath: RepoPath, isRelease = false, @@ -110,14 +109,6 @@ async function downloadGithubRepoZip( } } -type CloneGithubRepoOptions = { - isRelease?: boolean; // Download a repo release? (Default is to download the repo contents) - type?: string; // The type of asset being downloaded. Used for logging - branch?: string; // Repo branch - tag?: string; // Repo tag - sourceDir?: string; // The directory within the downloaded repo to write after extraction -}; - export async function cloneGithubRepo( repoPath: RepoPath, dest: string, diff --git a/lib/gitignore.ts b/lib/gitignore.ts index e3aa1c82..64fd77b6 100644 --- a/lib/gitignore.ts +++ b/lib/gitignore.ts @@ -8,6 +8,7 @@ import { } from '../utils/git'; import { DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME } from '../constants/config'; import { throwErrorWithMessage } from '../errors/standardErrors'; +import { GitInclusionResult } from '../types/Config'; const i18nKey = 'lib.gitignore'; @@ -34,12 +35,6 @@ export function checkAndAddConfigToGitignore(configPath: string): void { } } -type GitInclusionResult = { - inGit: boolean; - configIgnored: boolean; - gitignoreFiles: Array; -}; - export function checkGitInclusion(configPath: string): GitInclusionResult { const result: GitInclusionResult = { inGit: false, diff --git a/lib/personalAccessKey.ts b/lib/personalAccessKey.ts index a8fb0711..1cf1bab2 100644 --- a/lib/personalAccessKey.ts +++ b/lib/personalAccessKey.ts @@ -22,7 +22,7 @@ import { HUBSPOT_ACCOUNT_TYPES } from '../constants/config'; import { fetchDeveloperTestAccountData } from '../api/developerTestAccounts'; import { logger } from './logger'; import { getAxiosErrorWithContext } from '../errors/apiErrors'; -import { ValueOf } from '../types/Utils'; +import { AccessToken } from '../types/Accounts'; const i18nKey = 'lib.personalAccessKey'; @@ -32,17 +32,6 @@ function getRefreshKey(personalAccessKey: string, expiration?: string): string { return `${personalAccessKey}-${expiration || 'fresh'}`; } -type AccessToken = { - portalId: number; - accessToken: string; - expiresAt: string; - scopeGroups: Array; - enabledFeatures?: { [key: string]: number }; - encodedOAuthRefreshToken: string; - hubName: string; - accountType: ValueOf; -}; - export async function getAccessToken( personalAccessKey: string, env: Environment = ENVIRONMENTS.PROD, diff --git a/lib/portManager.ts b/lib/portManager.ts index 8c281dd3..8e272979 100644 --- a/lib/portManager.ts +++ b/lib/portManager.ts @@ -3,6 +3,7 @@ import axios from 'axios'; import PortManagerServer from '../utils/PortManagerServer'; import { detectPort } from '../utils/detectPort'; import { PORT_MANAGER_SERVER_PORT } from '../constants/ports'; +import { RequestPortsData } from '../types/PortManager'; export const BASE_URL = `http://localhost:${PORT_MANAGER_SERVER_PORT}`; @@ -27,11 +28,6 @@ export async function stopPortManagerServer(): Promise { } } -type RequestPortsData = { - instanceId: string; - port?: number; -}; - export async function requestPorts( portData: Array ): Promise<{ [instanceId: string]: number }> { diff --git a/types/Accounts.ts b/types/Accounts.ts index 201ff8f2..5c2acedd 100644 --- a/types/Accounts.ts +++ b/types/Accounts.ts @@ -160,3 +160,14 @@ export type APIKeyOptions = { apiKey: string; env: Environment; }; + +export type AccessToken = { + portalId: number; + accessToken: string; + expiresAt: string; + scopeGroups: Array; + enabledFeatures?: { [key: string]: number }; + encodedOAuthRefreshToken: string; + hubName: string; + accountType: ValueOf; +}; diff --git a/types/Config.ts b/types/Config.ts index 0809ca58..b54b60c7 100644 --- a/types/Config.ts +++ b/types/Config.ts @@ -35,3 +35,9 @@ export type EnvironmentConfigVariables = { refreshToken?: string; env?: Environment; }; + +export type GitInclusionResult = { + inGit: boolean; + configIgnored: boolean; + gitignoreFiles: Array; +}; diff --git a/types/FileManager.ts b/types/FileManager.ts index 41705377..4f36e908 100644 --- a/types/FileManager.ts +++ b/types/FileManager.ts @@ -73,3 +73,5 @@ export type FetchFolderResponse = { objects: Array; total_count: number; }; + +export type SimplifiedFolder = Partial & Pick; diff --git a/types/Files.ts b/types/Files.ts index 0794d034..36402ad7 100644 --- a/types/Files.ts +++ b/types/Files.ts @@ -50,3 +50,17 @@ export type FileTree = { path: string; children: Array; }; + +export type PathTypeData = { + isModule: boolean; + isHubspot: boolean; + isFile: boolean; + isRoot: boolean; + isFolder: boolean; +}; + +export type RecursiveFileMapperCallback = ( + node: FileMapperNode, + filepath?: string, + depth?: number +) => boolean; diff --git a/types/Functions.ts b/types/Functions.ts index 39ee5267..d7738251 100644 --- a/types/Functions.ts +++ b/types/Functions.ts @@ -39,3 +39,33 @@ export type GetBuildStatusResponse = { userId: number; deployId: number; }; + +export type FunctionConfig = { + runtime: string; + version: string; + environment: object; + secrets: Array; + endpoints: { + [key: string]: { + method: string; + file: string; + }; + }; +}; + +export type FunctionConfigInfo = { + endpointPath: string; + endpointMethod: string; + functionFile: string; +}; + +export type FunctionInfo = { + functionsFolder: string; + filename: string; + endpointPath: string; + endpointMethod: string; +}; + +export type FunctionOptions = { + allowExistingFile?: boolean; +}; diff --git a/types/Github.ts b/types/Github.ts index ff3b2ddc..6ae73cb4 100644 --- a/types/Github.ts +++ b/types/Github.ts @@ -66,3 +66,16 @@ export interface GithubSourceData { } export type RepoPath = `${string}/${string}`; + +export type DownloadGithubRepoZipOptions = { + branch?: string; + tag?: string; +}; + +export type CloneGithubRepoOptions = { + isRelease?: boolean; // Download a repo release? (Default is to download the repo contents) + type?: string; // The type of asset being downloaded. Used for logging + branch?: string; // Repo branch + tag?: string; // Repo tag + sourceDir?: string; // The directory within the downloaded repo to write after extraction +}; diff --git a/types/Modules.ts b/types/Modules.ts index 03b1ec16..a0b4a2b7 100644 --- a/types/Modules.ts +++ b/types/Modules.ts @@ -3,3 +3,15 @@ export type PathInput = { isHubSpot?: boolean; path: string; }; + +export type ValidationResult = { + id: string; + message: string; +}; + +export type ModuleDefinition = { + contentTypes: Array; + moduleLabel: string; + reactType: boolean; + global: boolean; +}; From 713b8ea04c8505add3d12d44270f3a6ff748cae5 Mon Sep 17 00:00:00 2001 From: Camden Phalen Date: Thu, 1 Aug 2024 14:25:28 -0400 Subject: [PATCH 04/25] Export all types --- config/config_DEPRECATED.ts | 1 + lib/cms/uploadFolder.ts | 31 +++++------------------- lib/cms/watch.ts | 34 +++++++------------------- models/OAuth2Manager.ts | 36 ++++++---------------------- types/Accounts.ts | 32 +++++++++++++++++++++++++ types/FieldsJS.ts | 1 + types/Files.ts | 45 +++++++++++++++++++++++++++++++++++ types/PortManager.ts | 10 ++++++++ utils/PortManagerServer.ts | 6 +---- utils/cms/fieldsJS.ts | 2 +- utils/detectPort.ts | 7 +----- utils/getAccountIdentifier.ts | 9 ++----- 12 files changed, 116 insertions(+), 98 deletions(-) create mode 100644 types/FieldsJS.ts diff --git a/config/config_DEPRECATED.ts b/config/config_DEPRECATED.ts index 1e146c9a..bafbd5ce 100644 --- a/config/config_DEPRECATED.ts +++ b/config/config_DEPRECATED.ts @@ -29,6 +29,7 @@ import { CLIAccount_DEPRECATED, FlatAccountFields_DEPRECATED, OAuthAccount_DEPRECATED, + UpdateAccountConfigOptions, } from '../types/Accounts'; import { BaseError } from '../types/Error'; import { Mode } from '../types/Files'; diff --git a/lib/cms/uploadFolder.ts b/lib/cms/uploadFolder.ts index a0c100c5..76173237 100644 --- a/lib/cms/uploadFolder.ts +++ b/lib/cms/uploadFolder.ts @@ -18,7 +18,12 @@ import { throwApiUploadError } from '../../errors/apiErrors'; import { FileMapperInputOptions } from '../../types/Files'; import { logger } from '../logger'; import { FILE_TYPES, FILE_UPLOAD_RESULT_TYPES } from '../../constants/files'; -import { FileType, UploadFolderResults } from '../../types/Files'; +import { + FileType, + UploadFolderResults, + CommandOptions, + FilePathsByType, +} from '../../types/Files'; import { Mode } from '../../types/Files'; import { i18n } from '../../utils/lang'; @@ -28,30 +33,6 @@ const queue = new PQueue({ concurrency: 10, }); -type CommandOptions = { - convertFields?: boolean; - fieldOptions?: string; - saveOutput?: boolean; - onAttemptCallback?: (file: string | undefined, destPath: string) => void; - onSuccessCallback?: (file: string | undefined, destPath: string) => void; - onFirstErrorCallback?: ( - file: string, - destPath: string, - error: AxiosError - ) => void; - onRetryCallback?: (file: string, destPath: string) => void; - onFinalErrorCallback?: ( - accountId: number, - file: string, - destPath: string, - error: AxiosError - ) => void; -}; - -type FilePathsByType = { - [key: string]: Array; -}; - function getFileType(filePath: string): FileType { const extension = getExt(filePath); const moduleFolder = isModuleFolderChild({ path: filePath, isLocal: true }); diff --git a/lib/cms/watch.ts b/lib/cms/watch.ts index 24857e4e..8be3b22f 100644 --- a/lib/cms/watch.ts +++ b/lib/cms/watch.ts @@ -15,7 +15,12 @@ import { convertToUnixPath, isAllowedExtension, getCwd } from '../path'; import { triggerNotify } from '../notify'; import { getThemePreviewUrl, getThemeJSONPath } from './themes'; import { logger } from '../logger'; -import { FileMapperInputOptions, Mode } from '../../types/Files'; +import { + UploadFileOptions, + Mode, + WatchOptions, + WatchErrorHandler, +} from '../../types/Files'; import { UploadFolderResults } from '../../types/Files'; import { i18n } from '../../utils/lang'; @@ -39,14 +44,6 @@ function _notifyOfThemePreview(filePath: string, accountId: number): void { const notifyOfThemePreview = debounce(_notifyOfThemePreview, 1000); -type UploadFileOptions = FileMapperInputOptions & { - src: string; - commandOptions: { - convertFields?: boolean; - }; - fieldOptions?: string; -}; - const defaultOnUploadFileError = (file: string, dest: string, accountId: number) => (error: AxiosError) => { logger.debug( @@ -160,19 +157,6 @@ async function deleteRemoteFile( }); } -type WatchOptions = { - mode?: Mode; - remove?: boolean; - disableInitial?: boolean; - notify?: string; - commandOptions: { - convertFields?: boolean; - }; - filePaths?: Array; -}; - -type ErrorHandler = (error: AxiosError) => void; - export function watch( accountId: number, src: string, @@ -188,13 +172,13 @@ export function watch( postInitialUploadCallback: | ((result: Array) => void) | null = null, - onUploadFolderError?: ErrorHandler, - onQueueAddError?: ErrorHandler, + onUploadFolderError?: WatchErrorHandler, + onQueueAddError?: WatchErrorHandler, onUploadFileError?: ( file: string, dest: string, accountId: number - ) => ErrorHandler + ) => WatchErrorHandler ) { const regex = new RegExp(`^${escapeRegExp(src)}`); if (notify) { diff --git a/models/OAuth2Manager.ts b/models/OAuth2Manager.ts index 52911d3a..7ae55f85 100644 --- a/models/OAuth2Manager.ts +++ b/models/OAuth2Manager.ts @@ -3,7 +3,13 @@ import moment from 'moment'; import { getHubSpotApiOrigin } from '../lib/urls'; import { getValidEnv } from '../lib/environment'; -import { FlatAccountFields, TokenInfo } from '../types/Accounts'; +import { + FlatAccountFields, + OAuth2ManagerAccountConfig, + WriteTokenInfoFunction, + RefreshTokenResponse, + ExchangeProof, +} from '../types/Accounts'; import { logger } from '../lib/logger'; import { getAccountIdentifier } from '../utils/getAccountIdentifier'; import { AUTH_METHODS } from '../constants/auth'; @@ -12,36 +18,8 @@ import { throwErrorWithMessage, throwAuthErrorWithMessage, } from '../errors/standardErrors'; -import { Environment } from '../types/Config'; import { i18n } from '../utils/lang'; -type OAuth2ManagerAccountConfig = { - name?: string; - accountId?: number; - clientId?: string; - clientSecret?: string; - scopes?: Array; - env?: Environment; - environment?: Environment; - tokenInfo?: TokenInfo; - authType?: 'oauth2'; -}; - -type WriteTokenInfoFunction = (tokenInfo: TokenInfo) => void; - -type RefreshTokenResponse = { - refresh_token: string; - access_token: string; - expires_in: string; -}; - -type ExchangeProof = { - grant_type: string; - client_id?: string; - client_secret?: string; - refresh_token?: string; -}; - const i18nKey = 'models.OAuth2Manager'; class OAuth2Manager { diff --git a/types/Accounts.ts b/types/Accounts.ts index 5c2acedd..919fe260 100644 --- a/types/Accounts.ts +++ b/types/Accounts.ts @@ -39,6 +39,11 @@ export interface CLIAccount_DEPRECATED { export type CLIAccount = CLIAccount_NEW | CLIAccount_DEPRECATED; +export type GenericAccount = { + portalId?: number; + accountId?: number; +}; + export type AccountType = ValueOf; export type TokenInfo = { @@ -171,3 +176,30 @@ export type AccessToken = { hubName: string; accountType: ValueOf; }; + +export type OAuth2ManagerAccountConfig = { + name?: string; + accountId?: number; + clientId?: string; + clientSecret?: string; + scopes?: Array; + env?: Environment; + environment?: Environment; + tokenInfo?: TokenInfo; + authType?: 'oauth2'; +}; + +export type WriteTokenInfoFunction = (tokenInfo: TokenInfo) => void; + +export type RefreshTokenResponse = { + refresh_token: string; + access_token: string; + expires_in: string; +}; + +export type ExchangeProof = { + grant_type: string; + client_id?: string; + client_secret?: string; + refresh_token?: string; +}; diff --git a/types/FieldsJS.ts b/types/FieldsJS.ts new file mode 100644 index 00000000..13430506 --- /dev/null +++ b/types/FieldsJS.ts @@ -0,0 +1 @@ +export type FieldsArray = Array>; diff --git a/types/Files.ts b/types/Files.ts index 36402ad7..d65c2b54 100644 --- a/types/Files.ts +++ b/types/Files.ts @@ -64,3 +64,48 @@ export type RecursiveFileMapperCallback = ( filepath?: string, depth?: number ) => boolean; + +export type CommandOptions = { + convertFields?: boolean; + fieldOptions?: string; + saveOutput?: boolean; + onAttemptCallback?: (file: string | undefined, destPath: string) => void; + onSuccessCallback?: (file: string | undefined, destPath: string) => void; + onFirstErrorCallback?: ( + file: string, + destPath: string, + error: AxiosError + ) => void; + onRetryCallback?: (file: string, destPath: string) => void; + onFinalErrorCallback?: ( + accountId: number, + file: string, + destPath: string, + error: AxiosError + ) => void; +}; + +export type FilePathsByType = { + [key: string]: Array; +}; + +export type UploadFileOptions = FileMapperInputOptions & { + src: string; + commandOptions: { + convertFields?: boolean; + }; + fieldOptions?: string; +}; + +export type WatchOptions = { + mode?: Mode; + remove?: boolean; + disableInitial?: boolean; + notify?: string; + commandOptions: { + convertFields?: boolean; + }; + filePaths?: Array; +}; + +export type WatchErrorHandler = (error: AxiosError) => void; diff --git a/types/PortManager.ts b/types/PortManager.ts index a319a031..920f2499 100644 --- a/types/PortManager.ts +++ b/types/PortManager.ts @@ -2,3 +2,13 @@ export type RequestPortsData = { instanceId: string; port?: number; }; + +export type NetError = Error & { + code: string; +}; + +export type ListenCallback = (error: NetError | null, port: number) => void; + +export type ServerPortMap = { + [instanceId: string]: number; +}; diff --git a/utils/PortManagerServer.ts b/utils/PortManagerServer.ts index 64ef978e..e079a47d 100644 --- a/utils/PortManagerServer.ts +++ b/utils/PortManagerServer.ts @@ -12,11 +12,7 @@ import { throwErrorWithMessage } from '../errors/standardErrors'; import { logger } from '../lib/logger'; import { i18n } from './lang'; import { BaseError } from '../types/Error'; -import { RequestPortsData } from '../types/PortManager'; - -type ServerPortMap = { - [instanceId: string]: number; -}; +import { RequestPortsData, ServerPortMap } from '../types/PortManager'; const i18nKey = 'utils.PortManagerServer'; diff --git a/utils/cms/fieldsJS.ts b/utils/cms/fieldsJS.ts index 990bc8a9..4e5fc8b0 100644 --- a/utils/cms/fieldsJS.ts +++ b/utils/cms/fieldsJS.ts @@ -1,4 +1,4 @@ -type FieldsArray = Array>; +import { FieldsArray } from '../../types/FieldsJS'; /* * Polyfill for `Array.flat(Infinity)` since the `flat` is only available for Node v11+ diff --git a/utils/detectPort.ts b/utils/detectPort.ts index bb4b9273..c6816b80 100644 --- a/utils/detectPort.ts +++ b/utils/detectPort.ts @@ -26,15 +26,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import net, { AddressInfo } from 'net'; import { ip } from 'address'; import { throwErrorWithMessage } from '../errors/standardErrors'; +import { NetError, ListenCallback } from '../types/PortManager'; import { MIN_PORT_NUMBER, MAX_PORT_NUMBER } from '../constants/ports'; -type NetError = Error & { - code: string; -}; - -type ListenCallback = (error: NetError | null, port: number) => void; - const i18nKey = 'utils.detectPort'; export function detectPort( diff --git a/utils/getAccountIdentifier.ts b/utils/getAccountIdentifier.ts index d652295f..cf73746e 100644 --- a/utils/getAccountIdentifier.ts +++ b/utils/getAccountIdentifier.ts @@ -1,17 +1,12 @@ -import { CLIAccount } from '../types/Accounts'; +import { CLIAccount, GenericAccount } from '../types/Accounts'; import { CLIConfig, CLIConfig_DEPRECATED, CLIConfig_NEW, } from '../types/Config'; -type Account = { - portalId?: number; - accountId?: number; -}; - export function getAccountIdentifier( - account?: Account | null + account?: GenericAccount | null ): number | undefined { if (!account) { return undefined; From e5c18e347ea112390ac8fc2485fdb36d306379b9 Mon Sep 17 00:00:00 2001 From: Camden Phalen Date: Thu, 1 Aug 2024 16:10:49 -0400 Subject: [PATCH 05/25] export declaration files from repo --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a6676fe0..f7399fb0 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,8 @@ "./http/*": "./http/*.js", "./config": "./config/index.js", "./constants/*": "./constants/*.js", - "./models/*": "./models/*.js" + "./models/*": "./models/*.js", + "./types/*": "./types/*.d.ts" }, "dependencies": { "address": "^2.0.1", From d4f9d41e41d7401e34c3cd247ad0eb2bad63d719 Mon Sep 17 00:00:00 2001 From: Camden Phalen Date: Thu, 1 Aug 2024 16:17:50 -0400 Subject: [PATCH 06/25] fix type errors --- lib/cms/watch.ts | 2 +- lib/fileMapper.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/cms/watch.ts b/lib/cms/watch.ts index 8be3b22f..94c85345 100644 --- a/lib/cms/watch.ts +++ b/lib/cms/watch.ts @@ -68,7 +68,7 @@ async function uploadFile( file: string, dest: string, accountId: number - ) => ErrorHandler = defaultOnUploadFileError + ) => WatchErrorHandler = defaultOnUploadFileError ): Promise { const src = options.src; diff --git a/lib/fileMapper.ts b/lib/fileMapper.ts index 5c45eea8..c1cf73db 100644 --- a/lib/fileMapper.ts +++ b/lib/fileMapper.ts @@ -21,6 +21,7 @@ import { FileMapperOptions, FileMapperInputOptions, PathTypeData, + RecursiveFileMapperCallback, } from '../types/Files'; import { throwFileSystemError } from '../errors/fileSystemErrors'; import { isTimeoutError } from '../errors/apiErrors'; From 59086d4b8754ba3b9a8f4ea3cfc4084355280397 Mon Sep 17 00:00:00 2001 From: Mark Tripoli Date: Mon, 5 Aug 2024 13:47:29 -0400 Subject: [PATCH 07/25] Update some endpoints to point towards new project's service routes --- api/projects.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/projects.ts b/api/projects.ts index 801a0a42..6382ef53 100644 --- a/api/projects.ts +++ b/api/projects.ts @@ -30,7 +30,7 @@ export async function fetchProjects( accountId: number ): Promise { return http.get(accountId, { - url: PROJECTS_API_PATH, + url: DEVELOPER_PROJECTS_API_PATH, }); } @@ -39,7 +39,7 @@ export async function createProject( name: string ): Promise { return http.post(accountId, { - url: PROJECTS_API_PATH, + url: DEVELOPER_PROJECTS_API_PATH, data: { name, }, @@ -74,7 +74,7 @@ export async function fetchProject( projectName: string ): Promise { return http.get(accountId, { - url: `${PROJECTS_API_PATH}/${encodeURIComponent(projectName)}`, + url: `${DEVELOPER_PROJECTS_API_PATH}/${encodeURIComponent(projectName)}`, }); } From 2aa9bbefe19449b1593608ec6fca56bc7dcd4ea7 Mon Sep 17 00:00:00 2001 From: Camden Phalen Date: Tue, 6 Aug 2024 13:57:35 -0400 Subject: [PATCH 08/25] fix PublicAppInstallCounts typo --- api/appsDev.ts | 6 +++--- types/Apps.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/appsDev.ts b/api/appsDev.ts index 346bf364..5630c685 100644 --- a/api/appsDev.ts +++ b/api/appsDev.ts @@ -1,7 +1,7 @@ import http from '../http'; import { PublicApp, - PublicApInstallCounts, + PublicAppInstallCounts, PublicAppDeveloperTestAccountInstallData, FetchPublicAppsForPortalResponse, } from '../types/Apps'; @@ -30,8 +30,8 @@ export function fetchPublicAppDeveloperTestAccountInstallData( export function fetchPublicAppProductionInstallCounts( appId: number, accountId: number -): Promise { - return http.get(accountId, { +): Promise { + return http.get(accountId, { url: `${APPS_DEV_API_PATH}/${appId}/install-counts-without-test-portals`, }); } diff --git a/types/Apps.ts b/types/Apps.ts index bb01f807..022ac6f2 100644 --- a/types/Apps.ts +++ b/types/Apps.ts @@ -15,7 +15,7 @@ export type PublicAppDeveloperTestAccountInstallData = { testPortalInstallCount: string; }; -export type PublicApInstallCounts = { +export type PublicAppInstallCounts = { uniquePortalInstallCount: number; uniqueUserInstallCount: number; uniqueBusinessUnitInstallCount: number; @@ -42,7 +42,7 @@ export type PublicApp = { supportPhone: string | null; extensionIconUrl: string | null; isAdvancedScopesSettingEnabled: boolean; - publicApplicationInstallCounts: PublicApInstallCounts; + publicApplicationInstallCounts: PublicAppInstallCounts; redirectUrls: Array; scopeGroupIds: Array; requiredScopeInfo?: Array<{ id: number; name: string }>; From 474f6072aac139dee03b7ec0d150aaa13a44ba2d Mon Sep 17 00:00:00 2001 From: Mark Tripoli Date: Wed, 7 Aug 2024 09:49:07 -0400 Subject: [PATCH 09/25] change fetchProjectSettings base --- api/projects.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/projects.ts b/api/projects.ts index 3c273f16..6c2949da 100644 --- a/api/projects.ts +++ b/api/projects.ts @@ -188,7 +188,7 @@ export async function fetchProjectSettings( projectName: string ): Promise { return http.get(accountId, { - url: `${PROJECTS_API_PATH}/${encodeURIComponent(projectName)}/settings`, + url: `${DEVELOPER_PROJECTS_API_PATH}/${encodeURIComponent(projectName)}/settings`, }); } From 9bb55506886e9581e6d0ceb9bf9e8d2ef7c59035 Mon Sep 17 00:00:00 2001 From: Mark Tripoli Date: Wed, 7 Aug 2024 13:43:45 -0400 Subject: [PATCH 10/25] Revert "Update some endpoints to point towards new project's service routes" --- api/projects.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/projects.ts b/api/projects.ts index 6c2949da..b0699f90 100644 --- a/api/projects.ts +++ b/api/projects.ts @@ -31,7 +31,7 @@ export async function fetchProjects( accountId: number ): Promise { return http.get(accountId, { - url: DEVELOPER_PROJECTS_API_PATH, + url: PROJECTS_API_PATH, }); } @@ -40,7 +40,7 @@ export async function createProject( name: string ): Promise { return http.post(accountId, { - url: DEVELOPER_PROJECTS_API_PATH, + url: PROJECTS_API_PATH, data: { name, }, @@ -75,7 +75,7 @@ export async function fetchProject( projectName: string ): Promise { return http.get(accountId, { - url: `${DEVELOPER_PROJECTS_API_PATH}/${encodeURIComponent(projectName)}`, + url: `${PROJECTS_API_PATH}/${encodeURIComponent(projectName)}`, }); } @@ -188,7 +188,7 @@ export async function fetchProjectSettings( projectName: string ): Promise { return http.get(accountId, { - url: `${DEVELOPER_PROJECTS_API_PATH}/${encodeURIComponent(projectName)}/settings`, + url: `${PROJECTS_API_PATH}/${encodeURIComponent(projectName)}/settings`, }); } From cfd8e72f06180b7a29b36419c674ac4efe9beb03 Mon Sep 17 00:00:00 2001 From: Mark Tripoli Date: Wed, 7 Aug 2024 13:52:48 -0400 Subject: [PATCH 11/25] add new paths --- api/projects.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api/projects.ts b/api/projects.ts index b0699f90..27487183 100644 --- a/api/projects.ts +++ b/api/projects.ts @@ -31,7 +31,7 @@ export async function fetchProjects( accountId: number ): Promise { return http.get(accountId, { - url: PROJECTS_API_PATH, + url: DEVELOPER_PROJECTS_API_PATH, }); } @@ -40,7 +40,7 @@ export async function createProject( name: string ): Promise { return http.post(accountId, { - url: PROJECTS_API_PATH, + url: DEVELOPER_PROJECTS_API_PATH, data: { name, }, @@ -75,7 +75,7 @@ export async function fetchProject( projectName: string ): Promise { return http.get(accountId, { - url: `${PROJECTS_API_PATH}/${encodeURIComponent(projectName)}`, + url: `${DEVELOPER_PROJECTS_API_PATH}/by-name/${encodeURIComponent(projectName)}`, }); } @@ -98,7 +98,7 @@ export async function deleteProject( projectName: string ): Promise { return http.delete(accountId, { - url: `${PROJECTS_API_PATH}/${encodeURIComponent(projectName)}`, + url: `${DEVELOPER_PROJECTS_API_PATH}/${encodeURIComponent(projectName)}`, }); } @@ -188,7 +188,7 @@ export async function fetchProjectSettings( projectName: string ): Promise { return http.get(accountId, { - url: `${PROJECTS_API_PATH}/${encodeURIComponent(projectName)}/settings`, + url: `${DEVELOPER_PROJECTS_API_PATH}/${encodeURIComponent(projectName)}/settings`, }); } From f79ba8bd7f7ade598c13066b49eb31fdc27f576d Mon Sep 17 00:00:00 2001 From: Joe Yeager Date: Wed, 14 Aug 2024 11:40:40 -0700 Subject: [PATCH 12/25] chore: Add .test extension prefix (#179) --- api/__tests__/{fileMapper.ts => fileMapper.test.ts} | 0 .../__tests__/{CLIConfiguration.ts => CLIConfiguration.test.ts} | 0 config/__tests__/{config.ts => config.test.ts} | 0 config/__tests__/{configFile.ts => configFile.test.ts} | 0 config/__tests__/{configUtils.ts => configUtils.test.ts} | 0 config/__tests__/{environment.ts => environment.test.ts} | 0 errors/__tests__/{apiErrors.ts => apiErrors.test.ts} | 0 .../__tests__/{fileSystemErrors.ts => fileSystemErrors.test.ts} | 0 errors/__tests__/{standardErrors.ts => standardErrors.test.ts} | 0 http/__tests__/{getAxiosConfig.ts => getAxiosConfig.test.ts} | 0 http/__tests__/{index.ts => index.test.ts} | 0 lib/__tests__/{archive.ts => archive.test.ts} | 0 lib/__tests__/{customObjects.ts => customObjects.test.ts} | 0 lib/__tests__/{environment.ts => environment.test.ts} | 0 lib/__tests__/{fileManager.ts => fileManager.test.ts} | 0 lib/__tests__/{fileMapper.ts => fileMapper.test.ts} | 0 lib/__tests__/{fs.ts => fs.test.ts} | 0 lib/__tests__/{github.ts => github.test.ts} | 0 lib/__tests__/{gitignore.ts => gitignore.test.ts} | 0 lib/__tests__/{handleFieldsJS.ts => handleFieldsJS.test.ts} | 0 lib/__tests__/{hubdb.ts => hubdb.test.ts} | 0 lib/__tests__/{ignoreRules.ts => ignoreRules.test.ts} | 0 lib/__tests__/{logger.ts => logger.test.ts} | 0 lib/__tests__/{modules.ts => modules.test.ts} | 0 lib/__tests__/{notify.ts => notify.test.ts} | 0 lib/__tests__/{oauth.ts => oauth.test.ts} | 0 lib/__tests__/{path.ts => path.test.ts} | 0 lib/__tests__/{personalAccessKey.ts => personalAccessKey.test.ts} | 0 lib/__tests__/{portManager.ts => portManager.test.ts} | 0 lib/__tests__/{sandboxes.ts => sandboxes.test.ts} | 0 lib/__tests__/{templates.ts => templates.test.ts} | 0 lib/__tests__/{text.ts => text.test.ts} | 0 lib/__tests__/{trackUsage.ts => trackUsage.test.ts} | 0 lib/__tests__/{uploadFolder.ts => uploadFolder.test.ts} | 0 lib/__tests__/{urls.ts => urls.test.ts} | 0 models/__tests__/{OAuth2Manager.ts => OAuth2Manager.test.ts} | 0 utils/__tests__/{fieldsJS.ts => fieldsJS.test.ts} | 0 utils/__tests__/{git.ts => git.test.ts} | 0 utils/__tests__/{modules.ts => modules.test.ts} | 0 39 files changed, 0 insertions(+), 0 deletions(-) rename api/__tests__/{fileMapper.ts => fileMapper.test.ts} (100%) rename config/__tests__/{CLIConfiguration.ts => CLIConfiguration.test.ts} (100%) rename config/__tests__/{config.ts => config.test.ts} (100%) rename config/__tests__/{configFile.ts => configFile.test.ts} (100%) rename config/__tests__/{configUtils.ts => configUtils.test.ts} (100%) rename config/__tests__/{environment.ts => environment.test.ts} (100%) rename errors/__tests__/{apiErrors.ts => apiErrors.test.ts} (100%) rename errors/__tests__/{fileSystemErrors.ts => fileSystemErrors.test.ts} (100%) rename errors/__tests__/{standardErrors.ts => standardErrors.test.ts} (100%) rename http/__tests__/{getAxiosConfig.ts => getAxiosConfig.test.ts} (100%) rename http/__tests__/{index.ts => index.test.ts} (100%) rename lib/__tests__/{archive.ts => archive.test.ts} (100%) rename lib/__tests__/{customObjects.ts => customObjects.test.ts} (100%) rename lib/__tests__/{environment.ts => environment.test.ts} (100%) rename lib/__tests__/{fileManager.ts => fileManager.test.ts} (100%) rename lib/__tests__/{fileMapper.ts => fileMapper.test.ts} (100%) rename lib/__tests__/{fs.ts => fs.test.ts} (100%) rename lib/__tests__/{github.ts => github.test.ts} (100%) rename lib/__tests__/{gitignore.ts => gitignore.test.ts} (100%) rename lib/__tests__/{handleFieldsJS.ts => handleFieldsJS.test.ts} (100%) rename lib/__tests__/{hubdb.ts => hubdb.test.ts} (100%) rename lib/__tests__/{ignoreRules.ts => ignoreRules.test.ts} (100%) rename lib/__tests__/{logger.ts => logger.test.ts} (100%) rename lib/__tests__/{modules.ts => modules.test.ts} (100%) rename lib/__tests__/{notify.ts => notify.test.ts} (100%) rename lib/__tests__/{oauth.ts => oauth.test.ts} (100%) rename lib/__tests__/{path.ts => path.test.ts} (100%) rename lib/__tests__/{personalAccessKey.ts => personalAccessKey.test.ts} (100%) rename lib/__tests__/{portManager.ts => portManager.test.ts} (100%) rename lib/__tests__/{sandboxes.ts => sandboxes.test.ts} (100%) rename lib/__tests__/{templates.ts => templates.test.ts} (100%) rename lib/__tests__/{text.ts => text.test.ts} (100%) rename lib/__tests__/{trackUsage.ts => trackUsage.test.ts} (100%) rename lib/__tests__/{uploadFolder.ts => uploadFolder.test.ts} (100%) rename lib/__tests__/{urls.ts => urls.test.ts} (100%) rename models/__tests__/{OAuth2Manager.ts => OAuth2Manager.test.ts} (100%) rename utils/__tests__/{fieldsJS.ts => fieldsJS.test.ts} (100%) rename utils/__tests__/{git.ts => git.test.ts} (100%) rename utils/__tests__/{modules.ts => modules.test.ts} (100%) diff --git a/api/__tests__/fileMapper.ts b/api/__tests__/fileMapper.test.ts similarity index 100% rename from api/__tests__/fileMapper.ts rename to api/__tests__/fileMapper.test.ts diff --git a/config/__tests__/CLIConfiguration.ts b/config/__tests__/CLIConfiguration.test.ts similarity index 100% rename from config/__tests__/CLIConfiguration.ts rename to config/__tests__/CLIConfiguration.test.ts diff --git a/config/__tests__/config.ts b/config/__tests__/config.test.ts similarity index 100% rename from config/__tests__/config.ts rename to config/__tests__/config.test.ts diff --git a/config/__tests__/configFile.ts b/config/__tests__/configFile.test.ts similarity index 100% rename from config/__tests__/configFile.ts rename to config/__tests__/configFile.test.ts diff --git a/config/__tests__/configUtils.ts b/config/__tests__/configUtils.test.ts similarity index 100% rename from config/__tests__/configUtils.ts rename to config/__tests__/configUtils.test.ts diff --git a/config/__tests__/environment.ts b/config/__tests__/environment.test.ts similarity index 100% rename from config/__tests__/environment.ts rename to config/__tests__/environment.test.ts diff --git a/errors/__tests__/apiErrors.ts b/errors/__tests__/apiErrors.test.ts similarity index 100% rename from errors/__tests__/apiErrors.ts rename to errors/__tests__/apiErrors.test.ts diff --git a/errors/__tests__/fileSystemErrors.ts b/errors/__tests__/fileSystemErrors.test.ts similarity index 100% rename from errors/__tests__/fileSystemErrors.ts rename to errors/__tests__/fileSystemErrors.test.ts diff --git a/errors/__tests__/standardErrors.ts b/errors/__tests__/standardErrors.test.ts similarity index 100% rename from errors/__tests__/standardErrors.ts rename to errors/__tests__/standardErrors.test.ts diff --git a/http/__tests__/getAxiosConfig.ts b/http/__tests__/getAxiosConfig.test.ts similarity index 100% rename from http/__tests__/getAxiosConfig.ts rename to http/__tests__/getAxiosConfig.test.ts diff --git a/http/__tests__/index.ts b/http/__tests__/index.test.ts similarity index 100% rename from http/__tests__/index.ts rename to http/__tests__/index.test.ts diff --git a/lib/__tests__/archive.ts b/lib/__tests__/archive.test.ts similarity index 100% rename from lib/__tests__/archive.ts rename to lib/__tests__/archive.test.ts diff --git a/lib/__tests__/customObjects.ts b/lib/__tests__/customObjects.test.ts similarity index 100% rename from lib/__tests__/customObjects.ts rename to lib/__tests__/customObjects.test.ts diff --git a/lib/__tests__/environment.ts b/lib/__tests__/environment.test.ts similarity index 100% rename from lib/__tests__/environment.ts rename to lib/__tests__/environment.test.ts diff --git a/lib/__tests__/fileManager.ts b/lib/__tests__/fileManager.test.ts similarity index 100% rename from lib/__tests__/fileManager.ts rename to lib/__tests__/fileManager.test.ts diff --git a/lib/__tests__/fileMapper.ts b/lib/__tests__/fileMapper.test.ts similarity index 100% rename from lib/__tests__/fileMapper.ts rename to lib/__tests__/fileMapper.test.ts diff --git a/lib/__tests__/fs.ts b/lib/__tests__/fs.test.ts similarity index 100% rename from lib/__tests__/fs.ts rename to lib/__tests__/fs.test.ts diff --git a/lib/__tests__/github.ts b/lib/__tests__/github.test.ts similarity index 100% rename from lib/__tests__/github.ts rename to lib/__tests__/github.test.ts diff --git a/lib/__tests__/gitignore.ts b/lib/__tests__/gitignore.test.ts similarity index 100% rename from lib/__tests__/gitignore.ts rename to lib/__tests__/gitignore.test.ts diff --git a/lib/__tests__/handleFieldsJS.ts b/lib/__tests__/handleFieldsJS.test.ts similarity index 100% rename from lib/__tests__/handleFieldsJS.ts rename to lib/__tests__/handleFieldsJS.test.ts diff --git a/lib/__tests__/hubdb.ts b/lib/__tests__/hubdb.test.ts similarity index 100% rename from lib/__tests__/hubdb.ts rename to lib/__tests__/hubdb.test.ts diff --git a/lib/__tests__/ignoreRules.ts b/lib/__tests__/ignoreRules.test.ts similarity index 100% rename from lib/__tests__/ignoreRules.ts rename to lib/__tests__/ignoreRules.test.ts diff --git a/lib/__tests__/logger.ts b/lib/__tests__/logger.test.ts similarity index 100% rename from lib/__tests__/logger.ts rename to lib/__tests__/logger.test.ts diff --git a/lib/__tests__/modules.ts b/lib/__tests__/modules.test.ts similarity index 100% rename from lib/__tests__/modules.ts rename to lib/__tests__/modules.test.ts diff --git a/lib/__tests__/notify.ts b/lib/__tests__/notify.test.ts similarity index 100% rename from lib/__tests__/notify.ts rename to lib/__tests__/notify.test.ts diff --git a/lib/__tests__/oauth.ts b/lib/__tests__/oauth.test.ts similarity index 100% rename from lib/__tests__/oauth.ts rename to lib/__tests__/oauth.test.ts diff --git a/lib/__tests__/path.ts b/lib/__tests__/path.test.ts similarity index 100% rename from lib/__tests__/path.ts rename to lib/__tests__/path.test.ts diff --git a/lib/__tests__/personalAccessKey.ts b/lib/__tests__/personalAccessKey.test.ts similarity index 100% rename from lib/__tests__/personalAccessKey.ts rename to lib/__tests__/personalAccessKey.test.ts diff --git a/lib/__tests__/portManager.ts b/lib/__tests__/portManager.test.ts similarity index 100% rename from lib/__tests__/portManager.ts rename to lib/__tests__/portManager.test.ts diff --git a/lib/__tests__/sandboxes.ts b/lib/__tests__/sandboxes.test.ts similarity index 100% rename from lib/__tests__/sandboxes.ts rename to lib/__tests__/sandboxes.test.ts diff --git a/lib/__tests__/templates.ts b/lib/__tests__/templates.test.ts similarity index 100% rename from lib/__tests__/templates.ts rename to lib/__tests__/templates.test.ts diff --git a/lib/__tests__/text.ts b/lib/__tests__/text.test.ts similarity index 100% rename from lib/__tests__/text.ts rename to lib/__tests__/text.test.ts diff --git a/lib/__tests__/trackUsage.ts b/lib/__tests__/trackUsage.test.ts similarity index 100% rename from lib/__tests__/trackUsage.ts rename to lib/__tests__/trackUsage.test.ts diff --git a/lib/__tests__/uploadFolder.ts b/lib/__tests__/uploadFolder.test.ts similarity index 100% rename from lib/__tests__/uploadFolder.ts rename to lib/__tests__/uploadFolder.test.ts diff --git a/lib/__tests__/urls.ts b/lib/__tests__/urls.test.ts similarity index 100% rename from lib/__tests__/urls.ts rename to lib/__tests__/urls.test.ts diff --git a/models/__tests__/OAuth2Manager.ts b/models/__tests__/OAuth2Manager.test.ts similarity index 100% rename from models/__tests__/OAuth2Manager.ts rename to models/__tests__/OAuth2Manager.test.ts diff --git a/utils/__tests__/fieldsJS.ts b/utils/__tests__/fieldsJS.test.ts similarity index 100% rename from utils/__tests__/fieldsJS.ts rename to utils/__tests__/fieldsJS.test.ts diff --git a/utils/__tests__/git.ts b/utils/__tests__/git.test.ts similarity index 100% rename from utils/__tests__/git.ts rename to utils/__tests__/git.test.ts diff --git a/utils/__tests__/modules.ts b/utils/__tests__/modules.test.ts similarity index 100% rename from utils/__tests__/modules.ts rename to utils/__tests__/modules.test.ts From 24ec6a9c1191d6941a385e3869a060e0f7ac521a Mon Sep 17 00:00:00 2001 From: Jessica Sines Date: Thu, 15 Aug 2024 16:16:30 -0400 Subject: [PATCH 13/25] Add extensions used by themes with JSR modules to fetch extensions --- lib/fileMapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fileMapper.ts b/lib/fileMapper.ts index aec3b40c..7c175338 100644 --- a/lib/fileMapper.ts +++ b/lib/fileMapper.ts @@ -197,7 +197,7 @@ async function fetchAndWriteFileStream( logger.log(i18n(`${i18nKey}.skippedExisting`, { filepath })); return; } - if (!isAllowedExtension(srcPath)) { + if (!isAllowedExtension(srcPath, ['tsx', 'jsx', 'ts'])) { throwErrorWithMessage(`${i18nKey}.errors.invalidFileType`, { srcPath }); } let node: FileMapperNode; From 7535a065ef665345ce0cc673092194853a418fa0 Mon Sep 17 00:00:00 2001 From: Jessica Sines Date: Thu, 15 Aug 2024 16:28:16 -0400 Subject: [PATCH 14/25] Add debug log for errors --- lib/fileMapper.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/fileMapper.ts b/lib/fileMapper.ts index 7c175338..eef32e26 100644 --- a/lib/fileMapper.ts +++ b/lib/fileMapper.ts @@ -197,7 +197,7 @@ async function fetchAndWriteFileStream( logger.log(i18n(`${i18nKey}.skippedExisting`, { filepath })); return; } - if (!isAllowedExtension(srcPath, ['tsx', 'jsx', 'ts'])) { + if (!isAllowedExtension(srcPath, ['tsx', 'ts', 'jsx'])) { throwErrorWithMessage(`${i18nKey}.errors.invalidFileType`, { srcPath }); } let node: FileMapperNode; @@ -368,6 +368,12 @@ async function downloadFolder( ); if (succeeded === false) { success = false; + logger.debug( + i18n(`${i18nKey}.errors.failedToFetchFile`, { + src: childNode.path, + dest: filepath || '', + }) + ); } }); return success; From 230e3b7a2875788ae8bb47d2030c375ee4f235dc Mon Sep 17 00:00:00 2001 From: Jessica Sines Date: Fri, 16 Aug 2024 10:29:51 -0400 Subject: [PATCH 15/25] Make const --- constants/extensions.ts | 1 + lib/fileMapper.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/constants/extensions.ts b/constants/extensions.ts index 8d8270d9..5190ac9f 100644 --- a/constants/extensions.ts +++ b/constants/extensions.ts @@ -18,6 +18,7 @@ export const ALLOWED_EXTENSIONS = new Set([ 'woff2', 'graphql', ]); +export const JSR_ALLOWED_EXTENSIONS = new Set(['jsx', 'tsx', 'ts']); export const HUBL_EXTENSIONS = new Set(['css', 'html', 'js']); export const MODULE_EXTENSION = 'module'; diff --git a/lib/fileMapper.ts b/lib/fileMapper.ts index eef32e26..b86ff20a 100644 --- a/lib/fileMapper.ts +++ b/lib/fileMapper.ts @@ -13,7 +13,11 @@ import { import { logger } from './logger'; import { fetchFileStream, download, downloadDefault } from '../api/fileMapper'; import { throwError, throwErrorWithMessage } from '../errors/standardErrors'; -import { MODULE_EXTENSION, FUNCTIONS_EXTENSION } from '../constants/extensions'; +import { + MODULE_EXTENSION, + FUNCTIONS_EXTENSION, + JSR_ALLOWED_EXTENSIONS, +} from '../constants/extensions'; import { MODE } from '../constants/files'; import { FileMapperNode, @@ -197,7 +201,7 @@ async function fetchAndWriteFileStream( logger.log(i18n(`${i18nKey}.skippedExisting`, { filepath })); return; } - if (!isAllowedExtension(srcPath, ['tsx', 'ts', 'jsx'])) { + if (!isAllowedExtension(srcPath, Array.from(JSR_ALLOWED_EXTENSIONS))) { throwErrorWithMessage(`${i18nKey}.errors.invalidFileType`, { srcPath }); } let node: FileMapperNode; From 137759c3eae45b24bb1cc0d2cf5c8492ad530156 Mon Sep 17 00:00:00 2001 From: Camden Phalen Date: Fri, 16 Aug 2024 11:00:15 -0400 Subject: [PATCH 16/25] v1.10.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f7399fb0..552c5ff3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hubspot/local-dev-lib", - "version": "1.9.1", + "version": "1.10.0", "description": "Provides library functionality for HubSpot local development tooling, including the HubSpot CLI", "main": "lib/index.js", "repository": { From e4771f6abea1728d9fe26f430b6d2609b3e0c20d Mon Sep 17 00:00:00 2001 From: Joe Yeager Date: Wed, 28 Aug 2024 09:07:53 -0700 Subject: [PATCH 17/25] feat: Add a method to force a token refresh (#187) --- lib/personalAccessKey.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/personalAccessKey.ts b/lib/personalAccessKey.ts index 1cf1bab2..70b5f636 100644 --- a/lib/personalAccessKey.ts +++ b/lib/personalAccessKey.ts @@ -140,7 +140,8 @@ async function getNewAccessTokenByAccountId( } export async function accessTokenForPersonalAccessKey( - accountId: number + accountId: number, + forceRefresh = false ): Promise { const account = getAccountConfig(accountId) as PersonalAccessKeyAccount; if (!account) { @@ -152,6 +153,7 @@ export async function accessTokenForPersonalAccessKey( if ( !authDataExists || + forceRefresh || moment().add(5, 'minutes').isAfter(moment(authTokenInfo.expiresAt)) ) { return getNewAccessToken( From 64f878762f7f04607290391894f7cfb4c21bb457 Mon Sep 17 00:00:00 2001 From: Joe Yeager Date: Wed, 28 Aug 2024 10:29:55 -0700 Subject: [PATCH 18/25] fix: Replace broken `fetchDeployComponentsMetadata` method (#181) --- api/__tests__/projects.test.ts | 577 +++++++++++++++++++++++++++++++++ api/projects.ts | 25 +- types/ComponentStructure.ts | 52 ++- 3 files changed, 630 insertions(+), 24 deletions(-) create mode 100644 api/__tests__/projects.test.ts diff --git a/api/__tests__/projects.test.ts b/api/__tests__/projects.test.ts new file mode 100644 index 00000000..d619ddbd --- /dev/null +++ b/api/__tests__/projects.test.ts @@ -0,0 +1,577 @@ +jest.mock('../../http'); +jest.mock('fs'); +import { createReadStream } from 'fs'; +import http from '../../http'; +import { + cancelStagedBuild, + checkCloneStatus, + checkMigrationStatus, + cloneApp, + createProject, + deleteFileFromBuild, + deleteProject, + deployProject, + downloadClonedProject, + downloadProject, + fetchBuildWarnLogs, + fetchDeployWarnLogs, + fetchPlatformVersions, + fetchProject, + fetchProjectBuilds, + fetchProjectComponentsMetadata, + fetchProjects, + fetchProjectSettings, + getBuildStatus, + getBuildStructure, + getDeployStatus, + getDeployStructure, + migrateApp, + provisionBuild, + queueBuild, + uploadFileToBuild, + uploadProject, +} from '../projects'; + +const createReadStreamMock = createReadStream as jest.MockedFunction< + typeof createReadStream +>; + +describe('api/projects', () => { + const accountId = 999999; + const projectId = 888888; + const projectName = 'super-cool-project'; + const projectNameIllegalChars = 'super-cool-project///////'; + + const formData = 'this is the form data that we are sending'; + + beforeEach(() => { + // @ts-expect-error Method signature mismatch + createReadStreamMock.mockImplementationOnce(() => { + return formData; + }); + }); + + describe('fetchProjects', () => { + it('should call http correctly', async () => { + await fetchProjects(accountId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `developer/projects/v1`, + }); + }); + }); + + describe('createProject', () => { + it('should call http correctly', async () => { + await createProject(accountId, projectName); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `developer/projects/v1`, + data: { + name: projectName, + }, + }); + }); + }); + + describe('uploadProject', () => { + const platformVersion = '2024.1'; + const projectFile = 'test.js'; + const uploadMessage = 'We did it'; + + it('should call http correctly', async () => { + await uploadProject(accountId, projectName, projectFile, uploadMessage); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/upload/${projectName}`, + timeout: 60_000, + data: { + file: formData, + uploadMessage, + }, + headers: { 'Content-Type': 'multipart/form-data' }, + }); + }); + + it('should encode the project name', async () => { + await uploadProject( + accountId, + projectNameIllegalChars, + projectFile, + uploadMessage + ); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/upload/${encodeURIComponent(projectNameIllegalChars)}`, + timeout: 60_000, + data: { + file: formData, + uploadMessage, + }, + headers: { 'Content-Type': 'multipart/form-data' }, + }); + }); + + it('should include the platform version if included', async () => { + await uploadProject( + accountId, + projectName, + projectFile, + uploadMessage, + platformVersion + ); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/upload/${projectName}`, + timeout: 60_000, + data: { + file: formData, + uploadMessage, + platformVersion, + }, + headers: { 'Content-Type': 'multipart/form-data' }, + }); + }); + }); + + describe('fetchProject', () => { + it('should call http correctly', async () => { + const projectName = 'super-cool-project'; + await fetchProject(accountId, projectName); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `developer/projects/v1/by-name/${projectName}`, + }); + }); + + it('should encode the project name', async () => { + await fetchProject(accountId, projectNameIllegalChars); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `developer/projects/v1/by-name/${encodeURIComponent(projectNameIllegalChars)}`, + }); + }); + }); + + describe('fetchProjectComponentsMetadata', () => { + it('should call http correctly', async () => { + await fetchProjectComponentsMetadata(accountId, projectId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects-deployed-build/${projectId}`, + }); + }); + }); + + describe('downloadProject', () => { + it('should call http correctly', async () => { + const buildId = 1; + await downloadProject(accountId, projectName, buildId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${projectName}/builds/${buildId}/archive-full`, + responseType: 'arraybuffer', + headers: { + accept: 'application/zip', + 'Content-Type': 'application/json', + }, + }); + }); + + it('should encode the project name', async () => { + const buildId = 1; + await downloadProject(accountId, projectNameIllegalChars, buildId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/${buildId}/archive-full`, + responseType: 'arraybuffer', + headers: { + accept: 'application/zip', + 'Content-Type': 'application/json', + }, + }); + }); + }); + + describe('deleteProject', () => { + it('should call http correctly', async () => { + await deleteProject(accountId, projectName); + expect(http.delete).toHaveBeenCalledTimes(1); + expect(http.delete).toHaveBeenCalledWith(accountId, { + url: `developer/projects/v1/${projectName}`, + }); + }); + + it('should encode the project name', async () => { + await deleteProject(accountId, projectNameIllegalChars); + expect(http.delete).toHaveBeenCalledTimes(1); + expect(http.delete).toHaveBeenCalledWith(accountId, { + url: `developer/projects/v1/${encodeURIComponent(projectNameIllegalChars)}`, + }); + }); + }); + + describe('fetchPlatformVersions', () => { + it('should call http correctly', async () => { + await fetchPlatformVersions(accountId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `developer/projects/v1/platformVersion`, + }); + }); + }); + + describe('fetchProjectBuilds', () => { + it('should call http correctly', async () => { + const params = { + foo: 'bar', + }; + await fetchProjectBuilds(accountId, projectName, params); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${projectName}/builds`, + params, + }); + }); + + it('should encode the project name', async () => { + await fetchProjectBuilds(accountId, projectNameIllegalChars); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds`, + params: {}, + }); + }); + }); + + describe('getBuildStatus', () => { + const buildId = 1; + it('should call http correctly', async () => { + await getBuildStatus(accountId, projectName, buildId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${projectName}/builds/${buildId}/status`, + }); + }); + + it('should encode the project name', async () => { + await getBuildStatus(accountId, projectNameIllegalChars, buildId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/${buildId}/status`, + }); + }); + }); + + describe('getBuildStructure', () => { + const buildId = 1; + it('should call http correctly', async () => { + await getBuildStructure(accountId, projectName, buildId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/builds/by-project-name/${projectName}/builds/${buildId}/structure`, + }); + }); + + it('should encode the project name', async () => { + await getBuildStructure(accountId, projectNameIllegalChars, buildId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/builds/by-project-name/${encodeURIComponent(projectNameIllegalChars)}/builds/${buildId}/structure`, + }); + }); + }); + + describe('deployProject', () => { + const buildId = 1; + it('should call http correctly', async () => { + await deployProject(accountId, projectName, buildId); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `dfs/deploy/v1/deploys/queue/async`, + data: { + projectName, + buildId, + }, + }); + }); + }); + + describe('getDeployStatus', () => { + const deployId = 1; + it('should call http correctly', async () => { + await getDeployStatus(accountId, projectName, deployId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/deploy/v1/deploy-status/projects/${projectName}/deploys/${deployId}`, + }); + }); + + it('should encode the project name', async () => { + await getDeployStatus(accountId, projectNameIllegalChars, deployId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/deploy/v1/deploy-status/projects/${encodeURIComponent(projectNameIllegalChars)}/deploys/${deployId}`, + }); + }); + }); + + describe('getDeployStructure', () => { + const deployId = 1; + it('should call http correctly', async () => { + await getDeployStructure(accountId, projectName, deployId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/deploy/v1/deploys/by-project-name/${projectName}/deploys/${deployId}/structure`, + }); + }); + + it('should encode the project name', async () => { + await getDeployStructure(accountId, projectNameIllegalChars, deployId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/deploy/v1/deploys/by-project-name/${encodeURIComponent(projectNameIllegalChars)}/deploys/${deployId}/structure`, + }); + }); + }); + + describe('fetchProjectSettings', () => { + it('should call http correctly', async () => { + await fetchProjectSettings(accountId, projectName); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `developer/projects/v1/${projectName}/settings`, + }); + }); + + it('should encode the project name', async () => { + await fetchProjectSettings(accountId, projectNameIllegalChars); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `developer/projects/v1/${encodeURIComponent(projectNameIllegalChars)}/settings`, + }); + }); + }); + + describe('provisionBuild', () => { + it('should call http correctly', async () => { + const platformVersion = '2023.1'; + await provisionBuild(accountId, projectName, platformVersion); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${projectName}/builds/staged/provision`, + params: { platformVersion }, + headers: { 'Content-Type': 'application/json' }, + timeout: 50_000, + }); + }); + + it('should encode the project name', async () => { + await provisionBuild(accountId, projectNameIllegalChars); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/provision`, + params: {}, + headers: { 'Content-Type': 'application/json' }, + timeout: 50_000, + }); + }); + }); + + describe('queueBuild', () => { + it('should call http correctly', async () => { + const platformVersion = '2023.1'; + await queueBuild(accountId, projectName, platformVersion); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${projectName}/builds/staged/queue`, + params: { platformVersion }, + headers: { 'Content-Type': 'application/json' }, + }); + }); + + it('should encode the project name', async () => { + await queueBuild(accountId, projectNameIllegalChars); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/queue`, + params: {}, + headers: { 'Content-Type': 'application/json' }, + }); + }); + }); + + describe('uploadFileToBuild', () => { + it('should call http correctly', async () => { + const filePath = 'file/to/upload'; + const path = 'path/to/file'; + await uploadFileToBuild(accountId, projectName, filePath, path); + expect(http.put).toHaveBeenCalledTimes(1); + expect(http.put).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${projectName}/builds/staged/files/${encodeURIComponent(path)}`, + data: { file: formData }, + headers: { 'Content-Type': 'multipart/form-data' }, + }); + }); + + it('should encode the project name', async () => { + const filePath = 'file/to/upload'; + const path = 'path/to/file'; + await uploadFileToBuild( + accountId, + projectNameIllegalChars, + filePath, + path + ); + expect(http.put).toHaveBeenCalledTimes(1); + expect(http.put).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/files/${encodeURIComponent(path)}`, + data: { file: formData }, + headers: { 'Content-Type': 'multipart/form-data' }, + }); + }); + }); + + describe('deleteFileFromBuild', () => { + it('should call http correctly', async () => { + const path = 'path/to/file'; + await deleteFileFromBuild(accountId, projectName, path); + expect(http.delete).toHaveBeenCalledTimes(1); + expect(http.delete).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${projectName}/builds/staged/files/${encodeURIComponent(path)}`, + }); + }); + + it('should encode the project name', async () => { + const filePath = 'file/to/upload'; + await deleteFileFromBuild(accountId, projectNameIllegalChars, filePath); + expect(http.delete).toHaveBeenCalledTimes(1); + expect(http.delete).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/files/${encodeURIComponent(filePath)}`, + }); + }); + }); + + describe('cancelStagedBuild', () => { + it('should call http correctly', async () => { + await cancelStagedBuild(accountId, projectName); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${projectName}/builds/staged/cancel`, + headers: { 'Content-Type': 'application/json' }, + }); + }); + + it('should encode the project name', async () => { + await cancelStagedBuild(accountId, projectNameIllegalChars); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/cancel`, + headers: { 'Content-Type': 'application/json' }, + }); + }); + }); + + describe('fetchBuildWarnLogs', () => { + const buildId = 1; + it('should call http correctly', async () => { + await fetchBuildWarnLogs(accountId, projectName, buildId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/logging/v1/logs/projects/${projectName}/builds/${buildId}/combined/warn`, + }); + }); + + it('should encode the project name', async () => { + await fetchBuildWarnLogs(accountId, projectNameIllegalChars, buildId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/logging/v1/logs/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/${buildId}/combined/warn`, + }); + }); + }); + + describe('fetchDeployWarnLogs', () => { + const deployId = 1; + it('should call http correctly', async () => { + await fetchDeployWarnLogs(accountId, projectName, deployId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/logging/v1/logs/projects/${projectName}/deploys/${deployId}/combined/warn`, + }); + }); + + it('should encode the project name', async () => { + await fetchDeployWarnLogs(accountId, projectNameIllegalChars, deployId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/logging/v1/logs/projects/${encodeURIComponent(projectNameIllegalChars)}/deploys/${deployId}/combined/warn`, + }); + }); + }); + + describe('migrateApp', () => { + const appId = 123456; + it('should call http correctly', async () => { + await migrateApp(accountId, appId, projectName); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `dfs/migrations/v1/migrations`, + data: { + componentId: appId, + componentType: 'PUBLIC_APP_ID', + projectName, + }, + }); + }); + }); + + describe('checkMigrationStatus', () => { + it('should call http correctly', async () => { + const migrationId = 123456; + await checkMigrationStatus(accountId, migrationId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/migrations/v1/migrations/${migrationId}`, + }); + }); + }); + + describe('cloneApp', () => { + it('should call http correctly', async () => { + const appId = 123456; + await cloneApp(accountId, appId); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `dfs/migrations/v1/exports`, + data: { + componentId: appId, + componentType: 'PUBLIC_APP_ID', + }, + }); + }); + }); + + describe('checkCloneStatus', () => { + it('should call http correctly', async () => { + const exportId = 123456; + await checkCloneStatus(accountId, exportId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/migrations/v1/exports/${exportId}/status`, + }); + }); + }); + + describe('downloadClonedProject', () => { + it('should call http correctly', async () => { + const exportId = 123456; + await downloadClonedProject(accountId, exportId); + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(accountId, { + url: `dfs/migrations/v1/exports/${exportId}/download-as-clone`, + responseType: 'arraybuffer', + }); + }); + }); +}); diff --git a/api/projects.ts b/api/projects.ts index 27487183..2e06b6c2 100644 --- a/api/projects.ts +++ b/api/projects.ts @@ -10,8 +10,8 @@ import { } from '../types/Project'; import { Build, FetchProjectBuildsResponse } from '../types/Build'; import { - ComponentMetadataResponse, ComponentStructureResponse, + ProjectComponentsMetadata, } from '../types/ComponentStructure'; import { Deploy, ProjectDeployResponse } from '../types/Deploy'; import { ProjectLog } from '../types/ProjectLog'; @@ -22,6 +22,7 @@ import { } from '../types/Migration'; const PROJECTS_API_PATH = 'dfs/v1/projects'; +const DEVELOPER_FILE_SYSTEM_PATH = 'dfs/v1'; const PROJECTS_DEPLOY_API_PATH = 'dfs/deploy/v1'; const PROJECTS_LOGS_API_PATH = 'dfs/logging/v1'; const DEVELOPER_PROJECTS_API_PATH = 'developer/projects/v1'; @@ -64,7 +65,7 @@ export async function uploadProject( return http.post(accountId, { url: `${PROJECTS_API_PATH}/upload/${encodeURIComponent(projectName)}`, - timeout: 60000, + timeout: 60_000, data: formData, headers: { 'Content-Type': 'multipart/form-data' }, }); @@ -79,6 +80,15 @@ export async function fetchProject( }); } +export async function fetchProjectComponentsMetadata( + accountId: number, + projectId: number +): Promise { + return http.get(accountId, { + url: `${DEVELOPER_FILE_SYSTEM_PATH}/projects-deployed-build/${projectId}`, + }); +} + export async function downloadProject( accountId: number, projectName: string, @@ -192,15 +202,6 @@ export async function fetchProjectSettings( }); } -export async function fetchDeployComponentsMetadata( - accountId: number, - projectId: number -): Promise { - return http.get(accountId, { - url: `${PROJECTS_API_PATH}/by-id/${projectId}/deploy-components-metadata`, - }); -} - export async function provisionBuild( accountId: number, projectName: string, @@ -212,7 +213,7 @@ export async function provisionBuild( )}/builds/staged/provision`, params: { platformVersion }, headers: { 'Content-Type': 'application/json' }, - timeout: 50000, + timeout: 50_000, }); } diff --git a/types/ComponentStructure.ts b/types/ComponentStructure.ts index 0f6c2184..199c6b75 100644 --- a/types/ComponentStructure.ts +++ b/types/ComponentStructure.ts @@ -9,18 +9,46 @@ export type ComponentStructureResponse = { topLevelComponentsWithChildren: ComponentStructure; }; -export type ComponentMetadata = { - componentIdentifier: string; +export type ProjectComponentsMetadata = { + topLevelComponentMetadata: TopLevelComponents[]; +}; + +export interface ComponentMetadata { componentName: string; - componentType: - | ValueOf - | ValueOf; - metadata: { - appId: string; + type: { + name: T; }; - parentComponentType: string; -}; + deployOutput: unknown; +} -export type ComponentMetadataResponse = { - results: Array; -}; +export interface TopLevelComponent + extends ComponentMetadata> { + featureComponents: FeatureComponents[]; +} + +export interface PrivateAppComponentMetadata extends TopLevelComponent { + deployOutput: { + cardId: number; + appId: number; + }; +} + +export type TopLevelComponents = + | PrivateAppComponentMetadata + | TopLevelComponent; + +export interface FeatureComponent + extends ComponentMetadata> { + deployOutput: T; +} + +export type AppFunctionComponentMetadata = FeatureComponent<{ + appId: number; + appFunctionName: string; + endpoint?: { + path: string; + methods: string[]; + }; +}>; + +export type FeatureComponents = FeatureComponent | AppFunctionComponentMetadata; From e3d24f82e06ade8f2c59a1e150f1fb15621c7f04 Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Wed, 28 Aug 2024 14:57:19 -0400 Subject: [PATCH 19/25] Add function to interpret tilde in file paths --- api/__tests__/projects.test.ts | 76 +++++++++++++++++++++++++--------- api/projects.ts | 8 +++- lib/path.ts | 13 ++++++ 3 files changed, 76 insertions(+), 21 deletions(-) diff --git a/api/__tests__/projects.test.ts b/api/__tests__/projects.test.ts index d619ddbd..c8de64ec 100644 --- a/api/__tests__/projects.test.ts +++ b/api/__tests__/projects.test.ts @@ -102,7 +102,9 @@ describe('api/projects', () => { ); expect(http.post).toHaveBeenCalledTimes(1); expect(http.post).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/upload/${encodeURIComponent(projectNameIllegalChars)}`, + url: `dfs/v1/projects/upload/${encodeURIComponent( + projectNameIllegalChars + )}`, timeout: 60_000, data: { file: formData, @@ -148,7 +150,9 @@ describe('api/projects', () => { await fetchProject(accountId, projectNameIllegalChars); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `developer/projects/v1/by-name/${encodeURIComponent(projectNameIllegalChars)}`, + url: `developer/projects/v1/by-name/${encodeURIComponent( + projectNameIllegalChars + )}`, }); }); }); @@ -183,7 +187,9 @@ describe('api/projects', () => { await downloadProject(accountId, projectNameIllegalChars, buildId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/${buildId}/archive-full`, + url: `dfs/v1/projects/${encodeURIComponent( + projectNameIllegalChars + )}/builds/${buildId}/archive-full`, responseType: 'arraybuffer', headers: { accept: 'application/zip', @@ -206,7 +212,9 @@ describe('api/projects', () => { await deleteProject(accountId, projectNameIllegalChars); expect(http.delete).toHaveBeenCalledTimes(1); expect(http.delete).toHaveBeenCalledWith(accountId, { - url: `developer/projects/v1/${encodeURIComponent(projectNameIllegalChars)}`, + url: `developer/projects/v1/${encodeURIComponent( + projectNameIllegalChars + )}`, }); }); }); @@ -238,7 +246,9 @@ describe('api/projects', () => { await fetchProjectBuilds(accountId, projectNameIllegalChars); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds`, + url: `dfs/v1/projects/${encodeURIComponent( + projectNameIllegalChars + )}/builds`, params: {}, }); }); @@ -258,7 +268,9 @@ describe('api/projects', () => { await getBuildStatus(accountId, projectNameIllegalChars, buildId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/${buildId}/status`, + url: `dfs/v1/projects/${encodeURIComponent( + projectNameIllegalChars + )}/builds/${buildId}/status`, }); }); }); @@ -277,7 +289,9 @@ describe('api/projects', () => { await getBuildStructure(accountId, projectNameIllegalChars, buildId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/builds/by-project-name/${encodeURIComponent(projectNameIllegalChars)}/builds/${buildId}/structure`, + url: `dfs/v1/builds/by-project-name/${encodeURIComponent( + projectNameIllegalChars + )}/builds/${buildId}/structure`, }); }); }); @@ -311,7 +325,9 @@ describe('api/projects', () => { await getDeployStatus(accountId, projectNameIllegalChars, deployId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/deploy/v1/deploy-status/projects/${encodeURIComponent(projectNameIllegalChars)}/deploys/${deployId}`, + url: `dfs/deploy/v1/deploy-status/projects/${encodeURIComponent( + projectNameIllegalChars + )}/deploys/${deployId}`, }); }); }); @@ -330,7 +346,9 @@ describe('api/projects', () => { await getDeployStructure(accountId, projectNameIllegalChars, deployId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/deploy/v1/deploys/by-project-name/${encodeURIComponent(projectNameIllegalChars)}/deploys/${deployId}/structure`, + url: `dfs/deploy/v1/deploys/by-project-name/${encodeURIComponent( + projectNameIllegalChars + )}/deploys/${deployId}/structure`, }); }); }); @@ -348,7 +366,9 @@ describe('api/projects', () => { await fetchProjectSettings(accountId, projectNameIllegalChars); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `developer/projects/v1/${encodeURIComponent(projectNameIllegalChars)}/settings`, + url: `developer/projects/v1/${encodeURIComponent( + projectNameIllegalChars + )}/settings`, }); }); }); @@ -370,7 +390,9 @@ describe('api/projects', () => { await provisionBuild(accountId, projectNameIllegalChars); expect(http.post).toHaveBeenCalledTimes(1); expect(http.post).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/provision`, + url: `dfs/v1/projects/${encodeURIComponent( + projectNameIllegalChars + )}/builds/staged/provision`, params: {}, headers: { 'Content-Type': 'application/json' }, timeout: 50_000, @@ -394,7 +416,9 @@ describe('api/projects', () => { await queueBuild(accountId, projectNameIllegalChars); expect(http.post).toHaveBeenCalledTimes(1); expect(http.post).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/queue`, + url: `dfs/v1/projects/${encodeURIComponent( + projectNameIllegalChars + )}/builds/staged/queue`, params: {}, headers: { 'Content-Type': 'application/json' }, }); @@ -408,7 +432,9 @@ describe('api/projects', () => { await uploadFileToBuild(accountId, projectName, filePath, path); expect(http.put).toHaveBeenCalledTimes(1); expect(http.put).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${projectName}/builds/staged/files/${encodeURIComponent(path)}`, + url: `dfs/v1/projects/${projectName}/builds/staged/files/${encodeURIComponent( + path + )}`, data: { file: formData }, headers: { 'Content-Type': 'multipart/form-data' }, }); @@ -425,7 +451,9 @@ describe('api/projects', () => { ); expect(http.put).toHaveBeenCalledTimes(1); expect(http.put).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/files/${encodeURIComponent(path)}`, + url: `dfs/v1/projects/${encodeURIComponent( + projectNameIllegalChars + )}/builds/staged/files/${encodeURIComponent(path)}`, data: { file: formData }, headers: { 'Content-Type': 'multipart/form-data' }, }); @@ -438,7 +466,9 @@ describe('api/projects', () => { await deleteFileFromBuild(accountId, projectName, path); expect(http.delete).toHaveBeenCalledTimes(1); expect(http.delete).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${projectName}/builds/staged/files/${encodeURIComponent(path)}`, + url: `dfs/v1/projects/${projectName}/builds/staged/files/${encodeURIComponent( + path + )}`, }); }); @@ -447,7 +477,9 @@ describe('api/projects', () => { await deleteFileFromBuild(accountId, projectNameIllegalChars, filePath); expect(http.delete).toHaveBeenCalledTimes(1); expect(http.delete).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/files/${encodeURIComponent(filePath)}`, + url: `dfs/v1/projects/${encodeURIComponent( + projectNameIllegalChars + )}/builds/staged/files/${encodeURIComponent(filePath)}`, }); }); }); @@ -466,7 +498,9 @@ describe('api/projects', () => { await cancelStagedBuild(accountId, projectNameIllegalChars); expect(http.post).toHaveBeenCalledTimes(1); expect(http.post).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/cancel`, + url: `dfs/v1/projects/${encodeURIComponent( + projectNameIllegalChars + )}/builds/staged/cancel`, headers: { 'Content-Type': 'application/json' }, }); }); @@ -486,7 +520,9 @@ describe('api/projects', () => { await fetchBuildWarnLogs(accountId, projectNameIllegalChars, buildId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/logging/v1/logs/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/${buildId}/combined/warn`, + url: `dfs/logging/v1/logs/projects/${encodeURIComponent( + projectNameIllegalChars + )}/builds/${buildId}/combined/warn`, }); }); }); @@ -505,7 +541,9 @@ describe('api/projects', () => { await fetchDeployWarnLogs(accountId, projectNameIllegalChars, deployId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/logging/v1/logs/projects/${encodeURIComponent(projectNameIllegalChars)}/deploys/${deployId}/combined/warn`, + url: `dfs/logging/v1/logs/projects/${encodeURIComponent( + projectNameIllegalChars + )}/deploys/${deployId}/combined/warn`, }); }); }); diff --git a/api/projects.ts b/api/projects.ts index 2e06b6c2..7908a602 100644 --- a/api/projects.ts +++ b/api/projects.ts @@ -76,7 +76,9 @@ export async function fetchProject( projectName: string ): Promise { return http.get(accountId, { - url: `${DEVELOPER_PROJECTS_API_PATH}/by-name/${encodeURIComponent(projectName)}`, + url: `${DEVELOPER_PROJECTS_API_PATH}/by-name/${encodeURIComponent( + projectName + )}`, }); } @@ -198,7 +200,9 @@ export async function fetchProjectSettings( projectName: string ): Promise { return http.get(accountId, { - url: `${DEVELOPER_PROJECTS_API_PATH}/${encodeURIComponent(projectName)}/settings`, + url: `${DEVELOPER_PROJECTS_API_PATH}/${encodeURIComponent( + projectName + )}/settings`, }); } diff --git a/lib/path.ts b/lib/path.ts index 141980fd..bb6a5df5 100644 --- a/lib/path.ts +++ b/lib/path.ts @@ -1,6 +1,7 @@ import path from 'path'; import unixify from 'unixify'; import { ALLOWED_EXTENSIONS } from '../constants/extensions'; +import os from 'os'; export function convertToUnixPath(_path: string): string { return unixify(path.normalize(_path)); @@ -129,3 +130,15 @@ export function isValidPath(_path: string): boolean { return true; } + +// Based on the untildify package: https://github.com/sindresorhus/untildify/blob/main/index.js +export function untildify(pathWithTilde: string): string { + const homeDirectory = os.homedir(); + if (typeof pathWithTilde !== 'string') { + throw new TypeError(`Expected a string, got ${typeof pathWithTilde}`); + } + + return homeDirectory + ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) + : pathWithTilde; +} From 2e46b9fbad8cf8ba9e59a94d1179725bcabb2734 Mon Sep 17 00:00:00 2001 From: Joe Yeager Date: Wed, 28 Aug 2024 12:52:13 -0700 Subject: [PATCH 20/25] v1.11.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 552c5ff3..8ca75282 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hubspot/local-dev-lib", - "version": "1.10.0", + "version": "1.11.0", "description": "Provides library functionality for HubSpot local development tooling, including the HubSpot CLI", "main": "lib/index.js", "repository": { From 1416429e4959b5d837d8fde733b0ce15d5ec90c4 Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Thu, 29 Aug 2024 11:50:43 -0400 Subject: [PATCH 21/25] Add tests --- lib/__tests__/path.test.ts | 368 +++++++++++++++++++++++-------------- 1 file changed, 235 insertions(+), 133 deletions(-) diff --git a/lib/__tests__/path.test.ts b/lib/__tests__/path.test.ts index 23cf02f7..950b7661 100644 --- a/lib/__tests__/path.test.ts +++ b/lib/__tests__/path.test.ts @@ -1,139 +1,241 @@ -import path, { PlatformPath } from 'path'; -import { splitHubSpotPath, splitLocalPath } from '../path'; +import os from 'os'; +import { + convertToUnixPath, + splitLocalPath, + splitHubSpotPath, + getCwd, + getExt, + getAllowedExtensions, + isAllowedExtension, + sanitizeFileName, + isValidPath, + untildify, +} from '../path'; +import { ALLOWED_EXTENSIONS } from '../../constants/extensions'; -describe('lib/path', () => { - describe('splitHubSpotPath()', () => { - const testSplit = ( - filepath: string, - expectedParts: Array, - joined: string - ) => { - test(filepath, () => { - const parts = splitHubSpotPath(filepath); - expect(parts).toEqual(expectedParts); - expect(path.posix.join(...parts)).toBe(joined); - }); - }; - testSplit('', [], '.'); - testSplit('a', ['a'], 'a'); - testSplit('a/b', ['a', 'b'], 'a/b'); - testSplit('a/b/', ['a', 'b'], 'a/b'); - testSplit('a/b///', ['a', 'b'], 'a/b'); - testSplit('/', ['/'], '/'); - testSplit('///', ['/'], '/'); - testSplit('/a', ['/', 'a'], '/a'); - testSplit('///a', ['/', 'a'], '/a'); - testSplit('/a/b', ['/', 'a', 'b'], '/a/b'); - testSplit('a.js', ['a.js'], 'a.js'); - testSplit('/a.js/', ['/', 'a.js'], '/a.js'); - testSplit('/x/a.js/', ['/', 'x', 'a.js'], '/x/a.js'); - testSplit('///x/////a.js///', ['/', 'x', 'a.js'], '/x/a.js'); - testSplit( - '/project/My Module.module', - ['/', 'project', 'My Module.module'], - '/project/My Module.module' - ); - testSplit( - 'project/My Module.module/js', - ['project', 'My Module.module', 'js'], - 'project/My Module.module/js' - ); - testSplit( - 'project/My Module.module/js/main.js/', - ['project', 'My Module.module', 'js', 'main.js'], - 'project/My Module.module/js/main.js' - ); - testSplit( - 'project/My Module.module/js/../css/', - ['project', 'My Module.module', 'css'], - 'project/My Module.module/css' - ); - testSplit( - './project/My Module.module/js', - ['project', 'My Module.module', 'js'], - 'project/My Module.module/js' - ); - testSplit( - '../project/My Module.module/js', - ['..', 'project', 'My Module.module', 'js'], - '../project/My Module.module/js' - ); +jest.mock('os', () => ({ + homedir: jest.fn(), +})); + +jest.mock('path', () => ({ + ...jest.requireActual('path'), + sep: '/', + posix: { + sep: '/', + }, + win32: { + sep: '\\', + }, +})); + +describe('path utility functions', () => { + describe('convertToUnixPath()', () => { + test('converts Windows path to Unix path', () => { + expect(convertToUnixPath('C:\\Users\\test\\file.txt')).toBe( + '/Users/test/file.txt' + ); + }); + + test('normalizes Unix path', () => { + expect(convertToUnixPath('/home//user/./file.txt')).toBe( + '/home/user/file.txt' + ); + }); }); + + describe('convertToLocalFileSystemPath()', () => { + afterEach(() => { + jest.resetModules(); + }); + + test('converts to Unix path when on Unix-like system', async () => { + jest.doMock('path', () => ({ ...jest.requireActual('path'), sep: '/' })); + const { convertToLocalFileSystemPath } = await import('../path'); + expect(convertToLocalFileSystemPath('/home/user/file.txt')).toBe( + '/home/user/file.txt' + ); + }); + + test('converts to Windows path when on Windows system', async () => { + jest.doMock('path', () => ({ ...jest.requireActual('path'), sep: '\\' })); + const { convertToLocalFileSystemPath } = await import('../path'); + expect(convertToLocalFileSystemPath('C:/Users/test/file.txt')).toBe( + 'C:\\Users\\test\\file.txt' + ); + }); + }); + describe('splitLocalPath()', () => { - function createTestSplit( - pathImplementation: PlatformPath - ): ( - filepath: string, - expectedParts: Array, - joined: string - ) => void { - return ( - filepath: string, - expectedParts: Array, - joined: string - ) => { - test(filepath, () => { - const parts = splitLocalPath(filepath, pathImplementation); - expect(parts).toEqual(expectedParts); - expect(pathImplementation.join(...parts)).toBe(joined); - }); - }; - } - - type TestCases = Array<[string, Array, string]>; - - function getLocalFileSystemTestCases( - pathImplementation: PlatformPath - ): TestCases { - const { sep } = pathImplementation; - const isWin32 = sep === path.win32.sep; - const splitRoot = isWin32 ? 'C:' : '/'; - const pathRoot = isWin32 ? 'C:\\' : '/'; - return [ - [ - `${pathRoot}My Module.module`, - [splitRoot, 'My Module.module'], - `${pathRoot}My Module.module`, - ], - [ - `${pathRoot}My Module.module${sep}`, - [splitRoot, 'My Module.module'], - `${pathRoot}My Module.module`, - ], - [`My Module.module${sep}`, ['My Module.module'], 'My Module.module'], - [ - `${pathRoot}My Module.module${sep}js${sep}main.js`, - [splitRoot, 'My Module.module', 'js', 'main.js'], - `${pathRoot}My Module.module${sep}js${sep}main.js`, - ], - [ - `${pathRoot}My Module.module${sep}${sep}${sep}js${sep}main.js${sep}${sep}`, - [splitRoot, 'My Module.module', 'js', 'main.js'], - `${pathRoot}My Module.module${sep}js${sep}main.js`, - ], - [ - `${pathRoot}My Module.module${sep}js${sep}..${sep}css`, - [splitRoot, 'My Module.module', 'css'], - `${pathRoot}My Module.module${sep}css`, - ], - [ - `My Module.module${sep}js${sep}..${sep}css`, - ['My Module.module', 'css'], - `My Module.module${sep}css`, - ], - ]; - } - const platforms = ['posix', 'win32'] as const; - platforms.forEach(platform => { - describe(platform, () => { - const pathImplementation = path[platform]; - const testSplit = createTestSplit(pathImplementation); - const testCases = getLocalFileSystemTestCases(pathImplementation); - if (!(testCases && testCases.length)) { - throw new Error(`Missing ${platform} splitLocalPath() test cases`); - } - testCases.forEach(testCase => testSplit(...testCase)); - }); + test('splits Unix path correctly', () => { + expect( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + splitLocalPath('/home/user/file.txt', { + sep: '/', + normalize: (p: string) => p, + }) + ).toEqual(['/', 'home', 'user', 'file.txt']); + }); + + test('splits Windows path correctly', () => { + expect( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + splitLocalPath('C:\\Users\\test\\file.txt', { + sep: '\\', + normalize: (p: string) => p, + }) + ).toEqual(['C:', 'Users', 'test', 'file.txt']); + }); + + test('handles empty path', () => { + expect(splitLocalPath('')).toEqual([]); + }); + }); + + describe('splitHubSpotPath()', () => { + test('splits HubSpot path correctly', () => { + expect(splitHubSpotPath('/project/My Module.module/js/main.js')).toEqual([ + '/', + 'project', + 'My Module.module', + 'js', + 'main.js', + ]); + }); + + test('handles root path', () => { + expect(splitHubSpotPath('/')).toEqual(['/']); + }); + + test('handles empty path', () => { + expect(splitHubSpotPath('')).toEqual([]); + }); + }); + + describe('getCwd()', () => { + const originalEnv = process.env; + const originalCwd = process.cwd; + + beforeEach(() => { + process.env = { ...originalEnv }; + process.cwd = jest.fn().mockReturnValue('/mocked/cwd'); + }); + + afterEach(() => { + process.env = originalEnv; + process.cwd = originalCwd; + }); + + test('returns INIT_CWD if set', () => { + process.env.INIT_CWD = '/custom/init/cwd'; + expect(getCwd()).toBe('/custom/init/cwd'); + }); + + test('returns process.cwd() if INIT_CWD not set', () => { + delete process.env.INIT_CWD; + expect(getCwd()).toBe('/mocked/cwd'); + }); + }); + + describe('getExt()', () => { + test('returns lowercase extension without dot', () => { + expect(getExt('file.TXT')).toBe('txt'); + }); + + test('returns empty string for no extension', () => { + expect(getExt('file')).toBe(''); + }); + + test('handles non-string input', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(getExt(null as '')).toBe(''); + }); + }); + + describe('getAllowedExtensions()', () => { + test('returns default allowed extensions', () => { + const result = getAllowedExtensions(); + expect(result).toBeInstanceOf(Set); + expect(result).toEqual(new Set(ALLOWED_EXTENSIONS)); + }); + + test('includes additional extensions', () => { + const result = getAllowedExtensions(['custom']); + expect(result.has('custom')).toBe(true); + }); + }); + + describe('isAllowedExtension()', () => { + test('returns true for allowed extension', () => { + expect(isAllowedExtension('file.txt')).toBe(true); + }); + + test('returns false for disallowed extension', () => { + expect(isAllowedExtension('file.exe')).toBe(false); + }); + + test('allows custom extensions', () => { + expect(isAllowedExtension('file.custom', ['custom'])).toBe(true); + }); + }); + + describe('sanitizeFileName()', () => { + test('replaces invalid characters', () => { + expect(sanitizeFileName('file:name?.txt')).toBe('file-name-.txt'); + }); + + test('handles reserved names', () => { + expect(sanitizeFileName('CON')).toBe('-CON'); + }); + + test('removes trailing periods and spaces', () => { + expect(sanitizeFileName('file.txt. ')).toBe('file.txt'); + }); + }); + + describe('isValidPath()', () => { + test('returns true for valid path', () => { + expect(isValidPath('/valid/path/file.txt')).toBe(true); + }); + + test('returns false for path with invalid characters', () => { + expect(isValidPath('/invalid/path/file?.txt')).toBe(false); + }); + + test('returns false for reserved names', () => { + expect(isValidPath('/some/path/CON')).toBe(false); + }); + }); + + describe('untildify()', () => { + const originalHomedir = os.homedir; + + beforeEach(() => { + (os.homedir as jest.Mock) = jest.fn().mockReturnValue('/home/user'); + }); + + afterEach(() => { + os.homedir = originalHomedir; + }); + + test('replaces tilde with home directory', () => { + expect(untildify('~/documents/file.txt')).toBe( + '/home/user/documents/file.txt' + ); + }); + + test('does not modify paths without tilde', () => { + expect(untildify('/absolute/path/file.txt')).toBe( + '/absolute/path/file.txt' + ); + }); + + test('throws TypeError for non-string input', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(() => untildify(null as '')).toThrow(TypeError); }); }); }); From 77b753a1f91c6307b6555f4691d7d1e965e1e36f Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Thu, 29 Aug 2024 12:35:39 -0400 Subject: [PATCH 22/25] Revert two files --- api/__tests__/projects.test.ts | 76 +++++++++------------------------- api/projects.ts | 8 +--- 2 files changed, 21 insertions(+), 63 deletions(-) diff --git a/api/__tests__/projects.test.ts b/api/__tests__/projects.test.ts index c8de64ec..d619ddbd 100644 --- a/api/__tests__/projects.test.ts +++ b/api/__tests__/projects.test.ts @@ -102,9 +102,7 @@ describe('api/projects', () => { ); expect(http.post).toHaveBeenCalledTimes(1); expect(http.post).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/upload/${encodeURIComponent( - projectNameIllegalChars - )}`, + url: `dfs/v1/projects/upload/${encodeURIComponent(projectNameIllegalChars)}`, timeout: 60_000, data: { file: formData, @@ -150,9 +148,7 @@ describe('api/projects', () => { await fetchProject(accountId, projectNameIllegalChars); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `developer/projects/v1/by-name/${encodeURIComponent( - projectNameIllegalChars - )}`, + url: `developer/projects/v1/by-name/${encodeURIComponent(projectNameIllegalChars)}`, }); }); }); @@ -187,9 +183,7 @@ describe('api/projects', () => { await downloadProject(accountId, projectNameIllegalChars, buildId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent( - projectNameIllegalChars - )}/builds/${buildId}/archive-full`, + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/${buildId}/archive-full`, responseType: 'arraybuffer', headers: { accept: 'application/zip', @@ -212,9 +206,7 @@ describe('api/projects', () => { await deleteProject(accountId, projectNameIllegalChars); expect(http.delete).toHaveBeenCalledTimes(1); expect(http.delete).toHaveBeenCalledWith(accountId, { - url: `developer/projects/v1/${encodeURIComponent( - projectNameIllegalChars - )}`, + url: `developer/projects/v1/${encodeURIComponent(projectNameIllegalChars)}`, }); }); }); @@ -246,9 +238,7 @@ describe('api/projects', () => { await fetchProjectBuilds(accountId, projectNameIllegalChars); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent( - projectNameIllegalChars - )}/builds`, + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds`, params: {}, }); }); @@ -268,9 +258,7 @@ describe('api/projects', () => { await getBuildStatus(accountId, projectNameIllegalChars, buildId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent( - projectNameIllegalChars - )}/builds/${buildId}/status`, + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/${buildId}/status`, }); }); }); @@ -289,9 +277,7 @@ describe('api/projects', () => { await getBuildStructure(accountId, projectNameIllegalChars, buildId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/builds/by-project-name/${encodeURIComponent( - projectNameIllegalChars - )}/builds/${buildId}/structure`, + url: `dfs/v1/builds/by-project-name/${encodeURIComponent(projectNameIllegalChars)}/builds/${buildId}/structure`, }); }); }); @@ -325,9 +311,7 @@ describe('api/projects', () => { await getDeployStatus(accountId, projectNameIllegalChars, deployId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/deploy/v1/deploy-status/projects/${encodeURIComponent( - projectNameIllegalChars - )}/deploys/${deployId}`, + url: `dfs/deploy/v1/deploy-status/projects/${encodeURIComponent(projectNameIllegalChars)}/deploys/${deployId}`, }); }); }); @@ -346,9 +330,7 @@ describe('api/projects', () => { await getDeployStructure(accountId, projectNameIllegalChars, deployId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/deploy/v1/deploys/by-project-name/${encodeURIComponent( - projectNameIllegalChars - )}/deploys/${deployId}/structure`, + url: `dfs/deploy/v1/deploys/by-project-name/${encodeURIComponent(projectNameIllegalChars)}/deploys/${deployId}/structure`, }); }); }); @@ -366,9 +348,7 @@ describe('api/projects', () => { await fetchProjectSettings(accountId, projectNameIllegalChars); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `developer/projects/v1/${encodeURIComponent( - projectNameIllegalChars - )}/settings`, + url: `developer/projects/v1/${encodeURIComponent(projectNameIllegalChars)}/settings`, }); }); }); @@ -390,9 +370,7 @@ describe('api/projects', () => { await provisionBuild(accountId, projectNameIllegalChars); expect(http.post).toHaveBeenCalledTimes(1); expect(http.post).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent( - projectNameIllegalChars - )}/builds/staged/provision`, + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/provision`, params: {}, headers: { 'Content-Type': 'application/json' }, timeout: 50_000, @@ -416,9 +394,7 @@ describe('api/projects', () => { await queueBuild(accountId, projectNameIllegalChars); expect(http.post).toHaveBeenCalledTimes(1); expect(http.post).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent( - projectNameIllegalChars - )}/builds/staged/queue`, + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/queue`, params: {}, headers: { 'Content-Type': 'application/json' }, }); @@ -432,9 +408,7 @@ describe('api/projects', () => { await uploadFileToBuild(accountId, projectName, filePath, path); expect(http.put).toHaveBeenCalledTimes(1); expect(http.put).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${projectName}/builds/staged/files/${encodeURIComponent( - path - )}`, + url: `dfs/v1/projects/${projectName}/builds/staged/files/${encodeURIComponent(path)}`, data: { file: formData }, headers: { 'Content-Type': 'multipart/form-data' }, }); @@ -451,9 +425,7 @@ describe('api/projects', () => { ); expect(http.put).toHaveBeenCalledTimes(1); expect(http.put).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent( - projectNameIllegalChars - )}/builds/staged/files/${encodeURIComponent(path)}`, + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/files/${encodeURIComponent(path)}`, data: { file: formData }, headers: { 'Content-Type': 'multipart/form-data' }, }); @@ -466,9 +438,7 @@ describe('api/projects', () => { await deleteFileFromBuild(accountId, projectName, path); expect(http.delete).toHaveBeenCalledTimes(1); expect(http.delete).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${projectName}/builds/staged/files/${encodeURIComponent( - path - )}`, + url: `dfs/v1/projects/${projectName}/builds/staged/files/${encodeURIComponent(path)}`, }); }); @@ -477,9 +447,7 @@ describe('api/projects', () => { await deleteFileFromBuild(accountId, projectNameIllegalChars, filePath); expect(http.delete).toHaveBeenCalledTimes(1); expect(http.delete).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent( - projectNameIllegalChars - )}/builds/staged/files/${encodeURIComponent(filePath)}`, + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/files/${encodeURIComponent(filePath)}`, }); }); }); @@ -498,9 +466,7 @@ describe('api/projects', () => { await cancelStagedBuild(accountId, projectNameIllegalChars); expect(http.post).toHaveBeenCalledTimes(1); expect(http.post).toHaveBeenCalledWith(accountId, { - url: `dfs/v1/projects/${encodeURIComponent( - projectNameIllegalChars - )}/builds/staged/cancel`, + url: `dfs/v1/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/staged/cancel`, headers: { 'Content-Type': 'application/json' }, }); }); @@ -520,9 +486,7 @@ describe('api/projects', () => { await fetchBuildWarnLogs(accountId, projectNameIllegalChars, buildId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/logging/v1/logs/projects/${encodeURIComponent( - projectNameIllegalChars - )}/builds/${buildId}/combined/warn`, + url: `dfs/logging/v1/logs/projects/${encodeURIComponent(projectNameIllegalChars)}/builds/${buildId}/combined/warn`, }); }); }); @@ -541,9 +505,7 @@ describe('api/projects', () => { await fetchDeployWarnLogs(accountId, projectNameIllegalChars, deployId); expect(http.get).toHaveBeenCalledTimes(1); expect(http.get).toHaveBeenCalledWith(accountId, { - url: `dfs/logging/v1/logs/projects/${encodeURIComponent( - projectNameIllegalChars - )}/deploys/${deployId}/combined/warn`, + url: `dfs/logging/v1/logs/projects/${encodeURIComponent(projectNameIllegalChars)}/deploys/${deployId}/combined/warn`, }); }); }); diff --git a/api/projects.ts b/api/projects.ts index 7908a602..2e06b6c2 100644 --- a/api/projects.ts +++ b/api/projects.ts @@ -76,9 +76,7 @@ export async function fetchProject( projectName: string ): Promise { return http.get(accountId, { - url: `${DEVELOPER_PROJECTS_API_PATH}/by-name/${encodeURIComponent( - projectName - )}`, + url: `${DEVELOPER_PROJECTS_API_PATH}/by-name/${encodeURIComponent(projectName)}`, }); } @@ -200,9 +198,7 @@ export async function fetchProjectSettings( projectName: string ): Promise { return http.get(accountId, { - url: `${DEVELOPER_PROJECTS_API_PATH}/${encodeURIComponent( - projectName - )}/settings`, + url: `${DEVELOPER_PROJECTS_API_PATH}/${encodeURIComponent(projectName)}/settings`, }); } From 8320c6fae11a8fee6e8a5de1069d4640ae12f99c Mon Sep 17 00:00:00 2001 From: Camden Phalen Date: Fri, 30 Aug 2024 14:56:57 -0400 Subject: [PATCH 23/25] v1.12.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8ca75282..38a733c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hubspot/local-dev-lib", - "version": "1.11.0", + "version": "1.12.0", "description": "Provides library functionality for HubSpot local development tooling, including the HubSpot CLI", "main": "lib/index.js", "repository": { From 0285d58039da2289b263e5c3717810530fcd1fc3 Mon Sep 17 00:00:00 2001 From: Camden Phalen Date: Wed, 4 Sep 2024 16:54:18 -0400 Subject: [PATCH 24/25] Add fetchEnabledFeatures --- api/localDevAuth.ts | 12 +++++++++++- types/Accounts.ts | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/api/localDevAuth.ts b/api/localDevAuth.ts index 4b28a8d2..370962fa 100644 --- a/api/localDevAuth.ts +++ b/api/localDevAuth.ts @@ -2,7 +2,11 @@ import { getAxiosConfig } from '../http/getAxiosConfig'; import http from '../http'; import { ENVIRONMENTS } from '../constants/environments'; import { Environment } from '../types/Config'; -import { ScopeData, AccessTokenResponse } from '../types/Accounts'; +import { + ScopeData, + AccessTokenResponse, + EnabledFeaturesResponse, +} from '../types/Accounts'; import axios from 'axios'; import { PublicAppInstallationData } from '../types/Apps'; @@ -59,3 +63,9 @@ export async function fetchAppInstallationData( }, }); } + +export async function fetchEnabledFeatures(accountId: number) { + return http.get(accountId, { + url: `${LOCALDEVAUTH_API_AUTH_PATH}/enabled-features`, + }); +} diff --git a/types/Accounts.ts b/types/Accounts.ts index 919fe260..0bcb1301 100644 --- a/types/Accounts.ts +++ b/types/Accounts.ts @@ -140,6 +140,10 @@ export type AccessTokenResponse = { accountType: ValueOf; }; +export type EnabledFeaturesResponse = { + enabledFeatures?: { [key: string]: number }; +}; + export type UpdateAccountConfigOptions = Partial & { environment?: Environment; From dd6a1a37507f00a830f174b1bd285cf9d9b5f1a5 Mon Sep 17 00:00:00 2001 From: Camden Phalen Date: Thu, 5 Sep 2024 15:49:40 -0400 Subject: [PATCH 25/25] fix EnabledFeaturesResponse type --- types/Accounts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/Accounts.ts b/types/Accounts.ts index 0bcb1301..948c8a7f 100644 --- a/types/Accounts.ts +++ b/types/Accounts.ts @@ -141,7 +141,7 @@ export type AccessTokenResponse = { }; export type EnabledFeaturesResponse = { - enabledFeatures?: { [key: string]: number }; + enabledFeatures: { [key: string]: boolean }; }; export type UpdateAccountConfigOptions =