@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.
- package/README.md +0 -4
- package/api/__tests__/migrate.test.js +5 -5
- package/api/migrate.d.ts +10 -4
- package/api/migrate.js +2 -2
- package/commands/__tests__/create.test.js +20 -0
- package/commands/__tests__/testAccount.test.js +2 -0
- package/commands/app/__tests__/migrate.test.js +1 -0
- package/commands/create/function.js +2 -2
- package/commands/create/module.js +2 -2
- package/commands/create/template.js +2 -2
- package/commands/create.js +47 -0
- package/commands/getStarted.js +66 -4
- package/commands/mcp/setup.d.ts +0 -1
- package/commands/mcp/setup.js +3 -11
- package/commands/project/__tests__/create.test.js +57 -0
- package/commands/project/__tests__/devUnifiedFlow.test.js +18 -30
- package/commands/project/create.js +6 -1
- package/commands/project/deploy.js +31 -1
- package/commands/project/dev/deprecatedFlow.js +2 -1
- package/commands/project/dev/index.js +32 -12
- package/commands/project/dev/unifiedFlow.d.ts +1 -1
- package/commands/project/dev/unifiedFlow.js +10 -16
- package/commands/project/profile/delete.js +26 -14
- package/commands/testAccount/__tests__/importData.test.d.ts +1 -0
- package/commands/testAccount/__tests__/importData.test.js +93 -0
- package/commands/testAccount/create.js +23 -13
- package/commands/testAccount/importData.d.ts +9 -0
- package/commands/testAccount/importData.js +61 -0
- package/commands/testAccount.js +2 -0
- package/lang/en.d.ts +162 -46
- package/lang/en.js +177 -59
- package/lang/en.lyaml +35 -14
- package/lib/__tests__/importData.test.d.ts +1 -0
- package/lib/__tests__/importData.test.js +89 -0
- package/lib/accountTypes.js +2 -3
- package/lib/app/__tests__/migrate.test.js +81 -36
- package/lib/app/migrate.d.ts +17 -4
- package/lib/app/migrate.js +97 -19
- package/lib/constants.d.ts +1 -0
- package/lib/constants.js +1 -0
- package/lib/hasFeature.d.ts +1 -0
- package/lib/hasFeature.js +7 -0
- package/lib/importData.d.ts +3 -0
- package/lib/importData.js +50 -0
- package/lib/mcp/setup.d.ts +3 -5
- package/lib/mcp/setup.js +39 -139
- package/lib/process.js +15 -4
- package/lib/projects/__tests__/AppDevModeInterface.test.js +3 -3
- package/lib/projects/__tests__/LocalDevProcess.test.js +5 -95
- package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +6 -6
- package/lib/projects/__tests__/components.test.js +164 -7
- package/lib/projects/__tests__/localDevProjectHelpers.test.d.ts +1 -0
- package/lib/projects/__tests__/localDevProjectHelpers.test.js +118 -0
- package/lib/projects/add/v3AddComponent.js +16 -4
- package/lib/projects/components.d.ts +1 -0
- package/lib/projects/components.js +27 -1
- package/lib/projects/localDev/AppDevModeInterface.js +35 -3
- package/lib/projects/localDev/LocalDevLogger.d.ts +0 -4
- package/lib/projects/localDev/LocalDevLogger.js +2 -19
- package/lib/projects/localDev/LocalDevManager.js +1 -1
- package/lib/projects/localDev/LocalDevProcess.d.ts +1 -2
- package/lib/projects/localDev/LocalDevProcess.js +3 -26
- package/lib/projects/localDev/LocalDevState.d.ts +6 -7
- package/lib/projects/localDev/LocalDevState.js +16 -15
- package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +1 -0
- package/lib/projects/localDev/LocalDevWebsocketServer.js +17 -2
- package/lib/projects/localDev/{helpers.d.ts → helpers/account.d.ts} +1 -7
- package/lib/projects/localDev/{helpers.js → helpers/account.js} +44 -144
- package/lib/projects/localDev/helpers/project.d.ts +12 -0
- package/lib/projects/localDev/helpers/project.js +173 -0
- package/lib/projects/urls.d.ts +1 -0
- package/lib/projects/urls.js +4 -0
- package/lib/prompts/__tests__/createFunctionPrompt.test.d.ts +1 -0
- package/lib/prompts/__tests__/createFunctionPrompt.test.js +129 -0
- package/lib/prompts/__tests__/createModulePrompt.test.d.ts +1 -0
- package/lib/prompts/__tests__/createModulePrompt.test.js +187 -0
- package/lib/prompts/__tests__/createTemplatePrompt.test.d.ts +1 -0
- package/lib/prompts/__tests__/createTemplatePrompt.test.js +102 -0
- package/lib/prompts/confirmImportDataPrompt.d.ts +1 -0
- package/lib/prompts/confirmImportDataPrompt.js +12 -0
- package/lib/prompts/createFunctionPrompt.d.ts +2 -1
- package/lib/prompts/createFunctionPrompt.js +36 -7
- package/lib/prompts/createModulePrompt.d.ts +2 -1
- package/lib/prompts/createModulePrompt.js +48 -1
- package/lib/prompts/createTemplatePrompt.d.ts +3 -24
- package/lib/prompts/createTemplatePrompt.js +9 -1
- package/lib/prompts/importDataFilePathPrompt.d.ts +1 -0
- package/lib/prompts/importDataFilePathPrompt.js +24 -0
- package/lib/prompts/importDataTestAccountSelectPrompt.d.ts +3 -0
- package/lib/prompts/importDataTestAccountSelectPrompt.js +29 -0
- package/lib/prompts/projectDevTargetAccountPrompt.js +1 -0
- package/lib/prompts/promptUtils.d.ts +7 -1
- package/lib/prompts/promptUtils.js +14 -1
- package/lib/ui/__tests__/removeAnsiCodes.test.d.ts +1 -0
- package/lib/ui/__tests__/removeAnsiCodes.test.js +84 -0
- package/lib/ui/index.js +3 -6
- package/lib/ui/removeAnsiCodes.d.ts +1 -0
- package/lib/ui/removeAnsiCodes.js +4 -0
- package/mcp-server/server.js +2 -1
- package/mcp-server/tools/cms/HsCreateModuleTool.d.ts +38 -0
- package/mcp-server/tools/cms/HsCreateModuleTool.js +118 -0
- package/mcp-server/tools/cms/HsListTool.d.ts +23 -0
- package/mcp-server/tools/cms/HsListTool.js +58 -0
- package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.d.ts +1 -0
- package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +224 -0
- package/mcp-server/tools/cms/__tests__/HsListTool.test.d.ts +1 -0
- package/mcp-server/tools/cms/__tests__/HsListTool.test.js +120 -0
- package/mcp-server/tools/index.d.ts +1 -0
- package/mcp-server/tools/index.js +12 -0
- package/mcp-server/tools/project/DocFetchTool.d.ts +17 -0
- package/mcp-server/tools/project/DocFetchTool.js +49 -0
- package/mcp-server/tools/project/DocsSearchTool.d.ts +26 -0
- package/mcp-server/tools/project/DocsSearchTool.js +62 -0
- package/mcp-server/tools/project/GetConfigValuesTool.js +3 -2
- package/mcp-server/tools/project/__tests__/DocFetchTool.test.d.ts +1 -0
- package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +117 -0
- package/mcp-server/tools/project/__tests__/DocsSearchTool.test.d.ts +1 -0
- package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +190 -0
- package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +1 -1
- package/mcp-server/tools/project/constants.d.ts +2 -0
- package/mcp-server/tools/project/constants.js +6 -0
- package/mcp-server/utils/toolUsageTracking.d.ts +3 -1
- package/mcp-server/utils/toolUsageTracking.js +2 -1
- package/package.json +9 -6
- package/types/Cms.d.ts +16 -0
- package/types/Cms.js +25 -1
- package/types/LocalDev.d.ts +0 -3
- package/types/Prompts.d.ts +1 -0
- package/ui/index.d.ts +1 -0
- package/ui/index.js +6 -0
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import { handleComponentCollision } from '../components.js';
|
|
2
|
+
import { handleComponentCollision, updateHsMetaFilesWithAutoGeneratedFields, } from '../components.js';
|
|
3
|
+
import { uiLogger } from '../../ui/logger.js';
|
|
4
|
+
import { coerceToValidUid } from '@hubspot/project-parsing-lib';
|
|
3
5
|
vi.mock('fs');
|
|
6
|
+
vi.mock('../../ui/logger.js');
|
|
7
|
+
vi.mock('@hubspot/project-parsing-lib', () => ({
|
|
8
|
+
coerceToValidUid: vi.fn(),
|
|
9
|
+
metafileExtension: '.module.meta.json',
|
|
10
|
+
}));
|
|
11
|
+
vi.mock('@hubspot/project-parsing-lib/src/lib/constants.js', () => ({
|
|
12
|
+
AppKey: 'app',
|
|
13
|
+
}));
|
|
14
|
+
vi.mock('../../../lang/en.js', () => ({
|
|
15
|
+
lib: {
|
|
16
|
+
projects: {
|
|
17
|
+
updateHsMetaFilesWithAutoGeneratedFields: {
|
|
18
|
+
header: 'Updating component metadata files...',
|
|
19
|
+
applicationLog: (type, uid, name) => `Updated ${type} component with uid: ${uid} and name: ${name}`,
|
|
20
|
+
componentLog: (type, uid) => `Updated ${type} component with uid: ${uid}`,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
4
25
|
const mockedFs = vi.mocked(fs);
|
|
26
|
+
const mockCoerceToValidUid = vi.mocked(coerceToValidUid);
|
|
5
27
|
describe('lib/projects/components', () => {
|
|
6
28
|
describe('handleComponentCollision()', () => {
|
|
7
29
|
const mockCollision = {
|
|
@@ -152,10 +174,9 @@ describe('lib/projects/components', () => {
|
|
|
152
174
|
collisions: [
|
|
153
175
|
'regular.js',
|
|
154
176
|
'nested/path/file.ts',
|
|
155
|
-
'component.
|
|
177
|
+
'component.meta.json',
|
|
156
178
|
'another.meta.json',
|
|
157
179
|
'package.json',
|
|
158
|
-
'nested/package.json',
|
|
159
180
|
],
|
|
160
181
|
};
|
|
161
182
|
// Mock metafileExtension
|
|
@@ -166,16 +187,152 @@ describe('lib/projects/components', () => {
|
|
|
166
187
|
const mockMetaContent = '{}';
|
|
167
188
|
mockedFs.readFileSync.mockReturnValue(mockMetaContent);
|
|
168
189
|
mockedFs.writeFileSync.mockImplementation(() => { });
|
|
190
|
+
// Track what files are being copied to debug the issue
|
|
169
191
|
mockedFs.copyFileSync.mockImplementation(() => { });
|
|
170
192
|
// Mock console.log for package.json handling
|
|
171
193
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
172
194
|
handleComponentCollision(collision);
|
|
173
|
-
|
|
174
|
-
expect(mockedFs.copyFileSync).toHaveBeenCalledTimes(2);
|
|
195
|
+
expect(mockedFs.readFileSync).toHaveBeenCalledTimes(2);
|
|
175
196
|
// Should handle 2 metafiles
|
|
176
|
-
expect(mockedFs.readFileSync).toHaveBeenCalledWith('/
|
|
177
|
-
expect(mockedFs.readFileSync).toHaveBeenCalledWith('/src/path/
|
|
197
|
+
expect(mockedFs.readFileSync).toHaveBeenCalledWith('/dest/path/package.json', 'utf-8');
|
|
198
|
+
expect(mockedFs.readFileSync).toHaveBeenCalledWith('/src/path/package.json', 'utf-8');
|
|
178
199
|
consoleSpy.mockRestore();
|
|
179
200
|
});
|
|
180
201
|
});
|
|
202
|
+
describe('updateHsMetaFilesWithAutoGeneratedFields()', () => {
|
|
203
|
+
const mockUiLogger = vi.mocked(uiLogger);
|
|
204
|
+
beforeEach(() => {
|
|
205
|
+
vi.resetAllMocks();
|
|
206
|
+
mockCoerceToValidUid.mockImplementation((input) => input);
|
|
207
|
+
});
|
|
208
|
+
afterEach(() => {
|
|
209
|
+
vi.restoreAllMocks();
|
|
210
|
+
});
|
|
211
|
+
it('updates component metadata files with project-specific UIDs', () => {
|
|
212
|
+
const projectName = 'my-project';
|
|
213
|
+
const hsMetaFilePaths = [
|
|
214
|
+
'/path/to/component1.meta.json',
|
|
215
|
+
'/path/to/component2.meta.json',
|
|
216
|
+
];
|
|
217
|
+
const component1 = {
|
|
218
|
+
type: 'card',
|
|
219
|
+
uid: 'old-uid-1',
|
|
220
|
+
config: {
|
|
221
|
+
name: 'Old Name',
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
const component2 = {
|
|
225
|
+
type: 'function',
|
|
226
|
+
uid: 'old-uid-2',
|
|
227
|
+
};
|
|
228
|
+
mockedFs.readFileSync
|
|
229
|
+
.mockReturnValueOnce(JSON.stringify(component1))
|
|
230
|
+
.mockReturnValueOnce(JSON.stringify(component2));
|
|
231
|
+
mockedFs.writeFileSync.mockImplementation(() => { });
|
|
232
|
+
mockCoerceToValidUid
|
|
233
|
+
.mockReturnValueOnce('card-my-project')
|
|
234
|
+
.mockReturnValueOnce('function-my-project');
|
|
235
|
+
updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths);
|
|
236
|
+
expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/component1.meta.json', JSON.stringify({
|
|
237
|
+
type: 'card',
|
|
238
|
+
uid: 'card-my-project',
|
|
239
|
+
config: {
|
|
240
|
+
name: 'Old Name',
|
|
241
|
+
},
|
|
242
|
+
}, null, 2));
|
|
243
|
+
expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/component2.meta.json', JSON.stringify({
|
|
244
|
+
type: 'function',
|
|
245
|
+
uid: 'function-my-project',
|
|
246
|
+
}, null, 2));
|
|
247
|
+
expect(mockUiLogger.log).toHaveBeenCalledWith('Updating component metadata files...');
|
|
248
|
+
expect(mockUiLogger.log).toHaveBeenCalledWith('Updated card component with uid: card-my-project');
|
|
249
|
+
expect(mockUiLogger.log).toHaveBeenCalledWith('Updated function component with uid: function-my-project');
|
|
250
|
+
});
|
|
251
|
+
it('handles app components by updating both uid and config.name', () => {
|
|
252
|
+
const projectName = 'test-app';
|
|
253
|
+
const hsMetaFilePaths = ['/path/to/app.meta.json'];
|
|
254
|
+
const appComponent = {
|
|
255
|
+
type: 'app',
|
|
256
|
+
uid: 'old-app-uid',
|
|
257
|
+
config: {
|
|
258
|
+
name: 'Old App Name',
|
|
259
|
+
other: 'property',
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
mockedFs.readFileSync.mockReturnValue(JSON.stringify(appComponent));
|
|
263
|
+
mockedFs.writeFileSync.mockImplementation(() => { });
|
|
264
|
+
mockCoerceToValidUid.mockReturnValue('app-test-app');
|
|
265
|
+
updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths);
|
|
266
|
+
expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/app.meta.json', JSON.stringify({
|
|
267
|
+
type: 'app',
|
|
268
|
+
uid: 'app-test-app',
|
|
269
|
+
config: {
|
|
270
|
+
name: 'test-app-Application',
|
|
271
|
+
other: 'property',
|
|
272
|
+
},
|
|
273
|
+
}, null, 2));
|
|
274
|
+
expect(mockUiLogger.log).toHaveBeenCalledWith('Updated app component with uid: app-test-app and name: test-app-Application');
|
|
275
|
+
});
|
|
276
|
+
it('handles UID collisions by using timestamps', () => {
|
|
277
|
+
const projectName = 'collision-project';
|
|
278
|
+
const hsMetaFilePaths = ['/path/to/component1.meta.json'];
|
|
279
|
+
const existingUids = ['card-collision-project'];
|
|
280
|
+
const component1 = { type: 'card', uid: 'old-uid-1' };
|
|
281
|
+
mockedFs.readFileSync.mockReturnValue(JSON.stringify(component1));
|
|
282
|
+
mockedFs.writeFileSync.mockImplementation(() => { });
|
|
283
|
+
// Mock Date.now to return consistent value for testing
|
|
284
|
+
vi.spyOn(Date, 'now').mockReturnValue(1234567890);
|
|
285
|
+
mockCoerceToValidUid
|
|
286
|
+
.mockReturnValueOnce('card-collision-project')
|
|
287
|
+
.mockReturnValueOnce('card-1234567890-collision-project');
|
|
288
|
+
updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths, existingUids);
|
|
289
|
+
expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/component1.meta.json', JSON.stringify({
|
|
290
|
+
type: 'card',
|
|
291
|
+
uid: 'card-1234567890-collision-project',
|
|
292
|
+
}, null, 2));
|
|
293
|
+
});
|
|
294
|
+
it('falls back to original uid when coerceToValidUid returns null', () => {
|
|
295
|
+
const projectName = 'fallback-project';
|
|
296
|
+
const hsMetaFilePaths = ['/path/to/component.meta.json'];
|
|
297
|
+
const component = {
|
|
298
|
+
type: 'card',
|
|
299
|
+
uid: 'original-uid',
|
|
300
|
+
};
|
|
301
|
+
mockedFs.readFileSync.mockReturnValue(JSON.stringify(component));
|
|
302
|
+
mockedFs.writeFileSync.mockImplementation(() => { });
|
|
303
|
+
mockCoerceToValidUid.mockReturnValue(undefined);
|
|
304
|
+
updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths);
|
|
305
|
+
expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/component.meta.json', JSON.stringify({
|
|
306
|
+
type: 'card',
|
|
307
|
+
uid: 'original-uid',
|
|
308
|
+
}, null, 2));
|
|
309
|
+
});
|
|
310
|
+
it('handles empty hsMetaFilePaths array', () => {
|
|
311
|
+
const projectName = 'empty-project';
|
|
312
|
+
const hsMetaFilePaths = [];
|
|
313
|
+
updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths);
|
|
314
|
+
expect(mockedFs.readFileSync).not.toHaveBeenCalled();
|
|
315
|
+
expect(mockedFs.writeFileSync).not.toHaveBeenCalled();
|
|
316
|
+
expect(mockUiLogger.log).toHaveBeenCalledWith('Updating component metadata files...');
|
|
317
|
+
expect(mockUiLogger.log).toHaveBeenCalledWith('');
|
|
318
|
+
});
|
|
319
|
+
it('handles components without config property for app type', () => {
|
|
320
|
+
const projectName = 'no-config-project';
|
|
321
|
+
const hsMetaFilePaths = ['/path/to/app.meta.json'];
|
|
322
|
+
const appComponent = {
|
|
323
|
+
type: 'app',
|
|
324
|
+
uid: 'app-uid',
|
|
325
|
+
// No config property
|
|
326
|
+
};
|
|
327
|
+
mockedFs.readFileSync.mockReturnValue(JSON.stringify(appComponent));
|
|
328
|
+
mockedFs.writeFileSync.mockImplementation(() => { });
|
|
329
|
+
mockCoerceToValidUid.mockReturnValue('app-no-config-project');
|
|
330
|
+
updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFilePaths);
|
|
331
|
+
expect(mockedFs.writeFileSync).toHaveBeenCalledWith('/path/to/app.meta.json', JSON.stringify({
|
|
332
|
+
type: 'app',
|
|
333
|
+
uid: 'app-no-config-project',
|
|
334
|
+
}, null, 2));
|
|
335
|
+
expect(mockUiLogger.log).toHaveBeenCalledWith('Updated app component with uid: app-no-config-project');
|
|
336
|
+
});
|
|
337
|
+
});
|
|
181
338
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
|
23
|
+
const currentProjectMetadata = await getProjectMetadata(projectSrcDirectory);
|
|
24
24
|
let derivedAuthType;
|
|
25
25
|
let derivedDistribution;
|
|
26
|
-
const appsMetadata =
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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 '
|
|
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
|
|
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.
|
|
179
|
+
this.state.clearUploadWarnings();
|
|
203
180
|
return true;
|
|
204
181
|
}
|
|
205
|
-
addStateListener(key, listener
|
|
206
|
-
this.state.addListener(key, listener
|
|
182
|
+
addStateListener(key, listener) {
|
|
183
|
+
this.state.addListener(key, listener);
|
|
207
184
|
}
|
|
208
185
|
sendDevServerMessage(message) {
|
|
209
186
|
this.state.devServerMessage = message;
|