@jsenv/core 27.4.0 → 27.5.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 (84) hide show
  1. package/dist/js/autoreload.js +359 -0
  2. package/dist/js/execute_using_dynamic_import.js +1 -1
  3. package/dist/js/html_supervisor_installer.js +221 -73
  4. package/dist/js/html_supervisor_setup.js +3 -4
  5. package/dist/js/new_stylesheet.js +26 -58
  6. package/dist/js/server_events_client.js +307 -0
  7. package/dist/main.js +7483 -7281
  8. package/package.json +11 -10
  9. package/{README.md → readme.md} +8 -9
  10. package/src/build/build.js +12 -16
  11. package/src/build/start_build_server.js +24 -28
  12. package/src/dev/start_dev_server.js +30 -94
  13. package/src/execute/execute.js +17 -35
  14. package/src/omega/errors.js +20 -18
  15. package/src/omega/kitchen.js +7 -6
  16. package/src/omega/omega_server.js +96 -127
  17. package/src/omega/server/file_service.js +247 -46
  18. package/src/omega/url_graph.js +33 -20
  19. package/src/plugins/autoreload/client/autoreload.js +201 -0
  20. package/src/plugins/autoreload/{dev_sse/client → client}/autoreload_preference.js +0 -0
  21. package/src/plugins/autoreload/{dev_sse/client → client}/reload.js +29 -10
  22. package/src/plugins/autoreload/{dev_sse/client → client}/url_helpers.js +0 -0
  23. package/src/plugins/autoreload/jsenv_plugin_autoreload.js +4 -4
  24. package/src/plugins/autoreload/{dev_sse/jsenv_plugin_dev_sse_client.js → jsenv_plugin_autoreload_client.js} +8 -8
  25. package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +196 -0
  26. package/src/{dev/plugins → plugins}/explorer/client/explorer.html +0 -0
  27. package/src/{dev/plugins → plugins}/explorer/client/jsenv.png +0 -0
  28. package/src/{dev/plugins → plugins}/explorer/jsenv_plugin_explorer.js +1 -3
  29. package/src/plugins/html_supervisor/client/{error_in_document.js → error_overlay.js} +73 -17
  30. package/src/plugins/html_supervisor/client/html_supervisor_installer.js +127 -54
  31. package/src/plugins/html_supervisor/client/html_supervisor_setup.js +3 -4
  32. package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +19 -12
  33. package/src/plugins/inline/jsenv_plugin_html_inline_content.js +97 -117
  34. package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +66 -58
  35. package/src/plugins/plugin_controller.js +102 -67
  36. package/src/plugins/plugins.js +10 -8
  37. package/src/{helpers/event_source/event_source.js → plugins/server_events/client/event_source_connection.js} +102 -31
  38. package/src/plugins/server_events/client/server_events_client.js +17 -0
  39. package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +48 -0
  40. package/src/plugins/server_events/server_events_dispatcher.js +69 -0
  41. package/src/{dev/plugins → plugins}/toolbar/client/animation/toolbar_animation.js +0 -0
  42. package/src/{dev/plugins → plugins}/toolbar/client/eventsource/eventsource.css +0 -0
  43. package/src/{dev/plugins → plugins}/toolbar/client/eventsource/toolbar_eventsource.js +0 -0
  44. package/src/{dev/plugins → plugins}/toolbar/client/execution/execution.css +0 -0
  45. package/src/{dev/plugins → plugins}/toolbar/client/execution/toolbar_execution.js +0 -0
  46. package/src/{dev/plugins → plugins}/toolbar/client/focus/focus.css +0 -0
  47. package/src/{dev/plugins → plugins}/toolbar/client/focus/toolbar_focus.js +0 -0
  48. package/src/{dev/plugins → plugins}/toolbar/client/jsenv_logo.svg +0 -0
  49. package/src/{dev/plugins → plugins}/toolbar/client/notification/toolbar_notification.js +0 -0
  50. package/src/{dev/plugins → plugins}/toolbar/client/responsive/overflow_menu.css +0 -0
  51. package/src/{dev/plugins → plugins}/toolbar/client/responsive/toolbar_responsive.js +0 -0
  52. package/src/{dev/plugins → plugins}/toolbar/client/settings/settings.css +0 -0
  53. package/src/{dev/plugins → plugins}/toolbar/client/settings/toolbar_settings.js +0 -0
  54. package/src/{dev/plugins → plugins}/toolbar/client/theme/jsenv_theme.css +0 -0
  55. package/src/{dev/plugins → plugins}/toolbar/client/theme/light_theme.css +0 -0
  56. package/src/{dev/plugins → plugins}/toolbar/client/theme/toolbar_theme.js +0 -0
  57. package/src/{dev/plugins → plugins}/toolbar/client/toolbar.html +0 -0
  58. package/src/{dev/plugins → plugins}/toolbar/client/toolbar_injector.js +0 -0
  59. package/src/{dev/plugins → plugins}/toolbar/client/toolbar_main.css +0 -0
  60. package/src/{dev/plugins → plugins}/toolbar/client/toolbar_main.js +0 -0
  61. package/src/{dev/plugins → plugins}/toolbar/client/tooltip/tooltip.css +0 -0
  62. package/src/{dev/plugins → plugins}/toolbar/client/tooltip/tooltip.js +0 -0
  63. package/src/{dev/plugins → plugins}/toolbar/client/util/animation.js +0 -0
  64. package/src/{dev/plugins → plugins}/toolbar/client/util/dom.js +0 -0
  65. package/src/{dev/plugins → plugins}/toolbar/client/util/fetch_using_xhr.js +0 -0
  66. package/src/{dev/plugins → plugins}/toolbar/client/util/fetching.js +0 -0
  67. package/src/{dev/plugins → plugins}/toolbar/client/util/iframe_to_parent_href.js +0 -0
  68. package/src/{dev/plugins → plugins}/toolbar/client/util/jsenv_logger.js +0 -0
  69. package/src/{dev/plugins → plugins}/toolbar/client/util/preferences.js +0 -0
  70. package/src/{dev/plugins → plugins}/toolbar/client/util/responsive.js +0 -0
  71. package/src/{dev/plugins → plugins}/toolbar/client/util/util.js +0 -0
  72. package/src/{dev/plugins → plugins}/toolbar/client/variant/variant.js +0 -0
  73. package/src/{dev/plugins → plugins}/toolbar/jsenv_plugin_toolbar.js +0 -0
  74. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +4 -3
  75. package/src/plugins/transpilation/babel/new_stylesheet/client/new_stylesheet.js +25 -55
  76. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +44 -24
  77. package/src/plugins/transpilation/jsenv_plugin_transpilation.js +6 -1
  78. package/src/plugins/url_analysis/html/html_urls.js +8 -8
  79. package/src/test/execute_plan.js +36 -54
  80. package/src/test/execute_test_plan.js +2 -2
  81. package/dist/js/event_source_client.js +0 -549
  82. package/src/helpers/event_source/sse_service.js +0 -53
  83. package/src/plugins/autoreload/dev_sse/client/event_source_client.js +0 -193
  84. package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +0 -192
@@ -20,129 +20,109 @@ export const jsenvPluginHtmlInlineContent = ({ analyzeConvertedScripts }) => {
20
20
  html: async (urlInfo, context) => {
21
21
  const htmlAst = parseHtmlString(urlInfo.content)
22
22
  const actions = []
23
- const handleInlineStyle = (node) => {
24
- const htmlNodeText = getHtmlNodeText(node)
25
- if (!htmlNodeText) {
26
- return
27
- }
28
- actions.push(async () => {
29
- const { line, column, lineEnd, columnEnd, isOriginal } =
30
- getHtmlNodePosition(node, {
31
- preferOriginal: true,
23
+ visitHtmlNodes(htmlAst, {
24
+ style: (styleNode) => {
25
+ const styleNodeText = getHtmlNodeText(styleNode)
26
+ if (!styleNodeText) {
27
+ return
28
+ }
29
+ actions.push(async () => {
30
+ const { line, column, lineEnd, columnEnd, isOriginal } =
31
+ getHtmlNodePosition(styleNode, {
32
+ preferOriginal: true,
33
+ })
34
+ const inlineStyleUrl = generateInlineContentUrl({
35
+ url: urlInfo.url,
36
+ extension: ".css",
37
+ line,
38
+ column,
39
+ lineEnd,
40
+ columnEnd,
32
41
  })
33
- const inlineStyleUrl = generateInlineContentUrl({
34
- url: urlInfo.url,
35
- extension: ".css",
36
- line,
37
- column,
38
- lineEnd,
39
- columnEnd,
40
- })
41
- const [inlineStyleReference, inlineStyleUrlInfo] =
42
- context.referenceUtils.foundInline({
43
- type: "link_href",
44
- expectedType: "css",
45
- isOriginalPosition: isOriginal,
46
- // we remove 1 to the line because imagine the following html:
47
- // <style>body { color: red; }</style>
48
- // -> content starts same line as <style>
49
- specifierLine: line - 1,
50
- specifierColumn: column,
51
- specifier: inlineStyleUrl,
52
- contentType: "text/css",
53
- content: htmlNodeText,
42
+ const [inlineStyleReference, inlineStyleUrlInfo] =
43
+ context.referenceUtils.foundInline({
44
+ node: styleNode,
45
+ type: "link_href",
46
+ expectedType: "css",
47
+ isOriginalPosition: isOriginal,
48
+ // we remove 1 to the line because imagine the following html:
49
+ // <style>body { color: red; }</style>
50
+ // -> content starts same line as <style>
51
+ specifierLine: line - 1,
52
+ specifierColumn: column,
53
+ specifier: inlineStyleUrl,
54
+ contentType: "text/css",
55
+ content: styleNodeText,
56
+ })
57
+ await context.cook(inlineStyleUrlInfo, {
58
+ reference: inlineStyleReference,
54
59
  })
55
- await context.cook(inlineStyleUrlInfo, {
56
- reference: inlineStyleReference,
57
- })
58
- setHtmlNodeText(node, inlineStyleUrlInfo.content)
59
- setHtmlNodeAttributes(node, {
60
- "generated-by": "jsenv:html_inline_content",
61
- })
62
- })
63
- }
64
- const handleInlineScript = (node) => {
65
- const htmlNodeText = getHtmlNodeText(node)
66
- if (!htmlNodeText) {
67
- return
68
- }
69
- // If the inline script was already handled by an other plugin, ignore it
70
- // - we want to preserve inline scripts generated by html supervisor during dev
71
- // - we want to avoid cooking twice a script during build
72
- const generatedBy = getHtmlNodeAttribute(node, "generated-by")
73
- if (
74
- generatedBy === "jsenv:as_js_classic_html" &&
75
- !analyzeConvertedScripts
76
- ) {
77
- return
78
- }
79
- if (generatedBy === "jsenv:html_supervisor") {
80
- return
81
- }
82
- actions.push(async () => {
83
- const scriptCategory = analyzeScriptNode(node)
84
- const { line, column, lineEnd, columnEnd, isOriginal } =
85
- getHtmlNodePosition(node, {
86
- preferOriginal: true,
60
+ setHtmlNodeText(styleNode, inlineStyleUrlInfo.content)
61
+ setHtmlNodeAttributes(styleNode, {
62
+ "generated-by": "jsenv:html_inline_content",
87
63
  })
88
- // from MDN about [type] attribute:
89
- // "Any other value: The embedded content is treated as a data block
90
- // which won't be processed by the browser. Developers must use a valid MIME type
91
- // that is not a JavaScript MIME type to denote data blocks.
92
- // The src attribute will be ignored."
93
- // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type
94
- const isJs =
95
- scriptCategory === "classic" || scriptCategory === "module"
96
- const isImportmap = scriptCategory === "importmap"
97
- const contentType = isJs
98
- ? "text/javascript"
99
- : isImportmap
100
- ? "application/importmap+json"
101
- : scriptCategory
102
-
103
- let inlineScriptUrl = generateInlineContentUrl({
104
- url: urlInfo.url,
105
- extension: CONTENT_TYPE.asFileExtension(contentType),
106
- line,
107
- column,
108
- lineEnd,
109
- columnEnd,
110
64
  })
111
- const [inlineScriptReference, inlineScriptUrlInfo] =
112
- context.referenceUtils.foundInline({
113
- node,
114
- type: "script_src",
115
- expectedType: {
116
- classic: "js_classic",
117
- module: "js_module",
118
- importmap: "importmap",
119
- }[scriptCategory],
120
- // we remove 1 to the line because imagine the following html:
121
- // <script>console.log('ok')</script>
122
- // -> content starts same line as <script>
123
- specifierLine: line - 1,
124
- specifierColumn: column,
125
- isOriginalPosition: isOriginal,
126
- specifier: inlineScriptUrl,
127
- contentType,
128
- content: htmlNodeText,
65
+ },
66
+ script: (scriptNode) => {
67
+ const scriptNodeText = getHtmlNodeText(scriptNode)
68
+ if (!scriptNodeText) {
69
+ return
70
+ }
71
+ // If the inline script was already handled by an other plugin, ignore it
72
+ // - we want to preserve inline scripts generated by html supervisor during dev
73
+ // - we want to avoid cooking twice a script during build
74
+ const generatedBy = getHtmlNodeAttribute(scriptNode, "generated-by")
75
+ if (
76
+ generatedBy === "jsenv:as_js_classic_html" &&
77
+ !analyzeConvertedScripts
78
+ ) {
79
+ return
80
+ }
81
+ if (generatedBy === "jsenv:html_supervisor") {
82
+ return
83
+ }
84
+ actions.push(async () => {
85
+ const { type, contentType, extension } =
86
+ analyzeScriptNode(scriptNode)
87
+ const { line, column, lineEnd, columnEnd, isOriginal } =
88
+ getHtmlNodePosition(scriptNode, {
89
+ preferOriginal: true,
90
+ })
91
+ let inlineScriptUrl = generateInlineContentUrl({
92
+ url: urlInfo.url,
93
+ extension:
94
+ extension || CONTENT_TYPE.asFileExtension(contentType),
95
+ line,
96
+ column,
97
+ lineEnd,
98
+ columnEnd,
99
+ })
100
+ const [inlineScriptReference, inlineScriptUrlInfo] =
101
+ context.referenceUtils.foundInline({
102
+ node: scriptNode,
103
+ type: "script_src",
104
+ expectedType: type,
105
+ // we remove 1 to the line because imagine the following html:
106
+ // <script>console.log('ok')</script>
107
+ // -> content starts same line as <script>
108
+ specifierLine: line - 1,
109
+ specifierColumn: column,
110
+ isOriginalPosition: isOriginal,
111
+ specifier: inlineScriptUrl,
112
+ contentType,
113
+ content: scriptNodeText,
114
+ })
115
+ await context.cook(inlineScriptUrlInfo, {
116
+ reference: inlineScriptReference,
117
+ })
118
+ setHtmlNodeText(scriptNode, inlineScriptUrlInfo.content)
119
+ setHtmlNodeAttributes(scriptNode, {
120
+ "generated-by": "jsenv:html_inline_content",
121
+ ...(extension
122
+ ? { type: type === "js_module" ? "module" : undefined }
123
+ : {}),
129
124
  })
130
-
131
- await context.cook(inlineScriptUrlInfo, {
132
- reference: inlineScriptReference,
133
- })
134
- setHtmlNodeText(node, inlineScriptUrlInfo.content)
135
- setHtmlNodeAttributes(node, {
136
- "generated-by": "jsenv:html_inline_content",
137
125
  })
138
- })
139
- }
140
- visitHtmlNodes(htmlAst, {
141
- style: (node) => {
142
- handleInlineStyle(node)
143
- },
144
- script: (node) => {
145
- handleInlineScript(node)
146
126
  },
147
127
  })
148
128
  if (actions.length === 0) {
@@ -17,72 +17,78 @@ import {
17
17
  } from "@jsenv/node-esm-resolution"
18
18
 
19
19
  export const jsenvPluginNodeEsmResolution = ({
20
- rootDirectoryUrl,
21
- urlGraph,
22
- runtimeCompat,
23
20
  packageConditions,
24
21
  filesInvalidatingCache = ["package.json", "package-lock.json"],
25
22
  }) => {
26
- const nodeRuntimeEnabled = Object.keys(runtimeCompat).includes("node")
27
- // https://nodejs.org/api/esm.html#resolver-algorithm-specification
28
- packageConditions = packageConditions || [
29
- ...readCustomConditionsFromProcessArgs(),
30
- nodeRuntimeEnabled ? "node" : "browser",
31
- "import",
32
- ]
33
-
34
- const packageScopesCache = new Map()
35
- const lookupPackageScope = (url) => {
36
- const fromCache = packageScopesCache.get(url)
37
- if (fromCache) {
38
- return fromCache
39
- }
40
- const packageScope = defaultLookupPackageScope(url)
41
- packageScopesCache.set(url, packageScope)
42
- return packageScope
43
- }
44
- const packageJsonsCache = new Map()
45
- const readPackageJson = (url) => {
46
- const fromCache = packageJsonsCache.get(url)
47
- if (fromCache) {
48
- return fromCache
49
- }
50
- const packageJson = defaultReadPackageJson(url)
51
- packageJsonsCache.set(url, packageJson)
52
- return packageJson
53
- }
54
-
55
23
  const unregisters = []
56
- const onFileChange = () => {
57
- packageScopesCache.clear()
58
- packageJsonsCache.clear()
59
- urlGraph.urlInfoMap.forEach((urlInfo) => {
60
- if (urlInfo.dependsOnPackageJson) {
61
- urlGraph.considerModified(urlInfo)
62
- }
63
- })
64
- }
65
- filesInvalidatingCache.forEach((file) => {
66
- const unregister = registerFileLifecycle(new URL(file, rootDirectoryUrl), {
67
- added: () => {
68
- onFileChange()
69
- },
70
- updated: () => {
71
- onFileChange()
72
- },
73
- removed: () => {
74
- onFileChange()
75
- },
76
- keepProcessAlive: false,
77
- })
78
- unregisters.push(unregister)
79
- })
24
+ let lookupPackageScope // defined in "init"
25
+ let readPackageJson // defined in "init"
80
26
 
81
27
  return {
82
28
  name: "jsenv:node_esm_resolution",
83
29
  appliesDuring: "*",
30
+ init: ({ rootDirectoryUrl, scenario, runtimeCompat, urlGraph }) => {
31
+ const nodeRuntimeEnabled = Object.keys(runtimeCompat).includes("node")
32
+ // https://nodejs.org/api/esm.html#resolver-algorithm-specification
33
+ packageConditions = packageConditions || [
34
+ ...readCustomConditionsFromProcessArgs(),
35
+ nodeRuntimeEnabled ? "node" : "browser",
36
+ "import",
37
+ ]
38
+
39
+ const packageScopesCache = new Map()
40
+ lookupPackageScope = (url) => {
41
+ const fromCache = packageScopesCache.get(url)
42
+ if (fromCache) {
43
+ return fromCache
44
+ }
45
+ const packageScope = defaultLookupPackageScope(url)
46
+ packageScopesCache.set(url, packageScope)
47
+ return packageScope
48
+ }
49
+ const packageJsonsCache = new Map()
50
+ readPackageJson = (url) => {
51
+ const fromCache = packageJsonsCache.get(url)
52
+ if (fromCache) {
53
+ return fromCache
54
+ }
55
+ const packageJson = defaultReadPackageJson(url)
56
+ packageJsonsCache.set(url, packageJson)
57
+ return packageJson
58
+ }
59
+
60
+ if (scenario === "dev") {
61
+ const onFileChange = () => {
62
+ packageScopesCache.clear()
63
+ packageJsonsCache.clear()
64
+ urlGraph.urlInfoMap.forEach((urlInfo) => {
65
+ if (urlInfo.dependsOnPackageJson) {
66
+ urlGraph.considerModified(urlInfo)
67
+ }
68
+ })
69
+ }
70
+ filesInvalidatingCache.forEach((file) => {
71
+ const unregister = registerFileLifecycle(
72
+ new URL(file, rootDirectoryUrl),
73
+ {
74
+ added: () => {
75
+ onFileChange()
76
+ },
77
+ updated: () => {
78
+ onFileChange()
79
+ },
80
+ removed: () => {
81
+ onFileChange()
82
+ },
83
+ keepProcessAlive: false,
84
+ },
85
+ )
86
+ unregisters.push(unregister)
87
+ })
88
+ }
89
+ },
84
90
  resolveUrl: {
85
- js_import_export: (reference) => {
91
+ js_import_export: (reference, context) => {
86
92
  const { parentUrl, specifier } = reference
87
93
  const { type, url } = applyNodeEsmResolution({
88
94
  conditions: packageConditions,
@@ -100,7 +106,9 @@ export const jsenvPluginNodeEsmResolution = ({
100
106
  type !== "relative_specifier" &&
101
107
  type !== "absolute_specifier" &&
102
108
  type !== "node_builtin_specifier"
103
- const relatedUrlInfos = urlGraph.getRelatedUrlInfos(reference.parentUrl)
109
+ const relatedUrlInfos = context.urlGraph.getRelatedUrlInfos(
110
+ reference.parentUrl,
111
+ )
104
112
  relatedUrlInfos.forEach((relatedUrlInfo) => {
105
113
  if (relatedUrlInfo.dependsOnPackageJson) {
106
114
  // the url may depend due to an other reference
@@ -1,44 +1,63 @@
1
1
  import { timeStart } from "@jsenv/server"
2
2
 
3
- export const createPluginController = ({
4
- plugins,
5
- scenario,
6
- hooks = [
7
- "init",
8
- "resolveUrl",
9
- "redirectUrl",
10
- "fetchUrlContent",
11
- "transformUrlContent",
12
- "transformUrlSearchParams",
13
- "formatUrl",
14
- "finalizeUrlContent",
15
- "cooked",
16
- "destroy",
17
- ],
18
- }) => {
19
- plugins = flattenAndFilterPlugins(plugins, { scenario })
20
- // precompute a list of hooks per hookName
21
- // For one major reason:
3
+ const HOOK_NAMES = [
4
+ "init",
5
+ "serve", // is called only during dev/tests
6
+ "resolveUrl",
7
+ "redirectUrl",
8
+ "fetchUrlContent",
9
+ "transformUrlContent",
10
+ "transformUrlSearchParams",
11
+ "formatUrl",
12
+ "finalizeUrlContent",
13
+ "bundle", // is called only during build
14
+ "optimizeUrlContent", // is called only during build
15
+ "cooked",
16
+ "augmentResponse", // is called only during dev/tests
17
+ "destroy",
18
+ ]
19
+
20
+ export const createPluginController = ({ plugins, scenario }) => {
21
+ const flatPlugins = flattenAndFilterPlugins(plugins, { scenario })
22
+ // precompute a list of hooks per hookName for one major reason:
22
23
  // - When debugging, there is less iteration
23
- // And also it should increase perf as there is less work to do
24
+ // also it should increase perf as there is less work to do
25
+
24
26
  const hookGroups = {}
25
- const addHook = (hookName) => {
26
- const hooks = []
27
- plugins.forEach((plugin) => {
28
- const hook = plugin[hookName]
29
- if (hook) {
30
- hooks.push({
27
+ const addPlugin = (plugin, { position = "start" }) => {
28
+ Object.keys(plugin).forEach((key) => {
29
+ if (key === "name" || key === "appliesDuring" || key === "serverEvents") {
30
+ return
31
+ }
32
+ const isHook = HOOK_NAMES.includes(key)
33
+ if (!isHook) {
34
+ console.warn(`Unexpected "${key}" property on "${plugin.name}" plugin`)
35
+ }
36
+ const hookName = key
37
+ const hookValue = plugin[hookName]
38
+ if (hookValue) {
39
+ const group = hookGroups[hookName] || (hookGroups[hookName] = [])
40
+ const hook = {
31
41
  plugin,
32
- hookName,
33
- value: hook,
34
- })
42
+ name: hookName,
43
+ value: hookValue,
44
+ }
45
+ if (position === "start") {
46
+ group.push(hook)
47
+ } else {
48
+ group.unshift(hook)
49
+ }
35
50
  }
36
51
  })
37
- hookGroups[hookName] = hooks
38
- return hooks
39
52
  }
40
- hooks.forEach((hookName) => {
41
- addHook(hookName)
53
+ const pushPlugin = (plugin) => {
54
+ addPlugin(plugin, { position: "start" })
55
+ }
56
+ const unshiftPlugin = (plugin) => {
57
+ addPlugin(plugin, { position: "end" })
58
+ }
59
+ flatPlugins.forEach((plugin) => {
60
+ pushPlugin(plugin)
42
61
  })
43
62
 
44
63
  let currentPlugin = null
@@ -49,15 +68,18 @@ export const createPluginController = ({
49
68
  return null
50
69
  }
51
70
  currentPlugin = hook.plugin
52
- currentHookName = hook.hookName
53
- const timeEnd = timeStart(
54
- `${currentHookName}-${currentPlugin.name.replace("jsenv:", "")}`,
55
- )
71
+ currentHookName = hook.name
72
+ let timeEnd
73
+ if (info.timing) {
74
+ timeEnd = timeStart(
75
+ `${currentHookName}-${currentPlugin.name.replace("jsenv:", "")}`,
76
+ )
77
+ }
56
78
  let valueReturned = hookFn(info, context)
57
79
  if (info.timing) {
58
80
  Object.assign(info.timing, timeEnd())
59
81
  }
60
- valueReturned = assertAndNormalizeReturnValue(hook.hookName, valueReturned)
82
+ valueReturned = assertAndNormalizeReturnValue(hook.name, valueReturned)
61
83
  currentPlugin = null
62
84
  currentHookName = null
63
85
  return valueReturned
@@ -68,15 +90,18 @@ export const createPluginController = ({
68
90
  return null
69
91
  }
70
92
  currentPlugin = hook.plugin
71
- currentHookName = hook.hookName
72
- const timeEnd = timeStart(
73
- `${currentHookName}-${currentPlugin.name.replace("jsenv:", "")}`,
74
- )
93
+ currentHookName = hook.name
94
+ let timeEnd
95
+ if (info.timing) {
96
+ timeEnd = timeStart(
97
+ `${currentHookName}-${currentPlugin.name.replace("jsenv:", "")}`,
98
+ )
99
+ }
75
100
  let valueReturned = await hookFn(info, context)
76
101
  if (info.timing) {
77
102
  Object.assign(info.timing, timeEnd())
78
103
  }
79
- valueReturned = assertAndNormalizeReturnValue(hook.hookName, valueReturned)
104
+ valueReturned = assertAndNormalizeReturnValue(hook.name, valueReturned)
80
105
  currentPlugin = null
81
106
  currentHookName = null
82
107
  return valueReturned
@@ -84,36 +109,45 @@ export const createPluginController = ({
84
109
 
85
110
  const callHooks = (hookName, info, context, callback) => {
86
111
  const hooks = hookGroups[hookName]
87
- for (const hook of hooks) {
88
- const returnValue = callHook(hook, info, context)
89
- if (returnValue) {
90
- callback(returnValue)
112
+ if (hooks) {
113
+ for (const hook of hooks) {
114
+ const returnValue = callHook(hook, info, context)
115
+ if (returnValue && callback) {
116
+ callback(returnValue)
117
+ }
91
118
  }
92
119
  }
93
120
  }
94
121
  const callAsyncHooks = async (hookName, info, context, callback) => {
95
122
  const hooks = hookGroups[hookName]
96
- await hooks.reduce(async (previous, hook) => {
97
- await previous
98
- const returnValue = await callAsyncHook(hook, info, context)
99
- if (returnValue && callback) {
100
- await callback(returnValue)
101
- }
102
- }, Promise.resolve())
123
+ if (hooks) {
124
+ await hooks.reduce(async (previous, hook) => {
125
+ await previous
126
+ const returnValue = await callAsyncHook(hook, info, context)
127
+ if (returnValue && callback) {
128
+ await callback(returnValue)
129
+ }
130
+ }, Promise.resolve())
131
+ }
103
132
  }
104
133
 
105
134
  const callHooksUntil = (hookName, info, context) => {
106
135
  const hooks = hookGroups[hookName]
107
- for (const hook of hooks) {
108
- const returnValue = callHook(hook, info, context)
109
- if (returnValue) {
110
- return returnValue
136
+ if (hooks) {
137
+ for (const hook of hooks) {
138
+ const returnValue = callHook(hook, info, context)
139
+ if (returnValue) {
140
+ return returnValue
141
+ }
111
142
  }
112
143
  }
113
144
  return null
114
145
  }
115
146
  const callAsyncHooksUntil = (hookName, info, context) => {
116
147
  const hooks = hookGroups[hookName]
148
+ if (!hooks) {
149
+ return null
150
+ }
117
151
  if (hooks.length === 0) {
118
152
  return null
119
153
  }
@@ -136,8 +170,9 @@ export const createPluginController = ({
136
170
  }
137
171
 
138
172
  return {
139
- plugins,
140
- addHook,
173
+ plugins: flatPlugins,
174
+ pushPlugin,
175
+ unshiftPlugin,
141
176
  getHookFunction,
142
177
  callHook,
143
178
  callAsyncHook,
@@ -152,8 +187,8 @@ export const createPluginController = ({
152
187
  }
153
188
  }
154
189
 
155
- const flattenAndFilterPlugins = (pluginsRaw, { scenario }) => {
156
- const plugins = []
190
+ const flattenAndFilterPlugins = (plugins, { scenario }) => {
191
+ const flatPlugins = []
157
192
  const visitPluginEntry = (pluginEntry) => {
158
193
  if (Array.isArray(pluginEntry)) {
159
194
  pluginEntry.forEach((value) => visitPluginEntry(value))
@@ -169,7 +204,7 @@ const flattenAndFilterPlugins = (pluginsRaw, { scenario }) => {
169
204
  return
170
205
  }
171
206
  if (appliesDuring === "*") {
172
- plugins.push(pluginEntry)
207
+ flatPlugins.push(pluginEntry)
173
208
  return
174
209
  }
175
210
  if (typeof appliesDuring === "string") {
@@ -179,7 +214,7 @@ const flattenAndFilterPlugins = (pluginsRaw, { scenario }) => {
179
214
  )
180
215
  }
181
216
  if (appliesDuring === scenario) {
182
- plugins.push(pluginEntry)
217
+ flatPlugins.push(pluginEntry)
183
218
  }
184
219
  return
185
220
  }
@@ -189,7 +224,7 @@ const flattenAndFilterPlugins = (pluginsRaw, { scenario }) => {
189
224
  )
190
225
  }
191
226
  if (appliesDuring[scenario]) {
192
- plugins.push(pluginEntry)
227
+ flatPlugins.push(pluginEntry)
193
228
  return
194
229
  }
195
230
  if (pluginEntry.destroy) {
@@ -199,8 +234,8 @@ const flattenAndFilterPlugins = (pluginsRaw, { scenario }) => {
199
234
  }
200
235
  throw new Error(`plugin must be objects, got ${pluginEntry}`)
201
236
  }
202
- pluginsRaw.forEach((plugin) => visitPluginEntry(plugin))
203
- return plugins
237
+ plugins.forEach((plugin) => visitPluginEntry(plugin))
238
+ return flatPlugins
204
239
  }
205
240
 
206
241
  const getHookFunction = (