@ts-for-gir/cli 4.0.0-rc.8 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +43 -3
  2. package/bin/ts-for-gir +458 -130
  3. package/bin/ts-for-gir-gjs +1213 -0
  4. package/package.json +194 -83
  5. package/src/commands/create.ts +84 -12
  6. package/src/commands/index.ts +1 -0
  7. package/src/commands/self-update.ts +142 -0
  8. package/src/config/config-loader.ts +6 -2
  9. package/src/config/defaults.ts +14 -0
  10. package/src/config/options.ts +2 -2
  11. package/src/start.ts +20 -6
  12. package/src/types/command-args.ts +8 -1
  13. package/dist-templates/types-locally/.ts-for-girrc.js +0 -6
  14. package/dist-templates/types-locally/README.md +0 -15
  15. package/dist-templates/types-locally/esbuild.ts +0 -10
  16. package/dist-templates/types-locally/main.ts +0 -21
  17. package/dist-templates/types-locally/package.json +0 -18
  18. package/dist-templates/types-locally/tsconfig.json +0 -17
  19. package/dist-templates/types-npm/README.md +0 -14
  20. package/dist-templates/types-npm/esbuild.ts +0 -10
  21. package/dist-templates/types-npm/main.ts +0 -19
  22. package/dist-templates/types-npm/package.json +0 -23
  23. package/dist-templates/types-npm/tsconfig.json +0 -15
  24. package/dist-templates/types-workspace/.ts-for-girrc.js +0 -12
  25. package/dist-templates/types-workspace/README.md +0 -26
  26. package/dist-templates/types-workspace/package.json +0 -22
  27. package/dist-templates/types-workspace/packages/app/esbuild.ts +0 -10
  28. package/dist-templates/types-workspace/packages/app/main.ts +0 -19
  29. package/dist-templates/types-workspace/packages/app/package.json +0 -23
  30. package/dist-templates/types-workspace/packages/app/tsconfig.json +0 -15
  31. package/dist-templates/types-workspace/tsconfig.json +0 -11
package/package.json CHANGED
@@ -1,84 +1,195 @@
1
1
  {
2
- "name": "@ts-for-gir/cli",
3
- "version": "4.0.0-rc.8",
4
- "description": "TypeScript type definition generator for GObject introspection GIR files",
5
- "main": "src/index.ts",
6
- "module": "src/index.ts",
7
- "type": "module",
8
- "bin": {
9
- "cli": "bin/ts-for-gir",
10
- "ts-for-gir": "bin/ts-for-gir",
11
- "ts-for-gir-dev": "bin/ts-for-gir-dev"
12
- },
13
- "engines": {
14
- "node": ">=18"
15
- },
16
- "scripts": {
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
- "build:templates": "node scripts/process-templates.mjs",
21
- "prepack": "node scripts/process-templates.mjs",
22
- "check:types": "tsc --noEmit",
23
- "check": "yarn check:types"
24
- },
25
- "repository": {
26
- "type": "git",
27
- "url": "git+https://github.com/gjsify/ts-for-gir.git"
28
- },
29
- "author": "Pascal Garber <pascal@mailfreun.de>",
30
- "files": [
31
- "src",
32
- "bin",
33
- "dist-templates"
34
- ],
35
- "license": "Apache-2.0",
36
- "bugs": {
37
- "url": "https://github.com/gjsify/ts-for-gir/issues"
38
- },
39
- "homepage": "https://github.com/gjsify/ts-for-gir#readme",
40
- "keywords": [
41
- "gjs",
42
- "typescript",
43
- "generate",
44
- "gir",
45
- "gobject-introspection",
46
- "gnome",
47
- "gtk",
48
- "glib",
49
- "gobject",
50
- "dts",
51
- "type definitions",
52
- "cli"
53
- ],
54
- "exports": {
55
- ".": "./src/index.ts"
56
- },
57
- "devDependencies": {
58
- "@gi.ts/parser": "^4.0.0-rc.8",
59
- "@ts-for-gir/generator-base": "^4.0.0-rc.8",
60
- "@ts-for-gir/generator-html-doc": "^4.0.0-rc.8",
61
- "@ts-for-gir/generator-json": "^4.0.0-rc.8",
62
- "@ts-for-gir/generator-typescript": "^4.0.0-rc.8",
63
- "@ts-for-gir/lib": "^4.0.0-rc.8",
64
- "@ts-for-gir/reporter": "^4.0.0-rc.8",
65
- "@ts-for-gir/tsconfig": "^4.0.0-rc.8",
66
- "@types/ejs": "^3.1.5",
67
- "@types/inquirer": "^9.0.9",
68
- "@types/node": "^25.6.0",
69
- "@types/yargs": "^17.0.35",
70
- "esbuild": "^0.28.0",
71
- "typescript": "^6.0.3"
72
- },
73
- "dependencies": {
74
- "@inquirer/prompts": "^8.4.2",
75
- "@ts-for-gir/templates": "^4.0.0-rc.8",
76
- "colorette": "^2.0.20",
77
- "cosmiconfig": "^9.0.1",
78
- "ejs": "^5.0.2",
79
- "glob": "^13.0.6",
80
- "inquirer": "^13.4.2",
81
- "typedoc": "^0.28.19",
82
- "yargs": "^18.0.0"
83
- }
84
- }
2
+ "name": "@ts-for-gir/cli",
3
+ "version": "4.0.0",
4
+ "description": "TypeScript type definition generator for GObject introspection GIR files",
5
+ "main": "src/index.ts",
6
+ "module": "src/index.ts",
7
+ "type": "module",
8
+ "bin": {
9
+ "cli": "bin/ts-for-gir",
10
+ "ts-for-gir": "bin/ts-for-gir",
11
+ "ts-for-gir-dev": "bin/ts-for-gir-dev",
12
+ "ts-for-gir-gjs": "bin/ts-for-gir-gjs"
13
+ },
14
+ "gjsify": {
15
+ "bin": {
16
+ "ts-for-gir": "bin/ts-for-gir-gjs"
17
+ },
18
+ "shebang": true,
19
+ "bundler": {
20
+ "input": "src/start.ts",
21
+ "output": {
22
+ "file": "bin/ts-for-gir-gjs"
23
+ },
24
+ "transform": {
25
+ "define": {
26
+ "__GJS_BUNDLE__": "true"
27
+ }
28
+ }
29
+ },
30
+ "defineFromPackageJson": {
31
+ "__TS_FOR_GIR_VERSION__": {
32
+ "field": "version"
33
+ }
34
+ },
35
+ "excludeGlobals": [
36
+ "XMLHttpRequest",
37
+ "XMLHttpRequestUpload"
38
+ ],
39
+ "flatpak": {
40
+ "appId": "io.github.gjsify.ts_for_gir",
41
+ "kind": "cli",
42
+ "name": "ts-for-gir",
43
+ "runtime": "gnome",
44
+ "runtimeVersion": "50",
45
+ "command": "ts-for-gir",
46
+ "finishArgs": [
47
+ "--share=network",
48
+ "--filesystem=host"
49
+ ],
50
+ "developer": {
51
+ "id": "io.github.gjsify",
52
+ "name": "gjsify contributors",
53
+ "email": "pascal@artandcode.studio"
54
+ },
55
+ "summary": "TypeScript type definitions for GObject Introspection (GJS)",
56
+ "description": [
57
+ {
58
+ "p": "ts-for-gir reads GObject Introspection (GIR) XML files and emits strongly-typed TypeScript definitions for use in GJS (GNOME JavaScript) projects. Type-check your GTK / Adwaita / GLib / Gio / GStreamer / WebKit / etc. code, get full IDE completion, and catch missing properties / wrong-arity signal handlers at build time instead of runtime."
59
+ },
60
+ {
61
+ "p": "This Flatpak ships ts-for-gir as a self-contained CLI runnable on any modern Linux distro — no Node.js installation required. It reads from the system's installed GIR catalog (read-only mounts of /usr/share/gir-1.0 and /usr/share/gobject-introspection-1.0) and writes generated types under your project directory."
62
+ },
63
+ {
64
+ "ul": [
65
+ {
66
+ "item": "`ts-for-gir generate Gtk-4.0` — generate types for one or more GI namespaces"
67
+ },
68
+ {
69
+ "item": "`ts-for-gir generate --reporter` — write a JSON report alongside types for downstream analysis"
70
+ },
71
+ {
72
+ "item": "`ts-for-gir analyze report.json` — inspect type-resolution issues by severity / category / namespace"
73
+ },
74
+ {
75
+ "item": "`ts-for-gir create my-app` — scaffold a new GJS app from a template"
76
+ },
77
+ {
78
+ "item": "`ts-for-gir list` — list all GIR namespaces available on this system"
79
+ },
80
+ {
81
+ "item": "`ts-for-gir self-update` — refresh the global install in place"
82
+ }
83
+ ]
84
+ }
85
+ ],
86
+ "license": {
87
+ "metadata": "CC0-1.0",
88
+ "project": "Apache-2.0"
89
+ },
90
+ "categories": [
91
+ "Development"
92
+ ],
93
+ "homepageUrl": "https://gjsify.github.io/gjsify/projects/ts-for-gir/",
94
+ "vcsBrowserUrl": "https://github.com/gjsify/ts-for-gir",
95
+ "issueTrackerUrl": "https://github.com/gjsify/ts-for-gir/issues",
96
+ "modules": [
97
+ {
98
+ "name": "ts-for-gir-cli",
99
+ "buildsystem": "simple",
100
+ "build-commands": [
101
+ "install -Dm755 package/bin/ts-for-gir-gjs /app/share/ts-for-gir/ts-for-gir.gjs.mjs",
102
+ "install -Dm755 launcher.sh /app/bin/ts-for-gir"
103
+ ],
104
+ "sources": [
105
+ {
106
+ "type": "archive",
107
+ "url": "https://registry.npmjs.org/@ts-for-gir/cli/-/cli-4.0.0-rc.17.tgz",
108
+ "sha256": "6ca2c7c73bbc37259ebec621f1b24f8e9ca896426783114e80faa59cb4983afe",
109
+ "strip-components": 0,
110
+ "dest-filename": "tarball.tgz"
111
+ },
112
+ {
113
+ "type": "file",
114
+ "path": "launcher.sh"
115
+ }
116
+ ]
117
+ }
118
+ ]
119
+ }
120
+ },
121
+ "engines": {
122
+ "node": ">=18"
123
+ },
124
+ "scripts": {
125
+ "start": "node bin/ts-for-gir-dev",
126
+ "start:prod": "node bin/ts-for-gir",
127
+ "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",
128
+ "build:gjs": "gjsify build",
129
+ "build:templates": "node scripts/process-templates.mjs",
130
+ "prepack": "node scripts/process-templates.mjs",
131
+ "check:types": "tsc --noEmit",
132
+ "check": "gjsify run check:types"
133
+ },
134
+ "repository": {
135
+ "type": "git",
136
+ "url": "git+https://github.com/gjsify/ts-for-gir.git"
137
+ },
138
+ "author": "Pascal Garber <pascal@mailfreun.de>",
139
+ "files": [
140
+ "src",
141
+ "bin",
142
+ "dist-templates"
143
+ ],
144
+ "license": "Apache-2.0",
145
+ "bugs": {
146
+ "url": "https://github.com/gjsify/ts-for-gir/issues"
147
+ },
148
+ "homepage": "https://github.com/gjsify/ts-for-gir#readme",
149
+ "keywords": [
150
+ "gjs",
151
+ "typescript",
152
+ "generate",
153
+ "gir",
154
+ "gobject-introspection",
155
+ "gnome",
156
+ "gtk",
157
+ "glib",
158
+ "gobject",
159
+ "dts",
160
+ "type definitions",
161
+ "cli"
162
+ ],
163
+ "exports": {
164
+ ".": "./src/index.ts"
165
+ },
166
+ "devDependencies": {
167
+ "@gi.ts/parser": "^4.0.0",
168
+ "@gjsify/cli": "^0.4.19",
169
+ "@ts-for-gir/generator-base": "^4.0.0",
170
+ "@ts-for-gir/generator-html-doc": "^4.0.0",
171
+ "@ts-for-gir/generator-json": "^4.0.0",
172
+ "@ts-for-gir/generator-typescript": "^4.0.0",
173
+ "@ts-for-gir/lib": "^4.0.0",
174
+ "@ts-for-gir/reporter": "^4.0.0",
175
+ "@ts-for-gir/tsconfig": "^4.0.0",
176
+ "@types/ejs": "^3.1.5",
177
+ "@types/inquirer": "^9.0.9",
178
+ "@types/node": "^25.6.2",
179
+ "@types/yargs": "^17.0.35",
180
+ "esbuild": "^0.28.0",
181
+ "source-map-support": "^0.5.21",
182
+ "typescript": "^6.0.3"
183
+ },
184
+ "dependencies": {
185
+ "@inquirer/prompts": "^8.4.2",
186
+ "@ts-for-gir/templates": "^4.0.0",
187
+ "colorette": "^2.0.20",
188
+ "cosmiconfig": "^9.0.1",
189
+ "ejs": "^5.0.2",
190
+ "glob": "^13.0.6",
191
+ "inquirer": "^13.4.2",
192
+ "typedoc": "^0.28.19",
193
+ "yargs": "^18.0.0"
194
+ }
195
+ }
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { spawnSync } from "node:child_process";
6
- import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
7
7
  import { dirname, join, resolve } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
 
@@ -19,7 +19,8 @@ const command = "create [name]";
19
19
  const description = "Scaffold a new GJS TypeScript project from a template";
20
20
 
21
21
  const examples: ReadonlyArray<[string, string?]> = [
22
- [`${APP_NAME} create my-app --template types-npm`, "Scaffold using the @girs/* NPM types"],
22
+ [`${APP_NAME} create my-app --template types-gjsify`, "Scaffold a Node-free GJS app via gjsify (recommended)"],
23
+ [`${APP_NAME} create my-app --template types-npm`, "Scaffold using the @girs/* NPM types + node/esbuild"],
23
24
  [`${APP_NAME} create my-app --template types-locally`, "Scaffold and generate types into ./@types/ locally"],
24
25
  [
25
26
  `${APP_NAME} create my-app --template types-workspace`,
@@ -30,15 +31,20 @@ const examples: ReadonlyArray<[string, string?]> = [
30
31
 
31
32
  const TEMPLATE_CHOICES: ReadonlyArray<{ value: CreateTemplateId; name: string; description: string }> = [
32
33
  {
33
- value: "types-locally",
34
- name: "types-locally",
35
- description: "Generate GIR types directly into ./@types/ (no package format, no @girs/* deps)",
34
+ value: "types-gjsify",
35
+ name: "types-gjsify",
36
+ description: "Node-free: @girs/* from NPM, all dev scripts (install/build/run/format) routed through gjsify",
36
37
  },
37
38
  {
38
39
  value: "types-npm",
39
40
  name: "types-npm",
40
41
  description: "Use pre-generated types from the @girs/* NPM packages",
41
42
  },
43
+ {
44
+ value: "types-locally",
45
+ name: "types-locally",
46
+ description: "Generate GIR types directly into ./@types/ (no package format, no @girs/* deps)",
47
+ },
42
48
  {
43
49
  value: "types-workspace",
44
50
  name: "types-workspace",
@@ -55,18 +61,44 @@ const builder = createBuilder<CreateCommandArgs>(createOptions, examples);
55
61
  function findTemplatesRoot(): string {
56
62
  const __filename = fileURLToPath(import.meta.url);
57
63
  const __dirname = dirname(__filename);
64
+ // Resolve symlinks too. When the CLI is installed via `npm i -g`,
65
+ // `gjsify install -g`, or any tool that lands a symlink in the user's PATH
66
+ // (Yarn Berry's bin links, asdf shims, ...), `import.meta.url` resolves to
67
+ // the symlink path — `<dir>/../dist-templates` would then look for the
68
+ // templates next to the symlink (e.g. `~/.local/bin/../dist-templates`)
69
+ // instead of next to the real package. Resolving via realpath first lets
70
+ // the same `<dir>/../dist-templates` candidate hit the actual install dir.
71
+ let realDirname = __dirname;
72
+ try {
73
+ realDirname = dirname(realpathSync(__filename));
74
+ } catch {
75
+ // `realpathSync` can throw on bundled paths that don't exist on disk
76
+ // (e.g. virtual entries in a single-file binary). Fall through to the
77
+ // direct __dirname — the candidate list below is a superset and will
78
+ // still find templates when they live alongside the bundle.
79
+ }
58
80
  const candidates = [
59
- // Bundled production binary (bin/ts-for-gir): ../dist-templates
81
+ // Symlink-resolved binary (`npm i -g` / `gjsify install -g`): the
82
+ // realpath-aware candidate is required when the CLI is launched via a
83
+ // `~/.local/bin/<name>` symlink that points at the real package's
84
+ // `bin/<name>`. Try it first so the success path is symmetric across
85
+ // install modes.
86
+ resolve(realDirname, "..", "dist-templates"),
87
+ // Bundled production binary invoked at its real path (no symlink),
88
+ // or a tarball extracted into a flat `bin/` + `dist-templates/` layout.
60
89
  resolve(__dirname, "..", "dist-templates"),
61
90
  // Source layout (src/commands/create.ts): ../../dist-templates then ../../templates
62
91
  resolve(__dirname, "..", "..", "dist-templates"),
63
92
  resolve(__dirname, "..", "..", "templates"),
64
93
  ];
94
+ const seen = new Set<string>();
65
95
  for (const path of candidates) {
96
+ if (seen.has(path)) continue;
97
+ seen.add(path);
66
98
  if (existsSync(path)) return path;
67
99
  }
68
100
  throw new Error(
69
- `Could not locate templates directory. Looked in:\n ${candidates.join("\n ")}\n` +
101
+ `Could not locate templates directory. Looked in:\n ${[...seen].join("\n ")}\n` +
70
102
  "If you are running from source, make sure packages/cli/templates/ exists. " +
71
103
  "If you are running the published CLI, make sure dist-templates/ was packed.",
72
104
  );
@@ -119,11 +151,36 @@ function walkAndSubstitute(rootDir: string, projectName: string): void {
119
151
  }
120
152
  }
121
153
 
154
+ declare const __GJS_BUNDLE__: boolean | undefined;
155
+
122
156
  const handler = async (args: ConfigFlags) => {
123
157
  const opts = args as unknown as CreateCommandArgs;
124
158
  const log = new Logger(opts.verbose ?? false, "CreateCommand");
125
159
 
126
- const templatesRoot = findTemplatesRoot();
160
+ let templatesRoot: string;
161
+ try {
162
+ templatesRoot = findTemplatesRoot();
163
+ } catch (err) {
164
+ // `dist-templates/` not next to the running file. Two known scenarios:
165
+ // 1. `install.js` (or any flow) deployed only the single-file GJS
166
+ // binary without the package tree. We can't scaffold without
167
+ // templates — point the user at the working install path.
168
+ // 2. Source-mode mis-checkout (no `packages/cli/templates/`). Surface
169
+ // the original error so the developer can fix their layout.
170
+ // The `__GJS_BUNDLE__` define lets us discriminate at build time.
171
+ if (typeof __GJS_BUNDLE__ !== "undefined") {
172
+ process.stderr.write(
173
+ "The 'create' command needs templates that aren't shipped alongside this binary.\n" +
174
+ "Install the full package instead so `dist-templates/` lives next to the bin:\n" +
175
+ " gjsify install -g @ts-for-gir/cli\n" +
176
+ " npm install -g @ts-for-gir/cli\n" +
177
+ " npx @ts-for-gir/cli create ... # one-shot, no install\n",
178
+ );
179
+ process.exitCode = 1;
180
+ return;
181
+ }
182
+ throw err;
183
+ }
127
184
  const available = listTemplates(templatesRoot);
128
185
  if (available.length === 0) {
129
186
  throw new Error(`No templates found in ${templatesRoot}`);
@@ -175,19 +232,30 @@ const handler = async (args: ConfigFlags) => {
175
232
  log.success(`Scaffolded ${template} into ${targetDir}`);
176
233
 
177
234
  if (opts.install) {
178
- log.info("Running npm install...");
179
- const result = spawnSync("npm", ["install", "--no-audit", "--no-fund"], {
235
+ // types-gjsify is Node-free at runtime — bootstrap deps via `gjsify install`
236
+ // so the user's first impression matches the rest of the template's scripts.
237
+ // Other templates remain on npm (matches the prior behavior + their README).
238
+ const installer = template === "types-gjsify" ? "gjsify" : "npm";
239
+ const installerArgs = template === "types-gjsify" ? ["install"] : ["install", "--no-audit", "--no-fund"];
240
+ log.info(`Running ${installer} install...`);
241
+ const result = spawnSync(installer, installerArgs, {
180
242
  cwd: targetDir,
181
243
  stdio: "inherit",
182
244
  });
183
245
  if (result.status !== 0) {
184
- log.warn("npm install failed; you can re-run it manually in the project directory.");
246
+ log.warn(`${installer} install failed; you can re-run it manually in the project directory.`);
185
247
  }
186
248
  }
187
249
 
188
250
  log.info("\nNext steps:");
189
251
  log.white(` cd ${projectName}`);
190
- if (!opts.install) log.white(" npm install");
252
+ if (!opts.install) {
253
+ if (template === "types-gjsify") {
254
+ log.white(" gjsify install");
255
+ } else {
256
+ log.white(" npm install");
257
+ }
258
+ }
191
259
  switch (template) {
192
260
  case "types-locally":
193
261
  log.white(" npm run generate");
@@ -202,6 +270,10 @@ const handler = async (args: ConfigFlags) => {
202
270
  log.white(" npm run build:types && npm install");
203
271
  log.white(" npm run build:app && npm start");
204
272
  break;
273
+ case "types-gjsify":
274
+ log.white(" gjsify run check");
275
+ log.white(" gjsify run build && gjsify run start");
276
+ break;
205
277
  }
206
278
  };
207
279
 
@@ -5,3 +5,4 @@ export * from "./doc.ts";
5
5
  export * from "./generate.ts";
6
6
  export * from "./json.ts";
7
7
  export * from "./list.ts";
8
+ export * from "./self-update.ts";
@@ -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
+ };
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { dirname, resolve } from "node:path";
6
+ import { pathToFileURL } from "node:url";
6
7
  import type { ConfigFlags, OptionsGeneration, UserConfig, UserConfigLoadResult } from "@ts-for-gir/lib";
7
8
  import { APP_NAME, isEqual } from "@ts-for-gir/lib";
8
9
  import { type Options as ConfigSearchOptions, cosmiconfig } from "cosmiconfig";
@@ -18,9 +19,12 @@ import { docOptions, options } from "./options.ts";
18
19
  export async function loadConfigFile(configName?: string): Promise<UserConfigLoadResult | null> {
19
20
  const configSearchOptions: Partial<ConfigSearchOptions> = {
20
21
  loaders: {
21
- // ESM loader
22
+ // ESM loader. cosmiconfig hands us an absolute filesystem path; Node's import()
23
+ // tolerates that as a non-spec extension, but spec-compliant runtimes (GJS /
24
+ // SpiderMonkey) reject it with "Module not found: <abs-path>". Convert to a
25
+ // file:// URL so the loader works in both runtimes.
22
26
  ".js": async (filepath) => {
23
- const file = await import(filepath);
27
+ const file = await import(pathToFileURL(filepath).href);
24
28
 
25
29
  // Files with `exports.default = { ... }`
26
30
  if (file?.default?.default) {
@@ -53,6 +53,20 @@ function getDefaultGirDirectories(): string[] {
53
53
  "/usr/lib/x86_64-linux-gnu/mutter-*",
54
54
  ];
55
55
 
56
+ // Flatpak: `--filesystem=host` exposes the host's filesystem under
57
+ // /run/host. The GNOME runtime ships GIR typelibs but not the XML, so
58
+ // inside the sandbox we must read from /run/host/usr/share/gir-1.0.
59
+ // Detected via FLATPAK_ID (set for every Flatpak-launched process) or
60
+ // the .flatpak-info marker file (always present in the sandbox).
61
+ if (process.env.FLATPAK_ID || existsSync("/.flatpak-info")) {
62
+ girDirectories.unshift(
63
+ "/run/host/usr/local/share/gir-1.0",
64
+ "/run/host/usr/share/gir-1.0",
65
+ "/run/host/usr/share/*/gir-1.0",
66
+ "/run/host/usr/lib/x86_64-linux-gnu/mutter-*",
67
+ );
68
+ }
69
+
56
70
  // NixOS and other distributions does not have a /usr/local/share directory.
57
71
  // Instead, the nix store paths with Gir files are set as XDG_DATA_DIRS.
58
72
  // See https://github.com/NixOS/nixpkgs/blob/96e18717904dfedcd884541e5a92bf9ff632cf39/pkgs/development/libraries/gobject-introspection/setup-hook.sh#L7-L10
@@ -257,8 +257,8 @@ export const createOptions = {
257
257
  template: {
258
258
  type: "string" as const,
259
259
  alias: "t",
260
- description: "Template to scaffold (types-locally, types-npm, types-workspace)",
261
- choices: ["types-locally", "types-npm", "types-workspace"] as const,
260
+ description: "Template to scaffold (types-locally, types-npm, types-workspace, types-gjsify)",
261
+ choices: ["types-locally", "types-npm", "types-workspace", "types-gjsify"] as const,
262
262
  },
263
263
  install: {
264
264
  type: "boolean" as const,