@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.
Files changed (69) hide show
  1. package/bin/wundr.js +13 -5
  2. package/package.json +30 -9
  3. package/src/ai/ai-service.ts +6 -4
  4. package/src/ai/claude-client.ts +6 -2
  5. package/src/ai/conversation-manager.ts +12 -5
  6. package/src/cli.ts +42 -13
  7. package/src/commands/ai.ts +340 -64
  8. package/src/commands/alignment.ts +1212 -0
  9. package/src/commands/analyze-optimized.ts +371 -33
  10. package/src/commands/analyze.ts +8 -6
  11. package/src/commands/batch.ts +166 -26
  12. package/src/commands/chat.ts +20 -10
  13. package/src/commands/claude-init.ts +31 -27
  14. package/src/commands/claude-setup.ts +761 -81
  15. package/src/commands/computer-setup.ts +524 -12
  16. package/src/commands/create-command.ts +3 -3
  17. package/src/commands/create.ts +9 -6
  18. package/src/commands/dashboard.ts +11 -6
  19. package/src/commands/govern.ts +11 -6
  20. package/src/commands/governance.ts +1005 -0
  21. package/src/commands/guardian.ts +887 -0
  22. package/src/commands/init.ts +104 -11
  23. package/src/commands/orchestrator.ts +789 -0
  24. package/src/commands/performance-optimizer.ts +15 -10
  25. package/src/commands/plugins.ts +8 -5
  26. package/src/commands/project-update.ts +1156 -0
  27. package/src/commands/rag.ts +1011 -0
  28. package/src/commands/session.ts +631 -0
  29. package/src/commands/setup.ts +42 -344
  30. package/src/commands/test-init.ts +3 -2
  31. package/src/commands/test.ts +3 -2
  32. package/src/commands/watch.ts +21 -11
  33. package/src/commands/worktree.ts +1057 -0
  34. package/src/context/context-manager.ts +5 -2
  35. package/src/context/session-manager.ts +18 -7
  36. package/src/framework/command-interface.ts +520 -0
  37. package/src/framework/command-registry.ts +942 -0
  38. package/src/framework/completion-exporter.ts +383 -0
  39. package/src/framework/debug-logger.ts +519 -0
  40. package/src/framework/error-handler.ts +867 -0
  41. package/src/framework/help-generator.ts +540 -0
  42. package/src/framework/index.ts +169 -0
  43. package/src/framework/interactive-repl.ts +703 -0
  44. package/src/framework/output-formatter.ts +834 -0
  45. package/src/framework/progress-manager.ts +539 -0
  46. package/src/index.ts +3 -2
  47. package/src/interactive/interactive-mode.ts +14 -7
  48. package/src/lib/conflict-resolution.ts +818 -0
  49. package/src/lib/merge-strategy.ts +550 -0
  50. package/src/lib/safety-mechanisms.ts +451 -0
  51. package/src/lib/state-detection.ts +1030 -0
  52. package/src/nlp/command-mapper.ts +8 -3
  53. package/src/nlp/command-parser.ts +5 -2
  54. package/src/nlp/intent-parser.ts +23 -9
  55. package/src/plugins/plugin-manager.ts +50 -24
  56. package/src/tests/computer-setup-integration.test.ts +46 -15
  57. package/src/types/index.ts +1 -1
  58. package/src/types/modules.d.ts +425 -1
  59. package/src/utils/backup-rollback-manager.ts +19 -14
  60. package/src/utils/claude-config-installer.ts +119 -28
  61. package/src/utils/config-manager.ts +9 -6
  62. package/src/utils/error-handler.ts +3 -1
  63. package/src/utils/logger.ts +35 -12
  64. package/templates/batch/ci-cd.yaml +7 -7
  65. package/test-suites/api/health.spec.ts +20 -23
  66. package/test-suites/helpers/test-config.ts +14 -13
  67. package/test-suites/ui/accessibility.spec.ts +27 -22
  68. package/test-suites/ui/smoke.spec.ts +26 -21
  69. package/src/commands/computer-setup-commands.ts +0 -869
@@ -1,35 +1,29 @@
1
1
  /**
2
2
  * Simple Setup Commands - Main setup entry points
3
- * Provides the primary wundr setup commands that integrate with computer-setup
3
+ *
4
+ * `wundr setup` (and its sub-commands) delegate to the SAME code path as
5
+ * `wundr computer-setup` (runComputerSetup -> ComputerSetupManager), so there is
6
+ * a single orchestrator engine across the whole CLI. The old RealSetupOrchestrator
7
+ * has been retired.
4
8
  */
5
9
 
6
- import { Command } from 'commander';
7
10
  import chalk from 'chalk';
8
- import ora from 'ora';
9
11
  import inquirer from 'inquirer';
10
- import { ConfigManager } from '../utils/config-manager';
11
- import { PluginManager } from '../plugins/plugin-manager';
12
- import { logger } from '../utils/logger';
13
- // Note: Using relative path import due to workspace resolution issues in this monorepo setup
14
- // The computer-setup package must be built first before building this CLI package
15
- import {
16
- SetupPlatform,
17
- SetupProgress,
18
- SetupResult,
19
- RealSetupOrchestrator,
20
- } from '../../../computer-setup/dist';
12
+ import ora from 'ora';
21
13
 
22
- export class SetupCommands {
23
- private orchestrator: RealSetupOrchestrator;
24
- private platform: SetupPlatform;
14
+ import { runComputerSetup } from './computer-setup';
15
+
16
+ import type { PluginManager } from '../plugins/plugin-manager';
17
+ import type { ConfigManager } from '../utils/config-manager';
25
18
 
19
+ import type { Command } from 'commander';
20
+
21
+ export class SetupCommands {
26
22
  constructor(
27
23
  private program: Command,
28
24
  private configManager: ConfigManager,
29
25
  private pluginManager: PluginManager
30
26
  ) {
31
- this.platform = this.detectPlatform();
32
- this.orchestrator = new RealSetupOrchestrator(this.platform);
33
27
  this.registerCommands();
34
28
  }
35
29
 
@@ -48,6 +42,7 @@ export class SetupCommands {
48
42
  'Show what would be installed without making changes'
49
43
  )
50
44
  .option('--interactive', 'Run in interactive mode')
45
+ .option('--no-remote-access', 'Skip remote-access provisioning')
51
46
  .action(async options => {
52
47
  await this.runSetup(options);
53
48
  });
@@ -57,33 +52,14 @@ export class SetupCommands {
57
52
  .command('setup:profile')
58
53
  .description('Set up using a specific developer profile');
59
54
 
60
- setupProfile
61
- .command('frontend')
62
- .description('Set up frontend development environment')
63
- .action(async () => {
64
- await this.runSetup({ profile: 'frontend' });
65
- });
66
-
67
- setupProfile
68
- .command('backend')
69
- .description('Set up backend development environment')
70
- .action(async () => {
71
- await this.runSetup({ profile: 'backend' });
72
- });
73
-
74
- setupProfile
75
- .command('fullstack')
76
- .description('Set up full-stack development environment')
77
- .action(async () => {
78
- await this.runSetup({ profile: 'fullstack' });
79
- });
80
-
81
- setupProfile
82
- .command('devops')
83
- .description('Set up DevOps engineering environment')
84
- .action(async () => {
85
- await this.runSetup({ profile: 'devops' });
86
- });
55
+ for (const profile of ['frontend', 'backend', 'fullstack', 'devops']) {
56
+ setupProfile
57
+ .command(profile)
58
+ .description(`Set up ${profile} development environment`)
59
+ .action(async () => {
60
+ await this.runSetup({ profile });
61
+ });
62
+ }
87
63
 
88
64
  // Validate setup (wundr setup:validate)
89
65
  this.program
@@ -95,12 +71,14 @@ export class SetupCommands {
95
71
  await this.validateSetup(options);
96
72
  });
97
73
 
98
- // Resume setup (wundr setup:resume)
74
+ // Resume setup (wundr setup:resume) — re-runs idempotently, skipping
75
+ // already-installed tools (the setup flow is now idempotent).
99
76
  this.program
100
77
  .command('setup:resume')
101
- .description('Resume interrupted setup from saved state')
102
- .action(async () => {
103
- await this.resumeSetup();
78
+ .description('Re-run setup, skipping tools that are already installed')
79
+ .option('-p, --profile <profile>', 'Profile to resume', 'fullstack')
80
+ .action(async options => {
81
+ await this.runSetup({ ...options, skipExisting: true });
104
82
  });
105
83
 
106
84
  // Personalize setup (wundr setup:personalize)
@@ -113,144 +91,15 @@ export class SetupCommands {
113
91
  }
114
92
 
115
93
  private async runSetup(options: any): Promise<void> {
116
- console.log(chalk.cyan('\nšŸš€ Wundr Development Environment Setup'));
117
- console.log(chalk.gray('Setting up your development machine...\n'));
118
-
119
- try {
120
- // Check for resumable setup
121
- const canResume = await this.orchestrator.canResume();
122
- if (canResume && !options.dryRun) {
123
- const { resume } = await inquirer.prompt([
124
- {
125
- type: 'confirm',
126
- name: 'resume',
127
- message: 'Found incomplete setup. Resume from where you left off?',
128
- default: true,
129
- },
130
- ]);
131
-
132
- if (resume) {
133
- return await this.resumeSetup();
134
- }
135
- }
136
-
137
- // Get profile
138
- let profileName = options.profile;
139
- if (options.interactive && !profileName) {
140
- profileName = await this.selectProfile();
141
- }
142
-
143
- // Validate profile
144
- const availableProfiles = this.orchestrator.getAvailableProfiles();
145
-
146
- // Try different matching strategies
147
- let profile = availableProfiles.find(
148
- p => p.name.toLowerCase() === profileName.toLowerCase()
149
- );
150
-
151
- if (!profile) {
152
- // Try partial match
153
- profile = availableProfiles.find(
154
- p =>
155
- p.name.toLowerCase().includes(profileName.toLowerCase()) ||
156
- profileName
157
- .toLowerCase()
158
- .includes(p.name.toLowerCase().split(' ')[0])
159
- );
160
- }
161
-
162
- // Map common aliases
163
- if (!profile) {
164
- const aliases: Record<string, string> = {
165
- fullstack: 'Full Stack Developer',
166
- 'full-stack': 'Full Stack Developer',
167
- frontend: 'Frontend Developer',
168
- backend: 'Backend Developer',
169
- devops: 'DevOps Engineer',
170
- };
171
-
172
- const mappedName = aliases[profileName.toLowerCase()];
173
- if (mappedName) {
174
- profile = availableProfiles.find(p => p.name === mappedName);
175
- }
176
- }
177
-
178
- if (!profile) {
179
- console.error(chalk.red(`āŒ Unknown profile: ${profileName}`));
180
- console.log(chalk.cyan('\nšŸ“‹ Available profiles:'));
181
- availableProfiles.forEach(p =>
182
- console.log(
183
- ` • ${chalk.white(p.name)}: ${chalk.gray(p.description)}`
184
- )
185
- );
186
- return;
187
- }
188
-
189
- console.log(
190
- chalk.cyan(`\nšŸ“‹ Selected Profile: ${chalk.white(profile.name)}`)
191
- );
192
- console.log(chalk.gray(`${profile.description}`));
193
- console.log(
194
- chalk.gray(`Estimated time: ${profile.estimatedTimeMinutes} minutes\n`)
195
- );
196
-
197
- if (options.dryRun) {
198
- console.log(
199
- chalk.yellow('šŸ” DRY RUN - Showing what would be installed:\n')
200
- );
201
- console.log(chalk.cyan('Required tools:'));
202
- profile.requiredTools.forEach(tool => console.log(` āœ“ ${tool}`));
203
- if (profile.optionalTools.length > 0) {
204
- console.log(chalk.cyan('\nOptional tools:'));
205
- profile.optionalTools.forEach(tool => console.log(` • ${tool}`));
206
- }
207
- return;
208
- }
209
-
210
- // Progress tracking
211
- const progressCallback = (progress: SetupProgress) => {
212
- process.stdout.clearLine(0);
213
- process.stdout.cursorTo(0);
214
- const progressBar = this.createProgressBar(progress.percentage);
215
- process.stdout.write(
216
- `${progressBar} ${progress.percentage.toFixed(1)}% - ${progress.currentStep}`
217
- );
218
- };
219
-
220
- console.log(chalk.cyan('šŸš€ Starting setup...\n'));
221
-
222
- const result: SetupResult = await this.orchestrator.orchestrate(
223
- profileName,
224
- {
225
- dryRun: options.dryRun,
226
- skipExisting: true,
227
- parallel: false,
228
- generateReport: true,
229
- },
230
- progressCallback
231
- );
232
-
233
- console.log('\n'); // New line after progress
234
-
235
- if (result.success) {
236
- console.log(chalk.green('\nāœ… Setup completed successfully!'));
237
- console.log(
238
- chalk.gray(`Duration: ${Math.round(result.duration / 1000)}s\n`)
239
- );
240
-
241
- this.showSetupSummary(result);
242
- this.showNextSteps();
243
- } else {
244
- console.log(chalk.red('\nāŒ Setup failed!'));
245
- this.showErrors(result);
246
- console.log(chalk.cyan('\nšŸ’” Resume with: wundr setup:resume'));
247
- process.exit(1);
248
- }
249
- } catch (error) {
250
- console.error(chalk.red('\nāŒ Setup failed:'), (error as Error).message);
251
- console.log(chalk.cyan('\nšŸ’” Resume with: wundr setup:resume'));
252
- process.exit(1);
253
- }
94
+ // Single source of truth: the same handler that backs `wundr computer-setup`.
95
+ await runComputerSetup({
96
+ profile: options.profile,
97
+ mode: options.interactive ? 'interactive' : 'automated',
98
+ dryRun: Boolean(options.dryRun),
99
+ interactive: Boolean(options.interactive),
100
+ skipExisting: Boolean(options.skipExisting),
101
+ remoteAccess: options.remoteAccess,
102
+ });
254
103
  }
255
104
 
256
105
  private async validateSetup(options: any): Promise<void> {
@@ -258,7 +107,6 @@ export class SetupCommands {
258
107
 
259
108
  const spinner = ora('Running validation checks...').start();
260
109
 
261
- // Basic validation checks
262
110
  const checks = [
263
111
  { name: 'Node.js', test: () => this.checkCommand('node --version') },
264
112
  { name: 'Git', test: () => this.checkCommand('git --version') },
@@ -270,23 +118,17 @@ export class SetupCommands {
270
118
  name: string;
271
119
  status: 'pass' | 'fail';
272
120
  version?: string;
273
- error?: string;
274
121
  }> = [];
275
122
 
276
123
  for (const check of checks) {
277
124
  try {
278
- const result = await check.test();
279
125
  results.push({
280
126
  name: check.name,
281
127
  status: 'pass',
282
- version: result,
283
- });
284
- } catch (error) {
285
- results.push({
286
- name: check.name,
287
- status: 'fail',
288
- error: (error as Error).message,
128
+ version: await check.test(),
289
129
  });
130
+ } catch {
131
+ results.push({ name: check.name, status: 'fail' });
290
132
  }
291
133
  }
292
134
 
@@ -297,7 +139,7 @@ export class SetupCommands {
297
139
  const icon = result.status === 'pass' ? 'āœ…' : 'āŒ';
298
140
  const status =
299
141
  result.status === 'pass'
300
- ? chalk.green(`${result.version || 'installed'}`)
142
+ ? chalk.green(result.version || 'installed')
301
143
  : chalk.red('not found');
302
144
  console.log(`${icon} ${result.name}: ${status}`);
303
145
  });
@@ -305,7 +147,6 @@ export class SetupCommands {
305
147
  const failed = results.filter(r => r.status === 'fail');
306
148
  if (failed.length > 0) {
307
149
  console.log(chalk.yellow(`\nāš ļø ${failed.length} issues found`));
308
-
309
150
  if (options.fix) {
310
151
  console.log(chalk.cyan('\nšŸ”§ Attempting to fix issues...'));
311
152
  await this.runSetup({ profile: options.profile || 'fullstack' });
@@ -319,37 +160,6 @@ export class SetupCommands {
319
160
  }
320
161
  }
321
162
 
322
- private async resumeSetup(): Promise<void> {
323
- console.log(chalk.cyan('\nšŸ”„ Resuming setup...\n'));
324
-
325
- const progressCallback = (progress: SetupProgress) => {
326
- process.stdout.clearLine(0);
327
- process.stdout.cursorTo(0);
328
- const progressBar = this.createProgressBar(progress.percentage);
329
- process.stdout.write(
330
- `${progressBar} ${progress.percentage.toFixed(1)}% - ${progress.currentStep}`
331
- );
332
- };
333
-
334
- try {
335
- const result = await this.orchestrator.resume(progressCallback);
336
- console.log('\n');
337
-
338
- if (result.success) {
339
- console.log(chalk.green('āœ… Setup completed successfully!'));
340
- this.showSetupSummary(result);
341
- this.showNextSteps();
342
- } else {
343
- console.log(chalk.red('āŒ Resume failed!'));
344
- this.showErrors(result);
345
- process.exit(1);
346
- }
347
- } catch (error) {
348
- console.error(chalk.red('āŒ Resume failed:'), (error as Error).message);
349
- process.exit(1);
350
- }
351
- }
352
-
353
163
  private async personalizeSetup(): Promise<void> {
354
164
  console.log(chalk.cyan('\nšŸ‘¤ Personal Configuration Setup\n'));
355
165
 
@@ -367,134 +177,22 @@ export class SetupCommands {
367
177
  validate: (input: string) =>
368
178
  /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input) || 'Valid email required',
369
179
  },
370
- {
371
- type: 'list',
372
- name: 'shell',
373
- message: 'Preferred shell:',
374
- choices: ['zsh', 'bash', 'fish'],
375
- default: 'zsh',
376
- },
377
- {
378
- type: 'confirm',
379
- name: 'aliases',
380
- message: 'Install helpful shell aliases?',
381
- default: true,
382
- },
383
180
  ]);
384
181
 
385
182
  console.log(chalk.cyan('\nāš™ļø Configuring personal settings...'));
386
-
387
- // Configure Git
388
183
  try {
389
184
  await this.runCommand(`git config --global user.name "${answers.name}"`);
390
185
  await this.runCommand(
391
186
  `git config --global user.email "${answers.email}"`
392
187
  );
393
188
  console.log(chalk.green('āœ… Git configured'));
394
- } catch (error) {
189
+ } catch {
395
190
  console.log(chalk.yellow('āš ļø Could not configure Git'));
396
191
  }
397
192
 
398
193
  console.log(chalk.green('\nāœ… Personalization complete!'));
399
194
  }
400
195
 
401
- private async selectProfile(): Promise<string> {
402
- const profiles = this.orchestrator.getAvailableProfiles();
403
-
404
- const { selectedProfile } = await inquirer.prompt([
405
- {
406
- type: 'list',
407
- name: 'selectedProfile',
408
- message: 'Select your development profile:',
409
- choices: profiles.map(p => ({
410
- name: `${p.name} - ${p.description}`,
411
- value: p.name.toLowerCase().replace(/\s+/g, ''),
412
- short: p.name,
413
- })),
414
- },
415
- ]);
416
-
417
- return selectedProfile;
418
- }
419
-
420
- private createProgressBar(percentage: number): string {
421
- const width = 20;
422
- const filled = Math.round((percentage / 100) * width);
423
- const empty = width - filled;
424
- return (
425
- chalk.cyan('[') +
426
- chalk.green('='.repeat(filled)) +
427
- chalk.gray('-'.repeat(empty)) +
428
- chalk.cyan(']')
429
- );
430
- }
431
-
432
- private showSetupSummary(result: SetupResult): void {
433
- if (result.completedSteps.length > 0) {
434
- console.log(
435
- chalk.cyan(`šŸŽÆ Completed (${result.completedSteps.length}):`)
436
- );
437
- result.completedSteps
438
- .slice(0, 5)
439
- .forEach(step => console.log(` āœ… ${step.replace('install-', '')}`));
440
- if (result.completedSteps.length > 5) {
441
- console.log(` ... and ${result.completedSteps.length - 5} more`);
442
- }
443
- }
444
-
445
- if (result.skippedSteps.length > 0) {
446
- console.log(
447
- chalk.yellow(`\nā­ļø Skipped (${result.skippedSteps.length}):`)
448
- );
449
- result.skippedSteps
450
- .slice(0, 3)
451
- .forEach(step =>
452
- console.log(
453
- ` ā­ļø ${step.replace('install-', '')} (already installed)`
454
- )
455
- );
456
- }
457
- }
458
-
459
- private showErrors(result: SetupResult): void {
460
- if (result.failedSteps.length > 0) {
461
- console.log(chalk.red(`āŒ Failed (${result.failedSteps.length}):`));
462
- result.failedSteps.forEach(step =>
463
- console.log(` āŒ ${step.replace('install-', '')}`)
464
- );
465
- }
466
-
467
- if (result.errors.length > 0) {
468
- console.log(chalk.red('\nšŸ” Errors:'));
469
- result.errors
470
- .slice(0, 3)
471
- .forEach(error => console.log(` • ${error.message}`));
472
- }
473
- }
474
-
475
- private showNextSteps(): void {
476
- console.log(chalk.cyan('\nšŸ“ Next Steps:'));
477
- const steps = [
478
- 'Restart your terminal to apply changes',
479
- 'Validate setup: wundr setup:validate',
480
- 'Personalize: wundr setup:personalize',
481
- 'Start coding! šŸš€',
482
- ];
483
-
484
- steps.forEach((step, i) => {
485
- console.log(` ${i + 1}. ${step}`);
486
- });
487
- console.log();
488
- }
489
-
490
- private detectPlatform(): SetupPlatform {
491
- return {
492
- os: process.platform as 'darwin' | 'linux' | 'win32',
493
- arch: process.arch as 'x64' | 'arm64',
494
- version: process.version || 'unknown',
495
- };
496
- }
497
-
498
196
  private async checkCommand(command: string): Promise<string> {
499
197
  const { execa } = await import('execa');
500
198
  const { stdout } = await execa('sh', ['-c', command]);
@@ -1,7 +1,8 @@
1
- import { Command } from 'commander';
1
+ import path from 'path';
2
+
2
3
  import chalk from 'chalk';
4
+ import { Command } from 'commander';
3
5
  import fs from 'fs-extra';
4
- import path from 'path';
5
6
  import inquirer from 'inquirer';
6
7
 
7
8
  export class TestInitCommand {
@@ -1,7 +1,8 @@
1
- import { Command } from 'commander';
2
- import chalk from 'chalk';
3
1
  import { spawn } from 'child_process';
4
2
  import path from 'path';
3
+
4
+ import chalk from 'chalk';
5
+ import { Command } from 'commander';
5
6
  import fs from 'fs-extra';
6
7
  import ora from 'ora';
7
8
 
@@ -1,14 +1,18 @@
1
- import { Command } from 'commander';
2
- import { watch, FSWatcher } from 'chokidar';
3
- import fs from 'fs-extra';
4
1
  import path from 'path';
2
+
5
3
  import chalk from 'chalk';
4
+ import { watch } from 'chokidar';
5
+ import fs from 'fs-extra';
6
6
  import YAML from 'yaml';
7
- import { ConfigManager } from '../utils/config-manager';
8
- import { PluginManager } from '../plugins/plugin-manager';
9
- import { logger } from '../utils/logger';
7
+
10
8
  import { errorHandler } from '../utils/error-handler';
11
- import { WatchConfig, WatchCommand } from '../types';
9
+ import { logger } from '../utils/logger';
10
+
11
+ import type { PluginManager } from '../plugins/plugin-manager';
12
+ import type { WatchConfig, WatchCommand } from '../types';
13
+ import type { ConfigManager } from '../utils/config-manager';
14
+ import type { FSWatcher } from 'chokidar';
15
+ import type { Command } from 'commander';
12
16
 
13
17
  /**
14
18
  * Watch commands for real-time monitoring
@@ -552,7 +556,9 @@ export class WatchCommands {
552
556
  }
553
557
 
554
558
  private shouldExecuteCommand(cmd: WatchCommand, filePath: string): boolean {
555
- if (!cmd.condition) return true;
559
+ if (!cmd.condition) {
560
+ return true;
561
+ }
556
562
 
557
563
  // Implement condition checking logic
558
564
  switch (cmd.condition) {
@@ -728,11 +734,15 @@ export class WatchCommands {
728
734
  }
729
735
 
730
736
  private getTestCommand(framework: string, options: any): string {
731
- const baseCmd = framework === 'npm' ? `npm test` : `npx ${framework}`;
737
+ const baseCmd = framework === 'npm' ? 'npm test' : `npx ${framework}`;
732
738
  const flags: string[] = [];
733
739
 
734
- if (options.coverage) flags.push('--coverage');
735
- if (options.changedOnly) flags.push('--changedSince=HEAD');
740
+ if (options.coverage) {
741
+ flags.push('--coverage');
742
+ }
743
+ if (options.changedOnly) {
744
+ flags.push('--changedSince=HEAD');
745
+ }
736
746
 
737
747
  return `${baseCmd} ${flags.join(' ')}`;
738
748
  }