diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f03b9f..29ee023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.0.4 (2018-02-06) + +### Features: + +- **Capture Mode**: Now you can automatically generate mock files from your api responses. +- **Verbose Mode**: `--verbose` cli parameter added to use verbose mode of connect-api-mocker. + ## 1.0.3 (2018-01-12) ### Features: diff --git a/README.md b/README.md index d895d6b..36f17db 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,13 @@ That command will start to serve your mocks on port `9090` by default. ## Arguments -`mockit` command has 3 available arguments. +`mockit` command has 4 available arguments. - [-p | --port] with default value `9090`, + [-p | --port] with default value `9090`, [-f | --fromPath] with default value `/`, [-t | --toPath] with default value `` + [-c | --capture] with default value `false` + [-v | --verbose] with default value `false` These arguments are optional. You can use `mockit` command with any one of them or any combination of them. @@ -36,9 +38,9 @@ You can see usage examples below: Or you can combine any of them like: -`mockit --port=8989 --fromPath=/api --toPath=/mapi` +`mockit --port=8989 --fromPath=/api --toPath=/mapi` -Or +Or `mockit -p 8989 -f '/api -t '/mapi'` @@ -96,3 +98,22 @@ module.exports = { } ``` +### Capture mode + +With capture mode, you can automatically create mock files from your api origin responses over proxy. You can enable capture mode by `--capture` (or `-c`) command line parameter or `capture` property in config file: + +```js +module.exports = { + port: 9090, + map: { + '/api': { + target: 'mocks/api', + proxy: 'https://api.yourdomain.com', + capture: true + } + } +} +``` + +When capture mode enabled, if you don't have a mock file for a request and if you have a proxy definition, a mock file will automatically generated for you for successful responses from your origin. + diff --git a/package.json b/package.json index ba97f53..e1716e3 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,15 @@ { "name": "cli-api-mocker", - "version": "1.0.3", + "version": "1.0.4", "description": "CLI wrapper for connect-api-mocker", "main": "src/index.js", "dependencies": { "command-line-args": "^4.0.7", - "connect-api-mocker": "^1.3.5", + "connect-api-mocker": "^1.3.6", "cors": "^2.8.4", "express": "^4.15.4", - "http-proxy-middleware": "^0.17.4" + "http-proxy-middleware": "^0.17.4", + "mkdirp": "^0.5.1" }, "devDependencies": {}, "scripts": { diff --git a/src/index.js b/src/index.js index 7d201ca..6c7eed7 100755 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,9 @@ var apiMocker = require('connect-api-mocker'); var bodyParser = require('body-parser') var proxy = require('http-proxy-middleware'); var cors = require('cors'); +var fs = require('fs'); +var pth = require('path'); +var mkdirp = require('mkdirp'); var commandLineArgs = require('command-line-args'); var defaultPortValue = 9090; var defaultFromPathValue = '/'; @@ -12,64 +15,117 @@ var defaultToPathValue = ''; var optionDefinitions = [ { name: 'port', alias: 'p', type: Number, defaultValue: defaultPortValue }, { name: 'fromPath', alias: 'f', type: String, defaultValue: defaultFromPathValue }, - { name: 'toPath', alias: 't', type: String, defaultValue: defaultToPathValue } + { name: 'toPath', alias: 't', type: String, defaultValue: defaultToPathValue }, + { name: 'capture', alias: 'c', type: Boolean, defaultValue: false }, + { name: 'verbose', alias: 'v', type: Boolean, defaultValue: false } ]; +function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); +} + var options = commandLineArgs(optionDefinitions); var app = express(); var mapping = {}; -mapping[options.fromPath] = options.toPath; +mapping[options.fromPath] = { + target: options.toPath +}; var defaultConfig = { - port: options.port, - map: mapping + port: options.port, + map: mapping }; var config = defaultConfig; try { - var loadedConfig = require(path.resolve(process.cwd() , 'mock.config.js')); - console.log('Config file found mocking!'); + var loadedConfig = require(path.resolve(process.cwd() , 'mock.config.js')); + console.log('Config file found mocking!'); - if (config.port === defaultPortValue && loadedConfig.port) { - config.port = loadedConfig.port; - } + if (config.port === defaultPortValue && loadedConfig.port) { + config.port = loadedConfig.port; + } - var mapKeys = Object.keys(config.map); - if (loadedConfig.map) { - mapKeys.forEach(function (mapKey) { - loadedConfig.map[mapKey] = config.map[mapKey]; - }); - config.map = loadedConfig.map; - } + var mapKeys = Object.keys(config.map); + if (loadedConfig.map) { + mapKeys.forEach(function (mapKey) { + loadedConfig.map[mapKey] = config.map[mapKey]; + }); + config.map = loadedConfig.map; + } } catch (error) { - // there is no config + // there is no config } -app.use(cors()) +if (options.verbose) { + Object.keys(config.map).forEach(function (key) { + config.map[key].verbose = true; + }); +} -for(var path in config.map) { +app.use(cors()); - var conf = config.map[path]; +for(var path in config.map) { + (function (basePath) { + var conf = config.map[basePath]; if (conf.proxy) { - conf.nextOnNotFound = true; + conf.nextOnNotFound = true; } - app.use(path, apiMocker(conf)); - console.log(`Mocking enabled: ${path} => ${conf}`); + app.use(basePath, apiMocker(conf)); + console.log(`Mocking enabled: ${basePath} => ${conf.target || conf}`); if (conf.proxy) { - console.log(`Proxy enabled: ${path} => ${conf.proxy}`); - if (typeof conf.proxy == 'string') { - config.proxy = { - target: conf.proxy - } + console.log(`Proxy enabled: ${basePath} => ${conf.proxy}`); + if (typeof conf.proxy == 'string') { + conf.proxy = { + target: conf.proxy } - app.use(path, proxy(conf.proxy)); + } + + if (conf.capture || options.capture) { + console.log('Capture Mode enabled for mocks!'); + + conf.proxy.onProxyRes = function(proxyRes, req, res) { + var body = ""; + if (proxyRes.statusCode < 404) { + proxyRes.on('data', function(data) { + data = data.toString('utf-8'); + body += data; + }); + + proxyRes.on('end', function() { + var requestedFilePath = req.path.replace(new RegExp('^(\/)?' + escapeRegExp(basePath)), '') + var targetPath = pth.join(conf.target || conf, requestedFilePath); + + var contentType = 'json'; + if (proxyRes.headers['content-type'].indexOf('xml') > -1) { + contentType = 'xml'; + } + + mkdirp.sync(targetPath); + + var targetFile = pth.join(targetPath, req.method + '.' + contentType); + + if (!fs.existsSync(targetFile)) { + fs.writeFileSync(targetFile, body); + console.log('[Capture Mode] New mock file saved to ' + targetFile); + } + }); + } + } + } + + app.use(basePath, proxy(conf.proxy)); + } else { + if (conf.capture || options.capture) { + console.error('You can not use capture mode without defining a proxy.'); + } } + })(path); } app.listen(config.port, function () {