1 | import { promises as fs } from 'fs';
|
2 |
|
3 | import { PartialStrykerOptions, StrykerOptions } from '@stryker-mutator/api/core';
|
4 | import { Logger } from '@stryker-mutator/api/logging';
|
5 | import { commonTokens, tokens } from '@stryker-mutator/api/plugin';
|
6 | import { childProcessAsPromised } from '@stryker-mutator/util';
|
7 |
|
8 | import { fileUtils } from '../utils/file-utils.js';
|
9 | import { CommandTestRunner } from '../test-runner/command-test-runner.js';
|
10 | import { SUPPORTED_CONFIG_FILE_BASE_NAMES, SUPPORTED_CONFIG_FILE_EXTENSIONS } from '../config/index.js';
|
11 |
|
12 | import { PresetConfiguration } from './presets/preset-configuration.js';
|
13 | import { PromptOption } from './prompt-option.js';
|
14 |
|
15 | import { initializerTokens } from './index.js';
|
16 |
|
17 | export class StrykerConfigWriter {
|
18 | public static inject = tokens(commonTokens.logger, initializerTokens.out);
|
19 | constructor(private readonly log: Logger, private readonly out: typeof console.log) {}
|
20 |
|
21 | public async guardForExistingConfig(): Promise<void> {
|
22 | for (const file of SUPPORTED_CONFIG_FILE_BASE_NAMES) {
|
23 | for (const ext of SUPPORTED_CONFIG_FILE_EXTENSIONS) {
|
24 | await this.checkIfConfigFileExists(`${file}${ext}`);
|
25 | }
|
26 | }
|
27 | }
|
28 |
|
29 | private async checkIfConfigFileExists(file: string) {
|
30 | if (await fileUtils.exists(file)) {
|
31 | const msg = `Stryker config file "${file}" already exists in the current directory. Please remove it and try again.`;
|
32 | this.log.error(msg);
|
33 | throw new Error(msg);
|
34 | }
|
35 | }
|
36 |
|
37 | |
38 |
|
39 |
|
40 |
|
41 | public write(
|
42 | selectedTestRunner: PromptOption,
|
43 | buildCommand: PromptOption,
|
44 | selectedReporters: PromptOption[],
|
45 | selectedPackageManager: PromptOption,
|
46 | requiredPlugins: string[],
|
47 | additionalPiecesOfConfig: Array<Partial<StrykerOptions>>,
|
48 | homepageOfSelectedTestRunner: string,
|
49 | exportAsJson: boolean
|
50 | ): Promise<string> {
|
51 | const configObject: Partial<StrykerOptions> & { _comment: string } = {
|
52 | _comment:
|
53 | "This config was generated using 'stryker init'. Please take a look at: https://stryker-mutator.io/docs/stryker-js/configuration/ for more information.",
|
54 | packageManager: selectedPackageManager.name as 'npm' | 'pnpm' | 'yarn',
|
55 | reporters: selectedReporters.map((rep) => rep.name),
|
56 | testRunner: selectedTestRunner.name,
|
57 | testRunner_comment: `Take a look at ${homepageOfSelectedTestRunner} for information about the ${selectedTestRunner.name} plugin.`,
|
58 | coverageAnalysis: CommandTestRunner.is(selectedTestRunner.name) ? 'off' : 'perTest',
|
59 | };
|
60 |
|
61 |
|
62 | if (buildCommand.name) configObject.buildCommand = buildCommand.name;
|
63 |
|
64 |
|
65 | if (selectedPackageManager.name === 'pnpm') configObject.plugins = requiredPlugins;
|
66 |
|
67 | Object.assign(configObject, ...additionalPiecesOfConfig);
|
68 | return this.writeStrykerConfig(configObject, exportAsJson);
|
69 | }
|
70 |
|
71 | |
72 |
|
73 |
|
74 |
|
75 | public async writePreset(presetConfig: PresetConfiguration, exportAsJson: boolean): Promise<string> {
|
76 | const config = {
|
77 | _comment: `This config was generated using 'stryker init'. Please see the guide for more information: ${presetConfig.guideUrl}`,
|
78 | ...presetConfig.config,
|
79 | };
|
80 |
|
81 | return this.writeStrykerConfig(config, exportAsJson);
|
82 | }
|
83 |
|
84 | private writeStrykerConfig(config: PartialStrykerOptions, exportAsJson: boolean) {
|
85 | if (exportAsJson) {
|
86 | return this.writeJsonConfig(config);
|
87 | } else {
|
88 | return this.writeJsConfig(config);
|
89 | }
|
90 | }
|
91 |
|
92 | private async writeJsConfig(commentedConfig: PartialStrykerOptions) {
|
93 | const configFileName = `${SUPPORTED_CONFIG_FILE_BASE_NAMES[0]}.mjs`;
|
94 | this.out(`Writing & formatting ${configFileName} ...`);
|
95 | const rawConfig = this.stringify(commentedConfig);
|
96 |
|
97 | const formattedConfig = `// @ts-check
|
98 | /** @type {import('@stryker-mutator/api/core').PartialStrykerOptions} */
|
99 | const config = ${rawConfig};
|
100 | export default config;`;
|
101 | await fs.writeFile(configFileName, formattedConfig);
|
102 | try {
|
103 | await childProcessAsPromised.exec(`npx prettier --write ${configFileName}`);
|
104 | } catch (error) {
|
105 | this.log.debug('Prettier exited with error', error);
|
106 | this.out('Unable to format stryker.conf.js file for you. This is not a big problem, but it might look a bit messy 🙈.');
|
107 | }
|
108 | return configFileName;
|
109 | }
|
110 |
|
111 | private async writeJsonConfig(commentedConfig: PartialStrykerOptions) {
|
112 | const configFileName = `${SUPPORTED_CONFIG_FILE_BASE_NAMES[0]}.json`;
|
113 | this.out(`Writing & formatting ${configFileName}...`);
|
114 | const typedConfig = {
|
115 | $schema: './node_modules/@stryker-mutator/core/schema/stryker-schema.json',
|
116 | ...commentedConfig,
|
117 | };
|
118 | const formattedConfig = this.stringify(typedConfig);
|
119 | await fs.writeFile(configFileName, formattedConfig);
|
120 |
|
121 | return configFileName;
|
122 | }
|
123 |
|
124 | private stringify(input: Record<string, unknown>): string {
|
125 | return JSON.stringify(input, undefined, 2);
|
126 | }
|
127 | }
|