@fern-api/generator-cli 0.9.18 → 0.9.20

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/api.d.ts CHANGED
@@ -507,10 +507,10 @@ interface ReplayInitResult {
507
507
  bootstrap: BootstrapResult;
508
508
  /** Raw lockfile YAML content, present when bootstrap succeeded and not dry-run */
509
509
  lockfileContent?: string;
510
- /** Raw replay.yml content, present only when fernignore migration created it */
511
- replayYmlContent?: string;
512
510
  /** Fernignore entries that the server should ensure exist */
513
511
  fernignoreEntries: string[];
512
+ /** Gitattributes entries the server should ensure exist (e.g. linguist-generated markers) */
513
+ gitattributesEntries: string[];
514
514
  /** Generated PR body markdown for the server to use */
515
515
  prBody?: string;
516
516
  }
@@ -523,8 +523,8 @@ interface ReplayInitResult {
523
523
  * Flow:
524
524
  * 1. Clone the SDK repo (read-only)
525
525
  * 2. Run bootstrap() to scan history and create lockfile
526
- * 3. Ensure .fernignore has replay entries
527
- * 4. Read lockfile content from disk and return it
526
+ * 3. Read lockfile content from disk
527
+ * 4. Return lockfile + fernignore/gitattributes entries for Fiddle to apply server-side
528
528
  */
529
529
  declare function replayInit(params: ReplayInitParams): Promise<ReplayInitResult>;
530
530
  interface BootstrapLogEntry {
package/dist/api.js CHANGED
@@ -10053,12 +10053,11 @@ function replayForget(params) {
10053
10053
 
10054
10054
  // src/replay/replay-init.ts
10055
10055
  var import_replay2 = require("@fern-api/replay");
10056
- var import_fs3 = require("fs");
10056
+ var import_fs2 = require("fs");
10057
10057
  var import_path4 = require("path");
10058
10058
  var import_tmp_promise2 = __toESM(require("tmp-promise"));
10059
10059
 
10060
10060
  // src/replay/fernignore.ts
10061
- var import_fs2 = require("fs");
10062
10061
  var import_promises2 = require("fs/promises");
10063
10062
  var import_path3 = require("path");
10064
10063
  var REPLAY_FERNIGNORE_ENTRIES = [".fern/replay.lock", ".fern/replay.yml", ".gitattributes"];
@@ -10093,32 +10092,6 @@ async function ensureGitattributesEntries(outputDir) {
10093
10092
  await (0, import_promises2.writeFile)(gitattributesPath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
10094
10093
  }
10095
10094
  }
10096
- function ensureReplayFernignoreEntriesSync(outputDir) {
10097
- const fernignorePath = (0, import_path3.join)(outputDir, ".fernignore");
10098
- let content = "";
10099
- if ((0, import_fs2.existsSync)(fernignorePath)) {
10100
- content = (0, import_fs2.readFileSync)(fernignorePath, "utf-8");
10101
- }
10102
- const lines = content.split("\n");
10103
- const toAdd = REPLAY_FERNIGNORE_ENTRIES.filter((entry) => !lines.some((line) => line.trim() === entry));
10104
- if (toAdd.length > 0) {
10105
- const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
10106
- (0, import_fs2.writeFileSync)(fernignorePath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
10107
- }
10108
- }
10109
- function ensureGitattributesEntriesSync(outputDir) {
10110
- const gitattributesPath = (0, import_path3.join)(outputDir, ".gitattributes");
10111
- let content = "";
10112
- if ((0, import_fs2.existsSync)(gitattributesPath)) {
10113
- content = (0, import_fs2.readFileSync)(gitattributesPath, "utf-8");
10114
- }
10115
- const lines = content.split("\n");
10116
- const toAdd = GITATTRIBUTES_ENTRIES.filter((entry) => !lines.some((line) => line.trim() === entry));
10117
- if (toAdd.length > 0) {
10118
- const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
10119
- (0, import_fs2.writeFileSync)(gitattributesPath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
10120
- }
10121
- }
10122
10095
 
10123
10096
  // src/replay/replay-init.ts
10124
10097
  async function replayInit(params) {
@@ -10138,22 +10111,18 @@ async function replayInit(params) {
10138
10111
  importHistory: params.importHistory
10139
10112
  });
10140
10113
  if (!bootstrapResult.generationCommit) {
10141
- return { bootstrap: bootstrapResult, fernignoreEntries: [] };
10114
+ return { bootstrap: bootstrapResult, fernignoreEntries: [], gitattributesEntries: [] };
10142
10115
  }
10143
10116
  if (dryRun) {
10144
- return { bootstrap: bootstrapResult, fernignoreEntries: [] };
10117
+ return { bootstrap: bootstrapResult, fernignoreEntries: [], gitattributesEntries: [] };
10145
10118
  }
10146
- ensureReplayFernignoreEntriesSync(repoPath);
10147
- ensureGitattributesEntriesSync(repoPath);
10148
10119
  const lockfilePath = (0, import_path4.join)(repoPath, ".fern", "replay.lock");
10149
- const lockfileContent = (0, import_fs3.existsSync)(lockfilePath) ? (0, import_fs3.readFileSync)(lockfilePath, "utf-8") : void 0;
10150
- const replayYmlPath = (0, import_path4.join)(repoPath, ".fern", "replay.yml");
10151
- const replayYmlContent = (0, import_fs3.existsSync)(replayYmlPath) ? (0, import_fs3.readFileSync)(replayYmlPath, "utf-8") : void 0;
10120
+ const lockfileContent = (0, import_fs2.existsSync)(lockfilePath) ? (0, import_fs2.readFileSync)(lockfilePath, "utf-8") : void 0;
10152
10121
  return {
10153
10122
  bootstrap: bootstrapResult,
10154
10123
  lockfileContent,
10155
- replayYmlContent,
10156
10124
  fernignoreEntries: REPLAY_FERNIGNORE_ENTRIES,
10125
+ gitattributesEntries: GITATTRIBUTES_ENTRIES,
10157
10126
  prBody: buildPrBody(bootstrapResult)
10158
10127
  };
10159
10128
  }
@@ -10274,7 +10243,7 @@ async function replayResolve(params) {
10274
10243
  // src/replay/replay-run.ts
10275
10244
  var import_replay4 = require("@fern-api/replay");
10276
10245
  var import_child_process3 = require("child_process");
10277
- var import_fs4 = require("fs");
10246
+ var import_fs3 = require("fs");
10278
10247
  var import_path5 = require("path");
10279
10248
  async function replayPrepare(params) {
10280
10249
  const {
@@ -10287,7 +10256,7 @@ async function replayPrepare(params) {
10287
10256
  logger
10288
10257
  } = params;
10289
10258
  const lockfilePath = (0, import_path5.join)(outputDir, ".fern", "replay.lock");
10290
- if (!(0, import_fs4.existsSync)(lockfilePath)) {
10259
+ if (!(0, import_fs3.existsSync)(lockfilePath)) {
10291
10260
  return null;
10292
10261
  }
10293
10262
  const baseBranchHead = gitRevParse(outputDir, "HEAD");
@@ -10485,6 +10454,7 @@ async function submitReplayInit(params) {
10485
10454
  repo,
10486
10455
  lockfileContents: initResult.lockfileContent,
10487
10456
  fernignoreEntries: initResult.fernignoreEntries,
10457
+ gitattributesEntries: initResult.gitattributesEntries,
10488
10458
  prBody: initResult.prBody
10489
10459
  })
10490
10460
  });
package/dist/cli.js CHANGED
@@ -22028,7 +22028,7 @@ var init_diff3 = __esm({
22028
22028
  }
22029
22029
  });
22030
22030
 
22031
- // ../../node_modules/.pnpm/@fern-api+replay@0.12.0/node_modules/@fern-api/replay/dist/index.js
22031
+ // ../../node_modules/.pnpm/@fern-api+replay@0.13.0/node_modules/@fern-api/replay/dist/index.js
22032
22032
  var dist_exports = {};
22033
22033
  __export(dist_exports, {
22034
22034
  CREDENTIAL_PATTERNS: () => CREDENTIAL_PATTERNS,
@@ -23043,7 +23043,7 @@ function status(outputDir) {
23043
23043
  }
23044
23044
  var import_fs6, import_path7, import_yaml, import_crypto, import_promises3, import_os, import_path8, import_fs7, import_path9, import_crypto2, import_fs8, import_path10, import_yaml2, import_crypto3, import_fs9, import_path11, import_fs10, __defProp3, __getOwnPropNames3, __esm3, __export3, GitClient_exports, GitClient, init_GitClient, HybridReconstruction_exports, init_HybridReconstruction, CREDENTIAL_PATTERNS, SENSITIVE_FILE_PATTERNS, FERN_BOT_NAME, FERN_BOT_EMAIL, FERN_BOT_LOGIN, FERN_SUPPORT_NAMES, LOCKFILE_HEADER, LockfileNotFoundError, LockfileManager, INFRASTRUCTURE_FILES, ReplayDetector, CONFLICT_OPENER, CONFLICT_SEPARATOR, CONFLICT_CLOSER, BINARY_EXTENSIONS, ReplayApplicator, ReplayCommitter, ReplayService, FernignoreMigrator, REPLAY_FERNIGNORE_ENTRIES, GITATTRIBUTES_ENTRIES, EMPTY_RESULT;
23045
23045
  var init_dist3 = __esm({
23046
- "../../node_modules/.pnpm/@fern-api+replay@0.12.0/node_modules/@fern-api/replay/dist/index.js"() {
23046
+ "../../node_modules/.pnpm/@fern-api+replay@0.13.0/node_modules/@fern-api/replay/dist/index.js"() {
23047
23047
  "use strict";
23048
23048
  init_esm4();
23049
23049
  import_fs6 = require("fs");
@@ -23457,17 +23457,57 @@ var init_dist3 = __esm({
23457
23457
  }
23458
23458
  const isAncestor = await this.git.isAncestor(lastGen.commit_sha, "HEAD");
23459
23459
  if (!isAncestor) {
23460
+ return this.detectPatchesViaMergeBase(lastGen, lock);
23461
+ }
23462
+ return this.detectPatchesInRange(lastGen.commit_sha, lastGen, lock);
23463
+ }
23464
+ /**
23465
+ * FER-10201 — Non-linear-history detection via `merge-base(prevGen, HEAD)`.
23466
+ *
23467
+ * After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
23468
+ * `[fern-generated]`) is orphaned but still in git's object database. The
23469
+ * merge-base is the commit on main from which the bot's branch was created —
23470
+ * a reachable ancestor of HEAD. Walking that range and classifying each
23471
+ * commit via `isGenerationCommit` mirrors the linear path and eliminates
23472
+ * the bug class where pipeline-driven changes (autoversion, replay,
23473
+ * generation) get bundled as customer customizations.
23474
+ *
23475
+ * Falls through to the legacy composite path when no common ancestor exists
23476
+ * (truly disjoint history — orphan branches with no shared root).
23477
+ */
23478
+ async detectPatchesViaMergeBase(lastGen, lock) {
23479
+ let mergeBase = "";
23480
+ try {
23481
+ mergeBase = (await this.git.exec(["merge-base", lastGen.commit_sha, "HEAD"])).trim();
23482
+ } catch {
23483
+ }
23484
+ if (!mergeBase) {
23485
+ this.warnings.push(
23486
+ `No common ancestor between previous generation ${lastGen.commit_sha.slice(0, 7)} and HEAD. Falling back to composite tree-diff detection.`
23487
+ );
23460
23488
  return this.detectPatchesViaTreeDiff(
23461
23489
  lastGen,
23462
23490
  /* commitKnownMissing */
23463
23491
  false
23464
23492
  );
23465
23493
  }
23494
+ return this.detectPatchesInRange(mergeBase, lastGen, lock);
23495
+ }
23496
+ /**
23497
+ * FER-10201 — Walk `${rangeStart}..HEAD`, classify each commit, emit one
23498
+ * `StoredPatch` per customer commit. Body shared by the linear path
23499
+ * (rangeStart = lastGen.commit_sha) and the merge-base path.
23500
+ *
23501
+ * Patch `base_generation` is always `lastGen.commit_sha` — the
23502
+ * lockfile-tracked reference the applicator uses to fetch base file
23503
+ * content, regardless of where the walk actually started.
23504
+ */
23505
+ async detectPatchesInRange(rangeStart, lastGen, lock) {
23466
23506
  const log = await this.git.exec([
23467
23507
  "log",
23468
23508
  "--first-parent",
23469
23509
  "--format=%H%x00%an%x00%ae%x00%s",
23470
- `${lastGen.commit_sha}..HEAD`,
23510
+ `${rangeStart}..HEAD`,
23471
23511
  "--",
23472
23512
  this.sdkOutputDir
23473
23513
  ]);
@@ -28389,7 +28429,7 @@ function resolve(...paths) {
28389
28429
  }
28390
28430
 
28391
28431
  // src/cli.ts
28392
- var import_fs16 = __toESM(require("fs"));
28432
+ var import_fs15 = __toESM(require("fs"));
28393
28433
  var import_promises11 = require("fs/promises");
28394
28434
  var import_path18 = __toESM(require("path"));
28395
28435
 
@@ -38091,12 +38131,11 @@ function replayForget(params) {
38091
38131
 
38092
38132
  // src/replay/replay-init.ts
38093
38133
  init_dist3();
38094
- var import_fs12 = require("fs");
38134
+ var import_fs11 = require("fs");
38095
38135
  var import_path13 = require("path");
38096
38136
  var import_tmp_promise2 = __toESM(require_tmp_promise());
38097
38137
 
38098
38138
  // src/replay/fernignore.ts
38099
- var import_fs11 = require("fs");
38100
38139
  var import_promises4 = require("fs/promises");
38101
38140
  var import_path12 = require("path");
38102
38141
  var REPLAY_FERNIGNORE_ENTRIES2 = [".fern/replay.lock", ".fern/replay.yml", ".gitattributes"];
@@ -38131,32 +38170,6 @@ async function ensureGitattributesEntries2(outputDir) {
38131
38170
  await (0, import_promises4.writeFile)(gitattributesPath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
38132
38171
  }
38133
38172
  }
38134
- function ensureReplayFernignoreEntriesSync(outputDir) {
38135
- const fernignorePath = (0, import_path12.join)(outputDir, ".fernignore");
38136
- let content = "";
38137
- if ((0, import_fs11.existsSync)(fernignorePath)) {
38138
- content = (0, import_fs11.readFileSync)(fernignorePath, "utf-8");
38139
- }
38140
- const lines = content.split("\n");
38141
- const toAdd = REPLAY_FERNIGNORE_ENTRIES2.filter((entry) => !lines.some((line) => line.trim() === entry));
38142
- if (toAdd.length > 0) {
38143
- const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
38144
- (0, import_fs11.writeFileSync)(fernignorePath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
38145
- }
38146
- }
38147
- function ensureGitattributesEntriesSync(outputDir) {
38148
- const gitattributesPath = (0, import_path12.join)(outputDir, ".gitattributes");
38149
- let content = "";
38150
- if ((0, import_fs11.existsSync)(gitattributesPath)) {
38151
- content = (0, import_fs11.readFileSync)(gitattributesPath, "utf-8");
38152
- }
38153
- const lines = content.split("\n");
38154
- const toAdd = GITATTRIBUTES_ENTRIES2.filter((entry) => !lines.some((line) => line.trim() === entry));
38155
- if (toAdd.length > 0) {
38156
- const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
38157
- (0, import_fs11.writeFileSync)(gitattributesPath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
38158
- }
38159
- }
38160
38173
 
38161
38174
  // src/replay/replay-init.ts
38162
38175
  async function replayInit(params) {
@@ -38176,22 +38189,18 @@ async function replayInit(params) {
38176
38189
  importHistory: params.importHistory
38177
38190
  });
38178
38191
  if (!bootstrapResult.generationCommit) {
38179
- return { bootstrap: bootstrapResult, fernignoreEntries: [] };
38192
+ return { bootstrap: bootstrapResult, fernignoreEntries: [], gitattributesEntries: [] };
38180
38193
  }
38181
38194
  if (dryRun) {
38182
- return { bootstrap: bootstrapResult, fernignoreEntries: [] };
38195
+ return { bootstrap: bootstrapResult, fernignoreEntries: [], gitattributesEntries: [] };
38183
38196
  }
38184
- ensureReplayFernignoreEntriesSync(repoPath);
38185
- ensureGitattributesEntriesSync(repoPath);
38186
38197
  const lockfilePath = (0, import_path13.join)(repoPath, ".fern", "replay.lock");
38187
- const lockfileContent = (0, import_fs12.existsSync)(lockfilePath) ? (0, import_fs12.readFileSync)(lockfilePath, "utf-8") : void 0;
38188
- const replayYmlPath = (0, import_path13.join)(repoPath, ".fern", "replay.yml");
38189
- const replayYmlContent = (0, import_fs12.existsSync)(replayYmlPath) ? (0, import_fs12.readFileSync)(replayYmlPath, "utf-8") : void 0;
38198
+ const lockfileContent = (0, import_fs11.existsSync)(lockfilePath) ? (0, import_fs11.readFileSync)(lockfilePath, "utf-8") : void 0;
38190
38199
  return {
38191
38200
  bootstrap: bootstrapResult,
38192
38201
  lockfileContent,
38193
- replayYmlContent,
38194
38202
  fernignoreEntries: REPLAY_FERNIGNORE_ENTRIES2,
38203
+ gitattributesEntries: GITATTRIBUTES_ENTRIES2,
38195
38204
  prBody: buildPrBody(bootstrapResult)
38196
38205
  };
38197
38206
  }
@@ -38307,7 +38316,7 @@ init_dist3();
38307
38316
  // src/replay/replay-run.ts
38308
38317
  init_dist3();
38309
38318
  var import_child_process3 = require("child_process");
38310
- var import_fs13 = require("fs");
38319
+ var import_fs12 = require("fs");
38311
38320
  var import_path14 = require("path");
38312
38321
  async function replayPrepare(params) {
38313
38322
  const {
@@ -38320,7 +38329,7 @@ async function replayPrepare(params) {
38320
38329
  logger
38321
38330
  } = params;
38322
38331
  const lockfilePath = (0, import_path14.join)(outputDir, ".fern", "replay.lock");
38323
- if (!(0, import_fs13.existsSync)(lockfilePath)) {
38332
+ if (!(0, import_fs12.existsSync)(lockfilePath)) {
38324
38333
  return null;
38325
38334
  }
38326
38335
  const baseBranchHead = gitRevParse(outputDir, "HEAD");
@@ -38552,7 +38561,7 @@ var FERN_BOT_LOGIN2 = "fern-api[bot]";
38552
38561
 
38553
38562
  // src/pipeline/steps/AutoVersionStep.ts
38554
38563
  var import_child_process4 = require("child_process");
38555
- var import_fs15 = require("fs");
38564
+ var import_fs14 = require("fs");
38556
38565
  var import_promises9 = require("fs/promises");
38557
38566
  var import_path16 = require("path");
38558
38567
 
@@ -38612,7 +38621,7 @@ async function loggingExeca(logger, executable, args = [], { doNotPipeOutput = f
38612
38621
  }
38613
38622
 
38614
38623
  // src/autoversion/AutoVersioningService.ts
38615
- var import_fs14 = require("fs");
38624
+ var import_fs13 = require("fs");
38616
38625
  var import_promises8 = require("fs/promises");
38617
38626
  var import_path15 = require("path");
38618
38627
  var import_semver = __toESM(require_semver2());
@@ -39302,7 +39311,7 @@ var AutoVersioningService = class _AutoVersioningService {
39302
39311
  return;
39303
39312
  }
39304
39313
  const goModPath = (0, import_path15.join)(workingDirectory, "go.mod");
39305
- if (!(0, import_fs14.existsSync)(goModPath)) {
39314
+ if (!(0, import_fs13.existsSync)(goModPath)) {
39306
39315
  this.logger.debug(`No go.mod found in ${workingDirectory}, skipping Go module path update`);
39307
39316
  return;
39308
39317
  }
@@ -39841,7 +39850,7 @@ ${body}` : `${COMMIT_MARKER} ${subject}`;
39841
39850
 
39842
39851
  `;
39843
39852
  let existing = "";
39844
- if ((0, import_fs15.existsSync)(changelogPath)) {
39853
+ if ((0, import_fs14.existsSync)(changelogPath)) {
39845
39854
  existing = await (0, import_promises9.readFile)(changelogPath, "utf-8");
39846
39855
  }
39847
39856
  let output;
@@ -41384,7 +41393,7 @@ async function createWriteStreamFromFile(filepath) {
41384
41393
  if (!await doesPathExist(filepath)) {
41385
41394
  await (0, import_promises11.mkdir)(import_path18.default.dirname(filepath), { recursive: true });
41386
41395
  }
41387
- return import_fs16.default.createWriteStream(filepath);
41396
+ return import_fs15.default.createWriteStream(filepath);
41388
41397
  }
41389
41398
  /*! Bundled license information:
41390
41399
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fern-api/generator-cli",
3
- "version": "0.9.18",
3
+ "version": "0.9.20",
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.12.0",
26
+ "@fern-api/replay": "0.13.0",
27
27
  "@octokit/rest": "^22.0.1",
28
28
  "es-toolkit": "^1.45.1",
29
29
  "semver": "^7.6.3",