@fern-api/generator-cli 0.9.25 → 0.9.26

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.
Files changed (2) hide show
  1. package/dist/cli.js +133 -2
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -22126,7 +22126,7 @@ var init_diff3 = __esm({
22126
22126
  }
22127
22127
  });
22128
22128
 
22129
- // ../../node_modules/.pnpm/@fern-api+replay@0.14.1/node_modules/@fern-api/replay/dist/index.js
22129
+ // ../../node_modules/.pnpm/@fern-api+replay@0.15.0/node_modules/@fern-api/replay/dist/index.js
22130
22130
  var dist_exports = {};
22131
22131
  __export(dist_exports, {
22132
22132
  CREDENTIAL_PATTERNS: () => CREDENTIAL_PATTERNS,
@@ -23630,7 +23630,7 @@ function status(outputDir) {
23630
23630
  }
23631
23631
  var import_promises3, import_os, import_path7, import_fs6, import_path8, import_yaml, import_crypto, import_promises4, import_os2, import_path9, import_fs7, import_promises5, import_path10, import_promises6, import_os3, import_path11, import_child_process3, import_crypto2, import_fs8, import_path12, import_yaml2, import_crypto3, import_fs9, import_path13, import_fs10, import_promises7, import_path14, __defProp3, __getOwnPropNames3, __esm3, __export3, GitClient_exports, GitClient, init_GitClient, HybridReconstruction_exports, init_HybridReconstruction, PatchApplyTheirs_exports, init_PatchApplyTheirs, CREDENTIAL_PATTERNS, SENSITIVE_FILE_PATTERNS, FERN_BOT_NAME, FERN_BOT_EMAIL, FERN_BOT_LOGIN, FERN_SUPPORT_NAMES, LOCKFILE_HEADER, LockfileNotFoundError, LockfileManager, CONFLICT_OPENER, CONFLICT_SEPARATOR, CONFLICT_CLOSER, BINARY_EXTENSIONS, ReplayApplicator, INFRASTRUCTURE_FILES, ReplayDetector, ReplayCommitter, ReplayService, FernignoreMigrator, REPLAY_FERNIGNORE_ENTRIES, GITATTRIBUTES_ENTRIES, EMPTY_RESULT;
23632
23632
  var init_dist3 = __esm({
23633
- "../../node_modules/.pnpm/@fern-api+replay@0.14.1/node_modules/@fern-api/replay/dist/index.js"() {
23633
+ "../../node_modules/.pnpm/@fern-api+replay@0.15.0/node_modules/@fern-api/replay/dist/index.js"() {
23634
23634
  "use strict";
23635
23635
  init_esm4();
23636
23636
  import_promises3 = require("fs/promises");
@@ -26243,6 +26243,66 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
26243
26243
  }
26244
26244
  return snapshot;
26245
26245
  }
26246
+ /**
26247
+ * FER-10203: returns true when a `[fern-autoversion]` commit between
26248
+ * `fromSha` and `toSha` touched any of the patch's `files`. Re-classifies
26249
+ * each grep match via `isGenerationCommit` so a customer commit that
26250
+ * merely quotes the marker in its body doesn't false-positive.
26251
+ */
26252
+ async hasAutoversionContamination(fromSha, toSha, files) {
26253
+ if (files.length === 0) return false;
26254
+ const log = await this.git.exec([
26255
+ "log",
26256
+ `${fromSha}..${toSha}`,
26257
+ "--grep=\\[fern-autoversion\\]",
26258
+ "--extended-regexp",
26259
+ "--format=%H%x09%aN%x09%ae%x09%s",
26260
+ "--",
26261
+ ...files
26262
+ ]).catch(() => "");
26263
+ if (!log.trim()) return false;
26264
+ for (const line of log.trim().split("\n")) {
26265
+ const [sha, authorName, authorEmail, message] = line.split(" ");
26266
+ if (sha == null) continue;
26267
+ if (isGenerationCommit({
26268
+ sha,
26269
+ authorName: authorName ?? "",
26270
+ authorEmail: authorEmail ?? "",
26271
+ message: message ?? ""
26272
+ })) {
26273
+ return true;
26274
+ }
26275
+ }
26276
+ return false;
26277
+ }
26278
+ /**
26279
+ * FER-10203: rebuild patch_content from `theirs_snapshot` (captured at
26280
+ * detection time, predates pipeline content) rather than a HEAD-relative
26281
+ * diff. Returns `null` when snapshot is missing or doesn't cover every
26282
+ * file in `patch.files` — caller leaves the patch unchanged.
26283
+ */
26284
+ async computeSnapshotBasedPatchDiff(patch, currentGen) {
26285
+ if (!patch.theirs_snapshot) return null;
26286
+ if (Object.keys(patch.theirs_snapshot).length === 0) return null;
26287
+ for (const file of patch.files) {
26288
+ if (patch.theirs_snapshot[file] == null) return null;
26289
+ }
26290
+ const fragments = [];
26291
+ for (const file of patch.files) {
26292
+ const theirsContent = patch.theirs_snapshot[file];
26293
+ const oursContent = await this.git.showFile(currentGen, file).catch(() => null);
26294
+ if (oursContent == null) {
26295
+ fragments.push(synthesizeNewFileDiff(file, theirsContent));
26296
+ continue;
26297
+ }
26298
+ if (oursContent === theirsContent) continue;
26299
+ const fileDiff = await unifiedDiff(oursContent, theirsContent, file);
26300
+ if (fileDiff.trim()) {
26301
+ fragments.push(fileDiff);
26302
+ }
26303
+ }
26304
+ return fragments.join("");
26305
+ }
26246
26306
  /**
26247
26307
  * Read THEIRS snapshot from a git tree-ish (commit, branch, or tree).
26248
26308
  * Used when the diff that produced `patch_content` was relative to that
@@ -26508,6 +26568,41 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
26508
26568
  }
26509
26569
  if (patch.base_generation === currentGen) {
26510
26570
  try {
26571
+ const contaminated = await this.hasAutoversionContamination(
26572
+ currentGen,
26573
+ "HEAD",
26574
+ patch.files
26575
+ );
26576
+ if (contaminated) {
26577
+ const snapshotDiff = await this.computeSnapshotBasedPatchDiff(
26578
+ patch,
26579
+ currentGen
26580
+ );
26581
+ if (snapshotDiff === null) continue;
26582
+ if (!snapshotDiff.trim()) {
26583
+ if (await allPatchFilesUserOwned(patch)) continue;
26584
+ this.lockManager.removePatch(patch.id);
26585
+ contentRefreshed++;
26586
+ continue;
26587
+ }
26588
+ const snapHasStaleMarkers = snapshotDiff.split("\n").some(
26589
+ (l) => l.startsWith("+<<<<<<< Generated") || l.startsWith("+>>>>>>> Your customization")
26590
+ );
26591
+ if (snapHasStaleMarkers) continue;
26592
+ const isProtectedSnap = patch.files.some(
26593
+ (file) => file === ".fernignore" || fernignorePatterns.some((p2) => file === p2 || minimatch(file, p2))
26594
+ );
26595
+ if (isProtectedSnap) continue;
26596
+ const snapHash = this.detector.computeContentHash(snapshotDiff);
26597
+ if (snapHash !== patch.content_hash) {
26598
+ this.lockManager.updatePatch(patch.id, {
26599
+ patch_content: snapshotDiff,
26600
+ content_hash: snapHash
26601
+ });
26602
+ contentRefreshed++;
26603
+ }
26604
+ continue;
26605
+ }
26511
26606
  const ppResult = await computePerPatchDiffWithFallback(
26512
26607
  patch,
26513
26608
  (f) => this.git.showFile(currentGen, f),
@@ -26559,6 +26654,42 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
26559
26654
  try {
26560
26655
  const markerFiles = await this.git.exec(["grep", "-l", "<<<<<<< Generated", "HEAD", "--", ...patch.files]).catch(() => "");
26561
26656
  if (markerFiles.trim()) continue;
26657
+ const contaminated = await this.hasAutoversionContamination(
26658
+ currentGen,
26659
+ "HEAD",
26660
+ patch.files
26661
+ );
26662
+ if (contaminated) {
26663
+ const snapshotDiff = await this.computeSnapshotBasedPatchDiff(
26664
+ patch,
26665
+ currentGen
26666
+ );
26667
+ if (snapshotDiff === null) continue;
26668
+ if (!snapshotDiff.trim()) {
26669
+ if (await allPatchFilesUserOwned(patch)) {
26670
+ try {
26671
+ this.lockManager.updatePatch(patch.id, { base_generation: currentGen });
26672
+ } catch {
26673
+ }
26674
+ continue;
26675
+ }
26676
+ this.lockManager.removePatch(patch.id);
26677
+ conflictAbsorbed++;
26678
+ continue;
26679
+ }
26680
+ const snapHasStaleMarkers = snapshotDiff.split("\n").some(
26681
+ (l) => l.startsWith("+<<<<<<< Generated") || l.startsWith("+>>>>>>> Your customization")
26682
+ );
26683
+ if (snapHasStaleMarkers) continue;
26684
+ const snapHash = this.detector.computeContentHash(snapshotDiff);
26685
+ this.lockManager.updatePatch(patch.id, {
26686
+ base_generation: currentGen,
26687
+ patch_content: snapshotDiff,
26688
+ content_hash: snapHash
26689
+ });
26690
+ conflictResolved++;
26691
+ continue;
26692
+ }
26562
26693
  const ppResult = await computePerPatchDiffWithFallback(
26563
26694
  patch,
26564
26695
  (f) => this.git.showFile(currentGen, f),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fern-api/generator-cli",
3
- "version": "0.9.25",
3
+ "version": "0.9.26",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fern-api/fern.git",
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "@boundaryml/baml": "^0.219.0",
26
- "@fern-api/replay": "0.14.1",
26
+ "@fern-api/replay": "0.15.0",
27
27
  "@octokit/rest": "^22.0.1",
28
28
  "es-toolkit": "^1.45.1",
29
29
  "semver": "^7.6.3",