@gjsify/cli 0.3.1 → 0.3.3
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 +4 -4
- package/lib/actions/build.js +138 -41
- package/lib/commands/build.js +5 -0
- package/lib/config.js +8 -0
- package/lib/types/cli-build-options.d.ts +7 -0
- package/lib/types/config-data.d.ts +7 -0
- package/package.json +11 -10
- package/src/actions/build.ts +378 -238
- package/src/commands/build.ts +5 -0
- package/src/config.ts +7 -0
- package/src/types/cli-build-options.ts +7 -0
- package/src/types/config-data.ts +7 -0
package/lib/actions/build.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ConfigData } from
|
|
2
|
-
import type { App } from
|
|
3
|
-
import { BuildOptions, BuildResult } from
|
|
1
|
+
import type { ConfigData } from "../types/index.js";
|
|
2
|
+
import type { App } from "@gjsify/esbuild-plugin-gjsify";
|
|
3
|
+
import { BuildOptions, BuildResult, Plugin } from "esbuild";
|
|
4
4
|
export declare class BuildAction {
|
|
5
5
|
readonly configData: ConfigData;
|
|
6
6
|
constructor(configData?: ConfigData);
|
|
@@ -34,7 +34,7 @@ export declare class BuildAction {
|
|
|
34
34
|
/** Application mode */
|
|
35
35
|
buildApp(app?: App): Promise<BuildResult<{
|
|
36
36
|
format: "esm" | "cjs";
|
|
37
|
-
plugins:
|
|
37
|
+
plugins: Plugin[];
|
|
38
38
|
bundle?: boolean;
|
|
39
39
|
splitting?: boolean;
|
|
40
40
|
preserveSymlinks?: boolean;
|
package/lib/actions/build.js
CHANGED
|
@@ -1,9 +1,61 @@
|
|
|
1
|
-
import { build } from
|
|
2
|
-
import { gjsifyPlugin } from
|
|
3
|
-
import { resolveGlobalsList, writeRegisterInjectFile, detectAutoGlobals } from
|
|
4
|
-
import { dirname, extname } from
|
|
5
|
-
import { chmod, readFile, writeFile } from
|
|
6
|
-
|
|
1
|
+
import { build } from "esbuild";
|
|
2
|
+
import { gjsifyPlugin } from "@gjsify/esbuild-plugin-gjsify";
|
|
3
|
+
import { resolveGlobalsList, writeRegisterInjectFile, detectAutoGlobals, } from "@gjsify/esbuild-plugin-gjsify/globals";
|
|
4
|
+
import { dirname, extname, join } from "node:path";
|
|
5
|
+
import { chmod, readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
const GJS_SHEBANG = "#!/usr/bin/env -S gjs -m\n";
|
|
8
|
+
/** Walk up from dir until .pnp.cjs is found; return its directory or null. */
|
|
9
|
+
function findPnpRoot(dir) {
|
|
10
|
+
let current = dir;
|
|
11
|
+
while (true) {
|
|
12
|
+
if (existsSync(join(current, ".pnp.cjs")))
|
|
13
|
+
return current;
|
|
14
|
+
const parent = dirname(current);
|
|
15
|
+
if (parent === current)
|
|
16
|
+
return null;
|
|
17
|
+
current = parent;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* If the current project uses Yarn PnP, return the official
|
|
22
|
+
* @yarnpkg/esbuild-plugin-pnp plugin so esbuild can resolve
|
|
23
|
+
* modules from zip archives without manual extraction.
|
|
24
|
+
*
|
|
25
|
+
* Custom onResolve: fall through on UNDECLARED_DEPENDENCY errors so the
|
|
26
|
+
* gjsify alias plugin can handle bare specifiers (e.g. `abort-controller`)
|
|
27
|
+
* that PnP can't resolve from the inject file's issuer context but that
|
|
28
|
+
* gjsify maps to `@gjsify/*` packages the project DOES have available.
|
|
29
|
+
*/
|
|
30
|
+
async function getPnpPlugin() {
|
|
31
|
+
if (!findPnpRoot(process.cwd()))
|
|
32
|
+
return null;
|
|
33
|
+
try {
|
|
34
|
+
const { pnpPlugin } = await import("@yarnpkg/esbuild-plugin-pnp");
|
|
35
|
+
return pnpPlugin({
|
|
36
|
+
onResolve: async (_args, { resolvedPath, error, watchFiles }) => {
|
|
37
|
+
if (resolvedPath !== null) {
|
|
38
|
+
return { namespace: "pnp", path: resolvedPath, watchFiles };
|
|
39
|
+
}
|
|
40
|
+
// UNDECLARED_DEPENDENCY: package exists transitively but isn't
|
|
41
|
+
// in the issuer's direct deps. Fall through so the gjsify alias
|
|
42
|
+
// plugin can resolve it (e.g. bare → @gjsify/* mappings).
|
|
43
|
+
if (error?.pnpCode ===
|
|
44
|
+
"UNDECLARED_DEPENDENCY") {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
external: true,
|
|
49
|
+
errors: error ? [{ text: error.message }] : [],
|
|
50
|
+
watchFiles,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
7
59
|
export class BuildAction {
|
|
8
60
|
configData;
|
|
9
61
|
constructor(configData = {}) {
|
|
@@ -11,7 +63,7 @@ export class BuildAction {
|
|
|
11
63
|
}
|
|
12
64
|
getEsBuildDefaults() {
|
|
13
65
|
const defaults = {
|
|
14
|
-
allowOverwrite: true
|
|
66
|
+
allowOverwrite: true,
|
|
15
67
|
};
|
|
16
68
|
return defaults;
|
|
17
69
|
}
|
|
@@ -23,45 +75,71 @@ export class BuildAction {
|
|
|
23
75
|
typescript ||= {};
|
|
24
76
|
const moduleOutdir = library?.module ? dirname(library.module) : undefined;
|
|
25
77
|
const mainOutdir = library?.main ? dirname(library.main) : undefined;
|
|
26
|
-
const moduleOutExt = library.module ? extname(library.module) :
|
|
27
|
-
const mainOutExt = library.main ? extname(library.main) :
|
|
28
|
-
const multipleBuilds = moduleOutdir && mainOutdir &&
|
|
78
|
+
const moduleOutExt = library.module ? extname(library.module) : ".js";
|
|
79
|
+
const mainOutExt = library.main ? extname(library.main) : ".js";
|
|
80
|
+
const multipleBuilds = moduleOutdir && mainOutdir && moduleOutdir !== mainOutdir;
|
|
81
|
+
const pnpPlugin = await getPnpPlugin();
|
|
82
|
+
const pnpPlugins = pnpPlugin ? [pnpPlugin] : [];
|
|
29
83
|
const results = [];
|
|
30
84
|
if (multipleBuilds) {
|
|
31
|
-
const moduleFormat = moduleOutdir.includes(
|
|
85
|
+
const moduleFormat = moduleOutdir.includes("/cjs") || moduleOutExt === ".cjs"
|
|
86
|
+
? "cjs"
|
|
87
|
+
: "esm";
|
|
32
88
|
results.push(await build({
|
|
33
89
|
...this.getEsBuildDefaults(),
|
|
34
90
|
...esbuild,
|
|
35
91
|
format: moduleFormat,
|
|
36
92
|
outdir: moduleOutdir,
|
|
37
93
|
plugins: [
|
|
38
|
-
|
|
39
|
-
|
|
94
|
+
...pnpPlugins,
|
|
95
|
+
gjsifyPlugin({
|
|
96
|
+
debug: verbose,
|
|
97
|
+
library: moduleFormat,
|
|
98
|
+
exclude,
|
|
99
|
+
reflection: typescript?.reflection,
|
|
100
|
+
jsExtension: moduleOutExt,
|
|
101
|
+
}),
|
|
102
|
+
],
|
|
40
103
|
}));
|
|
41
|
-
const mainFormat = mainOutdir.includes(
|
|
104
|
+
const mainFormat = mainOutdir.includes("/cjs") || mainOutExt === ".cjs" ? "cjs" : "esm";
|
|
42
105
|
results.push(await build({
|
|
43
106
|
...this.getEsBuildDefaults(),
|
|
44
107
|
...esbuild,
|
|
45
108
|
format: moduleFormat,
|
|
46
109
|
outdir: mainOutdir,
|
|
47
110
|
plugins: [
|
|
48
|
-
|
|
49
|
-
|
|
111
|
+
...pnpPlugins,
|
|
112
|
+
gjsifyPlugin({
|
|
113
|
+
debug: verbose,
|
|
114
|
+
library: mainFormat,
|
|
115
|
+
exclude,
|
|
116
|
+
reflection: typescript?.reflection,
|
|
117
|
+
jsExtension: mainOutdir,
|
|
118
|
+
}),
|
|
119
|
+
],
|
|
50
120
|
}));
|
|
51
121
|
}
|
|
52
122
|
else {
|
|
53
123
|
const outfilePath = esbuild?.outfile || library?.module || library?.main;
|
|
54
|
-
const outExt = outfilePath ? extname(outfilePath) :
|
|
124
|
+
const outExt = outfilePath ? extname(outfilePath) : ".js";
|
|
55
125
|
const outdir = esbuild?.outdir || (outfilePath ? dirname(outfilePath) : undefined);
|
|
56
|
-
const format = esbuild?.format ??
|
|
126
|
+
const format = esbuild?.format ??
|
|
127
|
+
(outdir?.includes("/cjs") || outExt === ".cjs" ? "cjs" : "esm");
|
|
57
128
|
results.push(await build({
|
|
58
129
|
...this.getEsBuildDefaults(),
|
|
59
130
|
...esbuild,
|
|
60
131
|
format,
|
|
61
132
|
outdir,
|
|
62
133
|
plugins: [
|
|
63
|
-
|
|
64
|
-
|
|
134
|
+
...pnpPlugins,
|
|
135
|
+
gjsifyPlugin({
|
|
136
|
+
debug: verbose,
|
|
137
|
+
library: format,
|
|
138
|
+
exclude,
|
|
139
|
+
reflection: typescript?.reflection,
|
|
140
|
+
jsExtension: outExt,
|
|
141
|
+
}),
|
|
142
|
+
],
|
|
65
143
|
}));
|
|
66
144
|
}
|
|
67
145
|
return results;
|
|
@@ -77,12 +155,15 @@ export class BuildAction {
|
|
|
77
155
|
*/
|
|
78
156
|
parseGlobalsValue(value) {
|
|
79
157
|
if (value === undefined)
|
|
80
|
-
return { autoMode: true, extras:
|
|
81
|
-
if (value ===
|
|
82
|
-
return { autoMode: false, extras:
|
|
83
|
-
const tokens = value
|
|
84
|
-
|
|
85
|
-
|
|
158
|
+
return { autoMode: true, extras: "" };
|
|
159
|
+
if (value === "none" || value === "")
|
|
160
|
+
return { autoMode: false, extras: "" };
|
|
161
|
+
const tokens = value
|
|
162
|
+
.split(",")
|
|
163
|
+
.map((t) => t.trim())
|
|
164
|
+
.filter(Boolean);
|
|
165
|
+
const hasAuto = tokens.includes("auto");
|
|
166
|
+
const extras = tokens.filter((t) => t !== "auto").join(",");
|
|
86
167
|
return { autoMode: hasAuto, extras };
|
|
87
168
|
}
|
|
88
169
|
/**
|
|
@@ -94,7 +175,7 @@ export class BuildAction {
|
|
|
94
175
|
* The auto path is handled in `buildApp` via the two-pass build.
|
|
95
176
|
*/
|
|
96
177
|
async resolveGlobalsInject(app, globals, verbose) {
|
|
97
|
-
if (app !==
|
|
178
|
+
if (app !== "gjs")
|
|
98
179
|
return undefined;
|
|
99
180
|
if (!globals)
|
|
100
181
|
return undefined;
|
|
@@ -114,11 +195,11 @@ export class BuildAction {
|
|
|
114
195
|
async applyShebang(outfile, verbose) {
|
|
115
196
|
if (!outfile) {
|
|
116
197
|
if (verbose)
|
|
117
|
-
console.warn(
|
|
198
|
+
console.warn("[gjsify] --shebang skipped: no single outfile (use --outfile for GJS executables)");
|
|
118
199
|
return;
|
|
119
200
|
}
|
|
120
|
-
const content = await readFile(outfile,
|
|
121
|
-
if (content.startsWith(
|
|
201
|
+
const content = await readFile(outfile, "utf-8");
|
|
202
|
+
if (content.startsWith("#!")) {
|
|
122
203
|
if (verbose)
|
|
123
204
|
console.debug(`[gjsify] --shebang skipped: ${outfile} already starts with a shebang`);
|
|
124
205
|
}
|
|
@@ -130,12 +211,19 @@ export class BuildAction {
|
|
|
130
211
|
console.debug(`[gjsify] --shebang: wrote shebang + chmod 0o755 to ${outfile}`);
|
|
131
212
|
}
|
|
132
213
|
/** Application mode */
|
|
133
|
-
async buildApp(app =
|
|
134
|
-
const { verbose, esbuild, typescript, exclude, library: pgk, aliases } = this.configData;
|
|
135
|
-
const format = esbuild?.format ??
|
|
214
|
+
async buildApp(app = "gjs") {
|
|
215
|
+
const { verbose, esbuild, typescript, exclude, library: pgk, aliases, excludeGlobals, } = this.configData;
|
|
216
|
+
const format = esbuild?.format ??
|
|
217
|
+
(esbuild?.outfile?.endsWith(".cjs") ? "cjs" : "esm");
|
|
136
218
|
// Set default outfile if no outdir is set
|
|
137
|
-
if (esbuild &&
|
|
138
|
-
esbuild
|
|
219
|
+
if (esbuild &&
|
|
220
|
+
!esbuild?.outfile &&
|
|
221
|
+
!esbuild?.outdir &&
|
|
222
|
+
(pgk?.main || pgk?.module)) {
|
|
223
|
+
esbuild.outfile =
|
|
224
|
+
esbuild?.format === "cjs"
|
|
225
|
+
? pgk.main || pgk.module
|
|
226
|
+
: pgk.module || pgk.main;
|
|
139
227
|
}
|
|
140
228
|
const { consoleShim, globals } = this.configData;
|
|
141
229
|
const pluginOpts = {
|
|
@@ -148,24 +236,32 @@ export class BuildAction {
|
|
|
148
236
|
...(aliases ? { aliases } : {}),
|
|
149
237
|
};
|
|
150
238
|
const { autoMode, extras } = this.parseGlobalsValue(globals);
|
|
239
|
+
const pnpPlugin = await getPnpPlugin();
|
|
240
|
+
const pnpPlugins = pnpPlugin ? [pnpPlugin] : [];
|
|
151
241
|
// --- Auto mode (with optional extras): iterative multi-pass build ---
|
|
152
242
|
// The extras token is used for cases where the detector cannot
|
|
153
243
|
// statically see a global (e.g. Excalibur indirects globalThis via
|
|
154
244
|
// BrowserComponent.nativeComponent). Common pattern: --globals auto,dom
|
|
155
|
-
if (app ===
|
|
156
|
-
const { injectPath } = await detectAutoGlobals({
|
|
245
|
+
if (app === "gjs" && autoMode) {
|
|
246
|
+
const { injectPath } = await detectAutoGlobals({
|
|
247
|
+
...this.getEsBuildDefaults(),
|
|
248
|
+
...esbuild,
|
|
249
|
+
format,
|
|
250
|
+
plugins: pnpPlugins,
|
|
251
|
+
}, pluginOpts, verbose, { extraGlobalsList: extras, excludeGlobals });
|
|
157
252
|
const result = await build({
|
|
158
253
|
...this.getEsBuildDefaults(),
|
|
159
254
|
...esbuild,
|
|
160
255
|
format,
|
|
161
256
|
plugins: [
|
|
257
|
+
...pnpPlugins,
|
|
162
258
|
gjsifyPlugin({
|
|
163
259
|
...pluginOpts,
|
|
164
260
|
autoGlobalsInject: injectPath,
|
|
165
261
|
}),
|
|
166
262
|
],
|
|
167
263
|
});
|
|
168
|
-
if (app ===
|
|
264
|
+
if (app === "gjs" && this.configData.shebang) {
|
|
169
265
|
await this.applyShebang(esbuild?.outfile, verbose);
|
|
170
266
|
}
|
|
171
267
|
return [result];
|
|
@@ -179,18 +275,19 @@ export class BuildAction {
|
|
|
179
275
|
...esbuild,
|
|
180
276
|
format,
|
|
181
277
|
plugins: [
|
|
278
|
+
...pnpPlugins,
|
|
182
279
|
gjsifyPlugin({
|
|
183
280
|
...pluginOpts,
|
|
184
281
|
autoGlobalsInject,
|
|
185
282
|
}),
|
|
186
|
-
]
|
|
283
|
+
],
|
|
187
284
|
});
|
|
188
|
-
if (app ===
|
|
285
|
+
if (app === "gjs" && this.configData.shebang) {
|
|
189
286
|
await this.applyShebang(esbuild?.outfile, verbose);
|
|
190
287
|
}
|
|
191
288
|
return [result];
|
|
192
289
|
}
|
|
193
|
-
async start(buildType = { app:
|
|
290
|
+
async start(buildType = { app: "gjs" }) {
|
|
194
291
|
const results = [];
|
|
195
292
|
if (buildType.library) {
|
|
196
293
|
results.push(...(await this.buildLibrary()));
|
package/lib/commands/build.js
CHANGED
|
@@ -117,6 +117,11 @@ export const buildCommand = {
|
|
|
117
117
|
type: 'string',
|
|
118
118
|
default: [],
|
|
119
119
|
coerce: (arg) => arg.flatMap((v) => v.split(',').map((s) => s.trim()).filter(Boolean)),
|
|
120
|
+
})
|
|
121
|
+
.option('exclude-globals', {
|
|
122
|
+
description: "Comma-separated global identifiers to remove from auto-detection results. Use for false positives from dead browser-compat code whose polyfills require unavailable native libraries (e.g. --exclude-globals fetch,XMLHttpRequest).",
|
|
123
|
+
type: 'string',
|
|
124
|
+
normalize: true,
|
|
120
125
|
});
|
|
121
126
|
},
|
|
122
127
|
handler: async (args) => {
|
package/lib/config.js
CHANGED
|
@@ -73,6 +73,14 @@ export class Config {
|
|
|
73
73
|
configData.globals = cliArgs.globals;
|
|
74
74
|
if (cliArgs.shebang !== undefined)
|
|
75
75
|
configData.shebang = cliArgs.shebang;
|
|
76
|
+
if (cliArgs.excludeGlobals) {
|
|
77
|
+
const raw = Array.isArray(cliArgs.excludeGlobals)
|
|
78
|
+
? cliArgs.excludeGlobals.join(',')
|
|
79
|
+
: String(cliArgs.excludeGlobals);
|
|
80
|
+
const ids = raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
81
|
+
if (ids.length)
|
|
82
|
+
configData.excludeGlobals = [...(configData.excludeGlobals ?? []), ...ids];
|
|
83
|
+
}
|
|
76
84
|
merge(configData.library ??= {}, pkg, configData.library);
|
|
77
85
|
merge(configData.typescript ??= {}, tsConfig, configData.typescript);
|
|
78
86
|
// Parse `KEY=VALUE` style flags into Record<string, string>.
|
|
@@ -89,4 +89,11 @@ export interface CliBuildOptions {
|
|
|
89
89
|
* scenario never executes). Layered on top of the built-in alias map.
|
|
90
90
|
*/
|
|
91
91
|
alias?: string[];
|
|
92
|
+
/**
|
|
93
|
+
* Comma-separated global identifiers to remove from the auto-detected set.
|
|
94
|
+
* Useful for false positives from dead browser-compat code in npm deps
|
|
95
|
+
* whose polyfills require unavailable native libraries.
|
|
96
|
+
* Example: `--exclude-globals fetch,XMLHttpRequest`
|
|
97
|
+
*/
|
|
98
|
+
excludeGlobals?: string[];
|
|
92
99
|
}
|
|
@@ -27,4 +27,11 @@ export interface ConfigData {
|
|
|
27
27
|
* Comes from `gjsify build --alias FROM=TO`.
|
|
28
28
|
*/
|
|
29
29
|
aliases?: Record<string, string>;
|
|
30
|
+
/**
|
|
31
|
+
* Global identifiers to remove from the auto-detected set before writing
|
|
32
|
+
* the inject stub. Useful for false positives from dead browser-compat
|
|
33
|
+
* code in npm dependencies whose polyfills require unavailable native libs.
|
|
34
|
+
* Example: `["fetch", "XMLHttpRequest"]` excludes the HTTP polyfill stack.
|
|
35
|
+
*/
|
|
36
|
+
excludeGlobals?: string[];
|
|
30
37
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -23,15 +23,16 @@
|
|
|
23
23
|
"cli"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@gjsify/create-app": "^0.3.
|
|
27
|
-
"@gjsify/esbuild-plugin-gjsify": "^0.3.
|
|
28
|
-
"@gjsify/example-dom-canvas2d-fireworks": "^0.3.
|
|
29
|
-
"@gjsify/example-dom-excalibur-jelly-jumper": "^0.3.
|
|
30
|
-
"@gjsify/example-dom-three-geometry-teapot": "^0.3.
|
|
31
|
-
"@gjsify/example-dom-three-postprocessing-pixel": "^0.3.
|
|
32
|
-
"@gjsify/example-node-express-webserver": "^0.3.
|
|
33
|
-
"@gjsify/node-polyfills": "^0.3.
|
|
34
|
-
"@gjsify/web-polyfills": "^0.3.
|
|
26
|
+
"@gjsify/create-app": "^0.3.3",
|
|
27
|
+
"@gjsify/esbuild-plugin-gjsify": "^0.3.3",
|
|
28
|
+
"@gjsify/example-dom-canvas2d-fireworks": "^0.3.3",
|
|
29
|
+
"@gjsify/example-dom-excalibur-jelly-jumper": "^0.3.3",
|
|
30
|
+
"@gjsify/example-dom-three-geometry-teapot": "^0.3.3",
|
|
31
|
+
"@gjsify/example-dom-three-postprocessing-pixel": "^0.3.3",
|
|
32
|
+
"@gjsify/example-node-express-webserver": "^0.3.3",
|
|
33
|
+
"@gjsify/node-polyfills": "^0.3.3",
|
|
34
|
+
"@gjsify/web-polyfills": "^0.3.3",
|
|
35
|
+
"@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.15",
|
|
35
36
|
"cosmiconfig": "^9.0.1",
|
|
36
37
|
"esbuild": "^0.28.0",
|
|
37
38
|
"get-tsconfig": "^4.14.0",
|
package/src/actions/build.ts
CHANGED
|
@@ -1,240 +1,380 @@
|
|
|
1
|
-
import type { ConfigData } from
|
|
2
|
-
import type { App } from
|
|
3
|
-
import { build, BuildOptions, BuildResult } from
|
|
4
|
-
import { gjsifyPlugin } from
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import type { ConfigData } from "../types/index.js";
|
|
2
|
+
import type { App } from "@gjsify/esbuild-plugin-gjsify";
|
|
3
|
+
import { build, BuildOptions, BuildResult, Plugin } from "esbuild";
|
|
4
|
+
import { gjsifyPlugin } from "@gjsify/esbuild-plugin-gjsify";
|
|
5
|
+
import {
|
|
6
|
+
resolveGlobalsList,
|
|
7
|
+
writeRegisterInjectFile,
|
|
8
|
+
detectAutoGlobals,
|
|
9
|
+
} from "@gjsify/esbuild-plugin-gjsify/globals";
|
|
10
|
+
import { dirname, extname, join } from "node:path";
|
|
11
|
+
import { chmod, readFile, writeFile } from "node:fs/promises";
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
|
|
14
|
+
const GJS_SHEBANG = "#!/usr/bin/env -S gjs -m\n";
|
|
15
|
+
|
|
16
|
+
/** Walk up from dir until .pnp.cjs is found; return its directory or null. */
|
|
17
|
+
function findPnpRoot(dir: string): string | null {
|
|
18
|
+
let current = dir;
|
|
19
|
+
while (true) {
|
|
20
|
+
if (existsSync(join(current, ".pnp.cjs"))) return current;
|
|
21
|
+
const parent = dirname(current);
|
|
22
|
+
if (parent === current) return null;
|
|
23
|
+
current = parent;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* If the current project uses Yarn PnP, return the official
|
|
29
|
+
* @yarnpkg/esbuild-plugin-pnp plugin so esbuild can resolve
|
|
30
|
+
* modules from zip archives without manual extraction.
|
|
31
|
+
*
|
|
32
|
+
* Custom onResolve: fall through on UNDECLARED_DEPENDENCY errors so the
|
|
33
|
+
* gjsify alias plugin can handle bare specifiers (e.g. `abort-controller`)
|
|
34
|
+
* that PnP can't resolve from the inject file's issuer context but that
|
|
35
|
+
* gjsify maps to `@gjsify/*` packages the project DOES have available.
|
|
36
|
+
*/
|
|
37
|
+
async function getPnpPlugin(): Promise<Plugin | null> {
|
|
38
|
+
if (!findPnpRoot(process.cwd())) return null;
|
|
39
|
+
try {
|
|
40
|
+
const { pnpPlugin } = await import("@yarnpkg/esbuild-plugin-pnp");
|
|
41
|
+
return pnpPlugin({
|
|
42
|
+
onResolve: async (_args, { resolvedPath, error, watchFiles }) => {
|
|
43
|
+
if (resolvedPath !== null) {
|
|
44
|
+
return { namespace: "pnp", path: resolvedPath, watchFiles };
|
|
45
|
+
}
|
|
46
|
+
// UNDECLARED_DEPENDENCY: package exists transitively but isn't
|
|
47
|
+
// in the issuer's direct deps. Fall through so the gjsify alias
|
|
48
|
+
// plugin can resolve it (e.g. bare → @gjsify/* mappings).
|
|
49
|
+
if (
|
|
50
|
+
(error as { pnpCode?: string } | null)?.pnpCode ===
|
|
51
|
+
"UNDECLARED_DEPENDENCY"
|
|
52
|
+
) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
external: true,
|
|
57
|
+
errors: error ? [{ text: error.message }] : [],
|
|
58
|
+
watchFiles,
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
10
66
|
|
|
11
67
|
export class BuildAction {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
68
|
+
constructor(readonly configData: ConfigData = {}) {}
|
|
69
|
+
|
|
70
|
+
getEsBuildDefaults() {
|
|
71
|
+
const defaults: BuildOptions = {
|
|
72
|
+
allowOverwrite: true,
|
|
73
|
+
};
|
|
74
|
+
return defaults;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Library mode */
|
|
78
|
+
async buildLibrary() {
|
|
79
|
+
let { verbose, library, esbuild, typescript, exclude } = this.configData;
|
|
80
|
+
library ||= {};
|
|
81
|
+
esbuild ||= {};
|
|
82
|
+
typescript ||= {};
|
|
83
|
+
|
|
84
|
+
const moduleOutdir = library?.module ? dirname(library.module) : undefined;
|
|
85
|
+
const mainOutdir = library?.main ? dirname(library.main) : undefined;
|
|
86
|
+
|
|
87
|
+
const moduleOutExt = library.module ? extname(library.module) : ".js";
|
|
88
|
+
const mainOutExt = library.main ? extname(library.main) : ".js";
|
|
89
|
+
|
|
90
|
+
const multipleBuilds =
|
|
91
|
+
moduleOutdir && mainOutdir && moduleOutdir !== mainOutdir;
|
|
92
|
+
|
|
93
|
+
const pnpPlugin = await getPnpPlugin();
|
|
94
|
+
const pnpPlugins: Plugin[] = pnpPlugin ? [pnpPlugin] : [];
|
|
95
|
+
|
|
96
|
+
const results: BuildResult[] = [];
|
|
97
|
+
|
|
98
|
+
if (multipleBuilds) {
|
|
99
|
+
const moduleFormat =
|
|
100
|
+
moduleOutdir.includes("/cjs") || moduleOutExt === ".cjs"
|
|
101
|
+
? "cjs"
|
|
102
|
+
: "esm";
|
|
103
|
+
results.push(
|
|
104
|
+
await build({
|
|
105
|
+
...this.getEsBuildDefaults(),
|
|
106
|
+
...esbuild,
|
|
107
|
+
format: moduleFormat,
|
|
108
|
+
outdir: moduleOutdir,
|
|
109
|
+
plugins: [
|
|
110
|
+
...pnpPlugins,
|
|
111
|
+
gjsifyPlugin({
|
|
112
|
+
debug: verbose,
|
|
113
|
+
library: moduleFormat,
|
|
114
|
+
exclude,
|
|
115
|
+
reflection: typescript?.reflection,
|
|
116
|
+
jsExtension: moduleOutExt,
|
|
117
|
+
}),
|
|
118
|
+
],
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const mainFormat =
|
|
123
|
+
mainOutdir.includes("/cjs") || mainOutExt === ".cjs" ? "cjs" : "esm";
|
|
124
|
+
results.push(
|
|
125
|
+
await build({
|
|
126
|
+
...this.getEsBuildDefaults(),
|
|
127
|
+
...esbuild,
|
|
128
|
+
format: moduleFormat,
|
|
129
|
+
outdir: mainOutdir,
|
|
130
|
+
plugins: [
|
|
131
|
+
...pnpPlugins,
|
|
132
|
+
gjsifyPlugin({
|
|
133
|
+
debug: verbose,
|
|
134
|
+
library: mainFormat,
|
|
135
|
+
exclude,
|
|
136
|
+
reflection: typescript?.reflection,
|
|
137
|
+
jsExtension: mainOutdir,
|
|
138
|
+
}),
|
|
139
|
+
],
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
} else {
|
|
143
|
+
const outfilePath = esbuild?.outfile || library?.module || library?.main;
|
|
144
|
+
const outExt = outfilePath ? extname(outfilePath) : ".js";
|
|
145
|
+
const outdir =
|
|
146
|
+
esbuild?.outdir || (outfilePath ? dirname(outfilePath) : undefined);
|
|
147
|
+
const format: "esm" | "cjs" =
|
|
148
|
+
(esbuild?.format as "esm" | "cjs") ??
|
|
149
|
+
(outdir?.includes("/cjs") || outExt === ".cjs" ? "cjs" : "esm");
|
|
150
|
+
results.push(
|
|
151
|
+
await build({
|
|
152
|
+
...this.getEsBuildDefaults(),
|
|
153
|
+
...esbuild,
|
|
154
|
+
format,
|
|
155
|
+
outdir,
|
|
156
|
+
plugins: [
|
|
157
|
+
...pnpPlugins,
|
|
158
|
+
gjsifyPlugin({
|
|
159
|
+
debug: verbose,
|
|
160
|
+
library: format,
|
|
161
|
+
exclude,
|
|
162
|
+
reflection: typescript?.reflection,
|
|
163
|
+
jsExtension: outExt,
|
|
164
|
+
}),
|
|
165
|
+
],
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
return results;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Parse the `--globals` value into { autoMode, extras }.
|
|
174
|
+
* - `auto` → { autoMode: true, extras: '' }
|
|
175
|
+
* - `auto,dom` → { autoMode: true, extras: 'dom' }
|
|
176
|
+
* - `auto,dom,fetch` → { autoMode: true, extras: 'dom,fetch' }
|
|
177
|
+
* - `dom,fetch` → { autoMode: false, extras: 'dom,fetch' }
|
|
178
|
+
* - `none` / `` → { autoMode: false, extras: '' }
|
|
179
|
+
* - `undefined` → { autoMode: true, extras: '' } (default)
|
|
180
|
+
*/
|
|
181
|
+
private parseGlobalsValue(value: string | undefined): {
|
|
182
|
+
autoMode: boolean;
|
|
183
|
+
extras: string;
|
|
184
|
+
} {
|
|
185
|
+
if (value === undefined) return { autoMode: true, extras: "" };
|
|
186
|
+
if (value === "none" || value === "")
|
|
187
|
+
return { autoMode: false, extras: "" };
|
|
188
|
+
|
|
189
|
+
const tokens = value
|
|
190
|
+
.split(",")
|
|
191
|
+
.map((t) => t.trim())
|
|
192
|
+
.filter(Boolean);
|
|
193
|
+
const hasAuto = tokens.includes("auto");
|
|
194
|
+
const extras = tokens.filter((t) => t !== "auto").join(",");
|
|
195
|
+
|
|
196
|
+
return { autoMode: hasAuto, extras };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Resolve the `--globals` CLI list into a pre-computed inject stub path
|
|
201
|
+
* that the esbuild plugin will append to its `inject` list. Only runs
|
|
202
|
+
* for `--app gjs` — Node and browser builds rely on native globals.
|
|
203
|
+
*
|
|
204
|
+
* Used only for the explicit-only path (no `auto` token in the value).
|
|
205
|
+
* The auto path is handled in `buildApp` via the two-pass build.
|
|
206
|
+
*/
|
|
207
|
+
private async resolveGlobalsInject(
|
|
208
|
+
app: App,
|
|
209
|
+
globals: string,
|
|
210
|
+
verbose: boolean | undefined,
|
|
211
|
+
): Promise<string | undefined> {
|
|
212
|
+
if (app !== "gjs") return undefined;
|
|
213
|
+
if (!globals) return undefined;
|
|
214
|
+
|
|
215
|
+
const registerPaths = resolveGlobalsList(globals);
|
|
216
|
+
if (registerPaths.size === 0) return undefined;
|
|
217
|
+
|
|
218
|
+
const injectPath = await writeRegisterInjectFile(
|
|
219
|
+
registerPaths,
|
|
220
|
+
process.cwd(),
|
|
221
|
+
);
|
|
222
|
+
if (verbose && injectPath) {
|
|
223
|
+
console.debug(
|
|
224
|
+
`[gjsify] globals: injected ${registerPaths.size} register module(s) from --globals ${globals}`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return injectPath ?? undefined;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Post-processing: prepend GJS shebang and mark the output file executable.
|
|
232
|
+
* Only runs for GJS app builds with a resolvable single outfile.
|
|
233
|
+
*/
|
|
234
|
+
private async applyShebang(
|
|
235
|
+
outfile: string | undefined,
|
|
236
|
+
verbose: boolean | undefined,
|
|
237
|
+
): Promise<void> {
|
|
238
|
+
if (!outfile) {
|
|
239
|
+
if (verbose)
|
|
240
|
+
console.warn(
|
|
241
|
+
"[gjsify] --shebang skipped: no single outfile (use --outfile for GJS executables)",
|
|
242
|
+
);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const content = await readFile(outfile, "utf-8");
|
|
247
|
+
if (content.startsWith("#!")) {
|
|
248
|
+
if (verbose)
|
|
249
|
+
console.debug(
|
|
250
|
+
`[gjsify] --shebang skipped: ${outfile} already starts with a shebang`,
|
|
251
|
+
);
|
|
252
|
+
} else {
|
|
253
|
+
await writeFile(outfile, GJS_SHEBANG + content);
|
|
254
|
+
}
|
|
255
|
+
await chmod(outfile, 0o755);
|
|
256
|
+
if (verbose)
|
|
257
|
+
console.debug(
|
|
258
|
+
`[gjsify] --shebang: wrote shebang + chmod 0o755 to ${outfile}`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Application mode */
|
|
263
|
+
async buildApp(app: App = "gjs") {
|
|
264
|
+
const {
|
|
265
|
+
verbose,
|
|
266
|
+
esbuild,
|
|
267
|
+
typescript,
|
|
268
|
+
exclude,
|
|
269
|
+
library: pgk,
|
|
270
|
+
aliases,
|
|
271
|
+
excludeGlobals,
|
|
272
|
+
} = this.configData;
|
|
273
|
+
|
|
274
|
+
const format: "esm" | "cjs" =
|
|
275
|
+
(esbuild?.format as "esm" | "cjs") ??
|
|
276
|
+
(esbuild?.outfile?.endsWith(".cjs") ? "cjs" : "esm");
|
|
277
|
+
|
|
278
|
+
// Set default outfile if no outdir is set
|
|
279
|
+
if (
|
|
280
|
+
esbuild &&
|
|
281
|
+
!esbuild?.outfile &&
|
|
282
|
+
!esbuild?.outdir &&
|
|
283
|
+
(pgk?.main || pgk?.module)
|
|
284
|
+
) {
|
|
285
|
+
esbuild.outfile =
|
|
286
|
+
esbuild?.format === "cjs"
|
|
287
|
+
? pgk.main || pgk.module
|
|
288
|
+
: pgk.module || pgk.main;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const { consoleShim, globals } = this.configData;
|
|
292
|
+
|
|
293
|
+
const pluginOpts = {
|
|
294
|
+
debug: verbose,
|
|
295
|
+
app,
|
|
296
|
+
format,
|
|
297
|
+
exclude,
|
|
298
|
+
reflection: typescript?.reflection,
|
|
299
|
+
consoleShim,
|
|
300
|
+
...(aliases ? { aliases } : {}),
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const { autoMode, extras } = this.parseGlobalsValue(globals);
|
|
304
|
+
|
|
305
|
+
const pnpPlugin = await getPnpPlugin();
|
|
306
|
+
const pnpPlugins: Plugin[] = pnpPlugin ? [pnpPlugin] : [];
|
|
307
|
+
|
|
308
|
+
// --- Auto mode (with optional extras): iterative multi-pass build ---
|
|
309
|
+
// The extras token is used for cases where the detector cannot
|
|
310
|
+
// statically see a global (e.g. Excalibur indirects globalThis via
|
|
311
|
+
// BrowserComponent.nativeComponent). Common pattern: --globals auto,dom
|
|
312
|
+
if (app === "gjs" && autoMode) {
|
|
313
|
+
const { injectPath } = await detectAutoGlobals(
|
|
314
|
+
{
|
|
315
|
+
...this.getEsBuildDefaults(),
|
|
316
|
+
...esbuild,
|
|
317
|
+
format,
|
|
318
|
+
plugins: pnpPlugins,
|
|
319
|
+
},
|
|
320
|
+
pluginOpts,
|
|
321
|
+
verbose,
|
|
322
|
+
{ extraGlobalsList: extras, excludeGlobals },
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const result = await build({
|
|
326
|
+
...this.getEsBuildDefaults(),
|
|
327
|
+
...esbuild,
|
|
328
|
+
format,
|
|
329
|
+
plugins: [
|
|
330
|
+
...pnpPlugins,
|
|
331
|
+
gjsifyPlugin({
|
|
332
|
+
...pluginOpts,
|
|
333
|
+
autoGlobalsInject: injectPath,
|
|
334
|
+
}),
|
|
335
|
+
],
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
if (app === "gjs" && this.configData.shebang) {
|
|
339
|
+
await this.applyShebang(esbuild?.outfile, verbose);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return [result];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// --- Explicit list (no `auto` token) or none mode ---
|
|
346
|
+
const autoGlobalsInject = extras
|
|
347
|
+
? await this.resolveGlobalsInject(app, extras, verbose)
|
|
348
|
+
: undefined;
|
|
349
|
+
|
|
350
|
+
const result = await build({
|
|
351
|
+
...this.getEsBuildDefaults(),
|
|
352
|
+
...esbuild,
|
|
353
|
+
format,
|
|
354
|
+
plugins: [
|
|
355
|
+
...pnpPlugins,
|
|
356
|
+
gjsifyPlugin({
|
|
357
|
+
...pluginOpts,
|
|
358
|
+
autoGlobalsInject,
|
|
359
|
+
}),
|
|
360
|
+
],
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
if (app === "gjs" && this.configData.shebang) {
|
|
364
|
+
await this.applyShebang(esbuild?.outfile, verbose);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return [result];
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async start(buildType: { library?: boolean; app?: App } = { app: "gjs" }) {
|
|
371
|
+
const results: BuildResult[] = [];
|
|
372
|
+
if (buildType.library) {
|
|
373
|
+
results.push(...(await this.buildLibrary()));
|
|
374
|
+
} else {
|
|
375
|
+
results.push(...(await this.buildApp(buildType.app)));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return results;
|
|
379
|
+
}
|
|
380
|
+
}
|
package/src/commands/build.ts
CHANGED
|
@@ -120,6 +120,11 @@ export const buildCommand: Command<any, CliBuildOptions> = {
|
|
|
120
120
|
default: [] as string[],
|
|
121
121
|
coerce: (arg: string[]) => arg.flatMap((v) => v.split(',').map((s) => s.trim()).filter(Boolean)),
|
|
122
122
|
})
|
|
123
|
+
.option('exclude-globals', {
|
|
124
|
+
description: "Comma-separated global identifiers to remove from auto-detection results. Use for false positives from dead browser-compat code whose polyfills require unavailable native libraries (e.g. --exclude-globals fetch,XMLHttpRequest).",
|
|
125
|
+
type: 'string',
|
|
126
|
+
normalize: true,
|
|
127
|
+
})
|
|
123
128
|
},
|
|
124
129
|
handler: async (args) => {
|
|
125
130
|
const config = new Config();
|
package/src/config.ts
CHANGED
|
@@ -84,6 +84,13 @@ export class Config {
|
|
|
84
84
|
if (cliArgs.consoleShim !== undefined) configData.consoleShim = cliArgs.consoleShim;
|
|
85
85
|
if (cliArgs.globals !== undefined) configData.globals = cliArgs.globals;
|
|
86
86
|
if (cliArgs.shebang !== undefined) configData.shebang = cliArgs.shebang;
|
|
87
|
+
if (cliArgs.excludeGlobals) {
|
|
88
|
+
const raw = Array.isArray(cliArgs.excludeGlobals)
|
|
89
|
+
? cliArgs.excludeGlobals.join(',')
|
|
90
|
+
: String(cliArgs.excludeGlobals);
|
|
91
|
+
const ids = raw.split(',').map((s: string) => s.trim()).filter(Boolean);
|
|
92
|
+
if (ids.length) configData.excludeGlobals = [...(configData.excludeGlobals ?? []), ...ids];
|
|
93
|
+
}
|
|
87
94
|
|
|
88
95
|
merge(configData.library ??= {}, pkg, configData.library);
|
|
89
96
|
merge(configData.typescript ??= {}, tsConfig, configData.typescript);
|
|
@@ -90,4 +90,11 @@ export interface CliBuildOptions {
|
|
|
90
90
|
* scenario never executes). Layered on top of the built-in alias map.
|
|
91
91
|
*/
|
|
92
92
|
alias?: string[];
|
|
93
|
+
/**
|
|
94
|
+
* Comma-separated global identifiers to remove from the auto-detected set.
|
|
95
|
+
* Useful for false positives from dead browser-compat code in npm deps
|
|
96
|
+
* whose polyfills require unavailable native libraries.
|
|
97
|
+
* Example: `--exclude-globals fetch,XMLHttpRequest`
|
|
98
|
+
*/
|
|
99
|
+
excludeGlobals?: string[];
|
|
93
100
|
}
|
package/src/types/config-data.ts
CHANGED
|
@@ -28,4 +28,11 @@ export interface ConfigData {
|
|
|
28
28
|
* Comes from `gjsify build --alias FROM=TO`.
|
|
29
29
|
*/
|
|
30
30
|
aliases?: Record<string, string>;
|
|
31
|
+
/**
|
|
32
|
+
* Global identifiers to remove from the auto-detected set before writing
|
|
33
|
+
* the inject stub. Useful for false positives from dead browser-compat
|
|
34
|
+
* code in npm dependencies whose polyfills require unavailable native libs.
|
|
35
|
+
* Example: `["fetch", "XMLHttpRequest"]` excludes the HTTP polyfill stack.
|
|
36
|
+
*/
|
|
37
|
+
excludeGlobals?: string[];
|
|
31
38
|
}
|