@fern-api/generator-cli 0.9.52 → 0.9.54

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 +143 -13
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -41162,6 +41162,7 @@ var AutoVersioningService = class _AutoVersioningService {
41162
41162
  }
41163
41163
  }
41164
41164
  let processedContent = this.removeGoModulePathSuffixPairs(contentLines);
41165
+ processedContent = this.removeGoExplicitFieldBitConstantPairs(processedContent);
41165
41166
  processedContent = this.removeVersionChangePairs(processedContent, mappedMagicVersion);
41166
41167
  processedContent = this.removeRemainingMagicVersionLines(processedContent, mappedMagicVersion);
41167
41168
  let hasChanges = false;
@@ -41366,6 +41367,90 @@ var AutoVersioningService = class _AutoVersioningService {
41366
41367
  hasSuffix: suffixPattern.test(content)
41367
41368
  };
41368
41369
  }
41370
+ /**
41371
+ * Removes Go `explicitFields` bit-constant reindexing churn from a diff section.
41372
+ *
41373
+ * The Go SDK tracks which optional fields were explicitly set via a per-struct `big.Int`
41374
+ * bitmask. For each struct property it generates a private constant whose value is a single
41375
+ * bit at the property's positional index, e.g.
41376
+ *
41377
+ * var (
41378
+ * entityFieldGeoShape = big.NewInt(1 << 3)
41379
+ * entityFieldGeoDetails = big.NewInt(1 << 4)
41380
+ * )
41381
+ *
41382
+ * Inserting a new field earlier in the struct shifts every following constant's bit index by
41383
+ * one, producing a deletion/addition pair per field that differs only by the integer:
41384
+ *
41385
+ * - entityFieldGeoShape = big.NewInt(1 << 3)
41386
+ * + entityFieldGeoShape = big.NewInt(1 << 4)
41387
+ *
41388
+ * This is a purely mechanical artifact of the field addition: the bit is assigned and read
41389
+ * within the same generated version (the wire format is name-based JSON, not bit position),
41390
+ * so shifting it is not observable to consumers. Left in the diff, the AI analyzer reads the
41391
+ * shifted bit positions as a serialization-compatibility break and misclassifies an additive
41392
+ * change as a spurious MAJOR bump. The genuinely new field, its own new bit constant, and its
41393
+ * setter are all pure additions with no matching deletion, so they survive this pass and the
41394
+ * AI still sees (and correctly classifies) the field addition as MINOR.
41395
+ */
41396
+ removeGoExplicitFieldBitConstantPairs(lines) {
41397
+ const additions = [];
41398
+ for (let index = 0; index < lines.length; index++) {
41399
+ const line = lines[index];
41400
+ if (line == null || !this.isAdditionLine(line)) {
41401
+ continue;
41402
+ }
41403
+ const info = this.analyzeGoBitConstantLine(line);
41404
+ if (info != null) {
41405
+ additions.push({ index, normalized: info.normalized });
41406
+ }
41407
+ }
41408
+ const removed = /* @__PURE__ */ new Set();
41409
+ const usedAdditions = /* @__PURE__ */ new Set();
41410
+ for (let index = 0; index < lines.length; index++) {
41411
+ const line = lines[index];
41412
+ if (line == null || !this.isDeletionLine(line)) {
41413
+ continue;
41414
+ }
41415
+ const info = this.analyzeGoBitConstantLine(line);
41416
+ if (info == null) {
41417
+ continue;
41418
+ }
41419
+ const match2 = additions.find(
41420
+ (addition) => !usedAdditions.has(addition.index) && addition.normalized === info.normalized
41421
+ );
41422
+ if (match2 != null) {
41423
+ usedAdditions.add(match2.index);
41424
+ removed.add(index);
41425
+ removed.add(match2.index);
41426
+ }
41427
+ }
41428
+ if (removed.size === 0) {
41429
+ return lines;
41430
+ }
41431
+ return lines.filter((_2, index) => !removed.has(index));
41432
+ }
41433
+ /**
41434
+ * If a diff line (with its leading +/- stripped) is a generated Go `explicitFields`
41435
+ * bit constant — `<name> = big.NewInt(1 << N)` or the >=63 form
41436
+ * `<name> = big.NewInt(0).Lsh(big.NewInt(1), N)` — returns a normalized key with the bit
41437
+ * index removed so a deletion and its reindexed addition compare equal. Requiring the exact
41438
+ * `big.NewInt` bit-shift shape keeps this from matching ordinary integer assignments.
41439
+ */
41440
+ analyzeGoBitConstantLine(line) {
41441
+ const content = line.substring(1);
41442
+ const shiftMatch = content.match(/^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=\s*big\.NewInt\(1\s*<<\s*\d+\)\s*$/);
41443
+ if (shiftMatch?.[2] != null) {
41444
+ return { normalized: `${shiftMatch[2]}=big.NewInt(1<<#)` };
41445
+ }
41446
+ const lshMatch = content.match(
41447
+ /^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=\s*big\.NewInt\(0\)\.Lsh\(big\.NewInt\(1\),\s*\d+\)\s*$/
41448
+ );
41449
+ if (lshMatch?.[2] != null) {
41450
+ return { normalized: `${lshMatch[2]}=big.NewInt(0).Lsh(big.NewInt(1),#)` };
41451
+ }
41452
+ return void 0;
41453
+ }
41369
41454
  /**
41370
41455
  * Gets the latest semantic version from git tags in the given repository.
41371
41456
  * Used as a fallback when the magic version is not embedded in any generated file
@@ -41758,11 +41843,20 @@ var SEMVER_PATTERN = /^(v)?(\d+)\.(\d+)\.(\d+)(?:-([\w.-]+))?(?:\+([\w.-]+))?$/;
41758
41843
  function isValidSemver(version2) {
41759
41844
  return SEMVER_PATTERN.test(version2);
41760
41845
  }
41846
+ function isPlaceholderVersion(version2) {
41847
+ const withoutPrefix = version2.startsWith("v") ? version2.slice(1) : version2;
41848
+ return withoutPrefix === MAGIC_VERSION || withoutPrefix.startsWith(`${MAGIC_VERSION}.`) || withoutPrefix === MAGIC_VERSION_PYTHON || withoutPrefix.startsWith(`${MAGIC_VERSION_PYTHON}.`);
41849
+ }
41761
41850
  function incrementVersion(currentVersion, versionBump) {
41762
41851
  const matcher = currentVersion.match(SEMVER_PATTERN);
41763
41852
  if (!matcher) {
41764
41853
  throw new AutoVersioningException("Invalid semantic version format: " + currentVersion);
41765
41854
  }
41855
+ if (isPlaceholderVersion(currentVersion)) {
41856
+ throw new AutoVersioningException(
41857
+ "Refusing to increment the magic placeholder version: " + currentVersion + ". A real previous version must be resolved first."
41858
+ );
41859
+ }
41766
41860
  const prefix = matcher[1] ?? "";
41767
41861
  const versionWithoutPrefix = currentVersion.slice(prefix.length);
41768
41862
  const preRelease = matcher[5];
@@ -41833,7 +41927,8 @@ var AutoVersionStep = class extends BaseStep {
41833
41927
  }
41834
41928
  async handleFirstGeneration(params) {
41835
41929
  const { service, language, mappedMagicVersion, commit } = params;
41836
- const initialVersion = this.config.baseVersion ?? (mappedMagicVersion.startsWith("v") ? "v0.0.1" : "0.0.1");
41930
+ const configuredBaseVersion = this.usableVersion(this.config.baseVersion, "baseVersion");
41931
+ const initialVersion = configuredBaseVersion ?? (mappedMagicVersion.startsWith("v") ? "v0.0.1" : "0.0.1");
41837
41932
  if (!isValidSemver(initialVersion)) {
41838
41933
  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.`;
41839
41934
  this.logger.error(errorMessage);
@@ -42167,14 +42262,18 @@ var AutoVersionStep = class extends BaseStep {
42167
42262
  */
42168
42263
  async resolvePreviousVersionNonReplay(params) {
42169
42264
  const { service, rawDiff, mappedMagicVersion, baseVersion } = params;
42170
- if (baseVersion != null && isValidSemver(baseVersion)) {
42265
+ const usableBaseVersion = this.usableVersion(baseVersion, "baseVersion");
42266
+ if (usableBaseVersion != null && isValidSemver(usableBaseVersion)) {
42171
42267
  this.logger.debug(
42172
- `AutoVersionStep (non-replay): previous version from pipeline baseVersion: ${baseVersion}`
42268
+ `AutoVersionStep (non-replay): previous version from pipeline baseVersion: ${usableBaseVersion}`
42173
42269
  );
42174
- return this.normalizeVersionPrefix(baseVersion, mappedMagicVersion);
42270
+ return this.normalizeVersionPrefix(usableBaseVersion, mappedMagicVersion);
42175
42271
  }
42176
42272
  try {
42177
- const extracted = service.extractPreviousVersion(rawDiff, mappedMagicVersion);
42273
+ const extracted = this.usableVersion(
42274
+ service.extractPreviousVersion(rawDiff, mappedMagicVersion) ?? void 0,
42275
+ "diff"
42276
+ );
42178
42277
  if (extracted != null) {
42179
42278
  this.logger.debug(`AutoVersionStep (non-replay): previous version from diff: ${extracted}`);
42180
42279
  return extracted;
@@ -42185,12 +42284,15 @@ var AutoVersionStep = class extends BaseStep {
42185
42284
  }
42186
42285
  this.logger.info("AutoVersionStep (non-replay): magic version not in diff; trying metadata + git tags.");
42187
42286
  }
42188
- const metadataVersion = this.readVersionFromMetadataAtHead();
42287
+ const metadataVersion = this.usableVersion(this.readVersionFromMetadataAtHead(), "metadata.json");
42189
42288
  if (metadataVersion != null) {
42190
42289
  return this.normalizeVersionPrefix(metadataVersion, mappedMagicVersion);
42191
42290
  }
42192
42291
  try {
42193
- const tagVersion = await service.getLatestVersionFromGitTags(this.outputDir);
42292
+ const tagVersion = this.usableVersion(
42293
+ await service.getLatestVersionFromGitTags(this.outputDir) ?? void 0,
42294
+ "git tags"
42295
+ );
42194
42296
  if (tagVersion != null) {
42195
42297
  return this.normalizeVersionPrefix(tagVersion, mappedMagicVersion);
42196
42298
  }
@@ -42201,12 +42303,16 @@ var AutoVersionStep = class extends BaseStep {
42201
42303
  }
42202
42304
  async resolvePreviousVersion(params) {
42203
42305
  const { service, rawDiff, mappedMagicVersion, baseVersion } = params;
42204
- if (baseVersion != null && isValidSemver(baseVersion)) {
42205
- this.logger.debug(`AutoVersionStep: previous version from pipeline baseVersion: ${baseVersion}`);
42206
- return this.normalizeVersionPrefix(baseVersion, mappedMagicVersion);
42306
+ const usableBaseVersion = this.usableVersion(baseVersion, "baseVersion");
42307
+ if (usableBaseVersion != null && isValidSemver(usableBaseVersion)) {
42308
+ this.logger.debug(`AutoVersionStep: previous version from pipeline baseVersion: ${usableBaseVersion}`);
42309
+ return this.normalizeVersionPrefix(usableBaseVersion, mappedMagicVersion);
42207
42310
  }
42208
42311
  try {
42209
- const extracted = service.extractPreviousVersion(rawDiff, mappedMagicVersion);
42312
+ const extracted = this.usableVersion(
42313
+ service.extractPreviousVersion(rawDiff, mappedMagicVersion) ?? void 0,
42314
+ "diff"
42315
+ );
42210
42316
  if (extracted != null) {
42211
42317
  this.logger.debug(`AutoVersionStep: previous version from diff: ${extracted}`);
42212
42318
  return extracted;
@@ -42217,12 +42323,15 @@ var AutoVersionStep = class extends BaseStep {
42217
42323
  }
42218
42324
  this.logger.info("AutoVersionStep: magic version not found in diff; falling back to metadata + git tags.");
42219
42325
  }
42220
- const metadataVersion = await this.readVersionFromMetadata();
42326
+ const metadataVersion = this.usableVersion(await this.readVersionFromMetadata(), "metadata.json");
42221
42327
  if (metadataVersion != null) {
42222
42328
  return this.normalizeVersionPrefix(metadataVersion, mappedMagicVersion);
42223
42329
  }
42224
42330
  try {
42225
- const tagVersion = await service.getLatestVersionFromGitTags(this.outputDir);
42331
+ const tagVersion = this.usableVersion(
42332
+ await service.getLatestVersionFromGitTags(this.outputDir) ?? void 0,
42333
+ "git tags"
42334
+ );
42226
42335
  if (tagVersion != null) {
42227
42336
  return this.normalizeVersionPrefix(tagVersion, mappedMagicVersion);
42228
42337
  }
@@ -42244,6 +42353,27 @@ var AutoVersionStep = class extends BaseStep {
42244
42353
  return void 0;
42245
42354
  }
42246
42355
  }
42356
+ /**
42357
+ * Drops candidate previous versions that are the magic placeholder. SDK repos
42358
+ * that derive their published version at release time keep the placeholder
42359
+ * committed, so every resolution source (baseVersion, diff extraction,
42360
+ * metadata.json, git tags) can hand back the sentinel; incrementing it would
42361
+ * emit a mutated placeholder such as `0.0.0-fern-placeholder.0` instead of a
42362
+ * real version. Returning undefined lets the caller fall through to the next
42363
+ * source, and ultimately to the first-generation path.
42364
+ */
42365
+ usableVersion(version2, source) {
42366
+ if (version2 == null) {
42367
+ return void 0;
42368
+ }
42369
+ if (isPlaceholderVersion(version2)) {
42370
+ this.logger.info(
42371
+ `AutoVersionStep: ignoring placeholder version ${version2} from ${source}; trying the next previous-version source.`
42372
+ );
42373
+ return void 0;
42374
+ }
42375
+ return version2;
42376
+ }
42247
42377
  normalizeVersionPrefix(version2, mappedMagicVersion) {
42248
42378
  const stripped = version2.startsWith("v") ? version2.slice(1) : version2;
42249
42379
  return mappedMagicVersion.startsWith("v") ? `v${stripped}` : stripped;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fern-api/generator-cli",
3
- "version": "0.9.52",
3
+ "version": "0.9.54",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fern-api/fern.git",