@gjsify/cli 0.1.2 → 0.1.4
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/commands/check.d.ts +6 -0
- package/lib/commands/check.js +44 -0
- package/lib/commands/index.d.ts +2 -0
- package/lib/commands/index.js +2 -0
- package/lib/commands/run.js +2 -25
- package/lib/commands/showcase.d.ts +8 -0
- package/lib/commands/showcase.js +83 -0
- package/lib/index.js +3 -1
- package/lib/utils/check-system-deps.d.ts +18 -0
- package/lib/utils/check-system-deps.js +185 -0
- package/lib/utils/detect-native-packages.d.ts +9 -0
- package/lib/utils/detect-native-packages.js +62 -1
- package/lib/utils/discover-examples.d.ts +19 -0
- package/lib/utils/discover-examples.js +69 -0
- package/lib/utils/run-gjs.d.ts +9 -0
- package/lib/utils/run-gjs.js +47 -0
- package/package.json +51 -5
- package/src/commands/check.ts +55 -0
- package/src/commands/index.ts +3 -1
- package/src/commands/run.ts +2 -28
- package/src/commands/showcase.ts +99 -0
- package/src/index.ts +3 -1
- package/src/utils/check-system-deps.ts +216 -0
- package/src/utils/detect-native-packages.ts +61 -1
- package/src/utils/discover-examples.ts +87 -0
- package/src/utils/run-gjs.ts +54 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -23,11 +23,57 @@
|
|
|
23
23
|
"cli"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@gjsify/esbuild-plugin-gjsify": "^0.1.
|
|
27
|
-
"@gjsify/
|
|
28
|
-
"@gjsify/
|
|
26
|
+
"@gjsify/esbuild-plugin-gjsify": "^0.1.4",
|
|
27
|
+
"@gjsify/example-dom-canvas2d-confetti": "^0.1.4",
|
|
28
|
+
"@gjsify/example-dom-canvas2d-fireworks": "^0.1.4",
|
|
29
|
+
"@gjsify/example-dom-canvas2d-text": "^0.1.4",
|
|
30
|
+
"@gjsify/example-dom-iframe-basic": "^0.1.4",
|
|
31
|
+
"@gjsify/example-dom-three-extrude-shapes": "^0.1.4",
|
|
32
|
+
"@gjsify/example-dom-three-geometry-cube": "^0.1.4",
|
|
33
|
+
"@gjsify/example-dom-three-geometry-shapes": "^0.1.4",
|
|
34
|
+
"@gjsify/example-dom-three-geometry-teapot": "^0.1.4",
|
|
35
|
+
"@gjsify/example-dom-three-loader-ldraw": "^0.1.4",
|
|
36
|
+
"@gjsify/example-dom-three-morphtargets": "^0.1.4",
|
|
37
|
+
"@gjsify/example-dom-three-multiple-rendertargets": "^0.1.4",
|
|
38
|
+
"@gjsify/example-dom-three-postprocessing-pixel": "^0.1.4",
|
|
39
|
+
"@gjsify/example-dom-webgl-demo-fade": "^0.1.4",
|
|
40
|
+
"@gjsify/example-dom-webgl-tutorial-01": "^0.1.4",
|
|
41
|
+
"@gjsify/example-dom-webgl-tutorial-02": "^0.1.4",
|
|
42
|
+
"@gjsify/example-dom-webgl-tutorial-03": "^0.1.4",
|
|
43
|
+
"@gjsify/example-dom-webgl-tutorial-04": "^0.1.4",
|
|
44
|
+
"@gjsify/example-dom-webgl-tutorial-05": "^0.1.4",
|
|
45
|
+
"@gjsify/example-dom-webgl-tutorial-06": "^0.1.4",
|
|
46
|
+
"@gjsify/example-dom-webgl-tutorial-07": "^0.1.4",
|
|
47
|
+
"@gjsify/example-node-cli-deepkit-di": "^0.1.4",
|
|
48
|
+
"@gjsify/example-node-cli-deepkit-events": "^0.1.4",
|
|
49
|
+
"@gjsify/example-node-cli-deepkit-types": "^0.1.4",
|
|
50
|
+
"@gjsify/example-node-cli-deepkit-validation": "^0.1.4",
|
|
51
|
+
"@gjsify/example-node-cli-deepkit-workflow": "^0.1.4",
|
|
52
|
+
"@gjsify/example-node-cli-dns-lookup": "^0.1.4",
|
|
53
|
+
"@gjsify/example-node-cli-esbuild-plugin-deepkit": "^0.1.4",
|
|
54
|
+
"@gjsify/example-node-cli-esbuild-plugin-transform-ext": "^0.1.4",
|
|
55
|
+
"@gjsify/example-node-cli-file-search": "^0.1.4",
|
|
56
|
+
"@gjsify/example-node-cli-gio-cat": "^0.1.4",
|
|
57
|
+
"@gjsify/example-node-cli-json-store": "^0.1.4",
|
|
58
|
+
"@gjsify/example-node-cli-node-buffer": "^0.1.4",
|
|
59
|
+
"@gjsify/example-node-cli-node-events": "^0.1.4",
|
|
60
|
+
"@gjsify/example-node-cli-node-fs": "^0.1.4",
|
|
61
|
+
"@gjsify/example-node-cli-node-os": "^0.1.4",
|
|
62
|
+
"@gjsify/example-node-cli-node-path": "^0.1.4",
|
|
63
|
+
"@gjsify/example-node-cli-node-url": "^0.1.4",
|
|
64
|
+
"@gjsify/example-node-cli-worker-pool": "^0.1.4",
|
|
65
|
+
"@gjsify/example-node-cli-yargs-demo": "^0.1.4",
|
|
66
|
+
"@gjsify/example-node-gtk-http-dashboard": "^0.1.4",
|
|
67
|
+
"@gjsify/example-node-net-express-hello": "^0.1.4",
|
|
68
|
+
"@gjsify/example-node-net-hono-rest": "^0.1.4",
|
|
69
|
+
"@gjsify/example-node-net-koa-blog": "^0.1.4",
|
|
70
|
+
"@gjsify/example-node-net-sse-chat": "^0.1.4",
|
|
71
|
+
"@gjsify/example-node-net-static-file-server": "^0.1.4",
|
|
72
|
+
"@gjsify/example-node-net-ws-chat": "^0.1.4",
|
|
73
|
+
"@gjsify/node-polyfills": "^0.1.4",
|
|
74
|
+
"@gjsify/web-polyfills": "^0.1.4",
|
|
29
75
|
"cosmiconfig": "^9.0.1",
|
|
30
|
-
"esbuild": "^0.
|
|
76
|
+
"esbuild": "^0.28.0",
|
|
31
77
|
"get-tsconfig": "^4.13.7",
|
|
32
78
|
"pkg-types": "^2.3.0",
|
|
33
79
|
"yargs": "^18.0.0"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Command } from '../types/index.js';
|
|
2
|
+
import { runAllChecks, detectPackageManager, buildInstallCommand } from '../utils/check-system-deps.js';
|
|
3
|
+
|
|
4
|
+
interface CheckOptions {
|
|
5
|
+
json: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const checkCommand: Command<any, CheckOptions> = {
|
|
9
|
+
command: 'check',
|
|
10
|
+
description: 'Check that all required system dependencies (GJS, GTK4, Blueprint Compiler, etc.) are installed.',
|
|
11
|
+
builder: (yargs) => {
|
|
12
|
+
return yargs
|
|
13
|
+
.option('json', {
|
|
14
|
+
description: 'Output results as JSON',
|
|
15
|
+
type: 'boolean',
|
|
16
|
+
default: false,
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
handler: async (args) => {
|
|
20
|
+
const cwd = process.cwd();
|
|
21
|
+
const results = runAllChecks(cwd);
|
|
22
|
+
const pm = detectPackageManager();
|
|
23
|
+
const missing = results.filter(r => !r.found);
|
|
24
|
+
|
|
25
|
+
if (args.json) {
|
|
26
|
+
console.log(JSON.stringify({ packageManager: pm, deps: results }, null, 2));
|
|
27
|
+
process.exit(missing.length > 0 ? 1 : 0);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log('System dependency check\n');
|
|
32
|
+
for (const dep of results) {
|
|
33
|
+
const icon = dep.found ? '✓' : '✗';
|
|
34
|
+
const ver = dep.version ? ` (${dep.version})` : '';
|
|
35
|
+
console.log(` ${icon} ${dep.name}${ver}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(`\nPackage manager: ${pm}`);
|
|
39
|
+
|
|
40
|
+
if (missing.length === 0) {
|
|
41
|
+
console.log('\nAll dependencies found.');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log(`\nMissing: ${missing.map(d => d.name).join(', ')}`);
|
|
46
|
+
const cmd = buildInstallCommand(pm, missing);
|
|
47
|
+
if (cmd) {
|
|
48
|
+
console.log(`\nTo install missing dependencies:\n ${cmd}`);
|
|
49
|
+
} else {
|
|
50
|
+
console.log('\nNo install command available for your package manager. Install manually.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
process.exit(1);
|
|
54
|
+
},
|
|
55
|
+
};
|
package/src/commands/index.ts
CHANGED
package/src/commands/run.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
1
|
import { resolve } from 'node:path';
|
|
3
2
|
import type { Command } from '../types/index.js';
|
|
4
|
-
import {
|
|
3
|
+
import { runGjsBundle } from '../utils/run-gjs.js';
|
|
5
4
|
|
|
6
5
|
interface RunOptions {
|
|
7
6
|
file: string;
|
|
@@ -29,31 +28,6 @@ export const runCommand: Command<any, RunOptions> = {
|
|
|
29
28
|
handler: async (args) => {
|
|
30
29
|
const file = resolve(args.file as string);
|
|
31
30
|
const extraArgs = (args.args as string[]) ?? [];
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const nativePackages = detectNativePackages(cwd);
|
|
35
|
-
const nativeEnv = buildNativeEnv(nativePackages);
|
|
36
|
-
|
|
37
|
-
const env = {
|
|
38
|
-
...process.env,
|
|
39
|
-
...nativeEnv,
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const gjsArgs = ['-m', file, ...extraArgs];
|
|
43
|
-
const child = spawn('gjs', gjsArgs, { env, stdio: 'inherit' });
|
|
44
|
-
|
|
45
|
-
await new Promise<void>((resolvePromise, reject) => {
|
|
46
|
-
child.on('close', (code) => {
|
|
47
|
-
if (code !== 0) {
|
|
48
|
-
reject(new Error(`gjs exited with code ${code}`));
|
|
49
|
-
} else {
|
|
50
|
-
resolvePromise();
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
child.on('error', reject);
|
|
54
|
-
}).catch((err) => {
|
|
55
|
-
console.error(err.message);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
});
|
|
31
|
+
await runGjsBundle(file, extraArgs);
|
|
58
32
|
},
|
|
59
33
|
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Command } from '../types/index.js';
|
|
2
|
+
import { discoverExamples, findExample } from '../utils/discover-examples.js';
|
|
3
|
+
import { runAllChecks, detectPackageManager, buildInstallCommand } from '../utils/check-system-deps.js';
|
|
4
|
+
import { runGjsBundle } from '../utils/run-gjs.js';
|
|
5
|
+
|
|
6
|
+
interface ShowcaseOptions {
|
|
7
|
+
name?: string;
|
|
8
|
+
json: boolean;
|
|
9
|
+
list: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const showcaseCommand: Command<any, ShowcaseOptions> = {
|
|
13
|
+
command: 'showcase [name]',
|
|
14
|
+
description: 'List or run built-in gjsify example applications.',
|
|
15
|
+
builder: (yargs) => {
|
|
16
|
+
return yargs
|
|
17
|
+
.positional('name', {
|
|
18
|
+
description: 'Example name to run (omit to list all)',
|
|
19
|
+
type: 'string',
|
|
20
|
+
})
|
|
21
|
+
.option('json', {
|
|
22
|
+
description: 'Output as JSON',
|
|
23
|
+
type: 'boolean',
|
|
24
|
+
default: false,
|
|
25
|
+
})
|
|
26
|
+
.option('list', {
|
|
27
|
+
description: 'List available examples',
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
default: false,
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
handler: async (args) => {
|
|
33
|
+
// List mode: no name given, or --list flag
|
|
34
|
+
if (!args.name || args.list) {
|
|
35
|
+
const examples = discoverExamples();
|
|
36
|
+
|
|
37
|
+
if (args.json) {
|
|
38
|
+
console.log(JSON.stringify(examples, null, 2));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (examples.length === 0) {
|
|
43
|
+
console.log('No examples found. Example packages may not be installed.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Group by category
|
|
48
|
+
const grouped = new Map<string, typeof examples>();
|
|
49
|
+
for (const ex of examples) {
|
|
50
|
+
const list = grouped.get(ex.category) ?? [];
|
|
51
|
+
list.push(ex);
|
|
52
|
+
grouped.set(ex.category, list);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log('Available gjsify examples:\n');
|
|
56
|
+
for (const [category, list] of grouped) {
|
|
57
|
+
console.log(` ${category.toUpperCase()}:`);
|
|
58
|
+
const maxNameLen = Math.max(...list.map(e => e.name.length));
|
|
59
|
+
for (const ex of list) {
|
|
60
|
+
const pad = ' '.repeat(maxNameLen - ex.name.length + 2);
|
|
61
|
+
const desc = ex.description ? `${pad}${ex.description}` : '';
|
|
62
|
+
console.log(` ${ex.name}${desc}`);
|
|
63
|
+
}
|
|
64
|
+
console.log('');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log('Run an example: gjsify showcase <name>');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Run mode: find the example
|
|
72
|
+
const example = findExample(args.name);
|
|
73
|
+
if (!example) {
|
|
74
|
+
console.error(`Unknown example: "${args.name}"`);
|
|
75
|
+
console.error('Run "gjsify showcase" to list available examples.');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// System dependency check before running
|
|
80
|
+
const results = runAllChecks(process.cwd());
|
|
81
|
+
const missing = results.filter(r => !r.found);
|
|
82
|
+
if (missing.length > 0) {
|
|
83
|
+
console.error('Missing system dependencies:\n');
|
|
84
|
+
for (const dep of missing) {
|
|
85
|
+
console.error(` ✗ ${dep.name}`);
|
|
86
|
+
}
|
|
87
|
+
const pm = detectPackageManager();
|
|
88
|
+
const cmd = buildInstallCommand(pm, missing);
|
|
89
|
+
if (cmd) {
|
|
90
|
+
console.error(`\nInstall with:\n ${cmd}`);
|
|
91
|
+
}
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Run the example via shared GJS runner
|
|
96
|
+
console.log(`Running example: ${example.name}\n`);
|
|
97
|
+
await runGjsBundle(example.bundlePath);
|
|
98
|
+
},
|
|
99
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import yargs from 'yargs'
|
|
3
3
|
import { hideBin } from 'yargs/helpers'
|
|
4
4
|
|
|
5
|
-
import { buildCommand as build, runCommand as run, infoCommand as info } from './commands/index.js'
|
|
5
|
+
import { buildCommand as build, runCommand as run, infoCommand as info, checkCommand as check, showcaseCommand as showcase } from './commands/index.js'
|
|
6
6
|
import { APP_NAME } from './constants.js'
|
|
7
7
|
|
|
8
8
|
void yargs(hideBin(process.argv))
|
|
@@ -12,5 +12,7 @@ void yargs(hideBin(process.argv))
|
|
|
12
12
|
.command(build.command, build.description, build.builder, build.handler)
|
|
13
13
|
.command(run.command, run.description, run.builder, run.handler)
|
|
14
14
|
.command(info.command, info.description, info.builder, info.handler)
|
|
15
|
+
.command(check.command, check.description, check.builder, check.handler)
|
|
16
|
+
.command(showcase.command, showcase.description, showcase.builder, showcase.handler)
|
|
15
17
|
.demandCommand(1)
|
|
16
18
|
.help().argv
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// System dependency checker for gjsify CLI.
|
|
2
|
+
// Uses execFileSync with explicit argument arrays — no shell injection possible.
|
|
3
|
+
// All binary names are hardcoded constants, never derived from user input.
|
|
4
|
+
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import { pathToFileURL } from 'node:url';
|
|
9
|
+
|
|
10
|
+
export interface DepCheck {
|
|
11
|
+
/** Stable key used for install command lookup, e.g. "gjs" */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Human-readable display name, e.g. "GJS" */
|
|
14
|
+
name: string;
|
|
15
|
+
found: boolean;
|
|
16
|
+
/** Version string when found, e.g. "1.86.0" */
|
|
17
|
+
version?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type PackageManager = 'apt' | 'dnf' | 'pacman' | 'zypper' | 'apk' | 'unknown';
|
|
21
|
+
|
|
22
|
+
/** Run a binary and return its stdout trimmed, or null if it fails. */
|
|
23
|
+
function tryExecFile(binary: string, args: string[]): string | null {
|
|
24
|
+
try {
|
|
25
|
+
return execFileSync(binary, args, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Check if a binary exists and optionally capture its version output. */
|
|
32
|
+
function checkBinary(id: string, name: string, binary: string, versionArgs: string[], parseVersion?: (out: string) => string): DepCheck {
|
|
33
|
+
const out = tryExecFile(binary, versionArgs);
|
|
34
|
+
if (out === null) return { id, name, found: false };
|
|
35
|
+
const version = parseVersion ? parseVersion(out) : out.split('\n')[0] ?? out;
|
|
36
|
+
return { id, name, found: true, version };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Check a pkg-config library. pkg-config --modversion returns version on stdout. */
|
|
40
|
+
function checkPkgConfig(id: string, name: string, libName: string): DepCheck {
|
|
41
|
+
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] };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check for an npm package by resolving it via Node.js module resolution from cwd.
|
|
48
|
+
* Mirrors the resolution logic used by esbuild / the build command.
|
|
49
|
+
*/
|
|
50
|
+
function checkNpmPackage(id: string, name: string, packageName: string, cwd: string): DepCheck {
|
|
51
|
+
try {
|
|
52
|
+
const requireFromCwd = createRequire(pathToFileURL(join(cwd, '_check_.js')).href);
|
|
53
|
+
requireFromCwd.resolve(packageName);
|
|
54
|
+
return { id, name, found: true };
|
|
55
|
+
} catch {
|
|
56
|
+
return { id, name, found: false };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function detectPackageManager(): PackageManager {
|
|
61
|
+
const managers: PackageManager[] = ['apt', 'dnf', 'pacman', 'zypper', 'apk'];
|
|
62
|
+
for (const pm of managers) {
|
|
63
|
+
const result = tryExecFile('which', [pm]);
|
|
64
|
+
if (result !== null) return pm;
|
|
65
|
+
}
|
|
66
|
+
return 'unknown';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function runAllChecks(cwd: string): DepCheck[] {
|
|
70
|
+
const results: DepCheck[] = [];
|
|
71
|
+
|
|
72
|
+
// Node.js — always present
|
|
73
|
+
results.push({ id: 'nodejs', name: 'Node.js', found: true, version: process.version });
|
|
74
|
+
|
|
75
|
+
// GJS
|
|
76
|
+
results.push(checkBinary('gjs', 'GJS', 'gjs', ['--version'],
|
|
77
|
+
(out) => out.replace(/^GJS\s+/i, '').split('\n')[0] ?? out));
|
|
78
|
+
|
|
79
|
+
// Blueprint Compiler
|
|
80
|
+
results.push(checkBinary('blueprint-compiler', 'Blueprint Compiler',
|
|
81
|
+
'blueprint-compiler', ['--version']));
|
|
82
|
+
|
|
83
|
+
// pkg-config (needed for library checks)
|
|
84
|
+
results.push(checkBinary('pkg-config', 'pkg-config', 'pkg-config', ['--version']));
|
|
85
|
+
|
|
86
|
+
// Meson (for building native extensions)
|
|
87
|
+
results.push(checkBinary('meson', 'Meson', 'meson', ['--version']));
|
|
88
|
+
|
|
89
|
+
// GTK4
|
|
90
|
+
results.push(checkPkgConfig('gtk4', 'GTK4', 'gtk4'));
|
|
91
|
+
|
|
92
|
+
// libadwaita
|
|
93
|
+
results.push(checkPkgConfig('libadwaita', 'libadwaita', 'libadwaita-1'));
|
|
94
|
+
|
|
95
|
+
// libsoup3
|
|
96
|
+
results.push(checkPkgConfig('libsoup3', 'libsoup3', 'libsoup-3.0'));
|
|
97
|
+
|
|
98
|
+
// WebKitGTK — try 4.1 first, fall back to 4.0
|
|
99
|
+
const webkitCheck = checkPkgConfig('webkitgtk', 'WebKitGTK', 'webkit2gtk-4.1');
|
|
100
|
+
if (webkitCheck.found) {
|
|
101
|
+
results.push(webkitCheck);
|
|
102
|
+
} else {
|
|
103
|
+
results.push(checkPkgConfig('webkitgtk', 'WebKitGTK', 'webkitgtk-6.0'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// GObject Introspection
|
|
107
|
+
results.push(checkPkgConfig('gobject-introspection', 'GObject Introspection', 'gobject-introspection-1.0'));
|
|
108
|
+
|
|
109
|
+
// gwebgl — resolve via Node.js module resolution from cwd, same as esbuild would.
|
|
110
|
+
results.push(checkNpmPackage('gwebgl', 'gwebgl (@gjsify/webgl)', '@gjsify/webgl', cwd));
|
|
111
|
+
|
|
112
|
+
return results;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Per-package-manager install package names, keyed by dep id.
|
|
116
|
+
// Entries with multiple space-separated packages are intentional (one dep needs multiple system pkgs).
|
|
117
|
+
const PM_PACKAGES: Record<PackageManager, Partial<Record<string, string>>> = {
|
|
118
|
+
apt: {
|
|
119
|
+
gjs: 'gjs',
|
|
120
|
+
'blueprint-compiler': 'blueprint-compiler',
|
|
121
|
+
'pkg-config': 'pkg-config',
|
|
122
|
+
meson: 'meson',
|
|
123
|
+
gtk4: 'libgtk-4-dev',
|
|
124
|
+
libadwaita: 'libadwaita-1-dev',
|
|
125
|
+
libsoup3: 'libsoup-3.0-dev',
|
|
126
|
+
webkitgtk: 'libwebkit2gtk-4.1-dev',
|
|
127
|
+
'gobject-introspection': 'gobject-introspection libgirepository1.0-dev',
|
|
128
|
+
},
|
|
129
|
+
dnf: {
|
|
130
|
+
gjs: 'gjs',
|
|
131
|
+
'blueprint-compiler': 'blueprint-compiler',
|
|
132
|
+
'pkg-config': 'pkgconf-pkg-config',
|
|
133
|
+
meson: 'meson',
|
|
134
|
+
gtk4: 'gtk4-devel',
|
|
135
|
+
libadwaita: 'libadwaita-devel',
|
|
136
|
+
libsoup3: 'libsoup3-devel',
|
|
137
|
+
webkitgtk: 'webkitgtk6.0-devel',
|
|
138
|
+
'gobject-introspection': 'gobject-introspection-devel',
|
|
139
|
+
},
|
|
140
|
+
pacman: {
|
|
141
|
+
gjs: 'gjs',
|
|
142
|
+
'blueprint-compiler': 'blueprint-compiler',
|
|
143
|
+
'pkg-config': 'pkgconf',
|
|
144
|
+
meson: 'meson',
|
|
145
|
+
gtk4: 'gtk4',
|
|
146
|
+
libadwaita: 'libadwaita',
|
|
147
|
+
libsoup3: 'libsoup3',
|
|
148
|
+
webkitgtk: 'webkit2gtk-4.1',
|
|
149
|
+
'gobject-introspection': 'gobject-introspection',
|
|
150
|
+
},
|
|
151
|
+
zypper: {
|
|
152
|
+
gjs: 'gjs',
|
|
153
|
+
'blueprint-compiler': 'blueprint-compiler',
|
|
154
|
+
'pkg-config': 'pkg-config',
|
|
155
|
+
meson: 'meson',
|
|
156
|
+
gtk4: 'gtk4-devel',
|
|
157
|
+
libadwaita: 'libadwaita-devel',
|
|
158
|
+
libsoup3: 'libsoup-3_0-devel',
|
|
159
|
+
webkitgtk: 'webkit2gtk3-devel',
|
|
160
|
+
'gobject-introspection': 'gobject-introspection-devel',
|
|
161
|
+
},
|
|
162
|
+
apk: {
|
|
163
|
+
gjs: 'gjs',
|
|
164
|
+
'blueprint-compiler': 'blueprint-compiler',
|
|
165
|
+
'pkg-config': 'pkgconf',
|
|
166
|
+
meson: 'meson',
|
|
167
|
+
gtk4: 'gtk4.0-dev',
|
|
168
|
+
libadwaita: 'libadwaita-dev',
|
|
169
|
+
libsoup3: 'libsoup3-dev',
|
|
170
|
+
webkitgtk: 'webkit2gtk-4.1-dev',
|
|
171
|
+
'gobject-introspection': 'gobject-introspection-dev',
|
|
172
|
+
},
|
|
173
|
+
unknown: {},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const PM_INSTALL_PREFIX: Record<PackageManager, string> = {
|
|
177
|
+
apt: 'sudo apt install',
|
|
178
|
+
dnf: 'sudo dnf install',
|
|
179
|
+
pacman: 'sudo pacman -S',
|
|
180
|
+
zypper: 'sudo zypper install',
|
|
181
|
+
apk: 'sudo apk add',
|
|
182
|
+
unknown: '',
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Build a suggested install command for missing dependencies.
|
|
187
|
+
* gwebgl is an npm package, not a system package — handled separately.
|
|
188
|
+
* Returns null when package manager is unknown or no installable deps are missing.
|
|
189
|
+
*/
|
|
190
|
+
export function buildInstallCommand(pm: PackageManager, missing: DepCheck[]): string | null {
|
|
191
|
+
if (pm === 'unknown') return null;
|
|
192
|
+
|
|
193
|
+
const pkgMap = PM_PACKAGES[pm];
|
|
194
|
+
const pkgs: string[] = [];
|
|
195
|
+
const npmDeps: string[] = [];
|
|
196
|
+
|
|
197
|
+
for (const dep of missing) {
|
|
198
|
+
if (dep.id === 'gwebgl') {
|
|
199
|
+
npmDeps.push('@gjsify/webgl');
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (dep.id === 'nodejs') continue; // can't be missing if we're running
|
|
203
|
+
const pkg = pkgMap[dep.id];
|
|
204
|
+
if (pkg) pkgs.push(pkg);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const lines: string[] = [];
|
|
208
|
+
if (pkgs.length > 0) {
|
|
209
|
+
lines.push(`${PM_INSTALL_PREFIX[pm]} ${pkgs.join(' ')}`);
|
|
210
|
+
}
|
|
211
|
+
if (npmDeps.length > 0) {
|
|
212
|
+
lines.push(`npm install ${npmDeps.join(' ')}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return lines.length > 0 ? lines.join('\n ') : null;
|
|
216
|
+
}
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
// The CLI uses this to auto-set LD_LIBRARY_PATH / GI_TYPELIB_PATH before running gjs.
|
|
4
4
|
|
|
5
5
|
import { readdirSync, existsSync, readFileSync } from 'node:fs';
|
|
6
|
-
import {
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { dirname, join, resolve } from 'node:path';
|
|
8
|
+
import { pathToFileURL } from 'node:url';
|
|
7
9
|
|
|
8
10
|
export interface NativePackage {
|
|
9
11
|
/** npm package name, e.g. "@gjsify/webgl" */
|
|
@@ -116,6 +118,64 @@ export function detectNativePackages(startDir: string): NativePackage[] {
|
|
|
116
118
|
return [];
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
/** Walk up from dir to find the nearest package.json. */
|
|
122
|
+
function findNearestPackageJson(startDir: string): string | null {
|
|
123
|
+
let dir = resolve(startDir);
|
|
124
|
+
while (true) {
|
|
125
|
+
const candidate = join(dir, 'package.json');
|
|
126
|
+
if (existsSync(candidate)) return candidate;
|
|
127
|
+
const parent = resolve(dir, '..');
|
|
128
|
+
if (parent === dir) return null;
|
|
129
|
+
dir = parent;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resolve native packages using Node.js module resolution from a given file path.
|
|
135
|
+
* Reads the nearest package.json to discover dependencies, then checks each
|
|
136
|
+
* for gjsify native prebuilds metadata.
|
|
137
|
+
*
|
|
138
|
+
* This complements detectNativePackages() (filesystem walk from CWD) by using
|
|
139
|
+
* require.resolve() — which handles hoisting, workspaces, and nested node_modules.
|
|
140
|
+
*/
|
|
141
|
+
export function resolveNativePackages(fromFilePath: string): NativePackage[] {
|
|
142
|
+
const arch = nodeArchToLinuxArch(process.arch);
|
|
143
|
+
const results: NativePackage[] = [];
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const req = createRequire(pathToFileURL(fromFilePath).href);
|
|
147
|
+
|
|
148
|
+
// Find the nearest package.json to get the dependency list
|
|
149
|
+
const nearestPkgJson = findNearestPackageJson(dirname(resolve(fromFilePath)));
|
|
150
|
+
if (!nearestPkgJson) return results;
|
|
151
|
+
|
|
152
|
+
const pkg = readPackageJson(nearestPkgJson);
|
|
153
|
+
if (!pkg) return results;
|
|
154
|
+
|
|
155
|
+
const deps = pkg['dependencies'] as Record<string, string> | undefined;
|
|
156
|
+
if (!deps) return results;
|
|
157
|
+
|
|
158
|
+
for (const depName of Object.keys(deps)) {
|
|
159
|
+
try {
|
|
160
|
+
// Resolve the package's main entry, then walk up to find its package.json.
|
|
161
|
+
// We cannot use require.resolve(name + '/package.json') because packages
|
|
162
|
+
// with an "exports" field may not expose ./package.json as a subpath.
|
|
163
|
+
const entryPath = req.resolve(depName);
|
|
164
|
+
const depPkgJson = findNearestPackageJson(dirname(entryPath));
|
|
165
|
+
if (!depPkgJson) continue;
|
|
166
|
+
const native = checkPackage(dirname(depPkgJson), depName, arch);
|
|
167
|
+
if (native) results.push(native);
|
|
168
|
+
} catch {
|
|
169
|
+
// Dependency not resolvable — skip
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} catch {
|
|
173
|
+
// Resolution failed — return empty
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return results;
|
|
177
|
+
}
|
|
178
|
+
|
|
119
179
|
/**
|
|
120
180
|
* Build the LD_LIBRARY_PATH and GI_TYPELIB_PATH env var values for the detected native packages.
|
|
121
181
|
* Prepends the new paths to any existing values from the environment.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Dynamic discovery of installed @gjsify/example-* packages.
|
|
2
|
+
// Scans the CLI's own package.json dependencies at runtime.
|
|
3
|
+
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
export interface ExampleInfo {
|
|
10
|
+
/** Short name, e.g. "three-geometry-shapes" */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Full npm package name, e.g. "@gjsify/example-dom-three-geometry-shapes" */
|
|
13
|
+
packageName: string;
|
|
14
|
+
/** Category: "dom" or "node" */
|
|
15
|
+
category: string;
|
|
16
|
+
/** Description from example's package.json */
|
|
17
|
+
description: string;
|
|
18
|
+
/** Absolute path to the GJS bundle (resolved from "main" field) */
|
|
19
|
+
bundlePath: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const EXAMPLE_PREFIX = '@gjsify/example-';
|
|
23
|
+
|
|
24
|
+
/** Extract short name and category from package name. */
|
|
25
|
+
function parseExampleName(packageName: string): { name: string; category: string } | null {
|
|
26
|
+
// @gjsify/example-dom-three-geometry-shapes → category=dom, name=three-geometry-shapes
|
|
27
|
+
// @gjsify/example-node-cli-node-path → category=node, name=cli-node-path
|
|
28
|
+
const suffix = packageName.slice(EXAMPLE_PREFIX.length);
|
|
29
|
+
const dashIdx = suffix.indexOf('-');
|
|
30
|
+
if (dashIdx === -1) return null;
|
|
31
|
+
return {
|
|
32
|
+
category: suffix.slice(0, dashIdx),
|
|
33
|
+
name: suffix.slice(dashIdx + 1),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Discover all installed example packages by scanning the CLI's own dependencies.
|
|
39
|
+
* Returns examples sorted by category then name.
|
|
40
|
+
*/
|
|
41
|
+
export function discoverExamples(): ExampleInfo[] {
|
|
42
|
+
const require = createRequire(import.meta.url);
|
|
43
|
+
const cliPkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json');
|
|
44
|
+
let cliPkg: Record<string, unknown>;
|
|
45
|
+
try {
|
|
46
|
+
cliPkg = JSON.parse(readFileSync(cliPkgPath, 'utf-8')) as Record<string, unknown>;
|
|
47
|
+
} catch {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const deps = cliPkg['dependencies'] as Record<string, string> | undefined;
|
|
52
|
+
if (!deps) return [];
|
|
53
|
+
|
|
54
|
+
const examples: ExampleInfo[] = [];
|
|
55
|
+
|
|
56
|
+
for (const packageName of Object.keys(deps)) {
|
|
57
|
+
if (!packageName.startsWith(EXAMPLE_PREFIX)) continue;
|
|
58
|
+
|
|
59
|
+
const parsed = parseExampleName(packageName);
|
|
60
|
+
if (!parsed) continue;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const pkgJsonPath = require.resolve(`${packageName}/package.json`);
|
|
64
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) as Record<string, unknown>;
|
|
65
|
+
const main = pkg['main'] as string | undefined;
|
|
66
|
+
if (!main) continue;
|
|
67
|
+
|
|
68
|
+
examples.push({
|
|
69
|
+
name: parsed.name,
|
|
70
|
+
packageName,
|
|
71
|
+
category: parsed.category,
|
|
72
|
+
description: (pkg['description'] as string) ?? '',
|
|
73
|
+
bundlePath: join(dirname(pkgJsonPath), main),
|
|
74
|
+
});
|
|
75
|
+
} catch {
|
|
76
|
+
// Package listed as dep but not resolvable — skip silently
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
examples.sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
|
|
81
|
+
return examples;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Find a single example by short name. */
|
|
85
|
+
export function findExample(name: string): ExampleInfo | undefined {
|
|
86
|
+
return discoverExamples().find(e => e.name === name);
|
|
87
|
+
}
|