@fern-api/generator-cli 0.6.10 → 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 +116 -354
  3. package/dist/cli.js +274 -323
  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
  });
@@ -8456,16 +8454,25 @@ var ReadmeGenerator = class {
8456
8454
  this.apiName = this.readmeConfig.apiName ?? this.organizationPascalCase;
8457
8455
  }
8458
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 }) {
8459
8468
  const blocks = await this.generateBlocks();
8460
8469
  const mergedBlocks = await this.mergeBlocks({ blocks });
8461
- const writer = new StreamWriter(output);
8462
8470
  await this.writeHeader({ writer });
8463
8471
  await this.writeTableOfContents({ writer, blocks: mergedBlocks });
8464
8472
  await this.writeBlocks({
8465
8473
  writer,
8466
8474
  blocks: mergedBlocks
8467
8475
  });
8468
- await writer.end();
8469
8476
  }
8470
8477
  async generateBlocks() {
8471
8478
  const blocks = [];
@@ -9337,28 +9344,14 @@ async function generateReadmeToStream(params) {
9337
9344
  }
9338
9345
  async function generateReadme(params) {
9339
9346
  const { originalReadmeContent, readmeConfig } = params;
9340
- const content = [];
9341
- const sink = new import_node_stream.Writable({
9342
- write(chunk, _encoding, callback) {
9343
- if (typeof chunk === "string") {
9344
- content.push(chunk);
9345
- } else {
9346
- content.push(Buffer.from(chunk).toString("utf8"));
9347
- }
9348
- callback();
9349
- }
9350
- });
9351
- await generateReadmeToStream({
9352
- originalReadmeContent,
9347
+ const generator = new ReadmeGenerator({
9348
+ readmeParser: new ReadmeParser(),
9353
9349
  readmeConfig,
9354
- outputStream: sink
9350
+ originalReadme: originalReadmeContent
9355
9351
  });
9356
- return content.join("");
9352
+ return generator.generateReadmeToString();
9357
9353
  }
9358
9354
 
9359
- // src/api/generate-reference.ts
9360
- var import_node_stream2 = require("stream");
9361
-
9362
9355
  // src/reference/ReferenceGenerator.ts
9363
9356
  var ReferenceGenerator = class {
9364
9357
  referenceConfig;
@@ -9369,6 +9362,16 @@ var ReferenceGenerator = class {
9369
9362
  }
9370
9363
  async generate({ output }) {
9371
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 }) {
9372
9375
  await writer.writeLine("# Reference");
9373
9376
  if (this.referenceConfig.rootSection != null) {
9374
9377
  await this.writeRootSection({
@@ -9379,7 +9382,6 @@ var ReferenceGenerator = class {
9379
9382
  for (const section of this.referenceConfig.sections) {
9380
9383
  await this.writeSection({ section, writer });
9381
9384
  }
9382
- await writer.end();
9383
9385
  }
9384
9386
  async writeRootSection({
9385
9387
  section,
@@ -9480,22 +9482,8 @@ async function generateReferenceToStream(params) {
9480
9482
  }
9481
9483
  async function generateReference(params) {
9482
9484
  const { referenceConfig } = params;
9483
- const content = [];
9484
- const sink = new import_node_stream2.Writable({
9485
- write(chunk, _encoding, callback) {
9486
- if (typeof chunk === "string") {
9487
- content.push(chunk);
9488
- } else {
9489
- content.push(Buffer.from(chunk).toString("utf8"));
9490
- }
9491
- callback();
9492
- }
9493
- });
9494
- await generateReferenceToStream({
9495
- referenceConfig,
9496
- outputStream: sink
9497
- });
9498
- return content.join("");
9485
+ const generator = new ReferenceGenerator({ referenceConfig });
9486
+ return generator.generateToString();
9499
9487
  }
9500
9488
 
9501
9489
  // ../commons/path-utils/lib/convertToOsPath.js
@@ -9689,41 +9677,19 @@ var import_path3 = require("path");
9689
9677
 
9690
9678
  // src/pipeline/github/createReplayBranch.ts
9691
9679
  async function createReplayBranch(repository, branchName, commitMessage, replayConflictInfo, logger) {
9692
- if (replayConflictInfo?.hasConflicts) {
9693
- const parentSha = replayConflictInfo.previousGenerationSha;
9694
- const lockfileRestoreSha = replayConflictInfo.baseBranchHead ?? replayConflictInfo.previousGenerationSha;
9695
- logger.debug(`Creating divergent PR branch with generation tree (parent: ${parentSha.substring(0, 7)})`);
9696
- const genTreeHash = await repository.getCommitTreeHash(replayConflictInfo.currentGenerationSha);
9697
- const syntheticCommitSha = await repository.commitTree(
9698
- genTreeHash,
9699
- parentSha,
9700
- `[fern-generated] ${commitMessage ?? "Update SDK"}`
9701
- );
9702
- await repository.createBranchFromCommit(branchName, syntheticCommitSha);
9703
- try {
9704
- await repository.restoreFilesFromCommit(lockfileRestoreSha, ".fern/replay.lock");
9705
- await repository.commitAllChanges(`[fern-generated] ${commitMessage ?? "Update SDK"}`);
9706
- } catch (error) {
9707
- logger.debug(
9708
- `Could not restore lockfile from base branch: ${error instanceof Error ? error.message : String(error)}`
9709
- );
9710
- }
9711
- return syntheticCommitSha;
9712
- } else if (replayConflictInfo != null) {
9713
- const parentSha = replayConflictInfo.previousGenerationSha;
9714
- logger.debug(`Creating linear PR branch from HEAD (no conflicts)`);
9715
- await repository.createBranchFromHead(branchName);
9716
- const genTreeHash = await repository.getCommitTreeHash(replayConflictInfo.currentGenerationSha);
9717
- const tagCommitSha = await repository.commitTree(
9718
- genTreeHash,
9719
- parentSha,
9720
- `[fern-generated] ${commitMessage ?? "Update SDK"}`
9721
- );
9722
- return tagCommitSha;
9723
- } else {
9680
+ if (replayConflictInfo == null) {
9724
9681
  await repository.createBranchFromHead(branchName);
9725
9682
  return void 0;
9726
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;
9727
9693
  }
9728
9694
 
9729
9695
  // src/pipeline/github/findExistingUpdatablePR.ts
@@ -9818,11 +9784,11 @@ function formatConflictReason(reason) {
9818
9784
  case "same-line-edit":
9819
9785
  return "The new generation changed the same lines you edited";
9820
9786
  case "new-file-both":
9821
- return "You and the generator both created this file";
9787
+ return "Both the generator and your customization created this file";
9822
9788
  case "base-generation-mismatch":
9823
- return "The generated code changed significantly around your edit";
9789
+ return "This customization was from a previous generation";
9824
9790
  case "patch-apply-failed":
9825
- return "Your change could not be applied to the new generated code";
9791
+ return "The customization could not be applied to the new code";
9826
9792
  default:
9827
9793
  return "Your edit overlaps with changes in the new generation";
9828
9794
  }
@@ -9831,7 +9797,7 @@ function patchDescription(detail) {
9831
9797
  if (detail.patchMessage && detail.patchMessage !== "update") {
9832
9798
  return detail.patchMessage;
9833
9799
  }
9834
- const firstFile = detail.files[0]?.file;
9800
+ const firstFile = detail.files[0];
9835
9801
  return firstFile != null ? `changes in ${firstFile}` : "customization";
9836
9802
  }
9837
9803
  function logReplaySummary(result, logger) {
@@ -9840,10 +9806,10 @@ function logReplaySummary(result, logger) {
9840
9806
  }
9841
9807
  const applied = result.patchesApplied ?? 0;
9842
9808
  const absorbed = result.patchesAbsorbed ?? 0;
9843
- const conflicts2 = result.patchesWithConflicts ?? 0;
9809
+ const unresolvedCount = result.unresolvedPatches?.length ?? 0;
9844
9810
  const preserved = applied - absorbed;
9845
9811
  logger.debug(
9846
- `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}`
9847
9813
  );
9848
9814
  if (preserved > 0) {
9849
9815
  const absorbedNote = absorbed > 0 ? ` (some customizations now part of generated code)` : "";
@@ -9851,14 +9817,17 @@ function logReplaySummary(result, logger) {
9851
9817
  } else if (absorbed > 0) {
9852
9818
  logger.info(`Replay: customizations now part of generated code`);
9853
9819
  }
9854
- if (conflicts2 > 0) {
9855
- 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
+ );
9856
9825
  logger.warn(
9857
- `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\``
9858
9827
  );
9859
- for (const detail of result.conflictDetails ?? []) {
9860
- logger.warn(` "${patchDescription(detail)}":`);
9861
- 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) {
9862
9831
  logger.warn(` ${file.file} \u2014 ${formatConflictReason(file.conflictReason)}`);
9863
9832
  }
9864
9833
  }
@@ -9873,70 +9842,66 @@ function formatReplayPrBody(result, options) {
9873
9842
  }
9874
9843
  const applied = result.patchesApplied ?? 0;
9875
9844
  const absorbed = result.patchesAbsorbed ?? 0;
9876
- const conflicts2 = result.patchesWithConflicts ?? 0;
9845
+ const unresolvedPatches = result.unresolvedPatches ?? [];
9846
+ const hasUnresolved = unresolvedPatches.length > 0;
9877
9847
  const preserved = applied - absorbed;
9878
- if (preserved === 0 && conflicts2 === 0) {
9848
+ if (preserved === 0 && !hasUnresolved) {
9879
9849
  return void 0;
9880
9850
  }
9881
9851
  const parts = [];
9882
- if (preserved > 0 && conflicts2 === 0) {
9852
+ if (preserved > 0 && !hasUnresolved) {
9883
9853
  parts.push(`\u2705 Customizations automatically preserved in this update.`);
9884
9854
  } else if (preserved > 0) {
9855
+ const totalFiles = unresolvedPatches.reduce((sum, patch) => sum + patch.conflictDetails.length, 0);
9885
9856
  parts.push(
9886
- `\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.`
9887
9858
  );
9888
9859
  }
9889
- if (conflicts2 > 0) {
9890
- const allFiles = (result.conflictDetails ?? []).flatMap((d) => d.files);
9891
- const totalFiles = allFiles.length;
9860
+ if (hasUnresolved) {
9861
+ const totalFiles = unresolvedPatches.reduce((sum, patch) => sum + patch.conflictDetails.length, 0);
9892
9862
  parts.push(`
9893
- ### \u26A0\uFE0F Action required: ${plural(totalFiles, "file")} with customization conflicts
9863
+ ### Action required: ${plural(totalFiles, "file")} with unresolved customization conflicts
9894
9864
  `);
9895
9865
  parts.push(
9896
- `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.
9897
- `
9898
- );
9899
- parts.push(
9900
- `> \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:
9901
9867
  `
9902
9868
  );
9903
9869
  parts.push(`| File | Your customization | Why it conflicted |`);
9904
9870
  parts.push(`|------|-------------------|-------------------|`);
9905
- for (const detail of result.conflictDetails ?? []) {
9906
- const description = patchDescription(detail);
9907
- for (const f of detail.files) {
9908
- 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)} |`);
9909
9875
  }
9910
9876
  }
9911
9877
  const branch = options?.branchName ?? "<branch-name>";
9878
+ const repoUri = options?.repoUri;
9879
+ const repoName = repoUri?.split("/").pop() ?? "<repo>";
9912
9880
  parts.push(`
9913
- #### How to fix
9914
- `);
9915
- parts.push(`**Option A: Resolve in GitHub (recommended for simple conflicts)**
9916
- `);
9917
- parts.push(`1. Click the **Resolve conflicts** button below`);
9918
- parts.push(`2. For each file, choose which code to keep:`);
9919
- parts.push(` - \`<<<<<<< HEAD\` / "Accept current changes" = **new generated code**`);
9920
- parts.push(` - \`>>>>>>> main\` / "Accept incoming changes" = **your customization**`);
9921
- parts.push(` - Or manually combine both, then delete the conflict markers`);
9922
- parts.push(`3. Click **Mark as resolved** for each file, then **Commit merge**
9923
- `);
9924
- parts.push(`**Option B: Resolve locally**
9881
+ #### How to resolve
9925
9882
  `);
9926
- parts.push(`1. Fetch and check out this branch:`);
9883
+ parts.push(`1. Check out this branch:`);
9927
9884
  parts.push(` \`\`\`sh`);
9928
- parts.push(` git fetch origin && git checkout ${branch}`);
9885
+ parts.push(` git fetch origin && git checkout -b ${branch} origin/${branch}`);
9929
9886
  parts.push(` \`\`\``);
9930
- parts.push(`2. Merge the base branch and resolve conflicts in your editor:`);
9931
- parts.push(` \`\`\`sh`);
9932
- parts.push(` git merge origin/main`);
9933
- parts.push(` \`\`\``);
9934
- parts.push(`3. Commit and push:`);
9935
- parts.push(` \`\`\`sh`);
9936
- parts.push(` git add -A && git commit -m "resolve conflicts" && git push`);
9937
- 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
9938
9900
  `);
9939
- 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
+ );
9940
9905
  }
9941
9906
  return parts.join("\n");
9942
9907
  }
@@ -10072,16 +10037,9 @@ var GithubStep = class extends BaseStep {
10072
10037
  }
10073
10038
  }
10074
10039
  }
10075
- if (!this.config.previewMode) {
10076
- const headSha = await repository.getHeadSha();
10077
- await this.postReplayConflictStatus(octokit, owner, repo, headSha, replayConflictInfo, replayResult);
10078
- if (isUpdatingExistingPR && existingPR != null) {
10079
- await this.togglePrDraftState(octokit, existingPR, replayConflictInfo);
10080
- }
10081
- }
10082
10040
  const finalCommitMessage = this.config.commitMessage ?? "SDK Generation";
10083
10041
  const { prTitle, prBody } = parseCommitMessageForPR(finalCommitMessage);
10084
- const replaySection = formatReplayPrBody(replayResult, { branchName: prBranch });
10042
+ const replaySection = formatReplayPrBody(replayResult, { branchName: prBranch, repoUri: this.config.uri });
10085
10043
  const enrichedBody = replaySection != null ? prBody + "\n\n---\n\n" + replaySection : prBody;
10086
10044
  if (isUpdatingExistingPR && existingPR != null) {
10087
10045
  this.logger.info(`Updated existing pull request: ${existingPR.htmlUrl}`);
@@ -10103,7 +10061,6 @@ var GithubStep = class extends BaseStep {
10103
10061
  }
10104
10062
  } else {
10105
10063
  const head = `${owner}:${prBranch}`;
10106
- const hasConflicts = replayConflictInfo?.hasConflicts === true;
10107
10064
  try {
10108
10065
  const { data: pullRequest } = await octokit.pulls.create({
10109
10066
  owner,
@@ -10111,8 +10068,7 @@ var GithubStep = class extends BaseStep {
10111
10068
  title: prTitle,
10112
10069
  body: enrichedBody,
10113
10070
  head,
10114
- base: baseBranch,
10115
- draft: hasConflicts
10071
+ base: baseBranch
10116
10072
  });
10117
10073
  this.logger.info(`Created pull request: ${pullRequest.html_url}`);
10118
10074
  result.prUrl = pullRequest.html_url;
@@ -10126,17 +10082,6 @@ var GithubStep = class extends BaseStep {
10126
10082
  }
10127
10083
  }
10128
10084
  }
10129
- if (!this.config.previewMode && result.prNumber != null && replayConflictInfo?.hasConflicts === true && replayResult != null) {
10130
- await this.postWebEditorFallbackComment(
10131
- octokit,
10132
- owner,
10133
- repo,
10134
- result.prNumber,
10135
- prBranch,
10136
- baseBranch,
10137
- replayResult
10138
- );
10139
- }
10140
10085
  return result;
10141
10086
  }
10142
10087
  async executePushMode(repository) {
@@ -10182,198 +10127,17 @@ var GithubStep = class extends BaseStep {
10182
10127
  if (replayResult == null) {
10183
10128
  return void 0;
10184
10129
  }
10185
- const hasConflicts = (replayResult.patchesWithConflicts ?? 0) > 0;
10186
10130
  const previousGenerationSha = replayResult.previousGenerationSha;
10187
10131
  const currentGenerationSha = replayResult.currentGenerationSha;
10188
10132
  if (previousGenerationSha != null && currentGenerationSha != null) {
10189
10133
  return {
10190
10134
  previousGenerationSha,
10191
- currentGenerationSha,
10192
- hasConflicts,
10193
- baseBranchHead: replayResult.baseBranchHead
10135
+ currentGenerationSha
10194
10136
  };
10195
10137
  }
10196
10138
  return void 0;
10197
10139
  }
10198
- async togglePrDraftState(octokit, existingPR, replayConflictInfo) {
10199
- const hasConflicts = replayConflictInfo?.hasConflicts === true;
10200
- if (hasConflicts && !existingPR.isDraft) {
10201
- await this.convertPrToDraft(octokit, existingPR.nodeId, existingPR.number);
10202
- } else if (!hasConflicts && existingPR.isDraft) {
10203
- await this.markPrReady(octokit, existingPR.nodeId, existingPR.number);
10204
- }
10205
- }
10206
- async convertPrToDraft(octokit, nodeId, prNumber) {
10207
- try {
10208
- await octokit.graphql(
10209
- `mutation($id: ID!) {
10210
- convertPullRequestToDraft(input: {pullRequestId: $id}) {
10211
- pullRequest { isDraft }
10212
- }
10213
- }`,
10214
- { id: nodeId }
10215
- );
10216
- this.logger.info(`Converted PR #${prNumber} to draft due to replay conflicts`);
10217
- } catch (error) {
10218
- this.logger.debug(
10219
- `Could not convert PR to draft: ${error instanceof Error ? error.message : String(error)}`
10220
- );
10221
- }
10222
- }
10223
- async markPrReady(octokit, nodeId, prNumber) {
10224
- try {
10225
- await octokit.graphql(
10226
- `mutation($id: ID!) {
10227
- markPullRequestReadyForReview(input: {pullRequestId: $id}) {
10228
- pullRequest { isDraft }
10229
- }
10230
- }`,
10231
- { id: nodeId }
10232
- );
10233
- this.logger.info(`Marked PR #${prNumber} as ready (conflicts resolved)`);
10234
- } catch (error) {
10235
- this.logger.debug(`Could not mark PR as ready: ${error instanceof Error ? error.message : String(error)}`);
10236
- }
10237
- }
10238
- async postReplayConflictStatus(octokit, owner, repo, sha, replayConflictInfo, replayResult) {
10239
- if (replayResult == null || !replayResult.executed) {
10240
- return;
10241
- }
10242
- const hasConflicts = replayConflictInfo?.hasConflicts === true;
10243
- const conflictFileCount = (replayResult.conflictDetails ?? []).reduce(
10244
- (sum, detail) => sum + detail.files.length,
10245
- 0
10246
- );
10247
- const sanitizedName = this.config.generatorName?.replace(/\//g, "--");
10248
- const context = sanitizedName != null ? `fern / sdk customizations / ${sanitizedName}` : "fern / sdk customizations";
10249
- try {
10250
- await octokit.repos.createCommitStatus({
10251
- owner,
10252
- repo,
10253
- sha,
10254
- state: hasConflicts ? "failure" : "success",
10255
- context,
10256
- description: hasConflicts ? `${conflictFileCount} file(s) need manual conflict resolution \u2014 see PR description` : "All customizations applied"
10257
- });
10258
- this.logger.debug(`Posted ${hasConflicts ? "failing" : "passing"} commit status (${context})`);
10259
- } catch (error) {
10260
- this.logger.debug(
10261
- `Could not post commit status: ${error instanceof Error ? error.message : String(error)}`
10262
- );
10263
- }
10264
- }
10265
- async postWebEditorFallbackComment(octokit, owner, repo, prNumber, branchName, baseBranch, replayResult) {
10266
- try {
10267
- const webEditorLikelyDisabled = await this.isWebEditorLikelyDisabled(
10268
- octokit,
10269
- owner,
10270
- repo,
10271
- prNumber,
10272
- replayResult
10273
- );
10274
- if (!webEditorLikelyDisabled) {
10275
- this.logger.debug(`PR #${prNumber}: web conflict editor appears usable, skipping fallback comment`);
10276
- return;
10277
- }
10278
- const comment = buildWebEditorFallbackComment(branchName, baseBranch);
10279
- await octokit.issues.createComment({
10280
- owner,
10281
- repo,
10282
- issue_number: prNumber,
10283
- body: comment
10284
- });
10285
- this.logger.info(`Posted web-editor fallback comment on PR #${prNumber}`);
10286
- } catch (error) {
10287
- this.logger.debug(
10288
- `Could not post web-editor fallback comment: ${error instanceof Error ? error.message : String(error)}`
10289
- );
10290
- }
10291
- }
10292
- async isWebEditorLikelyDisabled(octokit, owner, repo, prNumber, replayResult) {
10293
- const conflictDetails = replayResult.conflictDetails ?? [];
10294
- const allConflictFiles = conflictDetails.flatMap((d) => d.files);
10295
- const totalConflictFiles = allConflictFiles.length;
10296
- const hasFileDeletionConflict = allConflictFiles.some((f) => {
10297
- const status = (f.status ?? "").toLowerCase();
10298
- const reason = (f.conflictReason ?? "").toLowerCase();
10299
- return status === "skipped" || reason.includes("delete") || reason.includes("removed");
10300
- });
10301
- if (hasFileDeletionConflict) {
10302
- this.logger.debug(`PR #${prNumber}: detected file deletion conflict, web editor likely disabled`);
10303
- return true;
10304
- }
10305
- if (totalConflictFiles >= WEB_EDITOR_FILE_THRESHOLD) {
10306
- this.logger.debug(
10307
- `PR #${prNumber}: ${totalConflictFiles} conflicting files exceeds threshold (${WEB_EDITOR_FILE_THRESHOLD}), web editor likely disabled`
10308
- );
10309
- return true;
10310
- }
10311
- const totalHunks = (replayResult.conflicts ?? []).reduce(
10312
- (sum, fileConflict) => sum + fileConflict.conflicts.length,
10313
- 0
10314
- );
10315
- if (totalHunks >= WEB_EDITOR_HUNK_THRESHOLD) {
10316
- this.logger.debug(
10317
- `PR #${prNumber}: ${totalHunks} conflict hunks exceeds threshold (${WEB_EDITOR_HUNK_THRESHOLD}), web editor likely disabled`
10318
- );
10319
- return true;
10320
- }
10321
- const mergeableState = await this.pollMergeableState(octokit, owner, repo, prNumber);
10322
- if (mergeableState.mergeable === null) {
10323
- this.logger.debug(`PR #${prNumber}: mergeable state still null after polling, web editor likely disabled`);
10324
- return true;
10325
- }
10326
- return false;
10327
- }
10328
- async pollMergeableState(octokit, owner, repo, prNumber) {
10329
- for (let attempt = 0; attempt < MERGEABLE_POLL_MAX_ATTEMPTS; attempt++) {
10330
- const { data: pr } = await octokit.pulls.get({
10331
- owner,
10332
- repo,
10333
- pull_number: prNumber
10334
- });
10335
- if (pr.mergeable != null) {
10336
- return {
10337
- mergeable: pr.mergeable,
10338
- mergeableState: pr.mergeable_state
10339
- };
10340
- }
10341
- if (attempt < MERGEABLE_POLL_MAX_ATTEMPTS - 1) {
10342
- await sleep(MERGEABLE_POLL_DELAY_MS);
10343
- }
10344
- }
10345
- return { mergeable: null, mergeableState: "unknown" };
10346
- }
10347
10140
  };
10348
- var WEB_EDITOR_FILE_THRESHOLD = 20;
10349
- var WEB_EDITOR_HUNK_THRESHOLD = 50;
10350
- var MERGEABLE_POLL_MAX_ATTEMPTS = 3;
10351
- var MERGEABLE_POLL_DELAY_MS = 1500;
10352
- function buildWebEditorFallbackComment(branchName, baseBranch) {
10353
- const lines = [
10354
- `> **Note:** These conflicts may be too complex for GitHub's web editor. To resolve locally:`,
10355
- `>`,
10356
- `> 1. Check out this branch:`,
10357
- `> \`\`\`sh`,
10358
- `> git fetch origin && git checkout ${branchName}`,
10359
- `> \`\`\``,
10360
- `> 2. Merge the base branch:`,
10361
- `> \`\`\`sh`,
10362
- `> git merge origin/${baseBranch}`,
10363
- `> \`\`\``,
10364
- `> 3. Resolve conflicts in your editor (VS Code, IntelliJ, etc. have built-in merge tools)`,
10365
- `> 4. Commit and push:`,
10366
- `> \`\`\`sh`,
10367
- `> git add -A && git commit -m "resolve conflicts" && git push`,
10368
- `> \`\`\``,
10369
- `>`,
10370
- `> **Label guide:** In your editor's merge view, "current" = new generated code, "incoming" = your customization.`
10371
- ];
10372
- return lines.join("\n");
10373
- }
10374
- function sleep(ms) {
10375
- return new Promise((resolve3) => setTimeout(resolve3, ms));
10376
- }
10377
10141
 
10378
10142
  // src/replay/replay-run.ts
10379
10143
  var import_replay = require("@fern-api/replay");
@@ -10582,8 +10346,7 @@ var ReplayStep = class extends BaseStep {
10582
10346
  flow: "first-generation",
10583
10347
  patchesDetected: 0,
10584
10348
  patchesApplied: 0,
10585
- patchesWithConflicts: 0,
10586
- conflicts: []
10349
+ patchesWithConflicts: 0
10587
10350
  };
10588
10351
  }
10589
10352
  const report = result.report;
@@ -10601,22 +10364,12 @@ var ReplayStep = class extends BaseStep {
10601
10364
  patchesRepointed: report.patchesRepointed,
10602
10365
  patchesContentRebased: report.patchesContentRebased,
10603
10366
  patchesKeptAsUserOwned: report.patchesKeptAsUserOwned,
10604
- conflicts: report.conflicts.map((fileResult) => ({
10605
- filePath: fileResult.file,
10606
- conflicts: fileResult.conflicts?.map((conflict) => ({
10607
- startLine: conflict.startLine,
10608
- endLine: conflict.endLine,
10609
- ours: conflict.ours,
10610
- theirs: conflict.theirs
10611
- })) ?? []
10612
- })),
10613
- conflictDetails: report.conflictDetails?.map((detail) => ({
10614
- patchId: detail.patchId,
10615
- patchMessage: detail.patchMessage,
10616
- reason: detail.reason,
10617
- 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) => ({
10618
10372
  file: f.file,
10619
- status: f.status,
10620
10373
  conflictReason: f.conflictReason
10621
10374
  }))
10622
10375
  })),
@@ -10880,6 +10633,14 @@ function buildPrBody(result) {
10880
10633
  lines.push("Once merged, your customizations will be automatically preserved on future `fern generate` runs.");
10881
10634
  return lines.join("\n");
10882
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
+ }
10883
10644
  // Annotate the CommonJS export names for ESM import in node:
10884
10645
  0 && (module.exports = {
10885
10646
  PostGenerationPipeline,
@@ -10895,5 +10656,6 @@ function buildPrBody(result) {
10895
10656
  githubRelease,
10896
10657
  logReplaySummary,
10897
10658
  replayInit,
10659
+ replayResolve,
10898
10660
  replayRun
10899
10661
  });