@jsenv/test 3.6.1 → 3.6.2
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/dist/js/importmap_hooks.mjs +600 -1
- package/dist/jsenv_test.js +8 -25
- package/package.json +13 -9
- package/dist/js/vendors.js +0 -602
|
@@ -1,7 +1,606 @@
|
|
|
1
|
-
import { normalizeImportMap, resolveImport } from "./vendors.js";
|
|
2
1
|
import { readFileSync } from "node:fs";
|
|
3
2
|
import { pathToFileURL } from "node:url";
|
|
4
3
|
|
|
4
|
+
// duplicated from @jsenv/log to avoid the dependency
|
|
5
|
+
const createDetailedMessage = (message, details = {}) => {
|
|
6
|
+
let string = `${message}`;
|
|
7
|
+
|
|
8
|
+
Object.keys(details).forEach((key) => {
|
|
9
|
+
const value = details[key];
|
|
10
|
+
string += `
|
|
11
|
+
--- ${key} ---
|
|
12
|
+
${
|
|
13
|
+
Array.isArray(value)
|
|
14
|
+
? value.join(`
|
|
15
|
+
`)
|
|
16
|
+
: value
|
|
17
|
+
}`;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return string
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const assertImportMap = (value) => {
|
|
24
|
+
if (value === null) {
|
|
25
|
+
throw new TypeError(`an importMap must be an object, got null`)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const type = typeof value;
|
|
29
|
+
if (type !== "object") {
|
|
30
|
+
throw new TypeError(`an importMap must be an object, received ${value}`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
throw new TypeError(
|
|
35
|
+
`an importMap must be an object, received array ${value}`,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const hasScheme = (string) => {
|
|
41
|
+
return /^[a-zA-Z]{2,}:/.test(string)
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const urlToScheme = (urlString) => {
|
|
45
|
+
const colonIndex = urlString.indexOf(":");
|
|
46
|
+
if (colonIndex === -1) return ""
|
|
47
|
+
return urlString.slice(0, colonIndex)
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const urlToPathname = (urlString) => {
|
|
51
|
+
return ressourceToPathname(urlToRessource(urlString))
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const urlToRessource = (urlString) => {
|
|
55
|
+
const scheme = urlToScheme(urlString);
|
|
56
|
+
|
|
57
|
+
if (scheme === "file") {
|
|
58
|
+
return urlString.slice("file://".length)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (scheme === "https" || scheme === "http") {
|
|
62
|
+
// remove origin
|
|
63
|
+
const afterProtocol = urlString.slice(scheme.length + "://".length);
|
|
64
|
+
const pathnameSlashIndex = afterProtocol.indexOf("/", "://".length);
|
|
65
|
+
return afterProtocol.slice(pathnameSlashIndex)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return urlString.slice(scheme.length + 1)
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const ressourceToPathname = (ressource) => {
|
|
72
|
+
const searchSeparatorIndex = ressource.indexOf("?");
|
|
73
|
+
return searchSeparatorIndex === -1
|
|
74
|
+
? ressource
|
|
75
|
+
: ressource.slice(0, searchSeparatorIndex)
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const urlToOrigin = (urlString) => {
|
|
79
|
+
const scheme = urlToScheme(urlString);
|
|
80
|
+
|
|
81
|
+
if (scheme === "file") {
|
|
82
|
+
return "file://"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (scheme === "http" || scheme === "https") {
|
|
86
|
+
const secondProtocolSlashIndex = scheme.length + "://".length;
|
|
87
|
+
const pathnameSlashIndex = urlString.indexOf("/", secondProtocolSlashIndex);
|
|
88
|
+
|
|
89
|
+
if (pathnameSlashIndex === -1) return urlString
|
|
90
|
+
return urlString.slice(0, pathnameSlashIndex)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return urlString.slice(0, scheme.length + 1)
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const pathnameToParentPathname = (pathname) => {
|
|
97
|
+
const slashLastIndex = pathname.lastIndexOf("/");
|
|
98
|
+
if (slashLastIndex === -1) {
|
|
99
|
+
return "/"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return pathname.slice(0, slashLastIndex + 1)
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// could be useful: https://url.spec.whatwg.org/#url-miscellaneous
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
const resolveUrl = (specifier, baseUrl) => {
|
|
109
|
+
if (baseUrl) {
|
|
110
|
+
if (typeof baseUrl !== "string") {
|
|
111
|
+
throw new TypeError(writeBaseUrlMustBeAString({ baseUrl, specifier }))
|
|
112
|
+
}
|
|
113
|
+
if (!hasScheme(baseUrl)) {
|
|
114
|
+
throw new Error(writeBaseUrlMustBeAbsolute({ baseUrl, specifier }))
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (hasScheme(specifier)) {
|
|
119
|
+
return specifier
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!baseUrl) {
|
|
123
|
+
throw new Error(writeBaseUrlRequired({ baseUrl, specifier }))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// scheme relative
|
|
127
|
+
if (specifier.slice(0, 2) === "//") {
|
|
128
|
+
return `${urlToScheme(baseUrl)}:${specifier}`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// origin relative
|
|
132
|
+
if (specifier[0] === "/") {
|
|
133
|
+
return `${urlToOrigin(baseUrl)}${specifier}`
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const baseOrigin = urlToOrigin(baseUrl);
|
|
137
|
+
const basePathname = urlToPathname(baseUrl);
|
|
138
|
+
|
|
139
|
+
if (specifier === ".") {
|
|
140
|
+
const baseDirectoryPathname = pathnameToParentPathname(basePathname);
|
|
141
|
+
return `${baseOrigin}${baseDirectoryPathname}`
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// pathname relative inside
|
|
145
|
+
if (specifier.slice(0, 2) === "./") {
|
|
146
|
+
const baseDirectoryPathname = pathnameToParentPathname(basePathname);
|
|
147
|
+
return `${baseOrigin}${baseDirectoryPathname}${specifier.slice(2)}`
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// pathname relative outside
|
|
151
|
+
if (specifier.slice(0, 3) === "../") {
|
|
152
|
+
let unresolvedPathname = specifier;
|
|
153
|
+
const importerFolders = basePathname.split("/");
|
|
154
|
+
importerFolders.pop();
|
|
155
|
+
|
|
156
|
+
while (unresolvedPathname.slice(0, 3) === "../") {
|
|
157
|
+
unresolvedPathname = unresolvedPathname.slice(3);
|
|
158
|
+
// when there is no folder left to resolved
|
|
159
|
+
// we just ignore '../'
|
|
160
|
+
if (importerFolders.length) {
|
|
161
|
+
importerFolders.pop();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const resolvedPathname = `${importerFolders.join(
|
|
166
|
+
"/",
|
|
167
|
+
)}/${unresolvedPathname}`;
|
|
168
|
+
return `${baseOrigin}${resolvedPathname}`
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// bare
|
|
172
|
+
if (basePathname === "") {
|
|
173
|
+
return `${baseOrigin}/${specifier}`
|
|
174
|
+
}
|
|
175
|
+
if (basePathname[basePathname.length] === "/") {
|
|
176
|
+
return `${baseOrigin}${basePathname}${specifier}`
|
|
177
|
+
}
|
|
178
|
+
return `${baseOrigin}${pathnameToParentPathname(basePathname)}${specifier}`
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const writeBaseUrlMustBeAString = ({
|
|
182
|
+
baseUrl,
|
|
183
|
+
specifier,
|
|
184
|
+
}) => `baseUrl must be a string.
|
|
185
|
+
--- base url ---
|
|
186
|
+
${baseUrl}
|
|
187
|
+
--- specifier ---
|
|
188
|
+
${specifier}`;
|
|
189
|
+
|
|
190
|
+
const writeBaseUrlMustBeAbsolute = ({
|
|
191
|
+
baseUrl,
|
|
192
|
+
specifier,
|
|
193
|
+
}) => `baseUrl must be absolute.
|
|
194
|
+
--- base url ---
|
|
195
|
+
${baseUrl}
|
|
196
|
+
--- specifier ---
|
|
197
|
+
${specifier}`;
|
|
198
|
+
|
|
199
|
+
const writeBaseUrlRequired = ({
|
|
200
|
+
baseUrl,
|
|
201
|
+
specifier,
|
|
202
|
+
}) => `baseUrl required to resolve relative specifier.
|
|
203
|
+
--- base url ---
|
|
204
|
+
${baseUrl}
|
|
205
|
+
--- specifier ---
|
|
206
|
+
${specifier}`;
|
|
207
|
+
|
|
208
|
+
const tryUrlResolution = (string, url) => {
|
|
209
|
+
const result = resolveUrl(string, url);
|
|
210
|
+
return hasScheme(result) ? result : null
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const resolveSpecifier = (specifier, importer) => {
|
|
214
|
+
if (
|
|
215
|
+
specifier === "." ||
|
|
216
|
+
specifier[0] === "/" ||
|
|
217
|
+
specifier.startsWith("./") ||
|
|
218
|
+
specifier.startsWith("../")
|
|
219
|
+
) {
|
|
220
|
+
return resolveUrl(specifier, importer)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (hasScheme(specifier)) {
|
|
224
|
+
return specifier
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return null
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const applyImportMap = ({
|
|
231
|
+
importMap,
|
|
232
|
+
specifier,
|
|
233
|
+
importer,
|
|
234
|
+
createBareSpecifierError = ({ specifier, importer }) => {
|
|
235
|
+
return new Error(
|
|
236
|
+
createDetailedMessage(`Unmapped bare specifier.`, {
|
|
237
|
+
specifier,
|
|
238
|
+
importer,
|
|
239
|
+
}),
|
|
240
|
+
)
|
|
241
|
+
},
|
|
242
|
+
onImportMapping = () => {},
|
|
243
|
+
}) => {
|
|
244
|
+
assertImportMap(importMap);
|
|
245
|
+
if (typeof specifier !== "string") {
|
|
246
|
+
throw new TypeError(
|
|
247
|
+
createDetailedMessage("specifier must be a string.", {
|
|
248
|
+
specifier,
|
|
249
|
+
importer,
|
|
250
|
+
}),
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
if (importer) {
|
|
254
|
+
if (typeof importer !== "string") {
|
|
255
|
+
throw new TypeError(
|
|
256
|
+
createDetailedMessage("importer must be a string.", {
|
|
257
|
+
importer,
|
|
258
|
+
specifier,
|
|
259
|
+
}),
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
if (!hasScheme(importer)) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
createDetailedMessage(`importer must be an absolute url.`, {
|
|
265
|
+
importer,
|
|
266
|
+
specifier,
|
|
267
|
+
}),
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const specifierUrl = resolveSpecifier(specifier, importer);
|
|
273
|
+
const specifierNormalized = specifierUrl || specifier;
|
|
274
|
+
|
|
275
|
+
const { scopes } = importMap;
|
|
276
|
+
if (scopes && importer) {
|
|
277
|
+
const scopeSpecifierMatching = Object.keys(scopes).find(
|
|
278
|
+
(scopeSpecifier) => {
|
|
279
|
+
return (
|
|
280
|
+
scopeSpecifier === importer ||
|
|
281
|
+
specifierIsPrefixOf(scopeSpecifier, importer)
|
|
282
|
+
)
|
|
283
|
+
},
|
|
284
|
+
);
|
|
285
|
+
if (scopeSpecifierMatching) {
|
|
286
|
+
const scopeMappings = scopes[scopeSpecifierMatching];
|
|
287
|
+
const mappingFromScopes = applyMappings(
|
|
288
|
+
scopeMappings,
|
|
289
|
+
specifierNormalized,
|
|
290
|
+
scopeSpecifierMatching,
|
|
291
|
+
onImportMapping,
|
|
292
|
+
);
|
|
293
|
+
if (mappingFromScopes !== null) {
|
|
294
|
+
return mappingFromScopes
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const { imports } = importMap;
|
|
300
|
+
if (imports) {
|
|
301
|
+
const mappingFromImports = applyMappings(
|
|
302
|
+
imports,
|
|
303
|
+
specifierNormalized,
|
|
304
|
+
undefined,
|
|
305
|
+
onImportMapping,
|
|
306
|
+
);
|
|
307
|
+
if (mappingFromImports !== null) {
|
|
308
|
+
return mappingFromImports
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (specifierUrl) {
|
|
313
|
+
return specifierUrl
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
throw createBareSpecifierError({ specifier, importer })
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const applyMappings = (
|
|
320
|
+
mappings,
|
|
321
|
+
specifierNormalized,
|
|
322
|
+
scope,
|
|
323
|
+
onImportMapping,
|
|
324
|
+
) => {
|
|
325
|
+
const specifierCandidates = Object.keys(mappings);
|
|
326
|
+
|
|
327
|
+
let i = 0;
|
|
328
|
+
while (i < specifierCandidates.length) {
|
|
329
|
+
const specifierCandidate = specifierCandidates[i];
|
|
330
|
+
i++;
|
|
331
|
+
if (specifierCandidate === specifierNormalized) {
|
|
332
|
+
const address = mappings[specifierCandidate];
|
|
333
|
+
onImportMapping({
|
|
334
|
+
scope,
|
|
335
|
+
from: specifierCandidate,
|
|
336
|
+
to: address,
|
|
337
|
+
before: specifierNormalized,
|
|
338
|
+
after: address,
|
|
339
|
+
});
|
|
340
|
+
return address
|
|
341
|
+
}
|
|
342
|
+
if (specifierIsPrefixOf(specifierCandidate, specifierNormalized)) {
|
|
343
|
+
const address = mappings[specifierCandidate];
|
|
344
|
+
const afterSpecifier = specifierNormalized.slice(
|
|
345
|
+
specifierCandidate.length,
|
|
346
|
+
);
|
|
347
|
+
const addressFinal = tryUrlResolution(afterSpecifier, address);
|
|
348
|
+
onImportMapping({
|
|
349
|
+
scope,
|
|
350
|
+
from: specifierCandidate,
|
|
351
|
+
to: address,
|
|
352
|
+
before: specifierNormalized,
|
|
353
|
+
after: addressFinal,
|
|
354
|
+
});
|
|
355
|
+
return addressFinal
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return null
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const specifierIsPrefixOf = (specifierHref, href) => {
|
|
363
|
+
return (
|
|
364
|
+
specifierHref[specifierHref.length - 1] === "/" &&
|
|
365
|
+
href.startsWith(specifierHref)
|
|
366
|
+
)
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const sortImports = (imports) => {
|
|
370
|
+
const mappingsSorted = {};
|
|
371
|
+
|
|
372
|
+
Object.keys(imports)
|
|
373
|
+
.sort(compareLengthOrLocaleCompare)
|
|
374
|
+
.forEach((name) => {
|
|
375
|
+
mappingsSorted[name] = imports[name];
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
return mappingsSorted
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const sortScopes = (scopes) => {
|
|
382
|
+
const scopesSorted = {};
|
|
383
|
+
|
|
384
|
+
Object.keys(scopes)
|
|
385
|
+
.sort(compareLengthOrLocaleCompare)
|
|
386
|
+
.forEach((scopeSpecifier) => {
|
|
387
|
+
scopesSorted[scopeSpecifier] = sortImports(scopes[scopeSpecifier]);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
return scopesSorted
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const compareLengthOrLocaleCompare = (a, b) => {
|
|
394
|
+
return b.length - a.length || a.localeCompare(b)
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const normalizeImportMap = (importMap, baseUrl) => {
|
|
398
|
+
assertImportMap(importMap);
|
|
399
|
+
|
|
400
|
+
if (!isStringOrUrl(baseUrl)) {
|
|
401
|
+
throw new TypeError(formulateBaseUrlMustBeStringOrUrl({ baseUrl }))
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const { imports, scopes } = importMap;
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
imports: imports ? normalizeMappings(imports, baseUrl) : undefined,
|
|
408
|
+
scopes: scopes ? normalizeScopes(scopes, baseUrl) : undefined,
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const isStringOrUrl = (value) => {
|
|
413
|
+
if (typeof value === "string") {
|
|
414
|
+
return true
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (typeof URL === "function" && value instanceof URL) {
|
|
418
|
+
return true
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return false
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const normalizeMappings = (mappings, baseUrl) => {
|
|
425
|
+
const mappingsNormalized = {};
|
|
426
|
+
|
|
427
|
+
Object.keys(mappings).forEach((specifier) => {
|
|
428
|
+
const address = mappings[specifier];
|
|
429
|
+
|
|
430
|
+
if (typeof address !== "string") {
|
|
431
|
+
console.warn(
|
|
432
|
+
formulateAddressMustBeAString({
|
|
433
|
+
address,
|
|
434
|
+
specifier,
|
|
435
|
+
}),
|
|
436
|
+
);
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const specifierResolved = resolveSpecifier(specifier, baseUrl) || specifier;
|
|
441
|
+
|
|
442
|
+
const addressUrl = tryUrlResolution(address, baseUrl);
|
|
443
|
+
if (addressUrl === null) {
|
|
444
|
+
console.warn(
|
|
445
|
+
formulateAdressResolutionFailed({
|
|
446
|
+
address,
|
|
447
|
+
baseUrl,
|
|
448
|
+
specifier,
|
|
449
|
+
}),
|
|
450
|
+
);
|
|
451
|
+
return
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (specifier.endsWith("/") && !addressUrl.endsWith("/")) {
|
|
455
|
+
console.warn(
|
|
456
|
+
formulateAddressUrlRequiresTrailingSlash({
|
|
457
|
+
address,
|
|
458
|
+
specifier,
|
|
459
|
+
}),
|
|
460
|
+
);
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
mappingsNormalized[specifierResolved] = addressUrl;
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
return sortImports(mappingsNormalized)
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const normalizeScopes = (scopes, baseUrl) => {
|
|
470
|
+
const scopesNormalized = {};
|
|
471
|
+
|
|
472
|
+
Object.keys(scopes).forEach((scopeSpecifier) => {
|
|
473
|
+
const scopeMappings = scopes[scopeSpecifier];
|
|
474
|
+
const scopeUrl = tryUrlResolution(scopeSpecifier, baseUrl);
|
|
475
|
+
if (scopeUrl === null) {
|
|
476
|
+
console.warn(
|
|
477
|
+
formulateScopeResolutionFailed({
|
|
478
|
+
scope: scopeSpecifier,
|
|
479
|
+
baseUrl,
|
|
480
|
+
}),
|
|
481
|
+
);
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
const scopeValueNormalized = normalizeMappings(scopeMappings, baseUrl);
|
|
485
|
+
scopesNormalized[scopeUrl] = scopeValueNormalized;
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
return sortScopes(scopesNormalized)
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
const formulateBaseUrlMustBeStringOrUrl = ({
|
|
492
|
+
baseUrl,
|
|
493
|
+
}) => `baseUrl must be a string or an url.
|
|
494
|
+
--- base url ---
|
|
495
|
+
${baseUrl}`;
|
|
496
|
+
|
|
497
|
+
const formulateAddressMustBeAString = ({
|
|
498
|
+
specifier,
|
|
499
|
+
address,
|
|
500
|
+
}) => `Address must be a string.
|
|
501
|
+
--- address ---
|
|
502
|
+
${address}
|
|
503
|
+
--- specifier ---
|
|
504
|
+
${specifier}`;
|
|
505
|
+
|
|
506
|
+
const formulateAdressResolutionFailed = ({
|
|
507
|
+
address,
|
|
508
|
+
baseUrl,
|
|
509
|
+
specifier,
|
|
510
|
+
}) => `Address url resolution failed.
|
|
511
|
+
--- address ---
|
|
512
|
+
${address}
|
|
513
|
+
--- base url ---
|
|
514
|
+
${baseUrl}
|
|
515
|
+
--- specifier ---
|
|
516
|
+
${specifier}`;
|
|
517
|
+
|
|
518
|
+
const formulateAddressUrlRequiresTrailingSlash = ({
|
|
519
|
+
addressURL,
|
|
520
|
+
address,
|
|
521
|
+
specifier,
|
|
522
|
+
}) => `Address must end with /.
|
|
523
|
+
--- address url ---
|
|
524
|
+
${addressURL}
|
|
525
|
+
--- address ---
|
|
526
|
+
${address}
|
|
527
|
+
--- specifier ---
|
|
528
|
+
${specifier}`;
|
|
529
|
+
|
|
530
|
+
const formulateScopeResolutionFailed = ({
|
|
531
|
+
scope,
|
|
532
|
+
baseUrl,
|
|
533
|
+
}) => `Scope url resolution failed.
|
|
534
|
+
--- scope ---
|
|
535
|
+
${scope}
|
|
536
|
+
--- base url ---
|
|
537
|
+
${baseUrl}`;
|
|
538
|
+
|
|
539
|
+
const pathnameToExtension = (pathname) => {
|
|
540
|
+
const slashLastIndex = pathname.lastIndexOf("/");
|
|
541
|
+
if (slashLastIndex !== -1) {
|
|
542
|
+
pathname = pathname.slice(slashLastIndex + 1);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const dotLastIndex = pathname.lastIndexOf(".");
|
|
546
|
+
if (dotLastIndex === -1) return ""
|
|
547
|
+
// if (dotLastIndex === pathname.length - 1) return ""
|
|
548
|
+
return pathname.slice(dotLastIndex)
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
const resolveImport = ({
|
|
552
|
+
specifier,
|
|
553
|
+
importer,
|
|
554
|
+
importMap,
|
|
555
|
+
defaultExtension = false,
|
|
556
|
+
createBareSpecifierError,
|
|
557
|
+
onImportMapping = () => {},
|
|
558
|
+
}) => {
|
|
559
|
+
let url;
|
|
560
|
+
if (importMap) {
|
|
561
|
+
url = applyImportMap({
|
|
562
|
+
importMap,
|
|
563
|
+
specifier,
|
|
564
|
+
importer,
|
|
565
|
+
createBareSpecifierError,
|
|
566
|
+
onImportMapping,
|
|
567
|
+
});
|
|
568
|
+
} else {
|
|
569
|
+
url = resolveUrl(specifier, importer);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (defaultExtension) {
|
|
573
|
+
url = applyDefaultExtension({ url, importer, defaultExtension });
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return url
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
const applyDefaultExtension = ({ url, importer, defaultExtension }) => {
|
|
580
|
+
if (urlToPathname(url).endsWith("/")) {
|
|
581
|
+
return url
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (typeof defaultExtension === "string") {
|
|
585
|
+
const extension = pathnameToExtension(url);
|
|
586
|
+
if (extension === "") {
|
|
587
|
+
return `${url}${defaultExtension}`
|
|
588
|
+
}
|
|
589
|
+
return url
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (defaultExtension === true) {
|
|
593
|
+
const extension = pathnameToExtension(url);
|
|
594
|
+
if (extension === "" && importer) {
|
|
595
|
+
const importerPathname = urlToPathname(importer);
|
|
596
|
+
const importerExtension = pathnameToExtension(importerPathname);
|
|
597
|
+
return `${url}${importerExtension}`
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return url
|
|
602
|
+
};
|
|
603
|
+
|
|
5
604
|
let importMap;
|
|
6
605
|
|
|
7
606
|
const cwdUrl = `${String(pathToFileURL(process.cwd()))}/`;
|
package/dist/jsenv_test.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
+
import { readGitHubWorkflowEnv, startGithubCheckRun } from "@jsenv/github-check-run";
|
|
1
2
|
import process$1, { cpuUsage, memoryUsage } from "node:process";
|
|
2
|
-
import os, { cpus,
|
|
3
|
+
import os, { cpus, totalmem, release, availableParallelism, freemem } from "node:os";
|
|
3
4
|
import tty from "node:tty";
|
|
4
5
|
import stringWidth from "string-width";
|
|
5
|
-
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
6
|
-
import { readdir, chmod, stat, lstat, chmodSync, statSync, lstatSync, promises, readFile as readFile$1, unlinkSync, openSync, closeSync, readdirSync, rmdirSync, mkdirSync, readFileSync, writeFileSync as writeFileSync$1, unlink, rmdir, existsSync } from "node:fs";
|
|
7
|
-
import { URL_META, filterV8Coverage } from "./js/v8_coverage.js";
|
|
8
|
-
import { dirname } from "node:path";
|
|
9
|
-
import crypto from "node:crypto";
|
|
10
|
-
import { readGitHubWorkflowEnv, startGithubCheckRun } from "@jsenv/github-check-run";
|
|
11
6
|
import { applyNodeEsmResolution } from "@jsenv/node-esm-resolution";
|
|
7
|
+
import { URL_META, filterV8Coverage } from "./js/v8_coverage.js";
|
|
8
|
+
import { readdir, chmod, stat, lstat, chmodSync, statSync, lstatSync, promises, readFile as readFile$1, readdirSync, openSync, closeSync, unlinkSync, rmdirSync, mkdirSync, readFileSync, writeFileSync as writeFileSync$1, unlink, rmdir, existsSync } from "node:fs";
|
|
12
9
|
import { takeCoverage } from "node:v8";
|
|
13
10
|
import stripAnsi from "strip-ansi";
|
|
11
|
+
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
14
12
|
import { createRequire } from "node:module";
|
|
15
13
|
import { applyBabelPlugins } from "@jsenv/ast";
|
|
16
14
|
import { createException } from "@jsenv/exception";
|
|
15
|
+
import crypto from "node:crypto";
|
|
16
|
+
import { dirname } from "node:path";
|
|
17
17
|
import { createServer } from "node:net";
|
|
18
18
|
import { spawn, spawnSync, fork } from "node:child_process";
|
|
19
19
|
import { SOURCEMAP, generateSourcemapDataUrl } from "@jsenv/sourcemap";
|
|
@@ -3165,10 +3165,6 @@ const removeDirectoryNaive = (
|
|
|
3165
3165
|
});
|
|
3166
3166
|
};
|
|
3167
3167
|
|
|
3168
|
-
process.platform === "win32";
|
|
3169
|
-
|
|
3170
|
-
process.platform === "win32";
|
|
3171
|
-
|
|
3172
3168
|
const ensureEmptyDirectorySync = (source) => {
|
|
3173
3169
|
const stat = readEntryStatSync(source, {
|
|
3174
3170
|
nullIfNotFound: true,
|
|
@@ -3223,14 +3219,6 @@ const ensureEmptyDirectory = async (source) => {
|
|
|
3223
3219
|
);
|
|
3224
3220
|
};
|
|
3225
3221
|
|
|
3226
|
-
process.platform === "win32";
|
|
3227
|
-
|
|
3228
|
-
process.platform === "linux";
|
|
3229
|
-
|
|
3230
|
-
process.platform === "darwin";
|
|
3231
|
-
process.platform === "linux";
|
|
3232
|
-
process.platform === "freebsd";
|
|
3233
|
-
|
|
3234
3222
|
const normalizeFileByFileCoveragePaths = (
|
|
3235
3223
|
fileByFileCoverage,
|
|
3236
3224
|
rootDirectoryUrl,
|
|
@@ -5976,7 +5964,6 @@ const ensureWebServerIsStarted = async (
|
|
|
5976
5964
|
}
|
|
5977
5965
|
if (webServer.command) {
|
|
5978
5966
|
await startServerUsingCommand(webServer, {
|
|
5979
|
-
ignoreProcessExecArgv,
|
|
5980
5967
|
signal,
|
|
5981
5968
|
allocatedMs,
|
|
5982
5969
|
teardownCallbackSet,
|
|
@@ -7368,8 +7355,6 @@ const initJsSupervisorMiddleware = async (
|
|
|
7368
7355
|
const url = request.url();
|
|
7369
7356
|
if (url === fileServerUrl && urlToExtension(url) === ".html") {
|
|
7370
7357
|
interceptHtmlToExecute({
|
|
7371
|
-
url,
|
|
7372
|
-
request,
|
|
7373
7358
|
route,
|
|
7374
7359
|
});
|
|
7375
7360
|
return;
|
|
@@ -7377,14 +7362,13 @@ const initJsSupervisorMiddleware = async (
|
|
|
7377
7362
|
if (inlineScriptContents.has(url)) {
|
|
7378
7363
|
interceptInlineScript({
|
|
7379
7364
|
url,
|
|
7380
|
-
request,
|
|
7381
7365
|
route,
|
|
7382
7366
|
});
|
|
7383
7367
|
return;
|
|
7384
7368
|
}
|
|
7385
7369
|
const fsServerUrl = new URL("/@fs/", webServer.origin);
|
|
7386
7370
|
if (url.startsWith(fsServerUrl)) {
|
|
7387
|
-
interceptFileSystemUrl({ url,
|
|
7371
|
+
interceptFileSystemUrl({ url, route });
|
|
7388
7372
|
return;
|
|
7389
7373
|
}
|
|
7390
7374
|
route.fallback();
|
|
@@ -9868,7 +9852,6 @@ const execute = async ({
|
|
|
9868
9852
|
measureMemoryUsage,
|
|
9869
9853
|
onMeasureMemoryAvailable,
|
|
9870
9854
|
collectPerformance,
|
|
9871
|
-
collectCoverage,
|
|
9872
9855
|
coverageTempDirectoryUrl,
|
|
9873
9856
|
runtime,
|
|
9874
9857
|
runtimeParams,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/test",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"/dist/",
|
|
28
28
|
"/src/"
|
|
29
29
|
],
|
|
30
|
+
"sideEffects": [
|
|
31
|
+
"./src/runtime_node/node_child_process_controlled.mjs",
|
|
32
|
+
"./src/runtime_node/node_worker_thread_controlled.mjs"
|
|
33
|
+
],
|
|
30
34
|
"scripts": {
|
|
31
35
|
"build": "node --conditions=development ./scripts/build.mjs",
|
|
32
36
|
"prepublishOnly": "npm run build"
|
|
@@ -37,17 +41,17 @@
|
|
|
37
41
|
"dependencies": {
|
|
38
42
|
"@c88/v8-coverage": "0.1.1",
|
|
39
43
|
"@jsenv/abort": "4.3.1",
|
|
40
|
-
"@jsenv/ast": "6.6.
|
|
44
|
+
"@jsenv/ast": "6.6.1",
|
|
41
45
|
"@jsenv/exception": "1.1.5",
|
|
42
|
-
"@jsenv/filesystem": "4.14.
|
|
43
|
-
"@jsenv/github-check-run": "0.2.
|
|
46
|
+
"@jsenv/filesystem": "4.14.1",
|
|
47
|
+
"@jsenv/github-check-run": "0.2.15",
|
|
44
48
|
"@jsenv/node-esm-resolution": "1.1.0",
|
|
45
|
-
"@jsenv/humanize": "1.3.
|
|
46
|
-
"@jsenv/plugin-supervisor": "1.6.
|
|
47
|
-
"@jsenv/sourcemap": "1.3.
|
|
49
|
+
"@jsenv/humanize": "1.3.1",
|
|
50
|
+
"@jsenv/plugin-supervisor": "1.6.8",
|
|
51
|
+
"@jsenv/sourcemap": "1.3.1",
|
|
48
52
|
"@jsenv/url-meta": "8.5.4",
|
|
49
|
-
"@jsenv/urls": "2.6.
|
|
50
|
-
"@jsenv/utils": "2.2.
|
|
53
|
+
"@jsenv/urls": "2.6.2",
|
|
54
|
+
"@jsenv/utils": "2.2.1",
|
|
51
55
|
"he": "1.2.0",
|
|
52
56
|
"istanbul-lib-coverage": "3.2.2",
|
|
53
57
|
"istanbul-lib-instrument": "6.0.3",
|
package/dist/js/vendors.js
DELETED
|
@@ -1,602 +0,0 @@
|
|
|
1
|
-
// duplicated from @jsenv/log to avoid the dependency
|
|
2
|
-
const createDetailedMessage = (message, details = {}) => {
|
|
3
|
-
let string = `${message}`;
|
|
4
|
-
|
|
5
|
-
Object.keys(details).forEach((key) => {
|
|
6
|
-
const value = details[key];
|
|
7
|
-
string += `
|
|
8
|
-
--- ${key} ---
|
|
9
|
-
${
|
|
10
|
-
Array.isArray(value)
|
|
11
|
-
? value.join(`
|
|
12
|
-
`)
|
|
13
|
-
: value
|
|
14
|
-
}`;
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
return string
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const assertImportMap = (value) => {
|
|
21
|
-
if (value === null) {
|
|
22
|
-
throw new TypeError(`an importMap must be an object, got null`)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const type = typeof value;
|
|
26
|
-
if (type !== "object") {
|
|
27
|
-
throw new TypeError(`an importMap must be an object, received ${value}`)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (Array.isArray(value)) {
|
|
31
|
-
throw new TypeError(
|
|
32
|
-
`an importMap must be an object, received array ${value}`,
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const hasScheme = (string) => {
|
|
38
|
-
return /^[a-zA-Z]{2,}:/.test(string)
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const urlToScheme = (urlString) => {
|
|
42
|
-
const colonIndex = urlString.indexOf(":");
|
|
43
|
-
if (colonIndex === -1) return ""
|
|
44
|
-
return urlString.slice(0, colonIndex)
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const urlToPathname = (urlString) => {
|
|
48
|
-
return ressourceToPathname(urlToRessource(urlString))
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const urlToRessource = (urlString) => {
|
|
52
|
-
const scheme = urlToScheme(urlString);
|
|
53
|
-
|
|
54
|
-
if (scheme === "file") {
|
|
55
|
-
return urlString.slice("file://".length)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (scheme === "https" || scheme === "http") {
|
|
59
|
-
// remove origin
|
|
60
|
-
const afterProtocol = urlString.slice(scheme.length + "://".length);
|
|
61
|
-
const pathnameSlashIndex = afterProtocol.indexOf("/", "://".length);
|
|
62
|
-
return afterProtocol.slice(pathnameSlashIndex)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return urlString.slice(scheme.length + 1)
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const ressourceToPathname = (ressource) => {
|
|
69
|
-
const searchSeparatorIndex = ressource.indexOf("?");
|
|
70
|
-
return searchSeparatorIndex === -1
|
|
71
|
-
? ressource
|
|
72
|
-
: ressource.slice(0, searchSeparatorIndex)
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const urlToOrigin = (urlString) => {
|
|
76
|
-
const scheme = urlToScheme(urlString);
|
|
77
|
-
|
|
78
|
-
if (scheme === "file") {
|
|
79
|
-
return "file://"
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (scheme === "http" || scheme === "https") {
|
|
83
|
-
const secondProtocolSlashIndex = scheme.length + "://".length;
|
|
84
|
-
const pathnameSlashIndex = urlString.indexOf("/", secondProtocolSlashIndex);
|
|
85
|
-
|
|
86
|
-
if (pathnameSlashIndex === -1) return urlString
|
|
87
|
-
return urlString.slice(0, pathnameSlashIndex)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return urlString.slice(0, scheme.length + 1)
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const pathnameToParentPathname = (pathname) => {
|
|
94
|
-
const slashLastIndex = pathname.lastIndexOf("/");
|
|
95
|
-
if (slashLastIndex === -1) {
|
|
96
|
-
return "/"
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return pathname.slice(0, slashLastIndex + 1)
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
// could be useful: https://url.spec.whatwg.org/#url-miscellaneous
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const resolveUrl = (specifier, baseUrl) => {
|
|
106
|
-
if (baseUrl) {
|
|
107
|
-
if (typeof baseUrl !== "string") {
|
|
108
|
-
throw new TypeError(writeBaseUrlMustBeAString({ baseUrl, specifier }))
|
|
109
|
-
}
|
|
110
|
-
if (!hasScheme(baseUrl)) {
|
|
111
|
-
throw new Error(writeBaseUrlMustBeAbsolute({ baseUrl, specifier }))
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (hasScheme(specifier)) {
|
|
116
|
-
return specifier
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (!baseUrl) {
|
|
120
|
-
throw new Error(writeBaseUrlRequired({ baseUrl, specifier }))
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// scheme relative
|
|
124
|
-
if (specifier.slice(0, 2) === "//") {
|
|
125
|
-
return `${urlToScheme(baseUrl)}:${specifier}`
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// origin relative
|
|
129
|
-
if (specifier[0] === "/") {
|
|
130
|
-
return `${urlToOrigin(baseUrl)}${specifier}`
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const baseOrigin = urlToOrigin(baseUrl);
|
|
134
|
-
const basePathname = urlToPathname(baseUrl);
|
|
135
|
-
|
|
136
|
-
if (specifier === ".") {
|
|
137
|
-
const baseDirectoryPathname = pathnameToParentPathname(basePathname);
|
|
138
|
-
return `${baseOrigin}${baseDirectoryPathname}`
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// pathname relative inside
|
|
142
|
-
if (specifier.slice(0, 2) === "./") {
|
|
143
|
-
const baseDirectoryPathname = pathnameToParentPathname(basePathname);
|
|
144
|
-
return `${baseOrigin}${baseDirectoryPathname}${specifier.slice(2)}`
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// pathname relative outside
|
|
148
|
-
if (specifier.slice(0, 3) === "../") {
|
|
149
|
-
let unresolvedPathname = specifier;
|
|
150
|
-
const importerFolders = basePathname.split("/");
|
|
151
|
-
importerFolders.pop();
|
|
152
|
-
|
|
153
|
-
while (unresolvedPathname.slice(0, 3) === "../") {
|
|
154
|
-
unresolvedPathname = unresolvedPathname.slice(3);
|
|
155
|
-
// when there is no folder left to resolved
|
|
156
|
-
// we just ignore '../'
|
|
157
|
-
if (importerFolders.length) {
|
|
158
|
-
importerFolders.pop();
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const resolvedPathname = `${importerFolders.join(
|
|
163
|
-
"/",
|
|
164
|
-
)}/${unresolvedPathname}`;
|
|
165
|
-
return `${baseOrigin}${resolvedPathname}`
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// bare
|
|
169
|
-
if (basePathname === "") {
|
|
170
|
-
return `${baseOrigin}/${specifier}`
|
|
171
|
-
}
|
|
172
|
-
if (basePathname[basePathname.length] === "/") {
|
|
173
|
-
return `${baseOrigin}${basePathname}${specifier}`
|
|
174
|
-
}
|
|
175
|
-
return `${baseOrigin}${pathnameToParentPathname(basePathname)}${specifier}`
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
const writeBaseUrlMustBeAString = ({
|
|
179
|
-
baseUrl,
|
|
180
|
-
specifier,
|
|
181
|
-
}) => `baseUrl must be a string.
|
|
182
|
-
--- base url ---
|
|
183
|
-
${baseUrl}
|
|
184
|
-
--- specifier ---
|
|
185
|
-
${specifier}`;
|
|
186
|
-
|
|
187
|
-
const writeBaseUrlMustBeAbsolute = ({
|
|
188
|
-
baseUrl,
|
|
189
|
-
specifier,
|
|
190
|
-
}) => `baseUrl must be absolute.
|
|
191
|
-
--- base url ---
|
|
192
|
-
${baseUrl}
|
|
193
|
-
--- specifier ---
|
|
194
|
-
${specifier}`;
|
|
195
|
-
|
|
196
|
-
const writeBaseUrlRequired = ({
|
|
197
|
-
baseUrl,
|
|
198
|
-
specifier,
|
|
199
|
-
}) => `baseUrl required to resolve relative specifier.
|
|
200
|
-
--- base url ---
|
|
201
|
-
${baseUrl}
|
|
202
|
-
--- specifier ---
|
|
203
|
-
${specifier}`;
|
|
204
|
-
|
|
205
|
-
const tryUrlResolution = (string, url) => {
|
|
206
|
-
const result = resolveUrl(string, url);
|
|
207
|
-
return hasScheme(result) ? result : null
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const resolveSpecifier = (specifier, importer) => {
|
|
211
|
-
if (
|
|
212
|
-
specifier === "." ||
|
|
213
|
-
specifier[0] === "/" ||
|
|
214
|
-
specifier.startsWith("./") ||
|
|
215
|
-
specifier.startsWith("../")
|
|
216
|
-
) {
|
|
217
|
-
return resolveUrl(specifier, importer)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (hasScheme(specifier)) {
|
|
221
|
-
return specifier
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return null
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const applyImportMap = ({
|
|
228
|
-
importMap,
|
|
229
|
-
specifier,
|
|
230
|
-
importer,
|
|
231
|
-
createBareSpecifierError = ({ specifier, importer }) => {
|
|
232
|
-
return new Error(
|
|
233
|
-
createDetailedMessage(`Unmapped bare specifier.`, {
|
|
234
|
-
specifier,
|
|
235
|
-
importer,
|
|
236
|
-
}),
|
|
237
|
-
)
|
|
238
|
-
},
|
|
239
|
-
onImportMapping = () => {},
|
|
240
|
-
}) => {
|
|
241
|
-
assertImportMap(importMap);
|
|
242
|
-
if (typeof specifier !== "string") {
|
|
243
|
-
throw new TypeError(
|
|
244
|
-
createDetailedMessage("specifier must be a string.", {
|
|
245
|
-
specifier,
|
|
246
|
-
importer,
|
|
247
|
-
}),
|
|
248
|
-
)
|
|
249
|
-
}
|
|
250
|
-
if (importer) {
|
|
251
|
-
if (typeof importer !== "string") {
|
|
252
|
-
throw new TypeError(
|
|
253
|
-
createDetailedMessage("importer must be a string.", {
|
|
254
|
-
importer,
|
|
255
|
-
specifier,
|
|
256
|
-
}),
|
|
257
|
-
)
|
|
258
|
-
}
|
|
259
|
-
if (!hasScheme(importer)) {
|
|
260
|
-
throw new Error(
|
|
261
|
-
createDetailedMessage(`importer must be an absolute url.`, {
|
|
262
|
-
importer,
|
|
263
|
-
specifier,
|
|
264
|
-
}),
|
|
265
|
-
)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const specifierUrl = resolveSpecifier(specifier, importer);
|
|
270
|
-
const specifierNormalized = specifierUrl || specifier;
|
|
271
|
-
|
|
272
|
-
const { scopes } = importMap;
|
|
273
|
-
if (scopes && importer) {
|
|
274
|
-
const scopeSpecifierMatching = Object.keys(scopes).find(
|
|
275
|
-
(scopeSpecifier) => {
|
|
276
|
-
return (
|
|
277
|
-
scopeSpecifier === importer ||
|
|
278
|
-
specifierIsPrefixOf(scopeSpecifier, importer)
|
|
279
|
-
)
|
|
280
|
-
},
|
|
281
|
-
);
|
|
282
|
-
if (scopeSpecifierMatching) {
|
|
283
|
-
const scopeMappings = scopes[scopeSpecifierMatching];
|
|
284
|
-
const mappingFromScopes = applyMappings(
|
|
285
|
-
scopeMappings,
|
|
286
|
-
specifierNormalized,
|
|
287
|
-
scopeSpecifierMatching,
|
|
288
|
-
onImportMapping,
|
|
289
|
-
);
|
|
290
|
-
if (mappingFromScopes !== null) {
|
|
291
|
-
return mappingFromScopes
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const { imports } = importMap;
|
|
297
|
-
if (imports) {
|
|
298
|
-
const mappingFromImports = applyMappings(
|
|
299
|
-
imports,
|
|
300
|
-
specifierNormalized,
|
|
301
|
-
undefined,
|
|
302
|
-
onImportMapping,
|
|
303
|
-
);
|
|
304
|
-
if (mappingFromImports !== null) {
|
|
305
|
-
return mappingFromImports
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (specifierUrl) {
|
|
310
|
-
return specifierUrl
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
throw createBareSpecifierError({ specifier, importer })
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
const applyMappings = (
|
|
317
|
-
mappings,
|
|
318
|
-
specifierNormalized,
|
|
319
|
-
scope,
|
|
320
|
-
onImportMapping,
|
|
321
|
-
) => {
|
|
322
|
-
const specifierCandidates = Object.keys(mappings);
|
|
323
|
-
|
|
324
|
-
let i = 0;
|
|
325
|
-
while (i < specifierCandidates.length) {
|
|
326
|
-
const specifierCandidate = specifierCandidates[i];
|
|
327
|
-
i++;
|
|
328
|
-
if (specifierCandidate === specifierNormalized) {
|
|
329
|
-
const address = mappings[specifierCandidate];
|
|
330
|
-
onImportMapping({
|
|
331
|
-
scope,
|
|
332
|
-
from: specifierCandidate,
|
|
333
|
-
to: address,
|
|
334
|
-
before: specifierNormalized,
|
|
335
|
-
after: address,
|
|
336
|
-
});
|
|
337
|
-
return address
|
|
338
|
-
}
|
|
339
|
-
if (specifierIsPrefixOf(specifierCandidate, specifierNormalized)) {
|
|
340
|
-
const address = mappings[specifierCandidate];
|
|
341
|
-
const afterSpecifier = specifierNormalized.slice(
|
|
342
|
-
specifierCandidate.length,
|
|
343
|
-
);
|
|
344
|
-
const addressFinal = tryUrlResolution(afterSpecifier, address);
|
|
345
|
-
onImportMapping({
|
|
346
|
-
scope,
|
|
347
|
-
from: specifierCandidate,
|
|
348
|
-
to: address,
|
|
349
|
-
before: specifierNormalized,
|
|
350
|
-
after: addressFinal,
|
|
351
|
-
});
|
|
352
|
-
return addressFinal
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
return null
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
const specifierIsPrefixOf = (specifierHref, href) => {
|
|
360
|
-
return (
|
|
361
|
-
specifierHref[specifierHref.length - 1] === "/" &&
|
|
362
|
-
href.startsWith(specifierHref)
|
|
363
|
-
)
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
const sortImports = (imports) => {
|
|
367
|
-
const mappingsSorted = {};
|
|
368
|
-
|
|
369
|
-
Object.keys(imports)
|
|
370
|
-
.sort(compareLengthOrLocaleCompare)
|
|
371
|
-
.forEach((name) => {
|
|
372
|
-
mappingsSorted[name] = imports[name];
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
return mappingsSorted
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
const sortScopes = (scopes) => {
|
|
379
|
-
const scopesSorted = {};
|
|
380
|
-
|
|
381
|
-
Object.keys(scopes)
|
|
382
|
-
.sort(compareLengthOrLocaleCompare)
|
|
383
|
-
.forEach((scopeSpecifier) => {
|
|
384
|
-
scopesSorted[scopeSpecifier] = sortImports(scopes[scopeSpecifier]);
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
return scopesSorted
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
const compareLengthOrLocaleCompare = (a, b) => {
|
|
391
|
-
return b.length - a.length || a.localeCompare(b)
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
const normalizeImportMap = (importMap, baseUrl) => {
|
|
395
|
-
assertImportMap(importMap);
|
|
396
|
-
|
|
397
|
-
if (!isStringOrUrl(baseUrl)) {
|
|
398
|
-
throw new TypeError(formulateBaseUrlMustBeStringOrUrl({ baseUrl }))
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const { imports, scopes } = importMap;
|
|
402
|
-
|
|
403
|
-
return {
|
|
404
|
-
imports: imports ? normalizeMappings(imports, baseUrl) : undefined,
|
|
405
|
-
scopes: scopes ? normalizeScopes(scopes, baseUrl) : undefined,
|
|
406
|
-
}
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
const isStringOrUrl = (value) => {
|
|
410
|
-
if (typeof value === "string") {
|
|
411
|
-
return true
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (typeof URL === "function" && value instanceof URL) {
|
|
415
|
-
return true
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
return false
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
const normalizeMappings = (mappings, baseUrl) => {
|
|
422
|
-
const mappingsNormalized = {};
|
|
423
|
-
|
|
424
|
-
Object.keys(mappings).forEach((specifier) => {
|
|
425
|
-
const address = mappings[specifier];
|
|
426
|
-
|
|
427
|
-
if (typeof address !== "string") {
|
|
428
|
-
console.warn(
|
|
429
|
-
formulateAddressMustBeAString({
|
|
430
|
-
address,
|
|
431
|
-
specifier,
|
|
432
|
-
}),
|
|
433
|
-
);
|
|
434
|
-
return
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const specifierResolved = resolveSpecifier(specifier, baseUrl) || specifier;
|
|
438
|
-
|
|
439
|
-
const addressUrl = tryUrlResolution(address, baseUrl);
|
|
440
|
-
if (addressUrl === null) {
|
|
441
|
-
console.warn(
|
|
442
|
-
formulateAdressResolutionFailed({
|
|
443
|
-
address,
|
|
444
|
-
baseUrl,
|
|
445
|
-
specifier,
|
|
446
|
-
}),
|
|
447
|
-
);
|
|
448
|
-
return
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (specifier.endsWith("/") && !addressUrl.endsWith("/")) {
|
|
452
|
-
console.warn(
|
|
453
|
-
formulateAddressUrlRequiresTrailingSlash({
|
|
454
|
-
addressUrl,
|
|
455
|
-
address,
|
|
456
|
-
specifier,
|
|
457
|
-
}),
|
|
458
|
-
);
|
|
459
|
-
return
|
|
460
|
-
}
|
|
461
|
-
mappingsNormalized[specifierResolved] = addressUrl;
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
return sortImports(mappingsNormalized)
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
const normalizeScopes = (scopes, baseUrl) => {
|
|
468
|
-
const scopesNormalized = {};
|
|
469
|
-
|
|
470
|
-
Object.keys(scopes).forEach((scopeSpecifier) => {
|
|
471
|
-
const scopeMappings = scopes[scopeSpecifier];
|
|
472
|
-
const scopeUrl = tryUrlResolution(scopeSpecifier, baseUrl);
|
|
473
|
-
if (scopeUrl === null) {
|
|
474
|
-
console.warn(
|
|
475
|
-
formulateScopeResolutionFailed({
|
|
476
|
-
scope: scopeSpecifier,
|
|
477
|
-
baseUrl,
|
|
478
|
-
}),
|
|
479
|
-
);
|
|
480
|
-
return
|
|
481
|
-
}
|
|
482
|
-
const scopeValueNormalized = normalizeMappings(scopeMappings, baseUrl);
|
|
483
|
-
scopesNormalized[scopeUrl] = scopeValueNormalized;
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
return sortScopes(scopesNormalized)
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
const formulateBaseUrlMustBeStringOrUrl = ({
|
|
490
|
-
baseUrl,
|
|
491
|
-
}) => `baseUrl must be a string or an url.
|
|
492
|
-
--- base url ---
|
|
493
|
-
${baseUrl}`;
|
|
494
|
-
|
|
495
|
-
const formulateAddressMustBeAString = ({
|
|
496
|
-
specifier,
|
|
497
|
-
address,
|
|
498
|
-
}) => `Address must be a string.
|
|
499
|
-
--- address ---
|
|
500
|
-
${address}
|
|
501
|
-
--- specifier ---
|
|
502
|
-
${specifier}`;
|
|
503
|
-
|
|
504
|
-
const formulateAdressResolutionFailed = ({
|
|
505
|
-
address,
|
|
506
|
-
baseUrl,
|
|
507
|
-
specifier,
|
|
508
|
-
}) => `Address url resolution failed.
|
|
509
|
-
--- address ---
|
|
510
|
-
${address}
|
|
511
|
-
--- base url ---
|
|
512
|
-
${baseUrl}
|
|
513
|
-
--- specifier ---
|
|
514
|
-
${specifier}`;
|
|
515
|
-
|
|
516
|
-
const formulateAddressUrlRequiresTrailingSlash = ({
|
|
517
|
-
addressURL,
|
|
518
|
-
address,
|
|
519
|
-
specifier,
|
|
520
|
-
}) => `Address must end with /.
|
|
521
|
-
--- address url ---
|
|
522
|
-
${addressURL}
|
|
523
|
-
--- address ---
|
|
524
|
-
${address}
|
|
525
|
-
--- specifier ---
|
|
526
|
-
${specifier}`;
|
|
527
|
-
|
|
528
|
-
const formulateScopeResolutionFailed = ({
|
|
529
|
-
scope,
|
|
530
|
-
baseUrl,
|
|
531
|
-
}) => `Scope url resolution failed.
|
|
532
|
-
--- scope ---
|
|
533
|
-
${scope}
|
|
534
|
-
--- base url ---
|
|
535
|
-
${baseUrl}`;
|
|
536
|
-
|
|
537
|
-
const pathnameToExtension = (pathname) => {
|
|
538
|
-
const slashLastIndex = pathname.lastIndexOf("/");
|
|
539
|
-
if (slashLastIndex !== -1) {
|
|
540
|
-
pathname = pathname.slice(slashLastIndex + 1);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
const dotLastIndex = pathname.lastIndexOf(".");
|
|
544
|
-
if (dotLastIndex === -1) return ""
|
|
545
|
-
// if (dotLastIndex === pathname.length - 1) return ""
|
|
546
|
-
return pathname.slice(dotLastIndex)
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
const resolveImport = ({
|
|
550
|
-
specifier,
|
|
551
|
-
importer,
|
|
552
|
-
importMap,
|
|
553
|
-
defaultExtension = false,
|
|
554
|
-
createBareSpecifierError,
|
|
555
|
-
onImportMapping = () => {},
|
|
556
|
-
}) => {
|
|
557
|
-
let url;
|
|
558
|
-
if (importMap) {
|
|
559
|
-
url = applyImportMap({
|
|
560
|
-
importMap,
|
|
561
|
-
specifier,
|
|
562
|
-
importer,
|
|
563
|
-
createBareSpecifierError,
|
|
564
|
-
onImportMapping,
|
|
565
|
-
});
|
|
566
|
-
} else {
|
|
567
|
-
url = resolveUrl(specifier, importer);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
if (defaultExtension) {
|
|
571
|
-
url = applyDefaultExtension({ url, importer, defaultExtension });
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
return url
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
const applyDefaultExtension = ({ url, importer, defaultExtension }) => {
|
|
578
|
-
if (urlToPathname(url).endsWith("/")) {
|
|
579
|
-
return url
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
if (typeof defaultExtension === "string") {
|
|
583
|
-
const extension = pathnameToExtension(url);
|
|
584
|
-
if (extension === "") {
|
|
585
|
-
return `${url}${defaultExtension}`
|
|
586
|
-
}
|
|
587
|
-
return url
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
if (defaultExtension === true) {
|
|
591
|
-
const extension = pathnameToExtension(url);
|
|
592
|
-
if (extension === "" && importer) {
|
|
593
|
-
const importerPathname = urlToPathname(importer);
|
|
594
|
-
const importerExtension = pathnameToExtension(importerPathname);
|
|
595
|
-
return `${url}${importerExtension}`
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
return url
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
export { normalizeImportMap, resolveImport };
|