diff options
Diffstat (limited to 'src/write/build-modes/static-build.js')
-rw-r--r-- | src/write/build-modes/static-build.js | 186 |
1 files changed, 136 insertions, 50 deletions
diff --git a/src/write/build-modes/static-build.js b/src/write/build-modes/static-build.js index 68cf0949..b5ded04c 100644 --- a/src/write/build-modes/static-build.js +++ b/src/write/build-modes/static-build.js @@ -1,13 +1,7 @@ +import {cp, mkdir, stat, symlink, writeFile, unlink} from 'node:fs/promises'; import * as path from 'node:path'; -import { - copyFile, - mkdir, - stat, - symlink, - writeFile, - unlink, -} from 'node:fs/promises'; +import {rimraf} from 'rimraf'; import {quickLoadContentDependencies} from '#content-dependencies'; import {quickEvaluate} from '#content-function'; @@ -24,6 +18,7 @@ import { } from '#cli'; import { + getOrigin, getPagePathname, getURLsFrom, getURLsFromRoot, @@ -49,6 +44,10 @@ export const config = { default: 'perform', }, + search: { + default: 'perform', + }, + thumbs: { default: 'perform', }, @@ -103,22 +102,16 @@ export function getCLIOptions() { export async function go({ cliOptions, - mediaPath, queueSize, + universalUtilities, + defaultLanguage, languages, - missingImagePaths, - srcRootPath, - thumbsCache, urls, webRoutes, wikiData, - cachebust, - developersComment: _developersComment, - getSizeOfAdditionalFile, - getSizeOfImagePath, niceShowAggregate, }) { const outputPath = cliOptions['out-path'] || process.env.HSMUSIC_OUT; @@ -156,12 +149,12 @@ export async function go({ webRoutes, }); - if (writeAll) { - await writeFavicon({ - mediaPath, - outputPath, - }); + await writeWebRouteCopies({ + outputPath, + webRoutes, + }); + if (writeAll) { await writeSharedFilesAndPages({ outputPath, randomLinkDataJSON: generateRandomLinkDataJSON({wikiData}), @@ -186,7 +179,7 @@ export async function go({ return null; } - const paths = []; + let paths = []; if (pageSpec.pathsTargetless) { const result = pageSpec.pathsTargetless({wikiData}); @@ -216,6 +209,9 @@ export async function go({ // TODO: Validate each pathsForTargets entry } + paths = + paths.filter(path => path.condition?.() ?? true); + return paths; }) .filter(Boolean) @@ -277,6 +273,8 @@ export async function go({ showAggregate: niceShowAggregate, }); + const commonUtilities = {...universalUtilities}; + const perLanguageFn = async (language, i, entries) => { const baseDirectory = language === defaultLanguage ? '' : language.code; @@ -305,19 +303,13 @@ export async function go({ }); const bound = bindUtilities({ + ...commonUtilities, + absoluteTo, - cachebust, - defaultLanguage, - getSizeOfAdditionalFile, - getSizeOfImagePath, language, - languages, - missingImagePaths, pagePath, - thumbsCache, - to, - urls, - wikiData, + pagePathStringFromRoot: pathname, + to: page.absoluteLinks ? absoluteTo : to, }); let pageHTML, oEmbedJSON; @@ -432,12 +424,21 @@ async function writePage({ ].filter(Boolean)); } +function filterNoOrigin(route) { + return !getOrigin(route.to); +} + function writeWebRouteSymlinks({ outputPath, webRoutes, }) { + const symlinkRoutes = + webRoutes + .filter(route => route.statically === 'symlink') + .filter(filterNoOrigin); + const promises = - webRoutes.map(async route => { + symlinkRoutes.map(async route => { const parts = route.to.split('/'); const parentDirectoryParts = parts.slice(0, -1); const symlinkNamePart = parts.at(-1); @@ -469,28 +470,113 @@ function writeWebRouteSymlinks({ return progressPromiseAll(`Writing web route symlinks.`, promises); } -async function writeFavicon({ - mediaPath, +async function writeWebRouteCopies({ outputPath, + webRoutes, }) { - const faviconFile = 'favicon.ico'; + const copyRoutes = + webRoutes + .filter(route => route.statically === 'copy') + .filter(filterNoOrigin); - try { - await stat(path.join(mediaPath, faviconFile)); - } catch (error) { - return; - } + const promises = + copyRoutes.map(async route => { + const permissionName = '__hsmusic-ok-for-deletion.txt'; - try { - await copyFile( - path.join(mediaPath, faviconFile), - path.join(outputPath, faviconFile)); - } catch (error) { - logWarn`Failed to copy favicon! ${error.message}`; - return; - } + const parts = route.to.split('/'); + const parentDirectoryParts = parts.slice(0, -1); + const copyNamePart = parts.at(-1); + + const parentDirectory = path.join(outputPath, ...parentDirectoryParts); + const copyPath = path.join(parentDirectory, copyNamePart); + + // We're going to do a rimraf call! This is freaking terrifying, + // so nope out on a couple important conditions. - logInfo`Copied favicon to site root.`; + let needsDelete; + try { + await stat(copyPath); + needsDelete = true; + } catch (error) { + if (error.code === 'ENOENT') { + needsDelete = false; + } else { + throw error; + } + } + + if (needsDelete) { + // First remove it directly, in case it's a symlink. + try { + await unlink(copyPath); + needsDelete = false; + } catch (error) { + // EPERM is POSIX, but libuv may or may not flat-out just raise + // the system error (which is ostensibly EISDIR on Linux). + // https://github.com/nodejs/node-v0.x-archive/issues/5791 + // https://man7.org/linux/man-pages/man2/unlink.2.html + // + // Both of these indidcate "a directory, probably" and we'll + // still check for the deletion permission file where we expect + // it before actually touching anything. + if (error.code !== 'EPERM' && error.code !== 'EISDIR') { + throw error; + } + } + } + + if (needsDelete) { + // Then check that the deletion permission file exists + // where we expect it. + try { + await stat(path.join(copyPath, permissionName)); + } catch (error) { + if (error.code === 'ENOENT') { + throw new Error(`Couldn't find ${permissionName} in ${copyPath} - please delete or move away this folder manually`); + } else { + throw error; + } + } + + // And *then* actually delete that directory. + await rimraf(copyPath); + } + + // Actually copy the source path where it's wanted. + await cp(route.from, copyPath, {recursive: true}); + + // And certify that it's OK to delete this path, next time around. + await writeFile(path.join(copyPath, permissionName), + `The presence of this file (by its name, not its contents)\n` + + `indicates hsmusic may delete everything contained in this\n` + + `directory (the one which directly contains this file, *not*\n` + + `any further-up parent directories).\n` + + `\n` + + `If you make edits, or add any files, they will be deleted or\n` + + `overwritten the next time you run the build.\n` + + `\n` + + `If you delete *this* file, hsmusic will error during the next\n` + + `build, and will ask that you delete the containing directory\n` + + `yourself.\n`); + }); + + const results = + await Promise.allSettled(promises); + + const errors = + results + .filter(({status}) => status === 'rejected') + .map(({reason}) => reason) + .map(err => + (err.message.startsWith(`Couldn't find`) + ? err.message + : err)); + + if (empty(errors)) { + logInfo`Wrote web route copies.`; + } else { + throw new AggregateError(errors, `Errors copying internal files ("web routes")`); + } } async function writeSharedFilesAndPages({ |