@hubspot/cli 5.3.1 → 5.4.1-beta.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 +24 -5
- package/commands/__tests__/projects.test.js +105 -0
- package/commands/accounts/clean.js +1 -1
- package/commands/cms/convertFields.js +13 -7
- package/commands/project/__tests__/deploy.test.js +1 -1
- package/commands/project/__tests__/installDeps.test.js +168 -0
- package/commands/project/__tests__/logs.test.js +305 -0
- package/commands/project/add.js +24 -12
- package/commands/project/cloneApp.js +13 -21
- package/commands/project/deploy.js +4 -1
- package/commands/project/dev.js +22 -11
- package/commands/project/download.js +6 -3
- package/commands/project/installDeps.js +78 -0
- package/commands/project/logs.js +80 -242
- package/commands/project/migrateApp.js +8 -9
- package/commands/project/upload.js +5 -3
- package/commands/project/watch.js +3 -9
- package/commands/project.js +2 -0
- package/commands/sandbox/create.js +1 -0
- package/commands/sandbox.js +0 -2
- package/lang/en.lyaml +40 -75
- package/lib/LocalDevManager.js +1 -22
- package/lib/__tests__/dependencyManagement.test.js +245 -0
- package/lib/__tests__/projectLogsManager.test.js +210 -0
- package/lib/dependencyManagement.js +157 -0
- package/lib/errorHandlers/apiErrors.js +1 -3
- package/lib/errorHandlers/overrideErrors.js +57 -36
- package/lib/localDev.js +25 -16
- package/lib/projectLogsManager.js +144 -0
- package/lib/projects.js +17 -7
- package/lib/projectsWatch.js +2 -5
- package/lib/prompts/__tests__/projectsLogsPrompt.test.js +46 -0
- package/lib/prompts/createProjectPrompt.js +4 -0
- package/lib/prompts/projectAddPrompt.js +4 -21
- package/lib/prompts/projectDevTargetAccountPrompt.js +16 -25
- package/lib/prompts/projectsLogsPrompt.js +17 -108
- package/lib/sandboxSync.js +13 -15
- package/package.json +6 -6
- package/commands/sandbox/sync.js +0 -225
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
jest.mock('../../../lib/commonOpts');
|
|
2
|
+
jest.mock('../../../lib/usageTracking');
|
|
3
|
+
jest.mock('../../../lib/validation');
|
|
4
|
+
jest.mock('../../../lib/projectLogsManager');
|
|
5
|
+
jest.mock('../../../lib/prompts/projectsLogsPrompt');
|
|
6
|
+
jest.mock('@hubspot/local-dev-lib/logger');
|
|
7
|
+
jest.mock('../../../lib/errorHandlers/apiErrors');
|
|
8
|
+
jest.mock('../../../lib/ui/table');
|
|
9
|
+
jest.mock('../../../lib/ui');
|
|
10
|
+
jest.mock('../../../lib/errorHandlers/apiErrors');
|
|
11
|
+
|
|
12
|
+
// Deps where we don't want mocks
|
|
13
|
+
const libUi = jest.requireActual('../../../lib/ui');
|
|
14
|
+
|
|
15
|
+
const { uiLine, uiLink, uiBetaTag } = require('../../../lib/ui');
|
|
16
|
+
|
|
17
|
+
uiBetaTag.mockImplementation(libUi.uiBetaTag);
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
addUseEnvironmentOptions,
|
|
21
|
+
getAccountId,
|
|
22
|
+
} = require('../../../lib/commonOpts');
|
|
23
|
+
const ProjectLogsManager = require('../../../lib/projectLogsManager');
|
|
24
|
+
const {
|
|
25
|
+
projectLogsPrompt,
|
|
26
|
+
} = require('../../../lib/prompts/projectsLogsPrompt');
|
|
27
|
+
const { getTableContents, getTableHeader } = require('../../../lib/ui/table');
|
|
28
|
+
|
|
29
|
+
const { trackCommandUsage } = require('../../../lib/usageTracking');
|
|
30
|
+
const { logApiErrorInstance } = require('../../../lib/errorHandlers/apiErrors');
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
handler,
|
|
34
|
+
describe: logsDescribe,
|
|
35
|
+
command,
|
|
36
|
+
builder,
|
|
37
|
+
} = require('../logs');
|
|
38
|
+
const { EXIT_CODES } = require('../../../lib/enums/exitCodes');
|
|
39
|
+
|
|
40
|
+
describe('commands/project/logs', () => {
|
|
41
|
+
let processExitSpy;
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('command', () => {
|
|
47
|
+
it('should have the proper command string', async () => {
|
|
48
|
+
expect(command).toEqual('logs');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('describe', () => {
|
|
53
|
+
it('should contain the beta tag', () => {
|
|
54
|
+
expect(logsDescribe).toContain('[BETA]');
|
|
55
|
+
});
|
|
56
|
+
it('should provide an accurate description of what the command is doing', () => {
|
|
57
|
+
expect(logsDescribe).toMatch(
|
|
58
|
+
/Get execution logs for a serverless function within a project/
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('builder', () => {
|
|
64
|
+
let yargsMock = {};
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
yargsMock = {
|
|
67
|
+
options: jest.fn().mockImplementation(() => yargsMock),
|
|
68
|
+
conflicts: jest.fn().mockImplementation(() => yargsMock),
|
|
69
|
+
example: jest.fn().mockImplementation(() => yargsMock),
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should add all of the options', () => {
|
|
74
|
+
builder(yargsMock);
|
|
75
|
+
expect(yargsMock.options).toHaveBeenCalledTimes(1);
|
|
76
|
+
expect(yargsMock.options).toHaveBeenCalledWith({
|
|
77
|
+
function: {
|
|
78
|
+
alias: 'function',
|
|
79
|
+
requiresArg: true,
|
|
80
|
+
describe: 'App function name',
|
|
81
|
+
type: 'string',
|
|
82
|
+
},
|
|
83
|
+
latest: {
|
|
84
|
+
alias: 'l',
|
|
85
|
+
type: 'boolean',
|
|
86
|
+
describe: 'Retrieve most recent log only',
|
|
87
|
+
},
|
|
88
|
+
compact: {
|
|
89
|
+
type: 'boolean',
|
|
90
|
+
describe: 'Output compact logs',
|
|
91
|
+
},
|
|
92
|
+
tail: {
|
|
93
|
+
alias: ['t', 'follow'],
|
|
94
|
+
describe: 'Tail logs',
|
|
95
|
+
type: 'boolean',
|
|
96
|
+
},
|
|
97
|
+
limit: {
|
|
98
|
+
type: 'number',
|
|
99
|
+
describe: 'Limit the number of logs to output',
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should add the environment options', () => {
|
|
105
|
+
builder(yargsMock);
|
|
106
|
+
expect(addUseEnvironmentOptions).toHaveBeenCalledTimes(1);
|
|
107
|
+
expect(addUseEnvironmentOptions).toHaveBeenCalledWith(yargsMock);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should set tail and limit as conflicting arguments', () => {
|
|
111
|
+
builder(yargsMock);
|
|
112
|
+
expect(yargsMock.conflicts).toHaveBeenCalledTimes(1);
|
|
113
|
+
expect(yargsMock.conflicts).toHaveBeenCalledWith('tail', 'limit');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should set examples', () => {
|
|
117
|
+
builder(yargsMock);
|
|
118
|
+
expect(yargsMock.example).toHaveBeenCalledTimes(1);
|
|
119
|
+
expect(yargsMock.example).toHaveBeenCalledWith([
|
|
120
|
+
[
|
|
121
|
+
'$0 project logs',
|
|
122
|
+
'Open the project logs prompt to get logs for a serverless function',
|
|
123
|
+
],
|
|
124
|
+
[
|
|
125
|
+
'$0 project logs --function=my-function',
|
|
126
|
+
'Get logs for function named "my-function" within the app named "app" within the project named "my-project"',
|
|
127
|
+
],
|
|
128
|
+
]);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('handler', () => {
|
|
133
|
+
const accountId = 12345678;
|
|
134
|
+
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
getAccountId.mockReturnValue(accountId);
|
|
137
|
+
projectLogsPrompt.mockResolvedValue({ functionName: 'foo' });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should get the account id', async () => {
|
|
141
|
+
const options = {
|
|
142
|
+
foo: 'bar',
|
|
143
|
+
};
|
|
144
|
+
await handler(options);
|
|
145
|
+
expect(getAccountId).toHaveBeenCalledTimes(1);
|
|
146
|
+
expect(getAccountId).toHaveBeenCalledWith(options);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should track the command usage', async () => {
|
|
150
|
+
const options = {
|
|
151
|
+
foo: 'bar',
|
|
152
|
+
};
|
|
153
|
+
await handler(options);
|
|
154
|
+
expect(trackCommandUsage).toHaveBeenCalledTimes(1);
|
|
155
|
+
expect(trackCommandUsage).toHaveBeenCalledWith(
|
|
156
|
+
'project-logs',
|
|
157
|
+
null,
|
|
158
|
+
accountId
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should initialize the ProjectLogsManager', async () => {
|
|
163
|
+
const options = {
|
|
164
|
+
foo: 'bar',
|
|
165
|
+
};
|
|
166
|
+
await handler(options);
|
|
167
|
+
expect(ProjectLogsManager.init).toHaveBeenCalledTimes(1);
|
|
168
|
+
expect(ProjectLogsManager.init).toHaveBeenCalledWith(accountId);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should prompt the user for input', async () => {
|
|
172
|
+
const functionNames = ['function1', 'function2'];
|
|
173
|
+
ProjectLogsManager.getFunctionNames.mockReturnValue(functionNames);
|
|
174
|
+
const options = {
|
|
175
|
+
foo: 'bar',
|
|
176
|
+
};
|
|
177
|
+
await handler(options);
|
|
178
|
+
expect(projectLogsPrompt).toHaveBeenCalledTimes(1);
|
|
179
|
+
expect(projectLogsPrompt).toHaveBeenCalledWith({
|
|
180
|
+
functionChoices: functionNames,
|
|
181
|
+
promptOptions: options,
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should set the function', async () => {
|
|
186
|
+
const selectedFunction = 'function1';
|
|
187
|
+
ProjectLogsManager.getFunctionNames.mockReturnValue([
|
|
188
|
+
selectedFunction,
|
|
189
|
+
'function2',
|
|
190
|
+
]);
|
|
191
|
+
projectLogsPrompt.mockReturnValue({
|
|
192
|
+
functionName: selectedFunction,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await handler({});
|
|
196
|
+
expect(ProjectLogsManager.setFunction).toHaveBeenCalledTimes(1);
|
|
197
|
+
expect(ProjectLogsManager.setFunction).toHaveBeenCalledWith(
|
|
198
|
+
selectedFunction
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should log public functions correctly', async () => {
|
|
203
|
+
const functionNames = ['function1', 'function2'];
|
|
204
|
+
const selectedFunction = 'function1';
|
|
205
|
+
ProjectLogsManager.getFunctionNames.mockReturnValue(functionNames);
|
|
206
|
+
projectLogsPrompt.mockReturnValue({
|
|
207
|
+
functionName: selectedFunction,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const tableHeaders = ['Header 1', 'Header 2'];
|
|
211
|
+
getTableHeader.mockReturnValue(tableHeaders);
|
|
212
|
+
|
|
213
|
+
ProjectLogsManager.isPublicFunction = true;
|
|
214
|
+
ProjectLogsManager.accountId = accountId;
|
|
215
|
+
ProjectLogsManager.functionName = selectedFunction;
|
|
216
|
+
ProjectLogsManager.endpointName = 'my-endpoint';
|
|
217
|
+
ProjectLogsManager.appId = 123456;
|
|
218
|
+
|
|
219
|
+
await handler({});
|
|
220
|
+
expect(getTableHeader).toHaveBeenCalledTimes(1);
|
|
221
|
+
expect(getTableHeader).toHaveBeenCalledWith([
|
|
222
|
+
'Account',
|
|
223
|
+
'Function',
|
|
224
|
+
'Endpoint',
|
|
225
|
+
]);
|
|
226
|
+
|
|
227
|
+
expect(getTableContents).toHaveBeenCalledTimes(1);
|
|
228
|
+
expect(getTableContents).toHaveBeenCalledWith(
|
|
229
|
+
[
|
|
230
|
+
tableHeaders,
|
|
231
|
+
[
|
|
232
|
+
ProjectLogsManager.accountId,
|
|
233
|
+
ProjectLogsManager.functionName,
|
|
234
|
+
ProjectLogsManager.endpointName,
|
|
235
|
+
],
|
|
236
|
+
],
|
|
237
|
+
{ border: { bodyLeft: ' ' } }
|
|
238
|
+
);
|
|
239
|
+
expect(uiLink).toHaveBeenCalledTimes(1);
|
|
240
|
+
expect(uiLink).toHaveBeenCalledWith(
|
|
241
|
+
'View function logs in HubSpot',
|
|
242
|
+
`https://app.hubspot.com/private-apps/${accountId}/${ProjectLogsManager.appId}/logs/serverlessGatewayExecution?path=${ProjectLogsManager.endpointName}`
|
|
243
|
+
);
|
|
244
|
+
expect(uiLine).toHaveBeenCalledTimes(1);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should log private functions correctly', async () => {
|
|
248
|
+
const functionNames = ['function1', 'function2'];
|
|
249
|
+
const selectedFunction = 'function1';
|
|
250
|
+
|
|
251
|
+
ProjectLogsManager.getFunctionNames.mockReturnValue(functionNames);
|
|
252
|
+
projectLogsPrompt.mockReturnValue({
|
|
253
|
+
functionName: selectedFunction,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const tableHeaders = ['Header 1', 'Header 2'];
|
|
257
|
+
getTableHeader.mockReturnValue(tableHeaders);
|
|
258
|
+
|
|
259
|
+
ProjectLogsManager.isPublicFunction = false;
|
|
260
|
+
ProjectLogsManager.accountId = accountId;
|
|
261
|
+
ProjectLogsManager.functionName = selectedFunction;
|
|
262
|
+
ProjectLogsManager.appId = 456789;
|
|
263
|
+
|
|
264
|
+
await handler({});
|
|
265
|
+
expect(getTableHeader).toHaveBeenCalledTimes(1);
|
|
266
|
+
expect(getTableHeader).toHaveBeenCalledWith(['Account', 'Function']);
|
|
267
|
+
|
|
268
|
+
expect(getTableContents).toHaveBeenCalledTimes(1);
|
|
269
|
+
expect(getTableContents).toHaveBeenCalledWith(
|
|
270
|
+
[
|
|
271
|
+
tableHeaders,
|
|
272
|
+
[ProjectLogsManager.accountId, ProjectLogsManager.functionName],
|
|
273
|
+
],
|
|
274
|
+
{ border: { bodyLeft: ' ' } }
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
expect(uiLink).toHaveBeenCalledWith(
|
|
278
|
+
'View function logs in HubSpot',
|
|
279
|
+
`https://app.hubspot.com/private-apps/${accountId}/${ProjectLogsManager.appId}/logs/crm?serverlessFunction=${selectedFunction}`
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
expect(uiLine).toHaveBeenCalledTimes(1);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should handle errors correctly', async () => {
|
|
286
|
+
const error = new Error('Something went wrong');
|
|
287
|
+
ProjectLogsManager.init.mockImplementation(() => {
|
|
288
|
+
throw error;
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
ProjectLogsManager.projectName = 'Super cool project';
|
|
292
|
+
|
|
293
|
+
await handler({});
|
|
294
|
+
|
|
295
|
+
expect(logApiErrorInstance).toHaveBeenCalledTimes(1);
|
|
296
|
+
expect(logApiErrorInstance).toHaveBeenCalledWith(error, {
|
|
297
|
+
accountId: accountId,
|
|
298
|
+
projectName: ProjectLogsManager.projectName,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
expect(processExitSpy).toHaveBeenCalledTimes(1);
|
|
302
|
+
expect(processExitSpy).toHaveBeenCalledWith(EXIT_CODES.ERROR);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|
package/commands/project/add.js
CHANGED
|
@@ -6,7 +6,10 @@ const { fetchReleaseData } = require('@hubspot/local-dev-lib/github');
|
|
|
6
6
|
const { trackCommandUsage } = require('../../lib/usageTracking');
|
|
7
7
|
const { i18n } = require('../../lib/lang');
|
|
8
8
|
const { projectAddPrompt } = require('../../lib/prompts/projectAddPrompt');
|
|
9
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
createProjectComponent,
|
|
11
|
+
getProjectComponentsByVersion,
|
|
12
|
+
} = require('../../lib/projects');
|
|
10
13
|
const { loadAndValidateOptions } = require('../../lib/validation');
|
|
11
14
|
const { uiBetaTag } = require('../../lib/ui');
|
|
12
15
|
const {
|
|
@@ -21,34 +24,37 @@ exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false);
|
|
|
21
24
|
exports.handler = async options => {
|
|
22
25
|
await loadAndValidateOptions(options);
|
|
23
26
|
|
|
24
|
-
const accountId = getAccountId(options);
|
|
25
|
-
|
|
26
27
|
logger.log('');
|
|
27
28
|
logger.log(i18n(`${i18nKey}.creatingComponent.message`));
|
|
28
29
|
logger.log('');
|
|
29
30
|
|
|
31
|
+
const accountId = getAccountId(options);
|
|
32
|
+
|
|
30
33
|
const releaseData = await fetchReleaseData(
|
|
31
34
|
HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH
|
|
32
35
|
);
|
|
33
36
|
const projectComponentsVersion = releaseData.tag_name;
|
|
34
37
|
|
|
35
|
-
const
|
|
36
|
-
projectComponentsVersion
|
|
37
|
-
options
|
|
38
|
+
const components = await getProjectComponentsByVersion(
|
|
39
|
+
projectComponentsVersion
|
|
38
40
|
);
|
|
39
41
|
|
|
42
|
+
let { component, name } = await projectAddPrompt(components, options);
|
|
43
|
+
|
|
44
|
+
name = name || options.name;
|
|
45
|
+
|
|
46
|
+
if (!component) {
|
|
47
|
+
component = components.find(t => t.path === options.type);
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
trackCommandUsage('project-add', null, accountId);
|
|
41
51
|
|
|
42
52
|
try {
|
|
43
|
-
await createProjectComponent(
|
|
44
|
-
options.type || type,
|
|
45
|
-
options.name || name,
|
|
46
|
-
projectComponentsVersion
|
|
47
|
-
);
|
|
53
|
+
await createProjectComponent(component, name, projectComponentsVersion);
|
|
48
54
|
logger.log('');
|
|
49
55
|
logger.log(
|
|
50
56
|
i18n(`${i18nKey}.success.message`, {
|
|
51
|
-
componentName:
|
|
57
|
+
componentName: name,
|
|
52
58
|
})
|
|
53
59
|
);
|
|
54
60
|
} catch (error) {
|
|
@@ -69,6 +75,12 @@ exports.builder = yargs => {
|
|
|
69
75
|
});
|
|
70
76
|
|
|
71
77
|
yargs.example([['$0 project add', i18n(`${i18nKey}.examples.default`)]]);
|
|
78
|
+
yargs.example([
|
|
79
|
+
[
|
|
80
|
+
'$0 project add --name="my-component" --type="components/example-app"',
|
|
81
|
+
i18n(`${i18nKey}.examples.withFlags`),
|
|
82
|
+
],
|
|
83
|
+
]);
|
|
72
84
|
|
|
73
85
|
return yargs;
|
|
74
86
|
};
|
|
@@ -38,13 +38,10 @@ const {
|
|
|
38
38
|
checkCloneStatus,
|
|
39
39
|
downloadClonedProject,
|
|
40
40
|
} = require('@hubspot/local-dev-lib/api/projects');
|
|
41
|
-
const { getCwd } = require('@hubspot/local-dev-lib/path');
|
|
41
|
+
const { getCwd, sanitizeFileName } = require('@hubspot/local-dev-lib/path');
|
|
42
42
|
const { logger } = require('@hubspot/local-dev-lib/logger');
|
|
43
43
|
const { getAccountConfig } = require('@hubspot/local-dev-lib/config');
|
|
44
44
|
const { extractZipArchive } = require('@hubspot/local-dev-lib/archive');
|
|
45
|
-
const {
|
|
46
|
-
fetchPublicAppMetadata,
|
|
47
|
-
} = require('@hubspot/local-dev-lib/api/appsDev');
|
|
48
45
|
|
|
49
46
|
const i18nKey = 'commands.project.subcommands.cloneApp';
|
|
50
47
|
|
|
@@ -76,8 +73,6 @@ exports.handler = async options => {
|
|
|
76
73
|
let appId;
|
|
77
74
|
let name;
|
|
78
75
|
let location;
|
|
79
|
-
let preventProjectMigrations;
|
|
80
|
-
let listingInfo;
|
|
81
76
|
try {
|
|
82
77
|
appId = options.appId;
|
|
83
78
|
if (!appId) {
|
|
@@ -89,11 +84,6 @@ exports.handler = async options => {
|
|
|
89
84
|
});
|
|
90
85
|
appId = appIdResponse.appId;
|
|
91
86
|
}
|
|
92
|
-
const selectedApp = await fetchPublicAppMetadata(appId, accountId);
|
|
93
|
-
// preventProjectMigrations returns true if we have not added app to allowlist config.
|
|
94
|
-
// listingInfo will only exist for marketplace apps
|
|
95
|
-
preventProjectMigrations = selectedApp.preventProjectMigrations;
|
|
96
|
-
listingInfo = selectedApp.listingInfo;
|
|
97
87
|
|
|
98
88
|
const projectResponse = await createProjectPrompt('', options, true);
|
|
99
89
|
name = projectResponse.name;
|
|
@@ -119,10 +109,15 @@ exports.handler = async options => {
|
|
|
119
109
|
|
|
120
110
|
// Extract zipped app files and place them in correct directory
|
|
121
111
|
const zippedApp = await downloadClonedProject(accountId, exportId);
|
|
122
|
-
await extractZipArchive(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
112
|
+
await extractZipArchive(
|
|
113
|
+
zippedApp,
|
|
114
|
+
sanitizeFileName(name),
|
|
115
|
+
absoluteDestPath,
|
|
116
|
+
{
|
|
117
|
+
includesRootDir: true,
|
|
118
|
+
hideLogs: true,
|
|
119
|
+
}
|
|
120
|
+
);
|
|
126
121
|
|
|
127
122
|
// Create hsproject.json file
|
|
128
123
|
const configPath = path.join(baseDestPath, PROJECT_CONFIG_FILE);
|
|
@@ -133,15 +128,12 @@ exports.handler = async options => {
|
|
|
133
128
|
};
|
|
134
129
|
const success = writeProjectConfig(configPath, configContent);
|
|
135
130
|
|
|
136
|
-
const isListed = Boolean(listingInfo);
|
|
137
131
|
trackCommandMetadataUsage(
|
|
138
132
|
'clone-app',
|
|
139
133
|
{
|
|
140
|
-
|
|
141
|
-
appId,
|
|
142
|
-
|
|
143
|
-
preventProjectMigrations,
|
|
144
|
-
isListed,
|
|
134
|
+
type: name,
|
|
135
|
+
assetType: appId,
|
|
136
|
+
successful: success,
|
|
145
137
|
},
|
|
146
138
|
accountId
|
|
147
139
|
);
|
|
@@ -170,7 +170,10 @@ exports.handler = async options => {
|
|
|
170
170
|
} else if (e.response && e.response.status === 400) {
|
|
171
171
|
logger.error(e.message);
|
|
172
172
|
} else {
|
|
173
|
-
logApiErrorInstance(
|
|
173
|
+
logApiErrorInstance(
|
|
174
|
+
e,
|
|
175
|
+
new ApiErrorContext({ accountId, request: 'project deploy' })
|
|
176
|
+
);
|
|
174
177
|
}
|
|
175
178
|
return process.exit(EXIT_CODES.ERROR);
|
|
176
179
|
}
|
package/commands/project/dev.js
CHANGED
|
@@ -46,12 +46,12 @@ const {
|
|
|
46
46
|
confirmDefaultAccountIsTarget,
|
|
47
47
|
suggestRecommendedNestedAccount,
|
|
48
48
|
checkIfAppDeveloperAccount,
|
|
49
|
-
checkIfDeveloperTestAccount,
|
|
50
49
|
createSandboxForLocalDev,
|
|
51
50
|
createDeveloperTestAccountForLocalDev,
|
|
52
51
|
createNewProjectForLocalDev,
|
|
53
52
|
createInitialBuildForNewProject,
|
|
54
53
|
useExistingDevTestAccount,
|
|
54
|
+
validateAccountOption,
|
|
55
55
|
} = require('../../lib/localDev');
|
|
56
56
|
|
|
57
57
|
const i18nKey = 'commands.project.subcommands.dev';
|
|
@@ -86,11 +86,20 @@ exports.handler = async options => {
|
|
|
86
86
|
validateProjectConfig(projectConfig, projectDir);
|
|
87
87
|
|
|
88
88
|
const components = await findProjectComponents(projectDir);
|
|
89
|
-
const
|
|
89
|
+
const runnableComponents = components.filter(component => component.runnable);
|
|
90
|
+
const componentTypes = getProjectComponentTypes(runnableComponents);
|
|
90
91
|
const hasPrivateApps = !!componentTypes[COMPONENT_TYPES.privateApp];
|
|
91
92
|
const hasPublicApps = !!componentTypes[COMPONENT_TYPES.publicApp];
|
|
92
93
|
|
|
93
|
-
if (
|
|
94
|
+
if (runnableComponents.length === 0) {
|
|
95
|
+
logger.error(
|
|
96
|
+
i18n(`${i18nKey}.errors.noRunnableComponents`, {
|
|
97
|
+
projectDir,
|
|
98
|
+
command: uiCommandReference('hs project add'),
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
102
|
+
} else if (hasPrivateApps && hasPublicApps) {
|
|
94
103
|
logger.error(i18n(`${i18nKey}.errors.invalidProjectComponents`));
|
|
95
104
|
process.exit(EXIT_CODES.SUCCESS);
|
|
96
105
|
}
|
|
@@ -106,14 +115,13 @@ exports.handler = async options => {
|
|
|
106
115
|
// The account that we are locally testing against
|
|
107
116
|
let targetTestingAccountId = options.account ? accountId : null;
|
|
108
117
|
|
|
109
|
-
if (options.account
|
|
110
|
-
|
|
111
|
-
targetProjectAccountId = accountConfig.parentAccountId;
|
|
112
|
-
targetTestingAccountId = accountId;
|
|
113
|
-
}
|
|
118
|
+
if (options.account) {
|
|
119
|
+
validateAccountOption(accountConfig, hasPublicApps);
|
|
114
120
|
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
if (hasPublicApps) {
|
|
122
|
+
targetProjectAccountId = accountConfig.parentAccountId;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
117
125
|
|
|
118
126
|
// The user is targeting an account type that we recommend developing on
|
|
119
127
|
if (!targetProjectAccountId && defaultAccountIsRecommendedType) {
|
|
@@ -143,6 +151,9 @@ exports.handler = async options => {
|
|
|
143
151
|
checkIfAppDeveloperAccount(accountConfig);
|
|
144
152
|
}
|
|
145
153
|
|
|
154
|
+
let createNewSandbox = false;
|
|
155
|
+
let createNewDeveloperTestAccount = false;
|
|
156
|
+
|
|
146
157
|
if (!targetProjectAccountId) {
|
|
147
158
|
const {
|
|
148
159
|
targetAccountId,
|
|
@@ -222,7 +233,7 @@ exports.handler = async options => {
|
|
|
222
233
|
}
|
|
223
234
|
|
|
224
235
|
const LocalDev = new LocalDevManager({
|
|
225
|
-
|
|
236
|
+
runnableComponents,
|
|
226
237
|
debug: options.debug,
|
|
227
238
|
deployedBuild,
|
|
228
239
|
isGithubLinked,
|
|
@@ -6,7 +6,7 @@ const {
|
|
|
6
6
|
addUseEnvironmentOptions,
|
|
7
7
|
} = require('../../lib/commonOpts');
|
|
8
8
|
const { trackCommandUsage } = require('../../lib/usageTracking');
|
|
9
|
-
const { getCwd } = require('@hubspot/local-dev-lib/path');
|
|
9
|
+
const { getCwd, sanitizeFileName } = require('@hubspot/local-dev-lib/path');
|
|
10
10
|
const {
|
|
11
11
|
logApiErrorInstance,
|
|
12
12
|
ApiErrorContext,
|
|
@@ -95,7 +95,7 @@ exports.handler = async options => {
|
|
|
95
95
|
|
|
96
96
|
await extractZipArchive(
|
|
97
97
|
zippedProject,
|
|
98
|
-
projectName,
|
|
98
|
+
sanitizeFileName(projectName),
|
|
99
99
|
path.resolve(absoluteDestPath),
|
|
100
100
|
{ includesRootDir: false }
|
|
101
101
|
);
|
|
@@ -108,7 +108,10 @@ exports.handler = async options => {
|
|
|
108
108
|
);
|
|
109
109
|
process.exit(EXIT_CODES.SUCCESS);
|
|
110
110
|
} catch (e) {
|
|
111
|
-
logApiErrorInstance(
|
|
111
|
+
logApiErrorInstance(
|
|
112
|
+
e,
|
|
113
|
+
new ApiErrorContext({ accountId, request: 'project download' })
|
|
114
|
+
);
|
|
112
115
|
process.exit(EXIT_CODES.ERROR);
|
|
113
116
|
}
|
|
114
117
|
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const {
|
|
2
|
+
installPackages,
|
|
3
|
+
getProjectPackageJsonLocations,
|
|
4
|
+
} = require('../../lib/dependencyManagement');
|
|
5
|
+
const { logger } = require('@hubspot/local-dev-lib/logger');
|
|
6
|
+
const { EXIT_CODES } = require('../../lib/enums/exitCodes');
|
|
7
|
+
const { getProjectConfig } = require('../../lib/projects');
|
|
8
|
+
const { promptUser } = require('../../lib/prompts/promptUtils');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { i18n } = require('../../lib/lang');
|
|
11
|
+
const { trackCommandUsage } = require('../../lib/usageTracking');
|
|
12
|
+
const { getAccountId } = require('../../lib/commonOpts');
|
|
13
|
+
|
|
14
|
+
const i18nKey = `commands.project.subcommands.installDeps`;
|
|
15
|
+
|
|
16
|
+
exports.command = 'install-deps [packages..]';
|
|
17
|
+
// Intentionally making this null to hide command
|
|
18
|
+
exports.describe = null;
|
|
19
|
+
// exports.describe = uiBetaTag(i18n(`${i18nKey}.help.describe`), false);
|
|
20
|
+
|
|
21
|
+
exports.handler = async ({ packages }) => {
|
|
22
|
+
try {
|
|
23
|
+
const accountId = getAccountId();
|
|
24
|
+
trackCommandUsage('project-install-deps', null, accountId);
|
|
25
|
+
|
|
26
|
+
const projectConfig = await getProjectConfig();
|
|
27
|
+
if (!projectConfig || !projectConfig.projectDir) {
|
|
28
|
+
logger.error(i18n(`${i18nKey}.noProjectConfig`));
|
|
29
|
+
return process.exit(EXIT_CODES.ERROR);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { projectDir } = projectConfig;
|
|
33
|
+
|
|
34
|
+
let installLocations = await getProjectPackageJsonLocations();
|
|
35
|
+
if (packages) {
|
|
36
|
+
const { selectedInstallLocations } = await promptUser([
|
|
37
|
+
{
|
|
38
|
+
name: 'selectedInstallLocations',
|
|
39
|
+
type: 'checkbox',
|
|
40
|
+
when: () => packages && packages.length > 0,
|
|
41
|
+
message: i18n(`${i18nKey}.installLocationPrompt`),
|
|
42
|
+
choices: installLocations.map(dir => ({
|
|
43
|
+
name: path.relative(projectDir, dir),
|
|
44
|
+
value: dir,
|
|
45
|
+
})),
|
|
46
|
+
validate: choices => {
|
|
47
|
+
if (choices === undefined || choices.length === 0) {
|
|
48
|
+
return i18n(`${i18nKey}.installLocationPromptRequired`);
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
if (selectedInstallLocations) {
|
|
55
|
+
installLocations = selectedInstallLocations;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await installPackages({
|
|
60
|
+
packages,
|
|
61
|
+
installLocations,
|
|
62
|
+
});
|
|
63
|
+
} catch (e) {
|
|
64
|
+
logger.debug(e);
|
|
65
|
+
logger.error(e.message);
|
|
66
|
+
return process.exit(EXIT_CODES.ERROR);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
exports.builder = yargs => {
|
|
71
|
+
yargs.example([
|
|
72
|
+
['$0 project install-deps', i18n(`${i18nKey}.help.installAppDepsExample`)],
|
|
73
|
+
[
|
|
74
|
+
'$0 project install-deps dependency1 dependency2',
|
|
75
|
+
i18n(`${i18nKey}.help.addDepToSubComponentExample`),
|
|
76
|
+
],
|
|
77
|
+
]);
|
|
78
|
+
};
|