@hubspot/cli 5.3.1 → 5.4.1-beta.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.
Files changed (39) hide show
  1. package/bin/cli.js +24 -5
  2. package/commands/__tests__/projects.test.js +105 -0
  3. package/commands/accounts/clean.js +1 -1
  4. package/commands/cms/convertFields.js +13 -7
  5. package/commands/project/__tests__/deploy.test.js +1 -1
  6. package/commands/project/__tests__/installDeps.test.js +168 -0
  7. package/commands/project/__tests__/logs.test.js +305 -0
  8. package/commands/project/add.js +24 -12
  9. package/commands/project/cloneApp.js +13 -21
  10. package/commands/project/deploy.js +4 -1
  11. package/commands/project/dev.js +22 -11
  12. package/commands/project/download.js +6 -3
  13. package/commands/project/installDeps.js +78 -0
  14. package/commands/project/logs.js +80 -242
  15. package/commands/project/migrateApp.js +8 -9
  16. package/commands/project/upload.js +5 -3
  17. package/commands/project/watch.js +3 -9
  18. package/commands/project.js +2 -0
  19. package/commands/sandbox/create.js +1 -0
  20. package/commands/sandbox.js +0 -2
  21. package/lang/en.lyaml +40 -75
  22. package/lib/LocalDevManager.js +1 -22
  23. package/lib/__tests__/dependencyManagement.test.js +245 -0
  24. package/lib/__tests__/projectLogsManager.test.js +210 -0
  25. package/lib/dependencyManagement.js +157 -0
  26. package/lib/errorHandlers/apiErrors.js +1 -3
  27. package/lib/errorHandlers/overrideErrors.js +57 -36
  28. package/lib/localDev.js +25 -16
  29. package/lib/projectLogsManager.js +144 -0
  30. package/lib/projects.js +17 -7
  31. package/lib/projectsWatch.js +2 -5
  32. package/lib/prompts/__tests__/projectsLogsPrompt.test.js +46 -0
  33. package/lib/prompts/createProjectPrompt.js +4 -0
  34. package/lib/prompts/projectAddPrompt.js +4 -21
  35. package/lib/prompts/projectDevTargetAccountPrompt.js +16 -25
  36. package/lib/prompts/projectsLogsPrompt.js +17 -108
  37. package/lib/sandboxSync.js +13 -15
  38. package/package.json +6 -6
  39. package/commands/sandbox/sync.js +0 -225
package/lang/en.lyaml CHANGED
@@ -1,6 +1,10 @@
1
1
  en:
2
2
  commands:
3
3
  generalErrors:
4
+ updateNotify:
5
+ notifyTitle: "Update available"
6
+ cmsUpdateNotification: "{{#bold}}The CMS CLI is now the HubSpot CLI{{/bold}}\n\nTo upgrade, uninstall {{#bold}}{{ packageName }}{{/bold}}\nand then run {{ updateCommand }}"
7
+ cliUpdateNotification: "HubSpot CLI version {{#cyan}}{{#bold}}{currentVersion}{{/bold}}{{/cyan}} is outdated.\nRun {{ updateCommand }} to upgrade to version {{#cyan}}{{#bold}}{latestVersion}{{/bold}}{{/cyan}}"
4
8
  srcIsProject: "\"{{ src }}\" is in a project folder. Did you mean \"hs project {{command}}\"?"
5
9
  setDefaultAccountMoved: "This command has moved. Try `hs accounts use` instead"
6
10
  accounts:
@@ -479,6 +483,7 @@ en:
479
483
  errors:
480
484
  noProjectConfig: "No project detected. Please run this command again from a project directory."
481
485
  invalidProjectComponents: "Projects cannot contain both private and public apps. Move your apps to separate projects before attempting local development."
486
+ noRunnableComponents: "No supported components were found in this project. Run {{ command }} to see a list of available components and add one to your project."
482
487
  parentAccountNotConfigured: "To develop this project locally, run {{ authCommand }} to authenticate the App Developer Account {{ accountId }} associated with {{ accountIdentifier }}."
483
488
  examples:
484
489
  default: "Start local dev for the current project"
@@ -550,9 +555,9 @@ en:
550
555
  describe: "Create a new component within a project"
551
556
  options:
552
557
  name:
553
- describe: "Component name"
558
+ describe: "The name for your newly created component"
554
559
  type:
555
- describe: "The type of component"
560
+ describe: "The path to the component type's location within the hubspot-project-components Github repo: https://github.com/HubSpot/hubspot-project-components"
556
561
  creatingComponent:
557
562
  message: "Adding a new component to your project"
558
563
  success:
@@ -561,6 +566,7 @@ en:
561
566
  locationInProject: "The component location must be within a project folder"
562
567
  examples:
563
568
  default: "Create a component within your project"
569
+ withFlags: "Use --name and --type flags to bypass the prompt."
564
570
  deploy:
565
571
  describe: "Deploy a project build"
566
572
  debug:
@@ -586,16 +592,18 @@ en:
586
592
  logs:
587
593
  describe: "Get execution logs for a serverless function within a project"
588
594
  errors:
589
- invalidAppName: "Could not find app with name \"{{ appName }}\" in project \"{{ projectName }}\""
595
+ noProjectConfig: "No project detected. Run this command again from a project directory."
596
+ failedToFetchProjectDetails: "There was an error fetching project details"
597
+ noFunctionsLinkText: "Visit developer docs"
598
+ noFunctionsInProject: "There aren't any functions in this project\n\t- Run `{{#orange}}hs project logs --help{{/orange}}` to learn more about logs\n\t- {{link}} to learn more about serverless functions"
599
+ noFunctionWithName: "No function with name \"{{ name }}\""
600
+ functionNotDeployed: "The function with name \"{{ name }}\" is not deployed"
590
601
  logs:
591
602
  showingLogs: "Showing logs for:"
592
- hubspotLogsLink: "View private apps in HubSpot"
593
- hubspotLogsDirectLink: "View logs in HubSpot"
603
+ hubspotLogsDirectLink: "View function logs in HubSpot"
594
604
  noLogsFound: "No logs were found for \"{{ name }}\""
595
605
  table:
596
606
  accountHeader: "Account"
597
- projectHeader: "Project"
598
- appHeader: "App"
599
607
  functionHeader: "Function"
600
608
  endpointHeader: "Endpoint"
601
609
  examples:
@@ -612,12 +620,8 @@ en:
612
620
  describe: "Retrieve most recent log only"
613
621
  limit:
614
622
  describe: "Limit the number of logs to output"
615
- project:
616
- describe: "Project name"
617
623
  function:
618
624
  describe: "App function name"
619
- endpoint:
620
- describe: "Public endpoint path"
621
625
  upload:
622
626
  describe: "Upload your project files and create a new build"
623
627
  examples:
@@ -703,6 +707,20 @@ en:
703
707
  describe: "Open Github issues in your browser to report a bug."
704
708
  general:
705
709
  describe: "Open Github issues in your browser to give feedback."
710
+ installDeps:
711
+ help:
712
+ describe: "Install the dependencies for your project, or add a dependency to a subcomponent of a project"
713
+ installAppDepsExample: "Install the dependencies for the project"
714
+ addDepToSubComponentExample: "Install the dependencies to one or more project subcomponents"
715
+ installLocationPrompt: "Choose the project components to install the dependencies:"
716
+ installLocationPromptRequired: "You must choose at least one subcomponent"
717
+ installingDependencies: "Installing dependencies in {{directory}}"
718
+ installationSuccessful: "Installed dependencies in {{directory}}"
719
+ addingDependenciesToLocation: "Installing {{dependencies}} in {{directory}}"
720
+ installingDependenciesFailed: "Installing dependencies for {{directory}} failed"
721
+ noProjectConfig: "No project detected. Run this command from a project directory."
722
+ noPackageJsonInProject: "No dependencies to install. The project {{ projectName }} folder might be missing component or subcomponent files. {{ link }}"
723
+ packageManagerNotInstalled: "This command depends on {{ packageManager }}, install {{#bold}}{{ link }}{{/bold}}"
706
724
  remove:
707
725
  describe: "Delete a file or folder from HubSpot."
708
726
  deleted: "Deleted \"{{ path }}\" from account {{ accountId }}"
@@ -760,30 +778,6 @@ en:
760
778
  options:
761
779
  account:
762
780
  describe: "Account name or id to delete"
763
- sync:
764
- describe: "Sync to a sandbox account"
765
- examples:
766
- default: "Initiates a sync to a sandbox account."
767
- force: "Skips all confirmation prompts when initiating a sync."
768
- info:
769
- developmentSandbox: "This will sync CRM object definitions."
770
- standardSandbox: "This will sync all supported assets.
771
- \nTo sync only specific assets, follow this link: {{#bold}}{{ url }}{{/bold}}"
772
- sync: "\nSync direction:
773
- \n- Target sandbox: {{#cyan}}{{#bold}}{{ sandboxName }}{{/bold}}{{/cyan}}
774
- \n- Source account: {{#bold}}{{ parentAccountName }}{{/bold}}
775
- \n\nRun {{#bold}}hs accounts use{{/bold}} to change your default account and sync to a different target sandbox."
776
- warning:
777
- developmentSandbox: "Syncing will update previously synced object definitions and add new ones from production to your development sandbox. Object definitions that were created in your sandbox will stay the same."
778
- standardSandbox: "Syncing can have a big impact. Updates from your production account may overwrite changes in your standard sandbox. Standard sandboxes are usually shared with other Super Admins."
779
- confirm:
780
- developmentSandbox: "Sync CRM object definitions to {{#cyan}}{{#bold}}{{ sandboxName }}{{/bold}}{{/cyan}} from {{#bold}}{{ parentAccountName }}{{/bold}}?"
781
- standardSandbox: "Sync all supported assets to {{#cyan}}{{#bold}}{{ sandboxName }}{{/bold}}{{/cyan}} from {{#bold}}{{ parentAccountName }}{{/bold}}?"
782
- failure:
783
- invalidAccountType: "Sync must be run in a sandbox account. Your default account is a {{ accountType }}. Run {{#bold}}hs auth{{/bold}} to connect your sandbox account to the CLI or {{#bold}}hs accounts use{{/bold}} to change your default account, then try again."
784
- missingParentPortal: "The production account associated to {{#bold}}{{ sandboxName }}{{/bold}} is not connected to your HubSpot CLI.
785
- \n- Run {{#bold}}hs auth{{/bold}} to connect that account to your terminal, then try again.
786
- \n- Run {{#bold}}hs accounts use{{/bold}} to change your default account, if you want to sync to a different sandbox. Then try again.\n"
787
781
  secrets:
788
782
  describe: "Manage HubSpot secrets."
789
783
  subcommands:
@@ -964,6 +958,10 @@ en:
964
958
  options:
965
959
  options:
966
960
  describe: "Options to pass to javascript fields files"
961
+ errors:
962
+ invalidPath: "The path \"{{ path }}\" specified in the \"--src\" flag is not a path to a file or directory"
963
+ missingSrc: "Please specify the path to your javascript fields file or directory with the --src flag."
964
+
967
965
  lib:
968
966
  process:
969
967
  exitDebug: "Attempting to gracefully exit. Triggered by {{ signal }}"
@@ -975,7 +973,6 @@ en:
975
973
  failedToInitialize: "Missing required arguments to initialize Local Dev"
976
974
  noDeployedBuild: "Your project {{#bold}}{{ projectName }}{{/bold}} exists in {{ accountIdentifier }}, but has no deployed build. Projects must be successfully deployed to be developed locally. Address any build and deploy errors your project may have, then run {{ uploadCommand }} to upload and deploy your project."
977
975
  noComponents: "There are no components in this project."
978
- noRunnableComponents: "No supported components were found under {{#bold}}{{ projectSourceDir }}{{/bold}}. Run {{ command }} to see a list of available components."
979
976
  betaMessage: "HubSpot projects local development"
980
977
  learnMoreLocalDevServer: "Learn more about the projects local dev server"
981
978
  running: "Running {{#bold}}{{ projectName }}{{/bold}} locally on {{ accountIdentifier }}, waiting for changes ..."
@@ -1009,12 +1006,12 @@ en:
1009
1006
  localDev:
1010
1007
  confirmDefaultAccountIsTarget:
1011
1008
  declineDefaultAccountExplanation: "To develop on a different account, run {{ useCommand }} to change your default account, then re-run {{ devCommand }}."
1012
- checkIfAppDevloperAccount: "This project contains a public app. Local development of public apps is only supported on developer accounts and developer test accounts. Change your default account using {{#bold}}`hs accounts use`{{/bold}}, or link a new account with {{#bold}}`hs auth`{{/bold}}."
1013
- checkIfDeveloperTestAccount: "This project contains a public app. The \"--account\" flag must point to a developer test account to develop this project locally. Alternatively, change your default account to an App Developer Account using {{#bold}}`hs accounts use`{{/bold}} and run {{#bold}}`hs project dev`{{/bold}} to set up a new Developer Test Account."
1014
- suggestRecommendedNestedAccount:
1009
+ checkIfAppDevloperAccount: "This project contains a public app. Local development of public apps is only supported on developer accounts and developer test accounts. Change your default account using {{ useCommand }}, or link a new account with {{ authCommand }}."
1010
+ validateAccountOption:
1011
+ invalidPublicAppAccount: "This project contains a public app. The \"--account\" flag must point to a developer test account to develop this project locally. Alternatively, change your default account to an App Developer Account using {{ useCommand }} and run {{ devCommand }} to set up a new Developer Test Account."
1012
+ invalidPrivateAppAccount: "This project contains a private app. The account specified with the \"--account\" flag points to a developer account, which do not support the local development of private apps. Update the \"--account\" flag to point to a standard, sandbox, or developer test account, or change your default account by running {{ useCommand }}."
1015
1013
  nonSandboxWarning: "Testing in a sandbox is strongly recommended. To switch the target account, select an option below or run {{#bold}}`hs accounts use`{{/bold}} before running the command again."
1016
1014
  publicAppNonDeveloperTestAccountWarning: "Local development of public apps is only supported in {{#bold}}developer test accounts{{/bold}}."
1017
- privateAppInAppDeveloperAccountError: "Local development of private apps is not supported in {{#bold}}app developer accounts{{/bold}}"
1018
1015
  createNewProjectForLocalDev:
1019
1016
  projectMustExistExplanation: "The project {{ projectName }} does not exist in the target account {{ accountIdentifier}}. This command requires the project to exist in the target account."
1020
1017
  publicAppProjectMustExistExplanation: "The project {{ projectName }} does not exist in {{ accountIdentifier}}, the app developer account associated with your target account. This command requires the project to exist in this app developer account."
@@ -1110,12 +1107,6 @@ en:
1110
1107
  projectDevCommand:
1111
1108
  command: "hs project dev"
1112
1109
  message: "Run {{ command }} to set up your test environment and start local development"
1113
- sandboxSyncDevelopmentCommand:
1114
- command: "hs sandbox sync"
1115
- message: "Run {{ command }} to to update CRM object definitions in your sandbox"
1116
- sandboxSyncStandardCommand:
1117
- command: "hs sandbox sync"
1118
- message: "Run {{ command }} to to update all supported assets to your sandbox from production"
1119
1110
  sampleProjects:
1120
1111
  linkText: "HubSpot's sample projects"
1121
1112
  url: "https://developers.hubspot.com/docs/platform/sample-projects?utm_source=cli&utm_content=project_create_whats_next"
@@ -1160,16 +1151,7 @@ en:
1160
1151
  confirmDefaultAccount: "Continue testing on {{#bold}}{{ accountName }} ({{ accountType }}){{/bold}}? (Y/n)"
1161
1152
  confirmUseExistingDeveloperTestAccount: "Continue with {{ accountName }}? This account isn't currently connected to the HubSpot CLI. By continuing, you'll be prompted to generate a personal access key and connect it."
1162
1153
  projectLogsPrompt:
1163
- projectName:
1164
- message: "[--project] Enter the project name:"
1165
- error: "Project name is required"
1166
- logType:
1167
- message: "Select the type of serverless function"
1168
- function: "App function"
1169
- endpoint: "Public endpoint"
1170
- appName: "[--app] Select the app"
1171
- functionName: "[--function] Enter the app function name:"
1172
- endpointName: "[--endpoint] Enter the public endpoint path:"
1154
+ functionName: "[--function] Select function in {{#bold}}{{projectName}}{{/bold}} project"
1173
1155
  setAsDefaultAccountPrompt:
1174
1156
  setAsDefaultAccountMessage: "Set this account as the default?"
1175
1157
  setAsDefaultAccount: "Account \"{{ accountName }}\" set as the default account"
@@ -1411,8 +1393,7 @@ en:
1411
1393
  successDevSbInfo: "Initiated sync of object definitions from production to {{ accountName }}. It may take some time. {{ url }}"
1412
1394
  failure:
1413
1395
  invalidUser: "Couldn't sync {{ accountName }} because your account has been removed from {{ parentAccountName }} or your permission set doesn't allow you to sync the sandbox. To update your permissions, contact a super admin in {{ parentAccountName }}."
1414
- missingScopes: "Couldnt run the sync because there are scopes missing in your production account. To update scopes, deactivate your current personal access key for {{#bold}}{{ accountName }}{{/bold}}, and generate a new one. Then run `hs auth` to update the CLI with the new key."
1415
- syncInProgress: "Couldn’t run the sync because there’s another sync in progress. Wait for the current sync to finish and then try again. To check the sync status, visit the sync activity log: {{ url }}."
1396
+ syncInProgress: "Couldn't run the sync because there's another sync in progress. Wait for the current sync to finish and then try again. To check the sync status, visit the sync activity log: {{ url }}."
1416
1397
  notSuperAdmin: "Couldn't run the sync because you are not a super admin in {{ account }}. Ask the account owner for super admin access to the sandbox."
1417
1398
  objectNotFound: "Couldn't sync the sandbox because {{#bold}}{{ account }}{{/bold}} may have been deleted through the UI. Run {{#bold}}hs sandbox delete{{/bold}} to remove this account from the config. "
1418
1399
  errorHandlers:
@@ -1427,21 +1408,6 @@ en:
1427
1408
  errorOccurred: "An error occurred while {{ fileAction }} {{ filepath }}."
1428
1409
  errorExplanation: "This is the result of a system error: {{ errorMessage }}"
1429
1410
  apiErrors:
1430
- messageDetail: "{{ request }} in account {{ accountId }}"
1431
- unableToUpload: 'Unable to upload "{{ payload }}.'
1432
- codes:
1433
- 400: "The {{ messageDetail }} was bad."
1434
- 401: "The {{ messageDetail }} was unauthorized."
1435
- 403MissingScope: "Couldn't run the project command because there are scopes missing in your production account. To update scopes, deactivate your current personal access key for {{ accountId }}, and generate a new one. Then run `hs auth` to update the CLI with the new key."
1436
- 403Gating: "The current target account {{ accountId }} does not have access to HubSpot projects. To opt in to the CRM Development Beta and use projects, visit https://app.hubspot.com/l/product-updates/in-beta?update=13899236."
1437
- 403: "The {{ messageDetail }} was forbidden."
1438
- 404Request: 'The {{ action }} failed because "{{ request }}" was not found in account {{ accountId }}.'
1439
- 404: "The {{ messageDetail }} was not found."
1440
- 429: "The {{ messageDetail }} surpassed the rate limit. Retry in one minute."
1441
- 503: "The {{ messageDetail }} could not be handled at this time. Please try again or visit https://help.hubspot.com/ to submit a ticket or contact HubSpot Support if the issue persists."
1442
- 500Generic: "The {{ messageDetail }} failed due to a server error. Please try again or visit https://help.hubspot.com/ to submit a ticket or contact HubSpot Support if the issue persists."
1443
- 400Generic: "The {{ messageDetail }} failed due to a client error."
1444
- generic: "The {{ messageDetail }} failed."
1445
1411
  verifyAccessKeyAndUserAccess:
1446
1412
  fetchScopeDataError: "Error verifying access of scopeGroup {{ scopeGroup }}: {{ error }}"
1447
1413
  portalMissingScope: "Your account does not have access to this action. Talk to an account admin to request it."
@@ -1456,5 +1422,4 @@ en:
1456
1422
  updateProject: "Please update your project to the latest version and try again."
1457
1423
  docsLink: "Projects platform versioning (BETA)"
1458
1424
  betaLink: "For more info, see {{ docsLink }}."
1459
-
1460
-
1425
+ missingScopeError: "Couldn't execute the {{ request }} because the access key for {{ accountName }} is missing required scopes. To update scopes, run {{ authCommand }}. Then deactivate the existing key and generate a new one that includes the missing scopes."
@@ -63,7 +63,7 @@ class LocalDevManager {
63
63
  this.isGithubLinked = options.isGithubLinked;
64
64
  this.watcher = null;
65
65
  this.uploadWarnings = {};
66
- this.runnableComponents = this.getRunnableComponents(options.components);
66
+ this.runnableComponents = options.runnableComponents;
67
67
  this.activeApp = null;
68
68
  this.activePublicAppData = null;
69
69
  this.env = options.env;
@@ -78,27 +78,6 @@ class LocalDevManager {
78
78
  logger.log(i18n(`${i18nKey}.failedToInitialize`));
79
79
  process.exit(EXIT_CODES.ERROR);
80
80
  }
81
-
82
- // The project is empty, there is nothing to run locally
83
- if (!options.components.length) {
84
- logger.error(i18n(`${i18nKey}.noComponents`));
85
- process.exit(EXIT_CODES.SUCCESS);
86
- }
87
-
88
- // The project does not contain any components that support local development
89
- if (!this.runnableComponents.length) {
90
- logger.error(
91
- i18n(`${i18nKey}.noRunnableComponents`, {
92
- projectSourceDir: this.projectSourceDir,
93
- command: uiCommandReference('hs project add'),
94
- })
95
- );
96
- process.exit(EXIT_CODES.SUCCESS);
97
- }
98
- }
99
-
100
- getRunnableComponents(components) {
101
- return components.filter(component => component.runnable);
102
81
  }
103
82
 
104
83
  async setActiveApp(appUid) {
@@ -0,0 +1,245 @@
1
+ jest.mock('../projects');
2
+ jest.mock('@hubspot/local-dev-lib/logger');
3
+ jest.mock('@hubspot/local-dev-lib/fs');
4
+ jest.mock('../ui/SpinniesManager');
5
+ jest.mock('fs', () => ({
6
+ ...jest.requireActual('fs'),
7
+ existsSync: jest.fn().mockReturnValue(true),
8
+ }));
9
+
10
+ const util = require('util');
11
+ const {
12
+ isGloballyInstalled,
13
+ installPackages,
14
+ getProjectPackageJsonLocations,
15
+ } = require('../dependencyManagement');
16
+ const { walk } = require('@hubspot/local-dev-lib/fs');
17
+ const path = require('path');
18
+ const { getProjectConfig } = require('../projects');
19
+ const SpinniesManager = require('../ui/SpinniesManager');
20
+ const { existsSync } = require('fs');
21
+
22
+ describe('cli/lib/dependencyManagement', () => {
23
+ let execMock;
24
+
25
+ const projectDir = path.join('path', 'to', 'project');
26
+ const srcDir = 'src';
27
+ const appDir = path.join(projectDir, srcDir, 'app');
28
+ const appFunctionsDir = path.join(appDir, 'app.functions');
29
+ const extensionsDir = path.join(appDir, 'exensions');
30
+ const projectName = 'super cool test project';
31
+
32
+ beforeEach(() => {
33
+ execMock = jest.fn();
34
+ util.promisify = jest.fn().mockReturnValue(execMock);
35
+ getProjectConfig.mockResolvedValue({
36
+ projectDir,
37
+ projectConfig: {
38
+ srcDir,
39
+ name: projectName,
40
+ },
41
+ });
42
+ });
43
+
44
+ describe('isGloballyInstalled', () => {
45
+ it('should return true when exec is successful', async () => {
46
+ const actual = await isGloballyInstalled('npm');
47
+ expect(actual).toBe(true);
48
+ expect(execMock).toHaveBeenCalledTimes(1);
49
+ expect(execMock).toHaveBeenCalledWith('npm --version');
50
+ });
51
+
52
+ it('should return false when exec is unsuccessful', async () => {
53
+ execMock = jest.fn().mockImplementationOnce(() => {
54
+ throw new Error('unsuccessful');
55
+ });
56
+ util.promisify = jest.fn().mockReturnValueOnce(execMock);
57
+ const actual = await isGloballyInstalled('npm');
58
+ expect(actual).toBe(false);
59
+ expect(execMock).toHaveBeenCalledTimes(1);
60
+ expect(execMock).toHaveBeenCalledWith('npm --version');
61
+ });
62
+ });
63
+
64
+ describe('installPackages', () => {
65
+ it('should setup a loading spinner', async () => {
66
+ const packages = ['package1', 'package2'];
67
+ const installLocations = ['src/app/app.functions', 'src/app/extensions'];
68
+ await installPackages({ packages, installLocations });
69
+ expect(SpinniesManager.init).toHaveBeenCalledTimes(
70
+ installLocations.length
71
+ );
72
+ expect(SpinniesManager.add).toHaveBeenCalledTimes(
73
+ installLocations.length
74
+ );
75
+ expect(SpinniesManager.succeed).toHaveBeenCalledTimes(
76
+ installLocations.length
77
+ );
78
+ });
79
+
80
+ it('should install the provided packages in all the provided install locations', async () => {
81
+ const packages = ['package1', 'package2'];
82
+ const installLocations = ['src/app/app.functions', 'src/app/extensions'];
83
+ await installPackages({ packages, installLocations });
84
+
85
+ expect(execMock).toHaveBeenCalledTimes(installLocations.length);
86
+ expect(SpinniesManager.add).toHaveBeenCalledTimes(
87
+ installLocations.length
88
+ );
89
+ expect(SpinniesManager.succeed).toHaveBeenCalledTimes(
90
+ installLocations.length
91
+ );
92
+
93
+ for (const location of installLocations) {
94
+ expect(execMock).toHaveBeenCalledWith(
95
+ `npm --prefix=${location} install package1 package2`
96
+ );
97
+ expect(SpinniesManager.add).toHaveBeenCalledWith(
98
+ `installingDependencies-${location}`,
99
+ {
100
+ text: `Installing [package1, package2] in ${location}`,
101
+ }
102
+ );
103
+ expect(SpinniesManager.succeed).toHaveBeenCalledWith(
104
+ `installingDependencies-${location}`,
105
+ {
106
+ text: `Installed dependencies in ${location}`,
107
+ }
108
+ );
109
+ }
110
+ });
111
+
112
+ it('should use the provided install locations', async () => {
113
+ const installLocations = ['src/app/app.functions', 'src/app/extensions'];
114
+ await installPackages({ installLocations });
115
+ expect(execMock).toHaveBeenCalledTimes(installLocations.length);
116
+ expect(execMock).toHaveBeenCalledWith(
117
+ `npm --prefix=${installLocations[0]} install`
118
+ );
119
+ expect(execMock).toHaveBeenCalledWith(
120
+ `npm --prefix=${installLocations[1]} install`
121
+ );
122
+ });
123
+
124
+ it('should locate the projects package.json files when install locations is not provided', async () => {
125
+ const installLocations = [
126
+ path.join(appFunctionsDir, 'package.json'),
127
+ path.join(extensionsDir, 'package.json'),
128
+ ];
129
+
130
+ walk.mockResolvedValue(installLocations);
131
+
132
+ getProjectConfig.mockResolvedValue({
133
+ projectDir,
134
+ projectConfig: {
135
+ srcDir,
136
+ },
137
+ });
138
+
139
+ await installPackages({});
140
+ // Its called once per each install location, plus once to check if npm installed
141
+ expect(execMock).toHaveBeenCalledTimes(installLocations.length + 1);
142
+ expect(execMock).toHaveBeenCalledWith(
143
+ `npm --prefix=${appFunctionsDir} install`
144
+ );
145
+ expect(execMock).toHaveBeenCalledWith(
146
+ `npm --prefix=${extensionsDir} install`
147
+ );
148
+ });
149
+
150
+ it('should throw an error when installing the dependencies fails', async () => {
151
+ execMock = jest.fn().mockImplementation(command => {
152
+ if (command !== 'npm --version') {
153
+ throw new Error('OH NO');
154
+ }
155
+ });
156
+
157
+ util.promisify = jest.fn().mockReturnValue(execMock);
158
+
159
+ const installLocations = [
160
+ path.join(appFunctionsDir, 'package.json'),
161
+ path.join(extensionsDir, 'package.json'),
162
+ ];
163
+
164
+ walk.mockResolvedValue(installLocations);
165
+
166
+ getProjectConfig.mockResolvedValue({
167
+ projectDir,
168
+ projectConfig: {
169
+ srcDir,
170
+ },
171
+ });
172
+
173
+ await expect(() => installPackages({})).rejects.toThrowError(
174
+ `Installing dependencies for ${appFunctionsDir} failed`
175
+ );
176
+
177
+ expect(SpinniesManager.fail).toHaveBeenCalledTimes(
178
+ installLocations.length
179
+ );
180
+
181
+ expect(SpinniesManager.fail).toHaveBeenCalledWith(
182
+ `installingDependencies-${appFunctionsDir}`,
183
+ {
184
+ text: `Installing dependencies for ${appFunctionsDir} failed`,
185
+ }
186
+ );
187
+ expect(SpinniesManager.fail).toHaveBeenCalledWith(
188
+ `installingDependencies-${extensionsDir}`,
189
+ {
190
+ text: `Installing dependencies for ${extensionsDir} failed`,
191
+ }
192
+ );
193
+ });
194
+ });
195
+
196
+ describe('getProjectPackageJsonFiles', () => {
197
+ it('should throw an error when ran outside the boundary of a project', async () => {
198
+ getProjectConfig.mockResolvedValue({});
199
+ await expect(() => getProjectPackageJsonLocations()).rejects.toThrowError(
200
+ 'No project detected. Run this command from a project directory.'
201
+ );
202
+ });
203
+
204
+ it('should throw an error if npm is not globally installed', async () => {
205
+ execMock = jest.fn().mockImplementation(() => {
206
+ throw new Error('OH NO');
207
+ });
208
+ util.promisify = jest.fn().mockReturnValue(execMock);
209
+ await expect(() => getProjectPackageJsonLocations()).rejects.toThrowError(
210
+ /This command depends on npm, install/
211
+ );
212
+ });
213
+
214
+ it('should throw an error if the project directory does not exist', async () => {
215
+ existsSync.mockReturnValueOnce(false);
216
+ await expect(() => getProjectPackageJsonLocations()).rejects.toThrowError(
217
+ `No dependencies to install. The project ${projectName} folder might be missing component or subcomponent files. Learn how to create a project from scratch.: https://developers.hubspot.com/beta-docs/guides/crm/intro/create-a-project`
218
+ );
219
+ });
220
+
221
+ it('should ignore package.json files in certain directories', async () => {
222
+ const nodeModulesDir = path.join(appDir, 'node_modules');
223
+ const viteDir = path.join(appDir, '.vite');
224
+ const installLocations = [
225
+ path.join(appFunctionsDir, 'package.json'),
226
+ path.join(extensionsDir, 'package.json'),
227
+ path.join(viteDir, 'package.json'),
228
+ path.join(nodeModulesDir, 'package.json'),
229
+ ];
230
+
231
+ walk.mockResolvedValue(installLocations);
232
+
233
+ const actual = await getProjectPackageJsonLocations();
234
+ expect(actual).toEqual([appFunctionsDir, extensionsDir]);
235
+ });
236
+
237
+ it('should throw an error if no package.json files are found', async () => {
238
+ walk.mockResolvedValue([]);
239
+
240
+ await expect(() => getProjectPackageJsonLocations()).rejects.toThrowError(
241
+ `No dependencies to install. The project ${projectName} folder might be missing component or subcomponent files. Learn how to create a project from scratch.: https://developers.hubspot.com/beta-docs/guides/crm/intro/create-a-project`
242
+ );
243
+ });
244
+ });
245
+ });