@gjsify/cli 0.4.11 → 0.4.13

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.
@@ -0,0 +1,327 @@
1
+ // MetaInfo XML / .desktop / flathub.json scaffolding for
2
+ // `gjsify flatpak init`. Phase F.9.6 onwards builds the MetaInfo XML
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.
8
+ //
9
+ // `.desktop` and `flathub.json` keep their static templates (they're
10
+ // flat key=value or empty-object files where substitution is fine).
11
+ import { readFileSync } from 'node:fs';
12
+ /**
13
+ * Lazy template loaders for the two artefacts that stay template-based.
14
+ * `static-read-inliner` matches this shape and inlines the templates
15
+ * into the GJS bundle at build time.
16
+ */
17
+ function loadDesktopTemplate() {
18
+ return readFileSync(new URL('../../templates/flatpak/desktop.tmpl', import.meta.url), 'utf-8');
19
+ }
20
+ function loadFlathubAppTemplate() {
21
+ return readFileSync(new URL('../../templates/flatpak/flathub-app.json.tmpl', import.meta.url), 'utf-8');
22
+ }
23
+ function loadFlathubCliTemplate() {
24
+ return readFileSync(new URL('../../templates/flatpak/flathub-cli.json.tmpl', import.meta.url), 'utf-8');
25
+ }
26
+ /**
27
+ * Validate that the config has the minimum set of fields required for
28
+ * MetaInfo XML rendering. Returns the list of missing fields with
29
+ * actionable hints; empty list means OK.
30
+ */
31
+ export function validateScaffoldInputs(inputs) {
32
+ const f = inputs.flatpak;
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;
65
+ }
66
+ /** Render the MetaInfo XML for a desktop application. */
67
+ export function renderMetainfoApp(inputs) {
68
+ return renderMetainfo(inputs, 'desktop-application');
69
+ }
70
+ /** Render the MetaInfo XML for a console application. */
71
+ export function renderMetainfoCli(inputs) {
72
+ return renderMetainfo(inputs, 'console-application');
73
+ }
74
+ /** Render the .desktop entry (app kind only). */
75
+ export function renderDesktop(inputs) {
76
+ const f = inputs.flatpak;
77
+ const categoriesLine = (f.categories ?? ['Utility']).join(';') + ';';
78
+ const keywordsLine = f.keywords?.length
79
+ ? `Keywords=${f.keywords.join(';')};\n`
80
+ : '';
81
+ return substitute(loadDesktopTemplate(), {
82
+ NAME: inputs.name,
83
+ SUMMARY: f.summary ?? inputs.name,
84
+ COMMAND: inputs.command,
85
+ APP_ID: inputs.appId,
86
+ CATEGORIES_LINE: categoriesLine,
87
+ KEYWORDS_LINE: keywordsLine,
88
+ });
89
+ }
90
+ /** Render the flathub.json policy file. */
91
+ export function renderFlathubJson(kind) {
92
+ return kind === 'cli' ? loadFlathubCliTemplate() : loadFlathubAppTemplate();
93
+ }
94
+ // ─── MetaInfo XML builder ────────────────────────────────────────────────
95
+ function renderMetainfo(inputs, kind) {
96
+ const f = inputs.flatpak;
97
+ const year = new Date().getFullYear();
98
+ const developerName = f.developer?.name ?? '';
99
+ const lines = [];
100
+ lines.push('<?xml version="1.0" encoding="UTF-8"?>');
101
+ lines.push(`<!-- Copyright ${year} ${escapeXml(developerName)} -->`);
102
+ lines.push(`<component type="${kind}">`);
103
+ lines.push(` <id>${escapeXml(inputs.appId)}</id>`);
104
+ lines.push(` <metadata_license>${escapeXml(f.license?.metadata ?? 'CC0-1.0')}</metadata_license>`);
105
+ lines.push(` <project_license>${escapeXml(f.license?.project ?? '')}</project_license>`);
106
+ lines.push(` <name>${escapeXml(inputs.name)}</name>`);
107
+ pushTranslatorHint(lines, f.summaryTranslatorHint, ' ');
108
+ lines.push(` <summary>${escapeXml(f.summary ?? inputs.name)}</summary>`);
109
+ if (f.iconRemote) {
110
+ lines.push(` <icon type="remote">${escapeXml(f.iconRemote)}</icon>`);
111
+ }
112
+ // <description>
113
+ lines.push(' <description>');
114
+ for (const blockLine of renderDescriptionBlocks(f.description ?? '', ' ')) {
115
+ lines.push(blockLine);
116
+ }
117
+ lines.push(' </description>');
118
+ // <developer>
119
+ if (f.developer?.id && f.developer?.name) {
120
+ lines.push(` <developer id="${escapeXml(f.developer.id)}">`);
121
+ const translateAttr = f.developer.nameTranslatable === true ? '' : ' translate="no"';
122
+ lines.push(` <name${translateAttr}>${escapeXml(f.developer.name)}</name>`);
123
+ if (f.developer.email) {
124
+ lines.push(` <email>${escapeXml(f.developer.email)}</email>`);
125
+ }
126
+ lines.push(' </developer>');
127
+ }
128
+ if (kind === 'desktop-application') {
129
+ lines.push(` <launchable type="desktop-id">${escapeXml(inputs.appId)}.desktop</launchable>`);
130
+ }
131
+ // <screenshots>
132
+ if (f.screenshots?.length) {
133
+ lines.push(' <screenshots>');
134
+ f.screenshots.forEach((s, i) => {
135
+ const type = s.type ?? (i === 0 ? 'default' : undefined);
136
+ const typeAttr = type ? ` type="${escapeXml(type)}"` : '';
137
+ const envAttr = s.environment ? ` environment="${escapeXml(s.environment)}"` : '';
138
+ lines.push(` <screenshot${typeAttr}${envAttr}>`);
139
+ lines.push(` <image>${escapeXml(s.url)}</image>`);
140
+ if (s.caption) {
141
+ pushTranslatorHint(lines, s.captionTranslatorHint, ' ');
142
+ lines.push(` <caption>${escapeXml(s.caption)}</caption>`);
143
+ }
144
+ lines.push(' </screenshot>');
145
+ });
146
+ lines.push(' </screenshots>');
147
+ }
148
+ // <url> entries
149
+ if (f.homepageUrl)
150
+ lines.push(` <url type="homepage">${escapeXml(f.homepageUrl)}</url>`);
151
+ if (f.bugtrackerUrl)
152
+ lines.push(` <url type="bugtracker">${escapeXml(f.bugtrackerUrl)}</url>`);
153
+ if (f.vcsBrowserUrl)
154
+ lines.push(` <url type="vcs-browser">${escapeXml(f.vcsBrowserUrl)}</url>`);
155
+ if (f.donationUrl)
156
+ lines.push(` <url type="donation">${escapeXml(f.donationUrl)}</url>`);
157
+ if (f.translateUrl)
158
+ lines.push(` <url type="translate">${escapeXml(f.translateUrl)}</url>`);
159
+ // <content_rating>
160
+ const cr = normaliseContentRating(f.contentRating);
161
+ if (cr.attributes && Object.keys(cr.attributes).length > 0) {
162
+ lines.push(` <content_rating type="${escapeXml(cr.type)}">`);
163
+ for (const [key, value] of Object.entries(cr.attributes)) {
164
+ lines.push(` <content_attribute id="${escapeXml(key)}">${escapeXml(value)}</content_attribute>`);
165
+ }
166
+ lines.push(' </content_rating>');
167
+ }
168
+ else {
169
+ lines.push(` <content_rating type="${escapeXml(cr.type)}" />`);
170
+ }
171
+ // <releases>
172
+ if (f.releases?.length) {
173
+ lines.push(' <releases>');
174
+ for (const r of f.releases) {
175
+ if (r.description === undefined) {
176
+ lines.push(` <release version="${escapeXml(r.version)}" date="${escapeXml(r.date)}" />`);
177
+ }
178
+ else {
179
+ lines.push(` <release version="${escapeXml(r.version)}" date="${escapeXml(r.date)}">`);
180
+ lines.push(' <description>');
181
+ for (const blockLine of renderDescriptionBlocks(r.description, ' ')) {
182
+ lines.push(blockLine);
183
+ }
184
+ lines.push(' </description>');
185
+ lines.push(' </release>');
186
+ }
187
+ }
188
+ lines.push(' </releases>');
189
+ }
190
+ // <categories>
191
+ if (f.categories?.length) {
192
+ lines.push(' <categories>');
193
+ for (const c of f.categories)
194
+ lines.push(` <category>${escapeXml(c)}</category>`);
195
+ lines.push(' </categories>');
196
+ }
197
+ // <keywords>
198
+ if (f.keywords?.length) {
199
+ lines.push(' <keywords>');
200
+ for (const k of f.keywords)
201
+ lines.push(` <keyword>${escapeXml(k)}</keyword>`);
202
+ lines.push(' </keywords>');
203
+ }
204
+ // <branding> (apps only — Flathub ignores it on CLI)
205
+ if (kind === 'desktop-application' && f.branding) {
206
+ lines.push(' <branding>');
207
+ lines.push(` <color type="primary" scheme_preference="light">${escapeXml(f.branding.accentLight)}</color>`);
208
+ lines.push(` <color type="primary" scheme_preference="dark">${escapeXml(f.branding.accentDark)}</color>`);
209
+ lines.push(' </branding>');
210
+ }
211
+ // <kudos>
212
+ if (f.kudos?.length) {
213
+ lines.push(' <kudos>');
214
+ for (const k of f.kudos)
215
+ lines.push(` <kudo>${escapeXml(k)}</kudo>`);
216
+ lines.push(' </kudos>');
217
+ }
218
+ // <provides> — always emit <binary> for both kinds; <mediatype>/<dbus> only when configured
219
+ const binaries = f.provides?.binaries ?? [inputs.command];
220
+ const mimetypes = f.provides?.mimetypes ?? [];
221
+ const dbus = f.provides?.dbus ?? [];
222
+ if (binaries.length || mimetypes.length || dbus.length) {
223
+ lines.push(' <provides>');
224
+ for (const b of binaries)
225
+ lines.push(` <binary>${escapeXml(b)}</binary>`);
226
+ for (const m of mimetypes)
227
+ lines.push(` <mediatype>${escapeXml(m)}</mediatype>`);
228
+ for (const d of dbus)
229
+ lines.push(` <dbus type="${escapeXml(d.type)}">${escapeXml(d.id)}</dbus>`);
230
+ lines.push(' </provides>');
231
+ }
232
+ // <supports>
233
+ if (f.supports?.controls?.length || f.supports?.internet) {
234
+ lines.push(' <supports>');
235
+ for (const c of f.supports.controls ?? [])
236
+ lines.push(` <control>${escapeXml(c)}</control>`);
237
+ if (f.supports.internet)
238
+ lines.push(` <internet>${escapeXml(f.supports.internet)}</internet>`);
239
+ lines.push(' </supports>');
240
+ }
241
+ // <requires>
242
+ if (f.requires?.displayLengthMin || f.requires?.controls?.length || f.requires?.internet) {
243
+ lines.push(' <requires>');
244
+ if (f.requires.displayLengthMin) {
245
+ lines.push(` <display_length compare="ge">${f.requires.displayLengthMin}</display_length>`);
246
+ }
247
+ for (const c of f.requires.controls ?? [])
248
+ lines.push(` <control>${escapeXml(c)}</control>`);
249
+ if (f.requires.internet)
250
+ lines.push(` <internet>${escapeXml(f.requires.internet)}</internet>`);
251
+ lines.push(' </requires>');
252
+ }
253
+ // <recommends>
254
+ if (f.recommends?.displayLengthMin || f.recommends?.controls?.length) {
255
+ lines.push(' <recommends>');
256
+ if (f.recommends.displayLengthMin) {
257
+ lines.push(` <display_length compare="ge">${f.recommends.displayLengthMin}</display_length>`);
258
+ }
259
+ for (const c of f.recommends.controls ?? [])
260
+ lines.push(` <control>${escapeXml(c)}</control>`);
261
+ lines.push(' </recommends>');
262
+ }
263
+ lines.push('</component>');
264
+ return lines.join('\n') + '\n';
265
+ }
266
+ // ─── Description block renderer ──────────────────────────────────────────
267
+ function renderDescriptionBlocks(description, indent) {
268
+ const blocks = typeof description === 'string'
269
+ ? stringToBlocks(description)
270
+ : description;
271
+ const out = [];
272
+ for (const block of blocks) {
273
+ if ('p' in block) {
274
+ pushTranslatorHint(out, block.translatorHint, indent);
275
+ out.push(`${indent}<p>${escapeXml(block.p.trim().replace(/\s+/g, ' '))}</p>`);
276
+ }
277
+ else {
278
+ pushTranslatorHint(out, block.translatorHint, indent);
279
+ out.push(`${indent}<ul>`);
280
+ for (const item of block.ul) {
281
+ if (typeof item === 'string') {
282
+ out.push(`${indent} <li>${escapeXml(item)}</li>`);
283
+ }
284
+ else {
285
+ pushTranslatorHint(out, item.translatorHint, `${indent} `);
286
+ out.push(`${indent} <li>${escapeXml(item.item)}</li>`);
287
+ }
288
+ }
289
+ out.push(`${indent}</ul>`);
290
+ }
291
+ }
292
+ return out;
293
+ }
294
+ /** Auto-convert blank-line-split string into paragraph blocks. */
295
+ function stringToBlocks(s) {
296
+ return s
297
+ .trim()
298
+ .split(/\n\n+/)
299
+ .map((para) => ({ p: para.trim().replace(/\s+/g, ' ') }));
300
+ }
301
+ function pushTranslatorHint(out, hint, indent) {
302
+ if (!hint)
303
+ return;
304
+ out.push(`${indent}<!-- TRANSLATORS: ${hint} -->`);
305
+ }
306
+ // ─── Helpers ─────────────────────────────────────────────────────────────
307
+ function normaliseContentRating(cr) {
308
+ if (cr === undefined)
309
+ return { type: 'oars-1.1' };
310
+ if (typeof cr === 'string')
311
+ return { type: cr };
312
+ return { type: cr.type ?? 'oars-1.1', attributes: cr.attributes };
313
+ }
314
+ function substitute(template, tokens) {
315
+ let out = template;
316
+ for (const [key, value] of Object.entries(tokens)) {
317
+ out = out.split(`{{${key}}}`).join(value);
318
+ }
319
+ return out;
320
+ }
321
+ function escapeXml(value) {
322
+ return value
323
+ .replace(/&/g, '&amp;')
324
+ .replace(/</g, '&lt;')
325
+ .replace(/>/g, '&gt;')
326
+ .replace(/"/g, '&quot;');
327
+ }
@@ -0,0 +1,10 @@
1
+ [Desktop Entry]
2
+ Name={{NAME}}
3
+ Comment={{SUMMARY}}
4
+ Exec={{COMMAND}}
5
+ Icon={{APP_ID}}
6
+ Terminal=false
7
+ Type=Application
8
+ Categories={{CATEGORIES_LINE}}
9
+ {{KEYWORDS_LINE}}StartupNotify=true
10
+ StartupWMClass={{APP_ID}}
@@ -0,0 +1,3 @@
1
+ {
2
+ "skip-icons-check": true
3
+ }
@@ -233,4 +233,205 @@ export interface ConfigDataFlatpak {
233
233
  ciContainer?: string;
234
234
  /** Branches the generated workflow triggers on. Default `['main']`. */
235
235
  ciBranches?: string[];
236
+ /**
237
+ * App kind. `'app'` (default) → desktop-application MetaInfo, GUI
238
+ * finish-args, .desktop + icon required. `'cli'` → console-application
239
+ * MetaInfo with `<provides><binary>`, no .desktop, flathub.json sets
240
+ * `skip-icons-check`. Supersedes the older `--cli-only` flag on
241
+ * `gjsify flatpak init`.
242
+ */
243
+ kind?: 'app' | 'cli';
244
+ /**
245
+ * App display name (`.desktop` `Name=` + MetaInfo `<name>`). Defaults
246
+ * to a friendly derivation of `package.json#name` — that works when
247
+ * `name` is the reverse-DNS app id, but breaks when it's an npm
248
+ * package name like `learn6502`. Set this explicitly to the
249
+ * human-readable name shown in app stores (e.g. `"Learn 6502 Assembly"`).
250
+ */
251
+ name?: string;
252
+ /**
253
+ * Developer attribution required by Flathub. `id` must be reverse-DNS.
254
+ * `email` (optional) becomes `<email>` inside `<developer>`.
255
+ * `nameTranslatable: false` (default) emits `translate="no"` on the
256
+ * `<name>` tag — recommended for personal/brand names that should not
257
+ * be translated. Set to `true` if the name is a descriptive phrase
258
+ * that translators should localise.
259
+ */
260
+ developer?: {
261
+ id: string;
262
+ name: string;
263
+ email?: string;
264
+ nameTranslatable?: boolean;
265
+ };
266
+ /**
267
+ * One-line summary, ≤80 chars, no trailing period (Flathub rule).
268
+ * Translatable — gettext's `msgfmt --xml --template` substitutes
269
+ * `<summary>` from `.po` files at build time. Set
270
+ * `summaryTranslatorHint` to emit a `<!-- TRANSLATORS: ... -->`
271
+ * comment before the tag.
272
+ */
273
+ summary?: string;
274
+ /** Translator hint emitted as `<!-- TRANSLATORS: ... -->` before `<summary>`. */
275
+ summaryTranslatorHint?: string;
276
+ /**
277
+ * Long description. Two forms:
278
+ * - **String** — split on blank lines into `<p>` blocks. Best for
279
+ * simple descriptions without bullet lists.
280
+ * - **Block array** — explicit blocks (`{p:..., translatorHint?:...}`
281
+ * for paragraphs or `{ul:[...], translatorHint?:...}` for bullet
282
+ * lists). Each block can carry its own translator hint. Use this
283
+ * form when you need bullet lists or per-string translator context.
284
+ */
285
+ description?: string | DescriptionBlock[];
286
+ /** Project homepage URL. Recommended; required for Flathub submission. */
287
+ homepageUrl?: string;
288
+ /** Bug tracker URL. */
289
+ bugtrackerUrl?: string;
290
+ /** VCS browser URL (e.g. GitHub repo). */
291
+ vcsBrowserUrl?: string;
292
+ /** Donation URL (e.g. OpenCollective / GitHub Sponsors). */
293
+ donationUrl?: string;
294
+ /**
295
+ * License SPDX identifiers. `project` is the project's source license
296
+ * (mandatory). `metadata` is the license under which the MetaInfo XML
297
+ * is distributed (default `'CC0-1.0'`).
298
+ */
299
+ license?: {
300
+ metadata?: string;
301
+ project: string;
302
+ };
303
+ /**
304
+ * Content-rating policy. Two forms:
305
+ * - **String** — just the spec keyword (default `'oars-1.1'`), emits
306
+ * an empty `<content_rating type="..."/>` block.
307
+ * - **Object** — keyword + `attributes` map. Each attribute is an
308
+ * OARS key (`violence-cartoon`, `social-info`, etc.) → severity
309
+ * (`none`, `mild`, `moderate`, `intense`). Flathub recommends
310
+ * declaring attributes explicitly even when they're `none` so the
311
+ * rating audit is auditable.
312
+ */
313
+ contentRating?: string | {
314
+ type?: string;
315
+ attributes?: Record<string, 'none' | 'mild' | 'moderate' | 'intense'>;
316
+ };
317
+ /** Freedesktop Menu categories (e.g. `['Development', 'Utility']`). */
318
+ categories?: string[];
319
+ /** Search keywords for app stores. */
320
+ keywords?: string[];
321
+ /**
322
+ * Release history. Most recent first. Each entry produces a
323
+ * `<release version=… date=…>` block. `description` accepts the
324
+ * same string-or-block-array shape as the top-level `description`
325
+ * field — use the array form for release notes with bullet lists
326
+ * or per-string translator hints.
327
+ */
328
+ releases?: Array<{
329
+ version: string;
330
+ date: string;
331
+ description?: string | DescriptionBlock[];
332
+ }>;
333
+ /**
334
+ * Screenshots for app-stores. `url` is an absolute HTTPS URL to a PNG.
335
+ * `caption` is optional and translatable — set `captionTranslatorHint`
336
+ * for a `<!-- TRANSLATORS: ... -->` hint. `environment` is one of
337
+ * `'plasma'|'gnome'|'cli'` — Flathub uses it to group by desktop.
338
+ * First entry defaults to `type="default"`; override with `type`.
339
+ */
340
+ screenshots?: Array<{
341
+ url: string;
342
+ caption?: string;
343
+ captionTranslatorHint?: string;
344
+ environment?: 'plasma' | 'gnome' | 'cli';
345
+ type?: 'default' | 'source';
346
+ }>;
347
+ /** Light/dark accent colours (hex `#rrggbb`) — emit `<branding>` block. */
348
+ branding?: {
349
+ accentLight: string;
350
+ accentDark: string;
351
+ };
352
+ /**
353
+ * Path to a scalable SVG icon. Flathub requires SVG (`/app/share/icons/
354
+ * hicolor/scalable/apps/<app-id>.svg`). When set, init verifies the file
355
+ * exists; when unset on `--kind app`, init prints a Flathub hint.
356
+ */
357
+ icon?: string;
358
+ /**
359
+ * Remote-hosted icon URL — emitted as `<icon type="remote">`. Useful
360
+ * for the Flathub app-store thumbnail before the local SVG ships.
361
+ */
362
+ iconRemote?: string;
363
+ /**
364
+ * Translation platform URL (Weblate, Crowdin, Transifex, etc.).
365
+ * Emitted as `<url type="translate">`. Set this when your app accepts
366
+ * community translation contributions through a hosted platform.
367
+ */
368
+ translateUrl?: string;
369
+ /**
370
+ * AppStream kudos — Flathub recognises a fixed set of "well-behaved"
371
+ * markers. Common values: `ModernToolkit`, `HiDpiIcon`,
372
+ * `TouchscreenSupport`, `UserDocs`, `HighContrast`, `Notifications`,
373
+ * `SearchProvider`. Full list at
374
+ * https://www.freedesktop.org/software/appstream/docs/sect-Metadata-DesktopApps.html#tag-dapp-kudos
375
+ */
376
+ kudos?: string[];
377
+ /**
378
+ * Things this app provides to the system. `<binary>` is auto-included
379
+ * with the value of `command` when omitted (apps + CLIs both need
380
+ * this for AppStream to register the binary correctly).
381
+ */
382
+ provides?: {
383
+ binaries?: string[];
384
+ mimetypes?: string[];
385
+ dbus?: Array<{
386
+ type: 'user' | 'system';
387
+ id: string;
388
+ }>;
389
+ };
390
+ /**
391
+ * Hardware controls the app supports (best-effort declaration —
392
+ * AppStream `<supports>`). Common values:
393
+ * `keyboard`, `pointing`, `touch`, `gamepad`, `tablet`, `console`,
394
+ * `vision`.
395
+ */
396
+ supports?: {
397
+ controls?: Array<'keyboard' | 'pointing' | 'touch' | 'gamepad' | 'tablet' | 'console' | 'vision'>;
398
+ /** Internet connectivity requirement. */
399
+ internet?: 'always' | 'offline-only' | 'first-run';
400
+ };
401
+ /**
402
+ * Hard requirements — AppStream `<requires>`. App won't function
403
+ * without these. `displayLengthMin` is the minimum display length in
404
+ * pixels (logical units) — typical phone-portrait minimum is 360.
405
+ */
406
+ requires?: {
407
+ displayLengthMin?: number;
408
+ internet?: 'always' | 'offline-only' | 'first-run';
409
+ controls?: Array<'keyboard' | 'pointing' | 'touch' | 'gamepad' | 'tablet' | 'console'>;
410
+ };
411
+ /**
412
+ * Soft recommendations — AppStream `<recommends>`. App works better
413
+ * with these but functions without them. `displayLengthMin` typical
414
+ * tablet-min recommendation is 480.
415
+ */
416
+ recommends?: {
417
+ displayLengthMin?: number;
418
+ controls?: Array<'keyboard' | 'pointing' | 'touch' | 'gamepad' | 'tablet' | 'console'>;
419
+ };
236
420
  }
421
+ /**
422
+ * A single block inside a MetaInfo `<description>`. Either a paragraph
423
+ * (`{p}`) or a bullet list (`{ul}`). Each block can carry an optional
424
+ * `translatorHint` that becomes a `<!-- TRANSLATORS: ... -->` comment
425
+ * before the block in the emitted `.metainfo.xml.in` template — gives
426
+ * translators context when the string lands in their `.po` file.
427
+ */
428
+ export type DescriptionBlock = {
429
+ p: string;
430
+ translatorHint?: string;
431
+ } | {
432
+ ul: Array<string | {
433
+ item: string;
434
+ translatorHint?: string;
435
+ }>;
436
+ translatorHint?: string;
437
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -23,7 +23,7 @@
23
23
  "clear": "rm -rf lib dist tsconfig.tsbuildinfo || exit 0",
24
24
  "check": "tsc --noEmit",
25
25
  "start": "node lib/index.js",
26
- "build": "tsc && mkdir -p lib/templates && cp -L src/templates/install.mjs.tmpl lib/templates/install.mjs.tmpl && gjsify run chmod",
26
+ "build": "tsc && mkdir -p lib/templates/flatpak && cp -L src/templates/install.mjs.tmpl lib/templates/install.mjs.tmpl && cp src/templates/flatpak/*.tmpl lib/templates/flatpak/ && gjsify run chmod",
27
27
  "build:gjs-bundle": "node lib/index.js build src/index.ts --app gjs --outfile dist/cli.gjs.mjs --shebang",
28
28
  "chmod": "chmod +x ./lib/index.js",
29
29
  "build:test:node": "node lib/index.js build src/test.mts --app node --outfile dist/test.node.mjs",
@@ -37,18 +37,18 @@
37
37
  "cli"
38
38
  ],
39
39
  "dependencies": {
40
- "@gjsify/buffer": "^0.4.11",
41
- "@gjsify/create-app": "^0.4.11",
42
- "@gjsify/node-globals": "^0.4.11",
43
- "@gjsify/node-polyfills": "^0.4.11",
44
- "@gjsify/npm-registry": "^0.4.11",
45
- "@gjsify/resolve-npm": "^0.4.11",
46
- "@gjsify/rolldown-plugin-gjsify": "^0.4.11",
47
- "@gjsify/rolldown-plugin-pnp": "^0.4.11",
48
- "@gjsify/semver": "^0.4.11",
49
- "@gjsify/tar": "^0.4.11",
50
- "@gjsify/web-polyfills": "^0.4.11",
51
- "@gjsify/workspace": "^0.4.11",
40
+ "@gjsify/buffer": "^0.4.13",
41
+ "@gjsify/create-app": "^0.4.13",
42
+ "@gjsify/node-globals": "^0.4.13",
43
+ "@gjsify/node-polyfills": "^0.4.13",
44
+ "@gjsify/npm-registry": "^0.4.13",
45
+ "@gjsify/resolve-npm": "^0.4.13",
46
+ "@gjsify/rolldown-plugin-gjsify": "^0.4.13",
47
+ "@gjsify/rolldown-plugin-pnp": "^0.4.13",
48
+ "@gjsify/semver": "^0.4.13",
49
+ "@gjsify/tar": "^0.4.13",
50
+ "@gjsify/web-polyfills": "^0.4.13",
51
+ "@gjsify/workspace": "^0.4.13",
52
52
  "cosmiconfig": "^9.0.1",
53
53
  "get-tsconfig": "^4.14.0",
54
54
  "pkg-types": "^2.3.1",
@@ -56,12 +56,12 @@
56
56
  "yargs": "^18.0.0"
57
57
  },
58
58
  "devDependencies": {
59
- "@gjsify/unit": "^0.4.11",
59
+ "@gjsify/unit": "^0.4.13",
60
60
  "@types/yargs": "^17.0.35",
61
61
  "typescript": "^6.0.3"
62
62
  },
63
63
  "peerDependencies": {
64
- "@gjsify/rolldown-native": "^0.4.11"
64
+ "@gjsify/rolldown-native": "^0.4.13"
65
65
  },
66
66
  "peerDependenciesMeta": {
67
67
  "@gjsify/rolldown-native": {