flakiness 0.148.0 → 0.149.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/lib/cli/cli.js +36 -410
- package/lib/cli/cmd-convert.js +16 -60
- package/lib/cli/cmd-login.js +0 -2
- package/lib/cli/cmd-logout.js +0 -2
- package/lib/cli/cmd-status.js +0 -2
- package/lib/cli/cmd-upload.js +8 -17
- package/lib/cli/cmd-whoami.js +0 -2
- package/lib/flakinessSession.js +0 -2
- package/lib/junit.js +5 -5
- package/lib/serverapi.js +0 -2
- package/lib/utils.js +3 -190
- package/package.json +4 -4
- package/types/tsconfig.tsbuildinfo +1 -1
- package/lib/cli/cmd-upload-playwright-json.js +0 -463
- package/lib/playwrightJSONReport.js +0 -429
package/lib/cli/cmd-convert.js
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/cmd-convert.ts
|
|
4
|
+
import { GitWorktree, writeReport } from "@flakiness/sdk";
|
|
4
5
|
import fs3 from "fs/promises";
|
|
5
|
-
import
|
|
6
|
+
import path2 from "path";
|
|
6
7
|
|
|
7
8
|
// src/junit.ts
|
|
8
9
|
import { ReportUtils as ReportUtils2 } from "@flakiness/sdk";
|
|
9
10
|
import { parseXml, XmlElement, XmlText } from "@rgrove/parse-xml";
|
|
10
|
-
import
|
|
11
|
+
import assert from "assert";
|
|
11
12
|
import fs2 from "fs";
|
|
12
|
-
import
|
|
13
|
+
import path from "path";
|
|
13
14
|
|
|
14
15
|
// src/utils.ts
|
|
15
16
|
import { ReportUtils } from "@flakiness/sdk";
|
|
16
|
-
import assert from "assert";
|
|
17
|
-
import { spawnSync } from "child_process";
|
|
18
17
|
import crypto from "crypto";
|
|
19
18
|
import fs from "fs";
|
|
20
19
|
import http from "http";
|
|
21
20
|
import https from "https";
|
|
22
|
-
import path, { posix as posixPath, win32 as win32Path } from "path";
|
|
23
21
|
function sha1File(filePath) {
|
|
24
22
|
return new Promise((resolve, reject) => {
|
|
25
23
|
const hash = crypto.createHash("sha1");
|
|
@@ -123,51 +121,6 @@ var httpUtils;
|
|
|
123
121
|
httpUtils2.postJSON = postJSON;
|
|
124
122
|
})(httpUtils || (httpUtils = {}));
|
|
125
123
|
var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
|
|
126
|
-
async function saveReportAndAttachments(report, attachments, outputFolder) {
|
|
127
|
-
const reportPath = path.join(outputFolder, "report.json");
|
|
128
|
-
const attachmentsFolder = path.join(outputFolder, "attachments");
|
|
129
|
-
await fs.promises.rm(outputFolder, { recursive: true, force: true });
|
|
130
|
-
await fs.promises.mkdir(outputFolder, { recursive: true });
|
|
131
|
-
await fs.promises.writeFile(reportPath, JSON.stringify(report), "utf-8");
|
|
132
|
-
if (attachments.length)
|
|
133
|
-
await fs.promises.mkdir(attachmentsFolder);
|
|
134
|
-
const movedAttachments = [];
|
|
135
|
-
for (const attachment of attachments) {
|
|
136
|
-
const attachmentPath = path.join(attachmentsFolder, attachment.id);
|
|
137
|
-
if (attachment.path)
|
|
138
|
-
await fs.promises.cp(attachment.path, attachmentPath);
|
|
139
|
-
else if (attachment.body)
|
|
140
|
-
await fs.promises.writeFile(attachmentPath, attachment.body);
|
|
141
|
-
movedAttachments.push({
|
|
142
|
-
contentType: attachment.contentType,
|
|
143
|
-
id: attachment.id,
|
|
144
|
-
path: attachmentPath
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
return movedAttachments;
|
|
148
|
-
}
|
|
149
|
-
function shell(command, args, options) {
|
|
150
|
-
try {
|
|
151
|
-
const result = spawnSync(command, args, { encoding: "utf-8", ...options });
|
|
152
|
-
if (result.status !== 0) {
|
|
153
|
-
return void 0;
|
|
154
|
-
}
|
|
155
|
-
return result.stdout.trim();
|
|
156
|
-
} catch (e) {
|
|
157
|
-
console.error(e);
|
|
158
|
-
return void 0;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
function gitCommitInfo(gitRepo) {
|
|
162
|
-
const sha = shell(`git`, ["rev-parse", "HEAD"], {
|
|
163
|
-
cwd: gitRepo,
|
|
164
|
-
encoding: "utf-8"
|
|
165
|
-
});
|
|
166
|
-
assert(sha, `FAILED: git rev-parse HEAD @ ${gitRepo}`);
|
|
167
|
-
return sha.trim();
|
|
168
|
-
}
|
|
169
|
-
var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
|
|
170
|
-
var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
|
|
171
124
|
|
|
172
125
|
// src/junit.ts
|
|
173
126
|
function getProperties(element) {
|
|
@@ -211,19 +164,21 @@ function extractStdout(testcase, stdio) {
|
|
|
211
164
|
}));
|
|
212
165
|
}
|
|
213
166
|
async function parseAttachment(value) {
|
|
214
|
-
let absolutePath =
|
|
167
|
+
let absolutePath = path.resolve(process.cwd(), value);
|
|
215
168
|
if (fs2.existsSync(absolutePath)) {
|
|
216
169
|
const id = await sha1File(absolutePath);
|
|
217
170
|
return {
|
|
218
171
|
contentType: "image/png",
|
|
219
172
|
path: absolutePath,
|
|
220
|
-
id
|
|
173
|
+
id,
|
|
174
|
+
type: "file"
|
|
221
175
|
};
|
|
222
176
|
}
|
|
223
177
|
return {
|
|
224
178
|
contentType: "text/plain",
|
|
225
179
|
id: sha1Buffer(value),
|
|
226
|
-
body: Buffer.from(value)
|
|
180
|
+
body: Buffer.from(value),
|
|
181
|
+
type: "buffer"
|
|
227
182
|
};
|
|
228
183
|
}
|
|
229
184
|
async function traverseJUnitReport(context, node) {
|
|
@@ -264,7 +219,7 @@ async function traverseJUnitReport(context, node) {
|
|
|
264
219
|
currentEnvIndex = report.environments.push(currentEnv) - 1;
|
|
265
220
|
}
|
|
266
221
|
} else if (element.name === "testcase") {
|
|
267
|
-
|
|
222
|
+
assert(currentSuite);
|
|
268
223
|
const file = element.attributes["file"];
|
|
269
224
|
const name = element.attributes["name"];
|
|
270
225
|
const line = parseInt(element.attributes["line"], 10);
|
|
@@ -284,7 +239,7 @@ async function traverseJUnitReport(context, node) {
|
|
|
284
239
|
id: attachment.id,
|
|
285
240
|
contentType: attachment.contentType,
|
|
286
241
|
//TODO: better default names for attachments?
|
|
287
|
-
name: attachment.
|
|
242
|
+
name: attachment.type === "file" ? path.basename(attachment.path) : `attachment`
|
|
288
243
|
});
|
|
289
244
|
} else {
|
|
290
245
|
annotations.push({
|
|
@@ -359,7 +314,7 @@ async function parseJUnit(xmls, options) {
|
|
|
359
314
|
|
|
360
315
|
// src/cli/cmd-convert.ts
|
|
361
316
|
async function cmdConvert(junitPath, options) {
|
|
362
|
-
const fullPath =
|
|
317
|
+
const fullPath = path2.resolve(junitPath);
|
|
363
318
|
if (!await fs3.access(fullPath, fs3.constants.F_OK).then(() => true).catch(() => false)) {
|
|
364
319
|
console.error(`Error: path ${fullPath} is not accessible`);
|
|
365
320
|
process.exit(1);
|
|
@@ -389,7 +344,8 @@ async function cmdConvert(junitPath, options) {
|
|
|
389
344
|
commitId = options.commitId;
|
|
390
345
|
} else {
|
|
391
346
|
try {
|
|
392
|
-
|
|
347
|
+
const worktree = GitWorktree.create(process.cwd());
|
|
348
|
+
commitId = worktree.headCommitId();
|
|
393
349
|
} catch (e) {
|
|
394
350
|
console.error("Failed to get git commit info. Please provide --commit-id option.");
|
|
395
351
|
process.exit(1);
|
|
@@ -401,13 +357,13 @@ async function cmdConvert(junitPath, options) {
|
|
|
401
357
|
runStartTimestamp: Date.now(),
|
|
402
358
|
runDuration: 0
|
|
403
359
|
});
|
|
404
|
-
await
|
|
360
|
+
await writeReport(report, attachments, options.outputDir);
|
|
405
361
|
console.log(`\u2713 Saved to ${options.outputDir}`);
|
|
406
362
|
}
|
|
407
363
|
async function findXmlFiles(dir, result = []) {
|
|
408
364
|
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
409
365
|
for (const entry of entries) {
|
|
410
|
-
const fullPath =
|
|
366
|
+
const fullPath = path2.join(dir, entry.name);
|
|
411
367
|
if (entry.isFile() && entry.name.toLowerCase().endsWith(".xml"))
|
|
412
368
|
result.push(fullPath);
|
|
413
369
|
else if (entry.isDirectory())
|
package/lib/cli/cmd-login.js
CHANGED
|
@@ -103,8 +103,6 @@ var httpUtils;
|
|
|
103
103
|
httpUtils2.postJSON = postJSON;
|
|
104
104
|
})(httpUtils || (httpUtils = {}));
|
|
105
105
|
var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
|
|
106
|
-
var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
|
|
107
|
-
var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
|
|
108
106
|
|
|
109
107
|
// src/serverapi.ts
|
|
110
108
|
function createServerAPI(endpoint, options) {
|
package/lib/cli/cmd-logout.js
CHANGED
|
@@ -93,8 +93,6 @@ var httpUtils;
|
|
|
93
93
|
httpUtils2.postJSON = postJSON;
|
|
94
94
|
})(httpUtils || (httpUtils = {}));
|
|
95
95
|
var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
|
|
96
|
-
var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
|
|
97
|
-
var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
|
|
98
96
|
|
|
99
97
|
// src/serverapi.ts
|
|
100
98
|
function createServerAPI(endpoint, options) {
|
package/lib/cli/cmd-status.js
CHANGED
|
@@ -96,8 +96,6 @@ var httpUtils;
|
|
|
96
96
|
httpUtils2.postJSON = postJSON;
|
|
97
97
|
})(httpUtils || (httpUtils = {}));
|
|
98
98
|
var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
|
|
99
|
-
var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
|
|
100
|
-
var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
|
|
101
99
|
|
|
102
100
|
// src/serverapi.ts
|
|
103
101
|
function createServerAPI(endpoint, options) {
|
package/lib/cli/cmd-upload.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/cmd-upload.ts
|
|
4
|
-
import {
|
|
4
|
+
import { uploadReport } from "@flakiness/sdk";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import fs2 from "fs/promises";
|
|
7
7
|
import path2 from "path";
|
|
@@ -11,7 +11,7 @@ import { ReportUtils } from "@flakiness/sdk";
|
|
|
11
11
|
import fs from "fs";
|
|
12
12
|
import http from "http";
|
|
13
13
|
import https from "https";
|
|
14
|
-
import path
|
|
14
|
+
import path from "path";
|
|
15
15
|
var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
|
|
16
16
|
function errorText(error) {
|
|
17
17
|
return FLAKINESS_DBG ? error.stack : error.message;
|
|
@@ -110,7 +110,8 @@ async function resolveAttachmentPaths(report, attachmentsDir) {
|
|
|
110
110
|
attachmentIdToPath.set(attachment.id, {
|
|
111
111
|
contentType: attachment.contentType,
|
|
112
112
|
id: attachment.id,
|
|
113
|
-
path: attachmentPath
|
|
113
|
+
path: attachmentPath,
|
|
114
|
+
type: "file"
|
|
114
115
|
});
|
|
115
116
|
}
|
|
116
117
|
}
|
|
@@ -129,18 +130,11 @@ async function listFilesRecursively(dir, result = []) {
|
|
|
129
130
|
}
|
|
130
131
|
return result;
|
|
131
132
|
}
|
|
132
|
-
var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
|
|
133
|
-
var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
|
|
134
133
|
|
|
135
134
|
// src/cli/cmd-upload.ts
|
|
136
135
|
var warn = (txt) => console.warn(chalk.yellow(`[flakiness.io] WARN: ${txt}`));
|
|
137
136
|
var err = (txt) => console.error(chalk.red(`[flakiness.io] Error: ${txt}`));
|
|
138
|
-
var log = (txt) => console.log(`[flakiness.io] ${txt}`);
|
|
139
137
|
async function cmdUpload(relativePaths, options) {
|
|
140
|
-
const uploader = new ReportUploader({
|
|
141
|
-
flakinessAccessToken: options.accessToken,
|
|
142
|
-
flakinessEndpoint: options.endpoint
|
|
143
|
-
});
|
|
144
138
|
for (const relativePath of relativePaths) {
|
|
145
139
|
const fullPath = path2.resolve(relativePath);
|
|
146
140
|
if (!await fs2.access(fullPath, fs2.constants.F_OK).then(() => true).catch(() => false)) {
|
|
@@ -154,13 +148,10 @@ async function cmdUpload(relativePaths, options) {
|
|
|
154
148
|
if (missingAttachments.length) {
|
|
155
149
|
warn(`Missing ${missingAttachments.length} attachments`);
|
|
156
150
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
} else {
|
|
162
|
-
log(`\u2713 Uploaded ${uploadResult.reportUrl ?? uploadResult.message ?? ""}`);
|
|
163
|
-
}
|
|
151
|
+
await uploadReport(report, Array.from(attachmentIdToPath.values()), {
|
|
152
|
+
flakinessAccessToken: options.accessToken,
|
|
153
|
+
flakinessEndpoint: options.endpoint
|
|
154
|
+
});
|
|
164
155
|
}
|
|
165
156
|
}
|
|
166
157
|
export {
|
package/lib/cli/cmd-whoami.js
CHANGED
|
@@ -95,8 +95,6 @@ var httpUtils;
|
|
|
95
95
|
httpUtils2.postJSON = postJSON;
|
|
96
96
|
})(httpUtils || (httpUtils = {}));
|
|
97
97
|
var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
|
|
98
|
-
var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
|
|
99
|
-
var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
|
|
100
98
|
|
|
101
99
|
// src/serverapi.ts
|
|
102
100
|
function createServerAPI(endpoint, options) {
|
package/lib/flakinessSession.js
CHANGED
|
@@ -93,8 +93,6 @@ var httpUtils;
|
|
|
93
93
|
httpUtils2.postJSON = postJSON;
|
|
94
94
|
})(httpUtils || (httpUtils = {}));
|
|
95
95
|
var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
|
|
96
|
-
var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
|
|
97
|
-
var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
|
|
98
96
|
|
|
99
97
|
// src/serverapi.ts
|
|
100
98
|
function createServerAPI(endpoint, options) {
|
package/lib/junit.js
CHANGED
|
@@ -114,8 +114,6 @@ var httpUtils;
|
|
|
114
114
|
httpUtils2.postJSON = postJSON;
|
|
115
115
|
})(httpUtils || (httpUtils = {}));
|
|
116
116
|
var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
|
|
117
|
-
var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
|
|
118
|
-
var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
|
|
119
117
|
|
|
120
118
|
// src/junit.ts
|
|
121
119
|
function getProperties(element) {
|
|
@@ -165,13 +163,15 @@ async function parseAttachment(value) {
|
|
|
165
163
|
return {
|
|
166
164
|
contentType: "image/png",
|
|
167
165
|
path: absolutePath,
|
|
168
|
-
id
|
|
166
|
+
id,
|
|
167
|
+
type: "file"
|
|
169
168
|
};
|
|
170
169
|
}
|
|
171
170
|
return {
|
|
172
171
|
contentType: "text/plain",
|
|
173
172
|
id: sha1Buffer(value),
|
|
174
|
-
body: Buffer.from(value)
|
|
173
|
+
body: Buffer.from(value),
|
|
174
|
+
type: "buffer"
|
|
175
175
|
};
|
|
176
176
|
}
|
|
177
177
|
async function traverseJUnitReport(context, node) {
|
|
@@ -232,7 +232,7 @@ async function traverseJUnitReport(context, node) {
|
|
|
232
232
|
id: attachment.id,
|
|
233
233
|
contentType: attachment.contentType,
|
|
234
234
|
//TODO: better default names for attachments?
|
|
235
|
-
name: attachment.
|
|
235
|
+
name: attachment.type === "file" ? path.basename(attachment.path) : `attachment`
|
|
236
236
|
});
|
|
237
237
|
} else {
|
|
238
238
|
annotations.push({
|
package/lib/serverapi.js
CHANGED
|
@@ -88,8 +88,6 @@ var httpUtils;
|
|
|
88
88
|
httpUtils2.postJSON = postJSON;
|
|
89
89
|
})(httpUtils || (httpUtils = {}));
|
|
90
90
|
var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
|
|
91
|
-
var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
|
|
92
|
-
var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
|
|
93
91
|
|
|
94
92
|
// src/serverapi.ts
|
|
95
93
|
function createServerAPI(endpoint, options) {
|
package/lib/utils.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
// src/utils.ts
|
|
2
2
|
import { ReportUtils } from "@flakiness/sdk";
|
|
3
|
-
import assert from "assert";
|
|
4
3
|
import { spawnSync } from "child_process";
|
|
5
4
|
import crypto from "crypto";
|
|
6
5
|
import fs from "fs";
|
|
7
6
|
import http from "http";
|
|
8
7
|
import https from "https";
|
|
9
|
-
import
|
|
10
|
-
import path, { posix as posixPath, win32 as win32Path } from "path";
|
|
8
|
+
import path from "path";
|
|
11
9
|
async function existsAsync(aPath) {
|
|
12
10
|
return fs.promises.stat(aPath).then(() => true).catch((e) => false);
|
|
13
11
|
}
|
|
@@ -123,29 +121,6 @@ var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:
|
|
|
123
121
|
function stripAnsi(str) {
|
|
124
122
|
return str.replace(ansiRegex, "");
|
|
125
123
|
}
|
|
126
|
-
async function saveReportAndAttachments(report, attachments, outputFolder) {
|
|
127
|
-
const reportPath = path.join(outputFolder, "report.json");
|
|
128
|
-
const attachmentsFolder = path.join(outputFolder, "attachments");
|
|
129
|
-
await fs.promises.rm(outputFolder, { recursive: true, force: true });
|
|
130
|
-
await fs.promises.mkdir(outputFolder, { recursive: true });
|
|
131
|
-
await fs.promises.writeFile(reportPath, JSON.stringify(report), "utf-8");
|
|
132
|
-
if (attachments.length)
|
|
133
|
-
await fs.promises.mkdir(attachmentsFolder);
|
|
134
|
-
const movedAttachments = [];
|
|
135
|
-
for (const attachment of attachments) {
|
|
136
|
-
const attachmentPath = path.join(attachmentsFolder, attachment.id);
|
|
137
|
-
if (attachment.path)
|
|
138
|
-
await fs.promises.cp(attachment.path, attachmentPath);
|
|
139
|
-
else if (attachment.body)
|
|
140
|
-
await fs.promises.writeFile(attachmentPath, attachment.body);
|
|
141
|
-
movedAttachments.push({
|
|
142
|
-
contentType: attachment.contentType,
|
|
143
|
-
id: attachment.id,
|
|
144
|
-
path: attachmentPath
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
return movedAttachments;
|
|
148
|
-
}
|
|
149
124
|
function shell(command, args, options) {
|
|
150
125
|
try {
|
|
151
126
|
const result = spawnSync(command, args, { encoding: "utf-8", ...options });
|
|
@@ -158,58 +133,9 @@ function shell(command, args, options) {
|
|
|
158
133
|
return void 0;
|
|
159
134
|
}
|
|
160
135
|
}
|
|
161
|
-
function readLinuxOSRelease() {
|
|
162
|
-
const osReleaseText = fs.readFileSync("/etc/os-release", "utf-8");
|
|
163
|
-
return new Map(osReleaseText.toLowerCase().split("\n").filter((line) => line.includes("=")).map((line) => {
|
|
164
|
-
line = line.trim();
|
|
165
|
-
let [key, value] = line.split("=");
|
|
166
|
-
if (value.startsWith('"') && value.endsWith('"'))
|
|
167
|
-
value = value.substring(1, value.length - 1);
|
|
168
|
-
return [key, value];
|
|
169
|
-
}));
|
|
170
|
-
}
|
|
171
|
-
function osLinuxInfo() {
|
|
172
|
-
const arch = shell(`uname`, [`-m`]);
|
|
173
|
-
const osReleaseMap = readLinuxOSRelease();
|
|
174
|
-
const name = osReleaseMap.get("name") ?? shell(`uname`);
|
|
175
|
-
const version = osReleaseMap.get("version_id");
|
|
176
|
-
return { name, arch, version };
|
|
177
|
-
}
|
|
178
|
-
function osDarwinInfo() {
|
|
179
|
-
const name = "macos";
|
|
180
|
-
const arch = shell(`uname`, [`-m`]);
|
|
181
|
-
const version = shell(`sw_vers`, [`-productVersion`]);
|
|
182
|
-
return { name, arch, version };
|
|
183
|
-
}
|
|
184
|
-
function osWinInfo() {
|
|
185
|
-
const name = "win";
|
|
186
|
-
const arch = process.arch;
|
|
187
|
-
const version = os.release();
|
|
188
|
-
return { name, arch, version };
|
|
189
|
-
}
|
|
190
|
-
function getOSInfo() {
|
|
191
|
-
if (process.platform === "darwin")
|
|
192
|
-
return osDarwinInfo();
|
|
193
|
-
if (process.platform === "win32")
|
|
194
|
-
return osWinInfo();
|
|
195
|
-
return osLinuxInfo();
|
|
196
|
-
}
|
|
197
|
-
function inferRunUrl() {
|
|
198
|
-
if (process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID)
|
|
199
|
-
return `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
|
200
|
-
return void 0;
|
|
201
|
-
}
|
|
202
136
|
function parseStringDate(dateString) {
|
|
203
137
|
return +new Date(dateString);
|
|
204
138
|
}
|
|
205
|
-
function gitCommitInfo(gitRepo) {
|
|
206
|
-
const sha = shell(`git`, ["rev-parse", "HEAD"], {
|
|
207
|
-
cwd: gitRepo,
|
|
208
|
-
encoding: "utf-8"
|
|
209
|
-
});
|
|
210
|
-
assert(sha, `FAILED: git rev-parse HEAD @ ${gitRepo}`);
|
|
211
|
-
return sha.trim();
|
|
212
|
-
}
|
|
213
139
|
async function resolveAttachmentPaths(report, attachmentsDir) {
|
|
214
140
|
const attachmentFiles = await listFilesRecursively(attachmentsDir);
|
|
215
141
|
const filenameToPath = new Map(attachmentFiles.map((file) => [path.basename(file), file]));
|
|
@@ -225,7 +151,8 @@ async function resolveAttachmentPaths(report, attachmentsDir) {
|
|
|
225
151
|
attachmentIdToPath.set(attachment.id, {
|
|
226
152
|
contentType: attachment.contentType,
|
|
227
153
|
id: attachment.id,
|
|
228
|
-
path: attachmentPath
|
|
154
|
+
path: attachmentPath,
|
|
155
|
+
type: "file"
|
|
229
156
|
});
|
|
230
157
|
}
|
|
231
158
|
}
|
|
@@ -244,128 +171,14 @@ async function listFilesRecursively(dir, result = []) {
|
|
|
244
171
|
}
|
|
245
172
|
return result;
|
|
246
173
|
}
|
|
247
|
-
function computeGitRoot(somePathInsideGitRepo) {
|
|
248
|
-
const root = shell(`git`, ["rev-parse", "--show-toplevel"], {
|
|
249
|
-
cwd: somePathInsideGitRepo,
|
|
250
|
-
encoding: "utf-8"
|
|
251
|
-
});
|
|
252
|
-
assert(root, `FAILED: git rev-parse --show-toplevel HEAD @ ${somePathInsideGitRepo}`);
|
|
253
|
-
return normalizePath(root);
|
|
254
|
-
}
|
|
255
|
-
var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
|
|
256
|
-
var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
|
|
257
|
-
function normalizePath(aPath) {
|
|
258
|
-
if (IS_WIN32_PATH.test(aPath)) {
|
|
259
|
-
aPath = aPath.split(win32Path.sep).join(posixPath.sep);
|
|
260
|
-
}
|
|
261
|
-
if (IS_ALMOST_POSIX_PATH.test(aPath))
|
|
262
|
-
return "/" + aPath[0] + aPath.substring(2);
|
|
263
|
-
return aPath;
|
|
264
|
-
}
|
|
265
|
-
function getCallerLocation(gitRoot, offset = 0) {
|
|
266
|
-
const err = new Error();
|
|
267
|
-
const stack = err.stack?.split("\n");
|
|
268
|
-
const caller = stack?.[2 + offset]?.trim();
|
|
269
|
-
const match = caller?.match(/\((.*):(\d+):(\d+)\)$/);
|
|
270
|
-
if (!match) return void 0;
|
|
271
|
-
const [, filePath, line, column] = match;
|
|
272
|
-
return {
|
|
273
|
-
file: gitFilePath(gitRoot, normalizePath(filePath)),
|
|
274
|
-
line: Number(line),
|
|
275
|
-
column: Number(column)
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
function parseStackLocations() {
|
|
279
|
-
const err = new Error();
|
|
280
|
-
const stack = err.stack?.split("\n").slice(2);
|
|
281
|
-
if (!stack)
|
|
282
|
-
return [];
|
|
283
|
-
const result = [];
|
|
284
|
-
for (const caller of stack) {
|
|
285
|
-
const match = caller.trim().match(/\((.*):(\d+):(\d+)\)$/);
|
|
286
|
-
if (!match)
|
|
287
|
-
continue;
|
|
288
|
-
const [, file, line, column] = match;
|
|
289
|
-
result.push({ file, line, column });
|
|
290
|
-
}
|
|
291
|
-
return result;
|
|
292
|
-
}
|
|
293
|
-
function gitFilePath(gitRoot, absolutePath) {
|
|
294
|
-
return posixPath.relative(gitRoot, absolutePath);
|
|
295
|
-
}
|
|
296
|
-
function parseDurationMS(value) {
|
|
297
|
-
if (isNaN(value))
|
|
298
|
-
throw new Error("Duration cannot be NaN");
|
|
299
|
-
if (value < 0)
|
|
300
|
-
throw new Error(`Duration cannot be less than 0, found ${value}`);
|
|
301
|
-
return value | 0;
|
|
302
|
-
}
|
|
303
|
-
function createEnvironment(options) {
|
|
304
|
-
const osInfo = getOSInfo();
|
|
305
|
-
return {
|
|
306
|
-
name: options.name,
|
|
307
|
-
systemData: {
|
|
308
|
-
osArch: osInfo.arch,
|
|
309
|
-
osName: osInfo.name,
|
|
310
|
-
osVersion: osInfo.version
|
|
311
|
-
},
|
|
312
|
-
userSuppliedData: {
|
|
313
|
-
...extractEnvConfiguration(),
|
|
314
|
-
...options.userSuppliedData ?? {}
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
function createEnvironments(projects) {
|
|
319
|
-
const envConfiguration = extractEnvConfiguration();
|
|
320
|
-
const osInfo = getOSInfo();
|
|
321
|
-
let uniqueNames = /* @__PURE__ */ new Set();
|
|
322
|
-
const result = /* @__PURE__ */ new Map();
|
|
323
|
-
for (const project of projects) {
|
|
324
|
-
let defaultName = project.name;
|
|
325
|
-
if (!defaultName.trim())
|
|
326
|
-
defaultName = "anonymous";
|
|
327
|
-
let name = defaultName;
|
|
328
|
-
for (let i = 2; uniqueNames.has(name); ++i)
|
|
329
|
-
name = `${defaultName}-${i}`;
|
|
330
|
-
uniqueNames.add(defaultName);
|
|
331
|
-
result.set(project, {
|
|
332
|
-
name,
|
|
333
|
-
systemData: {
|
|
334
|
-
osArch: osInfo.arch,
|
|
335
|
-
osName: osInfo.name,
|
|
336
|
-
osVersion: osInfo.version
|
|
337
|
-
},
|
|
338
|
-
userSuppliedData: {
|
|
339
|
-
...envConfiguration,
|
|
340
|
-
...project.metadata
|
|
341
|
-
},
|
|
342
|
-
opaqueData: {
|
|
343
|
-
project
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
return result;
|
|
348
|
-
}
|
|
349
174
|
export {
|
|
350
|
-
computeGitRoot,
|
|
351
|
-
createEnvironment,
|
|
352
|
-
createEnvironments,
|
|
353
175
|
errorText,
|
|
354
176
|
existsAsync,
|
|
355
177
|
extractEnvConfiguration,
|
|
356
|
-
getCallerLocation,
|
|
357
|
-
getOSInfo,
|
|
358
|
-
gitCommitInfo,
|
|
359
|
-
gitFilePath,
|
|
360
178
|
httpUtils,
|
|
361
|
-
inferRunUrl,
|
|
362
|
-
normalizePath,
|
|
363
|
-
parseDurationMS,
|
|
364
|
-
parseStackLocations,
|
|
365
179
|
parseStringDate,
|
|
366
180
|
resolveAttachmentPaths,
|
|
367
181
|
retryWithBackoff,
|
|
368
|
-
saveReportAndAttachments,
|
|
369
182
|
sha1Buffer,
|
|
370
183
|
sha1File,
|
|
371
184
|
shell,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flakiness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.149.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"bin": {
|
|
6
6
|
"flakiness": "./lib/cli/cli.js"
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"author": "Degu Labs, Inc",
|
|
33
33
|
"license": "Fair Source 100",
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@flakiness/server": "0.
|
|
35
|
+
"@flakiness/server": "0.149.0",
|
|
36
36
|
"@playwright/test": "^1.57.0"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@flakiness/sdk": "0.
|
|
40
|
-
"@flakiness/shared": "0.
|
|
39
|
+
"@flakiness/sdk": "0.149.0",
|
|
40
|
+
"@flakiness/shared": "0.149.0",
|
|
41
41
|
"@rgrove/parse-xml": "^4.2.0",
|
|
42
42
|
"chalk": "^5.6.2",
|
|
43
43
|
"commander": "^13.1.0",
|