@gjsify/cli 0.4.21 → 0.4.23

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.
@@ -0,0 +1,72 @@
1
+ import { runAllChecks, detectPackageManager, buildInstallCommand } from '../utils/check-system-deps.js';
2
+ export const systemCheckCommand = {
3
+ command: 'system-check',
4
+ description: 'Check that required system dependencies (GJS, GTK4, libsoup3, …) are installed. Optional dependencies are detected only when their @gjsify/* package is in your project. (Previously called `gjsify check`; the bare name now runs TypeScript checks across the workspace — see `gjsify check --help`.)',
5
+ builder: (yargs) => {
6
+ return yargs
7
+ .option('json', {
8
+ description: 'Output results as JSON',
9
+ type: 'boolean',
10
+ default: false,
11
+ });
12
+ },
13
+ handler: async (args) => {
14
+ const results = runAllChecks(process.cwd());
15
+ const pm = detectPackageManager();
16
+ const missingRequired = results.filter(r => !r.found && r.severity === 'required');
17
+ const missingOptional = results.filter(r => !r.found && r.severity === 'optional');
18
+ const allMissing = [...missingRequired, ...missingOptional];
19
+ if (args.json) {
20
+ console.log(JSON.stringify({ packageManager: pm, deps: results }, null, 2));
21
+ // Only required deps influence the exit code.
22
+ process.exit(missingRequired.length > 0 ? 1 : 0);
23
+ return;
24
+ }
25
+ console.log('System dependency check\n');
26
+ const required = results.filter(r => r.severity === 'required');
27
+ const optional = results.filter(r => r.severity === 'optional');
28
+ if (required.length > 0) {
29
+ console.log('Required:');
30
+ for (const dep of required) {
31
+ const icon = dep.found ? '✓' : '✗';
32
+ const ver = dep.version ? ` (${dep.version})` : '';
33
+ console.log(` ${icon} ${dep.name}${ver}`);
34
+ }
35
+ }
36
+ if (optional.length > 0) {
37
+ console.log('\nOptional:');
38
+ for (const dep of optional) {
39
+ // ⚠ for missing-but-needed-by-installed-packages, ○ for missing-but-not-needed (shouldn't appear in conditional mode)
40
+ const icon = dep.found ? '✓' : '⚠';
41
+ const ver = dep.version ? ` (${dep.version})` : '';
42
+ const requiredBy = dep.requiredBy && dep.requiredBy.length > 0
43
+ ? ` — needed by ${dep.requiredBy.join(', ')}`
44
+ : '';
45
+ console.log(` ${icon} ${dep.name}${ver}${requiredBy}`);
46
+ }
47
+ }
48
+ console.log(`\nPackage manager: ${pm}`);
49
+ if (allMissing.length === 0) {
50
+ console.log('\nAll dependencies found.');
51
+ return;
52
+ }
53
+ if (missingRequired.length > 0) {
54
+ console.log(`\nMissing required: ${missingRequired.map(d => d.name).join(', ')}`);
55
+ }
56
+ if (missingOptional.length > 0) {
57
+ console.log(`Missing optional: ${missingOptional.map(d => d.name).join(', ')}`);
58
+ }
59
+ const cmd = buildInstallCommand(pm, allMissing);
60
+ if (cmd) {
61
+ console.log(`\nTo install:\n ${cmd}`);
62
+ }
63
+ else {
64
+ console.log('\nNo install command available for your package manager. Install manually.');
65
+ }
66
+ // Exit non-zero ONLY if a required dependency is missing.
67
+ // Optional deps that are missing but needed by an installed @gjsify/*
68
+ // package generate a warning but keep exit code 0 — the user can still
69
+ // build/run code paths that don't touch the optional library.
70
+ process.exit(missingRequired.length > 0 ? 1 : 0);
71
+ },
72
+ };
package/lib/index.js CHANGED
@@ -4,7 +4,7 @@ import { dirname, join } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import yargs from 'yargs';
6
6
  import { hideBin } from 'yargs/helpers';
7
- import { buildCommand as build, testCommand as test, 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, foreachCommand as foreach, workspaceCommand as workspace, packCommand as pack, publishCommand as publish, selfUpdateCommand as selfUpdate, generateInstallerCommand as generateInstaller, uninstallCommand as uninstall, formatCommand as format, lintCommand as lint, fixCommand as fix, upgradeCommand as upgrade, barrelsCommand as barrels, } from './commands/index.js';
7
+ import { buildCommand as build, testCommand as test, runCommand as run, infoCommand as info, systemCheckCommand as systemCheck, 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, foreachCommand as foreach, workspaceCommand as workspace, packCommand as pack, publishCommand as publish, selfUpdateCommand as selfUpdate, generateInstallerCommand as generateInstaller, uninstallCommand as uninstall, formatCommand as format, lintCommand as lint, fixCommand as fix, upgradeCommand as upgrade, barrelsCommand as barrels, } from './commands/index.js';
8
8
  import { APP_NAME } from './constants.js';
9
9
  // Read the version from package.json adjacent to the bundle. yargs's
10
10
  // auto-version-discovery (its `pkg-up`-driven default) doesn't reach
@@ -48,6 +48,7 @@ await cli
48
48
  .command(run.command, run.description, run.builder, run.handler)
49
49
  .command(dlx.command, dlx.description, dlx.builder, dlx.handler)
50
50
  .command(info.command, info.description, info.builder, info.handler)
51
+ .command(systemCheck.command, systemCheck.description, systemCheck.builder, systemCheck.handler)
51
52
  .command(check.command, check.description, check.builder, check.handler)
52
53
  .command(showcase.command, showcase.description, showcase.builder, showcase.handler)
53
54
  .command(gresource.command, gresource.description, gresource.builder, gresource.handler)
@@ -253,6 +253,13 @@ export interface ConfigDataFlatpak {
253
253
  finishArgs?: string[];
254
254
  /** Extra Flatpak modules prepended before the app's own meson/simple module (e.g. `blueprint-compiler` build). */
255
255
  extraModules?: unknown[];
256
+ /**
257
+ * Full replacement for the manifest's `modules` array. When set, neither
258
+ * `extraModules` nor the meson default get added — the array is used
259
+ * verbatim. Right shape for CLI tools that ship a pre-built bundle and
260
+ * install via shell commands (`buildsystem: simple`) instead of meson.
261
+ */
262
+ modules?: unknown[];
256
263
  /** Cleanup glob patterns applied to the final manifest, e.g. `['/include', '/lib/pkgconfig']`. */
257
264
  cleanup?: string[];
258
265
  /** Source-of-truth lockfile for `gjsify flatpak deps` — `yarn.lock` or `package-lock.json`. */
@@ -81,6 +81,14 @@ const OPTIONAL_DEPS = {
81
81
  pangocairo: { id: 'pangocairo', name: 'PangoCairo', pkgName: 'pangocairo' },
82
82
  webkitgtk: { id: 'webkitgtk', name: 'WebKitGTK', pkgName: 'webkitgtk-6.0' },
83
83
  cairo: { id: 'cairo', name: 'Cairo', pkgName: 'cairo' },
84
+ // Build-time deps for @gjsify/*-native Vala prebuilds. End-users with
85
+ // installed prebuilds don't need the -devel package — only contributors
86
+ // rebuilding from source via `yarn build:prebuilds`. We still surface
87
+ // them in the optional set so the "missing" diagnostic catches build-
88
+ // time failures with an actionable install-hint instead of a meson
89
+ // `Run-time dependency X found: NO` error mid-build.
90
+ gnutls: { id: 'gnutls', name: 'GnuTLS', pkgName: 'gnutls' },
91
+ nghttp2: { id: 'nghttp2', name: 'libnghttp2', pkgName: 'libnghttp2' },
84
92
  };
85
93
  /**
86
94
  * Map of @gjsify/* package name → ids of OPTIONAL_DEPS this package needs.
@@ -101,6 +109,12 @@ const PACKAGE_DEPS = {
101
109
  // runOptionalChecks. Mapping it here so its presence in the project's
102
110
  // dep tree triggers the check.
103
111
  '@gjsify/webgl': ['gwebgl'],
112
+ // Native Vala bridges with `dependency('gnutls')` / `dependency('libnghttp2')`
113
+ // in their meson.build. Optional because the shipped prebuild covers the
114
+ // common-arch user path; only contributors rebuilding from source hit the
115
+ // build-time dep.
116
+ '@gjsify/tls-native': ['gnutls'],
117
+ '@gjsify/http2-native': ['nghttp2'],
104
118
  // @gjsify/event-bridge only needs gtk4/gdk which are already in the
105
119
  // required set, so it doesn't need an optional entry.
106
120
  };
@@ -278,6 +292,8 @@ const PM_PACKAGES = {
278
292
  pango: 'libpango1.0-dev',
279
293
  pangocairo: 'libpango1.0-dev',
280
294
  cairo: 'libcairo2-dev',
295
+ gnutls: 'libgnutls28-dev',
296
+ nghttp2: 'libnghttp2-dev',
281
297
  },
282
298
  dnf: {
283
299
  gjs: 'gjs',
@@ -296,6 +312,8 @@ const PM_PACKAGES = {
296
312
  pango: 'pango-devel',
297
313
  pangocairo: 'pango-devel',
298
314
  cairo: 'cairo-devel',
315
+ gnutls: 'gnutls-devel',
316
+ nghttp2: 'libnghttp2-devel',
299
317
  },
300
318
  pacman: {
301
319
  gjs: 'gjs',
@@ -314,6 +332,8 @@ const PM_PACKAGES = {
314
332
  pango: 'pango',
315
333
  pangocairo: 'pango',
316
334
  cairo: 'cairo',
335
+ gnutls: 'gnutls',
336
+ nghttp2: 'libnghttp2',
317
337
  },
318
338
  zypper: {
319
339
  gjs: 'gjs',
@@ -332,6 +352,8 @@ const PM_PACKAGES = {
332
352
  pango: 'pango-devel',
333
353
  pangocairo: 'pango-devel',
334
354
  cairo: 'cairo-devel',
355
+ gnutls: 'libgnutls-devel',
356
+ nghttp2: 'libnghttp2-devel',
335
357
  },
336
358
  apk: {
337
359
  gjs: 'gjs',
@@ -350,6 +372,8 @@ const PM_PACKAGES = {
350
372
  pango: 'pango-dev',
351
373
  pangocairo: 'pango-dev',
352
374
  cairo: 'cairo-dev',
375
+ gnutls: 'gnutls-dev',
376
+ nghttp2: 'nghttp2-dev',
353
377
  },
354
378
  unknown: {},
355
379
  };
@@ -0,0 +1,21 @@
1
+ export interface RunLifecycleScriptOptions {
2
+ /** When true, do not throw on missing scripts — return `false` instead. */
3
+ optional?: boolean;
4
+ /**
5
+ * Stdio inheritance for the spawned script. Default `'inherit'` so output
6
+ * appears in the parent's terminal. Pass `'inherit-stderr'` to mirror
7
+ * inheritance but redirect the child's stdout → parent's stderr — used by
8
+ * `gjsify pack --json` and `gjsify publish` so the parent's stdout stays
9
+ * a clean JSON stream (script log lines would otherwise corrupt the
10
+ * machine-readable output that callers `JSON.parse`).
11
+ */
12
+ stdio?: 'inherit' | 'inherit-stderr' | 'pipe' | 'ignore';
13
+ /** Extra environment variables layered on top of the defaults. */
14
+ env?: Record<string, string>;
15
+ }
16
+ /**
17
+ * Run a lifecycle script defined in `pkg.scripts[name]` from `wsDir`.
18
+ * Returns `true` if the script existed and exited 0. Returns `false` if
19
+ * `optional: true` and the script is missing. Throws on non-zero exit.
20
+ */
21
+ export declare function runLifecycleScript(wsDir: string, pkg: Record<string, unknown>, name: string, opts?: RunLifecycleScriptOptions): Promise<boolean>;
@@ -0,0 +1,83 @@
1
+ // Run an npm-style lifecycle script (`prepack`, `prepare`, `prepublishOnly`,
2
+ // `postpack`, `postpublish`, …) from inside a `gjsify pack` / `gjsify
3
+ // publish` flow.
4
+ //
5
+ // Why this exists separately from `gjsify run`: `gjsify run <script>` ends
6
+ // with `process.exit(<code>)` so it can be used as a CLI entrypoint that
7
+ // behaves like `yarn run` / `npm run`. That's wrong for the embedded use
8
+ // case where pack/publish needs the lifecycle script to finish, then keep
9
+ // running its own logic. This helper resolves to a Promise that settles
10
+ // when the child exits — no process.exit, no GLib-mainloop intermingling
11
+ // from `ensureMainLoop`.
12
+ //
13
+ // Matches yarn / npm script semantics:
14
+ // - `shell: true` so `&&` / `|` / env-var refs work
15
+ // - PATH prepended with `<wsDir>/node_modules/.bin` + monorepo-root bin
16
+ // - `npm_lifecycle_event` / `npm_package_name` / `npm_package_version`
17
+ // env vars set
18
+ // - FORCE_COLOR=1 default unless caller overrides
19
+ import { spawn } from 'node:child_process';
20
+ import { delimiter, join } from 'node:path';
21
+ import { findWorkspaceRoot } from './workspace-root.js';
22
+ /**
23
+ * Run a lifecycle script defined in `pkg.scripts[name]` from `wsDir`.
24
+ * Returns `true` if the script existed and exited 0. Returns `false` if
25
+ * `optional: true` and the script is missing. Throws on non-zero exit.
26
+ */
27
+ export async function runLifecycleScript(wsDir, pkg, name, opts = {}) {
28
+ const scripts = pkg.scripts ?? {};
29
+ const literal = scripts[name];
30
+ if (typeof literal !== 'string') {
31
+ if (opts.optional !== false)
32
+ return false;
33
+ throw new Error(`gjsify lifecycle-script: no "${name}" in ${wsDir}/package.json`);
34
+ }
35
+ const monorepoRoot = findWorkspaceRoot(wsDir);
36
+ const binDirs = [join(wsDir, 'node_modules', '.bin')];
37
+ if (monorepoRoot && monorepoRoot !== wsDir) {
38
+ binDirs.push(join(monorepoRoot, 'node_modules', '.bin'));
39
+ }
40
+ // Match yarn / npm color-forcing default — see runScript() in run.ts
41
+ // for the full reasoning. Without this, lifecycle scripts that call
42
+ // tools like biome / esbuild / tsc lose ANSI color in piped contexts
43
+ // (CI logs, redirected output) because `process.stdout.isTTY` is
44
+ // false for the spawned child.
45
+ const colorEnv = process.env.FORCE_COLOR !== undefined || process.env.NO_COLOR !== undefined
46
+ ? {}
47
+ : { FORCE_COLOR: '1' };
48
+ const env = {
49
+ ...process.env,
50
+ ...colorEnv,
51
+ PATH: [...binDirs, process.env.PATH ?? ''].filter(Boolean).join(delimiter),
52
+ npm_lifecycle_event: name,
53
+ npm_package_name: pkg.name ?? '',
54
+ npm_package_version: pkg.version ?? '',
55
+ ...(opts.env ?? {}),
56
+ };
57
+ // `'inherit-stderr'` is our extension on top of node's stdio modes —
58
+ // child stdin inherits, child stdout → parent's stderr (fd 2), child
59
+ // stderr → parent's stderr. Used by `gjsify pack --json` so the
60
+ // prepack's log lines don't get interleaved with the JSON we emit on
61
+ // parent stdout. `spawn`'s `stdio` accepts numeric fds in array form
62
+ // and routes the child's matching stream to that fd.
63
+ const stdioConfig = opts.stdio === 'inherit-stderr'
64
+ ? ['inherit', 2, 2]
65
+ : (opts.stdio ?? 'inherit');
66
+ await new Promise((resolveOk, reject) => {
67
+ const child = spawn(literal, [], {
68
+ cwd: wsDir,
69
+ env: env,
70
+ stdio: stdioConfig,
71
+ shell: true,
72
+ });
73
+ child.on('close', (code) => {
74
+ if (code === 0)
75
+ resolveOk();
76
+ else {
77
+ reject(new Error(`gjsify lifecycle-script: "${name}" in ${wsDir} exited with code ${code}`));
78
+ }
79
+ });
80
+ child.on('error', reject);
81
+ });
82
+ return true;
83
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.4.21",
3
+ "version": "0.4.23",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -12,6 +12,89 @@
12
12
  "gjsify": {
13
13
  "bin": {
14
14
  "gjsify": "./dist/cli.gjs.mjs"
15
+ },
16
+ "flatpak": {
17
+ "appId": "io.github.gjsify.Cli",
18
+ "kind": "cli",
19
+ "name": "gjsify",
20
+ "runtime": "gnome",
21
+ "runtimeVersion": "50",
22
+ "sdkExtensions": [
23
+ "org.freedesktop.Sdk.Extension.node24"
24
+ ],
25
+ "appendPath": [
26
+ "/usr/lib/sdk/node24/bin"
27
+ ],
28
+ "command": "gjsify",
29
+ "finishArgs": [
30
+ "--share=network",
31
+ "--filesystem=host"
32
+ ],
33
+ "developer": {
34
+ "id": "io.github.gjsify",
35
+ "name": "gjsify contributors",
36
+ "email": "pascal@artandcode.studio"
37
+ },
38
+ "summary": "Node.js + Web APIs for GJS — bundle, install, lint, format, flatpak-pack GJS apps",
39
+ "description": [
40
+ {
41
+ "p": "gjsify is the build-and-tooling CLI for the gjsify project — a polyfill family + bundler that lets you write GNOME apps in Node-shaped TypeScript and run them on GJS (the GNOME JavaScript runtime) without Node.js at runtime."
42
+ },
43
+ {
44
+ "p": "Use this Flatpak when you want gjsify available on any modern Linux distro without installing Node.js or a system-wide npm setup. The CLI ships its own GJS bundle and a pinned Node 24 SDK extension for the subcommands (`gjsify build`, `gjsify lint`, `gjsify format`) that still call out to Node tooling internally."
45
+ },
46
+ {
47
+ "ul": [
48
+ {
49
+ "item": "`gjsify build` — bundle a TypeScript entry into a single GJS / Node / browser file via Rolldown"
50
+ },
51
+ {
52
+ "item": "`gjsify install` — Node-free npm install with a lockfile (XDG-aware, native backend)"
53
+ },
54
+ {
55
+ "item": "`gjsify dlx <pkg>` — fetch and run any npm package's GJS bundle without persisting it"
56
+ },
57
+ {
58
+ "item": "`gjsify lint` / `gjsify format` — Biome-powered linter + formatter"
59
+ },
60
+ {
61
+ "item": "`gjsify flatpak {init,build,check,deps,ci}` — pack a GJS app or CLI into a Flatpak end-to-end"
62
+ },
63
+ {
64
+ "item": "`gjsify gresource` — bundle GResource XML files for GTK apps"
65
+ },
66
+ {
67
+ "item": "`gjsify showcase` — run the bundled demo programs (Excalibur, three.js, WebRTC, …)"
68
+ }
69
+ ]
70
+ }
71
+ ],
72
+ "license": {
73
+ "metadata": "CC0-1.0",
74
+ "project": "MIT"
75
+ },
76
+ "categories": [
77
+ "Development"
78
+ ],
79
+ "homepageUrl": "https://gjsify.github.io/gjsify/",
80
+ "vcsBrowserUrl": "https://github.com/gjsify/gjsify",
81
+ "issueTrackerUrl": "https://github.com/gjsify/gjsify/issues",
82
+ "modules": [
83
+ {
84
+ "name": "gjsify-cli",
85
+ "buildsystem": "simple",
86
+ "build-commands": [
87
+ "install -Dm755 dist/cli.gjs.mjs /app/share/gjsify/cli.gjs.mjs",
88
+ "install -Dm755 launcher.sh /app/bin/gjsify"
89
+ ],
90
+ "sources": [
91
+ {
92
+ "type": "dir",
93
+ "path": "."
94
+ }
95
+ ]
96
+ }
97
+ ]
15
98
  }
16
99
  },
17
100
  "files": [
@@ -37,18 +120,18 @@
37
120
  "cli"
38
121
  ],
39
122
  "dependencies": {
40
- "@gjsify/buffer": "^0.4.21",
41
- "@gjsify/create-app": "^0.4.21",
42
- "@gjsify/node-globals": "^0.4.21",
43
- "@gjsify/node-polyfills": "^0.4.21",
44
- "@gjsify/npm-registry": "^0.4.21",
45
- "@gjsify/resolve-npm": "^0.4.21",
46
- "@gjsify/rolldown-plugin-gjsify": "^0.4.21",
47
- "@gjsify/rolldown-plugin-pnp": "^0.4.21",
48
- "@gjsify/semver": "^0.4.21",
49
- "@gjsify/tar": "^0.4.21",
50
- "@gjsify/web-polyfills": "^0.4.21",
51
- "@gjsify/workspace": "^0.4.21",
123
+ "@gjsify/buffer": "^0.4.23",
124
+ "@gjsify/create-app": "^0.4.23",
125
+ "@gjsify/node-globals": "^0.4.23",
126
+ "@gjsify/node-polyfills": "^0.4.23",
127
+ "@gjsify/npm-registry": "^0.4.23",
128
+ "@gjsify/resolve-npm": "^0.4.23",
129
+ "@gjsify/rolldown-plugin-gjsify": "^0.4.23",
130
+ "@gjsify/rolldown-plugin-pnp": "^0.4.23",
131
+ "@gjsify/semver": "^0.4.23",
132
+ "@gjsify/tar": "^0.4.23",
133
+ "@gjsify/web-polyfills": "^0.4.23",
134
+ "@gjsify/workspace": "^0.4.23",
52
135
  "cosmiconfig": "^9.0.1",
53
136
  "get-tsconfig": "^4.14.0",
54
137
  "pkg-types": "^2.3.1",
@@ -56,12 +139,12 @@
56
139
  "yargs": "^18.0.0"
57
140
  },
58
141
  "devDependencies": {
59
- "@gjsify/unit": "^0.4.21",
142
+ "@gjsify/unit": "^0.4.23",
60
143
  "@types/yargs": "^17.0.35",
61
144
  "typescript": "^6.0.3"
62
145
  },
63
146
  "peerDependencies": {
64
- "@gjsify/rolldown-native": "^0.4.21"
147
+ "@gjsify/rolldown-native": "^0.4.23"
65
148
  },
66
149
  "peerDependenciesMeta": {
67
150
  "@gjsify/rolldown-native": {