testbeats 2.1.5 → 2.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testbeats",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
4
4
  "description": "Publish test results to Microsoft Teams, Google Chat, Slack and InfluxDB",
5
5
  "main": "src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -53,6 +53,7 @@
53
53
  "performance-results-parser": "latest",
54
54
  "phin-retry": "^1.0.3",
55
55
  "pretty-ms": "^7.0.1",
56
+ "prompts": "^2.4.2",
56
57
  "rosters": "0.0.1",
57
58
  "sade": "^1.8.1",
58
59
  "test-results-parser": "0.2.5"
@@ -1,6 +1,9 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const zlib = require('zlib');
4
+ const stream = require('stream');
3
5
  const FormData = require('form-data-lite');
6
+ const ml = require('mime-lite')
4
7
  const TestResult = require('test-results-parser/src/models/TestResult');
5
8
  const { BeatsApi } = require('./beats.api');
6
9
  const logger = require('../utils/logger');
@@ -23,12 +26,14 @@ class BeatsAttachments {
23
26
  this.test_run_id = test_run_id;
24
27
  this.failed_test_cases = [];
25
28
  this.attachments = [];
29
+ this.compressed_attachment_paths = [];
26
30
  }
27
31
 
28
32
  async upload() {
29
33
  this.#setAllFailedTestCases();
30
34
  this.#setAttachments();
31
35
  await this.#uploadAttachments();
36
+ this.#deleteCompressedAttachments();
32
37
  }
33
38
 
34
39
  #setAllFailedTestCases() {
@@ -67,17 +72,22 @@ class BeatsAttachments {
67
72
  form.append('test_run_id', this.test_run_id);
68
73
  const file_images = []
69
74
  for (const attachment of attachments_subset) {
70
- const attachment_path = this.#getAttachmentFilePath(attachment);
75
+ let attachment_path = this.#getAttachmentFilePath(attachment);
71
76
  if (!attachment_path) {
72
77
  logger.warn(`⚠️ Unable to find attachment ${attachment.path}`);
73
78
  continue;
74
79
  }
80
+ attachment_path = await this.#compressAttachment(attachment_path);
75
81
  const stats = fs.statSync(attachment_path);
76
82
  if (stats.size > MAX_ATTACHMENT_SIZE) {
77
83
  logger.warn(`⚠️ Attachment ${attachment.path} is too big (${stats.size} bytes). Allowed size is ${MAX_ATTACHMENT_SIZE} bytes.`);
78
84
  continue;
79
85
  }
80
- form.append('images', fs.readFileSync(attachment_path), { filename: path.basename(attachment_path), filepath: attachment_path });
86
+ form.append('images', fs.readFileSync(attachment_path), {
87
+ filename: path.basename(attachment_path),
88
+ filepath: attachment_path,
89
+ contentType: ml.getType(attachment.path),
90
+ });
81
91
  file_images.push({
82
92
  file_name: attachment.name,
83
93
  file_path: attachment.path,
@@ -123,7 +133,38 @@ class BeatsAttachments {
123
133
  return null;
124
134
  }
125
135
 
136
+ /**
137
+ *
138
+ * @param {string} attachment_path
139
+ */
140
+ #compressAttachment(attachment_path) {
141
+ return new Promise((resolve, _) => {
142
+ if (attachment_path.endsWith('.br') || attachment_path.endsWith('.gz') || attachment_path.endsWith('.zst') || attachment_path.endsWith('.zip') || attachment_path.endsWith('.7z') || attachment_path.endsWith('.png') || attachment_path.endsWith('.jpg') || attachment_path.endsWith('.jpeg') || attachment_path.endsWith('.svg') || attachment_path.endsWith('.gif') || attachment_path.endsWith('.webp')) {
143
+ resolve(attachment_path);
144
+ return;
145
+ }
146
+ const read_stream = fs.createReadStream(attachment_path);
147
+ const br = zlib.createBrotliCompress();
126
148
 
149
+ const compressed_file_path = attachment_path + '.br';
150
+ const write_stream = fs.createWriteStream(compressed_file_path);
151
+ stream.pipeline(read_stream, br, write_stream, (err) => {
152
+ if (err) {
153
+ resolve(attachment_path);
154
+ return;
155
+ }
156
+ this.compressed_attachment_paths.push(compressed_file_path);
157
+ resolve(compressed_file_path);
158
+ return;
159
+ });
160
+ });
161
+ }
162
+
163
+ #deleteCompressedAttachments() {
164
+ for (const attachment_path of this.compressed_attachment_paths) {
165
+ fs.unlinkSync(attachment_path);
166
+ }
167
+ }
127
168
 
128
169
  }
129
170
 
package/src/cli.js CHANGED
@@ -5,13 +5,18 @@ const sade = require('sade');
5
5
 
6
6
  const prog = sade('testbeats');
7
7
  const { PublishCommand } = require('./commands/publish.command');
8
+ const { GenerateConfigCommand } = require('./commands/generate-config.command');
8
9
  const logger = require('./utils/logger');
9
10
  const pkg = require('../package.json');
10
11
 
11
12
  prog
12
13
  .version(pkg.version)
13
- .option('-c, --config', 'path to config file')
14
14
  .option('-l, --logLevel', 'Log Level', "INFO")
15
+
16
+
17
+ // Command to publish test results
18
+ prog.command('publish')
19
+ .option('-c, --config', 'path to config file')
15
20
  .option('--api-key', 'api key')
16
21
  .option('--project', 'project name')
17
22
  .option('--run', 'run name')
@@ -27,9 +32,7 @@ prog
27
32
  .option('--xunit', 'xunit xml path')
28
33
  .option('--mstest', 'mstest xml path')
29
34
  .option('-ci-info', 'ci info extension')
30
- .option('-chart-test-summary', 'chart test summary extension');
31
-
32
- prog.command('publish')
35
+ .option('-chart-test-summary', 'chart test summary extension')
33
36
  .action(async (opts) => {
34
37
  try {
35
38
  logger.setLevel(opts.logLevel);
@@ -41,4 +44,22 @@ prog.command('publish')
41
44
  }
42
45
  });
43
46
 
47
+ // Command to initialize and generate TestBeats Configuration file
48
+ prog.command('init')
49
+ .describe('Generate a TestBeats configuration file')
50
+ .example('init')
51
+ .action(async (opts) => {
52
+ try {
53
+ const generate_command = new GenerateConfigCommand(opts);
54
+ await generate_command.execute();
55
+ } catch (error) {
56
+ if (error.name === 'ExitPromptError') {
57
+ logger.info('😿 Configuration generation was canceled by the user.');
58
+ } else {
59
+ throw new Error(`❌ Error in generating configuration file: ${error.message}`)
60
+ }
61
+ process.exit(1);
62
+ }
63
+ });
64
+
44
65
  prog.parse(process.argv);
@@ -0,0 +1,440 @@
1
+ const prompts = require('prompts');
2
+ const fs = require('fs/promises');
3
+ const logger = require('../utils/logger');
4
+ const pkg = require('../../package.json');
5
+
6
+
7
+ class GenerateConfigCommand {
8
+ /**
9
+ * TODO: [BETA / Experimental Mode]
10
+ * Generates initial TestBests configuration file
11
+ */
12
+ constructor(opts) {
13
+ this.opts = opts;
14
+ this.configPath = '.testbeats.json';
15
+ this.config = {};
16
+ }
17
+
18
+ async execute() {
19
+ logger.setLevel(this.opts.logLevel);
20
+ this.#printBanner();
21
+ logger.info(`🚧 Config generation is still in BETA mode, please report any issues at ${pkg.bugs.url}\n`);
22
+
23
+ await this.#buildConfigFilePath();
24
+ await this.#buildTestResultsConfig();
25
+ await this.#buildTargetsConfig();
26
+ await this.#buildGobalExtensionConfig();
27
+ await this.#buildTestBeatsPortalConfig();
28
+ await this.#saveConfigFile();
29
+ }
30
+
31
+ #printBanner() {
32
+ const banner = `
33
+ _____ _ ___ _
34
+ (_ _) ( )_ ( _'\\ ( )_
35
+ | | __ ___ | ,_)| (_) ) __ _ _ | ,_) ___
36
+ | | /'__'\\/',__)| | | _ <' /'__'\\ /'_' )| | /',__)
37
+ | |( ___/\\__, \\| |_ | (_) )( ___/( (_| || |_ \\__, \\
38
+ (_)'\\____)(____/'\\__)(____/''\\____)'\\__,_)'\\__)(____/
39
+
40
+ v${pkg.version}
41
+ Config Generation [BETA]
42
+ `;
43
+ console.log(banner);
44
+ }
45
+
46
+ async #buildConfigFilePath() {
47
+ const { configPath } = await prompts({
48
+ type: 'text',
49
+ name: 'configPath',
50
+ message: 'Enter path for configuration file :',
51
+ initial: '.testbeats.json'
52
+ });
53
+ this.configPath = configPath;
54
+ }
55
+
56
+ async #buildTestResultsConfig() {
57
+ const runnerChoices = [
58
+ { title: 'Mocha', value: 'mocha', selected: true },
59
+ { title: 'JUnit', value: 'junit' },
60
+ { title: 'TestNG', value: 'testng' },
61
+ { title: 'Cucumber', value: 'cucumber' },
62
+ { title: 'NUnit', value: 'nunit' },
63
+ { title: 'xUnit', value: 'xunit' },
64
+ { title: 'MSTest', value: 'mstest' }
65
+ ]
66
+ // Get test results details
67
+ const { testResults } = await prompts([{
68
+ type: 'toggle',
69
+ name: 'includeResults',
70
+ message: 'Do you want to configure test results?',
71
+ initial: true,
72
+ active: 'Yes',
73
+ inactive: 'No'
74
+ },
75
+ {
76
+ type: (prev) => (prev ? "multiselect" : null),
77
+ name: 'testResults',
78
+ message: 'Select test result types to include:',
79
+ choices: runnerChoices,
80
+ min: 1
81
+ }]);
82
+
83
+ if (!testResults) { return };
84
+
85
+ // Handle result paths
86
+ this.config.results = []
87
+ for (const resultType of testResults) {
88
+ const { path } = await prompts({
89
+ type: 'text',
90
+ name: 'path',
91
+ message: `Enter file path for ${resultType} results (.json, .xml etc):`,
92
+ initial: ""
93
+ });
94
+ this.config.results.push({
95
+ files: path,
96
+ type: resultType
97
+ });
98
+ }
99
+ }
100
+
101
+ async #buildTargetsConfig() {
102
+ const targetChoices = [
103
+ { title: 'Slack', value: 'slack' },
104
+ { title: 'Microsoft Teams', value: 'teams' },
105
+ { title: 'Google Chat', value: 'chat' }
106
+ ];
107
+
108
+ const { titleInput, targets } = await prompts([{
109
+ type: 'toggle',
110
+ name: 'includeTargets',
111
+ message: 'Do you want to configure notification targets (slack, teams, chat etc)?',
112
+ initial: true,
113
+ active: 'Yes',
114
+ inactive: 'No'
115
+ },
116
+ {
117
+ type: (prev) => (prev ? "text" : null),
118
+ name: 'titleInput',
119
+ message: 'Enter notification title (optional):',
120
+ initial: 'TestBeats Report'
121
+ },
122
+ {
123
+ type: (prev, values) => (values.includeTargets ? "multiselect" : null),
124
+ name: 'targets',
125
+ message: 'Select notification targets:',
126
+ choices: targetChoices,
127
+ min: 1
128
+ }]);
129
+
130
+ if (!targets) { return }
131
+
132
+ this.config.targets = []
133
+
134
+ // For each target, ask about target-specific extensions
135
+ for (const target of targets) {
136
+ const { webhookEnvVar, selectedExtensions } = await prompts([{
137
+ type: 'text',
138
+ name: 'webhookEnvVar',
139
+ message: `Enter environment variable name for ${target} webhook URL:`,
140
+ initial: `${target.toUpperCase()}_WEBHOOK_URL`
141
+ },
142
+ {
143
+ type: 'toggle',
144
+ name: 'useExtensions',
145
+ message: `Do you want to configure extensions for ${target}?`,
146
+ initial: true,
147
+ active: 'Yes',
148
+ inactive: 'No'
149
+ },
150
+ {
151
+ type: (prev, values) => (values.useExtensions ? 'multiselect' : null),
152
+ name: 'selectedExtensions',
153
+ message: `Select extensions for ${target}:`,
154
+ choices: this.#getExtensionsList(),
155
+ min: 1
156
+ }]);
157
+
158
+ const targetConfig = {
159
+ name: target,
160
+ inputs: {
161
+ title: titleInput,
162
+ url: `{${webhookEnvVar}}`,
163
+ publish: 'test-summary'
164
+ }
165
+ };
166
+
167
+ if (selectedExtensions) {
168
+ targetConfig.extensions = [];
169
+ // Configure extension-specific inputs
170
+ for (const ext of selectedExtensions) {
171
+ const extConfig = await this.#buildExtensionConfig(ext, target);
172
+ targetConfig.extensions.push(extConfig);
173
+ }
174
+ }
175
+ this.config.targets.push(targetConfig);
176
+ }
177
+ }
178
+
179
+ async #buildGobalExtensionConfig() {
180
+ const { globalExtensionsSelected } = await prompts([{
181
+ type: 'toggle',
182
+ name: 'includeGlobalExtensions',
183
+ message: 'Do you want to configure global extensions?',
184
+ initial: false,
185
+ active: 'Yes',
186
+ inactive: 'No'
187
+ },
188
+ {
189
+ type: (prev) => (prev ? 'multiselect' : null),
190
+ name: 'globalExtensionsSelected',
191
+ message: 'Select global extensions to enable:',
192
+ choices: this.#getExtensionsList()
193
+ }]);
194
+
195
+ if (!globalExtensionsSelected) { return };
196
+
197
+ this.config.extensions = [];
198
+
199
+ // Configure extension-specific inputs
200
+ for (const ext of globalExtensionsSelected) {
201
+ const extDetails = await this.#buildExtensionConfig(ext, null);
202
+ this.config.extensions.push(extDetails);
203
+ }
204
+ }
205
+
206
+ async #buildExtHyperlinks() {
207
+ const links = [];
208
+ const { addLink } = await prompts({
209
+ type: 'toggle',
210
+ name: 'addLink',
211
+ message: 'Do you want to add a hyperlink?',
212
+ initial: true,
213
+ active: 'Yes',
214
+ inactive: 'No'
215
+ });
216
+
217
+ while (addLink) {
218
+ const { text, url } = await prompts([
219
+ {
220
+ type: 'text',
221
+ name: 'text',
222
+ message: 'Enter link text:'
223
+ },
224
+ {
225
+ type: 'text',
226
+ name: 'url',
227
+ message: 'Enter link URL:'
228
+ }
229
+ ]);
230
+
231
+ links.push({ text, url });
232
+
233
+ const { addAnother } = await prompts({
234
+ type: 'toggle',
235
+ name: 'addAnother',
236
+ message: 'Add another link?',
237
+ initial: false,
238
+ active: 'Yes',
239
+ inactive: 'No'
240
+ });
241
+ if (!addAnother) break;
242
+ }
243
+ return { links };
244
+ }
245
+
246
+ async #buildExtMentions(targetName) {
247
+ const users = [];
248
+ const { addUser } = await prompts({
249
+ type: 'toggle',
250
+ name: 'addUser',
251
+ message: 'Do you want to add user mentions?',
252
+ initial: true,
253
+ active: 'Yes',
254
+ inactive: 'No'
255
+ });
256
+
257
+ while (addUser) {
258
+ const user = {};
259
+ const { name } = await prompts({
260
+ type: 'text',
261
+ name: 'name',
262
+ message: 'Enter user name:'
263
+ });
264
+ user.name = name;
265
+
266
+ if (targetName === 'teams') {
267
+ const { teams_upn } = await prompts({
268
+ type: 'text',
269
+ name: 'teams_upn',
270
+ message: 'Enter Teams UPN (user principal name):'
271
+ });
272
+ user.teams_upn = teams_upn;
273
+ } else if (targetName === 'slack') {
274
+ const { slack_uid } = await prompts({
275
+ type: 'text',
276
+ name: 'slack_uid',
277
+ message: 'Enter Slack user ID:'
278
+ });
279
+ user.slack_uid = slack_uid;
280
+ } else if (targetName === 'chat') {
281
+ const { chat_uid } = await prompts({
282
+ type: 'text',
283
+ name: 'chat_uid',
284
+ message: 'Enter Google Chat user ID:'
285
+ });
286
+ user.chat_uid = chat_uid;
287
+ }
288
+
289
+ users.push(user);
290
+
291
+ const { addAnother } = await prompts({
292
+ type: 'toggle',
293
+ name: 'addAnother',
294
+ message: 'Add another user?',
295
+ initial: false,
296
+ active: 'Yes',
297
+ inactive: 'No'
298
+ });
299
+ if (!addAnother) break;
300
+ }
301
+ return { users };
302
+ }
303
+
304
+ async #buildExtMetadata() {
305
+ const data = [];
306
+ const { addMetadata } = await prompts({
307
+ type: 'toggle',
308
+ name: 'addMetadata',
309
+ message: 'Do you want to add metadata?',
310
+ initial: true,
311
+ active: 'Yes',
312
+ inactive: 'No'
313
+ });
314
+
315
+ while (addMetadata) {
316
+ const { key, value } = await prompts([
317
+ {
318
+ type: 'text',
319
+ name: 'key',
320
+ message: 'Enter metadata key:'
321
+ },
322
+ {
323
+ type: 'text',
324
+ name: 'value',
325
+ message: 'Enter metadata value:'
326
+ }
327
+ ]);
328
+
329
+ data.push({ key, value });
330
+
331
+ const { addAnother } = await prompts({
332
+ type: 'toggle',
333
+ name: 'addAnother',
334
+ message: 'Add another metadata item?',
335
+ initial: false,
336
+ active: 'Yes',
337
+ inactive: 'No'
338
+ });
339
+ if (!addAnother) break;
340
+ }
341
+ return { data };
342
+ }
343
+
344
+ async #buildExtensionConfig(extension, target) {
345
+ const extConfig = {
346
+ name: extension
347
+ };
348
+
349
+ switch (extension) {
350
+ case 'hyperlinks':
351
+ extConfig.inputs = await this.#buildExtHyperlinks()
352
+ break;
353
+
354
+ case 'mentions':
355
+ extConfig.inputs = await this.#buildExtMentions(target);
356
+ break;
357
+
358
+ case 'metadata':
359
+ extConfig.inputs = await this.#buildExtMetadata();
360
+ break;
361
+
362
+ default:
363
+ // Add default configuration for other extensions
364
+ extConfig.inputs = {
365
+ title: '',
366
+ separator: target === 'slack' ? false : true
367
+ };
368
+ }
369
+ return extConfig;
370
+ }
371
+
372
+ async #buildTestBeatsPortalConfig() {
373
+ // TestBeats configuration
374
+ const { apiKey, project } = await prompts([{
375
+ type: 'toggle',
376
+ name: 'includeTestBeats',
377
+ message: 'Do you want to configure TestBeats API key (optional)?',
378
+ initial: false,
379
+ active: 'Yes',
380
+ inactive: 'No'
381
+ },
382
+ {
383
+ type: (prev) => (prev ? 'text' : null),
384
+ name: 'apiKey',
385
+ message: 'Enter environment variable name for API key (optional):',
386
+ initial: '{TEST_RESULTS_API_KEY}'
387
+ },
388
+ {
389
+ type: (prev, values) => (values.includeTestBeats ? 'text' : null),
390
+ name: 'project',
391
+ message: 'Enter project name (optional):'
392
+ }]);
393
+
394
+ if (apiKey) {
395
+ this.config.api_key = apiKey;
396
+ // Add optional fields only if they have values
397
+ if (project?.trim()) {
398
+ this.config.project = project.trim();
399
+ }
400
+ }
401
+ }
402
+
403
+ #sortConfig() {
404
+ // Sort keys alphabetically
405
+ this.config = Object.keys(this.config).sort()
406
+ .reduce((sortedConfig, key) => {
407
+ sortedConfig[key] = this.config[key];
408
+ return sortedConfig;
409
+ }, {});
410
+ }
411
+
412
+ async #saveConfigFile() {
413
+ // Write config to file
414
+ try {
415
+ await fs.writeFile(this.configPath, JSON.stringify(this.config, null, 2));
416
+ logger.info(`✅ Configuration file successfully generated: ${this.configPath}`);
417
+ } catch (error) {
418
+ throw new Error(`Error: ${error.message}`)
419
+ }
420
+ }
421
+
422
+ #getExtensionsList() {
423
+ // List of Extensions supported
424
+ return [
425
+ { title: 'Quick Chart Test Summary', value: 'quick-chart-test-summary' },
426
+ { title: 'CI Information', value: 'ci-info' },
427
+ { title: 'Hyperlinks', value: 'hyperlinks' },
428
+ { title: 'Mentions', value: 'mentions' },
429
+ { title: 'Report Portal Analysis', value: 'report-portal-analysis' },
430
+ { title: 'Report Portal History', value: 'report-portal-history' },
431
+ { title: 'Percy Analysis', value: 'percy-analysis' },
432
+ { title: 'Metadata', value: 'metadata' },
433
+ { title: 'AI Failure Summary', value: 'ai-failure-summary' },
434
+ { title: 'Smart Analysis', value: 'smart-analysis' },
435
+ { title: 'Error Clusters', value: 'error-clusters' }
436
+ ];
437
+ }
438
+ }
439
+
440
+ module.exports = { GenerateConfigCommand };
@@ -56,19 +56,18 @@ class CIInfoExtension extends BaseExtension {
56
56
  this.#setRepositoryElement();
57
57
  }
58
58
  if (this.extension.inputs.show_repository_branch) {
59
- if (this.ci.repository_ref.includes('refs/pull')) {
59
+ if (this.ci.pull_request_name) {
60
60
  this.#setPullRequestElement();
61
61
  } else {
62
62
  this.#setRepositoryBranchElement();
63
63
  }
64
64
  }
65
65
  if (!this.extension.inputs.show_repository && !this.extension.inputs.show_repository_branch && this.extension.inputs.show_repository_non_common) {
66
- if (this.ci.repository_ref.includes('refs/pull')) {
66
+ if (this.ci.pull_request_name) {
67
67
  this.#setRepositoryElement();
68
68
  this.#setPullRequestElement();
69
69
  } else {
70
- const branch_name = this.ci.repository_ref.replace('refs/heads/', '');
71
- if (!COMMON_BRANCH_NAMES.includes(branch_name.toLowerCase())) {
70
+ if (!COMMON_BRANCH_NAMES.includes(this.ci.branch_name.toLowerCase())) {
72
71
  this.#setRepositoryElement();
73
72
  this.#setRepositoryBranchElement();
74
73
  }
@@ -81,15 +80,11 @@ class CIInfoExtension extends BaseExtension {
81
80
  }
82
81
 
83
82
  #setPullRequestElement() {
84
- const pr_url = this.ci.repository_url + this.ci.repository_ref.replace('refs/pull/', '/pull/');
85
- const pr_name = this.ci.repository_ref.replace('refs/pull/', '').replace('/merge', '');
86
- this.repository_elements.push({ label: 'Pull Request', key: pr_name, value: pr_url, type: 'hyperlink' });
83
+ this.repository_elements.push({ label: 'Pull Request', key: this.ci.pull_request_name, value: this.ci.pull_request_url, type: 'hyperlink' });
87
84
  }
88
85
 
89
86
  #setRepositoryBranchElement() {
90
- const branch_url = this.ci.repository_url + this.ci.repository_ref.replace('refs/heads/', '/tree/');
91
- const branch_name = this.ci.repository_ref.replace('refs/heads/', '');
92
- this.repository_elements.push({ label: 'Branch', key: branch_name, value: branch_url, type: 'hyperlink' });
87
+ this.repository_elements.push({ label: 'Branch', key: this.ci.branch_name, value: this.ci.branch_url, type: 'hyperlink' });
93
88
  }
94
89
 
95
90
  #setBuildElements() {
@@ -5,6 +5,10 @@ export type ICIInfo = {
5
5
  repository_name: string
6
6
  repository_ref: string
7
7
  repository_commit_sha: string
8
+ branch_url: string
9
+ branch_name: string
10
+ pull_request_url: string
11
+ pull_request_name: string
8
12
  build_url: string
9
13
  build_number: string
10
14
  build_name: string
@@ -0,0 +1,37 @@
1
+ const ENV = process.env;
2
+
3
+ /**
4
+ * @returns {import('../../extensions/extensions').ICIInfo}
5
+ */
6
+ function info() {
7
+ const azure_devops = {
8
+ ci: 'AZURE_DEVOPS_PIPELINES',
9
+ git: 'AZURE_DEVOPS_REPOS',
10
+ repository_url: ENV.BUILD_REPOSITORY_URI,
11
+ repository_name: ENV.BUILD_REPOSITORY_NAME,
12
+ repository_ref: ENV.BUILD_SOURCEBRANCH,
13
+ repository_commit_sha: ENV.BUILD_SOURCEVERSION,
14
+ branch_url: '',
15
+ branch_name: '',
16
+ pull_request_url:'',
17
+ pull_request_name: '',
18
+ build_url: ENV.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI + ENV.SYSTEM_TEAMPROJECT + '/_build/results?buildId=' + ENV.BUILD_BUILDID,
19
+ build_number: ENV.BUILD_BUILDNUMBER,
20
+ build_name: ENV.BUILD_DEFINITIONNAME,
21
+ build_reason: ENV.BUILD_REASON,
22
+ user: ENV.BUILD_REQUESTEDFOR
23
+ }
24
+
25
+ azure_devops.branch_url = azure_devops.repository_url + azure_devops.repository_ref.replace('refs/heads/', '/tree/');
26
+ azure_devops.branch_name = azure_devops.repository_ref.replace('refs/heads/', '');
27
+
28
+ if (azure_devops.repository_ref.includes('refs/pull')) {
29
+ azure_devops.pull_request_url = azure_devops.repository_url + azure_devops.repository_ref.replace('refs/pull/', '/pull/');
30
+ azure_devops.pull_request_name = azure_devops.repository_ref.replace('refs/pull/', '').replace('/merge', '');
31
+ }
32
+ return azure_devops;
33
+ }
34
+
35
+ module.exports = {
36
+ info
37
+ }
@@ -0,0 +1,38 @@
1
+ const ENV = process.env;
2
+
3
+ /**
4
+ * @returns {import('../../extensions/extensions').ICIInfo}
5
+ */
6
+ function info() {
7
+ const github = {
8
+ ci: 'GITHUB_ACTIONS',
9
+ git: 'GITHUB',
10
+ repository_url: ENV.GITHUB_SERVER_URL + '/' + ENV.GITHUB_REPOSITORY,
11
+ repository_name: ENV.GITHUB_REPOSITORY,
12
+ repository_ref: ENV.GITHUB_REF,
13
+ repository_commit_sha: ENV.GITHUB_SHA,
14
+ branch_url: '',
15
+ branch_name: '',
16
+ pull_request_url:'',
17
+ pull_request_name: '',
18
+ build_url: ENV.GITHUB_SERVER_URL + '/' + ENV.GITHUB_REPOSITORY + '/actions/runs/' + ENV.GITHUB_RUN_ID,
19
+ build_number: ENV.GITHUB_RUN_NUMBER,
20
+ build_name: ENV.GITHUB_WORKFLOW,
21
+ build_reason: ENV.GITHUB_EVENT_NAME,
22
+ user: ENV.GITHUB_ACTOR,
23
+ }
24
+
25
+ github.branch_url = github.repository_url + github.repository_ref.replace('refs/heads/', '/tree/');
26
+ github.branch_name = github.repository_ref.replace('refs/heads/', '');
27
+
28
+ if (github.repository_ref.includes('refs/pull')) {
29
+ github.pull_request_url = github.repository_url + github.repository_ref.replace('refs/pull/', '/pull/');
30
+ github.pull_request_name = github.repository_ref.replace('refs/pull/', '').replace('/merge', '');
31
+ }
32
+
33
+ return github
34
+ }
35
+
36
+ module.exports = {
37
+ info
38
+ }
@@ -0,0 +1,36 @@
1
+ const ENV = process.env;
2
+
3
+ /**
4
+ * @returns {import('../../extensions/extensions').ICIInfo}
5
+ */
6
+ function info() {
7
+ const gitlab = {
8
+ ci: 'GITLAB',
9
+ git: 'GITLAB',
10
+ repository_url: ENV.CI_PROJECT_URL,
11
+ repository_name: ENV.CI_PROJECT_NAME,
12
+ repository_ref: '/-/tree/' + (ENV.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME || ENV.CI_COMMIT_REF_NAME),
13
+ repository_commit_sha: ENV.CI_MERGE_REQUEST_SOURCE_BRANCH_SHA || ENV.CI_COMMIT_SHA,
14
+ branch_url: ENV.CI_PROJECT_URL + '/-/tree/' + (ENV.CI_COMMIT_REF_NAME || ENV.CI_COMMIT_BRANCH),
15
+ branch_name: ENV.CI_COMMIT_REF_NAME || ENV.CI_COMMIT_BRANCH,
16
+ pull_request_url:'',
17
+ pull_request_name: '',
18
+ build_url: ENV.CI_JOB_URL,
19
+ build_number: ENV.CI_JOB_ID,
20
+ build_name: ENV.CI_JOB_NAME,
21
+ build_reason: ENV.CI_PIPELINE_SOURCE,
22
+ user: ENV.GITLAB_USER_LOGIN || ENV.CI_COMMIT_AUTHOR
23
+ }
24
+
25
+ if (ENV.CI_OPEN_MERGE_REQUESTS) {
26
+ const pr_number = ENV.CI_OPEN_MERGE_REQUESTS.split("!")[1];
27
+ gitlab.pull_request_name = "#" + pr_number;
28
+ gitlab.pull_request_url = ENV.CI_PROJECT_URL + "/-/merge_requests/" + pr_number;
29
+ }
30
+
31
+ return gitlab
32
+ }
33
+
34
+ module.exports = {
35
+ info
36
+ }
@@ -0,0 +1,60 @@
1
+ const os = require('os');
2
+ const github = require('./github');
3
+ const gitlab = require('./gitlab');
4
+ const jenkins = require('./jenkins');
5
+ const azure_devops = require('./azure-devops');
6
+ const system = require('./system');
7
+
8
+ const ENV = process.env;
9
+
10
+ /**
11
+ * @returns {import('../../extensions/extensions').ICIInfo}
12
+ */
13
+ function getCIInformation() {
14
+ const ci_info = getBaseCIInfo();
15
+ const system_info = system.info();
16
+ return {
17
+ ...ci_info,
18
+ ...system_info
19
+ }
20
+ }
21
+
22
+ function getBaseCIInfo() {
23
+ if (ENV.GITHUB_ACTIONS) {
24
+ return github.info();
25
+ }
26
+ if (ENV.GITLAB_CI) {
27
+ return gitlab.info();
28
+ }
29
+ if (ENV.JENKINS_URL) {
30
+ return jenkins.info();
31
+ }
32
+ if (ENV.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI) {
33
+ return azure_devops.info();
34
+ }
35
+ return getDefaultInformation();
36
+ }
37
+
38
+ function getDefaultInformation() {
39
+ return {
40
+ ci: ENV.TEST_BEATS_CI_NAME,
41
+ git: ENV.TEST_BEATS_CI_GIT,
42
+ repository_url: ENV.TEST_BEATS_CI_REPOSITORY_URL,
43
+ repository_name: ENV.TEST_BEATS_CI_REPOSITORY_NAME,
44
+ repository_ref: ENV.TEST_BEATS_CI_REPOSITORY_REF,
45
+ repository_commit_sha: ENV.TEST_BEATS_CI_REPOSITORY_COMMIT_SHA,
46
+ branch_url: ENV.TEST_BEATS_BRANCH_URL,
47
+ branch_name: ENV.TEST_BEATS_BRANCH_NAME,
48
+ pull_request_url: ENV.TEST_BEATS_PULL_REQUEST_URL,
49
+ pull_request_name: ENV.TEST_BEATS_PULL_REQUEST_NAME,
50
+ build_url: ENV.TEST_BEATS_CI_BUILD_URL,
51
+ build_number: ENV.TEST_BEATS_CI_BUILD_NUMBER,
52
+ build_name: ENV.TEST_BEATS_CI_BUILD_NAME,
53
+ build_reason: ENV.TEST_BEATS_CI_BUILD_REASON,
54
+ user: ENV.TEST_BEATS_CI_USER || os.userInfo().username
55
+ }
56
+ }
57
+
58
+ module.exports = {
59
+ getCIInformation
60
+ }
@@ -0,0 +1,38 @@
1
+ const ENV = process.env;
2
+
3
+ /**
4
+ * @returns {import('../../extensions/extensions').ICIInfo}
5
+ */
6
+ function info() {
7
+ const jenkins = {
8
+ ci: 'JENKINS',
9
+ git: '',
10
+ repository_url: ENV.GIT_URL || ENV.GITHUB_URL || ENV.BITBUCKET_URL,
11
+ repository_name: ENV.JOB_NAME,
12
+ repository_ref: ENV.BRANCH || ENV.BRANCH_NAME,
13
+ repository_commit_sha: ENV.GIT_COMMIT || ENV.GIT_COMMIT_SHA || ENV.GITHUB_SHA || ENV.BITBUCKET_COMMIT,
14
+ branch_url: '',
15
+ branch_name: '',
16
+ pull_request_url:'',
17
+ pull_request_name: '',
18
+ build_url: ENV.BUILD_URL,
19
+ build_number: ENV.BUILD_NUMBER,
20
+ build_name: ENV.JOB_NAME,
21
+ build_reason: ENV.BUILD_CAUSE,
22
+ user: ENV.USER || ENV.USERNAME
23
+ }
24
+
25
+ jenkins.branch_url = jenkins.repository_url + jenkins.repository_ref.replace('refs/heads/', '/tree/');
26
+ jenkins.branch_name = jenkins.repository_ref.replace('refs/heads/', '');
27
+
28
+ if (jenkins.repository_ref.includes('refs/pull')) {
29
+ jenkins.pull_request_url = jenkins.repository_url + jenkins.repository_ref.replace('refs/pull/', '/pull/');
30
+ jenkins.pull_request_name = jenkins.repository_ref.replace('refs/pull/', '').replace('/merge', '');
31
+ }
32
+
33
+ return jenkins_info;
34
+ }
35
+
36
+ module.exports = {
37
+ info
38
+ }
@@ -0,0 +1,30 @@
1
+ const os = require('os');
2
+ const pkg = require('../../../package.json');
3
+
4
+ function info() {
5
+ function getRuntimeInfo() {
6
+ if (typeof process !== 'undefined' && process.versions && process.versions.node) {
7
+ return { name: 'node', version: process.versions.node };
8
+ } else if (typeof Deno !== 'undefined') {
9
+ return { name: 'deno', version: Deno.version.deno };
10
+ } else if (typeof Bun !== 'undefined') {
11
+ return { name: 'bun', version: Bun.version };
12
+ } else {
13
+ return { name: 'unknown', version: 'unknown' };
14
+ }
15
+ }
16
+
17
+ const runtime = getRuntimeInfo();
18
+
19
+ return {
20
+ runtime: runtime.name,
21
+ runtime_version: runtime.version,
22
+ os: os.platform(),
23
+ os_version: os.release(),
24
+ testbeats_version: pkg.version
25
+ }
26
+ }
27
+
28
+ module.exports = {
29
+ info
30
+ }
package/src/index.js CHANGED
@@ -1,15 +1,22 @@
1
1
  const { PublishCommand } = require('./commands/publish.command');
2
+ const { GenerateConfigCommand } = require('./commands/generate-config.command');
2
3
 
3
4
  function publish(options) {
4
5
  const publish_command = new PublishCommand(options);
5
6
  return publish_command.publish();
6
7
  }
7
8
 
9
+ function generateConfig() {
10
+ const generate_command = new GenerateConfigCommand();
11
+ return generate_command.execute();
12
+ }
13
+
8
14
  function defineConfig(config) {
9
- return config
15
+ return config;
10
16
  }
11
17
 
12
18
  module.exports = {
13
19
  publish,
20
+ generateConfig,
14
21
  defineConfig
15
22
  }
package/src/helpers/ci.js DELETED
@@ -1,140 +0,0 @@
1
- const os = require('os');
2
- const pkg = require('../../package.json');
3
-
4
- const ENV = process.env;
5
-
6
- /**
7
- * @returns {import('../extensions/extensions').ICIInfo}
8
- */
9
- function getCIInformation() {
10
- const ci_info = getBaseCIInfo();
11
- const system_info = getSystemInfo();
12
- return {
13
- ...ci_info,
14
- ...system_info
15
- }
16
- }
17
-
18
- function getBaseCIInfo() {
19
- if (ENV.GITHUB_ACTIONS) {
20
- return getGitHubActionsInformation();
21
- }
22
- if (ENV.GITLAB_CI) {
23
- return getGitLabInformation();
24
- }
25
- if (ENV.JENKINS_URL) {
26
- return getJenkinsInformation();
27
- }
28
- if (ENV.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI) {
29
- return getAzureDevOpsInformation();
30
- }
31
- return getDefaultInformation();
32
- }
33
-
34
- function getGitHubActionsInformation() {
35
- return {
36
- ci: 'GITHUB_ACTIONS',
37
- git: 'GITHUB',
38
- repository_url: ENV.GITHUB_SERVER_URL + '/' + ENV.GITHUB_REPOSITORY,
39
- repository_name: ENV.GITHUB_REPOSITORY,
40
- repository_ref: ENV.GITHUB_REF,
41
- repository_commit_sha: ENV.GITHUB_SHA,
42
- build_url: ENV.GITHUB_SERVER_URL + '/' + ENV.GITHUB_REPOSITORY + '/actions/runs/' + ENV.GITHUB_RUN_ID,
43
- build_number: ENV.GITHUB_RUN_NUMBER,
44
- build_name: ENV.GITHUB_WORKFLOW,
45
- build_reason: ENV.GITHUB_EVENT_NAME,
46
- user: ENV.GITHUB_ACTOR,
47
- }
48
- }
49
-
50
- function getAzureDevOpsInformation() {
51
- return {
52
- ci: 'AZURE_DEVOPS_PIPELINES',
53
- git: 'AZURE_DEVOPS_REPOS',
54
- repository_url: ENV.BUILD_REPOSITORY_URI,
55
- repository_name: ENV.BUILD_REPOSITORY_NAME,
56
- repository_ref: ENV.BUILD_SOURCEBRANCH,
57
- repository_commit_sha: ENV.BUILD_SOURCEVERSION,
58
- build_url: ENV.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI + ENV.SYSTEM_TEAMPROJECT + '/_build/results?buildId=' + ENV.BUILD_BUILDID,
59
- build_number: ENV.BUILD_BUILDNUMBER,
60
- build_name: ENV.BUILD_DEFINITIONNAME,
61
- build_reason: ENV.BUILD_REASON,
62
- user: ENV.BUILD_REQUESTEDFOR
63
- }
64
- }
65
-
66
- function getJenkinsInformation() {
67
- return {
68
- ci: 'JENKINS',
69
- git: '',
70
- repository_url: ENV.GIT_URL || ENV.GITHUB_URL || ENV.BITBUCKET_URL,
71
- repository_name: ENV.JOB_NAME,
72
- repository_ref: ENV.BRANCH || ENV.BRANCH_NAME,
73
- repository_commit_sha: ENV.GIT_COMMIT || ENV.GIT_COMMIT_SHA || ENV.GITHUB_SHA || ENV.BITBUCKET_COMMIT,
74
- build_url: ENV.BUILD_URL,
75
- build_number: ENV.BUILD_NUMBER,
76
- build_name: ENV.JOB_NAME,
77
- build_reason: ENV.BUILD_CAUSE,
78
- user: ENV.USER || ENV.USERNAME
79
- }
80
- }
81
-
82
- function getGitLabInformation() {
83
- return {
84
- ci: 'GITLAB',
85
- git: 'GITLAB',
86
- repository_url: ENV.CI_PROJECT_URL,
87
- repository_name: ENV.CI_PROJECT_NAME,
88
- repository_ref: ENV.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME || ENV.CI_COMMIT_REF_NAME,
89
- repository_commit_sha:ENV.CI_MERGE_REQUEST_SOURCE_BRANCH_SHA || ENV.CI_COMMIT_SHA,
90
- build_url: ENV.CI_JOB_URL,
91
- build_number: ENV.CI_JOB_ID,
92
- build_name: ENV.CI_JOB_NAME,
93
- build_reason: ENV.CI_PIPELINE_SOURCE,
94
- user: ENV.GITLAB_USER_LOGIN || ENV.CI_COMMIT_AUTHOR
95
- }
96
- }
97
-
98
- function getDefaultInformation() {
99
- return {
100
- ci: ENV.TEST_BEATS_CI_NAME,
101
- git: ENV.TEST_BEATS_CI_GIT,
102
- repository_url: ENV.TEST_BEATS_CI_REPOSITORY_URL,
103
- repository_name: ENV.TEST_BEATS_CI_REPOSITORY_NAME,
104
- repository_ref: ENV.TEST_BEATS_CI_REPOSITORY_REF,
105
- repository_commit_sha: ENV.TEST_BEATS_CI_REPOSITORY_COMMIT_SHA,
106
- build_url: ENV.TEST_BEATS_CI_BUILD_URL,
107
- build_number: ENV.TEST_BEATS_CI_BUILD_NUMBER,
108
- build_name: ENV.TEST_BEATS_CI_BUILD_NAME,
109
- build_reason: ENV.TEST_BEATS_CI_BUILD_REASON,
110
- user: ENV.TEST_BEATS_CI_USER || os.userInfo().username
111
- }
112
- }
113
-
114
- function getSystemInfo() {
115
- function getRuntimeInfo() {
116
- if (typeof process !== 'undefined' && process.versions && process.versions.node) {
117
- return { name: 'node', version: process.versions.node };
118
- } else if (typeof Deno !== 'undefined') {
119
- return { name: 'deno', version: Deno.version.deno };
120
- } else if (typeof Bun !== 'undefined') {
121
- return { name: 'bun', version: Bun.version };
122
- } else {
123
- return { name: 'unknown', version: 'unknown' };
124
- }
125
- }
126
-
127
- const runtime = getRuntimeInfo();
128
-
129
- return {
130
- runtime: runtime.name,
131
- runtime_version: runtime.version,
132
- os: os.platform(),
133
- os_version: os.release(),
134
- testbeats_version: pkg.version
135
- }
136
- }
137
-
138
- module.exports = {
139
- getCIInformation
140
- }