« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/write
diff options
context:
space:
mode:
Diffstat (limited to 'src/write')
-rw-r--r--src/write/bind-utilities.js2
-rw-r--r--src/write/build-modes/index.js1
-rw-r--r--src/write/build-modes/live-dev-server.js31
-rw-r--r--src/write/build-modes/static-build.js101
-rw-r--r--src/write/tidy-modes/format-urls.js34
-rw-r--r--src/write/tidy-modes/index.js2
-rw-r--r--src/write/tidy-modes/sort.js (renamed from src/write/build-modes/sort.js)69
7 files changed, 150 insertions, 90 deletions
diff --git a/src/write/bind-utilities.js b/src/write/bind-utilities.js
index d55ab215..afbf8b2f 100644
--- a/src/write/bind-utilities.js
+++ b/src/write/bind-utilities.js
@@ -24,6 +24,7 @@ export function bindUtilities({
   language,
   languages,
   missingImagePaths,
+  niceShowAggregate,
   pagePath,
   pagePathStringFromRoot,
   thumbsCache,
@@ -42,6 +43,7 @@ export function bindUtilities({
     language,
     languages,
     missingImagePaths,
+    niceShowAggregate,
     pagePath,
     pagePathStringFromRoot,
     thumb,
diff --git a/src/write/build-modes/index.js b/src/write/build-modes/index.js
index 4b61619d..3ae2cfc6 100644
--- a/src/write/build-modes/index.js
+++ b/src/write/build-modes/index.js
@@ -1,4 +1,3 @@
 export * as 'live-dev-server' from './live-dev-server.js';
 export * as 'repl' from './repl.js';
-export * as 'sort' from './sort.js';
 export * as 'static-build' from './static-build.js';
diff --git a/src/write/build-modes/live-dev-server.js b/src/write/build-modes/live-dev-server.js
index cd84dcc0..aebdb234 100644
--- a/src/write/build-modes/live-dev-server.js
+++ b/src/write/build-modes/live-dev-server.js
@@ -1,6 +1,7 @@
 import {spawn} from 'node:child_process';
 import * as http from 'node:http';
 import {open, stat} from 'node:fs/promises';
+import * as os from 'node:os';
 import * as path from 'node:path';
 import {pipeline} from 'node:stream/promises';
 import {inspect as nodeInspect} from 'node:util';
@@ -141,7 +142,7 @@ export async function go({
   const showError = (error) => {
     if (niceShowAggregate) {
       if (error.errors || error.cause) {
-        niceShowAggregate(error);
+        niceShowAggregate(error, {showTraces: true});
         return;
       }
     }
@@ -202,7 +203,7 @@ export async function go({
   try {
     pathAggregate.close();
   } catch (error) {
-    niceShowAggregate(error);
+    niceShowAggregate(error, {showTraces: true});
     logWarn`Failed to compute page paths for some targets.`;
     logWarn`This means some pages that normally exist will be 404s.`;
     fileIssue();
@@ -252,7 +253,7 @@ export async function go({
     let url;
     try {
       url = new URL(request.url, `http://${request.headers.host}`);
-    } catch (error) {
+    } catch {
       response.writeHead(500, contentTypePlain);
       response.end('Failed to parse request URL\n');
       return;
@@ -299,7 +300,7 @@ export async function go({
       let filePath;
       try {
         filePath = path.resolve(localDirectory, decodeURI(safePath.split('/').join(path.sep)));
-      } catch (error) {
+      } catch {
         response.writeHead(404, contentTypePlain);
         response.end(`File not found for: ${safePath}`);
         console.log(`${requestHead} [404] ${pathname}`);
@@ -355,6 +356,10 @@ export async function go({
         if (error.code === 'ERR_STREAM_PREMATURE_CLOSE') {
           // Connection was dropped, this is OK.
           return;
+        } else if (error.code === 'ERR_STREAM_UNABLE_TO_PIPE') {
+          // "Cannot pipe to a closed or destroyed stream"
+          // Sus-amongus networking error. Give up, it's probably OK.
+          return;
         } else {
           throw error;
         }
@@ -492,7 +497,13 @@ export async function go({
     }
   });
 
-  const address = `http://${host}:${port}/`;
+  const addresses =
+    (host === '0.0.0.0'
+      ? [`http://localhost:${port}/`,
+         `http://${os.hostname()}:${port}/`]
+   : host === '127.0.0.1'
+      ? [`http://localhost:${port}/`]
+      : [`http://${host}:${port}/`]);
 
   server.on('error', error => {
     if (error.code === 'EADDRINUSE') {
@@ -509,7 +520,15 @@ export async function go({
   });
 
   server.on('listening', () => {
-    logInfo`${'All done!'} Listening at: ${address}`;
+    if (addresses.length === 1) {
+      logInfo`${'All done!'} Listening at: ${addresses[0]}`;
+    } else {
+      logInfo`${`All done!`} Listening at:`;
+      for (const address of addresses) {
+        logInfo`- ${address}`;
+      }
+    }
+
     logInfo`Press ^C here (control+C) to stop the server and exit.`;
     if (showTimings && loudResponses) {
       logInfo`Printing all HTTP responses, plus page generation timings.`;
diff --git a/src/write/build-modes/static-build.js b/src/write/build-modes/static-build.js
index 2baed816..71553de3 100644
--- a/src/write/build-modes/static-build.js
+++ b/src/write/build-modes/static-build.js
@@ -1,14 +1,7 @@
 import * as path from 'node:path';
 
-import {
-  copyFile,
-  cp,
-  mkdir,
-  stat,
-  symlink,
-  writeFile,
-  unlink,
-} from 'node:fs/promises';
+import {cp, mkdir, readFile, stat, symlink, writeFile, unlink}
+  from 'node:fs/promises';
 
 import {rimraf} from 'rimraf';
 
@@ -95,6 +88,11 @@ export function getCLIOptions() {
       type: 'value',
     },
 
+    'paths': {
+      help: `Skip rest and build only pages matching paths in this text file`,
+      type: 'value',
+    },
+
     // NOT for neatly ena8ling or disa8ling specific features of the site!
     // This is only in charge of what general groups of files to write.
     // They're here to make development quicker when you're only working
@@ -115,8 +113,6 @@ export async function go({
 
   universalUtilities,
 
-  mediaPath,
-
   defaultLanguage,
   languages,
   urls,
@@ -128,6 +124,7 @@ export async function go({
   const outputPath = cliOptions['out-path'] || process.env.HSMUSIC_OUT;
   const appendIndexHTML = cliOptions['append-index-html'] ?? false;
   const writeOneLanguage = cliOptions['lang'] ?? null;
+  const pathsFromFile = cliOptions['paths'] ?? null;
 
   if (!outputPath) {
     logError`Expected ${'--out-path'} option or ${'HSMUSIC_OUT'} to be set`;
@@ -147,6 +144,36 @@ export async function go({
     logInfo`Writing all languages.`;
   }
 
+  let filterPaths = null;
+  if (pathsFromFile) {
+    let pathsText;
+    try {
+      pathsText = await readFile(pathsFromFile, 'utf8');
+    } catch (error) {
+      logError`Failed to read file specified in ${'--paths'}:`;
+      logError`${error.code}: ${pathsFromFile}`;
+      return false;
+    }
+
+    filterPaths = pathsText.split('\n').filter(Boolean);
+
+    if (empty(filterPaths)) {
+      logWarn`Specified to build only paths in file ${'--paths'}:`;
+      logWarn`${pathsFromFile}`;
+      logWarn`But this file is totally empty...`;
+    }
+
+    if (filterPaths.some(path => !path.startsWith('/'))) {
+      logError`All lines in ${'--paths'} file should start with slash ('${'/'}')`;
+      logError`These lines don't:`;
+      console.error(filterPaths.filter(path => !path.startsWith('/')).join('\n'));
+      logError`Please check file contents, or specified path, and try again.`;
+      return false;
+    }
+
+    logInfo`Writing ${filterPaths.length} paths specified in: ${pathsFromFile} (${'--paths'})`;
+  }
+
   const selectedPageFlags = Object.keys(cliOptions)
     .filter(key => pageFlags.includes(key));
 
@@ -166,11 +193,6 @@ export async function go({
   });
 
   if (writeAll) {
-    await writeFavicon({
-      mediaPath,
-      outputPath,
-    });
-
     await writeSharedFilesAndPages({
       outputPath,
       randomLinkDataJSON: generateRandomLinkDataJSON({wikiData}),
@@ -225,6 +247,27 @@ export async function go({
           // TODO: Validate each pathsForTargets entry
         }
 
+        if (!empty(filterPaths)) {
+          paths =
+            paths.filter(path =>
+              filterPaths.includes(
+                (path.type === 'page'
+                  ? '/' +
+                    getPagePathname({
+                      baseDirectory: '',
+                      pagePath: path.path,
+                      urls,
+                    })
+               : path.type === 'redirect'
+                  ? '/' +
+                    getPagePathname({
+                      baseDirectory: '',
+                      pagePath: path.fromPath,
+                      urls,
+                    })
+                  : null)));
+        }
+
         paths =
           paths.filter(path => path.condition?.() ?? true);
 
@@ -342,7 +385,7 @@ export async function go({
           ({pageHTML, oEmbedJSON} = html.resolve(topLevelResult));
         } catch (error) {
           logError`\rError generating page: ${pathname}`;
-          niceShowAggregate(error);
+          niceShowAggregate(error, {showTraces: true});
           errored = true;
           return;
         }
@@ -595,30 +638,6 @@ async function writeWebRouteCopies({
   }
 }
 
-async function writeFavicon({
-  mediaPath,
-  outputPath,
-}) {
-  const faviconFile = 'favicon.ico';
-
-  try {
-    await stat(path.join(mediaPath, faviconFile));
-  } catch (error) {
-    return;
-  }
-
-  try {
-    await copyFile(
-      path.join(mediaPath, faviconFile),
-      path.join(outputPath, faviconFile));
-  } catch (error) {
-    logWarn`Failed to copy favicon! ${error.message}`;
-    return;
-  }
-
-  logInfo`Copied favicon to site root.`;
-}
-
 async function writeSharedFilesAndPages({
   outputPath,
   randomLinkDataJSON,
diff --git a/src/write/tidy-modes/format-urls.js b/src/write/tidy-modes/format-urls.js
new file mode 100644
index 00000000..5771fe3e
--- /dev/null
+++ b/src/write/tidy-modes/format-urls.js
@@ -0,0 +1,34 @@
+export const description = `Update data files in-place to satisfy formatting rules for curated URLs`;
+
+import {logInfo} from '#cli';
+import {reformatCuratedURLs} from '#reformat-urls';
+
+export async function go({
+  dataPath,
+  tidyingOnly,
+}) {
+  const changedFiles =
+    await reformatCuratedURLs({
+      dataPath,
+      showChangedFiles: true,
+      showSatisfiedRules: tidyingOnly,
+    });
+
+  if (changedFiles.size === 0) {
+    if (tidyingOnly) {
+      logInfo`All URL formatting rules were already satisfied. Good to go!`;
+      return 'clean';
+    } else {
+      logInfo`All curated URL formatting rules are satisfied - nice!`;
+      return 'clean';
+    }
+  } else {
+    const filesPart =
+      (changedFiles.size === 1
+        ? `1 file`
+        : `${changedFiles.size} files`);
+
+    logInfo`Updated ${filesPart} to satisfy URL formatting rules.`;
+    return 'updated';
+  }
+}
diff --git a/src/write/tidy-modes/index.js b/src/write/tidy-modes/index.js
new file mode 100644
index 00000000..54e2bbf3
--- /dev/null
+++ b/src/write/tidy-modes/index.js
@@ -0,0 +1,2 @@
+export * as 'format-urls' from './format-urls.js';
+export * as 'sort' from './sort.js';
diff --git a/src/write/build-modes/sort.js b/src/write/tidy-modes/sort.js
index 1a738ac8..967a5be1 100644
--- a/src/write/build-modes/sort.js
+++ b/src/write/tidy-modes/sort.js
@@ -4,48 +4,34 @@ import {logInfo} from '#cli';
 import {empty} from '#sugar';
 import thingConstructors from '#things';
 
-export const config = {
-  fileSizes: {
-    applicable: false,
-  },
-
-  languageReloading: {
-    applicable: false,
-  },
-
-  mediaValidation: {
-    applicable: false,
-  },
-
-  search: {
-    applicable: false,
-  },
-
-  thumbs: {
-    applicable: false,
-  },
+export async function go({
+  wikiData,
+  dataPath,
+  tidyingOnly,
+}) {
+  if (empty(wikiData.sortingRules)) {
+    if (tidyingOnly) {
+      logInfo`There aren't any sorting rules in for this wiki.`;
+    }
 
-  webRoutes: {
-    applicable: false,
-  },
+    return 'clean';
+  }
 
-  sort: {
-    applicable: false,
-  },
-};
+  const {SortingRule} = thingConstructors;
 
-export function getCLIOptions() {
-  return {};
-}
+  if (!tidyingOnly) {
+    const results =
+      await Array.fromAsync(SortingRule.go({dataPath, wikiData}));
 
-export async function go({wikiData, dataPath}) {
-  if (empty(wikiData.sortingRules)) {
-    logInfo`There aren't any sorting rules in for this wiki.`;
-    return true;
+    if (results.some(result => result.changed)) {
+      logInfo`Updated data files to satisfy sorting.`;
+      return 'updated';
+    } else {
+      logInfo`All sorting rules are satisfied - nice!`;
+      return 'clean';
+    }
   }
 
-  const {SortingRule} = thingConstructors;
-
   let numUpdated = 0;
   let numActive = 0;
 
@@ -56,7 +42,7 @@ export async function go({wikiData, dataPath}) {
 
     if (result.changed) {
       numUpdated++;
-      logInfo`Updating to satisfy ${niceMessage}.`;
+      logInfo`Updated to satisfy ${niceMessage}.`;
     } else {
       logInfo`Already good: ${niceMessage}`;
     }
@@ -64,13 +50,12 @@ export async function go({wikiData, dataPath}) {
 
   if (numUpdated > 1) {
     logInfo`Updated data files to satisfy ${numUpdated} sorting rules.`;
+    return 'updated';
   } else if (numUpdated === 1) {
     logInfo`Updated data files to satisfy ${1} sorting rule.`
-  } else if (numActive >= 1) {
-    logInfo`All sorting rules were already satisfied. Good to go!`;
+    return 'updated';
   } else {
-    logInfo`No sorting rules are currently active.`;
+    logInfo`All sorting rules were already satisfied. Good to go!`;
+    return 'clean';
   }
-
-  return true;
 }