blob: b4db07e5bda7bfa82a5ad9ba388d4cbcc6c92dcc [file]
'use strict';
// Generates scripts/last-update/data.json: a { "<repo-relative md path>":
// "<ISO commit date>" } map of every built document's last update time,
// derived from this repo's git history.
//
// docusaurus.config.js reads this map at build time (markdown.parseFrontMatter)
// and injects it as each document's `last_update` front matter, so the regular
// 2-hourly deploy can render "last updated" without a full-history clone or a
// per-file git lookup. Refreshed on demand by the "Refresh Docs Last-Update
// Map" workflow (.github/workflows/refresh-docs-last-update.yml).
//
// Requires full git history to be accurate. In a shallow clone every file
// resolves to the single HEAD commit, so run this only where the history is
// available (locally, or the workflow's fetch-depth: 0 checkout).
const { execFileSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const REPO_ROOT = path.resolve(__dirname, '../..');
const OUTPUT = path.join(__dirname, 'data.json');
// Versions the site actually builds: docs/ (the "current"/Dev version) plus
// the versions listed in versions.json. Other version-* dirs on disk (e.g.
// version-1.2) are not built, so we don't spend map entries on them.
const VERSIONS = require(path.join(REPO_ROOT, 'versions.json'));
// Repo-relative directories whose Markdown we timestamp. These paths match
// what path.relative(siteDir, absFilePath) yields at build time, so the map
// keys line up with parseFrontMatter's lookup. zh-CN translations carry their
// own git history, hence their own (correct) timestamps.
function targetDirs() {
const dirs = ['docs'];
for (const v of VERSIONS) dirs.push(`versioned_docs/version-${v}`);
dirs.push('community');
dirs.push('i18n/zh-CN/docusaurus-plugin-content-docs/current');
for (const v of VERSIONS) {
dirs.push(`i18n/zh-CN/docusaurus-plugin-content-docs/version-${v}`);
}
dirs.push('i18n/zh-CN/docusaurus-plugin-content-docs-community/current');
return dirs.filter((d) => fs.existsSync(path.join(REPO_ROOT, d)));
}
// One pass over history: `git log` is newest-first, so the first date we see
// for a path is its most recent commit. Merges are skipped (Doris lands PRs as
// squashed single commits; --name-only shows nothing for merges anyway).
function buildHistoryMap(dirs) {
const out = execFileSync(
'git',
[
'-c', 'core.quotePath=false',
'log', '--no-merges', '--name-only', '--format=__C__%cI',
'--', ...dirs,
],
{ cwd: REPO_ROOT, encoding: 'utf8', maxBuffer: 512 * 1024 * 1024 },
);
const map = new Map();
let date = null;
for (const line of out.split('\n')) {
if (line.startsWith('__C__')) {
date = line.slice(5);
} else if (line && date && !map.has(line)) {
map.set(line, date);
}
}
return map;
}
function walkMarkdown(relDir) {
const files = [];
for (const entry of fs.readdirSync(path.join(REPO_ROOT, relDir), { withFileTypes: true })) {
const relPath = `${relDir}/${entry.name}`;
if (entry.isDirectory()) {
files.push(...walkMarkdown(relPath));
} else if (/\.mdx?$/.test(entry.name)) {
files.push(relPath);
}
}
return files;
}
// Per-file dates need the full history; a shallow clone collapses every file
// to the single HEAD commit. Fail loud rather than write a misleading map.
function assertFullHistory() {
let shallow;
try {
shallow = execFileSync('git', ['rev-parse', '--is-shallow-repository'], {
cwd: REPO_ROOT, encoding: 'utf8',
}).trim();
} catch (e) {
console.error('last-update: not a git repository, or git is not installed.');
process.exit(1);
}
if (shallow === 'true') {
console.error(
'last-update: shallow clone detected — every file would resolve to '
+ 'the latest commit.\nRun `git fetch --unshallow` and retry.',
);
process.exit(1);
}
}
function main() {
assertFullHistory();
const dirs = targetDirs();
const history = buildHistoryMap(dirs);
const result = {};
let missing = 0;
for (const dir of dirs) {
for (const file of walkMarkdown(dir)) {
const date = history.get(file);
if (date) result[file] = date;
else missing += 1;
}
}
// Sorted keys keep the committed JSON stable so each refresh produces a
// minimal diff (only the documents whose dates actually changed).
const sorted = {};
for (const key of Object.keys(result).sort()) sorted[key] = result[key];
fs.writeFileSync(OUTPUT, `${JSON.stringify(sorted, null, 2)}\n`, 'utf8');
console.log(
`last-update: wrote ${Object.keys(sorted).length} entries to `
+ `${path.relative(REPO_ROOT, OUTPUT)}`
+ (missing ? ` (${missing} files had no git history, skipped)` : ''),
);
}
main();