@jsenv/core 27.0.2 → 27.2.0

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.
Files changed (35) hide show
  1. package/dist/controllable_child_process.mjs +139 -0
  2. package/dist/controllable_worker_thread.mjs +103 -0
  3. package/dist/js/execute_using_dynamic_import.js +169 -0
  4. package/dist/js/v8_coverage.js +539 -0
  5. package/dist/main.js +703 -827
  6. package/package.json +13 -12
  7. package/src/build/build.js +9 -12
  8. package/src/build/build_urls_generator.js +1 -1
  9. package/src/build/inject_global_version_mappings.js +3 -2
  10. package/src/build/inject_service_worker_urls.js +1 -2
  11. package/src/execute/run.js +50 -68
  12. package/src/execute/runtimes/browsers/from_playwright.js +13 -8
  13. package/src/execute/runtimes/node/{controllable_file.mjs → controllable_child_process.mjs} +18 -50
  14. package/src/execute/runtimes/node/controllable_worker_thread.mjs +103 -0
  15. package/src/execute/runtimes/node/execute_using_dynamic_import.js +49 -0
  16. package/src/execute/runtimes/node/exit_codes.js +9 -0
  17. package/src/execute/runtimes/node/{node_process.js → node_child_process.js} +56 -50
  18. package/src/execute/runtimes/node/node_worker_thread.js +268 -25
  19. package/src/execute/runtimes/node/profiler_v8_coverage.js +56 -0
  20. package/src/main.js +3 -1
  21. package/src/omega/kitchen.js +19 -6
  22. package/src/omega/server/file_service.js +2 -2
  23. package/src/omega/url_graph/url_graph_load.js +0 -1
  24. package/src/omega/url_graph/url_info_transformations.js +27 -14
  25. package/src/omega/url_graph.js +2 -0
  26. package/src/plugins/bundling/js_module/bundle_js_module.js +2 -5
  27. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +18 -15
  28. package/src/plugins/url_resolution/jsenv_plugin_url_resolution.js +2 -1
  29. package/src/test/coverage/report_to_coverage.js +16 -19
  30. package/src/test/coverage/v8_coverage.js +26 -0
  31. package/src/test/coverage/{v8_coverage_from_directory.js → v8_coverage_node_directory.js} +22 -26
  32. package/src/test/execute_plan.js +98 -91
  33. package/src/test/execute_test_plan.js +19 -13
  34. package/src/test/logs_file_execution.js +90 -13
  35. package/dist/js/controllable_file.mjs +0 -227
@@ -0,0 +1,539 @@
1
+ const assertUrlLike = (value, name = "url") => {
2
+ if (typeof value !== "string") {
3
+ throw new TypeError(`${name} must be a url string, got ${value}`);
4
+ }
5
+
6
+ if (isWindowsPathnameSpecifier(value)) {
7
+ throw new TypeError(`${name} must be a url but looks like a windows pathname, got ${value}`);
8
+ }
9
+
10
+ if (!hasScheme(value)) {
11
+ throw new TypeError(`${name} must be a url and no scheme found, got ${value}`);
12
+ }
13
+ };
14
+ const isPlainObject = value => {
15
+ if (value === null) {
16
+ return false;
17
+ }
18
+
19
+ if (typeof value === "object") {
20
+ if (Array.isArray(value)) {
21
+ return false;
22
+ }
23
+
24
+ return true;
25
+ }
26
+
27
+ return false;
28
+ };
29
+
30
+ const isWindowsPathnameSpecifier = specifier => {
31
+ const firstChar = specifier[0];
32
+ if (!/[a-zA-Z]/.test(firstChar)) return false;
33
+ const secondChar = specifier[1];
34
+ if (secondChar !== ":") return false;
35
+ const thirdChar = specifier[2];
36
+ return thirdChar === "/" || thirdChar === "\\";
37
+ };
38
+
39
+ const hasScheme = specifier => /^[a-zA-Z]+:/.test(specifier);
40
+
41
+ const resolveAssociations = (associations, baseUrl) => {
42
+ assertUrlLike(baseUrl, "baseUrl");
43
+ const associationsResolved = {};
44
+ Object.keys(associations).forEach(key => {
45
+ const valueMap = associations[key];
46
+ const valueMapResolved = {};
47
+ Object.keys(valueMap).forEach(pattern => {
48
+ const value = valueMap[pattern];
49
+ const patternResolved = normalizeUrlPattern(pattern, baseUrl);
50
+ valueMapResolved[patternResolved] = value;
51
+ });
52
+ associationsResolved[key] = valueMapResolved;
53
+ });
54
+ return associationsResolved;
55
+ };
56
+
57
+ const normalizeUrlPattern = (urlPattern, baseUrl) => {
58
+ // starts with a scheme
59
+ if (/^[a-zA-Z]{2,}:/.test(urlPattern)) {
60
+ return urlPattern;
61
+ }
62
+
63
+ return String(new URL(urlPattern, baseUrl));
64
+ };
65
+
66
+ const asFlatAssociations = associations => {
67
+ if (!isPlainObject(associations)) {
68
+ throw new TypeError(`associations must be a plain object, got ${associations}`);
69
+ }
70
+
71
+ const flatAssociations = {};
72
+ Object.keys(associations).forEach(key => {
73
+ const valueMap = associations[key];
74
+
75
+ if (!isPlainObject(valueMap)) {
76
+ throw new TypeError(`all associations value must be objects, found "${key}": ${valueMap}`);
77
+ }
78
+
79
+ Object.keys(valueMap).forEach(pattern => {
80
+ const value = valueMap[pattern];
81
+ const previousValue = flatAssociations[pattern];
82
+ flatAssociations[pattern] = previousValue ? { ...previousValue,
83
+ [key]: value
84
+ } : {
85
+ [key]: value
86
+ };
87
+ });
88
+ });
89
+ return flatAssociations;
90
+ };
91
+
92
+ /*
93
+ * Link to things doing pattern matching:
94
+ * https://git-scm.com/docs/gitignore
95
+ * https://github.com/kaelzhang/node-ignore
96
+ */
97
+ /** @module jsenv_url_meta **/
98
+
99
+ /**
100
+ * An object representing the result of applying a pattern to an url
101
+ * @typedef {Object} MatchResult
102
+ * @property {boolean} matched Indicates if url matched pattern
103
+ * @property {number} patternIndex Index where pattern stopped matching url, otherwise pattern.length
104
+ * @property {number} urlIndex Index where url stopped matching pattern, otherwise url.length
105
+ * @property {Array} matchGroups Array of strings captured during pattern matching
106
+ */
107
+
108
+ /**
109
+ * Apply a pattern to an url
110
+ * @param {Object} applyPatternMatchingParams
111
+ * @param {string} applyPatternMatchingParams.pattern "*", "**" and trailing slash have special meaning
112
+ * @param {string} applyPatternMatchingParams.url a string representing an url
113
+ * @return {MatchResult}
114
+ */
115
+
116
+ const applyPatternMatching = ({
117
+ url,
118
+ pattern
119
+ }) => {
120
+ assertUrlLike(pattern, "pattern");
121
+ assertUrlLike(url, "url");
122
+ const {
123
+ matched,
124
+ patternIndex,
125
+ index,
126
+ groups
127
+ } = applyMatching(pattern, url);
128
+ const matchGroups = [];
129
+ let groupIndex = 0;
130
+ groups.forEach(group => {
131
+ if (group.name) {
132
+ matchGroups[group.name] = group.string;
133
+ } else {
134
+ matchGroups[groupIndex] = group.string;
135
+ groupIndex++;
136
+ }
137
+ });
138
+ return {
139
+ matched,
140
+ patternIndex,
141
+ urlIndex: index,
142
+ matchGroups
143
+ };
144
+ };
145
+
146
+ const applyMatching = (pattern, string) => {
147
+ const groups = [];
148
+ let patternIndex = 0;
149
+ let index = 0;
150
+ let remainingPattern = pattern;
151
+ let remainingString = string;
152
+ let restoreIndexes = true;
153
+
154
+ const consumePattern = count => {
155
+ const subpattern = remainingPattern.slice(0, count);
156
+ remainingPattern = remainingPattern.slice(count);
157
+ patternIndex += count;
158
+ return subpattern;
159
+ };
160
+
161
+ const consumeString = count => {
162
+ const substring = remainingString.slice(0, count);
163
+ remainingString = remainingString.slice(count);
164
+ index += count;
165
+ return substring;
166
+ };
167
+
168
+ const consumeRemainingString = () => {
169
+ return consumeString(remainingString.length);
170
+ };
171
+
172
+ let matched;
173
+
174
+ const iterate = () => {
175
+ const patternIndexBefore = patternIndex;
176
+ const indexBefore = index;
177
+ matched = matchOne();
178
+
179
+ if (matched === undefined) {
180
+ consumePattern(1);
181
+ consumeString(1);
182
+ iterate();
183
+ return;
184
+ }
185
+
186
+ if (matched === false && restoreIndexes) {
187
+ patternIndex = patternIndexBefore;
188
+ index = indexBefore;
189
+ }
190
+ };
191
+
192
+ const matchOne = () => {
193
+ // pattern consumed and string consumed
194
+ if (remainingPattern === "" && remainingString === "") {
195
+ return true; // string fully matched pattern
196
+ } // pattern consumed, string not consumed
197
+
198
+
199
+ if (remainingPattern === "" && remainingString !== "") {
200
+ return false; // fails because string longer than expected
201
+ } // -- from this point pattern is not consumed --
202
+ // string consumed, pattern not consumed
203
+
204
+
205
+ if (remainingString === "") {
206
+ if (remainingPattern === "**") {
207
+ // trailing "**" is optional
208
+ consumePattern(2);
209
+ return true;
210
+ }
211
+
212
+ if (remainingPattern === "*") {
213
+ groups.push({
214
+ string: ""
215
+ });
216
+ }
217
+
218
+ return false; // fail because string shorter than expected
219
+ } // -- from this point pattern and string are not consumed --
220
+ // fast path trailing slash
221
+
222
+
223
+ if (remainingPattern === "/") {
224
+ if (remainingString[0] === "/") {
225
+ // trailing slash match remaining
226
+ consumePattern(1);
227
+ groups.push({
228
+ string: consumeRemainingString()
229
+ });
230
+ return true;
231
+ }
232
+
233
+ return false;
234
+ } // fast path trailing '**'
235
+
236
+
237
+ if (remainingPattern === "**") {
238
+ consumePattern(2);
239
+ consumeRemainingString();
240
+ return true;
241
+ } // pattern leading **
242
+
243
+
244
+ if (remainingPattern.slice(0, 2) === "**") {
245
+ consumePattern(2); // consumes "**"
246
+
247
+ if (remainingPattern[0] === "/") {
248
+ consumePattern(1); // consumes "/"
249
+ } // pattern ending with ** always match remaining string
250
+
251
+
252
+ if (remainingPattern === "") {
253
+ consumeRemainingString();
254
+ return true;
255
+ }
256
+
257
+ const skipResult = skipUntilMatch({
258
+ pattern: remainingPattern,
259
+ string: remainingString,
260
+ canSkipSlash: true
261
+ });
262
+ groups.push(...skipResult.groups);
263
+ consumePattern(skipResult.patternIndex);
264
+ consumeRemainingString();
265
+ restoreIndexes = false;
266
+ return skipResult.matched;
267
+ }
268
+
269
+ if (remainingPattern[0] === "*") {
270
+ consumePattern(1); // consumes "*"
271
+
272
+ if (remainingPattern === "") {
273
+ // matches everything except '/'
274
+ const slashIndex = remainingString.indexOf("/");
275
+
276
+ if (slashIndex === -1) {
277
+ groups.push({
278
+ string: consumeRemainingString()
279
+ });
280
+ return true;
281
+ }
282
+
283
+ groups.push({
284
+ string: consumeString(slashIndex)
285
+ });
286
+ return false;
287
+ } // the next char must not the one expected by remainingPattern[0]
288
+ // because * is greedy and expect to skip at least one char
289
+
290
+
291
+ if (remainingPattern[0] === remainingString[0]) {
292
+ groups.push({
293
+ string: ""
294
+ });
295
+ patternIndex = patternIndex - 1;
296
+ return false;
297
+ }
298
+
299
+ const skipResult = skipUntilMatch({
300
+ pattern: remainingPattern,
301
+ string: remainingString,
302
+ canSkipSlash: false
303
+ });
304
+ groups.push(skipResult.group, ...skipResult.groups);
305
+ consumePattern(skipResult.patternIndex);
306
+ consumeString(skipResult.index);
307
+ restoreIndexes = false;
308
+ return skipResult.matched;
309
+ }
310
+
311
+ if (remainingPattern[0] !== remainingString[0]) {
312
+ return false;
313
+ }
314
+
315
+ return undefined;
316
+ };
317
+
318
+ iterate();
319
+ return {
320
+ matched,
321
+ patternIndex,
322
+ index,
323
+ groups
324
+ };
325
+ };
326
+
327
+ const skipUntilMatch = ({
328
+ pattern,
329
+ string,
330
+ canSkipSlash
331
+ }) => {
332
+ let index = 0;
333
+ let remainingString = string;
334
+ let longestMatchRange = null;
335
+
336
+ const tryToMatch = () => {
337
+ const matchAttempt = applyMatching(pattern, remainingString);
338
+
339
+ if (matchAttempt.matched) {
340
+ return {
341
+ matched: true,
342
+ patternIndex: matchAttempt.patternIndex,
343
+ index: index + matchAttempt.index,
344
+ groups: matchAttempt.groups,
345
+ group: {
346
+ string: remainingString === "" ? string : string.slice(0, -remainingString.length)
347
+ }
348
+ };
349
+ }
350
+
351
+ const matchAttemptIndex = matchAttempt.index;
352
+ const matchRange = {
353
+ patternIndex: matchAttempt.patternIndex,
354
+ index,
355
+ length: matchAttemptIndex,
356
+ groups: matchAttempt.groups
357
+ };
358
+
359
+ if (!longestMatchRange || longestMatchRange.length < matchRange.length) {
360
+ longestMatchRange = matchRange;
361
+ }
362
+
363
+ const nextIndex = matchAttemptIndex + 1;
364
+ const canSkip = nextIndex < remainingString.length && (canSkipSlash || remainingString[0] !== "/");
365
+
366
+ if (canSkip) {
367
+ // search against the next unattempted string
368
+ index += nextIndex;
369
+ remainingString = remainingString.slice(nextIndex);
370
+ return tryToMatch();
371
+ }
372
+
373
+ return {
374
+ matched: false,
375
+ patternIndex: longestMatchRange.patternIndex,
376
+ index: longestMatchRange.index + longestMatchRange.length,
377
+ groups: longestMatchRange.groups,
378
+ group: {
379
+ string: string.slice(0, longestMatchRange.index)
380
+ }
381
+ };
382
+ };
383
+
384
+ return tryToMatch();
385
+ };
386
+
387
+ const applyAssociations = ({
388
+ url,
389
+ associations
390
+ }) => {
391
+ assertUrlLike(url);
392
+ const flatAssociations = asFlatAssociations(associations);
393
+ return Object.keys(flatAssociations).reduce((previousValue, pattern) => {
394
+ const {
395
+ matched
396
+ } = applyPatternMatching({
397
+ pattern,
398
+ url
399
+ });
400
+
401
+ if (matched) {
402
+ const value = flatAssociations[pattern];
403
+
404
+ if (isPlainObject(previousValue) && isPlainObject(value)) {
405
+ return { ...previousValue,
406
+ ...value
407
+ };
408
+ }
409
+
410
+ return value;
411
+ }
412
+
413
+ return previousValue;
414
+ }, {});
415
+ };
416
+
417
+ const applyAliases = ({
418
+ url,
419
+ aliases
420
+ }) => {
421
+ let aliasFullMatchResult;
422
+ const aliasMatchingKey = Object.keys(aliases).find(key => {
423
+ const aliasMatchResult = applyPatternMatching({
424
+ pattern: key,
425
+ url
426
+ });
427
+
428
+ if (aliasMatchResult.matched) {
429
+ aliasFullMatchResult = aliasMatchResult;
430
+ return true;
431
+ }
432
+
433
+ return false;
434
+ });
435
+
436
+ if (!aliasMatchingKey) {
437
+ return url;
438
+ }
439
+
440
+ const {
441
+ matchGroups
442
+ } = aliasFullMatchResult;
443
+ const alias = aliases[aliasMatchingKey];
444
+ const parts = alias.split("*");
445
+ const newUrl = parts.reduce((previous, value, index) => {
446
+ return `${previous}${value}${index === parts.length - 1 ? "" : matchGroups[index]}`;
447
+ }, "");
448
+ return newUrl;
449
+ };
450
+
451
+ const urlChildMayMatch = ({
452
+ url,
453
+ associations,
454
+ predicate
455
+ }) => {
456
+ assertUrlLike(url, "url"); // the function was meants to be used on url ending with '/'
457
+
458
+ if (!url.endsWith("/")) {
459
+ throw new Error(`url should end with /, got ${url}`);
460
+ }
461
+
462
+ if (typeof predicate !== "function") {
463
+ throw new TypeError(`predicate must be a function, got ${predicate}`);
464
+ }
465
+
466
+ const flatAssociations = asFlatAssociations(associations); // for full match we must create an object to allow pattern to override previous ones
467
+
468
+ let fullMatchMeta = {};
469
+ let someFullMatch = false; // for partial match, any meta satisfying predicate will be valid because
470
+ // we don't know for sure if pattern will still match for a file inside pathname
471
+
472
+ const partialMatchMetaArray = [];
473
+ Object.keys(flatAssociations).forEach(pattern => {
474
+ const value = flatAssociations[pattern];
475
+ const matchResult = applyPatternMatching({
476
+ pattern,
477
+ url
478
+ });
479
+
480
+ if (matchResult.matched) {
481
+ someFullMatch = true;
482
+
483
+ if (isPlainObject(fullMatchMeta) && isPlainObject(value)) {
484
+ fullMatchMeta = { ...fullMatchMeta,
485
+ ...value
486
+ };
487
+ } else {
488
+ fullMatchMeta = value;
489
+ }
490
+ } else if (someFullMatch === false && matchResult.urlIndex >= url.length) {
491
+ partialMatchMetaArray.push(value);
492
+ }
493
+ });
494
+
495
+ if (someFullMatch) {
496
+ return Boolean(predicate(fullMatchMeta));
497
+ }
498
+
499
+ return partialMatchMetaArray.some(partialMatchMeta => predicate(partialMatchMeta));
500
+ };
501
+
502
+ const URL_META = {
503
+ resolveAssociations,
504
+ applyAssociations,
505
+ urlChildMayMatch,
506
+ applyPatternMatching,
507
+ applyAliases
508
+ };
509
+
510
+ const filterV8Coverage = async (v8Coverage, {
511
+ rootDirectoryUrl,
512
+ coverageConfig
513
+ }) => {
514
+ const associations = URL_META.resolveAssociations({
515
+ cover: coverageConfig
516
+ }, rootDirectoryUrl);
517
+
518
+ const urlShouldBeCovered = url => {
519
+ const {
520
+ cover
521
+ } = URL_META.applyAssociations({
522
+ url: new URL(url, rootDirectoryUrl).href,
523
+ associations
524
+ });
525
+ return cover;
526
+ };
527
+
528
+ const v8CoverageFiltered = { ...v8Coverage,
529
+ result: v8Coverage.result.filter(fileReport => urlShouldBeCovered(fileReport.url))
530
+ };
531
+ return v8CoverageFiltered;
532
+ };
533
+
534
+ var v8_coverage = /*#__PURE__*/Object.freeze({
535
+ __proto__: null,
536
+ filterV8Coverage: filterV8Coverage
537
+ });
538
+
539
+ export { URL_META as U, filterV8Coverage as f, v8_coverage as v };