@tdb/web
Version:
Common condiguration for serving a web-site and testing web-based UI components.
132 lines (116 loc) • 3.39 kB
text/typescript
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;
}