« 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.js2251
1 files changed, 105 insertions, 2146 deletions
diff --git a/src/upd8.js b/src/upd8.js
index 79b5747b..21413a17 100755
--- a/src/upd8.js
+++ b/src/upd8.js
@@ -31,38 +31,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 {execSync} from 'child_process';
 import * as path from 'path';
 import {fileURLToPath} from 'url';
 
-import chroma from 'chroma-js';
-
-import {
-  copyFile,
-  mkdir,
-  stat,
-  symlink,
-  writeFile,
-  unlink,
-} from 'fs/promises';
-
-import { execSync } from 'child_process';
-
 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 {getColors} from './util/colors.js';
-import {findFiles} from './util/io.js';
-import {isMain} from './util/node-utils.js';
+import {processLanguageFile} from './data/language.js';
 
 import CacheableObject from './data/things/cacheable-object.js';
 
-import {processLanguageFile} from './data/language.js';
-import {serializeThings} from './data/serialize.js';
-
 import {
   filterDuplicateDirectories,
   filterReferenceErrors,
@@ -72,33 +52,17 @@ import {
   WIKI_INFO_FILE,
 } from './data/yaml.js';
 
-import {
-  fancifyFlashURL,
-  fancifyURL,
-  generateAdditionalFilesShortcut,
-  generateAdditionalFilesList,
-  generateChronologyLinks,
-  generateCoverLink,
-  generateInfoGalleryLinks,
-  generateNavigationLinks,
-  generateStickyHeadingContainer,
-  generateTrackListDividedByGroups,
-  getAlbumGridHTML,
-  getAlbumStylesheet,
-  getArtistString,
-  getFlashGridHTML,
-  getFooterLocalizationLinks,
-  getGridHTML,
-  getCarouselHTML,
-  getRevealStringFromTags,
-  getRevealStringFromWarnings,
-  getThemeString as unbound_getThemeString,
-  iconifyURL,
-} from './misc-templates.js';
-
-import unbound_link, {
-  getLinkThemeString as unbound_getLinkThemeString,
-} from './util/link.js';
+import find from './util/find.js';
+import {findFiles} from './util/io.js';
+import link from './util/link.js';
+import {isMain} from './util/node-utils.js';
+import {validateReplacerSpec} from './util/replacer.js';
+import {empty, showAggregate} from './util/sugar.js';
+import {replacerSpec} from './util/transform-content.js';
+import {generateURLs} from './util/urls.js';
+
+import {generateDevelopersCommentHTML} from './write/page-template.js';
+import * as buildModes from './write/build-modes/index.js';
 
 import {
   color,
@@ -111,38 +75,6 @@ import {
   progressPromiseAll,
 } from './util/cli.js';
 
-import {validateReplacerSpec, transformInline} from './util/replacer.js';
-
-import {
-  getAlbumCover,
-  getArtistAvatar,
-  getFlashCover,
-  getTrackCover,
-} from './util/wiki-data.js';
-
-/*
-import {
-  serializeContribs,
-  serializeCover,
-  serializeGroupsForAlbum,
-  serializeGroupsForTrack,
-  serializeImagePaths,
-  serializeLink,
-} from './util/serialize.js';
-*/
-
-import {
-  bindOpts,
-  queue,
-  showAggregate,
-  withEntries,
-} from './util/sugar.js';
-
-import {generateURLs, thumb} from './util/urls.js';
-
-// Pensive emoji!
-import { OFFICIAL_GROUP_DIRECTORY } from './util/magic-constants.js';
-
 import FileSizePreloader from './file-size-preloader.js';
 
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -160,1504 +92,46 @@ const BUILD_TIME = new Date();
 
 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
-// file~~ is now a variety of useful utilities!
-//
-// 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';
-
-// 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';
-
-// This exists adjacent to index.html for any page with oEmbed metadata.
-const OEMBED_JSON_FILE = 'oembed.json';
-
-// Automatically copied (if present) from media directory to site root.
-const FAVICON_FILE = 'favicon.ico';
-
-// Shared varia8les! These are more efficient to access than a shared varia8le
-// (or at least I h8pe so), and are easier to pass across functions than a
-// 8unch of specific arguments.
-//
-// Upd8: Okay yeah these aren't actually any different. Still cleaner than
-// passing around a data object containing all this, though.
-let dataPath;
-let mediaPath;
-let langPath;
-let outputPath;
-
-// Glo8al data o8ject shared 8etween 8uild functions and all that. This keeps
-// everything encapsul8ted in one place, so it's easy to pass and share across
-// modules!
-let wikiData = {};
-
-let queueSize;
-
-const urls = generateURLs(urlSpec);
-
-function splitLines(text) {
-  return text.split(/\r\n|\r|\n/);
-}
-
-const replacerSpec = {
-  album: {
-    find: 'album',
-    link: 'album',
-  },
-  'album-commentary': {
-    find: 'album',
-    link: 'albumCommentary',
-  },
-  'album-gallery': {
-    find: 'album',
-    link: 'albumGallery',
-  },
-  artist: {
-    find: 'artist',
-    link: 'artist',
-  },
-  'artist-gallery': {
-    find: 'artist',
-    link: 'artistGallery',
-  },
-  'commentary-index': {
-    find: null,
-    link: 'commentaryIndex',
-  },
-  date: {
-    find: null,
-    value: (ref) => new Date(ref),
-    html: (date, {language}) =>
-      html.tag('time',
-        {datetime: date.toString()},
-        language.formatDate(date)),
-  },
-  'flash-index': {
-    find: null,
-    link: 'flashIndex',
-  },
-  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 === '.') {
-        return name.slice(0, -1);
-      } else {
-        return name;
-      }
-    },
-  },
-  group: {
-    find: 'group',
-    link: 'groupInfo',
-  },
-  'group-gallery': {
-    find: 'group',
-    link: 'groupGallery',
-  },
-  home: {
-    find: null,
-    link: 'home',
-  },
-  'listing-index': {
-    find: null,
-    link: 'listingIndex',
-  },
-  listing: {
-    find: 'listing',
-    link: 'listing',
-  },
-  media: {
-    find: null,
-    link: 'media',
-  },
-  'news-index': {
-    find: null,
-    link: 'newsIndex',
-  },
-  'news-entry': {
-    find: 'newsEntry',
-    link: 'newsEntry',
-  },
-  root: {
-    find: null,
-    link: 'root',
-  },
-  site: {
-    find: null,
-    link: 'site',
-  },
-  static: {
-    find: 'staticPage',
-    link: 'staticPage',
-  },
-  string: {
-    find: null,
-    value: (ref) => ref,
-    html: (ref, {language, args}) => language.$(ref, args),
-  },
-  tag: {
-    find: 'artTag',
-    link: 'tag',
-  },
-  track: {
-    find: 'track',
-    link: 'track',
-  },
-};
-
-if (!validateReplacerSpec(replacerSpec, {find, link: unbound_link})) {
+if (!validateReplacerSpec(replacerSpec, {find, link})) {
   process.exit();
 }
 
-function parseAttributes(string, {to}) {
-  const attributes = Object.create(null);
-  const skipWhitespace = (i) => {
-    const ws = /\s/;
-    if (ws.test(string[i])) {
-      const match = string.slice(i).match(/[^\s]/);
-      if (match) {
-        return i + match.index;
-      } else {
-        return string.length;
-      }
-    } else {
-      return i;
-    }
-  };
-
-  for (let i = 0; i < string.length; ) {
-    i = skipWhitespace(i);
-    const aStart = i;
-    const aEnd = i + string.slice(i).match(/[\s=]|$/).index;
-    const attribute = string.slice(aStart, aEnd);
-    i = skipWhitespace(aEnd);
-    if (string[i] === '=') {
-      i = skipWhitespace(i + 1);
-      let end, endOffset;
-      if (string[i] === '"' || string[i] === "'") {
-        end = string[i];
-        endOffset = 1;
-        i++;
-      } else {
-        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));
-      } else {
-        attributes[attribute] = value;
-      }
-    } else {
-      attributes[attribute] = attribute;
-    }
-  }
-  return Object.fromEntries(
-    Object.entries(attributes).map(([key, val]) => [
-      key,
-      val === 'true'
-        ? true
-        : val === 'false'
-        ? false
-        : val === key
-        ? true
-        : val,
-    ])
-  );
-}
-
-function joinLineBreaks(sourceLines) {
-  const outLines = [];
-
-  let lineSoFar = '';
-  for (let i = 0; i < sourceLines.length; i++) {
-    const line = sourceLines[i];
-    lineSoFar += line;
-    if (!line.endsWith('<br>')) {
-      outLines.push(lineSoFar);
-      lineSoFar = '';
-    }
-  }
-
-  if (lineSoFar) {
-    outLines.push(lineSoFar);
-  }
-
-  return outLines;
-}
-
-function transformMultiline(text, {
-  parseAttributes,
-  to,
-  transformInline,
-  thumb = null,
-}) {
-  // Heck yes, HTML magics.
-
-  text = transformInline(text.trim());
-
-  const outLines = [];
-
-  const indentString = ' '.repeat(4);
-
-  let levelIndents = [];
-  const openLevel = (indent) => {
-    // opening a sublist is a pain: to be semantically *and* visually
-    // 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>')) {
-      // we will re-close the <li> later
-      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>');
-    }
-    levelIndents.push(indent);
-  };
-  const closeLevel = () => {
-    levelIndents.pop();
-    if (levelIndents.length) {
-      // closing a sublist, so close the list item containing it too
-      outLines.push(indentString.repeat(levelIndents.length) + '</ul></li>');
-    } else {
-      // closing the final list level! no need for indent here
-      outLines.push('</ul>');
-    }
-  };
-
-  // okay yes we should support nested formatting, more than one blockquote
-  // layer, etc, but hear me out here: making all that work would basically
-  // be the same as implementing an entire markdown converter, which im not
-  // interested in doing lol. sorry!!!
-  let inBlockquote = false;
-
-  let lines = splitLines(text);
-  lines = joinLineBreaks(lines);
-  for (let line of lines) {
-    const imageLine = line.startsWith('<img');
-    line = line.replace(/<img (.*?)>/g, (match, attributes) =>
-      img({
-        lazy: true,
-        link: true,
-        thumb,
-        ...parseAttributes(attributes),
-      })
-    );
-
-    let indentThisLine = 0;
-    let lineContent = line;
-    let lineTag = 'p';
-
-    const listMatch = line.match(/^( *)- *(.*)$/);
-    if (listMatch) {
-      // is a list item!
-      if (!levelIndents.length) {
-        // first level is always indent = 0, regardless of actual line
-        // content (this is to avoid going to a lesser indent than the
-        // initial level)
-        openLevel(0);
-      } else {
-        // find level corresponding to indent
-        const indent = listMatch[1].length;
-        let i;
-        for (i = levelIndents.length - 1; i >= 0; i--) {
-          if (levelIndents[i] <= indent) break;
-        }
-        // note: i cannot equal -1 because the first indentation level
-        // is always 0, and the minimum indentation is also 0
-        if (levelIndents[i] === indent) {
-          // same indent! return to that level
-          while (levelIndents.length - 1 > i) closeLevel();
-          // (if this is already the current level, the above loop
-          // will do nothing)
-        } else if (levelIndents[i] < indent) {
-          // lesser indent! branch based on index
-          if (i === levelIndents.length - 1) {
-            // top level is lesser: add a new level
-            openLevel(indent);
-          } else {
-            // lower level is lesser: return to that level
-            while (levelIndents.length - 1 > i) closeLevel();
-          }
-        }
-      }
-      // finally, set variables for appending content line
-      indentThisLine = levelIndents.length;
-      lineContent = listMatch[2];
-      lineTag = 'li';
-    } else {
-      // not a list item! close any existing list levels
-      while (levelIndents.length) closeLevel();
-
-      // like i said, no nested shenanigans - quotes only appear outside
-      // of lists. sorry!
-      const quoteMatch = line.match(/^> *(.*)$/);
-      if (quoteMatch) {
-        // is a quote! open a blockquote tag if it doesnt already exist
-        if (!inBlockquote) {
-          inBlockquote = true;
-          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>');
-      }
-
-      // 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('\\-', '-');
-      } else if (lineContent.match(/( *)\\>/)) {
-        lineContent = lineContent.replace('\\>', '>');
-      }
-    }
-
-    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(/^<(.*?)[ >]/);
-      if (
-        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',
-        ].includes(elementMatch[1])
-      ) {
-        lineTag = '';
-      }
-
-      // for sticky headings!
-      if (elementMatch && elementMatch[1] === 'h2') {
-        lineContent = lineContent.replace(/<h2(.*?)>/g, (match, attributes) => {
-          const parsedAttributes = parseAttributes(attributes, {to});
-          return `<h2 ${html.attributes({
-            ...parsedAttributes,
-            class: [...parsedAttributes.class?.split(' ') ?? [], 'content-heading'],
-          })}>`;
-        });
-      }
-    }
-
-    let pushString = indentString.repeat(indentThisLine);
-    if (lineTag) {
-      pushString += `<${lineTag}>${lineContent}</${lineTag}>`;
-    } else {
-      pushString += lineContent;
-    }
-    outLines.push(pushString);
-  }
-
-  // after processing all lines...
-
-  // if still in a list, close all levels
-  while (levelIndents.length) closeLevel();
-
-  // if still in a blockquote, close its tag
-  if (inBlockquote) {
-    inBlockquote = false;
-    outLines.push('</blockquote>');
-  }
-
-  return outLines.join('\n');
-}
-
-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')) {
-    return transformMultiline(text);
-  }
-
-  text = transformInline(text.trim());
-
-  let buildLine = '';
-  const addLine = () => outLines.push(`<p>${buildLine}</p>`);
-  const outLines = [];
-  for (const line of text.split('\n')) {
-    if (line.length) {
-      if (buildLine.length) {
-        buildLine += '<br>';
-      }
-      buildLine += line;
-    } else if (buildLine.length) {
-      addLine();
-      buildLine = '';
-    }
-  }
-  if (buildLine.length) {
-    addLine();
-  }
-  return outLines.join('\n');
-}
-
-function stringifyThings(thingData) {
-  return JSON.stringify(serializeThings(thingData));
-}
-
-function img({
-  src,
-  alt,
-  noSrcText = '',
-  thumb: thumbKey,
-  reveal,
-  id,
-  class: className,
-  width,
-  height,
-  link = false,
-  lazy = false,
-  square = false,
-}) {
-  const willSquare = square;
-  const willLink = typeof link === 'string' || link;
-
-  const originalSrc = src;
-  const thumbSrc = src && (thumbKey ? thumb[thumbKey](src) : src);
-
-  const imgAttributes = {
-    id: link ? '' : id,
-    class: className,
-    alt,
-    width,
-    height,
-  };
-
-  const noSrcHTML =
-    !src &&
-      wrap(
-        html.tag('div',
-          {class: 'image-text-area'},
-          noSrcText));
-
-  const nonlazyHTML =
-    src &&
-      wrap(
-        html.tag('img', {
-          ...imgAttributes,
-          src: thumbSrc,
-        }));
-
-  const lazyHTML =
-    src &&
-    lazy &&
-      wrap(
-        html.tag('img',
-          {
-            ...imgAttributes,
-            class: [className, 'lazy'],
-            'data-original': thumbSrc,
-          }),
-        true);
-
-  if (!src) {
-    return noSrcHTML;
-  } else if (lazy) {
-    return html.tag('noscript', nonlazyHTML) + '\n' + lazyHTML;
-  } else {
-    return nonlazyHTML;
-  }
-
-  function wrap(input, hide = false) {
-    let wrapped = input;
-
-    wrapped = html.tag('div', {class: 'image-container'}, wrapped);
-
-    if (reveal) {
-      wrapped = html.tag('div', {class: 'reveal'}, [
-        wrapped,
-        html.tag('span', {class: 'reveal-text'}, reveal),
-      ]);
-    }
-
-    if (willSquare) {
-      wrapped = html.tag('div', {class: 'square-content'}, wrapped);
-      wrapped = html.tag('div',
-        {class: ['square', hide && !willLink && 'js-hide']},
-        wrapped);
-    }
-
-    if (willLink) {
-      wrapped = html.tag('a',
-        {
-          id,
-          class: ['box', hide && 'js-hide'],
-          href: typeof link === 'string' ? link : originalSrc,
-        },
-        wrapped);
-    }
-
-    return wrapped;
-  }
-}
-
-function validateWritePath(path, urlGroup) {
-  if (!Array.isArray(path)) {
-    return {error: `Expected array, got ${path}`};
-  }
-
-  const {paths} = urlGroup;
-
-  const definedKeys = Object.keys(paths);
-  const specifiedKey = path[0];
-
-  if (!definedKeys.includes(specifiedKey)) {
-    return {error: `Specified key ${specifiedKey} isn't defined`};
-  }
-
-  const expectedArgs = paths[specifiedKey].match(/<>/g)?.length ?? 0;
-  const specifiedArgs = path.length - 1;
-
-  if (specifiedArgs !== expectedArgs) {
-    return {
-      error: `Expected ${expectedArgs} arguments, got ${specifiedArgs}`,
-    };
-  }
-
-  return {success: true};
-}
-
-function validateWriteObject(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}`};
-  }
-
-  switch (obj.type) {
-    case 'legacy': {
-      if (typeof obj.write !== 'function') {
-        return {error: `Expected write to be string, got ${obj.write}`};
-      }
-
-      break;
-    }
-
-    case 'page': {
-      const path = validateWritePath(obj.path, urlSpec.localized);
-      if (path.error) {
-        return {error: `Path validation failed: ${path.error}`};
-      }
-
-      if (typeof obj.page !== 'function') {
-        return {error: `Expected page to be function, got ${obj.content}`};
-      }
-
-      break;
-    }
-
-    case 'data': {
-      const path = validateWritePath(obj.path, urlSpec.data);
-      if (path.error) {
-        return {error: `Path validation failed: ${path.error}`};
-      }
-
-      if (typeof obj.data !== 'function') {
-        return {error: `Expected data to be function, got ${obj.data}`};
-      }
-
-      break;
-    }
-
-    case 'redirect': {
-      const fromPath = validateWritePath(obj.fromPath, urlSpec.localized);
-      if (fromPath.error) {
-        return {
-          error: `Path (fromPath) validation failed: ${fromPath.error}`,
-        };
-      }
-
-      const toPath = validateWritePath(obj.toPath, urlSpec.localized);
-      if (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}`};
-      }
-
-      break;
-    }
-
-    default: {
-      return {error: `Unknown type: ${obj.type}`};
-    }
-  }
-
-  return {success: true};
-}
-
-export function getURLsFrom({
-  baseDirectory,
-  pageSubKey,
-  paths,
-}) {
-  return (targetFullKey, ...args) => {
-    const [groupKey, subKey] = targetFullKey.split('.');
-    let path = paths.subdirectoryPrefix;
-
-    let from;
-    let 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' &&
-      baseDirectory
-    ) {
-      from = 'localizedWithBaseDirectory.' + pageSubKey;
-      to = targetFullKey;
-    } 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') {
-      // Linking to the default, except surprise, we're already IN the default
-      // (no baseDirectory set).
-      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;
-      to = targetFullKey;
-    }
-
-    path += urls.from(from).to(to, ...args);
-
-    return path;
-  };
-}
-
-export function generateDocumentHTML(pageInfo, {
-  defaultLanguage,
-  getThemeString,
-  language,
-  languages,
-  localizedPaths,
-  paths,
-  oEmbedJSONHref,
-  to,
-  transformMultiline,
-  wikiData,
-}) {
-  const {wikiInfo} = wikiData;
-
-  let {
-    title = '',
-    meta = {},
-    theme = '',
-    stylesheet = '',
-
-    showWikiNameInTitle = true,
-    themeColor = '',
-
-    // missing properties are auto-filled, see below!
-    body = {},
-    banner = {},
-    main = {},
-    sidebarLeft = {},
-    sidebarRight = {},
-    nav = {},
-    secondaryNav = {},
-    footer = {},
-    socialEmbed = {},
-  } = pageInfo;
-
-  body.style ??= '';
-
-  theme = theme || getThemeString(wikiInfo.color);
-
-  banner ||= {};
-  banner.classes ??= [];
-  banner.src ??= '';
-  banner.position ??= '';
-  banner.dimensions ??= [0, 0];
-
-  main.classes ??= [];
-  main.content ??= '';
-
-  sidebarLeft ??= {};
-  sidebarRight ??= {};
-
-  for (const sidebar of [sidebarLeft, sidebarRight]) {
-    sidebar.classes ??= [];
-    sidebar.content ??= '';
-    sidebar.collapse ??= true;
-  }
-
-  nav.classes ??= [];
-  nav.content ??= '';
-  nav.bottomRowContent ??= '';
-  nav.links ??= [];
-  nav.linkContainerClasses ??= [];
-
-  secondaryNav ??= {};
-  secondaryNav.content ??= '';
-  secondaryNav.content ??= '';
-
-  footer.classes ??= [];
-  footer.content ??= wikiInfo.footerContent
-    ? transformMultiline(wikiInfo.footerContent)
-    : '';
-
-  const colors = themeColor
-    ? getColors(themeColor, {chroma})
-    : null;
-
-  const canonical = wikiInfo.canonicalBase
-    ? wikiInfo.canonicalBase + (paths.pathname === '/' ? '' : paths.pathname)
-    : '';
-
-  const localizedCanonical = wikiInfo.canonicalBase
-    ? Object.entries(localizedPaths).map(([code, {pathname}]) => ({
-        lang: code,
-        href: wikiInfo.canonicalBase + (pathname === '/' ? '' : pathname),
-      }))
-    : [];
-
-  const collapseSidebars =
-    sidebarLeft.collapse !== false && sidebarRight.collapse !== false;
-
-  const mainHTML =
-    main.content &&
-      html.tag('main',
-        {
-          id: 'content',
-          class: main.classes,
-        },
-        main.content);
-
-  const footerHTML =
-    html.tag('footer',
-      {
-        [html.onlyIfContent]: true,
-        id: 'footer',
-        class: footer.classes,
-      },
-      [
-        html.tag('div',
-          {
-            [html.onlyIfContent]: true,
-            class: 'footer-content',
-          },
-          footer.content),
-
-        getFooterLocalizationLinks(paths.pathname, {
-          defaultLanguage,
-          html,
-          language,
-          languages,
-          paths,
-          to,
-        }),
-      ]);
-
-  const generateSidebarHTML = (id, {
-    content,
-    multiple,
-    classes,
-    collapse = true,
-    wide = false,
-
-    // 'last' - last or only sidebar box is sticky
-    // 'column' - entire column, incl. multiple boxes from top, is sticky
-    // 'none' - sidebar not sticky at all, stays at top of page
-    stickyMode = 'last',
-  }) =>
-    content
-      ? html.tag('div',
-          {
-            id,
-            class: [
-              'sidebar-column',
-              'sidebar',
-              wide && 'wide',
-              !collapse && 'no-hide',
-              stickyMode !== 'none' && 'sticky-' + stickyMode,
-              ...classes,
-            ],
-          },
-          content)
-      : multiple
-      ? html.tag('div',
-          {
-            id,
-            class: [
-              'sidebar-column',
-              'sidebar-multiple',
-              wide && 'wide',
-              !collapse && 'no-hide',
-              stickyMode !== 'none' && 'sticky-' + stickyMode,
-            ],
-          },
-          multiple
-            .map((infoOrContent) =>
-              (typeof infoOrContent === 'object' && !Array.isArray(infoOrContent))
-                ? infoOrContent
-                : {content: infoOrContent})
-            .filter(({content}) => content)
-            .map(({
-              content,
-              classes: classes2 = [],
-            }) =>
-              html.tag('div',
-                {
-                  class: ['sidebar', ...classes, ...classes2],
-                },
-                html.fragment(content))))
-      : '';
-
-  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}];
-  }
-
-  const links = (nav.links || []).filter(Boolean);
-
-  const navLinkParts = [];
-  for (let i = 0; i < links.length; i++) {
-    let cur = links[i];
-
-    let {title: linkTitle} = cur;
-
-    if (cur.toHome) {
-      linkTitle ??= wikiInfo.nameShort;
-    } else if (cur.toCurrentPage) {
-      linkTitle ??= title;
-    }
-
-    let partContent;
-
-    if (typeof cur.html === 'string') {
-      partContent = cur.html;
-    } else {
-      const attributes = {
-        class: (cur.toCurrentPage || i === links.length - 1) && 'current',
-        href: cur.toCurrentPage
-          ? ''
-          : cur.toHome
-          ? to('localized.home')
-          : cur.path
-          ? to(...cur.path)
-          : cur.href
-          ? (() => {
-              logWarn`Using legacy href format nav link in ${paths.pathname}`;
-              return cur.href;
-            })()
-          : null,
-      };
-      if (attributes.href === null) {
-        throw new Error(
-          `Expected some href specifier for link to ${linkTitle} (${JSON.stringify(
-            cur
-          )})`
-        );
-      }
-      partContent = html.tag('a', attributes, linkTitle);
-    }
-
-    if (!partContent) continue;
-
-    const part = html.tag('span',
-      {class: cur.divider === false && 'no-divider'},
-      partContent);
-
-    navLinkParts.push(part);
-  }
-
-  const navHTML = html.tag('nav',
-    {
-      [html.onlyIfContent]: true,
-      id: 'header',
-      class: [
-        ...nav.classes,
-        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]},
-          navLinkParts
-        ),
-      nav.bottomRowContent &&
-        html.tag('div', {class: 'nav-bottom-row'}, nav.bottomRowContent),
-      nav.content && html.tag('div', {class: 'nav-content'}, nav.content),
-    ]);
-
-  const secondaryNavHTML = html.tag('nav',
-    {
-      [html.onlyIfContent]: true,
-      id: 'secondary-nav',
-      class: secondaryNav.classes,
-    },
-    secondaryNav.content);
-
-  const bannerSrc = banner.src
-    ? banner.src
-    : banner.path
-    ? to(...banner.path)
-    : null;
-
-  const bannerHTML =
-    banner.position &&
-    bannerSrc &&
-    html.tag('div',
-      {
-        id: 'banner',
-        class: banner.classes,
-      },
-      html.tag('img', {
-        src: bannerSrc,
-        alt: banner.alt,
-        width: banner.dimensions[0] || 1100,
-        height: banner.dimensions[1] || 200,
-      }));
-
-  const layoutHTML = [
-    navHTML,
-    banner.position === 'top' && bannerHTML,
-    secondaryNavHTML,
-    html.tag('div',
-      {
-        class: [
-          'layout-columns',
-          !collapseSidebars && 'vertical-when-thin',
-          (sidebarLeftHTML || sidebarRightHTML) && 'has-one-sidebar',
-          (sidebarLeftHTML && sidebarRightHTML) && 'has-two-sidebars',
-          !(sidebarLeftHTML || sidebarRightHTML) && 'has-zero-sidebars',
-          sidebarLeftHTML && 'has-sidebar-left',
-          sidebarRightHTML && 'has-sidebar-right',
-        ],
-      },
-      [
-        sidebarLeftHTML,
-        mainHTML,
-        sidebarRightHTML,
-      ]),
-    banner.position === 'bottom' && bannerHTML,
-    footerHTML,
-  ].filter(Boolean).join('\n');
-
-  const infoCardHTML = html.tag('div', {id: 'info-card-container'},
-    html.tag('div', {id: 'info-card-decor'},
-      html.tag('div', {id: 'info-card'}, [
-        html.tag('div', {class: ['info-card-art-container', 'no-reveal']},
-          img({
-            class: 'info-card-art',
-            src: '',
-            link: true,
-            square: true,
-          })),
-        html.tag('div', {class: ['info-card-art-container', 'reveal']},
-          img({
-            class: 'info-card-art',
-            src: '',
-            link: true,
-            square: true,
-            reveal: getRevealStringFromWarnings(
-              html.tag('span', {class: 'info-card-art-warnings'}),
-              {html, language}),
-          })),
-        html.tag('h1', {class: 'info-card-name'},
-          html.tag('a')),
-        html.tag('p', {class: 'info-card-album'},
-          language.$('releaseInfo.from', {
-            album: html.tag('a'),
-          })),
-        html.tag('p', {class: 'info-card-artists'},
-          language.$('releaseInfo.by', {
-            artists: html.tag('span'),
-          })),
-        html.tag('p', {class: 'info-card-cover-artists'},
-          language.$('releaseInfo.coverArtBy', {
-            artists: html.tag('span'),
-          })),
-      ])));
-
-  const socialEmbedHTML = [
-    socialEmbed.title &&
-      html.tag('meta', {property: 'og:title', content: socialEmbed.title}),
-
-    socialEmbed.description &&
-      html.tag('meta', {
-        property: 'og:description',
-        content: socialEmbed.description,
-      }),
-
-    socialEmbed.image &&
-      html.tag('meta', {property: 'og:image', content: socialEmbed.image}),
-
-    ...html.fragment(
-      colors && [
-        html.tag('meta', {
-          name: 'theme-color',
-          content: colors.dark,
-          media: '(prefers-color-scheme: dark)',
-        }),
-
-        html.tag('meta', {
-          name: 'theme-color',
-          content: colors.light,
-          media: '(prefers-color-scheme: light)',
-        }),
-
-        html.tag('meta', {
-          name: 'theme-color',
-          content: colors.primary,
-        }),
-      ]),
-
-    oEmbedJSONHref &&
-      html.tag('link', {
-        type: 'application/json+oembed',
-        href: oEmbedJSONHref,
-      }),
-  ].filter(Boolean).join('\n');
-
-  return `<!DOCTYPE html>\n` + html.tag('html',
-    {
-      lang: language.intlCode,
-      'data-language-code': language.code,
-      'data-url-key': paths.urlPath[0],
-      ...Object.fromEntries(
-        paths.urlPath.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'),
-    },
-    [
-      `<!--\n` + [
-        wikiInfo.canonicalBase
-          ? `hsmusic.wiki - ${wikiInfo.name}, ${wikiInfo.canonicalBase}`
-          : `hsmusic.wiki - ${wikiInfo.name}`,
-        'Code copyright 2019-2022 Quasar Nebula et al (MIT License)',
-        ...wikiInfo.canonicalBase === 'https://hsmusic.wiki/' ? [
-          'Data avidly compiled and localization brought to you',
-          'by our awesome team and community of wiki contributors',
-          '***',
-          'Want to contribute? Join our Discord or leave feedback!',
-          '- https://hsmusic.wiki/discord/',
-          '- https://hsmusic.wiki/feedback/',
-          '- https://github.com/hsmusic/',
-        ] : [
-          'https://github.com/hsmusic/',
-        ],
-        '***',
-        `Site built: ${BUILD_TIME.toLocaleString('en-US', {
-          dateStyle: 'long',
-          timeStyle: 'long',
-        })}`,
-        `Latest code commit: ${COMMIT}`,
-      ]
-        .filter(Boolean)
-        .map(line => `    ` + line)
-        .join('\n') + `\n-->`,
-
-      html.tag('head', [
-        html.tag('title',
-          showWikiNameInTitle
-            ? language.formatString('misc.pageTitle.withWikiName', {
-                title,
-                wikiName: wikiInfo.nameShort,
-              })
-            : language.formatString('misc.pageTitle', {title})),
-
-        html.tag('meta', {charset: 'utf-8'}),
-        html.tag('meta', {
-          name: 'viewport',
-          content: 'width=device-width, initial-scale=1',
-        }),
-
-        ...(
-          Object.entries(meta)
-            .filter(([key, value]) => value)
-            .map(([key, value]) => html.tag('meta', {[key]: value}))),
-
-        canonical &&
-          html.tag('link', {
-            rel: 'canonical',
-            href: canonical,
-          }),
-
-        ...(
-          localizedCanonical
-            .map(({lang, href}) => html.tag('link', {
-              rel: 'alternate',
-              hreflang: lang,
-              href,
-            }))),
-
-        socialEmbedHTML,
-
-        html.tag('link', {
-          rel: 'stylesheet',
-          href: to('shared.staticFile', `site2.css?${CACHEBUST}`),
-        }),
-
-        html.tag('style',
-          {[html.onlyIfContent]: true},
-          [
-            theme,
-            stylesheet,
-          ]),
-
-        html.tag('script', {
-          src: to('shared.staticFile', `lazy-loading.js?${CACHEBUST}`),
-        }),
-      ]),
-
-      html.tag('body',
-        {style: body.style || ''},
-        [
-          html.tag('div', {id: 'page-container'}, [
-            mainHTML &&
-              html.tag('div', {id: 'skippers'},
-                [
-                  ['#content', language.$('misc.skippers.skipToContent')],
-                  sidebarLeftHTML &&
-                    [
-                      '#sidebar-left',
-                      sidebarRightHTML
-                        ? language.$('misc.skippers.skipToSidebar.left')
-                        : language.$('misc.skippers.skipToSidebar'),
-                    ],
-                  sidebarRightHTML &&
-                    [
-                      '#sidebar-right',
-                      sidebarLeftHTML
-                        ? language.$('misc.skippers.skipToSidebar.right')
-                        : language.$('misc.skippers.skipToSidebar'),
-                    ],
-                  footerHTML &&
-                    ['#footer', language.$('misc.skippers.skipToFooter')],
-                ]
-                  .filter(Boolean)
-                  .map(([href, title]) =>
-                    html.tag('span', {class: 'skipper'},
-                      html.tag('a', {href}, title)))),
-            layoutHTML,
-          ]),
-
-          infoCardHTML,
-
-          html.tag('script', {
-            type: 'module',
-            src: to('shared.staticFile', `client.js?${CACHEBUST}`),
-          }),
-        ]),
-    ]);
-}
-
-function generateOEmbedJSON(pageInfo, {language, wikiData}) {
-  const {socialEmbed} = pageInfo;
-  const {wikiInfo} = wikiData;
-  const {canonicalBase, nameShort} = wikiInfo;
-
-  if (!socialEmbed) return '';
-
-  const entries = [
-    socialEmbed.heading && [
-      'author_name',
-      language.$('misc.socialEmbed.heading', {
-        wikiName: nameShort,
-        heading: socialEmbed.heading,
-      }),
-    ],
-    socialEmbed.headingLink &&
-      canonicalBase && [
-        'author_url',
-        canonicalBase.replace(/\/$/, '') +
-          '/' +
-          socialEmbed.headingLink.replace(/^\//, ''),
-      ],
-  ].filter(Boolean);
-
-  if (!entries.length) return '';
-
-  return JSON.stringify(Object.fromEntries(entries));
-}
-
-async function writePage({
-  html,
-  oEmbedJSON = '',
-  paths,
-}) {
-  await mkdir(paths.output.directory, {recursive: true});
-
-  await Promise.all(
-    [
-      writeFile(paths.output.documentHTML, html),
-
-      oEmbedJSON &&
-        writeFile(paths.output.oEmbedJSON, oEmbedJSON),
-    ].filter(Boolean)
-  );
-}
-
-function getPagePaths({
-  baseDirectory,
-  fullKey,
-  urlArgs,
-
-  file = 'index.html',
-}) {
-  const [groupKey, subKey] = fullKey.split('.');
-
-  const pathname =
-    groupKey === 'localized' && baseDirectory
-      ? urls
-          .from('shared.root')
-          .toDevice(
-            'localizedWithBaseDirectory.' + subKey,
-            baseDirectory,
-            ...urlArgs)
-      : urls
-          .from('shared.root')
-          .toDevice(fullKey, ...urlArgs);
-
-  // Needed for the rare path arguments which themselves contains one or more
-  // slashes, e.g. for listings, with arguments like 'albums/by-name'.
-  const subdirectoryPrefix =
-    '../'.repeat(urlArgs.join('/').split('/').length - 1);
-
-  const outputDirectory = path.join(outputPath, pathname);
-
-  const output = {
-    directory: outputDirectory,
-    documentHTML: path.join(outputDirectory, file),
-    oEmbedJSON: path.join(outputDirectory, OEMBED_JSON_FILE)
-  };
+async function main() {
+  Error.stackTraceLimit = Infinity;
 
-  return {
-    urlPath: [fullKey, ...urlArgs],
+  const selectedBuildModeFlags = Object.keys(
+    await parseOptions(process.argv.slice(2), {
+      [parseOptions.handleUnknown]: () => {},
 
-    output,
-    pathname,
-    subdirectoryPrefix,
-  };
-}
+      ...Object.fromEntries(Object.keys(buildModes)
+        .map((key) => [key, {type: 'flag'}])),
+    }));
 
-async function writeFavicon() {
-  try {
-    await stat(path.join(mediaPath, FAVICON_FILE));
-  } catch (error) {
-    return;
-  }
+  let selectedBuildModeFlag;
 
-  try {
-    await copyFile(
-      path.join(mediaPath, FAVICON_FILE),
-      path.join(outputPath, FAVICON_FILE)
-    );
-  } catch (error) {
-    logWarn`Failed to copy favicon! ${error.message}`;
+  if (empty(selectedBuildModeFlags)) {
+    selectedBuildModeFlag = 'static-build';
+    logInfo`No build mode specified, using default: ${selectedBuildModeFlag}`;
+  } else if (selectedBuildModeFlags.length > 1) {
+    logError`Building multiple modes (${selectedBuildModeFlags.join(', ')}) at once not supported.`;
+    logError`Please specify a maximum of one build mode.`;
     return;
+  } else {
+    selectedBuildModeFlag = selectedBuildModeFlags[0];
+    logInfo`Using specified build mode: ${selectedBuildModeFlag}`;
   }
 
-  logInfo`Copied favicon to site root.`;
-}
-
-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'),
-  ]);
+  const selectedBuildMode = buildModes[selectedBuildModeFlag];
 
-  async function link(directory, 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') {
-        throw error;
-      }
-    }
-    try {
-      await symlink(path.resolve(directory), file);
-    } catch (error) {
-      if (error.code === 'EPERM') {
-        await symlink(path.resolve(directory), file, 'junction');
-      }
-    }
-  }
-}
-
-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)
-    );
-    const content = generateRedirectHTML(title, target, {language});
-    await mkdir(path.join(outputPath, from), {recursive: true});
-    await writeFile(path.join(outputPath, from, 'index.html'), content);
+  // This is about to get a whole lot more stuff put in it.
+  const wikiData = {
+    listingSpec,
+    listingTargetSpec,
   };
 
-  return progressPromiseAll(`Writing files & pages shared across languages.`, [
-    groupData?.some((group) => group.directory === 'fandom') &&
-      redirect(
-        'Fandom - Gallery',
-        'albums/fandom',
-        'localized.groupGallery',
-        'fandom'
-      ),
-
-    groupData?.some((group) => group.directory === 'official') &&
-      redirect(
-        'Official - Gallery',
-        'albums/official',
-        'localized.groupGallery',
-        'official'
-      ),
-
-    wikiInfo.enableListings &&
-      redirect(
-        'Album Commentary',
-        'list/all-commentary',
-        'localized.commentaryIndex',
-        ''
-      ),
-
-    writeFile(
-      path.join(outputPath, 'data.json'),
-      (
-        '{\n' +
-        [
-          `"albumData": ${stringifyThings(wikiData.albumData)},`,
-          wikiInfo.enableFlashesAndGames &&
-            `"flashData": ${stringifyThings(wikiData.flashData)},`,
-          `"artistData": ${stringifyThings(wikiData.artistData)}`,
-        ]
-          .filter(Boolean)
-          .map(line => '  ' + line)
-          .join('\n') +
-        '\n}')),
-  ].filter(Boolean));
-}
+  const cliOptions = await parseOptions(process.argv.slice(2), {
+    ...selectedBuildMode.getCLIOptions(),
 
-function generateRedirectHTML(title, target, {language}) {
-  return `<!DOCTYPE html>\n` + html.tag('html', [
-    html.tag('head', [
-      html.tag('title', language.$('redirectPage.title', {title})),
-      html.tag('meta', {charset: 'utf-8'}),
-
-      html.tag('meta', {
-        'http-equiv': 'refresh',
-        content: `0;url=${target}`,
-      }),
-
-      // TODO: Is this OK for localized pages?
-      html.tag('link', {
-        rel: 'canonical',
-        href: target,
-      }),
-    ]),
-
-    html.tag('body',
-      html.tag('main', [
-        html.tag('h1',
-          language.$('redirectPage.title', {title})),
-        html.tag('p',
-          language.$('redirectPage.infoLine', {
-            target: html.tag('a', {href: target}, target),
-          })),
-      ])),
-  ]);
-}
-
-// Wrapper function for running a function once for all languages.
-async function wrapLanguages(fn, {languages, writeOneLanguage = null}) {
-  const k = writeOneLanguage;
-  const languagesToRun = k ? {[k]: languages[k]} : languages;
-
-  const entries = Object.entries(languagesToRun).filter(
-    ([key]) => key !== 'default'
-  );
-
-  for (let i = 0; i < entries.length; i++) {
-    const [_key, language] = entries[i];
-
-    await fn(language, i, entries);
-  }
-}
-
-async function main() {
-  Error.stackTraceLimit = Infinity;
-
-  const WD = wikiData;
-
-  WD.listingSpec = listingSpec;
-  WD.listingTargetSpec = listingTargetSpec;
-
-  const miscOptions = await parseOptions(process.argv.slice(2), {
     // 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!
@@ -1686,16 +160,6 @@ async function main() {
       type: 'value',
     },
 
-    // This is the output directory. It's the one you'll upload online with
-    // rsync or whatever when you're pushing an upd8, and also the one
-    // you'd archive if you wanted to make a 8ackup of the whole dang
-    // 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',
-    },
-
     // 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.
@@ -1716,20 +180,6 @@ async function main() {
       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',
-    },
-
-    // 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',
-    },
-
     // 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
@@ -1771,12 +221,23 @@ async function main() {
     [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;
+  const dataPath = cliOptions['data-path'] || process.env.HSMUSIC_DATA;
+  const mediaPath = cliOptions['media-path'] || process.env.HSMUSIC_MEDIA;
+  const langPath = cliOptions['lang-path'] || process.env.HSMUSIC_LANG; // Can 8e left unset!
+
+  const skipThumbs = cliOptions['skip-thumbs'] ?? false;
+  const thumbsOnly = cliOptions['thumbs-only'] ?? false;
+  const noBuild = cliOptions['no-build'] ?? false;
+
+  const showAggregateTraces = cliOptions['show-traces'] ?? false;
 
-  const writeOneLanguage = miscOptions['lang'];
+  const precacheData = cliOptions['precache-data'] ?? false;
+  const showInvalidPropertyAccesses = cliOptions['show-invalid-property-accesses'] ?? false;
+
+  // Makes writing nicer on the CPU and file I/O parts of the OS, with a
+  // marginal performance deficit while waiting for file writes to finish
+  // before proceeding to more page processing.
+  const queueSize = +(cliOptions['queue-size'] ?? 500);
 
   {
     let errored = false;
@@ -1788,46 +249,11 @@ async function main() {
     };
     error(!dataPath, `Expected --data-path option or HSMUSIC_DATA to be set`);
     error(!mediaPath, `Expected --media-path option or HSMUSIC_MEDIA to be set`);
-    error(!outputPath, `Expected --out-path option or HSMUSIC_OUT to be set`);
     if (errored) {
       return;
     }
   }
 
-  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 precacheData = miscOptions['precache-data'] ?? false;
-
-  // NOT for ena8ling or disa8ling specific features of the site!
-  // This is only in charge of what general groups of files to 8uild.
-  // They're here to make development quicker when you're only working
-  // 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.
-
-    // Kinda a hack t8h!
-    ...Object.fromEntries(
-      Object.keys(pageSpecs).map((key) => [key, {type: 'flag'}])
-    ),
-
-    [parseOptions.handleUnknown]: () => {},
-  });
-
-  const writeAll = !Object.keys(writeFlags).length || writeFlags.all;
-
-  logInfo`Writing site pages: ${
-    writeAll ? 'all' : Object.keys(writeFlags).join(', ')
-  }`;
-
   const niceShowAggregate = (error, ...opts) => {
     showAggregate(error, {
       showTraces: showAggregateTraces,
@@ -1851,9 +277,6 @@ async function main() {
     if (thumbsOnly) return;
   }
 
-  const showInvalidPropertyAccesses =
-    miscOptions['show-invalid-property-accesses'] ?? false;
-
   if (showInvalidPropertyAccesses) {
     CacheableObject.DEBUG_SLOW_TRACK_INVALID_PROPERTIES = true;
   }
@@ -1912,7 +335,7 @@ async function main() {
     }
   }
 
-  if (!WD.wikiInfo) {
+  if (!wikiData.wikiInfo) {
     logError`Can't proceed without wiki info file (${WIKI_INFO_FILE}) successfully loading`;
     return;
   }
@@ -1985,9 +408,7 @@ async function main() {
     progressCallAll('Caching all data values', Object.entries(wikiData)
       .filter(([key]) =>
         key !== 'listingSpec' &&
-        key !== 'listingTargetSpec' &&
-        key !== 'officialAlbumData' &&
-        key !== 'fandomAlbumData')
+        key !== 'listingTargetSpec')
       .map(([key, value]) =>
         key === 'wikiInfo' ? [key, [value]] :
         key === 'homepageLayout' ? [key, [value]] :
@@ -1997,8 +418,7 @@ async function main() {
   }
 
   const internalDefaultLanguage = await processLanguageFile(
-    path.join(__dirname, DEFAULT_STRINGS_FILE)
-  );
+    path.join(__dirname, DEFAULT_STRINGS_FILE));
 
   let languages;
   if (langPath) {
@@ -2006,28 +426,25 @@ async function main() {
       filter: (f) => path.extname(f) === '.json',
     });
 
-    const results = await progressPromiseAll(
-      `Reading & processing language files.`,
-      languageDataFiles.map((file) => processLanguageFile(file))
-    );
+    const results = await progressPromiseAll(`Reading & processing language files.`,
+      languageDataFiles.map((file) => processLanguageFile(file)));
 
     languages = Object.fromEntries(
-      results.map((language) => [language.code, language])
-    );
+      results.map((language) => [language.code, language]));
   } else {
     languages = {};
   }
 
   const customDefaultLanguage =
-    languages[WD.wikiInfo.defaultLanguage ?? internalDefaultLanguage.code];
+    languages[wikiData.wikiInfo.defaultLanguage ?? internalDefaultLanguage.code];
   let finalDefaultLanguage;
 
   if (customDefaultLanguage) {
     logInfo`Applying new default strings from custom ${customDefaultLanguage.code} language file.`;
     customDefaultLanguage.inheritedStrings = internalDefaultLanguage.strings;
     finalDefaultLanguage = customDefaultLanguage;
-  } else if (WD.wikiInfo.defaultLanguage) {
-    logError`Wiki info file specified default language is ${WD.wikiInfo.defaultLanguage}, but no such language file exists!`;
+  } else if (wikiData.wikiInfo.defaultLanguage) {
+    logError`Wiki info file specified default language is ${wikiData.wikiInfo.defaultLanguage}, but no such language file exists!`;
     if (langPath) {
       logError`Check if an appropriate file exists in ${langPath}?`;
     } else {
@@ -2051,22 +468,15 @@ async function main() {
 
   if (noBuild) {
     logInfo`Not generating any site or page files this run (--no-build passed).`;
-  } else if (writeOneLanguage && !(writeOneLanguage in languages)) {
-    logError`Specified to write only ${writeOneLanguage}, but there is no strings file with this language code!`;
-    return;
-  } else if (writeOneLanguage) {
-    logInfo`Writing only language ${writeOneLanguage} this run.`;
-  } else {
-    logInfo`Writing all languages.`;
   }
 
   {
     const tagRefs = new Set(
-      [...WD.trackData, ...WD.albumData]
+      [...wikiData.trackData, ...wikiData.albumData]
         .flatMap((thing) => thing.artTagsByRef ?? []));
 
     for (const ref of tagRefs) {
-      if (find.artTag(ref, WD.artTagData)) {
+      if (find.artTag(ref, wikiData.artTagData)) {
         tagRefs.delete(ref);
       }
     }
@@ -2079,10 +489,7 @@ async function main() {
     }
   }
 
-  WD.officialAlbumData = WD.albumData
-    .filter((album) => album.groups.some((group) => group.directory === OFFICIAL_GROUP_DIRECTORY));
-  WD.fandomAlbumData = WD.albumData
-    .filter((album) => album.groups.every((group) => group.directory !== OFFICIAL_GROUP_DIRECTORY));
+  const urls = generateURLs(urlSpec);
 
   const fileSizePreloader = new FileSizePreloader();
 
@@ -2094,7 +501,7 @@ async function main() {
   // function between them so that when site code requests a site path,
   // it'll get the size of the file at the corresponding device path.
   const additionalFilePaths = [
-    ...WD.albumData.flatMap((album) =>
+    ...wikiData.albumData.flatMap((album) =>
       [
         ...(album.additionalFiles ?? []),
         ...album.tracks.flatMap((track) => track.additionalFiles ?? []),
@@ -2130,503 +537,55 @@ async function main() {
 
   if (noBuild) return;
 
-  // Makes writing nicer on the CPU and file I/O parts of the OS, with a
-  // marginal performance deficit while waiting for file writes to finish
-  // before proceeding to more page processing.
-  queueSize = +(miscOptions['queue-size'] ?? 500);
-
-  const buildDictionary = pageSpecs;
-
-  await writeFavicon();
-  await writeSymlinks();
-  await writeSharedFilesAndPages({language: finalDefaultLanguage, wikiData});
-
-  const buildSteps = writeAll
-    ? Object.entries(buildDictionary)
-    : Object.entries(buildDictionary).filter(([flag]) => writeFlags[flag]);
-
-  let writes;
-  {
-    let error = false;
-
-    const buildStepsWithTargets = buildSteps
-      .map(([flag, pageSpec]) => {
-        // Condition not met: skip this build step altogether.
-        if (pageSpec.condition && !pageSpec.condition({wikiData})) {
-          return null;
-        }
-
-        // May still call writeTargetless if present.
-        if (!pageSpec.targets) {
-          return {flag, pageSpec, targets: []};
-        }
-
-        if (!pageSpec.write) {
-          logError`${flag + '.targets'} is specified, but ${flag + '.write'} is missing!`;
-          error = true;
-          return null;
-        }
-
-        const targets = pageSpec.targets({wikiData});
-        if (!Array.isArray(targets)) {
-          logError`${flag + '.targets'} was called, but it didn't return an array! (${typeof targets})`;
-          error = true;
-          return null;
-        }
-
-        return {flag, pageSpec, targets};
-      })
-      .filter(Boolean);
-
-    if (error) {
-      return;
-    }
-
-    const validateWrites = (writes, fnName) => {
-      // Do a quick valid8tion! If one of the writeThingPages functions go
-      // wrong, this will stall out early and tell us which did.
-
-      if (!Array.isArray(writes)) {
-        logError`${fnName} didn't return an array!`;
-        error = true;
-        return false;
-      }
-
-      if (
-        !(
-          writes.every((obj) => typeof obj === 'object') &&
-          writes.every((obj) => {
-            const result = validateWriteObject(obj);
-            if (result.error) {
-              logError`Validating write object failed: ${result.error}`;
-              return false;
-            } else {
-              return true;
-            }
-          })
-        )
-      ) {
-        logError`${fnName} returned invalid entries!`;
-        error = true;
-        return false;
-      }
-
-      return true;
-    };
-
-    // return;
-
-    writes = progressCallAll('Computing page & data writes.', buildStepsWithTargets.flatMap(({flag, pageSpec, targets}) => {
-      const writesFns = targets.map(target => () => {
-        const writes = pageSpec.write(target, {wikiData})?.slice() || [];
-        return validateWrites(writes, flag + '.write') ? writes : [];
-      });
-
-      if (pageSpec.writeTargetless) {
-        writesFns.push(() => {
-          const writes = pageSpec.writeTargetless({wikiData});
-          return validateWrites(writes, flag + '.writeTargetless') ? writes : [];
-        });
-      }
-
-      return writesFns;
-    })).flat();
-
-    if (error) {
-      return;
-    }
-  }
-
-  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)`;
-  } else {
-    logWarn`No writes returned at all, so exiting early. This is probably a bug!`;
-    return;
-  }
+  const developersComment = generateDevelopersCommentHTML({
+    buildTime: BUILD_TIME,
+    commit: COMMIT,
+    wikiData,
+  });
 
-  /*
-  await progressPromiseAll(`Writing data files shared across languages.`, queue(
-    dataWrites.map(({path, data}) => () => {
-      const bound = {};
-
-      bound.serializeLink = bindOpts(serializeLink, {});
-
-      bound.serializeContribs = bindOpts(serializeContribs, {});
-
-      bound.serializeImagePaths = bindOpts(serializeImagePaths, {
-        thumb
-      });
-
-      bound.serializeCover = bindOpts(serializeCover, {
-        [bindOpts.bindIndex]: 2,
-        serializeImagePaths: bound.serializeImagePaths,
-        urls
-      });
-
-      bound.serializeGroupsForAlbum = bindOpts(serializeGroupsForAlbum, {
-        serializeLink
-      });
-
-      bound.serializeGroupsForTrack = bindOpts(serializeGroupsForTrack, {
-        serializeLink
-      });
-
-      // TODO: This only supports one <>-style argument.
-      return writeData(path[0], path[1], data({...bound}));
-    }),
-    queueSize
-  ));
-  */
-
-  const perLanguageFn = async (language, i, entries) => {
-    const baseDirectory =
-      language === finalDefaultLanguage ? '' : language.code;
-
-    console.log(`\x1b[34;1m${`[${i + 1}/${entries.length}] ${language.code} (-> /${baseDirectory}) `.padEnd(60, '-')}\x1b[0m`);
-
-    await progressPromiseAll(`Writing ${language.code}`, queue([
-      ...pageWrites.map((props) => () => {
-        const {path, page} = props;
-
-        const pageSubKey = path[0];
-        const urlArgs = path.slice(1);
-
-        const localizedPaths = Object.fromEntries(
-          Object.entries(languages)
-            .filter(([key, language]) =>
-              key !== 'default' &&
-              !language.hidden)
-            .map(([_key, language]) => [
-              language.code,
-              getPagePaths({
-                baseDirectory:
-                  (language === finalDefaultLanguage
-                    ? ''
-                    : language.code),
-                fullKey: 'localized.' + pageSubKey,
-                urlArgs,
-              }),
-            ]));
-
-        const paths = getPagePaths({
-          baseDirectory,
-          fullKey: 'localized.' + pageSubKey,
-          urlArgs,
-        });
-
-        const to = getURLsFrom({
-          baseDirectory,
-          pageSubKey,
-          paths,
-        });
-
-        const absoluteTo = (targetFullKey, ...args) => {
-          const [groupKey, subKey] = targetFullKey.split('.');
-          const from = urls.from('shared.root');
-          return (
-            '/' +
-            (groupKey === 'localized' && baseDirectory
-              ? from.to(
-                  'localizedWithBaseDirectory.' + subKey,
-                  baseDirectory,
-                  ...args
-                )
-              : from.to(targetFullKey, ...args))
-          );
-        };
-
-        // TODO: Is there some nicer way to define these,
-        // may8e without totally re-8inding everything for
-        // each page?
-        const bound = {};
-
-        bound.html = html;
-
-        bound.getColors = bindOpts(getColors, {
-          chroma,
-        });
-
-        bound.getLinkThemeString = bindOpts(unbound_getLinkThemeString, {
-          getColors: bound.getColors,
-        });
-
-        bound.getThemeString = bindOpts(unbound_getThemeString, {
-          getColors: bound.getColors,
-        });
-
-        bound.link = withEntries(unbound_link, (entries) =>
-          entries
-            .map(([key, fn]) => [key, bindOpts(fn, {
-              getLinkThemeString: bound.getLinkThemeString,
-              to,
-            })]));
-
-        bound.parseAttributes = bindOpts(parseAttributes, {
-          to,
-        });
-
-        bound.find = bindFind(wikiData, {mode: 'warn'});
-
-        bound.transformInline = bindOpts(transformInline, {
-          find: bound.find,
-          link: bound.link,
-          replacerSpec,
-          language,
-          to,
-          wikiData,
-        });
-
-        bound.transformMultiline = bindOpts(transformMultiline, {
-          transformInline: bound.transformInline,
-          parseAttributes: bound.parseAttributes,
-          to,
-        });
-
-        bound.transformLyrics = bindOpts(transformLyrics, {
-          transformInline: bound.transformInline,
-          transformMultiline: bound.transformMultiline,
-        });
-
-        bound.iconifyURL = bindOpts(iconifyURL, {
-          html,
-          language,
-          to,
-        });
-
-        bound.fancifyURL = bindOpts(fancifyURL, {
-          html,
-          language,
-        });
-
-        bound.fancifyFlashURL = bindOpts(fancifyFlashURL, {
-          [bindOpts.bindIndex]: 2,
-          html,
-          language,
-
-          fancifyURL: bound.fancifyURL,
-        });
-
-        bound.getRevealStringFromWarnings = bindOpts(getRevealStringFromWarnings, {
-          html,
-          language,
-        });
-
-        bound.getRevealStringFromTags = bindOpts(getRevealStringFromTags, {
-          language,
-
-          getRevealStringFromWarnings: bound.getRevealStringFromWarnings,
-        });
-
-        bound.getArtistString = bindOpts(getArtistString, {
-          html,
-          link: bound.link,
-          language,
-
-          iconifyURL: bound.iconifyURL,
-        });
-
-        bound.getAlbumCover = bindOpts(getAlbumCover, {
-          to,
-        });
-
-        bound.getTrackCover = bindOpts(getTrackCover, {
-          to,
-        });
-
-        bound.getFlashCover = bindOpts(getFlashCover, {
-          to,
-        });
-
-        bound.getArtistAvatar = bindOpts(getArtistAvatar, {
-          to,
-        });
-
-        bound.generateAdditionalFilesShortcut = bindOpts(generateAdditionalFilesShortcut, {
-          html,
-          language,
-        });
-
-        bound.generateAdditionalFilesList = bindOpts(generateAdditionalFilesList, {
-          html,
-          language,
-        });
-
-        bound.generateNavigationLinks = bindOpts(generateNavigationLinks, {
-          link: bound.link,
-          language,
-        });
-
-        bound.generateStickyHeadingContainer = bindOpts(generateStickyHeadingContainer, {
-          [bindOpts.bindIndex]: 0,
-          getRevealStringFromTags: bound.getRevealStringFromTags,
-          html,
-          img,
-        });
-
-        bound.generateChronologyLinks = bindOpts(generateChronologyLinks, {
-          html,
-          language,
-          link: bound.link,
-          wikiData,
-
-          generateNavigationLinks: bound.generateNavigationLinks,
-        });
-
-        bound.generateCoverLink = bindOpts(generateCoverLink, {
-          [bindOpts.bindIndex]: 0,
-          html,
-          img,
-          link: bound.link,
-          language,
-          to,
-          wikiData,
-
-          getRevealStringFromTags: bound.getRevealStringFromTags,
-        });
-
-        bound.generateInfoGalleryLinks = bindOpts(generateInfoGalleryLinks, {
-          [bindOpts.bindIndex]: 2,
-          link: bound.link,
-          language,
-        });
-
-        bound.generateTrackListDividedByGroups = bindOpts(generateTrackListDividedByGroups, {
-          html,
-          language,
-          wikiData,
-        });
-
-        bound.getGridHTML = bindOpts(getGridHTML, {
-          [bindOpts.bindIndex]: 0,
-          img,
-          html,
-          language,
-
-          getRevealStringFromTags: bound.getRevealStringFromTags,
-        });
-
-        bound.getAlbumGridHTML = bindOpts(getAlbumGridHTML, {
-          [bindOpts.bindIndex]: 0,
-          link: bound.link,
-          language,
-
-          getAlbumCover: bound.getAlbumCover,
-          getGridHTML: bound.getGridHTML,
-        });
-
-        bound.getFlashGridHTML = bindOpts(getFlashGridHTML, {
-          [bindOpts.bindIndex]: 0,
-          link: bound.link,
-
-          getFlashCover: bound.getFlashCover,
-          getGridHTML: bound.getGridHTML,
-        });
-
-        bound.getCarouselHTML = bindOpts(getCarouselHTML, {
-          [bindOpts.bindIndex]: 0,
-          img,
-          html,
-        })
-
-        bound.getAlbumStylesheet = bindOpts(getAlbumStylesheet, {
-          to,
-        });
-
-        const pageInfo = page({
-          ...bound,
-
-          language,
-
-          absoluteTo,
-          relativeTo: to,
-          to,
-          urls,
-
-          getSizeOfAdditionalFile,
-        });
-
-        const oEmbedJSON = generateOEmbedJSON(pageInfo, {
-          language,
-          wikiData,
-        });
-
-        const oEmbedJSONHref =
-          oEmbedJSON &&
-          wikiData.wikiInfo.canonicalBase &&
-          wikiData.wikiInfo.canonicalBase +
-            urls
-              .from('shared.root')
-              .to('shared.path', paths.pathname + OEMBED_JSON_FILE);
-
-        const pageHTML = generateDocumentHTML(pageInfo, {
-          defaultLanguage: finalDefaultLanguage,
-          getThemeString: bound.getThemeString,
-          language,
-          languages,
-          localizedPaths,
-          oEmbedJSONHref,
-          paths,
-          to,
-          transformMultiline: bound.transformMultiline,
-          wikiData,
-        });
-
-        return writePage({
-          html: pageHTML,
-          oEmbedJSON,
-          paths,
-        });
-      }),
-      ...redirectWrites.map(({fromPath, toPath, title: titleFn}) => () => {
-        const title = titleFn({
-          language,
-        });
-
-        const from = getPagePaths({
-          baseDirectory,
-          fullKey: 'localized.' + fromPath[0],
-          urlArgs: fromPath.slice(1),
-        });
-
-        const to = getURLsFrom({
-          baseDirectory,
-          pageSubKey: fromPath[0],
-          paths: from,
-        });
-
-        const target = to('localized.' + toPath[0], ...toPath.slice(1));
-        const html = generateRedirectHTML(title, target, {language});
-        return writePage({html, paths: from});
-      }),
-    ], queueSize));
-  };
+  return selectedBuildMode.go({
+    cliOptions,
+    dataPath,
+    mediaPath,
+    queueSize,
+    srcRootPath: __dirname,
 
-  await wrapLanguages(perLanguageFn, {
+    defaultLanguage: finalDefaultLanguage,
     languages,
-    writeOneLanguage,
-  });
+    wikiData,
+    urls,
+    urlSpec,
 
-  // The single most important step.
-  logInfo`Written!`;
+    cachebust: '?' + CACHEBUST,
+    developersComment,
+    getSizeOfAdditionalFile,
+  });
 }
 
 // TODO: isMain detection isn't consistent across platforms here
 /* eslint-disable-next-line no-constant-condition */
 if (true || isMain(import.meta.url) || path.basename(process.argv[1]) === 'hsmusic') {
-  main()
-    .catch((error) => {
+  (async () => {
+    let result;
+
+    try {
+      result = await main();
+    } catch (error) {
       if (error instanceof AggregateError) {
         showAggregate(error);
       } else {
         console.error(error);
       }
-    })
-    .then(() => {
-      decorateTime.displayTime();
-      CacheableObject.showInvalidAccesses();
-    });
+    }
+
+    if (result !== true) {
+      process.exit(1);
+      return;
+    }
+
+    decorateTime.displayTime();
+    CacheableObject.showInvalidAccesses();
+
+    process.exit(0);
+  })();
 }