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,284 @@
import type { Plugin } from 'vite';
import fs from 'fs';
import path from 'path';
import formidable from 'formidable';
import { getRequestPathname } from './utils/httpUtils';
import {
resolveUniqueMarkdownPath,
sanitizeImportFileBaseName,
} from './utils/docUtils';
import {
convertFileToMarkdownWithMarkitdown,
DOC_IMPORT_MAX_FILE_COUNT,
DOC_IMPORT_MAX_FILE_SIZE,
DOC_IMPORT_MAX_TOTAL_SIZE,
DOC_IMPORT_SUPPORTED_EXTENSIONS,
resolveMarkitdownCommand,
} from './utils/markitdown';
export function docsImportApiPlugin(): Plugin {
return {
name: 'docs-import-api-plugin',
configureServer(server: any) {
server.middlewares.use((req: any, res: any, next: any) => {
const pathname = getRequestPathname(req);
const projectRoot = process.cwd();
const docsDir = path.resolve(projectRoot, 'src/docs');
if (pathname === '/api/docs/import/markitdown-status') {
if (req.method !== 'GET') {
res.statusCode = 405;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ error: 'Method not allowed' }));
return;
}
const resolved = resolveMarkitdownCommand();
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({
installed: resolved.installed,
commandSource: resolved.commandSource,
version: resolved.version,
installHints: resolved.installHints,
error: resolved.error,
}));
return;
}
if (pathname !== '/api/docs/import' && pathname !== '/api/docs/import/') {
return next();
}
if (req.method !== 'POST') {
res.statusCode = 405;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ error: 'Method not allowed' }));
return;
}
const uploadDir = path.resolve(projectRoot, 'temp', 'docs-import');
fs.mkdirSync(uploadDir, { recursive: true });
fs.mkdirSync(docsDir, { recursive: true });
const form = formidable({
uploadDir,
keepExtensions: true,
multiples: true,
maxFileSize: DOC_IMPORT_MAX_FILE_SIZE,
});
form.parse(req, async (error: any, _fields: any, files: any) => {
if (error) {
res.statusCode = 400;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ error: error?.message || 'Failed to parse upload payload' }));
return;
}
const normalizeFiles = (value: any): any[] => {
if (!value) return [];
return Array.isArray(value) ? value : [value];
};
const uploadedFiles = normalizeFiles(files?.files).length > 0
? normalizeFiles(files?.files)
: normalizeFiles(files?.file);
if (uploadedFiles.length === 0) {
res.statusCode = 400;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ error: 'Missing files' }));
return;
}
if (uploadedFiles.length > DOC_IMPORT_MAX_FILE_COUNT) {
res.statusCode = 413;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({
error: `Too many files. Maximum ${DOC_IMPORT_MAX_FILE_COUNT} files per import.`,
}));
return;
}
const totalSize = uploadedFiles.reduce(
(sum, file) => sum + Number(file?.size || 0),
0,
);
if (totalSize > DOC_IMPORT_MAX_TOTAL_SIZE) {
res.statusCode = 413;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({
error: `Total payload too large. Maximum ${Math.floor(DOC_IMPORT_MAX_TOTAL_SIZE / 1024 / 1024)}MB.`,
}));
return;
}
const markitdown = resolveMarkitdownCommand();
const results: Array<{
originalName: string;
extension: string;
success: boolean;
mode: 'direct-md' | 'markitdown';
savedName?: string;
savedPath?: string;
error?: string;
}> = [];
for (const file of uploadedFiles) {
const tempPath = String(file?.filepath || file?.path || '').trim();
const originalName = String(
file?.originalFilename || file?.name || file?.newFilename || 'unnamed-file',
).trim();
const extension = path.extname(originalName || tempPath).toLowerCase();
const safeOriginalName = originalName || path.basename(tempPath) || 'unnamed-file';
const cleanupTempFile = () => {
if (!tempPath) return;
try {
if (fs.existsSync(tempPath)) {
fs.unlinkSync(tempPath);
}
} catch {
// Ignore cleanup errors.
}
};
if (!tempPath || !fs.existsSync(tempPath)) {
results.push({
originalName: safeOriginalName,
extension,
success: false,
mode: extension === '.md' ? 'direct-md' : 'markitdown',
error: 'Uploaded file is missing from temporary storage',
});
cleanupTempFile();
continue;
}
if (!DOC_IMPORT_SUPPORTED_EXTENSIONS.has(extension)) {
results.push({
originalName: safeOriginalName,
extension,
success: false,
mode: extension === '.md' ? 'direct-md' : 'markitdown',
error: `Unsupported file extension: ${extension || 'unknown'}`,
});
cleanupTempFile();
continue;
}
const baseName = sanitizeImportFileBaseName(safeOriginalName)
|| `doc-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
const target = resolveUniqueMarkdownPath(docsDir, baseName);
if (!target.absolutePath.startsWith(docsDir)) {
results.push({
originalName: safeOriginalName,
extension,
success: false,
mode: extension === '.md' ? 'direct-md' : 'markitdown',
error: 'Forbidden target path',
});
cleanupTempFile();
continue;
}
if (extension === '.md') {
try {
fs.copyFileSync(tempPath, target.absolutePath);
results.push({
originalName: safeOriginalName,
extension,
success: true,
mode: 'direct-md',
savedName: target.fileName,
savedPath: `src/docs/${target.fileName}`,
});
} catch (copyError: any) {
results.push({
originalName: safeOriginalName,
extension,
success: false,
mode: 'direct-md',
error: copyError?.message || 'Failed to save markdown file',
});
} finally {
cleanupTempFile();
}
continue;
}
if (!markitdown.installed || !markitdown.command || !markitdown.args) {
results.push({
originalName: safeOriginalName,
extension,
success: false,
mode: 'markitdown',
error: markitdown.error || 'markitdown is not installed. Only .md files can be imported now.',
});
cleanupTempFile();
continue;
}
const conversion = convertFileToMarkdownWithMarkitdown({
command: markitdown.command,
args: markitdown.args,
sourcePath: tempPath,
});
if (!conversion.success) {
results.push({
originalName: safeOriginalName,
extension,
success: false,
mode: 'markitdown',
error: conversion.error,
});
cleanupTempFile();
continue;
}
try {
fs.writeFileSync(target.absolutePath, conversion.content, 'utf8');
results.push({
originalName: safeOriginalName,
extension,
success: true,
mode: 'markitdown',
savedName: target.fileName,
savedPath: `src/docs/${target.fileName}`,
});
} catch (writeError: any) {
results.push({
originalName: safeOriginalName,
extension,
success: false,
mode: 'markitdown',
error: writeError?.message || 'Failed to write converted markdown',
});
} finally {
cleanupTempFile();
}
}
const successCount = results.filter((item) => item.success).length;
const failedCount = results.length - successCount;
const hasSuccess = successCount > 0;
res.statusCode = hasSuccess ? 200 : 400;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({
success: failedCount === 0,
successCount,
failedCount,
commandSource: markitdown.commandSource,
markitdownInstalled: markitdown.installed,
markitdownError: markitdown.error,
results,
}));
});
});
},
};
}