@mbtest/mountebank 2.9.2-beta.9050

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +94 -0
  3. package/bin/mb +136 -0
  4. package/package.json +71 -0
  5. package/releases.json +52 -0
  6. package/src/cli/api.js +112 -0
  7. package/src/cli/cli.js +420 -0
  8. package/src/controllers/configController.js +64 -0
  9. package/src/controllers/feedController.js +115 -0
  10. package/src/controllers/homeController.js +58 -0
  11. package/src/controllers/imposterController.js +328 -0
  12. package/src/controllers/impostersController.js +215 -0
  13. package/src/controllers/logsController.js +52 -0
  14. package/src/models/behaviors.js +553 -0
  15. package/src/models/behaviorsValidator.js +186 -0
  16. package/src/models/compatibility.js +133 -0
  17. package/src/models/dryRunValidator.js +261 -0
  18. package/src/models/filesystemBackedImpostersRepository.js +908 -0
  19. package/src/models/http/baseHttpServer.js +207 -0
  20. package/src/models/http/headersMap.js +87 -0
  21. package/src/models/http/httpProxy.js +230 -0
  22. package/src/models/http/httpRequest.js +82 -0
  23. package/src/models/http/httpServer.js +18 -0
  24. package/src/models/http/index.js +18 -0
  25. package/src/models/https/cert/mb-cert.pem +20 -0
  26. package/src/models/https/cert/mb-csr.pem +16 -0
  27. package/src/models/https/cert/mb-key.pem +27 -0
  28. package/src/models/https/httpsServer.js +42 -0
  29. package/src/models/https/index.js +18 -0
  30. package/src/models/imposter.js +243 -0
  31. package/src/models/imposterPrinter.js +120 -0
  32. package/src/models/impostersRepository.js +49 -0
  33. package/src/models/inMemoryImpostersRepository.js +418 -0
  34. package/src/models/jsonpath.js +44 -0
  35. package/src/models/mbConnection.js +107 -0
  36. package/src/models/predicates.js +438 -0
  37. package/src/models/protocols.js +242 -0
  38. package/src/models/responseResolver.js +398 -0
  39. package/src/models/smtp/index.js +16 -0
  40. package/src/models/smtp/smtpRequest.js +60 -0
  41. package/src/models/smtp/smtpServer.js +109 -0
  42. package/src/models/tcp/index.js +18 -0
  43. package/src/models/tcp/tcpProxy.js +110 -0
  44. package/src/models/tcp/tcpRequest.js +23 -0
  45. package/src/models/tcp/tcpServer.js +156 -0
  46. package/src/models/tcp/tcpValidator.js +19 -0
  47. package/src/models/xpath.js +95 -0
  48. package/src/mountebank.js +245 -0
  49. package/src/public/images/arrow_down.png +0 -0
  50. package/src/public/images/arrow_up.png +0 -0
  51. package/src/public/images/book.jpg +0 -0
  52. package/src/public/images/dataflow.png +0 -0
  53. package/src/public/images/favicon.ico +0 -0
  54. package/src/public/images/forkme_right_orange_ff7600.png +0 -0
  55. package/src/public/images/mountebank.png +0 -0
  56. package/src/public/images/overview.gif +0 -0
  57. package/src/public/images/quote.png +0 -0
  58. package/src/public/images/tw-logo.png +0 -0
  59. package/src/public/scripts/jquery/jquery-3.6.1.min.js +2 -0
  60. package/src/public/scripts/urlHashHandler.js +31 -0
  61. package/src/public/stylesheets/application.css +424 -0
  62. package/src/public/stylesheets/ie.css +14 -0
  63. package/src/public/stylesheets/imposters.css +121 -0
  64. package/src/public/stylesheets/jqueryui/1.10.4/themes/smoothness/jquery-ui.css +1178 -0
  65. package/src/util/combinators.js +68 -0
  66. package/src/util/date.js +51 -0
  67. package/src/util/errors.js +55 -0
  68. package/src/util/helpers.js +131 -0
  69. package/src/util/inherit.js +28 -0
  70. package/src/util/ip.js +54 -0
  71. package/src/util/logger.js +83 -0
  72. package/src/util/middleware.js +256 -0
  73. package/src/util/scopedLogger.js +47 -0
  74. package/src/views/_footer.ejs +20 -0
  75. package/src/views/_header.ejs +113 -0
  76. package/src/views/_imposter.ejs +8 -0
  77. package/src/views/config.ejs +71 -0
  78. package/src/views/docs/api/behaviors/copy.ejs +427 -0
  79. package/src/views/docs/api/behaviors/decorate.ejs +182 -0
  80. package/src/views/docs/api/behaviors/lookup.ejs +220 -0
  81. package/src/views/docs/api/behaviors/shellTransform.ejs +153 -0
  82. package/src/views/docs/api/behaviors/wait.ejs +121 -0
  83. package/src/views/docs/api/behaviors.ejs +141 -0
  84. package/src/views/docs/api/contracts/addStub-description.ejs +10 -0
  85. package/src/views/docs/api/contracts/addStub.ejs +10 -0
  86. package/src/views/docs/api/contracts/config-description.ejs +32 -0
  87. package/src/views/docs/api/contracts/config.ejs +23 -0
  88. package/src/views/docs/api/contracts/home-description.ejs +18 -0
  89. package/src/views/docs/api/contracts/home.ejs +13 -0
  90. package/src/views/docs/api/contracts/imposter-description.ejs +439 -0
  91. package/src/views/docs/api/contracts/imposter.ejs +182 -0
  92. package/src/views/docs/api/contracts/imposters-description.ejs +13 -0
  93. package/src/views/docs/api/contracts/imposters.ejs +13 -0
  94. package/src/views/docs/api/contracts/logs-description.ejs +3 -0
  95. package/src/views/docs/api/contracts/logs.ejs +14 -0
  96. package/src/views/docs/api/contracts/stub-description.ejs +4 -0
  97. package/src/views/docs/api/contracts/stub.ejs +7 -0
  98. package/src/views/docs/api/contracts/stubs-description.ejs +4 -0
  99. package/src/views/docs/api/contracts/stubs.ejs +11 -0
  100. package/src/views/docs/api/contracts.ejs +133 -0
  101. package/src/views/docs/api/errors.ejs +64 -0
  102. package/src/views/docs/api/fault/connectionReset.ejs +31 -0
  103. package/src/views/docs/api/fault/randomDataThenClose.ejs +31 -0
  104. package/src/views/docs/api/faults.ejs +57 -0
  105. package/src/views/docs/api/injection.ejs +426 -0
  106. package/src/views/docs/api/json.ejs +205 -0
  107. package/src/views/docs/api/jsonpath.ejs +210 -0
  108. package/src/views/docs/api/mocks.ejs +130 -0
  109. package/src/views/docs/api/overview.ejs +968 -0
  110. package/src/views/docs/api/predicates/and.ejs +62 -0
  111. package/src/views/docs/api/predicates/contains.ejs +64 -0
  112. package/src/views/docs/api/predicates/deepEquals.ejs +114 -0
  113. package/src/views/docs/api/predicates/endsWith.ejs +66 -0
  114. package/src/views/docs/api/predicates/equals.ejs +125 -0
  115. package/src/views/docs/api/predicates/exists.ejs +118 -0
  116. package/src/views/docs/api/predicates/inject.ejs +67 -0
  117. package/src/views/docs/api/predicates/matches.ejs +66 -0
  118. package/src/views/docs/api/predicates/not.ejs +52 -0
  119. package/src/views/docs/api/predicates/or.ejs +79 -0
  120. package/src/views/docs/api/predicates/startsWith.ejs +62 -0
  121. package/src/views/docs/api/predicates.ejs +382 -0
  122. package/src/views/docs/api/proxies.ejs +191 -0
  123. package/src/views/docs/api/proxy/addDecorateBehavior.ejs +115 -0
  124. package/src/views/docs/api/proxy/addWaitBehavior.ejs +96 -0
  125. package/src/views/docs/api/proxy/injectHeaders.ejs +91 -0
  126. package/src/views/docs/api/proxy/predicateGenerators.ejs +600 -0
  127. package/src/views/docs/api/proxy/proxyModes.ejs +495 -0
  128. package/src/views/docs/api/stubs.ejs +391 -0
  129. package/src/views/docs/api/xpath.ejs +281 -0
  130. package/src/views/docs/cli/configFiles.ejs +133 -0
  131. package/src/views/docs/cli/customFormatters.ejs +53 -0
  132. package/src/views/docs/cli/help.ejs +6 -0
  133. package/src/views/docs/cli/replay.ejs +42 -0
  134. package/src/views/docs/cli/restart.ejs +10 -0
  135. package/src/views/docs/cli/save.ejs +68 -0
  136. package/src/views/docs/cli/start.ejs +234 -0
  137. package/src/views/docs/cli/stop.ejs +32 -0
  138. package/src/views/docs/commandLine.ejs +93 -0
  139. package/src/views/docs/communityExtensions.ejs +233 -0
  140. package/src/views/docs/gettingStarted.ejs +146 -0
  141. package/src/views/docs/mentalModel.ejs +51 -0
  142. package/src/views/docs/protocols/custom.ejs +231 -0
  143. package/src/views/docs/protocols/http.ejs +238 -0
  144. package/src/views/docs/protocols/https.ejs +246 -0
  145. package/src/views/docs/protocols/smtp.ejs +142 -0
  146. package/src/views/docs/protocols/tcp.ejs +431 -0
  147. package/src/views/docs/security.ejs +38 -0
  148. package/src/views/faqs.ejs +65 -0
  149. package/src/views/feed.ejs +33 -0
  150. package/src/views/imposter.ejs +22 -0
  151. package/src/views/imposters.ejs +33 -0
  152. package/src/views/index.ejs +89 -0
  153. package/src/views/license.ejs +30 -0
  154. package/src/views/logs.ejs +77 -0
  155. package/src/views/releases/v1.1.0.ejs +55 -0
  156. package/src/views/releases/v1.1.36.ejs +84 -0
  157. package/src/views/releases/v1.1.72.ejs +92 -0
  158. package/src/views/releases/v1.10.0.ejs +108 -0
  159. package/src/views/releases/v1.11.0.ejs +109 -0
  160. package/src/views/releases/v1.12.0.ejs +96 -0
  161. package/src/views/releases/v1.13.0.ejs +118 -0
  162. package/src/views/releases/v1.14.0.ejs +107 -0
  163. package/src/views/releases/v1.14.1.ejs +94 -0
  164. package/src/views/releases/v1.15.0.ejs +113 -0
  165. package/src/views/releases/v1.16.0.ejs +104 -0
  166. package/src/views/releases/v1.2.0.ejs +78 -0
  167. package/src/views/releases/v1.2.103.ejs +86 -0
  168. package/src/views/releases/v1.2.122.ejs +86 -0
  169. package/src/views/releases/v1.2.30.ejs +84 -0
  170. package/src/views/releases/v1.2.45.ejs +84 -0
  171. package/src/views/releases/v1.2.56.ejs +79 -0
  172. package/src/views/releases/v1.3.0.ejs +86 -0
  173. package/src/views/releases/v1.3.1.ejs +100 -0
  174. package/src/views/releases/v1.4.0.ejs +96 -0
  175. package/src/views/releases/v1.4.1.ejs +103 -0
  176. package/src/views/releases/v1.4.2.ejs +100 -0
  177. package/src/views/releases/v1.4.3.ejs +113 -0
  178. package/src/views/releases/v1.5.0.ejs +104 -0
  179. package/src/views/releases/v1.5.1.ejs +91 -0
  180. package/src/views/releases/v1.6.0.ejs +109 -0
  181. package/src/views/releases/v1.7.0.ejs +113 -0
  182. package/src/views/releases/v1.7.1.ejs +90 -0
  183. package/src/views/releases/v1.7.2.ejs +96 -0
  184. package/src/views/releases/v1.8.0.ejs +121 -0
  185. package/src/views/releases/v1.9.0.ejs +111 -0
  186. package/src/views/releases/v2.0.0.ejs +159 -0
  187. package/src/views/releases/v2.1.0.ejs +121 -0
  188. package/src/views/releases/v2.1.1.ejs +106 -0
  189. package/src/views/releases/v2.1.2.ejs +84 -0
  190. package/src/views/releases/v2.2.0.ejs +115 -0
  191. package/src/views/releases/v2.2.1.ejs +102 -0
  192. package/src/views/releases/v2.3.0.ejs +121 -0
  193. package/src/views/releases/v2.3.1.ejs +100 -0
  194. package/src/views/releases/v2.3.2.ejs +102 -0
  195. package/src/views/releases/v2.3.3.ejs +97 -0
  196. package/src/views/releases/v2.4.0.ejs +114 -0
  197. package/src/views/releases/v2.5.0.ejs +51 -0
  198. package/src/views/releases/v2.6.0.ejs +35 -0
  199. package/src/views/releases/v2.7.0.ejs +32 -0
  200. package/src/views/releases/v2.8.0.ejs +36 -0
  201. package/src/views/releases/v2.8.1.ejs +7 -0
  202. package/src/views/releases/v2.8.2.ejs +26 -0
  203. package/src/views/releases/v2.9.0.ejs +32 -0
  204. package/src/views/releases/v2.9.1.ejs +10 -0
  205. package/src/views/releases.ejs +26 -0
  206. package/src/views/sitemap.ejs +36 -0
  207. package/src/views/support.ejs +14 -0
package/src/cli/cli.js ADDED
@@ -0,0 +1,420 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs-extra'),
4
+ yargs = require('yargs'),
5
+ aliases = {
6
+ d: 'debug',
7
+ h: 'host',
8
+ i: 'allowInjection',
9
+ l: 'localOnly',
10
+ m: 'mock',
11
+ p: 'port',
12
+ r: 'removeProxies',
13
+ w: 'ipWhitelist',
14
+ o: 'origin'
15
+ },
16
+ options = {
17
+ rcfile: {
18
+ description: 'the startup file ("run commands") containing configuration options',
19
+ nargs: 1,
20
+ type: 'string',
21
+ global: false
22
+ },
23
+ port: {
24
+ default: 2525,
25
+ description: 'the port to run the mountebank server on',
26
+ nargs: 1,
27
+ type: 'number',
28
+ alias: aliases.p,
29
+ global: false
30
+ },
31
+ host: {
32
+ description: 'the hostname to bind the mountebank server to',
33
+ nargs: 1,
34
+ type: 'string',
35
+ alias: aliases.h,
36
+ global: false
37
+ },
38
+ configfile: {
39
+ description: 'file to load imposters from, can be an EJS template',
40
+ nargs: 1,
41
+ type: 'string',
42
+ global: false
43
+ },
44
+ noParse: {
45
+ default: false,
46
+ description: 'prevent rendering the configfile as an EJS template',
47
+ type: 'boolean',
48
+ global: false
49
+ },
50
+ formatter: {
51
+ default: 'mountebank-formatters',
52
+ description: 'the custom formatter module to interpret --configfile formats and to use during mb save operations',
53
+ nargs: 1,
54
+ type: 'string',
55
+ global: false
56
+ },
57
+ datadir: {
58
+ description: 'the directory to persist all imposter data',
59
+ nargs: 1,
60
+ type: 'string',
61
+ global: false
62
+ },
63
+ impostersRepository: {
64
+ description: 'path to custom imposters repository',
65
+ nargs: 1,
66
+ type: 'string',
67
+ global: false
68
+ },
69
+ pidfile: {
70
+ default: 'mb.pid',
71
+ description: 'where the pid is stored for the stop command',
72
+ nargs: 1,
73
+ type: 'string',
74
+ global: false
75
+ },
76
+ nologfile: {
77
+ default: false,
78
+ description: 'prevent logging to the filesystem',
79
+ type: 'boolean',
80
+ global: false
81
+ },
82
+ logfile: {
83
+ default: 'mb.log',
84
+ description: 'path to use for logging',
85
+ nargs: 1,
86
+ type: 'string',
87
+ global: false
88
+ },
89
+ loglevel: {
90
+ default: 'info',
91
+ description: 'level for logging',
92
+ nargs: 1,
93
+ type: 'string',
94
+ choices: ['debug', 'info', 'warn', 'error'],
95
+ global: false
96
+ },
97
+ log: {
98
+ description: 'customizable logging configuration',
99
+ nargs: 1,
100
+ type: 'string',
101
+ global: false
102
+ },
103
+ allowInjection: {
104
+ default: false,
105
+ description: 'set to allow JavaScript injection',
106
+ type: 'boolean',
107
+ alias: aliases.i,
108
+ global: false
109
+ },
110
+ localOnly: {
111
+ default: false,
112
+ description: 'allow connections only from localhost',
113
+ type: 'boolean',
114
+ alias: aliases.l,
115
+ global: false
116
+ },
117
+ ipWhitelist: {
118
+ default: '*',
119
+ description: 'pipe-delimited list of allowed IP addresses',
120
+ alias: aliases.w,
121
+ global: false
122
+ },
123
+ mock: {
124
+ default: false,
125
+ description: '[Deprecated] remember requests (use "recordRequests" per imposter instead)',
126
+ type: 'boolean',
127
+ alias: aliases.m,
128
+ global: false
129
+ },
130
+ debug: {
131
+ default: false,
132
+ description: 'include stub match information in imposter retrievals for debugging purposes',
133
+ type: 'boolean',
134
+ alias: aliases.d,
135
+ global: false
136
+ },
137
+ savefile: {
138
+ default: 'mb.json',
139
+ description: 'file to save imposters to',
140
+ nargs: 1,
141
+ type: 'string',
142
+ global: false
143
+ },
144
+ protofile: {
145
+ default: 'protocols.json',
146
+ description: 'file to load custom protocol implementations from',
147
+ nargs: 1,
148
+ type: 'string',
149
+ global: false
150
+ },
151
+ removeProxies: {
152
+ default: false,
153
+ description: 'removes proxies from the configuration when using the save command',
154
+ type: 'boolean',
155
+ alias: aliases.r,
156
+ global: false
157
+ },
158
+ origin: {
159
+ default: false,
160
+ description: 'safe origin for CORS requests',
161
+ type: 'string',
162
+ global: false
163
+ },
164
+ apikey: {
165
+ description: 'An optional API key. When provided, a user must add an API key to the header.',
166
+ default: null,
167
+ type: 'string',
168
+ global: false
169
+ }
170
+ },
171
+ startOptions = {
172
+ port: options.port,
173
+ host: options.host,
174
+ configfile: options.configfile,
175
+ noParse: options.noParse,
176
+ formatter: options.formatter,
177
+ datadir: options.datadir,
178
+ impostersRepository: options.impostersRepository,
179
+ pidfile: options.pidfile,
180
+ nologfile: options.nologfile,
181
+ logfile: options.logfile,
182
+ loglevel: options.loglevel,
183
+ log: options.log,
184
+ allowInjection: options.allowInjection,
185
+ localOnly: options.localOnly,
186
+ ipWhitelist: options.ipWhitelist,
187
+ mock: options.mock,
188
+ debug: options.debug,
189
+ protofile: options.protofile,
190
+ origin: options.origin,
191
+ rcfile: options.rcfile,
192
+ apikey: options.apikey
193
+ },
194
+ argv = yargs
195
+ .usage('Usage: mb [command=start] [options...]')
196
+ .command('start', 'Starts the server (default command)', startYargs => {
197
+ startYargs
198
+ .usage('Usage: mb [start] [options...]')
199
+ .help('help')
200
+ .wrap(null)
201
+ .options(startOptions)
202
+ .example('mb start --port 3000 --allowInjection --loglevel debug',
203
+ 'Starts on port 3000, allowing injections and turning on debug logging')
204
+ .example("mb --ipWhitelist '192.168.1.20|192.158.1.21'",
205
+ 'Starts on port 2525 with the given ip whitelist')
206
+ .epilog('Note on the last example that start is assumed if no command is specified\n\nFor more information, see http://www.mbtest.dev/docs/commandLine');
207
+ })
208
+ .command('stop', 'Stops the server', stopYargs => {
209
+ stopYargs
210
+ .usage('Usage: mb stop [--pidfile file.pid]')
211
+ .help('help')
212
+ .wrap(null)
213
+ .options({ pidfile: options.pidfile, rcfile: options.rcfile })
214
+ .example('mb stop', 'Stops the process identified in mb.pid')
215
+ .example('mb stop --pidfile test.pid', 'Stops the process identified in test.pid')
216
+ .epilog('For more information, see http://www.mbtest.dev/docs/commandLine');
217
+ })
218
+ .command('restart', "Stops the server if it's running and restarts it", restartYargs => {
219
+ restartYargs
220
+ .usage('Usage: mb restart [options...]')
221
+ .help('help')
222
+ .wrap(null)
223
+ .options(startOptions)
224
+ .example('mb restart --port 3000 --allowInjection --loglevel debug',
225
+ 'Restarts on port 3000, allowing injections and turning on debug logging')
226
+ .example('mb restart --pidfile test.pid', 'Stops the process identified in test.pid and restarts on port 2525')
227
+ .epilog('The port does not have to match the currently running instance, but the pidfile must match\n\nFor more information, see http://www.mbtest.dev/docs/commandLine');
228
+ })
229
+ .command('save', 'Saves current imposter configuration to a config file', saveYargs => {
230
+ saveYargs
231
+ .usage('Usage: mb save [options...]\n\nSaves a snapshot of your configuration, including new responses created by proxies')
232
+ .help('help')
233
+ .wrap(null)
234
+ .options({
235
+ port: options.port,
236
+ savefile: options.savefile,
237
+ formatter: options.formatter,
238
+ removeProxies: options.removeProxies,
239
+ host: options.host,
240
+ rcfile: options.rcfile
241
+ })
242
+ .example('mb save --savefile config.json --removeProxies --port 3000',
243
+ 'Saves the config without proxies into config.json by querying port 3000')
244
+ .example('mb save', 'Saves the config as is into mb.json by querying port 2525')
245
+ .epilog('For more information, see http://www.mbtest.dev/docs/commandLine');
246
+ })
247
+ .command('replay',
248
+ 'Switches from record mode to replay by removing proxies',
249
+ replayYargs => {
250
+ replayYargs
251
+ .usage('Usage: mb replay [--port 3000]')
252
+ .help('help')
253
+ .wrap(null)
254
+ .options({ port: options.port, host: options.host, rcfile: options.rcfile })
255
+ .example('mb replay --port 3000',
256
+ 'Resets the configuration of mountebank running on port 3000 to remove all proxies')
257
+ .example('mb replay', 'Resets the configuration of mountebank running on port 2525 to remove all proxies')
258
+ .epilog('For more information, see http://www.mbtest.dev/docs/commandLine');
259
+ })
260
+ .version()
261
+ .wrap(null)
262
+ .epilog('mb [command] --help provides more details\n\nFor more information, see http://www.mbtest.dev/docs/commandLine')
263
+ .argv;
264
+
265
+ function fixAliases (args) {
266
+ Object.keys(args).forEach(key => {
267
+ if (aliases[key]) {
268
+ args[aliases[key]] = args[key];
269
+ delete args[key];
270
+ }
271
+ });
272
+ }
273
+
274
+ // Unfortunately, while yargs defaults correctly when the command is present, I couldn't
275
+ // figure out how to make it default when the command is absent, which I need to default to start.
276
+ // You can set what yargs considers to be the default command by .command(['start', '*']), but
277
+ // that changes the help docs in ways I don't want.
278
+ function fixDefaults (args) {
279
+ if (args._.length === 0) {
280
+ Object.keys(startOptions).forEach(key => {
281
+ if (typeof args[key] === 'undefined') {
282
+ args[key] = startOptions[key].default;
283
+ }
284
+ });
285
+ }
286
+ }
287
+
288
+ function fixIPWhitelist (command, args) {
289
+ if (command === 'start' || command === 'restart') {
290
+ args.ipWhitelist = args.ipWhitelist.split('|');
291
+ }
292
+ }
293
+
294
+ function rawArgvIndexFor (key) {
295
+ // Use raw process.argv to ensure user actually passed the parameter, bypassing
296
+ // yargs defaulting. Also check alias.
297
+ let index = process.argv.indexOf(`--${key}`);
298
+ if (index < 0) {
299
+ const aliasKeys = Object.keys(aliases);
300
+ for (let i = 0; i < aliasKeys.length; i += 1) {
301
+ let alias = aliasKeys[i];
302
+ if (aliases[alias] === key) {
303
+ index = process.argv.indexOf(`-${alias}`);
304
+ }
305
+ }
306
+ }
307
+ return index;
308
+ }
309
+
310
+ function argIsPassedOnCLI (key) {
311
+ return rawArgvIndexFor(key) >= 0;
312
+ }
313
+
314
+ function argvOrDefaultFor (key) {
315
+ const index = rawArgvIndexFor(key);
316
+ if (index >= 0) {
317
+ return process.argv[index + 1];
318
+ }
319
+ else {
320
+ return options[key].default;
321
+ }
322
+ }
323
+
324
+ function parseLogConfiguration (args) {
325
+ // Not defaulted in yargs so we can test yargs value without defaulting interfering.
326
+ // This is needed for backwards compatibility with older CLI options.
327
+ const defaultConfig = {
328
+ level: 'info',
329
+ transports: {
330
+ console: {
331
+ colorize: true,
332
+ format: '%level: %message'
333
+ },
334
+ file: {
335
+ path: 'mb.log',
336
+ format: 'json'
337
+ }
338
+ }
339
+ };
340
+
341
+ if (typeof args.log === 'string') {
342
+ try {
343
+ args.log = JSON.parse(args.log);
344
+ }
345
+ catch (ex) {
346
+ console.error(`Cannot parse --log as JSON: ${ex}`);
347
+ args.log = defaultConfig;
348
+ }
349
+ }
350
+ else {
351
+ // Backwards compatibility with older CLI options. Using raw process.argv
352
+ // to ensure user actually passed the parameter, bypassing yargs defaulting
353
+ args.log = defaultConfig;
354
+ args.log.level = argvOrDefaultFor('loglevel');
355
+ args.log.transports.file.path = argvOrDefaultFor('logfile');
356
+ if (argIsPassedOnCLI('nologfile')) {
357
+ delete args.log.transports.file;
358
+ }
359
+ }
360
+
361
+ // Remove the old values to not confuse users retrieving configuration later
362
+ delete args.loglevel;
363
+ delete args.logfile;
364
+ delete args.nologfile;
365
+ }
366
+
367
+ // Prevent noise from being logged
368
+ function removeNoise (args) {
369
+ delete args._;
370
+ delete args.$0;
371
+ delete args.version;
372
+ }
373
+
374
+ function addStartupFile (args) {
375
+ if (typeof args.rcfile !== 'string' || args.rcfile === '') {
376
+ return;
377
+ }
378
+ if (!fs.existsSync(args.rcfile)) {
379
+ console.error(`Cannot find rcfile ${args.rcfile}`);
380
+ return;
381
+ }
382
+ try {
383
+ const rc = JSON.parse(fs.readFileSync(args.rcfile));
384
+
385
+ // CLI options take priority over anything in the rcfile.
386
+ Object.keys(rc).forEach(key => {
387
+ if (!argIsPassedOnCLI(key)) {
388
+ args[key] = rc[key];
389
+ }
390
+ });
391
+ }
392
+ catch (ex) {
393
+ console.error(`Cannot parse rcfile ${args.rcfile}: ${ex}`);
394
+ }
395
+ }
396
+
397
+ function getCommandLineArgs (command, args) {
398
+ fixAliases(args);
399
+ fixDefaults(args);
400
+ fixIPWhitelist(command, args);
401
+ parseLogConfiguration(args);
402
+ removeNoise(args);
403
+ addStartupFile(args);
404
+ return args;
405
+ }
406
+
407
+ function error (message) {
408
+ console.error(`${message}\n`);
409
+ yargs.showHelp();
410
+ process.exit(1); // eslint-disable-line no-process-exit
411
+ }
412
+
413
+ function help () {
414
+ yargs.showHelp();
415
+ }
416
+
417
+ const command = argv._.length === 0 ? 'start' : argv._[0],
418
+ args = getCommandLineArgs(command, argv);
419
+
420
+ module.exports = { command, args, error, help };
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ const helpers = require('../util/helpers');
4
+
5
+ /**
6
+ * The controller that exposes the mountebank configuration for the running process
7
+ * @module
8
+ */
9
+
10
+ /**
11
+ * Creates the config controller
12
+ * @param {string} version - The version of the currently running process
13
+ * @param {Object} options - The command line options used to start mb
14
+ * @returns {Object}
15
+ */
16
+ function create (version, options) {
17
+ const publicOptions = helpers.clone(options);
18
+
19
+ delete publicOptions.version;
20
+
21
+ if (!publicOptions.mock) {
22
+ delete publicOptions.mock; // deprecated
23
+ }
24
+
25
+ // On some OS's, it duplicates camelCase as hypen-case (e.g. noParse and no-parse)
26
+ // I assume this was a change in yargs at some point
27
+ for (var prop in publicOptions) {
28
+ if (prop.indexOf('-') > 0) {
29
+ delete publicOptions[prop];
30
+ }
31
+ }
32
+
33
+ /**
34
+ * The method that responds to GET /config
35
+ * @memberOf module:controllers/configController#
36
+ * @param {Object} request - The HTTP request
37
+ * @param {Object} response - The HTTP response
38
+ */
39
+ function get (request, response) {
40
+ const config = {
41
+ version,
42
+ options: publicOptions,
43
+ process: {
44
+ nodeVersion: process.version,
45
+ architecture: process.arch,
46
+ platform: process.platform,
47
+ rss: process.memoryUsage().rss,
48
+ heapTotal: process.memoryUsage().heapTotal,
49
+ heapUsed: process.memoryUsage().heapUsed,
50
+ uptime: process.uptime(),
51
+ cwd: process.cwd()
52
+ }
53
+ };
54
+
55
+ response.format({
56
+ json: () => response.send(config),
57
+ html: () => response.render('config', config)
58
+ });
59
+ }
60
+
61
+ return { get };
62
+ }
63
+
64
+ module.exports = { create };
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+
3
+ const path = require('path'),
4
+ fsExtra = require('fs-extra'),
5
+ ejs = require('ejs'),
6
+ helpers = require('../util/helpers.js');
7
+
8
+ /**
9
+ * The controller that exposes information about releases
10
+ * @module
11
+ */
12
+
13
+ /**
14
+ * @param {Object} releases - The object represented in the releases.json file
15
+ * @param {Object} options - The command line options used to start mountebank
16
+ * @returns {Object} The controller
17
+ */
18
+ function create (releases) {
19
+ const feedReleases = helpers.clone(releases);
20
+
21
+ // Init once since we hope many consumers poll the heroku feed and we don't have monitoring
22
+ feedReleases.reverse();
23
+
24
+ function releaseViewFor (version) {
25
+ return `releases/${version}.ejs`;
26
+ }
27
+
28
+ function releaseFilenameFor (version) {
29
+ return path.join(__dirname, '/../views/', releaseViewFor(version));
30
+ }
31
+
32
+ function versionInWhitelist (version) {
33
+ // Prevent path traversal attack like v2.3.0%2f..%2f..%2f_header
34
+ return feedReleases.some(release => version.toLowerCase() === release.version);
35
+ }
36
+
37
+ /**
38
+ * The function that responds to GET /feed
39
+ * @memberOf module:controllers/feedController#
40
+ * @param {Object} request - The HTTP request
41
+ * @param {Object} response - The HTTP response
42
+ */
43
+ function getFeed (request, response) {
44
+ const page = parseInt(request.query.page || '1'),
45
+ nextPage = page + 1,
46
+ entriesPerPage = 10,
47
+ hasNextPage = feedReleases.slice((nextPage * entriesPerPage) - 10, entriesPerPage * nextPage).length > 0,
48
+ config = {
49
+ host: request.headers.host,
50
+ releases: feedReleases.slice(page * entriesPerPage - 10, entriesPerPage * page),
51
+ hasNextPage: hasNextPage,
52
+ nextLink: `/feed?page=${nextPage}`
53
+ };
54
+
55
+ // I'd prefer putting this as an include in the view, but EJS doesn't support dynamic includes
56
+ config.releases.forEach(release => {
57
+ if (!release.view) {
58
+ const contents = fsExtra.readFileSync(releaseFilenameFor(release.version), { encoding: 'utf8' });
59
+ release.view = ejs.render(contents, {
60
+ host: request.headers.host,
61
+ releaseMajorMinor: release.version.replace(/^v(\d+\.\d+).*/, '$1'),
62
+ releaseVersion: release.version.replace('v', '')
63
+ });
64
+ }
65
+ });
66
+
67
+ response.type('application/atom+xml');
68
+ response.render('feed', config);
69
+ }
70
+
71
+ /**
72
+ * The function that responds to GET /releases
73
+ * @memberOf module:controllers/feedController#
74
+ * @param {Object} request - The HTTP request
75
+ * @param {Object} response - The HTTP response
76
+ */
77
+ function getReleases (request, response) {
78
+ response.render('releases', { releases: feedReleases });
79
+ }
80
+
81
+ /**
82
+ * The function that responds to GET /releases/:version
83
+ * @memberOf module:controllers/feedController#
84
+ * @param {Object} request - The HTTP request
85
+ * @param {Object} response - The HTTP response
86
+ */
87
+ function getRelease (request, response) {
88
+ const version = request.params.version,
89
+ config = {
90
+ host: request.headers.host,
91
+ releaseMajorMinor: version.replace(/^v(\d+\.\d+).*/, '$1'),
92
+ releaseVersion: version.replace('v', '')
93
+ };
94
+
95
+ if (versionInWhitelist(version) && fsExtra.existsSync(releaseFilenameFor(version))) {
96
+ response.render('_header', config, (headerError, header) => {
97
+ if (headerError) { throw headerError; }
98
+ response.render(releaseViewFor(version), config, (bodyError, body) => {
99
+ if (bodyError) { throw bodyError; }
100
+ response.render('_footer', config, (footerError, footer) => {
101
+ if (footerError) { throw footerError; }
102
+ response.send(header + body + footer);
103
+ });
104
+ });
105
+ });
106
+ }
107
+ else {
108
+ response.status(404).send('No such release');
109
+ }
110
+ }
111
+
112
+ return { getFeed, getReleases, getRelease };
113
+ }
114
+
115
+ module.exports = { create };
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const date = require('../util/date.js');
4
+
5
+ /**
6
+ * The controller that returns the base mountebank hypermedia
7
+ * @module
8
+ */
9
+
10
+ /**
11
+ * Creates the home controller
12
+ * @param {Object} releases - The releases.json file
13
+ * @returns {Object} The controller
14
+ */
15
+ function create (releases) {
16
+ function createNotice (release) {
17
+ return {
18
+ version: release.version,
19
+ when: date.howLongAgo(release.date)
20
+ };
21
+ }
22
+
23
+ function isRecent (notice) {
24
+ return notice.when !== '';
25
+ }
26
+
27
+ /**
28
+ * The function that responds to GET /
29
+ * @memberOf module:controllers/homeController#
30
+ * @param {Object} request - the HTTP request
31
+ * @param {Object} response - the HTTP response
32
+ */
33
+ function get (request, response) {
34
+ const hypermedia = {
35
+ _links: {
36
+ imposters: { href: '/imposters' },
37
+ config: { href: '/config' },
38
+ logs: { href: '/logs' }
39
+ }
40
+ },
41
+ notices = releases.map(createNotice).filter(isRecent),
42
+ viewNotices = [];
43
+
44
+ if (notices.length > 0) {
45
+ notices.reverse();
46
+ viewNotices.push(notices[0]);
47
+ }
48
+
49
+ response.format({
50
+ json: () => { response.send(hypermedia); },
51
+ html: () => { response.render('index', { notices: viewNotices }); }
52
+ });
53
+ }
54
+
55
+ return { get };
56
+ }
57
+
58
+ module.exports = { create };