« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/yaml.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/data/yaml.js')
-rw-r--r--src/data/yaml.js595
1 files changed, 297 insertions, 298 deletions
diff --git a/src/data/yaml.js b/src/data/yaml.js
index 5058bb3..a425576 100644
--- a/src/data/yaml.js
+++ b/src/data/yaml.js
@@ -1,13 +1,13 @@
-// @format
-//
+/** @format */
+
 // yaml.js - specification for HSMusic YAML data file format and utilities for
 // loading and processing YAML files and documents
 
-import * as path from "path";
-import yaml from "js-yaml";
+import * as path from 'path';
+import yaml from 'js-yaml';
 
-import { readFile } from "fs/promises";
-import { inspect as nodeInspect } from "util";
+import {readFile} from 'fs/promises';
+import {inspect as nodeInspect} from 'util';
 
 import {
   Album,
@@ -19,16 +19,15 @@ import {
   GroupCategory,
   HomepageLayout,
   HomepageLayoutAlbumsRow,
-  HomepageLayoutRow,
   NewsEntry,
   StaticPage,
   Thing,
   Track,
   TrackGroup,
   WikiInfo,
-} from "./things.js";
+} from './things.js';
 
-import { color, ENABLE_COLOR, logInfo, logWarn } from "../util/cli.js";
+import {color, ENABLE_COLOR, logInfo, logWarn} from '../util/cli.js';
 
 import {
   decorateErrorWithIndex,
@@ -36,36 +35,36 @@ import {
   openAggregate,
   showAggregate,
   withAggregate,
-} from "../util/sugar.js";
+} from '../util/sugar.js';
 
 import {
   sortAlbumsTracksChronologically,
   sortAlphabetically,
   sortChronologically,
-} from "../util/wiki-data.js";
+} from '../util/wiki-data.js';
 
-import find, { bindFind } from "../util/find.js";
-import { findFiles } from "../util/io.js";
+import find, {bindFind} from '../util/find.js';
+import {findFiles} from '../util/io.js';
 
 // --> General supporting stuff
 
 function inspect(value) {
-  return nodeInspect(value, { colors: ENABLE_COLOR });
+  return nodeInspect(value, {colors: ENABLE_COLOR});
 }
 
 // --> YAML data repository structure constants
 
-export const WIKI_INFO_FILE = "wiki-info.yaml";
-export const BUILD_DIRECTIVE_DATA_FILE = "build-directives.yaml";
-export const HOMEPAGE_LAYOUT_DATA_FILE = "homepage.yaml";
-export const ARTIST_DATA_FILE = "artists.yaml";
-export const FLASH_DATA_FILE = "flashes.yaml";
-export const NEWS_DATA_FILE = "news.yaml";
-export const ART_TAG_DATA_FILE = "tags.yaml";
-export const GROUP_DATA_FILE = "groups.yaml";
-export const STATIC_PAGE_DATA_FILE = "static-pages.yaml";
+export const WIKI_INFO_FILE = 'wiki-info.yaml';
+export const BUILD_DIRECTIVE_DATA_FILE = 'build-directives.yaml';
+export const HOMEPAGE_LAYOUT_DATA_FILE = 'homepage.yaml';
+export const ARTIST_DATA_FILE = 'artists.yaml';
+export const FLASH_DATA_FILE = 'flashes.yaml';
+export const NEWS_DATA_FILE = 'news.yaml';
+export const ART_TAG_DATA_FILE = 'tags.yaml';
+export const GROUP_DATA_FILE = 'groups.yaml';
+export const STATIC_PAGE_DATA_FILE = 'static-pages.yaml';
 
-export const DATA_ALBUM_DIRECTORY = "album";
+export const DATA_ALBUM_DIRECTORY = 'album';
 
 // --> Document processing functions
 
@@ -119,7 +118,7 @@ function makeProcessDocument(
   );
 
   const decorateErrorWithName = (fn) => {
-    const nameField = propertyFieldMapping["name"];
+    const nameField = propertyFieldMapping['name'];
     if (!nameField) return fn;
 
     return (document) => {
@@ -168,8 +167,8 @@ function makeProcessDocument(
     const thing = Reflect.construct(thingClass, []);
 
     withAggregate(
-      { message: `Errors applying ${color.green(thingClass.name)} properties` },
-      ({ call }) => {
+      {message: `Errors applying ${color.green(thingClass.name)} properties`},
+      ({call}) => {
         for (const [property, value] of Object.entries(sourceProperties)) {
           call(() => (thing[property] = value));
         }
@@ -184,7 +183,7 @@ makeProcessDocument.UnknownFieldsError = class UnknownFieldsError extends (
   Error
 ) {
   constructor(fields) {
-    super(`Unknown fields present: ${fields.join(", ")}`);
+    super(`Unknown fields present: ${fields.join(', ')}`);
     this.fields = fields;
   }
 };
@@ -192,72 +191,72 @@ makeProcessDocument.UnknownFieldsError = class UnknownFieldsError extends (
 export const processAlbumDocument = makeProcessDocument(Album, {
   fieldTransformations: {
     Artists: parseContributors,
-    "Cover Artists": parseContributors,
-    "Default Track Cover Artists": parseContributors,
-    "Wallpaper Artists": parseContributors,
-    "Banner Artists": parseContributors,
+    'Cover Artists': parseContributors,
+    'Default Track Cover Artists': parseContributors,
+    'Wallpaper Artists': parseContributors,
+    'Banner Artists': parseContributors,
 
     Date: (value) => new Date(value),
-    "Date Added": (value) => new Date(value),
-    "Cover Art Date": (value) => new Date(value),
-    "Default Track Cover Art Date": (value) => new Date(value),
+    'Date Added': (value) => new Date(value),
+    'Cover Art Date': (value) => new Date(value),
+    'Default Track Cover Art Date': (value) => new Date(value),
 
-    "Banner Dimensions": parseDimensions,
+    'Banner Dimensions': parseDimensions,
 
-    "Additional Files": parseAdditionalFiles,
+    'Additional Files': parseAdditionalFiles,
   },
 
   propertyFieldMapping: {
-    name: "Album",
+    name: 'Album',
 
-    color: "Color",
-    directory: "Directory",
-    urls: "URLs",
+    color: 'Color',
+    directory: 'Directory',
+    urls: 'URLs',
 
-    artistContribsByRef: "Artists",
-    coverArtistContribsByRef: "Cover Artists",
-    trackCoverArtistContribsByRef: "Default Track Cover Artists",
+    artistContribsByRef: 'Artists',
+    coverArtistContribsByRef: 'Cover Artists',
+    trackCoverArtistContribsByRef: 'Default Track Cover Artists',
 
-    coverArtFileExtension: "Cover Art File Extension",
-    trackCoverArtFileExtension: "Track Art File Extension",
+    coverArtFileExtension: 'Cover Art File Extension',
+    trackCoverArtFileExtension: 'Track Art File Extension',
 
-    wallpaperArtistContribsByRef: "Wallpaper Artists",
-    wallpaperStyle: "Wallpaper Style",
-    wallpaperFileExtension: "Wallpaper File Extension",
+    wallpaperArtistContribsByRef: 'Wallpaper Artists',
+    wallpaperStyle: 'Wallpaper Style',
+    wallpaperFileExtension: 'Wallpaper File Extension',
 
-    bannerArtistContribsByRef: "Banner Artists",
-    bannerStyle: "Banner Style",
-    bannerFileExtension: "Banner File Extension",
-    bannerDimensions: "Banner Dimensions",
+    bannerArtistContribsByRef: 'Banner Artists',
+    bannerStyle: 'Banner Style',
+    bannerFileExtension: 'Banner File Extension',
+    bannerDimensions: 'Banner Dimensions',
 
-    date: "Date",
-    trackArtDate: "Default Track Cover Art Date",
-    coverArtDate: "Cover Art Date",
-    dateAddedToWiki: "Date Added",
+    date: 'Date',
+    trackArtDate: 'Default Track Cover Art Date',
+    coverArtDate: 'Cover Art Date',
+    dateAddedToWiki: 'Date Added',
 
-    hasCoverArt: "Has Cover Art",
-    hasTrackArt: "Has Track Art",
-    hasTrackNumbers: "Has Track Numbers",
-    isMajorRelease: "Major Release",
-    isListedOnHomepage: "Listed on Homepage",
+    hasCoverArt: 'Has Cover Art',
+    hasTrackArt: 'Has Track Art',
+    hasTrackNumbers: 'Has Track Numbers',
+    isMajorRelease: 'Major Release',
+    isListedOnHomepage: 'Listed on Homepage',
 
-    groupsByRef: "Groups",
-    artTagsByRef: "Art Tags",
-    commentary: "Commentary",
+    groupsByRef: 'Groups',
+    artTagsByRef: 'Art Tags',
+    commentary: 'Commentary',
 
-    additionalFiles: "Additional Files",
+    additionalFiles: 'Additional Files',
   },
 });
 
 export const processTrackGroupDocument = makeProcessDocument(TrackGroup, {
   fieldTransformations: {
-    "Date Originally Released": (value) => new Date(value),
+    'Date Originally Released': (value) => new Date(value),
   },
 
   propertyFieldMapping: {
-    name: "Group",
-    color: "Color",
-    dateOriginallyReleased: "Date Originally Released",
+    name: 'Group',
+    color: 'Color',
+    dateOriginallyReleased: 'Date Originally Released',
   },
 });
 
@@ -265,60 +264,60 @@ export const processTrackDocument = makeProcessDocument(Track, {
   fieldTransformations: {
     Duration: getDurationInSeconds,
 
-    "Date First Released": (value) => new Date(value),
-    "Cover Art Date": (value) => new Date(value),
+    'Date First Released': (value) => new Date(value),
+    'Cover Art Date': (value) => new Date(value),
 
     Artists: parseContributors,
     Contributors: parseContributors,
-    "Cover Artists": parseContributors,
+    'Cover Artists': parseContributors,
 
-    "Additional Files": parseAdditionalFiles,
+    'Additional Files': parseAdditionalFiles,
   },
 
   propertyFieldMapping: {
-    name: "Track",
+    name: 'Track',
 
-    directory: "Directory",
-    duration: "Duration",
-    urls: "URLs",
+    directory: 'Directory',
+    duration: 'Duration',
+    urls: 'URLs',
 
-    coverArtDate: "Cover Art Date",
-    coverArtFileExtension: "Cover Art File Extension",
-    dateFirstReleased: "Date First Released",
-    hasCoverArt: "Has Cover Art",
-    hasURLs: "Has URLs",
+    coverArtDate: 'Cover Art Date',
+    coverArtFileExtension: 'Cover Art File Extension',
+    dateFirstReleased: 'Date First Released',
+    hasCoverArt: 'Has Cover Art',
+    hasURLs: 'Has URLs',
 
-    referencedTracksByRef: "Referenced Tracks",
-    artistContribsByRef: "Artists",
-    contributorContribsByRef: "Contributors",
-    coverArtistContribsByRef: "Cover Artists",
-    artTagsByRef: "Art Tags",
-    originalReleaseTrackByRef: "Originally Released As",
+    referencedTracksByRef: 'Referenced Tracks',
+    artistContribsByRef: 'Artists',
+    contributorContribsByRef: 'Contributors',
+    coverArtistContribsByRef: 'Cover Artists',
+    artTagsByRef: 'Art Tags',
+    originalReleaseTrackByRef: 'Originally Released As',
 
-    commentary: "Commentary",
-    lyrics: "Lyrics",
+    commentary: 'Commentary',
+    lyrics: 'Lyrics',
 
-    additionalFiles: "Additional Files",
+    additionalFiles: 'Additional Files',
   },
 
-  ignoredFields: ["Sampled Tracks"],
+  ignoredFields: ['Sampled Tracks'],
 });
 
 export const processArtistDocument = makeProcessDocument(Artist, {
   propertyFieldMapping: {
-    name: "Artist",
+    name: 'Artist',
 
-    directory: "Directory",
-    urls: "URLs",
-    hasAvatar: "Has Avatar",
-    avatarFileExtension: "Avatar File Extension",
+    directory: 'Directory',
+    urls: 'URLs',
+    hasAvatar: 'Has Avatar',
+    avatarFileExtension: 'Avatar File Extension',
 
-    aliasNames: "Aliases",
+    aliasNames: 'Aliases',
 
-    contextNotes: "Context Notes",
+    contextNotes: 'Context Notes',
   },
 
-  ignoredFields: ["Dead URLs"],
+  ignoredFields: ['Dead URLs'],
 });
 
 export const processFlashDocument = makeProcessDocument(Flash, {
@@ -329,26 +328,26 @@ export const processFlashDocument = makeProcessDocument(Flash, {
   },
 
   propertyFieldMapping: {
-    name: "Flash",
+    name: 'Flash',
 
-    directory: "Directory",
-    page: "Page",
-    date: "Date",
-    coverArtFileExtension: "Cover Art File Extension",
+    directory: 'Directory',
+    page: 'Page',
+    date: 'Date',
+    coverArtFileExtension: 'Cover Art File Extension',
 
-    featuredTracksByRef: "Featured Tracks",
-    contributorContribsByRef: "Contributors",
-    urls: "URLs",
+    featuredTracksByRef: 'Featured Tracks',
+    contributorContribsByRef: 'Contributors',
+    urls: 'URLs',
   },
 });
 
 export const processFlashActDocument = makeProcessDocument(FlashAct, {
   propertyFieldMapping: {
-    name: "Act",
-    color: "Color",
-    anchor: "Anchor",
-    jump: "Jump",
-    jumpColor: "Jump Color",
+    name: 'Act',
+    color: 'Color',
+    anchor: 'Anchor',
+    jump: 'Jump',
+    jumpColor: 'Jump Color',
   },
 });
 
@@ -358,66 +357,66 @@ export const processNewsEntryDocument = makeProcessDocument(NewsEntry, {
   },
 
   propertyFieldMapping: {
-    name: "Name",
-    directory: "Directory",
-    date: "Date",
-    content: "Content",
+    name: 'Name',
+    directory: 'Directory',
+    date: 'Date',
+    content: 'Content',
   },
 });
 
 export const processArtTagDocument = makeProcessDocument(ArtTag, {
   propertyFieldMapping: {
-    name: "Tag",
-    directory: "Directory",
-    color: "Color",
-    isContentWarning: "Is CW",
+    name: 'Tag',
+    directory: 'Directory',
+    color: 'Color',
+    isContentWarning: 'Is CW',
   },
 });
 
 export const processGroupDocument = makeProcessDocument(Group, {
   propertyFieldMapping: {
-    name: "Group",
-    directory: "Directory",
-    description: "Description",
-    urls: "URLs",
+    name: 'Group',
+    directory: 'Directory',
+    description: 'Description',
+    urls: 'URLs',
   },
 });
 
 export const processGroupCategoryDocument = makeProcessDocument(GroupCategory, {
   propertyFieldMapping: {
-    name: "Category",
-    color: "Color",
+    name: 'Category',
+    color: 'Color',
   },
 });
 
 export const processStaticPageDocument = makeProcessDocument(StaticPage, {
   propertyFieldMapping: {
-    name: "Name",
-    nameShort: "Short Name",
-    directory: "Directory",
+    name: 'Name',
+    nameShort: 'Short Name',
+    directory: 'Directory',
 
-    content: "Content",
-    stylesheet: "Style",
+    content: 'Content',
+    stylesheet: 'Style',
 
-    showInNavigationBar: "Show in Navigation Bar",
+    showInNavigationBar: 'Show in Navigation Bar',
   },
 });
 
 export const processWikiInfoDocument = makeProcessDocument(WikiInfo, {
   propertyFieldMapping: {
-    name: "Name",
-    nameShort: "Short Name",
-    color: "Color",
-    description: "Description",
-    footerContent: "Footer Content",
-    defaultLanguage: "Default Language",
-    canonicalBase: "Canonical Base",
-    divideTrackListsByGroupsByRef: "Divide Track Lists By Groups",
-    enableFlashesAndGames: "Enable Flashes & Games",
-    enableListings: "Enable Listings",
-    enableNews: "Enable News",
-    enableArtTagUI: "Enable Art Tag UI",
-    enableGroupUI: "Enable Group UI",
+    name: 'Name',
+    nameShort: 'Short Name',
+    color: 'Color',
+    description: 'Description',
+    footerContent: 'Footer Content',
+    defaultLanguage: 'Default Language',
+    canonicalBase: 'Canonical Base',
+    divideTrackListsByGroupsByRef: 'Divide Track Lists By Groups',
+    enableFlashesAndGames: 'Enable Flashes & Games',
+    enableListings: 'Enable Listings',
+    enableNews: 'Enable News',
+    enableArtTagUI: 'Enable Art Tag UI',
+    enableGroupUI: 'Enable Group UI',
   },
 });
 
@@ -425,10 +424,10 @@ export const processHomepageLayoutDocument = makeProcessDocument(
   HomepageLayout,
   {
     propertyFieldMapping: {
-      sidebarContent: "Sidebar Content",
+      sidebarContent: 'Sidebar Content',
     },
 
-    ignoredFields: ["Homepage"],
+    ignoredFields: ['Homepage'],
   }
 );
 
@@ -437,9 +436,9 @@ export function makeProcessHomepageLayoutRowDocument(rowClass, spec) {
     ...spec,
 
     propertyFieldMapping: {
-      name: "Row",
-      color: "Color",
-      type: "Type",
+      name: 'Row',
+      color: 'Color',
+      type: 'Type',
       ...spec.propertyFieldMapping,
     },
   });
@@ -448,16 +447,16 @@ export function makeProcessHomepageLayoutRowDocument(rowClass, spec) {
 export const homepageLayoutRowTypeProcessMapping = {
   albums: makeProcessHomepageLayoutRowDocument(HomepageLayoutAlbumsRow, {
     propertyFieldMapping: {
-      sourceGroupByRef: "Group",
-      countAlbumsFromGroup: "Count",
-      sourceAlbumsByRef: "Albums",
-      actionLinks: "Actions",
+      sourceGroupByRef: 'Group',
+      countAlbumsFromGroup: 'Count',
+      sourceAlbumsByRef: 'Albums',
+      actionLinks: 'Actions',
     },
   }),
 };
 
 export function processHomepageLayoutRowDocument(document) {
-  const type = document["Type"];
+  const type = document['Type'];
 
   const match = Object.entries(homepageLayoutRowTypeProcessMapping).find(
     ([key]) => key === type
@@ -473,15 +472,15 @@ export function processHomepageLayoutRowDocument(document) {
 // --> Utilities shared across document parsing functions
 
 export function getDurationInSeconds(string) {
-  if (typeof string === "number") {
+  if (typeof string === 'number') {
     return string;
   }
 
-  if (typeof string !== "string") {
+  if (typeof string !== 'string') {
     throw new TypeError(`Expected a string or number, got ${string}`);
   }
 
-  const parts = string.split(":").map((n) => parseInt(n));
+  const parts = string.split(':').map((n) => parseInt(n));
   if (parts.length === 3) {
     return parts[0] * 3600 + parts[1] * 60 + parts[2];
   } else if (parts.length === 2) {
@@ -499,16 +498,16 @@ export function parseAdditionalFiles(array) {
   }
 
   return array.map((item) => ({
-    title: item["Title"],
-    description: item["Description"] ?? null,
-    files: item["Files"],
+    title: item['Title'],
+    description: item['Description'] ?? null,
+    files: item['Files'],
   }));
 }
 
 export function parseCommentary(text) {
   if (text) {
-    const lines = String(text).split("\n");
-    if (!lines[0].replace(/<\/b>/g, "").includes(":</i>")) {
+    const lines = String(text).split('\n');
+    if (!lines[0].replace(/<\/b>/g, '').includes(':</i>')) {
       return {
         error: `An entry is missing commentary citation: "${lines[0].slice(
           0,
@@ -527,7 +526,7 @@ export function parseContributors(contributors) {
     return null;
   }
 
-  if (contributors.length === 1 && contributors[0].startsWith("<i>")) {
+  if (contributors.length === 1 && contributors[0].startsWith('<i>')) {
     const arr = [];
     arr.textContent = contributors[0];
     return arr;
@@ -542,17 +541,17 @@ export function parseContributors(contributors) {
     }
     const who = match[1];
     const what = match[3] || null;
-    return { who, what };
+    return {who, what};
   });
 
-  const badContributor = contributors.find((val) => typeof val === "string");
+  const badContributor = contributors.find((val) => typeof val === 'string');
   if (badContributor) {
     return {
       error: `An entry has an incorrectly formatted contributor, "${badContributor}".`,
     };
   }
 
-  if (contributors.length === 1 && contributors[0].who === "none") {
+  if (contributors.length === 1 && contributors[0].who === 'none') {
     return null;
   }
 
@@ -584,7 +583,7 @@ export const documentModes = {
   // processDocument function. Obviously, each specified data file should only
   // contain one YAML document (an error will be thrown otherwise). Calls save
   // with an array of processed documents (wiki objects).
-  onePerFile: Symbol("Document mode: onePerFile"),
+  onePerFile: Symbol('Document mode: onePerFile'),
 
   // headerAndEntries: One or more documents per file; the first document is
   // treated as a "header" and represents data which pertains to all following
@@ -599,12 +598,12 @@ export const documentModes = {
   // aggregate. However, if the processHeaderDocument function fails, all
   // following documents in the same file will be ignored as well (i.e. an
   // entire file will be excempt from the save() function's input).
-  headerAndEntries: Symbol("Document mode: headerAndEntries"),
+  headerAndEntries: Symbol('Document mode: headerAndEntries'),
 
   // allInOne: One or more documents, all contained in one file. Expects file
   // string (or function) and processDocument function. Calls save with an
   // array of processed documents (wiki objects).
-  allInOne: Symbol("Document mode: allInOne"),
+  allInOne: Symbol('Document mode: allInOne'),
 
   // oneDocumentTotal: Just a single document, represented in one file.
   // Expects file string (or function) and processDocument function. Calls
@@ -614,7 +613,7 @@ export const documentModes = {
   // function won't be called at all, generally resulting in an altogether
   // missing property from the global wikiData object. This should be caught
   // and handled externally.
-  oneDocumentTotal: Symbol("Document mode: oneDocumentTotal"),
+  oneDocumentTotal: Symbol('Document mode: oneDocumentTotal'),
 };
 
 // dataSteps: Top-level array of "steps" for loading YAML document files.
@@ -662,7 +661,7 @@ export const dataSteps = [
         return;
       }
 
-      return { wikiInfo };
+      return {wikiInfo};
     },
   },
 
@@ -671,7 +670,7 @@ export const dataSteps = [
     files: async (dataPath) =>
       (
         await findFiles(path.join(dataPath, DATA_ALBUM_DIRECTORY), {
-          filter: (f) => path.extname(f) === ".yaml",
+          filter: (f) => path.extname(f) === '.yaml',
           joinParentDirectory: false,
         })
       ).map((file) => path.join(DATA_ALBUM_DIRECTORY, file)),
@@ -679,7 +678,7 @@ export const dataSteps = [
     documentMode: documentModes.headerAndEntries,
     processHeaderDocument: processAlbumDocument,
     processEntryDocument(document) {
-      return "Group" in document
+      return 'Group' in document
         ? processTrackGroupDocument(document)
         : processTrackDocument(document);
     },
@@ -688,7 +687,7 @@ export const dataSteps = [
       const albumData = [];
       const trackData = [];
 
-      for (const { header: album, entries } of results) {
+      for (const {header: album, entries} of results) {
         // We can't mutate an array once it's set as a property
         // value, so prepare the tracks and track groups that will
         // show up in a track list all the way before actually
@@ -699,7 +698,7 @@ export const dataSteps = [
 
         const albumRef = Thing.getReference(album);
 
-        function closeCurrentTrackGroup() {
+        const closeCurrentTrackGroup = () => {
           if (currentTracksByRef) {
             let trackGroup;
 
@@ -715,7 +714,7 @@ export const dataSteps = [
             trackGroup.tracksByRef = currentTracksByRef;
             trackGroups.push(trackGroup);
           }
-        }
+        };
 
         for (const entry of entries) {
           if (entry instanceof TrackGroup) {
@@ -743,7 +742,7 @@ export const dataSteps = [
         albumData.push(album);
       }
 
-      return { albumData, trackData };
+      return {albumData, trackData};
     },
   },
 
@@ -771,7 +770,7 @@ export const dataSteps = [
         );
       });
 
-      return { artistData, artistAliasData };
+      return {artistData, artistAliasData};
     },
   },
 
@@ -782,7 +781,7 @@ export const dataSteps = [
 
     documentMode: documentModes.allInOne,
     processDocument(document) {
-      return "Act" in document
+      return 'Act' in document
         ? processFlashActDocument(document)
         : processFlashDocument(document);
     },
@@ -798,7 +797,7 @@ export const dataSteps = [
       for (const thing of results) {
         if (thing instanceof FlashAct) {
           if (flashAct) {
-            Object.assign(flashAct, { flashesByRef });
+            Object.assign(flashAct, {flashesByRef});
           }
 
           flashAct = thing;
@@ -809,13 +808,13 @@ export const dataSteps = [
       }
 
       if (flashAct) {
-        Object.assign(flashAct, { flashesByRef });
+        Object.assign(flashAct, {flashesByRef});
       }
 
       const flashData = results.filter((x) => x instanceof Flash);
       const flashActData = results.filter((x) => x instanceof FlashAct);
 
-      return { flashData, flashActData };
+      return {flashData, flashActData};
     },
   },
 
@@ -825,7 +824,7 @@ export const dataSteps = [
 
     documentMode: documentModes.allInOne,
     processDocument(document) {
-      return "Category" in document
+      return 'Category' in document
         ? processGroupCategoryDocument(document)
         : processGroupDocument(document);
     },
@@ -841,7 +840,7 @@ export const dataSteps = [
       for (const thing of results) {
         if (thing instanceof GroupCategory) {
           if (groupCategory) {
-            Object.assign(groupCategory, { groupsByRef });
+            Object.assign(groupCategory, {groupsByRef});
           }
 
           groupCategory = thing;
@@ -852,7 +851,7 @@ export const dataSteps = [
       }
 
       if (groupCategory) {
-        Object.assign(groupCategory, { groupsByRef });
+        Object.assign(groupCategory, {groupsByRef});
       }
 
       const groupData = results.filter((x) => x instanceof Group);
@@ -860,7 +859,7 @@ export const dataSteps = [
         (x) => x instanceof GroupCategory
       );
 
-      return { groupData, groupCategoryData };
+      return {groupData, groupCategoryData};
     },
   },
 
@@ -877,9 +876,9 @@ export const dataSteps = [
         return;
       }
 
-      const { header: homepageLayout, entries: rows } = results[0];
-      Object.assign(homepageLayout, { rows });
-      return { homepageLayout };
+      const {header: homepageLayout, entries: rows} = results[0];
+      Object.assign(homepageLayout, {rows});
+      return {homepageLayout};
     },
   },
 
@@ -895,7 +894,7 @@ export const dataSteps = [
       sortChronologically(newsData);
       newsData.reverse();
 
-      return { newsData };
+      return {newsData};
     },
   },
 
@@ -909,7 +908,7 @@ export const dataSteps = [
     save(artTagData) {
       sortAlphabetically(artTagData);
 
-      return { artTagData };
+      return {artTagData};
     },
   },
 
@@ -921,12 +920,12 @@ export const dataSteps = [
     processDocument: processStaticPageDocument,
 
     save(staticPageData) {
-      return { staticPageData };
+      return {staticPageData};
     },
   },
 ];
 
-export async function loadAndProcessDataDocuments({ dataPath }) {
+export async function loadAndProcessDataDocuments({dataPath}) {
   const processDataAggregate = openAggregate({
     message: `Errors processing data files`,
   });
@@ -938,7 +937,7 @@ export async function loadAndProcessDataDocuments({ dataPath }) {
         return fn(x, index, array);
       } catch (error) {
         error.message +=
-          (error.message.includes("\n") ? "\n" : " ") +
+          (error.message.includes('\n') ? '\n' : ' ') +
           `(file: ${color.bright(
             color.blue(path.relative(dataPath, x.file))
           )})`;
@@ -949,9 +948,9 @@ export async function loadAndProcessDataDocuments({ dataPath }) {
 
   for (const dataStep of dataSteps) {
     await processDataAggregate.nestAsync(
-      { message: `Errors during data step: ${dataStep.title}` },
-      async ({ call, callAsync, map, mapAsync, nest }) => {
-        const { documentMode } = dataStep;
+      {message: `Errors during data step: ${dataStep.title}`},
+      async ({call, callAsync, map, mapAsync, nest}) => {
+        const {documentMode} = dataStep;
 
         if (!Object.values(documentModes).includes(documentMode)) {
           throw new Error(`Invalid documentMode: ${documentMode.toString()}`);
@@ -969,12 +968,12 @@ export async function loadAndProcessDataDocuments({ dataPath }) {
 
           const file = path.join(
             dataPath,
-            typeof dataStep.file === "function"
+            typeof dataStep.file === 'function'
               ? await callAsync(dataStep.file, dataPath)
               : dataStep.file
           );
 
-          const readResult = await callAsync(readFile, file, "utf-8");
+          const readResult = await callAsync(readFile, file, 'utf-8');
 
           if (!readResult) {
             return;
@@ -992,14 +991,14 @@ export async function loadAndProcessDataDocuments({ dataPath }) {
           let processResults;
 
           if (documentMode === documentModes.oneDocumentTotal) {
-            nest({ message: `Errors processing document` }, ({ call }) => {
+            nest({message: `Errors processing document`}, ({call}) => {
               processResults = call(dataStep.processDocument, yamlResult);
             });
           } else {
-            const { result, aggregate } = mapAggregate(
+            const {result, aggregate} = mapAggregate(
               yamlResult,
               decorateErrorWithIndex(dataStep.processDocument),
-              { message: `Errors processing documents` }
+              {message: `Errors processing documents`}
             );
             processResults = result;
             call(aggregate.close);
@@ -1023,7 +1022,7 @@ export async function loadAndProcessDataDocuments({ dataPath }) {
         }
 
         const files = (
-          typeof dataStep.files === "function"
+          typeof dataStep.files === 'function'
             ? await callAsync(dataStep.files, dataPath)
             : dataStep.files
         ).map((file) => path.join(dataPath, file));
@@ -1031,35 +1030,35 @@ export async function loadAndProcessDataDocuments({ dataPath }) {
         const readResults = await mapAsync(
           files,
           (file) =>
-            readFile(file, "utf-8").then((contents) => ({ file, contents })),
-          { message: `Errors reading data files` }
+            readFile(file, 'utf-8').then((contents) => ({file, contents})),
+          {message: `Errors reading data files`}
         );
 
         const yamlResults = map(
           readResults,
-          decorateErrorWithFile(({ file, contents }) => ({
+          decorateErrorWithFile(({file, contents}) => ({
             file,
             documents: yaml.loadAll(contents),
           })),
-          { message: `Errors parsing data files as valid YAML` }
+          {message: `Errors parsing data files as valid YAML`}
         );
 
         let processResults;
 
         if (documentMode === documentModes.headerAndEntries) {
           nest(
-            { message: `Errors processing data files as valid documents` },
-            ({ call, map }) => {
+            {message: `Errors processing data files as valid documents`},
+            ({call, map}) => {
               processResults = [];
 
-              yamlResults.forEach(({ file, documents }) => {
+              yamlResults.forEach(({file, documents}) => {
                 const [headerDocument, ...entryDocuments] = documents;
 
                 const header = call(
-                  decorateErrorWithFile(({ document }) =>
+                  decorateErrorWithFile(({document}) =>
                     dataStep.processHeaderDocument(document)
                   ),
-                  { file, document: headerDocument }
+                  {file, document: headerDocument}
                 );
 
                 // Don't continue processing files whose header
@@ -1070,13 +1069,13 @@ export async function loadAndProcessDataDocuments({ dataPath }) {
                 }
 
                 const entries = map(
-                  entryDocuments.map((document) => ({ file, document })),
+                  entryDocuments.map((document) => ({file, document})),
                   decorateErrorWithFile(
-                    decorateErrorWithIndex(({ document }) =>
+                    decorateErrorWithIndex(({document}) =>
                       dataStep.processEntryDocument(document)
                     )
                   ),
-                  { message: `Errors processing entry documents` }
+                  {message: `Errors processing entry documents`}
                 );
 
                 // Entries may be incomplete (i.e. any errored
@@ -1084,7 +1083,7 @@ export async function loadAndProcessDataDocuments({ dataPath }) {
                 // represented here) - this is intentional! By
                 // principle, partial output is preferred over
                 // erroring an entire file.
-                processResults.push({ header, entries });
+                processResults.push({header, entries});
               });
             }
           );
@@ -1092,11 +1091,11 @@ export async function loadAndProcessDataDocuments({ dataPath }) {
 
         if (documentMode === documentModes.onePerFile) {
           nest(
-            { message: `Errors processing data files as valid documents` },
-            ({ call, map }) => {
+            {message: `Errors processing data files as valid documents`},
+            ({call}) => {
               processResults = [];
 
-              yamlResults.forEach(({ file, documents }) => {
+              yamlResults.forEach(({file, documents}) => {
                 if (documents.length > 1) {
                   call(
                     decorateErrorWithFile(() => {
@@ -1109,10 +1108,10 @@ export async function loadAndProcessDataDocuments({ dataPath }) {
                 }
 
                 const result = call(
-                  decorateErrorWithFile(({ document }) =>
+                  decorateErrorWithFile(({document}) =>
                     dataStep.processDocument(document)
                   ),
-                  { file, document: documents[0] }
+                  {file, document: documents[0]}
                 );
 
                 if (!result) {
@@ -1155,40 +1154,40 @@ export function linkWikiDataArrays(wikiData) {
 
   const WD = wikiData;
 
-  assignWikiData([WD.wikiInfo], "groupData");
+  assignWikiData([WD.wikiInfo], 'groupData');
 
   assignWikiData(
     WD.albumData,
-    "artistData",
-    "artTagData",
-    "groupData",
-    "trackData"
+    'artistData',
+    'artTagData',
+    'groupData',
+    'trackData'
   );
   WD.albumData.forEach((album) =>
-    assignWikiData(album.trackGroups, "trackData")
+    assignWikiData(album.trackGroups, 'trackData')
   );
 
   assignWikiData(
     WD.trackData,
-    "albumData",
-    "artistData",
-    "artTagData",
-    "flashData",
-    "trackData"
+    'albumData',
+    'artistData',
+    'artTagData',
+    'flashData',
+    'trackData'
   );
   assignWikiData(
     WD.artistData,
-    "albumData",
-    "artistData",
-    "flashData",
-    "trackData"
+    'albumData',
+    'artistData',
+    'flashData',
+    'trackData'
   );
-  assignWikiData(WD.groupData, "albumData", "groupCategoryData");
-  assignWikiData(WD.groupCategoryData, "groupData");
-  assignWikiData(WD.flashData, "artistData", "flashActData", "trackData");
-  assignWikiData(WD.flashActData, "flashData");
-  assignWikiData(WD.artTagData, "albumData", "trackData");
-  assignWikiData(WD.homepageLayout.rows, "albumData", "groupData");
+  assignWikiData(WD.groupData, 'albumData', 'groupCategoryData');
+  assignWikiData(WD.groupCategoryData, 'groupData');
+  assignWikiData(WD.flashData, 'artistData', 'flashActData', 'trackData');
+  assignWikiData(WD.flashActData, 'flashData');
+  assignWikiData(WD.artTagData, 'albumData', 'trackData');
+  assignWikiData(WD.homepageLayout.rows, 'albumData', 'groupData');
 }
 
 export function sortWikiDataArrays(wikiData) {
@@ -1213,28 +1212,28 @@ export function sortWikiDataArrays(wikiData) {
 // build, for example).
 export function filterDuplicateDirectories(wikiData) {
   const deduplicateSpec = [
-    "albumData",
-    "artTagData",
-    "flashData",
-    "groupData",
-    "newsData",
-    "trackData",
+    'albumData',
+    'artTagData',
+    'flashData',
+    'groupData',
+    'newsData',
+    'trackData',
   ];
 
-  const aggregate = openAggregate({ message: `Duplicate directories found` });
+  const aggregate = openAggregate({message: `Duplicate directories found`});
   for (const thingDataProp of deduplicateSpec) {
     const thingData = wikiData[thingDataProp];
     aggregate.nest(
       {
         message: `Duplicate directories found in ${color.green(
-          "wikiData." + thingDataProp
+          'wikiData.' + thingDataProp
         )}`,
       },
-      ({ call }) => {
+      ({call}) => {
         const directoryPlaces = Object.create(null);
         const duplicateDirectories = [];
         for (const thing of thingData) {
-          const { directory } = thing;
+          const {directory} = thing;
           if (directory in directoryPlaces) {
             directoryPlaces[directory].push(thing);
             duplicateDirectories.push(directory);
@@ -1253,7 +1252,7 @@ export function filterDuplicateDirectories(wikiData) {
           call(() => {
             throw new Error(
               `Duplicate directory ${color.green(directory)}:\n` +
-                places.map((thing) => ` - ` + inspect(thing)).join("\n")
+                places.map((thing) => ` - ` + inspect(thing)).join('\n')
             );
           });
         }
@@ -1292,64 +1291,64 @@ export function filterDuplicateDirectories(wikiData) {
 export function filterReferenceErrors(wikiData) {
   const referenceSpec = [
     [
-      "wikiInfo",
+      'wikiInfo',
       {
-        divideTrackListsByGroupsByRef: "group",
+        divideTrackListsByGroupsByRef: 'group',
       },
     ],
 
     [
-      "albumData",
+      'albumData',
       {
-        artistContribsByRef: "_contrib",
-        coverArtistContribsByRef: "_contrib",
-        trackCoverArtistContribsByRef: "_contrib",
-        wallpaperArtistContribsByRef: "_contrib",
-        bannerArtistContribsByRef: "_contrib",
-        groupsByRef: "group",
-        artTagsByRef: "artTag",
+        artistContribsByRef: '_contrib',
+        coverArtistContribsByRef: '_contrib',
+        trackCoverArtistContribsByRef: '_contrib',
+        wallpaperArtistContribsByRef: '_contrib',
+        bannerArtistContribsByRef: '_contrib',
+        groupsByRef: 'group',
+        artTagsByRef: 'artTag',
       },
     ],
 
     [
-      "trackData",
+      'trackData',
       {
-        artistContribsByRef: "_contrib",
-        contributorContribsByRef: "_contrib",
-        coverArtistContribsByRef: "_contrib",
-        referencedTracksByRef: "track",
-        artTagsByRef: "artTag",
-        originalReleaseTrackByRef: "track",
+        artistContribsByRef: '_contrib',
+        contributorContribsByRef: '_contrib',
+        coverArtistContribsByRef: '_contrib',
+        referencedTracksByRef: 'track',
+        artTagsByRef: 'artTag',
+        originalReleaseTrackByRef: 'track',
       },
     ],
 
     [
-      "groupCategoryData",
+      'groupCategoryData',
       {
-        groupsByRef: "group",
+        groupsByRef: 'group',
       },
     ],
 
     [
-      "homepageLayout.rows",
+      'homepageLayout.rows',
       {
-        sourceGroupsByRef: "group",
-        sourceAlbumsByRef: "album",
+        sourceGroupsByRef: 'group',
+        sourceAlbumsByRef: 'album',
       },
     ],
 
     [
-      "flashData",
+      'flashData',
       {
-        contributorContribsByRef: "_contrib",
-        featuredTracksByRef: "track",
+        contributorContribsByRef: '_contrib',
+        featuredTracksByRef: 'track',
       },
     ],
 
     [
-      "flashActData",
+      'flashActData',
       {
-        flashesByRef: "flash",
+        flashesByRef: 'flash',
       },
     ],
   ];
@@ -1364,35 +1363,35 @@ export function filterReferenceErrors(wikiData) {
   const aggregate = openAggregate({
     message: `Errors validating between-thing references in data`,
   });
-  const boundFind = bindFind(wikiData, { mode: "error" });
+  const boundFind = bindFind(wikiData, {mode: 'error'});
   for (const [thingDataProp, propSpec] of referenceSpec) {
     const thingData = getNestedProp(wikiData, thingDataProp);
     aggregate.nest(
       {
         message: `Reference errors in ${color.green(
-          "wikiData." + thingDataProp
+          'wikiData.' + thingDataProp
         )}`,
       },
-      ({ nest }) => {
+      ({nest}) => {
         const things = Array.isArray(thingData) ? thingData : [thingData];
         for (const thing of things) {
           nest(
-            { message: `Reference errors in ${inspect(thing)}` },
-            ({ filter }) => {
+            {message: `Reference errors in ${inspect(thing)}`},
+            ({filter}) => {
               for (const [property, findFnKey] of Object.entries(propSpec)) {
                 if (!thing[property]) continue;
-                if (findFnKey === "_contrib") {
+                if (findFnKey === '_contrib') {
                   thing[property] = filter(
                     thing[property],
-                    decorateErrorWithIndex(({ who }) => {
+                    decorateErrorWithIndex(({who}) => {
                       const alias = find.artist(who, wikiData.artistAliasData, {
-                        mode: "quiet",
+                        mode: 'quiet',
                       });
                       if (alias) {
                         const original = find.artist(
                           alias.aliasedArtistRef,
                           wikiData.artistData,
-                          { mode: "quiet" }
+                          {mode: 'quiet'}
                         );
                         throw new Error(
                           `Reference ${color.red(
@@ -1407,7 +1406,7 @@ export function filterReferenceErrors(wikiData) {
                     {
                       message: `Reference errors in contributions ${color.green(
                         property
-                      )} (${color.green("find.artist")})`,
+                      )} (${color.green('find.artist')})`,
                     }
                   );
                   continue;
@@ -1421,7 +1420,7 @@ export function filterReferenceErrors(wikiData) {
                     {
                       message: `Reference errors in property ${color.green(
                         property
-                      )} (${color.green("find." + findFnKey)})`,
+                      )} (${color.green('find.' + findFnKey)})`,
                     }
                   );
                 } else {
@@ -1429,9 +1428,9 @@ export function filterReferenceErrors(wikiData) {
                     {
                       message: `Reference error in property ${color.green(
                         property
-                      )} (${color.green("find." + findFnKey)})`,
+                      )} (${color.green('find.' + findFnKey)})`,
                     },
-                    ({ call }) => {
+                    ({call}) => {
                       try {
                         call(findFn, value);
                       } catch (error) {
@@ -1460,14 +1459,14 @@ export function filterReferenceErrors(wikiData) {
 // main wiki build process.
 export async function quickLoadAllFromYAML(
   dataPath,
-  { showAggregate: customShowAggregate = showAggregate } = {}
+  {showAggregate: customShowAggregate = showAggregate} = {}
 ) {
   const showAggregate = customShowAggregate;
 
   let wikiData;
 
   {
-    const { aggregate, result } = await loadAndProcessDataDocuments({
+    const {aggregate, result} = await loadAndProcessDataDocuments({
       dataPath,
     });