@gjsify/cli 0.38.1 → 0.40.0
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/dist/cli.gjs.mjs +212 -195
- package/lib/cli-app.js +11 -25
- package/lib/commands/affected-classify.d.ts +16 -0
- package/lib/commands/affected-classify.js +304 -0
- package/lib/commands/affected.js +2 -291
- package/lib/commands/flatpak/check.js +34 -43
- package/lib/commands/flatpak/ci.js +1 -1
- package/lib/commands/flatpak/deps.js +10 -19
- package/lib/commands/flatpak/diff.js +1 -1
- package/lib/commands/flatpak/init.js +1 -1
- package/lib/commands/flatpak/release.js +7 -11
- package/lib/commands/flatpak/scaffold.d.ts +2 -4
- package/lib/commands/flatpak/scaffold.js +25 -298
- package/lib/commands/flatpak/sync-flathub.js +18 -36
- package/lib/commands/foreach.js +34 -20
- package/lib/commands/gettext.js +7 -4
- package/lib/commands/index.d.ts +1 -0
- package/lib/commands/index.js +1 -0
- package/lib/commands/ship.d.ts +11 -0
- package/lib/commands/ship.js +217 -0
- package/lib/config.d.ts +12 -0
- package/lib/config.js +15 -0
- package/lib/types/config-data.d.ts +136 -46
- package/lib/utils/app-metadata.d.ts +51 -0
- package/lib/utils/app-metadata.js +338 -0
- package/lib/utils/cli-runtime-closure.d.ts +23 -0
- package/lib/utils/cli-runtime-closure.js +274 -0
- package/lib/utils/publish-headers.d.ts +13 -0
- package/lib/utils/publish-headers.js +30 -4
- package/lib/utils/ship/ar.d.ts +21 -0
- package/lib/utils/ship/ar.js +57 -0
- package/lib/utils/ship/bytes.d.ts +4 -0
- package/lib/utils/ship/bytes.js +23 -0
- package/lib/utils/ship/cpio.d.ts +22 -0
- package/lib/utils/ship/cpio.js +93 -0
- package/lib/utils/ship/deb.d.ts +24 -0
- package/lib/utils/ship/deb.js +118 -0
- package/lib/utils/ship/depends.d.ts +62 -0
- package/lib/utils/ship/depends.js +202 -0
- package/lib/utils/ship/discover.d.ts +21 -0
- package/lib/utils/ship/discover.js +129 -0
- package/lib/utils/ship/formats.d.ts +5 -0
- package/lib/utils/ship/formats.js +83 -0
- package/lib/utils/ship/gi-namespaces.d.ts +7 -0
- package/lib/utils/ship/gi-namespaces.js +49 -0
- package/lib/utils/ship/gzip.d.ts +2 -0
- package/lib/utils/ship/gzip.js +21 -0
- package/lib/utils/ship/launcher.d.ts +7 -0
- package/lib/utils/ship/launcher.js +37 -0
- package/lib/utils/ship/payload.d.ts +30 -0
- package/lib/utils/ship/payload.js +59 -0
- package/lib/utils/ship/plan.d.ts +34 -0
- package/lib/utils/ship/plan.js +167 -0
- package/lib/utils/ship/rpm-header.d.ts +29 -0
- package/lib/utils/ship/rpm-header.js +170 -0
- package/lib/utils/ship/rpm.d.ts +12 -0
- package/lib/utils/ship/rpm.js +377 -0
- package/lib/utils/ship/scripts.d.ts +31 -0
- package/lib/utils/ship/scripts.js +76 -0
- package/lib/utils/ship/settings.d.ts +60 -0
- package/lib/utils/ship/settings.js +184 -0
- package/lib/utils/ship/stage-writer.d.ts +17 -0
- package/lib/utils/ship/stage-writer.js +49 -0
- package/lib/utils/ship/types.d.ts +114 -0
- package/lib/utils/ship/types.js +10 -0
- package/lib/utils/ship/version.d.ts +17 -0
- package/lib/utils/ship/version.js +55 -0
- package/lib/utils/spawn.d.ts +11 -1
- package/lib/utils/spawn.js +28 -4
- package/package.json +19 -18
- /package/lib/templates/{flatpak → app}/desktop.tmpl +0 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// `gjsify ship` — turn a built application into something a stranger can
|
|
2
|
+
// install (ADR 0024).
|
|
3
|
+
//
|
|
4
|
+
// One payload, produced once, then wrapped per format. The command itself is
|
|
5
|
+
// thin on purpose: every decision worth testing lives in `utils/ship/*` as a
|
|
6
|
+
// pure function, and what is left here is filesystem work and printing.
|
|
7
|
+
//
|
|
8
|
+
// Scope today is Linux and `--app gjs`: no runtime is bundled, because GJS and
|
|
9
|
+
// GTK come from the distribution and shipping ~100 MiB of them would be cargo
|
|
10
|
+
// cult (ADR 0024 § 4). macOS and Windows layouts, and Flatpak as a target under
|
|
11
|
+
// this command, are the later stages of the same ADR.
|
|
12
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { basename, join, relative, resolve } from 'node:path';
|
|
14
|
+
import { Config } from '../config.js';
|
|
15
|
+
import { renderDesktopEntry, renderMetainfoApp, renderMetainfoCli, validateAppMetadata, } from '../utils/app-metadata.js';
|
|
16
|
+
import { readPackageJson } from '../utils/pkg-json.js';
|
|
17
|
+
import { describeExit, spawnToCompletion } from '../utils/spawn.js';
|
|
18
|
+
import { buildDeb } from '../utils/ship/deb.js';
|
|
19
|
+
import { deriveDepends, warnAboutGjsFloor } from '../utils/ship/depends.js';
|
|
20
|
+
import { discoverPayload } from '../utils/ship/discover.js';
|
|
21
|
+
import { resolveFormats, FORMAT_IDS } from '../utils/ship/formats.js';
|
|
22
|
+
import { scanGiNamespaces } from '../utils/ship/gi-namespaces.js';
|
|
23
|
+
import { buildTimestamp, isArchIndependent } from '../utils/ship/payload.js';
|
|
24
|
+
import { planOverlay, planStage } from '../utils/ship/plan.js';
|
|
25
|
+
import { renderLauncher } from '../utils/ship/launcher.js';
|
|
26
|
+
import { buildRpm } from '../utils/ship/rpm.js';
|
|
27
|
+
import { declaredBundlePath, resolveShipSettings } from '../utils/ship/settings.js';
|
|
28
|
+
import { readStage, writeStage } from '../utils/ship/stage-writer.js';
|
|
29
|
+
const LOG = '[gjsify ship]';
|
|
30
|
+
export const shipCommand = {
|
|
31
|
+
command: 'ship',
|
|
32
|
+
description: `Build installable artifacts (${FORMAT_IDS.join(', ')}) from the current project.`,
|
|
33
|
+
builder: (yargs) => yargs
|
|
34
|
+
.option('target', {
|
|
35
|
+
type: 'string',
|
|
36
|
+
array: true,
|
|
37
|
+
description: `Formats to build, comma-separated or repeated. Default: ${FORMAT_IDS.join(',')}.`,
|
|
38
|
+
})
|
|
39
|
+
.option('out', {
|
|
40
|
+
type: 'string',
|
|
41
|
+
normalize: true,
|
|
42
|
+
description: 'Output root, relative to the project. Default: `gjsify.ship.outDir`, else `ship`.',
|
|
43
|
+
})
|
|
44
|
+
.option('stage', {
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
default: false,
|
|
47
|
+
description: 'Produce the staged payload and stop, without packing anything.',
|
|
48
|
+
})
|
|
49
|
+
.option('skip-build', {
|
|
50
|
+
type: 'boolean',
|
|
51
|
+
default: false,
|
|
52
|
+
description: "Package what is already built instead of running the project's `build` script.",
|
|
53
|
+
})
|
|
54
|
+
.option('arch', {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description: 'Target architecture in `process.arch` spelling. Default: this host.',
|
|
57
|
+
})
|
|
58
|
+
.option('verbose', {
|
|
59
|
+
type: 'boolean',
|
|
60
|
+
default: false,
|
|
61
|
+
description: 'Print each staged file and the GI namespaces the bundle imports.',
|
|
62
|
+
}),
|
|
63
|
+
handler: async (args) => {
|
|
64
|
+
const projectDir = process.cwd();
|
|
65
|
+
const config = new Config();
|
|
66
|
+
// No `.catch(() => ({}))` here, unlike the flatpak commands: a config
|
|
67
|
+
// file that fails to load would silently become "no config", and the
|
|
68
|
+
// result is a shipped artifact carrying default metadata the author
|
|
69
|
+
// never chose. An unreadable config is an error worth printing.
|
|
70
|
+
const configData = await config.forCommand(projectDir);
|
|
71
|
+
const ship = configData.ship ?? {};
|
|
72
|
+
const flatpak = configData.flatpak ?? {};
|
|
73
|
+
assertShippableTarget(configData.app);
|
|
74
|
+
const pkg = (readPackageJson(join(projectDir, 'package.json')) ?? {});
|
|
75
|
+
// Resolved before anything is built or written: a typo'd `--target`
|
|
76
|
+
// should not cost a build and leave a half-written `ship/stage/`.
|
|
77
|
+
const formats = resolveFormats(args.target ?? ship.targets ?? FORMAT_IDS);
|
|
78
|
+
if (!args['skip-build'])
|
|
79
|
+
await runProjectBuild(projectDir);
|
|
80
|
+
const discovered = discoverPayload({
|
|
81
|
+
projectDir,
|
|
82
|
+
pkg,
|
|
83
|
+
ship,
|
|
84
|
+
flatpakIcon: flatpak.icon,
|
|
85
|
+
declaredBundle: declaredBundlePath(pkg, ship),
|
|
86
|
+
});
|
|
87
|
+
const { settings, metadata, warnings } = resolveShipSettings({
|
|
88
|
+
projectDir,
|
|
89
|
+
pkg,
|
|
90
|
+
ship,
|
|
91
|
+
flatpak,
|
|
92
|
+
cli: { outDir: args.out, arch: args.arch },
|
|
93
|
+
discovered,
|
|
94
|
+
});
|
|
95
|
+
for (const warning of warnings)
|
|
96
|
+
console.warn(`${LOG} ${warning}`);
|
|
97
|
+
const mtime = buildTimestamp(settings.bundlePath);
|
|
98
|
+
const metadataInputs = {
|
|
99
|
+
appId: settings.appId,
|
|
100
|
+
name: settings.name,
|
|
101
|
+
command: settings.binaryName,
|
|
102
|
+
kind: settings.kind,
|
|
103
|
+
metadata,
|
|
104
|
+
configKey: 'gjsify.ship',
|
|
105
|
+
copyrightYear: new Date(mtime * 1000).getUTCFullYear(),
|
|
106
|
+
};
|
|
107
|
+
// Warn rather than fail: an incomplete AppStream component still
|
|
108
|
+
// installs and still runs. It is app STORES that will reject it, and
|
|
109
|
+
// that is a different day's problem than getting the package built.
|
|
110
|
+
for (const missing of validateAppMetadata(metadataInputs)) {
|
|
111
|
+
console.warn(`${LOG} ${missing.field} is not set — ${missing.hint}`);
|
|
112
|
+
}
|
|
113
|
+
const stageInputs = {
|
|
114
|
+
bundleFiles: discovered.bundleFiles,
|
|
115
|
+
launcher: renderLauncher(settings, basename(settings.bundlePath)),
|
|
116
|
+
metainfo: settings.kind === 'app' ? renderMetainfoApp(metadataInputs) : renderMetainfoCli(metadataInputs),
|
|
117
|
+
desktopEntry: settings.kind === 'app' ? renderDesktopEntry(metadataInputs) : undefined,
|
|
118
|
+
licenseText: settings.licenseFile === undefined ? undefined : readFileSync(settings.licenseFile, 'utf-8'),
|
|
119
|
+
};
|
|
120
|
+
const outRoot = resolve(projectDir, settings.outDir);
|
|
121
|
+
const stageDir = join(outRoot, 'stage');
|
|
122
|
+
const staged = planStage(settings, stageInputs);
|
|
123
|
+
writeStage(stageDir, staged);
|
|
124
|
+
console.log(`${LOG} staged ${staged.length} file(s) in ${relative(projectDir, stageDir)}/`);
|
|
125
|
+
if (args.verbose)
|
|
126
|
+
for (const file of staged)
|
|
127
|
+
console.log(`${LOG} ${file.path}`);
|
|
128
|
+
if (args.stage)
|
|
129
|
+
return;
|
|
130
|
+
const namespaces = scanGiNamespaces(readFileSync(settings.bundlePath, 'utf-8'));
|
|
131
|
+
if (args.verbose)
|
|
132
|
+
console.log(`${LOG} gi namespaces: ${namespaces.join(', ') || '(none)'}`);
|
|
133
|
+
const artifacts = [];
|
|
134
|
+
for (const format of formats) {
|
|
135
|
+
artifacts.push(await packOne({ format, settings, stageInputs, staged, stageDir, outRoot, namespaces, mtime }));
|
|
136
|
+
}
|
|
137
|
+
for (const artifact of artifacts) {
|
|
138
|
+
console.log(`${LOG} ${artifact.format}: ${relative(projectDir, artifact.path)} (${artifact.size} bytes)`);
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
async function packOne(input) {
|
|
143
|
+
const { format, settings, stageDir, outRoot, mtime } = input;
|
|
144
|
+
const overlay = planOverlay(settings, format, input.stageInputs);
|
|
145
|
+
const overlayDir = join(outRoot, 'overlay', format.id);
|
|
146
|
+
writeStage(overlayDir, overlay);
|
|
147
|
+
const payload = readStage([stageDir, overlayDir], [...input.staged, ...overlay]);
|
|
148
|
+
const depends = deriveDepends(format.id, {
|
|
149
|
+
namespaces: input.namespaces,
|
|
150
|
+
kind: settings.kind,
|
|
151
|
+
hasSchemas: settings.schemaFiles.length > 0,
|
|
152
|
+
extra: settings.extraDepends[format.id],
|
|
153
|
+
typelibPackages: settings.typelibPackages,
|
|
154
|
+
minGjsVersion: settings.minGjsVersion,
|
|
155
|
+
});
|
|
156
|
+
for (const warning of warnAboutGjsFloor(format.id, settings.minGjsVersion))
|
|
157
|
+
console.warn(`${LOG} ${warning}`);
|
|
158
|
+
const archLabel = format.archName(settings.arch, isArchIndependent(payload));
|
|
159
|
+
const common = { settings, payload, prefix: format.prefix, depends, archLabel, mtime };
|
|
160
|
+
const bytes = format.id === 'deb' ? await buildDeb(common) : await buildRpm(common);
|
|
161
|
+
const outDir = join(outRoot, 'out');
|
|
162
|
+
mkdirSync(outDir, { recursive: true });
|
|
163
|
+
const target = join(outDir, format.fileName(settings, archLabel));
|
|
164
|
+
writeFileSync(target, bytes);
|
|
165
|
+
return { format: format.id, path: target, size: bytes.byteLength };
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Refuse a build target this command cannot package correctly.
|
|
169
|
+
*
|
|
170
|
+
* The launcher execs `gjs -m <bundle>`, so a `--app node` bundle would produce
|
|
171
|
+
* a package that installs and then fails at startup — and ADR 0024 § 4 says
|
|
172
|
+
* that case needs a BUNDLED Node, whose delivery (fetched at ship time versus a
|
|
173
|
+
* platform package) is still an open decision. Better to say so than to ship
|
|
174
|
+
* the broken half of it.
|
|
175
|
+
*
|
|
176
|
+
* An undeclared target is the common case for a GJS app and is allowed: `app`
|
|
177
|
+
* otherwise defaults to the HOST runtime, which would refuse a perfectly good
|
|
178
|
+
* GJS project merely for running this command under Node.
|
|
179
|
+
*/
|
|
180
|
+
function assertShippableTarget(app) {
|
|
181
|
+
if (app === undefined || app === 'gjs')
|
|
182
|
+
return;
|
|
183
|
+
throw new Error(`gjsify ship: this project declares \`gjsify.app: "${app}"\`, and only \`gjs\` can be packaged today. ` +
|
|
184
|
+
'The launcher execs `gjs -m <bundle>`, so any other target would install and then fail to start. ' +
|
|
185
|
+
'ADR 0024 § 4 has the runtime-per-OS table and what is still open for the other targets.');
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Run the project's own `build` script, as a CHILD process.
|
|
189
|
+
*
|
|
190
|
+
* NOT dispatched in-process, even though `cli-app.ts` exists to allow exactly
|
|
191
|
+
* that: `gjsify run`'s script path ends in `return process.exit(code)` on every
|
|
192
|
+
* branch — deliberately, because under GJS a bare `process.exit` falls through
|
|
193
|
+
* and it needs exactly one exit scheduled. Calling it from here would end the
|
|
194
|
+
* process after the build and never reach the packing, and neither the types
|
|
195
|
+
* nor a `--skip-build` test would have caught it.
|
|
196
|
+
*
|
|
197
|
+
* `completion: 'return'` because this handler returns afterwards; on GJS that
|
|
198
|
+
* selects the blocking path rather than arming a main loop only an exit quits.
|
|
199
|
+
*/
|
|
200
|
+
async function runProjectBuild(projectDir) {
|
|
201
|
+
const pkg = readPackageJson(join(projectDir, 'package.json'));
|
|
202
|
+
if (typeof pkg?.scripts?.build !== 'string') {
|
|
203
|
+
throw new Error('gjsify ship: this project has no `build` script to run. Add one, or pass --skip-build to ' +
|
|
204
|
+
'package what is already built.');
|
|
205
|
+
}
|
|
206
|
+
console.log(`${LOG} running the project's build script`);
|
|
207
|
+
const result = await spawnToCompletion('gjsify', ['run', 'build'], {
|
|
208
|
+
completion: 'return',
|
|
209
|
+
cwd: projectDir,
|
|
210
|
+
stdio: 'inherit',
|
|
211
|
+
notFound: () => new Error('gjsify ship: `gjsify` is not on PATH, so the project build cannot be run. Build the project ' +
|
|
212
|
+
'yourself and pass --skip-build.'),
|
|
213
|
+
});
|
|
214
|
+
if (result.code !== 0) {
|
|
215
|
+
throw new Error(`gjsify ship: the project's build script failed${describeExit(result)}.`);
|
|
216
|
+
}
|
|
217
|
+
}
|
package/lib/config.d.ts
CHANGED
|
@@ -24,5 +24,17 @@ export declare class Config {
|
|
|
24
24
|
globals?: string;
|
|
25
25
|
excludeGlobals?: string[];
|
|
26
26
|
}>;
|
|
27
|
+
/**
|
|
28
|
+
* The config for a command that is NOT a build — `gjsify flatpak …`,
|
|
29
|
+
* `gjsify ship …`.
|
|
30
|
+
*
|
|
31
|
+
* They all want exactly what the loader already produces: the merged
|
|
32
|
+
* `package.json#gjsify` + config-file object, with none of `forBuild`'s
|
|
33
|
+
* build-specific defaulting. Reaching it through `forBuild({} as never)`
|
|
34
|
+
* — five copies of that line before this method existed — lies to the type
|
|
35
|
+
* system and drags a tsconfig read and bundler defaults into a command
|
|
36
|
+
* that has no bundle.
|
|
37
|
+
*/
|
|
38
|
+
forCommand(searchFrom?: string): Promise<ConfigData>;
|
|
27
39
|
forBuild(cliArgs: ArgumentsCamelCase<CliBuildOptions>): Promise<ConfigData>;
|
|
28
40
|
}
|
package/lib/config.js
CHANGED
|
@@ -233,6 +233,21 @@ export class Config {
|
|
|
233
233
|
excludeGlobals: config.nodeScript?.excludeGlobals ?? config.excludeGlobals,
|
|
234
234
|
};
|
|
235
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* The config for a command that is NOT a build — `gjsify flatpak …`,
|
|
238
|
+
* `gjsify ship …`.
|
|
239
|
+
*
|
|
240
|
+
* They all want exactly what the loader already produces: the merged
|
|
241
|
+
* `package.json#gjsify` + config-file object, with none of `forBuild`'s
|
|
242
|
+
* build-specific defaulting. Reaching it through `forBuild({} as never)`
|
|
243
|
+
* — five copies of that line before this method existed — lies to the type
|
|
244
|
+
* system and drags a tsconfig read and bundler defaults into a command
|
|
245
|
+
* that has no bundle.
|
|
246
|
+
*/
|
|
247
|
+
async forCommand(searchFrom = process.cwd()) {
|
|
248
|
+
const { config } = await this.load(searchFrom);
|
|
249
|
+
return config;
|
|
250
|
+
}
|
|
236
251
|
async forBuild(cliArgs) {
|
|
237
252
|
const configFile = await this.load(process.cwd());
|
|
238
253
|
const configData = { ...configFile.config };
|
|
@@ -171,6 +171,13 @@ export interface ConfigData {
|
|
|
171
171
|
* defaults declaratively. CLI flags override these.
|
|
172
172
|
*/
|
|
173
173
|
flatpak?: ConfigDataFlatpak;
|
|
174
|
+
/**
|
|
175
|
+
* Config for `gjsify ship` — the installable artifacts (`.deb`, `.rpm`)
|
|
176
|
+
* built from one staged payload. Metadata left unset here falls back to
|
|
177
|
+
* `gjsify.flatpak`, because both blocks describe the SAME application
|
|
178
|
+
* (ADR 0024 § 8) and no project should have to write it twice.
|
|
179
|
+
*/
|
|
180
|
+
ship?: ConfigDataShip;
|
|
174
181
|
/**
|
|
175
182
|
* Config for `gjsify format` / `lint` / `fix`. A thin shell: oxc's own
|
|
176
183
|
* `.oxfmtrc.json` / `.oxlintrc.json` are the real configuration files.
|
|
@@ -197,52 +204,14 @@ export interface ConfigDataTest {
|
|
|
197
204
|
/** Default runtimes when `--runtime` not specified. Default: `['gjs', 'node']`. */
|
|
198
205
|
runtimes?: Array<'gjs' | 'node'>;
|
|
199
206
|
}
|
|
200
|
-
/**
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
*/
|
|
209
|
-
runtime?: 'gnome' | 'freedesktop';
|
|
210
|
-
/** Runtime/SDK version, e.g. `'50'` for GNOME or `'24.08'` for Freedesktop. */
|
|
211
|
-
runtimeVersion?: string;
|
|
212
|
-
/**
|
|
213
|
-
* Extra SDK extensions for the manifest, e.g.
|
|
214
|
-
* `['org.freedesktop.Sdk.Extension.llvm17']` for native code needing a specific
|
|
215
|
-
* toolchain. Leave empty for pure gjsify projects: the GNOME runtime already
|
|
216
|
-
* ships GJS + GLib + libsoup and `gjsify build` produces a self-contained bundle
|
|
217
|
-
* that needs no build-time Node.
|
|
218
|
-
*/
|
|
219
|
-
sdkExtensions?: string[];
|
|
220
|
-
/** Path components prepended to PATH inside the build sandbox. */
|
|
221
|
-
appendPath?: string[];
|
|
222
|
-
/** The binary name to run (`/app/bin/<command>`). Defaults to `appId`. */
|
|
223
|
-
command?: string;
|
|
224
|
-
/** Finish-args (capabilities). Default depends on `runtime` + `--cli-only`. */
|
|
225
|
-
finishArgs?: string[];
|
|
226
|
-
/** Extra Flatpak modules prepended before the app's own meson/simple module (e.g. `blueprint-compiler` build). */
|
|
227
|
-
extraModules?: unknown[];
|
|
228
|
-
/**
|
|
229
|
-
* Full replacement for the manifest's `modules` array: used verbatim, with
|
|
230
|
-
* neither `extraModules` nor the meson default added. For CLI tools that ship a
|
|
231
|
-
* pre-built bundle and install via shell commands (`buildsystem: simple`).
|
|
232
|
-
*/
|
|
233
|
-
modules?: unknown[];
|
|
234
|
-
/** Cleanup glob patterns applied to the final manifest, e.g. `['/include', '/lib/pkgconfig']`. */
|
|
235
|
-
cleanup?: string[];
|
|
236
|
-
/** Source-of-truth lockfile for `gjsify flatpak deps` — `yarn.lock` or `package-lock.json`. */
|
|
237
|
-
lockfile?: string;
|
|
238
|
-
/**
|
|
239
|
-
* GitHub-Actions container image override for `gjsify flatpak ci`. Default is
|
|
240
|
-
* derived from runtime + runtimeVersion, e.g. gnome+50 →
|
|
241
|
-
* `ghcr.io/flathub-infra/flatpak-github-actions:gnome-50`.
|
|
242
|
-
*/
|
|
243
|
-
ciContainer?: string;
|
|
244
|
-
/** Branches the generated workflow triggers on. Default `['main']`. */
|
|
245
|
-
ciBranches?: string[];
|
|
207
|
+
/**
|
|
208
|
+
* The app metadata every install format needs — AppStream MetaInfo fields plus
|
|
209
|
+
* what a freedesktop `.desktop` entry is built from. Shared on purpose: a
|
|
210
|
+
* `.deb`, an `.rpm`, a Flatpak and a macOS bundle all describe the SAME
|
|
211
|
+
* application, so this block belongs to the app rather than to whichever packer
|
|
212
|
+
* happens to read it (ADR 0024 § 8).
|
|
213
|
+
*/
|
|
214
|
+
export interface AppMetadata {
|
|
246
215
|
/**
|
|
247
216
|
* `'app'` (default) → desktop-application MetaInfo, GUI finish-args, .desktop +
|
|
248
217
|
* icon required. `'cli'` → console-application MetaInfo with
|
|
@@ -394,6 +363,127 @@ export interface ConfigDataFlatpak {
|
|
|
394
363
|
displayLengthMin?: number;
|
|
395
364
|
controls?: Array<'keyboard' | 'pointing' | 'touch' | 'gamepad' | 'tablet' | 'console'>;
|
|
396
365
|
};
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* `gjsify ship` configuration — the packaging half of {@link AppMetadata}.
|
|
369
|
+
*
|
|
370
|
+
* Everything here has a derived default; a project that already has a
|
|
371
|
+
* `gjsify.flatpak` block usually needs no `gjsify.ship` block at all.
|
|
372
|
+
*/
|
|
373
|
+
export interface ConfigDataShip extends AppMetadata {
|
|
374
|
+
/** Reverse-DNS app id. Falls back to `gjsify.flatpak.appId`, then `package.json#name`. */
|
|
375
|
+
appId?: string;
|
|
376
|
+
/**
|
|
377
|
+
* Package name and `bin/` entry. Default: `package.json#name` with the npm
|
|
378
|
+
* scope stripped, lowercased, non-alphanumerics folded to `-`.
|
|
379
|
+
*/
|
|
380
|
+
binaryName?: string;
|
|
381
|
+
/** Override the upstream version. Default: `package.json#version`, normalised. */
|
|
382
|
+
version?: string;
|
|
383
|
+
/** Package revision within one upstream version (deb revision / rpm release). Default `'1'`. */
|
|
384
|
+
release?: string;
|
|
385
|
+
/** `Maintainer:` / `Packager:` as `Name <email>`. Default: `package.json#author`. */
|
|
386
|
+
maintainer?: string;
|
|
387
|
+
/** Formats to build when `--target` is not given. Default `['deb', 'rpm']`. */
|
|
388
|
+
targets?: string[];
|
|
389
|
+
/** Output root, relative to the project. Default `'ship'`. */
|
|
390
|
+
outDir?: string;
|
|
391
|
+
/**
|
|
392
|
+
* The built bundle `bin/<name>` executes. Default: `gjsify.main`, then
|
|
393
|
+
* `package.json#main`. Its whole directory is staged into `lib/<name>/`.
|
|
394
|
+
*/
|
|
395
|
+
bundle?: string;
|
|
396
|
+
/** Icon file, or a directory of them. Sizes are read from the path or the filename. */
|
|
397
|
+
icon?: string;
|
|
398
|
+
/** A `*.gschema.xml` file, or a directory of them. */
|
|
399
|
+
schemas?: string;
|
|
400
|
+
/** Licence file. Default: the first of LICENSE, LICENSE.md, LICENSE.txt, COPYING. */
|
|
401
|
+
licenseFile?: string;
|
|
402
|
+
/** deb `Section:`. Default: derived from `categories`. */
|
|
403
|
+
section?: string;
|
|
404
|
+
/** rpm `Group:`. Default: derived from `categories`. */
|
|
405
|
+
group?: string;
|
|
406
|
+
/**
|
|
407
|
+
* Minimum GJS the emitted dependency asks for. Default `'1.86'` — what the
|
|
408
|
+
* bundler targets. Lower it only if the bundle genuinely runs on an older
|
|
409
|
+
* GJS: no released Debian ships 1.86, so the default makes a `.deb` that
|
|
410
|
+
* apt refuses on trixie rather than one that installs and then fails to
|
|
411
|
+
* start.
|
|
412
|
+
*/
|
|
413
|
+
minGjsVersion?: string;
|
|
414
|
+
/**
|
|
415
|
+
* Runtime dependencies appended to the set derived from the bundle's
|
|
416
|
+
* `gi://` imports — the escape hatch for a namespace the table does not
|
|
417
|
+
* know, and for anything that is not a typelib at all.
|
|
418
|
+
*/
|
|
419
|
+
depends?: {
|
|
420
|
+
deb?: string[];
|
|
421
|
+
rpm?: string[];
|
|
422
|
+
};
|
|
423
|
+
/**
|
|
424
|
+
* GI namespace → the package that ships its typelib, filling gaps in the
|
|
425
|
+
* built-in table (`Nautilus-3.0`, a namespace from a private library, …).
|
|
426
|
+
* A row here wins over the built-in one.
|
|
427
|
+
*
|
|
428
|
+
* This is what unblocks a namespace `gjsify ship` does not know; `depends`
|
|
429
|
+
* is for dependencies that are not typelibs at all and deliberately does
|
|
430
|
+
* not silence the unmapped-namespace failure.
|
|
431
|
+
*/
|
|
432
|
+
typelibPackages?: Record<string, {
|
|
433
|
+
deb: string;
|
|
434
|
+
rpm: string;
|
|
435
|
+
}>;
|
|
436
|
+
/** Extra payload entries: prefix-relative destination → project-relative source. */
|
|
437
|
+
extraFiles?: Record<string, string>;
|
|
438
|
+
/** Arguments the launcher appends before the user's own. */
|
|
439
|
+
execArgs?: string[];
|
|
440
|
+
}
|
|
441
|
+
/** Flatpak-toolchain config for the `gjsify flatpak` subcommand group. */
|
|
442
|
+
export interface ConfigDataFlatpak extends AppMetadata {
|
|
443
|
+
/** Reverse-DNS app id, e.g. `eu.jumplink.Learn6502`. Defaults to `package.json#name` if it looks like a reverse-DNS id. */
|
|
444
|
+
appId?: string;
|
|
445
|
+
/**
|
|
446
|
+
* Runtime family. Default `'gnome'` — needed at runtime by GJS bundles
|
|
447
|
+
* for GLib/GObject/GIO. `'freedesktop'` is only suitable for non-gjsify
|
|
448
|
+
* CLI tools (no GJS interpreter ships in the Freedesktop runtime).
|
|
449
|
+
*/
|
|
450
|
+
runtime?: 'gnome' | 'freedesktop';
|
|
451
|
+
/** Runtime/SDK version, e.g. `'50'` for GNOME or `'24.08'` for Freedesktop. */
|
|
452
|
+
runtimeVersion?: string;
|
|
453
|
+
/**
|
|
454
|
+
* Extra SDK extensions for the manifest, e.g.
|
|
455
|
+
* `['org.freedesktop.Sdk.Extension.llvm17']` for native code needing a specific
|
|
456
|
+
* toolchain. Leave empty for pure gjsify projects: the GNOME runtime already
|
|
457
|
+
* ships GJS + GLib + libsoup and `gjsify build` produces a self-contained bundle
|
|
458
|
+
* that needs no build-time Node.
|
|
459
|
+
*/
|
|
460
|
+
sdkExtensions?: string[];
|
|
461
|
+
/** Path components prepended to PATH inside the build sandbox. */
|
|
462
|
+
appendPath?: string[];
|
|
463
|
+
/** The binary name to run (`/app/bin/<command>`). Defaults to `appId`. */
|
|
464
|
+
command?: string;
|
|
465
|
+
/** Finish-args (capabilities). Default depends on `runtime` + `--cli-only`. */
|
|
466
|
+
finishArgs?: string[];
|
|
467
|
+
/** Extra Flatpak modules prepended before the app's own meson/simple module (e.g. `blueprint-compiler` build). */
|
|
468
|
+
extraModules?: unknown[];
|
|
469
|
+
/**
|
|
470
|
+
* Full replacement for the manifest's `modules` array: used verbatim, with
|
|
471
|
+
* neither `extraModules` nor the meson default added. For CLI tools that ship a
|
|
472
|
+
* pre-built bundle and install via shell commands (`buildsystem: simple`).
|
|
473
|
+
*/
|
|
474
|
+
modules?: unknown[];
|
|
475
|
+
/** Cleanup glob patterns applied to the final manifest, e.g. `['/include', '/lib/pkgconfig']`. */
|
|
476
|
+
cleanup?: string[];
|
|
477
|
+
/** Source-of-truth lockfile for `gjsify flatpak deps` — `yarn.lock` or `package-lock.json`. */
|
|
478
|
+
lockfile?: string;
|
|
479
|
+
/**
|
|
480
|
+
* GitHub-Actions container image override for `gjsify flatpak ci`. Default is
|
|
481
|
+
* derived from runtime + runtimeVersion, e.g. gnome+50 →
|
|
482
|
+
* `ghcr.io/flathub-infra/flatpak-github-actions:gnome-50`.
|
|
483
|
+
*/
|
|
484
|
+
ciContainer?: string;
|
|
485
|
+
/** Branches the generated workflow triggers on. Default `['main']`. */
|
|
486
|
+
ciBranches?: string[];
|
|
397
487
|
/**
|
|
398
488
|
* Flathub tracking-repo override for `gjsify flatpak sync-flathub` / `diff`.
|
|
399
489
|
* Default `flathub/<appId>`; set when the upstream repo deviates.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { AppMetadata } from '../types/config-data.js';
|
|
2
|
+
/** Everything a renderer needs that is not part of {@link AppMetadata} itself. */
|
|
3
|
+
export interface AppMetadataInputs {
|
|
4
|
+
/** Reverse-DNS application id — the MetaInfo `<id>` and the icon/desktop basename. */
|
|
5
|
+
appId: string;
|
|
6
|
+
/** Human-readable display name. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** The binary to launch (`Exec=`, and the default `<provides><binary>`). */
|
|
9
|
+
command: string;
|
|
10
|
+
/** `'app'` renders a desktop-application component, `'cli'` a console one. */
|
|
11
|
+
kind: 'app' | 'cli';
|
|
12
|
+
metadata: AppMetadata;
|
|
13
|
+
/**
|
|
14
|
+
* Config path the validator names in its hints, e.g. `gjsify.flatpak` or
|
|
15
|
+
* `gjsify.ship`. Both read the same fields, so the hint has to say which
|
|
16
|
+
* block the caller was actually reading.
|
|
17
|
+
*/
|
|
18
|
+
configKey: string;
|
|
19
|
+
/**
|
|
20
|
+
* Year for the copyright comment. Passed in rather than read from the
|
|
21
|
+
* clock: this XML is PAYLOAD, so `new Date()` here makes the artifact's
|
|
22
|
+
* bytes depend on when it was packed — a reproducibility hole no test
|
|
23
|
+
* running inside one second can see. Defaults to the current year for
|
|
24
|
+
* callers that write the file to a source tree rather than into a package.
|
|
25
|
+
*/
|
|
26
|
+
copyrightYear?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface MissingFieldError {
|
|
29
|
+
field: string;
|
|
30
|
+
hint: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Validate that the metadata has the minimum set of fields AppStream needs.
|
|
34
|
+
* Returns the missing fields with actionable hints; an empty list means OK.
|
|
35
|
+
*/
|
|
36
|
+
export declare function validateAppMetadata(inputs: AppMetadataInputs): MissingFieldError[];
|
|
37
|
+
/** Render the MetaInfo XML for a desktop application. */
|
|
38
|
+
export declare function renderMetainfoApp(inputs: AppMetadataInputs): string;
|
|
39
|
+
/** Render the MetaInfo XML for a console application. */
|
|
40
|
+
export declare function renderMetainfoCli(inputs: AppMetadataInputs): string;
|
|
41
|
+
/**
|
|
42
|
+
* Render the `.desktop` entry (app kind only).
|
|
43
|
+
*
|
|
44
|
+
* `Exec=` carries a field code when the app declares MIME types, because a
|
|
45
|
+
* desktop entry with `MimeType=` and no `%f`/`%u` is registered as a handler
|
|
46
|
+
* that is then launched with no argument — the file the user double-clicked is
|
|
47
|
+
* dropped on the floor, and `desktop-file-validate` says so. `x-scheme-handler/*`
|
|
48
|
+
* wins over file types: a URL handler must take `%u`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function renderDesktopEntry(inputs: AppMetadataInputs): string;
|
|
51
|
+
export declare function escapeXml(value: string): string;
|