diff options
Diffstat (limited to 'src/write')
-rw-r--r-- | src/write/build-modes/live-dev-server.js | 107 | ||||
-rw-r--r-- | src/write/build-modes/repl.js | 2 | ||||
-rw-r--r-- | src/write/build-modes/static-build.js | 81 |
3 files changed, 103 insertions, 87 deletions
diff --git a/src/write/build-modes/live-dev-server.js b/src/write/build-modes/live-dev-server.js index a2a84a8..03ef604 100644 --- a/src/write/build-modes/live-dev-server.js +++ b/src/write/build-modes/live-dev-server.js @@ -1,10 +1,11 @@ import {spawn} from 'node:child_process'; import * as http from 'node:http'; -import {readFile, stat} from 'node:fs/promises'; +import {open, stat} from 'node:fs/promises'; import * as path from 'node:path'; +import {pipeline} from 'node:stream/promises'; import {inspect as nodeInspect} from 'node:util'; -import {ENABLE_COLOR, logInfo, logWarn, progressCallAll} from '#cli'; +import {ENABLE_COLOR, colors, logInfo, logWarn, progressCallAll} from '#cli'; import {watchContentDependencies} from '#content-dependencies'; import {quickEvaluate} from '#content-function'; import * as html from '#html'; @@ -27,19 +28,23 @@ export const description = `Hosts a local HTTP server which generates page conte export const config = { fileSizes: { - default: true, + default: 'perform', }, languageReloading: { - default: true, + default: 'perform', }, mediaValidation: { - default: true, + default: 'perform', }, thumbs: { - default: true, + default: 'perform', + }, + + webRoutes: { + required: true, }, }; @@ -88,9 +93,6 @@ export function getCLIOptions() { export async function go({ cliOptions, - _dataPath, - mediaPath, - mediaCachePath, defaultLanguage, languages, @@ -98,6 +100,7 @@ export async function go({ srcRootPath, thumbsCache, urls, + webRoutes, wikiData, cachebust, @@ -211,7 +214,7 @@ export async function go({ response.writeHead(200, contentTypeJSON); response.end(json); - if (loudResponses) console.log(`${requestHead} [200] ${pathname}`); + if (loudResponses) console.log(`${requestHead} [200] ${pathname} (${colors.yellow(`special`)})`); } catch (error) { response.writeHead(500, contentTypeJSON); response.end(`Internal error serializing wiki JSON`); @@ -221,30 +224,27 @@ export async function go({ return; } - const { - area: localFileArea, - path: localFilePath - } = pathname.match(/^\/(?<area>static|util|media|thumb)\/(?<path>.*)/)?.groups ?? {}; + const matchedWebRoute = + webRoutes + .find(({to}) => pathname.startsWith('/' + to)); + + if (matchedWebRoute) { + const localFilePath = pathname.slice(1 + matchedWebRoute.to.length); - if (localFileArea) { // Not security tested, man, this is a dev server!! - const safePath = path.posix.resolve('/', localFilePath).replace(/^\//, ''); - - let localDirectory; - if (localFileArea === 'static' || localFileArea === 'util') { - localDirectory = path.join(srcRootPath, localFileArea); - } else if (localFileArea === 'media') { - localDirectory = mediaPath; - } else if (localFileArea === 'thumb') { - localDirectory = mediaCachePath; - } + const safePath = + path.posix + .resolve('/', localFilePath) + .replace(/^\//, ''); + + const localDirectory = matchedWebRoute.from; let filePath; try { filePath = path.resolve(localDirectory, decodeURI(safePath.split('/').join(path.sep))); } catch (error) { response.writeHead(404, contentTypePlain); - response.end(`No ${localFileArea} file found for: ${safePath}`); + response.end(`File not found for: ${safePath}`); console.log(`${requestHead} [404] ${pathname}`); console.log(`Failed to decode request pathname`); } @@ -254,12 +254,12 @@ export async function go({ } catch (error) { if (error.code === 'ENOENT') { response.writeHead(404, contentTypePlain); - response.end(`No ${localFileArea} file found for: ${safePath}`); + response.end(`File not found for: ${safePath}`); console.log(`${requestHead} [404] ${pathname}`); console.log(`ENOENT for stat: ${filePath}`); } else { response.writeHead(500, contentTypePlain); - response.end(`Internal error accessing ${localFileArea} file for: ${safePath}`); + response.end(`Internal error accessing file for: ${safePath}`); console.error(`${requestHead} [500] ${pathname}`); showError(error); } @@ -300,21 +300,33 @@ export async function go({ 'zip': 'application/zip', }[extname]; + let fd, size; try { - const {size} = await stat(filePath); - const buffer = await readFile(filePath) - response.writeHead(200, contentType ? { - 'Content-Type': contentType, - 'Content-Length': size, - } : {}); - response.end(buffer); - if (loudResponses) console.log(`${requestHead} [200] ${pathname}`); + ({size} = await stat(filePath)); + fd = await open(filePath); } catch (error) { - response.writeHead(500, contentTypePlain); - response.end(`Failed during file-to-response pipeline`); - console.error(`${requestHead} [500] ${pathname}`); - showError(error); + if (error.code === 'EISDIR') { + response.writeHead(404, contentTypePlain); + response.end(`File not found for: ${safePath}`); + console.error(`${requestHead} [404] ${pathname} (is directory)`); + } else { + response.writeHead(500, contentTypePlain); + response.end(`Failed during file-to-response pipeline`); + console.error(`${requestHead} [500] ${pathname}`); + showError(error); + } + return; } + + response.writeHead(200, { + ...contentType ? {'Content-Type': contentType} : {}, + 'Content-Length': size, + }); + + await pipeline(fd.createReadStream(), response); + + if (loudResponses) console.log(`${requestHead} [200] ${pathname} (${colors.magenta(`web route`)})`); + return; } @@ -421,9 +433,9 @@ export async function go({ ? `${(timeDelta / 1000).toFixed(2)}s` : `${timeDelta}ms`); - console.log(`${requestHead} [200, ${timeString}] ${pathname}`); + console.log(`${requestHead} [200, ${timeString}] ${pathname} (${colors.blue(`page`)})`); } else if (loudResponses) { - console.log(`${requestHead} [200] ${pathname}`); + console.log(`${requestHead} [200] ${pathname} (${colors.blue(`page`)})`); } response.writeHead(200, contentTypeHTML); @@ -474,10 +486,15 @@ export async function go({ if (skipServing) { logInfo`Ready to serve! But --skip-serving was passed, so all done.`; } else { - server.listen(port, host); + process.on('SIGINT', () => { + process.stdout.write('\n'); + server.close(); + }); - // Just keep going... forever!!! - await new Promise(() => {}); + await new Promise(resolve => { + server.listen(port, host); + server.on('close', () => resolve()); + }); } return true; diff --git a/src/write/build-modes/repl.js b/src/write/build-modes/repl.js index 2098559..b300e8e 100644 --- a/src/write/build-modes/repl.js +++ b/src/write/build-modes/repl.js @@ -6,7 +6,7 @@ export const config = { }, languageReloading: { - default: true, + default: 'perform', }, mediaValidation: { diff --git a/src/write/build-modes/static-build.js b/src/write/build-modes/static-build.js index a355a00..68cf094 100644 --- a/src/write/build-modes/static-build.js +++ b/src/write/build-modes/static-build.js @@ -38,7 +38,7 @@ export const description = `Generates all page content in one build (according t export const config = { fileSizes: { - default: true, + default: 'perform', }, languageReloading: { @@ -46,11 +46,15 @@ export const config = { }, mediaValidation: { - default: true, + default: 'perform', }, thumbs: { - default: true, + default: 'perform', + }, + + webRoutes: { + required: true, }, }; @@ -99,9 +103,7 @@ export function getCLIOptions() { export async function go({ cliOptions, - _dataPath, mediaPath, - mediaCachePath, queueSize, defaultLanguage, @@ -110,6 +112,7 @@ export async function go({ srcRootPath, thumbsCache, urls, + webRoutes, wikiData, cachebust, @@ -148,12 +151,9 @@ export async function go({ await mkdir(outputPath, {recursive: true}); - await writeSymlinks({ - srcRootPath, - mediaPath, - mediaCachePath, + await writeWebRouteSymlinks({ outputPath, - urls, + webRoutes, }); if (writeAll) { @@ -432,42 +432,41 @@ async function writePage({ ].filter(Boolean)); } -function writeSymlinks({ - srcRootPath, - mediaPath, - mediaCachePath, +function writeWebRouteSymlinks({ outputPath, - urls, + webRoutes, }) { - return progressPromiseAll('Writing site symlinks.', [ - link(path.join(srcRootPath, 'util'), 'shared.utilityRoot'), - link(path.join(srcRootPath, 'static'), 'shared.staticRoot'), - link(mediaPath, 'media.root'), - link(mediaCachePath, 'thumb.root'), - ]); - - async function link(directory, urlKey) { - const pathname = urls.from('shared.root').toDevice(urlKey); - const file = path.join(outputPath, pathname); - - try { - await unlink(file); - } catch (error) { - if (error.code !== 'ENOENT') { - throw error; + const promises = + webRoutes.map(async route => { + const parts = route.to.split('/'); + const parentDirectoryParts = parts.slice(0, -1); + const symlinkNamePart = parts.at(-1); + + const parentDirectory = path.join(outputPath, ...parentDirectoryParts); + const symlinkPath = path.join(parentDirectory, symlinkNamePart); + + try { + await unlink(symlinkPath); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } } - } - try { - await symlink(path.resolve(directory), file); - } catch (error) { - if (error.code === 'EPERM') { - await symlink(path.resolve(directory), file, 'junction'); - } else { - throw error; + await mkdir(parentDirectory, {recursive: true}); + + try { + await symlink(route.from, symlinkPath); + } catch (error) { + if (error.code === 'EPERM') { + await symlink(route.from, symlinkPath, 'junction'); + } else { + throw error; + } } - } - } + }); + + return progressPromiseAll(`Writing web route symlinks.`, promises); } async function writeFavicon({ |