diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index b9470778..00000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -dist/ diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 46d0a325..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,38 +0,0 @@ -module.exports = { - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], - root: true, - env: { - browser: false, - node: true, - commonjs: true, - es6: true, - jest: true, - }, - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2022, - }, - plugins: ['@typescript-eslint', 'import'], - rules: { - 'no-console': 'off', - 'no-return-await': 'error', - '@typescript-eslint/no-non-null-assertion': 'off', - 'import/no-default-export': 'error', - }, - overrides: [ - { - files: ['**/__tests__/**/*.ts', '**/__mocks__/**/*.ts'], - env: { - jest: true, - node: true, - }, - }, - { - files: ['acceptance-tests/tests/**/*.ts'], - env: { - jasmine: true, - node: true, - }, - }, - ], -}; diff --git a/api/__tests__/devSecrets.test.ts b/api/__tests__/devSecrets.test.ts index a7b2b09a..13a0f8bc 100644 --- a/api/__tests__/devSecrets.test.ts +++ b/api/__tests__/devSecrets.test.ts @@ -1,16 +1,18 @@ -jest.mock('../../http'); -import { http } from '../../http'; +import { http } from '../../http/index.js'; +import { vi, type MockedFunction } from 'vitest'; import { addAppSecret, updateAppSecret, deleteAppSecret, fetchAppSecrets, -} from '../devSecrets'; +} from '../devSecrets.js'; -const httpPostMock = http.post as jest.MockedFunction; -const httpPatchMock = http.patch as jest.MockedFunction; -const httpDeleteMock = http.delete as jest.MockedFunction; -const httpGetMock = http.get as jest.MockedFunction; +vi.mock('../../http'); + +const httpPostMock = http.post as MockedFunction; +const httpPatchMock = http.patch as MockedFunction; +const httpDeleteMock = http.delete as MockedFunction; +const httpGetMock = http.get as MockedFunction; describe('api/devSecrets', () => { const accountId = 123; diff --git a/api/__tests__/fileMapper.test.ts b/api/__tests__/fileMapper.test.ts index f36dcc4c..02b72f20 100644 --- a/api/__tests__/fileMapper.test.ts +++ b/api/__tests__/fileMapper.test.ts @@ -1,5 +1,5 @@ -import fileStreamResponse from './fixtures/fileStreamResponse.json'; -import { createFileMapperNodeFromStreamResponse } from '../fileMapper'; +import fileStreamResponse from './fixtures/fileStreamResponse.json' with { type: 'json' }; +import { createFileMapperNodeFromStreamResponse } from '../fileMapper.js'; describe('api/fileMapper', () => { describe('createFileMapperNodeFromStreamResponse()', () => { diff --git a/api/__tests__/projects.test.ts b/api/__tests__/projects.test.ts index 76718727..c041b955 100644 --- a/api/__tests__/projects.test.ts +++ b/api/__tests__/projects.test.ts @@ -1,7 +1,8 @@ -jest.mock('../../http'); -jest.mock('fs'); +vi.mock('../../http'); +vi.mock('fs'); import { createReadStream } from 'fs'; -import { http } from '../../http'; +import { http } from '../../http/index.js'; +import { vi, type MockedFunction } from 'vitest'; import { cancelStagedBuild, checkCloneStatus, @@ -30,13 +31,13 @@ import { queueBuild, uploadFileToBuild, uploadProject, -} from '../projects'; +} from '../projects.js'; -const createReadStreamMock = createReadStream as jest.MockedFunction< +const createReadStreamMock = createReadStream as MockedFunction< typeof createReadStream >; -const httpPostMock = http.post as jest.MockedFunction; +const httpPostMock = http.post as MockedFunction; describe('api/projects', () => { const accountId = 999999; diff --git a/api/appsDev.ts b/api/appsDev.ts index 40141b88..7abd64ef 100644 --- a/api/appsDev.ts +++ b/api/appsDev.ts @@ -1,11 +1,11 @@ -import { http } from '../http'; +import { http } from '../http/index.js'; import { PublicApp, PublicAppInstallCounts, PublicAppDeveloperTestAccountInstallData, FetchPublicAppsForPortalResponse, -} from '../types/Apps'; -import { HubSpotPromise } from '../types/Http'; +} from '../types/Apps.js'; +import { HubSpotPromise } from '../types/Http.js'; const APPS_DEV_API_PATH = 'apps-dev/external/public/v3'; const APPS_HUBLETS_API_PATH = 'apps-hublets/external/static-token/v3'; diff --git a/api/crm.ts b/api/crm.ts index d6c095cc..f7faf437 100644 --- a/api/crm.ts +++ b/api/crm.ts @@ -2,10 +2,10 @@ import path from 'path'; import fs from 'fs-extra'; import FormData from 'form-data'; -import { http } from '../http'; -import { getCwd } from '../lib/path'; -import { HubSpotPromise } from '../types/Http'; -import { ImportRequest, ImportResponse } from '../types/Crm'; +import { http } from '../http/index.js'; +import { getCwd } from '../lib/path.js'; +import { HubSpotPromise } from '../types/Http.js'; +import { ImportRequest, ImportResponse } from '../types/Crm.js'; const HUBSPOT_CRM_IMPORT_PATH = '/crm/v3/imports'; diff --git a/api/customObjects.ts b/api/customObjects.ts index 49cd0eb2..6e0f0cc2 100644 --- a/api/customObjects.ts +++ b/api/customObjects.ts @@ -1,12 +1,12 @@ -import { http } from '../http'; +import { http } from '../http/index.js'; import { FetchSchemasResponse, Schema, SchemaDefinition, ObjectDefinition, CreateObjectsResponse, -} from '../types/Schemas'; -import { HubSpotPromise } from '../types/Http'; +} from '../types/Schemas.js'; +import { HubSpotPromise } from '../types/Http.js'; const CUSTOM_OBJECTS_API_PATH = 'crm/v3/objects'; const SCHEMA_API_PATH = 'crm-object-schemas/v3/schemas'; diff --git a/api/designManager.ts b/api/designManager.ts index 245241e5..477b1bba 100644 --- a/api/designManager.ts +++ b/api/designManager.ts @@ -1,9 +1,9 @@ -import { http } from '../http'; -import { HubSpotPromise, QueryParams } from '../types/Http'; +import { http } from '../http/index.js'; +import { HubSpotPromise, QueryParams } from '../types/Http.js'; import { FetchThemesResponse, FetchBuiltinMappingResponse, -} from '../types/DesignManager'; +} from '../types/DesignManager.js'; const DESIGN_MANAGER_API_PATH = 'designmanager/v1'; diff --git a/api/devSecrets.ts b/api/devSecrets.ts index 77763105..81fa7f18 100644 --- a/api/devSecrets.ts +++ b/api/devSecrets.ts @@ -1,6 +1,6 @@ -import { http } from '../http'; -import { HubSpotPromise } from '../types/Http'; -import { FetchDevSecretsResponse } from '../types/DevSecrets'; +import { http } from '../http/index.js'; +import { HubSpotPromise } from '../types/Http.js'; +import { FetchDevSecretsResponse } from '../types/DevSecrets.js'; const DEV_SECRETS_API_PATH = 'dev-secrets/management/v3'; diff --git a/api/developerTestAccounts.ts b/api/developerTestAccounts.ts index 4c91abd3..acd4419a 100644 --- a/api/developerTestAccounts.ts +++ b/api/developerTestAccounts.ts @@ -1,7 +1,7 @@ import axios from 'axios'; -import { http } from '../http'; -import { getAxiosConfig } from '../http/getAxiosConfig'; -import { ENVIRONMENTS } from '../constants/environments'; +import { http } from '../http/index.js'; +import { getAxiosConfig } from '../http/getAxiosConfig.js'; +import { ENVIRONMENTS } from '../constants/environments.js'; import { DeveloperTestAccount, CreateDeveloperTestAccountResponse, @@ -11,10 +11,10 @@ import { InstallOauthAppIntoDeveloperTestAccountResponse, TestPortalStatusResponse, GenerateDeveloperTestAccountPersonalAccessKeyResponse, -} from '../types/developerTestAccounts'; -import { SANDBOX_TIMEOUT } from '../constants/api'; -import { Environment } from '../types/Config'; -import { HubSpotPromise } from '../types/Http'; +} from '../types/developerTestAccounts.js'; +import { SANDBOX_TIMEOUT } from '../constants/api.js'; +import { Environment } from '../types/Config.js'; +import { HubSpotPromise } from '../types/Http.js'; const TEST_ACCOUNTS_API_PATH = 'integrators/test-portals/v2'; const TEST_ACCOUNTS_API_PATH_V3 = 'integrators/test-portals/v3'; diff --git a/api/fileManager.ts b/api/fileManager.ts index 0bac5e60..7970f904 100644 --- a/api/fileManager.ts +++ b/api/fileManager.ts @@ -1,13 +1,13 @@ import fs from 'fs'; import path from 'path'; -import { http } from '../http'; -import { FormData, HubSpotPromise } from '../types/Http'; +import { http } from '../http/index.js'; +import { FormData, HubSpotPromise } from '../types/Http.js'; import { FetchStatResponse, FetchFilesResponse, FetchFolderResponse, UploadResponse, -} from '../types/FileManager'; +} from '../types/FileManager.js'; const FILE_MANAGER_V2_API_PATH = 'filemanager/api/v2'; const FILE_MANAGER_V3_API_PATH = 'filemanager/api/v3'; diff --git a/api/fileMapper.ts b/api/fileMapper.ts index 002068f6..f903f61e 100644 --- a/api/fileMapper.ts +++ b/api/fileMapper.ts @@ -2,10 +2,10 @@ import fs from 'fs'; import path from 'path'; import contentDisposition from 'content-disposition'; import { AxiosResponse } from 'axios'; -import { http } from '../http'; -import { getCwd } from '../lib/path'; -import { FileMapperNode, FileMapperOptions, FileTree } from '../types/Files'; -import { HubSpotPromise } from '../types/Http'; +import { http } from '../http/index.js'; +import { getCwd } from '../lib/path.js'; +import { FileMapperNode, FileMapperOptions, FileTree } from '../types/Files.js'; +import { HubSpotPromise } from '../types/Http.js'; export const FILE_MAPPER_API_PATH = 'content/filemapper/v1'; diff --git a/api/fileTransport.ts b/api/fileTransport.ts index 6d9a3ec9..5ae390be 100644 --- a/api/fileTransport.ts +++ b/api/fileTransport.ts @@ -1,8 +1,8 @@ import fs from 'fs'; import path from 'path'; -import { getCwd } from '../lib/path'; -import { http } from '../http'; -import { HubSpotPromise } from '../types/Http'; +import { getCwd } from '../lib/path.js'; +import { http } from '../http/index.js'; +import { HubSpotPromise } from '../types/Http.js'; const HUBFILES_API_PATH = '/file-transport/v1/hubfiles'; export function createSchemaFromHubFile( diff --git a/api/fireAlarm.ts b/api/fireAlarm.ts index 4bb70ef1..a1cb51bc 100644 --- a/api/fireAlarm.ts +++ b/api/fireAlarm.ts @@ -1,6 +1,6 @@ -import { http } from '../http'; -import { FireAlarm } from '../types/FireAlarm'; -import { HubSpotPromise } from '../types/Http'; +import { http } from '../http/index.js'; +import { FireAlarm } from '../types/FireAlarm.js'; +import { HubSpotPromise } from '../types/Http.js'; const FIREALARM_API_AUTH_PATH = 'firealarm/v4/alarm'; diff --git a/api/functions.ts b/api/functions.ts index f54e27c7..e70616f3 100644 --- a/api/functions.ts +++ b/api/functions.ts @@ -1,11 +1,11 @@ -import { http } from '../http'; -import { HubSpotPromise, QueryParams } from '../types/Http'; +import { http } from '../http/index.js'; +import { HubSpotPromise, QueryParams } from '../types/Http.js'; import { GetBuildStatusResponse, FunctionLog, GetRoutesResponse, GetFunctionLogsResponse, -} from '../types/Functions'; +} from '../types/Functions.js'; const FUNCTION_API_PATH = 'cms/v3/functions'; diff --git a/api/github.ts b/api/github.ts index 92394c46..e9335cc7 100644 --- a/api/github.ts +++ b/api/github.ts @@ -1,14 +1,17 @@ import axios, { ResponseType } from 'axios'; -import { getDefaultUserAgentHeader } from '../http/getAxiosConfig'; -import { GithubReleaseData, GithubRepoFile, RepoPath } from '../types/Github'; -import { HubSpotPromise } from '../types/Http'; -import { isSpecifiedError } from '../errors'; +import { getDefaultUserAgentHeader } from '../http/getAxiosConfig.js'; +import { + GithubReleaseData, + GithubRepoFile, + RepoPath, +} from '../types/Github.js'; +import { HubSpotPromise } from '../types/Http.js'; +import { isSpecifiedError } from '../errors/index.js'; const GITHUB_REPOS_API = 'https://api.github.com/repos'; const GITHUB_RAW_CONTENT_API_PATH = 'https://raw.githubusercontent.com'; declare global { - // eslint-disable-next-line no-var var githubToken: string; } diff --git a/api/hubdb.ts b/api/hubdb.ts index b010b533..dfd3e11c 100644 --- a/api/hubdb.ts +++ b/api/hubdb.ts @@ -1,5 +1,5 @@ -import { http } from '../http'; -import { HubSpotPromise, QueryParams } from '../types/Http'; +import { http } from '../http/index.js'; +import { HubSpotPromise, QueryParams } from '../types/Http.js'; import { CreateRowsResponse, FetchRowsResponse, @@ -7,7 +7,7 @@ import { Schema, Table, FetchTablesResponse, -} from '../types/Hubdb'; +} from '../types/Hubdb.js'; const HUBDB_API_PATH = 'cms/v3/hubdb'; diff --git a/api/lighthouseScore.ts b/api/lighthouseScore.ts index 60fe0350..ac91608a 100644 --- a/api/lighthouseScore.ts +++ b/api/lighthouseScore.ts @@ -1,9 +1,9 @@ -import { http } from '../http'; -import { Data, HubSpotPromise, QueryParams } from '../types/Http'; +import { http } from '../http/index.js'; +import { Data, HubSpotPromise, QueryParams } from '../types/Http.js'; import { GetLighthouseScoreResponse, RequestLighthouseScoreResponse, -} from '../types/Lighthouse'; +} from '../types/Lighthouse.js'; const LIGHTHOUSE_SCORE_API_BASE = 'quality-engine/v1/lighthouse'; diff --git a/api/localDevAuth.ts b/api/localDevAuth.ts index 96ba86d9..3aaf1112 100644 --- a/api/localDevAuth.ts +++ b/api/localDevAuth.ts @@ -1,16 +1,16 @@ -import { getAxiosConfig } from '../http/getAxiosConfig'; -import { http } from '../http'; -import { ENVIRONMENTS } from '../constants/environments'; -import { Environment } from '../types/Config'; +import { getAxiosConfig } from '../http/getAxiosConfig.js'; +import { http } from '../http/index.js'; +import { ENVIRONMENTS } from '../constants/environments.js'; +import { Environment } from '../types/Config.js'; import { ScopeData, AccessTokenResponse, EnabledFeaturesResponse, ScopeAuthorizationResponse, -} from '../types/Accounts'; +} from '../types/Accounts.js'; import axios from 'axios'; -import { PublicAppInstallationData } from '../types/Apps'; -import { HubSpotPromise } from '../types/Http'; +import { PublicAppInstallationData } from '../types/Apps.js'; +import { HubSpotPromise } from '../types/Http.js'; const LOCALDEVAUTH_API_AUTH_PATH = 'localdevauth/v1/auth'; diff --git a/api/marketplaceValidation.ts b/api/marketplaceValidation.ts index 7216493c..2156d292 100644 --- a/api/marketplaceValidation.ts +++ b/api/marketplaceValidation.ts @@ -1,6 +1,6 @@ -import { http } from '../http'; -import { Data, HubSpotPromise, QueryParams } from '../types/Http'; -import { GetValidationResultsResponse } from '../types/MarketplaceValidation'; +import { http } from '../http/index.js'; +import { Data, HubSpotPromise, QueryParams } from '../types/Http.js'; +import { GetValidationResultsResponse } from '../types/MarketplaceValidation.js'; const VALIDATION_API_BASE = 'quality-engine/v1/validation'; diff --git a/api/projects.ts b/api/projects.ts index 4d53989a..ea17ce3a 100644 --- a/api/projects.ts +++ b/api/projects.ts @@ -1,6 +1,6 @@ -import { http } from '../http'; +import { http } from '../http/index.js'; import fs from 'fs'; -import { FormData, HubSpotPromise, QueryParams } from '../types/Http'; +import { FormData, HubSpotPromise, QueryParams } from '../types/Http.js'; import { Project, FetchProjectResponse, @@ -9,18 +9,18 @@ import { FetchPlatformVersionResponse, WarnLogsResponse, UploadIRResponse, -} from '../types/Project'; -import { Build, FetchProjectBuildsResponse } from '../types/Build'; +} from '../types/Project.js'; +import { Build, FetchProjectBuildsResponse } from '../types/Build.js'; import { ComponentStructureResponse, ProjectComponentsMetadata, -} from '../types/ComponentStructure'; -import { Deploy, ProjectDeployResponse } from '../types/Deploy'; +} from '../types/ComponentStructure.js'; +import { Deploy, ProjectDeployResponse } from '../types/Deploy.js'; import { MigrateAppResponse, CloneAppResponse, PollAppResponse, -} from '../types/Migration'; +} from '../types/Migration.js'; const PROJECTS_API_PATH = 'dfs/v1/projects'; const DEVELOPER_FILE_SYSTEM_PATH = 'dfs/v1'; diff --git a/api/sandboxHubs.ts b/api/sandboxHubs.ts index 82225d57..0ede8b12 100644 --- a/api/sandboxHubs.ts +++ b/api/sandboxHubs.ts @@ -1,17 +1,17 @@ import axios, { AxiosPromise } from 'axios'; -import { http } from '../http'; -import { getAxiosConfig } from '../http/getAxiosConfig'; -import { ENVIRONMENTS } from '../constants/environments'; -import { SANDBOX_TIMEOUT } from '../constants/api'; -import { Environment } from '../types/Config'; +import { http } from '../http/index.js'; +import { getAxiosConfig } from '../http/getAxiosConfig.js'; +import { ENVIRONMENTS } from '../constants/environments.js'; +import { SANDBOX_TIMEOUT } from '../constants/api.js'; +import { Environment } from '../types/Config.js'; import { SandboxPersonalAccessKey, SandboxHubData, SandboxResponse, SandboxUsageLimitsResponse, V2Sandbox, -} from '../types/Sandbox'; -import { HubSpotPromise } from '../types/Http'; +} from '../types/Sandbox.js'; +import { HubSpotPromise } from '../types/Http.js'; const SANDBOX_API_PATH = 'sandbox-hubs/v1'; const SANDBOX_API_PATH_V2 = 'sandbox-hubs/v2'; diff --git a/api/sandboxSync.ts b/api/sandboxSync.ts index 6878c066..fb364972 100644 --- a/api/sandboxSync.ts +++ b/api/sandboxSync.ts @@ -1,11 +1,11 @@ -import { http } from '../http'; +import { http } from '../http/index.js'; import { InitiateSyncResponse, FetchTypesResponse, TaskRequestData, -} from '../types/Sandbox'; -import { SANDBOX_TIMEOUT } from '../constants/api'; -import { HubSpotPromise } from '../types/Http'; +} from '../types/Sandbox.js'; +import { SANDBOX_TIMEOUT } from '../constants/api.js'; +import { HubSpotPromise } from '../types/Http.js'; const SANDBOXES_SYNC_API_PATH = 'sandboxes-sync/v1'; export async function initiateSync( diff --git a/api/secrets.ts b/api/secrets.ts index a1d7c66e..446c8068 100644 --- a/api/secrets.ts +++ b/api/secrets.ts @@ -1,6 +1,6 @@ -import { http } from '../http'; -import { FetchSecretsResponse } from '../types/Secrets'; -import { HubSpotPromise } from '../types/Http'; +import { http } from '../http/index.js'; +import { FetchSecretsResponse } from '../types/Secrets.js'; +import { HubSpotPromise } from '../types/Http.js'; const SECRETS_API_PATH = 'cms/v3/functions/secrets'; diff --git a/api/validateHubl.ts b/api/validateHubl.ts index 15bac8d9..716e3268 100644 --- a/api/validateHubl.ts +++ b/api/validateHubl.ts @@ -1,6 +1,6 @@ -import { http } from '../http'; -import { Validation, HublValidationOptions } from '../types/HublValidation'; -import { HubSpotPromise } from '../types/Http'; +import { http } from '../http/index.js'; +import { Validation, HublValidationOptions } from '../types/HublValidation.js'; +import { HubSpotPromise } from '../types/Http.js'; const HUBL_VALIDATE_API_PATH = 'cos-rendering/v1/internal/validate'; diff --git a/config/CLIConfiguration.ts b/config/CLIConfiguration.ts index 5bd1b8c3..56be090e 100644 --- a/config/CLIConfiguration.ts +++ b/config/CLIConfiguration.ts @@ -1,37 +1,37 @@ import fs from 'fs'; import findup from 'findup-sync'; -import { getCwd } from '../lib/path'; -import { logger } from '../lib/logger'; -import { loadConfigFromEnvironment } from './environment'; -import { getValidEnv } from '../lib/environment'; +import { getCwd } from '../lib/path.js'; +import { logger } from '../lib/logger.js'; +import { loadConfigFromEnvironment } from './environment.js'; +import { getValidEnv } from '../lib/environment.js'; import { loadConfigFromFile, writeConfigToFile, configFileExists, configFileIsBlank, deleteConfigFile, -} from './configFile'; -import { commaSeparatedValues } from '../lib/text'; -import { ENVIRONMENTS } from '../constants/environments'; -import { API_KEY_AUTH_METHOD } from '../constants/auth'; +} from './configFile.js'; +import { commaSeparatedValues } from '../lib/text.js'; +import { ENVIRONMENTS } from '../constants/environments.js'; +import { API_KEY_AUTH_METHOD } from '../constants/auth.js'; import { HUBSPOT_ACCOUNT_TYPES, MIN_HTTP_TIMEOUT, DEFAULT_ACCOUNT_OVERRIDE_FILE_NAME, DEFAULT_ACCOUNT_OVERRIDE_ERROR_INVALID_ID, DEFAULT_ACCOUNT_OVERRIDE_ERROR_ACCOUNT_NOT_FOUND, -} from '../constants/config'; -import { CMS_PUBLISH_MODE } from '../constants/files'; -import { CLIConfig_NEW, Environment } from '../types/Config'; +} from '../constants/config.js'; +import { CMS_PUBLISH_MODE } from '../constants/files.js'; +import { CLIConfig_NEW, Environment } from '../types/Config.js'; import { CLIAccount_NEW, OAuthAccount_NEW, FlatAccountFields_NEW, AccountType, -} from '../types/Accounts'; -import { CLIOptions } from '../types/CLIOptions'; -import { i18n } from '../utils/lang'; -import { CmsPublishMode } from '../types/Files'; +} from '../types/Accounts.js'; +import { CLIOptions } from '../types/CLIOptions.js'; +import { i18n } from '../utils/lang.js'; +import { CmsPublishMode } from '../types/Files.js'; const i18nKey = 'config.cliConfiguration'; @@ -429,7 +429,6 @@ class _CLIConfiguration { // Allow everything except for 'undefined' values to override the existing values function safelyApplyUpdates( fieldName: T, - // eslint-disable-next-line @typescript-eslint/no-explicit-any newValue: FlatAccountFields_NEW[T] ) { if (typeof newValue !== 'undefined') { @@ -455,7 +454,6 @@ class _CLIConfiguration { safelyApplyUpdates('apiKey', apiKey); } if (typeof updatedDefaultCmsPublishMode !== 'undefined') { - // eslint-disable-next-line @typescript-eslint/no-explicit-any safelyApplyUpdates( 'defaultCmsPublishMode', CMS_PUBLISH_MODE[updatedDefaultCmsPublishMode] diff --git a/config/__tests__/CLIConfiguration.test.ts b/config/__tests__/CLIConfiguration.test.ts index 03a61576..803767d7 100644 --- a/config/__tests__/CLIConfiguration.test.ts +++ b/config/__tests__/CLIConfiguration.test.ts @@ -1,6 +1,7 @@ -import { HUBSPOT_ACCOUNT_TYPES } from '../../constants/config'; -import { ENVIRONMENTS } from '../../constants/environments'; -import { CLIConfiguration as config } from '../CLIConfiguration'; +import { HUBSPOT_ACCOUNT_TYPES } from '../../constants/config.js'; +import { ENVIRONMENTS } from '../../constants/environments.js'; +import { CLIConfiguration as config } from '../CLIConfiguration.js'; +import { vi } from 'vitest'; describe('config/CLIConfiguration', () => { afterAll(() => { @@ -161,11 +162,11 @@ describe('config/CLIConfiguration', () => { describe('addLocalStateFlag()', () => { beforeEach(() => { // Mock the write method to prevent actual file operations - jest.spyOn(config, 'write').mockImplementation(() => null); + vi.spyOn(config, 'write').mockImplementation(() => null); }); afterEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); }); it('throws when no config is loaded', () => { @@ -195,11 +196,11 @@ describe('config/CLIConfiguration', () => { describe('removeLocalStateFlag()', () => { beforeEach(() => { // Mock the write method to prevent actual file operations - jest.spyOn(config, 'write').mockImplementation(() => null); + vi.spyOn(config, 'write').mockImplementation(() => null); }); afterEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); }); it('throws when no config is loaded', () => { diff --git a/config/__tests__/config.test.ts b/config/__tests__/config.test.ts index 541ec298..45fe7108 100644 --- a/config/__tests__/config.test.ts +++ b/config/__tests__/config.test.ts @@ -15,12 +15,12 @@ import { setConfigPath, createEmptyConfigFile, configFileExists, -} from '../index'; -import { getAccountIdentifier } from '../getAccountIdentifier'; -import { getAccounts, getDefaultAccount } from '../../utils/accounts'; -import { ENVIRONMENTS } from '../../constants/environments'; -import { HUBSPOT_ACCOUNT_TYPES } from '../../constants/config'; -import { CLIConfig, CLIConfig_DEPRECATED } from '../../types/Config'; +} from '../index.js'; +import { getAccountIdentifier } from '../getAccountIdentifier.js'; +import { getAccounts, getDefaultAccount } from '../../utils/accounts.js'; +import { ENVIRONMENTS } from '../../constants/environments.js'; +import { HUBSPOT_ACCOUNT_TYPES } from '../../constants/config.js'; +import { CLIConfig, CLIConfig_DEPRECATED } from '../../types/Config.js'; import { APIKeyAccount_DEPRECATED, AuthType, @@ -30,9 +30,10 @@ import { APIKeyAccount, PersonalAccessKeyAccount, PersonalAccessKeyAccount_DEPRECATED, -} from '../../types/Accounts'; -import * as configFile from '../configFile'; -import * as config_DEPRECATED from '../config_DEPRECATED'; +} from '../../types/Accounts.js'; +import * as configFile from '../configFile.js'; +import * as config_DEPRECATED from '../config_DEPRECATED.js'; +import { vi, MockInstance } from 'vitest'; const CONFIG_PATHS = { none: null, @@ -44,18 +45,18 @@ const CONFIG_PATHS = { let mockedConfigPath: string | null = CONFIG_PATHS.default; -jest.mock('findup-sync', () => { - return jest.fn(() => mockedConfigPath); -}); +vi.mock('findup-sync', () => ({ + default: vi.fn(() => mockedConfigPath), +})); -jest.mock('../../lib/logger'); +vi.mock('../../lib/logger'); -const fsReadFileSyncSpy = jest.spyOn(fs, 'readFileSync'); -const fsWriteFileSyncSpy = jest.spyOn(fs, 'writeFileSync'); +const fsReadFileSyncSpy = vi.spyOn(fs, 'readFileSync'); +const fsWriteFileSyncSpy = vi.spyOn(fs, 'writeFileSync'); -jest.mock('../configFile', () => ({ - getConfigFilePath: jest.fn(), - configFileExists: jest.fn(), +vi.mock('../configFile', () => ({ + getConfigFilePath: vi.fn(), + configFileExists: vi.fn(), })); const API_KEY_CONFIG: APIKeyAccount_DEPRECATED = { @@ -114,8 +115,8 @@ function getAccountByAuthType( describe('config/config', () => { const globalConsole = global.console; beforeAll(() => { - global.console.error = jest.fn(); - global.console.debug = jest.fn(); + global.console.error = vi.fn(); + global.console.debug = vi.fn(); }); afterAll(() => { global.console = globalConsole; @@ -236,20 +237,20 @@ describe('config/config', () => { describe('deleteEmptyConfigFile()', () => { it('does not delete config file if there are contents', () => { - jest - .spyOn(fs, 'readFileSync') - .mockImplementation(() => 'defaultPortal: "test"'); - jest.spyOn(fs, 'existsSync').mockImplementation(() => true); - fs.unlinkSync = jest.fn(); + vi.spyOn(fs, 'readFileSync').mockImplementation( + () => 'defaultPortal: "test"' + ); + vi.spyOn(fs, 'existsSync').mockImplementation(() => true); + fs.unlinkSync = vi.fn(); deleteEmptyConfigFile(); expect(fs.unlinkSync).not.toHaveBeenCalled(); }); it('deletes config file if empty', () => { - jest.spyOn(fs, 'readFileSync').mockImplementation(() => ''); - jest.spyOn(fs, 'existsSync').mockImplementation(() => true); - fs.unlinkSync = jest.fn(); + vi.spyOn(fs, 'readFileSync').mockImplementation(() => ''); + vi.spyOn(fs, 'existsSync').mockImplementation(() => true); + fs.unlinkSync = vi.fn(); deleteEmptyConfigFile(); expect(fs.unlinkSync).toHaveBeenCalled(); @@ -475,7 +476,7 @@ describe('config/config', () => { }); it('loads a config from file if no combination of environment variables is sufficient', () => { - const readFileSyncSpy = jest.spyOn(fs, 'readFileSync'); + const readFileSyncSpy = vi.spyOn(fs, 'readFileSync'); getAndLoadConfigIfNeeded(); expect(fs.readFileSync).toHaveBeenCalled(); @@ -633,10 +634,10 @@ describe('config/config', () => { }); describe('getConfigPath()', () => { - let fsExistsSyncSpy: jest.SpyInstance; + let fsExistsSyncSpy: MockInstance; beforeAll(() => { - fsExistsSyncSpy = jest.spyOn(fs, 'existsSync').mockImplementation(() => { + fsExistsSyncSpy = vi.spyOn(fs, 'existsSync').mockImplementation(() => { return false; }); }); @@ -647,7 +648,7 @@ describe('config/config', () => { describe('when a standard config is present', () => { it('returns the standard config path when useHiddenConfig is false', () => { - (configFile.getConfigFilePath as jest.Mock).mockReturnValue( + vi.mocked(configFile.getConfigFilePath).mockReturnValue( CONFIG_PATHS.default ); const configPath = getConfigPath('', false); @@ -655,7 +656,7 @@ describe('config/config', () => { }); it('returns the hidden config path when useHiddenConfig is true', () => { - (configFile.getConfigFilePath as jest.Mock).mockReturnValue( + vi.mocked(configFile.getConfigFilePath).mockReturnValue( CONFIG_PATHS.hidden ); const hiddenConfigPath = getConfigPath(undefined, true); @@ -671,7 +672,7 @@ describe('config/config', () => { }); it('returns the hidden config path when useHiddenConfig is true, ignoring the passed path', () => { - (configFile.getConfigFilePath as jest.Mock).mockReturnValue( + vi.mocked(configFile.getConfigFilePath).mockReturnValue( CONFIG_PATHS.hidden ); const hiddenConfigPath = getConfigPath( @@ -688,13 +689,17 @@ describe('config/config', () => { }); it('returns default directory when useHiddenConfig is false', () => { - (configFile.getConfigFilePath as jest.Mock).mockReturnValue(null); + vi.mocked(configFile.getConfigFilePath).mockReturnValue( + null as unknown as string + ); const configPath = getConfigPath(undefined, false); expect(configPath).toBe(CONFIG_PATHS.default); }); it('returns null when useHiddenConfig is true and no hidden config exists', () => { - (configFile.getConfigFilePath as jest.Mock).mockReturnValue(null); + vi.mocked(configFile.getConfigFilePath).mockReturnValue( + null as unknown as string + ); const hiddenConfigPath = getConfigPath(undefined, true); expect(hiddenConfigPath).toBeNull(); }); @@ -703,13 +708,13 @@ describe('config/config', () => { describe('when a non-standard config is present', () => { beforeAll(() => { fsExistsSyncSpy.mockReturnValue(true); - (configFile.getConfigFilePath as jest.Mock).mockReturnValue( + vi.mocked(configFile.getConfigFilePath).mockReturnValue( CONFIG_PATHS.nonStandard ); }); it('returns the hidden config path when useHiddenConfig is true', () => { - (configFile.getConfigFilePath as jest.Mock).mockReturnValue( + vi.mocked(configFile.getConfigFilePath).mockReturnValue( CONFIG_PATHS.hidden ); const hiddenConfigPath = getConfigPath(undefined, true); @@ -720,16 +725,14 @@ describe('config/config', () => { describe('createEmptyConfigFile()', () => { describe('when no config is present', () => { - let fsExistsSyncSpy: jest.SpyInstance; + let fsExistsSyncSpy: MockInstance; beforeEach(() => { setConfigPath(CONFIG_PATHS.none); mockedConfigPath = CONFIG_PATHS.none; - fsExistsSyncSpy = jest - .spyOn(fs, 'existsSync') - .mockImplementation(() => { - return false; - }); + fsExistsSyncSpy = vi.spyOn(fs, 'existsSync').mockImplementation(() => { + return false; + }); }); afterAll(() => { @@ -746,12 +749,12 @@ describe('config/config', () => { }); describe('when a config is present', () => { - let fsExistsSyncAndReturnTrueSpy: jest.SpyInstance; + let fsExistsSyncAndReturnTrueSpy: MockInstance; beforeAll(() => { setConfigPath(CONFIG_PATHS.cwd); mockedConfigPath = CONFIG_PATHS.cwd; - fsExistsSyncAndReturnTrueSpy = jest + fsExistsSyncAndReturnTrueSpy = vi .spyOn(fs, 'existsSync') .mockImplementation(pathToCheck => { if (pathToCheck === CONFIG_PATHS.cwd) { @@ -789,14 +792,14 @@ describe('config/config', () => { }); describe('configFileExists', () => { - let getConfigPathSpy: jest.SpyInstance; + let getConfigPathSpy: MockInstance; beforeAll(() => { - getConfigPathSpy = jest.spyOn(config_DEPRECATED, 'getConfigPath'); + getConfigPathSpy = vi.spyOn(config_DEPRECATED, 'getConfigPath'); }); beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); afterAll(() => { @@ -804,7 +807,7 @@ describe('config/config', () => { }); it('returns true when useHiddenConfig is true and newConfigFileExists returns true', () => { - (configFile.configFileExists as jest.Mock).mockReturnValue(true); + vi.mocked(configFile.configFileExists).mockReturnValue(true); const result = configFileExists(true); @@ -813,7 +816,7 @@ describe('config/config', () => { }); it('returns false when useHiddenConfig is true and newConfigFileExists returns false', () => { - (configFile.configFileExists as jest.Mock).mockReturnValue(false); + vi.mocked(configFile.configFileExists).mockReturnValue(false); const result = configFileExists(true); diff --git a/config/__tests__/configFile.test.ts b/config/__tests__/configFile.test.ts index 32e6f67b..aa82fe68 100644 --- a/config/__tests__/configFile.test.ts +++ b/config/__tests__/configFile.test.ts @@ -10,23 +10,24 @@ import { parseConfig, loadConfigFromFile, writeConfigToFile, -} from '../configFile'; +} from '../configFile.js'; import { HUBSPOT_CONFIGURATION_FILE, HUBSPOT_CONFIGURATION_FOLDER, -} from '../../constants/config'; -import { CLIConfig_NEW } from '../../types/Config'; +} from '../../constants/config.js'; +import { CLIConfig_NEW } from '../../types/Config.js'; +import { vi } from 'vitest'; // fs spy -const existsSyncSpy = jest.spyOn(fs, 'existsSync'); -const readFileSyncSpy = jest.spyOn(fs, 'readFileSync'); -const unlinkSyncSpy = jest.spyOn(fs, 'unlinkSync'); -const ensureFileSyncSpy = jest.spyOn(fs, 'ensureFileSync'); -const writeFileSyncSpy = jest.spyOn(fs, 'writeFileSync'); +const existsSyncSpy = vi.spyOn(fs, 'existsSync'); +const readFileSyncSpy = vi.spyOn(fs, 'readFileSync'); +const unlinkSyncSpy = vi.spyOn(fs, 'unlinkSync'); +const ensureFileSyncSpy = vi.spyOn(fs, 'ensureFileSync'); +const writeFileSyncSpy = vi.spyOn(fs, 'writeFileSync'); // yamp spy -const loadSpy = jest.spyOn(yaml, 'load'); -const dumpSpy = jest.spyOn(yaml, 'dump'); +const loadSpy = vi.spyOn(yaml, 'load'); +const dumpSpy = vi.spyOn(yaml, 'dump'); const CONFIG = { defaultAccount: '', diff --git a/config/__tests__/configUtils.test.ts b/config/__tests__/configUtils.test.ts index bbd6df57..a2b43fbd 100644 --- a/config/__tests__/configUtils.test.ts +++ b/config/__tests__/configUtils.test.ts @@ -2,13 +2,13 @@ import { generateConfig, getOrderedAccount, getOrderedConfig, -} from '../configUtils'; -import { CLIConfig } from '../../types/Config'; +} from '../configUtils.js'; +import { CLIConfig } from '../../types/Config.js'; import { CLIAccount, OAuthAccount, PersonalAccessKeyAccount, -} from '../../types/Accounts'; +} from '../../types/Accounts.js'; const PAK_ACCOUNT: PersonalAccessKeyAccount = { accountId: 111, diff --git a/config/__tests__/environment.test.ts b/config/__tests__/environment.test.ts index 62b8015f..f5fe2732 100644 --- a/config/__tests__/environment.test.ts +++ b/config/__tests__/environment.test.ts @@ -1,13 +1,14 @@ -import { loadConfigFromEnvironment } from '../environment'; -import { ENVIRONMENT_VARIABLES } from '../../constants/environments'; -import { PERSONAL_ACCESS_KEY_AUTH_METHOD } from '../../constants/auth'; +import { loadConfigFromEnvironment } from '../environment.js'; +import { ENVIRONMENT_VARIABLES } from '../../constants/environments.js'; +import { PERSONAL_ACCESS_KEY_AUTH_METHOD } from '../../constants/auth.js'; +import { vi } from 'vitest'; describe('config/environment', () => { describe('loadConfigFromEnvironment()', () => { const INITIAL_ENV = process.env; beforeEach(() => { - jest.resetModules(); + vi.resetModules(); process.env = { ...INITIAL_ENV }; }); diff --git a/config/__tests__/migrate.test.ts b/config/__tests__/migrate.test.ts index 4c868bb1..1c676b54 100644 --- a/config/__tests__/migrate.test.ts +++ b/config/__tests__/migrate.test.ts @@ -1,40 +1,40 @@ -import * as migrate from '../migrate'; -import * as config_DEPRECATED from '../config_DEPRECATED'; -import { CLIConfiguration } from '../CLIConfiguration'; -import * as configIndex from '../index'; -import * as configFile from '../configFile'; -import { CLIConfig_DEPRECATED, CLIConfig_NEW } from '../../types/Config'; -import { ENVIRONMENTS } from '../../constants/environments'; -import { OAUTH_AUTH_METHOD } from '../../constants/auth'; -import { ARCHIVED_HUBSPOT_CONFIG_YAML_FILE_NAME } from '../../constants/config'; -import { i18n } from '../../utils/lang'; +import * as migrate from '../migrate.js'; +import * as config_DEPRECATED from '../config_DEPRECATED.js'; +import { CLIConfiguration } from '../CLIConfiguration.js'; +import * as configIndex from '../index.js'; +import * as configFile from '../configFile.js'; +import { CLIConfig_DEPRECATED, CLIConfig_NEW } from '../../types/Config.js'; +import { ENVIRONMENTS } from '../../constants/environments.js'; +import { OAUTH_AUTH_METHOD } from '../../constants/auth.js'; +import { ARCHIVED_HUBSPOT_CONFIG_YAML_FILE_NAME } from '../../constants/config.js'; +import { i18n } from '../../utils/lang.js'; import fs from 'fs'; import path from 'path'; - -// Mock dependencies -jest.mock('../config_DEPRECATED'); -jest.mock('../CLIConfiguration'); -jest.mock('../index'); -jest.mock('../configFile'); -jest.mock('../../utils/lang'); -jest.mock('fs'); -jest.mock('path'); - -const mockConfig_DEPRECATED = config_DEPRECATED as jest.Mocked< - typeof config_DEPRECATED ->; -const mockCLIConfiguration = CLIConfiguration as jest.Mocked< - typeof CLIConfiguration ->; -const mockConfigIndex = configIndex as jest.Mocked; -const mockConfigFile = configFile as jest.Mocked; -const mockI18n = i18n as jest.MockedFunction; -const mockFs = fs as jest.Mocked; -const mockPath = path as jest.Mocked; +import { vi } from 'vitest'; + +vi.mock('../config_DEPRECATED'); +vi.mock('../CLIConfiguration'); +vi.mock('../index'); +vi.mock('../configFile'); +vi.mock('../../utils/lang'); +vi.mock('fs'); +vi.mock('path'); + +const mockConfig_DEPRECATED = vi.mocked(config_DEPRECATED); +const mockCLIConfiguration = vi.mocked(CLIConfiguration); +const mockConfigIndex = vi.mocked(configIndex); +const mockConfigFile = vi.mocked(configFile); +const mockI18n = vi.mocked(i18n); +const mockFs = vi.mocked(fs); +const mockPath = vi.mocked(path); describe('migrate', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); + + mockFs.renameSync = vi.fn(); + mockPath.dirname = vi.fn().mockReturnValue('/old/config'); + mockPath.join = vi.fn().mockImplementation((...args) => args.join('/')); }); describe('getDeprecatedConfig', () => { @@ -176,16 +176,24 @@ describe('migrate', () => { describe('migrateConfig', () => { beforeEach(() => { mockI18n.mockImplementation(key => `translated-${key}`); - mockConfigIndex.createEmptyConfigFile.mockImplementation(); - mockConfigIndex.loadConfig.mockImplementation(); - mockConfigIndex.writeConfig.mockImplementation(); - mockConfigIndex.deleteEmptyConfigFile.mockImplementation(); + mockConfigIndex.createEmptyConfigFile.mockImplementation(() => { + return; + }); + mockConfigIndex.loadConfig.mockImplementation(() => null); + mockConfigIndex.writeConfig.mockImplementation(() => { + return; + }); + mockConfigIndex.deleteEmptyConfigFile.mockImplementation(() => { + return; + }); mockConfig_DEPRECATED.getConfigPath.mockReturnValue('/old/config/path'); mockPath.dirname.mockReturnValue('/old/config'); mockPath.join.mockReturnValue( `/old/config/${ARCHIVED_HUBSPOT_CONFIG_YAML_FILE_NAME}` ); - mockFs.renameSync.mockImplementation(); + mockFs.renameSync.mockImplementation(() => { + return; + }); }); it('should throw error when deprecatedConfig is null', () => { @@ -424,7 +432,9 @@ describe('migrate', () => { describe('mergeExistingConfigs', () => { beforeEach(() => { - mockConfigIndex.writeConfig.mockImplementation(); + mockConfigIndex.writeConfig.mockImplementation(() => { + return; + }); }); it('should merge accounts and return skipped account IDs', () => { diff --git a/config/configFile.ts b/config/configFile.ts index 89d884e7..cf3df7ec 100644 --- a/config/configFile.ts +++ b/config/configFile.ts @@ -1,11 +1,11 @@ import fs from 'fs-extra'; import yaml from 'js-yaml'; -import { logger } from '../lib/logger'; -import { GLOBAL_CONFIG_PATH } from '../constants/config'; -import { getOrderedConfig } from './configUtils'; -import { CLIConfig_NEW } from '../types/Config'; -import { i18n } from '../utils/lang'; -import { FileSystemError } from '../models/FileSystemError'; +import { logger } from '../lib/logger.js'; +import { GLOBAL_CONFIG_PATH } from '../constants/config.js'; +import { getOrderedConfig } from './configUtils.js'; +import { CLIConfig_NEW } from '../types/Config.js'; +import { i18n } from '../utils/lang.js'; +import { FileSystemError } from '../models/FileSystemError.js'; const i18nKey = 'config.configFile'; diff --git a/config/configUtils.ts b/config/configUtils.ts index 04c42265..a25d65cc 100644 --- a/config/configUtils.ts +++ b/config/configUtils.ts @@ -1,10 +1,10 @@ -import { logger } from '../lib/logger'; +import { logger } from '../lib/logger.js'; import { API_KEY_AUTH_METHOD, OAUTH_AUTH_METHOD, PERSONAL_ACCESS_KEY_AUTH_METHOD, -} from '../constants/auth'; -import { CLIConfig_NEW } from '../types/Config'; +} from '../constants/auth.js'; +import { CLIConfig_NEW } from '../types/Config.js'; import { AuthType, CLIAccount_NEW, @@ -14,8 +14,8 @@ import { PersonalAccessKeyOptions, OAuthOptions, APIKeyOptions, -} from '../types/Accounts'; -import { i18n } from '../utils/lang'; +} from '../types/Accounts.js'; +import { i18n } from '../utils/lang.js'; const i18nKey = 'config.configUtils'; diff --git a/config/config_DEPRECATED.ts b/config/config_DEPRECATED.ts index 350eb5d7..dad54b9f 100644 --- a/config/config_DEPRECATED.ts +++ b/config/config_DEPRECATED.ts @@ -1,27 +1,30 @@ import fs from 'fs-extra'; import yaml from 'js-yaml'; import findup from 'findup-sync'; -import { getCwd } from '../lib/path'; +import { getCwd } from '../lib/path.js'; import { DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME, MIN_HTTP_TIMEOUT, HUBSPOT_ACCOUNT_TYPES, -} from '../constants/config'; -import { ENVIRONMENTS, ENVIRONMENT_VARIABLES } from '../constants/environments'; +} from '../constants/config.js'; +import { + ENVIRONMENTS, + ENVIRONMENT_VARIABLES, +} from '../constants/environments.js'; import { API_KEY_AUTH_METHOD, OAUTH_AUTH_METHOD, PERSONAL_ACCESS_KEY_AUTH_METHOD, OAUTH_SCOPES, -} from '../constants/auth'; -import { CMS_PUBLISH_MODE } from '../constants/files'; -import { getValidEnv } from '../lib/environment'; -import { logger } from '../lib/logger'; +} from '../constants/auth.js'; +import { CMS_PUBLISH_MODE } from '../constants/files.js'; +import { getValidEnv } from '../lib/environment.js'; +import { logger } from '../lib/logger.js'; import { logErrorInstance, logFileSystemErrorInstance, -} from '../errors/errors_DEPRECATED'; -import { CLIConfig_DEPRECATED, Environment } from '../types/Config'; +} from '../errors/errors_DEPRECATED.js'; +import { CLIConfig_DEPRECATED, Environment } from '../types/Config.js'; import { APIKeyAccount_DEPRECATED, AccountType, @@ -29,10 +32,10 @@ import { FlatAccountFields_DEPRECATED, OAuthAccount_DEPRECATED, UpdateAccountConfigOptions, -} from '../types/Accounts'; -import { BaseError } from '../types/Error'; -import { CmsPublishMode } from '../types/Files'; -import { CLIOptions, WriteConfigOptions } from '../types/CLIOptions'; +} from '../types/Accounts.js'; +import { BaseError } from '../types/Error.js'; +import { CmsPublishMode } from '../types/Files.js'; +import { CLIOptions, WriteConfigOptions } from '../types/CLIOptions.js'; const ALL_CMS_PUBLISH_MODES = Object.values(CMS_PUBLISH_MODE); let _config: CLIConfig_DEPRECATED | null; @@ -313,7 +316,9 @@ export function loadConfig( logger.debug('Loaded environment variable config'); environmentVariableConfigLoaded = true; } else { - path && logger.debug(`Loading config from ${path}`); + if (path) { + logger.debug(`Loading config from ${path}`); + } loadConfigFromFile(path, options); environmentVariableConfigLoaded = false; } @@ -759,11 +764,15 @@ export function createEmptyConfigFile({ path }: { path?: string } = {}): void { } export function deleteEmptyConfigFile(): void { - configFileExists() && configFileIsBlank() && fs.unlinkSync(_configPath || ''); + if (configFileExists() && configFileIsBlank()) { + fs.unlinkSync(_configPath || ''); + } } export function deleteConfigFile(): void { - configFileExists() && fs.unlinkSync(_configPath || ''); + if (configFileExists()) { + fs.unlinkSync(_configPath || ''); + } } function getConfigVariablesFromEnv() { @@ -871,7 +880,10 @@ export function loadConfigFromEnvironment({ 'Unable to load config from environment variables.'; if (!portalId) { - useEnv && logger.error(unableToLoadEnvConfigError); + if (useEnv) { + logger.error(unableToLoadEnvConfigError); + } + return; } @@ -901,7 +913,9 @@ export function loadConfigFromEnvironment({ } else if (apiKey) { return generateApiKeyConfig(portalId, apiKey, env); } else { - useEnv && logger.error(unableToLoadEnvConfigError); + if (useEnv) { + logger.error(unableToLoadEnvConfigError); + } return; } } diff --git a/config/environment.ts b/config/environment.ts index 6062261f..cb25170b 100644 --- a/config/environment.ts +++ b/config/environment.ts @@ -2,18 +2,18 @@ import { CLIConfig_NEW, Environment, EnvironmentConfigVariables, -} from '../types/Config'; -import { logger } from '../lib/logger'; -import { ENVIRONMENT_VARIABLES } from '../constants/environments'; +} from '../types/Config.js'; +import { logger } from '../lib/logger.js'; +import { ENVIRONMENT_VARIABLES } from '../constants/environments.js'; import { API_KEY_AUTH_METHOD, OAUTH_AUTH_METHOD, PERSONAL_ACCESS_KEY_AUTH_METHOD, OAUTH_SCOPES, -} from '../constants/auth'; -import { generateConfig } from './configUtils'; -import { getValidEnv } from '../lib/environment'; -import { i18n } from '../utils/lang'; +} from '../constants/auth.js'; +import { generateConfig } from './configUtils.js'; +import { getValidEnv } from '../lib/environment.js'; +import { i18n } from '../utils/lang.js'; const i18nKey = 'config.environment'; diff --git a/config/getAccountIdentifier.ts b/config/getAccountIdentifier.ts index 56b8097b..2d3c0597 100644 --- a/config/getAccountIdentifier.ts +++ b/config/getAccountIdentifier.ts @@ -1,4 +1,4 @@ -import { GenericAccount } from '../types/Accounts'; +import { GenericAccount } from '../types/Accounts.js'; export function getAccountIdentifier( account?: GenericAccount | null diff --git a/config/index.ts b/config/index.ts index d943fad6..07a6b338 100644 --- a/config/index.ts +++ b/config/index.ts @@ -1,21 +1,21 @@ -import * as config_DEPRECATED from './config_DEPRECATED'; -import { CLIConfiguration } from './CLIConfiguration'; +import * as config_DEPRECATED from './config_DEPRECATED.js'; +import { CLIConfiguration } from './CLIConfiguration.js'; import { configFileExists as newConfigFileExists, getConfigFilePath, deleteConfigFile as newDeleteConfigFile, -} from './configFile'; -import { CLIConfig_NEW, CLIConfig } from '../types/Config'; -import { CLIOptions, WriteConfigOptions } from '../types/CLIOptions'; +} from './configFile.js'; +import { CLIConfig_NEW, CLIConfig } from '../types/Config.js'; +import { CLIOptions, WriteConfigOptions } from '../types/CLIOptions.js'; import { AccountType, CLIAccount, CLIAccount_NEW, CLIAccount_DEPRECATED, FlatAccountFields, -} from '../types/Accounts'; -import { getAccountIdentifier } from './getAccountIdentifier'; -import { CmsPublishMode } from '../types/Files'; +} from '../types/Accounts.js'; +import { getAccountIdentifier } from './getAccountIdentifier.js'; +import { CmsPublishMode } from '../types/Files.js'; // Use new config if it exists export function loadConfig( diff --git a/config/migrate.ts b/config/migrate.ts index b56e743f..e8f1d9c3 100644 --- a/config/migrate.ts +++ b/config/migrate.ts @@ -1,22 +1,22 @@ -import * as config_DEPRECATED from './config_DEPRECATED'; -import { CLIConfiguration } from './CLIConfiguration'; +import * as config_DEPRECATED from './config_DEPRECATED.js'; +import { CLIConfiguration } from './CLIConfiguration.js'; import { CLIConfig, CLIConfig_DEPRECATED, CLIConfig_NEW, Environment, -} from '../types/Config'; -import { CmsPublishMode } from '../types/Files'; +} from '../types/Config.js'; +import { CmsPublishMode } from '../types/Files.js'; import { writeConfig, createEmptyConfigFile, loadConfig, deleteEmptyConfigFile, -} from './index'; +} from './index.js'; import { getConfigFilePath, configFileExists as newConfigFileExists, -} from './configFile'; +} from './configFile.js'; import { GLOBAL_CONFIG_PATH, DEFAULT_CMS_PUBLISH_MODE, @@ -27,8 +27,8 @@ import { DEFAULT_ACCOUNT, DEFAULT_PORTAL, ARCHIVED_HUBSPOT_CONFIG_YAML_FILE_NAME, -} from '../constants/config'; -import { i18n } from '../utils/lang'; +} from '../constants/config.js'; +import { i18n } from '../utils/lang.js'; import fs from 'fs'; import path from 'path'; diff --git a/constants/config.ts b/constants/config.ts index 0ffe9432..6faeb4f1 100644 --- a/constants/config.ts +++ b/constants/config.ts @@ -1,4 +1,4 @@ -import { i18n } from '../utils/lang'; +import { i18n } from '../utils/lang.js'; import path from 'path'; import os from 'os'; diff --git a/errors/__tests__/index.ts b/errors/__tests__/index.ts index 51a8ab9a..92800cfc 100644 --- a/errors/__tests__/index.ts +++ b/errors/__tests__/index.ts @@ -3,9 +3,9 @@ import { isGatingError, isSpecifiedError, isSystemError, -} from '../index'; -import { BaseError } from '../../types/Error'; -import { HubSpotHttpError } from '../../models/HubSpotHttpError'; +} from '../index.js'; +import { BaseError } from '../../types/Error.js'; +import { HubSpotHttpError } from '../../models/HubSpotHttpError.js'; export const newError = (overrides = {}): BaseError => { return { diff --git a/errors/errors_DEPRECATED.ts b/errors/errors_DEPRECATED.ts index 45c86a53..bea1dd48 100644 --- a/errors/errors_DEPRECATED.ts +++ b/errors/errors_DEPRECATED.ts @@ -2,7 +2,7 @@ import { BaseError, FileSystemErrorContext, ErrorContext, -} from '../types/Error'; +} from '../types/Error.js'; function isSystemError(err: BaseError) { return err.errno != null && err.code != null && err.syscall != null; diff --git a/errors/index.ts b/errors/index.ts index 9840fbde..4a491f7a 100644 --- a/errors/index.ts +++ b/errors/index.ts @@ -1,6 +1,6 @@ -import { HubSpotHttpError } from '../models/HubSpotHttpError'; -import { BaseError } from '../types/Error'; -import { FileSystemError } from '../models/FileSystemError'; +import { HubSpotHttpError } from '../models/HubSpotHttpError.js'; +import { BaseError } from '../types/Error.js'; +import { FileSystemError } from '../models/FileSystemError.js'; export function isSpecifiedError( err: unknown, diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..58fc5b00 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,83 @@ +import eslint from '@eslint/js'; +import tseslint from '@typescript-eslint/eslint-plugin'; +import tsparser from '@typescript-eslint/parser'; +import importPlugin from 'eslint-plugin-import'; + +export default [ + eslint.configs.recommended, + { + files: ['**/*.ts', '**/*.tsx'], + languageOptions: { + parser: tsparser, + parserOptions: { + ecmaVersion: 2022, + }, + globals: { + console: 'readonly', + process: 'readonly', + Buffer: 'readonly', + __dirname: 'readonly', + __filename: 'readonly', + global: 'readonly', + module: 'readonly', + require: 'readonly', + exports: 'readonly', + setImmediate: 'readonly', + clearImmediate: 'readonly', + URLSearchParams: 'readonly', + URL: 'readonly', + setTimeout: 'readonly', + clearTimeout: 'readonly', + setInterval: 'readonly', + clearInterval: 'readonly', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + import: importPlugin, + }, + rules: { + ...tseslint.configs.recommended.rules, + 'no-console': 'off', + 'no-return-await': 'error', + '@typescript-eslint/no-non-null-assertion': 'off', + 'import/no-default-export': 'error', + 'no-redeclare': 'off', + '@typescript-eslint/no-redeclare': 'off', + }, + }, + { + files: ['**/__tests__/**/*.ts', '**/__mocks__/**/*.ts'], + languageOptions: { + globals: { + vi: 'readonly', + describe: 'readonly', + it: 'readonly', + test: 'readonly', + expect: 'readonly', + beforeEach: 'readonly', + afterEach: 'readonly', + beforeAll: 'readonly', + afterAll: 'readonly', + MockedFunction: 'readonly', + }, + }, + }, + { + files: ['acceptance-tests/tests/**/*.ts'], + languageOptions: { + globals: { + jasmine: 'readonly', + }, + }, + }, + { + files: ['*.config.*'], + rules: { + 'import/no-default-export': 'off', + }, + }, + { + ignores: ['dist/**', 'coverage/**', 'node_modules/**'], + }, +]; diff --git a/http/__tests__/getAxiosConfig.test.ts b/http/__tests__/getAxiosConfig.test.ts index f3cbc1f1..cf653f02 100644 --- a/http/__tests__/getAxiosConfig.test.ts +++ b/http/__tests__/getAxiosConfig.test.ts @@ -1,13 +1,13 @@ -import { getAndLoadConfigIfNeeded as __getAndLoadConfigIfNeeded } from '../../config'; -import { ENVIRONMENTS } from '../../constants/environments'; -import { getAxiosConfig } from '../getAxiosConfig'; +import { getAndLoadConfigIfNeeded as __getAndLoadConfigIfNeeded } from '../../config/index.js'; +import { ENVIRONMENTS } from '../../constants/environments.js'; +import { getAxiosConfig } from '../getAxiosConfig.js'; +import { vi, type MockedFunction } from 'vitest'; -jest.mock('../../config'); +vi.mock('../../config'); -const getAndLoadConfigIfNeeded = - __getAndLoadConfigIfNeeded as jest.MockedFunction< - typeof __getAndLoadConfigIfNeeded - >; +const getAndLoadConfigIfNeeded = __getAndLoadConfigIfNeeded as MockedFunction< + typeof __getAndLoadConfigIfNeeded +>; const url = 'https://app.hubspot.com'; diff --git a/http/__tests__/index.test.ts b/http/__tests__/index.test.ts index f86fc84d..687b89d8 100644 --- a/http/__tests__/index.test.ts +++ b/http/__tests__/index.test.ts @@ -4,40 +4,44 @@ import moment from 'moment'; import { getAndLoadConfigIfNeeded as __getAndLoadConfigIfNeeded, getAccountConfig as __getAccountConfig, -} from '../../config'; -import { ENVIRONMENTS } from '../../constants/environments'; -import { http } from '../'; -import { version } from '../../package.json'; -import { AuthType } from '../../types/Accounts'; +} from '../../config/index.js'; +import { ENVIRONMENTS } from '../../constants/environments.js'; +import { http } from '../index.js'; +import pkg from '../../package.json' with { type: 'json' }; +import { AuthType } from '../../types/Accounts.js'; +import { vi, type MockedFunction } from 'vitest'; -jest.mock('fs-extra'); -jest.mock('axios'); -jest.mock('../../config'); -jest.mock('../../lib/logger'); +vi.mock('fs-extra'); +vi.mock('axios'); +vi.mock('../../config'); +vi.mock('../../lib/logger'); -jest.mock('http', () => ({ - Agent: jest.fn().mockReturnValue({ - options: { keepAlive: true, maxSockets: 5, maxTotalSockets: 25 }, - }), +vi.mock('http', () => ({ + default: { + Agent: vi.fn().mockReturnValue({ + options: { keepAlive: true, maxSockets: 5, maxTotalSockets: 25 }, + }), + }, })); -jest.mock('https', () => ({ - Agent: jest.fn().mockReturnValue({ - options: { keepAlive: true, maxSockets: 6, maxTotalSockets: 26 }, - }), +vi.mock('https', () => ({ + default: { + Agent: vi.fn().mockReturnValue({ + options: { keepAlive: true, maxSockets: 6, maxTotalSockets: 26 }, + }), + }, })); -const mockedAxios = jest.mocked(axios); -const getAndLoadConfigIfNeeded = - __getAndLoadConfigIfNeeded as jest.MockedFunction< - typeof __getAndLoadConfigIfNeeded - >; -const getAccountConfig = __getAccountConfig as jest.MockedFunction< +const mockedAxios = vi.mocked(axios); +const getAndLoadConfigIfNeeded = __getAndLoadConfigIfNeeded as MockedFunction< + typeof __getAndLoadConfigIfNeeded +>; +const getAccountConfig = __getAccountConfig as MockedFunction< typeof __getAccountConfig >; -fs.createWriteStream = jest.fn().mockReturnValue({ - on: jest.fn((event, callback) => { +fs.createWriteStream = vi.fn().mockReturnValue({ + on: vi.fn((event, callback) => { if (event === 'close') { callback(); } @@ -74,7 +78,7 @@ describe('http/index', () => { status: 200, headers: [], data: { - pipe: jest.fn(), + pipe: vi.fn(), }, }); await http.getOctetStream( @@ -99,7 +103,7 @@ describe('http/index', () => { status: 404, headers: [], data: { - pipe: jest.fn(), + pipe: vi.fn(), }, }); @@ -152,7 +156,7 @@ describe('http/index', () => { baseURL: `https://api.hubapi.com`, url: 'some/endpoint/path', headers: { - 'User-Agent': `HubSpot Local Dev Lib/${version}`, + 'User-Agent': `HubSpot Local Dev Lib/${pkg.version}`, Authorization: `Bearer ${accessToken}`, }, timeout: 15000, @@ -195,7 +199,7 @@ describe('http/index', () => { baseURL: `https://api.hubapi.com`, url: 'some/endpoint/path', headers: { - 'User-Agent': `HubSpot Local Dev Lib/${version}`, + 'User-Agent': `HubSpot Local Dev Lib/${pkg.version}`, Authorization: `Bearer ${accessToken}`, }, timeout: 15000, @@ -237,7 +241,7 @@ describe('http/index', () => { baseURL: `https://api.hubapi.com`, url: 'some/endpoint/path', headers: { - 'User-Agent': `HubSpot Local Dev Lib/${version}`, + 'User-Agent': `HubSpot Local Dev Lib/${pkg.version}`, }, timeout: 1000, params: { diff --git a/http/__tests__/unauthed.ts b/http/__tests__/unauthed.ts index 7a0653f4..70464662 100644 --- a/http/__tests__/unauthed.ts +++ b/http/__tests__/unauthed.ts @@ -1,22 +1,28 @@ import axios from 'axios'; -import { http } from '../unauthed'; -import { version } from '../../package.json'; +import { http } from '../unauthed.js'; +import pkg from '../../package.json' with { type: 'json' }; -jest.mock('axios'); +import { vi } from 'vitest'; -jest.mock('http', () => ({ - Agent: jest.fn().mockReturnValue({ - options: { keepAlive: true, maxSockets: 5, maxTotalSockets: 25 }, - }), +vi.mock('axios'); + +vi.mock('http', () => ({ + default: { + Agent: vi.fn().mockReturnValue({ + options: { keepAlive: true, maxSockets: 5, maxTotalSockets: 25 }, + }), + }, })); -jest.mock('https', () => ({ - Agent: jest.fn().mockReturnValue({ - options: { keepAlive: true, maxSockets: 6, maxTotalSockets: 26 }, - }), +vi.mock('https', () => ({ + default: { + Agent: vi.fn().mockReturnValue({ + options: { keepAlive: true, maxSockets: 6, maxTotalSockets: 26 }, + }), + }, })); -const mockedAxios = jest.mocked(axios); +const mockedAxios = vi.mocked(axios); describe('http/index', () => { describe('http.get()', () => { @@ -30,7 +36,7 @@ describe('http/index', () => { baseURL: `https://api.hubapiqa.com`, url: 'some/endpoint/path', headers: { - 'User-Agent': `HubSpot Local Dev Lib/${version}`, + 'User-Agent': `HubSpot Local Dev Lib/${pkg.version}`, }, timeout: 15000, params: {}, diff --git a/http/addQueryParams.ts b/http/addQueryParams.ts index b4bed693..c2db97b1 100644 --- a/http/addQueryParams.ts +++ b/http/addQueryParams.ts @@ -1,4 +1,4 @@ -import { HttpOptions, QueryParams } from '../types/Http'; +import { HttpOptions, QueryParams } from '../types/Http.js'; export function addQueryParams( configOptions: HttpOptions, diff --git a/http/getAxiosConfig.ts b/http/getAxiosConfig.ts index 2f4ad454..15f9db15 100644 --- a/http/getAxiosConfig.ts +++ b/http/getAxiosConfig.ts @@ -1,7 +1,7 @@ -import { version } from '../package.json'; -import { getAndLoadConfigIfNeeded } from '../config'; -import { getHubSpotApiOrigin } from '../lib/urls'; -import { HttpOptions } from '../types/Http'; +import pkg from '../package.json' with { type: 'json' }; +import { getAndLoadConfigIfNeeded } from '../config/index.js'; +import { getHubSpotApiOrigin } from '../lib/urls.js'; +import { HttpOptions } from '../types/Http.js'; import { AxiosRequestConfig } from 'axios'; import https from 'https'; import http from 'http'; @@ -25,7 +25,7 @@ const httpsAgent = new https.Agent({ }); export const USER_AGENTS: { [key: string]: string } = { - 'HubSpot Local Dev Lib': version, + 'HubSpot Local Dev Lib': pkg.version, }; export function getDefaultUserAgentHeader(): { 'User-Agent': string } { diff --git a/http/index.ts b/http/index.ts index 82fa79de..f4a50b42 100644 --- a/http/index.ts +++ b/http/index.ts @@ -8,17 +8,17 @@ import axios, { isAxiosError, } from 'axios'; -import { getAccountConfig } from '../config'; -import { USER_AGENTS, getAxiosConfig } from './getAxiosConfig'; -import { addQueryParams } from './addQueryParams'; -import { accessTokenForPersonalAccessKey } from '../lib/personalAccessKey'; -import { getOauthManager } from '../lib/oauth'; -import { FlatAccountFields } from '../types/Accounts'; -import { HttpOptions, HubSpotPromise } from '../types/Http'; -import { logger } from '../lib/logger'; -import { i18n } from '../utils/lang'; -import { HubSpotHttpError } from '../models/HubSpotHttpError'; -import { LOCALDEVAUTH_ACCESS_TOKEN_PATH } from '../api/localDevAuth'; +import { getAccountConfig } from '../config/index.js'; +import { USER_AGENTS, getAxiosConfig } from './getAxiosConfig.js'; +import { addQueryParams } from './addQueryParams.js'; +import { accessTokenForPersonalAccessKey } from '../lib/personalAccessKey.js'; +import { getOauthManager } from '../lib/oauth.js'; +import { FlatAccountFields } from '../types/Accounts.js'; +import { HttpOptions, HubSpotPromise } from '../types/Http.js'; +import { logger } from '../lib/logger.js'; +import { i18n } from '../utils/lang.js'; +import { HubSpotHttpError } from '../models/HubSpotHttpError.js'; +import { LOCALDEVAUTH_ACCESS_TOKEN_PATH } from '../api/localDevAuth.js'; import * as util from 'util'; const i18nKey = 'http.index'; @@ -45,6 +45,7 @@ function logRequest(response: AxiosResponse) { ) ); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // Ignore any errors that occur while logging the response } @@ -60,6 +61,7 @@ axios.interceptors.response.use( if (isAxiosError(error) && error.response) { logRequest(error.response); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Ignore any errors that occur while logging the response } diff --git a/http/unauthed.ts b/http/unauthed.ts index 5e5ab37f..f4918d3c 100644 --- a/http/unauthed.ts +++ b/http/unauthed.ts @@ -1,7 +1,7 @@ import axios from 'axios'; -import { getAxiosConfig } from './getAxiosConfig'; -import { addQueryParams } from './addQueryParams'; -import { HttpOptions, HubSpotPromise } from '../types/Http'; +import { getAxiosConfig } from './getAxiosConfig.js'; +import { addQueryParams } from './addQueryParams.js'; +import { HttpOptions, HubSpotPromise } from '../types/Http.js'; async function getRequest(options: HttpOptions): HubSpotPromise { const { params, ...rest } = options; diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index a1425a95..00000000 --- a/jest.config.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - collectCoverage: true, - transform: { - 'node_modules/variables/.+\\.(j|t)sx?$': 'ts-jest', - }, - setupFiles: ['./setupTests.ts'], - transformIgnorePatterns: ['node_modules/(?!variables/.*)'], - modulePathIgnorePatterns: ['/dist/', '/__utils__/'], - clearMocks: true, -}; diff --git a/lib/__tests__/archive.test.ts b/lib/__tests__/archive.test.ts index 24e51d04..c603c51d 100644 --- a/lib/__tests__/archive.test.ts +++ b/lib/__tests__/archive.test.ts @@ -1,28 +1,27 @@ import os from 'os'; -jest.mock('fs-extra'); -jest.mock('extract-zip'); -jest.mock('os'); -jest.mock('../logger'); -jest.mock('../fs'); - -import { extractZipArchive } from '../archive'; -import { logger } from '../logger'; +vi.mock('fs-extra'); +vi.mock('extract-zip'); +vi.mock('os'); +vi.mock('../logger'); +vi.mock('../fs'); + +import { extractZipArchive } from '../archive.js'; +import { logger } from '../logger.js'; import fs from 'fs-extra'; import extract from 'extract-zip'; -import { walk } from '../fs'; - -const writeFileMock = fs.writeFile as jest.MockedFunction; -const makeDirMock = fs.mkdtemp as jest.MockedFunction; -const readDirMock = fs.readdir as jest.MockedFunction; -const osTmpDirMock = os.tmpdir as jest.MockedFunction; -const extractMock = extract as jest.MockedFunction; -const fsCopyMock = fs.copy as jest.MockedFunction; -const fsRemoveMock = fs.remove as jest.MockedFunction; -const fsExistsSyncMock = fs.existsSync as jest.MockedFunction< - typeof fs.existsSync ->; -const walkMock = walk as jest.MockedFunction; +import { walk } from '../fs.js'; +import { vi } from 'vitest'; + +const writeFileMock = vi.mocked(fs.writeFile); +const makeDirMock = vi.mocked(fs.mkdtemp); +const readDirMock = vi.mocked(fs.readdir); +const osTmpDirMock = vi.mocked(os.tmpdir); +const extractMock = vi.mocked(extract); +const fsCopyMock = vi.mocked(fs.copy); +const fsRemoveMock = vi.mocked(fs.remove); +const fsExistsSyncMock = vi.mocked(fs.existsSync); +const walkMock = vi.mocked(walk); describe('lib/archive', () => { const rootDir = 'rootdir'; @@ -35,9 +34,27 @@ describe('lib/archive', () => { const dest = 'where/to/save/'; beforeEach(() => { + // Reset specific mocks that are used in these tests + makeDirMock.mockReset(); + readDirMock.mockReset(); + osTmpDirMock.mockReset(); + fsExistsSyncMock.mockReset(); + walkMock.mockReset(); + writeFileMock.mockReset(); + fsCopyMock.mockReset(); + fsRemoveMock.mockReset(); + extractMock.mockReset(); + + // Set up default implementations makeDirMock.mockImplementation(value => Promise.resolve(value)); readDirMock.mockImplementation(() => [rootDir]); osTmpDirMock.mockImplementation(() => tmpDir); + fsExistsSyncMock.mockReturnValue(false); + walkMock.mockResolvedValue([]); + writeFileMock.mockResolvedValue(); + fsCopyMock.mockResolvedValue(); + fsRemoveMock.mockResolvedValue(); + extractMock.mockResolvedValue(); }); describe('extractZipArchive', () => { @@ -166,7 +183,7 @@ describe('lib/archive', () => { it('should call handleCollision when destination exists and collisions are detected', async () => { const sourceDir = 'sourceDir'; - const handleCollisionMock = jest.fn(); + const handleCollisionMock = vi.fn(); fsExistsSyncMock.mockReturnValue(true); walkMock @@ -195,7 +212,7 @@ describe('lib/archive', () => { it('should call handleCollision for each source directory when multiple dirs have collisions', async () => { const sourceDir = ['sourceDir1', 'sourceDir2']; - const handleCollisionMock = jest.fn(); + const handleCollisionMock = vi.fn(); fsExistsSyncMock.mockReturnValue(true); walkMock @@ -223,7 +240,7 @@ describe('lib/archive', () => { it('should not call handleCollision when destination does not exist', async () => { const sourceDir = 'sourceDir'; - const handleCollisionMock = jest.fn(); + const handleCollisionMock = vi.fn(); fsExistsSyncMock.mockReturnValue(false); @@ -257,7 +274,7 @@ describe('lib/archive', () => { it('should not call handleCollisions where there are no collisions', async () => { const sourceDir = 'sourceDir'; - const handleCollisionMock = jest.fn(); + const handleCollisionMock = vi.fn(); fsExistsSyncMock.mockReturnValue(true); walkMock @@ -276,7 +293,7 @@ describe('lib/archive', () => { it('should normalize paths when detecting collisions', async () => { const sourceDir = 'sourceDir'; - const handleCollisionMock = jest.fn(); + const handleCollisionMock = vi.fn(); fsExistsSyncMock.mockReturnValue(true); walkMock @@ -320,7 +337,7 @@ describe('lib/archive', () => { it('should handle async handleCollision function', async () => { const sourceDir = 'sourceDir'; - const handleCollisionMock = jest.fn().mockResolvedValue(undefined); + const handleCollisionMock = vi.fn().mockResolvedValue(undefined); fsExistsSyncMock.mockReturnValue(true); walkMock @@ -345,7 +362,7 @@ describe('lib/archive', () => { it('should handle synchronous handleCollision function wrapped in Promise.resolve', async () => { const sourceDir = 'sourceDir'; - const handleCollisionMock = jest.fn().mockReturnValue('sync result'); + const handleCollisionMock = vi.fn().mockReturnValue('sync result'); fsExistsSyncMock.mockReturnValue(true); walkMock @@ -369,7 +386,7 @@ describe('lib/archive', () => { it('should copy non-collided files when collisions are handled', async () => { const sourceDir = 'sourceDir'; - const handleCollisionMock = jest.fn(); + const handleCollisionMock = vi.fn(); fsExistsSyncMock.mockReturnValue(true); walkMock @@ -399,7 +416,7 @@ describe('lib/archive', () => { it('should copy all non-collided files when some files have collisions', async () => { const sourceDir = 'sourceDir'; - const handleCollisionMock = jest.fn(); + const handleCollisionMock = vi.fn(); fsExistsSyncMock.mockReturnValue(true); walkMock diff --git a/lib/__tests__/customObjects.test.ts b/lib/__tests__/customObjects.test.ts index 98f0bfa6..867d5dea 100644 --- a/lib/__tests__/customObjects.test.ts +++ b/lib/__tests__/customObjects.test.ts @@ -3,25 +3,26 @@ import { downloadSchema, downloadSchemas, writeSchemaToDisk, -} from '../customObjects'; +} from '../customObjects.js'; import { fetchObjectSchema as __fetchObjectSchema, fetchObjectSchemas as __fetchObjectSchemas, -} from '../../api/customObjects'; -import basicSchema from './fixtures/customObjects/basicSchema.json'; -import fullSchema from './fixtures/customObjects/fullSchema.json'; -import multipleSchemas from './fixtures/customObjects/multipleSchemas.json'; -import { mockAxiosResponse } from './__utils__/mockAxiosResponse'; - -jest.mock('fs-extra'); -jest.mock('../../api/customObjects'); - -const outputFileSyncSpy = jest.spyOn(fs, 'outputFileSync'); -jest.spyOn(fs, 'existsSync').mockReturnValue(true); -const fetchObjectSchema = __fetchObjectSchema as jest.MockedFunction< +} from '../../api/customObjects.js'; +import basicSchema from './fixtures/customObjects/basicSchema.json' with { type: 'json' }; +import fullSchema from './fixtures/customObjects/fullSchema.json' with { type: 'json' }; +import multipleSchemas from './fixtures/customObjects/multipleSchemas.json' with { type: 'json' }; +import { mockAxiosResponse } from './__utils__/mockAxiosResponse.js'; +import { vi, type MockedFunction } from 'vitest'; + +vi.mock('fs-extra'); +vi.mock('../../api/customObjects'); + +const outputFileSyncSpy = vi.spyOn(fs, 'outputFileSync'); +vi.spyOn(fs, 'existsSync').mockReturnValue(true); +const fetchObjectSchema = __fetchObjectSchema as MockedFunction< typeof __fetchObjectSchema >; -const fetchObjectSchemas = __fetchObjectSchemas as jest.MockedFunction< +const fetchObjectSchemas = __fetchObjectSchemas as MockedFunction< typeof __fetchObjectSchemas >; diff --git a/lib/__tests__/environment.test.ts b/lib/__tests__/environment.test.ts index ebce2670..fe913db7 100644 --- a/lib/__tests__/environment.test.ts +++ b/lib/__tests__/environment.test.ts @@ -1,5 +1,5 @@ -import { getValidEnv } from '../environment'; -import { ENVIRONMENTS } from '../../constants/environments'; +import { getValidEnv } from '../environment.js'; +import { ENVIRONMENTS } from '../../constants/environments.js'; const { QA, PROD } = ENVIRONMENTS; diff --git a/lib/__tests__/fileManager.test.ts b/lib/__tests__/fileManager.test.ts index f0739bcd..b7efae58 100644 --- a/lib/__tests__/fileManager.test.ts +++ b/lib/__tests__/fileManager.test.ts @@ -1,12 +1,13 @@ -import { uploadFolder } from '../fileManager'; -import { uploadFile } from '../../api/fileManager'; -import { walk } from '../fs'; -import { createIgnoreFilter } from '../ignoreRules'; -import { mockAxiosResponse } from './__utils__/mockAxiosResponse'; +import { uploadFolder } from '../fileManager.js'; +import { uploadFile } from '../../api/fileManager.js'; +import { walk } from '../fs.js'; +import { createIgnoreFilter } from '../ignoreRules.js'; +import { mockAxiosResponse } from './__utils__/mockAxiosResponse.js'; +import { vi } from 'vitest'; -jest.mock('../fs'); -jest.mock('../../api/fileManager'); -jest.mock('../ignoreRules'); +vi.mock('../fs'); +vi.mock('../../api/fileManager'); +vi.mock('../ignoreRules'); describe('lib/fileManager', () => { describe('uploadFolder()', () => { @@ -18,9 +19,9 @@ describe('lib/fileManager', () => { 'folder/video/video.mp4', ]; - const mockedWalk = jest.mocked(walk); - const mockedUploadFile = jest.mocked(uploadFile); - const mockedCreateIgnoreFilter = jest.mocked(createIgnoreFilter); + const mockedWalk = vi.mocked(walk); + const mockedUploadFile = vi.mocked(uploadFile); + const mockedCreateIgnoreFilter = vi.mocked(createIgnoreFilter); mockedWalk.mockResolvedValue(files); mockedUploadFile.mockImplementation(() => diff --git a/lib/__tests__/fileMapper.test.ts b/lib/__tests__/fileMapper.test.ts index a9910ad6..d0de3e9d 100644 --- a/lib/__tests__/fileMapper.test.ts +++ b/lib/__tests__/fileMapper.test.ts @@ -8,21 +8,22 @@ import { fetchFolderFromApi, getTypeDataFromPath, downloadFileOrFolder, -} from '../fileMapper'; +} from '../fileMapper.js'; import { download as __download, fetchFileStream as __fetchFileStream, -} from '../../api/fileMapper'; -import folderWithoutSources from './fixtures/fileMapper/folderWithoutSources.json'; -import { mockAxiosResponse } from './__utils__/mockAxiosResponse'; +} from '../../api/fileMapper.js'; +import folderWithoutSources from './fixtures/fileMapper/folderWithoutSources.json' with { type: 'json' }; +import { mockAxiosResponse } from './__utils__/mockAxiosResponse.js'; +import { vi, type MockedFunction } from 'vitest'; -jest.mock('../../api/fileMapper'); -const utimesSpy = jest.spyOn(fs, 'utimes'); -const ensureDirSpy = jest.spyOn(fs, 'ensureDir'); -const pathExistsSpy = jest.spyOn(fs, 'pathExists'); +vi.mock('../../api/fileMapper'); +const utimesSpy = vi.spyOn(fs, 'utimes'); +const ensureDirSpy = vi.spyOn(fs, 'ensureDir'); +const pathExistsSpy = vi.spyOn(fs, 'pathExists'); -const download = __download as jest.MockedFunction; -const fetchFileStream = __fetchFileStream as jest.MockedFunction< +const download = __download as MockedFunction; +const fetchFileStream = __fetchFileStream as MockedFunction< typeof __fetchFileStream >; diff --git a/lib/__tests__/fs.test.ts b/lib/__tests__/fs.test.ts index abb810cd..654add41 100644 --- a/lib/__tests__/fs.test.ts +++ b/lib/__tests__/fs.test.ts @@ -1,8 +1,9 @@ import fs, { Stats } from 'fs'; -import { flattenAndRemoveSymlinks, getFileInfoAsync } from '../fs'; -import { STAT_TYPES } from '../../constants/files'; +import { flattenAndRemoveSymlinks, getFileInfoAsync } from '../fs.js'; +import { STAT_TYPES } from '../../constants/files.js'; +import { vi } from 'vitest'; -jest.mock('fs'); +vi.mock('fs'); function buildLstatMock(opts: { isFile?: boolean; @@ -25,8 +26,8 @@ function buildLstatMock(opts: { describe('lib/fs', () => { describe('getFileInfoAsync()', () => { it('returns filepath and type for files', async () => { - (fs.lstat as unknown as jest.Mock).mockImplementation( - buildLstatMock({ isFile: true }) + vi.mocked(fs.lstat).mockImplementation( + buildLstatMock({ isFile: true }) as typeof fs.lstat ); const fileData = await getFileInfoAsync('modules', 'module.html'); @@ -35,8 +36,8 @@ describe('lib/fs', () => { }); it('returns filepath and type for directories', async () => { - (fs.lstat as unknown as jest.Mock).mockImplementation( - buildLstatMock({ isDirectory: true }) + vi.mocked(fs.lstat).mockImplementation( + buildLstatMock({ isDirectory: true }) as typeof fs.lstat ); const fileData = await getFileInfoAsync('modules', 'module.html'); @@ -45,8 +46,8 @@ describe('lib/fs', () => { }); it('returns filepath and type for symbolic links', async () => { - (fs.lstat as unknown as jest.Mock).mockImplementation( - buildLstatMock({ isSymbolicLink: true }) + vi.mocked(fs.lstat).mockImplementation( + buildLstatMock({ isSymbolicLink: true }) as typeof fs.lstat ); const fileData = await getFileInfoAsync('modules', 'module.html'); diff --git a/lib/__tests__/functions.test.ts b/lib/__tests__/functions.test.ts index 1a1a9e83..4ea0d04b 100644 --- a/lib/__tests__/functions.test.ts +++ b/lib/__tests__/functions.test.ts @@ -1,32 +1,32 @@ import fs, { PathLike } from 'fs-extra'; import findup from 'findup-sync'; -import { getCwd } from '../path'; -import { fetchFileFromRepository as __fetchFileFromRepository } from '../github'; +import { getCwd } from '../path.js'; +import { fetchFileFromRepository as __fetchFileFromRepository } from '../github.js'; +import { vi, type MockedFunction } from 'vitest'; import { createFunction, isObjectOrFunction, createEndpoint, createConfig, -} from '../cms/functions'; +} from '../cms/functions.js'; -jest.mock('fs-extra'); -jest.mock('findup-sync'); -jest.mock('../path'); -jest.mock('../github'); +vi.mock('fs-extra'); +vi.mock('findup-sync'); +vi.mock('../path'); +vi.mock('../github'); -const mockedGetCwd = getCwd as jest.MockedFunction; -const mockedFindup = findup as jest.MockedFunction; -const mockedFsExistsSync = fs.existsSync as jest.MockedFunction< +const mockedGetCwd = getCwd as MockedFunction; +const mockedFindup = findup as MockedFunction; +const mockedFsExistsSync = fs.existsSync as MockedFunction< typeof fs.existsSync >; -const mockedFsReadFileSync = fs.readFileSync as jest.MockedFunction< +const mockedFsReadFileSync = fs.readFileSync as MockedFunction< typeof fs.readFileSync >; -const fetchFileFromRepository = - __fetchFileFromRepository as jest.MockedFunction< - typeof __fetchFileFromRepository - >; +const fetchFileFromRepository = __fetchFileFromRepository as MockedFunction< + typeof __fetchFileFromRepository +>; describe('lib/cms/functions', () => { describe('createFunction', () => { @@ -39,6 +39,14 @@ describe('lib/cms/functions', () => { const mockDest = '/mock/dest'; beforeEach(() => { + mockedGetCwd.mockReset(); + mockedFindup.mockReset(); + mockedFsExistsSync.mockReset(); + mockedFsReadFileSync.mockReset(); + vi.mocked(fs.mkdirp).mockReset(); + vi.mocked(fs.writeFile).mockReset(); + fetchFileFromRepository.mockReset(); + mockedGetCwd.mockReturnValue('/mock/cwd'); mockedFindup.mockReturnValue(null); @@ -98,7 +106,6 @@ describe('lib/cms/functions', () => { }); it('should return true for functions', () => { - // eslint-disable-next-line @typescript-eslint/no-empty-function expect(isObjectOrFunction(() => {})).toBe(true); }); diff --git a/lib/__tests__/github.test.ts b/lib/__tests__/github.test.ts index 4b1fe0bd..3f74ecf6 100644 --- a/lib/__tests__/github.test.ts +++ b/lib/__tests__/github.test.ts @@ -5,37 +5,36 @@ import { listGithubRepoContents, fetchFileFromRepository, fetchGitHubRepoContentFromDownloadUrl, -} from '../github'; +} from '../github.js'; import { fetchRepoFile as __fetchRepoFile, fetchRepoReleaseData as __fetchRepoReleaseData, fetchRepoAsZip as __fetchRepoAsZip, fetchRepoContents as __fetchRepoContents, fetchRepoFileByDownloadUrl as __fetchRepoFileByDownloadUrl, -} from '../../api/github'; -import { extractZipArchive as __extractZipArchive } from '../archive'; +} from '../../api/github.js'; +import { extractZipArchive as __extractZipArchive } from '../archive.js'; +import { vi, type MockedFunction } from 'vitest'; -jest.mock('fs-extra'); -jest.mock('../../api/github'); -jest.mock('../archive'); +vi.mock('fs-extra'); +vi.mock('../../api/github'); +vi.mock('../archive'); -const fetchRepoFile = __fetchRepoFile as jest.MockedFunction< - typeof __fetchRepoFile ->; -const fetchRepoReleaseData = __fetchRepoReleaseData as jest.MockedFunction< +const fetchRepoFile = __fetchRepoFile as MockedFunction; +const fetchRepoReleaseData = __fetchRepoReleaseData as MockedFunction< typeof __fetchRepoReleaseData >; -const fetchRepoAsZip = __fetchRepoAsZip as jest.MockedFunction< +const fetchRepoAsZip = __fetchRepoAsZip as MockedFunction< typeof __fetchRepoAsZip >; -const fetchRepoContents = __fetchRepoContents as jest.MockedFunction< +const fetchRepoContents = __fetchRepoContents as MockedFunction< typeof __fetchRepoContents >; -const extractZipArchive = __extractZipArchive as jest.MockedFunction< +const extractZipArchive = __extractZipArchive as MockedFunction< typeof __extractZipArchive >; const fetchRepoFileByDownloadUrl = - __fetchRepoFileByDownloadUrl as jest.MockedFunction< + __fetchRepoFileByDownloadUrl as MockedFunction< typeof __fetchRepoFileByDownloadUrl >; diff --git a/lib/__tests__/gitignore.test.ts b/lib/__tests__/gitignore.test.ts index 710dcc12..5ccbc82a 100644 --- a/lib/__tests__/gitignore.test.ts +++ b/lib/__tests__/gitignore.test.ts @@ -1,40 +1,39 @@ -import { DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME } from '../../constants/config'; - -jest.mock('../../utils/git'); -jest.mock('path'); -jest.mock('fs-extra'); -jest.mock('path'); +import { vi } from 'vitest'; +import { DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME } from '../../constants/config.js'; + +vi.mock('../../utils/git'); +vi.mock('path'); + +vi.mock('fs-extra', () => { + const mockReadFileSync = vi.fn(); + const mockWriteFileSync = vi.fn(); + return { + default: { + readFileSync: mockReadFileSync, + writeFileSync: mockWriteFileSync, + }, + readFileSync: mockReadFileSync, + writeFileSync: mockWriteFileSync, + }; +}); import { configFilenameIsIgnoredByGitignore, getGitignoreFiles, isConfigPathInGitRepo, -} from '../../utils/git'; -import { checkAndAddConfigToGitignore, checkGitInclusion } from '../gitignore'; +} from '../../utils/git.js'; +import { + checkAndAddConfigToGitignore, + checkGitInclusion, +} from '../gitignore.js'; import { readFileSync, writeFileSync } from 'fs-extra'; import path from 'path'; -const isConfigPathInGitRepoMock = isConfigPathInGitRepo as jest.MockedFunction< - typeof isConfigPathInGitRepo ->; -const getGitignoreFilesMock = getGitignoreFiles as jest.MockedFunction< - typeof getGitignoreFiles ->; -const configFilenameIsIgnoredByGitignoreMock = - configFilenameIsIgnoredByGitignore as jest.MockedFunction< - typeof configFilenameIsIgnoredByGitignore - >; - -const pathResolveMock = path.resolve as jest.MockedFunction< - typeof path.resolve ->; -const pathDirnameMock = path.dirname as jest.MockedFunction< - typeof path.dirname ->; -const pathJoinMock = path.join as jest.MockedFunction; -const readFileSyncMock = readFileSync as jest.MockedFunction< - typeof readFileSync ->; +const isConfigPathInGitRepoMock = vi.mocked(isConfigPathInGitRepo); +const getGitignoreFilesMock = vi.mocked(getGitignoreFiles); +const configFilenameIsIgnoredByGitignoreMock = vi.mocked( + configFilenameIsIgnoredByGitignore +); describe('lib/gitignore', () => { const gitIgnoreFiles = ['/some/cool/file', 'some/other/file']; @@ -47,10 +46,12 @@ describe('lib/gitignore', () => { isConfigPathInGitRepoMock.mockReturnValue(true); getGitignoreFilesMock.mockReturnValue(gitIgnoreFiles); configFilenameIsIgnoredByGitignoreMock.mockReturnValue(false); - pathResolveMock.mockReturnValue(pathResolveReturnValue); - pathDirnameMock.mockReturnValue(configDirectoryPath); - pathJoinMock.mockReturnValue(pathResolveReturnValue); - readFileSyncMock.mockReturnValue(fileContents); + + vi.mocked(path.resolve).mockReturnValue(pathResolveReturnValue); + vi.mocked(path.dirname).mockReturnValue(configDirectoryPath); + vi.mocked(path.join).mockReturnValue(pathResolveReturnValue); + + vi.mocked(readFileSync).mockReturnValue(fileContents); }); describe('checkAndAddConfigToGitIgnore', () => { @@ -88,7 +89,7 @@ describe('lib/gitignore', () => { isConfigPathInGitRepoMock.mockReturnValue(true); configFilenameIsIgnoredByGitignoreMock.mockReturnValue(false); const error = new Error('OH NO'); - readFileSyncMock.mockImplementationOnce(() => { + vi.mocked(readFileSync).mockImplementationOnce(() => { throw error; }); diff --git a/lib/__tests__/handleFieldsJS.test.ts b/lib/__tests__/handleFieldsJS.test.ts index 532efb61..8900699c 100644 --- a/lib/__tests__/handleFieldsJS.test.ts +++ b/lib/__tests__/handleFieldsJS.test.ts @@ -1,36 +1,37 @@ -import { FieldsJs, isConvertableFieldJs } from '../cms/handleFieldsJS'; +import { FieldsJs, isConvertableFieldJs } from '../cms/handleFieldsJS.js'; import fs from 'fs-extra'; import child_process from 'child_process'; +import { vi } from 'vitest'; -jest.mock('../fs'); -jest.mock('child_process'); +vi.mock('../fs'); +vi.mock('child_process'); describe('lib/cms/handleFieldsJs', () => { describe('FieldsJs()', () => { beforeEach(() => { - (child_process.fork as jest.Mock).mockImplementation(() => { + vi.mocked(child_process.fork).mockImplementation(() => { return { pid: 123, on: () => { return {}; }, - }; + } as unknown as ReturnType; }); - jest.resetModules(); + vi.resetModules(); }); const projectRoot = 'folder'; const filePath = 'folder/sample.module/fields.js'; const defaultFieldsJs = new FieldsJs(projectRoot, filePath); - jest.spyOn(fs, 'existsSync').mockReturnValue(true); + vi.spyOn(fs, 'existsSync').mockReturnValue(true); - test('getOutputPathPromise() resolves to the correct path', async () => { + it('getOutputPathPromise() resolves to the correct path', async () => { const fieldsJs = new FieldsJs( 'folder', 'folder/sample.module/fields.js', 'temp-dir' ); - const convertSpy = jest + const convertSpy = vi .spyOn(FieldsJs.prototype, 'convertFieldsJs') .mockResolvedValue('temp-dir/sample.module/fields.js'); @@ -39,7 +40,7 @@ describe('lib/cms/handleFieldsJs', () => { convertSpy.mockRestore(); }); - test('getWriteDir() returns the correct path', () => { + it('getWriteDir() returns the correct path', () => { const fieldsJs = new FieldsJs( 'folder', 'folder/sample.module/fields.js', @@ -49,8 +50,8 @@ describe('lib/cms/handleFieldsJs', () => { expect(returned).toBe('temp-dir/sample.module'); }); - test('saveOutput() sets the save path correctly', () => { - const copyFileSpy = jest + it('saveOutput() sets the save path correctly', () => { + const copyFileSpy = vi .spyOn(fs, 'copyFileSync') .mockImplementation(() => null); const fieldsJs = new FieldsJs( @@ -68,7 +69,7 @@ describe('lib/cms/handleFieldsJs', () => { ); }); - test('convertFieldsJs returns a Promise', () => { + it('convertFieldsJs returns a Promise', () => { const returned = defaultFieldsJs.convertFieldsJs(''); expect(returned).toBeInstanceOf(Promise); }); diff --git a/lib/__tests__/hubdb.test.ts b/lib/__tests__/hubdb.test.ts index deec787d..df2b5bd2 100644 --- a/lib/__tests__/hubdb.test.ts +++ b/lib/__tests__/hubdb.test.ts @@ -3,35 +3,34 @@ import { downloadHubDbTable, createHubDbTable, clearHubDbTableRows, -} from '../hubdb'; +} from '../hubdb.js'; import { createRows as __createRows, createTable as __createTable, fetchRows as __fetchRows, fetchTable as __fetchTable, publishTable as __publishTable, -} from '../../api/hubdb'; -import { getCwd as __getCwd } from '../path'; -import hubdbFetchRowResponse from './fixtures/hubdb/fetchRowsResponse.json'; -import hubdbFetchRowsResponseWithPaging from './fixtures/hubdb/fetchRowsResponseWithPaging.json'; -import hubdbTableResponse from './fixtures/hubdb/tableResponse.json'; -import hubdbCreateRowsResponse from './fixtures/hubdb/createRowsResponse.json'; -import { Table } from '../../types/Hubdb'; -import { mockAxiosResponse } from './__utils__/mockAxiosResponse'; - -jest.mock('fs-extra'); -jest.mock('../path'); -jest.mock('../../api/hubdb'); - -const mockedFS = fs as jest.Mocked; -const getCwd = __getCwd as jest.MockedFunction; -const createRows = __createRows as jest.MockedFunction; -const createTable = __createTable as jest.MockedFunction; -const fetchRows = __fetchRows as jest.MockedFunction; -const fetchTable = __fetchTable as jest.MockedFunction; -const publishTable = __publishTable as jest.MockedFunction< - typeof __publishTable ->; +} from '../../api/hubdb.js'; +import { getCwd as __getCwd } from '../path.js'; +import hubdbFetchRowResponse from './fixtures/hubdb/fetchRowsResponse.json' with { type: 'json' }; +import hubdbFetchRowsResponseWithPaging from './fixtures/hubdb/fetchRowsResponseWithPaging.json' with { type: 'json' }; +import hubdbTableResponse from './fixtures/hubdb/tableResponse.json' with { type: 'json' }; +import hubdbCreateRowsResponse from './fixtures/hubdb/createRowsResponse.json' with { type: 'json' }; +import { Table } from '../../types/Hubdb.js'; +import { mockAxiosResponse } from './__utils__/mockAxiosResponse.js'; +import { vi, type MockedFunction } from 'vitest'; + +vi.mock('fs-extra'); +vi.mock('../path'); +vi.mock('../../api/hubdb'); + +const mockedFS = vi.mocked(fs); +const getCwd = __getCwd as MockedFunction; +const createRows = __createRows as MockedFunction; +const createTable = __createTable as MockedFunction; +const fetchRows = __fetchRows as MockedFunction; +const fetchTable = __fetchTable as MockedFunction; +const publishTable = __publishTable as MockedFunction; describe('lib/hubdb', () => { describe('downloadHubDbTable()', () => { diff --git a/lib/__tests__/ignoreRules.test.ts b/lib/__tests__/ignoreRules.test.ts index 11b9d260..0f9403d1 100644 --- a/lib/__tests__/ignoreRules.test.ts +++ b/lib/__tests__/ignoreRules.test.ts @@ -1,4 +1,4 @@ -import { shouldIgnoreFile } from '../ignoreRules'; +import { shouldIgnoreFile } from '../ignoreRules.js'; const CWD = '/Path/To/My/Repo'; const REPO_FOLDER = `${CWD}/repo-name-here`; diff --git a/lib/__tests__/isDeepEqual.test.ts b/lib/__tests__/isDeepEqual.test.ts index d91c7916..674e1964 100644 --- a/lib/__tests__/isDeepEqual.test.ts +++ b/lib/__tests__/isDeepEqual.test.ts @@ -1,4 +1,4 @@ -import { isDeepEqual } from '../isDeepEqual'; +import { isDeepEqual } from '../isDeepEqual.js'; describe('isDeepEqual', () => { describe('primitive values', () => { diff --git a/lib/__tests__/logger.test.ts b/lib/__tests__/logger.test.ts index ffc7c5a7..53f69c6d 100644 --- a/lib/__tests__/logger.test.ts +++ b/lib/__tests__/logger.test.ts @@ -1,4 +1,5 @@ import chalk from 'chalk'; +import { vi, MockInstance } from 'vitest'; import { Styles, stylize, @@ -7,7 +8,7 @@ import { LOG_LEVEL, logger, shouldLog, -} from '../logger'; +} from '../logger.js'; describe('lib/logger', () => { afterEach(() => { @@ -18,7 +19,7 @@ describe('lib/logger', () => { it('stylizes input', () => { const res = stylize('[ERROR]', Styles.error, ['test']); - expect(res[0]).toEqual(`${chalk.reset.red('[ERROR]')} test`); + expect(res[0]).toEqual(`${chalk.red('[ERROR]')} test`); }); }); @@ -78,21 +79,21 @@ describe('lib/logger', () => { }); describe('logger()', () => { - let warnSpy: jest.SpyInstance; - let logSpy: jest.SpyInstance; - let debugSpy: jest.SpyInstance; - let infoSpy: jest.SpyInstance; - let groupSpy: jest.SpyInstance; - let groupEndSpy: jest.SpyInstance; + let warnSpy: MockInstance; + let logSpy: MockInstance; + let debugSpy: MockInstance; + let infoSpy: MockInstance; + let groupSpy: MockInstance; + let groupEndSpy: MockInstance; beforeEach(() => { setLogLevel(LOG_LEVEL.LOG); - warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => null); - logSpy = jest.spyOn(console, 'log').mockImplementation(() => null); - debugSpy = jest.spyOn(console, 'debug').mockImplementation(() => null); - infoSpy = jest.spyOn(console, 'info').mockImplementation(() => null); - groupSpy = jest.spyOn(console, 'group').mockImplementation(() => null); - groupEndSpy = jest + warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => null); + logSpy = vi.spyOn(console, 'log').mockImplementation(() => null); + debugSpy = vi.spyOn(console, 'debug').mockImplementation(() => null); + infoSpy = vi.spyOn(console, 'info').mockImplementation(() => null); + groupSpy = vi.spyOn(console, 'group').mockImplementation(() => null); + groupEndSpy = vi .spyOn(console, 'groupEnd') .mockImplementation(() => null); }); diff --git a/lib/__tests__/modules.test.ts b/lib/__tests__/modules.test.ts index 132afbff..0ba5c22f 100644 --- a/lib/__tests__/modules.test.ts +++ b/lib/__tests__/modules.test.ts @@ -1,9 +1,9 @@ import path from 'path'; -import { validateSrcAndDestPaths, ValidationIds } from '../cms/modules'; +import { validateSrcAndDestPaths, ValidationIds } from '../cms/modules.js'; -import { PathInput } from '../../types/Modules'; -import { ValueOf } from '../../types/Utils'; +import { PathInput } from '../../types/Modules.js'; +import { ValueOf } from '../../types/Utils.js'; const isLocal = true; const isHubSpot = true; diff --git a/lib/__tests__/notify.test.ts b/lib/__tests__/notify.test.ts index 32d45281..dafa4aa0 100644 --- a/lib/__tests__/notify.test.ts +++ b/lib/__tests__/notify.test.ts @@ -1,7 +1,8 @@ -jest.mock('fs'); -import { triggerNotify } from '../notify'; +vi.mock('fs'); +import { triggerNotify } from '../notify.js'; import fs from 'fs'; -const fsAppendSyncMock = fs.appendFileSync as jest.MockedFunction< +import { vi, type MockedFunction } from 'vitest'; +const fsAppendSyncMock = fs.appendFileSync as MockedFunction< typeof fs.appendFileSync >; @@ -10,7 +11,7 @@ describe('lib/notify', () => { beforeEach(() => { now = Date.now(); // Use fake timers so we can test the debounce functionality - jest.useFakeTimers({ now }); + vi.useFakeTimers({ now }); }); describe('triggerNotify', () => { const filePathToNotify = '/my/super/cool/file/to/notify'; @@ -28,7 +29,7 @@ describe('lib/notify', () => { ); // Advance all of the timers to trigger the debounce - await jest.runAllTimersAsync(); + await vi.runAllTimersAsync(); expect(fsAppendSyncMock).toHaveBeenCalledTimes(1); expect(fsAppendSyncMock).toHaveBeenCalledWith( @@ -55,7 +56,7 @@ describe('lib/notify', () => { } // Advance all of the timers to trigger the debounce - await jest.runAllTimersAsync(); + await vi.runAllTimersAsync(); // Make sure the 10 calls still only lead to 1 write expect(fsAppendSyncMock).toHaveBeenCalledTimes(1); diff --git a/lib/__tests__/oauth.test.ts b/lib/__tests__/oauth.test.ts index 62a6f87c..96238eae 100644 --- a/lib/__tests__/oauth.test.ts +++ b/lib/__tests__/oauth.test.ts @@ -1,18 +1,19 @@ -import { addOauthToAccountConfig, getOauthManager } from '../oauth'; +import { addOauthToAccountConfig, getOauthManager } from '../oauth.js'; -jest.mock('../../config/getAccountIdentifier'); -jest.mock('../../config'); -jest.mock('../logger'); -jest.mock('../../errors'); +vi.mock('../../config/getAccountIdentifier'); +vi.mock('../../config'); +vi.mock('../logger'); +vi.mock('../../errors'); -import { updateAccountConfig, writeConfig } from '../../config'; -import { OAuth2Manager } from '../../models/OAuth2Manager'; -import { FlatAccountFields_NEW } from '../../types/Accounts'; -import { ENVIRONMENTS } from '../../constants/environments'; -import { AUTH_METHODS } from '../../constants/auth'; -import { logger } from '../logger'; +import { updateAccountConfig, writeConfig } from '../../config/index.js'; +import { OAuth2Manager } from '../../models/OAuth2Manager.js'; +import { FlatAccountFields_NEW } from '../../types/Accounts.js'; +import { ENVIRONMENTS } from '../../constants/environments.js'; +import { AUTH_METHODS } from '../../constants/auth.js'; +import { logger } from '../logger.js'; +import { vi } from 'vitest'; -const OAuth2ManagerFromConfigMock = jest.spyOn(OAuth2Manager, 'fromConfig'); +const OAuth2ManagerFromConfigMock = vi.spyOn(OAuth2Manager, 'fromConfig'); describe('lib/oauth', () => { const accountId = 123; diff --git a/lib/__tests__/path.test.ts b/lib/__tests__/path.test.ts index 950b7661..06c7f81e 100644 --- a/lib/__tests__/path.test.ts +++ b/lib/__tests__/path.test.ts @@ -1,6 +1,7 @@ import os from 'os'; import { convertToUnixPath, + convertToLocalFileSystemPath, splitLocalPath, splitHubSpotPath, getCwd, @@ -10,33 +11,55 @@ import { sanitizeFileName, isValidPath, untildify, -} from '../path'; -import { ALLOWED_EXTENSIONS } from '../../constants/extensions'; +} from '../path.js'; +import { ALLOWED_EXTENSIONS } from '../../constants/extensions.js'; +import { vi } from 'vitest'; -jest.mock('os', () => ({ - homedir: jest.fn(), +vi.mock('os', () => ({ + default: { + homedir: vi.fn(), + }, })); -jest.mock('path', () => ({ - ...jest.requireActual('path'), - sep: '/', - posix: { - sep: '/', - }, - win32: { - sep: '\\', - }, +// Create a controllable mock for path.sep +const mockPath = vi.hoisted(() => { + let mockSep = '/'; + // eslint-disable-next-line @typescript-eslint/no-require-imports + const actualPath = require('path'); + + return { + ...actualPath, + get sep() { + return mockSep; + }, + setSep: (newSep: string) => { + mockSep = newSep; + }, + posix: { sep: '/' }, + win32: { sep: '\\' }, + normalize: (p: string) => { + if (mockSep === '\\') { + return p.replace(/\//g, '\\'); + } + return actualPath.normalize(p); + }, + }; +}); + +vi.mock('path', () => ({ + default: mockPath, + ...mockPath, })); describe('path utility functions', () => { describe('convertToUnixPath()', () => { - test('converts Windows path to Unix path', () => { + it('converts Windows path to Unix path', () => { expect(convertToUnixPath('C:\\Users\\test\\file.txt')).toBe( '/Users/test/file.txt' ); }); - test('normalizes Unix path', () => { + it('normalizes Unix path', () => { expect(convertToUnixPath('/home//user/./file.txt')).toBe( '/home/user/file.txt' ); @@ -45,20 +68,19 @@ describe('path utility functions', () => { describe('convertToLocalFileSystemPath()', () => { afterEach(() => { - jest.resetModules(); + // Reset to Unix separator + mockPath.setSep('/'); }); - test('converts to Unix path when on Unix-like system', async () => { - jest.doMock('path', () => ({ ...jest.requireActual('path'), sep: '/' })); - const { convertToLocalFileSystemPath } = await import('../path'); + it('converts to Unix path when on Unix-like system', () => { + mockPath.setSep('/'); 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'); + it('converts to Windows path when on Windows system', () => { + mockPath.setSep('\\'); expect(convertToLocalFileSystemPath('C:/Users/test/file.txt')).toBe( 'C:\\Users\\test\\file.txt' ); @@ -66,7 +88,7 @@ describe('path utility functions', () => { }); describe('splitLocalPath()', () => { - test('splits Unix path correctly', () => { + it('splits Unix path correctly', () => { expect( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -77,7 +99,7 @@ describe('path utility functions', () => { ).toEqual(['/', 'home', 'user', 'file.txt']); }); - test('splits Windows path correctly', () => { + it('splits Windows path correctly', () => { expect( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -88,13 +110,13 @@ describe('path utility functions', () => { ).toEqual(['C:', 'Users', 'test', 'file.txt']); }); - test('handles empty path', () => { + it('handles empty path', () => { expect(splitLocalPath('')).toEqual([]); }); }); describe('splitHubSpotPath()', () => { - test('splits HubSpot path correctly', () => { + it('splits HubSpot path correctly', () => { expect(splitHubSpotPath('/project/My Module.module/js/main.js')).toEqual([ '/', 'project', @@ -104,11 +126,11 @@ describe('path utility functions', () => { ]); }); - test('handles root path', () => { + it('handles root path', () => { expect(splitHubSpotPath('/')).toEqual(['/']); }); - test('handles empty path', () => { + it('handles empty path', () => { expect(splitHubSpotPath('')).toEqual([]); }); }); @@ -119,7 +141,7 @@ describe('path utility functions', () => { beforeEach(() => { process.env = { ...originalEnv }; - process.cwd = jest.fn().mockReturnValue('/mocked/cwd'); + process.cwd = vi.fn().mockReturnValue('/mocked/cwd'); }); afterEach(() => { @@ -127,27 +149,27 @@ describe('path utility functions', () => { process.cwd = originalCwd; }); - test('returns INIT_CWD if set', () => { + it('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', () => { + it('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', () => { + it('returns lowercase extension without dot', () => { expect(getExt('file.TXT')).toBe('txt'); }); - test('returns empty string for no extension', () => { + it('returns empty string for no extension', () => { expect(getExt('file')).toBe(''); }); - test('handles non-string input', () => { + it('handles non-string input', () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore expect(getExt(null as '')).toBe(''); @@ -155,56 +177,56 @@ describe('path utility functions', () => { }); describe('getAllowedExtensions()', () => { - test('returns default allowed extensions', () => { + it('returns default allowed extensions', () => { const result = getAllowedExtensions(); expect(result).toBeInstanceOf(Set); expect(result).toEqual(new Set(ALLOWED_EXTENSIONS)); }); - test('includes additional extensions', () => { + it('includes additional extensions', () => { const result = getAllowedExtensions(['custom']); expect(result.has('custom')).toBe(true); }); }); describe('isAllowedExtension()', () => { - test('returns true for allowed extension', () => { + it('returns true for allowed extension', () => { expect(isAllowedExtension('file.txt')).toBe(true); }); - test('returns false for disallowed extension', () => { + it('returns false for disallowed extension', () => { expect(isAllowedExtension('file.exe')).toBe(false); }); - test('allows custom extensions', () => { + it('allows custom extensions', () => { expect(isAllowedExtension('file.custom', ['custom'])).toBe(true); }); }); describe('sanitizeFileName()', () => { - test('replaces invalid characters', () => { + it('replaces invalid characters', () => { expect(sanitizeFileName('file:name?.txt')).toBe('file-name-.txt'); }); - test('handles reserved names', () => { + it('handles reserved names', () => { expect(sanitizeFileName('CON')).toBe('-CON'); }); - test('removes trailing periods and spaces', () => { + it('removes trailing periods and spaces', () => { expect(sanitizeFileName('file.txt. ')).toBe('file.txt'); }); }); describe('isValidPath()', () => { - test('returns true for valid path', () => { + it('returns true for valid path', () => { expect(isValidPath('/valid/path/file.txt')).toBe(true); }); - test('returns false for path with invalid characters', () => { + it('returns false for path with invalid characters', () => { expect(isValidPath('/invalid/path/file?.txt')).toBe(false); }); - test('returns false for reserved names', () => { + it('returns false for reserved names', () => { expect(isValidPath('/some/path/CON')).toBe(false); }); }); @@ -213,26 +235,27 @@ describe('path utility functions', () => { const originalHomedir = os.homedir; beforeEach(() => { - (os.homedir as jest.Mock) = jest.fn().mockReturnValue('/home/user'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (os.homedir as any) = vi.fn().mockReturnValue('/home/user'); }); afterEach(() => { os.homedir = originalHomedir; }); - test('replaces tilde with home directory', () => { + it('replaces tilde with home directory', () => { expect(untildify('~/documents/file.txt')).toBe( '/home/user/documents/file.txt' ); }); - test('does not modify paths without tilde', () => { + it('does not modify paths without tilde', () => { expect(untildify('/absolute/path/file.txt')).toBe( '/absolute/path/file.txt' ); }); - test('throws TypeError for non-string input', () => { + it('throws TypeError for non-string input', () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore expect(() => untildify(null as '')).toThrow(TypeError); diff --git a/lib/__tests__/personalAccessKey.test.ts b/lib/__tests__/personalAccessKey.test.ts index 028c9838..7b749d74 100644 --- a/lib/__tests__/personalAccessKey.test.ts +++ b/lib/__tests__/personalAccessKey.test.ts @@ -3,44 +3,44 @@ import { getAndLoadConfigIfNeeded as __getAndLoadConfigIfNeeded, getAccountConfig as __getAccountConfig, updateAccountConfig as __updateAccountConfig, -} from '../../config'; -import { fetchAccessToken as __fetchAccessToken } from '../../api/localDevAuth'; -import { fetchSandboxHubData as __fetchSandboxHubData } from '../../api/sandboxHubs'; -import { fetchDeveloperTestAccountData as __fetchDeveloperTestAccountData } from '../../api/developerTestAccounts'; -import { ENVIRONMENTS } from '../../constants/environments'; -import { HUBSPOT_ACCOUNT_TYPES } from '../../constants/config'; +} from '../../config/index.js'; +import { fetchAccessToken as __fetchAccessToken } from '../../api/localDevAuth.js'; +import { fetchSandboxHubData as __fetchSandboxHubData } from '../../api/sandboxHubs.js'; +import { fetchDeveloperTestAccountData as __fetchDeveloperTestAccountData } from '../../api/developerTestAccounts.js'; +import { ENVIRONMENTS } from '../../constants/environments.js'; +import { HUBSPOT_ACCOUNT_TYPES } from '../../constants/config.js'; import { accessTokenForPersonalAccessKey, getAccessToken, updateConfigWithAccessToken, -} from '../personalAccessKey'; -import { AuthType } from '../../types/Accounts'; -import { mockAxiosResponse } from './__utils__/mockAxiosResponse'; +} from '../personalAccessKey.js'; +import { AuthType } from '../../types/Accounts.js'; +import { mockAxiosResponse } from './__utils__/mockAxiosResponse.js'; +import { vi, type MockedFunction } from 'vitest'; -jest.mock('../../config'); -jest.mock('../logger'); -jest.mock('../../api/localDevAuth'); -jest.mock('../../api/sandboxHubs'); -jest.mock('../../api/developerTestAccounts'); +vi.mock('../../config'); +vi.mock('../logger'); +vi.mock('../../api/localDevAuth'); +vi.mock('../../api/sandboxHubs'); +vi.mock('../../api/developerTestAccounts'); -const updateAccountConfig = __updateAccountConfig as jest.MockedFunction< +const updateAccountConfig = __updateAccountConfig as MockedFunction< typeof __updateAccountConfig >; -const getAccountConfig = __getAccountConfig as jest.MockedFunction< +const getAccountConfig = __getAccountConfig as MockedFunction< typeof __getAccountConfig >; -const getAndLoadConfigIfNeeded = - __getAndLoadConfigIfNeeded as jest.MockedFunction< - typeof __getAndLoadConfigIfNeeded - >; -const fetchAccessToken = __fetchAccessToken as jest.MockedFunction< +const getAndLoadConfigIfNeeded = __getAndLoadConfigIfNeeded as MockedFunction< + typeof __getAndLoadConfigIfNeeded +>; +const fetchAccessToken = __fetchAccessToken as MockedFunction< typeof __fetchAccessToken >; -const fetchSandboxHubData = __fetchSandboxHubData as jest.MockedFunction< +const fetchSandboxHubData = __fetchSandboxHubData as MockedFunction< typeof __fetchSandboxHubData >; const fetchDeveloperTestAccountData = - __fetchDeveloperTestAccountData as jest.MockedFunction< + __fetchDeveloperTestAccountData as MockedFunction< typeof __fetchDeveloperTestAccountData >; diff --git a/lib/__tests__/portManager.test.ts b/lib/__tests__/portManager.test.ts index ee4e442a..d367c34c 100644 --- a/lib/__tests__/portManager.test.ts +++ b/lib/__tests__/portManager.test.ts @@ -1,5 +1,5 @@ -import { MAX_PORT_NUMBER } from '../../constants/ports'; -import { PortManagerServer } from '../../utils/PortManagerServer'; +import { MAX_PORT_NUMBER } from '../../constants/ports.js'; +import { PortManagerServer } from '../../utils/PortManagerServer.js'; import { deleteServerInstance, getServerPortByInstanceId, @@ -7,7 +7,7 @@ import { requestPorts, startPortManagerServer, stopPortManagerServer, -} from '../portManager'; +} from '../portManager.js'; const INSTANCE_ID_1 = 'test1'; const INSTANCE_ID_2 = 'test2'; @@ -38,6 +38,14 @@ const BAD_PORT_DATA = [ ]; describe('lib/portManager', () => { + beforeEach(async () => { + await PortManagerServer.close(); + }); + + afterEach(async () => { + await PortManagerServer.close(); + }); + describe('startPortManagerServer()', () => { it('starts the PortManagerServer', async () => { expect(PortManagerServer.server).toBeUndefined(); @@ -73,9 +81,6 @@ describe('lib/portManager', () => { beforeEach(async () => { await startPortManagerServer(); }); - afterEach(async () => { - await stopPortManagerServer(); - }); it('returns ports when none are specified', async () => { const portData = await requestPorts(EMPTY_PORT_DATA); expect(typeof portData[INSTANCE_ID_1]).toBe('number'); @@ -111,9 +116,6 @@ describe('lib/portManager', () => { beforeEach(async () => { await startPortManagerServer(); }); - afterEach(async () => { - await stopPortManagerServer(); - }); it('deletes port data for a server instance', async () => { await requestPorts(PORT_DATA); @@ -131,9 +133,6 @@ describe('lib/portManager', () => { beforeEach(async () => { await startPortManagerServer(); }); - afterEach(async () => { - await stopPortManagerServer(); - }); it('returns false when no servers are active', async () => { const hasActiveServers = await portManagerHasActiveServers(); @@ -151,9 +150,6 @@ describe('lib/portManager', () => { beforeEach(async () => { await startPortManagerServer(); }); - afterEach(async () => { - await stopPortManagerServer(); - }); it('returns the port for known server instances', async () => { await requestPorts(PORT_DATA); diff --git a/lib/__tests__/templates.test.ts b/lib/__tests__/templates.test.ts index 1f83ec7f..067f8499 100644 --- a/lib/__tests__/templates.test.ts +++ b/lib/__tests__/templates.test.ts @@ -3,13 +3,14 @@ import { getAnnotationValue, isCodedFile, createTemplate, -} from '../cms/templates'; -import { cloneGithubRepo as __cloneGithubRepo } from '../github'; +} from '../cms/templates.js'; +import { cloneGithubRepo as __cloneGithubRepo } from '../github.js'; +import { vi, type MockedFunction } from 'vitest'; -jest.mock('fs-extra'); -jest.mock('../github'); +vi.mock('fs-extra'); +vi.mock('../github'); -const cloneGithubRepo = __cloneGithubRepo as jest.MockedFunction< +const cloneGithubRepo = __cloneGithubRepo as MockedFunction< typeof __cloneGithubRepo >; @@ -58,8 +59,8 @@ describe('lib/cms/templates', () => { describe('createTemplate()', () => { it('downloads a template from the HubSpot/cms-sample-assets repo', async () => { - jest.spyOn(fs, 'mkdirp').mockReturnValue(); - jest.spyOn(fs, 'existsSync').mockReturnValue(false); + vi.spyOn(fs, 'mkdirp').mockReturnValue(); + vi.spyOn(fs, 'existsSync').mockReturnValue(false); await createTemplate('my-template', '/', 'page-template'); expect(cloneGithubRepo).toHaveBeenCalledWith( diff --git a/lib/__tests__/text.test.ts b/lib/__tests__/text.test.ts index dee4bbc9..d2a60b78 100644 --- a/lib/__tests__/text.test.ts +++ b/lib/__tests__/text.test.ts @@ -1,4 +1,4 @@ -import { commaSeparatedValues, toKebabCase } from '../text'; +import { commaSeparatedValues, toKebabCase } from '../text.js'; describe('lib/text', () => { describe('commaSeparatedValues()', () => { diff --git a/lib/__tests__/themes.test.ts b/lib/__tests__/themes.test.ts index 391d536c..fe007858 100644 --- a/lib/__tests__/themes.test.ts +++ b/lib/__tests__/themes.test.ts @@ -1,25 +1,25 @@ import findup from 'findup-sync'; -import { getHubSpotWebsiteOrigin } from '../urls'; -import { getThemeJSONPath, getThemePreviewUrl } from '../cms/themes'; -import { getEnv } from '../../config'; -import { ENVIRONMENTS } from '../../constants/environments'; - -jest.mock('findup-sync'); -jest.mock('../urls'); -jest.mock('../../config'); -jest.mock('../../constants/environments', () => ({ +import { getHubSpotWebsiteOrigin } from '../urls.js'; +import { getThemeJSONPath, getThemePreviewUrl } from '../cms/themes.js'; +import { getEnv } from '../../config/index.js'; +import { ENVIRONMENTS } from '../../constants/environments.js'; +import { vi, type MockedFunction } from 'vitest'; + +vi.mock('findup-sync'); +vi.mock('../urls'); +vi.mock('../../config'); +vi.mock('../../constants/environments', () => ({ ENVIRONMENTS: { PROD: 'https://prod.hubspot.com', QA: 'https://qa.hubspot.com', }, })); -const mockedFindup = findup as jest.MockedFunction; -const mockedGetEnv = getEnv as jest.MockedFunction; -const mockedGetHubSpotWebsiteOrigin = - getHubSpotWebsiteOrigin as jest.MockedFunction< - typeof getHubSpotWebsiteOrigin - >; +const mockedFindup = findup as MockedFunction; +const mockedGetEnv = getEnv as MockedFunction; +const mockedGetHubSpotWebsiteOrigin = getHubSpotWebsiteOrigin as MockedFunction< + typeof getHubSpotWebsiteOrigin +>; describe('lib/cms/themes', () => { describe('getThemeJSONPath', () => { diff --git a/lib/__tests__/trackUsage.test.ts b/lib/__tests__/trackUsage.test.ts index 3f5557b5..bf1cb646 100644 --- a/lib/__tests__/trackUsage.test.ts +++ b/lib/__tests__/trackUsage.test.ts @@ -1,23 +1,23 @@ import axios from 'axios'; -import { trackUsage } from '../trackUsage'; +import { trackUsage } from '../trackUsage.js'; import { getAccountConfig as __getAccountConfig, getAndLoadConfigIfNeeded as __getAndLoadConfigIfNeeded, -} from '../../config'; -import { AuthType } from '../../types/Accounts'; -import { ENVIRONMENTS } from '../../constants/environments'; +} from '../../config/index.js'; +import { AuthType } from '../../types/Accounts.js'; +import { ENVIRONMENTS } from '../../constants/environments.js'; +import { vi, type MockedFunction } from 'vitest'; -jest.mock('axios'); -jest.mock('../../config'); +vi.mock('axios'); +vi.mock('../../config'); -const mockedAxios = jest.mocked(axios); -const getAccountConfig = __getAccountConfig as jest.MockedFunction< +const mockedAxios = vi.mocked(axios); +const getAccountConfig = __getAccountConfig as MockedFunction< typeof __getAccountConfig >; -const getAndLoadConfigIfNeeded = - __getAndLoadConfigIfNeeded as jest.MockedFunction< - typeof __getAndLoadConfigIfNeeded - >; +const getAndLoadConfigIfNeeded = __getAndLoadConfigIfNeeded as MockedFunction< + typeof __getAndLoadConfigIfNeeded +>; mockedAxios.mockResolvedValue({}); getAndLoadConfigIfNeeded.mockReturnValue({}); diff --git a/lib/__tests__/uploadFolder.test.ts b/lib/__tests__/uploadFolder.test.ts index 988f8bcd..09084726 100644 --- a/lib/__tests__/uploadFolder.test.ts +++ b/lib/__tests__/uploadFolder.test.ts @@ -1,67 +1,71 @@ import path from 'path'; import fs from 'fs-extra'; -import { uploadFolder, getFilesByType } from '../cms/uploadFolder'; -import { FILE_TYPES } from '../../constants/files'; -import { upload as __upload } from '../../api/fileMapper'; -import { walk as __walk } from '../fs'; -import { createIgnoreFilter as __createIgnoreFilter } from '../ignoreRules'; +import { uploadFolder, getFilesByType } from '../cms/uploadFolder.js'; +import { FILE_TYPES } from '../../constants/files.js'; +import { upload as __upload } from '../../api/fileMapper.js'; +import { walk as __walk } from '../fs.js'; +import { createIgnoreFilter as __createIgnoreFilter } from '../ignoreRules.js'; import { FieldsJs as __FieldsJs, isConvertableFieldJs as __isConvertableFields, cleanupTmpDirSync as __cleanupTmpDirSync, createTmpDirSync as __createTmpDirSync, -} from '../cms/handleFieldsJS'; -import { mockAxiosResponse } from './__utils__/mockAxiosResponse'; +} from '../cms/handleFieldsJS.js'; +import { mockAxiosResponse } from './__utils__/mockAxiosResponse.js'; +import { vi, type MockedFunction } from 'vitest'; -jest.mock('../fs'); -jest.mock('../../api/fileMapper'); -jest.mock('../ignoreRules'); -jest.mock('../cms/handleFieldsJS'); +vi.mock('../fs'); +vi.mock('../../api/fileMapper'); +vi.mock('../ignoreRules'); +vi.mock('../cms/handleFieldsJS'); -const listFilesInDir = jest.fn((dir: string) => { +const listFilesInDir = vi.fn((dir: string) => { return fs .readdirSync(dir, { withFileTypes: true }) .filter(file => !file.isDirectory()) .map(file => file.name); }); -const FieldsJs = __FieldsJs as jest.Mock; -const isConvertableFieldJs = __isConvertableFields as jest.MockedFunction< +const FieldsJs = vi.mocked(__FieldsJs); +const isConvertableFieldJs = __isConvertableFields as MockedFunction< typeof __isConvertableFields >; -const createIgnoreFilter = __createIgnoreFilter as jest.MockedFunction< +const createIgnoreFilter = __createIgnoreFilter as MockedFunction< typeof __createIgnoreFilter >; -const createTmpDirSync = __createTmpDirSync as jest.MockedFunction< +const createTmpDirSync = __createTmpDirSync as MockedFunction< typeof __createTmpDirSync >; -const walk = __walk as jest.MockedFunction; -const upload = __upload as jest.MockedFunction; -const cleanupTmpDirSync = __cleanupTmpDirSync as jest.MockedFunction< +const walk = __walk as MockedFunction; +const upload = __upload as MockedFunction; +const cleanupTmpDirSync = __cleanupTmpDirSync as MockedFunction< typeof __cleanupTmpDirSync >; //folder/fields.js -> folder/fields.converted.js // We add the .converted to differentiate from a unconverted fields.json -const defaultFieldsJsImplementation = jest.fn( +const mockSaveOutput = vi.fn(); +const defaultFieldsJsImplementation = vi.fn( (src: string, filePath: string, rootWriteDir: string | undefined | null) => { - const fieldsJs = Object.create(FieldsJs.prototype); const outputPath = filePath.substring(0, filePath.lastIndexOf('.')) + '.converted.json'; + const instance = { + projectDir: src, + filePath, + rootWriteDir, + rejected: false, + fieldOptions: '', + outputPath, + saveOutput: mockSaveOutput, + }; return { - init: jest.fn().mockReturnValue( - Object.assign(fieldsJs, { - src, - outputPath, - rootWriteDir, - getOutputPathPromise: jest.fn().mockResolvedValue(outputPath), - rejected: false, - }) - ), + ...instance, + init: vi.fn().mockReturnValue(instance), }; } ); -FieldsJs.mockImplementation(defaultFieldsJsImplementation); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(FieldsJs as any).mockImplementation(defaultFieldsJsImplementation); isConvertableFieldJs.mockImplementation((src: string, filePath: string) => { const fileName = path.basename(filePath); @@ -146,8 +150,7 @@ describe('lib/cms/uploadFolder', () => { }); it('tries to save output of each fields file', async () => { - const saveOutputSpy = jest.spyOn(FieldsJs.prototype, 'saveOutput'); - + mockSaveOutput.mockClear(); createTmpDirSync.mockReturnValue('folder'); upload.mockResolvedValue(mockAxiosResponse()); @@ -161,7 +164,7 @@ describe('lib/cms/uploadFolder', () => { 'publish' ); - expect(saveOutputSpy).toHaveBeenCalledTimes(2); + expect(mockSaveOutput).toHaveBeenCalledTimes(2); }); it('deletes the temporary directory', async () => { @@ -186,20 +189,20 @@ describe('lib/cms/uploadFolder', () => { describe('getFilesByType()', () => { const convertedFieldsObj = new FieldsJs( 'folder', - 'folder/fields.json', + 'folder/fields.js', 'folder' - ).init(); + ).init() as unknown as { outputPath: string }; const convertedFilePath = convertedFieldsObj.outputPath; const convertedModuleFieldsObj = new FieldsJs( 'folder', - 'folder/sample.module/fields.json', + 'folder/sample.module/fields.js', 'folder' - ).init(); + ).init() as unknown as { outputPath: string }; const convertedModuleFilePath = convertedModuleFieldsObj.outputPath; beforeEach(() => { - jest.resetModules(); + vi.resetModules(); }); it('outputs getFilesByType with no processing if convertFields is false', async () => { const files = [...filesProto]; diff --git a/lib/__tests__/urls.test.ts b/lib/__tests__/urls.test.ts index 4e2242b6..1a2034f2 100644 --- a/lib/__tests__/urls.test.ts +++ b/lib/__tests__/urls.test.ts @@ -1,4 +1,4 @@ -import { getHubSpotApiOrigin } from '../urls'; +import { getHubSpotApiOrigin } from '../urls.js'; describe('lib/urls', () => { describe('getHubSpotApiOrigin()', () => { diff --git a/lib/__tests__/validate.test.ts b/lib/__tests__/validate.test.ts index 05d98273..2110bd54 100644 --- a/lib/__tests__/validate.test.ts +++ b/lib/__tests__/validate.test.ts @@ -1,26 +1,23 @@ -import fs, { Stats } from 'fs-extra'; -import { validateHubl } from '../../api/validateHubl'; -import { walk } from '../fs'; -import { lint } from '../cms/validate'; -import { LintResult, Validation } from '../../types/HublValidation'; -import { HubSpotPromise } from '../../types/Http'; - -jest.mock('fs-extra'); -jest.mock('../../api/validateHubl'); -jest.mock('../fs'); - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockedFsStat = fs.stat as jest.MockedFunction; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockedFsReadFile = fs.readFile as jest.MockedFunction; -const mockedWalk = walk as jest.MockedFunction; -const mockedValidateHubl = validateHubl as jest.MockedFunction< - typeof validateHubl ->; - -const mockFsStats = jest.createMockFromModule('fs-extra'); - -mockFsStats.isDirectory = jest.fn(() => true); +/* eslint-disable @typescript-eslint/no-explicit-any */ +import fs from 'fs-extra'; +import { validateHubl } from '../../api/validateHubl.js'; +import { walk } from '../fs.js'; +import { lint } from '../cms/validate.js'; +import { LintResult, Validation } from '../../types/HublValidation.js'; +import { vi } from 'vitest'; + +vi.mock('fs-extra'); +vi.mock('../../api/validateHubl'); +vi.mock('../fs'); + +const mockedFsStat = vi.mocked(fs.stat); +const mockedFsReadFile = vi.mocked(fs.readFile); +const mockedWalk = vi.mocked(walk); +const mockedValidateHubl = vi.mocked(validateHubl); + +const mockFsStats = { + isDirectory: vi.fn(() => true), +} as any; const mockValidation: Validation = { meta: { @@ -55,7 +52,7 @@ describe('lib/cms/validate', () => { const filePath = 'test.html'; it('should return an empty array if directory has no files', async () => { - mockedFsStat.mockResolvedValue(mockFsStats); + mockedFsStat.mockResolvedValue(mockFsStats as any); mockedWalk.mockResolvedValue([]); const result = await lint(accountId, filePath); @@ -63,8 +60,10 @@ describe('lib/cms/validate', () => { }); it('should return the correct object if a file has no content', async () => { - mockedFsStat.mockResolvedValue({ isDirectory: () => false }); - mockedFsReadFile.mockResolvedValue(' '); + mockedFsStat.mockResolvedValue({ + isDirectory: () => false, + } as any); + mockedFsReadFile.mockResolvedValue(' ' as any); const result = await lint(accountId, filePath); expect(result).toEqual([{ file: filePath, validation: null }]); @@ -72,11 +71,17 @@ describe('lib/cms/validate', () => { it('should call validateHubl with the correct parameters', async () => { const mockSource = 'valid HUBL content'; - mockedFsStat.mockResolvedValue({ isDirectory: () => false }); - mockedFsReadFile.mockResolvedValue(mockSource); + mockedFsStat.mockResolvedValue({ + isDirectory: () => false, + } as any); + mockedFsReadFile.mockResolvedValue(mockSource as any); mockedValidateHubl.mockResolvedValue({ data: mockValidation, - } as unknown as HubSpotPromise); + status: 200, + statusText: 'OK', + headers: {} as any, + config: { headers: {} } as any, + }); const result = await lint(accountId, filePath); expect(validateHubl).toHaveBeenCalledWith(accountId, mockSource); expect(result).toEqual([{ file: filePath, validation: mockValidation }]); @@ -84,12 +89,18 @@ describe('lib/cms/validate', () => { it('should filter out files with invalid extensions', async () => { const invalidFile = 'test.txt'; - mockedFsStat.mockResolvedValue({ isDirectory: () => true }); + mockedFsStat.mockResolvedValue({ + isDirectory: () => true, + } as any); mockedWalk.mockResolvedValue([invalidFile, filePath]); - mockedFsReadFile.mockResolvedValue('valid HUBL content'); + mockedFsReadFile.mockResolvedValue('valid HUBL content' as any); mockedValidateHubl.mockResolvedValue({ data: mockValidation, - } as unknown as HubSpotPromise); + status: 200, + statusText: 'OK', + headers: {} as any, + config: { headers: {} } as any, + }); const result = await lint(accountId, filePath); @@ -98,13 +109,17 @@ describe('lib/cms/validate', () => { }); it('should execute callback if provided', async () => { - const mockCallback = jest.fn(); + const mockCallback = vi.fn(); const mockSource = 'valid HUBL content'; - mockedFsStat.mockResolvedValue({ isDirectory: () => false }); - mockedFsReadFile.mockResolvedValue(mockSource); + mockedFsStat.mockResolvedValue({ isDirectory: () => false } as any); + mockedFsReadFile.mockResolvedValue(mockSource as any); mockedValidateHubl.mockResolvedValue({ data: mockValidation, - } as unknown as HubSpotPromise); + status: 200, + statusText: 'OK', + headers: {} as any, + config: { headers: {} } as any, + }); await lint(accountId, filePath, mockCallback); expect(mockCallback).toHaveBeenCalledWith({ diff --git a/lib/__tests__/watch.test.ts b/lib/__tests__/watch.test.ts index 3254885e..a08a598c 100644 --- a/lib/__tests__/watch.test.ts +++ b/lib/__tests__/watch.test.ts @@ -1,28 +1,30 @@ import chokidar from 'chokidar'; import PQueue from 'p-queue'; -import { uploadFolder } from '../cms/uploadFolder'; -import { watch } from '../cms/watch'; -import { CMS_PUBLISH_MODE } from '../../constants/files'; +import { uploadFolder } from '../cms/uploadFolder.js'; +import { watch } from '../cms/watch.js'; +import { CMS_PUBLISH_MODE } from '../../constants/files.js'; +import { vi, MockInstance } from 'vitest'; -jest.mock('chokidar'); -jest.mock('axios'); -jest.mock('p-queue'); -jest.mock('../cms/uploadFolder'); +vi.mock('chokidar'); +vi.mock('axios'); +vi.mock('p-queue'); +vi.mock('../cms/uploadFolder'); describe('lib/cms/watch', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let chokidarMock: any; - let pQueueAddMock: jest.Mock; + let chokidarMock: { watch: MockInstance; on: MockInstance }; + let pQueueAddMock: MockInstance; beforeEach(() => { chokidarMock = { - watch: jest.fn().mockReturnThis(), - on: jest.fn().mockReturnThis(), + watch: vi.fn().mockReturnThis(), + on: vi.fn().mockReturnThis(), }; - (chokidar.watch as jest.Mock).mockReturnValue(chokidarMock); + vi.mocked(chokidar.watch).mockReturnValue( + chokidarMock as unknown as ReturnType + ); - pQueueAddMock = jest.fn(); + pQueueAddMock = vi.fn(); // @ts-expect-error test case PQueue.mockImplementation(() => ({ @@ -64,9 +66,9 @@ describe('lib/cms/watch', () => { commandOptions: {}, filePaths: [], }; - const postInitialUploadCallback = jest.fn(); + const postInitialUploadCallback = vi.fn(); - (uploadFolder as jest.Mock).mockResolvedValueOnce([]); + vi.mocked(uploadFolder).mockResolvedValueOnce([]); await watch(accountId, src, dest, options, postInitialUploadCallback); diff --git a/lib/archive.ts b/lib/archive.ts index e19d0312..917f1c42 100644 --- a/lib/archive.ts +++ b/lib/archive.ts @@ -3,11 +3,11 @@ import path, { join } from 'path'; import { tmpdir } from 'os'; import extract from 'extract-zip'; -import { logger } from './logger'; -import { i18n } from '../utils/lang'; -import { ZipData, CopySourceToDestOptions } from '../types/Archive'; -import { FileSystemError } from '../models/FileSystemError'; -import { walk } from './fs'; +import { logger } from './logger.js'; +import { i18n } from '../utils/lang.js'; +import { ZipData, CopySourceToDestOptions } from '../types/Archive.js'; +import { FileSystemError } from '../models/FileSystemError.js'; +import { walk } from './fs.js'; const i18nKey = 'lib.archive'; @@ -172,6 +172,7 @@ async function cleanupTempDir(tmpDir: string): Promise { if (!tmpDir) return; try { await fs.remove(tmpDir); + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { logger.debug(i18n(`${i18nKey}.cleanupTempDir.error`, { tmpDir })); } diff --git a/lib/cms/functions.ts b/lib/cms/functions.ts index 85d5a573..a895bdc8 100644 --- a/lib/cms/functions.ts +++ b/lib/cms/functions.ts @@ -1,18 +1,18 @@ import fs from 'fs-extra'; import path from 'path'; import findup from 'findup-sync'; -import { getCwd } from '../path'; -import { fetchFileFromRepository } from '../github'; -import { logger } from '../logger'; -import { i18n } from '../../utils/lang'; -import { FileSystemError } from '../../models/FileSystemError'; +import { getCwd } from '../path.js'; +import { fetchFileFromRepository } from '../github.js'; +import { logger } from '../logger.js'; +import { i18n } from '../../utils/lang.js'; +import { FileSystemError } from '../../models/FileSystemError.js'; import { FunctionConfig, FunctionConfigInfo, FunctionInfo, FunctionOptions, -} from '../../types/Functions'; +} from '../../types/Functions.js'; const i18nKey = 'lib.cms.functions'; export function isObjectOrFunction(value: object): boolean { diff --git a/lib/cms/handleFieldsJS.ts b/lib/cms/handleFieldsJS.ts index 62be5427..23e011e2 100644 --- a/lib/cms/handleFieldsJS.ts +++ b/lib/cms/handleFieldsJS.ts @@ -2,11 +2,11 @@ import fs from 'fs-extra'; import os from 'os'; import path from 'path'; import { fork } from 'child_process'; -import { escapeRegExp } from '../escapeRegExp'; -import { isModuleFolderChild } from '../../utils/cms/modules'; -import { logger } from '../logger'; -import { BaseError } from '../../types/Error'; -import { i18n } from '../../utils/lang'; +import { escapeRegExp } from '../escapeRegExp.js'; +import { isModuleFolderChild } from '../../utils/cms/modules.js'; +import { logger } from '../logger.js'; +import { BaseError } from '../../types/Error.js'; +import { i18n } from '../../utils/lang.js'; const i18nKey = 'lib.cms.handleFieldsJs'; diff --git a/lib/cms/modules.ts b/lib/cms/modules.ts index ab547515..7c6a6fbd 100644 --- a/lib/cms/modules.ts +++ b/lib/cms/modules.ts @@ -1,21 +1,21 @@ import path from 'path'; import fs from 'fs-extra'; -import { getCwd } from '../path'; -import { walk } from '../fs'; -import { listGithubRepoContents, cloneGithubRepo } from '../github'; -import { logger } from '../logger'; +import { getCwd } from '../path.js'; +import { walk } from '../fs.js'; +import { listGithubRepoContents, cloneGithubRepo } from '../github.js'; +import { logger } from '../logger.js'; import { isPathInput, isModuleFolder, isModuleFolderChild, -} from '../../utils/cms/modules'; +} from '../../utils/cms/modules.js'; import { PathInput, ValidationResult, ModuleDefinition, -} from '../../types/Modules'; -import { GithubRepoFile } from '../../types/Github'; -import { i18n } from '../../utils/lang'; +} from '../../types/Modules.js'; +import { GithubRepoFile } from '../../types/Github.js'; +import { i18n } from '../../utils/lang.js'; const i18nKey = 'lib.cms.modules'; diff --git a/lib/cms/processFieldsJs.ts b/lib/cms/processFieldsJs.ts index 586af2b8..119c6bc1 100644 --- a/lib/cms/processFieldsJs.ts +++ b/lib/cms/processFieldsJs.ts @@ -2,11 +2,9 @@ import path from 'path'; import fs from 'fs'; -import semver from 'semver'; import { pathToFileURL } from 'url'; -import { getExt } from '../path'; -import { FieldsJs } from './handleFieldsJS'; -import { i18n } from '../../utils/lang'; +import { FieldsJs } from './handleFieldsJS.js'; +import { i18n } from '../../utils/lang.js'; const i18nKey = 'lib.cms.processFieldsJs'; @@ -98,20 +96,11 @@ async function fieldsArrayToJson(fields: Array): Promise { } /** - * Takes in a path to a javascript file and either dynamically imports it or requires it, and returns, depending on node version. + * Takes in a path to a javascript file and dynamically imports it. * @param {string} filePath - Path to javascript file - * @returns {Promise | undefined} - Returns _default_ exported content if ESM, or exported module content if CJS, or undefined if node version < 13.2 and file is .mjs. + * @returns {Promise} - Returns _default_ exported content if ESM, or exported module content if CJS. */ async function dynamicImport(filePath: string): Promise { - if (semver.gte(process.version, '13.2.0')) { - const exported = await new Function( - `return import("${pathToFileURL(filePath)}")` - )(); - return exported.default; - } else { - if (getExt(filePath) == 'mjs') { - throw new Error(i18n(`${i18nKey}.errors.invalidMjsFile`)); - } - return require(filePath); - } + const exported = await import(pathToFileURL(filePath).href); + return exported.default || exported; } diff --git a/lib/cms/templates.ts b/lib/cms/templates.ts index b624072c..50d7b2de 100644 --- a/lib/cms/templates.ts +++ b/lib/cms/templates.ts @@ -1,8 +1,8 @@ import fs from 'fs-extra'; import path from 'path'; -import { cloneGithubRepo } from '../github'; -import { logger } from '../logger'; -import { i18n } from '../../utils/lang'; +import { cloneGithubRepo } from '../github.js'; +import { logger } from '../logger.js'; +import { i18n } from '../../utils/lang.js'; const i18nKey = 'lib.cms.templates'; diff --git a/lib/cms/themes.ts b/lib/cms/themes.ts index 9f03b479..68c31d5b 100644 --- a/lib/cms/themes.ts +++ b/lib/cms/themes.ts @@ -1,7 +1,7 @@ import findup from 'findup-sync'; -import { getHubSpotWebsiteOrigin } from '../urls'; -import { ENVIRONMENTS } from '../../constants/environments'; -import { getEnv } from '../../config'; +import { getHubSpotWebsiteOrigin } from '../urls.js'; +import { ENVIRONMENTS } from '../../constants/environments.js'; +import { getEnv } from '../../config/index.js'; export function getThemeJSONPath(path: string): string | null { return findup('theme.json', { diff --git a/lib/cms/uploadFolder.ts b/lib/cms/uploadFolder.ts index 5023de6d..9f190047 100644 --- a/lib/cms/uploadFolder.ts +++ b/lib/cms/uploadFolder.ts @@ -6,25 +6,25 @@ import { FieldsJs, createTmpDirSync, cleanupTmpDirSync, -} from './handleFieldsJS'; -import { getFileMapperQueryValues } from '../fileMapper'; -import { upload } from '../../api/fileMapper'; -import { isModuleFolderChild } from '../../utils/cms/modules'; -import { escapeRegExp } from '../escapeRegExp'; -import { convertToUnixPath, getExt } from '../path'; -import { isAuthError, isHubSpotHttpError } from '../../errors'; -import { FileMapperInputOptions } from '../../types/Files'; -import { logger } from '../logger'; -import { FILE_TYPES, FILE_UPLOAD_RESULT_TYPES } from '../../constants/files'; +} from './handleFieldsJS.js'; +import { getFileMapperQueryValues } from '../fileMapper.js'; +import { upload } from '../../api/fileMapper.js'; +import { isModuleFolderChild } from '../../utils/cms/modules.js'; +import { escapeRegExp } from '../escapeRegExp.js'; +import { convertToUnixPath, getExt } from '../path.js'; +import { isAuthError, isHubSpotHttpError } from '../../errors/index.js'; +import { FileMapperInputOptions } from '../../types/Files.js'; +import { logger } from '../logger.js'; +import { FILE_TYPES, FILE_UPLOAD_RESULT_TYPES } from '../../constants/files.js'; import { FileType, UploadFolderResults, CommandOptions, FilePathsByType, -} from '../../types/Files'; -import { CmsPublishMode } from '../../types/Files'; -import { i18n } from '../../utils/lang'; -import { HubSpotHttpError } from '../../models/HubSpotHttpError'; +} from '../../types/Files.js'; +import { CmsPublishMode } from '../../types/Files.js'; +import { i18n } from '../../utils/lang.js'; +import { HubSpotHttpError } from '../../models/HubSpotHttpError.js'; const i18nKey = 'lib.cms.uploadFolder'; diff --git a/lib/cms/validate.ts b/lib/cms/validate.ts index eaaa3523..5571c761 100644 --- a/lib/cms/validate.ts +++ b/lib/cms/validate.ts @@ -1,9 +1,9 @@ import fs from 'fs-extra'; -import { HUBL_EXTENSIONS } from '../../constants/extensions'; -import { validateHubl } from '../../api/validateHubl'; -import { walk } from '../fs'; -import { getExt } from '../path'; -import { LintResult } from '../../types/HublValidation'; +import { HUBL_EXTENSIONS } from '../../constants/extensions.js'; +import { validateHubl } from '../../api/validateHubl.js'; +import { walk } from '../fs.js'; +import { getExt } from '../path.js'; +import { LintResult } from '../../types/HublValidation.js'; export async function lint( accountId: number, diff --git a/lib/cms/watch.ts b/lib/cms/watch.ts index 409fc2a1..1937bf2a 100644 --- a/lib/cms/watch.ts +++ b/lib/cms/watch.ts @@ -4,26 +4,26 @@ import PQueue from 'p-queue'; import debounce from 'debounce'; import { AxiosError } from 'axios'; -import { isConvertableFieldJs, FieldsJs } from './handleFieldsJS'; -import { uploadFolder } from './uploadFolder'; -import { shouldIgnoreFile, ignoreFile } from '../ignoreRules'; -import { getFileMapperQueryValues } from '../fileMapper'; -import { upload, deleteFile } from '../../api/fileMapper'; -import { escapeRegExp } from '../escapeRegExp'; -import { convertToUnixPath, isAllowedExtension, getCwd } from '../path'; -import { triggerNotify } from '../notify'; -import { getThemePreviewUrl, getThemeJSONPath } from './themes'; -import { logger } from '../logger'; +import { isConvertableFieldJs, FieldsJs } from './handleFieldsJS.js'; +import { uploadFolder } from './uploadFolder.js'; +import { shouldIgnoreFile, ignoreFile } from '../ignoreRules.js'; +import { getFileMapperQueryValues } from '../fileMapper.js'; +import { upload, deleteFile } from '../../api/fileMapper.js'; +import { escapeRegExp } from '../escapeRegExp.js'; +import { convertToUnixPath, isAllowedExtension, getCwd } from '../path.js'; +import { triggerNotify } from '../notify.js'; +import { getThemePreviewUrl, getThemeJSONPath } from './themes.js'; +import { logger } from '../logger.js'; import { UploadFileOptions, CmsPublishMode, WatchOptions, WatchErrorHandler, -} from '../../types/Files'; -import { UploadFolderResults } from '../../types/Files'; -import { i18n } from '../../utils/lang'; -import { HubSpotHttpError } from '../../models/HubSpotHttpError'; -import { isHubSpotHttpError } from '../../errors'; +} from '../../types/Files.js'; +import { UploadFolderResults } from '../../types/Files.js'; +import { i18n } from '../../utils/lang.js'; +import { HubSpotHttpError } from '../../models/HubSpotHttpError.js'; +import { isHubSpotHttpError } from '../../errors/index.js'; const i18nKey = 'lib.cms.watch'; diff --git a/lib/crm.ts b/lib/crm.ts index 87d70f53..e062ff1a 100644 --- a/lib/crm.ts +++ b/lib/crm.ts @@ -1,8 +1,8 @@ import path from 'path'; -import { ImportRequest } from '../types/Crm'; -import { getCwd } from './path'; +import { ImportRequest } from '../types/Crm.js'; +import { getCwd } from './path.js'; import fs from 'fs-extra'; -import { i18n } from '../utils/lang'; +import { i18n } from '../utils/lang.js'; export function getImportDataRequest(fileName: string): { importRequest: ImportRequest; @@ -62,6 +62,7 @@ function fileExists(_path: string): boolean { if (!isFile) { return false; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return false; } diff --git a/lib/customObjects.ts b/lib/customObjects.ts index ded7be86..031026b9 100644 --- a/lib/customObjects.ts +++ b/lib/customObjects.ts @@ -1,9 +1,9 @@ import fs from 'fs-extra'; import path from 'path'; -import prettier from 'prettier'; -import { getCwd } from '../lib/path'; -import { fetchObjectSchemas, fetchObjectSchema } from '../api/customObjects'; -import { Schema } from '../types/Schemas'; +import { getCwd } from '../lib/path.js'; +import { fetchObjectSchemas, fetchObjectSchema } from '../api/customObjects.js'; +import { Schema } from '../types/Schemas.js'; +import { format } from 'prettier'; export function getResolvedPath(dest?: string, name?: string): string { if (name) return path.resolve(getCwd(), dest || '', `${name}.json`); @@ -15,7 +15,7 @@ export async function writeSchemaToDisk( schema: Schema, dest?: string ): Promise { - const formattedSchema = await prettier.format(JSON.stringify(schema), { + const formattedSchema = await format(JSON.stringify(schema), { parser: 'json', }); fs.outputFileSync(getResolvedPath(dest, schema.name), formattedSchema); diff --git a/lib/environment.ts b/lib/environment.ts index 6654d944..bac28417 100644 --- a/lib/environment.ts +++ b/lib/environment.ts @@ -1,5 +1,5 @@ -import { ENVIRONMENTS } from '../constants/environments'; -import { Environment } from '../types/Config'; +import { ENVIRONMENTS } from '../constants/environments.js'; +import { Environment } from '../types/Config.js'; export function getValidEnv( env?: Environment | null, @@ -11,9 +11,7 @@ export function getValidEnv( : ENVIRONMENTS.PROD; const returnVal = - typeof env && - typeof env === 'string' && - env.toLowerCase() === ENVIRONMENTS.QA + env && typeof env === 'string' && env.toLowerCase() === ENVIRONMENTS.QA ? ENVIRONMENTS.QA : prodValue; diff --git a/lib/fileManager.ts b/lib/fileManager.ts index 22cfc7fa..0de2a2cd 100644 --- a/lib/fileManager.ts +++ b/lib/fileManager.ts @@ -8,22 +8,22 @@ import { fetchStat, fetchFiles, fetchFolders, -} from '../api/fileManager'; -import { walk } from './fs'; -import { logger } from './logger'; -import { createIgnoreFilter } from './ignoreRules'; -import { http } from '../http'; -import { escapeRegExp } from './escapeRegExp'; +} from '../api/fileManager.js'; +import { walk } from './fs.js'; +import { logger } from './logger.js'; +import { createIgnoreFilter } from './ignoreRules.js'; +import { http } from '../http/index.js'; +import { escapeRegExp } from './escapeRegExp.js'; import { getCwd, convertToUnixPath, convertToLocalFileSystemPath, -} from './path'; +} from './path.js'; -import { File, SimplifiedFolder } from '../types/FileManager'; -import { i18n } from '../utils/lang'; -import { isAuthError, isHubSpotHttpError } from '../errors'; -import { FileSystemError } from '../models/FileSystemError'; +import { File, SimplifiedFolder } from '../types/FileManager.js'; +import { i18n } from '../utils/lang.js'; +import { isAuthError, isHubSpotHttpError } from '../errors/index.js'; +import { FileSystemError } from '../models/FileSystemError.js'; const i18nKey = 'lib.fileManager'; diff --git a/lib/fileMapper.ts b/lib/fileMapper.ts index 3d0fe014..c1a41c73 100644 --- a/lib/fileMapper.ts +++ b/lib/fileMapper.ts @@ -9,15 +9,19 @@ import { getExt, convertToLocalFileSystemPath, isAllowedExtension, -} from './path'; -import { logger } from './logger'; -import { fetchFileStream, download, downloadDefault } from '../api/fileMapper'; +} from './path.js'; +import { logger } from './logger.js'; +import { + fetchFileStream, + download, + downloadDefault, +} from '../api/fileMapper.js'; import { MODULE_EXTENSION, FUNCTIONS_EXTENSION, JSR_ALLOWED_EXTENSIONS, -} from '../constants/extensions'; -import { CMS_PUBLISH_MODE } from '../constants/files'; +} from '../constants/extensions.js'; +import { CMS_PUBLISH_MODE } from '../constants/files.js'; import { FileMapperNode, CmsPublishMode, @@ -25,10 +29,10 @@ import { FileMapperInputOptions, PathTypeData, RecursiveFileMapperCallback, -} from '../types/Files'; -import { isTimeoutError } from '../errors'; -import { i18n } from '../utils/lang'; -import { FileSystemError } from '../models/FileSystemError'; +} from '../types/Files.js'; +import { isTimeoutError } from '../errors/index.js'; +import { i18n } from '../utils/lang.js'; +import { FileSystemError } from '../models/FileSystemError.js'; const i18nKey = 'lib.fileMapper'; @@ -95,6 +99,7 @@ function validateFileMapperNode(node: FileMapperNode): void { let json; try { json = JSON.stringify(node, null, 2); + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { json = node; } @@ -233,6 +238,7 @@ async function writeFileMapperNode( options ); return true; + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { return false; } diff --git a/lib/fs.ts b/lib/fs.ts index c3e6ce9a..ac0e9a4e 100644 --- a/lib/fs.ts +++ b/lib/fs.ts @@ -1,9 +1,9 @@ import fs from 'fs'; import path from 'path'; -import { STAT_TYPES } from '../constants/files'; -import { StatType, FileData } from '../types/Files'; -import { FileSystemError } from '../models/FileSystemError'; +import { STAT_TYPES } from '../constants/files.js'; +import { StatType, FileData } from '../types/Files.js'; +import { FileSystemError } from '../models/FileSystemError.js'; export function getFileInfoAsync(dir: string, file: string): Promise { return new Promise((resolve, reject) => { diff --git a/lib/github.ts b/lib/github.ts index 48ca217a..98cda3af 100644 --- a/lib/github.ts +++ b/lib/github.ts @@ -1,28 +1,28 @@ import path from 'path'; import fs from 'fs-extra'; -import { extractZipArchive } from './archive'; -import { logger } from './logger'; +import { extractZipArchive } from './archive.js'; +import { logger } from './logger.js'; import { GithubReleaseData, GithubRepoFile, RepoPath, DownloadGithubRepoZipOptions, CloneGithubRepoOptions, -} from '../types/Github'; +} from '../types/Github.js'; import { fetchRepoFile, fetchRepoFileByDownloadUrl, fetchRepoAsZip, fetchRepoReleaseData, fetchRepoContents, -} from '../api/github'; -import { i18n } from '../utils/lang'; +} from '../api/github.js'; +import { i18n } from '../utils/lang.js'; import { isGithubRateLimitError, isHubSpotHttpError, isSystemError, -} from '../errors'; +} from '../errors/index.js'; const i18nKey = 'lib.github'; diff --git a/lib/gitignore.ts b/lib/gitignore.ts index 56627c8a..c14e782e 100644 --- a/lib/gitignore.ts +++ b/lib/gitignore.ts @@ -1,14 +1,14 @@ -import { readFileSync, writeFileSync } from 'fs-extra'; +import fs from 'fs-extra'; import path from 'path'; import { isConfigPathInGitRepo, getGitignoreFiles, configFilenameIsIgnoredByGitignore, -} from '../utils/git'; -import { DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME } from '../constants/config'; -import { i18n } from '../utils/lang'; -import { GitInclusionResult } from '../types/Config'; +} from '../utils/git.js'; +import { DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME } from '../constants/config.js'; +import { i18n } from '../utils/lang.js'; +import { GitInclusionResult } from '../types/Config.js'; const i18nKey = 'lib.gitignore'; @@ -24,12 +24,12 @@ export function checkAndAddConfigToGitignore(configPath: string): void { if (!gitignoreFilePath) { gitignoreFilePath = path.join(path.dirname(configPath), GITIGNORE_FILE); - writeFileSync(gitignoreFilePath, ''); + fs.writeFileSync(gitignoreFilePath, ''); } - const gitignoreContents = readFileSync(gitignoreFilePath).toString(); + const gitignoreContents = fs.readFileSync(gitignoreFilePath).toString(); const updatedContents = `${gitignoreContents.trim()}\n\n# HubSpot config file\n${DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME}\n`; - writeFileSync(gitignoreFilePath, updatedContents); + fs.writeFileSync(gitignoreFilePath, updatedContents); } catch (e) { throw new Error(i18n(`${i18nKey}.errors.configIgnore`), { cause: e }); } diff --git a/lib/hubdb.ts b/lib/hubdb.ts index 918824fe..766e6a5b 100644 --- a/lib/hubdb.ts +++ b/lib/hubdb.ts @@ -1,7 +1,7 @@ import path from 'path'; import fs from 'fs-extra'; -import prettier from 'prettier'; import { AxiosResponse } from 'axios'; +import { format } from 'prettier'; import { createTable, updateTable, @@ -10,10 +10,10 @@ import { fetchRows, publishTable, deleteRows, -} from '../api/hubdb'; -import { getCwd } from './path'; -import { FetchRowsResponse, Row, Table } from '../types/Hubdb'; -import { i18n } from '../utils/lang'; +} from '../api/hubdb.js'; +import { getCwd } from './path.js'; +import { FetchRowsResponse, Row, Table } from '../types/Hubdb.js'; +import { i18n } from '../utils/lang.js'; const i18nKey = 'lib.hubdb'; @@ -186,7 +186,8 @@ export async function downloadHubDbTable( const rows = await fetchAllRows(accountId, tableId); const tableToWrite = JSON.stringify(convertToJSON(table, rows)); - const tableJson = await prettier.format(tableToWrite, { + + const tableJson = await format(tableToWrite, { parser: 'json', }); diff --git a/lib/logger.ts b/lib/logger.ts index 884f6bf7..c78acf33 100644 --- a/lib/logger.ts +++ b/lib/logger.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import chalk, { Chalk } from 'chalk'; +import chalk, { type ChalkInstance } from 'chalk'; export const LOG_LEVEL = { NONE: 0, @@ -15,14 +15,14 @@ export const LOG_LEVEL = { */ export const Styles = { debug: chalk.reset.blue, - log: chalk.reset, + log: chalk.reset.white, success: chalk.reset.green, info: chalk.reset.white, warn: chalk.reset.yellow, error: chalk.reset.red, }; -export function stylize(label: string, style: Chalk, args: any[]) { +export function stylize(label: string, style: ChalkInstance, args: any[]) { const styledLabel = style(label); const [firstArg, ...rest] = args; if (typeof firstArg === 'string') { diff --git a/lib/notify.ts b/lib/notify.ts index 6998a25d..485d01d3 100644 --- a/lib/notify.ts +++ b/lib/notify.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import moment from 'moment'; import debounce from 'debounce'; -import { i18n } from '../utils/lang'; +import { i18n } from '../utils/lang.js'; const i18nKey = 'utils.notify'; diff --git a/lib/oauth.ts b/lib/oauth.ts index f4ea4881..4c3b470e 100644 --- a/lib/oauth.ts +++ b/lib/oauth.ts @@ -1,10 +1,10 @@ -import { OAuth2Manager } from '../models/OAuth2Manager'; -import { AUTH_METHODS } from '../constants/auth'; -import { FlatAccountFields } from '../types/Accounts'; -import { logger } from './logger'; -import { getAccountIdentifier } from '../config/getAccountIdentifier'; -import { updateAccountConfig, writeConfig } from '../config'; -import { i18n } from '../utils/lang'; +import { OAuth2Manager } from '../models/OAuth2Manager.js'; +import { AUTH_METHODS } from '../constants/auth.js'; +import { FlatAccountFields } from '../types/Accounts.js'; +import { logger } from './logger.js'; +import { getAccountIdentifier } from '../config/getAccountIdentifier.js'; +import { updateAccountConfig, writeConfig } from '../config/index.js'; +import { i18n } from '../utils/lang.js'; const i18nKey = 'lib.oauth'; diff --git a/lib/path.ts b/lib/path.ts index bb6a5df5..d9bee73f 100644 --- a/lib/path.ts +++ b/lib/path.ts @@ -1,6 +1,6 @@ import path from 'path'; import unixify from 'unixify'; -import { ALLOWED_EXTENSIONS } from '../constants/extensions'; +import { ALLOWED_EXTENSIONS } from '../constants/extensions.js'; import os from 'os'; export function convertToUnixPath(_path: string): string { diff --git a/lib/personalAccessKey.ts b/lib/personalAccessKey.ts index c982a6e2..b1701f47 100644 --- a/lib/personalAccessKey.ts +++ b/lib/personalAccessKey.ts @@ -1,31 +1,31 @@ import moment from 'moment'; -import { ENVIRONMENTS } from '../constants/environments'; -import { PERSONAL_ACCESS_KEY_AUTH_METHOD } from '../constants/auth'; +import { ENVIRONMENTS } from '../constants/environments.js'; +import { PERSONAL_ACCESS_KEY_AUTH_METHOD } from '../constants/auth.js'; import { fetchAccessToken, fetchScopeAuthorizationData, -} from '../api/localDevAuth'; -import { fetchSandboxHubData } from '../api/sandboxHubs'; +} from '../api/localDevAuth.js'; +import { fetchSandboxHubData } from '../api/sandboxHubs.js'; import { CLIAccount, PersonalAccessKeyAccount, ScopeGroupAuthorization, -} from '../types/Accounts'; -import { Environment } from '../types/Config'; +} from '../types/Accounts.js'; +import { Environment } from '../types/Config.js'; import { getAccountConfig, updateAccountConfig, writeConfig, getEnv, updateDefaultAccount, -} from '../config'; -import { HUBSPOT_ACCOUNT_TYPES } from '../constants/config'; -import { fetchDeveloperTestAccountData } from '../api/developerTestAccounts'; -import { logger } from './logger'; -import { CLIConfiguration } from '../config/CLIConfiguration'; -import { i18n } from '../utils/lang'; -import { isHubSpotHttpError } from '../errors'; -import { AccessToken } from '../types/Accounts'; +} from '../config/index.js'; +import { HUBSPOT_ACCOUNT_TYPES } from '../constants/config.js'; +import { fetchDeveloperTestAccountData } from '../api/developerTestAccounts.js'; +import { logger } from './logger.js'; +import { CLIConfiguration } from '../config/CLIConfiguration.js'; +import { i18n } from '../utils/lang.js'; +import { isHubSpotHttpError } from '../errors/index.js'; +import { AccessToken } from '../types/Accounts.js'; const i18nKey = 'lib.personalAccessKey'; diff --git a/lib/portManager.ts b/lib/portManager.ts index 5a234029..0547af65 100644 --- a/lib/portManager.ts +++ b/lib/portManager.ts @@ -4,11 +4,11 @@ import { HEALTH_CHECK_PATH, PortManagerServer, SERVICE_HEALTHY, -} from '../utils/PortManagerServer'; -import { PORT_MANAGER_SERVER_PORT } from '../constants/ports'; -import { RequestPortsData } from '../types/PortManager'; -import { detectPort } from '../utils/detectPort'; -import { logger } from './logger'; +} from '../utils/PortManagerServer.js'; +import { PORT_MANAGER_SERVER_PORT } from '../constants/ports.js'; +import { RequestPortsData } from '../types/PortManager.js'; +import { detectPort } from '../utils/detectPort.js'; +import { logger } from './logger.js'; export const BASE_URL = `http://localhost:${PORT_MANAGER_SERVER_PORT}`; diff --git a/lib/trackUsage.ts b/lib/trackUsage.ts index 9944b1c7..913086ce 100644 --- a/lib/trackUsage.ts +++ b/lib/trackUsage.ts @@ -1,10 +1,10 @@ import axios from 'axios'; -import { getAxiosConfig } from '../http/getAxiosConfig'; -import { logger } from './logger'; -import { http } from '../http'; -import { getAccountConfig, getEnv } from '../config'; -import { FILE_MAPPER_API_PATH } from '../api/fileMapper'; -import { i18n } from '../utils/lang'; +import { getAxiosConfig } from '../http/getAxiosConfig.js'; +import { logger } from './logger.js'; +import { http } from '../http/index.js'; +import { getAccountConfig, getEnv } from '../config/index.js'; +import { FILE_MAPPER_API_PATH } from '../api/fileMapper.js'; +import { i18n } from '../utils/lang.js'; const i18nKey = 'lib.trackUsage'; @@ -51,6 +51,7 @@ export async function trackUsage( resolveWithFullResponse: true, }); return; + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { logger.debug(i18n(`${i18nKey}.retryingEventUnauthenticated`)); } diff --git a/lib/urls.ts b/lib/urls.ts index 1b3f3474..a31524a9 100644 --- a/lib/urls.ts +++ b/lib/urls.ts @@ -1,4 +1,4 @@ -import { ENVIRONMENTS } from '../constants/environments'; +import { ENVIRONMENTS } from '../constants/environments.js'; function getEnvUrlString(env?: string): string { if (typeof env !== 'string') { diff --git a/models/FileSystemError.ts b/models/FileSystemError.ts index 8594728e..68e2ccc3 100644 --- a/models/FileSystemError.ts +++ b/models/FileSystemError.ts @@ -1,6 +1,6 @@ -import { FileSystemErrorContext } from '../types/Error'; -import { i18n } from '../utils/lang'; -import { isSystemError } from '../errors'; +import { FileSystemErrorContext } from '../types/Error.js'; +import { i18n } from '../utils/lang.js'; +import { isSystemError } from '../errors/index.js'; const i18nKey = 'errors.fileSystemErrors'; diff --git a/models/HubSpotHttpError.ts b/models/HubSpotHttpError.ts index e23efb2f..3496575b 100644 --- a/models/HubSpotHttpError.ts +++ b/models/HubSpotHttpError.ts @@ -1,8 +1,11 @@ import { isAxiosError, AxiosError } from 'axios'; -import { HubSpotHttpErrorContext, ValidationError } from '../types/Error'; -import { HttpMethod } from '../types/Api'; -import { HTTP_METHOD_PREPOSITIONS, HTTP_METHOD_VERBS } from '../constants/api'; -import { i18n } from '../utils/lang'; +import { HubSpotHttpErrorContext, ValidationError } from '../types/Error.js'; +import { HttpMethod } from '../types/Api.js'; +import { + HTTP_METHOD_PREPOSITIONS, + HTTP_METHOD_VERBS, +} from '../constants/api.js'; +import { i18n } from '../utils/lang.js'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export class HubSpotHttpError extends Error { diff --git a/models/OAuth2Manager.ts b/models/OAuth2Manager.ts index c32c6d17..0386cdc5 100644 --- a/models/OAuth2Manager.ts +++ b/models/OAuth2Manager.ts @@ -1,19 +1,19 @@ import axios from 'axios'; import moment from 'moment'; -import { getHubSpotApiOrigin } from '../lib/urls'; -import { getValidEnv } from '../lib/environment'; +import { getHubSpotApiOrigin } from '../lib/urls.js'; +import { getValidEnv } from '../lib/environment.js'; import { FlatAccountFields, OAuth2ManagerAccountConfig, WriteTokenInfoFunction, RefreshTokenResponse, ExchangeProof, -} from '../types/Accounts'; -import { logger } from '../lib/logger'; -import { getAccountIdentifier } from '../config/getAccountIdentifier'; -import { AUTH_METHODS } from '../constants/auth'; -import { i18n } from '../utils/lang'; +} from '../types/Accounts.js'; +import { logger } from '../lib/logger.js'; +import { getAccountIdentifier } from '../config/getAccountIdentifier.js'; +import { AUTH_METHODS } from '../constants/auth.js'; +import { i18n } from '../utils/lang.js'; const i18nKey = 'models.OAuth2Manager'; diff --git a/models/__tests__/HubSpotHttpError.ts b/models/__tests__/HubSpotHttpError.ts index a129e844..3f501bfb 100644 --- a/models/__tests__/HubSpotHttpError.ts +++ b/models/__tests__/HubSpotHttpError.ts @@ -1,4 +1,4 @@ -import { HubSpotHttpError } from '../HubSpotHttpError'; +import { HubSpotHttpError } from '../HubSpotHttpError.js'; import { AxiosError } from 'axios'; describe('models/HubSpotHttpError', () => { diff --git a/models/__tests__/OAuth2Manager.test.ts b/models/__tests__/OAuth2Manager.test.ts index 7e6f5e08..5e37bd88 100644 --- a/models/__tests__/OAuth2Manager.test.ts +++ b/models/__tests__/OAuth2Manager.test.ts @@ -1,11 +1,12 @@ import axios from 'axios'; import moment from 'moment'; -import { OAuth2Manager } from '../OAuth2Manager'; -import { ENVIRONMENTS } from '../../constants/environments'; +import { OAuth2Manager } from '../OAuth2Manager.js'; +import { ENVIRONMENTS } from '../../constants/environments.js'; +import { vi } from 'vitest'; -jest.mock('axios'); +vi.mock('axios'); -const axiosMock = axios as jest.MockedFunction; +const axiosMock = vi.mocked(axios); const mockRefreshTokenResponse = { refresh_token: 'new-token', diff --git a/package.json b/package.json index d6e73342..489df162 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "@hubspot/local-dev-lib", "version": "3.21.0", + "type": "module", "description": "Provides library functionality for HubSpot local development tooling, including the HubSpot CLI", "repository": { "type": "git", @@ -11,12 +12,13 @@ "access": "public" }, "scripts": { - "build": "ts-node ./scripts/build.ts", + "build": "tsx ./scripts/build.ts", "lint": "eslint --max-warnings=0 . && prettier . --check", "local-dev": "yarn build && cd dist && yarn link && cd .. && tsc --watch --rootDir . --outdir dist", "prettier:write": "prettier . --write", - "release": "ts-node ./scripts/release.ts release", - "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ./node_modules/.bin/jest" + "release": "tsx ./scripts/release.ts release", + "test": "vitest run", + "test:watch": "vitest" }, "license": "Apache-2.0", "devDependencies": { @@ -28,24 +30,24 @@ "@types/express": "^4.17.18", "@types/findup-sync": "^4.0.2", "@types/fs-extra": "^11.0.1", - "@types/jest": "^29.5.0", "@types/js-yaml": "^4.0.5", - "@types/node": "^18.14.2", + "@types/node": "^20.14.8", "@types/unixify": "^1.0.0", - "@typescript-eslint/eslint-plugin": "^5.54.0", - "@typescript-eslint/parser": "^5.59.7", - "eslint": "^8.35.0", - "eslint-plugin-import": "^2.29.1", + "@typescript-eslint/eslint-plugin": "^8.30.1", + "@typescript-eslint/parser": "^8.11.0", + "eslint": "^9.38.0", + "eslint-plugin-import": "^2.31.0", "husky": "^8.0.0", - "jest": "^29.5.0", "open": "^8.4.2", - "ts-jest": "^29.0.5", "ts-node": "^10.9.2", - "typescript": "^4.9.5", + "tsx": "^4.20.6", + "typescript": "^5.6.2", + "vitest": "^2.1.9", "yargs": "^17.7.2" }, "exports": { "./*": "./lib/*.js", + "./*/cms/*": "./lib/cms/*.js", "./api/*": "./api/*.js", "./errors/*": "./errors/*.js", "./http": "./http/index.js", @@ -63,7 +65,7 @@ "dependencies": { "address": "2.0.2", "axios": "1.12.0", - "chalk": "2.4.2", + "chalk": "5.6.2", "chokidar": "3.6.0", "content-disposition": "0.5.4", "cors": "2.8.5", @@ -73,15 +75,15 @@ "findup-sync": "5.0.0", "form-data": "^4.0.4", "fs-extra": "11.2.0", - "ignore": "5.3.1", + "ignore": "^7.0.0", "js-yaml": "4.1.0", "moment": "2.30.1", - "p-queue": "6.6.2", - "prettier": "3.3.1", + "p-queue": "^7.0.0", + "prettier": "^3.6.2", "semver": "6.3.1", "unixify": "1.0.0" }, "engines": { - "node": ">=16.20.0" + "node": ">=18.0.0" } } diff --git a/scripts/build.ts b/scripts/build.ts index 491d8255..0cdaea5d 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,3 +1,3 @@ -import { build } from './lib/build'; +import { build } from './lib/build.js'; build(); diff --git a/scripts/lib/build.ts b/scripts/lib/build.ts index 5edd1765..0053475e 100644 --- a/scripts/lib/build.ts +++ b/scripts/lib/build.ts @@ -2,7 +2,7 @@ import { spawn, exec as _exec } from 'child_process'; import { promisify } from 'util'; import fs from 'fs'; -import { logger, setLogLevel, LOG_LEVEL } from '../../lib/logger'; +import { logger, setLogLevel, LOG_LEVEL } from '../../lib/logger.js'; const exec = promisify(_exec); diff --git a/scripts/release.ts b/scripts/release.ts index 58fcccc0..a2324533 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -1,6 +1,6 @@ import { buildReleaseScript } from '@hubspot/npm-scripts/src/release'; import path from 'path'; -import { build } from './lib/build'; +import { build } from './lib/build.js'; const packageJsonLocation = path.resolve( path.join(__dirname, '..', 'package.json') diff --git a/setupTests.ts b/setupTests.ts deleted file mode 100644 index 2d7db869..00000000 --- a/setupTests.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Clear out the env variable HUBAPI_DOMAIN_OVERRIDE so it doesn't impact tests -// if it is set in the users local environment -delete process.env.HUBAPI_DOMAIN_OVERRIDE; diff --git a/tsconfig.json b/tsconfig.json index df2872dd..f2b6df2a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,14 +1,17 @@ { "compilerOptions": { "target": "esnext", - "module": "commonjs", + "module": "NodeNext", + "moduleResolution": "NodeNext", "declaration": true, "strict": true, "esModuleInterop": true, + "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "skipLibCheck": true, "rootDir": ".", - "outDir": "dist" + "outDir": "dist", + "types": ["vitest/globals"] }, "include": [ "api", @@ -22,5 +25,5 @@ "types", "utils" ], - "exclude": ["node_modules", "**/__tests__/*", "**/__mocks__/*"] + "exclude": ["node_modules", "**/__mocks__/*", "**/__tests__/*"] } diff --git a/types/Accounts.ts b/types/Accounts.ts index 4765eec4..a5db32db 100644 --- a/types/Accounts.ts +++ b/types/Accounts.ts @@ -1,7 +1,7 @@ -import { HUBSPOT_ACCOUNT_TYPES } from '../constants/config'; -import { CmsPublishMode } from './Files'; -import { Environment } from './Config'; -import { ValueOf } from './Utils'; +import { HUBSPOT_ACCOUNT_TYPES } from '../constants/config.js'; +import { CmsPublishMode } from './Files.js'; +import { Environment } from './Config.js'; +import { ValueOf } from './Utils.js'; export type AuthType = 'personalaccesskey' | 'apikey' | 'oauth2'; diff --git a/types/Activity.ts b/types/Activity.ts index 235b43cd..f8b1d048 100644 --- a/types/Activity.ts +++ b/types/Activity.ts @@ -1,6 +1,6 @@ -import { ACTIVITY_SOURCE } from '../enums/project'; -import { ValueOf } from './Utils'; -import { GithubSourceData } from './Github'; +import { ACTIVITY_SOURCE } from '../enums/project.js'; +import { ValueOf } from './Utils.js'; +import { GithubSourceData } from './Github.js'; export interface GithubActivitySource extends GithubSourceData { authorEmail: string; diff --git a/types/Api.ts b/types/Api.ts index 86f9dbc6..7ccf4776 100644 --- a/types/Api.ts +++ b/types/Api.ts @@ -1,3 +1,3 @@ -import { HTTP_METHOD_VERBS } from '../constants/api'; +import { HTTP_METHOD_VERBS } from '../constants/api.js'; export type HttpMethod = keyof typeof HTTP_METHOD_VERBS; diff --git a/types/Build.ts b/types/Build.ts index d928a716..b733e430 100644 --- a/types/Build.ts +++ b/types/Build.ts @@ -1,12 +1,12 @@ -import { ValueOf } from './Utils'; +import { ValueOf } from './Utils.js'; import { BUILD_STATUS, SUBBUILD_TYPES, DEPLOYABLE_STATES, -} from '../enums/build'; -import { ActivitySource } from './Activity'; -import { DeployStatusTaskLocator } from './Deploy'; -import { ProjectStandardError } from './Project'; +} from '../enums/build.js'; +import { ActivitySource } from './Activity.js'; +import { DeployStatusTaskLocator } from './Deploy.js'; +import { ProjectStandardError } from './Project.js'; export type SubbuildStatus = { buildName: string; diff --git a/types/ComponentStructure.ts b/types/ComponentStructure.ts index 199c6b75..63c51576 100644 --- a/types/ComponentStructure.ts +++ b/types/ComponentStructure.ts @@ -1,5 +1,5 @@ -import { COMPONENT_TYPES, SUBCOMPONENT_TYPES } from '../enums/build'; -import { ValueOf } from './Utils'; +import { COMPONENT_TYPES, SUBCOMPONENT_TYPES } from '../enums/build.js'; +import { ValueOf } from './Utils.js'; export type ComponentStructure = { [key: string]: Array; diff --git a/types/Config.ts b/types/Config.ts index 6150a530..2dbf0f98 100644 --- a/types/Config.ts +++ b/types/Config.ts @@ -1,7 +1,7 @@ -import { ENVIRONMENTS } from '../constants/environments'; -import { CLIAccount_NEW, CLIAccount_DEPRECATED } from './Accounts'; -import { CmsPublishMode } from './Files'; -import { ValueOf } from './Utils'; +import { ENVIRONMENTS } from '../constants/environments.js'; +import { CLIAccount_NEW, CLIAccount_DEPRECATED } from './Accounts.js'; +import { CmsPublishMode } from './Files.js'; +import { ValueOf } from './Utils.js'; export interface CLIConfig_NEW { accounts: Array; diff --git a/types/Deploy.ts b/types/Deploy.ts index 6c9db3a2..b20507ea 100644 --- a/types/Deploy.ts +++ b/types/Deploy.ts @@ -1,8 +1,8 @@ -import { ValueOf } from './Utils'; -import { ACTIVITY_SOURCE } from '../enums/project'; -import { DEPLOY_ACTION, DEPLOY_STATUS } from '../enums/deploy'; -import { COMPONENT_TYPES, SUBCOMPONENT_TYPES } from '../enums/build'; -import { ProjectStandardError } from './Project'; +import { ValueOf } from './Utils.js'; +import { ACTIVITY_SOURCE } from '../enums/project.js'; +import { DEPLOY_ACTION, DEPLOY_STATUS } from '../enums/deploy.js'; +import { COMPONENT_TYPES, SUBCOMPONENT_TYPES } from '../enums/build.js'; +import { ProjectStandardError } from './Project.js'; export type DeployStatus = ValueOf; diff --git a/types/Files.ts b/types/Files.ts index 47f18fd9..8939fb21 100644 --- a/types/Files.ts +++ b/types/Files.ts @@ -1,11 +1,11 @@ -import { ValueOf } from '../types/Utils'; +import { ValueOf } from '../types/Utils.js'; import { STAT_TYPES, FILE_TYPES, FILE_UPLOAD_RESULT_TYPES, -} from '../constants/files'; -import { CMS_PUBLISH_MODE } from '../constants/files'; -import { HttpOptions } from './Http'; +} from '../constants/files.js'; +import { CMS_PUBLISH_MODE } from '../constants/files.js'; +import { HttpOptions } from './Http.js'; import { AxiosError } from 'axios'; export type StatType = ValueOf; diff --git a/types/Github.ts b/types/Github.ts index 7e024842..503b18fb 100644 --- a/types/Github.ts +++ b/types/Github.ts @@ -1,4 +1,4 @@ -import { Collision } from './Archive'; +import { Collision } from './Archive.js'; type GithubAuthor = { login: string; diff --git a/types/Lang.ts b/types/Lang.ts index 768a162a..55c23500 100644 --- a/types/Lang.ts +++ b/types/Lang.ts @@ -1,5 +1,5 @@ -import lang from '../lang/en.json'; -import { Leaves } from './Utils'; +import lang from '../lang/en.json' with { type: 'json' }; +import { Leaves } from './Utils.js'; export type GenericLanguageObject = { [key: string]: string | GenericLanguageObject; diff --git a/types/Migration.ts b/types/Migration.ts index 5e77dec1..194d576f 100644 --- a/types/Migration.ts +++ b/types/Migration.ts @@ -1,5 +1,5 @@ -import { ValueOf } from './Utils'; -import { ProjectStandardError } from './Project'; +import { ValueOf } from './Utils.js'; +import { ProjectStandardError } from './Project.js'; export const MIGRATION_STATUS = { BUILDING: 'BUILDING', diff --git a/types/Project.ts b/types/Project.ts index a30696d9..09f9f3fe 100644 --- a/types/Project.ts +++ b/types/Project.ts @@ -1,6 +1,6 @@ -import { Build } from './Build'; -import { GithubSourceData } from './Github'; -import { ProjectLog } from './ProjectLog'; +import { Build } from './Build.js'; +import { GithubSourceData } from './Github.js'; +import { ProjectLog } from './ProjectLog.js'; export type Project = { createdAt: number; diff --git a/utils/PortManagerServer.ts b/utils/PortManagerServer.ts index 8681e7e2..7f80f9f0 100644 --- a/utils/PortManagerServer.ts +++ b/utils/PortManagerServer.ts @@ -2,17 +2,17 @@ import express, { Express, Request, Response } from 'express'; import { Server } from 'http'; import cors from 'cors'; -import { detectPort } from './detectPort'; +import { detectPort } from './detectPort.js'; import { MIN_PORT_NUMBER, MAX_PORT_NUMBER, PORT_MANAGER_SERVER_PORT, -} from '../constants/ports'; -import { isSystemError } from '../errors'; -import { logger } from '../lib/logger'; -import { i18n } from './lang'; -import { BaseError } from '../types/Error'; -import { RequestPortsData, ServerPortMap } from '../types/PortManager'; +} from '../constants/ports.js'; +import { isSystemError } from '../errors/index.js'; +import { logger } from '../lib/logger.js'; +import { i18n } from './lang.js'; +import { BaseError } from '../types/Error.js'; +import { RequestPortsData, ServerPortMap } from '../types/PortManager.js'; const i18nKey = 'utils.PortManagerServer'; @@ -193,13 +193,26 @@ class _PortManagerServer { } }; + async close(): Promise { + return new Promise(resolve => { + if (this.server) { + this.server.close(() => { + this.reset(); + resolve(); + }); + } else { + resolve(); + } + }); + } + closeServer = (req: Request, res: Response): void => { - if (this.server) { - logger.debug(i18n(`${i18nKey}.close`)); - res.sendStatus(200); - this.server.close(); - this.reset(); - } + logger.debug(i18n(`${i18nKey}.close`)); + res.sendStatus(200); + + setImmediate(() => { + this.close(); + }); }; } diff --git a/utils/__tests__/fieldsJS.test.ts b/utils/__tests__/fieldsJS.test.ts index 02d7ca50..28616c59 100644 --- a/utils/__tests__/fieldsJS.test.ts +++ b/utils/__tests__/fieldsJS.test.ts @@ -1,4 +1,4 @@ -import { fieldsArrayToJson } from '../cms/fieldsJS'; +import { fieldsArrayToJson } from '../cms/fieldsJS.js'; describe('utils/cms/fieldsJS', () => { describe('fieldsArrayToJson()', () => { diff --git a/utils/__tests__/git.test.ts b/utils/__tests__/git.test.ts index b49badb3..b2a0a8f5 100644 --- a/utils/__tests__/git.test.ts +++ b/utils/__tests__/git.test.ts @@ -1,14 +1,15 @@ import { configFilenameIsIgnoredByGitignore, getGitignoreFiles, -} from '../../utils/git'; +} from '../../utils/git.js'; import fs from 'fs-extra'; -jest.mock('findup-sync'); +vi.mock('findup-sync'); import findup from 'findup-sync'; import path from 'path'; +import { vi, type MockedFunction } from 'vitest'; -const findupMock = findup as jest.MockedFunction; +const findupMock = findup as MockedFunction; describe('utils/cms/git', () => { const projectBaseDir = '/Users/fakeuser/someproject'; @@ -23,7 +24,7 @@ describe('utils/cms/git', () => { it('returns false if the config file is not ignored', () => { const gitignoreContent = ''; - jest.spyOn(fs, 'readFileSync').mockImplementation(() => { + vi.spyOn(fs, 'readFileSync').mockImplementation(() => { return Buffer.from(gitignoreContent); }); @@ -37,7 +38,7 @@ describe('utils/cms/git', () => { it('identifies if a config file is ignored with a specific ignore statement', () => { const gitignoreContent = 'hubspot.config.yml'; - const readFileSyncSpy = jest + const readFileSyncSpy = vi .spyOn(fs, 'readFileSync') .mockImplementation(() => { return Buffer.from(gitignoreContent); @@ -54,7 +55,7 @@ describe('utils/cms/git', () => { it('identifies if a config file is ignored with a wildcard statement', () => { const gitignoreContent = 'hubspot.config.*'; - const readFileSyncSpy = jest + const readFileSyncSpy = vi .spyOn(fs, 'readFileSync') .mockImplementation(() => { return Buffer.from(gitignoreContent); @@ -73,7 +74,7 @@ describe('utils/cms/git', () => { const gitignoreContent = 'hubspot.config.yml'; findupMock.mockImplementation(() => customConfigPath); - const readFileSyncSpy = jest + const readFileSyncSpy = vi .spyOn(fs, 'readFileSync') .mockImplementation(() => { return Buffer.from(gitignoreContent); @@ -92,7 +93,7 @@ describe('utils/cms/git', () => { const gitignoreContent = 'my.custom.name.yml'; findupMock.mockImplementation(() => customConfigPath); - const readFileSyncSpy = jest + const readFileSyncSpy = vi .spyOn(fs, 'readFileSync') .mockImplementation(() => { return Buffer.from(gitignoreContent); @@ -117,10 +118,10 @@ describe('utils/cms/git', () => { it('should return an array of the gitignore files', () => { const gitignoreFile = `${projectBaseDir}/.gitignore`; - jest.spyOn(path, 'resolve').mockImplementation(() => { + vi.spyOn(path, 'resolve').mockImplementation(() => { return gitignoreFile; }); - jest.spyOn(path, 'dirname').mockImplementation(() => { + vi.spyOn(path, 'dirname').mockImplementation(() => { return projectBaseDir; }); diff --git a/utils/__tests__/modules.test.ts b/utils/__tests__/modules.test.ts index d49dea8f..48717420 100644 --- a/utils/__tests__/modules.test.ts +++ b/utils/__tests__/modules.test.ts @@ -1,8 +1,8 @@ import path from 'path'; -import { isModuleFolder, isModuleFolderChild } from '../cms/modules'; +import { isModuleFolder, isModuleFolderChild } from '../cms/modules.js'; -import { PathInput } from '../../types/Modules'; +import { PathInput } from '../../types/Modules.js'; const isLocal = true; const isHubSpot = true; diff --git a/utils/accounts.ts b/utils/accounts.ts index 4c2177b4..feb85ef5 100644 --- a/utils/accounts.ts +++ b/utils/accounts.ts @@ -1,9 +1,9 @@ -import { CLIAccount } from '../types/Accounts'; +import { CLIAccount } from '../types/Accounts.js'; import { CLIConfig, CLIConfig_DEPRECATED, CLIConfig_NEW, -} from '../types/Config'; +} from '../types/Config.js'; export function getAccounts(config?: CLIConfig | null): Array { if (!config) { diff --git a/utils/cms/fieldsJS.ts b/utils/cms/fieldsJS.ts index 4e5fc8b0..54da23ca 100644 --- a/utils/cms/fieldsJS.ts +++ b/utils/cms/fieldsJS.ts @@ -1,4 +1,4 @@ -import { FieldsArray } from '../../types/FieldsJS'; +import { FieldsArray } from '../../types/FieldsJS.js'; /* * Polyfill for `Array.flat(Infinity)` since the `flat` is only available for Node v11+ diff --git a/utils/cms/modules.ts b/utils/cms/modules.ts index 386feae9..b820c778 100644 --- a/utils/cms/modules.ts +++ b/utils/cms/modules.ts @@ -1,8 +1,8 @@ import path from 'path'; -import { getExt, splitHubSpotPath, splitLocalPath } from '../../lib/path'; -import { MODULE_EXTENSION } from '../../constants/extensions'; -import { PathInput } from '../../types/Modules'; -import { i18n } from '../lang'; +import { getExt, splitHubSpotPath, splitLocalPath } from '../../lib/path.js'; +import { MODULE_EXTENSION } from '../../constants/extensions.js'; +import { PathInput } from '../../types/Modules.js'; +import { i18n } from '../lang.js'; const i18nKey = 'utils.cms.modules'; const isBool = (x: boolean | undefined) => !!x === x; diff --git a/utils/detectPort.ts b/utils/detectPort.ts index 20a6e3ad..7d747ee3 100644 --- a/utils/detectPort.ts +++ b/utils/detectPort.ts @@ -25,10 +25,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import net, { AddressInfo } from 'net'; import { ip } from 'address'; -import { NetError, ListenCallback } from '../types/PortManager'; +import { NetError, ListenCallback } from '../types/PortManager.js'; -import { MIN_PORT_NUMBER, MAX_PORT_NUMBER } from '../constants/ports'; -import { i18n } from './lang'; +import { MIN_PORT_NUMBER, MAX_PORT_NUMBER } from '../constants/ports.js'; +import { i18n } from './lang.js'; const i18nKey = 'utils.detectPort'; diff --git a/utils/lang.ts b/utils/lang.ts index dfb1812e..244f0ed5 100644 --- a/utils/lang.ts +++ b/utils/lang.ts @@ -1,10 +1,10 @@ -import en from '../lang/en.json'; +import en from '../lang/en.json' with { type: 'json' }; import { LanguageObject, GenericLanguageObject, LangKey, InterpolationData, -} from '../types/Lang'; +} from '../types/Lang.js'; const LANGUAGES: { [language: string]: LanguageObject } = { en, diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..52e5719f --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,50 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // Test environment - use node for library tests + environment: 'node', + + // Coverage configuration + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/**', + 'dist/**', + '**/__tests__/**', + '**/__mocks__/**', + 'coverage/**', + '*.config.*', + '*.d.ts', + ], + }, + + // Test timeout + testTimeout: 15000, + + // Clear mocks between tests + clearMocks: true, + + // Include patterns + include: [ + '**/__tests__/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + '**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + ], + + // Exclude patterns + exclude: [ + 'node_modules/**', + 'dist/**', + '.git/**', + '.github/**', + 'coverage/**', + ], + + // Global test configuration + globals: true, + + // Test isolation + isolate: true, + }, +});