| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- #!/usr/bin/env node
- const fs = require('fs');
- const path = require('path');
- const { spawnSync } = require('child_process');
- const ROOT = path.resolve(__dirname, '..');
- function main() {
- const outputsRoot = path.resolve(arg('--outputs') || arg('--output-root') || path.join(ROOT, 'outputs'));
- const outputDir = path.resolve(arg('--output') || path.join(outputsRoot, 'software-client-latest'));
- const input = arg('--input') || latestManualReviewSample(outputsRoot);
- const csv = path.join(outputDir, 'software-client-list.final.csv');
- const markdown = path.join(outputDir, 'software-client-list.final.md');
- const refreshSummary = path.join(outputDir, 'software-client-refresh-summary.json');
- fs.mkdirSync(outputDir, { recursive: true });
- if (!input) {
- const existingSummary = csv.replace(/\.csv$/i, '.summary.json');
- const reusable = fs.existsSync(csv) && fs.existsSync(markdown) && fs.existsSync(existingSummary);
- const summary = {
- generatedAt: new Date().toISOString(),
- passed: reusable,
- skipped: true,
- reason: 'no_manual_review_sample_found',
- outputsRoot,
- outputDir,
- csv,
- markdown,
- summary: existingSummary
- };
- fs.writeFileSync(refreshSummary, JSON.stringify(summary, null, 2), 'utf8');
- console.log(JSON.stringify({ ...summary, refreshSummary }, null, 2));
- if (!reusable) process.exitCode = 1;
- return;
- }
- runNode(['scripts/export-software-table.js', '--input', input, '--output', csv]);
- runNode(['scripts/export-software-markdown.js', '--input', csv, '--output', markdown]);
- const clientSummaryPath = csv.replace(/\.csv$/i, '.summary.json');
- const clientSummary = readJson(clientSummaryPath);
- const summary = {
- generatedAt: new Date().toISOString(),
- passed: true,
- skipped: false,
- input,
- outputsRoot,
- outputDir,
- csv,
- markdown,
- summary: clientSummaryPath,
- sourceRows: Number(clientSummary.sourceRows || 0),
- outputRows: Number(clientSummary.outputRows || 0),
- sourceDuplicateRemovedCount: Number(clientSummary.sourceDuplicateRemovedCount || 0),
- finalDuplicateGroupCount: Number(clientSummary.finalDuplicateGroupCount || 0),
- duplicateCountMeaning: clientSummary.duplicateCountMeaning || '',
- rankContinuous: Boolean(clientSummary.rankContinuous)
- };
- fs.writeFileSync(refreshSummary, JSON.stringify(summary, null, 2), 'utf8');
- console.log(JSON.stringify({ ...summary, refreshSummary }, null, 2));
- }
- function runNode(args) {
- const result = spawnSync(process.execPath, args, {
- cwd: ROOT,
- encoding: 'utf8',
- shell: false
- });
- if (result.stdout) process.stdout.write(result.stdout);
- if (result.stderr) process.stderr.write(result.stderr);
- if (result.status !== 0) throw new Error(`${args[0]} failed with exit code ${result.status}`);
- }
- function latestManualReviewSample(root) {
- const files = listFiles(root).filter(file => path.basename(file) === 'manual-review-sample.csv');
- const candidates = files
- .filter(isRefreshCandidateSample)
- .map(file => ({
- file,
- sourceRows: manualReviewSampleRowCount(file),
- mtimeMs: fs.statSync(file).mtimeMs
- }))
- .filter(item => item.sourceRows > 0)
- .sort((a, b) => b.sourceRows - a.sourceRows || b.mtimeMs - a.mtimeMs);
- return candidates[0]?.file || '';
- }
- function isRefreshCandidateSample(file) {
- const normalized = file.replace(/\\/g, '/').toLowerCase();
- const parent = path.basename(path.dirname(file));
- return /^overnight-quality-\d+$/.test(parent) && !normalized.includes('smoke');
- }
- function manualReviewSampleRowCount(file) {
- const rows = parseCsv(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
- return rows.slice(1).filter(row => row.some(cell => String(cell || '').trim())).length;
- }
- function listFiles(dir) {
- if (!fs.existsSync(dir)) return [];
- return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
- const full = path.join(dir, entry.name);
- return entry.isDirectory() ? listFiles(full) : [full];
- });
- }
- function readJson(file) {
- return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
- }
- function parseCsv(text) {
- const rows = [];
- let row = [];
- let cell = '';
- let quoted = false;
- for (let index = 0; index < text.length; index += 1) {
- const char = text[index];
- if (char === '\r') continue;
- if (char === '"' && quoted && text[index + 1] === '"') {
- cell += '"';
- index += 1;
- } else if (char === '"') {
- quoted = !quoted;
- } else if (char === ',' && !quoted) {
- row.push(cell);
- cell = '';
- } else if (char === '\n' && !quoted) {
- row.push(cell);
- rows.push(row);
- row = [];
- cell = '';
- } else {
- cell += char;
- }
- }
- if (cell || row.length) {
- row.push(cell);
- rows.push(row);
- }
- return rows;
- }
- function arg(name) {
- const index = process.argv.indexOf(name);
- return index >= 0 ? process.argv[index + 1] : '';
- }
- if (require.main === module) main();
|