@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/actions/build.js
CHANGED
|
@@ -630,7 +630,7 @@ export class BuildAction {
|
|
|
630
630
|
throw new Error('gjsify build: an .html entry needs an explicit --outfile ' +
|
|
631
631
|
'(e.g. --outfile dist-app/app.js); the emitted index.html is written beside it.');
|
|
632
632
|
}
|
|
633
|
-
const { consoleShim, globals } = this.configData;
|
|
633
|
+
const { consoleShim, globals, dialect } = this.configData;
|
|
634
634
|
const userExternal = Array.isArray(userBundler.external) ? userBundler.external : undefined;
|
|
635
635
|
const userBanner = typeof userBundler.output?.banner === 'string' ? userBundler.output.banner : undefined;
|
|
636
636
|
const pluginOpts = {
|
|
@@ -640,6 +640,7 @@ export class BuildAction {
|
|
|
640
640
|
exclude,
|
|
641
641
|
reflection: typescript?.reflection,
|
|
642
642
|
consoleShim,
|
|
643
|
+
...(dialect !== undefined ? { dialect } : {}),
|
|
643
644
|
...(aliases ? { aliases } : {}),
|
|
644
645
|
...(opts.preserveDefaultExport ? { preserveDefaultExport: true } : {}),
|
|
645
646
|
...(opts.toolchainAnchor !== undefined ? { toolchainAnchor: opts.toolchainAnchor } : {}),
|
|
@@ -85,11 +85,13 @@ const IGNORE = [
|
|
|
85
85
|
/^docs\//,
|
|
86
86
|
...OTHER_WORKFLOW_INPUTS,
|
|
87
87
|
// Neither `@gjsify/node-gi` (the Node-native GI engine) nor `@gjsify/napi` (the
|
|
88
|
-
// N-API host over SpiderMonkey) is a gjsify workspace member
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
88
|
+
// N-API host over SpiderMonkey) is a gjsify workspace member — WHY is ADR 0031,
|
|
89
|
+
// which this comment used to carry alone. The short of it: node-gi's `build` is
|
|
90
|
+
// `node-gyp rebuild`, so a member would enter `foreach build` in a container with
|
|
91
|
+
// no GTK/GI toolchain. `main.yml` therefore neither builds nor tests them and
|
|
92
|
+
// their own workflows are the source of truth. Without the carve-outs their files
|
|
93
|
+
// map to no workspace, land in `unmatched`, and force a full run on every
|
|
94
|
+
// node-gi/napi PR.
|
|
93
95
|
/^packages\/node-gi\//,
|
|
94
96
|
/^packages\/napi\//,
|
|
95
97
|
// Flatpak build/distribution tooling (SDK-extension manifest + metainfo). No
|
package/lib/commands/build.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Config } from '../config.js';
|
|
2
2
|
import { BuildAction } from '../actions/build.js';
|
|
3
|
+
import { SOURCE_DIALECTS } from '@gjsify/rolldown-plugin-gjsify';
|
|
3
4
|
// NO `normalize: true` ON THE GLOB-BEARING OPTIONS — `entry-points` and
|
|
4
5
|
// `exclude` are PATTERNS, not paths.
|
|
5
6
|
//
|
|
@@ -106,6 +107,11 @@ export const buildCommand = {
|
|
|
106
107
|
type: 'string',
|
|
107
108
|
choices: ['silent', 'error', 'warning', 'info', 'debug', 'verbose'],
|
|
108
109
|
default: 'warning',
|
|
110
|
+
})
|
|
111
|
+
.option('dialect', {
|
|
112
|
+
description: "The dialect the SOURCE is written in. 'react-native' aliases 'react-native' onto '@gjsify/react-native' and fails the build on an import whose ADR 0032 support-table status is not supported/partial. GJS and Node app builds. This is NOT gjsify.runtimes['react-native'], which declares the runtime a package RUNS on — the two answer opposite questions.",
|
|
113
|
+
type: 'string',
|
|
114
|
+
choices: [...SOURCE_DIALECTS],
|
|
109
115
|
})
|
|
110
116
|
.option('console-shim', {
|
|
111
117
|
description: 'Inject a console shim into GJS builds for clean output without the GLib prefix and with working ANSI colors. Use --no-console-shim to disable. Only applies to GJS app builds.',
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
10
|
import { dirname, resolve } from 'node:path';
|
|
11
11
|
import { Config } from '../../config.js';
|
|
12
|
-
import { defaultCiContainer, looksLikeAppId, readPackageJson
|
|
12
|
+
import { defaultCiContainer, looksLikeAppId, readPackageJson } from './utils.js';
|
|
13
|
+
import { resolveRuntime } from '../../utils/flatpak-runtime.js';
|
|
14
|
+
import { pickFlatpakBuildKeys } from '../../utils/ship/flatpak-config.js';
|
|
13
15
|
export const flatpakCiCommand = {
|
|
14
16
|
command: 'ci',
|
|
15
17
|
description: 'Scaffold .github/workflows/flatpak.yml using the flathub-infra container + flatpak-builder@v6 action.',
|
|
@@ -68,7 +70,13 @@ export const flatpakCiCommand = {
|
|
|
68
70
|
}
|
|
69
71
|
const manifest = args.manifest ?? `${appId}.json`;
|
|
70
72
|
const bundle = args.bundle ?? `${appId}.flatpak`;
|
|
71
|
-
|
|
73
|
+
// Through the deprecation window, not straight off `gjsify.flatpak`: a
|
|
74
|
+
// project told to move these keys to `gjsify.ship.flatpak` must not lose
|
|
75
|
+
// them here — the CI container tag is derived from the runtime, so
|
|
76
|
+
// reading the old block alone scaffolds a workflow against the default
|
|
77
|
+
// GNOME release rather than the pinned one.
|
|
78
|
+
const buildKeys = pickFlatpakBuildKeys(configData.ship?.flatpak, flatpak).values;
|
|
79
|
+
const { runtime, runtimeVersion } = resolveRuntime(buildKeys, {});
|
|
72
80
|
const runtimeImage = args.runtimeImage ??
|
|
73
81
|
flatpak.ciContainer ??
|
|
74
82
|
defaultCiContainer(runtime, runtimeVersion);
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
// with `skip-icons-check`.
|
|
14
14
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
15
15
|
import { dirname, resolve } from 'node:path';
|
|
16
|
-
import {
|
|
16
|
+
import { looksLikeAppId, readPackageJson } from './utils.js';
|
|
17
|
+
import { DEFAULT_CLI_FINISH_ARGS, DEFAULT_GUI_FINISH_ARGS, deriveAppendPath, resolveRuntime, } from '../../utils/flatpak-runtime.js';
|
|
18
|
+
import { pickFlatpakBuildKeys } from '../../utils/ship/flatpak-config.js';
|
|
17
19
|
import { renderDesktop, renderFlathubJson, renderMetainfoApp, renderMetainfoCli, validateScaffoldInputs, } from './scaffold.js';
|
|
18
20
|
import { Config } from '../../config.js';
|
|
19
21
|
import { OxcNotFoundError, hasOxcDevDep, runOxfmt } from '../../utils/oxc-resolve.js';
|
|
@@ -109,17 +111,24 @@ export const flatpakInitCommand = {
|
|
|
109
111
|
'or rename the package to a reverse-DNS id like org.example.MyApp.');
|
|
110
112
|
}
|
|
111
113
|
const kind = args.kind ?? flatpak.kind ?? (args.cliOnly ? 'cli' : 'app');
|
|
112
|
-
|
|
114
|
+
// The six BUILD keys come through the deprecation window
|
|
115
|
+
// (`utils/ship/flatpak-config.ts`), not straight off `gjsify.flatpak`.
|
|
116
|
+
// These commands did NOT move, but the keys did: a project that followed
|
|
117
|
+
// `ship`'s own advice and moved them would otherwise lose them here and
|
|
118
|
+
// get a committed manifest against a different runtime version with
|
|
119
|
+
// different finish-args, silently, at exit 0.
|
|
120
|
+
const buildKeys = pickFlatpakBuildKeys(configData.ship?.flatpak, flatpak).values;
|
|
121
|
+
const { runtime, runtimeId, sdk, runtimeVersion } = resolveRuntime(buildKeys, {
|
|
113
122
|
runtime: args.runtime,
|
|
114
123
|
runtimeVersion: args.runtimeVersion,
|
|
115
124
|
});
|
|
116
|
-
const sdkExtensions = mergeArrays(
|
|
117
|
-
const appendPath =
|
|
125
|
+
const sdkExtensions = mergeArrays(buildKeys.sdkExtensions, args.sdkExtension);
|
|
126
|
+
const appendPath = buildKeys.appendPath ?? (sdkExtensions?.length ? deriveAppendPath(sdkExtensions) : undefined);
|
|
118
127
|
const command = args.command ?? flatpak.command ?? appId;
|
|
119
128
|
const explicitFinishArgs = args.finishArg;
|
|
120
129
|
const finishArgs = explicitFinishArgs !== undefined
|
|
121
130
|
? explicitFinishArgs
|
|
122
|
-
: (
|
|
131
|
+
: (buildKeys.finishArgs ?? (kind === 'cli' ? DEFAULT_CLI_FINISH_ARGS : DEFAULT_GUI_FINISH_ARGS));
|
|
123
132
|
const manifest = {
|
|
124
133
|
id: appId,
|
|
125
134
|
runtime: runtimeId,
|
|
@@ -133,7 +142,7 @@ export const flatpakInitCommand = {
|
|
|
133
142
|
}
|
|
134
143
|
manifest.command = command;
|
|
135
144
|
manifest['finish-args'] = finishArgs;
|
|
136
|
-
const cleanup =
|
|
145
|
+
const cleanup = buildKeys.cleanup;
|
|
137
146
|
if (cleanup?.length)
|
|
138
147
|
manifest.cleanup = cleanup;
|
|
139
148
|
// Module assembly. Two precedence rules:
|
|
@@ -253,16 +262,6 @@ function mergeArrays(a, b) {
|
|
|
253
262
|
return undefined;
|
|
254
263
|
return [...(a ?? []), ...(b ?? [])];
|
|
255
264
|
}
|
|
256
|
-
function deriveAppendPath(sdkExtensions) {
|
|
257
|
-
const out = [];
|
|
258
|
-
for (const ext of sdkExtensions) {
|
|
259
|
-
const m = /^org\.freedesktop\.Sdk\.Extension\.([A-Za-z0-9-]+)$/.exec(ext);
|
|
260
|
-
if (m)
|
|
261
|
-
out.push(`/usr/lib/sdk/${m[1]}/bin`);
|
|
262
|
-
}
|
|
263
|
-
out.push('/app/bin');
|
|
264
|
-
return out;
|
|
265
|
-
}
|
|
266
265
|
function deriveModuleName(appId) {
|
|
267
266
|
const parts = appId.split('.');
|
|
268
267
|
return parts[parts.length - 1] || appId;
|
|
@@ -1,32 +1,6 @@
|
|
|
1
|
-
import type { ConfigDataFlatpak } from '../../types/config-data.js';
|
|
2
|
-
/**
|
|
3
|
-
* Default GNOME-Platform runtime version. Bumped per release window.
|
|
4
|
-
* GNOME 50 = April 2026 stable; tracked in
|
|
5
|
-
* https://docs.flathub.org/docs/for-app-authors/requirements.
|
|
6
|
-
*/
|
|
7
|
-
export declare const DEFAULT_GNOME_RUNTIME_VERSION = "50";
|
|
8
|
-
/** Default Freedesktop-Platform runtime version (LTS-ish). */
|
|
9
|
-
export declare const DEFAULT_FREEDESKTOP_RUNTIME_VERSION = "24.08";
|
|
10
|
-
/** Permissive GUI defaults for GTK4 + Adwaita apps. */
|
|
11
|
-
export declare const DEFAULT_GUI_FINISH_ARGS: string[];
|
|
12
|
-
/** Lean defaults for headless CLI tools — no display, no GPU. */
|
|
13
|
-
export declare const DEFAULT_CLI_FINISH_ARGS: string[];
|
|
14
1
|
/** Read package.json from a directory. Throws a helpful error if missing/invalid. */
|
|
15
2
|
export declare function readPackageJson(dir: string): Record<string, unknown>;
|
|
16
3
|
/** True if a name string looks like a reverse-DNS Flatpak app id. */
|
|
17
4
|
export declare function looksLikeAppId(value: unknown): value is string;
|
|
18
|
-
/**
|
|
19
|
-
* Pick the runtime + sdk + version triple from config + CLI overrides.
|
|
20
|
-
* `--runtime` and `--runtime-version` flags win over config values.
|
|
21
|
-
*/
|
|
22
|
-
export declare function resolveRuntime(flatpak: ConfigDataFlatpak | undefined, overrides: {
|
|
23
|
-
runtime?: string;
|
|
24
|
-
runtimeVersion?: string;
|
|
25
|
-
}): {
|
|
26
|
-
runtime: 'gnome' | 'freedesktop';
|
|
27
|
-
runtimeId: string;
|
|
28
|
-
sdk: string;
|
|
29
|
-
runtimeVersion: string;
|
|
30
|
-
};
|
|
31
5
|
/** Default container image for the GitHub Actions workflow. */
|
|
32
6
|
export declare function defaultCiContainer(runtime: 'gnome' | 'freedesktop', runtimeVersion: string): string;
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
// Shared helpers for the `gjsify flatpak <sub>` subcommand group.
|
|
2
|
+
//
|
|
3
|
+
// The runtime/SDK/finish-args half moved to `utils/flatpak-runtime.ts` when
|
|
4
|
+
// `gjsify ship --target flatpak` became its second caller — see that file's
|
|
5
|
+
// header. What is left here is what only these subcommands ask.
|
|
2
6
|
import { readFileSync } from 'node:fs';
|
|
3
7
|
import { resolve } from 'node:path';
|
|
4
|
-
/**
|
|
5
|
-
* Default GNOME-Platform runtime version. Bumped per release window.
|
|
6
|
-
* GNOME 50 = April 2026 stable; tracked in
|
|
7
|
-
* https://docs.flathub.org/docs/for-app-authors/requirements.
|
|
8
|
-
*/
|
|
9
|
-
export const DEFAULT_GNOME_RUNTIME_VERSION = '50';
|
|
10
|
-
/** Default Freedesktop-Platform runtime version (LTS-ish). */
|
|
11
|
-
export const DEFAULT_FREEDESKTOP_RUNTIME_VERSION = '24.08';
|
|
12
|
-
/** Permissive GUI defaults for GTK4 + Adwaita apps. */
|
|
13
|
-
export const DEFAULT_GUI_FINISH_ARGS = ['--device=dri', '--share=ipc', '--socket=fallback-x11', '--socket=wayland'];
|
|
14
|
-
/** Lean defaults for headless CLI tools — no display, no GPU. */
|
|
15
|
-
export const DEFAULT_CLI_FINISH_ARGS = [];
|
|
16
8
|
/** Read package.json from a directory. Throws a helpful error if missing/invalid. */
|
|
17
9
|
export function readPackageJson(dir) {
|
|
18
10
|
const path = resolve(dir, 'package.json');
|
|
@@ -34,23 +26,6 @@ export function readPackageJson(dir) {
|
|
|
34
26
|
export function looksLikeAppId(value) {
|
|
35
27
|
return typeof value === 'string' && /^[A-Za-z][A-Za-z0-9_-]*(\.[A-Za-z][A-Za-z0-9_-]*){2,}$/.test(value);
|
|
36
28
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Pick the runtime + sdk + version triple from config + CLI overrides.
|
|
39
|
-
* `--runtime` and `--runtime-version` flags win over config values.
|
|
40
|
-
*/
|
|
41
|
-
export function resolveRuntime(flatpak, overrides) {
|
|
42
|
-
const runtime = (overrides.runtime ?? flatpak?.runtime ?? 'gnome');
|
|
43
|
-
if (runtime !== 'gnome' && runtime !== 'freedesktop') {
|
|
44
|
-
throw new Error(`gjsify flatpak: unknown runtime "${runtime}" (expected "gnome" or "freedesktop")`);
|
|
45
|
-
}
|
|
46
|
-
const runtimeVersion = overrides.runtimeVersion ??
|
|
47
|
-
flatpak?.runtimeVersion ??
|
|
48
|
-
(runtime === 'gnome' ? DEFAULT_GNOME_RUNTIME_VERSION : DEFAULT_FREEDESKTOP_RUNTIME_VERSION);
|
|
49
|
-
if (runtime === 'gnome') {
|
|
50
|
-
return { runtime, runtimeId: 'org.gnome.Platform', sdk: 'org.gnome.Sdk', runtimeVersion };
|
|
51
|
-
}
|
|
52
|
-
return { runtime, runtimeId: 'org.freedesktop.Platform', sdk: 'org.freedesktop.Sdk', runtimeVersion };
|
|
53
|
-
}
|
|
54
29
|
/** Default container image for the GitHub Actions workflow. */
|
|
55
30
|
export function defaultCiContainer(runtime, runtimeVersion) {
|
|
56
31
|
const tag = `${runtime}-${runtimeVersion}`;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { Command } from '../types/index.js';
|
|
2
|
-
type GettextFormat = 'mo' | 'xml' | 'desktop'
|
|
2
|
+
type GettextFormat = 'mo' | 'xml' | 'desktop';
|
|
3
3
|
interface GettextOptions {
|
|
4
4
|
poDir: string;
|
|
5
5
|
outDir: string;
|
|
6
6
|
domain: string;
|
|
7
7
|
format?: GettextFormat;
|
|
8
|
+
template?: string;
|
|
8
9
|
metainfo?: string;
|
|
9
10
|
filename?: string;
|
|
10
11
|
removeXmlComments?: boolean;
|
package/lib/commands/gettext.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
-
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
3
4
|
import { basename, join, resolve } from 'node:path';
|
|
4
5
|
import { promisify } from 'node:util';
|
|
6
|
+
import { mergeCatalogues } from '../utils/msgfmt-merge.js';
|
|
5
7
|
const execFileAsync = promisify(execFile);
|
|
6
8
|
async function listLanguages(poDir) {
|
|
7
9
|
const entries = await readdir(poDir);
|
|
@@ -25,40 +27,79 @@ async function fileExists(path) {
|
|
|
25
27
|
return false;
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Fold every catalogue into ONE template.
|
|
32
|
+
*
|
|
33
|
+
* The chain, and the four measured msgfmt constraints that shape it, live in
|
|
34
|
+
* `utils/msgfmt-merge.ts` — `gjsify ship` folds the catalogues it stages into the
|
|
35
|
+
* metadata it generates through the same function, and a second copy of those
|
|
36
|
+
* constraints here would be the copy that drifts.
|
|
37
|
+
*
|
|
38
|
+
* What is local to this command: the output keeps the CALLER's `--filename`,
|
|
39
|
+
* while every INTERMEDIATE is named after the TEMPLATE. Those have to be two
|
|
40
|
+
* different names, because msgfmt finds its ITS rules by filename pattern
|
|
41
|
+
* (constraint 4 over there) and from the second catalogue on the intermediates
|
|
42
|
+
* ARE templates. Named after `--filename` instead, the command's answer depended
|
|
43
|
+
* on how many catalogues there were: measured, `--format xml --template
|
|
44
|
+
* app.metainfo.xml.in --filename out.xml` wrote a translated file for a one-language
|
|
45
|
+
* `po/` and died on a two-language one with `cannot locate ITS rules for
|
|
46
|
+
* <tmpdir>/0-out.xml` — naming an internal path over a rule the user could act on.
|
|
47
|
+
* The template's suffix is what msgfmt validated for call 1, so it is the one
|
|
48
|
+
* suffix known to work for calls 2..n.
|
|
49
|
+
*/
|
|
50
|
+
async function compileMerged(opts) {
|
|
51
|
+
const languages = await listLanguages(opts.poDir);
|
|
52
|
+
if (languages.length === 0) {
|
|
53
|
+
console.warn(`[gjsify gettext] no .po files found in ${opts.poDir}`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
29
56
|
const outputFile = join(opts.outDir, opts.filename);
|
|
30
57
|
await ensureDir(opts.outDir);
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
58
|
+
const workDir = await mkdtemp(join(tmpdir(), 'gjsify-gettext-'));
|
|
59
|
+
try {
|
|
60
|
+
const merged = await readFile(mergeCatalogues({
|
|
61
|
+
mode: `--${opts.format}`,
|
|
62
|
+
template: opts.template,
|
|
63
|
+
// The TEMPLATE's name, minus the `.in` convention — see the header.
|
|
64
|
+
// `basename`, so an intermediate stays inside `workDir` rather than
|
|
65
|
+
// following the directory part of the path the user passed.
|
|
66
|
+
extension: `-${basename(opts.template).replace(/\.in$/, '')}`,
|
|
67
|
+
catalogues: languages.map((lang) => ({ locale: lang, po: join(opts.poDir, `${lang}.po`) })),
|
|
68
|
+
workDir,
|
|
69
|
+
onCall: opts.verbose ? (args) => console.log(`[gjsify gettext] msgfmt ${args.join(' ')}`) : undefined,
|
|
70
|
+
}), 'utf-8');
|
|
71
|
+
await writeFile(outputFile, opts.removeXmlComments && opts.format === 'xml' ? stripXmlComments(merged) : merged);
|
|
34
72
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const content = await readFile(outputFile, 'utf-8');
|
|
38
|
-
await writeFile(outputFile, stripXmlComments(content));
|
|
73
|
+
finally {
|
|
74
|
+
await rm(workDir, { recursive: true, force: true });
|
|
39
75
|
}
|
|
40
76
|
if (opts.verbose) {
|
|
41
|
-
console.log(`[gjsify gettext]
|
|
77
|
+
console.log(`[gjsify gettext] merged ${languages.length} language(s) into ${outputFile}`);
|
|
42
78
|
}
|
|
43
79
|
}
|
|
44
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Compile each `<lang>.po` into `<outDir>/<lang>/LC_MESSAGES/<filename>`.
|
|
82
|
+
*
|
|
83
|
+
* `mo` only. The other two formats SUBSTITUTE a template rather than producing a
|
|
84
|
+
* catalogue, and there is no per-language file for them to write — which is the
|
|
85
|
+
* defect this signature now makes unrepresentable: the old shared loop passed
|
|
86
|
+
* `--desktop`/`--xml` with no `--template`, and msgfmt refuses that outright
|
|
87
|
+
* (`--desktop requires a "--template template" specification`, exit 1). Every
|
|
88
|
+
* `gjsify gettext --format=desktop` invocation had therefore always failed; the
|
|
89
|
+
* only e2e coverage passes `--format mo`, so nothing ever ran the other branch.
|
|
90
|
+
*/
|
|
91
|
+
async function compileCatalogues(opts) {
|
|
45
92
|
const languages = await listLanguages(opts.poDir);
|
|
46
93
|
if (languages.length === 0) {
|
|
47
94
|
console.warn(`[gjsify gettext] no .po files found in ${opts.poDir}`);
|
|
48
95
|
return;
|
|
49
96
|
}
|
|
50
97
|
for (const lang of languages) {
|
|
51
|
-
const
|
|
52
|
-
const langDir = opts.format === 'mo' ? join(opts.outDir, lang, 'LC_MESSAGES') : join(opts.outDir, lang);
|
|
98
|
+
const langDir = join(opts.outDir, lang, 'LC_MESSAGES');
|
|
53
99
|
await ensureDir(langDir);
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
const args = [`--output-file=${outputFile}`];
|
|
58
|
-
if (opts.format !== 'mo') {
|
|
59
|
-
args.push(`--${opts.format}`);
|
|
60
|
-
}
|
|
61
|
-
args.push(poFile);
|
|
100
|
+
// msgfmt produces the binary .mo format by default — there is no `--mo`
|
|
101
|
+
// flag (only --xml, --desktop, --properties-output, ...).
|
|
102
|
+
const args = [`--output-file=${join(langDir, opts.filename)}`, join(opts.poDir, `${lang}.po`)];
|
|
62
103
|
if (opts.verbose) {
|
|
63
104
|
console.log(`[gjsify gettext] msgfmt ${args.join(' ')}`);
|
|
64
105
|
}
|
|
@@ -68,24 +109,22 @@ async function compilePerLanguage(opts) {
|
|
|
68
109
|
console.log(`[gjsify gettext] compiled ${languages.length} language(s) into ${opts.outDir}`);
|
|
69
110
|
}
|
|
70
111
|
}
|
|
71
|
-
function defaultFilename(domain, format,
|
|
112
|
+
function defaultFilename(domain, format, template) {
|
|
72
113
|
switch (format) {
|
|
73
114
|
case 'mo':
|
|
74
115
|
return `${domain}.mo`;
|
|
75
|
-
case 'json':
|
|
76
|
-
return `${domain}.json`;
|
|
77
116
|
case 'desktop':
|
|
78
117
|
return `${domain}.desktop`;
|
|
79
118
|
case 'xml': {
|
|
80
119
|
// Mirror the template filename but without the trailing `.in` (convention for
|
|
81
120
|
// pre-processed metainfo templates: `org.foo.Bar.metainfo.xml.in`).
|
|
82
|
-
if (
|
|
121
|
+
if (template) {
|
|
83
122
|
// `basename`, not a hand-rolled `lastIndexOf('/')`: this is a path the USER
|
|
84
123
|
// passed, and on win32 it separates with `\`, which the slice read as part
|
|
85
124
|
// of the name (#1143). `node:path` is the right owner for host tooling —
|
|
86
125
|
// though it only answers correctly under Node until #1146 makes
|
|
87
126
|
// `@gjsify/path` select win32 per host.
|
|
88
|
-
return basename(
|
|
127
|
+
return basename(template.replace(/\.in$/, ''));
|
|
89
128
|
}
|
|
90
129
|
return `${domain}.xml`;
|
|
91
130
|
}
|
|
@@ -93,7 +132,8 @@ function defaultFilename(domain, format, metainfoTemplate) {
|
|
|
93
132
|
}
|
|
94
133
|
export const gettextCommand = {
|
|
95
134
|
command: 'gettext <poDir> <outDir>',
|
|
96
|
-
description: 'Compile gettext .po files to .mo (per-language locale tree) or
|
|
135
|
+
description: 'Compile gettext .po files to .mo (per-language locale tree), or merge every catalogue into a ' +
|
|
136
|
+
'.desktop / AppStream template via msgfmt --desktop / --xml.',
|
|
97
137
|
builder: (yargs) => {
|
|
98
138
|
return yargs
|
|
99
139
|
.positional('poDir', {
|
|
@@ -103,7 +143,7 @@ export const gettextCommand = {
|
|
|
103
143
|
demandOption: true,
|
|
104
144
|
})
|
|
105
145
|
.positional('outDir', {
|
|
106
|
-
description: 'Output directory (locale tree for --format=mo, plain dir for xml/desktop
|
|
146
|
+
description: 'Output directory (locale tree for --format=mo, plain dir for xml/desktop)',
|
|
107
147
|
type: 'string',
|
|
108
148
|
normalize: true,
|
|
109
149
|
demandOption: true,
|
|
@@ -117,13 +157,21 @@ export const gettextCommand = {
|
|
|
117
157
|
.option('format', {
|
|
118
158
|
description: 'Output format',
|
|
119
159
|
type: 'string',
|
|
120
|
-
choices: ['mo', 'xml', 'desktop'
|
|
160
|
+
choices: ['mo', 'xml', 'desktop'],
|
|
121
161
|
default: 'mo',
|
|
162
|
+
})
|
|
163
|
+
.option('template', {
|
|
164
|
+
description: 'Required for --format=xml|desktop: the file msgfmt substitutes into. Its SUFFIX matters — ' +
|
|
165
|
+
'msgfmt --xml finds its ITS rules by filename pattern, so an AppStream template must be ' +
|
|
166
|
+
'named `*.metainfo.xml[.in]`.',
|
|
167
|
+
type: 'string',
|
|
168
|
+
normalize: true,
|
|
122
169
|
})
|
|
123
170
|
.option('metainfo', {
|
|
124
|
-
description: '
|
|
171
|
+
description: 'Deprecated alias for --template.',
|
|
125
172
|
type: 'string',
|
|
126
173
|
normalize: true,
|
|
174
|
+
hidden: true,
|
|
127
175
|
})
|
|
128
176
|
.option('filename', {
|
|
129
177
|
description: 'Override the output filename (defaults to <domain>.<ext>)',
|
|
@@ -146,8 +194,12 @@ export const gettextCommand = {
|
|
|
146
194
|
const outDir = resolve(args.outDir);
|
|
147
195
|
const domain = args.domain;
|
|
148
196
|
const format = args.format ?? 'mo';
|
|
149
|
-
|
|
150
|
-
|
|
197
|
+
// `--metainfo` predates the option being useful for `.desktop` too. Kept as a
|
|
198
|
+
// hidden alias rather than removed: it is the spelling `cli-reference.md`
|
|
199
|
+
// documented, so removing it would break the invocation the docs taught.
|
|
200
|
+
const templateArg = args.template ?? args.metainfo;
|
|
201
|
+
const template = templateArg ? resolve(templateArg) : undefined;
|
|
202
|
+
const filename = args.filename ?? defaultFilename(domain, format, template);
|
|
151
203
|
const verbose = !!args.verbose;
|
|
152
204
|
const removeXmlComments = !!args['remove-xml-comments'];
|
|
153
205
|
if (!(await fileExists(poDir))) {
|
|
@@ -155,28 +207,34 @@ export const gettextCommand = {
|
|
|
155
207
|
process.exitCode = 1;
|
|
156
208
|
return;
|
|
157
209
|
}
|
|
210
|
+
// REFUSED, not worked around. `--xml` and `--desktop` substitute into a
|
|
211
|
+
// template, and msgfmt rejects both without one (`--desktop requires a
|
|
212
|
+
// "--template template" specification`, exit 1). The old code fell back to a
|
|
213
|
+
// per-language loop that passed no template at all, so the fallback could only
|
|
214
|
+
// ever reproduce that same error one layer further down — with the user's
|
|
215
|
+
// `--format` reported as the thing that failed rather than the missing template.
|
|
216
|
+
if (format !== 'mo' && template === undefined) {
|
|
217
|
+
console.error(`[gjsify gettext] --format=${format} needs a template to substitute into: pass ` +
|
|
218
|
+
'`--template <file>`. msgfmt has no way to produce this format from .po files alone.' +
|
|
219
|
+
(format === 'xml'
|
|
220
|
+
? ' Name it `*.metainfo.xml[.in]` — msgfmt --xml finds its ITS rules by filename pattern.'
|
|
221
|
+
: ''));
|
|
222
|
+
process.exitCode = 1;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
158
225
|
try {
|
|
159
|
-
if (format === '
|
|
160
|
-
await
|
|
161
|
-
poDir,
|
|
162
|
-
outDir,
|
|
163
|
-
domain,
|
|
164
|
-
template: metainfo,
|
|
165
|
-
filename,
|
|
166
|
-
removeXmlComments,
|
|
167
|
-
verbose,
|
|
168
|
-
});
|
|
226
|
+
if (format === 'mo') {
|
|
227
|
+
await compileCatalogues({ poDir, outDir, filename, verbose });
|
|
169
228
|
}
|
|
170
229
|
else {
|
|
171
|
-
|
|
172
|
-
console.warn('[gjsify gettext] --format=xml without --metainfo: falling back to per-language XML files');
|
|
173
|
-
}
|
|
174
|
-
await compilePerLanguage({
|
|
230
|
+
await compileMerged({
|
|
175
231
|
poDir,
|
|
176
232
|
outDir,
|
|
177
|
-
domain,
|
|
178
233
|
format,
|
|
234
|
+
// Narrowed by the refusal above.
|
|
235
|
+
template: template,
|
|
179
236
|
filename,
|
|
237
|
+
removeXmlComments,
|
|
180
238
|
verbose,
|
|
181
239
|
});
|
|
182
240
|
}
|
|
@@ -6,6 +6,13 @@ interface OnboardOptions {
|
|
|
6
6
|
registry?: string;
|
|
7
7
|
otp?: string;
|
|
8
8
|
concurrency: number;
|
|
9
|
+
'write-concurrency': number;
|
|
10
|
+
packages?: string[];
|
|
11
|
+
include?: string[];
|
|
12
|
+
exclude?: string[];
|
|
13
|
+
access: string;
|
|
14
|
+
build: boolean;
|
|
15
|
+
verbose?: boolean;
|
|
9
16
|
'dry-run'?: boolean;
|
|
10
17
|
json?: boolean;
|
|
11
18
|
yes?: boolean;
|