@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/bin/Dockerfile
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Demon - Testing Tools Container
|
|
2
|
+
FROM node:20-slim
|
|
3
|
+
|
|
4
|
+
LABEL maintainer="Yanis"
|
|
5
|
+
LABEL description="Demon - Automated testing tools for web applications"
|
|
6
|
+
|
|
7
|
+
# Install system dependencies for Playwright
|
|
8
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
9
|
+
libnss3 \
|
|
10
|
+
libnspr4 \
|
|
11
|
+
libatk1.0-0 \
|
|
12
|
+
libatk-bridge2.0-0 \
|
|
13
|
+
libcups6 \
|
|
14
|
+
libdrm2 \
|
|
15
|
+
libxkbcommon0 \
|
|
16
|
+
libxcomposite1 \
|
|
17
|
+
libxdamage1 \
|
|
18
|
+
libxfixes3 \
|
|
19
|
+
libxrandr2 \
|
|
20
|
+
libgbm1 \
|
|
21
|
+
libpango-1.0-0 \
|
|
22
|
+
libcairo2 \
|
|
23
|
+
libasound2 \
|
|
24
|
+
libatspi2.0-0 \
|
|
25
|
+
libxshmfence1 \
|
|
26
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
27
|
+
|
|
28
|
+
# Install Playwright browsers
|
|
29
|
+
RUN npx playwright install --with-deps chromium
|
|
30
|
+
|
|
31
|
+
# Install testing tools globally
|
|
32
|
+
RUN npm install -g \
|
|
33
|
+
vitest \
|
|
34
|
+
@vitest/ui \
|
|
35
|
+
@playwright/test \
|
|
36
|
+
@vitejs/plugin-react \
|
|
37
|
+
@testing-library/react \
|
|
38
|
+
@testing-library/jest-dom \
|
|
39
|
+
@testing-library/user-event \
|
|
40
|
+
happy-dom
|
|
41
|
+
|
|
42
|
+
# Install k6 for performance testing
|
|
43
|
+
ARG TARGETARCH=amd64
|
|
44
|
+
RUN ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "arm64" || echo "amd64") && \
|
|
45
|
+
wget -q "https://github.com/grafana/k6/releases/download/v0.52.0/k6-v0.52.0-linux-${ARCH}.tar.gz" -O /tmp/k6.tar.gz && \
|
|
46
|
+
tar -xzf /tmp/k6.tar.gz -C /usr/local/bin && \
|
|
47
|
+
rm /tmp/k6.tar.gz
|
|
48
|
+
|
|
49
|
+
# Install additional tools
|
|
50
|
+
RUN npm install -g \
|
|
51
|
+
supertest \
|
|
52
|
+
msw \
|
|
53
|
+
@prisma/cli
|
|
54
|
+
|
|
55
|
+
# Create non-root user for security
|
|
56
|
+
RUN useradd -m -u 1000 -s /bin/bash demon && \
|
|
57
|
+
mkdir -p /app && \
|
|
58
|
+
chown -R demon:demon /app
|
|
59
|
+
|
|
60
|
+
# Switch to non-root user
|
|
61
|
+
USER demon
|
|
62
|
+
WORKDIR /app
|
|
63
|
+
|
|
64
|
+
# Keep container alive for docker exec
|
|
65
|
+
CMD ["sleep", "infinity"]
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
// --- Config ---
|
|
9
|
+
const CONFIG = {
|
|
10
|
+
IMAGE: 'demon-tools',
|
|
11
|
+
CONTAINER: 'demon-tools',
|
|
12
|
+
PROMPT_SRC: path.join(__dirname, '..', 'prompts', 'EXECUTE.md'),
|
|
13
|
+
// Cross-platform home directory
|
|
14
|
+
PROMPT_DEST: path.join(
|
|
15
|
+
process.env.HOME ||
|
|
16
|
+
process.env.USERPROFILE ||
|
|
17
|
+
path.join(process.env.HOMEDRIVE, process.env.HOMEPATH),
|
|
18
|
+
'.demon',
|
|
19
|
+
'EXECUTE.md'
|
|
20
|
+
),
|
|
21
|
+
PROJECT_DIR: process.cwd(),
|
|
22
|
+
DOCKERFILE: path.join(__dirname, 'Dockerfile')
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// --- Colors ---
|
|
26
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
27
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
28
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
29
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
30
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
31
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
32
|
+
const blue = (s) => `\x1b[34m${s}\x1b[0m`;
|
|
33
|
+
const magenta = (s) => `\x1b[35m${s}\x1b[0m`;
|
|
34
|
+
|
|
35
|
+
// --- Utilities ---
|
|
36
|
+
function run(cmd, { silent = false, timeout = 60000 } = {}) {
|
|
37
|
+
try {
|
|
38
|
+
return execSync(cmd, { encoding: 'utf-8', stdio: 'pipe', timeout }).trim();
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (!silent && error.status !== null) {
|
|
41
|
+
console.error(dim(` [debug] Command exited with code ${error.status}`));
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function fail(msg) {
|
|
48
|
+
console.error(`\n ${red('✗')} ${msg}\n`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function ask(question) {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
55
|
+
rl.question(question, (answer) => {
|
|
56
|
+
rl.close();
|
|
57
|
+
resolve(answer.trim().toLowerCase());
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// --- Project Markers ---
|
|
63
|
+
const PROJECT_MARKERS = [
|
|
64
|
+
'package.json',
|
|
65
|
+
'requirements.txt',
|
|
66
|
+
'pyproject.toml',
|
|
67
|
+
'Pipfile',
|
|
68
|
+
'go.mod',
|
|
69
|
+
'pom.xml',
|
|
70
|
+
'build.gradle',
|
|
71
|
+
'Gemfile',
|
|
72
|
+
'composer.json',
|
|
73
|
+
'Cargo.toml'
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
function hasProjectFiles() {
|
|
77
|
+
const cwd = path.resolve(process.cwd());
|
|
78
|
+
try {
|
|
79
|
+
const realCwd = fs.realpathSync(cwd);
|
|
80
|
+
if (realCwd !== cwd) {
|
|
81
|
+
console.warn(yellow(' ⚠ Symbolic link detected in path, using real path'));
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
// realpathSync failed, continue with cwd
|
|
85
|
+
}
|
|
86
|
+
return PROJECT_MARKERS.some((f) => fs.existsSync(path.join(cwd, f)));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- Detection Import ---
|
|
90
|
+
function runDetection(projectDir) {
|
|
91
|
+
const detectorPath = path.join(__dirname, '..', 'agents', 'detector.js');
|
|
92
|
+
|
|
93
|
+
if (!fs.existsSync(detectorPath)) {
|
|
94
|
+
// Fallback if detector doesn't exist yet
|
|
95
|
+
return {
|
|
96
|
+
framework: 'Unknown',
|
|
97
|
+
language: 'JavaScript/TypeScript',
|
|
98
|
+
testRunner: 'Vitest',
|
|
99
|
+
database: null,
|
|
100
|
+
existingTests: 0,
|
|
101
|
+
coverage: null,
|
|
102
|
+
dependencies: [],
|
|
103
|
+
target: 'http://localhost:3000'
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
// Execute detector as a module
|
|
109
|
+
const detector = require(detectorPath);
|
|
110
|
+
return detector.analyze(projectDir);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
console.log(dim(` [debug] Detection error: ${e.message}`));
|
|
113
|
+
return {
|
|
114
|
+
framework: 'Unknown',
|
|
115
|
+
language: 'JavaScript/TypeScript',
|
|
116
|
+
testRunner: 'Vitest',
|
|
117
|
+
database: null,
|
|
118
|
+
existingTests: 0,
|
|
119
|
+
coverage: null,
|
|
120
|
+
dependencies: [],
|
|
121
|
+
target: 'http://localhost:3000'
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// --- Prompt Generation ---
|
|
127
|
+
function generatePrompt(context) {
|
|
128
|
+
const promptSrc = CONFIG.PROMPT_SRC;
|
|
129
|
+
|
|
130
|
+
if (!fs.existsSync(promptSrc)) {
|
|
131
|
+
return generateFallbackPrompt(context);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const basePrompt = fs.readFileSync(promptSrc, 'utf-8');
|
|
135
|
+
|
|
136
|
+
// Build context block
|
|
137
|
+
const contextBlock = buildContextBlock(context);
|
|
138
|
+
|
|
139
|
+
return contextBlock + '\n' + basePrompt;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function buildContextBlock(context) {
|
|
143
|
+
const lines = [
|
|
144
|
+
'> **DETECTED CONTEXT**',
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
if (context.framework) {
|
|
148
|
+
lines.push(`> Framework: ${context.framework}`);
|
|
149
|
+
}
|
|
150
|
+
if (context.language) {
|
|
151
|
+
lines.push(`> Language: ${context.language}`);
|
|
152
|
+
}
|
|
153
|
+
if (context.testRunner) {
|
|
154
|
+
lines.push(`> Test Runner: ${context.testRunner}`);
|
|
155
|
+
}
|
|
156
|
+
if (context.database) {
|
|
157
|
+
lines.push(`> Database: ${context.database.type || 'detected'}`);
|
|
158
|
+
lines.push(`> DB Connection: ${context.database.connection || 'DATABASE_URL'}`);
|
|
159
|
+
lines.push(`> Test Strategy: Transaction rollback (do not modify real data)`);
|
|
160
|
+
} else {
|
|
161
|
+
lines.push(`> Database: none detected`);
|
|
162
|
+
}
|
|
163
|
+
lines.push(`> Existing Tests: ${context.existingTests || 0} found`);
|
|
164
|
+
if (context.coverage) {
|
|
165
|
+
lines.push(`> Current Coverage: ${context.coverage}`);
|
|
166
|
+
}
|
|
167
|
+
if (context.dependencies && context.dependencies.length > 0) {
|
|
168
|
+
lines.push(`> Key Dependencies: ${context.dependencies.join(', ')}`);
|
|
169
|
+
}
|
|
170
|
+
lines.push(`> Target: ${context.target || 'http://localhost:3000'}`);
|
|
171
|
+
|
|
172
|
+
lines.push('');
|
|
173
|
+
lines.push('> **IMPORTANT**:');
|
|
174
|
+
lines.push('> - Use this detected context. Do not re-detect.');
|
|
175
|
+
lines.push('> - Always read source code before generating tests.');
|
|
176
|
+
lines.push('> - Run tests to verify they work before declaring success.');
|
|
177
|
+
if (context.database) {
|
|
178
|
+
lines.push('> - Use transaction rollback for DB tests - never modify real data.');
|
|
179
|
+
}
|
|
180
|
+
lines.push('');
|
|
181
|
+
lines.push('> **WORKFLOW**:');
|
|
182
|
+
lines.push('> 1. Read ~/.demon/EXECUTE.md for full instructions');
|
|
183
|
+
lines.push('> 2. Generate tests following the detected patterns');
|
|
184
|
+
lines.push('> 3. Run tests via Docker container');
|
|
185
|
+
lines.push('> 4. Fix failures iteratively until all pass');
|
|
186
|
+
lines.push('> 5. Generate final report');
|
|
187
|
+
lines.push('');
|
|
188
|
+
|
|
189
|
+
return lines.join('\n');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function generateFallbackPrompt(context) {
|
|
193
|
+
return `# Demon — Automated Testing Process
|
|
194
|
+
|
|
195
|
+
> **DETECTED CONTEXT**
|
|
196
|
+
> Framework: ${context.framework || 'Unknown'}
|
|
197
|
+
> Language: ${context.language || 'JavaScript/TypeScript'}
|
|
198
|
+
> Test Runner: ${context.testRunner || 'Vitest'}
|
|
199
|
+
> Database: ${context.database?.type || 'none'}
|
|
200
|
+
> Target: ${context.target || 'http://localhost:3000'}
|
|
201
|
+
|
|
202
|
+
## Instructions
|
|
203
|
+
|
|
204
|
+
This is the automated testing agent. Follow these steps:
|
|
205
|
+
|
|
206
|
+
1. **Read the project structure** - Understand the framework and patterns
|
|
207
|
+
2. **Generate unit tests** - For components, hooks, and utilities
|
|
208
|
+
3. **Generate integration tests** - For API routes and database operations
|
|
209
|
+
4. **Generate E2E tests** - For critical user flows
|
|
210
|
+
5. **Run performance tests** - API load testing and DB query analysis
|
|
211
|
+
6. **Analyze dependencies** - Check for efficiency patterns
|
|
212
|
+
|
|
213
|
+
Always run tests to verify they work. When a test fails, analyze and fix before proceeding.
|
|
214
|
+
|
|
215
|
+
## Tool Execution
|
|
216
|
+
|
|
217
|
+
All test tools run inside the Demon Docker container:
|
|
218
|
+
|
|
219
|
+
\`\`\`bash
|
|
220
|
+
docker exec demon-tools <command>
|
|
221
|
+
\`\`\`
|
|
222
|
+
|
|
223
|
+
### Available Commands
|
|
224
|
+
|
|
225
|
+
| Task | Command |
|
|
226
|
+
|------|---------|
|
|
227
|
+
| Unit tests | \`docker exec demon-tools npm test\` |
|
|
228
|
+
| E2E tests | \`docker exec demon-tools npx playwright test\` |
|
|
229
|
+
| Performance | \`docker exec demon-tools k6 run tests/performance/load.js\` |
|
|
230
|
+
|
|
231
|
+
## Phase 1 - Unit Tests
|
|
232
|
+
|
|
233
|
+
Generate tests for:
|
|
234
|
+
- Components (render, props, events, edge cases)
|
|
235
|
+
- Hooks (state updates, return values, cleanup)
|
|
236
|
+
- Utils (pure functions, validators)
|
|
237
|
+
|
|
238
|
+
Template:
|
|
239
|
+
\`\`\`typescript
|
|
240
|
+
import { render, screen } from '@testing-library/react';
|
|
241
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
242
|
+
import { Component } from '@/components/Component';
|
|
243
|
+
|
|
244
|
+
describe('Component', () => {
|
|
245
|
+
it('should render', () => {
|
|
246
|
+
render(<Component />);
|
|
247
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
\`\`\`
|
|
251
|
+
|
|
252
|
+
## Phase 2 - Integration Tests
|
|
253
|
+
|
|
254
|
+
For API routes and database operations:
|
|
255
|
+
\`\`\`typescript
|
|
256
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
257
|
+
import { db } from '@test/db';
|
|
258
|
+
|
|
259
|
+
describe('API Integration', () => {
|
|
260
|
+
beforeEach(async () => {
|
|
261
|
+
await db.begin();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
afterEach(async () => {
|
|
265
|
+
await db.rollback();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should create resource', async () => {
|
|
269
|
+
const result = await db.resource.create({ data: { name: 'test' } });
|
|
270
|
+
expect(result).toHaveProperty('id');
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
\`\`\`
|
|
274
|
+
|
|
275
|
+
## Phase 3 - E2E Tests
|
|
276
|
+
|
|
277
|
+
Use Playwright for user flows:
|
|
278
|
+
\`\`\`typescript
|
|
279
|
+
import { test, expect } from '@playwright/test';
|
|
280
|
+
|
|
281
|
+
test('user journey', async ({ page }) => {
|
|
282
|
+
await page.goto('/login');
|
|
283
|
+
await page.fill('input[name="email"]', 'test@example.com');
|
|
284
|
+
await page.click('button[type="submit"]');
|
|
285
|
+
await expect(page).toHaveURL('/dashboard');
|
|
286
|
+
});
|
|
287
|
+
\`\`\`
|
|
288
|
+
|
|
289
|
+
## Fix Loop
|
|
290
|
+
|
|
291
|
+
When tests fail:
|
|
292
|
+
1. Analyze the error
|
|
293
|
+
2. Determine if it's a test issue or code bug
|
|
294
|
+
3. Apply fix
|
|
295
|
+
4. Re-test
|
|
296
|
+
|
|
297
|
+
## Completion
|
|
298
|
+
|
|
299
|
+
Report:
|
|
300
|
+
\`\`\`
|
|
301
|
+
✓ Unit Tests: X created, Y passing
|
|
302
|
+
✓ Integration: X created, Y passing
|
|
303
|
+
✓ E2E: X created, Y passing
|
|
304
|
+
✓ Performance: API p95 = Xms
|
|
305
|
+
\`\`\`
|
|
306
|
+
`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// --- Main Function ---
|
|
310
|
+
async function main() {
|
|
311
|
+
console.log('');
|
|
312
|
+
console.log(bold(magenta(' ╔═══════════════════════════════════════╗')));
|
|
313
|
+
console.log(bold(magenta(' ║ Demon ║')));
|
|
314
|
+
console.log(bold(magenta(' ║ AI-Powered Test Generation ║')));
|
|
315
|
+
console.log(bold(magenta(' ╚═══════════════════════════════════════╝')));
|
|
316
|
+
console.log('');
|
|
317
|
+
console.log(dim(' Automated testing toolkit for web applications'));
|
|
318
|
+
console.log('');
|
|
319
|
+
|
|
320
|
+
// Check if we're in a project directory
|
|
321
|
+
if (!hasProjectFiles()) {
|
|
322
|
+
console.log(yellow(' ⚠ No project markers found.'));
|
|
323
|
+
console.log(yellow(' Please run from a project directory with package.json or equivalent.'));
|
|
324
|
+
console.log('');
|
|
325
|
+
const answer = await ask(' Continue anyway? ' + dim('(Y/n)') + ' ');
|
|
326
|
+
if (answer === 'n' || answer === 'no') {
|
|
327
|
+
process.exit(0);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// --- Step 1: Check Docker ---
|
|
332
|
+
console.log(` ${dim('→')} Checking Docker...`);
|
|
333
|
+
if (run('docker info', { silent: true }) === null) {
|
|
334
|
+
fail(`Docker is not running.
|
|
335
|
+
|
|
336
|
+
Start Docker Desktop (or the Docker daemon) and try again.
|
|
337
|
+
|
|
338
|
+
Install Docker: ${cyan('https://docs.docker.com/get-docker/')}`);
|
|
339
|
+
}
|
|
340
|
+
console.log(` ${green('✓')} Docker is running`);
|
|
341
|
+
|
|
342
|
+
// --- Step 2: Build image if missing ---
|
|
343
|
+
const imageExists = run(`docker images -q ${CONFIG.IMAGE}`);
|
|
344
|
+
|
|
345
|
+
if (!imageExists) {
|
|
346
|
+
console.log('');
|
|
347
|
+
console.log(` ${yellow('◆')} The testing toolkit needs to be installed (~500 MB Docker image).`);
|
|
348
|
+
console.log(` ${dim('This only happens once.')}`);
|
|
349
|
+
console.log('');
|
|
350
|
+
const answer = await ask(` Install it now? ${dim('(Y/n)')} `);
|
|
351
|
+
if (answer === 'n' || answer === 'no') {
|
|
352
|
+
console.log('');
|
|
353
|
+
console.log(dim(' No problem. Run npx @oalacea/demon again when you\'re ready.'));
|
|
354
|
+
console.log('');
|
|
355
|
+
process.exit(0);
|
|
356
|
+
});
|
|
357
|
+
console.log('');
|
|
358
|
+
console.log(` ${yellow('→')} Building testing toolkit...`);
|
|
359
|
+
console.log(dim(' This may take 2-3 minutes on first run...'));
|
|
360
|
+
console.log('');
|
|
361
|
+
try {
|
|
362
|
+
execSync(
|
|
363
|
+
`docker build -t ${CONFIG.IMAGE} -f "${CONFIG.DOCKERFILE}" "${path.dirname(CONFIG.DOCKERFILE)}"`,
|
|
364
|
+
{ stdio: 'inherit', timeout: 600000 }
|
|
365
|
+
);
|
|
366
|
+
} catch {
|
|
367
|
+
fail(`Failed to build the testing toolkit image.
|
|
368
|
+
|
|
369
|
+
Try manually:
|
|
370
|
+
${cyan(`docker build -t ${CONFIG.IMAGE} -f "${CONFIG.DOCKERFILE}" "${path.dirname(CONFIG.DOCKERFILE)}"`)}`);
|
|
371
|
+
}
|
|
372
|
+
console.log('');
|
|
373
|
+
console.log(` ${green('✓')} Testing toolkit installed`);
|
|
374
|
+
} else {
|
|
375
|
+
console.log(` ${green('✓')} Testing toolkit ready`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// --- Step 3: Start container if not running ---
|
|
379
|
+
const containerRunning = run(
|
|
380
|
+
`docker ps --filter "name=^${CONFIG.CONTAINER}$" --format "{{.Names}}"`
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
if (containerRunning === CONFIG.CONTAINER) {
|
|
384
|
+
console.log(` ${green('✓')} Toolkit container running (${bold(CONFIG.CONTAINER)})`);
|
|
385
|
+
} else {
|
|
386
|
+
const containerExists = run(
|
|
387
|
+
`docker ps -a --filter "name=^${CONFIG.CONTAINER}$" --format "{{.Names}}"`
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
if (containerExists === CONFIG.CONTAINER) {
|
|
391
|
+
process.stdout.write(` ${yellow('→')} Starting toolkit container...`);
|
|
392
|
+
if (run(`docker start ${CONFIG.CONTAINER}`, { timeout: 30000 }) === null) {
|
|
393
|
+
console.log('');
|
|
394
|
+
fail(`Failed to start container.
|
|
395
|
+
|
|
396
|
+
Try manually:
|
|
397
|
+
${cyan(`docker start ${CONFIG.CONTAINER}`)}`);
|
|
398
|
+
}
|
|
399
|
+
console.log(` ${green('done')}`);
|
|
400
|
+
} else {
|
|
401
|
+
const isLinux = process.platform === 'linux';
|
|
402
|
+
const networkFlag = isLinux ? '--network=host' : '';
|
|
403
|
+
|
|
404
|
+
process.stdout.write(` ${yellow('→')} Creating toolkit container (${CONFIG.CONTAINER})...`);
|
|
405
|
+
const runCmd = `docker run -d --name ${CONFIG.CONTAINER} ${networkFlag} ${CONFIG.IMAGE}`.replace(/\s+/g, ' ');
|
|
406
|
+
if (run(runCmd, { timeout: 30000 }) === null) {
|
|
407
|
+
console.log('');
|
|
408
|
+
fail(`Failed to create container.
|
|
409
|
+
|
|
410
|
+
Try manually:
|
|
411
|
+
${cyan(runCmd)}`);
|
|
412
|
+
}
|
|
413
|
+
console.log(` ${green('done')}`);
|
|
414
|
+
}
|
|
415
|
+
console.log(` ${green('✓')} Toolkit container running (${bold(CONFIG.CONTAINER)})`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// --- Step 4: Run detection ---
|
|
419
|
+
console.log(` ${dim('→')} Analyzing project...`);
|
|
420
|
+
const context = runDetection(CONFIG.PROJECT_DIR);
|
|
421
|
+
|
|
422
|
+
// --- Step 5: Create demon directory and prompt ---
|
|
423
|
+
const demonDir = path.dirname(CONFIG.PROMPT_DEST);
|
|
424
|
+
if (!fs.existsSync(demonDir)) {
|
|
425
|
+
fs.mkdirSync(demonDir, { recursive: true });
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const prompt = generatePrompt(context);
|
|
429
|
+
fs.writeFileSync(CONFIG.PROMPT_DEST, prompt);
|
|
430
|
+
console.log(` ${green('✓')} Prompt installed to ${bold('~/.demon/EXECUTE.md')}`);
|
|
431
|
+
|
|
432
|
+
// --- Step 6: Print summary ---
|
|
433
|
+
console.log('');
|
|
434
|
+
console.log(bold(' Detected Configuration:'));
|
|
435
|
+
console.log(` Framework: ${cyan(context.framework || 'Unknown')}`);
|
|
436
|
+
console.log(` Language: ${cyan(context.language || 'JavaScript/TypeScript')}`);
|
|
437
|
+
console.log(` Test Runner: ${cyan(context.testRunner || 'Vitest')}`);
|
|
438
|
+
if (context.database) {
|
|
439
|
+
console.log(` Database: ${cyan(context.database.type)}`);
|
|
440
|
+
console.log(` Connection: ${cyan(context.database.connection)}`);
|
|
441
|
+
}
|
|
442
|
+
console.log(` Existing: ${cyan((context.existingTests || 0) + ' tests')}`);
|
|
443
|
+
console.log(` Target: ${cyan(context.target || 'http://localhost:3000')}`);
|
|
444
|
+
console.log('');
|
|
445
|
+
|
|
446
|
+
// --- Step 7: Print instructions ---
|
|
447
|
+
console.log(bold(' Ready!') + ' Open your AI agent from your project directory and paste:');
|
|
448
|
+
console.log('');
|
|
449
|
+
console.log(` ${cyan(`Read ~/.demon/EXECUTE.md and start the testing process`)}`);
|
|
450
|
+
console.log('');
|
|
451
|
+
console.log(dim(' Works with Claude Code, Cursor, Windsurf, Aider, Codex...'));
|
|
452
|
+
console.log('');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
main();
|