@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.
@@ -1,7 +1,18 @@
1
1
  /// <reference types="node" />
2
2
  import { Socket } from 'net';
3
3
  import { IDataStorage } from '../storage/DataStorage';
4
- import { Logger } from 'winston';
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
  */
@@ -43,10 +58,12 @@ export declare class Session {
43
58
  * This method also conducts the mapping based on the source map.
44
59
  *
45
60
  * @param fileId - The identifier of the instrumented bundle (file).
46
- * @param line - The line number within the bundle.
47
- * @param column - The column within the bundle.
61
+ * @param startLine - The line number within the bundle the range starts.
62
+ * @param startColumn - The column in the given `startLine` on that the range starts (inclusive).
63
+ * @param endLine - The line number within the bundle the range ends.
64
+ * @param endColumn - The column in the given `startLine` on that the range ends (inclusive).
48
65
  */
49
- putCoverage(fileId: string, line: number, column: number): void;
66
+ putCoverage(fileId: string, startLine: number, startColumn: number, endLine: number, endColumn: number): boolean;
50
67
  /**
51
68
  * Map to the original file position.
52
69
  *
@@ -61,7 +78,8 @@ export declare class Session {
61
78
  * @param fileId - The identifier of the file bundle.
62
79
  * @param sourceMapText - The actual source map.
63
80
  */
64
- putSourcemap(fileId: string, sourceMapText: string): void;
81
+ putSourcemap(fileId: string, sourceMapText: string): Promise<void>;
82
+ private processUnmappedCoverageOf;
65
83
  /**
66
84
  * Destroy the session and free the memory it allocates.
67
85
  * In particular the sourcemaps are freed (important to not run out of memory!).
@@ -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];
@@ -40,6 +44,7 @@ class Session {
40
44
  this.storage = commons_1.Contract.requireDefined(storage);
41
45
  this.logger = commons_1.Contract.requireDefined(logger);
42
46
  this.sourceMaps = new Map();
47
+ this.unmappedCoverage = new Map();
43
48
  this.projectId = ''; // We currently only support coverage for one project.
44
49
  }
45
50
  /**
@@ -47,17 +52,66 @@ class Session {
47
52
  * This method also conducts the mapping based on the source map.
48
53
  *
49
54
  * @param fileId - The identifier of the instrumented bundle (file).
50
- * @param line - The line number within the bundle.
51
- * @param column - The column within the bundle.
55
+ * @param startLine - The line number within the bundle the range starts.
56
+ * @param startColumn - The column in the given `startLine` on that the range starts (inclusive).
57
+ * @param endLine - The line number within the bundle the range ends.
58
+ * @param endColumn - The column in the given `startLine` on that the range ends (inclusive).
52
59
  */
53
- putCoverage(fileId, line, column) {
54
- const originalPosition = this.mapToOriginal(fileId, line, column);
55
- if (originalPosition.line && originalPosition.source) {
56
- this.storage.putCoverage(this.projectId, originalPosition.source, [originalPosition.line]);
60
+ putCoverage(fileId, startLine, startColumn, endLine, endColumn) {
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;
57
71
  }
58
- else {
59
- this.storage.signalUnmappedCoverage(this.projectId);
72
+ let mapped = false;
73
+ // Iterate over the lines to scan
74
+ let line = startLine;
75
+ while (line <= endLine) {
76
+ // Determine the column range to consider for this line
77
+ let scanFromColumn;
78
+ if (line === startLine) {
79
+ scanFromColumn = startColumn;
80
+ }
81
+ else {
82
+ scanFromColumn = 0;
83
+ }
84
+ let scanToColumn;
85
+ if (line === endLine) {
86
+ scanToColumn = endColumn;
87
+ }
88
+ else {
89
+ // Since we do not know the length of the different lines, we assume
90
+ // all to end in the lager of `endColumn` and `startColumn`.
91
+ // A better estimate (or the correct value) is supposed to be implemented
92
+ // in context of TS-30077.
93
+ scanToColumn = Math.max(endColumn, startColumn);
94
+ }
95
+ let column = scanFromColumn;
96
+ let lastCoveredLine = -1;
97
+ while (column <= scanToColumn) {
98
+ const originalPosition = this.mapToOriginal(fileId, line, column);
99
+ if (originalPosition.line && originalPosition.source) {
100
+ if (lastCoveredLine !== originalPosition.line) {
101
+ this.storage.putCoverage(this.projectId, originalPosition.source, [originalPosition.line]);
102
+ mapped = true;
103
+ lastCoveredLine = originalPosition.line;
104
+ }
105
+ }
106
+ // Step to the next column to map back to the original.
107
+ // `originalPosition.name` is the token on the position, that is, if it is present
108
+ // we increment the column by its length.
109
+ column = column + Math.max(1, (_b = (_a = originalPosition.name) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 1);
110
+ }
111
+ // And the next line
112
+ line++;
60
113
  }
114
+ return mapped;
61
115
  }
62
116
  /**
63
117
  * Map to the original file position.
@@ -82,14 +136,24 @@ class Session {
82
136
  * @param fileId - The identifier of the file bundle.
83
137
  * @param sourceMapText - The actual source map.
84
138
  */
85
- putSourcemap(fileId, sourceMapText) {
139
+ async putSourcemap(fileId, sourceMapText) {
86
140
  const rawSourceMap = JSON.parse(sourceMapText);
87
- new sourceMap.SourceMapConsumer(rawSourceMap)
88
- .then(consumer => {
89
- this.sourceMaps.set(fileId, consumer);
90
- })
91
- .catch(e => {
141
+ try {
142
+ const sourceMapConsumer = await new sourceMap.SourceMapConsumer(rawSourceMap);
143
+ this.sourceMaps.set(fileId, sourceMapConsumer);
144
+ this.processUnmappedCoverageOf(fileId);
145
+ }
146
+ catch (e) {
92
147
  this.logger.error(`Consuming source map failed! ${e}`);
148
+ }
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
+ }
93
157
  });
94
158
  }
95
159
  /**
@@ -1,4 +1,4 @@
1
- import { Logger } from 'winston';
1
+ import Logger from 'bunyan';
2
2
  /**
3
3
  * Lines covered for the specified file.
4
4
  */
@@ -21,11 +21,14 @@ export interface IReadableStorage {
21
21
  */
22
22
  getCoverageBySourceFile(project: string): IterableIterator<FileCoverage> | undefined;
23
23
  /**
24
- * Write the coverage to the specified file.
24
+ * Write the coverage to the specified file. A timestamp will be appended to the provided file path.
25
25
  *
26
- * @param filePath - Full path of the file to write the coverage to.
26
+ * @param coverageFolder - Full path of the file to write the coverage to.
27
+ * @param date - Date to use for the appended timestamp
28
+ *
29
+ * @return The number of lines written
27
30
  */
28
- dumpToSimpleCoverageFile(filePath: string): void;
31
+ dumpToSimpleCoverageFile(coverageFolder: string, date: Date): [string, number];
29
32
  }
30
33
  /**
31
34
  * Storage interface for writing information.
@@ -46,6 +49,10 @@ export interface IWriteableStorage {
46
49
  * @param project - The project to add the information to.
47
50
  */
48
51
  signalUnmappedCoverage(project: string): void;
52
+ /**
53
+ * Discard the coverage information that has been collected up to this point.
54
+ */
55
+ discardCollectedCoverage(): void;
49
56
  }
50
57
  /**
51
58
  * Union of write and read interface.
@@ -89,7 +96,7 @@ export declare class DataStorage implements IDataStorage {
89
96
  /**
90
97
  * Coverage information by project.
91
98
  */
92
- private readonly coverageByProject;
99
+ private coverageByProject;
93
100
  /**
94
101
  * Logger to use.
95
102
  */
@@ -98,6 +105,10 @@ export declare class DataStorage implements IDataStorage {
98
105
  * Times unmapped coverage received.
99
106
  */
100
107
  private timesUnmappedCoverage;
108
+ /**
109
+ * Date format for the timestamp appended to the coverage files
110
+ */
111
+ readonly DATE_FORMAT = "YYYY-MM-DD-HH-mm-ss.SSS";
101
112
  /**
102
113
  * Constructs the data storage.
103
114
  *
@@ -118,7 +129,7 @@ export declare class DataStorage implements IDataStorage {
118
129
  *
119
130
  * @param sourceFile - The file name to normalize, produced by the instrumenter.
120
131
  */
121
- private normalizeSourceFileName;
132
+ private static normalizeSourceFileName;
122
133
  /**
123
134
  * {@inheritDoc IWriteableStorage.signalUnmappedCoverage}
124
135
  */
@@ -128,11 +139,31 @@ export declare class DataStorage implements IDataStorage {
128
139
  */
129
140
  getCoverageBySourceFile(project: string): IterableIterator<FileCoverage> | undefined;
130
141
  /**
131
- * {@inheritDoc IReadableStorage.writeToSimpleCoverageFile}
142
+ * @inheritDoc
143
+ */
144
+ dumpToSimpleCoverageFile(coverageFolder: string, date: Date): [string, number];
145
+ /**
146
+ * Set the collected coverage to 0 for all projects
147
+ * @private
132
148
  */
133
- dumpToSimpleCoverageFile(filePath: string): number;
149
+ private resetCoverage;
150
+ /**
151
+ * Appends the timestamp given with date to the coverageFolder (before the file ending if there is one)
152
+ * @param coverageFolder Path to the coverage file
153
+ * @param date Represents the timestamp to be appended with the format {@link DataStorage.DATE_FORMAT}
154
+ * @private
155
+ */
156
+ private initCoverageFile;
157
+ /**
158
+ * Generate simple coverage format for the collected coverage
159
+ */
160
+ private toSimpleCoverage;
134
161
  /**
135
162
  * {@inheritDoc IReadableStorage.getProjects}
136
163
  */
137
164
  getProjects(): string[];
165
+ /**
166
+ * {@inheritDoc IWritableStorage.discardCollectedCoverage}
167
+ */
168
+ discardCollectedCoverage(): void;
138
169
  }
@@ -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];
@@ -18,10 +22,15 @@ var __importStar = (this && this.__importStar) || function (mod) {
18
22
  __setModuleDefault(result, mod);
19
23
  return result;
20
24
  };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
21
28
  Object.defineProperty(exports, "__esModule", { value: true });
22
29
  exports.DataStorage = exports.ProjectCoverage = void 0;
23
30
  const commons_1 = require("@cqse/commons");
24
31
  const fs = __importStar(require("fs"));
32
+ const path_1 = __importDefault(require("path"));
33
+ const dat = __importStar(require("date-and-time"));
25
34
  /**
26
35
  * The coverage information received for one particular project.
27
36
  */
@@ -77,6 +86,10 @@ class DataStorage {
77
86
  * @param logger - The logger to use.
78
87
  */
79
88
  constructor(logger) {
89
+ /**
90
+ * Date format for the timestamp appended to the coverage files
91
+ */
92
+ this.DATE_FORMAT = 'YYYY-MM-DD-HH-mm-ss.SSS';
80
93
  this.coverageByProject = new Map();
81
94
  this.logger = commons_1.Contract.requireDefined(logger);
82
95
  this.timesUnmappedCoverage = 0;
@@ -89,7 +102,7 @@ class DataStorage {
89
102
  * @param coveredOriginalLines - The lines covered in the file.
90
103
  */
91
104
  putCoverage(project, sourceFilePath, coveredOriginalLines) {
92
- const uniformPath = this.normalizeSourceFileName(sourceFilePath);
105
+ const uniformPath = DataStorage.normalizeSourceFileName(sourceFilePath);
93
106
  let projectCoverage = this.coverageByProject.get(project);
94
107
  if (!projectCoverage) {
95
108
  projectCoverage = new ProjectCoverage(project);
@@ -104,7 +117,7 @@ class DataStorage {
104
117
  *
105
118
  * @param sourceFile - The file name to normalize, produced by the instrumenter.
106
119
  */
107
- normalizeSourceFileName(sourceFile) {
120
+ static normalizeSourceFileName(sourceFile) {
108
121
  return (0, commons_1.removePrefix)('webpack:///', sourceFile.replace('\\', '/'));
109
122
  }
110
123
  /**
@@ -125,29 +138,55 @@ class DataStorage {
125
138
  return projectCoverage === null || projectCoverage === void 0 ? void 0 : projectCoverage.getCoverage();
126
139
  }
127
140
  /**
128
- * {@inheritDoc IReadableStorage.writeToSimpleCoverageFile}
141
+ * @inheritDoc
129
142
  */
130
- dumpToSimpleCoverageFile(filePath) {
131
- const toSimpleCoverage = () => {
132
- const result = [];
133
- commons_1.Contract.require(this.getProjects().length < 2, 'Only one project supported to be handled in parallel.');
134
- for (const project of this.getProjects()) {
135
- const projectCoverage = this.getCoverageBySourceFile(project);
136
- if (!projectCoverage) {
137
- return [0, ''];
138
- }
139
- for (const entry of projectCoverage) {
140
- result.push(this.normalizeSourceFileName(entry.sourceFile));
141
- for (const lineNo of entry.coveredLines) {
142
- result.push(String(lineNo));
143
- }
143
+ dumpToSimpleCoverageFile(coverageFolder, date) {
144
+ const [lines, content] = this.toSimpleCoverage();
145
+ const coverageFolderTrimmed = coverageFolder.trim();
146
+ const finalFilePath = this.initCoverageFile(coverageFolderTrimmed, date);
147
+ fs.writeFileSync(finalFilePath, content, { flag: 'w', encoding: 'utf8' });
148
+ this.resetCoverage();
149
+ return [finalFilePath, lines];
150
+ }
151
+ /**
152
+ * Set the collected coverage to 0 for all projects
153
+ * @private
154
+ */
155
+ resetCoverage() {
156
+ this.coverageByProject = new Map();
157
+ }
158
+ /**
159
+ * Appends the timestamp given with date to the coverageFolder (before the file ending if there is one)
160
+ * @param coverageFolder Path to the coverage file
161
+ * @param date Represents the timestamp to be appended with the format {@link DataStorage.DATE_FORMAT}
162
+ * @private
163
+ */
164
+ initCoverageFile(coverageFolder, date) {
165
+ if (!fs.existsSync(coverageFolder)) {
166
+ fs.mkdirSync(coverageFolder);
167
+ }
168
+ const formattedDate = dat.format(date, this.DATE_FORMAT);
169
+ return path_1.default.join(coverageFolder, `coverage-${formattedDate}.simple`);
170
+ }
171
+ /**
172
+ * Generate simple coverage format for the collected coverage
173
+ */
174
+ toSimpleCoverage() {
175
+ const result = [];
176
+ commons_1.Contract.require(this.getProjects().length < 2, 'Only one project supported to be handled in parallel.');
177
+ for (const project of this.getProjects()) {
178
+ const projectCoverage = this.getCoverageBySourceFile(project);
179
+ if (!projectCoverage) {
180
+ return [0, ''];
181
+ }
182
+ for (const entry of projectCoverage) {
183
+ result.push(DataStorage.normalizeSourceFileName(entry.sourceFile));
184
+ for (const lineNo of entry.coveredLines) {
185
+ result.push(String(lineNo));
144
186
  }
145
187
  }
146
- return [result.length, result.join('\n')];
147
- };
148
- const [lines, content] = toSimpleCoverage();
149
- fs.writeFileSync(filePath.trim(), content, { flag: 'w', encoding: 'utf8' });
150
- return lines;
188
+ }
189
+ return [result.length, result.join('\n')];
151
190
  }
152
191
  /**
153
192
  * {@inheritDoc IReadableStorage.getProjects}
@@ -155,5 +194,11 @@ class DataStorage {
155
194
  getProjects() {
156
195
  return Array.from(this.coverageByProject.keys());
157
196
  }
197
+ /**
198
+ * {@inheritDoc IWritableStorage.discardCollectedCoverage}
199
+ */
200
+ discardCollectedCoverage() {
201
+ this.coverageByProject.clear();
202
+ }
158
203
  }
159
204
  exports.DataStorage = DataStorage;
@@ -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,66 @@
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
+ const ProxyUpload_1 = require("./ProxyUpload");
10
+ /**
11
+ * Uploads a coverage file to artifactory with the provided configuration.
12
+ */
13
+ async function uploadToArtifactory(config, logger, coverageFile, lines) {
14
+ if (!(config.artifactory_access_token || (config.artifactory_user && config.artifactory_password))) {
15
+ throw new CommonUpload_1.UploadError('API key or user name and password must be configured!');
16
+ }
17
+ if (lines === 0) {
18
+ return;
19
+ }
20
+ logger.info('Preparing upload to Artifactory');
21
+ const form = (0, CommonUpload_1.prepareFormData)(coverageFile);
22
+ await performArtifactoryUpload(config, form, logger);
23
+ }
24
+ exports.uploadToArtifactory = uploadToArtifactory;
25
+ async function performArtifactoryUpload(config, form, logger) {
26
+ var _a;
27
+ if (!config.teamscale_commit) {
28
+ throw new CommonUpload_1.UploadError('The "--teamscale-commit" option must be set with a valid branch and timestamp.');
29
+ }
30
+ const branchAndTimestamp = config.teamscale_commit.split(':');
31
+ let url = `${(_a = config.artifactory_server_url) === null || _a === void 0 ? void 0 : _a.replace(/\/$/, '')}/uploads/${branchAndTimestamp[0]}/${branchAndTimestamp[1]}`;
32
+ if (config.teamscale_revision) {
33
+ url = url + `-${config.teamscale_revision}`;
34
+ }
35
+ url = url + `/${config.teamscale_partition}/simple`;
36
+ if (config.artifactory_path_suffix !== undefined) {
37
+ url = `${url}/${config.artifactory_path_suffix}`;
38
+ }
39
+ url = `${url}/report.simple`;
40
+ await (0, CommonUpload_1.performUpload)(url, form, prepareArtifactoryConfig(config, form), axios_1.default.put, logger);
41
+ }
42
+ function prepareArtifactoryConfig(config, form) {
43
+ var _a, _b;
44
+ const proxyConfig = (0, ProxyUpload_1.extractProxyOptions)(config);
45
+ if (config.artifactory_access_token) {
46
+ return {
47
+ headers: {
48
+ Accept: '*/*',
49
+ 'X-JFrog-Art-Api': config.artifactory_access_token,
50
+ 'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`
51
+ },
52
+ proxy: proxyConfig
53
+ };
54
+ }
55
+ return {
56
+ auth: {
57
+ username: (_a = config.artifactory_user) !== null && _a !== void 0 ? _a : 'no username provided',
58
+ password: (_b = config.artifactory_password) !== null && _b !== void 0 ? _b : 'no password provided'
59
+ },
60
+ headers: {
61
+ Accept: '*/*',
62
+ 'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`
63
+ },
64
+ proxy: proxyConfig
65
+ };
66
+ }
@@ -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 { AxiosProxyConfig } from "axios";
3
+ /**
4
+ * Creates an AxiosProxyConfig object if proxy variables are provided.
5
+ */
6
+ export declare function extractProxyOptions(config: ConfigParameters): AxiosProxyConfig | undefined;
@@ -0,0 +1,30 @@
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.extractProxyOptions = void 0;
7
+ const url_1 = __importDefault(require("url"));
8
+ /**
9
+ * Creates an AxiosProxyConfig object if proxy variables are provided.
10
+ */
11
+ function extractProxyOptions(config) {
12
+ if (config.http_proxy) {
13
+ // Expected format: http://username:password@host:port/
14
+ // See https://nodejs.org/api/url.html#url-strings-and-url-objects for URL parsing
15
+ const proxyAddress = new url_1.default.URL(config.http_proxy);
16
+ const proxyConfig = {
17
+ protocol: proxyAddress.protocol.replace(':', ''),
18
+ host: proxyAddress.hostname,
19
+ port: +proxyAddress.port,
20
+ };
21
+ if (proxyAddress.username && proxyAddress.password) {
22
+ proxyConfig.auth = {
23
+ username: proxyAddress.username,
24
+ password: proxyAddress.password
25
+ };
26
+ }
27
+ return proxyConfig;
28
+ }
29
+ }
30
+ exports.extractProxyOptions = extractProxyOptions;
@@ -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>;