@teamscale/coverage-collector 0.0.1-beta.3 → 0.0.1-beta.7

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
@@ -32,7 +32,7 @@ yarn build
32
32
  There are several options to run the Collector. For example, via `yarn` by running
33
33
 
34
34
  ```
35
- yarn serve --port 54678 --dump-to-file=./coverage.simple
35
+ yarn collector --port 54678 --dump-to-file=./coverage.simple
36
36
  ```
37
37
 
38
38
  or via `npx` by running
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamscale/coverage-collector",
3
- "version": "0.0.1-beta.3",
3
+ "version": "0.0.1-beta.7",
4
4
  "description": "Collector for JavaScript code coverage information",
5
5
  "main": "dist/src/main.js",
6
6
  "bin": "dist/src/main.js",
@@ -14,7 +14,7 @@
14
14
  "scripts": {
15
15
  "clean": "rimraf dist tsconfig.tsbuildinfo",
16
16
  "build": "tsc",
17
- "serve": "node dist/src/main.js",
17
+ "collector": "node dist/src/main.js",
18
18
  "test": "yarn build && NODE_OPTIONS='--experimental-vm-modules' jest --forceExit --coverage --silent=true --detectOpenHandles"
19
19
  },
20
20
  "files": [
@@ -24,8 +24,12 @@
24
24
  "@cqse/commons": "^0.0.1-beta.1",
25
25
  "argparse": "^2.0.1",
26
26
  "async": "^3.2.0",
27
+ "axios": "^0.24.0",
28
+ "dotenv": "^14.1.0",
29
+ "form-data": "^4.0.0",
27
30
  "rxjs": "^7.1.0",
28
31
  "source-map": "^0.7.3",
32
+ "tmp": "^0.2.1",
29
33
  "typescript-optional": "^2.0.1",
30
34
  "winston": "^3.3.3",
31
35
  "ws": "^7.4.5"
@@ -38,6 +42,7 @@
38
42
  "@types/jest": "^27.0.1",
39
43
  "@types/node": "^15.0.1",
40
44
  "@types/source-map": "^0.5.7",
45
+ "@types/tmp": "^0.2.3",
41
46
  "@types/winston": "^2.4.4",
42
47
  "@types/ws": "^7.4.2",
43
48
  "babel-jest": "^27.2.0",
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import 'dotenv/config';
2
3
  /**
3
4
  * The main class of the Teamscale JavaScript Collector.
4
- * Used to start-up the collector for with a given configuration.
5
+ * Used to start the collector for with a given configuration.
5
6
  */
6
7
  export declare class Main {
7
8
  /**
@@ -23,9 +24,10 @@ export declare class Main {
23
24
  /**
24
25
  * Start a timer for dumping the data, depending on the configuration.
25
26
  *
26
- * @param config - The config that determines whether or not to do the timed dump.
27
+ * @param config - The config that determines whether to do the timed dump or not.
27
28
  * @param storage - The storage with the information to dump.
28
29
  * @param logger - The logger to use.
29
30
  */
30
31
  private static maybeStartDumpTimer;
32
+ private static dumpCoverage;
31
33
  }
package/dist/src/main.js CHANGED
@@ -1,5 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
6
+ }) : (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ o[k2] = m[k];
9
+ }));
10
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
11
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
12
+ }) : function(o, v) {
13
+ o["default"] = v;
14
+ });
15
+ var __importStar = (this && this.__importStar) || function (mod) {
16
+ if (mod && mod.__esModule) return mod;
17
+ var result = {};
18
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
19
+ __setModuleDefault(result, mod);
20
+ return result;
21
+ };
3
22
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
23
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
24
  };
@@ -10,32 +29,76 @@ const argparse_1 = require("argparse");
10
29
  const winston_1 = __importDefault(require("winston"));
11
30
  const DataStorage_1 = require("./storage/DataStorage");
12
31
  const CollectingServer_1 = require("./receiver/CollectingServer");
32
+ require("dotenv/config");
33
+ const fs = __importStar(require("fs"));
34
+ const axios_1 = __importDefault(require("axios"));
35
+ const form_data_1 = __importDefault(require("form-data"));
36
+ const QueryParameters_1 = __importDefault(require("./utils/QueryParameters"));
37
+ const tmp_1 = __importDefault(require("tmp"));
13
38
  /**
14
39
  * The main class of the Teamscale JavaScript Collector.
15
- * Used to start-up the collector for with a given configuration.
40
+ * Used to start the collector for with a given configuration.
16
41
  */
17
42
  class Main {
18
43
  /**
19
44
  * Construct the object for parsing the command line arguments.
20
45
  */
21
46
  static buildParser() {
47
+ var _a;
22
48
  const parser = new argparse_1.ArgumentParser({
23
49
  description: 'Collector of the Teamscale JavaScript Profiler. Collects coverage information from a' +
24
50
  '(headless) Web browser that executes code instrumented with our instrumenter.'
25
51
  });
26
52
  parser.add_argument('-v', '--version', { action: 'version', version: package_json_1.version });
27
53
  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' });
54
+ parser.add_argument('-f', '--dump-to-file', { help: 'Target file to write coverage to.' });
29
55
  parser.add_argument('-l', '--log-to-file', { help: 'Log file', default: 'logs/collector-combined.log' });
30
56
  parser.add_argument('-e', '--log-level', { help: 'Log level', default: 'info' });
31
- parser.add_argument('-s', '--dump-after-secs', {
32
- help: 'Dump the coverage information to the target file every N seconds.',
33
- default: 120
57
+ parser.add_argument('-t', '--dump-after-mins', {
58
+ help: 'Dump the coverage information to the target file every N minutes.',
59
+ default: 2
34
60
  });
35
61
  parser.add_argument('-d', '--debug', {
36
62
  help: 'Print received coverage information to the terminal?',
37
63
  default: false
38
64
  });
65
+ // Parameters for the upload to Teamscale
66
+ parser.add_argument('-u', '--teamscale-server-url', {
67
+ help: 'Upload the coverage to the given Teamscale server URL, for example, https://teamscale.dev.example.com:8080/production.',
68
+ default: process.env.TEAMSCALE_SERVER_URL
69
+ });
70
+ parser.add_argument('--teamscale-access-token', {
71
+ help: 'The API key to use for uploading to Teamscale.',
72
+ default: process.env.TEAMSCALE_ACCESS_TOKEN
73
+ });
74
+ parser.add_argument('--teamscale-project', {
75
+ help: 'The project ID to upload coverage to.',
76
+ default: process.env.TEAMSCALE_PROJECT
77
+ });
78
+ parser.add_argument('--teamscale-user', {
79
+ help: 'The user for uploading coverage to Teamscale.',
80
+ default: process.env.TEAMSCALE_USER
81
+ });
82
+ parser.add_argument('--teamscale-partition', {
83
+ help: 'The partition to upload coverage to.',
84
+ default: process.env.TEAMSCALE_PARTITION
85
+ });
86
+ parser.add_argument('--teamscale-revision', {
87
+ help: 'The revision (commit hash, version id) to upload coverage for.',
88
+ default: process.env.TEAMSCALE_REVISION
89
+ });
90
+ parser.add_argument('--teamscale-commit', {
91
+ help: 'The branch and timestamp to upload coverage for, separated by colon.',
92
+ default: process.env.TEAMSCALE_COMMIT
93
+ });
94
+ parser.add_argument('--teamscale-repository', {
95
+ help: 'The repository to upload coverage for. Optional: Only needed when uploading via revision to a project that has more than one connector.',
96
+ default: process.env.TEAMSCALE_REPOSITORY
97
+ });
98
+ parser.add_argument('--teamscale-message', {
99
+ help: 'The commit message shown within Teamscale for the coverage upload. Default is "JavaScript coverage upload".',
100
+ default: (_a = process.env.TEAMSCALE_MESSAGE) !== null && _a !== void 0 ? _a : 'JavaScript coverage upload'
101
+ });
39
102
  return parser;
40
103
  }
41
104
  /**
@@ -70,6 +133,11 @@ class Main {
70
133
  const logger = this.buildLogger(config);
71
134
  logger.info(`Starting collector in working directory "${process.cwd()}".`);
72
135
  logger.info(`Logging "${config.log_level}" to "${config.log_to_file}".`);
136
+ // Check the command line arguments
137
+ if (!config.dump_to_file && !config.teamscale_server_url) {
138
+ logger.error('The Collector must be configured to either dump to a file or upload to Teamscale.');
139
+ process.exit(1);
140
+ }
73
141
  // Prepare the storage and the server
74
142
  const storage = new DataStorage_1.DataStorage(logger);
75
143
  const server = new CollectingServer_1.WebSocketCollectingServer(config.port, storage, logger);
@@ -79,7 +147,9 @@ class Main {
79
147
  // Optionally, start a timer that dumps the coverage after a N seconds
80
148
  this.maybeStartDumpTimer(config, storage, logger);
81
149
  // Say bye bye on CTRL+C and exit the process
82
- process.on('SIGINT', () => {
150
+ process.on('SIGINT', async () => {
151
+ // ... and do a final dump before.
152
+ await this.dumpCoverage(config, storage, logger).then();
83
153
  logger.info('Bye bye.');
84
154
  process.exit();
85
155
  });
@@ -87,32 +157,77 @@ class Main {
87
157
  /**
88
158
  * Start a timer for dumping the data, depending on the configuration.
89
159
  *
90
- * @param config - The config that determines whether or not to do the timed dump.
160
+ * @param config - The config that determines whether to do the timed dump or not.
91
161
  * @param storage - The storage with the information to dump.
92
162
  * @param logger - The logger to use.
93
163
  */
94
164
  static maybeStartDumpTimer(config, storage, logger) {
95
- if (config.dump_after_secs > 0) {
165
+ if (config.dump_after_mins > 0) {
166
+ logger.info(`Will dump coverage information every ${config.dump_after_mins} minute(s).`);
96
167
  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_secs * 1000);
168
+ this.dumpCoverage(config, storage, logger).then();
169
+ }, config.dump_after_mins * 1000 * 60);
105
170
  process.on('SIGINT', () => {
106
171
  // Stop the timed file dump
107
172
  if (timer) {
108
173
  clearInterval(timer);
109
174
  }
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
175
  });
114
176
  }
115
177
  }
178
+ static async dumpCoverage(config, storage, logger) {
179
+ var _a;
180
+ try {
181
+ const deleteCoverageFileAfterUpload = !config.dump_to_file;
182
+ const coverageFile = (_a = config.dump_to_file) !== null && _a !== void 0 ? _a : tmp_1.default.tmpNameSync();
183
+ try {
184
+ // 1. Write coverage to a file
185
+ const lines = storage.dumpToSimpleCoverageFile(coverageFile);
186
+ logger.info(`Dumped ${lines} lines of coverage to ${config.dump_to_file}.`);
187
+ // 2. Upload to Teamscale if configured
188
+ if (!config.teamscale_server_url) {
189
+ logger.info('Upload to Teamscale not configured.');
190
+ }
191
+ else if (lines === 0) {
192
+ // Nothing to upload
193
+ }
194
+ else if (config.teamscale_access_token && config.teamscale_user) {
195
+ logger.info('Preparing upload to Teamscale');
196
+ const form = new form_data_1.default();
197
+ form.append('report', fs.createReadStream(coverageFile), 'coverage.simple');
198
+ const parameters = new QueryParameters_1.default();
199
+ parameters.addIfDefined('format', 'SIMPLE');
200
+ parameters.addIfDefined('message', config.teamscale_message);
201
+ parameters.addIfDefined('repository', config.teamscale_repository);
202
+ parameters.addIfDefined('t', config.teamscale_commit);
203
+ parameters.addIfDefined('revision', config.teamscale_revision);
204
+ parameters.addIfDefined('partition', config.teamscale_partition);
205
+ const response = await axios_1.default.post(`${config.teamscale_server_url.replace(/\/$/, '')}/api/projects/${config.teamscale_project}/external-analysis/session/auto-create/report?${parameters.toString()}`, form, {
206
+ auth: {
207
+ username: config.teamscale_user,
208
+ password: config.teamscale_access_token
209
+ },
210
+ headers: {
211
+ Accept: '*/*',
212
+ 'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`
213
+ }
214
+ });
215
+ logger.info(`Upload with response ${response.status} finished.`);
216
+ }
217
+ else {
218
+ logger.error('Cannot upload to Teamscale: API key and user name must be configured!');
219
+ }
220
+ }
221
+ finally {
222
+ if (deleteCoverageFileAfterUpload) {
223
+ fs.unlinkSync(coverageFile);
224
+ }
225
+ }
226
+ }
227
+ catch (e) {
228
+ logger.error('Coverage dump failed.', e);
229
+ }
230
+ }
116
231
  }
117
232
  exports.Main = Main;
118
233
  Main.run();
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Helper class for building HTTP query parameter strings.
3
+ */
4
+ export default class QueryParameters extends URLSearchParams {
5
+ /**
6
+ * Adds a parameter if the value is defined.
7
+ */
8
+ addIfDefined(key: string, value: string | undefined): void;
9
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Helper class for building HTTP query parameter strings.
5
+ */
6
+ class QueryParameters extends URLSearchParams {
7
+ /**
8
+ * Adds a parameter if the value is defined.
9
+ */
10
+ addIfDefined(key, value) {
11
+ if (value) {
12
+ this.append(key, value);
13
+ }
14
+ else {
15
+ this.delete(key);
16
+ }
17
+ }
18
+ }
19
+ exports.default = QueryParameters;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamscale/coverage-collector",
3
- "version": "0.0.1-beta.3",
3
+ "version": "0.0.1-beta.7",
4
4
  "description": "Collector for JavaScript code coverage information",
5
5
  "main": "dist/src/main.js",
6
6
  "bin": "dist/src/main.js",
@@ -14,7 +14,7 @@
14
14
  "scripts": {
15
15
  "clean": "rimraf dist tsconfig.tsbuildinfo",
16
16
  "build": "tsc",
17
- "serve": "node dist/src/main.js",
17
+ "collector": "node dist/src/main.js",
18
18
  "test": "yarn build && NODE_OPTIONS='--experimental-vm-modules' jest --forceExit --coverage --silent=true --detectOpenHandles"
19
19
  },
20
20
  "files": [
@@ -24,8 +24,12 @@
24
24
  "@cqse/commons": "^0.0.1-beta.1",
25
25
  "argparse": "^2.0.1",
26
26
  "async": "^3.2.0",
27
+ "axios": "^0.24.0",
28
+ "dotenv": "^14.1.0",
29
+ "form-data": "^4.0.0",
27
30
  "rxjs": "^7.1.0",
28
31
  "source-map": "^0.7.3",
32
+ "tmp": "^0.2.1",
29
33
  "typescript-optional": "^2.0.1",
30
34
  "winston": "^3.3.3",
31
35
  "ws": "^7.4.5"
@@ -38,6 +42,7 @@
38
42
  "@types/jest": "^27.0.1",
39
43
  "@types/node": "^15.0.1",
40
44
  "@types/source-map": "^0.5.7",
45
+ "@types/tmp": "^0.2.3",
41
46
  "@types/winston": "^2.4.4",
42
47
  "@types/ws": "^7.4.2",
43
48
  "babel-jest": "^27.2.0",