@wundr.io/cli 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/README.md +551 -0
- package/bin/wundr.js +39 -0
- package/dist/ai/ai-service.d.ts +152 -0
- package/dist/ai/ai-service.d.ts.map +1 -0
- package/dist/ai/ai-service.js +430 -0
- package/dist/ai/ai-service.js.map +1 -0
- package/dist/ai/claude-client.d.ts +130 -0
- package/dist/ai/claude-client.d.ts.map +1 -0
- package/dist/ai/claude-client.js +339 -0
- package/dist/ai/claude-client.js.map +1 -0
- package/dist/ai/conversation-manager.d.ts +164 -0
- package/dist/ai/conversation-manager.d.ts.map +1 -0
- package/dist/ai/conversation-manager.js +612 -0
- package/dist/ai/conversation-manager.js.map +1 -0
- package/dist/ai/index.d.ts +5 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +8 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/cli.d.ts +36 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +173 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ai.d.ts +89 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +735 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/analyze-optimized.d.ts +14 -0
- package/dist/commands/analyze-optimized.d.ts.map +1 -0
- package/dist/commands/analyze-optimized.js +437 -0
- package/dist/commands/analyze-optimized.js.map +1 -0
- package/dist/commands/analyze.d.ts +65 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +435 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/batch.d.ts +71 -0
- package/dist/commands/batch.d.ts.map +1 -0
- package/dist/commands/batch.js +738 -0
- package/dist/commands/batch.js.map +1 -0
- package/dist/commands/chat.d.ts +71 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +674 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/claude-init.d.ts +28 -0
- package/dist/commands/claude-init.d.ts.map +1 -0
- package/dist/commands/claude-init.js +587 -0
- package/dist/commands/claude-init.js.map +1 -0
- package/dist/commands/claude-setup.d.ts +32 -0
- package/dist/commands/claude-setup.d.ts.map +1 -0
- package/dist/commands/claude-setup.js +570 -0
- package/dist/commands/claude-setup.js.map +1 -0
- package/dist/commands/computer-setup-commands.d.ts +39 -0
- package/dist/commands/computer-setup-commands.d.ts.map +1 -0
- package/dist/commands/computer-setup-commands.js +563 -0
- package/dist/commands/computer-setup-commands.js.map +1 -0
- package/dist/commands/computer-setup.d.ts +7 -0
- package/dist/commands/computer-setup.d.ts.map +1 -0
- package/dist/commands/computer-setup.js +481 -0
- package/dist/commands/computer-setup.js.map +1 -0
- package/dist/commands/create-command.d.ts +7 -0
- package/dist/commands/create-command.d.ts.map +1 -0
- package/dist/commands/create-command.js +158 -0
- package/dist/commands/create-command.js.map +1 -0
- package/dist/commands/create.d.ts +74 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +556 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dashboard.d.ts +91 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dashboard.js +537 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/govern.d.ts +70 -0
- package/dist/commands/govern.d.ts.map +1 -0
- package/dist/commands/govern.js +480 -0
- package/dist/commands/govern.js.map +1 -0
- package/dist/commands/init.d.ts +55 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +584 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/performance-optimizer.d.ts +30 -0
- package/dist/commands/performance-optimizer.d.ts.map +1 -0
- package/dist/commands/performance-optimizer.js +649 -0
- package/dist/commands/performance-optimizer.js.map +1 -0
- package/dist/commands/plugins.d.ts +87 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +685 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/setup.d.ts +29 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +399 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/test-init.d.ts +9 -0
- package/dist/commands/test-init.d.ts.map +1 -0
- package/dist/commands/test-init.js +222 -0
- package/dist/commands/test-init.js.map +1 -0
- package/dist/commands/test.d.ts +25 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +217 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/watch.d.ts +76 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +610 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/context/context-manager.d.ts +155 -0
- package/dist/context/context-manager.d.ts.map +1 -0
- package/dist/context/context-manager.js +383 -0
- package/dist/context/context-manager.js.map +1 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +6 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/session-manager.d.ts +207 -0
- package/dist/context/session-manager.d.ts.map +1 -0
- package/dist/context/session-manager.js +682 -0
- package/dist/context/session-manager.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive/interactive-mode.d.ts +76 -0
- package/dist/interactive/interactive-mode.d.ts.map +1 -0
- package/dist/interactive/interactive-mode.js +730 -0
- package/dist/interactive/interactive-mode.js.map +1 -0
- package/dist/nlp/command-mapper.d.ts +174 -0
- package/dist/nlp/command-mapper.d.ts.map +1 -0
- package/dist/nlp/command-mapper.js +623 -0
- package/dist/nlp/command-mapper.js.map +1 -0
- package/dist/nlp/command-parser.d.ts +106 -0
- package/dist/nlp/command-parser.d.ts.map +1 -0
- package/dist/nlp/command-parser.js +416 -0
- package/dist/nlp/command-parser.js.map +1 -0
- package/dist/nlp/index.d.ts +5 -0
- package/dist/nlp/index.d.ts.map +1 -0
- package/dist/nlp/index.js +8 -0
- package/dist/nlp/index.js.map +1 -0
- package/dist/nlp/intent-classifier.d.ts +59 -0
- package/dist/nlp/intent-classifier.d.ts.map +1 -0
- package/dist/nlp/intent-classifier.js +384 -0
- package/dist/nlp/intent-classifier.js.map +1 -0
- package/dist/nlp/intent-parser.d.ts +152 -0
- package/dist/nlp/intent-parser.d.ts.map +1 -0
- package/dist/nlp/intent-parser.js +739 -0
- package/dist/nlp/intent-parser.js.map +1 -0
- package/dist/plugins/plugin-manager.d.ts +120 -0
- package/dist/plugins/plugin-manager.d.ts.map +1 -0
- package/dist/plugins/plugin-manager.js +595 -0
- package/dist/plugins/plugin-manager.js.map +1 -0
- package/dist/types/index.d.ts +224 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/config-manager.d.ts +73 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +339 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/error-handler.d.ts +46 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +169 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/logger.d.ts +25 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +94 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +119 -0
- package/src/ai/ai-service.ts +595 -0
- package/src/ai/claude-client.ts +490 -0
- package/src/ai/conversation-manager.ts +907 -0
- package/src/ai/index.ts +8 -0
- package/src/cli.ts +202 -0
- package/src/commands/ai.ts +995 -0
- package/src/commands/analyze-optimized.ts +641 -0
- package/src/commands/analyze.ts +576 -0
- package/src/commands/batch.ts +935 -0
- package/src/commands/chat.ts +876 -0
- package/src/commands/claude-init.ts +715 -0
- package/src/commands/claude-setup.ts +697 -0
- package/src/commands/computer-setup-commands.ts +709 -0
- package/src/commands/computer-setup.ts +565 -0
- package/src/commands/create-command.ts +175 -0
- package/src/commands/create.ts +727 -0
- package/src/commands/dashboard.ts +691 -0
- package/src/commands/govern.ts +635 -0
- package/src/commands/init.ts +677 -0
- package/src/commands/performance-optimizer.ts +864 -0
- package/src/commands/plugins.ts +848 -0
- package/src/commands/setup.ts +508 -0
- package/src/commands/test-init.ts +242 -0
- package/src/commands/test.ts +264 -0
- package/src/commands/watch.ts +755 -0
- package/src/context/context-manager.ts +546 -0
- package/src/context/index.ts +9 -0
- package/src/context/session-manager.ts +1019 -0
- package/src/index.ts +64 -0
- package/src/interactive/interactive-mode.ts +830 -0
- package/src/nlp/command-mapper.ts +885 -0
- package/src/nlp/command-parser.ts +564 -0
- package/src/nlp/index.ts +4 -0
- package/src/nlp/intent-classifier.ts +458 -0
- package/src/nlp/intent-parser.ts +1101 -0
- package/src/plugins/plugin-manager.ts +744 -0
- package/src/types/index.ts +252 -0
- package/src/types/modules.d.ts +56 -0
- package/src/utils/config-manager.ts +391 -0
- package/src/utils/error-handler.ts +192 -0
- package/src/utils/logger.ts +104 -0
- package/templates/batch/ci-cd.yaml +62 -0
- package/templates/component/{{fileName}}.test.tsx +17 -0
- package/templates/component/{{fileName}}.tsx +21 -0
- package/templates/service/{{fileName}}.ts +98 -0
- package/templates/wundr-test.config.js +0 -0
- package/test-suites/api/health.spec.ts +134 -0
- package/test-suites/helpers/test-config.ts +84 -0
- package/test-suites/ui/accessibility.spec.ts +102 -0
- package/test-suites/ui/smoke.spec.ts +92 -0
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { watch, FSWatcher } from 'chokidar';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import chalk from 'chalk';
|
|
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';
|
|
10
|
+
import { errorHandler } from '../utils/error-handler';
|
|
11
|
+
import { WatchConfig, WatchCommand } from '../types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Watch commands for real-time monitoring
|
|
15
|
+
*/
|
|
16
|
+
export class WatchCommands {
|
|
17
|
+
private watchers: Map<string, FSWatcher> = new Map();
|
|
18
|
+
private activeWatches: Map<string, WatchConfig> = new Map();
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
private program: Command,
|
|
22
|
+
private configManager: ConfigManager,
|
|
23
|
+
private pluginManager: PluginManager
|
|
24
|
+
) {
|
|
25
|
+
this.registerCommands();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private registerCommands(): void {
|
|
29
|
+
const watchCmd = this.program
|
|
30
|
+
.command('watch')
|
|
31
|
+
.description('real-time file and project monitoring');
|
|
32
|
+
|
|
33
|
+
// Start watching
|
|
34
|
+
watchCmd
|
|
35
|
+
.command('start [patterns...]')
|
|
36
|
+
.description('start watching files or directories')
|
|
37
|
+
.option('--config <config>', 'watch configuration file')
|
|
38
|
+
.option('--ignore <patterns>', 'patterns to ignore (comma-separated)')
|
|
39
|
+
.option('--command <command>', 'command to run on changes')
|
|
40
|
+
.option('--debounce <ms>', 'debounce delay in milliseconds', '300')
|
|
41
|
+
.option('--recursive', 'watch subdirectories recursively')
|
|
42
|
+
.action(async (patterns, options) => {
|
|
43
|
+
await this.startWatching(patterns, options);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Stop watching
|
|
47
|
+
watchCmd
|
|
48
|
+
.command('stop [name]')
|
|
49
|
+
.description('stop specific watch or all watches')
|
|
50
|
+
.action(async name => {
|
|
51
|
+
await this.stopWatching(name);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// List active watches
|
|
55
|
+
watchCmd
|
|
56
|
+
.command('list')
|
|
57
|
+
.alias('ls')
|
|
58
|
+
.description('list active watches')
|
|
59
|
+
.action(async () => {
|
|
60
|
+
await this.listWatches();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Watch status
|
|
64
|
+
watchCmd
|
|
65
|
+
.command('status [name]')
|
|
66
|
+
.description('show watch status')
|
|
67
|
+
.action(async name => {
|
|
68
|
+
await this.showWatchStatus(name);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Create watch config
|
|
72
|
+
watchCmd
|
|
73
|
+
.command('config create <name>')
|
|
74
|
+
.description('create watch configuration')
|
|
75
|
+
.option('--patterns <patterns>', 'file patterns to watch')
|
|
76
|
+
.option('--commands <commands>', 'commands to run on changes')
|
|
77
|
+
.option('--interactive', 'create configuration interactively')
|
|
78
|
+
.action(async (name, options) => {
|
|
79
|
+
await this.createWatchConfig(name, options);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Load watch config
|
|
83
|
+
watchCmd
|
|
84
|
+
.command('config load <file>')
|
|
85
|
+
.description('load watch configuration from file')
|
|
86
|
+
.action(async file => {
|
|
87
|
+
await this.loadWatchConfig(file);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Save watch config
|
|
91
|
+
watchCmd
|
|
92
|
+
.command('config save <name> [file]')
|
|
93
|
+
.description('save watch configuration to file')
|
|
94
|
+
.action(async (name, file) => {
|
|
95
|
+
await this.saveWatchConfig(name, file);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Watch tests
|
|
99
|
+
watchCmd
|
|
100
|
+
.command('test')
|
|
101
|
+
.description('watch and run tests on changes')
|
|
102
|
+
.option(
|
|
103
|
+
'--framework <framework>',
|
|
104
|
+
'test framework (jest, mocha, vitest)',
|
|
105
|
+
'jest'
|
|
106
|
+
)
|
|
107
|
+
.option('--coverage', 'run with coverage')
|
|
108
|
+
.option('--changed-only', 'run tests for changed files only')
|
|
109
|
+
.action(async options => {
|
|
110
|
+
await this.watchTests(options);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Watch build
|
|
114
|
+
watchCmd
|
|
115
|
+
.command('build')
|
|
116
|
+
.description('watch and build on changes')
|
|
117
|
+
.option('--target <target>', 'build target')
|
|
118
|
+
.option('--incremental', 'enable incremental builds')
|
|
119
|
+
.action(async options => {
|
|
120
|
+
await this.watchBuild(options);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Watch lint
|
|
124
|
+
watchCmd
|
|
125
|
+
.command('lint')
|
|
126
|
+
.description('watch and lint on changes')
|
|
127
|
+
.option('--fix', 'automatically fix linting issues')
|
|
128
|
+
.option('--staged-only', 'lint staged files only')
|
|
129
|
+
.action(async options => {
|
|
130
|
+
await this.watchLint(options);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Watch analysis
|
|
134
|
+
watchCmd
|
|
135
|
+
.command('analyze')
|
|
136
|
+
.description('watch and analyze code quality')
|
|
137
|
+
.option(
|
|
138
|
+
'--type <type>',
|
|
139
|
+
'analysis type (quality, deps, security)',
|
|
140
|
+
'quality'
|
|
141
|
+
)
|
|
142
|
+
.option('--threshold <threshold>', 'quality threshold')
|
|
143
|
+
.action(async options => {
|
|
144
|
+
await this.watchAnalysis(options);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Start watching files or directories
|
|
150
|
+
*/
|
|
151
|
+
private async startWatching(patterns: string[], options: any): Promise<void> {
|
|
152
|
+
try {
|
|
153
|
+
let watchConfig: WatchConfig;
|
|
154
|
+
|
|
155
|
+
if (options.config) {
|
|
156
|
+
watchConfig = await this.loadWatchConfigFile(options.config);
|
|
157
|
+
} else {
|
|
158
|
+
watchConfig = this.createWatchConfigFromOptions(patterns, options);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const watchName = `watch-${Date.now()}`;
|
|
162
|
+
logger.info(`Starting watch: ${chalk.cyan(watchName)}`);
|
|
163
|
+
|
|
164
|
+
const watcher = watch(watchConfig.patterns, {
|
|
165
|
+
ignored: watchConfig.ignore || [],
|
|
166
|
+
persistent: true,
|
|
167
|
+
ignoreInitial: true,
|
|
168
|
+
awaitWriteFinish: {
|
|
169
|
+
stabilityThreshold: parseInt(options.debounce) || 300,
|
|
170
|
+
pollInterval: 100,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Set up event handlers
|
|
175
|
+
this.setupWatchHandlers(watcher, watchConfig, watchName);
|
|
176
|
+
|
|
177
|
+
// Store watcher and config
|
|
178
|
+
this.watchers.set(watchName, watcher);
|
|
179
|
+
this.activeWatches.set(watchName, watchConfig);
|
|
180
|
+
|
|
181
|
+
logger.success(`Watching ${watchConfig.patterns.join(', ')}`);
|
|
182
|
+
logger.info('Press Ctrl+C to stop watching');
|
|
183
|
+
} catch (error) {
|
|
184
|
+
throw errorHandler.createError(
|
|
185
|
+
'WUNDR_WATCH_START_FAILED',
|
|
186
|
+
'Failed to start watching',
|
|
187
|
+
{ patterns, options },
|
|
188
|
+
true
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Stop watching
|
|
195
|
+
*/
|
|
196
|
+
private async stopWatching(name?: string): Promise<void> {
|
|
197
|
+
try {
|
|
198
|
+
if (name) {
|
|
199
|
+
const watcher = this.watchers.get(name);
|
|
200
|
+
if (watcher) {
|
|
201
|
+
await watcher.close();
|
|
202
|
+
this.watchers.delete(name);
|
|
203
|
+
this.activeWatches.delete(name);
|
|
204
|
+
logger.success(`Stopped watch: ${name}`);
|
|
205
|
+
} else {
|
|
206
|
+
logger.warn(`Watch not found: ${name}`);
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
// Stop all watches
|
|
210
|
+
for (const [watchName, watcher] of this.watchers) {
|
|
211
|
+
await watcher.close();
|
|
212
|
+
logger.info(`Stopped watch: ${watchName}`);
|
|
213
|
+
}
|
|
214
|
+
this.watchers.clear();
|
|
215
|
+
this.activeWatches.clear();
|
|
216
|
+
logger.success('Stopped all watches');
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
throw errorHandler.createError(
|
|
220
|
+
'WUNDR_WATCH_STOP_FAILED',
|
|
221
|
+
'Failed to stop watching',
|
|
222
|
+
{ name },
|
|
223
|
+
true
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* List active watches
|
|
230
|
+
*/
|
|
231
|
+
private async listWatches(): Promise<void> {
|
|
232
|
+
try {
|
|
233
|
+
if (this.activeWatches.size === 0) {
|
|
234
|
+
logger.info('No active watches');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
logger.info(`Active watches (${this.activeWatches.size}):`);
|
|
239
|
+
|
|
240
|
+
const watchData = Array.from(this.activeWatches.entries()).map(
|
|
241
|
+
([name, config]) => ({
|
|
242
|
+
Name: name,
|
|
243
|
+
Patterns: config.patterns.join(', '),
|
|
244
|
+
Commands: config.commands.length,
|
|
245
|
+
Debounce: `${config.debounce || 300}ms`,
|
|
246
|
+
})
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
console.table(watchData);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
throw errorHandler.createError(
|
|
252
|
+
'WUNDR_WATCH_LIST_FAILED',
|
|
253
|
+
'Failed to list watches',
|
|
254
|
+
{},
|
|
255
|
+
true
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Show watch status
|
|
262
|
+
*/
|
|
263
|
+
private async showWatchStatus(name?: string): Promise<void> {
|
|
264
|
+
try {
|
|
265
|
+
if (name) {
|
|
266
|
+
const config = this.activeWatches.get(name);
|
|
267
|
+
const watcher = this.watchers.get(name);
|
|
268
|
+
|
|
269
|
+
if (!config || !watcher) {
|
|
270
|
+
logger.warn(`Watch not found: ${name}`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
console.log(chalk.blue(`\nWatch Status: ${name}`));
|
|
275
|
+
console.log(`Patterns: ${config.patterns.join(', ')}`);
|
|
276
|
+
console.log(`Commands: ${config.commands.length}`);
|
|
277
|
+
console.log(`Debounce: ${config.debounce || 300}ms`);
|
|
278
|
+
console.log(
|
|
279
|
+
`Watched Paths: ${Object.keys(watcher.getWatched()).length}`
|
|
280
|
+
);
|
|
281
|
+
} else {
|
|
282
|
+
console.log(chalk.blue('\nAll Watches Status:'));
|
|
283
|
+
console.log(`Active Watches: ${this.activeWatches.size}`);
|
|
284
|
+
console.log(`Total Watchers: ${this.watchers.size}`);
|
|
285
|
+
|
|
286
|
+
for (const [watchName] of this.activeWatches) {
|
|
287
|
+
await this.showWatchStatus(watchName);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} catch (error) {
|
|
291
|
+
throw errorHandler.createError(
|
|
292
|
+
'WUNDR_WATCH_STATUS_FAILED',
|
|
293
|
+
'Failed to show watch status',
|
|
294
|
+
{ name },
|
|
295
|
+
true
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Create watch configuration
|
|
302
|
+
*/
|
|
303
|
+
private async createWatchConfig(name: string, options: any): Promise<void> {
|
|
304
|
+
try {
|
|
305
|
+
let config: WatchConfig;
|
|
306
|
+
|
|
307
|
+
if (options.interactive) {
|
|
308
|
+
config = await this.createInteractiveWatchConfig();
|
|
309
|
+
} else {
|
|
310
|
+
config = {
|
|
311
|
+
patterns: options.patterns ? options.patterns.split(',') : ['**/*'],
|
|
312
|
+
commands: options.commands
|
|
313
|
+
? this.parseWatchCommands(options.commands)
|
|
314
|
+
: [],
|
|
315
|
+
debounce: 300,
|
|
316
|
+
recursive: true,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const configPath = path.join(
|
|
321
|
+
process.cwd(),
|
|
322
|
+
'.wundr',
|
|
323
|
+
'watch',
|
|
324
|
+
`${name}.yaml`
|
|
325
|
+
);
|
|
326
|
+
await fs.ensureDir(path.dirname(configPath));
|
|
327
|
+
await fs.writeFile(configPath, YAML.stringify(config));
|
|
328
|
+
|
|
329
|
+
logger.success(`Watch configuration created: ${configPath}`);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
throw errorHandler.createError(
|
|
332
|
+
'WUNDR_WATCH_CONFIG_CREATE_FAILED',
|
|
333
|
+
'Failed to create watch configuration',
|
|
334
|
+
{ name, options },
|
|
335
|
+
true
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Load watch configuration from file
|
|
342
|
+
*/
|
|
343
|
+
private async loadWatchConfig(file: string): Promise<void> {
|
|
344
|
+
try {
|
|
345
|
+
const config = await this.loadWatchConfigFile(file);
|
|
346
|
+
const name = path.basename(file, path.extname(file));
|
|
347
|
+
|
|
348
|
+
await this.startWatchingWithConfig(name, config);
|
|
349
|
+
logger.success(`Loaded watch configuration: ${file}`);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
throw errorHandler.createError(
|
|
352
|
+
'WUNDR_WATCH_CONFIG_LOAD_FAILED',
|
|
353
|
+
'Failed to load watch configuration',
|
|
354
|
+
{ file },
|
|
355
|
+
true
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Save watch configuration to file
|
|
362
|
+
*/
|
|
363
|
+
private async saveWatchConfig(name: string, file?: string): Promise<void> {
|
|
364
|
+
try {
|
|
365
|
+
const config = this.activeWatches.get(name);
|
|
366
|
+
if (!config) {
|
|
367
|
+
throw new Error(`Watch not found: ${name}`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const outputPath =
|
|
371
|
+
file || path.join(process.cwd(), '.wundr', 'watch', `${name}.yaml`);
|
|
372
|
+
await fs.ensureDir(path.dirname(outputPath));
|
|
373
|
+
await fs.writeFile(outputPath, YAML.stringify(config));
|
|
374
|
+
|
|
375
|
+
logger.success(`Watch configuration saved: ${outputPath}`);
|
|
376
|
+
} catch (error) {
|
|
377
|
+
throw errorHandler.createError(
|
|
378
|
+
'WUNDR_WATCH_CONFIG_SAVE_FAILED',
|
|
379
|
+
'Failed to save watch configuration',
|
|
380
|
+
{ name, file },
|
|
381
|
+
true
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Watch tests
|
|
388
|
+
*/
|
|
389
|
+
private async watchTests(options: any): Promise<void> {
|
|
390
|
+
try {
|
|
391
|
+
logger.info('Starting test watcher...');
|
|
392
|
+
|
|
393
|
+
const testPatterns = this.getTestPatterns(options.framework);
|
|
394
|
+
const watchConfig: WatchConfig = {
|
|
395
|
+
patterns: testPatterns,
|
|
396
|
+
commands: [
|
|
397
|
+
{
|
|
398
|
+
trigger: 'change',
|
|
399
|
+
command: this.getTestCommand(options.framework, options),
|
|
400
|
+
condition: 'test-files',
|
|
401
|
+
},
|
|
402
|
+
],
|
|
403
|
+
debounce: 1000,
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
await this.startWatchingWithConfig('test-watch', watchConfig);
|
|
407
|
+
} catch (error) {
|
|
408
|
+
throw errorHandler.createError(
|
|
409
|
+
'WUNDR_WATCH_TEST_FAILED',
|
|
410
|
+
'Failed to start test watcher',
|
|
411
|
+
{ options },
|
|
412
|
+
true
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Watch build
|
|
419
|
+
*/
|
|
420
|
+
private async watchBuild(options: any): Promise<void> {
|
|
421
|
+
try {
|
|
422
|
+
logger.info('Starting build watcher...');
|
|
423
|
+
|
|
424
|
+
const buildPatterns = ['src/**/*', 'lib/**/*', '*.config.*'];
|
|
425
|
+
const watchConfig: WatchConfig = {
|
|
426
|
+
patterns: buildPatterns,
|
|
427
|
+
commands: [
|
|
428
|
+
{
|
|
429
|
+
trigger: 'change',
|
|
430
|
+
command: this.getBuildCommand(options),
|
|
431
|
+
condition: 'source-files',
|
|
432
|
+
},
|
|
433
|
+
],
|
|
434
|
+
debounce: 500,
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
await this.startWatchingWithConfig('build-watch', watchConfig);
|
|
438
|
+
} catch (error) {
|
|
439
|
+
throw errorHandler.createError(
|
|
440
|
+
'WUNDR_WATCH_BUILD_FAILED',
|
|
441
|
+
'Failed to start build watcher',
|
|
442
|
+
{ options },
|
|
443
|
+
true
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Watch lint
|
|
450
|
+
*/
|
|
451
|
+
private async watchLint(options: any): Promise<void> {
|
|
452
|
+
try {
|
|
453
|
+
logger.info('Starting lint watcher...');
|
|
454
|
+
|
|
455
|
+
const lintPatterns = ['**/*.{ts,tsx,js,jsx}', '!node_modules/**'];
|
|
456
|
+
const watchConfig: WatchConfig = {
|
|
457
|
+
patterns: lintPatterns,
|
|
458
|
+
commands: [
|
|
459
|
+
{
|
|
460
|
+
trigger: 'change',
|
|
461
|
+
command: this.getLintCommand(options),
|
|
462
|
+
condition: 'lint-files',
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
debounce: 300,
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
await this.startWatchingWithConfig('lint-watch', watchConfig);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
throw errorHandler.createError(
|
|
471
|
+
'WUNDR_WATCH_LINT_FAILED',
|
|
472
|
+
'Failed to start lint watcher',
|
|
473
|
+
{ options },
|
|
474
|
+
true
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Watch analysis
|
|
481
|
+
*/
|
|
482
|
+
private async watchAnalysis(options: any): Promise<void> {
|
|
483
|
+
try {
|
|
484
|
+
logger.info(`Starting ${options.type} analysis watcher...`);
|
|
485
|
+
|
|
486
|
+
const analysisPatterns = ['src/**/*', 'lib/**/*'];
|
|
487
|
+
const watchConfig: WatchConfig = {
|
|
488
|
+
patterns: analysisPatterns,
|
|
489
|
+
commands: [
|
|
490
|
+
{
|
|
491
|
+
trigger: 'change',
|
|
492
|
+
command: this.getAnalysisCommand(options),
|
|
493
|
+
condition: 'analysis-files',
|
|
494
|
+
},
|
|
495
|
+
],
|
|
496
|
+
debounce: 2000,
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
await this.startWatchingWithConfig('analysis-watch', watchConfig);
|
|
500
|
+
} catch (error) {
|
|
501
|
+
throw errorHandler.createError(
|
|
502
|
+
'WUNDR_WATCH_ANALYSIS_FAILED',
|
|
503
|
+
'Failed to start analysis watcher',
|
|
504
|
+
{ options },
|
|
505
|
+
true
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Helper methods
|
|
512
|
+
*/
|
|
513
|
+
private setupWatchHandlers(
|
|
514
|
+
watcher: FSWatcher,
|
|
515
|
+
config: WatchConfig,
|
|
516
|
+
name: string
|
|
517
|
+
): void {
|
|
518
|
+
const executeCommands = async (eventType: string, filePath: string) => {
|
|
519
|
+
const relevantCommands = config.commands.filter(
|
|
520
|
+
cmd => cmd.trigger === eventType || cmd.trigger === 'change'
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
for (const cmd of relevantCommands) {
|
|
524
|
+
if (this.shouldExecuteCommand(cmd, filePath)) {
|
|
525
|
+
await this.executeWatchCommand(cmd, filePath);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
watcher.on('add', filePath => {
|
|
531
|
+
logger.debug(`File added: ${filePath}`);
|
|
532
|
+
executeCommands('add', filePath);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
watcher.on('change', filePath => {
|
|
536
|
+
logger.debug(`File changed: ${filePath}`);
|
|
537
|
+
executeCommands('change', filePath);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
watcher.on('unlink', filePath => {
|
|
541
|
+
logger.debug(`File deleted: ${filePath}`);
|
|
542
|
+
executeCommands('delete', filePath);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
watcher.on('error', error => {
|
|
546
|
+
logger.error(`Watch error in ${name}:`, error);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
watcher.on('ready', () => {
|
|
550
|
+
logger.debug(`Watch ready: ${name}`);
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private shouldExecuteCommand(cmd: WatchCommand, filePath: string): boolean {
|
|
555
|
+
if (!cmd.condition) return true;
|
|
556
|
+
|
|
557
|
+
// Implement condition checking logic
|
|
558
|
+
switch (cmd.condition) {
|
|
559
|
+
case 'test-files':
|
|
560
|
+
return filePath.includes('.test.') || filePath.includes('.spec.');
|
|
561
|
+
case 'source-files':
|
|
562
|
+
return !filePath.includes('.test.') && !filePath.includes('.spec.');
|
|
563
|
+
case 'lint-files':
|
|
564
|
+
return /\.(ts|tsx|js|jsx)$/.test(filePath);
|
|
565
|
+
case 'analysis-files':
|
|
566
|
+
return /\.(ts|tsx|js|jsx)$/.test(filePath);
|
|
567
|
+
default:
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
private async executeWatchCommand(
|
|
573
|
+
cmd: WatchCommand,
|
|
574
|
+
filePath: string
|
|
575
|
+
): Promise<void> {
|
|
576
|
+
try {
|
|
577
|
+
logger.info(`Executing: ${cmd.command}`);
|
|
578
|
+
|
|
579
|
+
// Replace placeholders in command
|
|
580
|
+
const command = cmd.command.replace('{{file}}', filePath);
|
|
581
|
+
|
|
582
|
+
// Execute command
|
|
583
|
+
const { spawn } = await import('child_process');
|
|
584
|
+
const [cmdName, ...args] = command.split(' ');
|
|
585
|
+
|
|
586
|
+
if (!cmdName) {
|
|
587
|
+
logger.error('Invalid command: empty command string');
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const child = spawn(cmdName, args, {
|
|
592
|
+
stdio: 'inherit',
|
|
593
|
+
shell: true,
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
child.on('exit', code => {
|
|
597
|
+
if (code === 0) {
|
|
598
|
+
logger.success(`Command completed: ${cmd.command}`);
|
|
599
|
+
} else {
|
|
600
|
+
logger.error(`Command failed with exit code ${code}: ${cmd.command}`);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
} catch (error) {
|
|
604
|
+
logger.error(`Failed to execute command: ${cmd.command}`, error);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
private createWatchConfigFromOptions(
|
|
609
|
+
patterns: string[],
|
|
610
|
+
options: any
|
|
611
|
+
): WatchConfig {
|
|
612
|
+
return {
|
|
613
|
+
patterns: patterns.length > 0 ? patterns : ['**/*'],
|
|
614
|
+
ignore: options.ignore ? options.ignore.split(',') : [],
|
|
615
|
+
commands: options.command
|
|
616
|
+
? [
|
|
617
|
+
{
|
|
618
|
+
trigger: 'change',
|
|
619
|
+
command: options.command,
|
|
620
|
+
},
|
|
621
|
+
]
|
|
622
|
+
: [],
|
|
623
|
+
debounce: parseInt(options.debounce) || 300,
|
|
624
|
+
recursive: options.recursive || true,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
private async loadWatchConfigFile(file: string): Promise<WatchConfig> {
|
|
629
|
+
if (!(await fs.pathExists(file))) {
|
|
630
|
+
throw new Error(`Configuration file not found: ${file}`);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const content = await fs.readFile(file, 'utf8');
|
|
634
|
+
const ext = path.extname(file).toLowerCase();
|
|
635
|
+
|
|
636
|
+
if (ext === '.yaml' || ext === '.yml') {
|
|
637
|
+
return YAML.parse(content);
|
|
638
|
+
} else if (ext === '.json') {
|
|
639
|
+
return JSON.parse(content);
|
|
640
|
+
} else {
|
|
641
|
+
throw new Error(`Unsupported configuration format: ${ext}`);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
private async startWatchingWithConfig(
|
|
646
|
+
name: string,
|
|
647
|
+
config: WatchConfig
|
|
648
|
+
): Promise<void> {
|
|
649
|
+
const watcher = watch(config.patterns, {
|
|
650
|
+
ignored: config.ignore || [],
|
|
651
|
+
persistent: true,
|
|
652
|
+
ignoreInitial: true,
|
|
653
|
+
awaitWriteFinish: {
|
|
654
|
+
stabilityThreshold: config.debounce || 300,
|
|
655
|
+
pollInterval: 100,
|
|
656
|
+
},
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
this.setupWatchHandlers(watcher, config, name);
|
|
660
|
+
this.watchers.set(name, watcher);
|
|
661
|
+
this.activeWatches.set(name, config);
|
|
662
|
+
|
|
663
|
+
logger.success(`Started watch: ${name}`);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
private async createInteractiveWatchConfig(): Promise<WatchConfig> {
|
|
667
|
+
const inquirer = await import('inquirer');
|
|
668
|
+
|
|
669
|
+
const answers = await inquirer.default.prompt([
|
|
670
|
+
{
|
|
671
|
+
type: 'input',
|
|
672
|
+
name: 'patterns',
|
|
673
|
+
message: 'File patterns to watch (comma-separated):',
|
|
674
|
+
default: '**/*',
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
type: 'input',
|
|
678
|
+
name: 'ignore',
|
|
679
|
+
message: 'Patterns to ignore (comma-separated):',
|
|
680
|
+
default: 'node_modules/**,dist/**',
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
type: 'input',
|
|
684
|
+
name: 'command',
|
|
685
|
+
message: 'Command to run on changes:',
|
|
686
|
+
validate: input => input.length > 0 || 'Command is required',
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
type: 'number',
|
|
690
|
+
name: 'debounce',
|
|
691
|
+
message: 'Debounce delay (ms):',
|
|
692
|
+
default: 300,
|
|
693
|
+
},
|
|
694
|
+
]);
|
|
695
|
+
|
|
696
|
+
return {
|
|
697
|
+
patterns: answers.patterns.split(',').map(p => p.trim()),
|
|
698
|
+
ignore: answers.ignore.split(',').map(p => p.trim()),
|
|
699
|
+
commands: [
|
|
700
|
+
{
|
|
701
|
+
trigger: 'change',
|
|
702
|
+
command: answers.command,
|
|
703
|
+
},
|
|
704
|
+
],
|
|
705
|
+
debounce: answers.debounce,
|
|
706
|
+
recursive: true,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
private parseWatchCommands(commandsStr: string): WatchCommand[] {
|
|
711
|
+
return commandsStr.split(',').map(cmd => ({
|
|
712
|
+
trigger: 'change',
|
|
713
|
+
command: cmd.trim(),
|
|
714
|
+
}));
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
private getTestPatterns(framework: string): string[] {
|
|
718
|
+
switch (framework) {
|
|
719
|
+
case 'jest':
|
|
720
|
+
return ['**/*.{test,spec}.{js,jsx,ts,tsx}', 'src/**/*'];
|
|
721
|
+
case 'mocha':
|
|
722
|
+
return ['test/**/*.js', 'src/**/*'];
|
|
723
|
+
case 'vitest':
|
|
724
|
+
return ['**/*.{test,spec}.{js,ts}', 'src/**/*'];
|
|
725
|
+
default:
|
|
726
|
+
return ['**/*.{test,spec}.*', 'src/**/*'];
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
private getTestCommand(framework: string, options: any): string {
|
|
731
|
+
const baseCmd = framework === 'npm' ? `npm test` : `npx ${framework}`;
|
|
732
|
+
const flags: string[] = [];
|
|
733
|
+
|
|
734
|
+
if (options.coverage) flags.push('--coverage');
|
|
735
|
+
if (options.changedOnly) flags.push('--changedSince=HEAD');
|
|
736
|
+
|
|
737
|
+
return `${baseCmd} ${flags.join(' ')}`;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
private getBuildCommand(options: any): string {
|
|
741
|
+
const cmd = options.target
|
|
742
|
+
? `npm run build:${options.target}`
|
|
743
|
+
: 'npm run build';
|
|
744
|
+
return options.incremental ? `${cmd} --incremental` : cmd;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private getLintCommand(options: any): string {
|
|
748
|
+
const cmd = 'npx eslint {{file}}';
|
|
749
|
+
return options.fix ? `${cmd} --fix` : cmd;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
private getAnalysisCommand(options: any): string {
|
|
753
|
+
return `wundr analyze ${options.type}`;
|
|
754
|
+
}
|
|
755
|
+
}
|