From f08d5c904cc9e9d912279717699aad6d68cf7f0c Mon Sep 17 00:00:00 2001 From: jialin Date: Fri, 8 May 2026 09:29:41 +0800 Subject: [PATCH] refactor: dynamic routes --- config/routes.extensions.ts | 3 + config/routes.ts | 15 ++--- package.json | 8 +-- scripts/run-enterprise-build.cjs | 47 --------------- scripts/run-enterprise-dev.cjs | 69 --------------------- scripts/sync-enterprise-config.cjs | 96 ------------------------------ src/pages/settings/index.tsx | 22 ------- src/plugins/route-merger.ts | 57 ------------------ 8 files changed, 8 insertions(+), 309 deletions(-) create mode 100644 config/routes.extensions.ts delete mode 100644 scripts/run-enterprise-build.cjs delete mode 100644 scripts/run-enterprise-dev.cjs delete mode 100644 scripts/sync-enterprise-config.cjs delete mode 100644 src/pages/settings/index.tsx delete mode 100644 src/plugins/route-merger.ts diff --git a/config/routes.extensions.ts b/config/routes.extensions.ts new file mode 100644 index 00000000..3a6e2c56 --- /dev/null +++ b/config/routes.extensions.ts @@ -0,0 +1,3 @@ +// Identity hook for build-time route extensions. Tooling may overwrite +// this file to inject additional routes; the original is restored on cleanup. +export const applyRouteExtensions = (base: T): T => base; diff --git a/config/routes.ts b/config/routes.ts index c60234e1..fb461d0c 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -1,6 +1,7 @@ import { keepAliveRoutes } from './keep-alive'; +import { applyRouteExtensions } from './routes.extensions'; -export default [ +const baseRoutes = [ { name: 'dashboard', path: '/dashboard', @@ -312,16 +313,6 @@ export default [ defaultIcon: 'icon-key', component: './api-keys' }, - { - name: 'settings', - path: '/settings', - key: 'settings', - hideInMenu: true, - selectedIcon: 'icon-settings-filled', - icon: 'icon-settings', - defaultIcon: 'icon-settings', - component: './settings' - }, { name: 'profile', path: '/profile', @@ -346,3 +337,5 @@ export default [ component: './404' } ]; + +export default applyRouteExtensions(baseRoutes); diff --git a/package.json b/package.json index 3419348a..e4780ede 100644 --- a/package.json +++ b/package.json @@ -3,20 +3,14 @@ "author": "gpustack", "scripts": { "build": "max build", - "build:enterprise": "node ./scripts/run-enterprise-build.cjs", "check:locales": "node --import tsx ./src/locales/check.ts", "dev": "max dev", - "dev:enterprise": "node ./scripts/run-enterprise-dev.cjs", "format": "prettier --cache --write .", "postinstall": "max setup", "prepare": "husky", "preview": "max preview", "setup": "max setup", - "start": "npm run dev", - "sync:enterprise-config": "node ./scripts/sync-enterprise-config.cjs", - "sync:enterprise-config:clean": "node ./scripts/sync-enterprise-config.cjs clean", - "sync:enterprise-global": "npm run sync:enterprise-config", - "sync:enterprise-global:clean": "npm run sync:enterprise-config:clean" + "start": "npm run dev" }, "dependencies": { "@ant-design/icons": "^6.1.0", diff --git a/scripts/run-enterprise-build.cjs b/scripts/run-enterprise-build.cjs deleted file mode 100644 index 050d2f8e..00000000 --- a/scripts/run-enterprise-build.cjs +++ /dev/null @@ -1,47 +0,0 @@ -const { spawnSync } = require('node:child_process'); -const path = require('node:path'); - -const syncScript = path.resolve(__dirname, './sync-enterprise-config.cjs'); - -const runNodeScript = (args) => { - const result = spawnSync(process.execPath, [syncScript, ...args], { - stdio: 'inherit' - }); - - if (result.error) { - throw result.error; - } - - if (typeof result.status === 'number' && result.status !== 0) { - process.exit(result.status); - } -}; - -const runBuild = () => - spawnSync('max', ['build'], { - env: { - ...process.env, - ENABLE_ENTERPRISE: 'true' - }, - shell: true, - stdio: 'inherit' - }); - -let buildResult; - -try { - runNodeScript([]); - buildResult = runBuild(); -} finally { - try { - runNodeScript(['clean']); - } catch (error) { - console.error('Failed to clean enterprise config:', error); - } -} - -if (buildResult?.error) { - throw buildResult.error; -} - -process.exit(buildResult?.status ?? 0); diff --git a/scripts/run-enterprise-dev.cjs b/scripts/run-enterprise-dev.cjs deleted file mode 100644 index 11663ede..00000000 --- a/scripts/run-enterprise-dev.cjs +++ /dev/null @@ -1,69 +0,0 @@ -const { spawn, spawnSync } = require('node:child_process'); -const path = require('node:path'); - -const syncScript = path.resolve(__dirname, './sync-enterprise-config.cjs'); - -const runNodeScript = (args) => { - const result = spawnSync(process.execPath, [syncScript, ...args], { - stdio: 'inherit' - }); - - if (typeof result.status === 'number' && result.status !== 0) { - process.exit(result.status); - } - - if (result.error) { - throw result.error; - } -}; - -runNodeScript([]); - -const child = spawn('max', ['dev'], { - env: { - ...process.env, - ENABLE_ENTERPRISE: 'true' - }, - shell: true, - stdio: 'inherit' -}); - -let cleaned = false; - -const clean = () => { - if (cleaned) { - return; - } - cleaned = true; - try { - runNodeScript(['clean']); - } catch (error) { - console.error('Failed to clean enterprise config:', error); - } -}; - -const forwardSignal = (signal) => { - if (!child.killed) { - child.kill(signal); - } -}; - -['SIGINT', 'SIGTERM', 'SIGHUP'].forEach((signal) => { - process.on(signal, () => { - forwardSignal(signal); - }); -}); - -child.on('error', (error) => { - clean(); - throw error; -}); - -child.on('exit', (code, signal) => { - clean(); - if (signal) { - process.kill(process.pid, signal); - return; - } - process.exit(code ?? 0); -}); diff --git a/scripts/sync-enterprise-config.cjs b/scripts/sync-enterprise-config.cjs deleted file mode 100644 index 9c8b7e8c..00000000 --- a/scripts/sync-enterprise-config.cjs +++ /dev/null @@ -1,96 +0,0 @@ -const fs = require('node:fs'); -const path = require('node:path'); - -const workspaceGlobalPath = path.resolve(__dirname, '../../../global.tsx'); -const workspaceDependenciesPath = path.resolve(__dirname, '../../../dependencies.json'); -const workspaceTsconfigPath = path.resolve(__dirname, '../../../tsconfig.json'); -const targetGlobalPath = path.resolve(__dirname, '../src/global.tsx'); -const targetPackageJsonPath = path.resolve(__dirname, '../package.json'); -const targetTsconfigPath = path.resolve(__dirname, '../tsconfig.json'); -const mode = process.argv[2] || 'append'; -const blockStart = '// ENTERPRISE_PLUGIN_BLOCK_START'; -const blockEnd = '// ENTERPRISE_PLUGIN_BLOCK_END'; - -const workspaceSource = fs.readFileSync(workspaceGlobalPath, 'utf8').trim(); -const blockContent = `\n${blockStart}\n${workspaceSource}\n${blockEnd}\n`; - -const stripBlock = (content) => { - const pattern = new RegExp(`\\n?${blockStart}[\\s\\S]*?${blockEnd}\\n?`, 'g'); - return content.replace(pattern, '\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n'; -}; - -const currentContent = fs.readFileSync(targetGlobalPath, 'utf8'); -const strippedContent = stripBlock(currentContent); - -const readJson = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf8')); -const writeJson = (filePath, data) => { - fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`); -}; - -const syncPackageJson = () => { - const sourceDependencies = - readJson(workspaceDependenciesPath).dependencies || {}; - const targetPackageJson = readJson(targetPackageJsonPath); - const currentDependencies = { - ...(targetPackageJson.dependencies || {}) - }; - - if (mode === 'clean') { - Object.keys(sourceDependencies).forEach((dependencyName) => { - delete currentDependencies[dependencyName]; - }); - } else { - Object.assign(currentDependencies, sourceDependencies); - } - - targetPackageJson.dependencies = currentDependencies; - writeJson(targetPackageJsonPath, targetPackageJson); - console.log( - `${mode === 'clean' ? 'Cleaned' : 'Synced'} enterprise dependencies in ${targetPackageJsonPath}`, - ); -}; - -const syncTsconfig = () => { - const sourcePaths = - readJson(workspaceTsconfigPath).compilerOptions?.paths || {}; - const targetTsconfig = readJson(targetTsconfigPath); - const currentPaths = { - ...(targetTsconfig.compilerOptions?.paths || {}) - }; - - if (mode === 'clean') { - Object.keys(sourcePaths).forEach((pathKey) => { - delete currentPaths[pathKey]; - }); - } else { - Object.assign(currentPaths, sourcePaths); - } - - targetTsconfig.compilerOptions = targetTsconfig.compilerOptions || {}; - targetTsconfig.compilerOptions.paths = currentPaths; - - writeJson(targetTsconfigPath, targetTsconfig); - console.log( - `${mode === 'clean' ? 'Cleaned' : 'Synced'} enterprise tsconfig paths in ${targetTsconfigPath}`, - ); -}; - -if (mode === 'clean') { - if (strippedContent !== currentContent) { - fs.writeFileSync(targetGlobalPath, strippedContent); - console.log(`Cleaned enterprise plugin block from ${targetGlobalPath}`); - } else { - console.log(`Enterprise plugin block already absent: ${targetGlobalPath}`); - } -} else { - const updatedContent = `${strippedContent.trimEnd()}${blockContent}`; - if (updatedContent !== currentContent) { - fs.writeFileSync(targetGlobalPath, updatedContent); - console.log(`Appended enterprise plugin block to ${targetGlobalPath}`); - } else { - console.log(`Enterprise plugin block already up to date: ${targetGlobalPath}`); - } -} - -syncPackageJson(); -syncTsconfig(); diff --git a/src/pages/settings/index.tsx b/src/pages/settings/index.tsx deleted file mode 100644 index 3b4b8b9b..00000000 --- a/src/pages/settings/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { getGPUStackPlugin } from '@/plugins'; -import { Result } from 'antd'; -import React from 'react'; - -const SettingsPage: React.FC = () => { - const enterprisePlugin = getGPUStackPlugin(); - const BrandingUI = enterprisePlugin?.components?.BrandingUI; - - if (!BrandingUI) { - return ( - - ); - } - - return ; -}; - -export default SettingsPage; diff --git a/src/plugins/route-merger.ts b/src/plugins/route-merger.ts deleted file mode 100644 index b9a31d77..00000000 --- a/src/plugins/route-merger.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { getGPUStackPlugin } from '@/plugins'; -import type { RouteConfig } from '@/plugins/types'; - -type ClientRoute = RouteConfig & { - id?: string; - children?: ClientRoute[]; - routes?: ClientRoute[]; -}; - -function getLayoutChildren(routes: ClientRoute[]): ClientRoute[] | null { - const rootRoute = routes.find((route) => route.path === '/'); - if (!rootRoute) return null; - - const rootChildren = rootRoute.routes || rootRoute.children; - if (!Array.isArray(rootChildren)) return null; - - const globalLayout = rootChildren.find((route) => - route.id?.includes('global-layout') - ); - if (!globalLayout) return null; - - const layoutChildren = globalLayout.children || globalLayout.routes; - return Array.isArray(layoutChildren) ? layoutChildren : null; -} - -function mergeRoute(targetRoutes: ClientRoute[], pluginRoute: ClientRoute) { - const existsIndex = targetRoutes.findIndex( - (route) => - (pluginRoute.path && route.path === pluginRoute.path) || - (pluginRoute.key && route.key === pluginRoute.key) - ); - - if (existsIndex === -1) { - targetRoutes.push(pluginRoute); - return; - } - - const currentRoute = targetRoutes[existsIndex]; - targetRoutes[existsIndex] = { - ...currentRoute, - ...pluginRoute, - routes: pluginRoute.routes || currentRoute.routes, - children: pluginRoute.children || currentRoute.children - }; -} - -export function mergeEnterpriseRoutes(routes: ClientRoute[]) { - const enterprisePlugin = getGPUStackPlugin(); - if (!enterprisePlugin?.routes?.length) return; - - const layoutChildren = getLayoutChildren(routes); - if (!layoutChildren) return; - - enterprisePlugin.routes.forEach((route) => - mergeRoute(layoutChildren, route as ClientRoute) - ); -}