@gjsify/cli 0.3.0 → 0.3.2

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.
@@ -1,6 +1,6 @@
1
1
  import type { ConfigData } from '../types/index.js';
2
2
  import type { App } from '@gjsify/esbuild-plugin-gjsify';
3
- import { BuildOptions, BuildResult } from 'esbuild';
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: import("esbuild").Plugin[];
37
+ plugins: Plugin[];
38
38
  bundle?: boolean;
39
39
  splitting?: boolean;
40
40
  preserveSymlinks?: boolean;
@@ -1,9 +1,38 @@
1
1
  import { build } from 'esbuild';
2
2
  import { gjsifyPlugin } from '@gjsify/esbuild-plugin-gjsify';
3
3
  import { resolveGlobalsList, writeRegisterInjectFile, detectAutoGlobals } from '@gjsify/esbuild-plugin-gjsify/globals';
4
- import { dirname, extname } from 'node:path';
4
+ import { dirname, extname, join } from 'node:path';
5
5
  import { chmod, readFile, writeFile } from 'node:fs/promises';
6
- const GJS_SHEBANG = '#!/usr/bin/env -S gjs -m\n';
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
+ async function getPnpPlugin() {
26
+ if (!findPnpRoot(process.cwd()))
27
+ return null;
28
+ try {
29
+ const { pnpPlugin } = await import("@yarnpkg/esbuild-plugin-pnp");
30
+ return pnpPlugin();
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
7
36
  export class BuildAction {
8
37
  configData;
9
38
  constructor(configData = {}) {
@@ -23,45 +52,50 @@ export class BuildAction {
23
52
  typescript ||= {};
24
53
  const moduleOutdir = library?.module ? dirname(library.module) : undefined;
25
54
  const mainOutdir = library?.main ? dirname(library.main) : undefined;
26
- const moduleOutExt = library.module ? extname(library.module) : '.js';
27
- const mainOutExt = library.main ? extname(library.main) : '.js';
28
- const multipleBuilds = moduleOutdir && mainOutdir && (moduleOutdir !== mainOutdir);
55
+ const moduleOutExt = library.module ? extname(library.module) : ".js";
56
+ const mainOutExt = library.main ? extname(library.main) : ".js";
57
+ const multipleBuilds = moduleOutdir && mainOutdir && moduleOutdir !== mainOutdir;
58
+ const pnpPlugin = await getPnpPlugin();
59
+ const pnpPlugins = pnpPlugin ? [pnpPlugin] : [];
29
60
  const results = [];
30
61
  if (multipleBuilds) {
31
- const moduleFormat = moduleOutdir.includes('/cjs') || moduleOutExt === '.cjs' ? 'cjs' : 'esm';
62
+ const moduleFormat = moduleOutdir.includes("/cjs") || moduleOutExt === ".cjs" ? "cjs" : "esm";
32
63
  results.push(await build({
33
64
  ...this.getEsBuildDefaults(),
34
65
  ...esbuild,
35
66
  format: moduleFormat,
36
67
  outdir: moduleOutdir,
37
68
  plugins: [
69
+ ...pnpPlugins,
38
70
  gjsifyPlugin({ debug: verbose, library: moduleFormat, exclude, reflection: typescript?.reflection, jsExtension: moduleOutExt }),
39
- ]
71
+ ],
40
72
  }));
41
- const mainFormat = mainOutdir.includes('/cjs') || mainOutExt === '.cjs' ? 'cjs' : 'esm';
73
+ const mainFormat = mainOutdir.includes("/cjs") || mainOutExt === ".cjs" ? "cjs" : "esm";
42
74
  results.push(await build({
43
75
  ...this.getEsBuildDefaults(),
44
76
  ...esbuild,
45
77
  format: moduleFormat,
46
78
  outdir: mainOutdir,
47
79
  plugins: [
48
- gjsifyPlugin({ debug: verbose, library: mainFormat, exclude, reflection: typescript?.reflection, jsExtension: mainOutdir })
49
- ]
80
+ ...pnpPlugins,
81
+ gjsifyPlugin({ debug: verbose, library: mainFormat, exclude, reflection: typescript?.reflection, jsExtension: mainOutdir }),
82
+ ],
50
83
  }));
51
84
  }
52
85
  else {
53
86
  const outfilePath = esbuild?.outfile || library?.module || library?.main;
54
- const outExt = outfilePath ? extname(outfilePath) : '.js';
87
+ const outExt = outfilePath ? extname(outfilePath) : ".js";
55
88
  const outdir = esbuild?.outdir || (outfilePath ? dirname(outfilePath) : undefined);
56
- const format = esbuild?.format ?? (outdir?.includes('/cjs') || outExt === '.cjs' ? 'cjs' : 'esm');
89
+ const format = esbuild?.format ?? (outdir?.includes("/cjs") || outExt === ".cjs" ? "cjs" : "esm");
57
90
  results.push(await build({
58
91
  ...this.getEsBuildDefaults(),
59
92
  ...esbuild,
60
93
  format,
61
94
  outdir,
62
95
  plugins: [
63
- gjsifyPlugin({ debug: verbose, library: format, exclude, reflection: typescript?.reflection, jsExtension: outExt })
64
- ]
96
+ ...pnpPlugins,
97
+ gjsifyPlugin({ debug: verbose, library: format, exclude, reflection: typescript?.reflection, jsExtension: outExt }),
98
+ ],
65
99
  }));
66
100
  }
67
101
  return results;
@@ -130,12 +164,12 @@ export class BuildAction {
130
164
  console.debug(`[gjsify] --shebang: wrote shebang + chmod 0o755 to ${outfile}`);
131
165
  }
132
166
  /** Application mode */
133
- async buildApp(app = 'gjs') {
134
- const { verbose, esbuild, typescript, exclude, library: pgk, aliases } = this.configData;
135
- const format = esbuild?.format ?? (esbuild?.outfile?.endsWith('.cjs') ? 'cjs' : 'esm');
167
+ async buildApp(app = "gjs") {
168
+ const { verbose, esbuild, typescript, exclude, library: pgk, aliases, excludeGlobals } = this.configData;
169
+ const format = esbuild?.format ?? (esbuild?.outfile?.endsWith(".cjs") ? "cjs" : "esm");
136
170
  // Set default outfile if no outdir is set
137
171
  if (esbuild && !esbuild?.outfile && !esbuild?.outdir && (pgk?.main || pgk?.module)) {
138
- esbuild.outfile = esbuild?.format === 'cjs' ? pgk.main || pgk.module : pgk.module || pgk.main;
172
+ esbuild.outfile = esbuild?.format === "cjs" ? pgk.main || pgk.module : pgk.module || pgk.main;
139
173
  }
140
174
  const { consoleShim, globals } = this.configData;
141
175
  const pluginOpts = {
@@ -148,44 +182,46 @@ export class BuildAction {
148
182
  ...(aliases ? { aliases } : {}),
149
183
  };
150
184
  const { autoMode, extras } = this.parseGlobalsValue(globals);
185
+ const pnpPlugin = await getPnpPlugin();
186
+ const pnpPlugins = pnpPlugin ? [pnpPlugin] : [];
151
187
  // --- Auto mode (with optional extras): iterative multi-pass build ---
152
188
  // The extras token is used for cases where the detector cannot
153
189
  // statically see a global (e.g. Excalibur indirects globalThis via
154
190
  // BrowserComponent.nativeComponent). Common pattern: --globals auto,dom
155
- if (app === 'gjs' && autoMode) {
156
- const { injectPath } = await detectAutoGlobals({ ...this.getEsBuildDefaults(), ...esbuild, format }, pluginOpts, verbose, { extraGlobalsList: extras });
191
+ if (app === "gjs" && autoMode) {
192
+ const { injectPath } = await detectAutoGlobals({ ...this.getEsBuildDefaults(), ...esbuild, format, plugins: pnpPlugins }, pluginOpts, verbose, { extraGlobalsList: extras, excludeGlobals });
157
193
  const result = await build({
158
194
  ...this.getEsBuildDefaults(),
159
195
  ...esbuild,
160
196
  format,
161
197
  plugins: [
198
+ ...pnpPlugins,
162
199
  gjsifyPlugin({
163
200
  ...pluginOpts,
164
201
  autoGlobalsInject: injectPath,
165
202
  }),
166
203
  ],
167
204
  });
168
- if (app === 'gjs' && this.configData.shebang) {
205
+ if (app === "gjs" && this.configData.shebang) {
169
206
  await this.applyShebang(esbuild?.outfile, verbose);
170
207
  }
171
208
  return [result];
172
209
  }
173
210
  // --- Explicit list (no `auto` token) or none mode ---
174
- const autoGlobalsInject = extras
175
- ? await this.resolveGlobalsInject(app, extras, verbose)
176
- : undefined;
211
+ const autoGlobalsInject = extras ? await this.resolveGlobalsInject(app, extras, verbose) : undefined;
177
212
  const result = await build({
178
213
  ...this.getEsBuildDefaults(),
179
214
  ...esbuild,
180
215
  format,
181
216
  plugins: [
217
+ ...pnpPlugins,
182
218
  gjsifyPlugin({
183
219
  ...pluginOpts,
184
220
  autoGlobalsInject,
185
221
  }),
186
- ]
222
+ ],
187
223
  });
188
- if (app === 'gjs' && this.configData.shebang) {
224
+ if (app === "gjs" && this.configData.shebang) {
189
225
  await this.applyShebang(esbuild?.outfile, verbose);
190
226
  }
191
227
  return [result];
@@ -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.0",
3
+ "version": "0.3.2",
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.0",
27
- "@gjsify/esbuild-plugin-gjsify": "^0.3.0",
28
- "@gjsify/example-dom-canvas2d-fireworks": "^0.3.0",
29
- "@gjsify/example-dom-excalibur-jelly-jumper": "^0.3.0",
30
- "@gjsify/example-dom-three-geometry-teapot": "^0.3.0",
31
- "@gjsify/example-dom-three-postprocessing-pixel": "^0.3.0",
32
- "@gjsify/example-node-express-webserver": "^0.3.0",
33
- "@gjsify/node-polyfills": "^0.3.0",
34
- "@gjsify/web-polyfills": "^0.3.0",
26
+ "@gjsify/create-app": "^0.3.2",
27
+ "@gjsify/esbuild-plugin-gjsify": "^0.3.2",
28
+ "@gjsify/example-dom-canvas2d-fireworks": "^0.3.2",
29
+ "@gjsify/example-dom-excalibur-jelly-jumper": "^0.3.2",
30
+ "@gjsify/example-dom-three-geometry-teapot": "^0.3.2",
31
+ "@gjsify/example-dom-three-postprocessing-pixel": "^0.3.2",
32
+ "@gjsify/example-node-express-webserver": "^0.3.2",
33
+ "@gjsify/node-polyfills": "^0.3.2",
34
+ "@gjsify/web-polyfills": "^0.3.2",
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",
@@ -1,12 +1,39 @@
1
1
  import type { ConfigData } from '../types/index.js';
2
2
  import type { App } from '@gjsify/esbuild-plugin-gjsify';
3
- import { build, BuildOptions, BuildResult } from 'esbuild';
3
+ import { build, BuildOptions, BuildResult, Plugin } from 'esbuild';
4
4
  import { gjsifyPlugin } from '@gjsify/esbuild-plugin-gjsify';
5
5
  import { resolveGlobalsList, writeRegisterInjectFile, detectAutoGlobals } from '@gjsify/esbuild-plugin-gjsify/globals';
6
- import { dirname, extname } from 'node:path';
6
+ import { dirname, extname, join } from 'node:path';
7
7
  import { chmod, readFile, writeFile } from 'node:fs/promises';
8
-
9
- const GJS_SHEBANG = '#!/usr/bin/env -S gjs -m\n';
8
+ import { existsSync } from 'node:fs';
9
+
10
+ const GJS_SHEBANG = "#!/usr/bin/env -S gjs -m\n";
11
+
12
+ /** Walk up from dir until .pnp.cjs is found; return its directory or null. */
13
+ function findPnpRoot(dir: string): string | null {
14
+ let current = dir;
15
+ while (true) {
16
+ if (existsSync(join(current, ".pnp.cjs"))) return current;
17
+ const parent = dirname(current);
18
+ if (parent === current) return null;
19
+ current = parent;
20
+ }
21
+ }
22
+
23
+ /**
24
+ * If the current project uses Yarn PnP, return the official
25
+ * @yarnpkg/esbuild-plugin-pnp plugin so esbuild can resolve
26
+ * modules from zip archives without manual extraction.
27
+ */
28
+ async function getPnpPlugin(): Promise<Plugin | null> {
29
+ if (!findPnpRoot(process.cwd())) return null;
30
+ try {
31
+ const { pnpPlugin } = await import("@yarnpkg/esbuild-plugin-pnp");
32
+ return pnpPlugin();
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
10
37
 
11
38
  export class BuildAction {
12
39
  constructor(readonly configData: ConfigData = {}) {
@@ -30,50 +57,61 @@ export class BuildAction {
30
57
  const moduleOutdir = library?.module ? dirname(library.module) : undefined;
31
58
  const mainOutdir = library?.main ? dirname(library.main) : undefined;
32
59
 
33
- const moduleOutExt = library.module ? extname(library.module) : '.js';
34
- const mainOutExt = library.main ? extname(library.main) : '.js';
60
+ const moduleOutExt = library.module ? extname(library.module) : ".js";
61
+ const mainOutExt = library.main ? extname(library.main) : ".js";
35
62
 
36
- const multipleBuilds = moduleOutdir && mainOutdir && (moduleOutdir !== mainOutdir);
63
+ const multipleBuilds = moduleOutdir && mainOutdir && moduleOutdir !== mainOutdir;
64
+
65
+ const pnpPlugin = await getPnpPlugin();
66
+ const pnpPlugins: Plugin[] = pnpPlugin ? [pnpPlugin] : [];
37
67
 
38
68
  const results: BuildResult[] = [];
39
-
40
- if(multipleBuilds) {
41
69
 
42
- const moduleFormat = moduleOutdir.includes('/cjs') || moduleOutExt === '.cjs' ? 'cjs' : 'esm';
43
- results.push(await build({
44
- ...this.getEsBuildDefaults(),
45
- ...esbuild,
46
- format: moduleFormat,
47
- outdir: moduleOutdir,
48
- plugins: [
49
- gjsifyPlugin({debug: verbose, library: moduleFormat, exclude, reflection: typescript?.reflection, jsExtension: moduleOutExt}),
50
- ]
51
- }));
52
-
53
- const mainFormat = mainOutdir.includes('/cjs') || mainOutExt === '.cjs' ? 'cjs' : 'esm';
54
- results.push(await build({
55
- ...this.getEsBuildDefaults(),
56
- ...esbuild,
57
- format: moduleFormat,
58
- outdir: mainOutdir,
59
- plugins: [
60
- gjsifyPlugin({debug: verbose, library: mainFormat, exclude, reflection: typescript?.reflection, jsExtension: mainOutdir})
61
- ]
62
- }));
70
+ if (multipleBuilds) {
71
+ const moduleFormat = moduleOutdir.includes("/cjs") || moduleOutExt === ".cjs" ? "cjs" : "esm";
72
+ results.push(
73
+ await build({
74
+ ...this.getEsBuildDefaults(),
75
+ ...esbuild,
76
+ format: moduleFormat,
77
+ outdir: moduleOutdir,
78
+ plugins: [
79
+ ...pnpPlugins,
80
+ gjsifyPlugin({ debug: verbose, library: moduleFormat, exclude, reflection: typescript?.reflection, jsExtension: moduleOutExt }),
81
+ ],
82
+ }),
83
+ );
84
+
85
+ const mainFormat = mainOutdir.includes("/cjs") || mainOutExt === ".cjs" ? "cjs" : "esm";
86
+ results.push(
87
+ await build({
88
+ ...this.getEsBuildDefaults(),
89
+ ...esbuild,
90
+ format: moduleFormat,
91
+ outdir: mainOutdir,
92
+ plugins: [
93
+ ...pnpPlugins,
94
+ gjsifyPlugin({ debug: verbose, library: mainFormat, exclude, reflection: typescript?.reflection, jsExtension: mainOutdir }),
95
+ ],
96
+ }),
97
+ );
63
98
  } else {
64
99
  const outfilePath = esbuild?.outfile || library?.module || library?.main;
65
- const outExt = outfilePath ? extname(outfilePath) : '.js';
100
+ const outExt = outfilePath ? extname(outfilePath) : ".js";
66
101
  const outdir = esbuild?.outdir || (outfilePath ? dirname(outfilePath) : undefined);
67
- const format: 'esm' | 'cjs' = (esbuild?.format as 'esm' | 'cjs') ?? (outdir?.includes('/cjs') || outExt === '.cjs' ? 'cjs' : 'esm');
68
- results.push(await build({
69
- ...this.getEsBuildDefaults(),
70
- ...esbuild,
71
- format,
72
- outdir,
73
- plugins: [
74
- gjsifyPlugin({debug: verbose, library: format, exclude, reflection: typescript?.reflection, jsExtension: outExt})
75
- ]
76
- }));
102
+ const format: "esm" | "cjs" = (esbuild?.format as "esm" | "cjs") ?? (outdir?.includes("/cjs") || outExt === ".cjs" ? "cjs" : "esm");
103
+ results.push(
104
+ await build({
105
+ ...this.getEsBuildDefaults(),
106
+ ...esbuild,
107
+ format,
108
+ outdir,
109
+ plugins: [
110
+ ...pnpPlugins,
111
+ gjsifyPlugin({ debug: verbose, library: format, exclude, reflection: typescript?.reflection, jsExtension: outExt }),
112
+ ],
113
+ }),
114
+ );
77
115
  }
78
116
  return results;
79
117
  }
@@ -147,15 +185,14 @@ export class BuildAction {
147
185
  }
148
186
 
149
187
  /** Application mode */
150
- async buildApp(app: App = 'gjs') {
188
+ async buildApp(app: App = "gjs") {
189
+ const { verbose, esbuild, typescript, exclude, library: pgk, aliases, excludeGlobals } = this.configData;
151
190
 
152
- const { verbose, esbuild, typescript, exclude, library: pgk, aliases } = this.configData;
153
-
154
- const format: 'esm' | 'cjs' = (esbuild?.format as 'esm' | 'cjs') ?? (esbuild?.outfile?.endsWith('.cjs') ? 'cjs' : 'esm');
191
+ const format: "esm" | "cjs" = (esbuild?.format as "esm" | "cjs") ?? (esbuild?.outfile?.endsWith(".cjs") ? "cjs" : "esm");
155
192
 
156
193
  // Set default outfile if no outdir is set
157
- if(esbuild && !esbuild?.outfile && !esbuild?.outdir && (pgk?.main || pgk?.module)) {
158
- esbuild.outfile = esbuild?.format === 'cjs' ? pgk.main || pgk.module : pgk.module || pgk.main;
194
+ if (esbuild && !esbuild?.outfile && !esbuild?.outdir && (pgk?.main || pgk?.module)) {
195
+ esbuild.outfile = esbuild?.format === "cjs" ? pgk.main || pgk.module : pgk.module || pgk.main;
159
196
  }
160
197
 
161
198
  const { consoleShim, globals } = this.configData;
@@ -172,16 +209,19 @@ export class BuildAction {
172
209
 
173
210
  const { autoMode, extras } = this.parseGlobalsValue(globals);
174
211
 
212
+ const pnpPlugin = await getPnpPlugin();
213
+ const pnpPlugins: Plugin[] = pnpPlugin ? [pnpPlugin] : [];
214
+
175
215
  // --- Auto mode (with optional extras): iterative multi-pass build ---
176
216
  // The extras token is used for cases where the detector cannot
177
217
  // statically see a global (e.g. Excalibur indirects globalThis via
178
218
  // BrowserComponent.nativeComponent). Common pattern: --globals auto,dom
179
- if (app === 'gjs' && autoMode) {
219
+ if (app === "gjs" && autoMode) {
180
220
  const { injectPath } = await detectAutoGlobals(
181
- { ...this.getEsBuildDefaults(), ...esbuild, format },
221
+ { ...this.getEsBuildDefaults(), ...esbuild, format, plugins: pnpPlugins },
182
222
  pluginOpts,
183
223
  verbose,
184
- { extraGlobalsList: extras },
224
+ { extraGlobalsList: extras, excludeGlobals },
185
225
  );
186
226
 
187
227
  const result = await build({
@@ -189,6 +229,7 @@ export class BuildAction {
189
229
  ...esbuild,
190
230
  format,
191
231
  plugins: [
232
+ ...pnpPlugins,
192
233
  gjsifyPlugin({
193
234
  ...pluginOpts,
194
235
  autoGlobalsInject: injectPath,
@@ -196,7 +237,7 @@ export class BuildAction {
196
237
  ],
197
238
  });
198
239
 
199
- if (app === 'gjs' && this.configData.shebang) {
240
+ if (app === "gjs" && this.configData.shebang) {
200
241
  await this.applyShebang(esbuild?.outfile, verbose);
201
242
  }
202
243
 
@@ -204,23 +245,22 @@ export class BuildAction {
204
245
  }
205
246
 
206
247
  // --- Explicit list (no `auto` token) or none mode ---
207
- const autoGlobalsInject = extras
208
- ? await this.resolveGlobalsInject(app, extras, verbose)
209
- : undefined;
248
+ const autoGlobalsInject = extras ? await this.resolveGlobalsInject(app, extras, verbose) : undefined;
210
249
 
211
250
  const result = await build({
212
251
  ...this.getEsBuildDefaults(),
213
252
  ...esbuild,
214
253
  format,
215
254
  plugins: [
255
+ ...pnpPlugins,
216
256
  gjsifyPlugin({
217
257
  ...pluginOpts,
218
258
  autoGlobalsInject,
219
259
  }),
220
- ]
260
+ ],
221
261
  });
222
262
 
223
- if (app === 'gjs' && this.configData.shebang) {
263
+ if (app === "gjs" && this.configData.shebang) {
224
264
  await this.applyShebang(esbuild?.outfile, verbose);
225
265
  }
226
266
 
@@ -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
  }
@@ -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
  }