@gjsify/cli 0.53.0 → 0.55.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 +266 -249
- package/lib/bundler-pick.d.ts +35 -0
- package/lib/bundler-pick.js +74 -7
- package/lib/cli-app.js +3 -1
- package/lib/commands/blueprint/index.d.ts +4 -0
- package/lib/commands/blueprint/index.js +15 -0
- package/lib/commands/blueprint/types.d.ts +8 -0
- package/lib/commands/blueprint/types.js +148 -0
- package/lib/commands/devtools.d.ts +21 -0
- package/lib/commands/devtools.js +146 -0
- package/lib/commands/index.d.ts +2 -0
- package/lib/commands/index.js +2 -0
- package/lib/commands/ship.js +56 -8
- package/lib/commands/webext/index.js +1 -1
- package/lib/types/config-data.d.ts +5 -0
- package/lib/utils/oxc-resolve.js +9 -7
- package/lib/utils/ship/app-runtime.d.ts +7 -5
- package/lib/utils/ship/app-runtime.js +11 -8
- package/lib/utils/ship/appimage.d.ts +9 -0
- package/lib/utils/ship/appimage.js +2 -1
- package/lib/utils/ship/deb.d.ts +10 -0
- package/lib/utils/ship/deb.js +7 -0
- package/lib/utils/ship/depends.d.ts +37 -3
- package/lib/utils/ship/depends.js +21 -9
- package/lib/utils/ship/flatpak-config.d.ts +16 -0
- package/lib/utils/ship/flatpak-config.js +26 -0
- package/lib/utils/ship/formats.js +10 -7
- package/lib/utils/ship/gi-namespaces.d.ts +16 -0
- package/lib/utils/ship/gi-namespaces.js +64 -1
- package/lib/utils/ship/launcher.js +2 -2
- package/lib/utils/ship/layout.d.ts +7 -6
- package/lib/utils/ship/layout.js +42 -16
- package/lib/utils/ship/rpm.d.ts +5 -0
- package/lib/utils/ship/rpm.js +42 -0
- package/lib/utils/ship/settings.js +6 -0
- package/lib/utils/ship/signing.d.ts +7 -2
- package/lib/utils/ship/signing.js +59 -32
- package/lib/utils/ship/stage-manifest.d.ts +14 -0
- package/lib/utils/ship/stage-manifest.js +7 -0
- package/lib/utils/ship/types.d.ts +2 -0
- package/lib/utils/webext/targets.d.ts +1 -1
- package/lib/utils/webext/targets.js +20 -8
- package/package.json +20 -19
package/lib/commands/ship.js
CHANGED
|
@@ -34,6 +34,7 @@ import { isGtkRuntimeTarget, stageAppRuntime } from '../utils/ship/app-runtime.j
|
|
|
34
34
|
import { discoverPayload } from '../utils/ship/discover.js';
|
|
35
35
|
import { appDirFor, appDirPayload, appImageHostRequirements, appImageRuntimeNotice, buildAppImage, findPinnedRuntime, stampAppDirTimes, } from '../utils/ship/appimage.js';
|
|
36
36
|
import { buildDmgImage, dmgVolumeDir, dmgVolumeName } from '../utils/ship/dmg.js';
|
|
37
|
+
import { assertNoDroppedFlatpakModules } from '../utils/ship/flatpak-config.js';
|
|
37
38
|
import { buildFlatpakBundle } from '../utils/ship/flatpak.js';
|
|
38
39
|
import { iconSizes, resolveAppIcon } from '../utils/ship/icons.js';
|
|
39
40
|
import { localizeMetadata } from '../utils/ship/localize-metadata.js';
|
|
@@ -45,7 +46,7 @@ import { compileSchemasForPayload, compileSchemasForStage } from '../utils/ship/
|
|
|
45
46
|
import { buildMsi, MSI_PAYLOAD_DIR } from '../utils/ship/msi.js';
|
|
46
47
|
import { buildZip, zipEntriesFromPayload } from '../utils/ship/zip.js';
|
|
47
48
|
import { assertEntryRunsUnder } from '../utils/ship/entry-interpreter.js';
|
|
48
|
-
import {
|
|
49
|
+
import { scanGiRequirements } from '../utils/ship/gi-namespaces.js';
|
|
49
50
|
import { assertLauncherMatchesInterpreter, assertPayloadMatchesArch, buildTimestamp, isArchIndependent, linuxInstallDependent, readPayloadFacts, } from '../utils/ship/payload.js';
|
|
50
51
|
import { assertOverlayIsLicensed, planOverlay, planStage } from '../utils/ship/plan.js';
|
|
51
52
|
import { assertHostCanSign, notarizeArtifact, stapleArtifact, resolveNotaryPlan, resolveSignPlan, signPayload, } from '../utils/ship/signing.js';
|
|
@@ -252,6 +253,10 @@ async function assemble(args) {
|
|
|
252
253
|
'thing as an error.');
|
|
253
254
|
}
|
|
254
255
|
}
|
|
256
|
+
// Under `--stage` too: the stage records the Flatpak settings, and a stage
|
|
257
|
+
// that quietly omits the project's modules packs without them later.
|
|
258
|
+
if (formats.some((format) => format.id === 'flatpak'))
|
|
259
|
+
assertNoDroppedFlatpakModules(flatpak);
|
|
255
260
|
if (!args.stage)
|
|
256
261
|
assertPackable(formats, layout, args.os === undefined ? 'host' : 'positional', unusable);
|
|
257
262
|
// BEFORE the build, beside `assertCanPack` and for the same reason: a
|
|
@@ -537,9 +542,20 @@ async function assemble(args) {
|
|
|
537
542
|
// `gi://` specifiers are what the emitted `Depends:` is derived from
|
|
538
543
|
// (ADR 0024 § 6), and the packing host has the staged copy but no way to
|
|
539
544
|
// tell which staged file is the entry.
|
|
540
|
-
const
|
|
541
|
-
|
|
545
|
+
const gi = scanGiRequirements(bundleSource);
|
|
546
|
+
const namespaces = gi.namespaces;
|
|
547
|
+
const optionalNamespaces = gi.optional;
|
|
548
|
+
if (args.verbose) {
|
|
549
|
+
const hard = namespaces.filter((ns) => !optionalNamespaces.includes(ns));
|
|
542
550
|
console.log(`${LOG} gi namespaces: ${namespaces.join(', ') || '(none)'}`);
|
|
551
|
+
// The split is worth its own line whenever it is non-empty: an author who
|
|
552
|
+
// wrote `&optional` and sees the namespace under the plain list has learned
|
|
553
|
+
// nothing about whether their package will declare it.
|
|
554
|
+
if (optionalNamespaces.length > 0) {
|
|
555
|
+
console.log(`${LOG} gi optional namespaces: ${optionalNamespaces.join(', ')}`);
|
|
556
|
+
console.log(`${LOG} gi required namespaces: ${hard.join(', ') || '(none)'}`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
543
559
|
// PLACED, like the payload, and through the same map. `planOverlay` answers a
|
|
544
560
|
// question about the FORMAT (`share/doc/<pkg>/copyright` for Debian policy,
|
|
545
561
|
// `share/licenses/<pkg>/LICENSE` for rpm) in the prefix-relative shape every
|
|
@@ -578,8 +594,9 @@ async function assemble(args) {
|
|
|
578
594
|
// into a Debian `Depends:`, at exit 0).
|
|
579
595
|
if (format.depends === null)
|
|
580
596
|
continue;
|
|
581
|
-
deriveDepends(format.depends, {
|
|
597
|
+
const derived = deriveDepends(format.depends, {
|
|
582
598
|
namespaces,
|
|
599
|
+
optionalNamespaces,
|
|
583
600
|
hasIcons: facts.hasIcons,
|
|
584
601
|
hasSchemas: facts.hasSchemas,
|
|
585
602
|
interpreter: settings.app,
|
|
@@ -589,6 +606,17 @@ async function assemble(args) {
|
|
|
589
606
|
minGjsVersion: settings.minGjsVersion,
|
|
590
607
|
minNodeVersion: settings.minNodeVersion,
|
|
591
608
|
});
|
|
609
|
+
// Reported here and not inside `deriveDepends`, which is also called by
|
|
610
|
+
// `packOne` on a host that has no project — and this is the only place that
|
|
611
|
+
// does. An optional namespace nobody mapped produces NO package field, and
|
|
612
|
+
// the app runs without the typelib either way, so nothing downstream will
|
|
613
|
+
// ever say so again.
|
|
614
|
+
for (const namespace of derived.unmappedOptional) {
|
|
615
|
+
console.warn(`${LOG} gi://${namespace} is imported optionally, but no ${format.depends} package is known ` +
|
|
616
|
+
'to ship its typelib, so no Recommends entry names it. The package builds and runs ' +
|
|
617
|
+
`without it. Add the row if the namespace should be recommended: \`gjsify.ship.typelibPackages.` +
|
|
618
|
+
`${namespace} = { "${format.depends}": "…" }\`.`);
|
|
619
|
+
}
|
|
592
620
|
// One warning per package, for the interpreter it actually declares. A
|
|
593
621
|
// floor warning about a dependency this package does not emit is noise,
|
|
594
622
|
// and a noisy warning is the one nobody reads when it matters.
|
|
@@ -602,7 +630,16 @@ async function assemble(args) {
|
|
|
602
630
|
// is sometimes packable elsewhere and sometimes not is a worse contract
|
|
603
631
|
// than one that always is, and it is what lets the e2e suite compare the
|
|
604
632
|
// one-process artifact with the two-phase one.
|
|
605
|
-
const manifest = writeStageManifest({
|
|
633
|
+
const manifest = writeStageManifest({
|
|
634
|
+
stageDir,
|
|
635
|
+
settings,
|
|
636
|
+
formats,
|
|
637
|
+
mtime,
|
|
638
|
+
namespaces,
|
|
639
|
+
optionalNamespaces,
|
|
640
|
+
staged,
|
|
641
|
+
overlay,
|
|
642
|
+
});
|
|
606
643
|
if (args.stage) {
|
|
607
644
|
// `(none)` spelled out, because an empty list printed as nothing after
|
|
608
645
|
// "formats " reads as a truncated line rather than as a fact. WHICH fact
|
|
@@ -638,6 +675,7 @@ async function assemble(args) {
|
|
|
638
675
|
stageDir,
|
|
639
676
|
outRoot,
|
|
640
677
|
namespaces,
|
|
678
|
+
optionalNamespaces,
|
|
641
679
|
mtime,
|
|
642
680
|
sign,
|
|
643
681
|
notarize,
|
|
@@ -799,6 +837,9 @@ async function finishFromStage(args, fromStage) {
|
|
|
799
837
|
for (const file of manifest.staged)
|
|
800
838
|
console.log(`${LOG} ${file.path}`);
|
|
801
839
|
console.log(`${LOG} gi namespaces: ${manifest.namespaces.join(', ') || '(none)'}`);
|
|
840
|
+
if ((manifest.optionalNamespaces ?? []).length > 0) {
|
|
841
|
+
console.log(`${LOG} gi optional namespaces: ${manifest.optionalNamespaces?.join(', ')}`);
|
|
842
|
+
}
|
|
802
843
|
}
|
|
803
844
|
// The stage's own directory is not written to — `writeStage` wipes what it
|
|
804
845
|
// is pointed at, and pointing it at an arriving stage would delete the
|
|
@@ -832,6 +873,7 @@ async function finishFromStage(args, fromStage) {
|
|
|
832
873
|
stageDir,
|
|
833
874
|
outRoot,
|
|
834
875
|
namespaces: manifest.namespaces,
|
|
876
|
+
optionalNamespaces: manifest.optionalNamespaces ?? [],
|
|
835
877
|
mtime,
|
|
836
878
|
sign,
|
|
837
879
|
notarize,
|
|
@@ -858,10 +900,11 @@ async function packOne(input) {
|
|
|
858
900
|
// this answers used to be answered from the project's file lists, which are
|
|
859
901
|
// absolute paths on the build host and therefore unavailable here.
|
|
860
902
|
const facts = readPayloadFacts(assembled);
|
|
861
|
-
const
|
|
862
|
-
? []
|
|
903
|
+
const derived = format.depends === null
|
|
904
|
+
? { requires: [], recommends: [], unmappedOptional: [] }
|
|
863
905
|
: deriveDepends(format.depends, {
|
|
864
906
|
namespaces: input.namespaces,
|
|
907
|
+
optionalNamespaces: input.optionalNamespaces,
|
|
865
908
|
hasIcons: facts.hasIcons,
|
|
866
909
|
hasSchemas: facts.hasSchemas,
|
|
867
910
|
interpreter: settings.app,
|
|
@@ -871,6 +914,8 @@ async function packOne(input) {
|
|
|
871
914
|
minGjsVersion: settings.minGjsVersion,
|
|
872
915
|
minNodeVersion: settings.minNodeVersion,
|
|
873
916
|
});
|
|
917
|
+
const depends = derived.requires;
|
|
918
|
+
const recommends = derived.recommends;
|
|
874
919
|
// `--arch` is a CLAIM about the payload and nothing else compared it to one:
|
|
875
920
|
// an x86-64 `.so` staged with `--arch arm64` packed at exit 0 and `rpm -qp
|
|
876
921
|
// --qf %{ARCH}` confirmed `aarch64`, because rpm reads the header and the
|
|
@@ -956,7 +1001,9 @@ async function packOne(input) {
|
|
|
956
1001
|
})
|
|
957
1002
|
: assembled;
|
|
958
1003
|
const archLabel = format.archName(input.arch, isArchIndependent(payload));
|
|
959
|
-
|
|
1004
|
+
// `recommends` rides the shared input object, so a format that does not write the
|
|
1005
|
+
// field (every non-deb/rpm row) simply ignores an extra property it never reads.
|
|
1006
|
+
const common = { settings, payload, prefix: format.prefix, depends, recommends, archLabel, mtime };
|
|
960
1007
|
const outDir = join(outRoot, 'out');
|
|
961
1008
|
mkdirSync(outDir, { recursive: true });
|
|
962
1009
|
const target = join(outDir, format.fileName(settings, archLabel));
|
|
@@ -1010,6 +1057,7 @@ async function packOne(input) {
|
|
|
1010
1057
|
minGjsVersion: settings.minGjsVersion,
|
|
1011
1058
|
minNodeVersion: settings.minNodeVersion,
|
|
1012
1059
|
namespaces: input.namespaces,
|
|
1060
|
+
optionalNamespaces: input.optionalNamespaces,
|
|
1013
1061
|
bundledTypelibs: facts.bundledTypelibs,
|
|
1014
1062
|
});
|
|
1015
1063
|
const appDir = appDirFor(outRoot);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { webextBuildCommand, webextDevCommand, webextZipCommand } from './subcommands.js';
|
|
6
6
|
export const webextCommand = {
|
|
7
7
|
command: 'webext <subcommand>',
|
|
8
|
-
description: 'Browser extensions: build, zip and develop one WebExtension for Chrome, Edge, Firefox and Safari.',
|
|
8
|
+
description: 'Browser extensions: build, zip and develop one WebExtension for Chrome, Edge, Firefox, Opera and Safari.',
|
|
9
9
|
builder: (yargs) => yargs
|
|
10
10
|
.command(webextBuildCommand.command, webextBuildCommand.description, webextBuildCommand.builder, webextBuildCommand.handler)
|
|
11
11
|
.command(webextZipCommand.command, webextZipCommand.description, webextZipCommand.builder, webextZipCommand.handler)
|
|
@@ -706,6 +706,11 @@ export interface ShipSignOptions {
|
|
|
706
706
|
* the one thing ADR 0024 § 8 gates the whole migration on. A project that has
|
|
707
707
|
* to BUILD something inside the sandbox still has `gjsify flatpak init` +
|
|
708
708
|
* `gjsify flatpak build`, unchanged.
|
|
709
|
+
*
|
|
710
|
+
* Setting `gjsify.flatpak.modules` / `extraModules` and shipping the `flatpak`
|
|
711
|
+
* target is REFUSED (`assertNoDroppedFlatpakModules`) rather than ignored:
|
|
712
|
+
* those keys are read by `flatpak init` only, and dropping them at exit 0 hid an
|
|
713
|
+
* `extra-data` module until the app ran without its downloaded binary.
|
|
709
714
|
*/
|
|
710
715
|
export interface ShipFlatpakOptions {
|
|
711
716
|
/**
|
package/lib/utils/oxc-resolve.js
CHANGED
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
// `GJSIFY_OXFMT=native|npm` is the explicit override (native throws when the
|
|
42
42
|
// prebuild isn't loadable instead of silently switching engines).
|
|
43
43
|
//
|
|
44
|
-
// oxlint has NO native path yet: its JS-plugin host (the
|
|
45
|
-
//
|
|
44
|
+
// oxlint has NO native path yet: its JS-plugin host (the
|
|
45
|
+
// `@gjsify/oxlint-plugin-gjsify` rules wired via `.oxlintrc.json` jsPlugins)
|
|
46
46
|
// lives in the Node launcher, so a native oxlint bridge could only run the
|
|
47
47
|
// Rust rule subset. Lint stays Node-spawned until that trade-off is decided
|
|
48
48
|
// (tracked in status/open-todos/README.md).
|
|
@@ -264,11 +264,13 @@ async function tryLoadNativeOxfmt() {
|
|
|
264
264
|
// `bundler-pick.ts`'s `tryLoadNative()` gives — so a library that will
|
|
265
265
|
// not load names itself instead of failing inside `runOxfmt()`.
|
|
266
266
|
// Resolved and imported, not statically imported — the reasoning is
|
|
267
|
-
// at the top of this file.
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
267
|
+
// at the top of this file. `./native-library` rather than `./core`:
|
|
268
|
+
// `core` re-exports `main-loop`, whose module-level singleton would
|
|
269
|
+
// then exist twice in a process that already carries it inlined in the
|
|
270
|
+
// GJS bundle. Off disk, so the walk follows the WORKSPACE and
|
|
271
|
+
// `@gjsify/utils build:esm` must precede this call — the same
|
|
272
|
+
// precondition `bundler-pick.ts` carries, held by rule 5 of
|
|
273
|
+
// `scripts/check-build-infra-order.mjs`.
|
|
272
274
|
//
|
|
273
275
|
// Its own `try` because the outer one cannot tell this apart from
|
|
274
276
|
// "there is no native backend", and `_nativeOxfmtLoadError` is the
|
|
@@ -102,8 +102,8 @@ export interface ResolvedNodeGiPackage {
|
|
|
102
102
|
* NOT the published tarball. `files` in node-gi's manifest also lists `src/`,
|
|
103
103
|
* `binding.gyp` and `scripts/install.mjs` — the inputs to `node-gyp`, which a
|
|
104
104
|
* `.app` never runs — and the `.d.ts` declarations, which nothing inside a bundle
|
|
105
|
-
* compiles against. And it lists `prebuilds/`, which this module stages
|
|
106
|
-
* `
|
|
105
|
+
* compiles against. And it lists `prebuilds/`, which this module stages under
|
|
106
|
+
* `Layout.dirs().native`, the directory a `.app` keeps loadable code in.
|
|
107
107
|
*
|
|
108
108
|
* So the rule is what `require('@gjsify/node-gi/<subpath>')` can REACH:
|
|
109
109
|
* `package.json` (the `exports` map is what resolves every subpath), every `.js`
|
|
@@ -120,15 +120,17 @@ export declare function resolveNodeGiPackage(options?: ResolveRuntimeOptions): R
|
|
|
120
120
|
* literals, so the paths the launcher exports and the paths the stager writes
|
|
121
121
|
* cannot drift: they are computed here once and both callers read this result.
|
|
122
122
|
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
123
|
+
* `<dirs.native>/node-gi/prebuilds/<target>/` is node-gi's OWN sibling layout
|
|
124
|
+
* reproduced verbatim inside `Layout.dirs().native` (`Contents/Resources/native`
|
|
125
|
+
* on a `.app` — `layout.ts`'s darwin row carries why that is not
|
|
126
|
+
* `Contents/Frameworks`). It is not a naming preference:
|
|
125
127
|
* `resolveGtkRuntimeBundle()` probes `<dir>/lib` and `<dir>/girepository-1.0`,
|
|
126
128
|
* the addon's `@rpath` is `@loader_path/gtk/lib`, and `loaders.cache` addresses
|
|
127
129
|
* each pixbuf loader relative to the bundle toplevel. Reproducing the shape is
|
|
128
130
|
* what makes every one of those relations still true after the copy.
|
|
129
131
|
*/
|
|
130
132
|
export interface AppRuntimePaths {
|
|
131
|
-
/** `<App>.app/Contents/
|
|
133
|
+
/** `<App>.app/Contents/Resources/native/node-gi` — the package root the layout imitates. */
|
|
132
134
|
nodeGiRoot: string;
|
|
133
135
|
/**
|
|
134
136
|
* `<App>.app/Contents/Resources/lib/node_modules/@gjsify/node-gi` — where
|
|
@@ -47,11 +47,14 @@
|
|
|
47
47
|
// THAT directory — so `prebuildAddonPath()` looks for
|
|
48
48
|
// `…/node_modules/@gjsify/node-gi/prebuilds/<target>/node_gi.node` and
|
|
49
49
|
// `resolveGtkRuntimeBundle()`'s candidate 2 for `…/prebuilds/<target>/gtk`, and
|
|
50
|
-
// neither is where a `.app` may keep loadable code: `
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
50
|
+
// neither is where a `.app` may keep loadable code: `Layout.dirs().native` is, and
|
|
51
|
+
// `utils/ship/signing.ts` re-signs every Mach-O in the payload by magic number
|
|
52
|
+
// (ADR 0024 § A4 — M6 landed), which is most of this closure. What `native` may NOT
|
|
53
|
+
// be is `Contents/Frameworks`: `codesign` reads a version-named DIRECTORY there as
|
|
54
|
+
// a nested bundle and refuses the seal, which is the whole reason this file's
|
|
55
|
+
// output went into `Contents/Resources/native` instead — see `layout.ts`'s darwin
|
|
56
|
+
// row for the measurement and `gjsify ship darwin --sign` for what it used to
|
|
57
|
+
// cost.
|
|
55
58
|
//
|
|
56
59
|
// WHY NOT `gjsify.ship.bundledTypelibs`. Because it FLATTENS. `plan.ts` stages
|
|
57
60
|
// each such file as `posix.join(libDir, 'gi', basename(file))`, and
|
|
@@ -210,8 +213,8 @@ function resolveStagedPackage(specifier, options) {
|
|
|
210
213
|
* NOT the published tarball. `files` in node-gi's manifest also lists `src/`,
|
|
211
214
|
* `binding.gyp` and `scripts/install.mjs` — the inputs to `node-gyp`, which a
|
|
212
215
|
* `.app` never runs — and the `.d.ts` declarations, which nothing inside a bundle
|
|
213
|
-
* compiles against. And it lists `prebuilds/`, which this module stages
|
|
214
|
-
* `
|
|
216
|
+
* compiles against. And it lists `prebuilds/`, which this module stages under
|
|
217
|
+
* `Layout.dirs().native`, the directory a `.app` keeps loadable code in.
|
|
215
218
|
*
|
|
216
219
|
* So the rule is what `require('@gjsify/node-gi/<subpath>')` can REACH:
|
|
217
220
|
* `package.json` (the `exports` map is what resolves every subpath), every `.js`
|
|
@@ -252,7 +255,7 @@ export function appRuntimePaths(layout, identity, target) {
|
|
|
252
255
|
addonPath: posix.join(prebuildDir, NODE_GI_ADDON_FILENAME),
|
|
253
256
|
// `Contents/MacOS`, because that is where a `.app` keeps executables and
|
|
254
257
|
// because `$here` — which the launcher already computes — is then the whole
|
|
255
|
-
// path expression. `
|
|
258
|
+
// path expression. `dirs.native` would work for dyld and would make
|
|
256
259
|
// the launcher walk back up for the one file it execs. On Windows
|
|
257
260
|
// `dirs.launcher` is the program directory itself, so the interpreter lands
|
|
258
261
|
// beside the `.cmd` for the same reason.
|
|
@@ -89,6 +89,15 @@ export declare function appImageHostRequirements(input: {
|
|
|
89
89
|
minGjsVersion?: string;
|
|
90
90
|
minNodeVersion?: string;
|
|
91
91
|
namespaces: readonly string[];
|
|
92
|
+
/**
|
|
93
|
+
* The subset of {@link namespaces} the bundle marks optional (ADR 0087).
|
|
94
|
+
*
|
|
95
|
+
* An AppImage has no `Depends:` field, so "optional" cannot become a package
|
|
96
|
+
* list here — it becomes WORDING instead. That is not cosmetic: an AppRun user
|
|
97
|
+
* told a typelib is required and lacking it goes looking for another distro
|
|
98
|
+
* package, while one told it is optional reads the same sentence correctly.
|
|
99
|
+
*/
|
|
100
|
+
optionalNamespaces?: readonly string[];
|
|
92
101
|
bundledTypelibs?: readonly string[];
|
|
93
102
|
}): string[];
|
|
94
103
|
/**
|
|
@@ -204,9 +204,10 @@ export function appDirFor(outRoot) {
|
|
|
204
204
|
*/
|
|
205
205
|
export function appImageHostRequirements(input) {
|
|
206
206
|
const floor = interpreterFloor(input);
|
|
207
|
+
const optional = new Set(input.optionalNamespaces ?? []);
|
|
207
208
|
return [
|
|
208
209
|
`${input.app} (>= ${floor})`,
|
|
209
|
-
...hostProvidedNamespaces(input.namespaces, input.bundledTypelibs).map((ns) => `the ${ns} typelib`),
|
|
210
|
+
...hostProvidedNamespaces(input.namespaces, input.bundledTypelibs).map((ns) => optional.has(ns) ? `the ${ns} typelib (optional)` : `the ${ns} typelib`),
|
|
210
211
|
];
|
|
211
212
|
}
|
|
212
213
|
/**
|
package/lib/utils/ship/deb.d.ts
CHANGED
|
@@ -8,6 +8,16 @@ export interface DebInputs {
|
|
|
8
8
|
prefix: string;
|
|
9
9
|
/** Already-derived dependency list, in `name`/`name >= version` form. */
|
|
10
10
|
depends: readonly string[];
|
|
11
|
+
/**
|
|
12
|
+
* Soft dependencies, rendered as `Recommends:`.
|
|
13
|
+
*
|
|
14
|
+
* Debian Policy § 7.2: a recommended package is installed by default and
|
|
15
|
+
* `--no-install-recommends` is the user's way out. That is what an `&optional`
|
|
16
|
+
* typelib is (ADR 0087) — the app runs without it and loses a feature — so
|
|
17
|
+
* putting it in `Depends:` would let an optional integration decide whether
|
|
18
|
+
* the package installs at all.
|
|
19
|
+
*/
|
|
20
|
+
recommends?: readonly string[];
|
|
11
21
|
/** Debian architecture (`amd64`, `all`, …). */
|
|
12
22
|
archLabel: string;
|
|
13
23
|
/** Unix seconds stamped into every header. */
|
package/lib/utils/ship/deb.js
CHANGED
|
@@ -104,6 +104,13 @@ function renderControl(inputs, installedSize) {
|
|
|
104
104
|
];
|
|
105
105
|
if (inputs.depends.length > 0)
|
|
106
106
|
fields.push(['Depends', inputs.depends.map(formatDebDepend).join(', ')]);
|
|
107
|
+
// After `Depends`, which is the field order dpkg-deb --control writes and the
|
|
108
|
+
// one `grep '^Depends:'` on a control file assumes. Omitted when empty rather
|
|
109
|
+
// than written as `Recommends:` with nothing after it — a field with an empty
|
|
110
|
+
// value is a parse error here, the same reason `formatDebDepend` exists.
|
|
111
|
+
if (inputs.recommends !== undefined && inputs.recommends.length > 0) {
|
|
112
|
+
fields.push(['Recommends', inputs.recommends.map(formatDebDepend).join(', ')]);
|
|
113
|
+
}
|
|
107
114
|
fields.push(['Section', settings.section], ['Priority', 'optional']);
|
|
108
115
|
if (settings.homepage)
|
|
109
116
|
fields.push(['Homepage', settings.homepage]);
|
|
@@ -43,6 +43,17 @@ export declare function warnAboutNodeFloor(format: DistroFormatId, floor: string
|
|
|
43
43
|
export interface DependsInputs {
|
|
44
44
|
/** GI namespaces the bundle imports, in `Ns-Version` spelling. */
|
|
45
45
|
namespaces: readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* The subset of {@link namespaces} the bundle declares OPTIONAL (ADR 0087 —
|
|
48
|
+
* `gi://Ns?version=X&optional`): a `Recommends:` entry, never a `Depends:`.
|
|
49
|
+
*
|
|
50
|
+
* Kept as its own list rather than filtered out of {@link namespaces}, because
|
|
51
|
+
* the AppImage has no package list at all and still has to SAY which of its host
|
|
52
|
+
* requirements are optional — and because a stage manifest written before the
|
|
53
|
+
* split carries no such field, where answering "everything is hard" is the safe
|
|
54
|
+
* direction.
|
|
55
|
+
*/
|
|
56
|
+
optionalNamespaces?: readonly string[];
|
|
46
57
|
/**
|
|
47
58
|
* Whether the payload installs into `share/icons/hicolor/`.
|
|
48
59
|
*
|
|
@@ -112,11 +123,34 @@ export interface DependsInputs {
|
|
|
112
123
|
*/
|
|
113
124
|
export declare function hostProvidedNamespaces(namespaces: readonly string[], bundledTypelibs?: readonly string[]): string[];
|
|
114
125
|
/**
|
|
115
|
-
*
|
|
126
|
+
* One format's dependency lists, split by how hard the requirement is.
|
|
127
|
+
*
|
|
128
|
+
* `requires` is what an absent package makes the app fail over; `recommends` is what
|
|
129
|
+
* its absence only costs. A namespace the BUNDLE marked optional (ADR 0087) is the
|
|
130
|
+
* second kind by declaration — the app's own `check()` degrades — and both `.deb`
|
|
131
|
+
* and `.rpm` have a field for it, so there is no reason to declare it hard.
|
|
132
|
+
*/
|
|
133
|
+
export interface DerivedDepends {
|
|
134
|
+
/** `Depends:` / `Requires:` — a package the artifact cannot run without. */
|
|
135
|
+
requires: string[];
|
|
136
|
+
/** `Recommends:` — installed by default where the format has such a field. */
|
|
137
|
+
recommends: string[];
|
|
138
|
+
/**
|
|
139
|
+
* Optional namespaces no table row is known for.
|
|
140
|
+
*
|
|
141
|
+
* REPORTED, never fatal: a `Recommends:` line for a namespace nobody mapped is
|
|
142
|
+
* the one case where saying nothing is correct — the app runs without the
|
|
143
|
+
* typelib either way, which is precisely what the flag declared. The HARD
|
|
144
|
+
* unmapped case still throws, below.
|
|
145
|
+
*/
|
|
146
|
+
unmappedOptional: string[];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The `Depends:` / `Requires:` list for one format, plus its `Recommends:`.
|
|
116
150
|
*
|
|
117
|
-
* @throws when a namespace has no entry in the table — see the module header.
|
|
151
|
+
* @throws when a HARD namespace has no entry in the table — see the module header.
|
|
118
152
|
*/
|
|
119
|
-
export declare function deriveDepends(format: DistroFormatId, inputs: DependsInputs):
|
|
153
|
+
export declare function deriveDepends(format: DistroFormatId, inputs: DependsInputs): DerivedDepends;
|
|
120
154
|
/** One parsed dependency. `relation`/`version` are absent for an unversioned one. */
|
|
121
155
|
export interface ParsedDepend {
|
|
122
156
|
name: string;
|
|
@@ -287,9 +287,9 @@ export function hostProvidedNamespaces(namespaces, bundledTypelibs = []) {
|
|
|
287
287
|
return [...new Set(namespaces)].sort().filter((ns) => !BUNDLED_TYPELIB.test(ns) && !shipped.has(ns));
|
|
288
288
|
}
|
|
289
289
|
/**
|
|
290
|
-
* The `Depends:` / `Requires:` list for one format
|
|
290
|
+
* The `Depends:` / `Requires:` list for one format, plus its `Recommends:`.
|
|
291
291
|
*
|
|
292
|
-
* @throws when a namespace has no entry in the table — see the module header.
|
|
292
|
+
* @throws when a HARD namespace has no entry in the table — see the module header.
|
|
293
293
|
*/
|
|
294
294
|
export function deriveDepends(format, inputs) {
|
|
295
295
|
// The interpreter the launcher execs, and nothing else: a package declares ONE.
|
|
@@ -297,17 +297,25 @@ export function deriveDepends(format, inputs) {
|
|
|
297
297
|
// no system Node, so an artifact for those CARRIES one from
|
|
298
298
|
// `@gjsify/node-runtime-<target>`, and that is the whole reason those three
|
|
299
299
|
// packages exist and this one line is their opposite.
|
|
300
|
-
const
|
|
300
|
+
const requires = inputs.interpreter === 'node'
|
|
301
301
|
? [`${NODE_PACKAGE[format]} >= ${inputs.minNodeVersion ?? DEFAULT_NODE_FLOOR}`]
|
|
302
302
|
: [`gjs >= ${inputs.minGjsVersion ?? DEFAULT_GJS_FLOOR}`];
|
|
303
|
+
const recommends = [];
|
|
304
|
+
const optional = new Set(inputs.optionalNamespaces ?? []);
|
|
303
305
|
const unmapped = [];
|
|
306
|
+
const unmappedOptional = [];
|
|
304
307
|
for (const namespace of hostProvidedNamespaces(inputs.namespaces, inputs.bundledTypelibs)) {
|
|
305
308
|
const entry = lookupTypelib(namespace, inputs.typelibPackages);
|
|
306
309
|
if (entry === undefined) {
|
|
307
|
-
unmapped.push(namespace);
|
|
310
|
+
(optional.has(namespace) ? unmappedOptional : unmapped).push(namespace);
|
|
308
311
|
continue;
|
|
309
312
|
}
|
|
310
|
-
|
|
313
|
+
// A namespace imported both ways is a HARD dependency — one importer asked
|
|
314
|
+
// for it unguarded — and a package manager field may not name it twice.
|
|
315
|
+
if (optional.has(namespace) && !requires.includes(entry[format]))
|
|
316
|
+
recommends.push(entry[format]);
|
|
317
|
+
else
|
|
318
|
+
requires.push(entry[format]);
|
|
311
319
|
}
|
|
312
320
|
if (unmapped.length > 0) {
|
|
313
321
|
throw new Error(`gjsify ship: the bundle imports ${unmapped.map((n) => `gi://${n.split('-')[0]}`).join(', ')}, ` +
|
|
@@ -323,7 +331,7 @@ export function deriveDepends(format, inputs) {
|
|
|
323
331
|
// The package that owns `/usr/share/icons/hicolor` — so this follows the icons, not the
|
|
324
332
|
// app/cli distinction that used to stand in for them.
|
|
325
333
|
if (inputs.hasIcons)
|
|
326
|
-
|
|
334
|
+
requires.push('hicolor-icon-theme');
|
|
327
335
|
// The package that ships `glib-compile-schemas`, NOT `gsettings-desktop-schemas`
|
|
328
336
|
// (which ships GNOME's own `org.gnome.desktop.*` schemas and has nothing to
|
|
329
337
|
// do with compiling ours). Measured: `rpm -qf /usr/bin/glib-compile-schemas`
|
|
@@ -333,11 +341,15 @@ export function deriveDepends(format, inputs) {
|
|
|
333
341
|
// `Gio.Settings.new()` aborts the app — an install that succeeds and an app
|
|
334
342
|
// that does not start.
|
|
335
343
|
if (inputs.hasSchemas)
|
|
336
|
-
|
|
337
|
-
|
|
344
|
+
requires.push(SCHEMA_COMPILER_PACKAGE[format]);
|
|
345
|
+
requires.push(...inputs.extra);
|
|
338
346
|
// Set-dedupe keeps first-insertion order, so the interpreter stays first and
|
|
339
347
|
// the list is stable across runs.
|
|
340
|
-
return
|
|
348
|
+
return {
|
|
349
|
+
requires: [...new Set(requires)],
|
|
350
|
+
recommends: [...new Set(recommends)].filter((name) => !requires.includes(name)),
|
|
351
|
+
unmappedOptional,
|
|
352
|
+
};
|
|
341
353
|
}
|
|
342
354
|
/**
|
|
343
355
|
* Resolve a scanned specifier to a table row.
|
|
@@ -64,3 +64,19 @@ export interface ResolvedShipFlatpak {
|
|
|
64
64
|
* has somewhere else to point at.
|
|
65
65
|
*/
|
|
66
66
|
export declare function resolveShipFlatpakSettings(input: ShipFlatpakInput): ResolvedShipFlatpak;
|
|
67
|
+
/**
|
|
68
|
+
* Refuse a Flatpak ship that would silently drop the project's own modules.
|
|
69
|
+
*
|
|
70
|
+
* `gjsify.flatpak.modules` / `extraModules` are read by `gjsify flatpak init`
|
|
71
|
+
* only; `ship` renders exactly one module, the staged payload (ADR 0024 § 2),
|
|
72
|
+
* and the keys' absence from {@link ShipFlatpakOptions} is deliberate. The
|
|
73
|
+
* defect was the silence: a project that moved to `ship` kept the keys, got a
|
|
74
|
+
* manifest without its `extra-data` module and exit 0, and found out when the
|
|
75
|
+
* app started without the binary the module downloads. A key `ship` does not
|
|
76
|
+
* read must not be one it ignores quietly, so the run stops BEFORE the build.
|
|
77
|
+
*
|
|
78
|
+
* Called only when `flatpak` is among the formats being made: the same keys
|
|
79
|
+
* still drive `gjsify flatpak init`, and a `ship --target deb` has nothing to
|
|
80
|
+
* drop them from.
|
|
81
|
+
*/
|
|
82
|
+
export declare function assertNoDroppedFlatpakModules(flatpak: ConfigDataFlatpak): void;
|
|
@@ -120,6 +120,32 @@ export function resolveShipFlatpakSettings(input) {
|
|
|
120
120
|
};
|
|
121
121
|
return { settings, warnings: fromLegacy.length === 0 ? [] : [deprecationWarning(fromLegacy)] };
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Refuse a Flatpak ship that would silently drop the project's own modules.
|
|
125
|
+
*
|
|
126
|
+
* `gjsify.flatpak.modules` / `extraModules` are read by `gjsify flatpak init`
|
|
127
|
+
* only; `ship` renders exactly one module, the staged payload (ADR 0024 § 2),
|
|
128
|
+
* and the keys' absence from {@link ShipFlatpakOptions} is deliberate. The
|
|
129
|
+
* defect was the silence: a project that moved to `ship` kept the keys, got a
|
|
130
|
+
* manifest without its `extra-data` module and exit 0, and found out when the
|
|
131
|
+
* app started without the binary the module downloads. A key `ship` does not
|
|
132
|
+
* read must not be one it ignores quietly, so the run stops BEFORE the build.
|
|
133
|
+
*
|
|
134
|
+
* Called only when `flatpak` is among the formats being made: the same keys
|
|
135
|
+
* still drive `gjsify flatpak init`, and a `ship --target deb` has nothing to
|
|
136
|
+
* drop them from.
|
|
137
|
+
*/
|
|
138
|
+
export function assertNoDroppedFlatpakModules(flatpak) {
|
|
139
|
+
const dropped = ['modules', 'extraModules'].filter((key) => (flatpak[key]?.length ?? 0) > 0);
|
|
140
|
+
if (dropped.length === 0)
|
|
141
|
+
return;
|
|
142
|
+
const list = dropped.map((key) => `gjsify.flatpak.${key}`).join(' and ');
|
|
143
|
+
throw new Error(`gjsify ship: ${list} ${dropped.length > 1 ? 'are' : 'is'} set, but the \`flatpak\` target renders ` +
|
|
144
|
+
'ONE module, the staged payload, and would drop ' +
|
|
145
|
+
`${dropped.length > 1 ? 'them' : 'it'} (ADR 0024 § 2). Build this Flatpak with ` +
|
|
146
|
+
'`gjsify flatpak init` + `gjsify flatpak build`, which honour the key, or leave `flatpak` out of ' +
|
|
147
|
+
'`--target` / `gjsify.ship.targets`.');
|
|
148
|
+
}
|
|
123
149
|
/** One line, naming every key still read from the old block and where each goes. */
|
|
124
150
|
function deprecationWarning(keys) {
|
|
125
151
|
const list = keys.map((key) => `gjsify.flatpak.${key}`).join(', ');
|
|
@@ -132,12 +132,14 @@ function macosArch(arch, _archIndependent) {
|
|
|
132
132
|
/**
|
|
133
133
|
* `process.arch` → the name Windows and everything that targets it use.
|
|
134
134
|
*
|
|
135
|
-
* ONE ROW, and it is not caution:
|
|
136
|
-
* `
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
135
|
+
* ONE ROW, and it is not caution: there is no arm64 GTK CLOSURE to put on it yet.
|
|
136
|
+
* `wingtk/gvsbuild` publishes no arm64 assets, which is why ADR 0089 builds that
|
|
137
|
+
* prefix from our `gjsify/gvsbuild` fork in CI instead — but what a Windows
|
|
138
|
+
* artifact stages is `@gjsify/gtk-runtime-*`, and the arm64 one is not built, so
|
|
139
|
+
* there is nothing for a Windows/ARM artifact to load (#1117). `Layout.arches`
|
|
140
|
+
* refuses the same value one phase earlier, at stage time, with that reason spelled
|
|
141
|
+
* out; this table is the second half — the one that stops a stage assembled by an
|
|
142
|
+
* older gjsify from acquiring a label here.
|
|
141
143
|
*
|
|
142
144
|
* `x64` maps to itself, and unlike the macOS table that is not a coincidence to be
|
|
143
145
|
* caught later: `x64` is the spelling Node's own release archives use
|
|
@@ -734,7 +736,8 @@ export const FORMATS = {
|
|
|
734
736
|
// * there is no `Info.plist` and no metadata file at all: what a Windows
|
|
735
737
|
// installer says about an application lives in the `.msi`'s own tables
|
|
736
738
|
// (#1354 M5), which is why `Layout.metadata` answers `[]` here.
|
|
737
|
-
// * `x64` alone, and the blocker is
|
|
739
|
+
// * `x64` alone, and the blocker is a package of ours we have not built —
|
|
740
|
+
// see `WINDOWS_ARCH`.
|
|
738
741
|
//
|
|
739
742
|
// NEITHER IS SIGNED BY DEFAULT, and Windows is the softer of the two asymmetries
|
|
740
743
|
// ADR 0024 § A5 records: Gatekeeper BLOCKS an unsigned `.app`, while SmartScreen
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a bundle loads, split by whether the host must provide it.
|
|
3
|
+
*
|
|
4
|
+
* `namespaces` is the union — the same answer {@link scanGiNamespaces} has always
|
|
5
|
+
* given — and `optional` is the subset the artifact itself marks optional. A namespace
|
|
6
|
+
* in both is still a hard dependency as far as anything else is concerned: the
|
|
7
|
+
* importer asked for it both ways, and only one of those edges degrades.
|
|
8
|
+
*/
|
|
9
|
+
export interface GiRequirements {
|
|
10
|
+
/** Every GI namespace the bundle loads, `Ns-Version` where a specifier pins one. */
|
|
11
|
+
namespaces: string[];
|
|
12
|
+
/** The subset carrying the bundler's optional marker — absent is not fatal. */
|
|
13
|
+
optional: string[];
|
|
14
|
+
}
|
|
1
15
|
/**
|
|
2
16
|
* Extract the GI namespaces a bundle imports, as `Ns-Version` when the
|
|
3
17
|
* specifier pins one and bare `Ns` when it does not.
|
|
4
18
|
*/
|
|
5
19
|
export declare function scanGiNamespaces(source: string): string[];
|
|
20
|
+
/** {@link scanGiNamespaces}, plus which of them the artifact marks optional (ADR 0087). */
|
|
21
|
+
export declare function scanGiRequirements(source: string): GiRequirements;
|
|
6
22
|
/** `gi://Gtk?version=4.0` → `Gtk-4.0`; `gi://Gtk` → `Gtk`; anything else → null. */
|
|
7
23
|
export declare function parseGiSpecifier(specifier: string): string | null;
|