allure-report-publisher 5.0.0-alpha.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/LICENSE.txt +21 -0
- package/README.md +253 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/commands/upload/index.d.ts +31 -0
- package/dist/commands/upload/index.js +198 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/allure/config.d.ts +8 -0
- package/dist/lib/allure/config.js +125 -0
- package/dist/lib/allure/report-generator.d.ts +11 -0
- package/dist/lib/allure/report-generator.js +43 -0
- package/dist/lib/ci/info/base.d.ts +6 -0
- package/dist/lib/ci/info/base.js +4 -0
- package/dist/lib/ci/info/github.d.ts +9 -0
- package/dist/lib/ci/info/github.js +21 -0
- package/dist/lib/ci/info/gitlab.d.ts +17 -0
- package/dist/lib/ci/info/gitlab.js +47 -0
- package/dist/lib/uploader/base.d.ts +40 -0
- package/dist/lib/uploader/base.js +101 -0
- package/dist/lib/uploader/s3.d.ts +17 -0
- package/dist/lib/uploader/s3.js +113 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/ci.d.ts +5 -0
- package/dist/utils/ci.js +13 -0
- package/dist/utils/config.d.ts +17 -0
- package/dist/utils/config.js +43 -0
- package/dist/utils/glob.d.ts +4 -0
- package/dist/utils/glob.js +35 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.js +51 -0
- package/dist/utils/spinner.d.ts +4 -0
- package/dist/utils/spinner.js +28 -0
- package/dist/utils/uploader.d.ts +11 -0
- package/dist/utils/uploader.js +19 -0
- package/oclif.manifest.json +237 -0
- package/package.json +82 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AllureConfig } from './config.js';
|
|
2
|
+
export declare class ReportGenerator {
|
|
3
|
+
private readonly resultGlob;
|
|
4
|
+
private readonly allureConfig;
|
|
5
|
+
private readonly downloadHistory;
|
|
6
|
+
private _outputPath;
|
|
7
|
+
constructor(resultGlob: string, allureConfig: AllureConfig, downloadHistory: () => Promise<void>);
|
|
8
|
+
execute(): Promise<void>;
|
|
9
|
+
private outputPath;
|
|
10
|
+
private generateReport;
|
|
11
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import spawn from 'nano-spawn';
|
|
2
|
+
import { globPaths } from '../../utils/glob.js';
|
|
3
|
+
import { logger } from '../../utils/logger.js';
|
|
4
|
+
import { spin } from '../../utils/spinner.js';
|
|
5
|
+
export class ReportGenerator {
|
|
6
|
+
resultGlob;
|
|
7
|
+
allureConfig;
|
|
8
|
+
downloadHistory;
|
|
9
|
+
_outputPath;
|
|
10
|
+
constructor(resultGlob, allureConfig, downloadHistory) {
|
|
11
|
+
this.resultGlob = resultGlob;
|
|
12
|
+
this.allureConfig = allureConfig;
|
|
13
|
+
this.downloadHistory = downloadHistory;
|
|
14
|
+
}
|
|
15
|
+
async execute() {
|
|
16
|
+
await spin(this.downloadHistory(), 'downloading previous run history', { ignoreError: true });
|
|
17
|
+
await spin(this.generateReport(), 'generating report');
|
|
18
|
+
}
|
|
19
|
+
async outputPath() {
|
|
20
|
+
if (!this._outputPath) {
|
|
21
|
+
this._outputPath = await this.allureConfig.outputPath();
|
|
22
|
+
}
|
|
23
|
+
return this._outputPath;
|
|
24
|
+
}
|
|
25
|
+
async generateReport() {
|
|
26
|
+
const args = ['generate', this.resultGlob, '-c', this.allureConfig.configPath(), '-o', await this.outputPath()];
|
|
27
|
+
try {
|
|
28
|
+
logger.debug(`Running allure with args: ${args.join(' ')}`);
|
|
29
|
+
const result = await spawn('allure', args, { preferLocal: true });
|
|
30
|
+
logger.debug('Allure report generation completed successfully');
|
|
31
|
+
if (result.output.trim().length > 0)
|
|
32
|
+
logger.debug(`Allure output:\n${result.output}`);
|
|
33
|
+
const generatedFiles = await globPaths(`${await this.outputPath()}/**/*`, { nodir: true });
|
|
34
|
+
logger.debug(`Total report files for upload: ${generatedFiles.length}`);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const processError = error;
|
|
38
|
+
logger.debug(`Command '${processError.command}' failed with exit code ${processError.exitCode}`);
|
|
39
|
+
logger.debug(`Command execution time: ${processError.durationMs}ms`);
|
|
40
|
+
throw new Error(`Allure report generation failed.\nMessage: ${processError.message}\nOutput: ${processError.output}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCiInfo } from './base.js';
|
|
2
|
+
export declare class GithubCiInfo extends BaseCiInfo {
|
|
3
|
+
get pr(): boolean;
|
|
4
|
+
get runId(): string | undefined;
|
|
5
|
+
get serverUrl(): string | undefined;
|
|
6
|
+
get buildName(): string | undefined;
|
|
7
|
+
get repository(): string | undefined;
|
|
8
|
+
get buildUrl(): string;
|
|
9
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BaseCiInfo } from './base.js';
|
|
2
|
+
export class GithubCiInfo extends BaseCiInfo {
|
|
3
|
+
get pr() {
|
|
4
|
+
return process.env.GITHUB_EVENT_NAME === 'pull_request';
|
|
5
|
+
}
|
|
6
|
+
get runId() {
|
|
7
|
+
return process.env[BaseCiInfo.ALLURE_RUN_ID] || process.env.GITHUB_RUN_ID;
|
|
8
|
+
}
|
|
9
|
+
get serverUrl() {
|
|
10
|
+
return process.env.GITHUB_SERVER_URL;
|
|
11
|
+
}
|
|
12
|
+
get buildName() {
|
|
13
|
+
return process.env[BaseCiInfo.ALLURE_JOB_NAME] || process.env.GITHUB_JOB;
|
|
14
|
+
}
|
|
15
|
+
get repository() {
|
|
16
|
+
return process.env.GITHUB_REPOSITORY;
|
|
17
|
+
}
|
|
18
|
+
get buildUrl() {
|
|
19
|
+
return `${this.serverUrl}/${this.repository}/actions/runs/${this.runId}`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BaseCiInfo } from './base.js';
|
|
2
|
+
export declare class GitlabCiInfo extends BaseCiInfo {
|
|
3
|
+
get pr(): boolean;
|
|
4
|
+
get runId(): string | undefined;
|
|
5
|
+
get jobId(): string | undefined;
|
|
6
|
+
get projectPath(): string | undefined;
|
|
7
|
+
get projectId(): string | undefined;
|
|
8
|
+
get buildDir(): string | undefined;
|
|
9
|
+
get branch(): string | undefined;
|
|
10
|
+
get serverUrl(): string | undefined;
|
|
11
|
+
get buildUrl(): string | undefined;
|
|
12
|
+
get allureProject(): string | undefined;
|
|
13
|
+
get mrIid(): string | undefined;
|
|
14
|
+
get allureMrIid(): string | undefined;
|
|
15
|
+
get buildName(): string | undefined;
|
|
16
|
+
get jobName(): string | undefined;
|
|
17
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { BaseCiInfo } from './base.js';
|
|
2
|
+
export class GitlabCiInfo extends BaseCiInfo {
|
|
3
|
+
get pr() {
|
|
4
|
+
return Boolean((this.allureProject && this.allureMrIid) || this.mrIid);
|
|
5
|
+
}
|
|
6
|
+
get runId() {
|
|
7
|
+
const allureRunId = process.env[BaseCiInfo.ALLURE_RUN_ID];
|
|
8
|
+
const ciPipelineId = process.env.CI_PIPELINE_ID;
|
|
9
|
+
return allureRunId || ciPipelineId;
|
|
10
|
+
}
|
|
11
|
+
get jobId() {
|
|
12
|
+
return process.env.CI_JOB_ID;
|
|
13
|
+
}
|
|
14
|
+
get projectPath() {
|
|
15
|
+
return process.env.CI_PROJECT_PATH;
|
|
16
|
+
}
|
|
17
|
+
get projectId() {
|
|
18
|
+
return process.env.CI_PROJECT_ID;
|
|
19
|
+
}
|
|
20
|
+
get buildDir() {
|
|
21
|
+
return process.env.CI_PROJECT_DIR;
|
|
22
|
+
}
|
|
23
|
+
get branch() {
|
|
24
|
+
return process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME || process.env.CI_COMMIT_REF_NAME;
|
|
25
|
+
}
|
|
26
|
+
get serverUrl() {
|
|
27
|
+
return process.env.CI_SERVER_URL;
|
|
28
|
+
}
|
|
29
|
+
get buildUrl() {
|
|
30
|
+
return process.env.CI_PIPELINE_URL;
|
|
31
|
+
}
|
|
32
|
+
get allureProject() {
|
|
33
|
+
return process.env.ALLURE_PROJECT_PATH;
|
|
34
|
+
}
|
|
35
|
+
get mrIid() {
|
|
36
|
+
return this.allureMrIid || process.env.CI_MERGE_REQUEST_IID;
|
|
37
|
+
}
|
|
38
|
+
get allureMrIid() {
|
|
39
|
+
return process.env.ALLURE_MERGE_REQUEST_IID;
|
|
40
|
+
}
|
|
41
|
+
get buildName() {
|
|
42
|
+
return process.env[BaseCiInfo.ALLURE_JOB_NAME] || this.jobName;
|
|
43
|
+
}
|
|
44
|
+
get jobName() {
|
|
45
|
+
return process.env.CI_JOB_NAME;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { GithubCiInfo } from '../ci/info/github.js';
|
|
2
|
+
import { GitlabCiInfo } from '../ci/info/gitlab.js';
|
|
3
|
+
export declare abstract class BaseUploader {
|
|
4
|
+
protected copyLatest: boolean;
|
|
5
|
+
protected readonly bucketName: string;
|
|
6
|
+
protected readonly parallel: number;
|
|
7
|
+
protected readonly reportPath: string;
|
|
8
|
+
protected readonly historyPath: string;
|
|
9
|
+
protected readonly plugins: string[];
|
|
10
|
+
protected readonly prefix: string | undefined;
|
|
11
|
+
protected readonly baseUrl: string | undefined;
|
|
12
|
+
private _runId;
|
|
13
|
+
constructor(opts: {
|
|
14
|
+
bucket: string;
|
|
15
|
+
copyLatest: boolean;
|
|
16
|
+
historyPath: string;
|
|
17
|
+
output: string;
|
|
18
|
+
parallel: number;
|
|
19
|
+
plugins: string[];
|
|
20
|
+
baseUrl?: string;
|
|
21
|
+
prefix?: string;
|
|
22
|
+
});
|
|
23
|
+
protected abstract uploadHistory(): Promise<void>;
|
|
24
|
+
protected abstract uploadReport(): Promise<void>;
|
|
25
|
+
protected abstract reportUrlBase(): string;
|
|
26
|
+
protected abstract createLatestCopy(): Promise<void>;
|
|
27
|
+
protected get ciInfo(): GithubCiInfo | GitlabCiInfo | undefined;
|
|
28
|
+
protected get runId(): string | undefined;
|
|
29
|
+
downloadHistory(): Promise<void>;
|
|
30
|
+
upload(): Promise<void>;
|
|
31
|
+
protected getReportFiles(): Promise<{
|
|
32
|
+
plugin: string;
|
|
33
|
+
files: string[];
|
|
34
|
+
}[]>;
|
|
35
|
+
protected historyFileName(): string;
|
|
36
|
+
protected historyUuid(): any;
|
|
37
|
+
protected key(...components: (null | string | undefined)[]): string;
|
|
38
|
+
private getReportUrls;
|
|
39
|
+
private outputReportUrls;
|
|
40
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* eslint-disable unicorn/no-array-for-each */
|
|
2
|
+
import { mkdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { ciInfo } from '../../utils/ci.js';
|
|
5
|
+
import { globPaths } from '../../utils/glob.js';
|
|
6
|
+
import { logger } from '../../utils/logger.js';
|
|
7
|
+
import { spin } from '../../utils/spinner.js';
|
|
8
|
+
export class BaseUploader {
|
|
9
|
+
copyLatest;
|
|
10
|
+
bucketName;
|
|
11
|
+
parallel;
|
|
12
|
+
reportPath;
|
|
13
|
+
historyPath;
|
|
14
|
+
plugins;
|
|
15
|
+
prefix;
|
|
16
|
+
baseUrl;
|
|
17
|
+
_runId;
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
this.bucketName = opts.bucket;
|
|
20
|
+
this.prefix = opts.prefix;
|
|
21
|
+
this.baseUrl = opts.baseUrl;
|
|
22
|
+
this.copyLatest = opts.copyLatest;
|
|
23
|
+
this.parallel = opts.parallel;
|
|
24
|
+
this.reportPath = opts.output;
|
|
25
|
+
this.historyPath = opts.historyPath;
|
|
26
|
+
this.plugins = opts.plugins;
|
|
27
|
+
}
|
|
28
|
+
get ciInfo() {
|
|
29
|
+
return ciInfo;
|
|
30
|
+
}
|
|
31
|
+
get runId() {
|
|
32
|
+
if (this._runId !== undefined)
|
|
33
|
+
return this._runId;
|
|
34
|
+
this._runId = this.ciInfo?.runId || this.historyUuid();
|
|
35
|
+
return this._runId;
|
|
36
|
+
}
|
|
37
|
+
async downloadHistory() {
|
|
38
|
+
const historyDir = path.dirname(this.historyPath);
|
|
39
|
+
logger.debug(`Creating destination directory for history file at ${historyDir}`);
|
|
40
|
+
mkdirSync(historyDir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
async upload() {
|
|
43
|
+
await spin(this.uploadHistory(), 'uploading history file');
|
|
44
|
+
await spin(this.uploadReport(), 'uploading report files');
|
|
45
|
+
if (this.copyLatest)
|
|
46
|
+
await spin(this.createLatestCopy(), 'creating latest report copy');
|
|
47
|
+
this.outputReportUrls();
|
|
48
|
+
}
|
|
49
|
+
async getReportFiles() {
|
|
50
|
+
const globPattern =
|
|
51
|
+
// when only 1 plugin is used, report files are directly under reportPath
|
|
52
|
+
this.plugins.length > 1
|
|
53
|
+
? (plugin) => `${this.reportPath}/${plugin}/**/*`
|
|
54
|
+
: (_plugin) => `${this.reportPath}/**/*`;
|
|
55
|
+
return Promise.all(this.plugins.map(async (plugin) => ({
|
|
56
|
+
plugin,
|
|
57
|
+
files: await globPaths(globPattern(plugin), { nodir: true }),
|
|
58
|
+
})));
|
|
59
|
+
}
|
|
60
|
+
historyFileName() {
|
|
61
|
+
return path.basename(this.historyPath);
|
|
62
|
+
}
|
|
63
|
+
historyUuid() {
|
|
64
|
+
const content = readFileSync(this.historyPath, 'utf8');
|
|
65
|
+
const lines = content
|
|
66
|
+
.trim()
|
|
67
|
+
.split('\n')
|
|
68
|
+
.filter((line) => line.trim() !== '');
|
|
69
|
+
if (lines.length === 0)
|
|
70
|
+
throw new Error(`History file is empty: ${this.historyPath}`);
|
|
71
|
+
const { uuid } = JSON.parse(lines.at(-1));
|
|
72
|
+
return uuid;
|
|
73
|
+
}
|
|
74
|
+
key(...components) {
|
|
75
|
+
return [this.prefix, ...components]
|
|
76
|
+
.filter((c) => typeof c === 'string' && c.length > 0)
|
|
77
|
+
.map((c) => c.replace(/\/$/, ''))
|
|
78
|
+
.join('/');
|
|
79
|
+
}
|
|
80
|
+
getReportUrls() {
|
|
81
|
+
const urls = {
|
|
82
|
+
run: this.plugins.map((plugin) => `${this.reportUrlBase()}/${this.runId}/${plugin}/index.html`),
|
|
83
|
+
latest: this.plugins.map((plugin) => `${this.reportUrlBase()}/latest/${plugin}/index.html`),
|
|
84
|
+
};
|
|
85
|
+
if (this.plugins.length > 1) {
|
|
86
|
+
urls.run.unshift(`${this.reportUrlBase()}/${this.runId}/index.html`);
|
|
87
|
+
urls.latest.unshift(`${this.reportUrlBase()}/latest/index.html`);
|
|
88
|
+
}
|
|
89
|
+
return urls;
|
|
90
|
+
}
|
|
91
|
+
outputReportUrls() {
|
|
92
|
+
logger.section('Report URLs');
|
|
93
|
+
const urls = this.getReportUrls();
|
|
94
|
+
logger.success('Fetching current run urls:');
|
|
95
|
+
urls.run.forEach((url) => logger.info(`- ${url}`));
|
|
96
|
+
if (this.copyLatest) {
|
|
97
|
+
logger.success('Fetching latest report urls:');
|
|
98
|
+
urls.latest.forEach((url) => logger.info(`- ${url}`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BaseUploader } from './base.js';
|
|
2
|
+
export declare class S3Uploader extends BaseUploader {
|
|
3
|
+
private _reportUrlBase;
|
|
4
|
+
private _reportFileUploadArgs;
|
|
5
|
+
private readonly s3Client;
|
|
6
|
+
private get awsEndpoint();
|
|
7
|
+
private get forcePathStyle();
|
|
8
|
+
private get region();
|
|
9
|
+
protected reportUrlBase(): string;
|
|
10
|
+
downloadHistory(): Promise<void>;
|
|
11
|
+
protected uploadHistory(): Promise<void>;
|
|
12
|
+
protected uploadReport(): Promise<void>;
|
|
13
|
+
protected createLatestCopy(): Promise<void>;
|
|
14
|
+
private allUploadArgs;
|
|
15
|
+
private uploadFile;
|
|
16
|
+
private copyFile;
|
|
17
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { CopyObjectCommand, GetObjectCommand, NoSuchKey, PutObjectCommand, S3Client, waitUntilObjectExists, } from '@aws-sdk/client-s3';
|
|
2
|
+
import { lookup } from 'mime-types';
|
|
3
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import pAll from 'p-all';
|
|
5
|
+
import { config } from '../../utils/config.js';
|
|
6
|
+
import { logger } from '../../utils/logger.js';
|
|
7
|
+
import { BaseUploader } from './base.js';
|
|
8
|
+
export class S3Uploader extends BaseUploader {
|
|
9
|
+
_reportUrlBase;
|
|
10
|
+
_reportFileUploadArgs;
|
|
11
|
+
s3Client = new S3Client({
|
|
12
|
+
endpoint: this.awsEndpoint,
|
|
13
|
+
forcePathStyle: this.forcePathStyle,
|
|
14
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
15
|
+
});
|
|
16
|
+
get awsEndpoint() {
|
|
17
|
+
return process.env.AWS_ENDPOINT;
|
|
18
|
+
}
|
|
19
|
+
get forcePathStyle() {
|
|
20
|
+
return process.env.AWS_FORCE_PATH_STYLE === 'true';
|
|
21
|
+
}
|
|
22
|
+
get region() {
|
|
23
|
+
return process.env.AWS_REGION || 'us-east-1';
|
|
24
|
+
}
|
|
25
|
+
reportUrlBase() {
|
|
26
|
+
if (this._reportUrlBase)
|
|
27
|
+
return this._reportUrlBase;
|
|
28
|
+
let base = `https://${this.bucketName}.s3.${this.region}.amazonaws.com`;
|
|
29
|
+
if (this.awsEndpoint) {
|
|
30
|
+
base = this.forcePathStyle ? `${this.awsEndpoint}/${this.bucketName}` : this.awsEndpoint;
|
|
31
|
+
}
|
|
32
|
+
this._reportUrlBase = this.prefix ? `${base}/${this.prefix}` : base;
|
|
33
|
+
return this._reportUrlBase;
|
|
34
|
+
}
|
|
35
|
+
async downloadHistory() {
|
|
36
|
+
await super.downloadHistory();
|
|
37
|
+
const key = this.key(this.historyFileName());
|
|
38
|
+
logger.debug(`Downloading history file from s3://${this.bucketName}/${key}`);
|
|
39
|
+
try {
|
|
40
|
+
const response = await this.s3Client.send(new GetObjectCommand({
|
|
41
|
+
Bucket: this.bucketName,
|
|
42
|
+
Key: key,
|
|
43
|
+
}));
|
|
44
|
+
if (!response.Body) {
|
|
45
|
+
throw new Error('Received empty response body when downloading history file');
|
|
46
|
+
}
|
|
47
|
+
const content = await response.Body.transformToString();
|
|
48
|
+
if (content.trim().length === 0)
|
|
49
|
+
throw new Error('Received empty response body when downloading history file');
|
|
50
|
+
writeFileSync(this.historyPath, content, 'utf8');
|
|
51
|
+
logger.debug(`History file downloaded successfully to ${this.historyPath}`);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw error instanceof NoSuchKey ? new Error(`History file not found!`) : error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async uploadHistory() {
|
|
58
|
+
logger.debug('Uploading history file');
|
|
59
|
+
await this.uploadFile({
|
|
60
|
+
filePath: this.historyPath,
|
|
61
|
+
key: this.key(this.historyFileName()),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async uploadReport() {
|
|
65
|
+
logger.debug(`Uploading report files with concurrency: ${this.parallel}`);
|
|
66
|
+
const uploads = (await this.allUploadArgs()).map(({ filePath, pathComponents }) => {
|
|
67
|
+
const key = this.key(this.runId, ...pathComponents);
|
|
68
|
+
return () => this.uploadFile({ filePath, key });
|
|
69
|
+
});
|
|
70
|
+
await pAll(uploads, { concurrency: config.parallel });
|
|
71
|
+
}
|
|
72
|
+
async createLatestCopy() {
|
|
73
|
+
logger.debug(`Creating latest report copy with concurrency: ${this.parallel}`);
|
|
74
|
+
const copies = (await this.allUploadArgs()).map(({ pathComponents }) => {
|
|
75
|
+
const sourceKey = this.key(this.runId, ...pathComponents);
|
|
76
|
+
const destinationKey = this.key('latest', ...pathComponents);
|
|
77
|
+
return () => this.copyFile({ sourceKey, destinationKey });
|
|
78
|
+
});
|
|
79
|
+
await pAll(copies, { concurrency: config.parallel });
|
|
80
|
+
}
|
|
81
|
+
async allUploadArgs() {
|
|
82
|
+
if (this._reportFileUploadArgs)
|
|
83
|
+
return this._reportFileUploadArgs;
|
|
84
|
+
const reportFiles = await this.getReportFiles();
|
|
85
|
+
this._reportFileUploadArgs = reportFiles.flatMap(({ plugin, files }) => files.map((file) => ({
|
|
86
|
+
filePath: file,
|
|
87
|
+
pathComponents: [plugin, file.replace(`${this.reportPath}/`, '')],
|
|
88
|
+
})));
|
|
89
|
+
return this._reportFileUploadArgs;
|
|
90
|
+
}
|
|
91
|
+
async uploadFile(opts) {
|
|
92
|
+
const content = readFileSync(opts.filePath);
|
|
93
|
+
const contentType = lookup(opts.filePath) || 'application/octet-stream';
|
|
94
|
+
logger.debug(`- uploading file: s3://${this.bucketName}/${opts.key} (type: ${contentType})`);
|
|
95
|
+
return this.s3Client.send(new PutObjectCommand({
|
|
96
|
+
Body: content,
|
|
97
|
+
Bucket: this.bucketName,
|
|
98
|
+
Key: opts.key,
|
|
99
|
+
CacheControl: opts.cacheControl || 'max-age=3600',
|
|
100
|
+
ContentType: contentType,
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
async copyFile(opts) {
|
|
104
|
+
const source = `${this.bucketName}/${opts.sourceKey}`;
|
|
105
|
+
logger.debug(`- copying file: s3://${source} to s3://${this.bucketName}/${opts.destinationKey}`);
|
|
106
|
+
await this.s3Client.send(new CopyObjectCommand({
|
|
107
|
+
CopySource: source,
|
|
108
|
+
Bucket: this.bucketName,
|
|
109
|
+
Key: opts.destinationKey,
|
|
110
|
+
}));
|
|
111
|
+
await waitUntilObjectExists({ client: this.s3Client, maxWaitTime: 60 }, { Bucket: this.bucketName, Key: opts.destinationKey });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for allure-report-publisher
|
|
3
|
+
*/
|
|
4
|
+
export type StorageType = 'gcs' | 'gitlab-artifacts' | 's3';
|
|
5
|
+
export type UpdatePRMode = 'actions' | 'comment' | 'description';
|
|
6
|
+
export type SummaryType = 'behaviors' | 'packages' | 'suites' | 'total';
|
|
7
|
+
export type SummaryTableType = 'ascii' | 'markdown';
|
|
8
|
+
export type PluginName = 'allure2' | 'awesome' | 'classic' | 'csv' | 'dashboard';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Gitlab } from '@gitbeaker/rest';
|
|
2
|
+
import { GithubCiInfo } from '../lib/ci/info/github.js';
|
|
3
|
+
import { GitlabCiInfo } from '../lib/ci/info/gitlab.js';
|
|
4
|
+
export declare const gitlabClient: Gitlab;
|
|
5
|
+
export declare const ciInfo: GithubCiInfo | GitlabCiInfo | undefined;
|
package/dist/utils/ci.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Gitlab } from '@gitbeaker/rest';
|
|
2
|
+
import { GithubCiInfo } from '../lib/ci/info/github.js';
|
|
3
|
+
import { GitlabCiInfo } from '../lib/ci/info/gitlab.js';
|
|
4
|
+
export const gitlabClient = new Gitlab({
|
|
5
|
+
host: process.env.CI_SERVER_URL || 'https://gitlab.com',
|
|
6
|
+
token: process.env.GITLAB_AUTH_TOKEN,
|
|
7
|
+
});
|
|
8
|
+
export const ciInfo = (() => {
|
|
9
|
+
if (process.env.GITLAB_CI)
|
|
10
|
+
return new GitlabCiInfo();
|
|
11
|
+
if (process.env.GITHUB_WORKFLOW)
|
|
12
|
+
return new GithubCiInfo();
|
|
13
|
+
})();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare class Config {
|
|
2
|
+
private _color;
|
|
3
|
+
private _debug;
|
|
4
|
+
private _parallel;
|
|
5
|
+
private initialized;
|
|
6
|
+
constructor();
|
|
7
|
+
get color(): boolean;
|
|
8
|
+
get debug(): boolean;
|
|
9
|
+
get parallel(): number;
|
|
10
|
+
initialize(options: {
|
|
11
|
+
color?: boolean;
|
|
12
|
+
debug?: boolean;
|
|
13
|
+
parallel?: number;
|
|
14
|
+
}): void;
|
|
15
|
+
}
|
|
16
|
+
export declare const config: Config;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
class Config {
|
|
2
|
+
_color;
|
|
3
|
+
_debug;
|
|
4
|
+
_parallel;
|
|
5
|
+
initialized;
|
|
6
|
+
constructor() {
|
|
7
|
+
this._color = true;
|
|
8
|
+
this._debug = false;
|
|
9
|
+
this._parallel = 8;
|
|
10
|
+
this.initialized = false;
|
|
11
|
+
}
|
|
12
|
+
get color() {
|
|
13
|
+
if (!this.initialized)
|
|
14
|
+
console.warn("Config has not been initialized yet, returning default value");
|
|
15
|
+
return this._color;
|
|
16
|
+
}
|
|
17
|
+
get debug() {
|
|
18
|
+
if (!this.initialized)
|
|
19
|
+
console.warn("Config has not been initialized yet, returning default value");
|
|
20
|
+
return this._debug;
|
|
21
|
+
}
|
|
22
|
+
get parallel() {
|
|
23
|
+
if (!this.initialized)
|
|
24
|
+
console.warn("Config has not been initialized yet, returning default value");
|
|
25
|
+
return this._parallel;
|
|
26
|
+
}
|
|
27
|
+
initialize(options) {
|
|
28
|
+
if (this.initialized) {
|
|
29
|
+
throw new Error('Config has already been initialized');
|
|
30
|
+
}
|
|
31
|
+
if (options.color !== undefined) {
|
|
32
|
+
this._color = options.color;
|
|
33
|
+
}
|
|
34
|
+
if (options.debug !== undefined) {
|
|
35
|
+
this._debug = options.debug;
|
|
36
|
+
}
|
|
37
|
+
if (options.parallel !== undefined) {
|
|
38
|
+
this._parallel = options.parallel;
|
|
39
|
+
}
|
|
40
|
+
this.initialized = true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export const config = new Config();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { glob } from 'glob';
|
|
2
|
+
import { statSync } from 'node:fs';
|
|
3
|
+
import { logger } from './logger.js';
|
|
4
|
+
export async function globPaths(pattern, opts = {}) {
|
|
5
|
+
return glob(pattern, {
|
|
6
|
+
absolute: true,
|
|
7
|
+
nodir: opts.nodir ?? false,
|
|
8
|
+
windowsPathsNoEscape: true,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export async function getAllureResultsPaths(pattern, ignoreMissing) {
|
|
12
|
+
const paths = await globPaths(pattern);
|
|
13
|
+
logger.debug(`Glob '${pattern}' found ${paths.length} entries`);
|
|
14
|
+
for (const path of paths) {
|
|
15
|
+
logger.debug(`- ${path}`);
|
|
16
|
+
}
|
|
17
|
+
if (paths.length === 0) {
|
|
18
|
+
const msg = [
|
|
19
|
+
`Pattern '${pattern}' did not match any paths`,
|
|
20
|
+
'Make sure the pattern is correct and points to directories containing allure results',
|
|
21
|
+
];
|
|
22
|
+
if (!ignoreMissing)
|
|
23
|
+
msg.push('Use --ignore-missing-results to exit without error if no result paths are found');
|
|
24
|
+
throw new Error(msg.join('\n'));
|
|
25
|
+
}
|
|
26
|
+
const nonDirectories = paths.filter((path) => !statSync(path).isDirectory());
|
|
27
|
+
if (nonDirectories.length > 0) {
|
|
28
|
+
const msg = [
|
|
29
|
+
`Pattern '${pattern}' matched ${nonDirectories.length} non-directory paths`,
|
|
30
|
+
'All matched paths must be directories containing allure results',
|
|
31
|
+
];
|
|
32
|
+
throw new Error(msg.join('\n'));
|
|
33
|
+
}
|
|
34
|
+
return paths;
|
|
35
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class Logger {
|
|
2
|
+
private _chalk?;
|
|
3
|
+
private debugBuffer;
|
|
4
|
+
private get chalk();
|
|
5
|
+
flushDebug(): void;
|
|
6
|
+
debug(message: string): void;
|
|
7
|
+
error(message: string): void;
|
|
8
|
+
info(message: string): void;
|
|
9
|
+
log(message: string): void;
|
|
10
|
+
success(message: string): void;
|
|
11
|
+
warn(message: string): void;
|
|
12
|
+
section(message: string): void;
|
|
13
|
+
}
|
|
14
|
+
export declare const logger: Logger;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// eslint-disable-next-line unicorn/import-style
|
|
2
|
+
import { Chalk } from 'chalk';
|
|
3
|
+
import { config } from './config.js';
|
|
4
|
+
export class Logger {
|
|
5
|
+
_chalk;
|
|
6
|
+
debugBuffer = [];
|
|
7
|
+
get chalk() {
|
|
8
|
+
if (!this._chalk) {
|
|
9
|
+
this._chalk = new Chalk({ level: config.color ? 3 : 0 });
|
|
10
|
+
}
|
|
11
|
+
return this._chalk;
|
|
12
|
+
}
|
|
13
|
+
flushDebug() {
|
|
14
|
+
if (this.debugBuffer.length === 0)
|
|
15
|
+
return;
|
|
16
|
+
console.log(this.chalk.gray('====== DEBUG LOG OUTPUT ======'));
|
|
17
|
+
for (const message of this.debugBuffer) {
|
|
18
|
+
console.log(this.chalk.gray(message));
|
|
19
|
+
}
|
|
20
|
+
console.log(this.chalk.gray('====== DEBUG LOG OUTPUT ======'));
|
|
21
|
+
this.debugBuffer = [];
|
|
22
|
+
}
|
|
23
|
+
debug(message) {
|
|
24
|
+
const timestamp = new Date().toISOString();
|
|
25
|
+
this.debugBuffer.push(`[${timestamp}] ${message}`);
|
|
26
|
+
}
|
|
27
|
+
error(message) {
|
|
28
|
+
console.error(this.chalk.red(message));
|
|
29
|
+
}
|
|
30
|
+
info(message) {
|
|
31
|
+
console.log(this.chalk.blue('ℹ'), message);
|
|
32
|
+
}
|
|
33
|
+
log(message) {
|
|
34
|
+
console.log(message);
|
|
35
|
+
}
|
|
36
|
+
success(message) {
|
|
37
|
+
console.log(this.chalk.green('✓'), message);
|
|
38
|
+
}
|
|
39
|
+
warn(message) {
|
|
40
|
+
console.warn(this.chalk.yellow(message));
|
|
41
|
+
}
|
|
42
|
+
section(message) {
|
|
43
|
+
if (config.color) {
|
|
44
|
+
console.log(this.chalk.bold.magenta(`\n${message}`));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.log(`\n=== ${message} ===`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export const logger = new Logger();
|