@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,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`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AppMetadata, ConfigDataFlatpak, ConfigDataShip, DescriptionBlock } from '../../types/config-data.js';
|
|
2
|
-
import type { ShipSettings } from './types.js';
|
|
1
|
+
import type { AppMetadata, ConfigDataFlatpak, ConfigDataShip, DescriptionBlock, ShipAppOptions } from '../../types/config-data.js';
|
|
2
|
+
import type { HostOs, ShipSettings } from './types.js';
|
|
3
3
|
/** The subset of `package.json` the resolver reads. */
|
|
4
4
|
export interface ShipPackageManifest {
|
|
5
5
|
name?: string;
|
|
@@ -34,6 +34,8 @@ export interface DiscoveredPayload {
|
|
|
34
34
|
rel: string;
|
|
35
35
|
abs: string;
|
|
36
36
|
}[];
|
|
37
|
+
/** Absolute paths of the font faces the application ships itself. */
|
|
38
|
+
fontFiles: string[];
|
|
37
39
|
licenseFile?: string;
|
|
38
40
|
}
|
|
39
41
|
export interface SettingsInput {
|
|
@@ -42,12 +44,99 @@ export interface SettingsInput {
|
|
|
42
44
|
ship: ConfigDataShip;
|
|
43
45
|
/** Metadata fallback — `gjsify.flatpak` already carries these fields for many projects. */
|
|
44
46
|
flatpak: ConfigDataFlatpak;
|
|
47
|
+
/**
|
|
48
|
+
* What the command line decided before any config was read.
|
|
49
|
+
*
|
|
50
|
+
* `layoutOs` is REQUIRED, unlike its two neighbours, and the asymmetry is the
|
|
51
|
+
* point: `outDir` and `arch` have derived defaults that are correct when
|
|
52
|
+
* nobody says anything, while the layout is chosen by `gjsify ship <os>` (or
|
|
53
|
+
* by the host) before this function is reached. Making it optional would put
|
|
54
|
+
* a fourth default for it in here, and a second place that decides the layout
|
|
55
|
+
* is how the manifest's `target.os` and the staged paths come apart.
|
|
56
|
+
*/
|
|
45
57
|
cli: {
|
|
46
58
|
outDir?: string;
|
|
47
59
|
arch?: string;
|
|
60
|
+
layoutOs: HostOs;
|
|
48
61
|
};
|
|
49
62
|
discovered: DiscoveredPayload;
|
|
63
|
+
/**
|
|
64
|
+
* THIS TARGET's runtime, as {@link resolveShipApp} resolved it — never the
|
|
65
|
+
* raw `gjsify.app`.
|
|
66
|
+
*
|
|
67
|
+
* The TYPE is the enforcement: a caller handing the project field straight
|
|
68
|
+
* through no longer compiles. That is exactly the regression this field used
|
|
69
|
+
* to carry in silence, since every generator downstream reads `settings.app`.
|
|
70
|
+
*/
|
|
71
|
+
app: 'gjs' | 'node';
|
|
50
72
|
}
|
|
73
|
+
/** The runtime one ship target runs, and which key decided it. */
|
|
74
|
+
export interface ResolvedShipApp {
|
|
75
|
+
/** What the launcher execs, what the package depends on, and which formats can wrap it. */
|
|
76
|
+
app: 'gjs' | 'node';
|
|
77
|
+
/** The config key the answer came from — named in the refusal and in the notice. */
|
|
78
|
+
key: string;
|
|
79
|
+
/** Whether a per-target key overrode the project default. */
|
|
80
|
+
overridden: boolean;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The ONE place a ship target's runtime is decided: `gjsify.app` as the DEFAULT,
|
|
84
|
+
* `gjsify.ship.app.<os>` as this target's override.
|
|
85
|
+
*
|
|
86
|
+
* WHY IT IS PER TARGET, measured on a GTK4 application rather than derived from
|
|
87
|
+
* the schema: with one field, `gjsify ship darwin` refused its formats until
|
|
88
|
+
* `gjsify.app` was `"node"` — and setting it turned the LINUX `.deb`'s
|
|
89
|
+
* `Depends: gjs (>= 1.86)` into `Depends: nodejs (>= 24)`, which apt refuses on
|
|
90
|
+
* trixie, Ubuntu 24.04 and Ubuntu 26.04. A macOS decision made the Linux package
|
|
91
|
+
* uninstallable, because every generator read the PROJECT field instead of the
|
|
92
|
+
* resolved runtime of its OWN target (#1486, ADR 0024 § A22). Nothing downstream
|
|
93
|
+
* reads either key; they all read `settings.app`, which is this result.
|
|
94
|
+
*
|
|
95
|
+
* NOT PER LAYOUT BY DEFAULT, and the first cut of the layout axis got this
|
|
96
|
+
* exactly backwards. Reading ADR § 4's runtime table as a per-layout
|
|
97
|
+
* REQUIREMENT refused `gjsify.app: "gjs"` for the macOS layout — the entire
|
|
98
|
+
* audience of that command — while a project with no `gjsify.app` key sailed
|
|
99
|
+
* through and staged a launcher naming `node` in front of a GJS bundle. § 4
|
|
100
|
+
* derives the runtime a shipped artifact CARRIES, which is
|
|
101
|
+
* `Layout.shippedRuntime`; what an artifact runs is what its author says, which
|
|
102
|
+
* is this. So an absent override means the project field and never the layout's
|
|
103
|
+
* derived answer.
|
|
104
|
+
*
|
|
105
|
+
* MUST NOT be fed `Config.forBuild`'s resolution, whose fallback is the HOST
|
|
106
|
+
* runtime. That fallback is right for a build (`gjsify build` under Node should
|
|
107
|
+
* produce a Node bundle by default) and catastrophic for a package: running this
|
|
108
|
+
* command under Node would silently turn every undeclared GJS project into one
|
|
109
|
+
* whose launcher execs `node`. An undeclared target is the common case for a GJS
|
|
110
|
+
* app, and it means GJS.
|
|
111
|
+
*
|
|
112
|
+
* TWO REFUSALS, and each is here because the alternative to refusing is SILENT.
|
|
113
|
+
* A `browser` or `nativescript` value has no artifact to be — no process to
|
|
114
|
+
* start, an APK/IPA through another pipeline (§ 4) — and would otherwise leave
|
|
115
|
+
* here as a `.deb` a stranger installs and cannot run, with no gate between here
|
|
116
|
+
* and their machine; `Layout.runtimeGap` only WARNS because what it describes is
|
|
117
|
+
* a staged tree rather than an artifact, and the per-FORMAT half of the question
|
|
118
|
+
* is `assertFormatCanRunInterpreter`, which asks what a container can EXECUTE.
|
|
119
|
+
* A key outside {@link SHIP_APP_OSES} — `windows`, the `gjsify ship <os>`
|
|
120
|
+
* POSITIONAL's spelling — would resolve to nothing and leave that target on the
|
|
121
|
+
* project-wide answer with every gate green, which is why the author who typed
|
|
122
|
+
* it reads the result as gjsify not supporting the split at all. Both refusals
|
|
123
|
+
* name the KEY that carried the value: with two keys, "this project declares" no
|
|
124
|
+
* longer locates it.
|
|
125
|
+
*
|
|
126
|
+
* `gjsify ship --from-stage` reads neither key and needs neither: the stage
|
|
127
|
+
* manifest records the RESOLVED value, so the packing host reads an answer
|
|
128
|
+
* rather than a default, and `assertLauncherMatchesInterpreter` still compares
|
|
129
|
+
* the staged `bin/<name>`'s own `exec` line against the dependency about to be
|
|
130
|
+
* written.
|
|
131
|
+
*/
|
|
132
|
+
export declare function resolveShipApp(input: {
|
|
133
|
+
/** `gjsify.app` as DECLARED — `undefined` when the project declares nothing. */
|
|
134
|
+
project?: string;
|
|
135
|
+
/** `gjsify.ship.app`, the per-layout override table. */
|
|
136
|
+
perTarget?: ShipAppOptions;
|
|
137
|
+
/** The layout this run assembles. */
|
|
138
|
+
layoutOs: HostOs;
|
|
139
|
+
}): ResolvedShipApp;
|
|
51
140
|
export interface ResolvedSettings {
|
|
52
141
|
settings: ShipSettings;
|
|
53
142
|
/** The merged metadata the renderers read. */
|
|
@@ -11,9 +11,93 @@
|
|
|
11
11
|
// indistinguishable from a value the user typed and would clobber the config.
|
|
12
12
|
import { validateMimeTypes } from './mime.js';
|
|
13
13
|
import { basename } from 'node:path';
|
|
14
|
-
import { DEFAULT_GJS_FLOOR } from './depends.js';
|
|
14
|
+
import { DEFAULT_GJS_FLOOR, DEFAULT_NODE_FLOOR } from './depends.js';
|
|
15
15
|
import { resolveShipFlatpakSettings } from './flatpak-config.js';
|
|
16
16
|
import { assertRelease, normaliseVersion } from './version.js';
|
|
17
|
+
/**
|
|
18
|
+
* The OS keys `gjsify.ship.app` may carry — {@link HostOs}, the
|
|
19
|
+
* `process.platform` spelling.
|
|
20
|
+
*
|
|
21
|
+
* `satisfies Record<HostOs, true>` rather than a bare list: a fourth layout
|
|
22
|
+
* cannot arrive without this object failing to compile, so the accepted set
|
|
23
|
+
* stays total without a second place to remember to update.
|
|
24
|
+
*/
|
|
25
|
+
const SHIP_APP_OSES = { linux: true, darwin: true, win32: true };
|
|
26
|
+
/**
|
|
27
|
+
* The ONE place a ship target's runtime is decided: `gjsify.app` as the DEFAULT,
|
|
28
|
+
* `gjsify.ship.app.<os>` as this target's override.
|
|
29
|
+
*
|
|
30
|
+
* WHY IT IS PER TARGET, measured on a GTK4 application rather than derived from
|
|
31
|
+
* the schema: with one field, `gjsify ship darwin` refused its formats until
|
|
32
|
+
* `gjsify.app` was `"node"` — and setting it turned the LINUX `.deb`'s
|
|
33
|
+
* `Depends: gjs (>= 1.86)` into `Depends: nodejs (>= 24)`, which apt refuses on
|
|
34
|
+
* trixie, Ubuntu 24.04 and Ubuntu 26.04. A macOS decision made the Linux package
|
|
35
|
+
* uninstallable, because every generator read the PROJECT field instead of the
|
|
36
|
+
* resolved runtime of its OWN target (#1486, ADR 0024 § A22). Nothing downstream
|
|
37
|
+
* reads either key; they all read `settings.app`, which is this result.
|
|
38
|
+
*
|
|
39
|
+
* NOT PER LAYOUT BY DEFAULT, and the first cut of the layout axis got this
|
|
40
|
+
* exactly backwards. Reading ADR § 4's runtime table as a per-layout
|
|
41
|
+
* REQUIREMENT refused `gjsify.app: "gjs"` for the macOS layout — the entire
|
|
42
|
+
* audience of that command — while a project with no `gjsify.app` key sailed
|
|
43
|
+
* through and staged a launcher naming `node` in front of a GJS bundle. § 4
|
|
44
|
+
* derives the runtime a shipped artifact CARRIES, which is
|
|
45
|
+
* `Layout.shippedRuntime`; what an artifact runs is what its author says, which
|
|
46
|
+
* is this. So an absent override means the project field and never the layout's
|
|
47
|
+
* derived answer.
|
|
48
|
+
*
|
|
49
|
+
* MUST NOT be fed `Config.forBuild`'s resolution, whose fallback is the HOST
|
|
50
|
+
* runtime. That fallback is right for a build (`gjsify build` under Node should
|
|
51
|
+
* produce a Node bundle by default) and catastrophic for a package: running this
|
|
52
|
+
* command under Node would silently turn every undeclared GJS project into one
|
|
53
|
+
* whose launcher execs `node`. An undeclared target is the common case for a GJS
|
|
54
|
+
* app, and it means GJS.
|
|
55
|
+
*
|
|
56
|
+
* TWO REFUSALS, and each is here because the alternative to refusing is SILENT.
|
|
57
|
+
* A `browser` or `nativescript` value has no artifact to be — no process to
|
|
58
|
+
* start, an APK/IPA through another pipeline (§ 4) — and would otherwise leave
|
|
59
|
+
* here as a `.deb` a stranger installs and cannot run, with no gate between here
|
|
60
|
+
* and their machine; `Layout.runtimeGap` only WARNS because what it describes is
|
|
61
|
+
* a staged tree rather than an artifact, and the per-FORMAT half of the question
|
|
62
|
+
* is `assertFormatCanRunInterpreter`, which asks what a container can EXECUTE.
|
|
63
|
+
* A key outside {@link SHIP_APP_OSES} — `windows`, the `gjsify ship <os>`
|
|
64
|
+
* POSITIONAL's spelling — would resolve to nothing and leave that target on the
|
|
65
|
+
* project-wide answer with every gate green, which is why the author who typed
|
|
66
|
+
* it reads the result as gjsify not supporting the split at all. Both refusals
|
|
67
|
+
* name the KEY that carried the value: with two keys, "this project declares" no
|
|
68
|
+
* longer locates it.
|
|
69
|
+
*
|
|
70
|
+
* `gjsify ship --from-stage` reads neither key and needs neither: the stage
|
|
71
|
+
* manifest records the RESOLVED value, so the packing host reads an answer
|
|
72
|
+
* rather than a default, and `assertLauncherMatchesInterpreter` still compares
|
|
73
|
+
* the staged `bin/<name>`'s own `exec` line against the dependency about to be
|
|
74
|
+
* written.
|
|
75
|
+
*/
|
|
76
|
+
export function resolveShipApp(input) {
|
|
77
|
+
const perTarget = input.perTarget ?? {};
|
|
78
|
+
// The WHOLE table, not only this run's key: a mis-keyed override is silent by
|
|
79
|
+
// construction, so `gjsify ship linux` is the run that has to say `windows` is
|
|
80
|
+
// not a key. Checking only the resolving key would leave the typo to be found
|
|
81
|
+
// by the `gjsify ship windows` that already reads as unsupported.
|
|
82
|
+
for (const os of Object.keys(perTarget)) {
|
|
83
|
+
if (Object.hasOwn(SHIP_APP_OSES, os))
|
|
84
|
+
continue;
|
|
85
|
+
throw new Error(`gjsify ship: \`gjsify.ship.app.${os}\` is not an OS this command assembles for. Known: ` +
|
|
86
|
+
`${Object.keys(SHIP_APP_OSES).join(', ')} — the \`process.platform\` spelling, not the ` +
|
|
87
|
+
"`gjsify ship <os>` positional's. A runtime under a key nothing reads leaves that target on " +
|
|
88
|
+
'`gjsify.app` with nothing to say so.');
|
|
89
|
+
}
|
|
90
|
+
const override = perTarget[input.layoutOs];
|
|
91
|
+
const overridden = override !== undefined;
|
|
92
|
+
const key = overridden ? `gjsify.ship.app.${input.layoutOs}` : 'gjsify.app';
|
|
93
|
+
const declared = overridden ? override : input.project;
|
|
94
|
+
if (declared !== undefined && declared !== 'gjs' && declared !== 'node') {
|
|
95
|
+
throw new Error(`gjsify ship: \`${key}\` is "${declared}", and only \`gjs\` and \`node\` can be ` +
|
|
96
|
+
'packaged. A browser bundle has no process to launch, and a NativeScript app ships as an APK/IPA ' +
|
|
97
|
+
'through a different pipeline. ADR 0024 § 4 has the runtime-per-OS table.');
|
|
98
|
+
}
|
|
99
|
+
return { app: declared === 'node' ? 'node' : 'gjs', key, overridden };
|
|
100
|
+
}
|
|
17
101
|
/** Freedesktop main category → deb `Section:` / rpm `Group:`. First match wins. */
|
|
18
102
|
const CATEGORY_SECTIONS = [
|
|
19
103
|
['Development', { section: 'devel', group: 'Development/Tools' }],
|
|
@@ -49,6 +133,10 @@ export function resolveShipSettings(input) {
|
|
|
49
133
|
const appId = ship.appId ?? flatpak.appId ?? reverseDnsOrThrow(pkg.name, binaryName);
|
|
50
134
|
const name = metadata.name ?? titleCase(binaryName);
|
|
51
135
|
const kind = metadata.kind ?? 'app';
|
|
136
|
+
// NOT resolved here: `resolveShipApp` already did, before the format list was
|
|
137
|
+
// decided from it. A second defaulting rule in this function would be the
|
|
138
|
+
// second path those two answers could come apart on.
|
|
139
|
+
const app = input.app;
|
|
52
140
|
const rawVersion = ship.version ?? pkg.version;
|
|
53
141
|
if (!rawVersion) {
|
|
54
142
|
throw new Error('gjsify ship: no version — set `version` in package.json or `gjsify.ship.version`.');
|
|
@@ -102,11 +190,15 @@ export function resolveShipSettings(input) {
|
|
|
102
190
|
schemaFiles: discovered.schemaFiles,
|
|
103
191
|
typelibFiles: discovered.typelibFiles,
|
|
104
192
|
localeFiles: discovered.localeFiles,
|
|
193
|
+
fontFiles: discovered.fontFiles,
|
|
105
194
|
extraFiles: ship.extraFiles ?? {},
|
|
106
195
|
execArgs: ship.execArgs ?? [],
|
|
107
196
|
outDir: input.cli.outDir ?? ship.outDir ?? 'ship',
|
|
108
197
|
arch: input.cli.arch ?? process.arch,
|
|
198
|
+
app,
|
|
199
|
+
layoutOs: input.cli.layoutOs,
|
|
109
200
|
minGjsVersion: ship.minGjsVersion ?? DEFAULT_GJS_FLOOR,
|
|
201
|
+
minNodeVersion: ship.minNodeVersion ?? DEFAULT_NODE_FLOOR,
|
|
110
202
|
flatpak: flatpakSettings.settings,
|
|
111
203
|
};
|
|
112
204
|
return { settings, metadata, warnings };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Each entry is a directory the freedesktop/GNOME stack looks in BY NAME. None of
|
|
3
|
+
* them is ours to choose: `bindtextdomain` reads `share/locale/<lang>/LC_MESSAGES`,
|
|
4
|
+
* `glib-compile-schemas` compiles `share/glib-2.0/schemas`, and the icon theme
|
|
5
|
+
* spec fixes `share/icons/hicolor`. That is also why this is a closed set rather
|
|
6
|
+
* than a convention — a typo in any of them installs a file nothing ever reads.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SHARE: {
|
|
9
|
+
/** freedesktop desktop entries. */
|
|
10
|
+
readonly applications: "share/applications";
|
|
11
|
+
/** The hicolor icon theme, whose cache `gtk-update-icon-cache` builds. */
|
|
12
|
+
readonly icons: "share/icons/hicolor";
|
|
13
|
+
/** GSettings schema sources, compiled into `gschemas.compiled` at install. */
|
|
14
|
+
readonly schemas: "share/glib-2.0/schemas";
|
|
15
|
+
/** shared-mime-info documents, folded into the mime cache at install. */
|
|
16
|
+
readonly mime: "share/mime/packages";
|
|
17
|
+
/** AppStream components. */
|
|
18
|
+
readonly metainfo: "share/metainfo";
|
|
19
|
+
/** Compiled gettext catalogues, read directly — no install step. */
|
|
20
|
+
readonly locale: "share/locale";
|
|
21
|
+
/**
|
|
22
|
+
* Font faces the application ships — ONE directory, and three different things
|
|
23
|
+
* reach it (ADR 0038). No install step on any of them.
|
|
24
|
+
*
|
|
25
|
+
* On LINUX fontconfig scans it, by two rules that are both the stock
|
|
26
|
+
* `fonts.conf`'s rather than ours: `<dir>/usr/share/fonts</dir>` unconditionally,
|
|
27
|
+
* which covers a `.deb`/`.rpm`, and `<dir prefix="xdg">fonts</dir>`, which
|
|
28
|
+
* fontconfig expands over `XDG_DATA_DIRS` as well as `XDG_DATA_HOME` — so the
|
|
29
|
+
* variable the launcher already exports covers `/app` in a Flatpak and any other
|
|
30
|
+
* prefix. `fonts-conf(5)` documents only `XDG_DATA_HOME` there; the expansion was
|
|
31
|
+
* measured in EIGHT independently built fontconfigs (2.14.1 through 2.18.3), in
|
|
32
|
+
* every list position, recursively, with a cold cache and a negative control.
|
|
33
|
+
* Behaviour, not documentation — which is why it is written here and in ADR 0038
|
|
34
|
+
* § *What was measured* rather than re-derived from the man page.
|
|
35
|
+
*
|
|
36
|
+
* On MACOS none of that applies and the directory is the same: Pango there is
|
|
37
|
+
* CoreText-backed and GTK is not built against fontconfig, so `Info.plist`'s
|
|
38
|
+
* `ATSApplicationFontsPath` points at this directory instead (`layout.ts`).
|
|
39
|
+
*
|
|
40
|
+
* On WINDOWS nothing reaches it by configuration at all — measured there, not
|
|
41
|
+
* inferred: the default font map does not read fontconfig even when the staged
|
|
42
|
+
* directory is the ONLY configuration it is given, while `add_font_file` on that
|
|
43
|
+
* same map does register the family. So the launcher's `GJSIFY_FONT_DIR` hands
|
|
44
|
+
* the directory to the app to register. `Layout.fontGap` carries the numbers and
|
|
45
|
+
* the three candidates that look like they would work and do not.
|
|
46
|
+
*
|
|
47
|
+
* That the PATH is one while the mechanisms are three is the point: `planStage`
|
|
48
|
+
* keeps emitting one payload, and every per-OS answer is a layout decision.
|
|
49
|
+
*/
|
|
50
|
+
readonly fonts: "share/fonts";
|
|
51
|
+
};
|
|
52
|
+
/** Is `path` inside `dir`? Directory-boundary aware, so `share/mimetypes` is not inside `share/mime`. */
|
|
53
|
+
export declare function isUnder(path: string, dir: string): boolean;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// The `share/` subdirectories a payload installs into — ONE definition.
|
|
2
|
+
//
|
|
3
|
+
// WHY THIS FILE EXISTS, and it is a repair rather than a tidy-up. Four call
|
|
4
|
+
// sites answer questions about the same directories and every one of them spelled
|
|
5
|
+
// them out again: `plan.ts` stages a file there, `readPayloadFacts` asks whether
|
|
6
|
+
// the payload put anything there, `cacheRefreshCommands` emits the install step
|
|
7
|
+
// that makes it WORK, and `linuxInstallDependent` names the ones a non-Linux
|
|
8
|
+
// layout carries without that step. `share/glib-2.0/schemas` alone existed as
|
|
9
|
+
// five independent string literals across the tree.
|
|
10
|
+
//
|
|
11
|
+
// A comment claiming they "cannot drift" was measured FALSE: pointing one rule in
|
|
12
|
+
// `linuxInstallDependent` at a directory that matches nothing silently dropped a
|
|
13
|
+
// file from the warning `gjsify ship` prints, and the whole suite stayed green at
|
|
14
|
+
// exit 0. The prose was the mechanism, which is to say there was none. Importing
|
|
15
|
+
// one constant makes the compiler the mechanism instead.
|
|
16
|
+
//
|
|
17
|
+
// PREFIX-RELATIVE and POSIX-separated, like every path in the plan. The layout
|
|
18
|
+
// map (`layout.ts`) is what turns these into `Contents/Resources/share/…` or a
|
|
19
|
+
// Windows program directory's `share\`; nothing here knows about that, and
|
|
20
|
+
// nothing here is absolute — `scripts.ts` prepends the format's install prefix,
|
|
21
|
+
// which is `/usr` for a `.deb` and `/app` for a Flatpak.
|
|
22
|
+
/**
|
|
23
|
+
* Each entry is a directory the freedesktop/GNOME stack looks in BY NAME. None of
|
|
24
|
+
* them is ours to choose: `bindtextdomain` reads `share/locale/<lang>/LC_MESSAGES`,
|
|
25
|
+
* `glib-compile-schemas` compiles `share/glib-2.0/schemas`, and the icon theme
|
|
26
|
+
* spec fixes `share/icons/hicolor`. That is also why this is a closed set rather
|
|
27
|
+
* than a convention — a typo in any of them installs a file nothing ever reads.
|
|
28
|
+
*/
|
|
29
|
+
export const SHARE = {
|
|
30
|
+
/** freedesktop desktop entries. */
|
|
31
|
+
applications: 'share/applications',
|
|
32
|
+
/** The hicolor icon theme, whose cache `gtk-update-icon-cache` builds. */
|
|
33
|
+
icons: 'share/icons/hicolor',
|
|
34
|
+
/** GSettings schema sources, compiled into `gschemas.compiled` at install. */
|
|
35
|
+
schemas: 'share/glib-2.0/schemas',
|
|
36
|
+
/** shared-mime-info documents, folded into the mime cache at install. */
|
|
37
|
+
mime: 'share/mime/packages',
|
|
38
|
+
/** AppStream components. */
|
|
39
|
+
metainfo: 'share/metainfo',
|
|
40
|
+
/** Compiled gettext catalogues, read directly — no install step. */
|
|
41
|
+
locale: 'share/locale',
|
|
42
|
+
/**
|
|
43
|
+
* Font faces the application ships — ONE directory, and three different things
|
|
44
|
+
* reach it (ADR 0038). No install step on any of them.
|
|
45
|
+
*
|
|
46
|
+
* On LINUX fontconfig scans it, by two rules that are both the stock
|
|
47
|
+
* `fonts.conf`'s rather than ours: `<dir>/usr/share/fonts</dir>` unconditionally,
|
|
48
|
+
* which covers a `.deb`/`.rpm`, and `<dir prefix="xdg">fonts</dir>`, which
|
|
49
|
+
* fontconfig expands over `XDG_DATA_DIRS` as well as `XDG_DATA_HOME` — so the
|
|
50
|
+
* variable the launcher already exports covers `/app` in a Flatpak and any other
|
|
51
|
+
* prefix. `fonts-conf(5)` documents only `XDG_DATA_HOME` there; the expansion was
|
|
52
|
+
* measured in EIGHT independently built fontconfigs (2.14.1 through 2.18.3), in
|
|
53
|
+
* every list position, recursively, with a cold cache and a negative control.
|
|
54
|
+
* Behaviour, not documentation — which is why it is written here and in ADR 0038
|
|
55
|
+
* § *What was measured* rather than re-derived from the man page.
|
|
56
|
+
*
|
|
57
|
+
* On MACOS none of that applies and the directory is the same: Pango there is
|
|
58
|
+
* CoreText-backed and GTK is not built against fontconfig, so `Info.plist`'s
|
|
59
|
+
* `ATSApplicationFontsPath` points at this directory instead (`layout.ts`).
|
|
60
|
+
*
|
|
61
|
+
* On WINDOWS nothing reaches it by configuration at all — measured there, not
|
|
62
|
+
* inferred: the default font map does not read fontconfig even when the staged
|
|
63
|
+
* directory is the ONLY configuration it is given, while `add_font_file` on that
|
|
64
|
+
* same map does register the family. So the launcher's `GJSIFY_FONT_DIR` hands
|
|
65
|
+
* the directory to the app to register. `Layout.fontGap` carries the numbers and
|
|
66
|
+
* the three candidates that look like they would work and do not.
|
|
67
|
+
*
|
|
68
|
+
* That the PATH is one while the mechanisms are three is the point: `planStage`
|
|
69
|
+
* keeps emitting one payload, and every per-OS answer is a layout decision.
|
|
70
|
+
*/
|
|
71
|
+
fonts: 'share/fonts',
|
|
72
|
+
};
|
|
73
|
+
/** Is `path` inside `dir`? Directory-boundary aware, so `share/mimetypes` is not inside `share/mime`. */
|
|
74
|
+
export function isUnder(path, dir) {
|
|
75
|
+
return path.startsWith(`${dir}/`);
|
|
76
|
+
}
|