Skip to content

Fix Dynamic require of net crash in the published Action - #15

Merged
dakshcodez merged 1 commit into
mainfrom
fix-action-cjs-bundle
Aug 15, 2026
Merged

Fix Dynamic require of net crash in the published Action#15
dakshcodez merged 1 commit into
mainfrom
fix-action-cjs-bundle

Conversation

@dakshcodez

Copy link
Copy Markdown
Owner

Summary

Real bug from live usage, reported after trying dakshcodez/docmend@v1 in another repo - the Action failed immediately on every run:

Error: Dynamic require of "net" is not supported
    at .../tunnel/lib/tunnel.js

esbuild's ESM output has no real require global, so it shims any require() call it can't statically resolve - and the tunnel package (pulled in transitively via @actions/http-client's proxy support, itself an octokit dependency) calls require('net') at its own top level, hits the shim, finds nothing real, throws immediately on module load.

What was tried and what actually worked

First attempt: switch the bundle to CJS output (real require exists there). This introduced a different bug instead - esbuild empties out import.meta.url entirely under CJS, and @docmend/core's wasm-path resolution depends on it. Would have traded a loud crash for silent wasm-loading failure.

Fixed properly by staying on ESM output and giving the bundle's shim a genuine require via esbuild's own documented banner-injection fix for this exact error class:

import { createRequire as X } from 'module';
const require = X(import.meta.url);

The createRequire import needed aliasing specifically because grammar-loader.ts already imports it under its own name and gets bundled into the same module scope - the unaliased version hit a real SyntaxError (duplicate declaration) during testing, caught before landing on this.

Also consolidated the build step into scripts/build.mjs using esbuild's JS API (the banner flag was awkward to correctly shell-quote as an inline package.json script string), and bumped runs.using from node20 to node24 - the same failing run's log included GitHub's deprecation notice that Node 20 is being phased out.

Test plan

  • Reproduced the exact original crash in a clean, zero-node_modules isolated directory
  • Confirmed an intermediate unaliased-banner attempt still failed (different, real SyntaxError) before landing on the working fix - not just accepting the first thing that seemed to work
  • Confirmed the final build loads successfully in that same clean isolation, reaching the expected next failure point (a missing test input) instead of crashing
  • Directly re-verified real WASM-based code parsing still works in that same isolated environment
  • npm run lint / typecheck / build all pass

Real bug reported from live usage in another repo: the Action failed
immediately on every run with

  Error: Dynamic require of "net" is not supported
    at .../tunnel/lib/tunnel.js
    at .../tunnel/index.js

esbuild's ESM output has no real `require` global, so it injects a
shim (`typeof require !== "undefined" ? require : ... throw`) for any
require() call it can't statically resolve. The `tunnel` package
(pulled in transitively via @actions/http-client's proxy support,
itself a dependency of octokit) does `require('net')`/`require('tls')`
etc. at its own top level, hits that shim, finds no real `require`,
and throws - immediately on module load, before any of our own code
runs.

First attempt was switching the bundle to CJS output (where require
is real), but that broke something else: esbuild empties out
import.meta.url entirely under CJS ("will be empty" warning), and
@docmend/core's wasm-path resolution (grammar-loader.ts, fixed for
the original bundling bug back in the publish-prep phase) depends on
import.meta.url to find the wasm files sitting next to the bundle.
Trading one crash for a silent one.

Fixed properly by staying on ESM output and instead giving the
bundle's shim a genuine `require` to find, via esbuild's own
documented banner-injection fix for exactly this class of error:

  import { createRequire as X } from 'module';
  const require = X(import.meta.url);

Aliased the createRequire import specifically because grammar-loader.ts
already imports createRequire under its own name and gets bundled
into the same module scope - importing it twice unaliased is a
duplicate declaration (a real SyntaxError I hit and fixed before
landing on this).

Consolidated the whole build step (previously a long inline esbuild
CLI invocation, awkward to correctly quote a banner flag inside a
package.json script string) into scripts/build.mjs using esbuild's
JS API instead, which also copies the wasm files as before.

Also bumped runs.using from 'node20' to 'node24' in action.yml -
the same failing run's log included GitHub's own deprecation notice
that Node 20 is being phased out and runners already default to
Node 24 regardless of what the action declares.

Verified with the same rigor as the original wasm-bundling fix, not
just "no warnings": reproduced the exact original crash in a clean,
zero-node_modules directory (confirmed the unaliased-banner attempt
still failed, this time with a different, real SyntaxError - caught
before considering this done), then confirmed the final build loads
successfully in that same clean isolation and that real WASM-based
code parsing still works correctly there.
@dakshcodez
dakshcodez merged commit 5306f85 into main Aug 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant