import * as fs from 'fs'; import path from 'path'; import prettier from 'prettier'; import { fileURLToPath } from 'url'; import { createRequire } from 'module'; import { exec as execCallback } from 'child_process'; import { getSortedNavigations } from '@common-stack/client-react/lib/route/react-navigation/get-navigation-utils.js'; import { performCopyOperations } from '@common-stack/rollup-vite-utils/lib/preStartup/configLoader/configLoader.js'; import { getReplacedRouteConfig } from './getReplacedRouteConfig.js'; const require = createRequire(import.meta.url); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const configFilePath = path.join(__dirname, '../layout.json'); const appNavigationFileName = 'navigation.js'; const mainRoutesFileName = 'main_routes.json'; const modulesFileName = 'modules.ts'; const stacksDirPath = 'stack/index.js'; const drawerFilePath = 'drawer/index.js'; const hostDrawerFilePath = 'host_drawer/index.js'; const bottomFilePath = 'bottom/index.js'; const hostBottomFilePath = 'host_bottom/index.js'; const mainRoutesJsFileName = 'mainRoutes.js'; const mainAppFileName = 'index.js'; type IGenerateMobileNavigationsProps = { configFilePath: any; }; export const readJsonFile = (filePath) => { return new Promise((resolve, reject) => { fs.readFile(filePath, 'utf8', (err, data) => { if (err) { return reject(err); } try { const jsonData = JSON.parse(data); resolve(jsonData); } catch (parseErr) { reject(parseErr); } }); }); }; export const readFile = (filePath) => { return new Promise((resolve, reject) => { fs.readFile(filePath, 'utf8', (err, data) => { if (err) { return reject(err); } try { resolve(data); } catch (parseErr) { reject(parseErr); } }); }); }; export const getLayoutConfig = async () => { const layoutConfigFileData = await readJsonFile(configFilePath); return layoutConfigFileData; }; export class GenerateMobileNavigations { #configFileData: any = {}; #configFilePath: any; #appPath: string = 'app'; #isDefaultPackagePathMobileRoot: boolean; #mobileStackPath: string = 'mobile-stack-react'; //#layoutSettings: any = process.env.LAYOUT_SETTINGS ? JSON.parse(process.env.LAYOUT_SETTINGS) : null; #appDirPath: any; #modules: any; #initialRouteNameRootStack: string; #initialRouteName: string; #unauthenticatedComponentPath: string; #customTabBarPath: string; #customDrawerPath: string; #customHeaderPath: string; #i18Options: any; #iconsRepository: string; constructor({ configFilePath }: IGenerateMobileNavigationsProps) { this.#configFilePath = configFilePath; const config = this.#readConfigFile(configFilePath); this.#configFileData = config; // this.#layoutSettings = process.env.LAYOUT_SETTINGS ? JSON.parse(process.env.LAYOUT_SETTINGS) : null; this.#appPath = config?.commonPaths?.appPath ?? 'app'; this.#isDefaultPackagePathMobileRoot = config?.mobileConfig?.isDefaultPackagePathMobileRoot ?? false; this.#iconsRepository = config?.iconsRepository ?? ''; this.#mobileStackPath = config?.mobileStackPath ?? 'mobile-stack-react'; this.#appDirPath = path.join(path.dirname(configFilePath), this.#appPath); this.#modules = config?.modules ?? []; this.#initialRouteName = config?.mobileConfig?.initialRouteName ?? ''; this.#initialRouteNameRootStack = config?.mobileConfig?.initialRouteNameRootStack ?? 'MainStack'; this.#unauthenticatedComponentPath = config?.mobileConfig?.unauthenticatedComponentPath ?? ''; this.#customTabBarPath = config?.mobileConfig?.customTabBarPath ?? ''; this.#customDrawerPath = config?.mobileConfig?.customDrawerPath ?? ''; this.#customHeaderPath = config?.mobileConfig?.customHeaderPath ?? ''; this.#i18Options = config?.i18n ?? {}; } #readConfigFile(configFilePath: any) { const jsonData = fs.readFileSync(configFilePath, 'utf-8'); return JSON.parse(jsonData); } async #readJsonFile(filePath) { return readJsonFile(filePath); } async #getLayoutConfig() { return getLayoutConfig(); } async #execPromise(command) { return new Promise(function (resolve, reject) { execCallback(command, (error, stdout, stderr) => { if (error) { return resolve(false); } return resolve(true); }); }); } async #renameFile(file, new_name) { return new Promise((resolve) => { fs.access(file, fs.constants.F_OK, (err) => { if (err) { resolve(false); } return fs.rename(file, new_name, (err) => { if (err) resolve(false); resolve(true); }); }); }); } async #writeFile(file, content) { return new Promise((resolve) => { return fs.writeFile(file, content, 'utf-8', (err) => { if (err) resolve(false); resolve(true); }); }); } async #makeDir(dirName) { return new Promise((resolve) => { return fs.mkdir(dirName, { recursive: true }, (err) => { if (err) resolve(false); resolve(true); }); }); } async #getModulesRouteConfig({ modules }) { const allFilteredRoutes = []; let pkgFile; for (const pkg of modules) { if (this.#isDefaultPackagePathMobileRoot) { const pkgRootFilePath = path.join( path.join(path.dirname(this.#configFilePath), 'node_modules/lib'), pkg, ); pkgFile = path.join(pkgRootFilePath, 'routes.json'); } else { const pkgPath = require.resolve(pkg); const pkgDirPath = path.dirname(pkgPath); pkgFile = path.join(pkgDirPath, 'routes.json'); } if (fs.existsSync(pkgFile)) { const fileModuleJSON: any = await readJsonFile(pkgFile); if (fileModuleJSON) { allFilteredRoutes.push([...fileModuleJSON]); } // const fileModuleJSON = await import(pkgFile, { assert: { type: 'json' } }); // if (fileModuleJSON.default) { // allFilteredRoutes.push([...fileModuleJSON.default]); // } } } return allFilteredRoutes; } async #resolveImportPath(routeConfig, importPath) { // Remove the '$.' part from the importPath to get the path within the JSON object const path = importPath?.replace('$.', '') ?? null; // Split the path into parts for nested property access const parts = path?.split('.') ?? []; // Traverse the routeConfig object to get the actual value let value = routeConfig; for (let part of parts) { if (value && Object.prototype.hasOwnProperty.call(value, part)) { value = value[part]; } else { // Return null or handle missing path return null; } } return value; } async #generateAppRoutesJson() { const appDirPath = this.#appDirPath; const parentDirPath = path.dirname(this.#configFileData?.mobileConfig?.computeFilePath ?? this.#configFilePath); const parentDirName = path.basename(parentDirPath); const appDirName = path.basename(appDirPath); // const tsFile = 'src/compute.ts'; // const outputDir = 'src/app'; const tsFile = `${parentDirName}/compute.ts`; // const outputDir = `${parentDirName}/${appDirName}`; const outputDir = `${appDirName}`; const tscCommand = `tsc ${tsFile} --outDir ${outputDir} --target es6 --module esnext --jsx react --allowSyntheticDefaultImports true --moduleResolution node --esModuleInterop true --forceConsistentCasingInFileNames true --skipLibCheck true`; const mainRoutesJsFile = path.join(appDirPath, '/compute.js'); const mainRoutesMjsFile = path.join(appDirPath, '/compute.mjs'); const outputFile = path.join(appDirPath, `/${mainRoutesFileName}`); const allFilteredRoutes = []; try { // Try to compile TypeScript if a compute.ts file exists, but don't fail // the JSON generation if compilation fails and a compute.js is already present. await this.#execPromise(tscCommand); if (fs.existsSync(mainRoutesJsFile)) { const jsFiledata = fs.readFileSync(mainRoutesJsFile, 'utf8'); const noCommentsData = jsFiledata .replace(/\/\/.*$/gm, '') // Remove single-line comments .replace(/\/\*[\s\S]*?\*\//g, ''); // Remove multi-line comments const noWhitespaceJsData = noCommentsData.replace(/\s+/g, ''); if (noWhitespaceJsData?.length == 0) { const outPutDirName = path.dirname(outputFile); try { await this.#makeDir(outPutDirName); await this.#writeFile(outputFile, JSON.stringify(allFilteredRoutes)); } catch (error) { console.log('Error directory/file create', error); } } else { const newFilePath = mainRoutesJsFile.replace('.js', '.mjs'); const renameFileResponse = await this.#renameFile(mainRoutesJsFile, newFilePath); if (renameFileResponse && fs.existsSync(mainRoutesMjsFile)) { // Dynamically import the JS file assuming it exports filteredRoutes const module = await import(mainRoutesMjsFile); // file is already absolute if (module.filteredRoutes) { const newRoutes = module.filteredRoutes.map((filteredRoute) => { // filteredRoute is of the form { [path]: routeConfig } const routConfig: any = Object.values(filteredRoute)[0]; const importMatch = routConfig.component ?.toString() ?.match(/import\(['"](.*)['"]\)/); if (importMatch && importMatch[1]) { const importPath = importMatch[1]; // e.g. './pages/Home' // Make the component path importable from files under `app/` // (like `stack/index.js`), which live one level deeper. // Example: '.././pages/Home.js' routConfig.componentPath = `.${importPath}.js`; } return { [routConfig.path]: routConfig }; }) ?? []; allFilteredRoutes.push(...newRoutes); try { const writeFileResponse = await this.#writeFile( outputFile, JSON.stringify(allFilteredRoutes, null, 2), ); if (writeFileResponse) fs.unlinkSync(mainRoutesMjsFile); } catch (error) { console.log('Error creating main routes file', error); } } } } } else { const outPutDirName = path.dirname(outputFile); try { await this.#makeDir(outPutDirName); await this.#writeFile(outputFile, JSON.stringify(allFilteredRoutes)); } catch (error) { console.log('Error creating main routes file', error); } } // return true; } catch (error) { console.error(`exec error: ${error.message}`); const outPutDirName = path.dirname(outputFile); try { await this.#makeDir(outPutDirName); await this.#writeFile(outputFile, JSON.stringify(allFilteredRoutes)); } catch (error) { console.log('Error creating main routes file', error); } } } async #generateImportStatements({ modules, initialRouteName }) { try { const layoutSettings = process.env.LAYOUT_SETTINGS ? JSON.parse(process.env.LAYOUT_SETTINGS) : null; const layoutConfigFileData = await this.#getLayoutConfig(); const hostRouteConfig = layoutConfigFileData['host-bottom']; const hostRouteKey = Object.keys(hostRouteConfig)[1]; const hostLayout = hostRouteConfig[hostRouteKey]; let importStatements = ''; let moduleNames = ''; let moduleRouteConfig = ''; let moduleNumber = 0; importStatements += `import React,{useState} from 'react';\n`; importStatements += `import {Feature} from '@common-stack/client-react/lib/connector/connector.native.js';\n`; importStatements += `import { useSelector, useDispatch } from 'react-redux';\n`; importStatements += `import { CHANGE_SETTINGS_ACTION } from '@admin-layout/client';\n`; importStatements += `import {layoutRouteConfig,getReplacedRouteConfig } from '@admin-layout/gluestack-ui-mobile';\n`; importStatements += `import mainRouteConfig from './main_routes.json';\n`; modules?.forEach((packageName) => { moduleNumber++; importStatements += `import module${moduleNumber} from '${packageName}';\n`; // moduleNames += `${moduleName}, `; moduleNames += `module${moduleNumber}, `; moduleRouteConfig += `...[module${moduleNumber}?.routeConfig], `; }); //const layoutSettings = process.env.LAYOUT_SETTINGS ? JSON.parse(process.env.LAYOUT_SETTINGS) : null; let classStructure = ` const features = new Feature( ${moduleNames.trim()} ); const mainAppRoutes = mainRouteConfig || []; const appRoutes = [...[mainAppRoutes],${moduleRouteConfig}]?.filter((rc)=>rc.length)??[]; const featureRouteConfig = appRoutes?.flat(1)??features?.routeConfig; features.routeConfig = featureRouteConfig; export function useGetModules(){ const dispatch = useDispatch(); const defaultSettings = useSelector((state:any) => state.settings); const initialRouteName = '${initialRouteName}'; const layoutSettings = ${JSON.stringify({ ...layoutSettings, hostLayout: hostLayout.key })} const [appRouteConfig, setAppRouteConfig]:any = useState(null); React.useEffect(() => { setDefalutSettings(); }, []); const setDefalutSettings = React.useCallback(()=>{ const config: any = { ...defaultSettings, ...layoutSettings, }; dispatch({ type: CHANGE_SETTINGS_ACTION, payload: config, }); },[]); React.useEffect(() => { if (defaultSettings) { const settingObj: any = { ...defaultSettings }; const layoutType: any = settingObj.layout; const {replacedConfiguredRouteConfig} = getReplacedRouteConfig({ layoutType: layoutType, routeConfig: appRoutes, layoutConfigData: layoutRouteConfig, initialRouteName, }); if(replacedConfiguredRouteConfig){ const moduleRouteConfigObject = Object.assign({}, ...replacedConfiguredRouteConfig?.flat(1)??[]); const replacedRouteConfig = Object.fromEntries(Object.entries(moduleRouteConfigObject)); const appReplacedRouteConfig = replacedRouteConfig ? Object.keys(replacedRouteConfig)?.map((k)=>({[k]:replacedRouteConfig[k]})) : []; if (appReplacedRouteConfig) { const hostRouteConfig = appReplacedRouteConfig?.map((obj)=> Object.fromEntries(Object.entries(obj)?.filter(([key,val])=>key === '/' || key.startsWith('//'+layoutSettings.hostLayout))))?.filter(value => Object.keys(value).length !== 0)??[]; const layoutRouteConfig = appReplacedRouteConfig?.map((obj)=> Object.fromEntries(Object.entries(obj)?.filter(([key,val])=>key === '/' || !key.startsWith('//'+layoutSettings.hostLayout))))?.filter(value => Object.keys(value).length !== 0)??[]; const featureRouteConfig = defaultSettings?.layout == 'host-bottom' ? hostRouteConfig:layoutRouteConfig; // features.routeConfig = featureRouteConfig; setAppRouteConfig(featureRouteConfig); } } } }, [defaultSettings]); return {appRouteConfig}; } export default features; `.replace(/,(\s*)$/, ''); // Removes trailing comma // Use Prettier to format the code classStructure = await prettier.format(classStructure, { parser: 'babel' }); const appFeatures = importStatements + '\n' + classStructure; return { appFeatures }; } catch (err) { console.error('Error:', err); return false; } } async #generateModulesTsFile({ appDirPath, modules, initialRouteName }) { const moduleTsFile = path.join(appDirPath, `/${modulesFileName}`); const imports: any = await this.#generateImportStatements({ modules, initialRouteName }); const { appFeatures } = imports; if (appFeatures) { const writeFileResponse = await this.#writeFile(moduleTsFile, appFeatures); if (writeFileResponse) return true; else return false; } return false; } async #generateMainRoutesFile({ initialRouteName, i18Options }) { try { const layoutSettings = process.env.LAYOUT_SETTINGS ? JSON.parse(process.env.LAYOUT_SETTINGS) : null; const layoutConfigFileData = await this.#getLayoutConfig(); const hostRouteConfig = layoutConfigFileData['host-bottom']; const hostRouteKey = Object.keys(hostRouteConfig)[1]; const hostLayout = hostRouteConfig[hostRouteKey]; let importStatements = ''; importStatements += `import React from 'react';\n`; importStatements += `import { InversifyProvider, PluginArea } from '@common-stack/client-react';\n`; importStatements += `import { useSelector, useDispatch } from 'react-redux';\n`; importStatements += `import { CHANGE_SETTINGS_ACTION } from '@admin-layout/client';\n`; importStatements += `import {layoutRouteConfig,getReplacedRouteConfig,ErrorBoundary,NavigationContainerComponent,ApplicationErrorHandler,Box,Spinner,} from '@admin-layout/gluestack-ui-mobile';\n`; importStatements += `import mainRouteConfig from './main_routes.json';\n`; importStatements += `import features from './mobile-stack-react/modules.js';\n`; importStatements += `import AppNavigations from './navigation';\n`; importStatements += `import {loadContext} from './mobile-stack-react/load-context.mobile.js';\n`; let classStructure = ` const mainAppRoutes = mainRouteConfig || []; const appRoutes = [...[mainAppRoutes],features?.routeConfig]; const featureRouteConfig = appRoutes?.flat(1)??features?.routeConfig; features.routeConfig = featureRouteConfig; let appConfiguredRoutes = features?.getConfiguredRoutes2("/"); const DefaultProvider = ({children}) => <>{children} export function MainRoute({ container,externalProvider: ExternalProvider = DefaultProvider,externalProviderProps={},linking = {} }){ const dispatch = useDispatch(); const defaultSettings = useSelector((state) => state.settings); const initialRouteName = '${initialRouteName}'; const layoutSettings = ${JSON.stringify({ ...layoutSettings, hostLayout: hostLayout.key })} const [mainFeatures, setMainFeatures] = React.useState(null); React.useEffect(() => { setDefalutSettings(); }, []); const setDefalutSettings = React.useCallback(()=>{ const config = { ...defaultSettings, ...layoutSettings, }; dispatch({ type: CHANGE_SETTINGS_ACTION, payload: config, }); },[]); React.useEffect(() => { if (defaultSettings) { const settingObj = { ...defaultSettings }; const layoutType = settingObj.layout; const {replacedConfiguredRouteConfig} = getReplacedRouteConfig({ layoutType: layoutType, routeConfig: appRoutes, layoutConfigData: layoutRouteConfig, initialRouteName, }); if(replacedConfiguredRouteConfig){ const moduleRouteConfigObject = Object.assign({}, ...replacedConfiguredRouteConfig?.flat(1)??[]); const replacedRouteConfig = Object.fromEntries(Object.entries(moduleRouteConfigObject)); const appReplacedRouteConfig = replacedRouteConfig ? Object.keys(replacedRouteConfig)?.map((k)=>({[k]:replacedRouteConfig[k]})) : []; if (appReplacedRouteConfig) { const hostRouteConfig = appReplacedRouteConfig.filter(item => { return Object.keys(item).some(key => key === "/" || (key.startsWith("//" + layoutSettings.hostLayout) || key.startsWith("//:orgName/" + layoutSettings.hostLayout)) ); })?.filter((value) => Object.keys(value).length !== 0) ?? []; const layoutRouteConfig = appReplacedRouteConfig.filter(item => { return Object.keys(item).some(key => key === "/" || (!key.startsWith("//" + layoutSettings.hostLayout) && !key.startsWith("//:orgName/" + layoutSettings.hostLayout)) ); })?.filter((value) => Object.keys(value).length !== 0) ?? []; // const hostRouteConfig = appReplacedRouteConfig?.map((obj)=> Object.fromEntries(Object.entries(obj)?.filter(([key,val])=>key === '/' || key.startsWith('//'+layoutSettings.hostLayout))))?.filter(value => Object.keys(value).length !== 0)??[]; // const layoutRouteConfig = appReplacedRouteConfig?.map((obj)=> Object.fromEntries(Object.entries(obj)?.filter(([key,val])=>key === '/' || !key.startsWith('//'+layoutSettings.hostLayout))))?.filter(value => Object.keys(value).length !== 0)??[]; const featureRouteConfig = defaultSettings?.layout == 'host-bottom' ? hostRouteConfig:layoutRouteConfig; features.routeConfig = featureRouteConfig; setMainFeatures(features); } } } }, [defaultSettings]); const loadingComponent = () => {}; if (!mainFeatures || mainFeatures?.routeConfig?.length == 0) return loadingComponent(); const plugins = mainFeatures?.getComponentFillPlugins(); const configuredRoutes = mainFeatures?.getConfiguredRoutes2('/'); appConfiguredRoutes = configuredRoutes; return ( {mainFeatures?.getWrappedRoot( , )} ); } export { loadContext,appConfiguredRoutes }; export default features; `.replace(/,(\s*)$/, ''); // Removes trailing comma // Use Prettier to format the code classStructure = await prettier.format(classStructure, { parser: 'babel' }); const appFeatures = importStatements + '\n' + classStructure; return { appFeatures }; } catch (err) { console.error('Error:', err); } } async #generateMainRoutes({ appDirPath, i18Options, initialRouteName }) { const mainRoutesFile = path.join(appDirPath, `/${mainRoutesJsFileName}`); const imports: any = await this.#generateMainRoutesFile({ initialRouteName, i18Options }); const { appFeatures } = imports; try { await this.#writeFile(mainRoutesFile, appFeatures); } catch (err) { console.error('Error generating main routes:', err); } } async #generateAppFile({ initialRouteName, i18Options }) { try { let importStatements = ''; importStatements += `import 'reflect-metadata';\n`; importStatements += `import React from 'react';\n`; importStatements += `import { SlotFillProvider } from '@common-stack/components-pro';\n`; importStatements += `import { ApolloProvider } from '@apollo/client';\n`; importStatements += `import { Provider } from 'react-redux';\n`; importStatements += `import { PersistGate } from 'redux-persist/integration/react';\n`; importStatements += `import { SafeAreaProvider } from 'react-native-safe-area-context';\n`; importStatements += `import {GluestackUIProvider,config,i18next,reactI18Next} from '@admin-layout/gluestack-ui-mobile';\n`; importStatements += `import { enableScreens } from 'react-native-screens';\n`; importStatements += `import {MainRoute,loadContext,appConfiguredRoutes} from './mainRoutes.js'\n`; let classStructure = ` enableScreens(true); const { I18nextProvider } = reactI18Next; i18next.options ={...i18next?.options??{},...${JSON.stringify(i18Options)}}; const DefaultProvider = ({children}) => <>{children} function App({externalProvider = DefaultProvider,externalProviderProps={},linking = {}}){ const {store,persistor,container,apolloClient: client} = loadContext(); return ( ); } export const configuredRoutes = appConfiguredRoutes; export default App; `.replace(/,(\s*)$/, ''); // Removes trailing comma // Use Prettier to format the code classStructure = await prettier.format(classStructure, { parser: 'babel' }); const appFeatures = importStatements + '\n' + classStructure; return { appFeatures }; } catch (err) { console.error('Error:', err); } } async #generateApp({ appDirPath, i18Options, initialRouteName }) { const mainRoutesFile = path.join(appDirPath, `/${mainAppFileName}`); const imports: any = await this.#generateAppFile({ initialRouteName, i18Options }); const { appFeatures } = imports; try { await this.#writeFile(mainRoutesFile, appFeatures); } catch (err) { console.error('Error generating app:', err); } } async #generateStackNavigations({ appDirPath, modules, initialRouteName, unauthenticatedComponentPath }) { const mainRoutes = path.join(appDirPath, `/${mainRoutesFileName}`); const stackDirPath = path.join(appDirPath, `/${stacksDirPath}`); const layoutSettings = process.env.LAYOUT_SETTINGS ? JSON.parse(process.env.LAYOUT_SETTINGS) : null; const layoutConfigFileData = await this.#getLayoutConfig(); const layoutType = layoutSettings?.layout || 'bottom'; const layoutRouteConfig = layoutConfigFileData[layoutType]; const layoutRouteKey = Object.keys(layoutRouteConfig)[1]; const appLayout = layoutRouteConfig[layoutRouteKey]; const modulesRouteConfig = await this.#getModulesRouteConfig({ modules: modules }); const mainRouteConfig = await this.#readJsonFile(mainRoutes); const allRoutes = [...[mainRouteConfig ?? []], ...(modulesRouteConfig ?? [])]; const { replacedNavigationRouteConfig: routeConfig } = getReplacedRouteConfig({ layoutType: layoutType, routeConfig: allRoutes, layoutConfigData: layoutConfigFileData, initialRouteName: initialRouteName, }); const keyToReplace = appLayout.key || 'bottom_tab'; const stackRouteConfig = routeConfig ?.map( (rArray) => rArray ?.map((r) => { const route = r[Object.keys(r)[0]]; const path = route.path; const isExcluded = path === '/' || path.startsWith(`/${keyToReplace}`) || path.startsWith(`/:orgName/${keyToReplace}`) || path.startsWith('/host_tab') || path.startsWith('/:orgName/host_tab'); return isExcluded ? false : route; }) ?.filter((route) => route !== false) ?? [], // Filter out false values ) ?.filter((subArray) => subArray.length > 0) ?? []; let moduleNumber = 0; let importStatements = ''; let moduleContent = ''; let moduleRender = ''; let stackNavigator = ''; let customHeaderNames = ''; let customHeaderPaths = ''; let customUnauthenticatedComponentPaths = ''; let customUnauthenticatedComponentNames = ''; const regex = /\.(tsx|ts|jsx|js)$/i; importStatements += `import * as React from 'react';\n`; importStatements += `import AuthWrapper from '@admin-layout/gluestack-ui-mobile/lib/components/AuthWrapper.js';\n`; if (unauthenticatedComponentPath) importStatements += `import UnauthenticatedComponent from '${unauthenticatedComponentPath}';\n`; for (const pkg of stackRouteConfig) { moduleContent += ``; for (const pkgRouteConfig of pkg) { moduleNumber++; let customHeaderName = null; const customHeaderComponentPath = await this.#resolveImportPath( pkgRouteConfig, pkgRouteConfig?.customHeader?.component, ); if ( pkgRouteConfig?.customHeader && Object.keys(pkgRouteConfig?.customHeader)?.length && customHeaderComponentPath ) { customHeaderPaths += `${customHeaderComponentPath},`; customHeaderName = `${pkgRouteConfig?.customHeader?.name ?? `CustomHeader${moduleNumber}`}` || `CustomHeader${moduleNumber}`; customHeaderNames += `${customHeaderName},`; } let customUnauthenticatedComponentName = null; const customUnauthenticatedComponentPath = await this.#resolveImportPath( pkgRouteConfig, pkgRouteConfig?.unauthenticatedComponent, ); if (pkgRouteConfig?.unauthenticatedComponent && customUnauthenticatedComponentPath) { customUnauthenticatedComponentPaths += `${customUnauthenticatedComponentPath},`; customUnauthenticatedComponentName = `UnauthenticatedComponent${moduleNumber}`; customUnauthenticatedComponentNames += `${customUnauthenticatedComponentName},`; } importStatements += `import Component${moduleNumber} from '${pkgRouteConfig.componentPath}';\n`; const options = JSON.stringify({ ...(pkgRouteConfig?.props?.options || {}) }); moduleContent += ` <${customHeaderName} {...props} {...${JSON.stringify( pkgRouteConfig?.customHeader?.props ?? '', )}} />` : '' }}}} >{(props) => } ${ pkgRouteConfig?.unauthenticatedComponent && customUnauthenticatedComponentPath ? `unauthenticatedComponent={<${customUnauthenticatedComponentName}/>}` : unauthenticatedComponentPath ? 'unauthenticatedComponent={}' : '' } ${ pkgRouteConfig?.withLifeCycle ? `withLifeCycle={${JSON.stringify(pkgRouteConfig?.withLifeCycle)}}` : '' } ${ pkgRouteConfig?.withInteraction ? `withInteraction={${JSON.stringify(pkgRouteConfig?.withInteraction)}}` : '' } ${ pkgRouteConfig?.withLifeCycleInteraction ? `withLifeCycleInteraction={${JSON.stringify( pkgRouteConfig?.withLifeCycleInteraction, )}}` : '' } />}`; } if (customHeaderPaths && customHeaderPaths?.length) { const uniqueHeaderNames = [...new Set(customHeaderNames.split(','))]?.filter((str) => str?.length); const uniqueHeaderPaths = [...new Set(customHeaderPaths.split(','))]?.filter((str) => str?.length); uniqueHeaderPaths?.forEach(function (hPath, i) { const impHeaderName = uniqueHeaderNames?.[i] ?? `customHeader${i}`; importStatements += `import ${impHeaderName} from '${hPath}';\n`; }); } if (customUnauthenticatedComponentPaths && customUnauthenticatedComponentPaths?.length) { const uniqueUnauthenticatedComponentNames = [ ...new Set(customUnauthenticatedComponentNames.split(',')), ]?.filter((str) => str?.length); const uniqueUnauthenticatedComponentPaths = [ ...new Set(customUnauthenticatedComponentPaths.split(',')), ]?.filter((str) => str?.length); uniqueUnauthenticatedComponentPaths?.forEach(function (unCompPath, i) { const impUnauthenticatedName = uniqueUnauthenticatedComponentNames?.[i] ?? `UnauthenticatedComponent${i}`; importStatements += `import ${impUnauthenticatedName} from '${unCompPath}';\n`; }); } moduleContent += ``; moduleRender = `export default ({Stack,...rest}) => { return (<>${moduleContent})}`; } stackNavigator = importStatements + '\n' + moduleRender; let stackNavigation = stackNavigator; stackNavigation = await prettier.format(stackNavigation, { parser: 'babel' }); const stackDirName = path.dirname(stackDirPath); try { const isDirCreated = await this.#makeDir(stackDirName); if (isDirCreated) { await this.#writeFile(stackDirPath, stackNavigation); } } catch (error) { console.log('Error generating stack navigation', error); } } async #generateDrawerNavigationsFile({ drawerConfig, unauthenticatedComponentPath, drawerDirPath }) { let moduleNumber = 0; let importStatements = ''; let moduleContent = ''; let moduleRender = ''; let moduleNavigation = ''; let icons = ''; let customHeaderNames = ''; let customHeaderPaths = ''; let customUnauthenticatedComponentPaths = ''; let customUnauthenticatedComponentNames = ''; const regex = /\.(tsx|ts|jsx|js)$/i; importStatements += `import * as React from 'react';\n`; importStatements += `import AuthWrapper from '@admin-layout/gluestack-ui-mobile/lib/components/AuthWrapper.js';\n`; importStatements += `import DynamicIcons from '@app/selectiveIcons';\n`; if (unauthenticatedComponentPath) importStatements += `import UnauthenticatedComponent from '${unauthenticatedComponentPath}';\n`; for (const pkgRouteConfig of drawerConfig) { moduleNumber++; if (pkgRouteConfig?.icon && Object.keys(pkgRouteConfig.icon)?.length && pkgRouteConfig?.icon?.name) { icons += `${pkgRouteConfig?.icon?.name},`; } let customHeaderName = null; const customHeaderComponentPath = await this.#resolveImportPath( pkgRouteConfig, pkgRouteConfig?.customHeader?.component, ); if ( pkgRouteConfig?.customHeader && Object.keys(pkgRouteConfig?.customHeader)?.length && customHeaderComponentPath ) { customHeaderPaths += `${customHeaderComponentPath},`; customHeaderName = `${pkgRouteConfig?.customHeader?.name ?? `CustomHeader${moduleNumber}`}` || `CustomHeader${moduleNumber}`; customHeaderNames += `${customHeaderName},`; } let customUnauthenticatedComponentName = null; const customUnauthenticatedComponentPath = await this.#resolveImportPath( pkgRouteConfig, pkgRouteConfig?.unauthenticatedComponent, ); if (pkgRouteConfig?.unauthenticatedComponent && customUnauthenticatedComponentPath) { customUnauthenticatedComponentPaths += `${customUnauthenticatedComponentPath},`; customUnauthenticatedComponentName = `UnauthenticatedComponent${moduleNumber}`; customUnauthenticatedComponentNames += `${customUnauthenticatedComponentName},`; } const options = JSON.stringify({ ...(pkgRouteConfig?.props?.options || {}) }); importStatements += `import Component${moduleNumber} from '${pkgRouteConfig.componentPath}';\n`; moduleContent += ` { const focused = color === '${pkgRouteConfig?.props?.options?.tabBarActiveTintColor}' ? true : false; const SelectedIcon = DynamicIcons('${pkgRouteConfig?.icon?.name}'); return ()},` : '' } ${ pkgRouteConfig?.customHeader && Object.keys(pkgRouteConfig.customHeader)?.length && customHeaderComponentPath ? `header: (props) => <${customHeaderName} {...props} {...${JSON.stringify( pkgRouteConfig?.customHeader?.props ?? '', )}} />` : '' }}}} >{(props) => } ${ pkgRouteConfig?.unauthenticatedComponent && customUnauthenticatedComponentPath ? `unauthenticatedComponent={<${customUnauthenticatedComponentName}/>}` : unauthenticatedComponentPath ? 'unauthenticatedComponent={}' : '' } ${ pkgRouteConfig?.withLifeCycle ? `withLifeCycle={${JSON.stringify(pkgRouteConfig?.withLifeCycle)}}` : '' } ${ pkgRouteConfig?.withInteraction ? `withInteraction={${JSON.stringify(pkgRouteConfig?.withInteraction)}}` : '' } ${ pkgRouteConfig?.withLifeCycleInteraction ? `withLifeCycleInteraction={${JSON.stringify( pkgRouteConfig?.withLifeCycleInteraction, )}}` : '' } />}`; } // if (icons && icons?.length) { // const uniqueIcons = [...new Set(icons.split(','))].join(','); // importStatements += `import Icons from '@expo/vector-icons';\n`; // } if (customHeaderPaths && customHeaderPaths?.length) { const uniqueHeaderNames = [...new Set(customHeaderNames.split(','))]?.filter((str) => str?.length); const uniqueHeaderPaths = [...new Set(customHeaderPaths.split(','))]?.filter((str) => str?.length); uniqueHeaderPaths?.forEach(function (hPath, i) { const impHeaderName = uniqueHeaderNames?.[i] ?? `customHeader${i}`; importStatements += `import ${impHeaderName} from '${hPath}';\n`; }); } if (customUnauthenticatedComponentPaths && customUnauthenticatedComponentPaths?.length) { const uniqueUnauthenticatedComponentNames = [ ...new Set(customUnauthenticatedComponentNames.split(',')), ]?.filter((str) => str?.length); const uniqueUnauthenticatedComponentPaths = [ ...new Set(customUnauthenticatedComponentPaths.split(',')), ]?.filter((str) => str?.length); uniqueUnauthenticatedComponentPaths?.forEach(function (unCompPath, i) { const impUnauthenticatedName = uniqueUnauthenticatedComponentNames?.[i] ?? `UnauthenticatedComponent${i}`; importStatements += `import ${impUnauthenticatedName} from '${unCompPath}';\n`; }); } moduleRender = `export default ({Drawer,...rest}) => { return (<>${moduleContent})}`; moduleNavigation = importStatements + '\n' + moduleRender; const drawerNavigator = moduleNavigation; let drawerNavigation = drawerNavigator; drawerNavigation = await prettier.format(drawerNavigation, { parser: 'babel' }); const drawerDirName = path.dirname(drawerDirPath); try { const isDirCreated = await this.#makeDir(drawerDirName); if (isDirCreated) { await this.#writeFile(drawerDirPath, drawerNavigation); } } catch (error) { console.log('Error generating drawer navigation file', error); } } async #generateDrawerNavigations({ appDirPath, modules, initialRouteName, unauthenticatedComponentPath }) { const mainRoutes = path.join(appDirPath, `/${mainRoutesFileName}`); const drawerDirPath = path.join(appDirPath, `/${drawerFilePath}`); const hostDirPath = path.join(appDirPath, `/${hostDrawerFilePath}`); const layoutSettings = process.env.LAYOUT_SETTINGS ? JSON.parse(process.env.LAYOUT_SETTINGS) : null; const layoutConfigFileData = await this.#getLayoutConfig(); const layoutType = 'side'; const layoutRouteConfig = layoutConfigFileData[layoutType]; const layoutRouteKey = Object.keys(layoutRouteConfig)[1]; const appLayout = layoutRouteConfig[layoutRouteKey]; const hostRouteConfig = layoutConfigFileData['host-bottom']; const hostRouteKey = Object.keys(hostRouteConfig)[1]; const hostLayout = hostRouteConfig[hostRouteKey]; const modulesRouteConfig = await this.#getModulesRouteConfig({ modules: modules }); const mainRouteConfig = await this.#readJsonFile(mainRoutes); const allRoutes = [...[mainRouteConfig ?? []], ...(modulesRouteConfig ?? [])]; const { replacedNavigationRouteConfig: routeConfig } = getReplacedRouteConfig({ layoutType: layoutType, routeConfig: allRoutes, layoutConfigData: layoutConfigFileData, initialRouteName: initialRouteName, }); const keyToReplace = appLayout.key || 'bottom_tab'; const keyToReplaceHost = hostLayout.key || 'host_tab'; const moduleRouteConfigObject = Object.assign({}, ...(routeConfig?.flat(1) ?? [])); const configuredRoutes = await getSortedNavigations('/', moduleRouteConfigObject); const layoutBottomTabRouteConfig = configuredRoutes?.[0]?.children?.filter((r) => r?.key == keyToReplace); const hostBottomTabRouteConfig = configuredRoutes?.[0]?.children?.filter((r) => r?.key == keyToReplaceHost); const drawerConfig = layoutBottomTabRouteConfig?.[0]?.children?.sort((a, b) => { if (a?.props?.options?.priority === undefined) return 1; // Push items with missing 'id' to the end if (b?.props?.options?.priority === undefined) return -1; return a?.props?.options?.priority - b?.props?.options?.priority; }) ?? []; const hostDrawerConfig = hostBottomTabRouteConfig?.[0]?.children?.sort((a, b) => { if (a?.props?.options?.priority === undefined) return 1; // Push items with missing 'id' to the end if (b?.props?.options?.priority === undefined) return -1; return a?.props?.options?.priority - b?.props?.options?.priority; }) ?? []; try { if (layoutSettings?.layout == 'side') { if (drawerConfig) { await this.#generateDrawerNavigationsFile({ drawerConfig: drawerConfig, unauthenticatedComponentPath, drawerDirPath: drawerDirPath, }); await this.#generateDrawerNavigationsFile({ drawerConfig: hostDrawerConfig, unauthenticatedComponentPath, drawerDirPath: hostDirPath, }); } else { if (hostDrawerConfig) { await this.#generateDrawerNavigationsFile({ drawerConfig: hostDrawerConfig, unauthenticatedComponentPath, drawerDirPath: hostDirPath, }); } } } } catch (error) { console.log('Error generating drawer navigation', error); } } async #generateBottomTabNavigationsFile({ bottomTabConfig, unauthenticatedComponentPath, bottomDirPath, mixLayout = null, }) { let moduleNumber = 0; let importStatements = ''; let moduleContent = ''; let moduleRender = ''; let moduleNavigation = ''; let icons = ''; let customHeaderNames = ''; let customHeaderPaths = ''; let customUnauthenticatedComponentPaths = ''; let customUnauthenticatedComponentNames = ''; const regex = /\.(tsx|ts|jsx|js)$/i; importStatements += `import * as React from 'react';\n`; importStatements += `import AuthWrapper from '@admin-layout/gluestack-ui-mobile/lib/components/AuthWrapper.js';\n`; importStatements += `import DynamicIcons from '@app/selectiveIcons';\n`; if (unauthenticatedComponentPath) importStatements += `import UnauthenticatedComponent from '${unauthenticatedComponentPath}';\n`; for (const pkgRouteConfig of bottomTabConfig) { moduleNumber++; if (pkgRouteConfig?.icon && Object.keys(pkgRouteConfig.icon)?.length && pkgRouteConfig?.icon?.name) { icons += `${pkgRouteConfig?.icon?.name},`; } let customHeaderName = null; const customHeaderComponentPath = await this.#resolveImportPath( pkgRouteConfig, pkgRouteConfig?.customHeader?.component, ); if ( pkgRouteConfig?.customHeader && Object.keys(pkgRouteConfig?.customHeader)?.length && customHeaderComponentPath ) { customHeaderPaths += `${customHeaderComponentPath},`; customHeaderName = `${pkgRouteConfig?.customHeader?.name ?? `CustomHeader${moduleNumber}`}` || `CustomHeader${moduleNumber}`; customHeaderNames += `${customHeaderName},`; } let customUnauthenticatedComponentName = null; const customUnauthenticatedComponentPath = await this.#resolveImportPath( pkgRouteConfig, pkgRouteConfig?.unauthenticatedComponent, ); if (pkgRouteConfig?.unauthenticatedComponent && customUnauthenticatedComponentPath) { customUnauthenticatedComponentPaths += `${customUnauthenticatedComponentPath},`; customUnauthenticatedComponentName = `UnauthenticatedComponent${moduleNumber}`; customUnauthenticatedComponentNames += `${customUnauthenticatedComponentName},`; } const options = JSON.stringify( pkgRouteConfig?.props?.options ? { ...pkgRouteConfig.props.options, headerShown: mixLayout ? false : pkgRouteConfig.props.options.headerShown, } : { headerShown: mixLayout ? false : true }, ); importStatements += `import Component${moduleNumber} from '${pkgRouteConfig.componentPath}';\n`; moduleContent += ` { const focused = color === '${pkgRouteConfig?.props?.options?.tabBarActiveTintColor}' ? true : false; const SelectedIcon = DynamicIcons('${pkgRouteConfig?.icon?.name}'); return ()}` : '' } ${ pkgRouteConfig?.customHeader && Object.keys(pkgRouteConfig.customHeader)?.length && customHeaderComponentPath ? `,header: (props) => <${customHeaderName} {...props} {...${JSON.stringify( pkgRouteConfig?.customHeader?.props ?? '', )}} />` : '' } }}} >{(props) => } ${ pkgRouteConfig?.unauthenticatedComponent && customUnauthenticatedComponentPath ? `unauthenticatedComponent={<${customUnauthenticatedComponentName}/>}` : unauthenticatedComponentPath ? 'unauthenticatedComponent={}' : '' } ${ pkgRouteConfig?.withLifeCycle ? `withLifeCycle={${JSON.stringify(pkgRouteConfig?.withLifeCycle)}}` : '' } ${ pkgRouteConfig?.withInteraction ? `withInteraction={${JSON.stringify(pkgRouteConfig?.withInteraction)}}` : '' } ${ pkgRouteConfig?.withLifeCycleInteraction ? `withLifeCycleInteraction={${JSON.stringify( pkgRouteConfig?.withLifeCycleInteraction, )}}` : '' } />} `; } // if (icons && icons?.length) { // const uniqueIcons = [...new Set(icons.split(','))].join(','); // importStatements += `import { ${uniqueIcons} } from '@expo/vector-icons';\n`; // } if (customHeaderPaths && customHeaderPaths?.length) { const uniqueHeaderNames = [...new Set(customHeaderNames.split(','))]?.filter((str) => str?.length); const uniqueHeaderPaths = [...new Set(customHeaderPaths.split(','))]?.filter((str) => str?.length); uniqueHeaderPaths?.forEach(function (hPath, i) { const impHeaderName = uniqueHeaderNames?.[i] ?? `customHeader${i}`; importStatements += `import ${impHeaderName} from '${hPath}';\n`; }); } if (customUnauthenticatedComponentPaths && customUnauthenticatedComponentPaths?.length) { const uniqueUnauthenticatedComponentNames = [ ...new Set(customUnauthenticatedComponentNames.split(',')), ]?.filter((str) => str?.length); const uniqueUnauthenticatedComponentPaths = [ ...new Set(customUnauthenticatedComponentPaths.split(',')), ]?.filter((str) => str?.length); uniqueUnauthenticatedComponentPaths?.forEach(function (unCompPath, i) { const impUnauthenticatedName = uniqueUnauthenticatedComponentNames?.[i] ?? `UnauthenticatedComponent${i}`; importStatements += `import ${impUnauthenticatedName} from '${unCompPath}';\n`; }); } moduleRender = `export default ({Tab,...rest}) => { return (<>${moduleContent})}`; moduleNavigation = importStatements + '\n' + moduleRender; const bottomTabNavigator = moduleNavigation; let bottomTabNavigation = bottomTabNavigator; bottomTabNavigation = await prettier.format(bottomTabNavigation, { parser: 'babel' }); const bottomDirName = path.dirname(bottomDirPath); try { const isDirCreated = await this.#makeDir(bottomDirName); if (isDirCreated) { await this.#writeFile(bottomDirPath, bottomTabNavigation); } } catch (error) { console.log('Error generating bottom tab navigation file', error); } } async #generateBottomTabNavigations({ appDirPath, modules, initialRouteName, unauthenticatedComponentPath }) { const mainRoutes = path.join(appDirPath, `/${mainRoutesFileName}`); const bottomDirPath = path.join(appDirPath, `/${bottomFilePath}`); const hostBottomDirPath = path.join(appDirPath, `/${hostBottomFilePath}`); const layoutSettings = process.env.LAYOUT_SETTINGS ? JSON.parse(process.env.LAYOUT_SETTINGS) : null; const layoutConfigFileData = await this.#getLayoutConfig(); const layoutType = layoutSettings?.layout ?? 'bottom'; const layoutRouteConfig = layoutConfigFileData[layoutType]; const layoutRouteKey = Object.keys(layoutRouteConfig)[1]; const appLayout = layoutRouteConfig[layoutRouteKey]; const hostRouteConfig = layoutConfigFileData['host-bottom']; const hostRouteKey = Object.keys(hostRouteConfig)[1]; const hostLayout = hostRouteConfig[hostRouteKey]; const mixLayoutRouteKey = Object.keys(layoutRouteConfig)?.[2] || null; const mixLayout = mixLayoutRouteKey ? layoutRouteConfig[mixLayoutRouteKey] : null; const modulesRouteConfig = await this.#getModulesRouteConfig({ modules: modules }); const mainRouteConfig = await this.#readJsonFile(mainRoutes); const allRoutes = [...[mainRouteConfig ?? []], ...(modulesRouteConfig ?? [])]; const { replacedNavigationRouteConfig: routeConfig } = getReplacedRouteConfig({ layoutType: layoutType, routeConfig: allRoutes, layoutConfigData: layoutConfigFileData, initialRouteName: initialRouteName, }); const keyToReplace = appLayout.key || 'bottom_tab'; const keyToReplaceHost = hostLayout.key || 'host_tab'; const moduleRouteConfigObject = Object.assign({}, ...(routeConfig?.flat(1) ?? [])); const configuredRoutes = await getSortedNavigations('/', moduleRouteConfigObject); const layoutBottomTabRouteConfig = configuredRoutes?.[0]?.children?.filter((r) => r?.key == keyToReplace); const hostBottomTabRouteConfig = configuredRoutes?.[0]?.children?.filter((r) => r?.key == keyToReplaceHost); const bottomTabConfig = layoutBottomTabRouteConfig?.[0]?.children?.sort((a, b) => { if (a?.props?.options?.priority === undefined) return 1; // Push items with missing 'id' to the end if (b?.props?.options?.priority === undefined) return -1; return a?.props?.options?.priority - b?.props?.options?.priority; }) ?? []; const hostBottomTabConfig = hostBottomTabRouteConfig?.[0]?.children?.sort((a, b) => { if (a?.props?.options?.priority === undefined) return 1; // Push items with missing 'id' to the end if (b?.props?.options?.priority === undefined) return -1; return a?.props?.options?.priority - b?.props?.options?.priority; }) ?? []; try { if (layoutType == 'bottom' || layoutType == 'mixSide') { if (bottomTabConfig) { await this.#generateBottomTabNavigationsFile({ bottomTabConfig: bottomTabConfig, unauthenticatedComponentPath, bottomDirPath: bottomDirPath, mixLayout, }); await this.#generateBottomTabNavigationsFile({ bottomTabConfig: hostBottomTabConfig, unauthenticatedComponentPath, bottomDirPath: hostBottomDirPath, mixLayout, }); } else { if (hostBottomTabConfig) { await this.#generateBottomTabNavigationsFile({ bottomTabConfig: hostBottomTabConfig, unauthenticatedComponentPath, bottomDirPath: hostBottomDirPath, mixLayout, }); } } } } catch (error) { console.log('Error generating bottom tab navigation', error); } } async #generateBottomTabDrawerNavigations({ appDirPath, modules, initialRouteName, unauthenticatedComponentPath }) { const mainRoutes = path.join(appDirPath, `/${mainRoutesFileName}`); const bottomDirPath = path.join(appDirPath, `/${bottomFilePath}`); const hostBottomDirPath = path.join(appDirPath, `/${hostBottomFilePath}`); const drawerDirPath = path.join(appDirPath, `/${drawerFilePath}`); const hostDirPath = path.join(appDirPath, `/${hostDrawerFilePath}`); const layoutSettings = process.env.LAYOUT_SETTINGS ? JSON.parse(process.env.LAYOUT_SETTINGS) : null; const layoutConfigFileData = await this.#getLayoutConfig(); const layoutType = layoutSettings?.layout ?? 'bottom'; const layoutRouteConfig = layoutConfigFileData[layoutType]; const layoutRouteKey = Object.keys(layoutRouteConfig)[1]; const appLayout = layoutRouteConfig[layoutRouteKey]; const hostRouteConfig = layoutConfigFileData['host-bottom']; const hostRouteKey = Object.keys(hostRouteConfig)[1]; const hostLayout = hostRouteConfig[hostRouteKey]; const mixLayoutRouteKey = Object.keys(layoutRouteConfig)?.[2] || null; const mixLayout = mixLayoutRouteKey ? layoutRouteConfig[mixLayoutRouteKey] : null; const modulesRouteConfig = await this.#getModulesRouteConfig({ modules: modules }); const mainRouteConfig = await this.#readJsonFile(mainRoutes); const allRoutes = [...[mainRouteConfig ?? []], ...(modulesRouteConfig ?? [])]; const { replacedNavigationRouteConfig: routeConfig } = getReplacedRouteConfig({ layoutType: layoutType, routeConfig: allRoutes, layoutConfigData: layoutConfigFileData, initialRouteName: initialRouteName, }); const keyToReplace = appLayout.key || 'bottom_tab'; const keyToReplaceHost = hostLayout.key || 'host_tab'; const moduleRouteConfigObject = Object.assign({}, ...(routeConfig?.flat(1) ?? [])); const configuredRoutes = await getSortedNavigations('/', moduleRouteConfigObject); const layoutBottomTabRouteConfig = configuredRoutes?.[0]?.children?.filter((r) => r?.key == keyToReplace); const hostBottomTabRouteConfig = configuredRoutes?.[0]?.children?.filter((r) => r?.key == keyToReplaceHost); const bottomTabConfig = layoutBottomTabRouteConfig?.[0]?.children ?.filter((r) => !r?.side) ?.sort((a, b) => { if (a?.props?.options?.priority === undefined) return 1; // Push items with missing 'id' to the end if (b?.props?.options?.priority === undefined) return -1; return a?.props?.options?.priority - b?.props?.options?.priority; }) ?? []; const hostBottomTabConfig = hostBottomTabRouteConfig?.[0]?.children ?.filter((r) => !r?.side) ?.sort((a, b) => { if (a?.props?.options?.priority === undefined) return 1; // Push items with missing 'id' to the end if (b?.props?.options?.priority === undefined) return -1; return a?.props?.options?.priority - b?.props?.options?.priority; }) ?? []; const drawerConfig = layoutBottomTabRouteConfig?.[0]?.children ?.filter((r) => r?.side) ?.sort((a, b) => { if (a?.props?.options?.priority === undefined) return 1; // Push items with missing 'id' to the end if (b?.props?.options?.priority === undefined) return -1; return a?.props?.options?.priority - b?.props?.options?.priority; }) ?? []; const hostDrawerConfig = hostBottomTabRouteConfig?.[0]?.children ?.filter((r) => r?.side) ?.sort((a, b) => { if (a?.props?.options?.priority === undefined) return 1; // Push items with missing 'id' to the end if (b?.props?.options?.priority === undefined) return -1; return a?.props?.options?.priority - b?.props?.options?.priority; }) ?? []; if (bottomTabConfig) { try { await this.#generateBottomTabNavigationsFile({ bottomTabConfig: bottomTabConfig, unauthenticatedComponentPath, bottomDirPath: bottomDirPath, mixLayout, }); await this.#generateBottomTabNavigationsFile({ bottomTabConfig: hostBottomTabConfig, unauthenticatedComponentPath, bottomDirPath: hostBottomDirPath, mixLayout, }); if (drawerConfig) { await this.#generateDrawerNavigationsFile({ drawerConfig: drawerConfig, unauthenticatedComponentPath, drawerDirPath: drawerDirPath, }); await this.#generateDrawerNavigationsFile({ drawerConfig: hostDrawerConfig, unauthenticatedComponentPath, drawerDirPath: hostDirPath, }); } } catch (error) { console.log('Error in generating drawer bottom tab navigation', error); } } } async #generateAppNavigationFile({ appDirPath, customTabBarPath, customDrawerPath, customHeaderPath }) { const navigationDirPath = path.join(appDirPath, `/${appNavigationFileName}`); const layoutSettings = process.env.LAYOUT_SETTINGS ? JSON.parse(process.env.LAYOUT_SETTINGS) : null; const layoutConfigFileData = await this.#getLayoutConfig(); const layoutType = layoutSettings?.layout || 'bottom'; const layoutRouteConfig = layoutConfigFileData[layoutType]; const layoutRouteKey = Object.keys(layoutRouteConfig)[1]; const appLayout = layoutRouteConfig[layoutRouteKey]; const initialRouteNameRootStack = this.#initialRouteNameRootStack; const layoutInitialRouteName = this.#initialRouteName; const initialRouteName = layoutInitialRouteName ? layoutInitialRouteName : layoutType === 'mixSide' ? appLayout?.[appLayout?.key]?.props?.initialRouteName ?? 'MainStack.Layout.Home' : appLayout?.props?.initialRouteName || 'MainStack.Home'; const isShowTabs = layoutType === 'mixSide' || layoutType === 'bottom' ? true : false; const isShowDefalutHeader = layoutType === 'mixSide' ? true : false; const defaultHeaderProps = { ...(layoutSettings || {}), showToggle: layoutSettings?.topLeftToggle || false, right: layoutSettings?.topRightSettingToggle || false, }; const screenOptionsTab = layoutType === 'mixSide' ? { ...(appLayout?.[appLayout?.key]?.props?.screenOptions ?? {}) } : appLayout?.props?.screenOptions || { headerShown: true, title: 'Home', headerTitle: 'Home' }; const screenOptions = appLayout?.props?.screenOptions || { headerShown: true, title: 'Home', headerTitle: 'Home', }; let importStatements = ` import * as React from 'react'; import { navigationRef } from '@common-stack/client-react'; import { createNativeStackNavigator } from '@react-navigation/native-stack';`; let rootComponent = ''; let appComponent = ''; let appNavigation = ''; if (layoutType == 'side') { if (customDrawerPath) importStatements += `import CustomDrawerContent from '${customDrawerPath}';\n`; if (customHeaderPath) importStatements += `import CustomHeader from '${customHeaderPath}';\n`; importStatements += `import { createDrawerNavigator } from '@react-navigation/drawer'; import { getHeaderTitle } from '@react-navigation/elements'; import { useSelector } from 'react-redux'; import stackNavigations from './stack'; import drawerNavigations from './drawer'; import hostDrawerNavigations from './host_drawer'; const Stack = createNativeStackNavigator(); const Drawer = createDrawerNavigator(); `; rootComponent += ` const RootComponent = (props) => { const settings = useSelector((state) => state.settings); const defaultHeaderProps = ${JSON.stringify(defaultHeaderProps || {})}; return ( ({ ...${JSON.stringify(screenOptions)} ,...{ ${ customHeaderPath ? `header: (props) => { const title = getHeaderTitle(props.options, props.route.name); return ; }` : '' } }})} ${ customDrawerPath ? 'drawerContent={(props) => }' : '' } > {settings?.layout == 'host-bottom' ? hostDrawerNavigations({ Drawer }) : drawerNavigations({ Drawer })} ); } `; } if (layoutType == 'bottom') { if (customTabBarPath) importStatements += `import CustomTabBar from '${customTabBarPath}';\n`; if (customHeaderPath) importStatements += `import CustomHeader from '${customHeaderPath}';\n`; importStatements += `import {Header,Drawer as DefaultDrawer} from '@admin-layout/gluestack-ui-mobile'; import { getHeaderTitle } from '@react-navigation/elements'; import { useSelector } from 'react-redux'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import stackNavigations from './stack'; import bottomNavigations from './bottom'; import hostBottomNavigations from './host_bottom'; const Stack = createNativeStackNavigator(); const Tab = createBottomTabNavigator(); `; rootComponent += ` const RootComponent = (props) => { const settings = useSelector((state) => state.settings); const initialRouteName = ${JSON.stringify(initialRouteName)}; let defaultScreenOptions = ${JSON.stringify(screenOptionsTab)}; const defaultHeaderProps = ${JSON.stringify(defaultHeaderProps || {})}; const defaultHeader = {${ isShowDefalutHeader ? `header:(props)=>
` : '' }}; return ( ({...props,...defaultScreenOptions,...{${ customHeaderPath ? `header: (props) => { const title = getHeaderTitle(props.options, props.route.name); return ; }` : '' }}})} ${ customTabBarPath ? 'tabBar={(props) => }' : '' } > {settings?.layout == 'host-bottom' ? hostBottomNavigations({ Tab }) : bottomNavigations({Tab})} ); } `; } if (layoutType == 'mixSide') { if (customTabBarPath) importStatements += `import CustomTabBar from '${customTabBarPath}';\n`; if (customDrawerPath) importStatements += `import CustomDrawerContent from '${customDrawerPath}';\n`; if (customHeaderPath) importStatements += `import CustomHeader from '${customHeaderPath}';\n`; importStatements += `import {Header,Drawer as DefaultDrawer} from '@admin-layout/gluestack-ui-mobile'; import { useSelector } from 'react-redux'; import { MaterialIcons } from "@expo/vector-icons"; import { getHeaderTitle } from '@react-navigation/elements'; import { createDrawerNavigator } from '@react-navigation/drawer'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import stackNavigations from './stack'; import bottomNavigations from './bottom'; import hostBottomNavigations from './host_bottom'; import drawerNavigations from './drawer'; import hostDrawerNavigations from './host_drawer'; const Stack = createNativeStackNavigator(); const Drawer = createDrawerNavigator(); const Tab = createBottomTabNavigator(); `; rootComponent += ` const TabNavigator = () => { const settings = useSelector((state) => state.settings); const initialRouteName = ${JSON.stringify(initialRouteName)}; let defaultScreenOptions = ${JSON.stringify(screenOptionsTab)}; const defaultHeaderProps = ${JSON.stringify(defaultHeaderProps || {})}; const defaultHeader = {${ isShowDefalutHeader ? `header:(props)=>
` : '' }}; return ( ({...props,...defaultScreenOptions,...{headerShown: false,header:()=>null},})} ${ customTabBarPath ? 'tabBar={(props) => }' : '' } > {settings?.layout == 'host-bottom' ? hostBottomNavigations({ Tab }) : bottomNavigations({Tab})} ) } const RootComponent = (props) => { const initialRouteName = ${JSON.stringify(initialRouteName)}; const settings = useSelector((state) => state.settings); let defaultScreenOptions = ${JSON.stringify(screenOptionsTab)}; const defaultHeaderProps = ${JSON.stringify(defaultHeaderProps || {})}; const defaultHeader = {${ isShowDefalutHeader ? `header:(props)=>
` : '' }}; return ( ({ ...${JSON.stringify( screenOptions, )} ,...{headerTitle:navigationRef?.isReady() && navigationRef?.getCurrentRoute() ? navigationRef?.getCurrentOptions()?.title ? navigationRef?.getCurrentOptions()?.headerTitle : navigationRef?.getCurrentRoute()?.route?.name : "Home", ${ customHeaderPath ? `header: (props) => { const title = getHeaderTitle(props.options, props.route.name); return ; }` : '' } }})} ${ customDrawerPath ? 'drawerContent={((props)) => }' : '' } > ( ),}} component={TabNavigator} /> {settings?.layout == 'host-bottom' ? hostDrawerNavigations({ Drawer }) : drawerNavigations({ Drawer })} ); } `; } appComponent += ` const AppNavigations = () => { return ( {stackNavigations({ Stack })} ) } export default AppNavigations; `; appNavigation = importStatements + '\n' + rootComponent + '\n' + appComponent; appNavigation = await prettier.format(appNavigation, { parser: 'babel' }); try { await this.#writeFile(navigationDirPath, appNavigation); } catch (error) { console.log('Error in generating app navigationfile', error); } } async #generateSelectiveIconsFile({ appDirPath, modules }) { const mainRoutes = path.join(appDirPath, `/${mainRoutesFileName}`); const modulesRouteConfig = await this.#getModulesRouteConfig({ modules: modules }); const mainRouteConfig = await this.#readJsonFile(mainRoutes); const allRoutes = [...[mainRouteConfig ?? []], ...(modulesRouteConfig ?? [])]; let iconNames = []; allRoutes?.flat(1)?.forEach((route) => { const key = Object.keys(route)[0]; const value = route[key]; if (value?.icon && typeof value.icon === 'object') { if (typeof value.icon.name === 'string') { if (!iconNames.includes(value.icon.name)) iconNames.push(value.icon.name); } } else if (value?.icon && typeof value.icon === 'string') { if (!iconNames.includes(value.icon)) iconNames.push(value.icon); } if (value?.extraIcons && Array.isArray(value.extraIcons) && value?.extraIcons?.length > 0) { value?.extraIcons?.map((icon) => { if (typeof icon === 'string') { if (!iconNames.includes(icon)) iconNames.push(icon); } else { console.warn(`Invalid icon type: ${typeof icon}`); } }); } else if (value?.extraIcons && typeof value.extraIcons === 'string') { if (!iconNames.includes(value.extraIcons)) iconNames.push(value.extraIcons); } }); const iconsRepository = this.#iconsRepository; const expoIcons = [ 'AntDesign', 'Entypo', 'EvilIcons', 'Feather', 'FontAwesome', 'FontAwesome5', 'Fontisto', 'Foundation', 'Ionicons', 'MaterialCommunityIcons', 'MaterialIcons', 'Octicons', 'SimpleLineIcons', 'Zocial', ]; let content = ` function __variableDynamicIcon(icon) { switch (icon) { `; iconNames.forEach((name) => { let prefix, iconName; if (name.includes('.')) { [prefix, iconName] = name.split('.'); } if (prefix && iconsRepository[prefix]) { const importPath = iconsRepository[prefix].replace( '{iconName}', prefix === 'expo' ? iconName : iconName + '.native', ); // prefix = prefix.charAt(0).toUpperCase() + prefix.slice(1).toLowerCase(); content += ` case '${prefix + '.' + iconName}': return require('${importPath}')?.default; `; } else { content += ` case '${name}': return ${ expoIcons.includes(name) ? `require('@expo/vector-icons/${name}.js')?.default` : `require('@expo/vector-icons/FontAwesome.js')?.default;` }; `; } }); content += ` default: console.warn('Sorry, the icon named "', icon, '" could not be found in "@react-icon/all-files" and "@app/icons". Please check again.'); return require('@expo/vector-icons/FontAwesome.js')?.default; } } export default function(icon) { return __variableDynamicIcon(icon); } `; const rootPath = process.cwd(); const fileName = `selectiveIcons.js`; const newFilePath = path.join(rootPath, 'app', fileName); // Ensure the directory exists if (!fs.existsSync(path.dirname(newFilePath))) { fs.mkdirSync(path.dirname(newFilePath), { recursive: true }); } fs.writeFileSync(newFilePath, content, 'utf8'); } async #setLayoutAndGenerateNavigation() { const appDirPath = this.#appDirPath; const modules = this.#modules; const initialRouteName = this.#initialRouteName; const unauthenticatedComponentPath = this.#unauthenticatedComponentPath; const customTabBarPath = this.#customTabBarPath; const customDrawerPath = this.#customDrawerPath; const customHeaderPath = this.#customHeaderPath; const i18Options = this.#i18Options; const layoutSettings = process.env.LAYOUT_SETTINGS ? JSON.parse(process.env.LAYOUT_SETTINGS) : null; const layoutConfigFileData = await this.#getLayoutConfig(); const layoutType = layoutSettings?.layout || 'bottom'; try { await this.#generateAppRoutesJson(); await this.#generateSelectiveIconsFile({ appDirPath, modules }); await this.#generateMainRoutes({ appDirPath, i18Options, initialRouteName, }); await this.#generateApp({ appDirPath, i18Options, initialRouteName, }); await this.#generateStackNavigations({ appDirPath, modules, initialRouteName, unauthenticatedComponentPath, }); if (layoutType == 'side') { try { await this.#generateDrawerNavigations({ appDirPath, modules, initialRouteName, unauthenticatedComponentPath, }); await this.#generateAppNavigationFile({ appDirPath, customTabBarPath, customDrawerPath, customHeaderPath, }); } catch (error) { console.log('Error in generating side navigation', error); } } else if (layoutType == 'bottom' || layoutType == 'host-bottom') { try { await this.#generateBottomTabNavigations({ appDirPath, modules, initialRouteName, unauthenticatedComponentPath, }); await this.#generateAppNavigationFile({ appDirPath, customTabBarPath, customDrawerPath, customHeaderPath, }); } catch (error) { console.log('Error in generating bottom navigation', error); } } else { try { await this.#generateBottomTabDrawerNavigations({ appDirPath, modules, initialRouteName, unauthenticatedComponentPath, }); await this.#generateAppNavigationFile({ appDirPath, customTabBarPath, customDrawerPath, customHeaderPath, }); } catch (error) { console.log('Error in generating drawer bottom navigation', error); } } } catch (error) { console.log('Error generating app navigations', error); } } async #performCopyOperations() { const config = this.#configFileData; try { await performCopyOperations(config); await this.#setLayoutAndGenerateNavigation(); } catch (error) { console.error('PerformCopyOperations error:', error); } } async #deleteDirectoryRecursive(dirPath: any) { if (fs.existsSync(dirPath)) { const files = fs.readdirSync(dirPath); for (const file of files) { const curPath = path.join(dirPath, file); if (fs.lstatSync(curPath).isDirectory()) { // Recursively delete subdirectories await this.#deleteDirectoryRecursive(curPath); } else { // Delete files fs.unlinkSync(curPath); } } // Delete the directory itself fs.rmdirSync(dirPath); } } async #createAppDirectory() { const appDir = this.#appDirPath; try { // Check if the directory exists if (fs.existsSync(appDir)) { console.log('Directory exists. Recreating it...'); // Delete the directory and its contents await this.#deleteDirectoryRecursive(appDir); } // Create the directory fs.mkdirSync(appDir); console.log('Directory created'); // Add 'app' to .gitignore if not already present const gitignorePath = path.resolve(appDir, '.gitignore'); let gitignoreContent = ''; if (fs.existsSync(gitignorePath)) { gitignoreContent = fs.readFileSync(gitignorePath, 'utf8'); } else { fs.writeFileSync(gitignorePath, ''); console.log('Created .gitignore file'); } if (!gitignoreContent.includes('*')) { fs.appendFileSync(gitignorePath, '*\n'); console.log('Added "*" to .gitignore'); } await this.#performCopyOperations(); } catch (error) { console.error('Error creating app directory:', error); } } async generateAppNavigations() { return this.#createAppDirectory(); } } export default GenerateMobileNavigations;