@oalacea/demon 1.0.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/CHANGELOG.md +38 -0
- package/LICENSE +23 -0
- package/README.md +103 -0
- package/agents/deps-analyzer.js +366 -0
- package/agents/detector.js +570 -0
- package/agents/fix-engine.js +305 -0
- package/agents/perf-analyzer.js +294 -0
- package/agents/test-generator.js +387 -0
- package/agents/test-runner.js +318 -0
- package/bin/Dockerfile +65 -0
- package/bin/cli.js +455 -0
- package/lib/config.js +237 -0
- package/lib/docker.js +207 -0
- package/lib/reporter.js +297 -0
- package/package.json +34 -0
- package/prompts/DEPS_EFFICIENCY.md +558 -0
- package/prompts/E2E.md +491 -0
- package/prompts/EXECUTE.md +782 -0
- package/prompts/INTEGRATION_API.md +484 -0
- package/prompts/INTEGRATION_DB.md +425 -0
- package/prompts/PERF_API.md +433 -0
- package/prompts/PERF_DB.md +430 -0
- package/prompts/REMEDIATION.md +482 -0
- package/prompts/UNIT.md +260 -0
- package/scripts/dev.js +106 -0
- package/templates/README.md +22 -0
- package/templates/k6/load-test.js +54 -0
- package/templates/playwright/e2e.spec.ts +61 -0
- package/templates/vitest/api.test.ts +51 -0
- package/templates/vitest/component.test.ts +27 -0
- package/templates/vitest/hook.test.ts +36 -0
package/lib/config.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demon - Configuration Utilities
|
|
3
|
+
*
|
|
4
|
+
* Handles project configuration and settings.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get project root directory
|
|
12
|
+
*/
|
|
13
|
+
function getProjectRoot() {
|
|
14
|
+
let currentDir = process.cwd();
|
|
15
|
+
|
|
16
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
17
|
+
const pkgPath = path.join(currentDir, 'package.json');
|
|
18
|
+
|
|
19
|
+
if (fs.existsSync(pkgPath)) {
|
|
20
|
+
return currentDir;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
currentDir = path.dirname(currentDir);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return process.cwd();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Read package.json
|
|
31
|
+
*/
|
|
32
|
+
function readPackageJson(projectDir = getProjectRoot()) {
|
|
33
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
34
|
+
|
|
35
|
+
if (!fs.existsSync(pkgPath)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get all dependencies
|
|
48
|
+
*/
|
|
49
|
+
function getDependencies(projectDir = getProjectRoot()) {
|
|
50
|
+
const pkg = readPackageJson(projectDir);
|
|
51
|
+
|
|
52
|
+
if (!pkg) {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
...(pkg.dependencies || {}),
|
|
58
|
+
...(pkg.devDependencies || {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if a dependency exists
|
|
64
|
+
*/
|
|
65
|
+
function hasDependency(depName, projectDir = getProjectRoot()) {
|
|
66
|
+
const deps = getDependencies(projectDir);
|
|
67
|
+
return depName in deps;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get dependency version
|
|
72
|
+
*/
|
|
73
|
+
function getDependencyVersion(depName, projectDir = getProjectRoot()) {
|
|
74
|
+
const deps = getDependencies(projectDir);
|
|
75
|
+
return deps[depName] || null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if project uses TypeScript
|
|
80
|
+
*/
|
|
81
|
+
function isTypeScriptProject(projectDir = getProjectRoot()) {
|
|
82
|
+
return (
|
|
83
|
+
hasDependency('typescript', projectDir) ||
|
|
84
|
+
fs.existsSync(path.join(projectDir, 'tsconfig.json'))
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get test runner
|
|
90
|
+
*/
|
|
91
|
+
function getTestRunner(projectDir = getProjectRoot()) {
|
|
92
|
+
const deps = getDependencies(projectDir);
|
|
93
|
+
|
|
94
|
+
if (deps.vitest) return 'vitest';
|
|
95
|
+
if (deps.jest) return 'jest';
|
|
96
|
+
if (deps.mocha) return 'mocha';
|
|
97
|
+
if (deps.jasmine) return 'jasmine';
|
|
98
|
+
|
|
99
|
+
// Check config files
|
|
100
|
+
const vitestConfig = path.join(projectDir, 'vitest.config.ts');
|
|
101
|
+
const jestConfig = path.join(projectDir, 'jest.config.js');
|
|
102
|
+
|
|
103
|
+
if (fs.existsSync(vitestConfig)) return 'vitest';
|
|
104
|
+
if (fs.existsSync(jestConfig)) return 'jest';
|
|
105
|
+
|
|
106
|
+
return 'vitest'; // Default
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get framework
|
|
111
|
+
*/
|
|
112
|
+
function getFramework(projectDir = getProjectRoot()) {
|
|
113
|
+
const deps = getDependencies(projectDir);
|
|
114
|
+
|
|
115
|
+
if (deps.next) return 'Next.js';
|
|
116
|
+
if (deps['@remix-run/node']) return 'Remix';
|
|
117
|
+
if (deps['@sveltejs/kit']) return 'SvelteKit';
|
|
118
|
+
if (deps.nuxt) return 'Nuxt';
|
|
119
|
+
if (deps.astro) return 'Astro';
|
|
120
|
+
if (deps.gatsby) return 'Gatsby';
|
|
121
|
+
if (deps.vite) return 'Vite';
|
|
122
|
+
if (deps.express) return 'Express';
|
|
123
|
+
if (deps.nest) return 'NestJS';
|
|
124
|
+
|
|
125
|
+
return 'Unknown';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get database config
|
|
130
|
+
*/
|
|
131
|
+
function getDatabaseConfig(projectDir = getProjectRoot()) {
|
|
132
|
+
const deps = getDependencies(projectDir);
|
|
133
|
+
|
|
134
|
+
if (deps['@prisma/client']) {
|
|
135
|
+
return {
|
|
136
|
+
type: 'Prisma',
|
|
137
|
+
schemaPath: path.join(projectDir, 'prisma', 'schema.prisma'),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (deps['drizzle-orm']) {
|
|
142
|
+
return {
|
|
143
|
+
type: 'Drizzle',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (deps.typeorm) {
|
|
148
|
+
return {
|
|
149
|
+
type: 'TypeORM',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (deps.mongoose) {
|
|
154
|
+
return {
|
|
155
|
+
type: 'Mongoose',
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get environment variable
|
|
164
|
+
*/
|
|
165
|
+
function getEnvVar(key, defaultValue = null) {
|
|
166
|
+
return process.env[key] || defaultValue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get Demon config from package.json
|
|
171
|
+
*/
|
|
172
|
+
function getDemonConfig(projectDir = getProjectRoot()) {
|
|
173
|
+
const pkg = readPackageJson(projectDir);
|
|
174
|
+
|
|
175
|
+
if (!pkg || !pkg.demon) {
|
|
176
|
+
return {};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return pkg.demon;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get source directory
|
|
184
|
+
*/
|
|
185
|
+
function getSourceDir(projectDir = getProjectRoot()) {
|
|
186
|
+
const srcDir = path.join(projectDir, 'src');
|
|
187
|
+
const appDir = path.join(projectDir, 'app');
|
|
188
|
+
|
|
189
|
+
if (fs.existsSync(appDir)) {
|
|
190
|
+
return 'app';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (fs.existsSync(srcDir)) {
|
|
194
|
+
return 'src';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return '.';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get test directory
|
|
202
|
+
*/
|
|
203
|
+
function getTestDir(projectDir = getProjectRoot()) {
|
|
204
|
+
const testsDir = path.join(projectDir, 'tests');
|
|
205
|
+
const testDir = path.join(projectDir, 'test');
|
|
206
|
+
const srcTestsDir = path.join(getSourceDir(projectDir), 'tests');
|
|
207
|
+
|
|
208
|
+
if (fs.existsSync(testsDir)) {
|
|
209
|
+
return 'tests';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (fs.existsSync(testDir)) {
|
|
213
|
+
return 'test';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (fs.existsSync(srcTestsDir)) {
|
|
217
|
+
return path.join(getSourceDir(projectDir), 'tests');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return 'tests';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = {
|
|
224
|
+
getProjectRoot,
|
|
225
|
+
readPackageJson,
|
|
226
|
+
getDependencies,
|
|
227
|
+
hasDependency,
|
|
228
|
+
getDependencyVersion,
|
|
229
|
+
isTypeScriptProject,
|
|
230
|
+
getTestRunner,
|
|
231
|
+
getFramework,
|
|
232
|
+
getDatabaseConfig,
|
|
233
|
+
getEnvVar,
|
|
234
|
+
getDemonConfig,
|
|
235
|
+
getSourceDir,
|
|
236
|
+
getTestDir,
|
|
237
|
+
};
|
package/lib/docker.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demon - Docker Management
|
|
3
|
+
*
|
|
4
|
+
* Handles Docker container operations for the Demon toolkit.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const CONFIG = {
|
|
10
|
+
imageName: 'demon-tools',
|
|
11
|
+
containerName: 'demon-tools',
|
|
12
|
+
dockerfile: path.join(__dirname, '..', 'bin', 'Dockerfile'),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if Docker is running
|
|
17
|
+
*/
|
|
18
|
+
function isDockerRunning() {
|
|
19
|
+
try {
|
|
20
|
+
execSync('docker info', { stdio: 'pipe' });
|
|
21
|
+
return true;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if image exists
|
|
29
|
+
*/
|
|
30
|
+
function imageExists() {
|
|
31
|
+
try {
|
|
32
|
+
const output = execSync(`docker images -q ${CONFIG.imageName}`, { stdio: 'pipe' });
|
|
33
|
+
return output.trim().length > 0;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build the Docker image
|
|
41
|
+
*/
|
|
42
|
+
function buildImage(options = {}) {
|
|
43
|
+
const cmd = `docker build -t ${CONFIG.imageName} -f "${CONFIG.dockerfile}" "${path.dirname(CONFIG.dockerfile)}"`;
|
|
44
|
+
|
|
45
|
+
return execSync(cmd, {
|
|
46
|
+
stdio: options.silent ? 'pipe' : 'inherit',
|
|
47
|
+
timeout: options.timeout || 600000, // 10 minutes
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if container is running
|
|
53
|
+
*/
|
|
54
|
+
function isContainerRunning() {
|
|
55
|
+
try {
|
|
56
|
+
const output = execSync(
|
|
57
|
+
`docker ps --filter "name=^${CONFIG.containerName}$" --format "{{.Names}}"`,
|
|
58
|
+
{ stdio: 'pipe' }
|
|
59
|
+
);
|
|
60
|
+
return output.trim() === CONFIG.containerName;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if container exists
|
|
68
|
+
*/
|
|
69
|
+
function containerExists() {
|
|
70
|
+
try {
|
|
71
|
+
const output = execSync(
|
|
72
|
+
`docker ps -a --filter "name=^${CONFIG.containerName}$" --format "{{.Names}}"`,
|
|
73
|
+
{ stdio: 'pipe' }
|
|
74
|
+
);
|
|
75
|
+
return output.trim() === CONFIG.containerName;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Start container
|
|
83
|
+
*/
|
|
84
|
+
function startContainer() {
|
|
85
|
+
const cmd = `docker start ${CONFIG.containerName}`;
|
|
86
|
+
return execSync(cmd, { stdio: 'pipe' });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create container
|
|
91
|
+
*/
|
|
92
|
+
function createContainer(options = {}) {
|
|
93
|
+
const isLinux = process.platform === 'linux';
|
|
94
|
+
const networkFlag = isLinux ? '--network=host' : '';
|
|
95
|
+
const cmd = `docker run -d --name ${CONFIG.containerName} ${networkFlag} ${CONFIG.imageName}`;
|
|
96
|
+
|
|
97
|
+
return execSync(cmd, { stdio: 'pipe' });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Stop container
|
|
102
|
+
*/
|
|
103
|
+
function stopContainer() {
|
|
104
|
+
try {
|
|
105
|
+
return execSync(`docker stop ${CONFIG.containerName}`, { stdio: 'pipe' });
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Remove container
|
|
113
|
+
*/
|
|
114
|
+
function removeContainer() {
|
|
115
|
+
try {
|
|
116
|
+
return execSync(`docker rm -f ${CONFIG.containerName}`, { stdio: 'pipe' });
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Execute command in container
|
|
124
|
+
*/
|
|
125
|
+
function execInContainer(command, options = {}) {
|
|
126
|
+
const cmd = `docker exec ${CONFIG.containerName} ${command}`;
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
return {
|
|
130
|
+
success: true,
|
|
131
|
+
output: execSync(cmd, {
|
|
132
|
+
encoding: 'utf-8',
|
|
133
|
+
stdio: options.silent ? 'pipe' : 'inherit',
|
|
134
|
+
timeout: options.timeout || 60000,
|
|
135
|
+
}),
|
|
136
|
+
};
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
error: error.message,
|
|
141
|
+
output: error.stdout || '',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get container logs
|
|
148
|
+
*/
|
|
149
|
+
function getLogs(options = {}) {
|
|
150
|
+
const tail = options.tail || 100;
|
|
151
|
+
const cmd = `docker logs --tail ${tail} ${CONFIG.containerName}`;
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
return execSync(cmd, { encoding: 'utf-8', stdio: 'pipe' });
|
|
155
|
+
} catch {
|
|
156
|
+
return '';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Setup container (build, start, or create as needed)
|
|
162
|
+
*/
|
|
163
|
+
async function setupContainer(options = {}) {
|
|
164
|
+
// Check Docker
|
|
165
|
+
if (!isDockerRunning()) {
|
|
166
|
+
throw new Error('Docker is not running');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Build image if needed
|
|
170
|
+
if (!imageExists()) {
|
|
171
|
+
if (options.onBuildStart) {
|
|
172
|
+
options.onBuildStart();
|
|
173
|
+
}
|
|
174
|
+
buildImage({ silent: options.silent });
|
|
175
|
+
if (options.onBuildComplete) {
|
|
176
|
+
options.onBuildComplete();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Start or create container
|
|
181
|
+
if (isContainerRunning()) {
|
|
182
|
+
return { status: 'running' };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (containerExists()) {
|
|
186
|
+
startContainer();
|
|
187
|
+
return { status: 'started' };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
createContainer();
|
|
191
|
+
return { status: 'created' };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = {
|
|
195
|
+
isDockerRunning,
|
|
196
|
+
imageExists,
|
|
197
|
+
buildImage,
|
|
198
|
+
isContainerRunning,
|
|
199
|
+
containerExists,
|
|
200
|
+
startContainer,
|
|
201
|
+
createContainer,
|
|
202
|
+
stopContainer,
|
|
203
|
+
removeContainer,
|
|
204
|
+
execInContainer,
|
|
205
|
+
getLogs,
|
|
206
|
+
setupContainer,
|
|
207
|
+
};
|