@hubspot/cli 5.0.1 → 5.0.2-beta.1

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.
@@ -40,7 +40,8 @@ module.exports = {
40
40
  downloadSpinner.start();
41
41
  const samplesConfig = await fetchJsonFromRepository(
42
42
  'HubSpot/sample-apps-list',
43
- 'main/samples.json'
43
+ 'samples.json',
44
+ 'main'
44
45
  );
45
46
  downloadSpinner.stop();
46
47
  if (!samplesConfig) {
@@ -63,7 +64,7 @@ module.exports = {
63
64
  const created = await cloneGitHubRepo(
64
65
  filePath,
65
66
  assetType,
66
- sampleType,
67
+ `HubSpot/${sampleType}`,
67
68
  sampleLanguage,
68
69
  {
69
70
  ...options,
@@ -4,5 +4,11 @@ module.exports = {
4
4
  hidden: true,
5
5
  dest: ({ name, assetType }) => name || assetType,
6
6
  execute: async ({ options, dest, assetType }) =>
7
- cloneGitHubRepo(dest, assetType, 'crm-card-weather-app', '', options),
7
+ cloneGitHubRepo(
8
+ dest,
9
+ assetType,
10
+ 'HubSpot/crm-card-weather-app',
11
+ '',
12
+ options
13
+ ),
8
14
  };
@@ -3,5 +3,11 @@ const { cloneGitHubRepo } = require('@hubspot/cli-lib/github');
3
3
  module.exports = {
4
4
  dest: ({ name, assetType }) => name || assetType,
5
5
  execute: async ({ options, dest, assetType }) =>
6
- cloneGitHubRepo(dest, assetType, 'cms-react-boilerplate', '', options),
6
+ cloneGitHubRepo(
7
+ dest,
8
+ assetType,
9
+ 'HubSpot/cms-react-boilerplate',
10
+ '',
11
+ options
12
+ ),
7
13
  };
@@ -3,5 +3,11 @@ const { cloneGitHubRepo } = require('@hubspot/cli-lib/github');
3
3
  module.exports = {
4
4
  dest: ({ name, assetType }) => name || assetType,
5
5
  execute: async ({ options, dest, assetType }) =>
6
- cloneGitHubRepo(dest, assetType, 'cms-vue-boilerplate', '', options),
6
+ cloneGitHubRepo(
7
+ dest,
8
+ assetType,
9
+ 'HubSpot/cms-vue-boilerplate',
10
+ '',
11
+ options
12
+ ),
7
13
  };
@@ -6,7 +6,7 @@ module.exports = {
6
6
  cloneGitHubRepo(
7
7
  dest,
8
8
  assetType,
9
- 'cms-webpack-serverless-boilerplate',
9
+ 'HubSpot/cms-webpack-serverless-boilerplate',
10
10
  '',
11
11
  options
12
12
  ),
@@ -14,6 +14,12 @@ 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
- cloneGitHubRepo(dest, assetType, 'cms-theme-boilerplate', 'src', options);
17
+ cloneGitHubRepo(
18
+ dest,
19
+ assetType,
20
+ 'HubSpot/cms-theme-boilerplate',
21
+ 'src',
22
+ options
23
+ );
18
24
  },
19
25
  };
@@ -4,7 +4,7 @@ const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
4
4
  const { loadAndValidateOptions } = require('../../../lib/validation');
5
5
  const { trackCommandUsage } = require('../../../lib/usageTracking');
6
6
  const { getAccountId } = require('../../../lib/commonOpts');
7
- const { listSchemas } = require('@hubspot/cli-lib/schema');
7
+ const { listSchemas } = require('../../../lib/schema');
8
8
  const { i18n } = require('../../../lib/lang');
9
9
 
10
10
  const i18nKey = 'cli.commands.customObject.subcommands.schema.subcommands.list';
@@ -4,7 +4,7 @@ const {
4
4
  logApiErrorInstance,
5
5
  ApiErrorContext,
6
6
  } = require('@hubspot/cli-lib/errorHandlers');
7
- const { getFunctionArrays } = require('@hubspot/cli-lib/lib/functions');
7
+ const { getFunctionArrays } = require('../../lib/getFunctionArrays');
8
8
  const {
9
9
  getTableContents,
10
10
  getTableHeader,
package/commands/init.js CHANGED
@@ -7,7 +7,7 @@ const {
7
7
  updateDefaultAccount,
8
8
  } = require('@hubspot/cli-lib/lib/config');
9
9
  const { addConfigOptions } = require('../lib/commonOpts');
10
- const { handleExit } = require('@hubspot/cli-lib/lib/process');
10
+ const { handleExit } = require('../lib/process');
11
11
  const { checkAndUpdateGitignore } = require('@hubspot/cli-lib/lib/git');
12
12
  const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
13
13
  const {
package/commands/lint.js CHANGED
@@ -1,7 +1,5 @@
1
- const {
2
- lint,
3
- printHublValidationResult,
4
- } = require('@hubspot/cli-lib/validate');
1
+ const { lint } = require('@hubspot/cli-lib/validate');
2
+ const { printHublValidationResult } = require('../lib/hublValidate');
5
3
  const { logger } = require('@hubspot/cli-lib/logger');
6
4
  const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
7
5
 
@@ -1,6 +1,7 @@
1
1
  const { logger } = require('@hubspot/cli-lib/logger');
2
2
  const { getAccountId } = require('@hubspot/cli-lib/lib/config');
3
3
  const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
4
+ const { fetchReleaseData } = require('@hubspot/cli-lib/github');
4
5
 
5
6
  const { trackCommandUsage } = require('../../lib/usageTracking');
6
7
  const { i18n } = require('../../lib/lang');
@@ -8,6 +9,9 @@ const { projectAddPrompt } = require('../../lib/prompts/projectAddPrompt');
8
9
  const { createProjectComponent } = require('../../lib/projects');
9
10
  const { loadAndValidateOptions } = require('../../lib/validation');
10
11
  const { uiBetaTag } = require('../../lib/ui');
12
+ const {
13
+ HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH,
14
+ } = require('../../lib/constants');
11
15
 
12
16
  const i18nKey = 'cli.commands.project.subcommands.add';
13
17
 
@@ -22,12 +26,25 @@ exports.handler = async options => {
22
26
  logger.log('');
23
27
  logger.log(i18n(`${i18nKey}.creatingComponent.message`));
24
28
  logger.log('');
25
- const { type, name } = await projectAddPrompt(options);
29
+
30
+ const releaseData = await fetchReleaseData(
31
+ HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH
32
+ );
33
+ const projectComponentsVersion = releaseData.tag_name;
34
+
35
+ const { type, name } = await projectAddPrompt(
36
+ projectComponentsVersion,
37
+ options
38
+ );
26
39
 
27
40
  trackCommandUsage('project-add', null, accountId);
28
41
 
29
42
  try {
30
- await createProjectComponent(options.type || type, options.name || name);
43
+ await createProjectComponent(
44
+ options.type || type,
45
+ options.name || name,
46
+ projectComponentsVersion
47
+ );
31
48
  logger.log('');
32
49
  logger.log(
33
50
  i18n(`${i18nKey}.success.message`, {
@@ -15,7 +15,11 @@ const {
15
15
  const { createProjectConfig } = require('../../lib/projects');
16
16
  const { i18n } = require('../../lib/lang');
17
17
  const { uiBetaTag, uiFeatureHighlight } = require('../../lib/ui');
18
+ const {
19
+ HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH,
20
+ } = require('../../lib/constants');
18
21
  const { logger } = require('@hubspot/cli-lib/logger');
22
+ const { fetchReleaseData } = require('@hubspot/cli-lib/github');
19
23
 
20
24
  const i18nKey = 'cli.commands.project.subcommands.create';
21
25
 
@@ -27,7 +31,21 @@ exports.handler = async options => {
27
31
 
28
32
  const accountId = getAccountId(options);
29
33
 
30
- const { name, template, location } = await createProjectPrompt(options);
34
+ const hasCustomTemplateSource = Boolean(options.templateSource);
35
+
36
+ let githubRef = '';
37
+
38
+ if (!hasCustomTemplateSource) {
39
+ const releaseData = await fetchReleaseData(
40
+ HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH
41
+ );
42
+ githubRef = releaseData.tag_name;
43
+ }
44
+
45
+ const { name, template, location } = await createProjectPrompt(
46
+ githubRef,
47
+ options
48
+ );
31
49
 
32
50
  trackCommandUsage(
33
51
  'project-create',
@@ -39,7 +57,8 @@ exports.handler = async options => {
39
57
  path.resolve(getCwd(), options.location || location),
40
58
  options.name || name,
41
59
  template || { path: options.template },
42
- options.templateSource
60
+ options.templateSource,
61
+ githubRef
43
62
  );
44
63
 
45
64
  logger.log('');
@@ -10,11 +10,11 @@ const {
10
10
  trackCommandMetadataUsage,
11
11
  } = require('../../lib/usageTracking');
12
12
  const { loadAndValidateOptions } = require('../../lib/validation');
13
+ const { handleExit } = require('../../lib/process');
13
14
  const { i18n } = require('../../lib/lang');
14
15
  const { logger } = require('@hubspot/cli-lib/logger');
15
16
  const { getConfigAccounts } = require('@hubspot/cli-lib/lib/config');
16
17
  const { createProject, fetchProject } = require('@hubspot/cli-lib/api/dfs');
17
- const { handleExit } = require('@hubspot/cli-lib/lib/process');
18
18
  const {
19
19
  getProjectConfig,
20
20
  ensureProjectExists,
@@ -1,5 +1,5 @@
1
1
  const { i18n } = require('../../lib/lang');
2
- const { createWatcher } = require('@hubspot/cli-lib/projectsWatch');
2
+ const { createWatcher } = require('../../lib/projectsWatch');
3
3
  const {
4
4
  logApiErrorInstance,
5
5
  ApiErrorContext,
@@ -30,7 +30,7 @@ const {
30
30
  } = require('@hubspot/cli-lib/api/dfs');
31
31
  const { loadAndValidateOptions } = require('../../lib/validation');
32
32
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
33
- const { handleKeypress, handleExit } = require('@hubspot/cli-lib/lib/process');
33
+ const { handleKeypress, handleExit } = require('../../lib/process');
34
34
 
35
35
  const i18nKey = 'cli.commands.project.subcommands.watch';
36
36
 
package/lang/en.lyaml CHANGED
@@ -587,12 +587,31 @@ en:
587
587
  default: "Watch a project within the myProjectFolder folder"
588
588
  logs:
589
589
  processExited: "Stopping watcher..."
590
+ watchCancelledFromUi: "The watch process has been cancelled from the UI. Any changes made since cancelling have not been uploaded. To resume watching, rerun {{#yellow}}`hs project watch`{{/yellow}}."
591
+ resuming: "Resuming watcher..."
592
+ uploadSucceeded: "Uploaded file \"{{ filePath }}\" to \"{{ remotePath }}\""
593
+ deleteFileSucceeded: "Deleted file \"{{ remotePath }}\""
594
+ deleteFolderSucceeded: "Deleted folder \"{{ remotePath }}\""
595
+ watching: "Watcher is ready and watching \"{{ projectDir }}\". Any changes detected will be automatically uploaded."
596
+ previousStagingBuildCancelled: "Killed the previous watch process. Please try running `hs project watch` again"
590
597
  positionals:
591
598
  path:
592
599
  describe: "Path to a project folder"
593
600
  options:
594
601
  initialUpload:
595
602
  describe: "Upload directory before watching for updates"
603
+ debug:
604
+ pause: "Pausing watcher, attempting to queue build"
605
+ buildStarted: "Build queued."
606
+ extensionNotAllowed: "Skipping \"{{ filePath }}\" due to unsupported extension"
607
+ ignored: "Skipping \"{{ filePath }}\" due to an ignore rule"
608
+ uploading: "Attempting to upload file \"{{ filePath }}\" to \"{{ remotePath }}\""
609
+ attemptNewBuild: "Attempting to create a new build"
610
+ fileAlreadyQueued: "File \"{{ filePath }}\" is already queued for upload"
611
+ errors:
612
+ uploadFailed: "Failed to upload file \"{{ filePath }}\" to \"{{ remotePath }}\""
613
+ deleteFileFailed: "Failed to delete file \"{{ remotePath }}\""
614
+ deleteFolderFailed: "Failed to delete folder \"{{ remotePath }}\""
596
615
  download:
597
616
  describe: "Download your project files from HubSpot and write to a path on your computer"
598
617
  examples:
@@ -874,7 +893,7 @@ en:
874
893
  failedToInitialize: "Missing required arguments to initialize Local Dev"
875
894
  noDeployedBuild: "There is no deployed build for this project in {{ accountIdentifier }}. Run {{ uploadCommand }} to upload and deploy your project."
876
895
  noComponents: "There are no components in this project."
877
- noRunnableComponents: "There are no components in this project that support local development."
896
+ noRunnableComponents: "No supported components were found under {{#bold}}{{ projectSourceDir }}{{/bold}}. Run {{ command }} to see a list of available components."
878
897
  betaMessage: "HubSpot projects local development"
879
898
  running: "Running {{#bold}}{{ projectName }}{{/bold}} locally on {{ accountIdentifier }}, waiting for changes ..."
880
899
  quitHelper: "Press {{#bold}}'q'{{/bold}} to stop the local dev server"
@@ -885,7 +904,7 @@ en:
885
904
  uploadWarning:
886
905
  appLabel: "[App]"
887
906
  uiExtensionLabel: "[UI Extension]"
888
- missingComponents: "The deployed build for this project does not contain {{#bold}}'{{ missingComponents }}'{{/bold}}. This may cause issues in local development."
907
+ missingComponents: "Couldn't find the following components in the deployed build for this project: {{#bold}}'{{ missingComponents }}'{{/bold}}. This may cause issues in local development."
889
908
  defaultWarning: "Changing project configuration requires a new project build."
890
909
  header: "{{ warning }} To reflect these changes and continue testing:"
891
910
  stopDev: " * Stop {{ command }}"
@@ -907,6 +926,7 @@ en:
907
926
  emptySource: "Source directory \"{{ srcDir }}\" is empty. Add files to your project and rerun `{{#yellow}}hs project upload{{/yellow}}` to upload them to HubSpot."
908
927
  compressed: "Project files compressed: {{ byteCount }} bytes"
909
928
  compressing: "Compressing build files to \"{{ path }}\""
929
+ fileFiltered: "Ignore rule triggered for \"{{ filename }}\""
910
930
  ensureProjectExists:
911
931
  createPrompt: "The project {{ projectName }} does not exist in {{ accountIdentifier }}. Would you like to create it?"
912
932
  createSuccess: "New project {{#bold}}{{ projectName }}{{/bold}} successfully created in {{#bold}}{{ accountIdentifier }}{{/bold}}."
@@ -2,8 +2,8 @@ const path = require('path');
2
2
  const chokidar = require('chokidar');
3
3
  const chalk = require('chalk');
4
4
  const { i18n } = require('./lang');
5
+ const { handleKeypress } = require('./process');
5
6
  const { logger } = require('@hubspot/cli-lib/logger');
6
- const { handleKeypress } = require('@hubspot/cli-lib/lib/process');
7
7
  const {
8
8
  getAccountId,
9
9
  getConfigDefaultAccount,
@@ -89,7 +89,12 @@ class LocalDevManager {
89
89
 
90
90
  // The project does not contain any components that support local development
91
91
  if (!runnableComponents.length) {
92
- logger.error(i18n(`${i18nKey}.noRunnableComponents`));
92
+ logger.error(
93
+ i18n(`${i18nKey}.noRunnableComponents`, {
94
+ projectSourceDir: this.projectSourceDir,
95
+ command: uiCommandReference('hs project add'),
96
+ })
97
+ );
93
98
  process.exit(EXIT_CODES.SUCCESS);
94
99
  }
95
100
 
@@ -0,0 +1,8 @@
1
+ const HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH =
2
+ 'HubSpot/hubspot-project-components';
3
+ const DEFAULT_PROJECT_TEMPLATE_BRANCH = 'main';
4
+
5
+ module.exports = {
6
+ HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH,
7
+ DEFAULT_PROJECT_TEMPLATE_BRANCH,
8
+ };
@@ -0,0 +1,18 @@
1
+ const moment = require('moment');
2
+
3
+ const getFunctionArrays = resp => {
4
+ return resp.objects.map(func => {
5
+ const { route, method, created, updated, secretNames } = func;
6
+ return [
7
+ route,
8
+ method,
9
+ secretNames.join(', '),
10
+ moment(created).format(),
11
+ moment(updated).format(),
12
+ ];
13
+ });
14
+ };
15
+
16
+ module.exports = {
17
+ getFunctionArrays,
18
+ };
@@ -0,0 +1,32 @@
1
+ const { logger } = require('@hubspot/cli-lib/logger');
2
+
3
+ const getErrorsFromHublValidationObject = validation =>
4
+ (validation && validation.meta && validation.meta.template_errors) || [];
5
+
6
+ function printHublValidationError(err) {
7
+ const { severity, message, lineno, startPosition } = err;
8
+ const method = severity === 'FATAL' ? 'error' : 'warn';
9
+ logger[method]('[%d, %d]: %s', lineno, startPosition, message);
10
+ }
11
+
12
+ function printHublValidationResult({ file, validation }) {
13
+ let count = 0;
14
+ const errors = getErrorsFromHublValidationObject(validation);
15
+ if (!errors.length) {
16
+ return count;
17
+ }
18
+ logger.group(file);
19
+ errors.forEach(err => {
20
+ if (err.reason !== 'SYNTAX_ERROR') {
21
+ return;
22
+ }
23
+ ++count;
24
+ printHublValidationError(err);
25
+ });
26
+ logger.groupEnd(file);
27
+ return count;
28
+ }
29
+
30
+ module.exports = {
31
+ printHublValidationResult,
32
+ };
@@ -19,8 +19,11 @@ const isHelperIdentifier = identifier => {
19
19
 
20
20
  const generateReplaceFn = (matchedText, startIndex, replacementString) => {
21
21
  return currentStringValue =>
22
- `${currentStringValue.slice(0, startIndex)}${replacementString ||
23
- ''}${currentStringValue.slice(startIndex + matchedText.length)}`;
22
+ `${currentStringValue.slice(0, startIndex)}${
23
+ replacementString !== null && replacementString !== undefined
24
+ ? replacementString
25
+ : ''
26
+ }${currentStringValue.slice(startIndex + matchedText.length)}`;
24
27
  };
25
28
 
26
29
  /**
package/lib/oauth.js CHANGED
@@ -3,7 +3,7 @@ const open = require('open');
3
3
  const OAuth2Manager = require('@hubspot/cli-lib/lib/models/OAuth2Manager');
4
4
  const { getAccountConfig } = require('@hubspot/cli-lib/lib/config');
5
5
  const { addOauthToAccountConfig } = require('@hubspot/cli-lib/oauth');
6
- const { handleExit } = require('@hubspot/cli-lib/lib/process');
6
+ const { handleExit } = require('./process');
7
7
  const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
8
8
  const { logger } = require('@hubspot/cli-lib/logger');
9
9
  const { ENVIRONMENTS } = require('@hubspot/cli-lib/lib/constants');
package/lib/process.js ADDED
@@ -0,0 +1,42 @@
1
+ const readline = require('readline');
2
+
3
+ const handleExit = callback => {
4
+ const terminationSignals = [
5
+ 'beforeExit',
6
+ 'SIGINT',
7
+ 'SIGUSR1',
8
+ 'SIGUSR2',
9
+ 'uncaughtException',
10
+ 'SIGTERM',
11
+ 'SIGHUP',
12
+ ];
13
+ terminationSignals.forEach(signal => {
14
+ process.removeAllListeners(signal);
15
+
16
+ process.on(signal, async () => {
17
+ await callback();
18
+ });
19
+ });
20
+ };
21
+
22
+ const handleKeypress = callback => {
23
+ readline.createInterface(process.stdin, process.stdout);
24
+ readline.emitKeypressEvents(process.stdin);
25
+
26
+ if (process.stdin.isTTY) {
27
+ process.stdin.setRawMode(true);
28
+ }
29
+
30
+ process.stdin.removeAllListeners('keypress');
31
+
32
+ process.stdin.on('keypress', (str, key) => {
33
+ if (key) {
34
+ callback(key);
35
+ }
36
+ });
37
+ };
38
+
39
+ module.exports = {
40
+ handleExit,
41
+ handleKeypress,
42
+ };
package/lib/projects.js CHANGED
@@ -45,6 +45,7 @@ const {
45
45
  isSpecifiedError,
46
46
  isSpecifiedHubSpotAuthError,
47
47
  } = require('@hubspot/cli-lib/errorHandlers/apiErrors');
48
+ const { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH } = require('./constants');
48
49
 
49
50
  const i18nKey = 'cli.lib.projects';
50
51
 
@@ -96,7 +97,8 @@ const createProjectConfig = async (
96
97
  projectPath,
97
98
  projectName,
98
99
  template,
99
- templateSource
100
+ templateSource,
101
+ githubRef
100
102
  ) => {
101
103
  const { projectConfig, projectDir } = await getProjectConfig(projectPath);
102
104
 
@@ -133,7 +135,14 @@ const createProjectConfig = async (
133
135
  }`
134
136
  );
135
137
 
136
- await downloadGitHubRepoContents(templateSource, template.path, projectPath);
138
+ const hasCustomTemplateSource = Boolean(templateSource);
139
+
140
+ await downloadGitHubRepoContents(
141
+ templateSource || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH,
142
+ template.path,
143
+ projectPath,
144
+ hasCustomTemplateSource ? {} : { ref: githubRef }
145
+ );
137
146
  const _config = JSON.parse(fs.readFileSync(projectConfigPath));
138
147
  writeProjectConfig(projectConfigPath, {
139
148
  ..._config,
@@ -513,9 +522,27 @@ const handleProjectUpload = async (
513
522
 
514
523
  archive.pipe(output);
515
524
 
516
- archive.directory(srcDir, false, file =>
517
- shouldIgnoreFile(file.name, true) ? false : file
518
- );
525
+ let loggedIgnoredNodeModule = false;
526
+
527
+ archive.directory(srcDir, false, file => {
528
+ const ignored = shouldIgnoreFile(file.name, true);
529
+ if (ignored) {
530
+ const isNodeModule = file.name.includes('node_modules');
531
+
532
+ if (!isNodeModule || !loggedIgnoredNodeModule) {
533
+ logger.debug(
534
+ i18n(`${i18nKey}.handleProjectUpload.fileFiltered`, {
535
+ filename: file.name,
536
+ })
537
+ );
538
+ }
539
+
540
+ if (isNodeModule && !loggedIgnoredNodeModule) {
541
+ loggedIgnoredNodeModule = true;
542
+ }
543
+ }
544
+ return ignored ? false : file;
545
+ });
519
546
 
520
547
  archive.finalize();
521
548
 
@@ -796,7 +823,11 @@ const logFeedbackMessage = buildId => {
796
823
  }
797
824
  };
798
825
 
799
- const createProjectComponent = async (component, name) => {
826
+ const createProjectComponent = async (
827
+ component,
828
+ name,
829
+ projectComponentsVersion
830
+ ) => {
800
831
  const i18nKey = 'cli.commands.project.subcommands.add';
801
832
  let componentName = name;
802
833
 
@@ -815,9 +846,12 @@ const createProjectComponent = async (component, name) => {
815
846
  );
816
847
 
817
848
  await downloadGitHubRepoContents(
818
- 'HubSpot/hubspot-project-components',
849
+ HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH,
819
850
  component.path,
820
- componentPath
851
+ componentPath,
852
+ {
853
+ ref: projectComponentsVersion,
854
+ }
821
855
  );
822
856
  };
823
857
 
@@ -0,0 +1,225 @@
1
+ const chokidar = require('chokidar');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const { default: PQueue } = require('p-queue');
5
+ const {
6
+ logApiErrorInstance,
7
+ ApiErrorContext,
8
+ } = require('@hubspot/cli-lib/errorHandlers');
9
+ const { i18n } = require('@hubspot/cli-lib/lib/lang');
10
+ const { logger } = require('@hubspot/cli-lib/logger');
11
+ const { isAllowedExtension } = require('@hubspot/cli-lib/path');
12
+ const { shouldIgnoreFile } = require('@hubspot/cli-lib/ignoreRules');
13
+ const {
14
+ cancelStagedBuild,
15
+ provisionBuild,
16
+ uploadFileToBuild,
17
+ deleteFileFromBuild,
18
+ queueBuild,
19
+ } = require('@hubspot/cli-lib/api/dfs');
20
+ const { ERROR_TYPES } = require('@hubspot/cli-lib/lib/constants');
21
+
22
+ const i18nKey = 'cli.commands.project.subcommands.watch';
23
+
24
+ const queue = new PQueue({
25
+ concurrency: 10,
26
+ });
27
+ const standbyeQueue = [];
28
+ let currentBuildId = null;
29
+ let handleBuildStatus, handleUserInput;
30
+ let timer;
31
+
32
+ const processStandByQueue = async (accountId, projectName) => {
33
+ queue.addAll(
34
+ standbyeQueue.map(({ filePath, remotePath, action }) => {
35
+ return async () => {
36
+ queueFileOrFolder(accountId, projectName, filePath, remotePath, action);
37
+ };
38
+ })
39
+ );
40
+ standbyeQueue.length = 0;
41
+ debounceQueueBuild(accountId, projectName);
42
+ };
43
+
44
+ const createNewStagingBuild = async (accountId, projectName) => {
45
+ currentBuildId = await createNewBuild(accountId, projectName);
46
+
47
+ handleUserInput(accountId, projectName, currentBuildId);
48
+ };
49
+
50
+ const debounceQueueBuild = (accountId, projectName) => {
51
+ if (timer) {
52
+ clearTimeout(timer);
53
+ }
54
+
55
+ timer = setTimeout(async () => {
56
+ logger.debug(i18n(`${i18nKey}.debug.pause`, { projectName }));
57
+ queue.pause();
58
+ await queue.onIdle();
59
+
60
+ try {
61
+ await queueBuild(accountId, projectName);
62
+ logger.debug(i18n(`${i18nKey}.debug.buildStarted`, { projectName }));
63
+ } catch (err) {
64
+ if (
65
+ err.error &&
66
+ err.error.subCategory === ERROR_TYPES.MISSING_PROJECT_PROVISION
67
+ ) {
68
+ logger.log(i18n(`${i18nKey}.logs.watchCancelledFromUi`));
69
+ process.exit(0);
70
+ } else {
71
+ logApiErrorInstance(
72
+ err,
73
+ new ApiErrorContext({ accountId, projectName })
74
+ );
75
+ }
76
+
77
+ return;
78
+ }
79
+
80
+ await handleBuildStatus(accountId, projectName, currentBuildId);
81
+
82
+ await createNewStagingBuild(accountId, projectName);
83
+
84
+ if (standbyeQueue.length > 0) {
85
+ await processStandByQueue(accountId, projectName);
86
+ }
87
+
88
+ queue.start();
89
+ logger.log(i18n(`${i18nKey}.logs.resuming`));
90
+ logger.log(`\n> Press ${chalk.bold('q')} to quit watching\n`);
91
+ }, 2000);
92
+ };
93
+
94
+ const queueFileOrFolder = async (
95
+ accountId,
96
+ projectName,
97
+ filePath,
98
+ remotePath,
99
+ action
100
+ ) => {
101
+ if (action === 'upload' && !isAllowedExtension(filePath)) {
102
+ logger.debug(i18n(`${i18nKey}.debug.extensionNotAllowed`, { filePath }));
103
+ return;
104
+ }
105
+ if (shouldIgnoreFile(filePath, true)) {
106
+ logger.debug(i18n(`${i18nKey}.debug.ignored`, { filePath }));
107
+ return;
108
+ }
109
+ if (!queue.isPaused) {
110
+ debounceQueueBuild(accountId, projectName);
111
+ }
112
+
113
+ logger.debug(i18n(`${i18nKey}.debug.uploading`, { filePath, remotePath }));
114
+
115
+ return queue.add(async () => {
116
+ try {
117
+ if (action === 'upload') {
118
+ await uploadFileToBuild(accountId, projectName, filePath, remotePath);
119
+ } else if (action === 'deleteFile' || action === 'deleteFolder') {
120
+ await deleteFileFromBuild(accountId, projectName, remotePath);
121
+ }
122
+ logger.log(
123
+ i18n(`${i18nKey}.logs.${action}Succeeded`, { filePath, remotePath })
124
+ );
125
+ } catch (err) {
126
+ logger.debug(
127
+ i18n(`${i18nKey}.errors.${action}Failed`, { filePath, remotePath })
128
+ );
129
+ }
130
+ });
131
+ };
132
+
133
+ const createNewBuild = async (accountId, projectName) => {
134
+ try {
135
+ logger.debug(i18n(`${i18nKey}.debug.attemptNewBuild`));
136
+ const { buildId } = await provisionBuild(accountId, projectName);
137
+ return buildId;
138
+ } catch (err) {
139
+ logApiErrorInstance(err, new ApiErrorContext({ accountId, projectName }));
140
+ if (err.error.subCategory !== ERROR_TYPES.PROJECT_LOCKED) {
141
+ await cancelStagedBuild(accountId, projectName);
142
+ logger.log(i18n(`${i18nKey}.logs.previousStagingBuildCancelled`));
143
+ }
144
+ process.exit(1);
145
+ }
146
+ };
147
+
148
+ const handleWatchEvent = async (
149
+ accountId,
150
+ projectName,
151
+ projectSourceDir,
152
+ filePath,
153
+ action = 'upload'
154
+ ) => {
155
+ const remotePath = path.relative(projectSourceDir, filePath);
156
+ if (queue.isPaused) {
157
+ standbyeQueue.find(file => file.filePath === filePath)
158
+ ? logger.debug(i18n(`${i18nKey}.debug.fileAlreadyQueued`, { filePath }))
159
+ : standbyeQueue.push({
160
+ filePath,
161
+ remotePath,
162
+ action,
163
+ });
164
+ } else {
165
+ await queueFileOrFolder(
166
+ accountId,
167
+ projectName,
168
+ filePath,
169
+ remotePath,
170
+ action
171
+ );
172
+ }
173
+ };
174
+
175
+ const createWatcher = async (
176
+ accountId,
177
+ projectConfig,
178
+ projectDir,
179
+ handleBuildStatusFn,
180
+ handleUserInputFn
181
+ ) => {
182
+ const projectSourceDir = path.join(projectDir, projectConfig.srcDir);
183
+
184
+ handleBuildStatus = handleBuildStatusFn;
185
+ handleUserInput = handleUserInputFn;
186
+
187
+ await createNewStagingBuild(accountId, projectConfig.name);
188
+
189
+ const watcher = chokidar.watch(projectSourceDir, {
190
+ ignoreInitial: true,
191
+ ignored: file => shouldIgnoreFile(file),
192
+ });
193
+ watcher.on('ready', async () => {
194
+ logger.log(i18n(`${i18nKey}.logs.watching`, { projectDir }));
195
+ logger.log(`\n> Press ${chalk.bold('q')} to quit watching\n`);
196
+ });
197
+ watcher.on('add', async path => {
198
+ handleWatchEvent(accountId, projectConfig.name, projectSourceDir, path);
199
+ });
200
+ watcher.on('change', async path => {
201
+ handleWatchEvent(accountId, projectConfig.name, projectSourceDir, path);
202
+ });
203
+ watcher.on('unlink', async path => {
204
+ handleWatchEvent(
205
+ accountId,
206
+ projectConfig.name,
207
+ projectSourceDir,
208
+ path,
209
+ 'deleteFile'
210
+ );
211
+ });
212
+ watcher.on('unlinkDir', async path => {
213
+ handleWatchEvent(
214
+ accountId,
215
+ projectConfig.name,
216
+ projectSourceDir,
217
+ path,
218
+ 'deleteFolder'
219
+ );
220
+ });
221
+ };
222
+
223
+ module.exports = {
224
+ createWatcher,
225
+ };
@@ -9,6 +9,10 @@ const { fetchJsonFromRepository } = require('@hubspot/cli-lib/github');
9
9
  const { i18n } = require('../lang');
10
10
  const { logger } = require('@hubspot/cli-lib/logger');
11
11
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
12
+ const {
13
+ HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH,
14
+ DEFAULT_PROJECT_TEMPLATE_BRANCH,
15
+ } = require('../constants');
12
16
 
13
17
  const i18nKey = 'cli.lib.prompts.createProjectPrompt';
14
18
 
@@ -20,12 +24,17 @@ const hasAllProperties = projectList => {
20
24
  );
21
25
  };
22
26
 
23
- const createTemplateOptions = async templateSource => {
24
- const isTemplateSource = !!templateSource;
27
+ const createTemplateOptions = async (templateSource, githubRef) => {
28
+ const hasCustomTemplateSource = Boolean(templateSource);
29
+ let branch = hasCustomTemplateSource
30
+ ? DEFAULT_PROJECT_TEMPLATE_BRANCH
31
+ : githubRef;
32
+
25
33
  const config = await fetchJsonFromRepository(
26
- templateSource,
27
- 'main/config.json',
28
- isTemplateSource
34
+ templateSource || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH,
35
+ 'config.json',
36
+ branch,
37
+ hasCustomTemplateSource
29
38
  );
30
39
 
31
40
  if (!config || !config[PROJECT_COMPONENT_TYPES.PROJECTS]) {
@@ -41,9 +50,10 @@ const createTemplateOptions = async templateSource => {
41
50
  return config[PROJECT_COMPONENT_TYPES.PROJECTS];
42
51
  };
43
52
 
44
- const createProjectPrompt = async (promptOptions = {}) => {
53
+ const createProjectPrompt = async (githubRef, promptOptions = {}) => {
45
54
  const projectTemplates = await createTemplateOptions(
46
- promptOptions.templateSource
55
+ promptOptions.templateSource,
56
+ githubRef
47
57
  );
48
58
 
49
59
  return promptUser([
@@ -1,21 +1,26 @@
1
1
  const { promptUser } = require('./promptUtils');
2
2
  const { fetchJsonFromRepository } = require('@hubspot/cli-lib/github');
3
3
  const { i18n } = require('../lang');
4
+ const { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH } = require('../constants');
4
5
  const { PROJECT_COMPONENT_TYPES } = require('@hubspot/cli-lib/lib/constants');
5
6
 
6
7
  const i18nKey = 'cli.lib.prompts.projectAddPrompt';
7
8
 
8
- const createTypeOptions = async () => {
9
+ const createTypeOptions = async projectComponentsVersion => {
9
10
  const config = await fetchJsonFromRepository(
10
- 'HubSpot/hubspot-project-components',
11
- 'main/config.json'
11
+ HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH,
12
+ 'config.json',
13
+ projectComponentsVersion
12
14
  );
13
15
 
14
16
  return config[PROJECT_COMPONENT_TYPES.COMPONENTS];
15
17
  };
16
18
 
17
- const projectAddPrompt = async (promptOptions = {}) => {
18
- const components = await createTypeOptions();
19
+ const projectAddPrompt = async (
20
+ projectComponentsVersion,
21
+ promptOptions = {}
22
+ ) => {
23
+ const components = await createTypeOptions(projectComponentsVersion);
19
24
  return promptUser([
20
25
  {
21
26
  name: 'type',
package/lib/sandboxes.js CHANGED
@@ -6,6 +6,7 @@ const {
6
6
  } = require('@hubspot/cli-lib');
7
7
  const chalk = require('chalk');
8
8
  const { i18n } = require('./lang');
9
+ const { handleExit, handleKeypress } = require('./process');
9
10
  const { logger } = require('@hubspot/cli-lib/logger');
10
11
  const {
11
12
  updateConfigWithPersonalAccessKey,
@@ -17,7 +18,6 @@ const {
17
18
  fetchTypes,
18
19
  getSandboxUsageLimits,
19
20
  } = require('@hubspot/cli-lib/sandboxes');
20
- const { handleExit, handleKeypress } = require('@hubspot/cli-lib/lib/process');
21
21
  const { accountNameExistsInConfig } = require('@hubspot/cli-lib/lib/config');
22
22
  const CliProgressMultibarManager = require('./CliProgressMultibarManager');
23
23
  const { promptUser } = require('./prompts/promptUtils');
package/lib/schema.js ADDED
@@ -0,0 +1,31 @@
1
+ const chalk = require('chalk');
2
+ const { logger } = require('@hubspot/cli-lib/logger');
3
+ const { table, getBorderCharacters } = require('table');
4
+ const { fetchSchemas } = require('@hubspot/cli-lib/api/schema');
5
+
6
+ const logSchemas = schemas => {
7
+ const data = schemas
8
+ .map(r => [r.labels.singular, r.name, r.objectTypeId || ''])
9
+ .sort((a, b) => (a[1] > b[1] ? 1 : -1));
10
+ data.unshift([
11
+ chalk.bold('Label'),
12
+ chalk.bold('Name'),
13
+ chalk.bold('objectTypeId'),
14
+ ]);
15
+
16
+ const tableConfig = {
17
+ singleLine: true,
18
+ border: getBorderCharacters('honeywell'),
19
+ };
20
+
21
+ logger.log(data.length ? table(data, tableConfig) : 'No Schemas were found');
22
+ };
23
+
24
+ const listSchemas = async accountId => {
25
+ const response = await fetchSchemas(accountId);
26
+ logSchemas(response.results);
27
+ };
28
+
29
+ module.exports = {
30
+ listSchemas,
31
+ };
@@ -1,5 +1,6 @@
1
1
  const https = require('https');
2
2
  const SpinniesManager = require('./SpinniesManager');
3
+ const { handleExit, handleKeypress } = require('./process');
3
4
  const chalk = require('chalk');
4
5
  const { logger } = require('@hubspot/cli-lib/logger');
5
6
  const { outputLogs } = require('@hubspot/cli-lib/lib/logs');
@@ -9,7 +10,6 @@ const {
9
10
  ApiErrorContext,
10
11
  } = require('@hubspot/cli-lib/errorHandlers');
11
12
  const { base64EncodeString } = require('@hubspot/cli-lib/lib/encoding');
12
- const { handleExit, handleKeypress } = require('@hubspot/cli-lib/lib/process');
13
13
 
14
14
  const { EXIT_CODES } = require('../lib/enums/exitCodes');
15
15
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "5.0.1",
3
+ "version": "5.0.2-beta.1",
4
4
  "description": "CLI for working with HubSpot",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -8,9 +8,9 @@
8
8
  "url": "https://github.com/HubSpot/hubspot-cms-tools"
9
9
  },
10
10
  "dependencies": {
11
- "@hubspot/cli-lib": "^4.2.2",
11
+ "@hubspot/cli-lib": "^5.0.1",
12
12
  "@hubspot/serverless-dev-runtime": "4.2.1-beta.3",
13
- "@hubspot/ui-extensions-dev-server": "^0.7.2",
13
+ "@hubspot/ui-extensions-dev-server": "^0.8.0",
14
14
  "archiver": "^5.3.0",
15
15
  "chalk": "^4.1.2",
16
16
  "chokidar": "^3.0.1",
@@ -24,7 +24,9 @@
24
24
  "moment": "^2.29.1",
25
25
  "open": "^7.0.3",
26
26
  "ora": "^4.0.3",
27
+ "p-queue": "^6.0.2",
27
28
  "strip-ansi": "^5.2.0",
29
+ "table": "^6.6.0",
28
30
  "tmp": "^0.2.1",
29
31
  "update-notifier": "^5.1.0",
30
32
  "yargs": "15.4.1"
@@ -42,5 +44,5 @@
42
44
  "publishConfig": {
43
45
  "access": "public"
44
46
  },
45
- "gitHead": "0f88273233124d513c4338fed7bd01fa4678251d"
47
+ "gitHead": "ffa9b2bd4597057e8b0c640c9ceb9c37a12df19c"
46
48
  }