command.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. const path = require('path');
  2. const fs = require('fs');
  3. exports = module.exports = Command;
  4. const subCommands = {
  5. init: {
  6. description: 'initialize jasmine',
  7. action: initJasmine
  8. },
  9. examples: {
  10. description: 'install examples',
  11. action: installExamples
  12. },
  13. help: {
  14. description: 'show help',
  15. action: help,
  16. alias: '-h'
  17. },
  18. version: {
  19. description: 'show jasmine and jasmine-core versions',
  20. action: version,
  21. alias: '-v'
  22. }
  23. };
  24. function Command(projectBaseDir, examplesDir, deps) {
  25. const {print, platform} = deps;
  26. const isWindows = platform() === 'win32';
  27. this.projectBaseDir = isWindows ? unWindows(projectBaseDir) : projectBaseDir;
  28. this.specDir = `${this.projectBaseDir}/spec`;
  29. const command = this;
  30. this.run = async function(jasmine, commands) {
  31. setEnvironmentVariables(commands);
  32. let commandToRun;
  33. Object.keys(subCommands).forEach(function(cmd) {
  34. const commandObject = subCommands[cmd];
  35. if (commands.indexOf(cmd) >= 0) {
  36. commandToRun = commandObject;
  37. } else if(commandObject.alias && commands.indexOf(commandObject.alias) >= 0) {
  38. commandToRun = commandObject;
  39. }
  40. });
  41. if (commandToRun) {
  42. commandToRun.action({jasmine: jasmine, projectBaseDir: command.projectBaseDir, specDir: command.specDir, examplesDir: examplesDir, print: print});
  43. } else {
  44. const env = parseOptions(commands, isWindows);
  45. if (env.unknownOptions.length > 0) {
  46. process.exitCode = 1;
  47. print('Unknown options: ' + env.unknownOptions.join(', '));
  48. print('');
  49. help({print: print});
  50. } else {
  51. await runJasmine(jasmine, env);
  52. }
  53. }
  54. };
  55. }
  56. function isFileArg(arg) {
  57. return arg.indexOf('--') !== 0 && !isEnvironmentVariable(arg);
  58. }
  59. function parseOptions(argv, isWindows) {
  60. let files = [],
  61. helpers = [],
  62. requires = [],
  63. unknownOptions = [],
  64. color = process.stdout.isTTY || false,
  65. reporter,
  66. configPath,
  67. filter,
  68. failFast,
  69. random,
  70. seed;
  71. for (const arg of argv) {
  72. if (arg === '--no-color') {
  73. color = false;
  74. } else if (arg === '--color') {
  75. color = true;
  76. } else if (arg.match("^--filter=")) {
  77. filter = arg.match("^--filter=(.*)")[1];
  78. } else if (arg.match("^--helper=")) {
  79. helpers.push(arg.match("^--helper=(.*)")[1]);
  80. } else if (arg.match("^--require=")) {
  81. requires.push(arg.match("^--require=(.*)")[1]);
  82. } else if (arg === '--fail-fast') {
  83. failFast = true;
  84. } else if (arg.match("^--random=")) {
  85. random = arg.match("^--random=(.*)")[1] === 'true';
  86. } else if (arg.match("^--seed=")) {
  87. seed = arg.match("^--seed=(.*)")[1];
  88. } else if (arg.match("^--config=")) {
  89. configPath = arg.match("^--config=(.*)")[1];
  90. } else if (arg.match("^--reporter=")) {
  91. reporter = arg.match("^--reporter=(.*)")[1];
  92. } else if (arg === '--') {
  93. break;
  94. } else if (isFileArg(arg)) {
  95. files.push(isWindows ? unWindows(arg) : arg);
  96. } else if (!isEnvironmentVariable(arg)) {
  97. unknownOptions.push(arg);
  98. }
  99. }
  100. return {
  101. color,
  102. configPath,
  103. filter,
  104. failFast,
  105. helpers,
  106. requires,
  107. reporter,
  108. files,
  109. random,
  110. seed,
  111. unknownOptions
  112. };
  113. }
  114. async function runJasmine(jasmine, options) {
  115. await jasmine.loadConfigFile(options.configPath || process.env.JASMINE_CONFIG_PATH);
  116. if (options.failFast !== undefined) {
  117. jasmine.env.configure({
  118. stopSpecOnExpectationFailure: options.failFast,
  119. stopOnSpecFailure: options.failFast
  120. });
  121. }
  122. if (options.seed !== undefined) {
  123. jasmine.seed(options.seed);
  124. }
  125. if (options.random !== undefined) {
  126. jasmine.randomizeTests(options.random);
  127. }
  128. if (options.helpers !== undefined && options.helpers.length) {
  129. jasmine.addMatchingHelperFiles(options.helpers);
  130. }
  131. if (options.requires !== undefined && options.requires.length) {
  132. jasmine.addRequires(options.requires);
  133. }
  134. if (options.reporter !== undefined) {
  135. await registerReporter(options.reporter, jasmine);
  136. }
  137. jasmine.showColors(options.color);
  138. try {
  139. await jasmine.execute(options.files, options.filter);
  140. } catch (error) {
  141. console.error(error);
  142. process.exit(1);
  143. }
  144. }
  145. async function registerReporter(reporterModuleName, jasmine) {
  146. let Reporter;
  147. try {
  148. Reporter = await jasmine.loader.load(resolveReporter(reporterModuleName));
  149. } catch (e) {
  150. throw new Error('Failed to load reporter module '+ reporterModuleName +
  151. '\nUnderlying error: ' + e.stack + '\n(end underlying error)');
  152. }
  153. let reporter;
  154. try {
  155. reporter = new Reporter();
  156. } catch (e) {
  157. throw new Error('Failed to instantiate reporter from '+ reporterModuleName +
  158. '\nUnderlying error: ' + e.stack + '\n(end underlying error)');
  159. }
  160. jasmine.clearReporters();
  161. jasmine.addReporter(reporter);
  162. }
  163. function resolveReporter(nameOrPath) {
  164. if (nameOrPath.startsWith('./') || nameOrPath.startsWith('../')) {
  165. return path.resolve(nameOrPath);
  166. } else {
  167. return nameOrPath;
  168. }
  169. }
  170. function initJasmine(options) {
  171. const print = options.print;
  172. const specDir = options.specDir;
  173. makeDirStructure(path.join(specDir, 'support/'));
  174. if(!fs.existsSync(path.join(specDir, 'support/jasmine.json'))) {
  175. fs.writeFileSync(path.join(specDir, 'support/jasmine.json'), fs.readFileSync(path.join(__dirname, '../lib/examples/jasmine.json'), 'utf-8'));
  176. }
  177. else {
  178. print('spec/support/jasmine.json already exists in your project.');
  179. }
  180. }
  181. function installExamples(options) {
  182. const specDir = options.specDir;
  183. const projectBaseDir = options.projectBaseDir;
  184. const examplesDir = options.examplesDir;
  185. makeDirStructure(path.join(specDir, 'support'));
  186. makeDirStructure(path.join(specDir, 'jasmine_examples'));
  187. makeDirStructure(path.join(specDir, 'helpers', 'jasmine_examples'));
  188. makeDirStructure(path.join(projectBaseDir, 'lib', 'jasmine_examples'));
  189. copyFiles(
  190. path.join(examplesDir, 'spec', 'helpers', 'jasmine_examples'),
  191. path.join(specDir, 'helpers', 'jasmine_examples'),
  192. new RegExp(/[Hh]elper\.js/)
  193. );
  194. copyFiles(
  195. path.join(examplesDir, 'lib', 'jasmine_examples'),
  196. path.join(projectBaseDir, 'lib', 'jasmine_examples'),
  197. new RegExp(/\.js/)
  198. );
  199. copyFiles(
  200. path.join(examplesDir, 'spec', 'jasmine_examples'),
  201. path.join(specDir, 'jasmine_examples'),
  202. new RegExp(/[Ss]pec.js/)
  203. );
  204. }
  205. function help(options) {
  206. const print = options.print;
  207. print('Usage: jasmine [command] [options] [files] [--]');
  208. print('');
  209. print('Commands:');
  210. Object.keys(subCommands).forEach(function(cmd) {
  211. let commandNameText = cmd;
  212. if(subCommands[cmd].alias) {
  213. commandNameText = commandNameText + ',' + subCommands[cmd].alias;
  214. }
  215. print('%s\t%s', lPad(commandNameText, 10), subCommands[cmd].description);
  216. });
  217. print('');
  218. print('If no command is given, jasmine specs will be run');
  219. print('');
  220. print('');
  221. print('Options:');
  222. print('%s\tturn off color in spec output', lPad('--no-color', 18));
  223. print('%s\tforce turn on color in spec output', lPad('--color', 18));
  224. print('%s\tfilter specs to run only those that match the given string', lPad('--filter=', 18));
  225. print('%s\tload helper files that match the given string', lPad('--helper=', 18));
  226. print('%s\tload module that match the given string', lPad('--require=', 18));
  227. print('%s\tstop Jasmine execution on spec failure', lPad('--fail-fast', 18));
  228. print('%s\tpath to your optional jasmine.json', lPad('--config=', 18));
  229. print('%s\tpath to reporter to use instead of the default Jasmine reporter', lPad('--reporter=', 18));
  230. print('%s\tmarker to signal the end of options meant for Jasmine', lPad('--', 18));
  231. print('');
  232. print('The given arguments take precedence over options in your jasmine.json');
  233. print('The path to your optional jasmine.json can also be configured by setting the JASMINE_CONFIG_PATH environment variable');
  234. }
  235. function version(options) {
  236. const print = options.print;
  237. print('jasmine v' + require('../package.json').version);
  238. print('jasmine-core v' + options.jasmine.coreVersion());
  239. }
  240. function lPad(str, length) {
  241. if (str.length >= length) {
  242. return str;
  243. } else {
  244. return lPad(' ' + str, length);
  245. }
  246. }
  247. function copyFiles(srcDir, destDir, pattern) {
  248. const srcDirFiles = fs.readdirSync(srcDir);
  249. srcDirFiles.forEach(function(file) {
  250. if (file.search(pattern) !== -1) {
  251. fs.writeFileSync(path.join(destDir, file), fs.readFileSync(path.join(srcDir, file)));
  252. }
  253. });
  254. }
  255. function makeDirStructure(absolutePath) {
  256. const splitPath = absolutePath.split(path.sep);
  257. splitPath.forEach(function(dir, index) {
  258. if(index > 1) {
  259. const fullPath = path.join(splitPath.slice(0, index).join('/'), dir);
  260. if (!fs.existsSync(fullPath)) {
  261. fs.mkdirSync(fullPath);
  262. }
  263. }
  264. });
  265. }
  266. function isEnvironmentVariable(command) {
  267. const envRegExp = /(.*)=(.*)/;
  268. return command.match(envRegExp);
  269. }
  270. function setEnvironmentVariables(commands) {
  271. commands.forEach(function (command) {
  272. const regExpMatch = isEnvironmentVariable(command);
  273. if(regExpMatch) {
  274. const key = regExpMatch[1];
  275. const value = regExpMatch[2];
  276. process.env[key] = value;
  277. }
  278. });
  279. }
  280. // Future versions of glob will interpret backslashes as escape sequences on
  281. // all platforms, and Jasmine warns about them. Convert to slashes to avoid
  282. // the warning and future behavior change. Should only be called when running
  283. // on Windows.
  284. function unWindows(projectBaseDir) {
  285. return projectBaseDir.replace(/\\/g, '/');
  286. }