@hubspot/cli 7.9.0 → 7.9.1-experimental.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/bin/cli.js +5 -4
  2. package/commands/__tests__/getStarted.test.js +10 -0
  3. package/commands/__tests__/project.test.js +2 -0
  4. package/commands/account/__tests__/rename.test.js +42 -0
  5. package/commands/account/auth.js +10 -14
  6. package/commands/account/clean.js +11 -19
  7. package/commands/account/createOverride.js +17 -23
  8. package/commands/account/info.js +8 -5
  9. package/commands/account/list.js +13 -18
  10. package/commands/account/remove.js +23 -22
  11. package/commands/account/removeOverride.js +8 -16
  12. package/commands/account/rename.d.ts +1 -1
  13. package/commands/account/rename.js +6 -3
  14. package/commands/account/use.js +19 -8
  15. package/commands/app/__tests__/migrate.test.js +8 -4
  16. package/commands/app/migrate.js +2 -2
  17. package/commands/auth.js +18 -14
  18. package/commands/cms/theme/preview.js +1 -4
  19. package/commands/config/migrate.js +5 -5
  20. package/commands/config/set.js +1 -2
  21. package/commands/customObject/createSchema.js +2 -3
  22. package/commands/customObject/updateSchema.js +2 -3
  23. package/commands/getStarted.js +15 -22
  24. package/commands/hubdb/__tests__/list.test.js +1 -0
  25. package/commands/hubdb/list.js +2 -2
  26. package/commands/hubdb.d.ts +1 -1
  27. package/commands/init.js +36 -32
  28. package/commands/project/__tests__/deploy.test.js +10 -5
  29. package/commands/project/__tests__/devUnifiedFlow.test.js +6 -4
  30. package/commands/project/__tests__/logs.test.js +4 -0
  31. package/commands/project/__tests__/updateDeps.test.js +142 -0
  32. package/commands/project/__tests__/validate.test.js +2 -2
  33. package/commands/project/cloneApp.js +2 -2
  34. package/commands/project/create.js +0 -1
  35. package/commands/project/deploy.js +2 -2
  36. package/commands/project/dev/deprecatedFlow.js +4 -5
  37. package/commands/project/dev/index.js +14 -4
  38. package/commands/project/dev/unifiedFlow.js +4 -5
  39. package/commands/project/listBuilds.js +7 -1
  40. package/commands/project/logs.js +2 -3
  41. package/commands/project/profile/add.js +6 -7
  42. package/commands/project/profile/delete.js +2 -2
  43. package/commands/project/updateDeps.d.ts +6 -0
  44. package/commands/project/updateDeps.js +80 -0
  45. package/commands/project/upload.js +9 -3
  46. package/commands/project/validate.js +9 -3
  47. package/commands/project/watch.js +7 -2
  48. package/commands/project.js +2 -0
  49. package/commands/sandbox/__tests__/create.test.js +14 -5
  50. package/commands/sandbox/create.js +4 -5
  51. package/commands/sandbox/delete.js +23 -20
  52. package/commands/testAccount/__tests__/create.test.js +68 -0
  53. package/commands/testAccount/create.d.ts +8 -0
  54. package/commands/testAccount/create.js +135 -45
  55. package/commands/testAccount/delete.js +9 -8
  56. package/commands/testAccount/importData.d.ts +1 -1
  57. package/lang/en.d.ts +3199 -3185
  58. package/lang/en.js +52 -14
  59. package/lib/__tests__/buildAccount.test.js +22 -30
  60. package/lib/__tests__/commonOpts.test.js +9 -13
  61. package/lib/__tests__/dependencyManagement.test.js +273 -1
  62. package/lib/__tests__/developerTestAccounts.test.js +29 -17
  63. package/lib/__tests__/importData.test.js +20 -10
  64. package/lib/__tests__/oauth.test.js +19 -8
  65. package/lib/__tests__/sandboxSync.test.js +33 -11
  66. package/lib/__tests__/sandboxes.test.js +30 -19
  67. package/lib/__tests__/usageTracking.test.js +10 -10
  68. package/lib/__tests__/validation.test.js +32 -32
  69. package/lib/accountTypes.d.ts +9 -9
  70. package/lib/accountTypes.js +2 -4
  71. package/lib/app/__tests__/migrate.test.js +15 -0
  72. package/lib/app/__tests__/migrate_legacy.test.js +9 -0
  73. package/lib/app/migrate_legacy.d.ts +2 -2
  74. package/lib/buildAccount.d.ts +4 -4
  75. package/lib/buildAccount.js +7 -14
  76. package/lib/commonOpts.js +5 -8
  77. package/lib/configMigrate.d.ts +2 -2
  78. package/lib/configMigrate.js +42 -18
  79. package/lib/configOptions.js +3 -2
  80. package/lib/constants.d.ts +1 -0
  81. package/lib/constants.js +6 -0
  82. package/lib/dependencyManagement.d.ts +8 -2
  83. package/lib/dependencyManagement.js +75 -12
  84. package/lib/developerTestAccounts.d.ts +3 -3
  85. package/lib/developerTestAccounts.js +4 -7
  86. package/lib/doctor/DiagnosticInfoBuilder.d.ts +1 -1
  87. package/lib/doctor/DiagnosticInfoBuilder.js +9 -6
  88. package/lib/doctor/Doctor.js +4 -3
  89. package/lib/doctor/__tests__/Diagnosis.test.js +4 -3
  90. package/lib/doctor/__tests__/DiagnosticInfoBuilder.test.js +17 -9
  91. package/lib/doctor/__tests__/Doctor.test.js +14 -0
  92. package/lib/importData.js +8 -7
  93. package/lib/links.js +5 -5
  94. package/lib/mcp/__tests__/setup.test.d.ts +1 -0
  95. package/lib/mcp/__tests__/setup.test.js +127 -0
  96. package/lib/mcp/setup.d.ts +4 -12
  97. package/lib/mcp/setup.js +34 -1
  98. package/lib/middleware/__test__/commandTargetingUtils.test.js +3 -3
  99. package/lib/middleware/__test__/configMiddleware.test.js +23 -22
  100. package/lib/middleware/__test__/gitMiddleware.test.js +9 -7
  101. package/lib/middleware/autoUpdateMiddleware.d.ts +3 -1
  102. package/lib/middleware/autoUpdateMiddleware.js +10 -2
  103. package/lib/middleware/commandTargetingUtils.js +2 -2
  104. package/lib/middleware/configMiddleware.d.ts +6 -1
  105. package/lib/middleware/configMiddleware.js +36 -15
  106. package/lib/middleware/gitMiddleware.js +8 -4
  107. package/lib/npm.d.ts +3 -0
  108. package/lib/npm.js +6 -0
  109. package/lib/oauth.d.ts +2 -2
  110. package/lib/oauth.js +8 -10
  111. package/lib/projects/__tests__/AppDevModeInterface.test.js +17 -6
  112. package/lib/projects/__tests__/DevServerManager.test.js +1 -0
  113. package/lib/projects/__tests__/LocalDevProcess.test.js +1 -0
  114. package/lib/projects/__tests__/components.test.js +148 -24
  115. package/lib/projects/__tests__/deploy.test.js +1 -0
  116. package/lib/projects/__tests__/platformVersion.test.js +5 -1
  117. package/lib/projects/__tests__/projects.test.js +13 -42
  118. package/lib/projects/components.js +76 -20
  119. package/lib/projects/config.js +5 -9
  120. package/lib/projects/create/__tests__/v2.test.js +11 -0
  121. package/lib/projects/localDev/AppDevModeInterface.js +2 -2
  122. package/lib/projects/localDev/DevServerManager_DEPRECATED.js +2 -2
  123. package/lib/projects/localDev/LocalDevLogger.js +4 -4
  124. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -3
  125. package/lib/projects/localDev/helpers/account.d.ts +10 -10
  126. package/lib/projects/localDev/helpers/account.js +6 -11
  127. package/lib/projects/platformVersion.js +1 -1
  128. package/lib/projects/urls.js +5 -6
  129. package/lib/prompts/__tests__/createDeveloperTestAccountConfigPrompt.test.d.ts +1 -0
  130. package/lib/prompts/__tests__/createDeveloperTestAccountConfigPrompt.test.js +153 -0
  131. package/lib/prompts/__tests__/downloadProjectPrompt.test.js +7 -5
  132. package/lib/prompts/accountNamePrompt.js +3 -3
  133. package/lib/prompts/accountsPrompt.d.ts +1 -1
  134. package/lib/prompts/accountsPrompt.js +6 -7
  135. package/lib/prompts/confirmImportDataPrompt.js +2 -2
  136. package/lib/prompts/createDeveloperTestAccountConfigPrompt.d.ts +5 -0
  137. package/lib/prompts/createDeveloperTestAccountConfigPrompt.js +76 -66
  138. package/lib/prompts/downloadProjectPrompt.d.ts +1 -0
  139. package/lib/prompts/downloadProjectPrompt.js +5 -2
  140. package/lib/prompts/importDataTestAccountSelectPrompt.js +4 -5
  141. package/lib/prompts/personalAccessKeyPrompt.js +2 -2
  142. package/lib/prompts/projectDevTargetAccountPrompt.d.ts +3 -3
  143. package/lib/prompts/projectDevTargetAccountPrompt.js +5 -7
  144. package/lib/prompts/sandboxesPrompt.js +7 -8
  145. package/lib/prompts/setAsDefaultAccountPrompt.js +7 -6
  146. package/lib/sandboxSync.d.ts +2 -2
  147. package/lib/sandboxSync.js +3 -9
  148. package/lib/sandboxes.d.ts +4 -4
  149. package/lib/sandboxes.js +6 -11
  150. package/lib/serverlessLogs.js +2 -2
  151. package/lib/theme/__tests__/migrate.test.js +15 -0
  152. package/lib/ui/index.js +6 -3
  153. package/lib/usageTracking.js +15 -8
  154. package/lib/validation.js +13 -11
  155. package/mcp-server/tools/cms/HsCreateFunctionTool.js +8 -2
  156. package/mcp-server/tools/cms/HsCreateModuleTool.d.ts +4 -4
  157. package/mcp-server/tools/cms/HsCreateModuleTool.js +8 -2
  158. package/mcp-server/tools/cms/HsCreateTemplateTool.js +8 -2
  159. package/mcp-server/tools/cms/HsFunctionLogsTool.d.ts +4 -4
  160. package/mcp-server/tools/cms/HsFunctionLogsTool.js +6 -2
  161. package/mcp-server/tools/cms/HsListFunctionsTool.js +5 -1
  162. package/mcp-server/tools/cms/HsListTool.js +5 -1
  163. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -0
  164. package/mcp-server/tools/index.js +4 -0
  165. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +1 -1
  166. package/mcp-server/tools/project/AddFeatureToProjectTool.js +9 -3
  167. package/mcp-server/tools/project/CreateProjectTool.js +8 -2
  168. package/mcp-server/tools/project/CreateTestAccountTool.d.ts +41 -0
  169. package/mcp-server/tools/project/CreateTestAccountTool.js +150 -0
  170. package/mcp-server/tools/project/DeployProjectTool.d.ts +1 -1
  171. package/mcp-server/tools/project/DeployProjectTool.js +8 -2
  172. package/mcp-server/tools/project/DocFetchTool.d.ts +1 -1
  173. package/mcp-server/tools/project/DocFetchTool.js +9 -5
  174. package/mcp-server/tools/project/DocsSearchTool.d.ts +1 -1
  175. package/mcp-server/tools/project/DocsSearchTool.js +12 -8
  176. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +1 -1
  177. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +11 -7
  178. package/mcp-server/tools/project/GetApplicationInfoTool.d.ts +1 -1
  179. package/mcp-server/tools/project/GetApplicationInfoTool.js +11 -7
  180. package/mcp-server/tools/project/GetBuildStatusTool.d.ts +26 -0
  181. package/mcp-server/tools/project/GetBuildStatusTool.js +164 -0
  182. package/mcp-server/tools/project/GetConfigValuesTool.d.ts +1 -1
  183. package/mcp-server/tools/project/GetConfigValuesTool.js +11 -7
  184. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +1 -1
  185. package/mcp-server/tools/project/GuidedWalkthroughTool.js +7 -3
  186. package/mcp-server/tools/project/UploadProjectTools.d.ts +9 -3
  187. package/mcp-server/tools/project/UploadProjectTools.js +51 -5
  188. package/mcp-server/tools/project/ValidateProjectTool.d.ts +1 -1
  189. package/mcp-server/tools/project/ValidateProjectTool.js +7 -3
  190. package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.d.ts +1 -0
  191. package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +454 -0
  192. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +5 -1
  193. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +25 -13
  194. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +7 -5
  195. package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +7 -5
  196. package/mcp-server/tools/project/__tests__/GetBuildStatusTool.test.d.ts +1 -0
  197. package/mcp-server/tools/project/__tests__/GetBuildStatusTool.test.js +240 -0
  198. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +8 -6
  199. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +56 -4
  200. package/mcp-server/utils/__tests__/content.test.js +21 -20
  201. package/mcp-server/utils/__tests__/feedbackTracking.test.js +33 -28
  202. package/mcp-server/utils/content.d.ts +1 -1
  203. package/mcp-server/utils/content.js +2 -2
  204. package/mcp-server/utils/feedbackTracking.d.ts +1 -1
  205. package/mcp-server/utils/feedbackTracking.js +3 -3
  206. package/mcp-server/utils/toolUsageTracking.js +4 -3
  207. package/package.json +8 -7
  208. package/lang/en.lyaml +0 -1508
  209. package/lib/lang.d.ts +0 -8
  210. package/lib/lang.js +0 -72
  211. package/mcp-server/utils/__tests__/cliConfig.test.js +0 -110
  212. package/mcp-server/utils/cliConfig.d.ts +0 -1
  213. package/mcp-server/utils/cliConfig.js +0 -12
  214. /package/{mcp-server/utils/__tests__/cliConfig.test.d.ts → commands/project/__tests__/updateDeps.test.d.ts} +0 -0
@@ -3,11 +3,16 @@ import { CLIOptions } from '@hubspot/local-dev-lib/types/CLIOptions';
3
3
  export declare function handleDeprecatedEnvVariables(argv: Arguments<{
4
4
  useEnv?: boolean;
5
5
  }>): void;
6
+ export declare function handleCustomConfigLocationMiddleware(argv: Arguments<{
7
+ useEnv?: boolean;
8
+ config?: string;
9
+ }>): void;
6
10
  /**
7
11
  * Auto-injects the derivedAccountId flag into all commands
8
12
  */
9
13
  export declare function injectAccountIdMiddleware(argv: Arguments<{
10
14
  account?: string;
15
+ config?: string;
11
16
  }>): Promise<void>;
12
- export declare function loadAndValidateConfigMiddleware(argv: Arguments<CLIOptions>): Promise<void>;
17
+ export declare function validateConfigMiddleware(argv: Arguments<CLIOptions>): Promise<void>;
13
18
  export declare function validateAccountOptions(argv: Arguments): Promise<void>;
@@ -1,9 +1,11 @@
1
- import { loadConfig, getAccountId, configFileExists, getConfigPath, validateConfig, } from '@hubspot/local-dev-lib/config';
1
+ import path from 'path';
2
+ import { getConfigAccountIfExists, validateConfig, getConfigDefaultAccountIfExists, configFileExists, } from '@hubspot/local-dev-lib/config';
3
+ import { getCwd } from '@hubspot/local-dev-lib/path';
2
4
  import { validateAccount } from '../validation.js';
3
5
  import { EXIT_CODES } from '../enums/exitCodes.js';
4
6
  import { commands } from '../../lang/en.js';
5
7
  import { uiDeprecatedTag } from '../ui/index.js';
6
- import { isTargetedCommand, shouldLoadConfigForCommand, shouldRunAccountValidationForCommand, shouldRunConfigValidationForCommand, } from './commandTargetingUtils.js';
8
+ import { shouldLoadConfigForCommand, shouldRunAccountValidationForCommand, shouldRunConfigValidationForCommand, } from './commandTargetingUtils.js';
7
9
  import { parseStringToNumber } from '../parsing.js';
8
10
  import { uiLogger } from '../ui/logger.js';
9
11
  import { lib } from '../../lang/en.js';
@@ -17,6 +19,18 @@ export function handleDeprecatedEnvVariables(argv) {
17
19
  process.env.HUBSPOT_ACCOUNT_ID = process.env.HUBSPOT_PORTAL_ID;
18
20
  }
19
21
  }
22
+ export function handleCustomConfigLocationMiddleware(argv) {
23
+ const { useEnv, config } = argv;
24
+ if (useEnv) {
25
+ process.env.USE_ENVIRONMENT_HUBSPOT_CONFIG = 'true';
26
+ }
27
+ else if (config && typeof config === 'string') {
28
+ const absoluteConfigPath = path.isAbsolute(config)
29
+ ? config
30
+ : path.join(getCwd(), config);
31
+ process.env.HUBSPOT_CONFIG_PATH = absoluteConfigPath;
32
+ }
33
+ }
20
34
  /**
21
35
  * Auto-injects the derivedAccountId flag into all commands
22
36
  */
@@ -33,10 +47,23 @@ export async function injectAccountIdMiddleware(argv) {
33
47
  }
34
48
  }
35
49
  else {
36
- argv.derivedAccountId = getAccountId(account);
50
+ // Wrap in try-catch to handle cases where config file doesn't exist yet (e.g., during hs init)
51
+ try {
52
+ let accountInConfig = account
53
+ ? getConfigAccountIfExists(account)
54
+ : undefined;
55
+ if (!accountInConfig) {
56
+ accountInConfig = getConfigDefaultAccountIfExists();
57
+ }
58
+ argv.derivedAccountId = accountInConfig?.accountId;
59
+ }
60
+ catch (err) {
61
+ // Config file doesn't exist yet, which is fine for commands like hs init
62
+ argv.derivedAccountId = undefined;
63
+ }
37
64
  }
38
65
  }
39
- export async function loadAndValidateConfigMiddleware(argv) {
66
+ export async function validateConfigMiddleware(argv) {
40
67
  // Skip this when no command is provided
41
68
  if (!argv._.length || argv.help) {
42
69
  return;
@@ -45,23 +72,17 @@ export async function loadAndValidateConfigMiddleware(argv) {
45
72
  if (!shouldLoadConfigForCommand(argv._)) {
46
73
  return;
47
74
  }
48
- // If the config file exists and the --config flag is used, exit with an error
49
- if (configFileExists(true) &&
50
- argv.config &&
51
- !isTargetedCommand(argv._, { config: { migrate: true } })) {
52
- uiLogger.error(commands.generalErrors.loadConfigMiddleware.configFileExists(getConfigPath()));
53
- process.exit(EXIT_CODES.ERROR);
54
- }
55
- const config = loadConfig(argv.config, argv);
56
75
  // We don't run validation for auth because users should be able to run it when
57
76
  // no accounts are configured, but we still want to exit if the config file is not found
58
- if (isTargetedCommand(argv._, { auth: true }) && !config) {
77
+ if (!process.env.USE_ENVIRONMENT_HUBSPOT_CONFIG && !configFileExists()) {
78
+ console.error('Config file not found, run hs account auth to configure your account');
59
79
  process.exit(EXIT_CODES.ERROR);
60
80
  }
61
81
  // Only validate the config if the command requires it
62
82
  if (shouldRunConfigValidationForCommand(argv._)) {
63
- const configIsValid = validateConfig();
64
- if (!configIsValid) {
83
+ const { isValid, errors } = validateConfig();
84
+ if (!isValid) {
85
+ uiLogger.error(commands.generalErrors.validateConfigMiddleware.configValidationFailed(errors));
65
86
  process.exit(EXIT_CODES.ERROR);
66
87
  }
67
88
  }
@@ -1,15 +1,19 @@
1
- import { getConfigPath, configFileExists } from '@hubspot/local-dev-lib/config';
1
+ import { getConfigFilePath, globalConfigFileExists, } from '@hubspot/local-dev-lib/config';
2
2
  import { checkAndWarnGitInclusion } from '../ui/git.js';
3
+ import { debugError } from '../errorHandlers/index.js';
3
4
  export function checkAndWarnGitInclusionMiddleware(argv) {
4
5
  // Skip this when no command is provided
5
6
  if (argv._.length) {
6
7
  // Skip if using global config
7
- if (configFileExists(true)) {
8
+ if (globalConfigFileExists()) {
8
9
  return;
9
10
  }
10
- const configPath = getConfigPath();
11
- if (configPath) {
11
+ try {
12
+ const configPath = getConfigFilePath();
12
13
  checkAndWarnGitInclusion(configPath);
13
14
  }
15
+ catch (error) {
16
+ debugError(error);
17
+ }
14
18
  }
15
19
  }
package/lib/npm.d.ts CHANGED
@@ -7,3 +7,6 @@ export declare function getLatestCliVersion(): Promise<{
7
7
  export declare function executeInstall(packages?: string[], flags?: string | null, options?: {
8
8
  cwd?: string;
9
9
  }): Promise<void>;
10
+ export declare function executeUpdate(packages?: string[], flags?: string | null, options?: {
11
+ cwd?: string;
12
+ }): Promise<void>;
package/lib/npm.js CHANGED
@@ -25,3 +25,9 @@ export async function executeInstall(packages = [], flags, options) {
25
25
  const exec = util.promisify(execAsync);
26
26
  await exec(installCommand, options);
27
27
  }
28
+ export async function executeUpdate(packages = [], flags, options) {
29
+ const updateCommand = `${DEFAULT_PACKAGE_MANAGER} update${flags ? ` ${flags}` : ''} ${packages.join(' ')}`;
30
+ uiLogger.debug('Running', updateCommand);
31
+ const exec = util.promisify(execAsync);
32
+ await exec(updateCommand, options);
33
+ }
package/lib/oauth.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { OAuth2ManagerAccountConfig } from '@hubspot/local-dev-lib/types/Accounts';
2
- export declare function authenticateWithOauth(accountConfig: OAuth2ManagerAccountConfig): Promise<void>;
1
+ import { OAuthConfigAccount } from '@hubspot/local-dev-lib/types/Accounts';
2
+ export declare function authenticateWithOauth(accountConfig: OAuthConfigAccount): Promise<void>;
package/lib/oauth.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import express from 'express';
2
2
  import open from 'open';
3
3
  import { OAuth2Manager } from '@hubspot/local-dev-lib/models/OAuth2Manager';
4
- import { getAccountConfig } from '@hubspot/local-dev-lib/config';
5
- import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier';
4
+ import { getConfigAccountById } from '@hubspot/local-dev-lib/config';
6
5
  import { addOauthToAccountConfig } from '@hubspot/local-dev-lib/oauth';
7
6
  import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
8
7
  import { uiLogger } from './ui/logger.js';
@@ -14,15 +13,15 @@ import { EXIT_CODES } from './enums/exitCodes.js';
14
13
  const PORT = 3000;
15
14
  const redirectUri = `http://localhost:${PORT}/oauth-callback`;
16
15
  function buildAuthUrl(oauthManager) {
17
- const { env: accountEnv, clientId, scopes: accountScopes, } = oauthManager.account;
16
+ const { env: accountEnv, auth } = oauthManager.account;
18
17
  const env = accountEnv || ENVIRONMENTS.PROD;
19
- const scopes = accountScopes || DEFAULT_OAUTH_SCOPES;
20
- if (!clientId) {
18
+ const scopes = auth.scopes.length > 0 ? auth.scopes : DEFAULT_OAUTH_SCOPES;
19
+ if (!auth.clientId) {
21
20
  uiLogger.error(lib.oauth.missingClientId);
22
21
  process.exit(EXIT_CODES.ERROR);
23
22
  }
24
23
  return (`${getHubSpotWebsiteOrigin(env)}/oauth/${oauthManager.account.accountId}/authorize` +
25
- `?client_id=${encodeURIComponent(clientId)}` + // app's client ID
24
+ `?client_id=${encodeURIComponent(auth.clientId)}` + // app's client ID
26
25
  `&scope=${encodeURIComponent(scopes.join(' '))}` + // scopes being requested by the app
27
26
  `&redirect_uri=${encodeURIComponent(redirectUri)}` // where to send the user after the consent page
28
27
  );
@@ -45,8 +44,8 @@ async function authorize(oauthManager) {
45
44
  if (req.query.code) {
46
45
  const authCodeProof = {
47
46
  grant_type: 'authorization_code',
48
- client_id: oauthManager.account.clientId,
49
- client_secret: oauthManager.account.clientSecret,
47
+ client_id: oauthManager.account.auth.clientId,
48
+ client_secret: oauthManager.account.auth.clientSecret,
50
49
  redirect_uri: redirectUri,
51
50
  code: req.query.code,
52
51
  };
@@ -82,8 +81,7 @@ async function authorize(oauthManager) {
82
81
  });
83
82
  }
84
83
  function setupOauth(accountConfig) {
85
- const accountId = getAccountIdentifier(accountConfig);
86
- const config = getAccountConfig(accountId);
84
+ const config = getConfigAccountById(accountConfig.accountId);
87
85
  return new OAuth2Manager({
88
86
  ...accountConfig,
89
87
  env: accountConfig.env || config?.env || ENVIRONMENTS.PROD,
@@ -12,7 +12,7 @@ vi.mock('@hubspot/ui-extensions-dev-server', () => {
12
12
  });
13
13
  import { fetchAppInstallationData } from '@hubspot/local-dev-lib/api/localDevAuth';
14
14
  import { fetchAppMetadataByUid, fetchPublicAppProductionInstallCounts, installStaticAuthAppOnTestAccount, } from '@hubspot/local-dev-lib/api/appsDev';
15
- import { getAccountConfig } from '@hubspot/local-dev-lib/config';
15
+ import { getConfigAccountById } from '@hubspot/local-dev-lib/config';
16
16
  import AppDevModeInterface from '../localDev/AppDevModeInterface.js';
17
17
  import LocalDevState from '../localDev/LocalDevState.js';
18
18
  import LocalDevLogger from '../localDev/LocalDevLogger.js';
@@ -121,7 +121,7 @@ describe('AppDevModeInterface', () => {
121
121
  previouslyAuthorizedScopeGroups: [],
122
122
  },
123
123
  });
124
- getAccountConfig.mockReturnValue({
124
+ getConfigAccountById.mockReturnValue({
125
125
  parentAccountId: 12345,
126
126
  });
127
127
  isDeveloperTestAccount.mockReturnValue(true);
@@ -290,13 +290,14 @@ describe('AppDevModeInterface', () => {
290
290
  });
291
291
  it('should handle app reinstallation', async () => {
292
292
  // Set up conditions for non-automatic installation
293
- getAccountConfig.mockReturnValue(null);
294
293
  fetchAppInstallationData.mockResolvedValue({
295
294
  data: {
296
295
  isInstalledWithScopeGroups: false,
297
296
  previouslyAuthorizedScopeGroups: ['old-scope'],
298
297
  },
299
298
  });
299
+ // Make it not automatically installable by making it not a test account
300
+ isDeveloperTestAccount.mockReturnValue(false);
300
301
  await appDevModeInterface.setup();
301
302
  expect(installAppBrowserPrompt).toHaveBeenCalledWith('http://static-install-url', true);
302
303
  });
@@ -308,7 +309,7 @@ describe('AppDevModeInterface', () => {
308
309
  });
309
310
  it('should exit if user declines auto-install', async () => {
310
311
  // Set up conditions for automatic installation
311
- getAccountConfig.mockReturnValue({
312
+ getConfigAccountById.mockReturnValue({
312
313
  parentAccountId: 12345, // matches targetProjectAccountId
313
314
  });
314
315
  isDeveloperTestAccount.mockReturnValue(true);
@@ -375,6 +376,15 @@ describe('AppDevModeInterface', () => {
375
376
  data: { uniquePortalInstallCount: 5 },
376
377
  });
377
378
  getStaticAuthAppInstallUrl.mockReturnValue('http://static-install-url');
379
+ getConfigAccountById.mockReturnValue({
380
+ parentAccountId: 12345,
381
+ });
382
+ isDeveloperTestAccount.mockReturnValue(true);
383
+ isSandbox.mockReturnValue(false);
384
+ installAppAutoPrompt.mockResolvedValue(true);
385
+ confirmPrompt.mockResolvedValue(true);
386
+ installStaticAuthAppOnTestAccount.mockResolvedValue(undefined);
387
+ isServerRunningAtUrl.mockResolvedValue(true);
378
388
  installAppBrowserPrompt.mockImplementation(async () => {
379
389
  const addListenerCall = mockLocalDevState.addListener.mock.calls.find(call => call[0] === 'devServerMessage');
380
390
  if (addListenerCall) {
@@ -387,8 +397,8 @@ describe('AppDevModeInterface', () => {
387
397
  mockLocalDevState.getAppDataByUid = vi.fn().mockReturnValue(mockAppData);
388
398
  mockLocalDevState.setAppDataForUid = vi.fn();
389
399
  mockLocalDevState.addListener = vi.fn();
390
- // Target account config is missing
391
- getAccountConfig.mockReturnValue(null);
400
+ // Target account config is missing - make it not a test account so isAutomaticallyInstallable returns false
401
+ isDeveloperTestAccount.mockReturnValue(false);
392
402
  // App is not installed
393
403
  fetchAppInstallationData.mockResolvedValue({
394
404
  data: {
@@ -401,6 +411,7 @@ describe('AppDevModeInterface', () => {
401
411
  localDevState: mockLocalDevState,
402
412
  localDevLogger: mockLocalDevLogger,
403
413
  });
414
+ // Remove the spy to see if that's causing the timeout
404
415
  await newAppDevModeInterface.setup();
405
416
  expect(installAppBrowserPrompt).toHaveBeenCalled();
406
417
  });
@@ -28,6 +28,7 @@ vi.mock('@hubspot/local-dev-lib/config', () => ({
28
28
  getAccountId: vi.fn().mockReturnValue(123),
29
29
  hasLocalStateFlag: vi.fn().mockReturnValue(false),
30
30
  getConfigDefaultAccount: vi.fn().mockReturnValue({ accountId: 123 }),
31
+ globalConfigFileExists: vi.fn().mockReturnValue(true),
31
32
  }));
32
33
  vi.mock('@hubspot/local-dev-lib/urls', () => ({
33
34
  getHubSpotApiOrigin: vi.fn().mockReturnValue('https://api.hubspot.com'),
@@ -26,6 +26,7 @@ vi.mock('../deploy');
26
26
  vi.mock('../config');
27
27
  vi.mock('@hubspot/local-dev-lib/api/projects');
28
28
  vi.mock('@hubspot/local-dev-lib/errors/index');
29
+ vi.mock('@hubspot/local-dev-lib/config');
29
30
  vi.mock('../localDev/LocalDevLogger');
30
31
  vi.mock('../localDev/DevServerManager');
31
32
  // Tests for LocalDevProcess and LocalDevState
@@ -2,8 +2,10 @@ import fs from 'fs';
2
2
  import { handleComponentCollision, updateHsMetaFilesWithAutoGeneratedFields, } from '../components.js';
3
3
  import { uiLogger } from '../../ui/logger.js';
4
4
  import { coerceToValidUid } from '@hubspot/project-parsing-lib';
5
+ import { fileExists } from '../../validation.js';
5
6
  vi.mock('fs');
6
7
  vi.mock('../../ui/logger.js');
8
+ vi.mock('../../validation.js');
7
9
  vi.mock('@hubspot/project-parsing-lib', () => ({
8
10
  coerceToValidUid: vi.fn(),
9
11
  metafileExtension: '.module.meta.json',
@@ -19,11 +21,15 @@ vi.mock('../../../lang/en.js', () => ({
19
21
  applicationLog: (type, uid, name) => `Updated ${type} component with uid: ${uid} and name: ${name}`,
20
22
  componentLog: (type, uid) => `Updated ${type} component with uid: ${uid}`,
21
23
  },
24
+ generateSafeFilenameDifferentiator: {
25
+ failedToCheckFiles: 'Failed to check files for filename differentiator. Falling back to timestamp.',
26
+ },
22
27
  },
23
28
  },
24
29
  }));
25
30
  const mockedFs = vi.mocked(fs);
26
31
  const mockCoerceToValidUid = vi.mocked(coerceToValidUid);
32
+ const mockedFileExists = vi.mocked(fileExists);
27
33
  describe('lib/projects/components', () => {
28
34
  describe('handleComponentCollision()', () => {
29
35
  const mockCollision = {
@@ -33,13 +39,13 @@ describe('lib/projects/components', () => {
33
39
  };
34
40
  beforeEach(() => {
35
41
  vi.resetAllMocks();
36
- // Mock Date.now to return consistent values for testing
37
- vi.spyOn(Date, 'now').mockReturnValue(1234567890);
42
+ // Default: fileExists returns false (file doesn't exist)
43
+ mockedFileExists.mockReturnValue(false);
38
44
  });
39
45
  afterEach(() => {
40
46
  vi.restoreAllMocks();
41
47
  });
42
- it('handles source file collisions by renaming them with timestamps', () => {
48
+ it('handles source file collisions by renaming them with sequential numbers', () => {
43
49
  const collision = {
44
50
  ...mockCollision,
45
51
  collisions: ['component.js', 'utils.ts'],
@@ -47,8 +53,8 @@ describe('lib/projects/components', () => {
47
53
  mockedFs.copyFileSync.mockImplementation(() => { });
48
54
  handleComponentCollision(collision);
49
55
  expect(mockedFs.copyFileSync).toHaveBeenCalledTimes(2);
50
- expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/component.js', '/dest/path/component-1234567890.js');
51
- expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/utils.ts', '/dest/path/utils-1234567890.ts');
56
+ expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/component.js', '/dest/path/component-2.js');
57
+ expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/utils.ts', '/dest/path/utils-2.ts');
52
58
  });
53
59
  it('handles metafile collisions by renaming and updating references', () => {
54
60
  const collision = {
@@ -69,8 +75,8 @@ describe('lib/projects/components', () => {
69
75
  });
70
76
  handleComponentCollision(collision);
71
77
  expect(mockedFs.readFileSync).toHaveBeenCalledWith('/src/path/component.module.meta.json', 'utf-8');
72
- expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/dest/path/component-1234567890.module.meta.json', expect.stringContaining('source-1234567890.js'));
73
- expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/source.js', '/dest/path/source-1234567890.js');
78
+ expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/dest/path/component-2.module.meta.json', expect.stringContaining('source-2.js'));
79
+ expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/source.js', '/dest/path/source-2.js');
74
80
  });
75
81
  it('handles package.json collisions by merging dependencies', () => {
76
82
  const collision = {
@@ -150,10 +156,10 @@ describe('lib/projects/components', () => {
150
156
  const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
151
157
  handleComponentCollision(collision);
152
158
  // Verify source files are copied with new names
153
- expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/component.js', '/dest/path/component-1234567890.js');
154
- expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/utils.ts', '/dest/path/utils-1234567890.ts');
159
+ expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/component.js', '/dest/path/component-2.js');
160
+ expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/utils.ts', '/dest/path/utils-2.ts');
155
161
  // Verify metafile is updated and written with new name
156
- expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/dest/path/component-1234567890.module.meta.json', expect.stringContaining('component-1234567890.js'));
162
+ expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/dest/path/component-2.module.meta.json', expect.stringContaining('component-2.js'));
157
163
  // Verify package.json is merged
158
164
  expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/dest/path/package.json', expect.stringContaining('"dependencies"'));
159
165
  consoleSpy.mockRestore();
@@ -198,6 +204,47 @@ describe('lib/projects/components', () => {
198
204
  expect(mockedFs.readFileSync).toHaveBeenCalledWith('/src/path/package.json', 'utf-8');
199
205
  consoleSpy.mockRestore();
200
206
  });
207
+ it('falls back to timestamp when maxAttempts is exhausted', () => {
208
+ const collision = {
209
+ ...mockCollision,
210
+ collisions: ['component.js'],
211
+ };
212
+ // Mock Date.now to return a consistent timestamp
213
+ const mockTimestamp = 1234567890;
214
+ vi.spyOn(Date, 'now').mockReturnValue(mockTimestamp);
215
+ // Mock fileExists to return true 10 times (exhausting maxAttempts)
216
+ // The function starts with differentiator = 1, then increments to 2, 3, etc.
217
+ // It will try 10 times (differentiators 2-11), and if all return true,
218
+ // maxAttempts will be 0 and it will fall back to timestamp
219
+ mockedFileExists.mockReturnValue(true);
220
+ mockedFs.copyFileSync.mockImplementation(() => { });
221
+ handleComponentCollision(collision);
222
+ // Should use timestamp as differentiator
223
+ expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/component.js', `/dest/path/component-${mockTimestamp}.js`);
224
+ vi.restoreAllMocks();
225
+ });
226
+ it('falls back to timestamp when fileExists throws an error', () => {
227
+ const collision = {
228
+ ...mockCollision,
229
+ collisions: ['component.js'],
230
+ };
231
+ // Mock Date.now to return a consistent timestamp
232
+ const mockTimestamp = 9876543210;
233
+ vi.spyOn(Date, 'now').mockReturnValue(mockTimestamp);
234
+ // Mock fileExists to throw an error
235
+ const mockError = new Error('File system error');
236
+ mockedFileExists.mockImplementation(() => {
237
+ throw mockError;
238
+ });
239
+ mockedFs.copyFileSync.mockImplementation(() => { });
240
+ const mockUiLogger = vi.mocked(uiLogger);
241
+ handleComponentCollision(collision);
242
+ // Should log debug message about the error
243
+ expect(mockUiLogger.debug).toHaveBeenCalledWith('Failed to check files for filename differentiator. Falling back to timestamp.');
244
+ // Should use timestamp as differentiator
245
+ expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/component.js', `/dest/path/component-${mockTimestamp}.js`);
246
+ vi.restoreAllMocks();
247
+ });
201
248
  });
202
249
  describe('updateHsMetaFilesWithAutoGeneratedFields()', () => {
203
250
  const mockUiLogger = vi.mocked(uiLogger);
@@ -233,20 +280,22 @@ describe('lib/projects/components', () => {
233
280
  .mockReturnValueOnce('card-my-project')
234
281
  .mockReturnValueOnce('function-my-project');
235
282
  updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths);
283
+ expect(mockCoerceToValidUid).toHaveBeenCalledWith('my-project_card');
284
+ expect(mockCoerceToValidUid).toHaveBeenCalledWith('my-project_function');
236
285
  expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/component1.meta.json', JSON.stringify({
237
286
  type: 'card',
238
- uid: 'card-my-project',
287
+ uid: 'card_my_project',
239
288
  config: {
240
289
  name: 'Old Name',
241
290
  },
242
291
  }, null, 2));
243
292
  expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/component2.meta.json', JSON.stringify({
244
293
  type: 'function',
245
- uid: 'function-my-project',
294
+ uid: 'function_my_project',
246
295
  }, null, 2));
247
296
  expect(mockUiLogger.log).toHaveBeenCalledWith('Updating component metadata files...');
248
- expect(mockUiLogger.log).toHaveBeenCalledWith('Updated card component with uid: card-my-project');
249
- expect(mockUiLogger.log).toHaveBeenCalledWith('Updated function component with uid: function-my-project');
297
+ expect(mockUiLogger.log).toHaveBeenCalledWith('Updated card component with uid: card_my_project');
298
+ expect(mockUiLogger.log).toHaveBeenCalledWith('Updated function component with uid: function_my_project');
250
299
  });
251
300
  it('handles app components by updating both uid and config.name', () => {
252
301
  const projectName = 'test-app';
@@ -263,32 +312,36 @@ describe('lib/projects/components', () => {
263
312
  mockedFs.writeFileSync.mockImplementation(() => { });
264
313
  mockCoerceToValidUid.mockReturnValue('app-test-app');
265
314
  updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths);
315
+ expect(mockCoerceToValidUid).toHaveBeenCalledWith('test-app_app');
266
316
  expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/app.meta.json', JSON.stringify({
267
317
  type: 'app',
268
- uid: 'app-test-app',
318
+ uid: 'app_test_app',
269
319
  config: {
270
320
  name: 'test-app-Application',
271
321
  other: 'property',
272
322
  },
273
323
  }, null, 2));
274
- expect(mockUiLogger.log).toHaveBeenCalledWith('Updated app component with uid: app-test-app and name: test-app-Application');
324
+ expect(mockUiLogger.log).toHaveBeenCalledWith('Updated app component with uid: app_test_app and name: test-app-Application');
275
325
  });
276
- it('handles UID collisions by using timestamps', () => {
326
+ it('handles UID collisions by using differentiators', () => {
277
327
  const projectName = 'collision-project';
278
328
  const hsMetaFilePaths = ['/path/to/component1.meta.json'];
279
- const existingUids = ['card-collision-project'];
329
+ const existingUids = ['card_collision_project'];
280
330
  const component1 = { type: 'card', uid: 'old-uid-1' };
281
331
  mockedFs.readFileSync.mockReturnValue(JSON.stringify(component1));
282
332
  mockedFs.writeFileSync.mockImplementation(() => { });
283
- // Mock Date.now to return consistent value for testing
284
- vi.spyOn(Date, 'now').mockReturnValue(1234567890);
333
+ // First call for getBaseUid() check, second call when adding differentiator
285
334
  mockCoerceToValidUid
286
335
  .mockReturnValueOnce('card-collision-project')
287
- .mockReturnValueOnce('card-1234567890-collision-project');
336
+ .mockReturnValueOnce('card-collision-project');
288
337
  updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths, existingUids);
338
+ expect(mockCoerceToValidUid).toHaveBeenCalledWith('collision-project_card');
339
+ // getBaseUid() is called twice - once for initial check, once when adding differentiator
340
+ expect(mockCoerceToValidUid).toHaveBeenCalledTimes(2);
341
+ // The differentiator is appended with a hyphen, so the final UID has a hyphen before the number
289
342
  expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/component1.meta.json', JSON.stringify({
290
343
  type: 'card',
291
- uid: 'card-1234567890-collision-project',
344
+ uid: 'card_collision_project_2',
292
345
  }, null, 2));
293
346
  });
294
347
  it('falls back to original uid when coerceToValidUid returns null', () => {
@@ -328,11 +381,82 @@ describe('lib/projects/components', () => {
328
381
  mockedFs.writeFileSync.mockImplementation(() => { });
329
382
  mockCoerceToValidUid.mockReturnValue('app-no-config-project');
330
383
  updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths);
384
+ expect(mockCoerceToValidUid).toHaveBeenCalledWith('no-config-project_app');
331
385
  expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/app.meta.json', JSON.stringify({
332
386
  type: 'app',
333
- uid: 'app-no-config-project',
387
+ uid: 'app_no_config_project',
388
+ }, null, 2));
389
+ expect(mockUiLogger.log).toHaveBeenCalledWith('Updated app component with uid: app_no_config_project');
390
+ });
391
+ it('replaces hyphens with underscores in coerced UIDs', () => {
392
+ const projectName = 'my-project';
393
+ const hsMetaFilePaths = ['/path/to/component.meta.json'];
394
+ const component = {
395
+ type: 'card',
396
+ uid: 'old-uid',
397
+ };
398
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(component));
399
+ mockedFs.writeFileSync.mockImplementation(() => { });
400
+ // coerceToValidUid returns a value with hyphens that should be converted to underscores
401
+ mockCoerceToValidUid.mockReturnValue('my-project-card-with-hyphens');
402
+ updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths);
403
+ expect(mockCoerceToValidUid).toHaveBeenCalledWith('my-project_card');
404
+ expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/component.meta.json', JSON.stringify({
405
+ type: 'card',
406
+ uid: 'my_project_card_with_hyphens',
407
+ }, null, 2));
408
+ });
409
+ it('handles UIDs with multiple hyphens correctly', () => {
410
+ const projectName = 'test-project';
411
+ const hsMetaFilePaths = ['/path/to/component.meta.json'];
412
+ const component = {
413
+ type: 'custom-object',
414
+ uid: 'old-uid',
415
+ };
416
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(component));
417
+ mockedFs.writeFileSync.mockImplementation(() => { });
418
+ mockCoerceToValidUid.mockReturnValue('test-project-custom-object-type');
419
+ updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths);
420
+ expect(mockCoerceToValidUid).toHaveBeenCalledWith('test-project_custom-object');
421
+ expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/component.meta.json', JSON.stringify({
422
+ type: 'custom-object',
423
+ uid: 'test_project_custom_object_type',
424
+ }, null, 2));
425
+ });
426
+ it('preserves UIDs without hyphens unchanged', () => {
427
+ const projectName = 'simpleproject';
428
+ const hsMetaFilePaths = ['/path/to/component.meta.json'];
429
+ const component = {
430
+ type: 'card',
431
+ uid: 'old-uid',
432
+ };
433
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(component));
434
+ mockedFs.writeFileSync.mockImplementation(() => { });
435
+ // coerceToValidUid returns a value without hyphens
436
+ mockCoerceToValidUid.mockReturnValue('simpleprojectcard');
437
+ updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths);
438
+ expect(mockCoerceToValidUid).toHaveBeenCalledWith('simpleproject_card');
439
+ expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/component.meta.json', JSON.stringify({
440
+ type: 'card',
441
+ uid: 'simpleprojectcard',
442
+ }, null, 2));
443
+ });
444
+ it('handles project names with hyphens in UID generation', () => {
445
+ const projectName = 'my-super-project';
446
+ const hsMetaFilePaths = ['/path/to/component.meta.json'];
447
+ const component = {
448
+ type: 'function',
449
+ uid: 'old-uid',
450
+ };
451
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(component));
452
+ mockedFs.writeFileSync.mockImplementation(() => { });
453
+ mockCoerceToValidUid.mockReturnValue('my-super-project-function');
454
+ updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths);
455
+ expect(mockCoerceToValidUid).toHaveBeenCalledWith('my-super-project_function');
456
+ expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/component.meta.json', JSON.stringify({
457
+ type: 'function',
458
+ uid: 'my_super_project_function',
334
459
  }, null, 2));
335
- expect(mockUiLogger.log).toHaveBeenCalledWith('Updated app component with uid: app-no-config-project');
336
460
  });
337
461
  });
338
462
  });
@@ -8,6 +8,7 @@ import { pollDeployStatus } from '../pollProjectBuildAndDeploy.js';
8
8
  // Mock external dependencies
9
9
  vi.mock('../../ui/logger.js');
10
10
  vi.mock('@hubspot/local-dev-lib/api/projects');
11
+ vi.mock('@hubspot/local-dev-lib/config');
11
12
  vi.mock('../pollProjectBuildAndDeploy.js');
12
13
  const mockUiLogger = vi.mocked(uiLogger);
13
14
  const mockDeployProject = vi.mocked(deployProject);
@@ -8,10 +8,14 @@ describe('platformVersion', () => {
8
8
  expect(isV2Project('2025.2')).toBe(true);
9
9
  });
10
10
  it('returns true if platform version is greater than the minimum', () => {
11
- expect(isV2Project('2026.2')).toBe(true);
11
+ expect(isV2Project('2025.3')).toBe(true);
12
+ expect(isV2Project('2026.03')).toBe(true);
13
+ expect(isV2Project('2026.03-beta')).toBe(true);
12
14
  });
13
15
  it('returns false if platform version is less than the minimum', () => {
14
16
  expect(isV2Project('2025.0')).toBe(false);
17
+ expect(isV2Project('2025.01')).toBe(false);
18
+ expect(isV2Project('2025.01-beta')).toBe(false);
15
19
  });
16
20
  it('returns false if platform version is invalid', () => {
17
21
  expect(isV2Project(null)).toBe(false);