routes 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  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,
  39. method: property.name,
  40. 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(
  53. acorn.parse(content, { sourceType: 'module', ecmaVersion: 2020 }),
  54. {
  55. // We only care about call expression ( like `a.b()` )
  56. CallExpression(node) {
  57. const call = routerCall(node)
  58. if (call) {
  59. const firstArg = _.first(call.args)
  60. try {
  61. print(
  62. ` ${formatRouterName(call.routerName)}\t .${call.method} \t: ${
  63. firstArg.value
  64. } => ${call.args.slice(1).map(formatMethodCall).join(' => ')}`
  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. }
  77. const routerNameMapping = {
  78. privateApiRouter: 'privateApi',
  79. publicApiRouter: 'publicApi',
  80. }
  81. const formatRouterName = name => {
  82. return routerNameMapping[name] || name
  83. }
  84. const main = async () => {
  85. // Take an optional filter to apply to file names
  86. const filter = process.argv[2] || null
  87. if (filter && (filter === '--help' || filter === 'help')) {
  88. print('')
  89. print(' Usage: bin/routes [filter]')
  90. print(' Examples:')
  91. print(' bin/routes')
  92. print(' bin/routes GitBridge')
  93. print('')
  94. process.exit(0)
  95. }
  96. // Find all routers
  97. const files = await glob('*[rR]outer.*js', { matchBase: true })
  98. for (const file of files) {
  99. if (file.match('^node_modules.*$') || file.match('.*/public/.*')) {
  100. continue
  101. }
  102. // Restrict to the filter (if filter is present)
  103. if (filter && !file.match(`.*${filter}.*`)) {
  104. continue
  105. }
  106. print(`[${file}]`)
  107. try {
  108. parseAndPrintRoutesSync(file)
  109. } catch (_e) {
  110. print('>> Error parsing file')
  111. }
  112. }
  113. }
  114. if (require.main === module) {
  115. main().then(() => process.exit(0)).catch(() => {
  116. console.error(err)
  117. process.exit(1)
  118. })
  119. }