@gjsify/cli 0.3.4 → 0.3.6

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,48 @@
1
+ // Parse a `gjsify dlx` package spec — distinguishes local paths from npm specs.
2
+
3
+ import { existsSync } from 'node:fs';
4
+ import { isAbsolute, resolve } from 'node:path';
5
+
6
+ export type ParsedSpec =
7
+ | { kind: 'local'; path: string }
8
+ | { kind: 'registry'; name: string; version?: string; spec: string };
9
+
10
+ const NPM_NAME_RE = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
11
+
12
+ /**
13
+ * Parse a CLI input into either a local-path or an npm-registry spec.
14
+ *
15
+ * Local: starts with `./`, `../`, `/`, or is an existing directory path.
16
+ * Registry: `<name>` | `<name>@<version>` | `@scope/<name>` | `@scope/<name>@<version>`
17
+ *
18
+ * The full spec string is preserved on the registry case so it can be passed
19
+ * verbatim to `npm install` (which already understands dist-tags, ranges,
20
+ * git URIs, tarball URLs, etc. — we don't re-parse those).
21
+ */
22
+ export function parseSpec(input: string): ParsedSpec {
23
+ if (!input) throw new Error('dlx: empty package spec');
24
+
25
+ if (input.startsWith('./') || input.startsWith('../') || isAbsolute(input)) {
26
+ return { kind: 'local', path: resolve(input) };
27
+ }
28
+
29
+ if (existsSync(input)) {
30
+ return { kind: 'local', path: resolve(input) };
31
+ }
32
+
33
+ // Registry spec: split off the version after the LAST `@` that isn't the
34
+ // leading scope separator.
35
+ let name = input;
36
+ let version: string | undefined;
37
+ const lastAt = input.lastIndexOf('@');
38
+ if (lastAt > 0) {
39
+ name = input.slice(0, lastAt);
40
+ version = input.slice(lastAt + 1);
41
+ }
42
+
43
+ if (!NPM_NAME_RE.test(name)) {
44
+ throw new Error(`dlx: invalid package name "${name}"`);
45
+ }
46
+
47
+ return { kind: 'registry', name, version, spec: input };
48
+ }
@@ -0,0 +1,96 @@
1
+ // Resolve the GJS entry point of an installed package.
2
+ //
3
+ // Per the `gjsify` field convention (see CLI reference):
4
+ //
5
+ // {
6
+ // "gjsify": {
7
+ // "main": "dist/gjs.js",
8
+ // "bin": { "name-a": "dist/a.js", "name-b": "dist/b.js" }
9
+ // }
10
+ // }
11
+ //
12
+ // Resolution order:
13
+ // 1. user-supplied bin name + `gjsify.bin[name]` → that path
14
+ // 2. single-entry `gjsify.bin` → the only path
15
+ // 3. `gjsify.main` → that path
16
+ // 4. fallback: `package.json#main` → that path (advisory warning)
17
+ // 5. otherwise: hard-fail with a fix hint
18
+
19
+ import { readFileSync } from 'node:fs';
20
+ import { existsSync } from 'node:fs';
21
+ import { join, resolve } from 'node:path';
22
+
23
+ interface ResolvedEntry {
24
+ bundlePath: string;
25
+ binName: string | null;
26
+ fromFallback: boolean;
27
+ }
28
+
29
+ interface PackageJson {
30
+ name?: string;
31
+ main?: string;
32
+ gjsify?: {
33
+ main?: string;
34
+ bin?: Record<string, string>;
35
+ prebuilds?: string;
36
+ };
37
+ }
38
+
39
+ export function resolveGjsEntry(
40
+ pkgDir: string,
41
+ binName: string | null,
42
+ ): ResolvedEntry {
43
+ const pkgJsonPath = join(pkgDir, 'package.json');
44
+ if (!existsSync(pkgJsonPath)) {
45
+ throw new Error(`dlx: no package.json found at ${pkgDir}`);
46
+ }
47
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) as PackageJson;
48
+
49
+ const gjsifyBin = pkg.gjsify?.bin;
50
+ const gjsifyMain = pkg.gjsify?.main;
51
+ const fallbackMain = pkg.main;
52
+
53
+ let entry: string | undefined;
54
+ let resolvedBin: string | null = null;
55
+ let fromFallback = false;
56
+
57
+ if (binName !== null) {
58
+ if (!gjsifyBin || !gjsifyBin[binName]) {
59
+ const known = gjsifyBin ? Object.keys(gjsifyBin).join(', ') : '(none)';
60
+ throw new Error(
61
+ `dlx: package "${pkg.name ?? pkgDir}" has no GJS bin named "${binName}" — known: ${known}`,
62
+ );
63
+ }
64
+ entry = gjsifyBin[binName];
65
+ resolvedBin = binName;
66
+ } else if (gjsifyBin && Object.keys(gjsifyBin).length === 1) {
67
+ const onlyBin = Object.keys(gjsifyBin)[0];
68
+ entry = gjsifyBin[onlyBin];
69
+ resolvedBin = onlyBin;
70
+ } else if (gjsifyMain) {
71
+ entry = gjsifyMain;
72
+ } else if (fallbackMain) {
73
+ entry = fallbackMain;
74
+ fromFallback = true;
75
+ }
76
+
77
+ if (gjsifyBin && Object.keys(gjsifyBin).length > 1 && binName === null) {
78
+ const names = Object.keys(gjsifyBin).join(', ');
79
+ throw new Error(
80
+ `dlx: package "${pkg.name ?? pkgDir}" defines multiple GJS bins — pass one of: ${names}`,
81
+ );
82
+ }
83
+
84
+ if (!entry) {
85
+ throw new Error(
86
+ `dlx: package "${pkg.name ?? pkgDir}" has no GJS entry — set \`gjsify.main\` (or \`gjsify.bin\`) in its package.json`,
87
+ );
88
+ }
89
+
90
+ const bundlePath = resolve(pkgDir, entry);
91
+ if (!existsSync(bundlePath)) {
92
+ throw new Error(`dlx: GJS entry not found: ${bundlePath}`);
93
+ }
94
+
95
+ return { bundlePath, binName: resolvedBin, fromFallback };
96
+ }