« get me outta code hell

finish up cosmetic style changes - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2022-11-26 23:44:08 -0400
committer(quasar) nebula <qznebula@protonmail.com>2022-11-26 23:44:08 -0400
commit003f594f6348b55109dd66416e75fcc2a88faade (patch)
treeb4cb05ed4e145e604356786a1d98926040fe5ff0
parent768927503b5948b846b9a6cddf4b788ca9792e8c (diff)
finish up cosmetic style changes
-rw-r--r--src/data/cacheable-object.js93
-rw-r--r--src/data/patches.js7
-rw-r--r--src/data/serialize.js15
-rw-r--r--src/data/things.js73
-rw-r--r--src/data/validators.js94
-rw-r--r--src/data/yaml.js620
-rw-r--r--src/file-size-preloader.js2
-rw-r--r--src/gen-thumbs.js17
-rw-r--r--src/listing-spec.js2
-rw-r--r--src/misc-templates.js8
-rw-r--r--src/page/album-commentary.js2
-rw-r--r--src/page/album.js3
-rw-r--r--src/page/artist-alias.js2
-rw-r--r--src/page/artist.js60
-rw-r--r--src/page/flash.js2
-rw-r--r--src/page/group.js2
-rw-r--r--src/page/index.js2
-rw-r--r--src/page/listing.js2
-rw-r--r--src/page/static.js2
-rw-r--r--src/page/tag.js13
-rw-r--r--src/page/track.js4
-rw-r--r--src/repl.js2
-rw-r--r--src/static/client.js5
-rw-r--r--src/static/lazy-loading.js2
-rwxr-xr-xsrc/upd8.js800
-rw-r--r--src/url-spec.js5
-rw-r--r--src/util/cli.js2
-rw-r--r--src/util/colors.js27
-rw-r--r--src/util/find.js10
-rw-r--r--src/util/html.js18
-rw-r--r--src/util/io.js2
-rw-r--r--src/util/link.js33
-rw-r--r--src/util/magic-constants.js2
-rw-r--r--src/util/node-utils.js2
-rw-r--r--src/util/replacer.js21
-rw-r--r--src/util/serialize.js11
-rw-r--r--src/util/sugar.js64
-rw-r--r--src/util/urls.js19
-rw-r--r--src/util/wiki-data.js54
-rw-r--r--test/cacheable-object.js416
-rw-r--r--test/data-validators.js376
-rw-r--r--test/things.js82
42 files changed, 1343 insertions, 1635 deletions
diff --git a/src/data/cacheable-object.js b/src/data/cacheable-object.js
index 688d8a0f..04e029f0 100644
--- a/src/data/cacheable-object.js
+++ b/src/data/cacheable-object.js
@@ -1,7 +1,3 @@
-/**
- * @format
- */
-
 // Generally extendable class for caching properties and handling dependencies,
 // with a few key properties:
 //
@@ -112,9 +108,7 @@ export default class CacheableObject {
         get: (obj, key) => {
           if (!Object.hasOwn(obj, key)) {
             if (key !== 'constructor') {
-              CacheableObject._invalidAccesses.add(
-                `(${obj.constructor.name}).${key}`
-              );
+              CacheableObject._invalidAccesses.add(`(${obj.constructor.name}).${key}`);
             }
           }
           return obj[key];
@@ -124,9 +118,7 @@ export default class CacheableObject {
   }
 
   #initializeUpdatingPropertyValues() {
-    for (const [property, descriptor] of Object.entries(
-      this.constructor.propertyDescriptors
-    )) {
+    for (const [property, descriptor] of Object.entries(this.constructor.propertyDescriptors)) {
       const {flags, update} = descriptor;
 
       if (!flags.update) {
@@ -143,14 +135,10 @@ export default class CacheableObject {
 
   #defineProperties() {
     if (!this.constructor.propertyDescriptors) {
-      throw new Error(
-        `Expected constructor ${this.constructor.name} to define propertyDescriptors`
-      );
+      throw new Error(`Expected constructor ${this.constructor.name} to define propertyDescriptors`);
     }
 
-    for (const [property, descriptor] of Object.entries(
-      this.constructor.propertyDescriptors
-    )) {
+    for (const [property, descriptor] of Object.entries(this.constructor.propertyDescriptors)) {
       const {flags} = descriptor;
 
       const definition = {
@@ -159,13 +147,11 @@ export default class CacheableObject {
       };
 
       if (flags.update) {
-        definition.set =
-          this.#getUpdateObjectDefinitionSetterFunction(property);
+        definition.set = this.#getUpdateObjectDefinitionSetterFunction(property);
       }
 
       if (flags.expose) {
-        definition.get =
-          this.#getExposeObjectDefinitionGetterFunction(property);
+        definition.get = this.#getExposeObjectDefinitionGetterFunction(property);
       }
 
       Object.defineProperty(this, property, definition);
@@ -198,9 +184,11 @@ export default class CacheableObject {
             throw new TypeError(`Validation failed for value ${newValue}`);
           }
         } catch (error) {
-          error.message = `Property ${color.green(property)} (${inspect(
-            this[property]
-          )} -> ${inspect(newValue)}): ${error.message}`;
+          error.message = [
+            `Property ${color.green(property)}`,
+            `(${inspect(this[property])} -> ${inspect(newValue)}):`,
+            error.message
+          ].join(' ');
           throw error;
         }
       }
@@ -215,8 +203,12 @@ export default class CacheableObject {
   }
 
   #invalidateCachesDependentUpon(property) {
-    for (const invalidate of this.#propertyUpdateCacheInvalidators[property] ||
-      []) {
+    const invalidators = this.#propertyUpdateCacheInvalidators[property];
+    if (!invalidators) {
+      return;
+    }
+
+    for (const invalidate of invalidators) {
       invalidate();
     }
   }
@@ -236,9 +228,7 @@ export default class CacheableObject {
         }
       };
     } else if (!flags.update && !compute) {
-      throw new Error(
-        `Exposed property ${property} does not update and is missing compute function`
-      );
+      throw new Error(`Exposed property ${property} does not update and is missing compute function`);
     } else {
       return () => this.#propertyUpdateValues[property];
     }
@@ -253,30 +243,31 @@ export default class CacheableObject {
     if (flags.update && !transform) {
       return null;
     } else if (flags.update && compute) {
-      throw new Error(
-        `Updating property ${property} has compute function, should be formatted as transform`
-      );
+      throw new Error(`Updating property ${property} has compute function, should be formatted as transform`);
     } else if (!flags.update && !compute) {
-      throw new Error(
-        `Exposed property ${property} does not update and is missing compute function`
-      );
+      throw new Error(`Exposed property ${property} does not update and is missing compute function`);
     }
 
-    const dependencyKeys = expose.dependencies || [];
-    const dependencyGetters = dependencyKeys.map((key) => () => [
-      key,
-      this.#propertyUpdateValues[key],
-    ]);
-    const getAllDependencies = () =>
-      Object.fromEntries(
-        dependencyGetters
-          .map((f) => f())
-          .concat([[this.constructor.instance, this]])
-      );
+    let getAllDependencies;
+
+    const dependencyKeys = expose.dependencies;
+    if (dependencyKeys?.length > 0) {
+      const reflectionEntry = [this.constructor.instance, this];
+      const dependencyGetters = dependencyKeys
+        .map(key => () => [key, this.#propertyUpdateValues[key]]);
+
+      getAllDependencies = () =>
+        Object.fromEntries(dependencyGetters
+          .map(f => f())
+          .concat([reflectionEntry]));
+    } else {
+      const allDependencies = {[this.constructor.instance]: this};
+      Object.freeze(allDependencies);
+      getAllDependencies = () => allDependencies;
+    }
 
     if (flags.update) {
-      return () =>
-        transform(this.#propertyUpdateValues[property], getAllDependencies());
+      return () => transform(this.#propertyUpdateValues[property], getAllDependencies());
     } else {
       return () => compute(getAllDependencies());
     }
@@ -321,14 +312,14 @@ export default class CacheableObject {
       return;
     }
 
-    if (!obj.constructor.propertyDescriptors) {
+    const {propertyDescriptors} = obj.constructor;
+
+    if (!propertyDescriptors) {
       console.warn('Missing property descriptors:', obj);
       return;
     }
 
-    for (const [property, descriptor] of Object.entries(
-      obj.constructor.propertyDescriptors
-    )) {
+    for (const [property, descriptor] of Object.entries(propertyDescriptors)) {
       const {flags} = descriptor;
 
       if (!flags.expose) {
diff --git a/src/data/patches.js b/src/data/patches.js
index dc757fa9..feeaf39b 100644
--- a/src/data/patches.js
+++ b/src/data/patches.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // --> Patch
 
 export class Patch {
@@ -137,6 +135,7 @@ export class PatchManager extends Patch {
     this.#externalInputPatch = new PatchManagerExternalInputPatch({
       manager: this,
     });
+
     this.#externalOutputPatch = new PatchManagerExternalOutputPatch({
       manager: this,
     });
@@ -184,9 +183,7 @@ export class PatchManager extends Patch {
 
   addManagedInput(patchWithInput, inputName, patchWithOutput, outputName) {
     if (patchWithInput.manager !== this || patchWithOutput.manager !== this) {
-      throw new Error(
-        `Input and output patches must belong to same manager (this)`
-      );
+      throw new Error(`Input and output patches must belong to same manager (this)`);
     }
 
     const input = patchWithInput.inputs[inputName];
diff --git a/src/data/serialize.js b/src/data/serialize.js
index a4206fd0..52aacb07 100644
--- a/src/data/serialize.js
+++ b/src/data/serialize.js
@@ -1,6 +1,4 @@
-/** @format */
-
-// serialize-util.js: simple interface and utility functions for converting
+// serialize.js: simple interface and utility functions for converting
 // Things into a directly serializeable format
 
 // Utility functions
@@ -27,17 +25,14 @@ export const serializeDescriptors = Symbol();
 
 export function serializeThing(thing) {
   const descriptors = thing.constructor[serializeDescriptors];
+
   if (!descriptors) {
-    throw new Error(
-      `Constructor ${thing.constructor.name} does not provide serialize descriptors`
-    );
+    throw new Error(`Constructor ${thing.constructor.name} does not provide serialize descriptors`);
   }
 
   return Object.fromEntries(
-    Object.entries(descriptors).map(([property, transform]) => [
-      property,
-      transform(thing[property]),
-    ])
+    Object.entries(descriptors)
+      .map(([property, transform]) => [property, transform(thing[property])])
   );
 }
 
diff --git a/src/data/things.js b/src/data/things.js
index 4aa684d4..33880460 100644
--- a/src/data/things.js
+++ b/src/data/things.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // things.js: class definitions for various object types used across the wiki,
 // most of which correspond to an output page, such as Track, Album, Artist
 
@@ -236,9 +234,7 @@ Thing.common = {
   referenceList: (thingClass) => {
     const {[Thing.referenceType]: referenceType} = thingClass;
     if (!referenceType) {
-      throw new Error(
-        `The passed constructor ${thingClass.name} doesn't define Thing.referenceType!`
-      );
+      throw new Error(`The passed constructor ${thingClass.name} doesn't define Thing.referenceType!`);
     }
 
     return {
@@ -251,9 +247,7 @@ Thing.common = {
   singleReference: (thingClass) => {
     const {[Thing.referenceType]: referenceType} = thingClass;
     if (!referenceType) {
-      throw new Error(
-        `The passed constructor ${thingClass.name} doesn't define Thing.referenceType!`
-      );
+      throw new Error(`The passed constructor ${thingClass.name} doesn't define Thing.referenceType!`);
     }
 
     return {
@@ -441,15 +435,13 @@ Thing.common = {
 // constructor's [Thing.referenceType] as the prefix. This will throw an error
 // if the thing's directory isn't yet provided/computable.
 Thing.getReference = function (thing) {
-  if (!thing.constructor[Thing.referenceType])
-    throw TypeError(
-      `Passed Thing is ${thing.constructor.name}, which provides no [Thing.referenceType]`
-    );
+  if (!thing.constructor[Thing.referenceType]) {
+    throw TypeError(`Passed Thing is ${thing.constructor.name}, which provides no [Thing.referenceType]`);
+  }
 
-  if (!thing.directory)
-    throw TypeError(
-      `Passed ${thing.constructor.name} is missing its directory`
-    );
+  if (!thing.directory) {
+    throw TypeError(`Passed ${thing.constructor.name} is missing its directory`);
+  }
 
   return `${thing.constructor[Thing.referenceType]}:${thing.directory}`;
 };
@@ -735,10 +727,11 @@ Track.propertyDescriptors = {
 
     expose: {
       dependencies: ['albumData', 'coverArtistContribsByRef'],
-      transform: (
-        hasCoverArt,
-        {albumData, coverArtistContribsByRef, [Track.instance]: track}
-      ) =>
+      transform: (hasCoverArt, {
+        albumData,
+        coverArtistContribsByRef,
+        [Track.instance]: track,
+      }) =>
         Track.hasCoverArt(
           track,
           albumData,
@@ -755,15 +748,12 @@ Track.propertyDescriptors = {
 
     expose: {
       dependencies: ['albumData', 'coverArtistContribsByRef'],
-      transform: (
-        coverArtFileExtension,
-        {
-          albumData,
-          coverArtistContribsByRef,
-          hasCoverArt,
-          [Track.instance]: track,
-        }
-      ) =>
+      transform: (coverArtFileExtension, {
+        albumData,
+        coverArtistContribsByRef,
+        hasCoverArt,
+        [Track.instance]: track,
+      }) =>
         coverArtFileExtension ??
         (Track.hasCoverArt(
           track,
@@ -851,10 +841,11 @@ Track.propertyDescriptors = {
 
     expose: {
       dependencies: ['albumData', 'dateFirstReleased'],
-      transform: (
-        coverArtDate,
-        {albumData, dateFirstReleased, [Track.instance]: track}
-      ) =>
+      transform: (coverArtDate, {
+        albumData,
+        dateFirstReleased,
+        [Track.instance]: track,
+      }) =>
         coverArtDate ??
         dateFirstReleased ??
         Track.findAlbum(track, albumData)?.trackArtDate ??
@@ -1691,9 +1682,7 @@ Object.assign(Language.prototype, {
 
   formatString(key, args = {}) {
     if (this.strings && !this.strings_htmlEscaped) {
-      throw new Error(
-        `HTML-escaped strings unavailable - please ensure escapeHTML function is provided`
-      );
+      throw new Error(`HTML-escaped strings unavailable - please ensure escapeHTML function is provided`);
     }
 
     return this.formatStringHelper(this.strings_htmlEscaped, key, args);
@@ -1780,12 +1769,7 @@ Object.assign(Language.prototype, {
 
   formatIndex(value) {
     this.assertIntlAvailable('intl_pluralOrdinal');
-    return this.formatString(
-      'count.index.' + this.intl_pluralOrdinal.select(value),
-      {
-        index: value,
-      }
-    );
+    return this.formatString('count.index.' + this.intl_pluralOrdinal.select(value), {index: value});
   },
 
   formatNumber(value) {
@@ -1803,10 +1787,7 @@ Object.assign(Language.prototype, {
         ? this.formatString('count.words.thousand', {words: num})
         : this.formatString('count.words', {words: num});
 
-    return this.formatString(
-      'count.words.withUnit.' + this.getUnitForm(value),
-      {words}
-    );
+    return this.formatString('count.words.withUnit.' + this.getUnitForm(value), {words});
   },
 
   // Conjunction list: A, B, and C
diff --git a/src/data/validators.js b/src/data/validators.js
index 8d922399..5c357c83 100644
--- a/src/data/validators.js
+++ b/src/data/validators.js
@@ -1,5 +1,3 @@
-/** @format */
-
 import {withAggregate} from '../util/sugar.js';
 
 import {color, ENABLE_COLOR} from '../util/cli.js';
@@ -104,9 +102,7 @@ export function isInstance(value, constructor) {
   isObject(value);
 
   if (!(value instanceof constructor))
-    throw new TypeError(
-      `Expected ${constructor.name}, got ${value.constructor.name}`
-    );
+    throw new TypeError(`Expected ${constructor.name}, got ${value.constructor.name}`);
 
   return true;
 }
@@ -142,9 +138,7 @@ function validateArrayItemsHelper(itemValidator) {
         throw new Error(`Expected validator to return true`);
       }
     } catch (error) {
-      error.message = `(index: ${color.green(index)}, item: ${inspect(item)}) ${
-        error.message
-      }`;
+      error.message = `(index: ${color.green(index)}, item: ${inspect(item)}) ${error.message}`;
       throw error;
     }
   };
@@ -174,10 +168,8 @@ export function isColor(color) {
   isStringNonEmpty(color);
 
   if (color.startsWith('#')) {
-    if (![1 + 3, 1 + 4, 1 + 6, 1 + 8].includes(color.length))
-      throw new TypeError(
-        `Expected #rgb, #rgba, #rrggbb, or #rrggbbaa, got length ${color.length}`
-      );
+    if (![4, 5, 7, 9].includes(color.length))
+      throw new TypeError(`Expected #rgb, #rgba, #rrggbb, or #rrggbbaa, got length ${color.length}`);
 
     if (/[^0-9a-fA-F]/.test(color.slice(1)))
       throw new TypeError(`Expected hexadecimal digits`);
@@ -204,37 +196,26 @@ export function validateProperties(spec) {
     if (Array.isArray(object))
       throw new TypeError(`Expected an object, got array`);
 
-    withAggregate(
-      {message: `Errors validating object properties`},
-      ({call}) => {
-        for (const [specKey, specValidator] of specEntries) {
-          call(() => {
-            const value = object[specKey];
-            try {
-              specValidator(value);
-            } catch (error) {
-              error.message = `(key: ${color.green(specKey)}, value: ${inspect(
-                value
-              )}) ${error.message}`;
-              throw error;
-            }
-          });
-        }
+    withAggregate({message: `Errors validating object properties`}, ({call}) => {
+      for (const [specKey, specValidator] of specEntries) {
+        call(() => {
+          const value = object[specKey];
+          try {
+            specValidator(value);
+          } catch (error) {
+            error.message = `(key: ${color.green(specKey)}, value: ${inspect(value)}) ${error.message}`;
+            throw error;
+          }
+        });
+      }
 
-        const unknownKeys = Object.keys(object).filter(
-          (key) => !specKeys.includes(key)
-        );
-        if (unknownKeys.length > 0) {
-          call(() => {
-            throw new Error(
-              `Unknown keys present (${
-                unknownKeys.length
-              }): [${unknownKeys.join(', ')}]`
-            );
-          });
-        }
+      const unknownKeys = Object.keys(object).filter((key) => !specKeys.includes(key));
+      if (unknownKeys.length > 0) {
+        call(() => {
+          throw new Error(`Unknown keys present (${unknownKeys.length}): [${unknownKeys.join(', ')}]`);
+        });
       }
-    );
+    });
 
     return true;
   };
@@ -243,7 +224,9 @@ export function validateProperties(spec) {
 export const isContribution = validateProperties({
   who: isArtistRef,
   what: (value) =>
-    value === undefined || value === null || isStringNonEmpty(value),
+    value === undefined ||
+    value === null ||
+    isStringNonEmpty(value),
 });
 
 export const isContributionList = validateArrayItems(isContribution);
@@ -251,7 +234,9 @@ export const isContributionList = validateArrayItems(isContribution);
 export const isAdditionalFile = validateProperties({
   title: isString,
   description: (value) =>
-    value === undefined || value === null || isString(value),
+    value === undefined ||
+    value === null ||
+    isString(value),
   files: validateArrayItems(isString),
 });
 
@@ -274,9 +259,7 @@ export function isDirectory(directory) {
   isStringNonEmpty(directory);
 
   if (directory.match(/[^a-zA-Z0-9_-]/))
-    throw new TypeError(
-      `Expected only letters, numbers, dash, and underscore, got "${directory}"`
-    );
+    throw new TypeError(`Expected only letters, numbers, dash, and underscore, got "${directory}"`);
 
   return true;
 }
@@ -331,16 +314,14 @@ export function validateReference(type = 'track') {
 
     if (!match) throw new TypeError(`Malformed reference`);
 
-    const {
-      groups: {typePart, directoryPart},
-    } = match;
+    const {groups: {typePart, directoryPart}} = match;
 
-    if (typePart && typePart !== type)
-      throw new TypeError(
-        `Expected ref to begin with "${type}:", got "${typePart}:"`
-      );
+    if (typePart) {
+      if (typePart !== type)
+        throw new TypeError(`Expected ref to begin with "${type}:", got "${typePart}:"`);
 
-    if (typePart) isDirectory(directoryPart);
+      isDirectory(directoryPart);
+    }
 
     isName(ref);
 
@@ -381,9 +362,6 @@ export function oneOf(...checks) {
       error.check = check;
       errors.push(error);
     }
-    throw new AggregateError(
-      errors,
-      `Expected one of ${checks.length} possible checks, but none were true`
-    );
+    throw new AggregateError(errors, `Expected one of ${checks.length} possible checks, but none were true`);
   };
 }
diff --git a/src/data/yaml.js b/src/data/yaml.js
index e18b7334..2adce50b 100644
--- a/src/data/yaml.js
+++ b/src/data/yaml.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // yaml.js - specification for HSMusic YAML data file format and utilities for
 // loading and processing YAML files and documents
 
@@ -112,11 +110,8 @@ function makeProcessDocument(
   // Invert the property-field mapping, since it'll come in handy for
   // assigning update() source values later.
   const fieldPropertyMapping = Object.fromEntries(
-    Object.entries(propertyFieldMapping).map(([property, field]) => [
-      field,
-      property,
-    ])
-  );
+    Object.entries(propertyFieldMapping)
+      .map(([property, field]) => [field, property]));
 
   const decorateErrorWithName = (fn) => {
     const nameField = propertyFieldMapping['name'];
@@ -136,9 +131,8 @@ function makeProcessDocument(
   };
 
   return decorateErrorWithName((document) => {
-    const documentEntries = Object.entries(document).filter(
-      ([field]) => !ignoredFields.includes(field)
-    );
+    const documentEntries = Object.entries(document)
+      .filter(([field]) => !ignoredFields.includes(field));
 
     const unknownFields = documentEntries
       .map(([field]) => field)
@@ -167,22 +161,17 @@ function makeProcessDocument(
 
     const thing = Reflect.construct(thingClass, []);
 
-    withAggregate(
-      {message: `Errors applying ${color.green(thingClass.name)} properties`},
-      ({call}) => {
-        for (const [property, value] of Object.entries(sourceProperties)) {
-          call(() => (thing[property] = value));
-        }
+    withAggregate({message: `Errors applying ${color.green(thingClass.name)} properties`}, ({call}) => {
+      for (const [property, value] of Object.entries(sourceProperties)) {
+        call(() => (thing[property] = value));
       }
-    );
+    });
 
     return thing;
   });
 }
 
-makeProcessDocument.UnknownFieldsError = class UnknownFieldsError extends (
-  Error
-) {
+makeProcessDocument.UnknownFieldsError = class UnknownFieldsError extends Error {
   constructor(fields) {
     super(`Unknown fields present: ${fields.join(', ')}`);
     this.fields = fields;
@@ -191,13 +180,13 @@ makeProcessDocument.UnknownFieldsError = class UnknownFieldsError extends (
 
 export const processAlbumDocument = makeProcessDocument(Album, {
   fieldTransformations: {
-    Artists: parseContributors,
+    'Artists': parseContributors,
     'Cover Artists': parseContributors,
     'Default Track Cover Artists': parseContributors,
     'Wallpaper Artists': parseContributors,
     'Banner Artists': parseContributors,
 
-    Date: (value) => new Date(value),
+    '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),
@@ -263,13 +252,13 @@ export const processTrackGroupDocument = makeProcessDocument(TrackGroup, {
 
 export const processTrackDocument = makeProcessDocument(Track, {
   fieldTransformations: {
-    Duration: getDurationInSeconds,
+    'Duration': getDurationInSeconds,
 
     'Date First Released': (value) => new Date(value),
     'Cover Art Date': (value) => new Date(value),
 
-    Artists: parseContributors,
-    Contributors: parseContributors,
+    'Artists': parseContributors,
+    'Contributors': parseContributors,
     'Cover Artists': parseContributors,
 
     'Additional Files': parseAdditionalFiles,
@@ -323,9 +312,9 @@ export const processArtistDocument = makeProcessDocument(Artist, {
 
 export const processFlashDocument = makeProcessDocument(Flash, {
   fieldTransformations: {
-    Date: (value) => new Date(value),
+    'Date': (value) => new Date(value),
 
-    Contributors: parseContributors,
+    'Contributors': parseContributors,
   },
 
   propertyFieldMapping: {
@@ -354,7 +343,7 @@ export const processFlashActDocument = makeProcessDocument(FlashAct, {
 
 export const processNewsEntryDocument = makeProcessDocument(NewsEntry, {
   fieldTransformations: {
-    Date: (value) => new Date(value),
+    'Date': (value) => new Date(value),
   },
 
   propertyFieldMapping: {
@@ -421,16 +410,13 @@ export const processWikiInfoDocument = makeProcessDocument(WikiInfo, {
   },
 });
 
-export const processHomepageLayoutDocument = makeProcessDocument(
-  HomepageLayout,
-  {
-    propertyFieldMapping: {
-      sidebarContent: 'Sidebar Content',
-    },
+export const processHomepageLayoutDocument = makeProcessDocument(HomepageLayout, {
+  propertyFieldMapping: {
+    sidebarContent: 'Sidebar Content',
+  },
 
-    ignoredFields: ['Homepage'],
-  }
-);
+  ignoredFields: ['Homepage'],
+});
 
 export function makeProcessHomepageLayoutRowDocument(rowClass, spec) {
   return makeProcessDocument(rowClass, {
@@ -459,9 +445,8 @@ export const homepageLayoutRowTypeProcessMapping = {
 export function processHomepageLayoutRowDocument(document) {
   const type = document['Type'];
 
-  const match = Object.entries(homepageLayoutRowTypeProcessMapping).find(
-    ([key]) => key === type
-  );
+  const match = Object.entries(homepageLayoutRowTypeProcessMapping)
+    .find(([key]) => key === type);
 
   if (!match) {
     throw new TypeError(`No processDocument function for row type ${type}!`);
@@ -507,14 +492,9 @@ export function parseAdditionalFiles(array) {
 
 export function parseCommentary(text) {
   if (text) {
-    const lines = String(text).split('\n');
+    const lines = String(text.trim()).split('\n');
     if (!lines[0].replace(/<\/b>/g, '').includes(':</i>')) {
-      return {
-        error: `An entry is missing commentary citation: "${lines[0].slice(
-          0,
-          40
-        )}..."`,
-      };
+      throw new Error(`Missing commentary citation: "${lines[0].slice(0, 40)}..."`);
     }
     return text;
   } else {
@@ -547,9 +527,7 @@ export function parseContributors(contributors) {
 
   const badContributor = contributors.find((val) => typeof val === 'string');
   if (badContributor) {
-    return {
-      error: `An entry has an incorrectly formatted contributor, "${badContributor}".`,
-    };
+    throw new Error(`Incorrectly formatted contribution: "${badContributor}".`);
   }
 
   if (contributors.length === 1 && contributors[0].who === 'none') {
@@ -565,13 +543,17 @@ function parseDimensions(string) {
   }
 
   const parts = string.split(/[x,* ]+/g);
-  if (parts.length !== 2)
-    throw new Error(`Invalid dimensions: ${string} (expected width & height)`);
+
+  if (parts.length !== 2) {
+    throw new Error(`Invalid dimensions: ${string} (expected "width & height")`);
+  }
+
   const nums = parts.map((part) => Number(part.trim()));
-  if (nums.includes(NaN))
-    throw new Error(
-      `Invalid dimensions: ${string} (couldn't parse as numbers)`
-    );
+
+  if (nums.includes(NaN)) {
+    throw new Error(`Invalid dimensions: ${string} (couldn't parse as numbers)`);
+  }
+
   return nums;
 }
 
@@ -671,7 +653,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)),
@@ -759,16 +741,14 @@ export const dataSteps = [
 
       const artistAliasData = results.flatMap((artist) => {
         const origRef = Thing.getReference(artist);
-        return (
-          artist.aliasNames?.map((name) => {
-            const alias = new Artist();
-            alias.name = name;
-            alias.isAlias = true;
-            alias.aliasedArtistRef = origRef;
-            alias.artistData = artistData;
-            return alias;
-          }) ?? []
-        );
+        return artist.aliasNames?.map((name) => {
+          const alias = new Artist();
+          alias.name = name;
+          alias.isAlias = true;
+          alias.aliasedArtistRef = origRef;
+          alias.artistData = artistData;
+          return alias;
+        }) ?? [];
       });
 
       return {artistData, artistAliasData};
@@ -856,9 +836,7 @@ export const dataSteps = [
       }
 
       const groupData = results.filter((x) => x instanceof Group);
-      const groupCategoryData = results.filter(
-        (x) => x instanceof GroupCategory
-      );
+      const groupCategoryData = results.filter((x) => x instanceof GroupCategory);
 
       return {groupData, groupCategoryData};
     },
@@ -945,9 +923,7 @@ export async function loadAndProcessDataDocuments({dataPath}) {
       } catch (error) {
         error.message +=
           (error.message.includes('\n') ? '\n' : ' ') +
-          `(file: ${color.bright(
-            color.blue(path.relative(dataPath, x.file))
-          )})`;
+          `(file: ${color.bright(color.blue(path.relative(dataPath, x.file)))})`;
         throw error;
       }
     };
@@ -968,17 +944,14 @@ export async function loadAndProcessDataDocuments({dataPath}) {
           documentMode === documentModes.oneDocumentTotal
         ) {
           if (!dataStep.file) {
-            throw new Error(
-              `Expected 'file' property for ${documentMode.toString()}`
-            );
+            throw new Error(`Expected 'file' property for ${documentMode.toString()}`);
           }
 
           const file = path.join(
             dataPath,
             typeof dataStep.file === 'function'
               ? await callAsync(dataStep.file, dataPath)
-              : dataStep.file
-          );
+              : dataStep.file);
 
           const readResult = await callAsync(readFile, file, 'utf-8');
 
@@ -1005,8 +978,7 @@ export async function loadAndProcessDataDocuments({dataPath}) {
             const {result, aggregate} = mapAggregate(
               yamlResult,
               decorateErrorWithIndex(dataStep.processDocument),
-              {message: `Errors processing documents`}
-            );
+              {message: `Errors processing documents`});
             processResults = result;
             call(aggregate.close);
           }
@@ -1023,9 +995,7 @@ export async function loadAndProcessDataDocuments({dataPath}) {
         }
 
         if (!dataStep.files) {
-          throw new Error(
-            `Expected 'files' property for ${documentMode.toString()}`
-          );
+          throw new Error(`Expected 'files' property for ${documentMode.toString()}`);
         }
 
         let files = (
@@ -1042,8 +1012,7 @@ export async function loadAndProcessDataDocuments({dataPath}) {
 
         const readResults = await mapAsync(
           files,
-          (file) =>
-            readFile(file, 'utf-8').then((contents) => ({file, contents})),
+          (file) => readFile(file, 'utf-8').then((contents) => ({file, contents})),
           {message: `Errors reading data files`}
         );
 
@@ -1059,82 +1028,76 @@ export async function loadAndProcessDataDocuments({dataPath}) {
         let processResults;
 
         if (documentMode === documentModes.headerAndEntries) {
-          nest(
-            {message: `Errors processing data files as valid documents`},
-            ({call, map}) => {
-              processResults = [];
-
-              yamlResults.forEach(({file, documents}) => {
-                const [headerDocument, ...entryDocuments] = documents;
-
-                const header = call(
-                  decorateErrorWithFile(({document}) =>
-                    dataStep.processHeaderDocument(document)
-                  ),
-                  {file, document: headerDocument}
-                );
-
-                // Don't continue processing files whose header
-                // document is invalid - the entire file is excempt
-                // from data in this case.
-                if (!header) {
-                  return;
-                }
-
-                const entries = map(
-                  entryDocuments.map((document) => ({file, document})),
-                  decorateErrorWithFile(
-                    decorateErrorWithIndex(({document}) =>
-                      dataStep.processEntryDocument(document)
-                    )
-                  ),
-                  {message: `Errors processing entry documents`}
-                );
+          nest({message: `Errors processing data files as valid documents`}, ({call, map}) => {
+            processResults = [];
+
+            yamlResults.forEach(({file, documents}) => {
+              const [headerDocument, ...entryDocuments] = documents;
+
+              const header = call(
+                decorateErrorWithFile(({document}) =>
+                  dataStep.processHeaderDocument(document)
+                ),
+                {file, document: headerDocument}
+              );
+
+              // Don't continue processing files whose header
+              // document is invalid - the entire file is excempt
+              // from data in this case.
+              if (!header) {
+                return;
+              }
 
-                // Entries may be incomplete (i.e. any errored
-                // documents won't have a processed output
-                // represented here) - this is intentional! By
-                // principle, partial output is preferred over
-                // erroring an entire file.
-                processResults.push({header, entries});
-              });
-            }
-          );
+              const entries = map(
+                entryDocuments.map((document) => ({file, document})),
+                decorateErrorWithFile(
+                  decorateErrorWithIndex(({document}) =>
+                    dataStep.processEntryDocument(document)
+                  )
+                ),
+                {message: `Errors processing entry documents`}
+              );
+
+              // Entries may be incomplete (i.e. any errored
+              // documents won't have a processed output
+              // represented here) - this is intentional! By
+              // principle, partial output is preferred over
+              // erroring an entire file.
+              processResults.push({header, entries});
+            });
+          });
         }
 
         if (documentMode === documentModes.onePerFile) {
-          nest(
-            {message: `Errors processing data files as valid documents`},
-            ({call}) => {
-              processResults = [];
-
-              yamlResults.forEach(({file, documents}) => {
-                if (documents.length > 1) {
-                  call(
-                    decorateErrorWithFile(() => {
-                      throw new Error(
-                        `Only expected one document to be present per file`
-                      );
-                    })
-                  );
-                  return;
-                }
-
-                const result = call(
-                  decorateErrorWithFile(({document}) =>
-                    dataStep.processDocument(document)
-                  ),
-                  {file, document: documents[0]}
+          nest({message: `Errors processing data files as valid documents`}, ({call}) => {
+            processResults = [];
+
+            yamlResults.forEach(({file, documents}) => {
+              if (documents.length > 1) {
+                call(
+                  decorateErrorWithFile(() => {
+                    throw new Error(
+                      `Only expected one document to be present per file`
+                    );
+                  })
                 );
+                return;
+              }
 
-                if (!result) {
-                  return;
-                }
+              const result = call(
+                decorateErrorWithFile(({document}) =>
+                  dataStep.processDocument(document)
+                ),
+                {file, document: documents[0]}
+              );
 
-                processResults.push(result);
-              });
-            }
-          );
+              if (!result) {
+                return;
+              }
+
+              processResults.push(result);
+            });
+          });
         }
 
         const saveResult = call(dataStep.save, processResults);
@@ -1158,9 +1121,10 @@ export async function loadAndProcessDataDocuments({dataPath}) {
 export function linkWikiDataArrays(wikiData) {
   function assignWikiData(things, ...keys) {
     for (let i = 0; i < things.length; i++) {
+      const thing = things[i];
       for (let j = 0; j < keys.length; j++) {
         const key = keys[j];
-        things[i][key] = wikiData[key];
+        thing[key] = wikiData[key];
       }
     }
   }
@@ -1169,32 +1133,11 @@ export function linkWikiDataArrays(wikiData) {
 
   assignWikiData([WD.wikiInfo], 'groupData');
 
-  assignWikiData(
-    WD.albumData,
-    'artistData',
-    'artTagData',
-    'groupData',
-    'trackData'
-  );
-  WD.albumData.forEach((album) =>
-    assignWikiData(album.trackGroups, 'trackData')
-  );
-
-  assignWikiData(
-    WD.trackData,
-    'albumData',
-    'artistData',
-    'artTagData',
-    'flashData',
-    'trackData'
-  );
-  assignWikiData(
-    WD.artistData,
-    'albumData',
-    'artistData',
-    'flashData',
-    'trackData'
-  );
+  assignWikiData(WD.albumData, 'artistData', 'artTagData', 'groupData', 'trackData');
+  WD.albumData.forEach((album) => assignWikiData(album.trackGroups, 'trackData'));
+
+  assignWikiData(WD.trackData, 'albumData', 'artistData', 'artTagData', 'flashData', 'trackData');
+  assignWikiData(WD.artistData, 'albumData', 'artistData', 'flashData', 'trackData');
   assignWikiData(WD.groupData, 'albumData', 'groupCategoryData');
   assignWikiData(WD.groupCategoryData, 'groupData');
   assignWikiData(WD.flashData, 'artistData', 'flashActData', 'trackData');
@@ -1236,48 +1179,47 @@ export function filterDuplicateDirectories(wikiData) {
   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
-        )}`,
-      },
-      ({call}) => {
-        const directoryPlaces = Object.create(null);
-        const duplicateDirectories = [];
-        for (const thing of thingData) {
-          const {directory} = thing;
-          if (directory in directoryPlaces) {
-            directoryPlaces[directory].push(thing);
-            duplicateDirectories.push(directory);
-          } else {
-            directoryPlaces[directory] = [thing];
-          }
+    aggregate.nest({message: `Duplicate directories found in ${color.green('wikiData.' + thingDataProp)}`}, ({call}) => {
+      const directoryPlaces = Object.create(null);
+      const duplicateDirectories = [];
+
+      for (const thing of thingData) {
+        const {directory} = thing;
+        if (directory in directoryPlaces) {
+          directoryPlaces[directory].push(thing);
+          duplicateDirectories.push(directory);
+        } else {
+          directoryPlaces[directory] = [thing];
         }
-        if (empty(duplicateDirectories)) return;
-        duplicateDirectories.sort((a, b) => {
-          const aL = a.toLowerCase();
-          const bL = b.toLowerCase();
-          return aL < bL ? -1 : aL > bL ? 1 : 0;
+      }
+
+      if (empty(duplicateDirectories)) return;
+
+      duplicateDirectories.sort((a, b) => {
+        const aL = a.toLowerCase();
+        const bL = b.toLowerCase();
+        return aL < bL ? -1 : aL > bL ? 1 : 0;
+      });
+
+      for (const directory of duplicateDirectories) {
+        const places = directoryPlaces[directory];
+        call(() => {
+          throw new Error(
+            `Duplicate directory ${color.green(directory)}:\n` +
+              places.map((thing) => ` - ` + inspect(thing)).join('\n')
+          );
         });
-        for (const directory of duplicateDirectories) {
-          const places = directoryPlaces[directory];
-          call(() => {
-            throw new Error(
-              `Duplicate directory ${color.green(directory)}:\n` +
-                places.map((thing) => ` - ` + inspect(thing)).join('\n')
-            );
-          });
-        }
-        const allDuplicatedThings = Object.values(directoryPlaces)
-          .filter((arr) => arr.length > 1)
-          .flat();
-        const filteredThings = thingData.filter(
-          (thing) => !allDuplicatedThings.includes(thing)
-        );
-        wikiData[thingDataProp] = filteredThings;
       }
-    );
+
+      const allDuplicatedThings = Object.values(directoryPlaces)
+        .filter((arr) => arr.length > 1)
+        .flat();
+
+      const filteredThings = thingData
+        .filter((thing) => !allDuplicatedThings.includes(thing));
+
+      wikiData[thingDataProp] = filteredThings;
+    });
   }
 
   // TODO: This code closes the aggregate but it generally gets closed again
@@ -1303,67 +1245,46 @@ export function filterDuplicateDirectories(wikiData) {
 // data array.
 export function filterReferenceErrors(wikiData) {
   const referenceSpec = [
-    [
-      'wikiInfo',
-      {
-        divideTrackListsByGroupsByRef: 'group',
-      },
-    ],
-
-    [
-      'albumData',
-      {
-        artistContribsByRef: '_contrib',
-        coverArtistContribsByRef: '_contrib',
-        trackCoverArtistContribsByRef: '_contrib',
-        wallpaperArtistContribsByRef: '_contrib',
-        bannerArtistContribsByRef: '_contrib',
-        groupsByRef: 'group',
-        artTagsByRef: 'artTag',
-      },
-    ],
-
-    [
-      'trackData',
-      {
-        artistContribsByRef: '_contrib',
-        contributorContribsByRef: '_contrib',
-        coverArtistContribsByRef: '_contrib',
-        referencedTracksByRef: 'track',
-        artTagsByRef: 'artTag',
-        originalReleaseTrackByRef: 'track',
-      },
-    ],
-
-    [
-      'groupCategoryData',
-      {
-        groupsByRef: 'group',
-      },
-    ],
-
-    [
-      'homepageLayout.rows',
-      {
-        sourceGroupsByRef: 'group',
-        sourceAlbumsByRef: 'album',
-      },
-    ],
-
-    [
-      'flashData',
-      {
-        contributorContribsByRef: '_contrib',
-        featuredTracksByRef: 'track',
-      },
-    ],
-
-    [
-      'flashActData',
-      {
-        flashesByRef: 'flash',
-      },
-    ],
+    ['wikiInfo', {
+      divideTrackListsByGroupsByRef: 'group',
+    }],
+
+    ['albumData', {
+      artistContribsByRef: '_contrib',
+      coverArtistContribsByRef: '_contrib',
+      trackCoverArtistContribsByRef: '_contrib',
+      wallpaperArtistContribsByRef: '_contrib',
+      bannerArtistContribsByRef: '_contrib',
+      groupsByRef: 'group',
+      artTagsByRef: 'artTag',
+    }],
+
+    ['trackData', {
+      artistContribsByRef: '_contrib',
+      contributorContribsByRef: '_contrib',
+      coverArtistContribsByRef: '_contrib',
+      referencedTracksByRef: 'track',
+      artTagsByRef: 'artTag',
+      originalReleaseTrackByRef: 'track',
+    }],
+
+    ['groupCategoryData', {
+      groupsByRef: 'group',
+    }],
+
+    ['homepageLayout.rows', {
+      sourceGroupsByRef: 'group',
+      sourceAlbumsByRef: 'album',
+    }],
+
+    ['flashData', {
+      contributorContribsByRef: '_contrib',
+      featuredTracksByRef: 'track',
+    }],
+
+    ['flashActData', {
+      flashesByRef: 'flash',
+    }],
   ];
 
   function getNestedProp(obj, key) {
@@ -1373,94 +1294,56 @@ export function filterReferenceErrors(wikiData) {
     return recursive(obj, keys);
   }
 
-  const aggregate = openAggregate({
-    message: `Errors validating between-thing references in data`,
-  });
+  const aggregate = openAggregate({message: `Errors validating between-thing references in data`});
   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
-        )}`,
-      },
-      ({nest}) => {
-        const things = Array.isArray(thingData) ? thingData : [thingData];
-        for (const thing of things) {
-          nest(
-            {message: `Reference errors in ${inspect(thing)}`},
-            ({filter}) => {
-              for (const [property, findFnKey] of Object.entries(propSpec)) {
-                if (!thing[property]) continue;
-                if (findFnKey === '_contrib') {
-                  thing[property] = filter(
-                    thing[property],
-                    decorateErrorWithIndex(({who}) => {
-                      const alias = find.artist(who, wikiData.artistAliasData, {
-                        mode: 'quiet',
-                      });
-                      if (alias) {
-                        const original = find.artist(
-                          alias.aliasedArtistRef,
-                          wikiData.artistData,
-                          {
-                            mode: 'quiet',
-                          }
-                        );
-                        throw new Error(
-                          `Reference ${color.red(
-                            who
-                          )} is to an alias, should be ${color.green(
-                            original.name
-                          )}`
-                        );
-                      }
-                      return boundFind.artist(who);
-                    }),
-                    {
-                      message: `Reference errors in contributions ${color.green(
-                        property
-                      )} (${color.green('find.artist')})`,
-                    }
-                  );
-                  continue;
-                }
-                const findFn = boundFind[findFnKey];
-                const value = thing[property];
-                if (Array.isArray(value)) {
-                  thing[property] = filter(
-                    value,
-                    decorateErrorWithIndex(findFn),
-                    {
-                      message: `Reference errors in property ${color.green(
-                        property
-                      )} (${color.green('find.' + findFnKey)})`,
-                    }
-                  );
-                } else {
-                  nest(
-                    {
-                      message: `Reference error in property ${color.green(
-                        property
-                      )} (${color.green('find.' + findFnKey)})`,
-                    },
-                    ({call}) => {
-                      try {
-                        call(findFn, value);
-                      } catch (error) {
-                        thing[property] = null;
-                        throw error;
-                      }
-                    }
-                  );
+
+    aggregate.nest({message: `Reference errors in ${color.green('wikiData.' + thingDataProp)}`}, ({nest}) => {
+      const things = Array.isArray(thingData) ? thingData : [thingData];
+
+      for (const thing of things) {
+        nest({message: `Reference errors in ${inspect(thing)}`}, ({filter}) => {
+          for (const [property, findFnKey] of Object.entries(propSpec)) {
+            if (!thing[property]) continue;
+
+            if (findFnKey === '_contrib') {
+              thing[property] = filter(
+                thing[property],
+                decorateErrorWithIndex(({who}) => {
+                  const alias = find.artist(who, wikiData.artistAliasData, {mode: 'quiet'});
+                  if (alias) {
+                    const original = find.artist(alias.aliasedArtistRef, wikiData.artistData, {mode: 'quiet'});
+                    throw new Error(`Reference ${color.red(who)} is to an alias, should be ${color.green(original.name)}`);
+                  }
+                  return boundFind.artist(who);
+                }),
+                {message: `Reference errors in contributions ${color.green(property)} (${color.green('find.artist')})`});
+              continue;
+            }
+
+            const findFn = boundFind[findFnKey];
+            const value = thing[property];
+
+            if (Array.isArray(value)) {
+              thing[property] = filter(
+                value,
+                decorateErrorWithIndex(findFn),
+                {message: `Reference errors in property ${color.green(property)} (${color.green('find.' + findFnKey)})`});
+            } else {
+              nest({message: `Reference error in property ${color.green(property)} (${color.green('find.' + findFnKey)})`}, ({call}) => {
+                try {
+                  call(findFn, value);
+                } catch (error) {
+                  thing[property] = null;
+                  throw error;
                 }
-              }
+              });
             }
-          );
-        }
+          }
+        });
       }
-    );
+    });
   }
 
   return aggregate;
@@ -1472,18 +1355,15 @@ export function filterReferenceErrors(wikiData) {
 // a boilerplate for more specialized output, or as a quick start in utilities
 // where reporting info about data loading isn't as relevant as during the
 // main wiki build process.
-export async function quickLoadAllFromYAML(
-  dataPath,
-  {showAggregate: customShowAggregate = showAggregate} = {}
-) {
+export async function quickLoadAllFromYAML(dataPath, {
+  showAggregate: customShowAggregate = showAggregate,
+} = {}) {
   const showAggregate = customShowAggregate;
 
   let wikiData;
 
   {
-    const {aggregate, result} = await loadAndProcessDataDocuments({
-      dataPath,
-    });
+    const {aggregate, result} = await loadAndProcessDataDocuments({dataPath});
 
     wikiData = result;
 
diff --git a/src/file-size-preloader.js b/src/file-size-preloader.js
index 363fb4c0..ca1452d0 100644
--- a/src/file-size-preloader.js
+++ b/src/file-size-preloader.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Very simple, bare-bones file size loader which takes a bunch of file
 // paths, gets their filesizes, and resolves a promise when it's done.
 //
diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js
index 9151201f..dc1f6fb4 100644
--- a/src/gen-thumbs.js
+++ b/src/gen-thumbs.js
@@ -1,5 +1,4 @@
 #!/usr/bin/env node
-/** @format */
 
 // Ok, so the d8te is 3 March 2021, and the music wiki was initially released
 // on 15 November 2019. That is 474 days or 11376 hours. In my opinion, and
@@ -98,10 +97,10 @@ import {commandExists, isMain, promisifyProcess} from './util/node-utils.js';
 
 import {delay, queue} from './util/sugar.js';
 
-function traverse(
-  startDirPath,
-  {filterFile = () => true, filterDir = () => true} = {}
-) {
+function traverse(startDirPath, {
+  filterFile = () => true,
+  filterDir = () => true
+} = {}) {
   const recursive = (names, subDirPath) =>
     Promise.all(
       names.map((name) =>
@@ -197,10 +196,10 @@ function generateImageThumbnails(filePath, {spawnConvert}) {
   ]);
 }
 
-export default async function genThumbs(
-  mediaPath,
-  {queueSize = 0, quiet = false} = {}
-) {
+export default async function genThumbs(mediaPath, {
+  queueSize = 0,
+  quiet = false,
+} = {}) {
   if (!mediaPath) {
     throw new Error('Expected mediaPath to be passed');
   }
diff --git a/src/listing-spec.js b/src/listing-spec.js
index e6cd7a04..2e129ff4 100644
--- a/src/listing-spec.js
+++ b/src/listing-spec.js
@@ -1,5 +1,3 @@
-/** @format */
-
 import {
   empty,
   accumulateSum,
diff --git a/src/misc-templates.js b/src/misc-templates.js
index c6275336..2614aac9 100644
--- a/src/misc-templates.js
+++ b/src/misc-templates.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Miscellaneous utility functions which are useful across page specifications.
 // These are made available right on a page spec's ({wikiData, language, ...})
 // args object!
@@ -163,9 +161,7 @@ function unbound_generateChronologyLinks(currentThing, {
   }
 
   if (contributions.length > 8) {
-    return `<div class="chronology">${language.$(
-      'misc.chronology.seeArtistPages'
-    )}</div>`;
+    return `<div class="chronology">${language.$('misc.chronology.seeArtistPages')}</div>`;
   }
 
   return contributions
@@ -789,4 +785,4 @@ export {
   unbound_generateNavigationLinks as generateNavigationLinks,
 
   unbound_getFooterLocalizationLinks as getFooterLocalizationLinks,
-}
\ No newline at end of file
+}
diff --git a/src/page/album-commentary.js b/src/page/album-commentary.js
index e3a63baf..a0ac8d94 100644
--- a/src/page/album-commentary.js
+++ b/src/page/album-commentary.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Album commentary page and index specifications.
 
 import {filterAlbumsByCommentary} from '../util/wiki-data.js';
diff --git a/src/page/album.js b/src/page/album.js
index 14d4a9d2..009ed55d 100644
--- a/src/page/album.js
+++ b/src/page/album.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Album page specification.
 
 import {
@@ -30,6 +28,7 @@ export function write(album, {wikiData}) {
       duration: language.formatDuration(track.duration ?? 0),
       track: link.track(track),
     };
+
     return html.tag('li',
       {style: getLinkThemeString(track.color)},
       compareArrays(
diff --git a/src/page/artist-alias.js b/src/page/artist-alias.js
index 3d882f65..e2b16046 100644
--- a/src/page/artist-alias.js
+++ b/src/page/artist-alias.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Artist alias redirect pages.
 // (Makes old permalinks bring visitors to the up-to-date page.)
 
diff --git a/src/page/artist.js b/src/page/artist.js
index f6a81f67..1b5a5060 100644
--- a/src/page/artist.js
+++ b/src/page/artist.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Artist page specification.
 //
 // NB: See artist-alias.js for artist alias redirect pages.
@@ -161,16 +159,13 @@ export function write(artist, {wikiData}) {
       ? language.$('artistPage.creditList.entry.rerelease', {entry})
       : !empty(artists)
       ? contrib.what || contrib.whatArray?.length
-        ? language.$(
-            'artistPage.creditList.entry.withArtists.withContribution',
-            {
-              entry,
-              artists: getArtistString(artists),
-              contribution: contrib.whatArray
-                ? language.formatUnitList(contrib.whatArray)
-                : contrib.what,
-            }
-          )
+        ? language.$('artistPage.creditList.entry.withArtists.withContribution', {
+            entry,
+            artists: getArtistString(artists),
+            contribution: contrib.whatArray
+              ? language.formatUnitList(contrib.whatArray)
+              : contrib.what,
+          })
         : language.$('artistPage.creditList.entry.withArtists', {
             entry,
             artists: getArtistString(artists),
@@ -227,15 +222,12 @@ export function write(artist, {wikiData}) {
                 original: track.originalReleaseTrack,
                 entry: language.$('artistPage.creditList.entry.track.withDuration', {
                   track: link.track(track),
-                  duration: language.formatDuration(
-                    track.duration ?? 0
-                  ),
+                  duration: language.formatDuration(track.duration ?? 0),
                 }),
                 ...props,
               }))
               .map(({original, ...opts}) =>
-                html.tag(
-                  'li',
+                html.tag('li',
                   {class: original && 'rerelease'},
                   generateEntryAccents({
                     getArtistString,
@@ -282,20 +274,14 @@ export function write(artist, {wikiData}) {
     type: 'data',
     path: ['artist', artist.directory],
     data: ({serializeContribs, serializeLink}) => {
-      const serializeArtistsAndContrib = bindOpts(
-        unbound_serializeArtistsAndContrib,
-        {
-          serializeContribs,
-          serializeLink,
-        }
-      );
-
-      const serializeTrackListChunks = bindOpts(
-        unbound_serializeTrackListChunks,
-        {
-          serializeLink,
-        }
-      );
+      const serializeArtistsAndContrib = bindOpts(unbound_serializeArtistsAndContrib, {
+        serializeContribs,
+        serializeLink,
+      });
+
+      const serializeTrackListChunks = bindOpts(unbound_serializeTrackListChunks, {
+        serializeLink,
+      });
 
       return {
         albums: {
@@ -663,12 +649,12 @@ export function write(artist, {wikiData}) {
 
 // Utility functions
 
-function generateNavForArtist(
-  artist,
-  isGallery,
-  hasGallery,
-  {generateInfoGalleryLinks, link, language, wikiData}
-) {
+function generateNavForArtist(artist, isGallery, hasGallery, {
+  generateInfoGalleryLinks,
+  language,
+  link,
+  wikiData,
+}) {
   const {wikiInfo} = wikiData;
 
   const infoGalleryLinks =
diff --git a/src/page/flash.js b/src/page/flash.js
index 237dd47f..74a6b4aa 100644
--- a/src/page/flash.js
+++ b/src/page/flash.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Flash page and index specifications.
 
 import {empty} from '../util/sugar.js';
diff --git a/src/page/group.js b/src/page/group.js
index 2bd6da96..c261565b 100644
--- a/src/page/group.js
+++ b/src/page/group.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Group page specifications.
 
 import {
diff --git a/src/page/index.js b/src/page/index.js
index 149503f0..f580cbea 100644
--- a/src/page/index.js
+++ b/src/page/index.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // NB: This is the index for the page/ directory and contains exports for all
 // other modules here! It's not the page spec for the homepage - see
 // homepage.js for that.
diff --git a/src/page/listing.js b/src/page/listing.js
index 65982f83..cb297a89 100644
--- a/src/page/listing.js
+++ b/src/page/listing.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Listing page specification.
 //
 // The targets here are a bit different than for most pages: rather than data
diff --git a/src/page/static.js b/src/page/static.js
index 8925b606..2a0f5e53 100644
--- a/src/page/static.js
+++ b/src/page/static.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Static content page specification. (These are static pages coded into the
 // wiki data folder, used for a variety of purposes, e.g. wiki info,
 // changelog, and so on.)
diff --git a/src/page/tag.js b/src/page/tag.js
index faa0df22..da4f194a 100644
--- a/src/page/tag.js
+++ b/src/page/tag.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Art tag page specification.
 
 export function condition({wikiData}) {
@@ -28,8 +26,8 @@ export function write(tag, {wikiData}) {
       getThemeString,
       getTrackCover,
       html,
-      link,
       language,
+      link,
     }) => ({
       title: language.$('tagPage.title', {tag: tag.name}),
       theme: getThemeString(tag.color),
@@ -79,10 +77,11 @@ export function write(tag, {wikiData}) {
 
 // Utility functions
 
-function generateTagNav(
-  tag,
-  {link, language, wikiData}
-) {
+function generateTagNav(tag, {
+  language,
+  link,
+  wikiData,
+}) {
   return {
     linkContainerClasses: ['nav-links-hierarchy'],
     links: [
diff --git a/src/page/track.js b/src/page/track.js
index cf937248..b61defe2 100644
--- a/src/page/track.js
+++ b/src/page/track.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Track page specification.
 
 import {
@@ -64,7 +62,7 @@ export function write(track, {wikiData}) {
   const hasCommentary =
     track.commentary || otherReleases.some((t) => t.commentary);
 
-  const generateCommentary = ({link, language, transformMultiline}) =>
+  const generateCommentary = ({language, link, transformMultiline}) =>
     transformMultiline([
       track.commentary,
       ...otherReleases.map((track) =>
diff --git a/src/repl.js b/src/repl.js
index bd447bec..5497ef55 100644
--- a/src/repl.js
+++ b/src/repl.js
@@ -1,5 +1,3 @@
-/** @format */
-
 import * as os from 'os';
 import * as path from 'path';
 import * as repl from 'repl';
diff --git a/src/static/client.js b/src/static/client.js
index 1ffcb939..32fb2abe 100644
--- a/src/static/client.js
+++ b/src/static/client.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // This is the JS file that gets loaded on the client! It's only really used for
 // the random track feature right now - the idea is we only use it for stuff
 // that cannot 8e done at static-site compile time, 8y its fundamentally
@@ -392,8 +390,7 @@ function makeInfoCardLinkHandlers(type) {
           fastHover = true;
           infoCard.show(type, evt.target);
         },
-        fastHover ? FAST_HOVER_INFO_DELAY : NORMAL_HOVER_INFO_DELAY
-      );
+        fastHover ? FAST_HOVER_INFO_DELAY : NORMAL_HOVER_INFO_DELAY);
 
       clearTimeout(endFastHoverTimeout);
       endFastHoverTimeout = null;
diff --git a/src/static/lazy-loading.js b/src/static/lazy-loading.js
index 1b779d26..b5b0a368 100644
--- a/src/static/lazy-loading.js
+++ b/src/static/lazy-loading.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Lazy loading! Roll your own. Woot.
 // This file includes a 8unch of fall8acks and stuff like that, and is written
 // with fairly Olden JavaScript(TM), so as to work on pretty much any 8rowser
diff --git a/src/upd8.js b/src/upd8.js
index 44cbce05..8e3e0920 100755
--- a/src/upd8.js
+++ b/src/upd8.js
@@ -1,5 +1,4 @@
 #!/usr/bin/env node
-/** @format */
 
 // HEY N8RDS!
 //
@@ -814,8 +813,8 @@ function validateWriteObject(obj) {
 
 /*
 async function writeData(subKey, directory, data) {
-    const paths = writePage.paths('', 'data.' + subKey, directory, {file: 'data.json'});
-    await writePage.write(JSON.stringify(data), {paths});
+  const paths = writePage.paths('', 'data.' + subKey, directory, {file: 'data.json'});
+  await writePage.write(JSON.stringify(data), {paths});
 }
 */
 
@@ -865,20 +864,17 @@ writePage.to =
     return path;
   };
 
-writePage.html = (
-  pageInfo,
-  {
-    defaultLanguage,
-    language,
-    languages,
-    localizedPaths,
-    paths,
-    oEmbedJSONHref,
-    to,
-    transformMultiline,
-    wikiData,
-  }
-) => {
+writePage.html = (pageInfo, {
+  defaultLanguage,
+  language,
+  languages,
+  localizedPaths,
+  paths,
+  oEmbedJSONHref,
+  to,
+  transformMultiline,
+  wikiData,
+}) => {
   const {wikiInfo} = wikiData;
 
   let {
@@ -986,10 +982,13 @@ writePage.html = (
         }),
       ]);
 
-  const generateSidebarHTML = (
-    id,
-    {content, multiple, classes, collapse = true, wide = false}
-  ) =>
+  const generateSidebarHTML = (id, {
+    content,
+    multiple,
+    classes,
+    collapse = true,
+    wide = false,
+  }) =>
     content
       ? html.tag('div',
           {
@@ -1388,12 +1387,9 @@ writePage.write = async ({html, oEmbedJSON = '', paths}) => {
 };
 
 // TODO: This only supports one <>-style argument.
-writePage.paths = (
-  baseDirectory,
-  fullKey,
-  directory = '',
-  {file = 'index.html'} = {}
-) => {
+writePage.paths = (baseDirectory, fullKey, directory = '', {
+  file = 'index.html',
+} = {}) => {
   const [groupKey, subKey] = fullKey.split('.');
 
   const pathname =
@@ -1485,49 +1481,46 @@ function writeSharedFilesAndPages({language, wikiData}) {
     await writeFile(path.join(outputPath, from, 'index.html'), content);
   };
 
-  return progressPromiseAll(
-    `Writing files & pages shared across languages.`,
-    [
-      groupData?.some((group) => group.directory === 'fandom') &&
-        redirect(
-          'Fandom - Gallery',
-          'albums/fandom',
-          'localized.groupGallery',
-          'fandom'
-        ),
+  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'
-        ),
+    groupData?.some((group) => group.directory === 'official') &&
+      redirect(
+        'Official - Gallery',
+        'albums/official',
+        'localized.groupGallery',
+        'official'
+      ),
 
-      wikiInfo.enableListings &&
-        redirect(
-          'Album Commentary',
-          'list/all-commentary',
-          'localized.commentaryIndex',
-          ''
-        ),
+    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)
-  );
+    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));
 }
 
 function generateRedirectPage(title, target, {language}) {
@@ -1752,10 +1745,7 @@ 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(!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;
@@ -1833,9 +1823,7 @@ async function main() {
 
   {
     const logThings = (thingDataProp, label) =>
-      logInfo` - ${
-        wikiData[thingDataProp]?.length ?? color.red('(Missing!)')
-      } ${color.normal(color.dim(label))}`;
+      logInfo` - ${wikiData[thingDataProp]?.length ?? color.red('(Missing!)')} ${color.normal(color.dim(label))}`;
     try {
       logInfo`Loaded data and processed objects:`;
       logThings('albumData', 'albums');
@@ -2032,10 +2020,8 @@ async function main() {
 
   {
     const tagRefs = new Set(
-      [...WD.trackData, ...WD.albumData].flatMap(
-        (thing) => thing.artTagsByRef ?? []
-      )
-    );
+      [...WD.trackData, ...WD.albumData]
+        .flatMap((thing) => thing.artTagsByRef ?? []));
 
     for (const ref of tagRefs) {
       if (find.artTag(ref, WD.artTagData)) {
@@ -2051,12 +2037,10 @@ 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)
-  );
+  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 fileSizePreloader = new FileSizePreloader();
 
@@ -2089,7 +2073,7 @@ async function main() {
   ];
 
   const getSizeOfAdditionalFile = (mediaPath) => {
-    const {device = null} =
+    const {device} =
       additionalFilePaths.find(({media}) => media === mediaPath) || {};
     if (!device) return null;
     return fileSizePreloader.getSizeOfPath(device);
@@ -2097,9 +2081,7 @@ async function main() {
 
   logInfo`Preloading filesizes for ${additionalFilePaths.length} additional files...`;
 
-  fileSizePreloader.loadPaths(
-    ...additionalFilePaths.map((path) => path.device)
-  );
+  fileSizePreloader.loadPaths(...additionalFilePaths.map((path) => path.device));
   await fileSizePreloader.waitUntilDoneLoading();
 
   logInfo`Done preloading filesizes!`;
@@ -2139,18 +2121,14 @@ async function main() {
         }
 
         if (!pageSpec.write) {
-          logError`${flag + '.targets'} is specified, but ${
-            flag + '.write'
-          } is missing!`;
+          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})`;
+          logError`${flag + '.targets'} was called, but it didn't return an array! (${typeof targets})`;
           error = true;
           return null;
         }
@@ -2230,345 +2208,333 @@ async function main() {
   }
 
   /*
-    await progressPromiseAll(`Writing data files shared across languages.`, queue(
-        dataWrites.map(({path, data}) => () => {
-            const bound = {};
+  await progressPromiseAll(`Writing data files shared across languages.`, queue(
+    dataWrites.map(({path, data}) => () => {
+      const bound = {};
 
-            bound.serializeLink = bindOpts(serializeLink, {});
+      bound.serializeLink = bindOpts(serializeLink, {});
 
-            bound.serializeContribs = bindOpts(serializeContribs, {});
+      bound.serializeContribs = bindOpts(serializeContribs, {});
 
-            bound.serializeImagePaths = bindOpts(serializeImagePaths, {
-                thumb
-            });
+      bound.serializeImagePaths = bindOpts(serializeImagePaths, {
+        thumb
+      });
 
-            bound.serializeCover = bindOpts(serializeCover, {
-                [bindOpts.bindIndex]: 2,
-                serializeImagePaths: bound.serializeImagePaths,
-                urls
-            });
+      bound.serializeCover = bindOpts(serializeCover, {
+        [bindOpts.bindIndex]: 2,
+        serializeImagePaths: bound.serializeImagePaths,
+        urls
+      });
 
-            bound.serializeGroupsForAlbum = bindOpts(serializeGroupsForAlbum, {
-                serializeLink
-            });
+      bound.serializeGroupsForAlbum = bindOpts(serializeGroupsForAlbum, {
+        serializeLink
+      });
 
-            bound.serializeGroupsForTrack = bindOpts(serializeGroupsForTrack, {
-                serializeLink
-            });
+      bound.serializeGroupsForTrack = bindOpts(serializeGroupsForTrack, {
+        serializeLink
+      });
 
-            // TODO: This only supports one <>-style argument.
-            return writeData(path[0], path[1], data({
-                ...bound
-            }));
-        }),
-        queueSize
-    ));
-    */
+      // 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`
-    );
+    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;
+
+        // TODO: This only supports one <>-style argument.
+        const pageSubKey = path[0];
+        const directory = path[1];
+
+        const localizedPaths = Object.fromEntries(
+          Object.entries(languages)
+            .filter(
+              ([key, language]) => key !== 'default' && !language.hidden
+            )
+            .map(([_key, language]) => [
+              language.code,
+              writePage.paths(
+                language === finalDefaultLanguage ? '' : language.code,
+                'localized.' + pageSubKey,
+                directory
+              ),
+            ])
+        );
 
-    await progressPromiseAll(
-      `Writing ${language.code}`,
-      queue(
-        [
-          ...pageWrites.map((props) => () => {
-            const {path, page} = props;
+        const paths = writePage.paths(
+          baseDirectory,
+          'localized.' + pageSubKey,
+          directory
+        );
 
-            // TODO: This only supports one <>-style argument.
-            const pageSubKey = path[0];
-            const directory = path[1];
+        const to = writePage.to({
+          baseDirectory,
+          pageSubKey,
+          paths,
+        });
 
-            const localizedPaths = Object.fromEntries(
-              Object.entries(languages)
-                .filter(
-                  ([key, language]) => key !== 'default' && !language.hidden
+        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
                 )
-                .map(([_key, language]) => [
-                  language.code,
-                  writePage.paths(
-                    language === finalDefaultLanguage ? '' : language.code,
-                    'localized.' + pageSubKey,
-                    directory
-                  ),
-                ])
-            );
-
-            const paths = writePage.paths(
-              baseDirectory,
-              'localized.' + pageSubKey,
-              directory
-            );
-
-            const to = writePage.to({
-              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.link = withEntries(unbound_link, (entries) =>
-              entries.map(([key, fn]) => [key, bindOpts(fn, {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,
-            });
-
-            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.getLinkThemeString = getLinkThemeString;
-
-            bound.getThemeString = getThemeString;
-
-            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.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.getAlbumStylesheet = bindOpts(getAlbumStylesheet, {
-              to,
-            });
-
-            const pageInfo = page({
-              ...bound,
-
-              language,
-
-              absoluteTo,
-              relativeTo: to,
-              to,
-              urls,
-
-              getSizeOfAdditionalFile,
-            });
-
-            const oEmbedJSON = writePage.oEmbedJSON(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 = writePage.html(pageInfo, {
-              defaultLanguage: finalDefaultLanguage,
-              language,
-              languages,
-              localizedPaths,
-              oEmbedJSONHref,
-              paths,
-              to,
-              transformMultiline: bound.transformMultiline,
-              wikiData,
-            });
-
-            return writePage.write({
-              html: pageHTML,
-              oEmbedJSON,
-              paths,
-            });
-          }),
-          ...redirectWrites.map(({fromPath, toPath, title: titleFn}) => () => {
-            const title = titleFn({
-              language,
-            });
-
-            // TODO: This only supports one <>-style argument.
-            const fromPaths = writePage.paths(
-              baseDirectory,
-              'localized.' + fromPath[0],
-              fromPath[1]
-            );
-            const to = writePage.to({
-              baseDirectory,
-              pageSubKey: fromPath[0],
-              paths: fromPaths,
-            });
-
-            const target = to('localized.' + toPath[0], ...toPath.slice(1));
-            const html = generateRedirectPage(title, target, {language});
-            return writePage.write({html, paths: fromPaths});
-          }),
-        ],
-        queueSize
-      )
-    );
+              : 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.link = withEntries(unbound_link, (entries) =>
+          entries.map(([key, fn]) => [key, bindOpts(fn, {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,
+        });
+
+        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.getLinkThemeString = getLinkThemeString;
+
+        bound.getThemeString = getThemeString;
+
+        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.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.getAlbumStylesheet = bindOpts(getAlbumStylesheet, {
+          to,
+        });
+
+        const pageInfo = page({
+          ...bound,
+
+          language,
+
+          absoluteTo,
+          relativeTo: to,
+          to,
+          urls,
+
+          getSizeOfAdditionalFile,
+        });
+
+        const oEmbedJSON = writePage.oEmbedJSON(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 = writePage.html(pageInfo, {
+          defaultLanguage: finalDefaultLanguage,
+          language,
+          languages,
+          localizedPaths,
+          oEmbedJSONHref,
+          paths,
+          to,
+          transformMultiline: bound.transformMultiline,
+          wikiData,
+        });
+
+        return writePage.write({
+          html: pageHTML,
+          oEmbedJSON,
+          paths,
+        });
+      }),
+      ...redirectWrites.map(({fromPath, toPath, title: titleFn}) => () => {
+        const title = titleFn({
+          language,
+        });
+
+        // TODO: This only supports one <>-style argument.
+        const fromPaths = writePage.paths(
+          baseDirectory,
+          'localized.' + fromPath[0],
+          fromPath[1]
+        );
+        const to = writePage.to({
+          baseDirectory,
+          pageSubKey: fromPath[0],
+          paths: fromPaths,
+        });
+
+        const target = to('localized.' + toPath[0], ...toPath.slice(1));
+        const html = generateRedirectPage(title, target, {language});
+        return writePage.write({html, paths: fromPaths});
+      }),
+    ], queueSize));
   };
 
   await wrapLanguages(perLanguageFn, {
diff --git a/src/url-spec.js b/src/url-spec.js
index bab97efa..ce479267 100644
--- a/src/url-spec.js
+++ b/src/url-spec.js
@@ -1,5 +1,3 @@
-/** @format */
-
 import {withEntries} from './util/sugar.js';
 
 const urlSpec = {
@@ -87,8 +85,7 @@ const urlSpec = {
 // so it should never be referenced manually.
 urlSpec.localizedWithBaseDirectory = {
   paths: withEntries(urlSpec.localized.paths, (entries) =>
-    entries.map(([key, path]) => [key, '<>/' + path])
-  ),
+    entries.map(([key, path]) => [key, '<>/' + path])),
 };
 
 export default urlSpec;
diff --git a/src/util/cli.js b/src/util/cli.js
index 99c0638c..f1a31900 100644
--- a/src/util/cli.js
+++ b/src/util/cli.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Utility functions for CLI- and de8ugging-rel8ted stuff.
 //
 // A 8unch of these depend on process.stdout 8eing availa8le, so they won't
diff --git a/src/util/colors.js b/src/util/colors.js
index 5848a820..a0cc7486 100644
--- a/src/util/colors.js
+++ b/src/util/colors.js
@@ -1,16 +1,25 @@
-/** @format */
-
 // Color and theming utility functions! Handy.
 
 // Graciously stolen from https://stackoverflow.com/a/54071699! ::::)
 // in: r,g,b in [0,1], out: h in [0,360) and s,l in [0,1]
 export function rgb2hsl(r, g, b) {
   let a = Math.max(r, g, b),
-    n = a - Math.min(r, g, b),
-    f = 1 - Math.abs(a + a - n - 1);
+      n = a - Math.min(r, g, b),
+      f = 1 - Math.abs(a + a - n - 1);
+
   let h =
-    n && (a == r ? (g - b) / n : a == g ? 2 + (b - r) / n : 4 + (r - g) / n);
-  return [60 * (h < 0 ? h + 6 : h), f ? n / f : 0, (a + a - n) / 2];
+    n &&
+      a == r
+        ? (g - b) / n
+        : a == g
+          ? 2 + (b - r) / n
+          : 4 + (r - g) / n;
+
+  return [
+    60 * (h < 0 ? h + 6 : h),
+    f ? n / f : 0,
+    (a + a - n) / 2
+  ];
 }
 
 export function getColors(primary) {
@@ -19,10 +28,10 @@ export function getColors(primary) {
     .match(/[0-9a-fA-F]{2,2}/g)
     .slice(0, 3)
     .map((val) => parseInt(val, 16) / 255);
+
   const [h, s, l] = rgb2hsl(r, g, b);
-  const dim = `hsl(${Math.round(h)}deg, ${Math.round(s * 50)}%, ${Math.round(
-    l * 80
-  )}%)`;
+
+  const dim = `hsl(${Math.round(h)}deg, ${Math.round(s * 50)}%, ${Math.round(l * 80)}%)`;
   const bg = `hsla(${Math.round(h)}deg, ${Math.round(s * 15)}%, 12%, 0.80)`;
 
   return {
diff --git a/src/util/find.js b/src/util/find.js
index 71026fa2..ed0a6809 100644
--- a/src/util/find.js
+++ b/src/util/find.js
@@ -1,5 +1,3 @@
-/** @format */
-
 import {color, logWarn} from './cli.js';
 
 import {inspect} from 'util';
@@ -26,9 +24,7 @@ function findHelper(keys, findFns = {}) {
   const byDirectory = findFns.byDirectory || matchDirectory;
   const byName = findFns.byName || matchName;
 
-  const keyRefRegex = new RegExp(
-    String.raw`^(?:(${keys.join('|')}):(?=\S))?(.*)$`
-  );
+  const keyRefRegex = new RegExp(String.raw`^(?:(${keys.join('|')}):(?=\S))?(.*)$`);
 
   // The mode argument here may be 'warn', 'error', or 'quiet'. 'error' throws
   // errors for null matches (with details about the error), while 'warn' and
@@ -37,9 +33,7 @@ function findHelper(keys, findFns = {}) {
   return (fullRef, data, {mode = 'warn'} = {}) => {
     if (!fullRef) return null;
     if (typeof fullRef !== 'string') {
-      throw new Error(
-        `Got a reference that is ${typeof fullRef}, not string: ${fullRef}`
-      );
+      throw new Error(`Got a reference that is ${typeof fullRef}, not string: ${fullRef}`);
     }
 
     if (!data) {
diff --git a/src/util/html.js b/src/util/html.js
index 752291e9..0a586223 100644
--- a/src/util/html.js
+++ b/src/util/html.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Some really simple functions for formatting HTML content.
 
 // COMPREHENSIVE!
@@ -116,16 +114,18 @@ export function escapeAttributeValue(value) {
 export function attributes(attribs) {
   return Object.entries(attribs)
     .map(([key, val]) => {
-      if (typeof val === 'undefined' || val === null) return [key, val, false];
-      else if (typeof val === 'string') return [key, val, true];
-      else if (typeof val === 'boolean') return [key, val, val];
-      else if (typeof val === 'number') return [key, val.toString(), true];
+      if (typeof val === 'undefined' || val === null)
+        return [key, val, false];
+      else if (typeof val === 'string')
+        return [key, val, true];
+      else if (typeof val === 'boolean')
+        return [key, val, val];
+      else if (typeof val === 'number')
+        return [key, val.toString(), true];
       else if (Array.isArray(val))
         return [key, val.filter(Boolean).join(' '), val.length > 0];
       else
-        throw new Error(
-          `Attribute value for ${key} should be primitive or array, got ${typeof val}`
-        );
+        throw new Error(`Attribute value for ${key} should be primitive or array, got ${typeof val}`);
     })
     .filter(([_key, _val, keep]) => keep)
     .map(([key, val]) =>
diff --git a/src/util/io.js b/src/util/io.js
index cfd6708d..6cc89b56 100644
--- a/src/util/io.js
+++ b/src/util/io.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Utility functions for interacting with files and other external data
 // interfacey constructs.
 
diff --git a/src/util/link.js b/src/util/link.js
index 8fe3c2f4..9de4c95a 100644
--- a/src/util/link.js
+++ b/src/util/link.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // This file is essentially one level of a8straction a8ove urls.js (and the
 // urlSpec it gets its paths from). It's a 8unch of utility functions which
 // take certain types of wiki data o8jects (colloquially known as "things")
@@ -35,18 +33,18 @@ export function getLinkThemeString(color) {
 const appendIndexHTMLRegex = /^(?!https?:\/\/).+\/$/;
 
 const linkHelper =
-  (hrefFn, {color = true, attr = null} = {}) =>
-  (
-    thing,
-    {
-      to,
-      text = '',
-      attributes = null,
-      class: className = '',
-      color: color2 = true,
-      hash = '',
-    }
-  ) => {
+  (hrefFn, {
+    color = true,
+    attr = null,
+  } = {}) =>
+  (thing, {
+    to,
+    text = '',
+    attributes = null,
+    class: className = '',
+    color: color2 = true,
+    hash = '',
+  }) => {
     let href = hrefFn(thing, {to});
 
     if (link.globalOptions.appendIndexHTML) {
@@ -88,6 +86,7 @@ const linkDirectory = (key, {expose = null, attr = null, ...conf} = {}) =>
 
 const linkPathname = (key, conf) =>
   linkHelper(({directory: pathname}, {to}) => to(key, pathname), conf);
+
 const linkIndex = (key, conf) =>
   linkHelper((_, {to}) => to('localized.' + key), conf);
 
@@ -143,8 +142,7 @@ const link = {
       to(
         'media.albumAdditionalFile',
         fakeFileObject.album.directory,
-        fakeFileObject.name
-      ),
+        fakeFileObject.name),
     {color: false}
   ),
   albumAdditionalFile: ({file, album}, {to}) =>
@@ -153,8 +151,7 @@ const link = {
         name: file,
         album,
       },
-      {to}
-    ),
+      {to}),
 
   media: linkPathname('media.path', {color: false}),
   root: linkPathname('shared.path', {color: false}),
diff --git a/src/util/magic-constants.js b/src/util/magic-constants.js
index dbdbcfda..73fdbc6d 100644
--- a/src/util/magic-constants.js
+++ b/src/util/magic-constants.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Magic constants only! These are hard-coded, and any use of them should be
 // considered a flaw in the codebase - areas where we use hard-coded behavior
 // to support one use of the wiki software (i.e. HSMusic, usually), rather than
diff --git a/src/util/node-utils.js b/src/util/node-utils.js
index df446654..252e920a 100644
--- a/src/util/node-utils.js
+++ b/src/util/node-utils.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Utility functions which are only relevant to particular Node.js constructs.
 
 import {fileURLToPath} from 'url';
diff --git a/src/util/replacer.js b/src/util/replacer.js
index ea5a674d..9d602ca9 100644
--- a/src/util/replacer.js
+++ b/src/util/replacer.js
@@ -1,15 +1,10 @@
-/** @format */
-
 import {logError, logWarn} from './cli.js';
 import {escapeRegex} from './sugar.js';
 
 export function validateReplacerSpec(replacerSpec, {find, link}) {
   let success = true;
 
-  for (const [
-    key,
-    {link: linkKey, find: findKey, html},
-  ] of Object.entries(replacerSpec)) {
+  for (const [key, {link: linkKey, find: findKey, html}] of Object.entries(replacerSpec)) {
     if (!html && !link[linkKey]) {
       logError`The replacer spec ${key} has invalid link key ${linkKey}! Specify it in link specs or fix typo.`;
       success = false;
@@ -436,14 +431,18 @@ function transformNodes(nodes, opts) {
   return nodes.map((node) => transformNode(node, opts)).join('');
 }
 
-export function transformInline(
-  input,
-  {replacerSpec, find, link, language, to, wikiData}
-) {
+export function transformInline(input, {
+  replacerSpec,
+  find,
+  language,
+  link,
+  to,
+  wikiData,
+}) {
   if (!replacerSpec) throw new Error('Expected replacerSpec');
   if (!find) throw new Error('Expected find');
-  if (!link) throw new Error('Expected link');
   if (!language) throw new Error('Expected language');
+  if (!link) throw new Error('Expected link');
   if (!to) throw new Error('Expected to');
   if (!wikiData) throw new Error('Expected wikiData');
 
diff --git a/src/util/serialize.js b/src/util/serialize.js
index 9aa8b0c5..73a31374 100644
--- a/src/util/serialize.js
+++ b/src/util/serialize.js
@@ -1,5 +1,3 @@
-/** @format */
-
 export function serializeLink(thing) {
   const ret = {};
   ret.name = thing.name;
@@ -25,11 +23,10 @@ export function serializeImagePaths(original, {thumb}) {
   };
 }
 
-export function serializeCover(
-  thing,
-  pathFunction,
-  {serializeImagePaths, urls}
-) {
+export function serializeCover(thing, pathFunction, {
+  serializeImagePaths,
+  urls,
+}) {
   const coverPath = pathFunction(thing, {
     to: urls.from('media.root').to,
   });
diff --git a/src/util/sugar.js b/src/util/sugar.js
index 24ae8639..808a7e1c 100644
--- a/src/util/sugar.js
+++ b/src/util/sugar.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Syntactic sugar! (Mostly.)
 // Generic functions - these are useful just a8out everywhere.
 //
@@ -17,9 +15,7 @@ import {color} from './cli.js';
 export function* splitArray(array, fn) {
   let lastIndex = 0;
   while (lastIndex < array.length) {
-    let nextIndex = array.findIndex(
-      (item, index) => index >= lastIndex && fn(item)
-    );
+    let nextIndex = array.findIndex((item, index) => index >= lastIndex && fn(item));
     if (nextIndex === -1) {
       nextIndex = array.length;
     }
@@ -54,8 +50,7 @@ export function accumulateSum(array, fn = x => x) {
     (accumulator, value, index, array) =>
       accumulator +
         fn(value, index, array) ?? 0,
-    0
-  );
+    0);
 }
 
 export const mapInPlace = (array, fn) =>
@@ -275,11 +270,10 @@ export function mapAggregate(array, fn, aggregateOpts) {
   return _mapAggregate('sync', null, array, fn, aggregateOpts);
 }
 
-export function mapAggregateAsync(
-  array,
-  fn,
-  {promiseAll = Promise.all.bind(Promise), ...aggregateOpts} = {}
-) {
+export function mapAggregateAsync(array, fn, {
+  promiseAll = Promise.all.bind(Promise),
+  ...aggregateOpts
+} = {}) {
   return _mapAggregate('async', promiseAll, array, fn, aggregateOpts);
 }
 
@@ -299,10 +293,11 @@ export function _mapAggregate(mode, promiseAll, array, fn, aggregateOpts) {
       .filter((value) => value !== failureSymbol);
     return {result, aggregate};
   } else {
-    return promiseAll(array.map(aggregate.wrapAsync(fn))).then((values) => {
-      const result = values.filter((value) => value !== failureSymbol);
-      return {result, aggregate};
-    });
+    return promiseAll(array.map(aggregate.wrapAsync(fn)))
+      .then((values) => {
+        const result = values.filter((value) => value !== failureSymbol);
+        return {result, aggregate};
+      });
   }
 }
 
@@ -317,11 +312,10 @@ export function filterAggregate(array, fn, aggregateOpts) {
   return _filterAggregate('sync', null, array, fn, aggregateOpts);
 }
 
-export async function filterAggregateAsync(
-  array,
-  fn,
-  {promiseAll = Promise.all.bind(Promise), ...aggregateOpts} = {}
-) {
+export async function filterAggregateAsync(array, fn, {
+  promiseAll = Promise.all.bind(Promise),
+  ...aggregateOpts
+} = {}) {
   return _filterAggregate('async', promiseAll, array, fn, aggregateOpts);
 }
 
@@ -357,24 +351,20 @@ function _filterAggregate(mode, promiseAll, array, fn, aggregateOpts) {
 
   if (mode === 'sync') {
     const result = array
-      .map(
-        aggregate.wrap((input, index, array) => {
-          const output = fn(input, index, array);
-          return {input, output};
-        })
-      )
+      .map(aggregate.wrap((input, index, array) => {
+        const output = fn(input, index, array);
+        return {input, output};
+      }))
       .filter(filterFunction)
       .map(mapFunction);
 
     return {result, aggregate};
   } else {
     return promiseAll(
-      array.map(
-        aggregate.wrapAsync(async (input, index, array) => {
-          const output = await fn(input, index, array);
-          return {input, output};
-        })
-      )
+      array.map(aggregate.wrapAsync(async (input, index, array) => {
+        const output = await fn(input, index, array);
+        return {input, output};
+      }))
     ).then((values) => {
       const result = values.filter(filterFunction).map(mapFunction);
 
@@ -414,10 +404,10 @@ export function _withAggregate(mode, aggregateOpts, fn) {
   }
 }
 
-export function showAggregate(
-  topError,
-  {pathToFile = (p) => p, showTraces = true} = {}
-) {
+export function showAggregate(topError, {
+  pathToFile = (p) => p,
+  showTraces = true,
+} = {}) {
   const recursive = (error, {level}) => {
     let header = showTraces
       ? `[${error.constructor.name || 'unnamed'}] ${
diff --git a/src/util/urls.js b/src/util/urls.js
index d86c047d..1f9cd9c0 100644
--- a/src/util/urls.js
+++ b/src/util/urls.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Code that deals with URLs (really the pathnames that get referenced all
 // throughout the gener8ted HTML). Most nota8ly here is generateURLs, which
 // is in charge of pre-gener8ting a complete network of template strings
@@ -28,9 +26,7 @@ export function generateURLs(urlSpec) {
     const group = obj[groupKey];
 
     if (!Object.hasOwn(group, subKey)) {
-      throw new Error(
-        `Expected valid subkey (got ${subKey} for group ${groupKey})`
-      );
+      throw new Error(`Expected valid subkey (got ${subKey} for group ${groupKey})`);
     }
 
     return {
@@ -47,9 +43,8 @@ export function generateURLs(urlSpec) {
   const generateTo = (fromPath, fromGroup) => {
     const A = trimLeadingSlash(fromPath);
 
-    const rebasePrefix = '../'.repeat(
-      (fromGroup.prefix || '').split('/').filter(Boolean).length
-    );
+    const rebasePrefix = '../'
+      .repeat((fromGroup.prefix || '').split('/').filter(Boolean).length);
 
     const pathHelper = (toPath, toGroup) => {
       let B = trimLeadingSlash(toPath);
@@ -117,14 +112,14 @@ export function generateURLs(urlSpec) {
   };
 
   const generateFrom = () => {
-    const map = withEntries(urlSpec, (entries) =>
-      entries.map(([key, group]) => [
+    const map = withEntries(
+      urlSpec,
+      (entries) => entries.map(([key, group]) => [
         key,
         withEntries(group.paths, (entries) =>
           entries.map(([key, path]) => [key, generateTo(path, group)])
         ),
-      ])
-    );
+      ]));
 
     const from = (key) => getValueForFullKey(map, key).value;
 
diff --git a/src/util/wiki-data.js b/src/util/wiki-data.js
index b0b0b2e0..7d2cfa9f 100644
--- a/src/util/wiki-data.js
+++ b/src/util/wiki-data.js
@@ -1,5 +1,3 @@
-/** @format */
-
 // Utility functions for interacting with wiki data.
 
 import {empty} from './sugar.js';
@@ -144,10 +142,9 @@ export function normalizeName(s) {
 // ...trackData]), because the initial sort places albums before tracks - and
 // sortByDirectory will handle the rest, given all directories are unique
 // except when album and track directories overlap with each other.
-export function sortByDirectory(
-  data,
-  {getDirectory = (o) => o.directory} = {}
-) {
+export function sortByDirectory(data, {
+  getDirectory = (o) => o.directory,
+} = {}) {
   return data.sort((a, b) => {
     const ad = getDirectory(a);
     const bd = getDirectory(b);
@@ -155,7 +152,9 @@ export function sortByDirectory(
   });
 }
 
-export function sortByName(data, {getName = (o) => o.name} = {}) {
+export function sortByName(data, {
+  getName = (o) => o.name,
+} = {}) {
   const nameMap = new Map();
   const normalizedNameMap = new Map();
   for (const o of data) {
@@ -178,7 +177,9 @@ export function sortByName(data, {getName = (o) => o.name} = {}) {
   });
 }
 
-export function sortByDate(data, {getDate = (o) => o.date} = {}) {
+export function sortByDate(data, {
+  getDate = (o) => o.date,
+} = {}) {
   return data.sort((a, b) => {
     const ad = getDate(a);
     const bd = getDate(b);
@@ -269,7 +270,10 @@ export function sortByConditions(data, conditions) {
 // Expects thing properties:
 //  * directory (or override getDirectory)
 //  * name (or override getName)
-export function sortAlphabetically(data, {getDirectory, getName} = {}) {
+export function sortAlphabetically(data, {
+  getDirectory,
+  getName,
+} = {}) {
   sortByDirectory(data, {getDirectory});
   sortByName(data, {getName});
   return data;
@@ -279,10 +283,11 @@ export function sortAlphabetically(data, {getDirectory, getName} = {}) {
 //  * directory (or override getDirectory)
 //  * name (or override getName)
 //  * date (or override getDate)
-export function sortChronologically(
-  data,
-  {getDirectory, getName, getDate} = {}
-) {
+export function sortChronologically(data, {
+  getDirectory,
+  getName,
+  getDate,
+} = {}) {
   sortAlphabetically(data, {getDirectory, getName});
   sortByDate(data, {getDate});
   return data;
@@ -296,7 +301,9 @@ export function sortChronologically(
 // release date but can be overridden) above all else.
 //
 // This function also works for data lists which contain only tracks.
-export function sortAlbumsTracksChronologically(data, {getDate} = {}) {
+export function sortAlbumsTracksChronologically(data, {
+  getDate,
+} = {}) {
   // Sort albums before tracks...
   sortByConditions(data, [(t) => t.album === undefined]);
 
@@ -320,9 +327,8 @@ export function sortAlbumsTracksChronologically(data, {getDate} = {}) {
 // Specific data utilities
 
 export function filterAlbumsByCommentary(albums) {
-  return albums.filter((album) =>
-    [album, ...album.tracks].some((x) => x.commentary)
-  );
+  return albums
+    .filter((album) => [album, ...album.tracks].some((x) => x.commentary));
 }
 
 export function getAlbumCover(album, {to}) {
@@ -387,12 +393,7 @@ export function getTrackCover(track, {to}) {
   if (!track.hasCoverArt) {
     return getAlbumCover(track.album, {to});
   } else {
-    return to(
-      'media.trackCover',
-      track.album.directory,
-      track.directory,
-      track.coverArtFileExtension
-    );
+    return to('media.trackCover', track.album.directory, track.directory, track.coverArtFileExtension);
   }
 }
 
@@ -455,11 +456,7 @@ export function getNewAdditions(numAlbums, {wikiData}) {
     const currentDate = sortedAlbums[i].dateAddedToWiki;
     const groupMap = new Map();
     const groupArray = [];
-    for (
-      let album;
-      (album = sortedAlbums[i]) && +album.dateAddedToWiki === +currentDate;
-      i++
-    ) {
+    for (let album; (album = sortedAlbums[i]) && +album.dateAddedToWiki === +currentDate; i++) {
       const primaryGroup = album.groups[0];
       if (groupMap.has(primaryGroup)) {
         groupMap.get(primaryGroup).push(album);
@@ -510,6 +507,7 @@ export function getNewReleases(numReleases, {wikiData}) {
   const latestFirst = albumData
     .filter((album) => album.isListedOnHomepage)
     .reverse();
+
   const majorReleases = latestFirst.filter((album) => album.isMajorRelease);
   majorReleases.splice(1);
 
diff --git a/test/cacheable-object.js b/test/cacheable-object.js
index dd93343f..664a648b 100644
--- a/test/cacheable-object.js
+++ b/test/cacheable-object.js
@@ -5,270 +5,270 @@ import CacheableObject from '../src/data/cacheable-object.js';
 // Utility
 
 function newCacheableObject(PD) {
-    return new (class extends CacheableObject {
-        static propertyDescriptors = PD;
-    });
+  return new (class extends CacheableObject {
+    static propertyDescriptors = PD;
+  });
 }
 
 // Tests
 
 test(`CacheableObject simple separate update & expose`, t => {
-    const obj = newCacheableObject({
-        number: {
-            flags: {
-                update: true
-            }
-        },
-
-        timesTwo: {
-            flags: {
-                expose: true
-            },
-
-            expose: {
-                dependencies: ['number'],
-                compute: ({ number }) => number * 2
-            }
-        }
-    });
+  const obj = newCacheableObject({
+    number: {
+      flags: {
+        update: true
+      }
+    },
+
+    timesTwo: {
+      flags: {
+        expose: true
+      },
+
+      expose: {
+        dependencies: ['number'],
+        compute: ({ number }) => number * 2
+      }
+    }
+  });
 
-    t.plan(1);
-    obj.number = 5;
-    t.equal(obj.timesTwo, 10);
+  t.plan(1);
+  obj.number = 5;
+  t.equal(obj.timesTwo, 10);
 });
 
 test(`CacheableObject basic cache behavior`, t => {
-    let computeCount = 0;
-
-    const obj = newCacheableObject({
-        string: {
-            flags: {
-                update: true
-            }
-        },
-
-        karkat: {
-            flags: {
-                expose: true
-            },
-
-            expose: {
-                dependencies: ['string'],
-                compute: ({ string }) => {
-                    computeCount++;
-                    return string.toUpperCase();
-                }
-            }
+  let computeCount = 0;
+
+  const obj = newCacheableObject({
+    string: {
+      flags: {
+        update: true
+      }
+    },
+
+    karkat: {
+      flags: {
+        expose: true
+      },
+
+      expose: {
+        dependencies: ['string'],
+        compute: ({ string }) => {
+          computeCount++;
+          return string.toUpperCase();
         }
-    });
+      }
+    }
+  });
 
-    t.plan(8);
+  t.plan(8);
 
-    t.is(computeCount, 0);
+  t.is(computeCount, 0);
 
-    obj.string = 'hello world';
-    t.is(computeCount, 0);
+  obj.string = 'hello world';
+  t.is(computeCount, 0);
 
-    obj.karkat;
-    t.is(computeCount, 1);
+  obj.karkat;
+  t.is(computeCount, 1);
 
-    obj.karkat;
-    t.is(computeCount, 1);
+  obj.karkat;
+  t.is(computeCount, 1);
 
-    obj.string = 'testing once again';
-    t.is(computeCount, 1);
+  obj.string = 'testing once again';
+  t.is(computeCount, 1);
 
-    obj.karkat;
-    t.is(computeCount, 2);
+  obj.karkat;
+  t.is(computeCount, 2);
 
-    obj.string = 'testing once again';
-    t.is(computeCount, 2);
+  obj.string = 'testing once again';
+  t.is(computeCount, 2);
 
-    obj.karkat;
-    t.is(computeCount, 2);
+  obj.karkat;
+  t.is(computeCount, 2);
 });
 
 test(`CacheableObject combined update & expose (no transform)`, t => {
-    const obj = newCacheableObject({
-        directory: {
-            flags: {
-                update: true,
-                expose: true
-            }
-        }
-    });
+  const obj = newCacheableObject({
+    directory: {
+      flags: {
+        update: true,
+        expose: true
+      }
+    }
+  });
 
-    t.plan(2);
+  t.plan(2);
 
-    t.directory = 'the-world-revolving';
-    t.is(t.directory, 'the-world-revolving');
+  t.directory = 'the-world-revolving';
+  t.is(t.directory, 'the-world-revolving');
 
-    t.directory = 'chaos-king';
-    t.is(t.directory, 'chaos-king');
+  t.directory = 'chaos-king';
+  t.is(t.directory, 'chaos-king');
 });
 
 test(`CacheableObject combined update & expose (basic transform)`, t => {
-    const obj = newCacheableObject({
-        getsRepeated: {
-            flags: {
-                update: true,
-                expose: true
-            },
-
-            expose: {
-                transform: value => value.repeat(2)
-            }
-        }
-    });
+  const obj = newCacheableObject({
+    getsRepeated: {
+      flags: {
+        update: true,
+        expose: true
+      },
+
+      expose: {
+        transform: value => value.repeat(2)
+      }
+    }
+  });
 
-    t.plan(1);
+  t.plan(1);
 
-    obj.getsRepeated = 'dog';
-    t.is(obj.getsRepeated, 'dogdog');
+  obj.getsRepeated = 'dog';
+  t.is(obj.getsRepeated, 'dogdog');
 });
 
 test(`CacheableObject combined update & expose (transform with dependency)`, t => {
-    const obj = newCacheableObject({
-        customRepeat: {
-            flags: {
-                update: true,
-                expose: true
-            },
-
-            expose: {
-                dependencies: ['times'],
-                transform: (value, { times }) => value.repeat(times)
-            }
-        },
-
-        times: {
-            flags: {
-                update: true
-            }
-        }
-    });
+  const obj = newCacheableObject({
+    customRepeat: {
+      flags: {
+        update: true,
+        expose: true
+      },
+
+      expose: {
+        dependencies: ['times'],
+        transform: (value, { times }) => value.repeat(times)
+      }
+    },
+
+    times: {
+      flags: {
+        update: true
+      }
+    }
+  });
 
-    t.plan(3);
+  t.plan(3);
 
-    obj.customRepeat = 'dog';
-    obj.times = 1;
-    t.is(obj.customRepeat, 'dog');
+  obj.customRepeat = 'dog';
+  obj.times = 1;
+  t.is(obj.customRepeat, 'dog');
 
-    obj.times = 5;
-    t.is(obj.customRepeat, 'dogdogdogdogdog');
+  obj.times = 5;
+  t.is(obj.customRepeat, 'dogdogdogdogdog');
 
-    obj.customRepeat = 'cat';
-    t.is(obj.customRepeat, 'catcatcatcatcat');
+  obj.customRepeat = 'cat';
+  t.is(obj.customRepeat, 'catcatcatcatcat');
 });
 
 test(`CacheableObject validate on update`, t => {
-    const mockError = new TypeError(`Expected a string, not ${typeof value}`);
-
-    const obj = newCacheableObject({
-        directory: {
-            flags: {
-                update: true,
-                expose: true
-            },
-
-            update: {
-                validate: value => {
-                    if (typeof value !== 'string') {
-                        throw mockError;
-                    }
-                    return true;
-                }
-            }
-        },
-
-        date: {
-            flags: {
-                update: true,
-                expose: true
-            },
-
-            update: {
-                validate: value => (value instanceof Date)
-            }
+  const mockError = new TypeError(`Expected a string, not ${typeof value}`);
+
+  const obj = newCacheableObject({
+    directory: {
+      flags: {
+        update: true,
+        expose: true
+      },
+
+      update: {
+        validate: value => {
+          if (typeof value !== 'string') {
+            throw mockError;
+          }
+          return true;
         }
-    });
+      }
+    },
+
+    date: {
+      flags: {
+        update: true,
+        expose: true
+      },
+
+      update: {
+        validate: value => (value instanceof Date)
+      }
+    }
+  });
 
-    let thrownError;
-    t.plan(6);
+  let thrownError;
+  t.plan(6);
 
-    obj.directory = 'megalovania';
-    t.is(obj.directory, 'megalovania');
+  obj.directory = 'megalovania';
+  t.is(obj.directory, 'megalovania');
 
-    try {
-        obj.directory = 25;
-    } catch (err) {
-        thrownError = err;
-    }
+  try {
+    obj.directory = 25;
+  } catch (err) {
+    thrownError = err;
+  }
 
-    t.is(thrownError, mockError);
-    t.is(obj.directory, 'megalovania');
+  t.is(thrownError, mockError);
+  t.is(obj.directory, 'megalovania');
 
-    const date = new Date(`25 December 2009`);
+  const date = new Date(`25 December 2009`);
 
-    obj.date = date;
-    t.is(obj.date, date);
+  obj.date = date;
+  t.is(obj.date, date);
 
-    try {
-        obj.date = `TWELFTH PERIGEE'S EVE`;
-    } catch (err) {
-        thrownError = err;
-    }
+  try {
+    obj.date = `TWELFTH PERIGEE'S EVE`;
+  } catch (err) {
+    thrownError = err;
+  }
 
-    t.is(thrownError?.constructor, TypeError);
-    t.is(obj.date, date);
+  t.is(thrownError?.constructor, TypeError);
+  t.is(obj.date, date);
 });
 
 test(`CacheableObject default update property value`, t => {
-    const obj = newCacheableObject({
-        fruit: {
-            flags: {
-                update: true,
-                expose: true
-            },
-
-            update: {
-                default: 'potassium'
-            }
-        }
-    });
+  const obj = newCacheableObject({
+    fruit: {
+      flags: {
+        update: true,
+        expose: true
+      },
+
+      update: {
+        default: 'potassium'
+      }
+    }
+  });
 
-    t.plan(1);
-    t.is(obj.fruit, 'potassium');
+  t.plan(1);
+  t.is(obj.fruit, 'potassium');
 });
 
 test(`CacheableObject default property throws if invalid`, t => {
-    const mockError = new TypeError(`Expected a string, not ${typeof value}`);
-
-    t.plan(1);
-
-    let thrownError;
-
-    try {
-        newCacheableObject({
-            string: {
-                flags: {
-                    update: true
-                },
-
-                update: {
-                    default: 123,
-                    validate: value => {
-                        if (typeof value !== 'string') {
-                            throw mockError;
-                        }
-                        return true;
-                    }
-                }
+  const mockError = new TypeError(`Expected a string, not ${typeof value}`);
+
+  t.plan(1);
+
+  let thrownError;
+
+  try {
+    newCacheableObject({
+      string: {
+        flags: {
+          update: true
+        },
+
+        update: {
+          default: 123,
+          validate: value => {
+            if (typeof value !== 'string') {
+              throw mockError;
             }
-        });
-    } catch (err) {
-        thrownError = err;
-    }
+            return true;
+          }
+        }
+      }
+    });
+  } catch (err) {
+    thrownError = err;
+  }
 
-    t.is(thrownError, mockError);
+  t.is(thrownError, mockError);
 });
diff --git a/test/data-validators.js b/test/data-validators.js
index a7b9b48d..f13f3f0f 100644
--- a/test/data-validators.js
+++ b/test/data-validators.js
@@ -2,41 +2,41 @@ import _test from 'tape';
 import { showAggregate } from '../src/util/sugar.js';
 
 import {
-    // Basic types
-    isBoolean,
-    isCountingNumber,
-    isNumber,
-    isString,
-    isStringNonEmpty,
-
-    // Complex types
-    isArray,
-    isObject,
-    validateArrayItems,
-
-    // Wiki data
-    isDimensions,
-    isDirectory,
-    isDuration,
-    isFileExtension,
-    validateReference,
-    validateReferenceList,
-
-    // Compositional utilities
-    oneOf,
+  // Basic types
+  isBoolean,
+  isCountingNumber,
+  isNumber,
+  isString,
+  isStringNonEmpty,
+
+  // Complex types
+  isArray,
+  isObject,
+  validateArrayItems,
+
+  // Wiki data
+  isDimensions,
+  isDirectory,
+  isDuration,
+  isFileExtension,
+  validateReference,
+  validateReferenceList,
+
+  // Compositional utilities
+  oneOf,
 } from '../src/data/validators.js';
 
 function test(msg, fn) {
-    _test(msg, t => {
-        try {
-            fn(t);
-        } catch (error) {
-            if (error instanceof AggregateError) {
-                showAggregate(error);
-            }
-            throw error;
-        }
-    });
+  _test(msg, t => {
+    try {
+      fn(t);
+    } catch (error) {
+      if (error instanceof AggregateError) {
+        showAggregate(error);
+      }
+      throw error;
+    }
+  });
 }
 
 test.skip = _test.skip;
@@ -44,234 +44,234 @@ test.skip = _test.skip;
 // Basic types
 
 test('isBoolean', t => {
-    t.plan(4);
-    t.ok(isBoolean(true));
-    t.ok(isBoolean(false));
-    t.throws(() => isBoolean(1), TypeError);
-    t.throws(() => isBoolean('yes'), TypeError);
+  t.plan(4);
+  t.ok(isBoolean(true));
+  t.ok(isBoolean(false));
+  t.throws(() => isBoolean(1), TypeError);
+  t.throws(() => isBoolean('yes'), TypeError);
 });
 
 test('isNumber', t => {
-    t.plan(6);
-    t.ok(isNumber(123));
-    t.ok(isNumber(0.05));
-    t.ok(isNumber(0));
-    t.ok(isNumber(-10));
-    t.throws(() => isNumber('413'), TypeError);
-    t.throws(() => isNumber(true), TypeError);
+  t.plan(6);
+  t.ok(isNumber(123));
+  t.ok(isNumber(0.05));
+  t.ok(isNumber(0));
+  t.ok(isNumber(-10));
+  t.throws(() => isNumber('413'), TypeError);
+  t.throws(() => isNumber(true), TypeError);
 });
 
 test('isCountingNumber', t => {
-    t.plan(6);
-    t.ok(isCountingNumber(3));
-    t.ok(isCountingNumber(1));
-    t.throws(() => isCountingNumber(1.75), TypeError);
-    t.throws(() => isCountingNumber(0), TypeError);
-    t.throws(() => isCountingNumber(-1), TypeError);
-    t.throws(() => isCountingNumber('612'), TypeError);
+  t.plan(6);
+  t.ok(isCountingNumber(3));
+  t.ok(isCountingNumber(1));
+  t.throws(() => isCountingNumber(1.75), TypeError);
+  t.throws(() => isCountingNumber(0), TypeError);
+  t.throws(() => isCountingNumber(-1), TypeError);
+  t.throws(() => isCountingNumber('612'), TypeError);
 });
 
 test('isString', t => {
-    t.plan(3);
-    t.ok(isString('hello!'));
-    t.ok(isString(''));
-    t.throws(() => isString(100), TypeError);
+  t.plan(3);
+  t.ok(isString('hello!'));
+  t.ok(isString(''));
+  t.throws(() => isString(100), TypeError);
 });
 
 test('isStringNonEmpty', t => {
-    t.plan(4);
-    t.ok(isStringNonEmpty('hello!'));
-    t.throws(() => isStringNonEmpty(''), TypeError);
-    t.throws(() => isStringNonEmpty('     '), TypeError);
-    t.throws(() => isStringNonEmpty(100), TypeError);
+  t.plan(4);
+  t.ok(isStringNonEmpty('hello!'));
+  t.throws(() => isStringNonEmpty(''), TypeError);
+  t.throws(() => isStringNonEmpty('     '), TypeError);
+  t.throws(() => isStringNonEmpty(100), TypeError);
 });
 
 // Complex types
 
 test('isArray', t => {
-    t.plan(3);
-    t.ok(isArray([]));
-    t.throws(() => isArray({}), TypeError);
-    t.throws(() => isArray('1, 2, 3'), TypeError);
+  t.plan(3);
+  t.ok(isArray([]));
+  t.throws(() => isArray({}), TypeError);
+  t.throws(() => isArray('1, 2, 3'), TypeError);
 });
 
 test.skip('isDate', t => {
-    // TODO
+  // TODO
 });
 
 test('isObject', t => {
-    t.plan(3);
-    t.ok(isObject({}));
-    t.ok(isObject([]));
-    t.throws(() => isObject(null), TypeError);
+  t.plan(3);
+  t.ok(isObject({}));
+  t.ok(isObject([]));
+  t.throws(() => isObject(null), TypeError);
 });
 
 test('validateArrayItems', t => {
-    t.plan(6);
-
-    t.ok(validateArrayItems(isNumber)([3, 4, 5]));
-    t.ok(validateArrayItems(validateArrayItems(isNumber))([[3, 4], [4, 5], [6, 7]]));
-
-    let caughtError = null;
-    try {
-        validateArrayItems(isNumber)([10, 20, 'one hundred million consorts', 30]);
-    } catch (err) {
-        caughtError = err;
-    }
-
-    t.isNot(caughtError, null);
-    t.true(caughtError instanceof AggregateError);
-    t.is(caughtError.errors.length, 1);
-    t.true(caughtError.errors[0] instanceof TypeError);
+  t.plan(6);
+
+  t.ok(validateArrayItems(isNumber)([3, 4, 5]));
+  t.ok(validateArrayItems(validateArrayItems(isNumber))([[3, 4], [4, 5], [6, 7]]));
+
+  let caughtError = null;
+  try {
+    validateArrayItems(isNumber)([10, 20, 'one hundred million consorts', 30]);
+  } catch (err) {
+    caughtError = err;
+  }
+
+  t.isNot(caughtError, null);
+  t.true(caughtError instanceof AggregateError);
+  t.is(caughtError.errors.length, 1);
+  t.true(caughtError.errors[0] instanceof TypeError);
 });
 
 // Wiki data
 
 test.skip('isColor', t => {
-    // TODO
+  // TODO
 });
 
 test.skip('isCommentary', t => {
-    // TODO
+  // TODO
 });
 
 test.skip('isContribution', t => {
-    // TODO
+  // TODO
 });
 
 test.skip('isContributionList', t => {
-    // TODO
+  // TODO
 });
 
 test('isDimensions', t => {
-    t.plan(6);
-    t.ok(isDimensions([1, 1]));
-    t.ok(isDimensions([50, 50]));
-    t.ok(isDimensions([5000, 1]));
-    t.throws(() => isDimensions([1]), TypeError);
-    t.throws(() => isDimensions([413, 612, 1025]), TypeError);
-    t.throws(() => isDimensions('800x200'), TypeError);
+  t.plan(6);
+  t.ok(isDimensions([1, 1]));
+  t.ok(isDimensions([50, 50]));
+  t.ok(isDimensions([5000, 1]));
+  t.throws(() => isDimensions([1]), TypeError);
+  t.throws(() => isDimensions([413, 612, 1025]), TypeError);
+  t.throws(() => isDimensions('800x200'), TypeError);
 });
 
 test('isDirectory', t => {
-    t.plan(6);
-    t.ok(isDirectory('savior-of-the-waking-world'));
-    t.ok(isDirectory('MeGaLoVania'));
-    t.ok(isDirectory('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'));
-    t.throws(() => isDirectory(123), TypeError);
-    t.throws(() => isDirectory(''), TypeError);
-    t.throws(() => isDirectory('troll saint nicholas and the quest for the holy pail'), TypeError);
+  t.plan(6);
+  t.ok(isDirectory('savior-of-the-waking-world'));
+  t.ok(isDirectory('MeGaLoVania'));
+  t.ok(isDirectory('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'));
+  t.throws(() => isDirectory(123), TypeError);
+  t.throws(() => isDirectory(''), TypeError);
+  t.throws(() => isDirectory('troll saint nicholas and the quest for the holy pail'), TypeError);
 });
 
 test('isDuration', t => {
-    t.plan(5);
-    t.ok(isDuration(60));
-    t.ok(isDuration(0.02));
-    t.ok(isDuration(0));
-    t.throws(() => isDuration(-1), TypeError);
-    t.throws(() => isDuration('10:25'), TypeError);
+  t.plan(5);
+  t.ok(isDuration(60));
+  t.ok(isDuration(0.02));
+  t.ok(isDuration(0));
+  t.throws(() => isDuration(-1), TypeError);
+  t.throws(() => isDuration('10:25'), TypeError);
 });
 
 test('isFileExtension', t => {
-    t.plan(6);
-    t.ok(isFileExtension('png'));
-    t.ok(isFileExtension('jpg'));
-    t.ok(isFileExtension('sub_loc'));
-    t.throws(() => isFileExtension(''), TypeError);
-    t.throws(() => isFileExtension('.jpg'), TypeError);
-    t.throws(() => isFileExtension('just an image bro!!!!'), TypeError);
+  t.plan(6);
+  t.ok(isFileExtension('png'));
+  t.ok(isFileExtension('jpg'));
+  t.ok(isFileExtension('sub_loc'));
+  t.throws(() => isFileExtension(''), TypeError);
+  t.throws(() => isFileExtension('.jpg'), TypeError);
+  t.throws(() => isFileExtension('just an image bro!!!!'), TypeError);
 });
 
 test.skip('isName', t => {
-    // TODO
+  // TODO
 });
 
 test.skip('isURL', t => {
-    // TODO
+  // TODO
 });
 
 test('validateReference', t => {
-    t.plan(16);
-
-    const typeless = validateReference();
-    const track = validateReference('track');
-    const album = validateReference('album');
-
-    t.ok(track('track:doctor'));
-    t.ok(track('track:MeGaLoVania'));
-    t.ok(track('Showtime (Imp Strife Mix)'));
-    t.throws(() => track('track:troll saint nic'), TypeError);
-    t.throws(() => track('track:'), TypeError);
-    t.throws(() => track('album:homestuck-vol-1'), TypeError);
-
-    t.ok(album('album:sburb'));
-    t.ok(album('album:the-wanderers'));
-    t.ok(album('Homestuck Vol. 8'));
-    t.throws(() => album('album:Hiveswap Friendsim'), TypeError);
-    t.throws(() => album('album:'), TypeError);
-    t.throws(() => album('track:showtime-piano-refrain'), TypeError);
-
-    t.ok(typeless('Hopes and Dreams'));
-    t.ok(typeless('track:snowdin-town'));
-    t.throws(() => typeless(''), TypeError);
-    t.throws(() => typeless('album:undertale-soundtrack'));
+  t.plan(16);
+
+  const typeless = validateReference();
+  const track = validateReference('track');
+  const album = validateReference('album');
+
+  t.ok(track('track:doctor'));
+  t.ok(track('track:MeGaLoVania'));
+  t.ok(track('Showtime (Imp Strife Mix)'));
+  t.throws(() => track('track:troll saint nic'), TypeError);
+  t.throws(() => track('track:'), TypeError);
+  t.throws(() => track('album:homestuck-vol-1'), TypeError);
+
+  t.ok(album('album:sburb'));
+  t.ok(album('album:the-wanderers'));
+  t.ok(album('Homestuck Vol. 8'));
+  t.throws(() => album('album:Hiveswap Friendsim'), TypeError);
+  t.throws(() => album('album:'), TypeError);
+  t.throws(() => album('track:showtime-piano-refrain'), TypeError);
+
+  t.ok(typeless('Hopes and Dreams'));
+  t.ok(typeless('track:snowdin-town'));
+  t.throws(() => typeless(''), TypeError);
+  t.throws(() => typeless('album:undertale-soundtrack'));
 });
 
 test('validateReferenceList', t => {
-    const track = validateReferenceList('track');
-    const artist = validateReferenceList('artist');
-
-    t.plan(9);
-
-    t.ok(track(['track:fallen-down', 'Once Upon a Time']));
-    t.ok(artist(['artist:toby-fox', 'Mark Hadley']));
-    t.ok(track(['track:amalgam']));
-    t.ok(track([]));
-
-    let caughtError = null;
-    try {
-        track(['Dog', 'album:vaporwave-2016', 'Cat', 'artist:john-madden']);
-    } catch (err) {
-        caughtError = err;
-    }
-
-    t.isNot(caughtError, null);
-    t.true(caughtError instanceof AggregateError);
-    t.is(caughtError.errors.length, 2);
-    t.true(caughtError.errors[0] instanceof TypeError);
-    t.true(caughtError.errors[1] instanceof TypeError);
+  const track = validateReferenceList('track');
+  const artist = validateReferenceList('artist');
+
+  t.plan(9);
+
+  t.ok(track(['track:fallen-down', 'Once Upon a Time']));
+  t.ok(artist(['artist:toby-fox', 'Mark Hadley']));
+  t.ok(track(['track:amalgam']));
+  t.ok(track([]));
+
+  let caughtError = null;
+  try {
+    track(['Dog', 'album:vaporwave-2016', 'Cat', 'artist:john-madden']);
+  } catch (err) {
+    caughtError = err;
+  }
+
+  t.isNot(caughtError, null);
+  t.true(caughtError instanceof AggregateError);
+  t.is(caughtError.errors.length, 2);
+  t.true(caughtError.errors[0] instanceof TypeError);
+  t.true(caughtError.errors[1] instanceof TypeError);
 });
 
 test('oneOf', t => {
-    t.plan(11);
+  t.plan(11);
 
-    const isStringOrNumber = oneOf(isString, isNumber);
+  const isStringOrNumber = oneOf(isString, isNumber);
 
-    t.ok(isStringOrNumber('hello world'));
-    t.ok(isStringOrNumber(42));
-    t.throws(() => isStringOrNumber(false));
+  t.ok(isStringOrNumber('hello world'));
+  t.ok(isStringOrNumber(42));
+  t.throws(() => isStringOrNumber(false));
 
-    const mockError = new Error();
-    const neverSucceeds = () => {
-        throw mockError;
-    };
+  const mockError = new Error();
+  const neverSucceeds = () => {
+    throw mockError;
+  };
 
-    const isStringOrGetRekt = oneOf(isString, neverSucceeds);
+  const isStringOrGetRekt = oneOf(isString, neverSucceeds);
 
-    t.ok(isStringOrGetRekt('phew!'));
+  t.ok(isStringOrGetRekt('phew!'));
 
-    let caughtError = null;
-    try {
-        isStringOrGetRekt(0xdeadbeef);
-    } catch (err) {
-        caughtError = err;
-    }
+  let caughtError = null;
+  try {
+    isStringOrGetRekt(0xdeadbeef);
+  } catch (err) {
+    caughtError = err;
+  }
 
-    t.isNot(caughtError, null);
-    t.true(caughtError instanceof AggregateError);
-    t.is(caughtError.errors.length, 2);
-    t.true(caughtError.errors[0] instanceof TypeError);
-    t.is(caughtError.errors[0].check, isString);
-    t.is(caughtError.errors[1], mockError);
-    t.is(caughtError.errors[1].check, neverSucceeds);
+  t.isNot(caughtError, null);
+  t.true(caughtError instanceof AggregateError);
+  t.is(caughtError.errors.length, 2);
+  t.true(caughtError.errors[0] instanceof TypeError);
+  t.is(caughtError.errors[0].check, isString);
+  t.is(caughtError.errors[1], mockError);
+  t.is(caughtError.errors[1].check, neverSucceeds);
 });
diff --git a/test/things.js b/test/things.js
index 7274515c..f36a4995 100644
--- a/test/things.js
+++ b/test/things.js
@@ -1,71 +1,71 @@
 import test from 'tape';
 
 import {
-    Album,
-    Thing,
-    Track,
-    TrackGroup,
+  Album,
+  Thing,
+  Track,
+  TrackGroup,
 } from '../src/data/things.js';
 
 function stubAlbum(tracks) {
-    const album = new Album();
-    const trackGroup = new TrackGroup();
-    trackGroup.tracksByRef = tracks.map(t => Thing.getReference(t));
-    album.trackGroups = [trackGroup];
-    album.trackData = tracks;
-    return album;
+  const album = new Album();
+  const trackGroup = new TrackGroup();
+  trackGroup.tracksByRef = tracks.map(t => Thing.getReference(t));
+  album.trackGroups = [trackGroup];
+  album.trackData = tracks;
+  return album;
 }
 
 test(`Track.coverArtDate`, t => {
-    t.plan(5);
+  t.plan(5);
 
-    // Priority order is as follows, with the last (trackCoverArtDate) being
-    // greatest priority.
-    const albumDate = new Date('2010-10-10');
-    const albumTrackArtDate = new Date('2012-12-12');
-    const trackDateFirstReleased = new Date('2008-08-08');
-    const trackCoverArtDate = new Date('2009-09-09');
+  // Priority order is as follows, with the last (trackCoverArtDate) being
+  // greatest priority.
+  const albumDate = new Date('2010-10-10');
+  const albumTrackArtDate = new Date('2012-12-12');
+  const trackDateFirstReleased = new Date('2008-08-08');
+  const trackCoverArtDate = new Date('2009-09-09');
 
-    const track = new Track();
-    track.directory = 'foo';
+  const track = new Track();
+  track.directory = 'foo';
 
-    const album = stubAlbum([track]);
+  const album = stubAlbum([track]);
 
-    track.albumData = [album];
+  track.albumData = [album];
 
-    // 1. coverArtDate defaults to null
+  // 1. coverArtDate defaults to null
 
-    t.is(track.coverArtDate, null);
+  t.is(track.coverArtDate, null);
 
-    // 2. coverArtDate inherits album release date
+  // 2. coverArtDate inherits album release date
 
-    album.date = albumDate;
+  album.date = albumDate;
 
-    // XXX clear cache so change in album's property is reflected
-    track.albumData = [];
-    track.albumData = [album];
+  // XXX clear cache so change in album's property is reflected
+  track.albumData = [];
+  track.albumData = [album];
 
-    t.is(track.coverArtDate, albumDate);
+  t.is(track.coverArtDate, albumDate);
 
-    // 3. coverArtDate inherits album trackArtDate
+  // 3. coverArtDate inherits album trackArtDate
 
-    album.trackArtDate = albumTrackArtDate;
+  album.trackArtDate = albumTrackArtDate;
 
-    // XXX clear cache again
-    track.albumData = [];
-    track.albumData = [album];
+  // XXX clear cache again
+  track.albumData = [];
+  track.albumData = [album];
 
-    t.is(track.coverArtDate, albumTrackArtDate);
+  t.is(track.coverArtDate, albumTrackArtDate);
 
-    // 4. coverArtDate is overridden dateFirstReleased
+  // 4. coverArtDate is overridden dateFirstReleased
 
-    track.dateFirstReleased = trackDateFirstReleased;
+  track.dateFirstReleased = trackDateFirstReleased;
 
-    t.is(track.coverArtDate, trackDateFirstReleased);
+  t.is(track.coverArtDate, trackDateFirstReleased);
 
-    // 5. coverArtDate is overridden coverArtDate
+  // 5. coverArtDate is overridden coverArtDate
 
-    track.coverArtDate = trackCoverArtDate;
+  track.coverArtDate = trackCoverArtDate;
 
-    t.is(track.coverArtDate, trackCoverArtDate);
+  t.is(track.coverArtDate, trackCoverArtDate);
 });