#!/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 (force) { finalWeather = weather || {}; } else 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; } } } else { 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); });