@gjsify/cli 0.1.8 → 0.1.10
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 +13 -0
- package/lib/actions/build.js +60 -18
- package/lib/commands/build.js +2 -2
- package/lib/commands/check.js +41 -12
- package/lib/commands/showcase.js +6 -4
- package/lib/utils/check-system-deps.d.ts +14 -2
- package/lib/utils/check-system-deps.js +214 -46
- package/lib/utils/detect-native-packages.d.ts +4 -0
- package/lib/utils/detect-native-packages.js +12 -0
- package/package.json +10 -9
- package/src/actions/build.ts +72 -20
- package/src/commands/build.ts +2 -2
- package/src/commands/check.ts +45 -12
- package/src/commands/showcase.ts +6 -4
- package/src/utils/check-system-deps.ts +264 -45
- package/src/utils/detect-native-packages.ts +12 -0
package/src/commands/check.ts
CHANGED
|
@@ -7,7 +7,7 @@ interface CheckOptions {
|
|
|
7
7
|
|
|
8
8
|
export const checkCommand: Command<any, CheckOptions> = {
|
|
9
9
|
command: 'check',
|
|
10
|
-
description: 'Check that
|
|
10
|
+
description: 'Check that required system dependencies (GJS, GTK4, libsoup3, …) are installed. Optional dependencies are detected only when their @gjsify/* package is in your project.',
|
|
11
11
|
builder: (yargs) => {
|
|
12
12
|
return yargs
|
|
13
13
|
.option('json', {
|
|
@@ -19,36 +19,69 @@ export const checkCommand: Command<any, CheckOptions> = {
|
|
|
19
19
|
handler: async (args) => {
|
|
20
20
|
const results = runAllChecks(process.cwd());
|
|
21
21
|
const pm = detectPackageManager();
|
|
22
|
-
const
|
|
22
|
+
const missingRequired = results.filter(r => !r.found && r.severity === 'required');
|
|
23
|
+
const missingOptional = results.filter(r => !r.found && r.severity === 'optional');
|
|
24
|
+
const allMissing = [...missingRequired, ...missingOptional];
|
|
23
25
|
|
|
24
26
|
if (args.json) {
|
|
25
27
|
console.log(JSON.stringify({ packageManager: pm, deps: results }, null, 2));
|
|
26
|
-
|
|
28
|
+
// Only required deps influence the exit code.
|
|
29
|
+
process.exit(missingRequired.length > 0 ? 1 : 0);
|
|
27
30
|
return;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
console.log('System dependency check\n');
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
const required = results.filter(r => r.severity === 'required');
|
|
36
|
+
const optional = results.filter(r => r.severity === 'optional');
|
|
37
|
+
|
|
38
|
+
if (required.length > 0) {
|
|
39
|
+
console.log('Required:');
|
|
40
|
+
for (const dep of required) {
|
|
41
|
+
const icon = dep.found ? '✓' : '✗';
|
|
42
|
+
const ver = dep.version ? ` (${dep.version})` : '';
|
|
43
|
+
console.log(` ${icon} ${dep.name}${ver}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (optional.length > 0) {
|
|
48
|
+
console.log('\nOptional:');
|
|
49
|
+
for (const dep of optional) {
|
|
50
|
+
// ⚠ for missing-but-needed-by-installed-packages, ○ for missing-but-not-needed (shouldn't appear in conditional mode)
|
|
51
|
+
const icon = dep.found ? '✓' : '⚠';
|
|
52
|
+
const ver = dep.version ? ` (${dep.version})` : '';
|
|
53
|
+
const requiredBy = dep.requiredBy && dep.requiredBy.length > 0
|
|
54
|
+
? ` — needed by ${dep.requiredBy.join(', ')}`
|
|
55
|
+
: '';
|
|
56
|
+
console.log(` ${icon} ${dep.name}${ver}${requiredBy}`);
|
|
57
|
+
}
|
|
35
58
|
}
|
|
36
59
|
|
|
37
60
|
console.log(`\nPackage manager: ${pm}`);
|
|
38
61
|
|
|
39
|
-
if (
|
|
62
|
+
if (allMissing.length === 0) {
|
|
40
63
|
console.log('\nAll dependencies found.');
|
|
41
64
|
return;
|
|
42
65
|
}
|
|
43
66
|
|
|
44
|
-
|
|
45
|
-
|
|
67
|
+
if (missingRequired.length > 0) {
|
|
68
|
+
console.log(`\nMissing required: ${missingRequired.map(d => d.name).join(', ')}`);
|
|
69
|
+
}
|
|
70
|
+
if (missingOptional.length > 0) {
|
|
71
|
+
console.log(`Missing optional: ${missingOptional.map(d => d.name).join(', ')}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const cmd = buildInstallCommand(pm, allMissing);
|
|
46
75
|
if (cmd) {
|
|
47
|
-
console.log(`\nTo install
|
|
76
|
+
console.log(`\nTo install:\n ${cmd}`);
|
|
48
77
|
} else {
|
|
49
78
|
console.log('\nNo install command available for your package manager. Install manually.');
|
|
50
79
|
}
|
|
51
80
|
|
|
52
|
-
|
|
81
|
+
// Exit non-zero ONLY if a required dependency is missing.
|
|
82
|
+
// Optional deps that are missing but needed by an installed @gjsify/*
|
|
83
|
+
// package generate a warning but keep exit code 0 — the user can still
|
|
84
|
+
// build/run code paths that don't touch the optional library.
|
|
85
|
+
process.exit(missingRequired.length > 0 ? 1 : 0);
|
|
53
86
|
},
|
|
54
87
|
};
|
package/src/commands/showcase.ts
CHANGED
|
@@ -83,14 +83,16 @@ export const showcaseCommand: Command<any, ShowcaseOptions> = {
|
|
|
83
83
|
if (needsWebgl) {
|
|
84
84
|
results.push(checkGwebgl(process.cwd()));
|
|
85
85
|
}
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
// Hard-fail only on missing REQUIRED deps (gjs, gwebgl is required if needsWebgl).
|
|
87
|
+
// For showcase, gwebgl is treated as required because the bundle won't run without it.
|
|
88
|
+
const missingHard = results.filter(r => !r.found && (r.severity === 'required' || r.id === 'gwebgl'));
|
|
89
|
+
if (missingHard.length > 0) {
|
|
88
90
|
console.error('Missing system dependencies:\n');
|
|
89
|
-
for (const dep of
|
|
91
|
+
for (const dep of missingHard) {
|
|
90
92
|
console.error(` ✗ ${dep.name}`);
|
|
91
93
|
}
|
|
92
94
|
const pm = detectPackageManager();
|
|
93
|
-
const cmd = buildInstallCommand(pm,
|
|
95
|
+
const cmd = buildInstallCommand(pm, missingHard);
|
|
94
96
|
if (cmd) {
|
|
95
97
|
console.error(`\nInstall with:\n ${cmd}`);
|
|
96
98
|
}
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
// System dependency checker for gjsify CLI.
|
|
2
2
|
// Uses execFileSync with explicit argument arrays — no shell injection possible.
|
|
3
3
|
// All binary names are hardcoded constants, never derived from user input.
|
|
4
|
+
//
|
|
5
|
+
// Severity model:
|
|
6
|
+
// - 'required' → must be present, exit code 1 if missing
|
|
7
|
+
// - 'optional' → nice to have, only warned about, exit code stays 0
|
|
8
|
+
//
|
|
9
|
+
// Conditional checking: when a project's package.json is reachable from cwd,
|
|
10
|
+
// optional system deps are only checked if the corresponding @gjsify/* package
|
|
11
|
+
// is in the project's dependency tree. A user with only @gjsify/fs in their
|
|
12
|
+
// project never sees a warning about libmanette.
|
|
4
13
|
|
|
5
14
|
import { execFileSync } from 'node:child_process';
|
|
6
|
-
import { join } from 'node:path';
|
|
15
|
+
import { join, resolve } from 'node:path';
|
|
7
16
|
import { createRequire } from 'node:module';
|
|
8
17
|
import { pathToFileURL } from 'node:url';
|
|
18
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
19
|
+
|
|
20
|
+
export type DepSeverity = 'required' | 'optional';
|
|
9
21
|
|
|
10
22
|
export interface DepCheck {
|
|
11
23
|
/** Stable key used for install command lookup, e.g. "gjs" */
|
|
@@ -15,6 +27,10 @@ export interface DepCheck {
|
|
|
15
27
|
found: boolean;
|
|
16
28
|
/** Version string when found, e.g. "1.86.0" */
|
|
17
29
|
version?: string;
|
|
30
|
+
/** required = exit 1 if missing, optional = warn only */
|
|
31
|
+
severity: DepSeverity;
|
|
32
|
+
/** For optional deps: which @gjsify/* packages need this lib */
|
|
33
|
+
requiredBy?: string[];
|
|
18
34
|
}
|
|
19
35
|
|
|
20
36
|
export type PackageManager = 'apt' | 'dnf' | 'pacman' | 'zypper' | 'apk' | 'unknown';
|
|
@@ -29,18 +45,32 @@ function tryExecFile(binary: string, args: string[]): string | null {
|
|
|
29
45
|
}
|
|
30
46
|
|
|
31
47
|
/** Check if a binary exists and optionally capture its version output. */
|
|
32
|
-
function checkBinary(
|
|
48
|
+
function checkBinary(
|
|
49
|
+
id: string,
|
|
50
|
+
name: string,
|
|
51
|
+
binary: string,
|
|
52
|
+
versionArgs: string[],
|
|
53
|
+
severity: DepSeverity,
|
|
54
|
+
parseVersion?: (out: string) => string,
|
|
55
|
+
requiredBy?: string[],
|
|
56
|
+
): DepCheck {
|
|
33
57
|
const out = tryExecFile(binary, versionArgs);
|
|
34
|
-
if (out === null) return { id, name, found: false };
|
|
58
|
+
if (out === null) return { id, name, found: false, severity, requiredBy };
|
|
35
59
|
const version = parseVersion ? parseVersion(out) : out.split('\n')[0] ?? out;
|
|
36
|
-
return { id, name, found: true, version };
|
|
60
|
+
return { id, name, found: true, version, severity, requiredBy };
|
|
37
61
|
}
|
|
38
62
|
|
|
39
63
|
/** Check a pkg-config library. pkg-config --modversion returns version on stdout. */
|
|
40
|
-
function checkPkgConfig(
|
|
64
|
+
function checkPkgConfig(
|
|
65
|
+
id: string,
|
|
66
|
+
name: string,
|
|
67
|
+
libName: string,
|
|
68
|
+
severity: DepSeverity,
|
|
69
|
+
requiredBy?: string[],
|
|
70
|
+
): DepCheck {
|
|
41
71
|
const version = tryExecFile('pkg-config', ['--modversion', libName]);
|
|
42
|
-
if (version === null) return { id, name, found: false };
|
|
43
|
-
return { id, name, found: true, version: version.split('\n')[0] };
|
|
72
|
+
if (version === null) return { id, name, found: false, severity, requiredBy };
|
|
73
|
+
return { id, name, found: true, version: version.split('\n')[0], severity, requiredBy };
|
|
44
74
|
}
|
|
45
75
|
|
|
46
76
|
/**
|
|
@@ -48,21 +78,28 @@ function checkPkgConfig(id: string, name: string, libName: string): DepCheck {
|
|
|
48
78
|
* back to the CLI's own node_modules. This way a locally installed version
|
|
49
79
|
* takes precedence, but npx usage still works via the CLI's own dependencies.
|
|
50
80
|
*/
|
|
51
|
-
function checkNpmPackage(
|
|
81
|
+
function checkNpmPackage(
|
|
82
|
+
id: string,
|
|
83
|
+
name: string,
|
|
84
|
+
packageName: string,
|
|
85
|
+
cwd: string,
|
|
86
|
+
severity: DepSeverity,
|
|
87
|
+
requiredBy?: string[],
|
|
88
|
+
): DepCheck {
|
|
52
89
|
// 1. Try user's project
|
|
53
90
|
try {
|
|
54
91
|
const requireFromCwd = createRequire(pathToFileURL(join(cwd, '_check_.js')).href);
|
|
55
92
|
requireFromCwd.resolve(packageName);
|
|
56
|
-
return { id, name, found: true };
|
|
93
|
+
return { id, name, found: true, severity, requiredBy };
|
|
57
94
|
} catch { /* not in project, try CLI fallback */ }
|
|
58
95
|
|
|
59
96
|
// 2. Fallback: CLI's own node_modules
|
|
60
97
|
try {
|
|
61
98
|
const requireFromCli = createRequire(import.meta.url);
|
|
62
99
|
requireFromCli.resolve(packageName);
|
|
63
|
-
return { id, name, found: true };
|
|
100
|
+
return { id, name, found: true, severity, requiredBy };
|
|
64
101
|
} catch {
|
|
65
|
-
return { id, name, found: false };
|
|
102
|
+
return { id, name, found: false, severity, requiredBy };
|
|
66
103
|
}
|
|
67
104
|
}
|
|
68
105
|
|
|
@@ -75,72 +112,219 @@ export function detectPackageManager(): PackageManager {
|
|
|
75
112
|
return 'unknown';
|
|
76
113
|
}
|
|
77
114
|
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// @gjsify/* package → system dependency mapping
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
//
|
|
119
|
+
// Lists which OPTIONAL system dependencies each gjsify package needs at
|
|
120
|
+
// runtime. Required deps (gjs, gtk4, libsoup3, libadwaita, etc.) are checked
|
|
121
|
+
// unconditionally because they're needed by the core toolchain.
|
|
122
|
+
//
|
|
123
|
+
// Used by `runAllChecks(cwd)` to skip optional deps for packages the user
|
|
124
|
+
// isn't using. If a project doesn't depend on @gjsify/gamepad, libmanette
|
|
125
|
+
// is not checked (and won't appear as a warning).
|
|
126
|
+
|
|
127
|
+
interface OptionalDep {
|
|
128
|
+
/** DepCheck id */
|
|
129
|
+
id: string;
|
|
130
|
+
/** Display name */
|
|
131
|
+
name: string;
|
|
132
|
+
/** pkg-config library name */
|
|
133
|
+
pkgName: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Optional system deps keyed by their DepCheck id. */
|
|
137
|
+
const OPTIONAL_DEPS: Record<string, OptionalDep> = {
|
|
138
|
+
manette: { id: 'manette', name: 'libmanette', pkgName: 'manette-0.2' },
|
|
139
|
+
gstreamer: { id: 'gstreamer', name: 'GStreamer', pkgName: 'gstreamer-1.0' },
|
|
140
|
+
'gst-app': { id: 'gst-app', name: 'GStreamer App', pkgName: 'gstreamer-app-1.0' },
|
|
141
|
+
'gdk-pixbuf':{ id: 'gdk-pixbuf', name: 'GdkPixbuf', pkgName: 'gdk-pixbuf-2.0' },
|
|
142
|
+
pango: { id: 'pango', name: 'Pango', pkgName: 'pango' },
|
|
143
|
+
pangocairo: { id: 'pangocairo', name: 'PangoCairo', pkgName: 'pangocairo' },
|
|
144
|
+
webkitgtk: { id: 'webkitgtk', name: 'WebKitGTK', pkgName: 'webkitgtk-6.0' },
|
|
145
|
+
cairo: { id: 'cairo', name: 'Cairo', pkgName: 'cairo' },
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Map of @gjsify/* package name → ids of OPTIONAL_DEPS this package needs.
|
|
150
|
+
* Generated by walking each package's `gi://` imports (excluding the always-
|
|
151
|
+
* available trio GLib/GObject/Gio).
|
|
152
|
+
*
|
|
153
|
+
* Used to compute the set of optional deps to check for a given project.
|
|
154
|
+
*/
|
|
155
|
+
const PACKAGE_DEPS: Record<string, string[]> = {
|
|
156
|
+
'@gjsify/gamepad': ['manette'],
|
|
157
|
+
'@gjsify/webaudio': ['gstreamer', 'gst-app'],
|
|
158
|
+
'@gjsify/iframe': ['webkitgtk'],
|
|
159
|
+
'@gjsify/canvas2d': ['gdk-pixbuf', 'pango', 'pangocairo', 'cairo'],
|
|
160
|
+
'@gjsify/canvas2d-core': ['gdk-pixbuf', 'pango', 'pangocairo', 'cairo'],
|
|
161
|
+
'@gjsify/dom-elements': ['gdk-pixbuf'],
|
|
162
|
+
// @gjsify/webgl, @gjsify/event-bridge only need gtk4/gdk which are
|
|
163
|
+
// already in the required set, so they don't need optional entries.
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/** Walk up from cwd looking for the nearest package.json. */
|
|
167
|
+
function findProjectRoot(cwd: string): string | null {
|
|
168
|
+
let dir = resolve(cwd);
|
|
169
|
+
while (true) {
|
|
170
|
+
if (existsSync(join(dir, 'package.json'))) return dir;
|
|
171
|
+
const parent = resolve(dir, '..');
|
|
172
|
+
if (parent === dir) return null;
|
|
173
|
+
dir = parent;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Discover which @gjsify/* packages a project depends on (transitively),
|
|
179
|
+
* by walking the node_modules tree from the project root. Returns a Set of
|
|
180
|
+
* package names like '@gjsify/webgl', '@gjsify/canvas2d'.
|
|
181
|
+
*
|
|
182
|
+
* If the project root cannot be determined or has no node_modules, returns
|
|
183
|
+
* `null` to signal "check all optional deps" — this matches the historical
|
|
184
|
+
* behaviour where `gjsify check` outside any project shows the full list.
|
|
185
|
+
*/
|
|
186
|
+
function discoverGjsifyPackages(cwd: string): Set<string> | null {
|
|
187
|
+
const root = findProjectRoot(cwd);
|
|
188
|
+
if (!root) return null;
|
|
189
|
+
|
|
190
|
+
const nodeModulesDir = join(root, 'node_modules', '@gjsify');
|
|
191
|
+
if (!existsSync(nodeModulesDir)) {
|
|
192
|
+
// Project exists but no @gjsify/* installed → only need core checks.
|
|
193
|
+
return new Set();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Read top-level package.json to determine if @gjsify/cli is the only
|
|
197
|
+
// dep (in which case we still want to warn about everything the CLI
|
|
198
|
+
// transitively brings in). Otherwise scan node_modules for installed packages.
|
|
199
|
+
const pkgJsonPath = join(root, 'package.json');
|
|
200
|
+
let topPkg: Record<string, unknown> = {};
|
|
201
|
+
try {
|
|
202
|
+
topPkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
203
|
+
} catch { /* ignore */ }
|
|
204
|
+
|
|
205
|
+
const directDeps = {
|
|
206
|
+
...(topPkg.dependencies as Record<string, string> | undefined),
|
|
207
|
+
...(topPkg.devDependencies as Record<string, string> | undefined),
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const found = new Set<string>();
|
|
211
|
+
try {
|
|
212
|
+
for (const entry of readdirSync(nodeModulesDir, { withFileTypes: true })) {
|
|
213
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
214
|
+
found.add(`@gjsify/${entry.name}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch { /* ignore */ }
|
|
218
|
+
|
|
219
|
+
// Also include direct deps even if node_modules walk failed
|
|
220
|
+
for (const dep of Object.keys(directDeps)) {
|
|
221
|
+
if (dep.startsWith('@gjsify/')) found.add(dep);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return found;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Compute the set of optional dep ids that should be checked for the
|
|
229
|
+
* current project. Returns null to indicate "check all" (no project context).
|
|
230
|
+
*/
|
|
231
|
+
function computeNeededOptionalDeps(cwd: string): Set<string> | null {
|
|
232
|
+
const installedPackages = discoverGjsifyPackages(cwd);
|
|
233
|
+
if (installedPackages === null) return null; // no project → check everything
|
|
234
|
+
|
|
235
|
+
const needed = new Set<string>();
|
|
236
|
+
for (const pkg of installedPackages) {
|
|
237
|
+
const deps = PACKAGE_DEPS[pkg];
|
|
238
|
+
if (deps) for (const id of deps) needed.add(id);
|
|
239
|
+
}
|
|
240
|
+
return needed;
|
|
241
|
+
}
|
|
242
|
+
|
|
78
243
|
/**
|
|
79
244
|
* Run all dependency checks. Used by `gjsify check` to show full system status.
|
|
245
|
+
*
|
|
246
|
+
* Required deps (gjs, gtk4, libsoup3, libadwaita, gobject-introspection,
|
|
247
|
+
* blueprint-compiler, pkg-config, meson) are always checked.
|
|
248
|
+
*
|
|
249
|
+
* Optional deps are checked conditionally based on which @gjsify/* packages
|
|
250
|
+
* the project (resolved from cwd) actually consumes. If no project context
|
|
251
|
+
* is available, all optional deps are checked.
|
|
80
252
|
*/
|
|
81
253
|
export function runAllChecks(cwd: string): DepCheck[] {
|
|
82
|
-
|
|
254
|
+
const needed = computeNeededOptionalDeps(cwd);
|
|
255
|
+
return [...runMinimalChecks(), ...runRequiredChecks(cwd), ...runOptionalChecks(needed, cwd)];
|
|
83
256
|
}
|
|
84
257
|
|
|
85
258
|
/**
|
|
86
|
-
* Minimal checks needed to run any GJS example (GJS
|
|
259
|
+
* Minimal checks needed to run any GJS example (Node + GJS binaries only).
|
|
87
260
|
* Used by `gjsify showcase` for examples that have no native deps.
|
|
88
261
|
*/
|
|
89
262
|
export function runMinimalChecks(): DepCheck[] {
|
|
90
263
|
const results: DepCheck[] = [];
|
|
91
264
|
|
|
92
265
|
// Node.js — always present
|
|
93
|
-
results.push({ id: 'nodejs', name: 'Node.js', found: true, version: process.version });
|
|
266
|
+
results.push({ id: 'nodejs', name: 'Node.js', found: true, version: process.version, severity: 'required' });
|
|
94
267
|
|
|
95
268
|
// GJS
|
|
96
|
-
results.push(checkBinary('gjs', 'GJS', 'gjs', ['--version'],
|
|
269
|
+
results.push(checkBinary('gjs', 'GJS', 'gjs', ['--version'], 'required',
|
|
97
270
|
(out) => out.replace(/^GJS\s+/i, '').split('\n')[0] ?? out));
|
|
98
271
|
|
|
99
272
|
return results;
|
|
100
273
|
}
|
|
101
274
|
|
|
102
|
-
/** Check gwebgl npm package (project first, CLI fallback). */
|
|
275
|
+
/** Check gwebgl npm package (project first, CLI fallback). Optional — only needed by @gjsify/webgl users. */
|
|
103
276
|
export function checkGwebgl(cwd: string): DepCheck {
|
|
104
|
-
return checkNpmPackage('gwebgl', 'gwebgl (@gjsify/webgl)', '@gjsify/webgl', cwd);
|
|
277
|
+
return checkNpmPackage('gwebgl', 'gwebgl (@gjsify/webgl)', '@gjsify/webgl', cwd, 'optional', ['@gjsify/webgl']);
|
|
105
278
|
}
|
|
106
279
|
|
|
107
280
|
/**
|
|
108
|
-
*
|
|
281
|
+
* Required system dependencies — always checked, missing → exit 1.
|
|
282
|
+
* Includes the core build toolchain (pkg-config, meson, blueprint-compiler)
|
|
283
|
+
* and the foundational libraries (gtk4, libadwaita, libsoup3,
|
|
284
|
+
* gobject-introspection) that nearly every gjsify app needs.
|
|
109
285
|
*/
|
|
110
|
-
function
|
|
286
|
+
function runRequiredChecks(_cwd: string): DepCheck[] {
|
|
111
287
|
const results: DepCheck[] = [];
|
|
112
288
|
|
|
113
|
-
//
|
|
289
|
+
// Build toolchain
|
|
114
290
|
results.push(checkBinary('blueprint-compiler', 'Blueprint Compiler',
|
|
115
|
-
'blueprint-compiler', ['--version']));
|
|
291
|
+
'blueprint-compiler', ['--version'], 'required'));
|
|
292
|
+
results.push(checkBinary('pkg-config', 'pkg-config', 'pkg-config', ['--version'], 'required'));
|
|
293
|
+
results.push(checkBinary('meson', 'Meson', 'meson', ['--version'], 'required'));
|
|
116
294
|
|
|
117
|
-
//
|
|
118
|
-
results.push(
|
|
295
|
+
// Foundational libraries
|
|
296
|
+
results.push(checkPkgConfig('gtk4', 'GTK4', 'gtk4', 'required'));
|
|
297
|
+
results.push(checkPkgConfig('libadwaita', 'libadwaita', 'libadwaita-1', 'required'));
|
|
298
|
+
results.push(checkPkgConfig('libsoup3', 'libsoup3', 'libsoup-3.0', 'required'));
|
|
299
|
+
results.push(checkPkgConfig('gobject-introspection', 'GObject Introspection',
|
|
300
|
+
'gobject-introspection-1.0', 'required'));
|
|
119
301
|
|
|
120
|
-
|
|
121
|
-
|
|
302
|
+
return results;
|
|
303
|
+
}
|
|
122
304
|
|
|
123
|
-
|
|
124
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Optional system dependencies — only checked if the corresponding @gjsify/*
|
|
307
|
+
* package is in use. Missing optional deps generate warnings, not errors.
|
|
308
|
+
*
|
|
309
|
+
* @param needed Set of optional dep ids to check, or null to check all.
|
|
310
|
+
* @param cwd Used to resolve the gwebgl npm package check.
|
|
311
|
+
*/
|
|
312
|
+
function runOptionalChecks(needed: Set<string> | null, cwd: string): DepCheck[] {
|
|
313
|
+
const results: DepCheck[] = [];
|
|
125
314
|
|
|
126
|
-
|
|
127
|
-
|
|
315
|
+
for (const [id, dep] of Object.entries(OPTIONAL_DEPS)) {
|
|
316
|
+
if (needed !== null && !needed.has(id)) continue;
|
|
128
317
|
|
|
129
|
-
|
|
130
|
-
|
|
318
|
+
const requiredBy = Object.entries(PACKAGE_DEPS)
|
|
319
|
+
.filter(([, ids]) => ids.includes(id))
|
|
320
|
+
.map(([pkg]) => pkg);
|
|
131
321
|
|
|
132
|
-
|
|
133
|
-
const webkitCheck = checkPkgConfig('webkitgtk', 'WebKitGTK', 'webkit2gtk-4.1');
|
|
134
|
-
if (webkitCheck.found) {
|
|
135
|
-
results.push(webkitCheck);
|
|
136
|
-
} else {
|
|
137
|
-
results.push(checkPkgConfig('webkitgtk', 'WebKitGTK', 'webkitgtk-6.0'));
|
|
322
|
+
results.push(checkPkgConfig(dep.id, dep.name, dep.pkgName, 'optional', requiredBy));
|
|
138
323
|
}
|
|
139
324
|
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// gwebgl — project first, CLI fallback.
|
|
325
|
+
// gwebgl npm package — special case (not a pkg-config lib).
|
|
326
|
+
// Always reported (the npm package is bundled with the CLI), but marked
|
|
327
|
+
// optional because only @gjsify/webgl users need it.
|
|
144
328
|
results.push(checkGwebgl(cwd));
|
|
145
329
|
|
|
146
330
|
return results;
|
|
@@ -157,8 +341,15 @@ const PM_PACKAGES: Record<PackageManager, Partial<Record<string, string>>> = {
|
|
|
157
341
|
gtk4: 'libgtk-4-dev',
|
|
158
342
|
libadwaita: 'libadwaita-1-dev',
|
|
159
343
|
libsoup3: 'libsoup-3.0-dev',
|
|
160
|
-
webkitgtk: 'libwebkit2gtk-
|
|
344
|
+
webkitgtk: 'libwebkit2gtk-6.0-dev',
|
|
161
345
|
'gobject-introspection': 'gobject-introspection libgirepository1.0-dev',
|
|
346
|
+
manette: 'libmanette-0.2-0 gir1.2-manette-0.2',
|
|
347
|
+
gstreamer: 'libgstreamer1.0-dev',
|
|
348
|
+
'gst-app': 'libgstreamer-plugins-base1.0-dev gir1.2-gst-plugins-base-1.0',
|
|
349
|
+
'gdk-pixbuf': 'libgdk-pixbuf-2.0-dev',
|
|
350
|
+
pango: 'libpango1.0-dev',
|
|
351
|
+
pangocairo: 'libpango1.0-dev',
|
|
352
|
+
cairo: 'libcairo2-dev',
|
|
162
353
|
},
|
|
163
354
|
dnf: {
|
|
164
355
|
gjs: 'gjs',
|
|
@@ -170,6 +361,13 @@ const PM_PACKAGES: Record<PackageManager, Partial<Record<string, string>>> = {
|
|
|
170
361
|
libsoup3: 'libsoup3-devel',
|
|
171
362
|
webkitgtk: 'webkitgtk6.0-devel',
|
|
172
363
|
'gobject-introspection': 'gobject-introspection-devel',
|
|
364
|
+
manette: 'libmanette-devel',
|
|
365
|
+
gstreamer: 'gstreamer1-devel',
|
|
366
|
+
'gst-app': 'gstreamer1-plugins-base-devel',
|
|
367
|
+
'gdk-pixbuf': 'gdk-pixbuf2-devel',
|
|
368
|
+
pango: 'pango-devel',
|
|
369
|
+
pangocairo: 'pango-devel',
|
|
370
|
+
cairo: 'cairo-devel',
|
|
173
371
|
},
|
|
174
372
|
pacman: {
|
|
175
373
|
gjs: 'gjs',
|
|
@@ -179,8 +377,15 @@ const PM_PACKAGES: Record<PackageManager, Partial<Record<string, string>>> = {
|
|
|
179
377
|
gtk4: 'gtk4',
|
|
180
378
|
libadwaita: 'libadwaita',
|
|
181
379
|
libsoup3: 'libsoup3',
|
|
182
|
-
webkitgtk: '
|
|
380
|
+
webkitgtk: 'webkitgtk-6.0',
|
|
183
381
|
'gobject-introspection': 'gobject-introspection',
|
|
382
|
+
manette: 'libmanette',
|
|
383
|
+
gstreamer: 'gstreamer',
|
|
384
|
+
'gst-app': 'gst-plugins-base',
|
|
385
|
+
'gdk-pixbuf': 'gdk-pixbuf2',
|
|
386
|
+
pango: 'pango',
|
|
387
|
+
pangocairo: 'pango',
|
|
388
|
+
cairo: 'cairo',
|
|
184
389
|
},
|
|
185
390
|
zypper: {
|
|
186
391
|
gjs: 'gjs',
|
|
@@ -190,8 +395,15 @@ const PM_PACKAGES: Record<PackageManager, Partial<Record<string, string>>> = {
|
|
|
190
395
|
gtk4: 'gtk4-devel',
|
|
191
396
|
libadwaita: 'libadwaita-devel',
|
|
192
397
|
libsoup3: 'libsoup-3_0-devel',
|
|
193
|
-
webkitgtk: '
|
|
398
|
+
webkitgtk: 'webkitgtk6_0-devel',
|
|
194
399
|
'gobject-introspection': 'gobject-introspection-devel',
|
|
400
|
+
manette: 'libmanette-0_2-0-devel',
|
|
401
|
+
gstreamer: 'gstreamer-devel',
|
|
402
|
+
'gst-app': 'gstreamer-plugins-base-devel',
|
|
403
|
+
'gdk-pixbuf': 'gdk-pixbuf-devel',
|
|
404
|
+
pango: 'pango-devel',
|
|
405
|
+
pangocairo: 'pango-devel',
|
|
406
|
+
cairo: 'cairo-devel',
|
|
195
407
|
},
|
|
196
408
|
apk: {
|
|
197
409
|
gjs: 'gjs',
|
|
@@ -201,8 +413,15 @@ const PM_PACKAGES: Record<PackageManager, Partial<Record<string, string>>> = {
|
|
|
201
413
|
gtk4: 'gtk4.0-dev',
|
|
202
414
|
libadwaita: 'libadwaita-dev',
|
|
203
415
|
libsoup3: 'libsoup3-dev',
|
|
204
|
-
webkitgtk: 'webkit2gtk-
|
|
416
|
+
webkitgtk: 'webkit2gtk-6.0-dev',
|
|
205
417
|
'gobject-introspection': 'gobject-introspection-dev',
|
|
418
|
+
manette: 'libmanette-dev',
|
|
419
|
+
gstreamer: 'gstreamer-dev',
|
|
420
|
+
'gst-app': 'gst-plugins-base-dev',
|
|
421
|
+
'gdk-pixbuf': 'gdk-pixbuf-dev',
|
|
422
|
+
pango: 'pango-dev',
|
|
423
|
+
pangocairo: 'pango-dev',
|
|
424
|
+
cairo: 'cairo-dev',
|
|
206
425
|
},
|
|
207
426
|
unknown: {},
|
|
208
427
|
};
|
|
@@ -135,6 +135,10 @@ function findNearestPackageJson(startDir: string): string | null {
|
|
|
135
135
|
* Reads the nearest package.json to discover dependencies, then checks each
|
|
136
136
|
* for gjsify native prebuilds metadata.
|
|
137
137
|
*
|
|
138
|
+
* Also checks the **nearest package.json itself** — a workspace package may
|
|
139
|
+
* have its own prebuilds (e.g. `@gjsify/webgl` running its own test) and
|
|
140
|
+
* never list itself in dependencies.
|
|
141
|
+
*
|
|
138
142
|
* This complements detectNativePackages() (filesystem walk from CWD) by using
|
|
139
143
|
* require.resolve() — which handles hoisting, workspaces, and nested node_modules.
|
|
140
144
|
*/
|
|
@@ -152,6 +156,14 @@ export function resolveNativePackages(fromFilePath: string): NativePackage[] {
|
|
|
152
156
|
const pkg = readPackageJson(nearestPkgJson);
|
|
153
157
|
if (!pkg) return results;
|
|
154
158
|
|
|
159
|
+
// Check the nearest package itself (e.g. @gjsify/webgl running its own
|
|
160
|
+
// test bundle — webgl never lists itself in dependencies)
|
|
161
|
+
const ownName = typeof pkg['name'] === 'string' ? pkg['name'] as string : '';
|
|
162
|
+
if (ownName) {
|
|
163
|
+
const ownNative = checkPackage(dirname(nearestPkgJson), ownName, arch);
|
|
164
|
+
if (ownNative) results.push(ownNative);
|
|
165
|
+
}
|
|
166
|
+
|
|
155
167
|
const deps = pkg['dependencies'] as Record<string, string> | undefined;
|
|
156
168
|
if (!deps) return results;
|
|
157
169
|
|