@hubspot/cli 7.0.15-experimental.0 → 7.0.16-experimental.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,8 +6,11 @@ const { confirmPrompt } = require('../../lib/prompts/promptUtils');
6
6
  const { logger } = require('@hubspot/local-dev-lib/logger');
7
7
  const path = require('path');
8
8
  const fs = require('fs-extra');
9
- const { fetchFileFromRepository, cloneGithubRepo, } = require('@hubspot/local-dev-lib/github');
9
+ const { fetchRepoFile } = require('@hubspot/local-dev-lib/api/github');
10
+ const { cloneGithubRepo } = require('@hubspot/local-dev-lib/github');
10
11
  const { i18n } = require('../../lib/lang');
12
+ const { debugError } = require('../../lib/errorHandlers');
13
+ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
11
14
  const i18nKey = 'commands.create.subcommands.apiSample';
12
15
  module.exports = {
13
16
  hidden: true,
@@ -30,15 +33,22 @@ module.exports = {
30
33
  return;
31
34
  }
32
35
  }
33
- const samplesConfig = await fetchFileFromRepository('HubSpot/sample-apps-list', 'samples.json', 'main');
36
+ let samplesConfig;
37
+ try {
38
+ const { data } = await fetchRepoFile('HubSpot/sample-apps-list', 'samples.json', 'main');
39
+ samplesConfig = data;
40
+ }
41
+ catch (err) {
42
+ debugError(err);
43
+ }
34
44
  if (!samplesConfig) {
35
45
  logger.error(i18n(`${i18nKey}.errors.noSamples`));
36
- return;
46
+ process.exit(EXIT_CODES.ERROR);
37
47
  }
38
48
  const { sampleType, sampleLanguage } = await createApiSamplePrompt(samplesConfig);
39
49
  if (!sampleType || !sampleLanguage) {
40
50
  logger.error(i18n(`${i18nKey}.errors.noSamples`));
41
- return;
51
+ process.exit(EXIT_CODES.ERROR);
42
52
  }
43
53
  logger.info(i18n(`${i18nKey}.info.sampleChosen`, {
44
54
  sampleType,
@@ -1,42 +1,88 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  // @ts-nocheck
4
+ const path = require('path');
4
5
  const { logger } = require('@hubspot/local-dev-lib/logger');
5
- const { logError } = require('../../lib/errorHandlers/index');
6
- const { fetchReleaseData } = require('@hubspot/local-dev-lib/github');
6
+ const { cloneGithubRepo, fetchReleaseData, } = require('@hubspot/local-dev-lib/github');
7
+ const { debugError } = require('../../lib/errorHandlers');
7
8
  const { trackCommandUsage } = require('../../lib/usageTracking');
8
9
  const { i18n } = require('../../lib/lang');
9
10
  const { projectAddPrompt } = require('../../lib/prompts/projectAddPrompt');
10
- const { createProjectComponent, getProjectComponentsByVersion, } = require('../../lib/projects');
11
+ const { getProjectConfig } = require('../../lib/projects');
12
+ const { getProjectComponentListFromRepo, } = require('../../lib/projects/create');
13
+ const { findProjectComponents } = require('../../lib/projects/structure');
14
+ const { ComponentTypes } = require('../../types/Projects');
11
15
  const { uiBetaTag } = require('../../lib/ui');
12
16
  const { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, } = require('../../lib/constants');
17
+ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
13
18
  const i18nKey = 'commands.project.subcommands.add';
14
19
  exports.command = 'add';
15
20
  exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false);
16
21
  exports.handler = async (options) => {
17
22
  const { derivedAccountId } = options;
23
+ trackCommandUsage('project-add', null, derivedAccountId);
24
+ const { projectConfig, projectDir } = await getProjectConfig();
25
+ if (!projectDir || !projectConfig) {
26
+ logger.error(i18n(`${i18nKey}.error.locationInProject`));
27
+ process.exit(EXIT_CODES.ERROR);
28
+ }
29
+ // We currently only support adding private apps to projects
30
+ let projectContainsPublicApp = false;
31
+ try {
32
+ const components = await findProjectComponents(projectDir);
33
+ projectContainsPublicApp = components.some(c => c.type === ComponentTypes.PublicApp);
34
+ }
35
+ catch (err) {
36
+ debugError(err);
37
+ }
38
+ if (projectContainsPublicApp) {
39
+ logger.error(i18n(`${i18nKey}.error.projectContainsPublicApp`));
40
+ process.exit(EXIT_CODES.ERROR);
41
+ }
18
42
  logger.log('');
19
- logger.log(i18n(`${i18nKey}.creatingComponent.message`));
43
+ logger.log(i18n(`${i18nKey}.creatingComponent`, {
44
+ projectName: projectConfig.name,
45
+ }));
20
46
  logger.log('');
21
- const releaseData = await fetchReleaseData(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH);
22
- const projectComponentsVersion = releaseData.tag_name;
23
- const components = await getProjectComponentsByVersion(projectComponentsVersion);
24
- let { component, name } = await projectAddPrompt(components, options);
25
- name = name || options.name;
26
- if (!component) {
27
- component = components.find(t => t.path === options.type);
47
+ let latestRepoReleaseTag;
48
+ try {
49
+ // We want the tag_name from the latest release of the components repo
50
+ const repoReleaseData = await fetchReleaseData(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH);
51
+ if (repoReleaseData) {
52
+ latestRepoReleaseTag = repoReleaseData.tag_name;
53
+ }
28
54
  }
29
- trackCommandUsage('project-add', null, derivedAccountId);
55
+ catch (err) {
56
+ debugError(err);
57
+ }
58
+ if (!latestRepoReleaseTag) {
59
+ logger.error(i18n(`${i18nKey}.error.failedToFetchComponentList`));
60
+ process.exit(EXIT_CODES.ERROR);
61
+ }
62
+ const components = await getProjectComponentListFromRepo(latestRepoReleaseTag);
63
+ if (!components.length) {
64
+ logger.error(i18n(`${i18nKey}.error.failedToFetchComponentList`));
65
+ process.exit(EXIT_CODES.ERROR);
66
+ }
67
+ const projectAddPromptResponse = await projectAddPrompt(components, options);
30
68
  try {
31
- await createProjectComponent(component, name, projectComponentsVersion);
69
+ const componentPath = path.join(projectDir, projectConfig.srcDir, projectAddPromptResponse.componentTemplate.insertPath, projectAddPromptResponse.name);
70
+ await cloneGithubRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, componentPath, {
71
+ sourceDir: projectAddPromptResponse.componentTemplate.path,
72
+ tag: latestRepoReleaseTag,
73
+ hideLogs: true,
74
+ });
32
75
  logger.log('');
33
- logger.log(i18n(`${i18nKey}.success.message`, {
34
- componentName: name,
76
+ logger.success(i18n(`${i18nKey}.success`, {
77
+ componentName: projectAddPromptResponse.name,
35
78
  }));
36
79
  }
37
80
  catch (error) {
38
- logError(error);
81
+ debugError(error);
82
+ logger.error(i18n(`${i18nKey}.error.failedToDownloadComponent`));
83
+ process.exit(EXIT_CODES.ERROR);
39
84
  }
85
+ process.exit(EXIT_CODES.SUCCESS);
40
86
  };
41
87
  exports.builder = yargs => {
42
88
  yargs.options({
@@ -53,9 +53,9 @@ exports.handler = async (options) => {
53
53
  });
54
54
  appId = appIdResponse.appId;
55
55
  }
56
- const { name, dest } = await createProjectPrompt('', options, true);
57
- projectName = name;
58
- projectDest = options.dest || dest;
56
+ const createProjectPromptResponse = await createProjectPrompt(options);
57
+ projectName = createProjectPromptResponse.name;
58
+ projectDest = createProjectPromptResponse.dest;
59
59
  }
60
60
  catch (error) {
61
61
  logError(error, new ApiErrorContext({ accountId: derivedAccountId }));
@@ -1,32 +1,86 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  // @ts-nocheck
4
- const { addAccountOptions, addConfigOptions, addUseEnvironmentOptions, } = require('../../lib/commonOpts');
5
- const { trackCommandUsage } = require('../../lib/usageTracking');
6
- const { getCwd } = require('@hubspot/local-dev-lib/path');
7
4
  const path = require('path');
5
+ const fs = require('fs-extra');
8
6
  const chalk = require('chalk');
7
+ const { logger } = require('@hubspot/local-dev-lib/logger');
8
+ const { fetchReleaseData, cloneGithubRepo, } = require('@hubspot/local-dev-lib/github');
9
+ const { getCwd } = require('@hubspot/local-dev-lib/path');
10
+ const { addAccountOptions, addConfigOptions, addUseEnvironmentOptions, } = require('../../lib/commonOpts');
11
+ const { trackCommandUsage } = require('../../lib/usageTracking');
9
12
  const { createProjectPrompt, } = require('../../lib/prompts/createProjectPrompt');
10
- const { createProjectConfig } = require('../../lib/projects');
13
+ const { writeProjectConfig, getProjectConfig } = require('../../lib/projects');
14
+ const { getProjectTemplateListFromRepo, EMPTY_PROJECT_TEMPLATE_NAME, } = require('../../lib/projects/create');
11
15
  const { i18n } = require('../../lib/lang');
12
16
  const { uiBetaTag, uiFeatureHighlight } = require('../../lib/ui');
13
- const { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, } = require('../../lib/constants');
14
- const { logger } = require('@hubspot/local-dev-lib/logger');
15
- const { fetchReleaseData } = require('@hubspot/local-dev-lib/github');
17
+ const { debugError } = require('../../lib/errorHandlers');
18
+ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
19
+ const { PROJECT_CONFIG_FILE, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, DEFAULT_PROJECT_TEMPLATE_BRANCH, } = require('../../lib/constants');
16
20
  const i18nKey = 'commands.project.subcommands.create';
17
21
  exports.command = 'create';
18
22
  exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false);
19
23
  exports.handler = async (options) => {
20
24
  const { derivedAccountId } = options;
21
- const hasCustomTemplateSource = Boolean(options.templateSource);
22
- let githubRef = '';
23
- if (!hasCustomTemplateSource) {
24
- const releaseData = await fetchReleaseData(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH);
25
- githubRef = releaseData.tag_name;
25
+ let latestRepoReleaseTag;
26
+ let templateSource = options.templateSource;
27
+ if (!templateSource) {
28
+ templateSource = HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH;
29
+ try {
30
+ const releaseData = await fetchReleaseData(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH);
31
+ if (releaseData) {
32
+ latestRepoReleaseTag = releaseData.tag_name;
33
+ }
34
+ }
35
+ catch (err) {
36
+ logger.error(i18n(`${i18nKey}.error.failedToFetchProjectList`));
37
+ process.exit(EXIT_CODES.ERROR);
38
+ }
26
39
  }
27
- const { name, template, dest } = await createProjectPrompt(githubRef, options);
28
- trackCommandUsage('project-create', { type: template.name }, derivedAccountId);
29
- await createProjectConfig(path.resolve(getCwd(), options.dest || dest), options.name || name, template, options.templateSource, githubRef);
40
+ const projectTemplates = await getProjectTemplateListFromRepo(templateSource, latestRepoReleaseTag || DEFAULT_PROJECT_TEMPLATE_BRANCH);
41
+ if (!projectTemplates.length) {
42
+ logger.error(i18n(`${i18nKey}.error.failedToFetchProjectList`));
43
+ process.exit(EXIT_CODES.ERROR);
44
+ }
45
+ const createProjectPromptResponse = await createProjectPrompt(options, projectTemplates);
46
+ const projectDest = path.resolve(getCwd(), createProjectPromptResponse.dest);
47
+ trackCommandUsage('project-create', { type: createProjectPromptResponse.projectTemplate.name }, derivedAccountId);
48
+ const { projectConfig: existingProjectConfig, projectDir: existingProjectDir, } = await getProjectConfig(projectDest);
49
+ // Exit if the target destination is within an existing project
50
+ if (existingProjectConfig && projectDest.startsWith(existingProjectDir)) {
51
+ logger.error(i18n(`${i18nKey}.errors.cannotNestProjects`, {
52
+ projectDir: existingProjectDir,
53
+ }));
54
+ process.exit(EXIT_CODES.ERROR);
55
+ }
56
+ try {
57
+ await cloneGithubRepo(templateSource, projectDest, {
58
+ sourceDir: createProjectPromptResponse.projectTemplate.path,
59
+ tag: latestRepoReleaseTag,
60
+ hideLogs: true,
61
+ });
62
+ }
63
+ catch (err) {
64
+ debugError(err);
65
+ logger.error(i18n(`${i18nKey}.errors.failedToDownloadProject`));
66
+ process.exit(EXIT_CODES.ERROR);
67
+ }
68
+ const projectConfigPath = path.join(projectDest, PROJECT_CONFIG_FILE);
69
+ const parsedConfigFile = JSON.parse(fs.readFileSync(projectConfigPath).toString());
70
+ writeProjectConfig(projectConfigPath, {
71
+ ...parsedConfigFile,
72
+ name: createProjectPromptResponse.name,
73
+ });
74
+ // If the template is 'no-template', we need to manually create a src directory
75
+ if (createProjectPromptResponse.projectTemplate.name ===
76
+ EMPTY_PROJECT_TEMPLATE_NAME) {
77
+ fs.ensureDirSync(path.join(projectDest, 'src'));
78
+ }
79
+ logger.log('');
80
+ logger.success(i18n(`${i18nKey}.logs.success`, {
81
+ projectName: createProjectPromptResponse.name,
82
+ projectDest,
83
+ }));
30
84
  logger.log('');
31
85
  logger.log(chalk.bold(i18n(`${i18nKey}.logs.welcomeMessage`)));
32
86
  uiFeatureHighlight([
@@ -35,6 +89,7 @@ exports.handler = async (options) => {
35
89
  'feedbackCommand',
36
90
  'sampleProjects',
37
91
  ]);
92
+ process.exit(EXIT_CODES.SUCCESS);
38
93
  };
39
94
  exports.builder = yargs => {
40
95
  yargs.options({
@@ -71,9 +71,9 @@ exports.handler = async (options) => {
71
71
  let projectName;
72
72
  let projectDest;
73
73
  try {
74
- const { name, dest } = await createProjectPrompt('', options, true);
75
- projectName = options.name || name;
76
- projectDest = options.dest || dest;
74
+ const createProjectPromptResponse = await createProjectPrompt(options);
75
+ projectName = createProjectPromptResponse.name;
76
+ projectDest = createProjectPromptResponse.dest;
77
77
  const { projectExists } = await ensureProjectExists(derivedAccountId, projectName, {
78
78
  allowCreate: false,
79
79
  noLogs: true,
@@ -20,7 +20,7 @@ const i18nKey = 'commands.project.subcommands.upload';
20
20
  exports.command = 'upload';
21
21
  exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false);
22
22
  exports.handler = async (options) => {
23
- const { forceCreate, message, derivedAccountId, bypassValidation } = options;
23
+ const { forceCreate, message, derivedAccountId } = options;
24
24
  const accountConfig = getAccountConfig(derivedAccountId);
25
25
  const accountType = accountConfig && accountConfig.accountType;
26
26
  const { projectConfig, projectDir } = await getProjectConfig();
@@ -31,7 +31,7 @@ exports.handler = async (options) => {
31
31
  uploadCommand: true,
32
32
  });
33
33
  try {
34
- const { result, uploadError } = await handleProjectUpload(derivedAccountId, projectConfig, projectDir, pollProjectBuildAndDeploy, message, useV3Api(projectConfig?.platformVersion), bypassValidation);
34
+ const { result, uploadError } = await handleProjectUpload(derivedAccountId, projectConfig, projectDir, pollProjectBuildAndDeploy, message, useV3Api(projectConfig?.platformVersion));
35
35
  if (uploadError) {
36
36
  if (isSpecifiedError(uploadError, {
37
37
  subCategory: PROJECT_ERROR_TYPES.PROJECT_LOCKED,
@@ -81,11 +81,6 @@ exports.builder = yargs => {
81
81
  type: 'string',
82
82
  default: '',
83
83
  },
84
- 'bypass-validation': {
85
- type: 'boolean',
86
- hidden: true,
87
- default: false,
88
- },
89
84
  });
90
85
  yargs.example([['$0 project upload', i18n(`${i18nKey}.examples.default`)]]);
91
86
  addConfigOptions(yargs);
package/lang/en.lyaml CHANGED
@@ -530,7 +530,12 @@ en:
530
530
  default: "Start local dev for the current project"
531
531
  create:
532
532
  describe: "Create a new project."
533
+ errors:
534
+ failedToDownloadProject: "Failed to download project. Please try again later."
535
+ failedToFetchProjectList: "Failed to fetch the list of available project templates. Please try again later."
536
+ cannotNestProjects: "A project already exists at {{ projectDir }}. Projects cannot be nested within other projects. Please choose a different destination and try again."
533
537
  logs:
538
+ success: "Project {{#bold}}{{ projectName }}{{/bold}} was successfully created in {{ projectDest }}"
534
539
  welcomeMessage: "Welcome to HubSpot Developer Projects!"
535
540
  examples:
536
541
  default: "Create a new project"
@@ -603,12 +608,13 @@ en:
603
608
  describe: "The name for your newly created component"
604
609
  type:
605
610
  describe: "The path to the component type's location within the hubspot-project-components Github repo: https://github.com/HubSpot/hubspot-project-components"
606
- creatingComponent:
607
- message: "Adding a new component to your project"
608
- success:
609
- message: "{{ componentName }} was added to your project"
611
+ creatingComponent: "Adding a new component to {{#bold}}{{ projectName }}{{/bold}}"
612
+ success: "{{ componentName }} was successfully added to your project."
610
613
  error:
611
- locationInProject: "The component location must be within a project folder"
614
+ failedToDownloadComponent: "Failed to download project component. Please try again later."
615
+ locationInProject: "This command must be run from within a project directory."
616
+ failedToFetchComponentList: "Failed to fetch the list of available components. Please try again later."
617
+ projectContainsPublicApp: "This project contains a public app. This command is currently only compatible with projects that contain private apps."
612
618
  examples:
613
619
  default: "Create a component within your project"
614
620
  withFlags: "Use --name and --type flags to bypass the prompt."
@@ -1106,6 +1112,11 @@ en:
1106
1112
  checkIfParentAccountIsAuthed:
1107
1113
  notAuthedError: "To develop this project locally, run {{ authCommand }} to authenticate the App Developer Account {{ accountId }} associated with {{ accountIdentifier }}."
1108
1114
  projects:
1115
+ create:
1116
+ errors:
1117
+ noProjectsInConfig: "Unable to find any projects in the target repository's config.json file. Please ensure that there is a \"projects\" array in the config file."
1118
+ missingConfigFileTemplateSource: "Failed to fetch the config.json file from the target repository. Please ensure that there is a valid config.json file at the root of the repository and try again."
1119
+ missingPropertiesInConfig: "Found misconfigured projects in the target repository's config.json file. Please ensure that each project in the target repository's config.json file contains the following properties: [\"name\", \"label\", \"path\", \"insertPath\"]."
1109
1120
  validateProjectConfig:
1110
1121
  configNotFound: "Unable to locate a project configuration file. Try running again from a project directory, or run {{ createCommand }} to create a new project."
1111
1122
  configMissingFields: "The project configuruation file is missing required fields."
@@ -1313,12 +1324,9 @@ en:
1313
1324
  errors:
1314
1325
  nameRequired: "A project name is required"
1315
1326
  destRequired: "A project dest is required"
1316
- invalidDest: "The selected destination already exists. Please provide a new path for this project."
1327
+ invalidDest: "There is an existing project at this destination. Please provide a new path for this project."
1317
1328
  invalidCharacters: "The selected destination contains invalid characters. Please provide a new path and try again."
1318
- invalidTemplate: "[--template] Could not find template {{ template }}. Please choose an available template."
1319
- noProjectsInConfig: "Please ensure that there is a config.json file that contains a \"projects\" field."
1320
- missingConfigFileTemplateSource: "Please ensure that there is a config.json file in the repository used as the --template-source"
1321
- missingPropertiesInConfig: "Please ensure that each of the projects in your config.json file contain the following properties: [\"name\", \"label\", \"path\", \"insertPath\"]."
1329
+ invalidTemplate: "[--template] Could not find template \"{{ template }}\". Please choose an available template:"
1322
1330
  selectPublicAppPrompt:
1323
1331
  selectAppIdMigrate: "[--appId] Choose an app under {{ accountName }} to migrate:"
1324
1332
  selectAppIdClone: "[--appId] Choose an app under {{ accountName }} to clone:"
@@ -1336,11 +1344,11 @@ en:
1336
1344
  projectNotFound: "Your project {{ projectName }} could not be found in {{ accountId }}. Please select a valid project:"
1337
1345
  accountIdRequired: "An account ID is required to download a project."
1338
1346
  projectAddPrompt:
1339
- selectType: "[--type] Select your component type:"
1347
+ selectType: "[--type] Select a component to add: "
1340
1348
  enterName: "[--name] Give your component a name: "
1341
1349
  errors:
1342
1350
  nameRequired: "A component name is required"
1343
- invalidType: "[--type] Could not find type {{ type }}. Please choose an available type."
1351
+ invalidType: "[--type] Could not find type \"{{ type }}\". Please choose an available type:"
1344
1352
  secretPrompt:
1345
1353
  enterValue: "Enter a value for your secret: "
1346
1354
  enterName: "Enter a name for your secret: "
@@ -0,0 +1,5 @@
1
+ import { RepoPath } from '@hubspot/local-dev-lib/types/Github';
2
+ import { ProjectTemplate, ComponentTemplate } from '../../types/Projects';
3
+ export declare const EMPTY_PROJECT_TEMPLATE_NAME = "no-template";
4
+ export declare function getProjectComponentListFromRepo(githubRef: string): Promise<ComponentTemplate[]>;
5
+ export declare function getProjectTemplateListFromRepo(templateSource: RepoPath, githubRef: string): Promise<ProjectTemplate[]>;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EMPTY_PROJECT_TEMPLATE_NAME = void 0;
4
+ exports.getProjectComponentListFromRepo = getProjectComponentListFromRepo;
5
+ exports.getProjectTemplateListFromRepo = getProjectTemplateListFromRepo;
6
+ const logger_1 = require("@hubspot/local-dev-lib/logger");
7
+ const github_1 = require("@hubspot/local-dev-lib/api/github");
8
+ const constants_1 = require("../constants");
9
+ const exitCodes_1 = require("../enums/exitCodes");
10
+ const lang_1 = require("../lang");
11
+ const index_1 = require("../errorHandlers/index");
12
+ const i18nKey = 'lib.projects.create';
13
+ exports.EMPTY_PROJECT_TEMPLATE_NAME = 'no-template';
14
+ const PROJECT_TEMPLATE_PROPERTIES = ['name', 'label', 'path', 'insertPath'];
15
+ async function getProjectComponentListFromRepo(githubRef) {
16
+ let config;
17
+ try {
18
+ const { data } = await (0, github_1.fetchRepoFile)(constants_1.HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, 'config.json', githubRef);
19
+ config = data;
20
+ }
21
+ catch (err) {
22
+ (0, index_1.debugError)(err);
23
+ }
24
+ if (config) {
25
+ return config[constants_1.PROJECT_COMPONENT_TYPES.COMPONENTS] || [];
26
+ }
27
+ return [];
28
+ }
29
+ async function getProjectTemplateListFromRepo(templateSource, githubRef) {
30
+ let config;
31
+ try {
32
+ const { data } = await (0, github_1.fetchRepoFile)(templateSource, 'config.json', githubRef);
33
+ config = data;
34
+ }
35
+ catch (e) {
36
+ (0, index_1.debugError)(e);
37
+ logger_1.logger.error((0, lang_1.i18n)(`${i18nKey}.errors.missingConfigFileTemplateSource`));
38
+ return process.exit(exitCodes_1.EXIT_CODES.ERROR);
39
+ }
40
+ if (!config || !config[constants_1.PROJECT_COMPONENT_TYPES.PROJECTS]) {
41
+ logger_1.logger.error((0, lang_1.i18n)(`${i18nKey}.errors.noProjectsInConfig`));
42
+ return process.exit(exitCodes_1.EXIT_CODES.ERROR);
43
+ }
44
+ const templates = config[constants_1.PROJECT_COMPONENT_TYPES.PROJECTS];
45
+ const templatesContainAllProperties = templates.every(config => PROJECT_TEMPLATE_PROPERTIES.every(p => Object.prototype.hasOwnProperty.call(config, p)));
46
+ if (!templatesContainAllProperties) {
47
+ logger_1.logger.error((0, lang_1.i18n)(`${i18nKey}.errors.missingPropertiesInConfig`));
48
+ return process.exit(exitCodes_1.EXIT_CODES.ERROR);
49
+ }
50
+ return templates;
51
+ }
@@ -1,13 +1,11 @@
1
- import { RepoPath } from '@hubspot/local-dev-lib/types/Github';
2
1
  import { Project } from '@hubspot/local-dev-lib/types/Project';
3
- import { ProjectTemplate, ProjectConfig, ProjectAddComponentData, ComponentTemplate } from '../../types/Projects';
2
+ import { ProjectConfig } from '../../types/Projects';
4
3
  export declare function writeProjectConfig(configPath: string, config: ProjectConfig): boolean;
5
4
  export declare function getIsInProject(dir?: string): boolean;
6
5
  export declare function getProjectConfig(dir?: string): Promise<{
7
6
  projectDir: string | null;
8
7
  projectConfig: ProjectConfig | null;
9
8
  }>;
10
- export declare function createProjectConfig(projectPath: string, projectName: string, template: ProjectTemplate, templateSource: RepoPath, githubRef: string): Promise<boolean>;
11
9
  export declare function validateProjectConfig(projectConfig: ProjectConfig, projectDir: string): void;
12
10
  export declare function ensureProjectExists(accountId: number, projectName: string, { forceCreate, allowCreate, noLogs, withPolling, uploadCommand, }?: {
13
11
  forceCreate?: boolean | undefined;
@@ -20,5 +18,3 @@ export declare function ensureProjectExists(accountId: number, projectName: stri
20
18
  project?: Project;
21
19
  }>;
22
20
  export declare function logFeedbackMessage(buildId: number): void;
23
- export declare function createProjectComponent(component: ProjectAddComponentData, name: string, projectComponentsVersion: string): Promise<void>;
24
- export declare function getProjectComponentsByVersion(projectComponentsVersion: string): Promise<ComponentTemplate[]>;
@@ -6,17 +6,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.writeProjectConfig = writeProjectConfig;
7
7
  exports.getIsInProject = getIsInProject;
8
8
  exports.getProjectConfig = getProjectConfig;
9
- exports.createProjectConfig = createProjectConfig;
10
9
  exports.validateProjectConfig = validateProjectConfig;
11
10
  exports.ensureProjectExists = ensureProjectExists;
12
11
  exports.logFeedbackMessage = logFeedbackMessage;
13
- exports.createProjectComponent = createProjectComponent;
14
- exports.getProjectComponentsByVersion = getProjectComponentsByVersion;
15
12
  const fs_extra_1 = __importDefault(require("fs-extra"));
16
13
  const path_1 = __importDefault(require("path"));
17
14
  const findup_sync_1 = __importDefault(require("findup-sync"));
18
15
  const logger_1 = require("@hubspot/local-dev-lib/logger");
19
- const github_1 = require("@hubspot/local-dev-lib/github");
20
16
  const projects_1 = require("@hubspot/local-dev-lib/api/projects");
21
17
  const index_1 = require("@hubspot/local-dev-lib/errors/index");
22
18
  const path_2 = require("@hubspot/local-dev-lib/path");
@@ -53,7 +49,7 @@ function getProjectConfigPath(dir) {
53
49
  return configPath;
54
50
  }
55
51
  async function getProjectConfig(dir) {
56
- const configPath = await getProjectConfigPath(dir);
52
+ const configPath = getProjectConfigPath(dir);
57
53
  if (!configPath) {
58
54
  return { projectConfig: null, projectDir: null };
59
55
  }
@@ -70,45 +66,6 @@ async function getProjectConfig(dir) {
70
66
  return { projectConfig: null, projectDir: null };
71
67
  }
72
68
  }
73
- async function createProjectConfig(projectPath, projectName, template, templateSource, githubRef) {
74
- const { projectConfig, projectDir } = await getProjectConfig(projectPath);
75
- if (projectConfig) {
76
- logger_1.logger.warn(projectPath === projectDir
77
- ? 'A project already exists in that location.'
78
- : `Found an existing project definition in ${projectDir}.`);
79
- const { shouldContinue } = await (0, promptUtils_1.promptUser)([
80
- {
81
- name: 'shouldContinue',
82
- message: () => {
83
- return projectPath === projectDir
84
- ? 'Do you want to overwrite the existing project definition with a new one?'
85
- : `Continue creating a new project in ${projectPath}?`;
86
- },
87
- type: 'confirm',
88
- default: false,
89
- },
90
- ]);
91
- if (!shouldContinue) {
92
- return false;
93
- }
94
- }
95
- const projectConfigPath = path_1.default.join(projectPath, constants_1.PROJECT_CONFIG_FILE);
96
- logger_1.logger.log(`Creating project config in ${projectPath ? projectPath : 'the current folder'}`);
97
- const hasCustomTemplateSource = Boolean(templateSource);
98
- await (0, github_1.cloneGithubRepo)(templateSource || constants_1.HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, projectPath, {
99
- sourceDir: template.path,
100
- tag: hasCustomTemplateSource ? undefined : githubRef,
101
- });
102
- const _config = JSON.parse(fs_extra_1.default.readFileSync(projectConfigPath).toString());
103
- writeProjectConfig(projectConfigPath, {
104
- ..._config,
105
- name: projectName,
106
- });
107
- if (template.name === 'no-template') {
108
- fs_extra_1.default.ensureDirSync(path_1.default.join(projectPath, 'src'));
109
- }
110
- return true;
111
- }
112
69
  function validateProjectConfig(projectConfig, projectDir) {
113
70
  if (!projectConfig) {
114
71
  logger_1.logger.error((0, lang_1.i18n)(`${i18nKey}.validateProjectConfig.configNotFound`, {
@@ -235,21 +192,3 @@ function logFeedbackMessage(buildId) {
235
192
  logger_1.logger.log((0, lang_1.i18n)(`${i18nKey}.logFeedbackMessage.feedbackMessage`));
236
193
  }
237
194
  }
238
- async function createProjectComponent(component, name, projectComponentsVersion) {
239
- const i18nKey = 'commands.project.subcommands.add';
240
- const componentName = name;
241
- const configInfo = await getProjectConfig();
242
- if (!configInfo.projectDir || !configInfo.projectConfig) {
243
- logger_1.logger.error((0, lang_1.i18n)(`${i18nKey}.error.locationInProject`));
244
- process.exit(exitCodes_1.EXIT_CODES.ERROR);
245
- }
246
- const componentPath = path_1.default.join(configInfo.projectDir, configInfo.projectConfig.srcDir, component.insertPath, componentName);
247
- await (0, github_1.cloneGithubRepo)(constants_1.HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, componentPath, {
248
- sourceDir: component.path,
249
- tag: projectComponentsVersion,
250
- });
251
- }
252
- async function getProjectComponentsByVersion(projectComponentsVersion) {
253
- const config = await (0, github_1.fetchFileFromRepository)(constants_1.HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, 'config.json', projectComponentsVersion);
254
- return config[constants_1.PROJECT_COMPONENT_TYPES.COMPONENTS] || [];
255
- }
@@ -5,5 +5,5 @@ type ProjectUploadResult<T> = {
5
5
  result?: T;
6
6
  uploadError?: unknown;
7
7
  };
8
- export declare function handleProjectUpload<T>(accountId: number, projectConfig: ProjectConfig, projectDir: string, callbackFunc: ProjectUploadCallbackFunction<T>, uploadMessage: string, sendIR?: boolean, skipValidation?: boolean): Promise<ProjectUploadResult<T>>;
8
+ export declare function handleProjectUpload<T>(accountId: number, projectConfig: ProjectConfig, projectDir: string, callbackFunc: ProjectUploadCallbackFunction<T>, uploadMessage: string, sendIR?: boolean): Promise<ProjectUploadResult<T>>;
9
9
  export {};
@@ -58,7 +58,7 @@ async function uploadProjectFiles(accountId, projectName, filePath, uploadMessag
58
58
  }
59
59
  return { buildId, error };
60
60
  }
61
- async function handleProjectUpload(accountId, projectConfig, projectDir, callbackFunc, uploadMessage, sendIR = false, skipValidation = false) {
61
+ async function handleProjectUpload(accountId, projectConfig, projectDir, callbackFunc, uploadMessage, sendIR = false) {
62
62
  const srcDir = path_1.default.resolve(projectDir, projectConfig.srcDir);
63
63
  const filenames = fs_extra_1.default.readdirSync(srcDir);
64
64
  if (!filenames || filenames.length === 0) {
@@ -84,7 +84,7 @@ async function handleProjectUpload(accountId, projectConfig, projectDir, callbac
84
84
  projectSourceDir: path_1.default.join(projectDir, projectConfig.srcDir),
85
85
  platformVersion: projectConfig.platformVersion,
86
86
  accountId,
87
- }, { skipValidation });
87
+ });
88
88
  logger_1.logger.debug(node_util_1.default.inspect(intermediateRepresentation, false, null, true));
89
89
  }
90
90
  catch (e) {
@@ -1,14 +1,12 @@
1
- import { RepoPath } from '@hubspot/local-dev-lib/types/Github';
2
1
  import { ProjectTemplate } from '../../types/Projects';
3
2
  type CreateProjectPromptResponse = {
4
3
  name: string;
5
4
  dest: string;
6
- template: ProjectTemplate;
5
+ projectTemplate?: ProjectTemplate;
7
6
  };
8
- export declare function createProjectPrompt(githubRef: string, promptOptions: {
9
- name: string;
10
- dest: string;
11
- template: string;
12
- templateSource: RepoPath;
13
- }, skipTemplatePrompt?: boolean): Promise<CreateProjectPromptResponse>;
7
+ export declare function createProjectPrompt(promptOptions: {
8
+ name?: string;
9
+ dest?: string;
10
+ template?: string;
11
+ }, projectTemplates?: ProjectTemplate[]): Promise<CreateProjectPromptResponse>;
14
12
  export {};
@@ -7,52 +7,17 @@ exports.createProjectPrompt = createProjectPrompt;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const path_2 = require("@hubspot/local-dev-lib/path");
10
- const github_1 = require("@hubspot/local-dev-lib/github");
11
- const logger_1 = require("@hubspot/local-dev-lib/logger");
12
- const constants_1 = require("../constants");
13
10
  const promptUtils_1 = require("./promptUtils");
14
11
  const lang_1 = require("../lang");
15
- const exitCodes_1 = require("../enums/exitCodes");
16
12
  const i18nKey = 'lib.prompts.createProjectPrompt';
17
- const PROJECT_TEMPLATE_PROPERTIES = ['name', 'label', 'path', 'insertPath'];
18
- function hasAllProperties(projectList) {
19
- return projectList.every(config => PROJECT_TEMPLATE_PROPERTIES.every(p => Object.prototype.hasOwnProperty.call(config, p)));
20
- }
21
- async function createTemplateOptions(templateSource, githubRef) {
22
- const hasCustomTemplateSource = Boolean(templateSource);
23
- const branch = hasCustomTemplateSource
24
- ? constants_1.DEFAULT_PROJECT_TEMPLATE_BRANCH
25
- : githubRef;
26
- let config;
27
- try {
28
- config = await (0, github_1.fetchFileFromRepository)(templateSource || constants_1.HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, 'config.json', branch);
29
- }
30
- catch (e) {
31
- logger_1.logger.error((0, lang_1.i18n)(`${i18nKey}.errors.missingConfigFileTemplateSource`));
32
- process.exit(exitCodes_1.EXIT_CODES.ERROR);
33
- }
34
- if (!config || !config[constants_1.PROJECT_COMPONENT_TYPES.PROJECTS]) {
35
- logger_1.logger.error((0, lang_1.i18n)(`${i18nKey}.errors.noProjectsInConfig`));
36
- process.exit(exitCodes_1.EXIT_CODES.ERROR);
37
- }
38
- if (!hasAllProperties(config[constants_1.PROJECT_COMPONENT_TYPES.PROJECTS])) {
39
- logger_1.logger.error((0, lang_1.i18n)(`${i18nKey}.errors.missingPropertiesInConfig`));
40
- process.exit(exitCodes_1.EXIT_CODES.ERROR);
41
- }
42
- return config[constants_1.PROJECT_COMPONENT_TYPES.PROJECTS];
43
- }
44
- function findTemplate(projectTemplates, templateNameOrLabel) {
13
+ function findTemplateByNameOrLabel(projectTemplates, templateNameOrLabel) {
45
14
  return projectTemplates.find(t => t.name === templateNameOrLabel || t.label === templateNameOrLabel);
46
15
  }
47
- async function createProjectPrompt(githubRef, promptOptions, skipTemplatePrompt = false) {
48
- let projectTemplates = [];
49
- let selectedTemplate;
50
- if (!skipTemplatePrompt) {
51
- projectTemplates = await createTemplateOptions(promptOptions.templateSource, githubRef);
52
- selectedTemplate =
53
- promptOptions.template &&
54
- findTemplate(projectTemplates, promptOptions.template);
55
- }
16
+ async function createProjectPrompt(promptOptions, projectTemplates) {
17
+ const createProjectFromTemplate = !!projectTemplates && projectTemplates.length > 0;
18
+ const providedTemplateIsValid = createProjectFromTemplate &&
19
+ !!promptOptions.template &&
20
+ !!findTemplateByNameOrLabel(projectTemplates, promptOptions.template);
56
21
  const result = await (0, promptUtils_1.promptUser)([
57
22
  {
58
23
  name: 'name',
@@ -70,7 +35,7 @@ async function createProjectPrompt(githubRef, promptOptions, skipTemplatePrompt
70
35
  message: (0, lang_1.i18n)(`${i18nKey}.enterDest`),
71
36
  when: !promptOptions.dest,
72
37
  default: answers => {
73
- const projectName = (0, path_2.sanitizeFileName)(answers.name || promptOptions.name);
38
+ const projectName = (0, path_2.sanitizeFileName)(promptOptions.name || answers.name);
74
39
  return path_1.default.resolve((0, path_2.getCwd)(), projectName);
75
40
  },
76
41
  validate: (input) => {
@@ -90,27 +55,34 @@ async function createProjectPrompt(githubRef, promptOptions, skipTemplatePrompt
90
55
  },
91
56
  },
92
57
  {
93
- name: 'template',
58
+ name: 'projectTemplate',
94
59
  message: () => {
95
- return promptOptions.template &&
96
- !findTemplate(projectTemplates, promptOptions.template)
60
+ return promptOptions.template && !providedTemplateIsValid
97
61
  ? (0, lang_1.i18n)(`${i18nKey}.errors.invalidTemplate`, {
98
62
  template: promptOptions.template,
99
63
  })
100
64
  : (0, lang_1.i18n)(`${i18nKey}.selectTemplate`);
101
65
  },
102
- when: !skipTemplatePrompt && !selectedTemplate,
66
+ when: createProjectFromTemplate && !providedTemplateIsValid,
103
67
  type: 'list',
104
- choices: projectTemplates.map(template => {
105
- return {
106
- name: template.label,
107
- value: template,
108
- };
109
- }),
68
+ choices: createProjectFromTemplate
69
+ ? projectTemplates.map(template => {
70
+ return {
71
+ name: template.label,
72
+ value: template,
73
+ };
74
+ })
75
+ : undefined,
110
76
  },
111
77
  ]);
112
- if (selectedTemplate) {
113
- result.template = selectedTemplate;
78
+ if (!result.name) {
79
+ result.name = promptOptions.name;
80
+ }
81
+ if (!result.dest) {
82
+ result.dest = promptOptions.dest;
83
+ }
84
+ if (providedTemplateIsValid) {
85
+ result.projectTemplate = findTemplateByNameOrLabel(projectTemplates, promptOptions.template);
114
86
  }
115
87
  return result;
116
88
  }
@@ -1,9 +1,9 @@
1
- import { ProjectAddComponentData } from '../../types/Projects';
1
+ import { ComponentTemplate } from '../../types/Projects';
2
2
  type ProjectAddPromptResponse = {
3
- component: ProjectAddComponentData;
3
+ componentTemplate: ComponentTemplate;
4
4
  name: string;
5
5
  };
6
- export declare function projectAddPrompt(components: ProjectAddComponentData[], promptOptions?: {
6
+ export declare function projectAddPrompt(components: ComponentTemplate[], promptOptions?: {
7
7
  name?: string;
8
8
  type?: string;
9
9
  }): Promise<ProjectAddPromptResponse>;
@@ -4,20 +4,23 @@ exports.projectAddPrompt = projectAddPrompt;
4
4
  const promptUtils_1 = require("./promptUtils");
5
5
  const lang_1 = require("../lang");
6
6
  const i18nKey = 'lib.prompts.projectAddPrompt';
7
+ function findComponentByPathOrLabel(components, componentPathOrLabel) {
8
+ return components.find(c => c.path === componentPathOrLabel || c.label === componentPathOrLabel);
9
+ }
7
10
  async function projectAddPrompt(components, promptOptions = {}) {
8
- return (0, promptUtils_1.promptUser)([
11
+ const providedTypeIsValid = !!promptOptions.type &&
12
+ !!findComponentByPathOrLabel(components, promptOptions.type);
13
+ const result = await (0, promptUtils_1.promptUser)([
9
14
  {
10
- name: 'component',
15
+ name: 'componentTemplate',
11
16
  message: () => {
12
- return promptOptions.type &&
13
- !components.find(t => t.path === promptOptions.type)
17
+ return promptOptions.type && !providedTypeIsValid
14
18
  ? (0, lang_1.i18n)(`${i18nKey}.errors.invalidType`, {
15
19
  type: promptOptions.type,
16
20
  })
17
21
  : (0, lang_1.i18n)(`${i18nKey}.selectType`);
18
22
  },
19
- when: !promptOptions.type ||
20
- !components.find(t => t.path === promptOptions.type),
23
+ when: !providedTypeIsValid,
21
24
  type: 'list',
22
25
  choices: components.map(type => {
23
26
  return {
@@ -38,4 +41,11 @@ async function projectAddPrompt(components, promptOptions = {}) {
38
41
  },
39
42
  },
40
43
  ]);
44
+ if (!result.name) {
45
+ result.name = promptOptions.name;
46
+ }
47
+ if (providedTypeIsValid) {
48
+ result.componentTemplate = findComponentByPathOrLabel(components, promptOptions.type);
49
+ }
50
+ return result;
41
51
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "7.0.15-experimental.0",
3
+ "version": "7.0.16-experimental.0",
4
4
  "description": "The official CLI for developing on HubSpot",
5
5
  "license": "Apache-2.0",
6
6
  "repository": "https://github.com/HubSpot/hubspot-cli",
7
7
  "dependencies": {
8
8
  "@hubspot/local-dev-lib": "3.3.1",
9
- "@hubspot/project-parsing-lib": "0.0.6-beta.0",
9
+ "@hubspot/project-parsing-lib": "0.0.6-experimental.0",
10
10
  "@hubspot/serverless-dev-runtime": "7.0.2",
11
11
  "@hubspot/theme-preview-dev-server": "0.0.10",
12
12
  "@hubspot/ui-extensions-dev-server": "0.8.42",
@@ -7,8 +7,8 @@ export type ProjectTemplate = {
7
7
  insertPath: string;
8
8
  };
9
9
  export type ComponentTemplate = {
10
- label: string;
11
10
  path: string;
11
+ label: string;
12
12
  insertPath: string;
13
13
  };
14
14
  export type ProjectConfig = {
@@ -32,11 +32,6 @@ export type ProjectPollStatusFunctionText = {
32
32
  TYPE_KEY: string;
33
33
  SUBTASK_NAME_KEY: string;
34
34
  };
35
- export type ProjectAddComponentData = {
36
- path: string;
37
- label: string;
38
- insertPath: string;
39
- };
40
35
  export type ProjectTemplateRepoConfig = {
41
36
  projects?: ProjectTemplate[];
42
37
  components?: ComponentTemplate[];