@hubspot/cli 7.7.27-experimental.1 → 7.7.28-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 (131) hide show
  1. package/README.md +0 -4
  2. package/api/__tests__/migrate.test.js +5 -5
  3. package/api/migrate.d.ts +10 -4
  4. package/api/migrate.js +2 -2
  5. package/commands/__tests__/create.test.js +20 -0
  6. package/commands/__tests__/testAccount.test.js +2 -0
  7. package/commands/app/__tests__/migrate.test.js +1 -0
  8. package/commands/create/function.js +2 -2
  9. package/commands/create/module.js +2 -2
  10. package/commands/create/template.js +2 -2
  11. package/commands/create.js +47 -0
  12. package/commands/getStarted.js +66 -4
  13. package/commands/mcp/setup.d.ts +0 -1
  14. package/commands/mcp/setup.js +3 -11
  15. package/commands/project/__tests__/create.test.js +57 -0
  16. package/commands/project/__tests__/devUnifiedFlow.test.js +18 -30
  17. package/commands/project/create.js +6 -1
  18. package/commands/project/deploy.js +31 -1
  19. package/commands/project/dev/deprecatedFlow.js +2 -1
  20. package/commands/project/dev/index.js +32 -12
  21. package/commands/project/dev/unifiedFlow.d.ts +1 -1
  22. package/commands/project/dev/unifiedFlow.js +10 -16
  23. package/commands/project/profile/delete.js +26 -14
  24. package/commands/project/upload.d.ts +2 -2
  25. package/commands/project/upload.js +1 -1
  26. package/commands/testAccount/__tests__/importData.test.d.ts +1 -0
  27. package/commands/testAccount/__tests__/importData.test.js +93 -0
  28. package/commands/testAccount/create.js +23 -13
  29. package/commands/testAccount/importData.d.ts +9 -0
  30. package/commands/testAccount/importData.js +61 -0
  31. package/commands/testAccount.js +2 -0
  32. package/lang/en.d.ts +160 -46
  33. package/lang/en.js +175 -59
  34. package/lang/en.lyaml +35 -14
  35. package/lib/__tests__/importData.test.d.ts +1 -0
  36. package/lib/__tests__/importData.test.js +89 -0
  37. package/lib/accountTypes.js +2 -3
  38. package/lib/app/__tests__/migrate.test.js +81 -36
  39. package/lib/app/migrate.d.ts +17 -4
  40. package/lib/app/migrate.js +97 -19
  41. package/lib/constants.d.ts +1 -0
  42. package/lib/constants.js +1 -0
  43. package/lib/hasFeature.d.ts +1 -0
  44. package/lib/hasFeature.js +7 -0
  45. package/lib/importData.d.ts +3 -0
  46. package/lib/importData.js +50 -0
  47. package/lib/mcp/setup.d.ts +0 -2
  48. package/lib/mcp/setup.js +0 -24
  49. package/lib/process.js +15 -4
  50. package/lib/projectProfiles.d.ts +1 -1
  51. package/lib/projectProfiles.js +10 -2
  52. package/lib/projects/__tests__/AppDevModeInterface.test.js +3 -3
  53. package/lib/projects/__tests__/LocalDevProcess.test.js +5 -95
  54. package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +6 -6
  55. package/lib/projects/__tests__/components.test.js +164 -7
  56. package/lib/projects/__tests__/localDevProjectHelpers.test.d.ts +1 -0
  57. package/lib/projects/__tests__/localDevProjectHelpers.test.js +118 -0
  58. package/lib/projects/add/v3AddComponent.js +16 -4
  59. package/lib/projects/components.d.ts +1 -0
  60. package/lib/projects/components.js +27 -1
  61. package/lib/projects/localDev/AppDevModeInterface.js +35 -3
  62. package/lib/projects/localDev/LocalDevLogger.d.ts +0 -4
  63. package/lib/projects/localDev/LocalDevLogger.js +2 -19
  64. package/lib/projects/localDev/LocalDevManager.js +1 -1
  65. package/lib/projects/localDev/LocalDevProcess.d.ts +1 -2
  66. package/lib/projects/localDev/LocalDevProcess.js +3 -26
  67. package/lib/projects/localDev/LocalDevState.d.ts +6 -7
  68. package/lib/projects/localDev/LocalDevState.js +16 -15
  69. package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +1 -0
  70. package/lib/projects/localDev/LocalDevWebsocketServer.js +17 -2
  71. package/lib/projects/localDev/{helpers.d.ts → helpers/account.d.ts} +1 -7
  72. package/lib/projects/localDev/{helpers.js → helpers/account.js} +44 -144
  73. package/lib/projects/localDev/helpers/project.d.ts +12 -0
  74. package/lib/projects/localDev/helpers/project.js +173 -0
  75. package/lib/projects/urls.d.ts +1 -0
  76. package/lib/projects/urls.js +4 -0
  77. package/lib/prompts/__tests__/createFunctionPrompt.test.d.ts +1 -0
  78. package/lib/prompts/__tests__/createFunctionPrompt.test.js +129 -0
  79. package/lib/prompts/__tests__/createModulePrompt.test.d.ts +1 -0
  80. package/lib/prompts/__tests__/createModulePrompt.test.js +187 -0
  81. package/lib/prompts/__tests__/createTemplatePrompt.test.d.ts +1 -0
  82. package/lib/prompts/__tests__/createTemplatePrompt.test.js +102 -0
  83. package/lib/prompts/confirmImportDataPrompt.d.ts +1 -0
  84. package/lib/prompts/confirmImportDataPrompt.js +12 -0
  85. package/lib/prompts/createFunctionPrompt.d.ts +2 -1
  86. package/lib/prompts/createFunctionPrompt.js +36 -7
  87. package/lib/prompts/createModulePrompt.d.ts +2 -1
  88. package/lib/prompts/createModulePrompt.js +48 -1
  89. package/lib/prompts/createTemplatePrompt.d.ts +3 -24
  90. package/lib/prompts/createTemplatePrompt.js +9 -1
  91. package/lib/prompts/importDataFilePathPrompt.d.ts +1 -0
  92. package/lib/prompts/importDataFilePathPrompt.js +24 -0
  93. package/lib/prompts/importDataTestAccountSelectPrompt.d.ts +3 -0
  94. package/lib/prompts/importDataTestAccountSelectPrompt.js +29 -0
  95. package/lib/prompts/projectDevTargetAccountPrompt.js +1 -0
  96. package/lib/prompts/promptUtils.d.ts +7 -1
  97. package/lib/prompts/promptUtils.js +14 -1
  98. package/lib/ui/__tests__/removeAnsiCodes.test.d.ts +1 -0
  99. package/lib/ui/__tests__/removeAnsiCodes.test.js +84 -0
  100. package/lib/ui/index.js +3 -6
  101. package/lib/ui/removeAnsiCodes.d.ts +1 -0
  102. package/lib/ui/removeAnsiCodes.js +4 -0
  103. package/mcp-server/server.js +2 -1
  104. package/mcp-server/tools/cms/HsListTool.d.ts +23 -0
  105. package/mcp-server/tools/cms/HsListTool.js +58 -0
  106. package/mcp-server/tools/cms/__tests__/HsListTool.test.d.ts +1 -0
  107. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +120 -0
  108. package/mcp-server/tools/index.d.ts +1 -0
  109. package/mcp-server/tools/index.js +8 -0
  110. package/mcp-server/tools/project/DocFetchTool.d.ts +17 -0
  111. package/mcp-server/tools/project/DocFetchTool.js +49 -0
  112. package/mcp-server/tools/project/DocsSearchTool.d.ts +26 -0
  113. package/mcp-server/tools/project/DocsSearchTool.js +62 -0
  114. package/mcp-server/tools/project/GetConfigValuesTool.js +3 -2
  115. package/mcp-server/tools/project/__tests__/DocFetchTool.test.d.ts +1 -0
  116. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +117 -0
  117. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.d.ts +1 -0
  118. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +190 -0
  119. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +1 -1
  120. package/mcp-server/tools/project/constants.d.ts +2 -0
  121. package/mcp-server/tools/project/constants.js +6 -0
  122. package/mcp-server/utils/toolUsageTracking.d.ts +3 -1
  123. package/mcp-server/utils/toolUsageTracking.js +2 -1
  124. package/package.json +9 -6
  125. package/types/Cms.d.ts +16 -0
  126. package/types/Cms.js +25 -1
  127. package/types/LocalDev.d.ts +0 -3
  128. package/types/Prompts.d.ts +1 -0
  129. package/types/Yargs.d.ts +1 -1
  130. package/ui/index.d.ts +1 -0
  131. package/ui/index.js +6 -0
@@ -16,6 +16,8 @@ import { PLATFORM_VERSIONS } from '@hubspot/local-dev-lib/constants/projects';
16
16
  import { commands } from '../../lang/en.js';
17
17
  import { uiLogger } from '../../lib/ui/logger.js';
18
18
  import { handleProjectCreationFlow, } from '../../lib/projects/create/index.js';
19
+ import { getProjectMetadata, } from '@hubspot/project-parsing-lib/src/lib/project.js';
20
+ import { updateHsMetaFilesWithAutoGeneratedFields } from '../../lib/projects/components.js';
19
21
  const command = ['create', 'init'];
20
22
  const describe = uiBetaTag(commands.project.create.describe, false);
21
23
  const { v2023_2, v2025_1, v2025_2 } = PLATFORM_VERSIONS;
@@ -73,10 +75,13 @@ async function handler(args) {
73
75
  }
74
76
  const projectConfigPath = path.join(projectDest, PROJECT_CONFIG_FILE);
75
77
  const parsedConfigFile = JSON.parse(fs.readFileSync(projectConfigPath).toString());
78
+ const projectName = projectNameAndDestPromptResponse.name;
76
79
  writeProjectConfig(projectConfigPath, {
77
80
  ...parsedConfigFile,
78
- name: projectNameAndDestPromptResponse.name,
81
+ name: projectName,
79
82
  });
83
+ const projectMetadata = await getProjectMetadata(path.join(projectDest, parsedConfigFile.srcDir));
84
+ updateHsMetaFilesWithAutoGeneratedFields(projectName, projectMetadata.hsMetaFiles);
80
85
  // If the template is 'no-template', we need to manually create a src directory
81
86
  if (selectProjectTemplatePromptResponse.projectTemplate?.name ===
82
87
  EMPTY_PROJECT_TEMPLATE_NAME ||
@@ -29,6 +29,7 @@ function validateBuildId(buildId, deployedBuildId, latestBuildId, projectName, a
29
29
  function logDeployErrors(errorData) {
30
30
  uiLogger.error(errorData.message);
31
31
  errorData.errors.forEach(err => {
32
+ // This is how the pre-deploy check manifests itself in < 2025.2 projects
32
33
  if (err.subCategory === PROJECT_ERROR_TYPES.DEPLOY_CONTAINS_REMOVALS) {
33
34
  uiLogger.log(commands.project.deploy.errors.deployContainsRemovals(err.context.COMPONENT_NAME));
34
35
  }
@@ -37,6 +38,29 @@ function logDeployErrors(errorData) {
37
38
  }
38
39
  });
39
40
  }
41
+ function handleBlockedDeploy(deployResp) {
42
+ const deployCanBeForced = deployResp.issues.every(issue => issue.blockingMessages.every(message => message.isWarning));
43
+ uiLogger.log('');
44
+ if (deployCanBeForced) {
45
+ uiLogger.warn(commands.project.deploy.errors.deployWarningsHeader);
46
+ uiLogger.log('');
47
+ }
48
+ else {
49
+ uiLogger.error(commands.project.deploy.errors.deployBlockedHeader);
50
+ uiLogger.log('');
51
+ }
52
+ deployResp.issues.forEach(issue => {
53
+ if (issue.blockingMessages.length > 0) {
54
+ issue.blockingMessages.forEach(message => {
55
+ uiLogger.log(commands.project.deploy.errors.deployIssueComponentWarning(issue.uid, issue.componentTypeName, message.message));
56
+ });
57
+ }
58
+ else {
59
+ uiLogger.log(commands.project.deploy.errors.deployIssueComponentGeneric(issue.uid, issue.componentTypeName));
60
+ }
61
+ uiLogger.log('');
62
+ });
63
+ }
40
64
  async function handler(args) {
41
65
  const { derivedAccountId, project: projectOption, buildId: buildIdOption, force: forceOption, deployLatestBuild: deployLatestBuildOption, json: formatOutputAsJson, } = args;
42
66
  const accountConfig = getAccountConfig(derivedAccountId);
@@ -109,7 +133,13 @@ async function handler(args) {
109
133
  }
110
134
  const { data: deployResp } = await deployProject(targetAccountId, projectName, buildIdToDeploy, useV3Api(projectConfig?.platformVersion), forceOption);
111
135
  if (!deployResp || deployResp.buildResultType !== 'DEPLOY_QUEUED') {
112
- uiLogger.error(commands.project.deploy.errors.deploy);
136
+ if (deployResp?.buildResultType === 'DEPLOY_BLOCKED') {
137
+ handleBlockedDeploy(deployResp);
138
+ process.exit(EXIT_CODES.ERROR);
139
+ }
140
+ else {
141
+ uiLogger.error(commands.project.deploy.errors.deploy);
142
+ }
113
143
  return process.exit(EXIT_CODES.ERROR);
114
144
  }
115
145
  else if (formatOutputAsJson) {
@@ -8,7 +8,8 @@ import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
8
8
  import { uiCommandReference } from '../../../lib/ui/index.js';
9
9
  import SpinniesManager from '../../../lib/ui/SpinniesManager.js';
10
10
  import LocalDevManager from '../../../lib/projects/localDev/LocalDevManager.js';
11
- import { confirmDefaultAccountIsTarget, suggestRecommendedNestedAccount, checkIfAccountFlagIsSupported, checkIfDefaultAccountIsSupported, createSandboxForLocalDev, createDeveloperTestAccountForLocalDev, createNewProjectForLocalDev, createInitialBuildForNewProject, useExistingDevTestAccount, checkIfParentAccountIsAuthed, hasSandboxes, } from '../../../lib/projects/localDev/helpers.js';
11
+ import { confirmDefaultAccountIsTarget, suggestRecommendedNestedAccount, checkIfAccountFlagIsSupported, checkIfDefaultAccountIsSupported, createSandboxForLocalDev, createDeveloperTestAccountForLocalDev, useExistingDevTestAccount, checkIfParentAccountIsAuthed, hasSandboxes, } from '../../../lib/projects/localDev/helpers/account.js';
12
+ import { createInitialBuildForNewProject, createNewProjectForLocalDev, } from '../../../lib/projects/localDev/helpers/project.js';
12
13
  import { handleExit } from '../../../lib/process.js';
13
14
  import { isSandbox, isDeveloperTestAccount, } from '../../../lib/accountTypes.js';
14
15
  import { ensureProjectExists } from '../../../lib/projects/ensureProjectExists.js';
@@ -7,7 +7,7 @@ import { deprecatedProjectDevFlow } from './deprecatedFlow.js';
7
7
  import { unifiedProjectDevFlow } from './unifiedFlow.js';
8
8
  import { useV3Api } from '../../../lib/projects/buildAndDeploy.js';
9
9
  import { makeYargsBuilder } from '../../../lib/yargsUtils.js';
10
- import { loadProfile, logProfileFooter, logProfileHeader, exitIfUsingProfiles, } from '../../../lib/projectProfiles.js';
10
+ import { loadProfile, exitIfUsingProfiles, } from '../../../lib/projectProfiles.js';
11
11
  import { commands } from '../../../lang/en.js';
12
12
  import { uiLogger } from '../../../lib/ui/logger.js';
13
13
  const command = 'dev';
@@ -33,19 +33,37 @@ async function handler(args) {
33
33
  process.exit(EXIT_CODES.ERROR);
34
34
  }
35
35
  validateAccountFlags(testingAccount, projectAccount, userProvidedAccount, useV3);
36
- let targetProjectAccountId = (projectAccount && getAccountId(projectAccount)) ||
37
- (userProvidedAccount && derivedAccountId);
36
+ uiBetaTag(commands.project.dev.logs.betaMessage);
37
+ if (useV3) {
38
+ uiLogger.log(commands.project.dev.logs.learnMoreMessageV3);
39
+ }
40
+ else {
41
+ uiLogger.log(commands.project.dev.logs.learnMoreMessageLegacy);
42
+ }
43
+ let targetProjectAccountId;
38
44
  let profile;
45
+ // Using the new --projectAccount flag
46
+ if (projectAccount) {
47
+ targetProjectAccountId = getAccountId(projectAccount);
48
+ if (targetProjectAccountId) {
49
+ uiLogger.log('');
50
+ uiLogger.log(commands.project.dev.logs.projectAccountFlagExplanation(targetProjectAccountId));
51
+ }
52
+ // Using the legacy --account flag
53
+ }
54
+ else if (userProvidedAccount && derivedAccountId) {
55
+ targetProjectAccountId = derivedAccountId;
56
+ }
39
57
  if (!targetProjectAccountId && useV3Api(projectConfig.platformVersion)) {
40
58
  if (args.profile) {
41
- logProfileHeader(args.profile);
42
59
  profile = loadProfile(projectConfig, projectDir, args.profile);
43
60
  if (!profile) {
44
61
  uiLine();
45
62
  process.exit(EXIT_CODES.ERROR);
46
63
  }
47
64
  targetProjectAccountId = profile.accountId;
48
- logProfileFooter(profile);
65
+ uiLogger.log('');
66
+ uiLogger.log(commands.project.dev.logs.profileProjectAccountExplanation(targetProjectAccountId, args.profile));
49
67
  }
50
68
  else {
51
69
  // A profile must be specified if this project has profiles configured
@@ -55,10 +73,12 @@ async function handler(args) {
55
73
  if (!targetProjectAccountId) {
56
74
  // The user is not using profile or account flags, so we can use the derived accountId
57
75
  targetProjectAccountId = derivedAccountId;
76
+ if (useV3) {
77
+ uiLogger.log('');
78
+ uiLogger.log(commands.project.dev.logs.defaultProjectAccountExplanation(targetProjectAccountId));
79
+ }
58
80
  }
59
81
  trackCommandUsage('project-dev', {}, targetProjectAccountId);
60
- uiBetaTag(commands.project.dev.logs.betaMessage);
61
- uiLogger.log(commands.project.dev.logs.learnMoreLocalDevServer);
62
82
  if (useV3Api(projectConfig.platformVersion)) {
63
83
  const targetTestingAccountId = (testingAccount && getAccountId(testingAccount)) || undefined;
64
84
  await unifiedProjectDevFlow({
@@ -86,13 +106,13 @@ function projectDevBuilder(yargs) {
86
106
  description: commands.project.dev.options.profile,
87
107
  hidden: true,
88
108
  });
89
- yargs.options('testingAccount', {
109
+ yargs.options('testing-account', {
90
110
  type: 'string',
91
111
  description: commands.project.dev.options.testingAccount,
92
112
  hidden: true,
93
- implies: ['projectAccount'],
113
+ implies: ['project-account'],
94
114
  });
95
- yargs.options('projectAccount', {
115
+ yargs.options('project-account', {
96
116
  type: 'string',
97
117
  description: commands.project.dev.options.projectAccount,
98
118
  hidden: true,
@@ -100,8 +120,8 @@ function projectDevBuilder(yargs) {
100
120
  });
101
121
  yargs.example([['$0 project dev', commands.project.dev.examples.default]]);
102
122
  yargs.conflicts('profile', 'account');
103
- yargs.conflicts('profile', 'testingAccount');
104
- yargs.conflicts('profile', 'projectAccount');
123
+ yargs.conflicts('profile', 'testing-account');
124
+ yargs.conflicts('profile', 'project-account');
105
125
  return yargs;
106
126
  }
107
127
  export const builder = makeYargsBuilder(projectDevBuilder, command, describe, {
@@ -10,5 +10,5 @@ type UnifiedProjectDevFlowArgs = {
10
10
  projectDir: string;
11
11
  profileConfig?: HsProfileFile;
12
12
  };
13
- export declare function unifiedProjectDevFlow({ args, targetProjectAccountId, providedTargetTestingAccountId, projectConfig, projectDir, profileConfig, }: UnifiedProjectDevFlowArgs): Promise<void>;
13
+ export declare function unifiedProjectDevFlow({ args, targetProjectAccountId, providedTargetTestingAccountId, projectConfig, projectDir, }: UnifiedProjectDevFlowArgs): Promise<void>;
14
14
  export {};
@@ -8,18 +8,18 @@ import { getValidEnv } from '@hubspot/local-dev-lib/environment';
8
8
  import { logError } from '../../../lib/errorHandlers/index.js';
9
9
  import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
10
10
  import { ensureProjectExists } from '../../../lib/projects/ensureProjectExists.js';
11
- import { createInitialBuildForNewProject, createNewProjectForLocalDev, useExistingDevTestAccount, createDeveloperTestAccountForLocalDev, selectAccountTypePrompt, } from '../../../lib/projects/localDev/helpers.js';
11
+ import { createInitialBuildForNewProject, createNewProjectForLocalDev, compareLocalProjectToDeployed, } from '../../../lib/projects/localDev/helpers/project.js';
12
+ import { useExistingDevTestAccount, createDeveloperTestAccountForLocalDev, selectAccountTypePrompt, } from '../../../lib/projects/localDev/helpers/account.js';
12
13
  import { selectDeveloperTestTargetAccountPrompt, selectSandboxTargetAccountPrompt, } from '../../../lib/prompts/projectDevTargetAccountPrompt.js';
13
14
  import SpinniesManager from '../../../lib/ui/SpinniesManager.js';
14
15
  import LocalDevProcess from '../../../lib/projects/localDev/LocalDevProcess.js';
15
16
  import LocalDevWatcher from '../../../lib/projects/localDev/LocalDevWatcher.js';
16
17
  import { handleExit, handleKeypress } from '../../../lib/process.js';
17
18
  import { isTestAccountOrSandbox, isUnifiedAccount, } from '../../../lib/accountTypes.js';
18
- import { uiLine } from '../../../lib/ui/index.js';
19
19
  import { uiLogger } from '../../../lib/ui/logger.js';
20
20
  import { commands } from '../../../lang/en.js';
21
21
  import LocalDevWebsocketServer from '../../../lib/projects/localDev/LocalDevWebsocketServer.js';
22
- export async function unifiedProjectDevFlow({ args, targetProjectAccountId, providedTargetTestingAccountId, projectConfig, projectDir, profileConfig, }) {
22
+ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, providedTargetTestingAccountId, projectConfig, projectDir, }) {
23
23
  const env = getValidEnv(getEnv(targetProjectAccountId));
24
24
  let projectNodes;
25
25
  // Get IR
@@ -53,7 +53,7 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
53
53
  const accounts = getConfigAccounts();
54
54
  const accountIsCombined = await isUnifiedAccount(targetProjectAccountConfig);
55
55
  const targetProjectAccountIsTestAccountOrSandbox = isTestAccountOrSandbox(targetProjectAccountConfig);
56
- if (!accountIsCombined && !profileConfig) {
56
+ if (!accountIsCombined) {
57
57
  uiLogger.error(commands.project.dev.errors.accountNotCombined);
58
58
  process.exit(EXIT_CODES.ERROR);
59
59
  }
@@ -68,13 +68,9 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
68
68
  !targetTestingAccountId &&
69
69
  targetProjectAccountIsTestAccountOrSandbox) {
70
70
  targetTestingAccountId = targetProjectAccountId;
71
+ uiLogger.log(commands.project.dev.logs.defaultSandboxOrDevTestTestingAccountExplanation(targetProjectAccountId));
71
72
  }
72
73
  else if (!targetTestingAccountId) {
73
- uiLogger.log('');
74
- uiLine();
75
- uiLogger.log(commands.project.dev.logs.accountTypeInformation);
76
- uiLogger.log(commands.project.dev.logs.learnMoreMessage);
77
- uiLine();
78
74
  uiLogger.log('');
79
75
  const accountType = await selectAccountTypePrompt(targetProjectAccountConfig);
80
76
  if (accountType === HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST) {
@@ -100,29 +96,27 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
100
96
  targetTestingAccountId = targetProjectAccountId;
101
97
  }
102
98
  }
99
+ else {
100
+ uiLogger.log(commands.project.dev.logs.testingAccountFlagExplanation(targetTestingAccountId));
101
+ }
103
102
  // Check if project exists in HubSpot
104
103
  const { projectExists, project: uploadedProject } = await ensureProjectExists(targetProjectAccountId, projectConfig.name, {
105
104
  allowCreate: false,
106
105
  noLogs: true,
107
106
  });
108
- let deployedBuild;
109
- let isGithubLinked = false;
110
107
  let project = uploadedProject;
111
108
  SpinniesManager.init();
112
109
  if (projectExists && project) {
113
- deployedBuild = project.deployedBuild;
114
- isGithubLinked = Boolean(project.sourceIntegration && project.sourceIntegration.source === 'GITHUB');
110
+ await compareLocalProjectToDeployed(projectConfig, targetProjectAccountId, project.deployedBuild?.buildId, projectNodes);
115
111
  }
116
112
  else {
117
113
  project = await createNewProjectForLocalDev(projectConfig, targetProjectAccountId, false, false);
118
- deployedBuild = await createInitialBuildForNewProject(projectConfig, projectDir, targetProjectAccountId, true, args.profile);
114
+ await createInitialBuildForNewProject(projectConfig, projectDir, targetProjectAccountId, true, args.profile);
119
115
  }
120
116
  // End setup, start local dev process
121
117
  const localDevProcess = new LocalDevProcess({
122
118
  initialProjectNodes: projectNodes,
123
119
  debug: args.debug,
124
- deployedBuild,
125
- isGithubLinked,
126
120
  profile: args.profile,
127
121
  targetProjectAccountId,
128
122
  targetTestingAccountId: targetTestingAccountId,
@@ -1,8 +1,8 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { getAllHsProfiles, getHsProfileFilename, loadHsProfileFile, } from '@hubspot/project-parsing-lib';
4
- import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
5
- import { deleteProject } from '@hubspot/local-dev-lib/api/projects';
4
+ import { fetchProject, deleteProject, } from '@hubspot/local-dev-lib/api/projects';
5
+ import { getAccountConfig } from '@hubspot/local-dev-lib/config';
6
6
  import { trackCommandUsage } from '../../../lib/usageTracking.js';
7
7
  import { getProjectConfig } from '../../../lib/projects/config.js';
8
8
  import { uiBetaTag } from '../../../lib/ui/index.js';
@@ -13,6 +13,7 @@ import { commands } from '../../../lang/en.js';
13
13
  import { confirmPrompt, listPrompt } from '../../../lib/prompts/promptUtils.js';
14
14
  import { fileExists } from '../../../lib/validation.js';
15
15
  import { debugError } from '../../../lib/errorHandlers/index.js';
16
+ import { isDeveloperTestAccount, isSandbox, } from '../../../lib/accountTypes.js';
16
17
  const command = 'delete [name]';
17
18
  const describe = uiBetaTag(commands.project.profile.delete.describe, false);
18
19
  async function handler(args) {
@@ -62,6 +63,15 @@ async function handler(args) {
62
63
  catch (err) {
63
64
  uiLogger.debug(commands.project.profile.delete.debug.failedToLoadProfile(profileName));
64
65
  }
66
+ const profileFilename = getHsProfileFilename(profileName);
67
+ try {
68
+ fs.unlinkSync(path.join(projectSourceDir, profileFilename));
69
+ }
70
+ catch (err) {
71
+ uiLogger.error(commands.project.profile.delete.errors.failedToDeleteProfile(profileFilename));
72
+ process.exit(EXIT_CODES.ERROR);
73
+ }
74
+ uiLogger.log(commands.project.profile.delete.logs.profileDeleted(profileFilename));
65
75
  if (targetAccountId) {
66
76
  let projectExists = false;
67
77
  try {
@@ -71,29 +81,31 @@ async function handler(args) {
71
81
  catch (err) {
72
82
  debugError(err);
73
83
  }
74
- if (projectExists) {
84
+ const targetAccountConfig = getAccountConfig(targetAccountId);
85
+ if (projectExists &&
86
+ targetAccountConfig &&
87
+ (isDeveloperTestAccount(targetAccountConfig) ||
88
+ isSandbox(targetAccountConfig))) {
89
+ uiLogger.log('');
75
90
  const confirmResponse = await confirmPrompt(commands.project.profile.delete.prompts.deleteProjectPrompt(targetAccountId), {
76
91
  defaultAnswer: false,
77
92
  });
78
93
  if (confirmResponse) {
79
- await deleteProject(targetAccountId, projectConfig.name);
94
+ try {
95
+ await deleteProject(targetAccountId, projectConfig.name);
96
+ }
97
+ catch (err) {
98
+ debugError(err);
99
+ uiLogger.error(commands.project.profile.delete.errors.failedToDeleteProject(targetAccountId));
100
+ process.exit(EXIT_CODES.ERROR);
101
+ }
80
102
  uiLogger.log(commands.project.profile.delete.logs.deletedProject(targetAccountId));
81
103
  }
82
104
  else {
83
105
  uiLogger.log(commands.project.profile.delete.logs.didNotDeleteProject(targetAccountId));
84
106
  }
85
- uiLogger.log('');
86
107
  }
87
108
  }
88
- const profileFilename = getHsProfileFilename(profileName);
89
- try {
90
- fs.unlinkSync(path.join(projectSourceDir, profileFilename));
91
- }
92
- catch (err) {
93
- uiLogger.error(commands.project.profile.delete.errors.failedToDeleteProfile(profileFilename));
94
- process.exit(EXIT_CODES.ERROR);
95
- }
96
- uiLogger.log(commands.project.profile.delete.logs.profileDeleted(profileFilename));
97
109
  process.exit(EXIT_CODES.SUCCESS);
98
110
  }
99
111
  function projectProfileDeleteBuilder(yargs) {
@@ -1,5 +1,5 @@
1
- import { CommonArgs, JSONOutputArgs, YargsCommandModule } from '../../types/Yargs.js';
2
- type ProjectUploadArgs = CommonArgs & JSONOutputArgs & {
1
+ import { CommonArgs, EnvironmentArgs, JSONOutputArgs, YargsCommandModule } from '../../types/Yargs.js';
2
+ type ProjectUploadArgs = CommonArgs & JSONOutputArgs & EnvironmentArgs & {
3
3
  forceCreate: boolean;
4
4
  message: string;
5
5
  m: string;
@@ -25,7 +25,7 @@ async function handler(args) {
25
25
  validateProjectConfig(projectConfig, projectDir);
26
26
  let targetAccountId;
27
27
  if (useV3Api(projectConfig.platformVersion)) {
28
- targetAccountId = await loadAndValidateProfile(projectConfig, projectDir, profile);
28
+ targetAccountId = await loadAndValidateProfile(projectConfig, projectDir, profile, args.useEnv);
29
29
  }
30
30
  targetAccountId = targetAccountId || derivedAccountId;
31
31
  const accountConfig = getAccountConfig(targetAccountId);
@@ -0,0 +1,93 @@
1
+ import yargs from 'yargs';
2
+ import { addAccountOptions, addConfigOptions, addUseEnvironmentOptions, } from '../../../lib/commonOpts.js';
3
+ import testAccountImportDataCommand from '../importData.js';
4
+ import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
5
+ import { handleImportData, handleTargetTestAccountSelectionFlow, } from '../../../lib/importData.js';
6
+ import { trackCommandUsage } from '../../../lib/usageTracking.js';
7
+ import { getImportDataRequest } from '@hubspot/local-dev-lib/crm';
8
+ import { logError } from '../../../lib/errorHandlers/index.js';
9
+ import { importDataFilePathPrompt } from '../../../lib/prompts/importDataFilePathPrompt.js';
10
+ import { confirmImportDataPrompt } from '../../../lib/prompts/confirmImportDataPrompt.js';
11
+ vi.mock('../../../lib/commonOpts');
12
+ vi.mock('../../../lib/importData');
13
+ vi.mock('../../../lib/usageTracking');
14
+ vi.mock('@hubspot/local-dev-lib/crm');
15
+ vi.mock('../../../lib/errorHandlers/index');
16
+ vi.mock('../../../lib/prompts/importDataFilePathPrompt');
17
+ vi.mock('../../../lib/prompts/confirmImportDataPrompt');
18
+ describe('commands/testAccount/importData', () => {
19
+ const yargsMock = yargs;
20
+ const mockExit = vi
21
+ .spyOn(process, 'exit')
22
+ .mockImplementation(() => undefined);
23
+ const mockHandleImportData = vi.mocked(handleImportData);
24
+ const mockHandleTargetTestAccountSelectionFlow = vi.mocked(handleTargetTestAccountSelectionFlow);
25
+ const mockTrackCommandUsage = vi.mocked(trackCommandUsage);
26
+ const mockGetImportDataRequest = vi.mocked(getImportDataRequest);
27
+ const mockLogError = vi.mocked(logError);
28
+ const mockImportDataFilePathPrompt = vi.mocked(importDataFilePathPrompt);
29
+ const mockConfirmImportDataPrompt = vi.mocked(confirmImportDataPrompt);
30
+ beforeEach(() => {
31
+ mockExit.mockReset();
32
+ mockHandleImportData.mockReset();
33
+ mockHandleTargetTestAccountSelectionFlow.mockReset();
34
+ mockTrackCommandUsage.mockReset();
35
+ mockGetImportDataRequest.mockReset();
36
+ mockLogError.mockReset();
37
+ mockImportDataFilePathPrompt.mockReset();
38
+ mockConfirmImportDataPrompt.mockReset();
39
+ });
40
+ describe('command', () => {
41
+ it('should have the correct command structure', () => {
42
+ expect(testAccountImportDataCommand.command).toEqual('import-data');
43
+ });
44
+ });
45
+ // describe('describe', () => {
46
+ // it('should provide a description', () => {
47
+ // expect(testAccountImportDataCommand.describe).toBeDefined();
48
+ // });
49
+ // });
50
+ describe('builder', () => {
51
+ it('should support the correct options', () => {
52
+ testAccountImportDataCommand.builder(yargsMock);
53
+ expect(yargsMock.example).toHaveBeenCalledTimes(1);
54
+ expect(yargsMock.options).toHaveBeenCalledTimes(1);
55
+ expect(addAccountOptions).toHaveBeenCalledTimes(1);
56
+ expect(addAccountOptions).toHaveBeenCalledWith(yargsMock);
57
+ expect(addConfigOptions).toHaveBeenCalledTimes(1);
58
+ expect(addConfigOptions).toHaveBeenCalledWith(yargsMock);
59
+ expect(addUseEnvironmentOptions).toHaveBeenCalledTimes(1);
60
+ expect(addUseEnvironmentOptions).toHaveBeenCalledWith(yargsMock);
61
+ });
62
+ });
63
+ describe('handler', () => {
64
+ it('should complete the flow given the correct args', async () => {
65
+ const targetAccountId = 123456789;
66
+ const mockArgs = {
67
+ d: false,
68
+ debug: false,
69
+ _: [],
70
+ $0: 'hs',
71
+ derivedAccountId: targetAccountId,
72
+ userProvidedAccount: 'test-account',
73
+ filePath: 'test-file.json',
74
+ skipConfirm: true,
75
+ };
76
+ mockHandleTargetTestAccountSelectionFlow.mockResolvedValue(targetAccountId);
77
+ mockGetImportDataRequest.mockReturnValue({
78
+ importRequest: {},
79
+ dataFileNames: ['test-file.json'],
80
+ });
81
+ mockHandleImportData.mockResolvedValue();
82
+ await testAccountImportDataCommand.handler(mockArgs);
83
+ expect(mockHandleTargetTestAccountSelectionFlow).toHaveBeenCalledWith(123456789, 'test-account');
84
+ expect(mockGetImportDataRequest).toHaveBeenCalledWith('test-file.json');
85
+ expect(mockHandleImportData).toHaveBeenCalledTimes(1);
86
+ expect(mockTrackCommandUsage).toHaveBeenCalledTimes(1);
87
+ expect(mockLogError).not.toHaveBeenCalled();
88
+ expect(mockImportDataFilePathPrompt).not.toHaveBeenCalled();
89
+ expect(mockConfirmImportDataPrompt).not.toHaveBeenCalled();
90
+ expect(mockExit).toHaveBeenCalledWith(EXIT_CODES.SUCCESS);
91
+ });
92
+ });
93
+ });
@@ -1,5 +1,7 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
+ import { getValidEnv } from '@hubspot/local-dev-lib/environment';
4
+ import { getEnv } from '@hubspot/local-dev-lib/config';
3
5
  import { getCwd } from '@hubspot/local-dev-lib/path';
4
6
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
5
7
  import { promptUser, listPrompt } from '../../lib/prompts/promptUtils.js';
@@ -9,14 +11,15 @@ import { trackCommandUsage } from '../../lib/usageTracking.js';
9
11
  import { fileExists } from '../../lib/validation.js';
10
12
  import { commands } from '../../lang/en.js';
11
13
  import { createDeveloperTestAccountConfigPrompt } from '../../lib/prompts/createDeveloperTestAccountConfigPrompt.js';
12
- import { logError } from '../../lib/errorHandlers/index.js';
14
+ import { debugError, logError } from '../../lib/errorHandlers/index.js';
13
15
  import SpinniesManager from '../../lib/ui/SpinniesManager.js';
14
- import { createDeveloperTestAccountV3 } from '../../lib/buildAccount.js';
16
+ import { createDeveloperTestAccountV3, saveAccountToConfig, } from '../../lib/buildAccount.js';
15
17
  const command = 'create';
16
18
  const describe = commands.testAccount.create.describe;
17
19
  async function handler(args) {
18
20
  const { derivedAccountId, configPath, formatOutputAsJson } = args;
19
21
  trackCommandUsage('test-account-create', {}, derivedAccountId);
22
+ const env = getValidEnv(getEnv(derivedAccountId));
20
23
  let accountConfigPath = configPath;
21
24
  let testAccountConfig;
22
25
  if (!accountConfigPath) {
@@ -58,8 +61,7 @@ async function handler(args) {
58
61
  else {
59
62
  testAccountConfig = await createDeveloperTestAccountConfigPrompt();
60
63
  }
61
- const jsonOutput = {};
62
- let testAccountId;
64
+ const resultJson = {};
63
65
  SpinniesManager.init({
64
66
  succeedColor: 'white',
65
67
  });
@@ -67,13 +69,10 @@ async function handler(args) {
67
69
  text: commands.testAccount.create.polling.start(testAccountConfig.accountName),
68
70
  });
69
71
  try {
70
- const result = await createDeveloperTestAccountV3(derivedAccountId, testAccountConfig);
71
- if (formatOutputAsJson) {
72
- jsonOutput.accountName = result.accountName;
73
- jsonOutput.accountId = result.accountId;
74
- jsonOutput.personalAccessKey = result.personalAccessKey;
75
- }
76
- testAccountId = result.accountId;
72
+ const createResult = await createDeveloperTestAccountV3(derivedAccountId, testAccountConfig);
73
+ resultJson.accountName = createResult.accountName;
74
+ resultJson.accountId = createResult.accountId;
75
+ resultJson.personalAccessKey = createResult.personalAccessKey;
77
76
  }
78
77
  catch (err) {
79
78
  logError(err);
@@ -83,10 +82,21 @@ async function handler(args) {
83
82
  process.exit(EXIT_CODES.ERROR);
84
83
  }
85
84
  SpinniesManager.succeed('createTestAccount', {
86
- text: commands.testAccount.create.polling.success(testAccountConfig.accountName, testAccountId),
85
+ text: commands.testAccount.create.polling.success(testAccountConfig.accountName, resultJson.accountId),
87
86
  });
88
87
  if (formatOutputAsJson) {
89
- uiLogger.json(jsonOutput);
88
+ uiLogger.json(resultJson);
89
+ }
90
+ else {
91
+ // Only save to config if not using json output
92
+ try {
93
+ await saveAccountToConfig(resultJson.accountId, testAccountConfig.accountName, env, resultJson.personalAccessKey);
94
+ }
95
+ catch (e) {
96
+ debugError(e);
97
+ uiLogger.error(commands.testAccount.create.errors.saveAccountToConfigFailure(testAccountConfig.accountName));
98
+ process.exit(EXIT_CODES.ERROR);
99
+ }
90
100
  }
91
101
  process.exit(EXIT_CODES.SUCCESS);
92
102
  }
@@ -0,0 +1,9 @@
1
+ import { AccountArgs, CommonArgs, ConfigArgs, EnvironmentArgs, YargsCommandModule } from '../../types/Yargs.js';
2
+ export declare const command = "import-data";
3
+ export declare const describe: undefined;
4
+ type CrmImportDataArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & {
5
+ filePath: string | undefined;
6
+ skipConfirm: boolean | undefined;
7
+ };
8
+ declare const crmImportDataCommand: YargsCommandModule<unknown, CrmImportDataArgs>;
9
+ export default crmImportDataCommand;
@@ -0,0 +1,61 @@
1
+ import { getImportDataRequest } from '@hubspot/local-dev-lib/crm';
2
+ import { logError } from '../../lib/errorHandlers/index.js';
3
+ import { makeYargsBuilder } from '../../lib/yargsUtils.js';
4
+ import { trackCommandUsage } from '../../lib/usageTracking.js';
5
+ import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
6
+ import { importDataFilePathPrompt } from '../../lib/prompts/importDataFilePathPrompt.js';
7
+ import { handleImportData, handleTargetTestAccountSelectionFlow, } from '../../lib/importData.js';
8
+ import { confirmImportDataPrompt } from '../../lib/prompts/confirmImportDataPrompt.js';
9
+ import { commands } from '../../lang/en.js';
10
+ export const command = 'import-data';
11
+ export const describe = undefined; // commands.testAccount.subcommands.importData.describe;
12
+ async function handler(args) {
13
+ const { derivedAccountId, userProvidedAccount, filePath: providedFilePath, skipConfirm, } = args;
14
+ trackCommandUsage('crm-import-data', {}, derivedAccountId);
15
+ let targetAccountId;
16
+ try {
17
+ targetAccountId = await handleTargetTestAccountSelectionFlow(derivedAccountId, userProvidedAccount);
18
+ const filePath = providedFilePath || (await importDataFilePathPrompt());
19
+ const { importRequest, dataFileNames } = getImportDataRequest(filePath);
20
+ const confirmImportData = skipConfirm ||
21
+ (await confirmImportDataPrompt(targetAccountId, dataFileNames));
22
+ if (!confirmImportData) {
23
+ process.exit(EXIT_CODES.SUCCESS);
24
+ }
25
+ await handleImportData(targetAccountId, dataFileNames, importRequest);
26
+ }
27
+ catch (error) {
28
+ logError(error);
29
+ process.exit(EXIT_CODES.ERROR);
30
+ }
31
+ process.exit(EXIT_CODES.SUCCESS);
32
+ }
33
+ function crmImportDataBuilder(yargs) {
34
+ yargs.example([['$0 test-account import-data']]);
35
+ yargs.options({
36
+ 'file-path': {
37
+ type: 'string',
38
+ describe: commands.testAccount.subcommands.importData.options.filePath.describe,
39
+ positional: false,
40
+ },
41
+ 'skip-confirm': {
42
+ type: 'boolean',
43
+ describe: commands.testAccount.subcommands.importData.options.skipConfirm
44
+ .describe,
45
+ },
46
+ });
47
+ return yargs;
48
+ }
49
+ const builder = makeYargsBuilder(crmImportDataBuilder, command, describe, {
50
+ useGlobalOptions: true,
51
+ useConfigOptions: true,
52
+ useAccountOptions: true,
53
+ useEnvironmentOptions: true,
54
+ });
55
+ const crmImportDataCommand = {
56
+ command,
57
+ describe,
58
+ builder,
59
+ handler,
60
+ };
61
+ export default crmImportDataCommand;
@@ -1,5 +1,6 @@
1
1
  import createTestAccountCommand from './testAccount/create.js';
2
2
  import createTestAccountConfigCommand from './testAccount/createConfig.js';
3
+ import importDataCommand from './testAccount/importData.js';
3
4
  import deleteTestAccountCommand from './testAccount/delete.js';
4
5
  import { makeYargsBuilder } from '../lib/yargsUtils.js';
5
6
  import { commands } from '../lang/en.js';
@@ -10,6 +11,7 @@ function testAccountBuilder(yargs) {
10
11
  .command(createTestAccountCommand)
11
12
  .command(createTestAccountConfigCommand)
12
13
  .command(deleteTestAccountCommand)
14
+ .command(importDataCommand)
13
15
  .demandCommand(1, '');
14
16
  return yargs;
15
17
  }