1
Files
2025/tools/add_weather.js

170 lines
4.9 KiB
JavaScript

#!/usr/bin/env node
const fs = require("fs/promises");
const path = require("path");
const { extractRawDate, readFrontmatter, writeFrontmatter } = require("./lib/weather/frontmatter");
const { resolveArticleDate } = require("./lib/weather/time");
const { fetchWeather, hasConfiguredProvider, mergeWeather } = require("./lib/weather/providers");
const { loadWeatherConfig } = require("./lib/weather/config");
const CONTENT_ROOT = path.resolve("content");
async function collectMarkdownFiles(rootDir) {
const entries = await fs.readdir(rootDir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const fullPath = path.join(rootDir, entry.name);
if (entry.isDirectory()) {
const nested = await collectMarkdownFiles(fullPath);
files.push(...nested);
continue;
}
if (!entry.isFile()) continue;
if (!entry.name.endsWith(".md")) continue;
if (entry.name === "_index.md") continue;
files.push(fullPath);
}
return files;
}
async function resolveTargets(args) {
if (args.length === 0) {
return collectMarkdownFiles(CONTENT_ROOT);
}
const targets = new Set();
for (const input of args) {
const resolved = path.resolve(input);
try {
const stat = await fs.stat(resolved);
if (stat.isDirectory()) {
const nested = await collectMarkdownFiles(resolved);
nested.forEach((file) => targets.add(file));
continue;
}
if (stat.isFile()) {
if (!resolved.endsWith(".md")) continue;
if (path.basename(resolved) === "_index.md") continue;
targets.add(resolved);
}
} catch (error) {
console.error(`Skipping ${input}: ${error.message}`);
}
}
return Array.from(targets);
}
async function processFile(filePath, config, { force = false } = {}) {
const frontmatter = await readFrontmatter(filePath);
if (!frontmatter) {
return { status: "no-frontmatter" };
}
const existingWeather = frontmatter.doc.has("weather") ? frontmatter.doc.get("weather") : null;
if (existingWeather && !force) {
return { status: "already-set" };
}
const dateValue = frontmatter.doc.get("date");
if (!dateValue) {
return { status: "no-date" };
}
const rawDate = extractRawDate(frontmatter.frontmatterText);
const targetDate = resolveArticleDate(dateValue, rawDate, config);
if (!targetDate) {
return { status: "invalid-date" };
}
const weather = await fetchWeather(targetDate, config);
let finalWeather = {};
if (existingWeather && typeof existingWeather === "object") {
finalWeather = JSON.parse(JSON.stringify(existingWeather));
}
if (weather) {
const added = mergeWeather(finalWeather, weather, weather.source?.[0]);
if (!added && Object.keys(finalWeather).length === 0) {
finalWeather = weather;
}
}
frontmatter.doc.set("weather", finalWeather);
await writeFrontmatter(filePath, frontmatter.doc, frontmatter.body);
return {
status: weather ? "updated" : "empty",
sources: weather?.source || [],
};
}
async function main() {
const cliArgs = process.argv.slice(2);
const force = cliArgs.includes("--force") || cliArgs.includes("-f");
const pathArgs = cliArgs.filter((arg) => arg !== "--force" && arg !== "-f");
const config = loadWeatherConfig();
if (!hasConfiguredProvider(config)) {
console.error("No weather provider configured. Update tools/config.json (weather.providers) before running this script.");
process.exit(1);
}
const files = await resolveTargets(pathArgs);
if (files.length === 0) {
console.log("No matching markdown files found.");
return;
}
let updated = 0;
let skipped = 0;
for (const file of files) {
const relativePath = path.relative(process.cwd(), file);
try {
const result = await processFile(file, config, { force });
switch (result.status) {
case "updated":
updated += 1;
console.log(`✔ Added weather to ${relativePath} (${result.sources.join(", ") || "unknown source"})`);
break;
case "empty":
updated += 1;
console.log(`• Added empty weather to ${relativePath}`);
break;
default:
skipped += 1;
console.log(`↷ Skipped ${relativePath} (${result.status})`);
}
} catch (error) {
skipped += 1;
console.error(`✖ Failed ${relativePath}: ${error.message}`);
}
}
console.log(`\nSummary: ${updated} updated, ${skipped} skipped.`);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});