cli.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. #!/usr/bin/env node
  2. const yargs = require("yargs");
  3. const importFresh = require("import-fresh");
  4. const { colors, Errors } = require("svcorelib");
  5. const { resolve } = require("path");
  6. const dotenv = require("dotenv");
  7. const settings = require("../settings");
  8. const { exit } = process;
  9. const col = colors.fg;
  10. /** Absolute path to JokeAPI's root directory */
  11. const rootDir = resolve(__dirname, "../"); // if this file is moved, make sure to change this accordingly
  12. //#SECTION run
  13. async function run()
  14. {
  15. try
  16. {
  17. // ensure cwd is correct if the binary is called in a global context
  18. process.chdir(rootDir);
  19. dotenv.config();
  20. const argv = prepareCLI();
  21. /** @type {string|null} */
  22. const command = argv && argv._ ? argv._[0] : null;
  23. let file, action;
  24. // TODO: (v2.4) remove comments below
  25. switch(command)
  26. {
  27. case "start":
  28. case "run":
  29. file = "../JokeAPI.js";
  30. break;
  31. case "submissions":
  32. case "sub":
  33. case "s":
  34. action = "Joke submissions";
  35. file = "./submissions.js";
  36. break;
  37. case "info":
  38. case "i":
  39. file = "./info.js";
  40. break;
  41. case "add-joke":
  42. case "aj":
  43. case "j":
  44. action = "Add joke";
  45. file = "./add-joke.js";
  46. break;
  47. case "reassign-ids":
  48. case "ri":
  49. action = "Reassign IDs";
  50. file = "./reassign-ids.js";
  51. break;
  52. case "add-token":
  53. case "at":
  54. case "t":
  55. action = "Add API token";
  56. file = "./add-token.js";
  57. break;
  58. case "validate-ids":
  59. case "vi":
  60. action = "Validate IDs";
  61. file = "./validate-ids.js";
  62. break;
  63. case "validate-jokes":
  64. case "vj":
  65. action = "Validate jokes";
  66. file = "./validate-jokes.js";
  67. break;
  68. case "generate-changelog":
  69. case "cl":
  70. action = "Generate changelog";
  71. file = "./generate-changelog.js";
  72. break;
  73. // case "stresstest": case "str":
  74. // action = "Stress test";
  75. // file = "./stresstest.js";
  76. // break;
  77. case "test":
  78. action = "Unit tests";
  79. file = "./test.js";
  80. break;
  81. // case "ip-info": case "ip":
  82. // action = "IP info";
  83. // file = "./ip-info.js";
  84. case undefined:
  85. case null:
  86. case "":
  87. console.log(`${settings.info.name} CLI v${settings.info.version}\n`);
  88. return yargs.showHelp();
  89. default:
  90. return warn(`Unrecognized command '${command}'\nUse '${argv.$0} -h' to see a list of commands`);
  91. }
  92. if(!file)
  93. throw new Error(`Command '${command}' (${action.toLowerCase()}) didn't yield an executable file`);
  94. action && console.log(`${settings.info.name} CLI - ${action}`);
  95. return importFresh(file);
  96. }
  97. catch(err)
  98. {
  99. return error(err);
  100. }
  101. }
  102. /**
  103. * Prepares the CLI so it can show help
  104. * @returns {yargs.Argv<*>}
  105. */
  106. function prepareCLI()
  107. {
  108. //#SECTION general
  109. yargs.scriptName("jokeapi")
  110. .usage("Usage: $0 <command>")
  111. .version(`${settings.info.name} v${settings.info.version} - ${settings.info.projGitHub}`)
  112. .alias("v", "version")
  113. .help()
  114. .alias("h", "help");
  115. //#SECTION commands
  116. // TODO: (v2.4) remove comments below
  117. yargs.command([ "start", "run" ], `Starts ${settings.info.name} (equivalent to 'npm start')`);
  118. yargs.command([ "info", "i" ], `Prints information about ${settings.info.name}, like the /info endpoint`);
  119. yargs.command([ "submissions", "sub", "s" ], "Goes through all joke submissions, prompting to edit, add or delete them");
  120. yargs.command([ "add-joke", "aj", "j" ], "Runs an interactive prompt that adds a joke");
  121. yargs.command([ "reassign-ids", "ri", "r" ], "Goes through each joke file and reassigns IDs to each one, consecutively");
  122. yargs.command([ "add-token [amount]", "at", "t" ], "Generates one or multiple API tokens to be used to gain unlimited access to the API", cmd => {
  123. cmd.positional("amount", {
  124. describe: "Specifies the amount of tokens to generate - min is 1, max is 10",
  125. type: "number",
  126. default: 1
  127. });
  128. // cmd.option("no-copy", {
  129. // alias: "nc",
  130. // describe: "Disables auto-copying the token to the clipboard (if amount = 1)",
  131. // type: "boolean"
  132. // });
  133. });
  134. yargs.command([ "validate-ids", "vi" ], "Goes through each joke file and makes sure the IDs are correct (no duplicates or skipped IDs & correct order)");
  135. yargs.command([ "validate-jokes", "vj" ], "Goes through each joke file and checks the validity of each joke and whether they can all be loaded to memory");
  136. yargs.command([ "generate-changelog", "cl" ], "Turns the changelog.txt file into a markdown file (changelog.md)", cmd => {
  137. cmd.option("generate-json", {
  138. alias: "j",
  139. describe: "Use this argument to generate a changelog-data.json file in addition to the markdown file",
  140. type: "boolean"
  141. });
  142. });
  143. // yargs.command([ "ip-info", "ip" ], "Starts a server at '127.0.0.1:8074' that just prints information about each request's IP", cmd => {
  144. // cmd.option("color-cycle", {
  145. // alias: "c",
  146. // describe: "Cycles the color of the output after each request (to make spotting a new request easier)",
  147. // type: "boolean"
  148. // });
  149. // });
  150. // yargs.command([ "stresstest", "str" ], `Sends lots of requests to ${settings.info.name} to stresstest it (requires the API to run in another process on the same machine)`);
  151. yargs.command("test", `Runs ${settings.info.name}'s unit tests`, cmd => {
  152. cmd.option("colorblind", {
  153. alias: "c",
  154. describe: "Include this argument to replace the colors green with cyan and red with magenta",
  155. type: "boolean"
  156. });
  157. });
  158. yargs.wrap(Math.min(100, process.stdout.columns));
  159. return yargs.argv;
  160. }
  161. //#SECTION on execute
  162. try
  163. {
  164. if(!process.stdin.isTTY)
  165. throw new Errors.NoStdinError("The process doesn't have an stdin channel to read input from");
  166. else
  167. run();
  168. }
  169. catch(err)
  170. {
  171. return error(err);
  172. }
  173. /**
  174. * @param {Error} err
  175. */
  176. function error(err)
  177. {
  178. console.error(`${col.red}${settings.info.name} CLI - ${err.name}:${col.rst}\n${err.stack}\n`);
  179. exit(1);
  180. }
  181. /**
  182. * @param {string} warning
  183. * @param {string} [type]
  184. */
  185. function warn(warning, type = "Warning")
  186. {
  187. console.log(`${col.yellow}${settings.info.name} CLI - ${type}:${col.rst}\n${warning}\n`);
  188. exit(1);
  189. }