main.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. const fs = require('fs');
  2. const chokidar = require('chokidar');
  3. const express = require('express');
  4. const removeRoute = require('remove-route-runtime');
  5. const portfinder = require('portfinder');
  6. const program = require('commander');
  7. require('colors');
  8. program.version('1.0.0');
  9. program.option('-p, --port <number>', 'change the port used', 8000);
  10. program.option('-v, --verbose', 'displays more information', false);
  11. program.parse(process.argv);
  12. const app = express();
  13. app.use(express.json());
  14. const wrapperPath = './wrapper.json';
  15. function isdir(path) {
  16. return fs.statSync(path).isDirectory();
  17. }
  18. function scandir(dirname, exclude = []) {
  19. dirname += (dirname[dirname.length - 1] === '/') ? '' : '/';
  20. const result = [];
  21. const items = fs.readdirSync(dirname)
  22. items.forEach(elt => {
  23. if (exclude.indexOf(elt) !== -1) {
  24. return;
  25. }
  26. const file = dirname + elt;
  27. if (isdir(file)) {
  28. const res = scandir(file, exclude);
  29. res.forEach(e => {
  30. result.push(e);
  31. })
  32. } else {
  33. result.push(file);
  34. }
  35. });
  36. return result;
  37. }
  38. function parseFilename(filename) {
  39. let data = null;
  40. if (fs.existsSync('./' + filename)) {
  41. data = fs.readFileSync('./' + filename).toString();
  42. }
  43. const split = filename.split('/');
  44. split.shift();
  45. const method = split.shift();
  46. const path = '/' + split.join('/').replace('.json', '').replace(/param-/g, ':').replace(/-optional/g, '?').replace(/\./g, '/');
  47. return {data: data, method: method, path: path};
  48. }
  49. function verbose(req, res, next) {
  50. if (program.verbose) {
  51. const body = Object.keys(req.body);
  52. const param = Object.keys(req.params);
  53. let nbParam = 0;
  54. for (let prop in req.params) {
  55. if (req.params[prop] !== undefined) {
  56. nbParam++;
  57. }
  58. }
  59. const total = body.length + nbParam
  60. console.info('*'.green, '>'.cyan, `Call ${req.method} ${req.originalUrl} with ${total} parameter(s) (${nbParam} params, ${body.length} body)`.bold);
  61. if (nbParam > 0) {
  62. for (let prop in req.params) {
  63. if (req.params[prop] !== undefined) {
  64. console.info('\t-'.cyan, `${prop}: ${req.params[prop]} (Param)`);
  65. }
  66. }
  67. }
  68. if (body.length > 0) {
  69. for (let prop in req.body) {
  70. console.info('\t-'.cyan, `${prop}: ${req.body[prop]} (Body)`);
  71. }
  72. }
  73. }
  74. next();
  75. }
  76. function extractParam(req) {
  77. const res = {};
  78. const body = Object.keys(req.body);
  79. const param = Object.keys(req.params);
  80. if (param.length > 0) {
  81. for (let prop in req.params) {
  82. if (req.params[prop] !== undefined) {
  83. res[prop] = req.params[prop];
  84. }
  85. }
  86. }
  87. if (body.length > 0) {
  88. for (let prop in req.body) {
  89. res[prop] = req.body[prop];
  90. }
  91. }
  92. return res;
  93. }
  94. function applyParam(data, param) {
  95. let result = data;
  96. const key = Object.keys(param);
  97. key.forEach(elt => {
  98. result = result.replace(new RegExp('\\$\\{' + elt + '\\}', 'g'), param[elt]);
  99. });
  100. return result;
  101. }
  102. function answer(req, res, data, wrapper = null) {
  103. const param = extractParam(req);
  104. let json = applyParam(data, param);
  105. try {
  106. if (json.trim() === '') {
  107. if (wrapper !== null) {
  108. json = wrapper.replace(new RegExp('\\$\\{data\\}', 'g'), 'null');
  109. } else {
  110. json = '{}';
  111. }
  112. } else if (wrapper !== null) {
  113. json = wrapper.replace(new RegExp('\\$\\{data\\}', 'g'), json);
  114. }
  115. return res.json(JSON.parse(json));
  116. } catch (error) {
  117. console.error('Unable to answer'.red);
  118. return res.json({error: 'Unable to answer'});
  119. }
  120. }
  121. function addRoute(app, method, route, data, wrapper = null) {
  122. switch (method) {
  123. case "GET":
  124. app.get(route, [verbose, (req, res) => {
  125. return answer(req, res, data, wrapper);
  126. }]);
  127. break;
  128. case "POST":
  129. app.post(route, [verbose, (req, res) => {
  130. return answer(req, res, data, wrapper);
  131. }]);;
  132. break;
  133. case "PUT":
  134. app.put(route, [verbose, (req, res) => {
  135. return answer(req, res, data, wrapper);
  136. }]);
  137. break;
  138. case "DELETE":
  139. app.delete(route, [verbose, (req, res) => {
  140. return answer(req, res, data, wrapper);
  141. }]);
  142. break;
  143. default:
  144. app.all(route, [verbose, (req, res) => {
  145. return answer(req, res, data, wrapper);
  146. }]);;
  147. }
  148. }
  149. // Recherche des fichiers
  150. console.info('*'.bold.green, 'Read mock files'.bold);
  151. const files = scandir('server', ['.gitkeep']);
  152. if (files.length === 0) {
  153. console.info('*'.bold.green, 'No mock files found'.bold.yellow);
  154. }
  155. // Création du serveur
  156. console.info('*'.bold.green, 'Creating mock server'.bold);
  157. // Chargement encapsulation
  158. let wrapper = null;
  159. if (fs.existsSync(wrapperPath)) {
  160. if (program.verbose) {
  161. console.info('*'.green, '>'.yellow, 'Load wrapper'.bold);
  162. }
  163. wrapper = fs.readFileSync(wrapperPath).toString();
  164. }
  165. // Ajout des routes pour chaque fichier
  166. files.forEach(elt => {
  167. const parse = parseFilename(elt);
  168. if (program.verbose) {
  169. console.info('*'.green, '>'.yellow, `Load URL: ${parse.method} ${parse.path}`.bold)
  170. }
  171. addRoute(app, parse.method, parse.path, parse.data, wrapper);
  172. });
  173. // Ajout page de test erreur
  174. if (fs.existsSync('./error.json')) {
  175. if (program.verbose) {
  176. console.info('*'.green, '>'.yellow, `Load URL: ANY /error`.bold)
  177. }
  178. addRoute(app, 'ANY', '/error', fs.readFileSync('./error.json').toString());
  179. }
  180. // Ajout watcher
  181. console.info('*'.green, 'Watching changes...'.bold);
  182. const watcher = chokidar.watch(['./server'], {
  183. ignored: /(^|[\/\\])\../,
  184. persistent: true
  185. });
  186. watcher.on('add', path => {
  187. // Remplace les \ du chemin Windows par des /
  188. path = path.replace(/\\/g, '/');
  189. // Skip la 1er detection
  190. const index = files.indexOf(path);
  191. if (index !== -1) {
  192. files.splice(index, 1);
  193. return;
  194. }
  195. // Ajoute les nouveaux chemins
  196. const parse = parseFilename(path);
  197. if (program.verbose) {
  198. console.info('*'.green, '>'.yellow, `Load URL: ${parse.method} ${parse.path}`.bold)
  199. }
  200. addRoute(app, parse.method, parse.path, parse.data, wrapper);
  201. });
  202. watcher.on('change', path => {
  203. const method = ['GET', 'POST', 'PUT', 'DELETE'];
  204. const parse = parseFilename(path.replace(/\\/g, '/'));
  205. // Supprime l'ancienne route avec les anciennes données
  206. if (method.indexOf(parse.method) === -1) {
  207. removeRoute(app, parse.path);
  208. } else {
  209. removeRoute(app, parse.path, parse.method.toLowerCase());
  210. }
  211. // Remet la route avec les nouvelles données
  212. if (program.verbose) {
  213. console.info('*'.green, '>'.yellow, `Reload URL: ${parse.method} ${parse.path}`.bold)
  214. }
  215. addRoute(app, parse.method, parse.path, parse.data, wrapper);
  216. });
  217. watcher.on('unlink', path => {
  218. const method = ['GET', 'POST', 'PUT', 'DELETE'];
  219. const parse = parseFilename(path.replace(/\\/g, '/'));
  220. if (program.verbose) {
  221. console.info('*'.green, '>'.yellow, `Unload URL: ${parse.method} ${parse.path}`.bold)
  222. }
  223. if (method.indexOf(parse.method) === -1) {
  224. removeRoute(app, parse.path);
  225. } else {
  226. removeRoute(app, parse.path, parse.method.toLowerCase());
  227. }
  228. });
  229. // Lancement du serveur
  230. portfinder.getPort({port: program.port}, (err, freePort) => {
  231. if (err) {
  232. console.error('*'.bold.green, 'Unable to find a free port'.bold.red);
  233. process.exit(-1);
  234. }
  235. if (program.port != freePort) {
  236. console.info('*'.bold.green, `Port ${program.port} is unavailable`.bold.yellow);
  237. }
  238. app.listen(freePort, () => {
  239. console.info('*'.bold.green, 'Mock server stating on'.bold, `http://localhost:${freePort}`.bold.blue);
  240. });
  241. });