From 021d153896ec95b808b4f1a74c115df81e21ea5c Mon Sep 17 00:00:00 2001 From: Branden Rodgers Date: Fri, 10 Jan 2025 14:21:37 -0500 Subject: [PATCH 1/7] chore: Deprecate portal env variable (#1329) --- bin/cli.js | 43 +++++++++++++++++++++++++++++++++++++------ lang/en.lyaml | 2 ++ lib/commonOpts.ts | 22 ---------------------- package.json | 2 +- yarn.lock | 40 ++++++++++++++++++++++++++++++++-------- 5 files changed, 72 insertions(+), 37 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 1644f7ed0..017485619 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -8,16 +8,13 @@ const { logger } = require('@hubspot/local-dev-lib/logger'); const { addUserAgentHeader } = require('@hubspot/local-dev-lib/http'); const { loadConfig, + getAccountId, configFileExists, getConfigPath, validateConfig, } = require('@hubspot/local-dev-lib/config'); const { logError } = require('../lib/errorHandlers/index'); -const { - setLogLevel, - getCommandName, - injectAccountIdMiddleware, -} = require('../lib/commonOpts'); +const { setLogLevel, getCommandName } = require('../lib/commonOpts'); const { validateAccount } = require('../lib/validation'); const { trackHelpUsage, @@ -27,7 +24,7 @@ const { getIsInProject } = require('../lib/projects'); const pkg = require('../package.json'); const { i18n } = require('../lib/lang'); const { EXIT_CODES } = require('../lib/enums/exitCodes'); -const { UI_COLORS, uiCommandReference } = require('../lib/ui'); +const { UI_COLORS, uiCommandReference, uiDeprecatedTag } = require('../lib/ui'); const { checkAndWarnGitInclusion } = require('../lib/ui/git'); const removeCommand = require('../commands/remove'); @@ -176,6 +173,39 @@ const SKIP_CONFIG_VALIDATION = { auth: { target: true }, }; +const handleDeprecatedEnvVariables = options => { + // HUBSPOT_PORTAL_ID is deprecated, but we'll still support it for now + // The HubSpot GH Deploy Action still uses HUBSPOT_PORTAL_ID + if ( + options.useEnv && + process.env.HUBSPOT_PORTAL_ID && + !process.env.HUBSPOT_ACCOUNT_ID + ) { + uiDeprecatedTag( + i18n(`${i18nKey}.handleDeprecatedEnvVariables.portalEnvVarDeprecated`, { + configPath: getConfigPath(), + }) + ); + process.env.HUBSPOT_ACCOUNT_ID = process.env.HUBSPOT_PORTAL_ID; + } +}; + +/** + * Auto-injects the derivedAccountId flag into all commands + */ +const injectAccountIdMiddleware = async options => { + const { account } = options; + + // Preserves the original --account flag for certain commands. + options.providedAccountId = account; + + if (options.useEnv && process.env.HUBSPOT_ACCOUNT_ID) { + options.derivedAccountId = parseInt(process.env.HUBSPOT_ACCOUNT_ID, 10); + } else { + options.derivedAccountId = getAccountId(account); + } +}; + const loadConfigMiddleware = async options => { // Skip this when no command is provided if (!options._.length) { @@ -261,6 +291,7 @@ const argv = yargs .middleware([ setLogLevel, setRequestHeaders, + handleDeprecatedEnvVariables, loadConfigMiddleware, injectAccountIdMiddleware, checkAndWarnGitInclusionMiddleware, diff --git a/lang/en.lyaml b/lang/en.lyaml index 3ce2a43d3..295c67f75 100644 --- a/lang/en.lyaml +++ b/lang/en.lyaml @@ -7,6 +7,8 @@ en: cliUpdateNotification: "HubSpot CLI version {{#cyan}}{{#bold}}{currentVersion}{{/bold}}{{/cyan}} is outdated.\nRun {{ updateCommand }} to upgrade to version {{#cyan}}{{#bold}}{latestVersion}{{/bold}}{{/cyan}}" srcIsProject: "\"{{ src }}\" is in a project folder. Did you mean \"hs project {{command}}\"?" setDefaultAccountMoved: "This command has moved. Try `hs accounts use` instead" + handleDeprecatedEnvVariables: + portalEnvVarDeprecated: "The HUBSPOT_PORTAL_ID environment variable is deprecated. Please use HUBSPOT_ACCOUNT_ID instead." loadConfigMiddleware: configFileExists: "A configuration file already exists at {{ configPath }}. To specify a new configuration file, delete the existing one and try again." completion: diff --git a/lib/commonOpts.ts b/lib/commonOpts.ts index ba6caf0d7..c2d37d152 100644 --- a/lib/commonOpts.ts +++ b/lib/commonOpts.ts @@ -117,28 +117,6 @@ export function getAccountId( return getAccountIdFromConfig(account); } -/** - * Auto-injects the derivedAccountId flag into all commands - */ -export async function injectAccountIdMiddleware( - options: Arguments<{ - derivedAccountId?: number | null; - account?: number | string; - }> -): Promise { - const { account } = options; - - // Preserves the original --account flag for certain commands. - options.providedAccountId = account; - - if (options.useEnv && process.env.HUBSPOT_ACCOUNT_ID) { - options.derivedAccountId = parseInt(process.env.HUBSPOT_ACCOUNT_ID, 10); - return; - } - - options.derivedAccountId = getAccountIdFromConfig(account); -} - export function getCmsPublishMode( options: Arguments<{ cmsPublishMode?: CmsPublishMode }> ): CmsPublishMode { diff --git a/package.json b/package.json index a51a207e1..e2a8bb558 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "license": "Apache-2.0", "repository": "https://github.com/HubSpot/hubspot-cli", "dependencies": { - "@hubspot/local-dev-lib": "3.1.0", + "@hubspot/local-dev-lib": "3.1.1", "@hubspot/serverless-dev-runtime": "7.0.1", "@hubspot/theme-preview-dev-server": "0.0.10", "@hubspot/ui-extensions-dev-server": "0.8.40", diff --git a/yarn.lock b/yarn.lock index 8d8182926..065849c51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1446,10 +1446,10 @@ semver "^6.3.0" unixify "^1.0.0" -"@hubspot/local-dev-lib@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@hubspot/local-dev-lib/-/local-dev-lib-3.1.0.tgz#76b5d524aa694aad2bfc6e199ee1dac314f15774" - integrity sha512-iop3PgZ0ZWejCH6PmSeYnnGwV8vGIuA4F+w7SxukdX3QfhivlczClATWPaZanv1CYJHdflqoItq1kdF8ASJJmA== +"@hubspot/local-dev-lib@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@hubspot/local-dev-lib/-/local-dev-lib-3.1.1.tgz#b67646d7a7b399cebc5d74f62c389c13554e950c" + integrity sha512-/SIKBuC3ORkKMXityS6tCz2sNU7OY96slJtLM0CxRlKDCiwEGb08Lf0Ruf1PMBJnAu8iHbw/6SprefFEHEQOpw== dependencies: address "^2.0.1" axios "^1.3.5" @@ -10709,7 +10709,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10791,7 +10800,7 @@ stringify-object@^3.2.1, stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -10805,6 +10814,13 @@ strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -11829,8 +11845,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: - name wrap-ansi-cjs +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -11848,6 +11863,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From adc38852796fff9e7a049cfedcd58344b276e04c Mon Sep 17 00:00:00 2001 From: Branden Rodgers Date: Fri, 10 Jan 2025 14:22:35 -0500 Subject: [PATCH 2/7] v7.0.1-beta.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e2a8bb558..cbe773169 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hubspot/cli", - "version": "7.0.0", + "version": "7.0.1-beta.0", "description": "The official CLI for developing on HubSpot", "license": "Apache-2.0", "repository": "https://github.com/HubSpot/hubspot-cli", From 1367aee80368588327e34dcc5e5ef02802c0b98f Mon Sep 17 00:00:00 2001 From: Branden Rodgers Date: Fri, 10 Jan 2025 14:57:28 -0500 Subject: [PATCH 3/7] v7.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cbe773169..e2af1bba7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hubspot/cli", - "version": "7.0.1-beta.0", + "version": "7.0.1", "description": "The official CLI for developing on HubSpot", "license": "Apache-2.0", "repository": "https://github.com/HubSpot/hubspot-cli", From 5b6b57ee85f86469b98ce3f9e526495ae0d7c91b Mon Sep 17 00:00:00 2001 From: Camden Phalen Date: Mon, 13 Jan 2025 11:32:46 -0500 Subject: [PATCH 4/7] Chore: convert lib/sandboxes and sandboxSync to TS (#1328) --- commands/sandbox/create.ts | 17 +-- lang/en.lyaml | 2 + lib/buildAccount.ts | 9 +- lib/localDev.ts | 10 +- lib/sandboxSync.ts | 76 +++++----- lib/sandboxes.ts | 276 +++++++++---------------------------- types/Sandboxes.ts | 3 + 7 files changed, 126 insertions(+), 267 deletions(-) create mode 100644 types/Sandboxes.ts diff --git a/commands/sandbox/create.ts b/commands/sandbox/create.ts index b516e307d..33dbb1314 100644 --- a/commands/sandbox/create.ts +++ b/commands/sandbox/create.ts @@ -10,9 +10,9 @@ const { EXIT_CODES } = require('../../lib/enums/exitCodes'); const { getAccountConfig, getEnv } = require('@hubspot/local-dev-lib/config'); const { uiFeatureHighlight, uiBetaTag } = require('../../lib/ui'); const { - sandboxTypeMap, + SANDBOX_TYPE_MAP, getAvailableSyncTypes, - syncTypes, + SYNC_TYPES, validateSandboxUsageLimits, } = require('../../lib/sandboxes'); const { getValidEnv } = require('@hubspot/local-dev-lib/environment'); @@ -65,7 +65,7 @@ exports.handler = async options => { let typePrompt; let namePrompt; - if ((type && !sandboxTypeMap[type.toLowerCase()]) || !type) { + if ((type && !SANDBOX_TYPE_MAP[type.toLowerCase()]) || !type) { if (!force) { typePrompt = await sandboxTypePrompt(); } else { @@ -74,7 +74,7 @@ exports.handler = async options => { } } const sandboxType = type - ? sandboxTypeMap[type.toLowerCase()] + ? SANDBOX_TYPE_MAP[type.toLowerCase()] : typePrompt.type; // Check usage limits and exit if parent portal has no available sandboxes for the selected type @@ -141,12 +141,7 @@ exports.handler = async options => { const sandboxAccountConfig = getAccountConfig(result.sandbox.sandboxHubId); // For v1 sandboxes, keep sync here. Once we migrate to v2, this will be handled by BE automatically const handleSyncSandbox = async syncTasks => { - await syncSandbox({ - accountConfig: sandboxAccountConfig, - parentAccountConfig: accountConfig, - env, - syncTasks, - }); + await syncSandbox(sandboxAccountConfig, accountConfig, env, syncTasks); }; try { let availableSyncTasks = await getAvailableSyncTypes( @@ -156,7 +151,7 @@ exports.handler = async options => { if (!contactRecordsSyncPromptResult) { availableSyncTasks = availableSyncTasks.filter( - t => t.type !== syncTypes.OBJECT_RECORDS + t => t.type !== SYNC_TYPES.OBJECT_RECORDS ); } await handleSyncSandbox(availableSyncTasks); diff --git a/lang/en.lyaml b/lang/en.lyaml index 295c67f75..0b4f5c7e4 100644 --- a/lang/en.lyaml +++ b/lang/en.lyaml @@ -1420,6 +1420,7 @@ en: failure: invalidUser: "Couldn't create {{#bold}}{{ accountName }}{{/bold}} because your account has been removed from {{#bold}}{{ parentAccountName }}{{/bold}} or your permission set doesn't allow you to create the sandbox. To update your permissions, contact a super admin in {{#bold}}{{ parentAccountName }}{{/bold}}." 403Gating: "Couldn't create {{#bold}}{{ accountName }}{{/bold}} because {{#bold}}{{ parentAccountName }}{{/bold}} does not have access to development sandboxes. To opt in to the CRM Development Beta and use development sandboxes, visit https://app.hubspot.com/l/product-updates/in-beta?update=13899236." + usageLimitsFetch: "Unable to fetch sandbox usage limits. Please try again." limit: developer: one: "{{#bold}}{{ accountName }}{{/bold}} reached the limit of {{ limit }} development sandbox. @@ -1479,6 +1480,7 @@ en: syncInProgress: "Couldn't run the sync because there's another sync in progress. Wait for the current sync to finish and then try again. To check the sync status, visit the sync activity log: {{ url }}." notSuperAdmin: "Couldn't run the sync because you are not a super admin in {{ account }}. Ask the account owner for super admin access to the sandbox." objectNotFound: "Couldn't sync the sandbox because {{#bold}}{{ account }}{{/bold}} may have been deleted through the UI. Run {{#bold}}hs sandbox delete{{/bold}} to remove this account from the config. " + syncTypeFetch: "Unable to fetch available sandbox sync types. Please try again." errorHandlers: index: errorOccurred: "Error: {{ error }}" diff --git a/lib/buildAccount.ts b/lib/buildAccount.ts index 874f05a2a..e6e236678 100644 --- a/lib/buildAccount.ts +++ b/lib/buildAccount.ts @@ -27,7 +27,10 @@ const { HUBSPOT_ACCOUNT_TYPES, } = require('@hubspot/local-dev-lib/constants/config'); const { createSandbox } = require('@hubspot/local-dev-lib/api/sandboxHubs'); -const { sandboxApiTypeMap, handleSandboxCreateError } = require('./sandboxes'); +const { + SANDBOX_API_TYPE_MAP, + handleSandboxCreateError, +} = require('./sandboxes'); const { handleDeveloperTestAccountCreateError, } = require('./developerTestAccounts'); @@ -132,7 +135,7 @@ async function buildNewAccount({ let resultAccountId; try { if (isSandbox) { - const sandboxApiType = sandboxApiTypeMap[accountType]; // API expects sandbox type as 1 or 2. + const sandboxApiType = SANDBOX_API_TYPE_MAP[accountType]; // API expects sandbox type as 1 or 2. const { data } = await createSandbox(accountId, name, sandboxApiType); result = { name, ...data }; @@ -159,7 +162,7 @@ async function buildNewAccount({ }); if (isSandbox) { - handleSandboxCreateError({ err, env, accountConfig, name, accountId }); + handleSandboxCreateError(err, env, name, accountId); } if (isDeveloperTestAccount) { handleDeveloperTestAccountCreateError(err, env, accountId, portalLimit); diff --git a/lib/localDev.ts b/lib/localDev.ts index 0f9d7ebd2..20d5b02b9 100644 --- a/lib/localDev.ts +++ b/lib/localDev.ts @@ -223,13 +223,13 @@ const createSandboxForLocalDev = async (accountId, accountConfig, env) => { sandboxAccountConfig ); // For v1 sandboxes, keep sync here. Once we migrate to v2, this will be handled by BE automatically - await syncSandbox({ - accountConfig: sandboxAccountConfig, - parentAccountConfig: accountConfig, + await syncSandbox( + sandboxAccountConfig, + accountConfig, env, syncTasks, - slimInfoMessage: true, - }); + true + ); return targetAccountId; } catch (err) { logError(err); diff --git a/lib/sandboxSync.ts b/lib/sandboxSync.ts index 0b6587d3b..2fc927f5e 100644 --- a/lib/sandboxSync.ts +++ b/lib/sandboxSync.ts @@ -1,50 +1,50 @@ -// @ts-nocheck -const SpinniesManager = require('./ui/SpinniesManager'); -const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { i18n } = require('./lang'); -const { getAvailableSyncTypes } = require('./sandboxes'); -const { initiateSync } = require('@hubspot/local-dev-lib/api/sandboxSync'); -const { - debugError, - logError, - ApiErrorContext, -} = require('./errorHandlers/index'); -const { isSpecifiedError } = require('@hubspot/local-dev-lib/errors/index'); -const { getSandboxTypeAsString } = require('./sandboxes'); -const { getAccountId } = require('@hubspot/local-dev-lib/config'); -const { - getAccountIdentifier, -} = require('@hubspot/local-dev-lib/config/getAccountIdentifier'); -const { +import SpinniesManager from './ui/SpinniesManager'; +import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { initiateSync } from '@hubspot/local-dev-lib/api/sandboxSync'; +import { isSpecifiedError } from '@hubspot/local-dev-lib/errors/index'; +import { getAccountId } from '@hubspot/local-dev-lib/config'; +import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier'; +import { CLIAccount } from '@hubspot/local-dev-lib/types/Accounts'; +import { Environment } from '@hubspot/local-dev-lib/types/Config'; + +import { i18n } from './lang'; +import { getAvailableSyncTypes } from './sandboxes'; +import { debugError, logError, ApiErrorContext } from './errorHandlers/index'; +import { getSandboxTypeAsString } from './sandboxes'; +import { uiAccountDescription, uiLine, uiLink, uiCommandDisabledBanner, -} = require('./ui'); -const { isDevelopmentSandbox } = require('./accountTypes'); +} from './ui'; +import { isDevelopmentSandbox } from './accountTypes'; +import { SandboxSyncTask } from '../types/Sandboxes'; const i18nKey = 'lib.sandbox.sync'; -/** - * @param {Object} accountConfig - Account config of sandbox portal - * @param {Object} parentAccountConfig - Account config of parent portal - * @param {String} env - Environment (QA/Prod) - * @param {Array} syncTasks - Array of available sync tasks - * @returns - */ -const syncSandbox = async ({ - accountConfig, - parentAccountConfig, - env, - syncTasks, - slimInfoMessage = false, -}) => { +export async function syncSandbox( + accountConfig: CLIAccount, + parentAccountConfig: CLIAccount, + env: Environment, + syncTasks: Array, + slimInfoMessage = false +) { const id = getAccountIdentifier(accountConfig); const accountId = getAccountId(id); const parentId = getAccountIdentifier(parentAccountConfig); const parentAccountId = getAccountId(parentId); const isDevSandbox = isDevelopmentSandbox(accountConfig); + + if (!accountId || !parentAccountId) { + throw new Error( + i18n(`${i18nKey}.failure.invalidUser`, { + accountName: uiAccountDescription(accountId), + parentAccountName: uiAccountDescription(parentAccountId), + }) + ); + } + SpinniesManager.init({ succeedColor: 'white', }); @@ -190,8 +190,4 @@ const syncSandbox = async ({ uiLine(); logger.log(); } -}; - -module.exports = { - syncSandbox, -}; +} diff --git a/lib/sandboxes.ts b/lib/sandboxes.ts index 2f0599870..feb561dd8 100644 --- a/lib/sandboxes.ts +++ b/lib/sandboxes.ts @@ -1,59 +1,55 @@ -// @ts-nocheck -const { i18n } = require('./lang'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { - getSandboxUsageLimits, -} = require('@hubspot/local-dev-lib/api/sandboxHubs'); -const { fetchTypes } = require('@hubspot/local-dev-lib/api/sandboxSync'); -const { - getAccountId, - getEnv, - getConfigAccounts, -} = require('@hubspot/local-dev-lib/config'); -const { promptUser } = require('./prompts/promptUtils'); -const { isDevelopmentSandbox } = require('./accountTypes'); -const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls'); -const { - HUBSPOT_ACCOUNT_TYPES, -} = require('@hubspot/local-dev-lib/constants/config'); -const { - getAccountIdentifier, -} = require('@hubspot/local-dev-lib/config/getAccountIdentifier'); -const { uiAccountDescription } = require('./ui'); -const { +import { logger } from '@hubspot/local-dev-lib/logger'; +import { getSandboxUsageLimits } from '@hubspot/local-dev-lib/api/sandboxHubs'; +import { fetchTypes } from '@hubspot/local-dev-lib/api/sandboxSync'; +import { getAccountId, getConfigAccounts } from '@hubspot/local-dev-lib/config'; +import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls'; +import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config'; +import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier'; +import { isMissingScopeError, isSpecifiedError, -} = require('@hubspot/local-dev-lib/errors/index'); -const { getValidEnv } = require('@hubspot/local-dev-lib/environment'); -const { logError } = require('./errorHandlers/index'); +} from '@hubspot/local-dev-lib/errors/index'; +import { AccountType, CLIAccount } from '@hubspot/local-dev-lib/types/Accounts'; +import { Environment } from '@hubspot/local-dev-lib/types/Config'; -const syncTypes = { +import { i18n } from './lang'; +import { uiAccountDescription } from './ui'; +import { logError } from './errorHandlers/index'; +import { SandboxSyncTask } from '../types/Sandboxes'; + +const i18nKey = 'lib.sandbox'; + +export const SYNC_TYPES = { OBJECT_RECORDS: 'object-records', -}; +} as const; -const sandboxTypeMap = { +export const SANDBOX_TYPE_MAP = { dev: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, developer: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, development: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, standard: HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX, -}; +} as const; -const sandboxApiTypeMap = { +export const SANDBOX_API_TYPE_MAP = { [HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX]: 1, [HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX]: 2, -}; +} as const; -const getSandboxTypeAsString = accountType => { +export function getSandboxTypeAsString(accountType?: AccountType): string { if (accountType === HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX) { return 'development'; // Only place we're using this specific name } return 'standard'; -}; +} -function getHasSandboxesByType(parentAccountConfig, type) { +function getHasSandboxesByType( + parentAccountConfig: CLIAccount, + type: AccountType +): boolean { const id = getAccountIdentifier(parentAccountConfig); const parentPortalId = getAccountId(id); - const accountsList = getConfigAccounts(); + const accountsList = getConfigAccounts() || []; + for (const portal of accountsList) { if ( (portal.parentAccountId !== null || @@ -68,79 +64,46 @@ function getHasSandboxesByType(parentAccountConfig, type) { return false; } -function getSandboxLimit(error) { - // Error context should contain a limit property with a list of one number. That number is the current limit - const limit = error.context && error.context.limit && error.context.limit[0]; - return limit ? parseInt(limit, 10) : 1; // Default to 1 -} - // Fetches available sync types for a given sandbox portal -async function getAvailableSyncTypes(parentAccountConfig, config) { +export async function getAvailableSyncTypes( + parentAccountConfig: CLIAccount, + config: CLIAccount +): Promise> { const parentId = getAccountIdentifier(parentAccountConfig); const parentPortalId = getAccountId(parentId); const id = getAccountIdentifier(config); const portalId = getAccountId(id); + + if (!parentPortalId || !portalId) { + throw new Error(i18n(`${i18nKey}.sync.failure.syncTypeFetch`)); + } + const { data: { results: syncTypes }, } = await fetchTypes(parentPortalId, portalId); if (!syncTypes) { - throw new Error( - 'Unable to fetch available sandbox sync types. Please try again.' - ); + throw new Error(i18n(`${i18nKey}.sync.failure.syncTypeFetch`)); } return syncTypes.map(t => ({ type: t.name })); } -/** - * @param {Object} accountConfig - Account config of sandbox portal - * @param {Array} availableSyncTasks - Array of available sync tasks - * @param {Boolean} skipPrompt - Option to skip contact records prompt and return all available sync tasks - * @returns {Array} Adjusted available sync task items - */ -const getSyncTypesWithContactRecordsPrompt = async ( - accountConfig, - syncTasks, - skipPrompt = false -) => { - // TODO: remove this entire helper once hs sandbox sync is fully deprecated - const isDevSandbox = isDevelopmentSandbox(accountConfig); - if (isDevSandbox) { - // Disable dev sandbox from syncing contacts - return syncTasks.filter(t => t.type !== syncTypes.OBJECT_RECORDS); - } - if ( - syncTasks && - syncTasks.some(t => t.type === syncTypes.OBJECT_RECORDS) && - !skipPrompt - ) { - const { contactRecordsSyncPrompt } = await promptUser([ - { - name: 'contactRecordsSyncPrompt', - type: 'confirm', - message: i18n('lib.sandbox.sync.confirm.syncContactRecords.standard'), - }, - ]); - if (!contactRecordsSyncPrompt) { - return syncTasks.filter(t => t.type !== syncTypes.OBJECT_RECORDS); - } - } - return syncTasks; -}; - -/** - * @param {Object} accountConfig - Account config of sandbox portal - * @param {String} sandboxType - Sandbox type for limit validation - * @param {String} env - Environment - * @returns {null} - */ -const validateSandboxUsageLimits = async (accountConfig, sandboxType, env) => { +export async function validateSandboxUsageLimits( + accountConfig: CLIAccount, + sandboxType: AccountType, + env: Environment +): Promise { const id = getAccountIdentifier(accountConfig); const accountId = getAccountId(id); + + if (!accountId) { + throw new Error(i18n(`${i18nKey}.create.failure.usageLimitFetch`)); + } + const { data: { usage }, } = await getSandboxUsageLimits(accountId); if (!usage) { - throw new Error('Unable to fetch sandbox usage limits. Please try again.'); + throw new Error(i18n(`${i18nKey}.create.failure.usageLimitFetch`)); } if (sandboxType === HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX) { if (usage['DEVELOPER'].available === 0) { @@ -153,7 +116,7 @@ const validateSandboxUsageLimits = async (accountConfig, sandboxType, env) => { if (hasDevelopmentSandboxes) { throw new Error( i18n( - `lib.sandbox.create.failure.alreadyInConfig.developer.${ + `${i18nKey}.create.failure.alreadyInConfig.developer.${ plural ? 'other' : 'one' }`, { @@ -166,7 +129,7 @@ const validateSandboxUsageLimits = async (accountConfig, sandboxType, env) => { const baseUrl = getHubSpotWebsiteOrigin(env); throw new Error( i18n( - `lib.sandbox.create.failure.limit.developer.${ + `${i18nKey}.create.failure.limit.developer.${ plural ? 'other' : 'one' }`, { @@ -190,7 +153,7 @@ const validateSandboxUsageLimits = async (accountConfig, sandboxType, env) => { if (hasStandardSandboxes) { throw new Error( i18n( - `lib.sandbox.create.failure.alreadyInConfig.standard.${ + `${i18nKey}.create.failure.alreadyInConfig.standard.${ plural ? 'other' : 'one' }`, { @@ -203,7 +166,7 @@ const validateSandboxUsageLimits = async (accountConfig, sandboxType, env) => { const baseUrl = getHubSpotWebsiteOrigin(env); throw new Error( i18n( - `lib.sandbox.create.failure.limit.standard.${ + `${i18nKey}.create.failure.limit.standard.${ plural ? 'other' : 'one' }`, { @@ -216,25 +179,24 @@ const validateSandboxUsageLimits = async (accountConfig, sandboxType, env) => { } } } -}; +} -function handleSandboxCreateError({ - err, - env, - accountId, - name, - accountConfig, -}) { +export function handleSandboxCreateError( + err: unknown, + env: Environment, + name: string, + accountId: number +) { if (isMissingScopeError(err)) { logger.error( - i18n('lib.sandboxes.create.failure.scopes.message', { + i18n(`${i18nKey}.create.failure.scopes.message`, { accountName: uiAccountDescription(accountId), }) ); const websiteOrigin = getHubSpotWebsiteOrigin(env); const url = `${websiteOrigin}/personal-access-key/${accountId}`; logger.info( - i18n('lib.sandboxes.create.failure.scopes.instructions', { + i18n(`${i18nKey}.create.failure.scopes.instructions`, { accountName: uiAccountDescription(accountId), url, }) @@ -248,7 +210,7 @@ function handleSandboxCreateError({ ) { logger.log(''); logger.error( - i18n('lib.sandboxes.create.failure.invalidUser', { + i18n(`${i18nKey}.create.failure.invalidUser`, { accountName: name, parentAccountName: uiAccountDescription(accountId), }) @@ -263,117 +225,15 @@ function handleSandboxCreateError({ ) { logger.log(''); logger.error( - i18n('lib.sandboxes.create.failure.403Gating', { + i18n(`${i18nKey}.create.failure.403Gating`, { accountName: name, parentAccountName: uiAccountDescription(accountId), accountId, }) ); logger.log(''); - } else if ( - isSpecifiedError(err, { - statusCode: 400, - category: 'VALIDATION_ERROR', - subCategory: - 'SandboxErrors.NUM_DEVELOPMENT_SANDBOXES_LIMIT_EXCEEDED_ERROR', - }) && - err.error && - err.error.message - ) { - logger.log(''); - const devSandboxLimit = getSandboxLimit(err.error); - const plural = devSandboxLimit !== 1; - const hasDevelopmentSandboxes = getHasSandboxesByType( - accountConfig, - HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX - ); - if (hasDevelopmentSandboxes) { - logger.error( - i18n( - `lib.sandboxes.create.failure.alreadyInConfig.developer.${ - plural ? 'other' : 'one' - }`, - { - accountName: uiAccountDescription(accountId), - limit: devSandboxLimit, - } - ) - ); - } else { - const baseUrl = getHubSpotWebsiteOrigin(getValidEnv(getEnv(accountId))); - logger.error( - i18n( - `lib.sandboxes.create.failure.limit.developer.${ - plural ? 'other' : 'one' - }`, - { - accountName: uiAccountDescription(accountId), - limit: devSandboxLimit, - link: `${baseUrl}/sandboxes-developer/${accountId}/development`, - } - ) - ); - } - logger.log(''); - } else if ( - isSpecifiedError(err, { - statusCode: 400, - category: 'VALIDATION_ERROR', - subCategory: 'SandboxErrors.NUM_STANDARD_SANDBOXES_LIMIT_EXCEEDED_ERROR', - }) && - err.error && - err.error.message - ) { - logger.log(''); - const standardSandboxLimit = getSandboxLimit(err.error); - const plural = standardSandboxLimit !== 1; - const hasStandardSandboxes = getHasSandboxesByType( - accountConfig, - HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX - ); - if (hasStandardSandboxes) { - logger.error( - i18n( - `lib.sandboxes.create.failure.alreadyInConfig.standard.${ - plural ? 'other' : 'one' - }`, - { - accountName: uiAccountDescription(accountId), - limit: standardSandboxLimit, - } - ) - ); - } else { - const baseUrl = getHubSpotWebsiteOrigin(getValidEnv(getEnv(accountId))); - logger.error( - i18n( - `lib.sandboxes.create.failure.limit.standard.${ - plural ? 'other' : 'one' - }`, - { - accountName: uiAccountDescription(accountId), - limit: standardSandboxLimit, - link: `${baseUrl}/sandboxes-developer/${accountId}/standard`, - } - ) - ); - } - logger.log(''); } else { logError(err); } throw err; } - -module.exports = { - sandboxTypeMap, - sandboxApiTypeMap, - syncTypes, - getSandboxTypeAsString, - getHasSandboxesByType, - getSandboxLimit, - validateSandboxUsageLimits, - getAvailableSyncTypes, - getSyncTypesWithContactRecordsPrompt, - handleSandboxCreateError, -}; diff --git a/types/Sandboxes.ts b/types/Sandboxes.ts new file mode 100644 index 000000000..3b58ecfc5 --- /dev/null +++ b/types/Sandboxes.ts @@ -0,0 +1,3 @@ +export type SandboxSyncTask = { + type: string; +}; From 0c4c1c1539c9cb5b9a6bcb618535aab5aa5de9ac Mon Sep 17 00:00:00 2001 From: Branden Rodgers Date: Mon, 13 Jan 2025 12:44:59 -0500 Subject: [PATCH 5/7] chore: Port dependencyManagement to TS (#1334) --- lib/dependencyManagement.ts | 64 +++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/lib/dependencyManagement.ts b/lib/dependencyManagement.ts index 7d124ee3a..e5ef8a62a 100644 --- a/lib/dependencyManagement.ts +++ b/lib/dependencyManagement.ts @@ -1,21 +1,20 @@ -// @ts-nocheck -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { getProjectConfig } = require('./projects'); -const { exec: execAsync } = require('child_process'); -const { walk } = require('@hubspot/local-dev-lib/fs'); -const path = require('path'); -const { uiLink } = require('./ui'); -const util = require('util'); -const { i18n } = require('./lang'); -const SpinniesManager = require('./ui/SpinniesManager'); -const fs = require('fs'); -const pkg = require('../package.json'); -const DEFAULT_PACKAGE_MANAGER = 'npm'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { getProjectConfig } from './projects'; +import { exec as execAsync } from 'child_process'; +import { walk } from '@hubspot/local-dev-lib/fs'; +import path from 'path'; +import { uiLink } from './ui'; +import util from 'util'; +import { i18n } from './lang'; +import SpinniesManager from './ui/SpinniesManager'; +import fs from 'fs'; +import pkg from '../package.json'; +const DEFAULT_PACKAGE_MANAGER = 'npm'; const i18nKey = `commands.project.subcommands.installDeps`; class NoPackageJsonFilesError extends Error { - constructor(projectName) { + constructor(projectName: string) { super( i18n(`${i18nKey}.noPackageJsonInProject`, { projectName, @@ -28,7 +27,7 @@ class NoPackageJsonFilesError extends Error { } } -export async function isGloballyInstalled(command) { +export async function isGloballyInstalled(command: string): Promise { const exec = util.promisify(execAsync); try { await exec(`${command} --version`); @@ -38,24 +37,36 @@ export async function isGloballyInstalled(command) { } } -export async function getLatestCliVersion(): { latest: string; next: string } { +export async function getLatestCliVersion(): Promise<{ + latest: string; + next: string; +}> { const exec = util.promisify(execAsync); const { stdout } = await exec(`npm info ${pkg.name} dist-tags --json`); const { latest, next } = JSON.parse(stdout); return { latest, next }; } -async function installPackages({ packages, installLocations }) { +export async function installPackages({ + packages, + installLocations, +}: { + packages?: string[]; + installLocations?: string[]; +}): Promise { const installDirs = installLocations || (await getProjectPackageJsonLocations()); await Promise.all( installDirs.map(async dir => { - await installPackagesInDirectory(packages, dir); + await installPackagesInDirectory(dir, packages); }) ); } -async function installPackagesInDirectory(packages, directory) { +async function installPackagesInDirectory( + directory: string, + packages?: string[] +): Promise { const spinner = `installingDependencies-${directory}`; const relativeDir = path.relative(process.cwd(), directory); SpinniesManager.init(); @@ -102,7 +113,7 @@ async function installPackagesInDirectory(packages, directory) { } } -async function getProjectPackageJsonLocations() { +export async function getProjectPackageJsonLocations(): Promise { const projectConfig = await getProjectConfig(); if ( @@ -148,7 +159,7 @@ async function getProjectPackageJsonLocations() { throw new NoPackageJsonFilesError(name); } - const packageParentDirs = []; + const packageParentDirs: string[] = []; packageJsonFiles.forEach(packageJsonFile => { const parentDir = path.dirname(packageJsonFile); packageParentDirs.push(parentDir); @@ -157,19 +168,10 @@ async function getProjectPackageJsonLocations() { return packageParentDirs; } -export async function hasMissingPackages(directory) { +export async function hasMissingPackages(directory: string): Promise { const exec = util.promisify(execAsync); const { stdout } = await exec(`npm install --ignore-scripts --dry-run`, { cwd: directory, }); return !stdout?.includes('up to date in'); } - -module.exports = { - isGloballyInstalled, - installPackages, - DEFAULT_PACKAGE_MANAGER, - getProjectPackageJsonLocations, - getLatestCliVersion, - hasMissingPackages, -}; From d58f2906711ffa0f88bff1d550a859e9e42aec73 Mon Sep 17 00:00:00 2001 From: Joe Yeager Date: Mon, 13 Jan 2025 10:19:16 -0800 Subject: [PATCH 6/7] chore: Build the project before running the tests (#1330) --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e2af1bba7..06cc0c486 100644 --- a/package.json +++ b/package.json @@ -64,10 +64,10 @@ "list-all-commands": "yarn ts-node ./scripts/get-all-commands.ts", "prettier:write": "prettier --write ./**/*.{ts,js,json}", "test": "jest", - "test-cli": "yarn --cwd 'acceptance-tests' test-ci", - "test-cli-debug": "yarn --cwd 'acceptance-tests' test-debug", - "test-cli-qa": "yarn --cwd 'acceptance-tests' test-qa", - "test-cli-latest": "yarn build-docker && docker container run -it --rm --name=hs-cli-container hs-cli-image yarn --cwd 'acceptance-tests' test-latest", + "test-cli": "yarn build && yarn --cwd 'acceptance-tests' test-ci", + "test-cli-debug": "yarn build && yarn --cwd 'acceptance-tests' test-debug", + "test-cli-qa": "yarn build && yarn --cwd 'acceptance-tests' test-qa", + "test-cli-latest": "yarn build && yarn build-docker && docker container run -it --rm --name=hs-cli-container hs-cli-image yarn --cwd 'acceptance-tests' test-latest", "build-docker": "docker image build --tag hs-cli-image . && docker image prune -f", "circular-deps": "yarn madge --circular .", "release": "yarn ts-node ./scripts/release.ts release", From 196917cc857384ead27da0225fad93f78e38e075 Mon Sep 17 00:00:00 2001 From: Joe Yeager Date: Mon, 13 Jan 2025 10:22:56 -0800 Subject: [PATCH 7/7] chore: Disable secrets test while caching issue is addressed (#1336) --- acceptance-tests/tests/workflows/secretsFlow.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/acceptance-tests/tests/workflows/secretsFlow.spec.ts b/acceptance-tests/tests/workflows/secretsFlow.spec.ts index 5eaaca3f2..ad07caa2a 100644 --- a/acceptance-tests/tests/workflows/secretsFlow.spec.ts +++ b/acceptance-tests/tests/workflows/secretsFlow.spec.ts @@ -25,7 +25,8 @@ async function waitForSecretsListToContainSecret(testState: TestState) { .toContain(SECRET.name); } -describe('Secrets Flow', () => { +// TODO: Re-enable when the caching issue is resolved on the BE +describe.skip('Secrets Flow', () => { let testState: TestState; beforeAll(async () => {