@gjsify/cli 0.38.1 → 0.40.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 +212 -195
- package/lib/cli-app.js +11 -25
- package/lib/commands/affected-classify.d.ts +16 -0
- package/lib/commands/affected-classify.js +304 -0
- package/lib/commands/affected.js +2 -291
- package/lib/commands/flatpak/check.js +34 -43
- package/lib/commands/flatpak/ci.js +1 -1
- package/lib/commands/flatpak/deps.js +10 -19
- package/lib/commands/flatpak/diff.js +1 -1
- package/lib/commands/flatpak/init.js +1 -1
- package/lib/commands/flatpak/release.js +7 -11
- package/lib/commands/flatpak/scaffold.d.ts +2 -4
- package/lib/commands/flatpak/scaffold.js +25 -298
- package/lib/commands/flatpak/sync-flathub.js +18 -36
- package/lib/commands/foreach.js +34 -20
- package/lib/commands/gettext.js +7 -4
- package/lib/commands/index.d.ts +1 -0
- package/lib/commands/index.js +1 -0
- package/lib/commands/ship.d.ts +11 -0
- package/lib/commands/ship.js +217 -0
- package/lib/config.d.ts +12 -0
- package/lib/config.js +15 -0
- package/lib/types/config-data.d.ts +136 -46
- package/lib/utils/app-metadata.d.ts +51 -0
- package/lib/utils/app-metadata.js +338 -0
- package/lib/utils/cli-runtime-closure.d.ts +23 -0
- package/lib/utils/cli-runtime-closure.js +274 -0
- package/lib/utils/publish-headers.d.ts +13 -0
- package/lib/utils/publish-headers.js +30 -4
- package/lib/utils/ship/ar.d.ts +21 -0
- package/lib/utils/ship/ar.js +57 -0
- package/lib/utils/ship/bytes.d.ts +4 -0
- package/lib/utils/ship/bytes.js +23 -0
- package/lib/utils/ship/cpio.d.ts +22 -0
- package/lib/utils/ship/cpio.js +93 -0
- package/lib/utils/ship/deb.d.ts +24 -0
- package/lib/utils/ship/deb.js +118 -0
- package/lib/utils/ship/depends.d.ts +62 -0
- package/lib/utils/ship/depends.js +202 -0
- package/lib/utils/ship/discover.d.ts +21 -0
- package/lib/utils/ship/discover.js +129 -0
- package/lib/utils/ship/formats.d.ts +5 -0
- package/lib/utils/ship/formats.js +83 -0
- package/lib/utils/ship/gi-namespaces.d.ts +7 -0
- package/lib/utils/ship/gi-namespaces.js +49 -0
- package/lib/utils/ship/gzip.d.ts +2 -0
- package/lib/utils/ship/gzip.js +21 -0
- package/lib/utils/ship/launcher.d.ts +7 -0
- package/lib/utils/ship/launcher.js +37 -0
- package/lib/utils/ship/payload.d.ts +30 -0
- package/lib/utils/ship/payload.js +59 -0
- package/lib/utils/ship/plan.d.ts +34 -0
- package/lib/utils/ship/plan.js +167 -0
- package/lib/utils/ship/rpm-header.d.ts +29 -0
- package/lib/utils/ship/rpm-header.js +170 -0
- package/lib/utils/ship/rpm.d.ts +12 -0
- package/lib/utils/ship/rpm.js +377 -0
- package/lib/utils/ship/scripts.d.ts +31 -0
- package/lib/utils/ship/scripts.js +76 -0
- package/lib/utils/ship/settings.d.ts +60 -0
- package/lib/utils/ship/settings.js +184 -0
- package/lib/utils/ship/stage-writer.d.ts +17 -0
- package/lib/utils/ship/stage-writer.js +49 -0
- package/lib/utils/ship/types.d.ts +114 -0
- package/lib/utils/ship/types.js +10 -0
- package/lib/utils/ship/version.d.ts +17 -0
- package/lib/utils/ship/version.js +55 -0
- package/lib/utils/spawn.d.ts +11 -1
- package/lib/utils/spawn.js +28 -4
- package/package.json +19 -18
- /package/lib/templates/{flatpak → app}/desktop.tmpl +0 -0
|
@@ -1,328 +1,55 @@
|
|
|
1
|
-
//
|
|
2
|
-
// `gjsify
|
|
3
|
-
// directly in TypeScript instead of substituting into a static template —
|
|
4
|
-
// the AppStream surface (description blocks, per-release rich notes,
|
|
5
|
-
// translator hints, kudos, supports/requires/recommends, content_rating
|
|
6
|
-
// attributes, provides) has too many optional nested sections for a
|
|
7
|
-
// template+placeholder approach to stay legible.
|
|
1
|
+
// Flathub-specific scaffolding for `gjsify flatpak init`, plus the thin adapter
|
|
2
|
+
// that hands `gjsify.flatpak`'s metadata half to the shared renderers.
|
|
8
3
|
//
|
|
9
|
-
// `.desktop`
|
|
10
|
-
//
|
|
4
|
+
// The MetaInfo XML and the `.desktop` entry moved to `utils/app-metadata.ts`
|
|
5
|
+
// (ADR 0024 § 8): they describe the APP, not the Flatpak, and `gjsify ship`
|
|
6
|
+
// stages the same two files into `share/metainfo/` and `share/applications/`.
|
|
7
|
+
// What stays here is genuinely Flathub's — `flathub.json`, a policy file no
|
|
8
|
+
// other format has.
|
|
11
9
|
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { renderDesktopEntry, renderMetainfoApp as renderMetainfoAppShared, renderMetainfoCli as renderMetainfoCliShared, validateAppMetadata, } from '../../utils/app-metadata.js';
|
|
12
11
|
/**
|
|
13
|
-
* Lazy template loaders
|
|
14
|
-
*
|
|
15
|
-
* into the GJS bundle at build time.
|
|
12
|
+
* Lazy template loaders. `static-read-inliner` matches this shape and inlines
|
|
13
|
+
* the templates into the GJS bundle at build time.
|
|
16
14
|
*/
|
|
17
|
-
function loadDesktopTemplate() {
|
|
18
|
-
return readFileSync(new URL('../../templates/flatpak/desktop.tmpl', import.meta.url), 'utf-8');
|
|
19
|
-
}
|
|
20
15
|
function loadFlathubAppTemplate() {
|
|
21
16
|
return readFileSync(new URL('../../templates/flatpak/flathub-app.json.tmpl', import.meta.url), 'utf-8');
|
|
22
17
|
}
|
|
23
18
|
function loadFlathubCliTemplate() {
|
|
24
19
|
return readFileSync(new URL('../../templates/flatpak/flathub-cli.json.tmpl', import.meta.url), 'utf-8');
|
|
25
20
|
}
|
|
21
|
+
/** `gjsify.flatpak` IS an `AppMetadata` plus Flatpak's own keys. */
|
|
22
|
+
function toMetadataInputs(inputs) {
|
|
23
|
+
return {
|
|
24
|
+
appId: inputs.appId,
|
|
25
|
+
name: inputs.name,
|
|
26
|
+
command: inputs.command,
|
|
27
|
+
kind: inputs.kind,
|
|
28
|
+
metadata: inputs.flatpak,
|
|
29
|
+
configKey: 'gjsify.flatpak',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
26
32
|
/**
|
|
27
33
|
* Validate that the config has the minimum set of fields required for
|
|
28
34
|
* MetaInfo XML rendering. Returns the list of missing fields with
|
|
29
35
|
* actionable hints; empty list means OK.
|
|
30
36
|
*/
|
|
31
37
|
export function validateScaffoldInputs(inputs) {
|
|
32
|
-
|
|
33
|
-
const missing = [];
|
|
34
|
-
if (!f.developer?.id || !f.developer?.name) {
|
|
35
|
-
missing.push({
|
|
36
|
-
field: 'gjsify.flatpak.developer',
|
|
37
|
-
hint: 'Set `gjsify.flatpak.developer = { "id": "io.github.you", "name": "Your Name" }` in package.json. The id is reverse-DNS.',
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
if (!f.summary) {
|
|
41
|
-
missing.push({
|
|
42
|
-
field: 'gjsify.flatpak.summary',
|
|
43
|
-
hint: 'One-line app summary, ≤80 chars, no trailing period. Example: "Learn 6502 assembly language".',
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
if (!f.description) {
|
|
47
|
-
missing.push({
|
|
48
|
-
field: 'gjsify.flatpak.description',
|
|
49
|
-
hint: 'Plain text (split on blank lines) or DescriptionBlock[] for rich content with bullet lists + translator hints.',
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
if (!f.license?.project) {
|
|
53
|
-
missing.push({
|
|
54
|
-
field: 'gjsify.flatpak.license.project',
|
|
55
|
-
hint: 'SPDX identifier of the project license, e.g. "MIT", "GPL-3.0-or-later".',
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
if (!f.homepageUrl) {
|
|
59
|
-
missing.push({
|
|
60
|
-
field: 'gjsify.flatpak.homepageUrl',
|
|
61
|
-
hint: 'Required by Flathub. Example: "https://github.com/you/your-repo".',
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
return missing;
|
|
38
|
+
return validateAppMetadata(toMetadataInputs(inputs));
|
|
65
39
|
}
|
|
66
40
|
/** Render the MetaInfo XML for a desktop application. */
|
|
67
41
|
export function renderMetainfoApp(inputs) {
|
|
68
|
-
return
|
|
42
|
+
return renderMetainfoAppShared(toMetadataInputs(inputs));
|
|
69
43
|
}
|
|
70
44
|
/** Render the MetaInfo XML for a console application. */
|
|
71
45
|
export function renderMetainfoCli(inputs) {
|
|
72
|
-
return
|
|
46
|
+
return renderMetainfoCliShared(toMetadataInputs(inputs));
|
|
73
47
|
}
|
|
74
48
|
/** Render the .desktop entry (app kind only). */
|
|
75
49
|
export function renderDesktop(inputs) {
|
|
76
|
-
|
|
77
|
-
const categoriesLine = (f.categories ?? ['Utility']).join(';') + ';';
|
|
78
|
-
const keywordsLine = f.keywords?.length ? `Keywords=${f.keywords.join(';')};\n` : '';
|
|
79
|
-
// `MimeType=` is sourced from `gjsify.flatpak.provides.mimetypes` —
|
|
80
|
-
// the same field already populates `<mediatype>` entries in the
|
|
81
|
-
// MetaInfo XML, so callers configure both with one knob. Typical
|
|
82
|
-
// entries: `x-scheme-handler/<scheme>` for URL-scheme handlers
|
|
83
|
-
// (Flatpak portal: chats / browsers can launch the app via a
|
|
84
|
-
// custom URL), or `application/<mime>` for file-type handlers.
|
|
85
|
-
const mimetypes = f.provides?.mimetypes ?? [];
|
|
86
|
-
const mimetypesLine = mimetypes.length ? `MimeType=${mimetypes.join(';')};\n` : '';
|
|
87
|
-
return substitute(loadDesktopTemplate(), {
|
|
88
|
-
NAME: inputs.name,
|
|
89
|
-
SUMMARY: f.summary ?? inputs.name,
|
|
90
|
-
COMMAND: inputs.command,
|
|
91
|
-
APP_ID: inputs.appId,
|
|
92
|
-
CATEGORIES_LINE: categoriesLine,
|
|
93
|
-
KEYWORDS_LINE: keywordsLine,
|
|
94
|
-
MIMETYPES_LINE: mimetypesLine,
|
|
95
|
-
});
|
|
50
|
+
return renderDesktopEntry(toMetadataInputs(inputs));
|
|
96
51
|
}
|
|
97
52
|
/** Render the flathub.json policy file. */
|
|
98
53
|
export function renderFlathubJson(kind) {
|
|
99
54
|
return kind === 'cli' ? loadFlathubCliTemplate() : loadFlathubAppTemplate();
|
|
100
55
|
}
|
|
101
|
-
// ─── MetaInfo XML builder ────────────────────────────────────────────────
|
|
102
|
-
function renderMetainfo(inputs, kind) {
|
|
103
|
-
const f = inputs.flatpak;
|
|
104
|
-
const year = new Date().getFullYear();
|
|
105
|
-
const developerName = f.developer?.name ?? '';
|
|
106
|
-
const lines = [];
|
|
107
|
-
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
108
|
-
lines.push(`<!-- Copyright ${year} ${escapeXml(developerName)} -->`);
|
|
109
|
-
lines.push(`<component type="${kind}">`);
|
|
110
|
-
lines.push(` <id>${escapeXml(inputs.appId)}</id>`);
|
|
111
|
-
lines.push(` <metadata_license>${escapeXml(f.license?.metadata ?? 'CC0-1.0')}</metadata_license>`);
|
|
112
|
-
lines.push(` <project_license>${escapeXml(f.license?.project ?? '')}</project_license>`);
|
|
113
|
-
lines.push(` <name>${escapeXml(inputs.name)}</name>`);
|
|
114
|
-
pushTranslatorHint(lines, f.summaryTranslatorHint, ' ');
|
|
115
|
-
lines.push(` <summary>${escapeXml(f.summary ?? inputs.name)}</summary>`);
|
|
116
|
-
if (f.iconRemote) {
|
|
117
|
-
lines.push(` <icon type="remote">${escapeXml(f.iconRemote)}</icon>`);
|
|
118
|
-
}
|
|
119
|
-
// <description>
|
|
120
|
-
lines.push(' <description>');
|
|
121
|
-
for (const blockLine of renderDescriptionBlocks(f.description ?? '', ' ')) {
|
|
122
|
-
lines.push(blockLine);
|
|
123
|
-
}
|
|
124
|
-
lines.push(' </description>');
|
|
125
|
-
// <developer>
|
|
126
|
-
if (f.developer?.id && f.developer?.name) {
|
|
127
|
-
lines.push(` <developer id="${escapeXml(f.developer.id)}">`);
|
|
128
|
-
const translateAttr = f.developer.nameTranslatable === true ? '' : ' translate="no"';
|
|
129
|
-
lines.push(` <name${translateAttr}>${escapeXml(f.developer.name)}</name>`);
|
|
130
|
-
if (f.developer.email) {
|
|
131
|
-
lines.push(` <email>${escapeXml(f.developer.email)}</email>`);
|
|
132
|
-
}
|
|
133
|
-
lines.push(' </developer>');
|
|
134
|
-
}
|
|
135
|
-
if (kind === 'desktop-application') {
|
|
136
|
-
lines.push(` <launchable type="desktop-id">${escapeXml(inputs.appId)}.desktop</launchable>`);
|
|
137
|
-
}
|
|
138
|
-
// <screenshots>
|
|
139
|
-
if (f.screenshots?.length) {
|
|
140
|
-
lines.push(' <screenshots>');
|
|
141
|
-
f.screenshots.forEach((s, i) => {
|
|
142
|
-
const type = s.type ?? (i === 0 ? 'default' : undefined);
|
|
143
|
-
const typeAttr = type ? ` type="${escapeXml(type)}"` : '';
|
|
144
|
-
const envAttr = s.environment ? ` environment="${escapeXml(s.environment)}"` : '';
|
|
145
|
-
lines.push(` <screenshot${typeAttr}${envAttr}>`);
|
|
146
|
-
lines.push(` <image>${escapeXml(s.url)}</image>`);
|
|
147
|
-
if (s.caption) {
|
|
148
|
-
pushTranslatorHint(lines, s.captionTranslatorHint, ' ');
|
|
149
|
-
lines.push(` <caption>${escapeXml(s.caption)}</caption>`);
|
|
150
|
-
}
|
|
151
|
-
lines.push(' </screenshot>');
|
|
152
|
-
});
|
|
153
|
-
lines.push(' </screenshots>');
|
|
154
|
-
}
|
|
155
|
-
// <url> entries
|
|
156
|
-
if (f.homepageUrl)
|
|
157
|
-
lines.push(` <url type="homepage">${escapeXml(f.homepageUrl)}</url>`);
|
|
158
|
-
if (f.bugtrackerUrl)
|
|
159
|
-
lines.push(` <url type="bugtracker">${escapeXml(f.bugtrackerUrl)}</url>`);
|
|
160
|
-
if (f.vcsBrowserUrl)
|
|
161
|
-
lines.push(` <url type="vcs-browser">${escapeXml(f.vcsBrowserUrl)}</url>`);
|
|
162
|
-
if (f.donationUrl)
|
|
163
|
-
lines.push(` <url type="donation">${escapeXml(f.donationUrl)}</url>`);
|
|
164
|
-
if (f.translateUrl)
|
|
165
|
-
lines.push(` <url type="translate">${escapeXml(f.translateUrl)}</url>`);
|
|
166
|
-
// <content_rating>
|
|
167
|
-
const cr = normaliseContentRating(f.contentRating);
|
|
168
|
-
if (cr.attributes && Object.keys(cr.attributes).length > 0) {
|
|
169
|
-
lines.push(` <content_rating type="${escapeXml(cr.type)}">`);
|
|
170
|
-
for (const [key, value] of Object.entries(cr.attributes)) {
|
|
171
|
-
lines.push(` <content_attribute id="${escapeXml(key)}">${escapeXml(value)}</content_attribute>`);
|
|
172
|
-
}
|
|
173
|
-
lines.push(' </content_rating>');
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
lines.push(` <content_rating type="${escapeXml(cr.type)}" />`);
|
|
177
|
-
}
|
|
178
|
-
// <releases>
|
|
179
|
-
if (f.releases?.length) {
|
|
180
|
-
lines.push(' <releases>');
|
|
181
|
-
for (const r of f.releases) {
|
|
182
|
-
if (r.description === undefined) {
|
|
183
|
-
lines.push(` <release version="${escapeXml(r.version)}" date="${escapeXml(r.date)}" />`);
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
lines.push(` <release version="${escapeXml(r.version)}" date="${escapeXml(r.date)}">`);
|
|
187
|
-
lines.push(' <description>');
|
|
188
|
-
for (const blockLine of renderDescriptionBlocks(r.description, ' ')) {
|
|
189
|
-
lines.push(blockLine);
|
|
190
|
-
}
|
|
191
|
-
lines.push(' </description>');
|
|
192
|
-
lines.push(' </release>');
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
lines.push(' </releases>');
|
|
196
|
-
}
|
|
197
|
-
// <categories>
|
|
198
|
-
if (f.categories?.length) {
|
|
199
|
-
lines.push(' <categories>');
|
|
200
|
-
for (const c of f.categories)
|
|
201
|
-
lines.push(` <category>${escapeXml(c)}</category>`);
|
|
202
|
-
lines.push(' </categories>');
|
|
203
|
-
}
|
|
204
|
-
// <keywords>
|
|
205
|
-
if (f.keywords?.length) {
|
|
206
|
-
lines.push(' <keywords>');
|
|
207
|
-
for (const k of f.keywords)
|
|
208
|
-
lines.push(` <keyword>${escapeXml(k)}</keyword>`);
|
|
209
|
-
lines.push(' </keywords>');
|
|
210
|
-
}
|
|
211
|
-
// <branding> (apps only — Flathub ignores it on CLI)
|
|
212
|
-
if (kind === 'desktop-application' && f.branding) {
|
|
213
|
-
lines.push(' <branding>');
|
|
214
|
-
lines.push(` <color type="primary" scheme_preference="light">${escapeXml(f.branding.accentLight)}</color>`);
|
|
215
|
-
lines.push(` <color type="primary" scheme_preference="dark">${escapeXml(f.branding.accentDark)}</color>`);
|
|
216
|
-
lines.push(' </branding>');
|
|
217
|
-
}
|
|
218
|
-
// <kudos>
|
|
219
|
-
if (f.kudos?.length) {
|
|
220
|
-
lines.push(' <kudos>');
|
|
221
|
-
for (const k of f.kudos)
|
|
222
|
-
lines.push(` <kudo>${escapeXml(k)}</kudo>`);
|
|
223
|
-
lines.push(' </kudos>');
|
|
224
|
-
}
|
|
225
|
-
// <provides> — always emit <binary> for both kinds; <mediatype>/<dbus> only when configured
|
|
226
|
-
const binaries = f.provides?.binaries ?? [inputs.command];
|
|
227
|
-
const mimetypes = f.provides?.mimetypes ?? [];
|
|
228
|
-
const dbus = f.provides?.dbus ?? [];
|
|
229
|
-
if (binaries.length || mimetypes.length || dbus.length) {
|
|
230
|
-
lines.push(' <provides>');
|
|
231
|
-
for (const b of binaries)
|
|
232
|
-
lines.push(` <binary>${escapeXml(b)}</binary>`);
|
|
233
|
-
for (const m of mimetypes)
|
|
234
|
-
lines.push(` <mediatype>${escapeXml(m)}</mediatype>`);
|
|
235
|
-
for (const d of dbus)
|
|
236
|
-
lines.push(` <dbus type="${escapeXml(d.type)}">${escapeXml(d.id)}</dbus>`);
|
|
237
|
-
lines.push(' </provides>');
|
|
238
|
-
}
|
|
239
|
-
// <supports>
|
|
240
|
-
if (f.supports?.controls?.length || f.supports?.internet) {
|
|
241
|
-
lines.push(' <supports>');
|
|
242
|
-
for (const c of f.supports.controls ?? [])
|
|
243
|
-
lines.push(` <control>${escapeXml(c)}</control>`);
|
|
244
|
-
if (f.supports.internet)
|
|
245
|
-
lines.push(` <internet>${escapeXml(f.supports.internet)}</internet>`);
|
|
246
|
-
lines.push(' </supports>');
|
|
247
|
-
}
|
|
248
|
-
// <requires>
|
|
249
|
-
if (f.requires?.displayLengthMin || f.requires?.controls?.length || f.requires?.internet) {
|
|
250
|
-
lines.push(' <requires>');
|
|
251
|
-
if (f.requires.displayLengthMin) {
|
|
252
|
-
lines.push(` <display_length compare="ge">${f.requires.displayLengthMin}</display_length>`);
|
|
253
|
-
}
|
|
254
|
-
for (const c of f.requires.controls ?? [])
|
|
255
|
-
lines.push(` <control>${escapeXml(c)}</control>`);
|
|
256
|
-
if (f.requires.internet)
|
|
257
|
-
lines.push(` <internet>${escapeXml(f.requires.internet)}</internet>`);
|
|
258
|
-
lines.push(' </requires>');
|
|
259
|
-
}
|
|
260
|
-
// <recommends>
|
|
261
|
-
if (f.recommends?.displayLengthMin || f.recommends?.controls?.length) {
|
|
262
|
-
lines.push(' <recommends>');
|
|
263
|
-
if (f.recommends.displayLengthMin) {
|
|
264
|
-
lines.push(` <display_length compare="ge">${f.recommends.displayLengthMin}</display_length>`);
|
|
265
|
-
}
|
|
266
|
-
for (const c of f.recommends.controls ?? [])
|
|
267
|
-
lines.push(` <control>${escapeXml(c)}</control>`);
|
|
268
|
-
lines.push(' </recommends>');
|
|
269
|
-
}
|
|
270
|
-
lines.push('</component>');
|
|
271
|
-
return lines.join('\n') + '\n';
|
|
272
|
-
}
|
|
273
|
-
// ─── Description block renderer ──────────────────────────────────────────
|
|
274
|
-
function renderDescriptionBlocks(description, indent) {
|
|
275
|
-
const blocks = typeof description === 'string' ? stringToBlocks(description) : description;
|
|
276
|
-
const out = [];
|
|
277
|
-
for (const block of blocks) {
|
|
278
|
-
if ('p' in block) {
|
|
279
|
-
pushTranslatorHint(out, block.translatorHint, indent);
|
|
280
|
-
out.push(`${indent}<p>${escapeXml(block.p.trim().replace(/\s+/g, ' '))}</p>`);
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
pushTranslatorHint(out, block.translatorHint, indent);
|
|
284
|
-
out.push(`${indent}<ul>`);
|
|
285
|
-
for (const item of block.ul) {
|
|
286
|
-
if (typeof item === 'string') {
|
|
287
|
-
out.push(`${indent} <li>${escapeXml(item)}</li>`);
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
pushTranslatorHint(out, item.translatorHint, `${indent} `);
|
|
291
|
-
out.push(`${indent} <li>${escapeXml(item.item)}</li>`);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
out.push(`${indent}</ul>`);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return out;
|
|
298
|
-
}
|
|
299
|
-
/** Auto-convert blank-line-split string into paragraph blocks. */
|
|
300
|
-
function stringToBlocks(s) {
|
|
301
|
-
return s
|
|
302
|
-
.trim()
|
|
303
|
-
.split(/\n\n+/)
|
|
304
|
-
.map((para) => ({ p: para.trim().replace(/\s+/g, ' ') }));
|
|
305
|
-
}
|
|
306
|
-
function pushTranslatorHint(out, hint, indent) {
|
|
307
|
-
if (!hint)
|
|
308
|
-
return;
|
|
309
|
-
out.push(`${indent}<!-- TRANSLATORS: ${hint} -->`);
|
|
310
|
-
}
|
|
311
|
-
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
312
|
-
function normaliseContentRating(cr) {
|
|
313
|
-
if (cr === undefined)
|
|
314
|
-
return { type: 'oars-1.1' };
|
|
315
|
-
if (typeof cr === 'string')
|
|
316
|
-
return { type: cr };
|
|
317
|
-
return { type: cr.type ?? 'oars-1.1', attributes: cr.attributes };
|
|
318
|
-
}
|
|
319
|
-
function substitute(template, tokens) {
|
|
320
|
-
let out = template;
|
|
321
|
-
for (const [key, value] of Object.entries(tokens)) {
|
|
322
|
-
out = out.split(`{{${key}}}`).join(value);
|
|
323
|
-
}
|
|
324
|
-
return out;
|
|
325
|
-
}
|
|
326
|
-
function escapeXml(value) {
|
|
327
|
-
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
328
|
-
}
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
18
18
|
import { join, resolve } from 'node:path';
|
|
19
19
|
import { homedir } from 'node:os';
|
|
20
|
-
import { execFile
|
|
20
|
+
import { execFile } from 'node:child_process';
|
|
21
|
+
import { describeExit, spawnToCompletion } from '../../utils/spawn.js';
|
|
21
22
|
import { promisify } from 'node:util';
|
|
22
23
|
import { Config } from '../../config.js';
|
|
23
24
|
import { readPackageJson } from './utils.js';
|
|
@@ -74,7 +75,7 @@ export const flatpakSyncFlathubCommand = {
|
|
|
74
75
|
handler: async (args) => {
|
|
75
76
|
const cwd = process.cwd();
|
|
76
77
|
const cfg = new Config();
|
|
77
|
-
const configData = await cfg.
|
|
78
|
+
const configData = await cfg.forCommand().catch(() => ({}));
|
|
78
79
|
const flatpak = configData.flatpak ?? {};
|
|
79
80
|
const appId = args.appId ?? flatpak.appId ?? readPackageJson(cwd).name;
|
|
80
81
|
if (!appId) {
|
|
@@ -204,28 +205,19 @@ async function detectDefaultBranch(cloneDir, verbose) {
|
|
|
204
205
|
}
|
|
205
206
|
return 'master';
|
|
206
207
|
}
|
|
207
|
-
|
|
208
|
+
// `completion: 'return'` on both helpers — the handler returns; see `utils/spawn.ts`.
|
|
209
|
+
async function gitInRepo(cwd, args, verbose) {
|
|
208
210
|
if (verbose)
|
|
209
211
|
console.log(`[gjsify flatpak sync-flathub] git ${args.join(' ')} (in ${cwd})`);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
rej(new Error('[gjsify flatpak sync-flathub] `git` not found. Install it from your distro.'));
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
rej(err);
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
child.on('exit', (code) => {
|
|
221
|
-
if (code === 0)
|
|
222
|
-
res();
|
|
223
|
-
else
|
|
224
|
-
rej(new Error(`git ${args[0]} exited ${code}`));
|
|
225
|
-
});
|
|
212
|
+
const result = await spawnToCompletion('git', args, {
|
|
213
|
+
completion: 'return',
|
|
214
|
+
cwd,
|
|
215
|
+
notFound: () => new Error('[gjsify flatpak sync-flathub] `git` not found. Install it from your distro.'),
|
|
226
216
|
});
|
|
217
|
+
if (result.code !== 0)
|
|
218
|
+
throw new Error(`git ${args[0]} exited ${describeExit(result)}`);
|
|
227
219
|
}
|
|
228
|
-
function ghCreate(cloneDir, flathubRepo, branch, version, body, verbose) {
|
|
220
|
+
async function ghCreate(cloneDir, flathubRepo, branch, version, body, verbose) {
|
|
229
221
|
const args = [
|
|
230
222
|
'pr',
|
|
231
223
|
'create',
|
|
@@ -240,23 +232,13 @@ function ghCreate(cloneDir, flathubRepo, branch, version, body, verbose) {
|
|
|
240
232
|
];
|
|
241
233
|
if (verbose)
|
|
242
234
|
console.log(`[gjsify flatpak sync-flathub] gh ${args.join(' ')} (in ${cloneDir})`);
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
rej(new Error('[gjsify flatpak sync-flathub] `gh` (GitHub CLI) not found. Install via `dnf install gh` (Fedora), `apt install gh` (Debian/Ubuntu), or `flatpak install -y flathub com.github.cli`.'));
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
rej(err);
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
child.on('exit', (code) => {
|
|
254
|
-
if (code === 0)
|
|
255
|
-
res();
|
|
256
|
-
else
|
|
257
|
-
rej(new Error(`gh pr create exited ${code}`));
|
|
258
|
-
});
|
|
235
|
+
const result = await spawnToCompletion('gh', args, {
|
|
236
|
+
completion: 'return',
|
|
237
|
+
cwd: cloneDir,
|
|
238
|
+
notFound: () => new Error('[gjsify flatpak sync-flathub] `gh` (GitHub CLI) not found. Install via `dnf install gh` (Fedora), `apt install gh` (Debian/Ubuntu), or `flatpak install -y flathub com.github.cli`.'),
|
|
259
239
|
});
|
|
240
|
+
if (result.code !== 0)
|
|
241
|
+
throw new Error(`gh pr create exited ${describeExit(result)}`);
|
|
260
242
|
}
|
|
261
243
|
/**
|
|
262
244
|
* Surgically edit a Flathub manifest to update the git source's `tag` +
|
package/lib/commands/foreach.js
CHANGED
|
@@ -13,9 +13,15 @@ import { readFileSync, readdirSync } from 'node:fs';
|
|
|
13
13
|
import { cpus } from 'node:os';
|
|
14
14
|
import { affectedClosure, buildDependencyGraph, discoverWorkspaces, filterWorkspaces, topologicalSort, } from '@gjsify/workspace';
|
|
15
15
|
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
16
|
+
import { cliRuntimeClosure } from '../utils/cli-runtime-closure.js';
|
|
16
17
|
import { prefixLines } from '../utils/prefixed-output.js';
|
|
17
18
|
import { BuildCacheRunner, buildCacheEnabledByEnv } from '../utils/build-cache.js';
|
|
18
|
-
|
|
19
|
+
// The ONE runner-selection rule, shared rather than re-derived. This file used to
|
|
20
|
+
// carry its own copy; `workspace.ts` grew the bootstrap-CLI branch and the copy
|
|
21
|
+
// here did not, so a `gjsify foreach` reached from a cold-tree bootstrap would
|
|
22
|
+
// have delegated to npm and lost the shim one level down — silently, and only on
|
|
23
|
+
// the OS legs that bootstrap cold. `onboard.ts` already imports it from here.
|
|
24
|
+
import { detectPackageManager } from './workspace.js';
|
|
19
25
|
// Every child spawned by spawnPrefixed registers here so fail-fast can
|
|
20
26
|
// terminate the whole in-flight set instead of waiting on it. On CI a
|
|
21
27
|
// single failed package used to leave foreach awaiting its still-running
|
|
@@ -425,20 +431,44 @@ export const foreachCommand = {
|
|
|
425
431
|
targets: selected.map((w) => w.name),
|
|
426
432
|
})
|
|
427
433
|
: undefined;
|
|
434
|
+
// The serial prefix: every child of this command BOOTS the CLI, so a
|
|
435
|
+
// package the CLI imports must not be written while siblings are starting
|
|
436
|
+
// (the torn `lib/esm` in `utils/cli-runtime-closure.ts`'s header). Building
|
|
437
|
+
// that closure first — alone, in the order the caller already sorted — is a
|
|
438
|
+
// REORDERING, not extra work: the prefix is removed from the parallel set,
|
|
439
|
+
// so the cost is only the parallelism given up over those few packages.
|
|
440
|
+
//
|
|
441
|
+
// Applied to every PARALLEL invocation, not only to `build`. Which scripts
|
|
442
|
+
// rewrite an output tree is not knowable from a script NAME — `build`,
|
|
443
|
+
// `build:types`, a package's own `dist` step and `clear` all do — and
|
|
444
|
+
// gating on the literal string "build" would be the hand-written list this
|
|
445
|
+
// closure exists to replace. A serial run needs no prefix at all: nothing
|
|
446
|
+
// else is in flight to read what it writes.
|
|
447
|
+
const prefixNames = args.parallel === true ? cliRuntimeClosure(allWorkspaces) : new Set();
|
|
448
|
+
const prefix = prefixNames.size > 0 ? selected.filter((ws) => prefixNames.has(ws.name)) : [];
|
|
449
|
+
const sweep = prefix.length > 0 ? selected.filter((ws) => !prefixNames.has(ws.name)) : selected;
|
|
428
450
|
try {
|
|
451
|
+
if (prefix.length > 0) {
|
|
452
|
+
console.error(`[gjsify foreach] serial prefix: ${prefix.length} package(s) the running CLI imports — ` +
|
|
453
|
+
`${prefix.map((ws) => ws.name).join(', ')}`);
|
|
454
|
+
for (const ws of prefix) {
|
|
455
|
+
console.error(`[gjsify foreach] start ${ws.name} (serial prefix)`);
|
|
456
|
+
await runOne(ws, finalCmd, cmdArgs, /* prefixOutput */ true, verbose, exec, cache);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
429
459
|
if (args.parallel && !args.topological && !args['topological-dev']) {
|
|
430
460
|
const jobs = args.jobs && args.jobs > 0 ? args.jobs : cpus().length;
|
|
431
|
-
await runParallel(
|
|
461
|
+
await runParallel(sweep, finalCmd, cmdArgs, jobs, verbose, exec, cache);
|
|
432
462
|
}
|
|
433
463
|
else if (args.parallel) {
|
|
434
464
|
// Topological + parallel: each workspace starts as soon as its
|
|
435
465
|
// deps (in the selected set) have finished. Yarn calls this
|
|
436
466
|
// "topological order with concurrency"; we cap at --jobs.
|
|
437
467
|
const jobs = args.jobs && args.jobs > 0 ? args.jobs : cpus().length;
|
|
438
|
-
await runTopologicalParallel(
|
|
468
|
+
await runTopologicalParallel(sweep, finalCmd, cmdArgs, jobs, verbose, args['topological-dev'] === true, exec, cache);
|
|
439
469
|
}
|
|
440
470
|
else {
|
|
441
|
-
await runSequential(
|
|
471
|
+
await runSequential(sweep, finalCmd, cmdArgs, verbose, exec, cache);
|
|
442
472
|
}
|
|
443
473
|
}
|
|
444
474
|
catch (err) {
|
|
@@ -703,22 +733,6 @@ async function runOne(ws, script, args, prefixOutput, verbose, exec, cache) {
|
|
|
703
733
|
if (cache && before)
|
|
704
734
|
cache.storeAfterSuccess(ws, before);
|
|
705
735
|
}
|
|
706
|
-
function detectPackageManager() {
|
|
707
|
-
// Under GJS there is no Node — neither `npm` nor `yarn` can run scripts, so
|
|
708
|
-
// the runner is `gjsify` itself via the GJS shim on PATH
|
|
709
|
-
// (`ensureGjsifyShimOnPath`). Makes node-free `gjsify foreach` work.
|
|
710
|
-
if (isGjs())
|
|
711
|
-
return 'gjsify';
|
|
712
|
-
// `npm_config_user_agent` is set by npm/yarn/pnpm — first token is
|
|
713
|
-
// `<name>/<version>`. Reuse it so `gjsify foreach build` invoked
|
|
714
|
-
// through `yarn run` keeps using yarn, etc.
|
|
715
|
-
const ua = process.env.npm_config_user_agent ?? '';
|
|
716
|
-
if (ua.startsWith('yarn/'))
|
|
717
|
-
return 'yarn';
|
|
718
|
-
if (ua.startsWith('gjsify/'))
|
|
719
|
-
return 'gjsify';
|
|
720
|
-
return 'npm';
|
|
721
|
-
}
|
|
722
736
|
async function spawnPrefixed(cmd, args, cwd, prefix) {
|
|
723
737
|
// `completion: 'exit'` — every path out of this command's handler ends in
|
|
724
738
|
// `process.exit(…)` (see the handler's tail), which is what quits the GLib
|
package/lib/commands/gettext.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
3
|
+
import { basename, join, resolve } from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
5
|
const execFileAsync = promisify(execFile);
|
|
6
6
|
async function listLanguages(poDir) {
|
|
@@ -80,9 +80,12 @@ function defaultFilename(domain, format, metainfoTemplate) {
|
|
|
80
80
|
// Mirror the template filename but without the trailing `.in` (convention for
|
|
81
81
|
// pre-processed metainfo templates: `org.foo.Bar.metainfo.xml.in`).
|
|
82
82
|
if (metainfoTemplate) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
// `basename`, not a hand-rolled `lastIndexOf('/')`: this is a path the USER
|
|
84
|
+
// passed, and on win32 it separates with `\`, which the slice read as part
|
|
85
|
+
// of the name (#1143). `node:path` is the right owner for host tooling —
|
|
86
|
+
// though it only answers correctly under Node until #1146 makes
|
|
87
|
+
// `@gjsify/path` select win32 per host.
|
|
88
|
+
return basename(metainfoTemplate.replace(/\.in$/, ''));
|
|
86
89
|
}
|
|
87
90
|
return `${domain}.xml`;
|
|
88
91
|
}
|
package/lib/commands/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from './gresource.js';
|
|
|
12
12
|
export * from './gettext.js';
|
|
13
13
|
export * from './gsettings.js';
|
|
14
14
|
export { flatpakCommand } from './flatpak/index.js';
|
|
15
|
+
export * from './ship.js';
|
|
15
16
|
export * from './dlx.js';
|
|
16
17
|
export * from './install.js';
|
|
17
18
|
export * from './foreach.js';
|
package/lib/commands/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from './gresource.js';
|
|
|
12
12
|
export * from './gettext.js';
|
|
13
13
|
export * from './gsettings.js';
|
|
14
14
|
export { flatpakCommand } from './flatpak/index.js';
|
|
15
|
+
export * from './ship.js';
|
|
15
16
|
export * from './dlx.js';
|
|
16
17
|
export * from './install.js';
|
|
17
18
|
export * from './foreach.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Command } from '../types/index.js';
|
|
2
|
+
interface ShipOptions {
|
|
3
|
+
target?: string[];
|
|
4
|
+
out?: string;
|
|
5
|
+
stage: boolean;
|
|
6
|
+
'skip-build': boolean;
|
|
7
|
+
arch?: string;
|
|
8
|
+
verbose: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const shipCommand: Command<unknown, ShipOptions>;
|
|
11
|
+
export {};
|