@allurereport/service 3.14.1 → 3.14.2

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 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 entries = await Promise.all(files.map(async ({ filename, ...filePayload }) => ({
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 Promise.all(files.map(async ({ filename, ...filePayload }) => {
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
- ...payload,
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 { 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);
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, pluginId, signal } = payload;
121
+ const { reportUuid, pluginId, filename, file, filepath, signal } = payload;
115
122
  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);
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
  }
@@ -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, addReportAsset, addReportFile, uploadConcurrency, uploadMaxAttempts, uploadMaxSimultaneousFailures, } = payload;
167
- const fileBatches = Array.isArray(files) ? files.flatMap(createUploadFileBatches) : createUploadFileBatches(files);
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.1",
3
+ "version": "3.14.2",
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.1",
34
- "@allurereport/plugin-api": "3.14.1",
33
+ "@allurereport/core-api": "3.14.2",
34
+ "@allurereport/plugin-api": "3.14.2",
35
35
  "axios": "^1.18.1",
36
36
  "open": "^11.0.0"
37
37
  },