@gjsify/cli 0.3.14 → 0.3.16
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.
- package/lib/actions/build.d.ts +6 -2
- package/lib/actions/build.js +37 -8
- package/lib/commands/flatpak/build.d.ts +16 -0
- package/lib/commands/flatpak/build.js +187 -0
- package/lib/commands/flatpak/ci.d.ts +13 -0
- package/lib/commands/flatpak/ci.js +133 -0
- package/lib/commands/flatpak/deps.d.ts +12 -0
- package/lib/commands/flatpak/deps.js +96 -0
- package/lib/commands/flatpak/index.d.ts +7 -0
- package/lib/commands/flatpak/index.js +23 -0
- package/lib/commands/flatpak/init.d.ts +15 -0
- package/lib/commands/flatpak/init.js +154 -0
- package/lib/commands/flatpak/utils.d.ts +32 -0
- package/lib/commands/flatpak/utils.js +63 -0
- package/lib/commands/gsettings.d.ts +9 -0
- package/lib/commands/gsettings.js +72 -0
- package/lib/commands/index.d.ts +2 -0
- package/lib/commands/index.js +2 -0
- package/lib/commands/install.d.ts +1 -0
- package/lib/commands/install.js +66 -11
- package/lib/config.js +54 -2
- package/lib/index.js +3 -1
- package/lib/types/config-data.d.ts +145 -4
- package/lib/utils/install-global.d.ts +54 -0
- package/lib/utils/install-global.js +153 -0
- package/lib/utils/resolve-plugin-by-name.d.ts +21 -0
- package/lib/utils/resolve-plugin-by-name.js +75 -0
- package/package.json +10 -10
- package/src/actions/build.ts +41 -8
- package/src/commands/flatpak/build.ts +225 -0
- package/src/commands/flatpak/ci.ts +173 -0
- package/src/commands/flatpak/deps.ts +120 -0
- package/src/commands/flatpak/index.ts +53 -0
- package/src/commands/flatpak/init.ts +191 -0
- package/src/commands/flatpak/utils.ts +76 -0
- package/src/commands/gsettings.ts +87 -0
- package/src/commands/index.ts +2 -0
- package/src/commands/install.ts +90 -11
- package/src/config.ts +58 -2
- package/src/index.ts +4 -0
- package/src/types/config-data.ts +142 -4
- package/src/utils/install-global.ts +182 -0
- package/src/utils/resolve-plugin-by-name.ts +106 -0
package/lib/actions/build.d.ts
CHANGED
|
@@ -26,8 +26,12 @@ export declare class BuildAction {
|
|
|
26
26
|
*/
|
|
27
27
|
private resolveGlobalsInject;
|
|
28
28
|
/**
|
|
29
|
-
* Post-processing: prepend
|
|
30
|
-
* Only runs for GJS app builds with a
|
|
29
|
+
* Post-processing: prepend the resolved shebang line and mark the
|
|
30
|
+
* output executable. Only runs for GJS app builds with a single outfile.
|
|
31
|
+
* The shebang plugin in `@gjsify/rolldown-plugin-gjsify` already injects
|
|
32
|
+
* during bundling — this hook is the safety net for anything that
|
|
33
|
+
* bypassed the plugin (e.g. user-supplied banners that out-ordered it),
|
|
34
|
+
* plus the chmod.
|
|
31
35
|
*/
|
|
32
36
|
private applyShebang;
|
|
33
37
|
/** Application mode */
|
package/lib/actions/build.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { rolldown } from "rolldown";
|
|
2
|
-
import { gjsifyPlugin } from "@gjsify/rolldown-plugin-gjsify";
|
|
2
|
+
import { gjsifyPlugin, textLoaderPlugin, resolveShebangLine } from "@gjsify/rolldown-plugin-gjsify";
|
|
3
|
+
import { resolveUserPlugins } from "../utils/resolve-plugin-by-name.js";
|
|
3
4
|
import { resolveGlobalsList, writeRegisterInjectFile, detectAutoGlobals, } from "@gjsify/rolldown-plugin-gjsify/globals";
|
|
4
5
|
import { pnpPlugin } from "@gjsify/rolldown-plugin-pnp";
|
|
5
6
|
import { dirname, extname } from "node:path";
|
|
6
7
|
import { chmod, readFile, writeFile } from "node:fs/promises";
|
|
7
8
|
import { normalizeBundlerOptions, mergeBundlerOptions } from "../utils/normalize-bundler-options.js";
|
|
8
|
-
const
|
|
9
|
+
const DEFAULT_GJS_SHEBANG = "#!/usr/bin/env -S gjs -m";
|
|
9
10
|
/**
|
|
10
11
|
* `true` when `path` points at a location that's unsafe to use as a build
|
|
11
12
|
* outfile (would overwrite source). Currently catches:
|
|
@@ -150,8 +151,12 @@ export class BuildAction {
|
|
|
150
151
|
return injectPath ?? undefined;
|
|
151
152
|
}
|
|
152
153
|
/**
|
|
153
|
-
* Post-processing: prepend
|
|
154
|
-
* Only runs for GJS app builds with a
|
|
154
|
+
* Post-processing: prepend the resolved shebang line and mark the
|
|
155
|
+
* output executable. Only runs for GJS app builds with a single outfile.
|
|
156
|
+
* The shebang plugin in `@gjsify/rolldown-plugin-gjsify` already injects
|
|
157
|
+
* during bundling — this hook is the safety net for anything that
|
|
158
|
+
* bypassed the plugin (e.g. user-supplied banners that out-ordered it),
|
|
159
|
+
* plus the chmod.
|
|
155
160
|
*/
|
|
156
161
|
async applyShebang(outfile, verbose) {
|
|
157
162
|
if (!outfile) {
|
|
@@ -159,17 +164,18 @@ export class BuildAction {
|
|
|
159
164
|
console.warn("[gjsify] --shebang skipped: no single outfile (use --outfile for GJS executables)");
|
|
160
165
|
return;
|
|
161
166
|
}
|
|
167
|
+
const line = resolveShebangLine(this.configData.shebang) ?? DEFAULT_GJS_SHEBANG;
|
|
162
168
|
const content = await readFile(outfile, "utf-8");
|
|
163
169
|
if (content.startsWith("#!")) {
|
|
164
170
|
if (verbose)
|
|
165
171
|
console.debug(`[gjsify] --shebang skipped: ${outfile} already starts with a shebang`);
|
|
166
172
|
}
|
|
167
173
|
else {
|
|
168
|
-
await writeFile(outfile,
|
|
174
|
+
await writeFile(outfile, line + "\n" + content);
|
|
169
175
|
}
|
|
170
176
|
await chmod(outfile, 0o755);
|
|
171
177
|
if (verbose)
|
|
172
|
-
console.debug(`[gjsify] --shebang: wrote
|
|
178
|
+
console.debug(`[gjsify] --shebang: wrote ${line} + chmod 0o755 to ${outfile}`);
|
|
173
179
|
}
|
|
174
180
|
/** Application mode */
|
|
175
181
|
async buildApp(app = "gjs") {
|
|
@@ -213,6 +219,24 @@ export class BuildAction {
|
|
|
213
219
|
const { autoMode, extras } = this.parseGlobalsValue(globals);
|
|
214
220
|
const pnp = await buildPnpPlugin();
|
|
215
221
|
const pnpPlugins = pnp ? [pnp] : [];
|
|
222
|
+
// User-supplied text loaders need to be available during BOTH the
|
|
223
|
+
// auto-globals pre-build (`detectAutoGlobals`) and the final build —
|
|
224
|
+
// otherwise Rolldown's parser hits unknown extensions like `.ui` /
|
|
225
|
+
// `.asm` during the pre-build, fails to parse them as JS/JSX, and
|
|
226
|
+
// the auto-globals iteration aborts before the final plugin chain is
|
|
227
|
+
// ever assembled. Build the user-plugin chain once, up front, and
|
|
228
|
+
// pass it into both passes.
|
|
229
|
+
const userTextLoader = textLoaderPlugin({ loaders: this.configData.loaders });
|
|
230
|
+
const userPlugins = userTextLoader ? [userTextLoader] : [];
|
|
231
|
+
// User-supplied bundler.plugins (mix of plugin objects + by-name
|
|
232
|
+
// entries) — resolved from the project's node_modules. Same
|
|
233
|
+
// ordering rationale as the text loader: must be present during
|
|
234
|
+
// auto-globals pre-build to avoid claiming the same files via
|
|
235
|
+
// Rolldown's default classifier.
|
|
236
|
+
if (userBundler.plugins?.length) {
|
|
237
|
+
const resolved = await resolveUserPlugins(userBundler.plugins, process.cwd());
|
|
238
|
+
userPlugins.push(...resolved);
|
|
239
|
+
}
|
|
216
240
|
// --- Auto mode (with optional extras): iterative multi-pass build ---
|
|
217
241
|
if (app === "gjs" && autoMode) {
|
|
218
242
|
const gjsifyPluginFactory = async (opts) => {
|
|
@@ -228,7 +252,7 @@ export class BuildAction {
|
|
|
228
252
|
};
|
|
229
253
|
const { injectPath } = await detectAutoGlobals({
|
|
230
254
|
input: userBundler.input,
|
|
231
|
-
plugins: pnpPlugins,
|
|
255
|
+
plugins: [...pnpPlugins, ...userPlugins],
|
|
232
256
|
external: userBundler.external,
|
|
233
257
|
transform: userBundler.transform,
|
|
234
258
|
format,
|
|
@@ -250,7 +274,12 @@ export class BuildAction {
|
|
|
250
274
|
const merged = mergeBundlerOptions(cfg.options, userBundler);
|
|
251
275
|
const finalOpts = {
|
|
252
276
|
...merged,
|
|
253
|
-
plugins
|
|
277
|
+
// Drop user-config plugins from `merged` — they survived
|
|
278
|
+
// mergeBundlerOptions via spread but have already been resolved
|
|
279
|
+
// and appended into `userPlugins` above. Re-emitting the raw
|
|
280
|
+
// entries (which may include `BundlerPluginByName` shapes
|
|
281
|
+
// Rolldown doesn't understand) would crash the build.
|
|
282
|
+
plugins: [...pnpPlugins, ...userPlugins, ...cfg.plugins],
|
|
254
283
|
};
|
|
255
284
|
const build = await rolldown(finalOpts);
|
|
256
285
|
let writeResult;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Command } from '../../types/index.js';
|
|
2
|
+
interface FlatpakBuildOptions {
|
|
3
|
+
manifest?: string;
|
|
4
|
+
buildDir?: string;
|
|
5
|
+
install?: boolean;
|
|
6
|
+
repo?: string;
|
|
7
|
+
bundle?: string;
|
|
8
|
+
tarball?: string;
|
|
9
|
+
forceClean?: boolean;
|
|
10
|
+
sandbox?: boolean;
|
|
11
|
+
deleteBuildDirs?: boolean;
|
|
12
|
+
installDepsFrom?: string;
|
|
13
|
+
verbose?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare const flatpakBuildCommand: Command<unknown, FlatpakBuildOptions>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,187 @@
|
|
|
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
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
8
|
+
import { dirname, resolve } from 'node:path';
|
|
9
|
+
export const flatpakBuildCommand = {
|
|
10
|
+
command: 'build [manifest]',
|
|
11
|
+
description: 'Build the Flatpak via `flatpak-builder`. Wraps a typical install + export + bundle + tarball pipeline.',
|
|
12
|
+
builder: (yargs) => {
|
|
13
|
+
return yargs
|
|
14
|
+
.positional('manifest', {
|
|
15
|
+
description: 'Path to the Flatpak manifest (default: first *.json that looks like a manifest in cwd)',
|
|
16
|
+
type: 'string',
|
|
17
|
+
normalize: true,
|
|
18
|
+
})
|
|
19
|
+
.option('build-dir', {
|
|
20
|
+
description: 'flatpak-builder working directory',
|
|
21
|
+
type: 'string',
|
|
22
|
+
default: 'flatpak-build',
|
|
23
|
+
normalize: true,
|
|
24
|
+
})
|
|
25
|
+
.option('install', {
|
|
26
|
+
description: 'After build, run `flatpak-builder --user --install` to install locally',
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
default: false,
|
|
29
|
+
})
|
|
30
|
+
.option('repo', {
|
|
31
|
+
description: 'Export the build into the given OSTree repo (passes `--repo=<dir>` to flatpak-builder)',
|
|
32
|
+
type: 'string',
|
|
33
|
+
normalize: true,
|
|
34
|
+
})
|
|
35
|
+
.option('bundle', {
|
|
36
|
+
description: 'After --repo export, build a single-file bundle (`flatpak build-bundle`) at this path',
|
|
37
|
+
type: 'string',
|
|
38
|
+
normalize: true,
|
|
39
|
+
})
|
|
40
|
+
.option('tarball', {
|
|
41
|
+
description: 'Create a tarball of the build dir (parity with the legacy build-flatpak.sh tarball step)',
|
|
42
|
+
type: 'string',
|
|
43
|
+
normalize: true,
|
|
44
|
+
})
|
|
45
|
+
.option('force-clean', {
|
|
46
|
+
description: 'Pass --force-clean to flatpak-builder (default true)',
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
default: true,
|
|
49
|
+
})
|
|
50
|
+
.option('sandbox', {
|
|
51
|
+
description: 'Pass --sandbox to flatpak-builder (default true)',
|
|
52
|
+
type: 'boolean',
|
|
53
|
+
default: true,
|
|
54
|
+
})
|
|
55
|
+
.option('delete-build-dirs', {
|
|
56
|
+
description: 'Pass --delete-build-dirs to flatpak-builder (default true)',
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
default: true,
|
|
59
|
+
})
|
|
60
|
+
.option('install-deps-from', {
|
|
61
|
+
description: 'Pass --install-deps-from to flatpak-builder (e.g. `flathub`)',
|
|
62
|
+
type: 'string',
|
|
63
|
+
})
|
|
64
|
+
.option('verbose', {
|
|
65
|
+
description: 'Print the underlying flatpak-builder invocations',
|
|
66
|
+
type: 'boolean',
|
|
67
|
+
default: false,
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
handler: async (args) => {
|
|
71
|
+
const cwd = process.cwd();
|
|
72
|
+
const manifest = resolve(cwd, args.manifest ?? findDefaultManifest(cwd));
|
|
73
|
+
if (!existsSync(manifest)) {
|
|
74
|
+
throw new Error(`gjsify flatpak build: manifest ${manifest} not found`);
|
|
75
|
+
}
|
|
76
|
+
const buildDir = resolve(cwd, args.buildDir ?? 'flatpak-build');
|
|
77
|
+
const sharedFlags = [];
|
|
78
|
+
if (args.forceClean !== false)
|
|
79
|
+
sharedFlags.push('--force-clean');
|
|
80
|
+
if (args.sandbox !== false)
|
|
81
|
+
sharedFlags.push('--sandbox');
|
|
82
|
+
if (args.deleteBuildDirs !== false)
|
|
83
|
+
sharedFlags.push('--delete-build-dirs');
|
|
84
|
+
if (args.installDepsFrom)
|
|
85
|
+
sharedFlags.push(`--install-deps-from=${args.installDepsFrom}`);
|
|
86
|
+
// Reset the build dir so re-runs don't pick up half-stale state.
|
|
87
|
+
if (existsSync(buildDir))
|
|
88
|
+
rmSync(buildDir, { recursive: true, force: true });
|
|
89
|
+
await runFlatpakBuilder([...sharedFlags, buildDir, manifest], { verbose: args.verbose });
|
|
90
|
+
if (args.install) {
|
|
91
|
+
await runFlatpakBuilder(['--user', '--install', '--force-clean', buildDir, manifest], { verbose: args.verbose });
|
|
92
|
+
}
|
|
93
|
+
if (args.repo) {
|
|
94
|
+
const repoPath = resolve(cwd, args.repo);
|
|
95
|
+
mkdirSync(dirname(repoPath), { recursive: true });
|
|
96
|
+
await runFlatpakBuilder([`--repo=${repoPath}`, '--force-clean', buildDir, manifest], { verbose: args.verbose });
|
|
97
|
+
}
|
|
98
|
+
if (args.bundle) {
|
|
99
|
+
if (!args.repo) {
|
|
100
|
+
throw new Error('gjsify flatpak build: --bundle requires --repo (the bundle is built from the OSTree repo).');
|
|
101
|
+
}
|
|
102
|
+
const bundlePath = resolve(cwd, args.bundle);
|
|
103
|
+
mkdirSync(dirname(bundlePath), { recursive: true });
|
|
104
|
+
const repoPath = resolve(cwd, args.repo);
|
|
105
|
+
const appId = readManifestAppId(manifest);
|
|
106
|
+
await runFlatpak(['build-bundle', repoPath, bundlePath, appId], { verbose: args.verbose });
|
|
107
|
+
}
|
|
108
|
+
if (args.tarball) {
|
|
109
|
+
const tarballPath = resolve(cwd, args.tarball);
|
|
110
|
+
mkdirSync(dirname(tarballPath), { recursive: true });
|
|
111
|
+
await runTar(['-czf', tarballPath, '-C', buildDir, '.'], { verbose: args.verbose });
|
|
112
|
+
}
|
|
113
|
+
console.log(`[gjsify flatpak build] done (${buildDir})`);
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
/** Pick the first JSON file in cwd that looks like a Flatpak manifest. */
|
|
117
|
+
function findDefaultManifest(cwd) {
|
|
118
|
+
return scanForManifest(cwd) ?? 'flatpak.json';
|
|
119
|
+
}
|
|
120
|
+
function scanForManifest(cwd) {
|
|
121
|
+
let entries = [];
|
|
122
|
+
try {
|
|
123
|
+
entries = readdirSync(cwd);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
for (const name of entries) {
|
|
129
|
+
if (!name.endsWith('.json'))
|
|
130
|
+
continue;
|
|
131
|
+
if (name === 'package.json' || name === 'tsconfig.json' || name.startsWith('.'))
|
|
132
|
+
continue;
|
|
133
|
+
try {
|
|
134
|
+
const json = JSON.parse(readFileSync(resolve(cwd, name), 'utf-8'));
|
|
135
|
+
if (typeof json.id === 'string' && typeof json.runtime === 'string' && Array.isArray(json.modules)) {
|
|
136
|
+
return name;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Not JSON or unreadable — skip.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
function readManifestAppId(manifest) {
|
|
146
|
+
const raw = readFileSync(manifest, 'utf-8');
|
|
147
|
+
const json = JSON.parse(raw);
|
|
148
|
+
if (typeof json.id !== 'string') {
|
|
149
|
+
throw new Error(`gjsify flatpak build: ${manifest} has no string "id" field`);
|
|
150
|
+
}
|
|
151
|
+
return json.id;
|
|
152
|
+
}
|
|
153
|
+
async function runFlatpakBuilder(args, opts) {
|
|
154
|
+
return runProc('flatpak-builder', args, opts, {
|
|
155
|
+
notFoundHint: 'flatpak-builder not found. Install via your distro (Fedora: `sudo dnf install flatpak-builder`).',
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async function runFlatpak(args, opts) {
|
|
159
|
+
return runProc('flatpak', args, opts, {
|
|
160
|
+
notFoundHint: 'flatpak not found. Install via your distro and add Flathub: see https://flathub.org/setup.',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async function runTar(args, opts) {
|
|
164
|
+
return runProc('tar', args, opts, { notFoundHint: 'tar not found.' });
|
|
165
|
+
}
|
|
166
|
+
function runProc(cmd, args, opts, extra) {
|
|
167
|
+
if (opts.verbose) {
|
|
168
|
+
console.log(`[gjsify flatpak] ${cmd} ${args.join(' ')}`);
|
|
169
|
+
}
|
|
170
|
+
return new Promise((res, rej) => {
|
|
171
|
+
const child = spawn(cmd, args, { stdio: 'inherit' });
|
|
172
|
+
child.on('error', (err) => {
|
|
173
|
+
if (err.code === 'ENOENT') {
|
|
174
|
+
rej(new Error(`gjsify flatpak: ${extra.notFoundHint}`));
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
rej(err);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
child.on('exit', (code) => {
|
|
181
|
+
if (code === 0)
|
|
182
|
+
res();
|
|
183
|
+
else
|
|
184
|
+
rej(new Error(`gjsify flatpak: ${cmd} exited with status ${code}`));
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Command } from '../../types/index.js';
|
|
2
|
+
interface FlatpakCiOptions {
|
|
3
|
+
manifest?: string;
|
|
4
|
+
bundle?: string;
|
|
5
|
+
runtimeImage?: string;
|
|
6
|
+
branches?: string[];
|
|
7
|
+
out?: string;
|
|
8
|
+
force?: boolean;
|
|
9
|
+
cacheKey?: string;
|
|
10
|
+
verbose?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const flatpakCiCommand: Command<unknown, FlatpakCiOptions>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { dirname, resolve } from 'node:path';
|
|
11
|
+
import { Config } from '../../config.js';
|
|
12
|
+
import { defaultCiContainer, looksLikeAppId, readPackageJson, resolveRuntime } from './utils.js';
|
|
13
|
+
export const flatpakCiCommand = {
|
|
14
|
+
command: 'ci',
|
|
15
|
+
description: 'Scaffold .github/workflows/flatpak.yml using the flathub-infra container + flatpak-builder@v6 action.',
|
|
16
|
+
builder: (yargs) => {
|
|
17
|
+
return yargs
|
|
18
|
+
.option('manifest', {
|
|
19
|
+
description: 'Manifest path the workflow points at (default: <app-id>.json)',
|
|
20
|
+
type: 'string',
|
|
21
|
+
normalize: true,
|
|
22
|
+
})
|
|
23
|
+
.option('bundle', {
|
|
24
|
+
description: 'Bundle filename produced by the action (default: <app-id>.flatpak)',
|
|
25
|
+
type: 'string',
|
|
26
|
+
normalize: true,
|
|
27
|
+
})
|
|
28
|
+
.option('runtime-image', {
|
|
29
|
+
description: 'Container image override. Default derived from gjsify.flatpak.runtime + runtimeVersion (e.g. `ghcr.io/flathub-infra/flatpak-github-actions:gnome-50`).',
|
|
30
|
+
type: 'string',
|
|
31
|
+
})
|
|
32
|
+
.option('branches', {
|
|
33
|
+
description: 'Branches the workflow runs on push for (default: main)',
|
|
34
|
+
type: 'string',
|
|
35
|
+
array: true,
|
|
36
|
+
})
|
|
37
|
+
.option('out', {
|
|
38
|
+
description: 'Output path',
|
|
39
|
+
type: 'string',
|
|
40
|
+
default: '.github/workflows/flatpak.yml',
|
|
41
|
+
normalize: true,
|
|
42
|
+
})
|
|
43
|
+
.option('cache-key', {
|
|
44
|
+
description: 'Override the action `cache-key` (default: `flatpak-builder-${{ github.sha }}`)',
|
|
45
|
+
type: 'string',
|
|
46
|
+
})
|
|
47
|
+
.option('force', {
|
|
48
|
+
description: 'Overwrite an existing workflow file',
|
|
49
|
+
type: 'boolean',
|
|
50
|
+
default: false,
|
|
51
|
+
})
|
|
52
|
+
.option('verbose', {
|
|
53
|
+
description: 'Print resolved fields',
|
|
54
|
+
type: 'boolean',
|
|
55
|
+
default: false,
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
handler: async (args) => {
|
|
59
|
+
const cwd = process.cwd();
|
|
60
|
+
const cfg = new Config();
|
|
61
|
+
const configData = await cfg.forBuild({}).catch(() => ({}));
|
|
62
|
+
const flatpak = configData.flatpak ?? {};
|
|
63
|
+
const pkg = readPackageJson(cwd);
|
|
64
|
+
const appId = flatpak.appId ??
|
|
65
|
+
(looksLikeAppId(pkg.name) ? pkg.name : undefined);
|
|
66
|
+
if (!appId) {
|
|
67
|
+
throw new Error('gjsify flatpak ci: no app id available. Set gjsify.flatpak.appId in package.json ' +
|
|
68
|
+
'or rename the package to a reverse-DNS id.');
|
|
69
|
+
}
|
|
70
|
+
const manifest = args.manifest ?? `${appId}.json`;
|
|
71
|
+
const bundle = args.bundle ?? `${appId}.flatpak`;
|
|
72
|
+
const { runtime, runtimeVersion } = resolveRuntime(flatpak, {});
|
|
73
|
+
const runtimeImage = args.runtimeImage ??
|
|
74
|
+
flatpak.ciContainer ??
|
|
75
|
+
defaultCiContainer(runtime, runtimeVersion);
|
|
76
|
+
const branches = args.branches ?? flatpak.ciBranches ?? ['main'];
|
|
77
|
+
const cacheKey = args.cacheKey ?? 'flatpak-builder-${{ github.sha }}';
|
|
78
|
+
const out = resolve(cwd, args.out ?? '.github/workflows/flatpak.yml');
|
|
79
|
+
if (existsSync(out) && !args.force) {
|
|
80
|
+
// Same content → silently skip; different content → fail with a hint.
|
|
81
|
+
const existing = readFileSync(out, 'utf-8');
|
|
82
|
+
const next = renderWorkflow({ manifest, bundle, runtimeImage, branches, cacheKey });
|
|
83
|
+
if (existing === next) {
|
|
84
|
+
console.log(`[gjsify flatpak ci] ${out} already up to date`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
throw new Error(`gjsify flatpak ci: ${out} exists with different content. Pass --force to overwrite.`);
|
|
88
|
+
}
|
|
89
|
+
const content = renderWorkflow({ manifest, bundle, runtimeImage, branches, cacheKey });
|
|
90
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
91
|
+
writeFileSync(out, content, 'utf-8');
|
|
92
|
+
if (args.verbose) {
|
|
93
|
+
console.log(`[gjsify flatpak ci] runtime-image=${runtimeImage} manifest=${manifest} bundle=${bundle}`);
|
|
94
|
+
}
|
|
95
|
+
console.log(`[gjsify flatpak ci] wrote ${out}`);
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Render the workflow YAML. Format string built explicitly (no template
|
|
100
|
+
* library) so the output is byte-stable for diff-based code review.
|
|
101
|
+
*/
|
|
102
|
+
function renderWorkflow(input) {
|
|
103
|
+
const branchesYaml = `[${input.branches.map((b) => JSON.stringify(b)).join(', ')}]`;
|
|
104
|
+
return `name: Flatpak
|
|
105
|
+
|
|
106
|
+
on:
|
|
107
|
+
push:
|
|
108
|
+
branches: ${branchesYaml}
|
|
109
|
+
pull_request:
|
|
110
|
+
branches: ${branchesYaml}
|
|
111
|
+
|
|
112
|
+
jobs:
|
|
113
|
+
flatpak:
|
|
114
|
+
name: Flatpak Build
|
|
115
|
+
runs-on: ubuntu-latest
|
|
116
|
+
container:
|
|
117
|
+
image: ${input.runtimeImage}
|
|
118
|
+
options: --privileged
|
|
119
|
+
|
|
120
|
+
steps:
|
|
121
|
+
- name: Checkout repository
|
|
122
|
+
uses: actions/checkout@v4
|
|
123
|
+
with:
|
|
124
|
+
submodules: false
|
|
125
|
+
|
|
126
|
+
- name: Build Flatpak
|
|
127
|
+
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
|
128
|
+
with:
|
|
129
|
+
manifest-path: ${input.manifest}
|
|
130
|
+
bundle: ${input.bundle}
|
|
131
|
+
cache-key: ${input.cacheKey}
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Command } from '../../types/index.js';
|
|
2
|
+
interface FlatpakDepsOptions {
|
|
3
|
+
lockfile?: string;
|
|
4
|
+
type?: 'yarn' | 'npm';
|
|
5
|
+
out?: string;
|
|
6
|
+
xdgLayout?: boolean;
|
|
7
|
+
nodeChromedriverFromElectron?: string;
|
|
8
|
+
electronNodeHeaders?: boolean;
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const flatpakDepsCommand: Command<unknown, FlatpakDepsOptions>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// `gjsify flatpak deps` — wrap `flatpak-node-generator` to convert a
|
|
2
|
+
// yarn.lock / package-lock.json into the JSON sources file that the
|
|
3
|
+
// Flatpak manifest references for offline `yarn install` inside the
|
|
4
|
+
// build sandbox.
|
|
5
|
+
//
|
|
6
|
+
// flatpak-node-generator is a Python tool from
|
|
7
|
+
// https://github.com/flatpak/flatpak-builder-tools — installed via
|
|
8
|
+
// `pipx install flatpak-node-generator` or `pip install --user
|
|
9
|
+
// flatpak-node-generator`.
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
import { dirname, resolve } from 'node:path';
|
|
13
|
+
import { mkdirSync } from 'node:fs';
|
|
14
|
+
export const flatpakDepsCommand = {
|
|
15
|
+
command: 'deps',
|
|
16
|
+
description: 'Generate Flatpak offline-cache sources from a yarn.lock / package-lock.json (wraps `flatpak-node-generator`).',
|
|
17
|
+
builder: (yargs) => {
|
|
18
|
+
return yargs
|
|
19
|
+
.option('lockfile', {
|
|
20
|
+
description: 'Path to lockfile (default: yarn.lock or package-lock.json in cwd)',
|
|
21
|
+
type: 'string',
|
|
22
|
+
normalize: true,
|
|
23
|
+
})
|
|
24
|
+
.option('type', {
|
|
25
|
+
description: 'Lockfile type. Default: detected from filename.',
|
|
26
|
+
choices: ['yarn', 'npm'],
|
|
27
|
+
})
|
|
28
|
+
.option('out', {
|
|
29
|
+
description: 'Output JSON sources file',
|
|
30
|
+
type: 'string',
|
|
31
|
+
default: 'flatpak-node-sources.json',
|
|
32
|
+
normalize: true,
|
|
33
|
+
})
|
|
34
|
+
.option('xdg-layout', {
|
|
35
|
+
description: 'Pass --xdg-layout (recommended for Yarn Berry / PnP setups)',
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
default: true,
|
|
38
|
+
})
|
|
39
|
+
.option('electron-node-headers', {
|
|
40
|
+
description: 'Pass --electron-node-headers',
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
default: false,
|
|
43
|
+
})
|
|
44
|
+
.option('verbose', {
|
|
45
|
+
description: 'Print the underlying flatpak-node-generator invocation',
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
default: false,
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
handler: async (args) => {
|
|
51
|
+
const cwd = process.cwd();
|
|
52
|
+
const lockfile = resolve(cwd, args.lockfile ?? detectLockfile(cwd));
|
|
53
|
+
if (!existsSync(lockfile)) {
|
|
54
|
+
throw new Error(`gjsify flatpak deps: lockfile ${lockfile} not found (use --lockfile to override)`);
|
|
55
|
+
}
|
|
56
|
+
const type = args.type ??
|
|
57
|
+
(lockfile.endsWith('package-lock.json') ? 'npm' : 'yarn');
|
|
58
|
+
const out = resolve(cwd, args.out ?? 'flatpak-node-sources.json');
|
|
59
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
60
|
+
const cmdArgs = [type, lockfile, '-o', out];
|
|
61
|
+
if (args.xdgLayout !== false)
|
|
62
|
+
cmdArgs.push('--xdg-layout');
|
|
63
|
+
if (args.electronNodeHeaders)
|
|
64
|
+
cmdArgs.push('--electron-node-headers');
|
|
65
|
+
if (args.verbose) {
|
|
66
|
+
console.log(`[gjsify flatpak deps] flatpak-node-generator ${cmdArgs.join(' ')}`);
|
|
67
|
+
}
|
|
68
|
+
await new Promise((res, rej) => {
|
|
69
|
+
const child = spawn('flatpak-node-generator', cmdArgs, { stdio: 'inherit' });
|
|
70
|
+
child.on('error', (err) => {
|
|
71
|
+
if (err.code === 'ENOENT') {
|
|
72
|
+
rej(new Error('gjsify flatpak deps: flatpak-node-generator not found. ' +
|
|
73
|
+
'Install via `pipx install flatpak-node-generator` ' +
|
|
74
|
+
'(see https://github.com/flatpak/flatpak-builder-tools/tree/master/node).'));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
rej(err);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
child.on('exit', (code) => {
|
|
81
|
+
if (code === 0)
|
|
82
|
+
res();
|
|
83
|
+
else
|
|
84
|
+
rej(new Error(`gjsify flatpak deps: flatpak-node-generator exited with status ${code}`));
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
console.log(`[gjsify flatpak deps] wrote ${out}`);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
function detectLockfile(cwd) {
|
|
91
|
+
if (existsSync(resolve(cwd, 'yarn.lock')))
|
|
92
|
+
return 'yarn.lock';
|
|
93
|
+
if (existsSync(resolve(cwd, 'package-lock.json')))
|
|
94
|
+
return 'package-lock.json';
|
|
95
|
+
return 'yarn.lock'; // surfaces a clear "not found" later
|
|
96
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Command } from '../../types/index.js';
|
|
2
|
+
import { flatpakInitCommand } from './init.js';
|
|
3
|
+
import { flatpakBuildCommand } from './build.js';
|
|
4
|
+
import { flatpakDepsCommand } from './deps.js';
|
|
5
|
+
import { flatpakCiCommand } from './ci.js';
|
|
6
|
+
export declare const flatpakCommand: Command;
|
|
7
|
+
export { flatpakInitCommand, flatpakBuildCommand, flatpakDepsCommand, flatpakCiCommand, };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// `gjsify flatpak` — yargs subcommand-group dispatcher.
|
|
2
|
+
//
|
|
3
|
+
// Wires {init, build, deps, ci}. Each subcommand is a self-contained
|
|
4
|
+
// `Command<>` so it composes the same way as `gresource` / `gettext` /
|
|
5
|
+
// `gsettings` at the top level.
|
|
6
|
+
import { flatpakInitCommand } from './init.js';
|
|
7
|
+
import { flatpakBuildCommand } from './build.js';
|
|
8
|
+
import { flatpakDepsCommand } from './deps.js';
|
|
9
|
+
import { flatpakCiCommand } from './ci.js';
|
|
10
|
+
export const flatpakCommand = {
|
|
11
|
+
command: 'flatpak <subcommand>',
|
|
12
|
+
description: 'Flatpak toolchain: init/build/deps/ci subcommands for shipping GJS apps and CLIs as Flatpaks.',
|
|
13
|
+
builder: (yargs) => {
|
|
14
|
+
return yargs
|
|
15
|
+
.command(flatpakInitCommand.command, flatpakInitCommand.description, flatpakInitCommand.builder, flatpakInitCommand.handler)
|
|
16
|
+
.command(flatpakBuildCommand.command, flatpakBuildCommand.description, flatpakBuildCommand.builder, flatpakBuildCommand.handler)
|
|
17
|
+
.command(flatpakDepsCommand.command, flatpakDepsCommand.description, flatpakDepsCommand.builder, flatpakDepsCommand.handler)
|
|
18
|
+
.command(flatpakCiCommand.command, flatpakCiCommand.description, flatpakCiCommand.builder, flatpakCiCommand.handler)
|
|
19
|
+
.demandCommand(1)
|
|
20
|
+
.strict();
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
export { flatpakInitCommand, flatpakBuildCommand, flatpakDepsCommand, flatpakCiCommand, };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Command } from '../../types/index.js';
|
|
2
|
+
interface FlatpakInitOptions {
|
|
3
|
+
appId?: string;
|
|
4
|
+
runtime?: string;
|
|
5
|
+
runtimeVersion?: string;
|
|
6
|
+
cliOnly?: boolean;
|
|
7
|
+
manifest?: string;
|
|
8
|
+
command?: string;
|
|
9
|
+
force?: boolean;
|
|
10
|
+
sdkExtension?: string[];
|
|
11
|
+
finishArg?: string[];
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare const flatpakInitCommand: Command<unknown, FlatpakInitOptions>;
|
|
15
|
+
export {};
|