@fern-api/replay 0.19.0 → 0.19.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/cli.cjs CHANGED
@@ -16449,6 +16449,51 @@ async function buildMergePlan(args) {
16449
16449
  // src/applicator/runMergeAndRecord.ts
16450
16450
  var import_promises2 = require("fs/promises");
16451
16451
  var import_node_path5 = require("path");
16452
+
16453
+ // src/autoversion.ts
16454
+ var AUTOVERSION_PLACEHOLDERS = [
16455
+ "v0.0.0-fern-placeholder",
16456
+ // Go -- a superstring of the default; check first
16457
+ "0.0.0-fern-placeholder",
16458
+ // TS/Java/C#/Ruby/PHP/Rust/default
16459
+ "0.0.0.dev0"
16460
+ // Python (PEP 440 -- Poetry rejects hyphens)
16461
+ ];
16462
+ var VERSION_TOKEN = /(?:v)?\d+\.\d+\.\d+[0-9A-Za-z.+-]*/g;
16463
+ var VERSION_MASK = "@@FERN_VERSION@@";
16464
+ function maskVersions(line) {
16465
+ return line.replace(VERSION_TOKEN, VERSION_MASK);
16466
+ }
16467
+ function containsPlaceholder(content) {
16468
+ if (!content) return false;
16469
+ return AUTOVERSION_PLACEHOLDERS.some((p) => content.includes(p));
16470
+ }
16471
+ function remapManagedLines(genBase, target, theirs) {
16472
+ if (!containsPlaceholder(genBase)) return theirs;
16473
+ const managedMasks = /* @__PURE__ */ new Set();
16474
+ for (const line of genBase.split("\n")) {
16475
+ if (AUTOVERSION_PLACEHOLDERS.some((p) => line.includes(p))) managedMasks.add(maskVersions(line));
16476
+ }
16477
+ if (managedMasks.size === 0) return theirs;
16478
+ const targetByMask = /* @__PURE__ */ new Map();
16479
+ for (const line of target.split("\n")) {
16480
+ const mask = maskVersions(line);
16481
+ if (managedMasks.has(mask) && !targetByMask.has(mask)) targetByMask.set(mask, line);
16482
+ }
16483
+ return theirs.split("\n").map((line) => {
16484
+ const mask = maskVersions(line);
16485
+ const replacement = managedMasks.has(mask) ? targetByMask.get(mask) : void 0;
16486
+ return replacement !== void 0 && replacement !== line ? replacement : line;
16487
+ }).join("\n");
16488
+ }
16489
+ function neutralizeAutoversionTheirs(genBase, theirs) {
16490
+ return remapManagedLines(genBase, genBase, theirs);
16491
+ }
16492
+ function alignAutoversionToOurs(genBase, ours, theirs) {
16493
+ return remapManagedLines(genBase, ours, theirs);
16494
+ }
16495
+
16496
+ // src/applicator/runMergeAndRecord.ts
16452
16497
  async function runMergeAndRecord(args) {
16453
16498
  const { plan, inputs, patch, accumulator } = args;
16454
16499
  const { resolvedPath, metadata, oursPath, ours } = inputs;
@@ -16479,11 +16524,13 @@ async function runMergeAndRecord(args) {
16479
16524
  accumulator.recordMerge(resolvedPath, merged2.content, patch.base_generation);
16480
16525
  return { file: resolvedPath, status: "merged", ...unreachableFlag };
16481
16526
  }
16482
- const merged = threeWayMerge(plan.mergeBase, ours, plan.theirs);
16527
+ const mergeBase = inputs.base != null ? alignAutoversionToOurs(inputs.base, ours, plan.mergeBase) : plan.mergeBase;
16528
+ const theirs = inputs.base != null ? alignAutoversionToOurs(inputs.base, ours, plan.theirs) : plan.theirs;
16529
+ const merged = threeWayMerge(mergeBase, ours, theirs);
16483
16530
  await (0, import_promises2.mkdir)((0, import_node_path5.dirname)(oursPath), { recursive: true });
16484
16531
  await (0, import_promises2.writeFile)(oursPath, merged.content);
16485
16532
  if (merged.hasConflicts) {
16486
- accumulator.recordConflict(resolvedPath, plan.theirs, patch.base_generation);
16533
+ accumulator.recordConflict(resolvedPath, theirs, patch.base_generation);
16487
16534
  return {
16488
16535
  file: resolvedPath,
16489
16536
  status: "conflict",
@@ -16492,7 +16539,7 @@ async function runMergeAndRecord(args) {
16492
16539
  conflictMetadata: metadata
16493
16540
  };
16494
16541
  }
16495
- accumulator.recordMerge(resolvedPath, plan.theirs, patch.base_generation);
16542
+ accumulator.recordMerge(resolvedPath, theirs, patch.base_generation);
16496
16543
  return {
16497
16544
  file: resolvedPath,
16498
16545
  status: "merged",
@@ -18270,7 +18317,9 @@ var ReplayService = class {
18270
18317
  if (baseChanged) repointed++;
18271
18318
  continue;
18272
18319
  }
18273
- const diff = await this.git.exec(["diff", currentGenSha, "--", ...patch.files]).catch(() => null);
18320
+ const rawDiff = await this.git.exec(["diff", currentGenSha, "--", ...patch.files]).catch(() => null);
18321
+ const neutralizedRebase = rawDiff != null && containsPlaceholder(rawDiff) ? await this.computeAutoversionNeutralizedRebase(patch.files, currentGenSha) : null;
18322
+ const diff = neutralizedRebase ? neutralizedRebase.diff : rawDiff;
18274
18323
  if (diff === null) continue;
18275
18324
  if (!diff.trim()) {
18276
18325
  if (hasProtectedFiles) {
@@ -18326,7 +18375,7 @@ var ReplayService = class {
18326
18375
  patch.base_generation = currentGenSha;
18327
18376
  patch.patch_content = diff;
18328
18377
  patch.content_hash = newContentHash;
18329
- const snapshot = await this.readSnapshotFromDisk(patch.files);
18378
+ const snapshot = neutralizedRebase ? neutralizedRebase.snapshot : await this.readSnapshotFromDisk(patch.files);
18330
18379
  const snapshotIsNonEmpty = Object.keys(snapshot).length > 0;
18331
18380
  if (snapshotIsNonEmpty) {
18332
18381
  patch.theirs_snapshot = snapshot;
@@ -18366,6 +18415,47 @@ var ReplayService = class {
18366
18415
  }
18367
18416
  return snapshot;
18368
18417
  }
18418
+ /**
18419
+ * Autoversion-aware replacement for the naive `git diff currentGen -- files`
18420
+ * + on-disk snapshot used when rebasing a cleanly-applied patch.
18421
+ *
18422
+ * The generation tree (BASE) still carries the placeholder on version lines
18423
+ * (autoversion runs after `[fern-generated]`), but the working tree holds
18424
+ * autoversion's resolved semver. A raw diff/snapshot would fold the
18425
+ * placeholder->semver swap into the customer's patch_content/theirs_snapshot,
18426
+ * pinning a stale version. This re-derives both against a version-neutralized
18427
+ * THEIRS so the pipeline-owned version line never enters the patch.
18428
+ *
18429
+ * Returns null when no file is under autoversion (fixed `--version`, i.e. no
18430
+ * placeholder in the generation tree) — callers keep their existing behavior.
18431
+ */
18432
+ async computeAutoversionNeutralizedRebase(files, currentGenSha) {
18433
+ const genByFile = /* @__PURE__ */ new Map();
18434
+ let anyPlaceholder = false;
18435
+ for (const file of files) {
18436
+ const gen = await this.git.showFile(currentGenSha, file).catch(() => null);
18437
+ genByFile.set(file, gen);
18438
+ if (containsPlaceholder(gen)) anyPlaceholder = true;
18439
+ }
18440
+ if (!anyPlaceholder) return null;
18441
+ const snapshot = {};
18442
+ const fragments = [];
18443
+ for (const file of files) {
18444
+ if (isBinaryFile(file)) continue;
18445
+ const disk = await (0, import_promises6.readFile)((0, import_node_path10.join)(this.outputDir, file), "utf-8").catch(() => null);
18446
+ if (disk == null) continue;
18447
+ const gen = genByFile.get(file) ?? null;
18448
+ const neutralized = gen != null ? neutralizeAutoversionTheirs(gen, disk) : disk;
18449
+ snapshot[file] = neutralized;
18450
+ if (gen == null) {
18451
+ fragments.push(synthesizeNewFileDiff(file, neutralized));
18452
+ } else if (gen !== neutralized) {
18453
+ const fileDiff = await unifiedDiff(gen, neutralized, file);
18454
+ if (fileDiff.trim()) fragments.push(fileDiff);
18455
+ }
18456
+ }
18457
+ return { diff: fragments.join(""), snapshot };
18458
+ }
18369
18459
  /**
18370
18460
  * Detects autoversion contamination — returns true when a
18371
18461
  * `[fern-autoversion]` commit between `fromSha` and `toSha` touched any