driftdetect 0.4.7 → 0.6.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.
Files changed (92) hide show
  1. package/dist/bin/drift.js +37 -1
  2. package/dist/bin/drift.js.map +1 -1
  3. package/dist/commands/approve.d.ts +20 -0
  4. package/dist/commands/approve.d.ts.map +1 -1
  5. package/dist/commands/approve.js +38 -72
  6. package/dist/commands/approve.js.map +1 -1
  7. package/dist/commands/check.d.ts +41 -0
  8. package/dist/commands/check.d.ts.map +1 -1
  9. package/dist/commands/check.js +21 -11
  10. package/dist/commands/check.js.map +1 -1
  11. package/dist/commands/constraints.d.ts +17 -0
  12. package/dist/commands/constraints.d.ts.map +1 -0
  13. package/dist/commands/constraints.js +686 -0
  14. package/dist/commands/constraints.js.map +1 -0
  15. package/dist/commands/coupling.d.ts +17 -0
  16. package/dist/commands/coupling.d.ts.map +1 -0
  17. package/dist/commands/coupling.js +726 -0
  18. package/dist/commands/coupling.js.map +1 -0
  19. package/dist/commands/decisions.d.ts +19 -0
  20. package/dist/commands/decisions.d.ts.map +1 -0
  21. package/dist/commands/decisions.js +771 -0
  22. package/dist/commands/decisions.js.map +1 -0
  23. package/dist/commands/error-handling.d.ts +15 -0
  24. package/dist/commands/error-handling.d.ts.map +1 -0
  25. package/dist/commands/error-handling.js +608 -0
  26. package/dist/commands/error-handling.js.map +1 -0
  27. package/dist/commands/export.d.ts +16 -0
  28. package/dist/commands/export.d.ts.map +1 -1
  29. package/dist/commands/export.js +46 -50
  30. package/dist/commands/export.js.map +1 -1
  31. package/dist/commands/files.d.ts +15 -0
  32. package/dist/commands/files.d.ts.map +1 -1
  33. package/dist/commands/files.js +27 -48
  34. package/dist/commands/files.js.map +1 -1
  35. package/dist/commands/go.d.ts +21 -0
  36. package/dist/commands/go.d.ts.map +1 -0
  37. package/dist/commands/go.js +530 -0
  38. package/dist/commands/go.js.map +1 -0
  39. package/dist/commands/ignore.d.ts +20 -0
  40. package/dist/commands/ignore.d.ts.map +1 -1
  41. package/dist/commands/ignore.js +25 -48
  42. package/dist/commands/ignore.js.map +1 -1
  43. package/dist/commands/index.d.ts +11 -0
  44. package/dist/commands/index.d.ts.map +1 -1
  45. package/dist/commands/index.js +15 -0
  46. package/dist/commands/index.js.map +1 -1
  47. package/dist/commands/migrate-storage.d.ts +23 -0
  48. package/dist/commands/migrate-storage.d.ts.map +1 -0
  49. package/dist/commands/migrate-storage.js +337 -0
  50. package/dist/commands/migrate-storage.js.map +1 -0
  51. package/dist/commands/report.d.ts +22 -0
  52. package/dist/commands/report.d.ts.map +1 -1
  53. package/dist/commands/report.js +19 -10
  54. package/dist/commands/report.js.map +1 -1
  55. package/dist/commands/scan.d.ts +2 -0
  56. package/dist/commands/scan.d.ts.map +1 -1
  57. package/dist/commands/scan.js +134 -3
  58. package/dist/commands/scan.js.map +1 -1
  59. package/dist/commands/simulate.d.ts +17 -0
  60. package/dist/commands/simulate.d.ts.map +1 -0
  61. package/dist/commands/simulate.js +253 -0
  62. package/dist/commands/simulate.js.map +1 -0
  63. package/dist/commands/skills.d.ts +16 -0
  64. package/dist/commands/skills.d.ts.map +1 -0
  65. package/dist/commands/skills.js +409 -0
  66. package/dist/commands/skills.js.map +1 -0
  67. package/dist/commands/status.d.ts +20 -0
  68. package/dist/commands/status.d.ts.map +1 -1
  69. package/dist/commands/status.js +74 -72
  70. package/dist/commands/status.js.map +1 -1
  71. package/dist/commands/test-topology.d.ts +15 -0
  72. package/dist/commands/test-topology.d.ts.map +1 -0
  73. package/dist/commands/test-topology.js +589 -0
  74. package/dist/commands/test-topology.js.map +1 -0
  75. package/dist/commands/where.d.ts +15 -0
  76. package/dist/commands/where.d.ts.map +1 -1
  77. package/dist/commands/where.js +41 -88
  78. package/dist/commands/where.js.map +1 -1
  79. package/dist/commands/wpf.d.ts +21 -0
  80. package/dist/commands/wpf.d.ts.map +1 -0
  81. package/dist/commands/wpf.js +632 -0
  82. package/dist/commands/wpf.js.map +1 -0
  83. package/dist/commands/wrappers.d.ts +16 -0
  84. package/dist/commands/wrappers.d.ts.map +1 -0
  85. package/dist/commands/wrappers.js +181 -0
  86. package/dist/commands/wrappers.js.map +1 -0
  87. package/dist/services/pattern-service-factory.d.ts +37 -0
  88. package/dist/services/pattern-service-factory.d.ts.map +1 -0
  89. package/dist/services/pattern-service-factory.js +41 -0
  90. package/dist/services/pattern-service-factory.js.map +1 -0
  91. package/package.json +35 -23
  92. package/LICENSE +0 -21
@@ -0,0 +1,632 @@
1
+ /**
2
+ * WPF Command - drift wpf
3
+ *
4
+ * Analyze WPF applications: bindings, MVVM compliance, data flow.
5
+ *
6
+ * @requirements WPF Framework Support
7
+ */
8
+ import { Command } from 'commander';
9
+ import chalk from 'chalk';
10
+ import { createWpfAnalyzer, createWpfDataFlowTracer, createValueConverterExtractor, } from 'driftdetect-core';
11
+ import { createSpinner } from '../ui/spinner.js';
12
+ /**
13
+ * Create the WPF command
14
+ */
15
+ export function createWpfCommand() {
16
+ const wpf = new Command('wpf')
17
+ .description('WPF framework analysis commands');
18
+ // drift wpf bindings
19
+ wpf
20
+ .command('bindings [path]')
21
+ .description('List all XAML bindings and their targets')
22
+ .option('--unresolved', 'Show only unresolved bindings')
23
+ .option('-f, --format <format>', 'Output format: text, json', 'text')
24
+ .option('-v, --verbose', 'Enable verbose output')
25
+ .action(async (targetPath, options) => {
26
+ await bindingsAction(targetPath, options);
27
+ });
28
+ // drift wpf mvvm
29
+ wpf
30
+ .command('mvvm [path]')
31
+ .description('Check MVVM compliance')
32
+ .option('--strict', 'Fail on any violation')
33
+ .option('-f, --format <format>', 'Output format: text, json', 'text')
34
+ .option('-v, --verbose', 'Enable verbose output')
35
+ .action(async (targetPath, options) => {
36
+ await mvvmAction(targetPath, options);
37
+ });
38
+ // drift wpf datacontext
39
+ wpf
40
+ .command('datacontext [path]')
41
+ .description('Show DataContext resolution for views')
42
+ .option('-f, --format <format>', 'Output format: text, json', 'text')
43
+ .option('-v, --verbose', 'Enable verbose output')
44
+ .action(async (targetPath, options) => {
45
+ await datacontextAction(targetPath, options);
46
+ });
47
+ // drift wpf commands
48
+ wpf
49
+ .command('commands [path]')
50
+ .description('List all commands and their handlers')
51
+ .option('-f, --format <format>', 'Output format: text, json', 'text')
52
+ .option('-v, --verbose', 'Enable verbose output')
53
+ .action(async (targetPath, options) => {
54
+ await commandsAction(targetPath, options);
55
+ });
56
+ // drift wpf status
57
+ wpf
58
+ .command('status [path]')
59
+ .description('Show WPF project analysis summary')
60
+ .option('-f, --format <format>', 'Output format: text, json', 'text')
61
+ .option('-v, --verbose', 'Enable verbose output')
62
+ .action(async (targetPath, options) => {
63
+ await statusAction(targetPath, options);
64
+ });
65
+ // drift wpf flow
66
+ wpf
67
+ .command('flow <element>')
68
+ .description('Trace data flow from UI element to database')
69
+ .option('-f, --format <format>', 'Output format: text, json', 'text')
70
+ .option('-v, --verbose', 'Enable verbose output')
71
+ .option('--max-depth <depth>', 'Maximum trace depth', '10')
72
+ .action(async (element, options) => {
73
+ await flowAction(element, options);
74
+ });
75
+ // drift wpf converters
76
+ wpf
77
+ .command('converters [path]')
78
+ .description('List all value converters and their usage')
79
+ .option('-f, --format <format>', 'Output format: text, json', 'text')
80
+ .option('-v, --verbose', 'Enable verbose output')
81
+ .action(async (targetPath, options) => {
82
+ await convertersAction(targetPath, options);
83
+ });
84
+ return wpf;
85
+ }
86
+ /**
87
+ * Bindings subcommand
88
+ */
89
+ async function bindingsAction(targetPath, options) {
90
+ const rootDir = targetPath ?? process.cwd();
91
+ const format = options.format ?? 'text';
92
+ const isTextFormat = format === 'text';
93
+ const spinner = isTextFormat ? createSpinner('Analyzing XAML bindings...') : null;
94
+ spinner?.start();
95
+ try {
96
+ const analyzer = createWpfAnalyzer({ rootDir, verbose: options.verbose });
97
+ const result = await analyzer.analyze();
98
+ spinner?.stop();
99
+ // JSON output
100
+ if (format === 'json') {
101
+ console.log(JSON.stringify({
102
+ total: result.stats.totalBindings,
103
+ resolved: result.stats.resolvedBindings,
104
+ unresolved: result.stats.unresolvedBindings,
105
+ bindings: options.unresolvedOnly
106
+ ? result.bindingErrors
107
+ : [...result.links, ...result.bindingErrors.map(e => ({ ...e, resolved: false }))],
108
+ }, null, 2));
109
+ return;
110
+ }
111
+ // Text output
112
+ console.log();
113
+ console.log(chalk.bold('šŸ“Š XAML Bindings Analysis'));
114
+ console.log(chalk.gray('─'.repeat(60)));
115
+ console.log();
116
+ console.log(`Total Bindings: ${chalk.cyan(result.stats.totalBindings)}`);
117
+ console.log(`Resolved: ${chalk.green(result.stats.resolvedBindings)}`);
118
+ console.log(`Unresolved: ${chalk.yellow(result.stats.unresolvedBindings)}`);
119
+ console.log();
120
+ // Group by XAML file
121
+ const byFile = new Map();
122
+ for (const link of result.links) {
123
+ const existing = byFile.get(link.xamlFile) ?? { links: [], errors: [] };
124
+ existing.links.push(link);
125
+ byFile.set(link.xamlFile, existing);
126
+ }
127
+ for (const error of result.bindingErrors) {
128
+ const existing = byFile.get(error.xamlFile) ?? { links: [], errors: [] };
129
+ existing.errors.push(error);
130
+ byFile.set(error.xamlFile, existing);
131
+ }
132
+ // Display by file
133
+ for (const [file, data] of byFile) {
134
+ const total = data.links.length + data.errors.length;
135
+ console.log(chalk.bold(`${file} (${total} bindings)`));
136
+ if (!options.unresolvedOnly) {
137
+ for (const link of data.links) {
138
+ console.log(` ${chalk.green('āœ“')} ${link.xamlElement}.${link.bindingPath} → ${chalk.cyan(link.viewModelClass)}.${link.viewModelProperty}`);
139
+ }
140
+ }
141
+ for (const error of data.errors) {
142
+ console.log(` ${chalk.yellow('⚠')} ${error.bindingPath} - ${error.message}`);
143
+ if (error.suggestion) {
144
+ console.log(chalk.gray(` ${error.suggestion}`));
145
+ }
146
+ }
147
+ console.log();
148
+ }
149
+ }
150
+ catch (error) {
151
+ spinner?.stop();
152
+ if (format === 'json') {
153
+ console.log(JSON.stringify({ error: String(error) }));
154
+ }
155
+ else {
156
+ console.log(chalk.red(`\nāŒ Error: ${error}`));
157
+ }
158
+ }
159
+ }
160
+ /**
161
+ * MVVM compliance subcommand
162
+ */
163
+ async function mvvmAction(targetPath, options) {
164
+ const rootDir = targetPath ?? process.cwd();
165
+ const format = options.format ?? 'text';
166
+ const isTextFormat = format === 'text';
167
+ const spinner = isTextFormat ? createSpinner('Checking MVVM compliance...') : null;
168
+ spinner?.start();
169
+ try {
170
+ const analyzer = createWpfAnalyzer({ rootDir, verbose: options.verbose });
171
+ const result = await analyzer.checkMvvmCompliance();
172
+ spinner?.stop();
173
+ // JSON output
174
+ if (format === 'json') {
175
+ console.log(JSON.stringify({
176
+ score: result.score,
177
+ violationCount: result.violations.length,
178
+ violations: result.violations,
179
+ recommendations: result.recommendations,
180
+ }, null, 2));
181
+ return;
182
+ }
183
+ // Text output
184
+ console.log();
185
+ console.log(chalk.bold('šŸ—ļø MVVM Compliance Check'));
186
+ console.log(chalk.gray('─'.repeat(60)));
187
+ console.log();
188
+ // Score with color
189
+ const scoreColor = result.score >= 80 ? chalk.green :
190
+ result.score >= 60 ? chalk.yellow : chalk.red;
191
+ console.log(`Score: ${scoreColor.bold(`${result.score}/100`)}`);
192
+ console.log();
193
+ // Violations
194
+ if (result.violations.length > 0) {
195
+ console.log(chalk.bold('Violations:'));
196
+ for (const v of result.violations) {
197
+ const severityIcon = v.severity === 'error' ? chalk.red('āœ—') :
198
+ v.severity === 'warning' ? chalk.yellow('⚠') : chalk.gray('ℹ');
199
+ console.log(` ${severityIcon} ${chalk.white(v.file)}:${v.line}`);
200
+ console.log(` ${v.message}`);
201
+ if (v.suggestion) {
202
+ console.log(chalk.gray(` → ${v.suggestion}`));
203
+ }
204
+ }
205
+ console.log();
206
+ }
207
+ else {
208
+ console.log(chalk.green('āœ“ No violations found'));
209
+ console.log();
210
+ }
211
+ // Recommendations
212
+ if (result.recommendations.length > 0) {
213
+ console.log(chalk.bold('Recommendations:'));
214
+ for (const rec of result.recommendations) {
215
+ console.log(` • ${rec}`);
216
+ }
217
+ console.log();
218
+ }
219
+ // Exit with error if strict mode and violations exist
220
+ if (options.strict && result.violations.length > 0) {
221
+ process.exit(1);
222
+ }
223
+ }
224
+ catch (error) {
225
+ spinner?.stop();
226
+ if (format === 'json') {
227
+ console.log(JSON.stringify({ error: String(error) }));
228
+ }
229
+ else {
230
+ console.log(chalk.red(`\nāŒ Error: ${error}`));
231
+ }
232
+ }
233
+ }
234
+ /**
235
+ * DataContext subcommand
236
+ */
237
+ async function datacontextAction(targetPath, options) {
238
+ const rootDir = targetPath ?? process.cwd();
239
+ const format = options.format ?? 'text';
240
+ const isTextFormat = format === 'text';
241
+ const spinner = isTextFormat ? createSpinner('Resolving DataContexts...') : null;
242
+ spinner?.start();
243
+ try {
244
+ const analyzer = createWpfAnalyzer({ rootDir, verbose: options.verbose });
245
+ const result = await analyzer.analyze();
246
+ spinner?.stop();
247
+ // JSON output
248
+ if (format === 'json') {
249
+ console.log(JSON.stringify({
250
+ views: result.dataContexts.map(dc => ({
251
+ view: dc.xamlFile,
252
+ dataContext: dc.resolvedType,
253
+ confidence: dc.confidence,
254
+ resolutionPath: dc.resolutionPath,
255
+ })),
256
+ }, null, 2));
257
+ return;
258
+ }
259
+ // Text output
260
+ console.log();
261
+ console.log(chalk.bold('šŸ”— DataContext Resolution'));
262
+ console.log(chalk.gray('─'.repeat(60)));
263
+ console.log();
264
+ for (const dc of result.dataContexts) {
265
+ const confidenceIcon = dc.confidence === 'high' ? chalk.green('ā—') :
266
+ dc.confidence === 'medium' ? chalk.yellow('ā—') : chalk.red('ā—');
267
+ const vmDisplay = dc.resolvedType ?? chalk.gray('UNRESOLVED');
268
+ console.log(`${confidenceIcon} ${chalk.white(dc.xamlFile)}`);
269
+ console.log(` DataContext: ${chalk.cyan(vmDisplay)}`);
270
+ console.log(` Confidence: ${dc.confidence}`);
271
+ if (options.verbose && dc.resolutionPath.length > 0) {
272
+ console.log(chalk.gray(' Resolution path:'));
273
+ for (const step of dc.resolutionPath) {
274
+ console.log(chalk.gray(` ${step.source}: ${step.type}`));
275
+ }
276
+ }
277
+ console.log();
278
+ }
279
+ }
280
+ catch (error) {
281
+ spinner?.stop();
282
+ if (format === 'json') {
283
+ console.log(JSON.stringify({ error: String(error) }));
284
+ }
285
+ else {
286
+ console.log(chalk.red(`\nāŒ Error: ${error}`));
287
+ }
288
+ }
289
+ }
290
+ /**
291
+ * Commands subcommand
292
+ */
293
+ async function commandsAction(targetPath, options) {
294
+ const rootDir = targetPath ?? process.cwd();
295
+ const format = options.format ?? 'text';
296
+ const isTextFormat = format === 'text';
297
+ const spinner = isTextFormat ? createSpinner('Extracting commands...') : null;
298
+ spinner?.start();
299
+ try {
300
+ const analyzer = createWpfAnalyzer({ rootDir, verbose: options.verbose });
301
+ const result = await analyzer.analyze();
302
+ spinner?.stop();
303
+ // Collect all commands from ViewModels
304
+ const commands = [];
305
+ for (const vm of result.viewModels.values()) {
306
+ for (const cmd of vm.commands) {
307
+ commands.push({
308
+ name: cmd.name,
309
+ viewModel: vm.className,
310
+ executeMethod: cmd.executeMethod,
311
+ canExecuteMethod: cmd.canExecuteMethod,
312
+ isAsync: cmd.isAsync,
313
+ file: vm.filePath,
314
+ line: cmd.location.line,
315
+ });
316
+ }
317
+ }
318
+ // JSON output
319
+ if (format === 'json') {
320
+ console.log(JSON.stringify({
321
+ total: commands.length,
322
+ commands,
323
+ }, null, 2));
324
+ return;
325
+ }
326
+ // Text output
327
+ console.log();
328
+ console.log(chalk.bold('⚔ Commands'));
329
+ console.log(chalk.gray('─'.repeat(60)));
330
+ console.log();
331
+ console.log(`Total Commands: ${chalk.cyan(commands.length)}`);
332
+ console.log();
333
+ // Group by ViewModel
334
+ const byViewModel = new Map();
335
+ for (const cmd of commands) {
336
+ const existing = byViewModel.get(cmd.viewModel) ?? [];
337
+ existing.push(cmd);
338
+ byViewModel.set(cmd.viewModel, existing);
339
+ }
340
+ for (const [vmName, vmCommands] of byViewModel) {
341
+ console.log(chalk.bold(`${vmName} (${vmCommands.length} commands)`));
342
+ for (const cmd of vmCommands) {
343
+ const asyncBadge = cmd.isAsync ? chalk.blue(' [async]') : '';
344
+ console.log(` ${chalk.cyan(cmd.name)}${asyncBadge}`);
345
+ if (cmd.executeMethod) {
346
+ console.log(chalk.gray(` Execute: ${cmd.executeMethod}()`));
347
+ }
348
+ if (cmd.canExecuteMethod) {
349
+ console.log(chalk.gray(` CanExecute: ${cmd.canExecuteMethod}()`));
350
+ }
351
+ }
352
+ console.log();
353
+ }
354
+ }
355
+ catch (error) {
356
+ spinner?.stop();
357
+ if (format === 'json') {
358
+ console.log(JSON.stringify({ error: String(error) }));
359
+ }
360
+ else {
361
+ console.log(chalk.red(`\nāŒ Error: ${error}`));
362
+ }
363
+ }
364
+ }
365
+ /**
366
+ * Status subcommand
367
+ */
368
+ async function statusAction(targetPath, options) {
369
+ const rootDir = targetPath ?? process.cwd();
370
+ const format = options.format ?? 'text';
371
+ const isTextFormat = format === 'text';
372
+ const spinner = isTextFormat ? createSpinner('Analyzing WPF project...') : null;
373
+ spinner?.start();
374
+ try {
375
+ const analyzer = createWpfAnalyzer({ rootDir, verbose: options.verbose });
376
+ const result = await analyzer.analyze();
377
+ spinner?.stop();
378
+ // JSON output
379
+ if (format === 'json') {
380
+ console.log(JSON.stringify({
381
+ project: result.project,
382
+ stats: result.stats,
383
+ viewModels: Array.from(result.viewModels.values()).map(vm => ({
384
+ name: vm.className,
385
+ properties: vm.properties.length,
386
+ commands: vm.commands.length,
387
+ implementsINPC: vm.implementsINPC,
388
+ })),
389
+ }, null, 2));
390
+ return;
391
+ }
392
+ // Text output
393
+ console.log();
394
+ console.log(chalk.bold('šŸ“Š WPF Project Status'));
395
+ console.log(chalk.gray('═'.repeat(60)));
396
+ console.log();
397
+ // Project info
398
+ if (result.project) {
399
+ console.log(chalk.bold('Project'));
400
+ console.log(chalk.gray('─'.repeat(40)));
401
+ console.log(` File: ${chalk.cyan(result.project.projectFile)}`);
402
+ console.log(` Framework: ${chalk.cyan(result.project.targetFramework)}`);
403
+ console.log(` XAML Files: ${chalk.cyan(result.project.xamlFiles.length)}`);
404
+ console.log(` ViewModels: ${chalk.cyan(result.project.viewModels.length)}`);
405
+ console.log(` Converters: ${chalk.cyan(result.project.converters.length)}`);
406
+ console.log();
407
+ }
408
+ else {
409
+ console.log(chalk.yellow('⚠ No WPF project detected'));
410
+ console.log();
411
+ }
412
+ // Statistics
413
+ console.log(chalk.bold('Statistics'));
414
+ console.log(chalk.gray('─'.repeat(40)));
415
+ console.log(` XAML Files Analyzed: ${chalk.cyan(result.stats.xamlFileCount)}`);
416
+ console.log(` ViewModels Found: ${chalk.cyan(result.stats.viewModelCount)}`);
417
+ console.log(` Total Bindings: ${chalk.cyan(result.stats.totalBindings)}`);
418
+ console.log(` Resolved Bindings: ${chalk.green(result.stats.resolvedBindings)}`);
419
+ console.log(` Unresolved Bindings: ${chalk.yellow(result.stats.unresolvedBindings)}`);
420
+ console.log(` Total Commands: ${chalk.cyan(result.stats.totalCommands)}`);
421
+ console.log(` Analysis Time: ${chalk.gray(`${result.stats.analysisTimeMs.toFixed(0)}ms`)}`);
422
+ console.log();
423
+ // ViewModels summary
424
+ if (result.viewModels.size > 0) {
425
+ console.log(chalk.bold('ViewModels'));
426
+ console.log(chalk.gray('─'.repeat(40)));
427
+ for (const vm of result.viewModels.values()) {
428
+ const inpcIcon = vm.implementsINPC ? chalk.green('āœ“') : chalk.yellow('⚠');
429
+ console.log(` ${inpcIcon} ${chalk.white(vm.className)}`);
430
+ console.log(chalk.gray(` ${vm.properties.length} properties, ${vm.commands.length} commands`));
431
+ }
432
+ console.log();
433
+ }
434
+ // Next steps
435
+ console.log(chalk.gray('─'.repeat(60)));
436
+ console.log(chalk.bold('šŸ“Œ Next Steps:'));
437
+ console.log(chalk.gray(` • drift wpf bindings ${chalk.white('View all bindings')}`));
438
+ console.log(chalk.gray(` • drift wpf mvvm ${chalk.white('Check MVVM compliance')}`));
439
+ console.log(chalk.gray(` • drift wpf datacontext ${chalk.white('View DataContext resolution')}`));
440
+ console.log(chalk.gray(` • drift wpf commands ${chalk.white('List all commands')}`));
441
+ console.log();
442
+ }
443
+ catch (error) {
444
+ spinner?.stop();
445
+ if (format === 'json') {
446
+ console.log(JSON.stringify({ error: String(error) }));
447
+ }
448
+ else {
449
+ console.log(chalk.red(`\nāŒ Error: ${error}`));
450
+ }
451
+ }
452
+ }
453
+ /**
454
+ * Flow subcommand - trace data flow from UI element
455
+ */
456
+ async function flowAction(element, options) {
457
+ const rootDir = process.cwd();
458
+ const format = options.format ?? 'text';
459
+ const isTextFormat = format === 'text';
460
+ // maxDepth option is available for future depth-limited tracing
461
+ void options.maxDepth;
462
+ const spinner = isTextFormat ? createSpinner(`Tracing data flow for '${element}'...`) : null;
463
+ spinner?.start();
464
+ try {
465
+ const analyzer = createWpfAnalyzer({ rootDir, verbose: options.verbose });
466
+ const result = await analyzer.analyze();
467
+ // Create data flow tracer
468
+ const tracer = createWpfDataFlowTracer();
469
+ tracer.initialize(result.xamlFiles, result.viewModels, result.links);
470
+ const flow = tracer.trace(element);
471
+ spinner?.stop();
472
+ // JSON output
473
+ if (format === 'json') {
474
+ console.log(JSON.stringify({
475
+ element: flow.element,
476
+ steps: flow.steps,
477
+ reachesDatabase: flow.reachesDatabase,
478
+ sensitiveDataAccessed: flow.sensitiveDataAccessed,
479
+ depth: flow.depth,
480
+ confidence: flow.confidence,
481
+ }, null, 2));
482
+ return;
483
+ }
484
+ // Text output
485
+ console.log();
486
+ console.log(chalk.bold(`šŸ” Data Flow: ${element}`));
487
+ console.log(chalk.gray('═'.repeat(60)));
488
+ console.log();
489
+ if (flow.steps.length === 0 || flow.confidence === 0) {
490
+ console.log(chalk.yellow(`⚠ Could not trace data flow for '${element}'`));
491
+ console.log();
492
+ return;
493
+ }
494
+ // Display flow steps
495
+ for (let i = 0; i < flow.steps.length; i++) {
496
+ const step = flow.steps[i];
497
+ const prefix = i === flow.steps.length - 1 ? '└─' : 'ā”œā”€';
498
+ const typeIcon = getStepIcon(step.type);
499
+ console.log(`${prefix} ${typeIcon} ${chalk.bold(step.type)}`);
500
+ console.log(` ${chalk.gray(step.location)}`);
501
+ if (step.details) {
502
+ if (step.details.bindingPath) {
503
+ console.log(chalk.gray(` Binding: ${step.details.bindingPath}`));
504
+ }
505
+ if (step.details.table) {
506
+ console.log(chalk.gray(` Table: ${step.details.table}`));
507
+ }
508
+ }
509
+ }
510
+ console.log();
511
+ // Summary
512
+ if (flow.reachesDatabase) {
513
+ console.log(chalk.green('āœ“ Reaches database'));
514
+ }
515
+ else {
516
+ console.log(chalk.gray('ā—‹ Does not reach database'));
517
+ }
518
+ if (flow.sensitiveDataAccessed.length > 0) {
519
+ console.log(chalk.yellow(`⚠ Sensitive data: ${flow.sensitiveDataAccessed.join(', ')}`));
520
+ }
521
+ console.log(chalk.gray(`Confidence: ${(flow.confidence * 100).toFixed(0)}%`));
522
+ console.log();
523
+ }
524
+ catch (error) {
525
+ spinner?.stop();
526
+ if (format === 'json') {
527
+ console.log(JSON.stringify({ error: String(error) }));
528
+ }
529
+ else {
530
+ console.log(chalk.red(`\nāŒ Error: ${error}`));
531
+ }
532
+ }
533
+ }
534
+ /**
535
+ * Get icon for flow step type
536
+ */
537
+ function getStepIcon(type) {
538
+ switch (type) {
539
+ case 'xaml-element': return 'šŸ–¼ļø';
540
+ case 'binding': return 'šŸ”—';
541
+ case 'viewmodel-property': return 'šŸ“¦';
542
+ case 'viewmodel-command': return '⚔';
543
+ case 'method-call': return 'šŸ“ž';
544
+ case 'service-call': return 'šŸ”§';
545
+ case 'ef-query': return 'šŸ—„ļø';
546
+ case 'database-table': return 'šŸ“Š';
547
+ default: return '•';
548
+ }
549
+ }
550
+ /**
551
+ * Converters subcommand - list value converters
552
+ */
553
+ async function convertersAction(targetPath, options) {
554
+ const rootDir = targetPath ?? process.cwd();
555
+ const format = options.format ?? 'text';
556
+ const isTextFormat = format === 'text';
557
+ const spinner = isTextFormat ? createSpinner('Analyzing value converters...') : null;
558
+ spinner?.start();
559
+ try {
560
+ const extractor = createValueConverterExtractor();
561
+ const result = await extractor.analyzeProject(rootDir);
562
+ spinner?.stop();
563
+ // JSON output
564
+ if (format === 'json') {
565
+ console.log(JSON.stringify({
566
+ total: result.converters.length,
567
+ totalUsages: result.totalUsages,
568
+ converters: result.converters.map(c => ({
569
+ className: c.className,
570
+ qualifiedName: c.qualifiedName,
571
+ type: c.converterType,
572
+ resourceKeys: c.resourceKeys,
573
+ hasConvert: c.convertMethod?.hasImplementation ?? false,
574
+ hasConvertBack: c.convertBackMethod?.hasImplementation ?? false,
575
+ usageCount: c.usages.length,
576
+ file: c.filePath,
577
+ })),
578
+ }, null, 2));
579
+ return;
580
+ }
581
+ // Text output
582
+ console.log();
583
+ console.log(chalk.bold('šŸ”„ Value Converters'));
584
+ console.log(chalk.gray('═'.repeat(60)));
585
+ console.log();
586
+ console.log(`Total Converters: ${chalk.cyan(result.converters.length)}`);
587
+ console.log(`Total Usages: ${chalk.cyan(result.totalUsages)}`);
588
+ console.log();
589
+ if (result.converters.length === 0) {
590
+ console.log(chalk.gray('No value converters found'));
591
+ console.log();
592
+ return;
593
+ }
594
+ for (const converter of result.converters) {
595
+ const typeLabel = converter.converterType === 'IMultiValueConverter'
596
+ ? chalk.blue('[Multi]')
597
+ : chalk.green('[Single]');
598
+ console.log(`${chalk.bold(converter.className)} ${typeLabel}`);
599
+ console.log(chalk.gray(` File: ${converter.filePath}`));
600
+ if (converter.resourceKeys.length > 0) {
601
+ console.log(chalk.gray(` Resource Keys: ${converter.resourceKeys.join(', ')}`));
602
+ }
603
+ const convertStatus = converter.convertMethod?.hasImplementation
604
+ ? chalk.green('āœ“')
605
+ : chalk.yellow('ā—‹');
606
+ const convertBackStatus = converter.convertBackMethod?.hasImplementation
607
+ ? chalk.green('āœ“')
608
+ : chalk.gray('ā—‹');
609
+ console.log(` Convert: ${convertStatus} ConvertBack: ${convertBackStatus}`);
610
+ console.log(` Usages: ${chalk.cyan(converter.usages.length)}`);
611
+ if (options.verbose && converter.usages.length > 0) {
612
+ for (const usage of converter.usages.slice(0, 5)) {
613
+ console.log(chalk.gray(` • ${usage.xamlFile}:${usage.line}`));
614
+ }
615
+ if (converter.usages.length > 5) {
616
+ console.log(chalk.gray(` ... and ${converter.usages.length - 5} more`));
617
+ }
618
+ }
619
+ console.log();
620
+ }
621
+ }
622
+ catch (error) {
623
+ spinner?.stop();
624
+ if (format === 'json') {
625
+ console.log(JSON.stringify({ error: String(error) }));
626
+ }
627
+ else {
628
+ console.log(chalk.red(`\nāŒ Error: ${error}`));
629
+ }
630
+ }
631
+ }
632
+ //# sourceMappingURL=wpf.js.map