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

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 (28) hide show
  1. package/dist/js/event_source_client.js +205 -1
  2. package/dist/main.js +845 -60
  3. package/package.json +2 -2
  4. package/src/build/start_build_server.js +29 -26
  5. package/src/dev/start_dev_server.js +34 -30
  6. package/src/execute/runtimes/browsers/from_playwright.js +3 -2
  7. package/src/helpers/event_source/event_source.js +197 -0
  8. package/src/helpers/event_source/sse_service.js +53 -0
  9. package/src/helpers/worker_reload.js +56 -0
  10. package/src/plugins/autoreload/dev_sse/client/event_source_client.js +1 -1
  11. package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +1 -1
  12. package/src/test/coverage/babel_plugin_instrument.js +82 -0
  13. package/src/test/coverage/coverage_reporter_html_directory.js +36 -0
  14. package/src/test/coverage/coverage_reporter_json_file.js +22 -0
  15. package/src/test/coverage/coverage_reporter_text_log.js +19 -0
  16. package/src/test/coverage/empty_coverage_factory.js +52 -0
  17. package/src/test/coverage/file_by_file_coverage.js +26 -0
  18. package/src/test/coverage/istanbul_coverage_composition.js +28 -0
  19. package/src/test/coverage/istanbul_coverage_map_from_coverage.js +16 -0
  20. package/src/test/coverage/list_files_not_covered.js +15 -0
  21. package/src/test/coverage/missing_coverage.js +41 -0
  22. package/src/test/coverage/report_to_coverage.js +196 -0
  23. package/src/test/coverage/v8_and_istanbul.js +37 -0
  24. package/src/test/coverage/v8_coverage_composition.js +24 -0
  25. package/src/test/coverage/v8_coverage_from_directory.js +87 -0
  26. package/src/test/coverage/v8_coverage_to_istanbul.js +99 -0
  27. package/src/test/execute_plan.js +2 -2
  28. package/src/test/execute_test_plan.js +3 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "27.0.0-alpha.82",
3
+ "version": "27.0.0-alpha.83",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -74,7 +74,7 @@
74
74
  "@jsenv/node-esm-resolution": "0.1.0",
75
75
  "@jsenv/server": "12.7.0",
76
76
  "@jsenv/uneval": "1.6.0",
77
- "@jsenv/utils": "1.9.1",
77
+ "@jsenv/utils": "1.9.2",
78
78
  "@jsenv/url-meta": "7.0.0",
79
79
  "@jsenv/urls": "1.2.5",
80
80
  "construct-style-sheets-polyfill": "3.1.0",
@@ -13,7 +13,6 @@
13
13
  * we want to be in the user shoes and we should not alter build files.
14
14
  */
15
15
 
16
- import { parentPort } from "node:worker_threads"
17
16
  import {
18
17
  jsenvAccessControlAllowedHeaders,
19
18
  startServer,
@@ -22,15 +21,17 @@ import {
22
21
  pluginCORS,
23
22
  fetchFileSystem,
24
23
  composeServices,
24
+ findFreePort,
25
25
  } from "@jsenv/server"
26
-
27
26
  import {
28
27
  assertAndNormalizeDirectoryUrl,
29
28
  registerDirectoryLifecycle,
30
29
  } from "@jsenv/filesystem"
30
+ import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
31
31
  import { createLogger, loggerToLevels, createTaskLog } from "@jsenv/log"
32
32
  import { getCallerPosition } from "@jsenv/urls"
33
- import { initReloadableProcess } from "@jsenv/utils/process_reload/process_reload.js"
33
+
34
+ import { createReloadableWorker } from "@jsenv/core/src/helpers/worker_reload.js"
34
35
 
35
36
  export const startBuildServer = async ({
36
37
  signal = new AbortController().signal,
@@ -56,10 +57,9 @@ export const startBuildServer = async ({
56
57
  buildServerMainFile = getCallerPosition().url,
57
58
  // force disable server autoreload when this code is executed:
58
59
  // - inside a forked child process
59
- // - inside a worker thread
60
- // (because node cluster won't work)
60
+ // - debugged by vscode
61
+ // otherwise we get net:ERR_CONNECTION_REFUSED
61
62
  buildServerAutoreload = typeof process.send !== "function" &&
62
- !parentPort &&
63
63
  !process.env.VSCODE_INSPECTOR_OPTIONS,
64
64
  cooldownBetweenFileEvents,
65
65
  }) => {
@@ -67,25 +67,29 @@ export const startBuildServer = async ({
67
67
  rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
68
68
  buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl)
69
69
 
70
- const reloadableProcess = await initReloadableProcess({
71
- signal,
72
- handleSIGINT,
73
- ...(buildServerAutoreload
74
- ? {
75
- enabled: true,
76
- logLevel: "info",
77
- fileToRestart: buildServerMainFile,
78
- }
79
- : {
80
- enabled: false,
81
- }),
82
- })
83
- if (reloadableProcess.isPrimary) {
70
+ const operation = Abort.startOperation()
71
+ operation.addAbortSignal(signal)
72
+ if (handleSIGINT) {
73
+ operation.addAbortSource((abort) => {
74
+ return raceProcessTeardownEvents(
75
+ {
76
+ SIGINT: true,
77
+ },
78
+ abort,
79
+ )
80
+ })
81
+ }
82
+ if (port === 0) {
83
+ port = await findFreePort(port, { signal: operation.signal })
84
+ }
85
+
86
+ const reloadableWorker = createReloadableWorker(buildServerMainFile)
87
+ if (buildServerAutoreload && reloadableWorker.isPrimary) {
84
88
  const buildServerFileChangeCallback = ({ relativeUrl, event }) => {
85
89
  const url = new URL(relativeUrl, rootDirectoryUrl).href
86
90
  if (buildServerAutoreload) {
87
91
  logger.info(`file ${event} ${url} -> restarting server...`)
88
- reloadableProcess.reload()
92
+ reloadableWorker.reload()
89
93
  }
90
94
  }
91
95
  const stopWatchingBuildServerFiles = registerDirectoryLifecycle(
@@ -109,19 +113,19 @@ export const startBuildServer = async ({
109
113
  },
110
114
  },
111
115
  )
112
- signal.addEventListener("abort", () => {
116
+ operation.addAbortCallback(() => {
113
117
  stopWatchingBuildServerFiles()
118
+ reloadableWorker.terminate()
114
119
  })
120
+ await reloadableWorker.load()
115
121
  return {
116
122
  origin: `${protocol}://127.0.0.1:${port}`,
117
123
  stop: () => {
118
124
  stopWatchingBuildServerFiles()
119
-
120
- reloadableProcess.stop()
125
+ reloadableWorker.terminate()
121
126
  },
122
127
  }
123
128
  }
124
- signal = reloadableProcess.signal
125
129
 
126
130
  const startBuildServerTask = createTaskLog("start build server", {
127
131
  disabled: !loggerToLevels(logger).info,
@@ -170,7 +174,6 @@ export const startBuildServer = async ({
170
174
  logger.info(`- ${server.origins[key]}`)
171
175
  })
172
176
  logger.info(``)
173
-
174
177
  return {
175
178
  origin: server.origin,
176
179
  stop: () => {
@@ -1,12 +1,13 @@
1
- import { parentPort } from "node:worker_threads"
2
-
1
+ import { findFreePort } from "@jsenv/server"
3
2
  import {
4
3
  assertAndNormalizeDirectoryUrl,
5
4
  registerDirectoryLifecycle,
6
5
  } from "@jsenv/filesystem"
6
+ import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
7
7
  import { createLogger, loggerToLevels, createTaskLog } from "@jsenv/log"
8
8
  import { getCallerPosition } from "@jsenv/urls"
9
- import { initReloadableProcess } from "@jsenv/utils/process_reload/process_reload.js"
9
+
10
+ import { createReloadableWorker } from "@jsenv/core/src/helpers/worker_reload.js"
10
11
  import { getCorePlugins } from "@jsenv/core/src/plugins/plugins.js"
11
12
  import { createUrlGraph } from "@jsenv/core/src/omega/url_graph.js"
12
13
  import { createKitchen } from "@jsenv/core/src/omega/kitchen.js"
@@ -17,7 +18,7 @@ import { jsenvPluginExplorer } from "./plugins/explorer/jsenv_plugin_explorer.js
17
18
 
18
19
  export const startDevServer = async ({
19
20
  signal = new AbortController().signal,
20
- handleSIGINT,
21
+ handleSIGINT = true,
21
22
  logLevel = "info",
22
23
  omegaServerLogLevel = "warn",
23
24
  port = 3456,
@@ -37,10 +38,9 @@ export const startDevServer = async ({
37
38
  devServerMainFile = getCallerPosition().url,
38
39
  // force disable server autoreload when this code is executed:
39
40
  // - inside a forked child process
40
- // - inside a worker thread
41
- // (because node cluster won't work)
41
+ // - debugged by vscode
42
+ // otherwise we get net:ERR_CONNECTION_REFUSED
42
43
  devServerAutoreload = typeof process.send !== "function" &&
43
- !parentPort &&
44
44
  !process.env.VSCODE_INSPECTOR_OPTIONS,
45
45
  clientFiles = {
46
46
  "./src/": true,
@@ -80,28 +80,30 @@ export const startDevServer = async ({
80
80
  }) => {
81
81
  const logger = createLogger({ logLevel })
82
82
  rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
83
- const reloadableProcess = await initReloadableProcess({
84
- signal,
85
- handleSIGINT,
86
- ...(devServerAutoreload
87
- ? {
88
- enabled: true,
89
- logLevel: "warn",
90
- fileToRestart: devServerMainFile,
91
- }
92
- : {
93
- enabled: false,
94
- }),
95
- })
96
- if (reloadableProcess.isPrimary) {
83
+ const operation = Abort.startOperation()
84
+ operation.addAbortSignal(signal)
85
+ if (handleSIGINT) {
86
+ operation.addAbortSource((abort) => {
87
+ return raceProcessTeardownEvents(
88
+ {
89
+ SIGINT: true,
90
+ },
91
+ abort,
92
+ )
93
+ })
94
+ }
95
+ if (port === 0) {
96
+ port = await findFreePort(port, { signal: operation.signal })
97
+ }
98
+
99
+ const reloadableWorker = createReloadableWorker(devServerMainFile)
100
+ if (devServerAutoreload && reloadableWorker.isPrimary) {
97
101
  const devServerFileChangeCallback = ({ relativeUrl, event }) => {
98
102
  const url = new URL(relativeUrl, rootDirectoryUrl).href
99
- if (devServerAutoreload) {
100
- logger.info(`file ${event} ${url} -> restarting server...`)
101
- reloadableProcess.reload()
102
- }
103
+ logger.info(`file ${event} ${url} -> restarting server...`)
104
+ reloadableWorker.reload()
103
105
  }
104
- const unregisterDevServerFilesWatcher = registerDirectoryLifecycle(
106
+ const stopWatchingDevServerFiles = registerDirectoryLifecycle(
105
107
  rootDirectoryUrl,
106
108
  {
107
109
  watchPatterns: {
@@ -122,14 +124,16 @@ export const startDevServer = async ({
122
124
  },
123
125
  },
124
126
  )
125
- signal.addEventListener("abort", () => {
126
- unregisterDevServerFilesWatcher()
127
+ operation.addAbortCallback(() => {
128
+ stopWatchingDevServerFiles()
129
+ reloadableWorker.terminate()
127
130
  })
131
+ await reloadableWorker.load()
128
132
  return {
129
133
  origin: `${protocol}://127.0.0.1:${port}`,
130
134
  stop: () => {
131
- unregisterDevServerFilesWatcher()
132
- reloadableProcess.stop()
135
+ stopWatchingDevServerFiles()
136
+ reloadableWorker.terminate()
133
137
  },
134
138
  }
135
139
  }
@@ -9,10 +9,11 @@ import {
9
9
  } from "@jsenv/abort"
10
10
  import { moveUrl } from "@jsenv/urls"
11
11
  import { memoize } from "@jsenv/utils/memoize/memoize.js"
12
- import { filterV8Coverage } from "@jsenv/utils/coverage/v8_coverage_from_directory.js"
13
- import { composeTwoFileByFileIstanbulCoverages } from "@jsenv/utils/coverage/istanbul_coverage_composition.js"
14
12
  import { escapeRegexpSpecialChars } from "@jsenv/utils/string/escape_regexp_special_chars.js"
15
13
 
14
+ import { filterV8Coverage } from "@jsenv/core/src/test/coverage/v8_coverage_from_directory.js"
15
+ import { composeTwoFileByFileIstanbulCoverages } from "@jsenv/core/src/test/coverage/istanbul_coverage_composition.js"
16
+
16
17
  export const createRuntimeFromPlaywright = ({
17
18
  browserName,
18
19
  browserVersion,
@@ -0,0 +1,197 @@
1
+ /* eslint-env browser */
2
+
3
+ export const createEventSourceConnection = (
4
+ eventSourceUrl,
5
+ events = {},
6
+ { retryMaxAttempt = Infinity, retryAllocatedMs = Infinity, lastEventId } = {},
7
+ ) => {
8
+ const { EventSource } = window
9
+ if (typeof EventSource !== "function") {
10
+ return () => {}
11
+ }
12
+
13
+ const eventSourceOrigin = new URL(eventSourceUrl).origin
14
+ Object.keys(events).forEach((eventName) => {
15
+ const eventCallback = events[eventName]
16
+ events[eventName] = (e) => {
17
+ if (e.origin === eventSourceOrigin) {
18
+ if (e.lastEventId) {
19
+ lastEventId = e.lastEventId
20
+ }
21
+ eventCallback(e)
22
+ }
23
+ }
24
+ })
25
+
26
+ const status = {
27
+ value: "default",
28
+ goTo: (value) => {
29
+ if (value === status.value) {
30
+ return
31
+ }
32
+ status.value = value
33
+ status.onchange()
34
+ },
35
+ onchange: () => {},
36
+ }
37
+ let _disconnect = () => {}
38
+
39
+ const attemptConnection = (url) => {
40
+ const eventSource = new EventSource(url, {
41
+ withCredentials: true,
42
+ })
43
+ _disconnect = () => {
44
+ if (status.value !== "connecting" && status.value !== "connected") {
45
+ console.warn(
46
+ `disconnect() ignored because connection is ${status.value}`,
47
+ )
48
+ return
49
+ }
50
+ eventSource.onerror = undefined
51
+ eventSource.close()
52
+ Object.keys(events).forEach((eventName) => {
53
+ eventSource.removeEventListener(eventName, events[eventName])
54
+ })
55
+ status.goTo("disconnected")
56
+ }
57
+ let retryCount = 0
58
+ let firstRetryMs = Date.now()
59
+ eventSource.onerror = (errorEvent) => {
60
+ if (errorEvent.target.readyState === EventSource.CONNECTING) {
61
+ if (retryCount > retryMaxAttempt) {
62
+ console.info(`could not connect after ${retryMaxAttempt} attempt`)
63
+ _disconnect()
64
+ return
65
+ }
66
+
67
+ if (retryCount === 0) {
68
+ firstRetryMs = Date.now()
69
+ } else {
70
+ const allRetryDuration = Date.now() - firstRetryMs
71
+ if (retryAllocatedMs && allRetryDuration > retryAllocatedMs) {
72
+ console.info(
73
+ `could not connect in less than ${retryAllocatedMs} ms`,
74
+ )
75
+ _disconnect()
76
+ return
77
+ }
78
+ }
79
+
80
+ retryCount++
81
+ status.goTo("connecting")
82
+ return
83
+ }
84
+
85
+ if (errorEvent.target.readyState === EventSource.CLOSED) {
86
+ _disconnect()
87
+ return
88
+ }
89
+ }
90
+ eventSource.onopen = () => {
91
+ status.goTo("connected")
92
+ }
93
+ Object.keys(events).forEach((eventName) => {
94
+ eventSource.addEventListener(eventName, events[eventName])
95
+ })
96
+ if (!events.hasOwnProperty("welcome")) {
97
+ eventSource.addEventListener("welcome", (e) => {
98
+ if (e.origin === eventSourceOrigin && e.lastEventId) {
99
+ lastEventId = e.lastEventId
100
+ }
101
+ })
102
+ }
103
+ status.goTo("connecting")
104
+ }
105
+
106
+ let connect = () => {
107
+ attemptConnection(eventSourceUrl)
108
+ connect = () => {
109
+ attemptConnection(
110
+ lastEventId
111
+ ? addLastEventIdIntoUrlSearchParams(eventSourceUrl, lastEventId)
112
+ : eventSourceUrl,
113
+ )
114
+ }
115
+ }
116
+
117
+ const removePageUnloadListener = listenPageUnload(() => {
118
+ if (status.value === "connecting" || status.value === "connected") {
119
+ _disconnect()
120
+ }
121
+ })
122
+
123
+ const destroy = () => {
124
+ removePageUnloadListener()
125
+ _disconnect()
126
+ }
127
+
128
+ return {
129
+ status,
130
+ connect,
131
+ disconnect: () => _disconnect(),
132
+ destroy,
133
+ }
134
+ }
135
+
136
+ const addLastEventIdIntoUrlSearchParams = (url, lastEventId) => {
137
+ if (url.indexOf("?") === -1) {
138
+ url += "?"
139
+ } else {
140
+ url += "&"
141
+ }
142
+ return `${url}last-event-id=${encodeURIComponent(lastEventId)}`
143
+ }
144
+
145
+ // const listenPageMightFreeze = (callback) => {
146
+ // const removePageHideListener = listenEvent(window, "pagehide", (pageHideEvent) => {
147
+ // if (pageHideEvent.persisted === true) {
148
+ // callback(pageHideEvent)
149
+ // }
150
+ // })
151
+ // return removePageHideListener
152
+ // }
153
+
154
+ // const listenPageFreeze = (callback) => {
155
+ // const removeFreezeListener = listenEvent(document, "freeze", (freezeEvent) => {
156
+ // callback(freezeEvent)
157
+ // })
158
+ // return removeFreezeListener
159
+ // }
160
+
161
+ // const listenPageIsRestored = (callback) => {
162
+ // const removeResumeListener = listenEvent(document, "resume", (resumeEvent) => {
163
+ // removePageshowListener()
164
+ // callback(resumeEvent)
165
+ // })
166
+ // const removePageshowListener = listenEvent(window, "pageshow", (pageshowEvent) => {
167
+ // if (pageshowEvent.persisted === true) {
168
+ // removePageshowListener()
169
+ // removeResumeListener()
170
+ // callback(pageshowEvent)
171
+ // }
172
+ // })
173
+ // return () => {
174
+ // removeResumeListener()
175
+ // removePageshowListener()
176
+ // }
177
+ // }
178
+
179
+ const listenPageUnload = (callback) => {
180
+ const removePageHideListener = listenEvent(
181
+ window,
182
+ "pagehide",
183
+ (pageHideEvent) => {
184
+ if (pageHideEvent.persisted !== true) {
185
+ callback(pageHideEvent)
186
+ }
187
+ },
188
+ )
189
+ return removePageHideListener
190
+ }
191
+
192
+ const listenEvent = (emitter, event, callback) => {
193
+ emitter.addEventListener(event, callback)
194
+ return () => {
195
+ emitter.removeEventListener(event, callback)
196
+ }
197
+ }
@@ -0,0 +1,53 @@
1
+ import { createSSERoom } from "@jsenv/server"
2
+ import { createCallbackListNotifiedOnce } from "@jsenv/abort"
3
+
4
+ export const createSSEService = ({ serverEventCallbackList }) => {
5
+ const destroyCallbackList = createCallbackListNotifiedOnce()
6
+
7
+ const cache = []
8
+ const sseRoomLimit = 100
9
+ const getOrCreateSSERoom = (request) => {
10
+ const htmlFileRelativeUrl = request.ressource.slice(1)
11
+ const cacheEntry = cache.find(
12
+ (cacheEntryCandidate) =>
13
+ cacheEntryCandidate.htmlFileRelativeUrl === htmlFileRelativeUrl,
14
+ )
15
+ if (cacheEntry) {
16
+ return cacheEntry.sseRoom
17
+ }
18
+ const sseRoom = createSSERoom({
19
+ retryDuration: 2000,
20
+ historyLength: 100,
21
+ welcomeEventEnabled: true,
22
+ effect: () => {
23
+ return serverEventCallbackList.add((event) => {
24
+ sseRoom.sendEvent(event)
25
+ })
26
+ },
27
+ })
28
+ const removeSSECleanupCallback = destroyCallbackList.add(() => {
29
+ removeSSECleanupCallback()
30
+ sseRoom.close()
31
+ })
32
+ cache.push({
33
+ htmlFileRelativeUrl,
34
+ sseRoom,
35
+ cleanup: () => {
36
+ removeSSECleanupCallback()
37
+ sseRoom.close()
38
+ },
39
+ })
40
+ if (cache.length >= sseRoomLimit) {
41
+ const firstCacheEntry = cache.shift()
42
+ firstCacheEntry.cleanup()
43
+ }
44
+ return sseRoom
45
+ }
46
+
47
+ return {
48
+ getOrCreateSSERoom,
49
+ destroy: () => {
50
+ destroyCallbackList.notify()
51
+ },
52
+ }
53
+ }
@@ -0,0 +1,56 @@
1
+ import { fileURLToPath } from "node:url"
2
+ import { Worker, workerData } from "node:worker_threads"
3
+
4
+ // https://nodejs.org/api/worker_threads.html
5
+ export const createReloadableWorker = (workerFileUrl, options = {}) => {
6
+ const workerFilePath = fileURLToPath(workerFileUrl)
7
+ const isPrimary = !workerData || workerData.workerFilePath !== workerFilePath
8
+ let worker
9
+
10
+ const terminate = async () => {
11
+ if (worker) {
12
+ let _worker = worker
13
+ worker = null
14
+ const exitPromise = new Promise((resolve) => {
15
+ _worker.once("exit", resolve)
16
+ })
17
+ _worker.terminate()
18
+ await exitPromise
19
+ }
20
+ }
21
+
22
+ const load = async () => {
23
+ if (!isPrimary) {
24
+ throw new Error(`worker can be loaded from primary file only`)
25
+ }
26
+ worker = new Worker(workerFilePath, {
27
+ ...options,
28
+ workerData: {
29
+ ...options.workerData,
30
+ workerFilePath,
31
+ },
32
+ })
33
+
34
+ worker.once("error", (error) => {
35
+ console.error(error)
36
+ })
37
+ worker.once("exit", () => {
38
+ worker = null
39
+ })
40
+ await new Promise((resolve) => {
41
+ worker.once("online", resolve)
42
+ })
43
+ }
44
+
45
+ const reload = async () => {
46
+ await terminate()
47
+ await load()
48
+ }
49
+
50
+ return {
51
+ isPrimary,
52
+ load,
53
+ reload,
54
+ terminate,
55
+ }
56
+ }
@@ -1,4 +1,4 @@
1
- import { createEventSourceConnection } from "@jsenv/utils/event_source/event_source.js"
1
+ import { createEventSourceConnection } from "@jsenv/core/src/helpers/event_source/event_source.js"
2
2
  import { urlHotMetas } from "../../../import_meta_hot/client/import_meta_hot.js"
3
3
  import {
4
4
  isAutoreloadEnabled,
@@ -1,7 +1,7 @@
1
1
  import { urlToRelativeUrl } from "@jsenv/urls"
2
2
  import { createCallbackList } from "@jsenv/abort"
3
3
 
4
- import { createSSEService } from "@jsenv/utils/event_source/sse_service.js"
4
+ import { createSSEService } from "@jsenv/core/src/helpers/event_source/sse_service.js"
5
5
 
6
6
  export const jsenvPluginDevSSEServer = ({
7
7
  rootDirectoryUrl,
@@ -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
+ }