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,159 @@
import type { Plugin } from 'vite';
import fs from 'fs';
import path from 'path';
import { getRequestPathname, readJsonBody } from './utils/httpUtils';
export interface SubPageItem {
name: string;
path: string;
}
interface PagesJson {
pages: SubPageItem[];
}
function getPagesJsonPath(prototypeName: string): string {
const projectRoot = process.cwd();
return path.resolve(projectRoot, 'src', 'prototypes', prototypeName, 'pages.json');
}
function normalizeSubPageName(value: unknown): string {
return typeof value === 'string' ? value.trim() : '';
}
function normalizeSubPagePath(value: unknown): string {
if (typeof value !== 'string') {
return '';
}
return value
.trim()
.replace(/^\/+/, '')
.replace(/\/+$/, '')
.replace(/\/{2,}/g, '/');
}
function readPagesJson(prototypeName: string): PagesJson {
const filePath = getPagesJsonPath(prototypeName);
if (!fs.existsSync(filePath)) {
return { pages: [] };
}
try {
const raw = fs.readFileSync(filePath, 'utf8');
const data = JSON.parse(raw);
if (Array.isArray(data.pages)) {
const seenPaths = new Set<string>();
const pages = data.pages
.map((page) => ({
name: normalizeSubPageName(page?.name),
path: normalizeSubPagePath(page?.path),
}))
.filter((page) => {
if (!page.name || !page.path || seenPaths.has(page.path)) {
return false;
}
seenPaths.add(page.path);
return true;
});
return { pages };
}
return { pages: [] };
} catch {
return { pages: [] };
}
}
function writePagesJson(prototypeName: string, data: PagesJson): void {
const filePath = getPagesJsonPath(prototypeName);
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
throw new Error(`原型目录不存在: ${prototypeName}`);
}
if (!data.pages.length) {
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
return;
}
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
}
export function subPagesApiPlugin(): Plugin {
return {
name: 'sub-pages-api-plugin',
configureServer(server: any) {
server.middlewares.use(async (req: any, res: any, next: any) => {
const pathname = getRequestPathname(req);
if (pathname !== '/api/sub-pages') {
return next();
}
const url = new URL(req.url || '/', `http://${req.headers.host}`);
const prototypeName = url.searchParams.get('prototype');
if (!prototypeName) {
res.statusCode = 400;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ error: '缺少 prototype 参数' }));
return;
}
// Validate prototype name to prevent path traversal
if (/[/\\]|\.\./.test(prototypeName)) {
res.statusCode = 400;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ error: '无效的原型名称' }));
return;
}
try {
if (req.method === 'GET') {
const data = readPagesJson(prototypeName);
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Cache-Control', 'no-store');
res.end(JSON.stringify(data));
return;
}
if (req.method === 'PUT') {
const body = await readJsonBody(req);
if (!body || !Array.isArray(body.pages)) {
res.statusCode = 400;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ error: '请求体必须包含 pages 数组' }));
return;
}
const seenPaths = new Set<string>();
const pages: SubPageItem[] = body.pages
.map((page: any) => ({
name: normalizeSubPageName(page?.name),
path: normalizeSubPagePath(page?.path),
}))
.filter((page: SubPageItem) => {
if (!page.name || !page.path || seenPaths.has(page.path)) {
return false;
}
seenPaths.add(page.path);
return true;
});
writePagesJson(prototypeName, { pages });
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ success: true, pages }));
return;
}
res.statusCode = 405;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ error: 'Method Not Allowed' }));
} catch (error: any) {
res.statusCode = 500;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ error: error?.message || '未知错误' }));
}
});
},
};
}