@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
|
@@ -1,10 +1,20 @@
|
|
|
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
|
import { execFileSync } from 'node:child_process';
|
|
5
|
-
import { join } from 'node:path';
|
|
14
|
+
import { join, resolve } from 'node:path';
|
|
6
15
|
import { createRequire } from 'node:module';
|
|
7
16
|
import { pathToFileURL } from 'node:url';
|
|
17
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
8
18
|
/** Run a binary and return its stdout trimmed, or null if it fails. */
|
|
9
19
|
function tryExecFile(binary, args) {
|
|
10
20
|
try {
|
|
@@ -15,41 +25,41 @@ function tryExecFile(binary, args) {
|
|
|
15
25
|
}
|
|
16
26
|
}
|
|
17
27
|
/** Check if a binary exists and optionally capture its version output. */
|
|
18
|
-
function checkBinary(id, name, binary, versionArgs, parseVersion) {
|
|
28
|
+
function checkBinary(id, name, binary, versionArgs, severity, parseVersion, requiredBy) {
|
|
19
29
|
const out = tryExecFile(binary, versionArgs);
|
|
20
30
|
if (out === null)
|
|
21
|
-
return { id, name, found: false };
|
|
31
|
+
return { id, name, found: false, severity, requiredBy };
|
|
22
32
|
const version = parseVersion ? parseVersion(out) : out.split('\n')[0] ?? out;
|
|
23
|
-
return { id, name, found: true, version };
|
|
33
|
+
return { id, name, found: true, version, severity, requiredBy };
|
|
24
34
|
}
|
|
25
35
|
/** Check a pkg-config library. pkg-config --modversion returns version on stdout. */
|
|
26
|
-
function checkPkgConfig(id, name, libName) {
|
|
36
|
+
function checkPkgConfig(id, name, libName, severity, requiredBy) {
|
|
27
37
|
const version = tryExecFile('pkg-config', ['--modversion', libName]);
|
|
28
38
|
if (version === null)
|
|
29
|
-
return { id, name, found: false };
|
|
30
|
-
return { id, name, found: true, version: version.split('\n')[0] };
|
|
39
|
+
return { id, name, found: false, severity, requiredBy };
|
|
40
|
+
return { id, name, found: true, version: version.split('\n')[0], severity, requiredBy };
|
|
31
41
|
}
|
|
32
42
|
/**
|
|
33
43
|
* Check for an npm package. Tries the user's project first (cwd), then falls
|
|
34
44
|
* back to the CLI's own node_modules. This way a locally installed version
|
|
35
45
|
* takes precedence, but npx usage still works via the CLI's own dependencies.
|
|
36
46
|
*/
|
|
37
|
-
function checkNpmPackage(id, name, packageName, cwd) {
|
|
47
|
+
function checkNpmPackage(id, name, packageName, cwd, severity, requiredBy) {
|
|
38
48
|
// 1. Try user's project
|
|
39
49
|
try {
|
|
40
50
|
const requireFromCwd = createRequire(pathToFileURL(join(cwd, '_check_.js')).href);
|
|
41
51
|
requireFromCwd.resolve(packageName);
|
|
42
|
-
return { id, name, found: true };
|
|
52
|
+
return { id, name, found: true, severity, requiredBy };
|
|
43
53
|
}
|
|
44
54
|
catch { /* not in project, try CLI fallback */ }
|
|
45
55
|
// 2. Fallback: CLI's own node_modules
|
|
46
56
|
try {
|
|
47
57
|
const requireFromCli = createRequire(import.meta.url);
|
|
48
58
|
requireFromCli.resolve(packageName);
|
|
49
|
-
return { id, name, found: true };
|
|
59
|
+
return { id, name, found: true, severity, requiredBy };
|
|
50
60
|
}
|
|
51
61
|
catch {
|
|
52
|
-
return { id, name, found: false };
|
|
62
|
+
return { id, name, found: false, severity, requiredBy };
|
|
53
63
|
}
|
|
54
64
|
}
|
|
55
65
|
export function detectPackageManager() {
|
|
@@ -61,56 +71,179 @@ export function detectPackageManager() {
|
|
|
61
71
|
}
|
|
62
72
|
return 'unknown';
|
|
63
73
|
}
|
|
74
|
+
/** Optional system deps keyed by their DepCheck id. */
|
|
75
|
+
const OPTIONAL_DEPS = {
|
|
76
|
+
manette: { id: 'manette', name: 'libmanette', pkgName: 'manette-0.2' },
|
|
77
|
+
gstreamer: { id: 'gstreamer', name: 'GStreamer', pkgName: 'gstreamer-1.0' },
|
|
78
|
+
'gst-app': { id: 'gst-app', name: 'GStreamer App', pkgName: 'gstreamer-app-1.0' },
|
|
79
|
+
'gdk-pixbuf': { id: 'gdk-pixbuf', name: 'GdkPixbuf', pkgName: 'gdk-pixbuf-2.0' },
|
|
80
|
+
pango: { id: 'pango', name: 'Pango', pkgName: 'pango' },
|
|
81
|
+
pangocairo: { id: 'pangocairo', name: 'PangoCairo', pkgName: 'pangocairo' },
|
|
82
|
+
webkitgtk: { id: 'webkitgtk', name: 'WebKitGTK', pkgName: 'webkitgtk-6.0' },
|
|
83
|
+
cairo: { id: 'cairo', name: 'Cairo', pkgName: 'cairo' },
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Map of @gjsify/* package name → ids of OPTIONAL_DEPS this package needs.
|
|
87
|
+
* Generated by walking each package's `gi://` imports (excluding the always-
|
|
88
|
+
* available trio GLib/GObject/Gio).
|
|
89
|
+
*
|
|
90
|
+
* Used to compute the set of optional deps to check for a given project.
|
|
91
|
+
*/
|
|
92
|
+
const PACKAGE_DEPS = {
|
|
93
|
+
'@gjsify/gamepad': ['manette'],
|
|
94
|
+
'@gjsify/webaudio': ['gstreamer', 'gst-app'],
|
|
95
|
+
'@gjsify/iframe': ['webkitgtk'],
|
|
96
|
+
'@gjsify/canvas2d': ['gdk-pixbuf', 'pango', 'pangocairo', 'cairo'],
|
|
97
|
+
'@gjsify/canvas2d-core': ['gdk-pixbuf', 'pango', 'pangocairo', 'cairo'],
|
|
98
|
+
'@gjsify/dom-elements': ['gdk-pixbuf'],
|
|
99
|
+
// @gjsify/webgl, @gjsify/event-bridge only need gtk4/gdk which are
|
|
100
|
+
// already in the required set, so they don't need optional entries.
|
|
101
|
+
};
|
|
102
|
+
/** Walk up from cwd looking for the nearest package.json. */
|
|
103
|
+
function findProjectRoot(cwd) {
|
|
104
|
+
let dir = resolve(cwd);
|
|
105
|
+
while (true) {
|
|
106
|
+
if (existsSync(join(dir, 'package.json')))
|
|
107
|
+
return dir;
|
|
108
|
+
const parent = resolve(dir, '..');
|
|
109
|
+
if (parent === dir)
|
|
110
|
+
return null;
|
|
111
|
+
dir = parent;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Discover which @gjsify/* packages a project depends on (transitively),
|
|
116
|
+
* by walking the node_modules tree from the project root. Returns a Set of
|
|
117
|
+
* package names like '@gjsify/webgl', '@gjsify/canvas2d'.
|
|
118
|
+
*
|
|
119
|
+
* If the project root cannot be determined or has no node_modules, returns
|
|
120
|
+
* `null` to signal "check all optional deps" — this matches the historical
|
|
121
|
+
* behaviour where `gjsify check` outside any project shows the full list.
|
|
122
|
+
*/
|
|
123
|
+
function discoverGjsifyPackages(cwd) {
|
|
124
|
+
const root = findProjectRoot(cwd);
|
|
125
|
+
if (!root)
|
|
126
|
+
return null;
|
|
127
|
+
const nodeModulesDir = join(root, 'node_modules', '@gjsify');
|
|
128
|
+
if (!existsSync(nodeModulesDir)) {
|
|
129
|
+
// Project exists but no @gjsify/* installed → only need core checks.
|
|
130
|
+
return new Set();
|
|
131
|
+
}
|
|
132
|
+
// Read top-level package.json to determine if @gjsify/cli is the only
|
|
133
|
+
// dep (in which case we still want to warn about everything the CLI
|
|
134
|
+
// transitively brings in). Otherwise scan node_modules for installed packages.
|
|
135
|
+
const pkgJsonPath = join(root, 'package.json');
|
|
136
|
+
let topPkg = {};
|
|
137
|
+
try {
|
|
138
|
+
topPkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
139
|
+
}
|
|
140
|
+
catch { /* ignore */ }
|
|
141
|
+
const directDeps = {
|
|
142
|
+
...topPkg.dependencies,
|
|
143
|
+
...topPkg.devDependencies,
|
|
144
|
+
};
|
|
145
|
+
const found = new Set();
|
|
146
|
+
try {
|
|
147
|
+
for (const entry of readdirSync(nodeModulesDir, { withFileTypes: true })) {
|
|
148
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
149
|
+
found.add(`@gjsify/${entry.name}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch { /* ignore */ }
|
|
154
|
+
// Also include direct deps even if node_modules walk failed
|
|
155
|
+
for (const dep of Object.keys(directDeps)) {
|
|
156
|
+
if (dep.startsWith('@gjsify/'))
|
|
157
|
+
found.add(dep);
|
|
158
|
+
}
|
|
159
|
+
return found;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Compute the set of optional dep ids that should be checked for the
|
|
163
|
+
* current project. Returns null to indicate "check all" (no project context).
|
|
164
|
+
*/
|
|
165
|
+
function computeNeededOptionalDeps(cwd) {
|
|
166
|
+
const installedPackages = discoverGjsifyPackages(cwd);
|
|
167
|
+
if (installedPackages === null)
|
|
168
|
+
return null; // no project → check everything
|
|
169
|
+
const needed = new Set();
|
|
170
|
+
for (const pkg of installedPackages) {
|
|
171
|
+
const deps = PACKAGE_DEPS[pkg];
|
|
172
|
+
if (deps)
|
|
173
|
+
for (const id of deps)
|
|
174
|
+
needed.add(id);
|
|
175
|
+
}
|
|
176
|
+
return needed;
|
|
177
|
+
}
|
|
64
178
|
/**
|
|
65
179
|
* Run all dependency checks. Used by `gjsify check` to show full system status.
|
|
180
|
+
*
|
|
181
|
+
* Required deps (gjs, gtk4, libsoup3, libadwaita, gobject-introspection,
|
|
182
|
+
* blueprint-compiler, pkg-config, meson) are always checked.
|
|
183
|
+
*
|
|
184
|
+
* Optional deps are checked conditionally based on which @gjsify/* packages
|
|
185
|
+
* the project (resolved from cwd) actually consumes. If no project context
|
|
186
|
+
* is available, all optional deps are checked.
|
|
66
187
|
*/
|
|
67
188
|
export function runAllChecks(cwd) {
|
|
68
|
-
|
|
189
|
+
const needed = computeNeededOptionalDeps(cwd);
|
|
190
|
+
return [...runMinimalChecks(), ...runRequiredChecks(cwd), ...runOptionalChecks(needed, cwd)];
|
|
69
191
|
}
|
|
70
192
|
/**
|
|
71
|
-
* Minimal checks needed to run any GJS example (GJS
|
|
193
|
+
* Minimal checks needed to run any GJS example (Node + GJS binaries only).
|
|
72
194
|
* Used by `gjsify showcase` for examples that have no native deps.
|
|
73
195
|
*/
|
|
74
196
|
export function runMinimalChecks() {
|
|
75
197
|
const results = [];
|
|
76
198
|
// Node.js — always present
|
|
77
|
-
results.push({ id: 'nodejs', name: 'Node.js', found: true, version: process.version });
|
|
199
|
+
results.push({ id: 'nodejs', name: 'Node.js', found: true, version: process.version, severity: 'required' });
|
|
78
200
|
// GJS
|
|
79
|
-
results.push(checkBinary('gjs', 'GJS', 'gjs', ['--version'], (out) => out.replace(/^GJS\s+/i, '').split('\n')[0] ?? out));
|
|
201
|
+
results.push(checkBinary('gjs', 'GJS', 'gjs', ['--version'], 'required', (out) => out.replace(/^GJS\s+/i, '').split('\n')[0] ?? out));
|
|
80
202
|
return results;
|
|
81
203
|
}
|
|
82
|
-
/** Check gwebgl npm package (project first, CLI fallback). */
|
|
204
|
+
/** Check gwebgl npm package (project first, CLI fallback). Optional — only needed by @gjsify/webgl users. */
|
|
83
205
|
export function checkGwebgl(cwd) {
|
|
84
|
-
return checkNpmPackage('gwebgl', 'gwebgl (@gjsify/webgl)', '@gjsify/webgl', cwd);
|
|
206
|
+
return checkNpmPackage('gwebgl', 'gwebgl (@gjsify/webgl)', '@gjsify/webgl', cwd, 'optional', ['@gjsify/webgl']);
|
|
85
207
|
}
|
|
86
208
|
/**
|
|
87
|
-
*
|
|
209
|
+
* Required system dependencies — always checked, missing → exit 1.
|
|
210
|
+
* Includes the core build toolchain (pkg-config, meson, blueprint-compiler)
|
|
211
|
+
* and the foundational libraries (gtk4, libadwaita, libsoup3,
|
|
212
|
+
* gobject-introspection) that nearly every gjsify app needs.
|
|
88
213
|
*/
|
|
89
|
-
function
|
|
214
|
+
function runRequiredChecks(_cwd) {
|
|
90
215
|
const results = [];
|
|
91
|
-
//
|
|
92
|
-
results.push(checkBinary('blueprint-compiler', 'Blueprint Compiler', 'blueprint-compiler', ['--version']));
|
|
93
|
-
|
|
94
|
-
results.push(checkBinary('
|
|
95
|
-
//
|
|
96
|
-
results.push(
|
|
97
|
-
|
|
98
|
-
results.push(checkPkgConfig('
|
|
99
|
-
|
|
100
|
-
results
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
216
|
+
// Build toolchain
|
|
217
|
+
results.push(checkBinary('blueprint-compiler', 'Blueprint Compiler', 'blueprint-compiler', ['--version'], 'required'));
|
|
218
|
+
results.push(checkBinary('pkg-config', 'pkg-config', 'pkg-config', ['--version'], 'required'));
|
|
219
|
+
results.push(checkBinary('meson', 'Meson', 'meson', ['--version'], 'required'));
|
|
220
|
+
// Foundational libraries
|
|
221
|
+
results.push(checkPkgConfig('gtk4', 'GTK4', 'gtk4', 'required'));
|
|
222
|
+
results.push(checkPkgConfig('libadwaita', 'libadwaita', 'libadwaita-1', 'required'));
|
|
223
|
+
results.push(checkPkgConfig('libsoup3', 'libsoup3', 'libsoup-3.0', 'required'));
|
|
224
|
+
results.push(checkPkgConfig('gobject-introspection', 'GObject Introspection', 'gobject-introspection-1.0', 'required'));
|
|
225
|
+
return results;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Optional system dependencies — only checked if the corresponding @gjsify/*
|
|
229
|
+
* package is in use. Missing optional deps generate warnings, not errors.
|
|
230
|
+
*
|
|
231
|
+
* @param needed Set of optional dep ids to check, or null to check all.
|
|
232
|
+
* @param cwd Used to resolve the gwebgl npm package check.
|
|
233
|
+
*/
|
|
234
|
+
function runOptionalChecks(needed, cwd) {
|
|
235
|
+
const results = [];
|
|
236
|
+
for (const [id, dep] of Object.entries(OPTIONAL_DEPS)) {
|
|
237
|
+
if (needed !== null && !needed.has(id))
|
|
238
|
+
continue;
|
|
239
|
+
const requiredBy = Object.entries(PACKAGE_DEPS)
|
|
240
|
+
.filter(([, ids]) => ids.includes(id))
|
|
241
|
+
.map(([pkg]) => pkg);
|
|
242
|
+
results.push(checkPkgConfig(dep.id, dep.name, dep.pkgName, 'optional', requiredBy));
|
|
243
|
+
}
|
|
244
|
+
// gwebgl npm package — special case (not a pkg-config lib).
|
|
245
|
+
// Always reported (the npm package is bundled with the CLI), but marked
|
|
246
|
+
// optional because only @gjsify/webgl users need it.
|
|
114
247
|
results.push(checkGwebgl(cwd));
|
|
115
248
|
return results;
|
|
116
249
|
}
|
|
@@ -125,8 +258,15 @@ const PM_PACKAGES = {
|
|
|
125
258
|
gtk4: 'libgtk-4-dev',
|
|
126
259
|
libadwaita: 'libadwaita-1-dev',
|
|
127
260
|
libsoup3: 'libsoup-3.0-dev',
|
|
128
|
-
webkitgtk: 'libwebkit2gtk-
|
|
261
|
+
webkitgtk: 'libwebkit2gtk-6.0-dev',
|
|
129
262
|
'gobject-introspection': 'gobject-introspection libgirepository1.0-dev',
|
|
263
|
+
manette: 'libmanette-0.2-0 gir1.2-manette-0.2',
|
|
264
|
+
gstreamer: 'libgstreamer1.0-dev',
|
|
265
|
+
'gst-app': 'libgstreamer-plugins-base1.0-dev gir1.2-gst-plugins-base-1.0',
|
|
266
|
+
'gdk-pixbuf': 'libgdk-pixbuf-2.0-dev',
|
|
267
|
+
pango: 'libpango1.0-dev',
|
|
268
|
+
pangocairo: 'libpango1.0-dev',
|
|
269
|
+
cairo: 'libcairo2-dev',
|
|
130
270
|
},
|
|
131
271
|
dnf: {
|
|
132
272
|
gjs: 'gjs',
|
|
@@ -138,6 +278,13 @@ const PM_PACKAGES = {
|
|
|
138
278
|
libsoup3: 'libsoup3-devel',
|
|
139
279
|
webkitgtk: 'webkitgtk6.0-devel',
|
|
140
280
|
'gobject-introspection': 'gobject-introspection-devel',
|
|
281
|
+
manette: 'libmanette-devel',
|
|
282
|
+
gstreamer: 'gstreamer1-devel',
|
|
283
|
+
'gst-app': 'gstreamer1-plugins-base-devel',
|
|
284
|
+
'gdk-pixbuf': 'gdk-pixbuf2-devel',
|
|
285
|
+
pango: 'pango-devel',
|
|
286
|
+
pangocairo: 'pango-devel',
|
|
287
|
+
cairo: 'cairo-devel',
|
|
141
288
|
},
|
|
142
289
|
pacman: {
|
|
143
290
|
gjs: 'gjs',
|
|
@@ -147,8 +294,15 @@ const PM_PACKAGES = {
|
|
|
147
294
|
gtk4: 'gtk4',
|
|
148
295
|
libadwaita: 'libadwaita',
|
|
149
296
|
libsoup3: 'libsoup3',
|
|
150
|
-
webkitgtk: '
|
|
297
|
+
webkitgtk: 'webkitgtk-6.0',
|
|
151
298
|
'gobject-introspection': 'gobject-introspection',
|
|
299
|
+
manette: 'libmanette',
|
|
300
|
+
gstreamer: 'gstreamer',
|
|
301
|
+
'gst-app': 'gst-plugins-base',
|
|
302
|
+
'gdk-pixbuf': 'gdk-pixbuf2',
|
|
303
|
+
pango: 'pango',
|
|
304
|
+
pangocairo: 'pango',
|
|
305
|
+
cairo: 'cairo',
|
|
152
306
|
},
|
|
153
307
|
zypper: {
|
|
154
308
|
gjs: 'gjs',
|
|
@@ -158,8 +312,15 @@ const PM_PACKAGES = {
|
|
|
158
312
|
gtk4: 'gtk4-devel',
|
|
159
313
|
libadwaita: 'libadwaita-devel',
|
|
160
314
|
libsoup3: 'libsoup-3_0-devel',
|
|
161
|
-
webkitgtk: '
|
|
315
|
+
webkitgtk: 'webkitgtk6_0-devel',
|
|
162
316
|
'gobject-introspection': 'gobject-introspection-devel',
|
|
317
|
+
manette: 'libmanette-0_2-0-devel',
|
|
318
|
+
gstreamer: 'gstreamer-devel',
|
|
319
|
+
'gst-app': 'gstreamer-plugins-base-devel',
|
|
320
|
+
'gdk-pixbuf': 'gdk-pixbuf-devel',
|
|
321
|
+
pango: 'pango-devel',
|
|
322
|
+
pangocairo: 'pango-devel',
|
|
323
|
+
cairo: 'cairo-devel',
|
|
163
324
|
},
|
|
164
325
|
apk: {
|
|
165
326
|
gjs: 'gjs',
|
|
@@ -169,8 +330,15 @@ const PM_PACKAGES = {
|
|
|
169
330
|
gtk4: 'gtk4.0-dev',
|
|
170
331
|
libadwaita: 'libadwaita-dev',
|
|
171
332
|
libsoup3: 'libsoup3-dev',
|
|
172
|
-
webkitgtk: 'webkit2gtk-
|
|
333
|
+
webkitgtk: 'webkit2gtk-6.0-dev',
|
|
173
334
|
'gobject-introspection': 'gobject-introspection-dev',
|
|
335
|
+
manette: 'libmanette-dev',
|
|
336
|
+
gstreamer: 'gstreamer-dev',
|
|
337
|
+
'gst-app': 'gst-plugins-base-dev',
|
|
338
|
+
'gdk-pixbuf': 'gdk-pixbuf-dev',
|
|
339
|
+
pango: 'pango-dev',
|
|
340
|
+
pangocairo: 'pango-dev',
|
|
341
|
+
cairo: 'cairo-dev',
|
|
174
342
|
},
|
|
175
343
|
unknown: {},
|
|
176
344
|
};
|
|
@@ -17,6 +17,10 @@ export declare function detectNativePackages(startDir: string): NativePackage[];
|
|
|
17
17
|
* Reads the nearest package.json to discover dependencies, then checks each
|
|
18
18
|
* for gjsify native prebuilds metadata.
|
|
19
19
|
*
|
|
20
|
+
* Also checks the **nearest package.json itself** — a workspace package may
|
|
21
|
+
* have its own prebuilds (e.g. `@gjsify/webgl` running its own test) and
|
|
22
|
+
* never list itself in dependencies.
|
|
23
|
+
*
|
|
20
24
|
* This complements detectNativePackages() (filesystem walk from CWD) by using
|
|
21
25
|
* require.resolve() — which handles hoisting, workspaces, and nested node_modules.
|
|
22
26
|
*/
|
|
@@ -125,6 +125,10 @@ function findNearestPackageJson(startDir) {
|
|
|
125
125
|
* Reads the nearest package.json to discover dependencies, then checks each
|
|
126
126
|
* for gjsify native prebuilds metadata.
|
|
127
127
|
*
|
|
128
|
+
* Also checks the **nearest package.json itself** — a workspace package may
|
|
129
|
+
* have its own prebuilds (e.g. `@gjsify/webgl` running its own test) and
|
|
130
|
+
* never list itself in dependencies.
|
|
131
|
+
*
|
|
128
132
|
* This complements detectNativePackages() (filesystem walk from CWD) by using
|
|
129
133
|
* require.resolve() — which handles hoisting, workspaces, and nested node_modules.
|
|
130
134
|
*/
|
|
@@ -140,6 +144,14 @@ export function resolveNativePackages(fromFilePath) {
|
|
|
140
144
|
const pkg = readPackageJson(nearestPkgJson);
|
|
141
145
|
if (!pkg)
|
|
142
146
|
return results;
|
|
147
|
+
// Check the nearest package itself (e.g. @gjsify/webgl running its own
|
|
148
|
+
// test bundle — webgl never lists itself in dependencies)
|
|
149
|
+
const ownName = typeof pkg['name'] === 'string' ? pkg['name'] : '';
|
|
150
|
+
if (ownName) {
|
|
151
|
+
const ownNative = checkPackage(dirname(nearestPkgJson), ownName, arch);
|
|
152
|
+
if (ownNative)
|
|
153
|
+
results.push(ownNative);
|
|
154
|
+
}
|
|
143
155
|
const deps = pkg['dependencies'];
|
|
144
156
|
if (!deps)
|
|
145
157
|
return results;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -23,14 +23,15 @@
|
|
|
23
23
|
"cli"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@gjsify/create-app": "^0.1.
|
|
27
|
-
"@gjsify/esbuild-plugin-gjsify": "^0.1.
|
|
28
|
-
"@gjsify/example-dom-canvas2d-fireworks": "^0.1.
|
|
29
|
-
"@gjsify/example-dom-
|
|
30
|
-
"@gjsify/example-dom-three-
|
|
31
|
-
"@gjsify/example-
|
|
32
|
-
"@gjsify/node-
|
|
33
|
-
"@gjsify/
|
|
26
|
+
"@gjsify/create-app": "^0.1.10",
|
|
27
|
+
"@gjsify/esbuild-plugin-gjsify": "^0.1.10",
|
|
28
|
+
"@gjsify/example-dom-canvas2d-fireworks": "^0.1.10",
|
|
29
|
+
"@gjsify/example-dom-excalibur-jelly-jumper": "^0.1.10",
|
|
30
|
+
"@gjsify/example-dom-three-geometry-teapot": "^0.1.10",
|
|
31
|
+
"@gjsify/example-dom-three-postprocessing-pixel": "^0.1.10",
|
|
32
|
+
"@gjsify/example-node-express-webserver": "^0.1.10",
|
|
33
|
+
"@gjsify/node-polyfills": "^0.1.10",
|
|
34
|
+
"@gjsify/web-polyfills": "^0.1.10",
|
|
34
35
|
"cosmiconfig": "^9.0.1",
|
|
35
36
|
"esbuild": "^0.28.0",
|
|
36
37
|
"get-tsconfig": "^4.13.7",
|
package/src/actions/build.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ConfigData } from '../types/index.js';
|
|
|
2
2
|
import type { App } from '@gjsify/esbuild-plugin-gjsify';
|
|
3
3
|
import { build, BuildOptions, BuildResult } from 'esbuild';
|
|
4
4
|
import { gjsifyPlugin } from '@gjsify/esbuild-plugin-gjsify';
|
|
5
|
-
import { resolveGlobalsList, writeRegisterInjectFile } from '@gjsify/esbuild-plugin-gjsify/globals';
|
|
5
|
+
import { resolveGlobalsList, writeRegisterInjectFile, detectAutoGlobals } from '@gjsify/esbuild-plugin-gjsify/globals';
|
|
6
6
|
import { dirname, extname } from 'path';
|
|
7
7
|
|
|
8
8
|
export class BuildAction {
|
|
@@ -75,17 +75,41 @@ export class BuildAction {
|
|
|
75
75
|
return results;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Parse the `--globals` value into { autoMode, extras }.
|
|
80
|
+
* - `auto` → { autoMode: true, extras: '' }
|
|
81
|
+
* - `auto,dom` → { autoMode: true, extras: 'dom' }
|
|
82
|
+
* - `auto,dom,fetch` → { autoMode: true, extras: 'dom,fetch' }
|
|
83
|
+
* - `dom,fetch` → { autoMode: false, extras: 'dom,fetch' }
|
|
84
|
+
* - `none` / `` → { autoMode: false, extras: '' }
|
|
85
|
+
* - `undefined` → { autoMode: true, extras: '' } (default)
|
|
86
|
+
*/
|
|
87
|
+
private parseGlobalsValue(value: string | undefined): { autoMode: boolean; extras: string } {
|
|
88
|
+
if (value === undefined) return { autoMode: true, extras: '' };
|
|
89
|
+
if (value === 'none' || value === '') return { autoMode: false, extras: '' };
|
|
90
|
+
|
|
91
|
+
const tokens = value.split(',').map(t => t.trim()).filter(Boolean);
|
|
92
|
+
const hasAuto = tokens.includes('auto');
|
|
93
|
+
const extras = tokens.filter(t => t !== 'auto').join(',');
|
|
94
|
+
|
|
95
|
+
return { autoMode: hasAuto, extras };
|
|
96
|
+
}
|
|
97
|
+
|
|
78
98
|
/**
|
|
79
99
|
* Resolve the `--globals` CLI list into a pre-computed inject stub path
|
|
80
100
|
* that the esbuild plugin will append to its `inject` list. Only runs
|
|
81
101
|
* for `--app gjs` — Node and browser builds rely on native globals.
|
|
102
|
+
*
|
|
103
|
+
* Used only for the explicit-only path (no `auto` token in the value).
|
|
104
|
+
* The auto path is handled in `buildApp` via the two-pass build.
|
|
82
105
|
*/
|
|
83
106
|
private async resolveGlobalsInject(
|
|
84
107
|
app: App,
|
|
85
|
-
globals: string
|
|
108
|
+
globals: string,
|
|
86
109
|
verbose: boolean | undefined,
|
|
87
110
|
): Promise<string | undefined> {
|
|
88
|
-
if (app !== 'gjs'
|
|
111
|
+
if (app !== 'gjs') return undefined;
|
|
112
|
+
if (!globals) return undefined;
|
|
89
113
|
|
|
90
114
|
const registerPaths = resolveGlobalsList(globals);
|
|
91
115
|
if (registerPaths.size === 0) return undefined;
|
|
@@ -106,13 +130,55 @@ export class BuildAction {
|
|
|
106
130
|
|
|
107
131
|
const format: 'esm' | 'cjs' = (esbuild?.format as 'esm' | 'cjs') ?? (esbuild?.outfile?.endsWith('.cjs') ? 'cjs' : 'esm');
|
|
108
132
|
|
|
109
|
-
// Set default outfile if no outdir is set
|
|
133
|
+
// Set default outfile if no outdir is set
|
|
110
134
|
if(esbuild && !esbuild?.outfile && !esbuild?.outdir && (pgk?.main || pgk?.module)) {
|
|
111
135
|
esbuild.outfile = esbuild?.format === 'cjs' ? pgk.main || pgk.module : pgk.module || pgk.main;
|
|
112
136
|
}
|
|
113
137
|
|
|
114
138
|
const { consoleShim, globals } = this.configData;
|
|
115
|
-
|
|
139
|
+
|
|
140
|
+
const pluginOpts = {
|
|
141
|
+
debug: verbose,
|
|
142
|
+
app,
|
|
143
|
+
format,
|
|
144
|
+
exclude,
|
|
145
|
+
reflection: typescript?.reflection,
|
|
146
|
+
consoleShim,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const { autoMode, extras } = this.parseGlobalsValue(globals);
|
|
150
|
+
|
|
151
|
+
// --- Auto mode (with optional extras): iterative multi-pass build ---
|
|
152
|
+
// The extras token is used for cases where the detector cannot
|
|
153
|
+
// statically see a global (e.g. Excalibur indirects globalThis via
|
|
154
|
+
// BrowserComponent.nativeComponent). Common pattern: --globals auto,dom
|
|
155
|
+
if (app === 'gjs' && autoMode) {
|
|
156
|
+
const { injectPath } = await detectAutoGlobals(
|
|
157
|
+
{ ...this.getEsBuildDefaults(), ...esbuild, format },
|
|
158
|
+
pluginOpts,
|
|
159
|
+
verbose,
|
|
160
|
+
{ extraGlobalsList: extras },
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const result = await build({
|
|
164
|
+
...this.getEsBuildDefaults(),
|
|
165
|
+
...esbuild,
|
|
166
|
+
format,
|
|
167
|
+
plugins: [
|
|
168
|
+
gjsifyPlugin({
|
|
169
|
+
...pluginOpts,
|
|
170
|
+
autoGlobalsInject: injectPath,
|
|
171
|
+
}),
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return [result];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// --- Explicit list (no `auto` token) or none mode ---
|
|
179
|
+
const autoGlobalsInject = extras
|
|
180
|
+
? await this.resolveGlobalsInject(app, extras, verbose)
|
|
181
|
+
: undefined;
|
|
116
182
|
|
|
117
183
|
const result = await build({
|
|
118
184
|
...this.getEsBuildDefaults(),
|
|
@@ -120,26 +186,12 @@ export class BuildAction {
|
|
|
120
186
|
format,
|
|
121
187
|
plugins: [
|
|
122
188
|
gjsifyPlugin({
|
|
123
|
-
|
|
124
|
-
app,
|
|
125
|
-
format,
|
|
126
|
-
exclude,
|
|
127
|
-
reflection: typescript?.reflection,
|
|
128
|
-
consoleShim,
|
|
189
|
+
...pluginOpts,
|
|
129
190
|
autoGlobalsInject,
|
|
130
191
|
}),
|
|
131
192
|
]
|
|
132
193
|
});
|
|
133
194
|
|
|
134
|
-
// See https://esbuild.github.io/api/#metafile
|
|
135
|
-
// TODO add cli options for this
|
|
136
|
-
// if(result.metafile) {
|
|
137
|
-
// const outFile = esbuild?.outfile ? esbuild.outfile + '.meta.json' : 'meta.json';
|
|
138
|
-
// await writeFile(outFile, JSON.stringify(result.metafile));
|
|
139
|
-
// let text = await analyzeMetafile(result.metafile)
|
|
140
|
-
// console.log(text)
|
|
141
|
-
// }
|
|
142
|
-
|
|
143
195
|
return [result];
|
|
144
196
|
}
|
|
145
197
|
|
package/src/commands/build.ts
CHANGED
|
@@ -89,10 +89,10 @@ export const buildCommand: Command<any, CliBuildOptions> = {
|
|
|
89
89
|
default: true
|
|
90
90
|
})
|
|
91
91
|
.option('globals', {
|
|
92
|
-
description: "Comma-separated list of global identifiers
|
|
92
|
+
description: "Comma-separated list of global identifiers, 'auto' (default) to detect automatically from the bundled output, or 'none' to disable. The 'auto' token may be combined with explicit identifiers/groups (e.g. 'auto,dom') for cases where the detector cannot statically see a global because it's accessed via indirection. Each identifier is mapped to the corresponding `@gjsify/<pkg>/register` module and injected into the bundle. See the CLI Reference docs for the full list of known identifiers. Only applies to GJS app builds.",
|
|
93
93
|
type: 'string',
|
|
94
94
|
normalize: true,
|
|
95
|
-
default: ''
|
|
95
|
+
default: 'auto'
|
|
96
96
|
})
|
|
97
97
|
},
|
|
98
98
|
handler: async (args) => {
|