« get me outta code hell

clean up imports & miscellaneous metastructures across codebase - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/write
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-08-19 14:13:31 -0300
committer(quasar) nebula <qznebula@protonmail.com>2023-08-19 14:13:31 -0300
commitef8acc5d50fa3c23bd7c9d4bb720b7ff78581981 (patch)
tree6c061e3771cb47619ce9163b559053504a000641 /src/write
parent37ef3f288fce9bd65a2cd86107239e22f977e78d (diff)
clean up imports & miscellaneous metastructures across codebase
Diffstat (limited to 'src/write')
-rw-r--r--src/write/bind-utilities.js74
-rw-r--r--src/write/build-modes/live-dev-server.js41
-rw-r--r--src/write/build-modes/static-build.js78
-rw-r--r--src/write/common-templates.js51
-rw-r--r--src/write/page-template.js635
-rw-r--r--src/write/validate-writes.js136
6 files changed, 92 insertions, 923 deletions
diff --git a/src/write/bind-utilities.js b/src/write/bind-utilities.js
index 2ddc2b3..8e2adea 100644
--- a/src/write/bind-utilities.js
+++ b/src/write/bind-utilities.js
@@ -4,12 +4,11 @@
 
 import chroma from 'chroma-js';
 
-import * as html from '../util/html.js';
-
-import {bindOpts} from '../util/sugar.js';
-import {getColors} from '../util/colors.js';
-import {bindFind} from '../util/find.js';
-import {thumb} from '../util/urls.js';
+import {getColors} from '#colors';
+import {bindFind} from '#find';
+import * as html from '#html';
+import {bindOpts} from '#sugar';
+import {thumb} from '#urls';
 
 export function bindUtilities({
   absoluteTo,
@@ -24,9 +23,6 @@ export function bindUtilities({
   urls,
   wikiData,
 }) {
-  // TODO: Is there some nicer way to define these,
-  // may8e without totally re-8inding everything for
-  // each page?
   const bound = {};
 
   Object.assign(bound, {
@@ -50,65 +46,5 @@ export function bindUtilities({
 
   bound.find = bindFind(wikiData, {mode: 'warn'});
 
-  /*
-  bound.generateNavigationLinks = bindOpts(generateNavigationLinks, {
-    link: bound.link,
-    language,
-  });
-
-  bound.generateStickyHeadingContainer = bindOpts(generateStickyHeadingContainer, {
-    [bindOpts.bindIndex]: 0,
-    html,
-    img: bound.img,
-  });
-
-  bound.generateChronologyLinks = bindOpts(generateChronologyLinks, {
-    html,
-    language,
-    link: bound.link,
-    wikiData,
-
-    generateNavigationLinks: bound.generateNavigationLinks,
-  });
-
-  bound.generateInfoGalleryLinks = bindOpts(generateInfoGalleryLinks, {
-    [bindOpts.bindIndex]: 2,
-    link: bound.link,
-    language,
-  });
-
-  bound.getGridHTML = bindOpts(getGridHTML, {
-    [bindOpts.bindIndex]: 0,
-    img: bound.img,
-    html,
-    language,
-
-    getRevealStringFromArtTags: bound.getRevealStringFromArtTags,
-  });
-
-  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: bound.img,
-    html,
-  });
-  */
-
   return bound;
 }
diff --git a/src/write/build-modes/live-dev-server.js b/src/write/build-modes/live-dev-server.js
index edee326..2767a02 100644
--- a/src/write/build-modes/live-dev-server.js
+++ b/src/write/build-modes/live-dev-server.js
@@ -1,35 +1,26 @@
-import * as http from 'http';
-import {createReadStream} from 'fs';
-import {stat} from 'fs/promises';
-import * as path from 'path';
-import {pipeline} from 'stream/promises'
+import * as http from 'node:http';
+import {createReadStream} from 'node:fs';
+import {stat} from 'node:fs/promises';
+import * as path from 'node:path';
+import {pipeline} from 'node:stream/promises'
+
+import {logInfo, logWarn, progressCallAll} from '#cli';
+import {watchContentDependencies} from '#content-dependencies';
+import {quickEvaluate} from '#content-function';
+import * as html from '#html';
+import * as pageSpecs from '#page-specs';
+import {serializeThings} from '#serialize';
+import {empty} from '#sugar';
 
-import {bindUtilities} from '../bind-utilities.js';
-
-import {serializeThings} from '../../data/serialize.js';
-
-import * as pageSpecs from '../../page/index.js';
-
-import {logInfo, logWarn, progressCallAll} from '../../util/cli.js';
-import * as html from '../../util/html.js';
-import {empty} from '../../util/sugar.js';
 import {
   getPagePathname,
   getPagePathnameAcrossLanguages,
   getURLsFrom,
   getURLsFromRoot,
-} from '../../util/urls.js';
+} from '#urls';
 
-import {
-  generateGlobalWikiDataJSON,
-  generateRedirectHTML,
-} from '../page-template.js';
-
-import {
-  watchContentDependencies,
-} from '../../content/dependencies/index.js';
-
-import {quickEvaluate} from '../../content-function.js';
+import {bindUtilities} from '../bind-utilities.js';
+import {generateGlobalWikiDataJSON, generateRedirectHTML} from '../common-templates.js';
 
 const defaultHost = '0.0.0.0';
 const defaultPort = 8002;
diff --git a/src/write/build-modes/static-build.js b/src/write/build-modes/static-build.js
index 4f07405..2210dfe 100644
--- a/src/write/build-modes/static-build.js
+++ b/src/write/build-modes/static-build.js
@@ -1,20 +1,20 @@
-import * as path from 'path';
-
-import {bindUtilities} from '../bind-utilities.js';
-// import {validateWrites} from '../validate-writes.js';
+import * as path from 'node:path';
 
 import {
-  quickLoadContentDependencies,
-} from '../../content/dependencies/index.js';
-
-import {quickEvaluate} from '../../content-function.js';
-
-import {serializeThings} from '../../data/serialize.js';
-
-import * as pageSpecs from '../../page/index.js';
+  copyFile,
+  mkdir,
+  stat,
+  symlink,
+  writeFile,
+  unlink,
+} from 'node:fs/promises';
 
-import * as html from '../../util/html.js';
-import {empty, queue, withEntries} from '../../util/sugar.js';
+import {quickLoadContentDependencies} from '#content-dependencies';
+import {quickEvaluate} from '#content-function';
+import * as html from '#html';
+import * as pageSpecs from '#page-specs';
+import {serializeThings} from '#serialize';
+import {empty, queue, withEntries} from '#sugar';
 
 import {
   logError,
@@ -22,14 +22,17 @@ import {
   logWarn,
   progressCallAll,
   progressPromiseAll,
-} from '../../util/cli.js';
+} from '#cli';
 
 import {
   getPagePathname,
   getPagePathnameAcrossLanguages,
   getURLsFrom,
   getURLsFromRoot,
-} from '../../util/urls.js';
+} from '#urls';
+
+import {bindUtilities} from '../bind-utilities.js';
+import {generateRedirectHTML, generateGlobalWikiDataJSON} from '../common-templates.js';
 
 const pageFlags = Object.keys(pageSpecs);
 
@@ -78,36 +81,6 @@ export function 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),
-          })),
-      ])),
-  ]);
-}
-
 export async function go({
   cliOptions,
   _dataPath,
@@ -173,12 +146,10 @@ export async function go({
     outputPath,
     urls,
     wikiData,
-    /*
     wikiDataJSON: generateGlobalWikiDataJSON({
       serializeThings,
       wikiData,
-    })
-    */
+    }),
   });
 
   const buildSteps = writeAll
@@ -409,15 +380,6 @@ async function wrapLanguages(fn, {
   }
 }
 
-import {
-  copyFile,
-  mkdir,
-  stat,
-  symlink,
-  writeFile,
-  unlink,
-} from 'fs/promises';
-
 async function writePage({
   pageHTML,
   oEmbedJSON = '',
diff --git a/src/write/common-templates.js b/src/write/common-templates.js
new file mode 100644
index 0000000..2dd4c92
--- /dev/null
+++ b/src/write/common-templates.js
@@ -0,0 +1,51 @@
+import * as html from '#html';
+
+export 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),
+          })),
+      ])),
+  ]);
+}
+
+export function generateGlobalWikiDataJSON({
+  serializeThings,
+  wikiData,
+}) {
+  const stringifyThings = thingData =>
+    JSON.stringify(serializeThings(thingData));
+
+  return '{\n' +
+    ([
+      `"albumData": ${stringifyThings(wikiData.albumData)},`,
+      wikiData.wikiInfo.enableFlashesAndGames &&
+        `"flashData": ${stringifyThings(wikiData.flashData)},`,
+      `"artistData": ${stringifyThings(wikiData.artistData)}`,
+    ]
+      .filter(Boolean)
+      .map(line => '  ' + line)
+      .join('\n')) +
+    '\n}';
+}
diff --git a/src/write/page-template.js b/src/write/page-template.js
deleted file mode 100644
index d3d7b09..0000000
--- a/src/write/page-template.js
+++ /dev/null
@@ -1,635 +0,0 @@
-import chroma from 'chroma-js';
-
-import * as html from '../util/html.js';
-import {getColors} from '../util/colors.js';
-
-export function generateDevelopersCommentHTML({
-  buildTime,
-  commit,
-  wikiData,
-}) {
-  const {name, canonicalBase} = wikiData.wikiInfo;
-  return `<!--\n` + [
-    canonicalBase
-      ? `hsmusic.wiki - ${name}, ${canonicalBase}`
-      : `hsmusic.wiki - ${name}`,
-    'Code copyright 2019-2023 Quasar Nebula et al (MIT License)',
-    ...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/',
-    ],
-    '***',
-    buildTime &&
-      `Site built: ${buildTime.toLocaleString('en-US', {
-        dateStyle: 'long',
-        timeStyle: 'long',
-      })}`,
-    commit &&
-      `Latest code commit: ${commit}`,
-  ]
-    .filter(Boolean)
-    .map(line => `    ` + line)
-    .join('\n') + `\n-->`;
-}
-
-export function generateDocumentHTML(pageInfo, {
-  cachebust,
-  defaultLanguage,
-  developersComment,
-  generateCoverLink,
-  generateStickyHeadingContainer,
-  img,
-  getThemeString,
-  language,
-  languages,
-  localizedPathnames,
-  oEmbedJSONHref,
-  pagePath,
-  pathname,
-  to,
-  transformMultiline,
-  wikiData,
-}) {
-  const {wikiInfo} = wikiData;
-
-  let {
-    title = '',
-    meta = {},
-    theme = '',
-    stylesheet = '',
-
-    showWikiNameInTitle = true,
-    themeColor = '',
-
-    // missing properties are auto-filled, see below!
-    body = {},
-    banner = {},
-    cover = {},
-    main = {},
-    sidebarLeft = {},
-    sidebarRight = {},
-    nav = {},
-    secondaryNav = {},
-    footer = {},
-    socialEmbed = {},
-  } = pageInfo;
-
-  body ||= {};
-  body.style ??= '';
-
-  theme ||= getThemeString(wikiInfo.color);
-
-  banner ||= {};
-  banner.classes ??= [];
-  banner.src ??= '';
-  banner.position ??= '';
-  banner.dimensions ??= [0, 0];
-
-  main ||= {};
-  main.classes ??= [];
-  main.content ??= '';
-  main.headingMode ??= 'none';
-
-  cover ||= {};
-  cover.src ??= '';
-  cover.alt ??= '';
-  cover.artTags ??= [];
-
-  sidebarLeft ||= {};
-  sidebarRight ||= {};
-
-  for (const sidebar of [sidebarLeft, sidebarRight]) {
-    sidebar.classes ??= [];
-    sidebar.content ??= '';
-    sidebar.collapse ??= true;
-  }
-
-  nav ||= {};
-  nav.classes ??= [];
-  nav.content ??= '';
-  nav.bottomRowContent ??= '';
-  nav.links ??= [];
-  nav.linkContainerClasses ??= [];
-
-  secondaryNav ||= {};
-  secondaryNav.content ??= '';
-  secondaryNav.content ??= '';
-
-  footer ||= {};
-  footer.classes ??= [];
-  footer.content ??= wikiInfo.footerContent
-    ? transformMultiline(wikiInfo.footerContent)
-    : '';
-
-  socialEmbed ||= {};
-
-  const colors = themeColor
-    ? getColors(themeColor, {chroma})
-    : null;
-
-  const canonical = wikiInfo.canonicalBase
-    ? wikiInfo.canonicalBase + (pathname === '/' ? '' : pathname)
-    : '';
-
-  const localizedCanonical = wikiInfo.canonicalBase
-    ? Object.entries(localizedPathnames).map(([code, pathname]) => ({
-        lang: code,
-        href: wikiInfo.canonicalBase + (pathname === '/' ? '' : pathname),
-      }))
-    : [];
-
-  const collapseSidebars =
-    sidebarLeft.collapse !== false && sidebarRight.collapse !== false;
-
-
-
-  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({
-          defaultLanguage,
-          html,
-          language,
-          languages,
-          pagePath,
-          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',
-          {
-            [html.onlyIfContent]: true,
-            id,
-            class: [
-              'sidebar-column',
-              'sidebar-multiple',
-              wide && 'wide',
-              !collapse && 'no-hide',
-              stickyMode !== 'none' && 'sticky-' + stickyMode,
-            ],
-          },
-          multiple
-            .filter(Boolean)
-            .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)
-          : 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 processSkippers = skipperList =>
-    skipperList
-      .filter(Boolean)
-      .map(([href, stringSubkey]) =>
-        html.tag('span', {class: 'skipper'},
-          html.tag('a',
-            {href},
-            language.$(`misc.skippers.${stringSubkey}`))));
-
-  // Hilariously jank. Sorry!
-  const hasID = id => mainHTML.includes(`id="${id}"`);
-  const hasTracks = hasID('tracks');
-  const hasArt = hasID('art');
-  const hasFlashes = hasID('flashes');
-  const hasContributors = hasID('contributors');
-  const hasReferences = hasID('references');
-  const hasReferencedBy = hasID('referenced-by');
-  const hasSamples = hasID('samples');
-  const hasSampledBy = hasID('sampled-by');
-  const hasFeatures = hasID('features');
-  const hasFeaturedIn = hasID('featured-in');
-  const hasLyrics = hasID('lyrics');
-  const hasSheetMusicFiles = hasID('sheet-music-files');
-  const hasMidiProjectFiles = hasID('midi-project-files');
-  const hasAdditionalFiles = hasID('additional-files');
-  const hasCommentary = hasID('commentary');
-  const hasArtistCommentary = hasID('artist-commentary');
-
-  const skippersHTML =
-    mainHTML &&
-      html.tag('div', {id: 'skippers'}, [
-        html.tag('span', language.$('misc.skippers.skipTo')),
-        html.tag('div', {class: 'skipper-list'},
-          processSkippers([
-            ['#content', 'content'],
-            sidebarLeftHTML &&
-              [
-                '#sidebar-left',
-                sidebarRightHTML
-                  ? 'sidebar.left'
-                  : 'sidebar',
-              ],
-            sidebarRightHTML &&
-              [
-                '#sidebar-right',
-                sidebarLeftHTML
-                  ? 'sidebar.right'
-                  : 'sidebar',
-              ],
-            navHTML &&
-              ['#header', 'header'],
-            footerHTML &&
-              ['#footer', 'footer'],
-          ])),
-
-        html.tag('div',
-          {
-            [html.onlyIfContent]: true,
-            class: 'skipper-list'
-          },
-          processSkippers([
-            hasTracks &&
-              ['#tracks', 'tracks'],
-            hasArt &&
-              ['#art', 'art'],
-            hasFlashes &&
-              ['#flashes', 'flashes'],
-            hasContributors &&
-              ['#contributors', 'contributors'],
-            hasReferences &&
-              ['#references', 'references'],
-            hasReferencedBy &&
-              ['#referenced-by', 'referencedBy'],
-            hasSamples &&
-              ['#samples', 'samples'],
-            hasSampledBy &&
-              ['#sampled-by', 'sampledBy'],
-            hasFeatures &&
-              ['#features', 'features'],
-            hasFeaturedIn &&
-              ['#featured-in', 'featuredIn'],
-            hasLyrics &&
-              ['#lyrics', 'lyrics'],
-            hasSheetMusicFiles &&
-              ['#sheet-music-files', 'sheetMusicFiles'],
-            hasMidiProjectFiles &&
-              ['#midi-project-files', 'midiProjectFiles'],
-            hasAdditionalFiles &&
-              ['#additional-files', 'additionalFiles'],
-            hasCommentary &&
-              ['#commentary', 'commentary'],
-            hasArtistCommentary &&
-              ['#artist-commentary', 'artistCommentary'],
-          ])),
-      ]);
-
-  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: getRevealStringFromContentWarningMessage(
-              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 imageOverlayHTML = html.tag('div', {id: 'image-overlay-container'},
-    html.tag('div', {id: 'image-overlay-content-container'}, [
-      html.tag('a', {id: 'image-overlay-image-container'}, [
-        html.tag('img', {id: 'image-overlay-image'}),
-        html.tag('img', {id: 'image-overlay-image-thumb'}),
-      ]),
-      html.tag('div', {id: 'image-overlay-action-container'}, [
-        html.tag('div', {id: 'image-overlay-action-content-without-size'},
-          language.$('releaseInfo.viewOriginalFile', {
-            link: html.tag('a', {class: 'image-overlay-view-original'},
-              language.$('releaseInfo.viewOriginalFile.link')),
-          })),
-
-        html.tag('div', {id: 'image-overlay-action-content-with-size'}, [
-          language.$('releaseInfo.viewOriginalFile.withSize', {
-            link: html.tag('a', {class: 'image-overlay-view-original'},
-              language.$('releaseInfo.viewOriginalFile.link')),
-            size: html.tag('span',
-              {[html.joinChildren]: ''},
-              [
-                html.tag('span', {id: 'image-overlay-file-size-kilobytes'},
-                  language.$('count.fileSize.kilobytes', {
-                    kilobytes: html.tag('span', {class: 'image-overlay-file-size-count'}),
-                  })),
-                html.tag('span', {id: 'image-overlay-file-size-megabytes'},
-                  language.$('count.fileSize.megabytes', {
-                    megabytes: html.tag('span', {class: 'image-overlay-file-size-count'}),
-                  })),
-              ]),
-          }),
-
-          html.tag('span', {id: 'image-overlay-file-size-warning'},
-            language.$('releaseInfo.viewOriginalFile.sizeWarning')),
-        ]),
-      ]),
-    ]));
-
-  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`
-}
-
-export 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));
-}
-
-export 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),
-          })),
-      ])),
-  ]);
-}
-
-export function generateGlobalWikiDataJSON({
-  serializeThings,
-  wikiData,
-}) {
-  return '{\n' +
-    ([
-      `"albumData": ${stringifyThings(wikiData.albumData)},`,
-      wikiData.wikiInfo.enableFlashesAndGames &&
-        `"flashData": ${stringifyThings(wikiData.flashData)},`,
-      `"artistData": ${stringifyThings(wikiData.artistData)}`,
-    ]
-      .filter(Boolean)
-      .map(line => '  ' + line)
-      .join('\n')) +
-    '\n}';
-
-  function stringifyThings(thingData) {
-    return JSON.stringify(serializeThings(thingData));
-  }
-}
diff --git a/src/write/validate-writes.js b/src/write/validate-writes.js
deleted file mode 100644
index 52c7dfa..0000000
--- a/src/write/validate-writes.js
+++ /dev/null
@@ -1,136 +0,0 @@
-// TODO: All this is for an outdated spec + should use aggregate errors
-
-import {logError} from '../util/cli.js';
-
-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, {
-  urlSpec,
-}) {
-  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 validateWrites(writes, {
-  functionName,
-  urlSpec,
-}) {
-  // 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`${functionName} didn't return an array!`;
-    return false;
-  }
-
-  if (!(
-    writes.every((obj) => typeof obj === 'object') &&
-    writes.every((obj) => {
-      const result = validateWriteObject(obj, {
-        urlSpec,
-      });
-      if (result.error) {
-        logError`Validating write object failed: ${result.error}`;
-        return false;
-      } else {
-        return true;
-      }
-    })
-  )) {
-    logError`${functionName} returned invalid entries!`;
-    return false;
-  }
-
-  return true;
-}