@teamscale/coverage-collector 0.0.1-beta.5 → 0.0.1-beta.51

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/README.md CHANGED
@@ -9,7 +9,7 @@ files in the [Teamscale Simple Coverage Format](https://docs.teamscale.com/refer
9
9
  The Teamscale JavaScript Profiler consists of this Coverage Collector and the
10
10
  [JavaScript Instrumenter](https://www.npmjs.com/package/@teamscale/javascript-instrumenter).
11
11
  More details on using them (in combination) can be found
12
- on the projects' [Github page](https://github.com/cqse/teamscale-javascript-profiler/).
12
+ in the [Teamscale Documentation](https://docs.teamscale.com/howto/recording-test-coverage-for-javascript/).
13
13
 
14
14
  The JavaScript Coverage Collector starts a server process that listens for
15
15
  code coverage information from manually or automatically exercised (tested)
@@ -49,8 +49,3 @@ for example, by setting a corresponding environment variable.
49
49
  export NODE_OPTIONS="$NODE_OPTIONS --max-old-space-size=8192"
50
50
  ```
51
51
 
52
- ## Publishing
53
-
54
- The list of files to publish is defined by the `files` attribute in `package.json`.
55
- The actual files packed by npm can be listed by running `npx npm-packlist`.
56
-
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamscale/coverage-collector",
3
- "version": "0.0.1-beta.5",
3
+ "version": "0.0.1-beta.51",
4
4
  "description": "Collector for JavaScript code coverage information",
5
5
  "main": "dist/src/main.js",
6
6
  "bin": "dist/src/main.js",
@@ -12,22 +12,30 @@
12
12
  "url": "https://github.com/cqse/teamscale-javascript-profiler.git"
13
13
  },
14
14
  "scripts": {
15
+ "prepublishOnly": "yarn clean && yarn build",
15
16
  "clean": "rimraf dist tsconfig.tsbuildinfo",
16
17
  "build": "tsc",
17
18
  "collector": "node dist/src/main.js",
18
- "test": "yarn build && NODE_OPTIONS='--experimental-vm-modules' jest --forceExit --coverage --silent=true --detectOpenHandles"
19
+ "test": "yarn build && NODE_OPTIONS='--experimental-vm-modules' jest --coverage --silent=true"
19
20
  },
20
21
  "files": [
21
22
  "dist/**/*"
22
23
  ],
23
24
  "dependencies": {
24
- "@cqse/commons": "^0.0.1-beta.1",
25
+ "@cqse/commons": "^0.0.1-beta.45",
25
26
  "argparse": "^2.0.1",
26
- "async": "^3.2.0",
27
+ "async": "^3.2.4",
28
+ "axios": "^0.24.0",
29
+ "bunyan": "^1.8.15",
30
+ "date-and-time": "^2.3.1",
31
+ "dotenv": "^14.1.0",
32
+ "express": "^4.18.1",
33
+ "form-data": "^4.0.0",
34
+ "mkdirp": "^1.0.4",
27
35
  "rxjs": "^7.1.0",
28
- "source-map": "^0.7.3",
36
+ "source-map": "^0.7.4",
37
+ "tmp": "^0.2.1",
29
38
  "typescript-optional": "^2.0.1",
30
- "winston": "^3.3.3",
31
39
  "ws": "^7.4.5"
32
40
  },
33
41
  "devDependencies": {
@@ -35,14 +43,18 @@
35
43
  "@babel/preset-env": "^7.14.1",
36
44
  "@types/argparse": "^2.0.5",
37
45
  "@types/async": "^3.2.6",
46
+ "@types/bunyan": "^1.8.8",
47
+ "@types/express": "^4.17.13",
38
48
  "@types/jest": "^27.0.1",
49
+ "@types/mkdirp": "^1.0.2",
39
50
  "@types/node": "^15.0.1",
40
51
  "@types/source-map": "^0.5.7",
41
- "@types/winston": "^2.4.4",
52
+ "@types/tmp": "^0.2.3",
42
53
  "@types/ws": "^7.4.2",
43
54
  "babel-jest": "^27.2.0",
44
55
  "esbuild": "^0.13.4",
45
56
  "jest": "^27.2.0",
57
+ "mockttp": "^3.4.0",
46
58
  "rimraf": "^3.0.2",
47
59
  "ts-jest": "^27.0.5",
48
60
  "ts-node": "^10.2.1",
@@ -0,0 +1,39 @@
1
+ import 'dotenv/config';
2
+ import { ConfigParameters } from './utils/ConfigParameters';
3
+ /**
4
+ * The main class of the Teamscale JavaScript Collector.
5
+ * Used to start the collector with a given configuration.
6
+ */
7
+ export declare class App {
8
+ /**
9
+ * Parse the given command line arguments into a corresponding options object.
10
+ */
11
+ private static parseArguments;
12
+ /**
13
+ * Construct the logger.
14
+ */
15
+ private static buildLogger;
16
+ /**
17
+ * Entry point of the Teamscale JavaScript Profiler.
18
+ */
19
+ static run(): void;
20
+ /**
21
+ * Run the collector with the given configuration options.
22
+ *
23
+ * @param config - The configuration options to run the collector with.
24
+ */
25
+ static runWithConfig(config: ConfigParameters): {
26
+ stop: () => Promise<void>;
27
+ };
28
+ /**
29
+ * Start a timer for dumping the data, depending on the configuration.
30
+ *
31
+ * @param config - The config that determines whether to do the timed dump or not.
32
+ * @param storage - The storage with the information to dump.
33
+ * @param logger - The logger to use.
34
+ */
35
+ private static maybeStartDumpTimer;
36
+ private static dumpCoverage;
37
+ private static uploadCoverage;
38
+ private static startControlServer;
39
+ }
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.App = void 0;
30
+ const bunyan_1 = __importDefault(require("bunyan"));
31
+ const DataStorage_1 = require("./storage/DataStorage");
32
+ const CollectingServer_1 = require("./receiver/CollectingServer");
33
+ require("dotenv/config");
34
+ const fs = __importStar(require("fs"));
35
+ const ConfigParameters_1 = require("./utils/ConfigParameters");
36
+ const mkdirp_1 = __importDefault(require("mkdirp"));
37
+ const path_1 = __importDefault(require("path"));
38
+ const StdConsoleLogger_1 = require("./utils/StdConsoleLogger");
39
+ const PrettyFileLogger_1 = require("./utils/PrettyFileLogger");
40
+ const express_1 = __importDefault(require("express"));
41
+ const TeamscaleUpload_1 = require("./upload/TeamscaleUpload");
42
+ const CommonUpload_1 = require("./upload/CommonUpload");
43
+ const ArtifactoryUpload_1 = require("./upload/ArtifactoryUpload");
44
+ /**
45
+ * The main class of the Teamscale JavaScript Collector.
46
+ * Used to start the collector with a given configuration.
47
+ */
48
+ class App {
49
+ /**
50
+ * Parse the given command line arguments into a corresponding options object.
51
+ */
52
+ static parseArguments() {
53
+ const parser = (0, ConfigParameters_1.buildParameterParser)();
54
+ return parser.parse_args();
55
+ }
56
+ /**
57
+ * Construct the logger.
58
+ */
59
+ static buildLogger(config) {
60
+ const logfilePath = config.log_to_file.trim();
61
+ mkdirp_1.default.sync(path_1.default.dirname(logfilePath));
62
+ const logLevel = config.log_level;
63
+ const logger = bunyan_1.default.createLogger({
64
+ name: 'Collector',
65
+ streams: [
66
+ // console output
67
+ { level: logLevel, stream: new StdConsoleLogger_1.StdConsoleLogger(), type: 'raw' },
68
+ // default log file
69
+ { level: logLevel, stream: new PrettyFileLogger_1.PrettyFileLogger(fs.createWriteStream(logfilePath)), type: 'raw' }
70
+ ]
71
+ });
72
+ // If the given flag is set, we also log with a JSON-like format
73
+ if (config.json_log) {
74
+ logger.addStream({ level: logLevel, path: `${logfilePath}.json` });
75
+ }
76
+ return logger;
77
+ }
78
+ /**
79
+ * Entry point of the Teamscale JavaScript Profiler.
80
+ */
81
+ static run() {
82
+ // Parse the command line arguments
83
+ const config = this.parseArguments();
84
+ App.runWithConfig(config);
85
+ }
86
+ /**
87
+ * Run the collector with the given configuration options.
88
+ *
89
+ * @param config - The configuration options to run the collector with.
90
+ */
91
+ static runWithConfig(config) {
92
+ // Build the logger
93
+ const logger = this.buildLogger(config);
94
+ logger.info(`Starting collector in working directory "${process.cwd()}".`);
95
+ logger.info(`Logging "${config.log_level}" to "${config.log_to_file}".`);
96
+ // Prepare the storage and the server
97
+ const storage = new DataStorage_1.DataStorage(logger);
98
+ const server = new CollectingServer_1.WebSocketCollectingServer(config.port, storage, logger);
99
+ // Enable the remote control API if configured
100
+ const controlServerState = this.startControlServer(config, storage, logger);
101
+ // Start the server socket.
102
+ // ATTENTION: The server is executed asynchronously
103
+ const serverState = server.start();
104
+ // Optionally, start a timer that dumps the coverage after a N seconds
105
+ const timerState = this.maybeStartDumpTimer(config, storage, logger);
106
+ // Say bye bye on CTRL+C and exit the process
107
+ process.on('SIGINT', async () => {
108
+ // ... and do a final dump before.
109
+ await this.dumpCoverage(config, storage, logger);
110
+ logger.info('Bye bye.');
111
+ process.exit();
112
+ });
113
+ return {
114
+ async stop() {
115
+ logger.info('Stopping the collector.');
116
+ timerState.stop();
117
+ await controlServerState.stop();
118
+ serverState.stop();
119
+ }
120
+ };
121
+ }
122
+ /**
123
+ * Start a timer for dumping the data, depending on the configuration.
124
+ *
125
+ * @param config - The config that determines whether to do the timed dump or not.
126
+ * @param storage - The storage with the information to dump.
127
+ * @param logger - The logger to use.
128
+ */
129
+ static maybeStartDumpTimer(config, storage, logger) {
130
+ if (config.dump_after_mins > 0) {
131
+ logger.info(`Will dump coverage information every ${config.dump_after_mins} minute(s).`);
132
+ const timer = setInterval(async () => {
133
+ await this.dumpCoverage(config, storage, logger);
134
+ }, config.dump_after_mins * 1000 * 60);
135
+ process.on('SIGINT', () => {
136
+ // Stop the timed file dump
137
+ if (timer) {
138
+ clearInterval(timer);
139
+ }
140
+ });
141
+ return {
142
+ stop: () => clearInterval(timer)
143
+ };
144
+ }
145
+ return {
146
+ stop() {
147
+ // no timer to stop yet
148
+ }
149
+ };
150
+ }
151
+ static async dumpCoverage(config, storage, logger) {
152
+ try {
153
+ // 1. Write coverage to a file
154
+ const [coverageFile, lines] = storage.dumpToSimpleCoverageFile(config.dump_to_folder, new Date());
155
+ logger.info(`Dumped ${lines} lines of coverage to ${coverageFile}.`);
156
+ // 2. Upload to Teamscale or Artifactory if configured
157
+ if (config.teamscale_server_url || config.artifactory_server_url) {
158
+ await this.uploadCoverage(config, coverageFile, lines, logger);
159
+ }
160
+ }
161
+ catch (e) {
162
+ if (e instanceof CommonUpload_1.UploadError) {
163
+ logger.error(`Coverage upload failed. The coverage files on disk (inside the folder "${config.dump_to_folder}") were not deleted.
164
+ You can still upload them manually.`, e);
165
+ }
166
+ else {
167
+ logger.error('Coverage dump failed.', e);
168
+ }
169
+ }
170
+ }
171
+ static async uploadCoverage(config, coverageFile, lines, logger) {
172
+ if (config.teamscale_server_url) {
173
+ await (0, TeamscaleUpload_1.uploadToTeamscale)(config, logger, coverageFile, lines);
174
+ }
175
+ if (config.artifactory_server_url) {
176
+ await (0, ArtifactoryUpload_1.uploadToArtifactory)(config, logger, coverageFile, lines);
177
+ }
178
+ // Delete coverage if upload was successful and keeping coverage files on disk was not configure by the user
179
+ if (!config.keep_coverage_files) {
180
+ fs.unlinkSync(coverageFile);
181
+ }
182
+ }
183
+ static startControlServer(config, storage, logger) {
184
+ if (!config.enable_control_port) {
185
+ return {
186
+ async stop() {
187
+ // nothing to stop in this case
188
+ }
189
+ };
190
+ }
191
+ const controlServer = (0, express_1.default)();
192
+ controlServer.use(express_1.default.text({}));
193
+ const serverSocket = controlServer.listen(config.enable_control_port);
194
+ controlServer.put('/partition', (request, response) => {
195
+ const targetPartition = request.body.trim();
196
+ config.teamscale_partition = targetPartition;
197
+ logger.info(`Switched the target partition to '${targetPartition}' via the control API.`);
198
+ response.sendStatus(200);
199
+ });
200
+ controlServer.post('/dump', async (request, response) => {
201
+ logger.info('Dumping coverage requested via the control API.');
202
+ await this.dumpCoverage(config, storage, logger);
203
+ response.sendStatus(200);
204
+ });
205
+ controlServer.put('/project', async (request, response) => {
206
+ const targetProject = request.body.trim();
207
+ config.teamscale_project = targetProject;
208
+ logger.info(`Switching the target project to '${targetProject}' via the control API.`);
209
+ response.sendStatus(200);
210
+ });
211
+ controlServer.put('/revision', async (request, response) => {
212
+ const targetRevision = request.body.trim();
213
+ config.teamscale_revision = targetRevision;
214
+ logger.info(`Switching the target revision to '${targetRevision}' via the control API.`);
215
+ response.sendStatus(200);
216
+ });
217
+ controlServer.put('/commit', async (request, response) => {
218
+ const targetCommit = request.body.trim();
219
+ config.teamscale_commit = targetCommit;
220
+ logger.info(`Switching the target commit to '${targetCommit}' via the control API.`);
221
+ response.sendStatus(200);
222
+ });
223
+ controlServer.put('/message', async (request, response) => {
224
+ const uploadMessage = request.body.trim();
225
+ config.teamscale_message = uploadMessage;
226
+ logger.info(`Switching the upload message to '${uploadMessage}' via the control API.`);
227
+ response.sendStatus(200);
228
+ });
229
+ controlServer.post('/reset', async (request, response) => {
230
+ storage.discardCollectedCoverage();
231
+ logger.info(`Discarding collected coverage information as requested via the control API.`);
232
+ response.sendStatus(200);
233
+ });
234
+ logger.info(`Control server enabled at port ${config.enable_control_port}`);
235
+ return {
236
+ async stop() {
237
+ return new Promise(resolve => {
238
+ serverSocket.close(() => resolve());
239
+ });
240
+ }
241
+ };
242
+ }
243
+ }
244
+ exports.App = App;
@@ -1,31 +1,2 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * The main class of the Teamscale JavaScript Collector.
4
- * Used to start-up the collector for with a given configuration.
5
- */
6
- export declare class Main {
7
- /**
8
- * Construct the object for parsing the command line arguments.
9
- */
10
- private static buildParser;
11
- /**
12
- * Parse the given command line arguments into a corresponding options object.
13
- */
14
- private static parseArguments;
15
- /**
16
- * Construct the logger.
17
- */
18
- private static buildLogger;
19
- /**
20
- * Entry point of the Teamscale JavaScript Profiler.
21
- */
22
- static run(): void;
23
- /**
24
- * Start a timer for dumping the data, depending on the configuration.
25
- *
26
- * @param config - The config that determines whether or not to do the timed dump.
27
- * @param storage - The storage with the information to dump.
28
- * @param logger - The logger to use.
29
- */
30
- private static maybeStartDumpTimer;
31
- }
2
+ export {};
package/dist/src/main.js CHANGED
@@ -1,118 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
3
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.Main = void 0;
8
- const package_json_1 = require("../package.json");
9
- const argparse_1 = require("argparse");
10
- const winston_1 = __importDefault(require("winston"));
11
- const DataStorage_1 = require("./storage/DataStorage");
12
- const CollectingServer_1 = require("./receiver/CollectingServer");
13
- /**
14
- * The main class of the Teamscale JavaScript Collector.
15
- * Used to start-up the collector for with a given configuration.
16
- */
17
- class Main {
18
- /**
19
- * Construct the object for parsing the command line arguments.
20
- */
21
- static buildParser() {
22
- const parser = new argparse_1.ArgumentParser({
23
- description: 'Collector of the Teamscale JavaScript Profiler. Collects coverage information from a' +
24
- '(headless) Web browser that executes code instrumented with our instrumenter.'
25
- });
26
- parser.add_argument('-v', '--version', { action: 'version', version: package_json_1.version });
27
- parser.add_argument('-p', '--port', { help: 'The port to receive coverage information on.', default: 54678 });
28
- parser.add_argument('-f', '--dump-to-file', { help: 'Target file', default: './coverage.simple' });
29
- parser.add_argument('-l', '--log-to-file', { help: 'Log file', default: 'logs/collector-combined.log' });
30
- parser.add_argument('-e', '--log-level', { help: 'Log level', default: 'info' });
31
- parser.add_argument('-t', '--dump-after-mins', {
32
- help: 'Dump the coverage information to the target file every N minutes.',
33
- default: 120
34
- });
35
- parser.add_argument('-d', '--debug', {
36
- help: 'Print received coverage information to the terminal?',
37
- default: false
38
- });
39
- return parser;
40
- }
41
- /**
42
- * Parse the given command line arguments into a corresponding options object.
43
- */
44
- static parseArguments() {
45
- const parser = this.buildParser();
46
- return parser.parse_args();
47
- }
48
- /**
49
- * Construct the logger.
50
- */
51
- static buildLogger(config) {
52
- return winston_1.default.createLogger({
53
- level: config.log_level,
54
- format: winston_1.default.format.json(),
55
- defaultMeta: {},
56
- transports: [
57
- new winston_1.default.transports.File({ filename: 'logs/collector-error.log', level: 'error' }),
58
- new winston_1.default.transports.File({ filename: config.log_to_file.trim() }),
59
- new winston_1.default.transports.Console({ format: winston_1.default.format.simple(), level: config.log_level })
60
- ]
61
- });
62
- }
63
- /**
64
- * Entry point of the Teamscale JavaScript Profiler.
65
- */
66
- static run() {
67
- // Parse the command line arguments
68
- const config = this.parseArguments();
69
- // Build the logger
70
- const logger = this.buildLogger(config);
71
- logger.info(`Starting collector in working directory "${process.cwd()}".`);
72
- logger.info(`Logging "${config.log_level}" to "${config.log_to_file}".`);
73
- // Prepare the storage and the server
74
- const storage = new DataStorage_1.DataStorage(logger);
75
- const server = new CollectingServer_1.WebSocketCollectingServer(config.port, storage, logger);
76
- // Start the server socket.
77
- // ATTENTION: The server is executed asynchronously
78
- server.start();
79
- // Optionally, start a timer that dumps the coverage after a N seconds
80
- this.maybeStartDumpTimer(config, storage, logger);
81
- // Say bye bye on CTRL+C and exit the process
82
- process.on('SIGINT', () => {
83
- logger.info('Bye bye.');
84
- process.exit();
85
- });
86
- }
87
- /**
88
- * Start a timer for dumping the data, depending on the configuration.
89
- *
90
- * @param config - The config that determines whether or not to do the timed dump.
91
- * @param storage - The storage with the information to dump.
92
- * @param logger - The logger to use.
93
- */
94
- static maybeStartDumpTimer(config, storage, logger) {
95
- if (config.dump_after_mins > 0) {
96
- const timer = setInterval(() => {
97
- try {
98
- const lines = storage.dumpToSimpleCoverageFile(config.dump_to_file);
99
- logger.info(`Conducted periodic coverage dump with ${lines} lines to ${config.dump_to_file}.`);
100
- }
101
- catch (e) {
102
- logger.error('Timed coverage dump failed.', e);
103
- }
104
- }, config.dump_after_mins * 1000 * 60);
105
- process.on('SIGINT', () => {
106
- // Stop the timed file dump
107
- if (timer) {
108
- clearInterval(timer);
109
- }
110
- // ... and do a final dump
111
- const written = storage.dumpToSimpleCoverageFile(config.dump_to_file);
112
- logger.info(`\nCaught interrupt signal. Written ${written} lines of the latest coverage.`);
113
- });
114
- }
115
- }
116
- }
117
- exports.Main = Main;
118
- Main.run();
4
+ const App_1 = require("./App");
5
+ App_1.App.run();
@@ -1,5 +1,5 @@
1
1
  import { IDataStorage } from '../storage/DataStorage';
2
- import { Logger } from 'winston';
2
+ import Logger from 'bunyan';
3
3
  /**
4
4
  * Various constants that are used to exchange data between
5
5
  * the instrumented application and the coverage collector.
@@ -18,7 +18,7 @@ export declare class WebSocketCollectingServer {
18
18
  /**
19
19
  * The WebSocket server component.
20
20
  */
21
- private readonly server;
21
+ private server;
22
22
  /**
23
23
  * The storage to put the received coverage information to for aggregation and further processing.
24
24
  */
@@ -38,7 +38,9 @@ export declare class WebSocketCollectingServer {
38
38
  /**
39
39
  * Start the server socket, handle sessions and dispatch messages.
40
40
  */
41
- start(): void;
41
+ start(): {
42
+ stop: () => void;
43
+ };
42
44
  /**
43
45
  * Handle a message from a client.
44
46
  *
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -60,9 +64,10 @@ class WebSocketCollectingServer {
60
64
  * Start the server socket, handle sessions and dispatch messages.
61
65
  */
62
66
  start() {
63
- this.logger.info(`Starting server on port ${this.server.options.port}.`);
67
+ var _a, _b;
68
+ this.logger.info(`Starting server on port ${(_a = this.server) === null || _a === void 0 ? void 0 : _a.options.port}.`);
64
69
  // Handle new connections from clients
65
- this.server.on('connection', (webSocket, req) => {
70
+ (_b = this.server) === null || _b === void 0 ? void 0 : _b.on('connection', (webSocket, req) => {
66
71
  let session = new Session_1.Session(req.socket, this.storage, this.logger);
67
72
  this.logger.debug(`Connection from: ${req.socket.remoteAddress}`);
68
73
  // Handle disconnecting clients
@@ -75,9 +80,9 @@ class WebSocketCollectingServer {
75
80
  }
76
81
  });
77
82
  // Handle incoming messages
78
- webSocket.on('message', (message) => {
83
+ webSocket.on('message', async (message) => {
79
84
  if (session && typeof message === 'string') {
80
- this.handleMessage(session, message);
85
+ await this.handleMessage(session, message);
81
86
  }
82
87
  });
83
88
  // Handle errors
@@ -85,6 +90,13 @@ class WebSocketCollectingServer {
85
90
  this.logger.error('Error on server socket triggered.', e);
86
91
  });
87
92
  });
93
+ return {
94
+ stop: () => {
95
+ var _a;
96
+ (_a = this.server) === null || _a === void 0 ? void 0 : _a.close();
97
+ this.server = null;
98
+ }
99
+ };
88
100
  }
89
101
  /**
90
102
  * Handle a message from a client.
@@ -92,13 +104,13 @@ class WebSocketCollectingServer {
92
104
  * @param session - The session that has been started for the client.
93
105
  * @param message - The message to handle.
94
106
  */
95
- handleMessage(session, message) {
107
+ async handleMessage(session, message) {
96
108
  try {
97
109
  if (message.startsWith(ProtocolMessageTypes.TYPE_SOURCEMAP)) {
98
- this.handleSourcemapMessage(session, message.substring(1));
110
+ await this.handleSourcemapMessage(session, message.substring(1));
99
111
  }
100
112
  else if (message.startsWith(ProtocolMessageTypes.TYPE_COVERAGE)) {
101
- this.handleCoverageMessage(session, message.substring(1));
113
+ await this.handleCoverageMessage(session, message.substring(1));
102
114
  }
103
115
  }
104
116
  catch (e) {
@@ -112,13 +124,13 @@ class WebSocketCollectingServer {
112
124
  * @param session - The session to handle the message for.
113
125
  * @param body - The body of the message (to be parsed).
114
126
  */
115
- handleSourcemapMessage(session, body) {
127
+ async handleSourcemapMessage(session, body) {
116
128
  const fileIdSeparatorPosition = body.indexOf(INSTRUMENTATION_SUBJECT_SEPARATOR);
117
129
  if (fileIdSeparatorPosition > -1) {
118
130
  const fileId = body.substring(0, fileIdSeparatorPosition).trim();
119
131
  this.logger.debug(`Received source map information for ${fileId}`);
120
132
  const sourcemap = body.substring(fileIdSeparatorPosition + 1);
121
- session.putSourcemap(fileId, sourcemap);
133
+ await session.putSourcemap(fileId, sourcemap);
122
134
  }
123
135
  }
124
136
  /**
@@ -127,16 +139,21 @@ class WebSocketCollectingServer {
127
139
  * @param session - The session to handle the message for.
128
140
  * @param body - The body of the message (to be parsed).
129
141
  */
130
- handleCoverageMessage(session, body) {
142
+ async handleCoverageMessage(session, body) {
131
143
  var _a;
132
- const bodyPattern = /(?<fileId>\S+) (?<positions>((\d+:\d+)\s+)*(\d+:\d+))/;
144
+ const bodyPattern = /(?<fileId>\S+) (?<positions>((\d+:\d+(:\d+:\d+)?\s+)*(\d+:\d+(:\d+:\d+)?)))/;
133
145
  const matches = bodyPattern.exec(body);
134
146
  if (matches === null || matches === void 0 ? void 0 : matches.groups) {
135
147
  const fileId = matches.groups.fileId;
136
148
  const positions = ((_a = matches.groups.positions) !== null && _a !== void 0 ? _a : '').split(/\s+/);
137
149
  for (const position of positions) {
138
- const [line, column] = position.split(':');
139
- session.putCoverage(fileId, Number.parseInt(line), Number.parseInt(column));
150
+ const positionParts = position.split(':');
151
+ if (positionParts.length === 2) {
152
+ session.putCoverage(fileId, Number.parseInt(positionParts[0]), Number.parseInt(positionParts[1]), Number.parseInt(positionParts[1]), Number.parseInt(positionParts[2]));
153
+ }
154
+ else if (positionParts.length === 4) {
155
+ session.putCoverage(fileId, Number.parseInt(positionParts[0]), Number.parseInt(positionParts[1]), Number.parseInt(positionParts[2]), Number.parseInt(positionParts[3]));
156
+ }
140
157
  }
141
158
  }
142
159
  }