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.
- package/dist/bin/drift.js +37 -1
- package/dist/bin/drift.js.map +1 -1
- package/dist/commands/approve.d.ts +20 -0
- package/dist/commands/approve.d.ts.map +1 -1
- package/dist/commands/approve.js +38 -72
- package/dist/commands/approve.js.map +1 -1
- package/dist/commands/check.d.ts +41 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +21 -11
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/constraints.d.ts +17 -0
- package/dist/commands/constraints.d.ts.map +1 -0
- package/dist/commands/constraints.js +686 -0
- package/dist/commands/constraints.js.map +1 -0
- package/dist/commands/coupling.d.ts +17 -0
- package/dist/commands/coupling.d.ts.map +1 -0
- package/dist/commands/coupling.js +726 -0
- package/dist/commands/coupling.js.map +1 -0
- package/dist/commands/decisions.d.ts +19 -0
- package/dist/commands/decisions.d.ts.map +1 -0
- package/dist/commands/decisions.js +771 -0
- package/dist/commands/decisions.js.map +1 -0
- package/dist/commands/error-handling.d.ts +15 -0
- package/dist/commands/error-handling.d.ts.map +1 -0
- package/dist/commands/error-handling.js +608 -0
- package/dist/commands/error-handling.js.map +1 -0
- package/dist/commands/export.d.ts +16 -0
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +46 -50
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/files.d.ts +15 -0
- package/dist/commands/files.d.ts.map +1 -1
- package/dist/commands/files.js +27 -48
- package/dist/commands/files.js.map +1 -1
- package/dist/commands/go.d.ts +21 -0
- package/dist/commands/go.d.ts.map +1 -0
- package/dist/commands/go.js +530 -0
- package/dist/commands/go.js.map +1 -0
- package/dist/commands/ignore.d.ts +20 -0
- package/dist/commands/ignore.d.ts.map +1 -1
- package/dist/commands/ignore.js +25 -48
- package/dist/commands/ignore.js.map +1 -1
- package/dist/commands/index.d.ts +11 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +15 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/migrate-storage.d.ts +23 -0
- package/dist/commands/migrate-storage.d.ts.map +1 -0
- package/dist/commands/migrate-storage.js +337 -0
- package/dist/commands/migrate-storage.js.map +1 -0
- package/dist/commands/report.d.ts +22 -0
- package/dist/commands/report.d.ts.map +1 -1
- package/dist/commands/report.js +19 -10
- package/dist/commands/report.js.map +1 -1
- package/dist/commands/scan.d.ts +2 -0
- package/dist/commands/scan.d.ts.map +1 -1
- package/dist/commands/scan.js +134 -3
- package/dist/commands/scan.js.map +1 -1
- package/dist/commands/simulate.d.ts +17 -0
- package/dist/commands/simulate.d.ts.map +1 -0
- package/dist/commands/simulate.js +253 -0
- package/dist/commands/simulate.js.map +1 -0
- package/dist/commands/skills.d.ts +16 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +409 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/status.d.ts +20 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +74 -72
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/test-topology.d.ts +15 -0
- package/dist/commands/test-topology.d.ts.map +1 -0
- package/dist/commands/test-topology.js +589 -0
- package/dist/commands/test-topology.js.map +1 -0
- package/dist/commands/where.d.ts +15 -0
- package/dist/commands/where.d.ts.map +1 -1
- package/dist/commands/where.js +41 -88
- package/dist/commands/where.js.map +1 -1
- package/dist/commands/wpf.d.ts +21 -0
- package/dist/commands/wpf.d.ts.map +1 -0
- package/dist/commands/wpf.js +632 -0
- package/dist/commands/wpf.js.map +1 -0
- package/dist/commands/wrappers.d.ts +16 -0
- package/dist/commands/wrappers.d.ts.map +1 -0
- package/dist/commands/wrappers.js +181 -0
- package/dist/commands/wrappers.js.map +1 -0
- package/dist/services/pattern-service-factory.d.ts +37 -0
- package/dist/services/pattern-service-factory.d.ts.map +1 -0
- package/dist/services/pattern-service-factory.js +41 -0
- package/dist/services/pattern-service-factory.js.map +1 -0
- package/package.json +35 -23
- 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
|