@jsenv/node-esm-resolution 0.0.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 ADDED
@@ -0,0 +1,4 @@
1
+ TODO:
2
+
3
+ - add more unit tests
4
+ - improve error messages
package/main.js ADDED
@@ -0,0 +1,5 @@
1
+ export { applyNodeEsmResolution } from "./src/node_esm_resolution.js"
2
+ export { determineModuleSystem } from "./src/determine_module_system.js"
3
+ export { applyFileSystemMagicResolution } from "./src/filesystem_magic_resolution.js"
4
+ export { lookupPackageScope } from "./src/lookup_package_scope.js"
5
+ export { readPackageJson } from "./src/read_package_json.js"
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@jsenv/node-esm-resolution",
3
+ "version": "0.0.1",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/jsenv/jsenv-core",
8
+ "directory": "packages/node-esm-resolution"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "type": "module",
14
+ "exports": {
15
+ ".": {
16
+ "import": "./main.js"
17
+ },
18
+ "./*": "./*"
19
+ },
20
+ "main": "./main.js",
21
+ "files": [
22
+ "/dist/",
23
+ "/src/",
24
+ "/main.js"
25
+ ]
26
+ }
@@ -0,0 +1,55 @@
1
+ import { lookupPackageScope } from "./lookup_package_scope.js"
2
+ import { readPackageJson } from "./read_package_json.js"
3
+
4
+ // https://nodejs.org/dist/latest-v16.x/docs/api/packages.html#packages_determining_module_system)
5
+ export const determineModuleSystem = (
6
+ url,
7
+ { ambiguousExtensions = [".js"] } = {},
8
+ ) => {
9
+ const inputTypeArgv = process.execArgv.find((argv) =>
10
+ argv.startsWith("--input-type="),
11
+ )
12
+ if (inputTypeArgv) {
13
+ const value = inputTypeArgv.slice("--input-type=".length)
14
+ if (value === "module") {
15
+ return "module"
16
+ }
17
+ if (value === "commonjs") {
18
+ return "commonjs"
19
+ }
20
+ }
21
+ const extension = extensionFromUrl(url)
22
+ if (extension === ".mjs") {
23
+ return "module"
24
+ }
25
+ if (extension === ".cjs") {
26
+ return "commonjs"
27
+ }
28
+ if (extension === ".json") {
29
+ return "json"
30
+ }
31
+ if (ambiguousExtensions.includes(extension)) {
32
+ const packageUrl = lookupPackageScope(url)
33
+ if (!packageUrl) {
34
+ return "commonjs"
35
+ }
36
+ const packageJson = readPackageJson(packageUrl)
37
+ if (packageJson.type === "module") {
38
+ return "module"
39
+ }
40
+ return "commonjs"
41
+ }
42
+ throw new Error("unsupported file extension")
43
+ }
44
+
45
+ const extensionFromUrl = (url) => {
46
+ const { pathname } = new URL(url)
47
+ const slashLastIndex = pathname.lastIndexOf("/")
48
+ const filename =
49
+ slashLastIndex === -1 ? pathname : pathname.slice(slashLastIndex + 1)
50
+ const dotLastIndex = filename.lastIndexOf(".")
51
+ if (dotLastIndex === -1) return ""
52
+ // if (dotLastIndex === pathname.length - 1) return ""
53
+ const extension = filename.slice(dotLastIndex)
54
+ return extension
55
+ }
package/src/errors.js ADDED
@@ -0,0 +1,83 @@
1
+ // https://github.com/nodejs/node/blob/0367b5c35ea0f98b323175a4aaa8e651af7a91e7/tools/node_modules/eslint/node_modules/%40babel/core/lib/vendor/import-meta-resolve.js#L2473
2
+ import { fileURLToPath } from "node:url"
3
+
4
+ export const createInvalidModuleSpecifierError = ({
5
+ specifier,
6
+ parentUrl,
7
+ reason,
8
+ }) => {
9
+ const error = new Error(
10
+ `Invalid module "${specifier}" ${reason} imported from ${fileURLToPath(
11
+ parentUrl,
12
+ )}`,
13
+ )
14
+ error.code = "INVALID_MODULE_SPECIFIER"
15
+ return error
16
+ }
17
+
18
+ export const createInvalidPackageTargetError = ({
19
+ parentUrl,
20
+ packageUrl,
21
+ target,
22
+ key,
23
+ isImport,
24
+ reason,
25
+ }) => {
26
+ let message
27
+ if (key === ".") {
28
+ message = `Invalid "exports" main target defined in ${fileURLToPath(
29
+ packageUrl,
30
+ )}package.json imported from ${fileURLToPath(parentUrl)}; ${reason}`
31
+ } else {
32
+ message = `Invalid "${
33
+ isImport ? "imports" : "exports"
34
+ }" target ${JSON.stringify(target)} defined for "${key}" in ${fileURLToPath(
35
+ packageUrl,
36
+ )}package.json imported from ${fileURLToPath(parentUrl)}; ${reason}`
37
+ }
38
+ const error = new Error(message)
39
+ error.code = "INVALID_PACKAGE_TARGET"
40
+ return error
41
+ }
42
+
43
+ export const createPackagePathNotExportedError = ({
44
+ subpath,
45
+ parentUrl,
46
+ packageUrl,
47
+ }) => {
48
+ let message
49
+ if (subpath === ".") {
50
+ message = `No "exports" main defined in ${fileURLToPath(
51
+ packageUrl,
52
+ )}package.json imported from ${fileURLToPath(parentUrl)}`
53
+ } else {
54
+ message = `Package subpath "${subpath}" is not defined by "exports" in ${fileURLToPath(
55
+ packageUrl,
56
+ )}package.json imported from ${fileURLToPath(parentUrl)}`
57
+ }
58
+ const error = new Error(message)
59
+ error.code = "PACKAGE_PATH_NOT_EXPORTED"
60
+ return error
61
+ }
62
+
63
+ export const createModuleNotFoundError = ({ specifier, parentUrl }) => {
64
+ const error = new Error(
65
+ `Cannot find "${specifier}" imported from ${fileURLToPath(parentUrl)}`,
66
+ )
67
+ error.code = "MODULE_NOT_FOUND"
68
+ return error
69
+ }
70
+
71
+ export const createPackageImportNotDefinedError = ({
72
+ specifier,
73
+ packageUrl,
74
+ parentUrl,
75
+ }) => {
76
+ const error = new Error(
77
+ `Package import specifier "${specifier}" is not defined in ${fileURLToPath(
78
+ packageUrl,
79
+ )}package.json imported from ${fileURLToPath(parentUrl)}`,
80
+ )
81
+ error.code = "PACKAGE_IMPORT_NOT_DEFINED"
82
+ return error
83
+ }
@@ -0,0 +1,71 @@
1
+ import { statSync } from "node:fs"
2
+
3
+ import { urlToFilename } from "./url_utils.js"
4
+
5
+ export const applyFileSystemMagicResolution = (
6
+ fileUrl,
7
+ { magicDirectoryIndex, magicExtensions },
8
+ ) => {
9
+ let lastENOENTError = null
10
+ const fileStatOrNull = (url) => {
11
+ try {
12
+ return statSync(new URL(url))
13
+ } catch (e) {
14
+ if (e.code === "ENOENT") {
15
+ lastENOENTError = e
16
+ return null
17
+ }
18
+ throw e
19
+ }
20
+ }
21
+
22
+ const fileStat = fileStatOrNull(fileUrl)
23
+ if (fileStat && fileStat.isFile()) {
24
+ return {
25
+ found: true,
26
+ url: fileUrl,
27
+ }
28
+ }
29
+ if (fileStat && fileStat.isDirectory()) {
30
+ if (magicDirectoryIndex) {
31
+ const indexFileSuffix = fileUrl.endsWith("/") ? "index" : "/index"
32
+ const indexFileUrl = `${fileUrl}${indexFileSuffix}`
33
+ const result = applyFileSystemMagicResolution(indexFileUrl, {
34
+ magicDirectoryIndex: false,
35
+ magicExtensions,
36
+ })
37
+ return {
38
+ magicDirectoryIndex: true,
39
+ ...result,
40
+ }
41
+ }
42
+ return {
43
+ isDirectory: true,
44
+ found: true,
45
+ url: fileUrl,
46
+ }
47
+ }
48
+ if (magicExtensions && magicExtensions.length) {
49
+ const parentUrl = new URL("./", fileUrl).href
50
+ const urlFilename = urlToFilename(fileUrl)
51
+ const extensionLeadingToFile = magicExtensions.find((extensionToTry) => {
52
+ const urlCandidate = `${parentUrl}${urlFilename}${extensionToTry}`
53
+ const stat = fileStatOrNull(urlCandidate)
54
+ return stat
55
+ })
56
+ if (extensionLeadingToFile) {
57
+ // magic extension worked
58
+ return {
59
+ magicExtension: extensionLeadingToFile,
60
+ found: true,
61
+ url: `${fileUrl}${extensionLeadingToFile}`,
62
+ }
63
+ }
64
+ }
65
+ // magic extension not found
66
+ return {
67
+ found: false,
68
+ url: fileUrl,
69
+ lastENOENTError,
70
+ }
71
+ }
@@ -0,0 +1,18 @@
1
+ import { existsSync } from "node:fs"
2
+
3
+ import { filesystemRootUrl, getParentUrl, asDirectoryUrl } from "./url_utils.js"
4
+
5
+ export const lookupPackageScope = (url) => {
6
+ let scopeUrl = asDirectoryUrl(url)
7
+ while (scopeUrl !== filesystemRootUrl) {
8
+ if (scopeUrl.endsWith("node_modules/")) {
9
+ return null
10
+ }
11
+ const packageJsonUrlObject = new URL("package.json", scopeUrl)
12
+ if (existsSync(packageJsonUrlObject)) {
13
+ return scopeUrl
14
+ }
15
+ scopeUrl = getParentUrl(scopeUrl)
16
+ }
17
+ return null
18
+ }
@@ -0,0 +1,84 @@
1
+ export const isSpecifierForNodeBuiltin = (specifier) => {
2
+ return (
3
+ specifier.startsWith("node:") ||
4
+ NODE_BUILTIN_MODULE_SPECIFIERS.includes(specifier)
5
+ )
6
+ }
7
+
8
+ const NODE_BUILTIN_MODULE_SPECIFIERS = [
9
+ "assert",
10
+ "assert/strict",
11
+ "async_hooks",
12
+ "buffer_ieee754",
13
+ "buffer",
14
+ "child_process",
15
+ "cluster",
16
+ "console",
17
+ "constants",
18
+ "crypto",
19
+ "_debugger",
20
+ "dgram",
21
+ "dns",
22
+ "domain",
23
+ "events",
24
+ "freelist",
25
+ "fs",
26
+ "fs/promises",
27
+ "_http_agent",
28
+ "_http_client",
29
+ "_http_common",
30
+ "_http_incoming",
31
+ "_http_outgoing",
32
+ "_http_server",
33
+ "http",
34
+ "http2",
35
+ "https",
36
+ "inspector",
37
+ "_linklist",
38
+ "module",
39
+ "net",
40
+ "node-inspect/lib/_inspect",
41
+ "node-inspect/lib/internal/inspect_client",
42
+ "node-inspect/lib/internal/inspect_repl",
43
+ "os",
44
+ "path",
45
+ "perf_hooks",
46
+ "process",
47
+ "punycode",
48
+ "querystring",
49
+ "readline",
50
+ "repl",
51
+ "smalloc",
52
+ "_stream_duplex",
53
+ "_stream_transform",
54
+ "_stream_wrap",
55
+ "_stream_passthrough",
56
+ "_stream_readable",
57
+ "_stream_writable",
58
+ "stream",
59
+ "stream/promises",
60
+ "string_decoder",
61
+ "sys",
62
+ "timers",
63
+ "_tls_common",
64
+ "_tls_legacy",
65
+ "_tls_wrap",
66
+ "tls",
67
+ "trace_events",
68
+ "tty",
69
+ "url",
70
+ "util",
71
+ "v8/tools/arguments",
72
+ "v8/tools/codemap",
73
+ "v8/tools/consarray",
74
+ "v8/tools/csvparser",
75
+ "v8/tools/logreader",
76
+ "v8/tools/profile_view",
77
+ "v8/tools/splaytree",
78
+ "v8",
79
+ "vm",
80
+ "worker_threads",
81
+ "zlib",
82
+ // global is special
83
+ "global",
84
+ ]
@@ -0,0 +1,782 @@
1
+ /*
2
+ * https://nodejs.org/api/esm.html#resolver-algorithm-specification
3
+ * https://github.com/nodejs/node/blob/0367b5c35ea0f98b323175a4aaa8e651af7a91e7/lib/internal/modules/esm/resolve.js#L1
4
+ * deviations from the spec:
5
+ * - take into account "browser", "module" and "jsnext"
6
+ * - the check for isDirectory -> throw is delayed is descoped to the caller
7
+ * - the call to real path ->
8
+ * delayed to the caller so that we can decide to
9
+ * maintain symlink as facade url when it's outside project directory
10
+ * or use the real path when inside
11
+ */
12
+ import { existsSync, readFileSync } from "node:fs"
13
+
14
+ import { isSpecifierForNodeBuiltin } from "./node_builtin_specifiers.js"
15
+ import { lookupPackageScope } from "./lookup_package_scope.js"
16
+ import { readPackageJson } from "./read_package_json.js"
17
+ import { filesystemRootUrl, getParentUrl, isValidUrl } from "./url_utils.js"
18
+ import {
19
+ createInvalidModuleSpecifierError,
20
+ createModuleNotFoundError,
21
+ createPackageImportNotDefinedError,
22
+ createPackagePathNotExportedError,
23
+ createInvalidPackageTargetError,
24
+ } from "./errors.js"
25
+
26
+ export const applyNodeEsmResolution = ({
27
+ conditions = ["node", "import"],
28
+ parentUrl,
29
+ specifier,
30
+ }) => {
31
+ const resolution = applyPackageSpecifierResolution({
32
+ conditions,
33
+ parentUrl: String(parentUrl),
34
+ specifier,
35
+ })
36
+ const { url } = resolution
37
+ if (url.startsWith("file:")) {
38
+ if (url.includes("%2F") || url.includes("%5C")) {
39
+ throw createInvalidModuleSpecifierError({
40
+ specifier,
41
+ parentUrl,
42
+ reason: `must not include encoded "/" or "\\" characters`,
43
+ })
44
+ }
45
+ return resolution
46
+ }
47
+ return resolution
48
+ }
49
+
50
+ const applyPackageSpecifierResolution = ({
51
+ conditions,
52
+ parentUrl,
53
+ specifier,
54
+ }) => {
55
+ // relative specifier
56
+ if (
57
+ specifier[0] === "/" ||
58
+ specifier.startsWith("./") ||
59
+ specifier.startsWith("../")
60
+ ) {
61
+ if (specifier[0] !== "/") {
62
+ const browserFieldResolution = applyBrowserFieldResolution({
63
+ conditions,
64
+ parentUrl,
65
+ specifier,
66
+ })
67
+ if (browserFieldResolution) {
68
+ return browserFieldResolution
69
+ }
70
+ }
71
+ return {
72
+ type: "relative_specifier",
73
+ url: new URL(specifier, parentUrl).href,
74
+ }
75
+ }
76
+ if (specifier[0] === "#") {
77
+ return applyPackageImportsResolution({
78
+ conditions,
79
+ parentUrl,
80
+ specifier,
81
+ })
82
+ }
83
+ try {
84
+ const urlObject = new URL(specifier)
85
+ return {
86
+ type: "absolute_specifier",
87
+ url: urlObject.href,
88
+ }
89
+ } catch (e) {
90
+ // bare specifier
91
+ const browserFieldResolution = applyBrowserFieldResolution({
92
+ conditions,
93
+ parentUrl,
94
+ packageSpecifier: specifier,
95
+ })
96
+ if (browserFieldResolution) {
97
+ return browserFieldResolution
98
+ }
99
+ return applyPackageResolve({
100
+ conditions,
101
+ parentUrl,
102
+ packageSpecifier: specifier,
103
+ })
104
+ }
105
+ }
106
+
107
+ const applyBrowserFieldResolution = ({ conditions, parentUrl, specifier }) => {
108
+ const browserCondition = conditions.includes("browser")
109
+ if (!browserCondition) {
110
+ return null
111
+ }
112
+ const packageUrl = lookupPackageScope(parentUrl)
113
+ if (!packageUrl) {
114
+ return null
115
+ }
116
+ const packageJson = readPackageJson(packageUrl)
117
+ if (!packageJson) {
118
+ return null
119
+ }
120
+ const { browser } = packageJson
121
+ if (!browser) {
122
+ return null
123
+ }
124
+ if (typeof browser !== "object") {
125
+ return null
126
+ }
127
+ let url
128
+ if (specifier.startsWith(".")) {
129
+ const specifierUrl = new URL(specifier, parentUrl).href
130
+ const specifierRelativeToPackage = specifierUrl.slice(packageUrl.length)
131
+ const specifierRelativeNotation = `./${specifierRelativeToPackage}`
132
+ const browserMapping = browser[specifierRelativeNotation]
133
+ if (typeof browserMapping === "string") {
134
+ url = new URL(browserMapping, packageUrl).href
135
+ } else if (browserMapping === false) {
136
+ url = `file:///@ignore/${specifierUrl.slice("file:///")}`
137
+ }
138
+ } else {
139
+ const browserMapping = browser[specifier]
140
+ if (typeof browserMapping === "string") {
141
+ url = new URL(browserMapping, packageUrl).href
142
+ } else if (browserMapping === false) {
143
+ url = `file:///@ignore/${specifier}`
144
+ }
145
+ }
146
+ if (url) {
147
+ return {
148
+ type: "browser",
149
+ packageUrl,
150
+ packageJson,
151
+ url,
152
+ }
153
+ }
154
+ return null
155
+ }
156
+
157
+ const applyPackageImportsResolution = ({
158
+ conditions,
159
+ parentUrl,
160
+ specifier,
161
+ }) => {
162
+ if (!specifier.startsWith("#")) {
163
+ throw createInvalidModuleSpecifierError({
164
+ specifier,
165
+ parentUrl,
166
+ reason: "internal imports must start with #",
167
+ })
168
+ }
169
+ if (specifier === "#" || specifier.startsWith("#/")) {
170
+ throw createInvalidModuleSpecifierError({
171
+ specifier,
172
+ parentUrl,
173
+ reason: "not a valid internal imports specifier name",
174
+ })
175
+ }
176
+ const packageUrl = lookupPackageScope(parentUrl)
177
+ if (packageUrl !== null) {
178
+ const packageJson = readPackageJson(packageUrl)
179
+ const { imports } = packageJson
180
+ if (imports !== null && typeof imports === "object") {
181
+ const resolved = applyPackageImportsExportsResolution({
182
+ conditions,
183
+ parentUrl,
184
+ packageUrl,
185
+ packageJson,
186
+ matchObject: imports,
187
+ matchKey: specifier,
188
+ isImports: true,
189
+ })
190
+ if (resolved) {
191
+ return resolved
192
+ }
193
+ }
194
+ }
195
+ throw createPackageImportNotDefinedError({
196
+ specifier,
197
+ packageUrl,
198
+ parentUrl,
199
+ })
200
+ }
201
+
202
+ const applyPackageResolve = ({ conditions, parentUrl, packageSpecifier }) => {
203
+ if (packageSpecifier === "") {
204
+ throw new Error("invalid module specifier")
205
+ }
206
+ if (
207
+ conditions.includes("node") &&
208
+ isSpecifierForNodeBuiltin(packageSpecifier)
209
+ ) {
210
+ return {
211
+ type: "node_builtin_specifier",
212
+ url: `node:${packageSpecifier}`,
213
+ }
214
+ }
215
+ const { packageName, packageSubpath } =
216
+ parsePackageSpecifier(packageSpecifier)
217
+ if (
218
+ packageName[0] === "." ||
219
+ packageName.includes("\\") ||
220
+ packageName.includes("%")
221
+ ) {
222
+ throw createInvalidModuleSpecifierError({
223
+ specifier: packageName,
224
+ parentUrl,
225
+ reason: `is not a valid package name`,
226
+ })
227
+ }
228
+ if (packageSubpath.endsWith("/")) {
229
+ throw new Error("invalid module specifier")
230
+ }
231
+ const selfResolution = applyPackageSelfResolution({
232
+ conditions,
233
+ parentUrl,
234
+ packageName,
235
+ packageSubpath,
236
+ })
237
+ if (selfResolution) {
238
+ return selfResolution
239
+ }
240
+ let currentUrl = parentUrl
241
+ while (currentUrl !== filesystemRootUrl) {
242
+ const packageUrl = new URL(`node_modules/${packageName}/`, currentUrl).href
243
+ if (!existsSync(new URL(packageUrl))) {
244
+ currentUrl = getParentUrl(currentUrl)
245
+ continue
246
+ }
247
+ const packageJson = readPackageJson(packageUrl)
248
+ if (packageJson !== null) {
249
+ const { exports } = packageJson
250
+ if (exports !== null && exports !== undefined) {
251
+ return applyPackageExportsResolution({
252
+ conditions,
253
+ parentUrl,
254
+ packageUrl,
255
+ packageJson,
256
+ packageSubpath,
257
+ exports,
258
+ })
259
+ }
260
+ }
261
+ return applyLegacySubpathResolution({
262
+ conditions,
263
+ parentUrl,
264
+ packageUrl,
265
+ packageJson,
266
+ packageSubpath,
267
+ })
268
+ }
269
+ throw createModuleNotFoundError({
270
+ specifier: packageName,
271
+ parentUrl,
272
+ })
273
+ }
274
+
275
+ const applyPackageSelfResolution = ({
276
+ conditions,
277
+ parentUrl,
278
+ packageName,
279
+ packageSubpath,
280
+ }) => {
281
+ const packageUrl = lookupPackageScope(parentUrl)
282
+ if (!packageUrl) {
283
+ return undefined
284
+ }
285
+ const packageJson = readPackageJson(packageUrl)
286
+ if (!packageJson) {
287
+ return undefined
288
+ }
289
+ if (packageJson.name !== packageName) {
290
+ return undefined
291
+ }
292
+ const { exports } = packageJson
293
+ if (!exports) {
294
+ const subpathResolution = applyLegacySubpathResolution({
295
+ conditions,
296
+ parentUrl,
297
+ packageUrl,
298
+ packageJson,
299
+ packageSubpath,
300
+ })
301
+ if (subpathResolution && subpathResolution.type !== "subpath") {
302
+ return subpathResolution
303
+ }
304
+ return undefined
305
+ }
306
+ return applyPackageExportsResolution({
307
+ conditions,
308
+ parentUrl,
309
+ packageUrl,
310
+ packageJson,
311
+ packageSubpath,
312
+ exports,
313
+ })
314
+ }
315
+
316
+ // https://github.com/nodejs/node/blob/0367b5c35ea0f98b323175a4aaa8e651af7a91e7/lib/internal/modules/esm/resolve.js#L642
317
+ const applyPackageExportsResolution = ({
318
+ conditions,
319
+ parentUrl,
320
+ packageUrl,
321
+ packageJson,
322
+ packageSubpath,
323
+ exports,
324
+ }) => {
325
+ const exportsInfo = readExports({ exports, packageUrl })
326
+ if (packageSubpath === ".") {
327
+ const mainExport = applyMainExportResolution({ exports, exportsInfo })
328
+ if (!mainExport) {
329
+ throw createPackagePathNotExportedError({
330
+ subpath: packageSubpath,
331
+ parentUrl,
332
+ packageUrl,
333
+ })
334
+ }
335
+ const resolved = applyPackageTargetResolution({
336
+ conditions,
337
+ parentUrl,
338
+ packageUrl,
339
+ packageJson,
340
+ key: ".",
341
+ target: mainExport,
342
+ })
343
+ if (resolved) {
344
+ return resolved
345
+ }
346
+ throw createPackagePathNotExportedError({
347
+ subpath: packageSubpath,
348
+ parentUrl,
349
+ packageUrl,
350
+ })
351
+ }
352
+ if (exportsInfo.type === "object" && exportsInfo.allKeysAreRelative) {
353
+ const resolved = applyPackageImportsExportsResolution({
354
+ conditions,
355
+ parentUrl,
356
+ packageUrl,
357
+ packageJson,
358
+ matchObject: exports,
359
+ matchKey: packageSubpath,
360
+ isImports: false,
361
+ })
362
+ if (resolved) {
363
+ return resolved
364
+ }
365
+ }
366
+ throw createPackagePathNotExportedError({
367
+ subpath: packageSubpath,
368
+ parentUrl,
369
+ packageUrl,
370
+ })
371
+ }
372
+
373
+ const applyPackageImportsExportsResolution = ({
374
+ conditions,
375
+ parentUrl,
376
+ packageUrl,
377
+ packageJson,
378
+ matchObject,
379
+ matchKey,
380
+ isImports,
381
+ }) => {
382
+ if (!matchKey.includes("*") && matchObject.hasOwnProperty(matchKey)) {
383
+ const target = matchObject[matchKey]
384
+ return applyPackageTargetResolution({
385
+ conditions,
386
+ parentUrl,
387
+ packageUrl,
388
+ packageJson,
389
+ key: matchKey,
390
+ target,
391
+ internal: isImports,
392
+ })
393
+ }
394
+ const expansionKeys = Object.keys(matchObject)
395
+ .filter((key) => key.split("*").length === 2)
396
+ .sort(comparePatternKeys)
397
+ for (const expansionKey of expansionKeys) {
398
+ const [patternBase, patternTrailer] = expansionKey.split("*")
399
+ if (matchKey === patternBase) continue
400
+ if (!matchKey.startsWith(patternBase)) continue
401
+ if (patternTrailer.length > 0) {
402
+ if (!matchKey.endsWith(patternTrailer)) continue
403
+ if (matchKey.length < expansionKey.length) continue
404
+ }
405
+ const target = matchObject[expansionKey]
406
+ const subpath = matchKey.slice(
407
+ patternBase.length,
408
+ matchKey.length - patternTrailer.length,
409
+ )
410
+ return applyPackageTargetResolution({
411
+ conditions,
412
+ parentUrl,
413
+ packageUrl,
414
+ packageJson,
415
+ key: matchKey,
416
+ target,
417
+ subpath,
418
+ pattern: true,
419
+ internal: isImports,
420
+ })
421
+ }
422
+ return null
423
+ }
424
+
425
+ const applyPackageTargetResolution = ({
426
+ conditions,
427
+ parentUrl,
428
+ packageUrl,
429
+ packageJson,
430
+ key,
431
+ target,
432
+ subpath = "",
433
+ pattern = false,
434
+ internal = false,
435
+ }) => {
436
+ if (typeof target === "string") {
437
+ if (pattern === false && subpath !== "" && !target.endsWith("/")) {
438
+ throw new Error("invalid module specifier")
439
+ }
440
+ if (target.startsWith("./")) {
441
+ const targetUrl = new URL(target, packageUrl).href
442
+ if (!targetUrl.startsWith(packageUrl)) {
443
+ throw createInvalidPackageTargetError({
444
+ parentUrl,
445
+ packageUrl,
446
+ target,
447
+ key,
448
+ isImport: internal,
449
+ reason: `target must be inside package`,
450
+ })
451
+ }
452
+ return {
453
+ type: internal ? "imports_subpath" : "exports_subpath",
454
+ packageUrl,
455
+ packageJson,
456
+ url: pattern
457
+ ? targetUrl.replaceAll("*", subpath)
458
+ : new URL(subpath, targetUrl).href,
459
+ }
460
+ }
461
+ if (!internal || target.startsWith("../") || isValidUrl(target)) {
462
+ throw createInvalidPackageTargetError({
463
+ parentUrl,
464
+ packageUrl,
465
+ target,
466
+ key,
467
+ isImport: internal,
468
+ reason: `target must starst with "./"`,
469
+ })
470
+ }
471
+ return applyPackageResolve({
472
+ conditions,
473
+ parentUrl: packageUrl,
474
+ packageSpecifier: pattern
475
+ ? target.replaceAll("*", subpath)
476
+ : `${target}${subpath}`,
477
+ })
478
+ }
479
+ if (Array.isArray(target)) {
480
+ if (target.length === 0) {
481
+ return null
482
+ }
483
+ let lastResult
484
+ let i = 0
485
+ while (i < target.length) {
486
+ const targetValue = target[i]
487
+ i++
488
+ try {
489
+ const resolved = applyPackageTargetResolution({
490
+ conditions,
491
+ parentUrl,
492
+ packageUrl,
493
+ packageJson,
494
+ key: `${key}[${i}]`,
495
+ target: targetValue,
496
+ subpath,
497
+ pattern,
498
+ internal,
499
+ })
500
+ if (resolved) {
501
+ return resolved
502
+ }
503
+ lastResult = resolved
504
+ } catch (e) {
505
+ if (e.code === "INVALID_PACKAGE_TARGET") {
506
+ continue
507
+ }
508
+ lastResult = e
509
+ }
510
+ }
511
+ if (lastResult) {
512
+ throw lastResult
513
+ }
514
+ return null
515
+ }
516
+ if (target === null) {
517
+ return null
518
+ }
519
+ if (typeof target === "object") {
520
+ const keys = Object.keys(target)
521
+ for (const key of keys) {
522
+ if (Number.isInteger(key)) {
523
+ throw new Error("Invalid package configuration")
524
+ }
525
+ if (key === "default" || conditions.includes(key)) {
526
+ const targetValue = target[key]
527
+ const resolved = applyPackageTargetResolution({
528
+ conditions,
529
+ parentUrl,
530
+ packageUrl,
531
+ packageJson,
532
+ key,
533
+ target: targetValue,
534
+ subpath,
535
+ pattern,
536
+ internal,
537
+ })
538
+ if (resolved) {
539
+ return resolved
540
+ }
541
+ }
542
+ }
543
+ return null
544
+ }
545
+ throw createInvalidPackageTargetError({
546
+ parentUrl,
547
+ packageUrl,
548
+ target,
549
+ key,
550
+ isImport: internal,
551
+ reason: `target must be a string, array, object or null`,
552
+ })
553
+ }
554
+
555
+ const readExports = ({ exports, packageUrl }) => {
556
+ if (Array.isArray(exports)) {
557
+ return {
558
+ type: "array",
559
+ }
560
+ }
561
+ if (exports === null) {
562
+ return {}
563
+ }
564
+ if (typeof exports === "object") {
565
+ const keys = Object.keys(exports)
566
+ const relativeKeys = []
567
+ const conditionalKeys = []
568
+ keys.forEach((availableKey) => {
569
+ if (availableKey.startsWith(".")) {
570
+ relativeKeys.push(availableKey)
571
+ } else {
572
+ conditionalKeys.push(availableKey)
573
+ }
574
+ })
575
+ const hasRelativeKey = relativeKeys.length > 0
576
+ if (hasRelativeKey && conditionalKeys.length > 0) {
577
+ throw new Error(
578
+ `Invalid package configuration: cannot mix relative and conditional keys in package.exports
579
+ --- unexpected keys ---
580
+ ${conditionalKeys.map((key) => `"${key}"`).join("\n")}
581
+ --- package.json ---
582
+ ${packageUrl}`,
583
+ )
584
+ }
585
+ return {
586
+ type: "object",
587
+ hasRelativeKey,
588
+ allKeysAreRelative: relativeKeys.length === keys.length,
589
+ }
590
+ }
591
+ if (typeof exports === "string") {
592
+ return { type: "string" }
593
+ }
594
+ return {}
595
+ }
596
+
597
+ const parsePackageSpecifier = (packageSpecifier) => {
598
+ if (packageSpecifier[0] === "@") {
599
+ const firstSlashIndex = packageSpecifier.indexOf("/")
600
+ if (firstSlashIndex === -1) {
601
+ throw new Error("invalid module specifier")
602
+ }
603
+ const secondSlashIndex = packageSpecifier.indexOf("/", firstSlashIndex + 1)
604
+ if (secondSlashIndex === -1) {
605
+ return {
606
+ packageName: packageSpecifier,
607
+ packageSubpath: ".",
608
+ isScoped: true,
609
+ }
610
+ }
611
+ const packageName = packageSpecifier.slice(0, secondSlashIndex)
612
+ const afterSecondSlash = packageSpecifier.slice(secondSlashIndex + 1)
613
+ const packageSubpath = `./${afterSecondSlash}`
614
+ return {
615
+ packageName,
616
+ packageSubpath,
617
+ isScoped: true,
618
+ }
619
+ }
620
+ const firstSlashIndex = packageSpecifier.indexOf("/")
621
+ if (firstSlashIndex === -1) {
622
+ return {
623
+ packageName: packageSpecifier,
624
+ packageSubpath: ".",
625
+ }
626
+ }
627
+ const packageName = packageSpecifier.slice(0, firstSlashIndex)
628
+ const afterFirstSlash = packageSpecifier.slice(firstSlashIndex + 1)
629
+ const packageSubpath = `./${afterFirstSlash}`
630
+ return {
631
+ packageName,
632
+ packageSubpath,
633
+ }
634
+ }
635
+
636
+ const applyMainExportResolution = ({ exports, exportsInfo }) => {
637
+ if (exportsInfo.type === "array" || exportsInfo.type === "string") {
638
+ return exports
639
+ }
640
+ if (exportsInfo.type === "object") {
641
+ if (exportsInfo.hasRelativeKey) {
642
+ return exports["."]
643
+ }
644
+ return exports
645
+ }
646
+ return undefined
647
+ }
648
+
649
+ const applyLegacySubpathResolution = ({
650
+ conditions,
651
+ parentUrl,
652
+ packageUrl,
653
+ packageJson,
654
+ packageSubpath,
655
+ }) => {
656
+ if (packageSubpath === ".") {
657
+ return applyLegacyMainResolution({
658
+ conditions,
659
+ packageUrl,
660
+ packageJson,
661
+ })
662
+ }
663
+ const browserFieldResolution = applyBrowserFieldResolution({
664
+ conditions,
665
+ parentUrl,
666
+ specifier: packageSubpath,
667
+ })
668
+ if (browserFieldResolution) {
669
+ return browserFieldResolution
670
+ }
671
+ return {
672
+ type: "subpath",
673
+ packageUrl,
674
+ packageJson,
675
+ url: new URL(packageSubpath, packageUrl).href,
676
+ }
677
+ }
678
+
679
+ const applyLegacyMainResolution = ({ conditions, packageUrl, packageJson }) => {
680
+ for (const condition of conditions) {
681
+ const resolved = mainLegacyResolvers[condition](packageJson, packageUrl)
682
+ if (resolved) {
683
+ return {
684
+ type: resolved.type,
685
+ packageUrl,
686
+ packageJson,
687
+ url: new URL(resolved.path, packageUrl).href,
688
+ }
689
+ }
690
+ }
691
+ return {
692
+ type: "default",
693
+ packageUrl,
694
+ packageJson,
695
+ url: new URL("index.js", packageUrl).href,
696
+ }
697
+ }
698
+ const mainLegacyResolvers = {
699
+ import: ({ module, jsnext }) => {
700
+ if (typeof module === "string") {
701
+ return { type: "module", path: module }
702
+ }
703
+ if (typeof jsnext === "string") {
704
+ return { type: "jsnext", path: jsnext }
705
+ }
706
+ return null
707
+ },
708
+ browser: ({ browser, module }, packageUrl) => {
709
+ const browserMain =
710
+ typeof browser === "string"
711
+ ? browser
712
+ : typeof browser === "object" && browser !== null
713
+ ? browser["."]
714
+ : ""
715
+ if (!browserMain) {
716
+ return null
717
+ }
718
+ if (typeof module !== "string" || module === browserMain) {
719
+ return {
720
+ type: "browser",
721
+ path: browserMain,
722
+ }
723
+ }
724
+ const browserMainUrlObject = new URL(browserMain, packageUrl)
725
+ const content = readFileSync(browserMainUrlObject, "utf-8")
726
+ if (
727
+ (/typeof exports\s*==/.test(content) &&
728
+ /typeof module\s*==/.test(content)) ||
729
+ /module\.exports\s*=/.test(content)
730
+ ) {
731
+ return {
732
+ type: "module",
733
+ path: module,
734
+ }
735
+ }
736
+ return {
737
+ type: "browser",
738
+ path: browserMain,
739
+ }
740
+ },
741
+ node: ({ main }) => {
742
+ if (typeof main === "string") {
743
+ return {
744
+ type: "main",
745
+ path: main,
746
+ }
747
+ }
748
+ return null
749
+ },
750
+ }
751
+
752
+ const comparePatternKeys = (keyA, keyB) => {
753
+ if (!keyA.endsWith("/") && !keyA.contains("*")) {
754
+ throw new Error("Invalid package configuration")
755
+ }
756
+ if (!keyB.endsWith("/") && !keyB.contains("*")) {
757
+ throw new Error("Invalid package configuration")
758
+ }
759
+ const aStarIndex = keyA.indexOf("*")
760
+ const baseLengthA = aStarIndex > -1 ? aStarIndex + 1 : keyA.length
761
+ const bStarIndex = keyB.indexOf("*")
762
+ const baseLengthB = bStarIndex > -1 ? bStarIndex + 1 : keyB.length
763
+ if (baseLengthA > baseLengthB) {
764
+ return -1
765
+ }
766
+ if (baseLengthB > baseLengthA) {
767
+ return 1
768
+ }
769
+ if (aStarIndex === -1) {
770
+ return 1
771
+ }
772
+ if (bStarIndex === -1) {
773
+ return -1
774
+ }
775
+ if (keyA.length > keyB.length) {
776
+ return -1
777
+ }
778
+ if (keyB.length > keyA.length) {
779
+ return 1
780
+ }
781
+ return 0
782
+ }
@@ -0,0 +1,12 @@
1
+ import { readFileSync } from "node:fs"
2
+
3
+ export const readPackageJson = (packageUrl) => {
4
+ const packageJsonUrl = new URL("package.json", packageUrl)
5
+ const buffer = readFileSync(packageJsonUrl)
6
+ const string = String(buffer)
7
+ try {
8
+ return JSON.parse(string)
9
+ } catch (e) {
10
+ throw new Error(`Invalid package configuration`)
11
+ }
12
+ }
@@ -0,0 +1,37 @@
1
+ export const filesystemRootUrl =
2
+ process.platform === "win32" ? `file///${process.cwd()[0]}:/` : "file:///"
3
+
4
+ export const asDirectoryUrl = (url) => {
5
+ const { pathname } = new URL(url)
6
+ if (pathname.endsWith("/")) {
7
+ return url
8
+ }
9
+ return new URL("./", url).href
10
+ }
11
+
12
+ export const getParentUrl = (url) => {
13
+ return new URL(url.endsWith("/") ? "../" : "./", url).href
14
+ }
15
+
16
+ export const isValidUrl = (url) => {
17
+ try {
18
+ // eslint-disable-next-line no-new
19
+ new URL(url)
20
+ return true
21
+ } catch (e) {
22
+ return false
23
+ }
24
+ }
25
+
26
+ export const urlToFilename = (url) => {
27
+ const { pathname } = new URL(url)
28
+ const pathnameBeforeLastSlash = pathname.endsWith("/")
29
+ ? pathname.slice(0, -1)
30
+ : pathname
31
+ const slashLastIndex = pathnameBeforeLastSlash.lastIndexOf("/")
32
+ const filename =
33
+ slashLastIndex === -1
34
+ ? pathnameBeforeLastSlash
35
+ : pathnameBeforeLastSlash.slice(slashLastIndex + 1)
36
+ return filename
37
+ }