playwright-slack-report-burak 2.4.3 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/LayoutGenerator.js +28 -7
- package/dist/src/ResultsParser.js +222 -17
- package/dist/src/SlackReporter.js +12 -3
- package/package.json +3 -2
|
@@ -8,6 +8,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
8
8
|
exports.generateAllRunSuites =
|
|
9
9
|
void 0;
|
|
10
10
|
|
|
11
|
+
// Helper function to get build URL and CI platform name
|
|
12
|
+
const getBuildInfo = () => {
|
|
13
|
+
if (process.env.GITHUB_ACTIONS || process.env.GITHUB_TOKEN) {
|
|
14
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
15
|
+
const runId = process.env.GITHUB_RUN_ID;
|
|
16
|
+
const serverUrl = process.env.GITHUB_SERVER_URL || 'https://github.com';
|
|
17
|
+
const buildUrl = `${serverUrl}/${repo}/actions/runs/${runId}`;
|
|
18
|
+
return {
|
|
19
|
+
url: buildUrl,
|
|
20
|
+
platform: 'GitHub Actions'
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
url: '#',
|
|
25
|
+
platform: 'CI'
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
11
29
|
const generateAllRunSuites = async (summaryResults) => {
|
|
12
30
|
const suitesResults = [];
|
|
13
31
|
const allSuites = [];
|
|
@@ -255,12 +273,13 @@ const generateProblemCaseList = async (summaryResults) => {
|
|
|
255
273
|
let text = `${title}${list.map((value, index) => `*${index + 1}.* ${value}`).join('\n')}`;
|
|
256
274
|
if (text.length > 2700) {
|
|
257
275
|
text = text.substring(0, 2700) + '...';
|
|
276
|
+
const buildInfo = getBuildInfo();
|
|
258
277
|
array.push({
|
|
259
278
|
type: 'section',
|
|
260
279
|
text: {
|
|
261
280
|
type: 'mrkdwn',
|
|
262
|
-
text: text + '\n\n\n' +
|
|
263
|
-
|
|
281
|
+
text: text + '\n\n\n' + `⚠️ *There are too many items to display here. ` +
|
|
282
|
+
`You can view more on ${buildInfo.platform}* ⚠️`,
|
|
264
283
|
},
|
|
265
284
|
},
|
|
266
285
|
{
|
|
@@ -271,7 +290,7 @@ const generateProblemCaseList = async (summaryResults) => {
|
|
|
271
290
|
type: 'plain_text',
|
|
272
291
|
text: 'Go to Build Details'
|
|
273
292
|
},
|
|
274
|
-
url:
|
|
293
|
+
url: buildInfo.url
|
|
275
294
|
}]
|
|
276
295
|
});
|
|
277
296
|
} else {
|
|
@@ -356,12 +375,13 @@ const generateFailuresReasons = async (summaryResults) => {
|
|
|
356
375
|
},
|
|
357
376
|
});
|
|
358
377
|
if (i === summaryResults.failures.length-1) {
|
|
378
|
+
const buildInfo = getBuildInfo();
|
|
359
379
|
failsList.push({
|
|
360
380
|
type: 'section',
|
|
361
381
|
text: {
|
|
362
382
|
type: 'mrkdwn',
|
|
363
383
|
text: '⚠️ *There are too many failures to display in full detail. '+
|
|
364
|
-
|
|
384
|
+
`You can view more on ${buildInfo.platform}* ⚠️`,
|
|
365
385
|
},
|
|
366
386
|
},
|
|
367
387
|
{
|
|
@@ -373,7 +393,7 @@ const generateFailuresReasons = async (summaryResults) => {
|
|
|
373
393
|
type: 'plain_text',
|
|
374
394
|
text: 'Go to Build Details'
|
|
375
395
|
},
|
|
376
|
-
url:
|
|
396
|
+
url: buildInfo.url
|
|
377
397
|
}
|
|
378
398
|
]
|
|
379
399
|
});
|
|
@@ -391,12 +411,13 @@ const generateFailuresReasons = async (summaryResults) => {
|
|
|
391
411
|
},
|
|
392
412
|
});
|
|
393
413
|
if (i > 23) {
|
|
414
|
+
const buildInfo = getBuildInfo();
|
|
394
415
|
failsList.push({
|
|
395
416
|
type: 'section',
|
|
396
417
|
text: {
|
|
397
418
|
type: 'mrkdwn',
|
|
398
419
|
text: '⚠️ *There are too many failures to display here '+
|
|
399
|
-
|
|
420
|
+
`You can view more on ${buildInfo.platform}* ⚠️`,
|
|
400
421
|
},
|
|
401
422
|
},
|
|
402
423
|
{
|
|
@@ -407,7 +428,7 @@ const generateFailuresReasons = async (summaryResults) => {
|
|
|
407
428
|
type: 'plain_text',
|
|
408
429
|
text: 'Go to Build Details'
|
|
409
430
|
},
|
|
410
|
-
url:
|
|
431
|
+
url: buildInfo.url
|
|
411
432
|
}]
|
|
412
433
|
});
|
|
413
434
|
break;
|
|
@@ -10,12 +10,27 @@ const fs = require('fs');
|
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const axios = require('axios');
|
|
12
12
|
const { exec } = require('child_process');
|
|
13
|
+
let AdmZip;
|
|
14
|
+
try {
|
|
15
|
+
AdmZip = require('adm-zip');
|
|
16
|
+
} catch (e) {
|
|
17
|
+
// adm-zip is required for GitHub Actions artifact extraction
|
|
18
|
+
AdmZip = null;
|
|
19
|
+
}
|
|
13
20
|
|
|
14
21
|
class ResultsParser {
|
|
15
22
|
result;
|
|
16
23
|
separateFlakyTests;
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
// Handle both 0-based and 1-based shard indexing
|
|
25
|
+
// If MATRIX_SHARD is not set, try to infer from workflow (1-based) or default to 0
|
|
26
|
+
totalShardCount = process.env.MATRIX_COUNT ? parseInt(process.env.MATRIX_COUNT, 10) : 1;
|
|
27
|
+
shardIndex = process.env.MATRIX_SHARD !== undefined
|
|
28
|
+
? parseInt(process.env.MATRIX_SHARD, 10)
|
|
29
|
+
: (process.env.MATRIX_INDEX !== undefined ? parseInt(process.env.MATRIX_INDEX, 10) : 0);
|
|
30
|
+
|
|
31
|
+
// Determine if we're using 1-based indexing (workflow-style) or 0-based
|
|
32
|
+
// This is detected by checking if artifacts exist with 1-based naming
|
|
33
|
+
isOneBasedIndexing = false;
|
|
19
34
|
constructor(options = { separateFlakyTests: false }) {
|
|
20
35
|
this.result = [];
|
|
21
36
|
this.separateFlakyTests = options.separateFlakyTests;
|
|
@@ -77,7 +92,7 @@ class ResultsParser {
|
|
|
77
92
|
|
|
78
93
|
if (this.shardIndex === 0 && this.totalShardCount > 1) {
|
|
79
94
|
if (process.env.CI) {
|
|
80
|
-
console.log('Fetching all artifacts...');
|
|
95
|
+
console.log('Fetching all artifacts from GitHub Actions...');
|
|
81
96
|
await this.fetchAllArtifacts();
|
|
82
97
|
} else {
|
|
83
98
|
while (!this.allNodeSummaryFilesExist() || !this.allBlobZipsExist()) {
|
|
@@ -333,6 +348,10 @@ class ResultsParser {
|
|
|
333
348
|
if (!fs.existsSync(summariesDir)) {
|
|
334
349
|
fs.mkdirSync(summariesDir, { recursive: true });
|
|
335
350
|
}
|
|
351
|
+
// Fetch artifacts for shards 1 to totalShardCount-1 (if using 1-based) or 0 to totalShardCount-1 (if 0-based)
|
|
352
|
+
// Since we're shard 0, we need to fetch from other shards
|
|
353
|
+
// The workflow uses 1-based shards (1, 2, 3...), but we need to map them to 0-based internally (0, 1, 2...)
|
|
354
|
+
// So shard 1 in workflow = shard 0 internally, shard 2 = shard 1, etc.
|
|
336
355
|
for (let i = 1; i < this.totalShardCount; i++) {
|
|
337
356
|
await this.fetchArtifact(i, `node_summary_${i}.json`, summariesDir);
|
|
338
357
|
await this.fetchArtifact(i, `blob-report-node-${i}.zip`, summariesDir);
|
|
@@ -341,30 +360,216 @@ class ResultsParser {
|
|
|
341
360
|
}
|
|
342
361
|
|
|
343
362
|
async fetchArtifact(i, file, summariesDir) {
|
|
344
|
-
const circleciToken = process.env.safetywingtest_CIRCLECI_API_TOKEN;
|
|
345
|
-
const circleciJobId = process.env.CIRCLE_WORKFLOW_JOB_ID || '8133c154-ceb3-466c-b4e1-d3d8768b60fa';
|
|
346
|
-
const circleciApiUrl = `https://output.circle-artifacts.com/output/job/${circleciJobId}/artifacts/${i}/html-report/${file}`;
|
|
347
363
|
const filePath = path.join(summariesDir, file);
|
|
364
|
+
await this.fetchArtifactFromGitHubActions(i, file, filePath);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Helper to extract shard index from artifact name
|
|
368
|
+
extractShardIndexFromArtifactName(artifactName, i) {
|
|
369
|
+
// Try to extract number from artifact name (e.g., html-report-1 -> 1)
|
|
370
|
+
const match = artifactName.match(/-(\d+)$/);
|
|
371
|
+
if (match) {
|
|
372
|
+
const artifactShard = parseInt(match[1], 10);
|
|
373
|
+
// If artifact uses 1-based indexing, convert to 0-based for internal use
|
|
374
|
+
// html-report-1 (workflow) -> shard 0 (internal)
|
|
375
|
+
// html-report-2 (workflow) -> shard 1 (internal)
|
|
376
|
+
return artifactShard - 1;
|
|
377
|
+
}
|
|
378
|
+
// Fallback to the provided index
|
|
379
|
+
return i;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async fetchArtifactFromGitHubActions(i, file, filePath) {
|
|
383
|
+
const githubToken = process.env.GITHUB_TOKEN;
|
|
384
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
385
|
+
const runId = process.env.GITHUB_RUN_ID;
|
|
386
|
+
|
|
387
|
+
if (!githubToken || !repo) {
|
|
388
|
+
console.error('GitHub Actions: Missing GITHUB_TOKEN or GITHUB_REPOSITORY');
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
348
391
|
|
|
392
|
+
// GitHub Actions artifact API endpoint
|
|
393
|
+
const githubApiUrl = `https://api.github.com/repos/${repo}/actions/artifacts`;
|
|
394
|
+
|
|
395
|
+
// Try multiple artifact naming patterns:
|
|
396
|
+
// 1. html-report-{i} (1-based workflow style)
|
|
397
|
+
// 2. blob-report-node-{i} (direct blob reports)
|
|
398
|
+
// 3. test-results-{i} (alternative naming)
|
|
399
|
+
// Note: i is 0-based internally, so for 1-based workflows we need i+1
|
|
400
|
+
const artifactNamePatterns = [
|
|
401
|
+
`html-report-${i + 1}`, // 1-based workflow style (most common)
|
|
402
|
+
`blob-report-node-${i}`, // 0-based direct blob
|
|
403
|
+
`blob-report-node-${i + 1}`, // 1-based direct blob
|
|
404
|
+
`html-report-${i}`, // 0-based html-report
|
|
405
|
+
`test-results-${i + 1}`, // Alternative naming
|
|
406
|
+
];
|
|
407
|
+
|
|
349
408
|
while (true) {
|
|
350
409
|
try {
|
|
351
|
-
|
|
410
|
+
// List all artifacts to find the one we need
|
|
411
|
+
const listResponse = await axios.get(githubApiUrl, {
|
|
352
412
|
headers: {
|
|
353
|
-
'
|
|
413
|
+
'Authorization': `token ${githubToken}`,
|
|
414
|
+
'Accept': 'application/vnd.github.v3+json'
|
|
354
415
|
},
|
|
355
|
-
|
|
416
|
+
params: {
|
|
417
|
+
per_page: 100
|
|
418
|
+
}
|
|
356
419
|
});
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
420
|
+
|
|
421
|
+
// Try to find artifact matching any of our patterns
|
|
422
|
+
let artifact = null;
|
|
423
|
+
for (const pattern of artifactNamePatterns) {
|
|
424
|
+
artifact = listResponse.data.artifacts.find(
|
|
425
|
+
(a) => a.name === pattern ||
|
|
426
|
+
a.name.includes(`shard-${i}`) ||
|
|
427
|
+
a.name.includes(`shard-${i + 1}`) ||
|
|
428
|
+
a.name.includes(`node-${i}`) ||
|
|
429
|
+
a.name.includes(`node-${i + 1}`)
|
|
430
|
+
);
|
|
431
|
+
if (artifact) {
|
|
432
|
+
console.log(`Found artifact: ${artifact.name} (matched pattern: ${pattern})`);
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (artifact && artifact.archive_download_url) {
|
|
438
|
+
// Download the artifact ZIP
|
|
439
|
+
const downloadResponse = await axios.get(artifact.archive_download_url, {
|
|
440
|
+
headers: {
|
|
441
|
+
'Authorization': `token ${githubToken}`,
|
|
442
|
+
'Accept': 'application/vnd.github.v3+json'
|
|
443
|
+
},
|
|
444
|
+
responseType: 'arraybuffer'
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
if (!AdmZip) {
|
|
448
|
+
console.error('adm-zip is required for GitHub Actions artifact extraction. Please install it: npm install adm-zip');
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Extract ZIP and find the specific file
|
|
453
|
+
const zip = new AdmZip(downloadResponse.data);
|
|
454
|
+
|
|
455
|
+
// Try direct file match first
|
|
456
|
+
let zipEntry = zip.getEntry(file);
|
|
457
|
+
|
|
458
|
+
// If not found, try common path variations
|
|
459
|
+
if (!zipEntry) {
|
|
460
|
+
const possiblePaths = [
|
|
461
|
+
file,
|
|
462
|
+
`playwright-report/${file}`,
|
|
463
|
+
`playwright-report/${file}`,
|
|
464
|
+
`html-report-${i + 1}/${file}`,
|
|
465
|
+
`html-report-${i}/${file}`,
|
|
466
|
+
];
|
|
467
|
+
for (const possiblePath of possiblePaths) {
|
|
468
|
+
zipEntry = zip.getEntry(possiblePath);
|
|
469
|
+
if (zipEntry) break;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (zipEntry) {
|
|
474
|
+
fs.writeFileSync(filePath, zipEntry.getData());
|
|
475
|
+
console.log(`Successfully fetched file ${file} from GitHub Actions shard ${i} (artifact: ${artifact.name})`);
|
|
476
|
+
break;
|
|
477
|
+
} else {
|
|
478
|
+
// If file not found in ZIP root, extract and search recursively
|
|
479
|
+
const extractPath = path.join(path.dirname(filePath), `temp-artifact-${i}`);
|
|
480
|
+
zip.extractAllTo(extractPath, true);
|
|
481
|
+
|
|
482
|
+
// Extract shard index from artifact name to handle files named with index 0
|
|
483
|
+
const artifactShardIndex = this.extractShardIndexFromArtifactName(artifact.name, i);
|
|
484
|
+
|
|
485
|
+
// Look for the file recursively, also try with shard index from artifact name
|
|
486
|
+
const searchInDir = (dir, targetFile, alternateTargetFile = null) => {
|
|
487
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
488
|
+
for (const entry of entries) {
|
|
489
|
+
const fullPath = path.join(dir, entry.name);
|
|
490
|
+
if (entry.isDirectory()) {
|
|
491
|
+
const found = searchInDir(fullPath, targetFile, alternateTargetFile);
|
|
492
|
+
if (found) return found;
|
|
493
|
+
} else {
|
|
494
|
+
// Check exact match
|
|
495
|
+
if (entry.name === targetFile) {
|
|
496
|
+
return fullPath;
|
|
497
|
+
}
|
|
498
|
+
// Check alternate filename (e.g., node_summary_0.json when we need node_summary_1.json)
|
|
499
|
+
if (alternateTargetFile && entry.name === alternateTargetFile) {
|
|
500
|
+
return fullPath;
|
|
501
|
+
}
|
|
502
|
+
// Check if ends with target file
|
|
503
|
+
if (entry.name.endsWith(targetFile)) {
|
|
504
|
+
return fullPath;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return null;
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// Try to find the file, also try with shard index from artifact
|
|
512
|
+
let foundPath = searchInDir(extractPath, file);
|
|
513
|
+
|
|
514
|
+
// If file uses index 0 pattern (like node_summary_0.json or blob-report-node-0.zip)
|
|
515
|
+
// but we need it for a different shard, try to find any matching pattern
|
|
516
|
+
if (!foundPath && (file.includes('_0') || file.includes('-node-0'))) {
|
|
517
|
+
// Recursively search for any node_summary or blob-report file
|
|
518
|
+
const findAllFiles = (dir, pattern) => {
|
|
519
|
+
const results = [];
|
|
520
|
+
try {
|
|
521
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
522
|
+
for (const entry of entries) {
|
|
523
|
+
const fullPath = path.join(dir, entry.name);
|
|
524
|
+
if (entry.isDirectory()) {
|
|
525
|
+
results.push(...findAllFiles(fullPath, pattern));
|
|
526
|
+
} else if (entry.name.includes(pattern)) {
|
|
527
|
+
results.push(fullPath);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
} catch (e) {
|
|
531
|
+
// Ignore errors
|
|
532
|
+
}
|
|
533
|
+
return results;
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
// Find any node_summary or blob-report files
|
|
537
|
+
const matchingFiles = findAllFiles(extractPath, file.includes('node_summary') ? 'node_summary' : 'blob-report');
|
|
538
|
+
|
|
539
|
+
if (matchingFiles.length > 0) {
|
|
540
|
+
foundPath = matchingFiles[0];
|
|
541
|
+
// Copy to the expected path with correct shard index
|
|
542
|
+
fs.copyFileSync(foundPath, filePath);
|
|
543
|
+
console.log(`Found and copied file ${path.basename(foundPath)} -> ${file} from artifact ${artifact.name}`);
|
|
544
|
+
// Clean up temp directory
|
|
545
|
+
fs.rmSync(extractPath, { recursive: true, force: true });
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (foundPath) {
|
|
551
|
+
fs.copyFileSync(foundPath, filePath);
|
|
552
|
+
console.log(`Successfully fetched file ${file} from GitHub Actions shard ${i} (artifact: ${artifact.name})`);
|
|
553
|
+
// Clean up temp directory
|
|
554
|
+
fs.rmSync(extractPath, { recursive: true, force: true });
|
|
555
|
+
break;
|
|
556
|
+
} else {
|
|
557
|
+
console.warn(`File ${file} not found in artifact ${artifact.name}. Retrying in 10 seconds...`);
|
|
558
|
+
fs.rmSync(extractPath, { recursive: true, force: true });
|
|
559
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
} else {
|
|
563
|
+
console.warn(`Artifact not found (tried: ${artifactNamePatterns.join(', ')}). Retrying in 10 seconds...`);
|
|
564
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
565
|
+
}
|
|
361
566
|
} catch (error) {
|
|
362
567
|
if (error.response && error.response.status === 404) {
|
|
363
|
-
console.warn(`
|
|
364
|
-
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
568
|
+
console.warn(`Artifact not found. Retrying in 10 seconds...`);
|
|
569
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
365
570
|
} else {
|
|
366
|
-
console.error(`Failed to fetch
|
|
367
|
-
|
|
571
|
+
console.error(`Failed to fetch artifact from GitHub Actions shard ${i}!`, error.message);
|
|
572
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
368
573
|
}
|
|
369
574
|
}
|
|
370
575
|
}
|
|
@@ -71,9 +71,18 @@ class SlackReporter {
|
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
73
|
const resultSummary = await this.resultsParser.getParsedResults();
|
|
74
|
-
// SHARDING SUPPORT - Stop slack messages for non-zero index
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
// SHARDING SUPPORT - Stop slack messages for non-zero index shard(s)
|
|
75
|
+
// Support both explicit MATRIX_SHARD/MATRIX_INDEX and workflow-style 1-based indexing
|
|
76
|
+
// If MATRIX_SHARD is not set, shard 0 will still send reports (handles single shard case)
|
|
77
|
+
const currentShardIndex = process.env.MATRIX_SHARD !== undefined
|
|
78
|
+
? process.env.MATRIX_SHARD
|
|
79
|
+
: (process.env.MATRIX_INDEX !== undefined ? process.env.MATRIX_INDEX : undefined);
|
|
80
|
+
|
|
81
|
+
// Only skip if explicitly set and not 0 (or '0')
|
|
82
|
+
// This allows shard 0 to send reports even when MATRIX_SHARD is not set
|
|
83
|
+
if (currentShardIndex !== undefined && currentShardIndex !== '0' && currentShardIndex !== 0) {
|
|
84
|
+
const totalShards = process.env.MATRIX_COUNT || '1';
|
|
85
|
+
this.log(`❌ Stopping reporter for non-zero index shard ${currentShardIndex} of ${totalShards}`);
|
|
77
86
|
return;
|
|
78
87
|
}
|
|
79
88
|
resultSummary.meta = this.meta;
|
package/package.json
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
"dependencies": {
|
|
3
3
|
"@slack/web-api": "^6.8.1",
|
|
4
4
|
"@slack/webhook": "^6.1.0",
|
|
5
|
-
"https-proxy-agent": "^7.0.1"
|
|
5
|
+
"https-proxy-agent": "^7.0.1",
|
|
6
|
+
"adm-zip": "^0.5.10"
|
|
6
7
|
},
|
|
7
8
|
"devDependencies": {
|
|
8
9
|
"@playwright/test": "^1.23.3",
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
"lint-fix": "npx eslint . --ext .ts --fix"
|
|
31
32
|
},
|
|
32
33
|
"name": "playwright-slack-report-burak",
|
|
33
|
-
"version": "
|
|
34
|
+
"version": "3.0.0",
|
|
34
35
|
"main": "index.js",
|
|
35
36
|
"types": "dist/index.d.ts",
|
|
36
37
|
"author": "Burak B. <burak.boluk@hotmail.com>",
|