@bitfab/sdk 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
|
@@ -320,6 +320,203 @@ var init_replayContext = __esm({
|
|
|
320
320
|
}
|
|
321
321
|
});
|
|
322
322
|
|
|
323
|
+
// src/codeChange.ts
|
|
324
|
+
async function resolveAutoCodeChange(label) {
|
|
325
|
+
if (typeof process === "undefined") {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
if (process.env?.BITFAB_DISABLE_CODE_CHANGE_CAPTURE) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
const fromEnv = await readCodeChangeFile();
|
|
332
|
+
if (fromEnv) {
|
|
333
|
+
return fromEnv;
|
|
334
|
+
}
|
|
335
|
+
return captureCodeChangeFromGit(process.cwd?.() ?? ".", label);
|
|
336
|
+
}
|
|
337
|
+
async function readCodeChangeFile() {
|
|
338
|
+
const path = process.env?.BITFAB_CODE_CHANGE_PATH;
|
|
339
|
+
if (!path) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
const { readFile } = await import("fs/promises");
|
|
344
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
345
|
+
const files = Array.isArray(parsed?.files) && parsed.files.every(
|
|
346
|
+
(f) => typeof f === "object" && f !== null && !Array.isArray(f)
|
|
347
|
+
) ? parsed.files : void 0;
|
|
348
|
+
const description = typeof parsed?.description === "string" ? parsed.description : void 0;
|
|
349
|
+
if (!files && description === void 0) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
return { description, files };
|
|
353
|
+
} catch {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async function captureCodeChangeFromGit(cwd, label) {
|
|
358
|
+
let execFile;
|
|
359
|
+
let readFile;
|
|
360
|
+
try {
|
|
361
|
+
;
|
|
362
|
+
({ execFile } = await import("child_process"));
|
|
363
|
+
({ readFile } = await import("fs/promises"));
|
|
364
|
+
} catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
const git = (dir, args) => new Promise((resolve) => {
|
|
368
|
+
execFile(
|
|
369
|
+
"git",
|
|
370
|
+
args,
|
|
371
|
+
// 30s timeout so a hung git (e.g. a network-touching ref op) can't
|
|
372
|
+
// block the whole replay indefinitely.
|
|
373
|
+
{ cwd: dir, maxBuffer: 64 * 1024 * 1024, timeout: 3e4 },
|
|
374
|
+
(err, stdout) => resolve(err ? null : stdout)
|
|
375
|
+
);
|
|
376
|
+
});
|
|
377
|
+
try {
|
|
378
|
+
const root = (await git(cwd, ["rev-parse", "--show-toplevel"]))?.trim();
|
|
379
|
+
if (!root) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
const resolved = await resolveBase(git, root);
|
|
383
|
+
if (!resolved) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
const { base, fromTrunk } = resolved;
|
|
387
|
+
const blobBytes = async (ref, path) => {
|
|
388
|
+
const out = await git(root, ["cat-file", "-s", `${ref}:${path}`]);
|
|
389
|
+
const n = out ? Number.parseInt(out.trim(), 10) : Number.NaN;
|
|
390
|
+
return Number.isFinite(n) ? n : 0;
|
|
391
|
+
};
|
|
392
|
+
const workingBytes = async (path) => {
|
|
393
|
+
try {
|
|
394
|
+
const { stat } = await import("fs/promises");
|
|
395
|
+
const { join } = await import("path");
|
|
396
|
+
return (await stat(join(root, path))).size;
|
|
397
|
+
} catch {
|
|
398
|
+
return 0;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
const tracked = await git(root, [
|
|
402
|
+
"diff",
|
|
403
|
+
"--name-status",
|
|
404
|
+
"--no-renames",
|
|
405
|
+
"-z",
|
|
406
|
+
base,
|
|
407
|
+
"--",
|
|
408
|
+
":!.bitfab"
|
|
409
|
+
]);
|
|
410
|
+
const untracked = await git(root, [
|
|
411
|
+
"ls-files",
|
|
412
|
+
"--others",
|
|
413
|
+
"--exclude-standard",
|
|
414
|
+
"-z",
|
|
415
|
+
"--",
|
|
416
|
+
":!.bitfab"
|
|
417
|
+
]);
|
|
418
|
+
const entries = [
|
|
419
|
+
...parseNameStatusZ(tracked ?? ""),
|
|
420
|
+
...(untracked ?? "").split(NUL).filter((p) => p.length > 0).map((path) => ({ status: "A", path }))
|
|
421
|
+
];
|
|
422
|
+
if (entries.length === 0) {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
const files = [];
|
|
426
|
+
let totalBytes = 0;
|
|
427
|
+
for (const { status, path } of entries) {
|
|
428
|
+
if (files.length >= MAX_FILES) {
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
const beforeBytes = status === "A" ? 0 : await blobBytes(base, path);
|
|
432
|
+
const afterBytes = status === "D" ? 0 : await workingBytes(path);
|
|
433
|
+
if (beforeBytes > MAX_FILE_BYTES || afterBytes > MAX_FILE_BYTES) {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
const before = (status === "A" ? "" : await git(root, ["show", `${base}:${path}`]) ?? "").replace(/\r\n/g, "\n");
|
|
437
|
+
const after = (status === "D" ? "" : await readWorkingFile(readFile, root, path)).replace(/\r\n/g, "\n");
|
|
438
|
+
if (before === after) {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const size = Buffer.byteLength(before, "utf8") + Buffer.byteLength(after, "utf8");
|
|
442
|
+
if (totalBytes + size > MAX_TOTAL_BYTES || looksBinary(before) || looksBinary(after)) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
totalBytes += size;
|
|
446
|
+
files.push({ path, before, after });
|
|
447
|
+
}
|
|
448
|
+
if (files.length === 0) {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
const subject = (await git(root, ["log", "-1", "--format=%s", "HEAD"]))?.trim();
|
|
452
|
+
const fileWord = files.length === 1 ? "file" : "files";
|
|
453
|
+
const head = label?.trim() || subject || "Working-tree change";
|
|
454
|
+
const against = fromTrunk ? "vs trunk" : "uncommitted (vs HEAD)";
|
|
455
|
+
return {
|
|
456
|
+
description: `${head} (${files.length} ${fileWord} changed ${against})`,
|
|
457
|
+
files
|
|
458
|
+
};
|
|
459
|
+
} catch {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
async function resolveBase(git, root) {
|
|
464
|
+
const forced = process.env?.BITFAB_CODE_CHANGE_BASE;
|
|
465
|
+
if (forced && await refExists(git, root, forced)) {
|
|
466
|
+
const base = (await git(root, ["merge-base", "HEAD", forced]))?.trim() || (await git(root, ["rev-parse", "--verify", forced]))?.trim() || null;
|
|
467
|
+
return base ? { base, fromTrunk: true } : null;
|
|
468
|
+
}
|
|
469
|
+
for (const candidate of TRUNK_CANDIDATES) {
|
|
470
|
+
if (!await refExists(git, root, candidate)) {
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
const mb = (await git(root, ["merge-base", "HEAD", candidate]))?.trim();
|
|
474
|
+
if (mb) {
|
|
475
|
+
return { base: mb, fromTrunk: true };
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return await refExists(git, root, "HEAD") ? { base: "HEAD", fromTrunk: false } : null;
|
|
479
|
+
}
|
|
480
|
+
async function refExists(git, root, ref) {
|
|
481
|
+
return await git(root, ["rev-parse", "--verify", `${ref}^{object}`]) !== null;
|
|
482
|
+
}
|
|
483
|
+
async function readWorkingFile(readFile, root, path) {
|
|
484
|
+
try {
|
|
485
|
+
const { join } = await import("path");
|
|
486
|
+
return await readFile(join(root, path), "utf8");
|
|
487
|
+
} catch {
|
|
488
|
+
return "";
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function parseNameStatusZ(raw) {
|
|
492
|
+
const parts = raw.split(NUL).filter((p) => p.length > 0);
|
|
493
|
+
const out = [];
|
|
494
|
+
for (let i = 0; i + 1 < parts.length; i += 2) {
|
|
495
|
+
out.push({ status: parts[i].charAt(0), path: parts[i + 1] });
|
|
496
|
+
}
|
|
497
|
+
return out;
|
|
498
|
+
}
|
|
499
|
+
function looksBinary(s) {
|
|
500
|
+
return s.slice(0, 8e3).includes(NUL);
|
|
501
|
+
}
|
|
502
|
+
var MAX_FILES, MAX_FILE_BYTES, MAX_TOTAL_BYTES, TRUNK_CANDIDATES, NUL;
|
|
503
|
+
var init_codeChange = __esm({
|
|
504
|
+
"src/codeChange.ts"() {
|
|
505
|
+
"use strict";
|
|
506
|
+
MAX_FILES = 60;
|
|
507
|
+
MAX_FILE_BYTES = 5e5;
|
|
508
|
+
MAX_TOTAL_BYTES = 2e6;
|
|
509
|
+
TRUNK_CANDIDATES = [
|
|
510
|
+
"origin/HEAD",
|
|
511
|
+
"origin/main",
|
|
512
|
+
"origin/master",
|
|
513
|
+
"main",
|
|
514
|
+
"master"
|
|
515
|
+
];
|
|
516
|
+
NUL = String.fromCharCode(0);
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
323
520
|
// src/replay.ts
|
|
324
521
|
var replay_exports = {};
|
|
325
522
|
__export(replay_exports, {
|
|
@@ -544,6 +741,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
544
741
|
}
|
|
545
742
|
}
|
|
546
743
|
await replayContextReady;
|
|
744
|
+
let codeChangeDescription = options?.codeChangeDescription;
|
|
745
|
+
let codeChangeFiles = options?.codeChangeFiles;
|
|
746
|
+
if (codeChangeDescription === void 0 && codeChangeFiles === void 0) {
|
|
747
|
+
const captured = await resolveAutoCodeChange(options?.name);
|
|
748
|
+
if (captured) {
|
|
749
|
+
codeChangeDescription = captured.description;
|
|
750
|
+
codeChangeFiles = captured.files;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
547
753
|
const {
|
|
548
754
|
testRunId,
|
|
549
755
|
testRunUrl,
|
|
@@ -555,12 +761,13 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
555
761
|
options?.traceIds ? void 0 : options?.limit ?? 5,
|
|
556
762
|
options?.traceIds,
|
|
557
763
|
options?.name,
|
|
558
|
-
|
|
559
|
-
|
|
764
|
+
codeChangeDescription,
|
|
765
|
+
codeChangeFiles,
|
|
560
766
|
options?.environment !== void 0,
|
|
561
767
|
// includeDbBranchLease
|
|
562
768
|
options?.experimentGroupId,
|
|
563
|
-
options?.datasetId
|
|
769
|
+
options?.datasetId,
|
|
770
|
+
options?.graderIds
|
|
564
771
|
);
|
|
565
772
|
const mockStrategy = options?.mock ?? "marked";
|
|
566
773
|
const maxConcurrency = options?.maxConcurrency ?? 10;
|
|
@@ -711,6 +918,7 @@ var BITFAB_PROGRESS_PREFIX;
|
|
|
711
918
|
var init_replay = __esm({
|
|
712
919
|
"src/replay.ts"() {
|
|
713
920
|
"use strict";
|
|
921
|
+
init_codeChange();
|
|
714
922
|
init_errors();
|
|
715
923
|
init_mockOverride();
|
|
716
924
|
init_randomUuid();
|
|
@@ -746,7 +954,7 @@ __export(index_exports, {
|
|
|
746
954
|
module.exports = __toCommonJS(index_exports);
|
|
747
955
|
|
|
748
956
|
// src/version.generated.ts
|
|
749
|
-
var __version__ = "0.
|
|
957
|
+
var __version__ = "0.30.1";
|
|
750
958
|
|
|
751
959
|
// src/constants.ts
|
|
752
960
|
var DEFAULT_SERVICE_URL = "https://bitfab.ai";
|
|
@@ -1105,7 +1313,7 @@ var HttpClient = class {
|
|
|
1105
1313
|
* Start a replay session by fetching historical traces.
|
|
1106
1314
|
* Blocking call - creates a test run and returns lightweight item references.
|
|
1107
1315
|
*/
|
|
1108
|
-
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId) {
|
|
1316
|
+
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId, graderIds) {
|
|
1109
1317
|
const payload = { traceFunctionKey };
|
|
1110
1318
|
if (limit !== void 0) {
|
|
1111
1319
|
payload.limit = limit;
|
|
@@ -1131,6 +1339,9 @@ var HttpClient = class {
|
|
|
1131
1339
|
if (datasetId !== void 0) {
|
|
1132
1340
|
payload.datasetId = datasetId;
|
|
1133
1341
|
}
|
|
1342
|
+
if (graderIds !== void 0) {
|
|
1343
|
+
payload.graderIds = graderIds;
|
|
1344
|
+
}
|
|
1134
1345
|
const timeout = includeDbBranchLease ? 18e4 : 3e4;
|
|
1135
1346
|
return this.request("/api/sdk/replay/start", payload, {
|
|
1136
1347
|
timeout
|