@hubspot/cli 7.7.34-experimental.0 → 7.8.0-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/bin/cli.js +2 -0
- package/commands/app.js +1 -6
- package/commands/getStarted.d.ts +1 -1
- package/commands/getStarted.js +16 -64
- package/commands/getStartedV2.d.ts +9 -0
- package/commands/getStartedV2.js +39 -0
- package/commands/project/dev/unifiedFlow.js +1 -1
- package/commands/project/migrate.js +14 -4
- package/commands/project/upload.d.ts +2 -2
- package/commands/project/upload.js +1 -1
- package/commands/testAccount/create.js +0 -3
- package/lang/en.d.ts +1 -23
- package/lang/en.js +1 -23
- package/lib/__tests__/hasFeature.test.js +7 -145
- package/lib/constants.d.ts +0 -1
- package/lib/constants.js +0 -1
- package/lib/dependencyManagement.d.ts +5 -0
- package/lib/dependencyManagement.js +9 -0
- package/lib/hasFeature.js +0 -6
- package/lib/mcp/setup.js +1 -1
- package/lib/projectProfiles.d.ts +1 -1
- package/lib/projectProfiles.js +2 -10
- package/lib/projects/create/v3.js +2 -3
- package/lib/projects/localDev/helpers/project.d.ts +2 -2
- package/lib/projects/localDev/helpers/project.js +6 -5
- package/lib/projects/structure.d.ts +2 -2
- package/lib/projects/upload.d.ts +1 -2
- package/lib/projects/upload.js +0 -1
- package/lib/ui/SpinniesManager.js +8 -105
- 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 +2 -2
- 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 -1
- package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +1 -1
- package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +1 -1
- package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +2 -2
- package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +1 -1
- package/mcp-server/tools/cms/__tests__/HsListTool.test.js +1 -1
- package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +3 -3
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +3 -3
- package/mcp-server/tools/project/CreateProjectTool.d.ts +3 -3
- package/mcp-server/tools/project/CreateProjectTool.js +5 -5
- package/mcp-server/tools/project/DeployProjectTool.js +1 -1
- package/mcp-server/tools/project/DocFetchTool.js +2 -2
- package/mcp-server/tools/project/DocsSearchTool.js +2 -2
- package/mcp-server/tools/project/GetConfigValuesTool.js +1 -1
- package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -1
- package/mcp-server/tools/project/UploadProjectTools.js +2 -2
- package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
- package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +1 -1
- package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +1 -1
- package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +1 -1
- package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +2 -2
- package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +2 -2
- package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +1 -1
- package/mcp-server/tools/project/__tests__/GuidedWalkthroughTool.test.js +1 -1
- package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +1 -1
- package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +1 -1
- package/mcp-server/tools/project/constants.d.ts +1 -1
- package/mcp-server/tools/project/constants.js +3 -9
- package/package.json +7 -2
- package/types/Yargs.d.ts +1 -1
- package/ui/components/Ascii.d.ts +10 -0
- package/ui/components/Ascii.js +11 -0
- package/ui/components/HorizontalSelectPrompt.js +1 -1
- package/ui/views/GetStarted.d.ts +7 -0
- package/ui/views/GetStarted.js +157 -0
- package/commands/app/__tests__/install.test.d.ts +0 -1
- package/commands/app/__tests__/install.test.js +0 -47
- package/commands/app/install.d.ts +0 -8
- package/commands/app/install.js +0 -122
package/bin/cli.js
CHANGED
|
@@ -42,6 +42,7 @@ import appCommand from '../commands/app.js';
|
|
|
42
42
|
import testAccountCommands from '../commands/testAccount.js';
|
|
43
43
|
import getStartedCommand from '../commands/getStarted.js';
|
|
44
44
|
import mcpCommand from '../commands/mcp.js';
|
|
45
|
+
import getStartedV2Command from '../commands/getStartedV2.js';
|
|
45
46
|
function getTerminalWidth() {
|
|
46
47
|
const width = yargs().terminalWidth();
|
|
47
48
|
if (width >= 100)
|
|
@@ -92,6 +93,7 @@ const argv = yargs(process.argv.slice(2))
|
|
|
92
93
|
type: 'boolean',
|
|
93
94
|
})
|
|
94
95
|
.check(performChecks)
|
|
96
|
+
.command(getStartedV2Command)
|
|
95
97
|
.command(authCommand)
|
|
96
98
|
.command(initCommand)
|
|
97
99
|
.command(logsCommand)
|
package/commands/app.js
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import migrateCommand from './app/migrate.js';
|
|
2
2
|
import appSecretCommand from './app/secret.js';
|
|
3
|
-
import installAppCommand from './app/install.js';
|
|
4
3
|
import { makeYargsBuilder } from '../lib/yargsUtils.js';
|
|
5
4
|
const command = ['app', 'apps'];
|
|
6
5
|
// Keep the command hidden for now
|
|
7
6
|
const describe = undefined;
|
|
8
7
|
function appBuilder(yargs) {
|
|
9
|
-
yargs
|
|
10
|
-
.command(migrateCommand)
|
|
11
|
-
.command(appSecretCommand)
|
|
12
|
-
.command(installAppCommand)
|
|
13
|
-
.demandCommand(1, '');
|
|
8
|
+
yargs.command(migrateCommand).command(appSecretCommand).demandCommand(1, '');
|
|
14
9
|
return yargs;
|
|
15
10
|
}
|
|
16
11
|
const builder = makeYargsBuilder(appBuilder, command, describe);
|
package/commands/getStarted.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AccountArgs, YargsCommandModule, CommonArgs, ConfigArgs, EnvironmentArgs } from '../types/Yargs.js';
|
|
2
2
|
export declare const command = "get-started";
|
|
3
3
|
export declare const describe: undefined;
|
|
4
|
-
type GetStartedArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & {
|
|
4
|
+
export type GetStartedArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & {
|
|
5
5
|
name?: string;
|
|
6
6
|
dest?: string;
|
|
7
7
|
};
|
package/commands/getStarted.js
CHANGED
|
@@ -4,7 +4,6 @@ import open from 'open';
|
|
|
4
4
|
import { getCwd } from '@hubspot/local-dev-lib/path';
|
|
5
5
|
import { cloneGithubRepo } from '@hubspot/local-dev-lib/github';
|
|
6
6
|
import { commands } from '../lang/en.js';
|
|
7
|
-
import { trackCommandMetadataUsage, trackCommandUsage, } from '../lib/usageTracking.js';
|
|
8
7
|
import { EXIT_CODES } from '../lib/enums/exitCodes.js';
|
|
9
8
|
import { makeYargsBuilder } from '../lib/yargsUtils.js';
|
|
10
9
|
import { promptUser } from '../lib/prompts/promptUtils.js';
|
|
@@ -28,8 +27,9 @@ export const describe = undefined;
|
|
|
28
27
|
async function handler(args) {
|
|
29
28
|
const { derivedAccountId } = args;
|
|
30
29
|
const env = getEnv(derivedAccountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD;
|
|
31
|
-
await trackCommandUsage('get-started', {}, derivedAccountId);
|
|
32
30
|
const accountName = uiAccountDescription(derivedAccountId);
|
|
31
|
+
// TODO: Put this in constants.ts once we have a defined place for the template before INBOUND
|
|
32
|
+
const templateSource = 'robrown-hubspot/hubspot-project-components-ua-app-objects-beta';
|
|
33
33
|
uiInfoSection(commands.getStarted.startTitle, () => {
|
|
34
34
|
uiLogger.log(commands.getStarted.startDescription);
|
|
35
35
|
uiLogger.log(commands.getStarted.guideOverview(accountName));
|
|
@@ -52,8 +52,6 @@ async function handler(args) {
|
|
|
52
52
|
default: GET_STARTED_OPTIONS.APP,
|
|
53
53
|
},
|
|
54
54
|
]);
|
|
55
|
-
// Track user's initial choice
|
|
56
|
-
await trackCommandMetadataUsage('get-started', { step: 'select-option', type: selectedOption }, derivedAccountId);
|
|
57
55
|
if (selectedOption === GET_STARTED_OPTIONS.CMS) {
|
|
58
56
|
uiLogger.log(' ');
|
|
59
57
|
uiLogger.log(commands.getStarted.designManager);
|
|
@@ -66,11 +64,6 @@ async function handler(args) {
|
|
|
66
64
|
message: commands.getStarted.openDesignManagerPrompt,
|
|
67
65
|
},
|
|
68
66
|
]);
|
|
69
|
-
// Track Design Manager browser action
|
|
70
|
-
await trackCommandMetadataUsage('get-started', {
|
|
71
|
-
step: 'open-design-manager',
|
|
72
|
-
type: shouldOpen ? 'opened' : 'declined',
|
|
73
|
-
}, derivedAccountId);
|
|
74
67
|
if (shouldOpen) {
|
|
75
68
|
uiLogger.log('');
|
|
76
69
|
openLink(derivedAccountId, 'design-manager');
|
|
@@ -81,37 +74,36 @@ async function handler(args) {
|
|
|
81
74
|
else {
|
|
82
75
|
uiLogger.log(' ');
|
|
83
76
|
uiLogger.log(commands.getStarted.logs.appSelected);
|
|
77
|
+
// 1. Fetch project templates
|
|
78
|
+
let latestRepoReleaseTag;
|
|
84
79
|
const { dest, name } = await projectNameAndDestPrompt(args);
|
|
80
|
+
// Specific template for get-started command
|
|
81
|
+
const projectTemplate = {
|
|
82
|
+
name: 'private-app-get-started-template',
|
|
83
|
+
label: 'CRM getting started project with private apps',
|
|
84
|
+
path: 'projects/private-app-get-started-template',
|
|
85
|
+
};
|
|
86
|
+
// 3. Create the project files
|
|
85
87
|
const projectDest = path.resolve(getCwd(), dest);
|
|
86
88
|
const { projectConfig: existingProjectConfig, projectDir: existingProjectDir, } = await getProjectConfig(projectDest);
|
|
87
89
|
if (existingProjectConfig &&
|
|
88
90
|
existingProjectDir &&
|
|
89
91
|
projectDest.startsWith(existingProjectDir)) {
|
|
90
|
-
// Track nested project error
|
|
91
|
-
await trackCommandMetadataUsage('get-started', {
|
|
92
|
-
successful: false,
|
|
93
|
-
step: 'project-creation',
|
|
94
|
-
}, derivedAccountId);
|
|
95
92
|
uiLogger.log(' ');
|
|
96
93
|
uiLogger.error(commands.project.create.errors.cannotNestProjects(existingProjectDir));
|
|
97
94
|
process.exit(EXIT_CODES.ERROR);
|
|
98
95
|
}
|
|
96
|
+
const repo = templateSource || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH;
|
|
99
97
|
// 4. Clone the project template from GitHub
|
|
98
|
+
// This is temporary until we have the UA template in the main repo
|
|
100
99
|
try {
|
|
101
|
-
await cloneGithubRepo(
|
|
102
|
-
sourceDir:
|
|
100
|
+
await cloneGithubRepo(repo, projectDest, {
|
|
101
|
+
sourceDir: projectTemplate.path,
|
|
102
|
+
tag: latestRepoReleaseTag,
|
|
103
103
|
hideLogs: true,
|
|
104
104
|
});
|
|
105
|
-
await trackCommandMetadataUsage('get-started', {
|
|
106
|
-
successful: true,
|
|
107
|
-
step: 'github-clone',
|
|
108
|
-
}, derivedAccountId);
|
|
109
105
|
}
|
|
110
106
|
catch (err) {
|
|
111
|
-
await trackCommandMetadataUsage('get-started', {
|
|
112
|
-
successful: false,
|
|
113
|
-
step: 'github-clone',
|
|
114
|
-
}, derivedAccountId);
|
|
115
107
|
debugError(err);
|
|
116
108
|
uiLogger.log(' ');
|
|
117
109
|
uiLogger.error(commands.project.create.errors.failedToDownloadProject);
|
|
@@ -130,11 +122,6 @@ async function handler(args) {
|
|
|
130
122
|
uiLogger.log(' ');
|
|
131
123
|
uiLogger.log(commands.getStarted.prompts.projectCreated.description);
|
|
132
124
|
uiLogger.log(' ');
|
|
133
|
-
// Track successful project creation
|
|
134
|
-
await trackCommandMetadataUsage('get-started', {
|
|
135
|
-
successful: true,
|
|
136
|
-
step: 'project-creation',
|
|
137
|
-
}, derivedAccountId);
|
|
138
125
|
// 5. Install dependencies
|
|
139
126
|
const installLocations = await getProjectPackageJsonLocations(projectDest);
|
|
140
127
|
try {
|
|
@@ -160,21 +147,11 @@ async function handler(args) {
|
|
|
160
147
|
default: true,
|
|
161
148
|
},
|
|
162
149
|
]);
|
|
163
|
-
// Track upload decision
|
|
164
|
-
await trackCommandMetadataUsage('get-started', {
|
|
165
|
-
step: 'upload-decision',
|
|
166
|
-
type: shouldUpload ? 'upload' : 'skip',
|
|
167
|
-
}, derivedAccountId);
|
|
168
150
|
if (shouldUpload) {
|
|
169
151
|
try {
|
|
170
152
|
// Get the project config for the newly created project
|
|
171
153
|
const { projectConfig: newProjectConfig, projectDir: newProjectDir } = await getProjectConfig(projectDest);
|
|
172
154
|
if (!newProjectConfig || !newProjectDir) {
|
|
173
|
-
// Track config file not found error
|
|
174
|
-
await trackCommandMetadataUsage('get-started', {
|
|
175
|
-
successful: false,
|
|
176
|
-
step: 'config-file-not-found',
|
|
177
|
-
}, derivedAccountId);
|
|
178
155
|
uiLogger.log(' ');
|
|
179
156
|
uiLogger.error(commands.getStarted.errors.configFileNotFound);
|
|
180
157
|
process.exit(EXIT_CODES.ERROR);
|
|
@@ -195,21 +172,11 @@ async function handler(args) {
|
|
|
195
172
|
skipValidation: false,
|
|
196
173
|
});
|
|
197
174
|
if (uploadError) {
|
|
198
|
-
// Track upload failure
|
|
199
|
-
await trackCommandMetadataUsage('get-started', {
|
|
200
|
-
successful: false,
|
|
201
|
-
step: 'upload',
|
|
202
|
-
}, derivedAccountId);
|
|
203
175
|
uiLogger.log(' ');
|
|
204
176
|
uiLogger.error(commands.getStarted.errors.uploadFailed);
|
|
205
177
|
debugError(uploadError);
|
|
206
178
|
}
|
|
207
179
|
else if (result) {
|
|
208
|
-
// Track successful upload completion
|
|
209
|
-
await trackCommandMetadataUsage('get-started', {
|
|
210
|
-
successful: true,
|
|
211
|
-
step: 'upload',
|
|
212
|
-
}, derivedAccountId);
|
|
213
180
|
uiLogger.log(' ');
|
|
214
181
|
uiLogger.success(commands.getStarted.logs.uploadSuccess);
|
|
215
182
|
const { data: { results }, } = await fetchPublicAppsForPortal(derivedAccountId);
|
|
@@ -225,11 +192,6 @@ async function handler(args) {
|
|
|
225
192
|
message: commands.getStarted.openInstallUrl,
|
|
226
193
|
},
|
|
227
194
|
]);
|
|
228
|
-
// Track Developer Overview browser action
|
|
229
|
-
await trackCommandMetadataUsage('get-started', {
|
|
230
|
-
step: 'open-distribution-page',
|
|
231
|
-
type: shouldOpenOverview ? 'opened' : 'declined',
|
|
232
|
-
}, derivedAccountId);
|
|
233
195
|
if (shouldOpenOverview) {
|
|
234
196
|
open(getStaticAuthAppInstallUrl({
|
|
235
197
|
targetAccountId: derivedAccountId,
|
|
@@ -245,11 +207,6 @@ async function handler(args) {
|
|
|
245
207
|
}
|
|
246
208
|
}
|
|
247
209
|
catch (err) {
|
|
248
|
-
// Track upload exception
|
|
249
|
-
await trackCommandMetadataUsage('get-started', {
|
|
250
|
-
successful: false,
|
|
251
|
-
step: 'upload',
|
|
252
|
-
}, derivedAccountId);
|
|
253
210
|
uiLogger.log(' ');
|
|
254
211
|
uiLogger.error(commands.getStarted.errors.uploadFailed);
|
|
255
212
|
debugError(err);
|
|
@@ -257,11 +214,6 @@ async function handler(args) {
|
|
|
257
214
|
}
|
|
258
215
|
}
|
|
259
216
|
}
|
|
260
|
-
// Track successful completion of get-started command
|
|
261
|
-
await trackCommandMetadataUsage('get-started', {
|
|
262
|
-
successful: true,
|
|
263
|
-
step: 'command-completed',
|
|
264
|
-
}, derivedAccountId);
|
|
265
217
|
process.exit(EXIT_CODES.SUCCESS);
|
|
266
218
|
}
|
|
267
219
|
function getStartedBuilder(yargs) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AccountArgs, YargsCommandModule, CommonArgs, ConfigArgs, EnvironmentArgs } from '../types/Yargs.js';
|
|
2
|
+
export declare const command = "get-started-v2";
|
|
3
|
+
export declare const describe: undefined;
|
|
4
|
+
export type GetStartedArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & {
|
|
5
|
+
name?: string;
|
|
6
|
+
dest?: string;
|
|
7
|
+
};
|
|
8
|
+
declare const getStartedV2Command: YargsCommandModule<unknown, GetStartedArgs>;
|
|
9
|
+
export default getStartedV2Command;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { commands } from '../lang/en.js';
|
|
2
|
+
import { makeYargsBuilder } from '../lib/yargsUtils.js';
|
|
3
|
+
import { getGetStarted } from '../ui/views/GetStarted.js';
|
|
4
|
+
import { render } from 'ink';
|
|
5
|
+
export const command = 'get-started-v2';
|
|
6
|
+
export const describe = undefined;
|
|
7
|
+
async function handler(args) {
|
|
8
|
+
render(getGetStarted({ args }));
|
|
9
|
+
}
|
|
10
|
+
function getStartedBuilder(yargs) {
|
|
11
|
+
yargs.options({
|
|
12
|
+
name: {
|
|
13
|
+
describe: commands.getStarted.options.name.describe,
|
|
14
|
+
type: 'string',
|
|
15
|
+
},
|
|
16
|
+
dest: {
|
|
17
|
+
describe: commands.getStarted.options.dest.describe,
|
|
18
|
+
type: 'string',
|
|
19
|
+
},
|
|
20
|
+
'template-source': {
|
|
21
|
+
describe: commands.getStarted.options.templateSource.describe,
|
|
22
|
+
type: 'string',
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
return yargs;
|
|
26
|
+
}
|
|
27
|
+
const builder = makeYargsBuilder(getStartedBuilder, command, commands.getStarted.verboseDescribe, {
|
|
28
|
+
useGlobalOptions: true,
|
|
29
|
+
useAccountOptions: true,
|
|
30
|
+
useConfigOptions: true,
|
|
31
|
+
useEnvironmentOptions: true,
|
|
32
|
+
});
|
|
33
|
+
const getStartedV2Command = {
|
|
34
|
+
command,
|
|
35
|
+
describe,
|
|
36
|
+
handler,
|
|
37
|
+
builder,
|
|
38
|
+
};
|
|
39
|
+
export default getStartedV2Command;
|
|
@@ -109,7 +109,7 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
|
|
|
109
109
|
let project = uploadedProject;
|
|
110
110
|
SpinniesManager.init();
|
|
111
111
|
if (projectExists && project) {
|
|
112
|
-
await compareLocalProjectToDeployed(projectConfig, targetProjectAccountId, project.deployedBuild?.buildId, projectNodes
|
|
112
|
+
await compareLocalProjectToDeployed(projectConfig, targetProjectAccountId, project.deployedBuild?.buildId, projectNodes);
|
|
113
113
|
}
|
|
114
114
|
else {
|
|
115
115
|
project = await createNewProjectForLocalDev(projectConfig, targetProjectAccountId, false, false);
|
|
@@ -8,6 +8,8 @@ import { uiCommandReference } from '../../lib/ui/index.js';
|
|
|
8
8
|
import { commands, lib } from '../../lang/en.js';
|
|
9
9
|
import { uiLogger } from '../../lib/ui/logger.js';
|
|
10
10
|
import { logInBox } from '../../lib/ui/boxen.js';
|
|
11
|
+
import { renderInline } from '../../ui/index.js';
|
|
12
|
+
import { getWarningBox } from '../../ui/components/StatusMessageBoxes.js';
|
|
11
13
|
const { v2025_2 } = PLATFORM_VERSIONS;
|
|
12
14
|
const command = 'migrate';
|
|
13
15
|
const describe = undefined; // commands.project.migrate.describe
|
|
@@ -19,10 +21,18 @@ async function handler(args) {
|
|
|
19
21
|
return process.exit(EXIT_CODES.ERROR);
|
|
20
22
|
}
|
|
21
23
|
if (projectConfig?.projectConfig) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
if (!process.env.ENABLE_INK) {
|
|
25
|
+
await logInBox({
|
|
26
|
+
contents: lib.migrate.projectMigrationWarning,
|
|
27
|
+
options: { title: lib.migrate.projectMigrationWarningTitle },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
await renderInline(getWarningBox({
|
|
32
|
+
title: lib.migrate.projectMigrationWarningTitle,
|
|
33
|
+
message: lib.migrate.projectMigrationWarning,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
26
36
|
}
|
|
27
37
|
const { derivedAccountId } = args;
|
|
28
38
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CommonArgs,
|
|
2
|
-
type ProjectUploadArgs = CommonArgs & JSONOutputArgs &
|
|
1
|
+
import { CommonArgs, JSONOutputArgs, YargsCommandModule } from '../../types/Yargs.js';
|
|
2
|
+
type ProjectUploadArgs = CommonArgs & JSONOutputArgs & {
|
|
3
3
|
forceCreate: boolean;
|
|
4
4
|
message: string;
|
|
5
5
|
m: string;
|
|
@@ -25,7 +25,7 @@ async function handler(args) {
|
|
|
25
25
|
validateProjectConfig(projectConfig, projectDir);
|
|
26
26
|
let targetAccountId;
|
|
27
27
|
if (useV3Api(projectConfig.platformVersion)) {
|
|
28
|
-
targetAccountId = await loadAndValidateProfile(projectConfig, projectDir, profile
|
|
28
|
+
targetAccountId = await loadAndValidateProfile(projectConfig, projectDir, profile);
|
|
29
29
|
}
|
|
30
30
|
targetAccountId = targetAccountId || derivedAccountId;
|
|
31
31
|
const accountConfig = getAccountConfig(targetAccountId);
|
|
@@ -75,9 +75,6 @@ async function handler(args) {
|
|
|
75
75
|
resultJson.personalAccessKey = createResult.personalAccessKey;
|
|
76
76
|
}
|
|
77
77
|
catch (err) {
|
|
78
|
-
SpinniesManager.fail('createTestAccount', {
|
|
79
|
-
text: commands.testAccount.create.polling.createFailure,
|
|
80
|
-
});
|
|
81
78
|
logError(err);
|
|
82
79
|
SpinniesManager.fail('createTestAccount', {
|
|
83
80
|
text: commands.testAccount.create.polling.createFailure,
|
package/lang/en.d.ts
CHANGED
|
@@ -1553,28 +1553,6 @@ ${string}`;
|
|
|
1553
1553
|
readonly app: {
|
|
1554
1554
|
readonly describe: "Commands for managing apps.";
|
|
1555
1555
|
readonly subcommands: {
|
|
1556
|
-
readonly install: {
|
|
1557
|
-
readonly describe: "Install an OAuth app into a test account.";
|
|
1558
|
-
readonly options: {
|
|
1559
|
-
readonly appUid: "The uid of the app to install";
|
|
1560
|
-
readonly projectName: "The name of the project that contains the app";
|
|
1561
|
-
};
|
|
1562
|
-
readonly positionals: {
|
|
1563
|
-
readonly testAccountId: "The id of the test account to install the app into";
|
|
1564
|
-
};
|
|
1565
|
-
readonly errors: {
|
|
1566
|
-
readonly mustSpecifyProjectName: `You must specify a project name. Use the ${string} flag to specify the project name or run this command from within a project directory.`;
|
|
1567
|
-
readonly noAppUidFound: `No app uid found. Please specify the app uid with the ${string} flag or run this command from within a project that contains an app.`;
|
|
1568
|
-
readonly appMustBeOauth: "This command only supports installing oauth apps. Please specify an app with oauth auth type.";
|
|
1569
|
-
};
|
|
1570
|
-
readonly polling: {
|
|
1571
|
-
readonly start: "Installing app...";
|
|
1572
|
-
readonly success: "App installed successfully";
|
|
1573
|
-
readonly failure: "App installation failed";
|
|
1574
|
-
readonly error: "Error installing app";
|
|
1575
|
-
};
|
|
1576
|
-
readonly example: "Install the app with uid my-app-uid from the project named \"my-project\" into the target account with id 1234567890";
|
|
1577
|
-
};
|
|
1578
1556
|
readonly secret: {
|
|
1579
1557
|
readonly describe: "Commands for managing secrets.";
|
|
1580
1558
|
readonly subcommands: {
|
|
@@ -2623,7 +2601,7 @@ export declare const lib: {
|
|
|
2623
2601
|
readonly checking: "Checking if your deployed build is up to date...";
|
|
2624
2602
|
readonly upToDate: "Deployed build is up to date.";
|
|
2625
2603
|
readonly notUpToDate: "Your project contains undeployed local changes.";
|
|
2626
|
-
readonly notUpToDateExplanation:
|
|
2604
|
+
readonly notUpToDateExplanation: `Run ${string} to upload these changes to HubSpot, then re-run ${string} to continue local development.`;
|
|
2627
2605
|
};
|
|
2628
2606
|
readonly createNewProjectForLocalDev: {
|
|
2629
2607
|
readonly projectMustExistExplanation: (projectName: string, accountId: number) => string;
|
package/lang/en.js
CHANGED
|
@@ -1550,28 +1550,6 @@ export const commands = {
|
|
|
1550
1550
|
app: {
|
|
1551
1551
|
describe: 'Commands for managing apps.',
|
|
1552
1552
|
subcommands: {
|
|
1553
|
-
install: {
|
|
1554
|
-
describe: 'Install an OAuth app into a test account.',
|
|
1555
|
-
options: {
|
|
1556
|
-
appUid: 'The uid of the app to install',
|
|
1557
|
-
projectName: 'The name of the project that contains the app',
|
|
1558
|
-
},
|
|
1559
|
-
positionals: {
|
|
1560
|
-
testAccountId: 'The id of the test account to install the app into',
|
|
1561
|
-
},
|
|
1562
|
-
errors: {
|
|
1563
|
-
mustSpecifyProjectName: `You must specify a project name. Use the ${uiCommandReference('--project-name')} flag to specify the project name or run this command from within a project directory.`,
|
|
1564
|
-
noAppUidFound: `No app uid found. Please specify the app uid with the ${uiCommandReference('--app-uid')} flag or run this command from within a project that contains an app.`,
|
|
1565
|
-
appMustBeOauth: 'This command only supports installing oauth apps. Please specify an app with oauth auth type.',
|
|
1566
|
-
},
|
|
1567
|
-
polling: {
|
|
1568
|
-
start: 'Installing app...',
|
|
1569
|
-
success: 'App installed successfully',
|
|
1570
|
-
failure: 'App installation failed',
|
|
1571
|
-
error: 'Error installing app',
|
|
1572
|
-
},
|
|
1573
|
-
example: 'Install the app with uid my-app-uid from the project named "my-project" into the target account with id 1234567890',
|
|
1574
|
-
},
|
|
1575
1553
|
secret: {
|
|
1576
1554
|
describe: 'Commands for managing secrets.',
|
|
1577
1555
|
subcommands: {
|
|
@@ -2620,7 +2598,7 @@ export const lib = {
|
|
|
2620
2598
|
checking: 'Checking if your deployed build is up to date...',
|
|
2621
2599
|
upToDate: 'Deployed build is up to date.',
|
|
2622
2600
|
notUpToDate: `Your project contains undeployed local changes.`,
|
|
2623
|
-
notUpToDateExplanation:
|
|
2601
|
+
notUpToDateExplanation: `Run ${uiCommandReference('hs project upload')} to upload these changes to HubSpot, then re-run ${uiCommandReference('hs project dev')} to continue local development.`,
|
|
2624
2602
|
},
|
|
2625
2603
|
createNewProjectForLocalDev: {
|
|
2626
2604
|
projectMustExistExplanation: (projectName, accountId) => `The project ${projectName} does not exist in the target account ${uiAccountDescription(accountId)}. This command requires the project to exist in the target account.`,
|
|
@@ -1,173 +1,35 @@
|
|
|
1
1
|
import { fetchEnabledFeatures } from '@hubspot/local-dev-lib/api/localDevAuth';
|
|
2
|
-
import {
|
|
3
|
-
import { hasFeature, hasUnfiedAppsAccess } from '../hasFeature.js';
|
|
4
|
-
import { FEATURES } from '../constants.js';
|
|
2
|
+
import { hasFeature } from '../hasFeature.js';
|
|
5
3
|
vi.mock('@hubspot/local-dev-lib/api/localDevAuth');
|
|
6
|
-
vi.mock('@hubspot/local-dev-lib/http');
|
|
7
4
|
const mockedFetchEnabledFeatures = fetchEnabledFeatures;
|
|
8
|
-
const mockedHttp = http;
|
|
9
5
|
describe('lib/hasFeature', () => {
|
|
10
6
|
describe('hasFeature()', () => {
|
|
11
7
|
const accountId = 123;
|
|
12
|
-
|
|
13
|
-
vi.clearAllMocks();
|
|
14
|
-
});
|
|
15
|
-
it('should return true if the feature is enabled', async () => {
|
|
8
|
+
beforeEach(() => {
|
|
16
9
|
mockedFetchEnabledFeatures.mockResolvedValueOnce({
|
|
17
10
|
data: {
|
|
18
11
|
enabledFeatures: {
|
|
19
12
|
'feature-1': true,
|
|
13
|
+
'feature-2': false,
|
|
14
|
+
'feature-3': true,
|
|
20
15
|
},
|
|
21
16
|
},
|
|
22
17
|
});
|
|
18
|
+
});
|
|
19
|
+
it('should return true if the feature is enabled', async () => {
|
|
23
20
|
// @ts-expect-error test data
|
|
24
21
|
const result = await hasFeature(accountId, 'feature-1');
|
|
25
22
|
expect(result).toBe(true);
|
|
26
23
|
});
|
|
27
|
-
it('should return false if the feature is
|
|
28
|
-
mockedFetchEnabledFeatures.mockResolvedValueOnce({
|
|
29
|
-
data: {
|
|
30
|
-
enabledFeatures: {
|
|
31
|
-
'feature-2': false,
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
});
|
|
24
|
+
it('should return false if the feature is not enabled', async () => {
|
|
35
25
|
// @ts-expect-error test data
|
|
36
26
|
const result = await hasFeature(accountId, 'feature-2');
|
|
37
27
|
expect(result).toBe(false);
|
|
38
28
|
});
|
|
39
29
|
it('should return false if the feature is not present', async () => {
|
|
40
|
-
mockedFetchEnabledFeatures.mockResolvedValueOnce({
|
|
41
|
-
data: {
|
|
42
|
-
enabledFeatures: {},
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
30
|
// @ts-expect-error test data
|
|
46
31
|
const result = await hasFeature(accountId, 'feature-4');
|
|
47
32
|
expect(result).toBe(false);
|
|
48
33
|
});
|
|
49
|
-
it('should return true for APPS_HOME feature when not present in enabled features (defaults on)', async () => {
|
|
50
|
-
mockedFetchEnabledFeatures.mockResolvedValueOnce({
|
|
51
|
-
data: {
|
|
52
|
-
enabledFeatures: {},
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
const result = await hasFeature(accountId, FEATURES.APPS_HOME);
|
|
56
|
-
expect(result).toBe(true);
|
|
57
|
-
});
|
|
58
|
-
it('should respect explicit setting for APPS_HOME feature even when it defaults on', async () => {
|
|
59
|
-
mockedFetchEnabledFeatures.mockResolvedValueOnce({
|
|
60
|
-
data: {
|
|
61
|
-
enabledFeatures: {
|
|
62
|
-
[FEATURES.APPS_HOME]: false,
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
const result = await hasFeature(accountId, FEATURES.APPS_HOME);
|
|
67
|
-
expect(result).toBe(false);
|
|
68
|
-
});
|
|
69
|
-
it('should handle truthy values correctly', async () => {
|
|
70
|
-
mockedFetchEnabledFeatures.mockResolvedValueOnce({
|
|
71
|
-
data: {
|
|
72
|
-
enabledFeatures: {
|
|
73
|
-
'feature-truthy': 'yes',
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
// @ts-expect-error test data
|
|
78
|
-
const truthyResult = await hasFeature(accountId, 'feature-truthy');
|
|
79
|
-
expect(truthyResult).toBe(true);
|
|
80
|
-
mockedFetchEnabledFeatures.mockResolvedValueOnce({
|
|
81
|
-
data: {
|
|
82
|
-
enabledFeatures: {
|
|
83
|
-
'feature-number': 1,
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
// @ts-expect-error test data
|
|
88
|
-
const numberResult = await hasFeature(accountId, 'feature-number');
|
|
89
|
-
expect(numberResult).toBe(true);
|
|
90
|
-
});
|
|
91
|
-
it('should handle falsy values correctly', async () => {
|
|
92
|
-
mockedFetchEnabledFeatures.mockResolvedValueOnce({
|
|
93
|
-
data: {
|
|
94
|
-
enabledFeatures: {
|
|
95
|
-
'feature-null': null,
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
// @ts-expect-error test data
|
|
100
|
-
const nullResult = await hasFeature(accountId, 'feature-null');
|
|
101
|
-
expect(nullResult).toBe(false);
|
|
102
|
-
mockedFetchEnabledFeatures.mockResolvedValueOnce({
|
|
103
|
-
data: {
|
|
104
|
-
enabledFeatures: {
|
|
105
|
-
'feature-zero': 0,
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
});
|
|
109
|
-
// @ts-expect-error test data
|
|
110
|
-
const zeroResult = await hasFeature(accountId, 'feature-zero');
|
|
111
|
-
expect(zeroResult).toBe(false);
|
|
112
|
-
mockedFetchEnabledFeatures.mockResolvedValueOnce({
|
|
113
|
-
data: {
|
|
114
|
-
enabledFeatures: {
|
|
115
|
-
'feature-empty': '',
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
// @ts-expect-error test data
|
|
120
|
-
const emptyResult = await hasFeature(accountId, 'feature-empty');
|
|
121
|
-
expect(emptyResult).toBe(false);
|
|
122
|
-
});
|
|
123
|
-
it('should propagate errors from fetchEnabledFeatures', async () => {
|
|
124
|
-
const error = new Error('API error');
|
|
125
|
-
mockedFetchEnabledFeatures.mockRejectedValueOnce(error);
|
|
126
|
-
await expect(hasFeature(accountId, FEATURES.UNIFIED_APPS)).rejects.toThrow('API error');
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
describe('hasUnfiedAppsAccess()', () => {
|
|
130
|
-
const accountId = 123;
|
|
131
|
-
afterEach(() => {
|
|
132
|
-
vi.clearAllMocks();
|
|
133
|
-
});
|
|
134
|
-
it('should return true when API returns true', async () => {
|
|
135
|
-
// @ts-expect-error Don't want to mock the full response object
|
|
136
|
-
mockedHttp.get.mockResolvedValueOnce({ data: true });
|
|
137
|
-
const result = await hasUnfiedAppsAccess(accountId);
|
|
138
|
-
expect(result).toBe(true);
|
|
139
|
-
expect(mockedHttp.get).toHaveBeenCalledWith(accountId, {
|
|
140
|
-
url: 'developer-tooling/external/developer-portal/has-unified-dev-platform-access',
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
it('should return false when API returns false', async () => {
|
|
144
|
-
// @ts-expect-error Don't want to mock the full response object
|
|
145
|
-
mockedHttp.get.mockResolvedValueOnce({ data: false });
|
|
146
|
-
const result = await hasUnfiedAppsAccess(accountId);
|
|
147
|
-
expect(result).toBe(false);
|
|
148
|
-
});
|
|
149
|
-
it('should handle truthy values correctly', async () => {
|
|
150
|
-
// @ts-expect-error Don't want to mock the full response object
|
|
151
|
-
mockedHttp.get.mockResolvedValueOnce({ data: 'yes' });
|
|
152
|
-
const result = await hasUnfiedAppsAccess(accountId);
|
|
153
|
-
expect(result).toBe(true);
|
|
154
|
-
});
|
|
155
|
-
it('should handle falsy values correctly', async () => {
|
|
156
|
-
// @ts-expect-error Don't want to mock the full response object
|
|
157
|
-
mockedHttp.get.mockResolvedValueOnce({ data: null });
|
|
158
|
-
const result = await hasUnfiedAppsAccess(accountId);
|
|
159
|
-
expect(result).toBe(false);
|
|
160
|
-
});
|
|
161
|
-
it('should handle undefined response data', async () => {
|
|
162
|
-
// @ts-expect-error Don't want to mock the full response object
|
|
163
|
-
mockedHttp.get.mockResolvedValueOnce({ data: undefined });
|
|
164
|
-
const result = await hasUnfiedAppsAccess(accountId);
|
|
165
|
-
expect(result).toBe(false);
|
|
166
|
-
});
|
|
167
|
-
it('should propagate errors from http.get', async () => {
|
|
168
|
-
const error = new Error('Network error');
|
|
169
|
-
mockedHttp.get.mockRejectedValueOnce(error);
|
|
170
|
-
await expect(hasUnfiedAppsAccess(accountId)).rejects.toThrow('Network error');
|
|
171
|
-
});
|
|
172
34
|
});
|
|
173
35
|
});
|
package/lib/constants.d.ts
CHANGED
|
@@ -81,7 +81,6 @@ export declare const FEATURES: {
|
|
|
81
81
|
readonly SANDBOXES_V2: "sandboxes:v2:enabled";
|
|
82
82
|
readonly SANDBOXES_V2_CLI: "sandboxes:v2:cliEnabled";
|
|
83
83
|
readonly APP_EVENTS: "Developers:UnifiedApps:AppEventsAccess";
|
|
84
|
-
readonly APPS_HOME: "UIE:AppHome";
|
|
85
84
|
};
|
|
86
85
|
export declare const LOCAL_DEV_UI_MESSAGE_SEND_TYPES: {
|
|
87
86
|
UPLOAD_SUCCESS: string;
|
package/lib/constants.js
CHANGED
|
@@ -73,7 +73,6 @@ export const FEATURES = {
|
|
|
73
73
|
SANDBOXES_V2: 'sandboxes:v2:enabled',
|
|
74
74
|
SANDBOXES_V2_CLI: 'sandboxes:v2:cliEnabled',
|
|
75
75
|
APP_EVENTS: 'Developers:UnifiedApps:AppEventsAccess',
|
|
76
|
-
APPS_HOME: 'UIE:AppHome',
|
|
77
76
|
};
|
|
78
77
|
export const LOCAL_DEV_UI_MESSAGE_SEND_TYPES = {
|
|
79
78
|
UPLOAD_SUCCESS: 'server:uploadSuccess',
|
|
@@ -2,5 +2,10 @@ export declare function installPackages({ packages, installLocations, }: {
|
|
|
2
2
|
packages?: string[];
|
|
3
3
|
installLocations?: string[];
|
|
4
4
|
}): Promise<void>;
|
|
5
|
+
export declare function installPackagesV2({ packages, installLocations, }: {
|
|
6
|
+
packages?: string[];
|
|
7
|
+
installLocations?: string[];
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
export declare function installPackagesInDirectoryV2(directory: string, packages?: string[]): Promise<void>;
|
|
5
10
|
export declare function getProjectPackageJsonLocations(dir?: string): Promise<string[]>;
|
|
6
11
|
export declare function hasMissingPackages(directory: string): Promise<boolean>;
|
|
@@ -22,6 +22,15 @@ export async function installPackages({ packages, installLocations, }) {
|
|
|
22
22
|
await installPackagesInDirectory(dir, packages);
|
|
23
23
|
}));
|
|
24
24
|
}
|
|
25
|
+
export async function installPackagesV2({ packages, installLocations, }) {
|
|
26
|
+
const installDirs = installLocations || (await getProjectPackageJsonLocations());
|
|
27
|
+
await Promise.all(installDirs.map(async (dir) => {
|
|
28
|
+
await installPackagesInDirectoryV2(dir, packages);
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
export async function installPackagesInDirectoryV2(directory, packages) {
|
|
32
|
+
await executeInstall(packages, null, { cwd: directory });
|
|
33
|
+
}
|
|
25
34
|
async function installPackagesInDirectory(directory, packages) {
|
|
26
35
|
const spinner = `installingDependencies-${directory}`;
|
|
27
36
|
const relativeDir = path.relative(process.cwd(), directory);
|