mirror of
https://github.com/typicode/json-server.git
synced 2025-07-27 20:23:34 +08:00
Refactor CLI and add tests
This commit is contained in:
165
bin/index.js
165
bin/index.js
@ -1,165 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
var updateNotifier = require('update-notifier')
|
||||
var _db = require('underscore-db')
|
||||
var yargs = require('yargs')
|
||||
var chalk = require('chalk')
|
||||
var got = require('got')
|
||||
var pkg = require('../package.json')
|
||||
var jsonServer = require('../src')
|
||||
var jsMockGenerator = null
|
||||
|
||||
updateNotifier({packageName: pkg.name, packageVersion: pkg.version}).notify()
|
||||
|
||||
// Parse arguments
|
||||
var argv = yargs
|
||||
.usage('$0 [options] <source>')
|
||||
.options({
|
||||
port: {
|
||||
alias: 'p',
|
||||
description: 'Set port',
|
||||
default: 3000
|
||||
},
|
||||
host: {
|
||||
alias: 'H',
|
||||
description: 'Set host',
|
||||
default: '0.0.0.0'
|
||||
},
|
||||
watch: {
|
||||
alias: 'w',
|
||||
description: 'Reload database on JSON file change'
|
||||
},
|
||||
routes: {
|
||||
alias: 'r',
|
||||
description: 'Load routes file'
|
||||
},
|
||||
id: {
|
||||
description: 'Set database id property (e.g. _id)',
|
||||
default: 'id'
|
||||
}
|
||||
})
|
||||
.boolean('watch')
|
||||
.help('help').alias('help', 'h')
|
||||
.version(pkg.version).alias('version', 'v')
|
||||
.example('$0 db.json', '')
|
||||
.example('$0 file.js', '')
|
||||
.example('$0 http://example.com/db.json', '')
|
||||
.epilog('https://github.com/typicode/json-server')
|
||||
.require(1, 'Missing <source> argument')
|
||||
.argv
|
||||
|
||||
function showResources (hostname, port, object) {
|
||||
for (var prop in object) {
|
||||
console.log(chalk.gray(' http://' + hostname + ':' + port + '/') + chalk.cyan(prop))
|
||||
}
|
||||
}
|
||||
|
||||
function start (object, filename) {
|
||||
var port = process.env.PORT || argv.port
|
||||
var hostname = argv.host === '0.0.0.0' ? 'localhost' : argv.host
|
||||
|
||||
console.log()
|
||||
showResources(hostname, port, object)
|
||||
console.log()
|
||||
console.log(
|
||||
'You can now go to ' + chalk.gray('http://' + hostname + ':' + port)
|
||||
)
|
||||
console.log()
|
||||
console.log(
|
||||
'Enter ' + chalk.cyan('s') + ' at any time to create a snapshot of the db'
|
||||
)
|
||||
|
||||
// Snapshot
|
||||
process.stdin.resume()
|
||||
process.stdin.setEncoding('utf8')
|
||||
process.stdin.on('data', function (chunk) {
|
||||
if (chunk.trim().toLowerCase() === 's') {
|
||||
var file = 'db-' + Date.now() + '.json'
|
||||
_db.save(object, file)
|
||||
console.log('Saved snapshot to ' + chalk.cyan(file) + '\n')
|
||||
}
|
||||
})
|
||||
|
||||
// Router
|
||||
var router = jsonServer.router(filename ? filename : object)
|
||||
|
||||
// Watcher
|
||||
if (argv.watch) {
|
||||
console.log('Watching', chalk.cyan(source))
|
||||
|
||||
var db = router.db
|
||||
var watchedDir = path.dirname(filename)
|
||||
var watchedFile = path.basename(filename)
|
||||
|
||||
fs.watch(watchedDir, function (event, changedFile) {
|
||||
// lowdb generates 'rename' event on watchedFile
|
||||
// using it to know if file has been modified by the user
|
||||
if ((event === 'change' || event === 'rename') && (changedFile === watchedFile || changedFile === source)) {
|
||||
console.log(chalk.cyan(source), 'has changed, reloading database')
|
||||
|
||||
try {
|
||||
if (filename) {
|
||||
db.object = JSON.parse(fs.readFileSync(filename))
|
||||
} else {
|
||||
require.cache[jsMockGenerator] = null
|
||||
db.object = require(jsMockGenerator)()
|
||||
}
|
||||
showResources(hostname, port, db.object)
|
||||
} catch (e) {
|
||||
console.log('Can\'t parse', chalk.cyan(source))
|
||||
console.log(e.message)
|
||||
}
|
||||
|
||||
console.log()
|
||||
}
|
||||
})
|
||||
}
|
||||
console.log()
|
||||
|
||||
var server = jsonServer.create()
|
||||
server.use(jsonServer.defaults)
|
||||
|
||||
// Rewriter
|
||||
if (argv.routes) {
|
||||
var routes = JSON.parse(fs.readFileSync(process.cwd() + '/' + argv.routes))
|
||||
var rewriter = jsonServer.rewriter(routes)
|
||||
server.use(rewriter)
|
||||
}
|
||||
|
||||
server.use(router)
|
||||
|
||||
// Custom id
|
||||
router.db._.id = argv.id
|
||||
|
||||
server.listen(port, argv.host)
|
||||
}
|
||||
|
||||
// Set file and port
|
||||
var source = argv._[0]
|
||||
|
||||
// Say hi, load file and start server
|
||||
console.log(chalk.cyan(' {^_^} Hi!\n'))
|
||||
console.log('Loading database from ' + chalk.cyan(source))
|
||||
|
||||
// Remote source
|
||||
if (/^(http|https):/.test(source)) {
|
||||
got(source, function (err, data) {
|
||||
if (err) {
|
||||
console.log('Error', err)
|
||||
process.exit(1)
|
||||
}
|
||||
var object = JSON.parse(data)
|
||||
start(object)
|
||||
})
|
||||
// JSON file
|
||||
} else if (/\.json$/.test(source)) {
|
||||
var filename = process.cwd() + '/' + source
|
||||
var object = require(filename)
|
||||
start(object, filename)
|
||||
// JS file
|
||||
} else if (/\.js$/.test(source)) {
|
||||
jsMockGenerator = process.cwd() + '/' + source
|
||||
var object = require(jsMockGenerator)()
|
||||
start(object)
|
||||
}
|
||||
require('../src/cli')()
|
||||
|
@ -2,7 +2,7 @@
|
||||
"name": "json-server",
|
||||
"version": "0.7.20",
|
||||
"description": "Serves JSON files through REST routes.",
|
||||
"main": "./src/index.js",
|
||||
"main": "./src/server/index.js",
|
||||
"bin": "./bin/index.js",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
@ -13,7 +13,7 @@
|
||||
"cors": "^2.3.0",
|
||||
"errorhandler": "^1.2.0",
|
||||
"express": "^4.9.5",
|
||||
"got": "^1.2.2",
|
||||
"got": "^3.3.0",
|
||||
"lodash": "^3.9.2",
|
||||
"lowdb": "^0.10.0",
|
||||
"method-override": "^2.1.2",
|
||||
@ -21,17 +21,18 @@
|
||||
"node-uuid": "^1.4.2",
|
||||
"pluralize": "^1.1.2",
|
||||
"underscore-db": "^0.9.0",
|
||||
"update-notifier": "^0.2.2",
|
||||
"update-notifier": "^0.5.0",
|
||||
"yargs": "^3.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"husky": "^0.6.1",
|
||||
"mocha": "^2.2.4",
|
||||
"rimraf": "^2.4.1",
|
||||
"standard": "^3.8.0",
|
||||
"supertest": "~0.8.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "standard && mocha -R spec test",
|
||||
"test": "NODE_ENV=test mocha -R spec test/**/*.js && standard",
|
||||
"start": "node bin",
|
||||
"prepush": "npm t"
|
||||
},
|
||||
|
48
src/cli/index.js
Normal file
48
src/cli/index.js
Normal file
@ -0,0 +1,48 @@
|
||||
var updateNotifier = require('update-notifier')
|
||||
var yargs = require('yargs')
|
||||
var run = require('./run')
|
||||
var pkg = require('../../package.json')
|
||||
|
||||
module.exports = function () {
|
||||
|
||||
updateNotifier({ pkg: pkg }).notify()
|
||||
|
||||
var argv = yargs
|
||||
.usage('$0 [options] <source>')
|
||||
.options({
|
||||
port: {
|
||||
alias: 'p',
|
||||
description: 'Set port',
|
||||
default: 3000
|
||||
},
|
||||
host: {
|
||||
alias: 'H',
|
||||
description: 'Set host',
|
||||
default: '0.0.0.0'
|
||||
},
|
||||
watch: {
|
||||
alias: 'w',
|
||||
description: 'Watch file(s)'
|
||||
},
|
||||
routes: {
|
||||
alias: 'r',
|
||||
description: 'Load routes file'
|
||||
},
|
||||
id: {
|
||||
description: 'Set database id property (e.g. _id)',
|
||||
default: 'id'
|
||||
}
|
||||
})
|
||||
.boolean('watch')
|
||||
.help('help').alias('help', 'h')
|
||||
.version(pkg.version).alias('version', 'v')
|
||||
.example('$0 db.json', '')
|
||||
.example('$0 file.js', '')
|
||||
.example('$0 http://example.com/db.json', '')
|
||||
.epilog('https://github.com/typicode/json-server')
|
||||
.require(1, 'Missing <source> argument')
|
||||
.argv
|
||||
|
||||
run(argv)
|
||||
|
||||
}
|
102
src/cli/run.js
Normal file
102
src/cli/run.js
Normal file
@ -0,0 +1,102 @@
|
||||
var fs = require('fs')
|
||||
var chalk = require('chalk')
|
||||
var is = require('./utils/is')
|
||||
var load = require('./utils/load')
|
||||
var watch = require('./watch')
|
||||
var jsonServer = require('../server')
|
||||
|
||||
function prettyPrint (argv, object, rules) {
|
||||
var host = argv.host === '0.0.0.0' ? 'localhost' : argv.host
|
||||
var port = argv.port
|
||||
var root = 'http://' + host + ':' + port
|
||||
|
||||
console.log()
|
||||
console.log(chalk.bold(' Resources'))
|
||||
for (var prop in object) {
|
||||
console.log(' ' + root + '/' + prop)
|
||||
}
|
||||
|
||||
if (rules) {
|
||||
console.log()
|
||||
console.log(chalk.bold(' Other routes'))
|
||||
for (var rule in rules) {
|
||||
console.log(' ' + rule + ' -> ' + rules[rule])
|
||||
}
|
||||
}
|
||||
|
||||
console.log()
|
||||
console.log(chalk.bold(' Home'))
|
||||
console.log(' ' + root)
|
||||
console.log()
|
||||
}
|
||||
|
||||
function createServer (source, object, routes) {
|
||||
var server = jsonServer.create()
|
||||
|
||||
var router = jsonServer.router(
|
||||
is.JSON(source) ?
|
||||
source :
|
||||
object
|
||||
)
|
||||
|
||||
server.use(jsonServer.defaults)
|
||||
|
||||
if (routes) {
|
||||
var rewriter = jsonServer.rewriter(routes)
|
||||
server.use(rewriter)
|
||||
}
|
||||
|
||||
server.use(router)
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
module.exports = function (argv) {
|
||||
|
||||
var source = argv._[0]
|
||||
var server
|
||||
|
||||
console.log()
|
||||
console.log(chalk.cyan(' \\{^_^}/ hi!'))
|
||||
|
||||
function start () {
|
||||
console.log()
|
||||
console.log(chalk.gray(' Loading', source))
|
||||
|
||||
// Load JSON, JS or HTTP database
|
||||
load(source, function (err, data) {
|
||||
|
||||
if (err) throw err
|
||||
|
||||
// Load additional routes
|
||||
if (argv.routes) {
|
||||
console.log(chalk.gray(' Loading', argv.routes))
|
||||
var routes = JSON.parse(fs.readFileSync(argv.routes))
|
||||
}
|
||||
|
||||
console.log(chalk.gray(' Done'))
|
||||
|
||||
// Create server and listen
|
||||
server = createServer(source, data, routes).listen(argv.port, argv.host)
|
||||
|
||||
// Display server informations
|
||||
prettyPrint(argv, data, routes)
|
||||
})
|
||||
}
|
||||
|
||||
// Start server
|
||||
start()
|
||||
|
||||
// Watch files
|
||||
if (argv.watch) {
|
||||
console.log(chalk.gray(' Watching...'))
|
||||
console.log()
|
||||
watch(argv, function (file) {
|
||||
console.log(chalk.gray(' ' + file + ' has changed, reloading...'))
|
||||
// Restart server
|
||||
server && server.close()
|
||||
start()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
17
src/cli/utils/is.js
Normal file
17
src/cli/utils/is.js
Normal file
@ -0,0 +1,17 @@
|
||||
module.exports = {
|
||||
JSON: isJSON,
|
||||
JS: isJS,
|
||||
URL: isURL
|
||||
}
|
||||
|
||||
function isJSON (s) {
|
||||
return /\.json$/.test(s)
|
||||
}
|
||||
|
||||
function isJS (s) {
|
||||
return /\.js$/.test(s)
|
||||
}
|
||||
|
||||
function isURL (s) {
|
||||
return /^(http|https):/.test(s)
|
||||
}
|
18
src/cli/utils/load.js
Normal file
18
src/cli/utils/load.js
Normal file
@ -0,0 +1,18 @@
|
||||
var path = require('path')
|
||||
var got = require('got')
|
||||
var is = require('./is')
|
||||
|
||||
module.exports = function (source, cb) {
|
||||
if (is.URL(source)) {
|
||||
// Load URL
|
||||
got(source, { json: true }, function (err, data) {
|
||||
cb(err, data)
|
||||
})
|
||||
} else {
|
||||
// Load JS or JSON
|
||||
var filename = path.resolve(source)
|
||||
delete require.cache[filename]
|
||||
var data = is.JSON(source) ? require(filename) : require(filename)()
|
||||
cb(null, data)
|
||||
}
|
||||
}
|
44
src/cli/watch.js
Normal file
44
src/cli/watch.js
Normal file
@ -0,0 +1,44 @@
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
var is = require('./utils/is')
|
||||
|
||||
module.exports = watch
|
||||
|
||||
// Because JSON file can be modified by the server, we need to be able to
|
||||
// distinguish between user modification vs server modification.
|
||||
// When the server modifies the JSON file, it generates a rename event.
|
||||
// When the user modifies the JSON file, it generate a change event.
|
||||
function watchDB (file, cb) {
|
||||
var watchedDir = path.dirname(file)
|
||||
var watchedFile = path.basename(file)
|
||||
|
||||
fs.watch(watchedDir, function (event, changedFile) {
|
||||
if (event === 'change' && changedFile === watchedFile) cb()
|
||||
})
|
||||
}
|
||||
|
||||
function watchJS (file, cb) {
|
||||
fs.watchFile(file, cb)
|
||||
}
|
||||
|
||||
function watchSource (source, cb) {
|
||||
if (is.JSON(source)) {
|
||||
return watchDB(source, cb)
|
||||
}
|
||||
if (is.JS(source)) return watchJS(source, cb)
|
||||
if (is.URL(source)) throw new Error('Can\'t watch URL')
|
||||
}
|
||||
|
||||
function watch (argv, cb) {
|
||||
var source = argv._[0]
|
||||
|
||||
watchSource(source, function () {
|
||||
cb(source)
|
||||
})
|
||||
|
||||
if (argv.routes) {
|
||||
fs.watchFile(argv.routes, function () {
|
||||
cb(argv.routes)
|
||||
})
|
||||
}
|
||||
}
|
@ -8,7 +8,10 @@ var arr = []
|
||||
|
||||
// Logger
|
||||
arr.push(logger('dev', {
|
||||
skip: function (req, res) { return req.path === '/favicon.ico' }
|
||||
skip: function (req, res) {
|
||||
return process.env.NODE_ENV === 'test' ||
|
||||
req.path === '/favicon.ico'
|
||||
}
|
||||
}))
|
||||
|
||||
// Enable CORS for all the requests, including static files
|
Before Width: | Height: | Size: 318 B After Width: | Height: | Size: 318 B |
Before Width: | Height: | Size: 278 B After Width: | Height: | Size: 278 B |
3
test/cli/fixtures/seed.js
Normal file
3
test/cli/fixtures/seed.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function () {
|
||||
return { posts: [] }
|
||||
}
|
123
test/cli/index.js
Normal file
123
test/cli/index.js
Normal file
@ -0,0 +1,123 @@
|
||||
var os = require('os')
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
var cp = require('child_process')
|
||||
var request = require('supertest')
|
||||
var rmrf = require('rimraf')
|
||||
var pkg = require('../../package.json')
|
||||
|
||||
request = request('http://localhost:3000')
|
||||
|
||||
var tmpDir = path.join(__dirname, '../../tmp')
|
||||
var dbFile = path.join(tmpDir, 'db.json')
|
||||
var routesFile = path.join(tmpDir, 'routes.json')
|
||||
|
||||
function cli (args) {
|
||||
var bin = path.join(__dirname, '../..', pkg.bin)
|
||||
return cp.spawn('node', [bin].concat(args), {
|
||||
stdio: 'inherit',
|
||||
cwd: __dirname
|
||||
})
|
||||
}
|
||||
|
||||
/* global beforeEach, afterEach, describe, it */
|
||||
|
||||
describe('cli', function () {
|
||||
|
||||
var child
|
||||
|
||||
beforeEach(function () {
|
||||
fs.mkdirSync(tmpDir)
|
||||
fs.writeFileSync(dbFile, JSON.stringify({ posts: [] }))
|
||||
fs.writeFileSync(routesFile, JSON.stringify({ '/blog/': '/' }))
|
||||
})
|
||||
|
||||
afterEach(function (done) {
|
||||
rmrf.sync(tmpDir)
|
||||
child.kill()
|
||||
setTimeout(done, 1000)
|
||||
})
|
||||
|
||||
describe('db.json', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
child = cli([dbFile])
|
||||
setTimeout(done, 1000)
|
||||
})
|
||||
|
||||
it('should support JSON dbFile', function (done) {
|
||||
request.get('/posts').expect(200, done)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('seed.js', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
child = cli(['fixtures/seed.js'])
|
||||
setTimeout(done, 1000)
|
||||
})
|
||||
|
||||
it('should support JS file', function (done) {
|
||||
request.get('/posts').expect(200, done)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('http://jsonplaceholder.typicode.com/db', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
this.timeout(6000)
|
||||
child = cli(['http://jsonplaceholder.typicode.com/db'])
|
||||
setTimeout(done, 5000)
|
||||
})
|
||||
|
||||
it('should support URL file', function (done) {
|
||||
request.get('/posts').expect(200, done)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('db.json -r routes.json', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
child = cli([dbFile, '-r', routesFile])
|
||||
setTimeout(done, 1000)
|
||||
})
|
||||
|
||||
it('should use routes.json', function (done) {
|
||||
request.get('/blog/posts').expect(200, done)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
// FIXME test fails on OS X and maybe on Windows
|
||||
// But manually updating db.json works...
|
||||
if (os.platform() === 'linux') {
|
||||
describe('--watch db.json -r routes.json', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
child = cli(['--watch', dbFile, '-r', routesFile])
|
||||
setTimeout(done, 1000)
|
||||
})
|
||||
|
||||
it('should watch db file', function (done) {
|
||||
fs.writeFileSync(dbFile, JSON.stringify({ foo: [] }))
|
||||
setTimeout(function () {
|
||||
request.get('/foo').expect(200, done)
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
it('should watch routes file', function (done) {
|
||||
// Can be very slow
|
||||
this.timeout(10000)
|
||||
fs.writeFileSync(routesFile, JSON.stringify({ '/api/': '/' }))
|
||||
setTimeout(function () {
|
||||
request.get('/api/posts').expect(200, done)
|
||||
}, 9000)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
})
|
@ -1,6 +1,6 @@
|
||||
var request = require('supertest')
|
||||
var assert = require('assert')
|
||||
var jsonServer = require('../src/')
|
||||
var jsonServer = require('../../src/server')
|
||||
|
||||
/* global beforeEach, describe, it */
|
||||
|
@ -1,7 +1,7 @@
|
||||
var assert = require('assert')
|
||||
var mixins = require('../src/mixins')
|
||||
var _ = require('lodash')
|
||||
var _db = require('underscore-db')
|
||||
var mixins = require('../../src/server/mixins')
|
||||
|
||||
/* global describe, it */
|
||||
|
@ -1,5 +1,5 @@
|
||||
var assert = require('assert')
|
||||
var utils = require('../src/utils')
|
||||
var utils = require('../../src/server/utils')
|
||||
|
||||
/* global describe, it */
|
||||
|
Reference in New Issue
Block a user