UNPKG

@tdb/web

Version:

Common condiguration for serving a web-site and testing web-based UI components.

132 lines (116 loc) 3.39 kB
import { express, time, queryString, value, fsPath, fs } from '../common'; import { Manifest } from './Manifest'; import { IManifest, IManifestResponse } from './types'; import * as util from './util'; export type IManifestQuery = { path: string; markdown?: boolean; yaml?: boolean; json?: boolean; }; export type IResourceQuery = { parse?: boolean; }; /** * Initializes a new manifest router. */ export function init(args: { rootDir: string; manifestPath?: string; resourcePath?: string; }) { const { rootDir, manifestPath = '/manifest', resourcePath = '/resource', } = args; const router = express.router(); /** * GET * Retrieves a manifest listing of the files at the given path. */ router.get(manifestPath, async (req, res) => { const timer = time.timer(); const query = req.query as IManifestQuery; const path = query.path as string; let loadExtensions: string[] = []; const loadFlag = (...flag: string[]) => { if (queryString.isFlag(flag, query)) { loadExtensions = [...loadExtensions, ...flag]; } }; loadFlag('markdown', 'md'); loadFlag('json'); loadFlag('yaml', 'yml'); const done = ( status: number, args: { error?: string; manifest?: IManifest }, ) => { const payload = value.deleteUndefined<IManifestResponse>({ status, elapsed: timer.elapsed(), error: args.error ? { message: args.error } : undefined, manifest: args.manifest, }); res.status(status).send(payload); }; if (!path) { const error = `A '?path=<...>' was not passed in the URL's querystring.`; return done(400, { error }); } const manifest = new Manifest({ rootDir, path, urlPrefix: resourcePath, }); return done(200, { manifest: await manifest.toObject({ loadExtensions }), }); }); /** * GET * Retrieves the specified resource path from the manifest. */ const RESOURCE_ROUTE = fsPath.join(resourcePath.replace(/\/\*$/, ''), '*'); router.get(RESOURCE_ROUTE, async (req, res) => { try { const path = (req.params['0'] || '') as string; const file = fsPath.resolve(fsPath.join(rootDir, path)); const exists = file ? await fs.pathExists(file) : false; const query = req.query as IResourceQuery; let data: any; const ext = fsPath.extname(path).toLowerCase(); if (['.yml', '.yaml'].includes(ext)) { const res = await util.parseYamlFile(file); if (res.error) { throw new Error(res.error.message); } data = res.data; } if ( ['.md', '.markdown'].includes(ext) && queryString.isFlag('parse', query) ) { const res = await util.parseMarkdownFile(file); if (res.error) { throw new Error(res.error.message); } data = res.data; } return file && exists ? data ? res.send(data) : res.sendFile(file) : res.status(404).send({ status: 404, error: { message: 'Resource not found.', path }, }); } catch (err) { const status = 500; const message = `Failed to retrieve resource. ${err.message}`; res.status(status).send({ status, error: { message } }); } }); // Finish up. return router; }