@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
|
@@ -6,19 +6,21 @@ import { walk } from '@hubspot/local-dev-lib/fs';
|
|
|
6
6
|
import { getProjectConfig } from './projects/config.js';
|
|
7
7
|
import { commands } from '../lang/en.js';
|
|
8
8
|
import SpinniesManager from './ui/SpinniesManager.js';
|
|
9
|
-
import { isGloballyInstalled, executeInstall, DEFAULT_PACKAGE_MANAGER, } from './npm.js';
|
|
9
|
+
import { isGloballyInstalled, executeInstall, executeUpdate, DEFAULT_PACKAGE_MANAGER, } from './npm.js';
|
|
10
10
|
class NoPackageJsonFilesError extends Error {
|
|
11
|
-
constructor(projectName) {
|
|
12
|
-
super(
|
|
11
|
+
constructor(projectName, isUpdate = false) {
|
|
12
|
+
super(isUpdate
|
|
13
|
+
? commands.project.updateDeps.noPackageJsonInProject(projectName)
|
|
14
|
+
: commands.project.installDeps.noPackageJsonInProject(projectName));
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
|
-
export async function installPackages({ packages, installLocations, }) {
|
|
17
|
+
export async function installPackages({ packages, installLocations, dev = false, }) {
|
|
16
18
|
const installDirs = installLocations || (await getProjectPackageJsonLocations());
|
|
17
19
|
await Promise.all(installDirs.map(async (dir) => {
|
|
18
|
-
await installPackagesInDirectory(dir, packages);
|
|
20
|
+
await installPackagesInDirectory(dir, packages, dev);
|
|
19
21
|
}));
|
|
20
22
|
}
|
|
21
|
-
async function installPackagesInDirectory(directory, packages) {
|
|
23
|
+
async function installPackagesInDirectory(directory, packages, dev = false) {
|
|
22
24
|
const spinner = `installingDependencies-${directory}`;
|
|
23
25
|
const relativeDir = path.relative(process.cwd(), directory);
|
|
24
26
|
SpinniesManager.init();
|
|
@@ -28,7 +30,8 @@ async function installPackagesInDirectory(directory, packages) {
|
|
|
28
30
|
: commands.project.installDeps.installingDependencies(relativeDir),
|
|
29
31
|
});
|
|
30
32
|
try {
|
|
31
|
-
|
|
33
|
+
const flags = dev && packages && packages.length > 0 ? '--save-dev' : null;
|
|
34
|
+
await executeInstall(packages, flags, { cwd: directory });
|
|
32
35
|
SpinniesManager.succeed(spinner, {
|
|
33
36
|
text: commands.project.installDeps.installationSuccessful(relativeDir),
|
|
34
37
|
});
|
|
@@ -42,26 +45,60 @@ async function installPackagesInDirectory(directory, packages) {
|
|
|
42
45
|
});
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
|
-
export async function
|
|
48
|
+
export async function updatePackages({ packages, installLocations, }) {
|
|
49
|
+
const installDirs = installLocations || (await getProjectPackageJsonLocations(undefined, true));
|
|
50
|
+
await Promise.all(installDirs.map(async (dir) => {
|
|
51
|
+
await updatePackagesInDirectory(dir, packages);
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
async function updatePackagesInDirectory(directory, packages) {
|
|
55
|
+
const spinner = `updatingDependencies-${directory}`;
|
|
56
|
+
const relativeDir = path.relative(process.cwd(), directory);
|
|
57
|
+
SpinniesManager.init();
|
|
58
|
+
SpinniesManager.add(spinner, {
|
|
59
|
+
text: packages && packages.length
|
|
60
|
+
? commands.project.updateDeps.updatingDependenciesToLocation(`[${packages.join(', ')}]`, relativeDir)
|
|
61
|
+
: commands.project.updateDeps.updatingDependencies(relativeDir),
|
|
62
|
+
});
|
|
63
|
+
try {
|
|
64
|
+
await executeUpdate(packages, null, { cwd: directory });
|
|
65
|
+
SpinniesManager.succeed(spinner, {
|
|
66
|
+
text: commands.project.updateDeps.updateSuccessful(relativeDir),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
SpinniesManager.fail(spinner, {
|
|
71
|
+
text: commands.project.updateDeps.updatingDependenciesFailed(relativeDir),
|
|
72
|
+
});
|
|
73
|
+
throw new Error(commands.project.updateDeps.updatingDependenciesFailed(relativeDir), {
|
|
74
|
+
cause: e,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export async function getProjectPackageJsonLocations(dir, isUpdate = false) {
|
|
46
79
|
const projectConfig = await getProjectConfig(dir);
|
|
47
80
|
if (!projectConfig ||
|
|
48
81
|
!projectConfig.projectDir ||
|
|
49
82
|
!projectConfig.projectConfig) {
|
|
50
|
-
throw new Error(
|
|
83
|
+
throw new Error(isUpdate
|
|
84
|
+
? commands.project.updateDeps.noProjectConfig
|
|
85
|
+
: commands.project.installDeps.noProjectConfig);
|
|
51
86
|
}
|
|
52
87
|
const { projectDir, projectConfig: { srcDir, name }, } = projectConfig;
|
|
53
88
|
if (!(await isGloballyInstalled(DEFAULT_PACKAGE_MANAGER))) {
|
|
54
|
-
throw new Error(
|
|
89
|
+
throw new Error(isUpdate
|
|
90
|
+
? commands.project.updateDeps.packageManagerNotInstalled(DEFAULT_PACKAGE_MANAGER)
|
|
91
|
+
: commands.project.installDeps.packageManagerNotInstalled(DEFAULT_PACKAGE_MANAGER));
|
|
55
92
|
}
|
|
56
93
|
if (!fs.existsSync(projectConfig.projectDir) ||
|
|
57
94
|
!fs.existsSync(path.join(projectDir, srcDir))) {
|
|
58
|
-
throw new NoPackageJsonFilesError(name);
|
|
95
|
+
throw new NoPackageJsonFilesError(name, isUpdate);
|
|
59
96
|
}
|
|
60
97
|
const packageJsonFiles = (await walk(path.join(projectDir, srcDir))).filter(file => file.includes('package.json') &&
|
|
61
98
|
!file.includes('node_modules') &&
|
|
62
99
|
!file.includes('.vite'));
|
|
63
100
|
if (packageJsonFiles.length === 0) {
|
|
64
|
-
throw new NoPackageJsonFilesError(name);
|
|
101
|
+
throw new NoPackageJsonFilesError(name, isUpdate);
|
|
65
102
|
}
|
|
66
103
|
const packageParentDirs = [];
|
|
67
104
|
packageJsonFiles.forEach(packageJsonFile => {
|
|
@@ -70,6 +107,32 @@ export async function getProjectPackageJsonLocations(dir) {
|
|
|
70
107
|
});
|
|
71
108
|
return packageParentDirs;
|
|
72
109
|
}
|
|
110
|
+
function isPackageInPackageJson(directory, packageName) {
|
|
111
|
+
const packageJsonPath = path.join(directory, 'package.json');
|
|
112
|
+
try {
|
|
113
|
+
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
114
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
115
|
+
return !!((packageJson.dependencies && packageJson.dependencies[packageName]) ||
|
|
116
|
+
(packageJson.devDependencies && packageJson.devDependencies[packageName]));
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function isPackageInNodeModules(directory, packageName) {
|
|
123
|
+
const packagePath = path.join(directory, 'node_modules', packageName);
|
|
124
|
+
try {
|
|
125
|
+
return fs.existsSync(packagePath);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export function isPackageInstalled(directory, packageName) {
|
|
132
|
+
const inPackageJson = isPackageInPackageJson(directory, packageName);
|
|
133
|
+
const actuallyInstalled = isPackageInNodeModules(directory, packageName);
|
|
134
|
+
return inPackageJson && actuallyInstalled;
|
|
135
|
+
}
|
|
73
136
|
export async function hasMissingPackages(directory) {
|
|
74
137
|
const exec = util.promisify(execAsync);
|
|
75
138
|
const { stdout } = await exec(`npm install --ignore-scripts --dry-run`, {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getProjectConfig } from '../projects/config.js';
|
|
2
2
|
import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import pkg from '
|
|
4
|
+
import { pkg } from '../jsonLoader.js';
|
|
5
5
|
import { uiLogger } from '../ui/logger.js';
|
|
6
6
|
import { getAccountId, getDefaultAccountOverrideFilePath, isConfigFlagEnabled, } from '@hubspot/local-dev-lib/config';
|
|
7
7
|
import { getAccountConfig, getConfigPath } from '@hubspot/local-dev-lib/config';
|
package/lib/doctor/Doctor.js
CHANGED
|
@@ -13,7 +13,7 @@ import { PORT_MANAGER_SERVER_PORT } from '@hubspot/local-dev-lib/constants/ports
|
|
|
13
13
|
import { accessTokenForPersonalAccessKey, authorizedScopesForPortalAndUser, scopesOnAccessToken, } from '@hubspot/local-dev-lib/personalAccessKey';
|
|
14
14
|
import { isSpecifiedError } from '@hubspot/local-dev-lib/errors/index';
|
|
15
15
|
import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
|
|
16
|
-
import pkg from '
|
|
16
|
+
import { pkg } from '../jsonLoader.js';
|
|
17
17
|
import { lib } from '../../lang/en.js';
|
|
18
18
|
import { uiLink } from '../ui/index.js';
|
|
19
19
|
const minMajorNodeVersion = 18;
|
|
@@ -6,11 +6,13 @@ vi.mock('@hubspot/local-dev-lib/personalAccessKey');
|
|
|
6
6
|
vi.mock('../../projects/config');
|
|
7
7
|
vi.mock('@hubspot/local-dev-lib/api/projects');
|
|
8
8
|
vi.mock('util');
|
|
9
|
-
vi.mock('
|
|
9
|
+
vi.mock('../../jsonLoader.js', () => {
|
|
10
10
|
return {
|
|
11
|
-
|
|
11
|
+
pkg: {
|
|
12
|
+
name: '@hubspot/cli',
|
|
12
13
|
version: '1.0.0',
|
|
13
14
|
},
|
|
15
|
+
loadJson: vi.fn(),
|
|
14
16
|
};
|
|
15
17
|
});
|
|
16
18
|
import { DiagnosticInfoBuilder, } from '../DiagnosticInfoBuilder.js';
|
|
@@ -359,7 +359,7 @@ describe('lib/doctor/Doctor', () => {
|
|
|
359
359
|
expect(doctor.diagnosis?.addProjectSection).toHaveBeenCalledWith({
|
|
360
360
|
type: 'warning',
|
|
361
361
|
message: 'Port 8080 is in use',
|
|
362
|
-
secondaryMessaging: expect.stringMatching(/Make sure it is available before running
|
|
362
|
+
secondaryMessaging: expect.stringMatching(/Make sure it is available before running/),
|
|
363
363
|
});
|
|
364
364
|
});
|
|
365
365
|
it('should add success section if port is available', async () => {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic JSON loader that works in both test (lib/) and production (dist/lib/) environments.
|
|
3
|
+
* Automatically resolves paths relative to the caller's location.
|
|
4
|
+
*
|
|
5
|
+
* @param importMetaUrl - Pass import.meta.url from the calling file
|
|
6
|
+
* @param relativePath - Path to JSON file relative to the caller (e.g., '../package.json', './fixtures/data.json')
|
|
7
|
+
* @returns The loaded JSON object
|
|
8
|
+
*/
|
|
9
|
+
export declare function loadJson<T = unknown>(importMetaUrl: string, relativePath: string): T;
|
|
10
|
+
export declare const pkg: {
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
name: string;
|
|
13
|
+
version: string;
|
|
14
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// NOTE: Can be switched back to standard import with min node version 23
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
/**
|
|
7
|
+
* Generic JSON loader that works in both test (lib/) and production (dist/lib/) environments.
|
|
8
|
+
* Automatically resolves paths relative to the caller's location.
|
|
9
|
+
*
|
|
10
|
+
* @param importMetaUrl - Pass import.meta.url from the calling file
|
|
11
|
+
* @param relativePath - Path to JSON file relative to the caller (e.g., '../package.json', './fixtures/data.json')
|
|
12
|
+
* @returns The loaded JSON object
|
|
13
|
+
*/
|
|
14
|
+
export function loadJson(importMetaUrl, relativePath) {
|
|
15
|
+
const callerDir = path.dirname(fileURLToPath(importMetaUrl));
|
|
16
|
+
const resolvedPath = path.resolve(callerDir, relativePath);
|
|
17
|
+
// If the resolved path exists, use it directly
|
|
18
|
+
if (existsSync(resolvedPath)) {
|
|
19
|
+
return createRequire(importMetaUrl)(resolvedPath);
|
|
20
|
+
}
|
|
21
|
+
// If not found, try adjusting for dist/ directory
|
|
22
|
+
// This handles the case where we're in dist/lib/ but the JSON is at project root
|
|
23
|
+
const pathParts = resolvedPath.split(path.sep);
|
|
24
|
+
const distIndex = pathParts.indexOf('dist');
|
|
25
|
+
if (distIndex !== -1) {
|
|
26
|
+
// Remove 'dist' from the path and try again
|
|
27
|
+
pathParts.splice(distIndex, 1);
|
|
28
|
+
const adjustedPath = pathParts.join(path.sep);
|
|
29
|
+
if (existsSync(adjustedPath)) {
|
|
30
|
+
return createRequire(importMetaUrl)(adjustedPath);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`JSON file not found: ${relativePath} (resolved to ${resolvedPath})`);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Helper to find package.json by walking up the directory tree.
|
|
37
|
+
* Works regardless of whether we're in lib/ or dist/lib/.
|
|
38
|
+
*/
|
|
39
|
+
function findPackageJsonPath(startDir) {
|
|
40
|
+
let currentDir = startDir;
|
|
41
|
+
while (true) {
|
|
42
|
+
const pkgPath = path.join(currentDir, 'package.json');
|
|
43
|
+
if (existsSync(pkgPath)) {
|
|
44
|
+
return pkgPath;
|
|
45
|
+
}
|
|
46
|
+
const parentDir = path.dirname(currentDir);
|
|
47
|
+
if (parentDir === currentDir) {
|
|
48
|
+
// Reached root without finding package.json (e.g., in test environments)
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
currentDir = parentDir;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Load package.json once when this module is imported for convenience
|
|
55
|
+
// In test environments where this can't be found, tests should mock this module
|
|
56
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
57
|
+
const pkgPath = findPackageJsonPath(__dirname);
|
|
58
|
+
export const pkg = pkgPath
|
|
59
|
+
? createRequire(import.meta.url)(pkgPath)
|
|
60
|
+
: { name: 'unknown', version: 'unknown' };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { addUserAgentHeader } from '@hubspot/local-dev-lib/http';
|
|
2
2
|
import { setRequestHeaders } from '../requestMiddleware.js';
|
|
3
|
-
import pkg from '
|
|
3
|
+
import { pkg } from '../../jsonLoader.js';
|
|
4
4
|
vi.mock('@hubspot/local-dev-lib/http', () => ({
|
|
5
5
|
addUserAgentHeader: vi.fn(),
|
|
6
6
|
}));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import updateNotifier from 'update-notifier';
|
|
2
2
|
import { isConfigFlagEnabled } from '@hubspot/local-dev-lib/config';
|
|
3
|
-
import pkg from '
|
|
3
|
+
import { pkg } from '../jsonLoader.js';
|
|
4
4
|
import { UI_COLORS } from '../ui/index.js';
|
|
5
5
|
import SpinniesManager from '../ui/SpinniesManager.js';
|
|
6
6
|
import { lib } from '../../lang/en.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { fetchFireAlarms } from '@hubspot/local-dev-lib/api/fireAlarm';
|
|
3
3
|
import { debugError } from '../errorHandlers/index.js';
|
|
4
|
-
import pkg from '
|
|
4
|
+
import { pkg } from '../jsonLoader.js';
|
|
5
5
|
import { logInBox } from '../ui/boxen.js';
|
|
6
6
|
import { renderInline } from '../../ui/index.js';
|
|
7
7
|
import { getWarningBox } from '../../ui/components/StatusMessageBoxes.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import updateNotifier from 'update-notifier';
|
|
2
|
-
import pkg from '
|
|
2
|
+
import { pkg } from '../jsonLoader.js';
|
|
3
3
|
import { UI_COLORS } from '../ui/index.js';
|
|
4
4
|
import { lib } from '../../lang/en.js';
|
|
5
5
|
const notifier = updateNotifier({
|
package/lib/npm.d.ts
CHANGED
|
@@ -7,3 +7,6 @@ export declare function getLatestCliVersion(): Promise<{
|
|
|
7
7
|
export declare function executeInstall(packages?: string[], flags?: string | null, options?: {
|
|
8
8
|
cwd?: string;
|
|
9
9
|
}): Promise<void>;
|
|
10
|
+
export declare function executeUpdate(packages?: string[], flags?: string | null, options?: {
|
|
11
|
+
cwd?: string;
|
|
12
|
+
}): Promise<void>;
|
package/lib/npm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { exec as execAsync } from 'node:child_process';
|
|
2
2
|
import util from 'util';
|
|
3
3
|
import { uiLogger } from './ui/logger.js';
|
|
4
|
-
import pkg from '
|
|
4
|
+
import { pkg } from './jsonLoader.js';
|
|
5
5
|
export const DEFAULT_PACKAGE_MANAGER = 'npm';
|
|
6
6
|
export async function isGloballyInstalled(packageName) {
|
|
7
7
|
const exec = util.promisify(execAsync);
|
|
@@ -25,3 +25,9 @@ export async function executeInstall(packages = [], flags, options) {
|
|
|
25
25
|
const exec = util.promisify(execAsync);
|
|
26
26
|
await exec(installCommand, options);
|
|
27
27
|
}
|
|
28
|
+
export async function executeUpdate(packages = [], flags, options) {
|
|
29
|
+
const updateCommand = `${DEFAULT_PACKAGE_MANAGER} update${flags ? ` ${flags}` : ''} ${packages.join(' ')}`;
|
|
30
|
+
uiLogger.debug('Running', updateCommand);
|
|
31
|
+
const exec = util.promisify(execAsync);
|
|
32
|
+
await exec(updateCommand, options);
|
|
33
|
+
}
|
|
@@ -21,6 +21,7 @@ import { confirmPrompt } from '../../prompts/promptUtils.js';
|
|
|
21
21
|
import { getOauthAppInstallUrl, getStaticAuthAppInstallUrl, } from '../../app/urls.js';
|
|
22
22
|
import { isDeveloperTestAccount, isSandbox } from '../../accountTypes.js';
|
|
23
23
|
import { logError } from '../../errorHandlers/index.js';
|
|
24
|
+
import { isServerRunningAtUrl } from '../../http.js';
|
|
24
25
|
import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, APP_INSTALLATION_STATES, LOCAL_DEV_SERVER_MESSAGE_TYPES, } from '../../constants.js';
|
|
25
26
|
import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments';
|
|
26
27
|
vi.mock('@hubspot/local-dev-lib/api/localDevAuth');
|
|
@@ -36,6 +37,7 @@ vi.mock('../../errorHandlers/index');
|
|
|
36
37
|
vi.mock('../localDev/LocalDevState');
|
|
37
38
|
vi.mock('../localDev/LocalDevLogger');
|
|
38
39
|
vi.mock('../../ui/SpinniesManager');
|
|
40
|
+
vi.mock('../../http');
|
|
39
41
|
describe('AppDevModeInterface', () => {
|
|
40
42
|
let appDevModeInterface;
|
|
41
43
|
let mockLocalDevState;
|
|
@@ -139,6 +141,7 @@ describe('AppDevModeInterface', () => {
|
|
|
139
141
|
});
|
|
140
142
|
confirmPrompt.mockResolvedValue(true);
|
|
141
143
|
installStaticAuthAppOnTestAccount.mockResolvedValue(undefined);
|
|
144
|
+
isServerRunningAtUrl.mockResolvedValue(true);
|
|
142
145
|
// Mock process.exit
|
|
143
146
|
vi.spyOn(global.process, 'exit').mockImplementation((code) => {
|
|
144
147
|
throw new Error(`Process.exit called with code ${code}`);
|
|
@@ -8,10 +8,14 @@ describe('platformVersion', () => {
|
|
|
8
8
|
expect(isV2Project('2025.2')).toBe(true);
|
|
9
9
|
});
|
|
10
10
|
it('returns true if platform version is greater than the minimum', () => {
|
|
11
|
-
expect(isV2Project('
|
|
11
|
+
expect(isV2Project('2025.3')).toBe(true);
|
|
12
|
+
expect(isV2Project('2026.03')).toBe(true);
|
|
13
|
+
expect(isV2Project('2026.03-beta')).toBe(true);
|
|
12
14
|
});
|
|
13
15
|
it('returns false if platform version is less than the minimum', () => {
|
|
14
16
|
expect(isV2Project('2025.0')).toBe(false);
|
|
17
|
+
expect(isV2Project('2025.01')).toBe(false);
|
|
18
|
+
expect(isV2Project('2025.01-beta')).toBe(false);
|
|
15
19
|
});
|
|
16
20
|
it('returns false if platform version is invalid', () => {
|
|
17
21
|
expect(isV2Project(null)).toBe(false);
|
|
@@ -37,12 +37,12 @@ describe('lib/projects/create/v2', () => {
|
|
|
37
37
|
};
|
|
38
38
|
it('returns enabled components when they meet all requirements', async () => {
|
|
39
39
|
const choices = await calculateComponentTemplateChoices(mockComponents, 'oauth', 'private', 123, mockProjectMetadataForChoices);
|
|
40
|
-
expect(choices).toHaveLength(
|
|
40
|
+
expect(choices).toHaveLength(2);
|
|
41
41
|
expect(choices[0]).toEqual({
|
|
42
42
|
name: 'Module Component [module]',
|
|
43
43
|
value: mockComponents[0],
|
|
44
44
|
});
|
|
45
|
-
expect(choices[
|
|
45
|
+
expect(choices[1]).toEqual({
|
|
46
46
|
name: expect.stringContaining('Card Component'),
|
|
47
47
|
value: mockComponents[1],
|
|
48
48
|
disabled: expect.stringContaining('maximum'),
|
|
@@ -50,20 +50,20 @@ describe('lib/projects/create/v2', () => {
|
|
|
50
50
|
});
|
|
51
51
|
it('disables components when auth type is not supported', async () => {
|
|
52
52
|
const choices = await calculateComponentTemplateChoices(mockComponents, 'privatekey', 'private', 123, mockProjectMetadataForChoices);
|
|
53
|
-
// All components should be disabled
|
|
54
|
-
expect(choices[
|
|
53
|
+
// All components should be disabled
|
|
54
|
+
expect(choices[0]).toEqual({
|
|
55
55
|
name: expect.stringContaining('Module Component'),
|
|
56
56
|
value: mockComponents[0],
|
|
57
|
-
disabled: expect.stringContaining('
|
|
57
|
+
disabled: expect.stringContaining('oauth'),
|
|
58
58
|
});
|
|
59
59
|
});
|
|
60
60
|
it('disables components when distribution is not supported', async () => {
|
|
61
61
|
const choices = await calculateComponentTemplateChoices(mockComponents, 'oauth', 'enterprise', 123, mockProjectMetadataForChoices);
|
|
62
|
-
// All components should be disabled
|
|
63
|
-
expect(choices[
|
|
62
|
+
// All components should be disabled
|
|
63
|
+
expect(choices[0]).toEqual({
|
|
64
64
|
name: expect.stringContaining('Module Component'),
|
|
65
65
|
value: mockComponents[0],
|
|
66
|
-
disabled: expect.stringContaining('
|
|
66
|
+
disabled: expect.stringContaining('private'),
|
|
67
67
|
});
|
|
68
68
|
});
|
|
69
69
|
it('handles components without auth type or distribution restrictions', async () => {
|
|
@@ -125,8 +125,8 @@ describe('lib/projects/create/v2', () => {
|
|
|
125
125
|
},
|
|
126
126
|
};
|
|
127
127
|
const choices = await calculateComponentTemplateChoices(componentWithCliSelector, 'oauth', 'private', 123, projectMetadataAtMaxWorkflowAction);
|
|
128
|
-
expect(choices).toHaveLength(
|
|
129
|
-
expect(choices[
|
|
128
|
+
expect(choices).toHaveLength(1);
|
|
129
|
+
expect(choices[0]).toEqual({
|
|
130
130
|
name: expect.stringContaining('Workflow Action Tool'),
|
|
131
131
|
value: componentWithCliSelector[0],
|
|
132
132
|
disabled: expect.stringContaining('maximum'),
|
|
@@ -183,12 +183,18 @@ describe('lib/projects/create/v2', () => {
|
|
|
183
183
|
supportedDistributions: ['private'],
|
|
184
184
|
},
|
|
185
185
|
];
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
const projectMetadataWithWorkflowAction = {
|
|
187
|
+
hsMetaFiles: [],
|
|
188
|
+
components: {
|
|
189
|
+
'workflow-action': { count: 0, maxCount: 3, hsMetaFiles: [] },
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
const choices = await calculateComponentTemplateChoices(gatedComponent, 'oauth', 'private', 123, projectMetadataWithWorkflowAction);
|
|
193
|
+
expect(choices).toHaveLength(1);
|
|
194
|
+
expect(choices[0]).toEqual({
|
|
189
195
|
name: expect.stringContaining('Workflow Action Tool'),
|
|
190
196
|
value: gatedComponent[0],
|
|
191
|
-
disabled: expect.stringContaining('
|
|
197
|
+
disabled: expect.stringContaining("doesn't have access to this feature"),
|
|
192
198
|
});
|
|
193
199
|
expect(mockHasFeature).toHaveBeenCalledWith(123, expect.any(String));
|
|
194
200
|
});
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { Separator } from '@inquirer/prompts';
|
|
2
1
|
import { marketplaceDistribution, oAuth, privateDistribution, staticAuth, EMPTY_PROJECT, PROJECT_WITH_APP, FEATURES, } from '../../constants.js';
|
|
3
2
|
import { commands, lib } from '../../../lang/en.js';
|
|
4
3
|
import { listPrompt } from '../../prompts/promptUtils.js';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
4
|
import { isV2Project } from '../platformVersion.js';
|
|
7
5
|
import path from 'path';
|
|
8
6
|
import { getConfigForPlatformVersion } from './legacy.js';
|
|
@@ -75,25 +73,27 @@ export async function calculateComponentTemplateChoices(components, authType, di
|
|
|
75
73
|
if (Array.isArray(supportedAuthTypes) &&
|
|
76
74
|
authType &&
|
|
77
75
|
!supportedAuthTypes.includes(authType.toLowerCase())) {
|
|
78
|
-
|
|
76
|
+
const supportedAuthTypesString = supportedAuthTypes.join(', ');
|
|
77
|
+
disabledReasons.push(commands.project.add.error.authTypeNotAllowed(supportedAuthTypesString));
|
|
79
78
|
}
|
|
80
79
|
if (Array.isArray(supportedDistributions) &&
|
|
81
80
|
distribution &&
|
|
82
81
|
!supportedDistributions.includes(distribution.toLowerCase())) {
|
|
83
|
-
|
|
82
|
+
const supportedDistributionsString = supportedDistributions.join(', ');
|
|
83
|
+
disabledReasons.push(commands.project.add.error.distributionNotAllowed(supportedDistributionsString));
|
|
84
84
|
}
|
|
85
85
|
const templateGate = componentTypeToGateMap[template.cliSelector || template.type];
|
|
86
86
|
if (templateGate) {
|
|
87
87
|
const isUngated = await hasFeature(accountId, templateGate);
|
|
88
88
|
if (!isUngated) {
|
|
89
|
-
disabledReasons.unshift(commands.project.add.error.portalDoesNotHaveAccessToThisFeature(
|
|
89
|
+
disabledReasons.unshift(commands.project.add.error.portalDoesNotHaveAccessToThisFeature());
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
if (disabledReasons.length > 0) {
|
|
93
93
|
disabledComponents.push({
|
|
94
|
-
name:
|
|
94
|
+
name: `${template.label} [${template.cliSelector || template.type}]`,
|
|
95
95
|
value: template,
|
|
96
|
-
disabled: disabledReasons.join(' ')
|
|
96
|
+
disabled: `– ${disabledReasons.join(' ')}`,
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
99
|
else {
|
|
@@ -104,12 +104,7 @@ export async function calculateComponentTemplateChoices(components, authType, di
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
return disabledComponents.length
|
|
107
|
-
? [
|
|
108
|
-
...enabledComponents,
|
|
109
|
-
new Separator(),
|
|
110
|
-
...disabledComponents,
|
|
111
|
-
new Separator(),
|
|
112
|
-
]
|
|
107
|
+
? [...enabledComponents, ...disabledComponents]
|
|
113
108
|
: [...enabledComponents];
|
|
114
109
|
}
|
|
115
110
|
export async function v2ComponentFlow(platformVersion, projectBase, providedAuth, providedDistribution, accountId) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getAccountId, hasLocalStateFlag } from '@hubspot/local-dev-lib/config';
|
|
2
2
|
import { getConfigDefaultAccount } from '@hubspot/local-dev-lib/config';
|
|
3
3
|
import { uiLogger } from '../../ui/logger.js';
|
|
4
|
-
import {
|
|
4
|
+
import { uiLine, uiAccountDescription, uiCommandReference, } from '../../ui/index.js';
|
|
5
5
|
import { lib } from '../../../lang/en.js';
|
|
6
6
|
import SpinniesManager from '../../ui/SpinniesManager.js';
|
|
7
7
|
import { logError } from '../../errorHandlers/index.js';
|
|
@@ -75,7 +75,7 @@ class LocalDevLogger {
|
|
|
75
75
|
if (!this.state.debug) {
|
|
76
76
|
console.clear();
|
|
77
77
|
}
|
|
78
|
-
|
|
78
|
+
uiLogger.log(lib.LocalDevManager.headerMessage);
|
|
79
79
|
uiLogger.log(lib.LocalDevManager.learnMoreLocalDevServer);
|
|
80
80
|
uiLogger.log('');
|
|
81
81
|
uiLogger.log(lib.LocalDevManager.running(this.state.projectConfig.name, uiAccountDescription(this.state.targetProjectAccountId)));
|
|
@@ -11,7 +11,7 @@ import { EXIT_CODES } from '../../enums/exitCodes.js';
|
|
|
11
11
|
import { getAccountHomeUrl } from '../urls.js';
|
|
12
12
|
import { componentIsApp, componentIsPublicApp, CONFIG_FILES, getAppCardConfigs, getComponentUid, } from '../structure.js';
|
|
13
13
|
import { ComponentTypes, } from '../../../types/Projects.js';
|
|
14
|
-
import { UI_COLORS, uiCommandReference, uiAccountDescription,
|
|
14
|
+
import { UI_COLORS, uiCommandReference, uiAccountDescription, uiLink, uiLine, } from '../../ui/index.js';
|
|
15
15
|
import { logError } from '../../errorHandlers/index.js';
|
|
16
16
|
import { installAppBrowserPrompt } from '../../prompts/installAppPrompt.js';
|
|
17
17
|
import { confirmPrompt } from '../../prompts/promptUtils.js';
|
|
@@ -131,8 +131,8 @@ class LocalDevManager {
|
|
|
131
131
|
else if (!this.debug) {
|
|
132
132
|
console.clear();
|
|
133
133
|
}
|
|
134
|
-
|
|
135
|
-
uiLogger.log(uiLink(lib.LocalDevManager.learnMoreLocalDevServer, 'https://developers.hubspot.com/docs/
|
|
134
|
+
uiLogger.log(lib.LocalDevManager.headerMessage);
|
|
135
|
+
uiLogger.log(uiLink(lib.LocalDevManager.learnMoreLocalDevServer, 'https://developers.hubspot.com/docs/developer-tooling/local-development/hubspot-cli/project-commands'));
|
|
136
136
|
uiLogger.log('');
|
|
137
137
|
uiLogger.log(chalk.hex(UI_COLORS.SORBET)(lib.LocalDevManager.running(this.projectConfig.name, uiAccountDescription(this.targetAccountId))));
|
|
138
138
|
uiLogger.log(lib.LocalDevManager.viewProjectLink(this.projectConfig.name, this.targetProjectAccountId));
|
|
@@ -6,7 +6,7 @@ import { LOCAL_DEV_UI_MESSAGE_SEND_TYPES, LOCAL_DEV_SERVER_MESSAGE_TYPES, CONFIG
|
|
|
6
6
|
import { lib } from '../../../lang/en.js';
|
|
7
7
|
import { removeAnsiCodes } from '../../ui/removeAnsiCodes.js';
|
|
8
8
|
import { isDeployWebsocketMessage, isViewedWelcomeScreenWebsocketMessage, isUploadWebsocketMessage, isAppInstallFailureWebsocketMessage, isAppInstallSuccessWebsocketMessage, isAppInstallInitiatedWebsocketMessage, } from './localDevWebsocketServerUtils.js';
|
|
9
|
-
import pkg from '
|
|
9
|
+
import { pkg } from '../../jsonLoader.js';
|
|
10
10
|
const LOCAL_DEV_WEBSOCKET_SERVER_VERSION = 2;
|
|
11
11
|
const LOG_PREFIX = '[LocalDevWebsocketServer]';
|
|
12
12
|
const DOMAINS = ['hubspot.com', 'hubspotqa.com'];
|
|
@@ -5,6 +5,6 @@ export function isV2Project(platformVersion) {
|
|
|
5
5
|
if (platformVersion.toLowerCase() === 'unstable') {
|
|
6
6
|
return true;
|
|
7
7
|
}
|
|
8
|
-
const [year, minor] = platformVersion.split(
|
|
8
|
+
const [year, minor] = platformVersion.split(/[.-]/);
|
|
9
9
|
return Number(year) >= 2025 && Number(minor) >= 2;
|
|
10
10
|
}
|
|
@@ -6,6 +6,14 @@ export declare const PROMPT_THEME: {
|
|
|
6
6
|
idle: string;
|
|
7
7
|
};
|
|
8
8
|
};
|
|
9
|
+
export declare const CHECKBOX_PROMPT_THEME: {
|
|
10
|
+
prefix: {
|
|
11
|
+
idle: string;
|
|
12
|
+
};
|
|
13
|
+
style: {
|
|
14
|
+
disabledChoice: (text: string) => string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
9
17
|
export declare function promptUser<T extends GenericPromptResponse>(config: PromptConfig<T> | PromptConfig<T>[]): Promise<T>;
|
|
10
18
|
export declare function confirmPrompt(message: string, options?: {
|
|
11
19
|
defaultAnswer?: boolean;
|
|
@@ -5,6 +5,12 @@ import { lib } from '../../lang/en.js';
|
|
|
5
5
|
import { uiLogger } from '../ui/logger.js';
|
|
6
6
|
export const Separator = new _Separator();
|
|
7
7
|
export const PROMPT_THEME = { prefix: { idle: chalk.green('?') } };
|
|
8
|
+
export const CHECKBOX_PROMPT_THEME = {
|
|
9
|
+
prefix: { idle: chalk.green('?') },
|
|
10
|
+
style: {
|
|
11
|
+
disabledChoice: (text) => chalk.dim(` ◯ ${text}`),
|
|
12
|
+
},
|
|
13
|
+
};
|
|
8
14
|
function isUserCancellationError(error) {
|
|
9
15
|
return error instanceof Error && error.name === 'ExitPromptError';
|
|
10
16
|
}
|
|
@@ -147,7 +153,7 @@ function handleCheckboxPrompt(config) {
|
|
|
147
153
|
pageSize: config.pageSize,
|
|
148
154
|
validate: config.validate,
|
|
149
155
|
loop: config.loop,
|
|
150
|
-
theme:
|
|
156
|
+
theme: CHECKBOX_PROMPT_THEME,
|
|
151
157
|
shortcuts: {
|
|
152
158
|
invert: null,
|
|
153
159
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Separator } from '@inquirer/prompts';
|
|
2
2
|
import { promptUser } from './promptUtils.js';
|
|
3
3
|
import { lib } from '../../lang/en.js';
|
|
4
|
+
import { uiLogger } from '../ui/logger.js';
|
|
4
5
|
function findTemplateByNameOrLabel(projectTemplates, templateNameOrLabel) {
|
|
5
6
|
return projectTemplates.find(t => t.name === templateNameOrLabel || t.label === templateNameOrLabel);
|
|
6
7
|
}
|
|
@@ -55,6 +56,9 @@ export async function selectProjectTemplatePrompt(promptOptions, projectTemplate
|
|
|
55
56
|
pageSize: componentTemplates?.length,
|
|
56
57
|
},
|
|
57
58
|
]);
|
|
59
|
+
if (result.componentTemplates?.length === 0) {
|
|
60
|
+
uiLogger.log(lib.projects.add.nothingAdded);
|
|
61
|
+
}
|
|
58
62
|
if (!result.componentTemplates) {
|
|
59
63
|
result.componentTemplates = selectedComponents;
|
|
60
64
|
}
|