flake-monster 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +281 -0
- package/bin/flake-monster.js +6 -0
- package/package.json +48 -0
- package/src/adapters/adapter-interface.js +86 -0
- package/src/adapters/javascript/codegen.js +13 -0
- package/src/adapters/javascript/index.js +76 -0
- package/src/adapters/javascript/injector.js +438 -0
- package/src/adapters/javascript/parser.js +19 -0
- package/src/adapters/javascript/remover.js +128 -0
- package/src/adapters/registry.js +64 -0
- package/src/cli/commands/inject.js +64 -0
- package/src/cli/commands/restore.js +107 -0
- package/src/cli/commands/test.js +215 -0
- package/src/cli/index.js +19 -0
- package/src/core/config.js +57 -0
- package/src/core/engine.js +156 -0
- package/src/core/flake-analyzer.js +64 -0
- package/src/core/manifest.js +137 -0
- package/src/core/parsers/index.js +40 -0
- package/src/core/parsers/jest.js +52 -0
- package/src/core/parsers/node-test.js +64 -0
- package/src/core/parsers/tap.js +92 -0
- package/src/core/profile.js +72 -0
- package/src/core/reporter.js +75 -0
- package/src/core/seed.js +59 -0
- package/src/core/workspace.js +139 -0
- package/src/runtime/javascript/flake-monster.runtime.js +5 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { rm, mkdir, readdir, copyFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { join, relative, basename } from 'node:path';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { randomBytes } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
const FLAKE_MONSTER_DIR = '.flake-monster';
|
|
7
|
+
const WORKSPACES_DIR = 'workspaces';
|
|
8
|
+
|
|
9
|
+
/** Default directories/patterns to exclude when copying. */
|
|
10
|
+
const DEFAULT_EXCLUDE = [
|
|
11
|
+
'node_modules',
|
|
12
|
+
'.git',
|
|
13
|
+
'.flake-monster',
|
|
14
|
+
'dist',
|
|
15
|
+
'build',
|
|
16
|
+
'.next',
|
|
17
|
+
'coverage',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Manages a temporary workspace copy of the project for safe injection.
|
|
22
|
+
*/
|
|
23
|
+
export class ProjectWorkspace {
|
|
24
|
+
/**
|
|
25
|
+
* @param {Object} options
|
|
26
|
+
* @param {string} options.sourceDir - Absolute path to original project root
|
|
27
|
+
* @param {string} [options.runId] - Unique identifier for this run
|
|
28
|
+
* @param {string[]} [options.exclude] - Directory names to skip
|
|
29
|
+
*/
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.sourceDir = options.sourceDir;
|
|
32
|
+
this.runId = options.runId || `run-${Date.now()}-${randomBytes(3).toString('hex')}`;
|
|
33
|
+
this.exclude = options.exclude || DEFAULT_EXCLUDE;
|
|
34
|
+
this._root = join(this.sourceDir, FLAKE_MONSTER_DIR, WORKSPACES_DIR, this.runId);
|
|
35
|
+
this._created = false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Absolute path to workspace root. */
|
|
39
|
+
get root() {
|
|
40
|
+
return this._root;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Copy project files into the workspace.
|
|
45
|
+
* Uses a filter to skip excluded directories.
|
|
46
|
+
* @returns {Promise<string>} absolute path to workspace root
|
|
47
|
+
*/
|
|
48
|
+
async create() {
|
|
49
|
+
await mkdir(this._root, { recursive: true });
|
|
50
|
+
|
|
51
|
+
const excludeSet = new Set(this.exclude);
|
|
52
|
+
|
|
53
|
+
// Manual recursive copy to avoid fs.cp's "cannot copy into subdirectory of self" check.
|
|
54
|
+
// This is needed because .flake-monster/workspaces/ lives inside the project root.
|
|
55
|
+
await this._copyDir(this.sourceDir, this._root, excludeSet);
|
|
56
|
+
|
|
57
|
+
// Symlink node_modules from source so tests can run
|
|
58
|
+
try {
|
|
59
|
+
const { symlinkSync } = await import('node:fs');
|
|
60
|
+
const sourceModules = join(this.sourceDir, 'node_modules');
|
|
61
|
+
const targetModules = join(this._root, 'node_modules');
|
|
62
|
+
symlinkSync(sourceModules, targetModules, 'junction');
|
|
63
|
+
} catch {
|
|
64
|
+
// node_modules may not exist, that's fine
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this._created = true;
|
|
68
|
+
return this._root;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Recursively copy a directory, skipping excluded names.
|
|
73
|
+
* @param {string} src
|
|
74
|
+
* @param {string} dest
|
|
75
|
+
* @param {Set<string>} excludeSet
|
|
76
|
+
*/
|
|
77
|
+
async _copyDir(src, dest, excludeSet) {
|
|
78
|
+
const entries = await readdir(src, { withFileTypes: true });
|
|
79
|
+
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
if (excludeSet.has(entry.name)) continue;
|
|
82
|
+
|
|
83
|
+
const srcPath = join(src, entry.name);
|
|
84
|
+
const destPath = join(dest, entry.name);
|
|
85
|
+
|
|
86
|
+
if (entry.isDirectory()) {
|
|
87
|
+
await mkdir(destPath, { recursive: true });
|
|
88
|
+
await this._copyDir(srcPath, destPath, excludeSet);
|
|
89
|
+
} else if (entry.isFile()) {
|
|
90
|
+
await copyFile(srcPath, destPath);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Execute a shell command inside the workspace.
|
|
97
|
+
* @param {string} command
|
|
98
|
+
* @param {Object} [options]
|
|
99
|
+
* @param {number} [options.timeout] - ms before killing the process
|
|
100
|
+
* @param {Object} [options.env] - additional env vars
|
|
101
|
+
* @returns {{ exitCode: number, stdout: string, stderr: string }}
|
|
102
|
+
*/
|
|
103
|
+
exec(command, options = {}) {
|
|
104
|
+
const { timeout, env } = options;
|
|
105
|
+
try {
|
|
106
|
+
const stdout = execSync(command, {
|
|
107
|
+
cwd: this._root,
|
|
108
|
+
timeout,
|
|
109
|
+
env: { ...process.env, ...env },
|
|
110
|
+
encoding: 'utf-8',
|
|
111
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
112
|
+
});
|
|
113
|
+
return { exitCode: 0, stdout, stderr: '' };
|
|
114
|
+
} catch (err) {
|
|
115
|
+
return {
|
|
116
|
+
exitCode: err.status ?? 1,
|
|
117
|
+
stdout: err.stdout || '',
|
|
118
|
+
stderr: err.stderr || '',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Delete the workspace directory.
|
|
125
|
+
*/
|
|
126
|
+
async destroy() {
|
|
127
|
+
await rm(this._root, { recursive: true, force: true });
|
|
128
|
+
this._created = false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get the .flake-monster directory path for a project.
|
|
134
|
+
* @param {string} projectRoot
|
|
135
|
+
* @returns {string}
|
|
136
|
+
*/
|
|
137
|
+
export function getFlakeMonsterDir(projectRoot) {
|
|
138
|
+
return join(projectRoot, FLAKE_MONSTER_DIR);
|
|
139
|
+
}
|