@hubspot/cli 7.7.35-experimental.0 → 7.8.0-beta.1
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/bin/cli.js +31 -25
- package/commands/__tests__/auth.test.js +5 -0
- package/commands/__tests__/doctor.test.js +16 -16
- package/commands/__tests__/getStarted.test.js +2 -2
- package/commands/__tests__/mcp.test.js +1 -1
- package/commands/__tests__/project.test.js +2 -3
- package/commands/account/auth.js +1 -0
- package/commands/account/clean.js +18 -27
- package/commands/account/createOverride.js +13 -31
- package/commands/account/info.js +20 -31
- package/commands/account/list.js +16 -22
- package/commands/account/remove.js +12 -20
- package/commands/account/removeOverride.js +11 -21
- package/commands/account/rename.js +6 -9
- package/commands/account/use.js +12 -26
- package/commands/account.js +2 -2
- package/commands/app/__tests__/migrate.test.js +5 -6
- package/commands/app/migrate.js +13 -19
- package/commands/app/secret/add.js +2 -1
- package/commands/app/secret/delete.js +2 -1
- package/commands/app/secret/list.js +2 -1
- package/commands/app/secret/update.js +2 -1
- package/commands/app/secret.js +2 -1
- package/commands/app.js +2 -2
- package/commands/auth.d.ts +1 -0
- package/commands/auth.js +17 -7
- package/commands/cms/convertFields.js +7 -9
- package/commands/cms/getReactModule.js +9 -14
- package/commands/cms/lighthouseScore.js +33 -36
- package/commands/cms.js +2 -2
- package/commands/completion.js +3 -3
- package/commands/config/set.d.ts +1 -1
- package/commands/config/set.js +64 -37
- package/commands/config.js +2 -2
- package/commands/create.js +2 -2
- package/commands/customObject/create.js +10 -12
- package/commands/customObject/schema/create.js +9 -11
- package/commands/customObject/schema/delete.js +16 -16
- package/commands/customObject/schema/fetch-all.js +12 -11
- package/commands/customObject/schema/fetch.js +15 -15
- package/commands/customObject/schema/list.js +4 -4
- package/commands/customObject/schema/update.js +13 -13
- package/commands/customObject/schema.js +2 -2
- package/commands/customObject.js +6 -7
- package/commands/doctor.js +8 -11
- package/commands/feedback.js +8 -13
- package/commands/fetch.js +8 -8
- package/commands/filemanager/fetch.js +7 -7
- package/commands/filemanager/upload.js +15 -34
- package/commands/filemanager.js +2 -2
- package/commands/function/deploy.js +11 -29
- package/commands/function/list.js +8 -8
- package/commands/function/server.js +9 -11
- package/commands/function.d.ts +1 -1
- package/commands/function.js +2 -2
- package/commands/getStarted.d.ts +0 -2
- package/commands/getStarted.js +4 -4
- package/commands/hubdb/clear.js +7 -15
- package/commands/hubdb/create.js +9 -15
- package/commands/hubdb/delete.js +8 -15
- package/commands/hubdb/fetch.js +6 -9
- package/commands/hubdb.d.ts +1 -1
- package/commands/hubdb.js +2 -2
- package/commands/init.js +2 -3
- package/commands/lint.js +16 -16
- package/commands/list.js +8 -14
- package/commands/logs.js +14 -20
- package/commands/mcp/__tests__/setup.test.js +2 -2
- package/commands/mcp/setup.js +11 -2
- package/commands/mcp.js +3 -3
- package/commands/mv.js +6 -17
- package/commands/open.js +5 -5
- package/commands/project/__tests__/add.test.js +15 -13
- package/commands/project/__tests__/create.test.js +6 -6
- package/commands/project/__tests__/deploy.test.js +3 -7
- package/commands/project/__tests__/devUnifiedFlow.test.js +2 -4
- package/commands/project/__tests__/installDeps.test.js +8 -8
- package/commands/project/__tests__/list.test.js +31 -0
- package/commands/project/__tests__/logs.test.js +1 -4
- package/commands/project/__tests__/migrate.test.js +7 -7
- package/commands/project/__tests__/migrateApp.test.js +3 -7
- package/commands/project/__tests__/profile.test.js +1 -1
- package/commands/project/__tests__/validate.test.js +98 -0
- package/commands/project/add.d.ts +2 -2
- package/commands/project/add.js +7 -10
- package/commands/project/cloneApp.js +14 -19
- package/commands/project/create.js +3 -10
- package/commands/project/deploy.js +5 -5
- package/commands/project/dev/deprecatedFlow.js +9 -18
- package/commands/project/dev/index.js +21 -18
- package/commands/project/dev/unifiedFlow.js +14 -7
- package/commands/project/download.js +15 -16
- package/commands/project/installDeps.d.ts +2 -2
- package/commands/project/installDeps.js +9 -9
- package/commands/project/list.d.ts +4 -0
- package/commands/project/list.js +62 -0
- package/commands/project/listBuilds.js +12 -21
- package/commands/project/logs.js +21 -24
- package/commands/project/migrate.js +46 -15
- package/commands/project/migrateApp.js +10 -17
- package/commands/project/open.js +6 -14
- package/commands/project/profile/add.js +3 -3
- package/commands/project/profile/delete.js +1 -2
- package/commands/project/profile.js +2 -3
- package/commands/project/upload.js +16 -25
- package/commands/project/validate.js +7 -7
- package/commands/project/watch.js +13 -22
- package/commands/project.js +4 -3
- package/commands/sandbox/__tests__/create.test.js +5 -5
- package/commands/sandbox/create.js +22 -32
- package/commands/sandbox/delete.js +39 -64
- package/commands/sandbox.js +2 -2
- package/commands/secret/addSecret.js +7 -17
- package/commands/secret/deleteSecret.js +10 -20
- package/commands/secret/listSecret.js +8 -10
- package/commands/secret/updateSecret.js +9 -17
- package/commands/secret.js +2 -2
- package/commands/testAccount/__tests__/delete.test.js +2 -4
- package/commands/testAccount/create.js +2 -2
- package/commands/testAccount/delete.d.ts +4 -3
- package/commands/testAccount/delete.js +155 -14
- package/commands/testAccount/importData.d.ts +1 -1
- package/commands/testAccount/importData.js +1 -1
- package/commands/testAccount.js +1 -1
- package/commands/theme/preview.js +1 -4
- package/lang/en.d.ts +364 -110
- package/lang/en.js +409 -158
- package/lang/en.lyaml +4 -4
- package/lib/__tests__/buildAccount.test.js +4 -3
- package/lib/__tests__/commonOpts.test.js +1 -1
- package/lib/__tests__/dependencyManagement.test.js +1 -1
- package/lib/__tests__/developerTestAccounts.test.js +3 -3
- package/lib/__tests__/npm.test.js +1 -1
- package/lib/__tests__/oauth.test.js +4 -4
- package/lib/__tests__/process.test.js +10 -5
- package/lib/__tests__/sandboxSync.test.js +8 -8
- package/lib/__tests__/sandboxes.test.js +8 -8
- package/lib/__tests__/serverlessLogs.test.js +1 -1
- package/lib/__tests__/usageTracking.test.js +5 -5
- package/lib/__tests__/validation.test.js +2 -1
- package/lib/__tests__/yargsUtils.test.js +83 -9
- package/lib/app/__tests__/migrate.test.js +19 -56
- package/lib/app/__tests__/migrate_legacy.test.js +1 -1
- package/lib/app/migrate.d.ts +2 -8
- package/lib/app/migrate.js +6 -81
- package/lib/app/migrate_legacy.js +20 -24
- package/lib/buildAccount.d.ts +2 -2
- package/lib/buildAccount.js +32 -64
- package/lib/commonOpts.d.ts +1 -1
- package/lib/commonOpts.js +25 -22
- package/lib/configMigrate.js +88 -9
- package/lib/configOptions.js +7 -0
- package/lib/constants.d.ts +21 -1
- package/lib/constants.js +25 -1
- package/lib/dependencyManagement.js +9 -27
- package/lib/developerTestAccounts.js +9 -23
- package/lib/doctor/Diagnosis.js +11 -23
- package/lib/doctor/DiagnosticInfoBuilder.js +12 -11
- package/lib/doctor/Doctor.js +42 -90
- package/lib/doctor/__tests__/Doctor.test.js +4 -4
- package/lib/errorHandlers/index.js +12 -20
- package/lib/errorHandlers/suppressError.js +11 -18
- package/lib/lang.js +6 -5
- package/lib/links.d.ts +1 -0
- package/lib/links.js +14 -7
- package/lib/mcp/setup.js +1 -1
- package/lib/middleware/__test__/commandTargetingUtils.test.js +99 -0
- package/lib/middleware/__test__/configMiddleware.test.js +11 -11
- package/lib/middleware/__test__/yargsChecksMiddleware.test.js +6 -8
- package/lib/middleware/commandTargetingUtils.d.ts +8 -0
- package/lib/middleware/commandTargetingUtils.js +74 -0
- package/lib/middleware/configMiddleware.d.ts +1 -1
- package/lib/middleware/configMiddleware.js +21 -81
- package/lib/middleware/fireAlarmMiddleware.js +15 -5
- package/lib/middleware/gitMiddleware.js +5 -1
- package/lib/middleware/notificationsMiddleware.js +5 -11
- package/lib/middleware/yargsChecksMiddleware.js +6 -9
- package/lib/npm.js +2 -2
- package/lib/oauth.js +5 -5
- package/lib/process.js +5 -4
- package/lib/projects/__tests__/AppDevModeInterface.test.js +87 -90
- package/lib/projects/__tests__/LocalDevProcess.test.js +231 -19
- package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +89 -63
- package/lib/projects/__tests__/deploy.test.js +73 -8
- package/lib/projects/__tests__/localDevProjectHelpers.test.js +6 -2
- package/lib/projects/__tests__/platformVersion.test.js +8 -8
- package/lib/projects/__tests__/projects.test.js +12 -12
- package/lib/projects/__tests__/structure.test.js +3 -3
- package/lib/projects/__tests__/upload.test.d.ts +1 -0
- package/lib/projects/__tests__/upload.test.js +82 -0
- package/lib/projects/add/__tests__/legacyAddComponent.test.js +6 -6
- package/lib/projects/add/__tests__/v2AddComponent.test.d.ts +1 -0
- package/lib/projects/add/__tests__/{v3AddComponent.test.js → v2AddComponent.test.js} +39 -39
- package/lib/projects/add/{v3AddComponent.d.ts → v2AddComponent.d.ts} +1 -1
- package/lib/projects/add/{v3AddComponent.js → v2AddComponent.js} +5 -5
- package/lib/projects/create/__tests__/legacy.test.js +5 -5
- package/lib/projects/create/__tests__/v2.test.d.ts +1 -0
- package/lib/projects/create/__tests__/{v3.test.js → v2.test.js} +82 -7
- package/lib/projects/create/index.js +4 -4
- package/lib/projects/create/legacy.js +2 -2
- package/lib/projects/create/{v3.d.ts → v2.d.ts} +3 -3
- package/lib/projects/create/{v3.js → v2.js} +13 -11
- package/lib/projects/deploy.d.ts +1 -1
- package/lib/projects/deploy.js +2 -2
- package/lib/projects/localDev/AppDevModeInterface.d.ts +10 -1
- package/lib/projects/localDev/AppDevModeInterface.js +118 -89
- package/lib/projects/localDev/DevServerManager.d.ts +11 -29
- package/lib/projects/localDev/DevServerManager.js +19 -61
- package/lib/projects/localDev/DevServerManager_DEPRECATED.d.ts +40 -0
- package/lib/projects/localDev/DevServerManager_DEPRECATED.js +120 -0
- package/lib/projects/localDev/LocalDevLogger.d.ts +4 -0
- package/lib/projects/localDev/LocalDevLogger.js +27 -6
- package/lib/projects/localDev/{LocalDevManager.js → LocalDevManager_DEPRECATED.js} +10 -11
- package/lib/projects/localDev/LocalDevProcess.d.ts +7 -5
- package/lib/projects/localDev/LocalDevProcess.js +93 -21
- package/lib/projects/localDev/LocalDevState.d.ts +12 -8
- package/lib/projects/localDev/LocalDevState.js +27 -17
- package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +6 -1
- package/lib/projects/localDev/LocalDevWebsocketServer.js +94 -33
- package/lib/projects/localDev/helpers/account.d.ts +1 -1
- package/lib/projects/localDev/helpers/account.js +2 -2
- package/lib/projects/localDev/helpers/project.d.ts +1 -0
- package/lib/projects/localDev/helpers/project.js +44 -4
- package/lib/projects/localDev/localDevWebsocketServerUtils.d.ts +7 -0
- package/lib/projects/localDev/localDevWebsocketServerUtils.js +19 -0
- package/lib/projects/platformVersion.d.ts +1 -1
- package/lib/projects/platformVersion.js +1 -1
- package/lib/projects/pollProjectBuildAndDeploy.js +4 -4
- package/lib/projects/structure.js +6 -6
- package/lib/projects/upload.d.ts +1 -1
- package/lib/projects/upload.js +17 -8
- package/lib/projects/urls.d.ts +0 -1
- package/lib/projects/urls.js +0 -3
- package/lib/prompts/__tests__/downloadProjectPrompt.test.js +1 -0
- package/lib/prompts/__tests__/projectAddPrompt.test.js +10 -10
- package/lib/prompts/accountNamePrompt.js +14 -19
- package/lib/prompts/accountsPrompt.js +2 -2
- package/lib/prompts/cmsFieldPrompt.js +2 -2
- package/lib/prompts/createApiSamplePrompt.js +5 -5
- package/lib/prompts/createDeveloperTestAccountConfigPrompt.js +10 -1
- package/lib/prompts/createFunctionPrompt.js +14 -14
- package/lib/prompts/createModulePrompt.js +9 -9
- package/lib/prompts/createTemplatePrompt.js +2 -2
- package/lib/prompts/downloadProjectPrompt.js +5 -8
- package/lib/prompts/installAppPrompt.d.ts +1 -6
- package/lib/prompts/installAppPrompt.js +1 -6
- package/lib/prompts/personalAccessKeyPrompt.js +3 -3
- package/lib/prompts/previewPrompt.js +6 -6
- package/lib/prompts/projectAddPrompt.d.ts +2 -2
- package/lib/prompts/projectAddPrompt.js +9 -2
- package/lib/prompts/projectDevTargetAccountPrompt.js +20 -32
- package/lib/prompts/projectNamePrompt.js +4 -8
- package/lib/prompts/projectsLogsPrompt.js +2 -4
- package/lib/prompts/promptUtils.js +30 -9
- package/lib/prompts/sandboxesPrompt.js +7 -7
- package/lib/prompts/secretPrompt.js +3 -3
- package/lib/prompts/selectAppPrompt.js +3 -3
- package/lib/prompts/selectHubDBTablePrompt.js +9 -13
- package/lib/prompts/selectProjectTemplatePrompt.js +2 -0
- package/lib/prompts/selectPublicAppForMigrationPrompt.js +15 -19
- package/lib/prompts/setAsDefaultAccountPrompt.js +4 -8
- package/lib/prompts/uploadPrompt.js +5 -5
- package/lib/sandboxSync.js +24 -41
- package/lib/sandboxes.js +19 -47
- package/lib/schema.js +3 -3
- package/lib/serverlessLogs.js +11 -13
- package/lib/theme/__tests__/migrate.test.d.ts +1 -0
- package/lib/theme/__tests__/migrate.test.js +233 -0
- package/lib/theme/migrate.d.ts +13 -0
- package/lib/theme/migrate.js +90 -0
- package/lib/ui/SpinniesManager.d.ts +2 -0
- package/lib/ui/SpinniesManager.js +7 -0
- package/lib/ui/boxen.js +1 -2
- package/lib/ui/git.js +13 -10
- package/lib/ui/index.d.ts +4 -0
- package/lib/ui/index.js +47 -38
- package/lib/ui/serverlessFunctionLogs.js +9 -7
- package/lib/ui/uiMessages.d.ts +72 -0
- package/lib/ui/uiMessages.js +75 -0
- package/lib/usageTracking.js +8 -8
- package/lib/validation.js +20 -23
- package/lib/yargsUtils.d.ts +1 -1
- package/lib/yargsUtils.js +12 -5
- package/mcp-server/tools/cms/HsCreateModuleTool.d.ts +2 -2
- package/mcp-server/tools/index.js +4 -0
- package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +2 -2
- package/mcp-server/tools/project/CreateProjectTool.d.ts +2 -2
- package/mcp-server/tools/project/DocsSearchTool.d.ts +4 -1
- package/mcp-server/tools/project/DocsSearchTool.js +5 -5
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +23 -0
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +68 -0
- package/mcp-server/tools/project/GetApplicationInfoTool.d.ts +11 -0
- package/mcp-server/tools/project/GetApplicationInfoTool.js +49 -0
- package/mcp-server/tools/project/GetConfigValuesTool.d.ts +4 -1
- package/mcp-server/tools/project/GetConfigValuesTool.js +12 -6
- package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +2 -2
- package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +1 -1
- package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +12 -10
- package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.d.ts +1 -0
- package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +169 -0
- package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.d.ts +1 -0
- package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +115 -0
- package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +8 -7
- package/mcp-server/utils/__tests__/cliConfig.test.d.ts +1 -0
- package/mcp-server/utils/__tests__/cliConfig.test.js +110 -0
- package/mcp-server/utils/cliConfig.d.ts +1 -0
- package/mcp-server/utils/cliConfig.js +12 -0
- package/mcp-server/utils/toolUsageTracking.js +2 -2
- package/package.json +8 -7
- package/types/LocalDev.d.ts +19 -3
- package/ui/index.js +1 -1
- package/lib/middleware/__test__/utils.test.js +0 -51
- package/lib/middleware/utils.d.ts +0 -8
- package/lib/middleware/utils.js +0 -14
- package/lib/projects/localDev/DevServerManagerV2.d.ts +0 -22
- package/lib/projects/localDev/DevServerManagerV2.js +0 -81
- /package/{lib/middleware/__test__/utils.test.d.ts → commands/project/__tests__/list.test.d.ts} +0 -0
- /package/{lib/projects/add/__tests__/v3AddComponent.test.d.ts → commands/project/__tests__/validate.test.d.ts} +0 -0
- /package/lib/{projects/create/__tests__/v3.test.d.ts → middleware/__test__/commandTargetingUtils.test.d.ts} +0 -0
- /package/lib/projects/localDev/{LocalDevManager.d.ts → LocalDevManager_DEPRECATED.d.ts} +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { WebSocketServer } from 'ws';
|
|
2
2
|
import { isPortManagerServerRunning, requestPorts, } from '@hubspot/local-dev-lib/portManager';
|
|
3
|
-
import {
|
|
3
|
+
import { uiLogger } from '../../ui/logger.js';
|
|
4
4
|
import { LOCAL_DEV_UI_MESSAGE_RECEIVE_TYPES, LOCAL_DEV_UI_MESSAGE_SEND_TYPES, LOCAL_DEV_SERVER_MESSAGE_TYPES, } from '../../constants.js';
|
|
5
5
|
import LocalDevWebsocketServer from '../localDev/LocalDevWebsocketServer.js';
|
|
6
6
|
import { lib } from '../../../lang/en.js';
|
|
7
7
|
vi.mock('ws');
|
|
8
8
|
vi.mock('@hubspot/local-dev-lib/portManager');
|
|
9
|
-
vi.mock('
|
|
9
|
+
vi.mock('../../ui/logger.js');
|
|
10
10
|
describe('LocalDevWebsocketServer', () => {
|
|
11
11
|
let mockLocalDevProcess;
|
|
12
12
|
let mockWebSocket;
|
|
@@ -30,8 +30,16 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
30
30
|
mockLocalDevProcess = {
|
|
31
31
|
addStateListener: vi.fn(),
|
|
32
32
|
removeStateListener: vi.fn(),
|
|
33
|
-
uploadProject: vi.fn(),
|
|
33
|
+
uploadProject: vi.fn().mockResolvedValue({}),
|
|
34
34
|
sendDevServerMessage: vi.fn(),
|
|
35
|
+
projectData: {
|
|
36
|
+
name: 'test-project',
|
|
37
|
+
id: 123,
|
|
38
|
+
latestBuild: { id: 'build-1', status: 'SUCCESS' },
|
|
39
|
+
deployedBuild: { id: 'build-1', status: 'SUCCESS' },
|
|
40
|
+
},
|
|
41
|
+
targetProjectAccountId: 456,
|
|
42
|
+
targetTestingAccountId: 789,
|
|
35
43
|
};
|
|
36
44
|
// Mock WebSocketServer constructor
|
|
37
45
|
WebSocketServer.mockImplementation(() => mockWebSocketServer);
|
|
@@ -51,39 +59,68 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
51
59
|
await server.start();
|
|
52
60
|
expect(WebSocketServer).toHaveBeenCalledWith({ port: 1234 });
|
|
53
61
|
expect(mockWebSocketServer.on).toHaveBeenCalledWith('connection', expect.any(Function));
|
|
54
|
-
expect(
|
|
62
|
+
expect(uiLogger.log).toHaveBeenCalled();
|
|
55
63
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
'
|
|
64
|
+
describe('valid origins', () => {
|
|
65
|
+
const validOrigins = [
|
|
66
|
+
'https://app.hubspot.com',
|
|
67
|
+
'https://app.hubspotqa.com',
|
|
68
|
+
'https://local.hubspot.com',
|
|
69
|
+
'https://local.hubspotqa.com',
|
|
70
|
+
'https://app-na2.hubspot.com',
|
|
71
|
+
'https://app-na2.hubspotqa.com',
|
|
72
|
+
'https://app-na3.hubspot.com',
|
|
73
|
+
'https://app-na3.hubspotqa.com',
|
|
74
|
+
'https://app-ap1.hubspot.com',
|
|
75
|
+
'https://app-ap1.hubspotqa.com',
|
|
76
|
+
'https://app-eu1.hubspot.com',
|
|
77
|
+
'https://app-eu1.hubspotqa.com',
|
|
78
|
+
];
|
|
79
|
+
validOrigins.forEach(origin => {
|
|
80
|
+
it(`should accept connection from ${origin}`, async () => {
|
|
81
|
+
isPortManagerServerRunning.mockResolvedValue(true);
|
|
82
|
+
requestPorts.mockResolvedValue({
|
|
83
|
+
'local-dev-ui-websocket-server': 1234,
|
|
84
|
+
});
|
|
85
|
+
await server.start();
|
|
86
|
+
// Get the connection callback
|
|
87
|
+
const connectionCallback = mockWebSocketServer.on.mock
|
|
88
|
+
.calls[0][1];
|
|
89
|
+
// Simulate connection from valid origin
|
|
90
|
+
connectionCallback(mockWebSocket, {
|
|
91
|
+
headers: { origin },
|
|
92
|
+
});
|
|
93
|
+
expect(mockWebSocket.on).toHaveBeenCalledWith('message', expect.any(Function));
|
|
94
|
+
expect(mockLocalDevProcess.addStateListener).toHaveBeenCalledWith('projectNodes', expect.any(Function));
|
|
95
|
+
expect(mockWebSocket.close).not.toHaveBeenCalled();
|
|
96
|
+
});
|
|
60
97
|
});
|
|
61
|
-
await server.start();
|
|
62
|
-
// Get the connection callback
|
|
63
|
-
const connectionCallback = mockWebSocketServer.on.mock.calls[0][1];
|
|
64
|
-
// Simulate connection from valid origin
|
|
65
|
-
connectionCallback(mockWebSocket, {
|
|
66
|
-
headers: { origin: 'https://app.hubspot.com' },
|
|
67
|
-
});
|
|
68
|
-
expect(mockWebSocket.on).toHaveBeenCalledWith('message', expect.any(Function));
|
|
69
|
-
expect(mockLocalDevProcess.addStateListener).toHaveBeenCalledWith('projectNodes', expect.any(Function));
|
|
70
|
-
expect(mockWebSocket.close).not.toHaveBeenCalled();
|
|
71
98
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
'
|
|
99
|
+
describe('invalid origins', () => {
|
|
100
|
+
const invalidOrigins = [
|
|
101
|
+
'https://malicious-site.com',
|
|
102
|
+
'https://app.malicious-site.com',
|
|
103
|
+
'https://app.hubspot.com.evil.com',
|
|
104
|
+
];
|
|
105
|
+
invalidOrigins.forEach(origin => {
|
|
106
|
+
it(`should reject connection from "${origin}"`, async () => {
|
|
107
|
+
isPortManagerServerRunning.mockResolvedValue(true);
|
|
108
|
+
requestPorts.mockResolvedValue({
|
|
109
|
+
'local-dev-ui-websocket-server': 1234,
|
|
110
|
+
});
|
|
111
|
+
await server.start();
|
|
112
|
+
// Get the connection callback
|
|
113
|
+
const connectionCallback = mockWebSocketServer.on.mock
|
|
114
|
+
.calls[0][1];
|
|
115
|
+
// Simulate connection from invalid origin
|
|
116
|
+
connectionCallback(mockWebSocket, {
|
|
117
|
+
headers: { origin },
|
|
118
|
+
});
|
|
119
|
+
expect(mockWebSocket.close).toHaveBeenCalledWith(1008, lib.LocalDevWebsocketServer.errors.originNotAllowed(origin));
|
|
120
|
+
expect(mockWebSocket.on).not.toHaveBeenCalled();
|
|
121
|
+
expect(mockLocalDevProcess.addStateListener).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
76
123
|
});
|
|
77
|
-
await server.start();
|
|
78
|
-
// Get the connection callback
|
|
79
|
-
const connectionCallback = mockWebSocketServer.on.mock.calls[0][1];
|
|
80
|
-
// Simulate connection from invalid origin
|
|
81
|
-
connectionCallback(mockWebSocket, {
|
|
82
|
-
headers: { origin: 'https://malicious-site.com' },
|
|
83
|
-
});
|
|
84
|
-
expect(mockWebSocket.close).toHaveBeenCalledWith(1008, lib.LocalDevWebsocketServer.errors.originNotAllowed('https://malicious-site.com'));
|
|
85
|
-
expect(mockWebSocket.on).not.toHaveBeenCalled();
|
|
86
|
-
expect(mockLocalDevProcess.addStateListener).not.toHaveBeenCalled();
|
|
87
124
|
});
|
|
88
125
|
it('should reject connection with no origin header', async () => {
|
|
89
126
|
isPortManagerServerRunning.mockResolvedValue(true);
|
|
@@ -111,7 +148,7 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
111
148
|
const connectionCallback = mockWebSocketServer.on.mock.calls[0][1];
|
|
112
149
|
// Simulate connection from valid origin
|
|
113
150
|
connectionCallback(mockWebSocket, {
|
|
114
|
-
headers: { origin: 'https://app.hubspot.com' },
|
|
151
|
+
headers: { origin: 'https://app-na3.hubspot.com' },
|
|
115
152
|
});
|
|
116
153
|
expect(mockLocalDevProcess.sendDevServerMessage).toHaveBeenCalledWith(LOCAL_DEV_SERVER_MESSAGE_TYPES.WEBSOCKET_SERVER_CONNECTED);
|
|
117
154
|
});
|
|
@@ -140,7 +177,7 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
140
177
|
const messageCallback = mockWebSocket.on.mock.calls[0][1];
|
|
141
178
|
const message = {};
|
|
142
179
|
messageCallback(JSON.stringify(message));
|
|
143
|
-
expect(
|
|
180
|
+
expect(uiLogger.error).toHaveBeenCalled();
|
|
144
181
|
});
|
|
145
182
|
it('should log error for unknown message type', () => {
|
|
146
183
|
const messageCallback = mockWebSocket.on.mock.calls[0][1];
|
|
@@ -148,13 +185,13 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
148
185
|
type: 'UNKNOWN_TYPE',
|
|
149
186
|
};
|
|
150
187
|
messageCallback(JSON.stringify(message));
|
|
151
|
-
expect(
|
|
188
|
+
expect(uiLogger.error).toHaveBeenCalled();
|
|
152
189
|
});
|
|
153
190
|
it('should log error for invalid JSON', () => {
|
|
154
191
|
const messageCallback = mockWebSocket.on.mock.calls[0][1];
|
|
155
192
|
const invalidJson = 'invalid json';
|
|
156
193
|
messageCallback(invalidJson);
|
|
157
|
-
expect(
|
|
194
|
+
expect(uiLogger.error).toHaveBeenCalled();
|
|
158
195
|
});
|
|
159
196
|
});
|
|
160
197
|
describe('shutdown()', () => {
|
|
@@ -205,7 +242,7 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
205
242
|
headers: { origin: 'https://app.hubspot.com' },
|
|
206
243
|
});
|
|
207
244
|
connectionCallback(mockWebSocket2, {
|
|
208
|
-
headers: { origin: 'https://app.hubspotqa.com' },
|
|
245
|
+
headers: { origin: 'https://app-na2.hubspotqa.com' },
|
|
209
246
|
});
|
|
210
247
|
connectionCallback(mockWebSocket3, {
|
|
211
248
|
headers: { origin: 'https://local.hubspot.com' },
|
|
@@ -215,7 +252,7 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
215
252
|
expect(mockWebSocket2.on).toHaveBeenCalledWith('message', expect.any(Function));
|
|
216
253
|
expect(mockWebSocket3.on).toHaveBeenCalledWith('message', expect.any(Function));
|
|
217
254
|
// Each connection should trigger state listener setup
|
|
218
|
-
expect(mockLocalDevProcess.addStateListener).toHaveBeenCalledTimes(
|
|
255
|
+
expect(mockLocalDevProcess.addStateListener).toHaveBeenCalledTimes(12); // 4 listeners per connection * 3 connections
|
|
219
256
|
// Each connection should trigger dev server message
|
|
220
257
|
expect(mockLocalDevProcess.sendDevServerMessage).toHaveBeenCalledTimes(3);
|
|
221
258
|
expect(mockLocalDevProcess.sendDevServerMessage).toHaveBeenCalledWith(LOCAL_DEV_SERVER_MESSAGE_TYPES.WEBSOCKET_SERVER_CONNECTED);
|
|
@@ -225,29 +262,12 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
225
262
|
expect(mockWebSocket3.close).not.toHaveBeenCalled();
|
|
226
263
|
});
|
|
227
264
|
it('should send project data to each connection independently', () => {
|
|
228
|
-
// Setup mock project data properties as getters
|
|
229
|
-
Object.defineProperty(mockLocalDevProcess, 'projectName', {
|
|
230
|
-
get: () => 'test-project',
|
|
231
|
-
configurable: true,
|
|
232
|
-
});
|
|
233
|
-
Object.defineProperty(mockLocalDevProcess, 'projectId', {
|
|
234
|
-
get: () => 123,
|
|
235
|
-
configurable: true,
|
|
236
|
-
});
|
|
237
|
-
Object.defineProperty(mockLocalDevProcess, 'targetProjectAccountId', {
|
|
238
|
-
get: () => 456,
|
|
239
|
-
configurable: true,
|
|
240
|
-
});
|
|
241
|
-
Object.defineProperty(mockLocalDevProcess, 'targetTestingAccountId', {
|
|
242
|
-
get: () => 789,
|
|
243
|
-
configurable: true,
|
|
244
|
-
});
|
|
245
265
|
// Establish multiple connections
|
|
246
266
|
connectionCallback(mockWebSocket1, {
|
|
247
267
|
headers: { origin: 'https://app.hubspot.com' },
|
|
248
268
|
});
|
|
249
269
|
connectionCallback(mockWebSocket2, {
|
|
250
|
-
headers: { origin: 'https://app.hubspotqa.com' },
|
|
270
|
+
headers: { origin: 'https://app-eu1.hubspotqa.com' },
|
|
251
271
|
});
|
|
252
272
|
// Each websocket should receive project data
|
|
253
273
|
expect(mockWebSocket1.send).toHaveBeenCalledWith(JSON.stringify({
|
|
@@ -255,6 +275,8 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
255
275
|
data: {
|
|
256
276
|
projectName: 'test-project',
|
|
257
277
|
projectId: 123,
|
|
278
|
+
latestBuild: { id: 'build-1', status: 'SUCCESS' },
|
|
279
|
+
deployedBuild: { id: 'build-1', status: 'SUCCESS' },
|
|
258
280
|
targetProjectAccountId: 456,
|
|
259
281
|
targetTestingAccountId: 789,
|
|
260
282
|
},
|
|
@@ -264,6 +286,8 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
264
286
|
data: {
|
|
265
287
|
projectName: 'test-project',
|
|
266
288
|
projectId: 123,
|
|
289
|
+
latestBuild: { id: 'build-1', status: 'SUCCESS' },
|
|
290
|
+
deployedBuild: { id: 'build-1', status: 'SUCCESS' },
|
|
267
291
|
targetProjectAccountId: 456,
|
|
268
292
|
targetTestingAccountId: 789,
|
|
269
293
|
},
|
|
@@ -275,7 +299,7 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
275
299
|
headers: { origin: 'https://app.hubspot.com' },
|
|
276
300
|
});
|
|
277
301
|
connectionCallback(mockWebSocket2, {
|
|
278
|
-
headers: { origin: 'https://app.hubspotqa.com' },
|
|
302
|
+
headers: { origin: 'https://app-ap1.hubspotqa.com' },
|
|
279
303
|
});
|
|
280
304
|
// Get all the close callbacks for both connections (there should be 2 per connection)
|
|
281
305
|
const closeCallbacks1 = mockWebSocket1.on.mock.calls
|
|
@@ -284,16 +308,16 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
284
308
|
const closeCallbacks2 = mockWebSocket2.on.mock.calls
|
|
285
309
|
.filter(call => call[0] === 'close')
|
|
286
310
|
.map(call => call[1]);
|
|
287
|
-
expect(closeCallbacks1).toHaveLength(
|
|
288
|
-
expect(closeCallbacks2).toHaveLength(
|
|
311
|
+
expect(closeCallbacks1).toHaveLength(4); // projectNodes, appData, devServersStarted, and uploadWarnings listeners
|
|
312
|
+
expect(closeCallbacks2).toHaveLength(4); // projectNodes, appData, devServersStarted, and uploadWarnings listeners
|
|
289
313
|
// Simulate first connection closing (call all close callbacks)
|
|
290
314
|
closeCallbacks1.forEach(callback => callback());
|
|
291
|
-
// Should have removed listeners for first connection (
|
|
292
|
-
expect(mockLocalDevProcess.removeStateListener).toHaveBeenCalledTimes(
|
|
315
|
+
// Should have removed listeners for first connection (4 listeners: projectNodes, appData, devServersStarted, and uploadWarnings)
|
|
316
|
+
expect(mockLocalDevProcess.removeStateListener).toHaveBeenCalledTimes(4);
|
|
293
317
|
// Simulate second connection closing
|
|
294
318
|
closeCallbacks2.forEach(callback => callback());
|
|
295
319
|
// Should have removed listeners for second connection as well
|
|
296
|
-
expect(mockLocalDevProcess.removeStateListener).toHaveBeenCalledTimes(
|
|
320
|
+
expect(mockLocalDevProcess.removeStateListener).toHaveBeenCalledTimes(8);
|
|
297
321
|
});
|
|
298
322
|
it('should broadcast state changes to all connected clients', () => {
|
|
299
323
|
// Establish connections
|
|
@@ -301,7 +325,7 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
301
325
|
headers: { origin: 'https://app.hubspot.com' },
|
|
302
326
|
});
|
|
303
327
|
connectionCallback(mockWebSocket2, {
|
|
304
|
-
headers: { origin: 'https://
|
|
328
|
+
headers: { origin: 'https://local.hubspotqa.com' },
|
|
305
329
|
});
|
|
306
330
|
// Get the projectNodes listeners that were registered
|
|
307
331
|
const projectNodesListeners = mockLocalDevProcess.addStateListener.mock.calls
|
|
@@ -317,6 +341,8 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
317
341
|
componentRoot: '/test/path',
|
|
318
342
|
componentConfigPath: '/test/path/config.json',
|
|
319
343
|
configUpdatedSinceLastUpload: false,
|
|
344
|
+
removed: false,
|
|
345
|
+
parsingErrors: [],
|
|
320
346
|
},
|
|
321
347
|
componentDeps: {},
|
|
322
348
|
metaFilePath: '/test/path',
|
|
@@ -99,7 +99,7 @@ describe('lib/projects/deploy', () => {
|
|
|
99
99
|
const targetAccountId = 12345;
|
|
100
100
|
const projectName = 'test-project';
|
|
101
101
|
const buildId = 5;
|
|
102
|
-
const
|
|
102
|
+
const useV2Api = true;
|
|
103
103
|
const force = false;
|
|
104
104
|
it('successfully deploys and returns deploy result', async () => {
|
|
105
105
|
const mockDeployResponseData = {
|
|
@@ -126,9 +126,9 @@ describe('lib/projects/deploy', () => {
|
|
|
126
126
|
data: mockDeployResponseData,
|
|
127
127
|
});
|
|
128
128
|
mockPollDeployStatus.mockResolvedValue(mockDeployResult);
|
|
129
|
-
const
|
|
130
|
-
expect(mockDeployProject).toHaveBeenCalledWith(targetAccountId, projectName, buildId,
|
|
131
|
-
expect(
|
|
129
|
+
const deploy = await handleProjectDeploy(targetAccountId, projectName, buildId, useV2Api, force);
|
|
130
|
+
expect(mockDeployProject).toHaveBeenCalledWith(targetAccountId, projectName, buildId, useV2Api, force);
|
|
131
|
+
expect(deploy).toEqual(mockDeployResult);
|
|
132
132
|
});
|
|
133
133
|
it('handles blocked deploy with warnings', async () => {
|
|
134
134
|
const mockBlockedResponse = {
|
|
@@ -150,15 +150,80 @@ describe('lib/projects/deploy', () => {
|
|
|
150
150
|
mockDeployProject.mockResolvedValue({
|
|
151
151
|
data: mockBlockedResponse,
|
|
152
152
|
});
|
|
153
|
-
|
|
153
|
+
await handleProjectDeploy(targetAccountId, projectName, buildId, useV2Api, force);
|
|
154
154
|
expect(mockUiLogger.warn).toHaveBeenCalledWith(commands.project.deploy.errors.deployWarningsHeader);
|
|
155
|
-
|
|
155
|
+
});
|
|
156
|
+
it('handles blocked deploy with errors (cannot be forced)', async () => {
|
|
157
|
+
const mockBlockedResponse = {
|
|
158
|
+
buildResultType: 'DEPLOY_BLOCKED',
|
|
159
|
+
issues: [
|
|
160
|
+
{
|
|
161
|
+
uid: 'component-1',
|
|
162
|
+
componentTypeName: 'module',
|
|
163
|
+
errorMessages: [],
|
|
164
|
+
blockingMessages: [
|
|
165
|
+
{
|
|
166
|
+
message: 'This is an error',
|
|
167
|
+
isWarning: false,
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
mockDeployProject.mockResolvedValue({
|
|
174
|
+
data: mockBlockedResponse,
|
|
175
|
+
});
|
|
176
|
+
await handleProjectDeploy(targetAccountId, projectName, buildId, useV2Api, force);
|
|
177
|
+
expect(mockUiLogger.error).toHaveBeenCalledWith(commands.project.deploy.errors.deployBlockedHeader);
|
|
178
|
+
expect(mockUiLogger.log).toHaveBeenCalledWith(commands.project.deploy.errors.deployIssueComponentWarning('component-1', 'module', 'This is an error'));
|
|
179
|
+
});
|
|
180
|
+
it('handles blocked deploy with no blocking messages', async () => {
|
|
181
|
+
const mockBlockedResponse = {
|
|
182
|
+
buildResultType: 'DEPLOY_BLOCKED',
|
|
183
|
+
issues: [
|
|
184
|
+
{
|
|
185
|
+
uid: 'component-1',
|
|
186
|
+
componentTypeName: 'module',
|
|
187
|
+
errorMessages: [],
|
|
188
|
+
blockingMessages: [],
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
};
|
|
192
|
+
mockDeployProject.mockResolvedValue({
|
|
193
|
+
data: mockBlockedResponse,
|
|
194
|
+
});
|
|
195
|
+
await handleProjectDeploy(targetAccountId, projectName, buildId, useV2Api, force);
|
|
196
|
+
expect(mockUiLogger.warn).toHaveBeenCalledWith(commands.project.deploy.errors.deployWarningsHeader);
|
|
197
|
+
expect(mockUiLogger.log).toHaveBeenCalledWith(commands.project.deploy.errors.deployIssueComponentGeneric('component-1', 'module'));
|
|
156
198
|
});
|
|
157
199
|
it('handles general deploy failure', async () => {
|
|
158
200
|
mockDeployProject.mockResolvedValue({ data: null });
|
|
159
|
-
const
|
|
201
|
+
const deploy = await handleProjectDeploy(targetAccountId, projectName, buildId, useV2Api, force);
|
|
202
|
+
expect(mockUiLogger.error).toHaveBeenCalledWith(commands.project.deploy.errors.deploy);
|
|
203
|
+
expect(deploy).toBeUndefined();
|
|
204
|
+
});
|
|
205
|
+
it('handles undefined deploy response', async () => {
|
|
206
|
+
mockDeployProject.mockResolvedValue({ data: undefined });
|
|
207
|
+
const deploy = await handleProjectDeploy(targetAccountId, projectName, buildId, useV2Api, force);
|
|
160
208
|
expect(mockUiLogger.error).toHaveBeenCalledWith(commands.project.deploy.errors.deploy);
|
|
161
|
-
expect(
|
|
209
|
+
expect(deploy).toBeUndefined();
|
|
210
|
+
});
|
|
211
|
+
it('passes correct parameters to deployProject', async () => {
|
|
212
|
+
const mockDeployResponseData = {
|
|
213
|
+
id: 'deploy-123',
|
|
214
|
+
buildResultType: 'DEPLOY_QUEUED',
|
|
215
|
+
links: {
|
|
216
|
+
status: 'http://status-url',
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
mockDeployProject.mockResolvedValue({
|
|
220
|
+
data: mockDeployResponseData,
|
|
221
|
+
});
|
|
222
|
+
mockPollDeployStatus.mockResolvedValue({});
|
|
223
|
+
await handleProjectDeploy(targetAccountId, projectName, buildId, false, true);
|
|
224
|
+
expect(mockDeployProject).toHaveBeenCalledWith(targetAccountId, projectName, buildId, false, // useV2Api
|
|
225
|
+
true // force
|
|
226
|
+
);
|
|
162
227
|
});
|
|
163
228
|
});
|
|
164
229
|
});
|
|
@@ -30,6 +30,8 @@ describe('isDeployedProjectUpToDateWithLocal', () => {
|
|
|
30
30
|
componentRoot: '/local/path',
|
|
31
31
|
componentConfigPath: '/local/path/config.json',
|
|
32
32
|
configUpdatedSinceLastUpload: false,
|
|
33
|
+
removed: false,
|
|
34
|
+
parsingErrors: [],
|
|
33
35
|
},
|
|
34
36
|
componentDeps: {},
|
|
35
37
|
metaFilePath: '/local/path',
|
|
@@ -99,7 +101,8 @@ describe('isDeployedProjectUpToDateWithLocal', () => {
|
|
|
99
101
|
it('should clean up temp directory even when errors occur', async () => {
|
|
100
102
|
// Mock downloadProject to throw an error after temp dir is created
|
|
101
103
|
downloadProject.mockRejectedValue(new Error('Download Error'));
|
|
102
|
-
await
|
|
104
|
+
const result = await isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes);
|
|
105
|
+
expect(result).toBe(false);
|
|
103
106
|
expect(fs.remove).toHaveBeenCalledWith(mockTempDir);
|
|
104
107
|
});
|
|
105
108
|
it('should handle translateForLocalDev errors', async () => {
|
|
@@ -111,7 +114,8 @@ describe('isDeployedProjectUpToDateWithLocal', () => {
|
|
|
111
114
|
extractZipArchive.mockResolvedValue(undefined);
|
|
112
115
|
// Mock translate to throw an error
|
|
113
116
|
translate.mockRejectedValue(new Error('Translation Error'));
|
|
114
|
-
await
|
|
117
|
+
const result = await isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes);
|
|
118
|
+
expect(result).toBe(false);
|
|
115
119
|
expect(fs.remove).toHaveBeenCalledWith(mockTempDir);
|
|
116
120
|
});
|
|
117
121
|
});
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isV2Project } from '../platformVersion.js';
|
|
2
2
|
describe('platformVersion', () => {
|
|
3
|
-
describe('
|
|
3
|
+
describe('isV2Project', () => {
|
|
4
4
|
it('returns true if platform version is UNSTABLE', () => {
|
|
5
|
-
expect(
|
|
5
|
+
expect(isV2Project('UNSTABLE')).toBe(true);
|
|
6
6
|
});
|
|
7
7
|
it('returns true if platform version is equal to the minimum', () => {
|
|
8
|
-
expect(
|
|
8
|
+
expect(isV2Project('2025.2')).toBe(true);
|
|
9
9
|
});
|
|
10
10
|
it('returns true if platform version is greater than the minimum', () => {
|
|
11
|
-
expect(
|
|
11
|
+
expect(isV2Project('2026.2')).toBe(true);
|
|
12
12
|
});
|
|
13
13
|
it('returns false if platform version is less than the minimum', () => {
|
|
14
|
-
expect(
|
|
14
|
+
expect(isV2Project('2025.0')).toBe(false);
|
|
15
15
|
});
|
|
16
16
|
it('returns false if platform version is invalid', () => {
|
|
17
|
-
expect(
|
|
17
|
+
expect(isV2Project(null)).toBe(false);
|
|
18
18
|
});
|
|
19
19
|
it('returns false for an invalid platform version', () => {
|
|
20
|
-
expect(
|
|
20
|
+
expect(isV2Project('notplaformversion')).toBe(false);
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
23
|
});
|
|
@@ -3,8 +3,8 @@ import os from 'os';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { EXIT_CODES } from '../../enums/exitCodes.js';
|
|
5
5
|
import { validateProjectConfig } from '../../projects/config.js';
|
|
6
|
-
import {
|
|
7
|
-
vi.mock('
|
|
6
|
+
import { uiLogger } from '../../ui/logger.js';
|
|
7
|
+
vi.mock('../../ui/logger.js');
|
|
8
8
|
describe('lib/projects', () => {
|
|
9
9
|
describe('validateProjectConfig()', () => {
|
|
10
10
|
let projectDir;
|
|
@@ -26,58 +26,58 @@ describe('lib/projects', () => {
|
|
|
26
26
|
// @ts-ignore Testing invalid input
|
|
27
27
|
validateProjectConfig(null, projectDir);
|
|
28
28
|
expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
|
|
29
|
-
expect(
|
|
29
|
+
expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/.*Unable to locate a project configuration file. Try running again from a project directory, or run*/));
|
|
30
30
|
});
|
|
31
31
|
it('rejects configuration with missing name', () => {
|
|
32
32
|
// @ts-ignore Testing invalid input
|
|
33
33
|
validateProjectConfig({ srcDir: '.' }, projectDir);
|
|
34
34
|
expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
|
|
35
|
-
expect(
|
|
35
|
+
expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/.*missing required fields*/));
|
|
36
36
|
});
|
|
37
37
|
it('rejects configuration with missing srcDir', () => {
|
|
38
38
|
// @ts-ignore Testing invalid input
|
|
39
39
|
validateProjectConfig({ name: 'hello' }, projectDir);
|
|
40
40
|
expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
|
|
41
|
-
expect(
|
|
41
|
+
expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/.*missing required fields.*/));
|
|
42
42
|
});
|
|
43
43
|
describe('rejects configuration with srcDir outside project directory', () => {
|
|
44
44
|
it('for parent directory', () => {
|
|
45
45
|
validateProjectConfig({ name: 'hello', srcDir: '..', platformVersion: '' }, projectDir);
|
|
46
46
|
expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
|
|
47
|
-
expect(
|
|
47
|
+
expect(uiLogger.error).toHaveBeenCalledWith(expect.stringContaining('srcDir: ".."'));
|
|
48
48
|
});
|
|
49
49
|
it('for root directory', () => {
|
|
50
50
|
validateProjectConfig({ name: 'hello', srcDir: '/', platformVersion: '' }, projectDir);
|
|
51
51
|
expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
|
|
52
|
-
expect(
|
|
52
|
+
expect(uiLogger.error).toHaveBeenCalledWith(expect.stringContaining('srcDir: "/"'));
|
|
53
53
|
});
|
|
54
54
|
it('for complicated directory', () => {
|
|
55
55
|
const srcDir = './src/././../src/../../src';
|
|
56
56
|
validateProjectConfig({ name: 'hello', srcDir, platformVersion: '' }, projectDir);
|
|
57
57
|
expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
|
|
58
|
-
expect(
|
|
58
|
+
expect(uiLogger.error).toHaveBeenCalledWith(expect.stringContaining(`srcDir: "${srcDir}"`));
|
|
59
59
|
});
|
|
60
60
|
});
|
|
61
61
|
it('rejects configuration with srcDir that does not exist', () => {
|
|
62
62
|
validateProjectConfig({ name: 'hello', srcDir: 'foo', platformVersion: '' }, projectDir);
|
|
63
63
|
expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
|
|
64
|
-
expect(
|
|
64
|
+
expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/.*could not be found in.*/));
|
|
65
65
|
});
|
|
66
66
|
describe('accepts configuration with valid srcDir', () => {
|
|
67
67
|
it('for current directory', () => {
|
|
68
68
|
validateProjectConfig({ name: 'hello', srcDir: '.', platformVersion: '' }, projectDir);
|
|
69
69
|
expect(exitMock).not.toHaveBeenCalled();
|
|
70
|
-
expect(
|
|
70
|
+
expect(uiLogger.error).not.toHaveBeenCalled();
|
|
71
71
|
});
|
|
72
72
|
it('for relative directory', () => {
|
|
73
73
|
validateProjectConfig({ name: 'hello', srcDir: './src', platformVersion: '' }, projectDir);
|
|
74
74
|
expect(exitMock).not.toHaveBeenCalled();
|
|
75
|
-
expect(
|
|
75
|
+
expect(uiLogger.error).not.toHaveBeenCalled();
|
|
76
76
|
});
|
|
77
77
|
it('for implied relative directory', () => {
|
|
78
78
|
validateProjectConfig({ name: 'hello', srcDir: 'src', platformVersion: '' }, projectDir);
|
|
79
79
|
expect(exitMock).not.toHaveBeenCalled();
|
|
80
|
-
expect(
|
|
80
|
+
expect(uiLogger.error).not.toHaveBeenCalled();
|
|
81
81
|
});
|
|
82
82
|
});
|
|
83
83
|
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import * as HSfs from '@hubspot/local-dev-lib/fs';
|
|
3
|
-
import {
|
|
3
|
+
import { uiLogger } from '../../ui/logger.js';
|
|
4
4
|
import { getComponentTypeFromConfigFile, loadConfigFile, getAppCardConfigs, getIsLegacyApp, componentIsApp, findProjectComponents, getProjectComponentTypes, getComponentUid, componentIsPublicApp, } from '../structure.js';
|
|
5
5
|
import { ComponentTypes } from '../../../types/Projects.js';
|
|
6
6
|
vi.mock('fs');
|
|
7
7
|
vi.mock('@hubspot/local-dev-lib/fs');
|
|
8
|
-
vi.mock('
|
|
8
|
+
vi.mock('../../ui/logger.js');
|
|
9
9
|
const mockedReadFileSync = fs.readFileSync;
|
|
10
10
|
const mockedWalk = HSfs.walk;
|
|
11
11
|
const getMockPrivateAppConfig = (cards = []) => ({
|
|
@@ -46,7 +46,7 @@ describe('lib/projects/structure', () => {
|
|
|
46
46
|
throw new Error('File not found');
|
|
47
47
|
});
|
|
48
48
|
expect(loadConfigFile('nonexistent/path/app.json')).toBeNull();
|
|
49
|
-
expect(
|
|
49
|
+
expect(uiLogger.debug).toHaveBeenCalled();
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
52
|
describe('getAppCardConfigs()', () => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { vi } from 'vitest';
|
|
5
|
+
import { validateSourceDirectory } from '../upload.js';
|
|
6
|
+
import { uiLogger } from '../../ui/logger.js';
|
|
7
|
+
import { lib } from '../../../lang/en.js';
|
|
8
|
+
import { isV2Project } from '../platformVersion.js';
|
|
9
|
+
import ProjectValidationError from '../../errors/ProjectValidationError.js';
|
|
10
|
+
import { walk } from '@hubspot/local-dev-lib/fs';
|
|
11
|
+
// Mock dependencies
|
|
12
|
+
vi.mock('../../ui/logger.js');
|
|
13
|
+
vi.mock('../platformVersion.js');
|
|
14
|
+
vi.mock('@hubspot/local-dev-lib/fs');
|
|
15
|
+
describe('lib/projects/upload', () => {
|
|
16
|
+
describe('validateSourceDirectory', () => {
|
|
17
|
+
let tempDir;
|
|
18
|
+
let srcDir;
|
|
19
|
+
let projectConfig;
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'upload-test-'));
|
|
22
|
+
srcDir = path.join(tempDir, 'src');
|
|
23
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
24
|
+
projectConfig = {
|
|
25
|
+
name: 'test-project',
|
|
26
|
+
srcDir: 'src',
|
|
27
|
+
platformVersion: '2025.2',
|
|
28
|
+
};
|
|
29
|
+
vi.clearAllMocks();
|
|
30
|
+
});
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
fs.removeSync(tempDir);
|
|
33
|
+
});
|
|
34
|
+
it('should throw ProjectValidationError when source directory is empty', async () => {
|
|
35
|
+
vi.mocked(walk).mockResolvedValue([]);
|
|
36
|
+
await expect(validateSourceDirectory(srcDir, projectConfig, tempDir)).rejects.toThrow(ProjectValidationError);
|
|
37
|
+
expect(walk).toHaveBeenCalledWith(srcDir, ['node_modules']);
|
|
38
|
+
});
|
|
39
|
+
it('should warn about legacy files in V2 projects', async () => {
|
|
40
|
+
vi.mocked(isV2Project).mockReturnValue(true);
|
|
41
|
+
const legacyFilePath = path.join(srcDir, 'app', 'serverless.json');
|
|
42
|
+
vi.mocked(walk).mockResolvedValue([legacyFilePath]);
|
|
43
|
+
await validateSourceDirectory(srcDir, projectConfig, tempDir);
|
|
44
|
+
expect(uiLogger.warn).toHaveBeenCalledWith(lib.projectUpload.handleProjectUpload.legacyFileDetected('src/app/serverless.json', '2025.2'));
|
|
45
|
+
});
|
|
46
|
+
it('should warn about multiple legacy files', async () => {
|
|
47
|
+
vi.mocked(isV2Project).mockReturnValue(true);
|
|
48
|
+
const filePaths = [
|
|
49
|
+
path.join(srcDir, 'app1', 'serverless.json'),
|
|
50
|
+
path.join(srcDir, 'app2', 'app.json'),
|
|
51
|
+
path.join(srcDir, 'app3', 'public-app.json'),
|
|
52
|
+
];
|
|
53
|
+
vi.mocked(walk).mockResolvedValue(filePaths);
|
|
54
|
+
await validateSourceDirectory(srcDir, projectConfig, tempDir);
|
|
55
|
+
expect(uiLogger.warn).toHaveBeenCalledTimes(3);
|
|
56
|
+
expect(uiLogger.warn).toHaveBeenCalledWith(lib.projectUpload.handleProjectUpload.legacyFileDetected('src/app1/serverless.json', '2025.2'));
|
|
57
|
+
expect(uiLogger.warn).toHaveBeenCalledWith(lib.projectUpload.handleProjectUpload.legacyFileDetected('src/app2/app.json', '2025.2'));
|
|
58
|
+
expect(uiLogger.warn).toHaveBeenCalledWith(lib.projectUpload.handleProjectUpload.legacyFileDetected('src/app3/public-app.json', '2025.2'));
|
|
59
|
+
});
|
|
60
|
+
it('should not warn about non-legacy files', async () => {
|
|
61
|
+
vi.mocked(isV2Project).mockReturnValue(true);
|
|
62
|
+
const filePaths = [
|
|
63
|
+
path.join(srcDir, 'component.js'),
|
|
64
|
+
path.join(srcDir, 'config.json'),
|
|
65
|
+
];
|
|
66
|
+
vi.mocked(walk).mockResolvedValue(filePaths);
|
|
67
|
+
await validateSourceDirectory(srcDir, projectConfig, tempDir);
|
|
68
|
+
expect(uiLogger.warn).not.toHaveBeenCalled();
|
|
69
|
+
});
|
|
70
|
+
it('should not warn about legacy files in non-V2 projects', async () => {
|
|
71
|
+
vi.mocked(isV2Project).mockReturnValue(false);
|
|
72
|
+
projectConfig.platformVersion = '2025.1';
|
|
73
|
+
const filePaths = [
|
|
74
|
+
path.join(srcDir, 'app', 'serverless.json'),
|
|
75
|
+
path.join(srcDir, 'app', 'app.json'),
|
|
76
|
+
];
|
|
77
|
+
vi.mocked(walk).mockResolvedValue(filePaths);
|
|
78
|
+
await validateSourceDirectory(srcDir, projectConfig, tempDir);
|
|
79
|
+
expect(uiLogger.warn).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|