@fern-api/generator-cli 0.9.44 → 0.9.45

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 (2) hide show
  1. package/dist/cli.js +150 -13
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -41018,7 +41018,8 @@ var AutoVersioningService = class _AutoVersioningService {
41018
41018
  contentLines.push(line);
41019
41019
  }
41020
41020
  }
41021
- let processedContent = this.removeVersionChangePairs(contentLines, mappedMagicVersion);
41021
+ let processedContent = this.removeGoModulePathSuffixPairs(contentLines);
41022
+ processedContent = this.removeVersionChangePairs(processedContent, mappedMagicVersion);
41022
41023
  processedContent = this.removeRemainingMagicVersionLines(processedContent, mappedMagicVersion);
41023
41024
  let hasChanges = false;
41024
41025
  for (const line of processedContent) {
@@ -41154,21 +41155,85 @@ var AutoVersioningService = class _AutoVersioningService {
41154
41155
  * so non-Go diffs like API URL version changes are not affected.
41155
41156
  */
41156
41157
  isGoModulePathSuffixChange(minusLine, plusLine) {
41157
- const minusContent = minusLine.substring(1);
41158
- const plusContent = plusLine.substring(1);
41159
- const goModulePathPattern = /"[^"]*(?:github\.com|golang\.org|google\.golang\.org|gopkg\.in)\/[^"]*"/;
41160
- const goModuleDirectivePattern = /^\s*module\s+(?:github\.com|golang\.org|google\.golang\.org|gopkg\.in)\/\S+/;
41161
- const hasGoModulePath = goModulePathPattern.test(minusContent) || goModulePathPattern.test(plusContent) || goModuleDirectivePattern.test(minusContent.trim()) || goModuleDirectivePattern.test(plusContent.trim());
41162
- if (!hasGoModulePath) {
41158
+ const minus = this.analyzeGoModulePathLine(minusLine);
41159
+ const plus = this.analyzeGoModulePathLine(plusLine);
41160
+ if (minus == null || plus == null) {
41163
41161
  return false;
41164
41162
  }
41165
- const suffixPattern = /\/v\d+(?=\/|"\s*$|$)/g;
41166
- const minusWithoutSuffix = minusContent.replace(suffixPattern, "");
41167
- const plusWithoutSuffix = plusContent.replace(suffixPattern, "");
41168
- if (minusWithoutSuffix !== plusWithoutSuffix) {
41163
+ if (minus.strippedContent !== plus.strippedContent) {
41169
41164
  return false;
41170
41165
  }
41171
- return /\/v\d+(?=\/|"\s*$|$)/.test(minusContent) || /\/v\d+(?=\/|"\s*$|$)/.test(plusContent);
41166
+ return minus.hasSuffix || plus.hasSuffix;
41167
+ }
41168
+ /**
41169
+ * Removes Go module path `/vN` suffix-only churn from a diff section, regardless of how the
41170
+ * deletion and addition lines are laid out. `removeVersionChangePairs` only removes a deletion
41171
+ * when it can pair it with a *nearby* addition and keeps every line in between — so a diff that
41172
+ * groups all deletions before all additions (as Go import blocks do) leaks the suffix churn for
41173
+ * every line except the first. That churn then reaches the AI diff analyzer, which sees the
41174
+ * previously published `/vN` module path changing on every import and misclassifies a
41175
+ * no-op regeneration as a breaking change (spurious major bump).
41176
+ *
41177
+ * This pass matches each suffix-bearing deletion with any suffix-only-equivalent addition in
41178
+ * the section (by suffix-stripped content) and drops both, so only genuine content changes to
41179
+ * Go module path lines survive.
41180
+ */
41181
+ removeGoModulePathSuffixPairs(lines) {
41182
+ const additions = [];
41183
+ for (let index = 0; index < lines.length; index++) {
41184
+ const line = lines[index];
41185
+ if (line == null || !this.isAdditionLine(line)) {
41186
+ continue;
41187
+ }
41188
+ const info = this.analyzeGoModulePathLine(line);
41189
+ if (info != null) {
41190
+ additions.push({ index, strippedContent: info.strippedContent });
41191
+ }
41192
+ }
41193
+ const removed = /* @__PURE__ */ new Set();
41194
+ const usedAdditions = /* @__PURE__ */ new Set();
41195
+ for (let index = 0; index < lines.length; index++) {
41196
+ const line = lines[index];
41197
+ if (line == null || !this.isDeletionLine(line)) {
41198
+ continue;
41199
+ }
41200
+ const info = this.analyzeGoModulePathLine(line);
41201
+ if (info == null || !info.hasSuffix) {
41202
+ continue;
41203
+ }
41204
+ const match2 = additions.find(
41205
+ (addition) => !usedAdditions.has(addition.index) && addition.strippedContent === info.strippedContent
41206
+ );
41207
+ if (match2 != null) {
41208
+ usedAdditions.add(match2.index);
41209
+ removed.add(index);
41210
+ removed.add(match2.index);
41211
+ }
41212
+ }
41213
+ if (removed.size === 0) {
41214
+ return lines;
41215
+ }
41216
+ return lines.filter((_2, index) => !removed.has(index));
41217
+ }
41218
+ /**
41219
+ * If a diff line (with its leading +/- stripped) references a Go module path — either a quoted
41220
+ * import path or a `module` directive — returns its content with any `/vN` module-version
41221
+ * suffixes removed, plus whether a suffix was present. Returns undefined for non-Go lines so
41222
+ * unrelated diffs (e.g. API URL version segments) are never touched.
41223
+ */
41224
+ analyzeGoModulePathLine(line) {
41225
+ const content = line.substring(1);
41226
+ const goModulePathPattern = /"[^"]*(?:github\.com|golang\.org|google\.golang\.org|gopkg\.in)\/[^"]*"/;
41227
+ const goModuleDirectivePattern = /^\s*module\s+(?:github\.com|golang\.org|google\.golang\.org|gopkg\.in)\/\S+/;
41228
+ const isGoModulePathLine = goModulePathPattern.test(content) || goModuleDirectivePattern.test(content.trim());
41229
+ if (!isGoModulePathLine) {
41230
+ return void 0;
41231
+ }
41232
+ const suffixPattern = /\/v\d+(?=\/|"|$)/g;
41233
+ return {
41234
+ strippedContent: content.replace(suffixPattern, ""),
41235
+ hasSuffix: suffixPattern.test(content)
41236
+ };
41172
41237
  }
41173
41238
  /**
41174
41239
  * Gets the latest semantic version from git tags in the given repository.
@@ -42846,11 +42911,16 @@ var GithubStep = class extends BaseStep {
42846
42911
  throw new Error(`Unexpected branch action: ${String(_exhaustive)}`);
42847
42912
  }
42848
42913
  }
42914
+ const createdChangelog = await this.ensureChangelogFile(resolved);
42849
42915
  if (!skipCommit) {
42850
42916
  await this.ensureFernignore();
42851
42917
  this.logger.debug("Committing changes...");
42852
42918
  await repository.commitAllChanges(resolved.commitMessage);
42853
42919
  this.logger.debug(`Committed changes to local copy of GitHub repository at ${this.outputDir}`);
42920
+ } else if (createdChangelog) {
42921
+ this.logger.debug("Committing reconstructed changelog.md on top of the replay commit...");
42922
+ await repository.add("changelog.md");
42923
+ await repository.commit(resolved.commitMessage);
42854
42924
  }
42855
42925
  if (shouldCheckNoDiff(this.config)) {
42856
42926
  const noDiff = await repository.treeHashEquals(`origin/${baseBranch}`);
@@ -42891,7 +42961,20 @@ var GithubStep = class extends BaseStep {
42891
42961
  }
42892
42962
  }
42893
42963
  const headSha = await repository.getHeadSha();
42894
- const changelogUrl = resolved.changelogEntry ? `https://${remote}/${owner}/${repo}/blob/${headSha}/changelog.md` : void 0;
42964
+ const changelogCommitted = (await repository.listTrackedFiles()).includes("changelog.md");
42965
+ const changelogUrl = resolveChangelogUrl({
42966
+ changelogEntry: resolved.changelogEntry,
42967
+ changelogCommitted,
42968
+ remote,
42969
+ owner,
42970
+ repo,
42971
+ headSha
42972
+ });
42973
+ if (resolved.changelogEntry && !changelogCommitted) {
42974
+ this.logger.warn(
42975
+ 'changelog.md is not present in the committed tree; omitting the "See full changelog" link.'
42976
+ );
42977
+ }
42895
42978
  const { prTitle, prBody } = parseCommitMessageForPR(
42896
42979
  resolved.commitMessage,
42897
42980
  resolved.changelogEntry,
@@ -42969,6 +43052,7 @@ var GithubStep = class extends BaseStep {
42969
43052
  this.logger.debug(`Checking out branch ${this.config.branch}`);
42970
43053
  await repository.checkout(this.config.branch);
42971
43054
  }
43055
+ await this.ensureChangelogFile(resolved);
42972
43056
  await this.ensureFernignore();
42973
43057
  this.logger.debug("Committing changes...");
42974
43058
  await repository.commitAllChanges(resolved.commitMessage);
@@ -43010,6 +43094,7 @@ var GithubStep = class extends BaseStep {
43010
43094
  this.logger.debug(`Checking out branch ${this.config.branch}`);
43011
43095
  await repository.checkout(this.config.branch);
43012
43096
  }
43097
+ await this.ensureChangelogFile(resolved);
43013
43098
  await this.ensureFernignore();
43014
43099
  this.logger.debug("Committing changes...");
43015
43100
  await repository.commitAllChanges(resolved.commitMessage);
@@ -43087,6 +43172,33 @@ var GithubStep = class extends BaseStep {
43087
43172
  await (0, import_promises16.writeFile)(fernignorePath, "# Specify files that shouldn't be modified by Fern\n", "utf-8");
43088
43173
  }
43089
43174
  }
43175
+ /**
43176
+ * Guarantees `changelog.md` is present in the output directory whenever a changelog entry
43177
+ * exists for this run, so the file lands in the committed tree that GithubStep pushes and the
43178
+ * "See full changelog" link resolves. AutoVersionStep normally writes this file via
43179
+ * `prependChangelogEntry`; this is a safety net for paths where that write never reached the
43180
+ * directory GithubStep commits (e.g. non-replay self-hosted generation), which previously left
43181
+ * the link pointing at a non-existent blob (404).
43182
+ *
43183
+ * Returns `true` when it created the file, `false` when the file already existed (or there is
43184
+ * no changelog entry to write).
43185
+ */
43186
+ async ensureChangelogFile(resolved) {
43187
+ const entry = resolved.changelogEntry?.trim();
43188
+ if (entry == null || entry.length === 0) {
43189
+ return false;
43190
+ }
43191
+ const changelogPath = (0, import_path24.join)(this.outputDir, "changelog.md");
43192
+ try {
43193
+ await (0, import_promises16.access)(changelogPath);
43194
+ return false;
43195
+ } catch {
43196
+ const version2 = resolved.newVersion ?? resolved.previousVersion;
43197
+ await (0, import_promises16.writeFile)(changelogPath, buildChangelogFileContents(entry, version2), "utf-8");
43198
+ this.logger.debug(`Wrote changelog.md${version2 != null ? ` for ${version2}` : ""}.`);
43199
+ return true;
43200
+ }
43201
+ }
43090
43202
  deriveSkipCommit(replayResult) {
43091
43203
  if (replayResult == null) {
43092
43204
  return false;
@@ -43170,6 +43282,31 @@ function resolveBranchAction({
43170
43282
  }
43171
43283
  return "create-from-head";
43172
43284
  }
43285
+ function buildChangelogFileContents(entry, version2) {
43286
+ const trimmed2 = entry.trim();
43287
+ const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
43288
+ const header = version2 != null ? `## [${version2}] - ${now}
43289
+ ` : `## ${now}
43290
+ `;
43291
+ return `# Changelog
43292
+
43293
+ ${header}${trimmed2}
43294
+
43295
+ `;
43296
+ }
43297
+ function resolveChangelogUrl({
43298
+ changelogEntry,
43299
+ changelogCommitted,
43300
+ remote,
43301
+ owner,
43302
+ repo,
43303
+ headSha
43304
+ }) {
43305
+ if (changelogEntry == null || changelogEntry.trim().length === 0 || !changelogCommitted) {
43306
+ return void 0;
43307
+ }
43308
+ return `https://${remote}/${owner}/${repo}/blob/${headSha}/changelog.md`;
43309
+ }
43173
43310
 
43174
43311
  // src/pipeline/steps/ReplayStep.ts
43175
43312
  var import_child_process6 = require("child_process");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fern-api/generator-cli",
3
- "version": "0.9.44",
3
+ "version": "0.9.45",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fern-api/fern.git",