123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- const fs = require('fs');
- const chokidar = require('chokidar');
- const express = require('express');
- const removeRoute = require('remove-route-runtime');
- const portfinder = require('portfinder');
- const program = require('commander');
- require('colors');
- program.version('1.0.0');
- program.option('-p, --port <number>', 'change the port used', 8000);
- program.option('-v, --verbose', 'displays more information', false);
- program.parse(process.argv);
- const app = express();
- app.use(express.json());
- const wrapperPath = './wrapper.json';
- function isdir(path) {
- return fs.statSync(path).isDirectory();
- }
-
- function scandir(dirname, exclude = []) {
- dirname += (dirname[dirname.length - 1] === '/') ? '' : '/';
- const result = [];
- const items = fs.readdirSync(dirname)
- items.forEach(elt => {
- if (exclude.indexOf(elt) !== -1) {
- return;
- }
- const file = dirname + elt;
- if (isdir(file)) {
- const res = scandir(file, exclude);
- res.forEach(e => {
- result.push(e);
- })
- } else {
- result.push(file);
- }
- });
- return result;
- }
- function parseFilename(filename) {
- let data = null;
- if (fs.existsSync('./' + filename)) {
- data = fs.readFileSync('./' + filename).toString();
- }
- const split = filename.split('/');
- split.shift();
- const method = split.shift();
- const path = '/' + split.join('/').replace('.json', '').replace(/param-/g, ':').replace(/-optional/g, '?').replace(/\./g, '/');
- return {data: data, method: method, path: path};
- }
- function verbose(req, res, next) {
- if (program.verbose) {
- const body = Object.keys(req.body);
- const param = Object.keys(req.params);
- let nbParam = 0;
- for (let prop in req.params) {
- if (req.params[prop] !== undefined) {
- nbParam++;
- }
- }
- const total = body.length + nbParam
- console.info('*'.green, '>'.cyan, `Call ${req.method} ${req.originalUrl} with ${total} parameter(s) (${nbParam} params, ${body.length} body)`.bold);
- if (nbParam > 0) {
- for (let prop in req.params) {
- if (req.params[prop] !== undefined) {
- console.info('\t-'.cyan, `${prop}: ${req.params[prop]} (Param)`);
- }
- }
- }
- if (body.length > 0) {
- for (let prop in req.body) {
- console.info('\t-'.cyan, `${prop}: ${req.body[prop]} (Body)`);
- }
- }
- }
- next();
- }
- function extractParam(req) {
- const res = {};
- const body = Object.keys(req.body);
- const param = Object.keys(req.params);
- if (param.length > 0) {
- for (let prop in req.params) {
- if (req.params[prop] !== undefined) {
- res[prop] = req.params[prop];
- }
- }
- }
- if (body.length > 0) {
- for (let prop in req.body) {
- res[prop] = req.body[prop];
- }
- }
- return res;
- }
- function applyParam(data, param) {
- let result = data;
- const key = Object.keys(param);
- key.forEach(elt => {
- result = result.replace(new RegExp('\\$\\{' + elt + '\\}', 'g'), param[elt]);
- });
- return result;
- }
- function answer(req, res, data, wrapper = null) {
- const param = extractParam(req);
- let json = applyParam(data, param);
- try {
- if (json.trim() === '') {
- if (wrapper !== null) {
- json = wrapper.replace(new RegExp('\\$\\{data\\}', 'g'), 'null');
- } else {
- json = '{}';
- }
- } else if (wrapper !== null) {
- json = wrapper.replace(new RegExp('\\$\\{data\\}', 'g'), json);
- }
- return res.json(JSON.parse(json));
- } catch (error) {
- console.error('Unable to answer'.red);
- return res.json({error: 'Unable to answer'});
- }
- }
- function addRoute(app, method, route, data, wrapper = null) {
- switch (method) {
- case "GET":
- app.get(route, [verbose, (req, res) => {
- return answer(req, res, data, wrapper);
- }]);
- break;
- case "POST":
- app.post(route, [verbose, (req, res) => {
- return answer(req, res, data, wrapper);
- }]);;
- break;
- case "PUT":
- app.put(route, [verbose, (req, res) => {
- return answer(req, res, data, wrapper);
- }]);
- break;
- case "DELETE":
- app.delete(route, [verbose, (req, res) => {
- return answer(req, res, data, wrapper);
- }]);
- break;
- default:
- app.all(route, [verbose, (req, res) => {
- return answer(req, res, data, wrapper);
- }]);;
- }
- }
- // Recherche des fichiers
- console.info('*'.bold.green, 'Read mock files'.bold);
- const files = scandir('server', ['.gitkeep']);
- if (files.length === 0) {
- console.info('*'.bold.green, 'No mock files found'.bold.yellow);
- }
- // Création du serveur
- console.info('*'.bold.green, 'Creating mock server'.bold);
- // Chargement encapsulation
- let wrapper = null;
- if (fs.existsSync(wrapperPath)) {
- if (program.verbose) {
- console.info('*'.green, '>'.yellow, 'Load wrapper'.bold);
- }
- wrapper = fs.readFileSync(wrapperPath).toString();
- }
- // Ajout des routes pour chaque fichier
- files.forEach(elt => {
- const parse = parseFilename(elt);
- if (program.verbose) {
- console.info('*'.green, '>'.yellow, `Load URL: ${parse.method} ${parse.path}`.bold)
- }
- addRoute(app, parse.method, parse.path, parse.data, wrapper);
- });
- // Ajout page de test erreur
- if (fs.existsSync('./error.json')) {
- if (program.verbose) {
- console.info('*'.green, '>'.yellow, `Load URL: ANY /error`.bold)
- }
- addRoute(app, 'ANY', '/error', fs.readFileSync('./error.json').toString());
- }
- // Ajout watcher
- console.info('*'.green, 'Watching changes...'.bold);
- const watcher = chokidar.watch(['./server'], {
- ignored: /(^|[\/\\])\../,
- persistent: true
- });
- watcher.on('add', path => {
- // Remplace les \ du chemin Windows par des /
- path = path.replace(/\\/g, '/');
- // Skip la 1er detection
- const index = files.indexOf(path);
- if (index !== -1) {
- files.splice(index, 1);
- return;
- }
- // Ajoute les nouveaux chemins
- const parse = parseFilename(path);
- if (program.verbose) {
- console.info('*'.green, '>'.yellow, `Load URL: ${parse.method} ${parse.path}`.bold)
- }
- addRoute(app, parse.method, parse.path, parse.data, wrapper);
- });
- watcher.on('change', path => {
- const method = ['GET', 'POST', 'PUT', 'DELETE'];
- const parse = parseFilename(path.replace(/\\/g, '/'));
- // Supprime l'ancienne route avec les anciennes données
- if (method.indexOf(parse.method) === -1) {
- removeRoute(app, parse.path);
- } else {
- removeRoute(app, parse.path, parse.method.toLowerCase());
- }
- // Remet la route avec les nouvelles données
- if (program.verbose) {
- console.info('*'.green, '>'.yellow, `Reload URL: ${parse.method} ${parse.path}`.bold)
- }
- addRoute(app, parse.method, parse.path, parse.data, wrapper);
- });
- watcher.on('unlink', path => {
- const method = ['GET', 'POST', 'PUT', 'DELETE'];
- const parse = parseFilename(path.replace(/\\/g, '/'));
- if (program.verbose) {
- console.info('*'.green, '>'.yellow, `Unload URL: ${parse.method} ${parse.path}`.bold)
- }
- if (method.indexOf(parse.method) === -1) {
- removeRoute(app, parse.path);
- } else {
- removeRoute(app, parse.path, parse.method.toLowerCase());
- }
- });
- // Lancement du serveur
- portfinder.getPort({port: program.port}, (err, freePort) => {
- if (err) {
- console.error('*'.bold.green, 'Unable to find a free port'.bold.red);
- process.exit(-1);
- }
- if (program.port != freePort) {
- console.info('*'.bold.green, `Port ${program.port} is unavailable`.bold.yellow);
- }
- app.listen(freePort, () => {
- console.info('*'.bold.green, 'Mock server stating on'.bold, `http://localhost:${freePort}`.bold.blue);
- });
- });
|