@jsenv/core 27.0.0-alpha.82 → 27.0.0-alpha.85

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 (76) hide show
  1. package/dist/js/event_source_client.js +208 -4
  2. package/dist/js/s.js +2 -2
  3. package/dist/main.js +1430 -615
  4. package/dist/s.js +2 -2
  5. package/dist/s.js.map +2 -1
  6. package/package.json +6 -2
  7. package/src/build/build.js +5 -8
  8. package/src/build/build_urls_generator.js +1 -2
  9. package/src/build/inject_global_version_mappings.js +4 -4
  10. package/src/build/inject_service_worker_urls.js +2 -2
  11. package/src/build/resync_ressource_hints.js +17 -18
  12. package/src/build/start_build_server.js +33 -26
  13. package/src/dev/plugins/explorer/jsenv_plugin_explorer.js +1 -2
  14. package/src/dev/plugins/toolbar/client/util/fetching.js +1 -1
  15. package/src/dev/plugins/toolbar/jsenv_plugin_toolbar.js +3 -3
  16. package/src/dev/start_dev_server.js +38 -30
  17. package/src/execute/runtimes/browsers/from_playwright.js +5 -4
  18. package/src/execute/runtimes/node/node_process.js +2 -2
  19. package/src/helpers/command/command.js +73 -0
  20. package/src/helpers/event_source/event_source.js +197 -0
  21. package/src/helpers/event_source/sse_service.js +53 -0
  22. package/src/helpers/worker_reload.js +57 -0
  23. package/src/omega/compat/runtime_compat.js +2 -1
  24. package/src/omega/kitchen.js +4 -1
  25. package/src/omega/server/user_agent.js +2 -1
  26. package/src/omega/url_graph/sort_by_dependencies.js +27 -0
  27. package/src/omega/url_graph/url_info_transformations.js +24 -14
  28. package/src/plugins/autoreload/dev_sse/client/event_source_client.js +1 -1
  29. package/src/plugins/autoreload/dev_sse/client/reload.js +6 -3
  30. package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_client.js +3 -3
  31. package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +1 -1
  32. package/src/plugins/bundling/css/bundle_css.js +4 -4
  33. package/src/plugins/bundling/js_module/bundle_js_module.js +86 -67
  34. package/src/plugins/commonjs_globals/jsenv_plugin_commonjs_globals.js +2 -2
  35. package/src/plugins/file_urls/jsenv_plugin_file_urls.js +4 -5
  36. package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +62 -74
  37. package/src/plugins/import_meta_hot/html_hot_dependencies.js +9 -15
  38. package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +3 -3
  39. package/src/plugins/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +2 -2
  40. package/src/plugins/importmap/jsenv_plugin_importmap.js +25 -27
  41. package/src/plugins/inject_globals/inject_globals.js +4 -4
  42. package/src/plugins/inline/jsenv_plugin_data_urls.js +1 -1
  43. package/src/plugins/inline/jsenv_plugin_html_inline_content.js +41 -43
  44. package/src/plugins/inline/jsenv_plugin_js_inline_content.js +4 -4
  45. package/src/plugins/minification/css/minify_css.js +1 -1
  46. package/src/plugins/transpilation/as_js_classic/client/s.js +2 -2
  47. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +2 -4
  48. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +45 -67
  49. package/src/plugins/transpilation/babel/global_this/babel_plugin_global_this_as_jsenv_import.js +2 -3
  50. package/src/plugins/transpilation/babel/helpers/babel_plugin_babel_helpers_as_jsenv_imports.js +3 -4
  51. package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +1 -1
  52. package/src/plugins/transpilation/babel/new_stylesheet/babel_plugin_new_stylesheet_as_jsenv_import.js +2 -3
  53. package/src/plugins/transpilation/babel/regenerator_runtime/babel_plugin_regenerator_runtime_as_jsenv_import.js +2 -3
  54. package/src/plugins/transpilation/css_parcel/jsenv_plugin_css_parcel.js +1 -1
  55. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +1 -1
  56. package/src/plugins/transpilation/jsenv_plugin_top_level_await.js +2 -1
  57. package/src/plugins/url_analysis/css/css_urls.js +2 -3
  58. package/src/plugins/url_analysis/html/html_urls.js +98 -113
  59. package/src/plugins/url_analysis/js/js_urls.js +3 -2
  60. package/src/test/coverage/babel_plugin_instrument.js +82 -0
  61. package/src/test/coverage/coverage_reporter_html_directory.js +36 -0
  62. package/src/test/coverage/coverage_reporter_json_file.js +22 -0
  63. package/src/test/coverage/coverage_reporter_text_log.js +19 -0
  64. package/src/test/coverage/empty_coverage_factory.js +52 -0
  65. package/src/test/coverage/file_by_file_coverage.js +25 -0
  66. package/src/test/coverage/istanbul_coverage_composition.js +28 -0
  67. package/src/test/coverage/istanbul_coverage_map_from_coverage.js +16 -0
  68. package/src/test/coverage/list_files_not_covered.js +15 -0
  69. package/src/test/coverage/missing_coverage.js +41 -0
  70. package/src/test/coverage/report_to_coverage.js +196 -0
  71. package/src/test/coverage/v8_and_istanbul.js +37 -0
  72. package/src/test/coverage/v8_coverage_composition.js +24 -0
  73. package/src/test/coverage/v8_coverage_from_directory.js +87 -0
  74. package/src/test/coverage/v8_coverage_to_istanbul.js +99 -0
  75. package/src/test/execute_plan.js +2 -2
  76. package/src/test/execute_test_plan.js +3 -3
@@ -2,9 +2,8 @@
2
2
  * https://github.com/parcel-bundler/parcel/blob/v2/packages/transformers/css/src/CSSTransformer.js
3
3
  */
4
4
 
5
- import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
6
- import { applyPostCss } from "@jsenv/utils/css_ast/apply_post_css.js"
7
- import { postCssPluginUrlVisitor } from "@jsenv/utils/css_ast/postcss_plugin_url_visitor.js"
5
+ import { createMagicSource } from "@jsenv/sourcemap"
6
+ import { applyPostCss, postCssPluginUrlVisitor } from "@jsenv/ast"
8
7
 
9
8
  export const parseAndTransformCssUrls = async (urlInfo, context) => {
10
9
  const actions = []
@@ -1,11 +1,13 @@
1
1
  import {
2
2
  parseHtmlString,
3
+ visitHtmlNodes,
4
+ getHtmlNodeAttribute,
5
+ getHtmlNodePosition,
6
+ setHtmlNodeAttributes,
7
+ getHtmlNodeAttributePosition,
8
+ parseSrcSet,
3
9
  stringifyHtmlAst,
4
- getHtmlNodeAttributeByName,
5
- htmlNodePosition,
6
- visitHtmlAst,
7
- } from "@jsenv/utils/html_ast/html_ast.js"
8
- import { htmlAttributeSrcSet } from "@jsenv/utils/html_ast/html_attribute_src_set.js"
10
+ } from "@jsenv/ast"
9
11
 
10
12
  export const parseAndTransformHtmlUrls = async (urlInfo, context) => {
11
13
  const url = urlInfo.originalUrl
@@ -26,9 +28,12 @@ export const parseAndTransformHtmlUrls = async (urlInfo, context) => {
26
28
  column,
27
29
  originalLine,
28
30
  originalColumn,
31
+ node,
32
+ attributeName,
29
33
  specifier,
30
- attribute,
31
34
  }) => {
35
+ const { crossorigin, integrity } = readFetchMetas(node)
36
+
32
37
  const isRessourceHint = [
33
38
  "preconnect",
34
39
  "dns-prefetch",
@@ -45,9 +50,15 @@ export const parseAndTransformHtmlUrls = async (urlInfo, context) => {
45
50
  specifierLine: line,
46
51
  specifierColumn: column,
47
52
  isRessourceHint,
53
+ crossorigin,
54
+ integrity,
48
55
  })
49
56
  actions.push(async () => {
50
- attribute.value = await referenceUtils.readGeneratedSpecifier(reference)
57
+ setHtmlNodeAttributes(node, {
58
+ [attributeName]: await referenceUtils.readGeneratedSpecifier(
59
+ reference,
60
+ ),
61
+ })
51
62
  })
52
63
  },
53
64
  })
@@ -60,25 +71,39 @@ export const parseAndTransformHtmlUrls = async (urlInfo, context) => {
60
71
  }
61
72
  }
62
73
 
74
+ const crossOriginCompatibleTagNames = ["script", "link", "img", "source"]
75
+ const integrityCompatibleTagNames = ["script", "link", "img", "source"]
76
+ const readFetchMetas = (node) => {
77
+ const meta = {}
78
+ if (crossOriginCompatibleTagNames.includes(node.nodeName)) {
79
+ const crossorigin = getHtmlNodeAttribute(node, "crossorigin") !== undefined
80
+ meta.crossorigin = crossorigin
81
+ }
82
+ if (integrityCompatibleTagNames.includes(node.nodeName)) {
83
+ const integrity = getHtmlNodeAttribute(node, "integrity")
84
+ meta.integrity = integrity
85
+ }
86
+ return meta
87
+ }
88
+
63
89
  const visitHtmlUrls = ({ url, htmlAst, onUrl }) => {
64
90
  const addDependency = ({
65
91
  type,
66
92
  subtype,
67
93
  expectedType,
68
94
  node,
69
- attribute,
95
+ attributeName,
70
96
  specifier,
71
97
  }) => {
72
- const generatedFromInlineContent = Boolean(
73
- getHtmlNodeAttributeByName(node, "generated-from-inline-content"),
74
- )
98
+ const generatedFromInlineContent =
99
+ getHtmlNodeAttribute(node, "generated-from-inline-content") !== undefined
75
100
  let position
76
101
  if (generatedFromInlineContent) {
77
102
  // when generated from inline content,
78
103
  // line, column is not "src" nor "generated-from-src" but "original-position"
79
- position = htmlNodePosition.readNodePosition(node)
104
+ position = getHtmlNodePosition(node)
80
105
  } else {
81
- position = htmlNodePosition.readAttributePosition(node, attribute.name)
106
+ position = getHtmlNodeAttributePosition(node, attributeName)
82
107
  }
83
108
  const {
84
109
  line,
@@ -93,18 +118,61 @@ const visitHtmlUrls = ({ url, htmlAst, onUrl }) => {
93
118
  column,
94
119
  // originalLine, originalColumn
95
120
  specifier,
96
- attribute,
97
- // injected:Boolean(getHtmlNodeAttributeByName(node, "injected-by"))
98
- // srcGeneratedFromInlineContent
99
- ...readFetchMetas(node),
121
+ node,
122
+ attributeName,
100
123
  })
101
124
  }
102
- const visitors = {
125
+ const visitAttributeAsUrlSpecifier = ({ node, attributeName, ...rest }) => {
126
+ const value = getHtmlNodeAttribute(node, attributeName)
127
+ if (value) {
128
+ const generatedBy = getHtmlNodeAttribute(node, "generated-by")
129
+ if (generatedBy !== undefined) {
130
+ // during build the importmap is inlined
131
+ // and shoud not be considered as a dependency anymore
132
+ return
133
+ }
134
+ addDependency({
135
+ ...rest,
136
+ node,
137
+ attributeName,
138
+ specifier:
139
+ attributeName === "generated-from-src" ||
140
+ attributeName === "generated-from-href"
141
+ ? new URL(value, url).href
142
+ : value,
143
+ })
144
+ } else if (attributeName === "src") {
145
+ visitAttributeAsUrlSpecifier({
146
+ ...rest,
147
+ node,
148
+ attributeName: "generated-from-src",
149
+ })
150
+ } else if (attributeName === "href") {
151
+ visitAttributeAsUrlSpecifier({
152
+ ...rest,
153
+ node,
154
+ attributeName: "generated-from-href",
155
+ })
156
+ }
157
+ }
158
+ const visitSrcset = ({ type, node }) => {
159
+ const srcset = getHtmlNodeAttribute(node, "srcset")
160
+ if (srcset) {
161
+ const srcCandidates = parseSrcSet(srcset)
162
+ srcCandidates.forEach((srcCandidate) => {
163
+ addDependency({
164
+ type,
165
+ node,
166
+ attributeName: "srcset",
167
+ specifier: srcCandidate.specifier,
168
+ })
169
+ })
170
+ }
171
+ }
172
+ visitHtmlNodes(htmlAst, {
103
173
  link: (node) => {
104
- const relAttribute = getHtmlNodeAttributeByName(node, "rel")
105
- const rel = relAttribute ? relAttribute.value : undefined
106
- const typeAttribute = getHtmlNodeAttributeByName(node, "type")
107
- const type = typeAttribute ? typeAttribute.value : undefined
174
+ const rel = getHtmlNodeAttribute(node, "rel")
175
+ const type = getHtmlNodeAttribute(node, "type")
108
176
  visitAttributeAsUrlSpecifier({
109
177
  type: "link_href",
110
178
  subtype: rel,
@@ -120,15 +188,16 @@ const visitHtmlUrls = ({ url, htmlAst, onUrl }) => {
120
188
  },
121
189
  // style: () => {},
122
190
  script: (node) => {
123
- const typeAttributeNode = getHtmlNodeAttributeByName(node, "type")
191
+ const type = getHtmlNodeAttribute(node, "type")
192
+ const expectedType = {
193
+ "undefined": "js_classic",
194
+ "text/javascript": "js_classic",
195
+ "module": "js_module",
196
+ "importmap": "importmap",
197
+ }[type]
124
198
  visitAttributeAsUrlSpecifier({
125
199
  type: "script_src",
126
- expectedType: {
127
- "undefined": "js_classic",
128
- "text/javascript": "js_classic",
129
- "module": "js_module",
130
- "importmap": "importmap",
131
- }[typeAttributeNode ? typeAttributeNode.value : undefined],
200
+ expectedType,
132
201
  node,
133
202
  attributeName: "src",
134
203
  })
@@ -184,89 +253,5 @@ const visitHtmlUrls = ({ url, htmlAst, onUrl }) => {
184
253
  attributeName: "href",
185
254
  })
186
255
  },
187
- }
188
- const visitAttributeAsUrlSpecifier = ({
189
- type,
190
- subtype,
191
- expectedType,
192
- node,
193
- attributeName,
194
- }) => {
195
- const attribute = getHtmlNodeAttributeByName(node, attributeName)
196
- const value = attribute ? attribute.value : undefined
197
- if (value) {
198
- const generatedBy = getHtmlNodeAttributeByName(node, "generated-by")
199
- if (generatedBy) {
200
- // during build the importmap is inlined
201
- // and shoud not be considered as a dependency anymore
202
- return
203
- }
204
- addDependency({
205
- type,
206
- subtype,
207
- expectedType,
208
- node,
209
- attribute,
210
- specifier:
211
- attributeName === "generated-from-src" ||
212
- attributeName === "generated-from-href"
213
- ? new URL(value, url).href
214
- : value,
215
- })
216
- } else if (attributeName === "src") {
217
- visitAttributeAsUrlSpecifier({
218
- type,
219
- subtype,
220
- expectedType,
221
- node,
222
- attributeName: "generated-from-src",
223
- })
224
- } else if (attributeName === "href") {
225
- visitAttributeAsUrlSpecifier({
226
- type,
227
- subtype,
228
- expectedType,
229
- node,
230
- attributeName: "generated-from-href",
231
- })
232
- }
233
- }
234
- const visitSrcset = ({ type, node }) => {
235
- const srcsetAttribute = getHtmlNodeAttributeByName(node, "srcset")
236
- const srcset = srcsetAttribute ? srcsetAttribute.value : undefined
237
- if (srcset) {
238
- const srcCandidates = htmlAttributeSrcSet.parse(srcset)
239
- srcCandidates.forEach((srcCandidate) => {
240
- addDependency({
241
- type,
242
- node,
243
- attribute: srcsetAttribute,
244
- specifier: srcCandidate.specifier,
245
- })
246
- })
247
- }
248
- }
249
- visitHtmlAst(htmlAst, (node) => {
250
- const visitor = visitors[node.nodeName]
251
- if (visitor) {
252
- visitor(node)
253
- }
254
256
  })
255
257
  }
256
-
257
- const crossOriginCompatibleTagNames = ["script", "link", "img", "source"]
258
- const integrityCompatibleTagNames = ["script", "link", "img", "source"]
259
- const readFetchMetas = (node) => {
260
- const meta = {}
261
- if (crossOriginCompatibleTagNames.includes(node.nodeName)) {
262
- const crossoriginAttribute = getHtmlNodeAttributeByName(node, "crossorigin")
263
- meta.crossorigin = crossoriginAttribute
264
- ? crossoriginAttribute.value
265
- : undefined
266
- }
267
- if (integrityCompatibleTagNames.includes(node.nodeName)) {
268
- const integrityAttribute = getHtmlNodeAttributeByName(node, "integrity")
269
- meta.integrity = integrityAttribute ? integrityAttribute.value : undefined
270
- }
271
- return meta
272
- }
@@ -1,5 +1,6 @@
1
- import { parseJsUrls } from "@jsenv/utils/js_ast/parse_js_urls.js"
2
- import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
1
+ import { createMagicSource } from "@jsenv/sourcemap"
2
+ import { parseJsUrls } from "@jsenv/ast"
3
+
3
4
  import { isWebWorkerUrlInfo } from "@jsenv/core/src/omega/web_workers.js"
4
5
 
5
6
  export const parseAndTransformJsUrls = async (urlInfo, context) => {
@@ -0,0 +1,82 @@
1
+ import { URL_META } from "@jsenv/url-meta"
2
+ import { fileSystemPathToUrl } from "@jsenv/urls"
3
+
4
+ import { requireFromJsenv } from "@jsenv/core/src/require_from_jsenv.js"
5
+
6
+ // https://github.com/istanbuljs/babel-plugin-istanbul/blob/321740f7b25d803f881466ea819d870f7ed6a254/src/index.js
7
+
8
+ export const babelPluginInstrument = (
9
+ api,
10
+ {
11
+ rootDirectoryUrl,
12
+ useInlineSourceMaps = false,
13
+ coverageConfig = { "./**/*": true },
14
+ },
15
+ ) => {
16
+ const { programVisitor } = requireFromJsenv("istanbul-lib-instrument")
17
+
18
+ const { types } = api
19
+
20
+ const associations = URL_META.resolveAssociations(
21
+ { cover: coverageConfig },
22
+ rootDirectoryUrl,
23
+ )
24
+ const shouldInstrument = (url) => {
25
+ return URL_META.applyAssociations({ url, associations }).cover
26
+ }
27
+
28
+ return {
29
+ name: "transform-instrument",
30
+ visitor: {
31
+ Program: {
32
+ enter(path) {
33
+ const { file } = this
34
+ const { opts } = file
35
+ if (!opts.sourceFileName) {
36
+ console.warn(
37
+ `cannot instrument file when "sourceFileName" option is not set`,
38
+ )
39
+ return
40
+ }
41
+ const fileUrl = fileSystemPathToUrl(opts.sourceFileName)
42
+ if (!shouldInstrument(fileUrl)) {
43
+ return
44
+ }
45
+
46
+ this.__dv__ = null
47
+
48
+ let inputSourceMap
49
+
50
+ if (useInlineSourceMaps) {
51
+ // https://github.com/istanbuljs/babel-plugin-istanbul/commit/a9e15643d249a2985e4387e4308022053b2cd0ad#diff-1fdf421c05c1140f6d71444ea2b27638R65
52
+ inputSourceMap =
53
+ opts.inputSourceMap || file.inputMap
54
+ ? file.inputMap.sourcemap
55
+ : null
56
+ } else {
57
+ inputSourceMap = opts.inputSourceMap
58
+ }
59
+
60
+ this.__dv__ = programVisitor(
61
+ types,
62
+ opts.filenameRelative || opts.filename,
63
+ {
64
+ coverageVariable: "__coverage__",
65
+ inputSourceMap,
66
+ },
67
+ )
68
+ this.__dv__.enter(path)
69
+ },
70
+
71
+ exit(path) {
72
+ if (!this.__dv__) {
73
+ return
74
+ }
75
+ const object = this.__dv__.exit(path)
76
+ // object got two properties: fileCoverage and sourceMappingURL
77
+ this.file.metadata.coverage = object.fileCoverage
78
+ },
79
+ },
80
+ },
81
+ }
82
+ }
@@ -0,0 +1,36 @@
1
+ import { readFileSync } from "node:fs"
2
+ import { resolveUrl, urlToFileSystemPath } from "@jsenv/urls"
3
+
4
+ import { requireFromJsenv } from "@jsenv/core/src/require_from_jsenv.js"
5
+ import { istanbulCoverageMapFromCoverage } from "./istanbul_coverage_map_from_coverage.js"
6
+
7
+ export const generateCoverageHtmlDirectory = async (
8
+ coverage,
9
+ {
10
+ rootDirectoryUrl,
11
+ coverageHtmlDirectoryRelativeUrl,
12
+ coverageSkipEmpty,
13
+ coverageSkipFull,
14
+ },
15
+ ) => {
16
+ const libReport = requireFromJsenv("istanbul-lib-report")
17
+ const reports = requireFromJsenv("istanbul-reports")
18
+
19
+ const context = libReport.createContext({
20
+ dir: urlToFileSystemPath(rootDirectoryUrl),
21
+ coverageMap: istanbulCoverageMapFromCoverage(coverage),
22
+ sourceFinder: (path) => {
23
+ return readFileSync(
24
+ urlToFileSystemPath(resolveUrl(path, rootDirectoryUrl)),
25
+ "utf8",
26
+ )
27
+ },
28
+ })
29
+
30
+ const report = reports.create("html", {
31
+ skipEmpty: coverageSkipEmpty,
32
+ skipFull: coverageSkipFull,
33
+ subdir: coverageHtmlDirectoryRelativeUrl,
34
+ })
35
+ report.execute(context)
36
+ }
@@ -0,0 +1,22 @@
1
+ import { writeFile } from "@jsenv/filesystem"
2
+ import { urlToFileSystemPath } from "@jsenv/urls"
3
+ import { byteAsFileSize } from "@jsenv/log"
4
+
5
+ export const generateCoverageJsonFile = async ({
6
+ coverage,
7
+ coverageJsonFileUrl,
8
+ coverageJsonFileLog,
9
+ logger,
10
+ }) => {
11
+ const coverageAsText = JSON.stringify(coverage, null, " ")
12
+
13
+ if (coverageJsonFileLog) {
14
+ logger.info(
15
+ `-> ${urlToFileSystemPath(coverageJsonFileUrl)} (${byteAsFileSize(
16
+ Buffer.byteLength(coverageAsText),
17
+ )})`,
18
+ )
19
+ }
20
+
21
+ await writeFile(coverageJsonFileUrl, coverageAsText)
22
+ }
@@ -0,0 +1,19 @@
1
+ import { requireFromJsenv } from "@jsenv/core/src/require_from_jsenv.js"
2
+ import { istanbulCoverageMapFromCoverage } from "./istanbul_coverage_map_from_coverage.js"
3
+
4
+ export const generateCoverageTextLog = (
5
+ coverage,
6
+ { coverageSkipEmpty, coverageSkipFull },
7
+ ) => {
8
+ const libReport = requireFromJsenv("istanbul-lib-report")
9
+ const reports = requireFromJsenv("istanbul-reports")
10
+
11
+ const context = libReport.createContext({
12
+ coverageMap: istanbulCoverageMapFromCoverage(coverage),
13
+ })
14
+ const report = reports.create("text", {
15
+ skipEmpty: coverageSkipEmpty,
16
+ skipFull: coverageSkipFull,
17
+ })
18
+ report.execute(context)
19
+ }
@@ -0,0 +1,52 @@
1
+ import { readFile } from "@jsenv/filesystem"
2
+ import { resolveUrl } from "@jsenv/urls"
3
+ import { Abort } from "@jsenv/abort"
4
+ import { applyBabelPlugins } from "@jsenv/ast"
5
+
6
+ import { requireFromJsenv } from "@jsenv/core/src/require_from_jsenv.js"
7
+ import { babelPluginInstrument } from "./babel_plugin_instrument.js"
8
+
9
+ export const relativeUrlToEmptyCoverage = async (
10
+ relativeUrl,
11
+ { signal, rootDirectoryUrl },
12
+ ) => {
13
+ const operation = Abort.startOperation()
14
+ operation.addAbortSignal(signal)
15
+
16
+ try {
17
+ const fileUrl = resolveUrl(relativeUrl, rootDirectoryUrl)
18
+ const content = await readFile(fileUrl, { as: "string" })
19
+
20
+ operation.throwIfAborted()
21
+ const { metadata } = await applyBabelPlugins({
22
+ babelPlugins: [[babelPluginInstrument, { rootDirectoryUrl }]],
23
+ urlInfo: {
24
+ originalUrl: fileUrl,
25
+ content,
26
+ },
27
+ })
28
+ const { coverage } = metadata
29
+ if (!coverage) {
30
+ throw new Error(`missing coverage for file`)
31
+ }
32
+ // https://github.com/gotwarlost/istanbul/blob/bc84c315271a5dd4d39bcefc5925cfb61a3d174a/lib/command/common/run-with-cover.js#L229
33
+ Object.keys(coverage.s).forEach(function (key) {
34
+ coverage.s[key] = 0
35
+ })
36
+ return coverage
37
+ } catch (e) {
38
+ if (e && e.code === "PARSE_ERROR") {
39
+ // return an empty coverage for that file when
40
+ // it contains a syntax error
41
+ return createEmptyCoverage(relativeUrl)
42
+ }
43
+ throw e
44
+ } finally {
45
+ await operation.end()
46
+ }
47
+ }
48
+
49
+ const createEmptyCoverage = (relativeUrl) => {
50
+ const { createFileCoverage } = requireFromJsenv("istanbul-lib-coverage")
51
+ return createFileCoverage(relativeUrl).toJSON()
52
+ }
@@ -0,0 +1,25 @@
1
+ import {
2
+ urlToRelativeUrl,
3
+ fileSystemPathToUrl,
4
+ isFileSystemPath,
5
+ } from "@jsenv/urls"
6
+
7
+ export const normalizeFileByFileCoveragePaths = (
8
+ fileByFileCoverage,
9
+ rootDirectoryUrl,
10
+ ) => {
11
+ const fileByFileNormalized = {}
12
+ Object.keys(fileByFileCoverage).forEach((key) => {
13
+ const fileCoverage = fileByFileCoverage[key]
14
+ const { path } = fileCoverage
15
+ const url = isFileSystemPath(path)
16
+ ? fileSystemPathToUrl(path)
17
+ : new URL(path, rootDirectoryUrl).href
18
+ const relativeUrl = urlToRelativeUrl(url, rootDirectoryUrl)
19
+ fileByFileNormalized[`./${relativeUrl}`] = {
20
+ ...fileCoverage,
21
+ path: `./${relativeUrl}`,
22
+ }
23
+ })
24
+ return fileByFileNormalized
25
+ }
@@ -0,0 +1,28 @@
1
+ import { requireFromJsenv } from "@jsenv/core/src/require_from_jsenv.js"
2
+
3
+ export const composeTwoFileByFileIstanbulCoverages = (
4
+ firstFileByFileIstanbulCoverage,
5
+ secondFileByFileIstanbulCoverage,
6
+ ) => {
7
+ const fileByFileIstanbulCoverage = {}
8
+ Object.keys(firstFileByFileIstanbulCoverage).forEach((key) => {
9
+ fileByFileIstanbulCoverage[key] = firstFileByFileIstanbulCoverage[key]
10
+ })
11
+ Object.keys(secondFileByFileIstanbulCoverage).forEach((key) => {
12
+ const firstCoverage = firstFileByFileIstanbulCoverage[key]
13
+ const secondCoverage = secondFileByFileIstanbulCoverage[key]
14
+ fileByFileIstanbulCoverage[key] = firstCoverage
15
+ ? merge(firstCoverage, secondCoverage)
16
+ : secondCoverage
17
+ })
18
+
19
+ return fileByFileIstanbulCoverage
20
+ }
21
+
22
+ const merge = (firstIstanbulCoverage, secondIstanbulCoverage) => {
23
+ const { createFileCoverage } = requireFromJsenv("istanbul-lib-coverage")
24
+ const istanbulFileCoverageObject = createFileCoverage(firstIstanbulCoverage)
25
+ istanbulFileCoverageObject.merge(secondIstanbulCoverage)
26
+ const istanbulCoverage = istanbulFileCoverageObject.toJSON()
27
+ return istanbulCoverage
28
+ }
@@ -0,0 +1,16 @@
1
+ import { requireFromJsenv } from "@jsenv/core/src/require_from_jsenv.js"
2
+
3
+ export const istanbulCoverageMapFromCoverage = (coverage) => {
4
+ const { createCoverageMap } = requireFromJsenv("istanbul-lib-coverage")
5
+
6
+ const coverageAdjusted = {}
7
+ Object.keys(coverage).forEach((key) => {
8
+ coverageAdjusted[key.slice(2)] = {
9
+ ...coverage[key],
10
+ path: key.slice(2),
11
+ }
12
+ })
13
+
14
+ const coverageMap = createCoverageMap(coverageAdjusted)
15
+ return coverageMap
16
+ }
@@ -0,0 +1,15 @@
1
+ import { collectFiles } from "@jsenv/filesystem"
2
+
3
+ export const listRelativeFileUrlToCover = async ({
4
+ signal,
5
+ rootDirectoryUrl,
6
+ coverageConfig,
7
+ }) => {
8
+ const matchingFileResultArray = await collectFiles({
9
+ signal,
10
+ directoryUrl: rootDirectoryUrl,
11
+ associations: { cover: coverageConfig },
12
+ predicate: ({ cover }) => cover,
13
+ })
14
+ return matchingFileResultArray.map(({ relativeUrl }) => relativeUrl)
15
+ }
@@ -0,0 +1,41 @@
1
+ import { Abort } from "@jsenv/abort"
2
+
3
+ import { listRelativeFileUrlToCover } from "./list_files_not_covered.js"
4
+ import { relativeUrlToEmptyCoverage } from "./empty_coverage_factory.js"
5
+
6
+ export const getMissingFileByFileCoverage = async ({
7
+ signal,
8
+ rootDirectoryUrl,
9
+ coverageConfig,
10
+ fileByFileCoverage,
11
+ }) => {
12
+ const relativeUrlsToCover = await listRelativeFileUrlToCover({
13
+ signal,
14
+ rootDirectoryUrl,
15
+ coverageConfig,
16
+ })
17
+ const relativeUrlsMissing = relativeUrlsToCover.filter((relativeUrlToCover) =>
18
+ Object.keys(fileByFileCoverage).every((key) => {
19
+ return key !== `./${relativeUrlToCover}`
20
+ }),
21
+ )
22
+
23
+ const operation = Abort.startOperation()
24
+ operation.addAbortSignal(signal)
25
+ const missingFileByFileCoverage = {}
26
+ await relativeUrlsMissing.reduce(async (previous, relativeUrlMissing) => {
27
+ operation.throwIfAborted()
28
+ await previous
29
+ await operation.withSignal(async (signal) => {
30
+ const emptyCoverage = await relativeUrlToEmptyCoverage(
31
+ relativeUrlMissing,
32
+ {
33
+ signal,
34
+ rootDirectoryUrl,
35
+ },
36
+ )
37
+ missingFileByFileCoverage[`./${relativeUrlMissing}`] = emptyCoverage
38
+ })
39
+ }, Promise.resolve())
40
+ return missingFileByFileCoverage
41
+ }