@hubspot/cli 8.0.10-experimental.1 → 8.0.10-experimental.3
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/commands/account/auth.js +15 -5
- package/commands/account/use.js +14 -4
- package/commands/auth.js +10 -6
- package/commands/cms/__tests__/watch.test.js +0 -8
- package/commands/cms/function/logs.js +1 -0
- package/commands/cms/theme/preview.js +2 -4
- package/commands/cms/watch.d.ts +0 -1
- package/commands/cms/watch.js +2 -8
- package/commands/feedback.js +1 -1
- package/commands/hubdb/clear.js +4 -0
- package/commands/hubdb/delete.js +4 -0
- package/commands/hubdb/fetch.js +4 -0
- package/commands/init.js +4 -0
- package/commands/mcp/__tests__/start.test.js +8 -1
- package/commands/mcp/setup.js +1 -9
- package/commands/mcp/start.js +0 -1
- package/commands/project/__tests__/create.test.js +1 -1
- package/commands/project/create.js +2 -2
- package/commands/project/dev/index.js +29 -19
- package/commands/project/download.js +5 -1
- package/commands/project/watch.js +15 -2
- package/commands/sandbox/__tests__/create.test.js +1 -48
- package/commands/sandbox/create.js +3 -30
- package/commands/testAccount/create.js +4 -0
- package/lang/en.d.ts +13 -6
- package/lang/en.js +13 -6
- package/lib/__tests__/buildAccount.test.js +1 -52
- package/lib/__tests__/sandboxes.test.js +1 -29
- package/lib/__tests__/serverlessLogs.test.js +79 -64
- package/lib/accountAuth.js +4 -0
- package/lib/buildAccount.d.ts +1 -6
- package/lib/buildAccount.js +9 -42
- package/lib/constants.d.ts +1 -3
- package/lib/constants.js +1 -3
- package/lib/errors/PromptExitError.d.ts +4 -0
- package/lib/errors/PromptExitError.js +8 -0
- package/lib/generateSelectors.js +1 -2
- package/lib/mcp/__tests__/setup.test.js +357 -28
- package/lib/mcp/setup.d.ts +1 -0
- package/lib/mcp/setup.js +77 -30
- package/lib/projects/__tests__/components.test.js +14 -0
- package/lib/projects/components.js +12 -2
- package/lib/projects/create/__tests__/legacy.test.js +6 -24
- package/lib/projects/create/index.js +1 -4
- package/lib/projects/create/legacy.js +3 -8
- package/lib/projects/create/v2.js +1 -9
- package/lib/projects/ensureProjectExists.js +1 -2
- package/lib/projects/localDev/AppDevModeInterface.js +4 -0
- package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +4 -0
- package/lib/projects/localDev/helpers/account.js +5 -11
- package/lib/projects/pollProjectBuildAndDeploy.js +90 -85
- package/lib/projects/upload.d.ts +1 -0
- package/lib/projects/upload.js +37 -46
- package/lib/projects/watch.d.ts +2 -1
- package/lib/projects/watch.js +32 -24
- package/lib/prompts/downloadProjectPrompt.js +11 -10
- package/lib/prompts/installAppPrompt.js +3 -2
- package/lib/prompts/personalAccessKeyPrompt.js +3 -2
- package/lib/prompts/projectDevTargetAccountPrompt.js +13 -16
- package/lib/prompts/selectHubDBTablePrompt.js +8 -4
- package/lib/prompts/selectPublicAppForMigrationPrompt.js +12 -6
- package/lib/sandboxes.d.ts +1 -9
- package/lib/sandboxes.js +0 -21
- package/lib/serverlessLogs.js +50 -44
- package/lib/{cms/devServerProcess.d.ts → theme/cmsDevServerProcess.d.ts} +2 -3
- package/lib/theme/cmsDevServerProcess.js +148 -0
- package/lib/theme/cmsDevServerRunner.d.ts +14 -0
- package/lib/theme/cmsDevServerRunner.js +90 -0
- package/lib/usageTracking.js +8 -5
- package/mcp-server/tools/cms/HsCreateFunctionTool.js +1 -1
- package/mcp-server/tools/cms/HsCreateModuleTool.js +1 -1
- package/mcp-server/tools/cms/HsCreateTemplateTool.js +1 -1
- package/mcp-server/tools/cms/HsFunctionLogsTool.js +1 -1
- package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
- package/mcp-server/tools/cms/HsListTool.js +1 -1
- package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -2
- package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +1 -2
- package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +1 -2
- package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +1 -2
- package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +1 -2
- package/mcp-server/tools/cms/__tests__/HsListTool.test.js +1 -2
- package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +1 -1
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +1 -1
- package/mcp-server/tools/project/CreateProjectTool.d.ts +1 -1
- package/mcp-server/tools/project/CreateProjectTool.js +1 -1
- package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
- package/mcp-server/tools/project/DeployProjectTool.js +1 -1
- package/mcp-server/tools/project/UploadProjectTools.js +1 -1
- package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
- package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +1 -2
- package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +1 -2
- package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +1 -2
- package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +1 -2
- package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +10 -2
- package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +2 -2
- package/mcp-server/tools/project/constants.d.ts +1 -1
- package/mcp-server/utils/__tests__/command.test.js +233 -3
- package/mcp-server/utils/__tests__/feedbackTracking.test.js +9 -64
- package/mcp-server/utils/command.d.ts +5 -0
- package/mcp-server/utils/command.js +24 -0
- package/mcp-server/utils/feedbackTracking.js +2 -17
- package/package.json +4 -5
- package/lib/__tests__/sandboxSync.test.d.ts +0 -1
- package/lib/__tests__/sandboxSync.test.js +0 -147
- package/lib/cms/devServerProcess.js +0 -200
- package/lib/sandboxSync.d.ts +0 -4
- package/lib/sandboxSync.js +0 -102
- package/mcp-server/utils/__tests__/project.test.d.ts +0 -1
- package/mcp-server/utils/__tests__/project.test.js +0 -140
- package/mcp-server/utils/project.d.ts +0 -5
- package/mcp-server/utils/project.js +0 -18
package/lib/projects/watch.js
CHANGED
|
@@ -18,6 +18,7 @@ const standbyQueue = [];
|
|
|
18
18
|
let currentBuildId;
|
|
19
19
|
let handleBuildStatus;
|
|
20
20
|
let handleUserInput;
|
|
21
|
+
let handleWatchTermination = () => { };
|
|
21
22
|
let timer;
|
|
22
23
|
async function processStandByQueue(accountId, projectName, platformVersion) {
|
|
23
24
|
queue.addAll(standbyQueue.map(({ filePath, remotePath, action }) => {
|
|
@@ -37,33 +38,39 @@ function debounceQueueBuild(accountId, projectName, platformVersion) {
|
|
|
37
38
|
clearTimeout(timer);
|
|
38
39
|
}
|
|
39
40
|
timer = setTimeout(async () => {
|
|
40
|
-
uiLogger.debug(commands.project.watch.debug.pause);
|
|
41
|
-
queue.pause();
|
|
42
|
-
await queue.onIdle();
|
|
43
41
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
uiLogger.debug(commands.project.watch.debug.pause);
|
|
43
|
+
queue.pause();
|
|
44
|
+
await queue.onIdle();
|
|
45
|
+
try {
|
|
46
|
+
await queueBuild(accountId, projectName, platformVersion);
|
|
47
|
+
uiLogger.debug(commands.project.watch.debug.buildStarted);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (isSpecifiedError(err, {
|
|
51
|
+
subCategory: PROJECT_ERROR_TYPES.MISSING_PROJECT_PROVISION,
|
|
52
|
+
})) {
|
|
53
|
+
uiLogger.log(commands.project.watch.logs.watchCancelledFromUi);
|
|
54
|
+
handleWatchTermination();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
logError(err, new ApiErrorContext({ accountId }));
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
53
61
|
}
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
await handleBuildStatus(accountId, projectName, currentBuildId);
|
|
63
|
+
await createNewStagingBuild(accountId, projectName, platformVersion);
|
|
64
|
+
if (standbyQueue.length > 0) {
|
|
65
|
+
await processStandByQueue(accountId, projectName, platformVersion);
|
|
56
66
|
}
|
|
57
|
-
|
|
67
|
+
queue.start();
|
|
68
|
+
uiLogger.log(commands.project.watch.logs.resuming);
|
|
69
|
+
uiLogger.log(`\n> Press ${chalk.bold('q')} to quit watching\n`);
|
|
58
70
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (standbyQueue.length > 0) {
|
|
62
|
-
await processStandByQueue(accountId, projectName, platformVersion);
|
|
71
|
+
catch (err) {
|
|
72
|
+
handleWatchTermination(err);
|
|
63
73
|
}
|
|
64
|
-
queue.start();
|
|
65
|
-
uiLogger.log(commands.project.watch.logs.resuming);
|
|
66
|
-
uiLogger.log(`\n> Press ${chalk.bold('q')} to quit watching\n`);
|
|
67
74
|
}, 2000);
|
|
68
75
|
}
|
|
69
76
|
async function queueFileOrFolder(accountId, projectName, platformVersion, filePath, remotePath, action) {
|
|
@@ -111,7 +118,7 @@ async function createNewBuild(accountId, projectName, platformVersion) {
|
|
|
111
118
|
await cancelStagedBuild(accountId, projectName);
|
|
112
119
|
uiLogger.log(commands.project.watch.logs.previousStagingBuildCancelled);
|
|
113
120
|
}
|
|
114
|
-
|
|
121
|
+
throw err;
|
|
115
122
|
}
|
|
116
123
|
}
|
|
117
124
|
async function handleWatchEvent(accountId, projectName, platformVersion, projectSourceDir, filePath, action = 'upload') {
|
|
@@ -132,10 +139,11 @@ async function handleWatchEvent(accountId, projectName, platformVersion, project
|
|
|
132
139
|
await queueFileOrFolder(accountId, projectName, platformVersion, filePath, remotePath, action);
|
|
133
140
|
}
|
|
134
141
|
}
|
|
135
|
-
export async function createWatcher(accountId, projectConfig, projectDir, handleBuildStatusFn, handleUserInputFn) {
|
|
142
|
+
export async function createWatcher(accountId, projectConfig, projectDir, handleBuildStatusFn, handleUserInputFn, handleWatchTerminationFn) {
|
|
136
143
|
const projectSourceDir = path.join(projectDir, projectConfig.srcDir);
|
|
137
144
|
handleBuildStatus = handleBuildStatusFn;
|
|
138
145
|
handleUserInput = handleUserInputFn;
|
|
146
|
+
handleWatchTermination = handleWatchTerminationFn;
|
|
139
147
|
await createNewStagingBuild(accountId, projectConfig.name, projectConfig.platformVersion);
|
|
140
148
|
const watcher = chokidar.watch(projectSourceDir, {
|
|
141
149
|
ignoreInitial: true,
|
|
@@ -2,21 +2,22 @@ import { promptUser } from './promptUtils.js';
|
|
|
2
2
|
import { getConfigAccountIfExists } from '@hubspot/local-dev-lib/config';
|
|
3
3
|
import { fetchProjects } from '@hubspot/local-dev-lib/api/projects';
|
|
4
4
|
import { logError, ApiErrorContext } from '../errorHandlers/index.js';
|
|
5
|
-
import { uiLogger } from '../ui/logger.js';
|
|
6
|
-
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
7
5
|
import { lib } from '../../lang/en.js';
|
|
6
|
+
import { PromptExitError } from '../errors/PromptExitError.js';
|
|
7
|
+
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
8
|
+
import { uiLogger } from '../ui/logger.js';
|
|
8
9
|
async function createProjectsList(accountId) {
|
|
9
|
-
|
|
10
|
-
if (accountId) {
|
|
11
|
-
const { data: projects } = await fetchProjects(accountId);
|
|
12
|
-
return projects.results;
|
|
13
|
-
}
|
|
10
|
+
if (!accountId) {
|
|
14
11
|
uiLogger.error(lib.prompts.downloadProjectPrompt.errors.accountIdRequired);
|
|
15
|
-
|
|
12
|
+
throw new PromptExitError(lib.prompts.downloadProjectPrompt.errors.accountIdRequired, EXIT_CODES.ERROR);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const { data: projects } = await fetchProjects(accountId);
|
|
16
|
+
return projects.results;
|
|
16
17
|
}
|
|
17
18
|
catch (e) {
|
|
18
|
-
logError(e,
|
|
19
|
-
|
|
19
|
+
logError(e, new ApiErrorContext({ accountId }));
|
|
20
|
+
throw new PromptExitError('Failed to fetch projects', EXIT_CODES.ERROR);
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
export async function downloadProjectPrompt(promptOptions) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import open from 'open';
|
|
2
2
|
import { promptUser } from './promptUtils.js';
|
|
3
|
-
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
4
3
|
import { lib } from '../../lang/en.js';
|
|
5
4
|
import { uiLogger } from '../ui/logger.js';
|
|
5
|
+
import { PromptExitError } from '../errors/PromptExitError.js';
|
|
6
|
+
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
6
7
|
export async function installAppBrowserPrompt(installUrl, isReinstall = false) {
|
|
7
8
|
uiLogger.log('');
|
|
8
9
|
if (isReinstall) {
|
|
@@ -20,7 +21,7 @@ export async function installAppBrowserPrompt(installUrl, isReinstall = false) {
|
|
|
20
21
|
});
|
|
21
22
|
if (!isReinstall && !shouldOpenBrowser) {
|
|
22
23
|
uiLogger.log(lib.prompts.installAppPrompt.decline);
|
|
23
|
-
|
|
24
|
+
throw new PromptExitError(lib.prompts.installAppPrompt.decline, EXIT_CODES.SUCCESS);
|
|
24
25
|
}
|
|
25
26
|
else if (!shouldOpenBrowser) {
|
|
26
27
|
return;
|
|
@@ -6,8 +6,9 @@ import { uiLogger } from '../ui/logger.js';
|
|
|
6
6
|
import { promptUser } from './promptUtils.js';
|
|
7
7
|
import { getCliAccountNamePromptConfig, } from './accountNamePrompt.js';
|
|
8
8
|
import { uiInfoSection } from '../ui/index.js';
|
|
9
|
-
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
10
9
|
import { lib } from '../../lang/en.js';
|
|
10
|
+
import { PromptExitError } from '../errors/PromptExitError.js';
|
|
11
|
+
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
11
12
|
/**
|
|
12
13
|
* Displays notification to user that we are about to open the browser,
|
|
13
14
|
* then opens their browser to the personal-access-key shortlink
|
|
@@ -27,7 +28,7 @@ export async function personalAccessKeyPrompt({ env, account, }) {
|
|
|
27
28
|
]);
|
|
28
29
|
if (!choice) {
|
|
29
30
|
deleteConfigFileIfEmpty();
|
|
30
|
-
|
|
31
|
+
throw new PromptExitError(lib.prompts.personalAccessKeyPrompt.errors.authCancelled, EXIT_CODES.SUCCESS);
|
|
31
32
|
}
|
|
32
33
|
if (choice ===
|
|
33
34
|
lib.prompts.personalAccessKeyPrompt.personalAccessKeyPromptChoices
|
|
@@ -6,6 +6,7 @@ import { lib } from '../../lang/en.js';
|
|
|
6
6
|
import { uiLogger } from '../ui/logger.js';
|
|
7
7
|
import { uiAccountDescription } from '../ui/index.js';
|
|
8
8
|
import { isSandbox } from '../accountTypes.js';
|
|
9
|
+
import { PromptExitError } from '../errors/PromptExitError.js';
|
|
9
10
|
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
10
11
|
function mapNestedAccount(accountConfig) {
|
|
11
12
|
const parentAccountId = accountConfig.parentAccountId ?? null;
|
|
@@ -23,20 +24,18 @@ function getNonConfigDeveloperTestAccountName(account) {
|
|
|
23
24
|
}
|
|
24
25
|
export async function selectSandboxTargetAccountPrompt(accounts, defaultAccountConfig) {
|
|
25
26
|
const defaultAccountId = defaultAccountConfig.accountId;
|
|
27
|
+
if (!defaultAccountId) {
|
|
28
|
+
uiLogger.error(lib.prompts.projectDevTargetAccountPrompt.noAccountId);
|
|
29
|
+
throw new PromptExitError(lib.prompts.projectDevTargetAccountPrompt.noAccountId, EXIT_CODES.ERROR);
|
|
30
|
+
}
|
|
26
31
|
let choices = [];
|
|
27
32
|
let sandboxUsage = {
|
|
28
33
|
STANDARD: { used: 0, available: 0, limit: 0 },
|
|
29
34
|
DEVELOPER: { used: 0, available: 0, limit: 0 },
|
|
30
35
|
};
|
|
31
36
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
sandboxUsage = data.usage;
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
uiLogger.error(lib.prompts.projectDevTargetAccountPrompt.noAccountId);
|
|
38
|
-
process.exit(EXIT_CODES.ERROR);
|
|
39
|
-
}
|
|
37
|
+
const { data } = await getSandboxUsageLimits(defaultAccountId);
|
|
38
|
+
sandboxUsage = data.usage;
|
|
40
39
|
}
|
|
41
40
|
catch (err) {
|
|
42
41
|
uiLogger.debug('Unable to fetch sandbox usage limits: ', err);
|
|
@@ -83,16 +82,14 @@ export async function selectSandboxTargetAccountPrompt(accounts, defaultAccountC
|
|
|
83
82
|
}
|
|
84
83
|
export async function selectDeveloperTestTargetAccountPrompt(accounts, defaultAccountConfig) {
|
|
85
84
|
const defaultAccountId = defaultAccountConfig.accountId;
|
|
85
|
+
if (!defaultAccountId) {
|
|
86
|
+
uiLogger.error(lib.prompts.projectDevTargetAccountPrompt.noAccountId);
|
|
87
|
+
throw new PromptExitError(lib.prompts.projectDevTargetAccountPrompt.noAccountId, EXIT_CODES.ERROR);
|
|
88
|
+
}
|
|
86
89
|
let devTestAccountsResponse;
|
|
87
90
|
try {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
devTestAccountsResponse = data;
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
uiLogger.error(lib.prompts.projectDevTargetAccountPrompt.noAccountId);
|
|
94
|
-
process.exit(EXIT_CODES.ERROR);
|
|
95
|
-
}
|
|
91
|
+
const { data } = await fetchDeveloperTestAccounts(defaultAccountId);
|
|
92
|
+
devTestAccountsResponse = data;
|
|
96
93
|
}
|
|
97
94
|
catch (err) {
|
|
98
95
|
uiLogger.debug('Unable to fetch developer test account usage limits: ', err);
|
|
@@ -4,25 +4,29 @@ import { lib } from '../../lang/en.js';
|
|
|
4
4
|
import { debugError } from '../errorHandlers/index.js';
|
|
5
5
|
import { uiLogger } from '../ui/logger.js';
|
|
6
6
|
import { fetchTables } from '@hubspot/local-dev-lib/api/hubdb';
|
|
7
|
-
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
8
7
|
import { isValidPath, untildify } from '@hubspot/local-dev-lib/path';
|
|
8
|
+
import { PromptExitError } from '../errors/PromptExitError.js';
|
|
9
|
+
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
9
10
|
async function fetchHubDBOptions(accountId) {
|
|
10
11
|
try {
|
|
11
12
|
const { data: { results: tables }, } = await fetchTables(accountId);
|
|
12
13
|
if (tables.length === 0) {
|
|
13
14
|
uiLogger.log(lib.prompts.selectHubDBTablePrompt.errors.noTables(accountId.toString()));
|
|
14
|
-
|
|
15
|
+
throw new PromptExitError(lib.prompts.selectHubDBTablePrompt.errors.noTables(accountId.toString()), EXIT_CODES.SUCCESS);
|
|
15
16
|
}
|
|
16
17
|
return tables;
|
|
17
18
|
}
|
|
18
19
|
catch (error) {
|
|
20
|
+
if (error instanceof PromptExitError) {
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
19
23
|
debugError(error, { accountId });
|
|
20
24
|
uiLogger.error(lib.prompts.selectHubDBTablePrompt.errors.errorFetchingTables(accountId.toString()));
|
|
21
|
-
|
|
25
|
+
throw new PromptExitError(lib.prompts.selectHubDBTablePrompt.errors.errorFetchingTables(accountId.toString()), EXIT_CODES.ERROR);
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
export async function selectHubDBTablePrompt({ accountId, options, skipDestPrompt = true, }) {
|
|
25
|
-
const hubdbTables =
|
|
29
|
+
const hubdbTables = await fetchHubDBOptions(accountId);
|
|
26
30
|
const id = options.tableId?.toString();
|
|
27
31
|
const isValidTable = options.tableId && hubdbTables.find(table => table.id === id);
|
|
28
32
|
return promptUser([
|
|
@@ -4,13 +4,14 @@ import { uiLine } from '../ui/index.js';
|
|
|
4
4
|
import { logError } from '../errorHandlers/index.js';
|
|
5
5
|
import { uiLogger } from '../ui/logger.js';
|
|
6
6
|
import { fetchPublicAppsForPortal } from '@hubspot/local-dev-lib/api/appsDev';
|
|
7
|
+
import { PromptExitError } from '../errors/PromptExitError.js';
|
|
7
8
|
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
8
9
|
async function fetchPublicAppOptions(accountId, accountName, isMigratingApp = false) {
|
|
10
|
+
if (!accountId) {
|
|
11
|
+
uiLogger.error(lib.prompts.selectPublicAppForMigrationPrompt.errors.noAccountId);
|
|
12
|
+
throw new PromptExitError(lib.prompts.selectPublicAppForMigrationPrompt.errors.noAccountId, EXIT_CODES.ERROR);
|
|
13
|
+
}
|
|
9
14
|
try {
|
|
10
|
-
if (!accountId) {
|
|
11
|
-
uiLogger.error(lib.prompts.selectPublicAppForMigrationPrompt.errors.noAccountId);
|
|
12
|
-
process.exit(EXIT_CODES.ERROR);
|
|
13
|
-
}
|
|
14
15
|
const { data: { results: publicApps }, } = await fetchPublicAppsForPortal(accountId);
|
|
15
16
|
const filteredPublicApps = publicApps.filter(app => !app.projectId && !app.sourceId);
|
|
16
17
|
if (!filteredPublicApps.length ||
|
|
@@ -24,14 +25,19 @@ async function fetchPublicAppOptions(accountId, accountName, isMigratingApp = fa
|
|
|
24
25
|
uiLogger.error(`${lib.prompts.selectPublicAppForMigrationPrompt.errors.noAppsClone}\n${lib.prompts.selectPublicAppForMigrationPrompt.errors.noAppsCloneMessage(accountName)}`);
|
|
25
26
|
}
|
|
26
27
|
uiLine();
|
|
27
|
-
|
|
28
|
+
throw new PromptExitError(isMigratingApp
|
|
29
|
+
? lib.prompts.selectPublicAppForMigrationPrompt.errors.noAppsMigration
|
|
30
|
+
: lib.prompts.selectPublicAppForMigrationPrompt.errors.noAppsClone, EXIT_CODES.SUCCESS);
|
|
28
31
|
}
|
|
29
32
|
return filteredPublicApps;
|
|
30
33
|
}
|
|
31
34
|
catch (error) {
|
|
35
|
+
if (error instanceof PromptExitError) {
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
32
38
|
logError(error, accountId ? { accountId } : undefined);
|
|
33
39
|
uiLogger.error(lib.prompts.selectPublicAppForMigrationPrompt.errors.errorFetchingApps);
|
|
34
|
-
|
|
40
|
+
throw new PromptExitError(lib.prompts.selectPublicAppForMigrationPrompt.errors.errorFetchingApps, EXIT_CODES.ERROR);
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
export async function selectPublicAppForMigrationPrompt({ accountId, accountName, isMigratingApp = false, }) {
|
package/lib/sandboxes.d.ts
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
import { AccountType, HubSpotConfigAccount } from '@hubspot/local-dev-lib/types/Accounts';
|
|
2
2
|
import { Environment } from '@hubspot/local-dev-lib/types/Accounts';
|
|
3
|
-
import {
|
|
4
|
-
export declare const SYNC_TYPES: {
|
|
5
|
-
readonly OBJECT_RECORDS: "object-records";
|
|
6
|
-
};
|
|
3
|
+
import { SandboxAccountType } from '../types/Sandboxes.js';
|
|
7
4
|
export declare const SANDBOX_TYPE_MAP: {
|
|
8
5
|
[key: string]: SandboxAccountType;
|
|
9
6
|
};
|
|
10
|
-
export declare const SANDBOX_API_TYPE_MAP: {
|
|
11
|
-
readonly STANDARD_SANDBOX: 1;
|
|
12
|
-
readonly DEVELOPMENT_SANDBOX: 2;
|
|
13
|
-
};
|
|
14
7
|
export declare const SANDBOX_TYPE_MAP_V2: {
|
|
15
8
|
readonly STANDARD_SANDBOX: "STANDARD";
|
|
16
9
|
readonly DEVELOPMENT_SANDBOX: "DEVELOPER";
|
|
17
10
|
};
|
|
18
11
|
export declare function getSandboxTypeAsString(accountType?: AccountType): string;
|
|
19
12
|
export declare function getHasSandboxesByType(parentAccountConfig: HubSpotConfigAccount, type: AccountType): boolean;
|
|
20
|
-
export declare function getAvailableSyncTypes(parentAccountConfig: HubSpotConfigAccount, config: HubSpotConfigAccount): Promise<Array<SandboxSyncTask>>;
|
|
21
13
|
export declare function validateSandboxUsageLimits(accountConfig: HubSpotConfigAccount, sandboxType: AccountType, env: Environment): Promise<void>;
|
|
22
14
|
export declare function handleSandboxCreateError(err: unknown, env: Environment, name: string, accountId: number): never;
|
package/lib/sandboxes.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getSandboxUsageLimits } from '@hubspot/local-dev-lib/api/sandboxHubs';
|
|
2
|
-
import { fetchTypes } from '@hubspot/local-dev-lib/api/sandboxSync';
|
|
3
2
|
import { getAllConfigAccounts } from '@hubspot/local-dev-lib/config';
|
|
4
3
|
import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
|
|
5
4
|
import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
|
|
@@ -8,19 +7,12 @@ import { uiLogger } from './ui/logger.js';
|
|
|
8
7
|
import { lib } from '../lang/en.js';
|
|
9
8
|
import { logError } from './errorHandlers/index.js';
|
|
10
9
|
import { uiAccountDescription } from './ui/index.js';
|
|
11
|
-
export const SYNC_TYPES = {
|
|
12
|
-
OBJECT_RECORDS: 'object-records',
|
|
13
|
-
};
|
|
14
10
|
export const SANDBOX_TYPE_MAP = {
|
|
15
11
|
dev: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
|
|
16
12
|
developer: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
|
|
17
13
|
development: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
|
|
18
14
|
standard: HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX,
|
|
19
15
|
};
|
|
20
|
-
export const SANDBOX_API_TYPE_MAP = {
|
|
21
|
-
[HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX]: 1,
|
|
22
|
-
[HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX]: 2,
|
|
23
|
-
};
|
|
24
16
|
export const SANDBOX_TYPE_MAP_V2 = {
|
|
25
17
|
[HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX]: 'STANDARD',
|
|
26
18
|
[HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX]: 'DEVELOPER',
|
|
@@ -45,19 +37,6 @@ export function getHasSandboxesByType(parentAccountConfig, type) {
|
|
|
45
37
|
}
|
|
46
38
|
return false;
|
|
47
39
|
}
|
|
48
|
-
// Fetches available sync types for a given sandbox portal
|
|
49
|
-
export async function getAvailableSyncTypes(parentAccountConfig, config) {
|
|
50
|
-
const parentPortalId = parentAccountConfig.accountId;
|
|
51
|
-
const portalId = config.accountId;
|
|
52
|
-
if (!parentPortalId || !portalId) {
|
|
53
|
-
throw new Error(lib.sandbox.sync.failure.syncTypeFetch);
|
|
54
|
-
}
|
|
55
|
-
const { data: { results: syncTypes }, } = await fetchTypes(parentPortalId, portalId);
|
|
56
|
-
if (!syncTypes) {
|
|
57
|
-
throw new Error(lib.sandbox.sync.failure.syncTypeFetch);
|
|
58
|
-
}
|
|
59
|
-
return syncTypes.map(t => ({ type: t.name }));
|
|
60
|
-
}
|
|
61
40
|
export async function validateSandboxUsageLimits(accountConfig, sandboxType, env) {
|
|
62
41
|
const accountId = accountConfig.accountId;
|
|
63
42
|
if (!accountId) {
|
package/lib/serverlessLogs.js
CHANGED
|
@@ -9,7 +9,6 @@ import { outputLogs } from './ui/serverlessFunctionLogs.js';
|
|
|
9
9
|
import { logError, ApiErrorContext } from './errorHandlers/index.js';
|
|
10
10
|
import SpinniesManager from './ui/SpinniesManager.js';
|
|
11
11
|
import { handleExit, handleKeypress } from './process.js';
|
|
12
|
-
import { EXIT_CODES } from './enums/exitCodes.js';
|
|
13
12
|
import { lib } from '../lang/en.js';
|
|
14
13
|
const TAIL_DELAY = 5000;
|
|
15
14
|
function base64EncodeString(valueToEncode) {
|
|
@@ -19,19 +18,6 @@ function base64EncodeString(valueToEncode) {
|
|
|
19
18
|
const stringBuffer = Buffer.from(valueToEncode);
|
|
20
19
|
return encodeURIComponent(stringBuffer.toString('base64'));
|
|
21
20
|
}
|
|
22
|
-
function handleUserInput() {
|
|
23
|
-
const onTerminate = async () => {
|
|
24
|
-
SpinniesManager.remove('tailLogs');
|
|
25
|
-
SpinniesManager.remove('stopMessage');
|
|
26
|
-
process.exit(EXIT_CODES.SUCCESS);
|
|
27
|
-
};
|
|
28
|
-
handleExit(onTerminate);
|
|
29
|
-
handleKeypress(key => {
|
|
30
|
-
if ((key.ctrl && key.name == 'c') || key.name === 'q') {
|
|
31
|
-
onTerminate();
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
21
|
async function verifyAccessKeyAndUserAccess(accountId, scopeGroup) {
|
|
36
22
|
const accountConfig = getConfigAccountById(accountId);
|
|
37
23
|
if (!accountConfig) {
|
|
@@ -80,40 +66,60 @@ export async function tailLogs(accountId, name, fetchLatest, tailCall, compact =
|
|
|
80
66
|
}
|
|
81
67
|
}
|
|
82
68
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const { data } = await tailCall(after);
|
|
88
|
-
latestLog = data;
|
|
89
|
-
nextAfter = latestLog.paging.next.after;
|
|
69
|
+
return new Promise(resolve => {
|
|
70
|
+
function cleanup() {
|
|
71
|
+
SpinniesManager.remove('tailLogs');
|
|
72
|
+
SpinniesManager.remove('stopMessage');
|
|
90
73
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
74
|
+
let resolved = false;
|
|
75
|
+
const onTerminate = async () => {
|
|
76
|
+
if (resolved)
|
|
77
|
+
return;
|
|
78
|
+
resolved = true;
|
|
79
|
+
cleanup();
|
|
80
|
+
resolve();
|
|
81
|
+
};
|
|
82
|
+
handleExit(onTerminate);
|
|
83
|
+
handleKeypress(key => {
|
|
84
|
+
if ((key.ctrl && key.name == 'c') || key.name === 'q') {
|
|
85
|
+
onTerminate();
|
|
96
86
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
87
|
+
});
|
|
88
|
+
async function tail(after) {
|
|
89
|
+
let latestLog;
|
|
90
|
+
let nextAfter;
|
|
91
|
+
try {
|
|
92
|
+
const { data } = await tailCall(after);
|
|
93
|
+
latestLog = data;
|
|
94
|
+
nextAfter = latestLog.paging.next.after;
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
if (isHubSpotHttpError(e) && e.status !== 404) {
|
|
98
|
+
logError(e, new ApiErrorContext({
|
|
99
|
+
accountId,
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
await onTerminate();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (latestLog && latestLog.results.length) {
|
|
106
|
+
outputLogs(latestLog, {
|
|
107
|
+
compact,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
setTimeout(async () => {
|
|
111
|
+
await tail(nextAfter);
|
|
112
|
+
}, TAIL_DELAY);
|
|
103
113
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
text: `> Press ${chalk.bold('q')} to stop following`,
|
|
113
|
-
status: 'non-spinnable',
|
|
114
|
+
SpinniesManager.add('tailLogs', {
|
|
115
|
+
text: `Following logs for ${name}`,
|
|
116
|
+
});
|
|
117
|
+
SpinniesManager.add('stopMessage', {
|
|
118
|
+
text: `> Press ${chalk.bold('q')} to stop following`,
|
|
119
|
+
status: 'non-spinnable',
|
|
120
|
+
});
|
|
121
|
+
tail(initialAfter);
|
|
114
122
|
});
|
|
115
|
-
handleUserInput();
|
|
116
|
-
await tail(initialAfter);
|
|
117
123
|
}
|
|
118
124
|
export async function outputBuildLog(buildLogUrl) {
|
|
119
125
|
if (!buildLogUrl) {
|
|
@@ -2,12 +2,11 @@ import { ChildProcess } from 'child_process';
|
|
|
2
2
|
interface DevServerOptions {
|
|
3
3
|
absoluteSrc: string;
|
|
4
4
|
accountName?: string;
|
|
5
|
-
configPath?: string;
|
|
6
5
|
noSsl?: boolean;
|
|
7
6
|
port?: number;
|
|
8
7
|
generateFieldsTypes?: boolean;
|
|
9
8
|
resetSession?: boolean;
|
|
10
|
-
dest
|
|
9
|
+
dest: string;
|
|
11
10
|
}
|
|
12
|
-
export declare function spawnDevServer(options: DevServerOptions): ChildProcess
|
|
11
|
+
export declare function spawnDevServer(options: DevServerOptions): Promise<ChildProcess>;
|
|
13
12
|
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { getConfigFilePath } from '@hubspot/local-dev-lib/config';
|
|
7
|
+
import SpinniesManager from '../ui/SpinniesManager.js';
|
|
8
|
+
import { lib } from '../../lang/en.js';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
// cms-dev-server version to install to isolated cache
|
|
12
|
+
const TARGET_CMS_DEV_SERVER_VERSION = '1.2.16';
|
|
13
|
+
/**
|
|
14
|
+
* Ensures cms-dev-server is installed in an isolated cache directory.
|
|
15
|
+
* This prevents React version conflicts with the CLI.
|
|
16
|
+
*/
|
|
17
|
+
async function ensureCmsDevServerCache(targetVersion) {
|
|
18
|
+
const cacheDir = path.join(os.homedir(), '.hscli', '.module-cache');
|
|
19
|
+
const packageJsonPath = path.join(cacheDir, 'node_modules', '@hubspot', 'cms-dev-server', 'package.json');
|
|
20
|
+
// Check if already installed with correct version
|
|
21
|
+
let needsInstall = true;
|
|
22
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
23
|
+
try {
|
|
24
|
+
const installedPackage = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
25
|
+
if (installedPackage.version === targetVersion) {
|
|
26
|
+
needsInstall = false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
// If we can't read the package.json, reinstall
|
|
31
|
+
needsInstall = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (needsInstall) {
|
|
35
|
+
// Show spinner during install (can take 10-30 seconds)
|
|
36
|
+
SpinniesManager.init({
|
|
37
|
+
succeedColor: 'white',
|
|
38
|
+
});
|
|
39
|
+
SpinniesManager.add('cms-dev-server-install', {
|
|
40
|
+
text: lib.theme.cmsDevServerProcess.installStarted(targetVersion),
|
|
41
|
+
});
|
|
42
|
+
// Create cache directory
|
|
43
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
44
|
+
// Clear old installation if exists
|
|
45
|
+
const nodeModulesDir = path.join(cacheDir, 'node_modules');
|
|
46
|
+
if (fs.existsSync(nodeModulesDir)) {
|
|
47
|
+
fs.rmSync(nodeModulesDir, { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
// Install cms-dev-server with production dependencies only (async to allow spinner)
|
|
50
|
+
await new Promise((resolve, reject) => {
|
|
51
|
+
const installProcess = spawn('npm', [
|
|
52
|
+
'install',
|
|
53
|
+
`@hubspot/cms-dev-server@${targetVersion}`,
|
|
54
|
+
'--production',
|
|
55
|
+
'--no-save',
|
|
56
|
+
'--loglevel=error',
|
|
57
|
+
], {
|
|
58
|
+
cwd: cacheDir,
|
|
59
|
+
stdio: 'ignore', // Suppress npm output
|
|
60
|
+
});
|
|
61
|
+
installProcess.on('close', code => {
|
|
62
|
+
if (code === 0) {
|
|
63
|
+
SpinniesManager.succeed('cms-dev-server-install', {
|
|
64
|
+
text: lib.theme.cmsDevServerProcess.installSucceeded,
|
|
65
|
+
});
|
|
66
|
+
resolve();
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
SpinniesManager.fail('cms-dev-server-install', {
|
|
70
|
+
text: lib.theme.cmsDevServerProcess.installFailed,
|
|
71
|
+
});
|
|
72
|
+
reject(new Error(lib.theme.cmsDevServerProcess.installFailed));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
installProcess.on('error', error => {
|
|
76
|
+
SpinniesManager.fail('cms-dev-server-install', {
|
|
77
|
+
text: lib.theme.cmsDevServerProcess.installFailed,
|
|
78
|
+
});
|
|
79
|
+
reject(error);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return cacheDir;
|
|
84
|
+
}
|
|
85
|
+
export async function spawnDevServer(options) {
|
|
86
|
+
const { absoluteSrc, accountName, noSsl, port, generateFieldsTypes, resetSession, dest, } = options;
|
|
87
|
+
// Ensure cms-dev-server is installed in isolated cache
|
|
88
|
+
const cacheDir = await ensureCmsDevServerCache(TARGET_CMS_DEV_SERVER_VERSION);
|
|
89
|
+
// Get config path to pass to createDevServer
|
|
90
|
+
let configPath = '';
|
|
91
|
+
try {
|
|
92
|
+
configPath = process.env.HUBSPOT_CONFIG_PATH || getConfigFilePath();
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
// Config file doesn't exist - cms-dev-server will handle this gracefully
|
|
96
|
+
}
|
|
97
|
+
// Copy the runner script to the cache directory so imports resolve from there
|
|
98
|
+
// This is critical: Node resolves ES module imports relative to the script location,
|
|
99
|
+
// not the cwd. By copying the script to the cache directory, imports will resolve
|
|
100
|
+
// from the cache's node_modules (React 18) instead of the CLI's node_modules (React 19)
|
|
101
|
+
const sourceRunnerPath = path.join(__dirname, 'cmsDevServerRunner.js');
|
|
102
|
+
const targetRunnerPath = path.join(cacheDir, 'cmsPreviewRunner.js');
|
|
103
|
+
fs.copyFileSync(sourceRunnerPath, targetRunnerPath);
|
|
104
|
+
// Set environment variables to pass configuration to the runner script
|
|
105
|
+
const env = { ...process.env };
|
|
106
|
+
env.CMS_DEV_SERVER_SRC = absoluteSrc;
|
|
107
|
+
env.CMS_DEV_SERVER_DEST = dest;
|
|
108
|
+
env.CMS_DEV_SERVER_CONFIG = configPath;
|
|
109
|
+
env.CMS_DEV_SERVER_ACCOUNT = accountName || '';
|
|
110
|
+
env.CMS_DEV_SERVER_SSL = (!noSsl).toString();
|
|
111
|
+
env.CMS_DEV_SERVER_FIELD_GEN = Boolean(generateFieldsTypes).toString();
|
|
112
|
+
env.CMS_DEV_SERVER_RESET_SESSION = Boolean(resetSession).toString();
|
|
113
|
+
if (port) {
|
|
114
|
+
env.PORT = port.toString();
|
|
115
|
+
}
|
|
116
|
+
// Suppress Node.js deprecation warnings
|
|
117
|
+
env.NODE_NO_WARNINGS = '1';
|
|
118
|
+
// Spawn Node with the runner script from the isolated cache directory
|
|
119
|
+
// This ensures complete isolation from CLI's React 19
|
|
120
|
+
const devServer = spawn('node', [targetRunnerPath], {
|
|
121
|
+
stdio: 'inherit',
|
|
122
|
+
env,
|
|
123
|
+
cwd: cacheDir,
|
|
124
|
+
});
|
|
125
|
+
// Handle process events
|
|
126
|
+
devServer.on('error', error => {
|
|
127
|
+
console.error(lib.theme.cmsDevServerProcess.serverStartError(error));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
});
|
|
130
|
+
devServer.on('exit', (code, signal) => {
|
|
131
|
+
if (code !== 0 && code !== null) {
|
|
132
|
+
console.error(lib.theme.cmsDevServerProcess.serverExit(code));
|
|
133
|
+
process.exit(code);
|
|
134
|
+
}
|
|
135
|
+
if (signal) {
|
|
136
|
+
console.error(lib.theme.cmsDevServerProcess.serverKill(signal));
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
// Handle CLI termination
|
|
141
|
+
process.once('SIGINT', () => {
|
|
142
|
+
devServer.kill('SIGINT');
|
|
143
|
+
});
|
|
144
|
+
process.once('SIGTERM', () => {
|
|
145
|
+
devServer.kill('SIGTERM');
|
|
146
|
+
});
|
|
147
|
+
return devServer;
|
|
148
|
+
}
|