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:
207
axhub-make/vite-plugins/virtualHtml/index.ts
Normal file
207
axhub-make/vite-plugins/virtualHtml/index.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import type { Plugin } from 'vite';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { handleHackCssRequest } from './handlers/hackCssHandler';
|
||||
import { handleHackCssSave } from './handlers/hackCssSaveHandler';
|
||||
import { handleHackCssClear } from './handlers/hackCssClearHandler';
|
||||
import { handleEntriesApi } from './handlers/entriesApiHandler';
|
||||
import { handleSpecHtml } from './handlers/specHtmlHandler';
|
||||
import { handleIndexHtml } from './handlers/indexHtmlHandler';
|
||||
import { handleAssetsRequest } from './handlers/assetsHandler';
|
||||
import { handleDocImageAssets } from './handlers/docImageAssetsHandler';
|
||||
import { handleBuildRequest } from './handlers/buildHandler';
|
||||
import { handleDocsMarkdown } from './handlers/docsMarkdownHandler';
|
||||
import { handleTextReplaceCount } from './handlers/textReplaceCountHandler';
|
||||
import { handleTextReplace } from './handlers/textReplaceHandler';
|
||||
import { handlePathRedirect } from './handlers/pathNormalizer';
|
||||
import {
|
||||
createDocUpdatePayload,
|
||||
createHackCssUpdatePayload,
|
||||
createPreviewHostModuleCode,
|
||||
parsePreviewHostModuleId,
|
||||
PREVIEW_HOST_MODULE_PREFIX,
|
||||
} from './previewHost';
|
||||
|
||||
/**
|
||||
* 虚拟 HTML 插件 - 在内存中生成 HTML,不写入文件系统
|
||||
*/
|
||||
export function virtualHtmlPlugin(): Plugin {
|
||||
const devTemplatePath = path.resolve(process.cwd(), 'admin/dev-template.html');
|
||||
const specTemplatePath = path.resolve(process.cwd(), 'admin/spec-template.html');
|
||||
const htmlTemplatePath = path.resolve(process.cwd(), 'admin/html-template.html');
|
||||
let devTemplate: string;
|
||||
let specTemplate: string;
|
||||
let htmlTemplate: string;
|
||||
|
||||
return {
|
||||
name: 'virtual-html',
|
||||
apply: 'serve',
|
||||
|
||||
resolveId(id) {
|
||||
if (id.startsWith(PREVIEW_HOST_MODULE_PREFIX)) {
|
||||
return `\0${id}`;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
load(id) {
|
||||
if (!id.startsWith(`\0${PREVIEW_HOST_MODULE_PREFIX}`)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const options = parsePreviewHostModuleId(id.slice(1));
|
||||
if (!options) {
|
||||
throw new Error(`Invalid preview host module id: ${id}`);
|
||||
}
|
||||
|
||||
return createPreviewHostModuleCode(options);
|
||||
},
|
||||
|
||||
handleHotUpdate(ctx) {
|
||||
const payload = createDocUpdatePayload(ctx.file, 'change');
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.server.ws.send({
|
||||
type: 'custom',
|
||||
event: 'axhub:spec-doc-update',
|
||||
data: payload,
|
||||
});
|
||||
|
||||
// src 下的 markdown 文档不再走 Vite 默认的全局 full-reload,
|
||||
// 只让对应的 spec/doc 页面自行按 URL 维度刷新内容。
|
||||
return [];
|
||||
},
|
||||
|
||||
async configureServer(server) {
|
||||
try {
|
||||
devTemplate = fs.readFileSync(devTemplatePath, 'utf8');
|
||||
} catch (err) {
|
||||
console.error('无法读取 dev-template 模板文件:', devTemplatePath);
|
||||
}
|
||||
|
||||
try {
|
||||
specTemplate = fs.readFileSync(specTemplatePath, 'utf8');
|
||||
} catch (err) {
|
||||
console.error('无法读取 spec-template 模板文件:', specTemplatePath);
|
||||
}
|
||||
|
||||
try {
|
||||
htmlTemplate = fs.readFileSync(htmlTemplatePath, 'utf8');
|
||||
} catch (err) {
|
||||
console.error('无法读取 html-template 模板文件:', htmlTemplatePath);
|
||||
}
|
||||
|
||||
const broadcastHackCssUpdate = (filePath: string, changeType: 'add' | 'change' | 'unlink') => {
|
||||
const payload = createHackCssUpdatePayload(filePath, changeType);
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
|
||||
server.ws.send({
|
||||
type: 'custom',
|
||||
event: 'axhub:hack-css-update',
|
||||
data: payload,
|
||||
});
|
||||
};
|
||||
|
||||
server.watcher.on('add', (filePath) => broadcastHackCssUpdate(filePath, 'add'));
|
||||
server.watcher.on('change', (filePath) => broadcastHackCssUpdate(filePath, 'change'));
|
||||
server.watcher.on('unlink', (filePath) => broadcastHackCssUpdate(filePath, 'unlink'));
|
||||
|
||||
server.middlewares.use(async (req, res, next) => {
|
||||
try {
|
||||
// CORS headers
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.statusCode = 204;
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!req.url) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const respondHtml = async (html: string, transformUrl?: string) => {
|
||||
const htmlUrl = transformUrl || req.url || '/index.html';
|
||||
const transformedHtml = await server.transformIndexHtml(htmlUrl, html, req.originalUrl || req.url || htmlUrl);
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
res.statusCode = 200;
|
||||
res.end(transformedHtml);
|
||||
};
|
||||
|
||||
// 🔥 处理旧路径重定向(必须在最前面)
|
||||
if (handlePathRedirect(req, res)) return;
|
||||
|
||||
// Handle hack.css GET request
|
||||
if (handleHackCssRequest(req, res)) return;
|
||||
|
||||
// Handle hack.css save POST request
|
||||
if (handleHackCssSave(req, res)) return;
|
||||
|
||||
// Handle hack.css clear POST request
|
||||
if (handleHackCssClear(req, res)) return;
|
||||
|
||||
// Handle text replace count POST request
|
||||
if (handleTextReplaceCount(req, res)) return;
|
||||
|
||||
// Handle text replace POST request
|
||||
if (handleTextReplace(req, res)) return;
|
||||
|
||||
// Handle root path
|
||||
if (req.url === '/' || req.url === '/index.html') {
|
||||
const indexHtmlPath = path.resolve(process.cwd(), 'admin/index.html');
|
||||
if (fs.existsSync(indexHtmlPath)) {
|
||||
try {
|
||||
const html = fs.readFileSync(indexHtmlPath, 'utf8');
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.statusCode = 200;
|
||||
res.end(html);
|
||||
return;
|
||||
} catch (err) {
|
||||
console.error('读取 index.html 失败:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle assets
|
||||
if (handleAssetsRequest(req, res)) return;
|
||||
|
||||
// Handle build requests
|
||||
if (handleBuildRequest(req, res)) return;
|
||||
|
||||
// Handle entries API
|
||||
if (handleEntriesApi(req, res)) return;
|
||||
|
||||
// Handle markdown-relative document images (assets/images/*)
|
||||
if (handleDocImageAssets(req, res)) return;
|
||||
|
||||
// Handle docs markdown files
|
||||
if (handleDocsMarkdown(req, res)) return;
|
||||
|
||||
// Handle spec.html
|
||||
if (await handleSpecHtml(req, res, specTemplate, respondHtml)) return;
|
||||
|
||||
// Handle index.html
|
||||
if (req.url?.includes('/themes/') && req.url?.includes('/index.html')) {
|
||||
try {
|
||||
htmlTemplate = fs.readFileSync(htmlTemplatePath, 'utf8');
|
||||
} catch (err) {
|
||||
console.error('无法读取 html-template 模板文件:', htmlTemplatePath);
|
||||
}
|
||||
}
|
||||
if (await handleIndexHtml(req, res, devTemplate, htmlTemplate, respondHtml)) return;
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user