« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/upd8.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/upd8.js')
-rwxr-xr-xsrc/upd8.js992
1 files changed, 494 insertions, 498 deletions
diff --git a/src/upd8.js b/src/upd8.js
index cff43135..8a5a2873 100755
--- a/src/upd8.js
+++ b/src/upd8.js
@@ -1,6 +1,5 @@
 #!/usr/bin/env node
-
-// @format
+/** @format */
 
 // HEY N8RDS!
 //
@@ -33,18 +32,18 @@
 // Oh yeah, like. Just run this through some relatively recent version of
 // node.js and you'll 8e fine. ...Within the project root. O8viously.
 
-import * as path from "path";
-import { promisify } from "util";
-import { fileURLToPath } from "url";
+import * as path from 'path';
+import {promisify} from 'util';
+import {fileURLToPath} from 'url';
 
 // I made this dependency myself! A long, long time ago. It is pro8a8ly my
 // most useful li8rary ever. I'm not sure 8esides me actually uses it, though.
-import fixWS from "fix-whitespace";
+import fixWS from 'fix-whitespace';
 // Wait nevermind, I forgot a8out why-do-kids-love-the-taste-of-cinnamon-toast-
 // crunch. THAT is my 8est li8rary.
 
 // It stands for "HTML Entities", apparently. Cursed.
-import he from "he";
+import he from 'he';
 
 import {
   copyFile,
@@ -54,25 +53,25 @@ import {
   symlink,
   writeFile,
   unlink,
-} from "fs/promises";
+} from 'fs/promises';
 
-import { inspect as nodeInspect } from "util";
+import {inspect as nodeInspect} from 'util';
 
-import genThumbs from "./gen-thumbs.js";
-import { listingSpec, listingTargetSpec } from "./listing-spec.js";
-import urlSpec from "./url-spec.js";
-import * as pageSpecs from "./page/index.js";
+import genThumbs from './gen-thumbs.js';
+import {listingSpec, listingTargetSpec} from './listing-spec.js';
+import urlSpec from './url-spec.js';
+import * as pageSpecs from './page/index.js';
 
-import find, { bindFind } from "./util/find.js";
-import * as html from "./util/html.js";
-import unbound_link, { getLinkThemeString } from "./util/link.js";
-import { findFiles } from "./util/io.js";
+import find, {bindFind} from './util/find.js';
+import * as html from './util/html.js';
+import unbound_link, {getLinkThemeString} from './util/link.js';
+import {findFiles} from './util/io.js';
 
-import CacheableObject from "./data/cacheable-object.js";
+import CacheableObject from './data/cacheable-object.js';
 
-import { serializeThings } from "./data/serialize.js";
+import {serializeThings} from './data/serialize.js';
 
-import { Language } from "./data/things.js";
+import {Language} from './data/things.js';
 
 import {
   filterDuplicateDirectories,
@@ -81,7 +80,7 @@ import {
   loadAndProcessDataDocuments,
   sortWikiDataArrays,
   WIKI_INFO_FILE,
-} from "./data/yaml.js";
+} from './data/yaml.js';
 
 import {
   fancifyFlashURL,
@@ -103,7 +102,7 @@ import {
   getRevealStringFromWarnings,
   getThemeString,
   iconifyURL,
-} from "./misc-templates.js";
+} from './misc-templates.js';
 
 import {
   color,
@@ -114,9 +113,9 @@ import {
   parseOptions,
   progressPromiseAll,
   ENABLE_COLOR,
-} from "./util/cli.js";
+} from './util/cli.js';
 
-import { validateReplacerSpec, transformInline } from "./util/replacer.js";
+import {validateReplacerSpec, transformInline} from './util/replacer.js';
 
 import {
   chunkByConditions,
@@ -130,7 +129,7 @@ import {
   getKebabCase,
   getTotalDuration,
   getTrackCover,
-} from "./util/wiki-data.js";
+} from './util/wiki-data.js';
 
 import {
   serializeContribs,
@@ -139,7 +138,7 @@ import {
   serializeGroupsForTrack,
   serializeImagePaths,
   serializeLink,
-} from "./util/serialize.js";
+} from './util/serialize.js';
 
 import {
   bindOpts,
@@ -155,23 +154,23 @@ import {
   unique,
   withAggregate,
   withEntries,
-} from "./util/sugar.js";
+} from './util/sugar.js';
 
-import { generateURLs, thumb } from "./util/urls.js";
+import {generateURLs, thumb} from './util/urls.js';
 
 // Pensive emoji!
 import {
   FANDOM_GROUP_DIRECTORY,
   OFFICIAL_GROUP_DIRECTORY,
-} from "./util/magic-constants.js";
+} from './util/magic-constants.js';
 
-import FileSizePreloader from "./file-size-preloader.js";
+import FileSizePreloader from './file-size-preloader.js';
 
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
 
 const CACHEBUST = 10;
 
-const DEFAULT_STRINGS_FILE = "strings-default.json";
+const DEFAULT_STRINGS_FILE = 'strings-default.json';
 
 // Code that's common 8etween the 8uild code (i.e. upd8.js) and gener8ted
 // site code should 8e put here. Which, uh, ~~only really means this one
@@ -180,20 +179,20 @@ const DEFAULT_STRINGS_FILE = "strings-default.json";
 // Rather than hard code it, anything in this directory can 8e shared across
 // 8oth ends of the code8ase.
 // (This gets symlinked into the --data-path directory.)
-const UTILITY_DIRECTORY = "util";
+const UTILITY_DIRECTORY = 'util';
 
 // Code that's used only in the static site! CSS, cilent JS, etc.
 // (This gets symlinked into the --data-path directory.)
-const STATIC_DIRECTORY = "static";
+const STATIC_DIRECTORY = 'static';
 
 // This exists adjacent to index.html for any page with oEmbed metadata.
-const OEMBED_JSON_FILE = "oembed.json";
+const OEMBED_JSON_FILE = 'oembed.json';
 
 // Automatically copied (if present) from media directory to site root.
-const FAVICON_FILE = "favicon.ico";
+const FAVICON_FILE = 'favicon.ico';
 
 function inspect(value) {
-  return nodeInspect(value, { colors: ENABLE_COLOR });
+  return nodeInspect(value, {colors: ENABLE_COLOR});
 }
 
 // Shared varia8les! These are more efficient to access than a shared varia8le
@@ -222,38 +221,38 @@ function splitLines(text) {
 
 const replacerSpec = {
   album: {
-    find: "album",
-    link: "album",
+    find: 'album',
+    link: 'album',
   },
-  "album-commentary": {
-    find: "album",
-    link: "albumCommentary",
+  'album-commentary': {
+    find: 'album',
+    link: 'albumCommentary',
   },
   artist: {
-    find: "artist",
-    link: "artist",
+    find: 'artist',
+    link: 'artist',
   },
-  "artist-gallery": {
-    find: "artist",
-    link: "artistGallery",
+  'artist-gallery': {
+    find: 'artist',
+    link: 'artistGallery',
   },
-  "commentary-index": {
+  'commentary-index': {
     find: null,
-    link: "commentaryIndex",
+    link: 'commentaryIndex',
   },
   date: {
     find: null,
     value: (ref) => new Date(ref),
-    html: (date, { language }) =>
+    html: (date, {language}) =>
       `<time datetime="${date.toString()}">${language.formatDate(date)}</time>`,
   },
   flash: {
-    find: "flash",
-    link: "flash",
+    find: 'flash',
+    link: 'flash',
     transformName(name, node, input) {
       const nextCharacter = input[node.iEnd];
       const lastCharacter = name[name.length - 1];
-      if (![" ", "\n", "<"].includes(nextCharacter) && lastCharacter === ".") {
+      if (![' ', '\n', '<'].includes(nextCharacter) && lastCharacter === '.') {
         return name.slice(0, -1);
       } else {
         return name;
@@ -261,69 +260,69 @@ const replacerSpec = {
     },
   },
   group: {
-    find: "group",
-    link: "groupInfo",
+    find: 'group',
+    link: 'groupInfo',
   },
-  "group-gallery": {
-    find: "group",
-    link: "groupGallery",
+  'group-gallery': {
+    find: 'group',
+    link: 'groupGallery',
   },
   home: {
     find: null,
-    link: "home",
+    link: 'home',
   },
-  "listing-index": {
+  'listing-index': {
     find: null,
-    link: "listingIndex",
+    link: 'listingIndex',
   },
   listing: {
-    find: "listing",
-    link: "listing",
+    find: 'listing',
+    link: 'listing',
   },
   media: {
     find: null,
-    link: "media",
+    link: 'media',
   },
-  "news-index": {
+  'news-index': {
     find: null,
-    link: "newsIndex",
+    link: 'newsIndex',
   },
-  "news-entry": {
-    find: "newsEntry",
-    link: "newsEntry",
+  'news-entry': {
+    find: 'newsEntry',
+    link: 'newsEntry',
   },
   root: {
     find: null,
-    link: "root",
+    link: 'root',
   },
   site: {
     find: null,
-    link: "site",
+    link: 'site',
   },
   static: {
-    find: "staticPage",
-    link: "staticPage",
+    find: 'staticPage',
+    link: 'staticPage',
   },
   string: {
     find: null,
     value: (ref) => ref,
-    html: (ref, { language, args }) => language.$(ref, args),
+    html: (ref, {language, args}) => language.$(ref, args),
   },
   tag: {
-    find: "artTag",
-    link: "tag",
+    find: 'artTag',
+    link: 'tag',
   },
   track: {
-    find: "track",
-    link: "track",
+    find: 'track',
+    link: 'track',
   },
 };
 
-if (!validateReplacerSpec(replacerSpec, { find, link: unbound_link })) {
+if (!validateReplacerSpec(replacerSpec, {find, link: unbound_link})) {
   process.exit();
 }
 
-function parseAttributes(string, { to }) {
+function parseAttributes(string, {to}) {
   const attributes = Object.create(null);
   const skipWhitespace = (i) => {
     const ws = /\s/;
@@ -345,7 +344,7 @@ function parseAttributes(string, { to }) {
     const aEnd = i + string.slice(i).match(/[\s=]|$/).index;
     const attribute = string.slice(aStart, aEnd);
     i = skipWhitespace(aEnd);
-    if (string[i] === "=") {
+    if (string[i] === '=') {
       i = skipWhitespace(i + 1);
       let end, endOffset;
       if (string[i] === '"' || string[i] === "'") {
@@ -353,15 +352,15 @@ function parseAttributes(string, { to }) {
         endOffset = 1;
         i++;
       } else {
-        end = "\\s";
+        end = '\\s';
         endOffset = 0;
       }
       const vStart = i;
       const vEnd = i + string.slice(i).match(new RegExp(`${end}|$`)).index;
       const value = string.slice(vStart, vEnd);
       i = vEnd + endOffset;
-      if (attribute === "src" && value.startsWith("media/")) {
-        attributes[attribute] = to("media.path", value.slice("media/".length));
+      if (attribute === 'src' && value.startsWith('media/')) {
+        attributes[attribute] = to('media.path', value.slice('media/'.length));
       } else {
         attributes[attribute] = value;
       }
@@ -372,9 +371,9 @@ function parseAttributes(string, { to }) {
   return Object.fromEntries(
     Object.entries(attributes).map(([key, val]) => [
       key,
-      val === "true"
+      val === 'true'
         ? true
-        : val === "false"
+        : val === 'false'
         ? false
         : val === key
         ? true
@@ -386,13 +385,13 @@ function parseAttributes(string, { to }) {
 function joinLineBreaks(sourceLines) {
   const outLines = [];
 
-  let lineSoFar = "";
+  let lineSoFar = '';
   for (let i = 0; i < sourceLines.length; i++) {
     const line = sourceLines[i];
     lineSoFar += line;
-    if (!line.endsWith("<br>")) {
+    if (!line.endsWith('<br>')) {
       outLines.push(lineSoFar);
-      lineSoFar = "";
+      lineSoFar = '';
     }
   }
 
@@ -403,14 +402,14 @@ function joinLineBreaks(sourceLines) {
   return outLines;
 }
 
-function transformMultiline(text, { parseAttributes, transformInline }) {
+function transformMultiline(text, {parseAttributes, transformInline}) {
   // Heck yes, HTML magics.
 
   text = transformInline(text.trim());
 
   const outLines = [];
 
-  const indentString = " ".repeat(4);
+  const indentString = ' '.repeat(4);
 
   let levelIndents = [];
   const openLevel = (indent) => {
@@ -418,13 +417,13 @@ function transformMultiline(text, { parseAttributes, transformInline }) {
     // correct, we have to append the <ul> at the end of the existing
     // previous <li>
     const previousLine = outLines[outLines.length - 1];
-    if (previousLine?.endsWith("</li>")) {
+    if (previousLine?.endsWith('</li>')) {
       // we will re-close the <li> later
-      outLines[outLines.length - 1] = previousLine.slice(0, -5) + " <ul>";
+      outLines[outLines.length - 1] = previousLine.slice(0, -5) + ' <ul>';
     } else {
       // if the previous line isn't a list item, this is the opening of
       // the first list level, so no need for indent
-      outLines.push("<ul>");
+      outLines.push('<ul>');
     }
     levelIndents.push(indent);
   };
@@ -432,10 +431,10 @@ function transformMultiline(text, { parseAttributes, transformInline }) {
     levelIndents.pop();
     if (levelIndents.length) {
       // closing a sublist, so close the list item containing it too
-      outLines.push(indentString.repeat(levelIndents.length) + "</ul></li>");
+      outLines.push(indentString.repeat(levelIndents.length) + '</ul></li>');
     } else {
       // closing the final list level! no need for indent here
-      outLines.push("</ul>");
+      outLines.push('</ul>');
     }
   };
 
@@ -448,19 +447,19 @@ function transformMultiline(text, { parseAttributes, transformInline }) {
   let lines = splitLines(text);
   lines = joinLineBreaks(lines);
   for (let line of lines) {
-    const imageLine = line.startsWith("<img");
+    const imageLine = line.startsWith('<img');
     line = line.replace(/<img (.*?)>/g, (match, attributes) =>
       img({
         lazy: true,
         link: true,
-        thumb: "medium",
+        thumb: 'medium',
         ...parseAttributes(attributes),
       })
     );
 
     let indentThisLine = 0;
     let lineContent = line;
-    let lineTag = "p";
+    let lineTag = 'p';
 
     const listMatch = line.match(/^( *)- *(.*)$/);
     if (listMatch) {
@@ -498,7 +497,7 @@ function transformMultiline(text, { parseAttributes, transformInline }) {
       // finally, set variables for appending content line
       indentThisLine = levelIndents.length;
       lineContent = listMatch[2];
-      lineTag = "li";
+      lineTag = 'li';
     } else {
       // not a list item! close any existing list levels
       while (levelIndents.length) closeLevel();
@@ -510,27 +509,27 @@ function transformMultiline(text, { parseAttributes, transformInline }) {
         // is a quote! open a blockquote tag if it doesnt already exist
         if (!inBlockquote) {
           inBlockquote = true;
-          outLines.push("<blockquote>");
+          outLines.push('<blockquote>');
         }
         indentThisLine = 1;
         lineContent = quoteMatch[1];
       } else if (inBlockquote) {
         // not a quote! close a blockquote tag if it exists
         inBlockquote = false;
-        outLines.push("</blockquote>");
+        outLines.push('</blockquote>');
       }
 
       // let some escaped symbols display as the normal symbol, since the
       // point of escaping them is just to avoid having them be treated as
       // syntax markers!
       if (lineContent.match(/( *)\\-/)) {
-        lineContent = lineContent.replace("\\-", "-");
+        lineContent = lineContent.replace('\\-', '-');
       } else if (lineContent.match(/( *)\\>/)) {
-        lineContent = lineContent.replace("\\>", ">");
+        lineContent = lineContent.replace('\\>', '>');
       }
     }
 
-    if (lineTag === "p") {
+    if (lineTag === 'p') {
       // certain inline element tags should still be postioned within a
       // paragraph; other elements (e.g. headings) should be added as-is
       const elementMatch = line.match(/^<(.*?)[ >]/);
@@ -538,40 +537,40 @@ function transformMultiline(text, { parseAttributes, transformInline }) {
         elementMatch &&
         !imageLine &&
         ![
-          "a",
-          "abbr",
-          "b",
-          "bdo",
-          "br",
-          "cite",
-          "code",
-          "data",
-          "datalist",
-          "del",
-          "dfn",
-          "em",
-          "i",
-          "img",
-          "ins",
-          "kbd",
-          "mark",
-          "output",
-          "picture",
-          "q",
-          "ruby",
-          "samp",
-          "small",
-          "span",
-          "strong",
-          "sub",
-          "sup",
-          "svg",
-          "time",
-          "var",
-          "wbr",
+          'a',
+          'abbr',
+          'b',
+          'bdo',
+          'br',
+          'cite',
+          'code',
+          'data',
+          'datalist',
+          'del',
+          'dfn',
+          'em',
+          'i',
+          'img',
+          'ins',
+          'kbd',
+          'mark',
+          'output',
+          'picture',
+          'q',
+          'ruby',
+          'samp',
+          'small',
+          'span',
+          'strong',
+          'sub',
+          'sup',
+          'svg',
+          'time',
+          'var',
+          'wbr',
         ].includes(elementMatch[1])
       ) {
-        lineTag = "";
+        lineTag = '';
       }
     }
 
@@ -592,43 +591,43 @@ function transformMultiline(text, { parseAttributes, transformInline }) {
   // if still in a blockquote, close its tag
   if (inBlockquote) {
     inBlockquote = false;
-    outLines.push("</blockquote>");
+    outLines.push('</blockquote>');
   }
 
-  return outLines.join("\n");
+  return outLines.join('\n');
 }
 
-function transformLyrics(text, { transformInline, transformMultiline }) {
+function transformLyrics(text, {transformInline, transformMultiline}) {
   // Different from transformMultiline 'cuz it joins multiple lines together
   // with line 8reaks (<br>); transformMultiline treats each line as its own
   // complete paragraph (or list, etc).
 
   // If it looks like old data, then like, oh god.
   // Use the normal transformMultiline tool.
-  if (text.includes("<br")) {
+  if (text.includes('<br')) {
     return transformMultiline(text);
   }
 
   text = transformInline(text.trim());
 
-  let buildLine = "";
+  let buildLine = '';
   const addLine = () => outLines.push(`<p>${buildLine}</p>`);
   const outLines = [];
-  for (const line of text.split("\n")) {
+  for (const line of text.split('\n')) {
     if (line.length) {
       if (buildLine.length) {
-        buildLine += "<br>";
+        buildLine += '<br>';
       }
       buildLine += line;
     } else if (buildLine.length) {
       addLine();
-      buildLine = "";
+      buildLine = '';
     }
   }
   if (buildLine.length) {
     addLine();
   }
-  return outLines.join("\n");
+  return outLines.join('\n');
 }
 
 function stringifyThings(thingData) {
@@ -638,7 +637,7 @@ function stringifyThings(thingData) {
 function img({
   src,
   alt,
-  noSrcText = "",
+  noSrcText = '',
   thumb: thumbKey,
   reveal,
   id,
@@ -650,13 +649,13 @@ function img({
   square = false,
 }) {
   const willSquare = square;
-  const willLink = typeof link === "string" || link;
+  const willLink = typeof link === 'string' || link;
 
   const originalSrc = src;
   const thumbSrc = src && (thumbKey ? thumb[thumbKey](src) : src);
 
   const imgAttributes = html.attributes({
-    id: link ? "" : id,
+    id: link ? '' : id,
     class: className,
     alt,
     width,
@@ -701,21 +700,21 @@ function img({
     }
 
     if (willSquare) {
-      wrapped = html.tag("div", { class: "square-content" }, wrapped);
+      wrapped = html.tag('div', {class: 'square-content'}, wrapped);
       wrapped = html.tag(
-        "div",
-        { class: ["square", hide && !willLink && "js-hide"] },
+        'div',
+        {class: ['square', hide && !willLink && 'js-hide']},
         wrapped
       );
     }
 
     if (willLink) {
       wrapped = html.tag(
-        "a",
+        'a',
         {
           id,
-          class: ["box", hide && "js-hide"],
-          href: typeof link === "string" ? link : originalSrc,
+          class: ['box', hide && 'js-hide'],
+          href: typeof link === 'string' ? link : originalSrc,
         },
         wrapped
       );
@@ -727,16 +726,16 @@ function img({
 
 function validateWritePath(path, urlGroup) {
   if (!Array.isArray(path)) {
-    return { error: `Expected array, got ${path}` };
+    return {error: `Expected array, got ${path}`};
   }
 
-  const { paths } = urlGroup;
+  const {paths} = urlGroup;
 
   const definedKeys = Object.keys(paths);
   const specifiedKey = path[0];
 
   if (!definedKeys.includes(specifiedKey)) {
-    return { error: `Specified key ${specifiedKey} isn't defined` };
+    return {error: `Specified key ${specifiedKey} isn't defined`};
   }
 
   const expectedArgs = paths[specifiedKey].match(/<>/g)?.length ?? 0;
@@ -748,54 +747,54 @@ function validateWritePath(path, urlGroup) {
     };
   }
 
-  return { success: true };
+  return {success: true};
 }
 
 function validateWriteObject(obj) {
-  if (typeof obj !== "object") {
-    return { error: `Expected object, got ${typeof obj}` };
+  if (typeof obj !== 'object') {
+    return {error: `Expected object, got ${typeof obj}`};
   }
 
-  if (typeof obj.type !== "string") {
-    return { error: `Expected type to be string, got ${obj.type}` };
+  if (typeof obj.type !== 'string') {
+    return {error: `Expected type to be string, got ${obj.type}`};
   }
 
   switch (obj.type) {
-    case "legacy": {
-      if (typeof obj.write !== "function") {
-        return { error: `Expected write to be string, got ${obj.write}` };
+    case 'legacy': {
+      if (typeof obj.write !== 'function') {
+        return {error: `Expected write to be string, got ${obj.write}`};
       }
 
       break;
     }
 
-    case "page": {
+    case 'page': {
       const path = validateWritePath(obj.path, urlSpec.localized);
       if (path.error) {
-        return { error: `Path validation failed: ${path.error}` };
+        return {error: `Path validation failed: ${path.error}`};
       }
 
-      if (typeof obj.page !== "function") {
-        return { error: `Expected page to be function, got ${obj.content}` };
+      if (typeof obj.page !== 'function') {
+        return {error: `Expected page to be function, got ${obj.content}`};
       }
 
       break;
     }
 
-    case "data": {
+    case 'data': {
       const path = validateWritePath(obj.path, urlSpec.data);
       if (path.error) {
-        return { error: `Path validation failed: ${path.error}` };
+        return {error: `Path validation failed: ${path.error}`};
       }
 
-      if (typeof obj.data !== "function") {
-        return { error: `Expected data to be function, got ${obj.data}` };
+      if (typeof obj.data !== 'function') {
+        return {error: `Expected data to be function, got ${obj.data}`};
       }
 
       break;
     }
 
-    case "redirect": {
+    case 'redirect': {
       const fromPath = validateWritePath(obj.fromPath, urlSpec.localized);
       if (fromPath.error) {
         return {
@@ -805,22 +804,22 @@ function validateWriteObject(obj) {
 
       const toPath = validateWritePath(obj.toPath, urlSpec.localized);
       if (toPath.error) {
-        return { error: `Path (toPath) validation failed: ${toPath.error}` };
+        return {error: `Path (toPath) validation failed: ${toPath.error}`};
       }
 
-      if (typeof obj.title !== "function") {
-        return { error: `Expected title to be function, got ${obj.title}` };
+      if (typeof obj.title !== 'function') {
+        return {error: `Expected title to be function, got ${obj.title}`};
       }
 
       break;
     }
 
     default: {
-      return { error: `Unknown type: ${obj.type}` };
+      return {error: `Unknown type: ${obj.type}`};
     }
   }
 
-  return { success: true };
+  return {success: true};
 }
 
 /*
@@ -836,9 +835,9 @@ async function writeData(subKey, directory, data) {
 const writePage = {};
 
 writePage.to =
-  ({ baseDirectory, pageSubKey, paths }) =>
+  ({baseDirectory, pageSubKey, paths}) =>
   (targetFullKey, ...args) => {
-    const [groupKey, subKey] = targetFullKey.split(".");
+    const [groupKey, subKey] = targetFullKey.split('.');
     let path = paths.subdirectoryPrefix;
 
     let from;
@@ -847,27 +846,27 @@ writePage.to =
     // When linking to *outside* the localized area of the site, we need to
     // make sure the result is correctly relative to the 8ase directory.
     if (
-      groupKey !== "localized" &&
-      groupKey !== "localizedDefaultLanguage" &&
+      groupKey !== 'localized' &&
+      groupKey !== 'localizedDefaultLanguage' &&
       baseDirectory
     ) {
-      from = "localizedWithBaseDirectory." + pageSubKey;
+      from = 'localizedWithBaseDirectory.' + pageSubKey;
       to = targetFullKey;
-    } else if (groupKey === "localizedDefaultLanguage" && baseDirectory) {
+    } else if (groupKey === 'localizedDefaultLanguage' && baseDirectory) {
       // Special case for specifically linking *from* a page with base
       // directory *to* a page without! Used for the language switcher and
       // hopefully nothing else oh god.
-      from = "localizedWithBaseDirectory." + pageSubKey;
-      to = "localized." + subKey;
-    } else if (groupKey === "localizedDefaultLanguage") {
+      from = 'localizedWithBaseDirectory.' + pageSubKey;
+      to = 'localized.' + subKey;
+    } else if (groupKey === 'localizedDefaultLanguage') {
       // Linking to the default, except surprise, we're already IN the default
       // (no baseDirectory set).
-      from = "localized." + pageSubKey;
-      to = "localized." + subKey;
+      from = 'localized.' + pageSubKey;
+      to = 'localized.' + subKey;
     } else {
       // If we're linking inside the localized area (or there just is no
       // 8ase directory), the 8ase directory doesn't matter.
-      from = "localized." + pageSubKey;
+      from = 'localized.' + pageSubKey;
       to = targetFullKey;
     }
 
@@ -890,13 +889,13 @@ writePage.html = (
     wikiData,
   }
 ) => {
-  const { wikiInfo } = wikiData;
+  const {wikiInfo} = wikiData;
 
   let {
-    title = "",
+    title = '',
     meta = {},
-    theme = "",
-    stylesheet = "",
+    theme = '',
+    stylesheet = '',
 
     showWikiNameInTitle = true,
 
@@ -912,45 +911,45 @@ writePage.html = (
     socialEmbed = {},
   } = pageInfo;
 
-  body.style ??= "";
+  body.style ??= '';
 
   theme = theme || getThemeString(wikiInfo.color);
 
   banner ||= {};
   banner.classes ??= [];
-  banner.src ??= "";
-  banner.position ??= "";
+  banner.src ??= '';
+  banner.position ??= '';
   banner.dimensions ??= [0, 0];
 
   main.classes ??= [];
-  main.content ??= "";
+  main.content ??= '';
 
   sidebarLeft ??= {};
   sidebarRight ??= {};
 
   for (const sidebar of [sidebarLeft, sidebarRight]) {
     sidebar.classes ??= [];
-    sidebar.content ??= "";
+    sidebar.content ??= '';
     sidebar.collapse ??= true;
   }
 
   nav.classes ??= [];
-  nav.content ??= "";
-  nav.bottomRowContent ??= "";
+  nav.content ??= '';
+  nav.bottomRowContent ??= '';
   nav.links ??= [];
   nav.linkContainerClasses ??= [];
 
   secondaryNav ??= {};
-  secondaryNav.content ??= "";
-  secondaryNav.content ??= "";
+  secondaryNav.content ??= '';
+  secondaryNav.content ??= '';
 
   footer.classes ??= [];
   footer.content ??= wikiInfo.footerContent
     ? transformMultiline(wikiInfo.footerContent)
-    : "";
+    : '';
 
   footer.content +=
-    "\n" +
+    '\n' +
     getFooterLocalizationLinks(paths.pathname, {
       defaultLanguage,
       languages,
@@ -960,13 +959,13 @@ writePage.html = (
     });
 
   const canonical = wikiInfo.canonicalBase
-    ? wikiInfo.canonicalBase + (paths.pathname === "/" ? "" : paths.pathname)
-    : "";
+    ? wikiInfo.canonicalBase + (paths.pathname === '/' ? '' : paths.pathname)
+    : '';
 
   const localizedCanonical = wikiInfo.canonicalBase
-    ? Object.entries(localizedPaths).map(([code, { pathname }]) => ({
+    ? Object.entries(localizedPaths).map(([code, {pathname}]) => ({
         lang: code,
-        href: wikiInfo.canonicalBase + (pathname === "/" ? "" : pathname),
+        href: wikiInfo.canonicalBase + (pathname === '/' ? '' : pathname),
       }))
     : [];
 
@@ -976,9 +975,9 @@ writePage.html = (
   const mainHTML =
     main.content &&
     html.tag(
-      "main",
+      'main',
       {
-        id: "content",
+        id: 'content',
         class: main.classes,
       },
       main.content
@@ -987,9 +986,9 @@ writePage.html = (
   const footerHTML =
     footer.content &&
     html.tag(
-      "footer",
+      'footer',
       {
-        id: "footer",
+        id: 'footer',
         class: footer.classes,
       },
       footer.content
@@ -997,18 +996,18 @@ writePage.html = (
 
   const generateSidebarHTML = (
     id,
-    { content, multiple, classes, collapse = true, wide = false }
+    {content, multiple, classes, collapse = true, wide = false}
   ) =>
     content
       ? html.tag(
-          "div",
+          'div',
           {
             id,
             class: [
-              "sidebar-column",
-              "sidebar",
-              wide && "wide",
-              !collapse && "no-hide",
+              'sidebar-column',
+              'sidebar',
+              wide && 'wide',
+              !collapse && 'no-hide',
               ...classes,
             ],
           },
@@ -1016,28 +1015,28 @@ writePage.html = (
         )
       : multiple
       ? html.tag(
-          "div",
+          'div',
           {
             id,
             class: [
-              "sidebar-column",
-              "sidebar-multiple",
-              wide && "wide",
-              !collapse && "no-hide",
+              'sidebar-column',
+              'sidebar-multiple',
+              wide && 'wide',
+              !collapse && 'no-hide',
             ],
           },
           multiple.map((content) =>
-            html.tag("div", { class: ["sidebar", ...classes] }, content)
+            html.tag('div', {class: ['sidebar', ...classes]}, content)
           )
         )
-      : "";
+      : '';
 
-  const sidebarLeftHTML = generateSidebarHTML("sidebar-left", sidebarLeft);
-  const sidebarRightHTML = generateSidebarHTML("sidebar-right", sidebarRight);
+  const sidebarLeftHTML = generateSidebarHTML('sidebar-left', sidebarLeft);
+  const sidebarRightHTML = generateSidebarHTML('sidebar-right', sidebarRight);
 
   if (nav.simple) {
-    nav.linkContainerClasses = ["nav-links-hierarchy"];
-    nav.links = [{ toHome: true }, { toCurrentPage: true }];
+    nav.linkContainerClasses = ['nav-links-hierarchy'];
+    nav.links = [{toHome: true}, {toCurrentPage: true}];
   }
 
   const links = (nav.links || []).filter(Boolean);
@@ -1048,7 +1047,7 @@ writePage.html = (
     const prev = links[i - 1];
     const next = links[i + 1];
 
-    let { title: linkTitle } = cur;
+    let {title: linkTitle} = cur;
 
     if (cur.toHome) {
       linkTitle ??= wikiInfo.nameShort;
@@ -1058,7 +1057,7 @@ writePage.html = (
 
     let partContent;
 
-    if (typeof cur.html === "string") {
+    if (typeof cur.html === 'string') {
       if (!cur.html) {
         logWarn`Empty HTML in nav link ${JSON.stringify(cur)}`;
         console.trace();
@@ -1066,11 +1065,11 @@ writePage.html = (
       partContent = cur.html;
     } else {
       const attributes = {
-        class: (cur.toCurrentPage || i === links.length - 1) && "current",
+        class: (cur.toCurrentPage || i === links.length - 1) && 'current',
         href: cur.toCurrentPage
-          ? ""
+          ? ''
           : cur.toHome
-          ? to("localized.home")
+          ? to('localized.home')
           : cur.path
           ? to(...cur.path)
           : cur.href
@@ -1087,12 +1086,12 @@ writePage.html = (
           )})`
         );
       }
-      partContent = html.tag("a", attributes, linkTitle);
+      partContent = html.tag('a', attributes, linkTitle);
     }
 
     const part = html.tag(
-      "span",
-      { class: cur.divider === false && "no-divider" },
+      'span',
+      {class: cur.divider === false && 'no-divider'},
       partContent
     );
 
@@ -1100,35 +1099,35 @@ writePage.html = (
   }
 
   const navHTML = html.tag(
-    "nav",
+    'nav',
     {
       [html.onlyIfContent]: true,
-      id: "header",
+      id: 'header',
       class: [
         ...nav.classes,
-        links.length && "nav-has-main-links",
-        nav.content && "nav-has-content",
-        nav.bottomRowContent && "nav-has-bottom-row",
+        links.length && 'nav-has-main-links',
+        nav.content && 'nav-has-content',
+        nav.bottomRowContent && 'nav-has-bottom-row',
       ],
     },
     [
       links.length &&
         html.tag(
-          "div",
-          { class: ["nav-main-links", ...nav.linkContainerClasses] },
+          'div',
+          {class: ['nav-main-links', ...nav.linkContainerClasses]},
           navLinkParts
         ),
-      nav.content && html.tag("div", { class: "nav-content" }, nav.content),
+      nav.content && html.tag('div', {class: 'nav-content'}, nav.content),
       nav.bottomRowContent &&
-        html.tag("div", { class: "nav-bottom-row" }, nav.bottomRowContent),
+        html.tag('div', {class: 'nav-bottom-row'}, nav.bottomRowContent),
     ]
   );
 
   const secondaryNavHTML = html.tag(
-    "nav",
+    'nav',
     {
       [html.onlyIfContent]: true,
-      id: "secondary-nav",
+      id: 'secondary-nav',
       class: secondaryNav.classes,
     },
     [secondaryNav.content]
@@ -1144,12 +1143,12 @@ writePage.html = (
     banner.position &&
     bannerSrc &&
     html.tag(
-      "div",
+      'div',
       {
-        id: "banner",
+        id: 'banner',
         class: banner.classes,
       },
-      html.tag("img", {
+      html.tag('img', {
         src: bannerSrc,
         alt: banner.alt,
         width: banner.dimensions[0] || 1100,
@@ -1159,18 +1158,18 @@ writePage.html = (
 
   const layoutHTML = [
     navHTML,
-    banner.position === "top" && bannerHTML,
+    banner.position === 'top' && bannerHTML,
     secondaryNavHTML,
     html.tag(
-      "div",
-      { class: ["layout-columns", !collapseSidebars && "vertical-when-thin"] },
+      'div',
+      {class: ['layout-columns', !collapseSidebars && 'vertical-when-thin']},
       [sidebarLeftHTML, mainHTML, sidebarRightHTML]
     ),
-    banner.position === "bottom" && bannerHTML,
+    banner.position === 'bottom' && bannerHTML,
     footerHTML,
   ]
     .filter(Boolean)
-    .join("\n");
+    .join('\n');
 
   const infoCardHTML = fixWS`
         <div id="info-card-container">
@@ -1178,36 +1177,36 @@ writePage.html = (
                 <div class="info-card">
                     <div class="info-card-art-container no-reveal">
                         ${img({
-                          class: "info-card-art",
-                          src: "",
+                          class: 'info-card-art',
+                          src: '',
                           link: true,
                           square: true,
                         })}
                     </div>
                     <div class="info-card-art-container reveal">
                         ${img({
-                          class: "info-card-art",
-                          src: "",
+                          class: 'info-card-art',
+                          src: '',
                           link: true,
                           square: true,
                           reveal: getRevealStringFromWarnings(
                             '<span class="info-card-art-warnings"></span>',
-                            { language }
+                            {language}
                           ),
                         })}
                     </div>
                     <h1 class="info-card-name"><a></a></h1>
                     <p class="info-card-album">${language.$(
-                      "releaseInfo.from",
-                      { album: "<a></a>" }
+                      'releaseInfo.from',
+                      {album: '<a></a>'}
                     )}</p>
                     <p class="info-card-artists">${language.$(
-                      "releaseInfo.by",
-                      { artists: "<span></span>" }
+                      'releaseInfo.by',
+                      {artists: '<span></span>'}
                     )}</p>
                     <p class="info-card-cover-artists">${language.$(
-                      "releaseInfo.coverArtBy",
-                      { artists: "<span></span>" }
+                      'releaseInfo.coverArtBy',
+                      {artists: '<span></span>'}
                     )}</p>
                 </div>
             </div>
@@ -1216,47 +1215,47 @@ writePage.html = (
 
   const socialEmbedHTML = [
     socialEmbed.title &&
-      html.tag("meta", { property: "og:title", content: socialEmbed.title }),
+      html.tag('meta', {property: 'og:title', content: socialEmbed.title}),
     socialEmbed.description &&
-      html.tag("meta", {
-        property: "og:description",
+      html.tag('meta', {
+        property: 'og:description',
         content: socialEmbed.description,
       }),
     socialEmbed.image &&
-      html.tag("meta", { property: "og:image", content: socialEmbed.image }),
+      html.tag('meta', {property: 'og:image', content: socialEmbed.image}),
     socialEmbed.color &&
-      html.tag("meta", { name: "theme-color", content: socialEmbed.color }),
+      html.tag('meta', {name: 'theme-color', content: socialEmbed.color}),
     oEmbedJSONHref &&
-      html.tag("link", {
-        type: "application/json+oembed",
+      html.tag('link', {
+        type: 'application/json+oembed',
         href: oEmbedJSONHref,
       }),
   ]
     .filter(Boolean)
-    .join("\n");
+    .join('\n');
 
   return filterEmptyLines(fixWS`
         <!DOCTYPE html>
         <html ${html.attributes({
           lang: language.intlCode,
-          "data-language-code": language.code,
-          "data-url-key": paths.toPath[0],
+          'data-language-code': language.code,
+          'data-url-key': paths.toPath[0],
           ...Object.fromEntries(
-            paths.toPath.slice(1).map((v, i) => [["data-url-value" + i], v])
+            paths.toPath.slice(1).map((v, i) => [['data-url-value' + i], v])
           ),
-          "data-rebase-localized": to("localized.root"),
-          "data-rebase-shared": to("shared.root"),
-          "data-rebase-media": to("media.root"),
-          "data-rebase-data": to("data.root"),
+          'data-rebase-localized': to('localized.root'),
+          'data-rebase-shared': to('shared.root'),
+          'data-rebase-media': to('media.root'),
+          'data-rebase-data': to('data.root'),
         })}>
             <head>
                 <title>${
                   showWikiNameInTitle
-                    ? language.formatString("misc.pageTitle.withWikiName", {
+                    ? language.formatString('misc.pageTitle.withWikiName', {
                         title,
                         wikiName: wikiInfo.nameShort,
                       })
-                    : language.formatString("misc.pageTitle", { title })
+                    : language.formatString('misc.pageTitle', {title})
                 }</title>
                 <meta charset="utf-8">
                 <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -1266,17 +1265,17 @@ writePage.html = (
                     ([key, value]) =>
                       `<meta ${key}="${html.escapeAttributeValue(value)}">`
                   )
-                  .join("\n")}
+                  .join('\n')}
                 ${canonical && `<link rel="canonical" href="${canonical}">`}
                 ${localizedCanonical
                   .map(
-                    ({ lang, href }) =>
+                    ({lang, href}) =>
                       `<link rel="alternate" hreflang="${lang}" href="${href}">`
                   )
-                  .join("\n")}
+                  .join('\n')}
                 ${socialEmbedHTML}
                 <link rel="stylesheet" href="${to(
-                  "shared.staticFile",
+                  'shared.staticFile',
                   `site.css?${CACHEBUST}`
                 )}">
                 ${
@@ -1289,11 +1288,11 @@ writePage.html = (
                 `
                 }
                 <script src="${to(
-                  "shared.staticFile",
+                  'shared.staticFile',
                   `lazy-loading.js?${CACHEBUST}`
                 )}"></script>
             </head>
-            <body ${html.attributes({ style: body.style || "" })}>
+            <body ${html.attributes({style: body.style || ''})}>
                 <div id="page-container">
                     ${
                       mainHTML &&
@@ -1301,28 +1300,28 @@ writePage.html = (
                         <div id="skippers">
                             ${[
                               [
-                                "#content",
-                                language.$("misc.skippers.skipToContent"),
+                                '#content',
+                                language.$('misc.skippers.skipToContent'),
                               ],
                               sidebarLeftHTML && [
-                                "#sidebar-left",
+                                '#sidebar-left',
                                 sidebarRightHTML
                                   ? language.$(
-                                      "misc.skippers.skipToSidebar.left"
+                                      'misc.skippers.skipToSidebar.left'
                                     )
-                                  : language.$("misc.skippers.skipToSidebar"),
+                                  : language.$('misc.skippers.skipToSidebar'),
                               ],
                               sidebarRightHTML && [
-                                "#sidebar-right",
+                                '#sidebar-right',
                                 sidebarLeftHTML
                                   ? language.$(
-                                      "misc.skippers.skipToSidebar.right"
+                                      'misc.skippers.skipToSidebar.right'
                                     )
-                                  : language.$("misc.skippers.skipToSidebar"),
+                                  : language.$('misc.skippers.skipToSidebar'),
                               ],
                               footerHTML && [
-                                "#footer",
-                                language.$("misc.skippers.skipToFooter"),
+                                '#footer',
+                                language.$('misc.skippers.skipToFooter'),
                               ],
                             ]
                               .filter(Boolean)
@@ -1331,7 +1330,7 @@ writePage.html = (
                                 <span class="skipper"><a href="${href}">${title}</a></span>
                             `
                               )
-                              .join("\n")}
+                              .join('\n')}
                         </div>
                     `
                     }
@@ -1339,7 +1338,7 @@ writePage.html = (
                 </div>
                 ${infoCardHTML}
                 <script type="module" src="${to(
-                  "shared.staticFile",
+                  'shared.staticFile',
                   `client.js?${CACHEBUST}`
                 )}"></script>
             </body>
@@ -1347,37 +1346,37 @@ writePage.html = (
     `);
 };
 
-writePage.oEmbedJSON = (pageInfo, { language, wikiData }) => {
-  const { socialEmbed } = pageInfo;
-  const { wikiInfo } = wikiData;
-  const { canonicalBase, nameShort } = wikiInfo;
+writePage.oEmbedJSON = (pageInfo, {language, wikiData}) => {
+  const {socialEmbed} = pageInfo;
+  const {wikiInfo} = wikiData;
+  const {canonicalBase, nameShort} = wikiInfo;
 
-  if (!socialEmbed) return "";
+  if (!socialEmbed) return '';
 
   const entries = [
     socialEmbed.heading && [
-      "author_name",
-      language.$("misc.socialEmbed.heading", {
+      'author_name',
+      language.$('misc.socialEmbed.heading', {
         wikiName: nameShort,
         heading: socialEmbed.heading,
       }),
     ],
     socialEmbed.headingLink &&
       canonicalBase && [
-        "author_url",
-        canonicalBase.replace(/\/$/, "") +
-          "/" +
-          socialEmbed.headingLink.replace(/^\//, ""),
+        'author_url',
+        canonicalBase.replace(/\/$/, '') +
+          '/' +
+          socialEmbed.headingLink.replace(/^\//, ''),
       ],
   ].filter(Boolean);
 
-  if (!entries.length) return "";
+  if (!entries.length) return '';
 
   return JSON.stringify(Object.fromEntries(entries));
 };
 
-writePage.write = async ({ html, oEmbedJSON = "", paths }) => {
-  await mkdir(paths.outputDirectory, { recursive: true });
+writePage.write = async ({html, oEmbedJSON = '', paths}) => {
+  await mkdir(paths.outputDirectory, {recursive: true});
   await Promise.all(
     [
       writeFile(paths.outputFile, html),
@@ -1390,25 +1389,25 @@ writePage.write = async ({ html, oEmbedJSON = "", paths }) => {
 writePage.paths = (
   baseDirectory,
   fullKey,
-  directory = "",
-  { file = "index.html" } = {}
+  directory = '',
+  {file = 'index.html'} = {}
 ) => {
-  const [groupKey, subKey] = fullKey.split(".");
+  const [groupKey, subKey] = fullKey.split('.');
 
   const pathname =
-    groupKey === "localized" && baseDirectory
+    groupKey === 'localized' && baseDirectory
       ? urls
-          .from("shared.root")
+          .from('shared.root')
           .toDevice(
-            "localizedWithBaseDirectory." + subKey,
+            'localizedWithBaseDirectory.' + subKey,
             baseDirectory,
             directory
           )
-      : urls.from("shared.root").toDevice(fullKey, directory);
+      : urls.from('shared.root').toDevice(fullKey, directory);
 
   // Needed for the rare directory which itself contains a slash, e.g. for
   // listings, with directories like 'albums/by-name'.
-  const subdirectoryPrefix = "../".repeat(directory.split("/").length - 1);
+  const subdirectoryPrefix = '../'.repeat(directory.split('/').length - 1);
 
   const outputDirectory = path.join(outputPath, pathname);
   const outputFile = path.join(outputDirectory, file);
@@ -1445,74 +1444,74 @@ async function writeFavicon() {
 }
 
 function writeSymlinks() {
-  return progressPromiseAll("Writing site symlinks.", [
-    link(path.join(__dirname, UTILITY_DIRECTORY), "shared.utilityRoot"),
-    link(path.join(__dirname, STATIC_DIRECTORY), "shared.staticRoot"),
-    link(mediaPath, "media.root"),
+  return progressPromiseAll('Writing site symlinks.', [
+    link(path.join(__dirname, UTILITY_DIRECTORY), 'shared.utilityRoot'),
+    link(path.join(__dirname, STATIC_DIRECTORY), 'shared.staticRoot'),
+    link(mediaPath, 'media.root'),
   ]);
 
   async function link(directory, urlKey) {
-    const pathname = urls.from("shared.root").toDevice(urlKey);
+    const pathname = urls.from('shared.root').toDevice(urlKey);
     const file = path.join(outputPath, pathname);
     try {
       await unlink(file);
     } catch (error) {
-      if (error.code !== "ENOENT") {
+      if (error.code !== 'ENOENT') {
         throw error;
       }
     }
     try {
       await symlink(path.resolve(directory), file);
     } catch (error) {
-      if (error.code === "EPERM") {
-        await symlink(path.resolve(directory), file, "junction");
+      if (error.code === 'EPERM') {
+        await symlink(path.resolve(directory), file, 'junction');
       }
     }
   }
 }
 
-function writeSharedFilesAndPages({ language, wikiData }) {
-  const { groupData, wikiInfo } = wikiData;
+function writeSharedFilesAndPages({language, wikiData}) {
+  const {groupData, wikiInfo} = wikiData;
 
   const redirect = async (title, from, urlKey, directory) => {
     const target = path.relative(
       from,
-      urls.from("shared.root").to(urlKey, directory)
+      urls.from('shared.root').to(urlKey, directory)
     );
-    const content = generateRedirectPage(title, target, { language });
-    await mkdir(path.join(outputPath, from), { recursive: true });
-    await writeFile(path.join(outputPath, from, "index.html"), content);
+    const content = generateRedirectPage(title, target, {language});
+    await mkdir(path.join(outputPath, from), {recursive: true});
+    await writeFile(path.join(outputPath, from, 'index.html'), content);
   };
 
   return progressPromiseAll(
     `Writing files & pages shared across languages.`,
     [
-      groupData?.some((group) => group.directory === "fandom") &&
+      groupData?.some((group) => group.directory === 'fandom') &&
         redirect(
-          "Fandom - Gallery",
-          "albums/fandom",
-          "localized.groupGallery",
-          "fandom"
+          'Fandom - Gallery',
+          'albums/fandom',
+          'localized.groupGallery',
+          'fandom'
         ),
 
-      groupData?.some((group) => group.directory === "official") &&
+      groupData?.some((group) => group.directory === 'official') &&
         redirect(
-          "Official - Gallery",
-          "albums/official",
-          "localized.groupGallery",
-          "official"
+          'Official - Gallery',
+          'albums/official',
+          'localized.groupGallery',
+          'official'
         ),
 
       wikiInfo.enableListings &&
         redirect(
-          "Album Commentary",
-          "list/all-commentary",
-          "localized.commentaryIndex",
-          ""
+          'Album Commentary',
+          'list/all-commentary',
+          'localized.commentaryIndex',
+          ''
         ),
 
       writeFile(
-        path.join(outputPath, "data.json"),
+        path.join(outputPath, 'data.json'),
         fixWS`
             {
                 "albumData": ${stringifyThings(wikiData.albumData)},
@@ -1528,12 +1527,12 @@ function writeSharedFilesAndPages({ language, wikiData }) {
   );
 }
 
-function generateRedirectPage(title, target, { language }) {
+function generateRedirectPage(title, target, {language}) {
   return fixWS`
         <!DOCTYPE html>
         <html>
             <head>
-                <title>${language.$("redirectPage.title", { title })}</title>
+                <title>${language.$('redirectPage.title', {title})}</title>
                 <meta charset="utf-8">
                 <meta http-equiv="refresh" content="0;url=${target}">
                 <link rel="canonical" href="${target}">
@@ -1541,8 +1540,8 @@ function generateRedirectPage(title, target, { language }) {
             </head>
             <body>
                 <main>
-                    <h1>${language.$("redirectPage.title", { title })}</h1>
-                    <p>${language.$("redirectPage.infoLine", {
+                    <h1>${language.$('redirectPage.title', {title})}</h1>
+                    <p>${language.$('redirectPage.infoLine', {
                       target: `<a href="${target}">${target}</a>`,
                     })}</p>
                 </main>
@@ -1553,41 +1552,41 @@ function generateRedirectPage(title, target, { language }) {
 
 // RIP toAnythingMan (previously getHrefOfAnythingMan), 2020-05-25<>2021-05-14.
 // ........Yet the function 8reathes life anew as linkAnythingMan! ::::)
-function linkAnythingMan(anythingMan, { link, wikiData, ...opts }) {
+function linkAnythingMan(anythingMan, {link, wikiData, ...opts}) {
   return wikiData.albumData.includes(anythingMan)
     ? link.album(anythingMan, opts)
     : wikiData.trackData.includes(anythingMan)
     ? link.track(anythingMan, opts)
     : wikiData.flashData?.includes(anythingMan)
     ? link.flash(anythingMan, opts)
-    : "idk bud";
+    : 'idk bud';
 }
 
 async function processLanguageFile(file) {
-  const contents = await readFile(file, "utf-8");
+  const contents = await readFile(file, 'utf-8');
   const json = JSON.parse(contents);
 
-  const code = json["meta.languageCode"];
+  const code = json['meta.languageCode'];
   if (!code) {
     throw new Error(`Missing language code (file: ${file})`);
   }
-  delete json["meta.languageCode"];
+  delete json['meta.languageCode'];
 
-  const intlCode = json["meta.languageIntlCode"] ?? null;
-  delete json["meta.languageIntlCode"];
+  const intlCode = json['meta.languageIntlCode'] ?? null;
+  delete json['meta.languageIntlCode'];
 
-  const name = json["meta.languageName"];
+  const name = json['meta.languageName'];
   if (!name) {
     throw new Error(`Missing language name (${code})`);
   }
-  delete json["meta.languageName"];
+  delete json['meta.languageName'];
 
-  const hidden = json["meta.hidden"] ?? false;
-  delete json["meta.hidden"];
+  const hidden = json['meta.hidden'] ?? false;
+  delete json['meta.hidden'];
 
-  if (json["meta.baseDirectory"]) {
+  if (json['meta.baseDirectory']) {
     logWarn`(${code}) Language JSON still has unused meta.baseDirectory`;
-    delete json["meta.baseDirectory"];
+    delete json['meta.baseDirectory'];
   }
 
   const language = new Language();
@@ -1596,18 +1595,18 @@ async function processLanguageFile(file) {
   language.name = name;
   language.hidden = hidden;
   language.escapeHTML = (string) =>
-    he.encode(string, { useNamedReferences: true });
+    he.encode(string, {useNamedReferences: true});
   language.strings = json;
   return language;
 }
 
 // Wrapper function for running a function once for all languages.
-async function wrapLanguages(fn, { languages, writeOneLanguage = null }) {
+async function wrapLanguages(fn, {languages, writeOneLanguage = null}) {
   const k = writeOneLanguage;
-  const languagesToRun = k ? { [k]: languages[k] } : languages;
+  const languagesToRun = k ? {[k]: languages[k]} : languages;
 
   const entries = Object.entries(languagesToRun).filter(
-    ([key]) => key !== "default"
+    ([key]) => key !== 'default'
   );
 
   for (let i = 0; i < entries.length; i++) {
@@ -1629,15 +1628,15 @@ async function main() {
     // Data files for the site, including flash, artist, and al8um data,
     // and like a jillion other things too. Pretty much everything which
     // makes an individual wiki what it is goes here!
-    "data-path": {
-      type: "value",
+    'data-path': {
+      type: 'value',
     },
 
     // Static media will 8e referenced in the site here! The contents are
     // categorized; check out MEDIA_ALBUM_ART_DIRECTORY and other constants
     // near the top of this file (upd8.js).
-    "media-path": {
-      type: "value",
+    'media-path': {
+      type: 'value',
     },
 
     // String files! For the most part, this is used for translating the
@@ -1650,8 +1649,8 @@ async function main() {
     // Unlike the other options here, this one's optional - the site will
     // 8uild with the default (English) strings if this path is left
     // unspecified.
-    "lang-path": {
-      type: "value",
+    'lang-path': {
+      type: 'value',
     },
 
     // This is the output directory. It's the one you'll upload online with
@@ -1660,78 +1659,78 @@ async function main() {
     // site. Just keep in mind that the gener8ted result will contain a
     // couple symlinked directories, so if you're uploading, you're pro8a8ly
     // gonna want to resolve those yourself.
-    "out-path": {
-      type: "value",
+    'out-path': {
+      type: 'value',
     },
 
     // Thum8nail gener8tion is *usually* something you want, 8ut it can 8e
     // kinda a pain to run every time, since it does necessit8te reading
     // every media file at run time. Pass this to skip it.
-    "skip-thumbs": {
-      type: "flag",
+    'skip-thumbs': {
+      type: 'flag',
     },
 
     // Or, if you *only* want to gener8te newly upd8ted thum8nails, you can
     // pass this flag! It exits 8efore 8uilding the rest of the site.
-    "thumbs-only": {
-      type: "flag",
+    'thumbs-only': {
+      type: 'flag',
     },
 
     // Just working on data entries and not interested in actually
     // generating site HTML yet? This flag will cut execution off right
     // 8efore any site 8uilding actually happens.
-    "no-build": {
-      type: "flag",
+    'no-build': {
+      type: 'flag',
     },
 
     // Only want to 8uild one language during testing? This can chop down
     // 8uild times a pretty 8ig chunk! Just pass a single language code.
     lang: {
-      type: "value",
+      type: 'value',
     },
 
     // Working without a dev server and just using file:// URLs in your we8
     // 8rowser? This will automatically append index.html to links across
     // the site. Not recommended for production, since it isn't guaranteed
     // 100% error-free (and index.html-style links are less pretty anyway).
-    "append-index-html": {
-      type: "flag",
+    'append-index-html': {
+      type: 'flag',
     },
 
     // Want sweet, sweet trace8ack info in aggreg8te error messages? This
     // will print all the juicy details (or at least the first relevant
     // line) right to your output, 8ut also pro8a8ly give you a headache
     // 8ecause wow that is a lot of visual noise.
-    "show-traces": {
-      type: "flag",
+    'show-traces': {
+      type: 'flag',
     },
 
-    "queue-size": {
-      type: "value",
+    'queue-size': {
+      type: 'value',
       validate(size) {
-        if (parseInt(size) !== parseFloat(size)) return "an integer";
-        if (parseInt(size) < 0) return "a counting number or zero";
+        if (parseInt(size) !== parseFloat(size)) return 'an integer';
+        if (parseInt(size) < 0) return 'a counting number or zero';
         return true;
       },
     },
-    queue: { alias: "queue-size" },
+    queue: {alias: 'queue-size'},
 
     // This option is super slow and has the potential for bugs! It puts
     // CacheableObject in a mode where every instance is a Proxy which will
     // keep track of invalid property accesses.
-    "show-invalid-property-accesses": {
-      type: "flag",
+    'show-invalid-property-accesses': {
+      type: 'flag',
     },
 
     [parseOptions.handleUnknown]: () => {},
   });
 
-  dataPath = miscOptions["data-path"] || process.env.HSMUSIC_DATA;
-  mediaPath = miscOptions["media-path"] || process.env.HSMUSIC_MEDIA;
-  langPath = miscOptions["lang-path"] || process.env.HSMUSIC_LANG; // Can 8e left unset!
-  outputPath = miscOptions["out-path"] || process.env.HSMUSIC_OUT;
+  dataPath = miscOptions['data-path'] || process.env.HSMUSIC_DATA;
+  mediaPath = miscOptions['media-path'] || process.env.HSMUSIC_MEDIA;
+  langPath = miscOptions['lang-path'] || process.env.HSMUSIC_LANG; // Can 8e left unset!
+  outputPath = miscOptions['out-path'] || process.env.HSMUSIC_OUT;
 
-  const writeOneLanguage = miscOptions["lang"];
+  const writeOneLanguage = miscOptions['lang'];
 
   {
     let errored = false;
@@ -1752,16 +1751,16 @@ async function main() {
     }
   }
 
-  const appendIndexHTML = miscOptions["append-index-html"] ?? false;
+  const appendIndexHTML = miscOptions['append-index-html'] ?? false;
   if (appendIndexHTML) {
     logWarn`Appending index.html to link hrefs. (Note: not recommended for production release!)`;
     unbound_link.globalOptions.appendIndexHTML = true;
   }
 
-  const skipThumbs = miscOptions["skip-thumbs"] ?? false;
-  const thumbsOnly = miscOptions["thumbs-only"] ?? false;
-  const noBuild = miscOptions["no-build"] ?? false;
-  const showAggregateTraces = miscOptions["show-traces"] ?? false;
+  const skipThumbs = miscOptions['skip-thumbs'] ?? false;
+  const thumbsOnly = miscOptions['thumbs-only'] ?? false;
+  const noBuild = miscOptions['no-build'] ?? false;
+  const showAggregateTraces = miscOptions['show-traces'] ?? false;
 
   const niceShowAggregate = (error, ...opts) => {
     showAggregate(error, {
@@ -1780,45 +1779,45 @@ async function main() {
     logInfo`Skipping thumbnail generation.`;
   } else {
     logInfo`Begin thumbnail generation... -----+`;
-    const result = await genThumbs(mediaPath, { queueSize, quiet: true });
+    const result = await genThumbs(mediaPath, {queueSize, quiet: true});
     logInfo`Done thumbnail generation! --------+`;
     if (!result) return;
     if (thumbsOnly) return;
   }
 
   const showInvalidPropertyAccesses =
-    miscOptions["show-invalid-property-accesses"] ?? false;
+    miscOptions['show-invalid-property-accesses'] ?? false;
 
   if (showInvalidPropertyAccesses) {
     CacheableObject.DEBUG_SLOW_TRACK_INVALID_PROPERTIES = true;
   }
 
-  const { aggregate: processDataAggregate, result: wikiDataResult } =
-    await loadAndProcessDataDocuments({ dataPath });
+  const {aggregate: processDataAggregate, result: wikiDataResult} =
+    await loadAndProcessDataDocuments({dataPath});
 
   Object.assign(wikiData, wikiDataResult);
 
   {
     const logThings = (thingDataProp, label) =>
       logInfo` - ${
-        wikiData[thingDataProp]?.length ?? color.red("(Missing!)")
+        wikiData[thingDataProp]?.length ?? color.red('(Missing!)')
       } ${color.normal(color.dim(label))}`;
     try {
       logInfo`Loaded data and processed objects:`;
-      logThings("albumData", "albums");
-      logThings("trackData", "tracks");
-      logThings("artistData", "artists");
+      logThings('albumData', 'albums');
+      logThings('trackData', 'tracks');
+      logThings('artistData', 'artists');
       if (wikiData.flashData) {
-        logThings("flashData", "flashes");
-        logThings("flashActData", "flash acts");
+        logThings('flashData', 'flashes');
+        logThings('flashActData', 'flash acts');
       }
-      logThings("groupData", "groups");
-      logThings("groupCategoryData", "group categories");
-      logThings("artTagData", "art tags");
+      logThings('groupData', 'groups');
+      logThings('groupCategoryData', 'group categories');
+      logThings('artTagData', 'art tags');
       if (wikiData.newsData) {
-        logThings("newsData", "news entries");
+        logThings('newsData', 'news entries');
       }
-      logThings("staticPageData", "static pages");
+      logThings('staticPageData', 'static pages');
       if (wikiData.homepageLayout) {
         logInfo` - ${1} homepage layout (${
           wikiData.homepageLayout.rows.length
@@ -1925,7 +1924,7 @@ async function main() {
   let languages;
   if (langPath) {
     const languageDataFiles = await findFiles(langPath, {
-      filter: (f) => path.extname(f) === ".json",
+      filter: (f) => path.extname(f) === '.json',
     });
 
     const results = await progressPromiseAll(
@@ -1953,7 +1952,7 @@ async function main() {
     if (langPath) {
       logError`Check if an appropriate file exists in ${langPath}?`;
     } else {
-      logError`Be sure to specify ${"--lang"} or ${"HSMUSIC_LANG"} with the path to language files.`;
+      logError`Be sure to specify ${'--lang'} or ${'HSMUSIC_LANG'} with the path to language files.`;
     }
     return;
   } else {
@@ -1969,7 +1968,7 @@ async function main() {
     language.inheritedStrings = finalDefaultLanguage.strings;
   }
 
-  logInfo`Loaded language strings: ${Object.keys(languages).join(", ")}`;
+  logInfo`Loaded language strings: ${Object.keys(languages).join(', ')}`;
 
   if (noBuild) {
     logInfo`Not generating any site or page files this run (--no-build passed).`;
@@ -2030,19 +2029,19 @@ async function main() {
           device: path.join(
             mediaPath,
             urls
-              .from("media.root")
-              .toDevice("media.albumAdditionalFile", album.directory, file)
+              .from('media.root')
+              .toDevice('media.albumAdditionalFile', album.directory, file)
           ),
           media: urls
-            .from("media.root")
-            .to("media.albumAdditionalFile", album.directory, file),
+            .from('media.root')
+            .to('media.albumAdditionalFile', album.directory, file),
         }))
     ),
   ];
 
   const getSizeOfAdditionalFile = (mediaPath) => {
-    const { device = null } =
-      additionalFilePaths.find(({ media }) => media === mediaPath) || {};
+    const {device = null} =
+      additionalFilePaths.find(({media}) => media === mediaPath) || {};
     if (!device) return null;
     return fileSizePreloader.getSizeOfPath(device);
   };
@@ -2062,7 +2061,7 @@ async function main() {
   // performance right now 'cuz it'll w8 for file writes to 8e completed
   // 8efore moving on to more data processing. So, defaults to zero, which
   // disa8les the queue feature altogether.
-  queueSize = +(miscOptions["queue-size"] ?? 0);
+  queueSize = +(miscOptions['queue-size'] ?? 0);
 
   const buildDictionary = pageSpecs;
 
@@ -2072,11 +2071,11 @@ async function main() {
   // on some particular area(s) of the site rather than making changes
   // across all of them.
   const writeFlags = await parseOptions(process.argv.slice(2), {
-    all: { type: "flag" }, // Defaults to true if none 8elow specified.
+    all: {type: 'flag'}, // Defaults to true if none 8elow specified.
 
     // Kinda a hack t8h!
     ...Object.fromEntries(
-      Object.keys(buildDictionary).map((key) => [key, { type: "flag" }])
+      Object.keys(buildDictionary).map((key) => [key, {type: 'flag'}])
     ),
 
     [parseOptions.handleUnknown]: () => {},
@@ -2085,12 +2084,12 @@ async function main() {
   const writeAll = !Object.keys(writeFlags).length || writeFlags.all;
 
   logInfo`Writing site pages: ${
-    writeAll ? "all" : Object.keys(writeFlags).join(", ")
+    writeAll ? 'all' : Object.keys(writeFlags).join(', ')
   }`;
 
   await writeFavicon();
   await writeSymlinks();
-  await writeSharedFilesAndPages({ language: finalDefaultLanguage, wikiData });
+  await writeSharedFilesAndPages({language: finalDefaultLanguage, wikiData});
 
   const buildSteps = writeAll
     ? Object.entries(buildDictionary)
@@ -2103,33 +2102,33 @@ async function main() {
     const buildStepsWithTargets = buildSteps
       .map(([flag, pageSpec]) => {
         // Condition not met: skip this build step altogether.
-        if (pageSpec.condition && !pageSpec.condition({ wikiData })) {
+        if (pageSpec.condition && !pageSpec.condition({wikiData})) {
           return null;
         }
 
         // May still call writeTargetless if present.
         if (!pageSpec.targets) {
-          return { flag, pageSpec, targets: [] };
+          return {flag, pageSpec, targets: []};
         }
 
         if (!pageSpec.write) {
-          logError`${flag + ".targets"} is specified, but ${
-            flag + ".write"
+          logError`${flag + '.targets'} is specified, but ${
+            flag + '.write'
           } is missing!`;
           error = true;
           return null;
         }
 
-        const targets = pageSpec.targets({ wikiData });
+        const targets = pageSpec.targets({wikiData});
         if (!Array.isArray(targets)) {
           logError`${
-            flag + ".targets"
+            flag + '.targets'
           } was called, but it didn't return an array! (${typeof targets})`;
           error = true;
           return null;
         }
 
-        return { flag, pageSpec, targets };
+        return {flag, pageSpec, targets};
       })
       .filter(Boolean);
 
@@ -2149,7 +2148,7 @@ async function main() {
 
       if (
         !(
-          writes.every((obj) => typeof obj === "object") &&
+          writes.every((obj) => typeof obj === 'object') &&
           writes.every((obj) => {
             const result = validateWriteObject(obj);
             if (result.error) {
@@ -2171,19 +2170,19 @@ async function main() {
 
     // return;
 
-    writes = buildStepsWithTargets.flatMap(({ flag, pageSpec, targets }) => {
+    writes = buildStepsWithTargets.flatMap(({flag, pageSpec, targets}) => {
       const writes = targets.flatMap(
-        (target) => pageSpec.write(target, { wikiData })?.slice() || []
+        (target) => pageSpec.write(target, {wikiData})?.slice() || []
       );
 
-      if (!validateWrites(writes, flag + ".write")) {
+      if (!validateWrites(writes, flag + '.write')) {
         return [];
       }
 
       if (pageSpec.writeTargetless) {
-        const writes2 = pageSpec.writeTargetless({ wikiData });
+        const writes2 = pageSpec.writeTargetless({wikiData});
 
-        if (!validateWrites(writes2, flag + ".writeTargetless")) {
+        if (!validateWrites(writes2, flag + '.writeTargetless')) {
           return [];
         }
 
@@ -2198,9 +2197,9 @@ async function main() {
     }
   }
 
-  const pageWrites = writes.filter(({ type }) => type === "page");
-  const dataWrites = writes.filter(({ type }) => type === "data");
-  const redirectWrites = writes.filter(({ type }) => type === "redirect");
+  const pageWrites = writes.filter(({type}) => type === 'page');
+  const dataWrites = writes.filter(({type}) => type === 'data');
+  const redirectWrites = writes.filter(({type}) => type === 'redirect');
 
   if (writes.length) {
     logInfo`Total of ${writes.length} writes returned. (${pageWrites.length} page, ${dataWrites.length} data [currently skipped], ${redirectWrites.length} redirect)`;
@@ -2247,20 +2246,20 @@ async function main() {
 
   const perLanguageFn = async (language, i, entries) => {
     const baseDirectory =
-      language === finalDefaultLanguage ? "" : language.code;
+      language === finalDefaultLanguage ? '' : language.code;
 
     console.log(
       `\x1b[34;1m${`[${i + 1}/${entries.length}] ${
         language.code
-      } (-> /${baseDirectory}) `.padEnd(60, "-")}\x1b[0m`
+      } (-> /${baseDirectory}) `.padEnd(60, '-')}\x1b[0m`
     );
 
     await progressPromiseAll(
       `Writing ${language.code}`,
       queue(
         [
-          ...pageWrites.map(({ type, ...props }) => () => {
-            const { path, page } = props;
+          ...pageWrites.map(({type, ...props}) => () => {
+            const {path, page} = props;
 
             // TODO: This only supports one <>-style argument.
             const pageSubKey = path[0];
@@ -2269,13 +2268,13 @@ async function main() {
             const localizedPaths = Object.fromEntries(
               Object.entries(languages)
                 .filter(
-                  ([key, language]) => key !== "default" && !language.hidden
+                  ([key, language]) => key !== 'default' && !language.hidden
                 )
                 .map(([key, language]) => [
                   language.code,
                   writePage.paths(
-                    language === finalDefaultLanguage ? "" : language.code,
-                    "localized." + pageSubKey,
+                    language === finalDefaultLanguage ? '' : language.code,
+                    'localized.' + pageSubKey,
                     directory
                   ),
                 ])
@@ -2283,7 +2282,7 @@ async function main() {
 
             const paths = writePage.paths(
               baseDirectory,
-              "localized." + pageSubKey,
+              'localized.' + pageSubKey,
               directory
             );
 
@@ -2294,13 +2293,13 @@ async function main() {
             });
 
             const absoluteTo = (targetFullKey, ...args) => {
-              const [groupKey, subKey] = targetFullKey.split(".");
-              const from = urls.from("shared.root");
+              const [groupKey, subKey] = targetFullKey.split('.');
+              const from = urls.from('shared.root');
               return (
-                "/" +
-                (groupKey === "localized" && baseDirectory
+                '/' +
+                (groupKey === 'localized' && baseDirectory
                   ? from.to(
-                      "localizedWithBaseDirectory." + subKey,
+                      'localizedWithBaseDirectory.' + subKey,
                       baseDirectory,
                       ...args
                     )
@@ -2314,7 +2313,7 @@ async function main() {
             const bound = {};
 
             bound.link = withEntries(unbound_link, (entries) =>
-              entries.map(([key, fn]) => [key, bindOpts(fn, { to })])
+              entries.map(([key, fn]) => [key, bindOpts(fn, {to})])
             );
 
             bound.linkAnythingMan = bindOpts(linkAnythingMan, {
@@ -2326,7 +2325,7 @@ async function main() {
               to,
             });
 
-            bound.find = bindFind(wikiData, { mode: "warn" });
+            bound.find = bindFind(wikiData, {mode: 'warn'});
 
             bound.transformInline = bindOpts(transformInline, {
               find: bound.find,
@@ -2501,8 +2500,8 @@ async function main() {
               wikiData.wikiInfo.canonicalBase &&
               wikiData.wikiInfo.canonicalBase +
                 urls
-                  .from("shared.root")
-                  .to("shared.path", paths.pathname + OEMBED_JSON_FILE);
+                  .from('shared.root')
+                  .to('shared.path', paths.pathname + OEMBED_JSON_FILE);
 
             const html = writePage.html(pageInfo, {
               defaultLanguage: finalDefaultLanguage,
@@ -2522,30 +2521,27 @@ async function main() {
               paths,
             });
           }),
-          ...redirectWrites.map(
-            ({ fromPath, toPath, title: titleFn }) =>
-              () => {
-                const title = titleFn({
-                  language,
-                });
-
-                // TODO: This only supports one <>-style argument.
-                const fromPaths = writePage.paths(
-                  baseDirectory,
-                  "localized." + fromPath[0],
-                  fromPath[1]
-                );
-                const to = writePage.to({
-                  baseDirectory,
-                  pageSubKey: fromPath[0],
-                  paths: fromPaths,
-                });
-
-                const target = to("localized." + toPath[0], ...toPath.slice(1));
-                const html = generateRedirectPage(title, target, { language });
-                return writePage.write({ html, paths: fromPaths });
-              }
-          ),
+          ...redirectWrites.map(({fromPath, toPath, title: titleFn}) => () => {
+            const title = titleFn({
+              language,
+            });
+
+            // TODO: This only supports one <>-style argument.
+            const fromPaths = writePage.paths(
+              baseDirectory,
+              'localized.' + fromPath[0],
+              fromPath[1]
+            );
+            const to = writePage.to({
+              baseDirectory,
+              pageSubKey: fromPath[0],
+              paths: fromPaths,
+            });
+
+            const target = to('localized.' + toPath[0], ...toPath.slice(1));
+            const html = generateRedirectPage(title, target, {language});
+            return writePage.write({html, paths: fromPaths});
+          }),
         ],
         queueSize
       )