@hubspot/cli 8.0.2-experimental.0 → 8.0.3-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__/getStarted.test.js +2 -2
- package/commands/__tests__/project.test.js +30 -0
- package/commands/account/auth.js +8 -97
- package/commands/account/use.js +19 -4
- package/commands/cms/module/marketplace-validate.js +23 -5
- package/commands/cms/theme/marketplace-validate.js +25 -6
- package/commands/mcp/setup.js +1 -2
- package/commands/mcp.js +1 -2
- package/commands/project.js +22 -1
- package/lang/en.d.ts +22 -1
- package/lang/en.js +26 -5
- package/lib/__tests__/accountAuth.test.d.ts +1 -0
- package/lib/__tests__/accountAuth.test.js +258 -0
- package/lib/accountAuth.d.ts +10 -0
- package/lib/accountAuth.js +105 -0
- package/lib/app/urls.d.ts +1 -0
- package/lib/app/urls.js +4 -0
- package/lib/errors/ProjectErrors.d.ts +15 -0
- package/lib/errors/ProjectErrors.js +30 -0
- package/lib/getStarted/getStartedV2.js +3 -41
- package/lib/getStartedV2Actions.d.ts +29 -0
- package/lib/getStartedV2Actions.js +104 -9
- package/lib/marketplaceValidate.d.ts +1 -1
- package/lib/marketplaceValidate.js +23 -41
- package/lib/projects/ProjectLogsManager.d.ts +12 -3
- package/lib/projects/ProjectLogsManager.js +70 -12
- package/lib/projects/__tests__/ProjectLogsManager.test.js +131 -18
- package/lib/projects/__tests__/platformVersion.test.js +37 -1
- package/lib/projects/__tests__/projects.test.js +6 -2
- package/lib/projects/components.d.ts +6 -0
- package/lib/projects/components.js +1 -1
- package/lib/projects/config.js +9 -2
- package/lib/projects/localDev/helpers/project.d.ts +4 -1
- package/lib/projects/localDev/helpers/project.js +13 -8
- package/lib/projects/platformVersion.d.ts +8 -0
- package/lib/projects/platformVersion.js +31 -2
- package/lib/prompts/accountsPrompt.d.ts +2 -1
- package/lib/prompts/accountsPrompt.js +10 -2
- package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +20 -3
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +6 -10
- package/mcp-server/tools/project/CreateProjectTool.d.ts +24 -4
- package/mcp-server/tools/project/CreateProjectTool.js +5 -10
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +5 -8
- package/mcp-server/tools/project/GetBuildLogsTool.d.ts +2 -2
- package/mcp-server/tools/project/GetBuildLogsTool.js +3 -4
- package/mcp-server/tools/project/GetBuildStatusTool.d.ts +1 -1
- package/mcp-server/tools/project/GetBuildStatusTool.js +3 -4
- package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +6 -1
- package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -6
- package/mcp-server/tools/project/constants.d.ts +12 -1
- package/mcp-server/tools/project/constants.js +12 -16
- package/package.json +2 -2
- package/ui/components/ActionSection.d.ts +1 -1
- package/ui/components/BoxWithTitle.js +1 -1
- package/ui/components/getStarted/GetStartedFlow.d.ts +8 -0
- package/ui/components/getStarted/GetStartedFlow.js +136 -0
- package/ui/components/getStarted/reducer.d.ts +59 -0
- package/ui/components/getStarted/reducer.js +72 -0
- package/ui/components/getStarted/screens/ProjectSetupScreen.d.ts +16 -0
- package/ui/components/getStarted/screens/ProjectSetupScreen.js +39 -0
- package/ui/components/getStarted/screens/UploadScreen.d.ts +7 -0
- package/ui/components/getStarted/screens/UploadScreen.js +43 -0
- package/ui/components/getStarted/selectors.d.ts +2 -0
- package/ui/components/getStarted/selectors.js +1 -0
- package/ui/lib/constants.d.ts +16 -0
- package/ui/lib/constants.js +16 -0
- package/ui/render.js +6 -0
- package/ui/components/GetStartedFlow.d.ts +0 -24
- package/ui/components/GetStartedFlow.js +0 -128
|
@@ -2,7 +2,7 @@ import { GetValidationResultsResponse } from '@hubspot/local-dev-lib/types/Marke
|
|
|
2
2
|
export declare function kickOffValidation(accountId: number, assetType: string, src: string): Promise<number>;
|
|
3
3
|
export declare function pollForValidationFinish(accountId: number, validationId: number): Promise<void>;
|
|
4
4
|
export declare function fetchValidationResults(accountId: number, validationId: number): Promise<GetValidationResultsResponse>;
|
|
5
|
-
export declare function
|
|
5
|
+
export declare function hasProcessValidationErrors(invalidPathError: (path: string) => string, validationResults: GetValidationResultsResponse): boolean;
|
|
6
6
|
type ResultsCopy = {
|
|
7
7
|
noErrors: string;
|
|
8
8
|
required: string;
|
|
@@ -1,54 +1,35 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { requestValidation, getValidationStatus, getValidationResults, } from '@hubspot/local-dev-lib/api/marketplaceValidation';
|
|
3
3
|
import { uiLogger } from './ui/logger.js';
|
|
4
|
-
import { EXIT_CODES } from './enums/exitCodes.js';
|
|
5
4
|
const SLEEP_TIME = 2000;
|
|
6
5
|
export async function kickOffValidation(accountId, assetType, src) {
|
|
7
6
|
const requestGroup = 'EXTERNAL_DEVELOPER';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return requestResult;
|
|
15
|
-
}
|
|
16
|
-
catch (err) {
|
|
17
|
-
uiLogger.debug(err);
|
|
18
|
-
process.exit(EXIT_CODES.ERROR);
|
|
19
|
-
}
|
|
7
|
+
const { data: requestResult } = await requestValidation(accountId, {
|
|
8
|
+
path: src,
|
|
9
|
+
assetType,
|
|
10
|
+
requestGroup,
|
|
11
|
+
});
|
|
12
|
+
return requestResult;
|
|
20
13
|
}
|
|
21
14
|
export async function pollForValidationFinish(accountId, validationId) {
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const { data: validationStatus } = await getValidationStatus(accountId, {
|
|
25
|
-
validationId,
|
|
26
|
-
});
|
|
27
|
-
if (validationStatus === 'REQUESTED') {
|
|
28
|
-
await new Promise(resolve => setTimeout(resolve, SLEEP_TIME));
|
|
29
|
-
await checkValidationStatus();
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
await checkValidationStatus();
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
uiLogger.debug(err);
|
|
36
|
-
process.exit(EXIT_CODES.ERROR);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
export async function fetchValidationResults(accountId, validationId) {
|
|
40
|
-
try {
|
|
41
|
-
const { data: validationResults } = await getValidationResults(accountId, {
|
|
15
|
+
async function checkValidationStatus() {
|
|
16
|
+
const { data: validationStatus } = await getValidationStatus(accountId, {
|
|
42
17
|
validationId,
|
|
43
18
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
process.exit(EXIT_CODES.ERROR);
|
|
19
|
+
if (validationStatus === 'REQUESTED') {
|
|
20
|
+
await new Promise(resolve => setTimeout(resolve, SLEEP_TIME));
|
|
21
|
+
await checkValidationStatus();
|
|
22
|
+
}
|
|
49
23
|
}
|
|
24
|
+
await checkValidationStatus();
|
|
25
|
+
}
|
|
26
|
+
export async function fetchValidationResults(accountId, validationId) {
|
|
27
|
+
const { data: validationResults } = await getValidationResults(accountId, {
|
|
28
|
+
validationId,
|
|
29
|
+
});
|
|
30
|
+
return validationResults;
|
|
50
31
|
}
|
|
51
|
-
export function
|
|
32
|
+
export function hasProcessValidationErrors(invalidPathError, validationResults) {
|
|
52
33
|
if (validationResults.errors.length) {
|
|
53
34
|
const { assetPath, errors } = validationResults;
|
|
54
35
|
errors.forEach(err => {
|
|
@@ -56,11 +37,12 @@ export function processValidationErrors(invalidPathError, validationResults) {
|
|
|
56
37
|
uiLogger.error(invalidPathError(assetPath));
|
|
57
38
|
}
|
|
58
39
|
else {
|
|
59
|
-
uiLogger.error(
|
|
40
|
+
uiLogger.error(err.context);
|
|
60
41
|
}
|
|
61
42
|
});
|
|
62
|
-
|
|
43
|
+
return true;
|
|
63
44
|
}
|
|
45
|
+
return false;
|
|
64
46
|
}
|
|
65
47
|
function displayFileInfo(file, line, resultsCopy) {
|
|
66
48
|
if (file) {
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ProjectConfig } from '../../types/Projects.js';
|
|
2
|
+
type FunctionInfo = {
|
|
3
|
+
componentName: string;
|
|
4
|
+
appId: number;
|
|
5
|
+
endpoint?: {
|
|
6
|
+
path: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
2
9
|
declare class _ProjectLogsManager {
|
|
3
10
|
projectName: string | undefined;
|
|
11
|
+
projectConfig: ProjectConfig | undefined;
|
|
4
12
|
projectId: number | undefined;
|
|
5
13
|
accountId: number | undefined;
|
|
6
|
-
functions:
|
|
7
|
-
selectedFunction:
|
|
14
|
+
functions: FunctionInfo[];
|
|
15
|
+
selectedFunction: FunctionInfo | undefined;
|
|
8
16
|
functionName: string | undefined;
|
|
9
17
|
appId: number | undefined;
|
|
10
18
|
isPublicFunction: boolean | undefined;
|
|
@@ -13,6 +21,7 @@ declare class _ProjectLogsManager {
|
|
|
13
21
|
constructor();
|
|
14
22
|
init(accountId: number): Promise<void>;
|
|
15
23
|
fetchFunctionDetails(): Promise<void>;
|
|
24
|
+
fetchFunctionDetailsV2(deployedBuildId: number): Promise<void>;
|
|
16
25
|
getFunctionNames(): string[];
|
|
17
26
|
setFunction(functionName?: string): void;
|
|
18
27
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { getProjectConfig } from './config.js';
|
|
2
2
|
import { ensureProjectExists } from './ensureProjectExists.js';
|
|
3
3
|
import { fetchProjectComponentsMetadata } from '@hubspot/local-dev-lib/api/projects';
|
|
4
|
+
import { fetchAppMetadataBySourceId } from '@hubspot/local-dev-lib/api/appsDev';
|
|
4
5
|
import { uiLogger } from '../ui/logger.js';
|
|
5
6
|
import { commands } from '../../lang/en.js';
|
|
7
|
+
import { isV2Project } from './platformVersion.js';
|
|
8
|
+
import { getDeployedProjectNodes } from './localDev/helpers/project.js';
|
|
9
|
+
import { debugError } from '../errorHandlers/index.js';
|
|
6
10
|
class _ProjectLogsManager {
|
|
7
11
|
projectName;
|
|
12
|
+
projectConfig;
|
|
8
13
|
projectId;
|
|
9
14
|
accountId;
|
|
10
15
|
functions;
|
|
@@ -15,6 +20,7 @@ class _ProjectLogsManager {
|
|
|
15
20
|
endpointName;
|
|
16
21
|
reset() {
|
|
17
22
|
this.projectName = undefined;
|
|
23
|
+
this.projectConfig = undefined;
|
|
18
24
|
this.projectId = undefined;
|
|
19
25
|
this.accountId = undefined;
|
|
20
26
|
this.functions = [];
|
|
@@ -32,8 +38,8 @@ class _ProjectLogsManager {
|
|
|
32
38
|
if (!projectConfig || !projectConfig.name) {
|
|
33
39
|
throw new Error(commands.project.logs.errors.noProjectConfig);
|
|
34
40
|
}
|
|
35
|
-
|
|
36
|
-
this.projectName =
|
|
41
|
+
this.projectConfig = projectConfig;
|
|
42
|
+
this.projectName = projectConfig.name;
|
|
37
43
|
this.accountId = accountId;
|
|
38
44
|
this.functions = [];
|
|
39
45
|
const { project } = await ensureProjectExists(this.accountId, this.projectName, {
|
|
@@ -45,7 +51,16 @@ class _ProjectLogsManager {
|
|
|
45
51
|
throw new Error(commands.project.logs.errors.failedToFetchProjectDetails);
|
|
46
52
|
}
|
|
47
53
|
this.projectId = project.id;
|
|
48
|
-
|
|
54
|
+
if (isV2Project(projectConfig.platformVersion)) {
|
|
55
|
+
const deployedBuildId = project.deployedBuild.buildId;
|
|
56
|
+
if (!deployedBuildId) {
|
|
57
|
+
throw new Error(commands.project.logs.errors.noDeployedBuild);
|
|
58
|
+
}
|
|
59
|
+
await this.fetchFunctionDetailsV2(deployedBuildId);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
await this.fetchFunctionDetails();
|
|
63
|
+
}
|
|
49
64
|
}
|
|
50
65
|
async fetchFunctionDetails() {
|
|
51
66
|
if (!this.projectId) {
|
|
@@ -61,14 +76,60 @@ class _ProjectLogsManager {
|
|
|
61
76
|
return type && type.name === 'PRIVATE_APP';
|
|
62
77
|
});
|
|
63
78
|
apps.forEach(app => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
79
|
+
const appFunctions = app.featureComponents.filter(component => component.type.name === 'APP_FUNCTION');
|
|
80
|
+
appFunctions.forEach(fn => {
|
|
81
|
+
if (fn.deployOutput) {
|
|
82
|
+
this.functions.push({
|
|
83
|
+
componentName: fn.componentName,
|
|
84
|
+
appId: fn.deployOutput.appId,
|
|
85
|
+
endpoint: fn.deployOutput.endpoint,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
67
89
|
});
|
|
68
90
|
if (this.functions.length === 0) {
|
|
69
91
|
throw new Error(commands.project.logs.errors.noFunctionsInProject);
|
|
70
92
|
}
|
|
71
93
|
}
|
|
94
|
+
async fetchFunctionDetailsV2(deployedBuildId) {
|
|
95
|
+
if (!this.projectId || !this.accountId || !this.projectConfig) {
|
|
96
|
+
uiLogger.debug(commands.project.logs.errors.projectLogsManagerNotInitialized);
|
|
97
|
+
throw new Error(commands.project.logs.errors.generic);
|
|
98
|
+
}
|
|
99
|
+
let deployedNodes;
|
|
100
|
+
try {
|
|
101
|
+
deployedNodes = await getDeployedProjectNodes(this.projectConfig, this.accountId, deployedBuildId);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
debugError(err);
|
|
105
|
+
throw new Error(commands.project.logs.errors.failedToFetchProjectDetails);
|
|
106
|
+
}
|
|
107
|
+
const appNode = Object.values(deployedNodes).find(node => node.componentType === 'APPLICATION');
|
|
108
|
+
if (!appNode) {
|
|
109
|
+
throw new Error(commands.project.logs.errors.noFunctionsInProject);
|
|
110
|
+
}
|
|
111
|
+
let appId;
|
|
112
|
+
try {
|
|
113
|
+
const { data: appMetadata } = await fetchAppMetadataBySourceId(this.projectId, appNode.uid, this.accountId);
|
|
114
|
+
appId = appMetadata.id;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
debugError(err);
|
|
118
|
+
throw new Error(commands.project.logs.errors.failedToFetchProjectDetails);
|
|
119
|
+
}
|
|
120
|
+
const functionNodes = Object.values(deployedNodes).filter(node => node.componentType === 'APP_FUNCTION');
|
|
121
|
+
for (const fnNode of functionNodes) {
|
|
122
|
+
const config = fnNode.config;
|
|
123
|
+
this.functions.push({
|
|
124
|
+
componentName: fnNode.uid,
|
|
125
|
+
appId,
|
|
126
|
+
endpoint: config.endpoint,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (this.functions.length === 0) {
|
|
130
|
+
throw new Error(commands.project.logs.errors.noFunctionsInProject);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
72
133
|
getFunctionNames() {
|
|
73
134
|
return this.functions.map(serverlessFunction => serverlessFunction.componentName);
|
|
74
135
|
}
|
|
@@ -81,12 +142,9 @@ class _ProjectLogsManager {
|
|
|
81
142
|
throw new Error(commands.project.logs.errors.noFunctionWithName(functionName));
|
|
82
143
|
}
|
|
83
144
|
this.functionName = functionName;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
this.appId = this.selectedFunction.deployOutput.appId;
|
|
88
|
-
if (this.selectedFunction.deployOutput.endpoint) {
|
|
89
|
-
this.endpointName = this.selectedFunction.deployOutput.endpoint.path;
|
|
145
|
+
this.appId = this.selectedFunction.appId;
|
|
146
|
+
if (this.selectedFunction.endpoint) {
|
|
147
|
+
this.endpointName = this.selectedFunction.endpoint.path;
|
|
90
148
|
this.isPublicFunction = true;
|
|
91
149
|
}
|
|
92
150
|
else {
|
|
@@ -2,6 +2,9 @@ import { ProjectLogsManager } from '../ProjectLogsManager.js';
|
|
|
2
2
|
import { getProjectConfig } from '../config.js';
|
|
3
3
|
import { ensureProjectExists } from '../ensureProjectExists.js';
|
|
4
4
|
import { fetchProjectComponentsMetadata } from '@hubspot/local-dev-lib/api/projects';
|
|
5
|
+
import { fetchAppMetadataBySourceId } from '@hubspot/local-dev-lib/api/appsDev';
|
|
6
|
+
import { getDeployedProjectNodes } from '../localDev/helpers/project.js';
|
|
7
|
+
import { isV2Project } from '../platformVersion.js';
|
|
5
8
|
const SUBCOMPONENT_TYPES = {
|
|
6
9
|
APP_ID: 'APP_ID',
|
|
7
10
|
PACKAGE_LOCK_FILE: 'PACKAGE_LOCK_FILE',
|
|
@@ -17,11 +20,20 @@ const SUBCOMPONENT_TYPES = {
|
|
|
17
20
|
vi.mock('../../projects/config');
|
|
18
21
|
vi.mock('../../projects/ensureProjectExists');
|
|
19
22
|
vi.mock('@hubspot/local-dev-lib/api/projects');
|
|
23
|
+
vi.mock('@hubspot/local-dev-lib/api/appsDev');
|
|
24
|
+
vi.mock('../../projects/localDev/helpers/project');
|
|
25
|
+
vi.mock('../../projects/platformVersion');
|
|
20
26
|
describe('lib/projects/ProjectLogsManager', () => {
|
|
21
27
|
const accountId = 12345678;
|
|
22
28
|
const appId = 999999;
|
|
23
29
|
const projectName = 'super cool test project';
|
|
24
|
-
const projectConfig = {
|
|
30
|
+
const projectConfig = {
|
|
31
|
+
projectConfig: {
|
|
32
|
+
name: projectName,
|
|
33
|
+
srcDir: 'src',
|
|
34
|
+
platformVersion: '2024.1',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
25
37
|
const projectId = 987654321;
|
|
26
38
|
const projectDetails = {
|
|
27
39
|
project: {
|
|
@@ -33,16 +45,26 @@ describe('lib/projects/ProjectLogsManager', () => {
|
|
|
33
45
|
};
|
|
34
46
|
const function1 = {
|
|
35
47
|
componentName: 'function1',
|
|
36
|
-
|
|
37
|
-
name: SUBCOMPONENT_TYPES.APP_FUNCTION,
|
|
38
|
-
},
|
|
39
|
-
deployOutput: {
|
|
40
|
-
appId,
|
|
41
|
-
appFunctionName: 'function1',
|
|
42
|
-
},
|
|
48
|
+
appId,
|
|
43
49
|
};
|
|
44
50
|
const functions = [
|
|
45
51
|
function1,
|
|
52
|
+
{
|
|
53
|
+
componentName: 'function2',
|
|
54
|
+
appId,
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
const legacyApiFunctions = [
|
|
58
|
+
{
|
|
59
|
+
componentName: 'function1',
|
|
60
|
+
type: {
|
|
61
|
+
name: SUBCOMPONENT_TYPES.APP_FUNCTION,
|
|
62
|
+
},
|
|
63
|
+
deployOutput: {
|
|
64
|
+
appId,
|
|
65
|
+
appFunctionName: 'function1',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
46
68
|
{
|
|
47
69
|
componentName: 'function2',
|
|
48
70
|
type: {
|
|
@@ -56,6 +78,7 @@ describe('lib/projects/ProjectLogsManager', () => {
|
|
|
56
78
|
];
|
|
57
79
|
beforeEach(() => {
|
|
58
80
|
ProjectLogsManager.reset();
|
|
81
|
+
isV2Project.mockReturnValue(false);
|
|
59
82
|
getProjectConfig.mockResolvedValue(projectConfig);
|
|
60
83
|
ensureProjectExists.mockResolvedValue(projectDetails);
|
|
61
84
|
fetchProjectComponentsMetadata.mockResolvedValue({
|
|
@@ -69,7 +92,7 @@ describe('lib/projects/ProjectLogsManager', () => {
|
|
|
69
92
|
appId,
|
|
70
93
|
},
|
|
71
94
|
featureComponents: [
|
|
72
|
-
...
|
|
95
|
+
...legacyApiFunctions,
|
|
73
96
|
{
|
|
74
97
|
type: {
|
|
75
98
|
name: 'NOT_AN_APP_FUNCTION',
|
|
@@ -128,6 +151,102 @@ describe('lib/projects/ProjectLogsManager', () => {
|
|
|
128
151
|
expect(ProjectLogsManager.functions).toEqual(functions);
|
|
129
152
|
});
|
|
130
153
|
});
|
|
154
|
+
describe('v2 project init', () => {
|
|
155
|
+
const v2ProjectConfig = {
|
|
156
|
+
projectConfig: {
|
|
157
|
+
name: projectName,
|
|
158
|
+
srcDir: 'src',
|
|
159
|
+
platformVersion: '2025.2',
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
const deployedBuildId = 555;
|
|
163
|
+
const v2ProjectDetails = {
|
|
164
|
+
project: {
|
|
165
|
+
id: projectId,
|
|
166
|
+
deployedBuild: {
|
|
167
|
+
buildId: deployedBuildId,
|
|
168
|
+
subbuildStatuses: {},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
const appUid = 'my-app';
|
|
173
|
+
const fnUid1 = 'my-app/app.functions/function1';
|
|
174
|
+
const fnUid2 = 'my-app/app.functions/function2';
|
|
175
|
+
const deployedNodes = {
|
|
176
|
+
[appUid]: {
|
|
177
|
+
componentType: 'APPLICATION',
|
|
178
|
+
componentDeps: {},
|
|
179
|
+
metaFilePath: 'src/app/app-hsmeta.json',
|
|
180
|
+
uid: appUid,
|
|
181
|
+
config: {},
|
|
182
|
+
files: {},
|
|
183
|
+
},
|
|
184
|
+
[fnUid1]: {
|
|
185
|
+
componentType: 'APP_FUNCTION',
|
|
186
|
+
componentDeps: { app: appUid },
|
|
187
|
+
metaFilePath: 'src/app/app.functions/function1.functions/function-hsmeta.json',
|
|
188
|
+
uid: fnUid1,
|
|
189
|
+
config: { endpoint: { path: '/my-endpoint' } },
|
|
190
|
+
files: {},
|
|
191
|
+
},
|
|
192
|
+
[fnUid2]: {
|
|
193
|
+
componentType: 'APP_FUNCTION',
|
|
194
|
+
componentDeps: { app: appUid },
|
|
195
|
+
metaFilePath: 'src/app/app.functions/function2.functions/function-hsmeta.json',
|
|
196
|
+
uid: fnUid2,
|
|
197
|
+
config: {},
|
|
198
|
+
files: {},
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
beforeEach(() => {
|
|
202
|
+
getProjectConfig.mockResolvedValue(v2ProjectConfig);
|
|
203
|
+
ensureProjectExists.mockResolvedValue(v2ProjectDetails);
|
|
204
|
+
isV2Project.mockReturnValue(true);
|
|
205
|
+
getDeployedProjectNodes.mockResolvedValue(deployedNodes);
|
|
206
|
+
fetchAppMetadataBySourceId.mockResolvedValue({
|
|
207
|
+
data: { id: appId },
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
it('should populate functions correctly for v2 projects', async () => {
|
|
211
|
+
await ProjectLogsManager.init(accountId);
|
|
212
|
+
expect(getDeployedProjectNodes).toHaveBeenCalledWith(v2ProjectConfig.projectConfig, accountId, deployedBuildId);
|
|
213
|
+
expect(fetchAppMetadataBySourceId).toHaveBeenCalledWith(projectId, appUid, accountId);
|
|
214
|
+
expect(ProjectLogsManager.functions).toEqual([
|
|
215
|
+
{
|
|
216
|
+
componentName: fnUid1,
|
|
217
|
+
appId,
|
|
218
|
+
endpoint: { path: '/my-endpoint' },
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
componentName: fnUid2,
|
|
222
|
+
appId,
|
|
223
|
+
endpoint: undefined,
|
|
224
|
+
},
|
|
225
|
+
]);
|
|
226
|
+
});
|
|
227
|
+
it('should throw noDeployedBuild when buildId is missing', async () => {
|
|
228
|
+
ensureProjectExists.mockResolvedValue({
|
|
229
|
+
project: {
|
|
230
|
+
id: projectId,
|
|
231
|
+
deployedBuild: {
|
|
232
|
+
buildId: undefined,
|
|
233
|
+
subbuildStatuses: {},
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
await expect(async () => ProjectLogsManager.init(accountId)).rejects.toThrow('This project has not been deployed yet. Deploy the project first, then try again.');
|
|
238
|
+
});
|
|
239
|
+
it('should throw noFunctionsInProject when no function nodes exist', async () => {
|
|
240
|
+
getDeployedProjectNodes.mockResolvedValue({
|
|
241
|
+
[appUid]: deployedNodes[appUid],
|
|
242
|
+
});
|
|
243
|
+
await expect(async () => ProjectLogsManager.init(accountId)).rejects.toThrow(/There aren't any functions in this project/);
|
|
244
|
+
});
|
|
245
|
+
it('should throw a user-friendly error when getDeployedProjectNodes fails', async () => {
|
|
246
|
+
getDeployedProjectNodes.mockRejectedValue(new Error('download failed'));
|
|
247
|
+
await expect(async () => ProjectLogsManager.init(accountId)).rejects.toThrow(/There was an error fetching project details/);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
131
250
|
describe('getFunctionNames', () => {
|
|
132
251
|
it('should return an empty array if functions is empty', async () => {
|
|
133
252
|
ProjectLogsManager.functions = [];
|
|
@@ -154,14 +273,8 @@ describe('lib/projects/ProjectLogsManager', () => {
|
|
|
154
273
|
it('should set the data correctly for public functions', async () => {
|
|
155
274
|
const functionToChoose = {
|
|
156
275
|
componentName: 'function1',
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
},
|
|
160
|
-
deployOutput: {
|
|
161
|
-
appId: 123,
|
|
162
|
-
appFunctionName: 'function1',
|
|
163
|
-
endpoint: { path: 'yooooooo', methods: ['GET'] },
|
|
164
|
-
},
|
|
276
|
+
appId: 123,
|
|
277
|
+
endpoint: { path: 'yooooooo' },
|
|
165
278
|
};
|
|
166
279
|
ProjectLogsManager.functions = [functionToChoose];
|
|
167
280
|
ProjectLogsManager.setFunction('function1');
|
|
@@ -170,7 +283,7 @@ describe('lib/projects/ProjectLogsManager', () => {
|
|
|
170
283
|
expect(ProjectLogsManager.selectedFunction).toEqual(functionToChoose);
|
|
171
284
|
expect(ProjectLogsManager.isPublicFunction).toEqual(true);
|
|
172
285
|
});
|
|
173
|
-
it('should set the data correctly for
|
|
286
|
+
it('should set the data correctly for private functions', async () => {
|
|
174
287
|
ProjectLogsManager.functions = functions;
|
|
175
288
|
ProjectLogsManager.setFunction('function1');
|
|
176
289
|
expect(ProjectLogsManager.selectedFunction).toEqual(function1);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isV2Project } from '../platformVersion.js';
|
|
1
|
+
import { isV2Project, isUnsupportedPlatformVersion, } from '../platformVersion.js';
|
|
2
2
|
describe('platformVersion', () => {
|
|
3
3
|
describe('isV2Project', () => {
|
|
4
4
|
it('returns true if platform version is UNSTABLE', () => {
|
|
@@ -24,4 +24,40 @@ describe('platformVersion', () => {
|
|
|
24
24
|
expect(isV2Project('notplaformversion')).toBe(false);
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
|
+
describe('isUnsupportedPlatformVersion', () => {
|
|
28
|
+
it('returns false for platform version 2026.03 (boundary)', () => {
|
|
29
|
+
expect(isUnsupportedPlatformVersion('2026.03')).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
it('returns false for platform versions less than 2026.03', () => {
|
|
32
|
+
expect(isUnsupportedPlatformVersion('2025.2')).toBe(false);
|
|
33
|
+
expect(isUnsupportedPlatformVersion('2026.01')).toBe(false);
|
|
34
|
+
expect(isUnsupportedPlatformVersion('2026.02')).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
it('returns true for platform version 2026.04', () => {
|
|
37
|
+
expect(isUnsupportedPlatformVersion('2026.04')).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
it('returns true for platform versions greater than 2026.03', () => {
|
|
40
|
+
expect(isUnsupportedPlatformVersion('2026.4')).toBe(true);
|
|
41
|
+
expect(isUnsupportedPlatformVersion('2027.01')).toBe(true);
|
|
42
|
+
expect(isUnsupportedPlatformVersion('2027.1')).toBe(true);
|
|
43
|
+
expect(isUnsupportedPlatformVersion('2028.10')).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
it('returns false for UNSTABLE', () => {
|
|
46
|
+
expect(isUnsupportedPlatformVersion('UNSTABLE')).toBe(false);
|
|
47
|
+
expect(isUnsupportedPlatformVersion('unstable')).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
it('returns false for null or undefined', () => {
|
|
50
|
+
expect(isUnsupportedPlatformVersion(null)).toBe(false);
|
|
51
|
+
expect(isUnsupportedPlatformVersion(undefined)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
it('returns false for invalid platform versions', () => {
|
|
54
|
+
expect(isUnsupportedPlatformVersion('notaversion')).toBe(false);
|
|
55
|
+
expect(isUnsupportedPlatformVersion('abc.def')).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
it('handles beta versions correctly', () => {
|
|
58
|
+
expect(isUnsupportedPlatformVersion('2026.03-beta')).toBe(false);
|
|
59
|
+
expect(isUnsupportedPlatformVersion('2026.04-beta')).toBe(true);
|
|
60
|
+
expect(isUnsupportedPlatformVersion('2027.01-beta')).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
27
63
|
});
|
|
@@ -16,12 +16,16 @@ describe('lib/projects', () => {
|
|
|
16
16
|
});
|
|
17
17
|
it('rejects configuration with missing name', () => {
|
|
18
18
|
// @ts-ignore Testing invalid input
|
|
19
|
-
expect(() => validateProjectConfig({ srcDir: '.' }, projectDir)).toThrow(
|
|
19
|
+
expect(() => validateProjectConfig({ srcDir: '.' }, projectDir)).toThrow(/missing required field.*name/);
|
|
20
20
|
});
|
|
21
21
|
it('rejects configuration with missing srcDir', () => {
|
|
22
22
|
expect(() =>
|
|
23
23
|
// @ts-ignore Testing invalid input
|
|
24
|
-
validateProjectConfig({ name: 'hello' }, projectDir)).toThrow(
|
|
24
|
+
validateProjectConfig({ name: 'hello' }, projectDir)).toThrow(/missing required field.*srcDir/);
|
|
25
|
+
});
|
|
26
|
+
it('rejects configuration with both name and srcDir missing', () => {
|
|
27
|
+
// @ts-ignore Testing invalid input
|
|
28
|
+
expect(() => validateProjectConfig({}, projectDir)).toThrow(/missing required fields:.*name.*srcDir/);
|
|
25
29
|
});
|
|
26
30
|
describe('rejects configuration with srcDir outside project directory', () => {
|
|
27
31
|
it('for parent directory', () => {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Collision } from '@hubspot/local-dev-lib/types/Archive';
|
|
2
2
|
import type { ProjectMetadata } from '@hubspot/project-parsing-lib/projects';
|
|
3
|
+
export interface ComponentInfo {
|
|
4
|
+
filename?: string;
|
|
5
|
+
isNew: boolean;
|
|
6
|
+
}
|
|
7
|
+
export type ComponentsByType = Map<string, ComponentInfo[]>;
|
|
8
|
+
export declare function buildProjectTree(projectName: string, uids: string[], componentsByType: ComponentsByType, showOnlyNew: boolean): string;
|
|
3
9
|
export declare function handleComponentCollision({ dest, src, collisions }: Collision): void;
|
|
4
10
|
export declare function updateHsMetaFilesWithAutoGeneratedFields(projectName: string, hsMetaFilePaths: string[], existingUids?: string[], options?: {
|
|
5
11
|
currentProjectMetadata?: ProjectMetadata;
|
|
@@ -12,7 +12,7 @@ import { renderInline } from '../../ui/render.js';
|
|
|
12
12
|
import { getSuccessBox } from '../../ui/components/StatusMessageBoxes.js';
|
|
13
13
|
// Prefix for the metafile extension
|
|
14
14
|
const metafileExtensionPrefix = path.parse(metafileExtension).name;
|
|
15
|
-
function buildProjectTree(projectName, uids, componentsByType, showOnlyNew) {
|
|
15
|
+
export function buildProjectTree(projectName, uids, componentsByType, showOnlyNew) {
|
|
16
16
|
const lines = [];
|
|
17
17
|
lines.push(chalk.bold(projectName));
|
|
18
18
|
const types = Array.from(componentsByType.keys());
|
package/lib/projects/config.js
CHANGED
|
@@ -52,8 +52,15 @@ export function validateProjectConfig(projectConfig, projectDir) {
|
|
|
52
52
|
if (!projectConfig || !projectDir) {
|
|
53
53
|
throw new ProjectValidationError(lib.projects.validateProjectConfig.configNotFound);
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
const missingFields = [];
|
|
56
|
+
if (!projectConfig.name) {
|
|
57
|
+
missingFields.push('name');
|
|
58
|
+
}
|
|
59
|
+
if (!projectConfig.srcDir) {
|
|
60
|
+
missingFields.push('srcDir');
|
|
61
|
+
}
|
|
62
|
+
if (missingFields.length > 0) {
|
|
63
|
+
throw new ProjectValidationError(lib.projects.validateProjectConfig.configMissingFields(missingFields));
|
|
57
64
|
}
|
|
58
65
|
const resolvedPath = path.resolve(projectDir, projectConfig.srcDir);
|
|
59
66
|
if (!resolvedPath.startsWith(projectDir)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type IntermediateRepresentationNodeLocalDev } from '@hubspot/project-parsing-lib/translate';
|
|
1
|
+
import { type IntermediateRepresentationNode, type IntermediateRepresentationNodeLocalDev } from '@hubspot/project-parsing-lib/translate';
|
|
2
2
|
import { Build } from '@hubspot/local-dev-lib/types/Build';
|
|
3
3
|
import { Project } from '@hubspot/local-dev-lib/types/Project';
|
|
4
4
|
import { ProjectConfig } from '../../../../types/Projects.js';
|
|
@@ -7,6 +7,9 @@ export declare function createInitialBuildForNewProject(projectConfig: ProjectCo
|
|
|
7
7
|
export declare function compareLocalProjectToDeployed(projectConfig: ProjectConfig, accountId: number, deployedBuildId: number | undefined, localProjectNodes: {
|
|
8
8
|
[key: string]: IntermediateRepresentationNodeLocalDev;
|
|
9
9
|
}, profile?: string): Promise<void>;
|
|
10
|
+
export declare function getDeployedProjectNodes(projectConfig: ProjectConfig, accountId: number, deployedBuildId: number, profile?: string): Promise<{
|
|
11
|
+
[key: string]: IntermediateRepresentationNode;
|
|
12
|
+
}>;
|
|
10
13
|
export declare function isDeployedProjectUpToDateWithLocal(projectConfig: ProjectConfig, accountId: number, deployedBuildId: number, localProjectNodes: {
|
|
11
14
|
[key: string]: IntermediateRepresentationNodeLocalDev;
|
|
12
15
|
}, profile?: string): Promise<boolean>;
|
|
@@ -149,31 +149,36 @@ export async function compareLocalProjectToDeployed(projectConfig, accountId, de
|
|
|
149
149
|
process.exit(EXIT_CODES.SUCCESS);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
-
export async function
|
|
152
|
+
export async function getDeployedProjectNodes(projectConfig, accountId, deployedBuildId, profile) {
|
|
153
153
|
let tempDir = null;
|
|
154
154
|
try {
|
|
155
155
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hubspot-project-compare-'));
|
|
156
156
|
const { data: zippedProject } = await downloadProject(accountId, projectConfig.name, deployedBuildId);
|
|
157
157
|
await extractZipArchive(zippedProject, sanitizeFileName(projectConfig.name), tempDir, { hideLogs: true });
|
|
158
158
|
const deployedProjectSourceDir = path.join(tempDir, projectConfig.srcDir);
|
|
159
|
-
const { intermediateNodesIndexedByUid
|
|
159
|
+
const { intermediateNodesIndexedByUid } = await translate({
|
|
160
160
|
projectSourceDir: deployedProjectSourceDir,
|
|
161
161
|
platformVersion: projectConfig.platformVersion,
|
|
162
162
|
accountId: accountId,
|
|
163
163
|
}, { profile });
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
catch (err) {
|
|
167
|
-
debugError(err);
|
|
168
|
-
return false;
|
|
164
|
+
return intermediateNodesIndexedByUid;
|
|
169
165
|
}
|
|
170
166
|
finally {
|
|
171
|
-
// Clean up temporary directory
|
|
172
167
|
if (tempDir && (await fs.pathExists(tempDir))) {
|
|
173
168
|
await fs.remove(tempDir);
|
|
174
169
|
}
|
|
175
170
|
}
|
|
176
171
|
}
|
|
172
|
+
export async function isDeployedProjectUpToDateWithLocal(projectConfig, accountId, deployedBuildId, localProjectNodes, profile) {
|
|
173
|
+
try {
|
|
174
|
+
const deployedProjectNodes = await getDeployedProjectNodes(projectConfig, accountId, deployedBuildId, profile);
|
|
175
|
+
return isDeepEqual(localProjectNodes, deployedProjectNodes, ['localDev']);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
debugError(err);
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
177
182
|
export async function checkAndInstallDependencies() {
|
|
178
183
|
uiLogger.log('');
|
|
179
184
|
SpinniesManager.add('checkingDependencies', {
|
|
@@ -1 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Used to surface warnings when users attempt to interact with new platform versions
|
|
3
|
+
* that were released after this version of the CLI was released.
|
|
4
|
+
*
|
|
5
|
+
* We are unable to reliably support versions of projects that are newer than any given CLI release
|
|
6
|
+
* */
|
|
7
|
+
export declare const LATEST_SUPPORTED_PLATFORM_VERSION = "2026.03";
|
|
1
8
|
export declare function isV2Project(platformVersion?: string | null): boolean;
|
|
9
|
+
export declare function isUnsupportedPlatformVersion(platformVersion?: string | null): boolean;
|