@jsenv/core 31.0.0 → 31.1.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.
- package/dist/main.js +75 -24
- package/package.json +3 -2
- package/src/build/start_build_server.js +41 -18
- package/src/dev/file_service.js +2 -1
- package/src/dev/start_dev_server.js +3 -0
- package/src/kitchen/kitchen.js +11 -1
- package/src/plugins/global_scenarios/jsenv_plugin_global_scenarios.js +27 -0
- package/src/plugins/plugins.js +2 -0
- package/src/test/logs_file_execution.js +1 -1
package/dist/main.js
CHANGED
|
@@ -20,6 +20,7 @@ import { parseHtmlString, stringifyHtmlAst, getHtmlNodeAttribute, visitHtmlNodes
|
|
|
20
20
|
import { createRequire } from "node:module";
|
|
21
21
|
import babelParser from "@babel/parser";
|
|
22
22
|
import { bundleJsModules } from "@jsenv/plugin-bundling";
|
|
23
|
+
import { replacePlaceholders } from "@jsenv/plugin-placeholders";
|
|
23
24
|
import v8, { takeCoverage } from "node:v8";
|
|
24
25
|
import wrapAnsi from "wrap-ansi";
|
|
25
26
|
import stripAnsi from "strip-ansi";
|
|
@@ -9225,7 +9226,13 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
|
|
|
9225
9226
|
} = urlInfo;
|
|
9226
9227
|
if (generatedUrl && generatedUrl.startsWith("file:")) {
|
|
9227
9228
|
if (urlInfo.type === "directory") ; else if (urlInfo.content === null) ; else {
|
|
9228
|
-
|
|
9229
|
+
let contentIsInlined = urlInfo.isInline;
|
|
9230
|
+
if (contentIsInlined && context.supervisor && urlGraph.getUrlInfo(urlInfo.inlineUrlSite.url).type === "html") {
|
|
9231
|
+
contentIsInlined = false;
|
|
9232
|
+
}
|
|
9233
|
+
if (!contentIsInlined) {
|
|
9234
|
+
writeFileSync(new URL(generatedUrl), urlInfo.content);
|
|
9235
|
+
}
|
|
9229
9236
|
const {
|
|
9230
9237
|
sourcemapGeneratedUrl,
|
|
9231
9238
|
sourcemap
|
|
@@ -17971,6 +17978,30 @@ const babelPluginMetadataImportMetaScenarios = () => {
|
|
|
17971
17978
|
};
|
|
17972
17979
|
};
|
|
17973
17980
|
|
|
17981
|
+
/*
|
|
17982
|
+
* Source code can contain the following
|
|
17983
|
+
* - __dev__
|
|
17984
|
+
* - __build__
|
|
17985
|
+
* A global will be injected with true/false when needed
|
|
17986
|
+
*/
|
|
17987
|
+
const jsenvPluginGlobalScenarios = () => {
|
|
17988
|
+
const transformIfNeeded = (urlInfo, context) => {
|
|
17989
|
+
return replacePlaceholders(urlInfo, {
|
|
17990
|
+
false: context.dev,
|
|
17991
|
+
true: context.build
|
|
17992
|
+
});
|
|
17993
|
+
};
|
|
17994
|
+
return {
|
|
17995
|
+
name: "jsenv:global_scenario",
|
|
17996
|
+
appliesDuring: "*",
|
|
17997
|
+
transformUrlContent: {
|
|
17998
|
+
js_classic: transformIfNeeded,
|
|
17999
|
+
js_module: transformIfNeeded,
|
|
18000
|
+
html: transformIfNeeded
|
|
18001
|
+
}
|
|
18002
|
+
};
|
|
18003
|
+
};
|
|
18004
|
+
|
|
17974
18005
|
const jsenvPluginCssTranspilation = () => {
|
|
17975
18006
|
return {
|
|
17976
18007
|
name: "jsenv:css_transpilation",
|
|
@@ -20303,7 +20334,7 @@ const getCorePlugins = ({
|
|
|
20303
20334
|
runtimeCompat,
|
|
20304
20335
|
clientMainFileUrl,
|
|
20305
20336
|
urlResolution
|
|
20306
|
-
}), jsenvPluginUrlVersion(), jsenvPluginCommonJsGlobals(), jsenvPluginImportMetaScenarios(), jsenvPluginNodeRuntime({
|
|
20337
|
+
}), jsenvPluginUrlVersion(), jsenvPluginCommonJsGlobals(), jsenvPluginImportMetaScenarios(), jsenvPluginGlobalScenarios(), jsenvPluginNodeRuntime({
|
|
20307
20338
|
runtimeCompat
|
|
20308
20339
|
}), jsenvPluginImportMetaHot(), ...(clientAutoreload ? [jsenvPluginAutoreload({
|
|
20309
20340
|
...clientAutoreload,
|
|
@@ -22256,6 +22287,7 @@ const createFileService = ({
|
|
|
22256
22287
|
logLevel,
|
|
22257
22288
|
serverStopCallbacks,
|
|
22258
22289
|
serverEventsDispatcher,
|
|
22290
|
+
contextCache,
|
|
22259
22291
|
rootDirectoryUrl,
|
|
22260
22292
|
runtimeCompat,
|
|
22261
22293
|
plugins,
|
|
@@ -22319,7 +22351,6 @@ const createFileService = ({
|
|
|
22319
22351
|
}
|
|
22320
22352
|
});
|
|
22321
22353
|
serverStopCallbacks.push(stopWatchingClientFiles);
|
|
22322
|
-
const contextCache = new Map();
|
|
22323
22354
|
const getOrCreateContext = request => {
|
|
22324
22355
|
const {
|
|
22325
22356
|
runtimeName,
|
|
@@ -22380,6 +22411,7 @@ const createFileService = ({
|
|
|
22380
22411
|
cacheControl,
|
|
22381
22412
|
ribbon
|
|
22382
22413
|
})],
|
|
22414
|
+
supervisor,
|
|
22383
22415
|
minification: false,
|
|
22384
22416
|
sourcemaps,
|
|
22385
22417
|
sourcemapsSourcesProtocol,
|
|
@@ -22849,6 +22881,7 @@ const startDevServer = async ({
|
|
|
22849
22881
|
serverStopCallbacks.push(() => {
|
|
22850
22882
|
serverEventsDispatcher.destroy();
|
|
22851
22883
|
});
|
|
22884
|
+
const contextCache = new Map();
|
|
22852
22885
|
const server = await startServer({
|
|
22853
22886
|
signal,
|
|
22854
22887
|
stopOnExit: false,
|
|
@@ -22877,6 +22910,7 @@ const startDevServer = async ({
|
|
|
22877
22910
|
logLevel,
|
|
22878
22911
|
serverStopCallbacks,
|
|
22879
22912
|
serverEventsDispatcher,
|
|
22913
|
+
contextCache,
|
|
22880
22914
|
rootDirectoryUrl,
|
|
22881
22915
|
runtimeCompat,
|
|
22882
22916
|
plugins,
|
|
@@ -22967,7 +23001,8 @@ const startDevServer = async ({
|
|
|
22967
23001
|
origin: server.origin,
|
|
22968
23002
|
stop: () => {
|
|
22969
23003
|
server.stop();
|
|
22970
|
-
}
|
|
23004
|
+
},
|
|
23005
|
+
contextCache
|
|
22971
23006
|
};
|
|
22972
23007
|
};
|
|
22973
23008
|
|
|
@@ -23965,7 +24000,7 @@ const descriptionFormatters = {
|
|
|
23965
24000
|
}) => {
|
|
23966
24001
|
return ANSI.color(`${UNICODE.FAILURE_RAW} execution ${index + 1} of ${total} timeout after ${executionParams.allocatedMs}ms`, EXECUTION_COLORS.timedout);
|
|
23967
24002
|
},
|
|
23968
|
-
|
|
24003
|
+
failed: ({
|
|
23969
24004
|
index,
|
|
23970
24005
|
total
|
|
23971
24006
|
}) => {
|
|
@@ -26153,30 +26188,46 @@ const startBuildServer = async ({
|
|
|
26153
26188
|
},
|
|
26154
26189
|
buildServerAutoreload = false,
|
|
26155
26190
|
buildServerMainFile = getCallerPosition().url,
|
|
26156
|
-
cooldownBetweenFileEvents
|
|
26191
|
+
cooldownBetweenFileEvents,
|
|
26192
|
+
...rest
|
|
26157
26193
|
}) => {
|
|
26158
|
-
|
|
26159
|
-
|
|
26160
|
-
|
|
26161
|
-
|
|
26162
|
-
|
|
26163
|
-
if (buildIndexPath) {
|
|
26164
|
-
if (typeof buildIndexPath !== "string") {
|
|
26165
|
-
throw new TypeError(`buildIndexPath must be a string, got ${buildIndexPath}`);
|
|
26194
|
+
// params validation
|
|
26195
|
+
{
|
|
26196
|
+
const unexpectedParamNames = Object.keys(rest);
|
|
26197
|
+
if (unexpectedParamNames.length > 0) {
|
|
26198
|
+
throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
|
|
26166
26199
|
}
|
|
26167
|
-
|
|
26168
|
-
|
|
26169
|
-
|
|
26170
|
-
const buildIndexUrl = new URL(buildIndexPath, buildDirectoryUrl).href;
|
|
26171
|
-
if (!buildIndexUrl.startsWith(buildDirectoryUrl)) {
|
|
26172
|
-
throw new Error(`buildIndexPath must be relative, got ${buildIndexPath}`);
|
|
26173
|
-
}
|
|
26174
|
-
buildIndexPath = buildIndexUrl.slice(buildDirectoryUrl.length);
|
|
26200
|
+
const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl);
|
|
26201
|
+
if (!rootDirectoryUrlValidation.valid) {
|
|
26202
|
+
throw new TypeError(`rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`);
|
|
26175
26203
|
}
|
|
26176
|
-
|
|
26177
|
-
|
|
26204
|
+
rootDirectoryUrl = rootDirectoryUrlValidation.value;
|
|
26205
|
+
const buildDirectoryUrlValidation = validateDirectoryUrl(buildDirectoryUrl);
|
|
26206
|
+
if (!buildDirectoryUrlValidation.valid) {
|
|
26207
|
+
throw new TypeError(`buildDirectoryUrl ${buildDirectoryUrlValidation.message}, got ${buildDirectoryUrlValidation}`);
|
|
26208
|
+
}
|
|
26209
|
+
buildDirectoryUrl = buildDirectoryUrlValidation.value;
|
|
26210
|
+
if (buildIndexPath) {
|
|
26211
|
+
if (typeof buildIndexPath !== "string") {
|
|
26212
|
+
throw new TypeError(`buildIndexPath must be a string, got ${buildIndexPath}`);
|
|
26213
|
+
}
|
|
26214
|
+
if (buildIndexPath[0] === "/") {
|
|
26215
|
+
buildIndexPath = buildIndexPath.slice(1);
|
|
26216
|
+
} else {
|
|
26217
|
+
const buildIndexUrl = new URL(buildIndexPath, buildDirectoryUrl).href;
|
|
26218
|
+
if (!buildIndexUrl.startsWith(buildDirectoryUrl)) {
|
|
26219
|
+
throw new Error(`buildIndexPath must be relative, got ${buildIndexPath}`);
|
|
26220
|
+
}
|
|
26221
|
+
buildIndexPath = buildIndexUrl.slice(buildDirectoryUrl.length);
|
|
26222
|
+
}
|
|
26223
|
+
if (!existsSync(new URL(buildIndexPath, buildDirectoryUrl))) {
|
|
26224
|
+
buildIndexPath = null;
|
|
26225
|
+
}
|
|
26178
26226
|
}
|
|
26179
26227
|
}
|
|
26228
|
+
const logger = createLogger({
|
|
26229
|
+
logLevel
|
|
26230
|
+
});
|
|
26180
26231
|
const operation = Abort.startOperation();
|
|
26181
26232
|
operation.addAbortSignal(signal);
|
|
26182
26233
|
if (handleSIGINT) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "31.
|
|
3
|
+
"version": "31.1.0",
|
|
4
4
|
"description": "Tool to develop, test and build js projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"@jsenv/log": "3.3.4",
|
|
76
76
|
"@jsenv/node-esm-resolution": "1.0.1",
|
|
77
77
|
"@jsenv/plugin-bundling": "2.0.0",
|
|
78
|
+
"@jsenv/plugin-globals": "1.1.0",
|
|
78
79
|
"@jsenv/server": "15.0.0",
|
|
79
80
|
"@jsenv/sourcemap": "1.0.9",
|
|
80
81
|
"@jsenv/uneval": "1.6.0",
|
|
@@ -113,6 +114,6 @@
|
|
|
113
114
|
"eslint-plugin-import": "2.27.5",
|
|
114
115
|
"eslint-plugin-react": "7.32.2",
|
|
115
116
|
"playwright": "1.31.2",
|
|
116
|
-
"prettier": "2.8.
|
|
117
|
+
"prettier": "2.8.6"
|
|
117
118
|
}
|
|
118
119
|
}
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
jsenvServiceErrorHandler,
|
|
24
24
|
} from "@jsenv/server"
|
|
25
25
|
import {
|
|
26
|
-
|
|
26
|
+
validateDirectoryUrl,
|
|
27
27
|
registerDirectoryLifecycle,
|
|
28
28
|
} from "@jsenv/filesystem"
|
|
29
29
|
import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
|
|
@@ -62,32 +62,55 @@ export const startBuildServer = async ({
|
|
|
62
62
|
buildServerAutoreload = false,
|
|
63
63
|
buildServerMainFile = getCallerPosition().url,
|
|
64
64
|
cooldownBetweenFileEvents,
|
|
65
|
+
...rest
|
|
65
66
|
}) => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
// params validation
|
|
68
|
+
{
|
|
69
|
+
const unexpectedParamNames = Object.keys(rest)
|
|
70
|
+
if (unexpectedParamNames.length > 0) {
|
|
71
|
+
throw new TypeError(
|
|
72
|
+
`${unexpectedParamNames.join(",")}: there is no such param`,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl)
|
|
76
|
+
if (!rootDirectoryUrlValidation.valid) {
|
|
77
|
+
throw new TypeError(
|
|
78
|
+
`rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`,
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
rootDirectoryUrl = rootDirectoryUrlValidation.value
|
|
82
|
+
const buildDirectoryUrlValidation = validateDirectoryUrl(buildDirectoryUrl)
|
|
83
|
+
if (!buildDirectoryUrlValidation.valid) {
|
|
71
84
|
throw new TypeError(
|
|
72
|
-
`
|
|
85
|
+
`buildDirectoryUrl ${buildDirectoryUrlValidation.message}, got ${buildDirectoryUrlValidation}`,
|
|
73
86
|
)
|
|
74
87
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
`buildIndexPath must be relative, got ${buildIndexPath}`,
|
|
88
|
+
buildDirectoryUrl = buildDirectoryUrlValidation.value
|
|
89
|
+
|
|
90
|
+
if (buildIndexPath) {
|
|
91
|
+
if (typeof buildIndexPath !== "string") {
|
|
92
|
+
throw new TypeError(
|
|
93
|
+
`buildIndexPath must be a string, got ${buildIndexPath}`,
|
|
82
94
|
)
|
|
83
95
|
}
|
|
84
|
-
buildIndexPath
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
96
|
+
if (buildIndexPath[0] === "/") {
|
|
97
|
+
buildIndexPath = buildIndexPath.slice(1)
|
|
98
|
+
} else {
|
|
99
|
+
const buildIndexUrl = new URL(buildIndexPath, buildDirectoryUrl).href
|
|
100
|
+
if (!buildIndexUrl.startsWith(buildDirectoryUrl)) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`buildIndexPath must be relative, got ${buildIndexPath}`,
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
buildIndexPath = buildIndexUrl.slice(buildDirectoryUrl.length)
|
|
106
|
+
}
|
|
107
|
+
if (!existsSync(new URL(buildIndexPath, buildDirectoryUrl))) {
|
|
108
|
+
buildIndexPath = null
|
|
109
|
+
}
|
|
88
110
|
}
|
|
89
111
|
}
|
|
90
112
|
|
|
113
|
+
const logger = createLogger({ logLevel })
|
|
91
114
|
const operation = Abort.startOperation()
|
|
92
115
|
operation.addAbortSignal(signal)
|
|
93
116
|
if (handleSIGINT) {
|
package/src/dev/file_service.js
CHANGED
|
@@ -20,6 +20,7 @@ export const createFileService = ({
|
|
|
20
20
|
logLevel,
|
|
21
21
|
serverStopCallbacks,
|
|
22
22
|
serverEventsDispatcher,
|
|
23
|
+
contextCache,
|
|
23
24
|
|
|
24
25
|
rootDirectoryUrl,
|
|
25
26
|
runtimeCompat,
|
|
@@ -82,7 +83,6 @@ export const createFileService = ({
|
|
|
82
83
|
})
|
|
83
84
|
serverStopCallbacks.push(stopWatchingClientFiles)
|
|
84
85
|
|
|
85
|
-
const contextCache = new Map()
|
|
86
86
|
const getOrCreateContext = (request) => {
|
|
87
87
|
const { runtimeName, runtimeVersion } = parseUserAgentHeader(
|
|
88
88
|
request.headers["user-agent"],
|
|
@@ -150,6 +150,7 @@ export const createFileService = ({
|
|
|
150
150
|
ribbon,
|
|
151
151
|
}),
|
|
152
152
|
],
|
|
153
|
+
supervisor,
|
|
153
154
|
minification: false,
|
|
154
155
|
sourcemaps,
|
|
155
156
|
sourcemapsSourcesProtocol,
|
|
@@ -173,6 +173,7 @@ export const startDevServer = async ({
|
|
|
173
173
|
serverStopCallbacks.push(() => {
|
|
174
174
|
serverEventsDispatcher.destroy()
|
|
175
175
|
})
|
|
176
|
+
const contextCache = new Map()
|
|
176
177
|
const server = await startServer({
|
|
177
178
|
signal,
|
|
178
179
|
stopOnExit: false,
|
|
@@ -208,6 +209,7 @@ export const startDevServer = async ({
|
|
|
208
209
|
logLevel,
|
|
209
210
|
serverStopCallbacks,
|
|
210
211
|
serverEventsDispatcher,
|
|
212
|
+
contextCache,
|
|
211
213
|
|
|
212
214
|
rootDirectoryUrl,
|
|
213
215
|
runtimeCompat,
|
|
@@ -304,5 +306,6 @@ export const startDevServer = async ({
|
|
|
304
306
|
stop: () => {
|
|
305
307
|
server.stop()
|
|
306
308
|
},
|
|
309
|
+
contextCache,
|
|
307
310
|
}
|
|
308
311
|
}
|
package/src/kitchen/kitchen.js
CHANGED
|
@@ -673,7 +673,17 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
|
|
|
673
673
|
// (error hapenning before urlInfo.content can be set, or 404 for instance)
|
|
674
674
|
// in that case we can't write anything
|
|
675
675
|
} else {
|
|
676
|
-
|
|
676
|
+
let contentIsInlined = urlInfo.isInline
|
|
677
|
+
if (
|
|
678
|
+
contentIsInlined &&
|
|
679
|
+
context.supervisor &&
|
|
680
|
+
urlGraph.getUrlInfo(urlInfo.inlineUrlSite.url).type === "html"
|
|
681
|
+
) {
|
|
682
|
+
contentIsInlined = false
|
|
683
|
+
}
|
|
684
|
+
if (!contentIsInlined) {
|
|
685
|
+
writeFileSync(new URL(generatedUrl), urlInfo.content)
|
|
686
|
+
}
|
|
677
687
|
const { sourcemapGeneratedUrl, sourcemap } = urlInfo
|
|
678
688
|
if (sourcemapGeneratedUrl && sourcemap) {
|
|
679
689
|
writeFileSync(
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Source code can contain the following
|
|
3
|
+
* - __dev__
|
|
4
|
+
* - __build__
|
|
5
|
+
* A global will be injected with true/false when needed
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { replacePlaceholders } from "@jsenv/plugin-placeholders"
|
|
9
|
+
|
|
10
|
+
export const jsenvPluginGlobalScenarios = () => {
|
|
11
|
+
const transformIfNeeded = (urlInfo, context) => {
|
|
12
|
+
return replacePlaceholders(urlInfo, {
|
|
13
|
+
__DEV__: context.dev,
|
|
14
|
+
__BUILD__: context.build,
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
name: "jsenv:global_scenario",
|
|
20
|
+
appliesDuring: "*",
|
|
21
|
+
transformUrlContent: {
|
|
22
|
+
js_classic: transformIfNeeded,
|
|
23
|
+
js_module: transformIfNeeded,
|
|
24
|
+
html: transformIfNeeded,
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/plugins/plugins.js
CHANGED
|
@@ -8,6 +8,7 @@ import { jsenvPluginInline } from "./inline/jsenv_plugin_inline.js"
|
|
|
8
8
|
import { jsenvPluginSupervisor } from "./supervisor/jsenv_plugin_supervisor.js"
|
|
9
9
|
import { jsenvPluginCommonJsGlobals } from "./commonjs_globals/jsenv_plugin_commonjs_globals.js"
|
|
10
10
|
import { jsenvPluginImportMetaScenarios } from "./import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js"
|
|
11
|
+
import { jsenvPluginGlobalScenarios } from "./global_scenarios/jsenv_plugin_global_scenarios.js"
|
|
11
12
|
import { jsenvPluginTranspilation } from "./transpilation/jsenv_plugin_transpilation.js"
|
|
12
13
|
import { jsenvPluginNodeRuntime } from "./node_runtime/jsenv_plugin_node_runtime.js"
|
|
13
14
|
// autoreload
|
|
@@ -89,6 +90,7 @@ export const getCorePlugins = ({
|
|
|
89
90
|
jsenvPluginUrlVersion(),
|
|
90
91
|
jsenvPluginCommonJsGlobals(),
|
|
91
92
|
jsenvPluginImportMetaScenarios(),
|
|
93
|
+
jsenvPluginGlobalScenarios(),
|
|
92
94
|
|
|
93
95
|
jsenvPluginNodeRuntime({ runtimeCompat }),
|
|
94
96
|
|
|
@@ -221,7 +221,7 @@ const descriptionFormatters = {
|
|
|
221
221
|
EXECUTION_COLORS.timedout,
|
|
222
222
|
)
|
|
223
223
|
},
|
|
224
|
-
|
|
224
|
+
failed: ({ index, total }) => {
|
|
225
225
|
return ANSI.color(
|
|
226
226
|
`${UNICODE.FAILURE_RAW} execution ${index + 1} of ${total} failed`,
|
|
227
227
|
EXECUTION_COLORS.failed,
|