@cpretzinger/boss-claude 1.0.0 → 1.0.2
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/README.md +304 -1
- package/bin/boss-claude.js +1138 -0
- package/bin/commands/mode.js +250 -0
- package/bin/onyx-guard.js +259 -0
- package/bin/onyx-guard.sh +251 -0
- package/bin/prompts.js +284 -0
- package/bin/rollback.js +85 -0
- package/bin/setup-wizard.js +492 -0
- package/config/.env.example +17 -0
- package/lib/README.md +83 -0
- package/lib/agent-logger.js +61 -0
- package/lib/agents/memory-engineers/github-memory-engineer.js +251 -0
- package/lib/agents/memory-engineers/postgres-memory-engineer.js +633 -0
- package/lib/agents/memory-engineers/qdrant-memory-engineer.js +358 -0
- package/lib/agents/memory-engineers/redis-memory-engineer.js +383 -0
- package/lib/agents/memory-supervisor.js +526 -0
- package/lib/agents/registry.js +135 -0
- package/lib/auto-monitor.js +131 -0
- package/lib/checkpoint-hook.js +112 -0
- package/lib/checkpoint.js +319 -0
- package/lib/commentator.js +213 -0
- package/lib/context-scribe.js +120 -0
- package/lib/delegation-strategies.js +326 -0
- package/lib/hierarchy-validator.js +643 -0
- package/lib/index.js +15 -0
- package/lib/init-with-mode.js +261 -0
- package/lib/init.js +44 -6
- package/lib/memory-result-aggregator.js +252 -0
- package/lib/memory.js +35 -7
- package/lib/mode-enforcer.js +473 -0
- package/lib/onyx-banner.js +169 -0
- package/lib/onyx-identity.js +214 -0
- package/lib/onyx-monitor.js +381 -0
- package/lib/onyx-reminder.js +188 -0
- package/lib/onyx-tool-interceptor.js +341 -0
- package/lib/onyx-wrapper.js +315 -0
- package/lib/orchestrator-gate.js +334 -0
- package/lib/output-formatter.js +296 -0
- package/lib/postgres.js +1 -1
- package/lib/prompt-injector.js +220 -0
- package/lib/prompts.js +532 -0
- package/lib/session.js +153 -6
- package/lib/setup/README.md +187 -0
- package/lib/setup/env-manager.js +785 -0
- package/lib/setup/error-recovery.js +630 -0
- package/lib/setup/explain-scopes.js +385 -0
- package/lib/setup/github-instructions.js +333 -0
- package/lib/setup/github-repo.js +254 -0
- package/lib/setup/import-credentials.js +498 -0
- package/lib/setup/index.js +62 -0
- package/lib/setup/init-postgres.js +785 -0
- package/lib/setup/init-redis.js +456 -0
- package/lib/setup/integration-test.js +652 -0
- package/lib/setup/progress.js +357 -0
- package/lib/setup/rollback.js +670 -0
- package/lib/setup/rollback.test.js +452 -0
- package/lib/setup/setup-with-rollback.example.js +351 -0
- package/lib/setup/summary.js +400 -0
- package/lib/setup/test-github-setup.js +10 -0
- package/lib/setup/test-postgres-init.js +98 -0
- package/lib/setup/verify-setup.js +102 -0
- package/lib/task-agent-worker.js +235 -0
- package/lib/token-monitor.js +466 -0
- package/lib/tool-wrapper-integration.js +369 -0
- package/lib/tool-wrapper.js +387 -0
- package/lib/validators/README.md +497 -0
- package/lib/validators/config.js +583 -0
- package/lib/validators/config.test.js +175 -0
- package/lib/validators/github.js +310 -0
- package/lib/validators/github.test.js +61 -0
- package/lib/validators/index.js +15 -0
- package/lib/validators/postgres.js +525 -0
- package/package.json +98 -13
- package/scripts/benchmark-memory.js +433 -0
- package/scripts/check-secrets.sh +12 -0
- package/scripts/fetch-todos.mjs +148 -0
- package/scripts/graceful-shutdown.sh +156 -0
- package/scripts/install-onyx-hooks.js +373 -0
- package/scripts/install.js +119 -18
- package/scripts/redis-monitor.js +284 -0
- package/scripts/redis-setup.js +412 -0
- package/scripts/test-memory-retrieval.js +201 -0
- package/scripts/validate-exports.js +68 -0
- package/scripts/validate-package.js +120 -0
- package/scripts/verify-onyx-deployment.js +309 -0
- package/scripts/verify-redis-deployment.js +354 -0
- package/scripts/verify-redis-init.js +219 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* BOSS CLAUDE - Rollback System Tests
|
|
4
|
+
* Demonstrates rollback functionality and validates operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { RollbackManager, SetupState, recordAction } from './rollback.js';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import { existsSync } from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import os from 'os';
|
|
13
|
+
|
|
14
|
+
const TEST_DIR = path.join(os.tmpdir(), 'boss-claude-rollback-test');
|
|
15
|
+
const TEST_STATE = path.join(TEST_DIR, 'test-state.json');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Test utilities
|
|
19
|
+
*/
|
|
20
|
+
class TestRunner {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.tests = [];
|
|
23
|
+
this.passed = 0;
|
|
24
|
+
this.failed = 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async test(name, fn) {
|
|
28
|
+
try {
|
|
29
|
+
console.log(chalk.blue(`\n▶ ${name}`));
|
|
30
|
+
await fn();
|
|
31
|
+
console.log(chalk.green(` ✓ PASSED`));
|
|
32
|
+
this.passed++;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.log(chalk.red(` ✗ FAILED: ${error.message}`));
|
|
35
|
+
if (error.stack) {
|
|
36
|
+
console.log(chalk.gray(error.stack));
|
|
37
|
+
}
|
|
38
|
+
this.failed++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
summary() {
|
|
43
|
+
console.log(chalk.bold('\n' + '='.repeat(70)));
|
|
44
|
+
console.log(chalk.bold('TEST SUMMARY'));
|
|
45
|
+
console.log('='.repeat(70));
|
|
46
|
+
console.log(chalk.green(` Passed: ${this.passed}`));
|
|
47
|
+
if (this.failed > 0) {
|
|
48
|
+
console.log(chalk.red(` Failed: ${this.failed}`));
|
|
49
|
+
}
|
|
50
|
+
console.log(chalk.white(` Total: ${this.passed + this.failed}`));
|
|
51
|
+
console.log('='.repeat(70) + '\n');
|
|
52
|
+
|
|
53
|
+
return this.failed === 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Setup test environment
|
|
59
|
+
*/
|
|
60
|
+
async function setupTestEnv() {
|
|
61
|
+
if (existsSync(TEST_DIR)) {
|
|
62
|
+
await fs.rm(TEST_DIR, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
await fs.mkdir(TEST_DIR, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Cleanup test environment
|
|
69
|
+
*/
|
|
70
|
+
async function cleanupTestEnv() {
|
|
71
|
+
if (existsSync(TEST_DIR)) {
|
|
72
|
+
await fs.rm(TEST_DIR, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Test setup state tracking
|
|
78
|
+
*/
|
|
79
|
+
async function testSetupState(runner) {
|
|
80
|
+
await runner.test('SetupState - Save and Load', async () => {
|
|
81
|
+
const state = new SetupState();
|
|
82
|
+
state.state.actions.push({
|
|
83
|
+
type: 'test',
|
|
84
|
+
data: { foo: 'bar' },
|
|
85
|
+
timestamp: new Date().toISOString()
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Override state file path for testing
|
|
89
|
+
const originalStateFile = path.join(os.homedir(), '.boss-claude', 'setup-state.json');
|
|
90
|
+
const testStateFile = TEST_STATE;
|
|
91
|
+
|
|
92
|
+
// Save to test location
|
|
93
|
+
await fs.writeFile(testStateFile, JSON.stringify(state.state, null, 2));
|
|
94
|
+
|
|
95
|
+
// Load from test location
|
|
96
|
+
const loaded = JSON.parse(await fs.readFile(testStateFile, 'utf8'));
|
|
97
|
+
|
|
98
|
+
if (loaded.actions.length !== 1) {
|
|
99
|
+
throw new Error('Failed to save/load state');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (loaded.actions[0].type !== 'test') {
|
|
103
|
+
throw new Error('State data mismatch');
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await runner.test('SetupState - Record Action', async () => {
|
|
108
|
+
const state = new SetupState();
|
|
109
|
+
state.state.actions = [];
|
|
110
|
+
|
|
111
|
+
// Record multiple actions
|
|
112
|
+
state.state.actions.push({
|
|
113
|
+
type: 'github_repo',
|
|
114
|
+
data: { owner: 'test', repo: 'test-repo' },
|
|
115
|
+
timestamp: new Date().toISOString()
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
state.state.actions.push({
|
|
119
|
+
type: 'env_vars',
|
|
120
|
+
data: { keys: ['TEST_KEY'] },
|
|
121
|
+
timestamp: new Date().toISOString()
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (state.state.actions.length !== 2) {
|
|
125
|
+
throw new Error('Failed to record actions');
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await runner.test('SetupState - Mark Completed', async () => {
|
|
130
|
+
const state = new SetupState();
|
|
131
|
+
state.state.completed = false;
|
|
132
|
+
|
|
133
|
+
state.state.completed = true;
|
|
134
|
+
|
|
135
|
+
if (!state.state.completed) {
|
|
136
|
+
throw new Error('Failed to mark completed');
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Test file rollback
|
|
143
|
+
*/
|
|
144
|
+
async function testFileRollback(runner) {
|
|
145
|
+
await runner.test('File Rollback - Single File', async () => {
|
|
146
|
+
const testFile = path.join(TEST_DIR, 'test-file.txt');
|
|
147
|
+
await fs.writeFile(testFile, 'test content');
|
|
148
|
+
|
|
149
|
+
const manager = new RollbackManager({ verbose: false });
|
|
150
|
+
const result = await manager.rollbackFiles({
|
|
151
|
+
data: { files: [testFile] }
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (!result.success) {
|
|
155
|
+
throw new Error('File rollback failed');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (existsSync(testFile)) {
|
|
159
|
+
throw new Error('File was not deleted');
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await runner.test('File Rollback - Multiple Files', async () => {
|
|
164
|
+
const files = [
|
|
165
|
+
path.join(TEST_DIR, 'file1.txt'),
|
|
166
|
+
path.join(TEST_DIR, 'file2.txt'),
|
|
167
|
+
path.join(TEST_DIR, 'file3.txt')
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
for (const file of files) {
|
|
171
|
+
await fs.writeFile(file, 'test');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const manager = new RollbackManager({ verbose: false });
|
|
175
|
+
const result = await manager.rollbackFiles({
|
|
176
|
+
data: { files }
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (!result.success) {
|
|
180
|
+
throw new Error('Multiple file rollback failed');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (result.removed !== 3) {
|
|
184
|
+
throw new Error(`Expected 3 files removed, got ${result.removed}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const file of files) {
|
|
188
|
+
if (existsSync(file)) {
|
|
189
|
+
throw new Error(`File ${file} was not deleted`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
await runner.test('File Rollback - Missing File (Should Succeed)', async () => {
|
|
195
|
+
const nonExistentFile = path.join(TEST_DIR, 'does-not-exist.txt');
|
|
196
|
+
|
|
197
|
+
const manager = new RollbackManager({ verbose: false });
|
|
198
|
+
const result = await manager.rollbackFiles({
|
|
199
|
+
data: { files: [nonExistentFile] }
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (!result.success) {
|
|
203
|
+
throw new Error('Should succeed for missing files');
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Test directory rollback
|
|
210
|
+
*/
|
|
211
|
+
async function testDirectoryRollback(runner) {
|
|
212
|
+
await runner.test('Directory Rollback - Single Directory', async () => {
|
|
213
|
+
const testDir = path.join(TEST_DIR, 'test-subdir');
|
|
214
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
215
|
+
await fs.writeFile(path.join(testDir, 'file.txt'), 'test');
|
|
216
|
+
|
|
217
|
+
const manager = new RollbackManager({ verbose: false });
|
|
218
|
+
const result = await manager.rollbackDirectories({
|
|
219
|
+
data: { directories: [testDir] }
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (!result.success) {
|
|
223
|
+
throw new Error('Directory rollback failed');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (existsSync(testDir)) {
|
|
227
|
+
throw new Error('Directory was not deleted');
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
await runner.test('Directory Rollback - Nested Directories', async () => {
|
|
232
|
+
const baseDir = path.join(TEST_DIR, 'nested');
|
|
233
|
+
const subDir = path.join(baseDir, 'level1', 'level2');
|
|
234
|
+
await fs.mkdir(subDir, { recursive: true });
|
|
235
|
+
await fs.writeFile(path.join(subDir, 'deep-file.txt'), 'test');
|
|
236
|
+
|
|
237
|
+
const manager = new RollbackManager({ verbose: false });
|
|
238
|
+
const result = await manager.rollbackDirectories({
|
|
239
|
+
data: { directories: [baseDir] }
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if (!result.success) {
|
|
243
|
+
throw new Error('Nested directory rollback failed');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (existsSync(baseDir)) {
|
|
247
|
+
throw new Error('Base directory was not deleted');
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Test dry run mode
|
|
254
|
+
*/
|
|
255
|
+
async function testDryRun(runner) {
|
|
256
|
+
await runner.test('Dry Run - Files Not Deleted', async () => {
|
|
257
|
+
const testFile = path.join(TEST_DIR, 'dry-run-test.txt');
|
|
258
|
+
await fs.writeFile(testFile, 'should not be deleted');
|
|
259
|
+
|
|
260
|
+
const manager = new RollbackManager({ verbose: false, dryRun: true });
|
|
261
|
+
const result = await manager.rollbackFiles({
|
|
262
|
+
data: { files: [testFile] }
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
if (!result.dryRun) {
|
|
266
|
+
throw new Error('Dry run flag not set in result');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!existsSync(testFile)) {
|
|
270
|
+
throw new Error('File was deleted in dry run mode!');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Cleanup
|
|
274
|
+
await fs.unlink(testFile);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await runner.test('Dry Run - Directories Not Deleted', async () => {
|
|
278
|
+
const testDir = path.join(TEST_DIR, 'dry-run-dir');
|
|
279
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
280
|
+
|
|
281
|
+
const manager = new RollbackManager({ verbose: false, dryRun: true });
|
|
282
|
+
const result = await manager.rollbackDirectories({
|
|
283
|
+
data: { directories: [testDir] }
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (!result.dryRun) {
|
|
287
|
+
throw new Error('Dry run flag not set in result');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!existsSync(testDir)) {
|
|
291
|
+
throw new Error('Directory was deleted in dry run mode!');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Cleanup
|
|
295
|
+
await fs.rm(testDir, { recursive: true });
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Test full rollback workflow
|
|
301
|
+
*/
|
|
302
|
+
async function testFullRollback(runner) {
|
|
303
|
+
await runner.test('Full Rollback - Multiple Action Types', async () => {
|
|
304
|
+
// Create test state
|
|
305
|
+
const state = {
|
|
306
|
+
timestamp: new Date().toISOString(),
|
|
307
|
+
completed: false,
|
|
308
|
+
actions: [
|
|
309
|
+
{
|
|
310
|
+
type: 'files',
|
|
311
|
+
data: {
|
|
312
|
+
files: [
|
|
313
|
+
path.join(TEST_DIR, 'rollback-file1.txt'),
|
|
314
|
+
path.join(TEST_DIR, 'rollback-file2.txt')
|
|
315
|
+
]
|
|
316
|
+
},
|
|
317
|
+
timestamp: new Date().toISOString()
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
type: 'directories',
|
|
321
|
+
data: {
|
|
322
|
+
directories: [path.join(TEST_DIR, 'rollback-dir')]
|
|
323
|
+
},
|
|
324
|
+
timestamp: new Date().toISOString()
|
|
325
|
+
}
|
|
326
|
+
]
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Create test files and directories
|
|
330
|
+
for (const file of state.actions[0].data.files) {
|
|
331
|
+
await fs.writeFile(file, 'test');
|
|
332
|
+
}
|
|
333
|
+
await fs.mkdir(state.actions[1].data.directories[0], { recursive: true });
|
|
334
|
+
|
|
335
|
+
// Save state
|
|
336
|
+
await fs.writeFile(TEST_STATE, JSON.stringify(state, null, 2));
|
|
337
|
+
|
|
338
|
+
// Execute rollback
|
|
339
|
+
const manager = new RollbackManager({ verbose: false });
|
|
340
|
+
const result = await manager.rollback(TEST_STATE);
|
|
341
|
+
|
|
342
|
+
if (!result.success) {
|
|
343
|
+
throw new Error('Full rollback failed');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (result.total !== 2) {
|
|
347
|
+
throw new Error(`Expected 2 actions, got ${result.total}`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Verify cleanup
|
|
351
|
+
for (const file of state.actions[0].data.files) {
|
|
352
|
+
if (existsSync(file)) {
|
|
353
|
+
throw new Error(`File ${file} was not deleted`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
for (const dir of state.actions[1].data.directories) {
|
|
358
|
+
if (existsSync(dir)) {
|
|
359
|
+
throw new Error(`Directory ${dir} was not deleted`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Test error handling
|
|
367
|
+
*/
|
|
368
|
+
async function testErrorHandling(runner) {
|
|
369
|
+
await runner.test('Error Handling - Invalid Action Type', async () => {
|
|
370
|
+
const manager = new RollbackManager({ verbose: false });
|
|
371
|
+
const result = await manager.rollbackAction({
|
|
372
|
+
type: 'invalid_type',
|
|
373
|
+
data: {},
|
|
374
|
+
timestamp: new Date().toISOString()
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
if (result.success) {
|
|
378
|
+
throw new Error('Should fail for invalid action type');
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
await runner.test('Error Handling - Permission Denied', async () => {
|
|
383
|
+
// This test is platform-specific and may not work everywhere
|
|
384
|
+
// Skip on systems where we can't test permissions
|
|
385
|
+
const testFile = path.join(TEST_DIR, 'permission-test.txt');
|
|
386
|
+
await fs.writeFile(testFile, 'test');
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
await fs.chmod(testFile, 0o000); // Remove all permissions
|
|
390
|
+
|
|
391
|
+
const manager = new RollbackManager({ verbose: false });
|
|
392
|
+
const result = await manager.rollbackFiles({
|
|
393
|
+
data: { files: [testFile] }
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Result may succeed or fail depending on permissions model
|
|
397
|
+
// Just verify it handles the case without crashing
|
|
398
|
+
|
|
399
|
+
// Cleanup - restore permissions first
|
|
400
|
+
await fs.chmod(testFile, 0o644);
|
|
401
|
+
await fs.unlink(testFile);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
// Permission tests may not work on all systems
|
|
404
|
+
console.log(chalk.gray(` Skipped: ${error.message}`));
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Main test execution
|
|
411
|
+
*/
|
|
412
|
+
async function runTests() {
|
|
413
|
+
console.log(chalk.bold.cyan('\n🧪 BOSS CLAUDE ROLLBACK TESTS\n'));
|
|
414
|
+
|
|
415
|
+
const runner = new TestRunner();
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
// Setup
|
|
419
|
+
await setupTestEnv();
|
|
420
|
+
|
|
421
|
+
// Run test suites
|
|
422
|
+
await testSetupState(runner);
|
|
423
|
+
await testFileRollback(runner);
|
|
424
|
+
await testDirectoryRollback(runner);
|
|
425
|
+
await testDryRun(runner);
|
|
426
|
+
await testFullRollback(runner);
|
|
427
|
+
await testErrorHandling(runner);
|
|
428
|
+
|
|
429
|
+
// Cleanup
|
|
430
|
+
await cleanupTestEnv();
|
|
431
|
+
|
|
432
|
+
// Summary
|
|
433
|
+
const success = runner.summary();
|
|
434
|
+
|
|
435
|
+
process.exit(success ? 0 : 1);
|
|
436
|
+
|
|
437
|
+
} catch (error) {
|
|
438
|
+
console.error(chalk.red.bold('\n✗ Test execution failed\n'));
|
|
439
|
+
console.error(chalk.red(error.message));
|
|
440
|
+
console.error(chalk.gray(error.stack));
|
|
441
|
+
|
|
442
|
+
await cleanupTestEnv();
|
|
443
|
+
process.exit(1);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Run tests if executed directly
|
|
448
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
449
|
+
runTests();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export { runTests, TestRunner };
|