126 lines
3.8 KiB
JavaScript
126 lines
3.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Convert downloaded issue table HTML files into web/data/issues/ markdown files.
|
|
*
|
|
* Usage:
|
|
* node reference/process_issues.mjs
|
|
*
|
|
* After this script, run:
|
|
* python scripts/update_products_from_issues.py
|
|
*/
|
|
|
|
import { JSDOM } from 'jsdom';
|
|
import { readFileSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
|
|
import { join, basename } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
const REPO_ROOT = join(__dirname, '..');
|
|
|
|
// Set up DOMParser global before importing modules that rely on it.
|
|
const { window } = new JSDOM('<!doctype html><html><body></body></html>');
|
|
globalThis.DOMParser = window.DOMParser;
|
|
|
|
const { parseIssuesFromHtmlTable } = await import(
|
|
new URL('../web/js/process.js', import.meta.url).href
|
|
);
|
|
const { buildIssueMarkdownDocument } = await import(
|
|
new URL('../web/js/markdown.js', import.meta.url).href
|
|
);
|
|
|
|
const OUTPUT_ROOT = join(REPO_ROOT, 'web', 'data', 'issues');
|
|
|
|
function readHtmlFiles(dirPath) {
|
|
try {
|
|
return readdirSync(dirPath).filter(name => name.endsWith('.html')).sort();
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function getProductNames() {
|
|
return readdirSync(__dirname, { withFileTypes: true })
|
|
.filter(entry => entry.isDirectory())
|
|
.map(entry => entry.name)
|
|
.filter(name => name !== '.' && name !== '..')
|
|
.sort();
|
|
}
|
|
|
|
function getInputEntries(productName) {
|
|
const productRoot = join(__dirname, productName);
|
|
const entries = [];
|
|
for (const issueType of ['addressed', 'known']) {
|
|
const issueTypeDir = join(productRoot, issueType);
|
|
const files = readHtmlFiles(issueTypeDir);
|
|
for (const file of files) {
|
|
const version = basename(file, '.html');
|
|
entries.push({
|
|
filePath: join(issueTypeDir, file),
|
|
version,
|
|
issueType,
|
|
});
|
|
}
|
|
}
|
|
return entries.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
}
|
|
|
|
let writtenCount = 0;
|
|
let skippedNoIssues = 0;
|
|
let processedProducts = 0;
|
|
|
|
const allEntries = getProductNames().flatMap(product =>
|
|
getInputEntries(product).map(entry => ({ ...entry, product }))
|
|
);
|
|
const totalEntries = allEntries.length;
|
|
let processedEntries = 0;
|
|
const seenProducts = new Set();
|
|
|
|
for (const entry of allEntries) {
|
|
const product = entry.product;
|
|
if (!seenProducts.has(product)) {
|
|
seenProducts.add(product);
|
|
processedProducts++;
|
|
}
|
|
|
|
const { filePath, issueType, version } = entry;
|
|
|
|
const capitalizedType = issueType.charAt(0).toUpperCase() + issueType.slice(1);
|
|
const html = readFileSync(filePath, 'utf-8');
|
|
|
|
const parsedIssues = parseIssuesFromHtmlTable(html, { type: capitalizedType });
|
|
processedEntries++;
|
|
if (parsedIssues.length === 0) {
|
|
console.warn(`[${processedEntries}/${totalEntries}] No issues parsed from ${basename(filePath)} — skipping`);
|
|
skippedNoIssues++;
|
|
continue;
|
|
}
|
|
|
|
const markdown = buildIssueMarkdownDocument({
|
|
type: capitalizedType,
|
|
product,
|
|
version,
|
|
issues: parsedIssues,
|
|
});
|
|
|
|
const outDir = join(OUTPUT_ROOT, product, issueType);
|
|
mkdirSync(outDir, { recursive: true });
|
|
|
|
const outFile = join(outDir, `${version}.md`);
|
|
writeFileSync(outFile, markdown, 'utf-8');
|
|
writtenCount++;
|
|
console.log(`[${processedEntries}/${totalEntries}] ${product} ${version} (${issueType})`);
|
|
}
|
|
|
|
if (processedProducts === 0) {
|
|
console.error(`No HTML files found under ${__dirname}.`);
|
|
process.exit(0);
|
|
}
|
|
|
|
console.log(`Wrote ${writtenCount} markdown file(s) across ${processedProducts} product(s).`);
|
|
if (skippedNoIssues > 0) {
|
|
console.log(`Skipped ${skippedNoIssues} file(s) with no parsed issues.`);
|
|
}
|
|
if (writtenCount > 0) {
|
|
console.log('Next step: python scripts/update_products_from_issues.py');
|
|
}
|