@hubspot/project-parsing-lib 0.2.0 → 0.2.1-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/README.md +4 -35
- package/package.json +124 -22
- package/src/exports/constants.d.ts +1 -0
- package/src/exports/constants.js +1 -0
- package/src/exports/lang.d.ts +1 -0
- package/src/exports/lang.js +1 -0
- package/src/exports/migrate.d.ts +1 -0
- package/src/exports/migrate.js +1 -0
- package/src/exports/profiles.d.ts +3 -0
- package/src/exports/profiles.js +2 -0
- package/src/exports/projects.d.ts +4 -0
- package/src/exports/projects.js +3 -0
- package/src/exports/schema.d.ts +2 -0
- package/src/exports/schema.js +1 -0
- package/src/exports/themes.d.ts +2 -0
- package/src/exports/themes.js +1 -0
- package/src/exports/transform.d.ts +2 -0
- package/src/exports/transform.js +1 -0
- package/src/exports/translate.d.ts +3 -0
- package/src/exports/translate.js +2 -0
- package/src/exports/uid.d.ts +1 -0
- package/src/exports/uid.js +1 -0
- package/src/exports/validation.d.ts +3 -0
- package/src/exports/validation.js +2 -0
- package/src/exports/workspaces.d.ts +2 -0
- package/src/exports/workspaces.js +1 -0
- package/src/lang/copy.d.ts +8 -1
- package/src/lang/copy.js +30 -33
- package/src/lib/constants.d.ts +55 -28
- package/src/lib/constants.js +172 -121
- package/src/lib/errors.d.ts +4 -3
- package/src/lib/errors.js +62 -38
- package/src/lib/files.d.ts +10 -1
- package/src/lib/files.js +51 -40
- package/src/lib/localDev.d.ts +4 -0
- package/src/lib/localDev.js +72 -0
- package/src/lib/migrate.d.ts +1 -0
- package/src/lib/migrate.js +43 -45
- package/src/lib/migrateThemes.d.ts +25 -0
- package/src/lib/migrateThemes.js +120 -0
- package/src/lib/minimalArboristTree.d.ts +118 -0
- package/src/lib/minimalArboristTree.js +32 -0
- package/src/lib/platformVersion.d.ts +3 -0
- package/src/lib/platformVersion.js +16 -0
- package/src/lib/profiles.d.ts +6 -1
- package/src/lib/profiles.js +95 -40
- package/src/lib/project.d.ts +13 -0
- package/src/lib/project.js +36 -0
- package/src/lib/schemas.d.ts +2 -2
- package/src/lib/schemas.js +11 -11
- package/src/lib/transform.d.ts +4 -2
- package/src/lib/transform.js +100 -53
- package/src/lib/translate.d.ts +3 -0
- package/src/lib/translate.js +62 -0
- package/src/lib/types.d.ts +30 -6
- package/src/lib/types.js +1 -2
- package/src/lib/uid.d.ts +2 -0
- package/src/lib/uid.js +14 -9
- package/src/lib/utils.d.ts +3 -0
- package/src/lib/utils.js +16 -0
- package/src/lib/validation.d.ts +4 -4
- package/src/lib/validation.js +61 -53
- package/src/lib/workspaces.d.ts +68 -0
- package/src/lib/workspaces.js +290 -0
- package/src/index.d.ts +0 -18
- package/src/index.js +0 -86
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PLATFORM_VERSIONS } from './constants.js';
|
|
2
|
+
// Used for logging suggestions to users whenever we encouter an unknown version
|
|
3
|
+
export const LATEST_SUPPORTED_PLATFORM_VERSION = PLATFORM_VERSIONS.v2026_03;
|
|
4
|
+
// We are unable to reliably interact versions of projects that are not officially supported by this library
|
|
5
|
+
export function isSupportedPlatformVersion(platformVersion) {
|
|
6
|
+
if (!platformVersion || typeof platformVersion !== 'string') {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
const supportedPlatformVersions = Object.values(PLATFORM_VERSIONS);
|
|
10
|
+
return supportedPlatformVersions.includes(platformVersion);
|
|
11
|
+
}
|
|
12
|
+
export function isLegacyProject(platformVersion) {
|
|
13
|
+
if (!platformVersion)
|
|
14
|
+
return false;
|
|
15
|
+
return [PLATFORM_VERSIONS.v2023_2, PLATFORM_VERSIONS.v2025_1].includes(platformVersion);
|
|
16
|
+
}
|
package/src/lib/profiles.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { FileParseResult, HsProfileFile } from './types';
|
|
1
|
+
import { FileParseResult, HsProfileFile, HSProfileVariables } from './types.js';
|
|
2
2
|
export declare function getIsProfileFile(filename: string): boolean;
|
|
3
3
|
export declare function getHsProfileFilename(profileName: string): string;
|
|
4
4
|
export declare function getHsProfileName(profileFilename: string): string;
|
|
5
|
+
export declare function getHsProfileVariables(hsProfileContents: HsProfileFile): HSProfileVariables;
|
|
6
|
+
export declare function validateProfileVariables(profileVariables: HSProfileVariables, filename: string): {
|
|
7
|
+
success: boolean;
|
|
8
|
+
errors: string[];
|
|
9
|
+
};
|
|
5
10
|
export declare function applyHsProfileVariables(fileParseResults: FileParseResult[], hsProfileContents: HsProfileFile): FileParseResult[];
|
package/src/lib/profiles.js
CHANGED
|
@@ -1,56 +1,111 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
exports.getHsProfileName = getHsProfileName;
|
|
6
|
-
exports.applyHsProfileVariables = applyHsProfileVariables;
|
|
7
|
-
const constants_1 = require("./constants");
|
|
8
|
-
const copy_1 = require("../lang/copy");
|
|
9
|
-
const profileFilenameRegex = new RegExp(`^${constants_1.profileFilePrefix}\\.([^.]+)\\.json$`);
|
|
1
|
+
import { PROFILE_FILE_PREFIX } from './constants.js';
|
|
2
|
+
import { errorMessages } from '../lang/copy.js';
|
|
3
|
+
const profileFilenameRegex = new RegExp(`^${PROFILE_FILE_PREFIX}\\.([^.]+)\\.json$`);
|
|
4
|
+
// Regex to match variable placeholders like ${variableName}
|
|
10
5
|
const profileInsertRegex = /\${(.*?)}/g;
|
|
11
|
-
|
|
6
|
+
// Helper to check if string is a single variable reference (e.g., "${port}" but not "http://${port}")
|
|
7
|
+
// Returns the variable key if it matches, null otherwise
|
|
8
|
+
function isSingleVariableReference(str) {
|
|
9
|
+
const match = str.match(/^\${([^}]+)}$/);
|
|
10
|
+
return match ? match[1] : null;
|
|
11
|
+
}
|
|
12
|
+
export function getIsProfileFile(filename) {
|
|
12
13
|
return profileFilenameRegex.test(filename);
|
|
13
14
|
}
|
|
14
|
-
function getHsProfileFilename(profileName) {
|
|
15
|
-
return `${
|
|
15
|
+
export function getHsProfileFilename(profileName) {
|
|
16
|
+
return `${PROFILE_FILE_PREFIX}.${profileName}.json`;
|
|
16
17
|
}
|
|
17
|
-
function getHsProfileName(profileFilename) {
|
|
18
|
+
export function getHsProfileName(profileFilename) {
|
|
18
19
|
const match = profileFilename.match(profileFilenameRegex);
|
|
19
20
|
return match ? match[1] : '';
|
|
20
21
|
}
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
export function getHsProfileVariables(hsProfileContents) {
|
|
23
|
+
const profileVariablesAreValid = hsProfileContents?.variables &&
|
|
24
|
+
typeof hsProfileContents.variables === 'object' &&
|
|
25
|
+
!Array.isArray(hsProfileContents.variables);
|
|
26
|
+
return profileVariablesAreValid ? hsProfileContents.variables : {};
|
|
27
|
+
}
|
|
28
|
+
export function validateProfileVariables(profileVariables, filename) {
|
|
29
|
+
const errors = [];
|
|
30
|
+
for (const [variableKey, variableValue] of Object.entries(profileVariables)) {
|
|
31
|
+
try {
|
|
32
|
+
const variableType = typeof variableValue;
|
|
33
|
+
validateProfileVariable(variableType, variableKey, filename);
|
|
26
34
|
}
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
catch (error) {
|
|
36
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
37
|
+
errors.push(errorMessage);
|
|
29
38
|
}
|
|
30
|
-
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
success: errors.length === 0,
|
|
42
|
+
errors,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function validateProfileVariable(variableType, variableKey, filename) {
|
|
46
|
+
const allowedTypes = ['string', 'number', 'boolean'];
|
|
47
|
+
if (variableType === 'undefined') {
|
|
48
|
+
throw new Error(errorMessages.profile.missingValue(variableKey, filename));
|
|
49
|
+
}
|
|
50
|
+
if (!allowedTypes.includes(variableType)) {
|
|
51
|
+
throw new Error(errorMessages.profile.invalidValue(variableKey));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function interpolate(file, template, profileVariables) {
|
|
55
|
+
return template.replace(profileInsertRegex, (_fullMatch, variableKey) => {
|
|
56
|
+
const variableValue = profileVariables[variableKey];
|
|
57
|
+
const variableType = typeof variableValue;
|
|
58
|
+
validateProfileVariable(variableType, variableKey, file);
|
|
59
|
+
return String(variableValue);
|
|
31
60
|
});
|
|
32
61
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
62
|
+
function replaceVariablesInValue(value, file, profileVariables) {
|
|
63
|
+
// Handle string values with variable substitution
|
|
64
|
+
if (typeof value === 'string') {
|
|
65
|
+
// If the entire value is a single variable (e.g., "${port}"),
|
|
66
|
+
// return the typed value to preserve number/boolean types
|
|
67
|
+
const variableKey = isSingleVariableReference(value);
|
|
68
|
+
if (variableKey) {
|
|
69
|
+
const variableValue = profileVariables[variableKey];
|
|
70
|
+
const variableType = typeof variableValue;
|
|
71
|
+
validateProfileVariable(variableType, variableKey, file);
|
|
72
|
+
return variableValue;
|
|
73
|
+
}
|
|
74
|
+
// Otherwise, perform string interpolation (no-op if no variables found)
|
|
75
|
+
// For strings with multiple variables (e.g., "http://${host}:${port}"),
|
|
76
|
+
// this converts all values to strings
|
|
77
|
+
return interpolate(file, value, profileVariables);
|
|
78
|
+
}
|
|
79
|
+
// Recursively handle arrays
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
return value.map(item => replaceVariablesInValue(item, file, profileVariables));
|
|
82
|
+
}
|
|
83
|
+
// Recursively handle objects
|
|
84
|
+
if (value !== null && typeof value === 'object') {
|
|
85
|
+
const result = {};
|
|
86
|
+
for (const [key, val] of Object.entries(value)) {
|
|
87
|
+
result[key] = replaceVariablesInValue(val, file, profileVariables);
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
// Return primitive values as-is
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
export function applyHsProfileVariables(fileParseResults, hsProfileContents) {
|
|
95
|
+
const profileVariables = getHsProfileVariables(hsProfileContents);
|
|
39
96
|
return fileParseResults.map(fileParseResult => {
|
|
40
97
|
const { content, file } = fileParseResult;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (interpolatedConfig) {
|
|
45
|
-
return {
|
|
46
|
-
...fileParseResult,
|
|
47
|
-
content: {
|
|
48
|
-
...content,
|
|
49
|
-
config: interpolatedConfig,
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
}
|
|
98
|
+
// Only process files that have content with a config section
|
|
99
|
+
if (!content?.config) {
|
|
100
|
+
return fileParseResult;
|
|
53
101
|
}
|
|
54
|
-
|
|
102
|
+
const configWithVariablesReplaced = replaceVariablesInValue(content.config, file, profileVariables);
|
|
103
|
+
return {
|
|
104
|
+
...fileParseResult,
|
|
105
|
+
content: {
|
|
106
|
+
...content,
|
|
107
|
+
config: configWithVariablesReplaced,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
55
110
|
});
|
|
56
111
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface ComponentMetadata {
|
|
2
|
+
count: number;
|
|
3
|
+
maxCount: number;
|
|
4
|
+
hsMetaFiles: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface ComponentMetadataMap {
|
|
7
|
+
[componentType: string]: ComponentMetadata;
|
|
8
|
+
}
|
|
9
|
+
export interface ProjectMetadata {
|
|
10
|
+
hsMetaFiles: string[];
|
|
11
|
+
components: ComponentMetadataMap;
|
|
12
|
+
}
|
|
13
|
+
export declare function getProjectMetadata(projectSrcDir: string): Promise<ProjectMetadata>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Components } from './constants.js';
|
|
2
|
+
import { locateHsMetaFiles } from './files.js';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
export async function getProjectMetadata(projectSrcDir) {
|
|
6
|
+
const hsMetaFiles = [];
|
|
7
|
+
const components = {};
|
|
8
|
+
let metafiles = [];
|
|
9
|
+
if (fs.existsSync(projectSrcDir)) {
|
|
10
|
+
metafiles = await locateHsMetaFiles(projectSrcDir, { silent: true });
|
|
11
|
+
}
|
|
12
|
+
const seenComponentPaths = new Map();
|
|
13
|
+
Object.keys(Components).forEach(componentType => {
|
|
14
|
+
const { parentComponent, singularComponent, dir } = Components[componentType];
|
|
15
|
+
const componentPath = path.join(projectSrcDir, parentComponent ? path.join(parentComponent, dir) : dir);
|
|
16
|
+
if (seenComponentPaths.has(componentPath)) {
|
|
17
|
+
components[componentType] = seenComponentPaths.get(componentPath);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const metaFilesByType = metafiles
|
|
21
|
+
.filter(metafile => path.parse(metafile.file).dir === componentPath)
|
|
22
|
+
.map(metaFile => metaFile.file);
|
|
23
|
+
const metadata = {
|
|
24
|
+
hsMetaFiles: metaFilesByType,
|
|
25
|
+
count: metaFilesByType.length,
|
|
26
|
+
maxCount: singularComponent ? 1 : Infinity,
|
|
27
|
+
};
|
|
28
|
+
hsMetaFiles.push(...metaFilesByType);
|
|
29
|
+
seenComponentPaths.set(componentPath, metadata);
|
|
30
|
+
components[componentType] = metadata;
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
hsMetaFiles,
|
|
34
|
+
components,
|
|
35
|
+
};
|
|
36
|
+
}
|
package/src/lib/schemas.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { AnySchema } from 'ajv
|
|
2
|
-
import { TranslationContext } from './types';
|
|
1
|
+
import type { AnySchema } from 'ajv';
|
|
2
|
+
import { TranslationContext } from './types.js';
|
|
3
3
|
export declare function getIntermediateRepresentationSchema(translationContext: TranslationContext): Promise<Record<string, AnySchema>>;
|
package/src/lib/schemas.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const index_1 = require("@hubspot/local-dev-lib/errors/index");
|
|
6
|
-
const copy_1 = require("../lang/copy");
|
|
7
|
-
async function getIntermediateRepresentationSchema(translationContext) {
|
|
1
|
+
import { http } from '@hubspot/local-dev-lib/http';
|
|
2
|
+
import { isHubSpotHttpError, isSpecifiedError, } from '@hubspot/local-dev-lib/errors/index';
|
|
3
|
+
import { errorMessages } from '../lang/copy.js';
|
|
4
|
+
export async function getIntermediateRepresentationSchema(translationContext) {
|
|
8
5
|
if (!translationContext.accountId) {
|
|
9
|
-
throw new Error(
|
|
6
|
+
throw new Error(errorMessages.api.accountIdIsRequiredToFetchSchemas);
|
|
10
7
|
}
|
|
11
8
|
try {
|
|
12
9
|
const { accountId, platformVersion } = translationContext;
|
|
13
|
-
const { data } = await
|
|
10
|
+
const { data } = await http.get(accountId, {
|
|
14
11
|
url: `project-components-external/project-schemas/v3/${platformVersion}`,
|
|
15
12
|
});
|
|
16
13
|
return data;
|
|
17
14
|
}
|
|
18
15
|
catch (e) {
|
|
19
|
-
if ((
|
|
16
|
+
if (isSpecifiedError(e, { statusCode: 403 })) {
|
|
17
|
+
throw new Error(errorMessages.api.failedToFetchSchemas403, { cause: e });
|
|
18
|
+
}
|
|
19
|
+
if (isHubSpotHttpError(e)) {
|
|
20
20
|
throw e;
|
|
21
21
|
}
|
|
22
|
-
throw new Error(
|
|
22
|
+
throw new Error(errorMessages.api.failedToFetchSchemas, { cause: e });
|
|
23
23
|
}
|
|
24
24
|
}
|
package/src/lib/transform.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Dependencies, FileParseResult, HsProfileFile, IntermediateRepresentation, Transformation, TranslationContext } from './types';
|
|
1
|
+
import { Dependencies, FileParseResult, HsProfileFile, IntermediateRepresentation, Transformation, TranslationContext } from './types.js';
|
|
2
2
|
export declare function mapToInternalType(type: string): string;
|
|
3
3
|
export declare function mapToUserFacingType(type: string): string;
|
|
4
4
|
export declare function mapToUserFriendlyName(internalType: string): string;
|
|
5
|
+
export declare function findTransformationByUid(transformations: Transformation[], uid: string): Transformation | undefined;
|
|
5
6
|
export declare function transform(fileParseResults: FileParseResult[], translationContext: TranslationContext, hsProfileContents?: HsProfileFile): Transformation[];
|
|
6
|
-
export declare function getIntermediateRepresentation(transformations: Transformation[], skipValidation
|
|
7
|
+
export declare function getIntermediateRepresentation(transformations: Transformation[], hsProfileContents?: HsProfileFile, skipValidation?: boolean): IntermediateRepresentation;
|
|
8
|
+
export declare function getParsingErrors(transformations: Transformation[]): FileParseResult[];
|
|
7
9
|
export declare function generateServerlessPackageComponent(appFunctionsDirectory: string, translationContext: TranslationContext, componentDeps: Dependencies): {
|
|
8
10
|
uid: string;
|
|
9
11
|
transformation: Transformation;
|
package/src/lib/transform.js
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
exports.transform = transform;
|
|
10
|
-
exports.getIntermediateRepresentation = getIntermediateRepresentation;
|
|
11
|
-
exports.generateServerlessPackageComponent = generateServerlessPackageComponent;
|
|
12
|
-
const profiles_1 = require("./profiles");
|
|
13
|
-
const constants_1 = require("./constants");
|
|
14
|
-
const copy_1 = require("../lang/copy");
|
|
15
|
-
const path_1 = __importDefault(require("path"));
|
|
16
|
-
const fs_1 = __importDefault(require("fs"));
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { logger } from '@hubspot/local-dev-lib/logger';
|
|
4
|
+
import { applyHsProfileVariables, getHsProfileVariables } from './profiles.js';
|
|
5
|
+
import { APP_KEY, APP_OBJECT_KEY, Components, PROJECT_STRUCTURE, APP_FUNCTIONS_KEY, APP_FUNCTIONS_PACKAGE_KEY, USER_FACING_TO_INTERNAL_TYPE, INTERNAL_TYPE_TO_USER_FACING, PACKAGE_JSON, PACKAGE_LOCK_JSON, PROFILE_VARIABLE_TYPES, } from './constants.js';
|
|
6
|
+
import { errorMessages } from '../lang/copy.js';
|
|
7
|
+
import { getJavaNumberType } from './utils.js';
|
|
8
|
+
import { convertPathToPosixPath } from './files.js';
|
|
17
9
|
function calculateComponentDeps(fileValidationResult, parentComponents, appObjects, appFunctionsPackageUid) {
|
|
18
10
|
let dependencies = {};
|
|
19
11
|
// If there are dependencies in the config file, pass them through
|
|
@@ -22,55 +14,65 @@ function calculateComponentDeps(fileValidationResult, parentComponents, appObjec
|
|
|
22
14
|
}
|
|
23
15
|
const { type } = fileValidationResult.content;
|
|
24
16
|
// If the component is a child of the App component, add the app and appObjects as dependencies
|
|
25
|
-
if (
|
|
26
|
-
const parentUid = parentComponents[
|
|
17
|
+
if (Components[type]?.parentComponent === APP_KEY) {
|
|
18
|
+
const parentUid = parentComponents[APP_KEY];
|
|
27
19
|
if (parentUid) {
|
|
28
20
|
dependencies.app = parentUid;
|
|
29
21
|
}
|
|
30
|
-
|
|
22
|
+
else {
|
|
23
|
+
logger.debug(parentComponents);
|
|
24
|
+
return {
|
|
25
|
+
dependencies,
|
|
26
|
+
errors: [errorMessages.project.mustHaveAppComponent(type)],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (type !== APP_OBJECT_KEY) {
|
|
31
30
|
dependencies.allAppObjects = appObjects;
|
|
32
31
|
}
|
|
33
|
-
if (type ===
|
|
32
|
+
if (type === APP_FUNCTIONS_KEY && appFunctionsPackageUid) {
|
|
34
33
|
dependencies.serverlessPackage = appFunctionsPackageUid;
|
|
35
34
|
}
|
|
36
35
|
}
|
|
37
|
-
return dependencies;
|
|
36
|
+
return { dependencies };
|
|
38
37
|
}
|
|
39
|
-
function mapToInternalType(type) {
|
|
40
|
-
const resolvedType =
|
|
38
|
+
export function mapToInternalType(type) {
|
|
39
|
+
const resolvedType = USER_FACING_TO_INTERNAL_TYPE[type] || type || '';
|
|
41
40
|
return resolvedType.toUpperCase().replace(/-/g, '_');
|
|
42
41
|
}
|
|
43
|
-
function mapToUserFacingType(type) {
|
|
44
|
-
return (
|
|
42
|
+
export function mapToUserFacingType(type) {
|
|
43
|
+
return (INTERNAL_TYPE_TO_USER_FACING[type] || type || '')
|
|
45
44
|
.toLowerCase()
|
|
46
45
|
.replace(/_/g, '-');
|
|
47
46
|
}
|
|
48
|
-
function mapToUserFriendlyName(internalType) {
|
|
49
|
-
return
|
|
47
|
+
export function mapToUserFriendlyName(internalType) {
|
|
48
|
+
return Components[mapToUserFacingType(internalType)]?.userFriendlyName || '';
|
|
50
49
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
50
|
+
export function findTransformationByUid(transformations, uid) {
|
|
51
|
+
return transformations.find(t => t.intermediateRepresentation?.uid === uid);
|
|
52
|
+
}
|
|
53
|
+
export function transform(fileParseResults, translationContext, hsProfileContents) {
|
|
54
|
+
const parentTypes = Object.keys(PROJECT_STRUCTURE);
|
|
53
55
|
const parentComponents = {};
|
|
54
56
|
const allAppObjects = [];
|
|
55
57
|
let appUid = '';
|
|
56
58
|
let appFunctionsDirectory;
|
|
57
59
|
// Apply the profile variable overrides to the config
|
|
58
60
|
if (hsProfileContents) {
|
|
59
|
-
fileParseResults =
|
|
61
|
+
fileParseResults = applyHsProfileVariables(fileParseResults, hsProfileContents);
|
|
60
62
|
}
|
|
61
63
|
// Compute the parent components, so we can add them as dependencies to the child components
|
|
62
64
|
fileParseResults.forEach(file => {
|
|
63
65
|
if (file.content?.type && parentTypes.includes(file.content.type)) {
|
|
64
|
-
if (file.content.type ===
|
|
66
|
+
if (file.content.type === APP_KEY) {
|
|
65
67
|
appUid = file.content.uid;
|
|
66
68
|
}
|
|
67
69
|
parentComponents[file.content.type] = file.content.uid;
|
|
68
70
|
}
|
|
69
|
-
if (file.content?.type ===
|
|
71
|
+
if (file.content?.type === APP_OBJECT_KEY) {
|
|
70
72
|
allAppObjects.push(file.content.uid);
|
|
71
73
|
}
|
|
72
|
-
if (file.content?.type ===
|
|
73
|
-
appFunctionsDirectory =
|
|
74
|
+
if (file.content?.type === APP_FUNCTIONS_KEY) {
|
|
75
|
+
appFunctionsDirectory = path.dirname(file.file);
|
|
74
76
|
}
|
|
75
77
|
});
|
|
76
78
|
const autoGeneratedComponents = [];
|
|
@@ -85,20 +87,26 @@ function transform(fileParseResults, translationContext, hsProfileContents) {
|
|
|
85
87
|
}
|
|
86
88
|
const transformations = fileParseResults.map((currentFile) => {
|
|
87
89
|
if (!currentFile.content) {
|
|
88
|
-
currentFile.errors?.
|
|
90
|
+
if (!currentFile.errors?.includes(errorMessages.validation.invalidJson)) {
|
|
91
|
+
currentFile.errors?.push(errorMessages.project.fileContentMissingFor(currentFile.file));
|
|
92
|
+
}
|
|
89
93
|
return {
|
|
90
94
|
intermediateRepresentation: null,
|
|
91
95
|
fileParseResult: currentFile,
|
|
92
96
|
};
|
|
93
97
|
}
|
|
94
98
|
const { config, uid, type } = currentFile.content;
|
|
99
|
+
const { dependencies, errors } = calculateComponentDeps(currentFile, parentComponents, allAppObjects, serverlessPackageUid);
|
|
100
|
+
if (errors) {
|
|
101
|
+
currentFile.errors?.push(...errors);
|
|
102
|
+
}
|
|
95
103
|
return {
|
|
96
104
|
intermediateRepresentation: {
|
|
97
105
|
uid,
|
|
98
106
|
config,
|
|
99
107
|
componentType: mapToInternalType(type),
|
|
100
|
-
componentDeps:
|
|
101
|
-
metaFilePath: currentFile.file,
|
|
108
|
+
componentDeps: dependencies,
|
|
109
|
+
metaFilePath: convertPathToPosixPath(currentFile.file),
|
|
102
110
|
files: {},
|
|
103
111
|
},
|
|
104
112
|
fileParseResult: currentFile,
|
|
@@ -106,7 +114,7 @@ function transform(fileParseResults, translationContext, hsProfileContents) {
|
|
|
106
114
|
});
|
|
107
115
|
return [...autoGeneratedComponents, ...transformations];
|
|
108
116
|
}
|
|
109
|
-
function getIntermediateRepresentation(transformations, skipValidation) {
|
|
117
|
+
export function getIntermediateRepresentation(transformations, hsProfileContents, skipValidation) {
|
|
110
118
|
const nodes = transformations.reduce((acc, current) => {
|
|
111
119
|
if (!current.intermediateRepresentation) {
|
|
112
120
|
return acc;
|
|
@@ -116,15 +124,14 @@ function getIntermediateRepresentation(transformations, skipValidation) {
|
|
|
116
124
|
const duplicates = transformations
|
|
117
125
|
.filter(t => t.intermediateRepresentation?.uid === uid)
|
|
118
126
|
.map(t => t.fileParseResult.file);
|
|
119
|
-
throw new Error(
|
|
127
|
+
throw new Error(errorMessages.project.duplicateUid(uid, duplicates));
|
|
120
128
|
}
|
|
121
129
|
if (!skipValidation) {
|
|
122
130
|
return {
|
|
123
131
|
...acc,
|
|
124
132
|
// If the uid is not defined just make one up for the sake of indexing.
|
|
125
133
|
// It will still fail validation, but this prevents collisions so we validate all files.
|
|
126
|
-
[uid ||
|
|
127
|
-
`missing-${current.fileParseResult.file}`]: current.intermediateRepresentation,
|
|
134
|
+
[uid || `missing-${current.fileParseResult.file}`]: current.intermediateRepresentation,
|
|
128
135
|
};
|
|
129
136
|
}
|
|
130
137
|
return {
|
|
@@ -132,17 +139,55 @@ function getIntermediateRepresentation(transformations, skipValidation) {
|
|
|
132
139
|
[uid]: current.intermediateRepresentation,
|
|
133
140
|
};
|
|
134
141
|
}, {});
|
|
142
|
+
const profileData = getProfileData(hsProfileContents);
|
|
135
143
|
return {
|
|
136
144
|
intermediateNodesIndexedByUid: nodes,
|
|
145
|
+
profileData,
|
|
137
146
|
};
|
|
138
147
|
}
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
export function getParsingErrors(transformations) {
|
|
149
|
+
return transformations
|
|
150
|
+
.filter(t => t.fileParseResult.errors.length > 0)
|
|
151
|
+
.map(t => t.fileParseResult);
|
|
152
|
+
}
|
|
153
|
+
function getProfileData(hsProfileContents) {
|
|
154
|
+
const profileVariablesForBE = {};
|
|
155
|
+
if (hsProfileContents && hsProfileContents.variables) {
|
|
156
|
+
const profileVariables = getHsProfileVariables(hsProfileContents);
|
|
157
|
+
Object.keys(profileVariables).forEach(key => {
|
|
158
|
+
const profileVar = profileVariables[key];
|
|
159
|
+
let variableTypeForBE;
|
|
160
|
+
switch (typeof profileVar) {
|
|
161
|
+
case 'string':
|
|
162
|
+
variableTypeForBE = PROFILE_VARIABLE_TYPES.PROFILE_STRING;
|
|
163
|
+
break;
|
|
164
|
+
case 'number':
|
|
165
|
+
variableTypeForBE = getJavaNumberType(profileVar);
|
|
166
|
+
break;
|
|
167
|
+
case 'boolean':
|
|
168
|
+
variableTypeForBE = PROFILE_VARIABLE_TYPES.PROFILE_BOOLEAN;
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
if (variableTypeForBE) {
|
|
174
|
+
profileVariablesForBE[key] = {
|
|
175
|
+
variableType: variableTypeForBE,
|
|
176
|
+
value: profileVar,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return { vars: { profileVariables: profileVariablesForBE } };
|
|
182
|
+
}
|
|
183
|
+
export function generateServerlessPackageComponent(appFunctionsDirectory, translationContext, componentDeps) {
|
|
184
|
+
const appFunctionsPosix = convertPathToPosixPath(appFunctionsDirectory);
|
|
185
|
+
const packageFilePosix = path.posix.join(appFunctionsPosix, PACKAGE_JSON);
|
|
186
|
+
const packageLockFilePosix = path.posix.join(appFunctionsPosix, PACKAGE_LOCK_JSON);
|
|
187
|
+
const projectSourceDirPosix = convertPathToPosixPath(translationContext.projectSourceDir);
|
|
188
|
+
const appFunctionsPackageFile = path.posix.join(projectSourceDirPosix, packageFilePosix);
|
|
189
|
+
if (!fs.existsSync(appFunctionsPackageFile)) {
|
|
190
|
+
throw new Error(errorMessages.project.noPackageJsonForServerless(appFunctionsPackageFile));
|
|
146
191
|
}
|
|
147
192
|
const uid = `hubspot-serverless-package-uid`;
|
|
148
193
|
return {
|
|
@@ -154,15 +199,17 @@ function generateServerlessPackageComponent(appFunctionsDirectory, translationCo
|
|
|
154
199
|
},
|
|
155
200
|
intermediateRepresentation: {
|
|
156
201
|
uid,
|
|
157
|
-
componentType: mapToInternalType(
|
|
202
|
+
componentType: mapToInternalType(APP_FUNCTIONS_PACKAGE_KEY),
|
|
158
203
|
config: {
|
|
159
|
-
packageFile,
|
|
160
|
-
packageLockfile:
|
|
161
|
-
|
|
204
|
+
packageFile: packageFilePosix,
|
|
205
|
+
packageLockfile: fs.existsSync(
|
|
206
|
+
// Don't use posix here because we are checking the file system
|
|
207
|
+
path.join(translationContext.projectSourceDir, path.join(appFunctionsDirectory, PACKAGE_LOCK_JSON)))
|
|
208
|
+
? packageLockFilePosix
|
|
162
209
|
: undefined,
|
|
163
210
|
},
|
|
164
211
|
componentDeps,
|
|
165
|
-
metaFilePath:
|
|
212
|
+
metaFilePath: packageFilePosix,
|
|
166
213
|
files: {},
|
|
167
214
|
},
|
|
168
215
|
},
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { IntermediateRepresentation, IntermediateRepresentationLocalDev, TranslationContext, TranslationOptions, TranslationOptionsLocalDev } from './types.js';
|
|
2
|
+
export declare function translate(translationContext: TranslationContext, translationOptions?: TranslationOptions): Promise<IntermediateRepresentation>;
|
|
3
|
+
export declare function translateForLocalDev(translationContext: TranslationContext, translationOptions?: TranslationOptionsLocalDev): Promise<IntermediateRepresentationLocalDev>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { loadHsProfileFile, loadHsMetaFiles } from './files.js';
|
|
2
|
+
import { validateIntermediateRepresentation } from './validation.js';
|
|
3
|
+
import { getIntermediateRepresentation, getParsingErrors, transform, } from './transform.js';
|
|
4
|
+
import { errorMessages } from '../lang/copy.js';
|
|
5
|
+
import { getLocalDevProfileData, getLocalDevProjectNodes, getRemovedNodesAndNodesWithErrors, } from './localDev.js';
|
|
6
|
+
import { isSupportedPlatformVersion } from './platformVersion.js';
|
|
7
|
+
const defaultOptions = {
|
|
8
|
+
skipValidation: false,
|
|
9
|
+
};
|
|
10
|
+
export async function translate(translationContext, translationOptions = defaultOptions) {
|
|
11
|
+
const { skipValidation, profile } = translationOptions;
|
|
12
|
+
if (!skipValidation &&
|
|
13
|
+
!isSupportedPlatformVersion(translationContext.platformVersion)) {
|
|
14
|
+
throw new Error(errorMessages.project.unsupportedPlatformVersion(translationContext.platformVersion));
|
|
15
|
+
}
|
|
16
|
+
const metafileContents = await loadHsMetaFiles(translationContext);
|
|
17
|
+
if (metafileContents.length === 0) {
|
|
18
|
+
throw new Error(errorMessages.project.noHsMetaFiles);
|
|
19
|
+
}
|
|
20
|
+
let hsProfileContents;
|
|
21
|
+
if (profile) {
|
|
22
|
+
hsProfileContents = loadHsProfileFile(translationContext.projectSourceDir, profile);
|
|
23
|
+
}
|
|
24
|
+
const transformations = transform(metafileContents, translationContext, hsProfileContents);
|
|
25
|
+
const intermediateRepresentation = getIntermediateRepresentation(transformations, hsProfileContents, skipValidation);
|
|
26
|
+
// Remove once extensions and serverless functions are supported
|
|
27
|
+
if (!skipValidation) {
|
|
28
|
+
await validateIntermediateRepresentation(intermediateRepresentation, transformations, translationContext);
|
|
29
|
+
}
|
|
30
|
+
return intermediateRepresentation;
|
|
31
|
+
}
|
|
32
|
+
export async function translateForLocalDev(translationContext, translationOptions) {
|
|
33
|
+
const { skipValidation, profile, projectNodesAtLastUpload } = translationOptions || {};
|
|
34
|
+
if (!skipValidation &&
|
|
35
|
+
!isSupportedPlatformVersion(translationContext.platformVersion)) {
|
|
36
|
+
throw new Error(errorMessages.project.unsupportedPlatformVersion(translationContext.platformVersion));
|
|
37
|
+
}
|
|
38
|
+
const metafileContents = await loadHsMetaFiles(translationContext);
|
|
39
|
+
if (metafileContents.length === 0) {
|
|
40
|
+
throw new Error(errorMessages.project.noHsMetaFiles);
|
|
41
|
+
}
|
|
42
|
+
let hsProfileContents;
|
|
43
|
+
if (profile) {
|
|
44
|
+
hsProfileContents = loadHsProfileFile(translationContext.projectSourceDir, profile);
|
|
45
|
+
}
|
|
46
|
+
const transformation = transform(metafileContents, translationContext, hsProfileContents);
|
|
47
|
+
const baseIntermediateRepresentation = getIntermediateRepresentation(transformation, hsProfileContents, true);
|
|
48
|
+
const parsingErrors = getParsingErrors(transformation);
|
|
49
|
+
const projectNodes = getLocalDevProjectNodes(baseIntermediateRepresentation.intermediateNodesIndexedByUid, translationContext.projectSourceDir, projectNodesAtLastUpload);
|
|
50
|
+
let projectNodesWithErrors = {};
|
|
51
|
+
if (projectNodesAtLastUpload) {
|
|
52
|
+
projectNodesWithErrors = getRemovedNodesAndNodesWithErrors(baseIntermediateRepresentation.intermediateNodesIndexedByUid, projectNodesAtLastUpload, parsingErrors);
|
|
53
|
+
}
|
|
54
|
+
const profileData = getLocalDevProfileData(baseIntermediateRepresentation.profileData?.vars.profileVariables);
|
|
55
|
+
return {
|
|
56
|
+
intermediateNodesIndexedByUid: {
|
|
57
|
+
...projectNodes,
|
|
58
|
+
...projectNodesWithErrors,
|
|
59
|
+
},
|
|
60
|
+
profileData,
|
|
61
|
+
};
|
|
62
|
+
}
|