@gjsify/cli 0.40.0 → 0.42.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/README.md +79 -33
- package/dist/cli.gjs.mjs +1043 -404
- package/lib/actions/build.d.ts +14 -0
- package/lib/actions/build.js +54 -7
- package/lib/bundler-pick.js +10 -2
- package/lib/cli-app.js +3 -1
- package/lib/commands/browse.js +1 -1
- package/lib/commands/create.d.ts +3 -0
- package/lib/commands/create.js +49 -6
- package/lib/commands/debug.js +1 -1
- package/lib/commands/dev.d.ts +13 -0
- package/lib/commands/dev.js +132 -0
- package/lib/commands/dlx.js +1 -1
- 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 +32 -1
- package/lib/commands/prune.d.ts +11 -0
- package/lib/commands/prune.js +84 -0
- package/lib/commands/run.js +38 -85
- package/lib/commands/self-update.d.ts +1 -0
- package/lib/commands/self-update.js +55 -0
- package/lib/commands/ship.js +6 -1
- package/lib/commands/showcase.js +1 -1
- package/lib/commands/storybook.js +17 -74
- package/lib/commands/test.js +12 -16
- package/lib/commands/tsc.js +96 -45
- package/lib/config.js +4 -0
- package/lib/templates/install.mjs.tmpl +8 -3
- package/lib/types/config-data.d.ts +27 -0
- package/lib/utils/app-metadata.js +27 -6
- package/lib/utils/check-system-deps.d.ts +2 -2
- package/lib/utils/check-system-deps.js +92 -13
- package/lib/utils/daemon-command.d.ts +7 -0
- package/lib/utils/daemon-command.js +30 -0
- package/lib/utils/dev-plan.d.ts +52 -0
- package/lib/utils/dev-plan.js +117 -0
- package/lib/utils/gi-runtime-paths.d.ts +27 -0
- package/lib/utils/gi-runtime-paths.js +84 -0
- package/lib/utils/gi-typelib.d.ts +49 -0
- package/lib/utils/gi-typelib.js +160 -0
- package/lib/utils/install-provenance.d.ts +44 -0
- package/lib/utils/install-provenance.js +106 -0
- package/lib/utils/node-script.d.ts +4 -1
- package/lib/utils/node-script.js +27 -3
- package/lib/utils/npm-oidc.js +8 -5
- package/lib/utils/prune-prefix.d.ts +83 -0
- package/lib/utils/prune-prefix.js +333 -0
- package/lib/utils/run-gjs.d.ts +12 -1
- package/lib/utils/run-gjs.js +24 -31
- package/lib/utils/run-node.d.ts +9 -4
- package/lib/utils/run-node.js +34 -41
- package/lib/utils/ship/depends.d.ts +5 -0
- package/lib/utils/ship/depends.js +38 -0
- package/lib/utils/ship/discover.js +106 -1
- package/lib/utils/ship/launcher.js +27 -4
- package/lib/utils/ship/payload.d.ts +35 -0
- package/lib/utils/ship/payload.js +84 -0
- package/lib/utils/ship/plan.js +20 -0
- package/lib/utils/ship/settings.d.ts +8 -1
- package/lib/utils/ship/settings.js +13 -1
- package/lib/utils/ship/types.d.ts +19 -0
- package/lib/utils/simple-command.d.ts +16 -0
- package/lib/utils/simple-command.js +77 -0
- package/lib/utils/spawn.d.ts +7 -1
- package/lib/utils/spawn.js +34 -7
- package/lib/utils/system-gi.d.ts +24 -0
- package/lib/utils/system-gi.js +14 -3
- package/lib/utils/watch-loop.d.ts +91 -0
- package/lib/utils/watch-loop.js +207 -0
- package/package.json +27 -18
- package/lib/templates/app/desktop.tmpl +0 -10
package/lib/commands/tsc.js
CHANGED
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
// if the dep was removed or the consumer is running a forked CLI.
|
|
18
18
|
//
|
|
19
19
|
// Reference: packages/infra/cli/src/commands/run.ts (env-setup precedent).
|
|
20
|
-
import { spawn } from 'node:child_process';
|
|
21
20
|
import { createRequire } from 'node:module';
|
|
22
21
|
import { dirname, join } from 'node:path';
|
|
23
22
|
import { pathToFileURL } from 'node:url';
|
|
@@ -27,6 +26,7 @@ import { existsSync } from 'node:fs';
|
|
|
27
26
|
import { forceExit } from '../utils/force-exit.js';
|
|
28
27
|
import { nodeBinary } from '../utils/run-node.js';
|
|
29
28
|
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
29
|
+
import { spawnToCompletion } from '../utils/spawn.js';
|
|
30
30
|
export const tscCommand = {
|
|
31
31
|
command: 'tsc [tscArgs..]',
|
|
32
32
|
description: 'Run TypeScript compiler (tsc) under GJS via the @gjsify/tsc bundle. All arguments are passed through to tsc. Equivalent to `gjsify-tsc <args>` from the @gjsify/tsc bin.',
|
|
@@ -139,7 +139,7 @@ export const tscCommand = {
|
|
|
139
139
|
// fallback below is the NORMAL path there, not an edge case.
|
|
140
140
|
// `run-node.ts` owns the one correct answer; every other spawn site in
|
|
141
141
|
// this CLI already routes through it.
|
|
142
|
-
const runNodeTsc = () => {
|
|
142
|
+
const runNodeTsc = async () => {
|
|
143
143
|
const nodeBin = nodeBinary();
|
|
144
144
|
// PRE-FLIGHT, deliberately NOT the spawn's `'error'` event.
|
|
145
145
|
//
|
|
@@ -161,64 +161,115 @@ export const tscCommand = {
|
|
|
161
161
|
console.error(' …or install Node.js so the upstream `typescript` fallback can be spawned.');
|
|
162
162
|
return forceExit(1);
|
|
163
163
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
// `forceExit` runs from the child's own `'close'` DISPATCH, never from a
|
|
165
|
+
// promise continuation — which is why this awaits a promise that only
|
|
166
|
+
// ever REJECTS instead of `await`ing the completion.
|
|
167
|
+
//
|
|
168
|
+
// Under GJS `forceExit` ends the process by THROWING SpiderMonkey's
|
|
169
|
+
// uncatchable exit exception. Thrown inside an `await` continuation that
|
|
170
|
+
// exception is captured by the async machinery as a rejection rather than
|
|
171
|
+
// unwinding the stack, so nothing exits — and the GLib main loop the spawn
|
|
172
|
+
// armed then keeps the process alive with nothing left to quit it.
|
|
173
|
+
// Measured: `gjsify tsc --version` printed `Version 6.0.3` and hung
|
|
174
|
+
// forever, which in CI is a 30-minute step timeout.
|
|
175
|
+
try {
|
|
176
|
+
await new Promise((_never, reject) => {
|
|
177
|
+
void spawnToCompletion(nodeBin, [nodeTscPath, ...tscArgs], {
|
|
178
|
+
completion: 'exit',
|
|
179
|
+
env,
|
|
180
|
+
onSpawn: (child) => {
|
|
181
|
+
// A FAILED spawn emits both `'error'` and `'close'`, and
|
|
182
|
+
// that `'close'` carries the SPAWN ERRNO rather than an
|
|
183
|
+
// exit status — win32 ENOENT is -4058, which reached the
|
|
184
|
+
// shell as 4294963238 and took every Windows build down
|
|
185
|
+
// with it. Exiting on it would also pre-empt the Node
|
|
186
|
+
// fallback. This is the race the old `gjsSpawnFailed`
|
|
187
|
+
// flag existed to break; deleting the flag deleted the
|
|
188
|
+
// property, so it is back, with its measurement.
|
|
189
|
+
let spawnFailed = false;
|
|
190
|
+
child.on('error', () => {
|
|
191
|
+
spawnFailed = true;
|
|
192
|
+
});
|
|
193
|
+
child.on('close', (code) => {
|
|
194
|
+
if (!spawnFailed)
|
|
195
|
+
forceExit(code ?? 1);
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
}).catch(reject);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
const e = err;
|
|
203
|
+
if (e.code === 'ENOENT') {
|
|
168
204
|
console.error('gjsify tsc: the @gjsify/tsc GJS bundle is unavailable and `node` is not on PATH, ' +
|
|
169
205
|
'so upstream `typescript` cannot run either.');
|
|
170
206
|
console.error(' Build the bundle with: gjsify workspace @gjsify/tsc build');
|
|
171
207
|
}
|
|
172
208
|
else {
|
|
173
|
-
console.error(`gjsify tsc (node typescript): ${
|
|
209
|
+
console.error(`gjsify tsc (node typescript): ${e.message}`);
|
|
174
210
|
}
|
|
175
|
-
forceExit(1);
|
|
176
|
-
}
|
|
211
|
+
return forceExit(1);
|
|
212
|
+
}
|
|
177
213
|
};
|
|
178
214
|
// Prefer GJS — the Node-free @gjsify/tsc bundle — when it resolves. If
|
|
179
215
|
// `gjs` is not on PATH (ENOENT), transparently fall back to upstream
|
|
180
216
|
// `typescript` under Node when available.
|
|
181
217
|
if (bundlePath) {
|
|
182
|
-
// A failed spawn (notably ENOENT when `gjs` is absent) emits BOTH
|
|
183
|
-
// 'error' AND 'close'
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
await
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
218
|
+
// A raw failed spawn (notably ENOENT when `gjs` is absent) emits BOTH
|
|
219
|
+
// 'error' AND 'close', and the two raced: the 'close' handler exited
|
|
220
|
+
// with the spawn errno (ENOENT → -2, surfacing as a misleading 254)
|
|
221
|
+
// and pre-empted the Node fallback, so `gjsify tsc` died instead of
|
|
222
|
+
// degrading to upstream tsc on a gjs-less host such as a CI runner. A
|
|
223
|
+
// promise cannot both reject and resolve, so routing through
|
|
224
|
+
// `spawnToCompletion` makes that race structurally impossible rather
|
|
225
|
+
// than guarded by a flag.
|
|
226
|
+
// Same shape, and the same reason, as `runNodeTsc` above: exit from the
|
|
227
|
+
// child's `'close'` dispatch, await a promise that only rejects.
|
|
228
|
+
try {
|
|
229
|
+
await new Promise((_never, reject) => {
|
|
230
|
+
void spawnToCompletion('gjs', ['-m', bundlePath, ...tscArgs], {
|
|
231
|
+
completion: 'exit',
|
|
232
|
+
env,
|
|
233
|
+
onSpawn: (child) => {
|
|
234
|
+
// A FAILED spawn emits both `'error'` and `'close'`, and
|
|
235
|
+
// that `'close'` carries the SPAWN ERRNO rather than an
|
|
236
|
+
// exit status — win32 ENOENT is -4058, which reached the
|
|
237
|
+
// shell as 4294963238 and took every Windows build down
|
|
238
|
+
// with it. Exiting on it would also pre-empt the Node
|
|
239
|
+
// fallback. This is the race the old `gjsSpawnFailed`
|
|
240
|
+
// flag existed to break; deleting the flag deleted the
|
|
241
|
+
// property, so it is back, with its measurement.
|
|
242
|
+
let spawnFailed = false;
|
|
243
|
+
child.on('error', () => {
|
|
244
|
+
spawnFailed = true;
|
|
245
|
+
});
|
|
246
|
+
child.on('close', (code) => {
|
|
247
|
+
if (!spawnFailed)
|
|
248
|
+
forceExit(code ?? 1);
|
|
249
|
+
});
|
|
250
|
+
},
|
|
251
|
+
}).catch(reject);
|
|
212
252
|
});
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
const e = err;
|
|
256
|
+
if (e.code === 'ENOENT' && nodeTscPath) {
|
|
257
|
+
// No gjs on PATH — run upstream typescript under Node.
|
|
258
|
+
return await runNodeTsc();
|
|
259
|
+
}
|
|
260
|
+
if (e.code === 'ENOENT') {
|
|
261
|
+
console.error('gjsify tsc: `gjs` not found on PATH and no npm `typescript` fallback.');
|
|
262
|
+
console.error(' Install GJS (e.g. `dnf install gjs`), or add `typescript` to the project.');
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
console.error(`gjsify tsc: ${e.message}`);
|
|
266
|
+
}
|
|
267
|
+
return forceExit(1);
|
|
268
|
+
}
|
|
218
269
|
}
|
|
219
270
|
else {
|
|
220
271
|
// No GJS bundle resolvable — go straight to the Node fallback.
|
|
221
|
-
runNodeTsc();
|
|
272
|
+
await runNodeTsc();
|
|
222
273
|
}
|
|
223
274
|
},
|
|
224
275
|
};
|
package/lib/config.js
CHANGED
|
@@ -38,6 +38,10 @@ async function loadConfigViaGjsBundle(filepath, verbose) {
|
|
|
38
38
|
// import.meta.url)), 'package.json'))`) relative to ITS OWN location.
|
|
39
39
|
// Bake the original config's URL so those reads hit the real dir.
|
|
40
40
|
define: { 'import.meta.url': JSON.stringify(pathToFileURL(filepath).href) },
|
|
41
|
+
// A config file is evaluated INSIDE the CLI process — toolchain by
|
|
42
|
+
// construction, so a `@gjsify/*` the project cannot resolve may come
|
|
43
|
+
// from the CLI's own install.
|
|
44
|
+
resolveFromToolchain: true,
|
|
41
45
|
});
|
|
42
46
|
}
|
|
43
47
|
catch (bundleErr) {
|
|
@@ -188,9 +188,14 @@ function checkGjsVersion() {
|
|
|
188
188
|
if (major < 1 || (major === 1 && minor < 86)) {
|
|
189
189
|
error(`gjs ${major}.${minor} is too old — gjsify requires gjs 1.86 or newer.`);
|
|
190
190
|
error('Install hints:');
|
|
191
|
-
error(' Fedora 43+:
|
|
192
|
-
error('
|
|
193
|
-
|
|
191
|
+
error(' Fedora 43+: sudo dnf install gjs');
|
|
192
|
+
error(' Arch: sudo pacman -S gjs');
|
|
193
|
+
// NOT "Debian 13+". Measured 2026-08-16 against tracker.debian.org: trixie
|
|
194
|
+
// (13, stable) ships 1.82.3, forky (testing) 1.88.1, sid 1.89.2 — 1.84 and
|
|
195
|
+
// 1.86 were skipped entirely. Sending a trixie user to `apt install gjs`
|
|
196
|
+
// hands them the very version this branch just rejected, from the branch
|
|
197
|
+
// that rejected it. Same fact, same wording as `utils/ship/depends.ts`.
|
|
198
|
+
error(' Debian forky/sid: sudo apt install gjs (trixie ships 1.82.3 — too old)');
|
|
194
199
|
exit(1);
|
|
195
200
|
}
|
|
196
201
|
}
|
|
@@ -433,6 +433,33 @@ export interface ConfigDataShip extends AppMetadata {
|
|
|
433
433
|
deb: string;
|
|
434
434
|
rpm: string;
|
|
435
435
|
}>;
|
|
436
|
+
/**
|
|
437
|
+
* Directories whose `*.typelib` and `lib*.so*` the package CARRIES ITSELF, e.g.
|
|
438
|
+
* `["../node_modules/@gjsify/webgl-linux-x64/prebuilds/linux-x64"]`.
|
|
439
|
+
*
|
|
440
|
+
* gjsify's own GI libraries — `Gwebgl`, the GTK runtime bundles, the napi host — arrive as npm
|
|
441
|
+
* prebuilds, not as distro packages, so an app that imports one has no `gir1.2-…` to depend on.
|
|
442
|
+
* Staging them here puts them in `lib/<binary>/gi/` and makes the launcher point
|
|
443
|
+
* `GI_TYPELIB_PATH` and `LD_LIBRARY_PATH` at that directory.
|
|
444
|
+
*
|
|
445
|
+
* The namespaces this covers are read back off the STAGED FILES, not from a separate list:
|
|
446
|
+
* a declaration that a namespace is bundled, without the file being there, is exactly the lie
|
|
447
|
+
* the dependency check exists to prevent.
|
|
448
|
+
*/
|
|
449
|
+
bundledTypelibs?: string[];
|
|
450
|
+
/**
|
|
451
|
+
* Directory holding COMPILED gettext catalogues in the layout `bindtextdomain` reads —
|
|
452
|
+
* `<lang>/LC_MESSAGES/<domain>.mo` — e.g. `"dist/locale"`, which is what
|
|
453
|
+
* `@gjsify/vite-plugin-gettext`'s `gettextPlugin` writes by default.
|
|
454
|
+
*
|
|
455
|
+
* Staged into `share/locale/` with that structure preserved, and the launcher then exports
|
|
456
|
+
* `GJSIFY_LOCALE_DIR` so the app can call `bindtextdomain(domain, dir)` without knowing which
|
|
457
|
+
* prefix it was installed under.
|
|
458
|
+
*
|
|
459
|
+
* `.po` sources are NOT accepted: `bindtextdomain` reads `.mo` only, and staging a `.po`
|
|
460
|
+
* produces a package that installs its translations and shows none of them.
|
|
461
|
+
*/
|
|
462
|
+
localeDir?: string;
|
|
436
463
|
/** Extra payload entries: prefix-relative destination → project-relative source. */
|
|
437
464
|
extraFiles?: Record<string, string>;
|
|
438
465
|
/** Arguments the launcher appends before the user's own. */
|
|
@@ -13,15 +13,36 @@
|
|
|
13
13
|
// attributes, provides) has too many optional nested sections for a
|
|
14
14
|
// template+placeholder approach to stay legible. The `.desktop` entry stays
|
|
15
15
|
// template-based — it is flat `key=value`.
|
|
16
|
-
import { readFileSync } from 'node:fs';
|
|
17
16
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* The `.desktop` skeleton, as source rather than as a file to find at runtime.
|
|
18
|
+
*
|
|
19
|
+
* It used to live in `src/templates/app/desktop.tmpl` and be read through
|
|
20
|
+
* `readFileSync(new URL(…, import.meta.url))`, with a comment stating that
|
|
21
|
+
* `static-read-inliner` inlines it into the GJS bundle. It does not: the
|
|
22
|
+
* inliner's `shouldRewrite` requires the file to sit under `node_modules`, and
|
|
23
|
+
* the CLI bundles its OWN source. So the read survived into
|
|
24
|
+
* `dist/cli.gjs.mjs`, resolved `../templates/…` against `dist/`, and
|
|
25
|
+
* `gjsify ship` died with ENOENT on every project — while the Node `lib/`
|
|
26
|
+
* entry, where the relative path happens to be right, worked. A comment
|
|
27
|
+
* asserting the opposite is why it went unnoticed.
|
|
28
|
+
*
|
|
29
|
+
* A ten-line skeleton is not worth a file the bundle has to locate. As a
|
|
30
|
+
* template literal there is nothing to resolve, nothing to package, and the
|
|
31
|
+
* two entry points cannot disagree.
|
|
22
32
|
*/
|
|
33
|
+
const DESKTOP_TEMPLATE = `[Desktop Entry]
|
|
34
|
+
Name={{NAME}}
|
|
35
|
+
Comment={{SUMMARY}}
|
|
36
|
+
Exec={{COMMAND}}
|
|
37
|
+
Icon={{APP_ID}}
|
|
38
|
+
Terminal=false
|
|
39
|
+
Type=Application
|
|
40
|
+
Categories={{CATEGORIES_LINE}}
|
|
41
|
+
{{KEYWORDS_LINE}}{{MIMETYPES_LINE}}StartupNotify=true
|
|
42
|
+
StartupWMClass={{APP_ID}}
|
|
43
|
+
`;
|
|
23
44
|
function loadDesktopTemplate() {
|
|
24
|
-
return
|
|
45
|
+
return DESKTOP_TEMPLATE;
|
|
25
46
|
}
|
|
26
47
|
/**
|
|
27
48
|
* Validate that the metadata has the minimum set of fields AppStream needs.
|
|
@@ -126,8 +126,8 @@ export declare const PACKAGE_DEPS: Record<string, string[]>;
|
|
|
126
126
|
*/
|
|
127
127
|
export declare function runAllChecks(cwd: string): DepCheck[];
|
|
128
128
|
/**
|
|
129
|
-
* Minimal checks needed to run any GJS example (Node
|
|
130
|
-
* Used by `gjsify showcase` for examples that have no native deps.
|
|
129
|
+
* Minimal checks needed to run any GJS example (the GJS binary; Node is reported but
|
|
130
|
+
* not required). Used by `gjsify showcase` for examples that have no native deps.
|
|
131
131
|
*/
|
|
132
132
|
export declare function runMinimalChecks(): DepCheck[];
|
|
133
133
|
/**
|
|
@@ -18,6 +18,8 @@ import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
|
18
18
|
// The zero-dependency subpath, deliberately: importing the plugin root would
|
|
19
19
|
// pull vite/execa/minify-xml into a system CHECK.
|
|
20
20
|
import { resolveBlueprintCompiler } from '@gjsify/vite-plugin-blueprint/resolve';
|
|
21
|
+
import { isNode } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
22
|
+
import { findSystemTypelib } from './gi-typelib.js';
|
|
21
23
|
/** Run a binary and return its stdout trimmed, or null if it fails. */
|
|
22
24
|
function tryExecFile(binary, args) {
|
|
23
25
|
try {
|
|
@@ -78,6 +80,35 @@ function checkPkgConfig(id, name, libName, severity, requiredBy) {
|
|
|
78
80
|
return { id, name, found: false, severity, requiredBy };
|
|
79
81
|
return { id, name, found: true, version: version.split('\n')[0], severity, requiredBy };
|
|
80
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Check a GI library the way a RUNNING app needs it: by its typelib.
|
|
85
|
+
*
|
|
86
|
+
* `checkPkgConfig` answers a different question — "is the DEVELOPMENT package
|
|
87
|
+
* installed" — and for a library an app merely loads, that question has the
|
|
88
|
+
* wrong answer on every distribution that splits `-dev` out. Measured on
|
|
89
|
+
* postmarketOS v26.06 / aarch64 (OnePlus 6T, gjs 1.88.1): libadwaita 1.9.2
|
|
90
|
+
* installed, `Adw-1.typelib` present, Adwaita apps running, and `system-check`
|
|
91
|
+
* printing `✗ libadwaita` under **Required** and exiting 1 because
|
|
92
|
+
* `libadwaita-dev` was absent. The install hint then named a package the user
|
|
93
|
+
* had no reason to install.
|
|
94
|
+
*
|
|
95
|
+
* So PRESENCE comes from {@link findSystemTypelib} — the same file
|
|
96
|
+
* `gi_repository_require()` resolves — and the VERSION still comes from
|
|
97
|
+
* pkg-config, which is a perfectly good version oracle when it happens to be
|
|
98
|
+
* there. A bare `✓ libadwaita` with no version is the honest reading of "the
|
|
99
|
+
* library is here and the `.pc` file is not"; `✗` was not.
|
|
100
|
+
*
|
|
101
|
+
* pkg-config remains the sole oracle for anything whose subject genuinely IS the
|
|
102
|
+
* development package (`gobject-introspection-1.0` gates `g-ir-scanner`, a build
|
|
103
|
+
* tool with no typelib of its own).
|
|
104
|
+
*/
|
|
105
|
+
function checkGiLibrary(id, name, namespace, apiVersion, pkgConfigName, severity, requiredBy) {
|
|
106
|
+
const found = findSystemTypelib(namespace, apiVersion) !== null;
|
|
107
|
+
if (!found)
|
|
108
|
+
return { id, name, found: false, severity, requiredBy };
|
|
109
|
+
const version = tryExecFile('pkg-config', ['--modversion', pkgConfigName]);
|
|
110
|
+
return { id, name, found: true, version: version?.split('\n')[0], severity, requiredBy };
|
|
111
|
+
}
|
|
81
112
|
/**
|
|
82
113
|
* Check for an npm package. Tries the user's project first (cwd), then falls
|
|
83
114
|
* back to the CLI's own node_modules. This way a locally installed version
|
|
@@ -378,13 +409,41 @@ export function runAllChecks(cwd) {
|
|
|
378
409
|
];
|
|
379
410
|
}
|
|
380
411
|
/**
|
|
381
|
-
*
|
|
382
|
-
*
|
|
412
|
+
* Is a Node.js runtime available — asked of the host rather than asserted.
|
|
413
|
+
*
|
|
414
|
+
* This row used to be the literal `{found: true, version: process.version}`, which is
|
|
415
|
+
* a lie on three of the four hosts: `@gjsify/process` stubs `process.version` to
|
|
416
|
+
* `"v20.0.0"` for npm compatibility (Bun and Deno fake `process.versions.node` for the
|
|
417
|
+
* same reason), so `gjsify system-check` under GJS reported `✓ Node.js (v20.0.0)` on a
|
|
418
|
+
* machine with no Node on it — under the NODE-FREE toolchain, whose entire premise is
|
|
419
|
+
* that there is none. `cli-app.ts`'s `runtimeLabel()` already had to know better; one
|
|
420
|
+
* question gets one answer, so this asks the same module.
|
|
421
|
+
*
|
|
422
|
+
* Under Node the running process IS the answer and its version cannot be faked from
|
|
423
|
+
* inside; elsewhere it is a PATH question whose answer may legitimately be "no".
|
|
424
|
+
*
|
|
425
|
+
* `isNode()` rather than `hostRuntime() === 'node'`: the latter RETURNS `'node'` as its
|
|
426
|
+
* fallback for an unrecognised host, which would put an exotic runtime's possibly-stubbed
|
|
427
|
+
* `process.version` on the trusted branch. `isNode()` demands positive evidence.
|
|
428
|
+
*
|
|
429
|
+
* `optional`, never `required`: `gjsify showcase` and `gjsify install` both treat a
|
|
430
|
+
* missing `required` row as fatal, and a GJS showcase needs no Node. The old row could
|
|
431
|
+
* not have failed either gate — it was constant — so nothing that used to pass now
|
|
432
|
+
* fails; what changes is only that the printed line is true.
|
|
433
|
+
*/
|
|
434
|
+
function checkNodeRuntime() {
|
|
435
|
+
if (isNode()) {
|
|
436
|
+
return { id: 'nodejs', name: 'Node.js', found: true, version: process.version, severity: 'optional' };
|
|
437
|
+
}
|
|
438
|
+
return checkBinary('nodejs', 'Node.js', 'node', ['--version'], 'optional');
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Minimal checks needed to run any GJS example (the GJS binary; Node is reported but
|
|
442
|
+
* not required). Used by `gjsify showcase` for examples that have no native deps.
|
|
383
443
|
*/
|
|
384
444
|
export function runMinimalChecks() {
|
|
385
445
|
const results = [];
|
|
386
|
-
|
|
387
|
-
results.push({ id: 'nodejs', name: 'Node.js', found: true, version: process.version, severity: 'required' });
|
|
446
|
+
results.push(checkNodeRuntime());
|
|
388
447
|
// GJS
|
|
389
448
|
results.push(checkBinary('gjs', 'GJS', 'gjs', ['--version'], 'required', (out) => out.replace(/^GJS\s+/i, '').split('\n')[0] ?? out));
|
|
390
449
|
// The one win32 system dependency that is real and was undiagnosed (#997).
|
|
@@ -433,14 +492,21 @@ export function checkGwebgl(cwd) {
|
|
|
433
492
|
*/
|
|
434
493
|
function runRequiredChecks(_cwd) {
|
|
435
494
|
const results = [];
|
|
436
|
-
// Build toolchain
|
|
437
|
-
|
|
495
|
+
// Build toolchain. NO blueprint-compiler here: it is checked by
|
|
496
|
+
// `checkBlueprintCompiler()` among the build toolchain below, through the
|
|
497
|
+
// SAME resolver the build uses. A second `checkBinary` probe lived here and
|
|
498
|
+
// was wrong twice over — it reported a PATH miss on win32, where the
|
|
499
|
+
// compiler is normally an MSYS2 script deliberately off PATH (the exact case
|
|
500
|
+
// that function's comment exists to explain), and it made `system-check`
|
|
501
|
+
// exit 1 on any host without it, `.blp` files in the project or not. It also
|
|
502
|
+
// printed a second entry under a second display name and put
|
|
503
|
+
// `blueprint-compiler` into the install hint twice.
|
|
438
504
|
results.push(checkBinary('pkg-config', 'pkg-config', 'pkg-config', ['--version'], 'required'));
|
|
439
505
|
results.push(checkBinary('meson', 'Meson', 'meson', ['--version'], 'required'));
|
|
440
|
-
// Foundational libraries
|
|
441
|
-
results.push(
|
|
442
|
-
results.push(
|
|
443
|
-
results.push(
|
|
506
|
+
// Foundational libraries — by TYPELIB, the runtime question. See checkGiLibrary.
|
|
507
|
+
results.push(checkGiLibrary('gtk4', 'GTK4', 'Gtk', '4.0', 'gtk4', 'required'));
|
|
508
|
+
results.push(checkGiLibrary('libadwaita', 'libadwaita', 'Adw', '1', 'libadwaita-1', 'required'));
|
|
509
|
+
results.push(checkGiLibrary('libsoup3', 'libsoup3', 'Soup', '3.0', 'libsoup-3.0', 'required'));
|
|
444
510
|
results.push(checkPkgConfig('gobject-introspection', 'GObject Introspection', 'gobject-introspection-1.0', 'required'));
|
|
445
511
|
return results;
|
|
446
512
|
}
|
|
@@ -528,6 +594,7 @@ function runOptionalChecks(needed, cwd) {
|
|
|
528
594
|
// Entries with multiple space-separated packages are intentional (one dep needs multiple system pkgs).
|
|
529
595
|
const PM_PACKAGES = {
|
|
530
596
|
apt: {
|
|
597
|
+
nodejs: 'nodejs',
|
|
531
598
|
gjs: 'gjs',
|
|
532
599
|
'blueprint-compiler': 'blueprint-compiler',
|
|
533
600
|
'pkg-config': 'pkg-config',
|
|
@@ -553,6 +620,7 @@ const PM_PACKAGES = {
|
|
|
553
620
|
epoxy: 'libepoxy-dev',
|
|
554
621
|
},
|
|
555
622
|
dnf: {
|
|
623
|
+
nodejs: 'nodejs',
|
|
556
624
|
gjs: 'gjs',
|
|
557
625
|
'blueprint-compiler': 'blueprint-compiler',
|
|
558
626
|
'pkg-config': 'pkgconf-pkg-config',
|
|
@@ -578,6 +646,7 @@ const PM_PACKAGES = {
|
|
|
578
646
|
epoxy: 'libepoxy-devel',
|
|
579
647
|
},
|
|
580
648
|
pacman: {
|
|
649
|
+
nodejs: 'nodejs',
|
|
581
650
|
gjs: 'gjs',
|
|
582
651
|
'blueprint-compiler': 'blueprint-compiler',
|
|
583
652
|
'pkg-config': 'pkgconf',
|
|
@@ -603,6 +672,7 @@ const PM_PACKAGES = {
|
|
|
603
672
|
epoxy: 'libepoxy',
|
|
604
673
|
},
|
|
605
674
|
zypper: {
|
|
675
|
+
nodejs: 'nodejs',
|
|
606
676
|
gjs: 'gjs',
|
|
607
677
|
'blueprint-compiler': 'blueprint-compiler',
|
|
608
678
|
'pkg-config': 'pkg-config',
|
|
@@ -628,6 +698,7 @@ const PM_PACKAGES = {
|
|
|
628
698
|
epoxy: 'libepoxy-devel',
|
|
629
699
|
},
|
|
630
700
|
apk: {
|
|
701
|
+
nodejs: 'nodejs',
|
|
631
702
|
gjs: 'gjs',
|
|
632
703
|
'blueprint-compiler': 'blueprint-compiler',
|
|
633
704
|
'pkg-config': 'pkgconf',
|
|
@@ -675,6 +746,7 @@ const PM_PACKAGES = {
|
|
|
675
746
|
// Recorded here so the next reader does not "fix" the gap by guessing a name;
|
|
676
747
|
// both are genuine platform gaps, not table omissions.
|
|
677
748
|
brew: {
|
|
749
|
+
nodejs: 'node',
|
|
678
750
|
gjs: 'gjs',
|
|
679
751
|
'blueprint-compiler': 'blueprint-compiler',
|
|
680
752
|
'pkg-config': 'pkgconf',
|
|
@@ -750,15 +822,22 @@ export function buildInstallCommand(pm, missing) {
|
|
|
750
822
|
'mingw-w64-ucrt-x86_64-libadwaita # inside MSYS2; found automatically afterwards');
|
|
751
823
|
continue;
|
|
752
824
|
}
|
|
753
|
-
|
|
754
|
-
|
|
825
|
+
// No `continue` for `nodejs`. It USED to be unmissable — the row was the
|
|
826
|
+
// literal `found: true` — and since it became a real probe it can report
|
|
827
|
+
// absent under GJS, so skipping it here printed "Missing optional: Node.js"
|
|
828
|
+
// above an install command that did not install it.
|
|
755
829
|
const pkg = pkgMap[dep.id];
|
|
756
830
|
if (pkg)
|
|
757
831
|
pkgs.push(pkg);
|
|
758
832
|
}
|
|
759
833
|
const lines = [];
|
|
760
834
|
if (pkgs.length > 0) {
|
|
761
|
-
|
|
835
|
+
// Deduplicated because the command is COPY-PASTED: two checks mapping to
|
|
836
|
+
// one system package (or one check reported under two ids) produced
|
|
837
|
+
// `apk add blueprint-compiler libadwaita-dev … blueprint-compiler`,
|
|
838
|
+
// which is how the duplicate blueprint check first became visible.
|
|
839
|
+
// Dropping the second occurrence cannot change what gets installed.
|
|
840
|
+
lines.push(`${PM_INSTALL_PREFIX[pm]} ${[...new Set(pkgs)].join(' ')}`);
|
|
762
841
|
}
|
|
763
842
|
if (npmDeps.length > 0) {
|
|
764
843
|
lines.push(`npm install ${npmDeps.join(' ')}`);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declare that this command keeps running after its handler resolves, so a
|
|
3
|
+
* caller must not treat that resolution as completion.
|
|
4
|
+
*/
|
|
5
|
+
export declare function markDaemonCommand(): void;
|
|
6
|
+
/** Whether a command dispatched in this process declared itself a daemon. */
|
|
7
|
+
export declare function isDaemonCommand(): boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Whether the command currently running SETTLES.
|
|
2
|
+
//
|
|
3
|
+
// Almost every gjsify command finishes: its handler resolves and the caller acts
|
|
4
|
+
// on the result. A watch loop does not — `runWatchLoop` resolves as soon as the
|
|
5
|
+
// watcher is armed and the process then lives on the watcher (Node) or the held
|
|
6
|
+
// main loop (GJS) until Ctrl+C. To anything that merely awaits the handler the
|
|
7
|
+
// two are the same event, and one caller has to tell them apart: `gjsify run`'s
|
|
8
|
+
// in-process fast path dispatches a `gjsify <sub>` script through the same yargs
|
|
9
|
+
// surface and then `process.exit()`s on the result. For `gjsify run dev` that
|
|
10
|
+
// exit fired the instant the first build+launch was done, killing the loop the
|
|
11
|
+
// script had just asked for — and that IS the documented entry point (`gjsify
|
|
12
|
+
// create` prints `gjsify run dev`, and every template's `dev` script is a bare
|
|
13
|
+
// `gjsify dev`). Invisible on Node, where the same script takes the spawn path.
|
|
14
|
+
//
|
|
15
|
+
// A module-level flag rather than a return value because the answer has to cross
|
|
16
|
+
// `runCli`, whose contract is `Promise<void>` for every command; threading a
|
|
17
|
+
// "did not settle" result through yargs would change the shape of all of them to
|
|
18
|
+
// carry a fact about one.
|
|
19
|
+
let daemon = false;
|
|
20
|
+
/**
|
|
21
|
+
* Declare that this command keeps running after its handler resolves, so a
|
|
22
|
+
* caller must not treat that resolution as completion.
|
|
23
|
+
*/
|
|
24
|
+
export function markDaemonCommand() {
|
|
25
|
+
daemon = true;
|
|
26
|
+
}
|
|
27
|
+
/** Whether a command dispatched in this process declared itself a daemon. */
|
|
28
|
+
export function isDaemonCommand() {
|
|
29
|
+
return daemon;
|
|
30
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface DevPlanRequest {
|
|
2
|
+
/** The project's `package.json#scripts`. */
|
|
3
|
+
scripts: Readonly<Record<string, string>>;
|
|
4
|
+
/** Name of the build script to reuse, e.g. `build:gjs`. */
|
|
5
|
+
scriptName: string;
|
|
6
|
+
/** `gjsify build --app <app>` target the chosen runtime consumes. */
|
|
7
|
+
app: 'gjs' | 'node';
|
|
8
|
+
/** The bundle the package declares for `app` (`gjsify.main` / `gjsify.example.node`). */
|
|
9
|
+
declaredBundle?: string;
|
|
10
|
+
/** `gjsify dev [entry]` — overrides the entry point the script names. */
|
|
11
|
+
entry?: string;
|
|
12
|
+
/** `--globals` override. */
|
|
13
|
+
globals?: string;
|
|
14
|
+
/** `--outfile` override. */
|
|
15
|
+
outfile?: string;
|
|
16
|
+
/** `--watch-dir` override. */
|
|
17
|
+
watchDir?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface DevPlan {
|
|
20
|
+
/** argv fed to `runCli` on every rebuild (starts with `build`). */
|
|
21
|
+
buildArgv: string[];
|
|
22
|
+
/**
|
|
23
|
+
* Bundle launched after a successful build, or undefined when nothing names
|
|
24
|
+
* one. See {@link noBundleError}.
|
|
25
|
+
*/
|
|
26
|
+
bundle?: string;
|
|
27
|
+
/** Directory watched recursively. */
|
|
28
|
+
watchDir: string;
|
|
29
|
+
}
|
|
30
|
+
/** The value of `--name value` / `--name=value` in `argv`, if present. */
|
|
31
|
+
export declare function readFlag(argv: readonly string[], name: string): string | undefined;
|
|
32
|
+
/** `argv` with `--name value` set — replacing any existing occurrence, else appended. */
|
|
33
|
+
export declare function withFlag(argv: readonly string[], name: string, value: string): string[];
|
|
34
|
+
/**
|
|
35
|
+
* The entry point a `gjsify build` argv names positionally. Only the token
|
|
36
|
+
* DIRECTLY after `build` counts: further positionals are indistinguishable from
|
|
37
|
+
* the value of a preceding flag without knowing every flag's arity, and a wrong
|
|
38
|
+
* guess here would silently build the wrong file.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildEntryPoint(argv: readonly string[]): string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the build argv, the bundle to launch and the directory to watch.
|
|
43
|
+
* Throws with both fixes named when the project declares neither.
|
|
44
|
+
*/
|
|
45
|
+
export declare function planDev(req: DevPlanRequest): DevPlan;
|
|
46
|
+
/**
|
|
47
|
+
* Message for a launch that has no bundle to launch. Separate from `planDev`
|
|
48
|
+
* because only the LAUNCHING caller needs one: a build writing an `--outdir`
|
|
49
|
+
* legitimately has no single output file, and refusing inside the planner would
|
|
50
|
+
* fail `--build-only` for a reason that does not apply to it.
|
|
51
|
+
*/
|
|
52
|
+
export declare function noBundleError(scriptName: string, app: 'gjs' | 'node'): Error;
|