@jsenv/core 38.3.4 → 38.3.5
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/jsenv_core.js +1603 -1631
- package/package.json +5 -5
- package/src/dev/start_dev_server.js +483 -103
- package/src/plugins/inlining/jsenv_plugin_inlining_as_data_url.js +7 -2
- package/src/plugins/reference_analysis/data_urls/jsenv_plugin_data_urls_analysis.js +2 -1
- package/src/dev/file_service.js +0 -413
- package/src/plugins/import_meta_url/client/import_meta_url_browser.js +0 -51
- package/src/plugins/import_meta_url/client/import_meta_url_commonjs.mjs +0 -9
package/dist/jsenv_core.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
2
1
|
import { chmod, stat, lstat, readdir, promises, unlink, openSync, closeSync, rmdir, watch, readdirSync, statSync, writeFileSync as writeFileSync$1, mkdirSync, createReadStream, readFile, existsSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
3
3
|
import crypto, { createHash } from "node:crypto";
|
|
4
4
|
import { extname } from "node:path";
|
|
5
5
|
import process$1 from "node:process";
|
|
@@ -20,132 +20,603 @@ import { systemJsClientFileUrlDefault, convertJsModuleToJsClassic } from "@jsenv
|
|
|
20
20
|
import { RUNTIME_COMPAT } from "@jsenv/runtime-compat";
|
|
21
21
|
import { jsenvPluginSupervisor } from "@jsenv/plugin-supervisor";
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const assertUrlLike = (value, name = "url") => {
|
|
24
|
+
if (typeof value !== "string") {
|
|
25
|
+
throw new TypeError(`${name} must be a url string, got ${value}`);
|
|
26
|
+
}
|
|
27
|
+
if (isWindowsPathnameSpecifier(value)) {
|
|
28
|
+
throw new TypeError(
|
|
29
|
+
`${name} must be a url but looks like a windows pathname, got ${value}`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
if (!hasScheme$1(value)) {
|
|
33
|
+
throw new TypeError(
|
|
34
|
+
`${name} must be a url and no scheme found, got ${value}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
27
38
|
|
|
28
|
-
|
|
39
|
+
const isPlainObject = (value) => {
|
|
40
|
+
if (value === null) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === "object") {
|
|
44
|
+
if (Array.isArray(value)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
};
|
|
29
51
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
const isWindowsPathnameSpecifier = (specifier) => {
|
|
53
|
+
const firstChar = specifier[0];
|
|
54
|
+
if (!/[a-zA-Z]/.test(firstChar)) return false;
|
|
55
|
+
const secondChar = specifier[1];
|
|
56
|
+
if (secondChar !== ":") return false;
|
|
57
|
+
const thirdChar = specifier[2];
|
|
58
|
+
return thirdChar === "/" || thirdChar === "\\";
|
|
59
|
+
};
|
|
35
60
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
61
|
+
const hasScheme$1 = (specifier) => /^[a-zA-Z]+:/.test(specifier);
|
|
62
|
+
|
|
63
|
+
const resolveAssociations = (associations, baseUrl) => {
|
|
64
|
+
assertUrlLike(baseUrl, "baseUrl");
|
|
65
|
+
const associationsResolved = {};
|
|
66
|
+
Object.keys(associations).forEach((key) => {
|
|
67
|
+
const value = associations[key];
|
|
68
|
+
if (typeof value === "object" && value !== null) {
|
|
69
|
+
const valueMapResolved = {};
|
|
70
|
+
Object.keys(value).forEach((pattern) => {
|
|
71
|
+
const valueAssociated = value[pattern];
|
|
72
|
+
const patternResolved = normalizeUrlPattern(pattern, baseUrl);
|
|
73
|
+
valueMapResolved[patternResolved] = valueAssociated;
|
|
74
|
+
});
|
|
75
|
+
associationsResolved[key] = valueMapResolved;
|
|
41
76
|
} else {
|
|
42
|
-
|
|
43
|
-
base64Flag = false;
|
|
77
|
+
associationsResolved[key] = value;
|
|
44
78
|
}
|
|
79
|
+
});
|
|
80
|
+
return associationsResolved;
|
|
81
|
+
};
|
|
45
82
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
},
|
|
83
|
+
const normalizeUrlPattern = (urlPattern, baseUrl) => {
|
|
84
|
+
try {
|
|
85
|
+
return String(new URL(urlPattern, baseUrl));
|
|
86
|
+
} catch (e) {
|
|
87
|
+
// it's not really an url, no need to perform url resolution nor encoding
|
|
88
|
+
return urlPattern;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
55
91
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
92
|
+
const asFlatAssociations = (associations) => {
|
|
93
|
+
if (!isPlainObject(associations)) {
|
|
94
|
+
throw new TypeError(
|
|
95
|
+
`associations must be a plain object, got ${associations}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const flatAssociations = {};
|
|
99
|
+
Object.keys(associations).forEach((associationName) => {
|
|
100
|
+
const associationValue = associations[associationName];
|
|
101
|
+
if (isPlainObject(associationValue)) {
|
|
102
|
+
Object.keys(associationValue).forEach((pattern) => {
|
|
103
|
+
const patternValue = associationValue[pattern];
|
|
104
|
+
const previousValue = flatAssociations[pattern];
|
|
105
|
+
if (isPlainObject(previousValue)) {
|
|
106
|
+
flatAssociations[pattern] = {
|
|
107
|
+
...previousValue,
|
|
108
|
+
[associationName]: patternValue,
|
|
109
|
+
};
|
|
110
|
+
} else {
|
|
111
|
+
flatAssociations[pattern] = {
|
|
112
|
+
[associationName]: patternValue,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
});
|
|
69
116
|
}
|
|
70
|
-
|
|
71
|
-
|
|
117
|
+
});
|
|
118
|
+
return flatAssociations;
|
|
72
119
|
};
|
|
73
120
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
121
|
+
/*
|
|
122
|
+
* Link to things doing pattern matching:
|
|
123
|
+
* https://git-scm.com/docs/gitignore
|
|
124
|
+
* https://github.com/kaelzhang/node-ignore
|
|
125
|
+
*/
|
|
78
126
|
|
|
79
127
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
let string = url;
|
|
128
|
+
/** @module jsenv_url_meta **/
|
|
129
|
+
/**
|
|
130
|
+
* An object representing the result of applying a pattern to an url
|
|
131
|
+
* @typedef {Object} MatchResult
|
|
132
|
+
* @property {boolean} matched Indicates if url matched pattern
|
|
133
|
+
* @property {number} patternIndex Index where pattern stopped matching url, otherwise pattern.length
|
|
134
|
+
* @property {number} urlIndex Index where url stopped matching pattern, otherwise url.length
|
|
135
|
+
* @property {Array} matchGroups Array of strings captured during pattern matching
|
|
136
|
+
*/
|
|
90
137
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Apply a pattern to an url
|
|
140
|
+
* @param {Object} applyPatternMatchingParams
|
|
141
|
+
* @param {string} applyPatternMatchingParams.pattern "*", "**" and trailing slash have special meaning
|
|
142
|
+
* @param {string} applyPatternMatchingParams.url a string representing an url
|
|
143
|
+
* @return {MatchResult}
|
|
144
|
+
*/
|
|
145
|
+
const applyPatternMatching = ({ url, pattern }) => {
|
|
146
|
+
assertUrlLike(pattern, "pattern");
|
|
147
|
+
assertUrlLike(url, "url");
|
|
148
|
+
const { matched, patternIndex, index, groups } = applyMatching(pattern, url);
|
|
149
|
+
const matchGroups = [];
|
|
150
|
+
let groupIndex = 0;
|
|
151
|
+
groups.forEach((group) => {
|
|
152
|
+
if (group.name) {
|
|
153
|
+
matchGroups[group.name] = group.string;
|
|
154
|
+
} else {
|
|
155
|
+
matchGroups[groupIndex] = group.string;
|
|
156
|
+
groupIndex++;
|
|
95
157
|
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (!showCodeFrame || typeof line !== "number" || !content) {
|
|
99
|
-
return string;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const sourceLoc = showSourceLocation({
|
|
103
|
-
content,
|
|
104
|
-
line,
|
|
105
|
-
column,
|
|
106
|
-
numberOfSurroundingLinesToShow,
|
|
107
|
-
lineMaxLength,
|
|
108
|
-
color,
|
|
109
158
|
});
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
159
|
+
return {
|
|
160
|
+
matched,
|
|
161
|
+
patternIndex,
|
|
162
|
+
urlIndex: index,
|
|
163
|
+
matchGroups,
|
|
164
|
+
};
|
|
113
165
|
};
|
|
114
166
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
let mark = (string) => string;
|
|
123
|
-
let aside = (string) => string;
|
|
124
|
-
// if (color) {
|
|
125
|
-
// mark = (string) => ANSI.color(string, ANSI.RED)
|
|
126
|
-
// aside = (string) => ANSI.color(string, ANSI.GREY)
|
|
127
|
-
// }
|
|
167
|
+
const applyMatching = (pattern, string) => {
|
|
168
|
+
const groups = [];
|
|
169
|
+
let patternIndex = 0;
|
|
170
|
+
let index = 0;
|
|
171
|
+
let remainingPattern = pattern;
|
|
172
|
+
let remainingString = string;
|
|
173
|
+
let restoreIndexes = true;
|
|
128
174
|
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
175
|
+
const consumePattern = (count) => {
|
|
176
|
+
const subpattern = remainingPattern.slice(0, count);
|
|
177
|
+
remainingPattern = remainingPattern.slice(count);
|
|
178
|
+
patternIndex += count;
|
|
179
|
+
return subpattern;
|
|
180
|
+
};
|
|
181
|
+
const consumeString = (count) => {
|
|
182
|
+
const substring = remainingString.slice(0, count);
|
|
183
|
+
remainingString = remainingString.slice(count);
|
|
184
|
+
index += count;
|
|
185
|
+
return substring;
|
|
186
|
+
};
|
|
187
|
+
const consumeRemainingString = () => {
|
|
188
|
+
return consumeString(remainingString.length);
|
|
134
189
|
};
|
|
135
|
-
lineRange = moveLineRangeUp(lineRange, numberOfSurroundingLinesToShow);
|
|
136
|
-
lineRange = moveLineRangeDown(lineRange, numberOfSurroundingLinesToShow);
|
|
137
|
-
lineRange = lineRangeWithinLines(lineRange, lines);
|
|
138
|
-
const linesToShow = lines.slice(lineRange.start, lineRange.end);
|
|
139
|
-
const endLineNumber = lineRange.end;
|
|
140
|
-
const lineNumberMaxWidth = String(endLineNumber).length;
|
|
141
|
-
|
|
142
|
-
if (column === 0) column = 1;
|
|
143
190
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
191
|
+
let matched;
|
|
192
|
+
const iterate = () => {
|
|
193
|
+
const patternIndexBefore = patternIndex;
|
|
194
|
+
const indexBefore = index;
|
|
195
|
+
matched = matchOne();
|
|
196
|
+
if (matched === undefined) {
|
|
197
|
+
consumePattern(1);
|
|
198
|
+
consumeString(1);
|
|
199
|
+
iterate();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (matched === false && restoreIndexes) {
|
|
203
|
+
patternIndex = patternIndexBefore;
|
|
204
|
+
index = indexBefore;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const matchOne = () => {
|
|
208
|
+
// pattern consumed and string consumed
|
|
209
|
+
if (remainingPattern === "" && remainingString === "") {
|
|
210
|
+
return true; // string fully matched pattern
|
|
211
|
+
}
|
|
212
|
+
// pattern consumed, string not consumed
|
|
213
|
+
if (remainingPattern === "" && remainingString !== "") {
|
|
214
|
+
return false; // fails because string longer than expected
|
|
215
|
+
}
|
|
216
|
+
// -- from this point pattern is not consumed --
|
|
217
|
+
// string consumed, pattern not consumed
|
|
218
|
+
if (remainingString === "") {
|
|
219
|
+
if (remainingPattern === "**") {
|
|
220
|
+
// trailing "**" is optional
|
|
221
|
+
consumePattern(2);
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if (remainingPattern === "*") {
|
|
225
|
+
groups.push({ string: "" });
|
|
226
|
+
}
|
|
227
|
+
return false; // fail because string shorter than expected
|
|
228
|
+
}
|
|
229
|
+
// -- from this point pattern and string are not consumed --
|
|
230
|
+
// fast path trailing slash
|
|
231
|
+
if (remainingPattern === "/") {
|
|
232
|
+
if (remainingString[0] === "/") {
|
|
233
|
+
// trailing slash match remaining
|
|
234
|
+
consumePattern(1);
|
|
235
|
+
groups.push({ string: consumeRemainingString() });
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
// fast path trailing '**'
|
|
241
|
+
if (remainingPattern === "**") {
|
|
242
|
+
consumePattern(2);
|
|
243
|
+
consumeRemainingString();
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
// pattern leading **
|
|
247
|
+
if (remainingPattern.slice(0, 2) === "**") {
|
|
248
|
+
consumePattern(2); // consumes "**"
|
|
249
|
+
let skipAllowed = true;
|
|
250
|
+
if (remainingPattern[0] === "/") {
|
|
251
|
+
consumePattern(1); // consumes "/"
|
|
252
|
+
// when remainingPattern was preceeded by "**/"
|
|
253
|
+
// and remainingString have no "/"
|
|
254
|
+
// then skip is not allowed, a regular match will be performed
|
|
255
|
+
if (!remainingString.includes("/")) {
|
|
256
|
+
skipAllowed = false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// pattern ending with "**" or "**/" match remaining string
|
|
260
|
+
if (remainingPattern === "") {
|
|
261
|
+
consumeRemainingString();
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
if (skipAllowed) {
|
|
265
|
+
const skipResult = skipUntilMatch({
|
|
266
|
+
pattern: remainingPattern,
|
|
267
|
+
string: remainingString,
|
|
268
|
+
canSkipSlash: true,
|
|
269
|
+
});
|
|
270
|
+
groups.push(...skipResult.groups);
|
|
271
|
+
consumePattern(skipResult.patternIndex);
|
|
272
|
+
consumeRemainingString();
|
|
273
|
+
restoreIndexes = false;
|
|
274
|
+
return skipResult.matched;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (remainingPattern[0] === "*") {
|
|
278
|
+
consumePattern(1); // consumes "*"
|
|
279
|
+
if (remainingPattern === "") {
|
|
280
|
+
// matches everything except "/"
|
|
281
|
+
const slashIndex = remainingString.indexOf("/");
|
|
282
|
+
if (slashIndex === -1) {
|
|
283
|
+
groups.push({ string: consumeRemainingString() });
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
groups.push({ string: consumeString(slashIndex) });
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
// the next char must not the one expected by remainingPattern[0]
|
|
290
|
+
// because * is greedy and expect to skip at least one char
|
|
291
|
+
if (remainingPattern[0] === remainingString[0]) {
|
|
292
|
+
groups.push({ string: "" });
|
|
293
|
+
patternIndex = patternIndex - 1;
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
const skipResult = skipUntilMatch({
|
|
297
|
+
pattern: remainingPattern,
|
|
298
|
+
string: remainingString,
|
|
299
|
+
canSkipSlash: false,
|
|
300
|
+
});
|
|
301
|
+
groups.push(skipResult.group, ...skipResult.groups);
|
|
302
|
+
consumePattern(skipResult.patternIndex);
|
|
303
|
+
consumeString(skipResult.index);
|
|
304
|
+
restoreIndexes = false;
|
|
305
|
+
return skipResult.matched;
|
|
306
|
+
}
|
|
307
|
+
if (remainingPattern[0] !== remainingString[0]) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
return undefined;
|
|
311
|
+
};
|
|
312
|
+
iterate();
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
matched,
|
|
316
|
+
patternIndex,
|
|
317
|
+
index,
|
|
318
|
+
groups,
|
|
319
|
+
};
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const skipUntilMatch = ({ pattern, string, canSkipSlash }) => {
|
|
323
|
+
let index = 0;
|
|
324
|
+
let remainingString = string;
|
|
325
|
+
let longestAttemptRange = null;
|
|
326
|
+
let isLastAttempt = false;
|
|
327
|
+
|
|
328
|
+
const failure = () => {
|
|
329
|
+
return {
|
|
330
|
+
matched: false,
|
|
331
|
+
patternIndex: longestAttemptRange.patternIndex,
|
|
332
|
+
index: longestAttemptRange.index + longestAttemptRange.length,
|
|
333
|
+
groups: longestAttemptRange.groups,
|
|
334
|
+
group: {
|
|
335
|
+
string: string.slice(0, longestAttemptRange.index),
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const tryToMatch = () => {
|
|
341
|
+
const matchAttempt = applyMatching(pattern, remainingString);
|
|
342
|
+
if (matchAttempt.matched) {
|
|
343
|
+
return {
|
|
344
|
+
matched: true,
|
|
345
|
+
patternIndex: matchAttempt.patternIndex,
|
|
346
|
+
index: index + matchAttempt.index,
|
|
347
|
+
groups: matchAttempt.groups,
|
|
348
|
+
group: {
|
|
349
|
+
string:
|
|
350
|
+
remainingString === ""
|
|
351
|
+
? string
|
|
352
|
+
: string.slice(0, -remainingString.length),
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
const attemptIndex = matchAttempt.index;
|
|
357
|
+
const attemptRange = {
|
|
358
|
+
patternIndex: matchAttempt.patternIndex,
|
|
359
|
+
index,
|
|
360
|
+
length: attemptIndex,
|
|
361
|
+
groups: matchAttempt.groups,
|
|
362
|
+
};
|
|
363
|
+
if (
|
|
364
|
+
!longestAttemptRange ||
|
|
365
|
+
longestAttemptRange.length < attemptRange.length
|
|
366
|
+
) {
|
|
367
|
+
longestAttemptRange = attemptRange;
|
|
368
|
+
}
|
|
369
|
+
if (isLastAttempt) {
|
|
370
|
+
return failure();
|
|
371
|
+
}
|
|
372
|
+
const nextIndex = attemptIndex + 1;
|
|
373
|
+
if (nextIndex >= remainingString.length) {
|
|
374
|
+
return failure();
|
|
375
|
+
}
|
|
376
|
+
if (remainingString[0] === "/") {
|
|
377
|
+
if (!canSkipSlash) {
|
|
378
|
+
return failure();
|
|
379
|
+
}
|
|
380
|
+
// when it's the last slash, the next attempt is the last
|
|
381
|
+
if (remainingString.indexOf("/", 1) === -1) {
|
|
382
|
+
isLastAttempt = true;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// search against the next unattempted string
|
|
386
|
+
index += nextIndex;
|
|
387
|
+
remainingString = remainingString.slice(nextIndex);
|
|
388
|
+
return tryToMatch();
|
|
389
|
+
};
|
|
390
|
+
return tryToMatch();
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const applyAssociations = ({ url, associations }) => {
|
|
394
|
+
assertUrlLike(url);
|
|
395
|
+
const flatAssociations = asFlatAssociations(associations);
|
|
396
|
+
return Object.keys(flatAssociations).reduce((previousValue, pattern) => {
|
|
397
|
+
const { matched } = applyPatternMatching({
|
|
398
|
+
pattern,
|
|
399
|
+
url,
|
|
400
|
+
});
|
|
401
|
+
if (matched) {
|
|
402
|
+
const value = flatAssociations[pattern];
|
|
403
|
+
if (isPlainObject(previousValue) && isPlainObject(value)) {
|
|
404
|
+
return {
|
|
405
|
+
...previousValue,
|
|
406
|
+
...value,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
return value;
|
|
410
|
+
}
|
|
411
|
+
return previousValue;
|
|
412
|
+
}, {});
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const applyAliases = ({ url, aliases }) => {
|
|
416
|
+
let aliasFullMatchResult;
|
|
417
|
+
const aliasMatchingKey = Object.keys(aliases).find((key) => {
|
|
418
|
+
const aliasMatchResult = applyPatternMatching({
|
|
419
|
+
pattern: key,
|
|
420
|
+
url,
|
|
421
|
+
});
|
|
422
|
+
if (aliasMatchResult.matched) {
|
|
423
|
+
aliasFullMatchResult = aliasMatchResult;
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
return false;
|
|
427
|
+
});
|
|
428
|
+
if (!aliasMatchingKey) {
|
|
429
|
+
return url;
|
|
430
|
+
}
|
|
431
|
+
const { matchGroups } = aliasFullMatchResult;
|
|
432
|
+
const alias = aliases[aliasMatchingKey];
|
|
433
|
+
const parts = alias.split("*");
|
|
434
|
+
const newUrl = parts.reduce((previous, value, index) => {
|
|
435
|
+
return `${previous}${value}${
|
|
436
|
+
index === parts.length - 1 ? "" : matchGroups[index]
|
|
437
|
+
}`;
|
|
438
|
+
}, "");
|
|
439
|
+
return newUrl;
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const urlChildMayMatch = ({ url, associations, predicate }) => {
|
|
443
|
+
assertUrlLike(url, "url");
|
|
444
|
+
// the function was meants to be used on url ending with '/'
|
|
445
|
+
if (!url.endsWith("/")) {
|
|
446
|
+
throw new Error(`url should end with /, got ${url}`);
|
|
447
|
+
}
|
|
448
|
+
if (typeof predicate !== "function") {
|
|
449
|
+
throw new TypeError(`predicate must be a function, got ${predicate}`);
|
|
450
|
+
}
|
|
451
|
+
const flatAssociations = asFlatAssociations(associations);
|
|
452
|
+
// for full match we must create an object to allow pattern to override previous ones
|
|
453
|
+
let fullMatchMeta = {};
|
|
454
|
+
let someFullMatch = false;
|
|
455
|
+
// for partial match, any meta satisfying predicate will be valid because
|
|
456
|
+
// we don't know for sure if pattern will still match for a file inside pathname
|
|
457
|
+
const partialMatchMetaArray = [];
|
|
458
|
+
Object.keys(flatAssociations).forEach((pattern) => {
|
|
459
|
+
const value = flatAssociations[pattern];
|
|
460
|
+
const matchResult = applyPatternMatching({
|
|
461
|
+
pattern,
|
|
462
|
+
url,
|
|
463
|
+
});
|
|
464
|
+
if (matchResult.matched) {
|
|
465
|
+
someFullMatch = true;
|
|
466
|
+
if (isPlainObject(fullMatchMeta) && isPlainObject(value)) {
|
|
467
|
+
fullMatchMeta = {
|
|
468
|
+
...fullMatchMeta,
|
|
469
|
+
...value,
|
|
470
|
+
};
|
|
471
|
+
} else {
|
|
472
|
+
fullMatchMeta = value;
|
|
473
|
+
}
|
|
474
|
+
} else if (someFullMatch === false && matchResult.urlIndex >= url.length) {
|
|
475
|
+
partialMatchMetaArray.push(value);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
if (someFullMatch) {
|
|
479
|
+
return Boolean(predicate(fullMatchMeta));
|
|
480
|
+
}
|
|
481
|
+
return partialMatchMetaArray.some((partialMatchMeta) =>
|
|
482
|
+
predicate(partialMatchMeta),
|
|
483
|
+
);
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const URL_META = {
|
|
487
|
+
resolveAssociations,
|
|
488
|
+
applyAssociations,
|
|
489
|
+
urlChildMayMatch,
|
|
490
|
+
applyPatternMatching,
|
|
491
|
+
applyAliases,
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
/*
|
|
495
|
+
* data:[<mediatype>][;base64],<data>
|
|
496
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs#syntax
|
|
497
|
+
*/
|
|
498
|
+
|
|
499
|
+
/* eslint-env browser, node */
|
|
500
|
+
|
|
501
|
+
const DATA_URL = {
|
|
502
|
+
parse: (string) => {
|
|
503
|
+
const afterDataProtocol = string.slice("data:".length);
|
|
504
|
+
const commaIndex = afterDataProtocol.indexOf(",");
|
|
505
|
+
const beforeComma = afterDataProtocol.slice(0, commaIndex);
|
|
506
|
+
|
|
507
|
+
let contentType;
|
|
508
|
+
let base64Flag;
|
|
509
|
+
if (beforeComma.endsWith(`;base64`)) {
|
|
510
|
+
contentType = beforeComma.slice(0, -`;base64`.length);
|
|
511
|
+
base64Flag = true;
|
|
512
|
+
} else {
|
|
513
|
+
contentType = beforeComma;
|
|
514
|
+
base64Flag = false;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
contentType =
|
|
518
|
+
contentType === "" ? "text/plain;charset=US-ASCII" : contentType;
|
|
519
|
+
const afterComma = afterDataProtocol.slice(commaIndex + 1);
|
|
520
|
+
return {
|
|
521
|
+
contentType,
|
|
522
|
+
base64Flag,
|
|
523
|
+
data: afterComma,
|
|
524
|
+
};
|
|
525
|
+
},
|
|
526
|
+
|
|
527
|
+
stringify: ({ contentType, base64Flag = true, data }) => {
|
|
528
|
+
if (!contentType || contentType === "text/plain;charset=US-ASCII") {
|
|
529
|
+
// can be a buffer or a string, hence check on data.length instead of !data or data === ''
|
|
530
|
+
if (data.length === 0) {
|
|
531
|
+
return `data:,`;
|
|
532
|
+
}
|
|
533
|
+
if (base64Flag) {
|
|
534
|
+
return `data:;base64,${data}`;
|
|
535
|
+
}
|
|
536
|
+
return `data:,${data}`;
|
|
537
|
+
}
|
|
538
|
+
if (base64Flag) {
|
|
539
|
+
return `data:${contentType};base64,${data}`;
|
|
540
|
+
}
|
|
541
|
+
return `data:${contentType},${data}`;
|
|
542
|
+
},
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// consider switching to https://babeljs.io/docs/en/babel-code-frame
|
|
546
|
+
// https://github.com/postcss/postcss/blob/fd30d3df5abc0954a0ec642a3cdc644ab2aacf9c/lib/css-syntax-error.js#L43
|
|
547
|
+
// https://github.com/postcss/postcss/blob/fd30d3df5abc0954a0ec642a3cdc644ab2aacf9c/lib/terminal-highlight.js#L50
|
|
548
|
+
// https://github.com/babel/babel/blob/eea156b2cb8deecfcf82d52aa1b71ba4995c7d68/packages/babel-code-frame/src/index.js#L1
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
const stringifyUrlSite = (
|
|
552
|
+
{ url, line, column, content },
|
|
553
|
+
{
|
|
554
|
+
showCodeFrame = true,
|
|
555
|
+
numberOfSurroundingLinesToShow,
|
|
556
|
+
lineMaxLength,
|
|
557
|
+
color,
|
|
558
|
+
} = {},
|
|
559
|
+
) => {
|
|
560
|
+
let string = url;
|
|
561
|
+
|
|
562
|
+
if (typeof line === "number") {
|
|
563
|
+
string += `:${line}`;
|
|
564
|
+
if (typeof column === "number") {
|
|
565
|
+
string += `:${column}`;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (!showCodeFrame || typeof line !== "number" || !content) {
|
|
570
|
+
return string;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const sourceLoc = showSourceLocation({
|
|
574
|
+
content,
|
|
575
|
+
line,
|
|
576
|
+
column,
|
|
577
|
+
numberOfSurroundingLinesToShow,
|
|
578
|
+
lineMaxLength,
|
|
579
|
+
color,
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
return `${string}
|
|
583
|
+
${sourceLoc}`;
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const showSourceLocation = ({
|
|
587
|
+
content,
|
|
588
|
+
line,
|
|
589
|
+
column,
|
|
590
|
+
numberOfSurroundingLinesToShow = 1,
|
|
591
|
+
lineMaxLength = 120,
|
|
592
|
+
} = {}) => {
|
|
593
|
+
let mark = (string) => string;
|
|
594
|
+
let aside = (string) => string;
|
|
595
|
+
// if (color) {
|
|
596
|
+
// mark = (string) => ANSI.color(string, ANSI.RED)
|
|
597
|
+
// aside = (string) => ANSI.color(string, ANSI.GREY)
|
|
598
|
+
// }
|
|
599
|
+
|
|
600
|
+
const lines = content.split(/\r?\n/);
|
|
601
|
+
if (line === 0) line = 1;
|
|
602
|
+
let lineRange = {
|
|
603
|
+
start: line - 1,
|
|
604
|
+
end: line,
|
|
605
|
+
};
|
|
606
|
+
lineRange = moveLineRangeUp(lineRange, numberOfSurroundingLinesToShow);
|
|
607
|
+
lineRange = moveLineRangeDown(lineRange, numberOfSurroundingLinesToShow);
|
|
608
|
+
lineRange = lineRangeWithinLines(lineRange, lines);
|
|
609
|
+
const linesToShow = lines.slice(lineRange.start, lineRange.end);
|
|
610
|
+
const endLineNumber = lineRange.end;
|
|
611
|
+
const lineNumberMaxWidth = String(endLineNumber).length;
|
|
612
|
+
|
|
613
|
+
if (column === 0) column = 1;
|
|
614
|
+
|
|
615
|
+
const columnRange = {};
|
|
616
|
+
if (column === undefined) {
|
|
617
|
+
columnRange.start = 0;
|
|
618
|
+
columnRange.end = lineMaxLength;
|
|
619
|
+
} else if (column > lineMaxLength) {
|
|
149
620
|
columnRange.start = column - Math.floor(lineMaxLength / 2);
|
|
150
621
|
columnRange.end = column + Math.ceil(lineMaxLength / 2);
|
|
151
622
|
} else {
|
|
@@ -1021,990 +1492,519 @@ const readStat = (
|
|
|
1021
1492
|
/*
|
|
1022
1493
|
* - Buffer documentation on Node.js
|
|
1023
1494
|
* https://nodejs.org/docs/latest-v13.x/api/buffer.html
|
|
1024
|
-
* - eTag documentation on MDN
|
|
1025
|
-
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
|
|
1026
|
-
*/
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
const ETAG_FOR_EMPTY_CONTENT$1 = '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
|
|
1030
|
-
|
|
1031
|
-
const bufferToEtag$1 = (buffer) => {
|
|
1032
|
-
if (!Buffer.isBuffer(buffer)) {
|
|
1033
|
-
throw new TypeError(`buffer expected, got ${buffer}`);
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
if (buffer.length === 0) {
|
|
1037
|
-
return ETAG_FOR_EMPTY_CONTENT$1;
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
const hash = createHash("sha1");
|
|
1041
|
-
hash.update(buffer, "utf8");
|
|
1042
|
-
|
|
1043
|
-
const hashBase64String = hash.digest("base64");
|
|
1044
|
-
const hashBase64StringSubset = hashBase64String.slice(0, 27);
|
|
1045
|
-
const length = buffer.length;
|
|
1046
|
-
|
|
1047
|
-
return `"${length.toString(16)}-${hashBase64StringSubset}"`;
|
|
1048
|
-
};
|
|
1049
|
-
|
|
1050
|
-
/*
|
|
1051
|
-
* See callback_race.md
|
|
1052
|
-
*/
|
|
1053
|
-
|
|
1054
|
-
const raceCallbacks = (raceDescription, winnerCallback) => {
|
|
1055
|
-
let cleanCallbacks = [];
|
|
1056
|
-
let status = "racing";
|
|
1057
|
-
|
|
1058
|
-
const clean = () => {
|
|
1059
|
-
cleanCallbacks.forEach((clean) => {
|
|
1060
|
-
clean();
|
|
1061
|
-
});
|
|
1062
|
-
cleanCallbacks = null;
|
|
1063
|
-
};
|
|
1064
|
-
|
|
1065
|
-
const cancel = () => {
|
|
1066
|
-
if (status !== "racing") {
|
|
1067
|
-
return;
|
|
1068
|
-
}
|
|
1069
|
-
status = "cancelled";
|
|
1070
|
-
clean();
|
|
1071
|
-
};
|
|
1072
|
-
|
|
1073
|
-
Object.keys(raceDescription).forEach((candidateName) => {
|
|
1074
|
-
const register = raceDescription[candidateName];
|
|
1075
|
-
const returnValue = register((data) => {
|
|
1076
|
-
if (status !== "racing") {
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
status = "done";
|
|
1080
|
-
clean();
|
|
1081
|
-
winnerCallback({
|
|
1082
|
-
name: candidateName,
|
|
1083
|
-
data,
|
|
1084
|
-
});
|
|
1085
|
-
});
|
|
1086
|
-
if (typeof returnValue === "function") {
|
|
1087
|
-
cleanCallbacks.push(returnValue);
|
|
1088
|
-
}
|
|
1089
|
-
});
|
|
1090
|
-
|
|
1091
|
-
return cancel;
|
|
1092
|
-
};
|
|
1093
|
-
|
|
1094
|
-
const createCallbackListNotifiedOnce = () => {
|
|
1095
|
-
let callbacks = [];
|
|
1096
|
-
let status = "waiting";
|
|
1097
|
-
let currentCallbackIndex = -1;
|
|
1098
|
-
|
|
1099
|
-
const callbackListOnce = {};
|
|
1100
|
-
|
|
1101
|
-
const add = (callback) => {
|
|
1102
|
-
if (status !== "waiting") {
|
|
1103
|
-
emitUnexpectedActionWarning({ action: "add", status });
|
|
1104
|
-
return removeNoop;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
if (typeof callback !== "function") {
|
|
1108
|
-
throw new Error(`callback must be a function, got ${callback}`);
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
// don't register twice
|
|
1112
|
-
const existingCallback = callbacks.find((callbackCandidate) => {
|
|
1113
|
-
return callbackCandidate === callback;
|
|
1114
|
-
});
|
|
1115
|
-
if (existingCallback) {
|
|
1116
|
-
emitCallbackDuplicationWarning();
|
|
1117
|
-
return removeNoop;
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
callbacks.push(callback);
|
|
1121
|
-
return () => {
|
|
1122
|
-
if (status === "notified") {
|
|
1123
|
-
// once called removing does nothing
|
|
1124
|
-
// as the callbacks array is frozen to null
|
|
1125
|
-
return;
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
const index = callbacks.indexOf(callback);
|
|
1129
|
-
if (index === -1) {
|
|
1130
|
-
return;
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
if (status === "looping") {
|
|
1134
|
-
if (index <= currentCallbackIndex) {
|
|
1135
|
-
// The callback was already called (or is the current callback)
|
|
1136
|
-
// We don't want to mutate the callbacks array
|
|
1137
|
-
// or it would alter the looping done in "call" and the next callback
|
|
1138
|
-
// would be skipped
|
|
1139
|
-
return;
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
// Callback is part of the next callback to call,
|
|
1143
|
-
// we mutate the callbacks array to prevent this callback to be called
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
callbacks.splice(index, 1);
|
|
1147
|
-
};
|
|
1148
|
-
};
|
|
1149
|
-
|
|
1150
|
-
const notify = (param) => {
|
|
1151
|
-
if (status !== "waiting") {
|
|
1152
|
-
emitUnexpectedActionWarning({ action: "call", status });
|
|
1153
|
-
return [];
|
|
1154
|
-
}
|
|
1155
|
-
status = "looping";
|
|
1156
|
-
const values = callbacks.map((callback, index) => {
|
|
1157
|
-
currentCallbackIndex = index;
|
|
1158
|
-
return callback(param);
|
|
1159
|
-
});
|
|
1160
|
-
callbackListOnce.notified = true;
|
|
1161
|
-
status = "notified";
|
|
1162
|
-
// we reset callbacks to null after looping
|
|
1163
|
-
// so that it's possible to remove during the loop
|
|
1164
|
-
callbacks = null;
|
|
1165
|
-
currentCallbackIndex = -1;
|
|
1166
|
-
|
|
1167
|
-
return values;
|
|
1168
|
-
};
|
|
1169
|
-
|
|
1170
|
-
callbackListOnce.notified = false;
|
|
1171
|
-
callbackListOnce.add = add;
|
|
1172
|
-
callbackListOnce.notify = notify;
|
|
1173
|
-
|
|
1174
|
-
return callbackListOnce;
|
|
1175
|
-
};
|
|
1176
|
-
|
|
1177
|
-
const emitUnexpectedActionWarning = ({ action, status }) => {
|
|
1178
|
-
if (typeof process.emitWarning === "function") {
|
|
1179
|
-
process.emitWarning(
|
|
1180
|
-
`"${action}" should not happen when callback list is ${status}`,
|
|
1181
|
-
{
|
|
1182
|
-
CODE: "UNEXPECTED_ACTION_ON_CALLBACK_LIST",
|
|
1183
|
-
detail: `Code is potentially executed when it should not`,
|
|
1184
|
-
},
|
|
1185
|
-
);
|
|
1186
|
-
} else {
|
|
1187
|
-
console.warn(
|
|
1188
|
-
`"${action}" should not happen when callback list is ${status}`,
|
|
1189
|
-
);
|
|
1190
|
-
}
|
|
1191
|
-
};
|
|
1192
|
-
|
|
1193
|
-
const emitCallbackDuplicationWarning = () => {
|
|
1194
|
-
if (typeof process.emitWarning === "function") {
|
|
1195
|
-
process.emitWarning(`Trying to add a callback already in the list`, {
|
|
1196
|
-
CODE: "CALLBACK_DUPLICATION",
|
|
1197
|
-
detail: `Code is potentially executed more than it should`,
|
|
1198
|
-
});
|
|
1199
|
-
} else {
|
|
1200
|
-
console.warn(`Trying to add same callback twice`);
|
|
1201
|
-
}
|
|
1202
|
-
};
|
|
1203
|
-
|
|
1204
|
-
const removeNoop = () => {};
|
|
1205
|
-
|
|
1206
|
-
/*
|
|
1207
|
-
* https://github.com/whatwg/dom/issues/920
|
|
1208
|
-
*/
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
const Abort = {
|
|
1212
|
-
isAbortError: (error) => {
|
|
1213
|
-
return error && error.name === "AbortError";
|
|
1214
|
-
},
|
|
1495
|
+
* - eTag documentation on MDN
|
|
1496
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
|
|
1497
|
+
*/
|
|
1215
1498
|
|
|
1216
|
-
startOperation: () => {
|
|
1217
|
-
return createOperation();
|
|
1218
|
-
},
|
|
1219
1499
|
|
|
1220
|
-
|
|
1221
|
-
if (signal.aborted) {
|
|
1222
|
-
const error = new Error(`The operation was aborted`);
|
|
1223
|
-
error.name = "AbortError";
|
|
1224
|
-
error.type = "aborted";
|
|
1225
|
-
throw error;
|
|
1226
|
-
}
|
|
1227
|
-
},
|
|
1228
|
-
};
|
|
1500
|
+
const ETAG_FOR_EMPTY_CONTENT$1 = '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
|
|
1229
1501
|
|
|
1230
|
-
const
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1502
|
+
const bufferToEtag$1 = (buffer) => {
|
|
1503
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
1504
|
+
throw new TypeError(`buffer expected, got ${buffer}`);
|
|
1505
|
+
}
|
|
1234
1506
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
// To be 100% sure we don't have memory leak, only Abortable.asyncCallback
|
|
1239
|
-
// uses abortCallbackList to know when something is aborted
|
|
1240
|
-
const abortCallbackList = createCallbackListNotifiedOnce();
|
|
1241
|
-
const endCallbackList = createCallbackListNotifiedOnce();
|
|
1507
|
+
if (buffer.length === 0) {
|
|
1508
|
+
return ETAG_FOR_EMPTY_CONTENT$1;
|
|
1509
|
+
}
|
|
1242
1510
|
|
|
1243
|
-
|
|
1511
|
+
const hash = createHash("sha1");
|
|
1512
|
+
hash.update(buffer, "utf8");
|
|
1244
1513
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1514
|
+
const hashBase64String = hash.digest("base64");
|
|
1515
|
+
const hashBase64StringSubset = hashBase64String.slice(0, 27);
|
|
1516
|
+
const length = buffer.length;
|
|
1247
1517
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
addEndCallback(async () => {
|
|
1251
|
-
await allAbortCallbacksPromise;
|
|
1252
|
-
});
|
|
1253
|
-
}
|
|
1254
|
-
};
|
|
1518
|
+
return `"${length.toString(16)}-${hashBase64StringSubset}"`;
|
|
1519
|
+
};
|
|
1255
1520
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1521
|
+
/*
|
|
1522
|
+
* See callback_race.md
|
|
1523
|
+
*/
|
|
1259
1524
|
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
// - It won't increase the count of listeners for "abort" that would
|
|
1264
|
-
// trigger max listeners warning when count > 10
|
|
1265
|
-
const addAbortCallback = (callback) => {
|
|
1266
|
-
// It would be painful and not super redable to check if signal is aborted
|
|
1267
|
-
// before deciding if it's an abort or end callback
|
|
1268
|
-
// with pseudo-code below where we want to stop server either
|
|
1269
|
-
// on abort or when ended because signal is aborted
|
|
1270
|
-
// operation[operation.signal.aborted ? 'addAbortCallback': 'addEndCallback'](async () => {
|
|
1271
|
-
// await server.stop()
|
|
1272
|
-
// })
|
|
1273
|
-
if (operationSignal.aborted) {
|
|
1274
|
-
return addEndCallback(callback);
|
|
1275
|
-
}
|
|
1276
|
-
return abortCallbackList.add(callback);
|
|
1277
|
-
};
|
|
1525
|
+
const raceCallbacks = (raceDescription, winnerCallback) => {
|
|
1526
|
+
let cleanCallbacks = [];
|
|
1527
|
+
let status = "racing";
|
|
1278
1528
|
|
|
1279
|
-
const
|
|
1280
|
-
|
|
1529
|
+
const clean = () => {
|
|
1530
|
+
cleanCallbacks.forEach((clean) => {
|
|
1531
|
+
clean();
|
|
1532
|
+
});
|
|
1533
|
+
cleanCallbacks = null;
|
|
1281
1534
|
};
|
|
1282
1535
|
|
|
1283
|
-
const
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
// "abortAfterEnd" can be handy to ensure "abort" callbacks
|
|
1287
|
-
// added with { once: true } are removed
|
|
1288
|
-
// It might also help garbage collection because
|
|
1289
|
-
// runtime implementing AbortSignal (Node.js, browsers) can consider abortSignal
|
|
1290
|
-
// as settled and clean up things
|
|
1291
|
-
if (abortAfterEnd) {
|
|
1292
|
-
// because of operationSignal.onabort = null
|
|
1293
|
-
// + abortCallbackList.clear() this won't re-call
|
|
1294
|
-
// callbacks
|
|
1295
|
-
if (!operationSignal.aborted) {
|
|
1296
|
-
isAbortAfterEnd = true;
|
|
1297
|
-
operationAbortController.abort();
|
|
1298
|
-
}
|
|
1536
|
+
const cancel = () => {
|
|
1537
|
+
if (status !== "racing") {
|
|
1538
|
+
return;
|
|
1299
1539
|
}
|
|
1540
|
+
status = "cancelled";
|
|
1541
|
+
clean();
|
|
1300
1542
|
};
|
|
1301
1543
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
if (operationSignal.aborted) {
|
|
1319
|
-
applyAbortEffects();
|
|
1320
|
-
applyRemoveEffects();
|
|
1321
|
-
return callbackNoop;
|
|
1544
|
+
Object.keys(raceDescription).forEach((candidateName) => {
|
|
1545
|
+
const register = raceDescription[candidateName];
|
|
1546
|
+
const returnValue = register((data) => {
|
|
1547
|
+
if (status !== "racing") {
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
status = "done";
|
|
1551
|
+
clean();
|
|
1552
|
+
winnerCallback({
|
|
1553
|
+
name: candidateName,
|
|
1554
|
+
data,
|
|
1555
|
+
});
|
|
1556
|
+
});
|
|
1557
|
+
if (typeof returnValue === "function") {
|
|
1558
|
+
cleanCallbacks.push(returnValue);
|
|
1322
1559
|
}
|
|
1560
|
+
});
|
|
1323
1561
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
applyAbortEffects();
|
|
1327
|
-
applyRemoveEffects();
|
|
1328
|
-
return callbackNoop;
|
|
1329
|
-
}
|
|
1562
|
+
return cancel;
|
|
1563
|
+
};
|
|
1330
1564
|
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
},
|
|
1336
|
-
operation_end: (cb) => {
|
|
1337
|
-
return addEndCallback(cb);
|
|
1338
|
-
},
|
|
1339
|
-
child_abort: (cb) => {
|
|
1340
|
-
return addEventListener(signal, "abort", cb);
|
|
1341
|
-
},
|
|
1342
|
-
},
|
|
1343
|
-
(winner) => {
|
|
1344
|
-
const raceEffects = {
|
|
1345
|
-
// Both "operation_abort" and "operation_end"
|
|
1346
|
-
// means we don't care anymore if the child aborts.
|
|
1347
|
-
// So we can:
|
|
1348
|
-
// - remove "abort" event listener on child (done by raceCallback)
|
|
1349
|
-
// - remove abort callback on operation (done by raceCallback)
|
|
1350
|
-
// - remove end callback on operation (done by raceCallback)
|
|
1351
|
-
// - call any custom cancel function
|
|
1352
|
-
operation_abort: () => {
|
|
1353
|
-
applyAbortEffects();
|
|
1354
|
-
applyRemoveEffects();
|
|
1355
|
-
},
|
|
1356
|
-
operation_end: () => {
|
|
1357
|
-
// Exists to
|
|
1358
|
-
// - remove abort callback on operation
|
|
1359
|
-
// - remove "abort" event listener on child
|
|
1360
|
-
// - call any custom cancel function
|
|
1361
|
-
applyRemoveEffects();
|
|
1362
|
-
},
|
|
1363
|
-
child_abort: () => {
|
|
1364
|
-
applyAbortEffects();
|
|
1365
|
-
operationAbortController.abort();
|
|
1366
|
-
},
|
|
1367
|
-
};
|
|
1368
|
-
raceEffects[winner.name](winner.value);
|
|
1369
|
-
},
|
|
1370
|
-
);
|
|
1565
|
+
const createCallbackListNotifiedOnce = () => {
|
|
1566
|
+
let callbacks = [];
|
|
1567
|
+
let status = "waiting";
|
|
1568
|
+
let currentCallbackIndex = -1;
|
|
1371
1569
|
|
|
1372
|
-
|
|
1373
|
-
cancelRace();
|
|
1374
|
-
applyRemoveEffects();
|
|
1375
|
-
};
|
|
1376
|
-
};
|
|
1570
|
+
const callbackListOnce = {};
|
|
1377
1571
|
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
remove: callbackNoop,
|
|
1383
|
-
};
|
|
1384
|
-
const abortSourceController = new AbortController();
|
|
1385
|
-
const abortSourceSignal = abortSourceController.signal;
|
|
1386
|
-
abortSource.signal = abortSourceSignal;
|
|
1387
|
-
if (operationSignal.aborted) {
|
|
1388
|
-
return abortSource;
|
|
1572
|
+
const add = (callback) => {
|
|
1573
|
+
if (status !== "waiting") {
|
|
1574
|
+
emitUnexpectedActionWarning({ action: "add", status });
|
|
1575
|
+
return removeNoop;
|
|
1389
1576
|
}
|
|
1390
|
-
const returnValue = abortSourceCallback((value) => {
|
|
1391
|
-
abortSourceController.abort(value);
|
|
1392
|
-
});
|
|
1393
|
-
const removeAbortSignal = addAbortSignal(abortSourceSignal, {
|
|
1394
|
-
onRemove: () => {
|
|
1395
|
-
if (typeof returnValue === "function") {
|
|
1396
|
-
returnValue();
|
|
1397
|
-
}
|
|
1398
|
-
abortSource.cleaned = true;
|
|
1399
|
-
},
|
|
1400
|
-
});
|
|
1401
|
-
abortSource.remove = removeAbortSignal;
|
|
1402
|
-
return abortSource;
|
|
1403
|
-
};
|
|
1404
|
-
|
|
1405
|
-
const timeout = (ms) => {
|
|
1406
|
-
return addAbortSource((abort) => {
|
|
1407
|
-
const timeoutId = setTimeout(abort, ms);
|
|
1408
|
-
// an abort source return value is called when:
|
|
1409
|
-
// - operation is aborted (by an other source)
|
|
1410
|
-
// - operation ends
|
|
1411
|
-
return () => {
|
|
1412
|
-
clearTimeout(timeoutId);
|
|
1413
|
-
};
|
|
1414
|
-
});
|
|
1415
|
-
};
|
|
1416
1577
|
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
const signal = abortController.signal;
|
|
1420
|
-
const removeAbortSignal = addAbortSignal(signal, {
|
|
1421
|
-
onAbort: () => {
|
|
1422
|
-
abortController.abort();
|
|
1423
|
-
},
|
|
1424
|
-
});
|
|
1425
|
-
try {
|
|
1426
|
-
const value = await asyncCallback(signal);
|
|
1427
|
-
removeAbortSignal();
|
|
1428
|
-
return value;
|
|
1429
|
-
} catch (e) {
|
|
1430
|
-
removeAbortSignal();
|
|
1431
|
-
throw e;
|
|
1578
|
+
if (typeof callback !== "function") {
|
|
1579
|
+
throw new Error(`callback must be a function, got ${callback}`);
|
|
1432
1580
|
}
|
|
1433
|
-
};
|
|
1434
1581
|
|
|
1435
|
-
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1438
|
-
const removeAbortSignal = addAbortSignal(signal, {
|
|
1439
|
-
onAbort: () => {
|
|
1440
|
-
abortController.abort();
|
|
1441
|
-
},
|
|
1582
|
+
// don't register twice
|
|
1583
|
+
const existingCallback = callbacks.find((callbackCandidate) => {
|
|
1584
|
+
return callbackCandidate === callback;
|
|
1442
1585
|
});
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
return value;
|
|
1447
|
-
} catch (e) {
|
|
1448
|
-
removeAbortSignal();
|
|
1449
|
-
throw e;
|
|
1586
|
+
if (existingCallback) {
|
|
1587
|
+
emitCallbackDuplicationWarning();
|
|
1588
|
+
return removeNoop;
|
|
1450
1589
|
}
|
|
1451
|
-
};
|
|
1452
1590
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
throwIfAborted,
|
|
1461
|
-
addAbortCallback,
|
|
1462
|
-
addAbortSignal,
|
|
1463
|
-
addAbortSource,
|
|
1464
|
-
timeout,
|
|
1465
|
-
withSignal,
|
|
1466
|
-
withSignalSync,
|
|
1467
|
-
addEndCallback,
|
|
1468
|
-
end,
|
|
1469
|
-
};
|
|
1470
|
-
};
|
|
1591
|
+
callbacks.push(callback);
|
|
1592
|
+
return () => {
|
|
1593
|
+
if (status === "notified") {
|
|
1594
|
+
// once called removing does nothing
|
|
1595
|
+
// as the callbacks array is frozen to null
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1471
1598
|
|
|
1472
|
-
const
|
|
1599
|
+
const index = callbacks.indexOf(callback);
|
|
1600
|
+
if (index === -1) {
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1473
1603
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1604
|
+
if (status === "looping") {
|
|
1605
|
+
if (index <= currentCallbackIndex) {
|
|
1606
|
+
// The callback was already called (or is the current callback)
|
|
1607
|
+
// We don't want to mutate the callbacks array
|
|
1608
|
+
// or it would alter the looping done in "call" and the next callback
|
|
1609
|
+
// would be skipped
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1480
1612
|
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
...(processTeardownEvents.SIGHUP ? SIGHUP_CALLBACK : {}),
|
|
1485
|
-
...(processTeardownEvents.SIGTERM ? SIGTERM_CALLBACK : {}),
|
|
1486
|
-
...(processTeardownEvents.SIGINT ? SIGINT_CALLBACK : {}),
|
|
1487
|
-
...(processTeardownEvents.beforeExit ? BEFORE_EXIT_CALLBACK : {}),
|
|
1488
|
-
...(processTeardownEvents.exit ? EXIT_CALLBACK : {}),
|
|
1489
|
-
},
|
|
1490
|
-
callback,
|
|
1491
|
-
);
|
|
1492
|
-
};
|
|
1613
|
+
// Callback is part of the next callback to call,
|
|
1614
|
+
// we mutate the callbacks array to prevent this callback to be called
|
|
1615
|
+
}
|
|
1493
1616
|
|
|
1494
|
-
|
|
1495
|
-
SIGHUP: (cb) => {
|
|
1496
|
-
process.on("SIGHUP", cb);
|
|
1497
|
-
return () => {
|
|
1498
|
-
process.removeListener("SIGHUP", cb);
|
|
1617
|
+
callbacks.splice(index, 1);
|
|
1499
1618
|
};
|
|
1500
|
-
}
|
|
1501
|
-
};
|
|
1619
|
+
};
|
|
1502
1620
|
|
|
1503
|
-
const
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1621
|
+
const notify = (param) => {
|
|
1622
|
+
if (status !== "waiting") {
|
|
1623
|
+
emitUnexpectedActionWarning({ action: "call", status });
|
|
1624
|
+
return [];
|
|
1625
|
+
}
|
|
1626
|
+
status = "looping";
|
|
1627
|
+
const values = callbacks.map((callback, index) => {
|
|
1628
|
+
currentCallbackIndex = index;
|
|
1629
|
+
return callback(param);
|
|
1630
|
+
});
|
|
1631
|
+
callbackListOnce.notified = true;
|
|
1632
|
+
status = "notified";
|
|
1633
|
+
// we reset callbacks to null after looping
|
|
1634
|
+
// so that it's possible to remove during the loop
|
|
1635
|
+
callbacks = null;
|
|
1636
|
+
currentCallbackIndex = -1;
|
|
1511
1637
|
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
process.on("beforeExit", cb);
|
|
1515
|
-
return () => {
|
|
1516
|
-
process.removeListener("beforeExit", cb);
|
|
1517
|
-
};
|
|
1518
|
-
},
|
|
1519
|
-
};
|
|
1638
|
+
return values;
|
|
1639
|
+
};
|
|
1520
1640
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
return () => {
|
|
1525
|
-
process.removeListener("exit", cb);
|
|
1526
|
-
};
|
|
1527
|
-
},
|
|
1528
|
-
};
|
|
1641
|
+
callbackListOnce.notified = false;
|
|
1642
|
+
callbackListOnce.add = add;
|
|
1643
|
+
callbackListOnce.notify = notify;
|
|
1529
1644
|
|
|
1530
|
-
|
|
1531
|
-
SIGINT: (cb) => {
|
|
1532
|
-
process.on("SIGINT", cb);
|
|
1533
|
-
return () => {
|
|
1534
|
-
process.removeListener("SIGINT", cb);
|
|
1535
|
-
};
|
|
1536
|
-
},
|
|
1645
|
+
return callbackListOnce;
|
|
1537
1646
|
};
|
|
1538
1647
|
|
|
1539
|
-
const
|
|
1540
|
-
if (typeof
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1648
|
+
const emitUnexpectedActionWarning = ({ action, status }) => {
|
|
1649
|
+
if (typeof process.emitWarning === "function") {
|
|
1650
|
+
process.emitWarning(
|
|
1651
|
+
`"${action}" should not happen when callback list is ${status}`,
|
|
1652
|
+
{
|
|
1653
|
+
CODE: "UNEXPECTED_ACTION_ON_CALLBACK_LIST",
|
|
1654
|
+
detail: `Code is potentially executed when it should not`,
|
|
1655
|
+
},
|
|
1546
1656
|
);
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
`${name} must be a url and no scheme found, got ${value}`,
|
|
1657
|
+
} else {
|
|
1658
|
+
console.warn(
|
|
1659
|
+
`"${action}" should not happen when callback list is ${status}`,
|
|
1551
1660
|
);
|
|
1552
1661
|
}
|
|
1553
1662
|
};
|
|
1554
1663
|
|
|
1555
|
-
const
|
|
1556
|
-
if (
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
return true;
|
|
1664
|
+
const emitCallbackDuplicationWarning = () => {
|
|
1665
|
+
if (typeof process.emitWarning === "function") {
|
|
1666
|
+
process.emitWarning(`Trying to add a callback already in the list`, {
|
|
1667
|
+
CODE: "CALLBACK_DUPLICATION",
|
|
1668
|
+
detail: `Code is potentially executed more than it should`,
|
|
1669
|
+
});
|
|
1670
|
+
} else {
|
|
1671
|
+
console.warn(`Trying to add same callback twice`);
|
|
1564
1672
|
}
|
|
1565
|
-
return false;
|
|
1566
1673
|
};
|
|
1567
1674
|
|
|
1568
|
-
const
|
|
1569
|
-
const firstChar = specifier[0];
|
|
1570
|
-
if (!/[a-zA-Z]/.test(firstChar)) return false;
|
|
1571
|
-
const secondChar = specifier[1];
|
|
1572
|
-
if (secondChar !== ":") return false;
|
|
1573
|
-
const thirdChar = specifier[2];
|
|
1574
|
-
return thirdChar === "/" || thirdChar === "\\";
|
|
1575
|
-
};
|
|
1675
|
+
const removeNoop = () => {};
|
|
1576
1676
|
|
|
1577
|
-
|
|
1677
|
+
/*
|
|
1678
|
+
* https://github.com/whatwg/dom/issues/920
|
|
1679
|
+
*/
|
|
1578
1680
|
|
|
1579
|
-
const resolveAssociations = (associations, baseUrl) => {
|
|
1580
|
-
assertUrlLike(baseUrl, "baseUrl");
|
|
1581
|
-
const associationsResolved = {};
|
|
1582
|
-
Object.keys(associations).forEach((key) => {
|
|
1583
|
-
const value = associations[key];
|
|
1584
|
-
if (typeof value === "object" && value !== null) {
|
|
1585
|
-
const valueMapResolved = {};
|
|
1586
|
-
Object.keys(value).forEach((pattern) => {
|
|
1587
|
-
const valueAssociated = value[pattern];
|
|
1588
|
-
const patternResolved = normalizeUrlPattern(pattern, baseUrl);
|
|
1589
|
-
valueMapResolved[patternResolved] = valueAssociated;
|
|
1590
|
-
});
|
|
1591
|
-
associationsResolved[key] = valueMapResolved;
|
|
1592
|
-
} else {
|
|
1593
|
-
associationsResolved[key] = value;
|
|
1594
|
-
}
|
|
1595
|
-
});
|
|
1596
|
-
return associationsResolved;
|
|
1597
|
-
};
|
|
1598
1681
|
|
|
1599
|
-
const
|
|
1600
|
-
|
|
1601
|
-
return
|
|
1602
|
-
}
|
|
1603
|
-
// it's not really an url, no need to perform url resolution nor encoding
|
|
1604
|
-
return urlPattern;
|
|
1605
|
-
}
|
|
1606
|
-
};
|
|
1682
|
+
const Abort = {
|
|
1683
|
+
isAbortError: (error) => {
|
|
1684
|
+
return error && error.name === "AbortError";
|
|
1685
|
+
},
|
|
1607
1686
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
Object.keys(associationValue).forEach((pattern) => {
|
|
1619
|
-
const patternValue = associationValue[pattern];
|
|
1620
|
-
const previousValue = flatAssociations[pattern];
|
|
1621
|
-
if (isPlainObject(previousValue)) {
|
|
1622
|
-
flatAssociations[pattern] = {
|
|
1623
|
-
...previousValue,
|
|
1624
|
-
[associationName]: patternValue,
|
|
1625
|
-
};
|
|
1626
|
-
} else {
|
|
1627
|
-
flatAssociations[pattern] = {
|
|
1628
|
-
[associationName]: patternValue,
|
|
1629
|
-
};
|
|
1630
|
-
}
|
|
1631
|
-
});
|
|
1687
|
+
startOperation: () => {
|
|
1688
|
+
return createOperation();
|
|
1689
|
+
},
|
|
1690
|
+
|
|
1691
|
+
throwIfAborted: (signal) => {
|
|
1692
|
+
if (signal.aborted) {
|
|
1693
|
+
const error = new Error(`The operation was aborted`);
|
|
1694
|
+
error.name = "AbortError";
|
|
1695
|
+
error.type = "aborted";
|
|
1696
|
+
throw error;
|
|
1632
1697
|
}
|
|
1633
|
-
}
|
|
1634
|
-
return flatAssociations;
|
|
1698
|
+
},
|
|
1635
1699
|
};
|
|
1636
1700
|
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
*/
|
|
1701
|
+
const createOperation = () => {
|
|
1702
|
+
const operationAbortController = new AbortController();
|
|
1703
|
+
// const abortOperation = (value) => abortController.abort(value)
|
|
1704
|
+
const operationSignal = operationAbortController.signal;
|
|
1642
1705
|
|
|
1706
|
+
// abortCallbackList is used to ignore the max listeners warning from Node.js
|
|
1707
|
+
// this warning is useful but becomes problematic when it's expected
|
|
1708
|
+
// (a function doing 20 http call in parallel)
|
|
1709
|
+
// To be 100% sure we don't have memory leak, only Abortable.asyncCallback
|
|
1710
|
+
// uses abortCallbackList to know when something is aborted
|
|
1711
|
+
const abortCallbackList = createCallbackListNotifiedOnce();
|
|
1712
|
+
const endCallbackList = createCallbackListNotifiedOnce();
|
|
1643
1713
|
|
|
1644
|
-
|
|
1645
|
-
/**
|
|
1646
|
-
* An object representing the result of applying a pattern to an url
|
|
1647
|
-
* @typedef {Object} MatchResult
|
|
1648
|
-
* @property {boolean} matched Indicates if url matched pattern
|
|
1649
|
-
* @property {number} patternIndex Index where pattern stopped matching url, otherwise pattern.length
|
|
1650
|
-
* @property {number} urlIndex Index where url stopped matching pattern, otherwise url.length
|
|
1651
|
-
* @property {Array} matchGroups Array of strings captured during pattern matching
|
|
1652
|
-
*/
|
|
1714
|
+
let isAbortAfterEnd = false;
|
|
1653
1715
|
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
assertUrlLike(pattern, "pattern");
|
|
1663
|
-
assertUrlLike(url, "url");
|
|
1664
|
-
const { matched, patternIndex, index, groups } = applyMatching(pattern, url);
|
|
1665
|
-
const matchGroups = [];
|
|
1666
|
-
let groupIndex = 0;
|
|
1667
|
-
groups.forEach((group) => {
|
|
1668
|
-
if (group.name) {
|
|
1669
|
-
matchGroups[group.name] = group.string;
|
|
1670
|
-
} else {
|
|
1671
|
-
matchGroups[groupIndex] = group.string;
|
|
1672
|
-
groupIndex++;
|
|
1716
|
+
operationSignal.onabort = () => {
|
|
1717
|
+
operationSignal.onabort = null;
|
|
1718
|
+
|
|
1719
|
+
const allAbortCallbacksPromise = Promise.all(abortCallbackList.notify());
|
|
1720
|
+
if (!isAbortAfterEnd) {
|
|
1721
|
+
addEndCallback(async () => {
|
|
1722
|
+
await allAbortCallbacksPromise;
|
|
1723
|
+
});
|
|
1673
1724
|
}
|
|
1674
|
-
});
|
|
1675
|
-
return {
|
|
1676
|
-
matched,
|
|
1677
|
-
patternIndex,
|
|
1678
|
-
urlIndex: index,
|
|
1679
|
-
matchGroups,
|
|
1680
1725
|
};
|
|
1681
|
-
};
|
|
1682
|
-
|
|
1683
|
-
const applyMatching = (pattern, string) => {
|
|
1684
|
-
const groups = [];
|
|
1685
|
-
let patternIndex = 0;
|
|
1686
|
-
let index = 0;
|
|
1687
|
-
let remainingPattern = pattern;
|
|
1688
|
-
let remainingString = string;
|
|
1689
|
-
let restoreIndexes = true;
|
|
1690
1726
|
|
|
1691
|
-
const
|
|
1692
|
-
|
|
1693
|
-
remainingPattern = remainingPattern.slice(count);
|
|
1694
|
-
patternIndex += count;
|
|
1695
|
-
return subpattern;
|
|
1696
|
-
};
|
|
1697
|
-
const consumeString = (count) => {
|
|
1698
|
-
const substring = remainingString.slice(0, count);
|
|
1699
|
-
remainingString = remainingString.slice(count);
|
|
1700
|
-
index += count;
|
|
1701
|
-
return substring;
|
|
1702
|
-
};
|
|
1703
|
-
const consumeRemainingString = () => {
|
|
1704
|
-
return consumeString(remainingString.length);
|
|
1727
|
+
const throwIfAborted = () => {
|
|
1728
|
+
Abort.throwIfAborted(operationSignal);
|
|
1705
1729
|
};
|
|
1706
1730
|
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1731
|
+
// add a callback called on abort
|
|
1732
|
+
// differences with signal.addEventListener('abort')
|
|
1733
|
+
// - operation.end awaits the return value of this callback
|
|
1734
|
+
// - It won't increase the count of listeners for "abort" that would
|
|
1735
|
+
// trigger max listeners warning when count > 10
|
|
1736
|
+
const addAbortCallback = (callback) => {
|
|
1737
|
+
// It would be painful and not super redable to check if signal is aborted
|
|
1738
|
+
// before deciding if it's an abort or end callback
|
|
1739
|
+
// with pseudo-code below where we want to stop server either
|
|
1740
|
+
// on abort or when ended because signal is aborted
|
|
1741
|
+
// operation[operation.signal.aborted ? 'addAbortCallback': 'addEndCallback'](async () => {
|
|
1742
|
+
// await server.stop()
|
|
1743
|
+
// })
|
|
1744
|
+
if (operationSignal.aborted) {
|
|
1745
|
+
return addEndCallback(callback);
|
|
1721
1746
|
}
|
|
1747
|
+
return abortCallbackList.add(callback);
|
|
1722
1748
|
};
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
//
|
|
1733
|
-
//
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
if (
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
return false; // fail because string shorter than expected
|
|
1744
|
-
}
|
|
1745
|
-
// -- from this point pattern and string are not consumed --
|
|
1746
|
-
// fast path trailing slash
|
|
1747
|
-
if (remainingPattern === "/") {
|
|
1748
|
-
if (remainingString[0] === "/") {
|
|
1749
|
-
// trailing slash match remaining
|
|
1750
|
-
consumePattern(1);
|
|
1751
|
-
groups.push({ string: consumeRemainingString() });
|
|
1752
|
-
return true;
|
|
1753
|
-
}
|
|
1754
|
-
return false;
|
|
1755
|
-
}
|
|
1756
|
-
// fast path trailing '**'
|
|
1757
|
-
if (remainingPattern === "**") {
|
|
1758
|
-
consumePattern(2);
|
|
1759
|
-
consumeRemainingString();
|
|
1760
|
-
return true;
|
|
1761
|
-
}
|
|
1762
|
-
// pattern leading **
|
|
1763
|
-
if (remainingPattern.slice(0, 2) === "**") {
|
|
1764
|
-
consumePattern(2); // consumes "**"
|
|
1765
|
-
let skipAllowed = true;
|
|
1766
|
-
if (remainingPattern[0] === "/") {
|
|
1767
|
-
consumePattern(1); // consumes "/"
|
|
1768
|
-
// when remainingPattern was preceeded by "**/"
|
|
1769
|
-
// and remainingString have no "/"
|
|
1770
|
-
// then skip is not allowed, a regular match will be performed
|
|
1771
|
-
if (!remainingString.includes("/")) {
|
|
1772
|
-
skipAllowed = false;
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
// pattern ending with "**" or "**/" match remaining string
|
|
1776
|
-
if (remainingPattern === "") {
|
|
1777
|
-
consumeRemainingString();
|
|
1778
|
-
return true;
|
|
1779
|
-
}
|
|
1780
|
-
if (skipAllowed) {
|
|
1781
|
-
const skipResult = skipUntilMatch({
|
|
1782
|
-
pattern: remainingPattern,
|
|
1783
|
-
string: remainingString,
|
|
1784
|
-
canSkipSlash: true,
|
|
1785
|
-
});
|
|
1786
|
-
groups.push(...skipResult.groups);
|
|
1787
|
-
consumePattern(skipResult.patternIndex);
|
|
1788
|
-
consumeRemainingString();
|
|
1789
|
-
restoreIndexes = false;
|
|
1790
|
-
return skipResult.matched;
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
if (remainingPattern[0] === "*") {
|
|
1794
|
-
consumePattern(1); // consumes "*"
|
|
1795
|
-
if (remainingPattern === "") {
|
|
1796
|
-
// matches everything except "/"
|
|
1797
|
-
const slashIndex = remainingString.indexOf("/");
|
|
1798
|
-
if (slashIndex === -1) {
|
|
1799
|
-
groups.push({ string: consumeRemainingString() });
|
|
1800
|
-
return true;
|
|
1801
|
-
}
|
|
1802
|
-
groups.push({ string: consumeString(slashIndex) });
|
|
1803
|
-
return false;
|
|
1804
|
-
}
|
|
1805
|
-
// the next char must not the one expected by remainingPattern[0]
|
|
1806
|
-
// because * is greedy and expect to skip at least one char
|
|
1807
|
-
if (remainingPattern[0] === remainingString[0]) {
|
|
1808
|
-
groups.push({ string: "" });
|
|
1809
|
-
patternIndex = patternIndex - 1;
|
|
1810
|
-
return false;
|
|
1749
|
+
|
|
1750
|
+
const addEndCallback = (callback) => {
|
|
1751
|
+
return endCallbackList.add(callback);
|
|
1752
|
+
};
|
|
1753
|
+
|
|
1754
|
+
const end = async ({ abortAfterEnd = false } = {}) => {
|
|
1755
|
+
await Promise.all(endCallbackList.notify());
|
|
1756
|
+
|
|
1757
|
+
// "abortAfterEnd" can be handy to ensure "abort" callbacks
|
|
1758
|
+
// added with { once: true } are removed
|
|
1759
|
+
// It might also help garbage collection because
|
|
1760
|
+
// runtime implementing AbortSignal (Node.js, browsers) can consider abortSignal
|
|
1761
|
+
// as settled and clean up things
|
|
1762
|
+
if (abortAfterEnd) {
|
|
1763
|
+
// because of operationSignal.onabort = null
|
|
1764
|
+
// + abortCallbackList.clear() this won't re-call
|
|
1765
|
+
// callbacks
|
|
1766
|
+
if (!operationSignal.aborted) {
|
|
1767
|
+
isAbortAfterEnd = true;
|
|
1768
|
+
operationAbortController.abort();
|
|
1811
1769
|
}
|
|
1812
|
-
const skipResult = skipUntilMatch({
|
|
1813
|
-
pattern: remainingPattern,
|
|
1814
|
-
string: remainingString,
|
|
1815
|
-
canSkipSlash: false,
|
|
1816
|
-
});
|
|
1817
|
-
groups.push(skipResult.group, ...skipResult.groups);
|
|
1818
|
-
consumePattern(skipResult.patternIndex);
|
|
1819
|
-
consumeString(skipResult.index);
|
|
1820
|
-
restoreIndexes = false;
|
|
1821
|
-
return skipResult.matched;
|
|
1822
|
-
}
|
|
1823
|
-
if (remainingPattern[0] !== remainingString[0]) {
|
|
1824
|
-
return false;
|
|
1825
1770
|
}
|
|
1826
|
-
return undefined;
|
|
1827
1771
|
};
|
|
1828
|
-
iterate();
|
|
1829
1772
|
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1773
|
+
const addAbortSignal = (
|
|
1774
|
+
signal,
|
|
1775
|
+
{ onAbort = callbackNoop, onRemove = callbackNoop } = {},
|
|
1776
|
+
) => {
|
|
1777
|
+
const applyAbortEffects = () => {
|
|
1778
|
+
const onAbortCallback = onAbort;
|
|
1779
|
+
onAbort = callbackNoop;
|
|
1780
|
+
onAbortCallback();
|
|
1781
|
+
};
|
|
1782
|
+
const applyRemoveEffects = () => {
|
|
1783
|
+
const onRemoveCallback = onRemove;
|
|
1784
|
+
onRemove = callbackNoop;
|
|
1785
|
+
onAbort = callbackNoop;
|
|
1786
|
+
onRemoveCallback();
|
|
1787
|
+
};
|
|
1788
|
+
|
|
1789
|
+
if (operationSignal.aborted) {
|
|
1790
|
+
applyAbortEffects();
|
|
1791
|
+
applyRemoveEffects();
|
|
1792
|
+
return callbackNoop;
|
|
1793
|
+
}
|
|
1837
1794
|
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1795
|
+
if (signal.aborted) {
|
|
1796
|
+
operationAbortController.abort();
|
|
1797
|
+
applyAbortEffects();
|
|
1798
|
+
applyRemoveEffects();
|
|
1799
|
+
return callbackNoop;
|
|
1800
|
+
}
|
|
1843
1801
|
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1802
|
+
const cancelRace = raceCallbacks(
|
|
1803
|
+
{
|
|
1804
|
+
operation_abort: (cb) => {
|
|
1805
|
+
return addAbortCallback(cb);
|
|
1806
|
+
},
|
|
1807
|
+
operation_end: (cb) => {
|
|
1808
|
+
return addEndCallback(cb);
|
|
1809
|
+
},
|
|
1810
|
+
child_abort: (cb) => {
|
|
1811
|
+
return addEventListener(signal, "abort", cb);
|
|
1812
|
+
},
|
|
1813
|
+
},
|
|
1814
|
+
(winner) => {
|
|
1815
|
+
const raceEffects = {
|
|
1816
|
+
// Both "operation_abort" and "operation_end"
|
|
1817
|
+
// means we don't care anymore if the child aborts.
|
|
1818
|
+
// So we can:
|
|
1819
|
+
// - remove "abort" event listener on child (done by raceCallback)
|
|
1820
|
+
// - remove abort callback on operation (done by raceCallback)
|
|
1821
|
+
// - remove end callback on operation (done by raceCallback)
|
|
1822
|
+
// - call any custom cancel function
|
|
1823
|
+
operation_abort: () => {
|
|
1824
|
+
applyAbortEffects();
|
|
1825
|
+
applyRemoveEffects();
|
|
1826
|
+
},
|
|
1827
|
+
operation_end: () => {
|
|
1828
|
+
// Exists to
|
|
1829
|
+
// - remove abort callback on operation
|
|
1830
|
+
// - remove "abort" event listener on child
|
|
1831
|
+
// - call any custom cancel function
|
|
1832
|
+
applyRemoveEffects();
|
|
1833
|
+
},
|
|
1834
|
+
child_abort: () => {
|
|
1835
|
+
applyAbortEffects();
|
|
1836
|
+
operationAbortController.abort();
|
|
1837
|
+
},
|
|
1838
|
+
};
|
|
1839
|
+
raceEffects[winner.name](winner.value);
|
|
1852
1840
|
},
|
|
1841
|
+
);
|
|
1842
|
+
|
|
1843
|
+
return () => {
|
|
1844
|
+
cancelRace();
|
|
1845
|
+
applyRemoveEffects();
|
|
1853
1846
|
};
|
|
1854
1847
|
};
|
|
1855
1848
|
|
|
1856
|
-
const
|
|
1857
|
-
const
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
patternIndex: matchAttempt.patternIndex,
|
|
1862
|
-
index: index + matchAttempt.index,
|
|
1863
|
-
groups: matchAttempt.groups,
|
|
1864
|
-
group: {
|
|
1865
|
-
string:
|
|
1866
|
-
remainingString === ""
|
|
1867
|
-
? string
|
|
1868
|
-
: string.slice(0, -remainingString.length),
|
|
1869
|
-
},
|
|
1870
|
-
};
|
|
1871
|
-
}
|
|
1872
|
-
const attemptIndex = matchAttempt.index;
|
|
1873
|
-
const attemptRange = {
|
|
1874
|
-
patternIndex: matchAttempt.patternIndex,
|
|
1875
|
-
index,
|
|
1876
|
-
length: attemptIndex,
|
|
1877
|
-
groups: matchAttempt.groups,
|
|
1849
|
+
const addAbortSource = (abortSourceCallback) => {
|
|
1850
|
+
const abortSource = {
|
|
1851
|
+
cleaned: false,
|
|
1852
|
+
signal: null,
|
|
1853
|
+
remove: callbackNoop,
|
|
1878
1854
|
};
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
) {
|
|
1883
|
-
|
|
1884
|
-
}
|
|
1885
|
-
if (isLastAttempt) {
|
|
1886
|
-
return failure();
|
|
1887
|
-
}
|
|
1888
|
-
const nextIndex = attemptIndex + 1;
|
|
1889
|
-
if (nextIndex >= remainingString.length) {
|
|
1890
|
-
return failure();
|
|
1855
|
+
const abortSourceController = new AbortController();
|
|
1856
|
+
const abortSourceSignal = abortSourceController.signal;
|
|
1857
|
+
abortSource.signal = abortSourceSignal;
|
|
1858
|
+
if (operationSignal.aborted) {
|
|
1859
|
+
return abortSource;
|
|
1891
1860
|
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1861
|
+
const returnValue = abortSourceCallback((value) => {
|
|
1862
|
+
abortSourceController.abort(value);
|
|
1863
|
+
});
|
|
1864
|
+
const removeAbortSignal = addAbortSignal(abortSourceSignal, {
|
|
1865
|
+
onRemove: () => {
|
|
1866
|
+
if (typeof returnValue === "function") {
|
|
1867
|
+
returnValue();
|
|
1868
|
+
}
|
|
1869
|
+
abortSource.cleaned = true;
|
|
1870
|
+
},
|
|
1871
|
+
});
|
|
1872
|
+
abortSource.remove = removeAbortSignal;
|
|
1873
|
+
return abortSource;
|
|
1874
|
+
};
|
|
1875
|
+
|
|
1876
|
+
const timeout = (ms) => {
|
|
1877
|
+
return addAbortSource((abort) => {
|
|
1878
|
+
const timeoutId = setTimeout(abort, ms);
|
|
1879
|
+
// an abort source return value is called when:
|
|
1880
|
+
// - operation is aborted (by an other source)
|
|
1881
|
+
// - operation ends
|
|
1882
|
+
return () => {
|
|
1883
|
+
clearTimeout(timeoutId);
|
|
1884
|
+
};
|
|
1885
|
+
});
|
|
1886
|
+
};
|
|
1887
|
+
|
|
1888
|
+
const withSignal = async (asyncCallback) => {
|
|
1889
|
+
const abortController = new AbortController();
|
|
1890
|
+
const signal = abortController.signal;
|
|
1891
|
+
const removeAbortSignal = addAbortSignal(signal, {
|
|
1892
|
+
onAbort: () => {
|
|
1893
|
+
abortController.abort();
|
|
1894
|
+
},
|
|
1895
|
+
});
|
|
1896
|
+
try {
|
|
1897
|
+
const value = await asyncCallback(signal);
|
|
1898
|
+
removeAbortSignal();
|
|
1899
|
+
return value;
|
|
1900
|
+
} catch (e) {
|
|
1901
|
+
removeAbortSignal();
|
|
1902
|
+
throw e;
|
|
1900
1903
|
}
|
|
1901
|
-
// search against the next unattempted string
|
|
1902
|
-
index += nextIndex;
|
|
1903
|
-
remainingString = remainingString.slice(nextIndex);
|
|
1904
|
-
return tryToMatch();
|
|
1905
1904
|
};
|
|
1906
|
-
return tryToMatch();
|
|
1907
|
-
};
|
|
1908
1905
|
|
|
1909
|
-
const
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1906
|
+
const withSignalSync = (callback) => {
|
|
1907
|
+
const abortController = new AbortController();
|
|
1908
|
+
const signal = abortController.signal;
|
|
1909
|
+
const removeAbortSignal = addAbortSignal(signal, {
|
|
1910
|
+
onAbort: () => {
|
|
1911
|
+
abortController.abort();
|
|
1912
|
+
},
|
|
1916
1913
|
});
|
|
1917
|
-
|
|
1918
|
-
const value =
|
|
1919
|
-
|
|
1920
|
-
return {
|
|
1921
|
-
...previousValue,
|
|
1922
|
-
...value,
|
|
1923
|
-
};
|
|
1924
|
-
}
|
|
1914
|
+
try {
|
|
1915
|
+
const value = callback(signal);
|
|
1916
|
+
removeAbortSignal();
|
|
1925
1917
|
return value;
|
|
1918
|
+
} catch (e) {
|
|
1919
|
+
removeAbortSignal();
|
|
1920
|
+
throw e;
|
|
1926
1921
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1922
|
+
};
|
|
1923
|
+
|
|
1924
|
+
return {
|
|
1925
|
+
// We could almost hide the operationSignal
|
|
1926
|
+
// But it can be handy for 2 things:
|
|
1927
|
+
// - know if operation is aborted (operation.signal.aborted)
|
|
1928
|
+
// - forward the operation.signal directly (not using "withSignal" or "withSignalSync")
|
|
1929
|
+
signal: operationSignal,
|
|
1930
|
+
|
|
1931
|
+
throwIfAborted,
|
|
1932
|
+
addAbortCallback,
|
|
1933
|
+
addAbortSignal,
|
|
1934
|
+
addAbortSource,
|
|
1935
|
+
timeout,
|
|
1936
|
+
withSignal,
|
|
1937
|
+
withSignalSync,
|
|
1938
|
+
addEndCallback,
|
|
1939
|
+
end,
|
|
1940
|
+
};
|
|
1941
|
+
};
|
|
1942
|
+
|
|
1943
|
+
const callbackNoop = () => {};
|
|
1944
|
+
|
|
1945
|
+
const addEventListener = (target, eventName, cb) => {
|
|
1946
|
+
target.addEventListener(eventName, cb);
|
|
1947
|
+
return () => {
|
|
1948
|
+
target.removeEventListener(eventName, cb);
|
|
1949
|
+
};
|
|
1950
|
+
};
|
|
1951
|
+
|
|
1952
|
+
const raceProcessTeardownEvents = (processTeardownEvents, callback) => {
|
|
1953
|
+
return raceCallbacks(
|
|
1954
|
+
{
|
|
1955
|
+
...(processTeardownEvents.SIGHUP ? SIGHUP_CALLBACK : {}),
|
|
1956
|
+
...(processTeardownEvents.SIGTERM ? SIGTERM_CALLBACK : {}),
|
|
1957
|
+
...(processTeardownEvents.SIGINT ? SIGINT_CALLBACK : {}),
|
|
1958
|
+
...(processTeardownEvents.beforeExit ? BEFORE_EXIT_CALLBACK : {}),
|
|
1959
|
+
...(processTeardownEvents.exit ? EXIT_CALLBACK : {}),
|
|
1960
|
+
},
|
|
1961
|
+
callback,
|
|
1962
|
+
);
|
|
1963
|
+
};
|
|
1964
|
+
|
|
1965
|
+
const SIGHUP_CALLBACK = {
|
|
1966
|
+
SIGHUP: (cb) => {
|
|
1967
|
+
process.on("SIGHUP", cb);
|
|
1968
|
+
return () => {
|
|
1969
|
+
process.removeListener("SIGHUP", cb);
|
|
1970
|
+
};
|
|
1971
|
+
},
|
|
1929
1972
|
};
|
|
1930
1973
|
|
|
1931
|
-
const
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
if (aliasMatchResult.matched) {
|
|
1939
|
-
aliasFullMatchResult = aliasMatchResult;
|
|
1940
|
-
return true;
|
|
1941
|
-
}
|
|
1942
|
-
return false;
|
|
1943
|
-
});
|
|
1944
|
-
if (!aliasMatchingKey) {
|
|
1945
|
-
return url;
|
|
1946
|
-
}
|
|
1947
|
-
const { matchGroups } = aliasFullMatchResult;
|
|
1948
|
-
const alias = aliases[aliasMatchingKey];
|
|
1949
|
-
const parts = alias.split("*");
|
|
1950
|
-
const newUrl = parts.reduce((previous, value, index) => {
|
|
1951
|
-
return `${previous}${value}${
|
|
1952
|
-
index === parts.length - 1 ? "" : matchGroups[index]
|
|
1953
|
-
}`;
|
|
1954
|
-
}, "");
|
|
1955
|
-
return newUrl;
|
|
1974
|
+
const SIGTERM_CALLBACK = {
|
|
1975
|
+
SIGTERM: (cb) => {
|
|
1976
|
+
process.on("SIGTERM", cb);
|
|
1977
|
+
return () => {
|
|
1978
|
+
process.removeListener("SIGTERM", cb);
|
|
1979
|
+
};
|
|
1980
|
+
},
|
|
1956
1981
|
};
|
|
1957
1982
|
|
|
1958
|
-
const
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
throw new TypeError(`predicate must be a function, got ${predicate}`);
|
|
1966
|
-
}
|
|
1967
|
-
const flatAssociations = asFlatAssociations(associations);
|
|
1968
|
-
// for full match we must create an object to allow pattern to override previous ones
|
|
1969
|
-
let fullMatchMeta = {};
|
|
1970
|
-
let someFullMatch = false;
|
|
1971
|
-
// for partial match, any meta satisfying predicate will be valid because
|
|
1972
|
-
// we don't know for sure if pattern will still match for a file inside pathname
|
|
1973
|
-
const partialMatchMetaArray = [];
|
|
1974
|
-
Object.keys(flatAssociations).forEach((pattern) => {
|
|
1975
|
-
const value = flatAssociations[pattern];
|
|
1976
|
-
const matchResult = applyPatternMatching({
|
|
1977
|
-
pattern,
|
|
1978
|
-
url,
|
|
1979
|
-
});
|
|
1980
|
-
if (matchResult.matched) {
|
|
1981
|
-
someFullMatch = true;
|
|
1982
|
-
if (isPlainObject(fullMatchMeta) && isPlainObject(value)) {
|
|
1983
|
-
fullMatchMeta = {
|
|
1984
|
-
...fullMatchMeta,
|
|
1985
|
-
...value,
|
|
1986
|
-
};
|
|
1987
|
-
} else {
|
|
1988
|
-
fullMatchMeta = value;
|
|
1989
|
-
}
|
|
1990
|
-
} else if (someFullMatch === false && matchResult.urlIndex >= url.length) {
|
|
1991
|
-
partialMatchMetaArray.push(value);
|
|
1992
|
-
}
|
|
1993
|
-
});
|
|
1994
|
-
if (someFullMatch) {
|
|
1995
|
-
return Boolean(predicate(fullMatchMeta));
|
|
1996
|
-
}
|
|
1997
|
-
return partialMatchMetaArray.some((partialMatchMeta) =>
|
|
1998
|
-
predicate(partialMatchMeta),
|
|
1999
|
-
);
|
|
1983
|
+
const BEFORE_EXIT_CALLBACK = {
|
|
1984
|
+
beforeExit: (cb) => {
|
|
1985
|
+
process.on("beforeExit", cb);
|
|
1986
|
+
return () => {
|
|
1987
|
+
process.removeListener("beforeExit", cb);
|
|
1988
|
+
};
|
|
1989
|
+
},
|
|
2000
1990
|
};
|
|
2001
1991
|
|
|
2002
|
-
const
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
1992
|
+
const EXIT_CALLBACK = {
|
|
1993
|
+
exit: (cb) => {
|
|
1994
|
+
process.on("exit", cb);
|
|
1995
|
+
return () => {
|
|
1996
|
+
process.removeListener("exit", cb);
|
|
1997
|
+
};
|
|
1998
|
+
},
|
|
1999
|
+
};
|
|
2000
|
+
|
|
2001
|
+
const SIGINT_CALLBACK = {
|
|
2002
|
+
SIGINT: (cb) => {
|
|
2003
|
+
process.on("SIGINT", cb);
|
|
2004
|
+
return () => {
|
|
2005
|
+
process.removeListener("SIGINT", cb);
|
|
2006
|
+
};
|
|
2007
|
+
},
|
|
2008
2008
|
};
|
|
2009
2009
|
|
|
2010
2010
|
const readDirectory = async (url, { emfileMaxWait = 1000 } = {}) => {
|
|
@@ -8088,6 +8088,102 @@ const generateAccessControlHeaders = ({
|
|
|
8088
8088
|
};
|
|
8089
8089
|
};
|
|
8090
8090
|
|
|
8091
|
+
const WEB_URL_CONVERTER = {
|
|
8092
|
+
asWebUrl: (fileUrl, webServer) => {
|
|
8093
|
+
if (urlIsInsideOf(fileUrl, webServer.rootDirectoryUrl)) {
|
|
8094
|
+
return moveUrl({
|
|
8095
|
+
url: fileUrl,
|
|
8096
|
+
from: webServer.rootDirectoryUrl,
|
|
8097
|
+
to: `${webServer.origin}/`,
|
|
8098
|
+
});
|
|
8099
|
+
}
|
|
8100
|
+
const fsRootUrl = ensureWindowsDriveLetter("file:///", fileUrl);
|
|
8101
|
+
return `${webServer.origin}/@fs/${fileUrl.slice(fsRootUrl.length)}`;
|
|
8102
|
+
},
|
|
8103
|
+
asFileUrl: (webUrl, webServer) => {
|
|
8104
|
+
const { pathname, search } = new URL(webUrl);
|
|
8105
|
+
if (pathname.startsWith("/@fs/")) {
|
|
8106
|
+
const fsRootRelativeUrl = pathname.slice("/@fs/".length);
|
|
8107
|
+
return `file:///${fsRootRelativeUrl}${search}`;
|
|
8108
|
+
}
|
|
8109
|
+
return moveUrl({
|
|
8110
|
+
url: webUrl,
|
|
8111
|
+
from: `${webServer.origin}/`,
|
|
8112
|
+
to: webServer.rootDirectoryUrl,
|
|
8113
|
+
});
|
|
8114
|
+
},
|
|
8115
|
+
};
|
|
8116
|
+
|
|
8117
|
+
const watchSourceFiles = (
|
|
8118
|
+
sourceDirectoryUrl,
|
|
8119
|
+
callback,
|
|
8120
|
+
{ sourceFileConfig = {}, keepProcessAlive, cooldownBetweenFileEvents },
|
|
8121
|
+
) => {
|
|
8122
|
+
// Project should use a dedicated directory (usually "src/")
|
|
8123
|
+
// passed to the dev server via "sourceDirectoryUrl" param
|
|
8124
|
+
// In that case all files inside the source directory should be watched
|
|
8125
|
+
// But some project might want to use their root directory as source directory
|
|
8126
|
+
// In that case source directory might contain files matching "node_modules/*" or ".git/*"
|
|
8127
|
+
// And jsenv should not consider these as source files and watch them (to not hurt performances)
|
|
8128
|
+
const watchPatterns = {
|
|
8129
|
+
"**/*": true, // by default watch everything inside the source directory
|
|
8130
|
+
// line below is commented until @jsenv/url-meta fixes the fact that is matches
|
|
8131
|
+
// any file with an extension
|
|
8132
|
+
"**/.*": false, // file starting with a dot -> do not watch
|
|
8133
|
+
"**/.*/": false, // directory starting with a dot -> do not watch
|
|
8134
|
+
"**/node_modules/": false, // node_modules directory -> do not watch
|
|
8135
|
+
...sourceFileConfig,
|
|
8136
|
+
};
|
|
8137
|
+
const stopWatchingSourceFiles = registerDirectoryLifecycle(
|
|
8138
|
+
sourceDirectoryUrl,
|
|
8139
|
+
{
|
|
8140
|
+
watchPatterns,
|
|
8141
|
+
cooldownBetweenFileEvents,
|
|
8142
|
+
keepProcessAlive,
|
|
8143
|
+
recursive: true,
|
|
8144
|
+
added: ({ relativeUrl }) => {
|
|
8145
|
+
callback({
|
|
8146
|
+
url: new URL(relativeUrl, sourceDirectoryUrl).href,
|
|
8147
|
+
event: "added",
|
|
8148
|
+
});
|
|
8149
|
+
},
|
|
8150
|
+
updated: ({ relativeUrl }) => {
|
|
8151
|
+
callback({
|
|
8152
|
+
url: new URL(relativeUrl, sourceDirectoryUrl).href,
|
|
8153
|
+
event: "modified",
|
|
8154
|
+
});
|
|
8155
|
+
},
|
|
8156
|
+
removed: ({ relativeUrl }) => {
|
|
8157
|
+
callback({
|
|
8158
|
+
url: new URL(relativeUrl, sourceDirectoryUrl).href,
|
|
8159
|
+
event: "removed",
|
|
8160
|
+
});
|
|
8161
|
+
},
|
|
8162
|
+
},
|
|
8163
|
+
);
|
|
8164
|
+
stopWatchingSourceFiles.watchPatterns = watchPatterns;
|
|
8165
|
+
return stopWatchingSourceFiles;
|
|
8166
|
+
};
|
|
8167
|
+
|
|
8168
|
+
const createEventEmitter = () => {
|
|
8169
|
+
const callbackSet = new Set();
|
|
8170
|
+
const on = (callback) => {
|
|
8171
|
+
callbackSet.add(callback);
|
|
8172
|
+
return () => {
|
|
8173
|
+
callbackSet.delete(callback);
|
|
8174
|
+
};
|
|
8175
|
+
};
|
|
8176
|
+
const off = (callback) => {
|
|
8177
|
+
callbackSet.delete(callback);
|
|
8178
|
+
};
|
|
8179
|
+
const emit = (...args) => {
|
|
8180
|
+
callbackSet.forEach((callback) => {
|
|
8181
|
+
callback(...args);
|
|
8182
|
+
});
|
|
8183
|
+
};
|
|
8184
|
+
return { on, off, emit };
|
|
8185
|
+
};
|
|
8186
|
+
|
|
8091
8187
|
const lookupPackageDirectory = (currentUrl) => {
|
|
8092
8188
|
if (currentUrl === "file:///") {
|
|
8093
8189
|
return null;
|
|
@@ -11023,58 +11119,7 @@ const jsenvPluginTranspilation = ({
|
|
|
11023
11119
|
: []),
|
|
11024
11120
|
|
|
11025
11121
|
...(css ? [jsenvPluginCssTranspilation()] : []),
|
|
11026
|
-
];
|
|
11027
|
-
};
|
|
11028
|
-
|
|
11029
|
-
const watchSourceFiles = (
|
|
11030
|
-
sourceDirectoryUrl,
|
|
11031
|
-
callback,
|
|
11032
|
-
{ sourceFileConfig = {}, keepProcessAlive, cooldownBetweenFileEvents },
|
|
11033
|
-
) => {
|
|
11034
|
-
// Project should use a dedicated directory (usually "src/")
|
|
11035
|
-
// passed to the dev server via "sourceDirectoryUrl" param
|
|
11036
|
-
// In that case all files inside the source directory should be watched
|
|
11037
|
-
// But some project might want to use their root directory as source directory
|
|
11038
|
-
// In that case source directory might contain files matching "node_modules/*" or ".git/*"
|
|
11039
|
-
// And jsenv should not consider these as source files and watch them (to not hurt performances)
|
|
11040
|
-
const watchPatterns = {
|
|
11041
|
-
"**/*": true, // by default watch everything inside the source directory
|
|
11042
|
-
// line below is commented until @jsenv/url-meta fixes the fact that is matches
|
|
11043
|
-
// any file with an extension
|
|
11044
|
-
"**/.*": false, // file starting with a dot -> do not watch
|
|
11045
|
-
"**/.*/": false, // directory starting with a dot -> do not watch
|
|
11046
|
-
"**/node_modules/": false, // node_modules directory -> do not watch
|
|
11047
|
-
...sourceFileConfig,
|
|
11048
|
-
};
|
|
11049
|
-
const stopWatchingSourceFiles = registerDirectoryLifecycle(
|
|
11050
|
-
sourceDirectoryUrl,
|
|
11051
|
-
{
|
|
11052
|
-
watchPatterns,
|
|
11053
|
-
cooldownBetweenFileEvents,
|
|
11054
|
-
keepProcessAlive,
|
|
11055
|
-
recursive: true,
|
|
11056
|
-
added: ({ relativeUrl }) => {
|
|
11057
|
-
callback({
|
|
11058
|
-
url: new URL(relativeUrl, sourceDirectoryUrl).href,
|
|
11059
|
-
event: "added",
|
|
11060
|
-
});
|
|
11061
|
-
},
|
|
11062
|
-
updated: ({ relativeUrl }) => {
|
|
11063
|
-
callback({
|
|
11064
|
-
url: new URL(relativeUrl, sourceDirectoryUrl).href,
|
|
11065
|
-
event: "modified",
|
|
11066
|
-
});
|
|
11067
|
-
},
|
|
11068
|
-
removed: ({ relativeUrl }) => {
|
|
11069
|
-
callback({
|
|
11070
|
-
url: new URL(relativeUrl, sourceDirectoryUrl).href,
|
|
11071
|
-
event: "removed",
|
|
11072
|
-
});
|
|
11073
|
-
},
|
|
11074
|
-
},
|
|
11075
|
-
);
|
|
11076
|
-
stopWatchingSourceFiles.watchPatterns = watchPatterns;
|
|
11077
|
-
return stopWatchingSourceFiles;
|
|
11122
|
+
];
|
|
11078
11123
|
};
|
|
11079
11124
|
|
|
11080
11125
|
const GRAPH_VISITOR = {};
|
|
@@ -11209,25 +11254,6 @@ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced = (initialUrlInfo, callback) => {
|
|
|
11209
11254
|
seen.clear();
|
|
11210
11255
|
};
|
|
11211
11256
|
|
|
11212
|
-
const createEventEmitter = () => {
|
|
11213
|
-
const callbackSet = new Set();
|
|
11214
|
-
const on = (callback) => {
|
|
11215
|
-
callbackSet.add(callback);
|
|
11216
|
-
return () => {
|
|
11217
|
-
callbackSet.delete(callback);
|
|
11218
|
-
};
|
|
11219
|
-
};
|
|
11220
|
-
const off = (callback) => {
|
|
11221
|
-
callbackSet.delete(callback);
|
|
11222
|
-
};
|
|
11223
|
-
const emit = (...args) => {
|
|
11224
|
-
callbackSet.forEach((callback) => {
|
|
11225
|
-
callback(...args);
|
|
11226
|
-
});
|
|
11227
|
-
};
|
|
11228
|
-
return { on, off, emit };
|
|
11229
|
-
};
|
|
11230
|
-
|
|
11231
11257
|
const urlSpecifierEncoding = {
|
|
11232
11258
|
encode: (reference) => {
|
|
11233
11259
|
const { generatedSpecifier } = reference;
|
|
@@ -14967,7 +14993,7 @@ const jsenvPluginDataUrlsAnalysis = () => {
|
|
|
14967
14993
|
contentType: urlInfo.contentType,
|
|
14968
14994
|
base64Flag: urlInfo.data.base64Flag,
|
|
14969
14995
|
data: urlInfo.data.base64Flag
|
|
14970
|
-
? dataToBase64(urlInfo.content)
|
|
14996
|
+
? dataToBase64$1(urlInfo.content)
|
|
14971
14997
|
: String(urlInfo.content),
|
|
14972
14998
|
});
|
|
14973
14999
|
return specifier;
|
|
@@ -15001,8 +15027,9 @@ const jsenvPluginDataUrlsAnalysis = () => {
|
|
|
15001
15027
|
data: urlData,
|
|
15002
15028
|
} = DATA_URL.parse(urlInfo.url);
|
|
15003
15029
|
urlInfo.data.base64Flag = base64Flag;
|
|
15030
|
+
const content = contentFromUrlData({ contentType, base64Flag, urlData });
|
|
15004
15031
|
return {
|
|
15005
|
-
content
|
|
15032
|
+
content,
|
|
15006
15033
|
contentType,
|
|
15007
15034
|
};
|
|
15008
15035
|
},
|
|
@@ -15025,7 +15052,7 @@ const contentFromUrlData = ({ contentType, base64Flag, urlData }) => {
|
|
|
15025
15052
|
const base64ToBuffer = (base64String) => Buffer.from(base64String, "base64");
|
|
15026
15053
|
const base64ToString = (base64String) =>
|
|
15027
15054
|
Buffer.from(base64String, "base64").toString("utf8");
|
|
15028
|
-
const dataToBase64 = (data) => Buffer.from(data).toString("base64");
|
|
15055
|
+
const dataToBase64$1 = (data) => Buffer.from(data).toString("base64");
|
|
15029
15056
|
|
|
15030
15057
|
// duplicated from @jsenv/log to avoid the dependency
|
|
15031
15058
|
const createDetailedMessage = (message, details = {}) => {
|
|
@@ -18386,9 +18413,11 @@ const jsenvPluginInliningAsDataUrl = () => {
|
|
|
18386
18413
|
return (async () => {
|
|
18387
18414
|
await urlInfoInlined.cook();
|
|
18388
18415
|
const base64Url = DATA_URL.stringify({
|
|
18389
|
-
|
|
18416
|
+
contentType: urlInfoInlined.contentType,
|
|
18390
18417
|
base64Flag: true,
|
|
18391
|
-
data: urlInfoInlined.
|
|
18418
|
+
data: urlInfoInlined.data.base64Flag
|
|
18419
|
+
? urlInfoInlined.content
|
|
18420
|
+
: dataToBase64(urlInfoInlined.content),
|
|
18392
18421
|
});
|
|
18393
18422
|
return base64Url;
|
|
18394
18423
|
})();
|
|
@@ -18403,6 +18432,7 @@ const jsenvPluginInliningAsDataUrl = () => {
|
|
|
18403
18432
|
const contentAsBase64 = Buffer.from(
|
|
18404
18433
|
withoutBase64ParamUrlInfo.content,
|
|
18405
18434
|
).toString("base64");
|
|
18435
|
+
urlInfo.data.base64Flag = true;
|
|
18406
18436
|
return {
|
|
18407
18437
|
originalContent: withoutBase64ParamUrlInfo.originalContent,
|
|
18408
18438
|
content: contentAsBase64,
|
|
@@ -18412,6 +18442,8 @@ const jsenvPluginInliningAsDataUrl = () => {
|
|
|
18412
18442
|
};
|
|
18413
18443
|
};
|
|
18414
18444
|
|
|
18445
|
+
const dataToBase64 = (data) => Buffer.from(data).toString("base64");
|
|
18446
|
+
|
|
18415
18447
|
const jsenvPluginInliningIntoHtml = () => {
|
|
18416
18448
|
return {
|
|
18417
18449
|
name: "jsenv:inlining_into_html",
|
|
@@ -22102,32 +22134,6 @@ build ${entryPointKeys.length} entry points`);
|
|
|
22102
22134
|
return stopWatchingSourceFiles;
|
|
22103
22135
|
};
|
|
22104
22136
|
|
|
22105
|
-
const WEB_URL_CONVERTER = {
|
|
22106
|
-
asWebUrl: (fileUrl, webServer) => {
|
|
22107
|
-
if (urlIsInsideOf(fileUrl, webServer.rootDirectoryUrl)) {
|
|
22108
|
-
return moveUrl({
|
|
22109
|
-
url: fileUrl,
|
|
22110
|
-
from: webServer.rootDirectoryUrl,
|
|
22111
|
-
to: `${webServer.origin}/`,
|
|
22112
|
-
});
|
|
22113
|
-
}
|
|
22114
|
-
const fsRootUrl = ensureWindowsDriveLetter("file:///", fileUrl);
|
|
22115
|
-
return `${webServer.origin}/@fs/${fileUrl.slice(fsRootUrl.length)}`;
|
|
22116
|
-
},
|
|
22117
|
-
asFileUrl: (webUrl, webServer) => {
|
|
22118
|
-
const { pathname, search } = new URL(webUrl);
|
|
22119
|
-
if (pathname.startsWith("/@fs/")) {
|
|
22120
|
-
const fsRootRelativeUrl = pathname.slice("/@fs/".length);
|
|
22121
|
-
return `file:///${fsRootRelativeUrl}${search}`;
|
|
22122
|
-
}
|
|
22123
|
-
return moveUrl({
|
|
22124
|
-
url: webUrl,
|
|
22125
|
-
from: `${webServer.origin}/`,
|
|
22126
|
-
to: webServer.rootDirectoryUrl,
|
|
22127
|
-
});
|
|
22128
|
-
},
|
|
22129
|
-
};
|
|
22130
|
-
|
|
22131
22137
|
/*
|
|
22132
22138
|
* This plugin is very special because it is here
|
|
22133
22139
|
* to provide "serverEvents" used by other plugins
|
|
@@ -22178,432 +22184,31 @@ const memoizeByFirstArgument = (compute) => {
|
|
|
22178
22184
|
fnWithMemoization.forget = () => {
|
|
22179
22185
|
urlCache.clear();
|
|
22180
22186
|
};
|
|
22181
|
-
|
|
22182
|
-
return fnWithMemoization;
|
|
22183
|
-
};
|
|
22184
|
-
|
|
22185
|
-
const requireFromJsenv = createRequire(import.meta.url);
|
|
22186
|
-
|
|
22187
|
-
const parseUserAgentHeader = memoizeByFirstArgument((userAgent) => {
|
|
22188
|
-
if (userAgent.includes("node-fetch/")) {
|
|
22189
|
-
// it's not really node and conceptually we can't assume the node version
|
|
22190
|
-
// but good enough for now
|
|
22191
|
-
return {
|
|
22192
|
-
runtimeName: "node",
|
|
22193
|
-
runtimeVersion: process.version.slice(1),
|
|
22194
|
-
};
|
|
22195
|
-
}
|
|
22196
|
-
const UA = requireFromJsenv("@financial-times/polyfill-useragent-normaliser");
|
|
22197
|
-
const { ua } = new UA(userAgent);
|
|
22198
|
-
const { family, major, minor, patch } = ua;
|
|
22199
|
-
return {
|
|
22200
|
-
runtimeName: family.toLowerCase(),
|
|
22201
|
-
runtimeVersion:
|
|
22202
|
-
family === "Other" ? "unknown" : `${major}.${minor}${patch}`,
|
|
22203
|
-
};
|
|
22204
|
-
});
|
|
22205
|
-
|
|
22206
|
-
const createFileService = ({
|
|
22207
|
-
signal,
|
|
22208
|
-
logLevel,
|
|
22209
|
-
serverStopCallbacks,
|
|
22210
|
-
serverEventsDispatcher,
|
|
22211
|
-
kitchenCache,
|
|
22212
|
-
onKitchenCreated = () => {},
|
|
22213
|
-
|
|
22214
|
-
sourceDirectoryUrl,
|
|
22215
|
-
sourceMainFilePath,
|
|
22216
|
-
ignore,
|
|
22217
|
-
sourceFilesConfig,
|
|
22218
|
-
runtimeCompat,
|
|
22219
|
-
|
|
22220
|
-
plugins,
|
|
22221
|
-
referenceAnalysis,
|
|
22222
|
-
nodeEsmResolution,
|
|
22223
|
-
magicExtensions,
|
|
22224
|
-
magicDirectoryIndex,
|
|
22225
|
-
supervisor,
|
|
22226
|
-
injections,
|
|
22227
|
-
transpilation,
|
|
22228
|
-
clientAutoreload,
|
|
22229
|
-
cacheControl,
|
|
22230
|
-
ribbon,
|
|
22231
|
-
sourcemaps,
|
|
22232
|
-
sourcemapsSourcesContent,
|
|
22233
|
-
outDirectoryUrl,
|
|
22234
|
-
}) => {
|
|
22235
|
-
if (clientAutoreload === true) {
|
|
22236
|
-
clientAutoreload = {};
|
|
22237
|
-
}
|
|
22238
|
-
if (clientAutoreload === false) {
|
|
22239
|
-
clientAutoreload = { enabled: false };
|
|
22240
|
-
}
|
|
22241
|
-
const clientFileChangeEventEmitter = createEventEmitter();
|
|
22242
|
-
const clientFileDereferencedEventEmitter = createEventEmitter();
|
|
22243
|
-
|
|
22244
|
-
clientAutoreload = {
|
|
22245
|
-
enabled: true,
|
|
22246
|
-
clientServerEventsConfig: {},
|
|
22247
|
-
clientFileChangeEventEmitter,
|
|
22248
|
-
clientFileDereferencedEventEmitter,
|
|
22249
|
-
...clientAutoreload,
|
|
22250
|
-
};
|
|
22251
|
-
|
|
22252
|
-
const stopWatchingSourceFiles = watchSourceFiles(
|
|
22253
|
-
sourceDirectoryUrl,
|
|
22254
|
-
(fileInfo) => {
|
|
22255
|
-
clientFileChangeEventEmitter.emit(fileInfo);
|
|
22256
|
-
},
|
|
22257
|
-
{
|
|
22258
|
-
sourceFilesConfig,
|
|
22259
|
-
keepProcessAlive: false,
|
|
22260
|
-
cooldownBetweenFileEvents: clientAutoreload.cooldownBetweenFileEvents,
|
|
22261
|
-
},
|
|
22262
|
-
);
|
|
22263
|
-
serverStopCallbacks.push(stopWatchingSourceFiles);
|
|
22264
|
-
|
|
22265
|
-
const getOrCreateKitchen = (request) => {
|
|
22266
|
-
const { runtimeName, runtimeVersion } = parseUserAgentHeader(
|
|
22267
|
-
request.headers["user-agent"] || "",
|
|
22268
|
-
);
|
|
22269
|
-
const runtimeId = `${runtimeName}@${runtimeVersion}`;
|
|
22270
|
-
const existing = kitchenCache.get(runtimeId);
|
|
22271
|
-
if (existing) {
|
|
22272
|
-
return existing;
|
|
22273
|
-
}
|
|
22274
|
-
const watchAssociations = URL_META.resolveAssociations(
|
|
22275
|
-
{ watch: stopWatchingSourceFiles.watchPatterns },
|
|
22276
|
-
sourceDirectoryUrl,
|
|
22277
|
-
);
|
|
22278
|
-
let kitchen;
|
|
22279
|
-
clientFileChangeEventEmitter.on(({ url }) => {
|
|
22280
|
-
const urlInfo = kitchen.graph.getUrlInfo(url);
|
|
22281
|
-
if (urlInfo) {
|
|
22282
|
-
urlInfo.onModified();
|
|
22283
|
-
}
|
|
22284
|
-
});
|
|
22285
|
-
const clientRuntimeCompat = { [runtimeName]: runtimeVersion };
|
|
22286
|
-
|
|
22287
|
-
kitchen = createKitchen({
|
|
22288
|
-
name: runtimeId,
|
|
22289
|
-
signal,
|
|
22290
|
-
logLevel,
|
|
22291
|
-
rootDirectoryUrl: sourceDirectoryUrl,
|
|
22292
|
-
mainFilePath: sourceMainFilePath,
|
|
22293
|
-
ignore,
|
|
22294
|
-
dev: true,
|
|
22295
|
-
runtimeCompat,
|
|
22296
|
-
clientRuntimeCompat,
|
|
22297
|
-
plugins: [
|
|
22298
|
-
...plugins,
|
|
22299
|
-
...getCorePlugins({
|
|
22300
|
-
rootDirectoryUrl: sourceDirectoryUrl,
|
|
22301
|
-
runtimeCompat,
|
|
22302
|
-
|
|
22303
|
-
referenceAnalysis,
|
|
22304
|
-
nodeEsmResolution,
|
|
22305
|
-
magicExtensions,
|
|
22306
|
-
magicDirectoryIndex,
|
|
22307
|
-
supervisor,
|
|
22308
|
-
injections,
|
|
22309
|
-
transpilation,
|
|
22310
|
-
|
|
22311
|
-
clientAutoreload,
|
|
22312
|
-
cacheControl,
|
|
22313
|
-
ribbon,
|
|
22314
|
-
}),
|
|
22315
|
-
],
|
|
22316
|
-
supervisor,
|
|
22317
|
-
minification: false,
|
|
22318
|
-
sourcemaps,
|
|
22319
|
-
sourcemapsSourcesContent,
|
|
22320
|
-
outDirectoryUrl: outDirectoryUrl
|
|
22321
|
-
? new URL(`${runtimeName}@${runtimeVersion}/`, outDirectoryUrl)
|
|
22322
|
-
: undefined,
|
|
22323
|
-
});
|
|
22324
|
-
kitchen.graph.urlInfoCreatedEventEmitter.on((urlInfoCreated) => {
|
|
22325
|
-
const { watch } = URL_META.applyAssociations({
|
|
22326
|
-
url: urlInfoCreated.url,
|
|
22327
|
-
associations: watchAssociations,
|
|
22328
|
-
});
|
|
22329
|
-
urlInfoCreated.isWatched = watch;
|
|
22330
|
-
// when an url depends on many others, we check all these (like package.json)
|
|
22331
|
-
urlInfoCreated.isValid = () => {
|
|
22332
|
-
if (!urlInfoCreated.url.startsWith("file:")) {
|
|
22333
|
-
return false;
|
|
22334
|
-
}
|
|
22335
|
-
if (urlInfoCreated.content === undefined) {
|
|
22336
|
-
// urlInfo content is undefined when:
|
|
22337
|
-
// - url info content never fetched
|
|
22338
|
-
// - it is considered as modified because undelying file is watched and got saved
|
|
22339
|
-
// - it is considered as modified because underlying file content
|
|
22340
|
-
// was compared using etag and it has changed
|
|
22341
|
-
return false;
|
|
22342
|
-
}
|
|
22343
|
-
if (!watch) {
|
|
22344
|
-
// file is not watched, check the filesystem
|
|
22345
|
-
let fileContentAsBuffer;
|
|
22346
|
-
try {
|
|
22347
|
-
fileContentAsBuffer = readFileSync(new URL(urlInfoCreated.url));
|
|
22348
|
-
} catch (e) {
|
|
22349
|
-
if (e.code === "ENOENT") {
|
|
22350
|
-
urlInfoCreated.onModified();
|
|
22351
|
-
return false;
|
|
22352
|
-
}
|
|
22353
|
-
return false;
|
|
22354
|
-
}
|
|
22355
|
-
const fileContentEtag = bufferToEtag$1(fileContentAsBuffer);
|
|
22356
|
-
if (fileContentEtag !== urlInfoCreated.originalContentEtag) {
|
|
22357
|
-
urlInfoCreated.onModified();
|
|
22358
|
-
// restore content to be able to compare it again later
|
|
22359
|
-
urlInfoCreated.kitchen.urlInfoTransformer.setContent(
|
|
22360
|
-
urlInfoCreated,
|
|
22361
|
-
String(fileContentAsBuffer),
|
|
22362
|
-
{
|
|
22363
|
-
contentEtag: fileContentEtag,
|
|
22364
|
-
},
|
|
22365
|
-
);
|
|
22366
|
-
return false;
|
|
22367
|
-
}
|
|
22368
|
-
}
|
|
22369
|
-
for (const implicitUrl of urlInfoCreated.implicitUrlSet) {
|
|
22370
|
-
const implicitUrlInfo = urlInfoCreated.graph.getUrlInfo(implicitUrl);
|
|
22371
|
-
if (implicitUrlInfo && !implicitUrlInfo.isValid()) {
|
|
22372
|
-
return false;
|
|
22373
|
-
}
|
|
22374
|
-
}
|
|
22375
|
-
return true;
|
|
22376
|
-
};
|
|
22377
|
-
});
|
|
22378
|
-
kitchen.graph.urlInfoDereferencedEventEmitter.on(
|
|
22379
|
-
(urlInfoDereferenced, lastReferenceFromOther) => {
|
|
22380
|
-
clientFileDereferencedEventEmitter.emit(
|
|
22381
|
-
urlInfoDereferenced,
|
|
22382
|
-
lastReferenceFromOther,
|
|
22383
|
-
);
|
|
22384
|
-
},
|
|
22385
|
-
);
|
|
22386
|
-
|
|
22387
|
-
serverStopCallbacks.push(() => {
|
|
22388
|
-
kitchen.pluginController.callHooks("destroy", kitchen.context);
|
|
22389
|
-
});
|
|
22390
|
-
{
|
|
22391
|
-
const allServerEvents = {};
|
|
22392
|
-
kitchen.pluginController.plugins.forEach((plugin) => {
|
|
22393
|
-
const { serverEvents } = plugin;
|
|
22394
|
-
if (serverEvents) {
|
|
22395
|
-
Object.keys(serverEvents).forEach((serverEventName) => {
|
|
22396
|
-
// we could throw on serverEvent name conflict
|
|
22397
|
-
// we could throw if serverEvents[serverEventName] is not a function
|
|
22398
|
-
allServerEvents[serverEventName] = serverEvents[serverEventName];
|
|
22399
|
-
});
|
|
22400
|
-
}
|
|
22401
|
-
});
|
|
22402
|
-
const serverEventNames = Object.keys(allServerEvents);
|
|
22403
|
-
if (serverEventNames.length > 0) {
|
|
22404
|
-
Object.keys(allServerEvents).forEach((serverEventName) => {
|
|
22405
|
-
const serverEventInfo = {
|
|
22406
|
-
...kitchen.context,
|
|
22407
|
-
sendServerEvent: (data) => {
|
|
22408
|
-
serverEventsDispatcher.dispatch({
|
|
22409
|
-
type: serverEventName,
|
|
22410
|
-
data,
|
|
22411
|
-
});
|
|
22412
|
-
},
|
|
22413
|
-
};
|
|
22414
|
-
const serverEventInit = allServerEvents[serverEventName];
|
|
22415
|
-
serverEventInit(serverEventInfo);
|
|
22416
|
-
});
|
|
22417
|
-
// "pushPlugin" so that event source client connection can be put as early as possible in html
|
|
22418
|
-
kitchen.pluginController.pushPlugin(
|
|
22419
|
-
jsenvPluginServerEventsClientInjection(
|
|
22420
|
-
clientAutoreload.clientServerEventsConfig,
|
|
22421
|
-
),
|
|
22422
|
-
);
|
|
22423
|
-
}
|
|
22424
|
-
}
|
|
22425
|
-
|
|
22426
|
-
kitchenCache.set(runtimeId, kitchen);
|
|
22427
|
-
onKitchenCreated(kitchen);
|
|
22428
|
-
return kitchen;
|
|
22429
|
-
};
|
|
22430
|
-
|
|
22431
|
-
return async (request) => {
|
|
22432
|
-
const kitchen = getOrCreateKitchen(request);
|
|
22433
|
-
const serveHookInfo = {
|
|
22434
|
-
...kitchen.context,
|
|
22435
|
-
request,
|
|
22436
|
-
};
|
|
22437
|
-
const responseFromPlugin =
|
|
22438
|
-
await kitchen.pluginController.callAsyncHooksUntil(
|
|
22439
|
-
"serve",
|
|
22440
|
-
serveHookInfo,
|
|
22441
|
-
);
|
|
22442
|
-
if (responseFromPlugin) {
|
|
22443
|
-
return responseFromPlugin;
|
|
22444
|
-
}
|
|
22445
|
-
const { referer } = request.headers;
|
|
22446
|
-
const parentUrl = referer
|
|
22447
|
-
? WEB_URL_CONVERTER.asFileUrl(referer, {
|
|
22448
|
-
origin: request.origin,
|
|
22449
|
-
rootDirectoryUrl: sourceDirectoryUrl,
|
|
22450
|
-
})
|
|
22451
|
-
: sourceDirectoryUrl;
|
|
22452
|
-
let reference = kitchen.graph.inferReference(request.resource, parentUrl);
|
|
22453
|
-
if (!reference) {
|
|
22454
|
-
reference =
|
|
22455
|
-
kitchen.graph.rootUrlInfo.dependencies.createResolveAndFinalize({
|
|
22456
|
-
trace: { message: parentUrl },
|
|
22457
|
-
type: "http_request",
|
|
22458
|
-
specifier: request.resource,
|
|
22459
|
-
});
|
|
22460
|
-
}
|
|
22461
|
-
const urlInfo = reference.urlInfo;
|
|
22462
|
-
const ifNoneMatch = request.headers["if-none-match"];
|
|
22463
|
-
const urlInfoTargetedByCache = urlInfo.findParentIfInline() || urlInfo;
|
|
22464
|
-
|
|
22465
|
-
try {
|
|
22466
|
-
if (!urlInfo.error && ifNoneMatch) {
|
|
22467
|
-
const [clientOriginalContentEtag, clientContentEtag] =
|
|
22468
|
-
ifNoneMatch.split("_");
|
|
22469
|
-
if (
|
|
22470
|
-
urlInfoTargetedByCache.originalContentEtag ===
|
|
22471
|
-
clientOriginalContentEtag &&
|
|
22472
|
-
urlInfoTargetedByCache.contentEtag === clientContentEtag &&
|
|
22473
|
-
urlInfoTargetedByCache.isValid()
|
|
22474
|
-
) {
|
|
22475
|
-
const headers = {
|
|
22476
|
-
"cache-control": `private,max-age=0,must-revalidate`,
|
|
22477
|
-
};
|
|
22478
|
-
Object.keys(urlInfo.headers).forEach((key) => {
|
|
22479
|
-
if (key !== "content-length") {
|
|
22480
|
-
headers[key] = urlInfo.headers[key];
|
|
22481
|
-
}
|
|
22482
|
-
});
|
|
22483
|
-
return {
|
|
22484
|
-
status: 304,
|
|
22485
|
-
headers,
|
|
22486
|
-
};
|
|
22487
|
-
}
|
|
22488
|
-
}
|
|
22489
|
-
|
|
22490
|
-
await urlInfo.cook({ request, reference });
|
|
22491
|
-
let { response } = urlInfo;
|
|
22492
|
-
if (response) {
|
|
22493
|
-
return response;
|
|
22494
|
-
}
|
|
22495
|
-
response = {
|
|
22496
|
-
url: reference.url,
|
|
22497
|
-
status: 200,
|
|
22498
|
-
headers: {
|
|
22499
|
-
// when we send eTag to the client the next request to the server
|
|
22500
|
-
// will send etag in request headers.
|
|
22501
|
-
// If they match jsenv bypass cooking and returns 304
|
|
22502
|
-
// This must not happen when a plugin uses "no-store" or "no-cache" as it means
|
|
22503
|
-
// plugin logic wants to happens for every request to this url
|
|
22504
|
-
...(urlInfo.headers["cache-control"] === "no-store" ||
|
|
22505
|
-
urlInfo.headers["cache-control"] === "no-cache"
|
|
22506
|
-
? {}
|
|
22507
|
-
: {
|
|
22508
|
-
"cache-control": `private,max-age=0,must-revalidate`,
|
|
22509
|
-
// it's safe to use "_" separator because etag is encoded with base64 (see https://stackoverflow.com/a/13195197)
|
|
22510
|
-
"eTag": `${urlInfoTargetedByCache.originalContentEtag}_${urlInfoTargetedByCache.contentEtag}`,
|
|
22511
|
-
}),
|
|
22512
|
-
...urlInfo.headers,
|
|
22513
|
-
"content-type": urlInfo.contentType,
|
|
22514
|
-
"content-length": urlInfo.contentLength,
|
|
22515
|
-
},
|
|
22516
|
-
body: urlInfo.content,
|
|
22517
|
-
timing: urlInfo.timing,
|
|
22518
|
-
};
|
|
22519
|
-
const augmentResponseInfo = {
|
|
22520
|
-
...kitchen.context,
|
|
22521
|
-
reference,
|
|
22522
|
-
urlInfo,
|
|
22523
|
-
};
|
|
22524
|
-
kitchen.pluginController.callHooks(
|
|
22525
|
-
"augmentResponse",
|
|
22526
|
-
augmentResponseInfo,
|
|
22527
|
-
(returnValue) => {
|
|
22528
|
-
response = composeTwoResponses(response, returnValue);
|
|
22529
|
-
},
|
|
22530
|
-
);
|
|
22531
|
-
return response;
|
|
22532
|
-
} catch (e) {
|
|
22533
|
-
urlInfo.error = e;
|
|
22534
|
-
const originalError = e ? e.cause || e : e;
|
|
22535
|
-
if (originalError.asResponse) {
|
|
22536
|
-
return originalError.asResponse();
|
|
22537
|
-
}
|
|
22538
|
-
const code = originalError.code;
|
|
22539
|
-
if (code === "PARSE_ERROR") {
|
|
22540
|
-
// when possible let browser re-throw the syntax error
|
|
22541
|
-
// it's not possible to do that when url info content is not available
|
|
22542
|
-
// (happens for js_module_fallback for instance)
|
|
22543
|
-
if (urlInfo.content !== undefined) {
|
|
22544
|
-
kitchen.context.logger.error(`Error while handling ${request.url}:
|
|
22545
|
-
${originalError.reasonCode || originalError.code}
|
|
22546
|
-
${e.traceMessage}`);
|
|
22547
|
-
return {
|
|
22548
|
-
url: reference.url,
|
|
22549
|
-
status: 200,
|
|
22550
|
-
// reason becomes the http response statusText, it must not contain invalid chars
|
|
22551
|
-
// https://github.com/nodejs/node/blob/0c27ca4bc9782d658afeaebcec85ec7b28f1cc35/lib/_http_common.js#L221
|
|
22552
|
-
statusText: e.reason,
|
|
22553
|
-
statusMessage: originalError.message,
|
|
22554
|
-
headers: {
|
|
22555
|
-
"content-type": urlInfo.contentType,
|
|
22556
|
-
"content-length": urlInfo.contentLength,
|
|
22557
|
-
"cache-control": "no-store",
|
|
22558
|
-
},
|
|
22559
|
-
body: urlInfo.content,
|
|
22560
|
-
};
|
|
22561
|
-
}
|
|
22562
|
-
return {
|
|
22563
|
-
url: reference.url,
|
|
22564
|
-
status: 500,
|
|
22565
|
-
statusText: e.reason,
|
|
22566
|
-
statusMessage: originalError.message,
|
|
22567
|
-
headers: {
|
|
22568
|
-
"cache-control": "no-store",
|
|
22569
|
-
},
|
|
22570
|
-
body: urlInfo.content,
|
|
22571
|
-
};
|
|
22572
|
-
}
|
|
22573
|
-
if (code === "DIRECTORY_REFERENCE_NOT_ALLOWED") {
|
|
22574
|
-
return serveDirectory(reference.url, {
|
|
22575
|
-
headers: {
|
|
22576
|
-
accept: "text/html",
|
|
22577
|
-
},
|
|
22578
|
-
canReadDirectory: true,
|
|
22579
|
-
rootDirectoryUrl: sourceDirectoryUrl,
|
|
22580
|
-
});
|
|
22581
|
-
}
|
|
22582
|
-
if (code === "NOT_ALLOWED") {
|
|
22583
|
-
return {
|
|
22584
|
-
url: reference.url,
|
|
22585
|
-
status: 403,
|
|
22586
|
-
statusText: originalError.reason,
|
|
22587
|
-
};
|
|
22588
|
-
}
|
|
22589
|
-
if (code === "NOT_FOUND") {
|
|
22590
|
-
return {
|
|
22591
|
-
url: reference.url,
|
|
22592
|
-
status: 404,
|
|
22593
|
-
statusText: originalError.reason,
|
|
22594
|
-
statusMessage: originalError.message,
|
|
22595
|
-
};
|
|
22596
|
-
}
|
|
22597
|
-
return {
|
|
22598
|
-
url: reference.url,
|
|
22599
|
-
status: 500,
|
|
22600
|
-
statusText: e.reason,
|
|
22601
|
-
statusMessage: e.stack,
|
|
22602
|
-
};
|
|
22603
|
-
}
|
|
22604
|
-
};
|
|
22187
|
+
|
|
22188
|
+
return fnWithMemoization;
|
|
22605
22189
|
};
|
|
22606
22190
|
|
|
22191
|
+
const requireFromJsenv = createRequire(import.meta.url);
|
|
22192
|
+
|
|
22193
|
+
const parseUserAgentHeader = memoizeByFirstArgument((userAgent) => {
|
|
22194
|
+
if (userAgent.includes("node-fetch/")) {
|
|
22195
|
+
// it's not really node and conceptually we can't assume the node version
|
|
22196
|
+
// but good enough for now
|
|
22197
|
+
return {
|
|
22198
|
+
runtimeName: "node",
|
|
22199
|
+
runtimeVersion: process.version.slice(1),
|
|
22200
|
+
};
|
|
22201
|
+
}
|
|
22202
|
+
const UA = requireFromJsenv("@financial-times/polyfill-useragent-normaliser");
|
|
22203
|
+
const { ua } = new UA(userAgent);
|
|
22204
|
+
const { family, major, minor, patch } = ua;
|
|
22205
|
+
return {
|
|
22206
|
+
runtimeName: family.toLowerCase(),
|
|
22207
|
+
runtimeVersion:
|
|
22208
|
+
family === "Other" ? "unknown" : `${major}.${minor}${patch}`,
|
|
22209
|
+
};
|
|
22210
|
+
});
|
|
22211
|
+
|
|
22607
22212
|
/**
|
|
22608
22213
|
* Start a server for source files:
|
|
22609
22214
|
* - cook source files according to jsenv plugins
|
|
@@ -22689,6 +22294,16 @@ const startDevServer = async ({
|
|
|
22689
22294
|
}
|
|
22690
22295
|
}
|
|
22691
22296
|
|
|
22297
|
+
// params normalization
|
|
22298
|
+
{
|
|
22299
|
+
if (clientAutoreload === true) {
|
|
22300
|
+
clientAutoreload = {};
|
|
22301
|
+
}
|
|
22302
|
+
if (clientAutoreload === false) {
|
|
22303
|
+
clientAutoreload = { enabled: false };
|
|
22304
|
+
}
|
|
22305
|
+
}
|
|
22306
|
+
|
|
22692
22307
|
const logger = createLogger({ logLevel });
|
|
22693
22308
|
const operation = Abort.startOperation();
|
|
22694
22309
|
operation.addAbortSignal(signal);
|
|
@@ -22712,48 +22327,38 @@ const startDevServer = async ({
|
|
|
22712
22327
|
serverEventsDispatcher.destroy();
|
|
22713
22328
|
});
|
|
22714
22329
|
const kitchenCache = new Map();
|
|
22715
|
-
const server = await startServer({
|
|
22716
|
-
signal,
|
|
22717
|
-
stopOnExit: false,
|
|
22718
|
-
stopOnSIGINT: handleSIGINT,
|
|
22719
|
-
stopOnInternalError: false,
|
|
22720
|
-
keepProcessAlive: process.env.IMPORTED_BY_TEST_PLAN
|
|
22721
|
-
? false
|
|
22722
|
-
: keepProcessAlive,
|
|
22723
|
-
logLevel: serverLogLevel,
|
|
22724
|
-
startLog: false,
|
|
22725
22330
|
|
|
22726
|
-
|
|
22727
|
-
|
|
22728
|
-
|
|
22729
|
-
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
|
|
22733
|
-
|
|
22734
|
-
|
|
22735
|
-
|
|
22736
|
-
|
|
22737
|
-
}
|
|
22738
|
-
|
|
22739
|
-
|
|
22740
|
-
|
|
22741
|
-
|
|
22742
|
-
|
|
22743
|
-
|
|
22744
|
-
|
|
22745
|
-
|
|
22746
|
-
|
|
22747
|
-
|
|
22748
|
-
|
|
22749
|
-
|
|
22750
|
-
|
|
22751
|
-
return null;
|
|
22752
|
-
},
|
|
22753
|
-
injectResponseHeaders: () => {
|
|
22754
|
-
return { server: "jsenv_dev_server/1" };
|
|
22755
|
-
},
|
|
22331
|
+
const finalServices = [];
|
|
22332
|
+
// x-server-inspect service
|
|
22333
|
+
{
|
|
22334
|
+
finalServices.push({
|
|
22335
|
+
handleRequest: (request) => {
|
|
22336
|
+
if (request.headers["x-server-inspect"]) {
|
|
22337
|
+
return { status: 200 };
|
|
22338
|
+
}
|
|
22339
|
+
if (request.pathname === "/__params__.json") {
|
|
22340
|
+
const json = JSON.stringify({
|
|
22341
|
+
sourceDirectoryUrl,
|
|
22342
|
+
});
|
|
22343
|
+
return {
|
|
22344
|
+
status: 200,
|
|
22345
|
+
headers: {
|
|
22346
|
+
"content-type": "application/json",
|
|
22347
|
+
"content-length": Buffer.byteLength(json),
|
|
22348
|
+
},
|
|
22349
|
+
body: json,
|
|
22350
|
+
};
|
|
22351
|
+
}
|
|
22352
|
+
return null;
|
|
22353
|
+
},
|
|
22354
|
+
injectResponseHeaders: () => {
|
|
22355
|
+
return { server: "jsenv_dev_server/1" };
|
|
22756
22356
|
},
|
|
22357
|
+
});
|
|
22358
|
+
}
|
|
22359
|
+
// cors service
|
|
22360
|
+
{
|
|
22361
|
+
finalServices.push(
|
|
22757
22362
|
jsenvServiceCORS({
|
|
22758
22363
|
accessControlAllowRequestOrigin: true,
|
|
22759
22364
|
accessControlAllowRequestMethod: true,
|
|
@@ -22765,86 +22370,453 @@ const startDevServer = async ({
|
|
|
22765
22370
|
accessControlAllowCredentials: true,
|
|
22766
22371
|
timingAllowOrigin: true,
|
|
22767
22372
|
}),
|
|
22768
|
-
|
|
22373
|
+
);
|
|
22374
|
+
}
|
|
22375
|
+
// custom services
|
|
22376
|
+
{
|
|
22377
|
+
finalServices.push(...services);
|
|
22378
|
+
}
|
|
22379
|
+
// file_service
|
|
22380
|
+
{
|
|
22381
|
+
const clientFileChangeEventEmitter = createEventEmitter();
|
|
22382
|
+
const clientFileDereferencedEventEmitter = createEventEmitter();
|
|
22383
|
+
clientAutoreload = {
|
|
22384
|
+
enabled: true,
|
|
22385
|
+
clientServerEventsConfig: {},
|
|
22386
|
+
clientFileChangeEventEmitter,
|
|
22387
|
+
clientFileDereferencedEventEmitter,
|
|
22388
|
+
...clientAutoreload,
|
|
22389
|
+
};
|
|
22390
|
+
const stopWatchingSourceFiles = watchSourceFiles(
|
|
22391
|
+
sourceDirectoryUrl,
|
|
22392
|
+
(fileInfo) => {
|
|
22393
|
+
clientFileChangeEventEmitter.emit(fileInfo);
|
|
22394
|
+
},
|
|
22769
22395
|
{
|
|
22770
|
-
|
|
22771
|
-
|
|
22772
|
-
|
|
22773
|
-
|
|
22774
|
-
|
|
22775
|
-
|
|
22776
|
-
kitchenCache,
|
|
22777
|
-
onKitchenCreated,
|
|
22778
|
-
|
|
22779
|
-
sourceDirectoryUrl,
|
|
22780
|
-
sourceMainFilePath,
|
|
22781
|
-
ignore,
|
|
22782
|
-
sourceFilesConfig,
|
|
22783
|
-
runtimeCompat,
|
|
22396
|
+
sourceFilesConfig,
|
|
22397
|
+
keepProcessAlive: false,
|
|
22398
|
+
cooldownBetweenFileEvents: clientAutoreload.cooldownBetweenFileEvents,
|
|
22399
|
+
},
|
|
22400
|
+
);
|
|
22401
|
+
serverStopCallbacks.push(stopWatchingSourceFiles);
|
|
22784
22402
|
|
|
22785
|
-
|
|
22786
|
-
|
|
22787
|
-
|
|
22788
|
-
|
|
22789
|
-
|
|
22790
|
-
|
|
22791
|
-
|
|
22792
|
-
|
|
22793
|
-
|
|
22794
|
-
|
|
22795
|
-
|
|
22796
|
-
|
|
22797
|
-
|
|
22798
|
-
|
|
22799
|
-
|
|
22800
|
-
|
|
22801
|
-
|
|
22802
|
-
|
|
22403
|
+
const getOrCreateKitchen = (request) => {
|
|
22404
|
+
const { runtimeName, runtimeVersion } = parseUserAgentHeader(
|
|
22405
|
+
request.headers["user-agent"] || "",
|
|
22406
|
+
);
|
|
22407
|
+
const runtimeId = `${runtimeName}@${runtimeVersion}`;
|
|
22408
|
+
const existing = kitchenCache.get(runtimeId);
|
|
22409
|
+
if (existing) {
|
|
22410
|
+
return existing;
|
|
22411
|
+
}
|
|
22412
|
+
const watchAssociations = URL_META.resolveAssociations(
|
|
22413
|
+
{ watch: stopWatchingSourceFiles.watchPatterns },
|
|
22414
|
+
sourceDirectoryUrl,
|
|
22415
|
+
);
|
|
22416
|
+
let kitchen;
|
|
22417
|
+
clientFileChangeEventEmitter.on(({ url }) => {
|
|
22418
|
+
const urlInfo = kitchen.graph.getUrlInfo(url);
|
|
22419
|
+
if (urlInfo) {
|
|
22420
|
+
urlInfo.onModified();
|
|
22421
|
+
}
|
|
22422
|
+
});
|
|
22423
|
+
const clientRuntimeCompat = { [runtimeName]: runtimeVersion };
|
|
22424
|
+
|
|
22425
|
+
kitchen = createKitchen({
|
|
22426
|
+
name: runtimeId,
|
|
22427
|
+
signal,
|
|
22428
|
+
logLevel,
|
|
22429
|
+
rootDirectoryUrl: sourceDirectoryUrl,
|
|
22430
|
+
mainFilePath: sourceMainFilePath,
|
|
22431
|
+
ignore,
|
|
22432
|
+
dev: true,
|
|
22433
|
+
runtimeCompat,
|
|
22434
|
+
clientRuntimeCompat,
|
|
22435
|
+
plugins: [
|
|
22436
|
+
...plugins,
|
|
22437
|
+
...getCorePlugins({
|
|
22438
|
+
rootDirectoryUrl: sourceDirectoryUrl,
|
|
22439
|
+
runtimeCompat,
|
|
22440
|
+
|
|
22441
|
+
referenceAnalysis,
|
|
22442
|
+
nodeEsmResolution,
|
|
22443
|
+
magicExtensions,
|
|
22444
|
+
magicDirectoryIndex,
|
|
22445
|
+
supervisor,
|
|
22446
|
+
injections,
|
|
22447
|
+
transpilation,
|
|
22448
|
+
|
|
22449
|
+
clientAutoreload,
|
|
22450
|
+
cacheControl,
|
|
22451
|
+
ribbon,
|
|
22452
|
+
}),
|
|
22453
|
+
],
|
|
22454
|
+
supervisor,
|
|
22455
|
+
minification: false,
|
|
22456
|
+
sourcemaps,
|
|
22457
|
+
sourcemapsSourcesContent,
|
|
22458
|
+
outDirectoryUrl: outDirectoryUrl
|
|
22459
|
+
? new URL(`${runtimeName}@${runtimeVersion}/`, outDirectoryUrl)
|
|
22460
|
+
: undefined,
|
|
22461
|
+
});
|
|
22462
|
+
kitchen.graph.urlInfoCreatedEventEmitter.on((urlInfoCreated) => {
|
|
22463
|
+
const { watch } = URL_META.applyAssociations({
|
|
22464
|
+
url: urlInfoCreated.url,
|
|
22465
|
+
associations: watchAssociations,
|
|
22466
|
+
});
|
|
22467
|
+
urlInfoCreated.isWatched = watch;
|
|
22468
|
+
// when an url depends on many others, we check all these (like package.json)
|
|
22469
|
+
urlInfoCreated.isValid = () => {
|
|
22470
|
+
if (!urlInfoCreated.url.startsWith("file:")) {
|
|
22471
|
+
return false;
|
|
22472
|
+
}
|
|
22473
|
+
if (urlInfoCreated.content === undefined) {
|
|
22474
|
+
// urlInfo content is undefined when:
|
|
22475
|
+
// - url info content never fetched
|
|
22476
|
+
// - it is considered as modified because undelying file is watched and got saved
|
|
22477
|
+
// - it is considered as modified because underlying file content
|
|
22478
|
+
// was compared using etag and it has changed
|
|
22479
|
+
return false;
|
|
22480
|
+
}
|
|
22481
|
+
if (!watch) {
|
|
22482
|
+
// file is not watched, check the filesystem
|
|
22483
|
+
let fileContentAsBuffer;
|
|
22484
|
+
try {
|
|
22485
|
+
fileContentAsBuffer = readFileSync(new URL(urlInfoCreated.url));
|
|
22486
|
+
} catch (e) {
|
|
22487
|
+
if (e.code === "ENOENT") {
|
|
22488
|
+
urlInfoCreated.onModified();
|
|
22489
|
+
return false;
|
|
22490
|
+
}
|
|
22491
|
+
return false;
|
|
22492
|
+
}
|
|
22493
|
+
const fileContentEtag = bufferToEtag$1(fileContentAsBuffer);
|
|
22494
|
+
if (fileContentEtag !== urlInfoCreated.originalContentEtag) {
|
|
22495
|
+
urlInfoCreated.onModified();
|
|
22496
|
+
// restore content to be able to compare it again later
|
|
22497
|
+
urlInfoCreated.kitchen.urlInfoTransformer.setContent(
|
|
22498
|
+
urlInfoCreated,
|
|
22499
|
+
String(fileContentAsBuffer),
|
|
22500
|
+
{
|
|
22501
|
+
contentEtag: fileContentEtag,
|
|
22502
|
+
},
|
|
22503
|
+
);
|
|
22504
|
+
return false;
|
|
22505
|
+
}
|
|
22506
|
+
}
|
|
22507
|
+
for (const implicitUrl of urlInfoCreated.implicitUrlSet) {
|
|
22508
|
+
const implicitUrlInfo =
|
|
22509
|
+
urlInfoCreated.graph.getUrlInfo(implicitUrl);
|
|
22510
|
+
if (implicitUrlInfo && !implicitUrlInfo.isValid()) {
|
|
22511
|
+
return false;
|
|
22512
|
+
}
|
|
22803
22513
|
}
|
|
22514
|
+
return true;
|
|
22515
|
+
};
|
|
22516
|
+
});
|
|
22517
|
+
kitchen.graph.urlInfoDereferencedEventEmitter.on(
|
|
22518
|
+
(urlInfoDereferenced, lastReferenceFromOther) => {
|
|
22519
|
+
clientFileDereferencedEventEmitter.emit(
|
|
22520
|
+
urlInfoDereferenced,
|
|
22521
|
+
lastReferenceFromOther,
|
|
22522
|
+
);
|
|
22804
22523
|
},
|
|
22805
|
-
|
|
22524
|
+
);
|
|
22525
|
+
|
|
22526
|
+
serverStopCallbacks.push(() => {
|
|
22527
|
+
kitchen.pluginController.callHooks("destroy", kitchen.context);
|
|
22528
|
+
});
|
|
22806
22529
|
{
|
|
22807
|
-
|
|
22808
|
-
|
|
22809
|
-
const
|
|
22810
|
-
|
|
22811
|
-
|
|
22812
|
-
|
|
22530
|
+
const allServerEvents = {};
|
|
22531
|
+
kitchen.pluginController.plugins.forEach((plugin) => {
|
|
22532
|
+
const { serverEvents } = plugin;
|
|
22533
|
+
if (serverEvents) {
|
|
22534
|
+
Object.keys(serverEvents).forEach((serverEventName) => {
|
|
22535
|
+
// we could throw on serverEvent name conflict
|
|
22536
|
+
// we could throw if serverEvents[serverEventName] is not a function
|
|
22537
|
+
allServerEvents[serverEventName] = serverEvents[serverEventName];
|
|
22538
|
+
});
|
|
22539
|
+
}
|
|
22540
|
+
});
|
|
22541
|
+
const serverEventNames = Object.keys(allServerEvents);
|
|
22542
|
+
if (serverEventNames.length > 0) {
|
|
22543
|
+
Object.keys(allServerEvents).forEach((serverEventName) => {
|
|
22544
|
+
const serverEventInfo = {
|
|
22545
|
+
...kitchen.context,
|
|
22546
|
+
sendServerEvent: (data) => {
|
|
22547
|
+
serverEventsDispatcher.dispatch({
|
|
22548
|
+
type: serverEventName,
|
|
22549
|
+
data,
|
|
22550
|
+
});
|
|
22551
|
+
},
|
|
22552
|
+
};
|
|
22553
|
+
const serverEventInit = allServerEvents[serverEventName];
|
|
22554
|
+
serverEventInit(serverEventInfo);
|
|
22555
|
+
});
|
|
22556
|
+
// "pushPlugin" so that event source client connection can be put as early as possible in html
|
|
22557
|
+
kitchen.pluginController.pushPlugin(
|
|
22558
|
+
jsenvPluginServerEventsClientInjection(
|
|
22559
|
+
clientAutoreload.clientServerEventsConfig,
|
|
22560
|
+
),
|
|
22561
|
+
);
|
|
22562
|
+
}
|
|
22563
|
+
}
|
|
22564
|
+
|
|
22565
|
+
kitchenCache.set(runtimeId, kitchen);
|
|
22566
|
+
onKitchenCreated(kitchen);
|
|
22567
|
+
return kitchen;
|
|
22568
|
+
};
|
|
22569
|
+
|
|
22570
|
+
finalServices.push({
|
|
22571
|
+
name: "jsenv:omega_file_service",
|
|
22572
|
+
handleRequest: async (request) => {
|
|
22573
|
+
const kitchen = getOrCreateKitchen(request);
|
|
22574
|
+
const serveHookInfo = {
|
|
22575
|
+
...kitchen.context,
|
|
22576
|
+
request,
|
|
22577
|
+
};
|
|
22578
|
+
const responseFromPlugin =
|
|
22579
|
+
await kitchen.pluginController.callAsyncHooksUntil(
|
|
22580
|
+
"serve",
|
|
22581
|
+
serveHookInfo,
|
|
22582
|
+
);
|
|
22583
|
+
if (responseFromPlugin) {
|
|
22584
|
+
return responseFromPlugin;
|
|
22585
|
+
}
|
|
22586
|
+
const { referer } = request.headers;
|
|
22587
|
+
const parentUrl = referer
|
|
22588
|
+
? WEB_URL_CONVERTER.asFileUrl(referer, {
|
|
22589
|
+
origin: request.origin,
|
|
22590
|
+
rootDirectoryUrl: sourceDirectoryUrl,
|
|
22591
|
+
})
|
|
22592
|
+
: sourceDirectoryUrl;
|
|
22593
|
+
let reference = kitchen.graph.inferReference(
|
|
22594
|
+
request.resource,
|
|
22595
|
+
parentUrl,
|
|
22596
|
+
);
|
|
22597
|
+
if (!reference) {
|
|
22598
|
+
reference =
|
|
22599
|
+
kitchen.graph.rootUrlInfo.dependencies.createResolveAndFinalize({
|
|
22600
|
+
trace: { message: parentUrl },
|
|
22601
|
+
type: "http_request",
|
|
22602
|
+
specifier: request.resource,
|
|
22603
|
+
});
|
|
22604
|
+
}
|
|
22605
|
+
const urlInfo = reference.urlInfo;
|
|
22606
|
+
const ifNoneMatch = request.headers["if-none-match"];
|
|
22607
|
+
const urlInfoTargetedByCache = urlInfo.findParentIfInline() || urlInfo;
|
|
22608
|
+
|
|
22609
|
+
try {
|
|
22610
|
+
if (!urlInfo.error && ifNoneMatch) {
|
|
22611
|
+
const [clientOriginalContentEtag, clientContentEtag] =
|
|
22612
|
+
ifNoneMatch.split("_");
|
|
22813
22613
|
if (
|
|
22814
|
-
|
|
22815
|
-
|
|
22614
|
+
urlInfoTargetedByCache.originalContentEtag ===
|
|
22615
|
+
clientOriginalContentEtag &&
|
|
22616
|
+
urlInfoTargetedByCache.contentEtag === clientContentEtag &&
|
|
22617
|
+
urlInfoTargetedByCache.isValid()
|
|
22816
22618
|
) {
|
|
22619
|
+
const headers = {
|
|
22620
|
+
"cache-control": `private,max-age=0,must-revalidate`,
|
|
22621
|
+
};
|
|
22622
|
+
Object.keys(urlInfo.headers).forEach((key) => {
|
|
22623
|
+
if (key !== "content-length") {
|
|
22624
|
+
headers[key] = urlInfo.headers[key];
|
|
22625
|
+
}
|
|
22626
|
+
});
|
|
22817
22627
|
return {
|
|
22818
|
-
status:
|
|
22628
|
+
status: 304,
|
|
22629
|
+
headers,
|
|
22819
22630
|
};
|
|
22820
22631
|
}
|
|
22821
|
-
return convertFileSystemErrorToResponseProperties(error);
|
|
22822
|
-
};
|
|
22823
|
-
const response = getResponseForError();
|
|
22824
|
-
if (!response) {
|
|
22825
|
-
return null;
|
|
22826
22632
|
}
|
|
22827
|
-
|
|
22828
|
-
|
|
22829
|
-
|
|
22830
|
-
|
|
22831
|
-
|
|
22832
|
-
}
|
|
22833
|
-
|
|
22633
|
+
|
|
22634
|
+
await urlInfo.cook({ request, reference });
|
|
22635
|
+
let { response } = urlInfo;
|
|
22636
|
+
if (response) {
|
|
22637
|
+
return response;
|
|
22638
|
+
}
|
|
22639
|
+
response = {
|
|
22640
|
+
url: reference.url,
|
|
22834
22641
|
status: 200,
|
|
22835
22642
|
headers: {
|
|
22836
|
-
|
|
22837
|
-
|
|
22643
|
+
// when we send eTag to the client the next request to the server
|
|
22644
|
+
// will send etag in request headers.
|
|
22645
|
+
// If they match jsenv bypass cooking and returns 304
|
|
22646
|
+
// This must not happen when a plugin uses "no-store" or "no-cache" as it means
|
|
22647
|
+
// plugin logic wants to happens for every request to this url
|
|
22648
|
+
...(urlInfo.headers["cache-control"] === "no-store" ||
|
|
22649
|
+
urlInfo.headers["cache-control"] === "no-cache"
|
|
22650
|
+
? {}
|
|
22651
|
+
: {
|
|
22652
|
+
"cache-control": `private,max-age=0,must-revalidate`,
|
|
22653
|
+
// it's safe to use "_" separator because etag is encoded with base64 (see https://stackoverflow.com/a/13195197)
|
|
22654
|
+
"eTag": `${urlInfoTargetedByCache.originalContentEtag}_${urlInfoTargetedByCache.contentEtag}`,
|
|
22655
|
+
}),
|
|
22656
|
+
...urlInfo.headers,
|
|
22657
|
+
"content-type": urlInfo.contentType,
|
|
22658
|
+
"content-length": urlInfo.contentLength,
|
|
22838
22659
|
},
|
|
22839
|
-
body,
|
|
22660
|
+
body: urlInfo.content,
|
|
22661
|
+
timing: urlInfo.timing,
|
|
22840
22662
|
};
|
|
22841
|
-
|
|
22663
|
+
const augmentResponseInfo = {
|
|
22664
|
+
...kitchen.context,
|
|
22665
|
+
reference,
|
|
22666
|
+
urlInfo,
|
|
22667
|
+
};
|
|
22668
|
+
kitchen.pluginController.callHooks(
|
|
22669
|
+
"augmentResponse",
|
|
22670
|
+
augmentResponseInfo,
|
|
22671
|
+
(returnValue) => {
|
|
22672
|
+
response = composeTwoResponses(response, returnValue);
|
|
22673
|
+
},
|
|
22674
|
+
);
|
|
22675
|
+
return response;
|
|
22676
|
+
} catch (e) {
|
|
22677
|
+
urlInfo.error = e;
|
|
22678
|
+
const originalError = e ? e.cause || e : e;
|
|
22679
|
+
if (originalError.asResponse) {
|
|
22680
|
+
return originalError.asResponse();
|
|
22681
|
+
}
|
|
22682
|
+
const code = originalError.code;
|
|
22683
|
+
if (code === "PARSE_ERROR") {
|
|
22684
|
+
// when possible let browser re-throw the syntax error
|
|
22685
|
+
// it's not possible to do that when url info content is not available
|
|
22686
|
+
// (happens for js_module_fallback for instance)
|
|
22687
|
+
if (urlInfo.content !== undefined) {
|
|
22688
|
+
kitchen.context.logger.error(`Error while handling ${request.url}:
|
|
22689
|
+
${originalError.reasonCode || originalError.code}
|
|
22690
|
+
${e.traceMessage}`);
|
|
22691
|
+
return {
|
|
22692
|
+
url: reference.url,
|
|
22693
|
+
status: 200,
|
|
22694
|
+
// reason becomes the http response statusText, it must not contain invalid chars
|
|
22695
|
+
// https://github.com/nodejs/node/blob/0c27ca4bc9782d658afeaebcec85ec7b28f1cc35/lib/_http_common.js#L221
|
|
22696
|
+
statusText: e.reason,
|
|
22697
|
+
statusMessage: originalError.message,
|
|
22698
|
+
headers: {
|
|
22699
|
+
"content-type": urlInfo.contentType,
|
|
22700
|
+
"content-length": urlInfo.contentLength,
|
|
22701
|
+
"cache-control": "no-store",
|
|
22702
|
+
},
|
|
22703
|
+
body: urlInfo.content,
|
|
22704
|
+
};
|
|
22705
|
+
}
|
|
22706
|
+
return {
|
|
22707
|
+
url: reference.url,
|
|
22708
|
+
status: 500,
|
|
22709
|
+
statusText: e.reason,
|
|
22710
|
+
statusMessage: originalError.message,
|
|
22711
|
+
headers: {
|
|
22712
|
+
"cache-control": "no-store",
|
|
22713
|
+
},
|
|
22714
|
+
body: urlInfo.content,
|
|
22715
|
+
};
|
|
22716
|
+
}
|
|
22717
|
+
if (code === "DIRECTORY_REFERENCE_NOT_ALLOWED") {
|
|
22718
|
+
return serveDirectory(reference.url, {
|
|
22719
|
+
headers: {
|
|
22720
|
+
accept: "text/html",
|
|
22721
|
+
},
|
|
22722
|
+
canReadDirectory: true,
|
|
22723
|
+
rootDirectoryUrl: sourceDirectoryUrl,
|
|
22724
|
+
});
|
|
22725
|
+
}
|
|
22726
|
+
if (code === "NOT_ALLOWED") {
|
|
22727
|
+
return {
|
|
22728
|
+
url: reference.url,
|
|
22729
|
+
status: 403,
|
|
22730
|
+
statusText: originalError.reason,
|
|
22731
|
+
};
|
|
22732
|
+
}
|
|
22733
|
+
if (code === "NOT_FOUND") {
|
|
22734
|
+
return {
|
|
22735
|
+
url: reference.url,
|
|
22736
|
+
status: 404,
|
|
22737
|
+
statusText: originalError.reason,
|
|
22738
|
+
statusMessage: originalError.message,
|
|
22739
|
+
};
|
|
22740
|
+
}
|
|
22741
|
+
return {
|
|
22742
|
+
url: reference.url,
|
|
22743
|
+
status: 500,
|
|
22744
|
+
statusText: e.reason,
|
|
22745
|
+
statusMessage: e.stack,
|
|
22746
|
+
};
|
|
22747
|
+
}
|
|
22748
|
+
},
|
|
22749
|
+
handleWebsocket: (websocket, { request }) => {
|
|
22750
|
+
if (request.headers["sec-websocket-protocol"] === "jsenv") {
|
|
22751
|
+
serverEventsDispatcher.addWebsocket(websocket, request);
|
|
22752
|
+
}
|
|
22753
|
+
},
|
|
22754
|
+
});
|
|
22755
|
+
}
|
|
22756
|
+
// jsenv error handler service
|
|
22757
|
+
{
|
|
22758
|
+
finalServices.push({
|
|
22759
|
+
name: "jsenv:omega_error_handler",
|
|
22760
|
+
handleError: (error) => {
|
|
22761
|
+
const getResponseForError = () => {
|
|
22762
|
+
if (error && error.asResponse) {
|
|
22763
|
+
return error.asResponse();
|
|
22764
|
+
}
|
|
22765
|
+
if (error && error.statusText === "Unexpected directory operation") {
|
|
22766
|
+
return {
|
|
22767
|
+
status: 403,
|
|
22768
|
+
};
|
|
22769
|
+
}
|
|
22770
|
+
return convertFileSystemErrorToResponseProperties(error);
|
|
22771
|
+
};
|
|
22772
|
+
const response = getResponseForError();
|
|
22773
|
+
if (!response) {
|
|
22774
|
+
return null;
|
|
22775
|
+
}
|
|
22776
|
+
const body = JSON.stringify({
|
|
22777
|
+
status: response.status,
|
|
22778
|
+
statusText: response.statusText,
|
|
22779
|
+
headers: response.headers,
|
|
22780
|
+
body: response.body,
|
|
22781
|
+
});
|
|
22782
|
+
return {
|
|
22783
|
+
status: 200,
|
|
22784
|
+
headers: {
|
|
22785
|
+
"content-type": "application/json",
|
|
22786
|
+
"content-length": Buffer.byteLength(body),
|
|
22787
|
+
},
|
|
22788
|
+
body,
|
|
22789
|
+
};
|
|
22842
22790
|
},
|
|
22843
|
-
|
|
22791
|
+
});
|
|
22792
|
+
}
|
|
22793
|
+
// default error handler
|
|
22794
|
+
{
|
|
22795
|
+
finalServices.push(
|
|
22844
22796
|
jsenvServiceErrorHandler({
|
|
22845
22797
|
sendErrorDetails: true,
|
|
22846
22798
|
}),
|
|
22847
|
-
|
|
22799
|
+
);
|
|
22800
|
+
}
|
|
22801
|
+
|
|
22802
|
+
const server = await startServer({
|
|
22803
|
+
signal,
|
|
22804
|
+
stopOnExit: false,
|
|
22805
|
+
stopOnSIGINT: handleSIGINT,
|
|
22806
|
+
stopOnInternalError: false,
|
|
22807
|
+
keepProcessAlive: process.env.IMPORTED_BY_TEST_PLAN
|
|
22808
|
+
? false
|
|
22809
|
+
: keepProcessAlive,
|
|
22810
|
+
logLevel: serverLogLevel,
|
|
22811
|
+
startLog: false,
|
|
22812
|
+
|
|
22813
|
+
https,
|
|
22814
|
+
http2,
|
|
22815
|
+
acceptAnyIp,
|
|
22816
|
+
hostname,
|
|
22817
|
+
port,
|
|
22818
|
+
requestWaitingMs: 60_000,
|
|
22819
|
+
services: finalServices,
|
|
22848
22820
|
});
|
|
22849
22821
|
server.stoppedPromise.then((reason) => {
|
|
22850
22822
|
onStop();
|