@fern-api/generator-cli 0.9.36 → 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 +251 -29
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -41181,32 +41181,30 @@ var AutoVersionStep = class extends BaseStep {
41181
41181
  name = "autoVersion";
41182
41182
  async execute(context) {
41183
41183
  const prepared = context.previousStepResults.generationCommit?.preparedReplay;
41184
- if (!prepared) {
41185
- this.logger.info(
41186
- "AutoVersionStep: no replay preparation available (replay uninitialized or prepare failed); skipping."
41187
- );
41188
- return { executed: true, success: true };
41189
- }
41190
- if (prepared.flow === "skip-application") {
41191
- this.logger.info("AutoVersionStep: replay flow is skip-application; skipping autoversion.");
41192
- return { executed: true, success: true };
41193
- }
41194
- const language = this.config.language;
41195
- const mappedMagicVersion = mapMagicVersionForLanguage(MAGIC_VERSION, language);
41196
- const service = new AutoVersioningService({ logger: this.toTaskLogger() });
41197
- if (prepared.previousGenerationSha == null) {
41198
- 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
+ });
41199
41202
  }
41200
- return await this.handleNormalFlow({
41201
- prepared,
41202
- service,
41203
- language,
41204
- mappedMagicVersion,
41205
- previousGenerationSha: prepared.previousGenerationSha
41206
- });
41203
+ this.logger.info("AutoVersionStep: running in non-replay mode (git diff HEAD).");
41204
+ return await this.handleNonReplayFlow();
41207
41205
  }
41208
41206
  async handleFirstGeneration(params) {
41209
- const { service, language, mappedMagicVersion } = params;
41207
+ const { service, language, mappedMagicVersion, commit } = params;
41210
41208
  const initialVersion = this.config.baseVersion ?? (mappedMagicVersion.startsWith("v") ? "v0.0.1" : "0.0.1");
41211
41209
  if (!isValidSemver(initialVersion)) {
41212
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.`;
@@ -41223,7 +41221,7 @@ var AutoVersionStep = class extends BaseStep {
41223
41221
  await service.addGoMajorVersionSuffix(this.outputDir, initialVersion);
41224
41222
  }
41225
41223
  const commitMessage = this.brandMessage("Initial SDK generation");
41226
- const commitSha = this.commitAutoversion(commitMessage);
41224
+ const commitSha = commit ? this.commitAutoversion(commitMessage) : void 0;
41227
41225
  return {
41228
41226
  executed: true,
41229
41227
  success: true,
@@ -41242,7 +41240,7 @@ var AutoVersionStep = class extends BaseStep {
41242
41240
  baseVersion: this.config.baseVersion
41243
41241
  });
41244
41242
  if (previousVersion == null) {
41245
- return await this.handleFirstGeneration({ service, language, mappedMagicVersion });
41243
+ return await this.handleFirstGeneration({ service, language, mappedMagicVersion, commit: true });
41246
41244
  }
41247
41245
  if (rawDiff.trim().length === 0) {
41248
41246
  this.logger.info(
@@ -41323,6 +41321,106 @@ var AutoVersionStep = class extends BaseStep {
41323
41321
  analysis
41324
41322
  });
41325
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
+ }
41326
41424
  /**
41327
41425
  * Terminal path for runs that determine no semver bump is needed. The
41328
41426
  * current working tree still contains `0.0.0-fern-placeholder` from this
@@ -41380,6 +41478,98 @@ var AutoVersionStep = class extends BaseStep {
41380
41478
  commitSha
41381
41479
  };
41382
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
+ }
41383
41573
  async resolvePreviousVersion(params) {
41384
41574
  const { service, rawDiff, mappedMagicVersion, baseVersion } = params;
41385
41575
  if (baseVersion != null && isValidSemver(baseVersion)) {
@@ -41429,6 +41619,42 @@ var AutoVersionStep = class extends BaseStep {
41429
41619
  const stripped = version2.startsWith("v") ? version2.slice(1) : version2;
41430
41620
  return mappedMagicVersion.startsWith("v") ? `v${stripped}` : stripped;
41431
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
+ }
41432
41658
  gitDiff(from, to) {
41433
41659
  return (0, import_child_process4.execFileSync)("git", ["diff", from, to, "--", ".", ":(exclude).fern/metadata.json"], {
41434
41660
  cwd: this.outputDir,
@@ -42903,10 +43129,6 @@ var PostGenerationPipeline = class {
42903
43129
  );
42904
43130
  replayEnabled = false;
42905
43131
  }
42906
- if (autoVersionEnabled && !replayEnabled) {
42907
- this.logger.warn("AutoVersion requires Replay to be enabled. Disabling AutoVersion for this run.");
42908
- autoVersionEnabled = false;
42909
- }
42910
43132
  if (replayEnabled && config.replay != null) {
42911
43133
  this.steps.push(
42912
43134
  new GenerationCommitStep(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fern-api/generator-cli",
3
- "version": "0.9.36",
3
+ "version": "0.9.37",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fern-api/fern.git",