@ts-for-gir/cli 4.0.0-rc.4 → 4.0.0-rc.6

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.
@@ -11,7 +11,7 @@
11
11
  "clear": "rm -rf dist @types"
12
12
  },
13
13
  "devDependencies": {
14
- "@ts-for-gir/cli": "^4.0.0-rc.4",
14
+ "@ts-for-gir/cli": "^4.0.0-rc.6",
15
15
  "esbuild": "^0.28.0",
16
16
  "typescript": "^6.0.2"
17
17
  }
@@ -14,10 +14,10 @@
14
14
  "typescript": "^6.0.2"
15
15
  },
16
16
  "dependencies": {
17
- "@girs/adw-1": "^1.10.0-4.0.0-rc.4",
18
- "@girs/gio-2.0": "^2.88.0-4.0.0-rc.4",
19
- "@girs/gjs": "^4.0.0-rc.4",
20
- "@girs/glib-2.0": "^2.88.0-4.0.0-rc.4",
21
- "@girs/gtk-4.0": "^4.23.0-4.0.0-rc.4"
17
+ "@girs/adw-1": "^1.10.0-4.0.0-rc.5",
18
+ "@girs/gio-2.0": "^2.88.0-4.0.0-rc.5",
19
+ "@girs/gjs": "^4.0.0-rc.5",
20
+ "@girs/glib-2.0": "^2.88.0-4.0.0-rc.5",
21
+ "@girs/gtk-4.0": "^4.23.0-4.0.0-rc.5"
22
22
  }
23
23
  }
@@ -16,7 +16,7 @@
16
16
  "clear": "rm -rf @girs packages/*/dist"
17
17
  },
18
18
  "devDependencies": {
19
- "@ts-for-gir/cli": "^4.0.0-rc.4",
19
+ "@ts-for-gir/cli": "^4.0.0-rc.6",
20
20
  "typescript": "^6.0.2"
21
21
  }
22
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ts-for-gir/cli",
3
- "version": "4.0.0-rc.4",
3
+ "version": "4.0.0-rc.6",
4
4
  "description": "TypeScript type definition generator for GObject introspection GIR files",
5
5
  "main": "src/index.ts",
6
6
  "module": "src/index.ts",
@@ -55,31 +55,31 @@
55
55
  ".": "./src/index.ts"
56
56
  },
57
57
  "devDependencies": {
58
- "@gi.ts/parser": "^4.0.0-rc.4",
59
- "@ts-for-gir/generator-base": "^4.0.0-rc.4",
60
- "@ts-for-gir/generator-html-doc": "^4.0.0-rc.4",
61
- "@ts-for-gir/generator-json": "^4.0.0-rc.4",
62
- "@ts-for-gir/generator-typescript": "^4.0.0-rc.4",
63
- "@ts-for-gir/lib": "^4.0.0-rc.4",
64
- "@ts-for-gir/reporter": "^4.0.0-rc.4",
65
- "@ts-for-gir/tsconfig": "^4.0.0-rc.4",
58
+ "@gi.ts/parser": "^4.0.0-rc.6",
59
+ "@ts-for-gir/generator-base": "^4.0.0-rc.6",
60
+ "@ts-for-gir/generator-html-doc": "^4.0.0-rc.6",
61
+ "@ts-for-gir/generator-json": "^4.0.0-rc.6",
62
+ "@ts-for-gir/generator-typescript": "^4.0.0-rc.6",
63
+ "@ts-for-gir/lib": "^4.0.0-rc.6",
64
+ "@ts-for-gir/reporter": "^4.0.0-rc.6",
65
+ "@ts-for-gir/tsconfig": "^4.0.0-rc.6",
66
66
  "@types/ejs": "^3.1.5",
67
67
  "@types/inquirer": "^9.0.9",
68
- "@types/node": "^24.12.2",
68
+ "@types/node": "^25.6.0",
69
69
  "@types/yargs": "^17.0.35",
70
70
  "esbuild": "^0.28.0",
71
- "typescript": "^6.0.2"
71
+ "typescript": "^6.0.3"
72
72
  },
73
73
  "dependencies": {
74
- "@inquirer/prompts": "^8.3.2",
75
- "@ts-for-gir/templates": "^4.0.0-rc.4",
74
+ "@inquirer/prompts": "^8.4.2",
75
+ "@ts-for-gir/templates": "^4.0.0-rc.6",
76
76
  "colorette": "^2.0.20",
77
77
  "cosmiconfig": "^9.0.1",
78
- "ejs": "^5.0.1",
78
+ "ejs": "^5.0.2",
79
79
  "glob": "^13.0.6",
80
- "inquirer": "^13.3.2",
81
- "prettier": "^3.8.1",
82
- "typedoc": "^0.28.18",
80
+ "inquirer": "^13.4.2",
81
+ "prettier": "^3.8.3",
82
+ "typedoc": "^0.28.19",
83
83
  "yargs": "^18.0.0"
84
84
  }
85
85
  }
@@ -6,6 +6,7 @@ import {
6
6
  Logger,
7
7
  NSRegistry,
8
8
  ReporterService,
9
+ ResolveType,
9
10
  } from "@ts-for-gir/lib";
10
11
  import { getOptionsGeneration, load } from "../config.ts";
11
12
  import { GenerationHandler } from "../generation-handler.ts";
@@ -45,7 +46,11 @@ export async function runGenerationCommand(args: ConfigFlags, options: Generatio
45
46
 
46
47
  moduleLoader.parse(keep);
47
48
 
48
- const girModules = Array.from(keep).map((girModuleResolvedBy) => girModuleResolvedBy.module as GirModule);
49
+ // In external-deps mode, only generate the user-requested module(s). Transitively-loaded
50
+ // deps stay in the registry for type resolution but must not produce their own output.
51
+ const toGenerate = generateConfig.externalDeps ? keep.filter((m) => m.resolvedBy === ResolveType.BY_HAND) : keep;
52
+
53
+ const girModules = Array.from(toGenerate).map((girModuleResolvedBy) => girModuleResolvedBy.module as GirModule);
49
54
 
50
55
  await tsForGir.start(girModules);
51
56
  } catch (error) {
@@ -59,6 +59,24 @@ export function getOptionsGeneration(config: UserConfig): OptionsGeneration {
59
59
  return generateConfig;
60
60
  }
61
61
 
62
+ /**
63
+ * Parse `Namespace=npm-package` strings (from repeatable `--external-package` flag) into a
64
+ * map. Silently drops entries that don't contain `=`. Empty input returns undefined so the
65
+ * field stays absent in the merged config (rather than `{}`, which would shadow rc values).
66
+ */
67
+ function parseExternalPackagePairs(pairs: string[] | undefined): Record<string, string> | undefined {
68
+ if (!pairs || pairs.length === 0) return undefined;
69
+ const map: Record<string, string> = {};
70
+ for (const pair of pairs) {
71
+ const eq = pair.indexOf("=");
72
+ if (eq < 1) continue;
73
+ const ns = pair.slice(0, eq).trim();
74
+ const pkg = pair.slice(eq + 1).trim();
75
+ if (ns && pkg) map[ns] = pkg;
76
+ }
77
+ return Object.keys(map).length > 0 ? map : undefined;
78
+ }
79
+
62
80
  /**
63
81
  * Validate the configuration
64
82
  */
@@ -111,9 +129,21 @@ export async function load(cliOptions: ConfigFlags): Promise<UserConfig> {
111
129
  const configFile = await loadConfigFile(cliOptions.configName);
112
130
  const configFileData = configFile?.config || {};
113
131
 
132
+ // `--external-package GLib=@girs/glib-2.0` arrives as a string[]; collapse to Record.
133
+ // Drop the raw array so it doesn't pollute the merged UserConfig surface.
134
+ const externalPackagesFromCli = parseExternalPackagePairs(
135
+ (cliOptions as { externalPackage?: string[] }).externalPackage,
136
+ );
137
+ const { externalPackage: _externalPackage, ...cliOptionsClean } = cliOptions as ConfigFlags & {
138
+ externalPackage?: string[];
139
+ };
140
+
114
141
  const userConfig: UserConfig = {
115
- ...cliOptions,
142
+ ...cliOptionsClean,
116
143
  };
144
+ if (externalPackagesFromCli) {
145
+ userConfig.externalPackages = externalPackagesFromCli;
146
+ }
117
147
 
118
148
  if (configFileData) {
119
149
  // Boolean options — config file overrides CLI defaults
@@ -130,6 +160,8 @@ export async function load(cliOptions: ConfigFlags): Promise<UserConfig> {
130
160
  ["noAdvancedVariants", options.noAdvancedVariants.default],
131
161
  ["package", options.package.default],
132
162
  ["reporter", options.reporter.default],
163
+ ["externalDeps", options.externalDeps.default],
164
+ ["allowMissingDeps", options.allowMissingDeps.default],
133
165
  ["combined", docOptions.combined.default],
134
166
  ["merge", docOptions.merge.default],
135
167
  ];
@@ -153,7 +185,6 @@ export async function load(cliOptions: ConfigFlags): Promise<UserConfig> {
153
185
 
154
186
  // Array options — config file overrides CLI defaults
155
187
  const arrayKeys: Array<[keyof UserConfig, unknown]> = [
156
- ["girDirectories", options.girDirectories.default],
157
188
  ["ignore", options.ignore.default],
158
189
  ["modules", options.modules.default],
159
190
  ];
@@ -161,6 +192,18 @@ export async function load(cliOptions: ConfigFlags): Promise<UserConfig> {
161
192
  mergeConfigValue(userConfig, configFileData, key, defaultVal);
162
193
  }
163
194
 
195
+ // girDirectories: rc-file entries are prepended to the current dirs (CLI-provided or
196
+ // system defaults) rather than replacing them. This lets projects add local GIR dirs
197
+ // (e.g. a Vala build output) without having to enumerate all system paths in the rc.
198
+ // To use ONLY the specified dirs (no system fallback), pass --girDirectories on the CLI.
199
+ if (configFileData.girDirectories?.length) {
200
+ const current = userConfig.girDirectories as string[];
201
+ const toAdd = (configFileData.girDirectories as string[]).filter((d) => !current.includes(d));
202
+ if (toAdd.length > 0) {
203
+ userConfig.girDirectories = [...toAdd, ...current];
204
+ }
205
+ }
206
+
164
207
  // Special handling for root
165
208
  if (userConfig.root === options.root.default && (configFileData.root || configFile?.filepath)) {
166
209
  userConfig.root =
@@ -172,6 +215,11 @@ export async function load(cliOptions: ConfigFlags): Promise<UserConfig> {
172
215
  if (isDefaultOutdir && configFileData.outdir) {
173
216
  userConfig.outdir = userConfig.print ? null : configFileData.outdir;
174
217
  }
218
+
219
+ // externalPackages is a Record<string, string> in rc files; CLI overrides take precedence.
220
+ if (!externalPackagesFromCli && configFileData.externalPackages) {
221
+ userConfig.externalPackages = configFileData.externalPackages;
222
+ }
175
223
  }
176
224
 
177
225
  // Make paths absolute relative to root
@@ -186,6 +234,5 @@ export async function load(cliOptions: ConfigFlags): Promise<UserConfig> {
186
234
  if (userConfig.girDirectories) {
187
235
  userConfig.girDirectories = userConfig.girDirectories.map(resolveToRoot);
188
236
  }
189
-
190
237
  return validate(userConfig);
191
238
  }
@@ -31,6 +31,8 @@ export const defaults = {
31
31
  package: false,
32
32
  reporter: false,
33
33
  reporterOutput: "ts-for-gir-report.json",
34
+ externalDeps: false,
35
+ allowMissingDeps: false,
34
36
  combined: true,
35
37
  /** Default theme for `ts-for-gir doc` (HTML documentation). */
36
38
  theme: "gi-docgen",
@@ -146,6 +146,26 @@ export const options: { [name: string]: Options } = {
146
146
  default: defaults.reporterOutput,
147
147
  normalize: true,
148
148
  },
149
+ externalDeps: {
150
+ type: "boolean",
151
+ description:
152
+ "Emit imports from installed @girs/* npm packages instead of regenerating dep types. Implies single-file ambient .d.ts output. Designed for project-local Vala/C bridges. Strict by default — missing transitive dep GIRs abort the run; pass --allow-missing-deps to override.",
153
+ default: defaults.externalDeps,
154
+ normalize: true,
155
+ },
156
+ allowMissingDeps: {
157
+ type: "boolean",
158
+ description:
159
+ "In --external-deps mode, allow generation to proceed when app-specific transitive dep GIRs are missing (e.g. CI without libsoup3-devel). Note: GLib/Gio/GObject GIRs are still architecturally required for class-hierarchy resolution. Default strict behavior prevents silent type-quality drift between environments.",
160
+ default: defaults.allowMissingDeps,
161
+ normalize: true,
162
+ },
163
+ externalPackage: {
164
+ type: "string",
165
+ description:
166
+ "Override the default namespace→npm package mapping for --external-deps mode. Repeatable. Format: 'Namespace=@scope/pkg'. Example: --external-package Soup=@girs/soup-3.0",
167
+ array: true,
168
+ },
149
169
  };
150
170
 
151
171
  /**
@@ -173,6 +193,9 @@ export const generateOptions = {
173
193
  package: options.package,
174
194
  reporter: options.reporter,
175
195
  reporterOutput: options.reporterOutput,
196
+ externalDeps: options.externalDeps,
197
+ allowMissingDeps: options.allowMissingDeps,
198
+ externalPackage: options.externalPackage,
176
199
  };
177
200
 
178
201
  export const listOptions = {
@@ -168,7 +168,13 @@ export class ModuleLoader {
168
168
  const girModule = await this.loadAndCreateGirModule(dependency);
169
169
  if (!girModule) {
170
170
  if (!failedGirModules.has(dependency.packageName)) {
171
- this.log.warn(WARN_NO_GIR_FILE_FOUND_FOR_PACKAGE(dependency.packageName));
171
+ // In external-deps mode the strict check after loading turns missing
172
+ // transitive deps into a hard error; suppress the per-dep warn here so
173
+ // the user sees one consolidated error instead of a wall of warnings.
174
+ // `--allow-missing-deps` opts into the same suppression with a soft fail.
175
+ if (!this.config.externalDeps) {
176
+ this.log.warn(WARN_NO_GIR_FILE_FOUND_FOR_PACKAGE(dependency.packageName));
177
+ }
172
178
  failedGirModules.add(dependency.packageName);
173
179
  }
174
180
  } else if (girModule?.packageName) {
@@ -239,23 +245,49 @@ export class ModuleLoader {
239
245
 
240
246
  const dependencies = await this.fileFinder.girFilePathToDependencies(girFiles);
241
247
 
248
+ // Load GJS force-loads as DEPENDENCE so they are never treated as user-requested
249
+ // modules. This ensures --external-deps mode doesn't emit output files for them.
250
+ const { loaded: forceLoaded, failed: forceFailed } = await this.loadGirModules(
251
+ [GLib, Gio, GObject, Cairo],
252
+ ignore,
253
+ [],
254
+ ResolveType.DEPENDENCE,
255
+ );
256
+
257
+ // Load user-requested modules (and their transitive deps) starting from the
258
+ // already-loaded force-load registry so they are not double-loaded.
242
259
  const { loaded, failed } = await this.loadGirModules(
243
- [
244
- GLib,
245
- Gio,
246
- GObject,
247
- Cairo,
248
- ...dependencies.filter(
249
- (dep) =>
250
- dep.namespace !== "GLib" &&
251
- dep.namespace !== "Gio" &&
252
- dep.namespace !== "GObject" &&
253
- dep.namespace !== "cairo",
254
- ),
255
- ],
260
+ dependencies.filter(
261
+ (dep) =>
262
+ dep.namespace !== "GLib" &&
263
+ dep.namespace !== "Gio" &&
264
+ dep.namespace !== "GObject" &&
265
+ dep.namespace !== "cairo",
266
+ ),
256
267
  ignore,
268
+ forceLoaded,
269
+ ResolveType.BY_HAND,
270
+ forceFailed,
257
271
  );
258
272
 
273
+ // External-deps mode is strict by default: any transitive dep GIR that couldn't be
274
+ // loaded would silently degrade type quality. The hardcoded GLib/Gio/GObject/cairo
275
+ // force-loads above are exempt (they exist for runtime convenience, not because the
276
+ // input GIR references them).
277
+ if (this.config.externalDeps && !this.config.allowMissingDeps && failed.size > 0) {
278
+ const exempt = new Set(["GLib-2.0", "Gio-2.0", "GObject-2.0", "cairo-1.0"]);
279
+ const critical = Array.from(failed).filter((pkg) => !exempt.has(pkg));
280
+ if (critical.length > 0) {
281
+ throw new Error(
282
+ `Missing GIR files for transitive dependencies in --external-deps mode:\n` +
283
+ critical.map((pkg) => ` - ${pkg}`).join("\n") +
284
+ `\n\nInstall the corresponding -devel packages, add their directories to ` +
285
+ `--girDirectories, or pass --allow-missing-deps to generate anyway ` +
286
+ `(warning: degraded type quality).`,
287
+ );
288
+ }
289
+ }
290
+
259
291
  let keep: GirModuleResolvedBy[] = [];
260
292
  if (doNotAskForVersionOnConflict) {
261
293
  keep = loaded;
@@ -50,6 +50,12 @@ export interface GenerateCommandArgs extends BaseCommandArgs {
50
50
  reporter: boolean;
51
51
  /** Output file path for the reporter */
52
52
  reporterOutput: string;
53
+ /** Emit imports from installed @girs/* npm packages instead of regenerating dep types */
54
+ externalDeps: boolean;
55
+ /** Allow externalDeps generation when transitive dep GIRs are missing (degraded type quality) */
56
+ allowMissingDeps: boolean;
57
+ /** Override default namespace→npm package mapping. Repeatable. Format: `Namespace=@girs/pkg` */
58
+ externalPackage?: string[];
53
59
  }
54
60
 
55
61
  /**