@@ -21,6 +21,102 @@ const STUB_MAP = {
2121 '^debug$' : 'debug.cjs' ,
2222}
2323
24+ // Import createRequire at top level
25+ import { createRequire } from 'node:module'
26+
27+ const requireResolve = createRequire ( import . meta. url )
28+
29+ /**
30+ * Create esbuild plugin to force npm packages to resolve from node_modules.
31+ * This prevents tsconfig.json path mappings from creating circular dependencies.
32+ *
33+ * @returns {import('esbuild').Plugin }
34+ */
35+ function createForceNodeModulesPlugin ( ) {
36+ /**
37+ * Packages that must be resolved from node_modules to prevent circular dependencies.
38+ *
39+ * THE PROBLEM:
40+ * ────────────
41+ * Some packages have tsconfig.json path mappings like:
42+ * "cacache": ["./src/external/cacache"]
43+ *
44+ * This creates a circular dependency during bundling:
45+ *
46+ * ┌─────────────────────────────────────────────────┐
47+ * │ │
48+ * │ esbuild bundles: src/external/cacache.js │
49+ * │ ↓ │
50+ * │ File contains: require('cacache') │
51+ * │ ↓ │
52+ * │ tsconfig redirects: 'cacache' → src/external/ │ ← LOOP!
53+ * │ ↓ │
54+ * │ esbuild tries to bundle: src/external/cacache │
55+ * │ ↓ │
56+ * │ Circular reference! ⚠️ │
57+ * └─────────────────────────────────────────────────┘
58+ *
59+ * THE SOLUTION:
60+ * ─────────────
61+ * This plugin intercepts resolution and forces these packages to resolve
62+ * from node_modules, bypassing the tsconfig path mappings:
63+ *
64+ * src/external/cacache.js
65+ * ↓
66+ * require('cacache')
67+ * ↓
68+ * Plugin intercepts → node_modules/cacache ✓
69+ *
70+ * PACKAGES WITH ACTUAL TSCONFIG MAPPINGS (as of now):
71+ * ────────────────────────────────────────────────────
72+ * ✓ cacache - line 37 in tsconfig.json
73+ * ✓ make-fetch-happen - line 38 in tsconfig.json
74+ * ✓ fast-sort - line 39 in tsconfig.json
75+ * ✓ pacote - line 40 in tsconfig.json
76+ *
77+ * ADDITIONAL PACKAGES (defensive):
78+ * ────────────────────────────────
79+ * · libnpmexec - Related to pacote, included for consistency
80+ * · libnpmpack - Related to pacote, included for consistency
81+ * · npm-package-arg - Related to pacote, included for consistency
82+ * · normalize-package-data - Related to npm packages, included for consistency
83+ *
84+ * NOTE: Other external packages (debug, del, semver, etc.) don't have
85+ * tsconfig mappings, so they naturally resolve from node_modules without
86+ * needing to be listed here.
87+ */
88+ const packagesWithPathMappings = [
89+ 'cacache' ,
90+ 'make-fetch-happen' ,
91+ 'fast-sort' ,
92+ 'pacote' ,
93+ 'libnpmexec' ,
94+ 'libnpmpack' ,
95+ 'npm-package-arg' ,
96+ 'normalize-package-data' ,
97+ ]
98+
99+ return {
100+ name : 'force-node-modules' ,
101+ setup ( build ) {
102+ for ( const pkg of packagesWithPathMappings ) {
103+ build . onResolve ( { filter : new RegExp ( `^${ pkg } $` ) } , args => {
104+ // Only intercept if not already in node_modules
105+ if ( ! args . importer . includes ( 'node_modules' ) ) {
106+ try {
107+ return { path : requireResolve . resolve ( pkg ) , external : false }
108+ } catch {
109+ // Package not found, let esbuild handle the error
110+ return null
111+ }
112+ }
113+ return null
114+ } )
115+ }
116+ } ,
117+ }
118+ }
119+
24120/**
25121 * Create esbuild plugin to stub modules using files from stubs/ directory.
26122 *
@@ -134,7 +230,7 @@ export function getEsbuildConfig(entryPoint, outfile, packageOpts = {}) {
134230 '@socketsecurity/registry' ,
135231 ...( packageOpts . external || [ ] ) ,
136232 ] ,
137- plugins : [ createStubPlugin ( ) ] ,
233+ plugins : [ createForceNodeModulesPlugin ( ) , createStubPlugin ( ) ] ,
138234 minify : true ,
139235 sourcemap : false ,
140236 metafile : true ,
0 commit comments