"use strict"; const fs = require('fs'); const path = require('path'); const jph = require('json-parse-helpfulerror'); const _ = require('lodash'); const chalk = require('chalk'); const enableDestroy = require('server-destroy'); const pause = require('connect-pause'); const is = require('./utils/is'); const load = require('./utils/load'); const jsonServer = require('../server'); function prettyPrint(argv, object, rules) { const root = `http://${argv.host}:${argv.port}`; console.log(); console.log(chalk.bold(' Resources')); for (const prop in object) { // skip printing $schema nodes if (prop === '$schema') continue; console.log(` ${root}/${prop}`); } if (rules) { console.log(); console.log(chalk.bold(' Other routes')); for (const rule in rules) { console.log(` ${rule} -> ${rules[rule]}`); } } console.log(); console.log(chalk.bold(' Home')); console.log(` ${root}`); console.log(); } function createApp(db, routes, middlewares, argv) { const app = jsonServer.create(); const { foreignKeySuffix } = argv; const router = jsonServer.router(db, foreignKeySuffix ? { foreignKeySuffix } : undefined); const defaultsOpts = { logger: !argv.quiet, readOnly: argv.readOnly, noCors: argv.noCors, noGzip: argv.noGzip, bodyParser: true }; if (argv.static) { defaultsOpts.static = path.join(process.cwd(), argv.static); } const defaults = jsonServer.defaults(defaultsOpts); app.use(defaults); if (routes) { const rewriter = jsonServer.rewriter(routes); app.use(rewriter); } if (middlewares) { app.use(middlewares); } if (argv.delay) { app.use(pause(argv.delay)); } router.db._.id = argv.id; app.db = router.db; app.use(router); return app; } module.exports = function (argv) { const source = argv._[0]; let app; let server; if (!fs.existsSync(argv.snapshots)) { console.log(`Error: snapshots directory ${argv.snapshots} doesn't exist`); process.exit(1); } // noop log fn if (argv.quiet) { console.log = () => {}; } console.log(); console.log(chalk.cyan(' \\{^_^}/ hi!')); function start(cb) { console.log(); console.log(chalk.gray(' Loading', source)); server = undefined; // create db and load object, JSON file, JS or HTTP database return load(source).then(db => { // Load additional routes let routes; if (argv.routes) { console.log(chalk.gray(' Loading', argv.routes)); routes = JSON.parse(fs.readFileSync(argv.routes)); } // Load middlewares let middlewares; if (argv.middlewares) { middlewares = argv.middlewares.map(function (m) { console.log(chalk.gray(' Loading', m)); return require(path.resolve(m)); }); } // Done console.log(chalk.gray(' Done')); // Create app and server app = createApp(db, routes, middlewares, argv); server = app.listen(argv.port, argv.host); // Enhance with a destroy function enableDestroy(server); // Display server informations prettyPrint(argv, db.getState(), routes); // Catch and handle any error occurring in the server process process.on('uncaughtException', error => { if (error.errno === 'EADDRINUSE') console.log(chalk.red(`Cannot bind to the port ${error.port}. Please specify another port number either through --port argument or through the json-server.json configuration file`));else console.log('Some error occurred', error); process.exit(1); }); }); } // Start server start().then(() => { // Snapshot console.log(chalk.gray(' Type s + enter at any time to create a snapshot of the database')); // Support nohup // https://github.com/typicode/json-server/issues/221 process.stdin.on('error', () => { console.log(` Error, can't read from stdin`); console.log(` Creating a snapshot from the CLI won't be possible`); }); process.stdin.setEncoding('utf8'); process.stdin.on('data', chunk => { if (chunk.trim().toLowerCase() === 's') { const filename = `db-${Date.now()}.json`; const file = path.join(argv.snapshots, filename); const state = app.db.getState(); fs.writeFileSync(file, JSON.stringify(state, null, 2), 'utf-8'); console.log(` Saved snapshot to ${path.relative(process.cwd(), file)}\n`); } }); // Watch files if (argv.watch) { console.log(chalk.gray(' Watching...')); console.log(); const source = argv._[0]; // Can't watch URL if (is.URL(source)) throw new Error("Can't watch URL"); // Watch .js or .json file // Since lowdb uses atomic writing, directory is watched instead of file const watchedDir = path.dirname(source); let readError = false; fs.watch(watchedDir, (event, file) => { // https://github.com/typicode/json-server/issues/420 // file can be null if (file) { const watchedFile = path.resolve(watchedDir, file); if (watchedFile === path.resolve(source)) { if (is.FILE(watchedFile)) { let obj; try { obj = jph.parse(fs.readFileSync(watchedFile)); if (readError) { console.log(chalk.green(` Read error has been fixed :)`)); readError = false; } } catch (e) { readError = true; console.log(chalk.red(` Error reading ${watchedFile}`)); console.error(e.message); return; } // Compare .json file content with in memory database const isDatabaseDifferent = !_.isEqual(obj, app.db.getState()); if (isDatabaseDifferent) { console.log(chalk.gray(` ${source} has changed, reloading...`)); server && server.destroy(() => start()); } } } } }); // Watch routes if (argv.routes) { const watchedDir = path.dirname(argv.routes); fs.watch(watchedDir, (event, file) => { if (file) { const watchedFile = path.resolve(watchedDir, file); if (watchedFile === path.resolve(argv.routes)) { console.log(chalk.gray(` ${argv.routes} has changed, reloading...`)); server && server.destroy(() => start()); } } }); } } }).catch(err => { console.log(err); process.exit(1); }); };