blob: c9bc24b62b1854c06025ac1619624032bf463945 [file]
const fs = require('node:fs');
const path = require('node:path');
const {identity, loadRecords, decide, stateFileFor} = require('./state.cjs');
const {scriptUnits} = require('./code-prose.cjs');
function matchesSource(group, file) {
const suffix = file.startsWith(`${group.sourceRoot}/`) && file.slice(group.sourceRoot.length + 1);
return !!suffix && !suffix.split('/').some(part => part.startsWith('_') || part === '..')
&& (!group.include || group.include.some(prefix => prefix.endsWith('/') ? suffix.startsWith(prefix) : suffix === prefix))
&& (group.kind === 'code' ? /(?<!\.d)\.tsx?$/.test(suffix) && !/\.(test|logic)\./.test(suffix) : /\.mdx?$/.test(suffix));
}
function sourceFiles(root, group) {
const walk = dir => !fs.existsSync(path.join(root, dir)) ? [] : fs.readdirSync(path.join(root, dir), {withFileTypes: true})
.flatMap(entry => entry.isDirectory() ? entry.name.startsWith('_') ? [] : walk(`${dir}/${entry.name}`)
: entry.isFile() && matchesSource(group, `${dir}/${entry.name}`) ? [`${dir}/${entry.name}`] : []);
return walk(group.sourceRoot).sort();
}
function pageFor(group, sourcePath, locale) {
const suffix = sourcePath.slice(group.sourceRoot.length + 1);
const docId = suffix.replace(/\.(mdx?|tsx?)$/, '');
return {pluginId: group.pluginId, version: group.version, docId, locale, sourceLocale: 'en', sourcePath,
englishPath: sourcePath, targetPath: `i18n/${locale}/docusaurus-plugin-content-docs-${group.pluginId}/current/${suffix}`,
kind: group.kind, draft: false, unlisted: true,
permalink: `/${locale}${group.routeBasePath}/#${encodeURIComponent(docId)}`,
englishPermalink: `${group.routeBasePath}/#${encodeURIComponent(docId)}`,
sourcePermalink: `${group.routeBasePath}/#${encodeURIComponent(docId)}`};
}
function contentGroups(root, locale) {
const file = path.join(root, `translations/scope/${locale}.json`);
return fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')).groups.filter(group => group.kind && group.kind!=='blog') : [];
}
function hasProse(root, group, file) {
return group.kind !== 'code' || scriptUnits(fs.readFileSync(path.join(root, file), 'utf8')).length > 0;
}
async function collectSources(root, locale) {
const groups = contentGroups(root, locale);
if (!groups.length) return {pages: [], reports: [], versions: []};
const {structure} = require('./structure.cjs');
const records = loadRecords(root, locale), pages = [], reports = [], versions = [];
for (const group of groups) {
const sources = new Set();
for (const file of sourceFiles(root, group)) {
if (!hasProse(root, group, file)) continue;
const page = pageFor(group, file, locale);
sources.add(identity(page));
const source = fs.readFileSync(path.join(root, file), 'utf8');
const target = fs.existsSync(path.join(root, page.targetPath)) ? fs.readFileSync(path.join(root, page.targetPath), 'utf8') : undefined;
const technicalHash = target === undefined ? undefined : (await structure(source, path.extname(file))).technicalHash;
pages.push({...decide({page, source, target, record: records.get(identity(page)), technicalHash}), indexable: false});
}
for (const record of records.values()) {
if (record.pluginId === group.pluginId && record.version === group.version && !sources.has(identity(record)))
reports.push({kind: 'source_deleted', ...record});
}
versions.push({pluginId: group.pluginId, version: group.version, order: new Map()});
}
return {pages, reports, versions};
}
// Load only this module's shard during Webpack compilation, rather than every
// document record for each course component/report partial.
function readRecord(root, page) {
const shard = stateFileFor(page);
if (fs.existsSync(path.join(root, shard))) return JSON.parse(fs.readFileSync(path.join(root, shard), 'utf8')).page;
const ledger = path.join(root, `translations/state/${page.locale}/docs.json`);
return fs.existsSync(ledger) ? JSON.parse(fs.readFileSync(ledger, 'utf8')).pages.find(record => identity(record) === identity(page)) : undefined;
}
function translatedContentPages(root, locale) {
const groups = contentGroups(root, locale);
const complete = group => {
const files = sourceFiles(root, group).filter(file => hasProse(root, group, file));
return files.length > 0 && files.every(file => {
const page = pageFor(group, file, locale), record = readRecord(root, page);
if (!record || !fs.existsSync(path.join(root, page.targetPath))) return false;
return decide({page, source: fs.readFileSync(path.join(root, file), 'utf8'),
target: fs.readFileSync(path.join(root, page.targetPath), 'utf8'), record}).indexable;
});
};
const ready = new Set(groups.filter(complete).map(group => group.pluginId));
return [...(ready.has('course-ui') ? ['/course', ...['a1-hybrid-search', 'a2-mcp', 'a3-log-search', 'a4-agent-traces', 'track-b']
.map(task => `/course/hackathon/${task}`)] : []),
...(ready.has('community-report') && ready.has('community-report-ui') ? ['/community-report'] : [])];
}
module.exports = {matchesSource, sourceFiles, pageFor, contentGroups, hasProse, collectSources, readRecord, translatedContentPages};