routes 3.0 KB

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