@hubspot/cli 8.2.0-experimental.0 → 8.2.0-experimental.2
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 +2 -0
- package/commands/app/logs.js +4 -0
- package/commands/getStarted.js +2 -0
- package/commands/init.js +2 -0
- package/commands/mcp/setup.js +2 -2
- package/commands/project/create.js +2 -0
- package/commands/project/installApp.d.ts +7 -0
- package/commands/project/installApp.js +181 -0
- package/commands/project/installDeps.js +2 -0
- package/commands/project/upload.js +4 -0
- package/commands/project.js +2 -0
- package/commands/upgrade.js +2 -0
- package/lang/en.d.ts +58 -0
- package/lang/en.js +71 -2
- package/lib/accountTargetDiscovery.d.ts +15 -0
- package/lib/accountTargetDiscovery.js +175 -0
- package/lib/app/install.d.ts +22 -0
- package/lib/app/install.js +42 -0
- package/lib/app/logs.js +1 -1
- package/lib/auth/awaitPersonalAccessKeyOverWebsocket.js +3 -2
- package/lib/doctor/Doctor.d.ts +1 -0
- package/lib/doctor/Doctor.js +26 -0
- package/lib/mcp/clients.d.ts +13 -0
- package/lib/mcp/clients.js +55 -0
- package/lib/mcp/promotion.d.ts +3 -0
- package/lib/mcp/promotion.js +109 -0
- package/lib/mcp/setup.d.ts +3 -2
- package/lib/mcp/setup.js +17 -12
- package/lib/projects/installApp.d.ts +58 -0
- package/lib/projects/installApp.js +278 -0
- package/lib/projects/localDev/AppDevModeInterface.d.ts +0 -3
- package/lib/projects/localDev/AppDevModeInterface.js +22 -44
- package/lib/projects/localDev/helpers/project.d.ts +4 -3
- package/lib/projects/localDev/helpers/project.js +46 -12
- package/lib/prompts/projectDevTargetAccountPrompt.js +3 -2
- package/lib/theme/cmsDevServerProcess.js +1 -1
- package/lib/usageTracking.d.ts +1 -0
- package/lib/usageTracking.js +9 -0
- package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
- package/package.json +4 -4
- package/types/AccountTargets.d.ts +37 -0
- package/types/AccountTargets.js +12 -0
- package/types/LocalDev.d.ts +3 -2
- package/types/ProjectComponents.d.ts +2 -1
- package/types/Projects.d.ts +5 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fetchAppInstallationData } from '@hubspot/local-dev-lib/api/localDevAuth';
|
|
3
|
+
import { fetchAppMetadataBySourceId, fetchPublicAppMetadata, installStaticAuthAppOnCurrentAccount, installStaticAuthAppOnTestAccount, } from '@hubspot/local-dev-lib/api/appsDev';
|
|
4
|
+
import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
|
|
5
|
+
import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
|
|
6
|
+
import { getAllConfigAccounts, getConfigAccountIfExists, } from '@hubspot/local-dev-lib/config';
|
|
7
|
+
import { translateForLocalDev } from '@hubspot/project-parsing-lib/translate';
|
|
8
|
+
import { commands } from '../../lang/en.js';
|
|
9
|
+
import { isAppDeveloperAccount } from '../accountTypes.js';
|
|
10
|
+
import { ApiErrorContext, debugError, logError, } from '../errorHandlers/index.js';
|
|
11
|
+
import { confirmPrompt, listPrompt } from '../prompts/promptUtils.js';
|
|
12
|
+
import { projectProfilePrompt } from '../prompts/projectProfilePrompt.js';
|
|
13
|
+
import { loadProfile } from './projectProfiles.js';
|
|
14
|
+
import { handleProjectUpload } from './upload.js';
|
|
15
|
+
import { pollProjectBuildAndDeploy } from './pollProjectBuildAndDeploy.js';
|
|
16
|
+
import { getAppNodeFromProjectNodes, canUseStaticAuthTestAccountInstall, isPrivateApp, isStaticAuthApp, } from '../app/install.js';
|
|
17
|
+
import { uiLogger } from '../ui/logger.js';
|
|
18
|
+
import { uiAccountDescription } from '../ui/index.js';
|
|
19
|
+
import { getStaticAuthAppInstallUrl } from '../app/urls.js';
|
|
20
|
+
export async function resolveProjectAccountId(derivedAccountId, projectConfig, projectDir, profileOption, useEnvOption) {
|
|
21
|
+
try {
|
|
22
|
+
const profileName = await projectProfilePrompt(projectDir, projectConfig, profileOption, useEnvOption);
|
|
23
|
+
if (profileName) {
|
|
24
|
+
const profile = loadProfile(projectConfig, projectDir, profileName);
|
|
25
|
+
uiLogger.log(commands.project.installApp.profileMessage(profileName, profile.accountId));
|
|
26
|
+
uiLogger.log('');
|
|
27
|
+
return { accountId: profile.accountId, profileName };
|
|
28
|
+
}
|
|
29
|
+
return { accountId: derivedAccountId };
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
logError(error);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function loadInstallableAppNode(projectConfig, projectDir, accountId) {
|
|
37
|
+
let appNode;
|
|
38
|
+
try {
|
|
39
|
+
const { intermediateNodesIndexedByUid } = await translateForLocalDev({
|
|
40
|
+
projectSourceDir: path.join(projectDir, projectConfig.srcDir),
|
|
41
|
+
platformVersion: projectConfig.platformVersion,
|
|
42
|
+
accountId,
|
|
43
|
+
}, { skipValidation: true });
|
|
44
|
+
appNode = getAppNodeFromProjectNodes(intermediateNodesIndexedByUid);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
debugError(error);
|
|
48
|
+
uiLogger.error(commands.project.installApp.errors.failedToParseProject);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
if (!appNode) {
|
|
52
|
+
uiLogger.error(commands.project.installApp.errors.noAppInProject);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
if (!isStaticAuthApp(appNode)) {
|
|
56
|
+
uiLogger.error(commands.project.installApp.errors.unsupportedAuthType(appNode.config.auth.type));
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
if (!isPrivateApp(appNode)) {
|
|
60
|
+
uiLogger.error(commands.project.installApp.errors.unsupportedDistribution(appNode.config.distribution || 'unknown'));
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return appNode;
|
|
64
|
+
}
|
|
65
|
+
// App developer accounts can't host app installs.
|
|
66
|
+
export async function resolveValidInstallAccount(targetAccountId, force, formatOutputAsJson) {
|
|
67
|
+
const accountConfig = getConfigAccountIfExists(targetAccountId);
|
|
68
|
+
if (!accountConfig || !isAppDeveloperAccount(accountConfig)) {
|
|
69
|
+
return targetAccountId;
|
|
70
|
+
}
|
|
71
|
+
const validAccounts = getAllConfigAccounts().filter(account => !isAppDeveloperAccount(account));
|
|
72
|
+
if (force || formatOutputAsJson || validAccounts.length === 0) {
|
|
73
|
+
uiLogger.error(commands.project.installApp.errors.invalidAppDeveloperAccount(targetAccountId));
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
uiLogger.log(commands.project.installApp.appDeveloperAccountNotice(targetAccountId));
|
|
77
|
+
return listPrompt(commands.project.installApp.selectInstallAccountPrompt, {
|
|
78
|
+
choices: validAccounts.map(account => ({
|
|
79
|
+
name: uiAccountDescription(account.accountId, false),
|
|
80
|
+
value: account.accountId,
|
|
81
|
+
})),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async function offerUploadAndDeploy(options) {
|
|
85
|
+
const { accountId, projectConfig, projectDir, profile, force } = options;
|
|
86
|
+
if (!force) {
|
|
87
|
+
if (options.formatOutputAsJson) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const shouldUpload = await confirmPrompt(commands.project.installApp.uploadAndDeployPrompt(accountId));
|
|
91
|
+
if (!shouldUpload) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const { result, uploadError } = await handleProjectUpload({
|
|
97
|
+
accountId,
|
|
98
|
+
projectConfig,
|
|
99
|
+
projectDir,
|
|
100
|
+
callbackFunc: pollProjectBuildAndDeploy,
|
|
101
|
+
isUploadCommand: true,
|
|
102
|
+
sendIR: true,
|
|
103
|
+
forceCreate: true,
|
|
104
|
+
profile,
|
|
105
|
+
});
|
|
106
|
+
if (uploadError) {
|
|
107
|
+
logError(uploadError, new ApiErrorContext({ accountId, projectName: projectConfig.name }));
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
return Boolean(result && result.succeeded);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
logError(error, new ApiErrorContext({ accountId, projectName: projectConfig.name }));
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export async function resolveProjectId(options) {
|
|
118
|
+
const { accountId, projectConfig } = options;
|
|
119
|
+
try {
|
|
120
|
+
const response = await fetchProject(accountId, projectConfig.name);
|
|
121
|
+
return response.data.id;
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (!isHubSpotHttpError(error) || error.status !== 404) {
|
|
125
|
+
logError(error, new ApiErrorContext({ accountId, projectName: projectConfig.name }));
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const uploaded = await offerUploadAndDeploy(options);
|
|
129
|
+
if (!uploaded) {
|
|
130
|
+
uiLogger.error(commands.project.installApp.errors.projectNotFound(accountId, projectConfig.name));
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const response = await fetchProject(accountId, projectConfig.name);
|
|
135
|
+
return response.data.id;
|
|
136
|
+
}
|
|
137
|
+
catch (retryError) {
|
|
138
|
+
logError(retryError, new ApiErrorContext({ accountId, projectName: projectConfig.name }));
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
export async function resolveAppMetadata(options) {
|
|
144
|
+
const { appId, projectId, appUid, appName, accountId, projectConfig } = options;
|
|
145
|
+
const fetchMetadata = (forceSourceIdLookup = false) => appId && !forceSourceIdLookup
|
|
146
|
+
? fetchPublicAppMetadata(appId, accountId)
|
|
147
|
+
: fetchAppMetadataBySourceId(projectId, appUid, accountId);
|
|
148
|
+
try {
|
|
149
|
+
const { data } = await fetchMetadata();
|
|
150
|
+
return {
|
|
151
|
+
appId: appId ?? data.id,
|
|
152
|
+
scopeGroupIds: data.scopeGroupIds,
|
|
153
|
+
ownerPortalId: data.portalId,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (!isHubSpotHttpError(error) || error.status !== 404) {
|
|
158
|
+
logError(error, new ApiErrorContext({ accountId, projectName: projectConfig.name }));
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const uploaded = await offerUploadAndDeploy(options);
|
|
162
|
+
if (!uploaded) {
|
|
163
|
+
uiLogger.error(commands.project.installApp.errors.appNotDeployed(appName, accountId));
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const { data } = await fetchMetadata(true);
|
|
168
|
+
return {
|
|
169
|
+
appId: data.id,
|
|
170
|
+
scopeGroupIds: data.scopeGroupIds,
|
|
171
|
+
ownerPortalId: data.portalId,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
catch (retryError) {
|
|
175
|
+
logError(retryError, new ApiErrorContext({ accountId, projectName: projectConfig.name }));
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
export async function fetchProjectAppInstallationData(targetAccountId, projectId, appNode, projectConfig) {
|
|
181
|
+
try {
|
|
182
|
+
const response = await fetchAppInstallationData(targetAccountId, projectId, appNode.uid, appNode.config.auth.requiredScopes, appNode.config.auth.optionalScopes);
|
|
183
|
+
return {
|
|
184
|
+
appId: response.data.appId,
|
|
185
|
+
isInstalledWithScopeGroups: response.data.isInstalledWithScopeGroups,
|
|
186
|
+
previouslyAuthorizedScopeGroups: response.data.previouslyAuthorizedScopeGroups,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
if (isHubSpotHttpError(error) && error.status === 404) {
|
|
191
|
+
return {
|
|
192
|
+
isInstalledWithScopeGroups: false,
|
|
193
|
+
previouslyAuthorizedScopeGroups: [],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
logError(error, new ApiErrorContext({
|
|
197
|
+
accountId: targetAccountId,
|
|
198
|
+
projectName: projectConfig.name,
|
|
199
|
+
}));
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export async function confirmInstallAppAction({ appName, targetAccountId, force, needsReinstall, }) {
|
|
204
|
+
if (force) {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (needsReinstall) {
|
|
208
|
+
uiLogger.log(commands.project.installApp.outdatedScopes(appName, targetAccountId));
|
|
209
|
+
return confirmPrompt(commands.project.installApp.reinstallPrompt);
|
|
210
|
+
}
|
|
211
|
+
return confirmPrompt(commands.project.installApp.installPrompt(appName, targetAccountId));
|
|
212
|
+
}
|
|
213
|
+
export async function installStaticAuthAppForAccount({ appId, appNode, appName, targetAccountId, targetAccountConfig, projectId, projectName, scopeGroupIds, ownerPortalId, installationState, formatOutputAsJson, }) {
|
|
214
|
+
const installingIntoOwnerAccount = ownerPortalId === targetAccountId;
|
|
215
|
+
const useTestAccountInstall = !installingIntoOwnerAccount &&
|
|
216
|
+
canUseStaticAuthTestAccountInstall({
|
|
217
|
+
accountConfig: targetAccountConfig,
|
|
218
|
+
appNode,
|
|
219
|
+
});
|
|
220
|
+
if (!installingIntoOwnerAccount && !useTestAccountInstall) {
|
|
221
|
+
const installUrl = getStaticAuthAppInstallUrl({
|
|
222
|
+
targetAccountId,
|
|
223
|
+
env: (targetAccountConfig?.env || 'prod'),
|
|
224
|
+
appId,
|
|
225
|
+
});
|
|
226
|
+
if (formatOutputAsJson) {
|
|
227
|
+
uiLogger.json({
|
|
228
|
+
appId,
|
|
229
|
+
appUid: appNode.uid,
|
|
230
|
+
accountId: targetAccountId,
|
|
231
|
+
projectId,
|
|
232
|
+
installationState,
|
|
233
|
+
installed: false,
|
|
234
|
+
reinstalled: false,
|
|
235
|
+
installUrl,
|
|
236
|
+
error: commands.project.installApp.jsonErrors.automaticInstallUnavailable,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
uiLogger.error(commands.project.installApp.errors.automaticInstallUnavailable(appName, targetAccountId));
|
|
241
|
+
uiLogger.log(commands.project.installApp.installFromBrowser(installUrl));
|
|
242
|
+
}
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
if (useTestAccountInstall) {
|
|
247
|
+
await installStaticAuthAppOnTestAccount(appId, targetAccountId, scopeGroupIds);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
await installStaticAuthAppOnCurrentAccount(appId, targetAccountId, scopeGroupIds);
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
if (formatOutputAsJson) {
|
|
256
|
+
uiLogger.json({
|
|
257
|
+
appId,
|
|
258
|
+
appUid: appNode.uid,
|
|
259
|
+
accountId: targetAccountId,
|
|
260
|
+
projectId,
|
|
261
|
+
installationState,
|
|
262
|
+
installed: false,
|
|
263
|
+
reinstalled: false,
|
|
264
|
+
error: isHubSpotHttpError(error)
|
|
265
|
+
? error.message
|
|
266
|
+
: commands.project.installApp.jsonErrors.installFailed(appName, targetAccountId),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
uiLogger.error(commands.project.installApp.errors.installFailed(appName, targetAccountId));
|
|
271
|
+
logError(error, new ApiErrorContext({
|
|
272
|
+
accountId: targetAccountId,
|
|
273
|
+
projectName,
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -12,12 +12,9 @@ declare class AppDevModeInterface {
|
|
|
12
12
|
marketplaceAppInstalls?: number;
|
|
13
13
|
private appInstallResolve?;
|
|
14
14
|
constructor(options: AppDevModeInterfaceConstructorOptions);
|
|
15
|
-
private getAppNodeFromProjectNodes;
|
|
16
15
|
private get appNode();
|
|
17
16
|
private get appData();
|
|
18
17
|
private set appData(value);
|
|
19
|
-
private isStaticAuthApp;
|
|
20
|
-
private isOAuthApp;
|
|
21
18
|
private isAutomaticallyInstallable;
|
|
22
19
|
private getAppInstallUrl;
|
|
23
20
|
private fetchAppData;
|
|
@@ -3,7 +3,6 @@ import { fetchAppMetadataBySourceId, fetchPublicAppProductionInstallCounts, inst
|
|
|
3
3
|
import { getConfigAccountById } from '@hubspot/local-dev-lib/config';
|
|
4
4
|
import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, APP_INSTALLATION_STATES, LOCAL_DEV_SERVER_MESSAGE_TYPES, } from '../../constants.js';
|
|
5
5
|
import { EXIT_CODES } from '../../enums/exitCodes.js';
|
|
6
|
-
import { isAppIRNode } from '../../projects/structure.js';
|
|
7
6
|
import { uiLine } from '../../ui/index.js';
|
|
8
7
|
import { logError } from '../../errorHandlers/index.js';
|
|
9
8
|
import { isPromptExitError } from '../../errors/PromptExitError.js';
|
|
@@ -12,9 +11,9 @@ import { confirmPrompt } from '../../prompts/promptUtils.js';
|
|
|
12
11
|
import { lib } from '../../../lang/en.js';
|
|
13
12
|
import { uiLogger } from '../../ui/logger.js';
|
|
14
13
|
import { getOauthAppInstallUrl, getStaticAuthAppInstallUrl, } from '../../app/urls.js';
|
|
15
|
-
import { isDeveloperTestAccount, isSandbox } from '../../accountTypes.js';
|
|
16
14
|
import SpinniesManager from '../../ui/SpinniesManager.js';
|
|
17
15
|
import { isServerRunningAtUrl } from '../../http.js';
|
|
16
|
+
import { canUseStaticAuthTestAccountInstall, getAppInstallationState, getAppNodeFromProjectNodes, isMarketplaceApp, isOAuthApp, } from '../../app/install.js';
|
|
18
17
|
class AppDevModeInterface {
|
|
19
18
|
localDevState;
|
|
20
19
|
localDevLogger;
|
|
@@ -31,13 +30,10 @@ class AppDevModeInterface {
|
|
|
31
30
|
throw new Error(lib.LocalDevManager.failedToInitialize);
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
|
-
getAppNodeFromProjectNodes(projectNodes) {
|
|
35
|
-
return Object.values(projectNodes).find(isAppIRNode) || null;
|
|
36
|
-
}
|
|
37
33
|
// Assumes only one app per project
|
|
38
34
|
get appNode() {
|
|
39
35
|
if (this._appNode === undefined) {
|
|
40
|
-
this._appNode =
|
|
36
|
+
this._appNode = getAppNodeFromProjectNodes(this.localDevState.projectNodes);
|
|
41
37
|
}
|
|
42
38
|
return this._appNode;
|
|
43
39
|
}
|
|
@@ -61,25 +57,16 @@ class AppDevModeInterface {
|
|
|
61
57
|
}
|
|
62
58
|
this.localDevState.setAppDataForUid(this.appNode.uid, appData);
|
|
63
59
|
}
|
|
64
|
-
isStaticAuthApp() {
|
|
65
|
-
return (this.appNode?.config.auth.type.toLowerCase() === APP_AUTH_TYPES.STATIC);
|
|
66
|
-
}
|
|
67
|
-
isOAuthApp() {
|
|
68
|
-
return (this.appNode?.config.auth.type.toLowerCase() === APP_AUTH_TYPES.OAUTH);
|
|
69
|
-
}
|
|
70
60
|
isAutomaticallyInstallable() {
|
|
71
61
|
const targetTestingAccount = getConfigAccountById(this.localDevState.targetTestingAccountId);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const hasCorrectParent = targetTestingAccount.parentAccountId ===
|
|
78
|
-
this.localDevState.targetProjectAccountId;
|
|
79
|
-
return isTestAccount && hasCorrectParent && this.isStaticAuthApp();
|
|
62
|
+
return canUseStaticAuthTestAccountInstall({
|
|
63
|
+
accountConfig: targetTestingAccount,
|
|
64
|
+
appNode: this.appNode,
|
|
65
|
+
parentAccountId: this.localDevState.targetProjectAccountId,
|
|
66
|
+
});
|
|
80
67
|
}
|
|
81
68
|
async getAppInstallUrl() {
|
|
82
|
-
if (this.appNode && this.
|
|
69
|
+
if (this.appNode && isOAuthApp(this.appNode)) {
|
|
83
70
|
return getOauthAppInstallUrl({
|
|
84
71
|
targetAccountId: this.localDevState.targetTestingAccountId,
|
|
85
72
|
env: this.localDevState.env,
|
|
@@ -195,26 +182,17 @@ class AppDevModeInterface {
|
|
|
195
182
|
return {};
|
|
196
183
|
}
|
|
197
184
|
const { data: { isInstalledWithScopeGroups, previouslyAuthorizedScopeGroups }, } = await fetchAppInstallationData(this.localDevState.targetTestingAccountId, this.localDevState.projectId, this.appNode.uid, this.appNode.config.auth.requiredScopes, this.appNode.config.auth.optionalScopes);
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
this.appData = {
|
|
213
|
-
...this.appData,
|
|
214
|
-
installationState: APP_INSTALLATION_STATES.NOT_INSTALLED,
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
return { needsInstall: !isInstalledWithScopeGroups, isReinstall };
|
|
185
|
+
const installationState = getAppInstallationState(isInstalledWithScopeGroups, previouslyAuthorizedScopeGroups);
|
|
186
|
+
const isReinstall = installationState ===
|
|
187
|
+
APP_INSTALLATION_STATES.INSTALLED_WITH_OUTDATED_SCOPES;
|
|
188
|
+
this.appData = {
|
|
189
|
+
...this.appData,
|
|
190
|
+
installationState,
|
|
191
|
+
};
|
|
192
|
+
return {
|
|
193
|
+
needsInstall: installationState !== APP_INSTALLATION_STATES.INSTALLED,
|
|
194
|
+
isReinstall,
|
|
195
|
+
};
|
|
218
196
|
}
|
|
219
197
|
async validateOauthAppRedirectUrl() {
|
|
220
198
|
const redirectUrl = this.appNode?.config.auth.redirectUrls[0];
|
|
@@ -263,7 +241,7 @@ class AppDevModeInterface {
|
|
|
263
241
|
}
|
|
264
242
|
};
|
|
265
243
|
onChangeProjectNodes = (nodes) => {
|
|
266
|
-
const newAppNode =
|
|
244
|
+
const newAppNode = getAppNodeFromProjectNodes(nodes);
|
|
267
245
|
const oldDistribution = this.appNode?.config.distribution;
|
|
268
246
|
const newDistribution = newAppNode?.config.distribution;
|
|
269
247
|
const oldAuthType = this.appNode?.config.auth.type;
|
|
@@ -287,7 +265,7 @@ class AppDevModeInterface {
|
|
|
287
265
|
}
|
|
288
266
|
try {
|
|
289
267
|
await this.fetchAppData();
|
|
290
|
-
if (this.appNode
|
|
268
|
+
if (isMarketplaceApp(this.appNode)) {
|
|
291
269
|
await this.checkMarketplaceAppInstalls();
|
|
292
270
|
}
|
|
293
271
|
const { needsInstall, isReinstall } = await this.checkTestAccountAppInstallation();
|
|
@@ -298,7 +276,7 @@ class AppDevModeInterface {
|
|
|
298
276
|
failColor: 'white',
|
|
299
277
|
});
|
|
300
278
|
}
|
|
301
|
-
if (this.
|
|
279
|
+
if (isOAuthApp(this.appNode)) {
|
|
302
280
|
await this.validateOauthAppRedirectUrl();
|
|
303
281
|
}
|
|
304
282
|
this.localDevState.addListener('devServerMessage', this.onDevServerMessage);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type IntermediateRepresentationNode, type IntermediateRepresentationNodeLocalDev } from '@hubspot/project-parsing-lib/translate';
|
|
2
2
|
import { Build } from '@hubspot/local-dev-lib/types/Build';
|
|
3
3
|
import { Project } from '@hubspot/local-dev-lib/types/Project';
|
|
4
|
-
import { ProjectConfig } from '../../../../types/Projects.js';
|
|
4
|
+
import { LocallyChangedComponents, ProjectConfig } from '../../../../types/Projects.js';
|
|
5
5
|
import { ExitFunction } from '../../../../types/Yargs.js';
|
|
6
6
|
export declare function createNewProjectForLocalDev(projectConfig: ProjectConfig, targetAccountId: number, shouldCreateWithoutConfirmation: boolean, hasPublicApps: boolean, exit: ExitFunction): Promise<Project>;
|
|
7
7
|
export declare function createInitialBuildForNewProject(projectConfig: ProjectConfig, projectDir: string, targetAccountId: number, exit: ExitFunction, sendIR?: boolean, profile?: string): Promise<Build>;
|
|
@@ -11,7 +11,8 @@ export declare function compareLocalProjectToDeployed(projectConfig: ProjectConf
|
|
|
11
11
|
export declare function getDeployedProjectNodes(projectConfig: ProjectConfig, accountId: number, deployedBuildId: number, profile?: string): Promise<{
|
|
12
12
|
[key: string]: IntermediateRepresentationNode;
|
|
13
13
|
}>;
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function hasLocalComponentChanges(changed: LocallyChangedComponents): boolean;
|
|
15
|
+
export declare function getLocallyChangedComponents(projectConfig: ProjectConfig, accountId: number, deployedBuildId: number, localProjectNodes: {
|
|
15
16
|
[key: string]: IntermediateRepresentationNodeLocalDev;
|
|
16
|
-
}, profile?: string): Promise<
|
|
17
|
+
}, profile?: string): Promise<LocallyChangedComponents>;
|
|
17
18
|
export declare function checkAndInstallDependencies(): Promise<void>;
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { createProject } from '@hubspot/local-dev-lib/api/projects';
|
|
5
|
-
import { downloadProject } from '@hubspot/local-dev-lib/api/projects';
|
|
4
|
+
import { createProject, downloadProject, } from '@hubspot/local-dev-lib/api/projects';
|
|
6
5
|
import { extractZipArchive } from '@hubspot/local-dev-lib/archive';
|
|
7
6
|
import { sanitizeFileName } from '@hubspot/local-dev-lib/path';
|
|
8
7
|
import { isDeepEqual } from '@hubspot/local-dev-lib/isDeepEqual';
|
|
9
8
|
import { translate, } from '@hubspot/project-parsing-lib/translate';
|
|
9
|
+
import { AUTO_GENERATED_COMPONENT_TYPES } from '@hubspot/project-parsing-lib/constants';
|
|
10
|
+
import { mapToUserFacingType } from '@hubspot/project-parsing-lib/transform';
|
|
10
11
|
import { isSpecifiedError } from '@hubspot/local-dev-lib/errors/index';
|
|
11
|
-
import {
|
|
12
|
+
import { PROJECT_BUILD_TEXT, PROJECT_CONFIG_FILE, PROJECT_DEPLOY_TEXT, PROJECT_ERROR_TYPES, } from '../../../constants.js';
|
|
12
13
|
import { lib } from '../../../../lang/en.js';
|
|
13
14
|
import { uiLogger } from '../../../ui/logger.js';
|
|
14
|
-
import { uiLine } from '../../../ui/index.js';
|
|
15
|
+
import { uiAccountDescription, uiLine } from '../../../ui/index.js';
|
|
15
16
|
import { confirmPrompt } from '../../../prompts/promptUtils.js';
|
|
16
|
-
import { uiAccountDescription } from '../../../ui/index.js';
|
|
17
17
|
import SpinniesManager from '../../../ui/SpinniesManager.js';
|
|
18
18
|
import { EXIT_CODES } from '../../../enums/exitCodes.js';
|
|
19
19
|
import { handleProjectUpload } from '../../upload.js';
|
|
20
20
|
import { pollProjectBuildAndDeploy } from '../../pollProjectBuildAndDeploy.js';
|
|
21
|
-
import { debugError, logError } from '../../../errorHandlers/index.js';
|
|
22
|
-
import { ApiErrorContext } from '../../../errorHandlers/index.js';
|
|
21
|
+
import { ApiErrorContext, debugError, logError, } from '../../../errorHandlers/index.js';
|
|
23
22
|
import { getProjectPackageJsonLocations, hasMissingPackages, installPackages, } from '../../../dependencyManagement.js';
|
|
24
23
|
// Prompt the user to create a new project if one doesn't exist on their target account
|
|
25
24
|
export async function createNewProjectForLocalDev(projectConfig, targetAccountId, shouldCreateWithoutConfirmation, hasPublicApps, exit) {
|
|
@@ -133,8 +132,8 @@ export async function compareLocalProjectToDeployed(projectConfig, accountId, de
|
|
|
133
132
|
SpinniesManager.add('compareLocalProjectToDeployed', {
|
|
134
133
|
text: lib.localDevHelpers.project.compareLocalProjectToDeployed.checking,
|
|
135
134
|
});
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
135
|
+
const changedComponents = await getLocallyChangedComponents(projectConfig, accountId, deployedBuildId, localProjectNodes, profile);
|
|
136
|
+
if (!hasLocalComponentChanges(changedComponents)) {
|
|
138
137
|
SpinniesManager.succeed('compareLocalProjectToDeployed', {
|
|
139
138
|
text: lib.localDevHelpers.project.compareLocalProjectToDeployed.upToDate,
|
|
140
139
|
});
|
|
@@ -145,6 +144,8 @@ export async function compareLocalProjectToDeployed(projectConfig, accountId, de
|
|
|
145
144
|
.notUpToDate,
|
|
146
145
|
});
|
|
147
146
|
uiLogger.log('');
|
|
147
|
+
uiLogger.log(lib.localDevHelpers.project.compareLocalProjectToDeployed.changedFiles(changedComponents));
|
|
148
|
+
uiLogger.log('');
|
|
148
149
|
uiLogger.log(lib.localDevHelpers.project.compareLocalProjectToDeployed.notUpToDateExplanation(profile));
|
|
149
150
|
return exit(EXIT_CODES.SUCCESS);
|
|
150
151
|
}
|
|
@@ -185,14 +186,47 @@ export async function getDeployedProjectNodes(projectConfig, accountId, deployed
|
|
|
185
186
|
}
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
|
-
|
|
189
|
+
function filterAutoGeneratedNodes(nodes) {
|
|
190
|
+
return Object.fromEntries(Object.entries(nodes).filter(([, node]) => !AUTO_GENERATED_COMPONENT_TYPES.includes(mapToUserFacingType(node.componentType))));
|
|
191
|
+
}
|
|
192
|
+
export function hasLocalComponentChanges(changed) {
|
|
193
|
+
return (changed.added.length > 0 ||
|
|
194
|
+
changed.updated.length > 0 ||
|
|
195
|
+
changed.removed.length > 0);
|
|
196
|
+
}
|
|
197
|
+
export async function getLocallyChangedComponents(projectConfig, accountId, deployedBuildId, localProjectNodes, profile) {
|
|
198
|
+
const result = {
|
|
199
|
+
added: [],
|
|
200
|
+
updated: [],
|
|
201
|
+
removed: [],
|
|
202
|
+
};
|
|
189
203
|
try {
|
|
190
204
|
const deployedProjectNodes = await getDeployedProjectNodes(projectConfig, accountId, deployedBuildId, profile);
|
|
191
|
-
|
|
205
|
+
const filteredLocal = filterAutoGeneratedNodes(localProjectNodes);
|
|
206
|
+
const filteredDeployed = filterAutoGeneratedNodes(deployedProjectNodes);
|
|
207
|
+
const allUids = new Set([
|
|
208
|
+
...Object.keys(filteredLocal),
|
|
209
|
+
...Object.keys(filteredDeployed),
|
|
210
|
+
]);
|
|
211
|
+
for (const uid of allUids) {
|
|
212
|
+
const local = filteredLocal[uid];
|
|
213
|
+
const deployed = filteredDeployed[uid];
|
|
214
|
+
if (!local) {
|
|
215
|
+
result.removed.push(path.join(projectConfig.srcDir, deployed.metaFilePath));
|
|
216
|
+
}
|
|
217
|
+
else if (!deployed) {
|
|
218
|
+
result.added.push(path.join(projectConfig.srcDir, local.metaFilePath));
|
|
219
|
+
}
|
|
220
|
+
else if (!isDeepEqual(local, deployed, ['localDev', 'componentDeps'])) {
|
|
221
|
+
result.updated.push(path.join(projectConfig.srcDir, local.metaFilePath));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
192
225
|
}
|
|
193
226
|
catch (err) {
|
|
194
227
|
debugError(err);
|
|
195
|
-
|
|
228
|
+
uiLogger.warn(lib.localDevHelpers.project.compareLocalProjectToDeployed.unableToCompare);
|
|
229
|
+
return result;
|
|
196
230
|
}
|
|
197
231
|
}
|
|
198
232
|
export async function checkAndInstallDependencies() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSandboxUsageLimits } from '@hubspot/local-dev-lib/api/sandboxHubs';
|
|
2
|
-
import {
|
|
2
|
+
import { HUBSPOT_ACCOUNT_TYPE_STRINGS, HUBSPOT_ACCOUNT_TYPES, } from '@hubspot/local-dev-lib/constants/config';
|
|
3
3
|
import { fetchDeveloperTestAccounts } from '@hubspot/local-dev-lib/api/developerTestAccounts';
|
|
4
4
|
import { promptUser } from './promptUtils.js';
|
|
5
5
|
import { lib } from '../../lang/en.js';
|
|
@@ -92,7 +92,8 @@ export async function selectDeveloperTestTargetAccountPrompt(accounts, defaultAc
|
|
|
92
92
|
devTestAccountsResponse = data;
|
|
93
93
|
}
|
|
94
94
|
catch (err) {
|
|
95
|
-
uiLogger.
|
|
95
|
+
uiLogger.error(lib.prompts.projectDevTargetAccountPrompt.fetchDeveloperTestAccountsError);
|
|
96
|
+
throw new PromptExitError(lib.prompts.projectDevTargetAccountPrompt.fetchDeveloperTestAccountsError, EXIT_CODES.ERROR);
|
|
96
97
|
}
|
|
97
98
|
let disabledMessage = false;
|
|
98
99
|
if (devTestAccountsResponse &&
|
|
@@ -10,7 +10,7 @@ import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
|
10
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
11
|
const __dirname = path.dirname(__filename);
|
|
12
12
|
// cms-dev-server version to install to isolated cache
|
|
13
|
-
const TARGET_CMS_DEV_SERVER_VERSION = '1.2.
|
|
13
|
+
const TARGET_CMS_DEV_SERVER_VERSION = '1.2.44';
|
|
14
14
|
/**
|
|
15
15
|
* Ensures cms-dev-server is installed in an isolated cache directory.
|
|
16
16
|
* This prevents React version conflicts with the CLI.
|
package/lib/usageTracking.d.ts
CHANGED
|
@@ -19,3 +19,4 @@ export declare function trackHelpUsage(command: string): Promise<void>;
|
|
|
19
19
|
export declare function trackConvertFieldsUsage(command: string): Promise<void>;
|
|
20
20
|
export declare function trackAuthAction(command: string, authType: string, step: string, accountId?: number): Promise<void>;
|
|
21
21
|
export declare function trackCommandMetadataUsage(command: string, meta?: UsageTrackingMeta, accountId?: number): Promise<void>;
|
|
22
|
+
export declare function trackMcpPromotionShown(command?: string): Promise<void>;
|
package/lib/usageTracking.js
CHANGED
|
@@ -171,6 +171,15 @@ export async function trackCommandMetadataUsage(command, meta = {}, accountId) {
|
|
|
171
171
|
meta,
|
|
172
172
|
});
|
|
173
173
|
}
|
|
174
|
+
export async function trackMcpPromotionShown(command) {
|
|
175
|
+
return trackCliInteraction({
|
|
176
|
+
action: 'cli-mcp-promotion',
|
|
177
|
+
command: command ?? 'mcp-promotion',
|
|
178
|
+
meta: {
|
|
179
|
+
step: 'shown',
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
}
|
|
174
183
|
async function trackCliInteraction({ action, accountId, command, authType, meta = {}, }) {
|
|
175
184
|
try {
|
|
176
185
|
const config = getConfig();
|
|
@@ -61,7 +61,7 @@ const inputSchema = {
|
|
|
61
61
|
commerceLevel: z
|
|
62
62
|
.enum(ACCOUNT_LEVEL_CHOICES_WITHOUT_STARTER)
|
|
63
63
|
.optional()
|
|
64
|
-
.describe(`
|
|
64
|
+
.describe(`Revenue Hub tier level. Options: ${ACCOUNT_LEVEL_CHOICES_WITHOUT_STARTER.join(', ')}. Defaults to ENTERPRISE if not specified.`),
|
|
65
65
|
};
|
|
66
66
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
67
67
|
const createTestAccountInputSchema = z.object({ ...inputSchema });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/cli",
|
|
3
|
-
"version": "8.2.0-experimental.
|
|
3
|
+
"version": "8.2.0-experimental.2",
|
|
4
4
|
"description": "The official CLI for developing on HubSpot",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": "https://github.com/HubSpot/hubspot-cli",
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
"!**/__tests__/**"
|
|
11
11
|
],
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@hubspot/local-dev-lib": "5.
|
|
14
|
-
"@hubspot/project-parsing-lib": "0.18.0
|
|
13
|
+
"@hubspot/local-dev-lib": "5.9.0",
|
|
14
|
+
"@hubspot/project-parsing-lib": "0.18.0",
|
|
15
15
|
"@hubspot/serverless-dev-runtime": "7.0.7",
|
|
16
|
-
"@hubspot/ui-extensions-dev-server": "2.0.
|
|
16
|
+
"@hubspot/ui-extensions-dev-server": "2.0.10",
|
|
17
17
|
"@inquirer/prompts": "7.1.0",
|
|
18
18
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
19
19
|
"archiver": "7.0.1",
|