routes 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. #! /usr/bin/env node
  2. const acorn = require('acorn')
  3. const acornWalk = require('acorn-walk')
  4. const fs = require('fs')
  5. const _ = require('lodash')
  6. const glob = require('glob')
  7. const escodegen = require('escodegen')
  8. const print = console.log
  9. const Methods = new Set([
  10. 'get',
  11. 'head',
  12. 'post',
  13. 'put',
  14. 'delete',
  15. 'connect',
  16. 'options',
  17. 'trace',
  18. 'patch',
  19. ])
  20. const isMethod = str => {
  21. return Methods.has(str)
  22. }
  23. // Check if the expression is a call on a router, return data about it, or null
  24. const routerCall = callExpression => {
  25. const callee = callExpression.callee
  26. const property = callee.property
  27. const args = callExpression.arguments
  28. if (!callee.object || !callee.object.name) {
  29. return false
  30. }
  31. const routerName = callee.object.name
  32. if (
  33. // Match known names for the Express routers: app, webRouter, whateverRouter, etc...
  34. isMethod(property.name) &&
  35. (routerName === 'app' || routerName.match('^.*[rR]outer$'))
  36. ) {
  37. return {
  38. routerName: routerName,
  39. method: property.name,
  40. args: args,
  41. }
  42. } else {
  43. return null
  44. }
  45. }
  46. const formatMethodCall = expression => {
  47. return escodegen.generate(expression, { format: { compact: true } })
  48. }
  49. const parseAndPrintRoutesSync = path => {
  50. const content = fs.readFileSync(path)
  51. // Walk the AST (Abstract Syntax Tree)
  52. acornWalk.simple(acorn.parse(content), {
  53. // We only care about call expression ( like `a.b()` )
  54. CallExpression(node) {
  55. const call = routerCall(node)
  56. if (call) {
  57. const firstArg = _.first(call.args)
  58. try {
  59. print(
  60. ` ${formatRouterName(call.routerName)}\t .${call.method} \t: ${
  61. firstArg.value
  62. } => ${call.args.slice(1).map(formatMethodCall).join(' => ')}`
  63. )
  64. } catch (e) {
  65. print('>> Error')
  66. print(e)
  67. print(JSON.stringify(call))
  68. process.exit(1)
  69. }
  70. }
  71. },
  72. })
  73. }
  74. const routerNameMapping = {
  75. privateApiRouter: 'privateApi',
  76. publicApiRouter: 'publicApi',
  77. }
  78. const formatRouterName = name => {
  79. return routerNameMapping[name] || name
  80. }
  81. const main = () => {
  82. // Take an optional filter to apply to file names
  83. const filter = process.argv[2] || null
  84. if (filter && (filter === '--help' || filter === 'help')) {
  85. print('')
  86. print(' Usage: bin/routes [filter]')
  87. print(' Examples:')
  88. print(' bin/routes')
  89. print(' bin/routes GitBridge')
  90. print('')
  91. process.exit(0)
  92. }
  93. // Find all routers
  94. glob('*[rR]outer.js', { matchBase: true }, (err, files) => {
  95. if (err) {
  96. console.error(err)
  97. process.exit(1)
  98. }
  99. for (const file of files) {
  100. if (file.match('^node_modules.*$') || file.match('.*/public/.*')) {
  101. continue
  102. }
  103. // Restrict to the filter (if filter is present)
  104. if (filter && !file.match(`.*${filter}.*`)) {
  105. continue
  106. }
  107. print(`[${file}]`)
  108. try {
  109. parseAndPrintRoutesSync(file)
  110. } catch (_e) {
  111. print('>> Error parsing file')
  112. continue
  113. }
  114. }
  115. process.exit(0)
  116. })
  117. }
  118. if (require.main === module) {
  119. main()
  120. }