@ts-for-gir/cli 4.0.0-beta.25 ā 4.0.0-beta.26
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 +84 -2
- package/bin/ts-for-gir.js +43 -0
- package/package.json +30 -36
- package/src/commands/analyze.ts +344 -0
- package/src/commands/command-builder.ts +30 -0
- package/src/commands/copy.ts +71 -76
- package/src/commands/doc.ts +58 -46
- package/src/commands/generate.ts +97 -77
- package/src/commands/index.ts +6 -4
- package/src/commands/json.ts +104 -0
- package/src/commands/list.ts +81 -90
- package/src/config/config-loader.ts +203 -0
- package/src/config/config-writer.ts +52 -0
- package/src/config/defaults.ts +61 -0
- package/src/config/index.ts +8 -0
- package/src/config/options.ts +292 -0
- package/src/config.ts +3 -456
- package/src/formatters/typescript-formatter.ts +24 -0
- package/src/generation-handler.ts +122 -67
- package/src/index.ts +4 -4
- package/src/module-loader/dependency-resolver.ts +100 -0
- package/src/module-loader/file-finder.ts +56 -0
- package/src/module-loader/index.ts +8 -0
- package/src/module-loader/module-grouper.ts +77 -0
- package/src/module-loader/prompt-handler.ts +111 -0
- package/src/module-loader.ts +280 -580
- package/src/start.ts +18 -14
- package/src/types/command-args.ts +110 -0
- package/src/types/command-definition.ts +15 -0
- package/src/types/commands.ts +35 -0
- package/src/types/index.ts +15 -0
- package/src/types/report-types.ts +34 -0
- package/lib/commands/copy.d.ts +0 -12
- package/lib/commands/copy.js +0 -78
- package/lib/commands/copy.js.map +0 -1
- package/lib/commands/doc.d.ts +0 -12
- package/lib/commands/doc.js +0 -38
- package/lib/commands/doc.js.map +0 -1
- package/lib/commands/generate.d.ts +0 -12
- package/lib/commands/generate.js +0 -70
- package/lib/commands/generate.js.map +0 -1
- package/lib/commands/index.d.ts +0 -4
- package/lib/commands/index.js +0 -5
- package/lib/commands/index.js.map +0 -1
- package/lib/commands/list.d.ts +0 -12
- package/lib/commands/list.js +0 -79
- package/lib/commands/list.js.map +0 -1
- package/lib/config.d.ts +0 -108
- package/lib/config.js +0 -409
- package/lib/config.js.map +0 -1
- package/lib/generation-handler.d.ts +0 -10
- package/lib/generation-handler.js +0 -48
- package/lib/generation-handler.js.map +0 -1
- package/lib/index.d.ts +0 -4
- package/lib/index.js +0 -5
- package/lib/index.js.map +0 -1
- package/lib/module-loader.d.ts +0 -154
- package/lib/module-loader.js +0 -465
- package/lib/module-loader.js.map +0 -1
- package/lib/start.d.ts +0 -2
- package/lib/start.js +0 -16
- package/lib/start.js.map +0 -1
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ TypeScript type definition generator for GObject introspection GIR files
|
|
|
45
45
|
Commands:
|
|
46
46
|
ts-for-gir generate [modules..] Generates .d.ts files from GIR for GJS
|
|
47
47
|
ts-for-gir list [modules..] Lists all available GIR modules
|
|
48
|
+
ts-for-gir analyze Analyze report files generated by ts-for-gir reporter
|
|
48
49
|
ts-for-gir copy [modules..] Scan for *.gir files and copy them to a new directory
|
|
49
50
|
ts-for-gir doc [modules..] The HTML documentation generator is not yet implemented, but feel free to implement it š¤
|
|
50
51
|
|
|
@@ -111,9 +112,14 @@ Options:
|
|
|
111
112
|
--noPrettyPrint Do not prettify the generated types
|
|
112
113
|
[boolean] [default: false]
|
|
113
114
|
--noAdvancedVariants Disable GLib.Variant class with string parsing
|
|
114
|
-
[boolean] [default:
|
|
115
|
+
[boolean] [default: false]
|
|
115
116
|
--package Generate the typescript types with package.json
|
|
116
117
|
support [boolean] [default: false]
|
|
118
|
+
--reporter Enable generation problem reporter and create a
|
|
119
|
+
detailed report file [boolean] [default: false]
|
|
120
|
+
--reporterOutput Output file path for the reporter (default:
|
|
121
|
+
ts-for-gir-report.json)
|
|
122
|
+
[string] [default: "ts-for-gir-report.json"]
|
|
117
123
|
|
|
118
124
|
Examples:
|
|
119
125
|
ts-for-gir generate Run 'ts-for-gir generate' in your gjs
|
|
@@ -155,6 +161,66 @@ Examples:
|
|
|
155
161
|
ts-for-gir list --ignore=Gtk-3.0 xrandr-1.3 Lists all available GIR modules but not Gtk-3.0 and xrandr-1.3
|
|
156
162
|
```
|
|
157
163
|
|
|
164
|
+
## Analyze report files
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
$ npx @ts-for-gir/cli analyze --help
|
|
168
|
+
|
|
169
|
+
ts-for-gir analyze [options]
|
|
170
|
+
|
|
171
|
+
Analyze report files generated by ts-for-gir reporter
|
|
172
|
+
|
|
173
|
+
Options:
|
|
174
|
+
--version Show version number [boolean]
|
|
175
|
+
--help Show help [boolean]
|
|
176
|
+
-f, --reportFile Path to the report file to analyze [string] [required]
|
|
177
|
+
--severity Filter by problem severity (debug, info, warning, error, critical)
|
|
178
|
+
[array]
|
|
179
|
+
--category Filter by problem category [array]
|
|
180
|
+
--namespace Filter by namespace/module [array]
|
|
181
|
+
--type Filter by specific type name [array]
|
|
182
|
+
--search Search for text in messages, details, or type names
|
|
183
|
+
[string]
|
|
184
|
+
--since Filter problems since date/time (ISO format) [string]
|
|
185
|
+
--until Filter problems until date/time (ISO format) [string]
|
|
186
|
+
--top Show top N most problematic items [number]
|
|
187
|
+
--format Output format (table, json, summary)
|
|
188
|
+
[string] [default: "summary"]
|
|
189
|
+
--export Export filtered results to file [string]
|
|
190
|
+
--verbose Enable verbose output [boolean] [default: false]
|
|
191
|
+
|
|
192
|
+
Examples:
|
|
193
|
+
ts-for-gir analyze -f ./ts-for-gir-report.json
|
|
194
|
+
Show summary statistics of the report
|
|
195
|
+
ts-for-gir analyze -f ./report.json --severity error critical
|
|
196
|
+
Show only critical and error problems
|
|
197
|
+
ts-for-gir analyze -f ./report.json --category type_resolution --format table
|
|
198
|
+
Show type resolution problems in table format
|
|
199
|
+
ts-for-gir analyze -f ./report.json --namespace Gtk --top 10
|
|
200
|
+
Show top 10 problems in Gtk namespace
|
|
201
|
+
ts-for-gir analyze -f ./report.json --search "Unable to resolve" --export errors.json
|
|
202
|
+
Export unresolved type errors to file
|
|
203
|
+
ts-for-gir analyze -f ./report.json --type time_t --format json
|
|
204
|
+
Analyze specific type problems in JSON format
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The `analyze` command is designed to help developers and AI agents efficiently debug type generation issues by providing powerful filtering and analysis capabilities for ts-for-gir report files.
|
|
208
|
+
|
|
209
|
+
**Key Features:**
|
|
210
|
+
- **Comprehensive Filtering**: Filter by severity, category, namespace, type name, or search text
|
|
211
|
+
- **Multiple Output Formats**: Table, JSON, or summary format for different use cases
|
|
212
|
+
- **Statistical Analysis**: Show most problematic types, namespaces, and categories
|
|
213
|
+
- **Export Capabilities**: Save filtered results for further analysis
|
|
214
|
+
- **Time-based Filtering**: Analyze problems within specific time ranges
|
|
215
|
+
- **AI-Friendly**: Structured output perfect for automated analysis and debugging
|
|
216
|
+
|
|
217
|
+
**Common Use Cases:**
|
|
218
|
+
- Debug type resolution failures for specific libraries
|
|
219
|
+
- Identify patterns in generation problems across modules
|
|
220
|
+
- Export specific error categories for detailed analysis
|
|
221
|
+
- Monitor type generation quality over time
|
|
222
|
+
- Automated problem detection in CI/CD pipelines
|
|
223
|
+
|
|
158
224
|
## Generate HTML documentation
|
|
159
225
|
|
|
160
226
|
```bash
|
|
@@ -310,7 +376,7 @@ ts-for-gir generate * --noPrettyPrint
|
|
|
310
376
|
```
|
|
311
377
|
|
|
312
378
|
### noAdvancedVariants
|
|
313
|
-
The `noAdvancedVariants` option disables the advanced GLib.Variant class with string parsing capabilities. This option is enabled by default (`
|
|
379
|
+
The `noAdvancedVariants` option disables the advanced GLib.Variant class with string parsing capabilities. This option is enabled by default (`false`) as these advanced features can impact performance, especially with older TypeScript versions.
|
|
314
380
|
|
|
315
381
|
```bash
|
|
316
382
|
ts-for-gir generate * --noAdvancedVariants=false
|
|
@@ -402,4 +468,20 @@ declare module "gi://Gtk" {
|
|
|
402
468
|
import Gtk from "gi://Gtk?version=4.0";
|
|
403
469
|
export default Gtk;
|
|
404
470
|
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## reporter
|
|
474
|
+
|
|
475
|
+
The `--reporter` option enables the generation of a problem reporter and creates a detailed report file.
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
ts-for-gir generate * --reporter
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## reporterOutput
|
|
482
|
+
|
|
483
|
+
The `--reporterOutput` option specifies the output file path for the reporter. The default value is `ts-for-gir-report.json`.
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
ts-for-gir generate * --reporterOutput custom-report.json
|
|
405
487
|
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI Wrapper for TypeScript Execution
|
|
5
|
+
*
|
|
6
|
+
* This wrapper is required to execute our CLI tool with the necessary Node.js parameters.
|
|
7
|
+
* Due to "type": "module" specified in package.json, Node.js only accepts .js files
|
|
8
|
+
* and rejects .ts or .sh files directly. This wrapper bridges that gap by:
|
|
9
|
+
*
|
|
10
|
+
* 1. Providing a .js entry point that Node.js can execute
|
|
11
|
+
* 2. Spawning the actual TypeScript file with experimental Node.js flags
|
|
12
|
+
* 3. Enabling TypeScript execution without compilation step
|
|
13
|
+
*
|
|
14
|
+
* The experimental flags used:
|
|
15
|
+
* - --experimental-specifier-resolution=node: Enables Node.js-style module resolution
|
|
16
|
+
* - --experimental-strip-types: Strips TypeScript types during execution
|
|
17
|
+
* - --experimental-transform-types: Transforms TypeScript syntax to JavaScript
|
|
18
|
+
* - --no-warnings: Suppresses experimental feature warnings
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { spawn } from 'node:child_process';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { dirname, resolve } from 'node:path';
|
|
24
|
+
|
|
25
|
+
// Get the current file's directory in ES module context
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
+
const __dirname = dirname(__filename);
|
|
28
|
+
|
|
29
|
+
// Resolve the path to the actual TypeScript CLI entry point
|
|
30
|
+
const tsPath = resolve(__dirname, '../src/start.ts');
|
|
31
|
+
|
|
32
|
+
// Configure Node.js arguments for TypeScript execution
|
|
33
|
+
const nodeArgs = [
|
|
34
|
+
'--experimental-specifier-resolution=node',
|
|
35
|
+
'--experimental-strip-types',
|
|
36
|
+
'--experimental-transform-types',
|
|
37
|
+
'--no-warnings',
|
|
38
|
+
tsPath,
|
|
39
|
+
...process.argv.slice(2), // Forward all CLI arguments to the TypeScript file
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// Spawn the Node.js process with TypeScript support and inherit stdio
|
|
43
|
+
spawn('node', nodeArgs, { stdio: 'inherit' });
|
package/package.json
CHANGED
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ts-for-gir/cli",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.26",
|
|
4
4
|
"description": "TypeScript type definition generator for GObject introspection GIR files",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"module": "src/index.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"bin": {
|
|
9
|
-
"ts-for-gir": "
|
|
9
|
+
"ts-for-gir": "bin/ts-for-gir.js"
|
|
10
10
|
},
|
|
11
11
|
"engines": {
|
|
12
|
-
"node": ">=
|
|
12
|
+
"node": ">=22.7.0"
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
|
-
"start": "
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"build:ts": "tsc",
|
|
20
|
-
"clear": "yarn clear:build",
|
|
21
|
-
"clear:build": "rimraf ./lib",
|
|
22
|
-
"watch": "yarn build:ts --watch",
|
|
23
|
-
"lint": "eslint . --fix"
|
|
15
|
+
"start": "node --experimental-specifier-resolution=node --experimental-strip-types --experimental-transform-types --no-warnings src/start.ts",
|
|
16
|
+
"check:types": "tsc --noEmit",
|
|
17
|
+
"check:deps": "dpdm -T src/index.ts",
|
|
18
|
+
"check": "yarn check:types && yarn check:deps"
|
|
24
19
|
},
|
|
25
20
|
"repository": {
|
|
26
21
|
"type": "git",
|
|
@@ -28,10 +23,7 @@
|
|
|
28
23
|
},
|
|
29
24
|
"author": "Pascal Garber <pascal@artandcode.studio>",
|
|
30
25
|
"files": [
|
|
31
|
-
"src"
|
|
32
|
-
"bin",
|
|
33
|
-
"lib",
|
|
34
|
-
"templates"
|
|
26
|
+
"src"
|
|
35
27
|
],
|
|
36
28
|
"license": "Apache-2.0",
|
|
37
29
|
"bugs": {
|
|
@@ -52,31 +44,33 @@
|
|
|
52
44
|
"type definitions",
|
|
53
45
|
"cli"
|
|
54
46
|
],
|
|
47
|
+
"exports": {
|
|
48
|
+
".": "./src/index.ts"
|
|
49
|
+
},
|
|
55
50
|
"devDependencies": {
|
|
56
|
-
"@types/
|
|
57
|
-
"@types/
|
|
51
|
+
"@types/ejs": "^3.1.5",
|
|
52
|
+
"@types/inquirer": "^9.0.9",
|
|
53
|
+
"@types/node": "^24.2.1",
|
|
58
54
|
"@types/yargs": "^17.0.33",
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"eslint": "^9.29.0",
|
|
62
|
-
"eslint-config-prettier": "^10.1.5",
|
|
63
|
-
"eslint-plugin-prettier": "^5.4.1",
|
|
64
|
-
"rimraf": "^6.0.1",
|
|
65
|
-
"ts-node": "^10.9.2",
|
|
66
|
-
"typescript": "^5.8.3"
|
|
55
|
+
"dpdm": "^3.14.0",
|
|
56
|
+
"typescript": "^5.9.2"
|
|
67
57
|
},
|
|
68
58
|
"dependencies": {
|
|
69
|
-
"@gi.ts/parser": "^4.0.0-beta.
|
|
70
|
-
"@inquirer/prompts": "^7.
|
|
71
|
-
"@ts-for-gir/generator-base": "^4.0.0-beta.
|
|
72
|
-
"@ts-for-gir/generator-html-doc": "^4.0.0-beta.
|
|
73
|
-
"@ts-for-gir/generator-
|
|
74
|
-
"@ts-for-gir/
|
|
59
|
+
"@gi.ts/parser": "^4.0.0-beta.26",
|
|
60
|
+
"@inquirer/prompts": "^7.8.2",
|
|
61
|
+
"@ts-for-gir/generator-base": "^4.0.0-beta.26",
|
|
62
|
+
"@ts-for-gir/generator-html-doc": "^4.0.0-beta.26",
|
|
63
|
+
"@ts-for-gir/generator-json": "^4.0.0-beta.26",
|
|
64
|
+
"@ts-for-gir/generator-typescript": "^4.0.0-beta.26",
|
|
65
|
+
"@ts-for-gir/lib": "^4.0.0-beta.26",
|
|
66
|
+
"@ts-for-gir/reporter": "^4.0.0-beta.26",
|
|
67
|
+
"@ts-for-gir/templates": "^4.0.0-beta.26",
|
|
75
68
|
"colorette": "^2.0.20",
|
|
76
69
|
"cosmiconfig": "^9.0.0",
|
|
70
|
+
"ejs": "^3.1.10",
|
|
77
71
|
"glob": "^11.0.3",
|
|
78
|
-
"inquirer": "^12.
|
|
79
|
-
"prettier": "^3.
|
|
72
|
+
"inquirer": "^12.9.2",
|
|
73
|
+
"prettier": "^3.6.2",
|
|
80
74
|
"yargs": "^18.0.0"
|
|
81
75
|
}
|
|
82
76
|
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything you need for the `ts-for-gir analyze` command is located here
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { APP_NAME, Logger } from "@ts-for-gir/lib";
|
|
7
|
+
import type { ProblemEntry } from "@ts-for-gir/reporter";
|
|
8
|
+
import { analyzeOptions } from "../config.ts";
|
|
9
|
+
import type { AnalyzeCommandArgs, ReportData } from "../types/index.ts";
|
|
10
|
+
import { createBuilder } from "./command-builder.ts";
|
|
11
|
+
|
|
12
|
+
const command = "analyze [options]";
|
|
13
|
+
|
|
14
|
+
const description = "Analyze report files generated by ts-for-gir reporter";
|
|
15
|
+
|
|
16
|
+
const examples: ReadonlyArray<[string, string?]> = [
|
|
17
|
+
[`${APP_NAME} analyze -f ./ts-for-gir-report.json`, "Show summary statistics of the report"],
|
|
18
|
+
[`${APP_NAME} analyze -f ./ts-for-gir-report.json --summary`, "Show only summary statistics"],
|
|
19
|
+
[`${APP_NAME} analyze -f ./ts-for-gir-report.json --severity error critical`, "Show only errors and critical issues"],
|
|
20
|
+
[
|
|
21
|
+
`${APP_NAME} analyze -f ./ts-for-gir-report.json --category type_resolution --detailed`,
|
|
22
|
+
"Show detailed type resolution problems",
|
|
23
|
+
],
|
|
24
|
+
[`${APP_NAME} analyze -f ./ts-for-gir-report.json --namespace GLib --top 5`, "Show top 5 problems in GLib namespace"],
|
|
25
|
+
[
|
|
26
|
+
`${APP_NAME} analyze -f ./ts-for-gir-report.json --type time_t --export ./time_t_issues.json`,
|
|
27
|
+
"Export all time_t related issues",
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
`${APP_NAME} analyze -f ./ts-for-gir-report.json --search "Unable to resolve" --format csv`,
|
|
31
|
+
"Search for resolution failures and export as CSV",
|
|
32
|
+
],
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const builder = createBuilder<AnalyzeCommandArgs>(analyzeOptions, examples);
|
|
36
|
+
|
|
37
|
+
const parseReportDate = (dateValue: string | Date): Date => {
|
|
38
|
+
return typeof dateValue === "string" ? new Date(dateValue) : dateValue;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const loadReportFile = (filePath: string): ReportData => {
|
|
42
|
+
if (!existsSync(filePath)) {
|
|
43
|
+
throw new Error(`Report file not found: ${filePath}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const content = readFileSync(filePath, "utf-8");
|
|
48
|
+
const report = JSON.parse(content) as ReportData;
|
|
49
|
+
|
|
50
|
+
// Convert string dates to Date objects
|
|
51
|
+
report.metadata.generatedAt = parseReportDate(report.metadata.generatedAt);
|
|
52
|
+
report.statistics.startTime = parseReportDate(report.statistics.startTime);
|
|
53
|
+
|
|
54
|
+
if (report.statistics.endTime) {
|
|
55
|
+
report.statistics.endTime = parseReportDate(report.statistics.endTime);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Convert problem timestamps
|
|
59
|
+
report.problems = report.problems.map((problem) => ({
|
|
60
|
+
...problem,
|
|
61
|
+
timestamp: parseReportDate(problem.timestamp),
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
return report;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
67
|
+
throw new Error(`Failed to parse report file: ${errorMessage}`);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const filterProblems = (problems: ProblemEntry[], args: AnalyzeCommandArgs): ProblemEntry[] => {
|
|
72
|
+
let filtered = [...problems];
|
|
73
|
+
|
|
74
|
+
// Filter by severity
|
|
75
|
+
if (args.severity?.length) {
|
|
76
|
+
filtered = filtered.filter((p) => args.severity?.includes(p.severity));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Filter by category
|
|
80
|
+
if (args.category?.length) {
|
|
81
|
+
filtered = filtered.filter((p) => args.category?.includes(p.category));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Filter by namespace
|
|
85
|
+
if (args.namespace?.length) {
|
|
86
|
+
filtered = filtered.filter((p) =>
|
|
87
|
+
args.namespace?.some((ns) => p.location?.includes(ns) || p.module?.includes(ns) || p.metadata?.namespace === ns),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Filter by type name
|
|
92
|
+
if (args.type?.length) {
|
|
93
|
+
filtered = filtered.filter((p) => Boolean(p.typeName && args.type?.includes(p.typeName)));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Search filter
|
|
97
|
+
if (args.search) {
|
|
98
|
+
const searchLower = args.search.toLowerCase();
|
|
99
|
+
filtered = filtered.filter(
|
|
100
|
+
(p) =>
|
|
101
|
+
p.message.toLowerCase().includes(searchLower) ||
|
|
102
|
+
p.details?.toLowerCase().includes(searchLower) ||
|
|
103
|
+
p.typeName?.toLowerCase().includes(searchLower),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Time range filters
|
|
108
|
+
if (args.since) {
|
|
109
|
+
const sinceDate = new Date(args.since);
|
|
110
|
+
filtered = filtered.filter((p) => p.timestamp >= sinceDate);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (args.until) {
|
|
114
|
+
const untilDate = new Date(args.until);
|
|
115
|
+
filtered = filtered.filter((p) => p.timestamp <= untilDate);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return filtered;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const displaySummary = (report: ReportData, args: AnalyzeCommandArgs): void => {
|
|
122
|
+
const { statistics } = report;
|
|
123
|
+
|
|
124
|
+
console.log("š Report Summary\n");
|
|
125
|
+
|
|
126
|
+
// Basic info
|
|
127
|
+
console.log(`Generated: ${report.metadata.generatedAt}`);
|
|
128
|
+
console.log(`Version: ${report.metadata.version}`);
|
|
129
|
+
console.log(`Total Problems: ${statistics.totalProblems}`);
|
|
130
|
+
|
|
131
|
+
if (statistics.durationMs) {
|
|
132
|
+
console.log(`Generation Duration: ${(statistics.durationMs / 1000).toFixed(2)}s`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Problems by severity
|
|
136
|
+
console.log("\nš“ Problems by Severity:");
|
|
137
|
+
Object.entries(statistics.bySeverity)
|
|
138
|
+
.filter(([, count]) => count > 0)
|
|
139
|
+
.sort(([, a], [, b]) => b - a)
|
|
140
|
+
.forEach(([severity, count]) => {
|
|
141
|
+
console.log(` ${severity}: ${count}`);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Problems by category
|
|
145
|
+
console.log("\nš Problems by Category:");
|
|
146
|
+
Object.entries(statistics.byCategory)
|
|
147
|
+
.filter(([, count]) => count > 0)
|
|
148
|
+
.sort(([, a], [, b]) => b - a)
|
|
149
|
+
.forEach(([category, count]) => {
|
|
150
|
+
console.log(` ${category}: ${count}`);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Top problematic namespaces
|
|
154
|
+
if (statistics.typeStatistics.problematicNamespaces.length > 0) {
|
|
155
|
+
console.log("\nš¢ Most Problematic Namespaces:");
|
|
156
|
+
const topCount = args.top ?? 10;
|
|
157
|
+
statistics.typeStatistics.problematicNamespaces.slice(0, topCount).forEach((ns) => {
|
|
158
|
+
console.log(` ${ns.namespace}: ${ns.problems} problems`);
|
|
159
|
+
if (args.detailed) {
|
|
160
|
+
const typesList = ns.types.slice(0, 5).join(", ");
|
|
161
|
+
const moreTypes = ns.types.length > 5 ? "..." : "";
|
|
162
|
+
console.log(` Types: ${typesList}${moreTypes}`);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Common unresolved types
|
|
168
|
+
if (statistics.typeStatistics.commonUnresolvedTypes.length > 0) {
|
|
169
|
+
console.log("\nš Most Common Unresolved Types:");
|
|
170
|
+
const topCount = args.top ?? 10;
|
|
171
|
+
statistics.typeStatistics.commonUnresolvedTypes.slice(0, topCount).forEach((type) => {
|
|
172
|
+
console.log(` ${type.type}: ${type.count} occurrences`);
|
|
173
|
+
if (args.detailed) {
|
|
174
|
+
const namespacesList = type.namespaces.slice(0, 3).join(", ");
|
|
175
|
+
const moreNamespaces = type.namespaces.length > 3 ? "..." : "";
|
|
176
|
+
console.log(` Namespaces: ${namespacesList}${moreNamespaces}`);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const displayProblems = (problems: ProblemEntry[], args: AnalyzeCommandArgs): void => {
|
|
183
|
+
if (problems.length === 0) {
|
|
184
|
+
console.log("No problems match the specified filters.");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log(`\nš Found ${problems.length} matching problems:\n`);
|
|
189
|
+
|
|
190
|
+
problems.forEach((problem, index) => {
|
|
191
|
+
const message = `${index + 1}. [${problem.severity.toUpperCase()}] ${problem.message}`;
|
|
192
|
+
|
|
193
|
+
console.log(message);
|
|
194
|
+
|
|
195
|
+
if (args.detailed) {
|
|
196
|
+
console.log(` ID: ${problem.id}`);
|
|
197
|
+
console.log(` Category: ${problem.category}`);
|
|
198
|
+
console.log(` Module: ${problem.module}`);
|
|
199
|
+
if (problem.typeName) {
|
|
200
|
+
console.log(` Type: ${problem.typeName}`);
|
|
201
|
+
}
|
|
202
|
+
if (problem.location) {
|
|
203
|
+
console.log(` Location: ${problem.location}`);
|
|
204
|
+
}
|
|
205
|
+
if (problem.details) {
|
|
206
|
+
console.log(` Details: ${problem.details}`);
|
|
207
|
+
}
|
|
208
|
+
console.log(` Timestamp: ${problem.timestamp}`);
|
|
209
|
+
if (problem.metadata && Object.keys(problem.metadata).length > 0) {
|
|
210
|
+
console.log(` Metadata: ${JSON.stringify(problem.metadata)}`);
|
|
211
|
+
}
|
|
212
|
+
} else if (problem.typeName) {
|
|
213
|
+
const location = problem.location ?? "unknown";
|
|
214
|
+
console.log(` Type: ${problem.typeName} | Location: ${location}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (index < problems.length - 1) {
|
|
218
|
+
console.log("");
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const formatAsTable = (problems: ProblemEntry[]): string => {
|
|
224
|
+
if (problems.length === 0) {
|
|
225
|
+
return "No problems found.";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const headers = ["Severity", "Category", "Module", "Type", "Message"];
|
|
229
|
+
const rows = problems.map((p) => [
|
|
230
|
+
p.severity,
|
|
231
|
+
p.category,
|
|
232
|
+
p.module ?? "",
|
|
233
|
+
p.typeName ?? "",
|
|
234
|
+
p.message.length > 50 ? `${p.message.substring(0, 47)}...` : p.message,
|
|
235
|
+
]);
|
|
236
|
+
|
|
237
|
+
const columnWidths = headers.map((header, i) => Math.max(header.length, ...rows.map((row) => row[i].length)));
|
|
238
|
+
|
|
239
|
+
const separator = columnWidths.map((w) => "-".repeat(w)).join(" | ");
|
|
240
|
+
const headerRow = headers.map((h, i) => h.padEnd(columnWidths[i])).join(" | ");
|
|
241
|
+
const dataRows = rows.map((row) => row.map((cell, i) => cell.padEnd(columnWidths[i])).join(" | "));
|
|
242
|
+
|
|
243
|
+
return [headerRow, separator, ...dataRows].join("\n");
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const formatAsCsv = (problems: ProblemEntry[]): string => {
|
|
247
|
+
const headers = ["id", "severity", "category", "module", "typeName", "location", "message", "details", "timestamp"];
|
|
248
|
+
const rows = problems.map((p) => [
|
|
249
|
+
p.id,
|
|
250
|
+
p.severity,
|
|
251
|
+
p.category,
|
|
252
|
+
p.module ?? "",
|
|
253
|
+
p.typeName ?? "",
|
|
254
|
+
p.location ?? "",
|
|
255
|
+
`"${p.message.replace(/"/g, '""')}"`,
|
|
256
|
+
`"${(p.details ?? "").replace(/"/g, '""')}"`,
|
|
257
|
+
p.timestamp.toISOString(),
|
|
258
|
+
]);
|
|
259
|
+
|
|
260
|
+
return [headers.join(","), ...rows.map((row) => row.join(","))].join("\n");
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const exportResults = (problems: ProblemEntry[], filePath: string, format: string, logger: Logger): void => {
|
|
264
|
+
let content: string;
|
|
265
|
+
|
|
266
|
+
switch (format) {
|
|
267
|
+
case "json": {
|
|
268
|
+
content = JSON.stringify(problems, null, 2);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case "csv": {
|
|
272
|
+
content = formatAsCsv(problems);
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
case "table": {
|
|
276
|
+
content = formatAsTable(problems);
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
default: {
|
|
280
|
+
throw new Error(`Unsupported export format: ${format}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
writeFileSync(filePath, content, "utf-8");
|
|
285
|
+
logger.success(`Results exported to: ${filePath}`);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const handler = async (args: AnalyzeCommandArgs): Promise<void> => {
|
|
289
|
+
const logger = new Logger(args.verbose ?? false, "AnalyzeCommand");
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
// Load and parse report file
|
|
293
|
+
const report = loadReportFile(args.reportFile);
|
|
294
|
+
|
|
295
|
+
if (args.verbose) {
|
|
296
|
+
logger.info(`Loaded report with ${report.problems.length} problems`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Show summary if requested or if no specific filters are applied
|
|
300
|
+
const hasFilters = Boolean(args.severity || args.category || args.namespace || args.type || args.search);
|
|
301
|
+
|
|
302
|
+
if (args.summary || !hasFilters) {
|
|
303
|
+
displaySummary(report, args);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// If summary-only mode, stop here
|
|
307
|
+
if (args.summary) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Filter problems based on criteria
|
|
312
|
+
const filteredProblems = filterProblems(report.problems, args);
|
|
313
|
+
|
|
314
|
+
// Display filtered results
|
|
315
|
+
if (hasFilters || args.detailed) {
|
|
316
|
+
displayProblems(filteredProblems, args);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Export results if requested
|
|
320
|
+
if (args.export) {
|
|
321
|
+
const format = args.format ?? "json";
|
|
322
|
+
exportResults(filteredProblems, args.export, format, logger);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Show filter summary if filters were applied
|
|
326
|
+
if (hasFilters && !args.summary) {
|
|
327
|
+
console.log(
|
|
328
|
+
`\nš Filter Summary: Showing ${filteredProblems.length} of ${report.problems.length} total problems`,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
} catch (error) {
|
|
332
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
333
|
+
logger.error(`Analysis failed: ${errorMessage}`);
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
export const analyze = {
|
|
339
|
+
command,
|
|
340
|
+
description,
|
|
341
|
+
builder,
|
|
342
|
+
handler,
|
|
343
|
+
examples,
|
|
344
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper to build yargs commands with common structure
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ConfigFlags } from "@ts-for-gir/lib";
|
|
6
|
+
import type { Argv, BuilderCallback, Options } from "yargs";
|
|
7
|
+
|
|
8
|
+
export interface CommandDefinition {
|
|
9
|
+
command: string;
|
|
10
|
+
description: string;
|
|
11
|
+
builder: BuilderCallback<unknown, ConfigFlags>;
|
|
12
|
+
handler: (args: ConfigFlags) => Promise<void>;
|
|
13
|
+
examples: ReadonlyArray<[string, string?]>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a builder function for yargs commands
|
|
18
|
+
*/
|
|
19
|
+
export function createBuilder<TArgs>(
|
|
20
|
+
options: Record<string, Options>,
|
|
21
|
+
examples: ReadonlyArray<[string, string?]>,
|
|
22
|
+
): BuilderCallback<TArgs, ConfigFlags> {
|
|
23
|
+
return (yargs: Argv<TArgs>) => {
|
|
24
|
+
const optionNames = Object.keys(options);
|
|
25
|
+
for (const optionName of optionNames) {
|
|
26
|
+
yargs = yargs.option(optionName, options[optionName]);
|
|
27
|
+
}
|
|
28
|
+
return yargs.example(examples) as Argv<ConfigFlags>;
|
|
29
|
+
};
|
|
30
|
+
}
|