Skip to content

Commit c36695c

Browse files
authored
Merge pull request #491 from romgrk/flathub-mariner-release
feat: flatpak desktop-integration discovery + Flathub-clean release layout
2 parents 8438bf1 + 3162eca commit c36695c

8 files changed

Lines changed: 288 additions & 55 deletions

File tree

.github/workflows/flatpak-build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
- uses: actions/cache@v4
3131
with:
3232
path: ~/.local/share/flatpak
33-
key: flatpak-org.gnome.Sdk-49-node24-v1
33+
key: flatpak-org.gnome.Sdk-50-node24-v1
3434

3535
# Staging only needs the JS dependency tree (node-gtk ships its compile
3636
# inputs; the addon builds inside the flatpak sandbox), so no host GTK

doc/bundling.md

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -177,19 +177,25 @@ The shared `"bundle"` key, plus a `"flatpak"` sub-key:
177177
```jsonc
178178
"bundle": {
179179
"name": "MyApp",
180-
"id": "com.example.MyApp", // MUST be your GApplication application-id
180+
"id": "io.github.you.myapp", // MUST be your GApplication application-id;
181+
// Flathub requires io.github.*/io.gitlab.*
182+
// for code-hosting-based ids
181183
"summary": "Does the thing", // .desktop comment + AppStream summary
182-
"icon": "assets/icon.svg", // svg or png; required by Flathub
183184
"license": "MIT", // SPDX, defaults to package.json "license"
184185
"categories": ["GTK", "Utility"],
185186
"flatpak": {
186-
"runtimeVersion": "49", // org.gnome.Platform version (50 is also current;
187-
// pick one and test against it)
187+
"runtimeVersion": "50", // org.gnome.Platform version — pin the one
188+
// you tested against
188189
"node": 26, // SDK extension major (20/22/24/26)
189190
"finishArgs": [ // sandbox permissions beyond the GUI defaults
190191
"--share=network",
191192
"--filesystem=home"
192-
]
193+
],
194+
"lintExceptions": { // linter errors your app stands by; each one
195+
// must be requested (with this justification)
196+
// in the Flathub submission PR
197+
"finish-args-host-filesystem-access": "MyApp is a file manager; …"
198+
}
193199
}
194200
}
195201
```
@@ -203,6 +209,24 @@ Defaults grant only GUI access (`wayland`, `fallback-x11`, `ipc`, `dri`) —
203209
network and filesystem are deliberately opt-in; request the minimum, Flathub
204210
reviews it.
205211

212+
## Desktop integration files
213+
214+
Ship your own files at the conventional locations and they are used as-is;
215+
anything missing gets a minimal generated stub:
216+
217+
- `data/<id>.desktop` — copied with only `Exec=` rewritten to the sandbox
218+
command, so one file serves host installs and the flatpak. Keep
219+
`Icon=<id>`: flatpak only exports icons named after the app id.
220+
- `data/<id>.metainfo.xml` — copied verbatim. The generated stub passes
221+
validation, but Flathub review wants a real description, screenshots and
222+
release notes only you can write — start from the stub in `dist/flatpak/`.
223+
- `data/icons/hicolor/<size>/apps/<id>.(svg|png)` — the whole theme tree
224+
ships (sized, scalable and symbolic variants). Single-file alternative:
225+
`"bundle": { "icon": "assets/icon.svg" }`.
226+
227+
Override the discovery with `"bundle": { "desktopFile": …, "metainfo": …,
228+
"iconsDir": … }` when your layout differs.
229+
206230
Three things that bite:
207231

208232
- **The flatpak id must equal your `Gtk.Application` `applicationId`**
@@ -231,19 +255,37 @@ npx node-gtk flatpak --release --lint
231255

232256
- `<Name>-<version>-flatpak-src.tar.gz` — the staged sources (app +
233257
production node_modules + desktop files) as one tarball
234-
- `<id>.flathub.yml` — the manifest referencing that tarball by URL + sha256
258+
- `flathub/<id>.yml` — the manifest referencing that tarball by URL + sha256
235259
(URL derived from package.json `"repository"`:
236260
`https://github.com/<you>/<app>/releases/download/v<version>/<tarball>`;
237-
override with `--release-url`)
238-
239-
The flow: **1.** fix everything `--lint` reports (the metainfo TODOs —
240-
description, screenshots, releases, content rating — and a real icon);
241-
**2.** create the GitHub release `v<version>` and upload the tarball;
242-
**3.** submit `<id>.flathub.yml` via PR to
243-
[flathub/flathub](https://github.com/flathub/flathub). After acceptance,
244-
users find the app in GNOME Software and every update ships automatically —
245-
new releases are a version bump + new tarball + manifest update in your
246-
Flathub repo.
261+
override with `--release-url`). The `flathub/` directory is exactly what
262+
the submission PR contains, and the manifest is named `<id>.yml` because
263+
the linter requires it.
264+
265+
The flow:
266+
267+
1. Fix everything `--lint` reports. Common gates: the app id — Flathub
268+
rejects `com.github.*`; a GitHub-hosted app must use `io.github.<you>.<app>`
269+
(and `applicationId` must match) — and broad permissions like
270+
`--filesystem=host`, which are declared in `"lintExceptions"` with a
271+
justification and granted per-app during review.
272+
2. Create the GitHub release `v<version>` and upload the tarball:
273+
`gh release create v<version> dist/flatpak/<tarball>`.
274+
3. Fork [flathub/flathub](https://github.com/flathub/flathub), branch off
275+
`new-pr` (not master), add the contents of `dist/flatpak/flathub/`, and
276+
open a PR against `new-pr`. Request any `lintExceptions` in the PR
277+
description with their justifications.
278+
279+
After acceptance, users find the app in GNOME Software and every update
280+
ships automatically — new releases are a version bump + new tarball +
281+
manifest update in your Flathub repo.
282+
283+
One caveat if you run the deepest check locally
284+
(`flatpak-builder-lint repo dist/flatpak/repo`): it reports
285+
`appstream-screenshots-not-mirrored-in-ostree` /
286+
`appstream-external-screenshot-url`. Both are expected outside Flathub's
287+
infrastructure — their pipeline mirrors screenshots into
288+
`dl.flathub.org/media` automatically.
247289

248290
Alternative without Flathub: host your own flatpak repository (an ostree
249291
repo is static files — GitHub Pages works) and point users at a `.flatpakref`;

scripts/flatpak-smoke-test.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,24 @@ process.exit(0)
5858
fs.mkdirSync(path.join(appDir, 'node_modules'))
5959
fs.symlinkSync(repoRoot, path.join(appDir, 'node_modules', 'node-gtk'), 'dir')
6060

61+
// App-provided desktop-integration files at the conventional data/ locations:
62+
// the metainfo and icon theme must be discovered and used instead of stubs
63+
// (the missing .desktop still exercises the generated-stub path).
64+
fs.mkdirSync(path.join(appDir, 'data', 'icons', 'hicolor', 'scalable', 'apps'), { recursive: true })
65+
fs.writeFileSync(path.join(appDir, 'data', `${id}.metainfo.xml`), `<?xml version="1.0" encoding="UTF-8"?>
66+
<component type="desktop-application">
67+
<id>${id}</id>
68+
<metadata_license>CC0-1.0</metadata_license>
69+
<project_license>MIT</project_license>
70+
<name>FlatpakSmoke</name>
71+
<summary>Flatpak generation smoke test</summary>
72+
<description><p>SMOKE_MARKER_APP_PROVIDED_METAINFO</p></description>
73+
<launchable type="desktop-id">${id}.desktop</launchable>
74+
</component>
75+
`)
76+
fs.writeFileSync(path.join(appDir, 'data', 'icons', 'hicolor', 'scalable', 'apps', `${id}.svg`),
77+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect width="16" height="16"/></svg>\n')
78+
6179
const outDir = path.join(tmp, 'out')
6280
const args = [path.join(repoRoot, 'bin', 'node-gtk.js'), 'flatpak', appDir, '--out', outDir, '--release']
6381
if (!full)
@@ -80,9 +98,12 @@ const checks = [
8098
// the gi: import scheme must work out of the box → loader registered
8199
['launcher.sh', content => content.includes('exec /app/bin/node --import node-gtk/register')],
82100
[`${id}.desktop`, content => content.includes('Exec=' + id)],
83-
[`${id}.metainfo.xml`, content => content.includes(`<id>${id}</id>`)],
84-
// --release: Flathub manifest referencing the tarball by url + sha256
85-
[`${id}.flathub.yml`, content => /type: archive/.test(content) && /sha256: [0-9a-f]{64}/.test(content)],
101+
// app-provided data/ files must be used instead of generated stubs
102+
[`${id}.metainfo.xml`, content => content.includes('SMOKE_MARKER_APP_PROVIDED_METAINFO')],
103+
[`icons/hicolor/scalable/apps/${id}.svg`, undefined],
104+
// --release: Flathub manifest (named <id>.yml for the linter) referencing
105+
// the tarball by url + sha256
106+
[`flathub/${id}.yml`, content => /type: archive/.test(content) && /sha256: [0-9a-f]{64}/.test(content)],
86107
['FlatpakSmoke-1.0.0-flatpak-src.tar.gz', undefined],
87108
// the staged tree must be COMPILABLE in the sandbox
88109
['app/node_modules/node-gtk/binding.gyp', undefined],

tools/bundle.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,17 @@ function loadConfig(appDir, flags) {
165165
repository: typeof pkg.repository === 'string' ? pkg.repository : (pkg.repository || {}).url,
166166
summary: raw.summary || `The ${name} application`,
167167
license: raw.license || pkg.license,
168+
author: pkg.author,
168169
icon: raw.icon,
169-
categories: raw.categories || ['GTK'],
170+
// App-provided desktop-integration files (flatpak): explicit paths here,
171+
// else discovered at the conventional data/ locations.
172+
desktopFile: raw.desktopFile,
173+
metainfo: raw.metainfo,
174+
iconsDir: raw.iconsDir,
175+
// The default needs a valid MAIN category (Utility): appstreamcli
176+
// compose, which flatpak-builder runs, rejects the app with
177+
// no-valid-category otherwise — GTK alone is additional-only.
178+
categories: raw.categories || ['Utility', 'GTK'],
170179
gtk: raw.gtk,
171180
entry: path.normalize(entry),
172181
include: raw.include || ['**/*'],
@@ -183,9 +192,14 @@ function loadConfig(appDir, flags) {
183192
out: path.resolve(appDir, flags.out || raw.out
184193
|| path.join('dist', `${name}-${process.platform}-${process.arch}`)),
185194
flatpak: {
186-
runtimeVersion: String(flatpakRaw.runtimeVersion || '49'),
195+
runtimeVersion: String(flatpakRaw.runtimeVersion || '50'),
187196
node: Number(flatpakRaw.node || defaultFlatpakNode()),
188197
finishArgs: flatpakRaw.finishArgs || [],
198+
// { "<linter-check>": "<justification>" } — permissions the app stands
199+
// by (a file manager needs --filesystem=host). Tolerated by --lint via
200+
// --user-exceptions; each needs the justification restated in the
201+
// Flathub submission PR.
202+
lintExceptions: flatpakRaw.lintExceptions || {},
189203
},
190204
}
191205

tools/create-app.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ const TEMPLATE_DIR = path.join(__dirname, 'templates', 'app')
1717
// template file -> destination path (relative to the new project root).
1818
// Templates carry a `.tmpl` suffix so npm never rewrites `.gitignore` to
1919
// `.npmignore` and never treats a nested `package.json` as a real manifest.
20+
// Destinations get token substitution too: the icon lands at the hicolor
21+
// location `node-gtk flatpak` discovers (and freedesktop tooling expects),
22+
// named after the app id.
2023
const FILES = [
2124
['package.json.tmpl', 'package.json'],
2225
['tsconfig.json.tmpl', 'tsconfig.json'],
@@ -25,7 +28,7 @@ const FILES = [
2528
['style.css.tmpl', 'style.css'],
2629
['src/main.ts.tmpl', path.join('src', 'main.ts')],
2730
['src/welcome.ts.tmpl', path.join('src', 'welcome.ts')],
28-
['data/icon.svg.tmpl', path.join('data', 'icon.svg')],
31+
['data/icon.svg.tmpl', path.join('data', 'icons', 'hicolor', 'scalable', 'apps', '__APP_ID__.svg')],
2932
]
3033

3134
// ---------------------------------------------------------------------------
@@ -105,10 +108,10 @@ function createProject(opts) {
105108
const written = []
106109
for (const [src, dest] of FILES) {
107110
const content = substitute(fs.readFileSync(path.join(TEMPLATE_DIR, src), 'utf8'))
108-
const destPath = path.join(dir, dest)
111+
const destPath = path.join(dir, substitute(dest))
109112
fs.mkdirSync(path.dirname(destPath), { recursive: true })
110113
fs.writeFileSync(destPath, content)
111-
written.push(dest)
114+
written.push(path.relative(dir, destPath))
112115
}
113116
return written
114117
}

0 commit comments

Comments
 (0)