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>
149 lines
5.3 KiB
TypeScript
149 lines
5.3 KiB
TypeScript
import type { IncomingMessage, ServerResponse } from 'http';
|
||
import fs from 'fs';
|
||
import path from 'path';
|
||
import { encodeRoutePath, normalizePath } from './pathNormalizer';
|
||
import { logVirtualHtmlDebug } from '../logger';
|
||
import { buildDocApiPath } from '../../utils/docUtils';
|
||
|
||
type HtmlResponder = (html: string, transformUrl?: string) => Promise<void>;
|
||
|
||
export async function handleSpecHtml(
|
||
req: IncomingMessage,
|
||
res: ServerResponse,
|
||
specTemplate: string,
|
||
respondHtml: HtmlResponder,
|
||
): Promise<boolean> {
|
||
if (!req.url) return false;
|
||
|
||
const rawPathname = req.url.split('?')[0];
|
||
if (rawPathname.startsWith('/docs/') && rawPathname.includes('/assets/')) {
|
||
return false;
|
||
}
|
||
|
||
// 先尝试标准化路径
|
||
const normalized = normalizePath(req.url);
|
||
|
||
// 只处理文档请求(action === 'spec')
|
||
if (normalized && normalized.action === 'spec') {
|
||
const { type, name, versionId } = normalized;
|
||
|
||
logVirtualHtmlDebug('文档请求:', normalized.originalUrl, '→', normalized.normalizedUrl);
|
||
|
||
// 处理 prototypes/components/themes 的 spec 请求
|
||
if (['components', 'prototypes', 'themes'].includes(type)) {
|
||
let basePath: string;
|
||
let specMdPath: string;
|
||
let prdMdPath: string;
|
||
|
||
// 如果有版本参数,从 Git 版本目录读取
|
||
if (versionId) {
|
||
const gitVersionsDir = path.resolve(process.cwd(), '.git-versions', versionId);
|
||
basePath = path.join(gitVersionsDir, 'src', type, name);
|
||
specMdPath = path.join(basePath, 'spec.md');
|
||
prdMdPath = path.join(basePath, 'prd.md');
|
||
logVirtualHtmlDebug('从 Git 版本读取:', versionId, basePath);
|
||
} else {
|
||
// 否则从当前工作目录读取
|
||
basePath = path.join(process.cwd(), 'src', type, name);
|
||
specMdPath = path.join(basePath, 'spec.md');
|
||
prdMdPath = path.join(basePath, 'prd.md');
|
||
}
|
||
|
||
const typeLabel = type === 'components' ? 'Component' : type === 'prototypes' ? 'Prototype' : 'Theme';
|
||
|
||
logVirtualHtmlDebug('检查文档文件:', { specMdPath, prdMdPath });
|
||
logVirtualHtmlDebug('文件存在:', {
|
||
spec: fs.existsSync(specMdPath),
|
||
prd: fs.existsSync(prdMdPath)
|
||
});
|
||
|
||
// 收集所有存在的文档
|
||
const docs: Array<{ key: string; label: string; url: string }> = [];
|
||
const urlPath = encodeRoutePath(`/${type}/${name}`);
|
||
|
||
if (fs.existsSync(specMdPath)) {
|
||
const docUrl = versionId
|
||
? `/api/git/version-file/${versionId}${urlPath}/spec.md`
|
||
: `${urlPath}/spec.md`;
|
||
docs.push({
|
||
key: 'spec',
|
||
label: 'Spec',
|
||
url: docUrl
|
||
});
|
||
}
|
||
|
||
if (fs.existsSync(prdMdPath)) {
|
||
const docUrl = versionId
|
||
? `/api/git/version-file/${versionId}${urlPath}/prd.md`
|
||
: `${urlPath}/prd.md`;
|
||
docs.push({
|
||
key: 'prd',
|
||
label: 'PRD',
|
||
url: docUrl
|
||
});
|
||
}
|
||
|
||
if (docs.length > 0) {
|
||
const title = versionId
|
||
? `${typeLabel}: ${name} (版本: ${versionId})`
|
||
: `${typeLabel}: ${name}`;
|
||
const isMultiDoc = docs.length > 1;
|
||
const transformUrl = versionId
|
||
? `${urlPath}/__axhub_version__/${versionId}/spec.html`
|
||
: `${urlPath}/spec.html`;
|
||
|
||
// 使用 spec-template.html 模板
|
||
let html = specTemplate.replace(/\{\{TITLE\}\}/g, title);
|
||
|
||
if (isMultiDoc) {
|
||
// 多文档模式
|
||
const docsConfig = JSON.stringify(docs);
|
||
html = html.replace(/\{\{SPEC_URL\}\}/g, '');
|
||
html = html.replace(/\{\{DOCS_CONFIG\}\}/g, docsConfig.replace(/"/g, '"'));
|
||
html = html.replace(/\{\{MULTI_DOC\}\}/g, 'true');
|
||
logVirtualHtmlDebug('返回多文档 Spec 虚拟 HTML:', normalized.normalizedUrl, '文档数:', docs.length);
|
||
} else {
|
||
// 单文档模式
|
||
html = html.replace(/\{\{SPEC_URL\}\}/g, docs[0].url);
|
||
html = html.replace(/\{\{DOCS_CONFIG\}\}/g, '[]');
|
||
html = html.replace(/\{\{MULTI_DOC\}\}/g, 'false');
|
||
logVirtualHtmlDebug('返回单文档 Spec 虚拟 HTML:', normalized.normalizedUrl);
|
||
}
|
||
|
||
await respondHtml(html, transformUrl);
|
||
return true;
|
||
} else {
|
||
logVirtualHtmlDebug('没有找到任何文档文件');
|
||
}
|
||
}
|
||
|
||
// 处理 /docs/* 的 spec 请求
|
||
if (type === 'docs') {
|
||
const decodedDocName = decodeURIComponent(name);
|
||
const mdPath = path.resolve(process.cwd(), 'src/docs', decodedDocName + '.md');
|
||
|
||
logVirtualHtmlDebug('检查 docs markdown 文件:', mdPath, '存在:', fs.existsSync(mdPath));
|
||
|
||
if (fs.existsSync(mdPath)) {
|
||
const title = `Docs: ${decodedDocName || 'Index'}`;
|
||
const specMdUrl = buildDocApiPath(`${decodedDocName}.md`);
|
||
|
||
let html = specTemplate.replace(/\{\{TITLE\}\}/g, title);
|
||
html = html.replace(/\{\{SPEC_URL\}\}/g, specMdUrl);
|
||
html = html.replace(/\{\{DOCS_CONFIG\}\}/g, '[]');
|
||
html = html.replace(/\{\{MULTI_DOC\}\}/g, 'false');
|
||
|
||
logVirtualHtmlDebug('返回 Docs 虚拟 HTML:', normalized.normalizedUrl);
|
||
|
||
const docsTransformUrl = `${encodeRoutePath(`/docs/${decodedDocName}`)}/spec.html`;
|
||
await respondHtml(html, docsTransformUrl);
|
||
return true;
|
||
} else {
|
||
logVirtualHtmlDebug('docs markdown 不存在:', mdPath);
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|