@jsenv/core 34.3.0 → 35.0.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 (73) hide show
  1. package/README.md +1 -1
  2. package/dist/{jsenv.js → jsenv_core.js} +1054 -3850
  3. package/package.json +6 -21
  4. package/src/build/build.js +2 -2
  5. package/src/dev/file_service.js +8 -8
  6. package/src/dev/start_dev_server.js +3 -3
  7. package/src/dev/user_agent.js +1 -1
  8. package/src/main.js +0 -23
  9. package/src/plugins/supervisor/jsenv_plugin_supervisor.js +1 -1
  10. package/src/plugins/transpilation/babel/require_babel_plugin.js +1 -1
  11. package/src/plugins/transpilation/js_module_fallback/convert_js_module_to_js_classic.js +1 -1
  12. package/dist/controllable_child_process.mjs +0 -129
  13. package/dist/controllable_worker_thread.mjs +0 -91
  14. package/dist/importmap_node_loader.mjs +0 -49
  15. package/dist/js/execute_using_dynamic_import.js +0 -850
  16. package/dist/js/resolveImport.js +0 -504
  17. package/dist/js/v8_coverage.js +0 -508
  18. package/dist/no_experimental_warnings.cjs +0 -8
  19. package/src/execute/execute.js +0 -111
  20. package/src/execute/run.js +0 -161
  21. package/src/execute/runtimes/browsers/chromium.js +0 -10
  22. package/src/execute/runtimes/browsers/firefox.js +0 -9
  23. package/src/execute/runtimes/browsers/from_playwright.js +0 -574
  24. package/src/execute/runtimes/browsers/middleware_istanbul.js +0 -65
  25. package/src/execute/runtimes/browsers/middleware_js_supervisor.js +0 -100
  26. package/src/execute/runtimes/browsers/webkit.js +0 -26
  27. package/src/execute/runtimes/node/child_exec_options.js +0 -166
  28. package/src/execute/runtimes/node/controllable_child_process.mjs +0 -135
  29. package/src/execute/runtimes/node/controllable_worker_thread.mjs +0 -103
  30. package/src/execute/runtimes/node/exec_options.js +0 -57
  31. package/src/execute/runtimes/node/execute_using_dynamic_import.js +0 -55
  32. package/src/execute/runtimes/node/exit_codes.js +0 -9
  33. package/src/execute/runtimes/node/importmap_node_loader.mjs +0 -51
  34. package/src/execute/runtimes/node/importmap_node_loader_file_url.js +0 -4
  35. package/src/execute/runtimes/node/kill_process_tree.js +0 -76
  36. package/src/execute/runtimes/node/no_experimental_warnings.cjs +0 -12
  37. package/src/execute/runtimes/node/no_experimental_warnings_file_url.js +0 -4
  38. package/src/execute/runtimes/node/node_child_process.js +0 -363
  39. package/src/execute/runtimes/node/node_execution_performance.js +0 -67
  40. package/src/execute/runtimes/node/node_worker_thread.js +0 -295
  41. package/src/execute/runtimes/node/profiler_v8_coverage.js +0 -56
  42. package/src/execute/runtimes/readme.md +0 -13
  43. package/src/execute/web_server_param.js +0 -74
  44. package/src/test/coverage/babel_plugin_instrument.js +0 -48
  45. package/src/test/coverage/coverage_reporter_html_directory.js +0 -32
  46. package/src/test/coverage/coverage_reporter_json_file.js +0 -17
  47. package/src/test/coverage/coverage_reporter_text_log.js +0 -19
  48. package/src/test/coverage/empty_coverage_factory.js +0 -52
  49. package/src/test/coverage/file_by_file_coverage.js +0 -25
  50. package/src/test/coverage/istanbul_coverage_composition.js +0 -28
  51. package/src/test/coverage/istanbul_coverage_map_from_coverage.js +0 -16
  52. package/src/test/coverage/list_files_not_covered.js +0 -15
  53. package/src/test/coverage/missing_coverage.js +0 -41
  54. package/src/test/coverage/report_to_coverage.js +0 -198
  55. package/src/test/coverage/v8_and_istanbul.js +0 -37
  56. package/src/test/coverage/v8_coverage.js +0 -26
  57. package/src/test/coverage/v8_coverage_composition.js +0 -24
  58. package/src/test/coverage/v8_coverage_node_directory.js +0 -85
  59. package/src/test/coverage/v8_coverage_to_istanbul.js +0 -99
  60. package/src/test/execute_steps.js +0 -425
  61. package/src/test/execute_test_plan.js +0 -372
  62. package/src/test/execution_colors.js +0 -10
  63. package/src/test/execution_steps.js +0 -65
  64. package/src/test/gc.js +0 -9
  65. package/src/test/logs_file_execution.js +0 -427
  66. package/src/test/logs_file_execution.test.mjs +0 -41
  67. package/src/test/readme.md +0 -3
  68. /package/src/{basic_fetch.js → helpers/basic_fetch.js} +0 -0
  69. /package/src/{lookup_package_directory.js → helpers/lookup_package_directory.js} +0 -0
  70. /package/src/{ping_server.js → helpers/ping_server.js} +0 -0
  71. /package/src/{require_from_jsenv.js → helpers/require_from_jsenv.js} +0 -0
  72. /package/src/{watch_source_files.js → helpers/watch_source_files.js} +0 -0
  73. /package/src/{web_url_converter.js → helpers/web_url_converter.js} +0 -0
@@ -1,372 +0,0 @@
1
- import { existsSync } from "node:fs"
2
- import { URL_META } from "@jsenv/url-meta"
3
- import { urlToFileSystemPath, urlToRelativeUrl } from "@jsenv/urls"
4
- import {
5
- ensureEmptyDirectory,
6
- assertAndNormalizeDirectoryUrl,
7
- assertAndNormalizeFileUrl,
8
- } from "@jsenv/filesystem"
9
- import { createLogger, createDetailedMessage } from "@jsenv/log"
10
-
11
- import { assertAndNormalizeWebServer } from "../execute/web_server_param.js"
12
- import { generateCoverageJsonFile } from "./coverage/coverage_reporter_json_file.js"
13
- import { generateCoverageHtmlDirectory } from "./coverage/coverage_reporter_html_directory.js"
14
- import { generateCoverageTextLog } from "./coverage/coverage_reporter_text_log.js"
15
- import { executionStepsFromTestPlan } from "./execution_steps.js"
16
- import { executeSteps } from "./execute_steps.js"
17
-
18
- /**
19
- * Execute a list of files and log how it goes.
20
- * @param {Object} testPlanParameters
21
- * @param {string|url} testPlanParameters.rootDirectoryUrl Directory containing test files;
22
- * @param {Object} [testPlanParameters.webServer] Web server info; required when executing test on browsers
23
- * @param {Object} testPlanParameters.testPlan Object associating files with runtimes where they will be executed
24
- * @param {boolean} [testPlanParameters.completedExecutionLogAbbreviation=false] Abbreviate completed execution information to shorten terminal output
25
- * @param {boolean} [testPlanParameters.completedExecutionLogMerging=false] Merge completed execution logs to shorten terminal output
26
- * @param {number} [testPlanParameters.maxExecutionsInParallel=1] Maximum amount of execution in parallel
27
- * @param {number} [testPlanParameters.defaultMsAllocatedPerExecution=30000] Milliseconds after which execution is aborted and considered as failed by timeout
28
- * @param {boolean} [testPlanParameters.failFast=false] Fails immediatly when a test execution fails
29
- * @param {number} [testPlanParameters.cooldownBetweenExecutions=0] Millisecond to wait between each execution
30
- * @param {boolean} [testPlanParameters.logMemoryHeapUsage=false] Add memory heap usage during logs
31
- * @param {boolean} [testPlanParameters.coverageEnabled=false] Controls if coverage is collected during files executions
32
- * @param {boolean} [testPlanParameters.coverageV8ConflictWarning=true] Warn when coverage from 2 executions cannot be merged
33
- * @return {Object} An object containing the result of all file executions
34
- */
35
- export const executeTestPlan = async ({
36
- signal = new AbortController().signal,
37
- handleSIGINT = true,
38
- logLevel = "info",
39
- logRefresh = true,
40
- logRuntime = true,
41
- logEachDuration = true,
42
- logSummary = true,
43
- logTimeUsage = false,
44
- logMemoryHeapUsage = false,
45
- logFileRelativeUrl = ".jsenv/test_plan_debug.txt",
46
- completedExecutionLogAbbreviation = false,
47
- completedExecutionLogMerging = false,
48
-
49
- rootDirectoryUrl,
50
- webServer,
51
- testPlan,
52
- updateProcessExitCode = true,
53
- maxExecutionsInParallel = 1,
54
- defaultMsAllocatedPerExecution = 30_000,
55
- failFast = false,
56
- // keepRunning: false to ensure runtime is stopped once executed
57
- // because we have what we wants: execution is completed and
58
- // we have associated coverage and console output
59
- // passsing true means all node process and browsers launched stays opened
60
- // (can eventually be used for debug)
61
- keepRunning = false,
62
- cooldownBetweenExecutions = 0,
63
- gcBetweenExecutions = logMemoryHeapUsage,
64
-
65
- coverageEnabled = process.argv.includes("--coverage"),
66
- coverageConfig = {
67
- "file:///**/node_modules/": false,
68
- "./**/.*": false,
69
- "./**/.*/": false,
70
- "./**/src/**/*.js": true,
71
- "./**/src/**/*.ts": true,
72
- "./**/src/**/*.jsx": true,
73
- "./**/src/**/*.tsx": true,
74
- "./**/tests/": false,
75
- "./**/*.test.html": false,
76
- "./**/*.test.js": false,
77
- "./**/*.test.mjs": false,
78
- },
79
- coverageIncludeMissing = true,
80
- coverageAndExecutionAllowed = false,
81
- coverageMethodForNodeJs = process.env.NODE_V8_COVERAGE
82
- ? "NODE_V8_COVERAGE"
83
- : "Profiler",
84
- // - When chromium only -> coverage generated by v8
85
- // - When chromium + node -> coverage generated by v8 are merged
86
- // - When firefox only -> coverage generated by babel+istanbul
87
- // - When chromium + firefox
88
- // -> by default only coverage from chromium is used
89
- // and a warning is logged according to coverageV8ConflictWarning
90
- // -> to collect coverage from both browsers, pass coverageMethodForBrowsers: "istanbul"
91
- coverageMethodForBrowsers, // undefined | "playwright" | "istanbul"
92
- coverageV8ConflictWarning = true,
93
- coverageTempDirectoryUrl,
94
- // skip empty means empty files won't appear in the coverage reports (json and html)
95
- coverageReportSkipEmpty = false,
96
- // skip full means file with 100% coverage won't appear in coverage reports (json and html)
97
- coverageReportSkipFull = false,
98
- coverageReportTextLog = true,
99
- coverageReportJson = process.env.CI,
100
- coverageReportJsonFileUrl,
101
- coverageReportHtml = !process.env.CI,
102
- coverageReportHtmlDirectoryUrl,
103
- ...rest
104
- }) => {
105
- let someNeedsServer = false
106
- let someHasCoverageV8 = false
107
- let someNodeRuntime = false
108
- const runtimes = {}
109
- // param validation
110
- {
111
- const unexpectedParamNames = Object.keys(rest)
112
- if (unexpectedParamNames.length > 0) {
113
- throw new TypeError(
114
- `${unexpectedParamNames.join(",")}: there is no such param`,
115
- )
116
- }
117
- rootDirectoryUrl = assertAndNormalizeDirectoryUrl(
118
- rootDirectoryUrl,
119
- "rootDirectoryUrl",
120
- )
121
- if (!existsSync(new URL(rootDirectoryUrl))) {
122
- throw new Error(`ENOENT on rootDirectoryUrl at ${rootDirectoryUrl}`)
123
- }
124
- if (typeof testPlan !== "object") {
125
- throw new Error(`testPlan must be an object, got ${testPlan}`)
126
- }
127
-
128
- Object.keys(testPlan).forEach((filePattern) => {
129
- const filePlan = testPlan[filePattern]
130
- if (!filePlan) return
131
- Object.keys(filePlan).forEach((executionName) => {
132
- const executionConfig = filePlan[executionName]
133
- const { runtime } = executionConfig
134
- if (runtime) {
135
- runtimes[runtime.name] = runtime.version
136
- if (runtime.type === "browser") {
137
- if (runtime.capabilities && runtime.capabilities.coverageV8) {
138
- someHasCoverageV8 = true
139
- }
140
- someNeedsServer = true
141
- }
142
- if (runtime.type === "node") {
143
- someNodeRuntime = true
144
- }
145
- }
146
- })
147
- })
148
-
149
- if (someNeedsServer) {
150
- await assertAndNormalizeWebServer(webServer)
151
- }
152
-
153
- if (coverageEnabled) {
154
- if (coverageMethodForBrowsers === undefined) {
155
- coverageMethodForBrowsers = someHasCoverageV8
156
- ? "playwright"
157
- : "istanbul"
158
- }
159
- if (typeof coverageConfig !== "object") {
160
- throw new TypeError(
161
- `coverageConfig must be an object, got ${coverageConfig}`,
162
- )
163
- }
164
- if (!coverageAndExecutionAllowed) {
165
- const associationsForExecute = URL_META.resolveAssociations(
166
- { execute: testPlan },
167
- "file:///",
168
- )
169
- const associationsForCover = URL_META.resolveAssociations(
170
- { cover: coverageConfig },
171
- "file:///",
172
- )
173
- const patternsMatchingCoverAndExecute = Object.keys(
174
- associationsForExecute.execute,
175
- ).filter((testPlanPattern) => {
176
- const { cover } = URL_META.applyAssociations({
177
- url: testPlanPattern,
178
- associations: associationsForCover,
179
- })
180
- return cover
181
- })
182
- if (patternsMatchingCoverAndExecute.length) {
183
- // It would be strange, for a given file to be both covered and executed
184
- throw new Error(
185
- createDetailedMessage(
186
- `some file will be both covered and executed`,
187
- {
188
- patterns: patternsMatchingCoverAndExecute,
189
- },
190
- ),
191
- )
192
- }
193
- }
194
-
195
- if (coverageTempDirectoryUrl === undefined) {
196
- coverageTempDirectoryUrl = new URL("./.coverage/tmp/", rootDirectoryUrl)
197
- } else {
198
- coverageTempDirectoryUrl = assertAndNormalizeDirectoryUrl(
199
- coverageTempDirectoryUrl,
200
- "coverageTempDirectoryUrl",
201
- )
202
- }
203
- if (coverageReportJson) {
204
- if (coverageReportJsonFileUrl === undefined) {
205
- coverageReportJsonFileUrl = new URL(
206
- "./.coverage/coverage.json",
207
- rootDirectoryUrl,
208
- )
209
- } else {
210
- coverageReportJsonFileUrl = assertAndNormalizeFileUrl(
211
- coverageReportJsonFileUrl,
212
- "coverageReportJsonFileUrl",
213
- )
214
- }
215
- }
216
- if (coverageReportHtml) {
217
- if (coverageReportHtmlDirectoryUrl === undefined) {
218
- coverageReportHtmlDirectoryUrl = new URL(
219
- "./.coverage/",
220
- rootDirectoryUrl,
221
- )
222
- } else {
223
- coverageReportHtmlDirectoryUrl = assertAndNormalizeDirectoryUrl(
224
- coverageReportHtmlDirectoryUrl,
225
- "coverageReportHtmlDirectoryUrl",
226
- )
227
- }
228
- }
229
- }
230
- }
231
-
232
- const logger = createLogger({ logLevel })
233
- logger.debug(
234
- createDetailedMessage(`Prepare executing plan`, {
235
- runtimes: JSON.stringify(runtimes, null, " "),
236
- }),
237
- )
238
-
239
- // param normalization
240
- {
241
- if (coverageEnabled) {
242
- if (Object.keys(coverageConfig).length === 0) {
243
- logger.warn(
244
- `coverageConfig is an empty object. Nothing will be instrumented for coverage so your coverage will be empty`,
245
- )
246
- }
247
- if (
248
- someNodeRuntime &&
249
- coverageEnabled &&
250
- coverageMethodForNodeJs === "NODE_V8_COVERAGE"
251
- ) {
252
- if (process.env.NODE_V8_COVERAGE) {
253
- // when runned multiple times, we don't want to keep previous files in this directory
254
- await ensureEmptyDirectory(process.env.NODE_V8_COVERAGE)
255
- } else {
256
- coverageMethodForNodeJs = "Profiler"
257
- logger.warn(
258
- createDetailedMessage(
259
- `process.env.NODE_V8_COVERAGE is required to generate coverage for Node.js subprocesses`,
260
- {
261
- "suggestion": `set process.env.NODE_V8_COVERAGE`,
262
- "suggestion 2": `use coverageMethodForNodeJs: "Profiler". But it means coverage for child_process and worker_thread cannot be collected`,
263
- },
264
- ),
265
- )
266
- }
267
- }
268
- }
269
- }
270
-
271
- testPlan = {
272
- "file:///**/node_modules/": null,
273
- "**/*./": null,
274
- ...testPlan,
275
- "**/.jsenv/": null,
276
- }
277
- logger.debug(`Generate executions`)
278
- const executionSteps = await executionStepsFromTestPlan({
279
- signal,
280
- testPlan,
281
- rootDirectoryUrl,
282
- })
283
- logger.debug(`${executionSteps.length} executions planned`)
284
-
285
- const result = await executeSteps(executionSteps, {
286
- signal,
287
- handleSIGINT,
288
- logger,
289
- logRefresh,
290
- logSummary,
291
- logRuntime,
292
- logEachDuration,
293
- logTimeUsage,
294
- logMemoryHeapUsage,
295
- logFileRelativeUrl,
296
- completedExecutionLogMerging,
297
- completedExecutionLogAbbreviation,
298
- rootDirectoryUrl,
299
- webServer,
300
-
301
- maxExecutionsInParallel,
302
- defaultMsAllocatedPerExecution,
303
- failFast,
304
- keepRunning,
305
- cooldownBetweenExecutions,
306
- gcBetweenExecutions,
307
-
308
- coverageEnabled,
309
- coverageConfig,
310
- coverageIncludeMissing,
311
- coverageMethodForBrowsers,
312
- coverageMethodForNodeJs,
313
- coverageV8ConflictWarning,
314
- coverageTempDirectoryUrl,
315
- })
316
- if (
317
- updateProcessExitCode &&
318
- result.planSummary.counters.total !== result.planSummary.counters.completed
319
- ) {
320
- process.exitCode = 1
321
- }
322
- const planCoverage = result.planCoverage
323
- // planCoverage can be null when execution is aborted
324
- if (planCoverage) {
325
- const promises = []
326
- // keep this one first because it does ensureEmptyDirectory
327
- // and in case coverage json file gets written in the same directory
328
- // it must be done before
329
- if (coverageEnabled && coverageReportHtml) {
330
- await ensureEmptyDirectory(coverageReportHtmlDirectoryUrl)
331
- const htmlCoverageDirectoryIndexFileUrl = `${coverageReportHtmlDirectoryUrl}index.html`
332
- logger.info(
333
- `-> ${urlToFileSystemPath(htmlCoverageDirectoryIndexFileUrl)}`,
334
- )
335
- promises.push(
336
- generateCoverageHtmlDirectory(planCoverage, {
337
- rootDirectoryUrl,
338
- coverageHtmlDirectoryRelativeUrl: urlToRelativeUrl(
339
- coverageReportHtmlDirectoryUrl,
340
- rootDirectoryUrl,
341
- ),
342
- coverageReportSkipEmpty,
343
- coverageReportSkipFull,
344
- }),
345
- )
346
- }
347
- if (coverageEnabled && coverageReportJson) {
348
- promises.push(
349
- generateCoverageJsonFile({
350
- coverage: result.planCoverage,
351
- coverageJsonFileUrl: coverageReportJsonFileUrl,
352
- logger,
353
- }),
354
- )
355
- }
356
- if (coverageEnabled && coverageReportTextLog) {
357
- promises.push(
358
- generateCoverageTextLog(result.planCoverage, {
359
- coverageReportSkipEmpty,
360
- coverageReportSkipFull,
361
- }),
362
- )
363
- }
364
- await Promise.all(promises)
365
- }
366
- return {
367
- testPlanAborted: result.aborted,
368
- testPlanSummary: result.planSummary,
369
- testPlanReport: result.planReport,
370
- testPlanCoverage: planCoverage,
371
- }
372
- }
@@ -1,10 +0,0 @@
1
- import { ANSI } from "@jsenv/log"
2
-
3
- export const EXECUTION_COLORS = {
4
- executing: ANSI.BLUE,
5
- aborted: ANSI.MAGENTA,
6
- timedout: ANSI.MAGENTA,
7
- failed: ANSI.RED,
8
- completed: ANSI.GREEN,
9
- cancelled: ANSI.GREY,
10
- }
@@ -1,65 +0,0 @@
1
- import { Abort } from "@jsenv/abort"
2
- import { collectFiles } from "@jsenv/filesystem"
3
- import { createDetailedMessage } from "@jsenv/log"
4
-
5
- export const executionStepsFromTestPlan = async ({
6
- signal,
7
- rootDirectoryUrl,
8
- testPlan,
9
- }) => {
10
- try {
11
- const fileResultArray = await collectFiles({
12
- signal,
13
- directoryUrl: rootDirectoryUrl,
14
- associations: { testPlan },
15
- predicate: ({ testPlan }) => testPlan,
16
- })
17
- const executionSteps = []
18
- fileResultArray.forEach(({ relativeUrl, meta }) => {
19
- const fileExecutionSteps = generateFileExecutionSteps({
20
- fileRelativeUrl: relativeUrl,
21
- filePlan: meta.testPlan,
22
- })
23
- executionSteps.push(...fileExecutionSteps)
24
- })
25
- return executionSteps
26
- } catch (e) {
27
- if (Abort.isAbortError(e)) {
28
- return {
29
- aborted: true,
30
- planSummary: {},
31
- planReport: {},
32
- planCoverage: null,
33
- }
34
- }
35
- throw e
36
- }
37
- }
38
-
39
- export const generateFileExecutionSteps = ({ fileRelativeUrl, filePlan }) => {
40
- const fileExecutionSteps = []
41
- Object.keys(filePlan).forEach((executionName) => {
42
- const stepConfig = filePlan[executionName]
43
- if (stepConfig === null || stepConfig === undefined) {
44
- return
45
- }
46
- if (typeof stepConfig !== "object") {
47
- throw new TypeError(
48
- createDetailedMessage(
49
- `found unexpected value in plan, they must be object`,
50
- {
51
- ["file relative path"]: fileRelativeUrl,
52
- ["execution name"]: executionName,
53
- ["value"]: stepConfig,
54
- },
55
- ),
56
- )
57
- }
58
- fileExecutionSteps.push({
59
- executionName,
60
- fileRelativeUrl,
61
- ...stepConfig,
62
- })
63
- })
64
- return fileExecutionSteps
65
- }
package/src/test/gc.js DELETED
@@ -1,9 +0,0 @@
1
- import v8 from "node:v8"
2
- import { runInNewContext } from "node:vm"
3
-
4
- export const ensureGlobalGc = () => {
5
- if (!global.gc) {
6
- v8.setFlagsFromString("--expose_gc")
7
- global.gc = runInNewContext("gc")
8
- }
9
- }