UNPKG

5.15 kBPlain TextView Raw
1import { promises as fs } from 'fs';
2
3import { PartialStrykerOptions, StrykerOptions } from '@stryker-mutator/api/core';
4import { Logger } from '@stryker-mutator/api/logging';
5import { commonTokens, tokens } from '@stryker-mutator/api/plugin';
6import { childProcessAsPromised } from '@stryker-mutator/util';
7
8import { fileUtils } from '../utils/file-utils.js';
9import { CommandTestRunner } from '../test-runner/command-test-runner.js';
10import { SUPPORTED_CONFIG_FILE_BASE_NAMES, SUPPORTED_CONFIG_FILE_EXTENSIONS } from '../config/index.js';
11
12import { PresetConfiguration } from './presets/preset-configuration.js';
13import { PromptOption } from './prompt-option.js';
14
15import { initializerTokens } from './index.js';
16
17export 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 * Create config based on the chosen framework and test runner
39 * @function
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 // Only write buildCommand to config file if non-empty
62 if (buildCommand.name) configObject.buildCommand = buildCommand.name;
63
64 // Automatic plugin discovery doesn't work with pnpm, so explicitly specify the required plugins in the config file
65 if (selectedPackageManager.name === 'pnpm') configObject.plugins = requiredPlugins;
66
67 Object.assign(configObject, ...additionalPiecesOfConfig);
68 return this.writeStrykerConfig(configObject, exportAsJson);
69 }
70
71 /**
72 * Create config based on the chosen preset
73 * @function
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}