@fern-api/generator-cli 0.9.6 → 0.9.8

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
@@ -536,8 +536,14 @@ interface GithubStepConfig {
536
536
  changelogEntry?: string;
537
537
  /** Structured PR description with Before/After code fences for breaking changes. Takes priority over changelogEntry for PR body. */
538
538
  prDescription?: string;
539
- /** One-sentence justification for WHY the version bump was chosen. Prepended to PR body when present. */
539
+ /** One-sentence justification for WHY the version bump was chosen. Shown only for breaking changes. */
540
540
  versionBumpReason?: string;
541
+ /** The previous SDK version before this change (e.g. "1.2.3"). Used for version header in PR body. */
542
+ previousVersion?: string;
543
+ /** The new SDK version after this change (e.g. "1.3.0"). Used for version header in PR body. */
544
+ newVersion?: string;
545
+ /** The version bump level: MAJOR, MINOR, or PATCH. Used for version header formatting. */
546
+ versionBump?: string;
541
547
  /** Skip push/PR creation, just prepare branches locally */
542
548
  previewMode?: boolean;
543
549
  /** Generator name for namespaced fern-generation-base tag */
package/dist/api.js CHANGED
@@ -10122,15 +10122,37 @@ async function checkPRHasOnlyGenerationCommits(octokit, owner, repo, pullNumber,
10122
10122
  }
10123
10123
 
10124
10124
  // src/pipeline/github/parseCommitMessage.ts
10125
- function parseCommitMessageForPR(commitMessage, changelogEntry, prDescription, versionBumpReason) {
10125
+ function parseCommitMessageForPR(commitMessage, changelogEntry, prDescription, versionBumpReason, previousVersion, newVersion, versionBump, changelogUrl) {
10126
10126
  const lines = commitMessage.split("\n");
10127
10127
  const prTitle = lines[0]?.trim() || "SDK Generation";
10128
10128
  const bodyFromCommit = lines.slice(1).join("\n").trim() || "Automated SDK generation by Fern";
10129
10129
  let prBody = prDescription?.trim() || changelogEntry?.trim() || bodyFromCommit;
10130
- if (versionBumpReason?.trim()) {
10131
- prBody = `**Version Bump:** ${versionBumpReason.trim()}
10130
+ const isBreaking = versionBump === "MAJOR";
10131
+ if (previousVersion?.trim() && newVersion?.trim()) {
10132
+ const emoji = isBreaking ? "\u26A0\uFE0F " : "";
10133
+ const header = `## ${emoji}${previousVersion.trim()} \u2192 ${newVersion.trim()}`;
10134
+ if (isBreaking && versionBumpReason?.trim()) {
10135
+ prBody = `${header}
10136
+
10137
+ **Breaking:** ${versionBumpReason.trim()}
10138
+
10139
+ ${prBody}`;
10140
+ } else {
10141
+ prBody = `${header}
10132
10142
 
10133
10143
  ${prBody}`;
10144
+ }
10145
+ } else if (isBreaking && versionBumpReason?.trim()) {
10146
+ prBody = `\u26A0\uFE0F **Breaking:** ${versionBumpReason.trim()}
10147
+
10148
+ ${prBody}`;
10149
+ }
10150
+ if (changelogUrl?.trim()) {
10151
+ prBody += `
10152
+
10153
+ ---
10154
+
10155
+ [See full changelog](${changelogUrl.trim()})`;
10134
10156
  }
10135
10157
  return { prTitle, prBody };
10136
10158
  }
@@ -10415,11 +10437,17 @@ var GithubStep = class extends BaseStep {
10415
10437
  }
10416
10438
  }
10417
10439
  const finalCommitMessage = this.config.commitMessage ?? "SDK Generation";
10440
+ const headSha = await repository.getHeadSha();
10441
+ const changelogUrl = this.config.changelogEntry ? `https://github.com/${this.config.uri}/blob/${headSha}/changelog.md` : void 0;
10418
10442
  const { prTitle, prBody } = parseCommitMessageForPR(
10419
10443
  finalCommitMessage,
10420
10444
  this.config.changelogEntry,
10421
10445
  this.config.prDescription,
10422
- this.config.versionBumpReason
10446
+ this.config.versionBumpReason,
10447
+ this.config.previousVersion,
10448
+ this.config.newVersion,
10449
+ this.config.versionBump,
10450
+ changelogUrl
10423
10451
  );
10424
10452
  const replaySection = formatReplayPrBody(replayResult, { branchName: prBranch, repoUri: this.config.uri });
10425
10453
  let enrichedBody = replaySection != null ? prBody + "\n\n---\n\n" + replaySection : prBody;
package/dist/cli.js CHANGED
@@ -20098,7 +20098,7 @@ var init_diff3 = __esm({
20098
20098
  }
20099
20099
  });
20100
20100
 
20101
- // ../../node_modules/.pnpm/@fern-api+replay@0.10.3/node_modules/@fern-api/replay/dist/index.js
20101
+ // ../../node_modules/.pnpm/@fern-api+replay@0.10.4/node_modules/@fern-api/replay/dist/index.js
20102
20102
  var dist_exports = {};
20103
20103
  __export(dist_exports, {
20104
20104
  FERN_BOT_EMAIL: () => FERN_BOT_EMAIL2,
@@ -21063,7 +21063,7 @@ function status(outputDir) {
21063
21063
  }
21064
21064
  var import_fs6, import_path8, import_yaml, import_crypto, import_promises4, import_os, import_path9, import_fs7, import_path10, import_crypto2, import_fs8, import_path11, import_yaml2, import_crypto3, import_fs9, import_path12, import_fs10, __defProp3, __getOwnPropNames3, __esm3, __export3, GitClient_exports, GitClient, init_GitClient, HybridReconstruction_exports, init_HybridReconstruction, FERN_BOT_NAME2, FERN_BOT_EMAIL2, FERN_BOT_LOGIN2, 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;
21065
21065
  var init_dist3 = __esm({
21066
- "../../node_modules/.pnpm/@fern-api+replay@0.10.3/node_modules/@fern-api/replay/dist/index.js"() {
21066
+ "../../node_modules/.pnpm/@fern-api+replay@0.10.4/node_modules/@fern-api/replay/dist/index.js"() {
21067
21067
  "use strict";
21068
21068
  init_esm4();
21069
21069
  import_fs6 = require("fs");
@@ -21427,6 +21427,7 @@ var init_dist3 = __esm({
21427
21427
  }
21428
21428
  const log = await this.git.exec([
21429
21429
  "log",
21430
+ "--first-parent",
21430
21431
  "--format=%H%x00%an%x00%ae%x00%s",
21431
21432
  `${lastGen.commit_sha}..HEAD`,
21432
21433
  "--",
@@ -21442,11 +21443,20 @@ var init_dist3 = __esm({
21442
21443
  if (isGenerationCommit(commit)) {
21443
21444
  continue;
21444
21445
  }
21445
- const parents = await this.git.getCommitParents(commit.sha);
21446
- if (parents.length > 1) {
21446
+ if (lock.patches.find((p) => p.original_commit === commit.sha)) {
21447
21447
  continue;
21448
21448
  }
21449
- if (lock.patches.find((p) => p.original_commit === commit.sha)) {
21449
+ const parents = await this.git.getCommitParents(commit.sha);
21450
+ if (parents.length > 1) {
21451
+ const compositePatch = await this.createMergeCompositePatch({
21452
+ mergeCommit: commit,
21453
+ firstParent: parents[0],
21454
+ lastGen,
21455
+ lock
21456
+ });
21457
+ if (compositePatch) {
21458
+ newPatches.push(compositePatch);
21459
+ }
21450
21460
  continue;
21451
21461
  }
21452
21462
  let patchContent;
@@ -21545,6 +21555,46 @@ var init_dist3 = __esm({
21545
21555
  const normalized = patchContent.split("\n").filter((line) => !line.startsWith("From ") && !line.startsWith("index ") && !line.startsWith("Date: ")).join("\n");
21546
21556
  return `sha256:${(0, import_crypto.createHash)("sha256").update(normalized).digest("hex")}`;
21547
21557
  }
21558
+ /**
21559
+ * Create a single composite patch for a merge commit by diffing the merge
21560
+ * commit against its first parent. This captures the net change of the
21561
+ * entire merged branch as one patch, eliminating accumulator chaining and
21562
+ * zero-net-change ghost conflicts.
21563
+ */
21564
+ async createMergeCompositePatch(input) {
21565
+ const { mergeCommit, firstParent, lastGen, lock } = input;
21566
+ const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
21567
+ const filesOutput = await this.git.exec([
21568
+ "diff",
21569
+ "--name-only",
21570
+ firstParent,
21571
+ mergeCommit.sha
21572
+ ]);
21573
+ const files = filesOutput.trim().split("\n").filter(Boolean).filter((f2) => !INFRASTRUCTURE_FILES.has(f2)).filter((f2) => !f2.startsWith(".fern/"));
21574
+ if (files.length === 0) return null;
21575
+ const diff = await this.git.exec([
21576
+ "diff",
21577
+ firstParent,
21578
+ mergeCommit.sha,
21579
+ "--",
21580
+ ...files
21581
+ ]);
21582
+ if (!diff.trim()) return null;
21583
+ const contentHash = this.computeContentHash(diff);
21584
+ if (lock.patches.some((p) => p.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
21585
+ return null;
21586
+ }
21587
+ return {
21588
+ id: `patch-merge-${mergeCommit.sha.slice(0, 8)}`,
21589
+ content_hash: contentHash,
21590
+ original_commit: mergeCommit.sha,
21591
+ original_message: mergeCommit.message,
21592
+ original_author: `${mergeCommit.authorName} <${mergeCommit.authorEmail}>`,
21593
+ base_generation: lastGen.commit_sha,
21594
+ files,
21595
+ patch_content: diff
21596
+ };
21597
+ }
21548
21598
  /**
21549
21599
  * Detect patches via tree diff for non-linear history. Returns a composite patch.
21550
21600
  * Revert reconciliation is skipped here because tree-diff produces a single composite
@@ -32982,15 +33032,37 @@ async function checkPRHasOnlyGenerationCommits(octokit, owner, repo, pullNumber,
32982
33032
  }
32983
33033
 
32984
33034
  // src/pipeline/github/parseCommitMessage.ts
32985
- function parseCommitMessageForPR(commitMessage, changelogEntry, prDescription, versionBumpReason) {
33035
+ function parseCommitMessageForPR(commitMessage, changelogEntry, prDescription, versionBumpReason, previousVersion, newVersion, versionBump, changelogUrl) {
32986
33036
  const lines = commitMessage.split("\n");
32987
33037
  const prTitle = lines[0]?.trim() || "SDK Generation";
32988
33038
  const bodyFromCommit = lines.slice(1).join("\n").trim() || "Automated SDK generation by Fern";
32989
33039
  let prBody = prDescription?.trim() || changelogEntry?.trim() || bodyFromCommit;
32990
- if (versionBumpReason?.trim()) {
32991
- prBody = `**Version Bump:** ${versionBumpReason.trim()}
33040
+ const isBreaking = versionBump === "MAJOR";
33041
+ if (previousVersion?.trim() && newVersion?.trim()) {
33042
+ const emoji = isBreaking ? "\u26A0\uFE0F " : "";
33043
+ const header = `## ${emoji}${previousVersion.trim()} \u2192 ${newVersion.trim()}`;
33044
+ if (isBreaking && versionBumpReason?.trim()) {
33045
+ prBody = `${header}
33046
+
33047
+ **Breaking:** ${versionBumpReason.trim()}
32992
33048
 
32993
33049
  ${prBody}`;
33050
+ } else {
33051
+ prBody = `${header}
33052
+
33053
+ ${prBody}`;
33054
+ }
33055
+ } else if (isBreaking && versionBumpReason?.trim()) {
33056
+ prBody = `\u26A0\uFE0F **Breaking:** ${versionBumpReason.trim()}
33057
+
33058
+ ${prBody}`;
33059
+ }
33060
+ if (changelogUrl?.trim()) {
33061
+ prBody += `
33062
+
33063
+ ---
33064
+
33065
+ [See full changelog](${changelogUrl.trim()})`;
32994
33066
  }
32995
33067
  return { prTitle, prBody };
32996
33068
  }
@@ -33239,11 +33311,17 @@ var GithubStep = class extends BaseStep {
33239
33311
  }
33240
33312
  }
33241
33313
  const finalCommitMessage = this.config.commitMessage ?? "SDK Generation";
33314
+ const headSha = await repository.getHeadSha();
33315
+ const changelogUrl = this.config.changelogEntry ? `https://github.com/${this.config.uri}/blob/${headSha}/changelog.md` : void 0;
33242
33316
  const { prTitle, prBody } = parseCommitMessageForPR(
33243
33317
  finalCommitMessage,
33244
33318
  this.config.changelogEntry,
33245
33319
  this.config.prDescription,
33246
- this.config.versionBumpReason
33320
+ this.config.versionBumpReason,
33321
+ this.config.previousVersion,
33322
+ this.config.newVersion,
33323
+ this.config.versionBump,
33324
+ changelogUrl
33247
33325
  );
33248
33326
  const replaySection = formatReplayPrBody(replayResult, { branchName: prBranch, repoUri: this.config.uri });
33249
33327
  let enrichedBody = replaySection != null ? prBody + "\n\n---\n\n" + replaySection : prBody;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fern-api/generator-cli",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fern-api/fern.git",
@@ -22,7 +22,7 @@
22
22
  "**"
23
23
  ],
24
24
  "dependencies": {
25
- "@fern-api/replay": "0.10.3",
25
+ "@fern-api/replay": "0.10.4",
26
26
  "@octokit/rest": "^22.0.1",
27
27
  "es-toolkit": "^1.45.1",
28
28
  "tmp-promise": "^3.0.3"