Skip to content

Commit 28a2737

Browse files
committed
Optimize docs precompile metadata scan
1 parent 149c604 commit 28a2737

1 file changed

Lines changed: 84 additions & 49 deletions

File tree

scripts/precompile-docs.ts

Lines changed: 84 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919

2020
import { readdirSync, readFileSync, writeFileSync, statSync, existsSync, mkdirSync } from 'fs';
21-
import { join, dirname } from 'path';
21+
import { join, dirname, relative } from 'path';
2222
import { fileURLToPath } from 'url';
2323
import { execSync } from 'child_process';
2424

@@ -33,6 +33,7 @@ const CONFIG_PATH = join(ROOT_DIR, 'src', 'data', 'projects.json');
3333

3434
// Canonical public URL (override via SITE_URL env var for staging builds).
3535
const SITE_URL = (process.env.SITE_URL || 'https://docs.mevera.studio').replace(/\/$/, '');
36+
const VERBOSE_DOCS = process.env.PRECOMPILE_VERBOSE === '1';
3637

3738
type YamlValue = string | number | boolean;
3839

@@ -109,6 +110,67 @@ function contributorKey(name: string, email: string): string {
109110
return `name:${name.trim().toLowerCase()}`;
110111
}
111112

113+
function collectGitDocMetadata(): Map<string, GitDocMetadata> {
114+
const metadataByPath = new Map<string, GitDocMetadata>();
115+
const contributorKeysByPath = new Map<string, Set<string>>();
116+
117+
try {
118+
const log = execSync('git log --format="commit:%H%x1f%aI%x1f%an%x1f%ae" --name-only -- docs', {
119+
cwd: ROOT_DIR,
120+
encoding: 'utf-8',
121+
maxBuffer: 1024 * 1024 * 50,
122+
stdio: ['pipe', 'pipe', 'ignore'],
123+
});
124+
125+
let currentCommit: { date: string; name: string; email: string } | null = null;
126+
127+
for (const rawLine of log.split(/\r?\n/)) {
128+
const line = rawLine.trim();
129+
if (!line) continue;
130+
131+
if (line.startsWith('commit:')) {
132+
const [, date, name, email] = line.slice('commit:'.length).split('\x1f');
133+
currentCommit = { date, name, email };
134+
continue;
135+
}
136+
137+
if (!currentCommit || !/\.(md|mdx)$/i.test(line)) continue;
138+
139+
const metadata = metadataByPath.get(line) || {};
140+
if (!metadata.lastUpdatedAt) {
141+
metadata.lastUpdatedAt = currentCommit.date;
142+
}
143+
144+
const key = contributorKey(currentCommit.name, currentCommit.email);
145+
let contributorKeys = contributorKeysByPath.get(line);
146+
if (!contributorKeys) {
147+
contributorKeys = new Set<string>();
148+
contributorKeysByPath.set(line, contributorKeys);
149+
}
150+
151+
if (!contributorKeys.has(key)) {
152+
contributorKeys.add(key);
153+
const contributors = metadata.contributors || [];
154+
if (contributors.length < 10) {
155+
const githubUsername = githubUsernameFromEmail(currentCommit.email);
156+
contributors.push({
157+
name: currentCommit.name,
158+
email: currentCommit.email,
159+
avatar: githubUsername ? `https://github.com/${githubUsername}.png?size=64` : undefined,
160+
});
161+
metadata.contributors = contributors;
162+
}
163+
}
164+
165+
metadataByPath.set(line, metadata);
166+
}
167+
} catch {
168+
// Git metadata is best-effort. Builds outside a git checkout still work.
169+
}
170+
171+
return metadataByPath;
172+
}
173+
112174
interface DocCategory {
113175
name: string;
114176
docs: DocFile[];
@@ -192,6 +254,15 @@ interface SearchIndexItem {
192254
category: string;
193255
}
194256

257+
interface GitDocMetadata {
258+
lastUpdatedAt?: string;
259+
contributors?: DocContributor[];
260+
}
261+
262+
function toGitPath(path: string): string {
263+
return relative(ROOT_DIR, path).replace(/\\/g, '/');
264+
}
265+
195266
/**
196267
* Parse YAML-like content (simplified parser)
197268
*/
@@ -424,6 +495,7 @@ function buildVersion(
424495
projectName: string,
425496
versionDir: string,
426497
versionMeta: { id: string; label: string; latest: boolean },
498+
gitMetadataByPath: Map<string, GitDocMetadata>,
427499
tocMap: Record<string, TocItem[]>,
428500
searchIndex: SearchIndexItem[],
429501
indexLatestOnlyForSearch: boolean
@@ -487,51 +559,7 @@ function buildVersion(
487559
categoryName = frontmatter.category;
488560
}
489561

490-
// Get last updated date from git
491-
let lastUpdatedAt: string | undefined = undefined;
492-
try {
493-
const stdout = execSync(`git log -1 --format="%aI" -- "${fullPath}"`, {
494-
encoding: 'utf-8',
495-
stdio: ['pipe', 'pipe', 'ignore']
496-
}).trim();
497-
498-
if (stdout) {
499-
lastUpdatedAt = stdout;
500-
}
501-
} catch {
502-
// Ignore errors (e.g., file not tracked by git yet)
503-
}
504-
505-
// Collect unique contributors from git log.
506-
let contributors: DocContributor[] = [];
507-
try {
508-
const log = execSync(`git log --format="%an|%ae" -- "${fullPath}"`, {
509-
encoding: 'utf-8',
510-
stdio: ['pipe', 'pipe', 'ignore']
511-
}).trim();
512-
513-
const seen = new Set<string>();
514-
for (const line of log.split('\n')) {
515-
if (!line) continue;
516-
const pipeIdx = line.indexOf('|');
517-
if (pipeIdx < 0) continue;
518-
const name = line.slice(0, pipeIdx).trim();
519-
const email = line.slice(pipeIdx + 1).trim();
520-
const key = contributorKey(name, email);
521-
if (seen.has(key)) continue;
522-
seen.add(key);
523-
524-
let avatar: string | undefined;
525-
const githubUsername = githubUsernameFromEmail(email);
526-
if (githubUsername) {
527-
avatar = `https://github.com/${githubUsername}.png?size=64`;
528-
}
529-
contributors.push({ name, email, avatar });
530-
}
531-
contributors = contributors.slice(0, 10);
532-
} catch {
533-
// ignore
534-
}
562+
const gitMetadata = gitMetadataByPath.get(toGitPath(fullPath));
535563

536564
const docFile: DocFile = {
537565
slug,
@@ -542,8 +570,8 @@ function buildVersion(
542570
version: versionMeta.id,
543571
category: categoryName,
544572
extension,
545-
lastUpdatedAt,
546-
contributors: contributors.length > 0 ? contributors : undefined,
573+
lastUpdatedAt: gitMetadata?.lastUpdatedAt,
574+
contributors: gitMetadata?.contributors,
547575
};
548576

549577
allDocs.push(docFile);
@@ -566,7 +594,9 @@ function buildVersion(
566594
});
567595
}
568596

569-
console.log(` ✓ ${versionMeta.id}/${slug}${extension}`);
597+
if (VERBOSE_DOCS) {
598+
console.log(` ✓ ${versionMeta.id}/${slug}${extension}`);
599+
}
570600
});
571601

572602
// Group docs into categories.
@@ -620,6 +650,7 @@ function precompileDocs() {
620650
const projectsMap = new Map<string, DocProject>();
621651
const searchIndex: SearchIndexItem[] = [];
622652
const tocMap: Record<string, TocItem[]> = {};
653+
const gitMetadataByPath = collectGitDocMetadata();
623654

624655
// Process each declared project.
625656
projectsConfig.forEach((projectCfg) => {
@@ -644,12 +675,16 @@ function precompileDocs() {
644675
projectCfg.title,
645676
join(projectDir, v.id),
646677
v,
678+
gitMetadataByPath,
647679
tocMap,
648680
searchIndex,
649681
/* indexLatestOnlyForSearch = */ true,
650682
)
651683
);
652684

685+
const projectDocCount = versions.reduce((sum, version) => sum + version.allDocs.length, 0);
686+
console.log(` ✓ ${projectDocCount} docs across ${versions.length} version(s)`);
687+
653688
projectsMap.set(projectCfg.id, {
654689
id: projectCfg.id,
655690
name: projectCfg.title,

0 commit comments

Comments
 (0)