@teamscale/coverage-collector 0.0.1-beta.43 → 0.0.1-beta.46
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/dist/package.json +4 -4
- package/dist/src/App.d.ts +1 -4
- package/dist/src/App.js +18 -81
- package/dist/src/receiver/Session.d.ts +17 -1
- package/dist/src/receiver/Session.js +24 -3
- package/dist/src/upload/ArtifactoryUpload.d.ts +6 -0
- package/dist/src/upload/ArtifactoryUpload.js +62 -0
- package/dist/src/upload/CommonUpload.d.ts +16 -0
- package/dist/src/upload/CommonUpload.js +55 -0
- package/dist/src/upload/TeamscaleUpload.d.ts +6 -0
- package/dist/src/upload/TeamscaleUpload.js +52 -0
- package/dist/src/utils/ConfigParameters.d.ts +5 -0
- package/dist/src/utils/ConfigParameters.js +20 -0
- package/package.json +4 -4
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamscale/coverage-collector",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.46",
|
|
4
4
|
"description": "Collector for JavaScript code coverage information",
|
|
5
5
|
"main": "dist/src/main.js",
|
|
6
6
|
"bin": "dist/src/main.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dist/**/*"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@cqse/commons": "^0.0.1-beta.
|
|
25
|
+
"@cqse/commons": "^0.0.1-beta.45",
|
|
26
26
|
"argparse": "^2.0.1",
|
|
27
27
|
"async": "^3.2.4",
|
|
28
28
|
"axios": "^0.24.0",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"form-data": "^4.0.0",
|
|
34
34
|
"mkdirp": "^1.0.4",
|
|
35
35
|
"rxjs": "^7.1.0",
|
|
36
|
-
"source-map": "^0.7.
|
|
36
|
+
"source-map": "^0.7.4",
|
|
37
37
|
"tmp": "^0.2.1",
|
|
38
38
|
"typescript-optional": "^2.0.1",
|
|
39
39
|
"ws": "^7.4.5"
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"babel-jest": "^27.2.0",
|
|
55
55
|
"esbuild": "^0.13.4",
|
|
56
56
|
"jest": "^27.2.0",
|
|
57
|
-
"mockttp": "^3.
|
|
57
|
+
"mockttp": "^3.4.0",
|
|
58
58
|
"rimraf": "^3.0.2",
|
|
59
59
|
"ts-jest": "^27.0.5",
|
|
60
60
|
"ts-node": "^10.2.1",
|
package/dist/src/App.d.ts
CHANGED
|
@@ -34,9 +34,6 @@ export declare class App {
|
|
|
34
34
|
*/
|
|
35
35
|
private static maybeStartDumpTimer;
|
|
36
36
|
private static dumpCoverage;
|
|
37
|
-
private static
|
|
38
|
-
private static performTeamscaleUpload;
|
|
39
|
-
private static prepareQueryParameters;
|
|
40
|
-
private static prepareFormData;
|
|
37
|
+
private static uploadCoverage;
|
|
41
38
|
private static startControlServer;
|
|
42
39
|
}
|
package/dist/src/App.js
CHANGED
|
@@ -32,21 +32,15 @@ const DataStorage_1 = require("./storage/DataStorage");
|
|
|
32
32
|
const CollectingServer_1 = require("./receiver/CollectingServer");
|
|
33
33
|
require("dotenv/config");
|
|
34
34
|
const fs = __importStar(require("fs"));
|
|
35
|
-
const axios_1 = __importDefault(require("axios"));
|
|
36
|
-
const form_data_1 = __importDefault(require("form-data"));
|
|
37
|
-
const QueryParameters_1 = __importDefault(require("./utils/QueryParameters"));
|
|
38
35
|
const ConfigParameters_1 = require("./utils/ConfigParameters");
|
|
39
|
-
const util_1 = require("util");
|
|
40
36
|
const mkdirp_1 = __importDefault(require("mkdirp"));
|
|
41
37
|
const path_1 = __importDefault(require("path"));
|
|
42
38
|
const StdConsoleLogger_1 = require("./utils/StdConsoleLogger");
|
|
43
39
|
const PrettyFileLogger_1 = require("./utils/PrettyFileLogger");
|
|
44
40
|
const express_1 = __importDefault(require("express"));
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class TeamscaleUploadError extends Error {
|
|
49
|
-
}
|
|
41
|
+
const TeamscaleUpload_1 = require("./upload/TeamscaleUpload");
|
|
42
|
+
const CommonUpload_1 = require("./upload/CommonUpload");
|
|
43
|
+
const ArtifactoryUpload_1 = require("./upload/ArtifactoryUpload");
|
|
50
44
|
/**
|
|
51
45
|
* The main class of the Teamscale JavaScript Collector.
|
|
52
46
|
* Used to start the collector with a given configuration.
|
|
@@ -159,18 +153,14 @@ class App {
|
|
|
159
153
|
// 1. Write coverage to a file
|
|
160
154
|
const [coverageFile, lines] = storage.dumpToSimpleCoverageFile(config.dump_to_folder, new Date());
|
|
161
155
|
logger.info(`Dumped ${lines} lines of coverage to ${coverageFile}.`);
|
|
162
|
-
// 2. Upload to Teamscale if configured
|
|
163
|
-
if (config.teamscale_server_url) {
|
|
164
|
-
await this.
|
|
165
|
-
// Delete coverage if upload was successful and keeping coverage files on disk was not configure by the user
|
|
166
|
-
if (!config.keep_coverage_files) {
|
|
167
|
-
fs.unlinkSync(coverageFile);
|
|
168
|
-
}
|
|
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);
|
|
169
159
|
}
|
|
170
160
|
}
|
|
171
161
|
catch (e) {
|
|
172
|
-
if (e instanceof
|
|
173
|
-
logger.error(`
|
|
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.
|
|
174
164
|
You can still upload them manually.`, e);
|
|
175
165
|
}
|
|
176
166
|
else {
|
|
@@ -178,71 +168,18 @@ class App {
|
|
|
178
168
|
}
|
|
179
169
|
}
|
|
180
170
|
}
|
|
181
|
-
static async
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
if (lines === 0) {
|
|
186
|
-
return;
|
|
171
|
+
static async uploadCoverage(config, coverageFile, lines, logger) {
|
|
172
|
+
if (config.teamscale_server_url) {
|
|
173
|
+
await (0, TeamscaleUpload_1.uploadToTeamscale)(config, logger, coverageFile, lines);
|
|
187
174
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const queryParameters = this.prepareQueryParameters(config);
|
|
191
|
-
await this.performTeamscaleUpload(config, queryParameters, form, logger);
|
|
192
|
-
}
|
|
193
|
-
static async performTeamscaleUpload(config, parameters, form, logger) {
|
|
194
|
-
var _a, _b, _c;
|
|
195
|
-
try {
|
|
196
|
-
const response = await axios_1.default
|
|
197
|
-
.post(`${(_a = config.teamscale_server_url) === null || _a === void 0 ? void 0 : _a.replace(/\/$/, '')}/api/projects/${config.teamscale_project}/external-analysis/session/auto-create/report?${parameters.toString()}`, form, {
|
|
198
|
-
auth: {
|
|
199
|
-
username: (_b = config.teamscale_user) !== null && _b !== void 0 ? _b : 'no username provided',
|
|
200
|
-
password: (_c = config.teamscale_access_token) !== null && _c !== void 0 ? _c : 'no password provided'
|
|
201
|
-
},
|
|
202
|
-
headers: {
|
|
203
|
-
Accept: '*/*',
|
|
204
|
-
'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
logger.info(`Upload finished with code ${response.status}.`);
|
|
175
|
+
if (config.artifactory_server_url) {
|
|
176
|
+
await (0, ArtifactoryUpload_1.uploadToArtifactory)(config, logger, coverageFile, lines);
|
|
208
177
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (response.status >= 400) {
|
|
213
|
-
throw new TeamscaleUploadError(`Upload failed with code ${response.status}: ${response.statusText}. Response Data: ${response.data}`);
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
logger.info(`Upload with status code ${response.status} finished.`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
else if (error.request) {
|
|
220
|
-
throw new TeamscaleUploadError(`Upload request did not receive a response.`);
|
|
221
|
-
}
|
|
222
|
-
if (error.message) {
|
|
223
|
-
logger.debug(`Something went wrong when uploading data: ${error.message}. Details of the error: ${(0, util_1.inspect)(error)}`);
|
|
224
|
-
throw new TeamscaleUploadError(`Something went wrong when uploading data: ${error.message}`);
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
throw new TeamscaleUploadError(`Something went wrong when uploading data: ${(0, util_1.inspect)(error)}`);
|
|
228
|
-
}
|
|
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);
|
|
229
181
|
}
|
|
230
182
|
}
|
|
231
|
-
static prepareQueryParameters(config) {
|
|
232
|
-
const parameters = new QueryParameters_1.default();
|
|
233
|
-
parameters.addIfDefined('format', 'SIMPLE');
|
|
234
|
-
parameters.addIfDefined('message', config.teamscale_message);
|
|
235
|
-
parameters.addIfDefined('repository', config.teamscale_repository);
|
|
236
|
-
parameters.addIfDefined('t', config.teamscale_commit);
|
|
237
|
-
parameters.addIfDefined('revision', config.teamscale_revision);
|
|
238
|
-
parameters.addIfDefined('partition', config.teamscale_partition);
|
|
239
|
-
return parameters;
|
|
240
|
-
}
|
|
241
|
-
static prepareFormData(coverageFile) {
|
|
242
|
-
const form = new form_data_1.default();
|
|
243
|
-
form.append('report', fs.createReadStream(coverageFile), 'coverage.simple');
|
|
244
|
-
return form;
|
|
245
|
-
}
|
|
246
183
|
static startControlServer(config, storage, logger) {
|
|
247
184
|
if (!config.enable_control_port) {
|
|
248
185
|
return {
|
|
@@ -251,9 +188,9 @@ class App {
|
|
|
251
188
|
}
|
|
252
189
|
};
|
|
253
190
|
}
|
|
254
|
-
|
|
191
|
+
const controlServer = (0, express_1.default)();
|
|
255
192
|
controlServer.use(express_1.default.text({}));
|
|
256
|
-
|
|
193
|
+
const serverSocket = controlServer.listen(config.enable_control_port);
|
|
257
194
|
controlServer.put('/partition', (request, response) => {
|
|
258
195
|
const targetPartition = request.body.trim();
|
|
259
196
|
config.teamscale_partition = targetPartition;
|
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
import { Socket } from 'net';
|
|
3
3
|
import { IDataStorage } from '../storage/DataStorage';
|
|
4
4
|
import Logger from 'bunyan';
|
|
5
|
+
/**
|
|
6
|
+
* Coverage information that has not been mapped back to the
|
|
7
|
+
* original code using a source map.
|
|
8
|
+
*/
|
|
9
|
+
export declare type UnmappedCoverage = {
|
|
10
|
+
fileId: string;
|
|
11
|
+
startLine: number;
|
|
12
|
+
startColumn: number;
|
|
13
|
+
endLine: number;
|
|
14
|
+
endColumn: number;
|
|
15
|
+
};
|
|
5
16
|
/**
|
|
6
17
|
* The session maintains the relevant information for a client.
|
|
7
18
|
* One session is created for each client.
|
|
@@ -22,6 +33,10 @@ export declare class Session {
|
|
|
22
33
|
* server per browser window.
|
|
23
34
|
*/
|
|
24
35
|
private readonly sourceMaps;
|
|
36
|
+
/**
|
|
37
|
+
* Unmapped coverage information.
|
|
38
|
+
*/
|
|
39
|
+
private readonly unmappedCoverage;
|
|
25
40
|
/**
|
|
26
41
|
* The logger to use.
|
|
27
42
|
*/
|
|
@@ -48,7 +63,7 @@ export declare class Session {
|
|
|
48
63
|
* @param endLine - The line number within the bundle the range ends.
|
|
49
64
|
* @param endColumn - The column in the given `startLine` on that the range ends (inclusive).
|
|
50
65
|
*/
|
|
51
|
-
putCoverage(fileId: string, startLine: number, startColumn: number, endLine: number, endColumn: number):
|
|
66
|
+
putCoverage(fileId: string, startLine: number, startColumn: number, endLine: number, endColumn: number): boolean;
|
|
52
67
|
/**
|
|
53
68
|
* Map to the original file position.
|
|
54
69
|
*
|
|
@@ -64,6 +79,7 @@ export declare class Session {
|
|
|
64
79
|
* @param sourceMapText - The actual source map.
|
|
65
80
|
*/
|
|
66
81
|
putSourcemap(fileId: string, sourceMapText: string): Promise<void>;
|
|
82
|
+
private processUnmappedCoverageOf;
|
|
67
83
|
/**
|
|
68
84
|
* Destroy the session and free the memory it allocates.
|
|
69
85
|
* In particular the sourcemaps are freed (important to not run out of memory!).
|
|
@@ -44,6 +44,7 @@ class Session {
|
|
|
44
44
|
this.storage = commons_1.Contract.requireDefined(storage);
|
|
45
45
|
this.logger = commons_1.Contract.requireDefined(logger);
|
|
46
46
|
this.sourceMaps = new Map();
|
|
47
|
+
this.unmappedCoverage = new Map();
|
|
47
48
|
this.projectId = ''; // We currently only support coverage for one project.
|
|
48
49
|
}
|
|
49
50
|
/**
|
|
@@ -58,6 +59,17 @@ class Session {
|
|
|
58
59
|
*/
|
|
59
60
|
putCoverage(fileId, startLine, startColumn, endLine, endColumn) {
|
|
60
61
|
var _a, _b;
|
|
62
|
+
// Delay the mapping if the sourcemap has not yet arrived
|
|
63
|
+
if (!this.sourceMaps.has(fileId)) {
|
|
64
|
+
let unmappedForFile = this.unmappedCoverage.get(fileId);
|
|
65
|
+
if (!unmappedForFile) {
|
|
66
|
+
unmappedForFile = [];
|
|
67
|
+
this.unmappedCoverage.set(fileId, unmappedForFile);
|
|
68
|
+
}
|
|
69
|
+
unmappedForFile.push({ endColumn, endLine, fileId, startColumn, startLine });
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
let mapped = false;
|
|
61
73
|
// Iterate over the lines to scan
|
|
62
74
|
let line = startLine;
|
|
63
75
|
while (line <= endLine) {
|
|
@@ -87,12 +99,10 @@ class Session {
|
|
|
87
99
|
if (originalPosition.line && originalPosition.source) {
|
|
88
100
|
if (lastCoveredLine !== originalPosition.line) {
|
|
89
101
|
this.storage.putCoverage(this.projectId, originalPosition.source, [originalPosition.line]);
|
|
102
|
+
mapped = true;
|
|
90
103
|
lastCoveredLine = originalPosition.line;
|
|
91
104
|
}
|
|
92
105
|
}
|
|
93
|
-
else {
|
|
94
|
-
this.storage.signalUnmappedCoverage(this.projectId);
|
|
95
|
-
}
|
|
96
106
|
// Step to the next column to map back to the original.
|
|
97
107
|
// `originalPosition.name` is the token on the position, that is, if it is present
|
|
98
108
|
// we increment the column by its length.
|
|
@@ -101,6 +111,7 @@ class Session {
|
|
|
101
111
|
// And the next line
|
|
102
112
|
line++;
|
|
103
113
|
}
|
|
114
|
+
return mapped;
|
|
104
115
|
}
|
|
105
116
|
/**
|
|
106
117
|
* Map to the original file position.
|
|
@@ -130,11 +141,21 @@ class Session {
|
|
|
130
141
|
try {
|
|
131
142
|
const sourceMapConsumer = await new sourceMap.SourceMapConsumer(rawSourceMap);
|
|
132
143
|
this.sourceMaps.set(fileId, sourceMapConsumer);
|
|
144
|
+
this.processUnmappedCoverageOf(fileId);
|
|
133
145
|
}
|
|
134
146
|
catch (e) {
|
|
135
147
|
this.logger.error(`Consuming source map failed! ${e}`);
|
|
136
148
|
}
|
|
137
149
|
}
|
|
150
|
+
processUnmappedCoverageOf(fileId) {
|
|
151
|
+
var _a;
|
|
152
|
+
const unmapped = (_a = this.unmappedCoverage.get(fileId)) !== null && _a !== void 0 ? _a : [];
|
|
153
|
+
unmapped.forEach((entry) => {
|
|
154
|
+
if (!this.putCoverage(entry.fileId, entry.startLine, entry.startColumn, entry.endLine, entry.endColumn)) {
|
|
155
|
+
this.storage.signalUnmappedCoverage(this.projectId);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
138
159
|
/**
|
|
139
160
|
* Destroy the session and free the memory it allocates.
|
|
140
161
|
* In particular the sourcemaps are freed (important to not run out of memory!).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ConfigParameters } from '../utils/ConfigParameters';
|
|
2
|
+
import Logger from 'bunyan';
|
|
3
|
+
/**
|
|
4
|
+
* Uploads a coverage file to artifactory with the provided configuration.
|
|
5
|
+
*/
|
|
6
|
+
export declare function uploadToArtifactory(config: ConfigParameters, logger: Logger, coverageFile: string, lines: number): Promise<void>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.uploadToArtifactory = void 0;
|
|
7
|
+
const CommonUpload_1 = require("./CommonUpload");
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
/**
|
|
10
|
+
* Uploads a coverage file to artifactory with the provided configuration.
|
|
11
|
+
*/
|
|
12
|
+
async function uploadToArtifactory(config, logger, coverageFile, lines) {
|
|
13
|
+
if (!(config.artifactory_access_token || (config.artifactory_user && config.artifactory_password))) {
|
|
14
|
+
throw new CommonUpload_1.UploadError('API key or user name and password must be configured!');
|
|
15
|
+
}
|
|
16
|
+
if (lines === 0) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
logger.info('Preparing upload to Artifactory');
|
|
20
|
+
const form = (0, CommonUpload_1.prepareFormData)(coverageFile);
|
|
21
|
+
await performArtifactoryUpload(config, form, logger);
|
|
22
|
+
}
|
|
23
|
+
exports.uploadToArtifactory = uploadToArtifactory;
|
|
24
|
+
async function performArtifactoryUpload(config, form, logger) {
|
|
25
|
+
var _a;
|
|
26
|
+
if (!config.teamscale_commit) {
|
|
27
|
+
throw new CommonUpload_1.UploadError('The "--teamscale-commit" option must be set with a valid branch and timestamp.');
|
|
28
|
+
}
|
|
29
|
+
const branchAndTimestamp = config.teamscale_commit.split(':');
|
|
30
|
+
let url = `${(_a = config.artifactory_server_url) === null || _a === void 0 ? void 0 : _a.replace(/\/$/, '')}/uploads/${branchAndTimestamp[0]}/${branchAndTimestamp[1]}`;
|
|
31
|
+
if (config.teamscale_revision) {
|
|
32
|
+
url = url + `-${config.teamscale_revision}`;
|
|
33
|
+
}
|
|
34
|
+
url = url + `/${config.teamscale_partition}/simple`;
|
|
35
|
+
if (config.artifactory_path_suffix !== undefined) {
|
|
36
|
+
url = `${url}/${config.artifactory_path_suffix}`;
|
|
37
|
+
}
|
|
38
|
+
url = `${url}/report.simple`;
|
|
39
|
+
await (0, CommonUpload_1.performUpload)(url, form, prepareArtifactoryConfig(config, form), axios_1.default.put, logger);
|
|
40
|
+
}
|
|
41
|
+
function prepareArtifactoryConfig(config, form) {
|
|
42
|
+
var _a, _b;
|
|
43
|
+
if (config.artifactory_access_token) {
|
|
44
|
+
return {
|
|
45
|
+
headers: {
|
|
46
|
+
Accept: '*/*',
|
|
47
|
+
'X-JFrog-Art-Api': config.artifactory_access_token,
|
|
48
|
+
'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
auth: {
|
|
54
|
+
username: (_a = config.artifactory_user) !== null && _a !== void 0 ? _a : 'no username provided',
|
|
55
|
+
password: (_b = config.artifactory_password) !== null && _b !== void 0 ? _b : 'no password provided'
|
|
56
|
+
},
|
|
57
|
+
headers: {
|
|
58
|
+
Accept: '*/*',
|
|
59
|
+
'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import FormData from 'form-data';
|
|
2
|
+
import Logger from 'bunyan';
|
|
3
|
+
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
4
|
+
/**
|
|
5
|
+
* Error that is thrown when the upload failed
|
|
6
|
+
*/
|
|
7
|
+
export declare class UploadError extends Error {
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Prepares the form data from a given configuration file for the upload.
|
|
11
|
+
*/
|
|
12
|
+
export declare function prepareFormData(coverageFile: string): FormData;
|
|
13
|
+
/**
|
|
14
|
+
* Uploads a coverage file with the provided configuration.
|
|
15
|
+
*/
|
|
16
|
+
export declare function performUpload(url: string, form: FormData, config: AxiosRequestConfig<FormData>, uploadFunction: <T = any, R = AxiosResponse<T>, D = FormData>(url: string, data?: D, config?: AxiosRequestConfig<D>) => Promise<R>, logger: Logger): Promise<void>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.performUpload = exports.prepareFormData = exports.UploadError = void 0;
|
|
7
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const util_1 = require("util");
|
|
10
|
+
/**
|
|
11
|
+
* Error that is thrown when the upload failed
|
|
12
|
+
*/
|
|
13
|
+
class UploadError extends Error {
|
|
14
|
+
}
|
|
15
|
+
exports.UploadError = UploadError;
|
|
16
|
+
/**
|
|
17
|
+
* Prepares the form data from a given configuration file for the upload.
|
|
18
|
+
*/
|
|
19
|
+
function prepareFormData(coverageFile) {
|
|
20
|
+
const form = new form_data_1.default();
|
|
21
|
+
form.append('report', fs_1.default.createReadStream(coverageFile), 'coverage.simple');
|
|
22
|
+
return form;
|
|
23
|
+
}
|
|
24
|
+
exports.prepareFormData = prepareFormData;
|
|
25
|
+
/**
|
|
26
|
+
* Uploads a coverage file with the provided configuration.
|
|
27
|
+
*/
|
|
28
|
+
async function performUpload(url, form, config, uploadFunction, logger) {
|
|
29
|
+
try {
|
|
30
|
+
const response = await uploadFunction(url, form, config);
|
|
31
|
+
logger.info(`Upload finished with code ${response.status}.`);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error.response) {
|
|
35
|
+
const response = error.response;
|
|
36
|
+
if (response.status >= 400) {
|
|
37
|
+
throw new UploadError(`Upload failed with code ${response.status}: ${response.statusText}. Response Data: ${response.data}`);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
logger.info(`Upload with status code ${response.status} finished.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (error.request) {
|
|
44
|
+
throw new UploadError(`Upload request did not receive a response.`);
|
|
45
|
+
}
|
|
46
|
+
if (error.message) {
|
|
47
|
+
logger.debug(`Something went wrong when uploading data: ${error.message}. Details of the error: ${(0, util_1.inspect)(error)}`);
|
|
48
|
+
throw new UploadError(`Something went wrong when uploading data: ${error.message}`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
throw new UploadError(`Something went wrong when uploading data: ${(0, util_1.inspect)(error)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.performUpload = performUpload;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ConfigParameters } from '../utils/ConfigParameters';
|
|
2
|
+
import Logger from 'bunyan';
|
|
3
|
+
/**
|
|
4
|
+
* Uploads a coverage file to Teamscale with the provided configuration.
|
|
5
|
+
*/
|
|
6
|
+
export declare function uploadToTeamscale(config: ConfigParameters, logger: Logger, coverageFile: string, lines: number): Promise<void>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.uploadToTeamscale = void 0;
|
|
7
|
+
const QueryParameters_1 = __importDefault(require("../utils/QueryParameters"));
|
|
8
|
+
const CommonUpload_1 = require("./CommonUpload");
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
/**
|
|
11
|
+
* Uploads a coverage file to Teamscale with the provided configuration.
|
|
12
|
+
*/
|
|
13
|
+
async function uploadToTeamscale(config, logger, coverageFile, lines) {
|
|
14
|
+
if (!(config.teamscale_access_token && config.teamscale_user && config.teamscale_server_url)) {
|
|
15
|
+
throw new CommonUpload_1.UploadError('API key and user name must be configured!');
|
|
16
|
+
}
|
|
17
|
+
if (lines === 0) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
logger.info('Preparing upload to Teamscale');
|
|
21
|
+
const form = (0, CommonUpload_1.prepareFormData)(coverageFile);
|
|
22
|
+
const queryParameters = prepareQueryParameters(config);
|
|
23
|
+
await performTeamscaleUpload(config, queryParameters, form, logger);
|
|
24
|
+
}
|
|
25
|
+
exports.uploadToTeamscale = uploadToTeamscale;
|
|
26
|
+
async function performTeamscaleUpload(config, parameters, form, logger) {
|
|
27
|
+
var _a;
|
|
28
|
+
await (0, CommonUpload_1.performUpload)(`${(_a = config.teamscale_server_url) === null || _a === void 0 ? void 0 : _a.replace(/\/$/, '')}/api/projects/${config.teamscale_project}/external-analysis/session/auto-create/report?${parameters.toString()}`, form, prepareTeamscaleConfig(config, form), axios_1.default.post, logger);
|
|
29
|
+
}
|
|
30
|
+
function prepareQueryParameters(config) {
|
|
31
|
+
const parameters = new QueryParameters_1.default();
|
|
32
|
+
parameters.addIfDefined('format', 'SIMPLE');
|
|
33
|
+
parameters.addIfDefined('message', config.teamscale_message);
|
|
34
|
+
parameters.addIfDefined('repository', config.teamscale_repository);
|
|
35
|
+
parameters.addIfDefined('t', config.teamscale_commit);
|
|
36
|
+
parameters.addIfDefined('revision', config.teamscale_revision);
|
|
37
|
+
parameters.addIfDefined('partition', config.teamscale_partition);
|
|
38
|
+
return parameters;
|
|
39
|
+
}
|
|
40
|
+
function prepareTeamscaleConfig(config, form) {
|
|
41
|
+
var _a, _b;
|
|
42
|
+
return {
|
|
43
|
+
auth: {
|
|
44
|
+
username: (_a = config.teamscale_user) !== null && _a !== void 0 ? _a : 'no username provided',
|
|
45
|
+
password: (_b = config.teamscale_access_token) !== null && _b !== void 0 ? _b : 'no password provided'
|
|
46
|
+
},
|
|
47
|
+
headers: {
|
|
48
|
+
Accept: '*/*',
|
|
49
|
+
'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -22,6 +22,11 @@ export declare type ConfigParameters = {
|
|
|
22
22
|
teamscale_commit?: string;
|
|
23
23
|
teamscale_repository?: string;
|
|
24
24
|
teamscale_message?: string;
|
|
25
|
+
artifactory_server_url?: string;
|
|
26
|
+
artifactory_user?: string;
|
|
27
|
+
artifactory_password?: string;
|
|
28
|
+
artifactory_access_token?: string;
|
|
29
|
+
artifactory_path_suffix?: string;
|
|
25
30
|
enable_control_port?: number;
|
|
26
31
|
};
|
|
27
32
|
/**
|
|
@@ -78,6 +78,26 @@ function buildParameterParser() {
|
|
|
78
78
|
help: 'The commit message shown within Teamscale for the coverage upload. Default is "JavaScript coverage upload".',
|
|
79
79
|
default: (_a = process.env.TEAMSCALE_MESSAGE) !== null && _a !== void 0 ? _a : 'JavaScript coverage upload'
|
|
80
80
|
});
|
|
81
|
+
parser.add_argument('--artifactory-server-url', {
|
|
82
|
+
help: 'Upload the coverage to the given Artifactory server URL. The URL may include a subpath on the artifactory server, e.g. https://artifactory.acme.com/my-repo/my/subpath',
|
|
83
|
+
default: process.env.ARTIFACTORY_SERVER_URL
|
|
84
|
+
});
|
|
85
|
+
parser.add_argument('--artifactory-user', {
|
|
86
|
+
help: 'The user for uploading coverage to Artifactory. Only needed when not using the --artifactory-access-token option',
|
|
87
|
+
default: process.env.ARTIFACTORY_USER
|
|
88
|
+
});
|
|
89
|
+
parser.add_argument('--artifactory-password', {
|
|
90
|
+
help: 'The password for uploading coverage to Artifactory. Only needed when not using the --artifactory-access-token option',
|
|
91
|
+
default: process.env.ARTIFACTORY_PASSWORD
|
|
92
|
+
});
|
|
93
|
+
parser.add_argument('--artifactory-access-token', {
|
|
94
|
+
help: 'The access_token for uploading coverage to Artifactory.',
|
|
95
|
+
default: process.env.ARTIFACTORY_ACCESS_TOKEN
|
|
96
|
+
});
|
|
97
|
+
parser.add_argument('--artifactory-path-suffix', {
|
|
98
|
+
help: '(optional): The path within the storage location between the default path and the uploaded artifact.',
|
|
99
|
+
default: process.env.ARTIFACTORY_PATH_SUFFIX
|
|
100
|
+
});
|
|
81
101
|
return parser;
|
|
82
102
|
}
|
|
83
103
|
exports.buildParameterParser = buildParameterParser;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamscale/coverage-collector",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.46",
|
|
4
4
|
"description": "Collector for JavaScript code coverage information",
|
|
5
5
|
"main": "dist/src/main.js",
|
|
6
6
|
"bin": "dist/src/main.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dist/**/*"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@cqse/commons": "^0.0.1-beta.
|
|
25
|
+
"@cqse/commons": "^0.0.1-beta.45",
|
|
26
26
|
"argparse": "^2.0.1",
|
|
27
27
|
"async": "^3.2.4",
|
|
28
28
|
"axios": "^0.24.0",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"form-data": "^4.0.0",
|
|
34
34
|
"mkdirp": "^1.0.4",
|
|
35
35
|
"rxjs": "^7.1.0",
|
|
36
|
-
"source-map": "^0.7.
|
|
36
|
+
"source-map": "^0.7.4",
|
|
37
37
|
"tmp": "^0.2.1",
|
|
38
38
|
"typescript-optional": "^2.0.1",
|
|
39
39
|
"ws": "^7.4.5"
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"babel-jest": "^27.2.0",
|
|
55
55
|
"esbuild": "^0.13.4",
|
|
56
56
|
"jest": "^27.2.0",
|
|
57
|
-
"mockttp": "^3.
|
|
57
|
+
"mockttp": "^3.4.0",
|
|
58
58
|
"rimraf": "^3.0.2",
|
|
59
59
|
"ts-jest": "^27.0.5",
|
|
60
60
|
"ts-node": "^10.2.1",
|