From 06d30e1cc9019ef645438183d2bcee78ca1eeb35 Mon Sep 17 00:00:00 2001 From: dakshcodez Date: Sat, 15 Aug 2026 17:59:23 +0530 Subject: [PATCH] Fix "Dynamic require of net is not supported" crash in the Action 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. --- action.yml | 2 +- packages/action/dist/index.js | 1 + packages/action/package.json | 2 +- packages/action/scripts/build.mjs | 50 +++++++++++++++++++++++++++ packages/action/scripts/copy-wasm.mjs | 23 ------------ 5 files changed, 53 insertions(+), 25 deletions(-) create mode 100644 packages/action/scripts/build.mjs delete mode 100644 packages/action/scripts/copy-wasm.mjs diff --git a/action.yml b/action.yml index 71ae614..8667d34 100644 --- a/action.yml +++ b/action.yml @@ -25,5 +25,5 @@ branding: icon: 'refresh-cw' color: 'blue' runs: - using: 'node20' + using: 'node24' main: 'packages/action/dist/index.js' diff --git a/packages/action/dist/index.js b/packages/action/dist/index.js index 7988690..a052b4e 100644 --- a/packages/action/dist/index.js +++ b/packages/action/dist/index.js @@ -1,3 +1,4 @@ +import { createRequire as __docmendCreateRequire } from 'module'; const require = __docmendCreateRequire(import.meta.url); var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; diff --git a/packages/action/package.json b/packages/action/package.json index a3c74ff..2bbc45e 100644 --- a/packages/action/package.json +++ b/packages/action/package.json @@ -9,7 +9,7 @@ "dist" ], "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=esm --external:@huggingface/transformers --outfile=dist/index.js && node scripts/copy-wasm.mjs", + "build": "node scripts/build.mjs", "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { diff --git a/packages/action/scripts/build.mjs b/packages/action/scripts/build.mjs new file mode 100644 index 0000000..720744e --- /dev/null +++ b/packages/action/scripts/build.mjs @@ -0,0 +1,50 @@ +import { build } from 'esbuild'; +import { copyFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { basename, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// esbuild's ESM output has no real `require`, so its internal shim throws +// whenever a bundled CJS dependency calls require() for something it can't +// statically resolve (e.g. the `tunnel` package, pulled in transitively via +// @actions/http-client's proxy support, does `require('net')` etc.). This +// banner gives the bundle a genuine `require` to find - esbuild's own +// documented fix for this exact class of error - so the output can stay ESM +// (needed for import.meta.url, which @docmend/core's wasm-path resolution +// depends on, and which esbuild empties out under CJS output instead). +await build({ + entryPoints: ['src/index.ts'], + bundle: true, + platform: 'node', + target: 'node24', + format: 'esm', + external: ['@huggingface/transformers'], + outfile: 'dist/index.js', + banner: { + // Aliased import: @docmend/core's grammar-loader.ts already imports + // createRequire under its own name, and gets bundled into this same + // module scope - importing it again under the unaliased name here + // would be a duplicate declaration (SyntaxError), not just a harmless + // redundant import. + js: "import { createRequire as __docmendCreateRequire } from 'module'; const require = __docmendCreateRequire(import.meta.url);", + }, +}); + +// Resolved from @docmend/core's own location (not this script's) so it works +// regardless of where npm happened to hoist these transitive dependencies. +const coreEntry = fileURLToPath(import.meta.resolve('@docmend/core')); +const coreRequire = createRequire(coreEntry); + +const wasmFiles = [ + coreRequire.resolve('web-tree-sitter/tree-sitter.wasm'), + coreRequire.resolve('tree-sitter-wasms/out/tree-sitter-typescript.wasm'), + coreRequire.resolve('tree-sitter-wasms/out/tree-sitter-tsx.wasm'), + coreRequire.resolve('tree-sitter-wasms/out/tree-sitter-javascript.wasm'), + coreRequire.resolve('tree-sitter-wasms/out/tree-sitter-python.wasm'), +]; + +for (const file of wasmFiles) { + copyFileSync(file, join('dist', basename(file))); +} + +console.log(`copied ${wasmFiles.length} wasm files into dist/`); diff --git a/packages/action/scripts/copy-wasm.mjs b/packages/action/scripts/copy-wasm.mjs deleted file mode 100644 index 58e348d..0000000 --- a/packages/action/scripts/copy-wasm.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import { copyFileSync } from 'node:fs'; -import { createRequire } from 'node:module'; -import { basename, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -// Resolved from @docmend/core's own location (not this script's) so it works -// regardless of where npm happened to hoist these transitive dependencies. -const coreEntry = fileURLToPath(import.meta.resolve('@docmend/core')); -const coreRequire = createRequire(coreEntry); - -const wasmFiles = [ - coreRequire.resolve('web-tree-sitter/tree-sitter.wasm'), - coreRequire.resolve('tree-sitter-wasms/out/tree-sitter-typescript.wasm'), - coreRequire.resolve('tree-sitter-wasms/out/tree-sitter-tsx.wasm'), - coreRequire.resolve('tree-sitter-wasms/out/tree-sitter-javascript.wasm'), - coreRequire.resolve('tree-sitter-wasms/out/tree-sitter-python.wasm'), -]; - -for (const file of wasmFiles) { - copyFileSync(file, join('dist', basename(file))); -} - -console.log(`copied ${wasmFiles.length} wasm files into dist/`);