@ts-for-gir/cli 4.0.0-rc.9 → 4.0.1

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.
@@ -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,
package/src/start.ts CHANGED
@@ -2,18 +2,30 @@ 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, create, 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
7
  try {
8
- await yargs(hideBin(process.argv))
8
+ const cli = yargs(hideBin(process.argv));
9
+ await cli
9
10
  .scriptName(APP_NAME)
10
11
  .strict()
11
12
  .usage(APP_USAGE)
12
13
  .version(APP_VERSION)
14
+ // Use the full terminal width for help. yargs's default caps at 80
15
+ // (`Math.min(80, process.stdout.columns)`); we explicitly opt into
16
+ // the real terminal width so long option/description lines wrap at
17
+ // the actual terminal edge instead of an arbitrary 80-col limit.
18
+ // Under GJS, `process.stdout.columns` is backed by
19
+ // `@gjsify/terminal-native` (ioctl TIOCGWINSZ) when the typelib is
20
+ // on `GI_TYPELIB_PATH` — the install.js launcher imports those env
21
+ // vars from the gjsify global install when present.
22
+ .wrap(cli.terminalWidth())
13
23
  // Disable yargs's internal `process.exit` and route both success
14
- // and failure through `parseAsync` + `process.exitCode` so async
15
- // command handlers complete and stdout drains before the runtime
16
- // (Node or the gjsify Node-compat loader on GJS) tears down.
24
+ // and failure through `parseAsync` + an explicit `process.exit` so
25
+ // async command handlers complete and stdout drains before the
26
+ // runtime (Node or the gjsify Node-compat loader on GJS, which
27
+ // keeps a GLib main loop alive that would otherwise prevent the
28
+ // process from exiting after main() returns) tears down.
17
29
  .exitProcess(false)
18
30
  .fail(false)
19
31
  // TODO: Fix this
@@ -24,11 +36,13 @@ try {
24
36
  .command(list as unknown as CommandModule)
25
37
  .command(copy as unknown as CommandModule)
26
38
  .command(doc as unknown as CommandModule)
39
+ .command(selfUpdate as unknown as CommandModule)
27
40
  .demandCommand(1)
28
41
  .help()
29
42
  .parseAsync();
43
+ process.exit(0);
30
44
  } catch (err) {
31
45
  const message = err instanceof Error ? err.message : String(err);
32
46
  process.stderr.write(`${message}\n`);
33
- process.exitCode = 1;
47
+ process.exit(1);
34
48
  }
@@ -94,7 +94,7 @@ export interface DocCommandArgs extends GenerateCommandArgs {
94
94
  /**
95
95
  * Available scaffolding template identifiers for the create command.
96
96
  */
97
- export type CreateTemplateId = "types-locally" | "types-npm" | "types-workspace";
97
+ export type CreateTemplateId = "types-locally" | "types-npm" | "types-workspace" | "types-gjsify";
98
98
 
99
99
  /**
100
100
  * Arguments for the create command
@@ -145,3 +145,10 @@ export interface AnalyzeCommandArgs {
145
145
  /** Switch on/off the verbose mode */
146
146
  verbose?: boolean;
147
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
+ }