Skip to content

Commit 68dfbdc

Browse files
authored
Merge pull request #697 from Hack23/copilot/enable-coverage-tracking
Enable coverage all:true, fix Vitest 4 threshold format, migrate fix-article-navigation to TypeScript
2 parents 53b8224 + b0d8b4f commit 68dfbdc

12 files changed

Lines changed: 526 additions & 15 deletions

.github/workflows/news-article-generator.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1483,7 +1483,7 @@ Remember: You are producing world-class political journalism that informs Swedis
14831483
**Article Maintenance & Fixing:**
14841484
| Script | Usage | Description |
14851485
|--------|-------|-------------|
1486-
| `scripts/fix-article-navigation.py` | `python3 scripts/fix-article-navigation.py [--dry-run]` | **Fallback only** — adds language switcher + article-top-nav to articles missing them (idempotent) |
1486+
| `scripts/fix-article-navigation.ts` | `npx tsx scripts/fix-article-navigation.ts [--dry-run]` | **Fallback only** — adds language switcher + article-top-nav to articles missing them (idempotent) |
14871487
| `scripts/fix-language-switchers-and-css.py` | `python3 scripts/fix-language-switchers-and-css.py` | Updates switchers to show only existing languages, removes embedded CSS |
14881488
| `scripts/fix-mixed-language-descriptions.py` | `python3 scripts/fix-mixed-language-descriptions.py` | Fixes articles with mixed-language meta descriptions |
14891489

.github/workflows/news-committee-reports.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ npx tsx scripts/generate-news-enhanced.ts \
228228
These elements are validated by `bash scripts/validate-news-generation.sh` (Checks 8–10). The fix script is a **fallback only** — do not run it by default:
229229
```bash
230230
# FALLBACK ONLY — use if validate-news-generation.sh reports missing navigation elements
231-
python3 scripts/fix-article-navigation.py
231+
npx tsx scripts/fix-article-navigation.ts
232232
```
233233

234234
### Step 4: Translate Swedish Content & Verify Analysis Quality

.github/workflows/news-evening-analysis.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1120,7 +1120,7 @@ For deeper analysis, combine MCP tools: `search_voteringar` → `get_voting_grou
11201120
| Script | Usage | Description |
11211121
|--------|-------|-------------|
11221122
| `scripts/generate-news-enhanced.ts` | `npx tsx scripts/generate-news-enhanced.ts --types=evening-analysis --languages=LANGS` | Main article generator |
1123-
| `scripts/fix-article-navigation.py` | `python3 scripts/fix-article-navigation.py` | **Fallback only** — fix missing language switcher + article-top-nav (idempotent) |
1123+
| `scripts/fix-article-navigation.ts` | `npx tsx scripts/fix-article-navigation.ts` | **Fallback only** — fix missing language switcher + article-top-nav (idempotent) |
11241124
| `scripts/validate-news-generation.sh` | `bash scripts/validate-news-generation.sh` | Validate generated article structure |
11251125
| `scripts/mcp-setup.sh` | `source scripts/mcp-setup.sh` | Set MCP environment variables |
11261126
| `scripts/mcp-query-cli.ts` | `npx tsx scripts/mcp-query-cli.ts <tool> '<json>'` | Query individual MCP tools |

.github/workflows/news-month-ahead.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ npx tsx scripts/generate-news-enhanced.ts \
227227
These elements are validated by `bash scripts/validate-news-generation.sh` (Checks 8–10). The fix script is a **fallback only** — do not run it by default:
228228
```bash
229229
# FALLBACK ONLY — use if validate-news-generation.sh reports missing navigation elements
230-
python3 scripts/fix-article-navigation.py
230+
npx tsx scripts/fix-article-navigation.ts
231231
```
232232

233233
### Step 4: Translate, Validate & Verify Analysis Quality

.github/workflows/news-monthly-review.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ npx tsx scripts/generate-news-enhanced.ts \
235235
These elements are validated by `bash scripts/validate-news-generation.sh` (Checks 8–10). The fix script is a **fallback only** — do not run it by default:
236236
```bash
237237
# FALLBACK ONLY — use if validate-news-generation.sh reports missing navigation elements
238-
python3 scripts/fix-article-navigation.py
238+
npx tsx scripts/fix-article-navigation.ts
239239
```
240240

241241
### Step 4: Translate, Validate & Verify Analysis Quality

.github/workflows/news-motions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ npx tsx scripts/generate-news-enhanced.ts \
226226
These elements are validated by `bash scripts/validate-news-generation.sh` (Checks 8–10). The fix script is a **fallback only** — do not run it by default:
227227
```bash
228228
# FALLBACK ONLY — use if validate-news-generation.sh reports missing navigation elements
229-
python3 scripts/fix-article-navigation.py
229+
npx tsx scripts/fix-article-navigation.ts
230230
```
231231

232232
### Step 4: Translate, Validate & Verify Analysis Quality

.github/workflows/news-propositions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ npx tsx scripts/generate-news-enhanced.ts \
225225
These elements are validated by `bash scripts/validate-news-generation.sh` (Checks 8–10). The fix script is a **fallback only** — do not run it by default:
226226
```bash
227227
# FALLBACK ONLY — use if validate-news-generation.sh reports missing navigation elements
228-
python3 scripts/fix-article-navigation.py
228+
npx tsx scripts/fix-article-navigation.ts
229229
```
230230

231231
### Step 4: Translate, Validate & Verify Analysis Quality

.github/workflows/news-week-ahead.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ npx tsx scripts/generate-news-enhanced.ts \
225225
These elements are validated by `bash scripts/validate-news-generation.sh` (Checks 8–10). The fix script is a **fallback only** — do not run it by default:
226226
```bash
227227
# FALLBACK ONLY — use if validate-news-generation.sh reports missing navigation elements
228-
python3 scripts/fix-article-navigation.py
228+
npx tsx scripts/fix-article-navigation.ts
229229
```
230230

231231
### Step 4: Translate, Validate & Verify Analysis Quality

.github/workflows/news-weekly-review.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ npx tsx scripts/generate-news-enhanced.ts \
230230
These elements are validated by `bash scripts/validate-news-generation.sh` (Checks 8–10). The fix script is a **fallback only** — do not run it by default:
231231
```bash
232232
# FALLBACK ONLY — use if validate-news-generation.sh reports missing navigation elements
233-
python3 scripts/fix-article-navigation.py
233+
npx tsx scripts/fix-article-navigation.ts
234234
```
235235

236236
### Step 4: Translate, Validate & Verify Analysis Quality

scripts/fix-article-navigation.ts

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
#!/usr/bin/env -S npx tsx
2+
/**
3+
* Fix Article Navigation: Language Switcher + Back-to-News Top Nav
4+
*
5+
* This script ensures ALL news articles have:
6+
* 1. A language switcher nav (14 languages) after <body>
7+
* 2. An article-top-nav div with a localized back-to-news link before the article
8+
*
9+
* It auto-discovers all articles in the news/ directory and processes them
10+
* idempotently — safe to run multiple times.
11+
*
12+
* Usage:
13+
* npx tsx scripts/fix-article-navigation.ts
14+
* npx tsx scripts/fix-article-navigation.ts --dry-run
15+
*
16+
* @author Hack23 AB
17+
* @license Apache-2.0
18+
*/
19+
20+
import fs from 'fs';
21+
import path from 'path';
22+
import { fileURLToPath } from 'url';
23+
import type { Language } from './types/language.js';
24+
import { ALL_LANG_CODES, FOOTER_LABELS } from './article-template/constants.js';
25+
import { getNewsIndexFilename, generateArticleLanguageSwitcher } from './article-template/helpers.js';
26+
27+
// ── Helpers ───────────────────────────────────────────────────────────────
28+
29+
function extractLang(filename: string): Language | null {
30+
const name = filename.replace(/\.html$/, '');
31+
for (const lang of ALL_LANG_CODES) {
32+
if (name.endsWith(`-${lang}`)) return lang;
33+
}
34+
return null;
35+
}
36+
37+
function extractBase(filename: string): string | null {
38+
const name = filename.replace(/\.html$/, '');
39+
for (const lang of ALL_LANG_CODES) {
40+
if (name.endsWith(`-${lang}`)) return name.slice(0, -(lang.length + 1));
41+
}
42+
return null;
43+
}
44+
45+
function generateTopNav(lang: Language): string {
46+
const label = FOOTER_LABELS[lang].backToNews;
47+
const index = getNewsIndexFilename(lang);
48+
return `\n<div class="article-top-nav">\n <a href="${index}" class="back-to-news">\n ← ${label}\n </a>\n</div>\n`;
49+
}
50+
51+
// ── Processing ────────────────────────────────────────────────────────────
52+
53+
export interface ProcessResult {
54+
addedSwitcher: boolean;
55+
addedTopnav: boolean;
56+
fixedTopnav: boolean;
57+
}
58+
59+
/**
60+
* Transform article HTML string: ensures language-switcher and article-top-nav
61+
* with a back-to-news link are present. Idempotent — safe to call multiple times.
62+
*
63+
* @param content Original HTML string
64+
* @param baseSlug Article base slug without directory prefix (e.g. "2026-01-01-article")
65+
* @param lang Language code for this variant
66+
* @returns Updated HTML string and flags indicating what changed
67+
*/
68+
export function transformContent(
69+
content: string,
70+
baseSlug: string,
71+
lang: Language,
72+
): { content: string } & ProcessResult {
73+
let result = content;
74+
let addedSwitcher = false;
75+
let addedTopnav = false;
76+
let fixedTopnav = false;
77+
78+
// ── 1. Language switcher ──────────────────────────────────────────
79+
const hasSwitcher = result.includes('language-switcher');
80+
if (!hasSwitcher) {
81+
const switcherHtml = generateArticleLanguageSwitcher(baseSlug, lang);
82+
// Prefer inserting AFTER the skip-link so it remains the first focusable element.
83+
const skipLinkPattern = /(<a[^>]*class="skip-link"[^>]*>[\s\S]*?<\/a>)/;
84+
if (skipLinkPattern.test(result)) {
85+
result = result.replace(skipLinkPattern, `$1\n${switcherHtml}`);
86+
} else {
87+
result = result.replace(/(<body[^>]*>)/, `$1\n${switcherHtml}`);
88+
}
89+
addedSwitcher = true;
90+
} else {
91+
// Update existing switcher to have all 14 languages.
92+
// Use [^\S\n]* (spaces/tabs, not newlines) to consume any indentation before <nav>,
93+
// so the replacement string's own leading spaces don't accumulate on repeated runs.
94+
const newSwitcher = generateArticleLanguageSwitcher(baseSlug, lang);
95+
result = result.replace(/[^\S\n]*<nav class="language-switcher"[^>]*>[\s\S]*?<\/nav>/, newSwitcher);
96+
}
97+
98+
// ── 2. article-top-nav ────────────────────────────────────────────
99+
const hasTopnav = result.includes('article-top-nav');
100+
101+
// If top-nav exists but is missing back-to-news link, replace it
102+
if (hasTopnav) {
103+
const topNavHasBackLink =
104+
/<div class="article-top-nav">[\s\S]*?class="back-to-news"[\s\S]*?<\/div>/.test(result);
105+
if (!topNavHasBackLink) {
106+
result = result.replace(
107+
/<div class="article-top-nav">[\s\S]*?<\/div>/,
108+
generateTopNav(lang).trim(),
109+
);
110+
fixedTopnav = true;
111+
}
112+
}
113+
114+
if (!hasTopnav) {
115+
const topNavHtml = generateTopNav(lang);
116+
let inserted = false;
117+
118+
// Pattern A: insert after closing </nav> of language-switcher, before article/div.news-article
119+
if (result.includes('</nav>')) {
120+
const navPattern = /((<\/nav>)([\s]*)(<(?:article|div)\s+class="(?:news-article|container)"))/s;
121+
const match = navPattern.exec(result);
122+
if (match) {
123+
const endOfNav = match.index + match[2].length;
124+
const whitespace = match[3];
125+
const articleTag = match[4];
126+
const afterFull = result.slice(match.index + match[0].length);
127+
result = result.slice(0, endOfNav) + topNavHtml + whitespace + articleTag + afterFull;
128+
inserted = true;
129+
}
130+
}
131+
132+
// Pattern B: insert directly before <article class="news-article"> or <div class="container">
133+
if (!inserted) {
134+
const articlePattern = /(<(?:article|div)\s+class="(?:news-article|container)")/;
135+
const match = articlePattern.exec(result);
136+
if (match) {
137+
result = result.slice(0, match.index) + topNavHtml + '\n' + result.slice(match.index);
138+
inserted = true;
139+
}
140+
}
141+
142+
if (inserted) addedTopnav = true;
143+
}
144+
145+
return { content: result, addedSwitcher, addedTopnav, fixedTopnav };
146+
}
147+
148+
function processArticle(filepath: string, baseSlug: string, lang: Language, dryRun: boolean): ProcessResult {
149+
const original = fs.readFileSync(filepath, 'utf-8');
150+
const { content, addedSwitcher, addedTopnav, fixedTopnav } = transformContent(original, baseSlug, lang);
151+
152+
// ── Write if changed ──────────────────────────────────────────────
153+
if (content !== original && !dryRun) {
154+
fs.writeFileSync(filepath, content, 'utf-8');
155+
}
156+
157+
return { addedSwitcher, addedTopnav, fixedTopnav };
158+
}
159+
160+
interface ArticleMap {
161+
[baseSlug: string]: Partial<Record<Language, string>>;
162+
}
163+
164+
function discoverArticles(newsDir: string): ArticleMap {
165+
const articles: ArticleMap = {};
166+
const files = fs.readdirSync(newsDir).sort();
167+
for (const name of files) {
168+
if (!name.endsWith('.html') || name.startsWith('index')) continue;
169+
const lang = extractLang(name);
170+
const base = extractBase(name);
171+
if (lang && base) {
172+
if (!articles[base]) articles[base] = {};
173+
articles[base][lang] = path.join(newsDir, name);
174+
}
175+
}
176+
return articles;
177+
}
178+
179+
function main(): void {
180+
const dryRun = process.argv.includes('--dry-run');
181+
if (dryRun) {
182+
console.log('=== DRY RUN — no files will be modified ===\n');
183+
}
184+
185+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
186+
const newsDir = path.resolve(scriptDir, '..', 'news');
187+
188+
if (!fs.existsSync(newsDir)) {
189+
console.error(`ERROR: news directory not found at ${newsDir}`);
190+
process.exit(1);
191+
}
192+
193+
console.log('=== Fix Article Navigation ===');
194+
console.log(`News directory: ${newsDir}\n`);
195+
196+
const articles = discoverArticles(newsDir);
197+
const slugs = Object.keys(articles).sort();
198+
console.log(`Discovered ${slugs.length} unique article slugs\n`);
199+
200+
let total = 0;
201+
let switchersAdded = 0;
202+
let topnavsAdded = 0;
203+
let topnavsFixed = 0;
204+
205+
for (const baseSlug of slugs) {
206+
const langFiles = articles[baseSlug];
207+
for (const lang of ALL_LANG_CODES) {
208+
const filepath = langFiles[lang];
209+
if (!filepath) continue;
210+
total++;
211+
const { addedSwitcher, addedTopnav, fixedTopnav } = processArticle(filepath, baseSlug, lang, dryRun);
212+
if (addedSwitcher) switchersAdded++;
213+
if (addedTopnav) topnavsAdded++;
214+
if (fixedTopnav) topnavsFixed++;
215+
}
216+
}
217+
218+
console.log('=== Summary ===');
219+
console.log(`Total files processed: ${total}`);
220+
console.log(`Language switchers added: ${switchersAdded}`);
221+
console.log(`Top nav (article-top-nav) added: ${topnavsAdded}`);
222+
console.log(`Top nav fixed (missing back-to-news link): ${topnavsFixed}`);
223+
if (dryRun) {
224+
console.log('\n(Dry run — no files were modified)');
225+
}
226+
console.log('\n✓ Done!');
227+
}
228+
229+
if (import.meta.url === `file://${process.argv[1]}`) {
230+
main();
231+
}

0 commit comments

Comments
 (0)