@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
@@ -1,83 +1,54 @@
1
1
  import fs from 'fs';
2
2
  import os from 'os';
3
3
  import path from 'path';
4
- import { EXIT_CODES } from '../../enums/exitCodes.js';
5
4
  import { validateProjectConfig } from '../../projects/config.js';
6
- import { uiLogger } from '../../ui/logger.js';
5
+ import ProjectValidationError from '../../errors/ProjectValidationError.js';
7
6
  vi.mock('../../ui/logger.js');
8
7
  describe('lib/projects', () => {
9
8
  describe('validateProjectConfig()', () => {
10
9
  let projectDir;
11
- let exitMock;
12
10
  beforeAll(() => {
13
11
  projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'projects-'));
14
12
  fs.mkdirSync(path.join(projectDir, 'src'));
15
13
  });
16
- beforeEach(() => {
17
- // @ts-expect-error - Mocking process.exit
18
- exitMock = vi
19
- .spyOn(process, 'exit')
20
- .mockImplementation(() => undefined);
21
- });
22
- afterEach(() => {
23
- exitMock.mockRestore();
24
- });
25
14
  it('rejects undefined configuration', () => {
26
15
  // @ts-ignore Testing invalid input
27
- validateProjectConfig(null, projectDir);
28
- expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
29
- expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/.*Unable to locate a project configuration file. Try running again from a project directory, or run*/));
16
+ expect(() => validateProjectConfig(null, projectDir)).toThrow(/.*Unable to locate a project configuration file. Try running again from a project directory, or run*/);
30
17
  });
31
18
  it('rejects configuration with missing name', () => {
32
19
  // @ts-ignore Testing invalid input
33
- validateProjectConfig({ srcDir: '.' }, projectDir);
34
- expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
35
- expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/.*missing required fields*/));
20
+ expect(() => validateProjectConfig({ srcDir: '.' }, projectDir)).toThrow(/.*missing required fields*/);
36
21
  });
37
22
  it('rejects configuration with missing srcDir', () => {
23
+ expect(() =>
38
24
  // @ts-ignore Testing invalid input
39
- validateProjectConfig({ name: 'hello' }, projectDir);
40
- expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
41
- expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/.*missing required fields.*/));
25
+ validateProjectConfig({ name: 'hello' }, projectDir)).toThrow(/.*missing required fields.*/);
42
26
  });
43
27
  describe('rejects configuration with srcDir outside project directory', () => {
44
28
  it('for parent directory', () => {
45
- validateProjectConfig({ name: 'hello', srcDir: '..', platformVersion: '' }, projectDir);
46
- expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
47
- expect(uiLogger.error).toHaveBeenCalledWith(expect.stringContaining('srcDir: ".."'));
29
+ expect(() => validateProjectConfig({ name: 'hello', srcDir: '..', platformVersion: '' }, projectDir)).toThrow(/srcDir: "\.\."/);
48
30
  });
49
31
  it('for root directory', () => {
50
- validateProjectConfig({ name: 'hello', srcDir: '/', platformVersion: '' }, projectDir);
51
- expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
52
- expect(uiLogger.error).toHaveBeenCalledWith(expect.stringContaining('srcDir: "/"'));
32
+ expect(() => validateProjectConfig({ name: 'hello', srcDir: '/', platformVersion: '' }, projectDir)).toThrow(/srcDir: "\/"/);
53
33
  });
54
34
  it('for complicated directory', () => {
55
35
  const srcDir = './src/././../src/../../src';
56
- validateProjectConfig({ name: 'hello', srcDir, platformVersion: '' }, projectDir);
57
- expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
58
- expect(uiLogger.error).toHaveBeenCalledWith(expect.stringContaining(`srcDir: "${srcDir}"`));
36
+ expect(() => validateProjectConfig({ name: 'hello', srcDir, platformVersion: '' }, projectDir)).toThrow(ProjectValidationError);
37
+ expect(() => validateProjectConfig({ name: 'hello', srcDir, platformVersion: '' }, projectDir)).toThrow(/srcDir:/);
59
38
  });
60
39
  });
61
40
  it('rejects configuration with srcDir that does not exist', () => {
62
- validateProjectConfig({ name: 'hello', srcDir: 'foo', platformVersion: '' }, projectDir);
63
- expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
64
- expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/.*could not be found in.*/));
41
+ expect(() => validateProjectConfig({ name: 'hello', srcDir: 'foo', platformVersion: '' }, projectDir)).toThrow(/.*could not be found in.*/);
65
42
  });
66
43
  describe('accepts configuration with valid srcDir', () => {
67
44
  it('for current directory', () => {
68
- validateProjectConfig({ name: 'hello', srcDir: '.', platformVersion: '' }, projectDir);
69
- expect(exitMock).not.toHaveBeenCalled();
70
- expect(uiLogger.error).not.toHaveBeenCalled();
45
+ expect(() => validateProjectConfig({ name: 'hello', srcDir: '.', platformVersion: '' }, projectDir)).not.toThrow();
71
46
  });
72
47
  it('for relative directory', () => {
73
- validateProjectConfig({ name: 'hello', srcDir: './src', platformVersion: '' }, projectDir);
74
- expect(exitMock).not.toHaveBeenCalled();
75
- expect(uiLogger.error).not.toHaveBeenCalled();
48
+ expect(() => validateProjectConfig({ name: 'hello', srcDir: './src', platformVersion: '' }, projectDir)).not.toThrow();
76
49
  });
77
50
  it('for implied relative directory', () => {
78
- validateProjectConfig({ name: 'hello', srcDir: 'src', platformVersion: '' }, projectDir);
79
- expect(exitMock).not.toHaveBeenCalled();
80
- expect(uiLogger.error).not.toHaveBeenCalled();
51
+ expect(() => validateProjectConfig({ name: 'hello', srcDir: 'src', platformVersion: '' }, projectDir)).not.toThrow();
81
52
  });
82
53
  });
83
54
  });
@@ -1,9 +1,51 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs';
3
3
  import { coerceToValidUid, metafileExtension, } from '@hubspot/project-parsing-lib';
4
+ import { fileExists } from '../validation.js';
4
5
  import { uiLogger } from '../ui/logger.js';
5
6
  import { AppKey } from '@hubspot/project-parsing-lib/src/lib/constants.js';
6
7
  import { lib } from '../../lang/en.js';
8
+ import { debugError } from '../errorHandlers/index.js';
9
+ // Prefix for the metafile extension
10
+ const metafileExtensionPrefix = path.parse(metafileExtension).name;
11
+ function applyDifferentiatorToFilename(filename, differentiator, isHsMetaFile) {
12
+ const { name, ext, dir } = path.parse(filename);
13
+ if (isHsMetaFile) {
14
+ return path.join(dir, `${name.replace(metafileExtensionPrefix, '')}-${differentiator}${metafileExtension}`);
15
+ }
16
+ return path.join(dir, `${name}-${differentiator}${ext}`);
17
+ }
18
+ // Generates safe filename differentiators, avoiding collisions with existing filenames
19
+ // E.x. "NewCard.tsx" -> "NewCard-1.tsx"
20
+ function generateSafeFilenameDifferentiator(sourceFiles, hsMetaFiles) {
21
+ let differentiator = 1;
22
+ let isDifferentiatorUnique = false;
23
+ let maxAttempts = 10;
24
+ while (!isDifferentiatorUnique) {
25
+ differentiator++;
26
+ maxAttempts--;
27
+ try {
28
+ const isDifferentiatorUniqueForSourceFiles = sourceFiles.every(file => {
29
+ return !fileExists(applyDifferentiatorToFilename(file, differentiator, false));
30
+ });
31
+ const isDifferentiatorUniqueForHsMetaFiles = hsMetaFiles.every(file => {
32
+ return !fileExists(applyDifferentiatorToFilename(file, differentiator, true));
33
+ });
34
+ isDifferentiatorUnique =
35
+ isDifferentiatorUniqueForSourceFiles &&
36
+ isDifferentiatorUniqueForHsMetaFiles;
37
+ }
38
+ catch (error) {
39
+ uiLogger.debug(lib.projects.generateSafeFilenameDifferentiator.failedToCheckFiles);
40
+ maxAttempts = 0;
41
+ }
42
+ // If we've tried too many times, just use a timestamp
43
+ if (maxAttempts <= 0) {
44
+ return Date.now();
45
+ }
46
+ }
47
+ return differentiator;
48
+ }
7
49
  // Handles a collision between component source files
8
50
  export function handleComponentCollision({ dest, src, collisions }) {
9
51
  const hsMetaFiles = [];
@@ -20,19 +62,20 @@ export function handleComponentCollision({ dest, src, collisions }) {
20
62
  sourceFiles.push(collision);
21
63
  }
22
64
  });
23
- const sourceFilenameMapping = sourceFiles.reduce((acc, filename) => {
24
- const { name, ext, dir } = path.parse(filename);
65
+ const filenameDifferentiator = generateSafeFilenameDifferentiator(sourceFiles, hsMetaFiles);
66
+ // Exclude markdown files fromthe rename process because they should not be duplicated
67
+ const sourceFilenameMapping = sourceFiles
68
+ .filter(filename => !filename.endsWith('.md'))
69
+ .reduce((acc, filename) => {
25
70
  return {
26
71
  ...acc,
27
- [filename]: path.join(dir, `${name}-${Date.now()}${ext}`),
72
+ [filename]: applyDifferentiatorToFilename(filename, filenameDifferentiator, false),
28
73
  };
29
74
  }, {});
30
- const metafileExtensionPrefix = path.parse(metafileExtension).name;
31
75
  const metaFilenameMapping = hsMetaFiles.reduce((acc, filename) => {
32
- const { name, dir } = path.parse(filename);
33
76
  return {
34
77
  ...acc,
35
- [filename]: path.join(dir, `${name.replace(metafileExtensionPrefix, '')}-${Date.now()}${metafileExtension}`),
78
+ [filename]: applyDifferentiatorToFilename(filename, filenameDifferentiator, true),
36
79
  };
37
80
  }, {});
38
81
  // Update the metafiles that might contain references to the old filenames
@@ -81,22 +124,35 @@ export function updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFile
81
124
  uiLogger.log('');
82
125
  uiLogger.log(lib.projects.updateHsMetaFilesWithAutoGeneratedFields.header);
83
126
  for (const hsMetaFile of hsMetaFilePaths) {
84
- const component = JSON.parse(fs.readFileSync(hsMetaFile).toString());
85
- let uid = coerceToValidUid(`${component.type}-${projectName}`) || component.uid;
86
- if (existingUids.includes(uid)) {
87
- uid =
88
- coerceToValidUid(`${component.type}-${Date.now()}-${projectName}`) ||
89
- component.uid;
127
+ try {
128
+ const component = JSON.parse(fs.readFileSync(hsMetaFile).toString());
129
+ const getBaseUid = () => {
130
+ const customUid = coerceToValidUid(`${projectName}_${component.type}`);
131
+ if (customUid) {
132
+ return customUid.replace(/-/g, '_');
133
+ }
134
+ return component.uid;
135
+ };
136
+ let uid = getBaseUid();
137
+ let differentiator = 1;
138
+ while (existingUids.includes(uid)) {
139
+ differentiator++;
140
+ uid = `${getBaseUid()}_${differentiator}`;
141
+ }
142
+ component.uid = uid;
143
+ if (component.type === AppKey && component.config) {
144
+ component.config.name = `${projectName}-Application`;
145
+ uiLogger.log(lib.projects.updateHsMetaFilesWithAutoGeneratedFields.applicationLog(component.type, component.uid, component.config.name));
146
+ }
147
+ else {
148
+ uiLogger.log(lib.projects.updateHsMetaFilesWithAutoGeneratedFields.componentLog(component.type, component.uid));
149
+ }
150
+ fs.writeFileSync(hsMetaFile, JSON.stringify(component, null, 2));
90
151
  }
91
- component.uid = uid;
92
- if (component.type === AppKey && component.config) {
93
- component.config.name = `${projectName}-Application`;
94
- uiLogger.log(lib.projects.updateHsMetaFilesWithAutoGeneratedFields.applicationLog(component.type, component.uid, component.config.name));
95
- }
96
- else {
97
- uiLogger.log(lib.projects.updateHsMetaFilesWithAutoGeneratedFields.componentLog(component.type, component.uid));
152
+ catch (error) {
153
+ debugError(error);
154
+ uiLogger.error(lib.projects.updateHsMetaFilesWithAutoGeneratedFields.failedToUpdate(hsMetaFile));
98
155
  }
99
- fs.writeFileSync(hsMetaFile, JSON.stringify(component, null, 2));
100
156
  }
101
157
  uiLogger.log('');
102
158
  }
@@ -4,8 +4,8 @@ import findup from 'findup-sync';
4
4
  import { getAbsoluteFilePath, getCwd } from '@hubspot/local-dev-lib/path';
5
5
  import { PROJECT_CONFIG_FILE } from '../constants.js';
6
6
  import { lib } from '../../lang/en.js';
7
- import { EXIT_CODES } from '../enums/exitCodes.js';
8
7
  import { uiLogger } from '../ui/logger.js';
8
+ import ProjectValidationError from '../errors/ProjectValidationError.js';
9
9
  export function writeProjectConfig(configPath, config) {
10
10
  try {
11
11
  fs.ensureFileSync(configPath);
@@ -50,21 +50,17 @@ export async function getProjectConfig(dir) {
50
50
  }
51
51
  export function validateProjectConfig(projectConfig, projectDir) {
52
52
  if (!projectConfig || !projectDir) {
53
- uiLogger.error(lib.projects.validateProjectConfig.configNotFound);
54
- return process.exit(EXIT_CODES.ERROR);
53
+ throw new ProjectValidationError(lib.projects.validateProjectConfig.configNotFound);
55
54
  }
56
55
  if (!projectConfig.name || !projectConfig.srcDir) {
57
- uiLogger.error(lib.projects.validateProjectConfig.configMissingFields);
58
- return process.exit(EXIT_CODES.ERROR);
56
+ throw new ProjectValidationError(lib.projects.validateProjectConfig.configMissingFields);
59
57
  }
60
58
  const resolvedPath = path.resolve(projectDir, projectConfig.srcDir);
61
59
  if (!resolvedPath.startsWith(projectDir)) {
62
60
  const projectConfigFile = path.relative('.', path.join(projectDir, PROJECT_CONFIG_FILE));
63
- uiLogger.error(lib.projects.validateProjectConfig.srcOutsideProjectDir(projectConfigFile, projectConfig.srcDir));
64
- return process.exit(EXIT_CODES.ERROR);
61
+ throw new ProjectValidationError(lib.projects.validateProjectConfig.srcOutsideProjectDir(projectConfigFile, projectConfig.srcDir));
65
62
  }
66
63
  if (!fs.existsSync(resolvedPath)) {
67
- uiLogger.error(lib.projects.validateProjectConfig.srcDirNotFound(projectConfig.srcDir, projectDir));
68
- return process.exit(EXIT_CODES.ERROR);
64
+ throw new ProjectValidationError(lib.projects.validateProjectConfig.srcDirNotFound(projectConfig.srcDir, projectDir));
69
65
  }
70
66
  }
@@ -1,12 +1,23 @@
1
1
  import { calculateComponentTemplateChoices } from '../v2.js';
2
2
  import { hasFeature } from '../../../hasFeature.js';
3
+ import { getConfigAccountById as __getConfigAccountById } from '@hubspot/local-dev-lib/config';
4
+ const getConfigAccountById = __getConfigAccountById;
3
5
  vi.mock('../../ui/logger.js');
4
6
  vi.mock('@hubspot/local-dev-lib/api/github');
5
7
  vi.mock('../../../hasFeature.js');
8
+ vi.mock('@hubspot/local-dev-lib/config');
6
9
  const mockHasFeature = vi.mocked(hasFeature);
7
10
  describe('lib/projects/create/v2', () => {
8
11
  beforeEach(() => {
9
12
  mockHasFeature.mockResolvedValue(true);
13
+ // Mock account config
14
+ getConfigAccountById.mockReturnValue({
15
+ accountId: 123,
16
+ name: 'Test Account',
17
+ authType: 'personalaccesskey',
18
+ personalAccessKey: 'test-key',
19
+ env: 'prod',
20
+ });
10
21
  });
11
22
  describe('calculateComponentTemplateChoices()', () => {
12
23
  beforeEach(() => {
@@ -1,6 +1,6 @@
1
1
  import { fetchAppInstallationData } from '@hubspot/local-dev-lib/api/localDevAuth';
2
2
  import { fetchAppMetadataByUid, fetchPublicAppProductionInstallCounts, installStaticAuthAppOnTestAccount, } from '@hubspot/local-dev-lib/api/appsDev';
3
- import { getAccountConfig } from '@hubspot/local-dev-lib/config';
3
+ import { getConfigAccountById } from '@hubspot/local-dev-lib/config';
4
4
  import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, APP_INSTALLATION_STATES, LOCAL_DEV_SERVER_MESSAGE_TYPES, } from '../../constants.js';
5
5
  import { EXIT_CODES } from '../../enums/exitCodes.js';
6
6
  import { isAppIRNode } from '../../projects/structure.js';
@@ -67,7 +67,7 @@ class AppDevModeInterface {
67
67
  return (this.appNode?.config.auth.type.toLowerCase() === APP_AUTH_TYPES.OAUTH);
68
68
  }
69
69
  isAutomaticallyInstallable() {
70
- const targetTestingAccount = getAccountConfig(this.localDevState.targetTestingAccountId);
70
+ const targetTestingAccount = getConfigAccountById(this.localDevState.targetTestingAccountId);
71
71
  if (!targetTestingAccount) {
72
72
  return false;
73
73
  }
@@ -2,7 +2,7 @@ import { logger } from '@hubspot/local-dev-lib/logger';
2
2
  import { DevModeInterface as UIEDevModeInterface } from '@hubspot/ui-extensions-dev-server';
3
3
  import { startPortManagerServer, stopPortManagerServer, requestPorts, } from '@hubspot/local-dev-lib/portManager';
4
4
  import { getHubSpotApiOrigin, getHubSpotWebsiteOrigin, } from '@hubspot/local-dev-lib/urls';
5
- import { getAccountConfig } from '@hubspot/local-dev-lib/config';
5
+ import { getConfigAccountById } from '@hubspot/local-dev-lib/config';
6
6
  import { ComponentTypes, } from '../../../types/Projects.js';
7
7
  import { lib } from '../../../lang/en.js';
8
8
  import { uiLogger } from '../../ui/logger.js';
@@ -58,7 +58,7 @@ class DevServerManager_DEPRECATED {
58
58
  async setup({ components, onUploadRequired, accountId, setActiveApp, }) {
59
59
  this.componentsByType = this.arrangeComponentsByType(components);
60
60
  let env;
61
- const accountConfig = getAccountConfig(accountId);
61
+ const accountConfig = getConfigAccountById(accountId);
62
62
  if (accountConfig) {
63
63
  env = accountConfig.env;
64
64
  }
@@ -1,5 +1,5 @@
1
- import { getAccountId, hasLocalStateFlag } from '@hubspot/local-dev-lib/config';
2
- import { getConfigDefaultAccount } from '@hubspot/local-dev-lib/config';
1
+ import { hasLocalStateFlag } from '@hubspot/local-dev-lib/config';
2
+ import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
3
3
  import { uiLogger } from '../../ui/logger.js';
4
4
  import { uiLine, uiAccountDescription, uiCommandReference, } from '../../ui/index.js';
5
5
  import { lib } from '../../../lang/en.js';
@@ -30,9 +30,9 @@ class LocalDevLogger {
30
30
  uiLogger.error(langFunction(e instanceof Error ? e.message : ''));
31
31
  }
32
32
  getUploadCommand() {
33
- const currentDefaultAccount = getConfigDefaultAccount() || undefined;
33
+ const currentDefaultAccount = getConfigDefaultAccountIfExists();
34
34
  return this.state.targetProjectAccountId !==
35
- getAccountId(currentDefaultAccount)
35
+ currentDefaultAccount?.accountId
36
36
  ? uiCommandReference(`hs project upload --account=${this.state.targetProjectAccountId}`)
37
37
  : uiCommandReference('hs project upload');
38
38
  }
@@ -3,7 +3,7 @@ import chokidar from 'chokidar';
3
3
  import chalk from 'chalk';
4
4
  import { fetchAppInstallationData } from '@hubspot/local-dev-lib/api/localDevAuth';
5
5
  import { fetchPublicAppsForPortal, fetchPublicAppProductionInstallCounts, } from '@hubspot/local-dev-lib/api/appsDev';
6
- import { getAccountId, getConfigDefaultAccount, } from '@hubspot/local-dev-lib/config';
6
+ import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
7
7
  import { PROJECT_CONFIG_FILE } from '../../constants.js';
8
8
  import SpinniesManager from '../../ui/SpinniesManager.js';
9
9
  import DevServerManager_DEPRECATED from './DevServerManager_DEPRECATED.js';
@@ -200,8 +200,8 @@ class LocalDevManager {
200
200
  });
201
201
  }
202
202
  getUploadCommand() {
203
- const currentDefaultAccount = getConfigDefaultAccount() || undefined;
204
- return this.targetProjectAccountId !== getAccountId(currentDefaultAccount)
203
+ const currentDefaultAccount = getConfigDefaultAccountIfExists();
204
+ return this.targetProjectAccountId !== currentDefaultAccount?.accountId
205
205
  ? uiCommandReference(`hs project upload --account=${this.targetProjectAccountId}`)
206
206
  : uiCommandReference('hs project upload');
207
207
  }
@@ -1,14 +1,14 @@
1
- import { CLIAccount } from '@hubspot/local-dev-lib/types/Accounts';
1
+ import { HubSpotConfigAccount } from '@hubspot/local-dev-lib/types/Accounts';
2
2
  import { Environment } from '@hubspot/local-dev-lib/types/Config';
3
3
  import { DeveloperTestAccount } from '@hubspot/local-dev-lib/types/developerTestAccounts.js';
4
4
  import { ProjectDevTargetAccountPromptResponse } from '../../../prompts/projectDevTargetAccountPrompt.js';
5
- export declare function confirmDefaultAccountIsTarget(accountConfig: CLIAccount): Promise<void>;
6
- export declare function checkIfDefaultAccountIsSupported(accountConfig: CLIAccount, hasPublicApps: boolean): Promise<void>;
7
- export declare function checkIfParentAccountIsAuthed(accountConfig: CLIAccount): void;
8
- export declare function checkIfAccountFlagIsSupported(accountConfig: CLIAccount, hasPublicApps: boolean): void;
9
- export declare function suggestRecommendedNestedAccount(accounts: CLIAccount[], accountConfig: CLIAccount, hasPublicApps: boolean): Promise<ProjectDevTargetAccountPromptResponse>;
10
- export declare function createSandboxForLocalDev(accountId: number, accountConfig: CLIAccount, env: Environment): Promise<number>;
11
- export declare function createDeveloperTestAccountForLocalDev(accountId: number, accountConfig: CLIAccount, env: Environment, useV2?: boolean): Promise<number>;
5
+ export declare function confirmDefaultAccountIsTarget(accountConfig: HubSpotConfigAccount): Promise<void>;
6
+ export declare function checkIfDefaultAccountIsSupported(accountConfig: HubSpotConfigAccount, hasPublicApps: boolean): Promise<void>;
7
+ export declare function checkIfParentAccountIsAuthed(accountConfig: HubSpotConfigAccount): void;
8
+ export declare function checkIfAccountFlagIsSupported(accountConfig: HubSpotConfigAccount, hasPublicApps: boolean): void;
9
+ export declare function suggestRecommendedNestedAccount(accounts: HubSpotConfigAccount[], accountConfig: HubSpotConfigAccount, hasPublicApps: boolean): Promise<ProjectDevTargetAccountPromptResponse>;
10
+ export declare function createSandboxForLocalDev(accountId: number, accountConfig: HubSpotConfigAccount, env: Environment): Promise<number>;
11
+ export declare function createDeveloperTestAccountForLocalDev(accountId: number, accountConfig: HubSpotConfigAccount, env: Environment, useV2?: boolean): Promise<number>;
12
12
  export declare function useExistingDevTestAccount(env: Environment, account: DeveloperTestAccount): Promise<void>;
13
- export declare function hasSandboxes(account: CLIAccount): Promise<boolean>;
14
- export declare function selectAccountTypePrompt(accountConfig: CLIAccount): Promise<string | null>;
13
+ export declare function hasSandboxes(account: HubSpotConfigAccount): Promise<boolean>;
14
+ export declare function selectAccountTypePrompt(accountConfig: HubSpotConfigAccount): Promise<string | null>;
@@ -1,6 +1,5 @@
1
1
  import { HUBSPOT_ACCOUNT_TYPE_STRINGS } from '@hubspot/local-dev-lib/constants/config';
2
- import { getAccountConfig } from '@hubspot/local-dev-lib/config';
3
- import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier';
2
+ import { getConfigAccountById, getConfigAccountIfExists, } from '@hubspot/local-dev-lib/config';
4
3
  import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
5
4
  import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
6
5
  import { isMissingScopeError } from '@hubspot/local-dev-lib/errors/index';
@@ -60,8 +59,8 @@ export async function checkIfDefaultAccountIsSupported(accountConfig, hasPublicA
60
59
  }
61
60
  export function checkIfParentAccountIsAuthed(accountConfig) {
62
61
  if (!accountConfig.parentAccountId ||
63
- !getAccountConfig(accountConfig.parentAccountId)) {
64
- uiLogger.error(lib.localDevHelpers.account.checkIfParentAccountIsAuthed.notAuthedError(accountConfig.parentAccountId || '', uiAccountDescription(getAccountIdentifier(accountConfig))));
62
+ !getConfigAccountIfExists(accountConfig.parentAccountId)?.accountId) {
63
+ uiLogger.error(lib.localDevHelpers.account.checkIfParentAccountIsAuthed.notAuthedError(accountConfig.parentAccountId || '', uiAccountDescription(accountConfig.accountId)));
65
64
  process.exit(EXIT_CODES.SUCCESS);
66
65
  }
67
66
  }
@@ -122,11 +121,7 @@ export async function createSandboxForLocalDev(accountId, accountConfig, env) {
122
121
  trackCommandMetadataUsage('sandbox-create', { step: 'project-dev' }, accountId);
123
122
  const result = await buildSandbox(name, accountConfig, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, env);
124
123
  const targetAccountId = result.sandbox.sandboxHubId;
125
- const sandboxAccountConfig = getAccountConfig(result.sandbox.sandboxHubId);
126
- if (!sandboxAccountConfig) {
127
- uiLogger.error(lib.sandbox.create.developer.failure.generic);
128
- process.exit(EXIT_CODES.ERROR);
129
- }
124
+ const sandboxAccountConfig = getConfigAccountById(result.sandbox.sandboxHubId);
130
125
  const syncTasks = await getAvailableSyncTypes(accountConfig, sandboxAccountConfig);
131
126
  // For v1 sandboxes, keep sync here. Once we migrate to v2, this will be handled by BE automatically
132
127
  await syncSandbox(sandboxAccountConfig, accountConfig, env, syncTasks, true);
@@ -190,7 +185,7 @@ export async function useExistingDevTestAccount(env, account) {
190
185
  uiLogger.success(lib.developerTestAccount.create.success.configFileUpdated(devTestAcctConfigName, PERSONAL_ACCESS_KEY_AUTH_METHOD.name));
191
186
  }
192
187
  export async function hasSandboxes(account) {
193
- const accountId = getAccountIdentifier(account);
188
+ const accountId = account.accountId;
194
189
  if (!accountId) {
195
190
  return false;
196
191
  }
@@ -206,7 +201,7 @@ export async function hasSandboxes(account) {
206
201
  // Top level prompt to choose the type of account to test on
207
202
  export async function selectAccountTypePrompt(accountConfig) {
208
203
  const hasAccessToSandboxes = await hasSandboxes(accountConfig);
209
- const accountId = getAccountIdentifier(accountConfig);
204
+ const accountId = accountConfig.accountId;
210
205
  const result = await listPrompt(lib.localDevHelpers.account.selectAccountTypePrompt.message, {
211
206
  choices: [
212
207
  {
@@ -5,6 +5,6 @@ export function isV2Project(platformVersion) {
5
5
  if (platformVersion.toLowerCase() === 'unstable') {
6
6
  return true;
7
7
  }
8
- const [year, minor] = platformVersion.split('.');
8
+ const [year, minor] = platformVersion.split(/[.-]/);
9
9
  return Number(year) >= 2025 && Number(minor) >= 2;
10
10
  }
@@ -1,18 +1,17 @@
1
1
  import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
2
- import { getEnv } from '@hubspot/local-dev-lib/config';
3
- import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments';
2
+ import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
4
3
  function getBaseUrl(accountId) {
5
- return getHubSpotWebsiteOrigin(getEnv(accountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD);
4
+ return getHubSpotWebsiteOrigin(getConfigAccountEnvironment(accountId));
6
5
  }
7
6
  function getProjectHomeUrl(accountId) {
8
7
  return `${getBaseUrl(accountId)}/developer-projects/${accountId}`;
9
8
  }
10
9
  export function getProjectComponentDistributionUrl(projectName, componentName, accountId) {
11
- const baseUrl = getHubSpotWebsiteOrigin(getEnv(accountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD);
10
+ const baseUrl = getHubSpotWebsiteOrigin(getConfigAccountEnvironment(accountId));
12
11
  return `${baseUrl}/developer-projects/${accountId}/project/${projectName}/component/${componentName}/distribution`;
13
12
  }
14
13
  export function getDeveloperOverviewUrl(accountId) {
15
- const baseUrl = getHubSpotWebsiteOrigin(getEnv(accountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD);
14
+ const baseUrl = getHubSpotWebsiteOrigin(getConfigAccountEnvironment(accountId));
16
15
  return `${baseUrl}/developer-overview/${accountId}`;
17
16
  }
18
17
  export function getProjectDetailUrl(projectName, accountId) {
@@ -38,6 +37,6 @@ export function getLocalDevUiUrl(accountId, showWelcomeScreen) {
38
37
  return `${getBaseUrl(accountId)}/developer-projects-local-dev/${accountId}${showWelcomeScreen ? '?welcome' : ''}`;
39
38
  }
40
39
  export function getAccountHomeUrl(accountId) {
41
- const baseUrl = getHubSpotWebsiteOrigin(getEnv(accountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD);
40
+ const baseUrl = getHubSpotWebsiteOrigin(getConfigAccountEnvironment(accountId));
42
41
  return `${baseUrl}/home?portalId=${accountId}`;
43
42
  }
@@ -0,0 +1,153 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { createDeveloperTestAccountConfigPrompt } from '../createDeveloperTestAccountConfigPrompt.js';
3
+ import * as promptUtils from '../promptUtils.js';
4
+ vi.mock('../promptUtils.js');
5
+ describe('createDeveloperTestAccountConfigPrompt', () => {
6
+ beforeEach(() => {
7
+ vi.clearAllMocks();
8
+ });
9
+ describe('with name and description provided via args', () => {
10
+ it('should skip name and description prompts when provided', async () => {
11
+ const mockPromptUser = vi.mocked(promptUtils.promptUser);
12
+ mockPromptUser.mockResolvedValueOnce({}); // name/description prompts skipped
13
+ mockPromptUser.mockResolvedValueOnce({
14
+ useDefaultAccountLevels: 'default',
15
+ }); // tier selection
16
+ const result = await createDeveloperTestAccountConfigPrompt({
17
+ name: 'TestAccount',
18
+ description: 'Test description',
19
+ });
20
+ expect(result).toEqual({
21
+ accountName: 'TestAccount',
22
+ description: 'Test description',
23
+ marketingLevel: 'ENTERPRISE',
24
+ opsLevel: 'ENTERPRISE',
25
+ serviceLevel: 'ENTERPRISE',
26
+ salesLevel: 'ENTERPRISE',
27
+ contentLevel: 'ENTERPRISE',
28
+ });
29
+ expect(mockPromptUser).toHaveBeenCalledTimes(2);
30
+ });
31
+ });
32
+ describe('with tier flags provided', () => {
33
+ it('should skip tier prompts and use provided values with defaults', async () => {
34
+ const mockPromptUser = vi.mocked(promptUtils.promptUser);
35
+ mockPromptUser.mockResolvedValueOnce({}); // name/description prompts skipped
36
+ const result = await createDeveloperTestAccountConfigPrompt({
37
+ name: 'TestAccount',
38
+ description: 'Test',
39
+ marketingLevel: 'PROFESSIONAL',
40
+ salesLevel: 'STARTER',
41
+ });
42
+ expect(result).toEqual({
43
+ accountName: 'TestAccount',
44
+ description: 'Test',
45
+ marketingLevel: 'PROFESSIONAL',
46
+ opsLevel: 'ENTERPRISE',
47
+ serviceLevel: 'ENTERPRISE',
48
+ salesLevel: 'STARTER',
49
+ contentLevel: 'ENTERPRISE',
50
+ });
51
+ // Should only call promptUser once (for name/description which are skipped)
52
+ expect(mockPromptUser).toHaveBeenCalledTimes(1);
53
+ });
54
+ it('should default unprovided tiers to ENTERPRISE', async () => {
55
+ const mockPromptUser = vi.mocked(promptUtils.promptUser);
56
+ mockPromptUser.mockResolvedValueOnce({
57
+ description: 'Test',
58
+ }); // description prompt (name provided via args, description not provided)
59
+ const result = await createDeveloperTestAccountConfigPrompt({
60
+ name: 'TestAccount',
61
+ contentLevel: 'FREE',
62
+ });
63
+ expect(result).toEqual({
64
+ accountName: 'TestAccount',
65
+ description: 'Test',
66
+ marketingLevel: 'ENTERPRISE',
67
+ opsLevel: 'ENTERPRISE',
68
+ serviceLevel: 'ENTERPRISE',
69
+ salesLevel: 'ENTERPRISE',
70
+ contentLevel: 'FREE',
71
+ });
72
+ });
73
+ });
74
+ describe('with no flags provided', () => {
75
+ it('should prompt for name, description, and tier selection', async () => {
76
+ const mockPromptUser = vi.mocked(promptUtils.promptUser);
77
+ // First call: name/description prompts
78
+ mockPromptUser.mockResolvedValueOnce({
79
+ accountName: 'PromptedAccount',
80
+ description: 'Prompted description',
81
+ });
82
+ // Second call: tier selection
83
+ mockPromptUser.mockResolvedValueOnce({
84
+ useDefaultAccountLevels: 'default',
85
+ });
86
+ const result = await createDeveloperTestAccountConfigPrompt({});
87
+ expect(result).toEqual({
88
+ accountName: 'PromptedAccount',
89
+ description: 'Prompted description',
90
+ marketingLevel: 'ENTERPRISE',
91
+ opsLevel: 'ENTERPRISE',
92
+ serviceLevel: 'ENTERPRISE',
93
+ salesLevel: 'ENTERPRISE',
94
+ contentLevel: 'ENTERPRISE',
95
+ });
96
+ expect(mockPromptUser).toHaveBeenCalledTimes(2);
97
+ });
98
+ it('should allow manual tier selection', async () => {
99
+ const mockPromptUser = vi.mocked(promptUtils.promptUser);
100
+ mockPromptUser.mockResolvedValueOnce({
101
+ accountName: 'TestAccount',
102
+ description: 'Test',
103
+ }); // name/description
104
+ mockPromptUser.mockResolvedValueOnce({
105
+ useDefaultAccountLevels: 'manual',
106
+ }); // tier choice
107
+ mockPromptUser.mockResolvedValueOnce({
108
+ testAccountLevels: [
109
+ { hub: 'MARKETING', tier: 'PROFESSIONAL' },
110
+ { hub: 'OPS', tier: 'STARTER' },
111
+ { hub: 'SERVICE', tier: 'ENTERPRISE' },
112
+ { hub: 'SALES', tier: 'FREE' },
113
+ { hub: 'CONTENT', tier: 'ENTERPRISE' },
114
+ ],
115
+ }); // manual tier selection
116
+ const result = await createDeveloperTestAccountConfigPrompt({});
117
+ expect(result).toEqual({
118
+ accountName: 'TestAccount',
119
+ description: 'Test',
120
+ marketingLevel: 'PROFESSIONAL',
121
+ opsLevel: 'STARTER',
122
+ serviceLevel: 'ENTERPRISE',
123
+ salesLevel: 'FREE',
124
+ contentLevel: 'ENTERPRISE',
125
+ });
126
+ expect(mockPromptUser).toHaveBeenCalledTimes(3);
127
+ });
128
+ });
129
+ describe('with only name provided', () => {
130
+ it('should skip name prompt but show description and tier prompts', async () => {
131
+ const mockPromptUser = vi.mocked(promptUtils.promptUser);
132
+ mockPromptUser.mockResolvedValueOnce({
133
+ description: 'Prompted description',
134
+ }); // description prompt (name skipped)
135
+ mockPromptUser.mockResolvedValueOnce({
136
+ useDefaultAccountLevels: 'default',
137
+ }); // tier selection
138
+ const result = await createDeveloperTestAccountConfigPrompt({
139
+ name: 'TestAccount',
140
+ });
141
+ expect(result).toEqual({
142
+ accountName: 'TestAccount',
143
+ description: 'Prompted description',
144
+ marketingLevel: 'ENTERPRISE',
145
+ opsLevel: 'ENTERPRISE',
146
+ serviceLevel: 'ENTERPRISE',
147
+ salesLevel: 'ENTERPRISE',
148
+ contentLevel: 'ENTERPRISE',
149
+ });
150
+ expect(mockPromptUser).toHaveBeenCalledTimes(2);
151
+ });
152
+ });
153
+ });