UNPKG

@barelyhuman/fs-route-manifest

Version:

Opinionated file system manifest generator

121 lines (118 loc) 4.55 kB
import fs from 'node:fs'; import { join, resolve } from 'node:path'; import { pathToRegexp } from 'path-to-regexp'; const defaultFS = { readDir: fs.readdirSync, }; function readDirectory(basePath, fsInterface = defaultFS) { return fsInterface .readDir(basePath) .map((file) => addFileInformation(basePath, file)) .reduce((acc, item) => { if (item.isDirectory) { return acc.concat(readDirectory(join(basePath, item.name))); } return acc.concat(item); }, []); } function addFileInformation(basePath, file) { const absPath = resolve(basePath, file); return { name: file, relativePath: join(basePath, file), absolutePath: absPath, isDirectory: fs.statSync(absPath).isDirectory() || false, }; } function generateRoutes(basePath, paths, options) { const { normalizer = normalizeURLPaths, transformer = expressTransformer, sorter = defaultURLSorter, } = options || {}; const normalizedURLs = normalizer(basePath, paths, sorter); const transformedURLS = normalizedURLs.map((x) => transformer(x)); return transformedURLS; } /** * Default transformer used by generateRoutes in conjunction with the `normalizeURLPaths` normalizer * to add matching regex to the final manifest. * If you've changed the normalizer, make sure you also change the transformer or validate everything * you wish for the transformer to handler * * The current transformer generates a similar regexp pattern to express.js */ function expressTransformer(x) { const regx = pathToRegexp(x.url); x.url = x.url .replace(/([\/]\${2}(\w+))/g, "/:$2*") .replace(/([\/]\$(\w+))/g, "/:$2"); x.match = regx; x.matchFlags = regx.flags; return x; } /** * Normalizer used by the generateRoutes function to find and clean up the file paths * to valid url segments that can be parsed/compiled to create regex matchers for the final manifest * This particular normalizer does the following * - Clean File Paths and creates basic urls * - Sorts the urls based on the required priority */ function normalizeURLPaths(basePath, paths = [], sorter = defaultURLSorter) { const normalizedBasePath = basePath.replace(/^\./, "").replace(/^\//, ""); const pathRegex = new RegExp(`^[\/]?(${normalizedBasePath})`); return paths .map((x) => { let url = x.relativePath .replace(pathRegex, "") .replace(/\.(js|ts)x?$/, "") .replace(/index$/, "") .replace(/(\w+)\/$/, "$1"); return { ...x, url: url, }; }) .sort(sorter); } // a variation of the following snippet // https://github.com/cyco130/smf/blob/c4b601f48cd3b3b71bea6d76b52b9a85800813e4/smf/shared.ts#L22 // this doesn't consider partial routes segments // eg: /about/$foo-$bar isn't a valid route segment for this sorter function defaultURLSorter(x, y) { const catchAll = Number(x.url.match(/\$\$(\w+)$/)) - Number(y.url.match(/\$\$(\w+)$/)); if (catchAll) return catchAll; const aSegments = x.url.split("/"); const bSegments = y.url.split("/"); const dynamicSegments = aSegments.filter((segment) => segment.startsWith("$")).length - bSegments.filter((segment) => segment.startsWith("$")).length; if (dynamicSegments) return dynamicSegments; const segments = aSegments.length - bSegments.length; if (segments) return segments; for (let i = 0; i < aSegments.length; i++) { const aSegment = aSegments[i]; const bSegment = bSegments[i]; const dynamic = Number(aSegment.startsWith("$")) - Number(bSegment.startsWith("$")); if (dynamic) return dynamic; } return 0; } /** * Stringify a given manifest with an optional replacer functions * the default replacer changes the `match` regex to a string representation of the regex */ function stringify(manifest, replacer = defaultReplacer) { let stringified = JSON.stringify(manifest, replacer, 2); return stringified; } /** * Default replacer used by the `stringify` function to decide what string to be returned in the stringified manifest. * As the stringification is on an object, the normal JSON.stringify rules still apply */ function defaultReplacer(k, v) { if (v instanceof RegExp) { return v.source; } return v; } export { defaultFS, defaultReplacer, defaultURLSorter, expressTransformer, generateRoutes, normalizeURLPaths, readDirectory, stringify };