import-in-the-middle 1.7.4 → 1.8.1
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/README.md +1 -1
- package/hook.js +139 -155
- package/index.d.ts +1 -1
- package/index.js +1 -0
- package/lib/get-esm-exports.js +6 -27
- package/lib/get-exports.js +60 -43
- package/package.json +5 -1
- package/test/README.md +1 -1
- package/test/fixtures/cyclical-self.mjs +8 -0
- package/test/fixtures/duplicate-a.mjs +4 -0
- package/test/fixtures/duplicate-b.mjs +1 -0
- package/test/fixtures/duplicate-c.mjs +1 -0
- package/test/fixtures/duplicate-explicit.mjs +5 -0
- package/test/fixtures/duplicate.mjs +5 -0
- package/test/fixtures/export-types/default-call-expression-renamed.mjs +2 -0
- package/test/fixtures/export-types/default-call-expression.mjs +1 -0
- package/test/fixtures/got-alike.mjs +0 -6
- package/test/fixtures/json-attributes.mjs +5 -0
- package/test/fixtures/re-export-cjs-built-in.js +1 -0
- package/test/fixtures/re-export-cjs.js +1 -0
- package/test/fixtures/re-export-star-external.mjs +1 -0
- package/test/fixtures/sub-directory/re-export-star-external.mjs +1 -0
- package/test/get-esm-exports/v20-get-esm-exports.js +1 -1
- package/test/hook/cyclical-self.mjs +4 -0
- package/test/hook/default-export.mjs +8 -0
- package/test/hook/duplicate-exports-explicit.mjs +13 -0
- package/test/hook/duplicate-exports.mjs +19 -0
- package/test/hook/dynamic-import.js +1 -1
- package/test/hook/re-export-cjs.mjs +19 -0
- package/test/hook/re-export-star-module.mjs +17 -0
- package/test/hook/static-import-default.mjs +1 -1
- package/test/hook/v14-date-fns.mjs +10 -0
- package/test/hook/v14-react-email-components.mjs +16 -0
- package/test/hook/v16-got.mjs +16 -0
- package/test/hook/v18.19-openai.mjs +16 -0
- package/test/hook/v18.19-static-import-gotalike.mjs +4 -13
- package/test/hook/v20.10-static-import-attributes.mjs +17 -0
- package/test/multiple-loaders/multiple-loaders.test.mjs +6 -0
- package/test/multiple-loaders/typescript-loader.mjs +26 -0
- package/test/other/v20.10-import-executable.mjs +8 -0
- package/test/typescript/ts-node.test.mts +10 -2
- package/test/version-check.js +7 -1
- package/fix.patch +0 -52
- package/test/fixtures/default-class.mjs +0 -3
- package/test/fixtures/snake_case.mjs +0 -2
- package/test/other/import-executable.mjs +0 -19
- /package/test/{other → fixtures}/executable +0 -0
- /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 {
|
|
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 = (
|
|
22
|
+
getExports = (url) => import(url).then(Object.keys)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function hasIitm (url) {
|
|
@@ -91,135 +91,112 @@ function isStarExportLine (line) {
|
|
|
91
91
|
return /^\* from /.test(line)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
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
|
-
*
|
|
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<
|
|
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
|
-
|
|
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 starExports = new Set()
|
|
151
133
|
const setters = new Map()
|
|
152
134
|
|
|
135
|
+
const addSetter = (name, setter, isStarExport = false) => {
|
|
136
|
+
if (setters.has(name)) {
|
|
137
|
+
if (isStarExport) {
|
|
138
|
+
// If there's already a matching star export, delete it
|
|
139
|
+
if (starExports.has(name)) {
|
|
140
|
+
setters.delete(name)
|
|
141
|
+
}
|
|
142
|
+
// and return so this is excluded
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// if we already have this export but it is from a * export, overwrite it
|
|
147
|
+
if (starExports.has(name)) {
|
|
148
|
+
starExports.delete(name)
|
|
149
|
+
setters.set(name, setter)
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
// Store export * exports so we know they can be overridden by explicit
|
|
153
|
+
// named exports
|
|
154
|
+
if (isStarExport) {
|
|
155
|
+
starExports.add(name)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
setters.set(name, setter)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
153
162
|
for (const n of exportNames) {
|
|
163
|
+
if (n === 'default' && excludeDefault) continue
|
|
164
|
+
|
|
154
165
|
if (isStarExportLine(n) === true) {
|
|
155
166
|
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
167
|
|
|
160
|
-
|
|
168
|
+
let modUrl
|
|
169
|
+
if (isBareSpecifier(modFile)) {
|
|
170
|
+
// Bare specifiers need to be resolved relative to the parent module.
|
|
171
|
+
const result = await parentResolve(modFile, { parentURL: srcUrl })
|
|
172
|
+
modUrl = result.url
|
|
173
|
+
} else {
|
|
174
|
+
modUrl = new URL(modFile, srcUrl).href
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const setters = await processModule({
|
|
161
178
|
srcUrl: modUrl,
|
|
162
179
|
context,
|
|
163
180
|
parentGetSource,
|
|
164
|
-
|
|
165
|
-
|
|
181
|
+
parentResolve,
|
|
182
|
+
excludeDefault: true
|
|
166
183
|
})
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
for (const [k, v] of data.setters.entries()) {
|
|
170
|
-
setters.set(k, v)
|
|
184
|
+
for (const [name, setter] of setters.entries()) {
|
|
185
|
+
addSetter(name, setter, true)
|
|
171
186
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
|
187
|
+
} else {
|
|
188
|
+
addSetter(n, `
|
|
189
|
+
let $${n} = _.${n}
|
|
190
|
+
export { $${n} as ${n} }
|
|
191
|
+
set.${n} = (v) => {
|
|
192
|
+
$${n} = v
|
|
188
193
|
return true
|
|
189
194
|
}
|
|
190
195
|
`)
|
|
191
|
-
continue
|
|
192
196
|
}
|
|
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
|
-
}
|
|
201
|
-
`)
|
|
202
197
|
}
|
|
203
198
|
|
|
204
|
-
return
|
|
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())
|
|
199
|
+
return setters
|
|
223
200
|
}
|
|
224
201
|
|
|
225
202
|
function addIitm (url) {
|
|
@@ -229,7 +206,20 @@ function addIitm (url) {
|
|
|
229
206
|
}
|
|
230
207
|
|
|
231
208
|
function createHook (meta) {
|
|
209
|
+
let cachedResolve
|
|
210
|
+
const iitmURL = new URL('lib/register.js', meta.url).toString()
|
|
211
|
+
|
|
232
212
|
async function resolve (specifier, context, parentResolve) {
|
|
213
|
+
cachedResolve = parentResolve
|
|
214
|
+
|
|
215
|
+
// See github.com/DataDog/import-in-the-middle/pull/76.
|
|
216
|
+
if (specifier === iitmURL) {
|
|
217
|
+
return {
|
|
218
|
+
url: specifier,
|
|
219
|
+
shortCircuit: true
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
233
223
|
const { parentURL = '' } = context
|
|
234
224
|
const newSpecifier = deleteIitm(specifier)
|
|
235
225
|
if (isWin && parentURL.indexOf('file:node') === 0) {
|
|
@@ -253,6 +243,15 @@ function createHook (meta) {
|
|
|
253
243
|
return url
|
|
254
244
|
}
|
|
255
245
|
|
|
246
|
+
// If the file is referencing itself, we need to skip adding the iitm search params
|
|
247
|
+
if (url.url === parentURL) {
|
|
248
|
+
return {
|
|
249
|
+
url: url.url,
|
|
250
|
+
shortCircuit: true,
|
|
251
|
+
format: url.format
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
256
255
|
specifiers.set(url.url, specifier)
|
|
257
256
|
|
|
258
257
|
return {
|
|
@@ -262,67 +261,52 @@ function createHook (meta) {
|
|
|
262
261
|
}
|
|
263
262
|
}
|
|
264
263
|
|
|
265
|
-
const iitmURL = new URL('lib/register.js', meta.url).toString()
|
|
266
264
|
async function getSource (url, context, parentGetSource) {
|
|
267
265
|
if (hasIitm(url)) {
|
|
268
266
|
const realUrl = deleteIitm(url)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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`
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const setters = await processModule({
|
|
270
|
+
srcUrl: realUrl,
|
|
271
|
+
context,
|
|
272
|
+
parentGetSource,
|
|
273
|
+
parentResolve: cachedResolve
|
|
288
274
|
})
|
|
289
|
-
|
|
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
|
-
return {
|
|
301
|
-
source: `
|
|
275
|
+
return {
|
|
276
|
+
source: `
|
|
302
277
|
import { register } from '${iitmURL}'
|
|
303
|
-
${
|
|
278
|
+
import * as namespace from ${JSON.stringify(realUrl)}
|
|
304
279
|
|
|
305
|
-
const namespaces = [${namespaces.join(', ')}]
|
|
306
280
|
// Mimic a Module object (https://tc39.es/ecma262/#sec-module-namespace-objects).
|
|
307
|
-
const _ = Object.
|
|
281
|
+
const _ = Object.assign(
|
|
282
|
+
Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } }),
|
|
283
|
+
namespace
|
|
284
|
+
)
|
|
308
285
|
const set = {}
|
|
309
286
|
|
|
310
|
-
|
|
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')}
|
|
287
|
+
${Array.from(setters.values()).join('\n')}
|
|
323
288
|
|
|
324
289
|
register(${JSON.stringify(realUrl)}, _, set, ${JSON.stringify(specifiers.get(realUrl))})
|
|
325
290
|
`
|
|
291
|
+
}
|
|
292
|
+
} catch (cause) {
|
|
293
|
+
// If there are other ESM loader hooks registered as well as iitm,
|
|
294
|
+
// depending on the order they are registered, source might not be
|
|
295
|
+
// JavaScript.
|
|
296
|
+
//
|
|
297
|
+
// If we fail to parse a module for exports, we should fall back to the
|
|
298
|
+
// parent loader. These modules will not be wrapped with proxies and
|
|
299
|
+
// cannot be Hook'ed but at least this does not take down the entire app
|
|
300
|
+
// and block iitm from being used.
|
|
301
|
+
//
|
|
302
|
+
// We log the error because there might be bugs in iitm and without this
|
|
303
|
+
// it would be very tricky to debug
|
|
304
|
+
const err = new Error(`'import-in-the-middle' failed to wrap '${realUrl}'`)
|
|
305
|
+
err.cause = cause
|
|
306
|
+
console.warn(err)
|
|
307
|
+
|
|
308
|
+
// Revert back to the non-iitm URL
|
|
309
|
+
url = realUrl
|
|
326
310
|
}
|
|
327
311
|
}
|
|
328
312
|
|
package/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export type Options = {
|
|
|
27
27
|
internals?: boolean
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
declare class Hook {
|
|
30
|
+
export declare class Hook {
|
|
31
31
|
/**
|
|
32
32
|
* Creates a hook to be run on any already loaded modules and any that will
|
|
33
33
|
* be loaded in the future. It will be run once per loaded module. If
|
package/index.js
CHANGED
package/lib/get-esm-exports.js
CHANGED
|
@@ -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
|
|
21
|
+
* However, there is a special case:
|
|
22
22
|
*
|
|
23
|
-
*
|
|
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
|
-
* @returns {string
|
|
32
|
+
* @returns {Set<string>} The identifiers exported by the module along with any
|
|
42
33
|
* custom directives.
|
|
43
34
|
*/
|
|
44
|
-
function getEsmExports (
|
|
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
|
-
|
|
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
|
|
|
@@ -83,7 +62,7 @@ function getEsmExports ({ moduleSource, defaultAs = 'default' }) {
|
|
|
83
62
|
warn('unrecognized export type: ' + node.type)
|
|
84
63
|
}
|
|
85
64
|
}
|
|
86
|
-
return
|
|
65
|
+
return exportedNames
|
|
87
66
|
}
|
|
88
67
|
|
|
89
68
|
function parseDeclaration (node, exportedNames) {
|
package/lib/get-exports.js
CHANGED
|
@@ -1,36 +1,60 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const getEsmExports = require('./get-esm-exports.js')
|
|
4
|
-
const { parse:
|
|
5
|
-
const
|
|
4
|
+
const { parse: parseCjs } = require('cjs-module-lexer')
|
|
5
|
+
const { readFileSync } = require('fs')
|
|
6
|
+
const { builtinModules } = require('module')
|
|
6
7
|
const { fileURLToPath, pathToFileURL } = require('url')
|
|
8
|
+
const { dirname } = require('path')
|
|
7
9
|
|
|
8
10
|
function addDefault (arr) {
|
|
9
|
-
return
|
|
11
|
+
return new Set(['default', ...arr])
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Cached exports for Node built-in modules
|
|
15
|
+
const BUILT_INS = new Map()
|
|
16
|
+
|
|
17
|
+
function getExportsForNodeBuiltIn (name) {
|
|
18
|
+
let exports = BUILT_INS.get()
|
|
19
|
+
|
|
20
|
+
if (!exports) {
|
|
21
|
+
exports = new Set(addDefault(Object.keys(require(name))))
|
|
22
|
+
BUILT_INS.set(name, exports)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return exports
|
|
10
26
|
}
|
|
11
27
|
|
|
12
28
|
const urlsBeingProcessed = new Set() // Guard against circular imports.
|
|
13
29
|
|
|
14
|
-
async function
|
|
30
|
+
async function getCjsExports (url, context, parentLoad, source) {
|
|
15
31
|
if (urlsBeingProcessed.has(url)) {
|
|
16
32
|
return []
|
|
17
33
|
}
|
|
18
34
|
urlsBeingProcessed.add(url)
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
try {
|
|
37
|
+
const result = parseCjs(source)
|
|
38
|
+
const full = addDefault(result.exports)
|
|
39
|
+
|
|
40
|
+
await Promise.all(result.reexports.map(async re => {
|
|
41
|
+
if (re.startsWith('node:') || builtinModules.includes(re)) {
|
|
42
|
+
for (const each of getExportsForNodeBuiltIn(re)) {
|
|
43
|
+
full.add(each)
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
// Resolve the re-exported module relative to the current module.
|
|
47
|
+
const newUrl = pathToFileURL(require.resolve(re, { paths: [dirname(fileURLToPath(url))] })).href
|
|
48
|
+
for (const each of await getExports(newUrl, context, parentLoad)) {
|
|
49
|
+
full.add(each)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
return full
|
|
55
|
+
} finally {
|
|
56
|
+
urlsBeingProcessed.delete(url)
|
|
57
|
+
}
|
|
34
58
|
}
|
|
35
59
|
|
|
36
60
|
/**
|
|
@@ -38,25 +62,18 @@ async function getFullCjsExports (url, context, parentLoad, source) {
|
|
|
38
62
|
* source code for said module from the loader API, and parses the result
|
|
39
63
|
* for the entities exported from that module.
|
|
40
64
|
*
|
|
41
|
-
* @param {
|
|
42
|
-
* @param {string} params.url A file URL string pointing to the module that
|
|
65
|
+
* @param {string} url A file URL string pointing to the module that
|
|
43
66
|
* we should get the exports of.
|
|
44
|
-
* @param {object}
|
|
67
|
+
* @param {object} context Context object as provided by the `load`
|
|
45
68
|
* hook from the loaders API.
|
|
46
|
-
* @param {Function}
|
|
69
|
+
* @param {Function} parentLoad Next hook function in the loaders API
|
|
47
70
|
* 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
71
|
*
|
|
55
|
-
* @returns {Promise<string
|
|
72
|
+
* @returns {Promise<Set<string>>} An array of identifiers exported by the module.
|
|
56
73
|
* Please see {@link getEsmExports} for caveats on special identifiers that may
|
|
57
74
|
* be included in the result set.
|
|
58
75
|
*/
|
|
59
|
-
async function getExports (
|
|
76
|
+
async function getExports (url, context, parentLoad) {
|
|
60
77
|
// `parentLoad` gives us the possibility of getting the source
|
|
61
78
|
// from an upstream loader. This doesn't always work though,
|
|
62
79
|
// so later on we fall back to reading it from disk.
|
|
@@ -64,34 +81,34 @@ async function getExports ({ url, context, parentLoad, defaultAs = 'default' })
|
|
|
64
81
|
let source = parentCtx.source
|
|
65
82
|
const format = parentCtx.format
|
|
66
83
|
|
|
67
|
-
// TODO support non-node/file urls somehow?
|
|
68
|
-
if (format === 'builtin') {
|
|
69
|
-
// Builtins don't give us the source property, so we're stuck
|
|
70
|
-
// just requiring it to get the exports.
|
|
71
|
-
return addDefault(Object.keys(require(url)))
|
|
72
|
-
}
|
|
73
|
-
|
|
74
84
|
if (!source) {
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
if (format === 'builtin') {
|
|
86
|
+
// Builtins don't give us the source property, so we're stuck
|
|
87
|
+
// just requiring it to get the exports.
|
|
88
|
+
return getExportsForNodeBuiltIn(url)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Sometimes source is retrieved by parentLoad, CommonJs isn't.
|
|
92
|
+
source = readFileSync(fileURLToPath(url), 'utf8')
|
|
77
93
|
}
|
|
78
94
|
|
|
79
95
|
if (format === 'module') {
|
|
80
|
-
return getEsmExports(
|
|
96
|
+
return getEsmExports(source)
|
|
81
97
|
}
|
|
98
|
+
|
|
82
99
|
if (format === 'commonjs') {
|
|
83
|
-
return
|
|
100
|
+
return getCjsExports(url, context, parentLoad, source)
|
|
84
101
|
}
|
|
85
102
|
|
|
86
103
|
// At this point our `format` is either undefined or not known by us. Fall
|
|
87
104
|
// back to parsing as ESM/CJS.
|
|
88
|
-
const esmExports = getEsmExports(
|
|
105
|
+
const esmExports = getEsmExports(source)
|
|
89
106
|
if (!esmExports.length) {
|
|
90
107
|
// TODO(bengl) it's might be possible to get here if somehow the format
|
|
91
108
|
// isn't set at first and yet we have an ESM module with no exports.
|
|
92
109
|
// I couldn't construct an example that would do this, so maybe it's
|
|
93
110
|
// impossible?
|
|
94
|
-
return
|
|
111
|
+
return getCjsExports(url, context, parentLoad, source)
|
|
95
112
|
}
|
|
96
113
|
}
|
|
97
114
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "import-in-the-middle",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
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.
|
|
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 @@
|
|
|
1
|
+
export const foo = 'b'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const foo = 'c'
|
|
@@ -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
|
+
module.exports = require('util').deprecate
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('some-external-cjs-module').foo
|
|
@@ -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(
|
|
18
|
+
const names = Array.from(getEsmExports(mod))
|
|
19
19
|
assert.deepEqual(expectedNames, names)
|
|
20
20
|
console.log(`${mod}\n ✅ contains exports: ${testStr}`)
|
|
21
21
|
})
|
|
@@ -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,13 @@
|
|
|
1
|
+
import * as lib from '../fixtures/duplicate-explicit.mjs'
|
|
2
|
+
import { strictEqual } from 'assert'
|
|
3
|
+
import Hook from '../../index.js'
|
|
4
|
+
|
|
5
|
+
Hook((exports, name) => {
|
|
6
|
+
if (name.endsWith('duplicate-explicit.mjs')) {
|
|
7
|
+
strictEqual(exports.foo, 'c')
|
|
8
|
+
exports.foo += '-wrapped'
|
|
9
|
+
}
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
// foo should not be exported because there are duplicate exports
|
|
13
|
+
strictEqual(lib.foo, 'c-wrapped')
|
|
@@ -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('foo' in exports, false)
|
|
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('foo' in lib, false)
|
|
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,19 @@
|
|
|
1
|
+
import Hook from '../../index.js'
|
|
2
|
+
import foo from '../fixtures/re-export-cjs-built-in.js'
|
|
3
|
+
import foo2 from '../fixtures/re-export-cjs.js'
|
|
4
|
+
import { strictEqual } from 'assert'
|
|
5
|
+
|
|
6
|
+
Hook((exports, name) => {
|
|
7
|
+
if (name.endsWith('fixtures/re-export-cjs-built-in.js')) {
|
|
8
|
+
strictEqual(typeof exports.default, 'function')
|
|
9
|
+
exports.default = '1'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (name.endsWith('fixtures/re-export-cjs.js')) {
|
|
13
|
+
strictEqual(exports.default, 'bar')
|
|
14
|
+
exports.default = '2'
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
strictEqual(foo, '1')
|
|
19
|
+
strictEqual(foo2, '2')
|
|
@@ -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,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,16 @@
|
|
|
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)
|
|
11
|
+
|
|
12
|
+
const openAI = new OpenAI({
|
|
13
|
+
apiKey: 'doesnt-matter'
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
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
|
|
7
|
-
exports.
|
|
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
|
-
|
|
22
|
-
defaultClass as DefaultClass,
|
|
23
|
-
snake_case,
|
|
18
|
+
foo,
|
|
24
19
|
renamedDefaultExport
|
|
25
20
|
} from '../fixtures/got-alike.mjs'
|
|
26
21
|
|
|
27
|
-
strictEqual(
|
|
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,17 @@
|
|
|
1
|
+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License.
|
|
2
|
+
//
|
|
3
|
+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
|
|
4
|
+
|
|
5
|
+
import jsonMjs from '../fixtures/json-attributes.mjs'
|
|
6
|
+
import { strictEqual } from 'assert'
|
|
7
|
+
|
|
8
|
+
// Acorn does not support import attributes so an error is logged but the import
|
|
9
|
+
// still works!
|
|
10
|
+
//
|
|
11
|
+
// Hook((exports, name) => {
|
|
12
|
+
// if (name.match(/json\.mjs/)) {
|
|
13
|
+
// exports.default.data += '-dawg'
|
|
14
|
+
// }
|
|
15
|
+
// })
|
|
16
|
+
|
|
17
|
+
strictEqual(jsonMjs.data, 'dog')
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License.
|
|
2
|
+
//
|
|
3
|
+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
|
|
4
|
+
|
|
5
|
+
(async () => {
|
|
6
|
+
const lib = await import('../fixtures/executable')
|
|
7
|
+
console.assert(lib)
|
|
8
|
+
})()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
|
-
import { addHook } from '../../index.js'
|
|
2
|
+
import defaultHook, { Hook, addHook } from '../../index.js'
|
|
3
3
|
import { sayHi } from '../fixtures/say-hi.mjs'
|
|
4
4
|
|
|
5
5
|
addHook((url, exported) => {
|
|
@@ -8,4 +8,12 @@ addHook((url, exported) => {
|
|
|
8
8
|
}
|
|
9
9
|
})
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
new defaultHook((exported: any, name: string, baseDir: string|void) => {
|
|
12
|
+
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
new Hook((exported: any, name: string, baseDir: string|void) => {
|
|
16
|
+
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
assert.equal(sayHi('test'), 'Hooked')
|
package/test/version-check.js
CHANGED
|
@@ -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,19 +0,0 @@
|
|
|
1
|
-
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License.
|
|
2
|
-
//
|
|
3
|
-
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
|
|
4
|
-
|
|
5
|
-
import { rejects } from 'assert'
|
|
6
|
-
(async () => {
|
|
7
|
-
const [processMajor, processMinor] = process.versions.node.split('.').map(Number)
|
|
8
|
-
const extensionlessSupported = processMajor >= 21 ||
|
|
9
|
-
(processMajor === 20 && processMinor >= 10) ||
|
|
10
|
-
(processMajor === 18 && processMinor >= 19)
|
|
11
|
-
if (extensionlessSupported) {
|
|
12
|
-
// Files without extension are supported in Node.js ^21, ^20.10.0, and ^18.19.0
|
|
13
|
-
return
|
|
14
|
-
}
|
|
15
|
-
await rejects(() => import('./executable'), {
|
|
16
|
-
name: 'TypeError',
|
|
17
|
-
code: 'ERR_UNKNOWN_FILE_EXTENSION'
|
|
18
|
-
})
|
|
19
|
-
})()
|
|
File without changes
|
|
File without changes
|