import process from "node:process"; import fs from "node:fs"; import mustache from "mustache"; import loadSVG from "./load-svg.mjs"; import getRSS from "./rss.mjs"; import loadMD from "./loadMD.mjs"; import { cmpArticles } from "./article.mjs"; import i18n from "./i18n.mjs"; import { addDescription } from "./i18n.mjs"; import { minifyHTML } from "./utils.mjs"; import { SitemapStream, streamToPromise } from "sitemap"; import { Readable } from "stream"; const leftPanelTmpl = fs.readFileSync("src/templates/left.tmpl", "utf8"); const likesTmpl = fs.readFileSync("src/templates/likes.tmpl", "utf8"); const headerTmpl = fs.readFileSync("src/templates/header.tmpl", "utf8"); const articleTmpl = fs.readFileSync("src/templates/article.tmpl", "utf8"); const indexTmpl = fs.readFileSync("src/templates/index.tmpl", "utf8"); const notesTmpl = fs.readFileSync("src/templates/notes.tmpl", "utf8"); const legalTmpl = fs.readFileSync("src/templates/legal.tmpl", "utf8"); const tagTmpl = fs.readFileSync("src/templates/tag.tmpl", "utf8"); const hidTmpl = fs.readFileSync("src/templates/hid.tmpl", "utf8"); const baseUrl = "https://ache.one/"; const partials = { header: headerTmpl, leftPanel: leftPanelTmpl, likesButton: likesTmpl, hid: hidTmpl, }; // Load global variables const svg = loadSVG(); let links = []; const listNotes = fs .readdirSync("notes") .filter((name) => name.endsWith(".md")); const notesAll = loadMD(listNotes, "notes"); // lang is determined from the note const legalPages = loadMD(["legal.md", "legal-en.md"], "src/pages"); for (const lang in i18n) { const tagsArticle = new Map(); const filter = process.argv.slice(2); const listArticles = filter.length > 0 ? i18n[lang].articles.filter((article) => filter.includes(article.name)) : i18n[lang].articles; const articles = loadMD(listArticles, "articles", lang); // Same for notes const notes = notesAll.filter((note) => note.metaData.lang == lang); const material = [...articles, ...notes]; // Make notes directory try { fs.mkdirSync(`${lang}/notes`, { recursive: true }); } catch { fs.rmSync(`${lang}/notes`, { force: true, recursive: true }); fs.mkdirSync(`${lang}/notes`, { recursive: true }); } for (const post of material) { const context = { svg, page_title: `${post.title} - ache`, title: i18n[lang].title, intro: i18n[lang].intro, lang, canonical: `${baseUrl}${post.url.slice(1)}`, content: post.content, title: i18n[lang].title, metaData: post.metaData, alt_lang: addDescription(post.metaData.alt_lang), description: post.intro, like_title: i18n[lang].like_title, like_text: i18n[lang].like_text, }; const output = mustache.render(articleTmpl, context, partials); console.log(`Create : ${post.title}`); const type = post.metaData?.type || "article"; fs.writeFileSync(`${type}s/${post.name}.html`, minifyHTML(output)); links.push({ url: context.canonical, changefreq: "yearly", priority: 0.6, img: [{ url: post?.imageUrl }], }); for (const tag of post.metaData.tags) { // Insert the tag, if it already exists add it, otherwise create a object for it. if (tagsArticle.has(tag)) { tagsArticle.get(tag).push(post); } else { tagsArticle.set(tag, [post]); } } } // Set of pages language dependant try { fs.mkdirSync(`${lang}/tag`, { recursive: true }); } catch { fs.rmSync(`${lang}/tag`, { force: true, recursive: true }); fs.mkdirSync(`${lang}/tag`, { recursive: true }); } for (const [tag, material] of tagsArticle.entries()) { console.log(`Create tag page : ${lang}/${tag}.html`); material.sort(cmpArticles); const articles = material.filter((item) => item.metaData.type != "note"); const notes = material.filter((item) => item.metaData.type == "note"); const context = { svg, page_title: `ache - Tag: ${tag}`, title: i18n[lang].title, intro: i18n[lang].intro, lang, tag, articles, notes, description: `${i18n[lang]["tag_desc"]} ${tag}.`, }; const output = mustache.render(tagTmpl, context, partials); fs.writeFileSync(`${lang}/tag/${tag}.html`, minifyHTML(output)); links.push({ url: `${baseUrl}${lang}/tag/${tag}`, changefreq: "monthly", priority: 0.3, }); } { console.log(`Create RSS Flux: ${lang}/rss.xml`); const xmlFeed = getRSS(articles, baseUrl, lang); fs.writeFileSync(`${lang}/rss.xml`, xmlFeed); links.push({ url: `${baseUrl}${lang}/rss.xml`, changefreq: "monthly", priority: 0.3, }); } { console.log(`Create RSS Flux: ${lang}/rss-full.xml`); const xmlFeed = getRSS(material, baseUrl, lang, "rss_full"); fs.writeFileSync(`${lang}/rss-full.xml`, xmlFeed); links.push({ url: `${baseUrl}${lang}/rss-full.xml`, changefreq: "monthly", priority: 0.3, }); } // Notes page: A note with a list of every note in that language { // Pages to redirect to when switching languages. const alt_lang = { fr: { lang: "en", url: "/en/notes", }, en: { lang: "fr", url: "/fr/notes", }, }; const context = { page_title: i18n[lang].title.notes, title: i18n[lang].title, intro: i18n[lang].intro, lang, canonical: `${baseUrl}${lang}`, svg, notes, alt_lang: [alt_lang[lang]], description: i18n[lang].index_desc, }; console.log(`Create : Notes page ${lang}`); const output = mustache.render(notesTmpl, context, partials); fs.writeFileSync(`${lang}/notes/index.html`, minifyHTML(output)); links.push({ url: `${baseUrl}${lang}/notes`, changefreq: "monthly", priority: 0.7, img: [{ url: "https://ache.one/res/ache.svg" }], }); } // Legal page { const legal_page = legalPages.filter( (page) => page.metaData.lang == lang, )[0]; // Pages to redirect to when switching languages. const alt_lang = { fr: { lang: "en", url: "/en/legal", }, en: { lang: "fr", url: "/fr/legal", }, }; const context = { page_title: i18n[lang].title.legal, title: i18n[lang].title, intro: i18n[lang].intro, lang, canonical: `${baseUrl}${lang}/legal`, svg, content: legal_page.content, alt_lang: [alt_lang[lang]], description: i18n[lang].index_desc, }; console.log(`Create : Legal page ${lang}`); const output = mustache.render(legalTmpl, context, partials); fs.writeFileSync(`${lang}/legal.html`, minifyHTML(output)); links.push({ url: `${lang}/legal.html`, changefreq: "yearly", priority: 0.1, }); } // Home page { // Pages to redirect to when switching languages. const alt_lang = { fr: { lang: "en", url: "/en/", }, en: { lang: "fr", url: "/fr/", }, }; const context = { page_title: i18n[lang].title.main, title: i18n[lang].title, intro: i18n[lang].intro, lang, canonical: `${baseUrl}${lang}/notes`, svg, articles, alt_lang: [alt_lang[lang]], description: i18n[lang].index_desc, }; console.log(`Create : Home page ${lang}`); const output = mustache.render(indexTmpl, context, partials); fs.writeFileSync(`${lang}/index.html`, minifyHTML(output)); links.push({ url: `${baseUrl}${lang}`, changefreq: "monthly", priority: 0.7, img: [{ url: "https://ache.one/res/ache.svg" }], }); } } console.log(`Create: sitemap.xml`); // Create a stream to write to const stream = new SitemapStream({ hostname: "https://ache.one" }); // Return a promise that resolves with your XML string streamToPromise(Readable.from(links).pipe(stream)) .then((data) => data.toString()) .then((sitemapXML) => fs.writeFileSync("sitemap.xml", sitemapXML));