@hubspot/cli 7.7.27-experimental.2 → 7.7.29-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 (130) 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/testAccount/__tests__/importData.test.d.ts +1 -0
  25. package/commands/testAccount/__tests__/importData.test.js +93 -0
  26. package/commands/testAccount/create.js +23 -13
  27. package/commands/testAccount/importData.d.ts +9 -0
  28. package/commands/testAccount/importData.js +61 -0
  29. package/commands/testAccount.js +2 -0
  30. package/lang/en.d.ts +162 -46
  31. package/lang/en.js +177 -59
  32. package/lang/en.lyaml +35 -14
  33. package/lib/__tests__/importData.test.d.ts +1 -0
  34. package/lib/__tests__/importData.test.js +89 -0
  35. package/lib/accountTypes.js +2 -3
  36. package/lib/app/__tests__/migrate.test.js +81 -36
  37. package/lib/app/migrate.d.ts +17 -4
  38. package/lib/app/migrate.js +97 -19
  39. package/lib/constants.d.ts +1 -0
  40. package/lib/constants.js +1 -0
  41. package/lib/hasFeature.d.ts +1 -0
  42. package/lib/hasFeature.js +7 -0
  43. package/lib/importData.d.ts +3 -0
  44. package/lib/importData.js +50 -0
  45. package/lib/mcp/setup.d.ts +3 -5
  46. package/lib/mcp/setup.js +39 -139
  47. package/lib/process.js +15 -4
  48. package/lib/projects/__tests__/AppDevModeInterface.test.js +3 -3
  49. package/lib/projects/__tests__/LocalDevProcess.test.js +5 -95
  50. package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +6 -6
  51. package/lib/projects/__tests__/components.test.js +164 -7
  52. package/lib/projects/__tests__/localDevProjectHelpers.test.d.ts +1 -0
  53. package/lib/projects/__tests__/localDevProjectHelpers.test.js +118 -0
  54. package/lib/projects/add/v3AddComponent.js +16 -4
  55. package/lib/projects/components.d.ts +1 -0
  56. package/lib/projects/components.js +27 -1
  57. package/lib/projects/localDev/AppDevModeInterface.js +35 -3
  58. package/lib/projects/localDev/LocalDevLogger.d.ts +0 -4
  59. package/lib/projects/localDev/LocalDevLogger.js +2 -19
  60. package/lib/projects/localDev/LocalDevManager.js +1 -1
  61. package/lib/projects/localDev/LocalDevProcess.d.ts +1 -2
  62. package/lib/projects/localDev/LocalDevProcess.js +3 -26
  63. package/lib/projects/localDev/LocalDevState.d.ts +6 -7
  64. package/lib/projects/localDev/LocalDevState.js +16 -15
  65. package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +1 -0
  66. package/lib/projects/localDev/LocalDevWebsocketServer.js +17 -2
  67. package/lib/projects/localDev/{helpers.d.ts → helpers/account.d.ts} +1 -7
  68. package/lib/projects/localDev/{helpers.js → helpers/account.js} +44 -144
  69. package/lib/projects/localDev/helpers/project.d.ts +12 -0
  70. package/lib/projects/localDev/helpers/project.js +173 -0
  71. package/lib/projects/urls.d.ts +1 -0
  72. package/lib/projects/urls.js +4 -0
  73. package/lib/prompts/__tests__/createFunctionPrompt.test.d.ts +1 -0
  74. package/lib/prompts/__tests__/createFunctionPrompt.test.js +129 -0
  75. package/lib/prompts/__tests__/createModulePrompt.test.d.ts +1 -0
  76. package/lib/prompts/__tests__/createModulePrompt.test.js +187 -0
  77. package/lib/prompts/__tests__/createTemplatePrompt.test.d.ts +1 -0
  78. package/lib/prompts/__tests__/createTemplatePrompt.test.js +102 -0
  79. package/lib/prompts/confirmImportDataPrompt.d.ts +1 -0
  80. package/lib/prompts/confirmImportDataPrompt.js +12 -0
  81. package/lib/prompts/createFunctionPrompt.d.ts +2 -1
  82. package/lib/prompts/createFunctionPrompt.js +36 -7
  83. package/lib/prompts/createModulePrompt.d.ts +2 -1
  84. package/lib/prompts/createModulePrompt.js +48 -1
  85. package/lib/prompts/createTemplatePrompt.d.ts +3 -24
  86. package/lib/prompts/createTemplatePrompt.js +9 -1
  87. package/lib/prompts/importDataFilePathPrompt.d.ts +1 -0
  88. package/lib/prompts/importDataFilePathPrompt.js +24 -0
  89. package/lib/prompts/importDataTestAccountSelectPrompt.d.ts +3 -0
  90. package/lib/prompts/importDataTestAccountSelectPrompt.js +29 -0
  91. package/lib/prompts/projectDevTargetAccountPrompt.js +1 -0
  92. package/lib/prompts/promptUtils.d.ts +7 -1
  93. package/lib/prompts/promptUtils.js +14 -1
  94. package/lib/ui/__tests__/removeAnsiCodes.test.d.ts +1 -0
  95. package/lib/ui/__tests__/removeAnsiCodes.test.js +84 -0
  96. package/lib/ui/index.js +3 -6
  97. package/lib/ui/removeAnsiCodes.d.ts +1 -0
  98. package/lib/ui/removeAnsiCodes.js +4 -0
  99. package/mcp-server/server.js +2 -1
  100. package/mcp-server/tools/cms/HsCreateModuleTool.d.ts +38 -0
  101. package/mcp-server/tools/cms/HsCreateModuleTool.js +118 -0
  102. package/mcp-server/tools/cms/HsListTool.d.ts +23 -0
  103. package/mcp-server/tools/cms/HsListTool.js +58 -0
  104. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.d.ts +1 -0
  105. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +224 -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 +12 -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/ui/index.d.ts +1 -0
  130. package/ui/index.js +6 -0
@@ -1,4 +1,3 @@
1
- import { Build } from '@hubspot/local-dev-lib/types/Build';
2
1
  import { IntermediateRepresentationNodeLocalDev } from '@hubspot/project-parsing-lib/src/lib/types.js';
3
2
  import { Environment } from '@hubspot/local-dev-lib/types/Config';
4
3
  import { ProjectConfig } from '../../../types/Projects.js';
@@ -12,15 +11,14 @@ declare class LocalDevState {
12
11
  private _projectId;
13
12
  private _projectName;
14
13
  private _debug;
15
- private _deployedBuild?;
16
- private _isGithubLinked;
17
14
  private _projectNodes;
18
15
  private _projectNodesAtLastUpload;
19
16
  private _env;
20
17
  private _listeners;
21
18
  private _appData;
22
19
  private _devServerMessage;
23
- constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectId, projectName, debug, deployedBuild, isGithubLinked, initialProjectNodes, profile, env, }: LocalDevStateConstructorOptions);
20
+ private _uploadWarnings;
21
+ constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectId, projectName, debug, initialProjectNodes, profile, env, }: LocalDevStateConstructorOptions);
24
22
  private runListeners;
25
23
  get targetProjectAccountId(): number;
26
24
  get targetTestingAccountId(): number;
@@ -30,8 +28,6 @@ declare class LocalDevState {
30
28
  get projectId(): number;
31
29
  get projectName(): string;
32
30
  get debug(): boolean;
33
- get deployedBuild(): Build | undefined;
34
- get isGithubLinked(): boolean;
35
31
  get projectNodes(): {
36
32
  [key: string]: IntermediateRepresentationNodeLocalDev;
37
33
  };
@@ -50,7 +46,10 @@ declare class LocalDevState {
50
46
  setAppDataForUid(uid: string, appData: AppLocalDevData): void;
51
47
  get devServerMessage(): string;
52
48
  set devServerMessage(message: LocalDevServerMessage);
53
- addListener<K extends keyof LocalDevState>(key: K, listener: LocalDevStateListener<K>, callOnInit?: boolean): void;
49
+ get uploadWarnings(): Set<string>;
50
+ addUploadWarning(warning: string): void;
51
+ clearUploadWarnings(): void;
52
+ addListener<K extends keyof LocalDevState>(key: K, listener: LocalDevStateListener<K>): void;
54
53
  removeListener<K extends keyof LocalDevState>(key: K, listener: LocalDevStateListener<K>): void;
55
54
  }
56
55
  export default LocalDevState;
@@ -8,15 +8,14 @@ class LocalDevState {
8
8
  _projectId;
9
9
  _projectName;
10
10
  _debug;
11
- _deployedBuild;
12
- _isGithubLinked;
13
11
  _projectNodes;
14
12
  _projectNodesAtLastUpload;
15
13
  _env;
16
14
  _listeners;
17
15
  _appData;
18
16
  _devServerMessage;
19
- constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectId, projectName, debug, deployedBuild, isGithubLinked, initialProjectNodes, profile, env, }) {
17
+ _uploadWarnings;
18
+ constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectId, projectName, debug, initialProjectNodes, profile, env, }) {
20
19
  this._targetProjectAccountId = targetProjectAccountId;
21
20
  this._targetTestingAccountId = targetTestingAccountId;
22
21
  this._profile = profile;
@@ -25,13 +24,12 @@ class LocalDevState {
25
24
  this._projectId = projectId;
26
25
  this._projectName = projectName;
27
26
  this._debug = debug || false;
28
- this._deployedBuild = deployedBuild;
29
- this._isGithubLinked = isGithubLinked;
30
27
  this._projectNodes = initialProjectNodes;
31
28
  this._projectNodesAtLastUpload = initialProjectNodes;
32
29
  this._env = env;
33
30
  this._appData = {};
34
31
  this._devServerMessage = LOCAL_DEV_SERVER_MESSAGE_TYPES.INITIAL;
32
+ this._uploadWarnings = new Set();
35
33
  this._listeners = {};
36
34
  }
37
35
  runListeners(key) {
@@ -63,12 +61,6 @@ class LocalDevState {
63
61
  get debug() {
64
62
  return this._debug;
65
63
  }
66
- get deployedBuild() {
67
- return this._deployedBuild && structuredClone(this._deployedBuild);
68
- }
69
- get isGithubLinked() {
70
- return this._isGithubLinked;
71
- }
72
64
  get projectNodes() {
73
65
  return structuredClone(this._projectNodes);
74
66
  }
@@ -102,14 +94,23 @@ class LocalDevState {
102
94
  this._devServerMessage = message;
103
95
  this.runListeners('devServerMessage');
104
96
  }
105
- addListener(key, listener, callOnInit = false) {
97
+ get uploadWarnings() {
98
+ return this._uploadWarnings;
99
+ }
100
+ addUploadWarning(warning) {
101
+ this.uploadWarnings.add(warning);
102
+ this.runListeners('uploadWarnings');
103
+ }
104
+ clearUploadWarnings() {
105
+ this.uploadWarnings.clear();
106
+ this.runListeners('uploadWarnings');
107
+ }
108
+ addListener(key, listener) {
106
109
  if (!this._listeners[key]) {
107
110
  this._listeners[key] = [];
108
111
  }
109
112
  this._listeners[key].push(listener);
110
- if (callOnInit) {
111
- listener(this[key]);
112
- }
113
+ listener(this[key]);
113
114
  }
114
115
  removeListener(key, listener) {
115
116
  if (this._listeners[key]) {
@@ -13,6 +13,7 @@ declare class LocalDevWebsocketServer {
13
13
  private sendProjectData;
14
14
  private setupProjectNodesListener;
15
15
  private setupAppDataListener;
16
+ private setupUploadWarningsListener;
16
17
  private setupStateListeners;
17
18
  start(): Promise<void>;
18
19
  shutdown(): void;
@@ -4,6 +4,7 @@ import { logger } from '@hubspot/local-dev-lib/logger';
4
4
  import { addLocalStateFlag } from '@hubspot/local-dev-lib/config';
5
5
  import { LOCAL_DEV_UI_MESSAGE_SEND_TYPES, LOCAL_DEV_UI_MESSAGE_RECEIVE_TYPES, LOCAL_DEV_SERVER_MESSAGE_TYPES, CONFIG_LOCAL_STATE_FLAGS, } from '../../constants.js';
6
6
  import { lib } from '../../../lang/en.js';
7
+ import { removeAnsiCodes } from '../../ui/removeAnsiCodes.js';
7
8
  const SERVER_INSTANCE_ID = 'local-dev-ui-websocket-server';
8
9
  const LOG_PREFIX = '[LocalDevWebsocketServer]';
9
10
  class LocalDevWebsocketServer {
@@ -88,7 +89,7 @@ class LocalDevWebsocketServer {
88
89
  data: nodes,
89
90
  });
90
91
  };
91
- this.localDevProcess.addStateListener('projectNodes', listener, true);
92
+ this.localDevProcess.addStateListener('projectNodes', listener);
92
93
  websocket.on('close', () => {
93
94
  this.localDevProcess.removeStateListener('projectNodes', listener);
94
95
  });
@@ -100,14 +101,28 @@ class LocalDevWebsocketServer {
100
101
  data: appData,
101
102
  });
102
103
  };
103
- this.localDevProcess.addStateListener('appData', listener, true);
104
+ this.localDevProcess.addStateListener('appData', listener);
104
105
  websocket.on('close', () => {
105
106
  this.localDevProcess.removeStateListener('appData', listener);
106
107
  });
107
108
  }
109
+ setupUploadWarningsListener(websocket) {
110
+ const listener = (uploadWarnings) => {
111
+ const formattedUploadWarnings = Array.from(uploadWarnings).map(removeAnsiCodes);
112
+ this.sendMessage(websocket, {
113
+ type: LOCAL_DEV_UI_MESSAGE_SEND_TYPES.UPDATE_UPLOAD_WARNINGS,
114
+ data: { uploadWarnings: formattedUploadWarnings },
115
+ });
116
+ };
117
+ this.localDevProcess.addStateListener('uploadWarnings', listener);
118
+ websocket.on('close', () => {
119
+ this.localDevProcess.removeStateListener('uploadWarnings', listener);
120
+ });
121
+ }
108
122
  setupStateListeners(websocket) {
109
123
  this.setupProjectNodesListener(websocket);
110
124
  this.setupAppDataListener(websocket);
125
+ this.setupUploadWarningsListener(websocket);
111
126
  }
112
127
  async start() {
113
128
  const portManagerIsRunning = await isPortManagerServerRunning();
@@ -1,10 +1,7 @@
1
1
  import { CLIAccount } 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
- import { Project } from '@hubspot/local-dev-lib/types/Project';
5
- import { Build } from '@hubspot/local-dev-lib/types/Build';
6
- import { ProjectConfig } from '../../../types/Projects.js';
7
- import { ProjectDevTargetAccountPromptResponse } from '../../prompts/projectDevTargetAccountPrompt.js';
4
+ import { ProjectDevTargetAccountPromptResponse } from '../../../prompts/projectDevTargetAccountPrompt.js';
8
5
  export declare function confirmDefaultAccountIsTarget(accountConfig: CLIAccount): Promise<void>;
9
6
  export declare function checkIfDefaultAccountIsSupported(accountConfig: CLIAccount, hasPublicApps: boolean): Promise<void>;
10
7
  export declare function checkIfParentAccountIsAuthed(accountConfig: CLIAccount): void;
@@ -13,8 +10,5 @@ export declare function suggestRecommendedNestedAccount(accounts: CLIAccount[],
13
10
  export declare function createSandboxForLocalDev(accountId: number, accountConfig: CLIAccount, env: Environment): Promise<number>;
14
11
  export declare function createDeveloperTestAccountForLocalDev(accountId: number, accountConfig: CLIAccount, env: Environment, useV3?: boolean): Promise<number>;
15
12
  export declare function useExistingDevTestAccount(env: Environment, account: DeveloperTestAccount): Promise<void>;
16
- export declare function createNewProjectForLocalDev(projectConfig: ProjectConfig, targetAccountId: number, shouldCreateWithoutConfirmation: boolean, hasPublicApps: boolean): Promise<Project>;
17
- export declare function createInitialBuildForNewProject(projectConfig: ProjectConfig, projectDir: string, targetAccountId: number, sendIR?: boolean, profile?: string): Promise<Build>;
18
- export declare function getAccountHomeUrl(accountId: number): string;
19
13
  export declare function hasSandboxes(account: CLIAccount): Promise<boolean>;
20
14
  export declare function selectAccountTypePrompt(accountConfig: CLIAccount): Promise<string | null>;
@@ -1,41 +1,44 @@
1
- import { HUBSPOT_ACCOUNT_TYPES, HUBSPOT_ACCOUNT_TYPE_STRINGS, } from '@hubspot/local-dev-lib/constants/config';
2
- import { isMissingScopeError, isSpecifiedError, } from '@hubspot/local-dev-lib/errors/index';
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';
4
+ import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
3
5
  import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
4
- import { getAccountConfig, getEnv } from '@hubspot/local-dev-lib/config';
5
- import { createProject } from '@hubspot/local-dev-lib/api/projects';
6
- import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments';
6
+ import { isMissingScopeError } from '@hubspot/local-dev-lib/errors/index';
7
7
  import { PERSONAL_ACCESS_KEY_AUTH_METHOD } from '@hubspot/local-dev-lib/constants/auth';
8
- import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier';
9
8
  import { getSandboxUsageLimits } from '@hubspot/local-dev-lib/api/sandboxHubs';
10
- import { confirmDefaultAccountPrompt, selectSandboxTargetAccountPrompt, selectDeveloperTestTargetAccountPrompt, confirmUseExistingDeveloperTestAccountPrompt, } from '../../prompts/projectDevTargetAccountPrompt.js';
11
- import { confirmPrompt, listPrompt } from '../../prompts/promptUtils.js';
12
- import { validateSandboxUsageLimits, getAvailableSyncTypes, } from '../../sandboxes.js';
13
- import { syncSandbox } from '../../sandboxSync.js';
14
- import { validateDevTestAccountUsageLimits } from '../../developerTestAccounts.js';
15
- import { uiLine, uiAccountDescription } from '../../ui/index.js';
16
- import SpinniesManager from '../../ui/SpinniesManager.js';
17
- import { EXIT_CODES } from '../../enums/exitCodes.js';
18
- import { trackCommandMetadataUsage } from '../../usageTracking.js';
19
- import { isAppDeveloperAccount, isDeveloperTestAccount, isUnifiedAccount, } from '../../accountTypes.js';
20
- import { handleProjectUpload } from '../../projects/upload.js';
21
- import { pollProjectBuildAndDeploy } from '../../projects/buildAndDeploy.js';
22
- import { PROJECT_ERROR_TYPES, PROJECT_BUILD_TEXT, PROJECT_DEPLOY_TEXT, } from '../../constants.js';
23
- import { logError, ApiErrorContext, debugError, } from '../../errorHandlers/index.js';
24
- import { buildSandbox, buildDeveloperTestAccount, saveAccountToConfig, } from '../../buildAccount.js';
25
- import { hubspotAccountNamePrompt } from '../../prompts/accountNamePrompt.js';
26
- import { lib } from '../../../lang/en.js';
27
- import { uiLogger } from '../../ui/logger.js';
9
+ import { uiLogger } from '../../../ui/logger.js';
10
+ import { lib } from '../../../../lang/en.js';
11
+ import { EXIT_CODES } from '../../../enums/exitCodes.js';
12
+ import { confirmDefaultAccountPrompt } from '../../../prompts/projectDevTargetAccountPrompt.js';
13
+ import { isUnifiedAccount } from '../../../accountTypes.js';
14
+ import { isAppDeveloperAccount } from '../../../accountTypes.js';
15
+ import { isDeveloperTestAccount } from '../../../accountTypes.js';
16
+ import { uiAccountDescription } from '../../../ui/index.js';
17
+ import { uiLine } from '../../../ui/index.js';
18
+ import { selectDeveloperTestTargetAccountPrompt } from '../../../prompts/projectDevTargetAccountPrompt.js';
19
+ import { selectSandboxTargetAccountPrompt } from '../../../prompts/projectDevTargetAccountPrompt.js';
20
+ import { validateSandboxUsageLimits } from '../../../sandboxes.js';
21
+ import { logError } from '../../../errorHandlers/index.js';
22
+ import { syncSandbox } from '../../../sandboxSync.js';
23
+ import { getAvailableSyncTypes } from '../../../sandboxes.js';
24
+ import { hubspotAccountNamePrompt } from '../../../prompts/accountNamePrompt.js';
25
+ import { trackCommandMetadataUsage } from '../../../usageTracking.js';
26
+ import { validateDevTestAccountUsageLimits } from '../../../developerTestAccounts.js';
27
+ import { buildSandbox, buildDeveloperTestAccount, saveAccountToConfig, } from '../../../buildAccount.js';
28
+ import { debugError } from '../../../errorHandlers/index.js';
29
+ import { listPrompt } from '../../../prompts/promptUtils.js';
30
+ import { confirmUseExistingDeveloperTestAccountPrompt } from '../../../prompts/projectDevTargetAccountPrompt.js';
28
31
  // If the user passed in the --account flag, confirm they want to use that account as
29
32
  // their target account, otherwise exit
30
33
  export async function confirmDefaultAccountIsTarget(accountConfig) {
31
34
  if (!accountConfig.name || !accountConfig.accountType) {
32
- uiLogger.error(lib.localDevHelpers.confirmDefaultAccountIsTarget.configError);
35
+ uiLogger.error(lib.localDevHelpers.account.confirmDefaultAccountIsTarget.configError);
33
36
  process.exit(EXIT_CODES.ERROR);
34
37
  }
35
38
  uiLogger.log('');
36
39
  const useDefaultAccount = await confirmDefaultAccountPrompt(accountConfig.name, HUBSPOT_ACCOUNT_TYPE_STRINGS[accountConfig.accountType]);
37
40
  if (!useDefaultAccount) {
38
- uiLogger.log(lib.localDevHelpers.confirmDefaultAccountIsTarget
41
+ uiLogger.log(lib.localDevHelpers.account.confirmDefaultAccountIsTarget
39
42
  .declineDefaultAccountExplanation);
40
43
  process.exit(EXIT_CODES.SUCCESS);
41
44
  }
@@ -47,18 +50,18 @@ export async function checkIfDefaultAccountIsSupported(accountConfig, hasPublicA
47
50
  !(isAppDeveloperAccount(accountConfig) ||
48
51
  isDeveloperTestAccount(accountConfig) ||
49
52
  defaultAccountIsUnified)) {
50
- uiLogger.error(lib.localDevHelpers.checkIfDefaultAccountIsSupported.publicApp);
53
+ uiLogger.error(lib.localDevHelpers.account.checkIfDefaultAccountIsSupported.publicApp);
51
54
  process.exit(EXIT_CODES.SUCCESS);
52
55
  }
53
56
  else if (!hasPublicApps && isAppDeveloperAccount(accountConfig)) {
54
- uiLogger.error(lib.localDevHelpers.checkIfDefaultAccountIsSupported.privateApp);
57
+ uiLogger.error(lib.localDevHelpers.account.checkIfDefaultAccountIsSupported.privateApp);
55
58
  process.exit(EXIT_CODES.SUCCESS);
56
59
  }
57
60
  }
58
61
  export function checkIfParentAccountIsAuthed(accountConfig) {
59
62
  if (!accountConfig.parentAccountId ||
60
63
  !getAccountConfig(accountConfig.parentAccountId)) {
61
- uiLogger.error(lib.localDevHelpers.checkIfParentAccountIsAuthed.notAuthedError(accountConfig.parentAccountId || '', uiAccountDescription(getAccountIdentifier(accountConfig))));
64
+ uiLogger.error(lib.localDevHelpers.account.checkIfParentAccountIsAuthed.notAuthedError(accountConfig.parentAccountId || '', uiAccountDescription(getAccountIdentifier(accountConfig))));
62
65
  process.exit(EXIT_CODES.SUCCESS);
63
66
  }
64
67
  }
@@ -66,13 +69,14 @@ export function checkIfParentAccountIsAuthed(accountConfig) {
66
69
  export function checkIfAccountFlagIsSupported(accountConfig, hasPublicApps) {
67
70
  if (hasPublicApps) {
68
71
  if (!isDeveloperTestAccount(accountConfig)) {
69
- uiLogger.error(lib.localDevHelpers.validateAccountOption.invalidPublicAppAccount);
72
+ uiLogger.error(lib.localDevHelpers.account.validateAccountOption
73
+ .invalidPublicAppAccount);
70
74
  process.exit(EXIT_CODES.SUCCESS);
71
75
  }
72
76
  checkIfParentAccountIsAuthed(accountConfig);
73
77
  }
74
78
  else if (isAppDeveloperAccount(accountConfig)) {
75
- uiLogger.error(lib.localDevHelpers.validateAccountOption.invalidPrivateAppAccount);
79
+ uiLogger.error(lib.localDevHelpers.account.validateAccountOption.invalidPrivateAppAccount);
76
80
  process.exit(EXIT_CODES.SUCCESS);
77
81
  }
78
82
  }
@@ -81,11 +85,11 @@ export async function suggestRecommendedNestedAccount(accounts, accountConfig, h
81
85
  uiLogger.log('');
82
86
  uiLine();
83
87
  if (hasPublicApps) {
84
- uiLogger.log(lib.localDevHelpers.validateAccountOption
88
+ uiLogger.log(lib.localDevHelpers.account.validateAccountOption
85
89
  .publicAppNonDeveloperTestAccountWarning);
86
90
  }
87
91
  else {
88
- uiLogger.log(lib.localDevHelpers.validateAccountOption.nonSandboxWarning);
92
+ uiLogger.log(lib.localDevHelpers.account.validateAccountOption.nonSandboxWarning);
89
93
  }
90
94
  uiLine();
91
95
  uiLogger.log('');
@@ -177,7 +181,7 @@ export async function useExistingDevTestAccount(env, account) {
177
181
  const useExistingDevTestAcct = await confirmUseExistingDeveloperTestAccountPrompt(account);
178
182
  if (!useExistingDevTestAcct) {
179
183
  uiLogger.log('');
180
- uiLogger.log(lib.localDevHelpers.confirmDefaultAccountIsTarget
184
+ uiLogger.log(lib.localDevHelpers.account.confirmDefaultAccountIsTarget
181
185
  .declineDefaultAccountExplanation);
182
186
  uiLogger.log('');
183
187
  process.exit(EXIT_CODES.SUCCESS);
@@ -185,110 +189,6 @@ export async function useExistingDevTestAccount(env, account) {
185
189
  const devTestAcctConfigName = await saveAccountToConfig(account.id, account.accountName, env);
186
190
  uiLogger.success(lib.developerTestAccount.create.success.configFileUpdated(devTestAcctConfigName, PERSONAL_ACCESS_KEY_AUTH_METHOD.name));
187
191
  }
188
- // Prompt the user to create a new project if one doesn't exist on their target account
189
- export async function createNewProjectForLocalDev(projectConfig, targetAccountId, shouldCreateWithoutConfirmation, hasPublicApps) {
190
- // Create the project without prompting if this is a newly created sandbox
191
- let shouldCreateProject = shouldCreateWithoutConfirmation;
192
- if (!shouldCreateProject) {
193
- const explanationLangFunction = hasPublicApps
194
- ? lib.localDevHelpers.createNewProjectForLocalDev
195
- .publicAppProjectMustExistExplanation
196
- : lib.localDevHelpers.createNewProjectForLocalDev
197
- .projectMustExistExplanation;
198
- const explanationString = explanationLangFunction(projectConfig.name, targetAccountId);
199
- uiLogger.log('');
200
- uiLine();
201
- uiLogger.log(explanationString);
202
- uiLine();
203
- shouldCreateProject = await confirmPrompt(lib.localDevHelpers.createNewProjectForLocalDev.createProject(projectConfig.name, uiAccountDescription(targetAccountId)));
204
- }
205
- if (shouldCreateProject) {
206
- SpinniesManager.add('createProject', {
207
- text: lib.localDevHelpers.createNewProjectForLocalDev.creatingProject(projectConfig.name, uiAccountDescription(targetAccountId)),
208
- });
209
- try {
210
- const { data: project } = await createProject(targetAccountId, projectConfig.name);
211
- SpinniesManager.succeed('createProject', {
212
- text: lib.localDevHelpers.createNewProjectForLocalDev.createdProject(projectConfig.name, uiAccountDescription(targetAccountId)),
213
- succeedColor: 'white',
214
- });
215
- return project;
216
- }
217
- catch (err) {
218
- SpinniesManager.fail('createProject');
219
- uiLogger.log(lib.localDevHelpers.createNewProjectForLocalDev.failedToCreateProject);
220
- process.exit(EXIT_CODES.ERROR);
221
- }
222
- }
223
- else {
224
- // We cannot continue if the project does not exist in the target account
225
- uiLogger.log('');
226
- uiLogger.log(lib.localDevHelpers.createNewProjectForLocalDev.choseNotToCreateProject);
227
- process.exit(EXIT_CODES.SUCCESS);
228
- }
229
- }
230
- function projectUploadCallback(accountId, projectConfig, tempFile, buildId) {
231
- if (!buildId) {
232
- uiLogger.error(lib.localDevHelpers.createInitialBuildForNewProject.genericError);
233
- process.exit(EXIT_CODES.ERROR);
234
- }
235
- return pollProjectBuildAndDeploy(accountId, projectConfig, tempFile, buildId, true);
236
- }
237
- // Create an initial build if the project was newly created in the account
238
- // Return the newly deployed build
239
- export async function createInitialBuildForNewProject(projectConfig, projectDir, targetAccountId, sendIR, profile) {
240
- const { result: initialUploadResult, uploadError } = await handleProjectUpload({
241
- accountId: targetAccountId,
242
- projectConfig,
243
- projectDir,
244
- callbackFunc: projectUploadCallback,
245
- uploadMessage: lib.localDevHelpers.createInitialBuildForNewProject
246
- .initialUploadMessage,
247
- forceCreate: true,
248
- skipValidation: true,
249
- sendIR,
250
- profile,
251
- });
252
- if (uploadError) {
253
- if (isSpecifiedError(uploadError, {
254
- subCategory: PROJECT_ERROR_TYPES.PROJECT_LOCKED,
255
- })) {
256
- uiLogger.log('');
257
- uiLogger.error(lib.localDevHelpers.createInitialBuildForNewProject.projectLockedError);
258
- uiLogger.log('');
259
- }
260
- else {
261
- logError(uploadError, new ApiErrorContext({
262
- accountId: targetAccountId,
263
- projectName: projectConfig.name,
264
- }));
265
- }
266
- process.exit(EXIT_CODES.ERROR);
267
- }
268
- if (!initialUploadResult?.succeeded) {
269
- let subTasks = [];
270
- if (initialUploadResult?.buildResult.status === 'FAILURE') {
271
- subTasks =
272
- initialUploadResult.buildResult[PROJECT_BUILD_TEXT.SUBTASK_KEY];
273
- }
274
- else if (initialUploadResult?.deployResult?.status === 'FAILURE') {
275
- subTasks =
276
- initialUploadResult.deployResult[PROJECT_DEPLOY_TEXT.SUBTASK_KEY];
277
- }
278
- const failedSubTasks = subTasks.filter(task => task.status === 'FAILURE');
279
- uiLogger.log('');
280
- failedSubTasks.forEach(failedSubTask => {
281
- uiLogger.error(failedSubTask.errorMessage);
282
- });
283
- uiLogger.log('');
284
- process.exit(EXIT_CODES.ERROR);
285
- }
286
- return initialUploadResult.buildResult;
287
- }
288
- export function getAccountHomeUrl(accountId) {
289
- const baseUrl = getHubSpotWebsiteOrigin(getEnv(accountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD);
290
- return `${baseUrl}/home?portalId=${accountId}`;
291
- }
292
192
  export async function hasSandboxes(account) {
293
193
  const accountId = getAccountIdentifier(account);
294
194
  if (!accountId) {
@@ -306,25 +206,25 @@ export async function hasSandboxes(account) {
306
206
  // Top level prompt to choose the type of account to test on
307
207
  export async function selectAccountTypePrompt(accountConfig) {
308
208
  const hasAccessToSandboxes = await hasSandboxes(accountConfig);
309
- const result = await listPrompt(lib.localDevHelpers.selectAccountTypePrompt.message, {
209
+ const accountId = getAccountIdentifier(accountConfig);
210
+ const result = await listPrompt(lib.localDevHelpers.account.selectAccountTypePrompt.message, {
310
211
  choices: [
311
212
  {
312
- name: lib.localDevHelpers.selectAccountTypePrompt
213
+ name: lib.localDevHelpers.account.selectAccountTypePrompt
313
214
  .developerTestAccountOption,
314
215
  value: HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST,
315
216
  },
316
217
  {
317
- name: lib.localDevHelpers.selectAccountTypePrompt
218
+ name: lib.localDevHelpers.account.selectAccountTypePrompt
318
219
  .sandboxAccountOption,
319
220
  value: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
320
221
  disabled: !hasAccessToSandboxes
321
- ? lib.localDevHelpers.selectAccountTypePrompt
222
+ ? lib.localDevHelpers.account.selectAccountTypePrompt
322
223
  .sandboxAccountOptionDisabled
323
224
  : false,
324
225
  },
325
226
  {
326
- name: lib.localDevHelpers.selectAccountTypePrompt
327
- .productionAccountOption,
227
+ name: lib.localDevHelpers.account.selectAccountTypePrompt.productionAccountOption(accountId),
328
228
  value: null,
329
229
  },
330
230
  ],
@@ -0,0 +1,12 @@
1
+ import { Build } from '@hubspot/local-dev-lib/types/Build';
2
+ import { Project } from '@hubspot/local-dev-lib/types/Project';
3
+ import { IntermediateRepresentationNodeLocalDev } from '@hubspot/project-parsing-lib/src/lib/types.js';
4
+ import { ProjectConfig } from '../../../../types/Projects.js';
5
+ export declare function createNewProjectForLocalDev(projectConfig: ProjectConfig, targetAccountId: number, shouldCreateWithoutConfirmation: boolean, hasPublicApps: boolean): Promise<Project>;
6
+ export declare function createInitialBuildForNewProject(projectConfig: ProjectConfig, projectDir: string, targetAccountId: number, sendIR?: boolean, profile?: string): Promise<Build>;
7
+ export declare function compareLocalProjectToDeployed(projectConfig: ProjectConfig, accountId: number, deployedBuildId: number | undefined, localProjectNodes: {
8
+ [key: string]: IntermediateRepresentationNodeLocalDev;
9
+ }): Promise<void>;
10
+ export declare function isDeployedProjectUpToDateWithLocal(projectConfig: ProjectConfig, accountId: number, deployedBuildId: number, localProjectNodes: {
11
+ [key: string]: IntermediateRepresentationNodeLocalDev;
12
+ }): Promise<boolean>;
@@ -0,0 +1,173 @@
1
+ import fs from 'fs-extra';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { createProject } from '@hubspot/local-dev-lib/api/projects';
5
+ import { downloadProject } from '@hubspot/local-dev-lib/api/projects';
6
+ import { extractZipArchive } from '@hubspot/local-dev-lib/archive';
7
+ import { sanitizeFileName } from '@hubspot/local-dev-lib/path';
8
+ import { isDeepEqual } from '@hubspot/local-dev-lib/isDeepEqual';
9
+ import { translate } from '@hubspot/project-parsing-lib';
10
+ import { isSpecifiedError } from '@hubspot/local-dev-lib/errors/index';
11
+ import { PROJECT_ERROR_TYPES, PROJECT_BUILD_TEXT, PROJECT_DEPLOY_TEXT, } from '../../../constants.js';
12
+ import { lib } from '../../../../lang/en.js';
13
+ import { uiLogger } from '../../../ui/logger.js';
14
+ import { uiLine } from '../../../ui/index.js';
15
+ import { confirmPrompt } from '../../../prompts/promptUtils.js';
16
+ import { uiAccountDescription } from '../../../ui/index.js';
17
+ import SpinniesManager from '../../../ui/SpinniesManager.js';
18
+ import { EXIT_CODES } from '../../../enums/exitCodes.js';
19
+ import { handleProjectUpload } from '../../upload.js';
20
+ import { pollProjectBuildAndDeploy } from '../../buildAndDeploy.js';
21
+ import { logError } from '../../../errorHandlers/index.js';
22
+ import { ApiErrorContext } from '../../../errorHandlers/index.js';
23
+ // Prompt the user to create a new project if one doesn't exist on their target account
24
+ export async function createNewProjectForLocalDev(projectConfig, targetAccountId, shouldCreateWithoutConfirmation, hasPublicApps) {
25
+ // Create the project without prompting if this is a newly created sandbox
26
+ let shouldCreateProject = shouldCreateWithoutConfirmation;
27
+ if (!shouldCreateProject) {
28
+ const explanationLangFunction = hasPublicApps
29
+ ? lib.localDevHelpers.project.createNewProjectForLocalDev
30
+ .publicAppProjectMustExistExplanation
31
+ : lib.localDevHelpers.project.createNewProjectForLocalDev
32
+ .projectMustExistExplanation;
33
+ const explanationString = explanationLangFunction(projectConfig.name, targetAccountId);
34
+ uiLogger.log('');
35
+ uiLine();
36
+ uiLogger.log(explanationString);
37
+ uiLine();
38
+ shouldCreateProject = await confirmPrompt(lib.localDevHelpers.project.createNewProjectForLocalDev.createProject(projectConfig.name, uiAccountDescription(targetAccountId)));
39
+ }
40
+ if (shouldCreateProject) {
41
+ SpinniesManager.add('createProject', {
42
+ text: lib.localDevHelpers.project.createNewProjectForLocalDev.creatingProject(projectConfig.name, uiAccountDescription(targetAccountId)),
43
+ });
44
+ try {
45
+ const { data: project } = await createProject(targetAccountId, projectConfig.name);
46
+ SpinniesManager.succeed('createProject', {
47
+ text: lib.localDevHelpers.project.createNewProjectForLocalDev.createdProject(projectConfig.name, uiAccountDescription(targetAccountId)),
48
+ succeedColor: 'white',
49
+ });
50
+ return project;
51
+ }
52
+ catch (err) {
53
+ SpinniesManager.fail('createProject');
54
+ uiLogger.log(lib.localDevHelpers.project.createNewProjectForLocalDev
55
+ .failedToCreateProject);
56
+ process.exit(EXIT_CODES.ERROR);
57
+ }
58
+ }
59
+ else {
60
+ // We cannot continue if the project does not exist in the target account
61
+ uiLogger.log('');
62
+ uiLogger.log(lib.localDevHelpers.project.createNewProjectForLocalDev
63
+ .choseNotToCreateProject);
64
+ process.exit(EXIT_CODES.SUCCESS);
65
+ }
66
+ }
67
+ function projectUploadCallback(accountId, projectConfig, tempFile, buildId) {
68
+ if (!buildId) {
69
+ uiLogger.error(lib.localDevHelpers.project.createInitialBuildForNewProject.genericError);
70
+ process.exit(EXIT_CODES.ERROR);
71
+ }
72
+ return pollProjectBuildAndDeploy(accountId, projectConfig, tempFile, buildId, true);
73
+ }
74
+ // Create an initial build if the project was newly created in the account
75
+ // Return the newly deployed build
76
+ export async function createInitialBuildForNewProject(projectConfig, projectDir, targetAccountId, sendIR, profile) {
77
+ const { result: initialUploadResult, uploadError } = await handleProjectUpload({
78
+ accountId: targetAccountId,
79
+ projectConfig,
80
+ projectDir,
81
+ callbackFunc: projectUploadCallback,
82
+ uploadMessage: lib.localDevHelpers.project.createInitialBuildForNewProject
83
+ .initialUploadMessage,
84
+ forceCreate: true,
85
+ skipValidation: true,
86
+ sendIR,
87
+ profile,
88
+ });
89
+ if (uploadError) {
90
+ if (isSpecifiedError(uploadError, {
91
+ subCategory: PROJECT_ERROR_TYPES.PROJECT_LOCKED,
92
+ })) {
93
+ uiLogger.log('');
94
+ uiLogger.error(lib.localDevHelpers.project.createInitialBuildForNewProject
95
+ .projectLockedError);
96
+ uiLogger.log('');
97
+ }
98
+ else {
99
+ logError(uploadError, new ApiErrorContext({
100
+ accountId: targetAccountId,
101
+ projectName: projectConfig.name,
102
+ }));
103
+ }
104
+ process.exit(EXIT_CODES.ERROR);
105
+ }
106
+ if (!initialUploadResult?.succeeded) {
107
+ let subTasks = [];
108
+ if (initialUploadResult?.buildResult.status === 'FAILURE') {
109
+ subTasks =
110
+ initialUploadResult.buildResult[PROJECT_BUILD_TEXT.SUBTASK_KEY];
111
+ }
112
+ else if (initialUploadResult?.deployResult?.status === 'FAILURE') {
113
+ subTasks =
114
+ initialUploadResult.deployResult[PROJECT_DEPLOY_TEXT.SUBTASK_KEY];
115
+ }
116
+ const failedSubTasks = subTasks.filter(task => task.status === 'FAILURE');
117
+ uiLogger.log('');
118
+ failedSubTasks.forEach(failedSubTask => {
119
+ uiLogger.error(failedSubTask.errorMessage);
120
+ });
121
+ uiLogger.log('');
122
+ process.exit(EXIT_CODES.ERROR);
123
+ }
124
+ return initialUploadResult.buildResult;
125
+ }
126
+ export async function compareLocalProjectToDeployed(projectConfig, accountId, deployedBuildId, localProjectNodes) {
127
+ uiLogger.log('');
128
+ if (!deployedBuildId) {
129
+ uiLogger.error(lib.localDevHelpers.project.compareLocalProjectToDeployed.noDeployedBuild(projectConfig.name, uiAccountDescription(accountId)));
130
+ process.exit(EXIT_CODES.SUCCESS);
131
+ }
132
+ SpinniesManager.add('compareLocalProjectToDeployed', {
133
+ text: lib.localDevHelpers.project.compareLocalProjectToDeployed.checking,
134
+ });
135
+ const isUpToDate = await isDeployedProjectUpToDateWithLocal(projectConfig, accountId, deployedBuildId, localProjectNodes);
136
+ if (isUpToDate) {
137
+ SpinniesManager.succeed('compareLocalProjectToDeployed', {
138
+ text: lib.localDevHelpers.project.compareLocalProjectToDeployed.upToDate,
139
+ });
140
+ }
141
+ else {
142
+ SpinniesManager.fail('compareLocalProjectToDeployed', {
143
+ text: lib.localDevHelpers.project.compareLocalProjectToDeployed
144
+ .notUpToDate,
145
+ });
146
+ uiLogger.log('');
147
+ uiLogger.log(lib.localDevHelpers.project.compareLocalProjectToDeployed
148
+ .notUpToDateExplanation);
149
+ process.exit(EXIT_CODES.SUCCESS);
150
+ }
151
+ }
152
+ export async function isDeployedProjectUpToDateWithLocal(projectConfig, accountId, deployedBuildId, localProjectNodes) {
153
+ let tempDir = null;
154
+ try {
155
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hubspot-project-compare-'));
156
+ const { data: zippedProject } = await downloadProject(accountId, projectConfig.name, deployedBuildId);
157
+ const extractedProjectPath = path.join(tempDir, sanitizeFileName(projectConfig.name));
158
+ await extractZipArchive(zippedProject, sanitizeFileName(projectConfig.name), tempDir, { includesRootDir: false, hideLogs: true });
159
+ const deployedProjectSourceDir = path.join(extractedProjectPath, projectConfig.srcDir);
160
+ const { intermediateNodesIndexedByUid: deployedProjectNodes } = await translate({
161
+ projectSourceDir: deployedProjectSourceDir,
162
+ platformVersion: projectConfig.platformVersion,
163
+ accountId: accountId,
164
+ }, {});
165
+ return isDeepEqual(localProjectNodes, deployedProjectNodes, ['localDev']);
166
+ }
167
+ finally {
168
+ // Clean up temporary directory
169
+ if (tempDir && (await fs.pathExists(tempDir))) {
170
+ await fs.remove(tempDir);
171
+ }
172
+ }
173
+ }