Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,16 @@ export default defineConfig({
// runtime, so every npm dependency (AWS SDK, @actions/core, @octokit/…) must
// be bundled in. Node.js built-ins stay external automatically (platform:'node').
noExternal: [/.*/],
// CJS packages bundled into ESM (e.g. tunnel@0.0.6 via @actions/http-client)
// call require('net') / require('tls') at runtime. esbuild's __require shim
// checks `typeof require !== "undefined"` — which is false in pure ESM — and
// throws "Dynamic require of 'net' is not supported". Injecting createRequire
// as a top-level banner supplies a real require() before the shim runs,
// so those CJS modules can load Node.js built-ins normally.
// NOTE: this banner uses import/import.meta.url — only valid in ESM output.
// If format is ever extended to include 'cjs', this must be conditioned or
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Module-scoped require in security-sensitive bundle — acknowledged

const require = createRequire(import.meta.url) introduces a working require() into the module scope of every output bundle. In pure ESM without this banner, no require exists at all.

A few things are worth being aware of (not blockers):

  • The require is module-scoped, not globalglobalThis.require stays undefined, so only code already executing inside the bundle can call it.
  • All CJS require() calls in bundled deps route through esbuild's __require shim (which delegates to this require), so the exposure is bounded to the bundle's existing dependency graph.
  • None of the current dependencies (tunnel, @actions/core, AWS SDK) are known to pass user-controlled input to require() — but the action handles API keys and signed commits, so it's worth keeping in mind if new CJS dependencies are added.

Empirically confirmed to not cause issues in any current code path. ✅

// moved to a format-specific banner ({ esm: '...' }) to avoid a parse error.
banner: {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Banner uses ESM-only syntax — will silently break if 'cjs' is ever added to format

The injected banner contains an import statement and import.meta.url, both of which are syntax errors in CommonJS output. The current config only emits ESM (format: ['esm']), so this is harmless today.

If a future maintainer extends format to ['esm', 'cjs'] (a common tsup pattern), the banner will be emitted verbatim into .cjs files and cause a parse failure. A short comment on the banner block noting the ESM-only constraint would guard against this:

// NOTE: this banner uses import/import.meta.url — only valid in ESM.
// If format is ever extended to include 'cjs', this must be conditioned
// or moved to a format-specific banner (banner: { esm: '...' }).
banner: {
  js: "import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);",
},

Not a current bug; just a maintenance note.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — the three-line ESM-only warning comment has been added directly above the banner: block in commit 843c614. It calls out explicitly that the import/import.meta.url syntax is only valid in ESM output and what action to take if 'cjs' is ever added to format.

js: "import { createRequire } from 'node:module'; var require = createRequire(import.meta.url);",
},
});
Loading