@gjsify/cli 0.3.13 → 0.3.15
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 +14 -93
- package/lib/actions/build.js +182 -154
- package/lib/commands/flatpak/build.d.ts +16 -0
- package/lib/commands/flatpak/build.js +187 -0
- package/lib/commands/flatpak/ci.d.ts +13 -0
- package/lib/commands/flatpak/ci.js +133 -0
- package/lib/commands/flatpak/deps.d.ts +12 -0
- package/lib/commands/flatpak/deps.js +96 -0
- package/lib/commands/flatpak/index.d.ts +7 -0
- package/lib/commands/flatpak/index.js +23 -0
- package/lib/commands/flatpak/init.d.ts +15 -0
- package/lib/commands/flatpak/init.js +154 -0
- package/lib/commands/flatpak/utils.d.ts +32 -0
- package/lib/commands/flatpak/utils.js +63 -0
- package/lib/commands/gsettings.d.ts +9 -0
- package/lib/commands/gsettings.js +72 -0
- 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 +66 -11
- package/lib/config.js +103 -11
- package/lib/index.js +3 -1
- package/lib/types/cli-build-options.d.ts +1 -1
- package/lib/types/config-data.d.ts +194 -4
- package/lib/utils/install-global.d.ts +54 -0
- package/lib/utils/install-global.js +153 -0
- package/lib/utils/normalize-bundler-options.d.ts +17 -0
- package/lib/utils/normalize-bundler-options.js +123 -0
- package/lib/utils/resolve-plugin-by-name.d.ts +21 -0
- package/lib/utils/resolve-plugin-by-name.js +75 -0
- package/package.json +11 -11
- package/src/actions/build.ts +406 -352
- package/src/commands/flatpak/build.ts +225 -0
- package/src/commands/flatpak/ci.ts +173 -0
- package/src/commands/flatpak/deps.ts +120 -0
- package/src/commands/flatpak/index.ts +53 -0
- package/src/commands/flatpak/init.ts +191 -0
- package/src/commands/flatpak/utils.ts +76 -0
- package/src/commands/gsettings.ts +87 -0
- package/src/commands/index.ts +2 -0
- package/src/commands/install.ts +90 -11
- package/src/config.ts +103 -11
- package/src/index.ts +4 -0
- package/src/types/cli-build-options.ts +1 -1
- package/src/types/config-data.ts +191 -4
- package/src/utils/install-global.ts +182 -0
- package/src/utils/normalize-bundler-options.ts +129 -0
- package/src/utils/resolve-plugin-by-name.ts +106 -0
|
@@ -1,9 +1,81 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RolldownOptions, OutputOptions, RolldownPluginOption } from 'rolldown';
|
|
2
2
|
import type { ConfigDataLibrary, ConfigDataTypescript } from './index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Plugin entry resolvable by package name from the project's `node_modules`.
|
|
5
|
+
* Lets users describe the plugin chain in `package.json#gjsify` without
|
|
6
|
+
* dropping to a JS-form config file. The CLI imports the named module,
|
|
7
|
+
* picks the chosen export (defaults to `default`), and calls it with
|
|
8
|
+
* `options`.
|
|
9
|
+
*
|
|
10
|
+
* Example:
|
|
11
|
+
* ```jsonc
|
|
12
|
+
* { "name": "@gjsify/vite-plugin-blueprint", "options": { "minify": true } }
|
|
13
|
+
* { "name": "@gjsify/vite-plugin-gettext", "export": "msgfmtPlugin", "options": { ... } }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export interface BundlerPluginByName {
|
|
17
|
+
name: string;
|
|
18
|
+
export?: string;
|
|
19
|
+
options?: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Subset of `RolldownOptions` accepted in `.gjsifyrc.js`. Mirrors the legacy
|
|
23
|
+
* `esbuild?: BuildOptions` field — a thin pass-through. The orchestrator
|
|
24
|
+
* applies platform defaults on top of these, so most projects only need
|
|
25
|
+
* `output.file` / `output.dir` here.
|
|
26
|
+
*
|
|
27
|
+
* `output` is constrained to a single `OutputOptions` object (Rolldown also
|
|
28
|
+
* accepts an array for multi-output builds, but the CLI surface targets the
|
|
29
|
+
* single-output use case).
|
|
30
|
+
*
|
|
31
|
+
* `plugins` is widened to also accept `BundlerPluginByName` entries — these
|
|
32
|
+
* are resolved by the CLI from the project's `node_modules` before the
|
|
33
|
+
* Rolldown call.
|
|
34
|
+
*/
|
|
35
|
+
export type BundlerOptions = Omit<RolldownOptions, 'output' | 'plugins'> & {
|
|
36
|
+
output?: OutputOptions;
|
|
37
|
+
plugins?: Array<RolldownPluginOption | BundlerPluginByName>;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Legacy `esbuild?: BuildOptions` shape — kept as a compatibility shim for
|
|
41
|
+
* one minor release. Setting it logs a deprecation warning; the supported
|
|
42
|
+
* subset of fields is mapped into `bundler` at config-load time.
|
|
43
|
+
*
|
|
44
|
+
* Drop in 0.5.0.
|
|
45
|
+
*/
|
|
46
|
+
export interface LegacyEsbuildOptions {
|
|
47
|
+
outfile?: string;
|
|
48
|
+
outdir?: string;
|
|
49
|
+
format?: 'esm' | 'cjs' | 'iife';
|
|
50
|
+
external?: string[];
|
|
51
|
+
define?: Record<string, string>;
|
|
52
|
+
inject?: string[];
|
|
53
|
+
banner?: {
|
|
54
|
+
js?: string;
|
|
55
|
+
};
|
|
56
|
+
target?: string | string[];
|
|
57
|
+
minify?: boolean;
|
|
58
|
+
sourcemap?: boolean | 'inline' | 'external' | 'both';
|
|
59
|
+
mainFields?: string[];
|
|
60
|
+
conditions?: string[];
|
|
61
|
+
platform?: 'browser' | 'node' | 'neutral';
|
|
62
|
+
loader?: Record<string, string>;
|
|
63
|
+
}
|
|
3
64
|
export interface ConfigData {
|
|
4
65
|
/** Switch on the verbose mode */
|
|
5
66
|
verbose?: boolean;
|
|
6
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Bundler-level options forwarded to Rolldown. Replaces the legacy
|
|
69
|
+
* `esbuild` field. The orchestrator applies platform-specific defaults
|
|
70
|
+
* on top — most projects only need to set `output.file` / `output.dir`.
|
|
71
|
+
*/
|
|
72
|
+
bundler?: BundlerOptions;
|
|
73
|
+
/**
|
|
74
|
+
* @deprecated Use `bundler` instead. Will be removed in 0.5.0. The shim
|
|
75
|
+
* maps the supported subset of esbuild fields into the equivalent
|
|
76
|
+
* Rolldown shape and logs a deprecation warning.
|
|
77
|
+
*/
|
|
78
|
+
esbuild?: LegacyEsbuildOptions;
|
|
7
79
|
library?: ConfigDataLibrary;
|
|
8
80
|
typescript?: ConfigDataTypescript;
|
|
9
81
|
/** An array of glob patterns to exclude matches and aliases */
|
|
@@ -19,9 +91,21 @@ export interface ConfigData {
|
|
|
19
91
|
*/
|
|
20
92
|
globals?: string;
|
|
21
93
|
/**
|
|
22
|
-
* Prepend
|
|
94
|
+
* Prepend a shebang to the output bundle and mark it executable.
|
|
95
|
+
*
|
|
96
|
+
* `true` → use the default `#!/usr/bin/env -S gjs -m` line
|
|
97
|
+
* `false` → no shebang (default)
|
|
98
|
+
* `"…"` → custom line. Supports `${env:NAME}` and `${env:NAME:-default}`
|
|
99
|
+
* placeholders against `process.env`. The leading `#!` is
|
|
100
|
+
* added automatically if omitted. Useful when an outer
|
|
101
|
+
* build tool (Meson, Flatpak) exports the GJS interpreter
|
|
102
|
+
* path as `GJS_CONSOLE` (e.g. `/usr/bin/gjs-console`).
|
|
103
|
+
*
|
|
104
|
+
* Example: `"shebang": "${env:GJS_CONSOLE:-/usr/bin/env -S gjs} -m"`
|
|
105
|
+
*
|
|
106
|
+
* See also `CliBuildOptions.shebang`.
|
|
23
107
|
*/
|
|
24
|
-
shebang?: boolean;
|
|
108
|
+
shebang?: boolean | string;
|
|
25
109
|
/**
|
|
26
110
|
* Extra module aliases layered on top of the built-in alias map.
|
|
27
111
|
* Comes from `gjsify build --alias FROM=TO`.
|
|
@@ -34,4 +118,110 @@ export interface ConfigData {
|
|
|
34
118
|
* Example: `["fetch", "XMLHttpRequest"]` excludes the HTTP polyfill stack.
|
|
35
119
|
*/
|
|
36
120
|
excludeGlobals?: string[];
|
|
121
|
+
/**
|
|
122
|
+
* Compile-time defines populated from `package.json` fields. Each entry
|
|
123
|
+
* maps a JS identifier (the define key) to a dotted package.json path.
|
|
124
|
+
* Values are JSON-stringified before merging into `bundler.transform.define`.
|
|
125
|
+
*
|
|
126
|
+
* Example:
|
|
127
|
+
* ```jsonc
|
|
128
|
+
* "defineFromPackageJson": {
|
|
129
|
+
* "__PACKAGE_VERSION__": { "field": "version" },
|
|
130
|
+
* "__PACKAGE_NAME__": { "field": "name" }
|
|
131
|
+
* }
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* Replaces the wrapper-script pattern (`spawnSync('gjsify', ['build',
|
|
135
|
+
* '--define', '__VERSION__=' + JSON.stringify(pkg.version)])`) used by
|
|
136
|
+
* `@ts-for-gir/cli` before this option existed.
|
|
137
|
+
*/
|
|
138
|
+
defineFromPackageJson?: Record<string, {
|
|
139
|
+
field: string;
|
|
140
|
+
}>;
|
|
141
|
+
/**
|
|
142
|
+
* Compile-time defines populated from `process.env` at config-load time.
|
|
143
|
+
* Each entry maps a JS identifier to an environment variable name with an
|
|
144
|
+
* optional default. Values are JSON-stringified before merging into
|
|
145
|
+
* `bundler.transform.define`. When the variable is unset and no default
|
|
146
|
+
* is provided, the identifier is replaced with the literal `undefined`
|
|
147
|
+
* so consumer code can safely guard with `typeof X === 'undefined'` or
|
|
148
|
+
* `X ?? fallback`.
|
|
149
|
+
*
|
|
150
|
+
* Example:
|
|
151
|
+
* ```jsonc
|
|
152
|
+
* "defineFromEnv": {
|
|
153
|
+
* "__APPLICATION_ID__": { "env": "APPLICATION_ID", "default": "org.example.App" },
|
|
154
|
+
* "__PREFIX__": { "env": "PREFIX" }
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* Designed for projects whose build is driven by an outer tool (Meson,
|
|
159
|
+
* Make, CI) that exports environment variables — avoids a wrapper script
|
|
160
|
+
* just to thread them through to the bundler.
|
|
161
|
+
*/
|
|
162
|
+
defineFromEnv?: Record<string, {
|
|
163
|
+
env: string;
|
|
164
|
+
default?: string;
|
|
165
|
+
}>;
|
|
166
|
+
/**
|
|
167
|
+
* Extension → loader-kind map for files Rolldown does not classify
|
|
168
|
+
* natively. Currently only `'text'` is implemented — the file's content
|
|
169
|
+
* becomes the JS string default export (`export default "<content>"`).
|
|
170
|
+
* Replaces the legacy esbuild `loader: { '.ui': 'text' }` pattern.
|
|
171
|
+
*
|
|
172
|
+
* Example:
|
|
173
|
+
* ```jsonc
|
|
174
|
+
* "loaders": { ".ui": "text", ".asm": "text" }
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* Lives at the top level (not under `bundler`) so it doesn't leak into
|
|
178
|
+
* Rolldown's options on pass-through; the CLI converts it into a
|
|
179
|
+
* `text-loader` plugin prepended to the bundler's plugin chain.
|
|
180
|
+
*/
|
|
181
|
+
loaders?: Record<string, 'text'>;
|
|
182
|
+
/**
|
|
183
|
+
* Flatpak-related configuration consumed by `gjsify flatpak <sub>`.
|
|
184
|
+
* Lives in its own top-level namespace so the bundler config doesn't
|
|
185
|
+
* accumulate concerns and `flatpak init` / `flatpak ci` can read defaults
|
|
186
|
+
* declaratively. CLI flags override these values.
|
|
187
|
+
*/
|
|
188
|
+
flatpak?: ConfigDataFlatpak;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Flatpak-toolchain config consumed by the `gjsify flatpak` subcommand
|
|
192
|
+
* group. All fields optional — sensible defaults apply when missing.
|
|
193
|
+
*/
|
|
194
|
+
export interface ConfigDataFlatpak {
|
|
195
|
+
/** Reverse-DNS app id, e.g. `eu.jumplink.Learn6502`. Defaults to `package.json#name` if it looks like a reverse-DNS id. */
|
|
196
|
+
appId?: string;
|
|
197
|
+
/**
|
|
198
|
+
* Runtime family. Default `'gnome'` — needed at runtime by GJS bundles
|
|
199
|
+
* for GLib/GObject/GIO. `'freedesktop'` is only suitable for non-gjsify
|
|
200
|
+
* CLI tools (no GJS interpreter ships in the Freedesktop runtime).
|
|
201
|
+
*/
|
|
202
|
+
runtime?: 'gnome' | 'freedesktop';
|
|
203
|
+
/** Runtime/SDK version, e.g. `'50'` for GNOME or `'24.08'` for Freedesktop. */
|
|
204
|
+
runtimeVersion?: string;
|
|
205
|
+
/** Extra SDK extensions, e.g. `['org.freedesktop.Sdk.Extension.node24']` for build-time `yarn install`. */
|
|
206
|
+
sdkExtensions?: string[];
|
|
207
|
+
/** Path components prepended to PATH inside the build sandbox. */
|
|
208
|
+
appendPath?: string[];
|
|
209
|
+
/** The binary name to run (`/app/bin/<command>`). Defaults to `appId`. */
|
|
210
|
+
command?: string;
|
|
211
|
+
/** Finish-args (capabilities). Default depends on `runtime` + `--cli-only`. */
|
|
212
|
+
finishArgs?: string[];
|
|
213
|
+
/** Extra Flatpak modules prepended before the app's own meson/simple module (e.g. `blueprint-compiler` build). */
|
|
214
|
+
extraModules?: unknown[];
|
|
215
|
+
/** Cleanup glob patterns applied to the final manifest, e.g. `['/include', '/lib/pkgconfig']`. */
|
|
216
|
+
cleanup?: string[];
|
|
217
|
+
/** Source-of-truth lockfile for `gjsify flatpak deps` — `yarn.lock` or `package-lock.json`. */
|
|
218
|
+
lockfile?: string;
|
|
219
|
+
/**
|
|
220
|
+
* GitHub-Actions container image override for `gjsify flatpak ci`.
|
|
221
|
+
* Default derived from runtime + runtimeVersion:
|
|
222
|
+
* gnome+50 → `ghcr.io/flathub-infra/flatpak-github-actions:gnome-50`
|
|
223
|
+
*/
|
|
224
|
+
ciContainer?: string;
|
|
225
|
+
/** Branches the generated workflow triggers on. Default `['main']`. */
|
|
226
|
+
ciBranches?: string[];
|
|
37
227
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface GlobalLayout {
|
|
2
|
+
/** Where extracted package trees live: `<prefix>/node_modules/<pkg>/`. */
|
|
3
|
+
prefix: string;
|
|
4
|
+
/** Where bin-name symlinks land. Typically `~/.local/bin`. */
|
|
5
|
+
binDir: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Compute the canonical global install layout for the current user. Honours
|
|
9
|
+
* `XDG_DATA_HOME` (per the XDG Base Directory Spec) plus
|
|
10
|
+
* `GJSIFY_GLOBAL_PREFIX` and `GJSIFY_GLOBAL_BIN_DIR` escape hatches for tests.
|
|
11
|
+
*/
|
|
12
|
+
export declare function defaultGlobalLayout(): GlobalLayout;
|
|
13
|
+
export interface LinkedBin {
|
|
14
|
+
/** The bin-name (key from `bin` / `gjsify.bin`). */
|
|
15
|
+
name: string;
|
|
16
|
+
/** Absolute path to the file the symlink points at. */
|
|
17
|
+
target: string;
|
|
18
|
+
/** Absolute path to the symlink (under `binDir`). */
|
|
19
|
+
link: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Install each top-level package's bin entries into `binDir` as small POSIX
|
|
23
|
+
* `sh` launchers that exec the real bin. Reads each package's installed
|
|
24
|
+
* `package.json` to discover bins; prefers `gjsify.bin` (GJS-bundled bins)
|
|
25
|
+
* over the npm `bin` field, falling back to npm `bin` when no GJS map is
|
|
26
|
+
* declared. Stale launchers are replaced (latest install wins).
|
|
27
|
+
*
|
|
28
|
+
* Why launchers instead of symlinks:
|
|
29
|
+
*
|
|
30
|
+
* When a GJS bundle is invoked via a symlink in $PATH, the kernel follows
|
|
31
|
+
* the symlink to find the executable but passes the original (symlink)
|
|
32
|
+
* path to it. The bundle's shebang then runs as `gjs -m <symlink-path>`,
|
|
33
|
+
* which makes `import.meta.url` resolve to the symlink directory — so any
|
|
34
|
+
* path computation relative to `import.meta.url` (e.g. ts-for-gir's
|
|
35
|
+
* `findTemplatesRoot()`, version-discovery `readFileSync`s, gjsify's own
|
|
36
|
+
* `import.meta.url` rewrites) looks for assets in the wrong place.
|
|
37
|
+
*
|
|
38
|
+
* A `sh` launcher invokes the real path explicitly, so the bundle sees its
|
|
39
|
+
* real install location in `import.meta.url` and every relative read works.
|
|
40
|
+
* This costs ~50 bytes per bin and one extra `exec` per launch — both
|
|
41
|
+
* negligible compared to `gjs -m` cold-start time.
|
|
42
|
+
*
|
|
43
|
+
* Plain Node bins are unaffected by either approach (Node defaults to
|
|
44
|
+
* resolving symlinks in ESM module URLs); launchers are uniform for
|
|
45
|
+
* simplicity and so we don't need to discriminate by runtime here.
|
|
46
|
+
*/
|
|
47
|
+
export declare function linkGlobalBins(packageNames: string[], layout: GlobalLayout): LinkedBin[];
|
|
48
|
+
/** Returns `true` if `binDir` is on the user's PATH. */
|
|
49
|
+
export declare function binDirOnPath(binDir: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Extracts the package name from a spec like `name@1.2`, `@scope/name`,
|
|
52
|
+
* or `@scope/name@latest`. Returns the unchanged string for plain names.
|
|
53
|
+
*/
|
|
54
|
+
export declare function specToPackageName(spec: string): string;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Global-install helpers for `gjsify install -g <pkg>`.
|
|
2
|
+
//
|
|
3
|
+
// Layout (XDG-compliant, ~ = $HOME):
|
|
4
|
+
//
|
|
5
|
+
// ~/.local/share/gjsify/global/
|
|
6
|
+
// node_modules/<pkg>/ ← extracted package contents
|
|
7
|
+
// package.json
|
|
8
|
+
// bin/<pkg> ← original npm bin
|
|
9
|
+
// bin/<pkg>-gjs ← GJS bundle (declared via `gjsify.bin`)
|
|
10
|
+
// <pkg-data-files> ← e.g. `dist-templates/` for ts-for-gir
|
|
11
|
+
// ~/.local/bin/<bin-name> ← symlink to the matching bin under node_modules
|
|
12
|
+
//
|
|
13
|
+
// Unlike the project install path (`gjsify install <pkg>` without -g), this
|
|
14
|
+
// mode installs the requested top-level packages plus their runtime deps into
|
|
15
|
+
// a user-owned XDG location, never mutates a project package.json, and links
|
|
16
|
+
// bins into the user's PATH so the package is invocable as `<bin-name>` from
|
|
17
|
+
// anywhere — the closest GJS equivalent of `npm i -g`.
|
|
18
|
+
//
|
|
19
|
+
// `gjsify.bin` (when declared) wins over the standard npm `bin` field on this
|
|
20
|
+
// path because the user explicitly asked for the GJS-runnable artifact: e.g.
|
|
21
|
+
// `ts-for-gir` (npm bin = bin/ts-for-gir Node script) becomes a symlink to
|
|
22
|
+
// `bin/ts-for-gir-gjs` (the self-contained GJS bundle) instead.
|
|
23
|
+
import * as fs from 'node:fs';
|
|
24
|
+
import * as os from 'node:os';
|
|
25
|
+
import * as path from 'node:path';
|
|
26
|
+
/**
|
|
27
|
+
* Compute the canonical global install layout for the current user. Honours
|
|
28
|
+
* `XDG_DATA_HOME` (per the XDG Base Directory Spec) plus
|
|
29
|
+
* `GJSIFY_GLOBAL_PREFIX` and `GJSIFY_GLOBAL_BIN_DIR` escape hatches for tests.
|
|
30
|
+
*/
|
|
31
|
+
export function defaultGlobalLayout() {
|
|
32
|
+
const prefixOverride = process.env.GJSIFY_GLOBAL_PREFIX;
|
|
33
|
+
const binOverride = process.env.GJSIFY_GLOBAL_BIN_DIR;
|
|
34
|
+
const home = os.homedir();
|
|
35
|
+
const xdgData = process.env.XDG_DATA_HOME ?? path.join(home, '.local', 'share');
|
|
36
|
+
return {
|
|
37
|
+
prefix: prefixOverride ?? path.join(xdgData, 'gjsify', 'global'),
|
|
38
|
+
binDir: binOverride ?? path.join(home, '.local', 'bin'),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Install each top-level package's bin entries into `binDir` as small POSIX
|
|
43
|
+
* `sh` launchers that exec the real bin. Reads each package's installed
|
|
44
|
+
* `package.json` to discover bins; prefers `gjsify.bin` (GJS-bundled bins)
|
|
45
|
+
* over the npm `bin` field, falling back to npm `bin` when no GJS map is
|
|
46
|
+
* declared. Stale launchers are replaced (latest install wins).
|
|
47
|
+
*
|
|
48
|
+
* Why launchers instead of symlinks:
|
|
49
|
+
*
|
|
50
|
+
* When a GJS bundle is invoked via a symlink in $PATH, the kernel follows
|
|
51
|
+
* the symlink to find the executable but passes the original (symlink)
|
|
52
|
+
* path to it. The bundle's shebang then runs as `gjs -m <symlink-path>`,
|
|
53
|
+
* which makes `import.meta.url` resolve to the symlink directory — so any
|
|
54
|
+
* path computation relative to `import.meta.url` (e.g. ts-for-gir's
|
|
55
|
+
* `findTemplatesRoot()`, version-discovery `readFileSync`s, gjsify's own
|
|
56
|
+
* `import.meta.url` rewrites) looks for assets in the wrong place.
|
|
57
|
+
*
|
|
58
|
+
* A `sh` launcher invokes the real path explicitly, so the bundle sees its
|
|
59
|
+
* real install location in `import.meta.url` and every relative read works.
|
|
60
|
+
* This costs ~50 bytes per bin and one extra `exec` per launch — both
|
|
61
|
+
* negligible compared to `gjs -m` cold-start time.
|
|
62
|
+
*
|
|
63
|
+
* Plain Node bins are unaffected by either approach (Node defaults to
|
|
64
|
+
* resolving symlinks in ESM module URLs); launchers are uniform for
|
|
65
|
+
* simplicity and so we don't need to discriminate by runtime here.
|
|
66
|
+
*/
|
|
67
|
+
export function linkGlobalBins(packageNames, layout) {
|
|
68
|
+
fs.mkdirSync(layout.binDir, { recursive: true });
|
|
69
|
+
const created = [];
|
|
70
|
+
for (const pkgName of packageNames) {
|
|
71
|
+
const pkgDir = path.join(layout.prefix, 'node_modules', pkgName);
|
|
72
|
+
const pkgJsonPath = path.join(pkgDir, 'package.json');
|
|
73
|
+
if (!fs.existsSync(pkgJsonPath))
|
|
74
|
+
continue;
|
|
75
|
+
const pkgJson = readJson(pkgJsonPath);
|
|
76
|
+
const binMap = pickBinMap(pkgName, pkgJson);
|
|
77
|
+
if (!binMap || binMap.size === 0)
|
|
78
|
+
continue;
|
|
79
|
+
for (const [binName, binTarget] of binMap) {
|
|
80
|
+
const targetAbs = path.join(pkgDir, binTarget);
|
|
81
|
+
if (!fs.existsSync(targetAbs))
|
|
82
|
+
continue;
|
|
83
|
+
try {
|
|
84
|
+
fs.chmodSync(targetAbs, 0o755);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
/* best effort */
|
|
88
|
+
}
|
|
89
|
+
const linkPath = path.join(layout.binDir, binName);
|
|
90
|
+
fs.rmSync(linkPath, { force: true });
|
|
91
|
+
// Inline `${target}` directly — this file is rewritten on every
|
|
92
|
+
// install, paths are user-owned, and POSIX `sh` quoting via
|
|
93
|
+
// single-quotes plus `'\''` for embedded quotes is well-defined.
|
|
94
|
+
const launcher = `#!/bin/sh\nexec ${shQuote(targetAbs)} "$@"\n`;
|
|
95
|
+
fs.writeFileSync(linkPath, launcher);
|
|
96
|
+
fs.chmodSync(linkPath, 0o755);
|
|
97
|
+
created.push({ name: binName, target: targetAbs, link: linkPath });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return created;
|
|
101
|
+
}
|
|
102
|
+
function shQuote(s) {
|
|
103
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
104
|
+
}
|
|
105
|
+
function pickBinMap(pkgName, pkgJson) {
|
|
106
|
+
const gjsifyEntry = pkgJson.gjsify;
|
|
107
|
+
if (gjsifyEntry?.bin !== undefined) {
|
|
108
|
+
return normalizeBin(pkgName, gjsifyEntry.bin);
|
|
109
|
+
}
|
|
110
|
+
const npmBin = pkgJson.bin;
|
|
111
|
+
if (npmBin !== undefined) {
|
|
112
|
+
return normalizeBin(pkgName, npmBin);
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
function normalizeBin(pkgName, bin) {
|
|
117
|
+
const out = new Map();
|
|
118
|
+
if (typeof bin === 'string') {
|
|
119
|
+
const baseName = pkgName.startsWith('@')
|
|
120
|
+
? pkgName.slice(pkgName.indexOf('/') + 1)
|
|
121
|
+
: pkgName;
|
|
122
|
+
out.set(baseName, bin);
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
for (const [k, v] of Object.entries(bin))
|
|
126
|
+
out.set(k, v);
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
function readJson(file) {
|
|
130
|
+
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
131
|
+
}
|
|
132
|
+
/** Returns `true` if `binDir` is on the user's PATH. */
|
|
133
|
+
export function binDirOnPath(binDir) {
|
|
134
|
+
const PATH = process.env.PATH ?? '';
|
|
135
|
+
const sep = process.platform === 'win32' ? ';' : ':';
|
|
136
|
+
const want = path.resolve(binDir);
|
|
137
|
+
return PATH.split(sep).some((entry) => entry && path.resolve(entry) === want);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Extracts the package name from a spec like `name@1.2`, `@scope/name`,
|
|
141
|
+
* or `@scope/name@latest`. Returns the unchanged string for plain names.
|
|
142
|
+
*/
|
|
143
|
+
export function specToPackageName(spec) {
|
|
144
|
+
if (spec.startsWith('@')) {
|
|
145
|
+
const slash = spec.indexOf('/');
|
|
146
|
+
if (slash < 0)
|
|
147
|
+
return spec;
|
|
148
|
+
const at = spec.indexOf('@', slash);
|
|
149
|
+
return at < 0 ? spec : spec.slice(0, at);
|
|
150
|
+
}
|
|
151
|
+
const at = spec.indexOf('@');
|
|
152
|
+
return at < 0 ? spec : spec.slice(0, at);
|
|
153
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ConfigData, BundlerOptions } from '../types/config-data.js';
|
|
2
|
+
export declare function normalizeBundlerOptions(configData: ConfigData): BundlerOptions;
|
|
3
|
+
/**
|
|
4
|
+
* Shallow merge with deep-merge of `output`, `transform`, and `resolve`. The
|
|
5
|
+
* second argument wins on conflicts, matching `merge(target, ...sources)`
|
|
6
|
+
* semantics from `@gjsify/rolldown-plugin-gjsify/utils/merge`.
|
|
7
|
+
*
|
|
8
|
+
* `base` is typically the Rolldown-generic shape returned by the orchestrator;
|
|
9
|
+
* `overrides` is the user's `BundlerOptions` from `.gjsifyrc.js` plus CLI
|
|
10
|
+
* flag merges. Single-output assumption matches `BundlerOptions['output']`.
|
|
11
|
+
*
|
|
12
|
+
* The orchestrator-side `input` is authoritative — it's the post-glob-expansion
|
|
13
|
+
* value. Overriding it with the user's raw glob string would re-introduce
|
|
14
|
+
* unresolved glob patterns into the final Rolldown call. Same for `external`,
|
|
15
|
+
* which the orchestrator concatenates with platform defaults already.
|
|
16
|
+
*/
|
|
17
|
+
export declare function mergeBundlerOptions(base: BundlerOptions, overrides: BundlerOptions): BundlerOptions;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// One-release deprecation shim: maps the legacy `esbuild?: BuildOptions`
|
|
2
|
+
// field on `.gjsifyrc.js` / `package.json#gjsify` into the equivalent
|
|
3
|
+
// `bundler?: RolldownOptions` shape. Logs a single warning per build.
|
|
4
|
+
//
|
|
5
|
+
// Drop in 0.5.0.
|
|
6
|
+
let warnedOnce = false;
|
|
7
|
+
export function normalizeBundlerOptions(configData) {
|
|
8
|
+
const fromBundler = (configData.bundler ?? {});
|
|
9
|
+
if (!configData.esbuild)
|
|
10
|
+
return fromBundler;
|
|
11
|
+
if (!warnedOnce) {
|
|
12
|
+
warnedOnce = true;
|
|
13
|
+
// eslint-disable-next-line no-console
|
|
14
|
+
console.warn("[gjsify] DEPRECATION: the 'esbuild' config key is deprecated and will be removed in 0.5.0. " +
|
|
15
|
+
"Rename it to 'bundler' (typed as RolldownOptions). See the migration notes in the gjsify CHANGELOG.");
|
|
16
|
+
}
|
|
17
|
+
const fromEsbuild = legacyEsbuildToRolldown(configData.esbuild);
|
|
18
|
+
// Plain user-config merge — we deliberately do NOT call
|
|
19
|
+
// `mergeBundlerOptions` here, because that function strips `input` and
|
|
20
|
+
// `external` from its overrides arg (it assumes the *orchestrator* is
|
|
21
|
+
// the override source and the user the base). Here both inputs are
|
|
22
|
+
// user-provided config and `input` must survive the merge.
|
|
23
|
+
const out = { ...fromEsbuild, ...fromBundler };
|
|
24
|
+
if (fromEsbuild.output || fromBundler.output) {
|
|
25
|
+
out.output = { ...(fromEsbuild.output ?? {}), ...(fromBundler.output ?? {}) };
|
|
26
|
+
}
|
|
27
|
+
if (fromEsbuild.transform || fromBundler.transform) {
|
|
28
|
+
out.transform = { ...(fromEsbuild.transform ?? {}), ...(fromBundler.transform ?? {}) };
|
|
29
|
+
if (fromEsbuild.transform?.define || fromBundler.transform?.define) {
|
|
30
|
+
out.transform.define = {
|
|
31
|
+
...(fromEsbuild.transform?.define ?? {}),
|
|
32
|
+
...(fromBundler.transform?.define ?? {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (fromEsbuild.resolve || fromBundler.resolve) {
|
|
37
|
+
out.resolve = { ...(fromEsbuild.resolve ?? {}), ...(fromBundler.resolve ?? {}) };
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/** Map the supported subset of esbuild BuildOptions into RolldownOptions. */
|
|
42
|
+
function legacyEsbuildToRolldown(esb) {
|
|
43
|
+
const out = {};
|
|
44
|
+
const output = {};
|
|
45
|
+
const transform = {};
|
|
46
|
+
const resolve = {};
|
|
47
|
+
if (esb.outfile !== undefined)
|
|
48
|
+
output.file = esb.outfile;
|
|
49
|
+
if (esb.outdir !== undefined)
|
|
50
|
+
output.dir = esb.outdir;
|
|
51
|
+
if (esb.format !== undefined)
|
|
52
|
+
output.format = esb.format;
|
|
53
|
+
if (esb.minify !== undefined)
|
|
54
|
+
output.minify = esb.minify;
|
|
55
|
+
if (esb.sourcemap !== undefined) {
|
|
56
|
+
// esbuild has 'external' / 'both' which Rolldown doesn't — coerce to boolean.
|
|
57
|
+
output.sourcemap =
|
|
58
|
+
esb.sourcemap === 'inline'
|
|
59
|
+
? 'inline'
|
|
60
|
+
: Boolean(esb.sourcemap);
|
|
61
|
+
}
|
|
62
|
+
if (esb.banner?.js !== undefined)
|
|
63
|
+
output.banner = esb.banner.js;
|
|
64
|
+
if (esb.target !== undefined) {
|
|
65
|
+
transform.target = Array.isArray(esb.target) ? esb.target.join(',') : esb.target;
|
|
66
|
+
}
|
|
67
|
+
if (esb.define !== undefined)
|
|
68
|
+
transform.define = esb.define;
|
|
69
|
+
if (esb.mainFields !== undefined)
|
|
70
|
+
resolve.mainFields = esb.mainFields;
|
|
71
|
+
if (esb.conditions !== undefined)
|
|
72
|
+
resolve.conditionNames = esb.conditions;
|
|
73
|
+
if (esb.external !== undefined)
|
|
74
|
+
out.external = esb.external;
|
|
75
|
+
if (esb.platform !== undefined)
|
|
76
|
+
out.platform = esb.platform;
|
|
77
|
+
if (Object.keys(output).length > 0)
|
|
78
|
+
out.output = output;
|
|
79
|
+
if (Object.keys(transform).length > 0)
|
|
80
|
+
out.transform = transform;
|
|
81
|
+
if (Object.keys(resolve).length > 0)
|
|
82
|
+
out.resolve = resolve;
|
|
83
|
+
// Discarded silently:
|
|
84
|
+
// esb.inject — esbuild's array-of-side-effect-files; surfaced at the
|
|
85
|
+
// CLI layer instead, via input expansion.
|
|
86
|
+
// esb.loader — Rolldown infers module types from extensions natively.
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Shallow merge with deep-merge of `output`, `transform`, and `resolve`. The
|
|
91
|
+
* second argument wins on conflicts, matching `merge(target, ...sources)`
|
|
92
|
+
* semantics from `@gjsify/rolldown-plugin-gjsify/utils/merge`.
|
|
93
|
+
*
|
|
94
|
+
* `base` is typically the Rolldown-generic shape returned by the orchestrator;
|
|
95
|
+
* `overrides` is the user's `BundlerOptions` from `.gjsifyrc.js` plus CLI
|
|
96
|
+
* flag merges. Single-output assumption matches `BundlerOptions['output']`.
|
|
97
|
+
*
|
|
98
|
+
* The orchestrator-side `input` is authoritative — it's the post-glob-expansion
|
|
99
|
+
* value. Overriding it with the user's raw glob string would re-introduce
|
|
100
|
+
* unresolved glob patterns into the final Rolldown call. Same for `external`,
|
|
101
|
+
* which the orchestrator concatenates with platform defaults already.
|
|
102
|
+
*/
|
|
103
|
+
export function mergeBundlerOptions(base, overrides) {
|
|
104
|
+
// Strip fields the orchestrator owns authoritatively — the user has
|
|
105
|
+
// already had their say via the orchestrator's `userExternal` / input
|
|
106
|
+
// expansion; merging the raw values back on top would clobber the
|
|
107
|
+
// post-processing.
|
|
108
|
+
const { input: _ignoredInput, external: _ignoredExternal, ...overridesRest } = overrides;
|
|
109
|
+
const out = { ...base, ...overridesRest };
|
|
110
|
+
if (base.output || overrides.output) {
|
|
111
|
+
out.output = { ...(base.output ?? {}), ...(overrides.output ?? {}) };
|
|
112
|
+
}
|
|
113
|
+
if (base.transform || overrides.transform) {
|
|
114
|
+
out.transform = { ...(base.transform ?? {}), ...(overrides.transform ?? {}) };
|
|
115
|
+
if (base.transform?.define || overrides.transform?.define) {
|
|
116
|
+
out.transform.define = { ...(base.transform?.define ?? {}), ...(overrides.transform?.define ?? {}) };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (base.resolve || overrides.resolve) {
|
|
120
|
+
out.resolve = { ...(base.resolve ?? {}), ...(overrides.resolve ?? {}) };
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RolldownPluginOption } from 'rolldown';
|
|
2
|
+
/** User-supplied entry: a package name + optional named export and options. */
|
|
3
|
+
export interface PluginByName {
|
|
4
|
+
name: string;
|
|
5
|
+
/** Named export to invoke. Defaults to the module's default export. */
|
|
6
|
+
export?: string;
|
|
7
|
+
/** Options forwarded to the plugin factory. */
|
|
8
|
+
options?: unknown;
|
|
9
|
+
}
|
|
10
|
+
/** Type-guard: a `PluginByName` shape rather than a Rolldown plugin object. */
|
|
11
|
+
export declare function isPluginByName(value: unknown): value is PluginByName;
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a list of mixed user plugins. Entries that are already plugin
|
|
14
|
+
* objects pass through unchanged; entries shaped like `PluginByName` get
|
|
15
|
+
* dynamically imported, instantiated with their `options`, and returned in
|
|
16
|
+
* the same position. Resolution is anchored at `projectDir`.
|
|
17
|
+
*
|
|
18
|
+
* Throws when a name fails to resolve, when the chosen export is not a
|
|
19
|
+
* function, or when the factory returns nothing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveUserPlugins(plugins: ReadonlyArray<RolldownPluginOption | PluginByName>, projectDir: string): Promise<RolldownPluginOption[]>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Resolve `bundler.plugins` entries that are specified by package name in
|
|
2
|
+
// the user's gjsify config, e.g.:
|
|
3
|
+
//
|
|
4
|
+
// "bundler": {
|
|
5
|
+
// "plugins": [
|
|
6
|
+
// { "name": "@gjsify/vite-plugin-blueprint", "options": { "minify": true } },
|
|
7
|
+
// { "name": "@gjsify/vite-plugin-gettext", "export": "msgfmtPlugin", "options": { ... } }
|
|
8
|
+
// ]
|
|
9
|
+
// }
|
|
10
|
+
//
|
|
11
|
+
// Lets `package.json#gjsify` describe the full plugin chain without dropping
|
|
12
|
+
// to a JS-form config file (`gjsify.config.mjs`). Resolution is anchored at
|
|
13
|
+
// the project root (where the config lives) so the project's own
|
|
14
|
+
// `node_modules` wins over the CLI's own dependencies.
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { pathToFileURL } from 'node:url';
|
|
18
|
+
/** Type-guard: a `PluginByName` shape rather than a Rolldown plugin object. */
|
|
19
|
+
export function isPluginByName(value) {
|
|
20
|
+
return (typeof value === 'object' &&
|
|
21
|
+
value !== null &&
|
|
22
|
+
typeof value.name === 'string' &&
|
|
23
|
+
// RolldownPluginOption can be `false | null | undefined | Plugin | Promise<Plugin>`.
|
|
24
|
+
// A real plugin always has a function-shape behavior; `name` alone is shared
|
|
25
|
+
// with our shape, so we additionally require absence of plugin-shape fields.
|
|
26
|
+
!('apply' in value) &&
|
|
27
|
+
!('resolveId' in value) &&
|
|
28
|
+
!('load' in value) &&
|
|
29
|
+
!('transform' in value) &&
|
|
30
|
+
!('renderChunk' in value) &&
|
|
31
|
+
!('generateBundle' in value));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a list of mixed user plugins. Entries that are already plugin
|
|
35
|
+
* objects pass through unchanged; entries shaped like `PluginByName` get
|
|
36
|
+
* dynamically imported, instantiated with their `options`, and returned in
|
|
37
|
+
* the same position. Resolution is anchored at `projectDir`.
|
|
38
|
+
*
|
|
39
|
+
* Throws when a name fails to resolve, when the chosen export is not a
|
|
40
|
+
* function, or when the factory returns nothing.
|
|
41
|
+
*/
|
|
42
|
+
export async function resolveUserPlugins(plugins, projectDir) {
|
|
43
|
+
const requireFromProject = createRequire(join(projectDir, 'package.json'));
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const entry of plugins) {
|
|
46
|
+
if (!isPluginByName(entry)) {
|
|
47
|
+
out.push(entry);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
let resolvedPath;
|
|
51
|
+
try {
|
|
52
|
+
resolvedPath = requireFromProject.resolve(entry.name);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
throw new Error(`gjsify config: failed to resolve plugin "${entry.name}" from ${projectDir}. ` +
|
|
56
|
+
`Add it to your project's dependencies, or pass a Plugin object directly. ` +
|
|
57
|
+
`(${err.message})`);
|
|
58
|
+
}
|
|
59
|
+
const mod = await import(pathToFileURL(resolvedPath).href);
|
|
60
|
+
const exportName = entry.export ?? 'default';
|
|
61
|
+
const factory = mod[exportName];
|
|
62
|
+
if (typeof factory !== 'function') {
|
|
63
|
+
const available = Object.keys(mod).filter((k) => typeof mod[k] === 'function');
|
|
64
|
+
throw new Error(`gjsify config: plugin "${entry.name}" has no function export "${exportName}". ` +
|
|
65
|
+
`Available function exports: ${available.length ? available.join(', ') : '(none)'}.`);
|
|
66
|
+
}
|
|
67
|
+
const plugin = await factory(entry.options);
|
|
68
|
+
if (plugin === undefined || plugin === null) {
|
|
69
|
+
throw new Error(`gjsify config: plugin "${entry.name}" factory returned ${plugin}. ` +
|
|
70
|
+
`Check the plugin's signature — it should return a Rolldown/Vite plugin object.`);
|
|
71
|
+
}
|
|
72
|
+
out.push(plugin);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|