import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { customRoutes } from '../src/router/routes/custom-routes.ts'; import { generatedRoutes } from '../src/router/elegant/routes.ts'; import zhCn from '../src/locales/langs/zh-cn.ts'; const currentFilePath = fileURLToPath(import.meta.url); const currentDirPath = path.dirname(currentFilePath); const rootDir = path.resolve(currentDirPath, '..'); const outputPath = path.resolve(rootDir, 'docs/frontend-page-resource-manifest.json'); const excludedPageResourceRouteNames = new Set(['exception_403', 'exception_404', 'exception_500']); const excludedPageResourcePathPrefixes = ['/function/', '/plugin/']; function isConstantRoute(route) { return Boolean(route.meta?.constant); } function getRouteOrder(route) { const order = Number(route.meta?.order); if (Number.isFinite(order)) { return order; } return Number.MAX_SAFE_INTEGER; } function sortRoutes(routes) { return [...routes].sort((next, prev) => { const orderDiff = getRouteOrder(next) - getRouteOrder(prev); if (orderDiff !== 0) { return orderDiff; } return String(next.path || '').localeCompare(String(prev.path || '')); }); } function getPageComponent(component) { if (!component) { return null; } if (component.startsWith('view.')) { return component; } if (component.startsWith('layout.') && component.includes('$view.')) { return component; } return null; } function getPageType(component) { if (!component) { return null; } if (component.startsWith('view.')) { return 'leaf'; } if (component.startsWith('layout.') && component.includes('$view.')) { return 'single'; } return null; } function shouldExcludePageResource(route) { if (excludedPageResourceRouteNames.has(route.name)) { return true; } return excludedPageResourcePathPrefixes.some(prefix => String(route.path || '').startsWith(prefix)); } function getNumberMetaValue(value) { const parsed = Number(value); if (Number.isFinite(parsed)) { return parsed; } return null; } function normalizeRouteProps(props) { if (props === true) { return true; } if (props && typeof props === 'object') { return props; } return null; } function getRouteLabel(route) { const routeLocales = zhCn.route || {}; const i18nKey = route.meta?.i18nKey; if (typeof i18nKey === 'string' && i18nKey.startsWith('route.')) { const localeKey = i18nKey.slice('route.'.length); if (routeLocales[localeKey]) { return routeLocales[localeKey]; } } if (routeLocales[route.name]) { return routeLocales[route.name]; } return route.meta?.title || route.name; } function getPageResourceMeta(route) { const hideInMenu = Boolean(route.meta?.hideInMenu); const order = getNumberMetaValue(route.meta?.order); const fixedIndexInTab = getNumberMetaValue(route.meta?.fixedIndexInTab); return { title: getRouteLabel(route), i18nKey: route.meta?.i18nKey || null, icon: route.meta?.icon || null, localIcon: route.meta?.localIcon || null, order, keepAlive: Boolean(route.meta?.keepAlive), hideInMenu, activeMenu: route.meta?.activeMenu || null, multiTab: Boolean(route.meta?.multiTab), fixedIndexInTab }; } function shouldCollectPageResource(route, pageComponent) { if (!pageComponent) { return false; } if (route.meta?.hideInMenu) { return false; } return !shouldExcludePageResource(route); } function createPageResourceItem(route, options) { const { pageComponent, parentName, source } = options; const props = normalizeRouteProps(route.props); const meta = getPageResourceMeta(route); return { name: route.name, path: route.path, component: pageComponent, title: meta.title, routeTitle: route.meta?.title || route.name, i18nKey: meta.i18nKey, icon: meta.icon, localIcon: meta.localIcon, order: meta.order, hideInMenu: meta.hideInMenu, keepAlive: meta.keepAlive, activeMenu: meta.activeMenu, multiTab: meta.multiTab, fixedIndexInTab: meta.fixedIndexInTab, redirect: route.redirect || null, props, meta, parentName, pageType: getPageType(pageComponent), source }; } function collectPageResources(routes, options, items) { const { parentName = null, source } = options; const orderedRoutes = sortRoutes(routes); for (const route of orderedRoutes) { const pageComponent = getPageComponent(route.component); // The backend page-node whitelist only contains menu-visible pages. if (shouldCollectPageResource(route, pageComponent)) { items.push(createPageResourceItem(route, { pageComponent, parentName, source })); } if (route.children?.length) { collectPageResources(route.children, { parentName: route.name, source }, items); } } } function getPageResources() { const items = []; collectPageResources( customRoutes.filter(route => !isConstantRoute(route)), { source: 'custom' }, items ); collectPageResources( generatedRoutes.filter(route => !isConstantRoute(route)), { source: 'generated' }, items ); const uniqueItems = Array.from(new Map(items.map(item => [item.name, item])).values()); return uniqueItems; } const pageResources = getPageResources(); const result = { generatedAt: new Date().toISOString(), description: 'Frontend visible page resource whitelist for backend route/menu configuration.', rules: { directoryComponent: 'layout.base', pageComponentPattern: 'view.', singlePageComponentPattern: 'layout.$view.' }, total: pageResources.length, items: pageResources }; fs.writeFileSync(outputPath, `${JSON.stringify(result, null, 2)}\n`, 'utf8'); console.log(`Generated ${pageResources.length} page resources -> ${path.relative(rootDir, outputPath)}`);