@fern-api/generator-cli 0.9.11 → 0.9.14
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.
- package/dist/api.d.ts +101 -31
- package/dist/api.js +1694 -276
- package/dist/cli.js +17473 -11396
- package/package.json +4 -2
package/dist/api.js
CHANGED
|
@@ -793,12 +793,12 @@ var require_src2 = __commonJS({
|
|
|
793
793
|
function check(path3, isFile, isDirectory) {
|
|
794
794
|
log(`checking %s`, path3);
|
|
795
795
|
try {
|
|
796
|
-
const
|
|
797
|
-
if (
|
|
796
|
+
const stat2 = fs_1.statSync(path3);
|
|
797
|
+
if (stat2.isFile() && isFile) {
|
|
798
798
|
log(`[OK] path represents a file`);
|
|
799
799
|
return true;
|
|
800
800
|
}
|
|
801
|
-
if (
|
|
801
|
+
if (stat2.isDirectory() && isDirectory) {
|
|
802
802
|
log(`[OK] path represents a directory`);
|
|
803
803
|
return true;
|
|
804
804
|
}
|
|
@@ -10060,46 +10060,1662 @@ var consolePipelineLogger = {
|
|
|
10060
10060
|
};
|
|
10061
10061
|
|
|
10062
10062
|
// src/pipeline/PostGenerationPipeline.ts
|
|
10063
|
-
var
|
|
10063
|
+
var import_child_process5 = require("child_process");
|
|
10064
10064
|
|
|
10065
10065
|
// src/pipeline/github/constants.ts
|
|
10066
10066
|
var FERN_BOT_NAME = "fern-api";
|
|
10067
10067
|
var FERN_BOT_EMAIL = "115122769+fern-api[bot]@users.noreply.github.com";
|
|
10068
10068
|
var FERN_BOT_LOGIN = "fern-api[bot]";
|
|
10069
10069
|
|
|
10070
|
-
// src/pipeline/steps/
|
|
10071
|
-
var
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10070
|
+
// src/pipeline/steps/AutoVersionStep.ts
|
|
10071
|
+
var import_child_process3 = require("child_process");
|
|
10072
|
+
var import_fs3 = require("fs");
|
|
10073
|
+
var import_promises3 = require("fs/promises");
|
|
10074
|
+
var import_path4 = require("path");
|
|
10075
|
+
|
|
10076
|
+
// src/autoversion/AutoVersioningService.ts
|
|
10077
|
+
var import_logging_execa = require("@fern-api/logging-execa");
|
|
10078
|
+
var import_fs2 = require("fs");
|
|
10079
|
+
var import_promises2 = require("fs/promises");
|
|
10080
|
+
var import_path3 = require("path");
|
|
10081
|
+
var import_semver = __toESM(require("semver"));
|
|
10082
|
+
var AutoVersioningException = class extends Error {
|
|
10083
|
+
/**
|
|
10084
|
+
* True when the magic version was not found at all in the diff,
|
|
10085
|
+
* indicating a generator that doesn't embed versions in source files
|
|
10086
|
+
* (e.g., Swift uses git tags via SPM). Only this case should fall back
|
|
10087
|
+
* to reading git tags.
|
|
10088
|
+
*/
|
|
10089
|
+
magicVersionAbsent;
|
|
10090
|
+
constructor(message, options) {
|
|
10091
|
+
super(message);
|
|
10092
|
+
this.name = "AutoVersioningException";
|
|
10093
|
+
this.magicVersionAbsent = options?.magicVersionAbsent ?? false;
|
|
10094
|
+
if (options?.cause) {
|
|
10095
|
+
this.cause = options.cause;
|
|
10096
|
+
}
|
|
10075
10097
|
}
|
|
10076
|
-
outputDir;
|
|
10077
|
-
logger;
|
|
10078
10098
|
};
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10099
|
+
function countFilesInDiff(diffContent) {
|
|
10100
|
+
const marker = "diff --git ";
|
|
10101
|
+
let count = 0;
|
|
10102
|
+
let pos = 0;
|
|
10103
|
+
while (true) {
|
|
10104
|
+
const idx = diffContent.indexOf(marker, pos);
|
|
10105
|
+
if (idx === -1) {
|
|
10106
|
+
break;
|
|
10107
|
+
}
|
|
10108
|
+
if (idx === 0 || diffContent[idx - 1] === "\n") {
|
|
10109
|
+
count++;
|
|
10110
|
+
}
|
|
10111
|
+
pos = idx + marker.length;
|
|
10112
|
+
}
|
|
10113
|
+
return count;
|
|
10114
|
+
}
|
|
10115
|
+
function formatSizeKB(charLength) {
|
|
10116
|
+
return (charLength / 1024).toFixed(1);
|
|
10117
|
+
}
|
|
10118
|
+
var EXCLUDED_FILE_PATTERNS = [
|
|
10119
|
+
// Documentation / generated reference
|
|
10120
|
+
/(?:^|\/)reference\.md$/i,
|
|
10121
|
+
/(?:^|\/)changelog(?:\.[^/]*)?$/i,
|
|
10122
|
+
/(?:^|\/)readme(?:\.[^/]*)?$/i,
|
|
10123
|
+
/(?:^|\/)license(?:\.[^/]*)?$/i,
|
|
10124
|
+
// Lock files (dependency resolution, zero semantic value)
|
|
10125
|
+
/(?:^|\/)pnpm-lock\.yaml$/i,
|
|
10126
|
+
/(?:^|\/)yarn\.lock$/i,
|
|
10127
|
+
/(?:^|\/)package-lock\.json$/i,
|
|
10128
|
+
/(?:^|\/)poetry\.lock$/i,
|
|
10129
|
+
/(?:^|\/)gemfile\.lock$/i,
|
|
10130
|
+
/(?:^|\/)go\.sum$/i,
|
|
10131
|
+
/(?:^|\/)cargo\.lock$/i,
|
|
10132
|
+
/(?:^|\/)composer\.lock$/i,
|
|
10133
|
+
/\.lock$/i,
|
|
10134
|
+
// catch-all for any other lockfiles
|
|
10135
|
+
// Test files (by naming convention only — directory patterns like tests/ and
|
|
10136
|
+
// test/ are intentionally omitted because a customer's API domain could use
|
|
10137
|
+
// those names, e.g. a QA platform SDK with a "tests" resource)
|
|
10138
|
+
/\.test\.ts$/i,
|
|
10139
|
+
/\.test\.js$/i,
|
|
10140
|
+
/\.test\.py$/i,
|
|
10141
|
+
/\.spec\.ts$/i,
|
|
10142
|
+
/\.spec\.js$/i,
|
|
10143
|
+
/_test\.go$/i,
|
|
10144
|
+
/_test\.py$/i,
|
|
10145
|
+
/Test\.java$/,
|
|
10146
|
+
/(?:^|\/)__tests__\//i,
|
|
10147
|
+
// __tests__/ is unambiguously a test directory
|
|
10148
|
+
/(?:^|\/)wiremock\//i,
|
|
10149
|
+
// Fern-generated WireMock test fixtures
|
|
10150
|
+
// Snapshot files
|
|
10151
|
+
/(?:^|\/)__snapshots__\//i,
|
|
10152
|
+
/\.snap$/i,
|
|
10153
|
+
// CI / editor config (no API surface relevance)
|
|
10154
|
+
/(?:^|\/)\.github\//i,
|
|
10155
|
+
/(?:^|\/)\.circleci\//i,
|
|
10156
|
+
/(?:^|\/)\.editorconfig$/i,
|
|
10157
|
+
/(?:^|\/)\.prettierrc/i,
|
|
10158
|
+
/(?:^|\/)biome\.json$/i,
|
|
10159
|
+
// Linting / static analysis config
|
|
10160
|
+
/(?:^|\/)\.eslintrc/i,
|
|
10161
|
+
/(?:^|\/)eslint\.config\./i,
|
|
10162
|
+
/(?:^|\/)\.rubocop/i,
|
|
10163
|
+
// Ruby linter
|
|
10164
|
+
/(?:^|\/)phpstan\.neon$/i,
|
|
10165
|
+
// PHP static analysis
|
|
10166
|
+
/(?:^|\/)phpunit\.xml$/i,
|
|
10167
|
+
// PHP test runner config
|
|
10168
|
+
/(?:^|\/)\.pylintrc$/i,
|
|
10169
|
+
// Python linter
|
|
10170
|
+
/(?:^|\/)pylintrc$/i,
|
|
10171
|
+
/(?:^|\/)\.flake8$/i,
|
|
10172
|
+
// Python linter
|
|
10173
|
+
/(?:^|\/)\.mypy\.ini$/i,
|
|
10174
|
+
// Python type checker
|
|
10175
|
+
/(?:^|\/)mypy\.ini$/i,
|
|
10176
|
+
/(?:^|\/)\.swiftlint\.yml$/i,
|
|
10177
|
+
// Swift linter
|
|
10178
|
+
/(?:^|\/)\.scalafmt\.conf$/i,
|
|
10179
|
+
// Scala formatter
|
|
10180
|
+
/(?:^|\/)rustfmt\.toml$/i,
|
|
10181
|
+
// Rust formatter
|
|
10182
|
+
/(?:^|\/)\.stylelintrc/i,
|
|
10183
|
+
// CSS linter
|
|
10184
|
+
// Build / devtool config (no API surface)
|
|
10185
|
+
/(?:^|\/)tsconfig[^/]*\.json$/i,
|
|
10186
|
+
// TypeScript compiler config
|
|
10187
|
+
/(?:^|\/)vitest\.config\./i,
|
|
10188
|
+
// Test runner config
|
|
10189
|
+
/(?:^|\/)jest\.config\./i,
|
|
10190
|
+
// Test runner config
|
|
10191
|
+
/(?:^|\/)pnpm-workspace\.yaml$/i,
|
|
10192
|
+
// Workspace config
|
|
10193
|
+
/(?:^|\/)\.npmrc$/i,
|
|
10194
|
+
// npm config
|
|
10195
|
+
/(?:^|\/)\.yarnrc/i,
|
|
10196
|
+
// Yarn config
|
|
10197
|
+
/(?:^|\/)tox\.ini$/i,
|
|
10198
|
+
// Python test runner
|
|
10199
|
+
/(?:^|\/)Makefile$/i,
|
|
10200
|
+
// Build automation
|
|
10201
|
+
/(?:^|\/)Rakefile$/i,
|
|
10202
|
+
// Ruby build tasks
|
|
10203
|
+
/(?:^|\/)contributing(?:\.[^/]*)?$/i,
|
|
10204
|
+
// Contributor docs
|
|
10205
|
+
/(?:^|\/)snippet\.json$/i,
|
|
10206
|
+
// Fern-generated snippet metadata
|
|
10207
|
+
/(?:^|\/)\.gitignore$/i,
|
|
10208
|
+
/(?:^|\/)\.gitattributes$/i,
|
|
10209
|
+
/\.slnx$/i
|
|
10210
|
+
// .NET solution files
|
|
10211
|
+
];
|
|
10212
|
+
var AutoVersioningService = class _AutoVersioningService {
|
|
10213
|
+
logger;
|
|
10214
|
+
constructor({ logger }) {
|
|
10215
|
+
this.logger = logger;
|
|
10216
|
+
}
|
|
10217
|
+
/**
|
|
10218
|
+
* Extracts the previous version from a git diff by finding lines with the magic version.
|
|
10219
|
+
* The diff should contain lines like: -version = '1.2.3' +version = '0.0.0-fern-placeholder'
|
|
10220
|
+
*
|
|
10221
|
+
* @param diffContent The git diff content
|
|
10222
|
+
* @param mappedMagicVersion The magic version after language transformations (e.g., "v0.0.0-fern-placeholder" for Go)
|
|
10223
|
+
* @return The previous version if found, or undefined if all magic version occurrences are in new files
|
|
10224
|
+
* @throws AutoVersioningException if the magic version is not found at all in the diff
|
|
10225
|
+
*/
|
|
10226
|
+
extractPreviousVersion(diffContent, mappedMagicVersion) {
|
|
10227
|
+
const lines = diffContent.split("\n");
|
|
10228
|
+
let currentFile = "unknown";
|
|
10229
|
+
let magicVersionOccurrences = 0;
|
|
10230
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10231
|
+
const line = lines[i];
|
|
10232
|
+
if (!line) {
|
|
10233
|
+
continue;
|
|
10234
|
+
}
|
|
10235
|
+
if (line.startsWith("+++ b/")) {
|
|
10236
|
+
currentFile = line.substring(6);
|
|
10237
|
+
}
|
|
10238
|
+
if (line.startsWith("+") && !line.startsWith("+++") && line.includes(mappedMagicVersion)) {
|
|
10239
|
+
magicVersionOccurrences++;
|
|
10240
|
+
const sanitizedPlusLine = line.replace(mappedMagicVersion, "<MAGIC>");
|
|
10241
|
+
this.logger.debug(
|
|
10242
|
+
`Found placeholder version in added line (file: ${currentFile}): ${sanitizedPlusLine}`
|
|
10243
|
+
);
|
|
10244
|
+
const matchingMinusLine = this.findMatchingMinusLine(lines, i, mappedMagicVersion);
|
|
10245
|
+
if (matchingMinusLine == void 0) {
|
|
10246
|
+
this.logger.debug(
|
|
10247
|
+
`No matching minus line in hunk; continuing search. file=${currentFile}, plusLine=${sanitizedPlusLine}`
|
|
10248
|
+
);
|
|
10249
|
+
continue;
|
|
10250
|
+
}
|
|
10251
|
+
const extracted = this.extractPreviousVersionFromDiffLine(matchingMinusLine);
|
|
10252
|
+
if (extracted != void 0) {
|
|
10253
|
+
this.logger.debug(`Extracted previous version from diff (file: ${currentFile}): ${extracted}`);
|
|
10254
|
+
return extracted;
|
|
10255
|
+
}
|
|
10256
|
+
this.logger.debug(
|
|
10257
|
+
`Could not parse version from matching minus line (file: ${currentFile}); continuing search.`
|
|
10258
|
+
);
|
|
10259
|
+
continue;
|
|
10260
|
+
}
|
|
10261
|
+
}
|
|
10262
|
+
if (magicVersionOccurrences > 0) {
|
|
10263
|
+
this.logger.info(
|
|
10264
|
+
`Found placeholder version in the diff but no matching previous version lines were found in any hunk. This indicates a new SDK repository with no previous version. occurrences=${magicVersionOccurrences}`
|
|
10265
|
+
);
|
|
10266
|
+
return void 0;
|
|
10267
|
+
}
|
|
10268
|
+
throw new AutoVersioningException(
|
|
10269
|
+
"Failed to extract version from diff. This may indicate the version file format is not supported for auto-versioning, or the placeholder version was not found in any added lines.",
|
|
10270
|
+
{ magicVersionAbsent: true }
|
|
10271
|
+
);
|
|
10272
|
+
}
|
|
10273
|
+
/**
|
|
10274
|
+
* Scans backwards from the given index to find a '-' line that forms a version-change pair
|
|
10275
|
+
* with the '+' line at the given index. Stops at hunk boundaries.
|
|
10276
|
+
*
|
|
10277
|
+
* @param lines All diff lines
|
|
10278
|
+
* @param plusLineIndex Index of the '+' line containing the magic version
|
|
10279
|
+
* @param mappedMagicVersion The magic version to match
|
|
10280
|
+
* @return The matching '-' line, or null if not found
|
|
10281
|
+
*/
|
|
10282
|
+
findMatchingMinusLine(lines, plusLineIndex, mappedMagicVersion) {
|
|
10283
|
+
const plusLine = lines[plusLineIndex];
|
|
10284
|
+
if (!plusLine) {
|
|
10285
|
+
return void 0;
|
|
10286
|
+
}
|
|
10287
|
+
let linesScanned = 0;
|
|
10288
|
+
for (let i = plusLineIndex - 1; i >= 0; i--) {
|
|
10289
|
+
const line = lines[i];
|
|
10290
|
+
if (!line) {
|
|
10291
|
+
continue;
|
|
10292
|
+
}
|
|
10293
|
+
linesScanned++;
|
|
10294
|
+
if (this.shouldStopSearching(line)) {
|
|
10295
|
+
this.logger.debug(`Stopped backward scan at hunk boundary after ${linesScanned} lines`);
|
|
10296
|
+
break;
|
|
10297
|
+
}
|
|
10298
|
+
if (this.isDeletionLine(line)) {
|
|
10299
|
+
if (this.isVersionChangePair(line, plusLine, mappedMagicVersion)) {
|
|
10300
|
+
this.logger.debug(`Found matching minus line after scanning ${linesScanned} lines backwards`);
|
|
10301
|
+
return line;
|
|
10302
|
+
}
|
|
10303
|
+
}
|
|
10304
|
+
}
|
|
10305
|
+
this.logger.warn(`No matching minus line found after scanning ${linesScanned} lines backwards`);
|
|
10306
|
+
return void 0;
|
|
10307
|
+
}
|
|
10308
|
+
/**
|
|
10309
|
+
* Cleans the git diff by removing version-related changes.
|
|
10310
|
+
* This prevents the AI from seeing our placeholder version and the version changes themselves.
|
|
10311
|
+
*
|
|
10312
|
+
* Algorithm:
|
|
10313
|
+
* 1. Parse diff into file sections (each starting with "diff --git")
|
|
10314
|
+
* 2. For each file section, identify and remove paired - and + lines where the only difference is the magic version
|
|
10315
|
+
* 3. Remove entire file sections that have no remaining content changes (no + or - lines)
|
|
10316
|
+
*
|
|
10317
|
+
* @param diffContent The git diff content
|
|
10318
|
+
* @param mappedMagicVersion The magic version after language transformations (e.g., "v0.0.0-fern-placeholder" for Go)
|
|
10319
|
+
* @return Cleaned diff content
|
|
10320
|
+
*/
|
|
10321
|
+
cleanDiffForAI(diffContent, mappedMagicVersion) {
|
|
10322
|
+
const lines = diffContent.split("\n");
|
|
10323
|
+
const fileSections = this.parseFileSections(lines);
|
|
10324
|
+
const cleanedSections = [];
|
|
10325
|
+
for (const section of fileSections) {
|
|
10326
|
+
const filePath = this.extractFilePathFromSection(section);
|
|
10327
|
+
if (filePath != null && this.shouldExcludeFile(filePath)) {
|
|
10328
|
+
this.logger.debug(`Excluding file from diff analysis: ${filePath}`);
|
|
10329
|
+
continue;
|
|
10330
|
+
}
|
|
10331
|
+
const cleaned = this.cleanFileSection(section, mappedMagicVersion);
|
|
10332
|
+
if (cleaned != void 0) {
|
|
10333
|
+
cleanedSections.push(cleaned);
|
|
10334
|
+
}
|
|
10335
|
+
}
|
|
10336
|
+
const result = [];
|
|
10337
|
+
for (const section of cleanedSections) {
|
|
10338
|
+
for (const line of section.lines) {
|
|
10339
|
+
result.push(line);
|
|
10340
|
+
}
|
|
10341
|
+
}
|
|
10342
|
+
this.logger.debug(
|
|
10343
|
+
`Cleaned diff: removed ${diffContent.length - result.join("\n").length} chars containing version changes`
|
|
10344
|
+
);
|
|
10345
|
+
return result.join("\n");
|
|
10346
|
+
}
|
|
10347
|
+
/**
|
|
10348
|
+
* Known patterns for API-surface signatures (function/method/class declarations).
|
|
10349
|
+
* Used by truncateDiff to prioritise sections with signature-like changes.
|
|
10350
|
+
*
|
|
10351
|
+
* Covers all languages Fern generates SDKs for:
|
|
10352
|
+
* - TypeScript/JavaScript: export function, export class, export interface, export type
|
|
10353
|
+
* - Java/C#: public class, public interface, public void, protected, internal
|
|
10354
|
+
* - Python: def method_name, class ClassName
|
|
10355
|
+
* - Go: func ExportedName (uppercase = exported)
|
|
10356
|
+
* - Ruby: def method_name, class ClassName, module ModuleName
|
|
10357
|
+
* - Swift: public func, public class, open class
|
|
10358
|
+
* - Rust: pub fn, pub struct, pub enum, pub trait
|
|
10359
|
+
* - PHP: public function, class ClassName
|
|
10360
|
+
*/
|
|
10361
|
+
static SIGNATURE_PATTERNS = [
|
|
10362
|
+
// TypeScript / JavaScript
|
|
10363
|
+
/^[+-]\s*export\s+/,
|
|
10364
|
+
// Java / C# / Swift / PHP — access modifiers
|
|
10365
|
+
/^[+-]\s*public\s+/,
|
|
10366
|
+
/^[+-]\s*protected\s+/,
|
|
10367
|
+
/^[+-]\s*internal\s+/,
|
|
10368
|
+
/^[+-]\s*open\s+(class|func)\s+/,
|
|
10369
|
+
// Python / Ruby
|
|
10370
|
+
/^[+-]\s*def\s+/,
|
|
10371
|
+
/^[+-]\s*class\s+[A-Z]/,
|
|
10372
|
+
/^[+-]\s*module\s+[A-Z]/,
|
|
10373
|
+
// Go — exported identifiers start with uppercase
|
|
10374
|
+
/^[+-]\s*func\s+[A-Z]/,
|
|
10375
|
+
/^[+-]\s*func\s+\([^)]+\)\s+[A-Z]/,
|
|
10376
|
+
/^[+-]\s*type\s+[A-Z]/,
|
|
10377
|
+
// Rust
|
|
10378
|
+
/^[+-]\s*pub\s+(fn|struct|enum|trait|type|mod)\s+/,
|
|
10379
|
+
// PHP
|
|
10380
|
+
/^[+-]\s*function\s+/
|
|
10381
|
+
];
|
|
10382
|
+
/**
|
|
10383
|
+
* Splits a cleaned diff into chunks that each fit within a byte budget.
|
|
10384
|
+
* File sections are ranked by semantic priority (same 5-tier system as
|
|
10385
|
+
* classifySection) so the first chunk always contains the highest-signal
|
|
10386
|
+
* sections. Each chunk contains only complete file sections — no partial
|
|
10387
|
+
* files.
|
|
10388
|
+
*
|
|
10389
|
+
* If a single file section exceeds `maxBytesPerChunk`, it is placed alone
|
|
10390
|
+
* in its own chunk so no information is lost.
|
|
10391
|
+
*
|
|
10392
|
+
* @param diff The cleaned diff string (output of cleanDiffForAI)
|
|
10393
|
+
* @param maxBytesPerChunk Maximum byte size for each chunk
|
|
10394
|
+
* @return Array of diff chunk strings (at least one element for non-empty diffs)
|
|
10395
|
+
*/
|
|
10396
|
+
chunkDiff(diff, maxBytesPerChunk) {
|
|
10397
|
+
const lines = diff.split("\n");
|
|
10398
|
+
const fileSections = this.parseFileSections(lines);
|
|
10399
|
+
if (fileSections.length === 0) {
|
|
10400
|
+
return [diff];
|
|
10401
|
+
}
|
|
10402
|
+
const ranked = fileSections.map((section) => ({
|
|
10403
|
+
text: section.lines.join("\n"),
|
|
10404
|
+
priority: this.classifySection(section)
|
|
10405
|
+
})).sort((a, b2) => a.priority - b2.priority);
|
|
10406
|
+
const chunks = [];
|
|
10407
|
+
let currentChunkTexts = [];
|
|
10408
|
+
let currentChunkBytes = 0;
|
|
10409
|
+
for (const entry of ranked) {
|
|
10410
|
+
const sectionBytes = Buffer.byteLength(entry.text, "utf-8");
|
|
10411
|
+
const newlineBytes = currentChunkTexts.length > 0 ? 1 : 0;
|
|
10412
|
+
if (sectionBytes > maxBytesPerChunk) {
|
|
10413
|
+
if (currentChunkTexts.length > 0) {
|
|
10414
|
+
chunks.push(currentChunkTexts.join("\n"));
|
|
10415
|
+
currentChunkTexts = [];
|
|
10416
|
+
}
|
|
10417
|
+
currentChunkBytes = 0;
|
|
10418
|
+
chunks.push(entry.text);
|
|
10419
|
+
continue;
|
|
10420
|
+
}
|
|
10421
|
+
if (currentChunkBytes + sectionBytes + newlineBytes > maxBytesPerChunk && currentChunkTexts.length > 0) {
|
|
10422
|
+
chunks.push(currentChunkTexts.join("\n"));
|
|
10423
|
+
currentChunkTexts = [];
|
|
10424
|
+
currentChunkBytes = 0;
|
|
10425
|
+
}
|
|
10426
|
+
currentChunkTexts.push(entry.text);
|
|
10427
|
+
currentChunkBytes += sectionBytes + (currentChunkTexts.length > 1 ? 1 : 0);
|
|
10428
|
+
}
|
|
10429
|
+
if (currentChunkTexts.length > 0) {
|
|
10430
|
+
chunks.push(currentChunkTexts.join("\n"));
|
|
10431
|
+
}
|
|
10432
|
+
return chunks;
|
|
10433
|
+
}
|
|
10434
|
+
/**
|
|
10435
|
+
* Classifies a file section into a priority bucket for truncation ranking.
|
|
10436
|
+
* Lower number = higher priority (included first).
|
|
10437
|
+
*
|
|
10438
|
+
* 1 = deletion-only, 2 = mixed with signatures, 3 = mixed,
|
|
10439
|
+
* 4 = addition-only, 5 = context-only
|
|
10440
|
+
*/
|
|
10441
|
+
static isDiffHeader(line) {
|
|
10442
|
+
return line.startsWith("--- a/") || line.startsWith("--- /dev/null") || line.startsWith("+++ b/") || line.startsWith("+++ /dev/null");
|
|
10443
|
+
}
|
|
10444
|
+
classifySection(section) {
|
|
10445
|
+
let hasAdditions = false;
|
|
10446
|
+
let hasDeletions = false;
|
|
10447
|
+
let hasSignature = false;
|
|
10448
|
+
for (const line of section.lines) {
|
|
10449
|
+
const isAddition = line.startsWith("+") && !_AutoVersioningService.isDiffHeader(line);
|
|
10450
|
+
const isDeletion = line.startsWith("-") && !_AutoVersioningService.isDiffHeader(line);
|
|
10451
|
+
if (isAddition) {
|
|
10452
|
+
hasAdditions = true;
|
|
10453
|
+
}
|
|
10454
|
+
if (isDeletion) {
|
|
10455
|
+
hasDeletions = true;
|
|
10456
|
+
}
|
|
10457
|
+
if (isAddition || isDeletion) {
|
|
10458
|
+
for (const pattern of _AutoVersioningService.SIGNATURE_PATTERNS) {
|
|
10459
|
+
if (pattern.test(line)) {
|
|
10460
|
+
hasSignature = true;
|
|
10461
|
+
break;
|
|
10462
|
+
}
|
|
10463
|
+
}
|
|
10464
|
+
}
|
|
10465
|
+
if (hasAdditions && hasDeletions && hasSignature) {
|
|
10466
|
+
break;
|
|
10467
|
+
}
|
|
10468
|
+
}
|
|
10469
|
+
if (hasDeletions && !hasAdditions) {
|
|
10470
|
+
return 1;
|
|
10471
|
+
}
|
|
10472
|
+
if (hasDeletions && hasAdditions && hasSignature) {
|
|
10473
|
+
return 2;
|
|
10474
|
+
}
|
|
10475
|
+
if (hasDeletions && hasAdditions) {
|
|
10476
|
+
return 3;
|
|
10477
|
+
}
|
|
10478
|
+
if (hasAdditions) {
|
|
10479
|
+
return 4;
|
|
10480
|
+
}
|
|
10481
|
+
return 5;
|
|
10482
|
+
}
|
|
10483
|
+
/**
|
|
10484
|
+
* Parses a diff into file sections, where each section starts with "diff --git".
|
|
10485
|
+
*/
|
|
10486
|
+
parseFileSections(lines) {
|
|
10487
|
+
const sections = [];
|
|
10488
|
+
let currentSection = void 0;
|
|
10489
|
+
for (const line of lines) {
|
|
10490
|
+
if (line.startsWith("diff --git")) {
|
|
10491
|
+
if (currentSection != void 0) {
|
|
10492
|
+
sections.push(currentSection);
|
|
10493
|
+
}
|
|
10494
|
+
currentSection = { lines: [line] };
|
|
10495
|
+
} else if (currentSection != void 0) {
|
|
10496
|
+
currentSection.lines.push(line);
|
|
10497
|
+
}
|
|
10498
|
+
}
|
|
10499
|
+
if (currentSection != void 0) {
|
|
10500
|
+
sections.push(currentSection);
|
|
10501
|
+
}
|
|
10502
|
+
return sections;
|
|
10503
|
+
}
|
|
10504
|
+
/**
|
|
10505
|
+
* Cleans a single file section by removing version-related changes.
|
|
10506
|
+
* Returns null if the file section should be completely removed.
|
|
10507
|
+
*/
|
|
10508
|
+
cleanFileSection(section, mappedMagicVersion) {
|
|
10509
|
+
const headerLines = [];
|
|
10510
|
+
const contentLines = [];
|
|
10511
|
+
let inContent = false;
|
|
10512
|
+
for (const line of section.lines) {
|
|
10513
|
+
if (line.startsWith("@@")) {
|
|
10514
|
+
inContent = true;
|
|
10515
|
+
headerLines.push(line);
|
|
10516
|
+
} else if (!inContent) {
|
|
10517
|
+
headerLines.push(line);
|
|
10518
|
+
} else {
|
|
10519
|
+
contentLines.push(line);
|
|
10520
|
+
}
|
|
10521
|
+
}
|
|
10522
|
+
let processedContent = this.removeVersionChangePairs(contentLines, mappedMagicVersion);
|
|
10523
|
+
processedContent = this.removeRemainingMagicVersionLines(processedContent, mappedMagicVersion);
|
|
10524
|
+
let hasChanges = false;
|
|
10525
|
+
for (const line of processedContent) {
|
|
10526
|
+
const isAddition = line.startsWith("+") && !_AutoVersioningService.isDiffHeader(line);
|
|
10527
|
+
const isDeletion = line.startsWith("-") && !_AutoVersioningService.isDiffHeader(line);
|
|
10528
|
+
if (isAddition || isDeletion) {
|
|
10529
|
+
hasChanges = true;
|
|
10530
|
+
break;
|
|
10531
|
+
}
|
|
10532
|
+
}
|
|
10533
|
+
if (!hasChanges) {
|
|
10534
|
+
return void 0;
|
|
10535
|
+
}
|
|
10536
|
+
const cleaned = { lines: [] };
|
|
10537
|
+
cleaned.lines.push(...headerLines);
|
|
10538
|
+
cleaned.lines.push(...processedContent);
|
|
10539
|
+
return cleaned;
|
|
10540
|
+
}
|
|
10541
|
+
/**
|
|
10542
|
+
* Removes any remaining lines (additions, deletions, or context) that contain the magic version.
|
|
10543
|
+
* This is a secondary pass to catch any lines that the pairing logic missed.
|
|
10544
|
+
*/
|
|
10545
|
+
removeRemainingMagicVersionLines(lines, mappedMagicVersion) {
|
|
10546
|
+
const result = [];
|
|
10547
|
+
for (const line of lines) {
|
|
10548
|
+
if (line.includes(mappedMagicVersion)) {
|
|
10549
|
+
continue;
|
|
10550
|
+
}
|
|
10551
|
+
result.push(line);
|
|
10552
|
+
}
|
|
10553
|
+
return result;
|
|
10554
|
+
}
|
|
10555
|
+
/**
|
|
10556
|
+
* Removes paired - and + lines where the only difference is the magic version.
|
|
10557
|
+
*/
|
|
10558
|
+
removeVersionChangePairs(lines, mappedMagicVersion) {
|
|
10559
|
+
const result = [];
|
|
10560
|
+
let lineIndex = 0;
|
|
10561
|
+
while (lineIndex < lines.length) {
|
|
10562
|
+
const currentLine = lines[lineIndex];
|
|
10563
|
+
if (!currentLine) {
|
|
10564
|
+
lineIndex++;
|
|
10565
|
+
continue;
|
|
10566
|
+
}
|
|
10567
|
+
if (this.isDeletionLine(currentLine)) {
|
|
10568
|
+
const pairIndex = this.findMatchingAdditionLine(lines, lineIndex, mappedMagicVersion);
|
|
10569
|
+
if (pairIndex !== -1) {
|
|
10570
|
+
result.push(...lines.slice(lineIndex + 1, pairIndex));
|
|
10571
|
+
lineIndex = pairIndex + 1;
|
|
10572
|
+
} else {
|
|
10573
|
+
result.push(currentLine);
|
|
10574
|
+
lineIndex++;
|
|
10575
|
+
}
|
|
10576
|
+
} else {
|
|
10577
|
+
result.push(currentLine);
|
|
10578
|
+
lineIndex++;
|
|
10579
|
+
}
|
|
10580
|
+
}
|
|
10581
|
+
return result;
|
|
10582
|
+
}
|
|
10583
|
+
isDeletionLine(line) {
|
|
10584
|
+
return line.startsWith("-") && !_AutoVersioningService.isDiffHeader(line);
|
|
10585
|
+
}
|
|
10586
|
+
findMatchingAdditionLine(lines, startIndex, mappedMagicVersion) {
|
|
10587
|
+
const startLine = lines[startIndex];
|
|
10588
|
+
if (!startLine) {
|
|
10589
|
+
return -1;
|
|
10590
|
+
}
|
|
10591
|
+
for (let nextIndex = startIndex + 1; nextIndex < lines.length; nextIndex++) {
|
|
10592
|
+
const nextLine = lines[nextIndex];
|
|
10593
|
+
if (!nextLine) {
|
|
10594
|
+
continue;
|
|
10595
|
+
}
|
|
10596
|
+
if (this.shouldStopSearching(nextLine)) {
|
|
10597
|
+
break;
|
|
10598
|
+
}
|
|
10599
|
+
if (this.isAdditionLine(nextLine)) {
|
|
10600
|
+
if (this.isVersionChangePair(startLine, nextLine, mappedMagicVersion)) {
|
|
10601
|
+
return nextIndex;
|
|
10602
|
+
}
|
|
10603
|
+
}
|
|
10604
|
+
}
|
|
10605
|
+
return -1;
|
|
10606
|
+
}
|
|
10607
|
+
isAdditionLine(line) {
|
|
10608
|
+
return line.startsWith("+") && !_AutoVersioningService.isDiffHeader(line);
|
|
10609
|
+
}
|
|
10610
|
+
shouldStopSearching(line) {
|
|
10611
|
+
return line.startsWith("@@") || line.startsWith("diff --git") || line.startsWith("index ") || _AutoVersioningService.isDiffHeader(line);
|
|
10612
|
+
}
|
|
10613
|
+
/**
|
|
10614
|
+
* Checks if two lines form a version change pair where the only difference is the magic version,
|
|
10615
|
+
* or where the only difference is a Go module path /vN suffix.
|
|
10616
|
+
*/
|
|
10617
|
+
isVersionChangePair(minusLine, plusLine, mappedMagicVersion) {
|
|
10618
|
+
if (this.isGoModulePathSuffixChange(minusLine, plusLine)) {
|
|
10619
|
+
return true;
|
|
10620
|
+
}
|
|
10621
|
+
if (!plusLine.includes(mappedMagicVersion)) {
|
|
10622
|
+
return false;
|
|
10623
|
+
}
|
|
10624
|
+
const minusContent = minusLine.substring(1);
|
|
10625
|
+
const plusContent = plusLine.substring(1);
|
|
10626
|
+
const magicIndex = plusContent.indexOf(mappedMagicVersion);
|
|
10627
|
+
if (magicIndex === -1) {
|
|
10628
|
+
return false;
|
|
10629
|
+
}
|
|
10630
|
+
const plusPrefix = plusContent.substring(0, magicIndex);
|
|
10631
|
+
const plusSuffix = plusContent.substring(magicIndex + mappedMagicVersion.length);
|
|
10632
|
+
if (!minusContent.startsWith(plusPrefix)) {
|
|
10633
|
+
return false;
|
|
10634
|
+
}
|
|
10635
|
+
const versionStart = plusPrefix.length;
|
|
10636
|
+
const versionEnd = minusContent.length - plusSuffix.length;
|
|
10637
|
+
if (versionEnd <= versionStart || versionEnd > minusContent.length) {
|
|
10638
|
+
return false;
|
|
10639
|
+
}
|
|
10640
|
+
const minusVersion = minusContent.substring(versionStart, versionEnd);
|
|
10641
|
+
const minusSuffix = minusContent.substring(versionEnd);
|
|
10642
|
+
if (minusSuffix !== plusSuffix) {
|
|
10643
|
+
return false;
|
|
10644
|
+
}
|
|
10645
|
+
return /.*\d+.*/.test(minusVersion);
|
|
10646
|
+
}
|
|
10647
|
+
/**
|
|
10648
|
+
* Detects when two diff lines differ only by a Go module path /vN suffix.
|
|
10649
|
+
* For example:
|
|
10650
|
+
* - core "github.com/org/repo/core"
|
|
10651
|
+
* + core "github.com/org/repo/v2/core"
|
|
10652
|
+
* These are artifacts of the major version bump and should be filtered from AI analysis.
|
|
10653
|
+
*
|
|
10654
|
+
* Only matches lines that contain a quoted Go-style module path (e.g., "github.com/..."),
|
|
10655
|
+
* so non-Go diffs like API URL version changes are not affected.
|
|
10656
|
+
*/
|
|
10657
|
+
isGoModulePathSuffixChange(minusLine, plusLine) {
|
|
10658
|
+
const minusContent = minusLine.substring(1);
|
|
10659
|
+
const plusContent = plusLine.substring(1);
|
|
10660
|
+
const goModulePathPattern = /"[^"]*(?:github\.com|golang\.org|google\.golang\.org|gopkg\.in)\/[^"]*"/;
|
|
10661
|
+
const goModuleDirectivePattern = /^\s*module\s+(?:github\.com|golang\.org|google\.golang\.org|gopkg\.in)\/\S+/;
|
|
10662
|
+
const hasGoModulePath = goModulePathPattern.test(minusContent) || goModulePathPattern.test(plusContent) || goModuleDirectivePattern.test(minusContent.trim()) || goModuleDirectivePattern.test(plusContent.trim());
|
|
10663
|
+
if (!hasGoModulePath) {
|
|
10664
|
+
return false;
|
|
10665
|
+
}
|
|
10666
|
+
const suffixPattern = /\/v\d+(?=\/|"\s*$|$)/g;
|
|
10667
|
+
const minusWithoutSuffix = minusContent.replace(suffixPattern, "");
|
|
10668
|
+
const plusWithoutSuffix = plusContent.replace(suffixPattern, "");
|
|
10669
|
+
if (minusWithoutSuffix !== plusWithoutSuffix) {
|
|
10670
|
+
return false;
|
|
10671
|
+
}
|
|
10672
|
+
return /\/v\d+(?=\/|"\s*$|$)/.test(minusContent) || /\/v\d+(?=\/|"\s*$|$)/.test(plusContent);
|
|
10673
|
+
}
|
|
10674
|
+
/**
|
|
10675
|
+
* Gets the latest semantic version from git tags in the given repository.
|
|
10676
|
+
* Used as a fallback when the magic version is not embedded in any generated file
|
|
10677
|
+
* (e.g., Swift SDKs which use git tags for versioning via SPM, not a version file).
|
|
10678
|
+
*
|
|
10679
|
+
* @param workingDirectory The git repository directory
|
|
10680
|
+
* @return The latest semver version from git tags, or undefined if no valid tags found
|
|
10681
|
+
*/
|
|
10682
|
+
async getLatestVersionFromGitTags(workingDirectory) {
|
|
10683
|
+
try {
|
|
10684
|
+
const result = await (0, import_logging_execa.loggingExeca)(this.logger, "git", ["ls-remote", "--tags", "origin"], {
|
|
10685
|
+
cwd: workingDirectory,
|
|
10686
|
+
doNotPipeOutput: true
|
|
10687
|
+
});
|
|
10688
|
+
const output = result.stdout;
|
|
10689
|
+
if (!output || output.trim().length === 0) {
|
|
10690
|
+
this.logger.info("No git tags found in repository");
|
|
10691
|
+
return void 0;
|
|
10692
|
+
}
|
|
10693
|
+
const tags = [];
|
|
10694
|
+
for (const line of output.split("\n")) {
|
|
10695
|
+
const trimmed2 = line.trim();
|
|
10696
|
+
if (trimmed2.length === 0 || trimmed2.includes("^{}")) {
|
|
10697
|
+
continue;
|
|
10698
|
+
}
|
|
10699
|
+
const match = trimmed2.match(/refs\/tags\/(.+)$/);
|
|
10700
|
+
if (match?.[1]) {
|
|
10701
|
+
tags.push(match[1]);
|
|
10702
|
+
}
|
|
10703
|
+
}
|
|
10704
|
+
const validTags = tags.filter((tag) => import_semver.default.valid(tag) != null).sort((a, b2) => import_semver.default.rcompare(a, b2));
|
|
10705
|
+
if (validTags.length > 0) {
|
|
10706
|
+
const latest = validTags[0];
|
|
10707
|
+
this.logger.info(`Found latest version from git tags: ${latest}`);
|
|
10708
|
+
return latest;
|
|
10709
|
+
}
|
|
10710
|
+
this.logger.info("No valid semver tags found in repository");
|
|
10711
|
+
return void 0;
|
|
10712
|
+
} catch (e) {
|
|
10713
|
+
this.logger.warn(`Failed to read git tags: ${e}`);
|
|
10714
|
+
return void 0;
|
|
10715
|
+
}
|
|
10716
|
+
}
|
|
10717
|
+
/**
|
|
10718
|
+
* Replaces all occurrences of the magic version with the final version in generated files.
|
|
10719
|
+
*
|
|
10720
|
+
* @param workingDirectory The directory containing generated files
|
|
10721
|
+
* @param mappedMagicVersion The magic version after language transformations (e.g., "v0.0.0-fern-placeholder" for Go)
|
|
10722
|
+
* @param finalVersion The final version to use
|
|
10723
|
+
* @throws Error if file operations fail or find/sed command fails
|
|
10724
|
+
*/
|
|
10725
|
+
async replaceMagicVersion(workingDirectory, mappedMagicVersion, finalVersion) {
|
|
10726
|
+
this.logger.debug(`Replacing placeholder version ${mappedMagicVersion} with final version: ${finalVersion}`);
|
|
10727
|
+
const sedCommand = `s/${this.escapeForSed(mappedMagicVersion)}/${this.escapeForSed(finalVersion)}/g`;
|
|
10728
|
+
let isGNUSed = false;
|
|
10729
|
+
try {
|
|
10730
|
+
await (0, import_logging_execa.loggingExeca)(this.logger, "sed", ["--version"], {
|
|
10731
|
+
doNotPipeOutput: true
|
|
10732
|
+
});
|
|
10733
|
+
isGNUSed = true;
|
|
10734
|
+
} catch {
|
|
10735
|
+
isGNUSed = false;
|
|
10736
|
+
}
|
|
10737
|
+
let command;
|
|
10738
|
+
if (isGNUSed) {
|
|
10739
|
+
command = `find "${workingDirectory}" -type f -not -path "*/.git/*" -exec sed -i '${sedCommand}' {} +`;
|
|
10740
|
+
} else {
|
|
10741
|
+
command = `find "${workingDirectory}" -type f -not -path "*/.git/*" -exec sed -i '' '${sedCommand}' {} +`;
|
|
10742
|
+
}
|
|
10743
|
+
await (0, import_logging_execa.loggingExeca)(this.logger, "bash", ["-c", command], {
|
|
10744
|
+
cwd: workingDirectory,
|
|
10745
|
+
doNotPipeOutput: true
|
|
10746
|
+
});
|
|
10747
|
+
this.logger.debug("Placeholder version replaced successfully");
|
|
10748
|
+
}
|
|
10749
|
+
/**
|
|
10750
|
+
* Adds /vN suffix to Go module paths and import statements when the computed
|
|
10751
|
+
* version is v2+. Go requires a /vN suffix on module paths for major versions >= 2.
|
|
10752
|
+
*
|
|
10753
|
+
* When using the safe magic version (major 0), the generated code has no /vN suffix.
|
|
10754
|
+
* After auto-versioning determines the real version, this method adds the correct suffix.
|
|
10755
|
+
*
|
|
10756
|
+
* @param workingDirectory The directory containing generated Go files
|
|
10757
|
+
* @param finalVersion The final computed version (e.g., "v2.0.0", "v3.1.0")
|
|
10758
|
+
*/
|
|
10759
|
+
async addGoMajorVersionSuffix(workingDirectory, finalVersion) {
|
|
10760
|
+
const major = this.extractMajorVersion(finalVersion);
|
|
10761
|
+
if (major == null || major < 2) {
|
|
10762
|
+
this.logger.debug(
|
|
10763
|
+
`Version ${finalVersion} has major ${major ?? "unknown"}, no Go module path suffix needed`
|
|
10764
|
+
);
|
|
10765
|
+
return;
|
|
10766
|
+
}
|
|
10767
|
+
const goModPath = (0, import_path3.join)(workingDirectory, "go.mod");
|
|
10768
|
+
if (!(0, import_fs2.existsSync)(goModPath)) {
|
|
10769
|
+
this.logger.debug(`No go.mod found in ${workingDirectory}, skipping Go module path update`);
|
|
10770
|
+
return;
|
|
10771
|
+
}
|
|
10772
|
+
const modulePath = await this.readGoModulePath(goModPath);
|
|
10773
|
+
if (modulePath == null) {
|
|
10774
|
+
this.logger.warn(`Could not read module path from go.mod in ${workingDirectory}`);
|
|
10775
|
+
return;
|
|
10776
|
+
}
|
|
10777
|
+
const suffix = `/v${major}`;
|
|
10778
|
+
if (/\/v\d+$/.test(modulePath)) {
|
|
10779
|
+
this.logger.debug(`Module path ${modulePath} already has a version suffix, skipping`);
|
|
10780
|
+
return;
|
|
10781
|
+
}
|
|
10782
|
+
await this.addSuffixToGoMod(goModPath, modulePath, suffix);
|
|
10783
|
+
await this.addSuffixToGoFiles(workingDirectory, modulePath, suffix);
|
|
10784
|
+
this.logger.info(`Added ${suffix} to Go module path ${modulePath} and all import paths`);
|
|
10785
|
+
}
|
|
10786
|
+
/**
|
|
10787
|
+
* Extracts the major version number from a version string.
|
|
10788
|
+
* Handles versions with or without 'v' prefix.
|
|
10789
|
+
*/
|
|
10790
|
+
extractMajorVersion(version) {
|
|
10791
|
+
const match = version.match(/^v?(\d+)\./);
|
|
10792
|
+
if (match?.[1] != null) {
|
|
10793
|
+
return parseInt(match[1], 10);
|
|
10794
|
+
}
|
|
10795
|
+
return void 0;
|
|
10796
|
+
}
|
|
10797
|
+
/**
|
|
10798
|
+
* Reads the module path from a go.mod file.
|
|
10799
|
+
*/
|
|
10800
|
+
async readGoModulePath(goModPath) {
|
|
10801
|
+
const content = await (0, import_promises2.readFile)(goModPath, "utf-8");
|
|
10802
|
+
for (const line of content.split("\n")) {
|
|
10803
|
+
const trimmed2 = line.trim();
|
|
10804
|
+
if (trimmed2.startsWith("module ")) {
|
|
10805
|
+
return trimmed2.substring("module ".length).trim();
|
|
10806
|
+
}
|
|
10807
|
+
}
|
|
10808
|
+
return void 0;
|
|
10809
|
+
}
|
|
10810
|
+
/**
|
|
10811
|
+
* Adds a version suffix to the module line in go.mod.
|
|
10812
|
+
*/
|
|
10813
|
+
async addSuffixToGoMod(goModPath, modulePath, suffix) {
|
|
10814
|
+
const content = await (0, import_promises2.readFile)(goModPath, "utf-8");
|
|
10815
|
+
const lines = content.split("\n");
|
|
10816
|
+
let changed = false;
|
|
10817
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10818
|
+
const line = lines[i];
|
|
10819
|
+
if (line != null && line.trim().startsWith("module ") && line.includes(modulePath)) {
|
|
10820
|
+
lines[i] = line.replace(modulePath, modulePath + suffix);
|
|
10821
|
+
changed = true;
|
|
10822
|
+
break;
|
|
10823
|
+
}
|
|
10824
|
+
}
|
|
10825
|
+
if (changed) {
|
|
10826
|
+
await (0, import_promises2.writeFile)(goModPath, lines.join("\n"), "utf-8");
|
|
10827
|
+
this.logger.debug(`Updated go.mod: added ${suffix} to module path`);
|
|
10828
|
+
}
|
|
10829
|
+
}
|
|
10830
|
+
/**
|
|
10831
|
+
* Recursively walks a directory and returns all file paths matching a filter.
|
|
10832
|
+
*/
|
|
10833
|
+
async walkDirectory(dir, filter) {
|
|
10834
|
+
const results = [];
|
|
10835
|
+
const entries2 = await (0, import_promises2.readdir)(dir);
|
|
10836
|
+
for (const entry of entries2) {
|
|
10837
|
+
const fullPath = (0, import_path3.join)(dir, entry);
|
|
10838
|
+
const entryStat = await (0, import_promises2.stat)(fullPath);
|
|
10839
|
+
if (entryStat.isDirectory()) {
|
|
10840
|
+
if (entry === ".git" || entry === "vendor") {
|
|
10841
|
+
continue;
|
|
10842
|
+
}
|
|
10843
|
+
const subResults = await this.walkDirectory(fullPath, filter);
|
|
10844
|
+
results.push(...subResults);
|
|
10845
|
+
} else if (filter(fullPath)) {
|
|
10846
|
+
results.push(fullPath);
|
|
10847
|
+
}
|
|
10848
|
+
}
|
|
10849
|
+
return results;
|
|
10850
|
+
}
|
|
10851
|
+
/**
|
|
10852
|
+
* Adds a version suffix to import paths in all .go files in the repository.
|
|
10853
|
+
*/
|
|
10854
|
+
async addSuffixToGoFiles(repoRoot, modulePath, suffix) {
|
|
10855
|
+
const goFiles = await this.walkDirectory(repoRoot, (filePath) => (0, import_path3.extname)(filePath) === ".go");
|
|
10856
|
+
for (const filePath of goFiles) {
|
|
10857
|
+
await this.addSuffixToGoFile(filePath, modulePath, suffix);
|
|
10858
|
+
}
|
|
10859
|
+
}
|
|
10860
|
+
/**
|
|
10861
|
+
* Adds a version suffix to import paths in a single .go file.
|
|
10862
|
+
* Replaces occurrences of the module path with modulePath + suffix in import statements.
|
|
10863
|
+
*/
|
|
10864
|
+
async addSuffixToGoFile(filePath, modulePath, suffix) {
|
|
10865
|
+
const content = await (0, import_promises2.readFile)(filePath, "utf-8");
|
|
10866
|
+
const lines = content.split("\n");
|
|
10867
|
+
let changed = false;
|
|
10868
|
+
let inImportBlock = false;
|
|
10869
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10870
|
+
const line = lines[i];
|
|
10871
|
+
if (line == null) {
|
|
10872
|
+
continue;
|
|
10873
|
+
}
|
|
10874
|
+
const trimmed2 = line.trim();
|
|
10875
|
+
if (trimmed2.startsWith("import (")) {
|
|
10876
|
+
inImportBlock = true;
|
|
10877
|
+
} else if (inImportBlock && trimmed2 === ")") {
|
|
10878
|
+
inImportBlock = false;
|
|
10879
|
+
}
|
|
10880
|
+
const isImportLine = inImportBlock || trimmed2.startsWith('import "') || trimmed2.startsWith('import "') || /^import\s+\w+\s+"/.test(trimmed2);
|
|
10881
|
+
if (!isImportLine) {
|
|
10882
|
+
continue;
|
|
10883
|
+
}
|
|
10884
|
+
if (!line.includes(modulePath)) {
|
|
10885
|
+
continue;
|
|
10886
|
+
}
|
|
10887
|
+
const newLine = line.replace(`"${modulePath}/`, `"${modulePath}${suffix}/`).replace(`"${modulePath}"`, `"${modulePath}${suffix}"`);
|
|
10888
|
+
if (newLine !== line) {
|
|
10889
|
+
lines[i] = newLine;
|
|
10890
|
+
changed = true;
|
|
10891
|
+
}
|
|
10892
|
+
}
|
|
10893
|
+
if (changed) {
|
|
10894
|
+
await (0, import_promises2.writeFile)(filePath, lines.join("\n"), "utf-8");
|
|
10895
|
+
this.logger.debug(`Updated Go imports in ${filePath}`);
|
|
10896
|
+
}
|
|
10897
|
+
}
|
|
10898
|
+
/**
|
|
10899
|
+
* Escapes special characters for use in sed command.
|
|
10900
|
+
*/
|
|
10901
|
+
escapeForSed(str) {
|
|
10902
|
+
return str.replace(/[/&]/g, "\\$&");
|
|
10903
|
+
}
|
|
10904
|
+
/**
|
|
10905
|
+
* Extracts the file path from a diff file section.
|
|
10906
|
+
* Parses the "diff --git a/path b/path" header line.
|
|
10907
|
+
*/
|
|
10908
|
+
extractFilePathFromSection(section) {
|
|
10909
|
+
const firstLine = section.lines[0];
|
|
10910
|
+
if (firstLine == null) {
|
|
10911
|
+
return void 0;
|
|
10912
|
+
}
|
|
10913
|
+
const match = firstLine.match(/^diff --git "?a\/(.+?)"? "?b\/(.+?)"?$/);
|
|
10914
|
+
if (match?.[2] != null) {
|
|
10915
|
+
return match[2];
|
|
10916
|
+
}
|
|
10917
|
+
return void 0;
|
|
10918
|
+
}
|
|
10919
|
+
/**
|
|
10920
|
+
* Checks whether a file path matches any of the exclusion patterns.
|
|
10921
|
+
* Excluded files are noise for AI-based semantic version analysis.
|
|
10922
|
+
*/
|
|
10923
|
+
shouldExcludeFile(filePath) {
|
|
10924
|
+
return EXCLUDED_FILE_PATTERNS.some((pattern) => pattern.test(filePath));
|
|
10925
|
+
}
|
|
10926
|
+
/**
|
|
10927
|
+
* Extracts the previous version from a line containing the magic version.
|
|
10928
|
+
* Assumes the line format is like: "version = '0.0.0-fern-placeholder'" or "version: 0.0.0-fern-placeholder"
|
|
10929
|
+
*
|
|
10930
|
+
* @param lineWithMagicVersion A line from git diff containing the magic version
|
|
10931
|
+
* @return The inferred previous version if found, or undefined if the version cannot be parsed
|
|
10932
|
+
*/
|
|
10933
|
+
extractPreviousVersionFromDiffLine(lineWithMagicVersion) {
|
|
10934
|
+
const prevVersionPattern = /[-].*?([v]?\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?)/;
|
|
10935
|
+
const matcher = lineWithMagicVersion.match(prevVersionPattern);
|
|
10936
|
+
if (matcher && matcher[1]) {
|
|
10937
|
+
const version = matcher[1];
|
|
10938
|
+
this.logger.info(`Extracted previous version from diff: ${version}`);
|
|
10939
|
+
return version;
|
|
10940
|
+
}
|
|
10941
|
+
this.logger.warn("Could not extract previous version from diff line: " + lineWithMagicVersion);
|
|
10942
|
+
return void 0;
|
|
10943
|
+
}
|
|
10944
|
+
};
|
|
10945
|
+
|
|
10946
|
+
// src/autoversion/VersionUtils.ts
|
|
10947
|
+
var MAGIC_VERSION = "0.0.0-fern-placeholder";
|
|
10948
|
+
var MAGIC_VERSION_PYTHON = "0.0.0.dev0";
|
|
10949
|
+
function mapMagicVersionForLanguage(magicVersion, language) {
|
|
10950
|
+
if (magicVersion !== MAGIC_VERSION) {
|
|
10951
|
+
return magicVersion;
|
|
10952
|
+
}
|
|
10953
|
+
if (language === "go") {
|
|
10954
|
+
return `v${magicVersion}`;
|
|
10955
|
+
}
|
|
10956
|
+
if (language === "python") {
|
|
10957
|
+
return MAGIC_VERSION_PYTHON;
|
|
10958
|
+
}
|
|
10959
|
+
return magicVersion;
|
|
10960
|
+
}
|
|
10961
|
+
var MAX_AI_DIFF_BYTES = 4e4;
|
|
10962
|
+
var MAX_CHUNKS = 40;
|
|
10963
|
+
var MAX_RAW_DIFF_BYTES = 1e7;
|
|
10964
|
+
var VERSION_BUMP_RANK = {
|
|
10965
|
+
["MAJOR" /* MAJOR */]: 3,
|
|
10966
|
+
["MINOR" /* MINOR */]: 2,
|
|
10967
|
+
["PATCH" /* PATCH */]: 1,
|
|
10968
|
+
["NO_CHANGE" /* NO_CHANGE */]: 0
|
|
10969
|
+
};
|
|
10970
|
+
function maxVersionBump(a, b2) {
|
|
10971
|
+
return (VERSION_BUMP_RANK[a] ?? 0) >= (VERSION_BUMP_RANK[b2] ?? 0) ? a : b2;
|
|
10972
|
+
}
|
|
10973
|
+
var SEMVER_PATTERN = /^(v)?(\d+)\.(\d+)\.(\d+)(?:-([\w.-]+))?(?:\+([\w.-]+))?$/;
|
|
10974
|
+
function isValidSemver(version) {
|
|
10975
|
+
return SEMVER_PATTERN.test(version);
|
|
10976
|
+
}
|
|
10977
|
+
function incrementVersion(currentVersion, versionBump) {
|
|
10978
|
+
const matcher = currentVersion.match(SEMVER_PATTERN);
|
|
10979
|
+
if (!matcher) {
|
|
10980
|
+
throw new AutoVersioningException("Invalid semantic version format: " + currentVersion);
|
|
10981
|
+
}
|
|
10982
|
+
const prefix = matcher[1] || "";
|
|
10983
|
+
let major = parseInt(matcher[2] ?? "0", 10);
|
|
10984
|
+
let minor = parseInt(matcher[3] ?? "0", 10);
|
|
10985
|
+
let patch = parseInt(matcher[4] ?? "0", 10);
|
|
10986
|
+
const preRelease = matcher[5];
|
|
10987
|
+
const buildMetadata = matcher[6];
|
|
10988
|
+
let preserveMetadata = false;
|
|
10989
|
+
if (versionBump === "MAJOR" /* MAJOR */) {
|
|
10990
|
+
major++;
|
|
10991
|
+
minor = 0;
|
|
10992
|
+
patch = 0;
|
|
10993
|
+
} else if (versionBump === "MINOR" /* MINOR */) {
|
|
10994
|
+
minor++;
|
|
10995
|
+
patch = 0;
|
|
10996
|
+
} else if (versionBump === "PATCH" /* PATCH */) {
|
|
10997
|
+
patch++;
|
|
10998
|
+
} else if (versionBump === "NO_CHANGE" /* NO_CHANGE */) {
|
|
10999
|
+
preserveMetadata = true;
|
|
11000
|
+
} else {
|
|
11001
|
+
throw new AutoVersioningException("Unknown version bump type: " + versionBump);
|
|
11002
|
+
}
|
|
11003
|
+
let newVersion = `${prefix}${major}.${minor}.${patch}`;
|
|
11004
|
+
if (preserveMetadata) {
|
|
11005
|
+
if (preRelease) {
|
|
11006
|
+
newVersion += `-${preRelease}`;
|
|
11007
|
+
}
|
|
11008
|
+
if (buildMetadata) {
|
|
11009
|
+
newVersion += `+${buildMetadata}`;
|
|
11010
|
+
}
|
|
11011
|
+
}
|
|
11012
|
+
return newVersion;
|
|
11013
|
+
}
|
|
11014
|
+
|
|
11015
|
+
// src/pipeline/steps/BaseStep.ts
|
|
11016
|
+
var BaseStep = class {
|
|
11017
|
+
constructor(outputDir, logger) {
|
|
11018
|
+
this.outputDir = outputDir;
|
|
11019
|
+
this.logger = logger;
|
|
11020
|
+
}
|
|
11021
|
+
outputDir;
|
|
11022
|
+
logger;
|
|
11023
|
+
};
|
|
11024
|
+
|
|
11025
|
+
// src/pipeline/steps/AutoVersionStep.ts
|
|
11026
|
+
var COMMIT_MARKER = "[fern-autoversion]";
|
|
11027
|
+
var FERN_TRAILER = "\n\n\u{1F33F} Generated with Fern";
|
|
11028
|
+
var AutoVersionStep = class extends BaseStep {
|
|
11029
|
+
constructor(outputDir, logger, config) {
|
|
11030
|
+
super(outputDir, logger);
|
|
11031
|
+
this.config = config;
|
|
11032
|
+
}
|
|
11033
|
+
config;
|
|
11034
|
+
name = "autoVersion";
|
|
11035
|
+
async execute(context) {
|
|
11036
|
+
const prepared = context.previousStepResults.generationCommit?.preparedReplay;
|
|
11037
|
+
if (!prepared) {
|
|
11038
|
+
this.logger.info(
|
|
11039
|
+
"AutoVersionStep: no replay preparation available (replay uninitialized or prepare failed); skipping."
|
|
11040
|
+
);
|
|
11041
|
+
return { executed: true, success: true };
|
|
11042
|
+
}
|
|
11043
|
+
if (prepared.flow === "skip-application") {
|
|
11044
|
+
this.logger.info("AutoVersionStep: replay flow is skip-application; skipping autoversion.");
|
|
11045
|
+
return { executed: true, success: true };
|
|
11046
|
+
}
|
|
11047
|
+
const language = this.config.language;
|
|
11048
|
+
const mappedMagicVersion = mapMagicVersionForLanguage(MAGIC_VERSION, language);
|
|
11049
|
+
const service = new AutoVersioningService({ logger: this.toTaskLogger() });
|
|
11050
|
+
if (prepared.previousGenerationSha == null) {
|
|
11051
|
+
return await this.handleFirstGeneration({ service, language, mappedMagicVersion });
|
|
11052
|
+
}
|
|
11053
|
+
return await this.handleNormalFlow({
|
|
11054
|
+
prepared,
|
|
11055
|
+
service,
|
|
11056
|
+
language,
|
|
11057
|
+
mappedMagicVersion,
|
|
11058
|
+
previousGenerationSha: prepared.previousGenerationSha
|
|
11059
|
+
});
|
|
11060
|
+
}
|
|
11061
|
+
async handleFirstGeneration(params) {
|
|
11062
|
+
const { service, language, mappedMagicVersion } = params;
|
|
11063
|
+
const initialVersion = this.config.baseVersion ?? (mappedMagicVersion.startsWith("v") ? "v0.0.1" : "0.0.1");
|
|
11064
|
+
if (!isValidSemver(initialVersion)) {
|
|
11065
|
+
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.`;
|
|
11066
|
+
this.logger.error(errorMessage);
|
|
11067
|
+
return {
|
|
11068
|
+
executed: true,
|
|
11069
|
+
success: false,
|
|
11070
|
+
errorMessage
|
|
11071
|
+
};
|
|
11072
|
+
}
|
|
11073
|
+
this.logger.info(`AutoVersionStep: first generation \u2014 using initial version ${initialVersion}`);
|
|
11074
|
+
await service.replaceMagicVersion(this.outputDir, mappedMagicVersion, initialVersion);
|
|
11075
|
+
if (language === "go") {
|
|
11076
|
+
await service.addGoMajorVersionSuffix(this.outputDir, initialVersion);
|
|
11077
|
+
}
|
|
11078
|
+
const commitMessage = this.brandMessage("Initial SDK generation");
|
|
11079
|
+
const commitSha = this.commitAutoversion(commitMessage);
|
|
11080
|
+
return {
|
|
11081
|
+
executed: true,
|
|
11082
|
+
success: true,
|
|
11083
|
+
version: initialVersion,
|
|
11084
|
+
commitMessage,
|
|
11085
|
+
commitSha
|
|
11086
|
+
};
|
|
11087
|
+
}
|
|
11088
|
+
async handleNormalFlow(params) {
|
|
11089
|
+
const { prepared, service, language, mappedMagicVersion, previousGenerationSha } = params;
|
|
11090
|
+
const rawDiff = this.gitDiff(previousGenerationSha, prepared.currentGenerationSha);
|
|
11091
|
+
if (rawDiff.trim().length === 0) {
|
|
11092
|
+
this.logger.info("AutoVersionStep: empty diff between generations; skipping autoversion.");
|
|
11093
|
+
return { executed: true, success: true };
|
|
11094
|
+
}
|
|
11095
|
+
const previousVersion = await this.resolvePreviousVersion({ service, rawDiff, mappedMagicVersion });
|
|
11096
|
+
if (previousVersion == null) {
|
|
11097
|
+
return await this.handleFirstGeneration({ service, language, mappedMagicVersion });
|
|
11098
|
+
}
|
|
11099
|
+
const cleanedDiff = service.cleanDiffForAI(rawDiff, mappedMagicVersion);
|
|
11100
|
+
const rawBytes = Buffer.byteLength(rawDiff, "utf-8");
|
|
11101
|
+
const cleanedBytes = Buffer.byteLength(cleanedDiff, "utf-8");
|
|
11102
|
+
this.logger.debug(
|
|
11103
|
+
`AutoVersionStep: raw=${formatSizeKB(rawBytes)}KB (${countFilesInDiff(rawDiff)} files), cleaned=${formatSizeKB(cleanedBytes)}KB (${countFilesInDiff(cleanedDiff)} files)`
|
|
11104
|
+
);
|
|
11105
|
+
if (cleanedDiff.trim().length === 0) {
|
|
11106
|
+
this.logger.info("AutoVersionStep: cleaned diff is empty; no semantic changes.");
|
|
11107
|
+
return { executed: true, success: true };
|
|
11108
|
+
}
|
|
11109
|
+
if (cleanedBytes > MAX_RAW_DIFF_BYTES) {
|
|
11110
|
+
this.logger.warn(
|
|
11111
|
+
`AutoVersionStep: diff too large for FAI (${formatSizeKB(cleanedBytes)}KB, limit ${formatSizeKB(MAX_RAW_DIFF_BYTES)}KB). Falling back to PATCH.`
|
|
11112
|
+
);
|
|
11113
|
+
return await this.finalizeWithBump({
|
|
11114
|
+
service,
|
|
11115
|
+
language,
|
|
11116
|
+
mappedMagicVersion,
|
|
11117
|
+
previousVersion,
|
|
11118
|
+
analysis: { versionBump: "PATCH", message: this.brandMessage("SDK regeneration") }
|
|
11119
|
+
});
|
|
11120
|
+
}
|
|
11121
|
+
const chunks = service.chunkDiff(cleanedDiff, MAX_AI_DIFF_BYTES);
|
|
11122
|
+
const cappedChunks = chunks.slice(0, MAX_CHUNKS);
|
|
11123
|
+
const skippedChunks = chunks.length - cappedChunks.length;
|
|
11124
|
+
if (chunks.length > 1) {
|
|
11125
|
+
this.logger.info(
|
|
11126
|
+
`AutoVersionStep: split diff into ${chunks.length} chunks` + (skippedChunks > 0 ? ` (capped at ${MAX_CHUNKS}, skipping ${skippedChunks})` : "")
|
|
11127
|
+
);
|
|
11128
|
+
}
|
|
11129
|
+
let analysis;
|
|
11130
|
+
try {
|
|
11131
|
+
analysis = cappedChunks.length <= 1 ? await this.analyzeSingle(cleanedDiff, language, previousVersion) : await this.analyzeChunks(cappedChunks, language, previousVersion);
|
|
11132
|
+
} catch (error) {
|
|
11133
|
+
this.logger.warn(`AutoVersionStep: FAI analysis failed (${String(error)}); falling back to PATCH bump.`);
|
|
11134
|
+
analysis = { versionBump: "PATCH", message: this.brandMessage("SDK regeneration") };
|
|
11135
|
+
}
|
|
11136
|
+
if (analysis == null) {
|
|
11137
|
+
this.logger.info("AutoVersionStep: FAI returned NO_CHANGE; skipping autoversion commit.");
|
|
11138
|
+
return {
|
|
11139
|
+
executed: true,
|
|
11140
|
+
success: true,
|
|
11141
|
+
previousVersion,
|
|
11142
|
+
versionBump: "NO_CHANGE"
|
|
11143
|
+
};
|
|
11144
|
+
}
|
|
11145
|
+
return await this.finalizeWithBump({
|
|
11146
|
+
service,
|
|
11147
|
+
language,
|
|
11148
|
+
mappedMagicVersion,
|
|
11149
|
+
previousVersion,
|
|
11150
|
+
analysis
|
|
11151
|
+
});
|
|
11152
|
+
}
|
|
11153
|
+
async finalizeWithBump(params) {
|
|
11154
|
+
const { service, language, mappedMagicVersion, previousVersion, analysis } = params;
|
|
11155
|
+
const newVersion = incrementVersion(previousVersion, analysis.versionBump);
|
|
11156
|
+
this.logger.info(`AutoVersionStep: ${analysis.versionBump} bump: ${previousVersion} \u2192 ${newVersion}`);
|
|
11157
|
+
await service.replaceMagicVersion(this.outputDir, mappedMagicVersion, newVersion);
|
|
11158
|
+
if (language === "go") {
|
|
11159
|
+
await service.addGoMajorVersionSuffix(this.outputDir, newVersion);
|
|
11160
|
+
}
|
|
11161
|
+
if (analysis.changelogEntry && analysis.changelogEntry.trim().length > 0) {
|
|
11162
|
+
await this.prependChangelogEntry({ version: newVersion, entry: analysis.changelogEntry });
|
|
11163
|
+
}
|
|
11164
|
+
const commitSha = this.commitAutoversion(analysis.message);
|
|
11165
|
+
return {
|
|
11166
|
+
executed: true,
|
|
11167
|
+
success: true,
|
|
11168
|
+
version: newVersion,
|
|
11169
|
+
commitMessage: analysis.message,
|
|
11170
|
+
changelogEntry: analysis.changelogEntry,
|
|
11171
|
+
previousVersion,
|
|
11172
|
+
versionBump: analysis.versionBump,
|
|
11173
|
+
prDescription: analysis.prDescription,
|
|
11174
|
+
versionBumpReason: analysis.versionBumpReason,
|
|
11175
|
+
commitSha
|
|
11176
|
+
};
|
|
11177
|
+
}
|
|
11178
|
+
async resolvePreviousVersion(params) {
|
|
11179
|
+
const { service, rawDiff, mappedMagicVersion } = params;
|
|
11180
|
+
try {
|
|
11181
|
+
const extracted = service.extractPreviousVersion(rawDiff, mappedMagicVersion);
|
|
11182
|
+
if (extracted != null) {
|
|
11183
|
+
this.logger.debug(`AutoVersionStep: previous version from diff: ${extracted}`);
|
|
11184
|
+
return extracted;
|
|
11185
|
+
}
|
|
11186
|
+
} catch (error) {
|
|
11187
|
+
if (!(error instanceof AutoVersioningException) || !error.magicVersionAbsent) {
|
|
11188
|
+
throw error;
|
|
11189
|
+
}
|
|
11190
|
+
this.logger.info("AutoVersionStep: magic version not found in diff; falling back to metadata + git tags.");
|
|
11191
|
+
}
|
|
11192
|
+
const metadataVersion = await this.readVersionFromMetadata();
|
|
11193
|
+
if (metadataVersion != null) {
|
|
11194
|
+
return this.normalizeVersionPrefix(metadataVersion, mappedMagicVersion);
|
|
11195
|
+
}
|
|
11196
|
+
try {
|
|
11197
|
+
const tagVersion = await service.getLatestVersionFromGitTags(this.outputDir);
|
|
11198
|
+
if (tagVersion != null) {
|
|
11199
|
+
return this.normalizeVersionPrefix(tagVersion, mappedMagicVersion);
|
|
11200
|
+
}
|
|
11201
|
+
} catch (error) {
|
|
11202
|
+
this.logger.debug(`AutoVersionStep: git-tags fallback failed (${String(error)}); ignoring.`);
|
|
11203
|
+
}
|
|
11204
|
+
return null;
|
|
11205
|
+
}
|
|
11206
|
+
async readVersionFromMetadata() {
|
|
11207
|
+
try {
|
|
11208
|
+
const output = (0, import_child_process3.execFileSync)("git", ["show", "HEAD~1:.fern/metadata.json"], {
|
|
11209
|
+
cwd: this.outputDir,
|
|
11210
|
+
encoding: "utf-8",
|
|
11211
|
+
stdio: "pipe"
|
|
11212
|
+
});
|
|
11213
|
+
const parsed = JSON.parse(output);
|
|
11214
|
+
return parsed.sdkVersion ?? void 0;
|
|
11215
|
+
} catch {
|
|
11216
|
+
return void 0;
|
|
11217
|
+
}
|
|
11218
|
+
}
|
|
11219
|
+
normalizeVersionPrefix(version, mappedMagicVersion) {
|
|
11220
|
+
const stripped = version.startsWith("v") ? version.slice(1) : version;
|
|
11221
|
+
return mappedMagicVersion.startsWith("v") ? `v${stripped}` : stripped;
|
|
11222
|
+
}
|
|
11223
|
+
gitDiff(from, to) {
|
|
11224
|
+
return (0, import_child_process3.execFileSync)("git", ["diff", from, to, "--", ".", ":(exclude).fern/metadata.json"], {
|
|
11225
|
+
cwd: this.outputDir,
|
|
11226
|
+
encoding: "utf-8",
|
|
11227
|
+
stdio: "pipe",
|
|
11228
|
+
maxBuffer: 256 * 1024 * 1024
|
|
11229
|
+
});
|
|
11230
|
+
}
|
|
11231
|
+
commitAutoversion(message) {
|
|
11232
|
+
const subject = message.split("\n")[0] ?? message;
|
|
11233
|
+
const body = message.slice(subject.length).trimStart();
|
|
11234
|
+
const fullMessage = body.length > 0 ? `${COMMIT_MARKER} ${subject}
|
|
11235
|
+
|
|
11236
|
+
${body}` : `${COMMIT_MARKER} ${subject}`;
|
|
11237
|
+
(0, import_child_process3.execFileSync)("git", ["add", "-A"], { cwd: this.outputDir, stdio: "pipe" });
|
|
11238
|
+
(0, import_child_process3.execFileSync)("git", ["commit", "--allow-empty", "-m", fullMessage], {
|
|
11239
|
+
cwd: this.outputDir,
|
|
11240
|
+
stdio: "pipe"
|
|
11241
|
+
});
|
|
11242
|
+
return (0, import_child_process3.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
11243
|
+
cwd: this.outputDir,
|
|
11244
|
+
encoding: "utf-8",
|
|
11245
|
+
stdio: "pipe"
|
|
11246
|
+
}).trim();
|
|
11247
|
+
}
|
|
11248
|
+
async prependChangelogEntry(params) {
|
|
11249
|
+
const { version, entry } = params;
|
|
11250
|
+
const changelogPath = (0, import_path4.join)(this.outputDir, "changelog.md");
|
|
11251
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
11252
|
+
const header = `## [${version}] - ${now}
|
|
11253
|
+
`;
|
|
11254
|
+
const newBlock = `${header}${entry.trim()}
|
|
11255
|
+
|
|
11256
|
+
`;
|
|
11257
|
+
let existing = "";
|
|
11258
|
+
if ((0, import_fs3.existsSync)(changelogPath)) {
|
|
11259
|
+
existing = await (0, import_promises3.readFile)(changelogPath, "utf-8");
|
|
11260
|
+
}
|
|
11261
|
+
let output;
|
|
11262
|
+
if (existing.trim().length === 0) {
|
|
11263
|
+
output = `# Changelog
|
|
11264
|
+
|
|
11265
|
+
${newBlock}`;
|
|
11266
|
+
} else if (existing.startsWith("# Changelog")) {
|
|
11267
|
+
const newlineIdx = existing.indexOf("\n");
|
|
11268
|
+
const headerLine = newlineIdx >= 0 ? existing.slice(0, newlineIdx) : existing;
|
|
11269
|
+
const remainder = (newlineIdx >= 0 ? existing.slice(newlineIdx + 1) : "").replace(/^\s*\n/, "");
|
|
11270
|
+
output = `${headerLine}
|
|
11271
|
+
|
|
11272
|
+
${newBlock}${remainder}`;
|
|
11273
|
+
} else {
|
|
11274
|
+
output = `${newBlock}${existing}`;
|
|
11275
|
+
}
|
|
11276
|
+
await (0, import_promises3.writeFile)(changelogPath, output, "utf-8");
|
|
11277
|
+
}
|
|
11278
|
+
brandMessage(message) {
|
|
11279
|
+
if (this.config.isWhitelabel) {
|
|
11280
|
+
return message;
|
|
11281
|
+
}
|
|
11282
|
+
if (message.includes("\u{1F33F} Generated with Fern")) {
|
|
11283
|
+
return message;
|
|
11284
|
+
}
|
|
11285
|
+
return `${message.trimEnd()}${FERN_TRAILER}`;
|
|
11286
|
+
}
|
|
11287
|
+
async analyzeSingle(cleanedDiff, language, previousVersion) {
|
|
11288
|
+
const { client, VersionBump: VersionBump2 } = await this.loadBaml();
|
|
11289
|
+
const result = await client.AnalyzeSdkDiff(
|
|
11290
|
+
cleanedDiff,
|
|
11291
|
+
language,
|
|
11292
|
+
previousVersion,
|
|
11293
|
+
this.config.priorChangelog ?? "",
|
|
11294
|
+
this.config.specCommitMessage ?? ""
|
|
11295
|
+
);
|
|
11296
|
+
if (result.version_bump === VersionBump2.NO_CHANGE) {
|
|
11297
|
+
return null;
|
|
11298
|
+
}
|
|
11299
|
+
return {
|
|
11300
|
+
versionBump: result.version_bump,
|
|
11301
|
+
message: result.message,
|
|
11302
|
+
changelogEntry: result.changelog_entry || void 0,
|
|
11303
|
+
versionBumpReason: result.version_bump_reason || void 0
|
|
11304
|
+
};
|
|
11305
|
+
}
|
|
11306
|
+
async analyzeChunks(chunks, language, previousVersion) {
|
|
11307
|
+
const { client, VersionBump: VersionBump2 } = await this.loadBaml();
|
|
11308
|
+
let bestBump = VersionBump2.NO_CHANGE;
|
|
11309
|
+
let bestMessage = "";
|
|
11310
|
+
let bestVersionBumpReason;
|
|
11311
|
+
const changelogEntries = [];
|
|
11312
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
11313
|
+
const chunk = chunks[i];
|
|
11314
|
+
if (!chunk) {
|
|
11315
|
+
continue;
|
|
11316
|
+
}
|
|
11317
|
+
const analysis = await client.AnalyzeSdkDiff(
|
|
11318
|
+
chunk,
|
|
11319
|
+
language,
|
|
11320
|
+
previousVersion,
|
|
11321
|
+
this.config.priorChangelog ?? "",
|
|
11322
|
+
this.config.specCommitMessage ?? ""
|
|
11323
|
+
);
|
|
11324
|
+
if (analysis.version_bump === VersionBump2.NO_CHANGE) {
|
|
11325
|
+
continue;
|
|
11326
|
+
}
|
|
11327
|
+
const prev = bestBump;
|
|
11328
|
+
bestBump = maxVersionBump(bestBump, analysis.version_bump);
|
|
11329
|
+
if (bestBump !== prev) {
|
|
11330
|
+
bestMessage = analysis.message;
|
|
11331
|
+
bestVersionBumpReason = analysis.version_bump_reason;
|
|
11332
|
+
}
|
|
11333
|
+
const entry = analysis.changelog_entry?.trim();
|
|
11334
|
+
if (entry) {
|
|
11335
|
+
changelogEntries.push(entry);
|
|
11336
|
+
}
|
|
11337
|
+
}
|
|
11338
|
+
if (bestBump === VersionBump2.NO_CHANGE) {
|
|
11339
|
+
return null;
|
|
11340
|
+
}
|
|
11341
|
+
if (changelogEntries.length <= 1) {
|
|
11342
|
+
return {
|
|
11343
|
+
versionBump: bestBump,
|
|
11344
|
+
message: bestMessage,
|
|
11345
|
+
changelogEntry: changelogEntries[0],
|
|
11346
|
+
versionBumpReason: bestVersionBumpReason
|
|
11347
|
+
};
|
|
11348
|
+
}
|
|
11349
|
+
try {
|
|
11350
|
+
const consolidated = await client.ConsolidateChangelog(
|
|
11351
|
+
changelogEntries.join("\n\n"),
|
|
11352
|
+
bestBump,
|
|
11353
|
+
language,
|
|
11354
|
+
previousVersion,
|
|
11355
|
+
incrementVersion(previousVersion, bestBump)
|
|
11356
|
+
);
|
|
11357
|
+
return {
|
|
11358
|
+
versionBump: bestBump,
|
|
11359
|
+
message: bestMessage,
|
|
11360
|
+
changelogEntry: consolidated.consolidated_changelog,
|
|
11361
|
+
prDescription: consolidated.pr_description || void 0,
|
|
11362
|
+
versionBumpReason: consolidated.version_bump_reason || bestVersionBumpReason
|
|
11363
|
+
};
|
|
11364
|
+
} catch (error) {
|
|
11365
|
+
this.logger.warn(`AutoVersionStep: ConsolidateChangelog failed (${String(error)}); using joined entries.`);
|
|
11366
|
+
return {
|
|
11367
|
+
versionBump: bestBump,
|
|
11368
|
+
message: bestMessage,
|
|
11369
|
+
changelogEntry: changelogEntries.join("\n\n"),
|
|
11370
|
+
versionBumpReason: bestVersionBumpReason
|
|
11371
|
+
};
|
|
11372
|
+
}
|
|
11373
|
+
}
|
|
11374
|
+
/**
|
|
11375
|
+
* Dynamically imports @fern-api/cli-ai only when autoversion actually needs to
|
|
11376
|
+
* call FAI. The package is ESM-only with extensionless internal imports, so
|
|
11377
|
+
* unbundled CJS consumers (like generator-cli's `bin/cli` used by the README /
|
|
11378
|
+
* reference commands) would fail at load time with a static import — but those
|
|
11379
|
+
* commands never reach this method, so dynamic import keeps them unaffected.
|
|
11380
|
+
*/
|
|
11381
|
+
async loadBaml() {
|
|
11382
|
+
if (this.config.ai == null) {
|
|
11383
|
+
throw new Error("AutoVersionStep: ai config is missing. Set autoVersion.ai to a BAML provider+model pair.");
|
|
11384
|
+
}
|
|
11385
|
+
const cliAi = await import("@fern-api/cli-ai");
|
|
11386
|
+
const registry = cliAi.configureBamlClient(this.config.ai);
|
|
11387
|
+
return {
|
|
11388
|
+
client: cliAi.b.withOptions({ clientRegistry: registry }),
|
|
11389
|
+
VersionBump: cliAi.VersionBump
|
|
11390
|
+
};
|
|
11391
|
+
}
|
|
11392
|
+
/**
|
|
11393
|
+
* AutoVersioningService was built against the CLI's TaskContext logger — a richer
|
|
11394
|
+
* shape with `trace`, `log`, `enable`, `disable`. Our pipeline only has the four
|
|
11395
|
+
* core levels; stub the rest as no-ops and adapt the method signatures. Cast to
|
|
11396
|
+
* the service's expected Logger type because the surface is structurally wider
|
|
11397
|
+
* than what PipelineLogger exposes.
|
|
11398
|
+
*/
|
|
11399
|
+
toTaskLogger() {
|
|
11400
|
+
const noop = () => void 0;
|
|
11401
|
+
const adapter = {
|
|
11402
|
+
debug: (msg) => this.logger.debug(msg),
|
|
11403
|
+
info: (msg) => this.logger.info(msg),
|
|
11404
|
+
warn: (msg) => this.logger.warn(msg),
|
|
11405
|
+
error: (msg) => this.logger.error(msg),
|
|
11406
|
+
trace: (msg) => this.logger.debug(msg),
|
|
11407
|
+
log: (msg) => this.logger.info(msg),
|
|
11408
|
+
enable: noop,
|
|
11409
|
+
disable: noop
|
|
11410
|
+
};
|
|
11411
|
+
return adapter;
|
|
11412
|
+
}
|
|
11413
|
+
};
|
|
11414
|
+
|
|
11415
|
+
// src/replay/replay-run.ts
|
|
11416
|
+
var import_replay = require("@fern-api/replay");
|
|
11417
|
+
var import_child_process4 = require("child_process");
|
|
11418
|
+
var import_fs5 = require("fs");
|
|
11419
|
+
var import_path6 = require("path");
|
|
11420
|
+
|
|
11421
|
+
// src/replay/fernignore.ts
|
|
11422
|
+
var import_fs4 = require("fs");
|
|
11423
|
+
var import_promises4 = require("fs/promises");
|
|
11424
|
+
var import_path5 = require("path");
|
|
11425
|
+
var REPLAY_FERNIGNORE_ENTRIES = [".fern/replay.lock", ".fern/replay.yml", ".gitattributes"];
|
|
11426
|
+
var GITATTRIBUTES_ENTRIES = [".fern/replay.lock linguist-generated=true"];
|
|
11427
|
+
async function ensureReplayFernignoreEntries(outputDir) {
|
|
11428
|
+
const fernignorePath = (0, import_path5.join)(outputDir, ".fernignore");
|
|
11429
|
+
let content = "";
|
|
11430
|
+
try {
|
|
11431
|
+
content = await (0, import_promises4.readFile)(fernignorePath, "utf-8");
|
|
11432
|
+
} catch {
|
|
11433
|
+
}
|
|
11434
|
+
const lines = content.split("\n");
|
|
11435
|
+
const toAdd = REPLAY_FERNIGNORE_ENTRIES.filter((entry) => !lines.some((line) => line.trim() === entry));
|
|
11436
|
+
if (toAdd.length > 0) {
|
|
11437
|
+
const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
11438
|
+
await (0, import_promises4.writeFile)(fernignorePath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
|
|
11439
|
+
return true;
|
|
11440
|
+
}
|
|
11441
|
+
return false;
|
|
11442
|
+
}
|
|
11443
|
+
async function ensureGitattributesEntries(outputDir) {
|
|
11444
|
+
const gitattributesPath = (0, import_path5.join)(outputDir, ".gitattributes");
|
|
11445
|
+
let content = "";
|
|
11446
|
+
try {
|
|
11447
|
+
content = await (0, import_promises4.readFile)(gitattributesPath, "utf-8");
|
|
11448
|
+
} catch {
|
|
11449
|
+
}
|
|
11450
|
+
const lines = content.split("\n");
|
|
11451
|
+
const toAdd = GITATTRIBUTES_ENTRIES.filter((entry) => !lines.some((line) => line.trim() === entry));
|
|
11452
|
+
if (toAdd.length > 0) {
|
|
11453
|
+
const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
11454
|
+
await (0, import_promises4.writeFile)(gitattributesPath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
|
|
11455
|
+
}
|
|
11456
|
+
}
|
|
11457
|
+
function ensureReplayFernignoreEntriesSync(outputDir) {
|
|
11458
|
+
const fernignorePath = (0, import_path5.join)(outputDir, ".fernignore");
|
|
11459
|
+
let content = "";
|
|
11460
|
+
if ((0, import_fs4.existsSync)(fernignorePath)) {
|
|
11461
|
+
content = (0, import_fs4.readFileSync)(fernignorePath, "utf-8");
|
|
11462
|
+
}
|
|
11463
|
+
const lines = content.split("\n");
|
|
11464
|
+
const toAdd = REPLAY_FERNIGNORE_ENTRIES.filter((entry) => !lines.some((line) => line.trim() === entry));
|
|
11465
|
+
if (toAdd.length > 0) {
|
|
11466
|
+
const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
11467
|
+
(0, import_fs4.writeFileSync)(fernignorePath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
|
|
11468
|
+
}
|
|
11469
|
+
}
|
|
11470
|
+
function ensureGitattributesEntriesSync(outputDir) {
|
|
11471
|
+
const gitattributesPath = (0, import_path5.join)(outputDir, ".gitattributes");
|
|
11472
|
+
let content = "";
|
|
11473
|
+
if ((0, import_fs4.existsSync)(gitattributesPath)) {
|
|
11474
|
+
content = (0, import_fs4.readFileSync)(gitattributesPath, "utf-8");
|
|
11475
|
+
}
|
|
11476
|
+
const lines = content.split("\n");
|
|
11477
|
+
const toAdd = GITATTRIBUTES_ENTRIES.filter((entry) => !lines.some((line) => line.trim() === entry));
|
|
11478
|
+
if (toAdd.length > 0) {
|
|
11479
|
+
const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
11480
|
+
(0, import_fs4.writeFileSync)(gitattributesPath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
|
|
11481
|
+
}
|
|
11482
|
+
}
|
|
11483
|
+
|
|
11484
|
+
// src/replay/replay-run.ts
|
|
11485
|
+
async function replayPrepare(params) {
|
|
11486
|
+
const {
|
|
11487
|
+
outputDir,
|
|
11488
|
+
cliVersion,
|
|
11489
|
+
generatorVersions,
|
|
11490
|
+
stageOnly = false,
|
|
11491
|
+
generatorName,
|
|
11492
|
+
skipApplication,
|
|
11493
|
+
logger
|
|
11494
|
+
} = params;
|
|
11495
|
+
const lockfilePath = (0, import_path6.join)(outputDir, ".fern", "replay.lock");
|
|
11496
|
+
if (!(0, import_fs5.existsSync)(lockfilePath)) {
|
|
11497
|
+
return null;
|
|
11498
|
+
}
|
|
11499
|
+
const baseBranchHead = gitRevParse(outputDir, "HEAD");
|
|
11500
|
+
let previousGenerationSha = null;
|
|
11501
|
+
let prevBaseBranchHead = null;
|
|
11502
|
+
try {
|
|
11503
|
+
const lockManager = new import_replay.LockfileManager(outputDir);
|
|
11504
|
+
if (lockManager.exists()) {
|
|
11505
|
+
const lock = lockManager.read();
|
|
11506
|
+
previousGenerationSha = lock.current_generation;
|
|
11507
|
+
const latestGen = lock.generations.find((g2) => g2.commit_sha === lock.current_generation);
|
|
11508
|
+
prevBaseBranchHead = latestGen?.base_branch_head ?? null;
|
|
11509
|
+
}
|
|
11510
|
+
} catch {
|
|
11511
|
+
}
|
|
11512
|
+
if (previousGenerationSha != null) {
|
|
11513
|
+
const sanitizedName = generatorName?.replace(/\//g, "--");
|
|
11514
|
+
const namespacedTag = sanitizedName != null ? `fern-generation-base--${sanitizedName}` : null;
|
|
11515
|
+
const tagSha = (namespacedTag != null ? gitRevParse(outputDir, namespacedTag) : null) ?? gitRevParse(outputDir, "fern-generation-base");
|
|
11516
|
+
let shouldSync = false;
|
|
11517
|
+
if (tagSha != null) {
|
|
11518
|
+
const tagParent = gitRevParse(outputDir, `${tagSha}^`);
|
|
11519
|
+
if (tagParent === prevBaseBranchHead || tagParent === previousGenerationSha) {
|
|
11520
|
+
const tagDistance = gitDiffNameOnly(outputDir, tagSha, "HEAD").length;
|
|
11521
|
+
const lockDistance = gitDiffNameOnly(
|
|
11522
|
+
outputDir,
|
|
11523
|
+
prevBaseBranchHead ?? previousGenerationSha,
|
|
11524
|
+
"HEAD"
|
|
11525
|
+
).length;
|
|
11526
|
+
shouldSync = tagDistance < lockDistance;
|
|
11527
|
+
}
|
|
11528
|
+
}
|
|
11529
|
+
if (shouldSync && tagSha != null) {
|
|
11530
|
+
if (!isTagMergedIntoHead(outputDir, tagSha)) {
|
|
11531
|
+
logger?.warn(
|
|
11532
|
+
`fern-generation-base tag ${tagSha} is not reachable from HEAD \u2014 skipping divergent-merge sync. The tag likely points at an unmerged generation (PR closed without merge).`
|
|
11533
|
+
);
|
|
11534
|
+
} else {
|
|
11535
|
+
const syncService = new import_replay.ReplayService(outputDir, { enabled: true });
|
|
11536
|
+
await syncService.syncFromDivergentMerge(tagSha, {
|
|
11537
|
+
cliVersion,
|
|
11538
|
+
generatorVersions,
|
|
11539
|
+
baseBranchHead: baseBranchHead ?? void 0
|
|
11540
|
+
});
|
|
11541
|
+
try {
|
|
11542
|
+
const freshLockManager = new import_replay.LockfileManager(outputDir);
|
|
11543
|
+
if (freshLockManager.exists()) {
|
|
11544
|
+
previousGenerationSha = freshLockManager.read().current_generation;
|
|
11545
|
+
}
|
|
11546
|
+
} catch {
|
|
11547
|
+
}
|
|
11548
|
+
}
|
|
11549
|
+
}
|
|
11550
|
+
}
|
|
11551
|
+
const service = new import_replay.ReplayService(outputDir, { enabled: true });
|
|
11552
|
+
let preparation;
|
|
11553
|
+
try {
|
|
11554
|
+
preparation = await service.prepareReplay({
|
|
11555
|
+
cliVersion,
|
|
11556
|
+
generatorVersions,
|
|
11557
|
+
stageOnly,
|
|
11558
|
+
skipApplication,
|
|
11559
|
+
baseBranchHead: baseBranchHead ?? void 0
|
|
11560
|
+
});
|
|
11561
|
+
} catch (error) {
|
|
11562
|
+
logger?.warn("Replay failed, continuing without replay: " + String(error));
|
|
11563
|
+
return null;
|
|
11564
|
+
}
|
|
11565
|
+
return {
|
|
11566
|
+
_service: service,
|
|
11567
|
+
_preparation: preparation,
|
|
11568
|
+
outputDir,
|
|
11569
|
+
flow: preparation.flow,
|
|
11570
|
+
previousGenerationSha,
|
|
11571
|
+
currentGenerationSha: preparation.currentGenerationSha,
|
|
11572
|
+
baseBranchHead
|
|
11573
|
+
};
|
|
11574
|
+
}
|
|
11575
|
+
async function replayApply(prepared, params = {}) {
|
|
11576
|
+
const { stageOnly = false, logger } = params;
|
|
11577
|
+
let report;
|
|
11578
|
+
try {
|
|
11579
|
+
report = await prepared._service.applyPreparedReplay(prepared._preparation, { stageOnly });
|
|
11580
|
+
} catch (error) {
|
|
11581
|
+
logger?.warn("Replay failed, continuing without replay: " + String(error));
|
|
11582
|
+
return {
|
|
11583
|
+
report: null,
|
|
11584
|
+
fernignoreUpdated: false,
|
|
11585
|
+
previousGenerationSha: prepared.previousGenerationSha,
|
|
11586
|
+
currentGenerationSha: prepared.currentGenerationSha,
|
|
11587
|
+
baseBranchHead: prepared.baseBranchHead
|
|
11588
|
+
};
|
|
11589
|
+
}
|
|
11590
|
+
let resolvedBaseBranchHead = prepared.baseBranchHead;
|
|
11591
|
+
try {
|
|
11592
|
+
const freshLockManager = new import_replay.LockfileManager(prepared.outputDir);
|
|
11593
|
+
if (freshLockManager.exists()) {
|
|
11594
|
+
const freshLock = freshLockManager.read();
|
|
11595
|
+
const latestGen = freshLock.generations.find((g2) => g2.commit_sha === freshLock.current_generation);
|
|
11596
|
+
if (latestGen?.base_branch_head) {
|
|
11597
|
+
resolvedBaseBranchHead = latestGen.base_branch_head;
|
|
11598
|
+
}
|
|
11599
|
+
}
|
|
11600
|
+
} catch {
|
|
11601
|
+
}
|
|
11602
|
+
const fernignoreUpdated = await ensureReplayFernignoreEntries(prepared.outputDir);
|
|
11603
|
+
await ensureGitattributesEntries(prepared.outputDir);
|
|
11604
|
+
return {
|
|
11605
|
+
report,
|
|
11606
|
+
fernignoreUpdated,
|
|
11607
|
+
previousGenerationSha: prepared.previousGenerationSha,
|
|
11608
|
+
currentGenerationSha: prepared.currentGenerationSha,
|
|
11609
|
+
baseBranchHead: resolvedBaseBranchHead
|
|
11610
|
+
};
|
|
11611
|
+
}
|
|
11612
|
+
async function replayRun(params) {
|
|
11613
|
+
const prepared = await replayPrepare(params);
|
|
11614
|
+
if (prepared == null) {
|
|
11615
|
+
return {
|
|
11616
|
+
report: null,
|
|
11617
|
+
fernignoreUpdated: false,
|
|
11618
|
+
previousGenerationSha: null,
|
|
11619
|
+
currentGenerationSha: null,
|
|
11620
|
+
baseBranchHead: null
|
|
11621
|
+
};
|
|
11622
|
+
}
|
|
11623
|
+
return replayApply(prepared, { stageOnly: params.stageOnly, logger: params.logger });
|
|
11624
|
+
}
|
|
11625
|
+
function isTagMergedIntoHead(cwd2, tagSha) {
|
|
11626
|
+
try {
|
|
11627
|
+
(0, import_child_process4.execFileSync)("git", ["merge-base", "--is-ancestor", tagSha, "HEAD"], {
|
|
11628
|
+
cwd: cwd2,
|
|
11629
|
+
stdio: "pipe"
|
|
11630
|
+
});
|
|
11631
|
+
return true;
|
|
11632
|
+
} catch {
|
|
11633
|
+
}
|
|
11634
|
+
const tagTree = gitRevParse(cwd2, `${tagSha}^{tree}`);
|
|
11635
|
+
if (tagTree == null) {
|
|
11636
|
+
return false;
|
|
11637
|
+
}
|
|
11638
|
+
try {
|
|
11639
|
+
const trees = (0, import_child_process4.execFileSync)("git", ["log", "HEAD", "--format=%T", "--max-count=500"], {
|
|
11640
|
+
cwd: cwd2,
|
|
11641
|
+
encoding: "utf-8",
|
|
11642
|
+
stdio: "pipe"
|
|
11643
|
+
});
|
|
11644
|
+
return trees.split("\n").some((t2) => t2.trim() === tagTree);
|
|
11645
|
+
} catch {
|
|
11646
|
+
return false;
|
|
11647
|
+
}
|
|
11648
|
+
}
|
|
11649
|
+
function gitRevParse(cwd2, rev) {
|
|
11650
|
+
try {
|
|
11651
|
+
return (0, import_child_process4.execFileSync)("git", ["rev-parse", "--verify", rev], {
|
|
11652
|
+
cwd: cwd2,
|
|
11653
|
+
encoding: "utf-8",
|
|
11654
|
+
stdio: "pipe"
|
|
11655
|
+
}).trim();
|
|
11656
|
+
} catch {
|
|
11657
|
+
return null;
|
|
11658
|
+
}
|
|
11659
|
+
}
|
|
11660
|
+
function gitDiffNameOnly(cwd2, from, to) {
|
|
11661
|
+
try {
|
|
11662
|
+
const output = (0, import_child_process4.execFileSync)("git", ["diff", "--name-only", from, to], {
|
|
11663
|
+
cwd: cwd2,
|
|
11664
|
+
encoding: "utf-8",
|
|
11665
|
+
stdio: "pipe"
|
|
11666
|
+
});
|
|
11667
|
+
return output.trim().split("\n").filter(Boolean);
|
|
11668
|
+
} catch {
|
|
11669
|
+
return [];
|
|
11670
|
+
}
|
|
11671
|
+
}
|
|
11672
|
+
|
|
11673
|
+
// src/pipeline/steps/GenerationCommitStep.ts
|
|
11674
|
+
var GenerationCommitStep = class extends BaseStep {
|
|
11675
|
+
constructor(outputDir, logger, config, cliVersion, generatorVersions, generatorName) {
|
|
10083
11676
|
super(outputDir, logger);
|
|
10084
11677
|
this.config = config;
|
|
11678
|
+
this.cliVersion = cliVersion;
|
|
11679
|
+
this.generatorVersions = generatorVersions;
|
|
11680
|
+
this.generatorName = generatorName;
|
|
10085
11681
|
}
|
|
10086
11682
|
config;
|
|
10087
|
-
|
|
11683
|
+
cliVersion;
|
|
11684
|
+
generatorVersions;
|
|
11685
|
+
generatorName;
|
|
11686
|
+
name = "generationCommit";
|
|
10088
11687
|
async execute(_context) {
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
11688
|
+
const prepared = await replayPrepare({
|
|
11689
|
+
outputDir: this.outputDir,
|
|
11690
|
+
cliVersion: this.cliVersion,
|
|
11691
|
+
generatorVersions: this.generatorVersions,
|
|
11692
|
+
generatorName: this.generatorName,
|
|
11693
|
+
skipApplication: this.config.skipApplication,
|
|
11694
|
+
logger: this.logger
|
|
11695
|
+
});
|
|
11696
|
+
if (prepared == null) {
|
|
11697
|
+
return {
|
|
11698
|
+
executed: true,
|
|
11699
|
+
success: true,
|
|
11700
|
+
preparedReplay: null
|
|
11701
|
+
};
|
|
11702
|
+
}
|
|
10092
11703
|
return {
|
|
10093
11704
|
executed: true,
|
|
10094
|
-
success: true
|
|
11705
|
+
success: true,
|
|
11706
|
+
preparedReplay: prepared,
|
|
11707
|
+
previousGenerationSha: prepared.previousGenerationSha ?? void 0,
|
|
11708
|
+
currentGenerationSha: prepared.currentGenerationSha,
|
|
11709
|
+
baseBranchHead: prepared.baseBranchHead ?? void 0,
|
|
11710
|
+
flow: prepared.flow
|
|
10095
11711
|
};
|
|
10096
11712
|
}
|
|
10097
11713
|
};
|
|
10098
11714
|
|
|
10099
11715
|
// src/pipeline/steps/GithubStep.ts
|
|
10100
11716
|
var import_rest2 = require("@octokit/rest");
|
|
10101
|
-
var
|
|
10102
|
-
var
|
|
11717
|
+
var import_promises5 = require("fs/promises");
|
|
11718
|
+
var import_path7 = require("path");
|
|
10103
11719
|
|
|
10104
11720
|
// src/pipeline/github/createReplayBranch.ts
|
|
10105
11721
|
async function createReplayBranch(repository, branchName, commitMessage, replayConflictInfo, logger) {
|
|
@@ -10745,13 +12361,13 @@ var GithubStep = class extends BaseStep {
|
|
|
10745
12361
|
}
|
|
10746
12362
|
async ensureFernignore() {
|
|
10747
12363
|
this.logger.debug("Checking for .fernignore file...");
|
|
10748
|
-
const fernignorePath = (0,
|
|
12364
|
+
const fernignorePath = (0, import_path7.join)(this.outputDir, ".fernignore");
|
|
10749
12365
|
try {
|
|
10750
|
-
await (0,
|
|
12366
|
+
await (0, import_promises5.access)(fernignorePath);
|
|
10751
12367
|
this.logger.debug(".fernignore already exists");
|
|
10752
12368
|
} catch {
|
|
10753
12369
|
this.logger.debug("Creating .fernignore file...");
|
|
10754
|
-
await (0,
|
|
12370
|
+
await (0, import_promises5.writeFile)(fernignorePath, "# Specify files that shouldn't be modified by Fern\n", "utf-8");
|
|
10755
12371
|
}
|
|
10756
12372
|
}
|
|
10757
12373
|
deriveSkipCommit(replayResult) {
|
|
@@ -10818,242 +12434,6 @@ function resolveBranchAction({
|
|
|
10818
12434
|
return "create-from-head";
|
|
10819
12435
|
}
|
|
10820
12436
|
|
|
10821
|
-
// src/replay/replay-run.ts
|
|
10822
|
-
var import_replay = require("@fern-api/replay");
|
|
10823
|
-
var import_child_process3 = require("child_process");
|
|
10824
|
-
var import_fs3 = require("fs");
|
|
10825
|
-
var import_path5 = require("path");
|
|
10826
|
-
|
|
10827
|
-
// src/replay/fernignore.ts
|
|
10828
|
-
var import_fs2 = require("fs");
|
|
10829
|
-
var import_promises3 = require("fs/promises");
|
|
10830
|
-
var import_path4 = require("path");
|
|
10831
|
-
var REPLAY_FERNIGNORE_ENTRIES = [".fern/replay.lock", ".fern/replay.yml", ".gitattributes"];
|
|
10832
|
-
var GITATTRIBUTES_ENTRIES = [".fern/replay.lock linguist-generated=true"];
|
|
10833
|
-
async function ensureReplayFernignoreEntries(outputDir) {
|
|
10834
|
-
const fernignorePath = (0, import_path4.join)(outputDir, ".fernignore");
|
|
10835
|
-
let content = "";
|
|
10836
|
-
try {
|
|
10837
|
-
content = await (0, import_promises3.readFile)(fernignorePath, "utf-8");
|
|
10838
|
-
} catch {
|
|
10839
|
-
}
|
|
10840
|
-
const lines = content.split("\n");
|
|
10841
|
-
const toAdd = REPLAY_FERNIGNORE_ENTRIES.filter((entry) => !lines.some((line) => line.trim() === entry));
|
|
10842
|
-
if (toAdd.length > 0) {
|
|
10843
|
-
const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
10844
|
-
await (0, import_promises3.writeFile)(fernignorePath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
|
|
10845
|
-
return true;
|
|
10846
|
-
}
|
|
10847
|
-
return false;
|
|
10848
|
-
}
|
|
10849
|
-
async function ensureGitattributesEntries(outputDir) {
|
|
10850
|
-
const gitattributesPath = (0, import_path4.join)(outputDir, ".gitattributes");
|
|
10851
|
-
let content = "";
|
|
10852
|
-
try {
|
|
10853
|
-
content = await (0, import_promises3.readFile)(gitattributesPath, "utf-8");
|
|
10854
|
-
} catch {
|
|
10855
|
-
}
|
|
10856
|
-
const lines = content.split("\n");
|
|
10857
|
-
const toAdd = GITATTRIBUTES_ENTRIES.filter((entry) => !lines.some((line) => line.trim() === entry));
|
|
10858
|
-
if (toAdd.length > 0) {
|
|
10859
|
-
const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
10860
|
-
await (0, import_promises3.writeFile)(gitattributesPath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
|
|
10861
|
-
}
|
|
10862
|
-
}
|
|
10863
|
-
function ensureReplayFernignoreEntriesSync(outputDir) {
|
|
10864
|
-
const fernignorePath = (0, import_path4.join)(outputDir, ".fernignore");
|
|
10865
|
-
let content = "";
|
|
10866
|
-
if ((0, import_fs2.existsSync)(fernignorePath)) {
|
|
10867
|
-
content = (0, import_fs2.readFileSync)(fernignorePath, "utf-8");
|
|
10868
|
-
}
|
|
10869
|
-
const lines = content.split("\n");
|
|
10870
|
-
const toAdd = REPLAY_FERNIGNORE_ENTRIES.filter((entry) => !lines.some((line) => line.trim() === entry));
|
|
10871
|
-
if (toAdd.length > 0) {
|
|
10872
|
-
const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
10873
|
-
(0, import_fs2.writeFileSync)(fernignorePath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
|
|
10874
|
-
}
|
|
10875
|
-
}
|
|
10876
|
-
function ensureGitattributesEntriesSync(outputDir) {
|
|
10877
|
-
const gitattributesPath = (0, import_path4.join)(outputDir, ".gitattributes");
|
|
10878
|
-
let content = "";
|
|
10879
|
-
if ((0, import_fs2.existsSync)(gitattributesPath)) {
|
|
10880
|
-
content = (0, import_fs2.readFileSync)(gitattributesPath, "utf-8");
|
|
10881
|
-
}
|
|
10882
|
-
const lines = content.split("\n");
|
|
10883
|
-
const toAdd = GITATTRIBUTES_ENTRIES.filter((entry) => !lines.some((line) => line.trim() === entry));
|
|
10884
|
-
if (toAdd.length > 0) {
|
|
10885
|
-
const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
10886
|
-
(0, import_fs2.writeFileSync)(gitattributesPath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
|
|
10887
|
-
}
|
|
10888
|
-
}
|
|
10889
|
-
|
|
10890
|
-
// src/replay/replay-run.ts
|
|
10891
|
-
async function replayRun(params) {
|
|
10892
|
-
const {
|
|
10893
|
-
outputDir,
|
|
10894
|
-
cliVersion,
|
|
10895
|
-
generatorVersions,
|
|
10896
|
-
stageOnly = false,
|
|
10897
|
-
generatorName,
|
|
10898
|
-
skipApplication,
|
|
10899
|
-
logger
|
|
10900
|
-
} = params;
|
|
10901
|
-
const lockfilePath = (0, import_path5.join)(outputDir, ".fern", "replay.lock");
|
|
10902
|
-
if (!(0, import_fs3.existsSync)(lockfilePath)) {
|
|
10903
|
-
return {
|
|
10904
|
-
report: null,
|
|
10905
|
-
fernignoreUpdated: false,
|
|
10906
|
-
previousGenerationSha: null,
|
|
10907
|
-
currentGenerationSha: null,
|
|
10908
|
-
baseBranchHead: null
|
|
10909
|
-
};
|
|
10910
|
-
}
|
|
10911
|
-
const baseBranchHead = gitRevParse(outputDir, "HEAD");
|
|
10912
|
-
let previousGenerationSha = null;
|
|
10913
|
-
let prevBaseBranchHead = null;
|
|
10914
|
-
try {
|
|
10915
|
-
const lockManager = new import_replay.LockfileManager(outputDir);
|
|
10916
|
-
if (lockManager.exists()) {
|
|
10917
|
-
const lock = lockManager.read();
|
|
10918
|
-
previousGenerationSha = lock.current_generation;
|
|
10919
|
-
const latestGen = lock.generations.find((g2) => g2.commit_sha === lock.current_generation);
|
|
10920
|
-
prevBaseBranchHead = latestGen?.base_branch_head ?? null;
|
|
10921
|
-
}
|
|
10922
|
-
} catch {
|
|
10923
|
-
}
|
|
10924
|
-
if (previousGenerationSha != null) {
|
|
10925
|
-
const sanitizedName = generatorName?.replace(/\//g, "--");
|
|
10926
|
-
const namespacedTag = sanitizedName != null ? `fern-generation-base--${sanitizedName}` : null;
|
|
10927
|
-
const tagSha = (namespacedTag != null ? gitRevParse(outputDir, namespacedTag) : null) ?? gitRevParse(outputDir, "fern-generation-base");
|
|
10928
|
-
let shouldSync = false;
|
|
10929
|
-
if (tagSha != null) {
|
|
10930
|
-
const tagParent = gitRevParse(outputDir, `${tagSha}^`);
|
|
10931
|
-
if (tagParent === prevBaseBranchHead || tagParent === previousGenerationSha) {
|
|
10932
|
-
const tagDistance = gitDiffNameOnly(outputDir, tagSha, "HEAD").length;
|
|
10933
|
-
const lockDistance = gitDiffNameOnly(
|
|
10934
|
-
outputDir,
|
|
10935
|
-
prevBaseBranchHead ?? previousGenerationSha,
|
|
10936
|
-
"HEAD"
|
|
10937
|
-
).length;
|
|
10938
|
-
shouldSync = tagDistance < lockDistance;
|
|
10939
|
-
}
|
|
10940
|
-
}
|
|
10941
|
-
if (shouldSync && tagSha != null) {
|
|
10942
|
-
if (!isTagMergedIntoHead(outputDir, tagSha)) {
|
|
10943
|
-
logger?.warn(
|
|
10944
|
-
`fern-generation-base tag ${tagSha} is not reachable from HEAD \u2014 skipping divergent-merge sync. The tag likely points at an unmerged generation (PR closed without merge).`
|
|
10945
|
-
);
|
|
10946
|
-
} else {
|
|
10947
|
-
const syncService = new import_replay.ReplayService(outputDir, { enabled: true });
|
|
10948
|
-
await syncService.syncFromDivergentMerge(tagSha, {
|
|
10949
|
-
cliVersion,
|
|
10950
|
-
generatorVersions,
|
|
10951
|
-
baseBranchHead: baseBranchHead ?? void 0
|
|
10952
|
-
});
|
|
10953
|
-
try {
|
|
10954
|
-
const freshLockManager = new import_replay.LockfileManager(outputDir);
|
|
10955
|
-
if (freshLockManager.exists()) {
|
|
10956
|
-
previousGenerationSha = freshLockManager.read().current_generation;
|
|
10957
|
-
}
|
|
10958
|
-
} catch {
|
|
10959
|
-
}
|
|
10960
|
-
}
|
|
10961
|
-
}
|
|
10962
|
-
}
|
|
10963
|
-
const service = new import_replay.ReplayService(outputDir, {
|
|
10964
|
-
enabled: true
|
|
10965
|
-
});
|
|
10966
|
-
let report;
|
|
10967
|
-
try {
|
|
10968
|
-
report = await service.runReplay({
|
|
10969
|
-
cliVersion,
|
|
10970
|
-
generatorVersions,
|
|
10971
|
-
stageOnly,
|
|
10972
|
-
skipApplication,
|
|
10973
|
-
baseBranchHead: baseBranchHead ?? void 0
|
|
10974
|
-
});
|
|
10975
|
-
} catch (error) {
|
|
10976
|
-
logger?.warn("Replay failed, continuing without replay: " + String(error));
|
|
10977
|
-
return {
|
|
10978
|
-
report: null,
|
|
10979
|
-
fernignoreUpdated: false,
|
|
10980
|
-
previousGenerationSha,
|
|
10981
|
-
currentGenerationSha: null,
|
|
10982
|
-
baseBranchHead
|
|
10983
|
-
};
|
|
10984
|
-
}
|
|
10985
|
-
let currentGenerationSha = null;
|
|
10986
|
-
let resolvedBaseBranchHead = baseBranchHead;
|
|
10987
|
-
try {
|
|
10988
|
-
const freshLockManager = new import_replay.LockfileManager(outputDir);
|
|
10989
|
-
if (freshLockManager.exists()) {
|
|
10990
|
-
const freshLock = freshLockManager.read();
|
|
10991
|
-
currentGenerationSha = freshLock.current_generation;
|
|
10992
|
-
const latestGen = freshLock.generations.find((g2) => g2.commit_sha === freshLock.current_generation);
|
|
10993
|
-
if (latestGen?.base_branch_head) {
|
|
10994
|
-
resolvedBaseBranchHead = latestGen.base_branch_head;
|
|
10995
|
-
}
|
|
10996
|
-
}
|
|
10997
|
-
} catch {
|
|
10998
|
-
}
|
|
10999
|
-
const fernignoreUpdated = await ensureReplayFernignoreEntries(outputDir);
|
|
11000
|
-
await ensureGitattributesEntries(outputDir);
|
|
11001
|
-
return {
|
|
11002
|
-
report,
|
|
11003
|
-
fernignoreUpdated,
|
|
11004
|
-
previousGenerationSha,
|
|
11005
|
-
currentGenerationSha,
|
|
11006
|
-
baseBranchHead: resolvedBaseBranchHead
|
|
11007
|
-
};
|
|
11008
|
-
}
|
|
11009
|
-
function isTagMergedIntoHead(cwd2, tagSha) {
|
|
11010
|
-
try {
|
|
11011
|
-
(0, import_child_process3.execFileSync)("git", ["merge-base", "--is-ancestor", tagSha, "HEAD"], {
|
|
11012
|
-
cwd: cwd2,
|
|
11013
|
-
stdio: "pipe"
|
|
11014
|
-
});
|
|
11015
|
-
return true;
|
|
11016
|
-
} catch {
|
|
11017
|
-
}
|
|
11018
|
-
const tagTree = gitRevParse(cwd2, `${tagSha}^{tree}`);
|
|
11019
|
-
if (tagTree == null) {
|
|
11020
|
-
return false;
|
|
11021
|
-
}
|
|
11022
|
-
try {
|
|
11023
|
-
const trees = (0, import_child_process3.execFileSync)("git", ["log", "HEAD", "--format=%T", "--max-count=500"], {
|
|
11024
|
-
cwd: cwd2,
|
|
11025
|
-
encoding: "utf-8",
|
|
11026
|
-
stdio: "pipe"
|
|
11027
|
-
});
|
|
11028
|
-
return trees.split("\n").some((t2) => t2.trim() === tagTree);
|
|
11029
|
-
} catch {
|
|
11030
|
-
return false;
|
|
11031
|
-
}
|
|
11032
|
-
}
|
|
11033
|
-
function gitRevParse(cwd2, rev) {
|
|
11034
|
-
try {
|
|
11035
|
-
return (0, import_child_process3.execFileSync)("git", ["rev-parse", "--verify", rev], {
|
|
11036
|
-
cwd: cwd2,
|
|
11037
|
-
encoding: "utf-8",
|
|
11038
|
-
stdio: "pipe"
|
|
11039
|
-
}).trim();
|
|
11040
|
-
} catch {
|
|
11041
|
-
return null;
|
|
11042
|
-
}
|
|
11043
|
-
}
|
|
11044
|
-
function gitDiffNameOnly(cwd2, from, to) {
|
|
11045
|
-
try {
|
|
11046
|
-
const output = (0, import_child_process3.execFileSync)("git", ["diff", "--name-only", from, to], {
|
|
11047
|
-
cwd: cwd2,
|
|
11048
|
-
encoding: "utf-8",
|
|
11049
|
-
stdio: "pipe"
|
|
11050
|
-
});
|
|
11051
|
-
return output.trim().split("\n").filter(Boolean);
|
|
11052
|
-
} catch {
|
|
11053
|
-
return [];
|
|
11054
|
-
}
|
|
11055
|
-
}
|
|
11056
|
-
|
|
11057
12437
|
// src/pipeline/steps/ReplayStep.ts
|
|
11058
12438
|
var ReplayStep = class extends BaseStep {
|
|
11059
12439
|
constructor(outputDir, logger, config, cliVersion, generatorVersions, generatorName) {
|
|
@@ -11068,8 +12448,23 @@ var ReplayStep = class extends BaseStep {
|
|
|
11068
12448
|
generatorVersions;
|
|
11069
12449
|
generatorName;
|
|
11070
12450
|
name = "replay";
|
|
11071
|
-
async execute(
|
|
11072
|
-
const
|
|
12451
|
+
async execute(context) {
|
|
12452
|
+
const generationCommit = context.previousStepResults.generationCommit;
|
|
12453
|
+
const prepared = generationCommit?.preparedReplay;
|
|
12454
|
+
if (generationCommit != null && prepared == null) {
|
|
12455
|
+
return {
|
|
12456
|
+
executed: true,
|
|
12457
|
+
success: true,
|
|
12458
|
+
flow: "first-generation",
|
|
12459
|
+
patchesDetected: 0,
|
|
12460
|
+
patchesApplied: 0,
|
|
12461
|
+
patchesWithConflicts: 0
|
|
12462
|
+
};
|
|
12463
|
+
}
|
|
12464
|
+
const result = prepared != null ? await replayApply(prepared, {
|
|
12465
|
+
stageOnly: this.config.stageOnly ?? false,
|
|
12466
|
+
logger: this.logger
|
|
12467
|
+
}) : await replayRun({
|
|
11073
12468
|
outputDir: this.outputDir,
|
|
11074
12469
|
cliVersion: this.cliVersion,
|
|
11075
12470
|
generatorVersions: this.generatorVersions,
|
|
@@ -11125,16 +12520,34 @@ var PostGenerationPipeline = class {
|
|
|
11125
12520
|
constructor(config, logger = consolePipelineLogger) {
|
|
11126
12521
|
this.config = config;
|
|
11127
12522
|
this.logger = logger;
|
|
11128
|
-
|
|
12523
|
+
let replayEnabled = config.replay?.enabled ?? false;
|
|
12524
|
+
let autoVersionEnabled = config.autoVersion?.enabled ?? false;
|
|
12525
|
+
if (replayEnabled && config.github?.mode === "push") {
|
|
11129
12526
|
this.logger.warn(
|
|
11130
12527
|
"Replay is not supported with GitHub push mode. Disabling replay to prevent force push to base branch."
|
|
11131
12528
|
);
|
|
11132
|
-
|
|
12529
|
+
replayEnabled = false;
|
|
12530
|
+
}
|
|
12531
|
+
if (autoVersionEnabled && !replayEnabled) {
|
|
12532
|
+
this.logger.warn("AutoVersion requires Replay to be enabled. Disabling AutoVersion for this run.");
|
|
12533
|
+
autoVersionEnabled = false;
|
|
12534
|
+
}
|
|
12535
|
+
if (replayEnabled && config.replay != null) {
|
|
12536
|
+
this.steps.push(
|
|
12537
|
+
new GenerationCommitStep(
|
|
12538
|
+
config.outputDir,
|
|
12539
|
+
this.logger,
|
|
12540
|
+
{ enabled: true, skipApplication: config.replay.skipApplication },
|
|
12541
|
+
config.cliVersion,
|
|
12542
|
+
config.generatorVersions,
|
|
12543
|
+
config.generatorName
|
|
12544
|
+
)
|
|
12545
|
+
);
|
|
11133
12546
|
}
|
|
11134
|
-
if (config.autoVersion
|
|
12547
|
+
if (autoVersionEnabled && config.autoVersion != null) {
|
|
11135
12548
|
this.steps.push(new AutoVersionStep(config.outputDir, this.logger, config.autoVersion));
|
|
11136
12549
|
}
|
|
11137
|
-
if (config.replay
|
|
12550
|
+
if (replayEnabled && config.replay != null) {
|
|
11138
12551
|
this.steps.push(
|
|
11139
12552
|
new ReplayStep(
|
|
11140
12553
|
config.outputDir,
|
|
@@ -11156,8 +12569,8 @@ var PostGenerationPipeline = class {
|
|
|
11156
12569
|
async run() {
|
|
11157
12570
|
if (this.config.github?.enabled) {
|
|
11158
12571
|
try {
|
|
11159
|
-
(0,
|
|
11160
|
-
(0,
|
|
12572
|
+
(0, import_child_process5.execFileSync)("git", ["config", "user.name", FERN_BOT_NAME], { cwd: this.config.outputDir });
|
|
12573
|
+
(0, import_child_process5.execFileSync)("git", ["config", "user.email", FERN_BOT_EMAIL], { cwd: this.config.outputDir });
|
|
11161
12574
|
} catch {
|
|
11162
12575
|
}
|
|
11163
12576
|
}
|
|
@@ -11171,7 +12584,12 @@ var PostGenerationPipeline = class {
|
|
|
11171
12584
|
for (const step of this.steps) {
|
|
11172
12585
|
try {
|
|
11173
12586
|
const stepResult = await step.execute(pipelineContext);
|
|
11174
|
-
if (step.name === "
|
|
12587
|
+
if (step.name === "generationCommit") {
|
|
12588
|
+
const gcResult = stepResult;
|
|
12589
|
+
const { preparedReplay: _preparedReplay, ...serializable } = gcResult;
|
|
12590
|
+
result.steps.generationCommit = serializable;
|
|
12591
|
+
pipelineContext.previousStepResults.generationCommit = gcResult;
|
|
12592
|
+
} else if (step.name === "replay") {
|
|
11175
12593
|
result.steps.replay = stepResult;
|
|
11176
12594
|
pipelineContext.previousStepResults.replay = stepResult;
|
|
11177
12595
|
} else if (step.name === "autoVersion") {
|
|
@@ -11206,8 +12624,8 @@ function replayForget(params) {
|
|
|
11206
12624
|
|
|
11207
12625
|
// src/replay/replay-init.ts
|
|
11208
12626
|
var import_replay3 = require("@fern-api/replay");
|
|
11209
|
-
var
|
|
11210
|
-
var
|
|
12627
|
+
var import_fs6 = require("fs");
|
|
12628
|
+
var import_path8 = require("path");
|
|
11211
12629
|
var import_tmp_promise2 = __toESM(require("tmp-promise"));
|
|
11212
12630
|
async function replayInit(params) {
|
|
11213
12631
|
const { githubRepo, token, dryRun } = params;
|
|
@@ -11233,10 +12651,10 @@ async function replayInit(params) {
|
|
|
11233
12651
|
}
|
|
11234
12652
|
ensureReplayFernignoreEntriesSync(repoPath);
|
|
11235
12653
|
ensureGitattributesEntriesSync(repoPath);
|
|
11236
|
-
const lockfilePath = (0,
|
|
11237
|
-
const lockfileContent = (0,
|
|
11238
|
-
const replayYmlPath = (0,
|
|
11239
|
-
const replayYmlContent = (0,
|
|
12654
|
+
const lockfilePath = (0, import_path8.join)(repoPath, ".fern", "replay.lock");
|
|
12655
|
+
const lockfileContent = (0, import_fs6.existsSync)(lockfilePath) ? (0, import_fs6.readFileSync)(lockfilePath, "utf-8") : void 0;
|
|
12656
|
+
const replayYmlPath = (0, import_path8.join)(repoPath, ".fern", "replay.yml");
|
|
12657
|
+
const replayYmlContent = (0, import_fs6.existsSync)(replayYmlPath) ? (0, import_fs6.readFileSync)(replayYmlPath, "utf-8") : void 0;
|
|
11240
12658
|
return {
|
|
11241
12659
|
bootstrap: bootstrapResult,
|
|
11242
12660
|
lockfileContent,
|