@jsenv/core 23.3.0 → 23.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 (36) hide show
  1. package/package.json +5 -5
  2. package/readme.md +4 -4
  3. package/src/execute.js +0 -6
  4. package/src/executeTestPlan.js +12 -10
  5. package/src/internal/browser-launcher/executeHtmlFile.js +20 -16
  6. package/src/internal/compiling/createCompiledFileService.js +13 -1
  7. package/src/internal/executing/coverage/babel_plugin_instrument.js +1 -0
  8. package/src/internal/executing/coverage/reportToCoverage.js +160 -119
  9. package/src/internal/executing/{coverage → coverage_missing}/createEmptyCoverage.js +0 -0
  10. package/src/internal/executing/coverage_missing/list_files_not_covered.js +20 -0
  11. package/src/internal/executing/coverage_missing/missing_coverage.js +46 -0
  12. package/src/internal/executing/{coverage → coverage_missing}/relativeUrlToEmptyCoverage.js +12 -8
  13. package/src/internal/executing/{coverage/generateCoverageHtmlDirectory.js → coverage_reporter/coverage_reporter_html_directory.js} +11 -4
  14. package/src/internal/executing/{coverage/generateCoverageJsonFile.js → coverage_reporter/coverage_reporter_json_file.js} +0 -0
  15. package/src/internal/executing/{coverage/generateCoverageTextLog.js → coverage_reporter/coverage_reporter_text_log.js} +4 -2
  16. package/src/internal/executing/{coverage → coverage_reporter}/istanbulCoverageMapFromCoverage.js +0 -0
  17. package/src/internal/executing/{coverage/normalizeIstanbulCoverage.js → coverage_utils/file_by_file_coverage.js} +9 -7
  18. package/src/internal/executing/coverage_utils/istanbul_coverage_composition.js +28 -0
  19. package/src/internal/executing/coverage_utils/v8_and_istanbul.js +38 -0
  20. package/src/internal/executing/coverage_utils/v8_coverage_composition.js +23 -0
  21. package/src/internal/executing/coverage_utils/v8_coverage_from_directory.js +77 -0
  22. package/src/internal/executing/{coverage/istanbulCoverageFromV8Coverage.js → coverage_utils/v8_coverage_to_istanbul.js} +38 -17
  23. package/src/internal/executing/createSummaryLog.js +5 -9
  24. package/src/internal/executing/executeConcurrently.js +199 -115
  25. package/src/internal/executing/executePlan.js +8 -5
  26. package/src/internal/executing/executionLogs.js +66 -37
  27. package/src/internal/executing/execution_colors.js +2 -1
  28. package/src/internal/executing/launchAndExecute.js +63 -58
  29. package/src/internal/logs/msAsDuration.js +4 -3
  30. package/src/launchBrowser.js +8 -8
  31. package/src/launchNode.js +5 -93
  32. package/src/internal/executing/coverage/composeIstanbulCoverages.js +0 -108
  33. package/src/internal/executing/coverage/composeV8Coverages.js +0 -20
  34. package/src/internal/executing/coverage/istanbulCoverageFromCoverages.js +0 -43
  35. package/src/internal/executing/coverage/v8CoverageFromAllV8Coverages.js +0 -40
  36. package/src/internal/executing/coverage/v8CoverageFromNodeV8Directory.js +0 -67
@@ -1,12 +1,21 @@
1
- import { stat } from "node:fs"
1
+ import { existsSync } from "node:fs"
2
2
  import wrapAnsi from "wrap-ansi"
3
- import { loggerToLevels, createDetailedMessage } from "@jsenv/logger"
4
- import { urlToFileSystemPath } from "@jsenv/filesystem"
5
- import { createLog } from "@jsenv/log"
3
+ import cuid from "cuid"
4
+ import { loggerToLevels } from "@jsenv/logger"
5
+ import { createLog, startSpinner } from "@jsenv/log"
6
+ import {
7
+ urlToFileSystemPath,
8
+ resolveUrl,
9
+ writeDirectory,
10
+ ensureEmptyDirectory,
11
+ normalizeStructuredMetaMap,
12
+ urlToMeta,
13
+ } from "@jsenv/filesystem"
14
+ import { Abort } from "@jsenv/abort"
6
15
 
7
16
  import { launchAndExecute } from "../executing/launchAndExecute.js"
8
17
  import { reportToCoverage } from "./coverage/reportToCoverage.js"
9
- import { createExecutionResultLog } from "./executionLogs.js"
18
+ import { formatExecuting, formatExecutionResult } from "./executionLogs.js"
10
19
  import { createSummaryLog } from "./createSummaryLog.js"
11
20
 
12
21
  export const executeConcurrently = async (
@@ -24,39 +33,107 @@ export const executeConcurrently = async (
24
33
  babelPluginMap,
25
34
 
26
35
  defaultMsAllocatedPerExecution = 30000,
36
+ cooldownBetweenExecutions = 0,
27
37
  maxExecutionsInParallel = 1,
28
38
  completedExecutionLogMerging,
29
39
  completedExecutionLogAbbreviation,
30
- measureGlobalDuration = true,
31
40
 
32
41
  coverage,
33
42
  coverageConfig,
34
43
  coverageIncludeMissing,
35
44
  coverageForceIstanbul,
36
- coverageV8MergeConflictIsExpected,
45
+ coverageV8ConflictWarning,
46
+ coverageTempDirectoryRelativeUrl,
47
+ runtimeSupport,
37
48
 
38
- mainFileNotFoundCallback = ({ fileRelativeUrl }) => {
39
- logger.error(
40
- new Error(
41
- createDetailedMessage(`an execution main file does not exists.`, {
42
- ["file relative path"]: fileRelativeUrl,
43
- }),
44
- ),
45
- )
46
- },
47
49
  beforeExecutionCallback = () => {},
48
50
  afterExecutionCallback = () => {},
49
51
 
50
52
  logSummary,
51
53
  },
52
54
  ) => {
55
+ if (completedExecutionLogMerging && !process.stdout.isTTY) {
56
+ completedExecutionLogMerging = false
57
+ logger.debug(
58
+ `Force completedExecutionLogMerging to false because process.stdout.isTTY is false`,
59
+ )
60
+ }
61
+ const executionLogsEnabled = loggerToLevels(logger).info
62
+ const executionSpinner = executionLogsEnabled && process.stdout.isTTY
63
+
53
64
  const startMs = Date.now()
54
65
 
55
66
  const report = {}
56
67
  const executionCount = executionSteps.length
57
68
 
58
- let previousExecutionResult
59
- let previousExecutionLog
69
+ let transformReturnValue = (value) => value
70
+
71
+ const coverageTempDirectoryUrl = resolveUrl(
72
+ coverageTempDirectoryRelativeUrl,
73
+ projectDirectoryUrl,
74
+ )
75
+
76
+ const structuredMetaMapForCover = normalizeStructuredMetaMap(
77
+ {
78
+ cover: coverageConfig,
79
+ },
80
+ projectDirectoryUrl,
81
+ )
82
+ const coverageIgnorePredicate = (url) => {
83
+ return !urlToMeta({
84
+ url: resolveUrl(url, projectDirectoryUrl),
85
+ structuredMetaMap: structuredMetaMapForCover,
86
+ }).cover
87
+ }
88
+
89
+ if (coverage) {
90
+ // in case runned multiple times, we don't want to keep writing lot of files in this directory
91
+ if (!process.env.NODE_V8_COVERAGE) {
92
+ await ensureEmptyDirectory(coverageTempDirectoryUrl)
93
+ }
94
+
95
+ if (runtimeSupport.node) {
96
+ // v8 coverage is written in a directoy and auto propagate to subprocesses
97
+ // through process.env.NODE_V8_COVERAGE.
98
+ if (!coverageForceIstanbul && !process.env.NODE_V8_COVERAGE) {
99
+ const v8CoverageDirectory = resolveUrl(
100
+ `./node_v8/${cuid()}`,
101
+ coverageTempDirectoryUrl,
102
+ )
103
+ await writeDirectory(v8CoverageDirectory, { allowUseless: true })
104
+ process.env.NODE_V8_COVERAGE = urlToFileSystemPath(v8CoverageDirectory)
105
+ }
106
+ }
107
+
108
+ transformReturnValue = async (value) => {
109
+ if (multipleExecutionsOperation.signal.aborted) {
110
+ // don't try to do the coverage stuff
111
+ return value
112
+ }
113
+
114
+ try {
115
+ value.coverage = await reportToCoverage(value.report, {
116
+ signal: multipleExecutionsOperation.signal,
117
+ logger,
118
+ projectDirectoryUrl,
119
+ babelPluginMap,
120
+ coverageConfig,
121
+ coverageIncludeMissing,
122
+ coverageForceIstanbul,
123
+ coverageIgnorePredicate,
124
+ coverageV8ConflictWarning,
125
+ })
126
+ } catch (e) {
127
+ if (Abort.isAbortError(e)) {
128
+ return value
129
+ }
130
+ throw e
131
+ }
132
+ return value
133
+ }
134
+ }
135
+
136
+ let executionLog = createLog({ newLine: "around" })
60
137
  let abortedCount = 0
61
138
  let timedoutCount = 0
62
139
  let erroredCount = 0
@@ -64,18 +141,19 @@ export const executeConcurrently = async (
64
141
  const executionsDone = await executeInParallel({
65
142
  multipleExecutionsOperation,
66
143
  maxExecutionsInParallel,
144
+ cooldownBetweenExecutions,
67
145
  executionSteps,
68
146
  start: async (paramsFromStep) => {
69
147
  const executionIndex = executionSteps.indexOf(paramsFromStep)
70
- const { executionName, fileRelativeUrl } = paramsFromStep
148
+ const { executionName, fileRelativeUrl, runtime } = paramsFromStep
149
+ const runtimeName = runtime.name
150
+ const runtimeVersion = runtime.version
151
+
71
152
  const executionParams = {
72
153
  // the params below can be overriden by executionDefaultParams
73
154
  measurePerformance: false,
74
155
  collectPerformance: false,
75
- measureDuration: true,
76
156
  captureConsole: true,
77
- collectRuntimeName: true,
78
- collectRuntimeVersion: true,
79
157
  // stopAfterExecute: true to ensure runtime is stopped once executed
80
158
  // because we have what we wants: execution is completed and
81
159
  // we have associated coverage and capturedConsole
@@ -85,6 +163,7 @@ export const executeConcurrently = async (
85
163
  stopAfterExecuteReason: "execution-done",
86
164
  allocatedMs: defaultMsAllocatedPerExecution,
87
165
  ...paramsFromStep,
166
+ runtime,
88
167
  // mirrorConsole: false because file will be executed in parallel
89
168
  // so log would be a mess to read
90
169
  mirrorConsole: false,
@@ -92,47 +171,69 @@ export const executeConcurrently = async (
92
171
 
93
172
  const beforeExecutionInfo = {
94
173
  fileRelativeUrl,
174
+ runtimeName,
175
+ runtimeVersion,
95
176
  executionIndex,
96
177
  executionParams,
97
178
  }
98
179
 
99
- const filePath = urlToFileSystemPath(
100
- `${projectDirectoryUrl}${fileRelativeUrl}`,
101
- )
102
- const fileExists = await pathLeadsToFile(filePath)
103
- if (!fileExists) {
104
- mainFileNotFoundCallback(beforeExecutionInfo)
105
- return
180
+ let spinner
181
+ if (executionSpinner) {
182
+ spinner = startSpinner({
183
+ log: executionLog,
184
+ text: formatExecuting(beforeExecutionInfo, {
185
+ executionCount,
186
+ abortedCount,
187
+ timedoutCount,
188
+ erroredCount,
189
+ completedCount,
190
+ }),
191
+ })
106
192
  }
107
-
108
193
  beforeExecutionCallback(beforeExecutionInfo)
109
194
 
110
- // launchAndExecute peut retourner un aborted
111
- // et c'est bien, on veut le gérer, si tous les suivants sont aborted
112
- // on le gere en dehors de cette boucle
113
- const executionResult = await launchAndExecute({
114
- signal: multipleExecutionsOperation.signal,
115
- launchAndExecuteLogLevel,
195
+ const filePath = urlToFileSystemPath(
196
+ `${projectDirectoryUrl}${fileRelativeUrl}`,
197
+ )
198
+ let executionResult
199
+ if (existsSync(filePath)) {
200
+ executionResult = await launchAndExecute({
201
+ signal: multipleExecutionsOperation.signal,
202
+ launchAndExecuteLogLevel,
116
203
 
117
- ...executionParams,
118
- collectCoverage: coverage,
119
- runtimeParams: {
120
- projectDirectoryUrl,
121
- compileServerOrigin,
122
- outDirectoryRelativeUrl,
204
+ ...executionParams,
123
205
  collectCoverage: coverage,
124
- coverageConfig,
125
- coverageForceIstanbul,
126
- ...executionParams.runtimeParams,
127
- },
128
- executeParams: {
129
- fileRelativeUrl,
130
- ...executionParams.executeParams,
131
- },
132
- coverageV8MergeConflictIsExpected,
133
- })
206
+ coverageTempDirectoryUrl,
207
+ runtimeParams: {
208
+ projectDirectoryUrl,
209
+ compileServerOrigin,
210
+ outDirectoryRelativeUrl,
211
+ collectCoverage: coverage,
212
+ coverageIgnorePredicate,
213
+ coverageForceIstanbul,
214
+ ...executionParams.runtimeParams,
215
+ },
216
+ executeParams: {
217
+ fileRelativeUrl,
218
+ ...executionParams.executeParams,
219
+ },
220
+ coverageV8ConflictWarning,
221
+ })
222
+ } else {
223
+ executionResult = {
224
+ status: "errored",
225
+ error: new Error(
226
+ `No file at ${fileRelativeUrl} for execution "${executionName}"`,
227
+ ),
228
+ }
229
+ }
230
+ if (fileRelativeUrl in report === false) {
231
+ report[fileRelativeUrl] = {}
232
+ }
233
+ report[fileRelativeUrl][executionName] = executionResult
134
234
  const afterExecutionInfo = {
135
235
  ...beforeExecutionInfo,
236
+ endMs: Date.now(),
136
237
  executionResult,
137
238
  }
138
239
  afterExecutionCallback(afterExecutionInfo)
@@ -147,8 +248,8 @@ export const executeConcurrently = async (
147
248
  completedCount++
148
249
  }
149
250
 
150
- if (loggerToLevels(logger).info) {
151
- let log = createExecutionResultLog(afterExecutionInfo, {
251
+ if (executionLogsEnabled) {
252
+ let log = formatExecutionResult(afterExecutionInfo, {
152
253
  completedExecutionLogAbbreviation,
153
254
  executionCount,
154
255
  abortedCount,
@@ -163,31 +264,21 @@ export const executeConcurrently = async (
163
264
  wordWrap: false,
164
265
  })
165
266
 
166
- if (
167
- previousExecutionLog &&
168
- completedExecutionLogMerging &&
169
- previousExecutionResult &&
170
- previousExecutionResult.status === "completed" &&
171
- (previousExecutionResult.consoleCalls
172
- ? previousExecutionResult.consoleCalls.length === 0
173
- : true) &&
174
- executionResult.status === "completed"
175
- ) {
176
- previousExecutionLog.write(log)
267
+ // replace spinner with this execution result
268
+ if (spinner) spinner.stop()
269
+ executionLog.write(log)
270
+
271
+ const canOverwriteLog = canOverwriteLogGetter({
272
+ completedExecutionLogMerging,
273
+ executionResult,
274
+ })
275
+ if (canOverwriteLog) {
276
+ // nothing to do, we reuse the current executionLog object
177
277
  } else {
178
- if (previousExecutionLog) {
179
- previousExecutionLog.destroy()
180
- }
181
- previousExecutionLog = createLog()
182
- previousExecutionLog.write(log)
278
+ executionLog.destroy()
279
+ executionLog = createLog({ newLine: "around" })
183
280
  }
184
281
  }
185
-
186
- if (fileRelativeUrl in report === false) {
187
- report[fileRelativeUrl] = {}
188
- }
189
- report[fileRelativeUrl][executionName] = executionResult
190
- previousExecutionResult = executionResult
191
282
  },
192
283
  })
193
284
 
@@ -202,37 +293,40 @@ export const executeConcurrently = async (
202
293
  executionsDone.length -
203
294
  // we substract abortedCount because they are not pushed into executionsDone
204
295
  summaryCounts.abortedCount,
205
- ...(measureGlobalDuration ? { startMs, endMs: Date.now() } : {}),
296
+ duration: Date.now() - startMs,
206
297
  }
207
298
  if (logSummary) {
208
299
  logger.info(createSummaryLog(summary))
209
300
  }
210
301
 
211
- if (multipleExecutionsOperation.signal.aborted) {
212
- // don't try to do the coverage stuff
213
- return {
214
- summary,
215
- report,
216
- }
217
- }
218
-
219
- return {
302
+ return transformReturnValue({
220
303
  summary,
221
304
  report,
222
- ...(coverage
223
- ? {
224
- coverage: await reportToCoverage(report, {
225
- multipleExecutionsOperation,
226
- logger,
227
- projectDirectoryUrl,
228
- babelPluginMap,
229
- coverageConfig,
230
- coverageIncludeMissing,
231
- coverageV8MergeConflictIsExpected,
232
- }),
233
- }
234
- : {}),
305
+ })
306
+ }
307
+
308
+ const canOverwriteLogGetter = ({
309
+ completedExecutionLogMerging,
310
+ executionResult,
311
+ }) => {
312
+ if (!completedExecutionLogMerging) {
313
+ return false
314
+ }
315
+
316
+ if (executionResult.status === "aborted") {
317
+ return true
318
+ }
319
+
320
+ if (executionResult.status !== "completed") {
321
+ return false
322
+ }
323
+
324
+ const { consoleCalls = [] } = executionResult
325
+ if (consoleCalls.length > 0) {
326
+ return false
235
327
  }
328
+
329
+ return true
236
330
  }
237
331
 
238
332
  const executeInParallel = async ({
@@ -240,6 +334,7 @@ const executeInParallel = async ({
240
334
  executionSteps,
241
335
  start,
242
336
  maxExecutionsInParallel = 1,
337
+ cooldownBetweenExecutions,
243
338
  }) => {
244
339
  const executionResults = []
245
340
  let progressionIndex = 0
@@ -275,6 +370,11 @@ const executeInParallel = async ({
275
370
  if (!multipleExecutionsOperation.signal.aborted) {
276
371
  executionResults[index] = output
277
372
  }
373
+ if (cooldownBetweenExecutions) {
374
+ await new Promise((resolve) =>
375
+ setTimeout(resolve, cooldownBetweenExecutions),
376
+ )
377
+ }
278
378
  }
279
379
 
280
380
  await nextChunk()
@@ -282,22 +382,6 @@ const executeInParallel = async ({
282
382
  return executionResults
283
383
  }
284
384
 
285
- const pathLeadsToFile = (path) => {
286
- return new Promise((resolve, reject) => {
287
- stat(path, (error, stats) => {
288
- if (error) {
289
- if (error.code === "ENOENT") {
290
- resolve(false)
291
- } else {
292
- reject(error)
293
- }
294
- } else {
295
- resolve(stats.isFile())
296
- }
297
- })
298
- })
299
- }
300
-
301
385
  const reportToSummary = (report) => {
302
386
  const fileNames = Object.keys(report)
303
387
 
@@ -25,16 +25,17 @@ export const executePlan = async (
25
25
 
26
26
  defaultMsAllocatedPerExecution,
27
27
  maxExecutionsInParallel,
28
+ cooldownBetweenExecutions,
28
29
  completedExecutionLogMerging,
29
30
  completedExecutionLogAbbreviation,
30
31
  logSummary,
31
- measureGlobalDuration,
32
32
 
33
33
  coverage,
34
34
  coverageConfig,
35
35
  coverageIncludeMissing,
36
36
  coverageForceIstanbul,
37
- coverageV8MergeConflictIsExpected,
37
+ coverageV8ConflictWarning,
38
+ coverageTempDirectoryRelativeUrl,
38
39
 
39
40
  compileServerProtocol,
40
41
  compileServerPrivateKey,
@@ -81,7 +82,6 @@ export const executePlan = async (
81
82
  SIGINT: true,
82
83
  },
83
84
  () => {
84
- logger.info("Aborting execution (SIGINT)")
85
85
  abort()
86
86
  },
87
87
  )
@@ -147,19 +147,22 @@ export const executePlan = async (
147
147
 
148
148
  defaultMsAllocatedPerExecution,
149
149
  maxExecutionsInParallel,
150
+ cooldownBetweenExecutions,
150
151
  completedExecutionLogMerging,
151
152
  completedExecutionLogAbbreviation,
152
153
  logSummary,
153
- measureGlobalDuration,
154
154
 
155
155
  coverage,
156
156
  coverageConfig,
157
157
  coverageIncludeMissing,
158
158
  coverageForceIstanbul,
159
- coverageV8MergeConflictIsExpected,
159
+ coverageV8ConflictWarning,
160
+ coverageTempDirectoryRelativeUrl,
161
+ runtimeSupport,
160
162
  })
161
163
 
162
164
  return {
165
+ aborted: multipleExecutionsOperation.signal.aborted,
163
166
  planSummary: result.summary,
164
167
  planReport: result.report,
165
168
  planCoverage: result.coverage,
@@ -4,8 +4,40 @@ import { msAsDuration } from "../logs/msAsDuration.js"
4
4
  import { EXECUTION_COLORS } from "./execution_colors.js"
5
5
  import { createSummaryDetails } from "./createSummaryLog.js"
6
6
 
7
- export const createExecutionResultLog = (
8
- { executionIndex, fileRelativeUrl, executionParams, executionResult },
7
+ export const formatExecuting = (
8
+ { executionIndex },
9
+ { executionCount, abortedCount, timedoutCount, erroredCount, completedCount },
10
+ ) => {
11
+ const executionNumber = executionIndex + 1
12
+ const description = ANSI.color(
13
+ `executing ${executionNumber} of ${executionCount}`,
14
+ EXECUTION_COLORS.executing,
15
+ )
16
+ const summary =
17
+ executionIndex === 0
18
+ ? ""
19
+ : `(${createSummaryDetails({
20
+ executionCount: executionIndex,
21
+ abortedCount,
22
+ timedoutCount,
23
+ erroredCount,
24
+ completedCount,
25
+ })})`
26
+
27
+ return formatExecution({
28
+ label: `${description} ${summary}`,
29
+ })
30
+ }
31
+
32
+ export const formatExecutionResult = (
33
+ {
34
+ executionIndex,
35
+ fileRelativeUrl,
36
+ runtimeName,
37
+ runtimeVersion,
38
+ executionParams,
39
+ executionResult,
40
+ },
9
41
  {
10
42
  completedExecutionLogAbbreviation,
11
43
  executionCount,
@@ -24,6 +56,7 @@ export const createExecutionResultLog = (
24
56
  executionCount,
25
57
  allocatedMs,
26
58
  })
59
+
27
60
  const summary = `(${createSummaryDetails({
28
61
  executionCount: executionNumber,
29
62
  abortedCount,
@@ -33,21 +66,22 @@ export const createExecutionResultLog = (
33
66
  })})`
34
67
 
35
68
  if (completedExecutionLogAbbreviation && status === "completed") {
36
- return `
37
- ${description} ${summary}`
69
+ return `${description} ${summary}`
38
70
  }
39
71
 
40
- const { runtimeName, runtimeVersion, consoleCalls, startMs, endMs, error } =
41
- executionResult
42
-
43
- const runtime = `${runtimeName}/${runtimeVersion}`
44
- return `
45
- ${description} ${summary}
46
- file: ${fileRelativeUrl}
47
- runtime: ${runtime}${appendDuration({
48
- startMs,
49
- endMs,
50
- })}${appendConsole(consoleCalls)}${appendError(error)}`
72
+ const { consoleCalls = [], error, duration } = executionResult
73
+ const console = formatConsoleCalls(consoleCalls)
74
+
75
+ return formatExecution({
76
+ label: `${description} ${summary}`,
77
+ details: {
78
+ file: fileRelativeUrl,
79
+ runtime: `${runtimeName}/${runtimeVersion}`,
80
+ duration: msAsDuration(duration),
81
+ ...(error ? { error: error.stack } : {}),
82
+ },
83
+ console,
84
+ })
51
85
  }
52
86
 
53
87
  const descriptionFormatters = {
@@ -83,38 +117,33 @@ const descriptionFormatters = {
83
117
  },
84
118
  }
85
119
 
86
- const appendDuration = ({ endMs, startMs }) => {
87
- if (!endMs) return ""
88
-
89
- return `
90
- duration: ${msAsDuration(endMs - startMs)}`
91
- }
92
-
93
- const appendConsole = (consoleCalls) => {
94
- if (!consoleCalls || consoleCalls.length === 0) return ""
95
-
120
+ const formatConsoleCalls = (consoleCalls) => {
96
121
  const consoleOutput = consoleCalls.reduce((previous, { text }) => {
97
122
  return `${previous}${text}`
98
123
  }, "")
99
124
 
100
125
  const consoleOutputTrimmed = consoleOutput.trim()
101
- if (consoleOutputTrimmed === "") return ""
126
+ if (consoleOutputTrimmed === "") {
127
+ return ""
128
+ }
102
129
 
103
- return `
104
- ${ANSI.color(`-------- console --------`, ANSI.GREY)}
130
+ return `${ANSI.color(`-------- console --------`, ANSI.GREY)}
105
131
  ${consoleOutputTrimmed}
106
132
  ${ANSI.color(`-------------------------`, ANSI.GREY)}`
107
133
  }
108
134
 
109
- const appendError = (error) => {
110
- if (!error) {
111
- return ``
135
+ const formatExecution = ({ label, details = {}, console }) => {
136
+ let message = ``
137
+
138
+ message += label
139
+ Object.keys(details).forEach((key) => {
140
+ message += `
141
+ ${key}: ${details[key]}`
142
+ })
143
+ if (console) {
144
+ message += `
145
+ ${console}`
112
146
  }
113
147
 
114
- return `
115
- error: ${error.stack}`
148
+ return message
116
149
  }
117
-
118
- // export const createShortExecutionResultLog = () => {
119
- // return `Execution completed (2/9) - (all completed)`
120
- // }
@@ -1,8 +1,9 @@
1
1
  import { ANSI } from "@jsenv/log"
2
2
 
3
3
  export const EXECUTION_COLORS = {
4
+ executing: ANSI.BLUE,
4
5
  aborted: ANSI.MAGENTA,
5
- timedout: ANSI.YELLOW,
6
+ timedout: ANSI.MAGENTA,
6
7
  errored: ANSI.RED,
7
8
  completed: ANSI.GREEN,
8
9
  cancelled: ANSI.GREY,