@gjsify/cli 0.44.0 → 0.46.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 (81) hide show
  1. package/dist/cli.gjs.mjs +202 -167
  2. package/lib/commands/gettext.d.ts +2 -1
  3. package/lib/commands/gettext.js +105 -47
  4. package/lib/commands/onboard.d.ts +7 -0
  5. package/lib/commands/onboard.js +349 -96
  6. package/lib/commands/ship.d.ts +3 -0
  7. package/lib/commands/ship.js +850 -58
  8. package/lib/commands/trust.d.ts +6 -0
  9. package/lib/commands/trust.js +1 -1
  10. package/lib/commands/upgrade.d.ts +23 -0
  11. package/lib/commands/upgrade.js +95 -12
  12. package/lib/templates/install.mjs.tmpl +2 -2
  13. package/lib/types/config-data.d.ts +127 -1
  14. package/lib/utils/gi-search-path.js +11 -3
  15. package/lib/utils/msgfmt-merge.d.ts +35 -0
  16. package/lib/utils/msgfmt-merge.js +102 -0
  17. package/lib/utils/npm-otp.d.ts +27 -2
  18. package/lib/utils/npm-otp.js +82 -7
  19. package/lib/utils/onboard-discovery.d.ts +68 -0
  20. package/lib/utils/onboard-discovery.js +178 -0
  21. package/lib/utils/onboard-probe.d.ts +118 -1
  22. package/lib/utils/onboard-probe.js +153 -4
  23. package/lib/utils/prompt-output.d.ts +12 -0
  24. package/lib/utils/prompt-output.js +54 -0
  25. package/lib/utils/prompt.js +22 -2
  26. package/lib/utils/publishable-packages.d.ts +1 -1
  27. package/lib/utils/publishable-packages.js +1 -0
  28. package/lib/utils/resolve-npm-package.d.ts +56 -0
  29. package/lib/utils/resolve-npm-package.js +69 -0
  30. package/lib/utils/ship/app-runtime.d.ts +214 -0
  31. package/lib/utils/ship/app-runtime.js +429 -0
  32. package/lib/utils/ship/depends.d.ts +50 -0
  33. package/lib/utils/ship/depends.js +117 -3
  34. package/lib/utils/ship/discover.js +103 -1
  35. package/lib/utils/ship/dmg.d.ts +71 -0
  36. package/lib/utils/ship/dmg.js +181 -0
  37. package/lib/utils/ship/flatpak.js +1 -1
  38. package/lib/utils/ship/formats.d.ts +108 -7
  39. package/lib/utils/ship/formats.js +778 -15
  40. package/lib/utils/ship/launcher.d.ts +13 -3
  41. package/lib/utils/ship/launcher.js +419 -17
  42. package/lib/utils/ship/layout.d.ts +330 -0
  43. package/lib/utils/ship/layout.js +504 -0
  44. package/lib/utils/ship/localize-metadata.d.ts +22 -0
  45. package/lib/utils/ship/localize-metadata.js +126 -0
  46. package/lib/utils/ship/mime.js +1 -9
  47. package/lib/utils/ship/msi.d.ts +126 -0
  48. package/lib/utils/ship/msi.js +434 -0
  49. package/lib/utils/ship/node-runtime.d.ts +71 -0
  50. package/lib/utils/ship/node-runtime.js +122 -0
  51. package/lib/utils/ship/payload.d.ts +156 -5
  52. package/lib/utils/ship/payload.js +599 -14
  53. package/lib/utils/ship/pe-launcher.d.ts +29 -0
  54. package/lib/utils/ship/pe-launcher.js +663 -0
  55. package/lib/utils/ship/plan.d.ts +21 -0
  56. package/lib/utils/ship/plan.js +55 -7
  57. package/lib/utils/ship/plist.d.ts +98 -0
  58. package/lib/utils/ship/plist.js +256 -0
  59. package/lib/utils/ship/rpm.js +16 -3
  60. package/lib/utils/ship/schemas.d.ts +61 -0
  61. package/lib/utils/ship/schemas.js +118 -0
  62. package/lib/utils/ship/scripts.js +7 -3
  63. package/lib/utils/ship/settings.d.ts +91 -2
  64. package/lib/utils/ship/settings.js +93 -1
  65. package/lib/utils/ship/share-dirs.d.ts +53 -0
  66. package/lib/utils/ship/share-dirs.js +76 -0
  67. package/lib/utils/ship/signing.d.ts +311 -0
  68. package/lib/utils/ship/signing.js +506 -0
  69. package/lib/utils/ship/stage-manifest.d.ts +40 -12
  70. package/lib/utils/ship/stage-manifest.js +99 -13
  71. package/lib/utils/ship/stage-writer.d.ts +48 -0
  72. package/lib/utils/ship/stage-writer.js +91 -2
  73. package/lib/utils/ship/types.d.ts +191 -14
  74. package/lib/utils/ship/xml.d.ts +7 -0
  75. package/lib/utils/ship/xml.js +25 -0
  76. package/lib/utils/ship/zip.d.ts +69 -0
  77. package/lib/utils/ship/zip.js +245 -0
  78. package/lib/utils/system-gi.d.ts +20 -5
  79. package/lib/utils/system-gi.js +36 -13
  80. package/lib/utils/trust-registry.js +5 -0
  81. package/package.json +19 -19
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Escape a value for XML element content or an attribute value.
3
+ *
4
+ * `&` FIRST, or the ampersands the four later replacements introduce are escaped
5
+ * again and `Ship & Co` comes out as `Ship & Co`.
6
+ */
7
+ export declare function xmlEscape(value: string): string;
@@ -0,0 +1,25 @@
1
+ // The XML escape `gjsify ship`'s document renderers share.
2
+ //
3
+ // TWO COPIES BECAME ONE. `mime.ts` and `msi.ts` each grew their own — five
4
+ // replacements, same entities, same order, byte-identical bodies — one milestone
5
+ // apart, by people who never saw the other file. The second copy is where the
6
+ // helper goes.
7
+ //
8
+ // `plist.ts` keeps its own and that divergence CARRIES something: a plist value
9
+ // lands only in element CONTENT, so `"` and `'` are left as themselves rather
10
+ // than becoming entities a human reading `Info.plist` has to decode. This one is
11
+ // used in ATTRIBUTES too — a `.wxs` is almost nothing else — so it escapes both.
12
+ /**
13
+ * Escape a value for XML element content or an attribute value.
14
+ *
15
+ * `&` FIRST, or the ampersands the four later replacements introduce are escaped
16
+ * again and `Ship & Co` comes out as `Ship & Co`.
17
+ */
18
+ export function xmlEscape(value) {
19
+ return value
20
+ .replace(/&/g, '&')
21
+ .replace(/</g, '&lt;')
22
+ .replace(/>/g, '&gt;')
23
+ .replace(/"/g, '&quot;')
24
+ .replace(/'/g, '&apos;');
25
+ }
@@ -0,0 +1,69 @@
1
+ import type { PayloadEntry } from './payload.js';
2
+ /** CRC-32 of a byte range, in ZIP's spelling (pre- and post-inverted). */
3
+ export declare function crc32(data: Uint8Array): number;
4
+ /**
5
+ * A Unix epoch second as the MS-DOS date and time pair ZIP stores.
6
+ *
7
+ * The format is from 1980 and cannot say anything earlier, and its seconds field
8
+ * holds HALF-seconds, so an odd second is not representable. Neither is refused:
9
+ * the stamp is metadata no reader in this repository asserts on, and failing a
10
+ * build because `SOURCE_DATE_EPOCH` named 1979 would reject an artifact over a
11
+ * display field. Earlier than 1980 is CLAMPED to it; the odd second is floored.
12
+ *
13
+ * UTC deliberately, not local time. A DOS stamp carries no zone, so reading it
14
+ * back as local time is the only convention available — and using the packing
15
+ * host's zone would mean two runners in different regions produce different bytes
16
+ * from one stage, which is exactly the property this module exists to hold.
17
+ */
18
+ export declare function dosDateTime(epochSeconds: number): {
19
+ date: number;
20
+ time: number;
21
+ };
22
+ /** One entry to store, addressed by its path INSIDE the archive. */
23
+ export interface ZipEntry {
24
+ /** Archive-relative, POSIX-separated. ZIP stores `/` on every platform. */
25
+ path: string;
26
+ /** POSIX mode bits, written into the external attributes' high 16 bits. */
27
+ mode: number;
28
+ data: Uint8Array;
29
+ }
30
+ /**
31
+ * Build a ZIP archive: local headers, then the central directory, then the EOCD.
32
+ *
33
+ * `mtime` is one stamp for every entry, taken from the caller. Per-file mtimes
34
+ * are deliberately not read off disk: an artifact upload does not carry them
35
+ * (`stage-manifest.ts`'s header), so re-stat'ing the stage on the packing host
36
+ * would stamp the archive with "whenever the download finished" and two hosts
37
+ * packing one stage would disagree.
38
+ *
39
+ * ENTRIES ARE SORTED BY PATH, and the caller's order is not trusted for the same
40
+ * reason `plan.ts` sorts: two runs over one payload must produce one byte
41
+ * sequence, and a `readdir` order is a filesystem's opinion.
42
+ *
43
+ * NO DIRECTORY ENTRIES. A ZIP may carry them and `unzip` recreates the tree from
44
+ * the file paths alone, so emitting them would add entries no reader here
45
+ * inspects and one more thing to keep deterministic. The consequence is stated
46
+ * rather than left to be discovered: an EMPTY directory inside a `.app` would not
47
+ * survive the archive — nothing in the payload can produce one, because
48
+ * `writeStage` only ever writes files.
49
+ *
50
+ * ZIP64 is NOT implemented, and the refusal below is what keeps that honest:
51
+ * past 4 GiB or 65535 entries the 32-bit fields silently wrap and the archive
52
+ * that comes out is well-formed and wrong. A loud limit is the better failure —
53
+ * the same argument `assertPayloadMatchesArch` makes one field over, where a
54
+ * label nothing compared to the payload produced an artifact `rpm` confirmed.
55
+ */
56
+ export declare function buildZip(entries: readonly ZipEntry[], mtime: number): Uint8Array;
57
+ /**
58
+ * The payload as zip entries, keeping each file's planned mode.
59
+ *
60
+ * `topLevel` names a directory every entry goes UNDER, and `''` means the payload
61
+ * paths already carry one. The two callers differ because their layouts do:
62
+ * a `<App>.app`'s staged paths already begin with the bundle directory
63
+ * (`Layout.root`), while a Windows program directory's do not — the stage IS its
64
+ * contents, because an installer chooses the parent. An archive without a top
65
+ * level expands into whatever directory the user happened to be in, scattering
66
+ * `app\`, `share\` and the launcher across it, and no listing of NAMES reads as
67
+ * wrong: every entry is individually correct.
68
+ */
69
+ export declare function zipEntriesFromPayload(payload: readonly PayloadEntry[], topLevel?: string): ZipEntry[];
@@ -0,0 +1,245 @@
1
+ // A deterministic ZIP writer, in-tree, execing nothing.
2
+ //
3
+ // SAME ARGUMENT AS THE HAND-WRITTEN `.deb` AND `.rpm` (ADR 0024 § A3): a format
4
+ // this tree writes itself needs no tool on the packing host, which is what keeps
5
+ // `zipinfo` an INDEPENDENT reader rather than the other half of a round trip.
6
+ // `zip(1)` would have been fewer lines and would have made the oracle
7
+ // `unzip`-reads-what-`zip`-wrote — two programs from one source distribution,
8
+ // which is the shape `oracle.selfReading` exists to confess.
9
+ //
10
+ // WHY THE MODE MATTERS ENOUGH TO WRITE A ZIP WRITER. The failure mode of a
11
+ // distributed `.app` is a launcher that arrives 0644 and will not run, and the
12
+ // zip's external-attributes field is the only place in the archive that can
13
+ // carry 0755. `actions/upload-artifact` has already proven the class inside this
14
+ // repository (`main.yml`'s ship-pack job: "the artifact round-trip does not
15
+ // preserve the mode"). So the writer sets the mode, and
16
+ // `.github/ship-oracle/verify-app-zip.sh` reads it back with `zipinfo -l` — not
17
+ // `unzip -Z1`, which prints names only and is blind to exactly this.
18
+ //
19
+ // STORE ONLY, no DEFLATE. A `.app` is mostly a JavaScript bundle and would
20
+ // compress well, but compression buys size and this milestone buys correctness:
21
+ // STORE is ~40 lines with one CRC table, DEFLATE means either a second
22
+ // implementation or peeling the 10-byte header and 8-byte trailer off
23
+ // `@gjsify/tar`'s gzip, and neither changes what any reader here checks. The
24
+ // method field is a per-entry constant so the day that trade flips, it is one
25
+ // branch and not a rewrite.
26
+ //
27
+ // DETERMINISM: every timestamp comes from the caller (the stage manifest's
28
+ // `mtime`), never `Date.now()`. Same rule as `buildTimestamp` and
29
+ // `gzipDeterministic`, and it is what lets `main.yml` compare two packs of one
30
+ // stage byte for byte.
31
+ /** ZIP's own constants, spelled once. */
32
+ const LOCAL_HEADER_SIGNATURE = 0x04034b50;
33
+ const CENTRAL_HEADER_SIGNATURE = 0x02014b50;
34
+ const EOCD_SIGNATURE = 0x06054b50;
35
+ /** Stored, not deflated — see the module header. */
36
+ const METHOD_STORE = 0;
37
+ /**
38
+ * `version made by`: high byte 3 = Unix, low byte 20 = ZIP spec 2.0.
39
+ *
40
+ * LOAD-BEARING, and the reason the mode survives. `zipinfo` and `unzip` read the
41
+ * external attributes as POSIX bits ONLY when the high byte says the archive was
42
+ * made on Unix; with the DOS value (0) the same 0755 in the same field is read as
43
+ * DOS attribute flags and every file extracts at the umask default. The mode
44
+ * would be in the archive and no reader would ever see it.
45
+ */
46
+ const VERSION_MADE_BY = 0x0314;
47
+ /** `version needed to extract`: 2.0, the floor for everything this writer emits. */
48
+ const VERSION_NEEDED = 20;
49
+ /** The CRC-32 table, built once on first use rather than as a 256-entry literal. */
50
+ let crcTable = null;
51
+ function getCrcTable() {
52
+ if (crcTable !== null)
53
+ return crcTable;
54
+ const table = new Uint32Array(256);
55
+ for (let i = 0; i < 256; i++) {
56
+ let c = i;
57
+ // The reversed polynomial 0xEDB88320 — ZIP, gzip and PNG all use this one.
58
+ for (let bit = 0; bit < 8; bit++)
59
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
60
+ table[i] = c >>> 0;
61
+ }
62
+ crcTable = table;
63
+ return table;
64
+ }
65
+ /** CRC-32 of a byte range, in ZIP's spelling (pre- and post-inverted). */
66
+ export function crc32(data) {
67
+ const table = getCrcTable();
68
+ let crc = 0xffffffff;
69
+ for (let i = 0; i < data.length; i++)
70
+ crc = table[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);
71
+ return (crc ^ 0xffffffff) >>> 0;
72
+ }
73
+ /**
74
+ * A Unix epoch second as the MS-DOS date and time pair ZIP stores.
75
+ *
76
+ * The format is from 1980 and cannot say anything earlier, and its seconds field
77
+ * holds HALF-seconds, so an odd second is not representable. Neither is refused:
78
+ * the stamp is metadata no reader in this repository asserts on, and failing a
79
+ * build because `SOURCE_DATE_EPOCH` named 1979 would reject an artifact over a
80
+ * display field. Earlier than 1980 is CLAMPED to it; the odd second is floored.
81
+ *
82
+ * UTC deliberately, not local time. A DOS stamp carries no zone, so reading it
83
+ * back as local time is the only convention available — and using the packing
84
+ * host's zone would mean two runners in different regions produce different bytes
85
+ * from one stage, which is exactly the property this module exists to hold.
86
+ */
87
+ export function dosDateTime(epochSeconds) {
88
+ const stamp = new Date(Math.max(epochSeconds, 315532800) * 1000);
89
+ const date = (((stamp.getUTCFullYear() - 1980) & 0x7f) << 9) | ((stamp.getUTCMonth() + 1) << 5) | stamp.getUTCDate();
90
+ const time = (stamp.getUTCHours() << 11) | (stamp.getUTCMinutes() << 5) | Math.floor(stamp.getUTCSeconds() / 2);
91
+ return { date, time };
92
+ }
93
+ /** A little-endian byte sink, so the header layouts below read as tables. */
94
+ class Writer {
95
+ parts = [];
96
+ length = 0;
97
+ get offset() {
98
+ return this.length;
99
+ }
100
+ u16(value) {
101
+ this.push(new Uint8Array([value & 0xff, (value >>> 8) & 0xff]));
102
+ }
103
+ u32(value) {
104
+ this.push(new Uint8Array([value & 0xff, (value >>> 8) & 0xff, (value >>> 16) & 0xff, (value >>> 24) & 0xff]));
105
+ }
106
+ bytes(value) {
107
+ this.push(value);
108
+ }
109
+ push(part) {
110
+ this.parts.push(part);
111
+ this.length += part.byteLength;
112
+ }
113
+ finish() {
114
+ const out = new Uint8Array(this.length);
115
+ let at = 0;
116
+ for (const part of this.parts) {
117
+ out.set(part, at);
118
+ at += part.byteLength;
119
+ }
120
+ return out;
121
+ }
122
+ }
123
+ /**
124
+ * Build a ZIP archive: local headers, then the central directory, then the EOCD.
125
+ *
126
+ * `mtime` is one stamp for every entry, taken from the caller. Per-file mtimes
127
+ * are deliberately not read off disk: an artifact upload does not carry them
128
+ * (`stage-manifest.ts`'s header), so re-stat'ing the stage on the packing host
129
+ * would stamp the archive with "whenever the download finished" and two hosts
130
+ * packing one stage would disagree.
131
+ *
132
+ * ENTRIES ARE SORTED BY PATH, and the caller's order is not trusted for the same
133
+ * reason `plan.ts` sorts: two runs over one payload must produce one byte
134
+ * sequence, and a `readdir` order is a filesystem's opinion.
135
+ *
136
+ * NO DIRECTORY ENTRIES. A ZIP may carry them and `unzip` recreates the tree from
137
+ * the file paths alone, so emitting them would add entries no reader here
138
+ * inspects and one more thing to keep deterministic. The consequence is stated
139
+ * rather than left to be discovered: an EMPTY directory inside a `.app` would not
140
+ * survive the archive — nothing in the payload can produce one, because
141
+ * `writeStage` only ever writes files.
142
+ *
143
+ * ZIP64 is NOT implemented, and the refusal below is what keeps that honest:
144
+ * past 4 GiB or 65535 entries the 32-bit fields silently wrap and the archive
145
+ * that comes out is well-formed and wrong. A loud limit is the better failure —
146
+ * the same argument `assertPayloadMatchesArch` makes one field over, where a
147
+ * label nothing compared to the payload produced an artifact `rpm` confirmed.
148
+ */
149
+ export function buildZip(entries, mtime) {
150
+ const sorted = [...entries].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
151
+ const { date, time } = dosDateTime(mtime);
152
+ const encoder = new TextEncoder();
153
+ if (sorted.length > 0xffff) {
154
+ throw new Error(`gjsify ship: this archive would hold ${sorted.length} entries and a non-ZIP64 central directory ` +
155
+ 'records the count in 16 bits, so it would silently wrap. Nothing here writes ZIP64 yet; the ' +
156
+ 'payload has to shrink, or this writer has to grow the ZIP64 records.');
157
+ }
158
+ const local = new Writer();
159
+ const central = new Writer();
160
+ let count = 0;
161
+ for (const entry of sorted) {
162
+ const name = encoder.encode(entry.path);
163
+ const crc = crc32(entry.data);
164
+ const size = entry.data.byteLength;
165
+ const offset = local.offset;
166
+ if (offset > 0xffffffff || size > 0xffffffff) {
167
+ throw new Error(`gjsify ship: ${entry.path} would push this archive past 4 GiB, which a non-ZIP64 header ` +
168
+ 'records in 32 bits and therefore truncates. Nothing here writes ZIP64 yet.');
169
+ }
170
+ // Local file header (PKWARE APPNOTE § 4.3.7).
171
+ local.u32(LOCAL_HEADER_SIGNATURE);
172
+ local.u16(VERSION_NEEDED);
173
+ // General purpose flags: bit 11 marks the name as UTF-8. Set
174
+ // unconditionally, because the alternative — CP437 — cannot spell the
175
+ // `<App>.app` directory of an app whose display name is not ASCII, and
176
+ // every reader in this decade honours the bit.
177
+ local.u16(0x0800);
178
+ local.u16(METHOD_STORE);
179
+ local.u16(time);
180
+ local.u16(date);
181
+ local.u32(crc);
182
+ local.u32(size);
183
+ local.u32(size);
184
+ local.u16(name.byteLength);
185
+ local.u16(0);
186
+ local.bytes(name);
187
+ local.bytes(entry.data);
188
+ // Central directory header (§ 4.3.12), same fields plus the mode.
189
+ central.u32(CENTRAL_HEADER_SIGNATURE);
190
+ central.u16(VERSION_MADE_BY);
191
+ central.u16(VERSION_NEEDED);
192
+ central.u16(0x0800);
193
+ central.u16(METHOD_STORE);
194
+ central.u16(time);
195
+ central.u16(date);
196
+ central.u32(crc);
197
+ central.u32(size);
198
+ central.u32(size);
199
+ central.u16(name.byteLength);
200
+ central.u16(0);
201
+ central.u16(0);
202
+ central.u16(0);
203
+ central.u16(0);
204
+ // External attributes: POSIX mode in the HIGH 16 bits, DOS attributes in
205
+ // the low ones. `0o100000` is `S_IFREG` — without the file-type bits
206
+ // `zipinfo` prints `?rwxr-xr-x` and some extractors treat the entry as
207
+ // having no type at all.
208
+ central.u32(((0o100000 | (entry.mode & 0o7777)) >>> 0) * 0x10000);
209
+ central.u32(offset);
210
+ central.bytes(name);
211
+ count += 1;
212
+ }
213
+ const centralBytes = central.finish();
214
+ const out = new Writer();
215
+ out.bytes(local.finish());
216
+ const centralOffset = out.offset;
217
+ out.bytes(centralBytes);
218
+ // End of central directory (§ 4.3.16). No comment — a comment would be one
219
+ // more field to keep identical across runs for no reader's benefit.
220
+ out.u32(EOCD_SIGNATURE);
221
+ out.u16(0);
222
+ out.u16(0);
223
+ out.u16(count);
224
+ out.u16(count);
225
+ out.u32(centralBytes.byteLength);
226
+ out.u32(centralOffset);
227
+ out.u16(0);
228
+ return out.finish();
229
+ }
230
+ /**
231
+ * The payload as zip entries, keeping each file's planned mode.
232
+ *
233
+ * `topLevel` names a directory every entry goes UNDER, and `''` means the payload
234
+ * paths already carry one. The two callers differ because their layouts do:
235
+ * a `<App>.app`'s staged paths already begin with the bundle directory
236
+ * (`Layout.root`), while a Windows program directory's do not — the stage IS its
237
+ * contents, because an installer chooses the parent. An archive without a top
238
+ * level expands into whatever directory the user happened to be in, scattering
239
+ * `app\`, `share\` and the launcher across it, and no listing of NAMES reads as
240
+ * wrong: every entry is individually correct.
241
+ */
242
+ export function zipEntriesFromPayload(payload, topLevel = '') {
243
+ const prefix = topLevel === '' ? '' : `${topLevel}/`;
244
+ return payload.map((entry) => ({ path: `${prefix}${entry.path}`, mode: entry.mode, data: entry.data }));
245
+ }
@@ -28,11 +28,25 @@ export declare const PROBED_GI_LIBDIRS: Record<string, readonly string[]>;
28
28
  /**
29
29
  * The subdir GI installs typelibs into — historically `1.0` even for the girepository-2.0 API.
30
30
  *
31
- * Exported so `gi-typelib.ts` shares it rather than keeping a third copy of the
32
- * one string this whole layout rule turns on. Both mirrors export it, so the
33
- * agreement suite still compares two files that differ only in language.
31
+ * Exported so `gi-typelib.ts` shares it rather than keeping a fourth copy of the
32
+ * one string this whole layout rule turns on, and compared across all three copies
33
+ * by the agreement suite rather than trusting three literals to stay equal.
34
34
  */
35
35
  export declare const TYPELIB_SUBDIR = "girepository-1.0";
36
+ /**
37
+ * The library directories a typelib directory implies, most specific first.
38
+ *
39
+ * TWO LAYOUTS, and a path alone cannot always say which: GI's INSTALL layout keeps
40
+ * the libraries in the PARENT (`<libdir>/girepository-1.0/…`), while a STAGED pair —
41
+ * an ADR 0017 prebuild directory, `gjsify.ship.bundledTypelibs` — keeps them in the
42
+ * DIRECTORY ITSELF. {@link TYPELIB_SUBDIR} as the basename is a positive signal for
43
+ * the first; absent it, both readings are offered and the caller's `existsDir` keeps
44
+ * whichever is real.
45
+ *
46
+ * The measurement, and why the ORDER of the two is the load-bearing half, are in the
47
+ * ORIGINAL, `packages/node-gi/node-gi/system-gi.js`.
48
+ */
49
+ export declare function giLibraryDirsForTypelibDir(typelibDir: string): string[];
36
50
  /** Split an OS search-path variable, dropping empty segments. */
37
51
  export declare function splitSearchPath(value: string | undefined, separator?: string): string[];
38
52
  /**
@@ -52,8 +66,9 @@ export declare function pkgConfigSearchDirs(env: Record<string, string | undefin
52
66
  *
53
67
  * Three sources, in precedence order:
54
68
  * 1. `GI_TYPELIB_PATH` — the host stating outright where typelibs live; the
55
- * libraries are the sibling `dirname()`. Accepted on directory existence
56
- * alone: an explicit statement is not second-guessed.
69
+ * libraries are wherever {@link giLibraryDirsForTypelibDir} says the layout
70
+ * puts them. Accepted on directory existence alone: an explicit statement is
71
+ * not second-guessed.
57
72
  * 2. `pkg-config`'s `.pc` search path → each `<dir>/pkgconfig`'s parent. The
58
73
  * general mechanism (any prefix, including a custom one).
59
74
  * 3. {@link PROBED_GI_LIBDIRS} — the standard macOS prefixes, for the common
@@ -3,10 +3,12 @@
3
3
  // dynamic loader can find the ones a typelib names by BARE LEAF
4
4
  // (`libgtk-4.1.dylib`, `libgdk_pixbuf-2.0.0.dylib`, …).
5
5
  //
6
- // A PINNED MIRROR of `packages/node-gi/node-gi/system-gi.js` — the two must agree
7
- // and `system-gi.spec.ts` asserts it over a table of injected host shapes. Why a
8
- // mirror and not an import, and what is owed to retire it:
9
- // `status/open-todos.md` § "systemGiLibraryDirs() lives in two places".
6
+ // A PINNED MIRROR of `packages/node-gi/node-gi/system-gi.js`, and of
7
+ // `@gjsify/utils/core`'s `system-gi-dirs.ts` — all three must agree and
8
+ // `system-gi.spec.ts` asserts it over a table of injected host shapes. Why a mirror
9
+ // and not an import, and what is owed to retire THIS copy (a delegation to
10
+ // `@gjsify/utils`, which the CLI already depends on):
11
+ // `status/open-todos.md` § "systemGiLibraryDirs() lives in three places".
10
12
  //
11
13
  // WHY THIS EXISTS. A failed `dlopen('libgtk-4.1.dylib')` surfaces one layer up as
12
14
  // something that reads like a type-system bug — `TypeError: Gtk.DrawingArea is not
@@ -32,6 +34,8 @@
32
34
  // `<libdir>/libgtk-4.1.dylib`. A directory holding `girepository-1.0/` therefore IS
33
35
  // a GI library directory, which is what makes this package-manager-agnostic — it
34
36
  // lands on Homebrew, MacPorts or a custom prefix without knowing any of them exist.
37
+ // THAT IS ONE LAYOUT, NOT THE LAYOUT — {@link giLibraryDirsForTypelibDir}; a staged
38
+ // pair keeps typelib and backer in ONE directory, and the incident is in the ORIGINAL.
35
39
  //
36
40
  // PURE FUNCTION of injected host facts (platform / env / fs / pkg-config), like
37
41
  // `resolvePrebuildDirName()` and `buildNativeEnv()` beside it, so the darwin branch
@@ -74,11 +78,28 @@ export const PROBED_GI_LIBDIRS = {
74
78
  /**
75
79
  * The subdir GI installs typelibs into — historically `1.0` even for the girepository-2.0 API.
76
80
  *
77
- * Exported so `gi-typelib.ts` shares it rather than keeping a third copy of the
78
- * one string this whole layout rule turns on. Both mirrors export it, so the
79
- * agreement suite still compares two files that differ only in language.
81
+ * Exported so `gi-typelib.ts` shares it rather than keeping a fourth copy of the
82
+ * one string this whole layout rule turns on, and compared across all three copies
83
+ * by the agreement suite rather than trusting three literals to stay equal.
80
84
  */
81
85
  export const TYPELIB_SUBDIR = 'girepository-1.0';
86
+ /**
87
+ * The library directories a typelib directory implies, most specific first.
88
+ *
89
+ * TWO LAYOUTS, and a path alone cannot always say which: GI's INSTALL layout keeps
90
+ * the libraries in the PARENT (`<libdir>/girepository-1.0/…`), while a STAGED pair —
91
+ * an ADR 0017 prebuild directory, `gjsify.ship.bundledTypelibs` — keeps them in the
92
+ * DIRECTORY ITSELF. {@link TYPELIB_SUBDIR} as the basename is a positive signal for
93
+ * the first; absent it, both readings are offered and the caller's `existsDir` keeps
94
+ * whichever is real.
95
+ *
96
+ * The measurement, and why the ORDER of the two is the load-bearing half, are in the
97
+ * ORIGINAL, `packages/node-gi/node-gi/system-gi.js`.
98
+ */
99
+ export function giLibraryDirsForTypelibDir(typelibDir) {
100
+ const parent = dirname(typelibDir);
101
+ return basename(typelibDir) === TYPELIB_SUBDIR ? [parent] : [typelibDir, parent];
102
+ }
82
103
  /** Memoized `pkg-config` answer: the spawn happens at most once per process. */
83
104
  let pkgConfigDirsCache = null;
84
105
  /** Split an OS search-path variable, dropping empty segments. */
@@ -124,8 +145,9 @@ export function pkgConfigSearchDirs(env) {
124
145
  *
125
146
  * Three sources, in precedence order:
126
147
  * 1. `GI_TYPELIB_PATH` — the host stating outright where typelibs live; the
127
- * libraries are the sibling `dirname()`. Accepted on directory existence
128
- * alone: an explicit statement is not second-guessed.
148
+ * libraries are wherever {@link giLibraryDirsForTypelibDir} says the layout
149
+ * puts them. Accepted on directory existence alone: an explicit statement is
150
+ * not second-guessed.
129
151
  * 2. `pkg-config`'s `.pc` search path → each `<dir>/pkgconfig`'s parent. The
130
152
  * general mechanism (any prefix, including a custom one).
131
153
  * 3. {@link PROBED_GI_LIBDIRS} — the standard macOS prefixes, for the common
@@ -155,11 +177,12 @@ export function systemGiLibraryDirs({ platform = process.platform, env = process
155
177
  if (dir && dir !== '/' && !out.includes(dir))
156
178
  out.push(dir);
157
179
  };
158
- // 1. Explicit: the typelib dir's sibling libdir.
180
+ // 1. Explicit: the libdirs each typelib dir implies, both layouts.
159
181
  for (const typelibDir of splitSearchPath(env['GI_TYPELIB_PATH'])) {
160
- const libDir = dirname(typelibDir);
161
- if (existsDir(libDir))
162
- add(libDir);
182
+ for (const libDir of giLibraryDirsForTypelibDir(typelibDir)) {
183
+ if (existsDir(libDir))
184
+ add(libDir);
185
+ }
163
186
  }
164
187
  // 2 + 3. Candidate prefixes, believed only on the girepository-1.0 marker.
165
188
  const candidates = [];
@@ -82,6 +82,11 @@ export function parseRepoFromGitRemote(url) {
82
82
  let s = url.trim();
83
83
  if (!s)
84
84
  return null;
85
+ // npm manifests spell the same URL `git+https://…` / `git+ssh://…`. The
86
+ // scheme prefix is npm's, not the transport's, and without stripping it the
87
+ // scheme match below fails on `+` and the whole URL reads as unparseable —
88
+ // which is indistinguishable from "this package declares no repository".
89
+ s = s.replace(/^git\+/, '');
85
90
  s = s.replace(/\.git$/, '');
86
91
  const scp = s.match(/^[^/@]+@[^:]+:(.+)$/);
87
92
  if (scp) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.44.0",
3
+ "version": "0.46.0",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -142,21 +142,21 @@
142
142
  "cli"
143
143
  ],
144
144
  "dependencies": {
145
- "@gjsify/buffer": "^0.44.0",
146
- "@gjsify/create-app": "^0.44.0",
147
- "@gjsify/node-globals": "^0.44.0",
148
- "@gjsify/node-polyfills": "^0.44.0",
149
- "@gjsify/npm-registry": "^0.44.0",
150
- "@gjsify/resolve-npm": "^0.44.0",
151
- "@gjsify/rolldown-plugin-gjsify": "^0.44.0",
152
- "@gjsify/vite-plugin-blueprint": "^0.44.0",
153
- "@gjsify/rolldown-plugin-pnp": "^0.44.0",
154
- "@gjsify/semver": "^0.44.0",
155
- "@gjsify/tar": "^0.44.0",
156
- "@gjsify/tsc": "^0.44.0",
157
- "@gjsify/utils": "^0.44.0",
158
- "@gjsify/web-polyfills": "^0.44.0",
159
- "@gjsify/workspace": "^0.44.0",
145
+ "@gjsify/buffer": "^0.46.0",
146
+ "@gjsify/create-app": "^0.46.0",
147
+ "@gjsify/node-globals": "^0.46.0",
148
+ "@gjsify/node-polyfills": "^0.46.0",
149
+ "@gjsify/npm-registry": "^0.46.0",
150
+ "@gjsify/resolve-npm": "^0.46.0",
151
+ "@gjsify/rolldown-plugin-gjsify": "^0.46.0",
152
+ "@gjsify/vite-plugin-blueprint": "^0.46.0",
153
+ "@gjsify/rolldown-plugin-pnp": "^0.46.0",
154
+ "@gjsify/semver": "^0.46.0",
155
+ "@gjsify/tar": "^0.46.0",
156
+ "@gjsify/tsc": "^0.46.0",
157
+ "@gjsify/utils": "^0.46.0",
158
+ "@gjsify/web-polyfills": "^0.46.0",
159
+ "@gjsify/workspace": "^0.46.0",
160
160
  "acorn": "^8.17.0",
161
161
  "cosmiconfig": "^9.0.2",
162
162
  "get-tsconfig": "^4.14.0",
@@ -165,13 +165,13 @@
165
165
  "yargs": "^18.0.0"
166
166
  },
167
167
  "devDependencies": {
168
- "@gjsify/unit": "^0.44.0",
168
+ "@gjsify/unit": "^0.46.0",
169
169
  "@types/yargs": "^17.0.35",
170
170
  "typescript": "^6.0.3"
171
171
  },
172
172
  "peerDependencies": {
173
- "@gjsify/oxfmt-native": "^0.44.0",
174
- "@gjsify/rolldown-native": "^0.44.0"
173
+ "@gjsify/oxfmt-native": "^0.46.0",
174
+ "@gjsify/rolldown-native": "^0.46.0"
175
175
  },
176
176
  "peerDependenciesMeta": {
177
177
  "@gjsify/oxfmt-native": {