Matins completed

This commit is contained in:
catarrh committed 2026-08-01 02:43:24 +03:00
1 parent 79ac617aa8
commit 366aea76bb
27 files changed
+3200

No files matched your search

+116
View File
@@ -0,0 +1,116 @@
/**
* Render-check QA: headlessly render every flat-data service and
* collection section through assembleService, verifying:
* - HTML output is non-empty
* - HEADER nodes actually render in the output (HEADER fix check)
* - No exceptions are thrown
* Run with: npx tsx scripts/render-check.ts
*/
import { readFileSync, existsSync, readdirSync } from 'fs';
import { join } from 'path';
import { fileURLToPath } from 'url';
import { assembleService, type ServiceContext } from '../src/core/service-assembler';
import type { EvalContext, ServiceNode } from '../src/core/types';
const __dirname = new URL('.', import.meta.url).pathname;
const SERVICES = join(__dirname, '..', 'static', 'data', 'shared', 'services');
interface ServiceNodeT { type: string; value?: string }
let errors = 0;
let warnings = 0;
let checked = 0;
function err(msg: string) { console.error(` ERROR: ${msg}`); errors++; }
function warn(msg: string) { console.warn(` WARN: ${msg}`); warnings++; }
async function render(nodes: ServiceNode[], label: string): Promise<void> {
const evalCtx: EvalContext = {};
const ctx: ServiceContext = {
evalCtx,
lang: 'cu',
fetchBibleText: async () => '',
fetchLives: async () => null,
fetchCommandText: async () => '',
resolveTimes: async () => '',
fetchText: async () => null,
fetchPrayerNodes: async () => null,
fetchServiceNodes: async () => null,
};
try {
const { html } = await assembleService(nodes, ctx);
if (!html.trim()) {
err(`${label}: empty HTML output`);
return;
}
// Verify HEADER nodes render
const headers = nodes.filter(n => n.type === 'HEADER' && n.value);
for (const h of headers.slice(0, 10)) {
const v = (h as ServiceNodeT).value ?? '';
const snippet = v.substring(0, 30);
if (!html.includes(snippet) && v.length > 3) {
err(`${label}: HEADER not rendered: '${snippet}'`);
break;
}
}
checked++;
} catch (e) {
err(`${label}: exception: ${(e as Error).message}`);
}
}
async function main() {
// 1. All flat-data services (subdirs containing full.json)
const serviceDirs = readdirSync(SERVICES).filter(d => existsSync(join(SERVICES, d, 'full.json')));
for (const dir of serviceDirs.sort()) {
const nodes = JSON.parse(readFileSync(join(SERVICES, dir, 'full.json'), 'utf-8')) as ServiceNode[];
await render(nodes, `service/${dir}`);
}
// 2. Collection sections
const collections = ['prayer-rule', 'akathists', 'parimii', 'horologion', 'sbornik', 'irmologion', 'horologionadd'];
// Triodion flat layout (NN.json)
{
const triDir = join(__dirname, '..', 'static', 'data', 'shared', 'triodion');
if (existsSync(triDir)) {
for (const fn of readdirSync(triDir).sort()) {
if (!fn.endsWith('.json')) continue;
const nodes = JSON.parse(readFileSync(join(triDir, fn), 'utf-8')) as ServiceNode[];
await render(nodes, `triodion/${fn}`);
}
}
}
const extra = [
['paraclete', 'services/paraclete'],
];
for (const [col, dir] of extra) {
const colDir = join(__dirname, '..', 'static', 'data', 'shared', dir);
if (!existsSync(colDir)) continue;
for (const tone of readdirSync(colDir).sort()) {
const toneDir = join(colDir, tone);
if (!existsSync(toneDir)) continue;
for (const day of readdirSync(toneDir).sort()) {
const p = join(toneDir, day);
if (!p.endsWith('.json')) continue;
const nodes = JSON.parse(readFileSync(p, 'utf-8')) as ServiceNode[];
await render(nodes, `${col}/${tone}/${day}`);
}
}
}
for (const col of collections) {
const colDir = join(__dirname, '..', 'static', 'data', 'shared', col);
if (!existsSync(colDir)) continue;
for (const section of readdirSync(colDir).sort()) {
const p = join(colDir, section, 'full.json');
if (!existsSync(p)) continue;
const nodes = JSON.parse(readFileSync(p, 'utf-8')) as ServiceNode[];
await render(nodes, `${col}/${section}`);
}
}
console.log(`\nRendered ${checked} services/sections successfully, ${errors} errors, ${warnings} warnings`);
process.exit(errors > 0 ? 1 : 0);
}
main();