From 85ac1f69c48c6081f888bd36a0b67665e25b4ce4 Mon Sep 17 00:00:00 2001 From: Prashant Pandey Date: Tue, 3 Sep 2024 11:05:43 +0530 Subject: [PATCH 01/16] added react section chunking --- src/lib/Theme.ts | 143 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 119 insertions(+), 24 deletions(-) diff --git a/src/lib/Theme.ts b/src/lib/Theme.ts index 2aa50d2d..a063fa09 100644 --- a/src/lib/Theme.ts +++ b/src/lib/Theme.ts @@ -88,6 +88,7 @@ export default class Theme { ); static BUILD_FOLDER = './.fdk/dist'; static SERVE_BUILD_FOLDER = './.fdk/distServed'; + static REACT_SECTIONS_SETTINGS_JSON = path.join(process.cwd(), 'theme', 'sections',"sectionsSettings.json"); static SRC_FOLDER = path.join('.fdk', 'temp-theme'); static VUE_CLI_CONFIG_PATH = path.join('.fdk', 'vue.config.js'); static REACT_CLI_CONFIG_PATH = 'webpack.config.js'; @@ -527,7 +528,9 @@ export default class Theme { // Create index.js with section file imports Logger.info('creating section index file'); + Theme.validateReactSectionFileNames() await Theme.createReactSectionsIndexFile(); + await Theme.createReactSectionsSettingsJson() Logger.info('created section index file'); Logger.info('Installing dependencies'); @@ -842,7 +845,7 @@ export default class Theme { // Clear previosu builds Theme.clearPreviousBuild(); - + Theme.validateReactSectionFileNames() // Create index.js with section file imports await Theme.createReactSectionsIndexFile(); @@ -873,6 +876,7 @@ export default class Theme { isHMREnabled: false, targetDirectory }); + await Theme.createReactSectionsSettingsJson() const parsed = await Theme.getThemeBundle(stats); Logger.info('Uploading theme assets/images'); @@ -1205,6 +1209,7 @@ export default class Theme { // initial build Logger.info(`Locally building`); + Theme.validateReactSectionFileNames() // Create index.js with section file imports await Theme.createReactSectionsIndexFile(); @@ -1367,17 +1372,33 @@ export default class Theme { if (!sections) { Logger.error('Error occured'); } - - const allSections = Object.entries<{ settings: any; Component: any }>( - sections, - ).map(([name, sectionModule]) => ({ - name, - ...(sectionModule.settings || {}), - })); - + const sectionsKeys = Object.keys(sections); + const fileContent = fs.readFileSync(Theme.REACT_SECTIONS_SETTINGS_JSON, 'utf-8'); + const sectionsSettings = JSON.parse(fileContent); + const allSections = [] + sectionsKeys.forEach(section => { + allSections.push({ name: section, ...sectionsSettings[section] }) + }) return allSections; } + static validateReactSectionFileNames() { + Logger.info('Validating Section File Names') + let fileNames = fs + .readdirSync(`${process.cwd()}/theme/sections`) + .filter((o) => o !== 'index.js' && o !== 'sectionsSettings.json'); + fileNames = fileNames.map(filename => filename.replace(/\.jsx$/, '')) + + // Define the regular expression for disallowed patterns + const disallowedPatterns = /[\.,_]|sections|section|\d/; + fileNames.forEach(fileName => { + // Check if the input string matches any disallowed pattern + if (disallowedPatterns.test(fileName.toLowerCase())) { + throw new Error('Invalid sectionFileName: The string contains disallowed characters or numbers or section words.'); + } + }) + return true; + } static evaluateBundle(path, key = 'themeBundle') { const code = fs.readFileSync(path, { encoding: 'utf8' }); const scope = { @@ -1520,31 +1541,103 @@ export default class Theme { fs.writeFileSync(`${process.cwd()}/theme/sections/index.js`, template); } private static async createReactSectionsIndexFile() { + function transformSectionFileName(fileName) { + // Remove the file extension + const nameWithoutExtension = fileName.replace(/\.jsx$/, ''); + + // Extract the base name before any `.section` or `.chunk` + const sectionKey = nameWithoutExtension + .replace(/(\.section|\.chunk|\.section\.chunk)$/, ''); + + // Convert the sectionKey to PascalCase + const SectionName = sectionKey + .split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join('') + 'SectionChunk'; + + return [SectionName, sectionKey]; + } + let fileNames = fs .readdirSync(`${process.cwd()}/theme/sections`) - .filter((o) => o != 'index.js'); + .filter((o) => o !== 'index.js' && o !== 'sectionsSettings.json'); const importingTemplate = fileNames .map((fileName) => { - const [SectionName] = transformSectionFileName(fileName); - return `import * as ${SectionName} from './${fileName}';`; + const [SectionName, sectionKey] = transformSectionFileName(fileName); + return `const ${SectionName} = loadable(() => import(/* webpackChunkName:"${SectionName}" */ './${fileName}'));\n`; }) .join('\n'); - + + const getbundleTemplate = `const getbundle = (type) => { + switch(type) { + ${fileNames.map((fileName) => { + const [SectionName, sectionKey] = transformSectionFileName(fileName); + return `case '${sectionKey}':\n return (props) => <${SectionName} {...props}/>;`; + }).join('\n ')} + default: + return null; + } + };\n`; + const exportingTemplate = `export default { ${fileNames.map((fileName) => { - const [SectionName, sectionKey] = - transformSectionFileName(fileName); - return `'${sectionKey}': { ...${SectionName}, },`; - }).join(` - `)} - }`; - - const content = importingTemplate + '\n\n' + exportingTemplate; - + const [SectionName, sectionKey] = transformSectionFileName(fileName); + return `'${sectionKey}': { ...${SectionName}, Component: getbundle('${sectionKey}') },`; + }).join('\n ')} + };`; + + const content = `import loadable from '@loadable/component';\nimport React from "react";\n\n` + importingTemplate + `\n\n` + getbundleTemplate + `\n\n` + exportingTemplate; + rimraf.sync(`${process.cwd()}/theme/sections/index.js`); fs.writeFileSync(`${process.cwd()}/theme/sections/index.js`, content); } + private static async createReactSectionsSettingsJson() { + const sectionsPath = path.join(process.cwd(), 'theme', 'sections'); + + // Function to extract settings from a JSX file + function extractSettings(fileContent) { + const settingsMatch = fileContent.match(/export\s+const\s+settings\s+=\s+({[\s\S]*?});/); + if (settingsMatch && settingsMatch.length > 1) { + try { + // Safely evaluate the object instead of using JSON.parse + return eval('(' + settingsMatch[1] + ')'); + } catch (error) { + console.error('Failed to parse settings:', error); + return null; + } + } + return null; + } + + // Read all files from the sections directory + let fileNames = fs + .readdirSync(sectionsPath) + .filter((fileName) => fileName.endsWith('.jsx')); + + let sectionsSettings = {}; + + fileNames.forEach((fileName) => { + const filePath = path.join(sectionsPath, fileName); + const fileContent = fs.readFileSync(filePath, 'utf-8'); + + const sectionKey = fileName.replace(/\.jsx$/, ''); + const settings = extractSettings(fileContent); + + if (settings) { + sectionsSettings[sectionKey] = settings; + } + }); + + // Define the output JSON file path + const outputFilePath = Theme.REACT_SECTIONS_SETTINGS_JSON; + + // Write the JSON object to the output file + rimraf.sync(outputFilePath); // Remove the existing JSON file if it exists + fs.writeFileSync(outputFilePath, JSON.stringify(sectionsSettings, null, 2)); + Logger.info('Sections settings JSON file created successfully!'); + } + private static extractSectionsFromFile(path) { let $ = cheerio.load(readFile(path)); let template = $('template').html(); @@ -1606,6 +1699,7 @@ export default class Theme { rimraf.sync(Theme.BUILD_FOLDER); rimraf.sync(Theme.SERVE_BUILD_FOLDER); rimraf.sync(Theme.SRC_ARCHIVE_FOLDER); + rimraf.sync(Theme.REACT_SECTIONS_SETTINGS_JSON) }; private static createVueConfig() { @@ -2352,7 +2446,6 @@ export default class Theme { ) || [] || []; - systemPage.type = 'system'; pagesToSave.push(systemPage); }); @@ -2683,9 +2776,11 @@ export default class Theme { isHMREnabled: false, }); + Theme.validateReactSectionFileNames() await Theme.createReactSectionsIndexFile(); - const parsed = await Theme.getThemeBundle(stats); + await Theme.createReactSectionsSettingsJson() + const parsed = await Theme.getThemeBundle(stats); let available_sections = await Theme.getAvailableReactSectionsForSync( parsed.sections, ); From 62fb8be11be8e346d93c072b41eb73e494228857 Mon Sep 17 00:00:00 2001 From: Prashant Pandey Date: Wed, 4 Sep 2024 11:42:31 +0530 Subject: [PATCH 02/16] createSectionsIndexFile rewritten for vue --- src/helper/build.ts | 4 ++-- src/lib/Theme.ts | 46 ++++++++++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/helper/build.ts b/src/helper/build.ts index 6f63bce3..f38afa8c 100755 --- a/src/helper/build.ts +++ b/src/helper/build.ts @@ -15,10 +15,10 @@ export const DEV_VUE_THEME_ENTRY_FILE = path.join('theme', 'index.js'); export const CDN_ENTRY_FILE = path.join('.fdk', 'cdn_index.js'); export const dynamicCDNScript = ({assetNormalizedBasePath,vueJs }) => { - + const cdnBasePath = vueJs ? `${assetNormalizedBasePath}/` : assetNormalizedBasePath const functionSnippet = ` function getCDNurl() { - let cdnUrl = '${assetNormalizedBasePath}'; + let cdnUrl = '${cdnBasePath}'; try { if (fynd_platform_cdn) { cdnUrl = fynd_platform_cdn diff --git a/src/lib/Theme.ts b/src/lib/Theme.ts index a063fa09..596ce11b 100644 --- a/src/lib/Theme.ts +++ b/src/lib/Theme.ts @@ -1510,35 +1510,43 @@ export default class Theme { } private static async createSectionsIndexFile(available_sections) { available_sections = available_sections || []; + let fileNames = fs .readdirSync(`${process.cwd()}/theme/sections`) - .filter((o) => o != 'index.js'); + .filter((o) => o !== 'index.js' && o !== 'sectionsSettings.json'); + let template = ` ${fileNames - .map((f, i) => `import * as component${i} from './${f}';`) + .map((f, i) => { + const chunkName = available_sections[i]?.label.replace(/\s+/g, '') + 'SectionChunk'; + return `const component${i} = () => import(/* webpackChunkName: "${chunkName}" */ './${f}');`; + }) .join('\n')} + function exportComponents(components) { - return [ - ${available_sections - .map((s, i) => { - return JSON.stringify({ - name: s.name, - label: s.label, - component: '', - }).replace( - '"component":""', - `"component": components[${i}].default`, - ); - }) - .join(',\n')} - ]; + return [ + ${available_sections + .map((s, i) => { + return JSON.stringify({ + name: s.name, + label: s.label, + component: '', + }).replace( + '"component":""', + `"component": components[${i}]`, + ); + }) + .join(',\n')} + ]; } + export default exportComponents([${fileNames .map((f, i) => `component${i}`) - .join(',')}]); - `; + .join(', ')}]); + `; + rimraf.sync(`${process.cwd()}/theme/sections/index.js`); - fs.writeFileSync(`${process.cwd()}/theme/sections/index.js`, template); + fs.writeFileSync(`${process.cwd()}/theme/sections/index.js`, template.trim()); } private static async createReactSectionsIndexFile() { function transformSectionFileName(fileName) { From efb52dc473c3144a5fcac5cb4b4b43ed743b5b2a Mon Sep 17 00:00:00 2001 From: Prashant Pandey Date: Tue, 1 Oct 2024 13:03:20 +0530 Subject: [PATCH 03/16] added non chunking support for react themes --- package.json | 2 +- src/lib/Theme.ts | 255 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 174 insertions(+), 83 deletions(-) diff --git a/package.json b/package.json index bfb207c7..f426998b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gofynd/fdk-cli", - "version": "6.2.1", + "version": "6.3.0", "main": "index.js", "license": "MIT", "bin": { diff --git a/src/lib/Theme.ts b/src/lib/Theme.ts index d5541c7b..30e41301 100644 --- a/src/lib/Theme.ts +++ b/src/lib/Theme.ts @@ -88,7 +88,7 @@ export default class Theme { ); static BUILD_FOLDER = './.fdk/dist'; static SERVE_BUILD_FOLDER = './.fdk/distServed'; - static REACT_SECTIONS_SETTINGS_JSON = path.join(process.cwd(), 'theme', 'sections',"sectionsSettings.json"); + static REACT_SECTIONS_SETTINGS_JSON = path.join(process.cwd(), 'theme', 'sections', "sectionsSettings.json"); static SRC_FOLDER = path.join('.fdk', 'temp-theme'); static VUE_CLI_CONFIG_PATH = path.join('.fdk', 'vue.config.js'); static REACT_CLI_CONFIG_PATH = 'webpack.config.js'; @@ -525,12 +525,17 @@ export default class Theme { process.chdir(path.join('.', options.name)); Logger.info('Installing dependencies'); - // Create index.js with section file imports Logger.info('creating section index file'); - Theme.validateReactSectionFileNames() - await Theme.createReactSectionsIndexFile(); - await Theme.createReactSectionsSettingsJson() + const sectionChunkingEnabled = await Theme.isSectionChunkingEnabled() + if (sectionChunkingEnabled) { + Theme.validateReactSectionFileNames() + await Theme.createReactSectionsChunksIndexFile(); + await Theme.createReactSectionsSettingsJson() + } + else { + await Theme.createReactSectionsBundleIndexFile() + } Logger.info('created section index file'); Logger.info('Installing dependencies'); @@ -553,7 +558,7 @@ export default class Theme { const parsed = await Theme.getThemeBundle(stats); const available_sections = - await Theme.getAvailableReactSectionsForSync(parsed.sections); + await Theme.getAvailableReactSectionsForSync(parsed.sections, sectionChunkingEnabled); let settings_schema = await fs.readJSON( path.join( process.cwd(), @@ -845,9 +850,14 @@ export default class Theme { // Clear previosu builds Theme.clearPreviousBuild(); - Theme.validateReactSectionFileNames() - // Create index.js with section file imports - await Theme.createReactSectionsIndexFile(); + const sectionChunkingEnabled = await Theme.isSectionChunkingEnabled() + if (sectionChunkingEnabled) { + Theme.validateReactSectionFileNames() + await Theme.createReactSectionsChunksIndexFile(); + } + else { + await Theme.createReactSectionsBundleIndexFile() + } const buildPath = path.join(process.cwd(), Theme.BUILD_FOLDER); @@ -876,7 +886,10 @@ export default class Theme { isHMREnabled: false, targetDirectory }); - await Theme.createReactSectionsSettingsJson() + + if (sectionChunkingEnabled) { + await Theme.createReactSectionsSettingsJson() + } const parsed = await Theme.getThemeBundle(stats); Logger.info('Uploading theme assets/images'); @@ -886,7 +899,7 @@ export default class Theme { await Theme.assetsFontsUploader(); let available_sections = - await Theme.getAvailableReactSectionsForSync(parsed.sections); + await Theme.getAvailableReactSectionsForSync(parsed.sections, sectionChunkingEnabled); await Theme.validateAvailableSections(available_sections); @@ -937,12 +950,9 @@ export default class Theme { chalk.green( terminalLink( '', - `${getPlatformUrls().platform}/company/${ - currentContext.company_id - }/application/${ - currentContext.application_id - }/themes/${ - currentContext.theme_id + `${getPlatformUrls().platform}/company/${currentContext.company_id + }/application/${currentContext.application_id + }/themes/${currentContext.theme_id }/edit?preview=true`, ), ), @@ -1082,12 +1092,9 @@ export default class Theme { chalk.green( terminalLink( '', - `${getPlatformUrls().platform}/company/${ - currentContext.company_id - }/application/${ - currentContext.application_id - }/themes/${ - currentContext.theme_id + `${getPlatformUrls().platform}/company/${currentContext.company_id + }/application/${currentContext.application_id + }/themes/${currentContext.theme_id }/edit?preview=true`, ), ), @@ -1123,8 +1130,8 @@ export default class Theme { typeof options['port'] === 'string' ? parseInt(options['port']) : typeof options['port'] === 'number' - ? options['port'] - : DEFAULT_PORT; + ? options['port'] + : DEFAULT_PORT; const port = await getPort(serverPort); if (port !== serverPort) Logger.warn( @@ -1154,8 +1161,8 @@ export default class Theme { typeof options['ssr'] === 'boolean' ? options['ssr'] : options['ssr'] == 'true' - ? true - : false; + ? true + : false; !isSSR ? Logger.warn('Disabling SSR') : null; // initial build @@ -1204,14 +1211,20 @@ export default class Theme { typeof options['hmr'] === 'boolean' ? options['hmr'] : options['hmr'] == 'true' - ? true - : false; + ? true + : false; // initial build Logger.info(`Locally building`); - Theme.validateReactSectionFileNames() - // Create index.js with section file imports - await Theme.createReactSectionsIndexFile(); + const sectionChunkingEnabled = await Theme.isSectionChunkingEnabled() + if (sectionChunkingEnabled) { + Theme.validateReactSectionFileNames() + await Theme.createReactSectionsChunksIndexFile(); + await Theme.createReactSectionsSettingsJson() + } + else { + await Theme.createReactSectionsBundleIndexFile() + } await devReactBuild({ buildFolder: Theme.SERVE_BUILD_FOLDER, @@ -1349,7 +1362,7 @@ export default class Theme { sectionsFiles = fs .readdirSync(path.join(Theme.TEMPLATE_DIRECTORY, '/sections')) .filter((o) => o != 'index.js'); - } catch (err) {} + } catch (err) { } let settings = sectionsFiles.map((f) => { return Theme.extractSettingsFromFile( `${Theme.TEMPLATE_DIRECTORY}/theme/sections/${f}`, @@ -1368,18 +1381,30 @@ export default class Theme { }); return settings; } - private static async getAvailableReactSectionsForSync(sections) { + private static async getAvailableReactSectionsForSync(sections, sectionChunkingEnabled) { if (!sections) { Logger.error('Error occured'); } - const sectionsKeys = Object.keys(sections); - const fileContent = fs.readFileSync(Theme.REACT_SECTIONS_SETTINGS_JSON, 'utf-8'); - const sectionsSettings = JSON.parse(fileContent); - const allSections = [] - sectionsKeys.forEach(section => { - allSections.push({ name: section, ...sectionsSettings[section] }) - }) - return allSections; + if (sectionChunkingEnabled) { + const sectionsKeys = Object.keys(sections); + const fileContent = fs.readFileSync(Theme.REACT_SECTIONS_SETTINGS_JSON, 'utf-8'); + const sectionsSettings = JSON.parse(fileContent); + const allSections = [] + sectionsKeys.forEach(section => { + allSections.push({ name: section, ...sectionsSettings[section] }) + }) + return allSections; + } else { + const allSections = Object.entries<{ settings: any; Component: any }>( + sections, + ).map(([name, sectionModule]) => ({ + name, + ...(sectionModule.settings || {}), + })); + + return allSections; + } + } static validateReactSectionFileNames() { Logger.info('Validating Section File Names') @@ -1510,11 +1535,11 @@ export default class Theme { } private static async createSectionsIndexFile(available_sections) { available_sections = available_sections || []; - + let fileNames = fs .readdirSync(`${process.cwd()}/theme/sections`) .filter((o) => o !== 'index.js'); - + let template = ` ${fileNames .map((f, i) => { @@ -1526,17 +1551,17 @@ export default class Theme { function exportComponents(components) { return [ ${available_sections - .map((s, i) => { - return JSON.stringify({ - name: s.name, - label: s.label, - component: '', - }).replace( - '"component":""', - `"component": components[${i}]`, - ); - }) - .join(',\n')} + .map((s, i) => { + return JSON.stringify({ + name: s.name, + label: s.label, + component: '', + }).replace( + '"component":""', + `"component": components[${i}]`, + ); + }) + .join(',\n')} ]; } @@ -1544,28 +1569,28 @@ export default class Theme { .map((f, i) => `component${i}`) .join(', ')}]); `; - + rimraf.sync(`${process.cwd()}/theme/sections/index.js`); fs.writeFileSync(`${process.cwd()}/theme/sections/index.js`, template.trim()); } - private static async createReactSectionsIndexFile() { + private static async createReactSectionsChunksIndexFile() { function transformSectionFileName(fileName) { // Remove the file extension const nameWithoutExtension = fileName.replace(/\.jsx$/, ''); - + // Extract the base name before any `.section` or `.chunk` const sectionKey = nameWithoutExtension .replace(/(\.section|\.chunk|\.section\.chunk)$/, ''); - + // Convert the sectionKey to PascalCase const SectionName = sectionKey .split('-') .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join('') + 'SectionChunk'; - + return [SectionName, sectionKey]; } - + let fileNames = fs .readdirSync(`${process.cwd()}/theme/sections`) .filter((o) => o !== 'index.js' && o !== 'sectionsSettings.json'); @@ -1576,30 +1601,90 @@ export default class Theme { return `const ${SectionName} = loadable(() => import(/* webpackChunkName:"${SectionName}" */ './${fileName}'));\n`; }) .join('\n'); - + const getbundleTemplate = `const getbundle = (type) => { switch(type) { ${fileNames.map((fileName) => { - const [SectionName, sectionKey] = transformSectionFileName(fileName); - return `case '${sectionKey}':\n return (props) => <${SectionName} {...props}/>;`; - }).join('\n ')} + const [SectionName, sectionKey] = transformSectionFileName(fileName); + return `case '${sectionKey}':\n return (props) => <${SectionName} {...props}/>;`; + }).join('\n ')} default: return null; } };\n`; - + const exportingTemplate = `export default { ${fileNames.map((fileName) => { - const [SectionName, sectionKey] = transformSectionFileName(fileName); - return `'${sectionKey}': { ...${SectionName}, Component: getbundle('${sectionKey}') },`; - }).join('\n ')} + const [SectionName, sectionKey] = transformSectionFileName(fileName); + return `'${sectionKey}': { ...${SectionName}, Component: getbundle('${sectionKey}') },`; + }).join('\n ')} };`; - + const content = `import loadable from '@loadable/component';\nimport React from "react";\n\n` + importingTemplate + `\n\n` + getbundleTemplate + `\n\n` + exportingTemplate; - + rimraf.sync(`${process.cwd()}/theme/sections/index.js`); fs.writeFileSync(`${process.cwd()}/theme/sections/index.js`, content); } + private static async isSectionChunkingEnabled() { + // Read the package.json file + const packageJsonPath = path.join(process.cwd(), 'package.json'); + + try { + const data = fs.readFileSync(packageJsonPath, 'utf-8'); + const packageJson = JSON.parse(data); + + // Check if the "section_chunking" feature is set to true + if (packageJson.fdk_feature && packageJson.fdk_feature?.section_chunking === true) { + return true; + } + return false; + } catch (error) { + console.error("Error reading or parsing package.json:", error); + return false; + } + } + private static async createReactSectionsBundleIndexFile() { + Theme.deleteReactSectionsSettingsJson() + let fileNames = fs + .readdirSync(`${process.cwd()}/theme/sections`) + .filter((o) => o != 'index.js'); + + const importingTemplate = fileNames + .map((fileName) => { + const [SectionName] = transformSectionFileName(fileName); + return `import * as ${SectionName} from './${fileName}';`; + }) + .join('\n'); + + const exportingTemplate = `export default { + ${fileNames.map((fileName) => { + const [SectionName, sectionKey] = + transformSectionFileName(fileName); + return `'${sectionKey}': { ...${SectionName}, },`; + }).join(` + `)} + }`; + + const content = importingTemplate + '\n\n' + exportingTemplate; + + rimraf.sync(`${process.cwd()}/theme/sections/index.js`); + fs.writeFileSync(`${process.cwd()}/theme/sections/index.js`, content); + } + private static deleteReactSectionsSettingsJson() { + const outputFilePath = Theme.REACT_SECTIONS_SETTINGS_JSON; + try { + // Check if the file exists + if (fs.existsSync(outputFilePath)) { + // Delete the file using rimraf (for recursive file deletion) + rimraf.sync(outputFilePath); + Logger.info(`File ${outputFilePath} has been deleted successfully.`); + } else { + Logger.info(`File ${outputFilePath} does not exist.`); + } + } catch (error) { + Logger.error(`Error while deleting the file: ${error}`); + } + } private static async createReactSectionsSettingsJson() { const sectionsPath = path.join(process.cwd(), 'theme', 'sections'); @@ -1641,11 +1726,11 @@ export default class Theme { const outputFilePath = Theme.REACT_SECTIONS_SETTINGS_JSON; // Write the JSON object to the output file - rimraf.sync(outputFilePath); // Remove the existing JSON file if it exists + Theme.deleteReactSectionsSettingsJson() // Remove the existing JSON file if it exists fs.writeFileSync(outputFilePath, JSON.stringify(sectionsSettings, null, 2)); Logger.info('Sections settings JSON file created successfully!'); } - + private static extractSectionsFromFile(path) { let $ = cheerio.load(readFile(path)); let template = $('template').html(); @@ -2299,9 +2384,9 @@ export default class Theme { ) { if ( prop.id === - defaultPage.props[i].id && + defaultPage.props[i].id && prop.type === - defaultPage.props[i].type + defaultPage.props[i].type ) return prop.id; } @@ -2357,7 +2442,7 @@ export default class Theme { ) { customRoutes(ctTemplates[key].children, routerPath); } - }else{ + } else { throw new Error(`Found an invalid custom page URL: ${key}`); } } @@ -2510,9 +2595,9 @@ export default class Theme { ) { if ( prop.id === - defaultPage.props[i].id && + defaultPage.props[i].id && prop.type === - defaultPage.props[i].type + defaultPage.props[i].type ) return prop.id; } @@ -2784,13 +2869,19 @@ export default class Theme { isHMREnabled: false, }); - Theme.validateReactSectionFileNames() - await Theme.createReactSectionsIndexFile(); - await Theme.createReactSectionsSettingsJson() - + const sectionChunkingEnabled = await Theme.isSectionChunkingEnabled() + if (sectionChunkingEnabled) { + Theme.validateReactSectionFileNames() + await Theme.createReactSectionsChunksIndexFile(); + await Theme.createReactSectionsSettingsJson() + } + else { + await Theme.createReactSectionsBundleIndexFile() + } const parsed = await Theme.getThemeBundle(stats); let available_sections = await Theme.getAvailableReactSectionsForSync( parsed.sections, + sectionChunkingEnabled ); await Theme.validateAvailableSections(available_sections); From 1d2b1bde9d0776b0432a9a54d4e5a193d4b565ce Mon Sep 17 00:00:00 2001 From: Prashant Pandey Date: Mon, 7 Oct 2024 11:58:24 +0530 Subject: [PATCH 04/16] Section chunking by default true --- src/lib/Theme.ts | 77 ++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/src/lib/Theme.ts b/src/lib/Theme.ts index 30e41301..213f559f 100644 --- a/src/lib/Theme.ts +++ b/src/lib/Theme.ts @@ -527,15 +527,15 @@ export default class Theme { Logger.info('Installing dependencies'); // Create index.js with section file imports Logger.info('creating section index file'); - const sectionChunkingEnabled = await Theme.isSectionChunkingEnabled() - if (sectionChunkingEnabled) { + const sectionChunkingDisabled = await Theme.isSectionChunkingDisabled() + if (sectionChunkingDisabled) { + await Theme.createReactSectionsBundleIndexFile() + } + else { Theme.validateReactSectionFileNames() await Theme.createReactSectionsChunksIndexFile(); await Theme.createReactSectionsSettingsJson() } - else { - await Theme.createReactSectionsBundleIndexFile() - } Logger.info('created section index file'); Logger.info('Installing dependencies'); @@ -558,7 +558,7 @@ export default class Theme { const parsed = await Theme.getThemeBundle(stats); const available_sections = - await Theme.getAvailableReactSectionsForSync(parsed.sections, sectionChunkingEnabled); + await Theme.getAvailableReactSectionsForSync(parsed.sections, sectionChunkingDisabled); let settings_schema = await fs.readJSON( path.join( process.cwd(), @@ -850,13 +850,13 @@ export default class Theme { // Clear previosu builds Theme.clearPreviousBuild(); - const sectionChunkingEnabled = await Theme.isSectionChunkingEnabled() - if (sectionChunkingEnabled) { - Theme.validateReactSectionFileNames() - await Theme.createReactSectionsChunksIndexFile(); + const sectionChunkingDisabled = await Theme.isSectionChunkingDisabled() + if (sectionChunkingDisabled) { + await Theme.createReactSectionsBundleIndexFile() } else { - await Theme.createReactSectionsBundleIndexFile() + Theme.validateReactSectionFileNames() + await Theme.createReactSectionsChunksIndexFile(); } const buildPath = path.join(process.cwd(), Theme.BUILD_FOLDER); @@ -887,7 +887,7 @@ export default class Theme { targetDirectory }); - if (sectionChunkingEnabled) { + if (!sectionChunkingDisabled) { await Theme.createReactSectionsSettingsJson() } @@ -899,7 +899,7 @@ export default class Theme { await Theme.assetsFontsUploader(); let available_sections = - await Theme.getAvailableReactSectionsForSync(parsed.sections, sectionChunkingEnabled); + await Theme.getAvailableReactSectionsForSync(parsed.sections, sectionChunkingDisabled); await Theme.validateAvailableSections(available_sections); @@ -1216,15 +1216,15 @@ export default class Theme { // initial build Logger.info(`Locally building`); - const sectionChunkingEnabled = await Theme.isSectionChunkingEnabled() - if (sectionChunkingEnabled) { + const sectionChunkingDisabled = await Theme.isSectionChunkingDisabled() + if (sectionChunkingDisabled) { + await Theme.createReactSectionsBundleIndexFile() + } + else { Theme.validateReactSectionFileNames() await Theme.createReactSectionsChunksIndexFile(); await Theme.createReactSectionsSettingsJson() } - else { - await Theme.createReactSectionsBundleIndexFile() - } await devReactBuild({ buildFolder: Theme.SERVE_BUILD_FOLDER, @@ -1381,20 +1381,11 @@ export default class Theme { }); return settings; } - private static async getAvailableReactSectionsForSync(sections, sectionChunkingEnabled) { + private static async getAvailableReactSectionsForSync(sections, sectionChunkingDisabled) { if (!sections) { Logger.error('Error occured'); } - if (sectionChunkingEnabled) { - const sectionsKeys = Object.keys(sections); - const fileContent = fs.readFileSync(Theme.REACT_SECTIONS_SETTINGS_JSON, 'utf-8'); - const sectionsSettings = JSON.parse(fileContent); - const allSections = [] - sectionsKeys.forEach(section => { - allSections.push({ name: section, ...sectionsSettings[section] }) - }) - return allSections; - } else { + if (sectionChunkingDisabled) { const allSections = Object.entries<{ settings: any; Component: any }>( sections, ).map(([name, sectionModule]) => ({ @@ -1402,6 +1393,15 @@ export default class Theme { ...(sectionModule.settings || {}), })); + return allSections; + } else { + const sectionsKeys = Object.keys(sections); + const fileContent = fs.readFileSync(Theme.REACT_SECTIONS_SETTINGS_JSON, 'utf-8'); + const sectionsSettings = JSON.parse(fileContent); + const allSections = [] + sectionsKeys.forEach(section => { + allSections.push({ name: section, ...sectionsSettings[section] }) + }) return allSections; } @@ -1625,16 +1625,15 @@ export default class Theme { rimraf.sync(`${process.cwd()}/theme/sections/index.js`); fs.writeFileSync(`${process.cwd()}/theme/sections/index.js`, content); } - private static async isSectionChunkingEnabled() { + private static async isSectionChunkingDisabled() { // Read the package.json file const packageJsonPath = path.join(process.cwd(), 'package.json'); - try { const data = fs.readFileSync(packageJsonPath, 'utf-8'); const packageJson = JSON.parse(data); - // Check if the "section_chunking" feature is set to true - if (packageJson.fdk_feature && packageJson.fdk_feature?.section_chunking === true) { + // Check if the "disable_section_chunking" feature is set to true + if (packageJson.fdk_feature && packageJson.fdk_feature?.disable_section_chunking === true) { return true; } return false; @@ -2869,19 +2868,19 @@ export default class Theme { isHMREnabled: false, }); - const sectionChunkingEnabled = await Theme.isSectionChunkingEnabled() - if (sectionChunkingEnabled) { + const sectionChunkingDisabled = await Theme.isSectionChunkingDisabled() + if (sectionChunkingDisabled) { + await Theme.createReactSectionsBundleIndexFile() + } + else { Theme.validateReactSectionFileNames() await Theme.createReactSectionsChunksIndexFile(); await Theme.createReactSectionsSettingsJson() } - else { - await Theme.createReactSectionsBundleIndexFile() - } const parsed = await Theme.getThemeBundle(stats); let available_sections = await Theme.getAvailableReactSectionsForSync( parsed.sections, - sectionChunkingEnabled + sectionChunkingDisabled ); await Theme.validateAvailableSections(available_sections); From 2740e9615ad6b25ed14dd62302326c7b502a0f07 Mon Sep 17 00:00:00 2001 From: Prashant Pandey Date: Tue, 15 Oct 2024 20:53:01 +0530 Subject: [PATCH 05/16] publishing beta version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f426998b..de0adf52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gofynd/fdk-cli", - "version": "6.3.0", + "version": "6.2.3-beta.1", "main": "index.js", "license": "MIT", "bin": { From c8c9613478b4e01ad33f223b4ca908d8bf8280cf Mon Sep 17 00:00:00 2001 From: Prashant Pandey Date: Mon, 21 Oct 2024 12:51:38 +0530 Subject: [PATCH 06/16] PR review changes --- src/lib/Theme.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/lib/Theme.ts b/src/lib/Theme.ts index 472b998a..843f9854 100644 --- a/src/lib/Theme.ts +++ b/src/lib/Theme.ts @@ -88,7 +88,7 @@ export default class Theme { ); static BUILD_FOLDER = './.fdk/dist'; static SERVE_BUILD_FOLDER = './.fdk/distServed'; - static REACT_SECTIONS_SETTINGS_JSON = path.join(process.cwd(), 'theme', 'sections', "sectionsSettings.json"); + static REACT_SECTIONS_SETTINGS_JSON = path.join('.fdk', "sectionsSettings.json"); static SRC_FOLDER = path.join('.fdk', 'temp-theme'); static VUE_CLI_CONFIG_PATH = path.join('.fdk', 'vue.config.js'); static REACT_CLI_CONFIG_PATH = 'webpack.config.js'; @@ -854,6 +854,7 @@ export default class Theme { } else { Theme.validateReactSectionFileNames() + await Theme.createReactSectionsSettingsJson() await Theme.createReactSectionsChunksIndexFile(); } @@ -884,10 +885,9 @@ export default class Theme { isHMREnabled: false, targetDirectory }); - - if (!sectionChunkingDisabled) { - await Theme.createReactSectionsSettingsJson() - } + // if (!sectionChunkingDisabled) { + // await Theme.createReactSectionsSettingsJson() + // } const parsed = await Theme.getThemeBundle(stats); Logger.info('Uploading theme assets/images'); @@ -1408,16 +1408,16 @@ export default class Theme { Logger.info('Validating Section File Names') let fileNames = fs .readdirSync(`${process.cwd()}/theme/sections`) - .filter((o) => o !== 'index.js' && o !== 'sectionsSettings.json'); + .filter((o) => o !== 'index.js'); fileNames = fileNames.map(filename => filename.replace(/\.jsx$/, '')) // Define the regular expression for disallowed patterns - const disallowedPatterns = /[\.,_]|sections|section|\d/; + const disallowedPatterns = /[\.,_]|sections|section|\d|[^a-zA-Z0-9-]/; fileNames.forEach(fileName => { // Check if the input string matches any disallowed pattern if (disallowedPatterns.test(fileName.toLowerCase())) { - throw new Error('Invalid sectionFileName: The string contains disallowed characters or numbers or section words.'); + throw new Error(`Invalid sectionFileName: The '${fileName}' contains disallowed characters or numbers or section words.`); } }) return true; @@ -1574,7 +1574,7 @@ export default class Theme { private static async createReactSectionsChunksIndexFile() { function transformSectionFileName(fileName) { // Remove the file extension - const nameWithoutExtension = fileName.replace(/\.jsx$/, ''); + const nameWithoutExtension = fileName.replace(/\.(jsx|tsx)$/, ''); // Extract the base name before any `.section` or `.chunk` const sectionKey = nameWithoutExtension @@ -1591,7 +1591,7 @@ export default class Theme { let fileNames = fs .readdirSync(`${process.cwd()}/theme/sections`) - .filter((o) => o !== 'index.js' && o !== 'sectionsSettings.json'); + .filter((o) => o !== 'index.js'); const importingTemplate = fileNames .map((fileName) => { @@ -2865,14 +2865,14 @@ export default class Theme { }); const sectionChunkingDisabled = await Theme.isSectionChunkingDisabled() - if (sectionChunkingDisabled) { - await Theme.createReactSectionsBundleIndexFile() - } - else { - Theme.validateReactSectionFileNames() - await Theme.createReactSectionsChunksIndexFile(); - await Theme.createReactSectionsSettingsJson() - } + if (sectionChunkingDisabled) { + await Theme.createReactSectionsBundleIndexFile() + } + else { + Theme.validateReactSectionFileNames() + await Theme.createReactSectionsChunksIndexFile(); + await Theme.createReactSectionsSettingsJson() + } const parsed = await Theme.getThemeBundle(stats); let available_sections = await Theme.getAvailableReactSectionsForSync( parsed.sections, From 10b8d318ba816785bf9055f62262c24b5fce1906 Mon Sep 17 00:00:00 2001 From: Prashant Pandey Date: Mon, 21 Oct 2024 16:39:03 +0530 Subject: [PATCH 07/16] added beta version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 979b6e20..d5bdaa73 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gofynd/fdk-cli", - "version": "6.2.2", + "version": "6.3.0.beta.0", "main": "index.js", "license": "MIT", "bin": { From 9c8fa6bd4921e539a5502d6493c0911cc4244cf5 Mon Sep 17 00:00:00 2001 From: Prashant Pandey Date: Tue, 22 Oct 2024 10:58:23 +0530 Subject: [PATCH 08/16] version updated --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d5bdaa73..45f0189f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gofynd/fdk-cli", - "version": "6.3.0.beta.0", + "version": "6.2.3-beta.2", "main": "index.js", "license": "MIT", "bin": { From a317e1f6a1cef623cbc79db36a4b8ad19885cc24 Mon Sep 17 00:00:00 2001 From: Prashant Pandey Date: Thu, 7 Nov 2024 11:47:15 +0530 Subject: [PATCH 09/16] version updated --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 45f0189f..280bc233 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gofynd/fdk-cli", - "version": "6.2.3-beta.2", + "version": "6.2.4-beta.1", "main": "index.js", "license": "MIT", "bin": { From acde9c7b4f980993adf4227cd2a9a192096142bf Mon Sep 17 00:00:00 2001 From: Muralidhar Edam Date: Fri, 27 Dec 2024 14:37:38 +0530 Subject: [PATCH 10/16] locales changes --- package.json | 2 +- src/helper/locales.ts | 203 ++++++++++++++++++++++++++++++++++++++++++ src/lib/Theme.ts | 67 +++++++++----- 3 files changed, 247 insertions(+), 25 deletions(-) create mode 100644 src/helper/locales.ts diff --git a/package.json b/package.json index 280bc233..f10cbaf7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gofynd/fdk-cli", - "version": "6.2.4-beta.1", + "version": "6.2.4-beta.2", "main": "index.js", "license": "MIT", "bin": { diff --git a/src/helper/locales.ts b/src/helper/locales.ts new file mode 100644 index 00000000..dc5ef93e --- /dev/null +++ b/src/helper/locales.ts @@ -0,0 +1,203 @@ +import axios, { AxiosResponse } from 'axios'; +import { promises as fs } from 'fs'; +import * as path from 'path'; + + +interface ApiConfig { + baseUrl: string; + headers: Record; +} + +interface LocaleResource { + locale: string; + resource: Record; +} + +const apiConfig: ApiConfig = { + baseUrl: 'http://localhost:7071/v1.0/organization/2323232/company/6/application/671b70ecfde3892821bd4f5d/static-resources', + headers: { + 'x-user-data': '{"debug":{"source":"grimlock","platform":"000000000000000000000001"},"gender":"male","accountType":"user","active":true,"profilePicUrl":"https://d2co8r51m5ca2d.cloudfront.net/inapp_banners/default_profile_img.png","hasOldPasswordHash":false,"_id":"5e199e6998cfe1776f1385dc","firstName":"Fynd","lastName":"App","username":"app@fynd.com","phoneNumbers":[{"active":true,"primary":true,"verified":true,"phone":"9999632145","countryCode":91}]}', + 'authorization': 'Bearer oa-cbfdf63cd52092d314ba7b377d767255942e68fe', + 'Content-Type': 'application/json' + } +}; + +export const syncLocales = async (): Promise => { + const localFirst: boolean = process.env.LOCAL_FIRST?.toLowerCase() === 'true'; + console.log(`Starting fetchData with LOCAL_FIRST=${localFirst}`); + + try { + const response: AxiosResponse = await axios.get( + `${apiConfig.baseUrl}?template_theme_id=674d7532cf6c66c4b308af58&page_size=500`, + { + headers: apiConfig.headers, + timeout: 10000, + } + ); + console.log('API response received'); + + if (response.status === 200) { + const data = response.data; + console.log(`Fetched ${data.items.length} items from API`); + + const localesFolder: string = path.resolve(process.cwd(),'theme/locales'); + + try { + await fs.mkdir(localesFolder, { recursive: true }); + console.log('Ensured locales folder exists'); + } catch (err) { + console.error(`Error ensuring locales folder exists: ${(err as Error).message}`); + return; + } + + const unmatchedLocales: LocaleResource[] = []; + + let localFiles: string[]; + try { + localFiles = await fs.readdir(localesFolder); + console.log(`Found ${localFiles.length} local files`); + } catch (err) { + console.error(`Error reading locales folder: ${(err as Error).message}`); + return; + } + + for (const file of localFiles) { + const locale: string = path.basename(file, '.json'); + console.log(`Processing local file: ${locale}`); + let localData: Record; + try { + localData = JSON.parse(await fs.readFile(path.join(localesFolder, file), 'utf-8')); + } catch (err) { + console.error(`Error reading file ${file}: ${(err as Error).message}`); + continue; + } + + const matchingItem = data.items.find((item: LocaleResource) => item.locale === locale); + if (!matchingItem) { + console.log(`No matching item found for locale: ${locale}`); + unmatchedLocales.push({ locale, resource: localData }); + if (localFirst) { + console.log(`Creating new resource in API for locale: ${locale}`); + await createLocaleInAPI(localData, locale, 'locale'); + } + } else { + if (localFirst) { + if (JSON.stringify(localData) !== JSON.stringify(matchingItem.resource)) { + console.log(`Updating API resource for locale: ${locale}`); + await updateLocaleInAPI(localData, matchingItem._id); + } else { + console.log(`No changes detected for API resource: ${locale}`); + } + } else { + if (JSON.stringify(localData) !== JSON.stringify(matchingItem.resource)) { + console.log(`Updating local file for locale: ${locale}`); + await updateLocaleFile(path.join(localesFolder, file), matchingItem.resource, matchingItem._id); + } else { + console.log(`No changes detected for local file: ${locale}`); + } + } + } + } + + if (unmatchedLocales.length > 0) { + const unmatchedFilePath: string = path.join(localesFolder, 'unmatched_locales.log'); + try { + await fs.writeFile( + unmatchedFilePath, + unmatchedLocales.map(locale => JSON.stringify(locale)).join('\n'), + 'utf-8' + ); + console.log(`Unmatched locales written to file: ${unmatchedFilePath}`); + } catch (err) { + console.error(`Error writing unmatched locales to file: ${(err as Error).message}`); + } + } + + if (!localFirst) { + for (const item of data.items) { + const locale: string = item.locale; + const localeFile: string = path.join(localesFolder, `${locale}.json`); + const localeData: Record = item.resource; + + if (!localeData) { + console.log(`Skipping empty resource for locale: ${locale}`); + continue; + } + + let currentData: Record; + try { + currentData = JSON.parse(await fs.readFile(localeFile, 'utf-8').catch(() => '{}')); + } catch (err) { + console.error(`Error reading file ${localeFile}: ${(err as Error).message}`); + continue; + } + + if (JSON.stringify(currentData) !== JSON.stringify(localeData)) { + try { + console.log(`Writing updated data to local file: ${localeFile}`); + await fs.writeFile(localeFile, JSON.stringify(localeData, null, 2), 'utf-8'); + } catch (err) { + console.error(`Error writing to file ${localeFile}: ${(err as Error).message}`); + } + } else { + console.log(`No changes detected for local file: ${locale}`); + } + } + } + + console.log('Sync completed successfully.'); + } else { + console.error(`Unexpected status code: ${response.status}.`); + } + } catch (error) { + if (axios.isAxiosError(error) && error.code === 'ECONNABORTED') { + console.error('Error: The request timed out. Please try again later.'); + } else { + console.error(`Error fetching data: ${error?.response?.status} - ${error?.response?.statusText || error?.message}`); + } + } +}; + +const createLocaleInAPI = async (data: Record, locale: string, type: string): Promise => { + try { + console.log(`Creating resource in API for locale: ${locale}`); + const response: AxiosResponse = await axios.post( + apiConfig.baseUrl, + { + template_theme_id: '674d7532cf6c66c4b308af58', + locale: locale, + resource: data, + type: type, + is_template: false, + }, + { headers: apiConfig.headers } + ); + console.log('Locale created in API:', response.data); + } catch (error) { + console.error('Error creating locale in API:', (error as Error).message); + } +}; + +const updateLocaleInAPI = async (data: Record, id: string): Promise => { + try { + console.log(`Updating resource in API for ID: ${id}`); + const response: AxiosResponse = await axios.put( + `${apiConfig.baseUrl}/${id}`, + { resource: data }, + { headers: apiConfig.headers } + ); + console.log('Locale updated in API:', response.data); + } catch (error) { + console.error('Error updating locale in API:', (error as Error).message); + } +}; + +const updateLocaleFile = async (filePath: string, data: Record, id: string): Promise => { + try { + await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8'); + console.log(`Locale file updated: ${filePath}`); + await updateLocaleInAPI(data, id); + } catch (err) { + console.error(`Error writing to file ${filePath}: ${(err as Error).message}`); + } +}; diff --git a/src/lib/Theme.ts b/src/lib/Theme.ts index 843f9854..f198fc08 100644 --- a/src/lib/Theme.ts +++ b/src/lib/Theme.ts @@ -3,7 +3,7 @@ import { createContext, decodeBase64, evaluateModule, - getActiveContext, + getActiveContext, pageNameModifier, isAThemeDirectory, installNpmPackages, @@ -27,6 +27,7 @@ import glob from 'glob'; import _ from 'lodash'; import React from 'react'; import * as ReactRouterDOM from 'react-router-dom'; +import { syncLocales } from '../helper/locales' import { createDirectory, writeFile, readFile } from '../helper/file.utils'; import { customAlphabet } from 'nanoid'; @@ -711,10 +712,7 @@ export default class Theme { Logger.error({ error }); } - await Theme.writeSettingJson( - Theme.getSettingsSchemaPath(), - _.get(themeData, 'config.global_schema', { props: [] }), - ); + await Theme.syncRemoteToLocal(); fs.writeJson( path.join(targetDirectory, 'config.json'), { @@ -860,6 +858,8 @@ export default class Theme { const buildPath = path.join(process.cwd(), Theme.BUILD_FOLDER); + await syncLocales(); + Logger.info('Creating theme source code zip file'); // Remove temp source folder rimraf.sync(path.join(process.cwd(), Theme.SRC_FOLDER)); @@ -1272,6 +1272,7 @@ export default class Theme { spinner.start(); rimraf.sync(path.resolve(process.cwd(), './theme')); createDirectory('theme'); + await syncLocales(); const { data: themeData } = await ThemeService.getThemeById(null); const theme = _.cloneDeep({ ...themeData }); rimraf.sync(path.resolve(process.cwd(), './.fdk/archive')); @@ -1303,6 +1304,8 @@ export default class Theme { Theme.getSettingsSchemaPath(), _.get(theme, 'config.global_schema', { props: [] }), ); + await Theme.syncRemoteToLocal(theme); + const packageJSON = await fs.readJSON( process.cwd() + '/package.json', ); @@ -1316,21 +1319,43 @@ export default class Theme { throw new CommandError(error.message, error.code); } }; - public static pullThemeConfig = async () => { + + public static syncRemoteToLocal = async (theme) => { try { - const { data: theme } = await ThemeService.getThemeById(null); - const { config } = theme; - const newConfig: any = {}; - if (config) { - newConfig.list = config.list; - newConfig.current = config.current; - newConfig.preset = config.preset; - } + const newConfig = Theme.getSettingsData(theme); await Theme.writeSettingJson( Theme.getSettingsDataPath(), newConfig, ); - Theme.createVueConfig(); + Logger.info('Config updated successfully'); + } catch (error) { + throw new CommandError(error.message, error.code); + } + } + + public static syncLocalToRemote = async (theme) => { + try { + const { data: theme } = await ThemeService.getThemeById(null); + Logger.info('Config updated successfully'); + } catch (error) { + throw new CommandError(error.message, error.code); + } + } + + public static isAnyDeltaBetweenLocalAndRemote = async (theme, isNew) => { + const newConfig = Theme.getSettingsData(theme); + const oldConfig = await Theme.readSettingsJson( + Theme.getSettingsDataPath(), + ); + + return !isNew && !_.isEqual(newConfig, oldConfig); + } + + public static pullThemeConfig = async () => { + try { + const { data: theme } = await ThemeService.getThemeById(null); + await Theme.isAnyDeltaBetweenLocalAndRemote(theme); + await Theme.syncRemoteToLocal(); Logger.info('Config updated successfully'); } catch (error) { throw new CommandError(error.message, error.code); @@ -2717,10 +2742,6 @@ export default class Theme { private static matchWithLatestPlatformConfig = async (theme, isNew) => { try { - const newConfig = Theme.getSettingsData(theme); - const oldConfig = await Theme.readSettingsJson( - Theme.getSettingsDataPath(), - ); const questions = [ { type: 'confirm', @@ -2728,15 +2749,13 @@ export default class Theme { message: 'Do you wish to pull config from remote?', }, ]; - if (!isNew && !_.isEqual(newConfig, oldConfig)) { + if (await Theme.isAnyDeltaBetweenLocalAndRemote(theme, isNew)) { await inquirer.prompt(questions).then(async (answers) => { if (answers.pullConfig) { - await Theme.writeSettingJson( - Theme.getSettingsDataPath(), - newConfig, - ); + await Theme.syncRemoteToLocal(theme); Logger.info('Config updated successfully'); } else { + await Theme.syncLocalToRemote(theme); Logger.warn('Using local config to sync'); } }); From aeec17f7e55c1885b735cd4bc71e04a4d7bf80cc Mon Sep 17 00:00:00 2001 From: Muralidhar Edam Date: Tue, 7 Jan 2025 19:36:05 +0530 Subject: [PATCH 11/16] locales changes --- .DS_Store | Bin 6148 -> 6148 bytes src/helper/locales.ts | 159 +++++++++++++++++------- src/helper/serve.utils.ts | 49 ++++++++ src/lib/Theme.ts | 32 ++--- src/lib/api/services/locales.service.ts | 66 ++++++++++ src/lib/api/services/url.ts | 33 +++++ 6 files changed, 276 insertions(+), 63 deletions(-) create mode 100644 src/lib/api/services/locales.service.ts diff --git a/.DS_Store b/.DS_Store index 406f5c3ff68699f73e231cedeafa26d8d26c9880..1cf87f697cc3f13c493e09339f72994e3f178a5f 100644 GIT binary patch delta 32 ocmZoMXfc@J&&atkU^g=(=Vl(3pNyN`*$yyHY;fAl&heKY0IdxQx&QzG delta 85 zcmZoMXfc@J&&ahgU^g=(*Jd7;pN!^$3nl|h#whan%#*JH?HC; } -const apiConfig: ApiConfig = { - baseUrl: 'http://localhost:7071/v1.0/organization/2323232/company/6/application/671b70ecfde3892821bd4f5d/static-resources', - headers: { - 'x-user-data': '{"debug":{"source":"grimlock","platform":"000000000000000000000001"},"gender":"male","accountType":"user","active":true,"profilePicUrl":"https://d2co8r51m5ca2d.cloudfront.net/inapp_banners/default_profile_img.png","hasOldPasswordHash":false,"_id":"5e199e6998cfe1776f1385dc","firstName":"Fynd","lastName":"App","username":"app@fynd.com","phoneNumbers":[{"active":true,"primary":true,"verified":true,"phone":"9999632145","countryCode":91}]}', - 'authorization': 'Bearer oa-cbfdf63cd52092d314ba7b377d767255942e68fe', - 'Content-Type': 'application/json' +export const hasAnyDeltaBetweenLocalAndRemote = async (): Promise => { + let comparedFiles = 0; // Track the number of files compared + try { + console.log('Starting comparison between local and remote data'); + + // Fetch remote data from the API + const response: AxiosResponse = await LocalesService.getLocalesByThemeId(null); + + console.log('Response received from API:', response.status); + + if (response.status === 200) { + const data = response.data; // Extract the data from the API response + console.log('Remote data retrieved:', data); + + // Ensure the local locales folder exists + const localesFolder: string = path.resolve(process.cwd(), 'theme/locales'); + await fs.mkdir(localesFolder, { recursive: true }); + console.log('Locales folder ensured at:', localesFolder); + + // Read all files in the local locales folder + const localFiles = await fs.readdir(localesFolder); + console.log('Local files found:', localFiles); + + // Compare each local file with the corresponding remote resource + for (const file of localFiles) { + const locale = path.basename(file, '.json'); // Extract the locale name from the file name + console.log('Processing local file:', file); + + const localData = JSON.parse(await fs.readFile(path.join(localesFolder, file), 'utf-8')); // Read and parse the local file + const matchingItem = data.items.find((item: LocaleResource) => item.locale === locale); // Find the corresponding remote item + + if (!matchingItem) { // If no matching remote item exists + console.log('No matching remote item found for locale:', locale); + return true; // Changes detected + } + + if (JSON.stringify(localData) !== JSON.stringify(matchingItem.resource)) { // Compare the local and remote data + console.log('Data mismatch found for locale:', locale); + return true; // Changes detected + } + + comparedFiles++; // Increment compared file count + } + + // Compare each remote resource with the corresponding local file + for (const item of data.items) { + const localeFile = path.join(localesFolder, `${item.locale}.json`); // Construct the local file path + console.log('Processing remote item for locale file:', localeFile); + + const localeData = item.resource; // Extract the remote resource data + let currentData = {}; + + try { + // Attempt to read and parse the local file + currentData = JSON.parse(await fs.readFile(localeFile, 'utf-8')); + } catch (error) { + console.log('Error reading local file or file not found for locale:', item.locale, error); + currentData = {}; // Default to an empty object if the file is missing or invalid + } + + if (JSON.stringify(currentData) !== JSON.stringify(localeData)) { // Compare the local and remote data + console.log('Data mismatch found for remote locale:', item.locale); + return true; // Changes detected + } + + comparedFiles++; // Increment compared file count + } + } else { + console.error(`Unexpected status code: ${response.status}.`); // Handle unexpected response status codes + return false; + } + } catch (error) { + console.error('Error checking for changes:', error); // Log errors during the comparison process + return false; } + + console.log(`Comparison completed. Total files compared: ${comparedFiles}`); // Log the summary of comparisons + console.log('No changes detected between local and remote data'); // Log when no changes are detected + return false; // Return false if no changes were found }; -export const syncLocales = async (): Promise => { - const localFirst: boolean = process.env.LOCAL_FIRST?.toLowerCase() === 'true'; - console.log(`Starting fetchData with LOCAL_FIRST=${localFirst}`); +export const syncLocales = async (syncMode: SyncMode): Promise => { + + console.log(`Starting fetchData with SyncMode=${syncMode}`); try { - const response: AxiosResponse = await axios.get( - `${apiConfig.baseUrl}?template_theme_id=674d7532cf6c66c4b308af58&page_size=500`, - { - headers: apiConfig.headers, - timeout: 10000, - } - ); + const response: AxiosResponse = await LocalesService.getLocalesByThemeId(null); + console.log('API response received'); if (response.status === 200) { @@ -76,12 +155,12 @@ export const syncLocales = async (): Promise => { if (!matchingItem) { console.log(`No matching item found for locale: ${locale}`); unmatchedLocales.push({ locale, resource: localData }); - if (localFirst) { + if (syncMode === SyncMode.PUSH) { console.log(`Creating new resource in API for locale: ${locale}`); await createLocaleInAPI(localData, locale, 'locale'); } } else { - if (localFirst) { + if (syncMode === SyncMode.PUSH) { if (JSON.stringify(localData) !== JSON.stringify(matchingItem.resource)) { console.log(`Updating API resource for locale: ${locale}`); await updateLocaleInAPI(localData, matchingItem._id); @@ -100,20 +179,10 @@ export const syncLocales = async (): Promise => { } if (unmatchedLocales.length > 0) { - const unmatchedFilePath: string = path.join(localesFolder, 'unmatched_locales.log'); - try { - await fs.writeFile( - unmatchedFilePath, - unmatchedLocales.map(locale => JSON.stringify(locale)).join('\n'), - 'utf-8' - ); - console.log(`Unmatched locales written to file: ${unmatchedFilePath}`); - } catch (err) { - console.error(`Error writing unmatched locales to file: ${(err as Error).message}`); - } + console.log('Unmatched locales:', unmatchedLocales); } - if (!localFirst) { + if (syncMode === SyncMode.PULL) { for (const item of data.items) { const locale: string = item.locale; const localeFile: string = path.join(localesFolder, `${locale}.json`); @@ -161,19 +230,17 @@ export const syncLocales = async (): Promise => { const createLocaleInAPI = async (data: Record, locale: string, type: string): Promise => { try { console.log(`Creating resource in API for locale: ${locale}`); - const response: AxiosResponse = await axios.post( - apiConfig.baseUrl, - { - template_theme_id: '674d7532cf6c66c4b308af58', - locale: locale, - resource: data, - type: type, - is_template: false, - }, - { headers: apiConfig.headers } - ); + const activeContext = getActiveContext(); + const response: AxiosResponse = await LocalesService.createLocale(null, { + theme_id: activeContext.theme_id, + locale: locale, + resource: data, + type: type, + template: false, + }) console.log('Locale created in API:', response.data); } catch (error) { + console.log(error); console.error('Error creating locale in API:', (error as Error).message); } }; @@ -181,13 +248,12 @@ const createLocaleInAPI = async (data: Record, locale: string, type const updateLocaleInAPI = async (data: Record, id: string): Promise => { try { console.log(`Updating resource in API for ID: ${id}`); - const response: AxiosResponse = await axios.put( - `${apiConfig.baseUrl}/${id}`, - { resource: data }, - { headers: apiConfig.headers } - ); + + const response: AxiosResponse = await LocalesService.updateLocale(null, id, { resource: data }); + console.log('Locale updated in API:', response.data); } catch (error) { + console.log(error); console.error('Error updating locale in API:', (error as Error).message); } }; @@ -196,7 +262,6 @@ const updateLocaleFile = async (filePath: string, data: Record, id: try { await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8'); console.log(`Locale file updated: ${filePath}`); - await updateLocaleInAPI(data, id); } catch (err) { console.error(`Error writing to file ${filePath}: ${(err as Error).message}`); } diff --git a/src/helper/serve.utils.ts b/src/helper/serve.utils.ts index d9883fab..6b5114b5 100644 --- a/src/helper/serve.utils.ts +++ b/src/helper/serve.utils.ts @@ -27,6 +27,7 @@ import CommandError from '../lib/CommandError'; import Debug from '../lib/Debug'; import { SupportedFrameworks } from '../lib/ExtensionSection'; import https from 'https'; +import Tunnel from '../lib/Tunnel'; const packageJSON = require('../../package.json'); const BUILD_FOLDER = './.fdk/dist'; @@ -360,9 +361,32 @@ export async function startServer({ domain, host, isSSR, port }) { }); } + async function startTunnel(port: number) { + + try { + const tunnelInstance = new Tunnel({ + port, + }) + + const tunnelUrl = await tunnelInstance.startTunnel(); + + console.info(` + Started cloudflare tunnel at ${port}: ${tunnelUrl}`) + return { + url: tunnelUrl, + port, + }; + } catch (error) { + Logger.error('Error during starting cloudflare tunnel: ' + error.message); + return; + } +} + export async function startReactServer({ domain, host, isHMREnabled, port }) { const { currentContext, app, server, io } = await setupServer({ domain }); + const { url } = await startTunnel(port); + if (isHMREnabled) { let webpackConfigFromTheme = {}; const themeWebpackConfigPath = path.join( @@ -421,6 +445,30 @@ export async function startReactServer({ domain, host, isHMREnabled, port }) { const uploadedFiles = {}; + app.get('/getAllStaticResources', (req, res) => { + const locale = req.query.locale || 'en'; + console.log(locale); + const localesFolder: string = path.resolve(process.cwd(), 'theme/locales'); + const locales = fs.readdirSync(localesFolder).filter(file => file.split('.')[0] === locale); + const localesArray = []; + + // Read content of each locale file + locales.forEach(locale => { + const filePath = path.join(localesFolder, locale); + try { + const content = fs.readFileSync(filePath, 'utf8'); + localesArray.push({ + "locale":locale.replace('.json', ''), + "resource":JSON.parse(content) + }); + } catch (error) { + Logger.error(`Error reading locale file ${locale}: ${error.message}`); + } + }); + + res.json(localesArray); + }); + app.get('/*', async (req, res) => { // If browser is not requesting for html page (it can be file, API call, etc...), then fetch and send requested data directly from source const acceptHeader = req.get('Accept'); @@ -489,6 +537,7 @@ export async function startReactServer({ domain, host, isHMREnabled, port }) { cliMeta: { port, domain: getFullLocalUrl(port), + tunnelUrl: url, }, }, { diff --git a/src/lib/Theme.ts b/src/lib/Theme.ts index f198fc08..580bbbb0 100644 --- a/src/lib/Theme.ts +++ b/src/lib/Theme.ts @@ -27,7 +27,7 @@ import glob from 'glob'; import _ from 'lodash'; import React from 'react'; import * as ReactRouterDOM from 'react-router-dom'; -import { syncLocales } from '../helper/locales' +import { syncLocales, hasAnyDeltaBetweenLocalAndRemote, SyncMode } from '../helper/locales' import { createDirectory, writeFile, readFile } from '../helper/file.utils'; import { customAlphabet } from 'nanoid'; @@ -711,8 +711,12 @@ export default class Theme { } catch (error) { Logger.error({ error }); } + await Theme.writeSettingJson( + Theme.getSettingsSchemaPath(), + _.get(themeData, 'config.global_schema', { props: [] }), + ); - await Theme.syncRemoteToLocal(); + // await Theme.syncRemoteToLocal(themeData); fs.writeJson( path.join(targetDirectory, 'config.json'), { @@ -857,8 +861,6 @@ export default class Theme { } const buildPath = path.join(process.cwd(), Theme.BUILD_FOLDER); - - await syncLocales(); Logger.info('Creating theme source code zip file'); // Remove temp source folder @@ -1269,10 +1271,10 @@ export default class Theme { public static pullTheme = async () => { let spinner = new Spinner('Pulling theme data'); try { + spinner.start(); rimraf.sync(path.resolve(process.cwd(), './theme')); createDirectory('theme'); - await syncLocales(); const { data: themeData } = await ThemeService.getThemeById(null); const theme = _.cloneDeep({ ...themeData }); rimraf.sync(path.resolve(process.cwd(), './.fdk/archive')); @@ -1304,8 +1306,8 @@ export default class Theme { Theme.getSettingsSchemaPath(), _.get(theme, 'config.global_schema', { props: [] }), ); - await Theme.syncRemoteToLocal(theme); - + // await Theme.syncRemoteToLocal(theme); + const packageJSON = await fs.readJSON( process.cwd() + '/package.json', ); @@ -1327,7 +1329,8 @@ export default class Theme { Theme.getSettingsDataPath(), newConfig, ); - Logger.info('Config updated successfully'); + await syncLocales(SyncMode.PULL); + Logger.info('Remote to Local: Config updated successfully'); } catch (error) { throw new CommandError(error.message, error.code); } @@ -1336,7 +1339,8 @@ export default class Theme { public static syncLocalToRemote = async (theme) => { try { const { data: theme } = await ThemeService.getThemeById(null); - Logger.info('Config updated successfully'); + await syncLocales(SyncMode.PUSH); + Logger.info('Locale to Remote: Config updated successfully'); } catch (error) { throw new CommandError(error.message, error.code); } @@ -1347,16 +1351,14 @@ export default class Theme { const oldConfig = await Theme.readSettingsJson( Theme.getSettingsDataPath(), ); - - return !isNew && !_.isEqual(newConfig, oldConfig); + const isLocalAndRemoteChanged = await hasAnyDeltaBetweenLocalAndRemote(); + return (!isNew && !_.isEqual(newConfig, oldConfig)) || isLocalAndRemoteChanged; } public static pullThemeConfig = async () => { try { const { data: theme } = await ThemeService.getThemeById(null); - await Theme.isAnyDeltaBetweenLocalAndRemote(theme); - await Theme.syncRemoteToLocal(); - Logger.info('Config updated successfully'); + await Theme.syncRemoteToLocal(theme); } catch (error) { throw new CommandError(error.message, error.code); } @@ -2753,10 +2755,8 @@ export default class Theme { await inquirer.prompt(questions).then(async (answers) => { if (answers.pullConfig) { await Theme.syncRemoteToLocal(theme); - Logger.info('Config updated successfully'); } else { await Theme.syncLocalToRemote(theme); - Logger.warn('Using local config to sync'); } }); } diff --git a/src/lib/api/services/locales.service.ts b/src/lib/api/services/locales.service.ts new file mode 100644 index 00000000..ac135b21 --- /dev/null +++ b/src/lib/api/services/locales.service.ts @@ -0,0 +1,66 @@ +import { getActiveContext } from '../../../helper/utils'; +import ApiClient from '../ApiClient'; +import { URLS } from './url'; +import { getCommonHeaderOptions } from './utils'; + + +export default { + getLocalesByThemeId: async (data) => { + try { + const activeContext = data ? data : getActiveContext(); + const axiosOption = Object.assign({}, getCommonHeaderOptions()); + const res = await ApiClient.get( + URLS.GET_LOCALES( + activeContext.application_id, + activeContext.company_id, + activeContext.theme_id, + ), + axiosOption, + ); + return res; + } catch (error) { + throw error; + } + }, + createLocale: async (data, requestBody) => { + try { + const activeContext = data ? data : getActiveContext(); + const axiosOption = Object.assign({}, + { + data: requestBody + }, + getCommonHeaderOptions()); + const res = await ApiClient.post( + URLS.CREATE_LOCALE( + activeContext.application_id, + activeContext.company_id + ), + axiosOption, + ); + return res; + } catch (error) { + throw error; + } + }, + updateLocale: async (data, resource_id, requestBody) => { + try { + const activeContext = data ? data : getActiveContext(); + const axiosOption = Object.assign({}, + { + data: requestBody, + }, + getCommonHeaderOptions()); + const res = await ApiClient.put( + URLS.UPDATE_LOCALE( + activeContext.application_id, + activeContext.company_id, + resource_id + ), + axiosOption, + ); + return res; + } catch (error) { + throw error; + } + } +} \ No newline at end of file diff --git a/src/lib/api/services/url.ts b/src/lib/api/services/url.ts index f06b3fc1..a2386506 100644 --- a/src/lib/api/services/url.ts +++ b/src/lib/api/services/url.ts @@ -28,6 +28,8 @@ const MIXMASTER_URL = (serverType: string) => getBaseURL() + `/service/${serverType}/partners/v` + apiVersion; const ASSET_URL = () => getBaseURL() + '/service/partner/assets/v' + apiVersion; +const LOCALES_URL = () => getBaseURL() + '/service/partner/content/v' + apiVersion; + export const URLS = { // AUTHENTICATION LOGIN_USER: () => { @@ -232,4 +234,35 @@ export const URLS = { `/organization/${getOrganizationId()}/accounts/access-request?page_size=${page_size}&page_no=${page_no}&request_status=accepted`, ); }, + + //Locales + GET_LOCALES: ( + application_id: string, + company_id: number, + theme_id: string, + ) => { + return urlJoin( + LOCALES_URL(), + `organization/${getOrganizationId()}/company/${company_id}/application/${application_id}/static-resources?theme_id=${theme_id}&page_size=500`, + ); + }, + CREATE_LOCALE: ( + application_id: string, + company_id: number + ) => { + return urlJoin( + LOCALES_URL(), + `organization/${getOrganizationId()}/company/${company_id}/application/${application_id}/static-resources`, + ); + }, + UPDATE_LOCALE: ( + application_id: string, + company_id: number, + resource_id: string + ) => { + return urlJoin( + LOCALES_URL(), + `organization/${getOrganizationId()}/company/${company_id}/application/${application_id}/static-resources/${resource_id}`, + ); + } }; From dee306a71e047217d2a683ea7dc203a891fd4a1a Mon Sep 17 00:00:00 2001 From: Muralidhar Edam Date: Wed, 8 Jan 2025 13:17:14 +0530 Subject: [PATCH 12/16] cookie set --- src/helper/serve.utils.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/helper/serve.utils.ts b/src/helper/serve.utils.ts index 6b5114b5..9c48b623 100644 --- a/src/helper/serve.utils.ts +++ b/src/helper/serve.utils.ts @@ -466,7 +466,7 @@ export async function startReactServer({ domain, host, isHMREnabled, port }) { } }); - res.json(localesArray); + res.json({"items":localesArray}); }); app.get('/*', async (req, res) => { @@ -541,7 +541,10 @@ export async function startReactServer({ domain, host, isHMREnabled, port }) { }, }, { - headers, + headers: { + ...headers, + cookie: req.headers.cookie, + }, }, ) .catch((error) => { From 90920d7d3e4f61d301182cd9570e18b8eb4ba0c9 Mon Sep 17 00:00:00 2001 From: Muralidhar Edam Date: Wed, 8 Jan 2025 14:15:08 +0530 Subject: [PATCH 13/16] skip if locales folder does not exists --- src/helper/locales.ts | 14 +++++++++++--- src/lib/Theme.ts | 9 ++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/helper/locales.ts b/src/helper/locales.ts index c2c6f083..dc7e410f 100644 --- a/src/helper/locales.ts +++ b/src/helper/locales.ts @@ -1,6 +1,7 @@ import axios, { AxiosResponse } from 'axios'; import { getActiveContext } from '../helper/utils'; import { promises as fs } from 'fs'; +import fs_sync from 'fs'; import * as path from 'path'; import LocalesService from '../lib/api/services/locales.service'; @@ -25,11 +26,19 @@ interface LocaleResource { resource: Record; } -export const hasAnyDeltaBetweenLocalAndRemote = async (): Promise => { +export const hasAnyDeltaBetweenLocalAndRemoteLocales = async (): Promise => { let comparedFiles = 0; // Track the number of files compared try { console.log('Starting comparison between local and remote data'); + const localesFolder: string = path.resolve(process.cwd(), 'theme/locales'); + + if (!fs_sync.existsSync(localesFolder)) { + console.log('Locales folder does not exist'); + return false; + } + + // Fetch remote data from the API const response: AxiosResponse = await LocalesService.getLocalesByThemeId(null); @@ -39,8 +48,7 @@ export const hasAnyDeltaBetweenLocalAndRemote = async (): Promise => { const data = response.data; // Extract the data from the API response console.log('Remote data retrieved:', data); - // Ensure the local locales folder exists - const localesFolder: string = path.resolve(process.cwd(), 'theme/locales'); + // Ensure the locales folder exists await fs.mkdir(localesFolder, { recursive: true }); console.log('Locales folder ensured at:', localesFolder); diff --git a/src/lib/Theme.ts b/src/lib/Theme.ts index 580bbbb0..742395a9 100644 --- a/src/lib/Theme.ts +++ b/src/lib/Theme.ts @@ -27,7 +27,7 @@ import glob from 'glob'; import _ from 'lodash'; import React from 'react'; import * as ReactRouterDOM from 'react-router-dom'; -import { syncLocales, hasAnyDeltaBetweenLocalAndRemote, SyncMode } from '../helper/locales' +import { syncLocales, hasAnyDeltaBetweenLocalAndRemoteLocales, SyncMode } from '../helper/locales' import { createDirectory, writeFile, readFile } from '../helper/file.utils'; import { customAlphabet } from 'nanoid'; @@ -1351,8 +1351,11 @@ export default class Theme { const oldConfig = await Theme.readSettingsJson( Theme.getSettingsDataPath(), ); - const isLocalAndRemoteChanged = await hasAnyDeltaBetweenLocalAndRemote(); - return (!isNew && !_.isEqual(newConfig, oldConfig)) || isLocalAndRemoteChanged; + const isLocalAndRemoteLocalesChanged = await hasAnyDeltaBetweenLocalAndRemoteLocales(); + console.log('Locales changed: ', isLocalAndRemoteLocalesChanged); + const themeConfigChanged = (!isNew && !_.isEqual(newConfig, oldConfig)); + console.log('Theme config changed: ', themeConfigChanged); + return themeConfigChanged || isLocalAndRemoteLocalesChanged; } public static pullThemeConfig = async () => { From 08c590eb3f4f95f0587534a3e2acde4c76e856a5 Mon Sep 17 00:00:00 2001 From: Vivek Date: Fri, 28 Feb 2025 15:55:05 +0530 Subject: [PATCH 14/16] static-resource uri modified --- src/lib/api/services/url.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/api/services/url.ts b/src/lib/api/services/url.ts index a2386506..ab8430d4 100644 --- a/src/lib/api/services/url.ts +++ b/src/lib/api/services/url.ts @@ -241,9 +241,13 @@ export const URLS = { company_id: number, theme_id: string, ) => { + console.log(urlJoin( + LOCALES_URL(), + `organization/${getOrganizationId()}/company/${company_id}/application/${application_id}/translate-ui-labels?theme_id=${theme_id}&page_size=500`, + )) return urlJoin( LOCALES_URL(), - `organization/${getOrganizationId()}/company/${company_id}/application/${application_id}/static-resources?theme_id=${theme_id}&page_size=500`, + `organization/${getOrganizationId()}/company/${company_id}/application/${application_id}/translate-ui-labels?theme_id=${theme_id}&page_size=500`, ); }, CREATE_LOCALE: ( @@ -252,7 +256,7 @@ export const URLS = { ) => { return urlJoin( LOCALES_URL(), - `organization/${getOrganizationId()}/company/${company_id}/application/${application_id}/static-resources`, + `organization/${getOrganizationId()}/company/${company_id}/application/${application_id}/translate-ui-labels`, ); }, UPDATE_LOCALE: ( @@ -262,7 +266,7 @@ export const URLS = { ) => { return urlJoin( LOCALES_URL(), - `organization/${getOrganizationId()}/company/${company_id}/application/${application_id}/static-resources/${resource_id}`, + `organization/${getOrganizationId()}/company/${company_id}/application/${application_id}/translate-ui-labels/${resource_id}`, ); } }; From c16cabf0d45c423c4a21e8d0afea6d71e96f6ea1 Mon Sep 17 00:00:00 2001 From: Vivek Date: Mon, 3 Mar 2025 12:44:01 +0530 Subject: [PATCH 15/16] local schema sync pull code added --- src/helper/locales.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/helper/locales.ts b/src/helper/locales.ts index dc7e410f..d9a87317 100644 --- a/src/helper/locales.ts +++ b/src/helper/locales.ts @@ -24,6 +24,7 @@ interface ApiConfig { interface LocaleResource { locale: string; resource: Record; + type: string; } export const hasAnyDeltaBetweenLocalAndRemoteLocales = async (): Promise => { @@ -149,8 +150,10 @@ export const syncLocales = async (syncMode: SyncMode): Promise => { } for (const file of localFiles) { - const locale: string = path.basename(file, '.json'); + let locale: string = path.basename(file, '.json'); console.log(`Processing local file: ${locale}`); + const localeType = locale.includes('schema') ? 'locale_schema' : 'locale'; + locale = locale.includes('schema') ? locale.replace('.schema', '') : locale; let localData: Record; try { localData = JSON.parse(await fs.readFile(path.join(localesFolder, file), 'utf-8')); @@ -158,14 +161,13 @@ export const syncLocales = async (syncMode: SyncMode): Promise => { console.error(`Error reading file ${file}: ${(err as Error).message}`); continue; } - - const matchingItem = data.items.find((item: LocaleResource) => item.locale === locale); + const matchingItem = data.items.find((item: LocaleResource) => item.locale === locale && item.type === localeType); if (!matchingItem) { console.log(`No matching item found for locale: ${locale}`); - unmatchedLocales.push({ locale, resource: localData }); + unmatchedLocales.push({ locale, resource: localData, type: localeType }); if (syncMode === SyncMode.PUSH) { console.log(`Creating new resource in API for locale: ${locale}`); - await createLocaleInAPI(localData, locale, 'locale'); + await createLocaleInAPI(localData, locale, localeType); } } else { if (syncMode === SyncMode.PUSH) { @@ -193,9 +195,8 @@ export const syncLocales = async (syncMode: SyncMode): Promise => { if (syncMode === SyncMode.PULL) { for (const item of data.items) { const locale: string = item.locale; - const localeFile: string = path.join(localesFolder, `${locale}.json`); + const localeFile = path.join(localesFolder, `${item.locale}${item.type === 'locale_schema' ? '.schema' : ''}.json`); const localeData: Record = item.resource; - if (!localeData) { console.log(`Skipping empty resource for locale: ${locale}`); continue; @@ -235,7 +236,7 @@ export const syncLocales = async (syncMode: SyncMode): Promise => { } }; -const createLocaleInAPI = async (data: Record, locale: string, type: string): Promise => { +const createLocaleInAPI = async (data: Record, locale: string, localeType: string): Promise => { try { console.log(`Creating resource in API for locale: ${locale}`); const activeContext = getActiveContext(); @@ -243,7 +244,7 @@ const createLocaleInAPI = async (data: Record, locale: string, type theme_id: activeContext.theme_id, locale: locale, resource: data, - type: type, + type: localeType, template: false, }) console.log('Locale created in API:', response.data); @@ -273,4 +274,4 @@ const updateLocaleFile = async (filePath: string, data: Record, id: } catch (err) { console.error(`Error writing to file ${filePath}: ${(err as Error).message}`); } -}; +}; \ No newline at end of file From 54525842c96f9c3124ef3345718e11fb1d7c4bc9 Mon Sep 17 00:00:00 2001 From: Vivek Date: Mon, 3 Mar 2025 15:35:54 +0530 Subject: [PATCH 16/16] hasAnyDeltaBetweenLocalAndRemoteLocales function modified --- src/helper/locales.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/helper/locales.ts b/src/helper/locales.ts index d9a87317..b27fe106 100644 --- a/src/helper/locales.ts +++ b/src/helper/locales.ts @@ -59,11 +59,12 @@ export const hasAnyDeltaBetweenLocalAndRemoteLocales = async (): Promise item.locale === locale); // Find the corresponding remote item + const matchingItem = data.items.find((item: LocaleResource) => item.locale === locale && item.type === localeType); // Find the corresponding remote item if (!matchingItem) { // If no matching remote item exists console.log('No matching remote item found for locale:', locale); @@ -71,7 +72,7 @@ export const hasAnyDeltaBetweenLocalAndRemoteLocales = async (): Promise