@gjsify/cli 0.44.0 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/cli.gjs.mjs +202 -167
  2. package/lib/commands/gettext.d.ts +2 -1
  3. package/lib/commands/gettext.js +105 -47
  4. package/lib/commands/onboard.d.ts +7 -0
  5. package/lib/commands/onboard.js +349 -96
  6. package/lib/commands/ship.d.ts +3 -0
  7. package/lib/commands/ship.js +850 -58
  8. package/lib/commands/trust.d.ts +6 -0
  9. package/lib/commands/trust.js +1 -1
  10. package/lib/commands/upgrade.d.ts +23 -0
  11. package/lib/commands/upgrade.js +95 -12
  12. package/lib/templates/install.mjs.tmpl +2 -2
  13. package/lib/types/config-data.d.ts +127 -1
  14. package/lib/utils/gi-search-path.js +11 -3
  15. package/lib/utils/msgfmt-merge.d.ts +35 -0
  16. package/lib/utils/msgfmt-merge.js +102 -0
  17. package/lib/utils/npm-otp.d.ts +27 -2
  18. package/lib/utils/npm-otp.js +82 -7
  19. package/lib/utils/onboard-discovery.d.ts +68 -0
  20. package/lib/utils/onboard-discovery.js +178 -0
  21. package/lib/utils/onboard-probe.d.ts +118 -1
  22. package/lib/utils/onboard-probe.js +153 -4
  23. package/lib/utils/prompt-output.d.ts +12 -0
  24. package/lib/utils/prompt-output.js +54 -0
  25. package/lib/utils/prompt.js +22 -2
  26. package/lib/utils/publishable-packages.d.ts +1 -1
  27. package/lib/utils/publishable-packages.js +1 -0
  28. package/lib/utils/resolve-npm-package.d.ts +56 -0
  29. package/lib/utils/resolve-npm-package.js +69 -0
  30. package/lib/utils/ship/app-runtime.d.ts +214 -0
  31. package/lib/utils/ship/app-runtime.js +429 -0
  32. package/lib/utils/ship/depends.d.ts +50 -0
  33. package/lib/utils/ship/depends.js +117 -3
  34. package/lib/utils/ship/discover.js +103 -1
  35. package/lib/utils/ship/dmg.d.ts +71 -0
  36. package/lib/utils/ship/dmg.js +181 -0
  37. package/lib/utils/ship/flatpak.js +1 -1
  38. package/lib/utils/ship/formats.d.ts +108 -7
  39. package/lib/utils/ship/formats.js +778 -15
  40. package/lib/utils/ship/launcher.d.ts +13 -3
  41. package/lib/utils/ship/launcher.js +419 -17
  42. package/lib/utils/ship/layout.d.ts +330 -0
  43. package/lib/utils/ship/layout.js +504 -0
  44. package/lib/utils/ship/localize-metadata.d.ts +22 -0
  45. package/lib/utils/ship/localize-metadata.js +126 -0
  46. package/lib/utils/ship/mime.js +1 -9
  47. package/lib/utils/ship/msi.d.ts +126 -0
  48. package/lib/utils/ship/msi.js +434 -0
  49. package/lib/utils/ship/node-runtime.d.ts +71 -0
  50. package/lib/utils/ship/node-runtime.js +122 -0
  51. package/lib/utils/ship/payload.d.ts +156 -5
  52. package/lib/utils/ship/payload.js +599 -14
  53. package/lib/utils/ship/pe-launcher.d.ts +29 -0
  54. package/lib/utils/ship/pe-launcher.js +663 -0
  55. package/lib/utils/ship/plan.d.ts +21 -0
  56. package/lib/utils/ship/plan.js +55 -7
  57. package/lib/utils/ship/plist.d.ts +98 -0
  58. package/lib/utils/ship/plist.js +256 -0
  59. package/lib/utils/ship/rpm.js +16 -3
  60. package/lib/utils/ship/schemas.d.ts +61 -0
  61. package/lib/utils/ship/schemas.js +118 -0
  62. package/lib/utils/ship/scripts.js +7 -3
  63. package/lib/utils/ship/settings.d.ts +91 -2
  64. package/lib/utils/ship/settings.js +93 -1
  65. package/lib/utils/ship/share-dirs.d.ts +53 -0
  66. package/lib/utils/ship/share-dirs.js +76 -0
  67. package/lib/utils/ship/signing.d.ts +311 -0
  68. package/lib/utils/ship/signing.js +506 -0
  69. package/lib/utils/ship/stage-manifest.d.ts +40 -12
  70. package/lib/utils/ship/stage-manifest.js +99 -13
  71. package/lib/utils/ship/stage-writer.d.ts +48 -0
  72. package/lib/utils/ship/stage-writer.js +91 -2
  73. package/lib/utils/ship/types.d.ts +191 -14
  74. package/lib/utils/ship/xml.d.ts +7 -0
  75. package/lib/utils/ship/xml.js +25 -0
  76. package/lib/utils/ship/zip.d.ts +69 -0
  77. package/lib/utils/ship/zip.js +245 -0
  78. package/lib/utils/system-gi.d.ts +20 -5
  79. package/lib/utils/system-gi.js +36 -13
  80. package/lib/utils/trust-registry.js +5 -0
  81. package/package.json +19 -19
@@ -0,0 +1,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}/share/applications`,
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}/share/icons/hicolor`,
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}/share/glib-2.0/schemas`,
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
+ }