@fern-api/generator-cli 0.9.5 → 0.9.7

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
@@ -549,6 +549,16 @@ interface GithubStepConfig {
549
549
  previousGenerationSha: string;
550
550
  currentGenerationSha: string;
551
551
  };
552
+ /** When true: separate PRs per generation, no-diff skip, automerge support, run_id in body */
553
+ automationMode?: boolean;
554
+ /** Enable GitHub automerge on the PR (only effective when automationMode && !hasBreakingChanges) */
555
+ autoMerge?: boolean;
556
+ /** Pre-computed: version bump is MAJOR (from --version AUTO AI analysis) */
557
+ hasBreakingChanges?: boolean;
558
+ /** Human-readable breaking changes summary for PR body (from autoVersioningPrDescription on MAJOR) */
559
+ breakingChangesSummary?: string;
560
+ /** FERN_RUN_ID for cross-repo correlation (set by GitHub Action) */
561
+ runId?: string;
552
562
  }
553
563
  interface PipelineResult {
554
564
  success: boolean;
@@ -593,6 +603,10 @@ interface GithubStepResult extends StepResult {
593
603
  prNumber?: number;
594
604
  updatedExistingPr?: boolean;
595
605
  generationBaseTagSha?: string;
606
+ /** True when generation produced no changes vs base branch — PR skipped */
607
+ skippedNoDiff?: boolean;
608
+ /** True when automerge was enabled on the PR */
609
+ autoMergeEnabled?: boolean;
596
610
  }
597
611
  interface StepResult {
598
612
  executed: boolean;
package/dist/api.js CHANGED
@@ -6022,6 +6022,24 @@ var ClonedRepository = class _ClonedRepository {
6022
6022
  const result = await this.git.raw(["rev-parse", `${commitSha}^{tree}`]);
6023
6023
  return result.trim();
6024
6024
  }
6025
+ /**
6026
+ * Returns true if the working tree has uncommitted changes (staged or unstaged).
6027
+ */
6028
+ async hasChanges() {
6029
+ await this.git.cwd(this.clonePath);
6030
+ const status2 = await this.git.status();
6031
+ return !status2.isClean();
6032
+ }
6033
+ /**
6034
+ * Compares the tree hash of HEAD against a ref (e.g. `origin/main`).
6035
+ * Returns true if the trees are identical (no content difference).
6036
+ * Useful for detecting no-diff after committing — if trees match, the commit is a no-op.
6037
+ */
6038
+ async treeHashEquals(ref) {
6039
+ const headTree = await this.getHeadTreeHash();
6040
+ const refTree = await this.getCommitTreeHash(ref);
6041
+ return headTree === refTree;
6042
+ }
6025
6043
  // This tag is a moving pointer (fern-generation-base--<generator>) that tracks the latest
6026
6044
  // clean generation SHA. --force is required because: (1) the tag is intentionally overwritten
6027
6045
  // each generation, and (2) --force-with-lease doesn't work for tags since git tag -f overwrites
@@ -10311,31 +10329,32 @@ var GithubStep = class extends BaseStep {
10311
10329
  const baseBranch = this.config.branch ?? await repository.getDefaultBranch();
10312
10330
  const octokit = new import_rest2.Octokit({ auth: this.config.token });
10313
10331
  const { owner, repo } = parseRepository(this.config.uri);
10314
- const existingPR = await findExistingUpdatablePR(octokit, owner, repo, baseBranch, this.logger);
10315
10332
  let prBranch;
10316
10333
  let isUpdatingExistingPR = false;
10317
10334
  let generationBaseSha;
10335
+ let existingPR;
10336
+ if (!this.config.automationMode) {
10337
+ existingPR = await findExistingUpdatablePR(octokit, owner, repo, baseBranch, this.logger);
10338
+ }
10318
10339
  if (existingPR != null) {
10319
10340
  this.logger.info(
10320
10341
  `Found existing updatable PR #${existingPR.number}, will update branch ${existingPR.headBranch}`
10321
10342
  );
10322
10343
  prBranch = existingPR.headBranch;
10323
10344
  isUpdatingExistingPR = true;
10324
- if (skipCommit) {
10325
- generationBaseSha = await createReplayBranch(
10326
- repository,
10327
- prBranch,
10328
- this.config.commitMessage,
10329
- replayConflictInfo,
10330
- this.logger
10331
- );
10332
- } else {
10333
- await repository.checkoutRemoteBranch(prBranch);
10334
- }
10335
10345
  } else {
10336
- this.logger.debug(`No existing updatable PR found, creating new branch ${newPrBranch}`);
10346
+ this.logger.debug(
10347
+ this.config.automationMode ? `Automation mode: creating new branch ${newPrBranch}` : `No existing updatable PR found, creating new branch ${newPrBranch}`
10348
+ );
10337
10349
  prBranch = newPrBranch;
10338
- if (skipCommit) {
10350
+ }
10351
+ const branchAction = resolveBranchAction({
10352
+ automationMode: this.config.automationMode === true,
10353
+ skipCommit,
10354
+ existingPR
10355
+ });
10356
+ switch (branchAction) {
10357
+ case "replay-branch":
10339
10358
  generationBaseSha = await createReplayBranch(
10340
10359
  repository,
10341
10360
  prBranch,
@@ -10343,8 +10362,16 @@ var GithubStep = class extends BaseStep {
10343
10362
  replayConflictInfo,
10344
10363
  this.logger
10345
10364
  );
10346
- } else {
10347
- await repository.checkout(prBranch);
10365
+ break;
10366
+ case "create-from-head":
10367
+ await repository.createBranchFromHead(prBranch);
10368
+ break;
10369
+ case "checkout-remote":
10370
+ await repository.checkoutRemoteBranch(prBranch);
10371
+ break;
10372
+ default: {
10373
+ const _exhaustive = branchAction;
10374
+ throw new Error(`Unexpected branch action: ${String(_exhaustive)}`);
10348
10375
  }
10349
10376
  }
10350
10377
  if (!skipCommit) {
@@ -10354,6 +10381,13 @@ var GithubStep = class extends BaseStep {
10354
10381
  await repository.commitAllChanges(finalCommitMessage2);
10355
10382
  this.logger.debug(`Committed changes to local copy of GitHub repository at ${this.outputDir}`);
10356
10383
  }
10384
+ if (this.config.automationMode) {
10385
+ const noDiff = await repository.treeHashEquals(`origin/${baseBranch}`);
10386
+ if (noDiff) {
10387
+ this.logger.info("No changes detected after generation \u2014 skipping PR creation");
10388
+ return { executed: true, success: true, skippedNoDiff: true };
10389
+ }
10390
+ }
10357
10391
  const result = {
10358
10392
  executed: true,
10359
10393
  success: true,
@@ -10388,7 +10422,8 @@ var GithubStep = class extends BaseStep {
10388
10422
  this.config.versionBumpReason
10389
10423
  );
10390
10424
  const replaySection = formatReplayPrBody(replayResult, { branchName: prBranch, repoUri: this.config.uri });
10391
- const enrichedBody = replaySection != null ? prBody + "\n\n---\n\n" + replaySection : prBody;
10425
+ let enrichedBody = replaySection != null ? prBody + "\n\n---\n\n" + replaySection : prBody;
10426
+ enrichedBody = enrichPrBodyForAutomation(enrichedBody, this.config);
10392
10427
  if (isUpdatingExistingPR && existingPR != null) {
10393
10428
  this.logger.info(`Updated existing pull request: ${existingPR.htmlUrl}`);
10394
10429
  result.prUrl = existingPR.htmlUrl;
@@ -10419,6 +10454,23 @@ var GithubStep = class extends BaseStep {
10419
10454
  this.logger.info(`Created pull request: ${pullRequest.html_url}`);
10420
10455
  result.prUrl = pullRequest.html_url;
10421
10456
  result.prNumber = pullRequest.number;
10457
+ if (shouldEnableAutomerge(this.config) && pullRequest.node_id) {
10458
+ try {
10459
+ await octokit.graphql(
10460
+ `mutation($pullRequestId: ID!) {
10461
+ enablePullRequestAutoMerge(input: {
10462
+ pullRequestId: $pullRequestId,
10463
+ mergeMethod: SQUASH
10464
+ }) { clientMutationId }
10465
+ }`,
10466
+ { pullRequestId: pullRequest.node_id }
10467
+ );
10468
+ this.logger.info(`Enabled automerge on PR #${pullRequest.number}`);
10469
+ result.autoMergeEnabled = true;
10470
+ } catch (autoMergeError) {
10471
+ this.logger.warn(`Could not enable automerge: ${extractErrorMessage(autoMergeError)}`);
10472
+ }
10473
+ }
10422
10474
  } catch (error) {
10423
10475
  const message = extractErrorMessage(error);
10424
10476
  if (message.includes("A pull request already exists for")) {
@@ -10431,6 +10483,7 @@ var GithubStep = class extends BaseStep {
10431
10483
  return result;
10432
10484
  }
10433
10485
  async executePushMode(repository) {
10486
+ const baseBranch = this.config.branch ?? await repository.getDefaultBranch();
10434
10487
  if (this.config.branch != null) {
10435
10488
  this.logger.debug(`Checking out branch ${this.config.branch}`);
10436
10489
  await repository.checkout(this.config.branch);
@@ -10440,6 +10493,13 @@ var GithubStep = class extends BaseStep {
10440
10493
  const finalCommitMessage = this.config.commitMessage ?? "SDK Generation";
10441
10494
  await repository.commitAllChanges(finalCommitMessage);
10442
10495
  this.logger.debug(`Committed changes to local copy of GitHub repository at ${this.outputDir}`);
10496
+ if (this.config.automationMode) {
10497
+ const noDiff = await repository.treeHashEquals(`origin/${baseBranch}`);
10498
+ if (noDiff) {
10499
+ this.logger.info("No changes detected after generation \u2014 skipping push");
10500
+ return { executed: true, success: true, skippedNoDiff: true };
10501
+ }
10502
+ }
10443
10503
  const result = {
10444
10504
  executed: true,
10445
10505
  success: true
@@ -10484,6 +10544,42 @@ var GithubStep = class extends BaseStep {
10484
10544
  return void 0;
10485
10545
  }
10486
10546
  };
10547
+ function enrichPrBodyForAutomation(body, config) {
10548
+ if (!config.automationMode) {
10549
+ return body;
10550
+ }
10551
+ let result = body;
10552
+ if (config.hasBreakingChanges && config.breakingChangesSummary) {
10553
+ result += "\n\n---\n\n## \u26A0\uFE0F Breaking Changes\n\nThis release contains breaking changes that require manual review:\n\n" + config.breakingChangesSummary;
10554
+ }
10555
+ if (config.runId) {
10556
+ result += `
10557
+
10558
+ ---
10559
+
10560
+ _Fern Run ID: \`${config.runId}\`_`;
10561
+ }
10562
+ return result;
10563
+ }
10564
+ function shouldEnableAutomerge(config) {
10565
+ return config.automationMode === true && config.autoMerge === true && config.hasBreakingChanges !== true;
10566
+ }
10567
+ function resolveBranchAction({
10568
+ automationMode,
10569
+ skipCommit,
10570
+ existingPR
10571
+ }) {
10572
+ if (skipCommit) {
10573
+ return "replay-branch";
10574
+ }
10575
+ if (automationMode) {
10576
+ return "create-from-head";
10577
+ }
10578
+ if (existingPR != null) {
10579
+ return "checkout-remote";
10580
+ }
10581
+ return "create-from-head";
10582
+ }
10487
10583
 
10488
10584
  // src/replay/replay-run.ts
10489
10585
  var import_replay = require("@fern-api/replay");
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.2/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.2/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
@@ -25063,6 +25113,24 @@ var ClonedRepository = class _ClonedRepository {
25063
25113
  const result = await this.git.raw(["rev-parse", `${commitSha}^{tree}`]);
25064
25114
  return result.trim();
25065
25115
  }
25116
+ /**
25117
+ * Returns true if the working tree has uncommitted changes (staged or unstaged).
25118
+ */
25119
+ async hasChanges() {
25120
+ await this.git.cwd(this.clonePath);
25121
+ const status2 = await this.git.status();
25122
+ return !status2.isClean();
25123
+ }
25124
+ /**
25125
+ * Compares the tree hash of HEAD against a ref (e.g. `origin/main`).
25126
+ * Returns true if the trees are identical (no content difference).
25127
+ * Useful for detecting no-diff after committing — if trees match, the commit is a no-op.
25128
+ */
25129
+ async treeHashEquals(ref) {
25130
+ const headTree = await this.getHeadTreeHash();
25131
+ const refTree = await this.getCommitTreeHash(ref);
25132
+ return headTree === refTree;
25133
+ }
25066
25134
  // This tag is a moving pointer (fern-generation-base--<generator>) that tracks the latest
25067
25135
  // clean generation SHA. --force is required because: (1) the tag is intentionally overwritten
25068
25136
  // each generation, and (2) --force-with-lease doesn't work for tags since git tag -f overwrites
@@ -33135,31 +33203,32 @@ var GithubStep = class extends BaseStep {
33135
33203
  const baseBranch = this.config.branch ?? await repository.getDefaultBranch();
33136
33204
  const octokit = new Octokit2({ auth: this.config.token });
33137
33205
  const { owner, repo } = parseRepository(this.config.uri);
33138
- const existingPR = await findExistingUpdatablePR(octokit, owner, repo, baseBranch, this.logger);
33139
33206
  let prBranch;
33140
33207
  let isUpdatingExistingPR = false;
33141
33208
  let generationBaseSha;
33209
+ let existingPR;
33210
+ if (!this.config.automationMode) {
33211
+ existingPR = await findExistingUpdatablePR(octokit, owner, repo, baseBranch, this.logger);
33212
+ }
33142
33213
  if (existingPR != null) {
33143
33214
  this.logger.info(
33144
33215
  `Found existing updatable PR #${existingPR.number}, will update branch ${existingPR.headBranch}`
33145
33216
  );
33146
33217
  prBranch = existingPR.headBranch;
33147
33218
  isUpdatingExistingPR = true;
33148
- if (skipCommit) {
33149
- generationBaseSha = await createReplayBranch(
33150
- repository,
33151
- prBranch,
33152
- this.config.commitMessage,
33153
- replayConflictInfo,
33154
- this.logger
33155
- );
33156
- } else {
33157
- await repository.checkoutRemoteBranch(prBranch);
33158
- }
33159
33219
  } else {
33160
- this.logger.debug(`No existing updatable PR found, creating new branch ${newPrBranch}`);
33220
+ this.logger.debug(
33221
+ this.config.automationMode ? `Automation mode: creating new branch ${newPrBranch}` : `No existing updatable PR found, creating new branch ${newPrBranch}`
33222
+ );
33161
33223
  prBranch = newPrBranch;
33162
- if (skipCommit) {
33224
+ }
33225
+ const branchAction = resolveBranchAction({
33226
+ automationMode: this.config.automationMode === true,
33227
+ skipCommit,
33228
+ existingPR
33229
+ });
33230
+ switch (branchAction) {
33231
+ case "replay-branch":
33163
33232
  generationBaseSha = await createReplayBranch(
33164
33233
  repository,
33165
33234
  prBranch,
@@ -33167,8 +33236,16 @@ var GithubStep = class extends BaseStep {
33167
33236
  replayConflictInfo,
33168
33237
  this.logger
33169
33238
  );
33170
- } else {
33171
- await repository.checkout(prBranch);
33239
+ break;
33240
+ case "create-from-head":
33241
+ await repository.createBranchFromHead(prBranch);
33242
+ break;
33243
+ case "checkout-remote":
33244
+ await repository.checkoutRemoteBranch(prBranch);
33245
+ break;
33246
+ default: {
33247
+ const _exhaustive = branchAction;
33248
+ throw new Error(`Unexpected branch action: ${String(_exhaustive)}`);
33172
33249
  }
33173
33250
  }
33174
33251
  if (!skipCommit) {
@@ -33178,6 +33255,13 @@ var GithubStep = class extends BaseStep {
33178
33255
  await repository.commitAllChanges(finalCommitMessage2);
33179
33256
  this.logger.debug(`Committed changes to local copy of GitHub repository at ${this.outputDir}`);
33180
33257
  }
33258
+ if (this.config.automationMode) {
33259
+ const noDiff = await repository.treeHashEquals(`origin/${baseBranch}`);
33260
+ if (noDiff) {
33261
+ this.logger.info("No changes detected after generation \u2014 skipping PR creation");
33262
+ return { executed: true, success: true, skippedNoDiff: true };
33263
+ }
33264
+ }
33181
33265
  const result = {
33182
33266
  executed: true,
33183
33267
  success: true,
@@ -33212,7 +33296,8 @@ var GithubStep = class extends BaseStep {
33212
33296
  this.config.versionBumpReason
33213
33297
  );
33214
33298
  const replaySection = formatReplayPrBody(replayResult, { branchName: prBranch, repoUri: this.config.uri });
33215
- const enrichedBody = replaySection != null ? prBody + "\n\n---\n\n" + replaySection : prBody;
33299
+ let enrichedBody = replaySection != null ? prBody + "\n\n---\n\n" + replaySection : prBody;
33300
+ enrichedBody = enrichPrBodyForAutomation(enrichedBody, this.config);
33216
33301
  if (isUpdatingExistingPR && existingPR != null) {
33217
33302
  this.logger.info(`Updated existing pull request: ${existingPR.htmlUrl}`);
33218
33303
  result.prUrl = existingPR.htmlUrl;
@@ -33243,6 +33328,23 @@ var GithubStep = class extends BaseStep {
33243
33328
  this.logger.info(`Created pull request: ${pullRequest.html_url}`);
33244
33329
  result.prUrl = pullRequest.html_url;
33245
33330
  result.prNumber = pullRequest.number;
33331
+ if (shouldEnableAutomerge(this.config) && pullRequest.node_id) {
33332
+ try {
33333
+ await octokit.graphql(
33334
+ `mutation($pullRequestId: ID!) {
33335
+ enablePullRequestAutoMerge(input: {
33336
+ pullRequestId: $pullRequestId,
33337
+ mergeMethod: SQUASH
33338
+ }) { clientMutationId }
33339
+ }`,
33340
+ { pullRequestId: pullRequest.node_id }
33341
+ );
33342
+ this.logger.info(`Enabled automerge on PR #${pullRequest.number}`);
33343
+ result.autoMergeEnabled = true;
33344
+ } catch (autoMergeError) {
33345
+ this.logger.warn(`Could not enable automerge: ${extractErrorMessage(autoMergeError)}`);
33346
+ }
33347
+ }
33246
33348
  } catch (error) {
33247
33349
  const message = extractErrorMessage(error);
33248
33350
  if (message.includes("A pull request already exists for")) {
@@ -33255,6 +33357,7 @@ var GithubStep = class extends BaseStep {
33255
33357
  return result;
33256
33358
  }
33257
33359
  async executePushMode(repository) {
33360
+ const baseBranch = this.config.branch ?? await repository.getDefaultBranch();
33258
33361
  if (this.config.branch != null) {
33259
33362
  this.logger.debug(`Checking out branch ${this.config.branch}`);
33260
33363
  await repository.checkout(this.config.branch);
@@ -33264,6 +33367,13 @@ var GithubStep = class extends BaseStep {
33264
33367
  const finalCommitMessage = this.config.commitMessage ?? "SDK Generation";
33265
33368
  await repository.commitAllChanges(finalCommitMessage);
33266
33369
  this.logger.debug(`Committed changes to local copy of GitHub repository at ${this.outputDir}`);
33370
+ if (this.config.automationMode) {
33371
+ const noDiff = await repository.treeHashEquals(`origin/${baseBranch}`);
33372
+ if (noDiff) {
33373
+ this.logger.info("No changes detected after generation \u2014 skipping push");
33374
+ return { executed: true, success: true, skippedNoDiff: true };
33375
+ }
33376
+ }
33267
33377
  const result = {
33268
33378
  executed: true,
33269
33379
  success: true
@@ -33308,6 +33418,42 @@ var GithubStep = class extends BaseStep {
33308
33418
  return void 0;
33309
33419
  }
33310
33420
  };
33421
+ function enrichPrBodyForAutomation(body, config) {
33422
+ if (!config.automationMode) {
33423
+ return body;
33424
+ }
33425
+ let result = body;
33426
+ if (config.hasBreakingChanges && config.breakingChangesSummary) {
33427
+ result += "\n\n---\n\n## \u26A0\uFE0F Breaking Changes\n\nThis release contains breaking changes that require manual review:\n\n" + config.breakingChangesSummary;
33428
+ }
33429
+ if (config.runId) {
33430
+ result += `
33431
+
33432
+ ---
33433
+
33434
+ _Fern Run ID: \`${config.runId}\`_`;
33435
+ }
33436
+ return result;
33437
+ }
33438
+ function shouldEnableAutomerge(config) {
33439
+ return config.automationMode === true && config.autoMerge === true && config.hasBreakingChanges !== true;
33440
+ }
33441
+ function resolveBranchAction({
33442
+ automationMode,
33443
+ skipCommit,
33444
+ existingPR
33445
+ }) {
33446
+ if (skipCommit) {
33447
+ return "replay-branch";
33448
+ }
33449
+ if (automationMode) {
33450
+ return "create-from-head";
33451
+ }
33452
+ if (existingPR != null) {
33453
+ return "checkout-remote";
33454
+ }
33455
+ return "create-from-head";
33456
+ }
33311
33457
 
33312
33458
  // src/replay/replay-run.ts
33313
33459
  init_dist3();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fern-api/generator-cli",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
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.2",
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"