#! /usr/bin/env node const acorn = require('acorn') const acornWalk = require('acorn-walk') const fs = require('fs') const _ = require('lodash') const glob = require('glob') const escodegen = require('escodegen') const print = console.log const Methods = new Set([ 'get', 'head', 'post', 'put', 'delete', 'connect', 'options', 'trace', 'patch', ]) const isMethod = str => { return Methods.has(str) } // Check if the expression is a call on a router, return data about it, or null const routerCall = callExpression => { const callee = callExpression.callee const property = callee.property const args = callExpression.arguments if (!callee.object || !callee.object.name) { return false } const routerName = callee.object.name if ( // Match known names for the Express routers: app, webRouter, whateverRouter, etc... isMethod(property.name) && (routerName === 'app' || routerName.match('^.*[rR]outer$')) ) { return { routerName: routerName, method: property.name, args: args, } } else { return null } } const formatMethodCall = expression => { return escodegen.generate(expression, { format: { compact: true } }) } const parseAndPrintRoutesSync = path => { const content = fs.readFileSync(path) // Walk the AST (Abstract Syntax Tree) acornWalk.simple(acorn.parse(content), { // We only care about call expression ( like `a.b()` ) CallExpression(node) { const call = routerCall(node) if (call) { const firstArg = _.first(call.args) try { print( ` ${formatRouterName(call.routerName)}\t .${call.method} \t: ${ firstArg.value } => ${call.args.slice(1).map(formatMethodCall).join(' => ')}` ) } catch (e) { print('>> Error') print(e) print(JSON.stringify(call)) process.exit(1) } } }, }) } const routerNameMapping = { privateApiRouter: 'privateApi', publicApiRouter: 'publicApi', } const formatRouterName = name => { return routerNameMapping[name] || name } const main = () => { // Take an optional filter to apply to file names const filter = process.argv[2] || null if (filter && (filter === '--help' || filter === 'help')) { print('') print(' Usage: bin/routes [filter]') print(' Examples:') print(' bin/routes') print(' bin/routes GitBridge') print('') process.exit(0) } // Find all routers glob('*[rR]outer.js', { matchBase: true }, (err, files) => { if (err) { console.error(err) process.exit(1) } for (const file of files) { if (file.match('^node_modules.*$') || file.match('.*/public/.*')) { continue } // Restrict to the filter (if filter is present) if (filter && !file.match(`.*${filter}.*`)) { continue } print(`[${file}]`) try { parseAndPrintRoutesSync(file) } catch (_e) { print('>> Error parsing file') continue } } process.exit(0) }) } if (require.main === module) { main() }