@hubspot/cli 3.0.9 → 3.0.10-beta.11

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.
Files changed (76) hide show
  1. package/README.md +6 -0
  2. package/bin/cli.js +3 -2
  3. package/commands/accounts/list.js +18 -26
  4. package/commands/accounts/rename.js +13 -24
  5. package/commands/accounts.js +4 -1
  6. package/commands/app/deploy.js +22 -28
  7. package/commands/auth.js +30 -13
  8. package/commands/config/set/allowUsageTracking.js +15 -31
  9. package/commands/config/set/defaultAccount.js +22 -32
  10. package/commands/config/set/defaultMode.js +23 -42
  11. package/commands/config/set/httpTimeout.js +10 -28
  12. package/commands/config/set.js +4 -1
  13. package/commands/config.js +4 -1
  14. package/commands/create/api-sample.js +14 -12
  15. package/commands/create/module.js +19 -6
  16. package/commands/create/project.js +8 -1
  17. package/commands/create/template.js +19 -4
  18. package/commands/create.js +23 -8
  19. package/commands/customObject/create.js +22 -24
  20. package/commands/customObject/schema/create.js +30 -28
  21. package/commands/customObject/schema/delete.js +20 -20
  22. package/commands/customObject/schema/fetch-all.js +17 -24
  23. package/commands/customObject/schema/fetch.js +29 -24
  24. package/commands/customObject/schema/list.js +8 -17
  25. package/commands/customObject/schema/update.js +31 -29
  26. package/commands/customObject/schema.js +4 -1
  27. package/commands/customObject.js +10 -21
  28. package/commands/fetch.js +15 -30
  29. package/commands/filemanager/fetch.js +13 -25
  30. package/commands/filemanager/upload.js +47 -35
  31. package/commands/filemanager.js +4 -1
  32. package/commands/functions/deploy.js +34 -37
  33. package/commands/functions/list.js +9 -24
  34. package/commands/functions/server.js +13 -29
  35. package/commands/functions.js +4 -1
  36. package/commands/hubdb/clear.js +25 -21
  37. package/commands/hubdb/create.js +25 -22
  38. package/commands/hubdb/delete.js +19 -20
  39. package/commands/hubdb/fetch.js +15 -20
  40. package/commands/hubdb.js +4 -1
  41. package/commands/init.js +23 -11
  42. package/commands/lint.js +14 -23
  43. package/commands/list.js +19 -25
  44. package/commands/logs.js +41 -135
  45. package/commands/mv.js +21 -25
  46. package/commands/open.js +7 -5
  47. package/commands/project/create.js +111 -0
  48. package/commands/project/deploy.js +30 -34
  49. package/commands/project/listBuilds.js +160 -0
  50. package/commands/project/logs.js +192 -0
  51. package/commands/project/upload.js +108 -55
  52. package/commands/project.js +7 -8
  53. package/commands/remove.js +12 -20
  54. package/commands/sandbox/create.js +16 -11
  55. package/commands/secrets/addSecret.js +18 -21
  56. package/commands/secrets/deleteSecret.js +18 -21
  57. package/commands/secrets/listSecrets.js +10 -19
  58. package/commands/secrets/updateSecret.js +18 -21
  59. package/commands/secrets.js +4 -1
  60. package/commands/server.js +13 -5
  61. package/commands/{marketplaceValidate/validateTheme.js → theme/marketplace-validate.js} +26 -24
  62. package/commands/theme.js +5 -3
  63. package/commands/upload.js +66 -45
  64. package/commands/watch.js +33 -55
  65. package/lib/__tests__/serverlessLogs.js +8 -9
  66. package/lib/commonOpts.js +14 -11
  67. package/lib/enums/exitCodes.js +14 -0
  68. package/lib/projects.js +246 -235
  69. package/lib/prompts/projects.js +8 -5
  70. package/lib/prompts/sandboxes.js +5 -2
  71. package/lib/prompts.js +26 -27
  72. package/lib/serverlessLogs.js +11 -12
  73. package/lib/ui.js +48 -0
  74. package/lib/validation.js +2 -1
  75. package/package.json +9 -7
  76. package/commands/project/init.js +0 -108
package/lib/projects.js CHANGED
@@ -1,4 +1,4 @@
1
- const fs = require('fs');
1
+ const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
 
4
4
  const chalk = require('chalk');
@@ -7,35 +7,61 @@ const { prompt } = require('inquirer');
7
7
  const Spinnies = require('spinnies');
8
8
  const { logger } = require('@hubspot/cli-lib/logger');
9
9
  const { getEnv } = require('@hubspot/cli-lib/lib/config');
10
+ const {
11
+ createProject: createProjectTemplate,
12
+ } = require('@hubspot/cli-lib/projects');
10
13
  const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
11
14
  const {
12
15
  ENVIRONMENTS,
13
16
  POLLING_DELAY,
14
- PROJECT_BUILD_STATUS,
15
- PROJECT_BUILD_STATUS_TEXT,
16
- PROJECT_DEPLOY_STATUS,
17
- PROJECT_DEPLOY_STATUS_TEXT,
17
+ PROJECT_TEMPLATES,
18
+ PROJECT_TEXT,
19
+ PROJECT_CONFIG_FILE,
18
20
  } = require('@hubspot/cli-lib/lib/constants');
19
21
  const {
22
+ createProject,
20
23
  getBuildStatus,
21
24
  getDeployStatus,
22
25
  fetchProject,
23
- createProject,
24
26
  } = require('@hubspot/cli-lib/api/dfs');
25
27
  const {
26
28
  logApiErrorInstance,
27
29
  ApiErrorContext,
28
30
  } = require('@hubspot/cli-lib/errorHandlers');
29
-
30
- const isBuildComplete = build => {
31
- return (
32
- build.status === PROJECT_BUILD_STATUS.SUCCESS ||
33
- build.status === PROJECT_BUILD_STATUS.FAILURE
34
- );
31
+ const { getCwd } = require('@hubspot/cli-lib/path');
32
+ const { EXIT_CODES } = require('./enums/exitCodes');
33
+ const { uiLine, uiAccountDescription } = require('../lib/ui');
34
+
35
+ const PROJECT_STRINGS = {
36
+ BUILD: {
37
+ INITIALIZE: (name, numOfComponents) =>
38
+ `Building ${chalk.bold(name)}\n\nFound ${numOfComponents} component${
39
+ numOfComponents !== 1 ? 's' : ''
40
+ } in this project ...\n`,
41
+ SUCCESS: name => `Built ${chalk.bold(name)}`,
42
+ FAIL: name => `Failed to build ${chalk.bold(name)}`,
43
+ SUBTASK_FAIL: (taskId, name) =>
44
+ `Build #${taskId} failed because there was a problem\nbuilding ${chalk.bold(
45
+ name
46
+ )}`,
47
+ },
48
+ DEPLOY: {
49
+ INITIALIZE: (name, numOfComponents) =>
50
+ `Deploying ${chalk.bold(name)}\n\nFound ${numOfComponents} component${
51
+ numOfComponents !== 1 ? 's' : ''
52
+ } in this project ...\n`,
53
+ SUCCESS: name => `Deployed ${chalk.bold(name)}`,
54
+ FAIL: name => `Failed to deploy ${chalk.bold(name)}`,
55
+ SUBTASK_FAIL: (taskId, name) =>
56
+ `Deploy for build #${taskId} failed because there was a\nproblem deploying ${chalk.bold(
57
+ name
58
+ )}`,
59
+ },
35
60
  };
36
61
 
37
62
  const writeProjectConfig = (configPath, config) => {
38
63
  try {
64
+ fs.ensureFileSync(configPath);
39
65
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
40
66
  logger.debug(`Wrote project config at ${configPath}`);
41
67
  } catch (e) {
@@ -43,55 +69,83 @@ const writeProjectConfig = (configPath, config) => {
43
69
  }
44
70
  };
45
71
 
46
- const getProjectConfig = async projectPath => {
47
- const configPath = findup('hsproject.json', {
48
- cwd: projectPath,
72
+ const getProjectConfig = async _dir => {
73
+ const projectDir = _dir ? path.resolve(getCwd(), _dir) : getCwd();
74
+
75
+ const configPath = findup(PROJECT_CONFIG_FILE, {
76
+ cwd: projectDir,
49
77
  nocase: true,
50
78
  });
51
79
 
52
80
  if (!configPath) {
53
- return null;
81
+ return { projectConfig: null, projectDir: null };
54
82
  }
55
83
 
56
84
  try {
57
- const projectConfig = fs.readFileSync(configPath);
58
- return JSON.parse(projectConfig);
85
+ const config = fs.readFileSync(configPath);
86
+ const projectConfig = JSON.parse(config);
87
+ return {
88
+ projectDir: path.dirname(configPath),
89
+ projectConfig,
90
+ };
59
91
  } catch (e) {
60
92
  logger.error('Could not read from project config');
61
93
  }
62
94
  };
63
95
 
64
- const getOrCreateProjectConfig = async projectPath => {
65
- const projectConfig = await getProjectConfig(projectPath);
96
+ const createProjectConfig = async (projectPath, projectName, template) => {
97
+ const { projectConfig, projectDir } = await getProjectConfig(projectPath);
66
98
 
67
- if (!projectConfig) {
68
- const { name, srcDir } = await prompt([
69
- {
70
- name: 'name',
71
- message: 'Please enter a project name:',
72
- validate: input => {
73
- if (!input) {
74
- return 'A project name is required';
75
- }
76
- return true;
77
- },
78
- },
99
+ if (projectConfig) {
100
+ logger.warn(
101
+ projectPath === projectDir
102
+ ? 'A project already exists in that location.'
103
+ : `Found an existing project definition in ${projectDir}.`
104
+ );
105
+
106
+ const { shouldContinue } = await prompt([
79
107
  {
80
- name: 'srcDir',
81
- message: 'Which directory contains your project files?',
82
- validate: input => {
83
- if (!input) {
84
- return 'A source directory is required';
85
- }
86
- return true;
108
+ name: 'shouldContinue',
109
+ message: () => {
110
+ return projectPath === projectDir
111
+ ? 'Do you want to overwrite the existing project definition with a new one?'
112
+ : `Continue creating a new project in ${projectPath}?`;
87
113
  },
114
+ type: 'confirm',
115
+ default: false,
88
116
  },
89
117
  ]);
90
- writeProjectConfig(path.join(projectPath, 'hsproject.json'), {
91
- name,
92
- srcDir,
118
+
119
+ if (!shouldContinue) {
120
+ return;
121
+ }
122
+ }
123
+
124
+ const projectConfigPath = path.join(projectPath, PROJECT_CONFIG_FILE);
125
+
126
+ logger.log(
127
+ `Creating project in ${projectPath ? projectPath : 'the current folder'}`
128
+ );
129
+
130
+ if (template === 'none') {
131
+ fs.ensureDirSync(path.join(projectPath, 'src'));
132
+
133
+ writeProjectConfig(projectConfigPath, {
134
+ name: projectName,
135
+ srcDir: 'src',
136
+ });
137
+ } else {
138
+ await createProjectTemplate(
139
+ projectPath,
140
+ 'project',
141
+ PROJECT_TEMPLATES.find(t => t.name === template).repo,
142
+ ''
143
+ );
144
+ const _config = JSON.parse(fs.readFileSync(projectConfigPath));
145
+ writeProjectConfig(projectConfigPath, {
146
+ ..._config,
147
+ name: projectName,
93
148
  });
94
- return { name, srcDir };
95
149
  }
96
150
 
97
151
  return projectConfig;
@@ -100,38 +154,45 @@ const getOrCreateProjectConfig = async projectPath => {
100
154
  const validateProjectConfig = (projectConfig, projectDir) => {
101
155
  if (!projectConfig) {
102
156
  logger.error(
103
- `Project config not found. Try running 'hs project init' first.`
157
+ `Project config not found. Try running 'hs project create' first.`
104
158
  );
105
- process.exit(1);
159
+ process.exit(EXIT_CODES.ERROR);
106
160
  }
107
161
 
108
162
  if (!projectConfig.name || !projectConfig.srcDir) {
109
163
  logger.error(
110
- 'Project config is missing required fields. Try running `hs project init`.'
164
+ 'Project config is missing required fields. Try running `hs project create`.'
111
165
  );
112
- process.exit(1);
166
+ process.exit(EXIT_CODES.ERROR);
113
167
  }
114
168
 
115
169
  if (!fs.existsSync(path.resolve(projectDir, projectConfig.srcDir))) {
116
170
  logger.error(
117
- `Project source directory '${projectConfig.srcDir}' does not exist.`
171
+ `Project source directory '${projectConfig.srcDir}' could not be found in ${projectDir}.`
118
172
  );
119
- process.exit(1);
173
+ process.exit(EXIT_CODES.ERROR);
120
174
  }
121
175
  };
122
176
 
123
- const ensureProjectExists = async (accountId, projectName) => {
177
+ const ensureProjectExists = async (accountId, projectName, forceCreate) => {
124
178
  try {
125
179
  await fetchProject(accountId, projectName);
126
180
  } catch (err) {
127
181
  if (err.statusCode === 404) {
128
- const { shouldCreateProject } = await prompt([
129
- {
130
- name: 'shouldCreateProject',
131
- message: `The project ${projectName} does not exist in ${accountId}. Would you like to create it?`,
132
- type: 'confirm',
133
- },
134
- ]);
182
+ let shouldCreateProject = forceCreate;
183
+
184
+ if (!shouldCreateProject) {
185
+ const promptResult = await prompt([
186
+ {
187
+ name: 'shouldCreateProject',
188
+ message: `The project ${projectName} does not exist in ${uiAccountDescription(
189
+ accountId
190
+ )}. Would you like to create it?`,
191
+ type: 'confirm',
192
+ },
193
+ ]);
194
+ shouldCreateProject = promptResult.shouldCreateProject;
195
+ }
135
196
 
136
197
  if (shouldCreateProject) {
137
198
  try {
@@ -161,219 +222,169 @@ const getProjectDetailUrl = (projectName, accountId) => {
161
222
  return `${baseUrl}/developer-projects/${accountId}/project/${projectName}`;
162
223
  };
163
224
 
164
- const showWelcomeMessage = (projectName, accountId) => {
165
- const projectDetailUrl = getProjectDetailUrl(projectName, accountId);
166
-
225
+ const showWelcomeMessage = () => {
167
226
  logger.log('');
168
- logger.log(chalk.bold('> Welcome to HubSpot Developer Projects!'));
227
+ logger.log(chalk.bold('Welcome to HubSpot Developer Projects!'));
169
228
  logger.log(
170
229
  '\n-------------------------------------------------------------\n'
171
230
  );
172
- if (projectDetailUrl) {
173
- logger.log(chalk.italic(`View this project at: ${projectDetailUrl}`));
174
- }
175
- logger.log('');
176
- logger.log(chalk.bold('Getting Started'));
177
- logger.log('');
178
- logger.log('1. hs project upload');
231
+ logger.log(chalk.bold("What's next?\n"));
232
+ logger.log('🎨 Add components to your project with `hs create`.\n');
179
233
  logger.log(
180
- ' Upload your project files to HubSpot. Upload action adds your files to a build.'
234
+ `🏗 Run \`hs project upload\` to upload your files to HubSpot and trigger builds.\n`
181
235
  );
182
- logger.log();
183
- logger.log('2. View your changes on the preview build url');
184
- logger.log();
185
- logger.log('Use `hs project --help` to learn more about the command.');
186
236
  logger.log(
187
- '\n-------------------------------------------------------------\n'
237
+ `🚀 Ready to take your project live? Run \`hs project deploy\`.\n`
188
238
  );
239
+ logger.log(
240
+ `🔗 Use \`hs project --help\` to learn more about available commands.\n`
241
+ );
242
+ logger.log('-------------------------------------------------------------');
189
243
  };
190
244
 
191
- const pollBuildStatus = async (accountId, name, buildId) => {
192
- const buildStatus = await getBuildStatus(accountId, name, buildId);
193
- const spinnies = new Spinnies();
194
-
195
- logger.log();
196
- logger.log(`Building ${chalk.bold(name)}`);
197
- logger.log();
198
- logger.log(`Found ${buildStatus.subbuildStatuses.length} deployables ...`);
199
- logger.log();
200
-
201
- for (let subBuild of buildStatus.subbuildStatuses) {
202
- spinnies.add(subBuild.buildName, {
203
- text: `${chalk.bold(subBuild.buildName)} #${buildId} ${
204
- PROJECT_BUILD_STATUS_TEXT[PROJECT_BUILD_STATUS.ENQUEUED]
205
- }`,
206
- });
245
+ const makeGetTaskStatus = taskType => {
246
+ let statusFn, statusText, statusStrings;
247
+ switch (taskType) {
248
+ case 'build':
249
+ statusFn = getBuildStatus;
250
+ statusText = PROJECT_TEXT.BUILD;
251
+ statusStrings = PROJECT_STRINGS.BUILD;
252
+ break;
253
+ case 'deploy':
254
+ statusFn = getDeployStatus;
255
+ statusText = PROJECT_TEXT.DEPLOY;
256
+ statusStrings = PROJECT_STRINGS.DEPLOY;
257
+ break;
258
+ default:
259
+ logger.error(`Cannot get status for task type ${taskType}`);
207
260
  }
208
261
 
209
- return new Promise((resolve, reject) => {
210
- const pollInterval = setInterval(async () => {
211
- const buildStatus = await getBuildStatus(accountId, name, buildId).catch(
212
- reject
213
- );
214
- const { status, subbuildStatuses } = buildStatus;
215
-
216
- if (spinnies.hasActiveSpinners()) {
217
- subbuildStatuses.forEach(subBuild => {
218
- if (!spinnies.pick(subBuild.buildName)) {
219
- return;
220
- }
221
-
222
- const updatedText = `${chalk.bold(subBuild.buildName)} #${buildId} ${
223
- PROJECT_BUILD_STATUS_TEXT[subBuild.status]
224
- }`;
225
-
226
- switch (subBuild.status) {
227
- case PROJECT_BUILD_STATUS.SUCCESS:
228
- spinnies.succeed(subBuild.buildName, {
229
- text: updatedText,
230
- });
231
- break;
232
- case PROJECT_BUILD_STATUS.FAILURE:
233
- spinnies.fail(subBuild.buildName, {
234
- text: updatedText,
235
- });
236
- break;
237
- default:
238
- spinnies.update(subBuild.buildName, {
239
- text: updatedText,
240
- });
241
- break;
242
- }
243
- });
262
+ return async (accountId, taskName, taskId, buildId) => {
263
+ const isTaskComplete = task => {
264
+ if (task.status === statusText.STATES.FAILURE) {
265
+ return true;
266
+ } else if (task.status === statusText.STATES.SUCCESS) {
267
+ return task.isAutoDeployEnabled ? !!task.deployStatusTaskLocator : true;
244
268
  }
269
+ };
245
270
 
246
- if (isBuildComplete(buildStatus)) {
247
- clearInterval(pollInterval);
248
-
249
- if (status === PROJECT_BUILD_STATUS.SUCCESS) {
250
- logger.success(
251
- `Your project ${chalk.bold(name)} ${
252
- PROJECT_BUILD_STATUS_TEXT[status]
253
- }.`
254
- );
255
- } else if (status === PROJECT_BUILD_STATUS.FAILURE) {
256
- logger.error(
257
- `Your project ${chalk.bold(name)} ${
258
- PROJECT_BUILD_STATUS_TEXT[status]
259
- }.`
260
- );
261
- subbuildStatuses.forEach(subBuild => {
262
- if (subBuild.status === PROJECT_BUILD_STATUS.FAILURE) {
263
- logger.error(
264
- `${chalk.bold(subBuild.buildName)} failed to build. ${
265
- subBuild.errorMessage
266
- }.`
267
- );
268
- }
269
- });
270
- }
271
- resolve(buildStatus);
272
- }
273
- }, POLLING_DELAY);
274
- });
275
- };
271
+ const spinnies = new Spinnies({
272
+ succeedColor: 'white',
273
+ failColor: 'white',
274
+ });
276
275
 
277
- const pollDeployStatus = async (accountId, name, deployId, deployedBuildId) => {
278
- const deployStatus = await getDeployStatus(accountId, name, deployId);
279
- const spinnies = new Spinnies();
276
+ spinnies.add('overallTaskStatus', { text: 'Beginning' });
280
277
 
281
- logger.log();
282
- logger.log(`Deploying ${chalk.bold(name)}`);
283
- logger.log();
284
- logger.log(
285
- `Found ${deployStatus.subdeployStatuses.length} sub-build deploys ...`
286
- );
287
- logger.log();
278
+ const initialTaskStatus = await statusFn(accountId, taskName, taskId);
288
279
 
289
- for (let subdeploy of deployStatus.subdeployStatuses) {
290
- spinnies.add(subdeploy.deployName, {
291
- text: `${chalk.bold(subdeploy.deployName)} #${deployedBuildId} ${
292
- PROJECT_DEPLOY_STATUS_TEXT[PROJECT_DEPLOY_STATUS.ENQUEUED]
293
- }`,
280
+ spinnies.update('overallTaskStatus', {
281
+ text: statusStrings.INITIALIZE(
282
+ taskName,
283
+ initialTaskStatus[statusText.SUBTASK_KEY].length
284
+ ),
294
285
  });
295
- }
296
286
 
297
- return new Promise((resolve, reject) => {
298
- const pollInterval = setInterval(async () => {
299
- const deployStatus = await getDeployStatus(
300
- accountId,
301
- name,
302
- deployId
303
- ).catch(reject);
287
+ for (let subTask of initialTaskStatus[statusText.SUBTASK_KEY]) {
288
+ spinnies.add(subTask[statusText.SUBTASK_NAME_KEY], {
289
+ text: `${chalk.bold(subTask[statusText.SUBTASK_NAME_KEY])} #${buildId ||
290
+ taskId} ${statusText.STATUS_TEXT[statusText.STATES.ENQUEUED]}\n`,
291
+ });
292
+ }
293
+
294
+ return new Promise((resolve, reject) => {
295
+ const pollInterval = setInterval(async () => {
296
+ const taskStatus = await statusFn(accountId, taskName, taskId).catch(
297
+ reject
298
+ );
304
299
 
305
- const { status, subdeployStatuses } = deployStatus;
300
+ const { status, [statusText.SUBTASK_KEY]: subTaskStatus } = taskStatus;
306
301
 
307
- if (spinnies.hasActiveSpinners()) {
308
- subdeployStatuses.forEach(subdeploy => {
309
- if (!spinnies.pick(subdeploy.deployName)) {
310
- return;
311
- }
302
+ if (spinnies.hasActiveSpinners()) {
303
+ subTaskStatus.forEach(subTask => {
304
+ if (!spinnies.pick(subTask[statusText.SUBTASK_NAME_KEY])) {
305
+ return;
306
+ }
312
307
 
313
- const updatedText = `${chalk.bold(
314
- subdeploy.deployName
315
- )} #${deployedBuildId} ${
316
- PROJECT_DEPLOY_STATUS_TEXT[subdeploy.status]
317
- }`;
308
+ const updatedText = `${chalk.bold(
309
+ subTask[statusText.SUBTASK_NAME_KEY]
310
+ )} #${taskId} ${statusText.STATUS_TEXT[subTask.status]}\n`;
311
+
312
+ switch (subTask.status) {
313
+ case statusText.STATES.SUCCESS:
314
+ spinnies.succeed(subTask[statusText.SUBTASK_NAME_KEY], {
315
+ text: updatedText,
316
+ });
317
+ break;
318
+ case statusText.STATES.FAILURE:
319
+ spinnies.fail(subTask[statusText.SUBTASK_NAME_KEY], {
320
+ text: updatedText,
321
+ });
322
+ break;
323
+ default:
324
+ spinnies.update(subTask[statusText.SUBTASK_NAME_KEY], {
325
+ text: updatedText,
326
+ });
327
+ break;
328
+ }
329
+ });
318
330
 
319
- switch (subdeploy.status) {
320
- case PROJECT_DEPLOY_STATUS.SUCCESS:
321
- spinnies.succeed(subdeploy.deployName, {
322
- text: updatedText,
323
- });
324
- break;
325
- case PROJECT_DEPLOY_STATUS.FAILURE:
326
- spinnies.fail(subdeploy.deployName, {
327
- text: updatedText,
331
+ if (isTaskComplete(taskStatus)) {
332
+ subTaskStatus.forEach(subTask => {
333
+ spinnies.remove(subTask[statusText.SUBTASK_NAME_KEY]);
334
+ });
335
+
336
+ if (status === statusText.STATES.SUCCESS) {
337
+ spinnies.succeed('overallTaskStatus', {
338
+ text: statusStrings.SUCCESS(taskName),
328
339
  });
329
- break;
330
- default:
331
- spinnies.update(subdeploy.deployName, {
332
- text: updatedText,
340
+ } else if (status === statusText.STATES.FAILURE) {
341
+ spinnies.fail('overallTaskStatus', {
342
+ text: statusStrings.FAIL(taskName),
333
343
  });
334
- break;
335
- }
336
- });
337
- }
338
344
 
339
- if (isBuildComplete(deployStatus)) {
340
- clearInterval(pollInterval);
341
-
342
- if (status === PROJECT_DEPLOY_STATUS.SUCCESS) {
343
- logger.success(
344
- `Your project ${chalk.bold(name)} ${
345
- PROJECT_DEPLOY_STATUS_TEXT[status]
346
- }.`
347
- );
348
- } else if (status === PROJECT_DEPLOY_STATUS.FAILURE) {
349
- logger.error(
350
- `Your project ${chalk.bold(name)} ${
351
- PROJECT_DEPLOY_STATUS_TEXT[status]
352
- }.`
353
- );
354
- subdeployStatuses.forEach(subdeploy => {
355
- if (subdeploy.status === PROJECT_DEPLOY_STATUS.FAILURE) {
356
- logger.error(
357
- `${chalk.bold(subdeploy.deployName)} failed to build. ${
358
- subdeploy.errorMessage
359
- }.`
345
+ const failedSubtask = subTaskStatus.filter(
346
+ subtask => subtask.status === 'FAILURE'
360
347
  );
348
+
349
+ uiLine();
350
+ logger.log(
351
+ `${statusStrings.SUBTASK_FAIL(
352
+ buildId || taskId,
353
+ failedSubtask.length === 1
354
+ ? failedSubtask[0][statusText.SUBTASK_NAME_KEY]
355
+ : failedSubtask.length + ' components'
356
+ )}\n`
357
+ );
358
+ logger.log('See below for a summary of errors.');
359
+ uiLine();
360
+
361
+ failedSubtask.forEach(subTask => {
362
+ logger.log(
363
+ `\n--- ${chalk.bold(subTask[statusText.SUBTASK_NAME_KEY])} ${
364
+ statusText.STATUS_TEXT[subTask.status]
365
+ } with the following error ---`
366
+ );
367
+ logger.error(subTask.errorMessage);
368
+ });
361
369
  }
362
- });
370
+
371
+ clearInterval(pollInterval);
372
+ resolve(taskStatus);
373
+ }
363
374
  }
364
- resolve(deployStatus);
365
- }
366
- }, POLLING_DELAY);
367
- });
375
+ }, POLLING_DELAY);
376
+ });
377
+ };
368
378
  };
369
379
 
370
380
  module.exports = {
371
381
  writeProjectConfig,
372
382
  getProjectConfig,
373
- getOrCreateProjectConfig,
383
+ createProjectConfig,
374
384
  validateProjectConfig,
375
385
  showWelcomeMessage,
376
- pollBuildStatus,
377
- pollDeployStatus,
386
+ getProjectDetailUrl,
387
+ pollBuildStatus: makeGetTaskStatus('build'),
388
+ pollDeployStatus: makeGetTaskStatus('deploy'),
378
389
  ensureProjectExists,
379
390
  };
@@ -1,5 +1,8 @@
1
1
  const inquirer = require('inquirer');
2
2
  const { PROJECT_TEMPLATE_TYPES } = require('@hubspot/cli-lib/lib/constants');
3
+ const { i18n } = require('@hubspot/cli-lib/lib/lang');
4
+
5
+ const i18nKey = 'cli.lib.prompts.projects';
3
6
 
4
7
  const createProjectPrompt = (promptOptions = {}) => {
5
8
  const prompt = inquirer.createPromptModule();
@@ -7,16 +10,16 @@ const createProjectPrompt = (promptOptions = {}) => {
7
10
  {
8
11
  type: 'list',
9
12
  name: 'template',
10
- message: 'Select a project template to use',
13
+ message: i18n(`${i18nKey}.selectTemplate`),
11
14
  default: PROJECT_TEMPLATE_TYPES.blank,
12
15
  choices: Object.keys(PROJECT_TEMPLATE_TYPES),
13
16
  },
14
17
  {
15
18
  name: 'label',
16
- message: 'Enter a label for the project',
19
+ message: i18n(`${i18nKey}.enterLabel`),
17
20
  validate(val) {
18
21
  if (typeof val !== 'string') {
19
- return 'You entered an invalid label. Please try again.';
22
+ return i18n(`${i18nKey}.errors.invalidLabel`);
20
23
  }
21
24
  return true;
22
25
  },
@@ -24,10 +27,10 @@ const createProjectPrompt = (promptOptions = {}) => {
24
27
  },
25
28
  {
26
29
  name: 'description',
27
- message: 'Enter a description for the project',
30
+ message: i18n(`${i18nKey}.enterDescription`),
28
31
  validate(val) {
29
32
  if (typeof val !== 'string') {
30
- return 'You entered an invalid description. Please try again.';
33
+ return i18n(`${i18nKey}.errors.invalidDescription`);
31
34
  }
32
35
  return true;
33
36
  },
@@ -1,14 +1,17 @@
1
1
  const inquirer = require('inquirer');
2
+ const { i18n } = require('@hubspot/cli-lib/lib/lang');
3
+
4
+ const i18nKey = 'cli.lib.prompts.sandboxes';
2
5
 
3
6
  const createSandbox = () => {
4
7
  const prompt = inquirer.createPromptModule();
5
8
  return prompt([
6
9
  {
7
10
  name: 'name',
8
- message: 'Enter a name to use for the sandbox: ',
11
+ message: i18n(`${i18nKey}.enterName`),
9
12
  validate(val) {
10
13
  if (typeof val !== 'string') {
11
- return 'You entered an invalid name. Please try again.';
14
+ return i18n(`${i18nKey}.errors.invalidName`);
12
15
  }
13
16
  return true;
14
17
  },