@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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// `gjsify flatpak init` — generate a Flatpak manifest from package.json
|
|
2
|
+
// + the `gjsify.flatpak` config namespace.
|
|
3
|
+
//
|
|
4
|
+
// Defaults are designed for the two real-world shapes:
|
|
5
|
+
// * GTK4 + Adwaita apps (Learn6502): `gnome` runtime, GUI finish-args
|
|
6
|
+
// * Headless CLI tools (ts-for-gir): same `gnome` runtime (GJS bundles
|
|
7
|
+
// need GLib/GIO at runtime — Freedesktop ships no GJS), but lean
|
|
8
|
+
// finish-args via `--cli-only`. Memory file
|
|
9
|
+
// `project_flatpak_runtime_choice.md` documents this trade-off.
|
|
10
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { resolve } from 'node:path';
|
|
12
|
+
import { DEFAULT_CLI_FINISH_ARGS, DEFAULT_GUI_FINISH_ARGS, looksLikeAppId, readPackageJson, resolveRuntime, } from './utils.js';
|
|
13
|
+
import { Config } from '../../config.js';
|
|
14
|
+
export const flatpakInitCommand = {
|
|
15
|
+
command: 'init',
|
|
16
|
+
description: 'Generate a Flatpak manifest from package.json + `gjsify.flatpak` config.',
|
|
17
|
+
builder: (yargs) => {
|
|
18
|
+
return yargs
|
|
19
|
+
.option('app-id', {
|
|
20
|
+
description: 'Reverse-DNS app id (default: `gjsify.flatpak.appId` or package.json#name)',
|
|
21
|
+
type: 'string',
|
|
22
|
+
})
|
|
23
|
+
.option('runtime', {
|
|
24
|
+
description: 'Runtime family',
|
|
25
|
+
choices: ['gnome', 'freedesktop'],
|
|
26
|
+
})
|
|
27
|
+
.option('runtime-version', {
|
|
28
|
+
description: 'Runtime version (default: gnome -> 50, freedesktop -> 24.08)',
|
|
29
|
+
type: 'string',
|
|
30
|
+
})
|
|
31
|
+
.option('cli-only', {
|
|
32
|
+
description: 'Strip GUI finish-args; keep `gnome` runtime so GJS is available at runtime',
|
|
33
|
+
type: 'boolean',
|
|
34
|
+
default: false,
|
|
35
|
+
})
|
|
36
|
+
.option('manifest', {
|
|
37
|
+
description: 'Output path. Default: `<app-id>.json` in cwd.',
|
|
38
|
+
type: 'string',
|
|
39
|
+
normalize: true,
|
|
40
|
+
})
|
|
41
|
+
.option('command', {
|
|
42
|
+
description: 'Binary name in /app/bin (default: app id)',
|
|
43
|
+
type: 'string',
|
|
44
|
+
})
|
|
45
|
+
.option('sdk-extension', {
|
|
46
|
+
description: 'Extra SDK extension (repeatable)',
|
|
47
|
+
type: 'string',
|
|
48
|
+
array: true,
|
|
49
|
+
})
|
|
50
|
+
.option('finish-arg', {
|
|
51
|
+
description: 'Extra finish-arg (repeatable). Override defaults entirely with multiple --finish-arg.',
|
|
52
|
+
type: 'string',
|
|
53
|
+
array: true,
|
|
54
|
+
})
|
|
55
|
+
.option('force', {
|
|
56
|
+
description: 'Overwrite an existing manifest',
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
default: false,
|
|
59
|
+
})
|
|
60
|
+
.option('verbose', {
|
|
61
|
+
description: 'Print the resolved manifest fields before writing',
|
|
62
|
+
type: 'boolean',
|
|
63
|
+
default: false,
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
handler: async (args) => {
|
|
67
|
+
const cfg = new Config();
|
|
68
|
+
const configData = await cfg.forBuild({}).catch(() => ({}));
|
|
69
|
+
const flatpak = configData.flatpak ?? {};
|
|
70
|
+
const cwd = process.cwd();
|
|
71
|
+
const pkg = readPackageJson(cwd);
|
|
72
|
+
const appId = args.appId ??
|
|
73
|
+
flatpak.appId ??
|
|
74
|
+
(looksLikeAppId(pkg.name) ? pkg.name : undefined);
|
|
75
|
+
if (!appId) {
|
|
76
|
+
throw new Error('gjsify flatpak init: no app id available. Pass --app-id, set gjsify.flatpak.appId in package.json, ' +
|
|
77
|
+
'or rename the package to a reverse-DNS id like org.example.MyApp.');
|
|
78
|
+
}
|
|
79
|
+
const { runtime, runtimeId, sdk, runtimeVersion } = resolveRuntime(flatpak, {
|
|
80
|
+
runtime: args.runtime,
|
|
81
|
+
runtimeVersion: args.runtimeVersion,
|
|
82
|
+
});
|
|
83
|
+
const sdkExtensions = mergeArrays(flatpak.sdkExtensions, args.sdkExtension);
|
|
84
|
+
const appendPath = flatpak.appendPath ?? (sdkExtensions?.length ? deriveAppendPath(sdkExtensions) : undefined);
|
|
85
|
+
const command = args.command ?? flatpak.command ?? appId;
|
|
86
|
+
const explicitFinishArgs = args.finishArg;
|
|
87
|
+
const cliOnly = args.cliOnly === true;
|
|
88
|
+
const finishArgs = explicitFinishArgs !== undefined
|
|
89
|
+
? explicitFinishArgs
|
|
90
|
+
: flatpak.finishArgs ??
|
|
91
|
+
(cliOnly ? DEFAULT_CLI_FINISH_ARGS : DEFAULT_GUI_FINISH_ARGS);
|
|
92
|
+
const manifest = {
|
|
93
|
+
id: appId,
|
|
94
|
+
runtime: runtimeId,
|
|
95
|
+
'runtime-version': runtimeVersion,
|
|
96
|
+
sdk,
|
|
97
|
+
};
|
|
98
|
+
if (sdkExtensions?.length)
|
|
99
|
+
manifest['sdk-extensions'] = sdkExtensions;
|
|
100
|
+
if (appendPath?.length) {
|
|
101
|
+
manifest['build-options'] = { 'append-path': appendPath.join(':') };
|
|
102
|
+
}
|
|
103
|
+
manifest.command = command;
|
|
104
|
+
manifest['finish-args'] = finishArgs;
|
|
105
|
+
const cleanup = flatpak.cleanup;
|
|
106
|
+
if (cleanup?.length)
|
|
107
|
+
manifest.cleanup = cleanup;
|
|
108
|
+
// Modules: caller-supplied `extraModules` first, then the app's own
|
|
109
|
+
// meson module pointing at the source dir.
|
|
110
|
+
const modules = [];
|
|
111
|
+
if (flatpak.extraModules?.length)
|
|
112
|
+
modules.push(...flatpak.extraModules);
|
|
113
|
+
modules.push({
|
|
114
|
+
name: deriveModuleName(appId),
|
|
115
|
+
buildsystem: 'meson',
|
|
116
|
+
sources: [{ type: 'dir', path: '.' }],
|
|
117
|
+
});
|
|
118
|
+
manifest.modules = modules;
|
|
119
|
+
const out = args.manifest ?? `${appId}.json`;
|
|
120
|
+
const outPath = resolve(cwd, out);
|
|
121
|
+
if (existsSync(outPath) && !args.force) {
|
|
122
|
+
throw new Error(`gjsify flatpak init: ${outPath} exists. Pass --force to overwrite.`);
|
|
123
|
+
}
|
|
124
|
+
const json = JSON.stringify(manifest, null, 4) + '\n';
|
|
125
|
+
writeFileSync(outPath, json, 'utf-8');
|
|
126
|
+
if (args.verbose) {
|
|
127
|
+
console.log(`[gjsify flatpak init] runtime=${runtimeId} ${runtimeVersion} sdk=${sdk}`);
|
|
128
|
+
console.log(`[gjsify flatpak init] command=${command} finish-args=${JSON.stringify(finishArgs)}`);
|
|
129
|
+
}
|
|
130
|
+
console.log(`[gjsify flatpak init] wrote ${outPath}`);
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
/** Concatenate two optional arrays, dropping `undefined`. */
|
|
134
|
+
function mergeArrays(a, b) {
|
|
135
|
+
if (!a?.length && !b?.length)
|
|
136
|
+
return undefined;
|
|
137
|
+
return [...(a ?? []), ...(b ?? [])];
|
|
138
|
+
}
|
|
139
|
+
/** Map known SDK extension ids to their /usr/lib/sdk/<name>/bin paths. */
|
|
140
|
+
function deriveAppendPath(sdkExtensions) {
|
|
141
|
+
const out = [];
|
|
142
|
+
for (const ext of sdkExtensions) {
|
|
143
|
+
const m = /^org\.freedesktop\.Sdk\.Extension\.([A-Za-z0-9-]+)$/.exec(ext);
|
|
144
|
+
if (m)
|
|
145
|
+
out.push(`/usr/lib/sdk/${m[1]}/bin`);
|
|
146
|
+
}
|
|
147
|
+
out.push('/app/bin');
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
/** Last segment of the reverse-DNS id, used as the meson-module name. */
|
|
151
|
+
function deriveModuleName(appId) {
|
|
152
|
+
const parts = appId.split('.');
|
|
153
|
+
return parts[parts.length - 1] || appId;
|
|
154
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ConfigDataFlatpak } from '../../types/config-data.js';
|
|
2
|
+
/**
|
|
3
|
+
* Default GNOME-Platform runtime version. Bumped per release window.
|
|
4
|
+
* GNOME 50 = April 2026 stable; tracked in
|
|
5
|
+
* https://docs.flathub.org/docs/for-app-authors/requirements.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_GNOME_RUNTIME_VERSION = "50";
|
|
8
|
+
/** Default Freedesktop-Platform runtime version (LTS-ish). */
|
|
9
|
+
export declare const DEFAULT_FREEDESKTOP_RUNTIME_VERSION = "24.08";
|
|
10
|
+
/** Permissive GUI defaults for GTK4 + Adwaita apps. */
|
|
11
|
+
export declare const DEFAULT_GUI_FINISH_ARGS: string[];
|
|
12
|
+
/** Lean defaults for headless CLI tools — no display, no GPU. */
|
|
13
|
+
export declare const DEFAULT_CLI_FINISH_ARGS: string[];
|
|
14
|
+
/** Read package.json from a directory. Throws a helpful error if missing/invalid. */
|
|
15
|
+
export declare function readPackageJson(dir: string): Record<string, unknown>;
|
|
16
|
+
/** True if a name string looks like a reverse-DNS Flatpak app id. */
|
|
17
|
+
export declare function looksLikeAppId(value: unknown): value is string;
|
|
18
|
+
/**
|
|
19
|
+
* Pick the runtime + sdk + version triple from config + CLI overrides.
|
|
20
|
+
* `--runtime` and `--runtime-version` flags win over config values.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveRuntime(flatpak: ConfigDataFlatpak | undefined, overrides: {
|
|
23
|
+
runtime?: string;
|
|
24
|
+
runtimeVersion?: string;
|
|
25
|
+
}): {
|
|
26
|
+
runtime: 'gnome' | 'freedesktop';
|
|
27
|
+
runtimeId: string;
|
|
28
|
+
sdk: string;
|
|
29
|
+
runtimeVersion: string;
|
|
30
|
+
};
|
|
31
|
+
/** Default container image for the GitHub Actions workflow. */
|
|
32
|
+
export declare function defaultCiContainer(runtime: 'gnome' | 'freedesktop', runtimeVersion: string): string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Shared helpers for the `gjsify flatpak <sub>` subcommand group.
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* Default GNOME-Platform runtime version. Bumped per release window.
|
|
6
|
+
* GNOME 50 = April 2026 stable; tracked in
|
|
7
|
+
* https://docs.flathub.org/docs/for-app-authors/requirements.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_GNOME_RUNTIME_VERSION = '50';
|
|
10
|
+
/** Default Freedesktop-Platform runtime version (LTS-ish). */
|
|
11
|
+
export const DEFAULT_FREEDESKTOP_RUNTIME_VERSION = '24.08';
|
|
12
|
+
/** Permissive GUI defaults for GTK4 + Adwaita apps. */
|
|
13
|
+
export const DEFAULT_GUI_FINISH_ARGS = [
|
|
14
|
+
'--device=dri',
|
|
15
|
+
'--share=ipc',
|
|
16
|
+
'--socket=fallback-x11',
|
|
17
|
+
'--socket=wayland',
|
|
18
|
+
];
|
|
19
|
+
/** Lean defaults for headless CLI tools — no display, no GPU. */
|
|
20
|
+
export const DEFAULT_CLI_FINISH_ARGS = [];
|
|
21
|
+
/** Read package.json from a directory. Throws a helpful error if missing/invalid. */
|
|
22
|
+
export function readPackageJson(dir) {
|
|
23
|
+
const path = resolve(dir, 'package.json');
|
|
24
|
+
let raw;
|
|
25
|
+
try {
|
|
26
|
+
raw = readFileSync(path, 'utf-8');
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw new Error(`gjsify flatpak: no package.json found at ${path}`);
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(raw);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
throw new Error(`gjsify flatpak: package.json at ${path} is not valid JSON: ${err.message}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** True if a name string looks like a reverse-DNS Flatpak app id. */
|
|
39
|
+
export function looksLikeAppId(value) {
|
|
40
|
+
return typeof value === 'string' && /^[A-Za-z][A-Za-z0-9_-]*(\.[A-Za-z][A-Za-z0-9_-]*){2,}$/.test(value);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Pick the runtime + sdk + version triple from config + CLI overrides.
|
|
44
|
+
* `--runtime` and `--runtime-version` flags win over config values.
|
|
45
|
+
*/
|
|
46
|
+
export function resolveRuntime(flatpak, overrides) {
|
|
47
|
+
const runtime = (overrides.runtime ?? flatpak?.runtime ?? 'gnome');
|
|
48
|
+
if (runtime !== 'gnome' && runtime !== 'freedesktop') {
|
|
49
|
+
throw new Error(`gjsify flatpak: unknown runtime "${runtime}" (expected "gnome" or "freedesktop")`);
|
|
50
|
+
}
|
|
51
|
+
const runtimeVersion = overrides.runtimeVersion ??
|
|
52
|
+
flatpak?.runtimeVersion ??
|
|
53
|
+
(runtime === 'gnome' ? DEFAULT_GNOME_RUNTIME_VERSION : DEFAULT_FREEDESKTOP_RUNTIME_VERSION);
|
|
54
|
+
if (runtime === 'gnome') {
|
|
55
|
+
return { runtime, runtimeId: 'org.gnome.Platform', sdk: 'org.gnome.Sdk', runtimeVersion };
|
|
56
|
+
}
|
|
57
|
+
return { runtime, runtimeId: 'org.freedesktop.Platform', sdk: 'org.freedesktop.Sdk', runtimeVersion };
|
|
58
|
+
}
|
|
59
|
+
/** Default container image for the GitHub Actions workflow. */
|
|
60
|
+
export function defaultCiContainer(runtime, runtimeVersion) {
|
|
61
|
+
const tag = `${runtime}-${runtimeVersion}`;
|
|
62
|
+
return `ghcr.io/flathub-infra/flatpak-github-actions:${tag}`;
|
|
63
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { mkdir } from 'node:fs/promises';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
export const gsettingsCommand = {
|
|
7
|
+
command: 'gsettings <schemadir>',
|
|
8
|
+
description: 'Compile GSettings schema XML files into a binary gschemas.compiled (wraps `glib-compile-schemas`).',
|
|
9
|
+
builder: (yargs) => {
|
|
10
|
+
return yargs
|
|
11
|
+
.positional('schemadir', {
|
|
12
|
+
description: 'Directory containing *.gschema.xml descriptors',
|
|
13
|
+
type: 'string',
|
|
14
|
+
normalize: true,
|
|
15
|
+
demandOption: true,
|
|
16
|
+
})
|
|
17
|
+
.option('targetdir', {
|
|
18
|
+
alias: 't',
|
|
19
|
+
description: 'Directory to write gschemas.compiled (default: <schemadir>)',
|
|
20
|
+
type: 'string',
|
|
21
|
+
normalize: true,
|
|
22
|
+
})
|
|
23
|
+
.option('strict', {
|
|
24
|
+
description: 'Abort on any schema warning (passes --strict to glib-compile-schemas)',
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
default: true,
|
|
27
|
+
})
|
|
28
|
+
.option('verbose', {
|
|
29
|
+
description: 'Print the underlying glib-compile-schemas invocation',
|
|
30
|
+
type: 'boolean',
|
|
31
|
+
default: false,
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
handler: async (args) => {
|
|
35
|
+
const schemadir = resolve(args.schemadir);
|
|
36
|
+
const targetdir = args.targetdir
|
|
37
|
+
? resolve(args.targetdir)
|
|
38
|
+
: schemadir;
|
|
39
|
+
const cmdArgs = [];
|
|
40
|
+
if (args.strict)
|
|
41
|
+
cmdArgs.push('--strict');
|
|
42
|
+
cmdArgs.push(`--targetdir=${targetdir}`);
|
|
43
|
+
cmdArgs.push(schemadir);
|
|
44
|
+
if (args.verbose) {
|
|
45
|
+
console.log(`[gjsify gsettings] glib-compile-schemas ${cmdArgs.join(' ')}`);
|
|
46
|
+
}
|
|
47
|
+
// glib-compile-schemas writes a temporary file in `targetdir` before
|
|
48
|
+
// renaming to gschemas.compiled — fails with ENOENT if missing.
|
|
49
|
+
await mkdir(targetdir, { recursive: true });
|
|
50
|
+
try {
|
|
51
|
+
const { stdout, stderr } = await execFileAsync('glib-compile-schemas', cmdArgs);
|
|
52
|
+
if (stdout)
|
|
53
|
+
process.stdout.write(stdout);
|
|
54
|
+
if (stderr)
|
|
55
|
+
process.stderr.write(stderr);
|
|
56
|
+
if (args.verbose) {
|
|
57
|
+
console.log(`[gjsify gsettings] wrote ${targetdir}/gschemas.compiled`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
if (err?.code === 'ENOENT') {
|
|
62
|
+
console.error('[gjsify gsettings] glib-compile-schemas not found. Install it via your distro (package: glib2-devel / libglib2.0-dev).');
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
if (err?.stderr)
|
|
66
|
+
process.stderr.write(err.stderr);
|
|
67
|
+
console.error(`[gjsify gsettings] glib-compile-schemas failed${err?.code !== undefined ? ` (exit ${err.code})` : ''}`);
|
|
68
|
+
}
|
|
69
|
+
process.exitCode = typeof err?.code === 'number' ? err.code : 1;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
package/lib/commands/index.d.ts
CHANGED
|
@@ -6,5 +6,7 @@ export * from './showcase.js';
|
|
|
6
6
|
export * from './create.js';
|
|
7
7
|
export * from './gresource.js';
|
|
8
8
|
export * from './gettext.js';
|
|
9
|
+
export * from './gsettings.js';
|
|
10
|
+
export { flatpakCommand } from './flatpak/index.js';
|
|
9
11
|
export * from './dlx.js';
|
|
10
12
|
export * from './install.js';
|
package/lib/commands/index.js
CHANGED
|
@@ -6,5 +6,7 @@ export * from './showcase.js';
|
|
|
6
6
|
export * from './create.js';
|
|
7
7
|
export * from './gresource.js';
|
|
8
8
|
export * from './gettext.js';
|
|
9
|
+
export * from './gsettings.js';
|
|
10
|
+
export { flatpakCommand } from './flatpak/index.js';
|
|
9
11
|
export * from './dlx.js';
|
|
10
12
|
export * from './install.js';
|
package/lib/commands/install.js
CHANGED
|
@@ -1,36 +1,65 @@
|
|
|
1
|
-
// `gjsify install [pkg...]` —
|
|
2
|
-
//
|
|
3
|
-
// The actual install is delegated to `npm install` in the user's project root
|
|
4
|
-
// (no `--prefix` rewrite, unlike `gjsify dlx`). After install completes we run
|
|
5
|
-
// `runMinimalChecks()` so missing system deps (gjs, gtk4, libsoup, ...) surface
|
|
6
|
-
// immediately, and report any installed `@gjsify/*` packages that ship native
|
|
7
|
-
// prebuilds so users know they can use `gjsify run` to wire `LD_LIBRARY_PATH` /
|
|
8
|
-
// `GI_TYPELIB_PATH` automatically.
|
|
1
|
+
// `gjsify install [pkg...]` — install packages with gjsify-aware post-checks.
|
|
9
2
|
//
|
|
10
3
|
// Modes:
|
|
11
4
|
// gjsify install → project install (npm install)
|
|
12
|
-
// gjsify install <pkg> [<pkg>...] → add package(s) (npm install <pkg>...)
|
|
5
|
+
// gjsify install <pkg> [<pkg>...] → add package(s) to project (npm install <pkg>...)
|
|
6
|
+
// gjsify install -g <pkg> [...] → user-global install (XDG, GJS-runnable bin)
|
|
7
|
+
//
|
|
8
|
+
// Project mode delegates to `npm install` in cwd and runs `runMinimalChecks()`
|
|
9
|
+
// + `detectNativePackages()` to surface missing system deps and `@gjsify/*`
|
|
10
|
+
// packages with native prebuilds.
|
|
11
|
+
//
|
|
12
|
+
// Global mode is the GJS equivalent of `npm i -g`: extracts the package tree
|
|
13
|
+
// into `${XDG_DATA_HOME}/gjsify/global/node_modules/<pkg>/` via the native
|
|
14
|
+
// install backend (no Node/npm required at runtime), then symlinks the bins
|
|
15
|
+
// declared by `gjsify.bin` (preferred) or `bin` (fallback) into
|
|
16
|
+
// `~/.local/bin/`. Subsequent commands invoked by name resolve to the
|
|
17
|
+
// extracted package, so package-relative assets like `@ts-for-gir/cli`'s
|
|
18
|
+
// `dist-templates/` are found by ordinary `__dirname/..` resolution — no
|
|
19
|
+
// embedded asset stores, no separate release tarballs.
|
|
13
20
|
import { spawn } from 'node:child_process';
|
|
21
|
+
import { mkdirSync } from 'node:fs';
|
|
14
22
|
import { buildInstallCommand, detectPackageManager, runMinimalChecks, } from '../utils/check-system-deps.js';
|
|
15
23
|
import { detectNativePackages } from '../utils/detect-native-packages.js';
|
|
24
|
+
import { installPackages } from '../utils/install-backend.js';
|
|
25
|
+
import { binDirOnPath, defaultGlobalLayout, linkGlobalBins, specToPackageName, } from '../utils/install-global.js';
|
|
16
26
|
export const installCommand = {
|
|
17
27
|
command: 'install [packages..]',
|
|
18
|
-
description: 'Install npm dependencies in the current project, then run gjsify-aware post-checks.',
|
|
28
|
+
description: 'Install npm dependencies in the current project (or globally with -g), then run gjsify-aware post-checks.',
|
|
19
29
|
builder: (yargs) => yargs
|
|
20
30
|
.positional('packages', {
|
|
21
31
|
description: 'Optional package specs. With none, runs a full project install.',
|
|
22
32
|
type: 'string',
|
|
23
33
|
array: true,
|
|
34
|
+
})
|
|
35
|
+
.option('global', {
|
|
36
|
+
description: 'Install into the user-global XDG location and symlink bins into ~/.local/bin.',
|
|
37
|
+
type: 'boolean',
|
|
38
|
+
alias: 'g',
|
|
39
|
+
default: false,
|
|
24
40
|
})
|
|
25
41
|
.option('save-dev', { type: 'boolean', alias: 'D' })
|
|
26
42
|
.option('save-peer', { type: 'boolean' })
|
|
27
43
|
.option('save-optional', { type: 'boolean', alias: 'O' })
|
|
28
44
|
.option('verbose', {
|
|
29
|
-
description: 'Verbose
|
|
45
|
+
description: 'Verbose install logging.',
|
|
30
46
|
type: 'boolean',
|
|
31
47
|
default: false,
|
|
32
48
|
}),
|
|
33
49
|
handler: async (args) => {
|
|
50
|
+
if (args.global) {
|
|
51
|
+
if (!args.packages || args.packages.length === 0) {
|
|
52
|
+
console.error('gjsify install --global requires at least one <pkg> argument.');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
for (const flag of ['save-dev', 'save-peer', 'save-optional']) {
|
|
56
|
+
if (args[flag]) {
|
|
57
|
+
console.warn(`gjsify install --global ignores --${flag}: global installs do not modify a project package.json.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
await installGlobalAndLink(args.packages, { verbose: args.verbose });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
34
63
|
const npmArgs = ['install'];
|
|
35
64
|
if (args['save-dev'])
|
|
36
65
|
npmArgs.push('--save-dev');
|
|
@@ -68,6 +97,32 @@ async function spawnNpm(npmArgs) {
|
|
|
68
97
|
process.exit(1);
|
|
69
98
|
});
|
|
70
99
|
}
|
|
100
|
+
async function installGlobalAndLink(specs, opts) {
|
|
101
|
+
const layout = defaultGlobalLayout();
|
|
102
|
+
mkdirSync(layout.prefix, { recursive: true });
|
|
103
|
+
console.log(`gjsify install --global → ${layout.prefix}`);
|
|
104
|
+
console.log(` bins → ${layout.binDir}`);
|
|
105
|
+
await installPackages({
|
|
106
|
+
prefix: layout.prefix,
|
|
107
|
+
specs,
|
|
108
|
+
verbose: opts.verbose,
|
|
109
|
+
});
|
|
110
|
+
const packageNames = specs.map(specToPackageName);
|
|
111
|
+
const created = linkGlobalBins(packageNames, layout);
|
|
112
|
+
if (created.length === 0) {
|
|
113
|
+
console.warn('\nNo bins declared (neither `gjsify.bin` nor `bin` in package.json) — nothing was symlinked.');
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.log(`\nLinked ${created.length} bin(s):`);
|
|
117
|
+
for (const e of created) {
|
|
118
|
+
console.log(` • ${e.link} → ${e.target}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (created.length > 0 && !binDirOnPath(layout.binDir)) {
|
|
122
|
+
console.warn(`\nNote: ${layout.binDir} is not on your PATH.\n` +
|
|
123
|
+
`Add it to your shell rc file:\n export PATH="${layout.binDir}:$PATH"`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
71
126
|
async function runPostInstallChecks() {
|
|
72
127
|
console.log('\n--- gjsify post-install checks ---');
|
|
73
128
|
// 1. System deps that GJS apps typically need.
|
package/lib/config.js
CHANGED
|
@@ -43,6 +43,23 @@ function merge(target, ...sources) {
|
|
|
43
43
|
function isPlainObject(val) {
|
|
44
44
|
return typeof val === 'object' && val !== null && !Array.isArray(val) && Object.getPrototypeOf(val) === Object.prototype;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Read a dotted path (`a.b.c`) from a plain object. Returns `undefined` for
|
|
48
|
+
* any missing segment. Intentionally narrow — only used for surfacing
|
|
49
|
+
* `package.json` fields into compile-time defines, not for arbitrary deep
|
|
50
|
+
* traversal.
|
|
51
|
+
*/
|
|
52
|
+
function readDottedPath(obj, path) {
|
|
53
|
+
if (!path.includes('.'))
|
|
54
|
+
return obj[path];
|
|
55
|
+
let cursor = obj;
|
|
56
|
+
for (const segment of path.split('.')) {
|
|
57
|
+
if (cursor === null || cursor === undefined || typeof cursor !== 'object')
|
|
58
|
+
return undefined;
|
|
59
|
+
cursor = cursor[segment];
|
|
60
|
+
}
|
|
61
|
+
return cursor;
|
|
62
|
+
}
|
|
46
63
|
export class Config {
|
|
47
64
|
loadOptions = {};
|
|
48
65
|
constructor(loadOptions = {}) {
|
|
@@ -155,6 +172,34 @@ export class Config {
|
|
|
155
172
|
if (Object.keys(aliasMap).length) {
|
|
156
173
|
configData.aliases = { ...(configData.aliases ?? {}), ...aliasMap };
|
|
157
174
|
}
|
|
175
|
+
// Resolve `defineFromPackageJson` / `defineFromEnv` into raw
|
|
176
|
+
// KEY=<JSON-stringified value> entries that get merged into the
|
|
177
|
+
// bundler's `transform.define` map below. Both produce JS expressions
|
|
178
|
+
// (the value side of a Rolldown define is substituted at the call
|
|
179
|
+
// site, not stringified again) — so a missing env variable resolves
|
|
180
|
+
// to the literal `undefined`, letting consumer code use
|
|
181
|
+
// `typeof X === 'undefined'` or `X ?? fallback` guards.
|
|
182
|
+
const fromPkgDefines = {};
|
|
183
|
+
if (configData.defineFromPackageJson) {
|
|
184
|
+
for (const [name, spec] of Object.entries(configData.defineFromPackageJson)) {
|
|
185
|
+
if (!spec || typeof spec.field !== 'string' || !spec.field) {
|
|
186
|
+
throw new Error(`gjsify config: defineFromPackageJson["${name}"] is missing a "field" string`);
|
|
187
|
+
}
|
|
188
|
+
const value = readDottedPath(pkg, spec.field);
|
|
189
|
+
fromPkgDefines[name] = value === undefined ? 'undefined' : JSON.stringify(value);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const fromEnvDefines = {};
|
|
193
|
+
if (configData.defineFromEnv) {
|
|
194
|
+
for (const [name, spec] of Object.entries(configData.defineFromEnv)) {
|
|
195
|
+
if (!spec || typeof spec.env !== 'string' || !spec.env) {
|
|
196
|
+
throw new Error(`gjsify config: defineFromEnv["${name}"] is missing an "env" string`);
|
|
197
|
+
}
|
|
198
|
+
const raw = process.env[spec.env];
|
|
199
|
+
const value = raw !== undefined ? raw : spec.default;
|
|
200
|
+
fromEnvDefines[name] = value === undefined ? 'undefined' : JSON.stringify(value);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
158
203
|
// Merge CLI flags into the Rolldown-shape `bundler` field. Mappings:
|
|
159
204
|
// --entry-points → bundler.input
|
|
160
205
|
// --outfile → bundler.output.file
|
|
@@ -202,8 +247,15 @@ export class Config {
|
|
|
202
247
|
const userExternal = Array.isArray(bundler.external) ? bundler.external : [];
|
|
203
248
|
bundler.external = [...userExternal, ...cliArgs.external];
|
|
204
249
|
}
|
|
205
|
-
if (Object.keys(defineMap).length) {
|
|
206
|
-
|
|
250
|
+
if (Object.keys(defineMap).length || Object.keys(fromPkgDefines).length || Object.keys(fromEnvDefines).length) {
|
|
251
|
+
// CLI --define wins over package.json/env (manual overrides during
|
|
252
|
+
// debugging beat declarative config).
|
|
253
|
+
transform.define = {
|
|
254
|
+
...(transform.define ?? {}),
|
|
255
|
+
...fromPkgDefines,
|
|
256
|
+
...fromEnvDefines,
|
|
257
|
+
...defineMap,
|
|
258
|
+
};
|
|
207
259
|
}
|
|
208
260
|
if (configData.verbose)
|
|
209
261
|
console.debug("configData", configData);
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import yargs from 'yargs';
|
|
3
3
|
import { hideBin } from 'yargs/helpers';
|
|
4
|
-
import { buildCommand as build, runCommand as run, infoCommand as info, checkCommand as check, showcaseCommand as showcase, createCommand as create, gresourceCommand as gresource, gettextCommand as gettext, dlxCommand as dlx, installCommand as install, } from './commands/index.js';
|
|
4
|
+
import { buildCommand as build, runCommand as run, infoCommand as info, checkCommand as check, showcaseCommand as showcase, createCommand as create, gresourceCommand as gresource, gettextCommand as gettext, gsettingsCommand as gsettings, flatpakCommand as flatpak, dlxCommand as dlx, installCommand as install, } from './commands/index.js';
|
|
5
5
|
import { APP_NAME } from './constants.js';
|
|
6
6
|
void yargs(hideBin(process.argv))
|
|
7
7
|
.scriptName(APP_NAME)
|
|
@@ -16,5 +16,7 @@ void yargs(hideBin(process.argv))
|
|
|
16
16
|
.command(showcase.command, showcase.description, showcase.builder, showcase.handler)
|
|
17
17
|
.command(gresource.command, gresource.description, gresource.builder, gresource.handler)
|
|
18
18
|
.command(gettext.command, gettext.description, gettext.builder, gettext.handler)
|
|
19
|
+
.command(gsettings.command, gsettings.description, gsettings.builder, gsettings.handler)
|
|
20
|
+
.command(flatpak.command, flatpak.description, flatpak.builder, flatpak.handler)
|
|
19
21
|
.demandCommand(1)
|
|
20
22
|
.help().argv;
|