Skip to content

Commit 1553c76

Browse files
authored
Generate Flatpak manifest (#121)
Co-authored-by: Semy Ingle <Photon101@users.noreply.github.com>
1 parent 48de09b commit 1553c76

2 files changed

Lines changed: 136 additions & 6 deletions

File tree

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,65 @@
1-
import { smokeTest } from '@profullstack/sh1pt-core/testing';
1+
import { fakeBuildContext, fakeShipContext, smokeTest } from '@profullstack/sh1pt-core/testing';
2+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
3+
import { tmpdir } from 'node:os';
4+
import { join } from 'node:path';
5+
import { afterEach, describe, expect, it } from 'vitest';
26
import adapter from './index.js';
37

48
smokeTest(adapter, { idPrefix: 'pkg', requireKind: true });
9+
10+
const tempDirs: string[] = [];
11+
12+
afterEach(async () => {
13+
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
14+
});
15+
16+
describe('Flatpak manifest generation', () => {
17+
it('writes a flatpak-builder manifest from release config', async () => {
18+
const outDir = await mkdtemp(join(tmpdir(), 'sh1pt-flatpak-'));
19+
tempDirs.push(outDir);
20+
21+
const result = await adapter.build(fakeBuildContext({
22+
outDir,
23+
projectDir: '/repo/myapp',
24+
version: '1.2.3',
25+
channel: 'stable',
26+
}) as any, {
27+
appId: 'com.example.MyApp',
28+
runtime: 'org.freedesktop.Platform',
29+
runtimeVersion: '24.08',
30+
sdk: 'org.freedesktop.Sdk',
31+
sdkExtensions: ['org.freedesktop.Sdk.Extension.node22'],
32+
command: 'myapp',
33+
moduleName: 'myapp',
34+
buildCommands: ['install -D myapp "$FLATPAK_DEST/bin/myapp"'],
35+
sourceUrl: 'https://downloads.example.com/myapp-1.2.3.tar.gz',
36+
sourceSha256: 'a'.repeat(64),
37+
finishArgs: ['--share=network', '--filesystem=home:ro'],
38+
});
39+
40+
expect(result.artifact).toBe(join(outDir, 'com.example.MyApp.yml'));
41+
const manifest = await readFile(result.artifact, 'utf-8');
42+
43+
expect(manifest).toContain('app-id: "com.example.MyApp"');
44+
expect(manifest).toContain('runtime-version: "24.08"');
45+
expect(manifest).toContain('sdk: "org.freedesktop.Sdk"');
46+
expect(manifest).toContain('command: "myapp"');
47+
expect(manifest).toContain('branch: "stable"');
48+
expect(manifest).toContain(' - "org.freedesktop.Sdk.Extension.node22"');
49+
expect(manifest).toContain(' - "--filesystem=home:ro"');
50+
expect(manifest).toContain('buildsystem: "simple"');
51+
expect(manifest).toContain(' - "install -D myapp \\"$FLATPAK_DEST/bin/myapp\\""');
52+
expect(manifest).toContain(' - type: archive');
53+
expect(manifest).toContain('url: "https://downloads.example.com/myapp-1.2.3.tar.gz"');
54+
expect(manifest).toContain(`sha256: "${'a'.repeat(64)}"`);
55+
});
56+
57+
it('keeps dry-run shipping side-effect free', async () => {
58+
await expect(adapter.ship(fakeShipContext({
59+
version: '1.2.3',
60+
dryRun: true,
61+
}) as any, {
62+
appId: 'com.example.MyApp',
63+
})).resolves.toEqual({ id: 'dry-run' });
64+
});
65+
});

packages/targets/pkg-flatpak/src/index.ts

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,82 @@
11
import { defineTarget, manualSetup } from '@profullstack/sh1pt-core';
2+
import { mkdir, writeFile } from 'node:fs/promises';
3+
import { join } from 'node:path';
24

35
interface Config {
46
appId: string; // Reverse-DNS app ID, e.g. "com.example.MyApp"
57
branch?: 'stable' | 'beta';
68
runtime?: string; // e.g. "org.freedesktop.Platform"
79
runtimeVersion?: string; // e.g. "23.08"
10+
sdk?: string; // e.g. "org.freedesktop.Sdk"
811
sdkExtensions?: string[]; // e.g. ["org.freedesktop.Sdk.Extension.node20"]
912
flathubRepo?: string; // defaults to "https://github.com/flathub/flathub"
13+
command?: string;
14+
moduleName?: string;
15+
buildsystem?: 'simple' | 'meson' | 'cmake' | 'autotools' | string;
16+
buildCommands?: string[];
17+
sourceUrl?: string;
18+
sourceSha256?: string;
19+
finishArgs?: string[];
20+
}
21+
22+
function yamlString(value: string): string {
23+
return JSON.stringify(value);
24+
}
25+
26+
function renderList(values: string[], indent: string): string[] {
27+
return values.map((value) => `${indent}- ${yamlString(value)}`);
28+
}
29+
30+
function renderFlatpakManifest(ctx: { projectDir: string; version: string; channel: string }, config: Config): string {
31+
const branch = config.branch ?? (ctx.channel === 'stable' ? 'stable' : 'beta');
32+
const runtime = config.runtime ?? 'org.freedesktop.Platform';
33+
const runtimeVersion = config.runtimeVersion ?? '23.08';
34+
const sdk = config.sdk ?? 'org.freedesktop.Sdk';
35+
const command = config.command ?? config.appId.split('.').at(-1) ?? config.appId;
36+
const moduleName = config.moduleName ?? command;
37+
const buildsystem = config.buildsystem ?? 'simple';
38+
const buildCommands = config.buildCommands ?? ['install -D app "$FLATPAK_DEST/bin/app"'];
39+
const finishArgs = config.finishArgs ?? ['--share=network'];
40+
const sourceUrl = config.sourceUrl ?? ctx.projectDir;
41+
const lines = [
42+
`app-id: ${yamlString(config.appId)}`,
43+
`runtime: ${yamlString(runtime)}`,
44+
`runtime-version: ${yamlString(runtimeVersion)}`,
45+
`sdk: ${yamlString(sdk)}`,
46+
`command: ${yamlString(command)}`,
47+
`branch: ${yamlString(branch)}`,
48+
];
49+
50+
if (config.sdkExtensions?.length) {
51+
lines.push('sdk-extensions:');
52+
lines.push(...renderList(config.sdkExtensions, ' '));
53+
}
54+
55+
if (finishArgs.length) {
56+
lines.push('finish-args:');
57+
lines.push(...renderList(finishArgs, ' '));
58+
}
59+
60+
lines.push('modules:');
61+
lines.push(` - name: ${yamlString(moduleName)}`);
62+
lines.push(` buildsystem: ${yamlString(buildsystem)}`);
63+
lines.push(' build-commands:');
64+
lines.push(...renderList(buildCommands, ' '));
65+
lines.push(' sources:');
66+
67+
if (config.sourceUrl) {
68+
lines.push(' - type: archive');
69+
lines.push(` url: ${yamlString(sourceUrl)}`);
70+
if (config.sourceSha256) {
71+
lines.push(` sha256: ${yamlString(config.sourceSha256)}`);
72+
}
73+
} else {
74+
lines.push(' - type: dir');
75+
lines.push(` path: ${yamlString(sourceUrl)}`);
76+
}
77+
78+
lines.push('');
79+
return lines.join('\n');
1080
}
1181

1282
export default defineTarget<Config>({
@@ -17,14 +87,13 @@ export default defineTarget<Config>({
1787
const branch = config.branch ?? (ctx.channel === 'stable' ? 'stable' : 'beta');
1888
const runtime = config.runtime ?? 'org.freedesktop.Platform';
1989
const runtimeVersion = config.runtimeVersion ?? '23.08';
90+
const manifestPath = join(ctx.outDir, `${config.appId}.yml`);
2091
ctx.log(`render ${config.appId}.yml manifest for v${ctx.version} (branch: ${branch})`);
2192
ctx.log(`runtime: ${runtime}//${runtimeVersion}`);
22-
// TODO: render Flatpak manifest YAML from template:
23-
// app-id: ${appId} runtime: ${runtime} runtime-version: ${runtimeVersion}
24-
// sdk-extensions: ${sdkExtensions}
25-
// modules: [{ name: <appName>, sources: [{ type: archive, url: ..., sha256: ... }] }]
93+
await mkdir(ctx.outDir, { recursive: true });
94+
await writeFile(manifestPath, renderFlatpakManifest(ctx, config), 'utf-8');
2695
// TODO: run `flatpak-builder --repo=repo --force-clean builddir ${appId}.yml`
27-
return { artifact: `${ctx.outDir}/${config.appId}.flatpak` };
96+
return { artifact: manifestPath };
2897
},
2998
async ship(ctx, config) {
3099
const branch = config.branch ?? (ctx.channel === 'stable' ? 'stable' : 'beta');

0 commit comments

Comments
 (0)