Skip to content

Commit

Permalink
feat(Initializer): Initialize config file as JSON by default (#2093)
Browse files Browse the repository at this point in the history
Fixes #2000 

During initialization, the user is now prompted to ask if they want their config to be in a JSON format instead of js. Both formats now have intellisense as well.
  • Loading branch information
simondel authored Mar 11, 2020
1 parent 1cd6dcc commit e07d953
Show file tree
Hide file tree
Showing 27 changed files with 386 additions and 300 deletions.
38 changes: 18 additions & 20 deletions e2e/test/babel-transpiling/stryker.conf.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
module.exports = function (config) {
config.set({
mutate: [
'src/*.js'
],
testFramework: 'mocha',
testRunner: 'mocha',
coverageAnalysis: 'off',
mutator: {
name: 'javascript',
plugins: [['pipelineOperator', { proposal: 'minimal' }]]
},
transpilers: [
'babel'
],
timeoutMS: 60000,
reporters: ['clear-text', 'html', 'event-recorder'],
maxConcurrentTestRunners: 2,
logLevel: 'info'
});
module.exports = {
mutate: [
'src/*.js'
],
testFramework: 'mocha',
testRunner: 'mocha',
coverageAnalysis: 'off',
mutator: {
name: 'javascript',
plugins: [['pipelineOperator', { proposal: 'minimal' }]]
},
transpilers: [
'babel'
],
timeoutMS: 60000,
reporters: ['clear-text', 'html', 'event-recorder'],
maxConcurrentTestRunners: 2,
logLevel: 'info'
};
2 changes: 1 addition & 1 deletion e2e/test/command/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"main": "index.js",
"scripts": {
"pretest": "rimraf \"reports\" \"stryker.log\"",
"test": "stryker run stryker.conf.js",
"test": "stryker run",
"mocha": "mocha",
"posttest": "mocha --require ts-node/register verify/*.ts"
},
Expand Down
15 changes: 0 additions & 15 deletions e2e/test/command/stryker.conf.js

This file was deleted.

18 changes: 18 additions & 0 deletions e2e/test/command/stryker.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"mutate": [
"src/*.js"
],
"testFramework": "mocha",
"coverageAnalysis": "off",
"reporters": [
"clear-text",
"event-recorder"
],
"mutator": "javascript",
"maxConcurrentTestRunners": 2,
"commandRunner": {
"command": "npm run mocha"
},
"symlinkNodeModules": false,
"fileLogLevel": "info"
}
36 changes: 17 additions & 19 deletions e2e/test/exit-prematurely/stryker.conf.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
module.exports = function (config) {
config.set({
mutate: [
'src/*.js'
],
testFramework: 'mocha',
testRunner: 'mocha',
coverageAnalysis: 'off',
mutator: 'javascript',
transpilers: [
'babel'
],
timeoutMS: 60000,
reporters: ['clear-text', 'html', 'event-recorder'],
maxConcurrentTestRunners: 2,
logLevel: 'info',
fileLogLevel: 'info'
});
};
module.exports = {
mutate: [
'src/*.js'
],
testFramework: 'mocha',
testRunner: 'mocha',
coverageAnalysis: 'off',
mutator: 'javascript',
transpilers: [
'babel'
],
timeoutMS: 60000,
reporters: ['clear-text', 'html', 'event-recorder'],
maxConcurrentTestRunners: 2,
logLevel: 'info',
fileLogLevel: 'info'
}
2 changes: 1 addition & 1 deletion packages/babel-transpiler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Next, install this package:
npm install --save-dev @stryker-mutator/babel-transpiler @babel/core
```

Next, open up your `stryker.conf.js` file and add the following properties:
Next, open up your `stryker.conf.js` (or `stryker.conf.json`) file and add the following properties:

```javascript
babel: {
Expand Down
50 changes: 32 additions & 18 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,40 @@ The main `command` for Stryker is `run`, which kicks off mutation testing.

Although Stryker can run without any configuration, it is recommended to configure it when you can, as it can greatly improve performance of the mutation testing process. By default, Stryker will look for a `stryker.conf.js` or `stryker.conf.json` file in the current working directory (if it exists). This can be overridden by specifying a different file as the last parameter.

Before your first run, we recommend you try the `init` command, which helps you to set up this `stryker.conf.js` file and install any missing packages needed for your specific configuration. We recommend you verify the contents of the configuration file after this initialization, to make sure everything is setup correctly. Of course, you can still make changes to it, before you run Stryker for the first time.

The following is an example `stryker.conf.js` file. It specifies running mocha tests with the mocha test runner.

```javascript
module.exports = function(config){
config.set({
mutate: [
'src/**/*.js',
'!src/index.js'
],
testFramework: 'mocha',
testRunner: 'mocha',
reporters: ['progress', 'clear-text', 'html'],
coverageAnalysis: 'perTest'
});
Before your first run, we recommend you try the `init` command, which helps you to set up this config file and install any missing packages needed for your specific configuration. We recommend you verify the contents of the configuration file after this initialization, to make sure everything is setup correctly. Of course, you can still make changes to it, before you run Stryker for the first time.

The following is an example `stryker.conf.json` file. It specifies running mocha tests with the mocha test runner.

```json
{
"$schema": "https://raw.githubusercontent.com/stryker-mutator/stryker/master/packages/api/schema/stryker-core.json",
"mutate": [
"src/**/*.js",
"!src/index.js"
],
"testFramework": "mocha",
"testRunner": "mocha",
"reporters": ["progress", "clear-text", "html"],
"coverageAnalysis": "perTest"
}
```

As you can see, the config file is *not* a simple JSON file. It should be a node module. You might recognize this way of working from the karma test runner.
As a `stryker.conf.js` file this looks like this:
```javascript
/**
* @type {import('@stryker-mutator/api/core').StrykerOptions}
*/
module.exports = {
mutate: [
'src/**/*.js',
'!src/index.js'
],
testFramework: 'mocha',
testRunner: 'mocha',
reporters: ['progress', 'clear-text', 'html'],
coverageAnalysis: 'perTest'
};
```

Make sure you *at least* specify the `testRunner` options when mixing the config file and/or command line options.

Expand All @@ -80,7 +94,7 @@ See our website for the [list of currently supported mutators](https://stryker-m

## Configuration

All configuration options can either be set via the command line or via the `stryker.conf.js` config file.
All configuration options can either be set via the command line or via a config file.

`files` and `mutate` both support globbing expressions using [node glob](https://github.com/isaacs/node-glob).
This is the same globbing format you might know from [Grunt](https://github.com/gruntjs/grunt) or [Karma](https://github.com/karma-runner/karma).
Expand Down
81 changes: 53 additions & 28 deletions packages/core/src/initializer/StrykerConfigWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,28 @@ import PromptOption from './PromptOption';

import { initializerTokens } from '.';

const STRYKER_CONFIG_FILE = 'stryker.conf.js';
const STRYKER_JS_CONFIG_FILE = 'stryker.conf.js';
const STRYKER_JSON_CONFIG_FILE = 'stryker.conf.json';

export default class StrykerConfigWriter {
public static inject = tokens(commonTokens.logger, initializerTokens.out);
constructor(private readonly log: Logger, private readonly out: typeof console.log) {}

public guardForExistingConfig() {
if (existsSync(STRYKER_CONFIG_FILE)) {
const msg = 'Stryker config file "stryker.conf.js" already exists in the current directory. Please remove it and try again.';
this.checkIfConfigFileExists(STRYKER_JS_CONFIG_FILE);
this.checkIfConfigFileExists(STRYKER_JSON_CONFIG_FILE);
}

private checkIfConfigFileExists(file: string) {
if (existsSync(file)) {
const msg = `Stryker config file "${file}" already exists in the current directory. Please remove it and try again.`;
this.log.error(msg);
throw new Error(msg);
}
}

/**
* Create stryker.conf.js based on the chosen framework and test runner
* Create config based on the chosen framework and test runner
* @function
*/
public write(
Expand All @@ -35,8 +41,9 @@ export default class StrykerConfigWriter {
selectedTranspilers: null | PromptOption[],
selectedReporters: PromptOption[],
selectedPackageManager: PromptOption,
additionalPiecesOfConfig: Array<Partial<StrykerOptions>>
): Promise<void> {
additionalPiecesOfConfig: Array<Partial<StrykerOptions>>,
exportAsJson: boolean
): Promise<string> {
const configObject: Partial<StrykerOptions> = {
mutator: selectedMutator ? selectedMutator.name : '',
packageManager: selectedPackageManager.name,
Expand All @@ -47,19 +54,20 @@ export default class StrykerConfigWriter {

this.configureTestFramework(configObject, selectedTestFramework);
Object.assign(configObject, ...additionalPiecesOfConfig);
return this.writeStrykerConfig(configObject);
return this.writeStrykerConfig(configObject, exportAsJson);
}

/**
* Create stryker.conf.js based on the chosen preset
* Create config based on the chosen preset
* @function
*/
public async writePreset(presetConfig: PresetConfiguration) {
return this.writeStrykerConfigRaw(
presetConfig.config,
`// This config was generated using a preset.
// Please see the handbook for more information: ${presetConfig.handbookUrl}`
);
public async writePreset(presetConfig: PresetConfiguration, exportAsJson: boolean) {
const config = {
comment: `This config was generated using a preset. Please see the handbook for more information: ${presetConfig.handbookUrl}`,
...presetConfig.config
};

return this.writeStrykerConfig(config, exportAsJson);
}

private configureTestFramework(configObject: Partial<StrykerOptions>, selectedTestFramework: null | PromptOption) {
Expand All @@ -71,28 +79,45 @@ export default class StrykerConfigWriter {
}
}

private async writeStrykerConfigRaw(rawConfig: string, rawHeader = '') {
this.out('Writing & formatting stryker.conf.js...');
const formattedConf = `${rawHeader}
module.exports = function(config){
config.set(
${rawConfig}
);
}`;
await fs.writeFile(STRYKER_CONFIG_FILE, formattedConf);
private writeStrykerConfig(config: Partial<StrykerOptions>, exportAsJson: boolean) {
if (exportAsJson) {
return this.writeJsonConfig(config);
} else {
return this.writeJsConfig(config);
}
}

private async writeJsConfig(commentedConfig: Partial<StrykerOptions>) {
this.out(`Writing & formatting ${STRYKER_JS_CONFIG_FILE}...`);
const rawConfig = this.stringify(commentedConfig);
const formattedConfig = `/**
* @type {import('@stryker-mutator/api/core').StrykerOptions}
*/
module.exports = ${rawConfig};`;
await fs.writeFile(STRYKER_JS_CONFIG_FILE, formattedConfig);
try {
await childProcessAsPromised.exec(`npx prettier --write ${STRYKER_CONFIG_FILE}`);
await childProcessAsPromised.exec(`npx prettier --write ${STRYKER_JS_CONFIG_FILE}`);
} catch (error) {
this.log.debug('Prettier exited with error', error);
this.out('Unable to format stryker.conf.js file for you. This is not a big problem, but it might look a bit messy 🙈.');
}

return STRYKER_JS_CONFIG_FILE;
}

private writeStrykerConfig(configObject: Partial<StrykerOptions>) {
return this.writeStrykerConfigRaw(this.wrapInModule(configObject));
private async writeJsonConfig(commentedConfig: Partial<StrykerOptions>) {
this.out(`Writing & formatting ${STRYKER_JSON_CONFIG_FILE}...`);
const typedConfig = {
$schema: 'https://raw.githubusercontent.com/stryker-mutator/stryker/master/packages/api/schema/stryker-core.json',
...commentedConfig
};
const formattedConfig = this.stringify(typedConfig);
await fs.writeFile(STRYKER_JSON_CONFIG_FILE, formattedConfig);

return STRYKER_JSON_CONFIG_FILE;
}

private wrapInModule(configObject: Partial<StrykerOptions>): string {
return JSON.stringify(configObject, null, 2);
private stringify(input: object): string {
return JSON.stringify(input, undefined, 2);
}
}
Loading

0 comments on commit e07d953

Please sign in to comment.