@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
@@ -0,0 +1,118 @@
1
+ import fs from 'fs-extra';
2
+ import os from 'os';
3
+ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
4
+ import { downloadProject } from '@hubspot/local-dev-lib/api/projects';
5
+ import { extractZipArchive } from '@hubspot/local-dev-lib/archive';
6
+ import { isDeepEqual } from '@hubspot/local-dev-lib/isDeepEqual';
7
+ import { translate } from '@hubspot/project-parsing-lib';
8
+ import { isDeployedProjectUpToDateWithLocal } from '../localDev/helpers/project.js';
9
+ // Mock all external dependencies
10
+ vi.mock('@hubspot/local-dev-lib/api/projects');
11
+ vi.mock('@hubspot/local-dev-lib/archive');
12
+ vi.mock('@hubspot/project-parsing-lib');
13
+ vi.mock('@hubspot/local-dev-lib/isDeepEqual');
14
+ vi.mock('fs-extra');
15
+ vi.mock('os');
16
+ vi.mock('../../utils/isDeepEqual.js');
17
+ describe('isDeployedProjectUpToDateWithLocal', () => {
18
+ const mockProjectName = 'test-project';
19
+ const mockAccountId = 123456;
20
+ const mockBuildId = 789;
21
+ const mockProjectConfig = {
22
+ name: mockProjectName,
23
+ srcDir: 'src',
24
+ platformVersion: '1.0.0',
25
+ };
26
+ const mockLocalNode = {
27
+ uid: 'component1',
28
+ componentType: 'APP',
29
+ localDev: {
30
+ componentRoot: '/local/path',
31
+ componentConfigPath: '/local/path/config.json',
32
+ configUpdatedSinceLastUpload: false,
33
+ },
34
+ componentDeps: {},
35
+ metaFilePath: '/local/path',
36
+ config: { name: 'Component 1' },
37
+ files: [],
38
+ };
39
+ const mockLocalProjectNodes = {
40
+ component1: mockLocalNode,
41
+ };
42
+ const mockTempDir = '/tmp/test-temp-dir';
43
+ const mockZippedProject = Buffer.from('fake-zip-data');
44
+ beforeEach(() => {
45
+ vi.clearAllMocks();
46
+ // Mock fs.mkdtemp
47
+ fs.mkdtemp.mockResolvedValue(mockTempDir);
48
+ // Mock fs.pathExists
49
+ fs.pathExists.mockResolvedValue(true);
50
+ // Mock fs.remove
51
+ fs.remove.mockResolvedValue(undefined);
52
+ // Mock os.tmpdir
53
+ os.tmpdir.mockReturnValue('/tmp');
54
+ });
55
+ afterEach(() => {
56
+ vi.restoreAllMocks();
57
+ });
58
+ describe('when projects are identical', () => {
59
+ it('should return true for identical projects', async () => {
60
+ // Mock downloadProject
61
+ downloadProject.mockResolvedValue({
62
+ data: mockZippedProject,
63
+ });
64
+ // Mock extractZipArchive
65
+ extractZipArchive.mockResolvedValue(undefined);
66
+ // Mock translate to return identical nodes
67
+ translate.mockResolvedValue({
68
+ intermediateNodesIndexedByUid: mockLocalProjectNodes,
69
+ });
70
+ // Mock isDeepEqual to return true for identical projects
71
+ isDeepEqual.mockReturnValue(true);
72
+ const result = await isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes);
73
+ expect(result).toBe(true);
74
+ expect(isDeepEqual).toHaveBeenCalledWith(mockLocalProjectNodes, mockLocalProjectNodes, ['localDev']);
75
+ expect(fs.remove).toHaveBeenCalledWith(mockTempDir);
76
+ });
77
+ });
78
+ describe('when projects are different', () => {
79
+ it('should return false for different projects', async () => {
80
+ // Mock downloadProject
81
+ downloadProject.mockResolvedValue({
82
+ data: mockZippedProject,
83
+ });
84
+ // Mock extractZipArchive
85
+ extractZipArchive.mockResolvedValue(undefined);
86
+ // Mock translate to return different nodes
87
+ const differentDeployedNodes = {};
88
+ translate.mockResolvedValue({
89
+ intermediateNodesIndexedByUid: differentDeployedNodes,
90
+ });
91
+ // Mock isDeepEqual to return false for different projects
92
+ isDeepEqual.mockReturnValue(false);
93
+ const result = await isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes);
94
+ expect(result).toBe(false);
95
+ expect(isDeepEqual).toHaveBeenCalledWith(mockLocalProjectNodes, differentDeployedNodes, ['localDev']);
96
+ });
97
+ });
98
+ describe('error handling', () => {
99
+ it('should clean up temp directory even when errors occur', async () => {
100
+ // Mock downloadProject to throw an error after temp dir is created
101
+ downloadProject.mockRejectedValue(new Error('Download Error'));
102
+ await expect(isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes)).rejects.toThrow('Download Error');
103
+ expect(fs.remove).toHaveBeenCalledWith(mockTempDir);
104
+ });
105
+ it('should handle translateForLocalDev errors', async () => {
106
+ // Mock downloadProject
107
+ downloadProject.mockResolvedValue({
108
+ data: mockZippedProject,
109
+ });
110
+ // Mock extractZipArchive
111
+ extractZipArchive.mockResolvedValue(undefined);
112
+ // Mock translate to throw an error
113
+ translate.mockRejectedValue(new Error('Translation Error'));
114
+ await expect(isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes)).rejects.toThrow('Translation Error');
115
+ expect(fs.remove).toHaveBeenCalledWith(mockTempDir);
116
+ });
117
+ });
118
+ });
@@ -6,7 +6,7 @@ import path from 'path';
6
6
  import fs from 'fs';
7
7
  import { projectAddPromptV3 } from '../../prompts/projectAddPrompt.js';
8
8
  import { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, DEFAULT_PROJECT_TEMPLATE_BRANCH, } from '../../constants.js';
9
- import { handleComponentCollision } from '../components.js';
9
+ import { updateHsMetaFilesWithAutoGeneratedFields, handleComponentCollision, } from '../components.js';
10
10
  import { getProjectMetadata, } from '@hubspot/project-parsing-lib/src/lib/project.js';
11
11
  import { AppKey } from '@hubspot/project-parsing-lib/src/lib/constants.js';
12
12
  import { cloneGithubRepo } from '@hubspot/local-dev-lib/github';
@@ -20,10 +20,10 @@ export async function v3AddComponent(args, projectDir, projectConfig) {
20
20
  throw new Error(commands.project.add.error.failedToFetchComponentList);
21
21
  }
22
22
  const projectSrcDirectory = path.join(projectDir, projectConfig.srcDir);
23
- const projectMetadata = await getProjectMetadata(projectSrcDirectory);
23
+ const currentProjectMetadata = await getProjectMetadata(projectSrcDirectory);
24
24
  let derivedAuthType;
25
25
  let derivedDistribution;
26
- const appsMetadata = projectMetadata.components[AppKey];
26
+ const appsMetadata = currentProjectMetadata.components[AppKey];
27
27
  const shouldCreateApp = appsMetadata.count === 0;
28
28
  if (shouldCreateApp) {
29
29
  const { authType, distribution } = await createV3App(args.auth, args.distribution);
@@ -45,7 +45,7 @@ export async function v3AddComponent(args, projectDir, projectConfig) {
45
45
  derivedDistribution = apps[0].config?.distribution;
46
46
  derivedAuthType = apps[0].config?.auth?.type;
47
47
  }
48
- const componentTemplateChoices = calculateComponentTemplateChoices(components, derivedAuthType, derivedDistribution, projectMetadata);
48
+ const componentTemplateChoices = calculateComponentTemplateChoices(components, derivedAuthType, derivedDistribution, currentProjectMetadata);
49
49
  const projectAddPromptResponse = await projectAddPromptV3(componentTemplateChoices, args.features);
50
50
  try {
51
51
  const components = projectAddPromptResponse.componentTemplate?.map((componentTemplate) => {
@@ -71,6 +71,18 @@ export async function v3AddComponent(args, projectDir, projectConfig) {
71
71
  branch: DEFAULT_PROJECT_TEMPLATE_BRANCH,
72
72
  handleCollision: handleComponentCollision,
73
73
  });
74
+ const updatedProjectMetadata = await getProjectMetadata(projectSrcDirectory);
75
+ const newHsMetaFiles = updatedProjectMetadata.hsMetaFiles.filter(hsMetaFile => !currentProjectMetadata.hsMetaFiles.includes(hsMetaFile));
76
+ const existingUids = currentProjectMetadata.hsMetaFiles.map(hsMetaFile => {
77
+ try {
78
+ const { uid } = JSON.parse(fs.readFileSync(hsMetaFile, 'utf8'));
79
+ return uid;
80
+ }
81
+ catch (err) {
82
+ return '';
83
+ }
84
+ });
85
+ updateHsMetaFilesWithAutoGeneratedFields(projectConfig.name, newHsMetaFiles, existingUids);
74
86
  uiLogger.success(commands.project.add.success(projectAddPromptResponse.componentTemplate
75
87
  .map(template => `'${template.label}'`)
76
88
  .join(', '), projectAddPromptResponse.componentTemplate.length > 1));
@@ -1,2 +1,3 @@
1
1
  import { Collision } from '@hubspot/local-dev-lib/types/Archive';
2
2
  export declare function handleComponentCollision({ dest, src, collisions }: Collision): void;
3
+ export declare function updateHsMetaFilesWithAutoGeneratedFields(projectName: string, hsMetaFilePaths: string[], existingUids?: string[]): void;
@@ -1,6 +1,9 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs';
3
- import { metafileExtension } from '@hubspot/project-parsing-lib';
3
+ import { coerceToValidUid, metafileExtension, } from '@hubspot/project-parsing-lib';
4
+ import { uiLogger } from '../ui/logger.js';
5
+ import { AppKey } from '@hubspot/project-parsing-lib/src/lib/constants.js';
6
+ import { lib } from '../../lang/en.js';
4
7
  // Handles a collision between component source files
5
8
  export function handleComponentCollision({ dest, src, collisions }) {
6
9
  const hsMetaFiles = [];
@@ -74,3 +77,26 @@ function handlePackageJsonCollisions(dest, src, packageJsonFiles) {
74
77
  fs.writeFileSync(path.join(dest, file), JSON.stringify(existingPackageJsonContents, null, 2));
75
78
  });
76
79
  }
80
+ export function updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths, existingUids = []) {
81
+ uiLogger.log('');
82
+ uiLogger.log(lib.projects.updateHsMetaFilesWithAutoGeneratedFields.header);
83
+ 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;
90
+ }
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));
98
+ }
99
+ fs.writeFileSync(hsMetaFile, JSON.stringify(component, null, 2));
100
+ }
101
+ uiLogger.log('');
102
+ }
@@ -14,6 +14,7 @@ import { lib } from '../../../lang/en.js';
14
14
  import { uiLogger } from '../../ui/logger.js';
15
15
  import { getOauthAppInstallUrl, getStaticAuthAppInstallUrl, } from '../../app/urls.js';
16
16
  import { isDeveloperTestAccount, isSandbox } from '../../accountTypes.js';
17
+ import SpinniesManager from '../../ui/SpinniesManager.js';
17
18
  class AppDevModeInterface {
18
19
  localDevState;
19
20
  localDevLogger;
@@ -86,7 +87,21 @@ class AppDevModeInterface {
86
87
  });
87
88
  }
88
89
  async fetchAppData() {
89
- const { data: { results: portalApps }, } = await fetchPublicAppsForPortal(this.localDevState.targetProjectAccountId);
90
+ SpinniesManager.add('fetchAppData', {
91
+ text: lib.AppDevModeInterface.fetchAppData.checking(this.appNode?.config.name || ''),
92
+ });
93
+ let portalApps = [];
94
+ try {
95
+ const { data: { results }, } = await fetchPublicAppsForPortal(this.localDevState.targetProjectAccountId);
96
+ portalApps = results;
97
+ }
98
+ catch (e) {
99
+ SpinniesManager.fail('fetchAppData', {
100
+ text: lib.AppDevModeInterface.fetchAppData.error,
101
+ });
102
+ logError(e);
103
+ process.exit(EXIT_CODES.ERROR);
104
+ }
90
105
  const appData = portalApps.find(({ sourceId }) => sourceId === this.appNode?.uid);
91
106
  if (!appData) {
92
107
  return;
@@ -105,15 +120,18 @@ class AppDevModeInterface {
105
120
  if (!this.appData || !this.marketplaceAppInstalls) {
106
121
  return;
107
122
  }
123
+ SpinniesManager.fail('fetchAppData', {
124
+ text: lib.AppDevModeInterface.fetchAppData.activeInstallations(this.appNode?.config.name || '', this.marketplaceAppInstalls),
125
+ failColor: 'yellow',
126
+ });
108
127
  uiLine();
109
- uiLogger.warn(lib.LocalDevManager.activeInstallWarning.installCount(this.appData.name, this.marketplaceAppInstalls));
110
128
  uiLogger.log(lib.LocalDevManager.activeInstallWarning.explanation);
111
129
  uiLine();
112
130
  const proceed = await confirmPrompt(lib.LocalDevManager.activeInstallWarning.confirmationPrompt, { defaultAnswer: false });
113
131
  if (!proceed) {
114
132
  process.exit(EXIT_CODES.SUCCESS);
115
133
  }
116
- this.localDevLogger.addUploadWarning(lib.AppDevModeInterface.defaultMarketplaceAppWarning(this.marketplaceAppInstalls));
134
+ this.localDevState.addUploadWarning(lib.AppDevModeInterface.defaultMarketplaceAppWarning(this.marketplaceAppInstalls));
117
135
  }
118
136
  async autoInstallStaticAuthApp() {
119
137
  const shouldInstall = await installAppAutoPrompt();
@@ -177,8 +195,22 @@ class AppDevModeInterface {
177
195
  }
178
196
  const { needsInstall, isReinstall } = await this.checkTestAccountAppInstallation();
179
197
  if (needsInstall) {
198
+ if (SpinniesManager.pick('fetchAppData')) {
199
+ SpinniesManager.fail('fetchAppData', {
200
+ text: lib.AppDevModeInterface.fetchAppData.notInstalled(this.appNode.config.name, this.localDevState.targetTestingAccountId),
201
+ failColor: 'white',
202
+ });
203
+ }
180
204
  await this.installAppOrOpenInstallUrl(isReinstall || false);
181
205
  }
206
+ else {
207
+ if (SpinniesManager.pick('fetchAppData')) {
208
+ SpinniesManager.succeed('fetchAppData', {
209
+ text: lib.AppDevModeInterface.fetchAppData.success(this.appNode.config.name, this.localDevState.targetTestingAccountId),
210
+ });
211
+ }
212
+ uiLogger.log('');
213
+ }
182
214
  }
183
215
  catch (e) {
184
216
  logError(e);
@@ -2,20 +2,16 @@ import LocalDevState from './LocalDevState.js';
2
2
  declare class LocalDevLogger {
3
3
  private state;
4
4
  private mostRecentUploadWarning;
5
- private uploadWarnings;
6
5
  constructor(state: LocalDevState);
7
6
  private logUploadInstructions;
8
7
  private handleError;
9
8
  getUploadCommand(): string;
10
9
  uploadWarning(): void;
11
- addUploadWarning(warning: string): void;
12
- clearUploadWarnings(): void;
13
10
  missingComponentsWarning(components: string[]): void;
14
11
  fileChangeError(e: unknown): void;
15
12
  devServerSetupError(e: unknown): void;
16
13
  devServerStartError(e: unknown): void;
17
14
  devServerCleanupError(e: unknown): void;
18
- noDeployedBuild(): void;
19
15
  resetSpinnies(): void;
20
16
  startupMessage(): void;
21
17
  cleanupStart(): void;
@@ -11,11 +11,9 @@ import { CONFIG_LOCAL_STATE_FLAGS } from '../../constants.js';
11
11
  class LocalDevLogger {
12
12
  state;
13
13
  mostRecentUploadWarning;
14
- uploadWarnings;
15
14
  constructor(state) {
16
15
  this.state = state;
17
16
  this.mostRecentUploadWarning = null;
18
- this.uploadWarnings = new Set();
19
17
  }
20
18
  logUploadInstructions(warning) {
21
19
  uiLogger.log('');
@@ -23,12 +21,7 @@ class LocalDevLogger {
23
21
  uiLogger.log('');
24
22
  uiLogger.log(lib.LocalDevManager.uploadWarning.instructionsHeader);
25
23
  uiLogger.log(lib.LocalDevManager.uploadWarning.stopDev);
26
- if (this.state.isGithubLinked) {
27
- uiLogger.log(lib.LocalDevManager.uploadWarning.pushToGithub);
28
- }
29
- else {
30
- uiLogger.log(lib.LocalDevManager.uploadWarning.runUpload(this.getUploadCommand()));
31
- }
24
+ uiLogger.log(lib.LocalDevManager.uploadWarning.runUpload(this.getUploadCommand()));
32
25
  uiLogger.log(lib.LocalDevManager.uploadWarning.restartDev);
33
26
  }
34
27
  handleError(e, langFunction) {
@@ -47,7 +40,7 @@ class LocalDevLogger {
47
40
  uploadWarning() {
48
41
  // At the moment, there is only one additional warning. We may need to do this in a
49
42
  // more robust way in the future
50
- const additionalWarnings = Array.from(this.uploadWarnings).join('\n\n');
43
+ const additionalWarnings = Array.from(this.state.uploadWarnings).join('\n\n');
51
44
  const warning = `${lib.LocalDevManager.uploadWarning.defaultWarning} ${additionalWarnings}`;
52
45
  // Avoid logging the warning to the console if it is currently the most
53
46
  // recently logged warning. We do not want to spam the console with the same message.
@@ -56,12 +49,6 @@ class LocalDevLogger {
56
49
  this.mostRecentUploadWarning = warning;
57
50
  }
58
51
  }
59
- addUploadWarning(warning) {
60
- this.uploadWarnings.add(warning);
61
- }
62
- clearUploadWarnings() {
63
- this.uploadWarnings.clear();
64
- }
65
52
  missingComponentsWarning(components) {
66
53
  const warning = lib.LocalDevManager.uploadWarning.missingComponents(components.join(', '));
67
54
  if (warning !== this.mostRecentUploadWarning) {
@@ -81,10 +68,6 @@ class LocalDevLogger {
81
68
  devServerCleanupError(e) {
82
69
  this.handleError(e, lib.LocalDevManager.devServer.cleanupError);
83
70
  }
84
- noDeployedBuild() {
85
- uiLogger.error(lib.LocalDevManager.noDeployedBuild(this.state.projectConfig.name, uiAccountDescription(this.state.targetProjectAccountId), this.getUploadCommand()));
86
- uiLogger.log('');
87
- }
88
71
  resetSpinnies() {
89
72
  SpinniesManager.stopAll();
90
73
  SpinniesManager.init();
@@ -9,7 +9,7 @@ import { PROJECT_CONFIG_FILE } from '../../constants.js';
9
9
  import SpinniesManager from '../../ui/SpinniesManager.js';
10
10
  import DevServerManager from './DevServerManager.js';
11
11
  import { EXIT_CODES } from '../../enums/exitCodes.js';
12
- import { getAccountHomeUrl } from './helpers.js';
12
+ import { getAccountHomeUrl } from '../urls.js';
13
13
  import { componentIsApp, componentIsPublicApp, CONFIG_FILES, getAppCardConfigs, getComponentUid, } from '../../projects/structure.js';
14
14
  import { ComponentTypes, } from '../../../types/Projects.js';
15
15
  import { UI_COLORS, uiCommandReference, uiAccountDescription, uiBetaTag, uiLink, uiLine, } from '../../ui/index.js';
@@ -19,7 +19,6 @@ declare class LocalDevProcess {
19
19
  private setupDevServers;
20
20
  private startDevServers;
21
21
  private cleanupDevServers;
22
- private compareLocalProjectToDeployed;
23
22
  private projectConfigValidForUpload;
24
23
  private getIntermediateRepresentation;
25
24
  private updateProjectNodes;
@@ -30,7 +29,7 @@ declare class LocalDevProcess {
30
29
  start(): Promise<void>;
31
30
  stop(showProgress?: boolean): Promise<void>;
32
31
  uploadProject(): Promise<boolean>;
33
- addStateListener<K extends keyof LocalDevState>(key: K, listener: LocalDevStateListener<K>, callOnInit?: boolean): void;
32
+ addStateListener<K extends keyof LocalDevState>(key: K, listener: LocalDevStateListener<K>): void;
34
33
  sendDevServerMessage(message: LocalDevServerMessage): void;
35
34
  removeStateListener<K extends keyof LocalDevState>(key: K, listener: LocalDevStateListener<K>): void;
36
35
  }
@@ -6,7 +6,6 @@ import LocalDevState from './LocalDevState.js';
6
6
  import LocalDevLogger from './LocalDevLogger.js';
7
7
  import DevServerManagerV2 from './DevServerManagerV2.js';
8
8
  import { EXIT_CODES } from '../../enums/exitCodes.js';
9
- import { mapToUserFriendlyName } from '@hubspot/project-parsing-lib/src/lib/transform.js';
10
9
  import { getProjectConfig } from '../config.js';
11
10
  import { handleProjectUpload } from '../upload.js';
12
11
  import { pollProjectBuildAndDeploy } from '../buildAndDeploy.js';
@@ -75,20 +74,6 @@ class LocalDevProcess {
75
74
  return false;
76
75
  }
77
76
  }
78
- compareLocalProjectToDeployed() {
79
- const deployedComponentNames = this.state.deployedBuild.subbuildStatuses.map(subbuildStatus => subbuildStatus.buildName);
80
- const missingProjectNodes = [];
81
- Object.values(this.projectNodes).forEach(node => {
82
- if (!deployedComponentNames.includes(node.uid)) {
83
- const userFriendlyName = mapToUserFriendlyName(node.componentType);
84
- const label = userFriendlyName ? `[${userFriendlyName}] ` : '';
85
- missingProjectNodes.push(`${label}${node.uid}`);
86
- }
87
- });
88
- if (missingProjectNodes.length) {
89
- this.logger.missingComponentsWarning(missingProjectNodes);
90
- }
91
- }
92
77
  async projectConfigValidForUpload() {
93
78
  const { projectConfig } = await getProjectConfig();
94
79
  if (!projectConfig) {
@@ -143,11 +128,6 @@ class LocalDevProcess {
143
128
  }
144
129
  async start() {
145
130
  this.logger.resetSpinnies();
146
- // Local dev currently relies on the existence of a deployed build in the target account
147
- if (!this.state.deployedBuild) {
148
- this.logger.noDeployedBuild();
149
- process.exit(EXIT_CODES.SUCCESS);
150
- }
151
131
  const setupSucceeded = await this.setupDevServers();
152
132
  if (!setupSucceeded) {
153
133
  process.exit(EXIT_CODES.ERROR);
@@ -158,9 +138,6 @@ class LocalDevProcess {
158
138
  }
159
139
  await this.startDevServers();
160
140
  this.logger.monitorConsoleOutput();
161
- // Verify that there are no mismatches between components in the local project
162
- // and components in the deployed build of the project.
163
- this.compareLocalProjectToDeployed();
164
141
  }
165
142
  async stop(showProgress = true) {
166
143
  if (showProgress) {
@@ -199,11 +176,11 @@ class LocalDevProcess {
199
176
  }
200
177
  await this.updateProjectNodesAfterUpload();
201
178
  this.logger.uploadSuccess();
202
- this.logger.clearUploadWarnings();
179
+ this.state.clearUploadWarnings();
203
180
  return true;
204
181
  }
205
- addStateListener(key, listener, callOnInit = false) {
206
- this.state.addListener(key, listener, callOnInit);
182
+ addStateListener(key, listener) {
183
+ this.state.addListener(key, listener);
207
184
  }
208
185
  sendDevServerMessage(message) {
209
186
  this.state.devServerMessage = message;
@@ -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>;