1 | import semver from 'semver';
|
2 | guardMinimalNodeVersion();
|
3 | import { Command } from 'commander';
|
4 | import { ALL_REPORT_TYPES } from '@stryker-mutator/api/core';
|
5 | import { initializerFactory } from './initializer/index.js';
|
6 | import { LogConfigurator } from './logging/index.js';
|
7 | import { Stryker } from './stryker.js';
|
8 | import { defaultOptions } from './config/index.js';
|
9 | import { strykerEngines, strykerVersion } from './stryker-package.js';
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | function deepOption(object, key) {
|
16 | return (value) => {
|
17 | object[key] = value;
|
18 | return undefined;
|
19 | };
|
20 | }
|
21 | const list = createSplitter(',');
|
22 | function createSplitter(sep) {
|
23 | return (val) => val.split(sep).filter(Boolean);
|
24 | }
|
25 | function parseBoolean(val) {
|
26 | const v = val.toLocaleLowerCase();
|
27 | return v !== 'false' && v !== '0';
|
28 | }
|
29 | export class StrykerCli {
|
30 | constructor(argv, program = new Command(), runMutationTest = async (options) => new Stryker(options).runMutationTest()) {
|
31 | this.argv = argv;
|
32 | this.program = program;
|
33 | this.runMutationTest = runMutationTest;
|
34 | this.command = '';
|
35 | this.strykerConfig = null;
|
36 | }
|
37 | run() {
|
38 | const dashboard = {};
|
39 | this.program
|
40 |
|
41 | .version(strykerVersion)
|
42 | .usage('<command> [options] [configFile]')
|
43 | .description(`Possible commands:
|
44 | run: Run mutation testing
|
45 | init: Initialize Stryker for your project
|
46 |
|
47 | Optional location to a JSON or JavaScript config file as the last argument. If it's a JavaScript file, that file should export the config directly.`)
|
48 | .arguments('<command> [configFile]')
|
49 | .action((cmd, config) => {
|
50 | this.command = cmd;
|
51 | this.strykerConfig = config;
|
52 | })
|
53 | .option('-f, --files <allFiles>', '[DEPRECATED, please use the inverse option `--ignorePatterns` instead] A comma separated list of patterns used for selecting all files needed to run the tests. For a more detailed way of selecting input files, please use a configFile. Example: src/**/*.js,!src/index.js,a.js,test/**/*.js.', list)
|
54 | .option('--ignorePatterns <filesToIgnore>', 'A comma separated list of patterns used for specifying which files need to be ignored. This should only be used in cases where you experience a slow Stryker startup, because too many (or too large) files are copied to the sandbox that are not needed to run the tests. For example, image or movie directories. Note: This option will have no effect when using the --inPlace option. The directories `node_modules`, `.git` and some others are always ignored. Example: --ignorePatterns dist', list)
|
55 | .option('--ignoreStatic', 'Ignore static mutants. Static mutants are mutants which are only executed during the loading of a file.')
|
56 | .option('--incremental', "Enable 'incremental mode'. Stryker will store results in a file and use that file to speed up the next --incremental run")
|
57 | .option('--incrementalFile <file>', 'Specify the file to use for incremental mode.')
|
58 | .option('--force', 'Run all mutants, even if --incremental is provided and an incremental file exists. Can be used to force a rebuild of the incremental file.')
|
59 | .option('-m, --mutate <filesToMutate>', 'A comma separated list of globbing expression used for selecting the files that should be mutated. Example: src/**/*.js,a.js. You can also specify specific lines and columns to mutate by adding :startLine[:startColumn]-endLine[:endColumn]. This will execute all mutants inside that range. It cannot be combined with glob patterns. Example: src/index.js:1:3-1:5', list)
|
60 | .option('-b, --buildCommand <command>', 'Configure a build command to run after mutating the code, but before mutants are tested. This is generally used to transpile your code before testing.' +
|
61 | " Only configure this if your test runner doesn't take care of this already and you're not using just-in-time transpiler like `babel/register` or `ts-node`.")
|
62 | .option('--dryRunOnly', 'Execute the initial test run only, without doing actual mutation testing. Doing a dry run only can be used to test that StrykerJS can run your test setup, for example, in CI pipelines.')
|
63 | .option('--checkers <listOfCheckersOrEmptyString>', 'A comma separated list of checkers to use, for example --checkers typescript', createSplitter(','))
|
64 | .option('--checkerNodeArgs <listOfNodeArgs>', 'A list of node args to be passed to checker child processes.', createSplitter(' '))
|
65 | .option('--coverageAnalysis <perTest|all|off>', `The coverage analysis strategy you want to use. Default value: "${defaultOptions.coverageAnalysis}"`)
|
66 | .option('--testRunner <name>', 'The name of the test runner you want to use')
|
67 | .option('--testRunnerNodeArgs <listOfNodeArgs>', 'A comma separated list of node args to be passed to test runner child processes.', createSplitter(' '))
|
68 | .option('--reporters <name>', 'A comma separated list of the names of the reporter(s) you want to use', list)
|
69 | .option('--plugins <listOfPlugins>', 'A list of plugins you want stryker to load (`require`).', list)
|
70 | .option('--appendPlugins <listOfPlugins>', 'A list of additional plugins you want Stryker to load (`require`) without overwriting the (default) `plugins`.', list)
|
71 | .option('--timeoutMS <number>', 'Tweak the absolute timeout used to wait for a test runner to complete', parseInt)
|
72 | .option('--timeoutFactor <number>', 'Tweak the standard deviation relative to the normal test run of a mutated test', parseFloat)
|
73 | .option('--dryRunTimeoutMinutes <number>', 'Configure an absolute timeout for the initial test run. (It can take a while.)', parseFloat)
|
74 | .option('--maxConcurrentTestRunners <n>', 'Set the number of max concurrent test runner to spawn (default: cpuCount)', parseInt)
|
75 | .option('-c, --concurrency <n>', 'Set the concurrency of workers. Stryker will always run checkers and test runners in parallel by creating worker processes (default: cpuCount - 1)', parseInt)
|
76 | .option('--disableBail', 'Force the test runner to keep running tests, even when a mutant is already killed.')
|
77 | .option('--maxTestRunnerReuse <n>', 'Restart each test runner worker process after `n` runs. Not recommended unless you are experiencing memory leaks that you are unable to resolve. Configuring `0` here means infinite reuse.', parseInt)
|
78 | .option('--logLevel <level>', `Set the log level for the console. Possible values: fatal, error, warn, info, debug, trace and off. Default is "${defaultOptions.logLevel}"`)
|
79 | .option('--fileLogLevel <level>', `Set the log4js log level for the "stryker.log" file. Possible values: fatal, error, warn, info, debug, trace and off. Default is "${defaultOptions.fileLogLevel}"`)
|
80 | .option('--allowConsoleColors <true/false>', 'Indicates whether or not Stryker should use colors in console.', parseBoolean)
|
81 | .option('--dashboard.project <name>', 'Indicates which project name to use if the "dashboard" reporter is enabled. Defaults to the git url configured in the environment of your CI server.', deepOption(dashboard, 'project'))
|
82 | .option('--dashboard.version <version>', 'Indicates which version to use if the "dashboard" reporter is enabled. Defaults to the branch name or tag name configured in the environment of your CI server.', deepOption(dashboard, 'version'))
|
83 | .option('--dashboard.module <name>', 'Indicates which module name to use if the "dashboard" reporter is enabled.', deepOption(dashboard, 'module'))
|
84 | .option('--dashboard.baseUrl <url>', `Indicates which baseUrl to use when reporting to the stryker dashboard. Default: "${defaultOptions.dashboard.baseUrl}"`, deepOption(dashboard, 'baseUrl'))
|
85 | .option(`--dashboard.reportType <${ALL_REPORT_TYPES.join('|')}>`, `Send a full report (inc. source code and mutant results) or only the mutation score. Default: ${defaultOptions.dashboard.reportType}`, deepOption(dashboard, 'reportType'))
|
86 | .option('--inPlace', 'Enable Stryker to mutate your files in place and put back the originals after its done. Note: mutating your files in place is generally not needed for mutation testing.')
|
87 | .option('--tempDirName <name>', 'Set the name of the directory that is used by Stryker as a working directory. This directory will be cleaned after a successful run')
|
88 | .option('--cleanTempDir <true/false>', `Choose whether or not to clean the temp dir (which is "${defaultOptions.tempDirName}" inside the current working directory by default) after a successful run. The temp dir will never be removed when the run failed for some reason (for debugging purposes).`, parseBoolean)
|
89 | .showSuggestionAfterError()
|
90 | .parse(this.argv);
|
91 |
|
92 | const options = this.program.opts();
|
93 | LogConfigurator.configureMainProcess(options.logLevel);
|
94 |
|
95 | delete options.version;
|
96 | Object.keys(options)
|
97 | .filter((key) => key.startsWith('dashboard.'))
|
98 | .forEach((key) => delete options[key]);
|
99 | if (this.strykerConfig) {
|
100 | options.configFile = this.strykerConfig;
|
101 | }
|
102 | if (Object.keys(dashboard).length > 0) {
|
103 | options.dashboard = dashboard;
|
104 | }
|
105 | const commands = {
|
106 | init: () => initializerFactory().initialize(),
|
107 | run: () => this.runMutationTest(options),
|
108 | };
|
109 | if (Object.keys(commands).includes(this.command)) {
|
110 | const promise = commands[this.command]();
|
111 | promise.catch(() => {
|
112 | process.exitCode = 1;
|
113 | });
|
114 | }
|
115 | else {
|
116 | console.error('Unknown command: "%s", supported commands: [%s], or use `stryker --help`.', this.command, Object.keys(commands));
|
117 | }
|
118 | }
|
119 | }
|
120 | export function guardMinimalNodeVersion(processVersion = process.version) {
|
121 |
|
122 | if (!semver.satisfies(processVersion, strykerEngines.node)) {
|
123 | throw new Error(`Node.js version ${processVersion} detected. StrykerJS requires version to match ${strykerEngines.node}. Please update your Node.js version or visit https://nodejs.org/ for additional instructions`);
|
124 | }
|
125 | }
|
126 |
|
\ | No newline at end of file |