@hubspot/cli 3.0.11 → 3.0.12-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ };
@@ -1,9 +1,4 @@
1
1
  const chalk = require('chalk');
2
- const fs = require('fs');
3
- const path = require('path');
4
- const archiver = require('archiver');
5
- const tmp = require('tmp');
6
- const Spinnies = require('spinnies');
7
2
  const {
8
3
  addAccountOptions,
9
4
  addConfigOptions,
@@ -11,84 +6,25 @@ const {
11
6
  addUseEnvironmentOptions,
12
7
  } = require('../../lib/commonOpts');
13
8
  const { trackCommandUsage } = require('../../lib/usageTracking');
14
- const {
15
- logApiErrorInstance,
16
- ApiErrorContext,
17
- } = require('@hubspot/cli-lib/errorHandlers');
18
9
  const { uiLine, uiAccountDescription } = require('../../lib/ui');
19
10
  const { logger } = require('@hubspot/cli-lib/logger');
20
- const { uploadProject } = require('@hubspot/cli-lib/api/dfs');
21
- const { shouldIgnoreFile } = require('@hubspot/cli-lib/ignoreRules');
22
11
  const { loadAndValidateOptions } = require('../../lib/validation');
23
12
  const {
13
+ ensureProjectExists,
24
14
  getProjectConfig,
25
- validateProjectConfig,
15
+ handleProjectUpload,
26
16
  pollBuildStatus,
27
- ensureProjectExists,
28
17
  pollDeployStatus,
18
+ validateProjectConfig,
29
19
  } = require('../../lib/projects');
30
20
  const { i18n } = require('@hubspot/cli-lib/lib/lang');
21
+ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
31
22
 
32
23
  const i18nKey = 'cli.commands.project.subcommands.upload';
33
- const { EXIT_CODES } = require('../../lib/enums/exitCodes');
34
24
 
35
25
  exports.command = 'upload [path]';
36
26
  exports.describe = i18n(`${i18nKey}.describe`);
37
27
 
38
- const uploadProjectFiles = async (accountId, projectName, filePath) => {
39
- const spinnies = new Spinnies({
40
- succeedColor: 'white',
41
- });
42
- const accountIdentifier = uiAccountDescription(accountId);
43
-
44
- spinnies.add('upload', {
45
- text: i18n(`${i18nKey}.loading.upload.add`, {
46
- accountIdentifier,
47
- projectName,
48
- }),
49
- });
50
-
51
- let buildId;
52
-
53
- try {
54
- const upload = await uploadProject(accountId, projectName, filePath);
55
-
56
- buildId = upload.buildId;
57
-
58
- spinnies.succeed('upload', {
59
- text: i18n(`${i18nKey}.loading.upload.succeed`, {
60
- accountIdentifier,
61
- projectName,
62
- }),
63
- });
64
-
65
- logger.debug(
66
- i18n(`${i18nKey}.debug.buildCreated`, {
67
- buildId,
68
- projectName,
69
- })
70
- );
71
- } catch (err) {
72
- spinnies.fail('upload', {
73
- text: i18n(`${i18nKey}.loading.upload.fail`, {
74
- accountIdentifier,
75
- projectName,
76
- }),
77
- });
78
-
79
- logApiErrorInstance(
80
- err,
81
- new ApiErrorContext({
82
- accountId,
83
- projectName,
84
- })
85
- );
86
- process.exit(EXIT_CODES.ERROR);
87
- }
88
-
89
- return { buildId };
90
- };
91
-
92
28
  exports.handler = async options => {
93
29
  await loadAndValidateOptions(options);
94
30
 
@@ -101,32 +37,10 @@ exports.handler = async options => {
101
37
 
102
38
  validateProjectConfig(projectConfig, projectDir);
103
39
 
104
- await ensureProjectExists(accountId, projectConfig.name, forceCreate);
105
-
106
- const tempFile = tmp.fileSync({ postfix: '.zip' });
107
-
108
- logger.debug(
109
- i18n(`${i18nKey}.debug.compressing`, {
110
- path: tempFile.name,
111
- })
112
- );
113
-
114
- const output = fs.createWriteStream(tempFile.name);
115
- const archive = archiver('zip');
40
+ await ensureProjectExists(accountId, projectConfig.name, { forceCreate });
116
41
 
117
- output.on('close', async function() {
42
+ const startPolling = async (tempFile, buildId) => {
118
43
  let exitCode = EXIT_CODES.SUCCESS;
119
- logger.debug(
120
- i18n(`${i18nKey}.debug.compressed`, {
121
- byteCount: archive.pointer(),
122
- })
123
- );
124
-
125
- const { buildId } = await uploadProjectFiles(
126
- accountId,
127
- projectConfig.name,
128
- tempFile.name
129
- );
130
44
 
131
45
  const {
132
46
  isAutoDeployEnabled,
@@ -134,6 +48,8 @@ exports.handler = async options => {
134
48
  status,
135
49
  } = await pollBuildStatus(accountId, projectConfig.name, buildId);
136
50
 
51
+ uiLine();
52
+
137
53
  if (status === 'FAILURE') {
138
54
  exitCode = EXIT_CODES.ERROR;
139
55
  return;
@@ -183,21 +99,9 @@ exports.handler = async options => {
183
99
  }
184
100
 
185
101
  process.exit(exitCode);
186
- });
187
-
188
- archive.on('error', function(err) {
189
- throw err;
190
- });
191
-
192
- archive.pipe(output);
193
-
194
- archive.directory(
195
- path.resolve(projectDir, projectConfig.srcDir),
196
- false,
197
- file => (shouldIgnoreFile(file.name) ? false : file)
198
- );
102
+ };
199
103
 
200
- archive.finalize();
104
+ await handleProjectUpload(accountId, projectConfig, projectDir, startPolling);
201
105
  };
202
106
 
203
107
  exports.builder = yargs => {
@@ -1,6 +1,5 @@
1
1
  const { i18n } = require('@hubspot/cli-lib/lib/lang');
2
2
  const { createWatcher } = require('@hubspot/cli-lib/projectsWatch');
3
- const { cancelStagedBuild } = require('@hubspot/cli-lib/api/dfs');
4
3
  const {
5
4
  logApiErrorInstance,
6
5
  ApiErrorContext,
@@ -14,13 +13,20 @@ const {
14
13
  } = require('../../lib/commonOpts');
15
14
  const { trackCommandUsage } = require('../../lib/usageTracking');
16
15
  const {
16
+ ensureProjectExists,
17
17
  getProjectConfig,
18
- validateProjectConfig,
18
+ handleProjectUpload,
19
19
  pollBuildStatus,
20
20
  pollDeployStatus,
21
+ validateProjectConfig,
21
22
  } = require('../../lib/projects');
23
+ const {
24
+ cancelStagedBuild,
25
+ fetchProjectBuilds,
26
+ } = require('@hubspot/cli-lib/api/dfs');
22
27
  const { loadAndValidateOptions } = require('../../lib/validation');
23
28
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
29
+ const { handleKeypress, handleExit } = require('@hubspot/cli-lib/lib/process');
24
30
 
25
31
  const i18nKey = 'cli.commands.project.subcommands.watch';
26
32
 
@@ -43,13 +49,13 @@ const handleBuildStatus = async (accountId, projectName, buildId) => {
43
49
  }
44
50
  };
45
51
 
46
- const handleSigInt = (accountId, projectName, currentBuildId) => {
47
- process.removeAllListeners('SIGINT');
48
- process.on('SIGINT', async () => {
52
+ const handleUserInput = (accountId, projectName, currentBuildId) => {
53
+ const onTerminate = async () => {
54
+ logger.log(i18n(`${i18nKey}.logs.processExited`));
55
+
49
56
  if (currentBuildId) {
50
57
  try {
51
58
  await cancelStagedBuild(accountId, projectName);
52
- logger.debug(i18n(`${i18nKey}.debug.buildCancelled`));
53
59
  process.exit(EXIT_CODES.SUCCESS);
54
60
  } catch (err) {
55
61
  logApiErrorInstance(
@@ -61,13 +67,20 @@ const handleSigInt = (accountId, projectName, currentBuildId) => {
61
67
  } else {
62
68
  process.exit(EXIT_CODES.SUCCESS);
63
69
  }
70
+ };
71
+
72
+ handleExit(onTerminate);
73
+ handleKeypress(key => {
74
+ if ((key.ctrl && key.name === 'c') || key.name === 'q') {
75
+ onTerminate();
76
+ }
64
77
  });
65
78
  };
66
79
 
67
80
  exports.handler = async options => {
68
81
  await loadAndValidateOptions(options);
69
82
 
70
- const { path: projectPath } = options;
83
+ const { initialUpload, path: projectPath } = options;
71
84
  const accountId = getAccountId(options);
72
85
 
73
86
  trackCommandUsage('project-watch', { projectPath }, accountId);
@@ -76,21 +89,50 @@ exports.handler = async options => {
76
89
 
77
90
  validateProjectConfig(projectConfig, projectDir);
78
91
 
79
- await createWatcher(
92
+ await ensureProjectExists(accountId, projectConfig.name);
93
+
94
+ const { results: builds } = await fetchProjectBuilds(
80
95
  accountId,
81
- projectConfig,
82
- projectDir,
83
- handleBuildStatus,
84
- handleSigInt
96
+ projectConfig.name,
97
+ options
85
98
  );
99
+ const hasNoBuilds = !builds || !builds.length;
100
+
101
+ const startWatching = async () => {
102
+ await createWatcher(
103
+ accountId,
104
+ projectConfig,
105
+ projectDir,
106
+ handleBuildStatus,
107
+ handleUserInput
108
+ );
109
+ };
110
+
111
+ // Upload all files if no build exists for this project yet
112
+ if (initialUpload || hasNoBuilds) {
113
+ await handleProjectUpload(
114
+ accountId,
115
+ projectConfig,
116
+ projectDir,
117
+ startWatching
118
+ );
119
+ } else {
120
+ await startWatching();
121
+ }
86
122
  };
87
123
 
88
124
  exports.builder = yargs => {
89
125
  yargs.positional('path', {
90
- describe: i18n(`${i18nKey}.describe`),
126
+ describe: i18n(`${i18nKey}.positionals.path.describe`),
91
127
  type: 'string',
92
128
  });
93
129
 
130
+ yargs.option('initial-upload', {
131
+ alias: 'i',
132
+ describe: i18n(`${i18nKey}.options.initialUpload.describe`),
133
+ type: 'boolean',
134
+ });
135
+
94
136
  yargs.example([
95
137
  ['$0 project watch myProjectFolder', i18n(`${i18nKey}.examples.default`)],
96
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
@@ -1,20 +1,20 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
-
3
+ const archiver = require('archiver');
4
+ const tmp = require('tmp');
4
5
  const chalk = require('chalk');
5
6
  const findup = require('findup-sync');
6
7
  const Spinnies = require('spinnies');
7
8
  const { logger } = require('@hubspot/cli-lib/logger');
8
9
  const { getEnv } = require('@hubspot/cli-lib/lib/config');
9
- const {
10
- createProject: createProjectTemplate,
11
- } = require('@hubspot/cli-lib/projects');
10
+ const { cloneGitHubRepo } = require('@hubspot/cli-lib/github');
12
11
  const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
13
12
  const {
14
13
  ENVIRONMENTS,
15
14
  POLLING_DELAY,
16
15
  PROJECT_TEMPLATES,
17
- PROJECT_TEXT,
16
+ PROJECT_BUILD_TEXT,
17
+ PROJECT_DEPLOY_TEXT,
18
18
  PROJECT_CONFIG_FILE,
19
19
  } = require('@hubspot/cli-lib/lib/constants');
20
20
  const {
@@ -22,42 +22,18 @@ const {
22
22
  getBuildStatus,
23
23
  getDeployStatus,
24
24
  fetchProject,
25
+ uploadProject,
25
26
  } = require('@hubspot/cli-lib/api/dfs');
26
27
  const {
27
28
  logApiErrorInstance,
28
29
  ApiErrorContext,
29
30
  } = require('@hubspot/cli-lib/errorHandlers');
31
+ const { shouldIgnoreFile } = require('@hubspot/cli-lib/ignoreRules');
30
32
  const { getCwd } = require('@hubspot/cli-lib/path');
31
33
  const { promptUser } = require('./prompts/promptUtils');
32
34
  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
- },
60
- };
35
+ const { uiLine, uiLink, uiAccountDescription } = require('../lib/ui');
36
+ const { i18n } = require('@hubspot/cli-lib/lib/lang');
61
37
 
62
38
  const writeProjectConfig = (configPath, config) => {
63
39
  try {
@@ -127,14 +103,16 @@ const createProjectConfig = async (projectPath, projectName, template) => {
127
103
  ]);
128
104
 
129
105
  if (!shouldContinue) {
130
- return;
106
+ return false;
131
107
  }
132
108
  }
133
109
 
134
110
  const projectConfigPath = path.join(projectPath, PROJECT_CONFIG_FILE);
135
111
 
136
112
  logger.log(
137
- `Creating project in ${projectPath ? projectPath : 'the current folder'}`
113
+ `Creating project config in ${
114
+ projectPath ? projectPath : 'the current folder'
115
+ }`
138
116
  );
139
117
 
140
118
  if (template === 'none') {
@@ -145,7 +123,7 @@ const createProjectConfig = async (projectPath, projectName, template) => {
145
123
  srcDir: 'src',
146
124
  });
147
125
  } else {
148
- await createProjectTemplate(
126
+ await cloneGitHubRepo(
149
127
  projectPath,
150
128
  'project',
151
129
  PROJECT_TEMPLATES.find(t => t.name === template).repo,
@@ -158,7 +136,7 @@ const createProjectConfig = async (projectPath, projectName, template) => {
158
136
  });
159
137
  }
160
138
 
161
- return projectConfig;
139
+ return true;
162
140
  };
163
141
 
164
142
  const validateProjectConfig = (projectConfig, projectDir) => {
@@ -184,14 +162,18 @@ const validateProjectConfig = (projectConfig, projectDir) => {
184
162
  }
185
163
  };
186
164
 
187
- const ensureProjectExists = async (accountId, projectName, forceCreate) => {
165
+ const ensureProjectExists = async (
166
+ accountId,
167
+ projectName,
168
+ { forceCreate = false, allowCreate = true } = {}
169
+ ) => {
188
170
  try {
189
171
  await fetchProject(accountId, projectName);
190
172
  } catch (err) {
191
173
  if (err.statusCode === 404) {
192
174
  let shouldCreateProject = forceCreate;
193
175
 
194
- if (!shouldCreateProject) {
176
+ if (allowCreate && !shouldCreateProject) {
195
177
  const promptResult = await promptUser([
196
178
  {
197
179
  name: 'shouldCreateProject',
@@ -232,12 +214,119 @@ const getProjectDetailUrl = (projectName, accountId) => {
232
214
  return `${baseUrl}/developer-projects/${accountId}/project/${projectName}`;
233
215
  };
234
216
 
235
- const showWelcomeMessage = () => {
217
+ const getProjectBuildDetailUrl = (projectName, buildId, accountId) => {
218
+ if (!projectName || !buildId || !accountId) return;
219
+ return `${getProjectDetailUrl(projectName, accountId)}/build/${buildId}`;
220
+ };
221
+
222
+ const uploadProjectFiles = async (accountId, projectName, filePath) => {
223
+ const i18nKey = 'cli.commands.project.subcommands.upload';
224
+ const spinnies = new Spinnies({
225
+ succeedColor: 'white',
226
+ });
227
+ const accountIdentifier = uiAccountDescription(accountId);
228
+
229
+ spinnies.add('upload', {
230
+ text: i18n(`${i18nKey}.loading.upload.add`, {
231
+ accountIdentifier,
232
+ projectName,
233
+ }),
234
+ });
235
+
236
+ let buildId;
237
+
238
+ try {
239
+ const upload = await uploadProject(accountId, projectName, filePath);
240
+
241
+ buildId = upload.buildId;
242
+
243
+ spinnies.succeed('upload', {
244
+ text: i18n(`${i18nKey}.loading.upload.succeed`, {
245
+ accountIdentifier,
246
+ projectName,
247
+ }),
248
+ });
249
+
250
+ logger.debug(
251
+ i18n(`${i18nKey}.debug.buildCreated`, {
252
+ buildId,
253
+ projectName,
254
+ })
255
+ );
256
+ } catch (err) {
257
+ spinnies.fail('upload', {
258
+ text: i18n(`${i18nKey}.loading.upload.fail`, {
259
+ accountIdentifier,
260
+ projectName,
261
+ }),
262
+ });
263
+
264
+ logApiErrorInstance(
265
+ err,
266
+ new ApiErrorContext({
267
+ accountId,
268
+ projectName,
269
+ })
270
+ );
271
+ process.exit(EXIT_CODES.ERROR);
272
+ }
273
+
274
+ return { buildId };
275
+ };
276
+
277
+ const handleProjectUpload = async (
278
+ accountId,
279
+ projectConfig,
280
+ projectDir,
281
+ callbackFunc
282
+ ) => {
283
+ const i18nKey = 'cli.commands.project.subcommands.upload';
284
+ const tempFile = tmp.fileSync({ postfix: '.zip' });
285
+
286
+ logger.debug(
287
+ i18n(`${i18nKey}.debug.compressing`, {
288
+ path: tempFile.name,
289
+ })
290
+ );
291
+
292
+ const output = fs.createWriteStream(tempFile.name);
293
+ const archive = archiver('zip');
294
+
295
+ output.on('close', async function() {
296
+ logger.debug(
297
+ i18n(`${i18nKey}.debug.compressed`, {
298
+ byteCount: archive.pointer(),
299
+ })
300
+ );
301
+
302
+ const { buildId } = await uploadProjectFiles(
303
+ accountId,
304
+ projectConfig.name,
305
+ tempFile.name
306
+ );
307
+
308
+ if (callbackFunc) {
309
+ callbackFunc(tempFile, buildId);
310
+ }
311
+ });
312
+
313
+ archive.pipe(output);
314
+
315
+ archive.directory(
316
+ path.resolve(projectDir, projectConfig.srcDir),
317
+ false,
318
+ file => (shouldIgnoreFile(file.name) ? false : file)
319
+ );
320
+
321
+ archive.finalize();
322
+ };
323
+
324
+ const showProjectWelcomeMessage = () => {
236
325
  logger.log('');
237
326
  logger.log(chalk.bold('Welcome to HubSpot Developer Projects!'));
238
- logger.log(
239
- '\n-------------------------------------------------------------\n'
240
- );
327
+ logger.log('\n');
328
+ uiLine();
329
+ logger.log('\n');
241
330
  logger.log(chalk.bold("What's next?\n"));
242
331
  logger.log('🎨 Add components to your project with `hs create`.\n');
243
332
  logger.log(
@@ -249,34 +338,31 @@ const showWelcomeMessage = () => {
249
338
  logger.log(
250
339
  `🔗 Use \`hs project --help\` to learn more about available commands.\n`
251
340
  );
252
- logger.log('-------------------------------------------------------------');
341
+ uiLine();
253
342
  };
254
343
 
255
- const makeGetTaskStatus = taskType => {
256
- let statusFn, statusText, statusStrings;
257
- switch (taskType) {
258
- case 'build':
259
- statusFn = getBuildStatus;
260
- statusText = PROJECT_TEXT.BUILD;
261
- statusStrings = PROJECT_STRINGS.BUILD;
262
- break;
263
- case 'deploy':
264
- statusFn = getDeployStatus;
265
- statusText = PROJECT_TEXT.DEPLOY;
266
- statusStrings = PROJECT_STRINGS.DEPLOY;
267
- break;
268
- default:
269
- logger.error(`Cannot get status for task type ${taskType}`);
270
- }
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
+ };
271
360
 
272
- return async (accountId, taskName, taskId, buildId) => {
273
- const isTaskComplete = task => {
274
- if (task.status === statusText.STATES.FAILURE) {
275
- return true;
276
- } else if (task.status === statusText.STATES.SUCCESS) {
277
- return task.isAutoDeployEnabled ? !!task.deployStatusTaskLocator : true;
278
- }
279
- };
361
+ return async (accountId, taskName, taskId) => {
362
+ let hubspotLinkText = '';
363
+ if (linkToHubSpot) {
364
+ logger.log(`\n${linkToHubSpot(taskName, taskId, accountId)}\n`);
365
+ }
280
366
 
281
367
  const spinnies = new Spinnies({
282
368
  succeedColor: 'white',
@@ -288,17 +374,23 @@ const makeGetTaskStatus = taskType => {
288
374
 
289
375
  const initialTaskStatus = await statusFn(accountId, taskName, taskId);
290
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
+
291
382
  spinnies.update('overallTaskStatus', {
292
- text: statusStrings.INITIALIZE(
293
- taskName,
294
- initialTaskStatus[statusText.SUBTASK_KEY].length
295
- ),
383
+ text: `${statusStrings.INITIALIZE(taskName)}${componentCountText}`,
296
384
  });
297
385
 
298
386
  for (let subTask of initialTaskStatus[statusText.SUBTASK_KEY]) {
299
- spinnies.add(subTask[statusText.SUBTASK_NAME_KEY], {
300
- text: `${chalk.bold(subTask[statusText.SUBTASK_NAME_KEY])} #${buildId ||
301
- 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,
302
394
  });
303
395
  }
304
396
 
@@ -312,29 +404,25 @@ const makeGetTaskStatus = taskType => {
312
404
 
313
405
  if (spinnies.hasActiveSpinners()) {
314
406
  subTaskStatus.forEach(subTask => {
315
- if (!spinnies.pick(subTask[statusText.SUBTASK_NAME_KEY])) {
407
+ const subTaskName = subTask[statusText.SUBTASK_NAME_KEY];
408
+
409
+ if (!spinnies.pick(subTaskName)) {
316
410
  return;
317
411
  }
318
412
 
319
- const updatedText = `${chalk.bold(
320
- subTask[statusText.SUBTASK_NAME_KEY]
321
- )} #${taskId} ${statusText.STATUS_TEXT[subTask.status]}\n`;
413
+ const updatedText = `${chalk.bold(subTaskName)} #${taskId} ${
414
+ statusText.STATUS_TEXT[subTask.status]
415
+ }\n`;
322
416
 
323
417
  switch (subTask.status) {
324
418
  case statusText.STATES.SUCCESS:
325
- spinnies.succeed(subTask[statusText.SUBTASK_NAME_KEY], {
326
- text: updatedText,
327
- });
419
+ spinnies.succeed(subTaskName, { text: updatedText });
328
420
  break;
329
421
  case statusText.STATES.FAILURE:
330
- spinnies.fail(subTask[statusText.SUBTASK_NAME_KEY], {
331
- text: updatedText,
332
- });
422
+ spinnies.fail(subTaskName, { text: updatedText });
333
423
  break;
334
424
  default:
335
- spinnies.update(subTask[statusText.SUBTASK_NAME_KEY], {
336
- text: updatedText,
337
- });
425
+ spinnies.update(subTaskName, { text: updatedText });
338
426
  break;
339
427
  }
340
428
  });
@@ -346,11 +434,11 @@ const makeGetTaskStatus = taskType => {
346
434
 
347
435
  if (status === statusText.STATES.SUCCESS) {
348
436
  spinnies.succeed('overallTaskStatus', {
349
- text: statusStrings.SUCCESS(taskName),
437
+ text: `${statusStrings.SUCCESS(taskName)}${hubspotLinkText}`,
350
438
  });
351
439
  } else if (status === statusText.STATES.FAILURE) {
352
440
  spinnies.fail('overallTaskStatus', {
353
- text: statusStrings.FAIL(taskName),
441
+ text: `${statusStrings.FAIL(taskName)}${hubspotLinkText}`,
354
442
  });
355
443
 
356
444
  const failedSubtask = subTaskStatus.filter(
@@ -360,7 +448,7 @@ const makeGetTaskStatus = taskType => {
360
448
  uiLine();
361
449
  logger.log(
362
450
  `${statusStrings.SUBTASK_FAIL(
363
- buildId || taskId,
451
+ taskId,
364
452
  failedSubtask.length === 1
365
453
  ? failedSubtask[0][statusText.SUBTASK_NAME_KEY]
366
454
  : failedSubtask.length + ' components'
@@ -388,15 +476,51 @@ const makeGetTaskStatus = taskType => {
388
476
  };
389
477
  };
390
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
+
391
513
  module.exports = {
392
514
  writeProjectConfig,
393
515
  getProjectConfig,
394
516
  getIsInProject,
517
+ handleProjectUpload,
395
518
  createProjectConfig,
396
519
  validateProjectConfig,
397
- showWelcomeMessage,
520
+ showProjectWelcomeMessage,
398
521
  getProjectDetailUrl,
399
- pollBuildStatus: makeGetTaskStatus('build'),
400
- pollDeployStatus: makeGetTaskStatus('deploy'),
522
+ getProjectBuildDetailUrl,
523
+ pollBuildStatus,
524
+ pollDeployStatus,
401
525
  ensureProjectExists,
402
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.11",
3
+ "version": "3.0.12-beta.2",
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.11",
12
- "@hubspot/serverless-dev-runtime": "^3.0.11",
11
+ "@hubspot/cli-lib": "^3.0.12-beta.2",
12
+ "@hubspot/serverless-dev-runtime": "^3.0.12-beta.2",
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": "fe651e504f86bddf71dab61ed7de83203af037e7"
42
+ "gitHead": "7ef17e48b31d6cc9bff3a0f7ab7857df4b82f14c"
43
43
  }