@fern-api/generator-cli 0.8.1 → 0.9.1

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
@@ -1,5 +1,5 @@
1
- import { BootstrapResult, ResolveResult, ReplayReport } from '@fern-api/replay';
2
- export { ConflictDetail, ReplayReport, ResolveResult } from '@fern-api/replay';
1
+ import { ForgetOptions, ForgetResult, BootstrapResult, ResolveResult, ReplayReport, StatusResult } from '@fern-api/replay';
2
+ export { ConflictDetail, DiffStat, ForgetOptions, ForgetResult, MatchedPatch, ReplayReport, ResolveResult, StatusGeneration, StatusPatch, StatusResult } from '@fern-api/replay';
3
3
  import fs from 'fs';
4
4
 
5
5
  /**
@@ -614,6 +614,14 @@ declare function formatReplayPrBody(result: ReplayStepResult | undefined, option
614
614
  repoUri?: string;
615
615
  }): string | undefined;
616
616
 
617
+ interface ReplayForgetParams {
618
+ /** Directory containing the SDK (where .fern/replay.lock lives) */
619
+ outputDir: string;
620
+ /** Options controlling which patches to forget */
621
+ options?: ForgetOptions;
622
+ }
623
+ declare function replayForget(params: ReplayForgetParams): ForgetResult;
624
+
617
625
  interface ReplayInitParams {
618
626
  /** GitHub repo URI (e.g., "fern-demo/fern-replay-testbed-java-sdk") */
619
627
  githubRepo: string;
@@ -699,4 +707,10 @@ interface ReplayRunResult {
699
707
  }
700
708
  declare function replayRun(params: ReplayRunParams): Promise<ReplayRunResult>;
701
709
 
702
- export { type BootstrapLogEntry, type GenerateReadmeParams, type GenerateReadmeToStreamParams, type GenerateReferenceParams, type GenerateReferenceToStreamParams, type GithubPrParams, type GithubPushParams, type GithubReleaseParams, type GithubStepConfig, type GithubStepResult, type PipelineConfig, type PipelineContext, type PipelineLogger, type PipelineResult, PostGenerationPipeline, type ReplayInitParams, type ReplayInitResult, type ReplayResolveParams, type ReplayRunParams, type ReplayRunResult, type ReplayStepResult, consolePipelineLogger, formatBootstrapSummary, formatReplayPrBody, generateReadme, generateReadmeToStream, generateReference, generateReferenceToStream, githubPr, githubPush, githubRelease, logReplaySummary, replayInit, replayResolve, replayRun };
710
+ interface ReplayStatusParams {
711
+ /** Directory containing the SDK (where .fern/replay.lock lives) */
712
+ outputDir: string;
713
+ }
714
+ declare function replayStatus(params: ReplayStatusParams): StatusResult;
715
+
716
+ export { type BootstrapLogEntry, type GenerateReadmeParams, type GenerateReadmeToStreamParams, type GenerateReferenceParams, type GenerateReferenceToStreamParams, type GithubPrParams, type GithubPushParams, type GithubReleaseParams, type GithubStepConfig, type GithubStepResult, type PipelineConfig, type PipelineContext, type PipelineLogger, type PipelineResult, PostGenerationPipeline, type ReplayForgetParams, type ReplayInitParams, type ReplayInitResult, type ReplayResolveParams, type ReplayRunParams, type ReplayRunResult, type ReplayStatusParams, type ReplayStepResult, consolePipelineLogger, formatBootstrapSummary, formatReplayPrBody, generateReadme, generateReadmeToStream, generateReference, generateReferenceToStream, githubPr, githubPush, githubRelease, logReplaySummary, replayForget, replayInit, replayResolve, replayRun, replayStatus };
package/dist/api.js CHANGED
@@ -844,7 +844,7 @@ var require_dist2 = __commonJS({
844
844
  function deferred2() {
845
845
  let done;
846
846
  let fail;
847
- let status = "pending";
847
+ let status2 = "pending";
848
848
  const promise = new Promise((_done, _fail) => {
849
849
  done = _done;
850
850
  fail = _fail;
@@ -852,22 +852,22 @@ var require_dist2 = __commonJS({
852
852
  return {
853
853
  promise,
854
854
  done(result) {
855
- if (status === "pending") {
856
- status = "resolved";
855
+ if (status2 === "pending") {
856
+ status2 = "resolved";
857
857
  done(result);
858
858
  }
859
859
  },
860
860
  fail(error) {
861
- if (status === "pending") {
862
- status = "rejected";
861
+ if (status2 === "pending") {
862
+ status2 = "rejected";
863
863
  fail(error);
864
864
  }
865
865
  },
866
866
  get fulfilled() {
867
- return status !== "pending";
867
+ return status2 !== "pending";
868
868
  },
869
869
  get status() {
870
- return status;
870
+ return status2;
871
871
  }
872
872
  };
873
873
  }
@@ -892,9 +892,11 @@ __export(api_exports2, {
892
892
  githubPush: () => githubPush,
893
893
  githubRelease: () => githubRelease,
894
894
  logReplaySummary: () => logReplaySummary,
895
+ replayForget: () => replayForget,
895
896
  replayInit: () => replayInit,
896
897
  replayResolve: () => replayResolve,
897
- replayRun: () => replayRun
898
+ replayRun: () => replayRun,
899
+ replayStatus: () => replayStatus
898
900
  });
899
901
  module.exports = __toCommonJS(api_exports2);
900
902
 
@@ -2827,7 +2829,7 @@ var init_parse_diff_summary = __esm({
2827
2829
  nameStatusParser = [
2828
2830
  new LineParser(
2829
2831
  /([ACDMRTUXB])([0-9]{0,3})\t(.[^\t]*)(\t(.[^\t]*))?$/,
2830
- (result, [status, similarity, from, _to, to]) => {
2832
+ (result, [status2, similarity, from, _to, to]) => {
2831
2833
  result.changed++;
2832
2834
  result.files.push({
2833
2835
  file: to ?? from,
@@ -2835,7 +2837,7 @@ var init_parse_diff_summary = __esm({
2835
2837
  insertions: 0,
2836
2838
  deletions: 0,
2837
2839
  binary: false,
2838
- status: orVoid(isDiffNameStatus(status) && status),
2840
+ status: orVoid(isDiffNameStatus(status2) && status2),
2839
2841
  from: orVoid(!!to && from !== to && from),
2840
2842
  similarity: asNumber(similarity)
2841
2843
  });
@@ -3361,10 +3363,10 @@ var init_merge = __esm({
3361
3363
  init_task();
3362
3364
  }
3363
3365
  });
3364
- function pushResultPushedItem(local, remote, status) {
3365
- const deleted = status.includes("deleted");
3366
- const tag = status.includes("tag") || /^refs\/tags/.test(local);
3367
- const alreadyUpdated = !status.includes("new");
3366
+ function pushResultPushedItem(local, remote, status2) {
3367
+ const deleted = status2.includes("deleted");
3368
+ const tag = status2.includes("tag") || /^refs\/tags/.test(local);
3369
+ const alreadyUpdated = !status2.includes("new");
3368
3370
  return {
3369
3371
  deleted,
3370
3372
  tag,
@@ -3678,7 +3680,7 @@ var init_StatusSummary = __esm({
3678
3680
  ]);
3679
3681
  parseStatusSummary = function(text) {
3680
3682
  const lines = text.split(NULL);
3681
- const status = new StatusSummary();
3683
+ const status2 = new StatusSummary();
3682
3684
  for (let i = 0, l = lines.length; i < l; ) {
3683
3685
  let line = lines[i++].trim();
3684
3686
  if (!line) {
@@ -3687,9 +3689,9 @@ var init_StatusSummary = __esm({
3687
3689
  if (line.charAt(0) === "R") {
3688
3690
  line += NULL + (lines[i++] || "");
3689
3691
  }
3690
- splitLine(status, line);
3692
+ splitLine(status2, line);
3691
3693
  }
3692
- return status;
3694
+ return status2;
3693
3695
  };
3694
3696
  }
3695
3697
  });
@@ -4116,15 +4118,15 @@ var init_BranchSummary = __esm({
4116
4118
  this.current = "";
4117
4119
  this.detached = false;
4118
4120
  }
4119
- push(status, detached, name, commit, label) {
4120
- if (status === "*") {
4121
+ push(status2, detached, name, commit, label) {
4122
+ if (status2 === "*") {
4121
4123
  this.detached = detached;
4122
4124
  this.current = name;
4123
4125
  }
4124
4126
  this.all.push(name);
4125
4127
  this.branches[name] = {
4126
- current: status === "*",
4127
- linkedWorkTree: status === "+",
4128
+ current: status2 === "*",
4129
+ linkedWorkTree: status2 === "+",
4128
4130
  name,
4129
4131
  commit,
4130
4132
  label
@@ -10488,8 +10490,14 @@ var PostGenerationPipeline = class {
10488
10490
  }
10489
10491
  };
10490
10492
 
10491
- // src/replay/replay-init.ts
10493
+ // src/replay/replay-forget.ts
10492
10494
  var import_replay2 = require("@fern-api/replay");
10495
+ function replayForget(params) {
10496
+ return (0, import_replay2.forget)(params.outputDir, params.options);
10497
+ }
10498
+
10499
+ // src/replay/replay-init.ts
10500
+ var import_replay3 = require("@fern-api/replay");
10493
10501
  var import_fs4 = require("fs");
10494
10502
  var import_path6 = require("path");
10495
10503
  var import_tmp_promise2 = __toESM(require("tmp-promise"));
@@ -10502,7 +10510,7 @@ async function replayInit(params) {
10502
10510
  installationToken: token,
10503
10511
  targetDirectory: repoPath
10504
10512
  });
10505
- const bootstrapResult = await (0, import_replay2.bootstrap)(repoPath, {
10513
+ const bootstrapResult = await (0, import_replay3.bootstrap)(repoPath, {
10506
10514
  dryRun,
10507
10515
  fernignoreAction: "skip",
10508
10516
  maxCommitsToScan: params.maxCommitsToScan,
@@ -10635,12 +10643,18 @@ function buildPrBody(result) {
10635
10643
  }
10636
10644
 
10637
10645
  // src/replay/replay-resolve.ts
10638
- var import_replay3 = require("@fern-api/replay");
10646
+ var import_replay4 = require("@fern-api/replay");
10639
10647
  async function replayResolve(params) {
10640
- return (0, import_replay3.resolve)(params.outputDir, {
10648
+ return (0, import_replay4.resolve)(params.outputDir, {
10641
10649
  checkMarkers: params.checkMarkers
10642
10650
  });
10643
10651
  }
10652
+
10653
+ // src/replay/replay-status.ts
10654
+ var import_replay5 = require("@fern-api/replay");
10655
+ function replayStatus(params) {
10656
+ return (0, import_replay5.status)(params.outputDir);
10657
+ }
10644
10658
  // Annotate the CommonJS export names for ESM import in node:
10645
10659
  0 && (module.exports = {
10646
10660
  PostGenerationPipeline,
@@ -10655,7 +10669,9 @@ async function replayResolve(params) {
10655
10669
  githubPush,
10656
10670
  githubRelease,
10657
10671
  logReplaySummary,
10672
+ replayForget,
10658
10673
  replayInit,
10659
10674
  replayResolve,
10660
- replayRun
10675
+ replayRun,
10676
+ replayStatus
10661
10677
  });
package/dist/cli.js CHANGED
@@ -19759,7 +19759,7 @@ var init_diff3 = __esm({
19759
19759
  }
19760
19760
  });
19761
19761
 
19762
- // ../../node_modules/.pnpm/@fern-api+replay@0.8.0/node_modules/@fern-api/replay/dist/index.js
19762
+ // ../../node_modules/.pnpm/@fern-api+replay@0.9.1/node_modules/@fern-api/replay/dist/index.js
19763
19763
  var dist_exports = {};
19764
19764
  __export(dist_exports, {
19765
19765
  FERN_BOT_EMAIL: () => FERN_BOT_EMAIL2,
@@ -19784,6 +19784,241 @@ __export(dist_exports, {
19784
19784
  status: () => status,
19785
19785
  threeWayMerge: () => threeWayMerge
19786
19786
  });
19787
+ function parseHunks(fileDiff) {
19788
+ const lines = fileDiff.split("\n");
19789
+ const hunks = [];
19790
+ let currentHunk = null;
19791
+ for (const line of lines) {
19792
+ const headerMatch = line.match(
19793
+ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/
19794
+ );
19795
+ if (headerMatch) {
19796
+ if (currentHunk) {
19797
+ hunks.push(currentHunk);
19798
+ }
19799
+ currentHunk = {
19800
+ oldStart: parseInt(headerMatch[1], 10),
19801
+ oldCount: headerMatch[2] != null ? parseInt(headerMatch[2], 10) : 1,
19802
+ newStart: parseInt(headerMatch[3], 10),
19803
+ newCount: headerMatch[4] != null ? parseInt(headerMatch[4], 10) : 1,
19804
+ lines: []
19805
+ };
19806
+ continue;
19807
+ }
19808
+ if (!currentHunk) continue;
19809
+ if (line.startsWith("diff --git") || line.startsWith("index ") || line.startsWith("---") || line.startsWith("+++") || line.startsWith("old mode") || line.startsWith("new mode") || line.startsWith("similarity index") || line.startsWith("rename from") || line.startsWith("rename to") || line.startsWith("new file mode") || line.startsWith("deleted file mode")) {
19810
+ continue;
19811
+ }
19812
+ if (line === "\") {
19813
+ continue;
19814
+ }
19815
+ if (line.startsWith("-")) {
19816
+ currentHunk.lines.push({ type: "remove", content: line.slice(1) });
19817
+ } else if (line.startsWith("+")) {
19818
+ currentHunk.lines.push({ type: "add", content: line.slice(1) });
19819
+ } else if (line.startsWith(" ") || line === "") {
19820
+ currentHunk.lines.push({
19821
+ type: "context",
19822
+ content: line.startsWith(" ") ? line.slice(1) : line
19823
+ });
19824
+ }
19825
+ }
19826
+ if (currentHunk) {
19827
+ hunks.push(currentHunk);
19828
+ }
19829
+ return hunks;
19830
+ }
19831
+ function extractLeadingContext(hunk) {
19832
+ const result = [];
19833
+ for (const line of hunk.lines) {
19834
+ if (line.type !== "context") break;
19835
+ result.push(line.content);
19836
+ }
19837
+ return result;
19838
+ }
19839
+ function extractTrailingContext(hunk) {
19840
+ const result = [];
19841
+ for (let i = hunk.lines.length - 1; i >= 0; i--) {
19842
+ if (hunk.lines[i].type !== "context") break;
19843
+ result.unshift(hunk.lines[i].content);
19844
+ }
19845
+ return result;
19846
+ }
19847
+ function countOursLinesBeforeTrailing(hunk) {
19848
+ let count = 0;
19849
+ const trailingStart = findTrailingContextStart(hunk);
19850
+ for (let i = 0; i < trailingStart; i++) {
19851
+ if (hunk.lines[i].type === "context") count++;
19852
+ }
19853
+ return count;
19854
+ }
19855
+ function findTrailingContextStart(hunk) {
19856
+ let i = hunk.lines.length - 1;
19857
+ while (i >= 0 && hunk.lines[i].type === "context") {
19858
+ i--;
19859
+ }
19860
+ return i + 1;
19861
+ }
19862
+ function matchesAt(needle, haystack, offset) {
19863
+ for (let i = 0; i < needle.length; i++) {
19864
+ if (haystack[offset + i] !== needle[i]) return false;
19865
+ }
19866
+ return true;
19867
+ }
19868
+ function findContextInOurs(contextLines, oursLines, minIndex, hint) {
19869
+ const SEARCH_WINDOW = 200;
19870
+ const maxStart = oursLines.length - contextLines.length;
19871
+ const clampedHint = Math.max(minIndex, Math.min(hint, maxStart));
19872
+ if (clampedHint >= minIndex && clampedHint <= maxStart) {
19873
+ if (matchesAt(contextLines, oursLines, clampedHint)) {
19874
+ return clampedHint;
19875
+ }
19876
+ }
19877
+ for (let delta = 1; delta <= SEARCH_WINDOW; delta++) {
19878
+ for (const sign of [1, -1]) {
19879
+ const idx = clampedHint + delta * sign;
19880
+ if (idx < minIndex || idx > maxStart) continue;
19881
+ if (matchesAt(contextLines, oursLines, idx)) {
19882
+ return idx;
19883
+ }
19884
+ }
19885
+ }
19886
+ return -1;
19887
+ }
19888
+ function computeOursSpan(hunk, oursLines, oursOffset) {
19889
+ const leading = extractLeadingContext(hunk);
19890
+ const trailing = extractTrailingContext(hunk);
19891
+ if (trailing.length === 0) {
19892
+ const contextCount2 = hunk.lines.filter(
19893
+ (l) => l.type === "context"
19894
+ ).length;
19895
+ return Math.min(contextCount2, oursLines.length - oursOffset);
19896
+ }
19897
+ const searchStart = oursOffset + leading.length;
19898
+ for (let i = searchStart; i <= oursLines.length - trailing.length; i++) {
19899
+ if (matchesAt(trailing, oursLines, i)) {
19900
+ return i + trailing.length - oursOffset;
19901
+ }
19902
+ }
19903
+ const contextCount = hunk.lines.filter(
19904
+ (l) => l.type === "context"
19905
+ ).length;
19906
+ return Math.min(contextCount, oursLines.length - oursOffset);
19907
+ }
19908
+ function locateHunksInOurs(hunks, oursLines) {
19909
+ const located = [];
19910
+ let minOursIndex = 0;
19911
+ for (const hunk of hunks) {
19912
+ const contextLines = extractLeadingContext(hunk);
19913
+ let oursOffset;
19914
+ if (contextLines.length > 0) {
19915
+ const found = findContextInOurs(
19916
+ contextLines,
19917
+ oursLines,
19918
+ minOursIndex,
19919
+ hunk.newStart - 1
19920
+ );
19921
+ if (found === -1) {
19922
+ const trailingContext = extractTrailingContext(hunk);
19923
+ if (trailingContext.length > 0) {
19924
+ const trailingFound = findContextInOurs(
19925
+ trailingContext,
19926
+ oursLines,
19927
+ minOursIndex,
19928
+ hunk.newStart - 1
19929
+ );
19930
+ if (trailingFound === -1) return null;
19931
+ const nonTrailingCount = countOursLinesBeforeTrailing(hunk);
19932
+ oursOffset = trailingFound - nonTrailingCount;
19933
+ if (oursOffset < minOursIndex) return null;
19934
+ } else {
19935
+ return null;
19936
+ }
19937
+ } else {
19938
+ oursOffset = found;
19939
+ }
19940
+ } else if (hunk.oldStart === 1 && hunk.oldCount === 0) {
19941
+ oursOffset = 0;
19942
+ } else {
19943
+ oursOffset = Math.max(hunk.newStart - 1, minOursIndex);
19944
+ }
19945
+ const oursSpan = computeOursSpan(hunk, oursLines, oursOffset);
19946
+ located.push({ hunk, oursOffset, oursSpan });
19947
+ minOursIndex = oursOffset + oursSpan;
19948
+ }
19949
+ return located;
19950
+ }
19951
+ function assembleHybrid(locatedHunks, oursLines) {
19952
+ const baseLines = [];
19953
+ const theirsLines = [];
19954
+ let oursCursor = 0;
19955
+ for (const { hunk, oursOffset, oursSpan } of locatedHunks) {
19956
+ if (oursOffset > oursCursor) {
19957
+ const gapLines = oursLines.slice(oursCursor, oursOffset);
19958
+ baseLines.push(...gapLines);
19959
+ theirsLines.push(...gapLines);
19960
+ }
19961
+ for (const line of hunk.lines) {
19962
+ switch (line.type) {
19963
+ case "context":
19964
+ baseLines.push(line.content);
19965
+ theirsLines.push(line.content);
19966
+ break;
19967
+ case "remove":
19968
+ baseLines.push(line.content);
19969
+ break;
19970
+ case "add":
19971
+ theirsLines.push(line.content);
19972
+ break;
19973
+ }
19974
+ }
19975
+ oursCursor = oursOffset + oursSpan;
19976
+ }
19977
+ if (oursCursor < oursLines.length) {
19978
+ const gapLines = oursLines.slice(oursCursor);
19979
+ baseLines.push(...gapLines);
19980
+ theirsLines.push(...gapLines);
19981
+ }
19982
+ return {
19983
+ base: baseLines.join("\n"),
19984
+ theirs: theirsLines.join("\n")
19985
+ };
19986
+ }
19987
+ function reconstructFromGhostPatch(fileDiff, ours) {
19988
+ const hunks = parseHunks(fileDiff);
19989
+ if (hunks.length === 0) {
19990
+ return null;
19991
+ }
19992
+ const isPureAddition = hunks.every(
19993
+ (h) => h.oldCount === 0 && h.lines.every((l) => l.type !== "remove")
19994
+ );
19995
+ if (isPureAddition) {
19996
+ return null;
19997
+ }
19998
+ const isPureDeletion = hunks.every(
19999
+ (h) => h.newCount === 0 && h.lines.every((l) => l.type !== "add")
20000
+ );
20001
+ if (isPureDeletion) {
20002
+ const baseLines = [];
20003
+ for (const hunk of hunks) {
20004
+ for (const line of hunk.lines) {
20005
+ if (line.type === "context" || line.type === "remove") {
20006
+ baseLines.push(line.content);
20007
+ }
20008
+ }
20009
+ }
20010
+ return {
20011
+ base: baseLines.join("\n"),
20012
+ theirs: ""
20013
+ };
20014
+ }
20015
+ const oursLines = ours.split("\n");
20016
+ const located = locateHunksInOurs(hunks, oursLines);
20017
+ if (!located) {
20018
+ return null;
20019
+ }
20020
+ return assembleHybrid(located, oursLines);
20021
+ }
19787
20022
  function isGenerationCommit(commit) {
19788
20023
  const isFernSupport = FERN_SUPPORT_NAMES.includes(commit.authorName);
19789
20024
  const isBotAuthor = !isFernSupport && (commit.authorLogin === FERN_BOT_LOGIN2 || commit.authorEmail === FERN_BOT_EMAIL2 || commit.authorName === FERN_BOT_NAME2);
@@ -19797,7 +20032,7 @@ function isReplayCommit(commit) {
19797
20032
  return commit.message.startsWith("[fern-replay]");
19798
20033
  }
19799
20034
  function isRevertCommit(message) {
19800
- return message.startsWith('Revert "');
20035
+ return /^Revert ".+"$/.test(message);
19801
20036
  }
19802
20037
  function parseRevertedSha(fullBody) {
19803
20038
  const match2 = fullBody.match(/This reverts commit ([0-9a-f]{40})\./);
@@ -19842,14 +20077,6 @@ function threeWayMerge(base, ours, theirs) {
19842
20077
  conflicts: conflicts2
19843
20078
  };
19844
20079
  }
19845
- function isBinaryFile(filePath) {
19846
- const ext2 = (0, import_path9.extname)(filePath).toLowerCase();
19847
- return BINARY_EXTENSIONS.has(ext2);
19848
- }
19849
- function isDiffLineForFile(diffLine, filePath) {
19850
- const match2 = diffLine.match(/^diff --git a\/.+ b\/(.+)$/);
19851
- return match2 !== null && match2[1] === filePath;
19852
- }
19853
20080
  function stripConflictMarkers(content) {
19854
20081
  const lines = content.split("\n");
19855
20082
  const result = [];
@@ -19876,6 +20103,14 @@ function stripConflictMarkers(content) {
19876
20103
  }
19877
20104
  return result.join("\n");
19878
20105
  }
20106
+ function isBinaryFile(filePath) {
20107
+ const ext2 = (0, import_path9.extname)(filePath).toLowerCase();
20108
+ return BINARY_EXTENSIONS.has(ext2);
20109
+ }
20110
+ function isDiffLineForFile(diffLine, filePath) {
20111
+ const match2 = diffLine.match(/^diff --git a\/.+ b\/(.+)$/);
20112
+ return match2 !== null && match2[1] === filePath;
20113
+ }
19879
20114
  async function bootstrap(outputDir, options) {
19880
20115
  const git = new GitClient(outputDir);
19881
20116
  const lockManager = new LockfileManager(outputDir);
@@ -19957,6 +20192,7 @@ async function bootstrap(outputDir, options) {
19957
20192
  }
19958
20193
  lockManager.save();
19959
20194
  const fernignoreUpdated = ensureFernignoreEntries(outputDir);
20195
+ ensureGitattributesEntries(outputDir);
19960
20196
  if (migrator.fernignoreExists() && fernignorePatterns.length > 0) {
19961
20197
  const action = options?.fernignoreAction ?? "skip";
19962
20198
  if (action === "migrate") {
@@ -20061,34 +20297,162 @@ function ensureFernignoreEntries(outputDir) {
20061
20297
  (0, import_fs9.writeFileSync)(fernignorePath, content, "utf-8");
20062
20298
  return true;
20063
20299
  }
20300
+ function ensureGitattributesEntries(outputDir) {
20301
+ const gitattributesPath = (0, import_path12.join)(outputDir, ".gitattributes");
20302
+ let content = "";
20303
+ if ((0, import_fs9.existsSync)(gitattributesPath)) {
20304
+ content = (0, import_fs9.readFileSync)(gitattributesPath, "utf-8");
20305
+ }
20306
+ const lines = content.split("\n");
20307
+ const toAdd = [];
20308
+ for (const entry of GITATTRIBUTES_ENTRIES) {
20309
+ if (!lines.some((line) => line.trim() === entry)) {
20310
+ toAdd.push(entry);
20311
+ }
20312
+ }
20313
+ if (toAdd.length === 0) {
20314
+ return;
20315
+ }
20316
+ if (content && !content.endsWith("\n")) {
20317
+ content += "\n";
20318
+ }
20319
+ content += toAdd.join("\n") + "\n";
20320
+ (0, import_fs9.writeFileSync)(gitattributesPath, content, "utf-8");
20321
+ }
20064
20322
  function computeContentHash(patchContent) {
20065
20323
  const normalized = patchContent.split("\n").filter((line) => !line.startsWith("From ") && !line.startsWith("index ") && !line.startsWith("Date: ")).join("\n");
20066
20324
  return `sha256:${(0, import_crypto3.createHash)("sha256").update(normalized).digest("hex")}`;
20067
20325
  }
20068
- function forget(outputDir, filePattern, options) {
20326
+ function parseDiffStat(patchContent) {
20327
+ let additions = 0;
20328
+ let deletions = 0;
20329
+ let inDiffHunk = false;
20330
+ for (const line of patchContent.split("\n")) {
20331
+ if (line.startsWith("diff --git ")) {
20332
+ inDiffHunk = true;
20333
+ continue;
20334
+ }
20335
+ if (!inDiffHunk) continue;
20336
+ if (line.startsWith("+") && !line.startsWith("+++")) {
20337
+ additions++;
20338
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
20339
+ deletions++;
20340
+ }
20341
+ }
20342
+ return { additions, deletions };
20343
+ }
20344
+ function toMatchedPatch(patch) {
20345
+ return {
20346
+ id: patch.id,
20347
+ message: patch.original_message,
20348
+ files: patch.files,
20349
+ diffstat: parseDiffStat(patch.patch_content),
20350
+ ...patch.status ? { status: patch.status } : {}
20351
+ };
20352
+ }
20353
+ function matchesPatch(patch, pattern) {
20354
+ const fileMatch = patch.files.some(
20355
+ (file) => file === pattern || minimatch(file, pattern)
20356
+ );
20357
+ if (fileMatch) return true;
20358
+ return patch.original_message.toLowerCase().includes(pattern.toLowerCase());
20359
+ }
20360
+ function buildWarnings(patches) {
20361
+ const warnings = [];
20362
+ for (const patch of patches) {
20363
+ if (patch.status === "resolving") {
20364
+ warnings.push(
20365
+ `patch ${patch.id} has conflict markers in these files: ${patch.files.join(", ")}. Run \`git checkout -- <files>\` to restore the generated versions.`
20366
+ );
20367
+ } else if (patch.status === "unresolved") {
20368
+ warnings.push(
20369
+ `patch ${patch.id} had unresolved conflicts (files: ${patch.files.join(", ")}).`
20370
+ );
20371
+ }
20372
+ }
20373
+ return warnings;
20374
+ }
20375
+ function forget(outputDir, options) {
20069
20376
  const lockManager = new LockfileManager(outputDir);
20070
20377
  if (!lockManager.exists()) {
20071
- return { removed: [], notFound: true };
20378
+ return { ...EMPTY_RESULT };
20072
20379
  }
20073
20380
  const lock = lockManager.read();
20074
- const matchingPatches = lock.patches.filter(
20075
- (patch) => patch.files.some((file) => file === filePattern || minimatch(file, filePattern))
20076
- );
20077
- if (matchingPatches.length === 0) {
20078
- return { removed: [], notFound: true };
20381
+ const totalPatches = lock.patches.length;
20382
+ if (options?.all) {
20383
+ const removed = lock.patches.map(toMatchedPatch);
20384
+ const warnings = buildWarnings(lock.patches);
20385
+ if (!options.dryRun) {
20386
+ for (const patch of lock.patches) {
20387
+ lockManager.addForgottenHash(patch.content_hash);
20388
+ }
20389
+ lockManager.clearPatches();
20390
+ lockManager.save();
20391
+ }
20392
+ return {
20393
+ initialized: true,
20394
+ removed,
20395
+ remaining: 0,
20396
+ notFound: false,
20397
+ alreadyForgotten: [],
20398
+ totalPatches,
20399
+ warnings
20400
+ };
20079
20401
  }
20080
- const removed = matchingPatches.map((p) => ({
20081
- id: p.id,
20082
- message: p.original_message,
20083
- files: p.files
20084
- }));
20085
- if (!options?.dryRun) {
20086
- for (const patch of matchingPatches) {
20087
- lockManager.removePatch(patch.id);
20402
+ if (options?.patchIds && options.patchIds.length > 0) {
20403
+ const removed = [];
20404
+ const alreadyForgotten = [];
20405
+ const patchesToRemove = [];
20406
+ for (const id of options.patchIds) {
20407
+ const patch = lock.patches.find((p) => p.id === id);
20408
+ if (patch) {
20409
+ removed.push(toMatchedPatch(patch));
20410
+ patchesToRemove.push(patch);
20411
+ } else {
20412
+ alreadyForgotten.push(id);
20413
+ }
20088
20414
  }
20089
- lockManager.save();
20415
+ const warnings = buildWarnings(patchesToRemove);
20416
+ if (!options.dryRun) {
20417
+ for (const patch of patchesToRemove) {
20418
+ lockManager.addForgottenHash(patch.content_hash);
20419
+ lockManager.removePatch(patch.id);
20420
+ }
20421
+ lockManager.save();
20422
+ }
20423
+ return {
20424
+ initialized: true,
20425
+ removed,
20426
+ remaining: totalPatches - removed.length,
20427
+ notFound: removed.length === 0 && alreadyForgotten.length > 0,
20428
+ alreadyForgotten,
20429
+ totalPatches,
20430
+ warnings
20431
+ };
20090
20432
  }
20091
- return { removed, notFound: false };
20433
+ if (options?.pattern) {
20434
+ const matched = lock.patches.filter((p) => matchesPatch(p, options.pattern)).map(toMatchedPatch);
20435
+ return {
20436
+ initialized: true,
20437
+ removed: [],
20438
+ remaining: totalPatches,
20439
+ notFound: matched.length === 0,
20440
+ alreadyForgotten: [],
20441
+ totalPatches,
20442
+ warnings: [],
20443
+ matched
20444
+ };
20445
+ }
20446
+ return {
20447
+ initialized: true,
20448
+ removed: [],
20449
+ remaining: totalPatches,
20450
+ notFound: totalPatches === 0,
20451
+ alreadyForgotten: [],
20452
+ totalPatches,
20453
+ warnings: [],
20454
+ matched: lock.patches.map(toMatchedPatch)
20455
+ };
20092
20456
  }
20093
20457
  function reset(outputDir, options) {
20094
20458
  const lockManager = new LockfileManager(outputDir);
@@ -20202,28 +20566,53 @@ async function getChangedFiles(git, currentGen, files) {
20202
20566
  function status(outputDir) {
20203
20567
  const lockManager = new LockfileManager(outputDir);
20204
20568
  if (!lockManager.exists()) {
20205
- return { initialized: false, patches: [], lastGeneration: void 0 };
20569
+ return {
20570
+ initialized: false,
20571
+ generationCount: 0,
20572
+ lastGeneration: void 0,
20573
+ patches: [],
20574
+ unresolvedCount: 0,
20575
+ excludePatterns: []
20576
+ };
20206
20577
  }
20207
20578
  const lock = lockManager.read();
20208
20579
  const patches = lock.patches.map((patch) => ({
20209
- sha: patch.original_commit.slice(0, 7),
20210
- author: patch.original_author.split("<")[0]?.trim() ?? "unknown",
20580
+ id: patch.id,
20581
+ type: patch.patch_content.includes("new file mode") ? "added" : "modified",
20211
20582
  message: patch.original_message,
20212
- files: patch.files
20583
+ author: patch.original_author.split("<")[0]?.trim() || "unknown",
20584
+ sha: patch.original_commit.slice(0, 7),
20585
+ files: patch.files,
20586
+ fileCount: patch.files.length,
20587
+ ...patch.status ? { status: patch.status } : {}
20213
20588
  }));
20589
+ const unresolvedCount = lock.patches.filter(
20590
+ (p) => p.status === "unresolved" || p.status === "resolving"
20591
+ ).length;
20214
20592
  let lastGeneration;
20215
20593
  const lastGen = lock.generations.find((g) => g.commit_sha === lock.current_generation);
20216
20594
  if (lastGen) {
20217
20595
  lastGeneration = {
20218
- sha: lastGen.commit_sha,
20219
- timestamp: lastGen.timestamp
20596
+ sha: lastGen.commit_sha.slice(0, 7),
20597
+ timestamp: lastGen.timestamp,
20598
+ cliVersion: lastGen.cli_version,
20599
+ generatorVersions: lastGen.generator_versions
20220
20600
  };
20221
20601
  }
20222
- return { initialized: true, patches, lastGeneration };
20602
+ const config = lockManager.getCustomizationsConfig();
20603
+ const excludePatterns = config.exclude ?? [];
20604
+ return {
20605
+ initialized: true,
20606
+ generationCount: lock.generations.length,
20607
+ lastGeneration,
20608
+ patches,
20609
+ unresolvedCount,
20610
+ excludePatterns
20611
+ };
20223
20612
  }
20224
- 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, FERN_BOT_NAME2, FERN_BOT_EMAIL2, FERN_BOT_LOGIN2, FERN_SUPPORT_NAMES, LOCKFILE_HEADER, LockfileManager, INFRASTRUCTURE_FILES, ReplayDetector, BINARY_EXTENSIONS, ReplayApplicator, ReplayCommitter, ReplayService, FernignoreMigrator, REPLAY_FERNIGNORE_ENTRIES;
20613
+ 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, LockfileManager, INFRASTRUCTURE_FILES, ReplayDetector, BINARY_EXTENSIONS, ReplayApplicator, ReplayCommitter, ReplayService, FernignoreMigrator, REPLAY_FERNIGNORE_ENTRIES, GITATTRIBUTES_ENTRIES, EMPTY_RESULT;
20225
20614
  var init_dist = __esm({
20226
- "../../node_modules/.pnpm/@fern-api+replay@0.8.0/node_modules/@fern-api/replay/dist/index.js"() {
20615
+ "../../node_modules/.pnpm/@fern-api+replay@0.9.1/node_modules/@fern-api/replay/dist/index.js"() {
20227
20616
  "use strict";
20228
20617
  init_esm4();
20229
20618
  import_fs6 = require("fs");
@@ -20357,6 +20746,14 @@ var init_dist = __esm({
20357
20746
  return false;
20358
20747
  }
20359
20748
  }
20749
+ async treeExists(treeHash) {
20750
+ try {
20751
+ const type = await this.exec(["cat-file", "-t", treeHash]);
20752
+ return type.trim() === "tree";
20753
+ } catch {
20754
+ return false;
20755
+ }
20756
+ }
20360
20757
  async getCommitBody(commitSha) {
20361
20758
  return this.exec(["log", "-1", "--format=%B", commitSha]);
20362
20759
  }
@@ -20366,6 +20763,18 @@ var init_dist = __esm({
20366
20763
  };
20367
20764
  }
20368
20765
  });
20766
+ HybridReconstruction_exports = {};
20767
+ __export3(HybridReconstruction_exports, {
20768
+ assembleHybrid: () => assembleHybrid,
20769
+ locateHunksInOurs: () => locateHunksInOurs,
20770
+ parseHunks: () => parseHunks,
20771
+ reconstructFromGhostPatch: () => reconstructFromGhostPatch
20772
+ });
20773
+ init_HybridReconstruction = __esm3({
20774
+ "src/HybridReconstruction.ts"() {
20775
+ "use strict";
20776
+ }
20777
+ });
20369
20778
  init_GitClient();
20370
20779
  FERN_BOT_NAME2 = "fern-api";
20371
20780
  FERN_BOT_EMAIL2 = "115122769+fern-api[bot]@users.noreply.github.com";
@@ -20457,6 +20866,15 @@ var init_dist = __esm({
20457
20866
  this.ensureLoaded();
20458
20867
  this.lock.patches = [];
20459
20868
  }
20869
+ addForgottenHash(hash) {
20870
+ this.ensureLoaded();
20871
+ if (!this.lock.forgotten_hashes) {
20872
+ this.lock.forgotten_hashes = [];
20873
+ }
20874
+ if (!this.lock.forgotten_hashes.includes(hash)) {
20875
+ this.lock.forgotten_hashes.push(hash);
20876
+ }
20877
+ }
20460
20878
  getUnresolvedPatches() {
20461
20879
  this.ensureLoaded();
20462
20880
  return this.lock.patches.filter((p) => p.status === "unresolved");
@@ -20550,6 +20968,7 @@ var init_dist = __esm({
20550
20968
  }
20551
20969
  const commits = this.parseGitLog(log);
20552
20970
  const newPatches = [];
20971
+ const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
20553
20972
  for (const commit of commits) {
20554
20973
  if (isGenerationCommit(commit)) {
20555
20974
  continue;
@@ -20563,7 +20982,7 @@ var init_dist = __esm({
20563
20982
  }
20564
20983
  const patchContent = await this.git.formatPatch(commit.sha);
20565
20984
  const contentHash = this.computeContentHash(patchContent);
20566
- if (lock.patches.find((p) => p.content_hash === contentHash)) {
20985
+ if (lock.patches.find((p) => p.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
20567
20986
  continue;
20568
20987
  }
20569
20988
  const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
@@ -20633,6 +21052,9 @@ var init_dist = __esm({
20633
21052
  revertIndicesToRemove.add(idx);
20634
21053
  }
20635
21054
  }
21055
+ if (!matchedExisting && !matchedNew) {
21056
+ revertIndicesToRemove.add(i);
21057
+ }
20636
21058
  }
20637
21059
  const filteredPatches = newPatches.filter((_, i) => !revertIndicesToRemove.has(i));
20638
21060
  return { patches: filteredPatches, revertedPatchIds: [...revertedPatchIdSet] };
@@ -20659,7 +21081,7 @@ var init_dist = __esm({
20659
21081
  if (!diff.trim()) return { patches: [], revertedPatchIds: [] };
20660
21082
  const contentHash = this.computeContentHash(diff);
20661
21083
  const lock = this.lockManager.read();
20662
- if (lock.patches.some((p) => p.content_hash === contentHash)) {
21084
+ if (lock.patches.some((p) => p.content_hash === contentHash) || (lock.forgotten_hashes ?? []).includes(contentHash)) {
20663
21085
  return { patches: [], revertedPatchIds: [] };
20664
21086
  }
20665
21087
  const headSha = (await this.git.exec(["rev-parse", "HEAD"])).trim();
@@ -20739,6 +21161,7 @@ var init_dist = __esm({
20739
21161
  lockManager;
20740
21162
  outputDir;
20741
21163
  renameCache = /* @__PURE__ */ new Map();
21164
+ treeExistsCache = /* @__PURE__ */ new Map();
20742
21165
  fileTheirsAccumulator = /* @__PURE__ */ new Map();
20743
21166
  constructor(git, lockManager, outputDir) {
20744
21167
  this.git = git;
@@ -20756,7 +21179,8 @@ var init_dist = __esm({
20756
21179
  async applyPatches(patches) {
20757
21180
  this.resetAccumulator();
20758
21181
  const results = [];
20759
- for (const patch of patches) {
21182
+ for (let i = 0; i < patches.length; i++) {
21183
+ const patch = patches[i];
20760
21184
  if (this.isExcluded(patch)) {
20761
21185
  results.push({
20762
21186
  patch,
@@ -20767,6 +21191,33 @@ var init_dist = __esm({
20767
21191
  }
20768
21192
  const result = await this.applyPatchWithFallback(patch);
20769
21193
  results.push(result);
21194
+ if (result.status === "conflict" && result.fileResults) {
21195
+ const laterFiles = /* @__PURE__ */ new Set();
21196
+ for (let j = i + 1; j < patches.length; j++) {
21197
+ for (const f of patches[j].files) {
21198
+ laterFiles.add(f);
21199
+ }
21200
+ }
21201
+ const resolvedToOriginal = /* @__PURE__ */ new Map();
21202
+ if (result.resolvedFiles) {
21203
+ for (const [orig, resolved] of Object.entries(result.resolvedFiles)) {
21204
+ resolvedToOriginal.set(resolved, orig);
21205
+ }
21206
+ }
21207
+ for (const fileResult of result.fileResults) {
21208
+ if (fileResult.status !== "conflict") continue;
21209
+ const originalPath = resolvedToOriginal.get(fileResult.file) ?? fileResult.file;
21210
+ if (laterFiles.has(fileResult.file) || laterFiles.has(originalPath)) {
21211
+ const filePath = (0, import_path9.join)(this.outputDir, fileResult.file);
21212
+ try {
21213
+ const content = await (0, import_promises4.readFile)(filePath, "utf-8");
21214
+ const stripped = stripConflictMarkers(content);
21215
+ await (0, import_promises4.writeFile)(filePath, stripped);
21216
+ } catch {
21217
+ }
21218
+ }
21219
+ }
21220
+ }
20770
21221
  }
20771
21222
  return results;
20772
21223
  }
@@ -20785,7 +21236,7 @@ var init_dist = __esm({
20785
21236
  const resolvedPath = await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash);
20786
21237
  const base = await this.git.showFile(baseGen.tree_hash, filePath);
20787
21238
  const theirs = await this.applyPatchToContent(base, patch.patch_content, filePath, tempGit, tempDir);
20788
- if (theirs && base) {
21239
+ if (theirs) {
20789
21240
  this.fileTheirsAccumulator.set(resolvedPath, {
20790
21241
  content: theirs,
20791
21242
  baseGeneration: patch.base_generation
@@ -20907,17 +21358,36 @@ var init_dist = __esm({
20907
21358
  }
20908
21359
  const oursPath = (0, import_path9.join)(this.outputDir, resolvedPath);
20909
21360
  const ours = await (0, import_promises4.readFile)(oursPath, "utf-8").catch(() => null);
20910
- let theirs = await this.applyPatchToContent(
20911
- base,
20912
- patch.patch_content,
20913
- filePath,
20914
- tempGit,
20915
- tempDir,
20916
- renameSourcePath
20917
- );
21361
+ let ghostReconstructed = false;
21362
+ let theirs = null;
21363
+ if (!base && ours && !renameSourcePath) {
21364
+ const treeReachable = await this.isTreeReachable(baseGen.tree_hash);
21365
+ if (!treeReachable) {
21366
+ const fileDiff = this.extractFileDiff(patch.patch_content, filePath);
21367
+ if (fileDiff) {
21368
+ const { reconstructFromGhostPatch: reconstructFromGhostPatch2 } = await Promise.resolve().then(() => (init_HybridReconstruction(), HybridReconstruction_exports));
21369
+ const result = reconstructFromGhostPatch2(fileDiff, ours);
21370
+ if (result) {
21371
+ base = result.base;
21372
+ theirs = result.theirs;
21373
+ ghostReconstructed = true;
21374
+ }
21375
+ }
21376
+ }
21377
+ }
21378
+ if (!ghostReconstructed) {
21379
+ theirs = await this.applyPatchToContent(
21380
+ base,
21381
+ patch.patch_content,
21382
+ filePath,
21383
+ tempGit,
21384
+ tempDir,
21385
+ renameSourcePath
21386
+ );
21387
+ }
20918
21388
  let useAccumulatorAsMergeBase = false;
20919
21389
  const accumulatorEntry = this.fileTheirsAccumulator.get(resolvedPath);
20920
- if (!theirs && base && accumulatorEntry) {
21390
+ if (!theirs && accumulatorEntry) {
20921
21391
  theirs = await this.applyPatchToContent(
20922
21392
  accumulatorEntry.content,
20923
21393
  patch.patch_content,
@@ -20947,13 +21417,13 @@ var init_dist = __esm({
20947
21417
  baseMismatchSkipped = true;
20948
21418
  }
20949
21419
  }
20950
- if (!base && !ours && effective_theirs) {
21420
+ if (base == null && !ours && effective_theirs) {
20951
21421
  const outDir2 = (0, import_path9.dirname)(oursPath);
20952
21422
  await (0, import_promises4.mkdir)(outDir2, { recursive: true });
20953
21423
  await (0, import_promises4.writeFile)(oursPath, effective_theirs);
20954
21424
  return { file: resolvedPath, status: "merged", reason: "new-file" };
20955
21425
  }
20956
- if (!base && ours && effective_theirs) {
21426
+ if (base == null && ours && effective_theirs) {
20957
21427
  const merged2 = threeWayMerge("", ours, effective_theirs);
20958
21428
  const outDir2 = (0, import_path9.dirname)(oursPath);
20959
21429
  await (0, import_promises4.mkdir)(outDir2, { recursive: true });
@@ -20976,7 +21446,7 @@ var init_dist = __esm({
20976
21446
  reason: "missing-content"
20977
21447
  };
20978
21448
  }
20979
- if (!base || !ours) {
21449
+ if (base == null && !useAccumulatorAsMergeBase || !ours) {
20980
21450
  return {
20981
21451
  file: resolvedPath,
20982
21452
  status: "skipped",
@@ -20984,11 +21454,18 @@ var init_dist = __esm({
20984
21454
  };
20985
21455
  }
20986
21456
  const mergeBase = useAccumulatorAsMergeBase && accumulatorEntry ? accumulatorEntry.content : base;
21457
+ if (mergeBase == null) {
21458
+ return {
21459
+ file: resolvedPath,
21460
+ status: "skipped",
21461
+ reason: "missing-content"
21462
+ };
21463
+ }
20987
21464
  const merged = threeWayMerge(mergeBase, ours, effective_theirs);
20988
21465
  const outDir = (0, import_path9.dirname)(oursPath);
20989
21466
  await (0, import_promises4.mkdir)(outDir, { recursive: true });
20990
21467
  await (0, import_promises4.writeFile)(oursPath, merged.content);
20991
- if (effective_theirs && base) {
21468
+ if (effective_theirs) {
20992
21469
  this.fileTheirsAccumulator.set(resolvedPath, {
20993
21470
  content: effective_theirs,
20994
21471
  baseGeneration: patch.base_generation
@@ -21012,6 +21489,14 @@ var init_dist = __esm({
21012
21489
  };
21013
21490
  }
21014
21491
  }
21492
+ async isTreeReachable(treeHash) {
21493
+ let result = this.treeExistsCache.get(treeHash);
21494
+ if (result === void 0) {
21495
+ result = await this.git.treeExists(treeHash);
21496
+ this.treeExistsCache.set(treeHash, result);
21497
+ }
21498
+ return result;
21499
+ }
21015
21500
  isExcluded(patch) {
21016
21501
  const config = this.lockManager.getCustomizationsConfig();
21017
21502
  if (!config.exclude) return false;
@@ -21978,7 +22463,17 @@ CLI Version: ${options.cliVersion}`;
21978
22463
  }
21979
22464
  };
21980
22465
  init_GitClient();
21981
- REPLAY_FERNIGNORE_ENTRIES = [".fern/replay.lock", ".fern/replay.yml"];
22466
+ REPLAY_FERNIGNORE_ENTRIES = [".fern/replay.lock", ".fern/replay.yml", ".gitattributes"];
22467
+ GITATTRIBUTES_ENTRIES = [".fern/replay.lock linguist-generated=true"];
22468
+ EMPTY_RESULT = {
22469
+ initialized: false,
22470
+ removed: [],
22471
+ remaining: 0,
22472
+ notFound: false,
22473
+ alreadyForgotten: [],
22474
+ totalPatches: 0,
22475
+ warnings: []
22476
+ };
21982
22477
  init_GitClient();
21983
22478
  }
21984
22479
  });
@@ -32328,6 +32823,12 @@ var PostGenerationPipeline = class {
32328
32823
  }
32329
32824
  };
32330
32825
 
32826
+ // src/replay/replay-forget.ts
32827
+ init_dist();
32828
+ function replayForget(params) {
32829
+ return forget(params.outputDir, params.options);
32830
+ }
32831
+
32331
32832
  // src/replay/replay-init.ts
32332
32833
  init_dist();
32333
32834
  var import_fs13 = require("fs");
@@ -32477,6 +32978,12 @@ function buildPrBody(result) {
32477
32978
  // src/replay/replay-resolve.ts
32478
32979
  init_dist();
32479
32980
 
32981
+ // src/replay/replay-status.ts
32982
+ init_dist();
32983
+ function replayStatus(params) {
32984
+ return status(params.outputDir);
32985
+ }
32986
+
32480
32987
  // src/configuration/loadGitHubConfig.ts
32481
32988
  var import_promises6 = require("fs/promises");
32482
32989
  async function loadGitHubConfig({
@@ -32853,6 +33360,84 @@ Bootstrap complete! Lockfile saved to ${outputDir}/.fern/replay.lock
32853
33360
  } catch (error) {
32854
33361
  const errorMessage = extractErrorMessage(error);
32855
33362
  process.stderr.write(`Bootstrap failed: ${errorMessage}
33363
+ `);
33364
+ process.exit(1);
33365
+ }
33366
+ }
33367
+ ).command(
33368
+ "status [directory]",
33369
+ false,
33370
+ // hidden from --help
33371
+ (subYargs) => {
33372
+ return subYargs.positional("directory", {
33373
+ type: "string",
33374
+ default: ".",
33375
+ description: "SDK directory containing .fern/replay.lock"
33376
+ });
33377
+ },
33378
+ (argv) => {
33379
+ try {
33380
+ const outputDir = resolve(cwd(), argv.directory ?? ".");
33381
+ const result = replayStatus({ outputDir });
33382
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
33383
+ process.exit(0);
33384
+ } catch (error) {
33385
+ const errorMessage = extractErrorMessage(error);
33386
+ process.stderr.write(`Replay status failed: ${errorMessage}
33387
+ `);
33388
+ process.exit(1);
33389
+ }
33390
+ }
33391
+ ).command(
33392
+ "forget [args..]",
33393
+ false,
33394
+ // hidden from --help
33395
+ (subYargs) => {
33396
+ return subYargs.positional("args", {
33397
+ type: "string",
33398
+ array: true,
33399
+ description: "Patch IDs or search pattern"
33400
+ }).option("dry-run", {
33401
+ type: "boolean",
33402
+ default: false,
33403
+ description: "Show what would be removed without actually removing"
33404
+ }).option("all", {
33405
+ type: "boolean",
33406
+ default: false,
33407
+ description: "Remove all tracked patches"
33408
+ }).option("pattern", {
33409
+ type: "string",
33410
+ description: "Search pattern (file path, glob, or commit message substring)"
33411
+ });
33412
+ },
33413
+ (argv) => {
33414
+ try {
33415
+ const outputDir = resolve(cwd(), ".");
33416
+ const args = argv.args ?? [];
33417
+ const dryRun = argv["dry-run"];
33418
+ let result;
33419
+ if (argv.all) {
33420
+ result = replayForget({ outputDir, options: { all: true, dryRun } });
33421
+ } else if (argv.pattern) {
33422
+ result = replayForget({ outputDir, options: { pattern: argv.pattern, dryRun } });
33423
+ } else if (args.length > 0) {
33424
+ const allPatchIds = args.every((a) => a.startsWith("patch-"));
33425
+ if (allPatchIds) {
33426
+ result = replayForget({ outputDir, options: { patchIds: args, dryRun } });
33427
+ } else {
33428
+ result = replayForget({
33429
+ outputDir,
33430
+ options: { pattern: args[0], dryRun }
33431
+ });
33432
+ }
33433
+ } else {
33434
+ result = replayForget({ outputDir, options: { dryRun } });
33435
+ }
33436
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
33437
+ process.exit(0);
33438
+ } catch (error) {
33439
+ const errorMessage = extractErrorMessage(error);
33440
+ process.stderr.write(`Replay forget failed: ${errorMessage}
32856
33441
  `);
32857
33442
  process.exit(1);
32858
33443
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fern-api/generator-cli",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
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.8.0",
25
+ "@fern-api/replay": "^0.9.1",
26
26
  "@octokit/rest": "^22.0.1",
27
27
  "es-toolkit": "^1.45.1",
28
28
  "tmp-promise": "^3.0.3"