@allurereport/service 3.8.2 → 3.9.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/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/model.d.ts +20 -2
- package/dist/model.js +2 -0
- package/dist/service.d.ts +4 -4
- package/dist/service.js +18 -2
- package/dist/testops.d.ts +37 -0
- package/dist/testops.js +155 -0
- package/dist/utils/files.d.ts +1 -0
- package/dist/utils/files.js +5 -0
- package/dist/utils/http.d.ts +28 -1
- package/dist/utils/http.js +153 -10
- package/dist/utils/token.d.ts +4 -2
- package/dist/utils/token.js +1 -4
- package/package.json +4 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export type * from "./model.js";
|
|
2
2
|
export * from "./service.js";
|
|
3
|
+
export * from "./testops.js";
|
|
3
4
|
export * from "./history.js";
|
|
4
|
-
export { KnownError, UnknownError } from "./utils/http.js";
|
|
5
|
+
export { KnownError, UnknownError, createServiceHttpClient } from "./utils/http.js";
|
|
6
|
+
export { isReportDataFile } from "./utils/files.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export * from "./service.js";
|
|
2
|
+
export * from "./testops.js";
|
|
2
3
|
export * from "./history.js";
|
|
3
|
-
export { KnownError, UnknownError } from "./utils/http.js";
|
|
4
|
+
export { KnownError, UnknownError, createServiceHttpClient } from "./utils/http.js";
|
|
5
|
+
export { isReportDataFile } from "./utils/files.js";
|
package/dist/model.d.ts
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
import type { HistoryDataPoint } from "@allurereport/core-api";
|
|
1
|
+
import type { AllureServiceConfig, HistoryDataPoint } from "@allurereport/core-api";
|
|
2
2
|
export declare const DEFAULT_HISTORY_SERVICE_URL = "https://history.allurereport.org";
|
|
3
3
|
export declare const ALLURE_FILES_DIRNAME: string;
|
|
4
4
|
export declare const ALLURE_LOGIN_EXCHANGE_TOKEN_PATH: string;
|
|
5
5
|
export declare const ALLURE_ACCESS_TOKEN_PATH: string;
|
|
6
|
+
export declare const ALLURE_SERVICE_STORAGE_PREFIX = "ars1.";
|
|
7
|
+
export declare const ALLURE_SERVICE_TESTOPS_PREFIX = "ato1.";
|
|
8
|
+
export type UploadReportConfig = Required<Pick<AllureServiceConfig, "uploadConcurrency" | "uploadMaxAttempts" | "uploadMaxSimultaneousFailures">>;
|
|
9
|
+
export type AllureServiceApiClientConfig = UploadReportConfig & {
|
|
10
|
+
accessToken: string;
|
|
11
|
+
private?: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type UploadReportPayload = {
|
|
14
|
+
reportUuid: string;
|
|
15
|
+
pluginId?: string;
|
|
16
|
+
files: Record<string, string>;
|
|
17
|
+
onProgress?: () => void;
|
|
18
|
+
};
|
|
19
|
+
export type UploadReportResult = {
|
|
20
|
+
indexHref?: string;
|
|
21
|
+
hrefs: Record<string, string>;
|
|
22
|
+
};
|
|
6
23
|
export interface AllureServiceApiClient {
|
|
7
24
|
downloadHistory(payload: {
|
|
8
25
|
repo?: string;
|
|
@@ -17,7 +34,7 @@ export interface AllureServiceApiClient {
|
|
|
17
34
|
}): Promise<URL>;
|
|
18
35
|
completeReport(payload: {
|
|
19
36
|
reportUuid: string;
|
|
20
|
-
historyPoint
|
|
37
|
+
historyPoint?: HistoryDataPoint;
|
|
21
38
|
}): Promise<unknown>;
|
|
22
39
|
deleteReport(payload: {
|
|
23
40
|
reportUuid: string;
|
|
@@ -37,4 +54,5 @@ export interface AllureServiceApiClient {
|
|
|
37
54
|
filepath?: string;
|
|
38
55
|
signal?: AbortSignal;
|
|
39
56
|
}): Promise<string>;
|
|
57
|
+
uploadReport(payload: UploadReportPayload): Promise<UploadReportResult>;
|
|
40
58
|
}
|
package/dist/model.js
CHANGED
|
@@ -4,3 +4,5 @@ export const DEFAULT_HISTORY_SERVICE_URL = "https://history.allurereport.org";
|
|
|
4
4
|
export const ALLURE_FILES_DIRNAME = resolve(homedir(), ".allure");
|
|
5
5
|
export const ALLURE_LOGIN_EXCHANGE_TOKEN_PATH = join(ALLURE_FILES_DIRNAME, "exchange_token");
|
|
6
6
|
export const ALLURE_ACCESS_TOKEN_PATH = join(ALLURE_FILES_DIRNAME, "access_token");
|
|
7
|
+
export const ALLURE_SERVICE_STORAGE_PREFIX = "ars1.";
|
|
8
|
+
export const ALLURE_SERVICE_TESTOPS_PREFIX = "ato1.";
|
package/dist/service.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { type HistoryDataPoint } from "@allurereport/core-api";
|
|
2
|
-
import { type
|
|
3
|
-
import type { AllureServiceApiClient } from "./model.js";
|
|
2
|
+
import { type AllureServiceApiClient, type AllureServiceApiClientConfig, type UploadReportPayload } from "./model.js";
|
|
4
3
|
export declare class AllureServiceClient implements AllureServiceApiClient {
|
|
5
4
|
#private;
|
|
6
|
-
readonly config:
|
|
7
|
-
constructor(config:
|
|
5
|
+
readonly config: AllureServiceApiClientConfig;
|
|
6
|
+
constructor(config: AllureServiceApiClientConfig);
|
|
8
7
|
downloadHistory(payload: {
|
|
9
8
|
repo?: string;
|
|
10
9
|
branch?: string;
|
|
@@ -38,4 +37,5 @@ export declare class AllureServiceClient implements AllureServiceApiClient {
|
|
|
38
37
|
filepath?: string;
|
|
39
38
|
signal?: AbortSignal;
|
|
40
39
|
}): Promise<string>;
|
|
40
|
+
uploadReport(payload: UploadReportPayload): Promise<import("./model.js").UploadReportResult>;
|
|
41
41
|
}
|
package/dist/service.js
CHANGED
|
@@ -12,7 +12,8 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
12
12
|
var _AllureServiceClient_client, _AllureServiceClient_url;
|
|
13
13
|
import { readFile } from "node:fs/promises";
|
|
14
14
|
import { join as joinPosix } from "node:path/posix";
|
|
15
|
-
import {
|
|
15
|
+
import { ALLURE_SERVICE_STORAGE_PREFIX, } from "./model.js";
|
|
16
|
+
import { createServiceHttpClient, uploadReport } from "./utils/http.js";
|
|
16
17
|
import { parseServiceToken } from "./utils/token.js";
|
|
17
18
|
const UPLOAD_CONTENT_TYPE = "application/octet-stream";
|
|
18
19
|
const createUploadBlob = (content) => new Blob([content], { type: UPLOAD_CONTENT_TYPE });
|
|
@@ -26,9 +27,14 @@ export class AllureServiceClient {
|
|
|
26
27
|
if (!config?.accessToken) {
|
|
27
28
|
throw new Error("Allure service access token is required");
|
|
28
29
|
}
|
|
30
|
+
if (!config.accessToken.startsWith(ALLURE_SERVICE_STORAGE_PREFIX)) {
|
|
31
|
+
throw new Error("Allure service access token is invalid");
|
|
32
|
+
}
|
|
29
33
|
const { url } = parseServiceToken(config.accessToken);
|
|
30
34
|
__classPrivateFieldSet(this, _AllureServiceClient_url, url.replace(/\/$/, ""), "f");
|
|
31
|
-
__classPrivateFieldSet(this, _AllureServiceClient_client, createServiceHttpClient(__classPrivateFieldGet(this, _AllureServiceClient_url, "f"),
|
|
35
|
+
__classPrivateFieldSet(this, _AllureServiceClient_client, createServiceHttpClient(__classPrivateFieldGet(this, _AllureServiceClient_url, "f"), {
|
|
36
|
+
accessToken: config.accessToken,
|
|
37
|
+
}), "f");
|
|
32
38
|
}
|
|
33
39
|
async downloadHistory(payload) {
|
|
34
40
|
const { repo, branch, limit } = payload ?? {};
|
|
@@ -115,5 +121,15 @@ export class AllureServiceClient {
|
|
|
115
121
|
});
|
|
116
122
|
return createReportFileUrl(__classPrivateFieldGet(this, _AllureServiceClient_url, "f"), reportUuid, reportFilename);
|
|
117
123
|
}
|
|
124
|
+
async uploadReport(payload) {
|
|
125
|
+
return uploadReport({
|
|
126
|
+
...payload,
|
|
127
|
+
uploadConcurrency: this.config.uploadConcurrency,
|
|
128
|
+
uploadMaxAttempts: this.config.uploadMaxAttempts,
|
|
129
|
+
uploadMaxSimultaneousFailures: this.config.uploadMaxSimultaneousFailures,
|
|
130
|
+
addReportAsset: this.addReportAsset.bind(this),
|
|
131
|
+
addReportFile: this.addReportFile.bind(this),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
118
134
|
}
|
|
119
135
|
_AllureServiceClient_client = new WeakMap(), _AllureServiceClient_url = new WeakMap();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type HistoryDataPoint } from "@allurereport/core-api";
|
|
2
|
+
import { type AllureServiceApiClient, type AllureServiceApiClientConfig, type UploadReportPayload } from "./model.js";
|
|
3
|
+
export declare class AllureTestOpsClient implements AllureServiceApiClient {
|
|
4
|
+
#private;
|
|
5
|
+
readonly config: AllureServiceApiClientConfig;
|
|
6
|
+
constructor(config: AllureServiceApiClientConfig);
|
|
7
|
+
downloadHistory(): Promise<HistoryDataPoint[]>;
|
|
8
|
+
createReport(payload: {
|
|
9
|
+
reportName: string;
|
|
10
|
+
reportUuid?: string;
|
|
11
|
+
repo?: string;
|
|
12
|
+
branch?: string;
|
|
13
|
+
}): Promise<import("url").URL>;
|
|
14
|
+
completeReport(payload: {
|
|
15
|
+
reportUuid: string;
|
|
16
|
+
historyPoint?: HistoryDataPoint;
|
|
17
|
+
}): Promise<unknown>;
|
|
18
|
+
deleteReport(payload: {
|
|
19
|
+
reportUuid: string;
|
|
20
|
+
pluginId?: string;
|
|
21
|
+
}): Promise<unknown>;
|
|
22
|
+
addReportAsset(payload: {
|
|
23
|
+
filename: string;
|
|
24
|
+
file?: Buffer;
|
|
25
|
+
filepath?: string;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
}): Promise<unknown>;
|
|
28
|
+
addReportFile(payload: {
|
|
29
|
+
reportUuid: string;
|
|
30
|
+
pluginId?: string;
|
|
31
|
+
filename: string;
|
|
32
|
+
file?: Buffer;
|
|
33
|
+
filepath?: string;
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
}): Promise<string>;
|
|
36
|
+
uploadReport(payload: UploadReportPayload): Promise<import("./model.js").UploadReportResult>;
|
|
37
|
+
}
|
package/dist/testops.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _AllureTestOpsClient_instances, _AllureTestOpsClient_url, _AllureTestOpsClient_accessToken, _AllureTestOpsClient_projectId, _AllureTestOpsClient_client, _AllureTestOpsClient_authorizedClient;
|
|
13
|
+
import { readFile } from "node:fs/promises";
|
|
14
|
+
import { extname, join as joinPosix } from "node:path/posix";
|
|
15
|
+
import { ALLURE_SERVICE_TESTOPS_PREFIX, } from "./model.js";
|
|
16
|
+
import { createServiceHttpClient, uploadReport } from "./utils/http.js";
|
|
17
|
+
import { parseServiceToken } from "./utils/token.js";
|
|
18
|
+
const DEFAULT_UPLOAD_CONTENT_TYPE = "application/octet-stream";
|
|
19
|
+
const CONTENT_TYPES = {
|
|
20
|
+
".css": "text/css",
|
|
21
|
+
".gif": "image/gif",
|
|
22
|
+
".html": "text/html",
|
|
23
|
+
".jpeg": "image/jpeg",
|
|
24
|
+
".jpg": "image/jpeg",
|
|
25
|
+
".js": "application/javascript",
|
|
26
|
+
".json": "application/json",
|
|
27
|
+
".map": "application/json",
|
|
28
|
+
".mjs": "application/javascript",
|
|
29
|
+
".otf": "font/otf",
|
|
30
|
+
".png": "image/png",
|
|
31
|
+
".svg": "image/svg+xml",
|
|
32
|
+
".ttf": "font/ttf",
|
|
33
|
+
".webp": "image/webp",
|
|
34
|
+
".woff": "font/woff",
|
|
35
|
+
".woff2": "font/woff2",
|
|
36
|
+
};
|
|
37
|
+
const contentTypeByFilename = (filename) => CONTENT_TYPES[extname(filename)] ?? DEFAULT_UPLOAD_CONTENT_TYPE;
|
|
38
|
+
const createUploadBlob = (content, filename) => new Blob([content], { type: contentTypeByFilename(filename) });
|
|
39
|
+
const createReportFileUrl = (baseUrl, reportUuid, reportFilename) => `${baseUrl}/api/test-report/view/${joinPosix(reportUuid, reportFilename)}`;
|
|
40
|
+
export class AllureTestOpsClient {
|
|
41
|
+
constructor(config) {
|
|
42
|
+
_AllureTestOpsClient_instances.add(this);
|
|
43
|
+
this.config = config;
|
|
44
|
+
_AllureTestOpsClient_url.set(this, void 0);
|
|
45
|
+
_AllureTestOpsClient_accessToken.set(this, void 0);
|
|
46
|
+
_AllureTestOpsClient_projectId.set(this, void 0);
|
|
47
|
+
_AllureTestOpsClient_client.set(this, void 0);
|
|
48
|
+
if (!config?.accessToken) {
|
|
49
|
+
throw new Error("Allure TestOps access token is required");
|
|
50
|
+
}
|
|
51
|
+
if (!config.accessToken.startsWith(ALLURE_SERVICE_TESTOPS_PREFIX)) {
|
|
52
|
+
throw new Error("Allure service access token is invalid");
|
|
53
|
+
}
|
|
54
|
+
const accessTokenPayload = parseServiceToken(config.accessToken);
|
|
55
|
+
const projectId = Number(accessTokenPayload.projectId);
|
|
56
|
+
if (!Number.isFinite(projectId)) {
|
|
57
|
+
throw new Error("Given access token doesn't contain project id");
|
|
58
|
+
}
|
|
59
|
+
if (!accessTokenPayload.url) {
|
|
60
|
+
throw new Error("Given access token doesn't contain url");
|
|
61
|
+
}
|
|
62
|
+
__classPrivateFieldSet(this, _AllureTestOpsClient_accessToken, accessTokenPayload.accessToken, "f");
|
|
63
|
+
__classPrivateFieldSet(this, _AllureTestOpsClient_projectId, projectId, "f");
|
|
64
|
+
__classPrivateFieldSet(this, _AllureTestOpsClient_url, accessTokenPayload.url.replace(/\/$/, ""), "f");
|
|
65
|
+
}
|
|
66
|
+
async downloadHistory() {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
async createReport(payload) {
|
|
70
|
+
const client = await __classPrivateFieldGet(this, _AllureTestOpsClient_instances, "m", _AllureTestOpsClient_authorizedClient).call(this);
|
|
71
|
+
const { reportName, reportUuid } = payload;
|
|
72
|
+
const { url } = await client.post("/api/test-report", {
|
|
73
|
+
body: {
|
|
74
|
+
projectId: __classPrivateFieldGet(this, _AllureTestOpsClient_projectId, "f"),
|
|
75
|
+
isPublic: !this.config?.private,
|
|
76
|
+
reportName,
|
|
77
|
+
reportUuid,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
return new URL(url, __classPrivateFieldGet(this, _AllureTestOpsClient_url, "f"));
|
|
81
|
+
}
|
|
82
|
+
async completeReport(payload) {
|
|
83
|
+
const client = await __classPrivateFieldGet(this, _AllureTestOpsClient_instances, "m", _AllureTestOpsClient_authorizedClient).call(this);
|
|
84
|
+
const { reportUuid } = payload;
|
|
85
|
+
return client.post(`/api/test-report/${reportUuid}/complete`);
|
|
86
|
+
}
|
|
87
|
+
async deleteReport(payload) {
|
|
88
|
+
const client = await __classPrivateFieldGet(this, _AllureTestOpsClient_instances, "m", _AllureTestOpsClient_authorizedClient).call(this);
|
|
89
|
+
const { reportUuid } = payload;
|
|
90
|
+
return client.delete(`/api/test-report/${reportUuid}`);
|
|
91
|
+
}
|
|
92
|
+
async addReportAsset(payload) {
|
|
93
|
+
const { filename, file, filepath, signal } = payload;
|
|
94
|
+
if (!file && !filepath) {
|
|
95
|
+
throw new Error("File or filepath is required");
|
|
96
|
+
}
|
|
97
|
+
let content = file;
|
|
98
|
+
if (!content) {
|
|
99
|
+
content = signal ? await readFile(filepath, { signal }) : await readFile(filepath);
|
|
100
|
+
}
|
|
101
|
+
const form = new FormData();
|
|
102
|
+
form.set("filename", filename);
|
|
103
|
+
form.set("file", createUploadBlob(content, filename), filename);
|
|
104
|
+
const client = await __classPrivateFieldGet(this, _AllureTestOpsClient_instances, "m", _AllureTestOpsClient_authorizedClient).call(this);
|
|
105
|
+
return client.post("/api/test-report/upload", {
|
|
106
|
+
body: form,
|
|
107
|
+
headers: {
|
|
108
|
+
"Content-Type": "multipart/form-data",
|
|
109
|
+
},
|
|
110
|
+
...(signal ? { signal } : {}),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async addReportFile(payload) {
|
|
114
|
+
const { reportUuid, filename, file, filepath, pluginId, signal } = payload;
|
|
115
|
+
const reportFilename = pluginId ? joinPosix(pluginId, filename) : filename;
|
|
116
|
+
if (!file && !filepath) {
|
|
117
|
+
throw new Error("File or filepath is required");
|
|
118
|
+
}
|
|
119
|
+
let content = file;
|
|
120
|
+
if (!content) {
|
|
121
|
+
content = signal ? await readFile(filepath, { signal }) : await readFile(filepath);
|
|
122
|
+
}
|
|
123
|
+
const form = new FormData();
|
|
124
|
+
form.set("filename", reportFilename);
|
|
125
|
+
form.set("file", createUploadBlob(content, reportFilename), reportFilename);
|
|
126
|
+
const client = await __classPrivateFieldGet(this, _AllureTestOpsClient_instances, "m", _AllureTestOpsClient_authorizedClient).call(this);
|
|
127
|
+
await client.post(`/api/test-report/${reportUuid}/upload`, {
|
|
128
|
+
body: form,
|
|
129
|
+
headers: {
|
|
130
|
+
"Content-Type": "multipart/form-data",
|
|
131
|
+
},
|
|
132
|
+
...(signal ? { signal } : {}),
|
|
133
|
+
});
|
|
134
|
+
return createReportFileUrl(__classPrivateFieldGet(this, _AllureTestOpsClient_url, "f"), reportUuid, reportFilename);
|
|
135
|
+
}
|
|
136
|
+
async uploadReport(payload) {
|
|
137
|
+
return uploadReport({
|
|
138
|
+
...payload,
|
|
139
|
+
uploadConcurrency: this.config.uploadConcurrency,
|
|
140
|
+
uploadMaxAttempts: this.config.uploadMaxAttempts,
|
|
141
|
+
uploadMaxSimultaneousFailures: this.config.uploadMaxSimultaneousFailures,
|
|
142
|
+
addReportAsset: this.addReportAsset.bind(this),
|
|
143
|
+
addReportFile: this.addReportFile.bind(this),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
_AllureTestOpsClient_url = new WeakMap(), _AllureTestOpsClient_accessToken = new WeakMap(), _AllureTestOpsClient_projectId = new WeakMap(), _AllureTestOpsClient_client = new WeakMap(), _AllureTestOpsClient_instances = new WeakSet(), _AllureTestOpsClient_authorizedClient = async function _AllureTestOpsClient_authorizedClient() {
|
|
148
|
+
if (__classPrivateFieldGet(this, _AllureTestOpsClient_client, "f")) {
|
|
149
|
+
return __classPrivateFieldGet(this, _AllureTestOpsClient_client, "f");
|
|
150
|
+
}
|
|
151
|
+
__classPrivateFieldSet(this, _AllureTestOpsClient_client, createServiceHttpClient(__classPrivateFieldGet(this, _AllureTestOpsClient_url, "f"), {
|
|
152
|
+
apiToken: __classPrivateFieldGet(this, _AllureTestOpsClient_accessToken, "f"),
|
|
153
|
+
}), "f");
|
|
154
|
+
return __classPrivateFieldGet(this, _AllureTestOpsClient_client, "f");
|
|
155
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const isReportDataFile: (filename: string) => boolean;
|
package/dist/utils/http.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AxiosRequestConfig } from "axios";
|
|
2
|
+
import type { UploadReportConfig, UploadReportPayload, UploadReportResult } from "../model.js";
|
|
2
3
|
export declare class KnownError extends Error {
|
|
3
4
|
status?: number;
|
|
4
5
|
constructor(message: string, status?: number);
|
|
@@ -7,7 +8,19 @@ export declare class UnknownError extends Error {
|
|
|
7
8
|
stack?: string;
|
|
8
9
|
constructor(message: string, stack?: string);
|
|
9
10
|
}
|
|
10
|
-
export declare const
|
|
11
|
+
export declare const formatResponseErrorData: (data: unknown) => string | undefined;
|
|
12
|
+
export declare const formatServiceHttpErrorMessage: (payload: {
|
|
13
|
+
method: string;
|
|
14
|
+
endpoint: string;
|
|
15
|
+
status?: number;
|
|
16
|
+
statusText?: string;
|
|
17
|
+
data?: unknown;
|
|
18
|
+
fallbackMessage?: string;
|
|
19
|
+
}) => string;
|
|
20
|
+
export declare const createServiceHttpClient: (serviceUrl: string, params?: {
|
|
21
|
+
accessToken?: string;
|
|
22
|
+
apiToken?: string;
|
|
23
|
+
}) => {
|
|
11
24
|
get: <T>(endpoint: string, payload?: AxiosRequestConfig & {
|
|
12
25
|
params?: Record<string, any>;
|
|
13
26
|
body?: any;
|
|
@@ -26,3 +39,17 @@ export declare const createServiceHttpClient: (serviceUrl: string, accessToken:
|
|
|
26
39
|
}) => Promise<T>;
|
|
27
40
|
};
|
|
28
41
|
export type HttpClient = ReturnType<typeof createServiceHttpClient>;
|
|
42
|
+
export declare const uploadReport: (payload: UploadReportPayload & UploadReportConfig & {
|
|
43
|
+
addReportAsset: (payload: {
|
|
44
|
+
filename: string;
|
|
45
|
+
filepath: string;
|
|
46
|
+
signal?: AbortSignal;
|
|
47
|
+
}) => Promise<unknown>;
|
|
48
|
+
addReportFile: (payload: {
|
|
49
|
+
reportUuid: string;
|
|
50
|
+
pluginId?: string;
|
|
51
|
+
filename: string;
|
|
52
|
+
filepath: string;
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
}) => Promise<string>;
|
|
55
|
+
}) => Promise<UploadReportResult>;
|
package/dist/utils/http.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import axios, { isAxiosError } from "axios";
|
|
2
|
+
import { isReportDataFile } from "./files.js";
|
|
2
3
|
export class KnownError extends Error {
|
|
3
4
|
constructor(message, status) {
|
|
4
5
|
super(message);
|
|
@@ -13,27 +14,74 @@ export class UnknownError extends Error {
|
|
|
13
14
|
this.stack = stack;
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
+
const ERROR_MESSAGE_FIELDS = ["message", "error_description", "error", "detail", "title", "description"];
|
|
18
|
+
const stringifyErrorObject = (value) => {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.stringify(value);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
const entries = Object.entries(value).map(([key, entryValue]) => `${key}=${String(entryValue)}`);
|
|
24
|
+
return entries.length > 0 ? `{ ${entries.join(", ")} }` : undefined;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
export const formatResponseErrorData = (data) => {
|
|
28
|
+
if (data === undefined || data === null || data === "") {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
if (typeof data === "string") {
|
|
32
|
+
return data;
|
|
33
|
+
}
|
|
34
|
+
if (typeof data === "number" || typeof data === "boolean" || typeof data === "bigint") {
|
|
35
|
+
return String(data);
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(data)) {
|
|
38
|
+
const items = data.map(formatResponseErrorData).filter(Boolean);
|
|
39
|
+
return items.length > 0 ? items.join("; ") : undefined;
|
|
40
|
+
}
|
|
41
|
+
if (typeof data !== "object") {
|
|
42
|
+
return String(data);
|
|
43
|
+
}
|
|
44
|
+
const errorData = data;
|
|
45
|
+
for (const field of ERROR_MESSAGE_FIELDS) {
|
|
46
|
+
const message = formatResponseErrorData(errorData[field]);
|
|
47
|
+
if (message) {
|
|
48
|
+
return message;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return stringifyErrorObject(errorData);
|
|
52
|
+
};
|
|
53
|
+
export const formatServiceHttpErrorMessage = (payload) => {
|
|
54
|
+
const { method, endpoint, status, statusText, data, fallbackMessage } = payload;
|
|
55
|
+
const request = `${method.toUpperCase()} ${endpoint}`;
|
|
56
|
+
const statusMessage = status ? ` responded with ${status}${statusText ? ` ${statusText}` : ""}` : " failed";
|
|
57
|
+
const details = formatResponseErrorData(data) || fallbackMessage;
|
|
58
|
+
return `Allure service request failed: ${request}${statusMessage}${details ? `: ${details}` : ""}`;
|
|
59
|
+
};
|
|
60
|
+
export const createServiceHttpClient = (serviceUrl, params) => {
|
|
17
61
|
const client = axios.create({
|
|
18
62
|
baseURL: serviceUrl,
|
|
19
|
-
withCredentials: true,
|
|
20
63
|
validateStatus: (status) => status < 400,
|
|
21
64
|
});
|
|
22
65
|
const sendRequest = (method) => async (endpoint, payload) => {
|
|
23
66
|
const headers = {
|
|
24
67
|
...(payload?.headers ?? {}),
|
|
25
|
-
Authorization: `Bearer ${accessToken}`,
|
|
26
68
|
};
|
|
69
|
+
if (params?.accessToken) {
|
|
70
|
+
headers.Authorization = `Bearer ${params.accessToken}`;
|
|
71
|
+
}
|
|
72
|
+
if (params?.apiToken) {
|
|
73
|
+
headers.Authorization = `api-token ${params.apiToken}`;
|
|
74
|
+
}
|
|
27
75
|
try {
|
|
28
76
|
let res;
|
|
29
|
-
if (
|
|
30
|
-
res = await client[method](endpoint,
|
|
77
|
+
if (method === "get" || method === "delete") {
|
|
78
|
+
res = await client[method](endpoint, {
|
|
31
79
|
...payload,
|
|
32
80
|
headers,
|
|
33
81
|
});
|
|
34
82
|
}
|
|
35
83
|
else {
|
|
36
|
-
res = await client[method](endpoint, {
|
|
84
|
+
res = await client[method](endpoint, payload?.body, {
|
|
37
85
|
...payload,
|
|
38
86
|
headers,
|
|
39
87
|
});
|
|
@@ -45,10 +93,18 @@ export const createServiceHttpClient = (serviceUrl, accessToken) => {
|
|
|
45
93
|
if (!axiosError) {
|
|
46
94
|
throw err;
|
|
47
95
|
}
|
|
48
|
-
const { status
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
96
|
+
const { data, status, statusText } = err.response ?? {};
|
|
97
|
+
const responseStatus = status ?? 500;
|
|
98
|
+
const errorMessage = formatServiceHttpErrorMessage({
|
|
99
|
+
method,
|
|
100
|
+
endpoint,
|
|
101
|
+
status,
|
|
102
|
+
statusText,
|
|
103
|
+
data,
|
|
104
|
+
fallbackMessage: err.message,
|
|
105
|
+
});
|
|
106
|
+
if (responseStatus < 500) {
|
|
107
|
+
throw new KnownError(errorMessage, responseStatus);
|
|
52
108
|
}
|
|
53
109
|
throw new UnknownError(errorMessage, err.stack);
|
|
54
110
|
}
|
|
@@ -60,3 +116,90 @@ export const createServiceHttpClient = (serviceUrl, accessToken) => {
|
|
|
60
116
|
delete: sendRequest("delete"),
|
|
61
117
|
};
|
|
62
118
|
};
|
|
119
|
+
const uploadWithRetry = async (filename, uploadAbortController, failedUploads, maxAttempts, maxSimultaneousFailures, uploadFn) => {
|
|
120
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
121
|
+
if (uploadAbortController.signal.aborted) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
await uploadFn();
|
|
126
|
+
failedUploads.delete(filename);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (uploadAbortController.signal.aborted) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
failedUploads.add(filename);
|
|
134
|
+
if (failedUploads.size > maxSimultaneousFailures || attempt >= maxAttempts) {
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
};
|
|
141
|
+
export const uploadReport = async (payload) => {
|
|
142
|
+
const { reportUuid, pluginId, files, onProgress, addReportAsset, addReportFile, uploadConcurrency, uploadMaxAttempts, uploadMaxSimultaneousFailures, } = payload;
|
|
143
|
+
const fileEntries = Object.entries(files);
|
|
144
|
+
if (fileEntries.length === 0) {
|
|
145
|
+
return {
|
|
146
|
+
hrefs: {},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const uploadAbortController = new AbortController();
|
|
150
|
+
const failedUploads = new Set();
|
|
151
|
+
const hrefs = {};
|
|
152
|
+
let indexHref;
|
|
153
|
+
let nextFileIndex = 0;
|
|
154
|
+
const uploadNext = async () => {
|
|
155
|
+
while (!uploadAbortController.signal.aborted) {
|
|
156
|
+
const fileIndex = nextFileIndex++;
|
|
157
|
+
if (fileIndex >= fileEntries.length) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const [filename, filepath] = fileEntries[fileIndex];
|
|
161
|
+
let fileUrl;
|
|
162
|
+
const uploaded = await uploadWithRetry(filename, uploadAbortController, failedUploads, uploadMaxAttempts, uploadMaxSimultaneousFailures, async () => {
|
|
163
|
+
if (isReportDataFile(filename)) {
|
|
164
|
+
fileUrl = await addReportFile({
|
|
165
|
+
reportUuid,
|
|
166
|
+
pluginId,
|
|
167
|
+
filename,
|
|
168
|
+
filepath,
|
|
169
|
+
signal: uploadAbortController.signal,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
await addReportAsset({
|
|
174
|
+
filename,
|
|
175
|
+
filepath,
|
|
176
|
+
signal: uploadAbortController.signal,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
if (!uploaded || uploadAbortController.signal.aborted) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (fileUrl) {
|
|
184
|
+
hrefs[filename] = fileUrl;
|
|
185
|
+
if (filename === "index.html") {
|
|
186
|
+
indexHref = fileUrl;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
onProgress?.();
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const uploadTasks = Array.from({ length: Math.min(uploadConcurrency, fileEntries.length) }, () => uploadNext());
|
|
193
|
+
try {
|
|
194
|
+
await Promise.all(uploadTasks);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
uploadAbortController.abort();
|
|
198
|
+
await Promise.allSettled(uploadTasks);
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
indexHref,
|
|
203
|
+
hrefs,
|
|
204
|
+
};
|
|
205
|
+
};
|
package/dist/utils/token.d.ts
CHANGED
package/dist/utils/token.js
CHANGED
|
@@ -11,10 +11,7 @@ export const parseServiceToken = (token) => {
|
|
|
11
11
|
if (!payload.url) {
|
|
12
12
|
throw new Error("missing url");
|
|
13
13
|
}
|
|
14
|
-
return
|
|
15
|
-
accessToken: payload.accessToken,
|
|
16
|
-
url: payload.url,
|
|
17
|
-
};
|
|
14
|
+
return payload;
|
|
18
15
|
}
|
|
19
16
|
catch {
|
|
20
17
|
throw new Error("Allure service access token is invalid");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allurereport/service",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0",
|
|
4
4
|
"description": "Allure Service API",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"allure",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"lint:fix": "oxlint --import-plugin --fix src test features stories"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@allurereport/core-api": "3.
|
|
34
|
-
"@allurereport/plugin-api": "3.
|
|
33
|
+
"@allurereport/core-api": "3.9.0",
|
|
34
|
+
"@allurereport/plugin-api": "3.9.0",
|
|
35
35
|
"axios": "^1.15.2",
|
|
36
36
|
"open": "^10.1.0"
|
|
37
37
|
},
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@types/node": "^20.17.9",
|
|
40
40
|
"@vitest/runner": "^2.1.8",
|
|
41
41
|
"@vitest/snapshot": "^2.1.8",
|
|
42
|
+
"allure-js-commons": "^3.3.3",
|
|
42
43
|
"allure-vitest": "^3.3.3",
|
|
43
44
|
"rimraf": "^6.0.1",
|
|
44
45
|
"typescript": "^5.6.3",
|