@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.
- package/dist/cli.gjs.mjs +202 -167
- package/lib/commands/gettext.d.ts +2 -1
- package/lib/commands/gettext.js +105 -47
- package/lib/commands/onboard.d.ts +7 -0
- package/lib/commands/onboard.js +349 -96
- package/lib/commands/ship.d.ts +3 -0
- package/lib/commands/ship.js +850 -58
- package/lib/commands/trust.d.ts +6 -0
- package/lib/commands/trust.js +1 -1
- package/lib/commands/upgrade.d.ts +23 -0
- package/lib/commands/upgrade.js +95 -12
- package/lib/templates/install.mjs.tmpl +2 -2
- package/lib/types/config-data.d.ts +127 -1
- package/lib/utils/gi-search-path.js +11 -3
- package/lib/utils/msgfmt-merge.d.ts +35 -0
- package/lib/utils/msgfmt-merge.js +102 -0
- package/lib/utils/npm-otp.d.ts +27 -2
- package/lib/utils/npm-otp.js +82 -7
- package/lib/utils/onboard-discovery.d.ts +68 -0
- package/lib/utils/onboard-discovery.js +178 -0
- package/lib/utils/onboard-probe.d.ts +118 -1
- package/lib/utils/onboard-probe.js +153 -4
- package/lib/utils/prompt-output.d.ts +12 -0
- package/lib/utils/prompt-output.js +54 -0
- package/lib/utils/prompt.js +22 -2
- package/lib/utils/publishable-packages.d.ts +1 -1
- package/lib/utils/publishable-packages.js +1 -0
- package/lib/utils/resolve-npm-package.d.ts +56 -0
- package/lib/utils/resolve-npm-package.js +69 -0
- package/lib/utils/ship/app-runtime.d.ts +214 -0
- package/lib/utils/ship/app-runtime.js +429 -0
- package/lib/utils/ship/depends.d.ts +50 -0
- package/lib/utils/ship/depends.js +117 -3
- package/lib/utils/ship/discover.js +103 -1
- package/lib/utils/ship/dmg.d.ts +71 -0
- package/lib/utils/ship/dmg.js +181 -0
- package/lib/utils/ship/flatpak.js +1 -1
- package/lib/utils/ship/formats.d.ts +108 -7
- package/lib/utils/ship/formats.js +778 -15
- package/lib/utils/ship/launcher.d.ts +13 -3
- package/lib/utils/ship/launcher.js +419 -17
- package/lib/utils/ship/layout.d.ts +330 -0
- package/lib/utils/ship/layout.js +504 -0
- package/lib/utils/ship/localize-metadata.d.ts +22 -0
- package/lib/utils/ship/localize-metadata.js +126 -0
- package/lib/utils/ship/mime.js +1 -9
- package/lib/utils/ship/msi.d.ts +126 -0
- package/lib/utils/ship/msi.js +434 -0
- package/lib/utils/ship/node-runtime.d.ts +71 -0
- package/lib/utils/ship/node-runtime.js +122 -0
- package/lib/utils/ship/payload.d.ts +156 -5
- package/lib/utils/ship/payload.js +599 -14
- package/lib/utils/ship/pe-launcher.d.ts +29 -0
- package/lib/utils/ship/pe-launcher.js +663 -0
- package/lib/utils/ship/plan.d.ts +21 -0
- package/lib/utils/ship/plan.js +55 -7
- package/lib/utils/ship/plist.d.ts +98 -0
- package/lib/utils/ship/plist.js +256 -0
- package/lib/utils/ship/rpm.js +16 -3
- package/lib/utils/ship/schemas.d.ts +61 -0
- package/lib/utils/ship/schemas.js +118 -0
- package/lib/utils/ship/scripts.js +7 -3
- package/lib/utils/ship/settings.d.ts +91 -2
- package/lib/utils/ship/settings.js +93 -1
- package/lib/utils/ship/share-dirs.d.ts +53 -0
- package/lib/utils/ship/share-dirs.js +76 -0
- package/lib/utils/ship/signing.d.ts +311 -0
- package/lib/utils/ship/signing.js +506 -0
- package/lib/utils/ship/stage-manifest.d.ts +40 -12
- package/lib/utils/ship/stage-manifest.js +99 -13
- package/lib/utils/ship/stage-writer.d.ts +48 -0
- package/lib/utils/ship/stage-writer.js +91 -2
- package/lib/utils/ship/types.d.ts +191 -14
- package/lib/utils/ship/xml.d.ts +7 -0
- package/lib/utils/ship/xml.js +25 -0
- package/lib/utils/ship/zip.d.ts +69 -0
- package/lib/utils/ship/zip.js +245 -0
- package/lib/utils/system-gi.d.ts +20 -5
- package/lib/utils/system-gi.js +36 -13
- package/lib/utils/trust-registry.js +5 -0
- 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, '<')
|
|
22
|
+
.replace(/>/g, '>')
|
|
23
|
+
.replace(/"/g, '"')
|
|
24
|
+
.replace(/'/g, ''');
|
|
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
|
+
}
|
package/lib/utils/system-gi.d.ts
CHANGED
|
@@ -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
|
|
32
|
-
* one string this whole layout rule turns on
|
|
33
|
-
* agreement suite
|
|
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
|
|
56
|
-
* alone: an explicit statement is
|
|
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
|
package/lib/utils/system-gi.js
CHANGED
|
@@ -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
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
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
|
|
78
|
-
* one string this whole layout rule turns on
|
|
79
|
-
* agreement suite
|
|
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
|
|
128
|
-
* alone: an explicit statement is
|
|
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
|
|
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
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
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.
|
|
146
|
-
"@gjsify/create-app": "^0.
|
|
147
|
-
"@gjsify/node-globals": "^0.
|
|
148
|
-
"@gjsify/node-polyfills": "^0.
|
|
149
|
-
"@gjsify/npm-registry": "^0.
|
|
150
|
-
"@gjsify/resolve-npm": "^0.
|
|
151
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.
|
|
152
|
-
"@gjsify/vite-plugin-blueprint": "^0.
|
|
153
|
-
"@gjsify/rolldown-plugin-pnp": "^0.
|
|
154
|
-
"@gjsify/semver": "^0.
|
|
155
|
-
"@gjsify/tar": "^0.
|
|
156
|
-
"@gjsify/tsc": "^0.
|
|
157
|
-
"@gjsify/utils": "^0.
|
|
158
|
-
"@gjsify/web-polyfills": "^0.
|
|
159
|
-
"@gjsify/workspace": "^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.
|
|
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.
|
|
174
|
-
"@gjsify/rolldown-native": "^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": {
|