@gjsify/cli 0.43.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.gjs.mjs +453 -425
- package/lib/actions/build.js +2 -1
- package/lib/commands/affected-classify.js +7 -5
- package/lib/commands/build.js +6 -0
- package/lib/commands/flatpak/ci.js +10 -2
- package/lib/commands/flatpak/init.js +15 -16
- package/lib/commands/flatpak/utils.d.ts +0 -26
- package/lib/commands/flatpak/utils.js +4 -29
- 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 +911 -70
- package/lib/commands/trust.d.ts +6 -0
- package/lib/commands/trust.js +1 -1
- package/lib/config.js +25 -0
- package/lib/types/cli-build-options.d.ts +11 -0
- package/lib/types/config-data.d.ts +137 -3
- package/lib/utils/flatpak-runtime.d.ts +41 -0
- package/lib/utils/flatpak-runtime.js +54 -0
- 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 +54 -7
- package/lib/utils/ship/depends.js +120 -6
- package/lib/utils/ship/discover.js +18 -1
- package/lib/utils/ship/dmg.d.ts +71 -0
- package/lib/utils/ship/dmg.js +181 -0
- package/lib/utils/ship/flatpak-config.d.ts +66 -0
- package/lib/utils/ship/flatpak-config.js +132 -0
- package/lib/utils/ship/flatpak.d.ts +45 -0
- package/lib/utils/ship/flatpak.js +148 -0
- package/lib/utils/ship/formats.d.ts +116 -3
- package/lib/utils/ship/formats.js +869 -11
- package/lib/utils/ship/launcher.d.ts +13 -3
- package/lib/utils/ship/launcher.js +362 -17
- package/lib/utils/ship/layout.d.ts +268 -0
- package/lib/utils/ship/layout.js +351 -0
- package/lib/utils/ship/localize-metadata.d.ts +22 -0
- package/lib/utils/ship/localize-metadata.js +126 -0
- package/lib/utils/ship/mime.js +1 -9
- package/lib/utils/ship/msi.d.ts +126 -0
- package/lib/utils/ship/msi.js +417 -0
- package/lib/utils/ship/node-runtime.d.ts +71 -0
- package/lib/utils/ship/node-runtime.js +122 -0
- package/lib/utils/ship/payload.d.ts +156 -5
- package/lib/utils/ship/payload.js +580 -14
- package/lib/utils/ship/plan.d.ts +21 -0
- package/lib/utils/ship/plan.js +20 -7
- package/lib/utils/ship/plist.d.ts +58 -0
- package/lib/utils/ship/plist.js +198 -0
- package/lib/utils/ship/rpm.js +9 -3
- package/lib/utils/ship/schemas.d.ts +61 -0
- package/lib/utils/ship/schemas.js +118 -0
- package/lib/utils/ship/scripts.js +7 -3
- package/lib/utils/ship/settings.d.ts +19 -1
- package/lib/utils/ship/settings.js +19 -1
- package/lib/utils/ship/share-dirs.d.ts +23 -0
- package/lib/utils/ship/share-dirs.js +46 -0
- package/lib/utils/ship/signing.d.ts +195 -0
- package/lib/utils/ship/signing.js +317 -0
- package/lib/utils/ship/stage-manifest.d.ts +50 -13
- package/lib/utils/ship/stage-manifest.js +137 -14
- package/lib/utils/ship/stage-writer.d.ts +48 -0
- package/lib/utils/ship/stage-writer.js +71 -1
- package/lib/utils/ship/types.d.ts +264 -16
- package/lib/utils/ship/xml.d.ts +7 -0
- package/lib/utils/ship/xml.js +25 -0
- package/lib/utils/ship/zip.d.ts +69 -0
- package/lib/utils/ship/zip.js +245 -0
- package/lib/utils/trust-registry.js +5 -0
- package/package.json +19 -19
package/lib/commands/trust.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ export interface TrustRequestResult {
|
|
|
20
20
|
status: number;
|
|
21
21
|
json: unknown;
|
|
22
22
|
text: string;
|
|
23
|
+
/**
|
|
24
|
+
* Response headers, so a caller can honour npm's own `Retry-After` on a 429
|
|
25
|
+
* instead of guessing a backoff. Optional because test doubles construct this
|
|
26
|
+
* shape by hand and a missing header is a well-defined "it said nothing".
|
|
27
|
+
*/
|
|
28
|
+
headers?: Headers;
|
|
23
29
|
}
|
|
24
30
|
/** A `(method, url, body?) => TrustRequestResult` function bound to a registry auth + OTP context. */
|
|
25
31
|
export type TrustRequester = (method: string, url: string, body?: unknown) => Promise<TrustRequestResult>;
|
package/lib/commands/trust.js
CHANGED
|
@@ -279,7 +279,7 @@ export function createTrustRequester(ctx) {
|
|
|
279
279
|
catch {
|
|
280
280
|
/* non-JSON body — leave json undefined */
|
|
281
281
|
}
|
|
282
|
-
return { status: res.status, json, text };
|
|
282
|
+
return { status: res.status, json, text, headers: res.headers };
|
|
283
283
|
};
|
|
284
284
|
}
|
|
285
285
|
function firstLine(s) {
|
package/lib/config.js
CHANGED
|
@@ -4,6 +4,7 @@ import { basename, dirname } from 'node:path';
|
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
5
|
import { isGjs, hostRuntime } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
6
6
|
import { buildAppForRuntime } from './utils/runtimes.js';
|
|
7
|
+
import { DIALECT_APPS, isSourceDialect, SOURCE_DIALECTS } from '@gjsify/rolldown-plugin-gjsify';
|
|
7
8
|
/**
|
|
8
9
|
* Does the failed `import()` of a config look like a MODULE-RESOLUTION failure
|
|
9
10
|
* (the config imports something GJS's ESM loader can't resolve for an external
|
|
@@ -268,6 +269,18 @@ export class Config {
|
|
|
268
269
|
configData.exclude = cliArgs.exclude || [];
|
|
269
270
|
if (cliArgs.consoleShim !== undefined)
|
|
270
271
|
configData.consoleShim = cliArgs.consoleShim;
|
|
272
|
+
// `--dialect` narrows here rather than at the call site. yargs `choices`
|
|
273
|
+
// already rejects an unknown value typed on the command line, but a
|
|
274
|
+
// `gjsify.dialect` in package.json or `.gjsifyrc.*` never passes through
|
|
275
|
+
// yargs — so the one place both spellings meet is the one place that
|
|
276
|
+
// checks, and it names the alternatives instead of silently ignoring.
|
|
277
|
+
if (cliArgs.dialect !== undefined) {
|
|
278
|
+
if (!isSourceDialect(cliArgs.dialect)) {
|
|
279
|
+
throw new Error(`gjsify build: "${cliArgs.dialect}" is not a source dialect. Known: ${SOURCE_DIALECTS.join(', ')}. ` +
|
|
280
|
+
'This is the dialect the SOURCE is written in — not gjsify.runtimes, which declares the runtime a package RUNS on.');
|
|
281
|
+
}
|
|
282
|
+
configData.dialect = cliArgs.dialect;
|
|
283
|
+
}
|
|
271
284
|
// Default `--app` FOLLOWS the host runtime the CLI executes in: gjs when
|
|
272
285
|
// run under gjs, node when run under node/bun/deno (both consume the
|
|
273
286
|
// `--app node` bundle). Applied post-merge (not as a yargs `default:`)
|
|
@@ -278,6 +291,18 @@ export class Config {
|
|
|
278
291
|
if (cliArgs.app !== undefined)
|
|
279
292
|
configData.app = cliArgs.app;
|
|
280
293
|
configData.app ??= buildAppForRuntime(hostRuntime());
|
|
294
|
+
// A dialect only composes on the targets that have a plugin for it, and
|
|
295
|
+
// saying so is the whole point: `--dialect react-native --app browser`
|
|
296
|
+
// parses, resolves and then composes NOTHING, so the alias never happens,
|
|
297
|
+
// the support gate never runs, and the build succeeds while doing none of
|
|
298
|
+
// what was asked. That is the shape this repo keeps paying for — a flag
|
|
299
|
+
// accepted and ignored. Checked here rather than in the plugin because the
|
|
300
|
+
// plugin only ever sees the targets it was composed on.
|
|
301
|
+
if (configData.dialect !== undefined && !DIALECT_APPS.has(configData.app)) {
|
|
302
|
+
throw new Error(`gjsify build: --dialect ${configData.dialect} has no effect on --app ${configData.app}. ` +
|
|
303
|
+
`It composes on ${[...DIALECT_APPS].join(' and ')} only. ` +
|
|
304
|
+
'Drop the dialect, or build one of those targets.');
|
|
305
|
+
}
|
|
281
306
|
if (cliArgs.globals !== undefined)
|
|
282
307
|
configData.globals = cliArgs.globals;
|
|
283
308
|
// Fallback applied post-merge (not as a yargs `default:`) so a
|
|
@@ -25,6 +25,17 @@ export interface CliBuildOptions {
|
|
|
25
25
|
* working ANSI colors). Disable with `--no-console-shim`. Default: true.
|
|
26
26
|
*/
|
|
27
27
|
consoleShim?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* The dialect the SOURCE is written in — `--dialect react-native` (ADR 0032):
|
|
30
|
+
* alias `react-native` onto `@gjsify/react-native` and fail the build on an
|
|
31
|
+
* import whose support-table status is not `supported` or `partial`. `gjs` and
|
|
32
|
+
* `node` targets. Default: unset — a tree with a phone leg has the real
|
|
33
|
+
* `react-native` on purpose.
|
|
34
|
+
*
|
|
35
|
+
* A `string` here and a `SourceDialect` in `ConfigData`: yargs hands back
|
|
36
|
+
* whatever was typed, and the narrowing happens once, where the value is read.
|
|
37
|
+
*/
|
|
38
|
+
dialect?: string;
|
|
28
39
|
/**
|
|
29
40
|
* Comma-separated global identifiers the code needs, e.g.
|
|
30
41
|
* `"fetch,Buffer,process,URL,crypto"`. Each maps to the corresponding
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RolldownOptions, OutputOptions, RolldownPluginOption } from 'rolldown';
|
|
2
|
-
import type { App } from '@gjsify/rolldown-plugin-gjsify';
|
|
2
|
+
import type { App, SourceDialect } from '@gjsify/rolldown-plugin-gjsify';
|
|
3
3
|
import type { ConfigDataLibrary, ConfigDataTypescript } from './index.js';
|
|
4
4
|
/**
|
|
5
5
|
* Plugin entry resolvable by package name from the project's `node_modules`, so
|
|
@@ -82,6 +82,20 @@ export interface ConfigData {
|
|
|
82
82
|
* working ANSI colors). Default: true.
|
|
83
83
|
*/
|
|
84
84
|
consoleShim?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* The dialect the SOURCE is written in (ADR 0032 § 2 + § 8). `'react-native'`
|
|
87
|
+
* aliases `react-native` onto `@gjsify/react-native` and gates every named
|
|
88
|
+
* import against the support table at BUILD time. `gjs` and `node` app builds.
|
|
89
|
+
* Default: unset.
|
|
90
|
+
*
|
|
91
|
+
* Opt-in and never inferred from the dependency list: a monorepo with a phone
|
|
92
|
+
* leg installs the real `react-native` on purpose, and redirecting the
|
|
93
|
+
* specifier for every build in that tree would change what that leg resolves.
|
|
94
|
+
*
|
|
95
|
+
* NOT `gjsify.runtimes['react-native']`, which answers the opposite question —
|
|
96
|
+
* see the header of `SourceDialect`.
|
|
97
|
+
*/
|
|
98
|
+
dialect?: SourceDialect;
|
|
85
99
|
/** Comma-separated global identifiers to register. Format: see CliBuildOptions. */
|
|
86
100
|
globals?: string;
|
|
87
101
|
/**
|
|
@@ -416,7 +430,15 @@ export interface ConfigDataShip extends AppMetadata {
|
|
|
416
430
|
release?: string;
|
|
417
431
|
/** `Maintainer:` / `Packager:` as `Name <email>`. Default: `package.json#author`. */
|
|
418
432
|
maintainer?: string;
|
|
419
|
-
/**
|
|
433
|
+
/**
|
|
434
|
+
* Formats to build when `--target` is not given. Default `['deb', 'rpm']`.
|
|
435
|
+
*
|
|
436
|
+
* A project-level DEFAULT, so it is filtered to the layout `gjsify ship <os>`
|
|
437
|
+
* assembles rather than refused against it — a name here that wraps another
|
|
438
|
+
* OS's layout is dropped, and `gjsify ship` prints which. A `--target` on the
|
|
439
|
+
* command line is a claim about ONE run and is refused instead. Without that
|
|
440
|
+
* split, declaring this key made `gjsify ship darwin --stage` impossible.
|
|
441
|
+
*/
|
|
420
442
|
targets?: string[];
|
|
421
443
|
/** Output root, relative to the project. Default `'ship'`. */
|
|
422
444
|
outDir?: string;
|
|
@@ -443,6 +465,18 @@ export interface ConfigDataShip extends AppMetadata {
|
|
|
443
465
|
* start.
|
|
444
466
|
*/
|
|
445
467
|
minGjsVersion?: string;
|
|
468
|
+
/**
|
|
469
|
+
* Minimum Node major the emitted dependency asks for, when the payload is a
|
|
470
|
+
* `--app node` bundle. Default `'24'` — the LTS line the
|
|
471
|
+
* `@gjsify/node-runtime-*` packages bundle for macOS and Windows.
|
|
472
|
+
*
|
|
473
|
+
* Lower it only if the bundle genuinely runs on an older Node. The default
|
|
474
|
+
* excludes every current DEB stable/LTS (trixie 20, Ubuntu 24.04 18, Ubuntu
|
|
475
|
+
* 26.04 22), so it makes a `.deb` that apt refuses there rather than one that
|
|
476
|
+
* installs and then dies on the first unsupported syntax — the same trade
|
|
477
|
+
* `minGjsVersion` makes, and `gjsify ship` warns about it either way.
|
|
478
|
+
*/
|
|
479
|
+
minNodeVersion?: string;
|
|
446
480
|
/**
|
|
447
481
|
* Runtime dependencies appended to the set derived from the bundle's
|
|
448
482
|
* `gi://` imports — the escape hatch for a namespace the table does not
|
|
@@ -496,8 +530,108 @@ export interface ConfigDataShip extends AppMetadata {
|
|
|
496
530
|
extraFiles?: Record<string, string>;
|
|
497
531
|
/** Arguments the launcher appends before the user's own. */
|
|
498
532
|
execArgs?: string[];
|
|
533
|
+
/**
|
|
534
|
+
* How `--target flatpak` builds the app: the runtime it links against and
|
|
535
|
+
* what the finished app is allowed to do.
|
|
536
|
+
*
|
|
537
|
+
* The new home of the `gjsify.flatpak` BUILD keys (ADR 0024 § 8). The old
|
|
538
|
+
* spelling still resolves and warns — the window, and what deliberately did
|
|
539
|
+
* NOT move, is `utils/ship/flatpak-config.ts`.
|
|
540
|
+
*/
|
|
541
|
+
flatpak?: ShipFlatpakOptions;
|
|
542
|
+
/**
|
|
543
|
+
* The signing identity this project uses by default, per OS.
|
|
544
|
+
*
|
|
545
|
+
* `--sign <identity>` on the command line wins. Absent means unsigned, which
|
|
546
|
+
* is the default path and a legitimate output (ADR 0024 § A13).
|
|
547
|
+
*/
|
|
548
|
+
sign?: ShipSignOptions;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* `gjsify.ship.sign` — an IDENTITY per OS, and never a certificate.
|
|
552
|
+
*
|
|
553
|
+
* KEYED PER OS, and the two keys must not be one field: a Developer ID string
|
|
554
|
+
* and an Authenticode subject name live in different namespaces, and the value
|
|
555
|
+
* that resolves is chosen by the LAYOUT being packed (`FormatDescriptor.layoutOs`
|
|
556
|
+
* and the stage manifest's `target.os`), not by the host doing the packing
|
|
557
|
+
* (ADR 0024 § A12).
|
|
558
|
+
*
|
|
559
|
+
* `linux` is deliberately absent: a `.deb` and an `.rpm` carry no per-file
|
|
560
|
+
* signature — the artifact is signed as a whole by the repository that serves
|
|
561
|
+
* it, with that repository's key. A key here would look like it did something.
|
|
562
|
+
*
|
|
563
|
+
* WHAT IS NOT HERE, and each is a decision rather than an omission:
|
|
564
|
+
* - a certificate, a `.p12` path or a password. `codesign` and `signtool` are
|
|
565
|
+
* handed a NAME and resolve the private key themselves, so `gjsify ship`
|
|
566
|
+
* never sees a secret and nothing on this surface has to be redacted from a
|
|
567
|
+
* log line (§ A14).
|
|
568
|
+
* - an `entitlements` key. § A16 leaves library validation vs. hardened runtime
|
|
569
|
+
* explicitly unmeasured, and a config key for an unmeasured mechanism is a
|
|
570
|
+
* promise the implementation cannot keep.
|
|
571
|
+
* - a notarisation credential. That is a per-RUN account credential, not a
|
|
572
|
+
* machine-held identity, and a config key for one is an invitation to commit
|
|
573
|
+
* it — `--notarize <keychain-profile>` is the only way to pass it.
|
|
574
|
+
*/
|
|
575
|
+
export interface ShipSignOptions {
|
|
576
|
+
/** `codesign --sign <identity>`. `'-'` is ad-hoc and needs no developer identity. */
|
|
577
|
+
darwin?: {
|
|
578
|
+
identity?: string;
|
|
579
|
+
};
|
|
580
|
+
/** `signtool sign /n <identity>` — the certificate's SUBJECT NAME in a Windows store. */
|
|
581
|
+
win32?: {
|
|
582
|
+
identity?: string;
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* The Flatpak knobs `gjsify ship` reads. Every one has a derived default.
|
|
587
|
+
*
|
|
588
|
+
* There is no `modules` / `extraModules` here, and that is a decision rather
|
|
589
|
+
* than an omission: under `ship` the module list IS the staged payload
|
|
590
|
+
* (`buildsystem: simple` + `cp -a stage/.`), and an escape hatch injecting
|
|
591
|
+
* arbitrary build modules would put the second staging model back in the tree —
|
|
592
|
+
* the one thing ADR 0024 § 8 gates the whole migration on. A project that has
|
|
593
|
+
* to BUILD something inside the sandbox still has `gjsify flatpak init` +
|
|
594
|
+
* `gjsify flatpak build`, unchanged.
|
|
595
|
+
*/
|
|
596
|
+
export interface ShipFlatpakOptions {
|
|
597
|
+
/**
|
|
598
|
+
* Runtime family. Default `'gnome'` — a GJS bundle needs GLib/GObject/GIO at
|
|
599
|
+
* runtime, and no GJS interpreter ships in the Freedesktop runtime.
|
|
600
|
+
*/
|
|
601
|
+
runtime?: 'gnome' | 'freedesktop';
|
|
602
|
+
/** Runtime/SDK version, e.g. `'50'` for GNOME or `'24.08'` for Freedesktop. */
|
|
603
|
+
runtimeVersion?: string;
|
|
604
|
+
/**
|
|
605
|
+
* The branch the app is exported under — the last segment of
|
|
606
|
+
* `app/<id>/<arch>/<branch>`. Default `'stable'`, which is what Flathub
|
|
607
|
+
* publishes and what `flatpak install` resolves without being told.
|
|
608
|
+
*/
|
|
609
|
+
branch?: string;
|
|
610
|
+
/** Extra SDK extensions, e.g. `['org.freedesktop.Sdk.Extension.llvm17']`. */
|
|
611
|
+
sdkExtensions?: string[];
|
|
612
|
+
/**
|
|
613
|
+
* Path components prepended to `PATH` inside the build sandbox. Derived from
|
|
614
|
+
* `sdkExtensions` when unset, because an extension whose `bin` is not on
|
|
615
|
+
* PATH is one the build cannot use.
|
|
616
|
+
*/
|
|
617
|
+
appendPath?: string[];
|
|
618
|
+
/** Finish-args (capabilities). Default depends on `kind`: GUI args for an app, none for a CLI. */
|
|
619
|
+
finishArgs?: string[];
|
|
620
|
+
/** Cleanup globs applied to `/app`, e.g. `['/include', '/lib/pkgconfig']`. */
|
|
621
|
+
cleanup?: string[];
|
|
499
622
|
}
|
|
500
|
-
/**
|
|
623
|
+
/**
|
|
624
|
+
* Flatpak-toolchain config for the `gjsify flatpak` subcommand group.
|
|
625
|
+
*
|
|
626
|
+
* Six of these keys — `runtime`, `runtimeVersion`, `sdkExtensions`,
|
|
627
|
+
* `appendPath`, `finishArgs`, `cleanup` — have a second home at
|
|
628
|
+
* {@link ShipFlatpakOptions}, and `gjsify ship` warns when it reads them from
|
|
629
|
+
* here (ADR 0024 § 8; the window is `utils/ship/flatpak-config.ts`). They are
|
|
630
|
+
* NOT marked `@deprecated`: for the subcommands in this group they are still
|
|
631
|
+
* the only spelling, and those commands have not moved under `ship` yet
|
|
632
|
+
* (`status/open-todos.md`). Nothing else in this block is deprecated at all —
|
|
633
|
+
* the {@link AppMetadata} half is a designed alias, not a legacy one.
|
|
634
|
+
*/
|
|
501
635
|
export interface ConfigDataFlatpak extends AppMetadata {
|
|
502
636
|
/** Reverse-DNS app id, e.g. `eu.jumplink.Learn6502`. Defaults to `package.json#name` if it looks like a reverse-DNS id. */
|
|
503
637
|
appId?: string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default GNOME-Platform runtime version. Bumped per release window.
|
|
3
|
+
* GNOME 50 = April 2026 stable; tracked in
|
|
4
|
+
* https://docs.flathub.org/docs/for-app-authors/requirements.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_GNOME_RUNTIME_VERSION = "50";
|
|
7
|
+
/** Default Freedesktop-Platform runtime version (LTS-ish). */
|
|
8
|
+
export declare const DEFAULT_FREEDESKTOP_RUNTIME_VERSION = "24.08";
|
|
9
|
+
/** Permissive GUI defaults for GTK4 + Adwaita apps. */
|
|
10
|
+
export declare const DEFAULT_GUI_FINISH_ARGS: string[];
|
|
11
|
+
/** Lean defaults for headless CLI tools — no display, no GPU. */
|
|
12
|
+
export declare const DEFAULT_CLI_FINISH_ARGS: string[];
|
|
13
|
+
/**
|
|
14
|
+
* The runtime keys, structurally — not `ConfigDataFlatpak`.
|
|
15
|
+
*
|
|
16
|
+
* Two config blocks answer this question now (`gjsify.flatpak` and
|
|
17
|
+
* `gjsify.ship.flatpak`, § the deprecation window in `utils/ship/flatpak-config.ts`),
|
|
18
|
+
* and a resolver typed to one of them would have to be duplicated for the other.
|
|
19
|
+
*/
|
|
20
|
+
export interface RuntimeSelection {
|
|
21
|
+
runtime?: string;
|
|
22
|
+
runtimeVersion?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ResolvedRuntime {
|
|
25
|
+
runtime: 'gnome' | 'freedesktop';
|
|
26
|
+
runtimeId: string;
|
|
27
|
+
sdk: string;
|
|
28
|
+
runtimeVersion: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Pick the runtime + sdk + version triple from config + CLI overrides.
|
|
32
|
+
* `--runtime` and `--runtime-version` flags win over config values.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveRuntime(config: RuntimeSelection | undefined, overrides: RuntimeSelection): ResolvedRuntime;
|
|
35
|
+
/**
|
|
36
|
+
* `append-path` for the build sandbox, derived from the declared SDK extensions.
|
|
37
|
+
*
|
|
38
|
+
* `/app/bin` is appended last so a build command finds the app's own binaries
|
|
39
|
+
* without knowing where they landed.
|
|
40
|
+
*/
|
|
41
|
+
export declare function deriveAppendPath(sdkExtensions: readonly string[]): string[];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Which runtime a Flatpak is built against, and what the finished app may do.
|
|
2
|
+
//
|
|
3
|
+
// Lifted out of `commands/flatpak/` when `gjsify ship --target flatpak` became
|
|
4
|
+
// the second caller (ADR 0024 § 8). Same move, same reason, as
|
|
5
|
+
// `utils/app-metadata.ts`: these are facts about the APP, not about whichever
|
|
6
|
+
// command happens to render them, and the second copy is the one that drifts —
|
|
7
|
+
// a `DEFAULT_GNOME_RUNTIME_VERSION` bumped in one place and not the other
|
|
8
|
+
// produces a `ship` Flatpak and a `flatpak init` Flatpak targeting different
|
|
9
|
+
// GNOME releases from the same `package.json`.
|
|
10
|
+
/**
|
|
11
|
+
* Default GNOME-Platform runtime version. Bumped per release window.
|
|
12
|
+
* GNOME 50 = April 2026 stable; tracked in
|
|
13
|
+
* https://docs.flathub.org/docs/for-app-authors/requirements.
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_GNOME_RUNTIME_VERSION = '50';
|
|
16
|
+
/** Default Freedesktop-Platform runtime version (LTS-ish). */
|
|
17
|
+
export const DEFAULT_FREEDESKTOP_RUNTIME_VERSION = '24.08';
|
|
18
|
+
/** Permissive GUI defaults for GTK4 + Adwaita apps. */
|
|
19
|
+
export const DEFAULT_GUI_FINISH_ARGS = ['--device=dri', '--share=ipc', '--socket=fallback-x11', '--socket=wayland'];
|
|
20
|
+
/** Lean defaults for headless CLI tools — no display, no GPU. */
|
|
21
|
+
export const DEFAULT_CLI_FINISH_ARGS = [];
|
|
22
|
+
/**
|
|
23
|
+
* Pick the runtime + sdk + version triple from config + CLI overrides.
|
|
24
|
+
* `--runtime` and `--runtime-version` flags win over config values.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveRuntime(config, overrides) {
|
|
27
|
+
const runtime = (overrides.runtime ?? config?.runtime ?? 'gnome');
|
|
28
|
+
if (runtime !== 'gnome' && runtime !== 'freedesktop') {
|
|
29
|
+
throw new Error(`gjsify flatpak: unknown runtime "${runtime}" (expected "gnome" or "freedesktop")`);
|
|
30
|
+
}
|
|
31
|
+
const runtimeVersion = overrides.runtimeVersion ??
|
|
32
|
+
config?.runtimeVersion ??
|
|
33
|
+
(runtime === 'gnome' ? DEFAULT_GNOME_RUNTIME_VERSION : DEFAULT_FREEDESKTOP_RUNTIME_VERSION);
|
|
34
|
+
if (runtime === 'gnome') {
|
|
35
|
+
return { runtime, runtimeId: 'org.gnome.Platform', sdk: 'org.gnome.Sdk', runtimeVersion };
|
|
36
|
+
}
|
|
37
|
+
return { runtime, runtimeId: 'org.freedesktop.Platform', sdk: 'org.freedesktop.Sdk', runtimeVersion };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* `append-path` for the build sandbox, derived from the declared SDK extensions.
|
|
41
|
+
*
|
|
42
|
+
* `/app/bin` is appended last so a build command finds the app's own binaries
|
|
43
|
+
* without knowing where they landed.
|
|
44
|
+
*/
|
|
45
|
+
export function deriveAppendPath(sdkExtensions) {
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const ext of sdkExtensions) {
|
|
48
|
+
const m = /^org\.freedesktop\.Sdk\.Extension\.([A-Za-z0-9-]+)$/.exec(ext);
|
|
49
|
+
if (m)
|
|
50
|
+
out.push(`/usr/lib/sdk/${m[1]}/bin`);
|
|
51
|
+
}
|
|
52
|
+
out.push('/app/bin');
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Which of msgfmt's two template writers to run. */
|
|
2
|
+
export type MsgfmtMergeMode = '--desktop' | '--xml';
|
|
3
|
+
/** One catalogue: the language tag msgfmt is TOLD, and the `.po` holding it. */
|
|
4
|
+
export interface MsgfmtCatalogue {
|
|
5
|
+
/** Passed as `--locale=`. Named explicitly so no `LINGUAS` file is needed — constraint 2. */
|
|
6
|
+
locale: string;
|
|
7
|
+
/** Path to a `.po`. msgfmt reads only this format here: a `.mo` gives `syntax error`, exit 1. */
|
|
8
|
+
po: string;
|
|
9
|
+
}
|
|
10
|
+
export interface MsgfmtMergeOptions {
|
|
11
|
+
mode: MsgfmtMergeMode;
|
|
12
|
+
/** The file the FIRST call substitutes into. Every later call substitutes into the previous output. */
|
|
13
|
+
template: string;
|
|
14
|
+
/** The suffix every intermediate keeps — load-bearing for `--xml`, see constraint 4. */
|
|
15
|
+
extension: string;
|
|
16
|
+
catalogues: readonly MsgfmtCatalogue[];
|
|
17
|
+
/** A directory this function may fill with intermediates; the caller owns its removal. */
|
|
18
|
+
workDir: string;
|
|
19
|
+
/** Called with each msgfmt argv, for `--verbose`. */
|
|
20
|
+
onCall?: (args: readonly string[]) => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Fold every catalogue into one file, chaining `msgfmt` once per language.
|
|
24
|
+
*
|
|
25
|
+
* Returns the PATH of the last file written — the caller decides whether to read
|
|
26
|
+
* it, post-process it or copy it. With no catalogues that is `template` itself,
|
|
27
|
+
* so a project with nothing to merge never spawns msgfmt at all.
|
|
28
|
+
*
|
|
29
|
+
* `stdio: 'pipe'` so a failure's stderr survives on the thrown error rather than
|
|
30
|
+
* going to the terminal and leaving the caller with an exit code.
|
|
31
|
+
*
|
|
32
|
+
* Throws when two catalogues name the same locale — constraint 5, which msgfmt
|
|
33
|
+
* itself reports as two successes and one invalid file.
|
|
34
|
+
*/
|
|
35
|
+
export declare function mergeCatalogues(options: MsgfmtMergeOptions): string;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Merging gettext catalogues INTO a template — the `msgfmt --desktop` / `--xml` half.
|
|
2
|
+
//
|
|
3
|
+
// Two callers need this and they are not variants of each other: `gjsify gettext`
|
|
4
|
+
// substitutes a template a user wrote, `gjsify ship` substitutes metadata it just
|
|
5
|
+
// rendered. What they share is every constraint below, all five measured against
|
|
6
|
+
// GNU gettext 0.26, and each one fails a DIFFERENT way — two of them silently.
|
|
7
|
+
// Written twice, the silent ones are the copies that lose them.
|
|
8
|
+
//
|
|
9
|
+
// 1. `--desktop` and `--xml` REQUIRE `--template`:
|
|
10
|
+
// `msgfmt: --desktop requires a "--template template" specification`, exit 1.
|
|
11
|
+
// Neither format can be produced from `.po` files alone, so a caller without a
|
|
12
|
+
// template has nothing to ask for.
|
|
13
|
+
//
|
|
14
|
+
// 2. The bulk form (`-d <podir>`) fails SILENTLY. With no `LINGUAS` file beside
|
|
15
|
+
// the `.po` files:
|
|
16
|
+
// msgfmt --desktop --template=app.desktop.in -d po -o out.desktop
|
|
17
|
+
// exit 0 · stderr `po/LINGUAS does not exist` · out.desktop UNTRANSLATED
|
|
18
|
+
// and `desktop-file-validate out.desktop` then exits 0 as well. Exit 0, an
|
|
19
|
+
// English file and a passing oracle is the worst of the three outcomes, which
|
|
20
|
+
// is why this module names each language with `--locale=` instead: that form
|
|
21
|
+
// needs no `LINGUAS` and cannot degrade to a no-op without a non-zero exit.
|
|
22
|
+
//
|
|
23
|
+
// 3. The template and the output must never be the SAME path. msgfmt truncates
|
|
24
|
+
// `--output-file` before it reads the template, so chaining in place destroys
|
|
25
|
+
// it: measured, `--desktop` wrote a 0-byte file and exited 0, `--xml` printed
|
|
26
|
+
// `cannot read <file>: Document is empty` and died on SIGSEGV (exit 139).
|
|
27
|
+
// Hence the numbered intermediates below rather than one file rewritten.
|
|
28
|
+
//
|
|
29
|
+
// 4. `--xml` finds its ITS rules by FILENAME PATTERN, not by reading the document.
|
|
30
|
+
// gettext walks `/usr/share/gettext/its/*.loc`, where each rule pairs a glob
|
|
31
|
+
// with a root element; AppStream's is `pattern="*.metainfo.xml"` +
|
|
32
|
+
// `localName="component"`, so the same bytes named `foo.xml` die with
|
|
33
|
+
// `msgfmt: cannot locate ITS rules for foo.xml` (exit 1). `extension` is
|
|
34
|
+
// therefore part of this function's contract, not a temp-file detail.
|
|
35
|
+
//
|
|
36
|
+
// Stated as "`--xml` needs a `.metainfo.xml` name" the rule would be wrong in
|
|
37
|
+
// the direction that matters next: `shared-mime-info.loc` pairs `pattern="*.xml"`
|
|
38
|
+
// with `localName="mime-info"`, so a MIME package needs no suffix care at all.
|
|
39
|
+
// The constraint belongs to the AppStream rule, not to `--xml`.
|
|
40
|
+
//
|
|
41
|
+
// 5. A locale may appear at most ONCE in a chain. Two calls naming the same
|
|
42
|
+
// `--locale` both APPEND — neither replaces the other's key — and the result
|
|
43
|
+
// is a file BOTH oracles reject, from two exit-0 msgfmt calls:
|
|
44
|
+
// msgfmt --desktop --template=app.desktop.in --locale=de -o d0 a.po # 0
|
|
45
|
+
// msgfmt --desktop --template=d0 --locale=de -o d1 b.po # 0
|
|
46
|
+
// desktop-file-validate d1
|
|
47
|
+
// → error: file contains multiple keys named "Name[de]" # 1
|
|
48
|
+
// appstreamcli validate --no-net x1.metainfo.xml (same shape, --xml)
|
|
49
|
+
// → E: tag-duplicated name (lang=de) · Validation failed # 3
|
|
50
|
+
// So this is worse than constraint 2: not an untranslated file that installs
|
|
51
|
+
// fine, but an INVALID one, produced without a single non-zero exit. It is
|
|
52
|
+
// refused here rather than deduplicated here, because the caller holding two
|
|
53
|
+
// catalogues for one language is the only party that can say which
|
|
54
|
+
// translation wins — `utils/ship/localize-metadata.ts` answers it with
|
|
55
|
+
// `msgcat --use-first`.
|
|
56
|
+
import { execFileSync } from 'node:child_process';
|
|
57
|
+
import { join } from 'node:path';
|
|
58
|
+
/**
|
|
59
|
+
* Fold every catalogue into one file, chaining `msgfmt` once per language.
|
|
60
|
+
*
|
|
61
|
+
* Returns the PATH of the last file written — the caller decides whether to read
|
|
62
|
+
* it, post-process it or copy it. With no catalogues that is `template` itself,
|
|
63
|
+
* so a project with nothing to merge never spawns msgfmt at all.
|
|
64
|
+
*
|
|
65
|
+
* `stdio: 'pipe'` so a failure's stderr survives on the thrown error rather than
|
|
66
|
+
* going to the terminal and leaving the caller with an exit code.
|
|
67
|
+
*
|
|
68
|
+
* Throws when two catalogues name the same locale — constraint 5, which msgfmt
|
|
69
|
+
* itself reports as two successes and one invalid file.
|
|
70
|
+
*/
|
|
71
|
+
export function mergeCatalogues(options) {
|
|
72
|
+
let current = options.template;
|
|
73
|
+
// BEFORE the first spawn, so a caller that got this wrong is told rather than
|
|
74
|
+
// handed a half-written chain to clean up. `seen` is a Set of the locale, not
|
|
75
|
+
// of the catalogue: the same language reached through two `.po` files is
|
|
76
|
+
// exactly the case that produces the duplicate key.
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
for (const { locale } of options.catalogues) {
|
|
79
|
+
if (seen.has(locale)) {
|
|
80
|
+
throw new Error(`mergeCatalogues: two catalogues both claim locale "${locale}". msgfmt APPENDS each ` +
|
|
81
|
+
`--locale, so the chain would emit a duplicate key (desktop-file-validate: ` +
|
|
82
|
+
`"multiple keys named Name[${locale}]"; appstreamcli: "tag-duplicated") from two ` +
|
|
83
|
+
'exit-0 calls. Merge them into one .po first (msgcat --use-first) and pass that.');
|
|
84
|
+
}
|
|
85
|
+
seen.add(locale);
|
|
86
|
+
}
|
|
87
|
+
for (const [index, catalogue] of options.catalogues.entries()) {
|
|
88
|
+
const next = join(options.workDir, `${index}${options.extension}`);
|
|
89
|
+
const args = [
|
|
90
|
+
options.mode,
|
|
91
|
+
`--template=${current}`,
|
|
92
|
+
`--locale=${catalogue.locale}`,
|
|
93
|
+
`--output-file=${next}`,
|
|
94
|
+
catalogue.po,
|
|
95
|
+
];
|
|
96
|
+
options.onCall?.(args);
|
|
97
|
+
execFileSync('msgfmt', args, { stdio: 'pipe' });
|
|
98
|
+
// Every language after the first merges INTO what the previous one wrote.
|
|
99
|
+
current = next;
|
|
100
|
+
}
|
|
101
|
+
return current;
|
|
102
|
+
}
|
package/lib/utils/npm-otp.d.ts
CHANGED
|
@@ -31,6 +31,23 @@ export declare class OtpProvider {
|
|
|
31
31
|
private cached;
|
|
32
32
|
private readonly promptFn;
|
|
33
33
|
private readonly registry;
|
|
34
|
+
/**
|
|
35
|
+
* Bumped every time a NEW code becomes current. Concurrent callers compare the
|
|
36
|
+
* epoch they last saw against this one to tell "my code was rejected" from
|
|
37
|
+
* "somebody already replaced it while I was waiting" — the second needs no
|
|
38
|
+
* prompt, it needs the other one's code.
|
|
39
|
+
*/
|
|
40
|
+
private generation;
|
|
41
|
+
/**
|
|
42
|
+
* The prompt currently on screen, if any. THE fix for a sweep that probes with
|
|
43
|
+
* concurrency > 1: `promptLine` puts the terminal in RAW mode and echoes each
|
|
44
|
+
* keystroke by hand (utils/prompt.ts), so two live prompts mean two `data`
|
|
45
|
+
* listeners on the same stdin and every digit is echoed TWICE. Measured on a
|
|
46
|
+
* real 703-package sweep at the default concurrency of 4: five prompts stacked
|
|
47
|
+
* up and the typed code came back as `666644440000999944444444` — four echoes
|
|
48
|
+
* per keystroke. Whoever arrives second awaits this promise instead.
|
|
49
|
+
*/
|
|
50
|
+
private pending;
|
|
34
51
|
/**
|
|
35
52
|
* @param seed Initial cached code (from `--otp`), or undefined.
|
|
36
53
|
* @param promptFn How to read a fresh code. Defaults to the TTY-aware
|
|
@@ -41,17 +58,25 @@ export declare class OtpProvider {
|
|
|
41
58
|
constructor(seed?: string, promptFn?: OtpPromptFn, options?: OtpProviderOptions);
|
|
42
59
|
/** The currently cached OTP code, if any. */
|
|
43
60
|
current(): string | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* How many times a new code has become current. Capture it before an attempt
|
|
63
|
+
* and hand it back to {@link refresh} / {@link invalidate} so a concurrent
|
|
64
|
+
* caller neither prompts for a code somebody already typed nor throws that
|
|
65
|
+
* code away on its own stale rejection.
|
|
66
|
+
*/
|
|
67
|
+
epoch(): number;
|
|
44
68
|
/**
|
|
45
69
|
* Prompt for a fresh code, cache it (in-process + file cache), and return the
|
|
46
70
|
* trimmed input — `''` when nothing was entered, which callers read as "give up".
|
|
47
71
|
*/
|
|
48
|
-
refresh(question?: string): Promise<string>;
|
|
72
|
+
refresh(question?: string, sinceEpoch?: number): Promise<string>;
|
|
73
|
+
private promptOnce;
|
|
49
74
|
/**
|
|
50
75
|
* Drop the cached code (in-process AND the cross-invocation file entry) after npm
|
|
51
76
|
* rejects it, so the NEXT command re-prompts instead of replaying a single-use
|
|
52
77
|
* code the registry already burned.
|
|
53
78
|
*/
|
|
54
|
-
invalidate(): void;
|
|
79
|
+
invalidate(sinceEpoch?: number): void;
|
|
55
80
|
}
|
|
56
81
|
export interface OtpRetryOptions {
|
|
57
82
|
/**
|