ui5-test-runner 6.0.0-beta.5 → 6.0.1

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 (130) hide show
  1. package/README.md +20 -29
  2. package/dist/Command.js +52 -0
  3. package/dist/Npm.js +154 -74
  4. package/dist/browsers/RecoverableBrowserError.js +6 -0
  5. package/dist/browsers/chromeArguments.js +5 -0
  6. package/dist/browsers/chromeOptions.js +29 -0
  7. package/dist/browsers/consoleMessage.js +23 -0
  8. package/dist/browsers/factory.js +168 -3
  9. package/dist/browsers/networkResponse.js +15 -0
  10. package/dist/browsers/playwright.js +104 -0
  11. package/dist/browsers/puppeteer.js +54 -88
  12. package/dist/browsers/seleniumWebdriver.js +124 -0
  13. package/dist/browsers/webdriverio.js +85 -0
  14. package/dist/cli.js +18 -12
  15. package/dist/configuration/CommandLine.js +46 -26
  16. package/dist/configuration/ConfigurationValidator.js +132 -18
  17. package/dist/configuration/OptionValidationError.js +50 -2
  18. package/dist/configuration/derivations.js +8 -0
  19. package/dist/configuration/options.js +380 -23
  20. package/dist/configuration/validations.js +135 -0
  21. package/dist/configuration/validators/boolean.js +1 -1
  22. package/dist/configuration/validators/enumeration.js +7 -0
  23. package/dist/configuration/validators/fsEntry.js +12 -12
  24. package/dist/configuration/validators/index.js +8 -2
  25. package/dist/configuration/validators/integer.js +7 -1
  26. package/dist/configuration/validators/json.js +23 -0
  27. package/dist/configuration/validators/libraryMapping.js +28 -0
  28. package/dist/configuration/validators/percent.js +2 -2
  29. package/dist/configuration/validators/regexp.js +2 -2
  30. package/dist/configuration/validators/secret.js +8 -0
  31. package/dist/configuration/validators/string.js +1 -1
  32. package/dist/configuration/validators/timeout.js +2 -2
  33. package/dist/configuration/validators/url.js +3 -3
  34. package/dist/end.js +58 -0
  35. package/dist/if.js +22 -0
  36. package/dist/modes/Modes.js +2 -1
  37. package/dist/modes/batch/BatchItem.js +1 -0
  38. package/dist/modes/batch/batchTask.js +159 -0
  39. package/dist/modes/batch/index.js +98 -0
  40. package/dist/modes/batch/output.js +22 -0
  41. package/dist/modes/batch/report.js +45 -0
  42. package/dist/modes/batch/resolve.js +105 -0
  43. package/dist/modes/dumpConfig.js +6 -0
  44. package/dist/modes/execute.js +6 -5
  45. package/dist/modes/help.js +87 -2
  46. package/dist/modes/log/LogReader.js +3 -2
  47. package/dist/modes/log/LogStorage.js +31 -27
  48. package/dist/modes/log/index.js +38 -6
  49. package/dist/modes/log/{REserve.js → reserve.js} +25 -2
  50. package/dist/modes/mcp/IDocument.js +2 -0
  51. package/dist/modes/mcp/frontmatter.js +142 -0
  52. package/dist/modes/mcp/index.js +49 -0
  53. package/dist/modes/mcp/knowledgeBase.js +104 -0
  54. package/dist/modes/mcp/remoteKnowledgeBase.js +79 -0
  55. package/dist/modes/mcp/reserve.js +175 -0
  56. package/dist/modes/mcp/tools/getTopic.js +46 -0
  57. package/dist/modes/mcp/tools/index.js +6 -0
  58. package/dist/modes/mcp/tools/listTopics.js +14 -0
  59. package/dist/modes/test/PageContext.js +1 -0
  60. package/dist/modes/test/Server.js +99 -0
  61. package/dist/modes/test/browser.js +31 -17
  62. package/dist/modes/test/browserConfig.js +17 -0
  63. package/dist/modes/test/coverage/collect.js +38 -0
  64. package/dist/modes/test/coverage/index.js +3 -0
  65. package/dist/modes/test/coverage/instrument.js +70 -0
  66. package/dist/modes/test/coverage/nyc.js +15 -0
  67. package/dist/modes/test/coverage/report.js +71 -0
  68. package/dist/modes/test/coverage/settings.js +53 -0
  69. package/dist/modes/test/index.js +94 -34
  70. package/dist/modes/test/pageTask.js +293 -98
  71. package/dist/modes/test/report.js +20 -3
  72. package/dist/modes/test/reserve.js +76 -0
  73. package/dist/modes/test/screenshot.js +49 -0
  74. package/dist/platform/Crypto.js +9 -0
  75. package/dist/platform/Exit.js +33 -24
  76. package/dist/platform/FileSystem.js +6 -2
  77. package/dist/platform/Host.js +9 -1
  78. package/dist/platform/Http.js +54 -29
  79. package/dist/platform/Module.js +2 -1
  80. package/dist/platform/Path.js +5 -1
  81. package/dist/platform/Process.js +67 -13
  82. package/dist/platform/Terminal.js +12 -8
  83. package/dist/platform/Thread.js +1 -1
  84. package/dist/platform/assert.js +13 -12
  85. package/dist/platform/environment.js +9 -18
  86. package/dist/platform/index.js +1 -0
  87. package/dist/platform/logger/CompressionContext.js +117 -0
  88. package/dist/platform/logger/allCompressed.js +8 -7
  89. package/dist/platform/logger/compress.js +29 -129
  90. package/dist/platform/logger/output/BaseLoggerOutput.js +49 -31
  91. package/dist/platform/logger/output/InteractiveLoggerOutput.js +1 -1
  92. package/dist/platform/logger/output/LlmLoggerOutput.js +29 -0
  93. package/dist/platform/logger/output/factory.js +5 -1
  94. package/dist/platform/logger/output.js +24 -6
  95. package/dist/platform/logger/toInternalLogAttributes.js +10 -11
  96. package/dist/platform/logger/traceFileName.js +1 -0
  97. package/dist/platform/logger.js +52 -21
  98. package/dist/platform/mock.js +110 -19
  99. package/dist/platform/version.js +1 -2
  100. package/dist/platform/workerBootstrap.js +1 -1
  101. package/dist/reports/initReportBuilder.js +18 -0
  102. package/dist/reports/junit.js +64 -0
  103. package/dist/reports/saveReport.js +10 -0
  104. package/dist/reports/textReport.js +92 -0
  105. package/dist/reserveLogger.js +22 -0
  106. package/dist/sendToParentProcess.js +6 -0
  107. package/dist/start.js +79 -0
  108. package/dist/types/ISecretValue.js +1 -0
  109. package/dist/types/SecretValue.js +15 -0
  110. package/dist/ui/agent.js +4 -2
  111. package/dist/ui/html-report.js +2 -2
  112. package/dist/ui/lib.js +1 -1
  113. package/dist/ui/log-viewer.js +1 -1
  114. package/dist/utils/node/Folder.js +2 -4
  115. package/dist/utils/node/FramedStreamReader.js +4 -5
  116. package/dist/utils/node/FramedStreamWriter.js +6 -5
  117. package/dist/utils/node/anonymize.js +11 -0
  118. package/dist/utils/node/timeout.js +11 -0
  119. package/dist/utils/shared/ProgressBar.js +1 -2
  120. package/dist/utils/shared/TestReportBuilder.js +11 -7
  121. package/dist/utils/shared/host.js +11 -0
  122. package/dist/utils/shared/object.js +12 -3
  123. package/dist/utils/shared/schema.js +20 -0
  124. package/dist/utils/shared/string.js +1 -0
  125. package/dist/utils/shared/toIError.js +3 -7
  126. package/dist/utils/shared/version.js +52 -0
  127. package/package.json +3 -79
  128. package/dist/configuration/validators/browser.js +0 -11
  129. package/dist/modes/test/REserve.js +0 -31
  130. package/dist/modes/test/server.js +0 -109
@@ -26,14 +26,14 @@ const socketHandleDescriptor = (handle) => {
26
26
  if (handle._handle) {
27
27
  const underlyingHandle = handle._handle;
28
28
  const underlyingClassName = underlyingHandle && underlyingHandle.constructor && underlyingHandle.constructor.name;
29
- return `${underlyingClassName || 'handle unknown'}`;
29
+ return underlyingClassName || 'handle unknown';
30
30
  }
31
31
  return 'unknown';
32
32
  };
33
33
  const handleDescriptors = {
34
34
  ChildProcess: (handle) => {
35
35
  return (`pid: ${handle.pid}` +
36
- (handle.spawnargs ? ` ${handle.spawnargs.map((value) => ('' + value).replaceAll(' ', '␣'))}` : ' unknown'));
36
+ (handle.spawnargs ? ` ${handle.spawnargs.map((value) => value.replaceAll(' ', '␣'))}` : ' unknown'));
37
37
  },
38
38
  ReadStream: (handle) => (handle.fd === 0 ? `stdin isTTY: ${handle.isTTY}` : `fd: ${handle.fd}`),
39
39
  Server: (handle) => `connections: ${handle._connections} events: ${handle._eventsCount}`,
@@ -44,10 +44,12 @@ const handleDescriptors = {
44
44
  : `fd: ${handle.fd}`
45
45
  };
46
46
  const isStdStream = (handle) => 'fd' in handle && handle.fd >= 0 && handle.fd < 3;
47
- const unknownHandleDescriptor = () => 'unknown';
47
+ const unknownHandleDescriptor = (_handle) => 'unknown';
48
48
  const describeHandle = (handle) => {
49
- const className = handle && handle.constructor && handle.constructor.name;
50
- return { className, label: (handleDescriptors[className] ?? unknownHandleDescriptor)(handle) };
49
+ const className = (handle && handle.constructor && handle.constructor.name);
50
+ return className
51
+ ? { className, label: (handleDescriptors[className] ?? unknownHandleDescriptor)(handle) }
52
+ : { className: 'unknown', label: unknownHandleDescriptor(handle) };
51
53
  };
52
54
  export class Exit {
53
55
  static _asyncTaskId = 0;
@@ -55,38 +57,42 @@ export class Exit {
55
57
  static _checkForHandlesLeak() {
56
58
  const undocumentedProcess = process;
57
59
  if (!undocumentedProcess._getActiveHandles) {
58
- logger?.warn({ source: 'exit/handle', message: 'Missing process._getActiveHandles' });
60
+ logger?.warn({ source: 'exit/handle', message: 'missing process._getActiveHandles' });
59
61
  return;
60
62
  }
61
63
  const activeHandles = undocumentedProcess._getActiveHandles();
62
- let messagePortFound = false;
64
+ let isMessagePortFound = false;
63
65
  for (const handle of activeHandles) {
64
66
  const { className, label } = describeHandle(handle);
65
67
  if (isStdStream(handle)) {
66
68
  logger?.debug({ source: 'exit/handle', message: `${className} ${label}` });
67
69
  }
68
- else if (className === 'MessagePort' && !messagePortFound) {
69
- messagePortFound = true;
70
+ else if (className === 'MessagePort' && !isMessagePortFound) {
71
+ isMessagePortFound = true;
70
72
  logger?.debug({ source: 'exit/handle', message: `${className} ${label}` });
71
73
  }
74
+ else if (['TLSSocket', 'Socket'].includes(className)) {
75
+ logger?.debug({ source: 'exit/handle', message: `destroying leak ${className} ${label}` });
76
+ handle.destroy();
77
+ }
72
78
  else {
73
79
  logger?.warn({ source: 'exit/handle', message: `possible leak ${className} ${label}` });
74
- if (className === 'TLSSocket' || className === 'Socket') {
75
- handle.destroy();
76
- }
77
80
  }
78
81
  }
79
82
  }
83
+ static get code() {
84
+ return (process.exitCode ?? 0);
85
+ }
80
86
  static set code(code) {
81
87
  assert(Thread.isMainThread, 'Exit.code can be set only on main thread');
82
88
  process.exitCode = code;
83
89
  }
84
90
  static registerAsyncTask(task) {
85
91
  assert(Thread.isMainThread, 'Exit.registerAsyncTask can be called only on main thread');
86
- if (Exit._enteringShutdown) {
92
+ if (this._enteringShutdown) {
87
93
  throw new ExitShutdownError();
88
94
  }
89
- const id = ++Exit._asyncTaskId;
95
+ const id = ++this._asyncTaskId;
90
96
  this._asyncTasks.push({
91
97
  id,
92
98
  ...task
@@ -95,7 +101,7 @@ export class Exit {
95
101
  [Symbol.dispose]() {
96
102
  const index = Exit._asyncTasks.findIndex((task) => task.id === id);
97
103
  try {
98
- assert(index !== -1, 'unable to find Exit async task to unregister');
104
+ assert(Exit._enteringShutdown || index !== -1, `unable to find Exit async task "${task.name}" to unregister (shutdown=${Exit._enteringShutdown})`);
99
105
  Exit._asyncTasks.splice(index, 1);
100
106
  }
101
107
  catch {
@@ -107,10 +113,13 @@ export class Exit {
107
113
  static _logLevel = 'debug';
108
114
  static async shutdown() {
109
115
  assert(Thread.isMainThread, 'Exit.shutdown can be called only on main thread');
110
- Exit._enteringShutdown = true;
111
- const logLevel = Exit._logLevel;
112
- while (Exit._asyncTasks.length > 0) {
113
- const task = Exit._asyncTasks.at(-1);
116
+ if (this._enteringShutdown) {
117
+ return;
118
+ }
119
+ this._enteringShutdown = true;
120
+ const logLevel = this._logLevel;
121
+ while (this._asyncTasks.length > 0) {
122
+ const task = this._asyncTasks.at(-1);
114
123
  try {
115
124
  logger?.[logLevel]({ source: 'exit', message: `Stopping ${task.name}...` });
116
125
  await task.stop();
@@ -120,20 +129,20 @@ export class Exit {
120
129
  logger?.[logLevel]({ source: 'exit', message: `Failed while stopping ${task.name}...`, error });
121
130
  }
122
131
  finally {
123
- if (task === Exit._asyncTasks.at(-1)) {
124
- Exit._asyncTasks.pop();
132
+ if (task === this._asyncTasks.at(-1)) {
133
+ this._asyncTasks.pop();
125
134
  }
126
135
  }
127
136
  }
128
- Exit._checkForHandlesLeak();
137
+ this._checkForHandlesLeak();
129
138
  logger?.[logLevel]({ source: 'exit', message: `Stopping logger...` });
130
139
  await logger?.stop();
131
140
  }
132
141
  static sigInt() {
133
142
  Exit._logLevel = 'info';
134
- void Exit.shutdown();
143
+ void this.shutdown();
135
144
  }
136
145
  }
137
146
  if (Thread.isMainThread) {
138
- process.on('SIGINT', Exit.sigInt);
147
+ process.on('SIGINT', () => Exit.sigInt());
139
148
  }
@@ -1,13 +1,17 @@
1
- import { access, stat, constants, readFile, mkdir, rm } from 'node:fs/promises';
2
- import { createReadStream, createWriteStream, writeFileSync } from 'node:fs';
1
+ import { access, stat, constants, readFile, readdir, mkdir, rename, rm, writeFile } from 'node:fs/promises';
2
+ import { createReadStream, createWriteStream, watch, writeFileSync } from 'node:fs';
3
3
  export class FileSystem {
4
4
  static access = access;
5
5
  static constants = constants;
6
6
  static createReadStream = createReadStream;
7
7
  static createWriteStream = createWriteStream;
8
8
  static mkdir = mkdir;
9
+ static readdir = readdir;
9
10
  static readFile = readFile;
11
+ static rename = rename;
10
12
  static rm = rm;
11
13
  static stat = stat;
14
+ static writeFile = writeFile;
15
+ static watch = watch;
12
16
  static writeFileSync = writeFileSync;
13
17
  }
@@ -1,10 +1,18 @@
1
- import { machine, cpus, platform } from 'node:os';
1
+ import { machine, cpus, platform, release, version, homedir, tmpdir } from 'node:os';
2
2
  export class Host {
3
+ static argv = process.argv;
4
+ static nodePath = process.argv[0];
3
5
  static cpus = cpus;
4
6
  static cwd = process.cwd.bind(process);
7
+ static env = process.env;
8
+ static tmpdir = tmpdir;
9
+ static homedir = homedir;
5
10
  static machine = machine;
6
11
  static memoryUsage = process.memoryUsage.bind(process);
7
12
  static nodeVersion = process.version;
13
+ static osRelease = release;
14
+ static osVersion = version;
8
15
  static pid = process.pid;
9
16
  static platform = platform;
17
+ static version = process.version;
10
18
  }
@@ -1,38 +1,63 @@
1
1
  import { logger } from './logger.js';
2
2
  import { Exit } from './Exit.js';
3
- let lastRequestId = 0;
3
+ let _lastRequestId = 0;
4
+ const _fetch = async (url, init) => {
5
+ const { requestId = ++_lastRequestId, controller = new AbortController(), ...fetchInit } = init ?? {};
6
+ let abortForwarder;
7
+ if (init?.signal) {
8
+ abortForwarder = () => controller.abort();
9
+ init.signal.addEventListener('abort', abortForwarder);
10
+ }
11
+ const loggedInit = Object.keys(fetchInit).length > 0 ? fetchInit : undefined;
12
+ using _ = Exit.registerAsyncTask({
13
+ name: `http.fetch#${requestId}`,
14
+ stop: () => controller.abort()
15
+ });
16
+ logger.debug({
17
+ source: 'http',
18
+ message: `${fetchInit?.method ?? 'GET'} ${url}`,
19
+ data: { requestId, init: loggedInit }
20
+ });
21
+ try {
22
+ const response = await fetch(url, {
23
+ ...fetchInit,
24
+ signal: controller.signal
25
+ });
26
+ const headers = Object.fromEntries(response.headers);
27
+ logger.debug({
28
+ source: 'http',
29
+ message: `${response.status} ${response.statusText}`,
30
+ data: { requestId, status: response.status, headers }
31
+ });
32
+ return response;
33
+ }
34
+ catch (error) {
35
+ logger.debug({ source: 'http', message: 'error caught', data: { requestId }, error });
36
+ throw error;
37
+ }
38
+ finally {
39
+ if (abortForwarder) {
40
+ init?.signal?.removeEventListener('abort', abortForwarder);
41
+ }
42
+ }
43
+ };
4
44
  export const Http = {
5
- async get(url) {
6
- const requestId = ++lastRequestId;
45
+ async fetch(url, init) {
46
+ return _fetch(url, init);
47
+ },
48
+ async getAsText(url) {
49
+ const requestId = ++_lastRequestId;
7
50
  const controller = new AbortController();
51
+ const response = await _fetch(url, { method: 'GET', requestId, controller });
52
+ if (!response.ok) {
53
+ throw new Error('HTTP request failed');
54
+ }
8
55
  using _ = Exit.registerAsyncTask({
9
- name: `http.get#${requestId}`,
56
+ name: `http.getAsText#${requestId}`,
10
57
  stop: () => controller.abort()
11
58
  });
12
- logger.debug({ source: 'http', message: `GET ${url}`, data: { requestId } });
13
- try {
14
- const response = await fetch(url, {
15
- signal: controller.signal
16
- });
17
- const headers = {};
18
- for (const [name, value] of response.headers) {
19
- headers[name] = value;
20
- }
21
- logger.debug({
22
- source: 'http',
23
- message: `${response.status} ${response.statusText}`,
24
- data: { requestId, status: response.status, headers }
25
- });
26
- if (!response.ok) {
27
- throw new Error('HTTP request failed');
28
- }
29
- const text = await response.text();
30
- logger.debug({ source: 'http', message: text, data: { requestId } });
31
- return text;
32
- }
33
- catch (error) {
34
- logger.debug({ source: 'http', message: 'error caught', data: { requestId }, error });
35
- throw error;
36
- }
59
+ const text = await response.text();
60
+ logger.debug({ source: 'http', message: text, data: { requestId } });
61
+ return text;
37
62
  }
38
63
  };
@@ -1,4 +1,5 @@
1
- import { createRequire } from 'node:module';
1
+ import { createRequire, findPackageJSON } from 'node:module';
2
2
  export class Module {
3
3
  static createRequire = createRequire;
4
+ static findPackageJSON = findPackageJSON;
4
5
  }
@@ -1,5 +1,9 @@
1
- import { join, isAbsolute } from 'node:path';
1
+ import { basename, dirname, extname, isAbsolute, join, relative } from 'node:path';
2
2
  export class Path {
3
+ static basename = basename;
4
+ static dirname = dirname;
5
+ static extname = extname;
3
6
  static isAbsolute = isAbsolute;
4
7
  static join = join;
8
+ static relative = relative;
5
9
  }
@@ -1,5 +1,6 @@
1
1
  import { Host } from './Host.js';
2
2
  import { spawn } from 'node:child_process';
3
+ import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
3
4
  import { logger } from './logger.js';
4
5
  import { Exit } from './Exit.js';
5
6
  class ProcessStopper {
@@ -12,9 +13,23 @@ class ProcessStopper {
12
13
  return this._process?.closed;
13
14
  }
14
15
  }
16
+ export const splitLines = (string) => string.split(/\r?\n/).filter((line, index, array) => index < array.length - 1 || line.length > 0);
17
+ function sanitizeSpawnOptions(options) {
18
+ if (!options.env)
19
+ return options;
20
+ const { env, ...rest } = options;
21
+ return { ...rest, env: Object.keys(env) };
22
+ }
15
23
  export class Process {
24
+ static sendToParent = (message) => {
25
+ if (process.send === undefined) {
26
+ throw new Error('process.send is not defined: this process has not been created with IPC mappings');
27
+ }
28
+ process.send(message);
29
+ };
16
30
  static spawn = (command, arguments_, options = {}) => {
17
- const finalCommand = command === 'node' ? process.argv[0] : command;
31
+ const { onMessage, forceRender: _forceRender, ...spawnOptions } = options;
32
+ const finalCommand = command === 'node' ? Host.nodePath : command;
18
33
  let asyncTask;
19
34
  try {
20
35
  const stopper = new ProcessStopper();
@@ -22,14 +37,21 @@ export class Process {
22
37
  name: `Process.spawn(${command},${arguments_.join(',')})`,
23
38
  stop: () => stopper.stop()
24
39
  });
25
- const childProcess = spawn(finalCommand, arguments_, options);
26
- logger.debug({
40
+ if (onMessage) {
41
+ const stdio = spawnOptions.stdio;
42
+ spawnOptions.stdio = Array.isArray(stdio)
43
+ ? [...stdio, 'ipc']
44
+ : [stdio ?? 'pipe', stdio ?? 'pipe', stdio ?? 'pipe', 'ipc'];
45
+ }
46
+ const childProcess = spawn(finalCommand, arguments_, spawnOptions);
47
+ const logMethod = childProcess.pid ? 'debug' : 'warn';
48
+ logger[logMethod]({
27
49
  source: 'process',
28
50
  processId: childProcess.pid,
29
51
  message: 'spawned',
30
- data: { command, arguments: arguments_, options }
52
+ data: { command, arguments: arguments_, options: sanitizeSpawnOptions(spawnOptions) }
31
53
  });
32
- const process = new Process(childProcess, asyncTask);
54
+ const process = new Process(childProcess, asyncTask, options);
33
55
  stopper.process = process;
34
56
  return process;
35
57
  }
@@ -37,7 +59,11 @@ export class Process {
37
59
  logger.error({
38
60
  source: 'process',
39
61
  message: 'spawn failed',
40
- data: { command, arguments: arguments_, options },
62
+ data: {
63
+ command,
64
+ arguments: arguments_,
65
+ options: sanitizeSpawnOptions(spawnOptions)
66
+ },
41
67
  error
42
68
  });
43
69
  asyncTask?.[Symbol.dispose]();
@@ -62,31 +88,49 @@ export class Process {
62
88
  }
63
89
  _childProcess;
64
90
  _asyncTask;
65
- constructor(childProcess, asyncTask) {
91
+ constructor(childProcess, asyncTask, options) {
66
92
  this._childProcess = childProcess;
67
93
  this._asyncTask = asyncTask;
68
94
  const { promise, resolve } = Promise.withResolvers();
69
95
  this._closed = promise;
96
+ if (options.onMessage) {
97
+ this._childProcess.on('message', options.onMessage);
98
+ }
99
+ const { forceRender } = options;
70
100
  this._childProcess.stdout?.on('data', (buffer) => {
71
101
  const message = buffer.toString();
72
- logger.debug({ source: 'process', processId: this.pid, message, data: { type: 'stdout' } });
102
+ for (const line of splitLines(message)) {
103
+ logger.info({ source: 'process/stdout', processId: this.pid, forceRender, message: line });
104
+ }
73
105
  this._stdout.push(message);
74
106
  });
75
107
  this._childProcess.stderr?.on('data', (buffer) => {
76
108
  const message = buffer.toString();
77
- logger.debug({ source: 'process', processId: this.pid, message, data: { type: 'stderr' } });
109
+ for (const line of splitLines(message)) {
110
+ logger.error({ source: 'process/stderr', processId: this.pid, forceRender, message: line });
111
+ }
78
112
  this._stderr.push(message);
79
113
  });
80
- this._childProcess.on('close', (code) => {
114
+ this._childProcess
115
+ .on('error', (error) => {
116
+ logger.error({
117
+ source: 'process',
118
+ processId: childProcess.pid,
119
+ message: error.message,
120
+ error
121
+ });
122
+ })
123
+ .on('close', (code) => {
81
124
  this._code = code ?? 0;
82
- logger.debug({
125
+ const logLevel = this._code === 0 ? 'debug' : 'warn';
126
+ logger[logLevel]({
83
127
  source: 'process',
84
128
  processId: this.pid,
85
129
  message: 'closed',
86
130
  data: { code: this._code }
87
131
  });
88
132
  this._asyncTask[Symbol.dispose]();
89
- resolve();
133
+ setTimeout(resolve, 0);
90
134
  });
91
135
  }
92
136
  get pid() {
@@ -112,7 +156,16 @@ export class Process {
112
156
  process.kill(-this.pid);
113
157
  }
114
158
  catch {
115
- process.kill(this.pid);
159
+ logger.warn({
160
+ source: 'process',
161
+ processId: this.pid,
162
+ message: 'group kill failed; killing pid only — if this process spawned children, they may be orphaned (was it spawned with detached:true?)'
163
+ });
164
+ try {
165
+ process.kill(this.pid);
166
+ }
167
+ catch {
168
+ }
116
169
  }
117
170
  }
118
171
  logger.debug({
@@ -130,4 +183,5 @@ export class Process {
130
183
  });
131
184
  }
132
185
  }
186
+ static sleep = (ms) => setTimeoutPromise(ms);
133
187
  }
@@ -1,17 +1,21 @@
1
1
  import { stripVTControlCharacters } from 'node:util';
2
- const ASCII_ESCAPE = '\u001B';
2
+ const ASCII_ESCAPE = '\u{1B}';
3
3
  const CSI = `${ASCII_ESCAPE}[`;
4
+ let isRawModeSet = false;
4
5
  export class Terminal {
5
6
  static isTTY = process.stdout.isTTY;
6
7
  static onResize(callback) {
7
- process.stdout.on('resize', () => callback(Terminal.width));
8
+ process.stdout.on('resize', () => callback(this.width));
8
9
  }
9
10
  static setRawMode(callback) {
10
11
  if (callback === false) {
11
- process.stdin.setRawMode(false);
12
- process.stdin.pause();
12
+ if (isRawModeSet) {
13
+ process.stdin.setRawMode(false);
14
+ process.stdin.pause();
15
+ }
13
16
  }
14
17
  else {
18
+ isRawModeSet = true;
15
19
  process.stdin.setRawMode(true);
16
20
  process.stdin.on('data', callback);
17
21
  }
@@ -24,17 +28,17 @@ export class Terminal {
24
28
  process.stdout.write(text);
25
29
  }
26
30
  static hideCursor() {
27
- Terminal.write(`${CSI}?25l`);
31
+ this.write(`${CSI}?25l`);
28
32
  }
29
33
  static showCursor() {
30
- Terminal.write(`${CSI}?25h`);
34
+ this.write(`${CSI}?25h`);
31
35
  }
32
36
  static eraseToEnd(lines) {
33
37
  if (lines === 0) {
34
- Terminal.write(`${CSI}0G`);
38
+ this.write(`${CSI}0G`);
35
39
  }
36
40
  else {
37
- Terminal.write(`${CSI}0G${CSI}${lines}A${CSI}0J`);
41
+ this.write(`${CSI}0G${CSI}${lines}A${CSI}0J`);
38
42
  }
39
43
  }
40
44
  static BLUE = `${CSI}34m`;
@@ -25,7 +25,7 @@ export class Thread {
25
25
  }
26
26
  logger?.debug({ source: 'thread', message: `Creating worker for ${name}`, data });
27
27
  const bootstrapPath = Path.join(__sourcesRoot, 'platform/workerBootstrap' + extension);
28
- const js2tsUrl = new URL('js2ts.mjs', import.meta.url).toString();
28
+ const js2tsUrl = new URL('js2ts.mjs', import.meta.url).href;
29
29
  const execArgv = extension === '.ts' ? ['--no-warnings', '--import', js2tsUrl] : [];
30
30
  const worker = new Worker(bootstrapPath, {
31
31
  execArgv,
@@ -1,17 +1,18 @@
1
1
  import { AssertionError } from 'node:assert/strict';
2
2
  import { __developmentMode } from './constants.js';
3
3
  import { logger } from './logger/proxy.js';
4
- export const assert = (condition, message = 'Assertion failed') => {
5
- if (!condition) {
6
- const error = new AssertionError({ message });
7
- if (__developmentMode) {
8
- let { stack } = error;
9
- stack = stack ? stack.split('\n').slice(1).join('\n') : '';
10
- logger?.fatal({ source: 'assert', message: (message ?? 'Assertion failed') + stack, error });
11
- }
12
- else {
13
- logger?.fatal({ source: 'assert', message: message ?? 'Assertion failed', error });
14
- }
15
- throw error;
4
+ export const assert = (shouldBeTrue, messageIfNotTrue = 'Assertion failed') => {
5
+ if (shouldBeTrue) {
6
+ return;
16
7
  }
8
+ const error = new AssertionError({ message: messageIfNotTrue });
9
+ if (__developmentMode) {
10
+ let { stack } = error;
11
+ stack = stack ? stack.split('\n').slice(1).join('\n') : '';
12
+ logger?.fatal({ source: 'assert', message: messageIfNotTrue + stack, error });
13
+ }
14
+ else {
15
+ logger?.fatal({ source: 'assert', message: messageIfNotTrue, error });
16
+ }
17
+ throw error;
17
18
  };
@@ -1,28 +1,19 @@
1
1
  import { Host } from './Host.js';
2
2
  import { logger } from './logger.js';
3
3
  import { version } from './version.js';
4
+ import { countCpuModels } from '../utils/shared/host.js';
4
5
  export const logEnvironnement = async () => {
5
- const runnerVersion = await version();
6
+ const { name, version: semver } = await version();
6
7
  const now = new Date();
7
8
  logger.info({
8
9
  source: 'job',
9
- message: `${runnerVersion} / Node.js ${Host.nodeVersion} / ${now.toISOString()} (${now.getTimezoneOffset()})`
10
+ message: `${name}@${semver} / Node.js ${Host.nodeVersion} / ${now.toISOString()} (${now.getTimezoneOffset()})`
10
11
  });
11
- const cpus = {};
12
- for (const { model } of Host.cpus()) {
13
- if (cpus[model]) {
14
- ++cpus[model];
15
- }
16
- else {
17
- cpus[model] = 1;
18
- }
19
- }
20
- for (const [model, count] of Object.entries(cpus)) {
21
- if (count === 1) {
22
- logger.info({ source: 'job', message: `${Host.machine()} / ${model}` });
23
- }
24
- else {
25
- logger.info({ source: 'job', message: `${Host.machine()} / ${count}x ${model}` });
26
- }
12
+ const machine = Host.machine();
13
+ for (const { model, count } of countCpuModels(Host.cpus())) {
14
+ logger.info({
15
+ source: 'job',
16
+ message: count === 1 ? `${machine} / ${model}` : `${machine} / ${count}x ${model}`
17
+ });
27
18
  }
28
19
  };
@@ -1,5 +1,6 @@
1
1
  export * from './assert.js';
2
2
  export * from './constants.js';
3
+ export * from './Crypto.js';
3
4
  export * from './environment.js';
4
5
  export * from './logger.js';
5
6
  export * from './Exit.js';