bitfab 0.29.1 → 0.30.1
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/{chunk-RTPEBWVO.js → chunk-ERHYLE4S.js} +8 -5
- package/dist/chunk-ERHYLE4S.js.map +1 -0
- package/dist/{chunk-YZU6WFG2.js → chunk-MYDYCNW4.js} +205 -4
- package/dist/chunk-MYDYCNW4.js.map +1 -0
- package/dist/index.cjs +216 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +2 -2
- package/dist/node.cjs +216 -5
- package/dist/node.cjs.map +1 -1
- package/dist/node.js +2 -2
- package/dist/{replay-SKW5A7II.js → replay-NFPCEVES.js} +2 -2
- package/package.json +2 -2
- package/dist/chunk-RTPEBWVO.js.map +0 -1
- package/dist/chunk-YZU6WFG2.js.map +0 -1
- /package/dist/{replay-SKW5A7II.js.map → replay-NFPCEVES.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -329,6 +329,203 @@ var init_replayContext = __esm({
|
|
|
329
329
|
}
|
|
330
330
|
});
|
|
331
331
|
|
|
332
|
+
// src/codeChange.ts
|
|
333
|
+
async function resolveAutoCodeChange(label) {
|
|
334
|
+
if (typeof process === "undefined") {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
if (process.env?.BITFAB_DISABLE_CODE_CHANGE_CAPTURE) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
const fromEnv = await readCodeChangeFile();
|
|
341
|
+
if (fromEnv) {
|
|
342
|
+
return fromEnv;
|
|
343
|
+
}
|
|
344
|
+
return captureCodeChangeFromGit(process.cwd?.() ?? ".", label);
|
|
345
|
+
}
|
|
346
|
+
async function readCodeChangeFile() {
|
|
347
|
+
const path = process.env?.BITFAB_CODE_CHANGE_PATH;
|
|
348
|
+
if (!path) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
const { readFile } = await import("fs/promises");
|
|
353
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
354
|
+
const files = Array.isArray(parsed?.files) && parsed.files.every(
|
|
355
|
+
(f) => typeof f === "object" && f !== null && !Array.isArray(f)
|
|
356
|
+
) ? parsed.files : void 0;
|
|
357
|
+
const description = typeof parsed?.description === "string" ? parsed.description : void 0;
|
|
358
|
+
if (!files && description === void 0) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
return { description, files };
|
|
362
|
+
} catch {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async function captureCodeChangeFromGit(cwd, label) {
|
|
367
|
+
let execFile;
|
|
368
|
+
let readFile;
|
|
369
|
+
try {
|
|
370
|
+
;
|
|
371
|
+
({ execFile } = await import("child_process"));
|
|
372
|
+
({ readFile } = await import("fs/promises"));
|
|
373
|
+
} catch {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
const git = (dir, args) => new Promise((resolve) => {
|
|
377
|
+
execFile(
|
|
378
|
+
"git",
|
|
379
|
+
args,
|
|
380
|
+
// 30s timeout so a hung git (e.g. a network-touching ref op) can't
|
|
381
|
+
// block the whole replay indefinitely.
|
|
382
|
+
{ cwd: dir, maxBuffer: 64 * 1024 * 1024, timeout: 3e4 },
|
|
383
|
+
(err, stdout) => resolve(err ? null : stdout)
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
try {
|
|
387
|
+
const root = (await git(cwd, ["rev-parse", "--show-toplevel"]))?.trim();
|
|
388
|
+
if (!root) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
const resolved = await resolveBase(git, root);
|
|
392
|
+
if (!resolved) {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
const { base, fromTrunk } = resolved;
|
|
396
|
+
const blobBytes = async (ref, path) => {
|
|
397
|
+
const out = await git(root, ["cat-file", "-s", `${ref}:${path}`]);
|
|
398
|
+
const n = out ? Number.parseInt(out.trim(), 10) : Number.NaN;
|
|
399
|
+
return Number.isFinite(n) ? n : 0;
|
|
400
|
+
};
|
|
401
|
+
const workingBytes = async (path) => {
|
|
402
|
+
try {
|
|
403
|
+
const { stat } = await import("fs/promises");
|
|
404
|
+
const { join } = await import("path");
|
|
405
|
+
return (await stat(join(root, path))).size;
|
|
406
|
+
} catch {
|
|
407
|
+
return 0;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const tracked = await git(root, [
|
|
411
|
+
"diff",
|
|
412
|
+
"--name-status",
|
|
413
|
+
"--no-renames",
|
|
414
|
+
"-z",
|
|
415
|
+
base,
|
|
416
|
+
"--",
|
|
417
|
+
":!.bitfab"
|
|
418
|
+
]);
|
|
419
|
+
const untracked = await git(root, [
|
|
420
|
+
"ls-files",
|
|
421
|
+
"--others",
|
|
422
|
+
"--exclude-standard",
|
|
423
|
+
"-z",
|
|
424
|
+
"--",
|
|
425
|
+
":!.bitfab"
|
|
426
|
+
]);
|
|
427
|
+
const entries = [
|
|
428
|
+
...parseNameStatusZ(tracked ?? ""),
|
|
429
|
+
...(untracked ?? "").split(NUL).filter((p) => p.length > 0).map((path) => ({ status: "A", path }))
|
|
430
|
+
];
|
|
431
|
+
if (entries.length === 0) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
const files = [];
|
|
435
|
+
let totalBytes = 0;
|
|
436
|
+
for (const { status, path } of entries) {
|
|
437
|
+
if (files.length >= MAX_FILES) {
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
const beforeBytes = status === "A" ? 0 : await blobBytes(base, path);
|
|
441
|
+
const afterBytes = status === "D" ? 0 : await workingBytes(path);
|
|
442
|
+
if (beforeBytes > MAX_FILE_BYTES || afterBytes > MAX_FILE_BYTES) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const before = (status === "A" ? "" : await git(root, ["show", `${base}:${path}`]) ?? "").replace(/\r\n/g, "\n");
|
|
446
|
+
const after = (status === "D" ? "" : await readWorkingFile(readFile, root, path)).replace(/\r\n/g, "\n");
|
|
447
|
+
if (before === after) {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
const size = Buffer.byteLength(before, "utf8") + Buffer.byteLength(after, "utf8");
|
|
451
|
+
if (totalBytes + size > MAX_TOTAL_BYTES || looksBinary(before) || looksBinary(after)) {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
totalBytes += size;
|
|
455
|
+
files.push({ path, before, after });
|
|
456
|
+
}
|
|
457
|
+
if (files.length === 0) {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
const subject = (await git(root, ["log", "-1", "--format=%s", "HEAD"]))?.trim();
|
|
461
|
+
const fileWord = files.length === 1 ? "file" : "files";
|
|
462
|
+
const head = label?.trim() || subject || "Working-tree change";
|
|
463
|
+
const against = fromTrunk ? "vs trunk" : "uncommitted (vs HEAD)";
|
|
464
|
+
return {
|
|
465
|
+
description: `${head} (${files.length} ${fileWord} changed ${against})`,
|
|
466
|
+
files
|
|
467
|
+
};
|
|
468
|
+
} catch {
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async function resolveBase(git, root) {
|
|
473
|
+
const forced = process.env?.BITFAB_CODE_CHANGE_BASE;
|
|
474
|
+
if (forced && await refExists(git, root, forced)) {
|
|
475
|
+
const base = (await git(root, ["merge-base", "HEAD", forced]))?.trim() || (await git(root, ["rev-parse", "--verify", forced]))?.trim() || null;
|
|
476
|
+
return base ? { base, fromTrunk: true } : null;
|
|
477
|
+
}
|
|
478
|
+
for (const candidate of TRUNK_CANDIDATES) {
|
|
479
|
+
if (!await refExists(git, root, candidate)) {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
const mb = (await git(root, ["merge-base", "HEAD", candidate]))?.trim();
|
|
483
|
+
if (mb) {
|
|
484
|
+
return { base: mb, fromTrunk: true };
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return await refExists(git, root, "HEAD") ? { base: "HEAD", fromTrunk: false } : null;
|
|
488
|
+
}
|
|
489
|
+
async function refExists(git, root, ref) {
|
|
490
|
+
return await git(root, ["rev-parse", "--verify", `${ref}^{object}`]) !== null;
|
|
491
|
+
}
|
|
492
|
+
async function readWorkingFile(readFile, root, path) {
|
|
493
|
+
try {
|
|
494
|
+
const { join } = await import("path");
|
|
495
|
+
return await readFile(join(root, path), "utf8");
|
|
496
|
+
} catch {
|
|
497
|
+
return "";
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function parseNameStatusZ(raw) {
|
|
501
|
+
const parts = raw.split(NUL).filter((p) => p.length > 0);
|
|
502
|
+
const out = [];
|
|
503
|
+
for (let i = 0; i + 1 < parts.length; i += 2) {
|
|
504
|
+
out.push({ status: parts[i].charAt(0), path: parts[i + 1] });
|
|
505
|
+
}
|
|
506
|
+
return out;
|
|
507
|
+
}
|
|
508
|
+
function looksBinary(s) {
|
|
509
|
+
return s.slice(0, 8e3).includes(NUL);
|
|
510
|
+
}
|
|
511
|
+
var MAX_FILES, MAX_FILE_BYTES, MAX_TOTAL_BYTES, TRUNK_CANDIDATES, NUL;
|
|
512
|
+
var init_codeChange = __esm({
|
|
513
|
+
"src/codeChange.ts"() {
|
|
514
|
+
"use strict";
|
|
515
|
+
MAX_FILES = 60;
|
|
516
|
+
MAX_FILE_BYTES = 5e5;
|
|
517
|
+
MAX_TOTAL_BYTES = 2e6;
|
|
518
|
+
TRUNK_CANDIDATES = [
|
|
519
|
+
"origin/HEAD",
|
|
520
|
+
"origin/main",
|
|
521
|
+
"origin/master",
|
|
522
|
+
"main",
|
|
523
|
+
"master"
|
|
524
|
+
];
|
|
525
|
+
NUL = String.fromCharCode(0);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
332
529
|
// src/replay.ts
|
|
333
530
|
var replay_exports = {};
|
|
334
531
|
__export(replay_exports, {
|
|
@@ -553,6 +750,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
553
750
|
}
|
|
554
751
|
}
|
|
555
752
|
await replayContextReady;
|
|
753
|
+
let codeChangeDescription = options?.codeChangeDescription;
|
|
754
|
+
let codeChangeFiles = options?.codeChangeFiles;
|
|
755
|
+
if (codeChangeDescription === void 0 && codeChangeFiles === void 0) {
|
|
756
|
+
const captured = await resolveAutoCodeChange(options?.name);
|
|
757
|
+
if (captured) {
|
|
758
|
+
codeChangeDescription = captured.description;
|
|
759
|
+
codeChangeFiles = captured.files;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
556
762
|
const {
|
|
557
763
|
testRunId,
|
|
558
764
|
testRunUrl,
|
|
@@ -564,12 +770,13 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
564
770
|
options?.traceIds ? void 0 : options?.limit ?? 5,
|
|
565
771
|
options?.traceIds,
|
|
566
772
|
options?.name,
|
|
567
|
-
|
|
568
|
-
|
|
773
|
+
codeChangeDescription,
|
|
774
|
+
codeChangeFiles,
|
|
569
775
|
options?.environment !== void 0,
|
|
570
776
|
// includeDbBranchLease
|
|
571
777
|
options?.experimentGroupId,
|
|
572
|
-
options?.datasetId
|
|
778
|
+
options?.datasetId,
|
|
779
|
+
options?.graderIds
|
|
573
780
|
);
|
|
574
781
|
const mockStrategy = options?.mock ?? "marked";
|
|
575
782
|
const maxConcurrency = options?.maxConcurrency ?? 10;
|
|
@@ -720,6 +927,7 @@ var BITFAB_PROGRESS_PREFIX;
|
|
|
720
927
|
var init_replay = __esm({
|
|
721
928
|
"src/replay.ts"() {
|
|
722
929
|
"use strict";
|
|
930
|
+
init_codeChange();
|
|
723
931
|
init_errors();
|
|
724
932
|
init_mockOverride();
|
|
725
933
|
init_randomUuid();
|
|
@@ -755,7 +963,7 @@ __export(index_exports, {
|
|
|
755
963
|
module.exports = __toCommonJS(index_exports);
|
|
756
964
|
|
|
757
965
|
// src/version.generated.ts
|
|
758
|
-
var __version__ = "0.
|
|
966
|
+
var __version__ = "0.30.1";
|
|
759
967
|
|
|
760
968
|
// src/constants.ts
|
|
761
969
|
var DEFAULT_SERVICE_URL = "https://bitfab.ai";
|
|
@@ -1114,7 +1322,7 @@ var HttpClient = class {
|
|
|
1114
1322
|
* Start a replay session by fetching historical traces.
|
|
1115
1323
|
* Blocking call - creates a test run and returns lightweight item references.
|
|
1116
1324
|
*/
|
|
1117
|
-
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId) {
|
|
1325
|
+
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId, graderIds) {
|
|
1118
1326
|
const payload = { traceFunctionKey };
|
|
1119
1327
|
if (limit !== void 0) {
|
|
1120
1328
|
payload.limit = limit;
|
|
@@ -1140,6 +1348,9 @@ var HttpClient = class {
|
|
|
1140
1348
|
if (datasetId !== void 0) {
|
|
1141
1349
|
payload.datasetId = datasetId;
|
|
1142
1350
|
}
|
|
1351
|
+
if (graderIds !== void 0) {
|
|
1352
|
+
payload.graderIds = graderIds;
|
|
1353
|
+
}
|
|
1143
1354
|
const timeout = includeDbBranchLease ? 18e4 : 3e4;
|
|
1144
1355
|
return this.request("/api/sdk/replay/start", payload, {
|
|
1145
1356
|
timeout
|