Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | 15x 1x 1x 14x 20x 83x 83x 4x 79x 79x 79x 2x 2x 4x 2x 6x 6x 6x 6x 6x 2x 4x 4x 4x 2x 5x 5x | // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
/**
* @module Utils/Articles/Filename
* @description Article-filename parsing, directory enumeration, and
* per-language grouping helpers.
*/
import fs from 'fs';
import path from 'path';
import { NEWS_DIR, ARTICLE_FILENAME_PATTERN } from '../../constants/config.js';
import { ALL_LANGUAGES } from '../../constants/language-core.js';
import type { LanguageCode, ParsedArticle } from '../../types/index.js';
/**
* Get all news article HTML files from the news directory
*
* @param newsDir - News directory path (defaults to NEWS_DIR)
* @returns List of article filenames
*/
export function getNewsArticles(newsDir: string = NEWS_DIR): string[] {
if (!fs.existsSync(newsDir)) {
console.log('📁 News directory does not exist yet');
return [];
}
const files = fs.readdirSync(newsDir);
return files.filter((f) => f.endsWith('.html') && !f.startsWith('index-'));
}
/**
* Parse article filename to extract metadata
*
* @param filename - Article filename (e.g., "2025-01-15-week-ahead-en.html")
* @returns Parsed metadata or null if filename doesn't match pattern
*/
export function parseArticleFilename(filename: string): ParsedArticle | null {
const match = filename.match(ARTICLE_FILENAME_PATTERN);
if (!match) {
return null;
}
const langCandidate = match[3] as string;
Iif (!ALL_LANGUAGES.includes(langCandidate as LanguageCode)) {
return null;
}
return {
date: match[1] as string,
slug: match[2] as string,
lang: langCandidate as LanguageCode,
filename,
};
}
/**
* Group articles by language code
*
* @param articles - List of article filenames
* @param languages - Supported language codes
* @returns Articles grouped by language, sorted newest first
*/
export function groupArticlesByLanguage(
articles: string[],
languages: readonly string[]
): Record<string, ParsedArticle[]> {
const grouped: Record<string, ParsedArticle[]> = {};
for (const lang of languages) {
grouped[lang] = [];
}
for (const article of articles) {
const parsed = parseArticleFilename(article);
Eif (parsed) {
const bucket = grouped[parsed.lang];
Eif (bucket) {
bucket.push(parsed);
}
}
}
for (const lang in grouped) {
const bucket = grouped[lang];
Eif (bucket) {
bucket.sort((a, b) => b.date.localeCompare(a.date));
}
}
return grouped;
}
/**
* Check whether a news article file already exists on disk.
*
* This is used by generation pipelines to skip work when a prior workflow run
* (or the same run) has already produced the article, avoiding unnecessary
* regeneration and potential merge conflicts.
*
* @param slug - Article slug including date prefix (e.g. `"2025-01-15-week-ahead"`)
* @param lang - Language code (e.g. `"en"`)
* @param newsDir - Absolute path to the news output directory (defaults to NEWS_DIR)
* @returns `true` when the article file exists
*/
export function checkArticleExists(
slug: string,
lang: string,
newsDir: string = NEWS_DIR
): boolean {
const filename = `${slug}-${lang}.html`;
return fs.existsSync(path.join(newsDir, filename));
}
|