-
-
Notifications
You must be signed in to change notification settings - Fork 600
/
Copy pathresolvePackageTarget.ts
173 lines (154 loc) · 5.96 KB
/
resolvePackageTarget.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/* eslint-disable no-await-in-loop, no-undefined */
import { pathToFileURL } from 'url';
import { isUrl, InvalidModuleSpecifierError, InvalidPackageTargetError } from './utils';
/**
* Check for invalid path segments
*/
function includesInvalidSegments(pathSegments: readonly string[], moduleDirs: readonly string[]) {
const invalidSegments = ['', '.', '..', ...moduleDirs];
// contains any "", ".", "..", or "node_modules" segments, including percent encoded variants
return pathSegments.some(
(v) => invalidSegments.includes(v) || invalidSegments.includes(decodeURI(v))
);
}
interface ParamObject {
target: any;
patternMatch?: string;
isImports?: boolean;
}
async function resolvePackageTarget(
context: any,
{ target, patternMatch, isImports }: ParamObject
): Promise<null | undefined | string | URL> {
// If target is a String, then
if (typeof target === 'string') {
// If target does not start with "./", then
if (!target.startsWith('./')) {
// If isImports is false, or if target starts with "../" or "/", or if target is a valid URL, then
if (!isImports || ['/', '../'].some((p) => target.startsWith(p)) || isUrl(target)) {
// Throw an Invalid Package Target error.
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`);
}
// If patternMatch is a String, then
if (typeof patternMatch === 'string') {
// Return PACKAGE_RESOLVE(target with every instance of "*" replaced by patternMatch, packageURL + "/")
const result = await context.resolveId(
target.replace(/\*/g, patternMatch),
context.pkgURL.href
);
return result ? pathToFileURL(result.location).href : null;
}
// Return PACKAGE_RESOLVE(target, packageURL + "/").
const result = await context.resolveId(target, context.pkgURL.href);
return result ? pathToFileURL(result.location).href : null;
}
// TODO: Drop if we do not support Node <= 16 anymore
// This behavior was removed in Node 17 (deprecated in Node 14), see DEP0148
if (context.allowExportsFolderMapping) {
target = target.replace(/\/$/, '/*');
}
// If target split on "/" or "\"
{
const pathSegments = target.split(/\/|\\/);
// after the first "." segment
const firstDot = pathSegments.indexOf('.');
firstDot !== -1 && pathSegments.slice(firstDot);
if (
firstDot !== -1 &&
firstDot < pathSegments.length - 1 &&
includesInvalidSegments(pathSegments.slice(firstDot + 1), context.moduleDirs)
) {
throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`);
}
}
// Let resolvedTarget be the URL resolution of the concatenation of packageURL and target.
const resolvedTarget = new URL(target, context.pkgURL);
// Assert: resolvedTarget is contained in packageURL.
if (!resolvedTarget.href.startsWith(context.pkgURL.href)) {
throw new InvalidPackageTargetError(
context,
`Resolved to ${resolvedTarget.href} which is outside package ${context.pkgURL.href}`
);
}
// If patternMatch is null, then
if (!patternMatch) {
// Return resolvedTarget.
return resolvedTarget;
}
// If patternMatch split on "/" or "\" contains invalid segments
if (includesInvalidSegments(patternMatch.split(/\/|\\/), context.moduleDirs)) {
// throw an Invalid Module Specifier error.
throw new InvalidModuleSpecifierError(context);
}
// Return the URL resolution of resolvedTarget with every instance of "*" replaced with patternMatch.
return resolvedTarget.href.replace(/\*/g, patternMatch);
}
// Otherwise, if target is an Array, then
if (Array.isArray(target)) {
// If _target.length is zero, return null.
if (target.length === 0) {
return null;
}
let lastError = null;
// For each item in target, do
for (const item of target) {
// Let resolved be the result of PACKAGE_TARGET_RESOLVE of the item
// continuing the loop on any Invalid Package Target error.
try {
const resolved = await resolvePackageTarget(context, {
target: item,
patternMatch,
isImports
});
// If resolved is undefined, continue the loop.
// Else Return resolved.
if (resolved !== undefined) {
return resolved;
}
} catch (error) {
if (!(error instanceof InvalidPackageTargetError)) {
throw error;
} else {
lastError = error;
}
}
}
// Return or throw the last fallback resolution null return or error
if (lastError) {
throw lastError;
}
return null;
}
// Otherwise, if target is a non-null Object, then
if (target && typeof target === 'object') {
// For each property of target
for (const [key, value] of Object.entries(target)) {
// If exports contains any index property keys, as defined in ECMA-262 6.1.7 Array Index, throw an Invalid Package Configuration error.
// TODO: We do not check if the key is a number here...
// If key equals "default" or conditions contains an entry for the key, then
if (key === 'default' || context.conditions.includes(key)) {
// Let targetValue be the value of the property in target.
// Let resolved be the result of PACKAGE_TARGET_RESOLVE of the targetValue
const resolved = await resolvePackageTarget(context, {
target: value,
patternMatch,
isImports
});
// If resolved is equal to undefined, continue the loop.
// Return resolved.
if (resolved !== undefined) {
return resolved;
}
}
}
// Return undefined.
return undefined;
}
// Otherwise, if target is null, return null.
if (target === null) {
return null;
}
// Otherwise throw an Invalid Package Target error.
throw new InvalidPackageTargetError(context, `Invalid exports field.`);
}
export default resolvePackageTarget;