@gjsify/cli 0.3.21 → 0.4.3

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.
Files changed (69) hide show
  1. package/dist/cli.gjs.mjs +791 -0
  2. package/lib/actions/build.js +4 -17
  3. package/lib/bundler-pick.d.ts +79 -0
  4. package/lib/bundler-pick.js +436 -0
  5. package/lib/commands/foreach.d.ts +17 -0
  6. package/lib/commands/foreach.js +341 -0
  7. package/lib/commands/index.d.ts +2 -0
  8. package/lib/commands/index.js +2 -0
  9. package/lib/commands/install.d.ts +1 -0
  10. package/lib/commands/install.js +401 -27
  11. package/lib/commands/run.d.ts +1 -1
  12. package/lib/commands/run.js +113 -20
  13. package/lib/commands/workspace.d.ts +8 -0
  14. package/lib/commands/workspace.js +79 -0
  15. package/lib/config.js +12 -1
  16. package/lib/index.js +11 -3
  17. package/lib/types/config-data.d.ts +10 -1
  18. package/lib/utils/install-backend-native.d.ts +5 -1
  19. package/lib/utils/install-backend-native.js +329 -70
  20. package/lib/utils/install-backend.d.ts +11 -1
  21. package/lib/utils/install-backend.js +4 -2
  22. package/lib/utils/pkg-json-edit.d.ts +47 -0
  23. package/lib/utils/pkg-json-edit.js +108 -0
  24. package/lib/utils/workspace-root.d.ts +1 -0
  25. package/lib/utils/workspace-root.js +46 -0
  26. package/package.json +70 -44
  27. package/src/actions/build.ts +0 -431
  28. package/src/actions/index.ts +0 -1
  29. package/src/commands/build.ts +0 -146
  30. package/src/commands/check.ts +0 -87
  31. package/src/commands/create.ts +0 -63
  32. package/src/commands/dlx.ts +0 -195
  33. package/src/commands/flatpak/build.ts +0 -225
  34. package/src/commands/flatpak/ci.ts +0 -173
  35. package/src/commands/flatpak/deps.ts +0 -120
  36. package/src/commands/flatpak/index.ts +0 -53
  37. package/src/commands/flatpak/init.ts +0 -191
  38. package/src/commands/flatpak/utils.ts +0 -76
  39. package/src/commands/gettext.ts +0 -258
  40. package/src/commands/gresource.ts +0 -97
  41. package/src/commands/gsettings.ts +0 -87
  42. package/src/commands/index.ts +0 -12
  43. package/src/commands/info.ts +0 -70
  44. package/src/commands/install.ts +0 -195
  45. package/src/commands/run.ts +0 -33
  46. package/src/commands/showcase.ts +0 -149
  47. package/src/config.ts +0 -304
  48. package/src/constants.ts +0 -1
  49. package/src/index.ts +0 -37
  50. package/src/types/cli-build-options.ts +0 -100
  51. package/src/types/command.ts +0 -10
  52. package/src/types/config-data-library.ts +0 -5
  53. package/src/types/config-data-typescript.ts +0 -6
  54. package/src/types/config-data.ts +0 -225
  55. package/src/types/cosmiconfig-result.ts +0 -5
  56. package/src/types/index.ts +0 -6
  57. package/src/utils/check-system-deps.ts +0 -480
  58. package/src/utils/detect-native-packages.ts +0 -153
  59. package/src/utils/discover-showcases.ts +0 -75
  60. package/src/utils/dlx-cache.ts +0 -135
  61. package/src/utils/install-backend-native.ts +0 -363
  62. package/src/utils/install-backend.ts +0 -88
  63. package/src/utils/install-global.ts +0 -182
  64. package/src/utils/normalize-bundler-options.ts +0 -129
  65. package/src/utils/parse-spec.ts +0 -48
  66. package/src/utils/resolve-gjs-entry.ts +0 -96
  67. package/src/utils/resolve-plugin-by-name.ts +0 -106
  68. package/src/utils/run-gjs.ts +0 -90
  69. package/tsconfig.json +0 -16
@@ -1,63 +0,0 @@
1
- import { createProject, discoverTemplates } from '@gjsify/create-app';
2
- import { promptTemplate } from '@gjsify/create-app/prompt';
3
- import type { Command } from '../types/index.js';
4
-
5
- interface CreateOptions {
6
- 'project-name': string;
7
- template?: string;
8
- force: boolean;
9
- install: boolean;
10
- }
11
-
12
- export const createCommand: Command<any, CreateOptions> = {
13
- command: 'create [project-name]',
14
- description: 'Scaffold a new Gjsify project in a new directory.',
15
- builder: (yargs) => {
16
- const templates = discoverTemplates();
17
- const templateChoices = templates.map((t) => t.name);
18
- return yargs
19
- .positional('project-name', {
20
- describe: 'Name of the project directory to create',
21
- type: 'string',
22
- default: 'my-gjs-app',
23
- })
24
- .option('template', {
25
- alias: 't',
26
- describe: 'Template to scaffold from',
27
- type: 'string',
28
- choices: templateChoices.length > 0 ? templateChoices : undefined,
29
- })
30
- .option('force', {
31
- alias: 'f',
32
- describe: 'Scaffold into a non-empty directory',
33
- type: 'boolean',
34
- default: false,
35
- })
36
- .option('install', {
37
- describe: 'Run npm install after scaffolding',
38
- type: 'boolean',
39
- default: false,
40
- });
41
- },
42
- handler: async (args) => {
43
- let template = args.template;
44
- if (!template) {
45
- const templates = discoverTemplates();
46
- if (!process.stdin.isTTY) {
47
- const list = templates.map((t) => t.name).join(', ');
48
- console.error(
49
- `Error: --template is required in non-interactive mode. Available templates: ${list || '(none)'}`,
50
- );
51
- process.exit(1);
52
- }
53
- const picked = await promptTemplate(templates);
54
- template = picked.name;
55
- }
56
- await createProject({
57
- projectName: args['project-name'],
58
- template,
59
- force: args.force,
60
- install: args.install,
61
- });
62
- },
63
- };
@@ -1,195 +0,0 @@
1
- // `gjsify dlx <package> [bin] [-- args...]` — runs the GJS bundle of an
2
- // npm-published package without persisting it in the user's project.
3
- //
4
- // Cardinal rule: dlx is a **GJS-bundle runner**, not a generic bin runner.
5
- // It always invokes `gjs -m <bundle>` via the existing `runGjsBundle()` util.
6
- // Packages without a GJS entry (no `gjsify.main`/`gjsify.bin`, no fallback
7
- // `main`) fail loudly.
8
- //
9
- // Cache: $XDG_CACHE_HOME/gjsify/dlx/<sha256>/ with TTL (default 7d, override
10
- // via --cache-max-age=<minutes>). Cache hit on second run skips `npm install`
11
- // entirely. Layout + atomic-swap pattern adapted from pnpm's dlx implementation
12
- // (refs/pnpm/exec/commands/src/dlx.ts).
13
-
14
- import type { Command } from '../types/index.js';
15
- import { runGjsBundle } from '../utils/run-gjs.js';
16
- import { parseSpec, type ParsedSpec } from '../utils/parse-spec.js';
17
- import { resolveGjsEntry } from '../utils/resolve-gjs-entry.js';
18
- import {
19
- cacheDirFor,
20
- createCacheKey,
21
- getValidCachedPkg,
22
- makePrepareDir,
23
- resolveInstalledPkgDir,
24
- symlinkSwap,
25
- } from '../utils/dlx-cache.js';
26
- import { installPackages } from '../utils/install-backend.js';
27
-
28
- interface DlxOptions {
29
- spec: string;
30
- binOrArg?: string;
31
- extraArgs?: string[];
32
- 'cache-max-age': number;
33
- reinstall: boolean;
34
- frozen: boolean;
35
- verbose: boolean;
36
- registry?: string;
37
- }
38
-
39
- export const dlxCommand: Command<any, DlxOptions> = {
40
- command: 'dlx <spec> [binOrArg] [extraArgs..]',
41
- description:
42
- 'Run the GJS bundle of an npm-published package without installing it locally.',
43
- builder: (yargs) =>
44
- yargs
45
- .positional('spec', {
46
- description:
47
- 'Package spec (`name`, `name@version`, `@scope/name@spec`, or local path).',
48
- type: 'string',
49
- demandOption: true,
50
- })
51
- .positional('binOrArg', {
52
- description:
53
- 'Optional bin name when the package defines `gjsify.bin` with multiple entries; otherwise treated as the first argument forwarded to the bundle.',
54
- type: 'string',
55
- })
56
- .positional('extraArgs', {
57
- description: 'Extra args forwarded to `gjs -m <bundle>`.',
58
- type: 'string',
59
- array: true,
60
- })
61
- .option('cache-max-age', {
62
- description:
63
- 'Cache TTL in minutes. Defaults to 7 days. Use 0 to bypass cache.',
64
- type: 'number',
65
- default: 60 * 24 * 7,
66
- })
67
- .option('reinstall', {
68
- description:
69
- 'Bypass the cache for this run (alias for --cache-max-age=0).',
70
- type: 'boolean',
71
- default: false,
72
- })
73
- .option('frozen', {
74
- description:
75
- 'Use the project-local gjsify-lock.json verbatim — fail if missing or stale (no resolver pass).',
76
- type: 'boolean',
77
- default: false,
78
- })
79
- .option('verbose', {
80
- description: 'Verbose logging (passes --loglevel verbose to npm).',
81
- type: 'boolean',
82
- default: false,
83
- })
84
- .option('registry', {
85
- description: 'Registry URL override.',
86
- type: 'string',
87
- }),
88
- handler: async (args) => {
89
- const parsed = parseSpec(args.spec);
90
-
91
- const cacheMaxAge = args.reinstall ? 0 : args['cache-max-age'];
92
- const { pkgDir, cachedPkgName } = await ensurePkgDir(parsed, {
93
- verbose: args.verbose,
94
- registry: args.registry,
95
- cacheMaxAge,
96
- frozen: args.frozen,
97
- });
98
-
99
- // Bin / args disambiguation:
100
- // gjsify dlx <pkg> → no bin, no args
101
- // gjsify dlx <pkg> mybin → bin if package has gjsify.bin[mybin], else arg
102
- // gjsify dlx <pkg> mybin -- arg1 arg2 → bin + extra args
103
- // gjsify dlx <pkg> -- arg1 arg2 → no bin, extra args
104
- const { binName, extraArgs } = splitBinAndArgs(
105
- pkgDir,
106
- args.binOrArg,
107
- args.extraArgs ?? [],
108
- );
109
-
110
- const entry = resolveGjsEntry(pkgDir, binName);
111
- if (entry.fromFallback) {
112
- console.warn(
113
- `[gjsify dlx] package "${cachedPkgName ?? parsed.kind}" has no \`gjsify\` field — falling back to package.json#main. Add \`gjsify.main\` to silence.`,
114
- );
115
- }
116
-
117
- await runGjsBundle(entry.bundlePath, extraArgs);
118
- },
119
- };
120
-
121
- interface EnsureOpts {
122
- verbose: boolean;
123
- registry?: string;
124
- cacheMaxAge: number;
125
- frozen: boolean;
126
- }
127
-
128
- async function ensurePkgDir(
129
- parsed: ParsedSpec,
130
- opts: EnsureOpts,
131
- ): Promise<{ pkgDir: string; cachedPkgName: string | null }> {
132
- if (parsed.kind === 'local') {
133
- return { pkgDir: parsed.path, cachedPkgName: null };
134
- }
135
-
136
- const cacheKey = createCacheKey({ packages: [parsed.spec] });
137
- const cacheDir = cacheDirFor(cacheKey);
138
-
139
- const cached = opts.cacheMaxAge > 0 ? getValidCachedPkg(cacheDir, opts.cacheMaxAge) : undefined;
140
- if (cached) {
141
- return {
142
- pkgDir: resolveInstalledPkgDir(cached, parsed.name),
143
- cachedPkgName: parsed.name,
144
- };
145
- }
146
-
147
- const prepareDir = makePrepareDir(cacheDir);
148
- await installPackages({
149
- prefix: prepareDir,
150
- specs: [parsed.spec],
151
- verbose: opts.verbose,
152
- registry: opts.registry,
153
- // Cache-prepare dirs are scoped per cache key, so writing a lockfile
154
- // there gives us reproducibility for repeated `gjsify dlx <pkg>` calls
155
- // and lets `--frozen` short-circuit the resolver entirely.
156
- lockfile: true,
157
- frozen: opts.frozen,
158
- });
159
-
160
- const liveTarget = symlinkSwap(cacheDir, prepareDir);
161
- return {
162
- pkgDir: resolveInstalledPkgDir(liveTarget, parsed.name),
163
- cachedPkgName: parsed.name,
164
- };
165
- }
166
-
167
- import { existsSync, readFileSync } from 'node:fs';
168
- import { join } from 'node:path';
169
-
170
- function splitBinAndArgs(
171
- pkgDir: string,
172
- binOrArg: string | undefined,
173
- extraArgs: string[],
174
- ): { binName: string | null; extraArgs: string[] } {
175
- if (!binOrArg) {
176
- return { binName: null, extraArgs };
177
- }
178
-
179
- const pkgJsonPath = join(pkgDir, 'package.json');
180
- if (existsSync(pkgJsonPath)) {
181
- try {
182
- const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) as {
183
- gjsify?: { bin?: Record<string, string> };
184
- };
185
- const bins = pkg.gjsify?.bin;
186
- if (bins && Object.prototype.hasOwnProperty.call(bins, binOrArg)) {
187
- return { binName: binOrArg, extraArgs };
188
- }
189
- } catch {
190
- // Fall through to treating as an arg.
191
- }
192
- }
193
- // Not a known bin — treat the positional as the first argv to the bundle.
194
- return { binName: null, extraArgs: [binOrArg, ...extraArgs] };
195
- }
@@ -1,225 +0,0 @@
1
- // `gjsify flatpak build` — wrap `flatpak-builder` with sensible defaults.
2
- //
3
- // Replaces the project-local `build-flatpak.sh`-style scripts: same flag
4
- // shape (manifest, build-dir, install, repo, bundle), plus a `--tarball`
5
- // helper for Flathub submissions.
6
-
7
- import { spawn } from 'node:child_process';
8
- import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
9
- import { dirname, resolve } from 'node:path';
10
- import type { Command } from '../../types/index.js';
11
-
12
- interface FlatpakBuildOptions {
13
- manifest?: string;
14
- buildDir?: string;
15
- install?: boolean;
16
- repo?: string;
17
- bundle?: string;
18
- tarball?: string;
19
- forceClean?: boolean;
20
- sandbox?: boolean;
21
- deleteBuildDirs?: boolean;
22
- installDepsFrom?: string;
23
- verbose?: boolean;
24
- }
25
-
26
- export const flatpakBuildCommand: Command<unknown, FlatpakBuildOptions> = {
27
- command: 'build [manifest]',
28
- description:
29
- 'Build the Flatpak via `flatpak-builder`. Wraps a typical install + export + bundle + tarball pipeline.',
30
- builder: (yargs) => {
31
- return yargs
32
- .positional('manifest', {
33
- description: 'Path to the Flatpak manifest (default: first *.json that looks like a manifest in cwd)',
34
- type: 'string',
35
- normalize: true,
36
- })
37
- .option('build-dir', {
38
- description: 'flatpak-builder working directory',
39
- type: 'string',
40
- default: 'flatpak-build',
41
- normalize: true,
42
- })
43
- .option('install', {
44
- description: 'After build, run `flatpak-builder --user --install` to install locally',
45
- type: 'boolean',
46
- default: false,
47
- })
48
- .option('repo', {
49
- description: 'Export the build into the given OSTree repo (passes `--repo=<dir>` to flatpak-builder)',
50
- type: 'string',
51
- normalize: true,
52
- })
53
- .option('bundle', {
54
- description: 'After --repo export, build a single-file bundle (`flatpak build-bundle`) at this path',
55
- type: 'string',
56
- normalize: true,
57
- })
58
- .option('tarball', {
59
- description: 'Create a tarball of the build dir (parity with the legacy build-flatpak.sh tarball step)',
60
- type: 'string',
61
- normalize: true,
62
- })
63
- .option('force-clean', {
64
- description: 'Pass --force-clean to flatpak-builder (default true)',
65
- type: 'boolean',
66
- default: true,
67
- })
68
- .option('sandbox', {
69
- description: 'Pass --sandbox to flatpak-builder (default true)',
70
- type: 'boolean',
71
- default: true,
72
- })
73
- .option('delete-build-dirs', {
74
- description: 'Pass --delete-build-dirs to flatpak-builder (default true)',
75
- type: 'boolean',
76
- default: true,
77
- })
78
- .option('install-deps-from', {
79
- description: 'Pass --install-deps-from to flatpak-builder (e.g. `flathub`)',
80
- type: 'string',
81
- })
82
- .option('verbose', {
83
- description: 'Print the underlying flatpak-builder invocations',
84
- type: 'boolean',
85
- default: false,
86
- });
87
- },
88
- handler: async (args) => {
89
- const cwd = process.cwd();
90
- const manifest = resolve(cwd, (args.manifest as string | undefined) ?? findDefaultManifest(cwd));
91
- if (!existsSync(manifest)) {
92
- throw new Error(`gjsify flatpak build: manifest ${manifest} not found`);
93
- }
94
-
95
- const buildDir = resolve(cwd, args.buildDir ?? 'flatpak-build');
96
- const sharedFlags: string[] = [];
97
- if (args.forceClean !== false) sharedFlags.push('--force-clean');
98
- if (args.sandbox !== false) sharedFlags.push('--sandbox');
99
- if (args.deleteBuildDirs !== false) sharedFlags.push('--delete-build-dirs');
100
- if (args.installDepsFrom) sharedFlags.push(`--install-deps-from=${args.installDepsFrom}`);
101
-
102
- // Reset the build dir so re-runs don't pick up half-stale state.
103
- if (existsSync(buildDir)) rmSync(buildDir, { recursive: true, force: true });
104
-
105
- await runFlatpakBuilder([...sharedFlags, buildDir, manifest], { verbose: args.verbose });
106
-
107
- if (args.install) {
108
- await runFlatpakBuilder(
109
- ['--user', '--install', '--force-clean', buildDir, manifest],
110
- { verbose: args.verbose },
111
- );
112
- }
113
-
114
- if (args.repo) {
115
- const repoPath = resolve(cwd, args.repo);
116
- mkdirSync(dirname(repoPath), { recursive: true });
117
- await runFlatpakBuilder(
118
- [`--repo=${repoPath}`, '--force-clean', buildDir, manifest],
119
- { verbose: args.verbose },
120
- );
121
- }
122
-
123
- if (args.bundle) {
124
- if (!args.repo) {
125
- throw new Error(
126
- 'gjsify flatpak build: --bundle requires --repo (the bundle is built from the OSTree repo).',
127
- );
128
- }
129
- const bundlePath = resolve(cwd, args.bundle);
130
- mkdirSync(dirname(bundlePath), { recursive: true });
131
- const repoPath = resolve(cwd, args.repo);
132
- const appId = readManifestAppId(manifest);
133
- await runFlatpak(
134
- ['build-bundle', repoPath, bundlePath, appId],
135
- { verbose: args.verbose },
136
- );
137
- }
138
-
139
- if (args.tarball) {
140
- const tarballPath = resolve(cwd, args.tarball);
141
- mkdirSync(dirname(tarballPath), { recursive: true });
142
- await runTar(['-czf', tarballPath, '-C', buildDir, '.'], { verbose: args.verbose });
143
- }
144
-
145
- console.log(`[gjsify flatpak build] done (${buildDir})`);
146
- },
147
- };
148
-
149
- /** Pick the first JSON file in cwd that looks like a Flatpak manifest. */
150
- function findDefaultManifest(cwd: string): string {
151
- return scanForManifest(cwd) ?? 'flatpak.json';
152
- }
153
-
154
- function scanForManifest(cwd: string): string | undefined {
155
- let entries: string[] = [];
156
- try {
157
- entries = readdirSync(cwd);
158
- } catch {
159
- return undefined;
160
- }
161
- for (const name of entries) {
162
- if (!name.endsWith('.json')) continue;
163
- if (name === 'package.json' || name === 'tsconfig.json' || name.startsWith('.')) continue;
164
- try {
165
- const json = JSON.parse(readFileSync(resolve(cwd, name), 'utf-8')) as Record<string, unknown>;
166
- if (typeof json.id === 'string' && typeof json.runtime === 'string' && Array.isArray(json.modules)) {
167
- return name;
168
- }
169
- } catch {
170
- // Not JSON or unreadable — skip.
171
- }
172
- }
173
- return undefined;
174
- }
175
-
176
- function readManifestAppId(manifest: string): string {
177
- const raw = readFileSync(manifest, 'utf-8');
178
- const json = JSON.parse(raw) as { id?: unknown };
179
- if (typeof json.id !== 'string') {
180
- throw new Error(`gjsify flatpak build: ${manifest} has no string "id" field`);
181
- }
182
- return json.id;
183
- }
184
-
185
- async function runFlatpakBuilder(args: string[], opts: { verbose?: boolean }) {
186
- return runProc('flatpak-builder', args, opts, {
187
- notFoundHint:
188
- 'flatpak-builder not found. Install via your distro (Fedora: `sudo dnf install flatpak-builder`).',
189
- });
190
- }
191
-
192
- async function runFlatpak(args: string[], opts: { verbose?: boolean }) {
193
- return runProc('flatpak', args, opts, {
194
- notFoundHint: 'flatpak not found. Install via your distro and add Flathub: see https://flathub.org/setup.',
195
- });
196
- }
197
-
198
- async function runTar(args: string[], opts: { verbose?: boolean }) {
199
- return runProc('tar', args, opts, { notFoundHint: 'tar not found.' });
200
- }
201
-
202
- function runProc(
203
- cmd: string,
204
- args: string[],
205
- opts: { verbose?: boolean },
206
- extra: { notFoundHint: string },
207
- ): Promise<void> {
208
- if (opts.verbose) {
209
- console.log(`[gjsify flatpak] ${cmd} ${args.join(' ')}`);
210
- }
211
- return new Promise((res, rej) => {
212
- const child = spawn(cmd, args, { stdio: 'inherit' });
213
- child.on('error', (err: NodeJS.ErrnoException) => {
214
- if (err.code === 'ENOENT') {
215
- rej(new Error(`gjsify flatpak: ${extra.notFoundHint}`));
216
- } else {
217
- rej(err);
218
- }
219
- });
220
- child.on('exit', (code) => {
221
- if (code === 0) res();
222
- else rej(new Error(`gjsify flatpak: ${cmd} exited with status ${code}`));
223
- });
224
- });
225
- }
@@ -1,173 +0,0 @@
1
- // `gjsify flatpak ci` — scaffold .github/workflows/flatpak.yml in the
2
- // shape used by Flathub-hosted apps:
3
- //
4
- // * upstream `flatpak/flatpak-github-actions/flatpak-builder@v6` action
5
- // * `ghcr.io/flathub-infra/flatpak-github-actions:<runtime>-<version>` container
6
- // * cache key keyed by commit SHA
7
- //
8
- // Idempotent: refuses to overwrite an existing workflow without `--force`.
9
-
10
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
11
- import { dirname, resolve } from 'node:path';
12
- import type { Command, ConfigData, ConfigDataFlatpak } from '../../types/index.js';
13
- import { Config } from '../../config.js';
14
- import { defaultCiContainer, looksLikeAppId, readPackageJson, resolveRuntime } from './utils.js';
15
-
16
- interface FlatpakCiOptions {
17
- manifest?: string;
18
- bundle?: string;
19
- runtimeImage?: string;
20
- branches?: string[];
21
- out?: string;
22
- force?: boolean;
23
- cacheKey?: string;
24
- verbose?: boolean;
25
- }
26
-
27
- export const flatpakCiCommand: Command<unknown, FlatpakCiOptions> = {
28
- command: 'ci',
29
- description:
30
- 'Scaffold .github/workflows/flatpak.yml using the flathub-infra container + flatpak-builder@v6 action.',
31
- builder: (yargs) => {
32
- return yargs
33
- .option('manifest', {
34
- description: 'Manifest path the workflow points at (default: <app-id>.json)',
35
- type: 'string',
36
- normalize: true,
37
- })
38
- .option('bundle', {
39
- description: 'Bundle filename produced by the action (default: <app-id>.flatpak)',
40
- type: 'string',
41
- normalize: true,
42
- })
43
- .option('runtime-image', {
44
- description:
45
- 'Container image override. Default derived from gjsify.flatpak.runtime + runtimeVersion (e.g. `ghcr.io/flathub-infra/flatpak-github-actions:gnome-50`).',
46
- type: 'string',
47
- })
48
- .option('branches', {
49
- description: 'Branches the workflow runs on push for (default: main)',
50
- type: 'string',
51
- array: true,
52
- })
53
- .option('out', {
54
- description: 'Output path',
55
- type: 'string',
56
- default: '.github/workflows/flatpak.yml',
57
- normalize: true,
58
- })
59
- .option('cache-key', {
60
- description: 'Override the action `cache-key` (default: `flatpak-builder-${{ github.sha }}`)',
61
- type: 'string',
62
- })
63
- .option('force', {
64
- description: 'Overwrite an existing workflow file',
65
- type: 'boolean',
66
- default: false,
67
- })
68
- .option('verbose', {
69
- description: 'Print resolved fields',
70
- type: 'boolean',
71
- default: false,
72
- });
73
- },
74
- handler: async (args) => {
75
- const cwd = process.cwd();
76
- const cfg = new Config();
77
- const configData = await cfg.forBuild({} as never).catch(() => ({} as ConfigData));
78
- const flatpak: ConfigDataFlatpak = configData.flatpak ?? {};
79
- const pkg = readPackageJson(cwd);
80
-
81
- const appId =
82
- flatpak.appId ??
83
- (looksLikeAppId(pkg.name) ? (pkg.name as string) : undefined);
84
- if (!appId) {
85
- throw new Error(
86
- 'gjsify flatpak ci: no app id available. Set gjsify.flatpak.appId in package.json ' +
87
- 'or rename the package to a reverse-DNS id.',
88
- );
89
- }
90
-
91
- const manifest = (args.manifest as string | undefined) ?? `${appId}.json`;
92
- const bundle = (args.bundle as string | undefined) ?? `${appId}.flatpak`;
93
-
94
- const { runtime, runtimeVersion } = resolveRuntime(flatpak, {});
95
- const runtimeImage =
96
- (args.runtimeImage as string | undefined) ??
97
- flatpak.ciContainer ??
98
- defaultCiContainer(runtime, runtimeVersion);
99
-
100
- const branches = (args.branches as string[] | undefined) ?? flatpak.ciBranches ?? ['main'];
101
- const cacheKey = args.cacheKey ?? 'flatpak-builder-${{ github.sha }}';
102
-
103
- const out = resolve(cwd, args.out ?? '.github/workflows/flatpak.yml');
104
- if (existsSync(out) && !args.force) {
105
- // Same content → silently skip; different content → fail with a hint.
106
- const existing = readFileSync(out, 'utf-8');
107
- const next = renderWorkflow({ manifest, bundle, runtimeImage, branches, cacheKey });
108
- if (existing === next) {
109
- console.log(`[gjsify flatpak ci] ${out} already up to date`);
110
- return;
111
- }
112
- throw new Error(
113
- `gjsify flatpak ci: ${out} exists with different content. Pass --force to overwrite.`,
114
- );
115
- }
116
-
117
- const content = renderWorkflow({ manifest, bundle, runtimeImage, branches, cacheKey });
118
- mkdirSync(dirname(out), { recursive: true });
119
- writeFileSync(out, content, 'utf-8');
120
-
121
- if (args.verbose) {
122
- console.log(
123
- `[gjsify flatpak ci] runtime-image=${runtimeImage} manifest=${manifest} bundle=${bundle}`,
124
- );
125
- }
126
- console.log(`[gjsify flatpak ci] wrote ${out}`);
127
- },
128
- };
129
-
130
- interface RenderInput {
131
- manifest: string;
132
- bundle: string;
133
- runtimeImage: string;
134
- branches: string[];
135
- cacheKey: string;
136
- }
137
-
138
- /**
139
- * Render the workflow YAML. Format string built explicitly (no template
140
- * library) so the output is byte-stable for diff-based code review.
141
- */
142
- function renderWorkflow(input: RenderInput): string {
143
- const branchesYaml = `[${input.branches.map((b) => JSON.stringify(b)).join(', ')}]`;
144
- return `name: Flatpak
145
-
146
- on:
147
- push:
148
- branches: ${branchesYaml}
149
- pull_request:
150
- branches: ${branchesYaml}
151
-
152
- jobs:
153
- flatpak:
154
- name: Flatpak Build
155
- runs-on: ubuntu-latest
156
- container:
157
- image: ${input.runtimeImage}
158
- options: --privileged
159
-
160
- steps:
161
- - name: Checkout repository
162
- uses: actions/checkout@v4
163
- with:
164
- submodules: false
165
-
166
- - name: Build Flatpak
167
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
168
- with:
169
- manifest-path: ${input.manifest}
170
- bundle: ${input.bundle}
171
- cache-key: ${input.cacheKey}
172
- `;
173
- }