@testomatio/reporter 1.5.1 → 1.6.0-beta-1-artifacts

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.
@@ -2,7 +2,6 @@ const debug = require('debug')('@testomatio/reporter:adapter:codeceptjs');
2
2
  const chalk = require('chalk');
3
3
  const TestomatClient = require('../client');
4
4
  const { STATUS, APP_PREFIX, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
5
- const upload = require('../fileUploader');
6
5
  const { getTestomatIdFromTestTitle, fileSystem } = require('../utils/utils');
7
6
  const { services } = require('../services');
8
7
 
@@ -128,10 +127,8 @@ function CodeceptReporter(config) {
128
127
  // all tests were reported and we can upload videos
129
128
  await Promise.all(reportTestPromises);
130
129
 
131
- if (upload.isArtifactsEnabled()) {
132
- await uploadAttachments(client, videos, '🎞️ Uploading', 'video');
133
- await uploadAttachments(client, traces, '📁 Uploading', 'trace');
134
- }
130
+ await uploadAttachments(client, videos, '🎞️ Uploading', 'video');
131
+ await uploadAttachments(client, traces, '📁 Uploading', 'trace');
135
132
 
136
133
  const status = failedTests.length === 0 ? STATUS.PASSED : STATUS.FAILED;
137
134
  client.updateRunStatus(status);
@@ -304,13 +301,17 @@ function CodeceptReporter(config) {
304
301
  }
305
302
 
306
303
  async function uploadAttachments(client, attachments, messagePrefix, attachmentType) {
307
- if (!attachments?.length) return;
304
+ if (!attachments?.length) return;
308
305
 
309
- console.log(APP_PREFIX, `Attachments: ${messagePrefix} ${attachments.length} ${attachmentType} ...`);
306
+ if (client.uploader.isEnabled) console.log(APP_PREFIX, `Attachments: ${messagePrefix} ${attachments.length} ${attachmentType} ...`);
310
307
 
311
308
  const promises = attachments.map(async attachment => {
312
309
  const { rid, title, path, type } = attachment;
313
310
  const file = { path, type, title };
311
+
312
+ // we are storing file if upload is disabled
313
+ if (!client.uploader.isEnabled) return client.uploader.storeUploadedFile(path, client.runId, rid, false);
314
+
314
315
  return client.addTestRun(undefined, {
315
316
  ...stripExampleFromTitle(title),
316
317
  rid,
@@ -6,7 +6,6 @@ const { v4: uuidv4 } = require('uuid');
6
6
  const fs = require('fs');
7
7
  const { APP_PREFIX, STATUS: Status, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
8
8
  const TestomatioClient = require('../client');
9
- const { isArtifactsEnabled } = require('../fileUploader');
10
9
  const { getTestomatIdFromTestTitle, fileSystem } = require('../utils/utils');
11
10
  // const debug = require('debug')('@testomatio/reporter:adapter:playwright');
12
11
  const { services } = require('../services');
@@ -106,31 +105,37 @@ class PlaywrightReporter {
106
105
 
107
106
  await Promise.all(reportTestPromises);
108
107
 
109
- if (this.uploads.length && isArtifactsEnabled()) {
110
- console.log(APP_PREFIX, `🎞️ Uploading ${this.uploads.length} files...`);
108
+ if (!this.uploads.length) return;
109
+
110
+ if (this.client.uploader.isEnabled) console.log(APP_PREFIX, `🎞️ Uploading ${this.uploads.length} files...`);
111
111
 
112
- const promises = [];
112
+ const promises = [];
113
113
 
114
- for (const upload of this.uploads) {
115
- const { rid, file, title } = upload;
114
+ for (const upload of this.uploads) {
115
+ const { rid, file, title } = upload;
116
116
 
117
- const files = upload.files.map(attachment => ({
118
- path: this.#getArtifactPath(attachment),
119
- title,
120
- type: attachment.contentType,
121
- }));
122
-
123
- promises.push(
124
- this.client.addTestRun(undefined, {
125
- rid,
126
- title,
127
- files,
128
- file,
129
- }),
130
- );
117
+ const files = upload.files.map(attachment => ({
118
+ path: this.#getArtifactPath(attachment),
119
+ title,
120
+ type: attachment.contentType,
121
+ }));
122
+
123
+ if (!this.client.uploader.isEnabled) {
124
+ files.forEach(f => this.client.uploader.storeUploadedFile(f, this.client.runId, rid, false));
125
+ continue;
131
126
  }
132
- await Promise.all(promises);
127
+
128
+ promises.push(
129
+ this.client.addTestRun(undefined, {
130
+ rid,
131
+ title,
132
+ files,
133
+ file,
134
+ }),
135
+ );
133
136
  }
137
+ await Promise.all(promises);
138
+
134
139
 
135
140
  await this.client.updateRunStatus(checkStatus(result.status));
136
141
  }
package/lib/bin/cli.js ADDED
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env node
2
+ const { program } = require('commander');
3
+ const spawn = require('cross-spawn');
4
+ const chalk = require('chalk');
5
+ const glob = require('glob');
6
+ const debug = require('debug')('@testomatio/reporter:cli');
7
+ const TestomatClient = require('../client');
8
+ const XmlReader = require('../xmlReader');
9
+ const { APP_PREFIX, STATUS } = require('../constants');
10
+ const { version } = require('../../package.json');
11
+ const config = require('../config');
12
+
13
+ console.log(chalk.cyan.bold(` 🤩 Testomat.io Reporter v${version}`));
14
+
15
+ program
16
+ .version(version)
17
+ .option('--env-file <envfile>', 'Load environment variables from env file')
18
+ .hook('preAction', (thisCommand) => {
19
+ const opts = thisCommand.opts();
20
+ if (opts.envFile) {
21
+ require('dotenv').config({ path: opts.envFile });
22
+ } else {
23
+ require('dotenv').config();
24
+ }
25
+ });
26
+
27
+ program
28
+ .command('start')
29
+ .description('Start a new run and return its ID')
30
+ .action(async () => {
31
+ console.log('Starting a new Run on Testomat.io...');
32
+ const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
33
+ const client = new TestomatClient({ apiKey });
34
+
35
+ client.createRun().then(() => {
36
+ console.log(process.env.runId);
37
+ process.exit(0);
38
+ });
39
+ });
40
+
41
+ program
42
+ .command('finish')
43
+ .description('Finish Run by its ID')
44
+ .action(async () => {
45
+ if (!process.env.TESTOMATIO_RUN) {
46
+ console.log('TESTOMATIO_RUN environment variable must be set.');
47
+ return process.exit(1);
48
+ }
49
+
50
+ console.log('Finishing Run on Testomat.io...');
51
+ const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
52
+ const client = new TestomatClient({ apiKey });
53
+
54
+ client.updateRunStatus(STATUS.FINISHED).then(() => {
55
+ console.log(chalk.yellow(`Run ${process.env.TESTOMATIO_RUN} was finished`));
56
+ process.exit(0);
57
+ });
58
+ });
59
+
60
+ program
61
+ .command('run')
62
+ .description('Run tests with the specified command')
63
+ .argument('<command>', 'Test runner command')
64
+ .option('--filter <filter>', 'Additional execution filter')
65
+ .action(async (command, opts) => {
66
+ const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
67
+ const title = process.env.TESTOMATIO_TITLE;
68
+
69
+ if (!command || !command.split) {
70
+ console.log(APP_PREFIX, `No command provided. Use -c option to launch a test runner.`);
71
+ return process.exit(255);
72
+ }
73
+
74
+ const client = new TestomatClient({ apiKey, title, parallel: true });
75
+
76
+ if (opts.filter) {
77
+ const [pipe, ...optsArray] = opts.filter.split(':');
78
+ const pipeOptions = optsArray.join(':');
79
+
80
+ try {
81
+ const tests = await client.prepareRun({ pipe, pipeOptions });
82
+ if (tests && tests.length > 0) {
83
+ command += ` --grep (${tests.join('|')})`;
84
+ }
85
+ } catch (err) {
86
+ console.log(APP_PREFIX, err);
87
+ }
88
+ }
89
+
90
+ console.log(APP_PREFIX, `🚀 Running`, chalk.green(command));
91
+
92
+ const runTests = () => {
93
+ const testCmds = command.split(' ');
94
+ const cmd = spawn(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
95
+
96
+ cmd.on('close', code => {
97
+ const emoji = code === 0 ? '🟢' : '🔴';
98
+ console.log(APP_PREFIX, emoji, `Runner exited with ${chalk.bold(code)}`);
99
+ if (apiKey) {
100
+ const status = code === 0 ? 'passed' : 'failed';
101
+ client.updateRunStatus(status, true);
102
+ }
103
+ process.exit(code);
104
+ });
105
+ };
106
+
107
+ if (apiKey) {
108
+ client.createRun().then(runTests);
109
+ } else {
110
+ runTests();
111
+ }
112
+ });
113
+
114
+ program
115
+ .command('xml')
116
+ .description('Parse XML reports and upload to Testomat.io')
117
+ .argument('<pattern>', 'XML file pattern')
118
+ .option('-d, --dir <dir>', 'Project directory')
119
+ .option('--java-tests [java-path]', 'Load Java tests from path, by default: src/test/java')
120
+ .option('--lang <lang>', 'Language used (python, ruby, java)')
121
+ .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
122
+ .action(async (pattern, opts) => {
123
+ if (!pattern.endsWith('.xml')) {
124
+ pattern += '.xml';
125
+ }
126
+ let { javaTests, lang } = opts;
127
+ if (javaTests === true) javaTests = 'src/test/java';
128
+ lang = lang?.toLowerCase();
129
+ const runReader = new XmlReader({ javaTests, lang });
130
+ const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
131
+ if (!files.length) {
132
+ console.log(APP_PREFIX, `Report can't be created. No XML files found 😥`);
133
+ process.exit(1);
134
+ }
135
+
136
+ for (const file of files) {
137
+ console.log(APP_PREFIX, `Parsed ${file}`);
138
+ runReader.parse(file);
139
+ }
140
+
141
+ let timeoutTimer;
142
+ if (opts.timelimit) {
143
+ timeoutTimer = setTimeout(() => {
144
+ console.log(`⚠️ Reached timeout of ${opts.timelimit}s. Exiting... (Exit code is 0 to not fail the pipeline)`);
145
+ process.exit(0);
146
+ }, parseInt(opts.timelimit, 10) * 1000);
147
+ }
148
+
149
+ try {
150
+ await runReader.createRun();
151
+ await runReader.uploadData();
152
+ } catch (err) {
153
+ console.log(APP_PREFIX, 'Error updating status, skipping...', err);
154
+ }
155
+
156
+ if (timeoutTimer) clearTimeout(timeoutTimer);
157
+ });
158
+
159
+ program
160
+ .command('upload-artifacts')
161
+ .description('Upload artifacts to Testomat.io')
162
+ .option('--force', 'Re-upload artifacts even if they were uploaded before')
163
+ .action(async (opts) => {
164
+ const apiKey = config.TESTOMATIO;
165
+ process.env.TESTOMATIO_DISABLE_ARTIFACTS = '';
166
+
167
+ const client = new TestomatClient({
168
+ apiKey,
169
+ isBatchEnabled: false,
170
+ });
171
+
172
+ if (!process.env.TESTOMATIO_RUN) {
173
+ console.log(APP_PREFIX, 'No run ID provided');
174
+ process.exit(1);
175
+ }
176
+
177
+ let testruns = client.uploader.readUploadedFiles(process.env.TESTOMATIO_RUN);
178
+ const numTotalArtifacts = testruns.length;
179
+
180
+ debug('Found testruns:', testruns);
181
+
182
+ if (!opts.force) testruns = testruns.filter(tr => !tr.uploaded);
183
+
184
+ if (!testruns.length) {
185
+ console.log(APP_PREFIX, 'Total artifacts:', numTotalArtifacts);
186
+ if (numTotalArtifacts) {
187
+ console.log(APP_PREFIX, 'No new artifacts to upload');
188
+ console.log(APP_PREFIX, 'To re-upload artifacts run this command with --force flag');
189
+ }
190
+ process.exit(0);
191
+ }
192
+
193
+ const testrunsByRid = testruns.reduce((acc, { rid, file }) => {
194
+ if (!acc[rid]) {
195
+ acc[rid] = [];
196
+ }
197
+ if (!acc[rid].includes(file)) acc[rid].push(file);
198
+ return acc;
199
+ }, {});
200
+
201
+ await client.createRun();
202
+ client.uploader.checkEnabled();
203
+ client.uploader.disbleLogStorage();
204
+
205
+ for (const rid in testrunsByRid) {
206
+ const files = testrunsByRid[rid];
207
+ await client.addTestRun(undefined, { rid, files });
208
+ }
209
+
210
+ console.log(APP_PREFIX, client.uploader.totalUploaded, 'artifacts uploaded');
211
+ if (client.uploader.failedUpload) {
212
+ console.log(APP_PREFIX, client.uploader.failedUpload, 'artifacts failed to upload');
213
+ }
214
+ });
215
+
216
+ program.parse(process.argv);
217
+
218
+ if (!process.argv.slice(2).length) {
219
+ program.outputHelp();
220
+ }
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ const program = require('commander');
3
+ const chalk = require('chalk');
4
+ const debug = require('debug')('@testomatio/reporter:upload-cli');
5
+ const TestomatClient = require('../client');
6
+ const { APP_PREFIX } = require('../constants');
7
+ const { version } = require('../../package.json');
8
+ const config = require('../config');
9
+
10
+ console.log(chalk.cyan.bold(` 🤩 Testomat.io Reporter v${version}`));
11
+
12
+ program
13
+ .option('--env-file <envfile>', 'Load environment variables from env file')
14
+ .option('--force', 'Re-upload artifacts even if they were uploaded before')
15
+ .action(async opts => {
16
+
17
+ if (opts.envFile) {
18
+ require('dotenv').config(opts.envFile); // eslint-disable-line
19
+ } else {
20
+ // try to load from env file
21
+ require('dotenv').config(); // eslint-disable-line
22
+ }
23
+
24
+ const apiKey = config.TESTOMATIO;
25
+
26
+ process.env.TESTOMATIO_DISABLE_ARTIFACTS = '';
27
+ // process.env.TESTOMATIO_ARTIFACTS_SIZE = null;
28
+
29
+ const client = new TestomatClient({
30
+ apiKey,
31
+ isBatchEnabled: false,
32
+ });
33
+
34
+ if (!process.env.TESTOMATIO_RUN) {
35
+ console.log(APP_PREFIX, 'No run ID provided');
36
+ process.exit(1);
37
+ }
38
+
39
+ let testruns = client.uploader.readUploadedFiles(process.env.TESTOMATIO_RUN);
40
+
41
+ const numTotalArtifacts = testruns.length;
42
+
43
+ debug('Found testruns:', testruns);
44
+
45
+ if (!opts.force) testruns = testruns.filter(tr => !tr.uploaded);
46
+
47
+ if (!testruns.length) {
48
+ console.log(APP_PREFIX, 'Total artifacts:', numTotalArtifacts);
49
+ if (numTotalArtifacts) {
50
+ console.log(APP_PREFIX, 'No new artifacts to upload');
51
+ console.log(APP_PREFIX, 'To re-upload artifacts run this command with --force flag');
52
+ }
53
+ process.exit(0);
54
+ }
55
+
56
+ const testrunsByRid = testruns.reduce((acc, { rid, file }) => {
57
+ if (!acc[rid]) {
58
+ acc[rid] = [];
59
+ }
60
+ if (!acc[rid].includes(file)) acc[rid].push(file);
61
+ return acc;
62
+ }, {});
63
+
64
+ let numArtifacts = 0;
65
+
66
+ // we need to obtain S3 credentials
67
+ await client.createRun();
68
+
69
+ client.uploader.checkEnabled();
70
+ client.uploader.disbleLogStorage();
71
+
72
+ for (const rid in testrunsByRid) {
73
+ const files = testrunsByRid[rid];
74
+ numArtifacts += files.length;
75
+ await client.addTestRun(undefined, {
76
+ rid,
77
+ files,
78
+ });
79
+ }
80
+
81
+ console.log(APP_PREFIX, client.uploader.totalUploaded, 'artifacts uploaded');
82
+ if (client.uploader.failedUpload) {
83
+ console.log(APP_PREFIX, client.uploader.failedUpload, 'artifacts failed to upload');
84
+ }
85
+ });
86
+
87
+ if (process.argv.length <= 1) {
88
+ program.outputHelp();
89
+ }
90
+
91
+ program.parse(process.argv);
package/lib/client.js CHANGED
@@ -5,7 +5,7 @@ const { minimatch } = require('minimatch');
5
5
  const fs = require('fs');
6
6
  const chalk = require('chalk');
7
7
  const { randomUUID } = require('crypto');
8
- const upload = require('./fileUploader');
8
+ const S3Uploader = require('./uploader');
9
9
  const { APP_PREFIX, STATUS } = require('./constants');
10
10
  const pipesFactory = require('./pipe');
11
11
  const { glob } = require('glob');
@@ -26,14 +26,14 @@ class Client {
26
26
  * @param {*} params
27
27
  */
28
28
  constructor(params = {}) {
29
- const store = {};
30
- this.uuid = randomUUID();
31
- this.pipes = pipesFactory(params, store);
29
+ this.pipeStore = {};
30
+ this.runId = randomUUID(); // will be replaced by real run id
31
+ this.pipes = pipesFactory(params, this.pipeStore);
32
32
  this.queue = Promise.resolve();
33
- this.totalUploaded = 0;
34
- this.failedToUpload = 0;
35
33
  this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
36
34
  this.executionList = Promise.resolve();
35
+ this.uploader = new S3Uploader();
36
+ this.uploader.checkEnabled();
37
37
 
38
38
  console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
39
39
  }
@@ -104,7 +104,13 @@ class Client {
104
104
  this.queue = this.queue
105
105
  .then(() => Promise.all(this.pipes.map(p => p.createRun())))
106
106
  .catch(err => console.log(APP_PREFIX, err))
107
+ .then(() => {
108
+ const runId = this.pipeStore?.runId;
109
+ if (runId) this.runId = runId;
110
+ this.uploader.checkEnabled();
111
+ })
107
112
  .then(() => undefined); // fixes return type
113
+
108
114
  // debug('Run', this.queue);
109
115
  return this.queue;
110
116
  }
@@ -169,24 +175,28 @@ class Client {
169
175
 
170
176
  const uploadedFiles = [];
171
177
 
172
- for (const f of files) {
173
- uploadedFiles.push(upload.uploadFileByPath(f, this.uuid));
178
+ for (let f of files) {
179
+ if (typeof f === 'object') {
180
+ if (!f.path) continue;
181
+
182
+ f = f.path;
183
+ }
184
+
185
+ uploadedFiles.push(this.uploader.uploadFileByPath(f, [
186
+ this.runId,
187
+ rid,
188
+ path.basename(f)
189
+ ]));
190
+
174
191
  }
175
192
 
176
193
  for (const [idx, buffer] of filesBuffers.entries()) {
177
194
  const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
178
- uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
195
+ uploadedFiles.push(this.uploader.uploadFileAsBuffer(buffer, [this.runId, rid, fileName]));
179
196
  }
180
197
 
181
198
  const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
182
199
 
183
- if (artifacts.length < uploadedFiles.length) {
184
- const failedUploading = uploadedFiles.length - artifacts.length;
185
- this.failedToUpload += failedUploading;
186
- }
187
-
188
- this.totalUploaded += artifacts.length;
189
-
190
200
  const data = {
191
201
  rid,
192
202
  files,
@@ -242,28 +252,30 @@ class Client {
242
252
  this.queue = this.queue
243
253
  .then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
244
254
  .then(() => {
245
- debug('TOTAL artifacts', this.totalUploaded);
246
- if (this.totalUploaded && !upload.isArtifactsEnabled())
247
- debug(`${this.totalUploaded} artifacts are not uploaded, because artifacts uploading is not enabled`);
255
+ debug('TOTAL artifacts', this.uploader.totalUploaded);
256
+ debug(`${this.uploader.skippedUpload} artifacts skipped`);
248
257
 
249
- if (this.totalUploaded && upload.isArtifactsEnabled()) {
258
+ if (this.uploader.totalUploaded && this.uploader.isEnabled) {
250
259
  console.log(
251
260
  APP_PREFIX,
252
- `🗄️ ${this.totalUploaded} artifacts ${
261
+ `🗄️ ${this.uploader.totalUploaded} artifacts ${
253
262
  process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
254
263
  } uploaded to S3 bucket`,
255
- );
256
-
257
- if (this.failedToUpload > 0) {
258
- console.log(
259
- APP_PREFIX,
260
- chalk.yellow(
261
- `Some artifacts were not uploaded. ${this.failedToUpload} artifacts could not be uploaded.
262
- Run tests with DEBUG="@testomatio/reporter:file-uploader" to see details"`,
263
- ),
264
- );
265
- }
264
+ );
266
265
  }
266
+
267
+ if (this.uploader.failedUpload) {
268
+ console.log(APP_PREFIX, `${this.uploader.failedUpload} artifacts failed to upload`);
269
+ }
270
+
271
+ if (this.uploader.isEnabled && this.uploader.skippedUpload) {
272
+ console.log(APP_PREFIX, `${chalk.bold(this.uploader.skippedUpload)} artifacts skipped to upload`);
273
+ }
274
+
275
+ if (this.uploader.skippedUpload || this.uploader.failedUpload) {
276
+ console.log(APP_PREFIX, `Run "${chalk.magenta(`TESTOMATIO_RUN=${this.runId} npx upload-artifacts`)}" with valid S3 credentials to upload skipped & failed artifacts`);
277
+ }
278
+
267
279
  })
268
280
  .catch(err => console.log(APP_PREFIX, err));
269
281
 
@@ -37,7 +37,7 @@ class TestomatioPipe {
37
37
  this.isEnabled = false;
38
38
  this.url = params.testomatioUrl || process.env.TESTOMATIO_URL || 'https://app.testomat.io';
39
39
  this.apiKey = params.apiKey || config.TESTOMATIO;
40
- debug('Testomatio Pipe: ', this.apiKey ? 'API KEY' : '*no api key*');
40
+
41
41
  if (!this.apiKey) {
42
42
  return;
43
43
  }
@@ -149,7 +149,6 @@ class TestomatioPipe {
149
149
  */
150
150
  async createRun(params = {}) {
151
151
  this.batch.isEnabled = params.isBatchEnabled ?? this.batch.isEnabled;
152
- debug('Creating run...');
153
152
  if (!this.isEnabled) return;
154
153
  if (this.batch.isEnabled) this.batch.intervalFunction = setInterval(this.#batchUpload, this.batch.intervalTime);
155
154
 
@@ -191,12 +190,14 @@ class TestomatioPipe {
191
190
  debug('Run params', JSON.stringify(runParams, null, 2));
192
191
 
193
192
  if (this.runId) {
193
+ this.store.runId = this.runId;
194
194
  debug(`Run with id ${this.runId} already created, updating...`);
195
195
  const resp = await this.axios.put(`/api/reporter/${this.runId}`, runParams);
196
196
  if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
197
197
  return;
198
198
  }
199
199
 
200
+ debug('Creating run...');
200
201
  try {
201
202
  const resp = await this.axios.post(`/api/reporter`, runParams, {
202
203
  maxContentLength: Infinity,
@@ -412,6 +413,7 @@ class TestomatioPipe {
412
413
  console.log(APP_PREFIX, `📊 ${notFinishedMessage}. Report URL: ${chalk.magenta(this.runUrl)}`);
413
414
  console.log(APP_PREFIX, `🛬 Run to finish it: TESTOMATIO_RUN=${this.runId} npx start-test-run --finish`);
414
415
  }
416
+
415
417
  if (this.hasUnmatchedTests) {
416
418
  console.log('');
417
419
  // eslint-disable-next-line max-len
@@ -0,0 +1,308 @@
1
+ const debug = require('debug')('@testomatio/reporter:uploader');
2
+ const { S3 } = require('@aws-sdk/client-s3');
3
+ const { Upload } = require('@aws-sdk/lib-storage');
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+ const promiseRetry = require('promise-retry');
8
+ const chalk = require('chalk');
9
+ const { APP_PREFIX } = require('./constants');
10
+
11
+ class S3Uploader {
12
+ constructor() {
13
+ this.isEnabled = undefined;
14
+ this.storeEnabled = true;
15
+ this.config = undefined;
16
+
17
+ // counters
18
+ this.skippedUpload = 0;
19
+ this.failedUpload = 0;
20
+ this.totalUploaded = 0;
21
+
22
+ this.succesfulUploads = {};
23
+
24
+ this.configKeys = [
25
+ 'S3_ENDPOINT',
26
+ 'S3_REGION',
27
+ 'S3_BUCKET',
28
+ 'S3_ACCESS_KEY_ID',
29
+ 'S3_SECRET_ACCESS_KEY',
30
+ 'S3_SESSION_TOKEN',
31
+ 'S3_FORCE_PATH_STYLE',
32
+ 'TESTOMATIO_DISABLE_ARTIFACTS',
33
+ 'TESTOMATIO_PRIVATE_ARTIFACTS',
34
+ 'TESTOMATIO_ARTIFACTS_SIZE'
35
+ ];
36
+ }
37
+
38
+ resetConfig() {
39
+ this.config = undefined;
40
+ this.isEnabled = undefined;
41
+ }
42
+
43
+ getConfig() {
44
+ if (this.config) return this.config;
45
+ this.config = this.configKeys.reduce((acc, key) => {
46
+ acc[key] = process.env[key];
47
+ return acc;
48
+ }, {});
49
+ return this.config;
50
+ }
51
+
52
+ getMaskedConfig() {
53
+ return Object.fromEntries(
54
+ Object.entries(this.getConfig()).map(([key, value]) => [
55
+ key,
56
+ key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
57
+ ]),
58
+ );
59
+ }
60
+
61
+ checkEnabled() {
62
+ if (this.isEnabled !== undefined) return this.isEnabled;
63
+
64
+ const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = this.getConfig();
65
+ if (!S3_BUCKET) debug(`Upload is disabled because S3_BUCKET is not set`);
66
+ this.isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
67
+
68
+ if (this.isEnabled) debug('S3 uploader is enabled');
69
+ debug(this.getMaskedConfig());
70
+
71
+ return this.isEnabled;
72
+ }
73
+
74
+ enableLogStorage() {
75
+ this.storeEnabled = true;
76
+ }
77
+
78
+ disbleLogStorage() {
79
+ this.storeEnabled = false;
80
+ }
81
+
82
+ async uploadToS3(Body, Key) {
83
+ const { S3_BUCKET, TESTOMATIO_PRIVATE_ARTIFACTS } = this.getConfig();
84
+ const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
85
+
86
+ if (!S3_BUCKET || !Body) {
87
+ console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), this.getMaskedConfig());
88
+ return;
89
+ }
90
+
91
+ debug('Uploading to S3:', Key);
92
+
93
+ const s3 = new S3(this.getS3Config());
94
+
95
+ try {
96
+ const upload = new Upload({
97
+ client: s3,
98
+ params: {
99
+ Bucket: S3_BUCKET,
100
+ Key,
101
+ Body,
102
+ ACL,
103
+ },
104
+ });
105
+
106
+ const link = await this.getS3LocationLink(upload);
107
+ this.totalUploaded++;
108
+ this.succesfulUploads[Key] = link;
109
+ return link;
110
+ } catch (e) {
111
+ this.failedUpload++;
112
+ debug('S3 uploading error:', e);
113
+ console.log(APP_PREFIX, "Upload failed:", e.message, this.getMaskedConfig());
114
+ }
115
+ }
116
+
117
+ readUploadedFiles(runId) {
118
+ const tempFilePath = this.#getUploadFilePath(runId);
119
+
120
+ debug('Reading file', tempFilePath);
121
+
122
+ if (!fs.existsSync(tempFilePath)) {
123
+ debug('File not found:', tempFilePath);
124
+ return [];
125
+ }
126
+
127
+ const stats = fs.statSync(tempFilePath);
128
+ const diff = (+new Date()) - (+stats.mtime);
129
+ const diffHours = diff / 1000 / 60 / 60;
130
+ if (diffHours > 3) {
131
+ console.log(APP_PREFIX, 'Artifacts file is too old, can\'t process artifacts. Please re-run the tests.');
132
+ return [];
133
+ }
134
+
135
+ const data = fs.readFileSync(tempFilePath, 'utf8');
136
+ const lines = data.split('\n').filter(Boolean);
137
+ return lines.map(line => JSON.parse(line));
138
+ }
139
+
140
+ #getUploadFilePath(runId, forceCreate = false) {
141
+ const tempFilePath = path.join(os.tmpdir(), `testomatio.run.${runId}.jsonl`);
142
+ if (!fs.existsSync(tempFilePath) || forceCreate) {
143
+ debug('Creating artifacts file:', tempFilePath);
144
+ fs.writeFileSync(tempFilePath, '');
145
+ // make symlink to 'testomatio.run.latest.jsonl' file
146
+ const latestFilePath = path.join(os.tmpdir(), 'testomatio.run.latest.jsonl');
147
+ if (fs.existsSync(latestFilePath)) {
148
+ fs.unlinkSync(latestFilePath);
149
+ }
150
+ fs.symlinkSync(tempFilePath, latestFilePath);
151
+
152
+ }
153
+ return tempFilePath;
154
+ }
155
+
156
+ storeUploadedFile(filePath, runId, rid, uploaded = false) {
157
+ if (!this.storeEnabled) return;
158
+
159
+ if (!filePath || !runId || !rid ) return;
160
+
161
+ const tempFilePath = this.#getUploadFilePath(runId);
162
+
163
+ const data = { rid, file: filePath, uploaded };
164
+ const jsonLine = JSON.stringify(data) + '\n';
165
+
166
+ fs.appendFileSync(tempFilePath, jsonLine);
167
+ }
168
+
169
+ getskippedUpload() {
170
+ return this.skippedUpload;
171
+ }
172
+
173
+ async uploadFileByPath(filePath, pathInS3) {
174
+ const [runId, rid] = pathInS3;
175
+
176
+ if (!this.isEnabled) {
177
+ this.storeUploadedFile(filePath, runId, rid, false);
178
+ this.skippedUpload++;
179
+ return;
180
+ }
181
+
182
+ const {
183
+ S3_BUCKET,
184
+ TESTOMATIO_ARTIFACTS_SIZE,
185
+ } = this.getConfig();
186
+
187
+ debug('Started upload', filePath, 'to', S3_BUCKET);
188
+
189
+ const isFileExist = await this.checkFileExists(filePath, 20, 500);
190
+
191
+ if (!isFileExist) {
192
+ this.failedUpload++;
193
+ console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
194
+ return;
195
+ }
196
+
197
+ const fileSize = fs.statSync(filePath).size;
198
+ const fileSizeInMb = fileSize / (1024 * 1024);
199
+
200
+ if (TESTOMATIO_ARTIFACTS_SIZE && fileSizeInMb > parseInt(TESTOMATIO_ARTIFACTS_SIZE)) {
201
+ this.skippedUpload++;
202
+ console.error(chalk.yellow(`Artifacts file ${filePath} exceeds the maximum allowed size. Skipping...`));
203
+ return;
204
+ }
205
+ debug('File:', filePath, 'exists, size:', fileSizeInMb.toFixed(2), 'MB');
206
+
207
+ const fileStream = fs.createReadStream(filePath);
208
+ const Key = pathInS3.join('/');
209
+
210
+ const link = await this.uploadToS3(fileStream, Key);
211
+
212
+ this.storeUploadedFile(filePath, runId, rid, !!link);
213
+
214
+ return link;
215
+ }
216
+
217
+ async uploadFileAsBuffer(buffer, pathInS3) {
218
+ if (!this.isEnabled) return;
219
+
220
+ let Key = pathInS3.join('/');
221
+ const ext = this.#getFileExtBase64(buffer);
222
+
223
+ if (ext) {
224
+ Key = `${Key}.${ext}`;
225
+ }
226
+
227
+ return this.uploadToS3(buffer, Key);
228
+ }
229
+
230
+ async checkFileExists(filePath, attempts = 5, intervalMs = 500) {
231
+ return promiseRetry(
232
+ async (retry, number) => {
233
+ try {
234
+ fs.accessSync(filePath);
235
+ return true;
236
+ } catch (err) {
237
+ if (number === attempts) {
238
+ return false;
239
+ }
240
+ debug(`File not found, retrying (attempt ${number}/${attempts})`);
241
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
242
+ retry(err);
243
+ }
244
+ },
245
+ {
246
+ retries: attempts,
247
+ minTimeout: intervalMs,
248
+ maxTimeout: intervalMs,
249
+ }
250
+ );
251
+ }
252
+
253
+ async getS3LocationLink(out) {
254
+ const response = await out.done();
255
+
256
+ let s3Location = response?.Location;
257
+
258
+ if (!s3Location) {
259
+ s3Location = out?.singleUploadResult?.Location;
260
+ debug('Uploaded singleUploadResult.Location', s3Location);
261
+
262
+ if (!s3Location) {
263
+ throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
264
+ }
265
+ }
266
+
267
+ return s3Location;
268
+ }
269
+
270
+ #getFileExtBase64(str) {
271
+ const type = str.charAt(0);
272
+
273
+ return (
274
+ {
275
+ '/': 'jpg',
276
+ i: 'png',
277
+ R: 'gif',
278
+ U: 'webp',
279
+ }[type] || ''
280
+ );
281
+ }
282
+
283
+ getS3Config() {
284
+ const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
285
+ this.getConfig();
286
+
287
+ const cfg = {
288
+ region: S3_REGION,
289
+ credentials: {
290
+ accessKeyId: S3_ACCESS_KEY_ID,
291
+ secretAccessKey: S3_SECRET_ACCESS_KEY,
292
+ s3ForcePathStyle: S3_FORCE_PATH_STYLE,
293
+ },
294
+ };
295
+
296
+ if (S3_SESSION_TOKEN) {
297
+ cfg.credentials.sessionToken = S3_SESSION_TOKEN;
298
+ }
299
+
300
+ if (S3_ENDPOINT) {
301
+ cfg.endpoint = S3_ENDPOINT;
302
+ }
303
+
304
+ return cfg;
305
+ }
306
+ }
307
+
308
+ module.exports = S3Uploader;
@@ -1,4 +1,4 @@
1
- const { resetConfig } = require('../fileUploader');
1
+ const { resetConfig } = require('../uploader');
2
2
  const { APP_PREFIX } = require('../constants');
3
3
 
4
4
  /**
package/lib/xmlReader.js CHANGED
@@ -13,7 +13,7 @@ const {
13
13
  fetchIdFromCode,
14
14
  humanize,
15
15
  } = require('./utils/utils');
16
- const upload = require('./fileUploader');
16
+ const S3Uploader = require('./uploader');
17
17
  const pipesFactory = require('./pipe');
18
18
  const adapterFactory = require('./junit-adapter');
19
19
  const config = require('./config');
@@ -56,7 +56,7 @@ class XmlReader {
56
56
  this.tests = [];
57
57
  this.stats = {};
58
58
  this.stats.language = opts.lang?.toLowerCase();
59
- this.filesToUpload = {};
59
+ this.uploader = new S3Uploader();
60
60
 
61
61
  this.version = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString()).version;
62
62
  console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
@@ -382,7 +382,7 @@ class XmlReader {
382
382
  if (!files.length) continue;
383
383
 
384
384
  const runId = this.runId || this.store.runId || Date.now().toString();
385
- test.artifacts = await Promise.all(files.map(f => upload.uploadFileByPath(f, runId)));
385
+ test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId])));
386
386
  console.log(APP_PREFIX, `🗄️ Uploaded ${chalk.bold(`${files.length} artifacts`)} for test ${test.title}`);
387
387
  }
388
388
  }
@@ -398,7 +398,9 @@ class XmlReader {
398
398
 
399
399
  debug('Run', runParams);
400
400
 
401
- return Promise.all(this.pipes.map(p => p.createRun(runParams)));
401
+ await Promise.all(this.pipes.map(p => p.createRun(runParams)));
402
+
403
+ this.uploader.checkEnabled();
402
404
  }
403
405
 
404
406
  async uploadData() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "1.5.1",
3
+ "version": "1.6.0-beta-1-artifacts",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "main": "./lib/reporter.js",
6
6
  "typings": "typings/index.d.ts",
@@ -16,7 +16,7 @@
16
16
  "axios-retry": "^3.9.1",
17
17
  "callsite-record": "^4.1.4",
18
18
  "chalk": "^4.1.0",
19
- "commander": "^4.1.1",
19
+ "commander": "^12",
20
20
  "cross-spawn": "^7.0.3",
21
21
  "csv-writer": "^1.6.0",
22
22
  "debug": "^4.3.4",
@@ -81,7 +81,9 @@
81
81
  "puppeteer": "^22.15.0"
82
82
  },
83
83
  "bin": {
84
+ "@testomatio/reporter": "./lib/bin/cli.js",
84
85
  "report-xml": "./lib/bin/reportXml.js",
85
- "start-test-run": "./lib/bin/startTest.js"
86
+ "start-test-run": "./lib/bin/startTest.js",
87
+ "upload-artifacts": "./lib/bin/uploadArtifacts.js"
86
88
  }
87
89
  }
@@ -1,306 +0,0 @@
1
- const debug = require('debug')('@testomatio/reporter:file-uploader');
2
- const { S3 } = require('@aws-sdk/client-s3');
3
- const { Upload } = require('@aws-sdk/lib-storage');
4
-
5
- const fs = require('fs');
6
- const util = require('util');
7
- const path = require('path');
8
- const promiseRetry = require('promise-retry');
9
-
10
- const readFile = util.promisify(fs.readFile);
11
- const stat = util.promisify(fs.stat);
12
- const chalk = require('chalk');
13
- const { randomUUID } = require('crypto');
14
-
15
- const { APP_PREFIX } = require('./constants');
16
-
17
- const keys = [
18
- 'S3_ENDPOINT',
19
- 'S3_REGION',
20
- 'S3_BUCKET',
21
- 'S3_ACCESS_KEY_ID',
22
- 'S3_SECRET_ACCESS_KEY',
23
- 'S3_SESSION_TOKEN',
24
- 'TESTOMATIO_DISABLE_ARTIFACTS',
25
- 'TESTOMATIO_PRIVATE_ARTIFACTS',
26
- 'S3_FORCE_PATH_STYLE',
27
- ];
28
-
29
- let config;
30
-
31
- function resetConfig() {
32
- config = undefined;
33
- isEnabled = undefined;
34
- }
35
-
36
- function getConfig() {
37
- if (config) return config;
38
- config = keys.reduce((acc, key) => {
39
- acc[key] = process.env[key];
40
- return acc;
41
- }, {});
42
- return config;
43
- }
44
-
45
- function getMaskedConfig() {
46
- return Object.fromEntries(
47
- Object.entries(getConfig()).map(([key, value]) => [
48
- key,
49
- key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
50
- ]),
51
- );
52
- }
53
-
54
- let isEnabled;
55
-
56
- const isArtifactsEnabled = () => {
57
- if (isEnabled !== undefined) return isEnabled;
58
- const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = getConfig();
59
- isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
60
- debug(`Upload is ${isEnabled ? 'enabled' : 'disabled'}`);
61
- return isEnabled;
62
- };
63
-
64
- const _getFileExtBase64 = str => {
65
- const type = str.charAt(0);
66
-
67
- return (
68
- {
69
- '/': '.jpg',
70
- i: '.png',
71
- R: '.gif',
72
- U: '.webp',
73
- }[type] || ''
74
- );
75
- };
76
-
77
- const _getS3Config = () => {
78
- const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
79
- getConfig();
80
-
81
- const cfg = {
82
- region: S3_REGION,
83
- credentials: {
84
- accessKeyId: S3_ACCESS_KEY_ID,
85
- secretAccessKey: S3_SECRET_ACCESS_KEY,
86
- s3ForcePathStyle: S3_FORCE_PATH_STYLE,
87
- },
88
- };
89
-
90
- if (S3_SESSION_TOKEN) {
91
- cfg.credentials.sessionToken = S3_SESSION_TOKEN;
92
- }
93
-
94
- if (S3_ENDPOINT) {
95
- cfg.endpoint = S3_ENDPOINT;
96
- }
97
-
98
- return cfg;
99
- };
100
-
101
- const uploadUsingS3 = async (filePath, runId) => {
102
- let ContentType;
103
- let Key;
104
-
105
- if (typeof filePath === 'object') {
106
- ContentType = filePath?.type;
107
- filePath = filePath?.path;
108
- Key = filePath?.name;
109
- }
110
-
111
- const { TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } = getConfig();
112
-
113
- try {
114
- debug('S3 config', getMaskedConfig());
115
- debug('Started upload', filePath, 'to ', S3_BUCKET);
116
-
117
- // Verification that the file was actually created: 20 attempts of 0.5 second => 10sec
118
- const isFileExist = await checkFileExists(filePath, 20, 500);
119
-
120
- if (!isFileExist) {
121
- console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
122
- return;
123
- }
124
-
125
- debug('File: ', filePath, ' exists');
126
-
127
- const fileData = await readFile(filePath);
128
-
129
- Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
130
-
131
- const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
132
-
133
- if (!S3_BUCKET || !fileData) {
134
- console.log(
135
- APP_PREFIX,
136
- chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`),
137
- getMaskedConfig(),
138
- );
139
- return;
140
- }
141
-
142
- const s3 = new S3(_getS3Config());
143
-
144
- const params = {
145
- Bucket: S3_BUCKET,
146
- Key,
147
- Body: fileData,
148
- ContentType,
149
- ACL,
150
- };
151
-
152
- const out = new Upload({
153
- client: s3,
154
- params,
155
- });
156
-
157
- const link = await getS3LocationLink(out);
158
-
159
- debug(`Succesfully uploaded ${filePath} => ${S3_BUCKET}/${Key} | URL: ${link}`);
160
-
161
- return link;
162
- } catch (e) {
163
- debug('S3 file uploading error: ', e);
164
-
165
- console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
166
-
167
- if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
168
- console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
169
- } else {
170
- console.log(
171
- APP_PREFIX,
172
- `To enable ${chalk.bold('PUBLIC')} uploads remove TESTOMATIO_PRIVATE_ARTIFACTS env variable`,
173
- );
174
- }
175
- console.log(APP_PREFIX, '---------------');
176
- }
177
- };
178
-
179
- const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
180
- const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } =
181
- getConfig();
182
-
183
- const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
184
-
185
- const fileExtension = _getFileExtBase64(buffer.toString('base64'));
186
- const Key = `${runId}/${fileName}${fileExtension}`;
187
-
188
- if (!S3_BUCKET || !buffer) {
189
- console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), {
190
- accessKeyId: S3_ACCESS_KEY_ID,
191
- secretAccessKey: S3_SECRET_ACCESS_KEY ? '**** (hidden) ***' : '(empty)',
192
- region: S3_REGION,
193
- bucket: S3_BUCKET,
194
- acl: ACL,
195
- endpoint: S3_ENDPOINT,
196
- });
197
- return;
198
- }
199
-
200
- const s3 = new S3(_getS3Config());
201
-
202
- try {
203
- const out = new Upload({
204
- client: s3,
205
-
206
- params: {
207
- Bucket: S3_BUCKET,
208
- Key,
209
- Body: buffer,
210
- ACL,
211
- },
212
- });
213
-
214
- return await getS3LocationLink(out);
215
- } catch (e) {
216
- debug('S3 buffer uploading error: ', e);
217
-
218
- console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
219
-
220
- if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
221
- console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
222
- } else {
223
- console.log(
224
- APP_PREFIX,
225
- `To enable ${chalk.bold('PUBLIC')} uploads remove TESTOMATIO_PRIVATE_ARTIFACTS env variable`,
226
- );
227
- }
228
- console.log(APP_PREFIX, '---------------');
229
- }
230
- };
231
-
232
- const uploadFileByPath = async (filePath, runId) => {
233
- try {
234
- if (isArtifactsEnabled()) {
235
- return uploadUsingS3(filePath, runId);
236
- }
237
- } catch (e) {
238
- debug(e);
239
-
240
- console.error(chalk.red('Error occurred while uploading artifacts! '), e);
241
- }
242
- };
243
-
244
- const uploadFileAsBuffer = async (buffer, fileName, runId) => {
245
- try {
246
- if (isArtifactsEnabled()) {
247
- return uploadUsingS3AsBuffer(buffer, fileName, runId);
248
- }
249
- } catch (e) {
250
- debug(e);
251
-
252
- console.error(chalk.red('Error occurred while uploading artifacts! '), e);
253
- }
254
- };
255
-
256
- const checkFileExists = async (filePath, attempts = 5, intervalMs = 500) => {
257
- const checkFile = async () => {
258
- const fileStats = await stat(filePath);
259
- if (fileStats.isFile()) {
260
- return true;
261
- }
262
-
263
- throw new Error('File not found');
264
- };
265
-
266
- try {
267
- await promiseRetry(
268
- {
269
- retries: attempts,
270
- minTimeout: intervalMs,
271
- },
272
- checkFile,
273
- );
274
-
275
- return true;
276
- } catch (err) {
277
- console.error(chalk.yellow(`File ${filePath} was not found or did not have time to be generated...`));
278
-
279
- return false;
280
- }
281
- };
282
-
283
- const getS3LocationLink = async out => {
284
- const response = await out.done();
285
-
286
- let s3Location = response?.Location;
287
-
288
- if (!s3Location) {
289
- // TODO: out: a fallback case - remove after deeper testing
290
- s3Location = out?.singleUploadResult?.Location;
291
- debug('Uploaded singleUploadResult.Location', s3Location);
292
-
293
- if (!s3Location) {
294
- throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
295
- }
296
- }
297
-
298
- return s3Location;
299
- };
300
-
301
- module.exports = {
302
- uploadFileByPath,
303
- uploadFileAsBuffer,
304
- isArtifactsEnabled,
305
- resetConfig,
306
- };