@hubspot/cli 3.0.12-beta.0 → 3.0.12

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.
@@ -10,7 +10,7 @@ const fs = require('fs-extra');
10
10
  const ora = require('ora');
11
11
  const { fetchJsonFromRepository } = require('@hubspot/cli-lib/github');
12
12
  const { GITHUB_RELEASE_TYPES } = require('@hubspot/cli-lib/lib/constants');
13
- const { createProject } = require('@hubspot/cli-lib/projects');
13
+ const { cloneGitHubRepo } = require('@hubspot/cli-lib/github');
14
14
  const { i18n } = require('@hubspot/cli-lib/lib/lang');
15
15
 
16
16
  const i18nKey = 'cli.commands.create.subcommands.apiSample';
@@ -60,7 +60,7 @@ module.exports = {
60
60
  sampleLanguage,
61
61
  })
62
62
  );
63
- const created = await createProject(
63
+ const created = await cloneGitHubRepo(
64
64
  filePath,
65
65
  assetType,
66
66
  sampleType,
@@ -1,8 +1,8 @@
1
- const { createProject } = require('@hubspot/cli-lib/projects');
1
+ const { cloneGitHubRepo } = require('@hubspot/cli-lib/github');
2
2
 
3
3
  module.exports = {
4
4
  hidden: true,
5
5
  dest: ({ name, assetType }) => name || assetType,
6
6
  execute: async ({ options, dest, assetType }) =>
7
- createProject(dest, assetType, 'crm-card-weather-app', '', options),
7
+ cloneGitHubRepo(dest, assetType, 'crm-card-weather-app', '', options),
8
8
  };
@@ -1,7 +1,7 @@
1
- const { createProject } = require('@hubspot/cli-lib/projects');
1
+ const { cloneGitHubRepo } = require('@hubspot/cli-lib/github');
2
2
 
3
3
  module.exports = {
4
4
  dest: ({ name, assetType }) => name || assetType,
5
5
  execute: async ({ options, dest, assetType }) =>
6
- createProject(dest, assetType, 'cms-react-boilerplate', '', options),
6
+ cloneGitHubRepo(dest, assetType, 'cms-react-boilerplate', '', options),
7
7
  };
@@ -1,7 +1,7 @@
1
- const { createProject } = require('@hubspot/cli-lib/projects');
1
+ const { cloneGitHubRepo } = require('@hubspot/cli-lib/github');
2
2
 
3
3
  module.exports = {
4
4
  dest: ({ name, assetType }) => name || assetType,
5
5
  execute: async ({ options, dest, assetType }) =>
6
- createProject(dest, assetType, 'cms-vue-boilerplate', '', options),
6
+ cloneGitHubRepo(dest, assetType, 'cms-vue-boilerplate', '', options),
7
7
  };
@@ -1,9 +1,9 @@
1
- const { createProject } = require('@hubspot/cli-lib/projects');
1
+ const { cloneGitHubRepo } = require('@hubspot/cli-lib/github');
2
2
 
3
3
  module.exports = {
4
4
  dest: ({ name, assetType }) => name || assetType,
5
5
  execute: async ({ options, dest, assetType }) =>
6
- createProject(
6
+ cloneGitHubRepo(
7
7
  dest,
8
8
  assetType,
9
9
  'cms-webpack-serverless-boilerplate',
@@ -1,4 +1,4 @@
1
- const { createProject } = require('@hubspot/cli-lib/projects');
1
+ const { cloneGitHubRepo } = require('@hubspot/cli-lib/github');
2
2
  const { GITHUB_RELEASE_TYPES } = require('@hubspot/cli-lib/lib/constants');
3
3
  const { getIsInProject } = require('../../lib/projects');
4
4
 
@@ -14,6 +14,6 @@ module.exports = {
14
14
  // releaseType has to be 'REPOSITORY' to download a specific branch
15
15
  options.releaseType = GITHUB_RELEASE_TYPES.REPOSITORY;
16
16
  }
17
- createProject(dest, assetType, 'cms-theme-boilerplate', 'src', options);
17
+ cloneGitHubRepo(dest, assetType, 'cms-theme-boilerplate', 'src', options);
18
18
  },
19
19
  };
@@ -11,7 +11,10 @@ const path = require('path');
11
11
  const {
12
12
  createProjectPrompt,
13
13
  } = require('../../lib/prompts/createProjectPrompt');
14
- const { createProjectConfig } = require('../../lib/projects');
14
+ const {
15
+ createProjectConfig,
16
+ showProjectWelcomeMessage,
17
+ } = require('../../lib/projects');
15
18
  const { i18n } = require('@hubspot/cli-lib/lib/lang');
16
19
 
17
20
  const i18nKey = 'cli.commands.project.subcommands.create';
@@ -33,6 +36,8 @@ exports.handler = async options => {
33
36
  options.name || name,
34
37
  options.template || template
35
38
  );
39
+
40
+ showProjectWelcomeMessage();
36
41
  };
37
42
 
38
43
  exports.builder = yargs => {
@@ -0,0 +1,127 @@
1
+ const path = require('path');
2
+
3
+ const {
4
+ getAccountId,
5
+ addUseEnvironmentOptions,
6
+ } = require('../../lib/commonOpts');
7
+ const { trackCommandUsage } = require('../../lib/usageTracking');
8
+ const { getCwd } = require('@hubspot/cli-lib/path');
9
+ const {
10
+ logApiErrorInstance,
11
+ ApiErrorContext,
12
+ } = require('@hubspot/cli-lib/errorHandlers');
13
+ const { logger } = require('@hubspot/cli-lib/logger');
14
+ const { extractZipArchive } = require('@hubspot/cli-lib/archive');
15
+ const {
16
+ downloadProject,
17
+ fetchProjectBuilds,
18
+ } = require('@hubspot/cli-lib/api/dfs');
19
+ const {
20
+ createProjectConfig,
21
+ ensureProjectExists,
22
+ } = require('../../lib/projects');
23
+ const { loadAndValidateOptions } = require('../../lib/validation');
24
+ const { i18n } = require('@hubspot/cli-lib/lib/lang');
25
+
26
+ const i18nKey = 'cli.commands.project.subcommands.download';
27
+ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
28
+
29
+ exports.command = 'download <name> [dest]';
30
+ exports.describe = i18n(`${i18nKey}.describe`);
31
+
32
+ exports.handler = async options => {
33
+ await loadAndValidateOptions(options);
34
+
35
+ const { name: projectName, dest, buildNumber } = options;
36
+ const accountId = getAccountId(options);
37
+
38
+ trackCommandUsage('project-download', { projectName }, accountId);
39
+
40
+ await ensureProjectExists(accountId, projectName, { allowCreate: false });
41
+
42
+ const absoluteDestPath = dest ? path.resolve(getCwd(), dest) : getCwd();
43
+
44
+ const projectConfigCreated = await createProjectConfig(
45
+ absoluteDestPath,
46
+ projectName,
47
+ 'none'
48
+ );
49
+
50
+ if (!projectConfigCreated) {
51
+ logger.log(i18n(`${i18nKey}.logs.downloadCancelled`));
52
+ process.exit(EXIT_CODES.SUCCESS);
53
+ }
54
+
55
+ let success = false;
56
+ let buildNumberToDownload = buildNumber;
57
+
58
+ if (!buildNumberToDownload) {
59
+ let projectBuildsResult;
60
+
61
+ try {
62
+ projectBuildsResult = await fetchProjectBuilds(accountId, projectName);
63
+ } catch (e) {
64
+ logApiErrorInstance(e, new ApiErrorContext({ accountId }));
65
+ process.exit(EXIT_CODES.ERROR);
66
+ }
67
+
68
+ const { results: projectBuilds } = projectBuildsResult;
69
+
70
+ if (projectBuilds && projectBuilds.length) {
71
+ const latestBuild = projectBuilds[0];
72
+ buildNumberToDownload = latestBuild.buildId;
73
+ }
74
+ }
75
+
76
+ const zippedProject = await downloadProject(
77
+ accountId,
78
+ projectName,
79
+ buildNumberToDownload
80
+ );
81
+
82
+ success = await extractZipArchive(
83
+ zippedProject,
84
+ projectName,
85
+ path.resolve(absoluteDestPath, 'src'),
86
+ {
87
+ includesRootDir: false,
88
+ }
89
+ );
90
+
91
+ if (!success) {
92
+ logger.log(i18n(`${i18nKey}.errors.downloadFailed`));
93
+ process.exit(EXIT_CODES.ERROR);
94
+ }
95
+
96
+ logger.log(
97
+ i18n(`${i18nKey}.logs.downloadSucceeded`, {
98
+ buildId: buildNumberToDownload,
99
+ projectName,
100
+ })
101
+ );
102
+ process.exit(EXIT_CODES.SUCCESS);
103
+ };
104
+
105
+ exports.builder = yargs => {
106
+ addUseEnvironmentOptions(yargs, true);
107
+
108
+ yargs.positional('name', {
109
+ describe: i18n(`${i18nKey}.positionals.name.describe`),
110
+ type: 'string',
111
+ });
112
+ yargs.positional('dest', {
113
+ describe: i18n(`${i18nKey}.positionals.dest.describe`),
114
+ type: 'string',
115
+ });
116
+ yargs.option('buildNumber', {
117
+ describe: i18n(`${i18nKey}.options.buildNumber.describe`),
118
+ type: 'number',
119
+ });
120
+ yargs.example([
121
+ [
122
+ '$0 project download myProject myProjectFolder',
123
+ i18n(`${i18nKey}.examples.default`),
124
+ ],
125
+ ]);
126
+ return yargs;
127
+ };
@@ -37,7 +37,7 @@ exports.handler = async options => {
37
37
 
38
38
  validateProjectConfig(projectConfig, projectDir);
39
39
 
40
- await ensureProjectExists(accountId, projectConfig.name, forceCreate);
40
+ await ensureProjectExists(accountId, projectConfig.name, { forceCreate });
41
41
 
42
42
  const startPolling = async (tempFile, buildId) => {
43
43
  let exitCode = EXIT_CODES.SUCCESS;
@@ -48,6 +48,8 @@ exports.handler = async options => {
48
48
  status,
49
49
  } = await pollBuildStatus(accountId, projectConfig.name, buildId);
50
50
 
51
+ uiLine();
52
+
51
53
  if (status === 'FAILURE') {
52
54
  exitCode = EXIT_CODES.ERROR;
53
55
  return;
@@ -26,6 +26,7 @@ const {
26
26
  } = require('@hubspot/cli-lib/api/dfs');
27
27
  const { loadAndValidateOptions } = require('../../lib/validation');
28
28
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
29
+ const { handleKeypress, handleExit } = require('@hubspot/cli-lib/lib/process');
29
30
 
30
31
  const i18nKey = 'cli.commands.project.subcommands.watch';
31
32
 
@@ -48,13 +49,13 @@ const handleBuildStatus = async (accountId, projectName, buildId) => {
48
49
  }
49
50
  };
50
51
 
51
- const handleSigInt = (accountId, projectName, currentBuildId) => {
52
- process.removeAllListeners('SIGINT');
53
- process.on('SIGINT', async () => {
52
+ const handleUserInput = (accountId, projectName, currentBuildId) => {
53
+ const onTerminate = async () => {
54
+ logger.log(i18n(`${i18nKey}.logs.processExited`));
55
+
54
56
  if (currentBuildId) {
55
57
  try {
56
58
  await cancelStagedBuild(accountId, projectName);
57
- logger.debug(i18n(`${i18nKey}.debug.buildCancelled`));
58
59
  process.exit(EXIT_CODES.SUCCESS);
59
60
  } catch (err) {
60
61
  logApiErrorInstance(
@@ -66,13 +67,20 @@ const handleSigInt = (accountId, projectName, currentBuildId) => {
66
67
  } else {
67
68
  process.exit(EXIT_CODES.SUCCESS);
68
69
  }
70
+ };
71
+
72
+ handleExit(onTerminate);
73
+ handleKeypress(key => {
74
+ if ((key.ctrl && key.name === 'c') || key.name === 'q') {
75
+ onTerminate();
76
+ }
69
77
  });
70
78
  };
71
79
 
72
80
  exports.handler = async options => {
73
81
  await loadAndValidateOptions(options);
74
82
 
75
- const { path: projectPath } = options;
83
+ const { initialUpload, path: projectPath } = options;
76
84
  const accountId = getAccountId(options);
77
85
 
78
86
  trackCommandUsage('project-watch', { projectPath }, accountId);
@@ -83,11 +91,12 @@ exports.handler = async options => {
83
91
 
84
92
  await ensureProjectExists(accountId, projectConfig.name);
85
93
 
86
- const { results } = await fetchProjectBuilds(
94
+ const { results: builds } = await fetchProjectBuilds(
87
95
  accountId,
88
96
  projectConfig.name,
89
97
  options
90
98
  );
99
+ const hasNoBuilds = !builds || !builds.length;
91
100
 
92
101
  const startWatching = async () => {
93
102
  await createWatcher(
@@ -95,12 +104,12 @@ exports.handler = async options => {
95
104
  projectConfig,
96
105
  projectDir,
97
106
  handleBuildStatus,
98
- handleSigInt
107
+ handleUserInput
99
108
  );
100
109
  };
101
110
 
102
111
  // Upload all files if no build exists for this project yet
103
- if (!results || !results.length) {
112
+ if (initialUpload || hasNoBuilds) {
104
113
  await handleProjectUpload(
105
114
  accountId,
106
115
  projectConfig,
@@ -114,10 +123,16 @@ exports.handler = async options => {
114
123
 
115
124
  exports.builder = yargs => {
116
125
  yargs.positional('path', {
117
- describe: i18n(`${i18nKey}.describe`),
126
+ describe: i18n(`${i18nKey}.positionals.path.describe`),
118
127
  type: 'string',
119
128
  });
120
129
 
130
+ yargs.option('initial-upload', {
131
+ alias: 'i',
132
+ describe: i18n(`${i18nKey}.options.initialUpload.describe`),
133
+ type: 'boolean',
134
+ });
135
+
121
136
  yargs.example([
122
137
  ['$0 project watch myProjectFolder', i18n(`${i18nKey}.examples.default`)],
123
138
  ]);
@@ -5,6 +5,7 @@ const upload = require('./project/upload');
5
5
  const listBuilds = require('./project/listBuilds');
6
6
  const logs = require('./project/logs');
7
7
  const watch = require('./project/watch');
8
+ const download = require('./project/download');
8
9
 
9
10
  exports.command = 'project';
10
11
  exports.describe = false; //'Commands for working with projects';
@@ -20,6 +21,7 @@ exports.builder = yargs => {
20
21
  yargs.command(watch).demandCommand(0, '');
21
22
  yargs.command(listBuilds).demandCommand(0, '');
22
23
  yargs.command(logs).demandCommand(1, '');
24
+ yargs.command(download).demandCommand(0, '');
23
25
 
24
26
  return yargs;
25
27
  };
package/lib/projects.js CHANGED
@@ -7,15 +7,14 @@ const findup = require('findup-sync');
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
+ const { cloneGitHubRepo } = require('@hubspot/cli-lib/github');
13
11
  const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
14
12
  const {
15
13
  ENVIRONMENTS,
16
14
  POLLING_DELAY,
17
15
  PROJECT_TEMPLATES,
18
- PROJECT_TEXT,
16
+ PROJECT_BUILD_TEXT,
17
+ PROJECT_DEPLOY_TEXT,
19
18
  PROJECT_CONFIG_FILE,
20
19
  } = require('@hubspot/cli-lib/lib/constants');
21
20
  const {
@@ -33,36 +32,9 @@ const { shouldIgnoreFile } = require('@hubspot/cli-lib/ignoreRules');
33
32
  const { getCwd } = require('@hubspot/cli-lib/path');
34
33
  const { promptUser } = require('./prompts/promptUtils');
35
34
  const { EXIT_CODES } = require('./enums/exitCodes');
36
- const { uiLine, uiAccountDescription } = require('../lib/ui');
35
+ const { uiLine, uiLink, uiAccountDescription } = require('../lib/ui');
37
36
  const { i18n } = require('@hubspot/cli-lib/lib/lang');
38
37
 
39
- const PROJECT_STRINGS = {
40
- BUILD: {
41
- INITIALIZE: (name, numOfComponents) =>
42
- `Building ${chalk.bold(name)}\n\nFound ${numOfComponents} component${
43
- numOfComponents !== 1 ? 's' : ''
44
- } in this project ...\n`,
45
- SUCCESS: name => `Built ${chalk.bold(name)}`,
46
- FAIL: name => `Failed to build ${chalk.bold(name)}`,
47
- SUBTASK_FAIL: (taskId, name) =>
48
- `Build #${taskId} failed because there was a problem\nbuilding ${chalk.bold(
49
- name
50
- )}`,
51
- },
52
- DEPLOY: {
53
- INITIALIZE: (name, numOfComponents) =>
54
- `Deploying ${chalk.bold(name)}\n\nFound ${numOfComponents} component${
55
- numOfComponents !== 1 ? 's' : ''
56
- } in this project ...\n`,
57
- SUCCESS: name => `Deployed ${chalk.bold(name)}`,
58
- FAIL: name => `Failed to deploy ${chalk.bold(name)}`,
59
- SUBTASK_FAIL: (taskId, name) =>
60
- `Deploy for build #${taskId} failed because there was a\nproblem deploying ${chalk.bold(
61
- name
62
- )}`,
63
- },
64
- };
65
-
66
38
  const writeProjectConfig = (configPath, config) => {
67
39
  try {
68
40
  fs.ensureFileSync(configPath);
@@ -131,14 +103,16 @@ const createProjectConfig = async (projectPath, projectName, template) => {
131
103
  ]);
132
104
 
133
105
  if (!shouldContinue) {
134
- return;
106
+ return false;
135
107
  }
136
108
  }
137
109
 
138
110
  const projectConfigPath = path.join(projectPath, PROJECT_CONFIG_FILE);
139
111
 
140
112
  logger.log(
141
- `Creating project in ${projectPath ? projectPath : 'the current folder'}`
113
+ `Creating project config in ${
114
+ projectPath ? projectPath : 'the current folder'
115
+ }`
142
116
  );
143
117
 
144
118
  if (template === 'none') {
@@ -149,7 +123,7 @@ const createProjectConfig = async (projectPath, projectName, template) => {
149
123
  srcDir: 'src',
150
124
  });
151
125
  } else {
152
- await createProjectTemplate(
126
+ await cloneGitHubRepo(
153
127
  projectPath,
154
128
  'project',
155
129
  PROJECT_TEMPLATES.find(t => t.name === template).repo,
@@ -162,7 +136,7 @@ const createProjectConfig = async (projectPath, projectName, template) => {
162
136
  });
163
137
  }
164
138
 
165
- return projectConfig;
139
+ return true;
166
140
  };
167
141
 
168
142
  const validateProjectConfig = (projectConfig, projectDir) => {
@@ -188,14 +162,18 @@ const validateProjectConfig = (projectConfig, projectDir) => {
188
162
  }
189
163
  };
190
164
 
191
- const ensureProjectExists = async (accountId, projectName, forceCreate) => {
165
+ const ensureProjectExists = async (
166
+ accountId,
167
+ projectName,
168
+ { forceCreate = false, allowCreate = true } = {}
169
+ ) => {
192
170
  try {
193
171
  await fetchProject(accountId, projectName);
194
172
  } catch (err) {
195
173
  if (err.statusCode === 404) {
196
174
  let shouldCreateProject = forceCreate;
197
175
 
198
- if (!shouldCreateProject) {
176
+ if (allowCreate && !shouldCreateProject) {
199
177
  const promptResult = await promptUser([
200
178
  {
201
179
  name: 'shouldCreateProject',
@@ -236,6 +214,11 @@ const getProjectDetailUrl = (projectName, accountId) => {
236
214
  return `${baseUrl}/developer-projects/${accountId}/project/${projectName}`;
237
215
  };
238
216
 
217
+ const getProjectBuildDetailUrl = (projectName, buildId, accountId) => {
218
+ if (!projectName || !buildId || !accountId) return;
219
+ return `${getProjectDetailUrl(projectName, accountId)}/build/${buildId}`;
220
+ };
221
+
239
222
  const uploadProjectFiles = async (accountId, projectName, filePath) => {
240
223
  const i18nKey = 'cli.commands.project.subcommands.upload';
241
224
  const spinnies = new Spinnies({
@@ -338,12 +321,12 @@ const handleProjectUpload = async (
338
321
  archive.finalize();
339
322
  };
340
323
 
341
- const showWelcomeMessage = () => {
324
+ const showProjectWelcomeMessage = () => {
342
325
  logger.log('');
343
326
  logger.log(chalk.bold('Welcome to HubSpot Developer Projects!'));
344
- logger.log(
345
- '\n-------------------------------------------------------------\n'
346
- );
327
+ logger.log('\n');
328
+ uiLine();
329
+ logger.log('\n');
347
330
  logger.log(chalk.bold("What's next?\n"));
348
331
  logger.log('🎨 Add components to your project with `hs create`.\n');
349
332
  logger.log(
@@ -355,34 +338,31 @@ const showWelcomeMessage = () => {
355
338
  logger.log(
356
339
  `🔗 Use \`hs project --help\` to learn more about available commands.\n`
357
340
  );
358
- logger.log('-------------------------------------------------------------');
341
+ uiLine();
359
342
  };
360
343
 
361
- const makeGetTaskStatus = taskType => {
362
- let statusFn, statusText, statusStrings;
363
- switch (taskType) {
364
- case 'build':
365
- statusFn = getBuildStatus;
366
- statusText = PROJECT_TEXT.BUILD;
367
- statusStrings = PROJECT_STRINGS.BUILD;
368
- break;
369
- case 'deploy':
370
- statusFn = getDeployStatus;
371
- statusText = PROJECT_TEXT.DEPLOY;
372
- statusStrings = PROJECT_STRINGS.DEPLOY;
373
- break;
374
- default:
375
- logger.error(`Cannot get status for task type ${taskType}`);
376
- }
344
+ const makePollTaskStatusFunc = ({
345
+ statusFn,
346
+ statusText,
347
+ statusStrings,
348
+ linkToHubSpot,
349
+ }) => {
350
+ const isTaskComplete = task => {
351
+ if (
352
+ !task[statusText.SUBTASK_KEY].length ||
353
+ task.status === statusText.STATES.FAILURE
354
+ ) {
355
+ return true;
356
+ } else if (task.status === statusText.STATES.SUCCESS) {
357
+ return task.isAutoDeployEnabled ? !!task.deployStatusTaskLocator : true;
358
+ }
359
+ };
377
360
 
378
- return async (accountId, taskName, taskId, buildId) => {
379
- const isTaskComplete = task => {
380
- if (task.status === statusText.STATES.FAILURE) {
381
- return true;
382
- } else if (task.status === statusText.STATES.SUCCESS) {
383
- return task.isAutoDeployEnabled ? !!task.deployStatusTaskLocator : true;
384
- }
385
- };
361
+ return async (accountId, taskName, taskId) => {
362
+ let hubspotLinkText = '';
363
+ if (linkToHubSpot) {
364
+ logger.log(`\n${linkToHubSpot(taskName, taskId, accountId)}\n`);
365
+ }
386
366
 
387
367
  const spinnies = new Spinnies({
388
368
  succeedColor: 'white',
@@ -394,17 +374,23 @@ const makeGetTaskStatus = taskType => {
394
374
 
395
375
  const initialTaskStatus = await statusFn(accountId, taskName, taskId);
396
376
 
377
+ const numOfComponents = initialTaskStatus[statusText.SUBTASK_KEY].length;
378
+ const componentCountText = `\nFound ${numOfComponents} component${
379
+ numOfComponents !== 1 ? 's' : ''
380
+ } in this project ...\n`;
381
+
397
382
  spinnies.update('overallTaskStatus', {
398
- text: statusStrings.INITIALIZE(
399
- taskName,
400
- initialTaskStatus[statusText.SUBTASK_KEY].length
401
- ),
383
+ text: `${statusStrings.INITIALIZE(taskName)}${componentCountText}`,
402
384
  });
403
385
 
404
386
  for (let subTask of initialTaskStatus[statusText.SUBTASK_KEY]) {
405
- spinnies.add(subTask[statusText.SUBTASK_NAME_KEY], {
406
- text: `${chalk.bold(subTask[statusText.SUBTASK_NAME_KEY])} #${buildId ||
407
- taskId} ${statusText.STATUS_TEXT[statusText.STATES.ENQUEUED]}\n`,
387
+ const subTaskName = subTask[statusText.SUBTASK_NAME_KEY];
388
+
389
+ spinnies.add(subTaskName, {
390
+ text: `${chalk.bold(subTaskName)} #${taskId} ${
391
+ statusText.STATUS_TEXT[statusText.STATES.ENQUEUED]
392
+ }\n`,
393
+ indent: 2,
408
394
  });
409
395
  }
410
396
 
@@ -418,29 +404,25 @@ const makeGetTaskStatus = taskType => {
418
404
 
419
405
  if (spinnies.hasActiveSpinners()) {
420
406
  subTaskStatus.forEach(subTask => {
421
- if (!spinnies.pick(subTask[statusText.SUBTASK_NAME_KEY])) {
407
+ const subTaskName = subTask[statusText.SUBTASK_NAME_KEY];
408
+
409
+ if (!spinnies.pick(subTaskName)) {
422
410
  return;
423
411
  }
424
412
 
425
- const updatedText = `${chalk.bold(
426
- subTask[statusText.SUBTASK_NAME_KEY]
427
- )} #${taskId} ${statusText.STATUS_TEXT[subTask.status]}\n`;
413
+ const updatedText = `${chalk.bold(subTaskName)} #${taskId} ${
414
+ statusText.STATUS_TEXT[subTask.status]
415
+ }\n`;
428
416
 
429
417
  switch (subTask.status) {
430
418
  case statusText.STATES.SUCCESS:
431
- spinnies.succeed(subTask[statusText.SUBTASK_NAME_KEY], {
432
- text: updatedText,
433
- });
419
+ spinnies.succeed(subTaskName, { text: updatedText });
434
420
  break;
435
421
  case statusText.STATES.FAILURE:
436
- spinnies.fail(subTask[statusText.SUBTASK_NAME_KEY], {
437
- text: updatedText,
438
- });
422
+ spinnies.fail(subTaskName, { text: updatedText });
439
423
  break;
440
424
  default:
441
- spinnies.update(subTask[statusText.SUBTASK_NAME_KEY], {
442
- text: updatedText,
443
- });
425
+ spinnies.update(subTaskName, { text: updatedText });
444
426
  break;
445
427
  }
446
428
  });
@@ -452,11 +434,11 @@ const makeGetTaskStatus = taskType => {
452
434
 
453
435
  if (status === statusText.STATES.SUCCESS) {
454
436
  spinnies.succeed('overallTaskStatus', {
455
- text: statusStrings.SUCCESS(taskName),
437
+ text: `${statusStrings.SUCCESS(taskName)}${hubspotLinkText}`,
456
438
  });
457
439
  } else if (status === statusText.STATES.FAILURE) {
458
440
  spinnies.fail('overallTaskStatus', {
459
- text: statusStrings.FAIL(taskName),
441
+ text: `${statusStrings.FAIL(taskName)}${hubspotLinkText}`,
460
442
  });
461
443
 
462
444
  const failedSubtask = subTaskStatus.filter(
@@ -466,7 +448,7 @@ const makeGetTaskStatus = taskType => {
466
448
  uiLine();
467
449
  logger.log(
468
450
  `${statusStrings.SUBTASK_FAIL(
469
- buildId || taskId,
451
+ taskId,
470
452
  failedSubtask.length === 1
471
453
  ? failedSubtask[0][statusText.SUBTASK_NAME_KEY]
472
454
  : failedSubtask.length + ' components'
@@ -494,6 +476,40 @@ const makeGetTaskStatus = taskType => {
494
476
  };
495
477
  };
496
478
 
479
+ const pollBuildStatus = makePollTaskStatusFunc({
480
+ linkToHubSpot: (projectName, buildId, accountId) =>
481
+ uiLink(
482
+ `View build #${buildId} in HubSpot`,
483
+ getProjectBuildDetailUrl(projectName, buildId, accountId),
484
+ { useColor: true }
485
+ ),
486
+ statusFn: getBuildStatus,
487
+ statusText: PROJECT_BUILD_TEXT,
488
+ statusStrings: {
489
+ INITIALIZE: name => `Building ${chalk.bold(name)}`,
490
+ SUCCESS: name => `Built ${chalk.bold(name)}`,
491
+ FAIL: name => `Failed to build ${chalk.bold(name)}`,
492
+ SUBTASK_FAIL: (taskId, name) =>
493
+ `Build #${taskId} failed because there was a problem\nbuilding ${chalk.bold(
494
+ name
495
+ )}`,
496
+ },
497
+ });
498
+
499
+ const pollDeployStatus = makePollTaskStatusFunc({
500
+ statusFn: getDeployStatus,
501
+ statusText: PROJECT_DEPLOY_TEXT,
502
+ statusStrings: {
503
+ INITIALIZE: name => `Deploying ${chalk.bold(name)}`,
504
+ SUCCESS: name => `Deployed ${chalk.bold(name)}`,
505
+ FAIL: name => `Failed to deploy ${chalk.bold(name)}`,
506
+ SUBTASK_FAIL: (taskId, name) =>
507
+ `Deploy for build #${taskId} failed because there was a\nproblem deploying ${chalk.bold(
508
+ name
509
+ )}`,
510
+ },
511
+ });
512
+
497
513
  module.exports = {
498
514
  writeProjectConfig,
499
515
  getProjectConfig,
@@ -501,9 +517,10 @@ module.exports = {
501
517
  handleProjectUpload,
502
518
  createProjectConfig,
503
519
  validateProjectConfig,
504
- showWelcomeMessage,
520
+ showProjectWelcomeMessage,
505
521
  getProjectDetailUrl,
506
- pollBuildStatus: makeGetTaskStatus('build'),
507
- pollDeployStatus: makeGetTaskStatus('deploy'),
522
+ getProjectBuildDetailUrl,
523
+ pollBuildStatus,
524
+ pollDeployStatus,
508
525
  ensureProjectExists,
509
526
  };
@@ -1,5 +1,4 @@
1
1
  const https = require('https');
2
- const readline = require('readline');
3
2
 
4
3
  const { logger } = require('@hubspot/cli-lib/logger');
5
4
  const { outputLogs } = require('@hubspot/cli-lib/lib/logs');
@@ -9,20 +8,12 @@ const {
9
8
  ApiErrorContext,
10
9
  } = require('@hubspot/cli-lib/errorHandlers');
11
10
  const { base64EncodeString } = require('@hubspot/cli-lib/lib/encoding');
11
+ const { handleKeypress } = require('@hubspot/cli-lib/lib/process');
12
+
12
13
  const { EXIT_CODES } = require('../lib/enums/exitCodes');
13
14
 
14
15
  const TAIL_DELAY = 5000;
15
16
 
16
- const handleKeypressToExit = exit => {
17
- readline.emitKeypressEvents(process.stdin);
18
- process.stdin.setRawMode(true);
19
- process.stdin.on('keypress', (str, key) => {
20
- if (key && ((key.ctrl && key.name == 'c') || key.name === 'escape')) {
21
- exit(key.name === 'escape' ? 'esc' : 'ctrl+c');
22
- }
23
- });
24
- };
25
-
26
17
  const tailLogs = async ({
27
18
  accountId,
28
19
  compact,
@@ -76,12 +67,13 @@ const tailLogs = async ({
76
67
  }, TAIL_DELAY);
77
68
  };
78
69
 
79
- handleKeypressToExit(exitKey => {
80
- spinnies.succeed('tailLogs', {
81
- text: `Stopped polling because "${exitKey}" was pressed.`,
82
- });
83
- process.exit(EXIT_CODES.SUCCESS);
70
+ handleKeypress(key => {
71
+ if ((key.ctrl && key.name == 'c') || key.name === 'escape') {
72
+ spinnies.succeed('tailLogs', { text: `Stopped polling` });
73
+ process.exit(EXIT_CODES.SUCCESS);
74
+ }
84
75
  });
76
+
85
77
  await tail(initialAfter);
86
78
  };
87
79
 
package/lib/ui.js CHANGED
@@ -22,7 +22,14 @@ const uiLine = () => {
22
22
  */
23
23
  const uiLink = (linkText, url, options = {}) => {
24
24
  if (supportsHyperlinks.stdout) {
25
- return ['\u001B]8;;', url, '\u0007', linkText, '\u001B]8;;\u0007'].join('');
25
+ const result = [
26
+ '\u001B]8;;',
27
+ url,
28
+ '\u0007',
29
+ linkText,
30
+ '\u001B]8;;\u0007',
31
+ ].join('');
32
+ return options.useColor ? chalk.cyan(result) : result;
26
33
  } else {
27
34
  return options.fallback ? `${linkText}: ${url}` : linkText;
28
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "3.0.12-beta.0",
3
+ "version": "3.0.12",
4
4
  "description": "CLI for working with HubSpot",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -8,8 +8,8 @@
8
8
  "url": "https://github.com/HubSpot/hubspot-cms-tools"
9
9
  },
10
10
  "dependencies": {
11
- "@hubspot/cli-lib": "^3.0.12-beta.0",
12
- "@hubspot/serverless-dev-runtime": "^3.0.12-beta.0",
11
+ "@hubspot/cli-lib": "^3.0.12",
12
+ "@hubspot/serverless-dev-runtime": "^3.0.12",
13
13
  "archiver": "^5.3.0",
14
14
  "chalk": "^4.1.2",
15
15
  "express": "^4.17.1",
@@ -39,5 +39,5 @@
39
39
  "publishConfig": {
40
40
  "access": "public"
41
41
  },
42
- "gitHead": "cb056da116cf2f64b7e2f7df69f44e5a19d03527"
42
+ "gitHead": "837c419fdb8dec12ad25c5c2965b9174409a0ee2"
43
43
  }