@hubspot/project-parsing-lib 0.2.2-beta.0 → 0.2.2-experimental.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +4 -35
  2. package/package.json +128 -22
  3. package/src/exports/constants.d.ts +1 -0
  4. package/src/exports/constants.js +1 -0
  5. package/src/exports/lang.d.ts +1 -0
  6. package/src/exports/lang.js +1 -0
  7. package/src/exports/migrate.d.ts +1 -0
  8. package/src/exports/migrate.js +1 -0
  9. package/src/exports/profiles.d.ts +3 -0
  10. package/src/exports/profiles.js +2 -0
  11. package/src/exports/projects.d.ts +4 -0
  12. package/src/exports/projects.js +3 -0
  13. package/src/exports/schema.d.ts +2 -0
  14. package/src/exports/schema.js +1 -0
  15. package/src/exports/themes.d.ts +2 -0
  16. package/src/exports/themes.js +1 -0
  17. package/src/exports/transform.d.ts +2 -0
  18. package/src/exports/transform.js +1 -0
  19. package/src/exports/translate.d.ts +3 -0
  20. package/src/exports/translate.js +2 -0
  21. package/src/exports/uid.d.ts +1 -0
  22. package/src/exports/uid.js +1 -0
  23. package/src/exports/validation.d.ts +3 -0
  24. package/src/exports/validation.js +2 -0
  25. package/src/exports/workspaces.d.ts +2 -0
  26. package/src/exports/workspaces.js +1 -0
  27. package/src/lang/copy.d.ts +12 -1
  28. package/src/lang/copy.js +33 -32
  29. package/src/lib/constants.d.ts +56 -29
  30. package/src/lib/constants.js +180 -126
  31. package/src/lib/errors.d.ts +3 -3
  32. package/src/lib/errors.js +25 -33
  33. package/src/lib/files.d.ts +3 -3
  34. package/src/lib/files.js +46 -42
  35. package/src/lib/localDev.d.ts +4 -0
  36. package/src/lib/localDev.js +72 -0
  37. package/src/lib/migrate.d.ts +1 -0
  38. package/src/lib/migrate.js +43 -45
  39. package/src/lib/migrateThemes.d.ts +25 -0
  40. package/src/lib/migrateThemes.js +120 -0
  41. package/src/lib/minimalArboristTree.d.ts +118 -0
  42. package/src/lib/minimalArboristTree.js +32 -0
  43. package/src/lib/platformVersion.d.ts +4 -0
  44. package/src/lib/platformVersion.js +30 -0
  45. package/src/lib/profiles.d.ts +6 -1
  46. package/src/lib/profiles.js +95 -40
  47. package/src/lib/project.js +22 -17
  48. package/src/lib/schemas.d.ts +2 -2
  49. package/src/lib/schemas.js +11 -11
  50. package/src/lib/transform.d.ts +4 -2
  51. package/src/lib/transform.js +109 -59
  52. package/src/lib/translate.d.ts +3 -0
  53. package/src/lib/translate.js +62 -0
  54. package/src/lib/types.d.ts +37 -6
  55. package/src/lib/types.js +1 -2
  56. package/src/lib/uid.d.ts +2 -0
  57. package/src/lib/uid.js +14 -9
  58. package/src/lib/utils.d.ts +3 -0
  59. package/src/lib/utils.js +16 -0
  60. package/src/lib/validation.d.ts +4 -4
  61. package/src/lib/validation.js +66 -53
  62. package/src/lib/workspaces.d.ts +113 -0
  63. package/src/lib/workspaces.js +403 -0
  64. package/src/index.d.ts +0 -18
  65. package/src/index.js +0 -87
@@ -0,0 +1,4 @@
1
+ export declare const LATEST_SUPPORTED_PLATFORM_VERSION: string;
2
+ export declare function isSupportedPlatformVersion(platformVersion?: string | null): boolean;
3
+ export declare function isLegacyProject(platformVersion?: string | null): boolean;
4
+ export declare function supportsComponentPermissions(platformVersion: string): boolean;
@@ -0,0 +1,30 @@
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
+ if (process.env.HUBSPOT_PROJECT_INTERNAL_TEST === 'true')
10
+ return true;
11
+ const supportedPlatformVersions = Object.values(PLATFORM_VERSIONS);
12
+ return supportedPlatformVersions.includes(platformVersion);
13
+ }
14
+ export function isLegacyProject(platformVersion) {
15
+ if (!platformVersion)
16
+ return false;
17
+ if (process.env.HUBSPOT_PROJECT_INTERNAL_TEST === 'true')
18
+ return false;
19
+ return [PLATFORM_VERSIONS.v2023_2, PLATFORM_VERSIONS.v2025_1].includes(platformVersion);
20
+ }
21
+ const PRE_PERMISSIONS_VERSIONS = new Set([
22
+ PLATFORM_VERSIONS.v2023_2,
23
+ PLATFORM_VERSIONS.v2025_1,
24
+ PLATFORM_VERSIONS.v2025_2,
25
+ PLATFORM_VERSIONS.v2026_03_BETA,
26
+ PLATFORM_VERSIONS.v2026_03,
27
+ ]);
28
+ export function supportsComponentPermissions(platformVersion) {
29
+ return !PRE_PERMISSIONS_VERSIONS.has(platformVersion);
30
+ }
@@ -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[];
@@ -1,56 +1,111 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getIsProfileFile = getIsProfileFile;
4
- exports.getHsProfileFilename = getHsProfileFilename;
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
- function getIsProfileFile(filename) {
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 `${constants_1.profileFilePrefix}.${profileName}.json`;
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 interpolate(file, template, profileVariables) {
22
- return template.replace(profileInsertRegex, (__match, key) => {
23
- const profileVariableType = typeof profileVariables[key];
24
- if (profileVariableType === 'undefined') {
25
- throw new Error(copy_1.errorMessages.profile.missingValue(key, file));
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
- if (!['string', 'number', 'boolean'].includes(profileVariableType)) {
28
- throw new Error(copy_1.errorMessages.profile.invalidValue(key));
35
+ catch (error) {
36
+ const errorMessage = error instanceof Error ? error.message : String(error);
37
+ errors.push(errorMessage);
29
38
  }
30
- return String(profileVariables[key]);
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 applyHsProfileVariables(fileParseResults, hsProfileContents) {
34
- const profileVariables = hsProfileContents?.variables &&
35
- typeof hsProfileContents.variables === 'object' &&
36
- !Array.isArray(hsProfileContents.variables)
37
- ? hsProfileContents.variables
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
- if (content && content.config) {
42
- let interpolatedConfig = content.config;
43
- interpolatedConfig = JSON.parse(interpolate(file, JSON.stringify(content.config), profileVariables));
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
- return fileParseResult;
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
  }
@@ -1,28 +1,33 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getProjectMetadata = getProjectMetadata;
7
- const constants_1 = require("./constants");
8
- const files_1 = require("./files");
9
- const path_1 = __importDefault(require("path"));
10
- async function getProjectMetadata(projectSrcDir) {
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) {
11
6
  const hsMetaFiles = [];
12
7
  const components = {};
13
- const metafiles = await (0, files_1.locateHsMetaFiles)(projectSrcDir, { silent: true });
14
- Object.keys(constants_1.Components).forEach(componentType => {
15
- const { parentComponent, singularComponent, dir } = constants_1.Components[componentType];
16
- const componentPath = path_1.default.join(projectSrcDir, parentComponent ? path_1.default.join(parentComponent, dir) : dir);
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
+ }
17
20
  const metaFilesByType = metafiles
18
- .filter(metafile => path_1.default.parse(metafile.file).dir === componentPath)
21
+ .filter(metafile => path.parse(metafile.file).dir === componentPath)
19
22
  .map(metaFile => metaFile.file);
20
- hsMetaFiles.push(...metaFilesByType);
21
- components[componentType] = {
23
+ const metadata = {
22
24
  hsMetaFiles: metaFilesByType,
23
25
  count: metaFilesByType.length,
24
26
  maxCount: singularComponent ? 1 : Infinity,
25
27
  };
28
+ hsMetaFiles.push(...metaFilesByType);
29
+ seenComponentPaths.set(componentPath, metadata);
30
+ components[componentType] = metadata;
26
31
  });
27
32
  return {
28
33
  hsMetaFiles,
@@ -1,3 +1,3 @@
1
- import { AnySchema } from 'ajv/dist/2020';
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>>;
@@ -1,24 +1,24 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getIntermediateRepresentationSchema = getIntermediateRepresentationSchema;
4
- const http_1 = require("@hubspot/local-dev-lib/http");
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(copy_1.errorMessages.api.accountIdIsRequiredToFetchSchemas);
6
+ throw new Error(errorMessages.api.accountIdIsRequiredToFetchSchemas);
10
7
  }
11
8
  try {
12
9
  const { accountId, platformVersion } = translationContext;
13
- const { data } = await http_1.http.get(accountId, {
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 ((0, index_1.isHubSpotHttpError)(e)) {
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(copy_1.errorMessages.api.failedToFetchSchemas, { cause: e });
22
+ throw new Error(errorMessages.api.failedToFetchSchemas, { cause: e });
23
23
  }
24
24
  }
@@ -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: boolean | undefined): IntermediateRepresentation;
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;
@@ -1,76 +1,80 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.mapToInternalType = mapToInternalType;
7
- exports.mapToUserFacingType = mapToUserFacingType;
8
- exports.mapToUserFriendlyName = mapToUserFriendlyName;
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
- let dependencies = {};
19
- // If there are dependencies in the config file, pass them through
20
- if (!fileValidationResult.content?.dependencies) {
21
- dependencies = { ...fileValidationResult.content?.dependencies };
22
- }
10
+ // Pass through any developer-authored dependencies declared in the hsmeta
11
+ // file. Framework-injected structural dependencies (app, allAppObjects,
12
+ // serverlessPackage) are layered on top below and take precedence over any
13
+ // authored value for those reserved keys.
14
+ const dependencies = {
15
+ ...fileValidationResult.content?.dependencies,
16
+ };
23
17
  const { type } = fileValidationResult.content;
24
18
  // If the component is a child of the App component, add the app and appObjects as dependencies
25
- if (constants_1.Components[type]?.parentComponent === constants_1.AppKey) {
26
- const parentUid = parentComponents[fileValidationResult.parentDirectory];
19
+ if (Components[type]?.parentComponent === APP_KEY) {
20
+ const parentUid = parentComponents[APP_KEY];
27
21
  if (parentUid) {
28
22
  dependencies.app = parentUid;
29
23
  }
30
- if (type !== constants_1.AppObjectKey) {
24
+ else {
25
+ logger.debug(parentComponents);
26
+ return {
27
+ dependencies,
28
+ errors: [errorMessages.project.mustHaveAppComponent(type)],
29
+ };
30
+ }
31
+ if (type !== APP_OBJECT_KEY) {
31
32
  dependencies.allAppObjects = appObjects;
32
33
  }
33
- if (type === constants_1.AppFunctionsKey && appFunctionsPackageUid) {
34
+ if (type === APP_FUNCTIONS_KEY && appFunctionsPackageUid) {
34
35
  dependencies.serverlessPackage = appFunctionsPackageUid;
35
36
  }
36
37
  }
37
- return dependencies;
38
+ return { dependencies };
38
39
  }
39
- function mapToInternalType(type) {
40
- const resolvedType = constants_1.userFacingToInternalType[type] || type || '';
40
+ export function mapToInternalType(type) {
41
+ const resolvedType = USER_FACING_TO_INTERNAL_TYPE[type] || type || '';
41
42
  return resolvedType.toUpperCase().replace(/-/g, '_');
42
43
  }
43
- function mapToUserFacingType(type) {
44
- return (constants_1.internalTypeToUserFacing[type] || type || '')
44
+ export function mapToUserFacingType(type) {
45
+ return (INTERNAL_TYPE_TO_USER_FACING[type] || type || '')
45
46
  .toLowerCase()
46
47
  .replace(/_/g, '-');
47
48
  }
48
- function mapToUserFriendlyName(internalType) {
49
- return constants_1.Components[mapToUserFacingType(internalType)]?.userFriendlyName || '';
49
+ export function mapToUserFriendlyName(internalType) {
50
+ return Components[mapToUserFacingType(internalType)]?.userFriendlyName || '';
51
+ }
52
+ export function findTransformationByUid(transformations, uid) {
53
+ return transformations.find(t => t.intermediateRepresentation?.uid === uid);
50
54
  }
51
- function transform(fileParseResults, translationContext, hsProfileContents) {
52
- const parentTypes = Object.keys(constants_1.ProjectStructure);
55
+ export function transform(fileParseResults, translationContext, hsProfileContents) {
56
+ const parentTypes = Object.keys(PROJECT_STRUCTURE);
53
57
  const parentComponents = {};
54
58
  const allAppObjects = [];
55
59
  let appUid = '';
56
60
  let appFunctionsDirectory;
57
61
  // Apply the profile variable overrides to the config
58
62
  if (hsProfileContents) {
59
- fileParseResults = (0, profiles_1.applyHsProfileVariables)(fileParseResults, hsProfileContents);
63
+ fileParseResults = applyHsProfileVariables(fileParseResults, hsProfileContents);
60
64
  }
61
65
  // Compute the parent components, so we can add them as dependencies to the child components
62
66
  fileParseResults.forEach(file => {
63
67
  if (file.content?.type && parentTypes.includes(file.content.type)) {
64
- if (file.content.type === constants_1.AppKey) {
68
+ if (file.content.type === APP_KEY) {
65
69
  appUid = file.content.uid;
66
70
  }
67
71
  parentComponents[file.content.type] = file.content.uid;
68
72
  }
69
- if (file.content?.type === constants_1.AppObjectKey) {
73
+ if (file.content?.type === APP_OBJECT_KEY) {
70
74
  allAppObjects.push(file.content.uid);
71
75
  }
72
- if (file.content?.type === constants_1.AppFunctionsKey) {
73
- appFunctionsDirectory = path_1.default.dirname(file.file);
76
+ if (file.content?.type === APP_FUNCTIONS_KEY) {
77
+ appFunctionsDirectory = path.dirname(file.file);
74
78
  }
75
79
  });
76
80
  const autoGeneratedComponents = [];
@@ -85,28 +89,35 @@ function transform(fileParseResults, translationContext, hsProfileContents) {
85
89
  }
86
90
  const transformations = fileParseResults.map((currentFile) => {
87
91
  if (!currentFile.content) {
88
- currentFile.errors?.push(copy_1.errorMessages.project.fileContentMissingFor(currentFile.file));
92
+ if (!currentFile.errors?.includes(errorMessages.validation.invalidJson)) {
93
+ currentFile.errors?.push(errorMessages.project.fileContentMissingFor(currentFile.file));
94
+ }
89
95
  return {
90
96
  intermediateRepresentation: null,
91
97
  fileParseResult: currentFile,
92
98
  };
93
99
  }
94
- const { config, uid, type } = currentFile.content;
100
+ const { config, uid, type, permissions } = currentFile.content;
101
+ const { dependencies, errors } = calculateComponentDeps(currentFile, parentComponents, allAppObjects, serverlessPackageUid);
102
+ if (errors) {
103
+ currentFile.errors?.push(...errors);
104
+ }
95
105
  return {
96
106
  intermediateRepresentation: {
97
107
  uid,
98
108
  config,
99
109
  componentType: mapToInternalType(type),
100
- componentDeps: calculateComponentDeps(currentFile, parentComponents, allAppObjects, serverlessPackageUid),
101
- metaFilePath: currentFile.file,
110
+ componentDeps: dependencies,
111
+ metaFilePath: convertPathToPosixPath(currentFile.file),
102
112
  files: {},
113
+ ...(permissions ? { permissions } : {}),
103
114
  },
104
115
  fileParseResult: currentFile,
105
116
  };
106
117
  });
107
118
  return [...autoGeneratedComponents, ...transformations];
108
119
  }
109
- function getIntermediateRepresentation(transformations, skipValidation) {
120
+ export function getIntermediateRepresentation(transformations, hsProfileContents, skipValidation) {
110
121
  const nodes = transformations.reduce((acc, current) => {
111
122
  if (!current.intermediateRepresentation) {
112
123
  return acc;
@@ -116,15 +127,14 @@ function getIntermediateRepresentation(transformations, skipValidation) {
116
127
  const duplicates = transformations
117
128
  .filter(t => t.intermediateRepresentation?.uid === uid)
118
129
  .map(t => t.fileParseResult.file);
119
- throw new Error(copy_1.errorMessages.project.duplicateUid(uid, duplicates));
130
+ throw new Error(errorMessages.project.duplicateUid(uid, duplicates));
120
131
  }
121
132
  if (!skipValidation) {
122
133
  return {
123
134
  ...acc,
124
135
  // If the uid is not defined just make one up for the sake of indexing.
125
136
  // It will still fail validation, but this prevents collisions so we validate all files.
126
- [uid ||
127
- `missing-${current.fileParseResult.file}`]: current.intermediateRepresentation,
137
+ [uid || `missing-${current.fileParseResult.file}`]: current.intermediateRepresentation,
128
138
  };
129
139
  }
130
140
  return {
@@ -132,17 +142,55 @@ function getIntermediateRepresentation(transformations, skipValidation) {
132
142
  [uid]: current.intermediateRepresentation,
133
143
  };
134
144
  }, {});
145
+ const profileData = getProfileData(hsProfileContents);
135
146
  return {
136
147
  intermediateNodesIndexedByUid: nodes,
148
+ profileData,
137
149
  };
138
150
  }
139
- function generateServerlessPackageComponent(appFunctionsDirectory, translationContext, componentDeps) {
140
- const packageFile = path_1.default.join(appFunctionsDirectory, constants_1.packageJson);
141
- const packageLockFile = path_1.default.join(appFunctionsDirectory, constants_1.packageLockJson);
142
- const { projectSourceDir } = translationContext;
143
- const appFunctionsPackageFile = path_1.default.join(projectSourceDir, packageFile);
144
- if (!fs_1.default.existsSync(appFunctionsPackageFile)) {
145
- throw new Error(copy_1.errorMessages.project.noPackageJsonForServerless(appFunctionsPackageFile));
151
+ export function getParsingErrors(transformations) {
152
+ return transformations
153
+ .filter(t => t.fileParseResult.errors.length > 0)
154
+ .map(t => t.fileParseResult);
155
+ }
156
+ function getProfileData(hsProfileContents) {
157
+ const profileVariablesForBE = {};
158
+ if (hsProfileContents && hsProfileContents.variables) {
159
+ const profileVariables = getHsProfileVariables(hsProfileContents);
160
+ Object.keys(profileVariables).forEach(key => {
161
+ const profileVar = profileVariables[key];
162
+ let variableTypeForBE;
163
+ switch (typeof profileVar) {
164
+ case 'string':
165
+ variableTypeForBE = PROFILE_VARIABLE_TYPES.PROFILE_STRING;
166
+ break;
167
+ case 'number':
168
+ variableTypeForBE = getJavaNumberType(profileVar);
169
+ break;
170
+ case 'boolean':
171
+ variableTypeForBE = PROFILE_VARIABLE_TYPES.PROFILE_BOOLEAN;
172
+ break;
173
+ default:
174
+ break;
175
+ }
176
+ if (variableTypeForBE) {
177
+ profileVariablesForBE[key] = {
178
+ variableType: variableTypeForBE,
179
+ value: profileVar,
180
+ };
181
+ }
182
+ });
183
+ }
184
+ return { vars: { profileVariables: profileVariablesForBE } };
185
+ }
186
+ export function generateServerlessPackageComponent(appFunctionsDirectory, translationContext, componentDeps) {
187
+ const appFunctionsPosix = convertPathToPosixPath(appFunctionsDirectory);
188
+ const packageFilePosix = path.posix.join(appFunctionsPosix, PACKAGE_JSON);
189
+ const packageLockFilePosix = path.posix.join(appFunctionsPosix, PACKAGE_LOCK_JSON);
190
+ const projectSourceDirPosix = convertPathToPosixPath(translationContext.projectSourceDir);
191
+ const appFunctionsPackageFile = path.posix.join(projectSourceDirPosix, packageFilePosix);
192
+ if (!fs.existsSync(appFunctionsPackageFile)) {
193
+ throw new Error(errorMessages.project.noPackageJsonForServerless(appFunctionsPackageFile));
146
194
  }
147
195
  const uid = `hubspot-serverless-package-uid`;
148
196
  return {
@@ -154,15 +202,17 @@ function generateServerlessPackageComponent(appFunctionsDirectory, translationCo
154
202
  },
155
203
  intermediateRepresentation: {
156
204
  uid,
157
- componentType: mapToInternalType(constants_1.AppFunctionsPackageKey),
205
+ componentType: mapToInternalType(APP_FUNCTIONS_PACKAGE_KEY),
158
206
  config: {
159
- packageFile,
160
- packageLockfile: fs_1.default.existsSync(path_1.default.join(projectSourceDir, packageLockFile))
161
- ? packageLockFile
207
+ packageFile: packageFilePosix,
208
+ packageLockfile: fs.existsSync(
209
+ // Don't use posix here because we are checking the file system
210
+ path.join(translationContext.projectSourceDir, path.join(appFunctionsDirectory, PACKAGE_LOCK_JSON)))
211
+ ? packageLockFilePosix
162
212
  : undefined,
163
213
  },
164
214
  componentDeps,
165
- metaFilePath: packageFile,
215
+ metaFilePath: packageFilePosix,
166
216
  files: {},
167
217
  },
168
218
  },
@@ -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
+ }