import { existsSync } from 'node:fs' import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' /** 生产构建只替换连接配置,保留运行时 JSON 中的 GIS/模型参数。 */ export function productionRuntimeConfig(raw, env) { const keys = ['VITE_COCKPIT_DATA_SOURCE', 'VITE_COCKPIT_API_BASE_URL', 'VITE_COCKPIT_WS_URL'] for (const key of keys) { if (typeof env[key] !== 'string' || !env[key].trim()) throw new Error('缺少生产环境变量:' + key) } const imageBase = typeof env.VITE_APP_MINIO_BASE_URL === 'string' && env.VITE_APP_MINIO_BASE_URL.trim() ? env.VITE_APP_MINIO_BASE_URL.trim() : '/minio/pipe/' try { const imageUrl = new URL(imageBase, 'http://runtime-image-config.invalid/') const publicPath = /^https?:\/\//i.test(imageBase) || (imageBase.startsWith('/') && !imageBase.startsWith('//')) if (!publicPath || !['http:', 'https:'].includes(imageUrl.protocol) || imageUrl.username || imageUrl.password || imageUrl.search || imageUrl.hash) throw new Error() } catch { throw new Error('生产图片访问基址必须是无凭据或签名参数的公开地址或同源路径') } return { ...raw, dataSource: env.VITE_COCKPIT_DATA_SOURCE, api: { ...raw.api, baseUrl: env.VITE_COCKPIT_API_BASE_URL }, ws: { ...raw.ws, url: env.VITE_COCKPIT_WS_URL }, images: { baseUrl: imageBase }, } } /** 生产配置来自可复现的部署模板,绝不复制本机 public/cockpit/config.json。 */ export default function cockpitEnvPlugin() { let config const readTemplate = async () => productionRuntimeConfig( JSON.parse(await readFile(resolve(config.root, 'deploy/cockpit.config.json'), 'utf8')), config.env, ) return { name: 'cockpit-environment-config', configResolved(resolved) { config = resolved }, configureServer(server) { // 干净检出也有完整地图配置;已有本机 JSON 时仍遵循原覆盖规则。 server.middlewares.use('/cockpit/config.json', async (req, res, next) => { if (existsSync(resolve(config.publicDir, 'cockpit/config.json'))) return next() try { const raw = await readTemplate() res.setHeader('Content-Type', 'application/json; charset=utf-8') res.setHeader('Cache-Control', 'no-cache') res.end(JSON.stringify(raw)) } catch (error) { next(error) } }) }, async generateBundle() { this.emitFile({ type: 'asset', fileName: 'cockpit/config.json', source: JSON.stringify(await readTemplate(), null, 2) + '\n', }) }, } }