@fern-api/generator-cli 0.6.9 → 0.6.11

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 (4) hide show
  1. package/dist/api.d.ts +15 -19
  2. package/dist/api.js +124 -356
  3. package/dist/cli.js +612 -414
  4. package/package.json +2 -2
package/dist/api.js CHANGED
@@ -893,13 +893,11 @@ __export(api_exports2, {
893
893
  githubRelease: () => githubRelease,
894
894
  logReplaySummary: () => logReplaySummary,
895
895
  replayInit: () => replayInit,
896
+ replayResolve: () => replayResolve,
896
897
  replayRun: () => replayRun
897
898
  });
898
899
  module.exports = __toCommonJS(api_exports2);
899
900
 
900
- // src/api/generate-readme.ts
901
- var import_node_stream = require("stream");
902
-
903
901
  // ../commons/github/lib/ClonedRepository.js
904
902
  var import_promises = require("fs/promises");
905
903
  var import_path = __toESM(require("path"), 1);
@@ -8256,7 +8254,7 @@ var StreamWriter = class extends Writer {
8256
8254
  this.stream = stream;
8257
8255
  }
8258
8256
  async write(content) {
8259
- return new Promise((resolve3, reject) => {
8257
+ return new Promise((resolve4, reject) => {
8260
8258
  const handleError = (err) => {
8261
8259
  this.stream.removeListener("error", handleError);
8262
8260
  reject(err);
@@ -8267,12 +8265,12 @@ var StreamWriter = class extends Writer {
8267
8265
  if (error) {
8268
8266
  reject(error);
8269
8267
  } else {
8270
- resolve3();
8268
+ resolve4();
8271
8269
  }
8272
8270
  });
8273
8271
  this.stream.once("error", handleError);
8274
8272
  if (!canWrite) {
8275
- this.stream.once("drain", resolve3);
8273
+ this.stream.once("drain", resolve4);
8276
8274
  }
8277
8275
  } else {
8278
8276
  const canWrite = this.stream.write(content);
@@ -8280,26 +8278,26 @@ var StreamWriter = class extends Writer {
8280
8278
  if (!canWrite) {
8281
8279
  this.stream.once("drain", () => {
8282
8280
  this.stream.removeListener("error", handleError);
8283
- resolve3();
8281
+ resolve4();
8284
8282
  });
8285
8283
  } else {
8286
8284
  this.stream.removeListener("error", handleError);
8287
- resolve3();
8285
+ resolve4();
8288
8286
  }
8289
8287
  }
8290
8288
  });
8291
8289
  }
8292
8290
  end() {
8293
- return new Promise((resolve3, reject) => {
8291
+ return new Promise((resolve4, reject) => {
8294
8292
  if (this.stream instanceof import_fs.default.WriteStream) {
8295
8293
  this.stream.end(() => {
8296
- resolve3();
8294
+ resolve4();
8297
8295
  });
8298
8296
  this.stream.once("error", reject);
8299
- this.stream.once("finish", resolve3);
8297
+ this.stream.once("finish", resolve4);
8300
8298
  } else {
8301
8299
  this.stream.write("", () => {
8302
- resolve3();
8300
+ resolve4();
8303
8301
  });
8304
8302
  }
8305
8303
  });
@@ -8388,7 +8386,8 @@ var BlockList = class {
8388
8386
  }
8389
8387
  addBlock(block) {
8390
8388
  if (this.hasBlock(block.id)) {
8391
- throw new Error(`block with id "${block.id}" already exists`);
8389
+ console.warn(`[readme] Duplicate block with id "${block.id}" detected; overriding with latest content`);
8390
+ this.blocks = this.blocks.filter((b) => b.id !== block.id);
8392
8391
  }
8393
8392
  this.ids.add(block.id);
8394
8393
  this.blocks.push(block);
@@ -8402,7 +8401,9 @@ var BlockList = class {
8402
8401
  // }
8403
8402
  insertBlock(block, precedingBlockId) {
8404
8403
  if (this.hasBlock(block.id)) {
8405
- throw new Error(`block with id "${block.id}" already exists`);
8404
+ console.warn(`[readme] Duplicate block with id "${block.id}" detected; overriding with latest content`);
8405
+ this.blocks = this.blocks.filter((b) => b.id !== block.id);
8406
+ this.ids.delete(block.id);
8406
8407
  }
8407
8408
  if (precedingBlockId === void 0) {
8408
8409
  this.addBlock(block);
@@ -8453,16 +8454,25 @@ var ReadmeGenerator = class {
8453
8454
  this.apiName = this.readmeConfig.apiName ?? this.organizationPascalCase;
8454
8455
  }
8455
8456
  async generateReadme({ output }) {
8457
+ const writer = new StreamWriter(output);
8458
+ await this.writeReadmeContent({ writer });
8459
+ await writer.end();
8460
+ }
8461
+ async generateReadmeToString() {
8462
+ const writer = new StringWriter();
8463
+ await this.writeReadmeContent({ writer });
8464
+ await writer.end();
8465
+ return writer.toString();
8466
+ }
8467
+ async writeReadmeContent({ writer }) {
8456
8468
  const blocks = await this.generateBlocks();
8457
8469
  const mergedBlocks = await this.mergeBlocks({ blocks });
8458
- const writer = new StreamWriter(output);
8459
8470
  await this.writeHeader({ writer });
8460
8471
  await this.writeTableOfContents({ writer, blocks: mergedBlocks });
8461
8472
  await this.writeBlocks({
8462
8473
  writer,
8463
8474
  blocks: mergedBlocks
8464
8475
  });
8465
- await writer.end();
8466
8476
  }
8467
8477
  async generateBlocks() {
8468
8478
  const blocks = [];
@@ -9334,28 +9344,14 @@ async function generateReadmeToStream(params) {
9334
9344
  }
9335
9345
  async function generateReadme(params) {
9336
9346
  const { originalReadmeContent, readmeConfig } = params;
9337
- const content = [];
9338
- const sink = new import_node_stream.Writable({
9339
- write(chunk, _encoding, callback) {
9340
- if (typeof chunk === "string") {
9341
- content.push(chunk);
9342
- } else {
9343
- content.push(Buffer.from(chunk).toString("utf8"));
9344
- }
9345
- callback();
9346
- }
9347
- });
9348
- await generateReadmeToStream({
9349
- originalReadmeContent,
9347
+ const generator = new ReadmeGenerator({
9348
+ readmeParser: new ReadmeParser(),
9350
9349
  readmeConfig,
9351
- outputStream: sink
9350
+ originalReadme: originalReadmeContent
9352
9351
  });
9353
- return content.join("");
9352
+ return generator.generateReadmeToString();
9354
9353
  }
9355
9354
 
9356
- // src/api/generate-reference.ts
9357
- var import_node_stream2 = require("stream");
9358
-
9359
9355
  // src/reference/ReferenceGenerator.ts
9360
9356
  var ReferenceGenerator = class {
9361
9357
  referenceConfig;
@@ -9366,6 +9362,16 @@ var ReferenceGenerator = class {
9366
9362
  }
9367
9363
  async generate({ output }) {
9368
9364
  const writer = new StreamWriter(output);
9365
+ await this.writeReferenceContent({ writer });
9366
+ await writer.end();
9367
+ }
9368
+ async generateToString() {
9369
+ const writer = new StringWriter();
9370
+ await this.writeReferenceContent({ writer });
9371
+ await writer.end();
9372
+ return writer.toString();
9373
+ }
9374
+ async writeReferenceContent({ writer }) {
9369
9375
  await writer.writeLine("# Reference");
9370
9376
  if (this.referenceConfig.rootSection != null) {
9371
9377
  await this.writeRootSection({
@@ -9376,7 +9382,6 @@ var ReferenceGenerator = class {
9376
9382
  for (const section of this.referenceConfig.sections) {
9377
9383
  await this.writeSection({ section, writer });
9378
9384
  }
9379
- await writer.end();
9380
9385
  }
9381
9386
  async writeRootSection({
9382
9387
  section,
@@ -9477,22 +9482,8 @@ async function generateReferenceToStream(params) {
9477
9482
  }
9478
9483
  async function generateReference(params) {
9479
9484
  const { referenceConfig } = params;
9480
- const content = [];
9481
- const sink = new import_node_stream2.Writable({
9482
- write(chunk, _encoding, callback) {
9483
- if (typeof chunk === "string") {
9484
- content.push(chunk);
9485
- } else {
9486
- content.push(Buffer.from(chunk).toString("utf8"));
9487
- }
9488
- callback();
9489
- }
9490
- });
9491
- await generateReferenceToStream({
9492
- referenceConfig,
9493
- outputStream: sink
9494
- });
9495
- return content.join("");
9485
+ const generator = new ReferenceGenerator({ referenceConfig });
9486
+ return generator.generateToString();
9496
9487
  }
9497
9488
 
9498
9489
  // ../commons/path-utils/lib/convertToOsPath.js
@@ -9686,41 +9677,19 @@ var import_path3 = require("path");
9686
9677
 
9687
9678
  // src/pipeline/github/createReplayBranch.ts
9688
9679
  async function createReplayBranch(repository, branchName, commitMessage, replayConflictInfo, logger) {
9689
- if (replayConflictInfo?.hasConflicts) {
9690
- const parentSha = replayConflictInfo.previousGenerationSha;
9691
- const lockfileRestoreSha = replayConflictInfo.baseBranchHead ?? replayConflictInfo.previousGenerationSha;
9692
- logger.debug(`Creating divergent PR branch with generation tree (parent: ${parentSha.substring(0, 7)})`);
9693
- const genTreeHash = await repository.getCommitTreeHash(replayConflictInfo.currentGenerationSha);
9694
- const syntheticCommitSha = await repository.commitTree(
9695
- genTreeHash,
9696
- parentSha,
9697
- `[fern-generated] ${commitMessage ?? "Update SDK"}`
9698
- );
9699
- await repository.createBranchFromCommit(branchName, syntheticCommitSha);
9700
- try {
9701
- await repository.restoreFilesFromCommit(lockfileRestoreSha, ".fern/replay.lock");
9702
- await repository.commitAllChanges(`[fern-generated] ${commitMessage ?? "Update SDK"}`);
9703
- } catch (error) {
9704
- logger.debug(
9705
- `Could not restore lockfile from base branch: ${error instanceof Error ? error.message : String(error)}`
9706
- );
9707
- }
9708
- return syntheticCommitSha;
9709
- } else if (replayConflictInfo != null) {
9710
- const parentSha = replayConflictInfo.previousGenerationSha;
9711
- logger.debug(`Creating linear PR branch from HEAD (no conflicts)`);
9712
- await repository.createBranchFromHead(branchName);
9713
- const genTreeHash = await repository.getCommitTreeHash(replayConflictInfo.currentGenerationSha);
9714
- const tagCommitSha = await repository.commitTree(
9715
- genTreeHash,
9716
- parentSha,
9717
- `[fern-generated] ${commitMessage ?? "Update SDK"}`
9718
- );
9719
- return tagCommitSha;
9720
- } else {
9680
+ if (replayConflictInfo == null) {
9721
9681
  await repository.createBranchFromHead(branchName);
9722
9682
  return void 0;
9723
9683
  }
9684
+ const parentSha = replayConflictInfo.previousGenerationSha;
9685
+ await repository.createBranchFromHead(branchName);
9686
+ const genTreeHash = await repository.getCommitTreeHash(replayConflictInfo.currentGenerationSha);
9687
+ const tagCommitSha = await repository.commitTree(
9688
+ genTreeHash,
9689
+ parentSha,
9690
+ `[fern-generated] ${commitMessage ?? "Update SDK"}`
9691
+ );
9692
+ return tagCommitSha;
9724
9693
  }
9725
9694
 
9726
9695
  // src/pipeline/github/findExistingUpdatablePR.ts
@@ -9815,11 +9784,11 @@ function formatConflictReason(reason) {
9815
9784
  case "same-line-edit":
9816
9785
  return "The new generation changed the same lines you edited";
9817
9786
  case "new-file-both":
9818
- return "You and the generator both created this file";
9787
+ return "Both the generator and your customization created this file";
9819
9788
  case "base-generation-mismatch":
9820
- return "The generated code changed significantly around your edit";
9789
+ return "This customization was from a previous generation";
9821
9790
  case "patch-apply-failed":
9822
- return "Your change could not be applied to the new generated code";
9791
+ return "The customization could not be applied to the new code";
9823
9792
  default:
9824
9793
  return "Your edit overlaps with changes in the new generation";
9825
9794
  }
@@ -9828,7 +9797,7 @@ function patchDescription(detail) {
9828
9797
  if (detail.patchMessage && detail.patchMessage !== "update") {
9829
9798
  return detail.patchMessage;
9830
9799
  }
9831
- const firstFile = detail.files[0]?.file;
9800
+ const firstFile = detail.files[0];
9832
9801
  return firstFile != null ? `changes in ${firstFile}` : "customization";
9833
9802
  }
9834
9803
  function logReplaySummary(result, logger) {
@@ -9837,10 +9806,10 @@ function logReplaySummary(result, logger) {
9837
9806
  }
9838
9807
  const applied = result.patchesApplied ?? 0;
9839
9808
  const absorbed = result.patchesAbsorbed ?? 0;
9840
- const conflicts2 = result.patchesWithConflicts ?? 0;
9809
+ const unresolvedCount = result.unresolvedPatches?.length ?? 0;
9841
9810
  const preserved = applied - absorbed;
9842
9811
  logger.debug(
9843
- `Replay: flow=${result.flow}, detected=${result.patchesDetected ?? 0}, applied=${applied}, absorbed=${absorbed}, conflicts=${conflicts2}`
9812
+ `Replay: flow=${result.flow}, detected=${result.patchesDetected ?? 0}, applied=${applied}, absorbed=${absorbed}, unresolved=${unresolvedCount}`
9844
9813
  );
9845
9814
  if (preserved > 0) {
9846
9815
  const absorbedNote = absorbed > 0 ? ` (some customizations now part of generated code)` : "";
@@ -9848,14 +9817,17 @@ function logReplaySummary(result, logger) {
9848
9817
  } else if (absorbed > 0) {
9849
9818
  logger.info(`Replay: customizations now part of generated code`);
9850
9819
  }
9851
- if (conflicts2 > 0) {
9852
- const totalFiles = (result.conflictDetails ?? []).reduce((sum, d) => sum + d.files.length, 0);
9820
+ if (unresolvedCount > 0) {
9821
+ const totalFiles = (result.unresolvedPatches ?? []).reduce(
9822
+ (sum, patch) => sum + patch.conflictDetails.length,
9823
+ 0
9824
+ );
9853
9825
  logger.warn(
9854
- `Replay: ${plural(totalFiles, "file")} ${totalFiles === 1 ? "has" : "have"} merge conflicts \u2014 resolve in the PR or locally`
9826
+ `Replay: ${plural(totalFiles, "file")} ${totalFiles === 1 ? "has" : "have"} unresolved conflicts \u2014 resolve via \`fern replay resolve\``
9855
9827
  );
9856
- for (const detail of result.conflictDetails ?? []) {
9857
- logger.warn(` "${patchDescription(detail)}":`);
9858
- for (const file of detail.files) {
9828
+ for (const patch of result.unresolvedPatches ?? []) {
9829
+ logger.warn(` "${patchDescription(patch)}":`);
9830
+ for (const file of patch.conflictDetails) {
9859
9831
  logger.warn(` ${file.file} \u2014 ${formatConflictReason(file.conflictReason)}`);
9860
9832
  }
9861
9833
  }
@@ -9870,70 +9842,66 @@ function formatReplayPrBody(result, options) {
9870
9842
  }
9871
9843
  const applied = result.patchesApplied ?? 0;
9872
9844
  const absorbed = result.patchesAbsorbed ?? 0;
9873
- const conflicts2 = result.patchesWithConflicts ?? 0;
9845
+ const unresolvedPatches = result.unresolvedPatches ?? [];
9846
+ const hasUnresolved = unresolvedPatches.length > 0;
9874
9847
  const preserved = applied - absorbed;
9875
- if (preserved === 0 && conflicts2 === 0) {
9848
+ if (preserved === 0 && !hasUnresolved) {
9876
9849
  return void 0;
9877
9850
  }
9878
9851
  const parts = [];
9879
- if (preserved > 0 && conflicts2 === 0) {
9852
+ if (preserved > 0 && !hasUnresolved) {
9880
9853
  parts.push(`\u2705 Customizations automatically preserved in this update.`);
9881
9854
  } else if (preserved > 0) {
9855
+ const totalFiles = unresolvedPatches.reduce((sum, patch) => sum + patch.conflictDetails.length, 0);
9882
9856
  parts.push(
9883
- `\u2705 Customizations automatically preserved, but ${plural(conflicts2, "file")} ${conflicts2 === 1 ? "needs" : "need"} your attention.`
9857
+ `\u2705 Customizations automatically preserved, but ${plural(totalFiles, "file")} ${totalFiles === 1 ? "needs" : "need"} your attention.`
9884
9858
  );
9885
9859
  }
9886
- if (conflicts2 > 0) {
9887
- const allFiles = (result.conflictDetails ?? []).flatMap((d) => d.files);
9888
- const totalFiles = allFiles.length;
9860
+ if (hasUnresolved) {
9861
+ const totalFiles = unresolvedPatches.reduce((sum, patch) => sum + patch.conflictDetails.length, 0);
9889
9862
  parts.push(`
9890
- ### \u26A0\uFE0F Action required: ${plural(totalFiles, "file")} with customization conflicts
9863
+ ### Action required: ${plural(totalFiles, "file")} with unresolved customization conflicts
9891
9864
  `);
9892
9865
  parts.push(
9893
- `You previously customized ${totalFiles === 1 ? "a file" : "files"} in this SDK. This generation changed the same code, so your ${totalFiles === 1 ? "edit" : "edits"} couldn't be applied automatically.
9894
- `
9895
- );
9896
- parts.push(
9897
- `> \u26A0\uFE0F **Label guide:** In GitHub's conflict editor, \`<<<<<<< HEAD\` ("Accept current") = new generated code. \`>>>>>>> main\` ("Accept incoming") = your customization.
9866
+ `The new generation changed code you previously customized. Non-conflicting customizations have been applied automatically. The following files need manual resolution:
9898
9867
  `
9899
9868
  );
9900
9869
  parts.push(`| File | Your customization | Why it conflicted |`);
9901
9870
  parts.push(`|------|-------------------|-------------------|`);
9902
- for (const detail of result.conflictDetails ?? []) {
9903
- const description = patchDescription(detail);
9904
- for (const f of detail.files) {
9905
- parts.push(`| \`${f.file}\` | ${description} | ${formatConflictReason(f.conflictReason)} |`);
9871
+ for (const patch of unresolvedPatches) {
9872
+ const description = patchDescription(patch);
9873
+ for (const file of patch.conflictDetails) {
9874
+ parts.push(`| \`${file.file}\` | ${description} | ${formatConflictReason(file.conflictReason)} |`);
9906
9875
  }
9907
9876
  }
9908
9877
  const branch = options?.branchName ?? "<branch-name>";
9878
+ const repoUri = options?.repoUri;
9879
+ const repoName = repoUri?.split("/").pop() ?? "<repo>";
9909
9880
  parts.push(`
9910
- #### How to fix
9911
- `);
9912
- parts.push(`**Option A: Resolve in GitHub (recommended for simple conflicts)**
9913
- `);
9914
- parts.push(`1. Click the **Resolve conflicts** button below`);
9915
- parts.push(`2. For each file, choose which code to keep:`);
9916
- parts.push(` - \`<<<<<<< HEAD\` / "Accept current changes" = **new generated code**`);
9917
- parts.push(` - \`>>>>>>> main\` / "Accept incoming changes" = **your customization**`);
9918
- parts.push(` - Or manually combine both, then delete the conflict markers`);
9919
- parts.push(`3. Click **Mark as resolved** for each file, then **Commit merge**
9920
- `);
9921
- parts.push(`**Option B: Resolve locally**
9881
+ #### How to resolve
9922
9882
  `);
9923
- parts.push(`1. Fetch and check out this branch:`);
9883
+ parts.push(`1. Check out this branch:`);
9924
9884
  parts.push(` \`\`\`sh`);
9925
- parts.push(` git fetch origin && git checkout ${branch}`);
9885
+ parts.push(` git fetch origin && git checkout -b ${branch} origin/${branch}`);
9926
9886
  parts.push(` \`\`\``);
9927
- parts.push(`2. Merge the base branch and resolve conflicts in your editor:`);
9928
- parts.push(` \`\`\`sh`);
9929
- parts.push(` git merge origin/main`);
9930
- parts.push(` \`\`\``);
9931
- parts.push(`3. Commit and push:`);
9932
- parts.push(` \`\`\`sh`);
9933
- parts.push(` git add -A && git commit -m "resolve conflicts" && git push`);
9934
- parts.push(` \`\`\`
9887
+ if (repoUri != null) {
9888
+ parts.push(` Or if you don't have the repo cloned:`);
9889
+ parts.push(` \`\`\`sh`);
9890
+ parts.push(
9891
+ ` git clone https://github.com/${repoUri}.git && cd ${repoName} && git checkout -b ${branch} origin/${branch}`
9892
+ );
9893
+ parts.push(` \`\`\``);
9894
+ }
9895
+ parts.push(`2. Run: \`fern replay resolve\``);
9896
+ parts.push(`3. Open the conflicting files in your editor \u2014 you'll see standard merge conflict markers`);
9897
+ parts.push(`4. Resolve using your editor's merge tools (VS Code, IntelliJ, etc.)`);
9898
+ parts.push(`5. Run: \`fern replay resolve\` again to finalize`);
9899
+ parts.push(`6. Push your changes
9935
9900
  `);
9936
- parts.push(`Your resolved changes will be remembered on future SDK generations.`);
9901
+ parts.push(`Your resolved customizations will be remembered on future SDK generations.`);
9902
+ parts.push(
9903
+ `If you merge this PR without resolving, your unresolved customizations will conflict again on the next generation.`
9904
+ );
9937
9905
  }
9938
9906
  return parts.join("\n");
9939
9907
  }
@@ -10069,16 +10037,9 @@ var GithubStep = class extends BaseStep {
10069
10037
  }
10070
10038
  }
10071
10039
  }
10072
- if (!this.config.previewMode) {
10073
- const headSha = await repository.getHeadSha();
10074
- await this.postReplayConflictStatus(octokit, owner, repo, headSha, replayConflictInfo, replayResult);
10075
- if (isUpdatingExistingPR && existingPR != null) {
10076
- await this.togglePrDraftState(octokit, existingPR, replayConflictInfo);
10077
- }
10078
- }
10079
10040
  const finalCommitMessage = this.config.commitMessage ?? "SDK Generation";
10080
10041
  const { prTitle, prBody } = parseCommitMessageForPR(finalCommitMessage);
10081
- const replaySection = formatReplayPrBody(replayResult, { branchName: prBranch });
10042
+ const replaySection = formatReplayPrBody(replayResult, { branchName: prBranch, repoUri: this.config.uri });
10082
10043
  const enrichedBody = replaySection != null ? prBody + "\n\n---\n\n" + replaySection : prBody;
10083
10044
  if (isUpdatingExistingPR && existingPR != null) {
10084
10045
  this.logger.info(`Updated existing pull request: ${existingPR.htmlUrl}`);
@@ -10100,7 +10061,6 @@ var GithubStep = class extends BaseStep {
10100
10061
  }
10101
10062
  } else {
10102
10063
  const head = `${owner}:${prBranch}`;
10103
- const hasConflicts = replayConflictInfo?.hasConflicts === true;
10104
10064
  try {
10105
10065
  const { data: pullRequest } = await octokit.pulls.create({
10106
10066
  owner,
@@ -10108,8 +10068,7 @@ var GithubStep = class extends BaseStep {
10108
10068
  title: prTitle,
10109
10069
  body: enrichedBody,
10110
10070
  head,
10111
- base: baseBranch,
10112
- draft: hasConflicts
10071
+ base: baseBranch
10113
10072
  });
10114
10073
  this.logger.info(`Created pull request: ${pullRequest.html_url}`);
10115
10074
  result.prUrl = pullRequest.html_url;
@@ -10123,17 +10082,6 @@ var GithubStep = class extends BaseStep {
10123
10082
  }
10124
10083
  }
10125
10084
  }
10126
- if (!this.config.previewMode && result.prNumber != null && replayConflictInfo?.hasConflicts === true && replayResult != null) {
10127
- await this.postWebEditorFallbackComment(
10128
- octokit,
10129
- owner,
10130
- repo,
10131
- result.prNumber,
10132
- prBranch,
10133
- baseBranch,
10134
- replayResult
10135
- );
10136
- }
10137
10085
  return result;
10138
10086
  }
10139
10087
  async executePushMode(repository) {
@@ -10179,198 +10127,17 @@ var GithubStep = class extends BaseStep {
10179
10127
  if (replayResult == null) {
10180
10128
  return void 0;
10181
10129
  }
10182
- const hasConflicts = (replayResult.patchesWithConflicts ?? 0) > 0;
10183
10130
  const previousGenerationSha = replayResult.previousGenerationSha;
10184
10131
  const currentGenerationSha = replayResult.currentGenerationSha;
10185
10132
  if (previousGenerationSha != null && currentGenerationSha != null) {
10186
10133
  return {
10187
10134
  previousGenerationSha,
10188
- currentGenerationSha,
10189
- hasConflicts,
10190
- baseBranchHead: replayResult.baseBranchHead
10135
+ currentGenerationSha
10191
10136
  };
10192
10137
  }
10193
10138
  return void 0;
10194
10139
  }
10195
- async togglePrDraftState(octokit, existingPR, replayConflictInfo) {
10196
- const hasConflicts = replayConflictInfo?.hasConflicts === true;
10197
- if (hasConflicts && !existingPR.isDraft) {
10198
- await this.convertPrToDraft(octokit, existingPR.nodeId, existingPR.number);
10199
- } else if (!hasConflicts && existingPR.isDraft) {
10200
- await this.markPrReady(octokit, existingPR.nodeId, existingPR.number);
10201
- }
10202
- }
10203
- async convertPrToDraft(octokit, nodeId, prNumber) {
10204
- try {
10205
- await octokit.graphql(
10206
- `mutation($id: ID!) {
10207
- convertPullRequestToDraft(input: {pullRequestId: $id}) {
10208
- pullRequest { isDraft }
10209
- }
10210
- }`,
10211
- { id: nodeId }
10212
- );
10213
- this.logger.info(`Converted PR #${prNumber} to draft due to replay conflicts`);
10214
- } catch (error) {
10215
- this.logger.debug(
10216
- `Could not convert PR to draft: ${error instanceof Error ? error.message : String(error)}`
10217
- );
10218
- }
10219
- }
10220
- async markPrReady(octokit, nodeId, prNumber) {
10221
- try {
10222
- await octokit.graphql(
10223
- `mutation($id: ID!) {
10224
- markPullRequestReadyForReview(input: {pullRequestId: $id}) {
10225
- pullRequest { isDraft }
10226
- }
10227
- }`,
10228
- { id: nodeId }
10229
- );
10230
- this.logger.info(`Marked PR #${prNumber} as ready (conflicts resolved)`);
10231
- } catch (error) {
10232
- this.logger.debug(`Could not mark PR as ready: ${error instanceof Error ? error.message : String(error)}`);
10233
- }
10234
- }
10235
- async postReplayConflictStatus(octokit, owner, repo, sha, replayConflictInfo, replayResult) {
10236
- if (replayResult == null || !replayResult.executed) {
10237
- return;
10238
- }
10239
- const hasConflicts = replayConflictInfo?.hasConflicts === true;
10240
- const conflictFileCount = (replayResult.conflictDetails ?? []).reduce(
10241
- (sum, detail) => sum + detail.files.length,
10242
- 0
10243
- );
10244
- const sanitizedName = this.config.generatorName?.replace(/\//g, "--");
10245
- const context = sanitizedName != null ? `fern / sdk customizations / ${sanitizedName}` : "fern / sdk customizations";
10246
- try {
10247
- await octokit.repos.createCommitStatus({
10248
- owner,
10249
- repo,
10250
- sha,
10251
- state: hasConflicts ? "failure" : "success",
10252
- context,
10253
- description: hasConflicts ? `${conflictFileCount} file(s) need manual conflict resolution \u2014 see PR description` : "All customizations applied"
10254
- });
10255
- this.logger.debug(`Posted ${hasConflicts ? "failing" : "passing"} commit status (${context})`);
10256
- } catch (error) {
10257
- this.logger.debug(
10258
- `Could not post commit status: ${error instanceof Error ? error.message : String(error)}`
10259
- );
10260
- }
10261
- }
10262
- async postWebEditorFallbackComment(octokit, owner, repo, prNumber, branchName, baseBranch, replayResult) {
10263
- try {
10264
- const webEditorLikelyDisabled = await this.isWebEditorLikelyDisabled(
10265
- octokit,
10266
- owner,
10267
- repo,
10268
- prNumber,
10269
- replayResult
10270
- );
10271
- if (!webEditorLikelyDisabled) {
10272
- this.logger.debug(`PR #${prNumber}: web conflict editor appears usable, skipping fallback comment`);
10273
- return;
10274
- }
10275
- const comment = buildWebEditorFallbackComment(branchName, baseBranch);
10276
- await octokit.issues.createComment({
10277
- owner,
10278
- repo,
10279
- issue_number: prNumber,
10280
- body: comment
10281
- });
10282
- this.logger.info(`Posted web-editor fallback comment on PR #${prNumber}`);
10283
- } catch (error) {
10284
- this.logger.debug(
10285
- `Could not post web-editor fallback comment: ${error instanceof Error ? error.message : String(error)}`
10286
- );
10287
- }
10288
- }
10289
- async isWebEditorLikelyDisabled(octokit, owner, repo, prNumber, replayResult) {
10290
- const conflictDetails = replayResult.conflictDetails ?? [];
10291
- const allConflictFiles = conflictDetails.flatMap((d) => d.files);
10292
- const totalConflictFiles = allConflictFiles.length;
10293
- const hasFileDeletionConflict = allConflictFiles.some((f) => {
10294
- const status = (f.status ?? "").toLowerCase();
10295
- const reason = (f.conflictReason ?? "").toLowerCase();
10296
- return status === "skipped" || reason.includes("delete") || reason.includes("removed");
10297
- });
10298
- if (hasFileDeletionConflict) {
10299
- this.logger.debug(`PR #${prNumber}: detected file deletion conflict, web editor likely disabled`);
10300
- return true;
10301
- }
10302
- if (totalConflictFiles >= WEB_EDITOR_FILE_THRESHOLD) {
10303
- this.logger.debug(
10304
- `PR #${prNumber}: ${totalConflictFiles} conflicting files exceeds threshold (${WEB_EDITOR_FILE_THRESHOLD}), web editor likely disabled`
10305
- );
10306
- return true;
10307
- }
10308
- const totalHunks = (replayResult.conflicts ?? []).reduce(
10309
- (sum, fileConflict) => sum + fileConflict.conflicts.length,
10310
- 0
10311
- );
10312
- if (totalHunks >= WEB_EDITOR_HUNK_THRESHOLD) {
10313
- this.logger.debug(
10314
- `PR #${prNumber}: ${totalHunks} conflict hunks exceeds threshold (${WEB_EDITOR_HUNK_THRESHOLD}), web editor likely disabled`
10315
- );
10316
- return true;
10317
- }
10318
- const mergeableState = await this.pollMergeableState(octokit, owner, repo, prNumber);
10319
- if (mergeableState.mergeable === null) {
10320
- this.logger.debug(`PR #${prNumber}: mergeable state still null after polling, web editor likely disabled`);
10321
- return true;
10322
- }
10323
- return false;
10324
- }
10325
- async pollMergeableState(octokit, owner, repo, prNumber) {
10326
- for (let attempt = 0; attempt < MERGEABLE_POLL_MAX_ATTEMPTS; attempt++) {
10327
- const { data: pr } = await octokit.pulls.get({
10328
- owner,
10329
- repo,
10330
- pull_number: prNumber
10331
- });
10332
- if (pr.mergeable != null) {
10333
- return {
10334
- mergeable: pr.mergeable,
10335
- mergeableState: pr.mergeable_state
10336
- };
10337
- }
10338
- if (attempt < MERGEABLE_POLL_MAX_ATTEMPTS - 1) {
10339
- await sleep(MERGEABLE_POLL_DELAY_MS);
10340
- }
10341
- }
10342
- return { mergeable: null, mergeableState: "unknown" };
10343
- }
10344
10140
  };
10345
- var WEB_EDITOR_FILE_THRESHOLD = 20;
10346
- var WEB_EDITOR_HUNK_THRESHOLD = 50;
10347
- var MERGEABLE_POLL_MAX_ATTEMPTS = 3;
10348
- var MERGEABLE_POLL_DELAY_MS = 1500;
10349
- function buildWebEditorFallbackComment(branchName, baseBranch) {
10350
- const lines = [
10351
- `> **Note:** These conflicts may be too complex for GitHub's web editor. To resolve locally:`,
10352
- `>`,
10353
- `> 1. Check out this branch:`,
10354
- `> \`\`\`sh`,
10355
- `> git fetch origin && git checkout ${branchName}`,
10356
- `> \`\`\``,
10357
- `> 2. Merge the base branch:`,
10358
- `> \`\`\`sh`,
10359
- `> git merge origin/${baseBranch}`,
10360
- `> \`\`\``,
10361
- `> 3. Resolve conflicts in your editor (VS Code, IntelliJ, etc. have built-in merge tools)`,
10362
- `> 4. Commit and push:`,
10363
- `> \`\`\`sh`,
10364
- `> git add -A && git commit -m "resolve conflicts" && git push`,
10365
- `> \`\`\``,
10366
- `>`,
10367
- `> **Label guide:** In your editor's merge view, "current" = new generated code, "incoming" = your customization.`
10368
- ];
10369
- return lines.join("\n");
10370
- }
10371
- function sleep(ms) {
10372
- return new Promise((resolve3) => setTimeout(resolve3, ms));
10373
- }
10374
10141
 
10375
10142
  // src/replay/replay-run.ts
10376
10143
  var import_replay = require("@fern-api/replay");
@@ -10579,8 +10346,7 @@ var ReplayStep = class extends BaseStep {
10579
10346
  flow: "first-generation",
10580
10347
  patchesDetected: 0,
10581
10348
  patchesApplied: 0,
10582
- patchesWithConflicts: 0,
10583
- conflicts: []
10349
+ patchesWithConflicts: 0
10584
10350
  };
10585
10351
  }
10586
10352
  const report = result.report;
@@ -10598,22 +10364,12 @@ var ReplayStep = class extends BaseStep {
10598
10364
  patchesRepointed: report.patchesRepointed,
10599
10365
  patchesContentRebased: report.patchesContentRebased,
10600
10366
  patchesKeptAsUserOwned: report.patchesKeptAsUserOwned,
10601
- conflicts: report.conflicts.map((fileResult) => ({
10602
- filePath: fileResult.file,
10603
- conflicts: fileResult.conflicts?.map((conflict) => ({
10604
- startLine: conflict.startLine,
10605
- endLine: conflict.endLine,
10606
- ours: conflict.ours,
10607
- theirs: conflict.theirs
10608
- })) ?? []
10609
- })),
10610
- conflictDetails: report.conflictDetails?.map((detail) => ({
10611
- patchId: detail.patchId,
10612
- patchMessage: detail.patchMessage,
10613
- reason: detail.reason,
10614
- files: detail.files.map((f) => ({
10367
+ unresolvedPatches: report.unresolvedPatches?.map((info) => ({
10368
+ patchId: info.patchId,
10369
+ patchMessage: info.patchMessage,
10370
+ files: info.files,
10371
+ conflictDetails: info.conflictDetails.map((f) => ({
10615
10372
  file: f.file,
10616
- status: f.status,
10617
10373
  conflictReason: f.conflictReason
10618
10374
  }))
10619
10375
  })),
@@ -10738,7 +10494,10 @@ Base generation: ${bootstrapResult.generationCommit.sha.slice(0, 7)}`;
10738
10494
  `user.name=${import_replay2.FERN_BOT_NAME}`,
10739
10495
  "-c",
10740
10496
  `user.email=${import_replay2.FERN_BOT_EMAIL}`,
10497
+ "-c",
10498
+ "commit.gpgsign=false",
10741
10499
  "commit",
10500
+ "--no-verify",
10742
10501
  "-m",
10743
10502
  commitMessage
10744
10503
  ]);
@@ -10874,6 +10633,14 @@ function buildPrBody(result) {
10874
10633
  lines.push("Once merged, your customizations will be automatically preserved on future `fern generate` runs.");
10875
10634
  return lines.join("\n");
10876
10635
  }
10636
+
10637
+ // src/replay/replay-resolve.ts
10638
+ var import_replay3 = require("@fern-api/replay");
10639
+ async function replayResolve(params) {
10640
+ return (0, import_replay3.resolve)(params.outputDir, {
10641
+ checkMarkers: params.checkMarkers
10642
+ });
10643
+ }
10877
10644
  // Annotate the CommonJS export names for ESM import in node:
10878
10645
  0 && (module.exports = {
10879
10646
  PostGenerationPipeline,
@@ -10889,5 +10656,6 @@ function buildPrBody(result) {
10889
10656
  githubRelease,
10890
10657
  logReplaySummary,
10891
10658
  replayInit,
10659
+ replayResolve,
10892
10660
  replayRun
10893
10661
  });