@hubspot/cli 7.9.0-beta.1 → 7.9.0-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.
- package/commands/__tests__/project.test.js +2 -0
- package/commands/account/createOverride.js +2 -12
- package/commands/account/removeOverride.js +2 -10
- package/commands/cms/theme/preview.js +1 -4
- package/commands/getStarted.js +7 -19
- package/commands/project/__tests__/deploy.test.js +4 -3
- package/commands/project/__tests__/updateDeps.test.d.ts +1 -0
- package/commands/project/__tests__/updateDeps.test.js +142 -0
- package/commands/project/create.js +0 -1
- package/commands/project/updateDeps.d.ts +6 -0
- package/commands/project/updateDeps.js +80 -0
- package/commands/project.js +2 -0
- package/commands/testAccount/create.js +1 -1
- package/lang/en.d.ts +30 -15
- package/lang/en.js +32 -18
- package/lib/__tests__/dependencyManagement.test.js +273 -1
- package/lib/__tests__/npm.test.js +1 -1
- package/lib/__tests__/sandboxSync.test.js +1 -1
- package/lib/__tests__/usageTracking.test.js +2 -2
- package/lib/commonOpts.js +2 -5
- package/lib/configMigrate.js +3 -3
- package/lib/dependencyManagement.d.ts +8 -2
- package/lib/dependencyManagement.js +75 -12
- package/lib/doctor/DiagnosticInfoBuilder.js +1 -1
- package/lib/doctor/Doctor.js +1 -1
- package/lib/doctor/__tests__/DiagnosticInfoBuilder.test.js +4 -2
- package/lib/doctor/__tests__/Doctor.test.js +1 -1
- package/lib/jsonLoader.d.ts +14 -0
- package/lib/jsonLoader.js +60 -0
- package/lib/middleware/__test__/requestMiddleware.test.js +1 -1
- package/lib/middleware/autoUpdateMiddleware.js +1 -1
- package/lib/middleware/commandTargetingUtils.js +1 -0
- package/lib/middleware/fireAlarmMiddleware.js +1 -1
- package/lib/middleware/notificationsMiddleware.js +1 -1
- package/lib/middleware/requestMiddleware.js +1 -1
- package/lib/npm.d.ts +3 -0
- package/lib/npm.js +7 -1
- package/lib/projects/__tests__/AppDevModeInterface.test.js +3 -0
- package/lib/projects/__tests__/platformVersion.test.js +5 -1
- package/lib/projects/create/__tests__/v2.test.js +20 -14
- package/lib/projects/create/v2.js +8 -13
- package/lib/projects/localDev/LocalDevLogger.js +2 -2
- package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -3
- package/lib/projects/localDev/LocalDevWebsocketServer.js +1 -1
- package/lib/projects/platformVersion.js +1 -1
- package/lib/prompts/promptUtils.d.ts +8 -0
- package/lib/prompts/promptUtils.js +7 -1
- package/lib/prompts/selectProjectTemplatePrompt.js +4 -0
- package/lib/sandboxSync.js +1 -1
- package/lib/usageTracking.js +2 -2
- package/mcp-server/tools/cms/HsCreateFunctionTool.js +2 -2
- package/mcp-server/tools/cms/HsCreateModuleTool.js +2 -2
- package/mcp-server/tools/cms/HsCreateTemplateTool.js +2 -2
- package/mcp-server/tools/cms/HsFunctionLogsTool.js +2 -9
- package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
- package/mcp-server/tools/cms/HsListTool.js +1 -1
- package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +7 -4
- package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +7 -3
- package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +7 -4
- package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +5 -1
- package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +8 -3
- package/mcp-server/tools/cms/__tests__/HsListTool.test.js +8 -3
- package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +4 -1
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +6 -5
- package/mcp-server/tools/project/CreateProjectTool.js +2 -2
- package/mcp-server/tools/project/DeployProjectTool.d.ts +4 -1
- package/mcp-server/tools/project/DeployProjectTool.js +4 -3
- package/mcp-server/tools/project/DocFetchTool.d.ts +4 -1
- package/mcp-server/tools/project/DocFetchTool.js +7 -6
- package/mcp-server/tools/project/DocsSearchTool.js +5 -5
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +4 -1
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +7 -5
- package/mcp-server/tools/project/GetApplicationInfoTool.d.ts +8 -2
- package/mcp-server/tools/project/GetApplicationInfoTool.js +7 -6
- package/mcp-server/tools/project/GetConfigValuesTool.js +4 -4
- package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +4 -1
- package/mcp-server/tools/project/GuidedWalkthroughTool.js +6 -14
- package/mcp-server/tools/project/UploadProjectTools.d.ts +4 -1
- package/mcp-server/tools/project/UploadProjectTools.js +4 -3
- package/mcp-server/tools/project/ValidateProjectTool.d.ts +4 -1
- package/mcp-server/tools/project/ValidateProjectTool.js +5 -4
- package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +6 -1
- package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +7 -3
- package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +7 -2
- package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +8 -3
- package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +6 -2
- package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +8 -3
- package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +9 -5
- package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +6 -2
- package/mcp-server/tools/project/__tests__/GuidedWalkthroughTool.test.js +43 -13
- package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +8 -2
- package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +8 -2
- package/mcp-server/utils/__tests__/content.test.d.ts +1 -0
- package/mcp-server/utils/__tests__/content.test.js +166 -0
- package/mcp-server/utils/__tests__/feedbackTracking.test.d.ts +1 -0
- package/mcp-server/utils/__tests__/feedbackTracking.test.js +121 -0
- package/mcp-server/utils/content.d.ts +1 -1
- package/mcp-server/utils/content.js +8 -1
- package/mcp-server/utils/feedbackTracking.d.ts +1 -0
- package/mcp-server/utils/feedbackTracking.js +41 -0
- package/package.json +2 -2
- package/commands/project/__tests__/fixtures/exampleProject.json +0 -33
- package/lang/en.lyaml +0 -1508
- package/lib/lang.d.ts +0 -8
- package/lib/lang.js +0 -72
package/lang/en.js
CHANGED
|
@@ -51,12 +51,11 @@ export const commands = {
|
|
|
51
51
|
uploadProject: (accountName) => `Would you like to upload this project to account "${accountName}" now?`,
|
|
52
52
|
projectCreated: {
|
|
53
53
|
title: chalk.bold('Next steps:'),
|
|
54
|
-
description: `Let's prepare and upload your project to HubSpot.\nYou can use ${uiCommandReference('hs project
|
|
54
|
+
description: `Let's prepare and upload your project to HubSpot.\nYou can use ${uiCommandReference('hs project upload')} to ${chalk.bold('upload')} your project.`,
|
|
55
55
|
},
|
|
56
56
|
},
|
|
57
57
|
logs: {
|
|
58
58
|
appSelected: `We'll create a new project with a sample app for you.\nProjects are what you can use to create apps with HubSpot.\nUsually you'll use the ${uiCommandReference('hs project create')} command, but we'll go ahead and make one now.`,
|
|
59
|
-
dependenciesInstalled: 'Dependencies installed successfully.',
|
|
60
59
|
uploadingProject: 'Uploading your project to HubSpot...',
|
|
61
60
|
uploadSuccess: 'Project uploaded successfully!',
|
|
62
61
|
developerOverviewLink: 'Open this link to navigate to your HubSpot developer portal',
|
|
@@ -64,7 +63,6 @@ export const commands = {
|
|
|
64
63
|
errors: {
|
|
65
64
|
uploadFailed: 'Failed to upload project to HubSpot.',
|
|
66
65
|
configFileNotFound: 'Could not find project configuration for upload.',
|
|
67
|
-
installDepsFailed: 'Failed to install dependencies.',
|
|
68
66
|
},
|
|
69
67
|
},
|
|
70
68
|
completion: {
|
|
@@ -1382,7 +1380,7 @@ export const commands = {
|
|
|
1382
1380
|
welcomeMessage: `\n${chalk.bold('Welcome to HubSpot Developer Projects!')}`,
|
|
1383
1381
|
},
|
|
1384
1382
|
prompts: {
|
|
1385
|
-
parentComponents: '[--project-base]
|
|
1383
|
+
parentComponents: '[--project-base] Choose what to include in your project:',
|
|
1386
1384
|
emptyProject: 'Empty Project',
|
|
1387
1385
|
app: 'App',
|
|
1388
1386
|
},
|
|
@@ -1526,9 +1524,9 @@ export const commands = {
|
|
|
1526
1524
|
failedToDownloadComponent: 'Failed to download project. Please try again later.',
|
|
1527
1525
|
invalidComponentType: (componentType) => `'${componentType}' is not a valid project component type.`,
|
|
1528
1526
|
maxExceeded: (maxCount) => `This project has the maximum allowed(${maxCount})`,
|
|
1529
|
-
authTypeNotAllowed: (authType) => `
|
|
1530
|
-
distributionNotAllowed: (dist) => `
|
|
1531
|
-
portalDoesNotHaveAccessToThisFeature: (
|
|
1527
|
+
authTypeNotAllowed: (authType) => `Requires auth type '${authType}'.`,
|
|
1528
|
+
distributionNotAllowed: (dist) => `Requires distribution '${dist}'.`,
|
|
1529
|
+
portalDoesNotHaveAccessToThisFeature: () => "This account doesn't have access to this feature.",
|
|
1532
1530
|
locationInProject: 'This command must be run from within a project directory.',
|
|
1533
1531
|
failedToFetchComponentList: 'Failed to fetch the list of available features. Please try again later.',
|
|
1534
1532
|
projectContainsPublicApp: 'This project contains a public app. This command is currently only compatible with projects that contain private apps.',
|
|
@@ -1765,6 +1763,22 @@ export const commands = {
|
|
|
1765
1763
|
noPackageJsonInProject: (projectName) => `No dependencies to install. The project ${projectName} folder might be missing component or subcomponent files. ${uiLink('Learn how to create a project from scratch', 'https://developers.hubspot.com/docs/apps/developer-platform/build-apps/create-an-app#customize-a-new-project-using-the-cli')}`,
|
|
1766
1764
|
packageManagerNotInstalled: (packageManager) => `This command depends on ${packageManager}, install ${uiLink(packageManager, 'https://docs.npmjs.com/downloading-and-installing-node-js-and-npm')}`,
|
|
1767
1765
|
},
|
|
1766
|
+
updateDeps: {
|
|
1767
|
+
help: {
|
|
1768
|
+
describe: 'Update the npm dependencies for your project, or update specific dependencies in a subcomponent of a project.',
|
|
1769
|
+
updateAppDepsExample: 'Update the dependencies for the project',
|
|
1770
|
+
updateDepToSubComponentExample: 'Update the npm dependencies in one or more project subcomponents',
|
|
1771
|
+
},
|
|
1772
|
+
installLocationPrompt: 'Choose which project components you would like to update the dependencies for:',
|
|
1773
|
+
installLocationPromptRequired: 'You must choose at least one subcomponent',
|
|
1774
|
+
updatingDependencies: (directory) => `Updating dependencies in ${directory}`,
|
|
1775
|
+
updateSuccessful: (directory) => `Updated dependencies in ${directory}`,
|
|
1776
|
+
updatingDependenciesToLocation: (dependencies, directory) => `Updating ${dependencies} in ${directory}`,
|
|
1777
|
+
updatingDependenciesFailed: (directory) => `Updating dependencies for ${directory} failed`,
|
|
1778
|
+
noProjectConfig: 'No project detected. Run this command from a project directory.',
|
|
1779
|
+
noPackageJsonInProject: (projectName) => `No dependencies to update. The project ${projectName} folder might be missing component or subcomponent files. ${uiLink('Learn how to create a project from scratch', 'https://developers.hubspot.com/docs/apps/developer-platform/build-apps/create-an-app#customize-a-new-project-using-the-cli')}`,
|
|
1780
|
+
packageManagerNotInstalled: (packageManager) => `This command depends on ${packageManager}, install ${uiLink(packageManager, 'https://docs.npmjs.com/downloading-and-installing-node-js-and-npm')}`,
|
|
1781
|
+
},
|
|
1768
1782
|
validate: {
|
|
1769
1783
|
describe: 'Validate the project before uploading',
|
|
1770
1784
|
mustBeRanWithinAProject: 'This command must be run from within a project directory.',
|
|
@@ -2082,7 +2096,7 @@ export const commands = {
|
|
|
2082
2096
|
},
|
|
2083
2097
|
},
|
|
2084
2098
|
create: {
|
|
2085
|
-
describe:
|
|
2099
|
+
describe: `Create a test account from scratch or from a config file. Use ${uiCommandReference('hs test-account create-config')} to generate a config file. \n${uiLink('Learn more', 'https://developers.hubspot.com/docs/developer-tooling/local-development/configurable-test-accounts')}`,
|
|
2086
2100
|
configPathPrompt: '[--config-path] Enter the path to the test account config: ',
|
|
2087
2101
|
createTestAccountFromConfigPrompt: 'How would you like to create your test account?',
|
|
2088
2102
|
createFromConfigOption: 'Create test account from config file',
|
|
@@ -2765,8 +2779,8 @@ export const lib = {
|
|
|
2765
2779
|
failedToInitialize: 'Missing required arguments to initialize Local Dev',
|
|
2766
2780
|
noDeployedBuild: (projectName, accountIdentifier, uploadCommand) => `Your project ${chalk.bold(projectName)} 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.`,
|
|
2767
2781
|
noComponents: 'There are no components in this project.',
|
|
2768
|
-
|
|
2769
|
-
learnMoreLocalDevServer: uiLink('Learn more about the projects local dev server', 'https://developers.hubspot.com/docs/
|
|
2782
|
+
headerMessage: 'HubSpot projects local development',
|
|
2783
|
+
learnMoreLocalDevServer: uiLink('Learn more about the projects local dev server', 'https://developers.hubspot.com/docs/developer-tooling/local-development/hubspot-cli/project-commands'),
|
|
2770
2784
|
running: (projectName, accountIdentifier) => chalk.hex(UI_COLORS.SORBET)(`Running ${chalk.bold(projectName)} locally on ${accountIdentifier}, waiting for changes ...`),
|
|
2771
2785
|
quitHelper: `Press ${chalk.bold('q')} to stop the local dev server`,
|
|
2772
2786
|
viewProjectLink: (name, accountId) => uiLink('View project in HubSpot', getProjectDetailUrl(name, accountId) || ''),
|
|
@@ -2949,8 +2963,8 @@ export const lib = {
|
|
|
2949
2963
|
prompt: {
|
|
2950
2964
|
marketPlaceDistribution: 'On the HubSpot marketplace',
|
|
2951
2965
|
privateDistribution: 'Privately',
|
|
2952
|
-
distribution: '[--distribution]
|
|
2953
|
-
auth: '[--auth]
|
|
2966
|
+
distribution: '[--distribution] Choose how to distribute your application:',
|
|
2967
|
+
auth: '[--auth] Choose your authentication type:',
|
|
2954
2968
|
staticAuth: 'Static Auth',
|
|
2955
2969
|
oauth: 'OAuth',
|
|
2956
2970
|
},
|
|
@@ -2964,7 +2978,7 @@ export const lib = {
|
|
|
2964
2978
|
},
|
|
2965
2979
|
},
|
|
2966
2980
|
add: {
|
|
2967
|
-
nothingAdded: 'No features added.',
|
|
2981
|
+
nothingAdded: 'No features were added to the project. Use the space bar to select features from the list.',
|
|
2968
2982
|
},
|
|
2969
2983
|
updateHsMetaFilesWithAutoGeneratedFields: {
|
|
2970
2984
|
header: 'Created the following components and features:',
|
|
@@ -3355,8 +3369,8 @@ export const lib = {
|
|
|
3355
3369
|
},
|
|
3356
3370
|
},
|
|
3357
3371
|
projectNameAndDestPrompt: {
|
|
3358
|
-
enterName: '[--name]
|
|
3359
|
-
enterDest: '[--dest]
|
|
3372
|
+
enterName: '[--name] Enter your project name:',
|
|
3373
|
+
enterDest: '[--dest] Choose where to create the project:',
|
|
3360
3374
|
errors: {
|
|
3361
3375
|
nameRequired: 'A project name is required',
|
|
3362
3376
|
destRequired: 'A project dest is required',
|
|
@@ -3366,7 +3380,7 @@ export const lib = {
|
|
|
3366
3380
|
},
|
|
3367
3381
|
selectProjectTemplatePrompt: {
|
|
3368
3382
|
selectTemplate: '[--template] Choose a project template: ',
|
|
3369
|
-
features: '[--features]
|
|
3383
|
+
features: '[--features] Choose which features to add:',
|
|
3370
3384
|
errors: {
|
|
3371
3385
|
invalidTemplate: (template) => `[--template] Could not find template "${template}". Please choose an available template:`,
|
|
3372
3386
|
projectTemplateRequired: 'Project template is required when projectTemplates is provided',
|
|
@@ -3400,8 +3414,8 @@ export const lib = {
|
|
|
3400
3414
|
},
|
|
3401
3415
|
},
|
|
3402
3416
|
projectAddPrompt: {
|
|
3403
|
-
selectType: '[--type]
|
|
3404
|
-
selectFeatures: '[--features]
|
|
3417
|
+
selectType: '[--type] Choose which features to add: ',
|
|
3418
|
+
selectFeatures: '[--features] Choose which features to add: ',
|
|
3405
3419
|
enterName: '[--name] Give your component a name: ',
|
|
3406
3420
|
errors: {
|
|
3407
3421
|
nameRequired: 'A component name is required',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import util from 'util';
|
|
2
|
-
import { installPackages, getProjectPackageJsonLocations, } from '../dependencyManagement.js';
|
|
2
|
+
import { installPackages, updatePackages, getProjectPackageJsonLocations, isPackageInstalled, } from '../dependencyManagement.js';
|
|
3
3
|
import { walk } from '@hubspot/local-dev-lib/fs';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { getProjectConfig } from '../projects/config.js';
|
|
@@ -105,6 +105,54 @@ describe('lib/dependencyManagement', () => {
|
|
|
105
105
|
cwd: extensionsDir,
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
|
+
it('should install packages as dev dependencies when dev flag is true', async () => {
|
|
109
|
+
const packages = ['eslint', 'prettier'];
|
|
110
|
+
await installPackages({ packages, installLocations, dev: true });
|
|
111
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
112
|
+
for (const location of installLocations) {
|
|
113
|
+
expect(execMock).toHaveBeenCalledWith(`npm install --save-dev eslint prettier`, {
|
|
114
|
+
cwd: location,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
it('should install packages as regular dependencies when dev flag is false', async () => {
|
|
119
|
+
const packages = ['react', 'react-dom'];
|
|
120
|
+
await installPackages({ packages, installLocations, dev: false });
|
|
121
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
122
|
+
for (const location of installLocations) {
|
|
123
|
+
expect(execMock).toHaveBeenCalledWith(`npm install react react-dom`, {
|
|
124
|
+
cwd: location,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
it('should install packages as regular dependencies when dev flag is not provided', async () => {
|
|
129
|
+
const packages = ['axios'];
|
|
130
|
+
await installPackages({ packages, installLocations });
|
|
131
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
132
|
+
for (const location of installLocations) {
|
|
133
|
+
expect(execMock).toHaveBeenCalledWith(`npm install axios`, {
|
|
134
|
+
cwd: location,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
it('should not use --save-dev flag when dev is true but no packages are provided', async () => {
|
|
139
|
+
await installPackages({ installLocations, dev: true });
|
|
140
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
141
|
+
for (const location of installLocations) {
|
|
142
|
+
expect(execMock).toHaveBeenCalledWith(`npm install `, {
|
|
143
|
+
cwd: location,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
it('should not use --save-dev flag when dev is true but packages array is empty', async () => {
|
|
148
|
+
await installPackages({ packages: [], installLocations, dev: true });
|
|
149
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
150
|
+
for (const location of installLocations) {
|
|
151
|
+
expect(execMock).toHaveBeenCalledWith(`npm install `, {
|
|
152
|
+
cwd: location,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
108
156
|
it('should throw an error when installing the dependencies fails', async () => {
|
|
109
157
|
execMock = vi.fn().mockImplementation(command => {
|
|
110
158
|
if (command === 'npm --version') {
|
|
@@ -133,6 +181,92 @@ describe('lib/dependencyManagement', () => {
|
|
|
133
181
|
});
|
|
134
182
|
});
|
|
135
183
|
});
|
|
184
|
+
describe('updatePackages()', () => {
|
|
185
|
+
it('should setup a loading spinner', async () => {
|
|
186
|
+
const packages = ['package1', 'package2'];
|
|
187
|
+
await updatePackages({ packages, installLocations });
|
|
188
|
+
expect(SpinniesManager.init).toHaveBeenCalledTimes(installLocations.length);
|
|
189
|
+
expect(SpinniesManager.add).toHaveBeenCalledTimes(installLocations.length);
|
|
190
|
+
expect(SpinniesManager.succeed).toHaveBeenCalledTimes(installLocations.length);
|
|
191
|
+
});
|
|
192
|
+
it('should update the provided packages in all the provided install locations', async () => {
|
|
193
|
+
const packages = ['package1', 'package2'];
|
|
194
|
+
await updatePackages({ packages, installLocations });
|
|
195
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
196
|
+
expect(SpinniesManager.add).toHaveBeenCalledTimes(installLocations.length);
|
|
197
|
+
expect(SpinniesManager.succeed).toHaveBeenCalledTimes(installLocations.length);
|
|
198
|
+
for (const location of installLocations) {
|
|
199
|
+
expect(execMock).toHaveBeenCalledWith(`npm update package1 package2`, {
|
|
200
|
+
cwd: location,
|
|
201
|
+
});
|
|
202
|
+
expect(SpinniesManager.add).toHaveBeenCalledWith(`updatingDependencies-${location}`, {
|
|
203
|
+
text: `Updating [package1, package2] in ${location}`,
|
|
204
|
+
});
|
|
205
|
+
expect(SpinniesManager.succeed).toHaveBeenCalledWith(`updatingDependencies-${location}`, {
|
|
206
|
+
text: `Updated dependencies in ${location}`,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
it('should use the provided install locations', async () => {
|
|
211
|
+
await updatePackages({ installLocations });
|
|
212
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
213
|
+
expect(execMock).toHaveBeenCalledWith(`npm update `, {
|
|
214
|
+
cwd: appFunctionsDir,
|
|
215
|
+
});
|
|
216
|
+
expect(execMock).toHaveBeenCalledWith(`npm update `, {
|
|
217
|
+
cwd: extensionsDir,
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
it('should locate the projects package.json files when install locations is not provided', async () => {
|
|
221
|
+
const installLocations = [
|
|
222
|
+
path.join(appFunctionsDir, 'package.json'),
|
|
223
|
+
path.join(extensionsDir, 'package.json'),
|
|
224
|
+
];
|
|
225
|
+
mockedWalk.mockResolvedValue(installLocations);
|
|
226
|
+
mockedGetProjectConfig.mockResolvedValue({
|
|
227
|
+
projectDir,
|
|
228
|
+
projectConfig: {
|
|
229
|
+
srcDir,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
await updatePackages({});
|
|
233
|
+
// It's called once per each install location, plus once to check if npm installed
|
|
234
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length + 1);
|
|
235
|
+
expect(execMock).toHaveBeenCalledWith(`npm update `, {
|
|
236
|
+
cwd: appFunctionsDir,
|
|
237
|
+
});
|
|
238
|
+
expect(execMock).toHaveBeenCalledWith(`npm update `, {
|
|
239
|
+
cwd: extensionsDir,
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
it('should throw an error when updating the dependencies fails', async () => {
|
|
243
|
+
execMock = vi.fn().mockImplementation(command => {
|
|
244
|
+
if (command === 'npm --version') {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
throw new Error('OH NO');
|
|
248
|
+
});
|
|
249
|
+
util.promisify = mockedPromisify(execMock);
|
|
250
|
+
// Mock walk to return the directory paths instead of package.json paths
|
|
251
|
+
mockedWalk.mockResolvedValue([appFunctionsDir, extensionsDir]);
|
|
252
|
+
mockedFs.existsSync.mockImplementation(filePath => {
|
|
253
|
+
const pathStr = filePath.toString();
|
|
254
|
+
if (pathStr === projectDir ||
|
|
255
|
+
pathStr === path.join(projectDir, srcDir)) {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
});
|
|
260
|
+
await expect(() => updatePackages({ installLocations: [appFunctionsDir, extensionsDir] })).rejects.toThrowError(`Updating dependencies for ${appFunctionsDir} failed`);
|
|
261
|
+
expect(SpinniesManager.fail).toHaveBeenCalledTimes(installLocations.length);
|
|
262
|
+
expect(SpinniesManager.fail).toHaveBeenCalledWith(`updatingDependencies-${appFunctionsDir}`, {
|
|
263
|
+
text: `Updating dependencies for ${appFunctionsDir} failed`,
|
|
264
|
+
});
|
|
265
|
+
expect(SpinniesManager.fail).toHaveBeenCalledWith(`updatingDependencies-${extensionsDir}`, {
|
|
266
|
+
text: `Updating dependencies for ${extensionsDir} failed`,
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
136
270
|
describe('getProjectPackageJsonFiles()', () => {
|
|
137
271
|
it('should throw an error when ran outside the boundary of a project', async () => {
|
|
138
272
|
mockedGetProjectConfig.mockResolvedValue({});
|
|
@@ -149,6 +283,30 @@ describe('lib/dependencyManagement', () => {
|
|
|
149
283
|
mockedFs.existsSync.mockReturnValueOnce(false);
|
|
150
284
|
await expect(() => getProjectPackageJsonLocations()).rejects.toThrowError(new RegExp(`No dependencies to install. The project ${projectName} folder might be missing component or subcomponent files.`));
|
|
151
285
|
});
|
|
286
|
+
it('should throw "install" error message when isUpdate=false and no package.json files found', async () => {
|
|
287
|
+
mockedWalk.mockResolvedValue([]);
|
|
288
|
+
mockedFs.existsSync.mockImplementation(filePath => {
|
|
289
|
+
const pathStr = filePath.toString();
|
|
290
|
+
if (pathStr === projectDir ||
|
|
291
|
+
pathStr === path.join(projectDir, srcDir)) {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
return false;
|
|
295
|
+
});
|
|
296
|
+
await expect(() => getProjectPackageJsonLocations(undefined, false)).rejects.toThrowError(new RegExp(`No dependencies to install. The project ${projectName} folder might be missing component or subcomponent files.`));
|
|
297
|
+
});
|
|
298
|
+
it('should throw "update" error message when isUpdate=true and no package.json files found', async () => {
|
|
299
|
+
mockedWalk.mockResolvedValue([]);
|
|
300
|
+
mockedFs.existsSync.mockImplementation(filePath => {
|
|
301
|
+
const pathStr = filePath.toString();
|
|
302
|
+
if (pathStr === projectDir ||
|
|
303
|
+
pathStr === path.join(projectDir, srcDir)) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
return false;
|
|
307
|
+
});
|
|
308
|
+
await expect(() => getProjectPackageJsonLocations(undefined, true)).rejects.toThrowError(new RegExp(`No dependencies to update. The project ${projectName} folder might be missing component or subcomponent files.`));
|
|
309
|
+
});
|
|
152
310
|
it('should ignore package.json files in certain directories', async () => {
|
|
153
311
|
const nodeModulesDir = path.join(appDir, 'node_modules');
|
|
154
312
|
const viteDir = path.join(appDir, '.vite');
|
|
@@ -172,4 +330,118 @@ describe('lib/dependencyManagement', () => {
|
|
|
172
330
|
expect(actual).toEqual([appFunctionsDir, extensionsDir]);
|
|
173
331
|
});
|
|
174
332
|
});
|
|
333
|
+
describe('isPackageInstalled()', () => {
|
|
334
|
+
const testDir = '/test/directory';
|
|
335
|
+
const readFileSyncSpy = vi.spyOn(fs, 'readFileSync');
|
|
336
|
+
const existsSyncSpy = vi.spyOn(fs, 'existsSync');
|
|
337
|
+
function mockNodeModulesExists(packageName, exists = true) {
|
|
338
|
+
existsSyncSpy.mockImplementation(filePath => {
|
|
339
|
+
const pathStr = filePath.toString();
|
|
340
|
+
return (exists && pathStr === path.join(testDir, 'node_modules', packageName));
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
beforeEach(() => {
|
|
344
|
+
vi.clearAllMocks();
|
|
345
|
+
readFileSyncSpy.mockReset();
|
|
346
|
+
existsSyncSpy.mockReset();
|
|
347
|
+
});
|
|
348
|
+
it('should return true if package is in dependencies and in node_modules', () => {
|
|
349
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
350
|
+
dependencies: {
|
|
351
|
+
eslint: '^9.0.0',
|
|
352
|
+
},
|
|
353
|
+
}));
|
|
354
|
+
mockNodeModulesExists('eslint', true);
|
|
355
|
+
const result = isPackageInstalled(testDir, 'eslint');
|
|
356
|
+
expect(result).toBe(true);
|
|
357
|
+
expect(readFileSyncSpy).toHaveBeenCalledWith(path.join(testDir, 'package.json'), 'utf-8');
|
|
358
|
+
expect(existsSyncSpy).toHaveBeenCalledWith(path.join(testDir, 'node_modules', 'eslint'));
|
|
359
|
+
});
|
|
360
|
+
it('should return true if package is in devDependencies and in node_modules', () => {
|
|
361
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
362
|
+
devDependencies: {
|
|
363
|
+
prettier: '^3.0.0',
|
|
364
|
+
},
|
|
365
|
+
}));
|
|
366
|
+
mockNodeModulesExists('prettier', true);
|
|
367
|
+
const result = isPackageInstalled(testDir, 'prettier');
|
|
368
|
+
expect(result).toBe(true);
|
|
369
|
+
});
|
|
370
|
+
it('should return false if package is in package.json but not in node_modules', () => {
|
|
371
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
372
|
+
dependencies: {
|
|
373
|
+
react: '^18.0.0',
|
|
374
|
+
},
|
|
375
|
+
}));
|
|
376
|
+
mockNodeModulesExists('react', false);
|
|
377
|
+
const result = isPackageInstalled(testDir, 'react');
|
|
378
|
+
expect(result).toBe(false);
|
|
379
|
+
});
|
|
380
|
+
it('should return false if package is not in package.json but is in node_modules', () => {
|
|
381
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
382
|
+
dependencies: {
|
|
383
|
+
typescript: '^5.0.0',
|
|
384
|
+
},
|
|
385
|
+
}));
|
|
386
|
+
mockNodeModulesExists('lodash', true);
|
|
387
|
+
const result = isPackageInstalled(testDir, 'lodash');
|
|
388
|
+
expect(result).toBe(false);
|
|
389
|
+
});
|
|
390
|
+
it('should return false if package is not in package.json and not in node_modules', () => {
|
|
391
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
392
|
+
dependencies: {},
|
|
393
|
+
}));
|
|
394
|
+
mockNodeModulesExists('nonexistent-package', false);
|
|
395
|
+
const result = isPackageInstalled(testDir, 'nonexistent-package');
|
|
396
|
+
expect(result).toBe(false);
|
|
397
|
+
});
|
|
398
|
+
it('should return false if package.json cannot be read', () => {
|
|
399
|
+
readFileSyncSpy.mockImplementationOnce(() => {
|
|
400
|
+
throw new Error('File not found');
|
|
401
|
+
});
|
|
402
|
+
const result = isPackageInstalled(testDir, 'eslint');
|
|
403
|
+
expect(result).toBe(false);
|
|
404
|
+
});
|
|
405
|
+
it('should return false if package.json has invalid JSON', () => {
|
|
406
|
+
readFileSyncSpy.mockReturnValueOnce('invalid json{');
|
|
407
|
+
const result = isPackageInstalled(testDir, 'eslint');
|
|
408
|
+
expect(result).toBe(false);
|
|
409
|
+
});
|
|
410
|
+
it('should return false if checking node_modules throws an error', () => {
|
|
411
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
412
|
+
dependencies: {
|
|
413
|
+
eslint: '^9.0.0',
|
|
414
|
+
},
|
|
415
|
+
}));
|
|
416
|
+
existsSyncSpy.mockImplementation(() => {
|
|
417
|
+
throw new Error('Permission denied');
|
|
418
|
+
});
|
|
419
|
+
const result = isPackageInstalled(testDir, 'eslint');
|
|
420
|
+
expect(result).toBe(false);
|
|
421
|
+
});
|
|
422
|
+
it('should handle scoped packages correctly', () => {
|
|
423
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
424
|
+
dependencies: {
|
|
425
|
+
'@typescript-eslint/parser': '^8.0.0',
|
|
426
|
+
},
|
|
427
|
+
}));
|
|
428
|
+
mockNodeModulesExists('@typescript-eslint/parser', true);
|
|
429
|
+
const result = isPackageInstalled(testDir, '@typescript-eslint/parser');
|
|
430
|
+
expect(result).toBe(true);
|
|
431
|
+
expect(existsSyncSpy).toHaveBeenCalledWith(path.join(testDir, 'node_modules', '@typescript-eslint/parser'));
|
|
432
|
+
});
|
|
433
|
+
it('should check both dependencies and devDependencies', () => {
|
|
434
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
435
|
+
dependencies: {
|
|
436
|
+
react: '^18.0.0',
|
|
437
|
+
},
|
|
438
|
+
devDependencies: {
|
|
439
|
+
eslint: '^9.0.0',
|
|
440
|
+
},
|
|
441
|
+
}));
|
|
442
|
+
mockNodeModulesExists('eslint', true);
|
|
443
|
+
const result = isPackageInstalled(testDir, 'eslint');
|
|
444
|
+
expect(result).toBe(true);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
175
447
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import util from 'util';
|
|
2
2
|
import { isGloballyInstalled, getLatestCliVersion, DEFAULT_PACKAGE_MANAGER, } from '../npm.js';
|
|
3
|
-
import pkg from '
|
|
3
|
+
import { pkg } from '../jsonLoader.js';
|
|
4
4
|
vi.mock('../../ui/logger.js');
|
|
5
5
|
vi.mock('../ui/SpinniesManager');
|
|
6
6
|
describe('lib/npm', () => {
|
|
@@ -59,7 +59,7 @@ describe('lib/sandboxSync', () => {
|
|
|
59
59
|
it('throws error when account IDs are missing', async () => {
|
|
60
60
|
mockedGetAccountId.mockReset();
|
|
61
61
|
mockedGetAccountId.mockReturnValue(null);
|
|
62
|
-
const errorRegex = new RegExp(`
|
|
62
|
+
const errorRegex = new RegExp(`because your account has been removed from`);
|
|
63
63
|
await expect(syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks)).rejects.toThrow(errorRegex);
|
|
64
64
|
});
|
|
65
65
|
it('handles sync in progress error', async () => {
|
|
@@ -3,8 +3,8 @@ import { isTrackingAllowed, getAccountConfig, } from '@hubspot/local-dev-lib/con
|
|
|
3
3
|
import { API_KEY_AUTH_METHOD } from '@hubspot/local-dev-lib/constants/auth';
|
|
4
4
|
import { uiLogger } from '../ui/logger.js';
|
|
5
5
|
import { trackCommandUsage, trackHelpUsage, trackConvertFieldsUsage, trackAuthAction, trackCommandMetadataUsage, } from '../usageTracking.js';
|
|
6
|
-
import
|
|
7
|
-
const version =
|
|
6
|
+
import { pkg } from '../jsonLoader.js';
|
|
7
|
+
const version = pkg.version;
|
|
8
8
|
vi.mock('@hubspot/local-dev-lib/trackUsage');
|
|
9
9
|
vi.mock('@hubspot/local-dev-lib/config');
|
|
10
10
|
vi.mock('../ui/logger.js');
|
package/lib/commonOpts.js
CHANGED
|
@@ -149,16 +149,13 @@ export function setCLILogLevel(options) {
|
|
|
149
149
|
setLogLevel(LOG_LEVEL.ERROR);
|
|
150
150
|
SpinniesManager.setDisableOutput(true);
|
|
151
151
|
}
|
|
152
|
-
else if (debug) {
|
|
152
|
+
else if (debug || networkDebug) {
|
|
153
153
|
setLogLevel(LOG_LEVEL.DEBUG);
|
|
154
|
+
process.env.HUBSPOT_NETWORK_LOGGING = 'true';
|
|
154
155
|
}
|
|
155
156
|
else {
|
|
156
157
|
setLogLevel(LOG_LEVEL.LOG);
|
|
157
158
|
}
|
|
158
|
-
if (networkDebug) {
|
|
159
|
-
process.env.HUBSPOT_NETWORK_LOGGING = 'true';
|
|
160
|
-
setLogLevel(LOG_LEVEL.DEBUG);
|
|
161
|
-
}
|
|
162
159
|
}
|
|
163
160
|
export function getCommandName(argv) {
|
|
164
161
|
return String(argv && argv._ && argv._[0]) || '';
|
package/lib/configMigrate.js
CHANGED
|
@@ -24,7 +24,7 @@ async function promptNewAccountName(account, globalConfig, renamedAccounts) {
|
|
|
24
24
|
if (value === account.name) {
|
|
25
25
|
return lib.configMigrate.handleAccountNameConflicts.errors.sameName;
|
|
26
26
|
}
|
|
27
|
-
const existingAccount = globalConfig.accounts
|
|
27
|
+
const existingAccount = globalConfig.accounts?.some(acc => acc.name === value);
|
|
28
28
|
const renamedAccount = renamedAccounts.some(acc => acc.name === value);
|
|
29
29
|
if (existingAccount || renamedAccount) {
|
|
30
30
|
return lib.configMigrate.handleAccountNameConflicts.errors.nameAlreadyInConfig(value);
|
|
@@ -85,8 +85,8 @@ async function handleAccountNameConflicts(globalConfig, deprecatedConfig, force)
|
|
|
85
85
|
const accountsWithConflictsToRemove = new Set();
|
|
86
86
|
const renamedAccounts = [];
|
|
87
87
|
const accountsNotYetInGlobal = deprecatedConfig.portals.filter(portal => portal.portalId &&
|
|
88
|
-
!globalConfig.accounts
|
|
89
|
-
const accountsWithConflicts = accountsNotYetInGlobal.filter(localAccount => globalConfig.accounts
|
|
88
|
+
!globalConfig.accounts?.some(acc => acc.accountId === portal.portalId));
|
|
89
|
+
const accountsWithConflicts = accountsNotYetInGlobal.filter(localAccount => globalConfig.accounts?.some(globalAccount => globalAccount.name === localAccount.name));
|
|
90
90
|
if (accountsWithConflicts.length > 0) {
|
|
91
91
|
uiLogger.log('');
|
|
92
92
|
uiLogger.warn(lib.configMigrate.handleAccountNameConflicts.warnings.accountNameConflictMessage(accountsWithConflicts.length));
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
export declare function installPackages({ packages, installLocations, }: {
|
|
1
|
+
export declare function installPackages({ packages, installLocations, dev, }: {
|
|
2
2
|
packages?: string[];
|
|
3
3
|
installLocations?: string[];
|
|
4
|
+
dev?: boolean;
|
|
4
5
|
}): Promise<void>;
|
|
5
|
-
export declare function
|
|
6
|
+
export declare function updatePackages({ packages, installLocations, }: {
|
|
7
|
+
packages?: string[];
|
|
8
|
+
installLocations?: string[];
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
export declare function getProjectPackageJsonLocations(dir?: string, isUpdate?: boolean): Promise<string[]>;
|
|
11
|
+
export declare function isPackageInstalled(directory: string, packageName: string): boolean;
|
|
6
12
|
export declare function hasMissingPackages(directory: string): Promise<boolean>;
|