@gjsify/cli 0.44.0 → 0.45.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 +192 -165
- 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 +833 -54
- package/lib/commands/trust.d.ts +6 -0
- package/lib/commands/trust.js +1 -1
- package/lib/types/config-data.d.ts +63 -1
- 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 +18 -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 +85 -7
- package/lib/utils/ship/formats.js +734 -15
- package/lib/utils/ship/launcher.d.ts +13 -3
- package/lib/utils/ship/launcher.js +362 -17
- package/lib/utils/ship/layout.d.ts +268 -0
- package/lib/utils/ship/layout.js +351 -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 +417 -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 +580 -14
- package/lib/utils/ship/plan.d.ts +21 -0
- package/lib/utils/ship/plan.js +20 -7
- package/lib/utils/ship/plist.d.ts +58 -0
- package/lib/utils/ship/plist.js +198 -0
- package/lib/utils/ship/rpm.js +9 -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 +19 -1
- package/lib/utils/ship/settings.js +12 -1
- package/lib/utils/ship/share-dirs.d.ts +23 -0
- package/lib/utils/ship/share-dirs.js +46 -0
- package/lib/utils/ship/signing.d.ts +195 -0
- package/lib/utils/ship/signing.js +317 -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 +71 -1
- package/lib/utils/ship/types.d.ts +158 -13
- 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/trust-registry.js +5 -0
- package/package.json +19 -19
package/lib/utils/ship/plan.d.ts
CHANGED
|
@@ -18,6 +18,13 @@ export interface StageInputs {
|
|
|
18
18
|
* The result is deduplicated by path (last write wins, so `extraFiles` can
|
|
19
19
|
* override anything the defaults staged) and sorted, which makes the tree —
|
|
20
20
|
* and therefore every artifact built from it — deterministic.
|
|
21
|
+
*
|
|
22
|
+
* ONE plan, in the Linux/XDG shape, for every OS. Where those paths end up is
|
|
23
|
+
* `utils/ship/layout.ts`'s `placeStage`, applied afterwards by the caller, and
|
|
24
|
+
* keeping the two steps apart is what makes ADR 0024 § 2's "one payload, a
|
|
25
|
+
* handful of layouts" checkable: `tests/e2e/ship-layout` asserts the three
|
|
26
|
+
* staged trees agree modulo that map. A planner that took the layout would have
|
|
27
|
+
* three code paths to keep in agreement instead.
|
|
21
28
|
*/
|
|
22
29
|
export declare function planStage(settings: ShipSettings, inputs: StageInputs): StagedFile[];
|
|
23
30
|
/**
|
|
@@ -61,3 +68,17 @@ export declare function assertOverlayIsLicensed(formatId: FormatId, binaryName:
|
|
|
61
68
|
* filename.
|
|
62
69
|
*/
|
|
63
70
|
export declare function iconSizeDir(iconPath: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* A native module inside the payload has to keep its executable bit.
|
|
73
|
+
*
|
|
74
|
+
* By NAME here, unlike `isArchIndependent`, which reads the file's magic: the
|
|
75
|
+
* planner is pure and has no bytes to look at. The two answer different
|
|
76
|
+
* questions — "should this be 0755" versus "is this package portable" — and
|
|
77
|
+
* getting this one wrong costs a mode bit, not an unusable package.
|
|
78
|
+
*
|
|
79
|
+
* EXPORTED for `utils/ship/app-runtime.ts`, which stages a relocated GTK closure
|
|
80
|
+
* that never passes through this plan and needs the same answer for the same
|
|
81
|
+
* files. A second copy of the pattern there would be a second answer to "is this
|
|
82
|
+
* a shared library", and the drifted one is the one that stages a `.dylib` 0644.
|
|
83
|
+
*/
|
|
84
|
+
export declare function isExecutableAsset(rel: string): boolean;
|
package/lib/utils/ship/plan.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// as `utils/copy-targets.ts`, and for the same reason: a stager that discovers
|
|
4
4
|
// its own inputs can only be tested by building a real project.
|
|
5
5
|
import { renderMimePackage } from './mime.js';
|
|
6
|
+
import { SHARE } from './share-dirs.js';
|
|
6
7
|
import { basename, extname, posix } from 'node:path';
|
|
7
8
|
/**
|
|
8
9
|
* Build the prefix-relative payload.
|
|
@@ -10,6 +11,13 @@ import { basename, extname, posix } from 'node:path';
|
|
|
10
11
|
* The result is deduplicated by path (last write wins, so `extraFiles` can
|
|
11
12
|
* override anything the defaults staged) and sorted, which makes the tree —
|
|
12
13
|
* and therefore every artifact built from it — deterministic.
|
|
14
|
+
*
|
|
15
|
+
* ONE plan, in the Linux/XDG shape, for every OS. Where those paths end up is
|
|
16
|
+
* `utils/ship/layout.ts`'s `placeStage`, applied afterwards by the caller, and
|
|
17
|
+
* keeping the two steps apart is what makes ADR 0024 § 2's "one payload, a
|
|
18
|
+
* handful of layouts" checkable: `tests/e2e/ship-layout` asserts the three
|
|
19
|
+
* staged trees agree modulo that map. A planner that took the layout would have
|
|
20
|
+
* three code paths to keep in agreement instead.
|
|
13
21
|
*/
|
|
14
22
|
export function planStage(settings, inputs) {
|
|
15
23
|
const files = [];
|
|
@@ -23,7 +31,7 @@ export function planStage(settings, inputs) {
|
|
|
23
31
|
});
|
|
24
32
|
}
|
|
25
33
|
files.push({
|
|
26
|
-
path:
|
|
34
|
+
path: `${SHARE.metainfo}/${settings.appId}.metainfo.xml`,
|
|
27
35
|
mode: 0o644,
|
|
28
36
|
source: { kind: 'text', text: inputs.metainfo },
|
|
29
37
|
});
|
|
@@ -32,7 +40,7 @@ export function planStage(settings, inputs) {
|
|
|
32
40
|
throw new Error('gjsify ship: internal error — a desktop entry is required for `kind: "app"`.');
|
|
33
41
|
}
|
|
34
42
|
files.push({
|
|
35
|
-
path:
|
|
43
|
+
path: `${SHARE.applications}/${settings.appId}.desktop`,
|
|
36
44
|
mode: 0o644,
|
|
37
45
|
source: { kind: 'text', text: inputs.desktopEntry },
|
|
38
46
|
});
|
|
@@ -50,7 +58,7 @@ export function planStage(settings, inputs) {
|
|
|
50
58
|
'Every installed schema shares one system directory, so the app id is what keeps it unique.');
|
|
51
59
|
}
|
|
52
60
|
files.push({
|
|
53
|
-
path:
|
|
61
|
+
path: `${SHARE.schemas}/${name}`,
|
|
54
62
|
mode: 0o644,
|
|
55
63
|
source: { kind: 'file', path: schema },
|
|
56
64
|
});
|
|
@@ -71,7 +79,7 @@ export function planStage(settings, inputs) {
|
|
|
71
79
|
// reason it is for the GSettings schema: a generic name collides with another package's file.
|
|
72
80
|
if (settings.mimeTypes.length > 0) {
|
|
73
81
|
files.push({
|
|
74
|
-
path:
|
|
82
|
+
path: `${SHARE.mime}/${settings.appId}.xml`,
|
|
75
83
|
mode: 0o644,
|
|
76
84
|
source: { kind: 'text', text: renderMimePackage(settings.mimeTypes) },
|
|
77
85
|
});
|
|
@@ -80,7 +88,7 @@ export function planStage(settings, inputs) {
|
|
|
80
88
|
// is where `bindtextdomain` looks, and nowhere else.
|
|
81
89
|
for (const locale of settings.localeFiles) {
|
|
82
90
|
files.push({
|
|
83
|
-
path: posix.join(
|
|
91
|
+
path: posix.join(SHARE.locale, locale.rel),
|
|
84
92
|
mode: 0o644,
|
|
85
93
|
source: { kind: 'file', path: locale.abs },
|
|
86
94
|
});
|
|
@@ -178,7 +186,7 @@ function planIcons(settings) {
|
|
|
178
186
|
for (const icon of settings.iconFiles) {
|
|
179
187
|
const ext = extname(icon);
|
|
180
188
|
const dir = iconSizeDir(icon);
|
|
181
|
-
const path =
|
|
189
|
+
const path = `${SHARE.icons}/${dir}/apps/${settings.appId}${ext}`;
|
|
182
190
|
const previous = seen.get(path);
|
|
183
191
|
if (previous !== undefined) {
|
|
184
192
|
throw new Error(`gjsify ship: ${icon} and ${previous} both install as ${path}. ` +
|
|
@@ -226,8 +234,13 @@ function assertInsidePrefix(dest) {
|
|
|
226
234
|
* planner is pure and has no bytes to look at. The two answer different
|
|
227
235
|
* questions — "should this be 0755" versus "is this package portable" — and
|
|
228
236
|
* getting this one wrong costs a mode bit, not an unusable package.
|
|
237
|
+
*
|
|
238
|
+
* EXPORTED for `utils/ship/app-runtime.ts`, which stages a relocated GTK closure
|
|
239
|
+
* that never passes through this plan and needs the same answer for the same
|
|
240
|
+
* files. A second copy of the pattern there would be a second answer to "is this
|
|
241
|
+
* a shared library", and the drifted one is the one that stages a `.dylib` 0644.
|
|
229
242
|
*/
|
|
230
|
-
function isExecutableAsset(rel) {
|
|
243
|
+
export function isExecutableAsset(rel) {
|
|
231
244
|
return /\.(so|node|dylib)(\.\d+)*$/.test(rel);
|
|
232
245
|
}
|
|
233
246
|
/** Last write wins, then sort — deterministic output for a deterministic artifact. */
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { LayoutMetadataInput } from './layout.js';
|
|
2
|
+
/** Where the two files live inside a bundle. Apple's names, not ours. */
|
|
3
|
+
export declare const BUNDLE_INFO_PLIST = "Contents/Info.plist";
|
|
4
|
+
export declare const BUNDLE_PKGINFO = "Contents/PkgInfo";
|
|
5
|
+
/**
|
|
6
|
+
* Render `Contents/Info.plist`.
|
|
7
|
+
*
|
|
8
|
+
* The eleven keys, each with the file in `refs/` it was read off (all paths
|
|
9
|
+
* relative to the repository root, all read-only submodules):
|
|
10
|
+
*
|
|
11
|
+
* | key | value | citation |
|
|
12
|
+
* |---|---|---|
|
|
13
|
+
* | `CFBundleExecutable` | `binaryName` | `refs/node/test/fixtures/macos-app-sandbox/Info.plist:5` |
|
|
14
|
+
* | `CFBundleIdentifier` | `appId` | same file, line 7 |
|
|
15
|
+
* | `CFBundleInfoDictionaryVersion` | `6.0` | same file, line 9 |
|
|
16
|
+
* | `CFBundleName` | `name` | same file, line 11 |
|
|
17
|
+
* | `CFBundlePackageType` | `APPL` | same file, line 13 |
|
|
18
|
+
* | `CFBundleShortVersionString` | `version` | same file, line 15 |
|
|
19
|
+
* | `CFBundleSupportedPlatforms` | `["MacOSX"]` | same file, lines 17-20 |
|
|
20
|
+
* | `CFBundleVersion` | `version-release` | same file, line 21 |
|
|
21
|
+
* | `CFBundleDevelopmentRegion` | `en` | `refs/node/deps/v8/gni/Info.plist:5` |
|
|
22
|
+
* | `CFBundleDisplayName` | `name` | same file, line 7 |
|
|
23
|
+
* | `CFBundleSignature` | `????` | same file, line 21 |
|
|
24
|
+
*
|
|
25
|
+
* `CFBundleVersion` carries the RELEASE and `CFBundleShortVersionString` does
|
|
26
|
+
* not, which is the one place the two differ. Apple's rule is that the short
|
|
27
|
+
* string is the marketing version a user sees and `CFBundleVersion` is the build
|
|
28
|
+
* — and this command already has that exact pair: `version` is upstream's,
|
|
29
|
+
* `release` is the packaging revision within it (the deb revision, the rpm
|
|
30
|
+
* release). Emitting `version` twice would throw the distinction away on the one
|
|
31
|
+
* OS that has a field for it.
|
|
32
|
+
*
|
|
33
|
+
* KEYS DELIBERATELY ABSENT are listed in this module's header. `CFBundleIconFile`
|
|
34
|
+
* is the one a reader will reach for first, and it is absent for a reason that
|
|
35
|
+
* outlives the key: M2a ships NO icon. `png2icns`, `icnsutil` and `iconutil` are
|
|
36
|
+
* all absent from this workstation and from the CI image, so an `.icns` written
|
|
37
|
+
* here could only be read back by a reader written here — `selfReading: true`,
|
|
38
|
+
* which `flatpak.spec.ts` reds. The unblocker is an independent Linux ICNS reader
|
|
39
|
+
* entering the CI image; until one does, the hicolor PNG/SVG the payload already
|
|
40
|
+
* carries stays the only icon, unread on macOS.
|
|
41
|
+
*/
|
|
42
|
+
export declare function renderInfoPlist(input: LayoutMetadataInput): string;
|
|
43
|
+
/**
|
|
44
|
+
* Render `Contents/PkgInfo`: the package type and the signature, eight bytes, no
|
|
45
|
+
* newline.
|
|
46
|
+
*
|
|
47
|
+
* `refs/node/tools/gyp/pylib/gyp/mac_tool.py:236-245` is the whole
|
|
48
|
+
* specification — "eight characters, representing the bundle type and bundle
|
|
49
|
+
* signature, each four characters" — and its
|
|
50
|
+
* `fp.write(f"{package_type}{signature_code}")` writes no terminator. A trailing
|
|
51
|
+
* newline would make the file nine bytes, which is the kind of difference
|
|
52
|
+
* nothing complains about and every byte comparison notices.
|
|
53
|
+
*
|
|
54
|
+
* Redundant with `Info.plist` by design: it is the pre-plist Carbon record that
|
|
55
|
+
* LaunchServices still reads, and gyp writes both from one source for exactly
|
|
56
|
+
* the same reason this function takes none — both values are constants here.
|
|
57
|
+
*/
|
|
58
|
+
export declare function renderPkgInfo(): string;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// `Contents/Info.plist` and `Contents/PkgInfo` — what makes a `*.app` directory
|
|
2
|
+
// an application BUNDLE rather than a directory whose name ends in `.app`.
|
|
3
|
+
//
|
|
4
|
+
// M1 staged `<Name>.app/Contents/{MacOS,Resources}` with no `Info.plist` at all.
|
|
5
|
+
// LaunchServices reads that file to learn which file under `Contents/MacOS` to
|
|
6
|
+
// exec and what identity to register the bundle under, so without it the tree is
|
|
7
|
+
// a directory the Finder shows as a folder — the hole #1354 M2a closes.
|
|
8
|
+
//
|
|
9
|
+
// XML, never the binary variant, and that is an ORACLE decision rather than a
|
|
10
|
+
// taste one. `plistlib` reads both, but a binary plist this tree wrote and then
|
|
11
|
+
// read back with a reader this tree also wrote is `selfReading: true` — ADR 0024
|
|
12
|
+
// § A3 calls that legal to declare and illegal to release. CPython's `plistlib`
|
|
13
|
+
// is a different implementation family and is already precedent here
|
|
14
|
+
// (`.github/ship-oracle/verify-modes.py`), and it parses the XML form.
|
|
15
|
+
//
|
|
16
|
+
// EVERY KEY BELOW IS CITED to a file in `refs/` that a real macOS toolchain
|
|
17
|
+
// produced or consumes. Keys that are merely plausible — `CFBundleIconFile`,
|
|
18
|
+
// `LSMinimumSystemVersion`, `NSHighResolutionCapable`, `LSApplicationCategoryType`,
|
|
19
|
+
// `NSHumanReadableCopyright` — are not emitted, because nothing a reader here can
|
|
20
|
+
// open contains them.
|
|
21
|
+
//
|
|
22
|
+
// AND THE SCOPE OF THAT MEASUREMENT IS PART OF IT, because `grep -r … refs/` is a
|
|
23
|
+
// command that answers cheerfully after reading almost nothing: 89 of the 95
|
|
24
|
+
// submodules `.gitmodules` declares are not checked out in a working tree, and an
|
|
25
|
+
// empty directory produces zero hits with no error. Measured on a checkout with
|
|
26
|
+
// SIX of them present (`cambalache`, `gtkx`, `libadwaita`, `metro`, `node`,
|
|
27
|
+
// `peachy`): 0 files for each of the five keys above, against 3 files for
|
|
28
|
+
// `CFBundleSignature` as the CONTROL — a string known to be there, which is what
|
|
29
|
+
// makes the zeros mean anything. So the claim is "absent from the six pools a
|
|
30
|
+
// reader can open", not "absent from the pool", and a key that turns up in a
|
|
31
|
+
// seventh is a citation waiting to be written, not a contradiction.
|
|
32
|
+
/** Where the two files live inside a bundle. Apple's names, not ours. */
|
|
33
|
+
export const BUNDLE_INFO_PLIST = 'Contents/Info.plist';
|
|
34
|
+
export const BUNDLE_PKGINFO = 'Contents/PkgInfo';
|
|
35
|
+
/**
|
|
36
|
+
* The four-character bundle type of an application, and the signature that says
|
|
37
|
+
* "none".
|
|
38
|
+
*
|
|
39
|
+
* `refs/node/tools/gyp/pylib/gyp/mac_tool.py:232` reads `CFBundlePackageType` and
|
|
40
|
+
* writes a `PkgInfo` only when it is `APPL`; line 239 defaults
|
|
41
|
+
* `CFBundleSignature` to `????` and line 240 resets a signature of the wrong
|
|
42
|
+
* length to the same four question marks. `refs/node/deps/v8/gni/Info.plist:22`
|
|
43
|
+
* spells `????` out.
|
|
44
|
+
*/
|
|
45
|
+
const PACKAGE_TYPE = 'APPL';
|
|
46
|
+
const SIGNATURE = '????';
|
|
47
|
+
/**
|
|
48
|
+
* The one XML escape set a plist needs.
|
|
49
|
+
*
|
|
50
|
+
* `&` first, or the ampersands introduced by the later replacements get escaped
|
|
51
|
+
* twice and `CFBundleName` comes out as `Ship &amp; Co`. `<` and `>` are the
|
|
52
|
+
* element delimiters; `"` and `'` are not special in element CONTENT, which is
|
|
53
|
+
* the only place this function's output ever lands, so they are left alone
|
|
54
|
+
* rather than turned into entities a human reading the file has to decode.
|
|
55
|
+
*/
|
|
56
|
+
function xmlText(value) {
|
|
57
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Refuse a value a plist cannot carry, naming the field.
|
|
61
|
+
*
|
|
62
|
+
* XML 1.0 has no escape for most C0 control characters — they are not
|
|
63
|
+
* representable in a document at all, not merely inconvenient — so a name
|
|
64
|
+
* carrying one produces a file `plistlib` and `CFPropertyList` both reject. The
|
|
65
|
+
* refusal is here rather than at parse time on a Mac three milestones away.
|
|
66
|
+
*
|
|
67
|
+
* C0 MINUS TAB/LF/CR, AND NOTHING ELSE — measured against the oracle rather than
|
|
68
|
+
* reasoned from the spec, because the first cut of this guard also refused DEL
|
|
69
|
+
* and the C1 range on the stated grounds that XML forbids them, and XML 1.0 does
|
|
70
|
+
* not. Probed through `plistlib` on CPython 3.14, one code point per document:
|
|
71
|
+
*
|
|
72
|
+
* U+0001, U+0008, U+000B ExpatError: not well-formed (invalid token)
|
|
73
|
+
* U+0009 (TAB) accepted
|
|
74
|
+
* U+007F (DEL) accepted
|
|
75
|
+
* U+0085, U+009F (C1) accepted
|
|
76
|
+
*
|
|
77
|
+
* So refusing those four would reject a name every reader in the chain reads
|
|
78
|
+
* back correctly — a guard that turns working packages into failures, which buys
|
|
79
|
+
* nothing over the defect it prevents. The rule that survives is the measured
|
|
80
|
+
* one; the reason that did not is recorded here rather than quietly dropped,
|
|
81
|
+
* because a rule carrying an invented reason is one that gets "simplified" back
|
|
82
|
+
* into the bug.
|
|
83
|
+
*/
|
|
84
|
+
function assertPlistText(value, field, configKey) {
|
|
85
|
+
// A SCAN rather than a character class, and not only to keep `no-control-regex`
|
|
86
|
+
// quiet: the code point is what the message needs, and lifting it back out of
|
|
87
|
+
// a match is a second step that can disagree with the first.
|
|
88
|
+
let point = -1;
|
|
89
|
+
for (const char of value) {
|
|
90
|
+
const code = char.codePointAt(0) ?? 0;
|
|
91
|
+
// Tab, LF and CR are the three C0 code points XML 1.0 does carry.
|
|
92
|
+
if (code < 0x20 && code !== 0x09 && code !== 0x0a && code !== 0x0d) {
|
|
93
|
+
point = code;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (point !== -1) {
|
|
98
|
+
throw new Error(`gjsify ship: ${field} would carry the control character ` +
|
|
99
|
+
`U+${point.toString(16).padStart(4, '0').toUpperCase()}, and XML has no escape for it — the ` +
|
|
100
|
+
'Info.plist would be unparseable by every reader macOS has. Set ' +
|
|
101
|
+
`\`${configKey}\` to a value without it.`);
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
/** One `<key>…</key><string>…</string>` pair, indented to sit inside the top-level `<dict>`. */
|
|
106
|
+
function stringEntry(key, value) {
|
|
107
|
+
return `\t<key>${key}</key>\n\t<string>${xmlText(value)}</string>`;
|
|
108
|
+
}
|
|
109
|
+
/** `CFBundleSupportedPlatforms` is the one array here — see the key table below. */
|
|
110
|
+
function arrayEntry(key, values) {
|
|
111
|
+
const items = values.map((value) => `\t\t<string>${xmlText(value)}</string>`).join('\n');
|
|
112
|
+
return `\t<key>${key}</key>\n\t<array>\n${items}\n\t</array>`;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Render `Contents/Info.plist`.
|
|
116
|
+
*
|
|
117
|
+
* The eleven keys, each with the file in `refs/` it was read off (all paths
|
|
118
|
+
* relative to the repository root, all read-only submodules):
|
|
119
|
+
*
|
|
120
|
+
* | key | value | citation |
|
|
121
|
+
* |---|---|---|
|
|
122
|
+
* | `CFBundleExecutable` | `binaryName` | `refs/node/test/fixtures/macos-app-sandbox/Info.plist:5` |
|
|
123
|
+
* | `CFBundleIdentifier` | `appId` | same file, line 7 |
|
|
124
|
+
* | `CFBundleInfoDictionaryVersion` | `6.0` | same file, line 9 |
|
|
125
|
+
* | `CFBundleName` | `name` | same file, line 11 |
|
|
126
|
+
* | `CFBundlePackageType` | `APPL` | same file, line 13 |
|
|
127
|
+
* | `CFBundleShortVersionString` | `version` | same file, line 15 |
|
|
128
|
+
* | `CFBundleSupportedPlatforms` | `["MacOSX"]` | same file, lines 17-20 |
|
|
129
|
+
* | `CFBundleVersion` | `version-release` | same file, line 21 |
|
|
130
|
+
* | `CFBundleDevelopmentRegion` | `en` | `refs/node/deps/v8/gni/Info.plist:5` |
|
|
131
|
+
* | `CFBundleDisplayName` | `name` | same file, line 7 |
|
|
132
|
+
* | `CFBundleSignature` | `????` | same file, line 21 |
|
|
133
|
+
*
|
|
134
|
+
* `CFBundleVersion` carries the RELEASE and `CFBundleShortVersionString` does
|
|
135
|
+
* not, which is the one place the two differ. Apple's rule is that the short
|
|
136
|
+
* string is the marketing version a user sees and `CFBundleVersion` is the build
|
|
137
|
+
* — and this command already has that exact pair: `version` is upstream's,
|
|
138
|
+
* `release` is the packaging revision within it (the deb revision, the rpm
|
|
139
|
+
* release). Emitting `version` twice would throw the distinction away on the one
|
|
140
|
+
* OS that has a field for it.
|
|
141
|
+
*
|
|
142
|
+
* KEYS DELIBERATELY ABSENT are listed in this module's header. `CFBundleIconFile`
|
|
143
|
+
* is the one a reader will reach for first, and it is absent for a reason that
|
|
144
|
+
* outlives the key: M2a ships NO icon. `png2icns`, `icnsutil` and `iconutil` are
|
|
145
|
+
* all absent from this workstation and from the CI image, so an `.icns` written
|
|
146
|
+
* here could only be read back by a reader written here — `selfReading: true`,
|
|
147
|
+
* which `flatpak.spec.ts` reds. The unblocker is an independent Linux ICNS reader
|
|
148
|
+
* entering the CI image; until one does, the hicolor PNG/SVG the payload already
|
|
149
|
+
* carries stays the only icon, unread on macOS.
|
|
150
|
+
*/
|
|
151
|
+
export function renderInfoPlist(input) {
|
|
152
|
+
const name = assertPlistText(input.name, 'CFBundleName', 'gjsify.ship.name');
|
|
153
|
+
const executable = assertPlistText(input.binaryName, 'CFBundleExecutable', 'gjsify.ship.binaryName');
|
|
154
|
+
const appId = assertPlistText(input.appId, 'CFBundleIdentifier', 'gjsify.ship.appId');
|
|
155
|
+
return [
|
|
156
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
157
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
158
|
+
'<plist version="1.0">',
|
|
159
|
+
'<dict>',
|
|
160
|
+
// Alphabetical, because both cited files are: `macos-app-sandbox/Info.plist`
|
|
161
|
+
// runs Executable → Identifier → InfoDictionaryVersion → Name → PackageType
|
|
162
|
+
// → ShortVersionString → SupportedPlatforms → Version, and `v8/gni`'s
|
|
163
|
+
// CFBundle keys sort the same way. Matching them keeps a diff against a
|
|
164
|
+
// real bundle readable; no claim is made about what Xcode emits.
|
|
165
|
+
stringEntry('CFBundleDevelopmentRegion', 'en'),
|
|
166
|
+
stringEntry('CFBundleDisplayName', name),
|
|
167
|
+
stringEntry('CFBundleExecutable', executable),
|
|
168
|
+
stringEntry('CFBundleIdentifier', appId),
|
|
169
|
+
stringEntry('CFBundleInfoDictionaryVersion', '6.0'),
|
|
170
|
+
stringEntry('CFBundleName', name),
|
|
171
|
+
stringEntry('CFBundlePackageType', PACKAGE_TYPE),
|
|
172
|
+
stringEntry('CFBundleShortVersionString', input.version),
|
|
173
|
+
stringEntry('CFBundleSignature', SIGNATURE),
|
|
174
|
+
arrayEntry('CFBundleSupportedPlatforms', ['MacOSX']),
|
|
175
|
+
stringEntry('CFBundleVersion', `${input.version}-${input.release}`),
|
|
176
|
+
'</dict>',
|
|
177
|
+
'</plist>',
|
|
178
|
+
'',
|
|
179
|
+
].join('\n');
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Render `Contents/PkgInfo`: the package type and the signature, eight bytes, no
|
|
183
|
+
* newline.
|
|
184
|
+
*
|
|
185
|
+
* `refs/node/tools/gyp/pylib/gyp/mac_tool.py:236-245` is the whole
|
|
186
|
+
* specification — "eight characters, representing the bundle type and bundle
|
|
187
|
+
* signature, each four characters" — and its
|
|
188
|
+
* `fp.write(f"{package_type}{signature_code}")` writes no terminator. A trailing
|
|
189
|
+
* newline would make the file nine bytes, which is the kind of difference
|
|
190
|
+
* nothing complains about and every byte comparison notices.
|
|
191
|
+
*
|
|
192
|
+
* Redundant with `Info.plist` by design: it is the pre-plist Carbon record that
|
|
193
|
+
* LaunchServices still reads, and gyp writes both from one source for exactly
|
|
194
|
+
* the same reason this function takes none — both values are constants here.
|
|
195
|
+
*/
|
|
196
|
+
export function renderPkgInfo() {
|
|
197
|
+
return `${PACKAGE_TYPE}${SIGNATURE}`;
|
|
198
|
+
}
|
package/lib/utils/ship/rpm.js
CHANGED
|
@@ -19,6 +19,7 @@ import { readPayloadFacts, readShebangInterpreters } from './payload.js';
|
|
|
19
19
|
import { buildRpmHeader, buildRpmLead, padToEight, RpmType, RPM_TAG_HEADERIMMUTABLE, RPM_TAG_HEADERSIGNATURES, } from './rpm-header.js';
|
|
20
20
|
import { gzipDeterministic } from './gzip.js';
|
|
21
21
|
import { renderRpmScriptlets } from './scripts.js';
|
|
22
|
+
import { SHARE } from './share-dirs.js';
|
|
22
23
|
// Header tags. Named rather than inlined because a wrong NUMBER produces a
|
|
23
24
|
// package that parses and is quietly wrong, which is the hardest kind to spot.
|
|
24
25
|
const TAG = {
|
|
@@ -115,6 +116,11 @@ const RPMLIB_REQUIRES = [
|
|
|
115
116
|
* rpmlint noise, not a broken install. The entries that matter are the ones a
|
|
116
117
|
* package would otherwise claim on every install.
|
|
117
118
|
*/
|
|
119
|
+
// The three that name a directory `plan.ts` actually stages into are DERIVED, so
|
|
120
|
+
// they cannot drift from it. The rest stay literal on purpose: they are PARENTS
|
|
121
|
+
// (`/usr/share/icons`, `/usr/share/glib-2.0`) or directories this packer never
|
|
122
|
+
// writes (`/etc`, `/opt`, `/var`, `/usr/share/man`), which is a different question
|
|
123
|
+
// from "where does the payload install" and would be wrong to fold into one list.
|
|
118
124
|
const SYSTEM_OWNED_DIRECTORIES = new Set([
|
|
119
125
|
'/usr',
|
|
120
126
|
'/usr/bin',
|
|
@@ -123,16 +129,16 @@ const SYSTEM_OWNED_DIRECTORIES = new Set([
|
|
|
123
129
|
'/usr/libexec',
|
|
124
130
|
'/usr/sbin',
|
|
125
131
|
'/usr/share',
|
|
126
|
-
|
|
132
|
+
`/usr/${SHARE.applications}`,
|
|
127
133
|
'/usr/share/doc',
|
|
128
134
|
'/usr/share/icons',
|
|
129
135
|
'/usr/share/licenses',
|
|
130
136
|
'/usr/share/man',
|
|
131
|
-
|
|
137
|
+
`/usr/${SHARE.metainfo}`,
|
|
132
138
|
// Owned by glib2 — an app that installs a schema must not claim the
|
|
133
139
|
// directory the whole system's schemas live in. Verified with `rpm -qf`.
|
|
134
140
|
'/usr/share/glib-2.0',
|
|
135
|
-
|
|
141
|
+
`/usr/${SHARE.schemas}`,
|
|
136
142
|
'/etc',
|
|
137
143
|
'/opt',
|
|
138
144
|
'/var',
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { StagedFile } from './types.js';
|
|
2
|
+
/** The one spelling of the tool, read by this module AND by the format rows. */
|
|
3
|
+
export declare const SCHEMA_COMPILER = "glib-compile-schemas";
|
|
4
|
+
/**
|
|
5
|
+
* How to install it, in the two distributions' words.
|
|
6
|
+
*
|
|
7
|
+
* The same two package names `depends.ts`'s `SCHEMA_COMPILER_PACKAGE` emits into
|
|
8
|
+
* a `Depends:`/`Requires:`, so the advice a user gets here and the dependency a
|
|
9
|
+
* `.deb` declares cannot name different packages.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SCHEMA_COMPILER_HINT = "Fedora: `sudo dnf install glib2`, Debian/Ubuntu: `sudo apt install libglib2.0-bin`";
|
|
12
|
+
/** The compiled cache's prefix-relative path — one definition, several readers. */
|
|
13
|
+
export declare const SCHEMA_CACHE: string;
|
|
14
|
+
export interface CompileSchemasInput {
|
|
15
|
+
/** Absolute paths of the project's `*.gschema.xml` files. */
|
|
16
|
+
schemaFiles: readonly string[];
|
|
17
|
+
/** A scratch directory this function owns; wiped and rewritten on every run. */
|
|
18
|
+
workDir: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Compile the payload's schemas and return the cache as ONE prefix-relative
|
|
22
|
+
* planned file, or nothing when the payload has no schema.
|
|
23
|
+
*
|
|
24
|
+
* PREFIX-RELATIVE, so `place()` maps it into `Contents/Resources/share/…` or a
|
|
25
|
+
* Windows `share\` with no rule of its own. That is the whole reason this returns
|
|
26
|
+
* a `StagedFile` rather than writing into the stage directly: the layout map stays
|
|
27
|
+
* the single place that knows where `share/` goes, and the e2e's hand-written map
|
|
28
|
+
* needs no new entry to cover it.
|
|
29
|
+
*
|
|
30
|
+
* `--strict` IS LOAD-BEARING and was measured, not assumed. Against a schema whose
|
|
31
|
+
* `path` attribute lacks its leading slash, on glib 2.88.3 — the observable is the
|
|
32
|
+
* exit code and the file, not the wording, because the diagnostic is translated
|
|
33
|
+
* and was read here in German:
|
|
34
|
+
*
|
|
35
|
+
* glib-compile-schemas in --targetdir out → exit 0, warns that the
|
|
36
|
+
* whole file was ignored,
|
|
37
|
+
* gschemas.compiled WRITTEN
|
|
38
|
+
* glib-compile-schemas in --targetdir out --strict → exit 1, nothing written
|
|
39
|
+
*
|
|
40
|
+
* Without the flag a broken schema is skipped at exit 0 and a cache is produced
|
|
41
|
+
* that simply does not contain it — so the stage looks compiled, the warning goes
|
|
42
|
+
* away, and `g_settings_new()` still aborts on the schema that was dropped. That
|
|
43
|
+
* is a green that checked nothing, produced by the step meant to close the hole.
|
|
44
|
+
*
|
|
45
|
+
* A DIRECTORY, because that is the only input the tool takes: the schemas are
|
|
46
|
+
* copied into `workDir` under their own basenames first. `plan.ts` has already
|
|
47
|
+
* refused any basename not prefixed with the app id, so two schemas cannot
|
|
48
|
+
* collide here.
|
|
49
|
+
*
|
|
50
|
+
* ENDIANNESS, stated rather than assumed away. The output is GVDB, and the file
|
|
51
|
+
* this tree produces is HOST-endian — measured on x86-64 with glib 2.88.3: in a
|
|
52
|
+
* 256-byte `gschemas.compiled` compiled from one schema with one key, the eight
|
|
53
|
+
* bytes at offset 16 read `18 00 00 00 58 00 00 00`, i.e. the pointer pair 24 and
|
|
54
|
+
* 88 stored little-endian. Whether GLib's reader byte-swaps a foreign-endian
|
|
55
|
+
* cache is UNVERIFIED here — nothing in this tree tested it and nothing below
|
|
56
|
+
* assumes an answer. It does not bite today: both darwin targets (x64, arm64) and
|
|
57
|
+
* every runner in this project's CI are little-endian, so no cache yet crosses
|
|
58
|
+
* that boundary. A big-endian assembling host is the case to measure before
|
|
59
|
+
* anyone claims otherwise.
|
|
60
|
+
*/
|
|
61
|
+
export declare function compileSchemasForStage(input: CompileSchemasInput): Promise<StagedFile[]>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Compiling `gschemas.compiled` into a stage that has no install step.
|
|
2
|
+
//
|
|
3
|
+
// THE DEFECT THIS CLOSES is not cosmetic and `payload.ts` already named it:
|
|
4
|
+
// every launcher form exports `XDG_DATA_DIRS` at the staged `share/`, and
|
|
5
|
+
// GSettings ABORTS on a schema directory holding a `.gschema.xml` with no
|
|
6
|
+
// `gschemas.compiled` beside it. On Linux the `.deb`/`.rpm` postinst runs
|
|
7
|
+
// `glib-compile-schemas` at install (`scripts.ts`); a `.app` has no postinst and
|
|
8
|
+
// nothing else was going to run it. So `gjsify ship darwin --stage` produced a
|
|
9
|
+
// bundle whose first `Gio.Settings.new()` kills the process — an artifact that
|
|
10
|
+
// installs and does not start, which is the class this whole command exists
|
|
11
|
+
// against.
|
|
12
|
+
//
|
|
13
|
+
// AT STAGE TIME, not at pack time, and that is the decision worth stating.
|
|
14
|
+
// A stage is a deliverable in its own right — it is what crosses to the host
|
|
15
|
+
// that finishes a `.dmg` or an `.msi` — and the warning `gjsify ship` prints
|
|
16
|
+
// about it is a claim about the TREE. Leaving the compile to a packer would keep
|
|
17
|
+
// `--stage` producing an aborting bundle while the message said otherwise.
|
|
18
|
+
//
|
|
19
|
+
// The cost is that assembly now execs one tool for a non-Linux layout with
|
|
20
|
+
// schemas in the payload. That does not make assembly host-BOUND (ADR 0024 § A1):
|
|
21
|
+
// `glib-compile-schemas` is GLib's and runs on all three OSes, which is why the
|
|
22
|
+
// four rows that ASSEMBLE a non-Linux tree — the `.app`, its zip, the Windows
|
|
23
|
+
// program directory and its zip — stay `finishOn: 'any'` and declare the tool
|
|
24
|
+
// through `requiredTools` instead. The `.dmg` and the `.msi` deliberately do not
|
|
25
|
+
// declare it: they wrap a tree that is already assembled, so a `--from-stage`
|
|
26
|
+
// pack of one needs no GLib at all (the `macos-app-dmg` row says so).
|
|
27
|
+
import { execFile } from 'node:child_process';
|
|
28
|
+
import { basename, join } from 'node:path';
|
|
29
|
+
import { copyFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
30
|
+
import { promisify } from 'node:util';
|
|
31
|
+
import { SHARE } from './share-dirs.js';
|
|
32
|
+
const execFileAsync = promisify(execFile);
|
|
33
|
+
/** The one spelling of the tool, read by this module AND by the format rows. */
|
|
34
|
+
export const SCHEMA_COMPILER = 'glib-compile-schemas';
|
|
35
|
+
/**
|
|
36
|
+
* How to install it, in the two distributions' words.
|
|
37
|
+
*
|
|
38
|
+
* The same two package names `depends.ts`'s `SCHEMA_COMPILER_PACKAGE` emits into
|
|
39
|
+
* a `Depends:`/`Requires:`, so the advice a user gets here and the dependency a
|
|
40
|
+
* `.deb` declares cannot name different packages.
|
|
41
|
+
*/
|
|
42
|
+
export const SCHEMA_COMPILER_HINT = 'Fedora: `sudo dnf install glib2`, Debian/Ubuntu: `sudo apt install libglib2.0-bin`';
|
|
43
|
+
/** The compiled cache's prefix-relative path — one definition, several readers. */
|
|
44
|
+
export const SCHEMA_CACHE = `${SHARE.schemas}/gschemas.compiled`;
|
|
45
|
+
/**
|
|
46
|
+
* Compile the payload's schemas and return the cache as ONE prefix-relative
|
|
47
|
+
* planned file, or nothing when the payload has no schema.
|
|
48
|
+
*
|
|
49
|
+
* PREFIX-RELATIVE, so `place()` maps it into `Contents/Resources/share/…` or a
|
|
50
|
+
* Windows `share\` with no rule of its own. That is the whole reason this returns
|
|
51
|
+
* a `StagedFile` rather than writing into the stage directly: the layout map stays
|
|
52
|
+
* the single place that knows where `share/` goes, and the e2e's hand-written map
|
|
53
|
+
* needs no new entry to cover it.
|
|
54
|
+
*
|
|
55
|
+
* `--strict` IS LOAD-BEARING and was measured, not assumed. Against a schema whose
|
|
56
|
+
* `path` attribute lacks its leading slash, on glib 2.88.3 — the observable is the
|
|
57
|
+
* exit code and the file, not the wording, because the diagnostic is translated
|
|
58
|
+
* and was read here in German:
|
|
59
|
+
*
|
|
60
|
+
* glib-compile-schemas in --targetdir out → exit 0, warns that the
|
|
61
|
+
* whole file was ignored,
|
|
62
|
+
* gschemas.compiled WRITTEN
|
|
63
|
+
* glib-compile-schemas in --targetdir out --strict → exit 1, nothing written
|
|
64
|
+
*
|
|
65
|
+
* Without the flag a broken schema is skipped at exit 0 and a cache is produced
|
|
66
|
+
* that simply does not contain it — so the stage looks compiled, the warning goes
|
|
67
|
+
* away, and `g_settings_new()` still aborts on the schema that was dropped. That
|
|
68
|
+
* is a green that checked nothing, produced by the step meant to close the hole.
|
|
69
|
+
*
|
|
70
|
+
* A DIRECTORY, because that is the only input the tool takes: the schemas are
|
|
71
|
+
* copied into `workDir` under their own basenames first. `plan.ts` has already
|
|
72
|
+
* refused any basename not prefixed with the app id, so two schemas cannot
|
|
73
|
+
* collide here.
|
|
74
|
+
*
|
|
75
|
+
* ENDIANNESS, stated rather than assumed away. The output is GVDB, and the file
|
|
76
|
+
* this tree produces is HOST-endian — measured on x86-64 with glib 2.88.3: in a
|
|
77
|
+
* 256-byte `gschemas.compiled` compiled from one schema with one key, the eight
|
|
78
|
+
* bytes at offset 16 read `18 00 00 00 58 00 00 00`, i.e. the pointer pair 24 and
|
|
79
|
+
* 88 stored little-endian. Whether GLib's reader byte-swaps a foreign-endian
|
|
80
|
+
* cache is UNVERIFIED here — nothing in this tree tested it and nothing below
|
|
81
|
+
* assumes an answer. It does not bite today: both darwin targets (x64, arm64) and
|
|
82
|
+
* every runner in this project's CI are little-endian, so no cache yet crosses
|
|
83
|
+
* that boundary. A big-endian assembling host is the case to measure before
|
|
84
|
+
* anyone claims otherwise.
|
|
85
|
+
*/
|
|
86
|
+
export async function compileSchemasForStage(input) {
|
|
87
|
+
if (input.schemaFiles.length === 0)
|
|
88
|
+
return [];
|
|
89
|
+
rmSync(input.workDir, { recursive: true, force: true, maxRetries: 5 });
|
|
90
|
+
mkdirSync(input.workDir, { recursive: true });
|
|
91
|
+
for (const schema of input.schemaFiles)
|
|
92
|
+
copyFileSync(schema, join(input.workDir, basename(schema)));
|
|
93
|
+
try {
|
|
94
|
+
await execFileAsync(SCHEMA_COMPILER, ['--strict', `--targetdir=${input.workDir}`, input.workDir]);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
const failure = error;
|
|
98
|
+
if (failure.code === 'ENOENT') {
|
|
99
|
+
throw new Error(`gjsify ship: this layout has no install step, so \`${SCHEMA_COMPILER}\` has to run HERE to ` +
|
|
100
|
+
`turn the payload's schemas into \`${SCHEMA_CACHE}\` — and it is not on PATH. ` +
|
|
101
|
+
`Install it (${SCHEMA_COMPILER_HINT}). Without the compiled cache the launcher points ` +
|
|
102
|
+
'XDG_DATA_DIRS at a schema directory GSettings aborts on, so the bundle would build and ' +
|
|
103
|
+
'then die at its first `Gio.Settings.new()`.');
|
|
104
|
+
}
|
|
105
|
+
throw new Error(`gjsify ship: \`${SCHEMA_COMPILER} --strict\` refused this project's schemas.\n` +
|
|
106
|
+
`${(failure.stderr ?? '').trimEnd()}\n` +
|
|
107
|
+
' `--strict` is deliberate: without it a malformed schema is SKIPPED at exit 0 and a ' +
|
|
108
|
+
'cache\n is written without it, so the app still aborts on the schema that was dropped. ' +
|
|
109
|
+
'Fix the\n file it names, or drop it from `gjsify.ship.schemas`.');
|
|
110
|
+
}
|
|
111
|
+
return [
|
|
112
|
+
{
|
|
113
|
+
path: SCHEMA_CACHE,
|
|
114
|
+
mode: 0o644,
|
|
115
|
+
source: { kind: 'file', path: join(input.workDir, 'gschemas.compiled') },
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// reading `rpm -qp --scripts` on the first artifact this packer produced —
|
|
12
12
|
// which is exactly the class of defect a rendered-output assertion would have
|
|
13
13
|
// waved through.
|
|
14
|
+
import { SHARE } from './share-dirs.js';
|
|
14
15
|
/**
|
|
15
16
|
* The cache refreshes this payload makes necessary, each guarded by the tool's
|
|
16
17
|
* presence and `|| true`.
|
|
@@ -34,13 +35,13 @@ export function cacheRefreshCommands(facts, prefix) {
|
|
|
34
35
|
if (facts.hasDesktopEntry) {
|
|
35
36
|
refreshes.push({
|
|
36
37
|
probe: 'update-desktop-database',
|
|
37
|
-
run: `update-desktop-database -q ${prefix}
|
|
38
|
+
run: `update-desktop-database -q ${prefix}/${SHARE.applications}`,
|
|
38
39
|
});
|
|
39
40
|
}
|
|
40
41
|
if (facts.hasIcons) {
|
|
41
42
|
refreshes.push({
|
|
42
43
|
probe: 'gtk-update-icon-cache',
|
|
43
|
-
run: `gtk-update-icon-cache -q -t -f ${prefix}
|
|
44
|
+
run: `gtk-update-icon-cache -q -t -f ${prefix}/${SHARE.icons}`,
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
47
|
if (facts.hasMimeTypes) {
|
|
@@ -48,13 +49,16 @@ export function cacheRefreshCommands(facts, prefix) {
|
|
|
48
49
|
// off the compiled cache in `share/mime/`, not off the packages directory.
|
|
49
50
|
refreshes.push({
|
|
50
51
|
probe: 'update-mime-database',
|
|
52
|
+
// The PARENT of `SHARE.mime`, not that directory: `update-mime-database` is
|
|
53
|
+
// given the mime ROOT and reads `packages/` inside it. Deriving it from
|
|
54
|
+
// `SHARE.mime` would pass the wrong path, so it stays spelled out.
|
|
51
55
|
run: `update-mime-database ${prefix}/share/mime`,
|
|
52
56
|
});
|
|
53
57
|
}
|
|
54
58
|
if (facts.hasSchemas) {
|
|
55
59
|
refreshes.push({
|
|
56
60
|
probe: 'glib-compile-schemas',
|
|
57
|
-
run: `glib-compile-schemas ${prefix}
|
|
61
|
+
run: `glib-compile-schemas ${prefix}/${SHARE.schemas}`,
|
|
58
62
|
});
|
|
59
63
|
}
|
|
60
64
|
return refreshes.map(({ probe, run }) => `if command -v ${probe} >/dev/null 2>&1; then ${run} || true; fi`);
|