@wundr.io/cli 1.0.1 → 1.0.2-dev.20260530180455.e1307186
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/bin/wundr.js +13 -5
- package/package.json +30 -9
- package/src/ai/ai-service.ts +6 -4
- package/src/ai/claude-client.ts +6 -2
- package/src/ai/conversation-manager.ts +12 -5
- package/src/cli.ts +42 -13
- package/src/commands/ai.ts +340 -64
- package/src/commands/alignment.ts +1212 -0
- package/src/commands/analyze-optimized.ts +371 -33
- package/src/commands/analyze.ts +8 -6
- package/src/commands/batch.ts +166 -26
- package/src/commands/chat.ts +20 -10
- package/src/commands/claude-init.ts +31 -27
- package/src/commands/claude-setup.ts +761 -81
- package/src/commands/computer-setup.ts +524 -12
- package/src/commands/create-command.ts +3 -3
- package/src/commands/create.ts +9 -6
- package/src/commands/dashboard.ts +11 -6
- package/src/commands/govern.ts +11 -6
- package/src/commands/governance.ts +1005 -0
- package/src/commands/guardian.ts +887 -0
- package/src/commands/init.ts +104 -11
- package/src/commands/orchestrator.ts +789 -0
- package/src/commands/performance-optimizer.ts +15 -10
- package/src/commands/plugins.ts +8 -5
- package/src/commands/project-update.ts +1156 -0
- package/src/commands/rag.ts +1011 -0
- package/src/commands/session.ts +631 -0
- package/src/commands/setup.ts +42 -344
- package/src/commands/test-init.ts +3 -2
- package/src/commands/test.ts +3 -2
- package/src/commands/watch.ts +21 -11
- package/src/commands/worktree.ts +1057 -0
- package/src/context/context-manager.ts +5 -2
- package/src/context/session-manager.ts +18 -7
- package/src/framework/command-interface.ts +520 -0
- package/src/framework/command-registry.ts +942 -0
- package/src/framework/completion-exporter.ts +383 -0
- package/src/framework/debug-logger.ts +519 -0
- package/src/framework/error-handler.ts +867 -0
- package/src/framework/help-generator.ts +540 -0
- package/src/framework/index.ts +169 -0
- package/src/framework/interactive-repl.ts +703 -0
- package/src/framework/output-formatter.ts +834 -0
- package/src/framework/progress-manager.ts +539 -0
- package/src/index.ts +3 -2
- package/src/interactive/interactive-mode.ts +14 -7
- package/src/lib/conflict-resolution.ts +818 -0
- package/src/lib/merge-strategy.ts +550 -0
- package/src/lib/safety-mechanisms.ts +451 -0
- package/src/lib/state-detection.ts +1030 -0
- package/src/nlp/command-mapper.ts +8 -3
- package/src/nlp/command-parser.ts +5 -2
- package/src/nlp/intent-parser.ts +23 -9
- package/src/plugins/plugin-manager.ts +50 -24
- package/src/tests/computer-setup-integration.test.ts +46 -15
- package/src/types/index.ts +1 -1
- package/src/types/modules.d.ts +425 -1
- package/src/utils/backup-rollback-manager.ts +19 -14
- package/src/utils/claude-config-installer.ts +119 -28
- package/src/utils/config-manager.ts +9 -6
- package/src/utils/error-handler.ts +3 -1
- package/src/utils/logger.ts +35 -12
- package/templates/batch/ci-cd.yaml +7 -7
- package/test-suites/api/health.spec.ts +20 -23
- package/test-suites/helpers/test-config.ts +14 -13
- package/test-suites/ui/accessibility.spec.ts +27 -22
- package/test-suites/ui/smoke.spec.ts +26 -21
- package/src/commands/computer-setup-commands.ts +0 -869
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
* Handles installation of CLAUDE.md, hooks, conventions, and agent templates
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { existsSync } from 'fs';
|
|
6
7
|
import * as fs from 'fs/promises';
|
|
7
8
|
import * as path from 'path';
|
|
8
|
-
|
|
9
|
+
|
|
9
10
|
import chalk from 'chalk';
|
|
10
11
|
import ora from 'ora';
|
|
11
|
-
|
|
12
|
+
|
|
12
13
|
import { BackupRollbackManager } from './backup-rollback-manager';
|
|
14
|
+
import { logger } from './logger';
|
|
13
15
|
|
|
14
16
|
export interface ClaudeConfigOptions {
|
|
15
17
|
claudeDir?: string;
|
|
@@ -55,7 +57,12 @@ export class ClaudeConfigInstaller {
|
|
|
55
57
|
* Install all Claude Code configurations
|
|
56
58
|
*/
|
|
57
59
|
async install(options: ClaudeConfigOptions = {}): Promise<InstallResult> {
|
|
58
|
-
const {
|
|
60
|
+
const {
|
|
61
|
+
dryRun = false,
|
|
62
|
+
skipBackup = false,
|
|
63
|
+
overwrite = false,
|
|
64
|
+
verbose = false,
|
|
65
|
+
} = options;
|
|
59
66
|
|
|
60
67
|
const result: InstallResult = {
|
|
61
68
|
success: false,
|
|
@@ -71,7 +78,9 @@ export class ClaudeConfigInstaller {
|
|
|
71
78
|
if (!skipBackup && !dryRun) {
|
|
72
79
|
const existingFiles = await this.getExistingConfigFiles();
|
|
73
80
|
if (existingFiles.length > 0) {
|
|
74
|
-
const spinner = ora(
|
|
81
|
+
const spinner = ora(
|
|
82
|
+
'Creating backup of existing configurations...'
|
|
83
|
+
).start();
|
|
75
84
|
const backup = await this.backupManager.createBackup(
|
|
76
85
|
existingFiles,
|
|
77
86
|
'Pre-installation backup'
|
|
@@ -94,10 +103,18 @@ export class ClaudeConfigInstaller {
|
|
|
94
103
|
await this.installAgentTemplates(result, { dryRun, overwrite, verbose });
|
|
95
104
|
|
|
96
105
|
// Install git-worktree workflows
|
|
97
|
-
await this.installGitWorktreeWorkflows(result, {
|
|
106
|
+
await this.installGitWorktreeWorkflows(result, {
|
|
107
|
+
dryRun,
|
|
108
|
+
overwrite,
|
|
109
|
+
verbose,
|
|
110
|
+
});
|
|
98
111
|
|
|
99
112
|
// Install validation scripts
|
|
100
|
-
await this.installValidationScripts(result, {
|
|
113
|
+
await this.installValidationScripts(result, {
|
|
114
|
+
dryRun,
|
|
115
|
+
overwrite,
|
|
116
|
+
verbose,
|
|
117
|
+
});
|
|
101
118
|
|
|
102
119
|
result.success = result.errors.length === 0;
|
|
103
120
|
|
|
@@ -109,7 +126,7 @@ export class ClaudeConfigInstaller {
|
|
|
109
126
|
logger.error('Installation failed', error);
|
|
110
127
|
result.errors.push({
|
|
111
128
|
file: 'general',
|
|
112
|
-
error: error instanceof Error ? error.message : String(error)
|
|
129
|
+
error: error instanceof Error ? error.message : String(error),
|
|
113
130
|
});
|
|
114
131
|
return result;
|
|
115
132
|
}
|
|
@@ -157,7 +174,7 @@ export class ClaudeConfigInstaller {
|
|
|
157
174
|
spinner.fail('Failed to install CLAUDE.md');
|
|
158
175
|
result.errors.push({
|
|
159
176
|
file: 'CLAUDE.md',
|
|
160
|
-
error: error instanceof Error ? error.message : String(error)
|
|
177
|
+
error: error instanceof Error ? error.message : String(error),
|
|
161
178
|
});
|
|
162
179
|
logger.error('CLAUDE.md installation failed', error);
|
|
163
180
|
}
|
|
@@ -202,7 +219,7 @@ export class ClaudeConfigInstaller {
|
|
|
202
219
|
spinner.fail('Failed to install hooks');
|
|
203
220
|
result.errors.push({
|
|
204
221
|
file: 'hooks',
|
|
205
|
-
error: error instanceof Error ? error.message : String(error)
|
|
222
|
+
error: error instanceof Error ? error.message : String(error),
|
|
206
223
|
});
|
|
207
224
|
logger.error('Hooks installation failed', error);
|
|
208
225
|
}
|
|
@@ -242,7 +259,7 @@ export class ClaudeConfigInstaller {
|
|
|
242
259
|
spinner.fail('Failed to install conventions');
|
|
243
260
|
result.errors.push({
|
|
244
261
|
file: 'conventions.json',
|
|
245
|
-
error: error instanceof Error ? error.message : String(error)
|
|
262
|
+
error: error instanceof Error ? error.message : String(error),
|
|
246
263
|
});
|
|
247
264
|
logger.error('Conventions installation failed', error);
|
|
248
265
|
}
|
|
@@ -264,34 +281,90 @@ export class ClaudeConfigInstaller {
|
|
|
264
281
|
const templates = this.generateAgentTemplates();
|
|
265
282
|
|
|
266
283
|
for (const [agentName, agentConfig] of Object.entries(templates)) {
|
|
267
|
-
|
|
284
|
+
// Generate .md files with proper YAML frontmatter (not .json)
|
|
285
|
+
const agentPath = path.join(agentsDir, `${agentName}.md`);
|
|
268
286
|
|
|
269
287
|
if (existsSync(agentPath) && !options.overwrite) {
|
|
270
|
-
result.skipped.push(`agents/${agentName}.
|
|
288
|
+
result.skipped.push(`agents/${agentName}.md`);
|
|
271
289
|
continue;
|
|
272
290
|
}
|
|
273
291
|
|
|
274
292
|
if (options.dryRun) {
|
|
275
|
-
result.installed.push(`agents/${agentName}.
|
|
293
|
+
result.installed.push(`agents/${agentName}.md (dry-run)`);
|
|
276
294
|
continue;
|
|
277
295
|
}
|
|
278
296
|
|
|
279
|
-
|
|
280
|
-
|
|
297
|
+
// Generate markdown with YAML frontmatter
|
|
298
|
+
const mdContent = this.generateAgentMarkdownContent(
|
|
299
|
+
agentName,
|
|
300
|
+
agentConfig
|
|
301
|
+
);
|
|
302
|
+
await fs.writeFile(agentPath, mdContent);
|
|
303
|
+
result.installed.push(`agents/${agentName}.md`);
|
|
281
304
|
}
|
|
282
305
|
|
|
283
|
-
spinner.succeed(
|
|
284
|
-
|
|
306
|
+
spinner.succeed(
|
|
307
|
+
`Agent templates installed (${Object.keys(templates).length} templates)`
|
|
308
|
+
);
|
|
309
|
+
logger.info('Agent templates installed', {
|
|
310
|
+
count: Object.keys(templates).length,
|
|
311
|
+
});
|
|
285
312
|
} catch (error) {
|
|
286
313
|
spinner.fail('Failed to install agent templates');
|
|
287
314
|
result.errors.push({
|
|
288
315
|
file: 'agent-templates',
|
|
289
|
-
error: error instanceof Error ? error.message : String(error)
|
|
316
|
+
error: error instanceof Error ? error.message : String(error),
|
|
290
317
|
});
|
|
291
318
|
logger.error('Agent templates installation failed', error);
|
|
292
319
|
}
|
|
293
320
|
}
|
|
294
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Generate markdown content with proper YAML frontmatter for agent files
|
|
324
|
+
*/
|
|
325
|
+
private generateAgentMarkdownContent(
|
|
326
|
+
agentName: string,
|
|
327
|
+
config: {
|
|
328
|
+
name: string;
|
|
329
|
+
role: string;
|
|
330
|
+
responsibilities: string[];
|
|
331
|
+
tools: string[];
|
|
332
|
+
patterns: string[];
|
|
333
|
+
}
|
|
334
|
+
): string {
|
|
335
|
+
const firstResponsibility =
|
|
336
|
+
config.responsibilities[0] ?? 'software development';
|
|
337
|
+
const description = `${config.name} specialist for ${firstResponsibility.toLowerCase()}.`;
|
|
338
|
+
const toolsList = 'Read, Write, Edit, Bash, Glob, Grep';
|
|
339
|
+
const useFor =
|
|
340
|
+
config.responsibilities.slice(0, 2).join(', ').toLowerCase() ||
|
|
341
|
+
'development tasks';
|
|
342
|
+
|
|
343
|
+
return `---
|
|
344
|
+
name: ${agentName}
|
|
345
|
+
description: >
|
|
346
|
+
${description}
|
|
347
|
+
Use for ${useFor}.
|
|
348
|
+
tools: ${toolsList}
|
|
349
|
+
model: sonnet
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
# ${config.name}
|
|
353
|
+
|
|
354
|
+
## Role
|
|
355
|
+
${config.role}
|
|
356
|
+
|
|
357
|
+
## Responsibilities
|
|
358
|
+
${config.responsibilities.map(r => `- ${r}`).join('\n')}
|
|
359
|
+
|
|
360
|
+
## Tools & Technologies
|
|
361
|
+
${config.tools.map(t => `- ${t}`).join('\n')}
|
|
362
|
+
|
|
363
|
+
## Patterns
|
|
364
|
+
${config.patterns.map(p => `- ${p}`).join('\n')}
|
|
365
|
+
`;
|
|
366
|
+
}
|
|
367
|
+
|
|
295
368
|
/**
|
|
296
369
|
* Install git-worktree workflows
|
|
297
370
|
*/
|
|
@@ -320,17 +393,24 @@ export class ClaudeConfigInstaller {
|
|
|
320
393
|
continue;
|
|
321
394
|
}
|
|
322
395
|
|
|
323
|
-
await fs.writeFile(
|
|
396
|
+
await fs.writeFile(
|
|
397
|
+
workflowPath,
|
|
398
|
+
JSON.stringify(workflowContent, null, 2)
|
|
399
|
+
);
|
|
324
400
|
result.installed.push(`workflows/${workflowName}.json`);
|
|
325
401
|
}
|
|
326
402
|
|
|
327
|
-
spinner.succeed(
|
|
328
|
-
|
|
403
|
+
spinner.succeed(
|
|
404
|
+
`Git-worktree workflows installed (${Object.keys(workflows).length} workflows)`
|
|
405
|
+
);
|
|
406
|
+
logger.info('Git-worktree workflows installed', {
|
|
407
|
+
count: Object.keys(workflows).length,
|
|
408
|
+
});
|
|
329
409
|
} catch (error) {
|
|
330
410
|
spinner.fail('Failed to install git-worktree workflows');
|
|
331
411
|
result.errors.push({
|
|
332
412
|
file: 'git-worktree-workflows',
|
|
333
|
-
error: error instanceof Error ? error.message : String(error)
|
|
413
|
+
error: error instanceof Error ? error.message : String(error),
|
|
334
414
|
});
|
|
335
415
|
logger.error('Git-worktree workflows installation failed', error);
|
|
336
416
|
}
|
|
@@ -369,13 +449,17 @@ export class ClaudeConfigInstaller {
|
|
|
369
449
|
result.installed.push(`scripts/${scriptName}`);
|
|
370
450
|
}
|
|
371
451
|
|
|
372
|
-
spinner.succeed(
|
|
373
|
-
|
|
452
|
+
spinner.succeed(
|
|
453
|
+
`Validation scripts installed (${Object.keys(scripts).length} scripts)`
|
|
454
|
+
);
|
|
455
|
+
logger.info('Validation scripts installed', {
|
|
456
|
+
count: Object.keys(scripts).length,
|
|
457
|
+
});
|
|
374
458
|
} catch (error) {
|
|
375
459
|
spinner.fail('Failed to install validation scripts');
|
|
376
460
|
result.errors.push({
|
|
377
461
|
file: 'validation-scripts',
|
|
378
|
-
error: error instanceof Error ? error.message : String(error)
|
|
462
|
+
error: error instanceof Error ? error.message : String(error),
|
|
379
463
|
});
|
|
380
464
|
logger.error('Validation scripts installation failed', error);
|
|
381
465
|
}
|
|
@@ -571,7 +655,8 @@ echo "✅ Post-checkout completed"
|
|
|
571
655
|
},
|
|
572
656
|
'bug-fix': {
|
|
573
657
|
name: 'Bug Fix Workflow',
|
|
574
|
-
description:
|
|
658
|
+
description:
|
|
659
|
+
'Workflow for fixing bugs without affecting main development',
|
|
575
660
|
steps: [
|
|
576
661
|
{
|
|
577
662
|
name: 'Create worktree',
|
|
@@ -719,13 +804,19 @@ echo "✅ Configuration check complete"
|
|
|
719
804
|
console.log(chalk.green('✅ Installation completed successfully!\n'));
|
|
720
805
|
console.log(chalk.cyan('Next steps:'));
|
|
721
806
|
console.log(chalk.white(' 1. Review installed configurations'));
|
|
722
|
-
console.log(
|
|
807
|
+
console.log(
|
|
808
|
+
chalk.white(' 2. Run validation: ~/.claude/scripts/validate-setup.sh')
|
|
809
|
+
);
|
|
723
810
|
console.log(chalk.white(' 3. Customize agent templates as needed'));
|
|
724
811
|
} else {
|
|
725
812
|
console.log(chalk.red('❌ Installation completed with errors\n'));
|
|
726
813
|
if (result.backupId) {
|
|
727
814
|
console.log(chalk.cyan('To rollback:'));
|
|
728
|
-
console.log(
|
|
815
|
+
console.log(
|
|
816
|
+
chalk.white(
|
|
817
|
+
` wundr computer-setup rollback --backup ${result.backupId}`
|
|
818
|
+
)
|
|
819
|
+
);
|
|
729
820
|
}
|
|
730
821
|
}
|
|
731
822
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
1
|
import os from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import fs from 'fs-extra';
|
|
4
5
|
import { z } from 'zod';
|
|
5
|
-
|
|
6
|
-
import { logger } from './logger';
|
|
6
|
+
|
|
7
7
|
import { errorHandler } from './error-handler';
|
|
8
|
+
import { logger } from './logger';
|
|
9
|
+
|
|
10
|
+
import type { WundrConfig } from '../types';
|
|
8
11
|
|
|
9
12
|
// Zod schema for configuration validation
|
|
10
13
|
const WundrConfigSchema = z.object({
|
|
@@ -213,7 +216,7 @@ export class ConfigManager {
|
|
|
213
216
|
throw new Error(`Invalid path: empty key at position ${i}`);
|
|
214
217
|
}
|
|
215
218
|
if (!current || typeof current !== 'object') {
|
|
216
|
-
throw new Error(
|
|
219
|
+
throw new Error('Invalid path: cannot set property on non-object');
|
|
217
220
|
}
|
|
218
221
|
if (!current[key] || typeof current[key] !== 'object') {
|
|
219
222
|
current[key] = {};
|
|
@@ -226,7 +229,7 @@ export class ConfigManager {
|
|
|
226
229
|
throw new Error('Invalid path: empty final key');
|
|
227
230
|
}
|
|
228
231
|
if (!finalKey || !current || typeof current !== 'object') {
|
|
229
|
-
throw new Error(
|
|
232
|
+
throw new Error('Invalid path: cannot set property');
|
|
230
233
|
}
|
|
231
234
|
current[finalKey] = value;
|
|
232
235
|
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import type { Logger } from '../types';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Enhanced logger with multiple levels and colored output
|
|
@@ -17,7 +18,9 @@ class WundrLogger implements Logger {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
private shouldLog(level: string): boolean {
|
|
20
|
-
if (this.silent)
|
|
21
|
+
if (this.silent) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
21
24
|
|
|
22
25
|
const levels = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
23
26
|
return levels[level as keyof typeof levels] >= levels[this.level];
|
|
@@ -36,54 +39,74 @@ class WundrLogger implements Logger {
|
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
debug(message: string, ...args: any[]): void {
|
|
39
|
-
if (!this.shouldLog('debug'))
|
|
42
|
+
if (!this.shouldLog('debug')) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
40
45
|
console.log(chalk.gray(this.formatMessage('debug', message, ...args)));
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
info(message: string, ...args: any[]): void {
|
|
44
|
-
if (!this.shouldLog('info'))
|
|
49
|
+
if (!this.shouldLog('info')) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
45
52
|
console.log(chalk.blue(this.formatMessage('info', message, ...args)));
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
warn(message: string, ...args: any[]): void {
|
|
49
|
-
if (!this.shouldLog('warn'))
|
|
56
|
+
if (!this.shouldLog('warn')) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
50
59
|
console.warn(chalk.yellow(this.formatMessage('warn', message, ...args)));
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
error(message: string, ...args: any[]): void {
|
|
54
|
-
if (!this.shouldLog('error'))
|
|
63
|
+
if (!this.shouldLog('error')) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
55
66
|
console.error(chalk.red(this.formatMessage('error', message, ...args)));
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
success(message: string, ...args: any[]): void {
|
|
59
|
-
if (!this.shouldLog('info'))
|
|
70
|
+
if (!this.shouldLog('info')) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
60
73
|
console.log(chalk.green(this.formatMessage('success', message, ...args)));
|
|
61
74
|
}
|
|
62
75
|
|
|
63
76
|
// Utility methods for structured logging
|
|
64
77
|
table(data: any[]): void {
|
|
65
|
-
if (this.silent)
|
|
78
|
+
if (this.silent) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
66
81
|
console.table(data);
|
|
67
82
|
}
|
|
68
83
|
|
|
69
84
|
json(data: any): void {
|
|
70
|
-
if (this.silent)
|
|
85
|
+
if (this.silent) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
71
88
|
console.log(JSON.stringify(data, null, 2));
|
|
72
89
|
}
|
|
73
90
|
|
|
74
91
|
group(label: string): void {
|
|
75
|
-
if (this.silent)
|
|
92
|
+
if (this.silent) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
76
95
|
console.group(chalk.cyan(label));
|
|
77
96
|
}
|
|
78
97
|
|
|
79
98
|
groupEnd(): void {
|
|
80
|
-
if (this.silent)
|
|
99
|
+
if (this.silent) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
81
102
|
console.groupEnd();
|
|
82
103
|
}
|
|
83
104
|
|
|
84
105
|
// Progress logging
|
|
85
106
|
progress(current: number, total: number, message?: string): void {
|
|
86
|
-
if (this.silent)
|
|
107
|
+
if (this.silent) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
87
110
|
const percentage = Math.round((current / total) * 100);
|
|
88
111
|
const bar = '█'.repeat(Math.round(percentage / 2));
|
|
89
112
|
const empty = '░'.repeat(50 - Math.round(percentage / 2));
|
|
@@ -7,7 +7,7 @@ description: Continuous Integration and Deployment pipeline
|
|
|
7
7
|
# Global settings
|
|
8
8
|
parallel: false
|
|
9
9
|
continueOnError: false
|
|
10
|
-
timeout: 1800000
|
|
10
|
+
timeout: 1800000 # 30 minutes
|
|
11
11
|
|
|
12
12
|
# Environment variables
|
|
13
13
|
variables:
|
|
@@ -23,7 +23,7 @@ commands:
|
|
|
23
23
|
# Install dependencies
|
|
24
24
|
- command: "npm ci"
|
|
25
25
|
retry: 2
|
|
26
|
-
timeout: 300000
|
|
26
|
+
timeout: 300000 # 5 minutes
|
|
27
27
|
|
|
28
28
|
# Code quality checks
|
|
29
29
|
- command: "npm run lint"
|
|
@@ -34,11 +34,11 @@ commands:
|
|
|
34
34
|
|
|
35
35
|
# Testing
|
|
36
36
|
- command: "npm run test"
|
|
37
|
-
timeout: 600000
|
|
37
|
+
timeout: 600000 # 10 minutes
|
|
38
38
|
|
|
39
39
|
- command: "npm run test:e2e"
|
|
40
40
|
condition: "e2e-tests-exist"
|
|
41
|
-
timeout: 900000
|
|
41
|
+
timeout: 900000 # 15 minutes
|
|
42
42
|
|
|
43
43
|
# Security audit
|
|
44
44
|
- command: "npm audit --audit-level high"
|
|
@@ -46,7 +46,7 @@ commands:
|
|
|
46
46
|
|
|
47
47
|
# Build
|
|
48
48
|
- command: "npm run build"
|
|
49
|
-
timeout: 300000
|
|
49
|
+
timeout: 300000 # 5 minutes
|
|
50
50
|
|
|
51
51
|
# Package
|
|
52
52
|
- command: "npm pack"
|
|
@@ -55,8 +55,8 @@ commands:
|
|
|
55
55
|
# Deploy (conditional)
|
|
56
56
|
- command: "npm run deploy"
|
|
57
57
|
condition: "production"
|
|
58
|
-
timeout: 600000
|
|
58
|
+
timeout: 600000 # 10 minutes
|
|
59
59
|
|
|
60
60
|
# Cleanup
|
|
61
61
|
- command: "echo 'CI/CD pipeline completed'"
|
|
62
|
-
condition: "always"
|
|
62
|
+
condition: "always"
|
|
@@ -15,11 +15,11 @@ test.describe('API Health Checks', () => {
|
|
|
15
15
|
'/status',
|
|
16
16
|
'/api/status',
|
|
17
17
|
'/_health',
|
|
18
|
-
'/ping'
|
|
18
|
+
'/ping',
|
|
19
19
|
];
|
|
20
20
|
|
|
21
21
|
let healthyEndpoint = null;
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
for (const endpoint of healthEndpoints) {
|
|
24
24
|
try {
|
|
25
25
|
const response = await request.get(`${baseURL}${endpoint}`);
|
|
@@ -38,23 +38,18 @@ test.describe('API Health Checks', () => {
|
|
|
38
38
|
|
|
39
39
|
test('API returns proper content types', async ({ request, baseURL }) => {
|
|
40
40
|
// Try to find an API endpoint
|
|
41
|
-
const apiEndpoints = [
|
|
42
|
-
'/api',
|
|
43
|
-
'/api/v1',
|
|
44
|
-
'/api/v2',
|
|
45
|
-
'/graphql'
|
|
46
|
-
];
|
|
41
|
+
const apiEndpoints = ['/api', '/api/v1', '/api/v2', '/graphql'];
|
|
47
42
|
|
|
48
43
|
for (const endpoint of apiEndpoints) {
|
|
49
44
|
try {
|
|
50
45
|
const response = await request.get(`${baseURL}${endpoint}`);
|
|
51
46
|
if (response.ok() || response.status() === 404) {
|
|
52
47
|
const contentType = response.headers()['content-type'];
|
|
53
|
-
|
|
48
|
+
|
|
54
49
|
// Should return JSON or HTML
|
|
55
50
|
expect(
|
|
56
51
|
contentType?.includes('application/json') ||
|
|
57
|
-
|
|
52
|
+
contentType?.includes('text/html')
|
|
58
53
|
).toBeTruthy();
|
|
59
54
|
}
|
|
60
55
|
} catch {
|
|
@@ -65,11 +60,13 @@ test.describe('API Health Checks', () => {
|
|
|
65
60
|
|
|
66
61
|
test('API handles errors gracefully', async ({ request, baseURL }) => {
|
|
67
62
|
// Test 404 handling
|
|
68
|
-
const response = await request.get(
|
|
69
|
-
|
|
63
|
+
const response = await request.get(
|
|
64
|
+
`${baseURL}/api/nonexistent-endpoint-${Date.now()}`
|
|
65
|
+
);
|
|
66
|
+
|
|
70
67
|
// Should return appropriate status code
|
|
71
68
|
expect([404, 400, 401, 403]).toContain(response.status());
|
|
72
|
-
|
|
69
|
+
|
|
73
70
|
// Should not expose sensitive information
|
|
74
71
|
const body = await response.text();
|
|
75
72
|
expect(body).not.toContain('stack');
|
|
@@ -78,12 +75,12 @@ test.describe('API Health Checks', () => {
|
|
|
78
75
|
|
|
79
76
|
test('API responds within acceptable time', async ({ request, baseURL }) => {
|
|
80
77
|
const startTime = Date.now();
|
|
81
|
-
|
|
78
|
+
|
|
82
79
|
// Make a simple request
|
|
83
80
|
await request.get(`${baseURL}/`);
|
|
84
|
-
|
|
81
|
+
|
|
85
82
|
const responseTime = Date.now() - startTime;
|
|
86
|
-
|
|
83
|
+
|
|
87
84
|
// Should respond within 5 seconds
|
|
88
85
|
expect(responseTime).toBeLessThan(5000);
|
|
89
86
|
});
|
|
@@ -92,18 +89,18 @@ test.describe('API Health Checks', () => {
|
|
|
92
89
|
try {
|
|
93
90
|
const response = await request.get(`${baseURL}/api`, {
|
|
94
91
|
headers: {
|
|
95
|
-
|
|
96
|
-
}
|
|
92
|
+
Origin: 'https://example.com',
|
|
93
|
+
},
|
|
97
94
|
});
|
|
98
95
|
|
|
99
96
|
const headers = response.headers();
|
|
100
|
-
|
|
97
|
+
|
|
101
98
|
// Check for CORS headers if API exists
|
|
102
99
|
if (response.status() !== 404) {
|
|
103
|
-
const hasCorsHeaders =
|
|
100
|
+
const hasCorsHeaders =
|
|
104
101
|
headers['access-control-allow-origin'] !== undefined ||
|
|
105
102
|
headers['access-control-allow-methods'] !== undefined;
|
|
106
|
-
|
|
103
|
+
|
|
107
104
|
// Log CORS configuration
|
|
108
105
|
console.log('CORS configured:', hasCorsHeaders);
|
|
109
106
|
}
|
|
@@ -119,7 +116,7 @@ test.describe('API Health Checks', () => {
|
|
|
119
116
|
for (const method of methods) {
|
|
120
117
|
try {
|
|
121
118
|
const response = await request.fetch(`${baseURL}/api`, {
|
|
122
|
-
method
|
|
119
|
+
method,
|
|
123
120
|
});
|
|
124
121
|
results[method] = response.status();
|
|
125
122
|
} catch {
|
|
@@ -131,4 +128,4 @@ test.describe('API Health Checks', () => {
|
|
|
131
128
|
expect(results['GET']).toBeGreaterThan(0);
|
|
132
129
|
expect(results['OPTIONS']).toBeGreaterThan(0);
|
|
133
130
|
});
|
|
134
|
-
});
|
|
131
|
+
});
|
|
@@ -12,7 +12,7 @@ export interface TestConfig {
|
|
|
12
12
|
screenshot: 'off' | 'on' | 'only-on-failure';
|
|
13
13
|
video: 'off' | 'on' | 'retain-on-failure';
|
|
14
14
|
trace: 'off' | 'on' | 'on-first-retry';
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
// Custom selectors for specific apps
|
|
17
17
|
selectors?: {
|
|
18
18
|
navigation?: string;
|
|
@@ -21,14 +21,14 @@ export interface TestConfig {
|
|
|
21
21
|
searchInput?: string;
|
|
22
22
|
loginButton?: string;
|
|
23
23
|
};
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
// API configuration
|
|
26
26
|
api?: {
|
|
27
27
|
baseURL?: string;
|
|
28
28
|
headers?: Record<string, string>;
|
|
29
29
|
timeout?: number;
|
|
30
30
|
};
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
// Test data
|
|
33
33
|
testData?: {
|
|
34
34
|
validUser?: {
|
|
@@ -47,18 +47,19 @@ export const defaultConfig: TestConfig = {
|
|
|
47
47
|
screenshot: 'only-on-failure',
|
|
48
48
|
video: 'retain-on-failure',
|
|
49
49
|
trace: 'on-first-retry',
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
selectors: {
|
|
52
52
|
navigation: 'nav, [role="navigation"], header',
|
|
53
53
|
mainContent: 'main, [role="main"], #content',
|
|
54
54
|
footer: 'footer, [role="contentinfo"]',
|
|
55
55
|
searchInput: 'input[type="search"], input[placeholder*="search" i]',
|
|
56
|
-
loginButton:
|
|
56
|
+
loginButton:
|
|
57
|
+
'button[type="submit"], button:has-text("Login"), button:has-text("Sign in")',
|
|
57
58
|
},
|
|
58
|
-
|
|
59
|
+
|
|
59
60
|
api: {
|
|
60
|
-
timeout: 10000
|
|
61
|
-
}
|
|
61
|
+
timeout: 10000,
|
|
62
|
+
},
|
|
62
63
|
};
|
|
63
64
|
|
|
64
65
|
/**
|
|
@@ -70,15 +71,15 @@ export function loadConfig(customConfig?: Partial<TestConfig>): TestConfig {
|
|
|
70
71
|
...customConfig,
|
|
71
72
|
selectors: {
|
|
72
73
|
...defaultConfig.selectors,
|
|
73
|
-
...customConfig?.selectors
|
|
74
|
+
...customConfig?.selectors,
|
|
74
75
|
},
|
|
75
76
|
api: {
|
|
76
77
|
...defaultConfig.api,
|
|
77
|
-
...customConfig?.api
|
|
78
|
+
...customConfig?.api,
|
|
78
79
|
},
|
|
79
80
|
testData: {
|
|
80
81
|
...defaultConfig.testData,
|
|
81
|
-
...customConfig?.testData
|
|
82
|
-
}
|
|
82
|
+
...customConfig?.testData,
|
|
83
|
+
},
|
|
83
84
|
};
|
|
84
|
-
}
|
|
85
|
+
}
|