1 | import path from 'path';
|
2 | import { npmRunPathEnv } from 'npm-run-path';
|
3 | import { normalizeWhitespaces } from '@stryker-mutator/util';
|
4 | import { tokens, commonTokens } from '@stryker-mutator/api/plugin';
|
5 | import { fileUtils } from '../utils/file-utils.js';
|
6 | import { coreTokens } from '../di/index.js';
|
7 | import { objectUtils } from '../utils/index.js';
|
8 | export class Sandbox {
|
9 | constructor(options, log, temporaryDirectory, project, execCommand, unexpectedExitHandler) {
|
10 | this.options = options;
|
11 | this.log = log;
|
12 | this.temporaryDirectory = temporaryDirectory;
|
13 | this.project = project;
|
14 | this.execCommand = execCommand;
|
15 | this.fileMap = new Map();
|
16 | |
17 |
|
18 |
|
19 | this.backupDirectory = '';
|
20 | if (options.inPlace) {
|
21 | this.workingDirectory = process.cwd();
|
22 | this.backupDirectory = temporaryDirectory.getRandomDirectory('backup');
|
23 | this.tempDirectory = this.backupDirectory;
|
24 | this.log.info('In place mode is enabled, Stryker will be overriding YOUR files. Find your backup at: %s', path.relative(process.cwd(), this.backupDirectory));
|
25 | unexpectedExitHandler.registerHandler(this.dispose.bind(this, true));
|
26 | }
|
27 | else {
|
28 | this.workingDirectory = temporaryDirectory.getRandomDirectory('sandbox');
|
29 | this.tempDirectory = this.workingDirectory;
|
30 | this.log.debug('Creating a sandbox for files in %s', this.workingDirectory);
|
31 | }
|
32 | }
|
33 | async init() {
|
34 | await this.temporaryDirectory.createDirectory(this.tempDirectory);
|
35 | await this.fillSandbox();
|
36 | await this.runBuildCommand();
|
37 | await this.symlinkNodeModulesIfNeeded();
|
38 | }
|
39 | sandboxFileFor(fileName) {
|
40 | const sandboxFileName = this.fileMap.get(fileName);
|
41 | if (sandboxFileName === undefined) {
|
42 | throw new Error(`Cannot find sandbox file for ${fileName}`);
|
43 | }
|
44 | return sandboxFileName;
|
45 | }
|
46 | originalFileFor(sandboxFileName) {
|
47 | return path.resolve(sandboxFileName).replace(this.workingDirectory, process.cwd());
|
48 | }
|
49 | async fillSandbox() {
|
50 | await Promise.all(objectUtils.map(this.project.files, (file, name) => this.sandboxFile(name, file)));
|
51 | }
|
52 | async runBuildCommand() {
|
53 | if (this.options.buildCommand) {
|
54 | const env = npmRunPathEnv();
|
55 | this.log.info('Running build command "%s" in "%s".', this.options.buildCommand, this.workingDirectory);
|
56 | this.log.debug('(using PATH: %s)', env.PATH);
|
57 | await this.execCommand(this.options.buildCommand, { cwd: this.workingDirectory, env });
|
58 | }
|
59 | }
|
60 | async symlinkNodeModulesIfNeeded() {
|
61 | this.log.debug('Start symlink node_modules');
|
62 | if (this.options.symlinkNodeModules && !this.options.inPlace) {
|
63 |
|
64 | const basePath = process.cwd();
|
65 | const nodeModulesList = await fileUtils.findNodeModulesList(basePath, this.options.tempDirName);
|
66 | if (nodeModulesList.length > 0) {
|
67 | for (const nodeModules of nodeModulesList) {
|
68 | this.log.debug(`Create symlink from ${path.resolve(nodeModules)} to ${path.join(this.workingDirectory, nodeModules)}`);
|
69 | await fileUtils
|
70 | .symlinkJunction(path.resolve(nodeModules), path.join(this.workingDirectory, nodeModules))
|
71 | .catch((error) => {
|
72 | if (error.code === 'EEXIST') {
|
73 | this.log.warn(normalizeWhitespaces(`Could not symlink "${nodeModules}" in sandbox directory,
|
74 | it is already created in the sandbox. Please remove the node_modules from your sandbox files.
|
75 | Alternatively, set \`symlinkNodeModules\` to \`false\` to disable this warning.`));
|
76 | }
|
77 | else {
|
78 | this.log.warn(`Unexpected error while trying to symlink "${nodeModules}" in sandbox directory.`, error);
|
79 | }
|
80 | });
|
81 | }
|
82 | }
|
83 | else {
|
84 | this.log.debug(`Could not find a node_modules folder to symlink into the sandbox directory. Search "${basePath}" and its parent directories`);
|
85 | }
|
86 | }
|
87 | }
|
88 | |
89 |
|
90 |
|
91 |
|
92 |
|
93 | async sandboxFile(name, file) {
|
94 | if (this.options.inPlace) {
|
95 | if (file.hasChanges) {
|
96 |
|
97 | const backupFileName = await file.backupTo(this.backupDirectory);
|
98 | this.log.debug('Stored backup file at %s', backupFileName);
|
99 | await file.writeInPlace();
|
100 | }
|
101 | this.fileMap.set(name, name);
|
102 | }
|
103 | else {
|
104 | const targetFileName = await file.writeToSandbox(this.workingDirectory);
|
105 | this.fileMap.set(name, targetFileName);
|
106 | }
|
107 | }
|
108 | dispose(unexpected = false) {
|
109 | if (this.backupDirectory) {
|
110 | if (unexpected) {
|
111 | console.error(`Detecting unexpected exit, recovering original files from ${path.relative(process.cwd(), this.backupDirectory)}`);
|
112 | }
|
113 | else {
|
114 | this.log.info(`Resetting your original files from ${path.relative(process.cwd(), this.backupDirectory)}.`);
|
115 | }
|
116 | fileUtils.moveDirectoryRecursiveSync(this.backupDirectory, this.workingDirectory);
|
117 | }
|
118 | }
|
119 | }
|
120 | Sandbox.inject = tokens(commonTokens.options, commonTokens.logger, coreTokens.temporaryDirectory, coreTokens.project, coreTokens.execa, coreTokens.unexpectedExitRegistry);
|
121 |
|
\ | No newline at end of file |