import-in-the-middle 2.0.3 → 2.0.5

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.5](https://github.com/nodejs/import-in-the-middle/compare/import-in-the-middle-v2.0.4...import-in-the-middle-v2.0.5) (2026-01-20)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * handle lazy initialization and circular dependencies ([#229](https://github.com/nodejs/import-in-the-middle/issues/229)) ([d1421dc](https://github.com/nodejs/import-in-the-middle/commit/d1421dc0ae65ce6da5de5cb58f41af99f9d87371))
9
+
10
+ ## [2.0.4](https://github.com/nodejs/import-in-the-middle/compare/import-in-the-middle-v2.0.3...import-in-the-middle-v2.0.4) (2026-01-14)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * do not instrument the top level module ([#225](https://github.com/nodejs/import-in-the-middle/issues/225)) ([b563b35](https://github.com/nodejs/import-in-the-middle/commit/b563b35c74b96554b5112905391ec3842162b7ee))
16
+
3
17
  ## [2.0.3](https://github.com/nodejs/import-in-the-middle/compare/import-in-the-middle-v2.0.2...import-in-the-middle-v2.0.3) (2026-01-13)
4
18
 
5
19
 
package/create-hook.mjs CHANGED
@@ -24,7 +24,7 @@ const HANDLED_FORMATS = new Set(['builtin', 'module', 'commonjs'])
24
24
  const TRACE_WARNINGS = process.execArgv.includes('--trace-warnings')
25
25
 
26
26
  let getExports
27
- if (NODE_MAJOR >= 20 || (NODE_MAJOR === 18 && NODE_MINOR >= 19)) {
27
+ if (NODE_MAJOR > 16 || (NODE_MAJOR === 16 && NODE_MINOR >= 16)) {
28
28
  getExports = getExportsImpl
29
29
  } else {
30
30
  getExports = (url) => import(url).then(Object.keys)
@@ -295,13 +295,25 @@ async function processModule ({ srcUrl, context, parentGetSource, parentResolve,
295
295
 
296
296
  addSetter(n, `
297
297
  let ${variableName}
298
+ __overridden[${objectKey}] = false
299
+ let ${variableName}Defer = false
298
300
  try {
299
301
  ${variableName} = _[${objectKey}] = namespace[${objectKey}]
300
302
  } catch (err) {
301
303
  if (!(err instanceof ReferenceError)) throw err
304
+ ${variableName}Defer = true
305
+ }
306
+
307
+ if (${variableName}Defer || ${variableName} === undefined) {
308
+ __pending.push(__makeUpdater(
309
+ ${objectKey},
310
+ () => namespace[${objectKey}],
311
+ (v) => { ${variableName} = _[${objectKey}] = v }
312
+ ))
302
313
  }
303
314
  export { ${variableName} as ${reExportedName} }
304
315
  set[${objectKey}] = (v) => {
316
+ __overridden[${objectKey}] = true
305
317
  ${variableName} = v
306
318
  return true
307
319
  }
@@ -375,10 +387,20 @@ export function createHook (meta) {
375
387
  if (isWin && parentURL.indexOf('file:node') === 0) {
376
388
  context.parentURL = ''
377
389
  }
378
- const result = await parentResolve(newSpecifier, context, parentResolve)
379
- if (parentURL === '' && !EXTENSION_RE.test(result.url)) {
380
- entrypoint = result.url
381
- return { url: result.url, format: 'commonjs' }
390
+ const result = await parentResolve(newSpecifier, context)
391
+
392
+ // Do not wrap the entrypoint module. Many CLIs check whether they are the
393
+ // "main" module (e.g. require.main === module). Wrapping changes how they
394
+ // are evaluated, and can make them exit without doing anything.
395
+ if (parentURL === '') {
396
+ if (!EXTENSION_RE.test(result.url) && !hasIitm(result.url)) {
397
+ entrypoint = result.url
398
+ return { url: result.url, format: 'commonjs' }
399
+ }
400
+ if (NODE_MAJOR > 16 || (NODE_MAJOR === 16 && NODE_MINOR >= 16)) {
401
+ entrypoint = result.url
402
+ return result
403
+ }
382
404
  }
383
405
 
384
406
  // For included/excluded modules, we check the specifier to match libraries
@@ -472,9 +494,65 @@ ${experimentalPatchInternals ? `import { setExperimentalPatchInternals } from '$
472
494
  const _ = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } })
473
495
  const set = {}
474
496
  const get = {}
497
+ const __overridden = Object.create(null)
498
+ let __pending = []
499
+
500
+ function __makeUpdater (key, read, assign) {
501
+ return () => {
502
+ if (__overridden[key] === true) return true
503
+ try {
504
+ const v = read()
505
+ if (v !== undefined) {
506
+ assign(v)
507
+ return true
508
+ }
509
+ return false
510
+ } catch (err) {
511
+ if (err instanceof ReferenceError) return false
512
+ throw err
513
+ }
514
+ }
515
+ }
516
+
517
+ function __flushPendingOnce () {
518
+ if (__pending.length === 0) return
519
+ const next = []
520
+ for (const fn of __pending) {
521
+ // If it still throws ReferenceError, keep it for the (single) next attempt.
522
+ if (fn() !== true) next.push(fn)
523
+ }
524
+ __pending = next
525
+ }
475
526
 
476
527
  ${Array.from(setters.values()).join('\n')}
477
528
 
529
+ if (__pending.length > 0) {
530
+ queueMicrotask(() => {
531
+ __flushPendingOnce()
532
+
533
+ if (__pending.length > 0) {
534
+ const __retryDelays = [0, 10, 50]
535
+ const __schedulePending = (i) => {
536
+ if (__pending.length === 0) return
537
+ if (i >= __retryDelays.length) {
538
+ // Give up: leave exports as-is to avoid unbounded retries.
539
+ __pending = []
540
+ return
541
+ }
542
+
543
+ const t = setTimeout(() => {
544
+ __flushPendingOnce()
545
+ __schedulePending(i + 1)
546
+ }, __retryDelays[i])
547
+ // Don't keep the process alive just for best-effort retries.
548
+ if (t && typeof t.unref === 'function') t.unref()
549
+ }
550
+
551
+ __schedulePending(0)
552
+ }
553
+ })
554
+ }
555
+
478
556
  register(${JSON.stringify(realUrl)}, _, set, get, ${JSON.stringify(originalSpecifier)})
479
557
  `
480
558
  }
@@ -502,21 +580,28 @@ register(${JSON.stringify(realUrl)}, _, set, get, ${JSON.stringify(originalSpeci
502
580
  }
503
581
  }
504
582
 
505
- return parentGetSource(url, context, parentGetSource)
583
+ return parentGetSource(url, context)
506
584
  }
507
585
 
508
586
  // For Node.js 16.12.0 and higher.
509
587
  async function load (url, context, parentLoad) {
510
588
  if (hasIitm(url)) {
511
- const { source } = await getSource(url, context, parentLoad)
512
- return {
513
- source,
514
- shortCircuit: true,
515
- format: 'module'
589
+ const result = await getSource(url, context, parentLoad)
590
+ // If wrapping failed, `getSource()` may have fallen back to `parentLoad`,
591
+ // which can legally return `source: null` (e.g. for non-JS formats).
592
+ if (result && typeof result === 'object' && result.source != null) {
593
+ return {
594
+ source: result.source,
595
+ shortCircuit: true,
596
+ format: 'module'
597
+ }
516
598
  }
599
+
600
+ // Fall back to the parent loader with the original (non-iitm) URL.
601
+ return parentLoad(deleteIitm(url), context)
517
602
  }
518
603
 
519
- return parentLoad(url, context, parentLoad)
604
+ return parentLoad(url, context)
520
605
  }
521
606
 
522
607
  if (NODE_MAJOR >= 17 || (NODE_MAJOR === 16 && NODE_MINOR >= 12)) {
@@ -539,7 +624,7 @@ register(${JSON.stringify(realUrl)}, _, set, get, ${JSON.stringify(originalSpeci
539
624
  }
540
625
  }
541
626
 
542
- return parentGetFormat(url, context, parentGetFormat)
627
+ return parentGetFormat(url, context)
543
628
  }
544
629
  }
545
630
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "import-in-the-middle",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "Intercept imports in Node.js",
5
5
  "main": "index.js",
6
6
  "scripts": {