@fern-api/generator-cli 0.9.37 → 0.9.40

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 (3) hide show
  1. package/dist/api.js +6 -2
  2. package/dist/cli.js +130 -42
  3. package/package.json +2 -2
package/dist/api.js CHANGED
@@ -6,7 +6,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __commonJS = (cb, mod) => function __require() {
9
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ try {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ } catch (e) {
12
+ throw mod = 0, e;
13
+ }
10
14
  };
11
15
  var __export = (target, all) => {
12
16
  for (var name in all)
@@ -12293,7 +12297,7 @@ var ReplayPrepareError = class extends Error {
12293
12297
  if (Error.captureStackTrace) {
12294
12298
  Error.captureStackTrace(this, this.constructor);
12295
12299
  }
12296
- this.name = this.constructor.name;
12300
+ this.name = "ReplayPrepareError";
12297
12301
  this.reason = reason;
12298
12302
  }
12299
12303
  };
package/dist/cli.js CHANGED
@@ -5,11 +5,20 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __esm = (fn, res) => function __init() {
9
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ var __esm = (fn, res, err) => function __init() {
9
+ if (err) throw err[0];
10
+ try {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ } catch (e) {
13
+ throw err = [e], e;
14
+ }
10
15
  };
11
16
  var __commonJS = (cb, mod) => function __require() {
12
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
+ try {
18
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
19
+ } catch (e) {
20
+ throw mod = 0, e;
21
+ }
13
22
  };
14
23
  var __export = (target, all) => {
15
24
  for (var name in all)
@@ -22134,7 +22143,7 @@ var init_diff3 = __esm({
22134
22143
  }
22135
22144
  });
22136
22145
 
22137
- // ../../node_modules/.pnpm/@fern-api+replay@0.16.2/node_modules/@fern-api/replay/dist/index.js
22146
+ // ../../node_modules/.pnpm/@fern-api+replay@0.18.0/node_modules/@fern-api/replay/dist/index.js
22138
22147
  var dist_exports = {};
22139
22148
  __export(dist_exports, {
22140
22149
  CREDENTIAL_PATTERNS: () => CREDENTIAL_PATTERNS,
@@ -22495,7 +22504,8 @@ function isBinaryFile(filePath) {
22495
22504
  }
22496
22505
  function matchesFernignorePattern(filePath, patterns) {
22497
22506
  if (patterns.length === 0) return false;
22498
- return patterns.some((p2) => {
22507
+ return patterns.some((rawPattern) => {
22508
+ const p2 = rawPattern.endsWith("/") ? rawPattern.slice(0, -1) : rawPattern;
22499
22509
  if (filePath === p2) return true;
22500
22510
  if (minimatch(filePath, p2)) return true;
22501
22511
  if (!p2.includes("*") && !p2.includes("?") && filePath.startsWith(p2 + "/")) return true;
@@ -22739,16 +22749,20 @@ async function resolveInputs(args) {
22739
22749
  const oursPath = (0, import_path11.join)(outputDir, resolvedPath);
22740
22750
  const ours = await (0, import_promises5.readFile)(oursPath, "utf-8").catch(() => null);
22741
22751
  let ghostTheirs = null;
22742
- if (!base && ours && !renameSourcePath) {
22752
+ let baseTreeUnreachable = false;
22753
+ if (!base && !renameSourcePath) {
22743
22754
  const treeReachable = await isTreeReachable(baseGen.tree_hash);
22744
22755
  if (!treeReachable) {
22745
- const fileDiff = extractFileDiff2(patch.patch_content, filePath);
22746
- if (fileDiff) {
22747
- const { reconstructFromGhostPatch: reconstructFromGhostPatch2 } = await Promise.resolve().then(() => (init_HybridReconstruction(), HybridReconstruction_exports));
22748
- const result = reconstructFromGhostPatch2(fileDiff, ours);
22749
- if (result) {
22750
- base = result.base;
22751
- ghostTheirs = result.theirs;
22756
+ baseTreeUnreachable = true;
22757
+ if (ours) {
22758
+ const fileDiff = extractFileDiff2(patch.patch_content, filePath);
22759
+ if (fileDiff) {
22760
+ const { reconstructFromGhostPatch: reconstructFromGhostPatch2 } = await Promise.resolve().then(() => (init_HybridReconstruction(), HybridReconstruction_exports));
22761
+ const result = reconstructFromGhostPatch2(fileDiff, ours);
22762
+ if (result) {
22763
+ base = result.base;
22764
+ ghostTheirs = result.theirs;
22765
+ }
22752
22766
  }
22753
22767
  }
22754
22768
  }
@@ -22760,7 +22774,11 @@ async function resolveInputs(args) {
22760
22774
  ours,
22761
22775
  oursPath,
22762
22776
  renameSourcePath,
22763
- ghostTheirs
22777
+ ghostTheirs,
22778
+ // Only meaningful when ghost reconstruction did NOT recover a base; if it
22779
+ // did, `base` is non-null and the merge proceeds normally. We still set
22780
+ // the flag truthfully (the tree IS unreachable) so Phase 3 can decide.
22781
+ baseTreeUnreachable
22764
22782
  };
22765
22783
  }
22766
22784
  function trimCR(line) {
@@ -22936,7 +22954,11 @@ async function buildMergePlan(args) {
22936
22954
  return { kind: "new-file-only-user", theirs: effective_theirs };
22937
22955
  }
22938
22956
  if (base == null && ours != null && effective_theirs != null && !useAccumulatorAsMergeBase) {
22939
- return { kind: "new-file-both", theirs: effective_theirs };
22957
+ return {
22958
+ kind: "new-file-both",
22959
+ theirs: effective_theirs,
22960
+ ...inputs.baseTreeUnreachable ? { baseTreeUnreachable: true } : {}
22961
+ };
22940
22962
  }
22941
22963
  if (effective_theirs == null) {
22942
22964
  return { kind: "skipped", reason: "missing-content" };
@@ -22980,17 +23002,19 @@ async function runMergeAndRecord(args) {
22980
23002
  const merged2 = threeWayMerge("", ours, plan.theirs);
22981
23003
  await (0, import_promises6.mkdir)((0, import_path12.dirname)(oursPath), { recursive: true });
22982
23004
  await (0, import_promises6.writeFile)(oursPath, merged2.content);
23005
+ const unreachableFlag = plan.baseTreeUnreachable ? { baseTreeUnreachable: true } : {};
22983
23006
  if (merged2.hasConflicts) {
22984
23007
  return {
22985
23008
  file: resolvedPath,
22986
23009
  status: "conflict",
22987
23010
  conflicts: merged2.conflicts,
22988
23011
  conflictReason: "new-file-both",
22989
- conflictMetadata: metadata
23012
+ conflictMetadata: metadata,
23013
+ ...unreachableFlag
22990
23014
  };
22991
23015
  }
22992
23016
  accumulator.recordMerge(resolvedPath, merged2.content, patch.base_generation);
22993
- return { file: resolvedPath, status: "merged" };
23017
+ return { file: resolvedPath, status: "merged", ...unreachableFlag };
22994
23018
  }
22995
23019
  const merged = threeWayMerge(plan.mergeBase, ours, plan.theirs);
22996
23020
  await (0, import_promises6.mkdir)((0, import_path12.dirname)(oursPath), { recursive: true });
@@ -24064,7 +24088,7 @@ function status(outputDir) {
24064
24088
  }
24065
24089
  var import_promises3, import_os, import_path7, import_fs6, import_path8, import_yaml, import_crypto, import_path9, import_promises4, import_os2, import_path10, import_promises5, import_path11, import_promises6, import_path12, import_fs7, import_promises7, import_path13, import_promises8, import_os3, import_path14, import_child_process3, import_crypto2, import_fs8, import_path15, import_yaml2, import_crypto3, import_fs9, import_path16, import_fs10, import_promises9, import_path17, __defProp3, __getOwnPropNames3, __esm3, __export3, GitClient_exports, GitClient, init_GitClient, HybridReconstruction_exports, init_HybridReconstruction, PatchApplyTheirs_exports, init_PatchApplyTheirs, CREDENTIAL_PATTERNS, SENSITIVE_FILE_PATTERNS, FERN_BOT_NAME, FERN_BOT_EMAIL, FERN_BOT_LOGIN, FERN_SUPPORT_NAMES, LOCKFILE_HEADER, LockfileNotFoundError, LockfileManager, BINARY_EXTENSIONS, INFRASTRUCTURE_FILES, ReplayDetector, MergeAccumulator, CONFLICT_OPENER, CONFLICT_SEPARATOR, CONFLICT_CLOSER, ReplayApplicator, ReplayCommitter, FileOwnership, FirstGenerationFlow, SkipApplicationFlow, NoPatchesFlow, NormalRegenerationFlow, ReplayService, FernignoreMigrator, REPLAY_FERNIGNORE_ENTRIES, GITATTRIBUTES_ENTRIES, EMPTY_RESULT;
24066
24090
  var init_dist3 = __esm({
24067
- "../../node_modules/.pnpm/@fern-api+replay@0.16.2/node_modules/@fern-api/replay/dist/index.js"() {
24091
+ "../../node_modules/.pnpm/@fern-api+replay@0.18.0/node_modules/@fern-api/replay/dist/index.js"() {
24068
24092
  "use strict";
24069
24093
  init_esm4();
24070
24094
  import_promises3 = require("fs/promises");
@@ -24145,6 +24169,11 @@ var init_dist3 = __esm({
24145
24169
  proc.stderr.on("data", (data) => {
24146
24170
  stderr += data.toString();
24147
24171
  });
24172
+ proc.on("error", (err) => {
24173
+ reject(new Error(`git ${args.join(" ")} failed to spawn: ${err.message}`));
24174
+ });
24175
+ proc.stdin.on("error", () => {
24176
+ });
24148
24177
  proc.on("close", (code) => {
24149
24178
  if (code === 0) {
24150
24179
  resolve22(stdout);
@@ -24599,6 +24628,22 @@ var init_dist3 = __esm({
24599
24628
  return commit.sha;
24600
24629
  }
24601
24630
  }
24631
+ let fullLog;
24632
+ try {
24633
+ fullLog = await this.git.exec([
24634
+ "log",
24635
+ "--no-merges",
24636
+ "--format=%H%x00%an%x00%ae%x00%s",
24637
+ "HEAD"
24638
+ ]);
24639
+ } catch {
24640
+ return null;
24641
+ }
24642
+ for (const commit of this.parseGitLog(fullLog)) {
24643
+ if (isGenerationBoundary(commit)) {
24644
+ return commit.sha;
24645
+ }
24646
+ }
24602
24647
  return null;
24603
24648
  }
24604
24649
  async detectNewPatches() {
@@ -24610,6 +24655,11 @@ var init_dist3 = __esm({
24610
24655
  }
24611
24656
  const lastGen = this.getLastGeneration(lock);
24612
24657
  if (!lastGen) {
24658
+ if (lock.current_generation) {
24659
+ this.warnings.push(
24660
+ `No generation boundary found in git history and the lockfile's recorded generation (${lock.current_generation.slice(0, 7)}) matches no entry in its generation records. Customer customizations may differ from the last known generation and could be lost on the next regeneration. Run \`fern-replay bootstrap --force\` to re-initialize tracking.`
24661
+ );
24662
+ }
24613
24663
  return { patches: [], revertedPatchIds: [] };
24614
24664
  }
24615
24665
  const exists2 = await this.git.commitExists(lastGen.commit_sha);
@@ -24657,20 +24707,15 @@ var init_dist3 = __esm({
24657
24707
  continue;
24658
24708
  }
24659
24709
  const parents = await this.git.getCommitParents(commit.sha);
24660
- let patchContent;
24710
+ let filesOutput;
24661
24711
  try {
24662
- patchContent = await this.git.formatPatch(commit.sha);
24712
+ filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
24663
24713
  } catch {
24664
24714
  this.warnings.push(
24665
24715
  `Could not generate patch for commit ${commit.sha.slice(0, 7)} \u2014 it may be unreachable in a shallow clone. Skipping.`
24666
24716
  );
24667
24717
  continue;
24668
24718
  }
24669
- let contentHash = this.computeContentHash(patchContent);
24670
- if (lock.patches.find((p2) => p2.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
24671
- continue;
24672
- }
24673
- const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
24674
24719
  const { files, sensitiveFiles, fernignoredFiles } = filterInfrastructureAndSensitiveFiles(
24675
24720
  filesOutput,
24676
24721
  this.fernignorePatterns
@@ -24685,19 +24730,31 @@ var init_dist3 = __esm({
24685
24730
  `.fernignore-protected file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${fernignoredFiles.join(", ")}. These files are managed by .fernignore, not Replay.`
24686
24731
  );
24687
24732
  }
24688
- if ((sensitiveFiles.length > 0 || fernignoredFiles.length > 0) && files.length > 0) {
24689
- const parentSha = parents[0];
24690
- if (parentSha) {
24691
- try {
24692
- patchContent = await this.git.exec(["diff", parentSha, commit.sha, "--", ...files]);
24693
- } catch {
24694
- continue;
24695
- }
24696
- if (!patchContent.trim()) continue;
24697
- contentHash = this.computeContentHash(patchContent);
24733
+ if (files.length === 0) {
24734
+ continue;
24735
+ }
24736
+ const wasFiltered = sensitiveFiles.length > 0 || fernignoredFiles.length > 0;
24737
+ const parentSha = parents[0];
24738
+ let patchContent;
24739
+ if (wasFiltered && parentSha) {
24740
+ try {
24741
+ patchContent = await this.git.exec(["diff", parentSha, commit.sha, "--", ...files]);
24742
+ } catch {
24743
+ continue;
24744
+ }
24745
+ if (!patchContent.trim()) continue;
24746
+ } else {
24747
+ try {
24748
+ patchContent = await this.git.formatPatch(commit.sha);
24749
+ } catch {
24750
+ this.warnings.push(
24751
+ `Could not generate patch for commit ${commit.sha.slice(0, 7)} \u2014 it may be unreachable in a shallow clone. Skipping.`
24752
+ );
24753
+ continue;
24698
24754
  }
24699
24755
  }
24700
- if (files.length === 0) {
24756
+ const contentHash = this.computeContentHash(patchContent);
24757
+ if (lock.patches.find((p2) => p2.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
24701
24758
  continue;
24702
24759
  }
24703
24760
  const theirsSnapshot = await capturePatchSnapshot(this.git, files, commit.sha);
@@ -25984,6 +26041,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
25984
26041
  results = await this.service.applicator.applyPatches(newPatches);
25985
26042
  this.service._lastApplyResults = results;
25986
26043
  this.service.recordDroppedContextLineWarnings(results);
26044
+ this.service.recordUnreachableBaseWarnings(results);
25987
26045
  this.service.revertConflictingFiles(results);
25988
26046
  for (const result of results) {
25989
26047
  if (result.status === "conflict") {
@@ -26137,6 +26195,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
26137
26195
  const results = await this.service.applicator.applyPatches(allPatches);
26138
26196
  this.service._lastApplyResults = results;
26139
26197
  this.service.recordDroppedContextLineWarnings(results);
26198
+ this.service.recordUnreachableBaseWarnings(results);
26140
26199
  this.service.revertConflictingFiles(results);
26141
26200
  for (const result of results) {
26142
26201
  if (result.status === "conflict") {
@@ -27047,6 +27106,37 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
27047
27106
  }
27048
27107
  }
27049
27108
  }
27109
+ /**
27110
+ * After applyPatches(), surface a warning for each (patch × file) where a
27111
+ * customization's BASE could not be read because the patch's
27112
+ * base-generation tree is unreachable in this clone (shallow-clone window
27113
+ * too small / pruned history). In that state the merge pipeline falls
27114
+ * through to `new-file-both` and the customer's edit can be dropped with NO
27115
+ * conflict and NO error — the INC-866 silent-loss class. This converts that
27116
+ * silence into an explicit, actionable warning naming the file and the
27117
+ * likely cause (insufficient clone window).
27118
+ *
27119
+ * Mirrors `recordDroppedContextLineWarnings`'s "surface, never silently
27120
+ * drop" contract. De-duplicated per file path so a single insufficient
27121
+ * window doesn't spam one warning per touched file repeatedly.
27122
+ */
27123
+ /** @internal Used by Flow implementations in `src/flows/`. */
27124
+ recordUnreachableBaseWarnings(results) {
27125
+ const warned = /* @__PURE__ */ new Set();
27126
+ for (const result of results) {
27127
+ if (!result.fileResults) continue;
27128
+ for (const fr of result.fileResults) {
27129
+ if (!fr.baseTreeUnreachable) continue;
27130
+ if (warned.has(fr.file)) continue;
27131
+ warned.add(fr.file);
27132
+ const base = fr.conflictMetadata?.baseGeneration;
27133
+ const baseRef = base ? ` (base generation ${base.slice(0, 7)})` : "";
27134
+ this.warnings.push(
27135
+ `${fr.file}: could not read the prior generation's version of this file${baseRef} because that generation's tree is outside the clone window (insufficient shallow-clone depth or pruned history). The customization was treated as a new file, which can drop your edits silently. Re-run with the prior generation in the clone window \u2014 e.g. \`git fetch --shallow-since=<date just before the prior [fern-generated] commit>\` or \`git fetch --unshallow\` \u2014 then re-run replay.`
27136
+ );
27137
+ }
27138
+ }
27139
+ }
27050
27140
  /**
27051
27141
  * After applyPatches(), strip conflict markers from conflicting files
27052
27142
  * so only clean content is committed. Keeps the Generated (OURS) side.
@@ -39977,7 +40067,7 @@ var ReplayPrepareError = class extends Error {
39977
40067
  if (Error.captureStackTrace) {
39978
40068
  Error.captureStackTrace(this, this.constructor);
39979
40069
  }
39980
- this.name = this.constructor.name;
40070
+ this.name = "ReplayPrepareError";
39981
40071
  this.reason = reason;
39982
40072
  }
39983
40073
  };
@@ -40106,7 +40196,7 @@ var CliError = class _CliError extends Error {
40106
40196
  if (Error.captureStackTrace) {
40107
40197
  Error.captureStackTrace(this, this.constructor);
40108
40198
  }
40109
- this.name = this.constructor.name;
40199
+ this.name = "CliError";
40110
40200
  this.code = code;
40111
40201
  this.docsLink = docsLink;
40112
40202
  }
@@ -42145,16 +42235,14 @@ async function pushSignedCommit({
42145
42235
  await repository.pushObjectToRef(localHeadSha, tempRef);
42146
42236
  tempRefPushed = true;
42147
42237
  for (let attempt2 = 0; attempt2 < MAX_CONCURRENT_PUSH_RETRIES; attempt2++) {
42148
- const resolvedAuthor = author ?? { name: FERN_BOT_NAME2, email: FERN_BOT_EMAIL2 };
42149
- const committerField = author != null ? { committer: author } : {};
42238
+ const identityFields = author != null ? { author, committer: author } : {};
42150
42239
  const { data: signedCommit } = await octokit.git.createCommit({
42151
42240
  owner,
42152
42241
  repo,
42153
42242
  message,
42154
42243
  tree: treeSha,
42155
42244
  parents,
42156
- author: resolvedAuthor,
42157
- ...committerField
42245
+ ...identityFields
42158
42246
  });
42159
42247
  const signedSha = signedCommit.sha;
42160
42248
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fern-api/generator-cli",
3
- "version": "0.9.37",
3
+ "version": "0.9.40",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fern-api/fern.git",
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "@boundaryml/baml": "^0.219.0",
26
- "@fern-api/replay": "0.16.2",
26
+ "@fern-api/replay": "0.18.0",
27
27
  "@octokit/rest": "^22.0.1",
28
28
  "es-toolkit": "^1.45.1",
29
29
  "semver": "^7.6.3",