@allurereport/service 3.14.1 → 3.14.3
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/model.d.ts +1 -0
- package/dist/service.js +5 -34
- package/dist/testops.d.ts +22 -0
- package/dist/testops.js +43 -23
- package/dist/utils/http.js +39 -2
- package/dist/utils/upload.d.ts +10 -0
- package/dist/utils/upload.js +27 -0
- package/package.json +3 -3
package/dist/model.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export type UploadReportPayload = {
|
|
|
15
15
|
pluginId?: string;
|
|
16
16
|
files: Record<string, string> | Record<string, string>[];
|
|
17
17
|
onProgress?: (uploadedFilesDelta?: number) => void;
|
|
18
|
+
uploadBatchMaxBytes?: number;
|
|
18
19
|
};
|
|
19
20
|
export type UploadReportFilePayload = {
|
|
20
21
|
filename: string;
|
package/dist/service.js
CHANGED
|
@@ -10,25 +10,15 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
12
|
var _AllureServiceClient_client, _AllureServiceClient_url;
|
|
13
|
-
import { readFile } from "node:fs/promises";
|
|
14
13
|
import { join as joinPosix } from "node:path/posix";
|
|
15
14
|
import { ALLURE_SERVICE_STORAGE_PREFIX, } from "./model.js";
|
|
16
15
|
import { createServiceHttpClient, uploadReport } from "./utils/http.js";
|
|
17
16
|
import { parseServiceToken } from "./utils/token.js";
|
|
17
|
+
import { createUploadForm } from "./utils/upload.js";
|
|
18
18
|
const UPLOAD_CONTENT_TYPE = "application/octet-stream";
|
|
19
19
|
const createUploadBlob = (content) => new Blob([content], { type: UPLOAD_CONTENT_TYPE });
|
|
20
20
|
const createReportUrl = (baseUrl, reportUuid) => `${baseUrl}/${reportUuid}`;
|
|
21
21
|
const createReportFileUrl = (baseUrl, reportUuid, reportFilename) => `${baseUrl}/${joinPosix(reportUuid, reportFilename)}`;
|
|
22
|
-
const readUploadContent = async (payload) => {
|
|
23
|
-
const { file, filepath, signal } = payload;
|
|
24
|
-
if (file) {
|
|
25
|
-
return file;
|
|
26
|
-
}
|
|
27
|
-
if (!filepath) {
|
|
28
|
-
throw new Error("File or filepath is required");
|
|
29
|
-
}
|
|
30
|
-
return signal ? readFile(filepath, { signal }) : readFile(filepath);
|
|
31
|
-
};
|
|
32
22
|
export class AllureServiceClient {
|
|
33
23
|
constructor(config) {
|
|
34
24
|
this.config = config;
|
|
@@ -94,15 +84,7 @@ export class AllureServiceClient {
|
|
|
94
84
|
if (files.length === 0) {
|
|
95
85
|
return undefined;
|
|
96
86
|
}
|
|
97
|
-
const
|
|
98
|
-
filename,
|
|
99
|
-
content: await readUploadContent({ filename, ...filePayload, signal: signal ?? filePayload.signal }),
|
|
100
|
-
})));
|
|
101
|
-
const form = new FormData();
|
|
102
|
-
for (const { filename, content } of entries) {
|
|
103
|
-
form.append("filename", filename);
|
|
104
|
-
form.append("file", createUploadBlob(content), filename);
|
|
105
|
-
}
|
|
87
|
+
const { form } = await createUploadForm(files, createUploadBlob, signal);
|
|
106
88
|
return __classPrivateFieldGet(this, _AllureServiceClient_client, "f").post("/api/assets/upload", {
|
|
107
89
|
body: form,
|
|
108
90
|
headers: {
|
|
@@ -119,19 +101,7 @@ export class AllureServiceClient {
|
|
|
119
101
|
if (files.length === 0) {
|
|
120
102
|
return {};
|
|
121
103
|
}
|
|
122
|
-
const entries = await
|
|
123
|
-
const reportFilename = pluginId ? joinPosix(pluginId, filename) : filename;
|
|
124
|
-
return {
|
|
125
|
-
filename,
|
|
126
|
-
reportFilename,
|
|
127
|
-
content: await readUploadContent({ filename, ...filePayload, signal: signal ?? filePayload.signal }),
|
|
128
|
-
};
|
|
129
|
-
}));
|
|
130
|
-
const form = new FormData();
|
|
131
|
-
for (const { reportFilename, content } of entries) {
|
|
132
|
-
form.append("filename", reportFilename);
|
|
133
|
-
form.append("file", createUploadBlob(content), reportFilename);
|
|
134
|
-
}
|
|
104
|
+
const { entries, form } = await createUploadForm(files, createUploadBlob, signal, (filename) => pluginId ? joinPosix(pluginId, filename) : filename);
|
|
135
105
|
await __classPrivateFieldGet(this, _AllureServiceClient_client, "f").post(`/api/reports/${reportUuid}/upload`, {
|
|
136
106
|
body: form,
|
|
137
107
|
headers: {
|
|
@@ -154,8 +124,9 @@ export class AllureServiceClient {
|
|
|
154
124
|
return result[payload.filename];
|
|
155
125
|
}
|
|
156
126
|
async uploadReport(payload) {
|
|
127
|
+
const { uploadBatchMaxBytes: _uploadBatchMaxBytes, ...uploadPayload } = payload;
|
|
157
128
|
return uploadReport({
|
|
158
|
-
...
|
|
129
|
+
...uploadPayload,
|
|
159
130
|
uploadConcurrency: this.config.uploadConcurrency,
|
|
160
131
|
uploadMaxAttempts: this.config.uploadMaxAttempts,
|
|
161
132
|
uploadMaxSimultaneousFailures: this.config.uploadMaxSimultaneousFailures,
|
package/dist/testops.d.ts
CHANGED
|
@@ -25,6 +25,15 @@ export declare class AllureTestOpsClient implements AllureServiceApiClient {
|
|
|
25
25
|
filepath?: string;
|
|
26
26
|
signal?: AbortSignal;
|
|
27
27
|
}): Promise<unknown>;
|
|
28
|
+
addReportAssets(payload: {
|
|
29
|
+
files: {
|
|
30
|
+
filename: string;
|
|
31
|
+
file?: Buffer;
|
|
32
|
+
filepath?: string;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
}[];
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}): Promise<unknown>;
|
|
28
37
|
addReportFile(payload: {
|
|
29
38
|
reportUuid: string;
|
|
30
39
|
pluginId?: string;
|
|
@@ -33,5 +42,18 @@ export declare class AllureTestOpsClient implements AllureServiceApiClient {
|
|
|
33
42
|
filepath?: string;
|
|
34
43
|
signal?: AbortSignal;
|
|
35
44
|
}): Promise<string>;
|
|
45
|
+
addReportFiles(payload: {
|
|
46
|
+
reportUuid: string;
|
|
47
|
+
pluginId?: string;
|
|
48
|
+
files: {
|
|
49
|
+
filename: string;
|
|
50
|
+
file?: Buffer;
|
|
51
|
+
filepath?: string;
|
|
52
|
+
signal?: AbortSignal;
|
|
53
|
+
}[];
|
|
54
|
+
signal?: AbortSignal;
|
|
55
|
+
}): Promise<{
|
|
56
|
+
[k: string]: string;
|
|
57
|
+
}>;
|
|
36
58
|
uploadReport(payload: UploadReportPayload): Promise<import("./model.js").UploadReportResult>;
|
|
37
59
|
}
|
package/dist/testops.js
CHANGED
|
@@ -10,12 +10,13 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
12
|
var _AllureTestOpsClient_instances, _AllureTestOpsClient_url, _AllureTestOpsClient_accessToken, _AllureTestOpsClient_projectId, _AllureTestOpsClient_client, _AllureTestOpsClient_authorizedClient;
|
|
13
|
-
import { readFile } from "node:fs/promises";
|
|
14
13
|
import { extname, join as joinPosix } from "node:path/posix";
|
|
15
14
|
import { ALLURE_SERVICE_TESTOPS_PREFIX, } from "./model.js";
|
|
16
15
|
import { createServiceHttpClient, uploadReport } from "./utils/http.js";
|
|
17
16
|
import { parseServiceToken } from "./utils/token.js";
|
|
17
|
+
import { createUploadForm } from "./utils/upload.js";
|
|
18
18
|
const DEFAULT_UPLOAD_CONTENT_TYPE = "application/octet-stream";
|
|
19
|
+
const UPLOAD_BATCH_MAX_BYTES = 8 * 1024 * 1024;
|
|
19
20
|
const CONTENT_TYPES = {
|
|
20
21
|
".css": "text/css",
|
|
21
22
|
".gif": "image/gif",
|
|
@@ -90,17 +91,8 @@ export class AllureTestOpsClient {
|
|
|
90
91
|
return client.delete(`/api/test-report/${reportUuid}`);
|
|
91
92
|
}
|
|
92
93
|
async addReportAsset(payload) {
|
|
93
|
-
const {
|
|
94
|
-
|
|
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);
|
|
94
|
+
const { file, filepath, filename, signal } = payload;
|
|
95
|
+
const { form } = await createUploadForm([{ filename, file, filepath, signal }], createUploadBlob, signal);
|
|
104
96
|
const client = await __classPrivateFieldGet(this, _AllureTestOpsClient_instances, "m", _AllureTestOpsClient_authorizedClient).call(this);
|
|
105
97
|
return client.post("/api/test-report/upload", {
|
|
106
98
|
body: form,
|
|
@@ -110,19 +102,25 @@ export class AllureTestOpsClient {
|
|
|
110
102
|
...(signal ? { signal } : {}),
|
|
111
103
|
});
|
|
112
104
|
}
|
|
105
|
+
async addReportAssets(payload) {
|
|
106
|
+
const { files, signal } = payload;
|
|
107
|
+
if (files.length === 0) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
const { form } = await createUploadForm(files, createUploadBlob, signal);
|
|
111
|
+
const client = await __classPrivateFieldGet(this, _AllureTestOpsClient_instances, "m", _AllureTestOpsClient_authorizedClient).call(this);
|
|
112
|
+
return client.post("/api/test-report/upload/batch", {
|
|
113
|
+
body: form,
|
|
114
|
+
headers: {
|
|
115
|
+
"Content-Type": "multipart/form-data",
|
|
116
|
+
},
|
|
117
|
+
...(signal ? { signal } : {}),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
113
120
|
async addReportFile(payload) {
|
|
114
|
-
const { reportUuid, filename, file, filepath,
|
|
121
|
+
const { reportUuid, pluginId, filename, file, filepath, signal } = payload;
|
|
115
122
|
const reportFilename = pluginId ? joinPosix(pluginId, filename) : filename;
|
|
116
|
-
|
|
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);
|
|
123
|
+
const { form } = await createUploadForm([{ filename, file, filepath, signal }], createUploadBlob, signal, () => reportFilename);
|
|
126
124
|
const client = await __classPrivateFieldGet(this, _AllureTestOpsClient_instances, "m", _AllureTestOpsClient_authorizedClient).call(this);
|
|
127
125
|
await client.post(`/api/test-report/${reportUuid}/upload`, {
|
|
128
126
|
body: form,
|
|
@@ -133,14 +131,36 @@ export class AllureTestOpsClient {
|
|
|
133
131
|
});
|
|
134
132
|
return createReportFileUrl(__classPrivateFieldGet(this, _AllureTestOpsClient_url, "f"), reportUuid, reportFilename);
|
|
135
133
|
}
|
|
134
|
+
async addReportFiles(payload) {
|
|
135
|
+
const { reportUuid, pluginId, files, signal } = payload;
|
|
136
|
+
if (files.length === 0) {
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
const { entries, form } = await createUploadForm(files, createUploadBlob, signal, (filename) => pluginId ? joinPosix(pluginId, filename) : filename);
|
|
140
|
+
const client = await __classPrivateFieldGet(this, _AllureTestOpsClient_instances, "m", _AllureTestOpsClient_authorizedClient).call(this);
|
|
141
|
+
await client.post(`/api/test-report/${reportUuid}/upload/batch`, {
|
|
142
|
+
body: form,
|
|
143
|
+
headers: {
|
|
144
|
+
"Content-Type": "multipart/form-data",
|
|
145
|
+
},
|
|
146
|
+
...(signal ? { signal } : {}),
|
|
147
|
+
});
|
|
148
|
+
return Object.fromEntries(entries.map(({ filename, reportFilename }) => [
|
|
149
|
+
filename,
|
|
150
|
+
createReportFileUrl(__classPrivateFieldGet(this, _AllureTestOpsClient_url, "f"), reportUuid, reportFilename),
|
|
151
|
+
]));
|
|
152
|
+
}
|
|
136
153
|
async uploadReport(payload) {
|
|
137
154
|
return uploadReport({
|
|
138
155
|
...payload,
|
|
139
156
|
uploadConcurrency: this.config.uploadConcurrency,
|
|
140
157
|
uploadMaxAttempts: this.config.uploadMaxAttempts,
|
|
141
158
|
uploadMaxSimultaneousFailures: this.config.uploadMaxSimultaneousFailures,
|
|
159
|
+
uploadBatchMaxBytes: UPLOAD_BATCH_MAX_BYTES,
|
|
142
160
|
addReportAsset: this.addReportAsset.bind(this),
|
|
161
|
+
addReportAssets: this.addReportAssets.bind(this),
|
|
143
162
|
addReportFile: this.addReportFile.bind(this),
|
|
163
|
+
addReportFiles: this.addReportFiles.bind(this),
|
|
144
164
|
});
|
|
145
165
|
}
|
|
146
166
|
}
|
package/dist/utils/http.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
1
2
|
import axios, { isAxiosError } from "axios";
|
|
2
3
|
import { isReportDataFile } from "./files.js";
|
|
3
4
|
export class KnownError extends Error {
|
|
@@ -48,6 +49,33 @@ const createUploadFileBatches = (files) => {
|
|
|
48
49
|
flushBatch();
|
|
49
50
|
return batches;
|
|
50
51
|
};
|
|
52
|
+
const createUploadFileBatchesBySize = async (files, maxBytes) => {
|
|
53
|
+
const entries = Object.entries(files);
|
|
54
|
+
const batches = [];
|
|
55
|
+
let batch = {};
|
|
56
|
+
let batchFiles = 0;
|
|
57
|
+
let batchBytes = 0;
|
|
58
|
+
const flushBatch = () => {
|
|
59
|
+
if (batchFiles === 0) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
batches.push(batch);
|
|
63
|
+
batch = {};
|
|
64
|
+
batchFiles = 0;
|
|
65
|
+
batchBytes = 0;
|
|
66
|
+
};
|
|
67
|
+
for (const [filename, filepath] of entries) {
|
|
68
|
+
const fileBytes = (await stat(filepath)).size;
|
|
69
|
+
if (batchFiles >= MAX_UPLOAD_BATCH_FILES || (batchBytes > 0 && batchBytes + fileBytes > maxBytes)) {
|
|
70
|
+
flushBatch();
|
|
71
|
+
}
|
|
72
|
+
batch[filename] = filepath;
|
|
73
|
+
batchFiles += 1;
|
|
74
|
+
batchBytes += fileBytes;
|
|
75
|
+
}
|
|
76
|
+
flushBatch();
|
|
77
|
+
return batches;
|
|
78
|
+
};
|
|
51
79
|
export const formatResponseErrorData = (data) => {
|
|
52
80
|
if (data === undefined || data === null || data === "") {
|
|
53
81
|
return undefined;
|
|
@@ -163,8 +191,17 @@ const uploadWithRetry = async (batchId, uploadAbortController, failedUploads, ma
|
|
|
163
191
|
return false;
|
|
164
192
|
};
|
|
165
193
|
export const uploadReport = async (payload) => {
|
|
166
|
-
const { reportUuid, pluginId, files, onProgress,
|
|
167
|
-
|
|
194
|
+
const { reportUuid, pluginId, files, onProgress, uploadBatchMaxBytes, uploadConcurrency, uploadMaxAttempts, uploadMaxSimultaneousFailures, } = payload;
|
|
195
|
+
let fileBatches;
|
|
196
|
+
if (uploadBatchMaxBytes === undefined) {
|
|
197
|
+
fileBatches = Array.isArray(files) ? files.flatMap(createUploadFileBatches) : createUploadFileBatches(files);
|
|
198
|
+
}
|
|
199
|
+
else if (Array.isArray(files)) {
|
|
200
|
+
fileBatches = (await Promise.all(files.map((fileBatch) => createUploadFileBatchesBySize(fileBatch, uploadBatchMaxBytes)))).flat();
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
fileBatches = await createUploadFileBatchesBySize(files, uploadBatchMaxBytes);
|
|
204
|
+
}
|
|
168
205
|
if (fileBatches.length === 0) {
|
|
169
206
|
return {
|
|
170
207
|
hrefs: {},
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { UploadReportFilePayload } from "../model.js";
|
|
2
|
+
export declare const readUploadContent: (payload: UploadReportFilePayload) => Promise<Buffer<ArrayBufferLike>>;
|
|
3
|
+
export declare const createUploadForm: (files: UploadReportFilePayload[], createUploadBlob: (content: Buffer, filename: string) => Blob, signal?: AbortSignal, mapFilename?: (filename: string) => string) => Promise<{
|
|
4
|
+
entries: {
|
|
5
|
+
filename: string;
|
|
6
|
+
reportFilename: string;
|
|
7
|
+
content: Buffer<ArrayBufferLike>;
|
|
8
|
+
}[];
|
|
9
|
+
form: import("undici-types").FormData;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
export const readUploadContent = async (payload) => {
|
|
3
|
+
const { file, filepath, signal } = payload;
|
|
4
|
+
if (file) {
|
|
5
|
+
return file;
|
|
6
|
+
}
|
|
7
|
+
if (!filepath) {
|
|
8
|
+
throw new Error("File or filepath is required");
|
|
9
|
+
}
|
|
10
|
+
return signal ? readFile(filepath, { signal }) : readFile(filepath);
|
|
11
|
+
};
|
|
12
|
+
export const createUploadForm = async (files, createUploadBlob, signal, mapFilename) => {
|
|
13
|
+
const entries = await Promise.all(files.map(async ({ filename, ...filePayload }) => {
|
|
14
|
+
const reportFilename = mapFilename ? mapFilename(filename) : filename;
|
|
15
|
+
return {
|
|
16
|
+
filename,
|
|
17
|
+
reportFilename,
|
|
18
|
+
content: await readUploadContent({ filename, ...filePayload, signal: signal ?? filePayload.signal }),
|
|
19
|
+
};
|
|
20
|
+
}));
|
|
21
|
+
const form = new FormData();
|
|
22
|
+
for (const { reportFilename, content } of entries) {
|
|
23
|
+
form.append("filename", reportFilename);
|
|
24
|
+
form.append("file", createUploadBlob(content, reportFilename), reportFilename);
|
|
25
|
+
}
|
|
26
|
+
return { entries, form };
|
|
27
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allurereport/service",
|
|
3
|
-
"version": "3.14.
|
|
3
|
+
"version": "3.14.3",
|
|
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.14.
|
|
34
|
-
"@allurereport/plugin-api": "3.14.
|
|
33
|
+
"@allurereport/core-api": "3.14.3",
|
|
34
|
+
"@allurereport/plugin-api": "3.14.3",
|
|
35
35
|
"axios": "^1.18.1",
|
|
36
36
|
"open": "^11.0.0"
|
|
37
37
|
},
|