From 5c11117b1ee9f2d787820bd58c54bf3ff9cc106b Mon Sep 17 00:00:00 2001 From: Tanya Scales Date: Thu, 21 Dec 2023 16:33:51 -0500 Subject: [PATCH 1/7] starting to toy with a createReactModule function --- modules.js | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/modules.js b/modules.js index 4a9babc..5705fb5 100644 --- a/modules.js +++ b/modules.js @@ -183,6 +183,7 @@ const createModule = async ( } ) => { const i18nKey = 'cli.commands.create.subcommands.module'; + const writeModuleMeta = ({ contentTypes, moduleLabel, global }, dest) => { const metaData = { label: moduleLabel, @@ -252,6 +253,86 @@ const createModule = async ( ); }; +const createReactModule = async ( + moduleDefinition, + name, + dest, + getInternalVersion, + options = { + allowExistingDir: false, + } +) => { + // Params shape + /* + { + moduleLabel: 'duh', + reactType: true, + contentTypes: [ 'PAGE' ], + global: false + } + test + /Users/tscales/hubspot-repos/Growth + undefined + */ + + console.log(moduleDefinition.contentTypes); + + // TODO: Refactor to abstract from both create commands + const i18nKey = 'cli.commands.create.subcommands.module'; + const destPath = path.join(dest, `${name}React`); + if (!options.allowExistingDir && fs.existsSync(destPath)) { + logger.error( + i18n(`${i18nKey}.errors.pathExists`, { + path: destPath, + }) + ); + return; + } else { + logger.log( + i18n(`${i18nKey}.creatingPath`, { + path: destPath, + }) + ); + fs.ensureDirSync(destPath); + } + + logger.log( + i18n(`${i18nKey}.creatingModule`, { + path: destPath, + }) + ); + + // meta pattern for react modules + const contentTypeArrayToString = + `[` + + moduleDefinition.contentTypes.map(type => { + return '"' + type + '"'; + }) + + `]`; + + const reactMeta = + `export const meta = { + label: "${moduleDefinition.moduleLabel}", + host_template_types:` + + contentTypeArrayToString + + `, + global: ${moduleDefinition.global} +}`; + + await downloadGitHubRepoContents( + 'HubSpot/cms-sample-assets', + 'modules/SampleJSR', + destPath, + { ref: 'ts/152-JSR-module-create' } + ); + + fs.appendFile(`${destPath}/index.tsx`, reactMeta, err => { + if (err) { + console.log(err); + } + }); +}; + module.exports = { isModuleFolder, isModuleFolderChild, @@ -260,4 +341,5 @@ module.exports = { isModuleHTMLFile, isModuleCSSFile, createModule, + createReactModule, }; From a20f6c6fe2c9dd18fb74d8e47f9341de31de738e Mon Sep 17 00:00:00 2001 From: Tanya Scales Date: Thu, 11 Jan 2024 14:46:30 -0500 Subject: [PATCH 2/7] update create function to handle JSR modules --- lang/en.lyaml | 1 + modules.js | 197 +++++++++++++++++++++++--------------------------- 2 files changed, 93 insertions(+), 105 deletions(-) diff --git a/lang/en.lyaml b/lang/en.lyaml index 9051199..5e0131b 100644 --- a/lang/en.lyaml +++ b/lang/en.lyaml @@ -22,6 +22,7 @@ en: creatingPath: "Creating {{ path }}" errors: pathExists: "The {{ path }} path already exists" + failedToWriteMeta: "There was an problem writing the module's meta data at {{ path }}" project: subcommands: watch: diff --git a/modules.js b/modules.js index 5705fb5..c834183 100644 --- a/modules.js +++ b/modules.js @@ -174,6 +174,18 @@ const isModuleHTMLFile = filePath => MODULE_HTML_EXTENSION_REGEX.test(filePath); */ const isModuleCSSFile = filePath => MODULE_CSS_EXTENSION_REGEX.test(filePath); +/** + * Creates a sample module in the specified destination locally + * @param {object} moduleDefinition + * @param {string} moduleDefinition.moduleLabel - Label for the module + * @param {boolean} moduleDefinition.reactType - Identifies if the module is a JSR type + * @param {Array} moduleDefinition.contentTypes - List of content types that the module can be used with + * @param {boolean} moduleDefinition.global - Identifies if the module is global + * @param {string} name + * @param {string} dest + * @param {object} options + */ + const createModule = async ( moduleDefinition, name, @@ -183,8 +195,53 @@ const createModule = async ( } ) => { const i18nKey = 'cli.commands.create.subcommands.module'; + const { reactType: isReactModule } = moduleDefinition; + + // Ascertain the module's dest path based on module type + const parseDestPath = (name, dest, isReactModule) => { + const folderName = name.endsWith('.module') ? name : `${name}.module`; + + const modulePath = !isReactModule + ? path.join(dest, folderName) + : path.join(dest, `${name}`); + + return modulePath; + }; + + const destPath = parseDestPath(name, dest, isReactModule); - const writeModuleMeta = ({ contentTypes, moduleLabel, global }, dest) => { + // Create module directory + const createModuleDirectory = (allowExistingDir, destPath) => { + if (!allowExistingDir && fs.existsSync(destPath)) { + logger.error( + i18n(`${i18nKey}.errors.pathExists`, { + path: destPath, + }) + ); + return; + } else { + logger.log( + i18n(`${i18nKey}.creatingPath`, { + path: destPath, + }) + ); + fs.ensureDirSync(destPath); + } + + logger.log( + i18n(`${i18nKey}.creatingModule`, { + path: destPath, + }) + ); + }; + + createModuleDirectory(options.allowExistingDir, destPath); + + // Write module meta + const writeModuleMeta = ( + { moduleLabel, contentTypes, global, reactType }, + dest + ) => { const metaData = { label: moduleLabel, css_assets: [], @@ -199,9 +256,27 @@ const createModule = async ( is_available_for_new_content: false, }; - fs.writeJSONSync(dest, metaData, { spaces: 2 }); + if (!reactType) { + fs.writeJSONSync(dest, metaData, { spaces: 2 }); + } else { + fs.appendFile( + `${dest}/index.tsx`, + 'export const meta = ' + JSON.stringify(metaData, null, ' '), + err => { + if (err) { + logger.error( + i18n(`${i18nKey}.errors.failedToWrite`, { + path: `${dest}/index.tsx`, + }) + ); + return; + } + } + ); + } }; + // Filter out ceratin fetched files from the response const moduleFileFilter = (src, dest) => { const emailEnabled = moduleDefinition.contentTypes.includes('EMAIL'); @@ -215,122 +290,35 @@ const createModule = async ( return false; } return true; + case 'global-samplejsr.css': + case 'stories': + case 'tests': + return false; default: return true; } }; - const folderName = - !name || name.endsWith('.module') ? name : `${name}.module`; - const destPath = path.join(dest, folderName); - if (!options.allowExistingDir && fs.existsSync(destPath)) { - logger.error( - i18n(`${i18nKey}.errors.pathExists`, { - path: destPath, - }) - ); - return; - } else { - logger.log( - i18n(`${i18nKey}.creatingPath`, { - path: destPath, - }) - ); - fs.ensureDirSync(destPath); - } + // Download gitHub contents to the dest directory + const sampleAssetPath = !isReactModule + ? 'Sample.module' + : 'SampleJSRInternal'; - logger.log( - i18n(`${i18nKey}.creatingModule`, { - path: destPath, - }) - ); + // TEMPORARY - REMOVE WHEN SAMPLE ASSET IS MERGED + const refBranch = !isReactModule ? '' : 'ts/152-JSR-module-create'; await downloadGitHubRepoContents( 'HubSpot/cms-sample-assets', - 'modules/Sample.module', + `modules/${sampleAssetPath}`, destPath, - { filter: moduleFileFilter } + { filter: moduleFileFilter, ref: refBranch } ); -}; - -const createReactModule = async ( - moduleDefinition, - name, - dest, - getInternalVersion, - options = { - allowExistingDir: false, - } -) => { - // Params shape - /* - { - moduleLabel: 'duh', - reactType: true, - contentTypes: [ 'PAGE' ], - global: false - } - test - /Users/tscales/hubspot-repos/Growth - undefined - */ - console.log(moduleDefinition.contentTypes); + if (isReactModule) { + writeModuleMeta(moduleDefinition, destPath); - // TODO: Refactor to abstract from both create commands - const i18nKey = 'cli.commands.create.subcommands.module'; - const destPath = path.join(dest, `${name}React`); - if (!options.allowExistingDir && fs.existsSync(destPath)) { - logger.error( - i18n(`${i18nKey}.errors.pathExists`, { - path: destPath, - }) - ); - return; - } else { - logger.log( - i18n(`${i18nKey}.creatingPath`, { - path: destPath, - }) - ); - fs.ensureDirSync(destPath); + // TODO: Go into index.tsx and find/replace some string to add import and defaultconfig } - - logger.log( - i18n(`${i18nKey}.creatingModule`, { - path: destPath, - }) - ); - - // meta pattern for react modules - const contentTypeArrayToString = - `[` + - moduleDefinition.contentTypes.map(type => { - return '"' + type + '"'; - }) + - `]`; - - const reactMeta = - `export const meta = { - label: "${moduleDefinition.moduleLabel}", - host_template_types:` + - contentTypeArrayToString + - `, - global: ${moduleDefinition.global} -}`; - - await downloadGitHubRepoContents( - 'HubSpot/cms-sample-assets', - 'modules/SampleJSR', - destPath, - { ref: 'ts/152-JSR-module-create' } - ); - - fs.appendFile(`${destPath}/index.tsx`, reactMeta, err => { - if (err) { - console.log(err); - } - }); }; module.exports = { @@ -341,5 +329,4 @@ module.exports = { isModuleHTMLFile, isModuleCSSFile, createModule, - createReactModule, }; From 239666790aba775505d15a9a9a48ab7e732ddd96 Mon Sep 17 00:00:00 2001 From: Tanya Scales Date: Thu, 11 Jan 2024 15:54:32 -0500 Subject: [PATCH 3/7] Finish manipulating files based on internal flag --- lang/en.lyaml | 1 + modules.js | 73 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/lang/en.lyaml b/lang/en.lyaml index 5e0131b..7c0fb98 100644 --- a/lang/en.lyaml +++ b/lang/en.lyaml @@ -23,6 +23,7 @@ en: errors: pathExists: "The {{ path }} path already exists" failedToWriteMeta: "There was an problem writing the module's meta data at {{ path }}" + fileReadFailure: "Failed to read file at {{ path }}" project: subcommands: watch: diff --git a/modules.js b/modules.js index c834183..2bc4a0e 100644 --- a/modules.js +++ b/modules.js @@ -183,6 +183,7 @@ const isModuleCSSFile = filePath => MODULE_CSS_EXTENSION_REGEX.test(filePath); * @param {boolean} moduleDefinition.global - Identifies if the module is global * @param {string} name * @param {string} dest + * @param {boolean} getInternalVersion - flag to get internal spec of module * @param {object} options */ @@ -190,6 +191,7 @@ const createModule = async ( moduleDefinition, name, dest, + getInternalVersion, options = { allowExistingDir: false, } @@ -259,20 +261,50 @@ const createModule = async ( if (!reactType) { fs.writeJSONSync(dest, metaData, { spaces: 2 }); } else { - fs.appendFile( - `${dest}/index.tsx`, - 'export const meta = ' + JSON.stringify(metaData, null, ' '), - err => { - if (err) { - logger.error( - i18n(`${i18nKey}.errors.failedToWrite`, { - path: `${dest}/index.tsx`, - }) - ); - return; - } + const globalImportString = getInternalVersion + ? 'import "./global-samplejsr.css";' + : ''; + const defaultconfigString = getInternalVersion + ? `export const defaultModuleConfig = { + moduleName: "sample_jsr", + version: 0, +}; + ` + : ''; + + fs.readFile(`${destPath}/index.tsx`, 'utf8', function(err, data) { + if (err) { + logger.error( + i18n(`${i18nKey}.errors.fileReadFailure`, { + path: `${dest}/index.tsx`, + }) + ); + return; } - ); + + const result = data + .replace(/\/\* import global styles \*\//g, globalImportString) + .replace(/\/\* Default config \*\//g, defaultconfigString); + + fs.writeFile(`${destPath}/index.tsx`, result, 'utf8', function(err) { + if (err) return console.log(err); + }); + + fs.appendFile( + `${dest}/index.tsx`, + 'export const meta = ' + JSON.stringify(metaData, null, ' '), + err => { + if (err) { + logger.error( + i18n(`${i18nKey}.errors.failedToWrite`, { + path: `${dest}/index.tsx`, + }) + ); + return; + } + } + ); + }); } }; @@ -293,6 +325,9 @@ const createModule = async ( case 'global-samplejsr.css': case 'stories': case 'tests': + if (getInternalVersion) { + return true; + } return false; default: return true; @@ -300,24 +335,18 @@ const createModule = async ( }; // Download gitHub contents to the dest directory - const sampleAssetPath = !isReactModule - ? 'Sample.module' - : 'SampleJSRInternal'; - - // TEMPORARY - REMOVE WHEN SAMPLE ASSET IS MERGED - const refBranch = !isReactModule ? '' : 'ts/152-JSR-module-create'; + const sampleAssetPath = !isReactModule ? 'Sample.module' : 'SampleJSR'; await downloadGitHubRepoContents( 'HubSpot/cms-sample-assets', `modules/${sampleAssetPath}`, destPath, - { filter: moduleFileFilter, ref: refBranch } + { filter: moduleFileFilter } ); + // Mutating React module files after fetch if (isReactModule) { writeModuleMeta(moduleDefinition, destPath); - - // TODO: Go into index.tsx and find/replace some string to add import and defaultconfig } }; From e95b669f4c146ff118a2d20b64285c7dc3026418 Mon Sep 17 00:00:00 2001 From: Tanya Scales <4976331+TanyaScales@users.noreply.github.com> Date: Fri, 12 Jan 2024 13:42:05 -0500 Subject: [PATCH 4/7] Update modules.js update to arrow function Co-authored-by: Jesse M <48874841+j-malt@users.noreply.github.com> --- modules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules.js b/modules.js index 2bc4a0e..318565f 100644 --- a/modules.js +++ b/modules.js @@ -286,7 +286,7 @@ const createModule = async ( .replace(/\/\* import global styles \*\//g, globalImportString) .replace(/\/\* Default config \*\//g, defaultconfigString); - fs.writeFile(`${destPath}/index.tsx`, result, 'utf8', function(err) { + fs.writeFile(`${destPath}/index.tsx`, result, 'utf8', (err) => { if (err) return console.log(err); }); From cfbefca3aaba3a59d4e47db547798f355cde70f5 Mon Sep 17 00:00:00 2001 From: Tanya Scales Date: Fri, 12 Jan 2024 13:43:37 -0500 Subject: [PATCH 5/7] update path to sample asset --- modules.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules.js b/modules.js index 2bc4a0e..10eb790 100644 --- a/modules.js +++ b/modules.js @@ -335,7 +335,9 @@ const createModule = async ( }; // Download gitHub contents to the dest directory - const sampleAssetPath = !isReactModule ? 'Sample.module' : 'SampleJSR'; + const sampleAssetPath = !isReactModule + ? 'Sample.module' + : 'SampleReactModule'; await downloadGitHubRepoContents( 'HubSpot/cms-sample-assets', From 9b610f5b0d028d444f606a77c27138e562eb861e Mon Sep 17 00:00:00 2001 From: Tanya Scales Date: Fri, 12 Jan 2024 13:44:54 -0500 Subject: [PATCH 6/7] updating to arrow functions --- modules.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules.js b/modules.js index c7c6c52..2340e19 100644 --- a/modules.js +++ b/modules.js @@ -272,7 +272,7 @@ const createModule = async ( ` : ''; - fs.readFile(`${destPath}/index.tsx`, 'utf8', function(err, data) { + fs.readFile(`${destPath}/index.tsx`, 'utf8', (err, data) => { if (err) { logger.error( i18n(`${i18nKey}.errors.fileReadFailure`, { @@ -286,7 +286,7 @@ const createModule = async ( .replace(/\/\* import global styles \*\//g, globalImportString) .replace(/\/\* Default config \*\//g, defaultconfigString); - fs.writeFile(`${destPath}/index.tsx`, result, 'utf8', (err) => { + fs.writeFile(`${destPath}/index.tsx`, result, 'utf8', err => { if (err) return console.log(err); }); From a892862e29e331e4eea2d10186148136b2189af4 Mon Sep 17 00:00:00 2001 From: Tanya Scales Date: Fri, 12 Jan 2024 16:27:45 -0500 Subject: [PATCH 7/7] Keeping the transformation function out of the main create function, moving the other smaller bits back in without function blocks --- lang/en.lyaml | 1 + modules.js | 173 +++++++++++++++++++++++++++----------------------- 2 files changed, 94 insertions(+), 80 deletions(-) diff --git a/lang/en.lyaml b/lang/en.lyaml index 7c0fb98..d355f78 100644 --- a/lang/en.lyaml +++ b/lang/en.lyaml @@ -24,6 +24,7 @@ en: pathExists: "The {{ path }} path already exists" failedToWriteMeta: "There was an problem writing the module's meta data at {{ path }}" fileReadFailure: "Failed to read file at {{ path }}" + failedToWrite: "failed to write to file at {{ path }}" project: subcommands: watch: diff --git a/modules.js b/modules.js index 2340e19..1636888 100644 --- a/modules.js +++ b/modules.js @@ -174,6 +174,76 @@ const isModuleHTMLFile = filePath => MODULE_HTML_EXTENSION_REGEX.test(filePath); */ const isModuleCSSFile = filePath => MODULE_CSS_EXTENSION_REGEX.test(filePath); +// Strings to replace in React module files +const MODULE_STRING_TRANSFORMATIONS = [ + { + regex: /\/\* import global styles \*\//g, + string: 'import "./global-samplejsr.css";', + fallback: '', + }, + { + regex: /\/\* Default config \*\//g, + string: + 'export const defaultModuleConfig = { \n moduleName: "sample_jsr", \n version: 0, \n};', + fallback: '', + }, +]; +/** + * createModule() helper - Takes a file and uses the constants above to transform the contents + * @param {string} file - the file being manipulated + * @param {object} metaData - an object containing the module's metadata + */ + +const transformFileContents = (file, metaData, getInternalVersion) => { + const i18nKey = 'cli.commands.create.subcommands.module.errors'; + fs.readFile(file, 'utf8', (err, data) => { + if (err) { + logger.error( + i18n(`${i18nKey}.fileReadFailure`, { + path: file, + }) + ); + return; + } + + let results = data; + + MODULE_STRING_TRANSFORMATIONS.forEach(entry => { + const replacementString = getInternalVersion + ? entry.string + : entry.fallback; + + results = results.replace(entry.regex, replacementString); + }); + + fs.writeFile(file, results, 'utf8', err => { + if (err) { + logger.error( + i18n(`${i18nKey}.failedToWrite`, { + path: file, + }) + ); + return; + } + }); + + fs.appendFile( + file, + 'export const meta = ' + JSON.stringify(metaData, null, ' '), + err => { + if (err) { + logger.error( + i18n(`${i18nKey}.failedToWrite`, { + path: `${dest}/index.tsx`, + }) + ); + return; + } + } + ); + }); +}; + /** * Creates a sample module in the specified destination locally * @param {object} moduleDefinition @@ -198,46 +268,32 @@ const createModule = async ( ) => { const i18nKey = 'cli.commands.create.subcommands.module'; const { reactType: isReactModule } = moduleDefinition; - - // Ascertain the module's dest path based on module type - const parseDestPath = (name, dest, isReactModule) => { - const folderName = name.endsWith('.module') ? name : `${name}.module`; - - const modulePath = !isReactModule - ? path.join(dest, folderName) - : path.join(dest, `${name}`); - - return modulePath; - }; - - const destPath = parseDestPath(name, dest, isReactModule); - - // Create module directory - const createModuleDirectory = (allowExistingDir, destPath) => { - if (!allowExistingDir && fs.existsSync(destPath)) { - logger.error( - i18n(`${i18nKey}.errors.pathExists`, { - path: destPath, - }) - ); - return; - } else { - logger.log( - i18n(`${i18nKey}.creatingPath`, { - path: destPath, - }) - ); - fs.ensureDirSync(destPath); - } - + const folderName = name.endsWith('.module') ? name : `${name}.module`; + const destPath = !isReactModule + ? path.join(dest, folderName) + : path.join(dest, `${name}`); + + if (!options.allowExistingDir && fs.existsSync(destPath)) { + logger.error( + i18n(`${i18nKey}.errors.pathExists`, { + path: destPath, + }) + ); + return; + } else { logger.log( - i18n(`${i18nKey}.creatingModule`, { + i18n(`${i18nKey}.creatingPath`, { path: destPath, }) ); - }; + fs.ensureDirSync(destPath); + } - createModuleDirectory(options.allowExistingDir, destPath); + logger.log( + i18n(`${i18nKey}.creatingModule`, { + path: destPath, + }) + ); // Write module meta const writeModuleMeta = ( @@ -261,54 +317,11 @@ const createModule = async ( if (!reactType) { fs.writeJSONSync(dest, metaData, { spaces: 2 }); } else { - const globalImportString = getInternalVersion - ? 'import "./global-samplejsr.css";' - : ''; - const defaultconfigString = getInternalVersion - ? `export const defaultModuleConfig = { - moduleName: "sample_jsr", - version: 0, -}; - ` - : ''; - - fs.readFile(`${destPath}/index.tsx`, 'utf8', (err, data) => { - if (err) { - logger.error( - i18n(`${i18nKey}.errors.fileReadFailure`, { - path: `${dest}/index.tsx`, - }) - ); - return; - } - - const result = data - .replace(/\/\* import global styles \*\//g, globalImportString) - .replace(/\/\* Default config \*\//g, defaultconfigString); - - fs.writeFile(`${destPath}/index.tsx`, result, 'utf8', err => { - if (err) return console.log(err); - }); - - fs.appendFile( - `${dest}/index.tsx`, - 'export const meta = ' + JSON.stringify(metaData, null, ' '), - err => { - if (err) { - logger.error( - i18n(`${i18nKey}.errors.failedToWrite`, { - path: `${dest}/index.tsx`, - }) - ); - return; - } - } - ); - }); + transformFileContents(`${dest}/index.tsx`, metaData, getInternalVersion); } }; - // Filter out ceratin fetched files from the response + // Filter out certain fetched files from the response const moduleFileFilter = (src, dest) => { const emailEnabled = moduleDefinition.contentTypes.includes('EMAIL');