import-in-the-middle 1.7.4 → 1.8.0

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.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/hook.js +96 -151
  3. package/index.js +1 -0
  4. package/lib/get-esm-exports.js +4 -25
  5. package/lib/get-exports.js +9 -16
  6. package/package.json +5 -1
  7. package/test/README.md +1 -1
  8. package/test/fixtures/cyclical-self.mjs +8 -0
  9. package/test/fixtures/duplicate-a.mjs +4 -0
  10. package/test/fixtures/duplicate-b.mjs +1 -0
  11. package/test/fixtures/duplicate.mjs +5 -0
  12. package/test/fixtures/export-types/default-call-expression-renamed.mjs +2 -0
  13. package/test/fixtures/export-types/default-call-expression.mjs +1 -0
  14. package/test/fixtures/got-alike.mjs +0 -6
  15. package/test/fixtures/re-export-star-external.mjs +1 -0
  16. package/test/fixtures/sub-directory/re-export-star-external.mjs +1 -0
  17. package/test/get-esm-exports/v20-get-esm-exports.js +1 -1
  18. package/test/hook/cyclical-self.mjs +4 -0
  19. package/test/hook/default-export.mjs +8 -0
  20. package/test/hook/duplicate-exports.mjs +19 -0
  21. package/test/hook/dynamic-import.js +1 -1
  22. package/test/hook/re-export-star-module.mjs +17 -0
  23. package/test/hook/static-import-default.mjs +1 -1
  24. package/test/hook/v14-date-fns.mjs +10 -0
  25. package/test/hook/v14-react-email-components.mjs +16 -0
  26. package/test/hook/v16-got.mjs +16 -0
  27. package/test/hook/v18.19-openai.mjs +10 -0
  28. package/test/hook/v18.19-static-import-gotalike.mjs +4 -13
  29. package/test/multiple-loaders/multiple-loaders.test.mjs +6 -0
  30. package/test/multiple-loaders/typescript-loader.mjs +26 -0
  31. package/test/version-check.js +7 -1
  32. package/fix.patch +0 -52
  33. package/test/fixtures/default-class.mjs +0 -3
  34. package/test/fixtures/snake_case.mjs +0 -2
  35. /package/test/hook/{v18-static-import-assert.mjs → v18-v21-static-import-assert.mjs} +0 -0
package/README.md CHANGED
@@ -18,7 +18,7 @@ imported in ESM files, regardless of whether they're imported statically or
18
18
  dynamically.
19
19
 
20
20
  ```js
21
- import Hook from 'import-in-the-middle'
21
+ import { Hook } from 'import-in-the-middle'
22
22
  import { foo } from 'package-i-want-to-modify'
23
23
 
24
24
  console.log(foo) // whatever that module exported
package/hook.js CHANGED
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
4
4
 
5
- const { randomBytes } = require('crypto')
5
+ const { URL } = require('url')
6
6
  const specifiers = new Map()
7
7
  const isWin = process.platform === 'win32'
8
8
 
@@ -19,7 +19,7 @@ let getExports
19
19
  if (NODE_MAJOR >= 20 || (NODE_MAJOR === 18 && NODE_MINOR >= 19)) {
20
20
  getExports = require('./lib/get-exports.js')
21
21
  } else {
22
- getExports = ({ url }) => import(url).then(Object.keys)
22
+ getExports = (url) => import(url).then(Object.keys)
23
23
  }
24
24
 
25
25
  function hasIitm (url) {
@@ -91,135 +91,94 @@ function isStarExportLine (line) {
91
91
  return /^\* from /.test(line)
92
92
  }
93
93
 
94
- /**
95
- * @typedef {object} ProcessedModule
96
- * @property {string[]} imports A set of ESM import lines to be added to the
97
- * shimmed module source.
98
- * @property {string[]} namespaces A set of identifiers representing the
99
- * modules in `imports`, e.g. for `import * as foo from 'bar'`, "foo" will be
100
- * present in this array.
101
- * @property {Map<string, string>} setters The shimmed setters for all the
102
- * exports from the module and any transitive export all modules. The key is
103
- * used to deduplicate conflicting exports, assigning a priority to `default`
104
- * exports.
105
- */
94
+ function isBareSpecifier (specifier) {
95
+ // Relative and absolute paths are not bare specifiers.
96
+ if (
97
+ specifier.startsWith('.') ||
98
+ specifier.startsWith('/')) {
99
+ return false
100
+ }
101
+
102
+ // Valid URLs are not bare specifiers. (file:, http:, node:, etc.)
103
+
104
+ // eslint-disable-next-line no-prototype-builtins
105
+ if (URL.hasOwnProperty('canParse')) {
106
+ return !URL.canParse(specifier)
107
+ }
108
+
109
+ try {
110
+ // eslint-disable-next-line no-new
111
+ new URL(specifier)
112
+ return false
113
+ } catch (err) {
114
+ return true
115
+ }
116
+ }
106
117
 
107
118
  /**
108
- * Processes a module's exports and builds a set of new import statements,
109
- * namespace names, and setter blocks. If an export all export if encountered,
110
- * the target exports will be hoisted to the current module via a generated
111
- * namespace.
119
+ * Processes a module's exports and builds a set of setter blocks.
112
120
  *
113
121
  * @param {object} params
114
122
  * @param {string} params.srcUrl The full URL to the module to process.
115
123
  * @param {object} params.context Provided by the loaders API.
116
- * @param {Function} params.parentGetSource Provides the source code for the
117
- * parent module.
118
- * @param {string} [params.ns='namespace'] A string identifier that will be
119
- * used as the namespace for the identifiers exported by the module.
120
- * @param {string} [params.defaultAs='default'] The name to give the default
121
- * identifier exported by the module (if one exists). This is really only
122
- * useful in a recursive situation where a transitive module's default export
123
- * needs to be renamed to the name of the module.
124
+ * @param {Function} params.parentGetSource Provides the source code for the parent module.
125
+ * @param {bool} params.excludeDefault Exclude the default export.
124
126
  *
125
- * @returns {Promise<ProcessedModule>}
127
+ * @returns {Promise<Map<string, string>>} The shimmed setters for all the exports
128
+ * from the module and any transitive export all modules.
126
129
  */
127
- async function processModule ({
128
- srcUrl,
129
- context,
130
- parentGetSource,
131
- ns = 'namespace',
132
- defaultAs = 'default'
133
- }) {
134
- const exportNames = await getExports({
135
- url: srcUrl,
136
- context,
137
- parentLoad: parentGetSource,
138
- defaultAs
139
- })
140
- const imports = [`import * as ${ns} from ${JSON.stringify(srcUrl)}`]
141
- const namespaces = [ns]
142
-
143
- // As we iterate found module exports we will add setter code blocks
144
- // to this map that will eventually be inserted into the shim module's
145
- // source code. We utilize a map in order to prevent duplicate exports.
146
- // As a consequence of default renaming, it is possible that a file named
147
- // `foo.mjs` which has `export function foo() {}` and `export default foo`
148
- // exports will result in the "foo" export being defined twice in our shim.
149
- // The map allows us to avoid this situation at the cost of losing the
150
- // named export in favor of the default export.
130
+ async function processModule ({ srcUrl, context, parentGetSource, parentResolve, excludeDefault }) {
131
+ const exportNames = await getExports(srcUrl, context, parentGetSource)
132
+ const duplicates = new Set()
151
133
  const setters = new Map()
152
134
 
135
+ const addSetter = (name, setter) => {
136
+ // When doing an `import *` duplicates become undefined, so do the same
137
+ if (setters.has(name)) {
138
+ duplicates.add(name)
139
+ setters.delete(name)
140
+ } else if (!duplicates.has(name)) {
141
+ setters.set(name, setter)
142
+ }
143
+ }
144
+
153
145
  for (const n of exportNames) {
146
+ if (n === 'default' && excludeDefault) continue
147
+
154
148
  if (isStarExportLine(n) === true) {
155
149
  const [, modFile] = n.split('* from ')
156
- const normalizedModName = normalizeModName(modFile)
157
- const modUrl = new URL(modFile, srcUrl).toString()
158
- const modName = Buffer.from(modFile, 'hex') + Date.now() + randomBytes(4).toString('hex')
159
150
 
160
- const data = await processModule({
151
+ let modUrl
152
+ if (isBareSpecifier(modFile)) {
153
+ // Bare specifiers need to be resolved relative to the parent module.
154
+ const result = await parentResolve(modFile, { parentURL: srcUrl })
155
+ modUrl = result.url
156
+ } else {
157
+ modUrl = new URL(modFile, srcUrl).href
158
+ }
159
+
160
+ const setters = await processModule({
161
161
  srcUrl: modUrl,
162
162
  context,
163
163
  parentGetSource,
164
- ns: `$${modName}`,
165
- defaultAs: normalizedModName
164
+ excludeDefault: true
166
165
  })
167
- Array.prototype.push.apply(imports, data.imports)
168
- Array.prototype.push.apply(namespaces, data.namespaces)
169
- for (const [k, v] of data.setters.entries()) {
170
- setters.set(k, v)
166
+ for (const [name, setter] of setters.entries()) {
167
+ addSetter(name, setter)
171
168
  }
172
-
173
- continue
174
- }
175
-
176
- const matches = /^rename (.+) as (.+)$/.exec(n)
177
- if (matches !== null) {
178
- // Transitive modules that export a default identifier need to have
179
- // that identifier renamed to the name of module. And our shim setter
180
- // needs to utilize that new name while being initialized from the
181
- // corresponding origin namespace.
182
- const renamedExport = matches[2]
183
- setters.set(`$${renamedExport}${ns}`, `
184
- let $${renamedExport} = ${ns}.default
185
- export { $${renamedExport} as ${renamedExport} }
186
- set.${renamedExport} = (v) => {
187
- $${renamedExport} = v
169
+ } else {
170
+ addSetter(n, `
171
+ let $${n} = _.${n}
172
+ export { $${n} as ${n} }
173
+ set.${n} = (v) => {
174
+ $${n} = v
188
175
  return true
189
176
  }
190
177
  `)
191
- continue
192
- }
193
-
194
- setters.set(`$${n}` + ns, `
195
- let $${n} = ${ns}.${n}
196
- export { $${n} as ${n} }
197
- set.${n} = (v) => {
198
- $${n} = v
199
- return true
200
178
  }
201
- `)
202
179
  }
203
180
 
204
- return { imports, namespaces, setters }
205
- }
206
-
207
- /**
208
- * Given a module name, e.g. 'foo-bar' or './foo-bar.js', normalize it to a
209
- * string that is a valid JavaScript identifier, e.g. `fooBar`. Normalization
210
- * means converting kebab-case to camelCase while removing any path tokens and
211
- * file extensions.
212
- *
213
- * @param {string} name The module name to normalize.
214
- *
215
- * @returns {string} The normalized identifier.
216
- */
217
- function normalizeModName (name) {
218
- return name
219
- .split('/')
220
- .pop()
221
- .replace(/(.+)\.(?:js|mjs)$/, '$1')
222
- .replaceAll(/(-.)/g, x => x[1].toUpperCase())
181
+ return setters
223
182
  }
224
183
 
225
184
  function addIitm (url) {
@@ -229,7 +188,20 @@ function addIitm (url) {
229
188
  }
230
189
 
231
190
  function createHook (meta) {
191
+ let cachedResolve
192
+ const iitmURL = new URL('lib/register.js', meta.url).toString()
193
+
232
194
  async function resolve (specifier, context, parentResolve) {
195
+ cachedResolve = parentResolve
196
+
197
+ // See github.com/DataDog/import-in-the-middle/pull/76.
198
+ if (specifier === iitmURL) {
199
+ return {
200
+ url: specifier,
201
+ shortCircuit: true
202
+ }
203
+ }
204
+
233
205
  const { parentURL = '' } = context
234
206
  const newSpecifier = deleteIitm(specifier)
235
207
  if (isWin && parentURL.indexOf('file:node') === 0) {
@@ -253,6 +225,15 @@ function createHook (meta) {
253
225
  return url
254
226
  }
255
227
 
228
+ // If the file is referencing itself, we need to skip adding the iitm search params
229
+ if (url.url === parentURL) {
230
+ return {
231
+ url: url.url,
232
+ shortCircuit: true,
233
+ format: url.format
234
+ }
235
+ }
236
+
256
237
  specifiers.set(url.url, specifier)
257
238
 
258
239
  return {
@@ -262,64 +243,28 @@ function createHook (meta) {
262
243
  }
263
244
  }
264
245
 
265
- const iitmURL = new URL('lib/register.js', meta.url).toString()
266
246
  async function getSource (url, context, parentGetSource) {
267
247
  if (hasIitm(url)) {
268
248
  const realUrl = deleteIitm(url)
269
- const { imports, namespaces, setters: mapSetters } = await processModule({
249
+ const setters = await processModule({
270
250
  srcUrl: realUrl,
271
251
  context,
272
- parentGetSource
252
+ parentGetSource,
253
+ parentResolve: cachedResolve
273
254
  })
274
- const setters = Array.from(mapSetters.values())
275
-
276
- // When we encounter modules that re-export all identifiers from other
277
- // modules, it is possible that the transitive modules export a default
278
- // identifier. Due to us having to merge all transitive modules into a
279
- // single common namespace, we need to recognize these default exports
280
- // and remap them to a name based on the module name. This prevents us
281
- // from overriding the top-level module's (the one actually being imported
282
- // by some source code) default export when we merge the namespaces.
283
- const renamedDefaults = setters
284
- .map(s => {
285
- const matches = /let \$(.+) = (\$.+)\.default/.exec(s)
286
- if (matches === null) return undefined
287
- return `_['${matches[1]}'] = ${matches[2]}.default`
288
- })
289
- .filter(s => s)
290
-
291
- // The for loops are how we merge namespaces into a common namespace that
292
- // can be proxied. We can't use a simple `Object.assign` style merging
293
- // because transitive modules can export a default identifier that would
294
- // override the desired default identifier. So we need to do manual
295
- // merging with some logic around default identifiers.
296
- //
297
- // Additionally, we need to make sure any renamed default exports in
298
- // transitive dependencies are added to the common namespace. This is
299
- // accomplished through the `renamedDefaults` array.
300
255
  return {
301
256
  source: `
302
257
  import { register } from '${iitmURL}'
303
- ${imports.join('\n')}
258
+ import * as namespace from ${JSON.stringify(realUrl)}
304
259
 
305
- const namespaces = [${namespaces.join(', ')}]
306
260
  // Mimic a Module object (https://tc39.es/ecma262/#sec-module-namespace-objects).
307
- const _ = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } })
261
+ const _ = Object.assign(
262
+ Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } }),
263
+ namespace
264
+ )
308
265
  const set = {}
309
266
 
310
- const primary = namespaces.shift()
311
- for (const [k, v] of Object.entries(primary)) {
312
- _[k] = v
313
- }
314
- for (const ns of namespaces) {
315
- for (const [k, v] of Object.entries(ns)) {
316
- if (k === 'default') continue
317
- _[k] = v
318
- }
319
- }
320
-
321
- ${setters.join('\n')}
322
- ${renamedDefaults.join('\n')}
267
+ ${Array.from(setters.values()).join('\n')}
323
268
 
324
269
  register(${JSON.stringify(realUrl)}, _, set, ${JSON.stringify(specifiers.get(realUrl))})
325
270
  `
package/index.js CHANGED
@@ -89,5 +89,6 @@ Hook.prototype.unhook = function () {
89
89
  }
90
90
 
91
91
  module.exports = Hook
92
+ module.exports.Hook = Hook
92
93
  module.exports.addHook = addHook
93
94
  module.exports.removeHook = removeHook
@@ -18,30 +18,21 @@ function warn (txt) {
18
18
  * Utilizes an AST parser to interpret ESM source code and build a list of
19
19
  * exported identifiers. In the baseline case, the list of identifiers will be
20
20
  * the simple identifier names as written in the source code of the module.
21
- * However, there are some special cases:
21
+ * However, there is a special case:
22
22
  *
23
- * 1. When an `export * from './foo.js'` line is encountered it is rewritten
23
+ * When an `export * from './foo.js'` line is encountered it is rewritten
24
24
  * as `* from ./foo.js`. This allows the interpreting code to recognize a
25
25
  * transitive export and recursively parse the indicated module. The returned
26
26
  * identifier list will have "* from ./foo.js" as an item.
27
27
  *
28
- * 2. When `defaultAs` has a value other than 'default', the export line will
29
- * be rewritten as `rename <identifier> as <defaultAsValue>`. This rename string
30
- * will be an item in the returned identifier list.
31
- *
32
28
  * @param {object} params
33
29
  * @param {string} params.moduleSource The source code of the module to parse
34
30
  * and interpret.
35
- * @param {string} [defaultAs='default'] When anything other than 'default' any
36
- * `export default` lines will be rewritten utilizing the value provided. For
37
- * example, if a module 'foo-bar.js' has the line `export default foo` and the
38
- * value of this parameter is 'baz', then the export will be rewritten to
39
- * `rename foo as baz`.
40
31
  *
41
32
  * @returns {string[]} The identifiers exported by the module along with any
42
33
  * custom directives.
43
34
  */
44
- function getEsmExports ({ moduleSource, defaultAs = 'default' }) {
35
+ function getEsmExports (moduleSource) {
45
36
  const exportedNames = new Set()
46
37
  const tree = parser.parse(moduleSource, acornOpts)
47
38
  for (const node of tree.body) {
@@ -56,19 +47,7 @@ function getEsmExports ({ moduleSource, defaultAs = 'default' }) {
56
47
  break
57
48
 
58
49
  case 'ExportDefaultDeclaration': {
59
- if (defaultAs === 'default') {
60
- exportedNames.add('default')
61
- break
62
- }
63
-
64
- if (node.declaration.type.toLowerCase() === 'identifier') {
65
- // e.g. `export default foo`
66
- exportedNames.add(`rename ${node.declaration.name} as ${defaultAs}`)
67
- } else {
68
- // e.g. `export function foo () {}
69
- exportedNames.add(`rename ${node.declaration.id.name} as ${defaultAs}`)
70
- }
71
-
50
+ exportedNames.add('default')
72
51
  break
73
52
  }
74
53
 
@@ -20,13 +20,13 @@ async function getFullCjsExports (url, context, parentLoad, source) {
20
20
  const ex = getCjsExports(source)
21
21
  const full = Array.from(new Set([
22
22
  ...addDefault(ex.exports),
23
- ...(await Promise.all(ex.reexports.map(re => getExports(({
24
- url: (/^(..?($|\/|\\))/).test(re)
23
+ ...(await Promise.all(ex.reexports.map(re => getExports(
24
+ (/^(..?($|\/|\\))/).test(re)
25
25
  ? pathToFileURL(require.resolve(fileURLToPath(new URL(re, url)))).toString()
26
26
  : pathToFileURL(require.resolve(re)).toString(),
27
27
  context,
28
28
  parentLoad
29
- }))))).flat()
29
+ )))).flat()
30
30
  ]))
31
31
 
32
32
  urlsBeingProcessed.delete(url)
@@ -38,25 +38,18 @@ async function getFullCjsExports (url, context, parentLoad, source) {
38
38
  * source code for said module from the loader API, and parses the result
39
39
  * for the entities exported from that module.
40
40
  *
41
- * @param {object} params
42
- * @param {string} params.url A file URL string pointing to the module that
41
+ * @param {string} url A file URL string pointing to the module that
43
42
  * we should get the exports of.
44
- * @param {object} params.context Context object as provided by the `load`
43
+ * @param {object} context Context object as provided by the `load`
45
44
  * hook from the loaders API.
46
- * @param {Function} params.parentLoad Next hook function in the loaders API
45
+ * @param {Function} parentLoad Next hook function in the loaders API
47
46
  * hook chain.
48
- * @param {string} [defaultAs='default'] When anything other than 'default',
49
- * will trigger remapping of default exports in ESM source files to the
50
- * provided name. For example, if a submodule has `export default foo` and
51
- * 'myFoo' is provided for this parameter, the export line will be rewritten
52
- * to `rename foo as myFoo`. This is key to being able to support
53
- * `export * from 'something'` exports.
54
47
  *
55
48
  * @returns {Promise<string[]>} An array of identifiers exported by the module.
56
49
  * Please see {@link getEsmExports} for caveats on special identifiers that may
57
50
  * be included in the result set.
58
51
  */
59
- async function getExports ({ url, context, parentLoad, defaultAs = 'default' }) {
52
+ async function getExports (url, context, parentLoad) {
60
53
  // `parentLoad` gives us the possibility of getting the source
61
54
  // from an upstream loader. This doesn't always work though,
62
55
  // so later on we fall back to reading it from disk.
@@ -77,7 +70,7 @@ async function getExports ({ url, context, parentLoad, defaultAs = 'default' })
77
70
  }
78
71
 
79
72
  if (format === 'module') {
80
- return getEsmExports({ moduleSource: source, defaultAs })
73
+ return getEsmExports(source)
81
74
  }
82
75
  if (format === 'commonjs') {
83
76
  return getFullCjsExports(url, context, parentLoad, source)
@@ -85,7 +78,7 @@ async function getExports ({ url, context, parentLoad, defaultAs = 'default' })
85
78
 
86
79
  // At this point our `format` is either undefined or not known by us. Fall
87
80
  // back to parsing as ESM/CJS.
88
- const esmExports = getEsmExports({ moduleSource: source, defaultAs })
81
+ const esmExports = getEsmExports(source)
89
82
  if (!esmExports.length) {
90
83
  // TODO(bengl) it's might be possible to get here if somehow the format
91
84
  // isn't set at first and yet we have an ESM module with no exports.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "import-in-the-middle",
3
- "version": "1.7.4",
3
+ "version": "1.8.0",
4
4
  "description": "Intercept imports in Node.js",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -36,15 +36,19 @@
36
36
  "@babel/core": "^7.23.7",
37
37
  "@babel/eslint-parser": "^7.23.3",
38
38
  "@babel/plugin-syntax-import-assertions": "^7.23.3",
39
+ "@react-email/components": "^0.0.19",
39
40
  "@types/node": "^18.0.6",
40
41
  "c8": "^7.8.0",
42
+ "date-fns": "^3.6.0",
41
43
  "eslint": "^8.55.0",
42
44
  "eslint-config-standard": "^17.1.0",
43
45
  "eslint-plugin-import": "^2.29.0",
44
46
  "eslint-plugin-n": "^16.4.0",
45
47
  "eslint-plugin-node": "^11.1.0",
46
48
  "eslint-plugin-promise": "^6.1.1",
49
+ "got": "^14.3.0",
47
50
  "imhotap": "^2.1.0",
51
+ "openai": "^4.47.2",
48
52
  "ts-node": "^10.9.1",
49
53
  "typescript": "^4.7.4"
50
54
  },
package/test/README.md CHANGED
@@ -11,7 +11,7 @@ options (assuming they're run from the project root):
11
11
 
12
12
  ```
13
13
  --require ./test/version-check.js
14
- --experimental-loader ./test/generic-loader.mmjs
14
+ --experimental-loader ./test/generic-loader.mjs
15
15
  ```
16
16
 
17
17
  The entire test suite can be run with `npm test`.
@@ -0,0 +1,8 @@
1
+ // File generated from our OpenAPI spec by Stainless.
2
+ import * as BatchesAPI from './cyclical-self.mjs'
3
+ export class Batches {}
4
+ export class BatchesPage {}
5
+ (function (Batches) {
6
+ Batches.BatchesPage = BatchesAPI.BatchesPage
7
+ // eslint-disable-next-line no-class-assign
8
+ })(Batches || (Batches = {}))
@@ -0,0 +1,4 @@
1
+ export const foo = 'a'
2
+ export default function () {
3
+ return 'c'
4
+ }
@@ -0,0 +1 @@
1
+ export const foo = 'a'
@@ -0,0 +1,5 @@
1
+ export * from './duplicate-a.mjs'
2
+ export * from './duplicate-b.mjs'
3
+
4
+ const foo = 'foo'
5
+ export default foo
@@ -0,0 +1,2 @@
1
+ export * from './default-call-expression.mjs'
2
+ export { default as somethingElse } from './default-call-expression.mjs'
@@ -0,0 +1 @@
1
+ export default parseInt('1')
@@ -1,7 +1,3 @@
1
- // The purpose of this fixture is to replicate a situation where we may
2
- // end up exporting `default` twice: first from this script itself and second
3
- // via the `export * from` line.
4
- //
5
1
  // This replicates the way the in-the-wild `got` module does things:
6
2
  // https://github.com/sindresorhus/got/blob/3822412/source/index.ts
7
3
 
@@ -12,6 +8,4 @@ class got {
12
8
  export default got
13
9
  export { got }
14
10
  export * from './something.mjs'
15
- export * from './default-class.mjs'
16
- export * from './snake_case.mjs'
17
11
  export { default as renamedDefaultExport } from './lib/baz.mjs'
@@ -0,0 +1 @@
1
+ export * from 'some-external-module'
@@ -0,0 +1 @@
1
+ export * from 'some-external-module/sub'
@@ -15,7 +15,7 @@ fixture.split('\n').forEach(line => {
15
15
  if (expectedNames[0] === '') {
16
16
  expectedNames.length = 0
17
17
  }
18
- const names = getEsmExports({ moduleSource: mod })
18
+ const names = getEsmExports(mod)
19
19
  assert.deepEqual(expectedNames, names)
20
20
  console.log(`${mod}\n ✅ contains exports: ${testStr}`)
21
21
  })
@@ -0,0 +1,4 @@
1
+ import { Batches, BatchesPage } from '../fixtures/cyclical-self.mjs'
2
+ import { strictEqual } from 'assert'
3
+
4
+ strictEqual(Batches.BatchesPage, BatchesPage)
@@ -12,6 +12,8 @@ import gfn from '../fixtures/export-types/default-generator.mjs'
12
12
  import afn from '../fixtures/export-types/default-function-anon.mjs'
13
13
  import acn from '../fixtures/export-types/default-class-anon.mjs'
14
14
  import agfn from '../fixtures/export-types/default-generator-anon.mjs'
15
+ import callEx from '../fixtures/export-types/default-call-expression.mjs'
16
+ import { somethingElse } from '../fixtures/export-types/default-call-expression-renamed.mjs'
15
17
  import defaultImportExport from '../fixtures/export-types/import-default-export.mjs'
16
18
  import varDefaultExport from '../fixtures/export-types/variable-default-export.mjs'
17
19
  import { strictEqual } from 'assert'
@@ -61,6 +63,10 @@ Hook((exports, name) => {
61
63
  exports.default = function () {
62
64
  return orig4() + 1
63
65
  }
66
+ } else if (name.match(/default-call-expression\.m?js/)) {
67
+ exports.default += 1
68
+ } else if (name.match(/default-call-expression-renamed\.m?js/)) {
69
+ exports.somethingElse += 1
64
70
  }
65
71
  })
66
72
 
@@ -76,3 +82,5 @@ strictEqual(new acn().getFoo(), 2)
76
82
  strictEqual(agfn().next().value, 2)
77
83
  strictEqual(n, 2)
78
84
  strictEqual(s, 'dogdawg')
85
+ strictEqual(callEx, 2)
86
+ strictEqual(somethingElse, 2)
@@ -0,0 +1,19 @@
1
+ import * as lib from '../fixtures/duplicate.mjs'
2
+ import { notEqual, strictEqual } from 'assert'
3
+ import Hook from '../../index.js'
4
+
5
+ Hook((exports, name) => {
6
+ if (name.match(/duplicate\.mjs/)) {
7
+ // foo should not be exported because there are duplicate exports
8
+ strictEqual(exports.foo, undefined)
9
+ // default should be exported
10
+ strictEqual(exports.default, 'foo')
11
+ }
12
+ })
13
+
14
+ notEqual(lib, undefined)
15
+
16
+ // foo should not be exported because there are duplicate exports
17
+ strictEqual(lib.foo, undefined)
18
+ // default should be exported
19
+ strictEqual(lib.default, 'foo')
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
4
4
 
5
- const Hook = require('../../index.js')
5
+ const { Hook } = require('../../index.js')
6
6
  const { strictEqual } = require('assert')
7
7
 
8
8
  Hook((exports, name) => {
@@ -0,0 +1,17 @@
1
+ import Hook from '../../index.js'
2
+ import { foo } from '../fixtures/re-export-star-external.mjs'
3
+ import { bar } from '../fixtures/sub-directory/re-export-star-external.mjs'
4
+ import { strictEqual } from 'assert'
5
+
6
+ Hook((exports, name) => {
7
+ if (name.endsWith('fixtures/re-export-star-external.mjs')) {
8
+ exports.foo = '1'
9
+ }
10
+
11
+ if (name.endsWith('fixtures/sub-directory/re-export-star-external.mjs')) {
12
+ exports.bar = '2'
13
+ }
14
+ })
15
+
16
+ strictEqual(foo, '1')
17
+ strictEqual(bar, '2')
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
4
4
 
5
- import Hook from '../../index.js'
5
+ import { Hook } from '../../index.js'
6
6
  import barMjs from '../fixtures/something.mjs'
7
7
  import barJs from '../fixtures/something.js'
8
8
  import { strictEqual } from 'assert'
@@ -0,0 +1,10 @@
1
+ import { format, parse } from 'date-fns'
2
+ import Hook from '../../index.js'
3
+
4
+ Hook((exports, name) => {
5
+ if (name === 'date-fns') {
6
+ //
7
+ }
8
+ })
9
+
10
+ console.assert(format, parse)
@@ -0,0 +1,16 @@
1
+ import * as lib from '@react-email/components'
2
+ import { Heading } from '@react-email/components'
3
+ import Hook from '../../index.js'
4
+ import { strictEqual } from 'assert'
5
+
6
+ Hook((exports, name) => {
7
+ if (name.match(/@react-email\/components/)) {
8
+ exports.Heading = function wrappedHeading () {
9
+ return 'heading-wrapped'
10
+ }
11
+ }
12
+ })
13
+
14
+ strictEqual(typeof lib.Button, 'function')
15
+ strictEqual(lib.Heading(), 'heading-wrapped')
16
+ strictEqual(Heading(), 'heading-wrapped')
@@ -0,0 +1,16 @@
1
+ import got, { Options } from 'got'
2
+ import { strictEqual } from 'assert'
3
+ import Hook from '../../index.js'
4
+
5
+ Hook((exports, name) => {
6
+ if (name === 'got' && 'Options' in exports) {
7
+ exports.Options = 'nothing'
8
+ }
9
+ })
10
+
11
+ strictEqual(typeof got, 'function')
12
+ strictEqual(typeof got.post, 'function')
13
+ strictEqual(typeof got.stream, 'function')
14
+ strictEqual(typeof got.extend, 'function')
15
+
16
+ strictEqual(Options, 'nothing')
@@ -0,0 +1,10 @@
1
+ import OpenAI from 'openai'
2
+ import Hook from '../../index.js'
3
+
4
+ Hook((exports, name) => {
5
+ if (name === 'openai') {
6
+ console.assert(name, exports)
7
+ }
8
+ })
9
+
10
+ console.assert(OpenAI)
@@ -3,10 +3,8 @@ import Hook from '../../index.js'
3
3
  Hook((exports, name) => {
4
4
  if (/got-alike\.mjs/.test(name) === false) return
5
5
 
6
- const bar = exports.something
7
- exports.something = function barWrapped () {
8
- return bar() + '-wrapped'
9
- }
6
+ const foo = exports.foo
7
+ exports.foo = foo + '-wrapped'
10
8
 
11
9
  const renamedDefaultExport = exports.renamedDefaultExport
12
10
  exports.renamedDefaultExport = function bazWrapped () {
@@ -15,21 +13,14 @@ Hook((exports, name) => {
15
13
  })
16
14
 
17
15
  /* eslint-disable import/no-named-default */
18
- /* eslint-disable camelcase */
19
16
  import {
20
17
  default as Got,
21
- something,
22
- defaultClass as DefaultClass,
23
- snake_case,
18
+ foo,
24
19
  renamedDefaultExport
25
20
  } from '../fixtures/got-alike.mjs'
26
21
 
27
- strictEqual(something(), '42-wrapped')
22
+ strictEqual(foo, '42-wrapped')
28
23
  const got = new Got()
29
24
  strictEqual(got.foo, 'foo')
30
25
 
31
- const dc = new DefaultClass()
32
- strictEqual(dc.value, 'DefaultClass')
33
-
34
- strictEqual(snake_case, 'snake_case')
35
26
  strictEqual(renamedDefaultExport(), 'baz-wrapped')
@@ -0,0 +1,6 @@
1
+ import { register } from 'node:module'
2
+
3
+ register('./typescript-loader.mjs', import.meta.url)
4
+ register('../../hook.mjs', import.meta.url)
5
+
6
+ await import('node:util')
@@ -0,0 +1,26 @@
1
+ /**
2
+ * This simulates what something like `tsx` (https://github.com/privatenumber/tsx)
3
+ * will do: it will try to resolve a URL with a `.js` extension to a `.ts` extension.
4
+ *
5
+ * Combined with the test case in the adjacent `multiple-loaders.test.mjs` file,
6
+ * this forces `import-in-the-middle` into what used to be a failure state: where
7
+ * `context.parentURL` is a `node:*` specifier and the `specifier` refers to a file
8
+ * that does not exist.
9
+ *
10
+ * See https://github.com/nodejs/node/issues/52987 for more details.
11
+ */
12
+ export async function resolve (specifier, context, defaultResolve) {
13
+ if (!specifier.endsWith('.js') && !specifier.endsWith('.mjs')) {
14
+ return await defaultResolve(specifier, context)
15
+ }
16
+
17
+ try {
18
+ return await defaultResolve(specifier.replace(/\.m?js/, '.ts'), context)
19
+ } catch (err) {
20
+ if (err.code !== 'ERR_MODULE_NOT_FOUND') {
21
+ throw err
22
+ }
23
+
24
+ return await defaultResolve(specifier, context)
25
+ }
26
+ }
@@ -12,10 +12,11 @@ process.env.IITM_TEST_FILE = filename
12
12
 
13
13
  const [processMajor, processMinor] = process.versions.node.split('.').map(Number)
14
14
 
15
- const match = filename.match(/v([0-9]+)(?:\.([0-9]+))?/)
15
+ const match = filename.match(/v([0-9]+)(?:\.([0-9]+))?(?:-v([0-9]+))?/)
16
16
 
17
17
  const majorRequirement = match ? match[1] : 0
18
18
  const minorRequirement = match && match[2]
19
+ const majorMax = match ? match[3] : Infinity
19
20
 
20
21
  if (processMajor < majorRequirement) {
21
22
  console.log(`skipping ${filename} as this is Node.js v${processMajor} and test wants v${majorRequirement}`)
@@ -25,3 +26,8 @@ if (processMajor <= majorRequirement && processMinor < minorRequirement) {
25
26
  console.log(`skipping ${filename} as this is Node.js v${processMajor}.${processMinor} and test wants >=v${majorRequirement}.${minorRequirement}`)
26
27
  process.exit(0)
27
28
  }
29
+
30
+ if (processMajor > majorMax) {
31
+ console.log(`skipping ${filename} as this is Node.js v${processMajor} and test wants <=v${majorMax}`)
32
+ process.exit(0)
33
+ }
package/fix.patch DELETED
@@ -1,52 +0,0 @@
1
- From 5a9ae09d53e43a30709c7162fb351f3a57626f79 Mon Sep 17 00:00:00 2001
2
- From: Bryan English <bryan.english@datadoghq.com>
3
- Date: Fri, 21 Jul 2023 11:50:34 -0400
4
- Subject: [PATCH] sanitize URLs
5
-
6
- ---
7
- hook.js | 4 ++--
8
- test/low-level/sanitized-url.mjs | 11 +++++++++++
9
- 2 files changed, 13 insertions(+), 2 deletions(-)
10
- create mode 100644 test/low-level/sanitized-url.mjs
11
-
12
- diff --git a/hook.js b/hook.js
13
- index 884ee3a..3639fbf 100644
14
- --- a/hook.js
15
- +++ b/hook.js
16
- @@ -122,7 +122,7 @@ function createHook (meta) {
17
- return {
18
- source: `
19
- import { register } from '${iitmURL}'
20
- -import * as namespace from '${url}'
21
- +import * as namespace from ${JSON.stringify(url)}
22
- const set = {}
23
- ${exportNames.map((n) => `
24
- let $${n} = namespace.${n}
25
- @@ -132,7 +132,7 @@ set.${n} = (v) => {
26
- return true
27
- }
28
- `).join('\n')}
29
- -register('${realUrl}', namespace, set, '${specifiers.get(realUrl)}')
30
- +register(${JSON.stringify(realUrl)}, namespace, set, ${JSON.stringify(specifiers.get(realUrl))})
31
- `
32
- }
33
- }
34
- diff --git a/test/low-level/sanitized-url.mjs b/test/low-level/sanitized-url.mjs
35
- new file mode 100644
36
- index 0000000..6fe5e81
37
- --- /dev/null
38
- +++ b/test/low-level/sanitized-url.mjs
39
- @@ -0,0 +1,11 @@
40
- +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License.
41
- +//
42
- +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
43
- +
44
- +import { addHook } from '../../index.js'
45
- +
46
- +addHook(() => {})
47
- +
48
- +;(async () => {
49
- + await import("../fixtures/something.mjs#*/'/*';eval('process.exit\x281\x29\x0A')")
50
- +})()
51
- --
52
- 2.39.0
@@ -1,3 +0,0 @@
1
- export default class DefaultClass {
2
- value = 'DefaultClass'
3
- }
@@ -1,2 +0,0 @@
1
- const snakeCase = 'snake_case'
2
- export default snakeCase