@fern-api/generator-cli 0.9.35 → 0.9.37

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 +334 -45
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -3822,9 +3822,9 @@ var init_esm3 = __esm({
3822
3822
  }
3823
3823
  });
3824
3824
 
3825
- // ../../node_modules/.pnpm/tmp@0.2.5/node_modules/tmp/lib/tmp.js
3825
+ // ../../node_modules/.pnpm/tmp@0.2.6/node_modules/tmp/lib/tmp.js
3826
3826
  var require_tmp = __commonJS({
3827
- "../../node_modules/.pnpm/tmp@0.2.5/node_modules/tmp/lib/tmp.js"(exports2, module2) {
3827
+ "../../node_modules/.pnpm/tmp@0.2.6/node_modules/tmp/lib/tmp.js"(exports2, module2) {
3828
3828
  "use strict";
3829
3829
  var fs3 = require("fs");
3830
3830
  var os = require("os");
@@ -4075,13 +4075,20 @@ var require_tmp = __commonJS({
4075
4075
  ].join("");
4076
4076
  return path5.join(tmpDir, opts.dir, name);
4077
4077
  }
4078
+ function _assertPath(path6) {
4079
+ if (path6.includes("..")) {
4080
+ throw new Error("Relative value not allowed");
4081
+ }
4082
+ return path6;
4083
+ }
4078
4084
  function _assertOptionsBase(options) {
4079
4085
  if (!_isUndefined(options.name)) {
4080
4086
  const name = options.name;
4081
4087
  if (path5.isAbsolute(name)) throw new Error(`name option must not contain an absolute path, found "${name}".`);
4082
4088
  const basename2 = path5.basename(name);
4083
- if (basename2 === ".." || basename2 === "." || basename2 !== name)
4089
+ if (basename2 === ".." || basename2 === "." || basename2 !== name) {
4084
4090
  throw new Error(`name option must not contain a path, found "${name}".`);
4091
+ }
4085
4092
  }
4086
4093
  if (!_isUndefined(options.template) && !options.template.match(TEMPLATE_PATTERN)) {
4087
4094
  throw new Error(`Invalid template, found "${options.template}".`);
@@ -4094,15 +4101,16 @@ var require_tmp = __commonJS({
4094
4101
  options.detachDescriptor = !!options.detachDescriptor;
4095
4102
  options.discardDescriptor = !!options.discardDescriptor;
4096
4103
  options.unsafeCleanup = !!options.unsafeCleanup;
4097
- options.prefix = _isUndefined(options.prefix) ? "" : options.prefix;
4098
- options.postfix = _isUndefined(options.postfix) ? "" : options.postfix;
4104
+ options.prefix = _isUndefined(options.prefix) ? "" : _assertPath(options.prefix);
4105
+ options.postfix = _isUndefined(options.postfix) ? "" : _assertPath(options.postfix);
4106
+ options.template = _isUndefined(options.template) ? void 0 : _assertPath(options.template);
4099
4107
  }
4100
4108
  function _getRelativePath(option, name, tmpDir, cb) {
4101
4109
  if (_isUndefined(name)) return cb(null);
4102
4110
  _resolvePath(name, tmpDir, function(err, resolvedPath) {
4103
4111
  if (err) return cb(err);
4104
4112
  const relativePath = path5.relative(tmpDir, resolvedPath);
4105
- if (!resolvedPath.startsWith(tmpDir)) {
4113
+ if (relativePath.startsWith("..") || path5.isAbsolute(relativePath)) {
4106
4114
  return cb(new Error(`${option} option must be relative to "${tmpDir}", found "${relativePath}".`));
4107
4115
  }
4108
4116
  cb(null, relativePath);
@@ -4112,7 +4120,7 @@ var require_tmp = __commonJS({
4112
4120
  if (_isUndefined(name)) return;
4113
4121
  const resolvedPath = _resolvePathSync(name, tmpDir);
4114
4122
  const relativePath = path5.relative(tmpDir, resolvedPath);
4115
- if (!resolvedPath.startsWith(tmpDir)) {
4123
+ if (relativePath.startsWith("..") || path5.isAbsolute(relativePath)) {
4116
4124
  throw new Error(`${option} option must be relative to "${tmpDir}", found "${relativePath}".`);
4117
4125
  }
4118
4126
  return relativePath;
@@ -41173,32 +41181,30 @@ var AutoVersionStep = class extends BaseStep {
41173
41181
  name = "autoVersion";
41174
41182
  async execute(context) {
41175
41183
  const prepared = context.previousStepResults.generationCommit?.preparedReplay;
41176
- if (!prepared) {
41177
- this.logger.info(
41178
- "AutoVersionStep: no replay preparation available (replay uninitialized or prepare failed); skipping."
41179
- );
41180
- return { executed: true, success: true };
41181
- }
41182
- if (prepared.flow === "skip-application") {
41183
- this.logger.info("AutoVersionStep: replay flow is skip-application; skipping autoversion.");
41184
- return { executed: true, success: true };
41185
- }
41186
- const language = this.config.language;
41187
- const mappedMagicVersion = mapMagicVersionForLanguage(MAGIC_VERSION, language);
41188
- const service = new AutoVersioningService({ logger: this.toTaskLogger() });
41189
- if (prepared.previousGenerationSha == null) {
41190
- return await this.handleFirstGeneration({ service, language, mappedMagicVersion });
41184
+ if (prepared) {
41185
+ if (prepared.flow === "skip-application") {
41186
+ this.logger.info("AutoVersionStep: replay flow is skip-application; skipping autoversion.");
41187
+ return { executed: true, success: true };
41188
+ }
41189
+ const language = this.config.language;
41190
+ const mappedMagicVersion = mapMagicVersionForLanguage(MAGIC_VERSION, language);
41191
+ const service = new AutoVersioningService({ logger: this.toTaskLogger() });
41192
+ if (prepared.previousGenerationSha == null) {
41193
+ return await this.handleFirstGeneration({ service, language, mappedMagicVersion, commit: true });
41194
+ }
41195
+ return await this.handleNormalFlow({
41196
+ prepared,
41197
+ service,
41198
+ language,
41199
+ mappedMagicVersion,
41200
+ previousGenerationSha: prepared.previousGenerationSha
41201
+ });
41191
41202
  }
41192
- return await this.handleNormalFlow({
41193
- prepared,
41194
- service,
41195
- language,
41196
- mappedMagicVersion,
41197
- previousGenerationSha: prepared.previousGenerationSha
41198
- });
41203
+ this.logger.info("AutoVersionStep: running in non-replay mode (git diff HEAD).");
41204
+ return await this.handleNonReplayFlow();
41199
41205
  }
41200
41206
  async handleFirstGeneration(params) {
41201
- const { service, language, mappedMagicVersion } = params;
41207
+ const { service, language, mappedMagicVersion, commit } = params;
41202
41208
  const initialVersion = this.config.baseVersion ?? (mappedMagicVersion.startsWith("v") ? "v0.0.1" : "0.0.1");
41203
41209
  if (!isValidSemver(initialVersion)) {
41204
41210
  const errorMessage = `AutoVersionStep: baseVersion ${JSON.stringify(initialVersion)} is not a valid semver string (expected e.g. "1.2.3" or "v1.2.3"). Refusing to run to avoid shell injection into the placeholder-rewrite step.`;
@@ -41215,7 +41221,7 @@ var AutoVersionStep = class extends BaseStep {
41215
41221
  await service.addGoMajorVersionSuffix(this.outputDir, initialVersion);
41216
41222
  }
41217
41223
  const commitMessage = this.brandMessage("Initial SDK generation");
41218
- const commitSha = this.commitAutoversion(commitMessage);
41224
+ const commitSha = commit ? this.commitAutoversion(commitMessage) : void 0;
41219
41225
  return {
41220
41226
  executed: true,
41221
41227
  success: true,
@@ -41234,7 +41240,7 @@ var AutoVersionStep = class extends BaseStep {
41234
41240
  baseVersion: this.config.baseVersion
41235
41241
  });
41236
41242
  if (previousVersion == null) {
41237
- return await this.handleFirstGeneration({ service, language, mappedMagicVersion });
41243
+ return await this.handleFirstGeneration({ service, language, mappedMagicVersion, commit: true });
41238
41244
  }
41239
41245
  if (rawDiff.trim().length === 0) {
41240
41246
  this.logger.info(
@@ -41278,17 +41284,21 @@ var AutoVersionStep = class extends BaseStep {
41278
41284
  analysis: { versionBump: "PATCH", message: this.brandMessage("SDK regeneration") }
41279
41285
  });
41280
41286
  }
41281
- const chunks = service.chunkDiff(cleanedDiff, MAX_AI_DIFF_BYTES);
41282
- const cappedChunks = chunks.slice(0, MAX_CHUNKS);
41283
- const skippedChunks = chunks.length - cappedChunks.length;
41284
- if (chunks.length > 1) {
41285
- this.logger.info(
41286
- `AutoVersionStep: split diff into ${chunks.length} chunks` + (skippedChunks > 0 ? ` (capped at ${MAX_CHUNKS}, skipping ${skippedChunks})` : "")
41287
- );
41288
- }
41289
41287
  let analysis;
41290
41288
  try {
41291
- analysis = cappedChunks.length <= 1 ? await this.analyzeSingle(cleanedDiff, language, previousVersion) : await this.analyzeChunks(cappedChunks, language, previousVersion);
41289
+ if (this.config.ai == null && this.config.fernToken != null) {
41290
+ analysis = await this.analyzeViaFaiService(cleanedDiff, language, previousVersion);
41291
+ } else {
41292
+ const chunks = service.chunkDiff(cleanedDiff, MAX_AI_DIFF_BYTES);
41293
+ const cappedChunks = chunks.slice(0, MAX_CHUNKS);
41294
+ const skippedChunks = chunks.length - cappedChunks.length;
41295
+ if (chunks.length > 1) {
41296
+ this.logger.info(
41297
+ `AutoVersionStep: split diff into ${chunks.length} chunks` + (skippedChunks > 0 ? ` (capped at ${MAX_CHUNKS}, skipping ${skippedChunks})` : "")
41298
+ );
41299
+ }
41300
+ analysis = cappedChunks.length <= 1 ? await this.analyzeSingle(cleanedDiff, language, previousVersion) : await this.analyzeChunks(cappedChunks, language, previousVersion);
41301
+ }
41292
41302
  } catch (error) {
41293
41303
  this.logger.warn(`AutoVersionStep: FAI analysis failed (${String(error)}); falling back to PATCH bump.`);
41294
41304
  analysis = { versionBump: "PATCH", message: this.brandMessage("SDK regeneration") };
@@ -41311,6 +41321,106 @@ var AutoVersionStep = class extends BaseStep {
41311
41321
  analysis
41312
41322
  });
41313
41323
  }
41324
+ /**
41325
+ * Non-replay autoversion: diffs HEAD (committed SDK) vs the working tree
41326
+ * (freshly generated files). Does NOT commit — GithubStep will commit all
41327
+ * changes (including the rewritten placeholder) in a single generation commit.
41328
+ */
41329
+ async handleNonReplayFlow() {
41330
+ const language = this.config.language;
41331
+ const mappedMagicVersion = mapMagicVersionForLanguage(MAGIC_VERSION, language);
41332
+ const service = new AutoVersioningService({ logger: this.toTaskLogger() });
41333
+ const rawDiff = this.gitDiffHead();
41334
+ const previousVersion = await this.resolvePreviousVersionNonReplay({
41335
+ service,
41336
+ rawDiff,
41337
+ mappedMagicVersion,
41338
+ baseVersion: this.config.baseVersion
41339
+ });
41340
+ if (previousVersion == null) {
41341
+ return await this.handleFirstGeneration({ service, language, mappedMagicVersion, commit: false });
41342
+ }
41343
+ if (rawDiff.trim().length === 0) {
41344
+ this.logger.info(`AutoVersionStep: empty diff (non-replay); rewriting placeholder to ${previousVersion}.`);
41345
+ return await this.finalizeNoChangeNonReplay({
41346
+ service,
41347
+ language,
41348
+ mappedMagicVersion,
41349
+ previousVersion,
41350
+ reason: "no diff between generations"
41351
+ });
41352
+ }
41353
+ const cleanedDiff = service.cleanDiffForAI(rawDiff, mappedMagicVersion);
41354
+ const rawBytes = Buffer.byteLength(rawDiff, "utf-8");
41355
+ const cleanedBytes = Buffer.byteLength(cleanedDiff, "utf-8");
41356
+ this.logger.debug(
41357
+ `AutoVersionStep (non-replay): raw=${formatSizeKB(rawBytes)}KB (${countFilesInDiff(rawDiff)} files), cleaned=${formatSizeKB(cleanedBytes)}KB (${countFilesInDiff(cleanedDiff)} files)`
41358
+ );
41359
+ if (cleanedDiff.trim().length === 0) {
41360
+ this.logger.info(
41361
+ `AutoVersionStep: cleaned diff empty (non-replay); rewriting placeholder to ${previousVersion}.`
41362
+ );
41363
+ return await this.finalizeNoChangeNonReplay({
41364
+ service,
41365
+ language,
41366
+ mappedMagicVersion,
41367
+ previousVersion,
41368
+ reason: "no semantic changes"
41369
+ });
41370
+ }
41371
+ if (cleanedBytes > MAX_RAW_DIFF_BYTES) {
41372
+ this.logger.warn(
41373
+ `AutoVersionStep (non-replay): diff too large (${formatSizeKB(cleanedBytes)}KB). Falling back to PATCH.`
41374
+ );
41375
+ return await this.finalizeWithBumpNonReplay({
41376
+ service,
41377
+ language,
41378
+ mappedMagicVersion,
41379
+ previousVersion,
41380
+ analysis: { versionBump: "PATCH", message: this.brandMessage("SDK regeneration") }
41381
+ });
41382
+ }
41383
+ let analysis;
41384
+ try {
41385
+ if (this.config.ai == null && this.config.fernToken != null) {
41386
+ analysis = await this.analyzeViaFaiService(cleanedDiff, language, previousVersion);
41387
+ } else {
41388
+ const chunks = service.chunkDiff(cleanedDiff, MAX_AI_DIFF_BYTES);
41389
+ const cappedChunks = chunks.slice(0, MAX_CHUNKS);
41390
+ const skippedChunks = chunks.length - cappedChunks.length;
41391
+ if (chunks.length > 1) {
41392
+ this.logger.info(
41393
+ `AutoVersionStep (non-replay): split diff into ${chunks.length} chunks` + (skippedChunks > 0 ? ` (capped at ${MAX_CHUNKS}, skipping ${skippedChunks})` : "")
41394
+ );
41395
+ }
41396
+ analysis = cappedChunks.length <= 1 ? await this.analyzeSingle(cleanedDiff, language, previousVersion) : await this.analyzeChunks(cappedChunks, language, previousVersion);
41397
+ }
41398
+ } catch (error) {
41399
+ this.logger.warn(
41400
+ `AutoVersionStep (non-replay): FAI analysis failed (${String(error)}); falling back to PATCH bump.`
41401
+ );
41402
+ analysis = { versionBump: "PATCH", message: this.brandMessage("SDK regeneration") };
41403
+ }
41404
+ if (analysis == null) {
41405
+ this.logger.info(
41406
+ `AutoVersionStep (non-replay): FAI returned NO_CHANGE; rewriting placeholder to ${previousVersion}.`
41407
+ );
41408
+ return await this.finalizeNoChangeNonReplay({
41409
+ service,
41410
+ language,
41411
+ mappedMagicVersion,
41412
+ previousVersion,
41413
+ reason: "FAI returned NO_CHANGE"
41414
+ });
41415
+ }
41416
+ return await this.finalizeWithBumpNonReplay({
41417
+ service,
41418
+ language,
41419
+ mappedMagicVersion,
41420
+ previousVersion,
41421
+ analysis
41422
+ });
41423
+ }
41314
41424
  /**
41315
41425
  * Terminal path for runs that determine no semver bump is needed. The
41316
41426
  * current working tree still contains `0.0.0-fern-placeholder` from this
@@ -41368,6 +41478,98 @@ var AutoVersionStep = class extends BaseStep {
41368
41478
  commitSha
41369
41479
  };
41370
41480
  }
41481
+ /**
41482
+ * Non-replay variant of finalizeNoChange. Rewrites the placeholder to
41483
+ * `previousVersion` but does NOT commit — GithubStep handles the commit.
41484
+ */
41485
+ async finalizeNoChangeNonReplay(params) {
41486
+ const { service, language, mappedMagicVersion, previousVersion, reason } = params;
41487
+ if (!isValidSemver(previousVersion)) {
41488
+ const errorMessage = `AutoVersionStep: resolved previousVersion ${JSON.stringify(previousVersion)} is not a valid semver string. Refusing to rewrite placeholder to avoid shell injection.`;
41489
+ this.logger.error(errorMessage);
41490
+ return { executed: true, success: false, errorMessage };
41491
+ }
41492
+ await service.replaceMagicVersion(this.outputDir, mappedMagicVersion, previousVersion);
41493
+ if (language === "go") {
41494
+ await service.addGoMajorVersionSuffix(this.outputDir, previousVersion);
41495
+ }
41496
+ const commitMessage = this.brandMessage(`SDK regeneration (no semver change: ${reason})`);
41497
+ return {
41498
+ executed: true,
41499
+ success: true,
41500
+ version: previousVersion,
41501
+ previousVersion,
41502
+ versionBump: "NO_CHANGE",
41503
+ commitMessage
41504
+ };
41505
+ }
41506
+ /**
41507
+ * Non-replay variant of finalizeWithBump. Rewrites the placeholder to
41508
+ * the bumped version but does NOT commit — GithubStep handles the commit.
41509
+ */
41510
+ async finalizeWithBumpNonReplay(params) {
41511
+ const { service, language, mappedMagicVersion, previousVersion, analysis } = params;
41512
+ const newVersion = incrementVersion(previousVersion, analysis.versionBump);
41513
+ this.logger.info(
41514
+ `AutoVersionStep (non-replay): ${analysis.versionBump} bump: ${previousVersion} \u2192 ${newVersion}`
41515
+ );
41516
+ await service.replaceMagicVersion(this.outputDir, mappedMagicVersion, newVersion);
41517
+ if (language === "go") {
41518
+ await service.addGoMajorVersionSuffix(this.outputDir, newVersion);
41519
+ }
41520
+ if (analysis.changelogEntry && analysis.changelogEntry.trim().length > 0) {
41521
+ await this.prependChangelogEntry({ version: newVersion, entry: analysis.changelogEntry });
41522
+ }
41523
+ return {
41524
+ executed: true,
41525
+ success: true,
41526
+ version: newVersion,
41527
+ commitMessage: analysis.message,
41528
+ changelogEntry: analysis.changelogEntry,
41529
+ previousVersion,
41530
+ versionBump: analysis.versionBump,
41531
+ prDescription: analysis.prDescription,
41532
+ versionBumpReason: analysis.versionBumpReason
41533
+ };
41534
+ }
41535
+ /**
41536
+ * Resolves the previous version for non-replay mode. Reads HEAD:.fern/metadata.json
41537
+ * (not HEAD~1, since no generation commit exists yet) as the primary fallback.
41538
+ */
41539
+ async resolvePreviousVersionNonReplay(params) {
41540
+ const { service, rawDiff, mappedMagicVersion, baseVersion } = params;
41541
+ if (baseVersion != null && isValidSemver(baseVersion)) {
41542
+ this.logger.debug(
41543
+ `AutoVersionStep (non-replay): previous version from pipeline baseVersion: ${baseVersion}`
41544
+ );
41545
+ return this.normalizeVersionPrefix(baseVersion, mappedMagicVersion);
41546
+ }
41547
+ try {
41548
+ const extracted = service.extractPreviousVersion(rawDiff, mappedMagicVersion);
41549
+ if (extracted != null) {
41550
+ this.logger.debug(`AutoVersionStep (non-replay): previous version from diff: ${extracted}`);
41551
+ return extracted;
41552
+ }
41553
+ } catch (error) {
41554
+ if (!(error instanceof AutoVersioningException) || !error.magicVersionAbsent) {
41555
+ throw error;
41556
+ }
41557
+ this.logger.info("AutoVersionStep (non-replay): magic version not in diff; trying metadata + git tags.");
41558
+ }
41559
+ const metadataVersion = this.readVersionFromMetadataAtHead();
41560
+ if (metadataVersion != null) {
41561
+ return this.normalizeVersionPrefix(metadataVersion, mappedMagicVersion);
41562
+ }
41563
+ try {
41564
+ const tagVersion = await service.getLatestVersionFromGitTags(this.outputDir);
41565
+ if (tagVersion != null) {
41566
+ return this.normalizeVersionPrefix(tagVersion, mappedMagicVersion);
41567
+ }
41568
+ } catch (error) {
41569
+ this.logger.debug(`AutoVersionStep (non-replay): git-tags fallback failed (${String(error)}); ignoring.`);
41570
+ }
41571
+ return null;
41572
+ }
41371
41573
  async resolvePreviousVersion(params) {
41372
41574
  const { service, rawDiff, mappedMagicVersion, baseVersion } = params;
41373
41575
  if (baseVersion != null && isValidSemver(baseVersion)) {
@@ -41417,6 +41619,42 @@ var AutoVersionStep = class extends BaseStep {
41417
41619
  const stripped = version2.startsWith("v") ? version2.slice(1) : version2;
41418
41620
  return mappedMagicVersion.startsWith("v") ? `v${stripped}` : stripped;
41419
41621
  }
41622
+ /**
41623
+ * Reads the sdkVersion from the committed (HEAD) .fern/metadata.json.
41624
+ * Used by the non-replay path where no generation commit exists yet,
41625
+ * so HEAD is the unmodified SDK repository.
41626
+ */
41627
+ readVersionFromMetadataAtHead() {
41628
+ try {
41629
+ const output = (0, import_child_process4.execFileSync)("git", ["show", "HEAD:.fern/metadata.json"], {
41630
+ cwd: this.outputDir,
41631
+ encoding: "utf-8",
41632
+ stdio: "pipe"
41633
+ });
41634
+ const parsed = JSON.parse(output);
41635
+ return parsed.sdkVersion ?? void 0;
41636
+ } catch (error) {
41637
+ this.logger.debug(`AutoVersionStep: failed to read HEAD:.fern/metadata.json (${String(error)})`);
41638
+ return void 0;
41639
+ }
41640
+ }
41641
+ /**
41642
+ * Computes a diff between HEAD (committed SDK) and the working tree (new
41643
+ * generation). Marks untracked files as intent-to-add so they appear in
41644
+ * the diff, matching LocalTaskHandler.generateDiffFile() behavior.
41645
+ */
41646
+ gitDiffHead() {
41647
+ (0, import_child_process4.execFileSync)("git", ["add", "-N", "."], {
41648
+ cwd: this.outputDir,
41649
+ stdio: "pipe"
41650
+ });
41651
+ return (0, import_child_process4.execFileSync)("git", ["diff", "HEAD", "--", ".", ":(exclude).fern/metadata.json"], {
41652
+ cwd: this.outputDir,
41653
+ encoding: "utf-8",
41654
+ stdio: "pipe",
41655
+ maxBuffer: 256 * 1024 * 1024
41656
+ });
41657
+ }
41420
41658
  gitDiff(from, to) {
41421
41659
  return (0, import_child_process4.execFileSync)("git", ["diff", from, to, "--", ".", ":(exclude).fern/metadata.json"], {
41422
41660
  cwd: this.outputDir,
@@ -41568,6 +41806,47 @@ ${newBlock}${remainder}`;
41568
41806
  };
41569
41807
  }
41570
41808
  }
41809
+ /**
41810
+ * Calls the hosted FAI service (`/sdks/analyze-commit-diff`) with the fern token.
41811
+ * Used when no BAML `ai` config is supplied (remote generation via fiddle). FAI
41812
+ * handles chunking, parallelism, and retries server-side. Returns null on
41813
+ * NO_CHANGE; throws on transport/HTTP errors so the caller's PATCH fallback applies.
41814
+ */
41815
+ async analyzeViaFaiService(cleanedDiff, language, previousVersion) {
41816
+ const baseUrl = this.config.faiBaseUrl ?? "https://fai.buildwithfern.com";
41817
+ const response = await fetch(`${baseUrl}/sdks/analyze-commit-diff`, {
41818
+ method: "POST",
41819
+ headers: {
41820
+ Authorization: `Bearer ${this.config.fernToken}`,
41821
+ "Content-Type": "application/json"
41822
+ },
41823
+ body: JSON.stringify({
41824
+ diff: cleanedDiff,
41825
+ language,
41826
+ previous_version: previousVersion,
41827
+ prior_changelog: this.config.priorChangelog ?? void 0,
41828
+ spec_commit_message: this.config.specCommitMessage ?? void 0
41829
+ })
41830
+ });
41831
+ if (!response.ok) {
41832
+ const body = await response.text().catch(() => "");
41833
+ throw new Error(`FAI analyze-commit-diff failed with status ${response.status}: ${body.slice(0, 500)}`);
41834
+ }
41835
+ const parsed = await response.json();
41836
+ if (!isFaiAnalyzeResponse(parsed)) {
41837
+ throw new Error("FAI analyze-commit-diff returned an unexpected response shape");
41838
+ }
41839
+ if (parsed.version_bump === "NO_CHANGE") {
41840
+ return null;
41841
+ }
41842
+ return {
41843
+ versionBump: parsed.version_bump,
41844
+ message: this.brandMessage(parsed.message),
41845
+ changelogEntry: nonEmpty(parsed.changelog_entry),
41846
+ prDescription: nonEmpty(parsed.pr_description),
41847
+ versionBumpReason: nonEmpty(parsed.version_bump_reason)
41848
+ };
41849
+ }
41571
41850
  /**
41572
41851
  * Dynamically imports @fern-api/cli-ai only when autoversion actually needs to
41573
41852
  * call FAI. The package is ESM-only with extensionless internal imports, so
@@ -41609,6 +41888,20 @@ ${newBlock}${remainder}`;
41609
41888
  return adapter;
41610
41889
  }
41611
41890
  };
41891
+ var FAI_VERSION_BUMPS = ["MAJOR", "MINOR", "PATCH", "NO_CHANGE"];
41892
+ function isFaiAnalyzeResponse(value) {
41893
+ if (typeof value !== "object" || value == null) {
41894
+ return false;
41895
+ }
41896
+ const candidate = value;
41897
+ return typeof candidate.message === "string" && typeof candidate.version_bump === "string" && FAI_VERSION_BUMPS.includes(candidate.version_bump) && isStringOrAbsent(candidate.changelog_entry) && isStringOrAbsent(candidate.pr_description) && isStringOrAbsent(candidate.version_bump_reason);
41898
+ }
41899
+ function isStringOrAbsent(value) {
41900
+ return value == null || typeof value === "string";
41901
+ }
41902
+ function nonEmpty(value) {
41903
+ return value != null && value.trim().length > 0 ? value : void 0;
41904
+ }
41612
41905
 
41613
41906
  // src/pipeline/steps/GenerationCommitStep.ts
41614
41907
  var import_child_process5 = require("child_process");
@@ -42836,10 +43129,6 @@ var PostGenerationPipeline = class {
42836
43129
  );
42837
43130
  replayEnabled = false;
42838
43131
  }
42839
- if (autoVersionEnabled && !replayEnabled) {
42840
- this.logger.warn("AutoVersion requires Replay to be enabled. Disabling AutoVersion for this run.");
42841
- autoVersionEnabled = false;
42842
- }
42843
43132
  if (replayEnabled && config.replay != null) {
42844
43133
  this.steps.push(
42845
43134
  new GenerationCommitStep(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fern-api/generator-cli",
3
- "version": "0.9.35",
3
+ "version": "0.9.37",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fern-api/fern.git",