@testomatio/reporter 0.8.0-beta.3 → 0.8.0-beta.31

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,48 +1,56 @@
1
+ const debug = require('debug')('@testomatio/reporter:pipe:github');
2
+ const path = require('path');
1
3
  const chalk = require('chalk');
2
- const { Octokit } = require("@octokit/rest");
4
+ const humanizeDuration = require('humanize-duration');
5
+ const merge = require('lodash.merge');
6
+ const { Octokit } = require('@octokit/rest');
3
7
  const { APP_PREFIX } = require('../constants');
4
8
  const { ansiRegExp, isSameTest } = require('../util');
5
- const merge = require('lodash.merge');
6
-
7
9
 
8
10
  class GitHubPipe {
9
-
10
- isEnabled = false;
11
-
12
11
  constructor(params, store = {}) {
12
+ this.isEnabled = false;
13
13
  this.store = store;
14
- this.tests = []
14
+ this.tests = [];
15
15
  this.token = params.GH_PAT || process.env.GH_PAT;
16
- this.ref = process.env.GITHUB_REF
17
- this.repo = process.env.GITHUB_ACTION_REPOSITORY
16
+ this.ref = process.env.GITHUB_REF;
17
+ this.repo = process.env.GITHUB_REPOSITORY;
18
+ this.hiddenCommentData = `<!--- testomat.io report ${process.env.GITHUB_WORKFLOW || ''} -->`;
19
+
20
+ debug('GitHub Pipe: ', this.token ? 'TOKEN' : '*no token*', 'Ref:', this.ref, 'Repo:', this.repo);
21
+
18
22
  if (!this.token || !this.ref || !this.repo) return;
19
23
  this.isEnabled = true;
20
- this.issue = this.ref.match(/refs\/pull\/(\d+)\/merge/)[1]
24
+ const matchedIssue = this.ref.match(/refs\/pull\/(\d+)\/merge/);
25
+ if (!matchedIssue) return;
26
+ this.issue = matchedIssue[1];
21
27
  this.start = new Date();
28
+
29
+ debug('GitHub Pipe: Enabled');
22
30
  }
23
-
31
+
24
32
  async createRun() {}
25
33
 
26
34
  updateRun() {}
27
35
 
28
36
  addTest(test) {
37
+ debug('Adding test:', test);
29
38
  if (!this.isEnabled) return;
30
39
 
31
- const index = this.tests.findIndex(t => isSameTest(t, test))
40
+ const index = this.tests.findIndex(t => isSameTest(t, test));
32
41
  // update if they were already added
33
42
  if (index >= 0) {
34
43
  this.tests[index] = merge(this.tests[index], test);
35
44
  return;
36
45
  }
37
46
 
38
- this.tests.push(test)
47
+ this.tests.push(test);
39
48
  }
40
49
 
41
50
  async finishRun(runParams) {
42
51
  if (!this.isEnabled) return;
43
52
 
44
53
  if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
45
-
46
54
 
47
55
  this.octokit = new Octokit({
48
56
  auth: this.token,
@@ -56,15 +64,17 @@ class GitHubPipe {
56
64
  const failedCount = this.tests.filter(t => t.status === 'failed').length;
57
65
  const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
58
66
 
59
- let summary = `
60
- ### Run Report
67
+ let summary = `${this.hiddenCommentData}
61
68
 
62
- | | ${statusEmoji(runParams.status)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
69
+ | [![Testomat.io Report](https://avatars.githubusercontent.com/u/59105116?s=36&v=4)](https://testomat.io) | ${
70
+ statusEmoji(runParams.status,)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
63
71
  | --- | --- |
64
72
  | Tests | ✔️ **${this.tests.length}** tests run |
65
- | Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji('passed')} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
66
- | Duration | 🕐 **${parseFloat(this.tests.reduce((a, t) => a + (t.run_time || 0), 0)).toFixed(2)}**ms |
67
- `
73
+ | Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
74
+ 'passed',
75
+ )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
76
+ | Duration | 🕐 **${humanizeDuration(parseFloat(this.tests.reduce((a, t) => a + (t.run_time || 0), 0)))}** |
77
+ `;
68
78
  if (this.store.runUrl) {
69
79
  summary += `| Testomat.io Report | 📊 [Run #${this.store.runId}](${this.store.runUrl}) | `;
70
80
  }
@@ -81,83 +91,145 @@ class GitHubPipe {
81
91
  summary += `| Run Attempt | 🌒 \`${process.env.GITHUB_RUN_ATTEMPT}\` | `;
82
92
  }
83
93
  if (process.env.GITHUB_RUN_ID) {
84
- summary += `| Build Log | ✒️ ${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${this.repo}/actions/runs/${process.env.GITHUB_RUN_ID} | `;
94
+ summary += `| Build Log | ✒️ ${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${
95
+ this.repo
96
+ }/actions/runs/${process.env.GITHUB_RUN_ID} | `;
85
97
  }
86
98
 
99
+ const failures = this.tests
100
+ .filter(t => t.status === 'failed')
101
+ .slice(0, 20)
102
+ .map(t => {
103
+ let text = `#### ${statusEmoji('failed')} ${fullName(t)} `;
104
+ text += '\n\n';
105
+ if (t.message)
106
+ text += `> ${t.message
107
+ .replace(/[^\x20-\x7E]/g, '')
108
+ .replace(ansiRegExp(), '')
109
+ .trim()}\n`;
110
+ if (t.stack) text += `\`\`\`diff\n${t.stack.replace(ansiRegExp(), '').trim()}\n\`\`\`\n`;
111
+
112
+ if (t.artifacts && t.artifacts.length && !process.env.TESTOMATIO_PRIVATE_ARTIFACTS) {
113
+ t.artifacts
114
+ .filter(f => !!f)
115
+ .filter(f => f.endsWith('.png'))
116
+ .forEach(f => {
117
+ if (f.endsWith('.png')) {
118
+ text += `![](${f})\n`;
119
+ return text;
120
+ }
121
+ text += `[📄 ${path.basename(f)}](${f})\n`;
122
+ return text;
123
+ });
124
+ }
125
+
126
+ text += '\n---\n';
127
+
128
+ return text;
129
+ });
87
130
 
88
- const failures = this.tests.filter(t => t.status === 'failed').slice(0, 20).map(t => {
89
- let text = `#### ${statusEmoji('failed')} ${fullName(t)} `;
90
- text += "\n\n"
91
- if (t.message) text += "> " + t.message.replace(/[^\x20-\x7E]/g, '').replace(ansiRegExp(), '').trim() + "\n"
92
- if (t.stack) text += "```diff\n" + t.stack.replace(ansiRegExp(), '').trim() + "\n```\n";
93
-
94
- if (t.artifacts && t.artifacts.length && !process.env.TESTOMATIO_PRIVATE_ARTIFACTS) {
95
- t.artifacts.filter(f => f.endsWith('.png')).forEach(f => {
96
- if (f.endsWith('.png')) return text+= `![](${f})\n`
97
- });
98
- }
99
-
100
- text += "\n---\n"
101
-
102
- return text;
103
- })
104
-
105
131
  let body = summary;
106
132
 
107
133
  if (failures.length) {
108
- body += `<details>\n<summary>### 🟥 Failures (${failures.length})</summary>\n${failures.join('\n')} </details>`;
109
- }
110
-
111
- if (failures.length > 20) {
112
- body += "\n> Notice\n> Only first 20 failures shown*"
134
+ body += `\n<details>\n<summary><h3>🟥 Failures (${failures.length})</h4></summary>\n\n${failures.join('\n')}\n`;
135
+ if (failures.length > 20) {
136
+ body += '\n> Notice\n> Only first 20 failures shown*';
137
+ }
138
+ body += '\n\n</details>';
113
139
  }
114
140
 
115
141
  if (this.tests.length > 0) {
116
- body += "\n### 🐢 Slowest Tests\n\n"
117
- body += this.tests.sort((a, b) => b?.run_time - a?.run_time).slice(0, 5).map(t => {
118
- return `* ${fullName(t)} (${parseFloat(t.run_time).toFixed(2)}ms)`
119
- }).join('\n')
142
+ body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
143
+ body += this.tests
144
+ // eslint-disable-next-line no-unsafe-optional-chaining
145
+ .sort((a, b) => b?.run_time - a?.run_time)
146
+ .slice(0, 5)
147
+ .map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
148
+ .join('\n');
149
+ body += '\n</details>';
120
150
  }
121
151
 
152
+ await deletePreviousReport(this.octokit, owner, repo, this.issue, this.hiddenCommentData);
153
+
154
+ // add report as comment
122
155
  try {
156
+ debug('Adding comment\n', body);
123
157
  const resp = await this.octokit.rest.issues.createComment({
124
158
  owner,
125
159
  repo,
126
160
  issue_number: this.issue,
127
161
  body,
128
162
  });
129
-
163
+
130
164
  const url = resp.data?.html_url;
165
+ debug('Comment URL:', url);
131
166
  this.store.githubUrl = url;
132
-
133
- console.log(
134
- APP_PREFIX,
135
- chalk.yellow('GitHub'),
136
- `Report created: ${chalk.magenta(url)}`,
137
- );
167
+
168
+ console.log(APP_PREFIX, chalk.yellow('GitHub'), `Report created: ${chalk.magenta(url)}`);
138
169
  } catch (err) {
139
- console.log(
140
- APP_PREFIX,
141
- chalk.yellow('GitHub'),
142
- `Couldn't create GitHub report ${err}`,
143
- );
170
+ console.log(APP_PREFIX, chalk.yellow('GitHub'), `Couldn't create GitHub report ${err}`);
144
171
  }
145
172
  }
173
+
174
+ toString() {
175
+ return 'GitHub Reporter';
176
+ }
146
177
  }
147
178
 
148
179
  function statusEmoji(status) {
149
180
  if (status === 'passed') return '🟢';
150
181
  if (status === 'failed') return '🔴';
151
182
  if (status === 'skipped') return '🟡';
152
- return ''
183
+ return '';
153
184
  }
154
185
 
155
186
  function fullName(t) {
156
187
  let line = '';
157
- if (t.suite_title) line = `${t.suite_title}: `;
158
- line += `**${t.title}**`
188
+ if (t.suite_title) line = `${t.suite_title}: `;
189
+ line += `**${t.title}**`;
159
190
  if (t.example) line += ` \`[${Object.values(t.example)}]\``;
160
191
  return line;
161
192
  }
162
193
 
163
- module.exports = GitHubPipe;
194
+ async function deletePreviousReport(octokit, owner, repo, issue, hiddenCommentData) {
195
+ if (process.env.GITHUB_KEEP_OUTDATED_REPORTS) return;
196
+
197
+ // get comments
198
+ let comments = [];
199
+ try {
200
+ const response = await octokit.rest.issues.listComments({
201
+ owner,
202
+ repo,
203
+ issue_number: issue,
204
+ });
205
+ comments = response.data;
206
+ } catch (e) {
207
+ console.error('Error while attempt to retrieve comments on GitHub Pull Request:\n', e);
208
+ }
209
+
210
+ if (!comments.length) return;
211
+
212
+ for (const comment of comments) {
213
+ // if comment was left by the same workflow
214
+ if (comment.body.includes(hiddenCommentData)) {
215
+ try {
216
+ // delete previous comment
217
+ await octokit.rest.issues.deleteComment({
218
+ owner,
219
+ repo,
220
+ issue_number: issue,
221
+ comment_id: comment.id,
222
+ });
223
+ } catch (e) {
224
+ console.warn(`Can't delete previously added comment with testomat.io report. Ignore.`);
225
+ }
226
+
227
+ // pass next env var if need to clear all previous reports;
228
+ // only the last one is removed by default
229
+ if (!process.env.GITHUB_REMOVE_ALL_OUTDATED_REPORTS) break;
230
+ // TODO: in case of many reports should implement pagination
231
+ }
232
+ }
233
+ }
234
+
235
+ module.exports = GitHubPipe;
@@ -0,0 +1,225 @@
1
+ const debug = require('debug')('@testomatio/reporter:pipe:gitlab');
2
+ const axios = require('axios');
3
+ const chalk = require('chalk');
4
+ const humanizeDuration = require('humanize-duration');
5
+ const merge = require('lodash.merge');
6
+ const path = require('path');
7
+ const { APP_PREFIX } = require('../constants');
8
+ const { ansiRegExp, isSameTest } = require('../util');
9
+
10
+ //! GITLAB_PAT environment variable is required for this functionality to work
11
+ //! and your pipeline trigger should be merge_request
12
+
13
+ class GitLabPipe {
14
+ constructor(params, store = {}) {
15
+ this.isEnabled = false;
16
+ this.ENV = process.env;
17
+ this.store = store;
18
+ this.tests = [];
19
+ // GitLab PAT looks like glpat-nKGdja3jsG4850sGksh7
20
+ this.token = params.GITLAB_PAT || this.ENV.GITLAB_PAT;
21
+ this.hiddenCommentData = `<!--- testomat.io report ${process.env.CI_JOB_NAME || ''} -->`;
22
+ this.axios = axios.create();
23
+
24
+ debug(
25
+ chalk.yellow('GitLab Pipe:'),
26
+ this.token ? 'TOKEN passed' : '*no token*',
27
+ `Project id: ${this.ENV.CI_PROJECT_ID}, MR id: ${this.ENV.CI_MERGE_REQUEST_IID}`,
28
+ );
29
+
30
+ if (!this.ENV.CI_PROJECT_ID || !this.ENV.CI_MERGE_REQUEST_IID) {
31
+ debug(`CI pipeline should be run in Merge Request to have ability to add the report comment.`);
32
+ return;
33
+ }
34
+
35
+ if (!this.token) {
36
+ debug(`Hint: GitLab CI variables are unavailable for unprotected branches by default.`);
37
+ return;
38
+ }
39
+
40
+ this.isEnabled = true;
41
+
42
+ debug('GitLab Pipe: Enabled');
43
+ }
44
+
45
+ async createRun() {}
46
+
47
+ addTest(test) {
48
+ if (!this.isEnabled) return;
49
+
50
+ const index = this.tests.findIndex(t => isSameTest(t, test));
51
+ // update if they were already added
52
+ if (index >= 0) {
53
+ this.tests[index] = merge(this.tests[index], test);
54
+ return;
55
+ }
56
+
57
+ this.tests.push(test);
58
+ }
59
+
60
+ async finishRun(runParams) {
61
+ if (!this.isEnabled) return;
62
+
63
+ if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
64
+
65
+ // ... create a comment on GitLab
66
+ const passedCount = this.tests.filter(t => t.status === 'passed').length;
67
+ const failedCount = this.tests.filter(t => t.status === 'failed').length;
68
+ const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
69
+
70
+ // constructing the table
71
+ let summary = `${this.hiddenCommentData}
72
+
73
+ | [![Testomat.io Report](https://avatars.githubusercontent.com/u/59105116?s=36&v=4)](https://testomat.io) | ${
74
+ statusEmoji(runParams.status)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
75
+ | --- | --- |
76
+ | Tests | ✔️ **${this.tests.length}** tests run |
77
+ | Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
78
+ 'passed',
79
+ )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
80
+ | Duration | 🕐 **${humanizeDuration(parseFloat(this.tests.reduce((a, t) => a + (t.run_time || 0), 0)))}** |
81
+ `;
82
+
83
+ if (this.ENV.CI_JOB_NAME && this.ENV.CI_JOB_ID) {
84
+ // eslint-disable-next-line max-len
85
+ summary += `| Job | 👷 [${this.ENV.CI_JOB_ID}](${this.ENV.CI_JOB_URL})<br>Name: **${this.ENV.CI_JOB_NAME}**<br>Stage: **${this.ENV.CI_JOB_STAGE}** | `;
86
+ }
87
+
88
+ const failures = this.tests
89
+ .filter(t => t.status === 'failed')
90
+ .slice(0, 20)
91
+ .map(t => {
92
+ let text = `#### ${statusEmoji('failed')} ${fullName(t)} `;
93
+ text += '\n\n';
94
+ if (t.message)
95
+ text += `> ${t.message
96
+ .replace(/[^\x20-\x7E]/g, '')
97
+ .replace(ansiRegExp(), '')
98
+ .trim()}\n`;
99
+ if (t.stack) text += `\`\`\`diff\n${t.stack.replace(ansiRegExp(), '').trim()}\n\`\`\`\n`;
100
+
101
+ if (t.artifacts && t.artifacts.length && !this.ENV.TESTOMATIO_PRIVATE_ARTIFACTS) {
102
+ t.artifacts
103
+ .filter(f => !!f)
104
+ .filter(f => f.endsWith('.png'))
105
+ .forEach(f => {
106
+ if (f.endsWith('.png')) {
107
+ text += `![](${f})\n`;
108
+ return text;
109
+ }
110
+ text += `[📄 ${path.basename(f)}](${f})\n`;
111
+ return text;
112
+ });
113
+ }
114
+
115
+ text += '\n---\n';
116
+
117
+ return text;
118
+ });
119
+
120
+ let body = summary;
121
+
122
+ if (failures.length) {
123
+ body += `\n<details>\n<summary><h3>🟥 Failures (${failures.length})</h4></summary>\n\n${failures.join('\n')}\n`;
124
+ if (failures.length > 20) {
125
+ body += '\n> Notice\n> Only first 20 failures shown*';
126
+ }
127
+ body += '\n\n</details>';
128
+ }
129
+
130
+ if (this.tests.length > 0) {
131
+ body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
132
+ body += this.tests
133
+ // eslint-disable-next-line no-unsafe-optional-chaining
134
+ .sort((a, b) => b?.run_time - a?.run_time)
135
+ .slice(0, 5)
136
+ .map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
137
+ .join('\n');
138
+ body += '\n</details>';
139
+ }
140
+
141
+ // eslint-disable-next-line max-len
142
+ const commentsRequestURL = `https://gitlab.com/api/v4/projects/${this.ENV.CI_PROJECT_ID}/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}/notes`;
143
+
144
+ // delete previous report
145
+ await deletePreviousReport(this.axios, commentsRequestURL, this.hiddenCommentData, this.token);
146
+
147
+ // add current report
148
+ debug(`Adding comment via url: ${commentsRequestURL}`);
149
+
150
+ try {
151
+ const addCommentResponse = await this.axios.post(`${commentsRequestURL}?access_token=${this.token}`, { body });
152
+
153
+ const commentID = addCommentResponse.data.id;
154
+ // eslint-disable-next-line max-len
155
+ const commentURL = `${this.ENV.CI_PROJECT_URL}/-/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}#note_${commentID}`;
156
+
157
+ console.log(APP_PREFIX, chalk.yellow('GitLab'), `Report created: ${chalk.magenta(commentURL)}`);
158
+ } catch (err) {
159
+ console.error(
160
+ APP_PREFIX,
161
+ chalk.yellow('GitLab'),
162
+ `Couldn't create GitLab report\n${err}.
163
+ Request url: ${commentsRequestURL}
164
+ Request data: ${body}`,
165
+ );
166
+ }
167
+ }
168
+
169
+ toString() {
170
+ return 'GitLab Reporter';
171
+ }
172
+
173
+ updateRun() {}
174
+ }
175
+
176
+ function statusEmoji(status) {
177
+ if (status === 'passed') return '🟢';
178
+ if (status === 'failed') return '🔴';
179
+ if (status === 'skipped') return '🟡';
180
+ return '';
181
+ }
182
+
183
+ function fullName(t) {
184
+ let line = '';
185
+ if (t.suite_title) line = `${t.suite_title}: `;
186
+ line += `**${t.title}**`;
187
+ if (t.example) line += ` \`[${Object.values(t.example)}]\``;
188
+ return line;
189
+ }
190
+
191
+ async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCommentData, token) {
192
+ if (process.env.GITLAB_KEEP_OUTDATED_REPORTS) return;
193
+
194
+ // get comments
195
+ let comments = [];
196
+
197
+ try {
198
+ const response = await axiosInstance.get(`${commentsRequestURL}?access_token=${token}`);
199
+ comments = response.data;
200
+ } catch (e) {
201
+ console.error('Error while attempt to retrieve comments on GitLab Merge Request:\n', e);
202
+ }
203
+
204
+ if (!comments.length) return;
205
+
206
+ for (const comment of comments) {
207
+ // if comment was left by the same workflow
208
+ if (comment.body.includes(hiddenCommentData)) {
209
+ try {
210
+ // delete previous comment
211
+ const deleteCommentURL = `${commentsRequestURL}/${comment.id}?access_token=${token}`;
212
+ await axiosInstance.delete(deleteCommentURL);
213
+ } catch (e) {
214
+ console.warn(`Can't delete previously added comment with testomat.io report. Ignore.`);
215
+ }
216
+
217
+ // pass next env var if need to clear all previous reports;
218
+ // only the last one is removed by default
219
+ if (!process.env.GITLAB_REMOVE_ALL_OUTDATED_REPORTS) break;
220
+ // TODO: in case of many reports should implement pagination
221
+ }
222
+ }
223
+ }
224
+
225
+ module.exports = GitLabPipe;
package/lib/pipe/index.js CHANGED
@@ -1,45 +1,61 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const chalk = require('chalk');
3
4
  const { APP_PREFIX } = require('../constants');
4
5
  const TestomatioPipe = require('./testomatio');
5
6
  const GitHubPipe = require('./github');
7
+ const GitLabPipe = require('./gitlab');
8
+ const CsvPipe = require('./csv');
6
9
 
7
- module.exports = function(params, opts) {
10
+ module.exports = function (params, opts) {
8
11
  const extraPipes = [];
9
12
 
10
13
  // Add extra pipes into package.json file:
11
14
  // "testomatio": {
12
- // "pipes": ["my-module-pipe", "./local/js/file/pipe"]
15
+ // "pipes": ["my-module-pipe", "./local/js/file/pipe"]
13
16
  // }
14
17
 
15
18
  const packageJsonFile = path.join(process.cwd(), 'package.json');
16
19
  if (fs.existsSync(packageJsonFile)) {
17
- const package = fs.readFileSync(packageJsonFile);
18
- const pipeDefs = package?.testomatio?.pipes || [];
19
-
20
+ const packageJson = fs.readFileSync(packageJsonFile);
21
+ const pipeDefs = packageJson?.testomatio?.pipes || [];
22
+
20
23
  for (const pipeDef of pipeDefs) {
21
- let pipeClass;
24
+ let PipeClass;
22
25
  try {
23
- pipeClass = require(pipeDef);
26
+ PipeClass = require(pipeDef);
24
27
  } catch (err) {
25
28
  console.log(APP_PREFIX, `Can't load module Testomatio pipe module from ${pipeDef}`);
26
29
  continue;
27
30
  }
28
31
 
29
32
  try {
30
- extraPipes.push(new pipeClass(params, opts))
33
+ extraPipes.push(new PipeClass(params, opts));
31
34
  } catch (err) {
32
35
  console.log(APP_PREFIX, `Can't instantiate Testomatio for ${pipeDef}`, err);
33
36
  continue;
34
37
  }
35
38
  }
36
39
  }
37
-
38
40
 
39
- const defaultPipes = [
41
+ const pipes = [
40
42
  new TestomatioPipe(params, opts),
41
- new GitHubPipe(params, opts),
43
+ new GitHubPipe(params, opts),
44
+ new GitLabPipe(params, opts),
45
+ new CsvPipe(params, opts),
46
+ ...extraPipes,
42
47
  ];
43
48
 
44
- return [...defaultPipes, ...extraPipes];
45
- }
49
+ console.log(
50
+ APP_PREFIX,
51
+ chalk.cyan('Pipes:'),
52
+ chalk.cyan(
53
+ pipes
54
+ .filter(p => p.isEnabled)
55
+ .map(p => p.toString())
56
+ .join(', ') || 'No pipes enabled',
57
+ ),
58
+ );
59
+
60
+ return pipes;
61
+ };