@ts-for-gir/cli 4.0.0-beta.4 → 4.0.0-beta.41

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 (64) hide show
  1. package/README.md +307 -193
  2. package/bin/ts-for-gir +23851 -0
  3. package/bin/ts-for-gir-dev +43 -0
  4. package/package.json +39 -38
  5. package/src/commands/analyze.ts +344 -0
  6. package/src/commands/command-builder.ts +30 -0
  7. package/src/commands/copy.ts +71 -76
  8. package/src/commands/doc.ts +44 -47
  9. package/src/commands/generate.ts +35 -81
  10. package/src/commands/index.ts +6 -4
  11. package/src/commands/json.ts +43 -0
  12. package/src/commands/list.ts +71 -90
  13. package/src/commands/run-generation-command.ts +70 -0
  14. package/src/config/config-loader.ts +190 -0
  15. package/src/config/config-writer.ts +52 -0
  16. package/src/config/defaults.ts +66 -0
  17. package/src/config/index.ts +8 -0
  18. package/src/config/options.ts +315 -0
  19. package/src/config.ts +3 -450
  20. package/src/formatters/typescript-formatter.ts +24 -0
  21. package/src/generation-handler.ts +122 -67
  22. package/src/index.ts +4 -4
  23. package/src/module-loader/dependency-resolver.ts +100 -0
  24. package/src/module-loader/file-finder.ts +65 -0
  25. package/src/module-loader/index.ts +8 -0
  26. package/src/module-loader/module-grouper.ts +77 -0
  27. package/src/module-loader/prompt-handler.ts +111 -0
  28. package/src/module-loader.ts +289 -578
  29. package/src/start.ts +17 -14
  30. package/src/types/command-args.ts +118 -0
  31. package/src/types/command-definition.ts +17 -0
  32. package/src/types/commands.ts +30 -0
  33. package/src/types/index.ts +15 -0
  34. package/src/types/report-types.ts +34 -0
  35. package/lib/commands/copy.d.ts +0 -12
  36. package/lib/commands/copy.js +0 -80
  37. package/lib/commands/copy.js.map +0 -1
  38. package/lib/commands/doc.d.ts +0 -12
  39. package/lib/commands/doc.js +0 -40
  40. package/lib/commands/doc.js.map +0 -1
  41. package/lib/commands/generate.d.ts +0 -12
  42. package/lib/commands/generate.js +0 -73
  43. package/lib/commands/generate.js.map +0 -1
  44. package/lib/commands/index.d.ts +0 -4
  45. package/lib/commands/index.js +0 -5
  46. package/lib/commands/index.js.map +0 -1
  47. package/lib/commands/list.d.ts +0 -12
  48. package/lib/commands/list.js +0 -81
  49. package/lib/commands/list.js.map +0 -1
  50. package/lib/config.d.ts +0 -104
  51. package/lib/config.js +0 -408
  52. package/lib/config.js.map +0 -1
  53. package/lib/generation-handler.d.ts +0 -10
  54. package/lib/generation-handler.js +0 -47
  55. package/lib/generation-handler.js.map +0 -1
  56. package/lib/index.d.ts +0 -4
  57. package/lib/index.js +0 -5
  58. package/lib/index.js.map +0 -1
  59. package/lib/module-loader.d.ts +0 -148
  60. package/lib/module-loader.js +0 -468
  61. package/lib/module-loader.js.map +0 -1
  62. package/lib/start.d.ts +0 -2
  63. package/lib/start.js +0 -16
  64. package/lib/start.js.map +0 -1
@@ -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 { dirname, resolve } from "node:path";
23
+ import { fileURLToPath } from "node:url";
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,37 +1,33 @@
1
1
  {
2
2
  "name": "@ts-for-gir/cli",
3
- "version": "4.0.0-beta.4",
3
+ "version": "4.0.0-beta.41",
4
4
  "description": "TypeScript type definition generator for GObject introspection GIR files",
5
- "module": "lib/index.js",
6
- "main": "lib/index.js",
5
+ "main": "src/index.ts",
6
+ "module": "src/index.ts",
7
7
  "type": "module",
8
8
  "bin": {
9
- "ts-for-gir": "./lib/start.js"
9
+ "cli": "bin/ts-for-gir",
10
+ "ts-for-gir": "bin/ts-for-gir",
11
+ "ts-for-gir-dev": "bin/ts-for-gir-dev"
10
12
  },
11
13
  "engines": {
12
14
  "node": ">=18"
13
15
  },
14
16
  "scripts": {
15
- "start": "yarn node --max_old_space_size=9216 lib/start.js",
16
- "start:dev": "yarn node --max_old_space_size=9216 --loader ts-node/esm src/start.ts",
17
- "build": "yarn lint && yarn build:ts && yarn chmod",
18
- "chmod": "chmod +x ./lib/start.js",
19
- "build:ts": "tsc",
20
- "clear": "yarn clear:build",
21
- "clear:build": "rimraf ./lib",
22
- "watch": "yarn build:ts --watch",
23
- "lint": "eslint . --ext .ts,.tsx --fix"
17
+ "start": "node bin/ts-for-gir-dev",
18
+ "start:prod": "node bin/ts-for-gir",
19
+ "build": "node --experimental-specifier-resolution=node --experimental-strip-types --experimental-transform-types --no-warnings esbuild.ts && chmod +x bin/ts-for-gir-dev && chmod +x bin/ts-for-gir",
20
+ "check:types": "tsc --noEmit",
21
+ "check": "yarn check:types"
24
22
  },
25
23
  "repository": {
26
24
  "type": "git",
27
25
  "url": "git+https://github.com/gjsify/ts-for-gir.git"
28
26
  },
29
- "author": "Pascal Garber <pascal@artandcode.studio>",
27
+ "author": "Pascal Garber <pascal@mailfreun.de>",
30
28
  "files": [
31
29
  "src",
32
- "bin",
33
- "lib",
34
- "templates"
30
+ "bin"
35
31
  ],
36
32
  "license": "Apache-2.0",
37
33
  "bugs": {
@@ -52,30 +48,35 @@
52
48
  "type definitions",
53
49
  "cli"
54
50
  ],
51
+ "exports": {
52
+ ".": "./src/index.ts"
53
+ },
55
54
  "devDependencies": {
56
- "@types/inquirer": "^9.0.7",
57
- "@types/node": "^20.12.8",
58
- "@types/yargs": "^17.0.32",
59
- "@typescript-eslint/eslint-plugin": "^7.8.0",
60
- "@typescript-eslint/parser": "^7.8.0",
61
- "eslint": "^8.57.0",
62
- "eslint-config-prettier": "^9.1.0",
63
- "eslint-plugin-prettier": "^5.1.3",
64
- "prettier": "^3.2.5",
65
- "rimraf": "^5.0.5",
66
- "ts-node": "^10.9.2",
67
- "typescript": "^5.4.5"
55
+ "@gi.ts/parser": "^4.0.0-beta.41",
56
+ "@ts-for-gir/generator-base": "^4.0.0-beta.41",
57
+ "@ts-for-gir/generator-html-doc": "^4.0.0-beta.41",
58
+ "@ts-for-gir/generator-json": "^4.0.0-beta.41",
59
+ "@ts-for-gir/generator-typescript": "^4.0.0-beta.41",
60
+ "@ts-for-gir/lib": "^4.0.0-beta.41",
61
+ "@ts-for-gir/reporter": "^4.0.0-beta.41",
62
+ "@ts-for-gir/tsconfig": "^4.0.0-beta.41",
63
+ "@types/ejs": "^3.1.5",
64
+ "@types/inquirer": "^9.0.9",
65
+ "@types/node": "^24.12.0",
66
+ "@types/yargs": "^17.0.35",
67
+ "esbuild": "^0.27.4",
68
+ "typescript": "^5.9.3"
68
69
  },
69
70
  "dependencies": {
70
- "@gi.ts/parser": "^2.0.0",
71
- "@ts-for-gir/generator-base": "^4.0.0-beta.4",
72
- "@ts-for-gir/generator-html-doc": "^4.0.0-beta.4",
73
- "@ts-for-gir/generator-typescript": "^4.0.0-beta.4",
74
- "@ts-for-gir/lib": "^4.0.0-beta.4",
71
+ "@inquirer/prompts": "^8.3.2",
72
+ "@ts-for-gir/templates": "^4.0.0-beta.41",
75
73
  "colorette": "^2.0.20",
76
- "cosmiconfig": "^9.0.0",
77
- "glob": "^10.3.12",
78
- "inquirer": "^9.2.20",
79
- "yargs": "^17.7.2"
74
+ "cosmiconfig": "^9.0.1",
75
+ "ejs": "^5.0.1",
76
+ "glob": "^13.0.6",
77
+ "inquirer": "^13.3.2",
78
+ "prettier": "^3.8.1",
79
+ "typedoc": "^0.28.17",
80
+ "yargs": "^18.0.0"
80
81
  }
81
82
  }
@@ -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
+ }