@ts-for-gir/cli 4.0.0-rc.1 → 4.0.0-rc.12
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 +43 -3
- package/bin/ts-for-gir +2993 -727
- package/bin/ts-for-gir-gjs +296069 -0
- package/dist-templates/types-locally/.ts-for-girrc.js +6 -0
- package/dist-templates/types-locally/README.md +15 -0
- package/dist-templates/types-locally/esbuild.ts +10 -0
- package/dist-templates/types-locally/main.ts +21 -0
- package/dist-templates/types-locally/package.json +18 -0
- package/dist-templates/types-locally/tsconfig.json +17 -0
- package/dist-templates/types-npm/README.md +14 -0
- package/dist-templates/types-npm/esbuild.ts +10 -0
- package/dist-templates/types-npm/main.ts +19 -0
- package/dist-templates/types-npm/package.json +23 -0
- package/dist-templates/types-npm/tsconfig.json +15 -0
- package/dist-templates/types-workspace/.ts-for-girrc.js +12 -0
- package/dist-templates/types-workspace/README.md +26 -0
- package/dist-templates/types-workspace/package.json +22 -0
- package/dist-templates/types-workspace/packages/app/esbuild.ts +10 -0
- package/dist-templates/types-workspace/packages/app/main.ts +19 -0
- package/dist-templates/types-workspace/packages/app/package.json +23 -0
- package/dist-templates/types-workspace/packages/app/tsconfig.json +15 -0
- package/dist-templates/types-workspace/tsconfig.json +11 -0
- package/package.json +40 -19
- package/src/commands/create.ts +223 -0
- package/src/commands/index.ts +2 -0
- package/src/commands/run-generation-command.ts +6 -1
- package/src/commands/self-update.ts +142 -0
- package/src/config/config-loader.ts +51 -3
- package/src/config/defaults.ts +2 -0
- package/src/config/index.ts +9 -1
- package/src/config/options.ts +50 -0
- package/src/formatters/typescript-formatter.ts +9 -16
- package/src/module-loader.ts +46 -14
- package/src/start.ts +33 -15
- package/src/types/command-args.ts +36 -0
|
@@ -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
|
-
|
|
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) {
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything you need for the `ts-for-gir self-update` command is located here
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { chmodSync, existsSync, renameSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
import { APP_NAME, APP_VERSION } from "@ts-for-gir/lib";
|
|
10
|
+
|
|
11
|
+
import type { SelfUpdateCommandArgs } from "../types/index.ts";
|
|
12
|
+
|
|
13
|
+
const REPO = "gjsify/ts-for-gir";
|
|
14
|
+
const GITHUB_API = "https://api.github.com";
|
|
15
|
+
const GJS_ASSET_NAME = "ts-for-gir-gjs";
|
|
16
|
+
|
|
17
|
+
function getCurrentBinaryPath(): string | null {
|
|
18
|
+
const p = process.argv[1] ?? null;
|
|
19
|
+
if (!p) return null;
|
|
20
|
+
// Refuse to update in dev mode (source file or node_modules path)
|
|
21
|
+
if (p.endsWith(".ts") || p.includes("node_modules")) return null;
|
|
22
|
+
return p;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function fetchJson(url: string): Promise<unknown> {
|
|
26
|
+
const headers: Record<string, string> = {
|
|
27
|
+
Accept: "application/vnd.github.v3+json",
|
|
28
|
+
"User-Agent": `ts-for-gir/${APP_VERSION}`,
|
|
29
|
+
};
|
|
30
|
+
const token = process.env.GITHUB_TOKEN;
|
|
31
|
+
if (token) headers.Authorization = `token ${token}`;
|
|
32
|
+
|
|
33
|
+
const response = await fetch(url, { headers });
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`HTTP ${response.status} from ${url}`);
|
|
36
|
+
}
|
|
37
|
+
return response.json();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function downloadBinary(url: string, destPath: string): Promise<void> {
|
|
41
|
+
const response = await fetch(url, {
|
|
42
|
+
headers: { "User-Agent": `ts-for-gir/${APP_VERSION}` },
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new Error(`HTTP ${response.status} downloading binary`);
|
|
46
|
+
}
|
|
47
|
+
const buffer = await response.arrayBuffer();
|
|
48
|
+
const tmpPath = join(tmpdir(), `ts-for-gir-update-${Date.now()}`);
|
|
49
|
+
writeFileSync(tmpPath, Buffer.from(buffer));
|
|
50
|
+
chmodSync(tmpPath, 0o755);
|
|
51
|
+
renameSync(tmpPath, destPath); // atomic on POSIX
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const command = "self-update";
|
|
55
|
+
const description = "Update ts-for-gir to the latest version from GitHub releases";
|
|
56
|
+
const examples: ReadonlyArray<[string, string?]> = [
|
|
57
|
+
[`${APP_NAME} self-update`, "Check for updates and install the latest version"],
|
|
58
|
+
[`${APP_NAME} self-update --check`, "Only check for a newer version, do not install"],
|
|
59
|
+
[`${APP_NAME} self-update --force`, "Force reinstall even if already on the latest version"],
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const builder = (yargs: import("yargs").Argv) =>
|
|
63
|
+
yargs
|
|
64
|
+
.option("check", {
|
|
65
|
+
description: "Only check for a newer version, do not install",
|
|
66
|
+
type: "boolean",
|
|
67
|
+
default: false,
|
|
68
|
+
})
|
|
69
|
+
.option("force", {
|
|
70
|
+
description: "Force reinstall even if already on the latest version",
|
|
71
|
+
type: "boolean",
|
|
72
|
+
default: false,
|
|
73
|
+
})
|
|
74
|
+
.example(examples as [string, string?][]);
|
|
75
|
+
|
|
76
|
+
const handler = async (args: SelfUpdateCommandArgs): Promise<void> => {
|
|
77
|
+
console.log(`Checking for updates... (current: v${APP_VERSION})`);
|
|
78
|
+
|
|
79
|
+
let release: Record<string, unknown>;
|
|
80
|
+
try {
|
|
81
|
+
release = (await fetchJson(`${GITHUB_API}/repos/${REPO}/releases/latest`)) as Record<string, unknown>;
|
|
82
|
+
} catch (err) {
|
|
83
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
84
|
+
process.stderr.write(`Failed to fetch release information: ${msg}\n`);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const latestVersion = (release.tag_name as string).replace(/^v/, "");
|
|
90
|
+
|
|
91
|
+
if (latestVersion === APP_VERSION && !args.force) {
|
|
92
|
+
console.log(`Already up to date (v${APP_VERSION})`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (args.check) {
|
|
97
|
+
console.log(`New version available: v${latestVersion} (current: v${APP_VERSION})`);
|
|
98
|
+
console.log(`Run \`${APP_NAME} self-update\` to install it.`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(`Updating to v${latestVersion}...`);
|
|
103
|
+
|
|
104
|
+
const assets = release.assets as Array<Record<string, string>>;
|
|
105
|
+
const asset = assets.find((a) => a.name === GJS_ASSET_NAME);
|
|
106
|
+
if (!asset) {
|
|
107
|
+
process.stderr.write(
|
|
108
|
+
`No GJS binary found in release ${release.tag_name}.\n` +
|
|
109
|
+
"self-update requires the GJS bundle to be installed via install.js.\n" +
|
|
110
|
+
"For npm installations use: npm update -g @ts-for-gir/cli\n",
|
|
111
|
+
);
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const currentPath = getCurrentBinaryPath();
|
|
117
|
+
if (!currentPath || !existsSync(currentPath)) {
|
|
118
|
+
process.stderr.write(
|
|
119
|
+
"Cannot determine current binary path for update.\n" +
|
|
120
|
+
"self-update only works when running the installed GJS binary.\n",
|
|
121
|
+
);
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
await downloadBinary(asset.browser_download_url, currentPath);
|
|
128
|
+
console.log(`Successfully updated to v${latestVersion}`);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
131
|
+
process.stderr.write(`Update failed: ${msg}\n`);
|
|
132
|
+
process.exitCode = 1;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const selfUpdate = {
|
|
137
|
+
command,
|
|
138
|
+
description,
|
|
139
|
+
builder,
|
|
140
|
+
handler,
|
|
141
|
+
examples,
|
|
142
|
+
};
|
|
@@ -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
|
-
...
|
|
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
|
];
|
|
@@ -141,6 +173,7 @@ export async function load(cliOptions: ConfigFlags): Promise<UserConfig> {
|
|
|
141
173
|
const stringKeys: Array<[keyof UserConfig, unknown]> = [
|
|
142
174
|
["npmScope", options.npmScope.default],
|
|
143
175
|
["reporterOutput", options.reporterOutput.default],
|
|
176
|
+
["depVersionFormat", undefined],
|
|
144
177
|
["theme", docOptions.theme.default],
|
|
145
178
|
["sourceLinkTemplate", undefined],
|
|
146
179
|
["readme", undefined],
|
|
@@ -152,7 +185,6 @@ export async function load(cliOptions: ConfigFlags): Promise<UserConfig> {
|
|
|
152
185
|
|
|
153
186
|
// Array options — config file overrides CLI defaults
|
|
154
187
|
const arrayKeys: Array<[keyof UserConfig, unknown]> = [
|
|
155
|
-
["girDirectories", options.girDirectories.default],
|
|
156
188
|
["ignore", options.ignore.default],
|
|
157
189
|
["modules", options.modules.default],
|
|
158
190
|
];
|
|
@@ -160,6 +192,18 @@ export async function load(cliOptions: ConfigFlags): Promise<UserConfig> {
|
|
|
160
192
|
mergeConfigValue(userConfig, configFileData, key, defaultVal);
|
|
161
193
|
}
|
|
162
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
|
+
|
|
163
207
|
// Special handling for root
|
|
164
208
|
if (userConfig.root === options.root.default && (configFileData.root || configFile?.filepath)) {
|
|
165
209
|
userConfig.root =
|
|
@@ -171,6 +215,11 @@ export async function load(cliOptions: ConfigFlags): Promise<UserConfig> {
|
|
|
171
215
|
if (isDefaultOutdir && configFileData.outdir) {
|
|
172
216
|
userConfig.outdir = userConfig.print ? null : configFileData.outdir;
|
|
173
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
|
+
}
|
|
174
223
|
}
|
|
175
224
|
|
|
176
225
|
// Make paths absolute relative to root
|
|
@@ -185,6 +234,5 @@ export async function load(cliOptions: ConfigFlags): Promise<UserConfig> {
|
|
|
185
234
|
if (userConfig.girDirectories) {
|
|
186
235
|
userConfig.girDirectories = userConfig.girDirectories.map(resolveToRoot);
|
|
187
236
|
}
|
|
188
|
-
|
|
189
237
|
return validate(userConfig);
|
|
190
238
|
}
|
package/src/config/defaults.ts
CHANGED
|
@@ -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",
|
package/src/config/index.ts
CHANGED
|
@@ -5,4 +5,12 @@
|
|
|
5
5
|
export { getOptionsGeneration, load, validate } from "./config-loader.ts";
|
|
6
6
|
export { addToConfig, configFilePath } from "./config-writer.ts";
|
|
7
7
|
export { defaults } from "./defaults.ts";
|
|
8
|
-
export {
|
|
8
|
+
export {
|
|
9
|
+
analyzeOptions,
|
|
10
|
+
copyOptions,
|
|
11
|
+
createOptions,
|
|
12
|
+
docOptions,
|
|
13
|
+
generateOptions,
|
|
14
|
+
listOptions,
|
|
15
|
+
options,
|
|
16
|
+
} from "./options.ts";
|
package/src/config/options.ts
CHANGED
|
@@ -103,6 +103,12 @@ export const options: { [name: string]: Options } = {
|
|
|
103
103
|
default: defaults.workspace,
|
|
104
104
|
normalize: true,
|
|
105
105
|
},
|
|
106
|
+
depVersionFormat: {
|
|
107
|
+
type: "string",
|
|
108
|
+
description:
|
|
109
|
+
"Dependency version spec format in generated package.json files. Defaults to 'workspace' when --workspace, else 'exact'",
|
|
110
|
+
choices: ["workspace", "caret", "any", "exact"] as const,
|
|
111
|
+
},
|
|
106
112
|
onlyVersionPrefix: {
|
|
107
113
|
type: "boolean",
|
|
108
114
|
description:
|
|
@@ -140,6 +146,26 @@ export const options: { [name: string]: Options } = {
|
|
|
140
146
|
default: defaults.reporterOutput,
|
|
141
147
|
normalize: true,
|
|
142
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
|
+
},
|
|
143
169
|
};
|
|
144
170
|
|
|
145
171
|
/**
|
|
@@ -160,12 +186,16 @@ export const generateOptions = {
|
|
|
160
186
|
promisify: options.promisify,
|
|
161
187
|
npmScope: options.npmScope,
|
|
162
188
|
workspace: options.workspace,
|
|
189
|
+
depVersionFormat: options.depVersionFormat,
|
|
163
190
|
onlyVersionPrefix: options.onlyVersionPrefix,
|
|
164
191
|
noPrettyPrint: options.noPrettyPrint,
|
|
165
192
|
noAdvancedVariants: options.noAdvancedVariants,
|
|
166
193
|
package: options.package,
|
|
167
194
|
reporter: options.reporter,
|
|
168
195
|
reporterOutput: options.reporterOutput,
|
|
196
|
+
externalDeps: options.externalDeps,
|
|
197
|
+
allowMissingDeps: options.allowMissingDeps,
|
|
198
|
+
externalPackage: options.externalPackage,
|
|
169
199
|
};
|
|
170
200
|
|
|
171
201
|
export const listOptions = {
|
|
@@ -223,6 +253,26 @@ export const docOptions = {
|
|
|
223
253
|
},
|
|
224
254
|
};
|
|
225
255
|
|
|
256
|
+
export const createOptions = {
|
|
257
|
+
template: {
|
|
258
|
+
type: "string" as const,
|
|
259
|
+
alias: "t",
|
|
260
|
+
description: "Template to scaffold (types-locally, types-npm, types-workspace)",
|
|
261
|
+
choices: ["types-locally", "types-npm", "types-workspace"] as const,
|
|
262
|
+
},
|
|
263
|
+
install: {
|
|
264
|
+
type: "boolean" as const,
|
|
265
|
+
description: "Run npm install after scaffolding (use --no-install to skip)",
|
|
266
|
+
default: true,
|
|
267
|
+
},
|
|
268
|
+
force: {
|
|
269
|
+
type: "boolean" as const,
|
|
270
|
+
description: "Allow scaffolding into a non-empty target directory",
|
|
271
|
+
default: false,
|
|
272
|
+
},
|
|
273
|
+
verbose: options.verbose,
|
|
274
|
+
};
|
|
275
|
+
|
|
226
276
|
export const analyzeOptions = {
|
|
227
277
|
reportFile: {
|
|
228
278
|
type: "string" as const,
|
|
@@ -1,24 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TypeScript formatter
|
|
2
|
+
* TypeScript formatter — pass-through.
|
|
3
|
+
*
|
|
4
|
+
* The generator's templates and string-builders emit the desired output
|
|
5
|
+
* shape directly; we no longer round-trip through Prettier. This shrinks
|
|
6
|
+
* the generation pipeline (no AST parse + reprint per file), drops a
|
|
7
|
+
* heavy bundler-hostile dependency for non-Node consumers, and makes
|
|
8
|
+
* generated output deterministic without an external formatter.
|
|
3
9
|
*/
|
|
4
10
|
|
|
5
|
-
import { Formatter
|
|
6
|
-
import prettier from "prettier";
|
|
7
|
-
|
|
8
|
-
const logger = new Logger(false, "TypeScriptFormatter");
|
|
11
|
+
import { Formatter } from "@ts-for-gir/lib";
|
|
9
12
|
|
|
10
13
|
export class TypeScriptFormatter extends Formatter {
|
|
11
14
|
format(input: string): Promise<string> {
|
|
12
|
-
|
|
13
|
-
return prettier.format(input, {
|
|
14
|
-
singleQuote: true,
|
|
15
|
-
parser: "typescript",
|
|
16
|
-
printWidth: 120,
|
|
17
|
-
tabWidth: 4,
|
|
18
|
-
});
|
|
19
|
-
} catch (error) {
|
|
20
|
-
logger.warn("Failed to format with prettier, returning original input", error);
|
|
21
|
-
return Promise.resolve(input);
|
|
22
|
-
}
|
|
15
|
+
return Promise.resolve(input);
|
|
23
16
|
}
|
|
24
17
|
}
|
package/src/module-loader.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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;
|
package/src/start.ts
CHANGED
|
@@ -2,19 +2,37 @@ import { APP_NAME, APP_USAGE, APP_VERSION } from "@ts-for-gir/lib";
|
|
|
2
2
|
import yargs, { type CommandModule } from "yargs";
|
|
3
3
|
import { hideBin } from "yargs/helpers";
|
|
4
4
|
|
|
5
|
-
import { analyze, copy, doc, generate, json, list } from "./commands/index.ts";
|
|
5
|
+
import { analyze, copy, create, doc, generate, json, list, selfUpdate } from "./commands/index.ts";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
7
|
+
try {
|
|
8
|
+
await yargs(hideBin(process.argv))
|
|
9
|
+
.scriptName(APP_NAME)
|
|
10
|
+
.strict()
|
|
11
|
+
.usage(APP_USAGE)
|
|
12
|
+
.version(APP_VERSION)
|
|
13
|
+
// Disable yargs's internal `process.exit` and route both success
|
|
14
|
+
// and failure through `parseAsync` + an explicit `process.exit` so
|
|
15
|
+
// async command handlers complete and stdout drains before the
|
|
16
|
+
// runtime (Node or the gjsify Node-compat loader on GJS, which
|
|
17
|
+
// keeps a GLib main loop alive that would otherwise prevent the
|
|
18
|
+
// process from exiting after main() returns) tears down.
|
|
19
|
+
.exitProcess(false)
|
|
20
|
+
.fail(false)
|
|
21
|
+
// TODO: Fix this
|
|
22
|
+
.command(analyze as unknown as CommandModule)
|
|
23
|
+
.command(create as unknown as CommandModule)
|
|
24
|
+
.command(generate as unknown as CommandModule)
|
|
25
|
+
.command(json as unknown as CommandModule)
|
|
26
|
+
.command(list as unknown as CommandModule)
|
|
27
|
+
.command(copy as unknown as CommandModule)
|
|
28
|
+
.command(doc as unknown as CommandModule)
|
|
29
|
+
.command(selfUpdate as unknown as CommandModule)
|
|
30
|
+
.demandCommand(1)
|
|
31
|
+
.help()
|
|
32
|
+
.parseAsync();
|
|
33
|
+
process.exit(0);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
36
|
+
process.stderr.write(`${message}\n`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
@@ -36,6 +36,8 @@ export interface GenerateCommandArgs extends BaseCommandArgs {
|
|
|
36
36
|
npmScope: string;
|
|
37
37
|
/** Uses the workspace protocol for the generated packages which can be used with package managers like Yarn and PNPM */
|
|
38
38
|
workspace: boolean;
|
|
39
|
+
/** Dependency version spec format in generated package.json files */
|
|
40
|
+
depVersionFormat?: "workspace" | "caret" | "any" | "exact";
|
|
39
41
|
/** Only use the version prefix for the ambient module exports */
|
|
40
42
|
onlyVersionPrefix: boolean;
|
|
41
43
|
/** Do not prettify the generated types */
|
|
@@ -48,6 +50,12 @@ export interface GenerateCommandArgs extends BaseCommandArgs {
|
|
|
48
50
|
reporter: boolean;
|
|
49
51
|
/** Output file path for the reporter */
|
|
50
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[];
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
/**
|
|
@@ -83,6 +91,27 @@ export interface DocCommandArgs extends GenerateCommandArgs {
|
|
|
83
91
|
jsonDir?: string;
|
|
84
92
|
}
|
|
85
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Available scaffolding template identifiers for the create command.
|
|
96
|
+
*/
|
|
97
|
+
export type CreateTemplateId = "types-locally" | "types-npm" | "types-workspace";
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Arguments for the create command
|
|
101
|
+
*/
|
|
102
|
+
export interface CreateCommandArgs {
|
|
103
|
+
/** Project name and target directory (positional) */
|
|
104
|
+
name?: string;
|
|
105
|
+
/** Template identifier */
|
|
106
|
+
template?: CreateTemplateId;
|
|
107
|
+
/** Run npm install after scaffolding (use --no-install to skip) */
|
|
108
|
+
install: boolean;
|
|
109
|
+
/** Allow scaffolding into a non-empty target directory */
|
|
110
|
+
force: boolean;
|
|
111
|
+
/** Switch on/off the verbose mode */
|
|
112
|
+
verbose: boolean;
|
|
113
|
+
}
|
|
114
|
+
|
|
86
115
|
/**
|
|
87
116
|
* Arguments for the analyze command
|
|
88
117
|
*/
|
|
@@ -116,3 +145,10 @@ export interface AnalyzeCommandArgs {
|
|
|
116
145
|
/** Switch on/off the verbose mode */
|
|
117
146
|
verbose?: boolean;
|
|
118
147
|
}
|
|
148
|
+
|
|
149
|
+
export interface SelfUpdateCommandArgs {
|
|
150
|
+
/** Only check for a newer version, do not install */
|
|
151
|
+
check: boolean;
|
|
152
|
+
/** Force reinstall even if already on the latest version */
|
|
153
|
+
force: boolean;
|
|
154
|
+
}
|