@gjsify/cli 0.4.11 → 0.4.12

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,197 @@ 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
+ * Developer attribution required by Flathub. `id` must be reverse-DNS.
246
+ * `email` (optional) becomes `<email>` inside `<developer>`.
247
+ * `nameTranslatable: false` (default) emits `translate="no"` on the
248
+ * `<name>` tag — recommended for personal/brand names that should not
249
+ * be translated. Set to `true` if the name is a descriptive phrase
250
+ * that translators should localise.
251
+ */
252
+ developer?: {
253
+ id: string;
254
+ name: string;
255
+ email?: string;
256
+ nameTranslatable?: boolean;
257
+ };
258
+ /**
259
+ * One-line summary, ≤80 chars, no trailing period (Flathub rule).
260
+ * Translatable — gettext's `msgfmt --xml --template` substitutes
261
+ * `<summary>` from `.po` files at build time. Set
262
+ * `summaryTranslatorHint` to emit a `<!-- TRANSLATORS: ... -->`
263
+ * comment before the tag.
264
+ */
265
+ summary?: string;
266
+ /** Translator hint emitted as `<!-- TRANSLATORS: ... -->` before `<summary>`. */
267
+ summaryTranslatorHint?: string;
268
+ /**
269
+ * Long description. Two forms:
270
+ * - **String** — split on blank lines into `<p>` blocks. Best for
271
+ * simple descriptions without bullet lists.
272
+ * - **Block array** — explicit blocks (`{p:..., translatorHint?:...}`
273
+ * for paragraphs or `{ul:[...], translatorHint?:...}` for bullet
274
+ * lists). Each block can carry its own translator hint. Use this
275
+ * form when you need bullet lists or per-string translator context.
276
+ */
277
+ description?: string | DescriptionBlock[];
278
+ /** Project homepage URL. Recommended; required for Flathub submission. */
279
+ homepageUrl?: string;
280
+ /** Bug tracker URL. */
281
+ bugtrackerUrl?: string;
282
+ /** VCS browser URL (e.g. GitHub repo). */
283
+ vcsBrowserUrl?: string;
284
+ /** Donation URL (e.g. OpenCollective / GitHub Sponsors). */
285
+ donationUrl?: string;
286
+ /**
287
+ * License SPDX identifiers. `project` is the project's source license
288
+ * (mandatory). `metadata` is the license under which the MetaInfo XML
289
+ * is distributed (default `'CC0-1.0'`).
290
+ */
291
+ license?: {
292
+ metadata?: string;
293
+ project: string;
294
+ };
295
+ /**
296
+ * Content-rating policy. Two forms:
297
+ * - **String** — just the spec keyword (default `'oars-1.1'`), emits
298
+ * an empty `<content_rating type="..."/>` block.
299
+ * - **Object** — keyword + `attributes` map. Each attribute is an
300
+ * OARS key (`violence-cartoon`, `social-info`, etc.) → severity
301
+ * (`none`, `mild`, `moderate`, `intense`). Flathub recommends
302
+ * declaring attributes explicitly even when they're `none` so the
303
+ * rating audit is auditable.
304
+ */
305
+ contentRating?: string | {
306
+ type?: string;
307
+ attributes?: Record<string, 'none' | 'mild' | 'moderate' | 'intense'>;
308
+ };
309
+ /** Freedesktop Menu categories (e.g. `['Development', 'Utility']`). */
310
+ categories?: string[];
311
+ /** Search keywords for app stores. */
312
+ keywords?: string[];
313
+ /**
314
+ * Release history. Most recent first. Each entry produces a
315
+ * `<release version=… date=…>` block. `description` accepts the
316
+ * same string-or-block-array shape as the top-level `description`
317
+ * field — use the array form for release notes with bullet lists
318
+ * or per-string translator hints.
319
+ */
320
+ releases?: Array<{
321
+ version: string;
322
+ date: string;
323
+ description?: string | DescriptionBlock[];
324
+ }>;
325
+ /**
326
+ * Screenshots for app-stores. `url` is an absolute HTTPS URL to a PNG.
327
+ * `caption` is optional and translatable — set `captionTranslatorHint`
328
+ * for a `<!-- TRANSLATORS: ... -->` hint. `environment` is one of
329
+ * `'plasma'|'gnome'|'cli'` — Flathub uses it to group by desktop.
330
+ * First entry defaults to `type="default"`; override with `type`.
331
+ */
332
+ screenshots?: Array<{
333
+ url: string;
334
+ caption?: string;
335
+ captionTranslatorHint?: string;
336
+ environment?: 'plasma' | 'gnome' | 'cli';
337
+ type?: 'default' | 'source';
338
+ }>;
339
+ /** Light/dark accent colours (hex `#rrggbb`) — emit `<branding>` block. */
340
+ branding?: {
341
+ accentLight: string;
342
+ accentDark: string;
343
+ };
344
+ /**
345
+ * Path to a scalable SVG icon. Flathub requires SVG (`/app/share/icons/
346
+ * hicolor/scalable/apps/<app-id>.svg`). When set, init verifies the file
347
+ * exists; when unset on `--kind app`, init prints a Flathub hint.
348
+ */
349
+ icon?: string;
350
+ /**
351
+ * Remote-hosted icon URL — emitted as `<icon type="remote">`. Useful
352
+ * for the Flathub app-store thumbnail before the local SVG ships.
353
+ */
354
+ iconRemote?: string;
355
+ /**
356
+ * Translation platform URL (Weblate, Crowdin, Transifex, etc.).
357
+ * Emitted as `<url type="translate">`. Set this when your app accepts
358
+ * community translation contributions through a hosted platform.
359
+ */
360
+ translateUrl?: string;
361
+ /**
362
+ * AppStream kudos — Flathub recognises a fixed set of "well-behaved"
363
+ * markers. Common values: `ModernToolkit`, `HiDpiIcon`,
364
+ * `TouchscreenSupport`, `UserDocs`, `HighContrast`, `Notifications`,
365
+ * `SearchProvider`. Full list at
366
+ * https://www.freedesktop.org/software/appstream/docs/sect-Metadata-DesktopApps.html#tag-dapp-kudos
367
+ */
368
+ kudos?: string[];
369
+ /**
370
+ * Things this app provides to the system. `<binary>` is auto-included
371
+ * with the value of `command` when omitted (apps + CLIs both need
372
+ * this for AppStream to register the binary correctly).
373
+ */
374
+ provides?: {
375
+ binaries?: string[];
376
+ mimetypes?: string[];
377
+ dbus?: Array<{
378
+ type: 'user' | 'system';
379
+ id: string;
380
+ }>;
381
+ };
382
+ /**
383
+ * Hardware controls the app supports (best-effort declaration —
384
+ * AppStream `<supports>`). Common values:
385
+ * `keyboard`, `pointing`, `touch`, `gamepad`, `tablet`, `console`,
386
+ * `vision`.
387
+ */
388
+ supports?: {
389
+ controls?: Array<'keyboard' | 'pointing' | 'touch' | 'gamepad' | 'tablet' | 'console' | 'vision'>;
390
+ /** Internet connectivity requirement. */
391
+ internet?: 'always' | 'offline-only' | 'first-run';
392
+ };
393
+ /**
394
+ * Hard requirements — AppStream `<requires>`. App won't function
395
+ * without these. `displayLengthMin` is the minimum display length in
396
+ * pixels (logical units) — typical phone-portrait minimum is 360.
397
+ */
398
+ requires?: {
399
+ displayLengthMin?: number;
400
+ internet?: 'always' | 'offline-only' | 'first-run';
401
+ controls?: Array<'keyboard' | 'pointing' | 'touch' | 'gamepad' | 'tablet' | 'console'>;
402
+ };
403
+ /**
404
+ * Soft recommendations — AppStream `<recommends>`. App works better
405
+ * with these but functions without them. `displayLengthMin` typical
406
+ * tablet-min recommendation is 480.
407
+ */
408
+ recommends?: {
409
+ displayLengthMin?: number;
410
+ controls?: Array<'keyboard' | 'pointing' | 'touch' | 'gamepad' | 'tablet' | 'console'>;
411
+ };
236
412
  }
413
+ /**
414
+ * A single block inside a MetaInfo `<description>`. Either a paragraph
415
+ * (`{p}`) or a bullet list (`{ul}`). Each block can carry an optional
416
+ * `translatorHint` that becomes a `<!-- TRANSLATORS: ... -->` comment
417
+ * before the block in the emitted `.metainfo.xml.in` template — gives
418
+ * translators context when the string lands in their `.po` file.
419
+ */
420
+ export type DescriptionBlock = {
421
+ p: string;
422
+ translatorHint?: string;
423
+ } | {
424
+ ul: Array<string | {
425
+ item: string;
426
+ translatorHint?: string;
427
+ }>;
428
+ translatorHint?: string;
429
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.4.11",
3
+ "version": "0.4.12",
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.12",
41
+ "@gjsify/create-app": "^0.4.12",
42
+ "@gjsify/node-globals": "^0.4.12",
43
+ "@gjsify/node-polyfills": "^0.4.12",
44
+ "@gjsify/npm-registry": "^0.4.12",
45
+ "@gjsify/resolve-npm": "^0.4.12",
46
+ "@gjsify/rolldown-plugin-gjsify": "^0.4.12",
47
+ "@gjsify/rolldown-plugin-pnp": "^0.4.12",
48
+ "@gjsify/semver": "^0.4.12",
49
+ "@gjsify/tar": "^0.4.12",
50
+ "@gjsify/web-polyfills": "^0.4.12",
51
+ "@gjsify/workspace": "^0.4.12",
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.12",
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.12"
65
65
  },
66
66
  "peerDependenciesMeta": {
67
67
  "@gjsify/rolldown-native": {