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;