@hubspot/project-parsing-lib 0.2.0-beta.1 → 0.2.0-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 (53) hide show
  1. package/README.md +4 -35
  2. package/package.json +54 -19
  3. package/src/exports/constants.d.ts +1 -0
  4. package/src/exports/constants.js +1 -0
  5. package/src/exports/migrate.d.ts +1 -0
  6. package/src/exports/migrate.js +1 -0
  7. package/src/exports/profiles.d.ts +3 -0
  8. package/src/exports/profiles.js +2 -0
  9. package/src/exports/projects.d.ts +3 -0
  10. package/src/exports/projects.js +2 -0
  11. package/src/exports/schema.d.ts +2 -0
  12. package/src/exports/schema.js +1 -0
  13. package/src/exports/themes.d.ts +2 -0
  14. package/src/exports/themes.js +1 -0
  15. package/src/exports/transform.d.ts +2 -0
  16. package/src/exports/transform.js +1 -0
  17. package/src/exports/translate.d.ts +3 -0
  18. package/src/exports/translate.js +2 -0
  19. package/src/exports/uid.d.ts +1 -0
  20. package/src/exports/uid.js +1 -0
  21. package/src/lang/copy.d.ts +7 -1
  22. package/src/lang/copy.js +29 -33
  23. package/src/lib/constants.d.ts +45 -28
  24. package/src/lib/constants.js +160 -121
  25. package/src/lib/errors.d.ts +4 -3
  26. package/src/lib/errors.js +62 -38
  27. package/src/lib/files.d.ts +10 -1
  28. package/src/lib/files.js +51 -40
  29. package/src/lib/localDev.d.ts +4 -0
  30. package/src/lib/localDev.js +72 -0
  31. package/src/lib/migrate.js +32 -42
  32. package/src/lib/migrateThemes.d.ts +25 -0
  33. package/src/lib/migrateThemes.js +120 -0
  34. package/src/lib/profiles.d.ts +6 -1
  35. package/src/lib/profiles.js +95 -40
  36. package/src/lib/project.d.ts +13 -0
  37. package/src/lib/project.js +29 -0
  38. package/src/lib/schemas.d.ts +2 -2
  39. package/src/lib/schemas.js +11 -11
  40. package/src/lib/transform.d.ts +4 -2
  41. package/src/lib/transform.js +100 -53
  42. package/src/lib/translate.d.ts +3 -0
  43. package/src/lib/translate.js +52 -0
  44. package/src/lib/types.d.ts +28 -4
  45. package/src/lib/types.js +1 -2
  46. package/src/lib/uid.d.ts +2 -0
  47. package/src/lib/uid.js +14 -9
  48. package/src/lib/utils.d.ts +3 -0
  49. package/src/lib/utils.js +16 -0
  50. package/src/lib/validation.d.ts +4 -4
  51. package/src/lib/validation.js +61 -53
  52. package/src/index.d.ts +0 -18
  53. package/src/index.js +0 -86
package/src/lib/files.js CHANGED
@@ -1,66 +1,59 @@
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.loadHsProfileFile = loadHsProfileFile;
7
- exports.getAllHsProfiles = getAllHsProfiles;
8
- exports.loadHsMetaFiles = loadHsMetaFiles;
9
- const fs_1 = __importDefault(require("fs"));
10
- const path_1 = __importDefault(require("path"));
11
- const fs_2 = require("@hubspot/local-dev-lib/fs");
12
- const constants_1 = require("./constants");
13
- const copy_1 = require("../lang/copy");
14
- const logger_1 = require("@hubspot/local-dev-lib/logger");
15
- const profiles_1 = require("./profiles");
16
- function loadHsProfileFile(projectSourceDir, profile) {
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { walk } from '@hubspot/local-dev-lib/fs';
4
+ import { ALLOWED_COMPONENT_DIRECTORIES, ALLOWED_SUB_COMPONENT_DIRECTORIES, METAFILE_EXTENSION, ALLOWED_TOP_LEVEL_FIELDS, } from './constants.js';
5
+ import { errorMessages, logMessages } from '../lang/copy.js';
6
+ import { logger } from '@hubspot/local-dev-lib/logger';
7
+ import { getHsProfileFilename, getHsProfileName, getIsProfileFile, } from './profiles.js';
8
+ export function loadHsProfileFile(projectSourceDir, profile) {
17
9
  if (!profile) {
18
- throw new Error(copy_1.errorMessages.profile.noProfileSpecified);
10
+ throw new Error(errorMessages.profile.noProfileSpecified);
19
11
  }
20
- const profileFile = (0, profiles_1.getHsProfileFilename)(profile);
21
- const profileFilepath = path_1.default.join(projectSourceDir, profileFile);
12
+ const profileFile = getHsProfileFilename(profile);
13
+ const profileFilepath = path.join(projectSourceDir, profileFile);
22
14
  let hsProfile;
23
15
  try {
24
- hsProfile = JSON.parse(fs_1.default.readFileSync(profileFilepath, 'utf8'));
16
+ hsProfile = JSON.parse(fs.readFileSync(profileFilepath, 'utf8'));
25
17
  }
26
18
  catch (_e) {
27
- throw new Error(copy_1.errorMessages.profile.failedToLoadHsProfile(profileFile));
19
+ throw new Error(errorMessages.profile.failedToLoadHsProfile(profileFile));
28
20
  }
29
21
  return hsProfile;
30
22
  }
31
- function getAllHsProfiles(projectSourceDir) {
23
+ export function getAllHsProfiles(projectSourceDir) {
32
24
  return new Promise((resolve, _reject) => {
33
- fs_1.default.readdir(projectSourceDir, { recursive: false, withFileTypes: true }, (err, files) => {
25
+ fs.readdir(projectSourceDir, { recursive: false, withFileTypes: true }, (err, files) => {
34
26
  if (err) {
35
27
  return resolve([]);
36
28
  }
37
29
  return resolve(files
38
- .filter(file => file.isFile() && (0, profiles_1.getIsProfileFile)(file.name))
39
- .map(file => (0, profiles_1.getHsProfileName)(file.name)));
30
+ .filter(file => file.isFile() && getIsProfileFile(file.name))
31
+ .map(file => getHsProfileName(file.name)));
40
32
  });
41
33
  });
42
34
  }
43
- async function loadHsMetaFiles(translationContext) {
44
- const metaFiles = await locateHsMetaFiles(translationContext);
35
+ export async function loadHsMetaFiles(translationContext) {
36
+ const metaFiles = await locateHsMetaFiles(translationContext.projectSourceDir);
45
37
  return parseHsMetaFiles(metaFiles, translationContext);
46
38
  }
47
- async function locateHsMetaFiles(translationContext) {
48
- const { projectSourceDir } = translationContext;
49
- return (await (0, fs_2.walk)(projectSourceDir, ['node_modules'])).reduce((metaFiles, file) => {
50
- if (!file.endsWith(constants_1.metafileExtension)) {
39
+ export async function locateHsMetaFiles(projectSourceDir, options = { silent: false }) {
40
+ return (await walk(projectSourceDir, ['node_modules'])).reduce((metaFiles, file) => {
41
+ if (!file.endsWith(METAFILE_EXTENSION)) {
51
42
  return metaFiles;
52
43
  }
53
- const pathRelativeToProjectSrcDir = path_1.default.relative(projectSourceDir, file);
54
- const { dir: metaFileDir } = path_1.default.parse(pathRelativeToProjectSrcDir);
55
- const parentDirectory = metaFileDir.split(path_1.default.sep)[0];
56
- if (constants_1.allowedComponentDirectories.includes(metaFileDir)) {
44
+ const pathRelativeToProjectSrcDir = path.relative(projectSourceDir, file);
45
+ const { dir: metaFileDir } = path.parse(pathRelativeToProjectSrcDir);
46
+ const parentDirectory = metaFileDir.split(path.sep)[0];
47
+ if (ALLOWED_COMPONENT_DIRECTORIES.includes(metaFileDir)) {
57
48
  metaFiles.push({ file });
58
49
  }
59
- else if (constants_1.allowedSubComponentDirectories.includes(metaFileDir)) {
50
+ else if (ALLOWED_SUB_COMPONENT_DIRECTORIES.includes(metaFileDir)) {
60
51
  metaFiles.push({ file, parentDirectory });
61
52
  }
62
53
  else {
63
- logger_1.logger.warn(copy_1.logMessages.files.skippingPath(pathRelativeToProjectSrcDir));
54
+ if (!options.silent) {
55
+ logger.warn(logMessages.files.skippingPath(pathRelativeToProjectSrcDir));
56
+ }
64
57
  }
65
58
  return metaFiles;
66
59
  }, []);
@@ -75,7 +68,7 @@ function loadFile(metaFileLocation, translationContext) {
75
68
  const { projectSourceDir } = translationContext;
76
69
  return new Promise(async (resolve) => {
77
70
  const { file, parentDirectory } = metaFileLocation;
78
- fs_1.default.readFile(file, 'utf-8', (err, content) => {
71
+ fs.readFile(file, 'utf-8', (err, content) => {
79
72
  if (err) {
80
73
  return resolve({
81
74
  file,
@@ -83,7 +76,7 @@ function loadFile(metaFileLocation, translationContext) {
83
76
  });
84
77
  }
85
78
  resolve({
86
- file: path_1.default.relative(projectSourceDir, file),
79
+ file: path.relative(projectSourceDir, file),
87
80
  content,
88
81
  parentDirectory,
89
82
  errors: [],
@@ -101,7 +94,25 @@ function parseFile(fileLoadResult) {
101
94
  parsedFileContents = JSON.parse(fileLoadResult.content);
102
95
  }
103
96
  catch (_e) {
104
- fileLoadResult.errors?.push(copy_1.errorMessages.validation.invalidJson);
97
+ fileLoadResult.errors?.push(errorMessages.validation.invalidJson);
98
+ }
99
+ // Validate top-level fields (only uid, type, and config are expected)
100
+ if (parsedFileContents &&
101
+ typeof parsedFileContents === 'object' &&
102
+ !Array.isArray(parsedFileContents)) {
103
+ const actualFields = Object.keys(parsedFileContents);
104
+ const unexpectedFields = actualFields.filter(field => !ALLOWED_TOP_LEVEL_FIELDS.has(field));
105
+ if (unexpectedFields.length > 0) {
106
+ const unexpectedFieldsList = unexpectedFields.join(', ');
107
+ fileLoadResult.errors?.push(errorMessages.validation.unexpectedToplevelFields(unexpectedFieldsList));
108
+ }
105
109
  }
106
110
  return { ...fileLoadResult, content: parsedFileContents };
107
111
  }
112
+ export async function projectContainsHsMetaFiles(projectSourceDir) {
113
+ const hsMetaFiles = (await walk(projectSourceDir, ['node_modules'])).filter(file => file.endsWith(METAFILE_EXTENSION));
114
+ return hsMetaFiles.length > 0;
115
+ }
116
+ export function convertPathToPosixPath(filepath) {
117
+ return filepath.replaceAll(path.win32.sep, path.posix.sep);
118
+ }
@@ -0,0 +1,4 @@
1
+ import { BEProfileVariables, HSProfileVariables, IntermediateRepresentationNode, IntermediateRepresentationNodeLocalDev, FileParseResult } from './types.js';
2
+ export declare function getLocalDevProjectNodes(intermediateNodesIndexedByUid: Record<string, IntermediateRepresentationNode>, projectSourceDir: string, projectNodesAtLastUpload?: Record<string, IntermediateRepresentationNodeLocalDev>): Record<string, IntermediateRepresentationNodeLocalDev>;
3
+ export declare function getRemovedNodesAndNodesWithErrors(intermediateNodesIndexedByUid: Record<string, IntermediateRepresentationNode>, projectNodesAtLastUpload: Record<string, IntermediateRepresentationNodeLocalDev>, parsingErrors: FileParseResult[]): Record<string, IntermediateRepresentationNodeLocalDev>;
4
+ export declare function getLocalDevProfileData(profileVariables?: BEProfileVariables): HSProfileVariables;
@@ -0,0 +1,72 @@
1
+ import path from 'path';
2
+ import { isDeepEqual } from '@hubspot/local-dev-lib/isDeepEqual';
3
+ export function getLocalDevProjectNodes(intermediateNodesIndexedByUid, projectSourceDir, projectNodesAtLastUpload) {
4
+ const localDevProjectNodes = {};
5
+ Object.entries(intermediateNodesIndexedByUid).forEach(([uid, node]) => {
6
+ const component = intermediateNodesIndexedByUid[uid];
7
+ const componentConfigPath = path.join(projectSourceDir, component.metaFilePath);
8
+ let configUpdatedSinceLastUpload = false;
9
+ if (projectNodesAtLastUpload) {
10
+ const componentAtLastUpload = projectNodesAtLastUpload[uid];
11
+ if (componentAtLastUpload) {
12
+ configUpdatedSinceLastUpload = !isDeepEqual(component.config, componentAtLastUpload.config);
13
+ }
14
+ else {
15
+ // Component is net new
16
+ configUpdatedSinceLastUpload = true;
17
+ }
18
+ }
19
+ localDevProjectNodes[uid] = {
20
+ ...node,
21
+ localDev: {
22
+ componentRoot: path.dirname(componentConfigPath),
23
+ componentConfigPath,
24
+ configUpdatedSinceLastUpload,
25
+ removed: false,
26
+ parsingErrors: [],
27
+ },
28
+ };
29
+ });
30
+ return localDevProjectNodes;
31
+ }
32
+ export function getRemovedNodesAndNodesWithErrors(intermediateNodesIndexedByUid, projectNodesAtLastUpload, parsingErrors) {
33
+ const nodesWithErrors = {};
34
+ Object.entries(projectNodesAtLastUpload).forEach(([uid, node]) => {
35
+ // Node is still in the project
36
+ if (intermediateNodesIndexedByUid[uid]) {
37
+ return;
38
+ // Node has parsing errors but was not removed
39
+ }
40
+ const errorForNode = parsingErrors.find(error => error.file === node.metaFilePath);
41
+ if (errorForNode) {
42
+ nodesWithErrors[uid] = {
43
+ ...node,
44
+ localDev: {
45
+ ...node.localDev,
46
+ parsingErrors: errorForNode.errors,
47
+ },
48
+ };
49
+ // Node was removed since the last upload
50
+ }
51
+ else {
52
+ nodesWithErrors[uid] = {
53
+ ...node,
54
+ localDev: {
55
+ ...node.localDev,
56
+ removed: true,
57
+ },
58
+ };
59
+ }
60
+ });
61
+ return nodesWithErrors;
62
+ }
63
+ export function getLocalDevProfileData(profileVariables) {
64
+ if (!profileVariables) {
65
+ return {};
66
+ }
67
+ const localDevProfileData = {};
68
+ Object.entries(profileVariables).forEach(([key, value]) => {
69
+ localDevProfileData[key] = value.value;
70
+ });
71
+ return localDevProfileData;
72
+ }
@@ -1,35 +1,30 @@
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.migrate = migrate;
7
- const fs_1 = require("@hubspot/local-dev-lib/fs");
8
- const path_1 = __importDefault(require("path"));
9
- const fs_2 = __importDefault(require("fs"));
10
- const transform_1 = require("./transform");
11
- const constants_1 = require("./constants");
12
- const index_1 = require("../index");
1
+ import { walk } from '@hubspot/local-dev-lib/fs';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { mapToUserFacingType } from './transform.js';
5
+ import { METAFILE_EXTENSION, Components, HS_PROJECT_JSON_FILENAME, } from './constants.js';
6
+ import { translate } from './translate.js';
7
+ import { loadJsonFile } from './utils.js';
13
8
  const IR_FILENAME = 'ir.json';
14
9
  const filesDirectory = 'files';
15
10
  const OUTPUT_IR_FILE = 'FULL_IR.json';
16
- async function migrate(inputDir, outputDir) {
17
- if (!fs_2.default.existsSync(inputDir)) {
11
+ export async function migrate(inputDir, outputDir) {
12
+ if (!fs.existsSync(inputDir)) {
18
13
  throw new Error(`Input directory ${inputDir} does not exist`);
19
14
  }
20
- const hsProjectJsonPath = path_1.default.join(inputDir, constants_1.hsProjectJsonFilename);
21
- if (!fs_2.default.existsSync(hsProjectJsonPath)) {
22
- throw new Error(`${constants_1.hsProjectJsonFilename} file does not exist in ${inputDir}`);
15
+ const hsProjectJsonPath = path.join(inputDir, HS_PROJECT_JSON_FILENAME);
16
+ if (!fs.existsSync(hsProjectJsonPath)) {
17
+ throw new Error(`${HS_PROJECT_JSON_FILENAME} file does not exist in ${inputDir}`);
23
18
  }
24
19
  let hsProjectJson;
25
20
  try {
26
21
  hsProjectJson = loadJsonFile(hsProjectJsonPath);
27
22
  }
28
23
  catch (e) {
29
- throw new Error(`Error parsing ${constants_1.hsProjectJsonFilename}: ${e}`);
24
+ throw new Error(`Error parsing ${HS_PROJECT_JSON_FILENAME}: ${e}`);
30
25
  }
31
- const files = await (0, fs_1.walk)(inputDir);
32
- const sourceCodeOutputDir = path_1.default.join(outputDir, 'src');
26
+ const files = await walk(inputDir);
27
+ const sourceCodeOutputDir = path.join(outputDir, 'src');
33
28
  // Create the output directory if it doesn't exist
34
29
  ensureDirExists(sourceCodeOutputDir);
35
30
  files.forEach((filename) => {
@@ -37,63 +32,58 @@ async function migrate(inputDir, outputDir) {
37
32
  if (!isIRFile(filename)) {
38
33
  return;
39
34
  }
40
- const irDirName = path_1.default.dirname(filename);
35
+ const irDirName = path.dirname(filename);
41
36
  const IR = loadJsonFile(filename);
42
37
  const { metaFilePath } = IR;
43
38
  const projectConfig = convertIRToProjectConfig(IR);
44
- const fullOutputPath = path_1.default.join(sourceCodeOutputDir, getTargetDirectoryFromComponentType(projectConfig.type));
39
+ const fullOutputPath = path.join(sourceCodeOutputDir, getTargetDirectoryFromComponentType(projectConfig.type));
45
40
  // Ensure the output directory exists
46
- const currentFilesDirectory = path_1.default.join(irDirName, filesDirectory);
41
+ const currentFilesDirectory = path.join(irDirName, filesDirectory);
47
42
  ensureDirExists(currentFilesDirectory);
48
- fs_2.default.cpSync(currentFilesDirectory, fullOutputPath, {
43
+ fs.cpSync(currentFilesDirectory, fullOutputPath, {
49
44
  recursive: true,
50
45
  });
51
46
  // Use the metaFilePath if provided, otherwise use the project UID
52
- const hsmetaFilePath = path_1.default.join(fullOutputPath, metaFilePath
53
- ? path_1.default.basename(metaFilePath)
54
- : `${projectConfig.uid}${constants_1.metafileExtension}`);
47
+ const hsmetaFilePath = path.join(fullOutputPath, metaFilePath
48
+ ? path.basename(metaFilePath)
49
+ : `${projectConfig.uid}${METAFILE_EXTENSION}`);
55
50
  // Write the hsmeta file to the output directory
56
- fs_2.default.writeFileSync(hsmetaFilePath, JSON.stringify(projectConfig, null, 2));
51
+ fs.writeFileSync(hsmetaFilePath, JSON.stringify(projectConfig, null, 2));
57
52
  });
58
- const IR = await (0, index_1.translate)({
53
+ const IR = await translate({
59
54
  projectSourceDir: sourceCodeOutputDir,
60
55
  platformVersion: hsProjectJson.platformVersion,
61
56
  }, { skipValidation: true });
62
57
  // Write the IR file to the output directory
63
- fs_2.default.writeFileSync(path_1.default.join(outputDir, OUTPUT_IR_FILE), JSON.stringify(IR, null, 2));
58
+ fs.writeFileSync(path.join(outputDir, OUTPUT_IR_FILE), JSON.stringify(IR, null, 2));
64
59
  }
65
60
  function ensureDirExists(dir) {
66
- if (!fs_2.default.existsSync(dir)) {
67
- fs_2.default.mkdirSync(dir, { recursive: true });
61
+ if (!fs.existsSync(dir)) {
62
+ fs.mkdirSync(dir, { recursive: true });
68
63
  }
69
64
  }
70
65
  function isIRFile(filename) {
71
- const { base } = path_1.default.parse(filename);
66
+ const { base } = path.parse(filename);
72
67
  return base.toLowerCase() === IR_FILENAME;
73
68
  }
74
- function loadJsonFile(filename) {
75
- return JSON.parse(fs_2.default.readFileSync(filename, {
76
- encoding: 'utf-8',
77
- }));
78
- }
79
69
  function convertIRToProjectConfig(IR) {
80
70
  const { config, uid, componentType } = IR;
81
71
  return {
82
72
  config,
83
73
  uid,
84
- type: (0, transform_1.mapToUserFacingType)(componentType),
74
+ type: mapToUserFacingType(componentType),
85
75
  // We are assuming no dependencies for now since this will be a freshly migrated
86
76
  // project and the current supported components don't have non-hierarchical dependencies
87
77
  dependencies: undefined,
88
78
  };
89
79
  }
90
80
  function getTargetDirectoryFromComponentType(componentType) {
91
- const component = constants_1.Components[componentType];
81
+ const component = Components[componentType];
92
82
  if (!component) {
93
83
  throw Error('Unsupported component type');
94
84
  }
95
85
  const parentDirectory = component.parentComponent
96
- ? constants_1.Components[component.parentComponent].dir
86
+ ? Components[component.parentComponent].dir
97
87
  : '';
98
- return path_1.default.join(parentDirectory, component.dir);
88
+ return path.join(parentDirectory, component.dir);
99
89
  }
@@ -0,0 +1,25 @@
1
+ export type ThemeDetails = {
2
+ configFilepath: string;
3
+ themePath: string;
4
+ themeConfig: {
5
+ secret_names?: string[];
6
+ };
7
+ };
8
+ export type ReactThemeDetails = {
9
+ configFilepath: string;
10
+ themePath: string;
11
+ themeConfig: {
12
+ secretNames?: string[];
13
+ };
14
+ };
15
+ export type ProjectThemeDetails = {
16
+ legacyThemeDetails: ThemeDetails[];
17
+ legacyReactThemeDetails: ReactThemeDetails[];
18
+ };
19
+ export declare function getProjectThemeDetails(projectSourceDir: string): Promise<ProjectThemeDetails>;
20
+ export declare function migrateThemes(projectDir: string, projectSourceDir: string): Promise<{
21
+ legacyThemeDetails: ThemeDetails[];
22
+ legacyReactThemeDetails: ReactThemeDetails[];
23
+ migrated: boolean;
24
+ failureReason?: string;
25
+ }>;
@@ -0,0 +1,120 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { promisify } from 'util';
5
+ import { walk } from '@hubspot/local-dev-lib/fs';
6
+ import { THEME_KEY, CMS_ASSETS_KEY, METAFILE_EXTENSION } from './constants.js';
7
+ import { loadJsonFile } from './utils.js';
8
+ import { errorMessages } from '../lang/copy.js';
9
+ const mkdtempAsync = promisify(fs.mkdtemp);
10
+ const LEGACY_THEME_CONFIG = 'theme.json';
11
+ const LEGACY_REACT_THEME_CONFIG = 'cms-assets.json';
12
+ export async function getProjectThemeDetails(projectSourceDir) {
13
+ const files = await walk(projectSourceDir, ['node_modules']);
14
+ const legacyThemeDetails = [];
15
+ const legacyReactThemeDetails = [];
16
+ files.forEach((filename) => {
17
+ const isLegacyThemeConfig = filename.endsWith(LEGACY_THEME_CONFIG);
18
+ const isLegacyReactThemeConfig = filename.endsWith(LEGACY_REACT_THEME_CONFIG);
19
+ if (isLegacyThemeConfig || isLegacyReactThemeConfig) {
20
+ const parsedConfig = loadJsonFile(filename);
21
+ const themeDetails = {
22
+ configFilepath: filename,
23
+ themePath: path.dirname(filename),
24
+ themeConfig: parsedConfig,
25
+ };
26
+ if (isLegacyThemeConfig) {
27
+ legacyThemeDetails.push(themeDetails);
28
+ }
29
+ else {
30
+ legacyReactThemeDetails.push(themeDetails);
31
+ }
32
+ }
33
+ });
34
+ return {
35
+ legacyThemeDetails,
36
+ legacyReactThemeDetails,
37
+ };
38
+ }
39
+ function buildNewTheme(componentKey, migratedProjectTempDir, themeDetails) {
40
+ const themeName = path.basename(themeDetails.themePath);
41
+ const componentDir = path.join(migratedProjectTempDir, componentKey);
42
+ const newThemeDir = path.join(componentDir, themeName);
43
+ fs.cpSync(themeDetails.themePath, newThemeDir, {
44
+ recursive: true,
45
+ });
46
+ const newThemeConfig = {
47
+ uid: themeName,
48
+ type: componentKey,
49
+ config: {
50
+ themePath: themeName,
51
+ secretNames: [],
52
+ },
53
+ };
54
+ // Remove secrets from the legacy theme config if they exist & move them to the new theme config
55
+ let updatedLegacyThemeConfig;
56
+ if ('secret_names' in themeDetails.themeConfig) {
57
+ const { secret_names, ...configWithoutSecrets } = themeDetails.themeConfig;
58
+ if (secret_names) {
59
+ newThemeConfig.config.secretNames = secret_names;
60
+ updatedLegacyThemeConfig = configWithoutSecrets;
61
+ }
62
+ }
63
+ else if ('secretNames' in themeDetails.themeConfig) {
64
+ const { secretNames, ...configWithoutSecrets } = themeDetails.themeConfig;
65
+ if (secretNames) {
66
+ newThemeConfig.config.secretNames = secretNames;
67
+ updatedLegacyThemeConfig = configWithoutSecrets;
68
+ }
69
+ }
70
+ if (updatedLegacyThemeConfig) {
71
+ const newLegacyConfigFilepath = path.join(newThemeDir, path.basename(themeDetails.configFilepath));
72
+ fs.writeFileSync(newLegacyConfigFilepath, JSON.stringify(updatedLegacyThemeConfig, null, 2));
73
+ }
74
+ fs.writeFileSync(path.join(componentDir, `${themeName}${METAFILE_EXTENSION}`), JSON.stringify(newThemeConfig, null, 2));
75
+ }
76
+ export async function migrateThemes(projectDir, projectSourceDir) {
77
+ const { legacyThemeDetails, legacyReactThemeDetails } = await getProjectThemeDetails(projectSourceDir);
78
+ // Migrate the project to a temporary directory to avoid file conflicts in the original project
79
+ const migratedProjectTempDir = await mkdtempAsync(path.join(os.tmpdir(), 'hubspot-migrated-project-'));
80
+ let isRootReactThemeProject = false;
81
+ legacyReactThemeDetails.forEach(themeDetails => {
82
+ isRootReactThemeProject = themeDetails.themePath === projectSourceDir;
83
+ if (!isRootReactThemeProject) {
84
+ buildNewTheme(CMS_ASSETS_KEY, migratedProjectTempDir, themeDetails);
85
+ }
86
+ });
87
+ // Do not support migrating themes that live at the root of the project src directory
88
+ if (isRootReactThemeProject) {
89
+ return {
90
+ legacyThemeDetails,
91
+ legacyReactThemeDetails,
92
+ migrated: false,
93
+ failureReason: errorMessages.migrateThemes.rootReactThemeNotSupported,
94
+ };
95
+ }
96
+ legacyThemeDetails.forEach(themeDetails => {
97
+ buildNewTheme(THEME_KEY, migratedProjectTempDir, themeDetails);
98
+ });
99
+ // Copy the rest of the project files to the temp directory
100
+ fs.cpSync(projectSourceDir, migratedProjectTempDir, {
101
+ recursive: true,
102
+ filter: src => {
103
+ return (!legacyThemeDetails.some(file => src.includes(file.themePath)) &&
104
+ !legacyReactThemeDetails.some(file => src.includes(file.themePath)));
105
+ },
106
+ });
107
+ // Archive the legacy theme files
108
+ const archiveDest = path.join(projectDir, 'archive');
109
+ fs.renameSync(projectSourceDir, archiveDest);
110
+ // Copy the new theme config files to the project source directory
111
+ fs.cpSync(migratedProjectTempDir, projectSourceDir, {
112
+ recursive: true,
113
+ });
114
+ fs.rmSync(migratedProjectTempDir, { recursive: true });
115
+ return {
116
+ legacyThemeDetails,
117
+ legacyReactThemeDetails,
118
+ migrated: true,
119
+ };
120
+ }
@@ -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[];