@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.
- 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/project/upload.d.ts +2 -2
- package/commands/project/upload.js +1 -1
- 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 +160 -46
- package/lang/en.js +175 -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 +0 -2
- package/lib/mcp/setup.js +0 -24
- package/lib/process.js +15 -4
- package/lib/projectProfiles.d.ts +1 -1
- package/lib/projectProfiles.js +10 -2
- 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/HsListTool.d.ts +23 -0
- package/mcp-server/tools/cms/HsListTool.js +58 -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 +8 -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/types/Yargs.d.ts +1 -1
- package/ui/index.d.ts +1 -0
- package/ui/index.js +6 -0
package/lib/hasFeature.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { FEATURES } from './constants.js';
|
|
2
2
|
import { ValueOf } from '@hubspot/local-dev-lib/types/Utils';
|
|
3
3
|
export declare function hasFeature(accountId: number, feature: ValueOf<typeof FEATURES>): Promise<boolean>;
|
|
4
|
+
export declare function hasUnfiedAppsAccess(accountId: number): Promise<boolean>;
|
package/lib/hasFeature.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import { http } from '@hubspot/local-dev-lib/http';
|
|
1
2
|
import { fetchEnabledFeatures } from '@hubspot/local-dev-lib/api/localDevAuth';
|
|
2
3
|
export async function hasFeature(accountId, feature) {
|
|
3
4
|
const { data: { enabledFeatures }, } = await fetchEnabledFeatures(accountId);
|
|
4
5
|
return Boolean(enabledFeatures[feature]);
|
|
5
6
|
}
|
|
7
|
+
export async function hasUnfiedAppsAccess(accountId) {
|
|
8
|
+
const response = await http.get(accountId, {
|
|
9
|
+
url: 'developer-tooling/external/developer-portal/has-unified-dev-platform-access',
|
|
10
|
+
});
|
|
11
|
+
return Boolean(response.data);
|
|
12
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ImportRequest } from '@hubspot/local-dev-lib/types/Crm';
|
|
2
|
+
export declare function handleImportData(targetAccountId: number, dataFileNames: string[], importRequest: ImportRequest): Promise<void>;
|
|
3
|
+
export declare function handleTargetTestAccountSelectionFlow(derivedAccountId: number, userProvidedAccount: string | number | undefined): Promise<number>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getAccountConfig, getAccountId, getEnv, } from '@hubspot/local-dev-lib/config';
|
|
2
|
+
import { createImport } from '@hubspot/local-dev-lib/api/crm';
|
|
3
|
+
import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
|
|
4
|
+
import { importDataTestAccountSelectPrompt } from './prompts/importDataTestAccountSelectPrompt.js';
|
|
5
|
+
import { lib } from '../lang/en.js';
|
|
6
|
+
import { isAppDeveloperAccount, isDeveloperTestAccount, isStandardAccount, } from './accountTypes.js';
|
|
7
|
+
import { uiLogger } from './ui/logger.js';
|
|
8
|
+
export async function handleImportData(targetAccountId, dataFileNames, importRequest) {
|
|
9
|
+
try {
|
|
10
|
+
const baseUrl = getHubSpotWebsiteOrigin(getEnv());
|
|
11
|
+
const response = await createImport(targetAccountId, importRequest, dataFileNames);
|
|
12
|
+
const importId = response.data.id;
|
|
13
|
+
uiLogger.success(lib.importData.viewImportLink(baseUrl, targetAccountId, importId));
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
uiLogger.error(lib.importData.errors.failedToImportData);
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function handleTargetTestAccountSelectionFlow(derivedAccountId, userProvidedAccount) {
|
|
21
|
+
let targetAccountId = null;
|
|
22
|
+
if (userProvidedAccount) {
|
|
23
|
+
targetAccountId = getAccountId(userProvidedAccount);
|
|
24
|
+
}
|
|
25
|
+
// Only allow users to pass in test accounts
|
|
26
|
+
if (targetAccountId) {
|
|
27
|
+
const testAccount = getAccountConfig(targetAccountId);
|
|
28
|
+
if (!testAccount || !isDeveloperTestAccount(testAccount)) {
|
|
29
|
+
throw new Error(lib.importData.errors.notDeveloperTestAccount);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const targetProjectAccountConfig = getAccountConfig(derivedAccountId);
|
|
34
|
+
if (!targetProjectAccountConfig) {
|
|
35
|
+
throw new Error(lib.importData.errors.noAccountConfig(derivedAccountId));
|
|
36
|
+
}
|
|
37
|
+
if (isDeveloperTestAccount(targetProjectAccountConfig)) {
|
|
38
|
+
targetAccountId = derivedAccountId;
|
|
39
|
+
}
|
|
40
|
+
else if (!isStandardAccount(targetProjectAccountConfig) &&
|
|
41
|
+
!isAppDeveloperAccount(targetProjectAccountConfig)) {
|
|
42
|
+
throw new Error(lib.importData.errors.incorrectAccountType(derivedAccountId));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const { selectedAccountId } = await importDataTestAccountSelectPrompt(derivedAccountId);
|
|
46
|
+
targetAccountId = selectedAccountId;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return targetAccountId;
|
|
50
|
+
}
|
package/lib/mcp/setup.d.ts
CHANGED
|
@@ -15,8 +15,6 @@ interface McpCommand {
|
|
|
15
15
|
command: string;
|
|
16
16
|
args: string[];
|
|
17
17
|
}
|
|
18
|
-
export declare function addMintlifyMcpServer(installTargets: string[]): Promise<void>;
|
|
19
|
-
export declare function setupMintlify(derivedTargets?: string[]): Promise<boolean>;
|
|
20
18
|
export declare function addMcpServerToConfig(targets: string[] | undefined): Promise<string[]>;
|
|
21
19
|
export declare function setupVsCode(mcpCommand?: McpCommand): Promise<boolean>;
|
|
22
20
|
export declare function setupClaudeCode(mcpCommand?: McpCommand): Promise<boolean>;
|
package/lib/mcp/setup.js
CHANGED
|
@@ -4,7 +4,6 @@ import { promptUser } from '../prompts/promptUtils.js';
|
|
|
4
4
|
import SpinniesManager from '../ui/SpinniesManager.js';
|
|
5
5
|
import { logError } from '../errorHandlers/index.js';
|
|
6
6
|
import { execAsync } from '../../mcp-server/utils/command.js';
|
|
7
|
-
import { spawn } from 'node:child_process';
|
|
8
7
|
import path from 'path';
|
|
9
8
|
import os from 'os';
|
|
10
9
|
import fs from 'fs-extra';
|
|
@@ -14,7 +13,6 @@ const claudeCode = 'claude';
|
|
|
14
13
|
const windsurf = 'windsurf';
|
|
15
14
|
const cursor = 'cursor';
|
|
16
15
|
const vscode = 'vscode';
|
|
17
|
-
const supportedMintlifyClients = [windsurf, cursor];
|
|
18
16
|
export const supportedTools = [
|
|
19
17
|
{ name: commands.mcp.setup.claudeCode, value: claudeCode },
|
|
20
18
|
{ name: commands.mcp.setup.cursor, value: cursor },
|
|
@@ -25,28 +23,6 @@ const defaultMcpCommand = {
|
|
|
25
23
|
command: 'hs',
|
|
26
24
|
args: ['mcp', 'start'],
|
|
27
25
|
};
|
|
28
|
-
export async function addMintlifyMcpServer(installTargets) {
|
|
29
|
-
await runSetupFunction(() => setupMintlify(installTargets));
|
|
30
|
-
}
|
|
31
|
-
export async function setupMintlify(derivedTargets = supportedMintlifyClients) {
|
|
32
|
-
uiLogger.info(commands.mcp.setup.installingDocSearch);
|
|
33
|
-
uiLogger.log('');
|
|
34
|
-
return new Promise(resolve => {
|
|
35
|
-
const subcommands = ['mint-mcp', 'add', 'hubspot-migration'];
|
|
36
|
-
const docsSearchClients = derivedTargets.filter(target => supportedMintlifyClients.includes(target));
|
|
37
|
-
const childProcess = spawn(`npx`, docsSearchClients && docsSearchClients.length
|
|
38
|
-
? [...subcommands, '--client', ...docsSearchClients]
|
|
39
|
-
: subcommands, {
|
|
40
|
-
stdio: 'inherit',
|
|
41
|
-
});
|
|
42
|
-
childProcess.on('exit', code => {
|
|
43
|
-
if (code !== 0) {
|
|
44
|
-
resolve(false);
|
|
45
|
-
}
|
|
46
|
-
resolve(true);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
26
|
export async function addMcpServerToConfig(targets) {
|
|
51
27
|
try {
|
|
52
28
|
let derivedTargets = [];
|
package/lib/process.js
CHANGED
|
@@ -1,29 +1,40 @@
|
|
|
1
1
|
import readline from 'readline';
|
|
2
2
|
import { logger, setLogLevel, LOG_LEVEL } from '@hubspot/local-dev-lib/logger';
|
|
3
3
|
import { i18n } from './lang.js';
|
|
4
|
+
import { logError } from './errorHandlers/index.js';
|
|
5
|
+
const SIGHUP = 'SIGHUP';
|
|
6
|
+
const uncaughtException = 'uncaughtException';
|
|
4
7
|
export const TERMINATION_SIGNALS = [
|
|
5
8
|
'beforeExit',
|
|
6
9
|
'SIGINT', // Terminal trying to interrupt (Ctrl + C)
|
|
7
10
|
'SIGUSR1', // Start Debugger User-defined signal 1
|
|
8
11
|
'SIGUSR2', // User-defined signal 2
|
|
9
|
-
|
|
12
|
+
uncaughtException,
|
|
10
13
|
'SIGTERM', // Represents a graceful termination
|
|
11
|
-
|
|
14
|
+
SIGHUP, // Parent terminal has been closed
|
|
12
15
|
];
|
|
13
16
|
export function handleExit(callback) {
|
|
14
17
|
let exitInProgress = false;
|
|
15
18
|
TERMINATION_SIGNALS.forEach(signal => {
|
|
16
19
|
process.removeAllListeners(signal);
|
|
17
|
-
process.on(signal, async () => {
|
|
20
|
+
process.on(signal, async (...args) => {
|
|
18
21
|
// Prevent duplicate exit handling
|
|
19
22
|
if (!exitInProgress) {
|
|
20
23
|
exitInProgress = true;
|
|
21
|
-
const isSIGHUP = signal ===
|
|
24
|
+
const isSIGHUP = signal === SIGHUP;
|
|
22
25
|
// Prevent logs when terminal closes
|
|
23
26
|
if (isSIGHUP) {
|
|
24
27
|
setLogLevel(LOG_LEVEL.NONE);
|
|
25
28
|
}
|
|
26
29
|
logger.debug(i18n(`lib.process.exitDebug`, { signal }));
|
|
30
|
+
if (signal === uncaughtException && args && args.length > 0) {
|
|
31
|
+
try {
|
|
32
|
+
logError(args[0]);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
logger.error(args[0]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
27
38
|
await callback({ isSIGHUP });
|
|
28
39
|
}
|
|
29
40
|
});
|
package/lib/projectProfiles.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ export declare function logProfileHeader(profileName: string): void;
|
|
|
4
4
|
export declare function logProfileFooter(profile: HsProfileFile, includeVariables?: boolean): void;
|
|
5
5
|
export declare function loadProfile(projectConfig: ProjectConfig | null, projectDir: string | null, profileName: string): HsProfileFile | undefined;
|
|
6
6
|
export declare function exitIfUsingProfiles(projectConfig: ProjectConfig | null, projectDir: string | null): Promise<void>;
|
|
7
|
-
export declare function loadAndValidateProfile(projectConfig: ProjectConfig | null, projectDir: string | null, argsProfile: string | undefined): Promise<number | undefined>;
|
|
7
|
+
export declare function loadAndValidateProfile(projectConfig: ProjectConfig | null, projectDir: string | null, argsProfile: string | undefined, useEnv?: boolean): Promise<number | undefined>;
|
package/lib/projectProfiles.js
CHANGED
|
@@ -38,7 +38,12 @@ export function loadProfile(projectConfig, projectDir, profileName) {
|
|
|
38
38
|
uiLogger.error(lib.projectProfiles.loadProfile.errors.missingAccountId(profileFilename));
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
-
return
|
|
41
|
+
return {
|
|
42
|
+
...profile,
|
|
43
|
+
accountId: process.env.HUBSPOT_ACCOUNT_ID
|
|
44
|
+
? Number(process.env.HUBSPOT_ACCOUNT_ID)
|
|
45
|
+
: profile.accountId,
|
|
46
|
+
};
|
|
42
47
|
}
|
|
43
48
|
catch (e) {
|
|
44
49
|
uiLogger.error(lib.projectProfiles.loadProfile.errors.failedToLoadProfile(profileFilename));
|
|
@@ -54,7 +59,7 @@ export async function exitIfUsingProfiles(projectConfig, projectDir) {
|
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
|
-
export async function loadAndValidateProfile(projectConfig, projectDir, argsProfile) {
|
|
62
|
+
export async function loadAndValidateProfile(projectConfig, projectDir, argsProfile, useEnv = false) {
|
|
58
63
|
if (argsProfile) {
|
|
59
64
|
logProfileHeader(argsProfile);
|
|
60
65
|
const profile = loadProfile(projectConfig, projectDir, argsProfile);
|
|
@@ -63,6 +68,9 @@ export async function loadAndValidateProfile(projectConfig, projectDir, argsProf
|
|
|
63
68
|
process.exit(EXIT_CODES.ERROR);
|
|
64
69
|
}
|
|
65
70
|
logProfileFooter(profile, true);
|
|
71
|
+
if (useEnv) {
|
|
72
|
+
return Number(process.env.HUBSPOT_ACCOUNT_ID);
|
|
73
|
+
}
|
|
66
74
|
return profile.accountId;
|
|
67
75
|
}
|
|
68
76
|
else {
|
|
@@ -37,6 +37,7 @@ vi.mock('../../ui/logger');
|
|
|
37
37
|
vi.mock('../../errorHandlers/index');
|
|
38
38
|
vi.mock('../localDev/LocalDevState');
|
|
39
39
|
vi.mock('../localDev/LocalDevLogger');
|
|
40
|
+
vi.mock('../../ui/SpinniesManager');
|
|
40
41
|
describe('AppDevModeInterface', () => {
|
|
41
42
|
let appDevModeInterface;
|
|
42
43
|
let mockLocalDevState;
|
|
@@ -98,10 +99,9 @@ describe('AppDevModeInterface', () => {
|
|
|
98
99
|
getAppDataByUid: vi.fn(),
|
|
99
100
|
setAppDataForUid: vi.fn(),
|
|
100
101
|
addListener: vi.fn(),
|
|
101
|
-
};
|
|
102
|
-
mockLocalDevLogger = {
|
|
103
102
|
addUploadWarning: vi.fn(),
|
|
104
103
|
};
|
|
104
|
+
mockLocalDevLogger = {};
|
|
105
105
|
// Mock constructors
|
|
106
106
|
LocalDevState.mockImplementation(() => mockLocalDevState);
|
|
107
107
|
LocalDevLogger.mockImplementation(() => mockLocalDevLogger);
|
|
@@ -211,7 +211,7 @@ describe('AppDevModeInterface', () => {
|
|
|
211
211
|
};
|
|
212
212
|
await appDevModeInterface.setup({});
|
|
213
213
|
expect(confirmPrompt).toHaveBeenCalled();
|
|
214
|
-
expect(
|
|
214
|
+
expect(mockLocalDevState.addUploadWarning).toHaveBeenCalled();
|
|
215
215
|
});
|
|
216
216
|
it('should exit if user declines marketplace warning', async () => {
|
|
217
217
|
const marketplaceAppNode = {
|
|
@@ -32,51 +32,14 @@ describe('LocalDevProcess', () => {
|
|
|
32
32
|
srcDir: 'src',
|
|
33
33
|
platformVersion: '1.0.0',
|
|
34
34
|
};
|
|
35
|
-
const mockSubbuildStatus = {
|
|
36
|
-
buildName: 'component1',
|
|
37
|
-
buildType: 'APP',
|
|
38
|
-
errorMessage: '',
|
|
39
|
-
finishedAt: new Date().toISOString(),
|
|
40
|
-
rootPath: '/test/path',
|
|
41
|
-
startedAt: new Date().toISOString(),
|
|
42
|
-
status: 'SUCCESS',
|
|
43
|
-
id: '123',
|
|
44
|
-
standardError: null,
|
|
45
|
-
visible: true,
|
|
46
|
-
};
|
|
47
|
-
const mockBuild = {
|
|
48
|
-
activitySource: { type: 'HUBSPOT' },
|
|
49
|
-
projectName: 'test-project',
|
|
50
|
-
uploadMessage: 'test-upload-message',
|
|
51
|
-
autoDeployId: 123,
|
|
52
|
-
buildId: 123,
|
|
53
|
-
createdAt: new Date().toISOString(),
|
|
54
|
-
deployableState: 'DEPLOYED',
|
|
55
|
-
finishedAt: new Date().toISOString(),
|
|
56
|
-
startedAt: new Date().toISOString(),
|
|
57
|
-
status: 'SUCCESS',
|
|
58
|
-
subbuildStatuses: [
|
|
59
|
-
{ ...mockSubbuildStatus, buildName: 'component1' },
|
|
60
|
-
{ ...mockSubbuildStatus, buildName: 'component2' },
|
|
61
|
-
],
|
|
62
|
-
deployStatusTaskLocator: {
|
|
63
|
-
id: '123',
|
|
64
|
-
links: [],
|
|
65
|
-
},
|
|
66
|
-
enqueuedAt: new Date().toISOString(),
|
|
67
|
-
isAutoDeployEnabled: false,
|
|
68
|
-
portalId: 123,
|
|
69
|
-
};
|
|
70
35
|
const mockOptions = {
|
|
71
36
|
projectDir: '/test/project',
|
|
72
37
|
projectConfig: mockProjectConfig,
|
|
73
38
|
targetProjectAccountId: 123,
|
|
74
39
|
targetTestingAccountId: 456,
|
|
75
40
|
projectId: 789,
|
|
76
|
-
isGithubLinked: false,
|
|
77
41
|
initialProjectNodes: {},
|
|
78
42
|
env: ENVIRONMENTS.PROD,
|
|
79
|
-
deployedBuild: mockBuild,
|
|
80
43
|
projectName: 'test-project',
|
|
81
44
|
};
|
|
82
45
|
beforeEach(() => {
|
|
@@ -87,7 +50,6 @@ describe('LocalDevProcess', () => {
|
|
|
87
50
|
devServerStartError: vi.fn(),
|
|
88
51
|
devServerCleanupError: vi.fn(),
|
|
89
52
|
missingComponentsWarning: vi.fn(),
|
|
90
|
-
noDeployedBuild: vi.fn(),
|
|
91
53
|
startupMessage: vi.fn(),
|
|
92
54
|
monitorConsoleOutput: vi.fn(),
|
|
93
55
|
cleanupStart: vi.fn(),
|
|
@@ -97,7 +59,6 @@ describe('LocalDevProcess', () => {
|
|
|
97
59
|
projectConfigMismatch: vi.fn(),
|
|
98
60
|
uploadError: vi.fn(),
|
|
99
61
|
uploadSuccess: vi.fn(),
|
|
100
|
-
clearUploadWarnings: vi.fn(),
|
|
101
62
|
fileChangeError: vi.fn(),
|
|
102
63
|
uploadWarning: vi.fn(),
|
|
103
64
|
};
|
|
@@ -118,14 +79,6 @@ describe('LocalDevProcess', () => {
|
|
|
118
79
|
});
|
|
119
80
|
});
|
|
120
81
|
describe('start()', () => {
|
|
121
|
-
it('should exit if no deployed build exists', async () => {
|
|
122
|
-
const processWithoutBuild = new LocalDevProcess({
|
|
123
|
-
...mockOptions,
|
|
124
|
-
deployedBuild: undefined,
|
|
125
|
-
});
|
|
126
|
-
await expect(processWithoutBuild.start()).rejects.toThrow('Process.exit called with code 0');
|
|
127
|
-
expect(mockLocalDevLogger.noDeployedBuild).toHaveBeenCalled();
|
|
128
|
-
});
|
|
129
82
|
it('should exit if dev server setup fails', async () => {
|
|
130
83
|
mockDevServerManager.setup.mockRejectedValue(new Error('Setup failed'));
|
|
131
84
|
await expect(process.start()).rejects.toThrow('Process.exit called with code 1');
|
|
@@ -138,31 +91,6 @@ describe('LocalDevProcess', () => {
|
|
|
138
91
|
expect(mockLocalDevLogger.monitorConsoleOutput).toHaveBeenCalled();
|
|
139
92
|
expect(mockLocalDevLogger.missingComponentsWarning).not.toHaveBeenCalled();
|
|
140
93
|
});
|
|
141
|
-
it('should warn about missing components', async () => {
|
|
142
|
-
const mockNode = {
|
|
143
|
-
uid: 'component3',
|
|
144
|
-
componentType: 'APP',
|
|
145
|
-
localDev: {
|
|
146
|
-
componentRoot: '/test/path',
|
|
147
|
-
componentConfigPath: '/test/path/config.json',
|
|
148
|
-
configUpdatedSinceLastUpload: false,
|
|
149
|
-
},
|
|
150
|
-
componentDeps: {},
|
|
151
|
-
metaFilePath: '/test/path',
|
|
152
|
-
config: {},
|
|
153
|
-
files: [],
|
|
154
|
-
};
|
|
155
|
-
const processWithNode = new LocalDevProcess({
|
|
156
|
-
...mockOptions,
|
|
157
|
-
initialProjectNodes: {
|
|
158
|
-
component3: mockNode,
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
await processWithNode.start();
|
|
162
|
-
expect(mockLocalDevLogger.missingComponentsWarning).toHaveBeenCalledWith([
|
|
163
|
-
'[App] component3',
|
|
164
|
-
]);
|
|
165
|
-
});
|
|
166
94
|
});
|
|
167
95
|
describe('stop()', () => {
|
|
168
96
|
it('should exit with error if cleanup fails', async () => {
|
|
@@ -226,7 +154,8 @@ describe('LocalDevProcess', () => {
|
|
|
226
154
|
});
|
|
227
155
|
const success = await process.uploadProject();
|
|
228
156
|
expect(mockLocalDevLogger.uploadSuccess).toHaveBeenCalled();
|
|
229
|
-
expect
|
|
157
|
+
// @ts-expect-error accessing private property for testing
|
|
158
|
+
expect(process.state.uploadWarnings.size).toBe(0);
|
|
230
159
|
expect(success).toBe(true);
|
|
231
160
|
});
|
|
232
161
|
it('should reset projectNodesAtLastUpload', async () => {
|
|
@@ -300,10 +229,10 @@ describe('LocalDevProcess', () => {
|
|
|
300
229
|
process.state.projectNodes = {};
|
|
301
230
|
expect(listener).toHaveBeenCalled();
|
|
302
231
|
});
|
|
303
|
-
it('should call listener immediately
|
|
232
|
+
it('should call listener immediately', () => {
|
|
304
233
|
const listener = vi.fn();
|
|
305
234
|
const key = 'projectNodes';
|
|
306
|
-
process.addStateListener(key, listener
|
|
235
|
+
process.addStateListener(key, listener);
|
|
307
236
|
expect(listener).toHaveBeenCalledWith(process.projectNodes);
|
|
308
237
|
});
|
|
309
238
|
});
|
|
@@ -313,32 +242,13 @@ describe('LocalDevProcess', () => {
|
|
|
313
242
|
const key = 'projectNodes';
|
|
314
243
|
// Add the listener first
|
|
315
244
|
process.addStateListener(key, listener);
|
|
316
|
-
// Trigger state change to verify listener is called
|
|
317
|
-
// @ts-expect-error
|
|
318
|
-
process.state.projectNodes = {};
|
|
319
245
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
320
246
|
// Remove the listener
|
|
321
247
|
process.removeStateListener(key, listener);
|
|
322
248
|
// Trigger state change again to verify listener is no longer called
|
|
323
249
|
// @ts-expect-error
|
|
324
250
|
process.state.projectNodes = { newNode: { uid: 'newNode' } };
|
|
325
|
-
expect(listener).toHaveBeenCalledTimes(1);
|
|
326
|
-
});
|
|
327
|
-
it('should not affect other listeners when removing one', () => {
|
|
328
|
-
const listener1 = vi.fn();
|
|
329
|
-
const listener2 = vi.fn();
|
|
330
|
-
const key = 'projectNodes';
|
|
331
|
-
// Add two listeners
|
|
332
|
-
process.addStateListener(key, listener1);
|
|
333
|
-
process.addStateListener(key, listener2);
|
|
334
|
-
// Remove only the first listener
|
|
335
|
-
process.removeStateListener(key, listener1);
|
|
336
|
-
// Trigger state change
|
|
337
|
-
// @ts-expect-error
|
|
338
|
-
process.state.projectNodes = {};
|
|
339
|
-
// Only listener2 should be called
|
|
340
|
-
expect(listener1).not.toHaveBeenCalled();
|
|
341
|
-
expect(listener2).toHaveBeenCalled();
|
|
251
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
342
252
|
});
|
|
343
253
|
});
|
|
344
254
|
});
|
|
@@ -66,7 +66,7 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
66
66
|
headers: { origin: 'https://app.hubspot.com' },
|
|
67
67
|
});
|
|
68
68
|
expect(mockWebSocket.on).toHaveBeenCalledWith('message', expect.any(Function));
|
|
69
|
-
expect(mockLocalDevProcess.addStateListener).toHaveBeenCalledWith('projectNodes', expect.any(Function)
|
|
69
|
+
expect(mockLocalDevProcess.addStateListener).toHaveBeenCalledWith('projectNodes', expect.any(Function));
|
|
70
70
|
expect(mockWebSocket.close).not.toHaveBeenCalled();
|
|
71
71
|
});
|
|
72
72
|
it('should reject connection from invalid origin', async () => {
|
|
@@ -215,7 +215,7 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
215
215
|
expect(mockWebSocket2.on).toHaveBeenCalledWith('message', expect.any(Function));
|
|
216
216
|
expect(mockWebSocket3.on).toHaveBeenCalledWith('message', expect.any(Function));
|
|
217
217
|
// Each connection should trigger state listener setup
|
|
218
|
-
expect(mockLocalDevProcess.addStateListener).toHaveBeenCalledTimes(
|
|
218
|
+
expect(mockLocalDevProcess.addStateListener).toHaveBeenCalledTimes(9); // 3 listeners per connection * 3 connections
|
|
219
219
|
// Each connection should trigger dev server message
|
|
220
220
|
expect(mockLocalDevProcess.sendDevServerMessage).toHaveBeenCalledTimes(3);
|
|
221
221
|
expect(mockLocalDevProcess.sendDevServerMessage).toHaveBeenCalledWith(LOCAL_DEV_SERVER_MESSAGE_TYPES.WEBSOCKET_SERVER_CONNECTED);
|
|
@@ -284,16 +284,16 @@ describe('LocalDevWebsocketServer', () => {
|
|
|
284
284
|
const closeCallbacks2 = mockWebSocket2.on.mock.calls
|
|
285
285
|
.filter(call => call[0] === 'close')
|
|
286
286
|
.map(call => call[1]);
|
|
287
|
-
expect(closeCallbacks1).toHaveLength(
|
|
288
|
-
expect(closeCallbacks2).toHaveLength(
|
|
287
|
+
expect(closeCallbacks1).toHaveLength(3); // projectNodes and appData listeners
|
|
288
|
+
expect(closeCallbacks2).toHaveLength(3); // projectNodes and appData listeners
|
|
289
289
|
// Simulate first connection closing (call all close callbacks)
|
|
290
290
|
closeCallbacks1.forEach(callback => callback());
|
|
291
291
|
// Should have removed listeners for first connection (2 listeners: projectNodes and appData)
|
|
292
|
-
expect(mockLocalDevProcess.removeStateListener).toHaveBeenCalledTimes(
|
|
292
|
+
expect(mockLocalDevProcess.removeStateListener).toHaveBeenCalledTimes(3);
|
|
293
293
|
// Simulate second connection closing
|
|
294
294
|
closeCallbacks2.forEach(callback => callback());
|
|
295
295
|
// Should have removed listeners for second connection as well
|
|
296
|
-
expect(mockLocalDevProcess.removeStateListener).toHaveBeenCalledTimes(
|
|
296
|
+
expect(mockLocalDevProcess.removeStateListener).toHaveBeenCalledTimes(6);
|
|
297
297
|
});
|
|
298
298
|
it('should broadcast state changes to all connected clients', () => {
|
|
299
299
|
// Establish connections
|
|
@@ -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 {};
|