feat: sync full workspace including web modules, docs, and configurations to Gitea

Optimized the root .gitignore to exclude virtual environments, node modules,
and temp folders to ensure clean and lightweight version tracking.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
王冕
2026-06-09 18:12:25 +08:00
parent 351688006e
commit a27e3b8e43
1510 changed files with 162044 additions and 1517 deletions

View File

@@ -0,0 +1,270 @@
import type { Plugin } from 'vite';
import fs from 'fs';
import path from 'path';
import { getLocalIP, getRequestPathname } from './utils/httpUtils';
import { MAKE_ENTRIES_RELATIVE_PATH } from './utils/makeConstants';
import { buildDocApiPath } from './utils/docUtils';
function readInjectedHtml(htmlPath: string, injectScript: string) {
let html = fs.readFileSync(htmlPath, 'utf8');
html = html.replace('</head>', `${injectScript}\n</head>`);
return html;
}
function setNoStoreHeaders(res: any) {
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
}
function setImmutableAssetHeaders(res: any) {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
}
function setImageCacheHeaders(res: any) {
res.setHeader('Cache-Control', 'public, max-age=86400');
}
function hasVersionQuery(requestUrl: string) {
return /[?&]v=/.test(requestUrl);
}
function setAdminStaticCacheHeaders(res: any, pathname: string, requestUrl: string) {
if (!hasVersionQuery(requestUrl)) {
setNoStoreHeaders(res);
return;
}
if (pathname.startsWith('/images/')) {
setImageCacheHeaders(res);
return;
}
if (pathname.startsWith('/assets/')) {
setImmutableAssetHeaders(res);
return;
}
setNoStoreHeaders(res);
}
function escapeHtmlAttribute(value: string) {
return value
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function getCanvasDisplayName(canvasName: string) {
return path.basename(canvasName, path.extname(canvasName)) || canvasName;
}
function injectCanvasTemplateHtml(templateHtml: string, canvasName: string, injectScript: string) {
const displayName = getCanvasDisplayName(canvasName);
const pageTitle = `${displayName} - Canvas`;
return templateHtml
.replace(/\{\{CANVAS_NAME\}\}/g, escapeHtmlAttribute(canvasName))
.replace(/\{\{CANVAS_TITLE\}\}/g, escapeHtmlAttribute(pageTitle))
.replace('<title>Canvas</title>', `<title>${escapeHtmlAttribute(pageTitle)}</title>`)
.replace('</head>', `${injectScript}\n</head>`);
}
export function serveAdminPlugin(): Plugin {
const projectRoot = process.cwd();
const appsMatch = projectRoot.match(/[\/\\]apps[\/\\]([^\/\\]+)/);
let projectPrefix = '';
if (appsMatch) {
const rootDir = projectRoot.split(/[\/\\]apps[\/\\]/)[0];
const appsDir = path.join(rootDir, 'apps');
if (fs.existsSync(appsDir)) {
const appFolders = fs.readdirSync(appsDir);
for (const folder of appFolders) {
const folderPath = path.join(appsDir, folder);
const entriesPath = path.join(folderPath, MAKE_ENTRIES_RELATIVE_PATH);
if (fs.existsSync(entriesPath)) {
projectPrefix = `apps/${folder}/`;
break;
}
}
}
}
const isMixedProject = Boolean(projectPrefix);
return {
name: 'serve-admin-plugin',
configureServer(server: any) {
server.middlewares.use(async (req: any, res: any, next: any) => {
try {
const adminDir = path.resolve(projectRoot, 'admin');
const pathname = getRequestPathname(req);
const requestUrl = String(req.url || pathname || '/');
const localIP = getLocalIP();
const actualPort = server.httpServer?.address()?.port || server.config.server?.port || 5173;
const injectScript = `
<script>
window.__PROJECT_PREFIX__ = '${projectPrefix}';
window.__IS_MIXED_PROJECT__ = ${isMixedProject};
window.__LOCAL_IP__ = '${localIP}';
window.__LOCAL_PORT__ = ${actualPort};
</script>`;
const sendHtml = async (html: string, options?: { transform?: boolean }) => {
let responseHtml = html;
if (options?.transform) {
const htmlUrl = requestUrl === '/' ? '/index.html' : requestUrl;
responseHtml = await server.transformIndexHtml(htmlUrl, html, requestUrl);
}
setNoStoreHeaders(res);
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(responseHtml);
};
if (pathname === '/' || pathname === '/index.html') {
const indexPath = path.join(adminDir, 'index.html');
if (fs.existsSync(indexPath)) {
// 首页 admin 壳不参与 src HMR避免外层页面被 Vite client 带着刷新。
await sendHtml(readInjectedHtml(indexPath, injectScript), { transform: false });
return;
}
}
if (pathname && pathname.match(/^\/[^/]+\.html$/)) {
const htmlPath = path.join(adminDir, pathname);
if (fs.existsSync(htmlPath)) {
// 其他 admin 静态壳页面同样不接入 HMR只保留 iframe 内 src 页面自己的热更。
await sendHtml(readInjectedHtml(htmlPath, injectScript), { transform: false });
return;
}
}
if (pathname && pathname.startsWith('/assets/')) {
const assetPath = path.join(adminDir, pathname);
if (fs.existsSync(assetPath)) {
const ext = path.extname(assetPath);
const contentTypes: Record<string, string> = {
'.js': 'application/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.eot': 'application/vnd.ms-fontobject',
};
setAdminStaticCacheHeaders(res, pathname, requestUrl);
res.setHeader('Content-Type', contentTypes[ext] || 'application/octet-stream');
res.end(fs.readFileSync(assetPath));
return;
}
}
if (pathname && pathname.startsWith('/images/')) {
const imagePath = path.join(adminDir, pathname);
if (fs.existsSync(imagePath)) {
const ext = path.extname(imagePath);
const contentTypes: Record<string, string> = {
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
};
setAdminStaticCacheHeaders(res, pathname, requestUrl);
res.setHeader('Content-Type', contentTypes[ext] || 'image/png');
res.end(fs.readFileSync(imagePath));
return;
}
}
if (pathname && pathname.startsWith('/admin/')) {
const adminFilePath = path.join(adminDir, pathname.replace('/admin/', ''));
if (fs.existsSync(adminFilePath)) {
const ext = path.extname(adminFilePath);
const contentTypes: Record<string, string> = {
'.js': 'application/javascript; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.html': 'text/html; charset=utf-8',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.eot': 'application/vnd.ms-fontobject',
};
const adminPathname = pathname.replace('/admin', '') || '/';
setAdminStaticCacheHeaders(res, adminPathname, requestUrl);
res.setHeader('Content-Type', contentTypes[ext] || 'application/octet-stream');
res.end(fs.readFileSync(adminFilePath));
return;
}
}
if (pathname && pathname.match(/^\/[^/]+\.js$/)) {
const jsPath = path.join(adminDir, pathname);
if (fs.existsSync(jsPath)) {
setNoStoreHeaders(res);
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
res.end(fs.readFileSync(jsPath));
return;
}
}
const isDocsAssetRequest = pathname?.startsWith('/docs/') && pathname.includes('/assets/');
const encodedDocName = isDocsAssetRequest
? undefined
: pathname?.match(/^\/docs\/(.+?)(?:\/spec\.html)?$/)?.[1];
if (encodedDocName) {
const specTemplatePath = path.join(adminDir, 'spec-template.html');
if (fs.existsSync(specTemplatePath)) {
let html = fs.readFileSync(specTemplatePath, 'utf8');
const docName = decodeURIComponent(encodedDocName);
const docFileName = docName.endsWith('.md') ? docName : `${docName}.md`;
const specUrl = buildDocApiPath(docFileName);
html = html.replace(/\{\{SPEC_URL\}\}/g, specUrl);
html = html.replace(/\{\{TITLE\}\}/g, docName);
html = html.replace(/\{\{MULTI_DOC\}\}/g, 'false');
html = html.replace(/\{\{DOCS_CONFIG\}\}/g, '[]');
html = html.replace('</head>', `${injectScript}\n</head>`);
// 文档页内容源现在也在 src 下,保留它自己的 Vite 转换与更新能力。
await sendHtml(html, { transform: true });
return;
}
}
const encodedCanvasName = pathname?.match(/^\/canvas\/(.+?)\/?$/)?.[1];
if (encodedCanvasName) {
const canvasTemplatePath = path.join(adminDir, 'canvas-template.html');
if (fs.existsSync(canvasTemplatePath)) {
const canvasName = decodeURIComponent(encodedCanvasName);
const html = injectCanvasTemplateHtml(
fs.readFileSync(canvasTemplatePath, 'utf8'),
canvasName,
injectScript,
);
await sendHtml(html, { transform: true });
return;
}
}
next();
} catch (error) {
next(error);
}
});
},
};
}