ui5-test-runner 5.13.0 → 6.0.0-beta.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 (186) hide show
  1. package/README.md +3 -2
  2. package/dist/Npm.js +80 -0
  3. package/dist/browsers/IBrowser.js +1 -0
  4. package/dist/browsers/factory.js +9 -0
  5. package/dist/browsers/puppeteer.js +158 -0
  6. package/dist/cli.js +17 -0
  7. package/dist/configuration/CommandLine.js +112 -0
  8. package/dist/configuration/Configuration.js +1 -0
  9. package/dist/configuration/ConfigurationValidator.js +79 -0
  10. package/dist/configuration/Option.js +1 -0
  11. package/dist/configuration/OptionValidationError.js +15 -0
  12. package/dist/configuration/indexedOptions.js +13 -0
  13. package/dist/configuration/options.js +191 -0
  14. package/dist/configuration/validators/OptionValidator.js +1 -0
  15. package/dist/configuration/validators/boolean.js +15 -0
  16. package/dist/configuration/validators/browser.js +11 -0
  17. package/dist/configuration/validators/fsEntry.js +70 -0
  18. package/dist/configuration/validators/index.js +20 -0
  19. package/dist/configuration/validators/integer.js +10 -0
  20. package/dist/configuration/validators/percent.js +17 -0
  21. package/dist/configuration/validators/regexp.js +20 -0
  22. package/dist/configuration/validators/string.js +7 -0
  23. package/dist/configuration/validators/timeout.js +24 -0
  24. package/dist/configuration/validators/url.js +8 -0
  25. package/dist/modes/ModeFunction.js +1 -0
  26. package/dist/modes/Modes.js +9 -0
  27. package/dist/modes/execute.js +27 -0
  28. package/dist/modes/help.js +3 -0
  29. package/dist/modes/log/ILogStorage.js +1 -0
  30. package/dist/modes/log/LogMetrics.js +9 -0
  31. package/dist/modes/log/LogReader.js +37 -0
  32. package/dist/modes/log/LogStorage.js +68 -0
  33. package/dist/modes/log/REserve.js +101 -0
  34. package/dist/modes/log/index.js +58 -0
  35. package/dist/modes/test/REserve.js +31 -0
  36. package/dist/modes/test/agent.js +8 -0
  37. package/dist/modes/test/browser.js +37 -0
  38. package/dist/modes/test/index.js +66 -0
  39. package/dist/modes/test/pageTask.js +145 -0
  40. package/dist/modes/test/report.js +3 -0
  41. package/dist/modes/test/server.js +109 -0
  42. package/dist/modes/version.js +11 -0
  43. package/dist/platform/Exit.js +139 -0
  44. package/dist/platform/FileSystem.js +13 -0
  45. package/dist/platform/Host.js +10 -0
  46. package/dist/platform/Http.js +38 -0
  47. package/dist/platform/Path.js +5 -0
  48. package/dist/platform/Process.js +133 -0
  49. package/dist/platform/Terminal.js +47 -0
  50. package/dist/platform/Thread.js +43 -0
  51. package/dist/platform/ZLib.js +7 -0
  52. package/dist/platform/assert.js +17 -0
  53. package/dist/platform/constants.js +5 -0
  54. package/dist/platform/environment.js +28 -0
  55. package/dist/platform/index.js +13 -0
  56. package/dist/platform/logger/ILogger.js +1 -0
  57. package/dist/platform/logger/allCompressed.js +54 -0
  58. package/dist/platform/logger/compress.js +277 -0
  59. package/dist/platform/logger/output/BaseLoggerOutput.js +158 -0
  60. package/dist/platform/logger/output/InteractiveLoggerOutput.js +102 -0
  61. package/dist/platform/logger/output/StaticLoggerOutput.js +32 -0
  62. package/dist/platform/logger/output/factory.js +10 -0
  63. package/dist/platform/logger/output.js +58 -0
  64. package/dist/platform/logger/proxy.js +6 -0
  65. package/dist/platform/logger/toInternalLogAttributes.js +22 -0
  66. package/dist/platform/logger/types.js +7 -0
  67. package/dist/platform/logger.js +138 -0
  68. package/dist/platform/mock.js +104 -0
  69. package/dist/platform/version.js +8 -0
  70. package/dist/platform/workerBootstrap.js +21 -0
  71. package/dist/reports/html.js +46 -0
  72. package/dist/types/AgentState.js +1 -0
  73. package/dist/types/CommonTestReportFormat.js +50 -0
  74. package/dist/types/IError.js +1 -0
  75. package/dist/types/IUserInterfaceController.js +1 -0
  76. package/dist/types/typeUtilities.js +1 -0
  77. package/dist/ui/agent.js +3 -0
  78. package/dist/ui/html-report.js +2 -0
  79. package/dist/ui/lib.js +1 -0
  80. package/dist/ui/log-viewer.js +2 -0
  81. package/dist/utils/node/Folder.js +28 -0
  82. package/dist/utils/node/FramedStreamReader.js +86 -0
  83. package/dist/utils/node/FramedStreamWriter.js +27 -0
  84. package/dist/utils/shared/ProgressBar.js +43 -0
  85. package/dist/utils/shared/TestReportBuilder.js +48 -0
  86. package/dist/utils/shared/memoize.js +19 -0
  87. package/dist/utils/shared/object.js +8 -0
  88. package/dist/utils/shared/parallelize.js +59 -0
  89. package/dist/utils/shared/string.js +23 -0
  90. package/dist/utils/shared/toIError.js +17 -0
  91. package/package.json +73 -50
  92. package/.releaserc +0 -5
  93. package/index.js +0 -175
  94. package/jest.config.json +0 -31
  95. package/src/add-test-pages.js +0 -67
  96. package/src/batch.js +0 -214
  97. package/src/browsers.js +0 -319
  98. package/src/capabilities/index.js +0 -204
  99. package/src/capabilities/tests/basic/iframe.html +0 -8
  100. package/src/capabilities/tests/basic/index.html +0 -12
  101. package/src/capabilities/tests/basic/index.js +0 -20
  102. package/src/capabilities/tests/basic/ui5.html +0 -24
  103. package/src/capabilities/tests/dynamic-include/index.js +0 -21
  104. package/src/capabilities/tests/dynamic-include/mix.html +0 -11
  105. package/src/capabilities/tests/dynamic-include/one.html +0 -11
  106. package/src/capabilities/tests/dynamic-include/post.js +0 -3
  107. package/src/capabilities/tests/dynamic-include/test.js +0 -1
  108. package/src/capabilities/tests/dynamic-include/two.html +0 -11
  109. package/src/capabilities/tests/index.js +0 -16
  110. package/src/capabilities/tests/local-storage/index.html +0 -16
  111. package/src/capabilities/tests/local-storage/index.js +0 -21
  112. package/src/capabilities/tests/screenshot/index.html +0 -23
  113. package/src/capabilities/tests/screenshot/index.js +0 -24
  114. package/src/capabilities/tests/scripts/coverage.html +0 -32
  115. package/src/capabilities/tests/scripts/iframe.html +0 -18
  116. package/src/capabilities/tests/scripts/index.js +0 -59
  117. package/src/capabilities/tests/scripts/qunit.html +0 -22
  118. package/src/capabilities/tests/scripts/testsuite.html +0 -10
  119. package/src/capabilities/tests/scripts/testsuite.js +0 -8
  120. package/src/capabilities/tests/timeout/index.html +0 -21
  121. package/src/capabilities/tests/timeout/index.js +0 -19
  122. package/src/capabilities/tests/traces/index.html +0 -18
  123. package/src/capabilities/tests/traces/index.js +0 -81
  124. package/src/capabilities/tests/ui5/focus.html +0 -89
  125. package/src/capabilities/tests/ui5/index.js +0 -39
  126. package/src/capabilities/tests/ui5/language.html +0 -50
  127. package/src/capabilities/tests/ui5/timezone.html +0 -27
  128. package/src/clean.js +0 -22
  129. package/src/cors.js +0 -21
  130. package/src/coverage.js +0 -384
  131. package/src/csv-reader.js +0 -36
  132. package/src/csv-writer.js +0 -55
  133. package/src/defaults/.nycrc.json +0 -4
  134. package/src/defaults/browser.js +0 -217
  135. package/src/defaults/happy-dom.js +0 -123
  136. package/src/defaults/jsdom/compatibility.js +0 -163
  137. package/src/defaults/jsdom/debug.js +0 -23
  138. package/src/defaults/jsdom/resource-loader.js +0 -44
  139. package/src/defaults/jsdom/sap.ui.test.matchers.visible.js +0 -39
  140. package/src/defaults/jsdom.js +0 -95
  141. package/src/defaults/json-report.js +0 -36
  142. package/src/defaults/junit-xml-report.js +0 -90
  143. package/src/defaults/playwright.js +0 -142
  144. package/src/defaults/puppeteer.js +0 -124
  145. package/src/defaults/report/common.js +0 -38
  146. package/src/defaults/report/decompress.js +0 -19
  147. package/src/defaults/report/default.html +0 -99
  148. package/src/defaults/report/main.js +0 -69
  149. package/src/defaults/report/progress.js +0 -60
  150. package/src/defaults/report/styles.css +0 -66
  151. package/src/defaults/report.js +0 -91
  152. package/src/defaults/scan-ui5.js +0 -26
  153. package/src/defaults/selenium-webdriver/chrome.js +0 -39
  154. package/src/defaults/selenium-webdriver/edge.js +0 -24
  155. package/src/defaults/selenium-webdriver/firefox.js +0 -30
  156. package/src/defaults/selenium-webdriver.js +0 -129
  157. package/src/defaults/text-report.js +0 -108
  158. package/src/defaults/webdriverio.js +0 -80
  159. package/src/end.js +0 -62
  160. package/src/endpoints.js +0 -219
  161. package/src/error.js +0 -54
  162. package/src/get-job-progress.js +0 -78
  163. package/src/handle.js +0 -43
  164. package/src/if.js +0 -10
  165. package/src/inject/jest2qunit.js +0 -289
  166. package/src/inject/opa-iframe-coverage.js +0 -22
  167. package/src/inject/post.js +0 -141
  168. package/src/inject/qunit-hooks.js +0 -107
  169. package/src/inject/qunit-redirect.js +0 -65
  170. package/src/inject/ui5-coverage.js +0 -33
  171. package/src/job-mode.js +0 -65
  172. package/src/job.js +0 -493
  173. package/src/npm.js +0 -136
  174. package/src/options.js +0 -95
  175. package/src/output.js +0 -739
  176. package/src/parallelize.js +0 -63
  177. package/src/qunit-hooks.js +0 -219
  178. package/src/report.js +0 -89
  179. package/src/reserve.js +0 -25
  180. package/src/start.js +0 -129
  181. package/src/symbols.js +0 -8
  182. package/src/tests.js +0 -183
  183. package/src/timeout.js +0 -53
  184. package/src/tools.js +0 -179
  185. package/src/ui5.js +0 -199
  186. package/src/unhandled.js +0 -32
@@ -0,0 +1,5 @@
1
+ import { join, isAbsolute } from 'node:path';
2
+ export class Path {
3
+ static isAbsolute = isAbsolute;
4
+ static join = join;
5
+ }
@@ -0,0 +1,133 @@
1
+ import { Host } from './Host.js';
2
+ import { spawn } from 'node:child_process';
3
+ import { logger } from './logger.js';
4
+ import { Exit } from './Exit.js';
5
+ class ProcessStopper {
6
+ _process;
7
+ set process(value) {
8
+ this._process = value;
9
+ }
10
+ async stop() {
11
+ await this._process?.kill();
12
+ return this._process?.closed;
13
+ }
14
+ }
15
+ export class Process {
16
+ static spawn = (command, arguments_, options = {}) => {
17
+ const finalCommand = command === 'node' ? process.argv[0] : command;
18
+ let asyncTask;
19
+ try {
20
+ const stopper = new ProcessStopper();
21
+ asyncTask = Exit.registerAsyncTask({
22
+ name: `Process.spawn(${command},${arguments_.join(',')})`,
23
+ stop: () => stopper.stop()
24
+ });
25
+ const childProcess = spawn(finalCommand, arguments_, options);
26
+ logger.debug({
27
+ source: 'process',
28
+ processId: childProcess.pid,
29
+ message: 'spawned',
30
+ data: { command, arguments: arguments_, options }
31
+ });
32
+ const process = new Process(childProcess, asyncTask);
33
+ stopper.process = process;
34
+ return process;
35
+ }
36
+ catch (error) {
37
+ logger.error({
38
+ source: 'process',
39
+ message: 'spawn failed',
40
+ data: { command, arguments: arguments_, options },
41
+ error
42
+ });
43
+ asyncTask?.[Symbol.dispose]();
44
+ throw error;
45
+ }
46
+ };
47
+ _stdout = [];
48
+ get stdout() {
49
+ return this._stdout.join('');
50
+ }
51
+ _stderr = [];
52
+ get stderr() {
53
+ return this._stderr.join('');
54
+ }
55
+ _code;
56
+ get code() {
57
+ return this._code;
58
+ }
59
+ _closed;
60
+ get closed() {
61
+ return this._closed;
62
+ }
63
+ _childProcess;
64
+ _asyncTask;
65
+ constructor(childProcess, asyncTask) {
66
+ this._childProcess = childProcess;
67
+ this._asyncTask = asyncTask;
68
+ const { promise, resolve } = Promise.withResolvers();
69
+ this._closed = promise;
70
+ this._childProcess.stdout?.on('data', (buffer) => {
71
+ const message = buffer.toString();
72
+ logger.debug({ source: 'process', processId: this.pid, message, data: { type: 'stdout' } });
73
+ this._stdout.push(message);
74
+ });
75
+ this._childProcess.stderr?.on('data', (buffer) => {
76
+ const message = buffer.toString();
77
+ logger.debug({ source: 'process', processId: this.pid, message, data: { type: 'stderr' } });
78
+ this._stderr.push(message);
79
+ });
80
+ this._childProcess.on('close', (code) => {
81
+ this._code = code ?? 0;
82
+ logger.debug({
83
+ source: 'process',
84
+ processId: this.pid,
85
+ message: 'closed',
86
+ data: { code: this._code }
87
+ });
88
+ this._asyncTask[Symbol.dispose]();
89
+ resolve();
90
+ });
91
+ }
92
+ get pid() {
93
+ return this._childProcess.pid;
94
+ }
95
+ async kill() {
96
+ logger.debug({
97
+ source: 'process',
98
+ processId: this.pid,
99
+ message: 'kill'
100
+ });
101
+ try {
102
+ if (Host.platform() === 'win32') {
103
+ const killProcess = spawn('taskkill', ['/F', '/T', '/PID', this.pid.toString()], {
104
+ windowsHide: true
105
+ });
106
+ const { promise, resolve } = Promise.withResolvers();
107
+ killProcess.on('close', () => setTimeout(resolve, 0));
108
+ await promise;
109
+ }
110
+ else {
111
+ try {
112
+ process.kill(-this.pid);
113
+ }
114
+ catch {
115
+ process.kill(this.pid);
116
+ }
117
+ }
118
+ logger.debug({
119
+ source: 'process',
120
+ processId: this.pid,
121
+ message: 'killed'
122
+ });
123
+ }
124
+ catch (error) {
125
+ logger.debug({
126
+ source: 'process',
127
+ processId: this.pid,
128
+ message: 'unable to kill',
129
+ error
130
+ });
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,47 @@
1
+ import { stripVTControlCharacters } from 'node:util';
2
+ const ASCII_ESCAPE = '\u001B';
3
+ const CSI = `${ASCII_ESCAPE}[`;
4
+ export class Terminal {
5
+ static isTTY = process.stdout.isTTY;
6
+ static onResize(callback) {
7
+ process.stdout.on('resize', () => callback(Terminal.width));
8
+ }
9
+ static setRawMode(callback) {
10
+ if (callback === false) {
11
+ process.stdin.setRawMode(false);
12
+ process.stdin.pause();
13
+ }
14
+ else {
15
+ process.stdin.setRawMode(true);
16
+ process.stdin.on('data', callback);
17
+ }
18
+ }
19
+ static stripVTControlCharacters = stripVTControlCharacters;
20
+ static get width() {
21
+ return process.stdout.columns;
22
+ }
23
+ static write(text) {
24
+ process.stdout.write(text);
25
+ }
26
+ static hideCursor() {
27
+ Terminal.write(`${CSI}?25l`);
28
+ }
29
+ static showCursor() {
30
+ Terminal.write(`${CSI}?25h`);
31
+ }
32
+ static eraseToEnd(lines) {
33
+ if (lines === 0) {
34
+ Terminal.write(`${CSI}0G`);
35
+ }
36
+ else {
37
+ Terminal.write(`${CSI}0G${CSI}${lines}A${CSI}0J`);
38
+ }
39
+ }
40
+ static BLUE = `${CSI}34m`;
41
+ static CYAN = `${CSI}36m`;
42
+ static GREEN = `${CSI}32m`;
43
+ static MAGENTA = `${CSI}35m`;
44
+ static RED = `${CSI}31m`;
45
+ static WHITE = `${CSI}37m`;
46
+ static YELLOW = `${CSI}33m`;
47
+ }
@@ -0,0 +1,43 @@
1
+ import { extname } from 'node:path';
2
+ import { Path } from './Path.js';
3
+ import { BroadcastChannel, Worker, isMainThread, threadId } from 'node:worker_threads';
4
+ import { __sourcesRoot } from './constants.js';
5
+ import { logger } from './logger/proxy.js';
6
+ export class Thread {
7
+ static threadCpuUsage = process.threadCpuUsage.bind(process);
8
+ static createBroadcastChannel = (name) => new BroadcastChannel(name);
9
+ static createWorker = (name, data) => {
10
+ const extension = extname(import.meta.url);
11
+ const workerPath = '../' + name + extension;
12
+ if (process.env['NO_WORKERS']) {
13
+ logger?.debug({ source: 'thread', message: `Creating fiber for ${name}`, data });
14
+ void (async () => {
15
+ const { workerMain } = (await import(workerPath));
16
+ workerMain(data);
17
+ })();
18
+ return {
19
+ on: (event, callback) => {
20
+ if (event === 'exit') {
21
+ callback(0);
22
+ }
23
+ }
24
+ };
25
+ }
26
+ logger?.debug({ source: 'thread', message: `Creating worker for ${name}`, data });
27
+ const bootstrapPath = Path.join(__sourcesRoot, 'platform/workerBootstrap' + extension);
28
+ const js2tsUrl = new URL('js2ts.mjs', import.meta.url).toString();
29
+ const execArgv = extension === '.ts' ? ['--no-warnings', '--import', js2tsUrl] : [];
30
+ const worker = new Worker(bootstrapPath, {
31
+ execArgv,
32
+ workerData: {
33
+ path: workerPath,
34
+ data
35
+ }
36
+ });
37
+ worker.on('online', () => logger?.debug({ source: 'thread', message: `Worker for ${name} online`, data: { threadId: worker.threadId } }));
38
+ worker.on('exit', () => logger?.debug({ source: 'thread', message: `Worker for ${name} offline`, data: { threadId: worker.threadId } }));
39
+ return worker;
40
+ };
41
+ static isMainThread = isMainThread;
42
+ static threadId = threadId;
43
+ }
@@ -0,0 +1,7 @@
1
+ import zlib from 'node:zlib';
2
+ export class ZLib {
3
+ static constants = zlib.constants;
4
+ static gzipSync = zlib.gzipSync.bind(zlib);
5
+ static deflateRawSync = zlib.deflateRawSync.bind(zlib);
6
+ static inflateRawSync = zlib.inflateRawSync.bind(zlib);
7
+ }
@@ -0,0 +1,17 @@
1
+ import { AssertionError } from 'node:assert/strict';
2
+ import { __developmentMode } from './constants.js';
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;
16
+ }
17
+ };
@@ -0,0 +1,5 @@
1
+ import { dirname } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ const __filename = fileURLToPath(import.meta.url);
4
+ export const __sourcesRoot = dirname(dirname(__filename));
5
+ export const __developmentMode = __filename.endsWith('.ts');
@@ -0,0 +1,28 @@
1
+ import { Host } from './Host.js';
2
+ import { logger } from './logger.js';
3
+ import { version } from './version.js';
4
+ export const logEnvironnement = async () => {
5
+ const runnerVersion = await version();
6
+ const now = new Date();
7
+ logger.info({
8
+ source: 'job',
9
+ message: `${runnerVersion} / Node.js ${Host.nodeVersion} / ${now.toISOString()} (${now.getTimezoneOffset()})`
10
+ });
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
+ }
27
+ }
28
+ };
@@ -0,0 +1,13 @@
1
+ export * from './assert.js';
2
+ export * from './constants.js';
3
+ export * from './environment.js';
4
+ export * from './logger.js';
5
+ export * from './Exit.js';
6
+ export * from './FileSystem.js';
7
+ export * from './Host.js';
8
+ export * from './Http.js';
9
+ export * from './Path.js';
10
+ export * from './Process.js';
11
+ export * from './Terminal.js';
12
+ export * from './Thread.js';
13
+ export * from './ZLib.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,54 @@
1
+ import { Path, Thread, ZLib } from '../index.js';
2
+ import { LogLevel } from './types.js';
3
+ import { toInternalLogAttributes } from './toInternalLogAttributes.js';
4
+ import { createCompressionContext, compress } from './compress.js';
5
+ import { FramedStreamWriter } from '../../utils/node/FramedStreamWriter.js';
6
+ export const MAX_BUFFER_COUNT = 50;
7
+ const FLUSH_INTERVAL_MS = 200;
8
+ const compressionContext = createCompressionContext();
9
+ export const workerMain = ({ configuration }) => {
10
+ const LOG_FILE_NAME = `traces-${new Date().toISOString().slice(0, 19).replaceAll(/[-:]/g, '').replace('T', '-')}.logz`;
11
+ const stream = FramedStreamWriter.create(Path.join(configuration.reportDir, LOG_FILE_NAME));
12
+ const buffer = [];
13
+ let flushTimeout;
14
+ let writePromise = Promise.resolve();
15
+ const flushBuffer = () => {
16
+ if (buffer.length === 0) {
17
+ return;
18
+ }
19
+ const chunk = buffer.join('\n');
20
+ buffer.length = 0;
21
+ clearTimeout(flushTimeout);
22
+ flushTimeout = undefined;
23
+ const compressed = ZLib.deflateRawSync(chunk, { level: ZLib.constants.Z_BEST_COMPRESSION });
24
+ writePromise = writePromise.then(() => stream.write(compressed));
25
+ };
26
+ const log = (attributes) => {
27
+ buffer.push(compress(compressionContext, attributes));
28
+ if (buffer.length >= MAX_BUFFER_COUNT) {
29
+ flushBuffer();
30
+ }
31
+ else if (!flushTimeout) {
32
+ flushTimeout = setTimeout(flushBuffer, FLUSH_INTERVAL_MS);
33
+ }
34
+ };
35
+ const _log = (attributes) => log(toInternalLogAttributes(attributes, LogLevel.info));
36
+ const channel = Thread.createBroadcastChannel('logger');
37
+ channel.onmessage = (event) => {
38
+ const { data: message } = event;
39
+ if (message.command === 'terminate') {
40
+ _log({ source: 'logger', message: 'Logger terminating' });
41
+ channel.close();
42
+ flushBuffer();
43
+ void writePromise.then(() => stream.end());
44
+ }
45
+ else if (message.command === 'log') {
46
+ log(message);
47
+ }
48
+ };
49
+ _log({ source: 'logger', message: 'Logger ready' });
50
+ channel.postMessage({
51
+ command: 'ready',
52
+ source: 'allCompressed'
53
+ });
54
+ };
@@ -0,0 +1,277 @@
1
+ import { LogLevel } from './types.js';
2
+ import { isDeepStrictEqual } from 'node:util';
3
+ import assert from 'node:assert/strict';
4
+ import { split } from '../../utils/shared/string.js';
5
+ const ASCII_RECORD_SEPARATOR = '\u001E';
6
+ export const DIGITS = Array.from({ length: 127 - 32 })
7
+ .fill(0)
8
+ .map((_, index) => String.fromCodePoint(32 + index))
9
+ .join('');
10
+ const JSON_VALUE_SEP = ASCII_RECORD_SEPARATOR;
11
+ const MAX_INDEX_DIGITS = 2;
12
+ const CONTEXT_PROCESS_ID = 'p';
13
+ const CONTEXT_SOURCE_ID = 's';
14
+ export const MAX_TIMESTAMP_DIGITS = 7;
15
+ export const MAX_DWORD_DIGITS = 5;
16
+ class Context {
17
+ static compressNumber(value, maxLength) {
18
+ const digits = [];
19
+ while (value > 0) {
20
+ const digit = value % DIGITS.length;
21
+ digits.push(DIGITS[digit]);
22
+ value = (value - digit) / DIGITS.length;
23
+ }
24
+ return digits.join('').padEnd(maxLength, DIGITS[0]);
25
+ }
26
+ static uncompressNumber(value) {
27
+ let result = 0;
28
+ let factor = 1;
29
+ for (const digit of value) {
30
+ const index = DIGITS.indexOf(digit);
31
+ result += index * factor;
32
+ factor *= DIGITS.length;
33
+ }
34
+ return result;
35
+ }
36
+ fail(message) {
37
+ throw new Error(message);
38
+ }
39
+ _compressWithList({ array, value, compress }) {
40
+ const index = array.findIndex((candidate) => isDeepStrictEqual(candidate, value));
41
+ if (index !== -1) {
42
+ const compressed = Context.compressNumber(index, MAX_INDEX_DIGITS);
43
+ return {
44
+ context: '',
45
+ compressed
46
+ };
47
+ }
48
+ const lastIndex = array.length;
49
+ array.push(value);
50
+ return {
51
+ context: compress(value).join(''),
52
+ compressed: Context.compressNumber(lastIndex, MAX_INDEX_DIGITS)
53
+ };
54
+ }
55
+ _uncompressFromList({ type, array, compressed }) {
56
+ const index = Context.uncompressNumber(compressed);
57
+ return array[index] ?? this.fail(`Invalid ${type} index ${index} (length: ${array.length})`);
58
+ }
59
+ _processes = [];
60
+ compressProcess(value) {
61
+ return this._compressWithList({
62
+ array: this._processes,
63
+ value,
64
+ compress: ({ processId, threadId, isMainThread }) => {
65
+ if (threadId === -1) {
66
+ return [CONTEXT_PROCESS_ID, Context.compressNumber(processId, MAX_DWORD_DIGITS)];
67
+ }
68
+ return [
69
+ CONTEXT_PROCESS_ID,
70
+ Context.compressNumber(processId, MAX_DWORD_DIGITS),
71
+ Context.compressNumber(threadId, MAX_DWORD_DIGITS),
72
+ isMainThread ? '!' : ''
73
+ ];
74
+ }
75
+ });
76
+ }
77
+ addProcess(compressed) {
78
+ const [, cProcessId, cThreadId, cIsMainThread] = split(compressed, 1, MAX_DWORD_DIGITS, MAX_DWORD_DIGITS, 1);
79
+ if (cThreadId) {
80
+ this._processes.push({
81
+ processId: Context.uncompressNumber(cProcessId),
82
+ threadId: Context.uncompressNumber(cThreadId),
83
+ isMainThread: cIsMainThread === '!'
84
+ });
85
+ }
86
+ else {
87
+ this._processes.push({
88
+ processId: Context.uncompressNumber(cProcessId),
89
+ threadId: -1,
90
+ isMainThread: false
91
+ });
92
+ }
93
+ }
94
+ uncompressProcess(compressed) {
95
+ return this._uncompressFromList({
96
+ type: 'process',
97
+ array: this._processes,
98
+ compressed
99
+ });
100
+ }
101
+ _sources = [];
102
+ compressSource(value) {
103
+ return this._compressWithList({
104
+ array: this._sources,
105
+ value,
106
+ compress: (value) => [CONTEXT_SOURCE_ID, value]
107
+ });
108
+ }
109
+ addSource(compressed) {
110
+ this._sources.push(compressed.slice(1));
111
+ }
112
+ uncompressSource(compressed) {
113
+ return this._uncompressFromList({
114
+ type: 'source',
115
+ array: this._sources,
116
+ compressed
117
+ });
118
+ }
119
+ }
120
+ export const createCompressionContext = () => new Context();
121
+ const LEVEL_MAPPING = {
122
+ [LogLevel.debug]: 'D',
123
+ [LogLevel.info]: 'I',
124
+ [LogLevel.warn]: 'W',
125
+ [LogLevel.error]: 'E',
126
+ [LogLevel.fatal]: 'F'
127
+ };
128
+ const LEVELS = Object.values(LEVEL_MAPPING).join('');
129
+ const levelSlot = {
130
+ width: 1,
131
+ compress(_, { level }) {
132
+ return { compressed: LEVEL_MAPPING[level] };
133
+ },
134
+ uncompress(_, compressed) {
135
+ return { level: LEVELS.indexOf(compressed) };
136
+ }
137
+ };
138
+ const timestampSlot = {
139
+ width: MAX_TIMESTAMP_DIGITS,
140
+ compress(_, { timestamp }) {
141
+ return { compressed: Context.compressNumber(timestamp, MAX_TIMESTAMP_DIGITS) };
142
+ },
143
+ uncompress(_, compressed) {
144
+ return { timestamp: Context.uncompressNumber(compressed) };
145
+ }
146
+ };
147
+ const processSlot = {
148
+ width: MAX_INDEX_DIGITS,
149
+ compress(context, { processId, threadId, isMainThread }) {
150
+ const { context: contextLine, compressed } = context.compressProcess({ processId, threadId, isMainThread });
151
+ return { contextLine: contextLine || undefined, compressed };
152
+ },
153
+ uncompress(context, compressed) {
154
+ return context.uncompressProcess(compressed);
155
+ }
156
+ };
157
+ const sourceSlot = {
158
+ width: MAX_INDEX_DIGITS,
159
+ compress(context, { source }) {
160
+ const { context: contextLine, compressed } = context.compressSource(source);
161
+ return { contextLine: contextLine || undefined, compressed };
162
+ },
163
+ uncompress(context, compressed) {
164
+ return { source: context.uncompressSource(compressed) };
165
+ }
166
+ };
167
+ const pageIdSlot = {
168
+ width: MAX_INDEX_DIGITS,
169
+ compress(_, { pageId }) {
170
+ return { compressed: Context.compressNumber(pageId === undefined ? 0 : pageId + 1, MAX_INDEX_DIGITS) };
171
+ },
172
+ uncompress(_, compressed) {
173
+ const value = Context.uncompressNumber(compressed);
174
+ return value > 0 ? { pageId: value - 1 } : {};
175
+ }
176
+ };
177
+ const messageAndExtraSlot = {
178
+ width: 0,
179
+ compress(_, { message, data, error }) {
180
+ const parts = [message.replaceAll(/\r?\n/g, '\r')];
181
+ if (data || error) {
182
+ parts.push(JSON_VALUE_SEP, JSON.stringify([data ?? 0, error ?? 0]).replaceAll(/"(\w+)":/g, (_, name) => `${name}${JSON_VALUE_SEP}`));
183
+ }
184
+ return { compressed: parts.join('') };
185
+ },
186
+ uncompress(_, compressed) {
187
+ const startOfJson = compressed.indexOf(JSON_VALUE_SEP);
188
+ if (startOfJson === -1) {
189
+ return { message: compressed.replaceAll('\r', '\n') };
190
+ }
191
+ const message = compressed.slice(0, startOfJson).replaceAll('\r', '\n');
192
+ const json = compressed
193
+ .slice(startOfJson + 1)
194
+ .replaceAll(new RegExp(String.raw `(\w+)${JSON_VALUE_SEP}`, 'g'), (_, name) => `"${name}":`);
195
+ const [data, error] = JSON.parse(json);
196
+ return {
197
+ message,
198
+ ...(data ? { data } : {}),
199
+ ...(error ? { error } : {})
200
+ };
201
+ }
202
+ };
203
+ const DATA_LINE_SLOTS = [
204
+ levelSlot,
205
+ timestampSlot,
206
+ processSlot,
207
+ sourceSlot,
208
+ pageIdSlot,
209
+ messageAndExtraSlot
210
+ ];
211
+ const FIXED_SLOTS = DATA_LINE_SLOTS.filter((s) => s.width > 0);
212
+ const FIXED_SLOT_WIDTHS = FIXED_SLOTS.map((s) => s.width);
213
+ const VARIABLE_SLOTS = DATA_LINE_SLOTS.filter((s) => s.width === 0);
214
+ assert.ok(VARIABLE_SLOTS.length === 1, 'DATA_LINE_SLOTS must contain exactly one variable-width slot');
215
+ const VARIABLE_SLOT = VARIABLE_SLOTS[0];
216
+ export const _ALL_LOG_ATTRIBUTES_ARE_HANDLED = {
217
+ level: levelSlot,
218
+ timestamp: timestampSlot,
219
+ processId: processSlot,
220
+ threadId: processSlot,
221
+ isMainThread: processSlot,
222
+ source: sourceSlot,
223
+ pageId: pageIdSlot,
224
+ message: messageAndExtraSlot,
225
+ data: messageAndExtraSlot,
226
+ error: messageAndExtraSlot
227
+ };
228
+ export const compress = (context, attributes) => {
229
+ assert.ok(context instanceof Context);
230
+ const contextLines = [];
231
+ const parts = [];
232
+ for (const slot of DATA_LINE_SLOTS) {
233
+ const { contextLine, compressed } = slot.compress(context, attributes);
234
+ if (contextLine)
235
+ contextLines.push(contextLine);
236
+ parts.push(compressed);
237
+ }
238
+ return [...contextLines, parts.join('')].join('\n') + '\n';
239
+ };
240
+ const augmentContext = (context, line) => {
241
+ const firstChar = line.charAt(0);
242
+ if (firstChar === CONTEXT_PROCESS_ID) {
243
+ context.addProcess(line);
244
+ }
245
+ else {
246
+ assert.ok(firstChar === CONTEXT_SOURCE_ID, `unexpected context operator ${firstChar}`);
247
+ context.addSource(line);
248
+ }
249
+ };
250
+ const uncompressLine = (context, line) => {
251
+ const firstChar = line.charAt(0);
252
+ if (!LEVELS.includes(firstChar)) {
253
+ augmentContext(context, line);
254
+ return;
255
+ }
256
+ const parts = split(line, ...FIXED_SLOT_WIDTHS);
257
+ const attributes = {};
258
+ for (const [index, fixedSlot] of FIXED_SLOTS.entries()) {
259
+ Object.assign(attributes, fixedSlot.uncompress(context, parts[index]));
260
+ }
261
+ Object.assign(attributes, VARIABLE_SLOT.uncompress(context, parts[FIXED_SLOTS.length]));
262
+ return attributes;
263
+ };
264
+ export const uncompress = (context, compressed) => {
265
+ assert.ok(context instanceof Context);
266
+ const result = [];
267
+ for (const line of compressed.split('\n')) {
268
+ if (!line) {
269
+ continue;
270
+ }
271
+ const attributes = uncompressLine(context, line);
272
+ if (attributes) {
273
+ result.push(attributes);
274
+ }
275
+ }
276
+ return result;
277
+ };