@fern-api/generator-cli 0.9.9 → 0.9.11
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 +80 -2
- package/dist/api.js +342 -38
- package/dist/cli.js +278 -36
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -499,6 +499,7 @@ declare const consolePipelineLogger: PipelineLogger;
|
|
|
499
499
|
interface PipelineConfig {
|
|
500
500
|
outputDir: string;
|
|
501
501
|
replay?: ReplayStepConfig;
|
|
502
|
+
autoVersion?: AutoVersionStepConfig;
|
|
502
503
|
fernignore?: FernignoreStepConfig;
|
|
503
504
|
github?: GithubStepConfig;
|
|
504
505
|
cliVersion?: string;
|
|
@@ -508,6 +509,7 @@ interface PipelineConfig {
|
|
|
508
509
|
interface PipelineContext {
|
|
509
510
|
previousStepResults: {
|
|
510
511
|
replay?: ReplayStepResult;
|
|
512
|
+
autoVersion?: AutoVersionStepResult;
|
|
511
513
|
fernignore?: FernignoreStepResult;
|
|
512
514
|
};
|
|
513
515
|
}
|
|
@@ -516,6 +518,17 @@ interface ReplayStepConfig {
|
|
|
516
518
|
stageOnly?: boolean;
|
|
517
519
|
skipApplication?: boolean;
|
|
518
520
|
}
|
|
521
|
+
interface AutoVersionStepConfig {
|
|
522
|
+
enabled: boolean;
|
|
523
|
+
/** Generator language: typescript | python | java | go | ruby | csharp | php | swift | rust | kotlin */
|
|
524
|
+
language: string;
|
|
525
|
+
/** Existing changelog content passed to the AI for context. */
|
|
526
|
+
priorChangelog?: string;
|
|
527
|
+
/** Fallback version when no prior generation exists (first run). */
|
|
528
|
+
baseVersion?: string;
|
|
529
|
+
/** Fern token used to authorize the FAI (AnalyzeSdkDiff) call. */
|
|
530
|
+
fernToken: string;
|
|
531
|
+
}
|
|
519
532
|
interface FernignoreStepConfig {
|
|
520
533
|
enabled: boolean;
|
|
521
534
|
customContents?: string;
|
|
@@ -555,8 +568,10 @@ interface GithubStepConfig {
|
|
|
555
568
|
previousGenerationSha: string;
|
|
556
569
|
currentGenerationSha: string;
|
|
557
570
|
};
|
|
558
|
-
/** When true: separate PRs per generation,
|
|
571
|
+
/** When true: separate PRs per generation, automerge support, run_id in body */
|
|
559
572
|
automationMode?: boolean;
|
|
573
|
+
/** When true: skip opening a PR / pushing when the generated output has no diff from the base branch. */
|
|
574
|
+
skipIfNoDiff?: boolean;
|
|
560
575
|
/** Enable GitHub automerge on the PR (only effective when automationMode && !hasBreakingChanges) */
|
|
561
576
|
autoMerge?: boolean;
|
|
562
577
|
/** Pre-computed: version bump is MAJOR (from --version AUTO AI analysis) */
|
|
@@ -570,6 +585,7 @@ interface PipelineResult {
|
|
|
570
585
|
success: boolean;
|
|
571
586
|
steps: {
|
|
572
587
|
replay?: ReplayStepResult;
|
|
588
|
+
autoVersion?: AutoVersionStepResult;
|
|
573
589
|
fernignore?: FernignoreStepResult;
|
|
574
590
|
github?: GithubStepResult;
|
|
575
591
|
};
|
|
@@ -602,6 +618,24 @@ interface ReplayStepResult extends StepResult {
|
|
|
602
618
|
interface FernignoreStepResult extends StepResult {
|
|
603
619
|
pathsPreserved?: string[];
|
|
604
620
|
}
|
|
621
|
+
interface AutoVersionStepResult extends StepResult {
|
|
622
|
+
/** The new version to use (e.g. "1.2.3"). Field name matches fiddle's Java AutoVersionResult DTO. */
|
|
623
|
+
version?: string;
|
|
624
|
+
/** Commit subject for the `[fern-autoversion]` commit. */
|
|
625
|
+
commitMessage?: string;
|
|
626
|
+
/** User-facing changelog entry appended to changelog.md. */
|
|
627
|
+
changelogEntry?: string;
|
|
628
|
+
/** Prior SDK version before this change (e.g. "1.2.2"). */
|
|
629
|
+
previousVersion?: string;
|
|
630
|
+
/** The version bump level determined by FAI. */
|
|
631
|
+
versionBump?: "MAJOR" | "MINOR" | "PATCH" | "NO_CHANGE";
|
|
632
|
+
/** Structured PR description with Before/After code fences for breaking changes. */
|
|
633
|
+
prDescription?: string;
|
|
634
|
+
/** One-sentence justification for WHY the version bump was chosen. */
|
|
635
|
+
versionBumpReason?: string;
|
|
636
|
+
/** SHA of the `[fern-autoversion]` commit once it's been made. TS-only; no fiddle counterpart. */
|
|
637
|
+
commitSha?: string;
|
|
638
|
+
}
|
|
605
639
|
interface GithubStepResult extends StepResult {
|
|
606
640
|
commitSha?: string;
|
|
607
641
|
branchUrl?: string;
|
|
@@ -733,4 +767,48 @@ interface ReplayStatusParams {
|
|
|
733
767
|
}
|
|
734
768
|
declare function replayStatus(params: ReplayStatusParams): StatusResult;
|
|
735
769
|
|
|
736
|
-
|
|
770
|
+
interface SubmitReplayInitParams {
|
|
771
|
+
/** Fiddle API origin (e.g. "https://fiddle.buildwithfern.com") */
|
|
772
|
+
fiddleOrigin: string;
|
|
773
|
+
/** Fern auth token */
|
|
774
|
+
fernToken: string;
|
|
775
|
+
/** GitHub repository (e.g. "owner/repo" or full URL) */
|
|
776
|
+
githubRepo: string;
|
|
777
|
+
/** Result from replayInit() */
|
|
778
|
+
initResult: ReplayInitResult;
|
|
779
|
+
}
|
|
780
|
+
interface SubmitReplayInitResult {
|
|
781
|
+
/** URL of the PR created by Fiddle */
|
|
782
|
+
prUrl: string;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Submits a Replay init lockfile to the Fiddle API for server-side PR creation.
|
|
786
|
+
*
|
|
787
|
+
* Extracts owner/repo from the GitHub repository string, posts the lockfile
|
|
788
|
+
* and related metadata to the Fiddle remote-gen endpoint, and returns the
|
|
789
|
+
* resulting PR URL.
|
|
790
|
+
*
|
|
791
|
+
* This function is shared between CLI v1 and CLI v2 to avoid duplicating
|
|
792
|
+
* the Fiddle submission logic.
|
|
793
|
+
*/
|
|
794
|
+
declare function submitReplayInit(params: SubmitReplayInitParams): Promise<SubmitReplayInitResult>;
|
|
795
|
+
/**
|
|
796
|
+
* Parses a GitHub repository string into owner and repo components.
|
|
797
|
+
* Accepts formats: "owner/repo", "github.com/owner/repo",
|
|
798
|
+
* "https://github.com/owner/repo", "https://github.com/owner/repo.git"
|
|
799
|
+
*/
|
|
800
|
+
declare function parseOwnerRepo(githubRepo: string): {
|
|
801
|
+
owner: string;
|
|
802
|
+
repo: string;
|
|
803
|
+
};
|
|
804
|
+
/**
|
|
805
|
+
* Error thrown when submitting a Replay init to Fiddle fails.
|
|
806
|
+
* Consumers can inspect `statusCode` to distinguish between
|
|
807
|
+
* different failure modes (e.g. 404 = GitHub App not installed).
|
|
808
|
+
*/
|
|
809
|
+
declare class ReplaySubmitError extends Error {
|
|
810
|
+
readonly statusCode: number | undefined;
|
|
811
|
+
constructor(message: string, statusCode?: number);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
export { type BootstrapLogEntry, type GenerateReadmeParams, type GenerateReadmeToStreamParams, type GenerateReferenceParams, type GenerateReferenceToStreamParams, type GithubPrParams, type GithubPushParams, type GithubReleaseParams, type GithubStepConfig, type GithubStepResult, type PipelineConfig, type PipelineContext, type PipelineLogger, type PipelineResult, PostGenerationPipeline, type ReplayForgetParams, type ReplayInitParams, type ReplayInitResult, type ReplayResolveParams, type ReplayRunParams, type ReplayRunResult, type ReplayStatusParams, type ReplayStepResult, ReplaySubmitError, type SubmitReplayInitParams, type SubmitReplayInitResult, consolePipelineLogger, formatBootstrapSummary, formatReplayPrBody, generateReadme, generateReadmeToStream, generateReference, generateReferenceToStream, githubPr, githubPush, githubRelease, logReplaySummary, parseOwnerRepo, replayForget, replayInit, replayResolve, replayRun, replayStatus, submitReplayInit };
|
package/dist/api.js
CHANGED
|
@@ -881,6 +881,7 @@ var require_dist2 = __commonJS({
|
|
|
881
881
|
var api_exports2 = {};
|
|
882
882
|
__export(api_exports2, {
|
|
883
883
|
PostGenerationPipeline: () => PostGenerationPipeline,
|
|
884
|
+
ReplaySubmitError: () => ReplaySubmitError,
|
|
884
885
|
consolePipelineLogger: () => consolePipelineLogger,
|
|
885
886
|
formatBootstrapSummary: () => formatBootstrapSummary,
|
|
886
887
|
formatReplayPrBody: () => formatReplayPrBody,
|
|
@@ -892,11 +893,13 @@ __export(api_exports2, {
|
|
|
892
893
|
githubPush: () => githubPush,
|
|
893
894
|
githubRelease: () => githubRelease,
|
|
894
895
|
logReplaySummary: () => logReplaySummary,
|
|
896
|
+
parseOwnerRepo: () => parseOwnerRepo,
|
|
895
897
|
replayForget: () => replayForget,
|
|
896
898
|
replayInit: () => replayInit,
|
|
897
899
|
replayResolve: () => replayResolve,
|
|
898
900
|
replayRun: () => replayRun,
|
|
899
|
-
replayStatus: () => replayStatus
|
|
901
|
+
replayStatus: () => replayStatus,
|
|
902
|
+
submitReplayInit: () => submitReplayInit
|
|
900
903
|
});
|
|
901
904
|
module.exports = __toCommonJS(api_exports2);
|
|
902
905
|
|
|
@@ -6017,6 +6020,46 @@ var ClonedRepository = class _ClonedRepository {
|
|
|
6017
6020
|
const result = await this.git.raw(["rev-parse", "HEAD^{tree}"]);
|
|
6018
6021
|
return result.trim();
|
|
6019
6022
|
}
|
|
6023
|
+
async getHeadCommitMessage() {
|
|
6024
|
+
await this.git.cwd(this.clonePath);
|
|
6025
|
+
const result = await this.git.raw(["log", "-1", "--format=%B", "HEAD"]);
|
|
6026
|
+
return result.replace(/\n$/, "");
|
|
6027
|
+
}
|
|
6028
|
+
async getHeadParents() {
|
|
6029
|
+
await this.git.cwd(this.clonePath);
|
|
6030
|
+
const result = await this.git.raw(["rev-list", "--parents", "-n", "1", "HEAD"]);
|
|
6031
|
+
const parts = result.trim().split(/\s+/);
|
|
6032
|
+
return parts.slice(1);
|
|
6033
|
+
}
|
|
6034
|
+
/**
|
|
6035
|
+
* Pushes a specific object (commit SHA) to a remote ref.
|
|
6036
|
+
* Used to upload tree and blob objects to the remote without moving a branch.
|
|
6037
|
+
*
|
|
6038
|
+
* Pass `force: true` when overwriting a ref that may already point to an
|
|
6039
|
+
* unrelated commit (e.g. retrying a temp ref after a local rebase rewrites
|
|
6040
|
+
* history so the new SHA is not a descendant of the previous one).
|
|
6041
|
+
*/
|
|
6042
|
+
async pushObjectToRef(localSha, remoteRef, { force = false } = {}) {
|
|
6043
|
+
await this.git.cwd(this.clonePath);
|
|
6044
|
+
const spec = force ? `+${localSha}:${remoteRef}` : `${localSha}:${remoteRef}`;
|
|
6045
|
+
await this.git.raw(["push", "origin", spec]);
|
|
6046
|
+
}
|
|
6047
|
+
/**
|
|
6048
|
+
* Resets the current branch HEAD to the given SHA. After fetching a newly-created remote
|
|
6049
|
+
* signed commit, use this to align the local branch so subsequent operations (tag push,
|
|
6050
|
+
* getHeadSha(), etc.) see the signed SHA.
|
|
6051
|
+
*/
|
|
6052
|
+
async resetHardToSha(sha) {
|
|
6053
|
+
await this.git.cwd(this.clonePath);
|
|
6054
|
+
await this.git.raw(["reset", "--hard", sha]);
|
|
6055
|
+
}
|
|
6056
|
+
/**
|
|
6057
|
+
* Rebases the current branch onto the given remote branch (`git pull --rebase origin <branch>`).
|
|
6058
|
+
*/
|
|
6059
|
+
async pullWithRebase(branch) {
|
|
6060
|
+
await this.git.cwd(this.clonePath);
|
|
6061
|
+
await this.git.raw(["pull", "origin", branch, "--rebase"]);
|
|
6062
|
+
}
|
|
6020
6063
|
async getCommitTreeHash(commitSha) {
|
|
6021
6064
|
await this.git.cwd(this.clonePath);
|
|
6022
6065
|
const result = await this.git.raw(["rev-parse", `${commitSha}^{tree}`]);
|
|
@@ -10024,6 +10067,35 @@ var FERN_BOT_NAME = "fern-api";
|
|
|
10024
10067
|
var FERN_BOT_EMAIL = "115122769+fern-api[bot]@users.noreply.github.com";
|
|
10025
10068
|
var FERN_BOT_LOGIN = "fern-api[bot]";
|
|
10026
10069
|
|
|
10070
|
+
// src/pipeline/steps/BaseStep.ts
|
|
10071
|
+
var BaseStep = class {
|
|
10072
|
+
constructor(outputDir, logger) {
|
|
10073
|
+
this.outputDir = outputDir;
|
|
10074
|
+
this.logger = logger;
|
|
10075
|
+
}
|
|
10076
|
+
outputDir;
|
|
10077
|
+
logger;
|
|
10078
|
+
};
|
|
10079
|
+
|
|
10080
|
+
// src/pipeline/steps/AutoVersionStep.ts
|
|
10081
|
+
var AutoVersionStep = class extends BaseStep {
|
|
10082
|
+
constructor(outputDir, logger, config) {
|
|
10083
|
+
super(outputDir, logger);
|
|
10084
|
+
this.config = config;
|
|
10085
|
+
}
|
|
10086
|
+
config;
|
|
10087
|
+
name = "autoVersion";
|
|
10088
|
+
async execute(_context) {
|
|
10089
|
+
this.logger.info(
|
|
10090
|
+
`AutoVersionStep: no-op scaffold for language=${this.config.language} (logic lands in FER-9980)`
|
|
10091
|
+
);
|
|
10092
|
+
return {
|
|
10093
|
+
executed: true,
|
|
10094
|
+
success: true
|
|
10095
|
+
};
|
|
10096
|
+
}
|
|
10097
|
+
};
|
|
10098
|
+
|
|
10027
10099
|
// src/pipeline/steps/GithubStep.ts
|
|
10028
10100
|
var import_rest2 = require("@octokit/rest");
|
|
10029
10101
|
var import_promises2 = require("fs/promises");
|
|
@@ -10157,6 +10229,128 @@ ${prBody}`;
|
|
|
10157
10229
|
return { prTitle, prBody };
|
|
10158
10230
|
}
|
|
10159
10231
|
|
|
10232
|
+
// src/pipeline/github/pushSignedCommit.ts
|
|
10233
|
+
var MAX_CONCURRENT_PUSH_RETRIES = 3;
|
|
10234
|
+
async function pushSignedCommit({
|
|
10235
|
+
repository,
|
|
10236
|
+
octokit,
|
|
10237
|
+
owner,
|
|
10238
|
+
repo,
|
|
10239
|
+
branch,
|
|
10240
|
+
force,
|
|
10241
|
+
rebaseOnConflict = false,
|
|
10242
|
+
logger
|
|
10243
|
+
}) {
|
|
10244
|
+
const tempRef = `refs/temp/fern-${Date.now()}`;
|
|
10245
|
+
let tempRefPushed = false;
|
|
10246
|
+
try {
|
|
10247
|
+
let [localHeadSha, treeSha, message, parents] = await Promise.all([
|
|
10248
|
+
repository.getHeadSha(),
|
|
10249
|
+
repository.getHeadTreeHash(),
|
|
10250
|
+
repository.getHeadCommitMessage(),
|
|
10251
|
+
repository.getHeadParents()
|
|
10252
|
+
]);
|
|
10253
|
+
await repository.pushObjectToRef(localHeadSha, tempRef);
|
|
10254
|
+
tempRefPushed = true;
|
|
10255
|
+
for (let attempt = 0; attempt < MAX_CONCURRENT_PUSH_RETRIES; attempt++) {
|
|
10256
|
+
const { data: signedCommit } = await octokit.git.createCommit({
|
|
10257
|
+
owner,
|
|
10258
|
+
repo,
|
|
10259
|
+
message,
|
|
10260
|
+
tree: treeSha,
|
|
10261
|
+
parents
|
|
10262
|
+
});
|
|
10263
|
+
const signedSha = signedCommit.sha;
|
|
10264
|
+
try {
|
|
10265
|
+
await upsertBranchRef({ octokit, owner, repo, branch, sha: signedSha, force });
|
|
10266
|
+
logger.debug(`Updated refs/heads/${branch} to signed commit ${signedSha}`);
|
|
10267
|
+
await syncLocalToSignedCommit({ repository, branch, signedSha });
|
|
10268
|
+
return signedSha;
|
|
10269
|
+
} catch (err) {
|
|
10270
|
+
if (!isNonFastForwardError(err) || force || attempt >= MAX_CONCURRENT_PUSH_RETRIES - 1) {
|
|
10271
|
+
throw err;
|
|
10272
|
+
}
|
|
10273
|
+
if (rebaseOnConflict) {
|
|
10274
|
+
logger.warn(
|
|
10275
|
+
`Non-fast-forward on refs/heads/${branch} (attempt ${attempt + 1}/${MAX_CONCURRENT_PUSH_RETRIES}); rebasing locally and retrying.`
|
|
10276
|
+
);
|
|
10277
|
+
await repository.pullWithRebase(branch);
|
|
10278
|
+
[localHeadSha, treeSha, message, parents] = await Promise.all([
|
|
10279
|
+
repository.getHeadSha(),
|
|
10280
|
+
repository.getHeadTreeHash(),
|
|
10281
|
+
repository.getHeadCommitMessage(),
|
|
10282
|
+
repository.getHeadParents()
|
|
10283
|
+
]);
|
|
10284
|
+
await repository.pushObjectToRef(localHeadSha, tempRef, { force: true });
|
|
10285
|
+
} else {
|
|
10286
|
+
throw err;
|
|
10287
|
+
}
|
|
10288
|
+
}
|
|
10289
|
+
}
|
|
10290
|
+
throw new Error(
|
|
10291
|
+
`Failed to push signed commit to refs/heads/${branch} after ${MAX_CONCURRENT_PUSH_RETRIES} attempts`
|
|
10292
|
+
);
|
|
10293
|
+
} finally {
|
|
10294
|
+
if (tempRefPushed) {
|
|
10295
|
+
try {
|
|
10296
|
+
await octokit.git.deleteRef({ owner, repo, ref: tempRef.replace(/^refs\//, "") });
|
|
10297
|
+
} catch (err) {
|
|
10298
|
+
logger.debug(`Failed to delete temp ref ${tempRef}: ${extractErrorMessage(err)}`);
|
|
10299
|
+
}
|
|
10300
|
+
}
|
|
10301
|
+
}
|
|
10302
|
+
}
|
|
10303
|
+
async function upsertBranchRef({
|
|
10304
|
+
octokit,
|
|
10305
|
+
owner,
|
|
10306
|
+
repo,
|
|
10307
|
+
branch,
|
|
10308
|
+
sha,
|
|
10309
|
+
force
|
|
10310
|
+
}) {
|
|
10311
|
+
try {
|
|
10312
|
+
await octokit.git.updateRef({
|
|
10313
|
+
owner,
|
|
10314
|
+
repo,
|
|
10315
|
+
ref: `heads/${branch}`,
|
|
10316
|
+
sha,
|
|
10317
|
+
force
|
|
10318
|
+
});
|
|
10319
|
+
} catch (err) {
|
|
10320
|
+
if (isNotFoundError(err)) {
|
|
10321
|
+
await octokit.git.createRef({
|
|
10322
|
+
owner,
|
|
10323
|
+
repo,
|
|
10324
|
+
ref: `refs/heads/${branch}`,
|
|
10325
|
+
sha
|
|
10326
|
+
});
|
|
10327
|
+
return;
|
|
10328
|
+
}
|
|
10329
|
+
throw err;
|
|
10330
|
+
}
|
|
10331
|
+
}
|
|
10332
|
+
async function syncLocalToSignedCommit({
|
|
10333
|
+
repository,
|
|
10334
|
+
branch,
|
|
10335
|
+
signedSha
|
|
10336
|
+
}) {
|
|
10337
|
+
await repository.fetch(["origin", branch]);
|
|
10338
|
+
await repository.resetHardToSha(signedSha);
|
|
10339
|
+
}
|
|
10340
|
+
function hasNumericStatus(err) {
|
|
10341
|
+
return typeof err === "object" && err != null && "status" in err && typeof err.status === "number";
|
|
10342
|
+
}
|
|
10343
|
+
function isNotFoundError(err) {
|
|
10344
|
+
return hasNumericStatus(err) && err.status === 404;
|
|
10345
|
+
}
|
|
10346
|
+
function isNonFastForwardError(err) {
|
|
10347
|
+
if (!hasNumericStatus(err) || err.status !== 422) {
|
|
10348
|
+
return false;
|
|
10349
|
+
}
|
|
10350
|
+
const message = extractErrorMessage(err).toLowerCase();
|
|
10351
|
+
return message.includes("not a fast forward") || message.includes("not a fast-forward") || message.includes("update is not a fast forward");
|
|
10352
|
+
}
|
|
10353
|
+
|
|
10160
10354
|
// src/pipeline/replay-summary.ts
|
|
10161
10355
|
function plural(n, word) {
|
|
10162
10356
|
return `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
@@ -10288,16 +10482,6 @@ function formatReplayPrBody(result, options) {
|
|
|
10288
10482
|
return parts.join("\n");
|
|
10289
10483
|
}
|
|
10290
10484
|
|
|
10291
|
-
// src/pipeline/steps/BaseStep.ts
|
|
10292
|
-
var BaseStep = class {
|
|
10293
|
-
constructor(outputDir, logger) {
|
|
10294
|
-
this.outputDir = outputDir;
|
|
10295
|
-
this.logger = logger;
|
|
10296
|
-
}
|
|
10297
|
-
outputDir;
|
|
10298
|
-
logger;
|
|
10299
|
-
};
|
|
10300
|
-
|
|
10301
10485
|
// src/pipeline/steps/GithubStep.ts
|
|
10302
10486
|
var GithubStep = class extends BaseStep {
|
|
10303
10487
|
constructor(outputDir, logger, config) {
|
|
@@ -10377,13 +10561,17 @@ var GithubStep = class extends BaseStep {
|
|
|
10377
10561
|
});
|
|
10378
10562
|
switch (branchAction) {
|
|
10379
10563
|
case "replay-branch":
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10564
|
+
if (shouldPushGenerationBaseTag(replayResult)) {
|
|
10565
|
+
generationBaseSha = await createReplayBranch(
|
|
10566
|
+
repository,
|
|
10567
|
+
prBranch,
|
|
10568
|
+
this.config.commitMessage,
|
|
10569
|
+
replayConflictInfo,
|
|
10570
|
+
this.logger
|
|
10571
|
+
);
|
|
10572
|
+
} else {
|
|
10573
|
+
await repository.createBranchFromHead(prBranch);
|
|
10574
|
+
}
|
|
10387
10575
|
break;
|
|
10388
10576
|
case "create-from-head":
|
|
10389
10577
|
await repository.createBranchFromHead(prBranch);
|
|
@@ -10403,7 +10591,7 @@ var GithubStep = class extends BaseStep {
|
|
|
10403
10591
|
await repository.commitAllChanges(finalCommitMessage2);
|
|
10404
10592
|
this.logger.debug(`Committed changes to local copy of GitHub repository at ${this.outputDir}`);
|
|
10405
10593
|
}
|
|
10406
|
-
if (this.config
|
|
10594
|
+
if (shouldCheckNoDiff(this.config)) {
|
|
10407
10595
|
const noDiff = await repository.treeHashEquals(`origin/${baseBranch}`);
|
|
10408
10596
|
if (noDiff) {
|
|
10409
10597
|
this.logger.info("No changes detected after generation \u2014 skipping PR creation");
|
|
@@ -10416,11 +10604,15 @@ var GithubStep = class extends BaseStep {
|
|
|
10416
10604
|
updatedExistingPr: isUpdatingExistingPR
|
|
10417
10605
|
};
|
|
10418
10606
|
if (!this.config.previewMode) {
|
|
10419
|
-
|
|
10420
|
-
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
|
|
10607
|
+
await pushSignedCommit({
|
|
10608
|
+
repository,
|
|
10609
|
+
octokit,
|
|
10610
|
+
owner,
|
|
10611
|
+
repo,
|
|
10612
|
+
branch: prBranch,
|
|
10613
|
+
force: isUpdatingExistingPR,
|
|
10614
|
+
logger: this.logger
|
|
10615
|
+
});
|
|
10424
10616
|
const pushedBranch = await repository.getCurrentBranch();
|
|
10425
10617
|
result.branchUrl = `https://github.com/${this.config.uri}/tree/${pushedBranch}`;
|
|
10426
10618
|
this.logger.info(`Pushed branch: ${result.branchUrl}`);
|
|
@@ -10521,7 +10713,7 @@ var GithubStep = class extends BaseStep {
|
|
|
10521
10713
|
const finalCommitMessage = this.config.commitMessage ?? "SDK Generation";
|
|
10522
10714
|
await repository.commitAllChanges(finalCommitMessage);
|
|
10523
10715
|
this.logger.debug(`Committed changes to local copy of GitHub repository at ${this.outputDir}`);
|
|
10524
|
-
if (this.config
|
|
10716
|
+
if (shouldCheckNoDiff(this.config)) {
|
|
10525
10717
|
const noDiff = await repository.treeHashEquals(`origin/${baseBranch}`);
|
|
10526
10718
|
if (noDiff) {
|
|
10527
10719
|
this.logger.info("No changes detected after generation \u2014 skipping push");
|
|
@@ -10533,7 +10725,18 @@ var GithubStep = class extends BaseStep {
|
|
|
10533
10725
|
success: true
|
|
10534
10726
|
};
|
|
10535
10727
|
if (!this.config.previewMode) {
|
|
10536
|
-
|
|
10728
|
+
const octokit = new import_rest2.Octokit({ auth: this.config.token });
|
|
10729
|
+
const { owner, repo } = parseRepository(this.config.uri);
|
|
10730
|
+
await pushSignedCommit({
|
|
10731
|
+
repository,
|
|
10732
|
+
octokit,
|
|
10733
|
+
owner,
|
|
10734
|
+
repo,
|
|
10735
|
+
branch: baseBranch,
|
|
10736
|
+
force: false,
|
|
10737
|
+
rebaseOnConflict: true,
|
|
10738
|
+
logger: this.logger
|
|
10739
|
+
});
|
|
10537
10740
|
const pushedBranch = await repository.getCurrentBranch();
|
|
10538
10741
|
result.branchUrl = `https://github.com/${this.config.uri}/tree/${pushedBranch}`;
|
|
10539
10742
|
this.logger.info(`Pushed branch: ${result.branchUrl}`);
|
|
@@ -10592,6 +10795,12 @@ _Fern Run ID: \`${config.runId}\`_`;
|
|
|
10592
10795
|
function shouldEnableAutomerge(config) {
|
|
10593
10796
|
return config.automationMode === true && config.autoMerge === true && config.hasBreakingChanges !== true;
|
|
10594
10797
|
}
|
|
10798
|
+
function shouldCheckNoDiff(config) {
|
|
10799
|
+
return config.skipIfNoDiff === true;
|
|
10800
|
+
}
|
|
10801
|
+
function shouldPushGenerationBaseTag(replayResult) {
|
|
10802
|
+
return (replayResult?.patchesWithConflicts ?? 0) > 0;
|
|
10803
|
+
}
|
|
10595
10804
|
function resolveBranchAction({
|
|
10596
10805
|
automationMode,
|
|
10597
10806
|
skipCommit,
|
|
@@ -10730,18 +10939,24 @@ async function replayRun(params) {
|
|
|
10730
10939
|
}
|
|
10731
10940
|
}
|
|
10732
10941
|
if (shouldSync && tagSha != null) {
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
|
|
10740
|
-
|
|
10741
|
-
|
|
10742
|
-
|
|
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 {
|
|
10743
10959
|
}
|
|
10744
|
-
} catch {
|
|
10745
10960
|
}
|
|
10746
10961
|
}
|
|
10747
10962
|
}
|
|
@@ -10791,6 +11006,30 @@ async function replayRun(params) {
|
|
|
10791
11006
|
baseBranchHead: resolvedBaseBranchHead
|
|
10792
11007
|
};
|
|
10793
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
|
+
}
|
|
10794
11033
|
function gitRevParse(cwd2, rev) {
|
|
10795
11034
|
try {
|
|
10796
11035
|
return (0, import_child_process3.execFileSync)("git", ["rev-parse", "--verify", rev], {
|
|
@@ -10892,6 +11131,9 @@ var PostGenerationPipeline = class {
|
|
|
10892
11131
|
);
|
|
10893
11132
|
config.replay.enabled = false;
|
|
10894
11133
|
}
|
|
11134
|
+
if (config.autoVersion?.enabled) {
|
|
11135
|
+
this.steps.push(new AutoVersionStep(config.outputDir, this.logger, config.autoVersion));
|
|
11136
|
+
}
|
|
10895
11137
|
if (config.replay?.enabled) {
|
|
10896
11138
|
this.steps.push(
|
|
10897
11139
|
new ReplayStep(
|
|
@@ -10932,6 +11174,9 @@ var PostGenerationPipeline = class {
|
|
|
10932
11174
|
if (step.name === "replay") {
|
|
10933
11175
|
result.steps.replay = stepResult;
|
|
10934
11176
|
pipelineContext.previousStepResults.replay = stepResult;
|
|
11177
|
+
} else if (step.name === "autoVersion") {
|
|
11178
|
+
result.steps.autoVersion = stepResult;
|
|
11179
|
+
pipelineContext.previousStepResults.autoVersion = stepResult;
|
|
10935
11180
|
} else if (step.name === "fernignore") {
|
|
10936
11181
|
result.steps.fernignore = stepResult;
|
|
10937
11182
|
} else if (step.name === "github") {
|
|
@@ -11119,9 +11364,66 @@ var import_replay5 = require("@fern-api/replay");
|
|
|
11119
11364
|
function replayStatus(params) {
|
|
11120
11365
|
return (0, import_replay5.status)(params.outputDir);
|
|
11121
11366
|
}
|
|
11367
|
+
|
|
11368
|
+
// src/replay/replay-submit-init.ts
|
|
11369
|
+
async function submitReplayInit(params) {
|
|
11370
|
+
const { fiddleOrigin, fernToken, githubRepo, initResult } = params;
|
|
11371
|
+
const { owner, repo } = parseOwnerRepo(githubRepo);
|
|
11372
|
+
const response = await fetch(`${fiddleOrigin}/api/remote-gen/replay/init`, {
|
|
11373
|
+
method: "POST",
|
|
11374
|
+
headers: {
|
|
11375
|
+
"Content-Type": "application/json",
|
|
11376
|
+
Authorization: `Bearer ${fernToken}`
|
|
11377
|
+
},
|
|
11378
|
+
body: JSON.stringify({
|
|
11379
|
+
owner,
|
|
11380
|
+
repo,
|
|
11381
|
+
lockfileContents: initResult.lockfileContent,
|
|
11382
|
+
fernignoreEntries: initResult.fernignoreEntries,
|
|
11383
|
+
prBody: initResult.prBody
|
|
11384
|
+
})
|
|
11385
|
+
});
|
|
11386
|
+
if (!response.ok) {
|
|
11387
|
+
if (response.status === 404) {
|
|
11388
|
+
throw new ReplaySubmitError(
|
|
11389
|
+
"The Fern GitHub App is not installed on this repository. Install it at https://github.com/apps/fern-api to enable server-side PR creation.",
|
|
11390
|
+
response.status
|
|
11391
|
+
);
|
|
11392
|
+
}
|
|
11393
|
+
const body = await response.text();
|
|
11394
|
+
throw new ReplaySubmitError(`Failed to create PR via Fern: ${body}`, response.status);
|
|
11395
|
+
}
|
|
11396
|
+
const data = await response.json();
|
|
11397
|
+
if (!isSubmitResponse(data)) {
|
|
11398
|
+
throw new ReplaySubmitError("Unexpected response from Fern: missing prUrl field.");
|
|
11399
|
+
}
|
|
11400
|
+
return { prUrl: data.prUrl };
|
|
11401
|
+
}
|
|
11402
|
+
function parseOwnerRepo(githubRepo) {
|
|
11403
|
+
const cleaned = githubRepo.replace(/^https?:\/\//, "").replace(/\.git$/, "").replace(/^github\.com\//, "");
|
|
11404
|
+
const parts = cleaned.split("/");
|
|
11405
|
+
const owner = parts[parts.length - 2];
|
|
11406
|
+
const repo = parts[parts.length - 1];
|
|
11407
|
+
if (!owner || !repo) {
|
|
11408
|
+
throw new Error(`Could not parse owner/repo from: ${githubRepo}`);
|
|
11409
|
+
}
|
|
11410
|
+
return { owner, repo };
|
|
11411
|
+
}
|
|
11412
|
+
var ReplaySubmitError = class extends Error {
|
|
11413
|
+
statusCode;
|
|
11414
|
+
constructor(message, statusCode) {
|
|
11415
|
+
super(message);
|
|
11416
|
+
this.name = "ReplaySubmitError";
|
|
11417
|
+
this.statusCode = statusCode;
|
|
11418
|
+
}
|
|
11419
|
+
};
|
|
11420
|
+
function isSubmitResponse(data) {
|
|
11421
|
+
return typeof data === "object" && data != null && "prUrl" in data && typeof data.prUrl === "string";
|
|
11422
|
+
}
|
|
11122
11423
|
// Annotate the CommonJS export names for ESM import in node:
|
|
11123
11424
|
0 && (module.exports = {
|
|
11124
11425
|
PostGenerationPipeline,
|
|
11426
|
+
ReplaySubmitError,
|
|
11125
11427
|
consolePipelineLogger,
|
|
11126
11428
|
formatBootstrapSummary,
|
|
11127
11429
|
formatReplayPrBody,
|
|
@@ -11133,9 +11435,11 @@ function replayStatus(params) {
|
|
|
11133
11435
|
githubPush,
|
|
11134
11436
|
githubRelease,
|
|
11135
11437
|
logReplaySummary,
|
|
11438
|
+
parseOwnerRepo,
|
|
11136
11439
|
replayForget,
|
|
11137
11440
|
replayInit,
|
|
11138
11441
|
replayResolve,
|
|
11139
11442
|
replayRun,
|
|
11140
|
-
replayStatus
|
|
11443
|
+
replayStatus,
|
|
11444
|
+
submitReplayInit
|
|
11141
11445
|
});
|
package/dist/cli.js
CHANGED
|
@@ -25629,6 +25629,46 @@ var ClonedRepository = class _ClonedRepository {
|
|
|
25629
25629
|
const result = await this.git.raw(["rev-parse", "HEAD^{tree}"]);
|
|
25630
25630
|
return result.trim();
|
|
25631
25631
|
}
|
|
25632
|
+
async getHeadCommitMessage() {
|
|
25633
|
+
await this.git.cwd(this.clonePath);
|
|
25634
|
+
const result = await this.git.raw(["log", "-1", "--format=%B", "HEAD"]);
|
|
25635
|
+
return result.replace(/\n$/, "");
|
|
25636
|
+
}
|
|
25637
|
+
async getHeadParents() {
|
|
25638
|
+
await this.git.cwd(this.clonePath);
|
|
25639
|
+
const result = await this.git.raw(["rev-list", "--parents", "-n", "1", "HEAD"]);
|
|
25640
|
+
const parts = result.trim().split(/\s+/);
|
|
25641
|
+
return parts.slice(1);
|
|
25642
|
+
}
|
|
25643
|
+
/**
|
|
25644
|
+
* Pushes a specific object (commit SHA) to a remote ref.
|
|
25645
|
+
* Used to upload tree and blob objects to the remote without moving a branch.
|
|
25646
|
+
*
|
|
25647
|
+
* Pass `force: true` when overwriting a ref that may already point to an
|
|
25648
|
+
* unrelated commit (e.g. retrying a temp ref after a local rebase rewrites
|
|
25649
|
+
* history so the new SHA is not a descendant of the previous one).
|
|
25650
|
+
*/
|
|
25651
|
+
async pushObjectToRef(localSha, remoteRef, { force = false } = {}) {
|
|
25652
|
+
await this.git.cwd(this.clonePath);
|
|
25653
|
+
const spec = force ? `+${localSha}:${remoteRef}` : `${localSha}:${remoteRef}`;
|
|
25654
|
+
await this.git.raw(["push", "origin", spec]);
|
|
25655
|
+
}
|
|
25656
|
+
/**
|
|
25657
|
+
* Resets the current branch HEAD to the given SHA. After fetching a newly-created remote
|
|
25658
|
+
* signed commit, use this to align the local branch so subsequent operations (tag push,
|
|
25659
|
+
* getHeadSha(), etc.) see the signed SHA.
|
|
25660
|
+
*/
|
|
25661
|
+
async resetHardToSha(sha) {
|
|
25662
|
+
await this.git.cwd(this.clonePath);
|
|
25663
|
+
await this.git.raw(["reset", "--hard", sha]);
|
|
25664
|
+
}
|
|
25665
|
+
/**
|
|
25666
|
+
* Rebases the current branch onto the given remote branch (`git pull --rebase origin <branch>`).
|
|
25667
|
+
*/
|
|
25668
|
+
async pullWithRebase(branch) {
|
|
25669
|
+
await this.git.cwd(this.clonePath);
|
|
25670
|
+
await this.git.raw(["pull", "origin", branch, "--rebase"]);
|
|
25671
|
+
}
|
|
25632
25672
|
async getCommitTreeHash(commitSha) {
|
|
25633
25673
|
await this.git.cwd(this.clonePath);
|
|
25634
25674
|
const result = await this.git.raw(["rev-parse", `${commitSha}^{tree}`]);
|
|
@@ -33456,6 +33496,35 @@ var FERN_BOT_NAME = "fern-api";
|
|
|
33456
33496
|
var FERN_BOT_EMAIL = "115122769+fern-api[bot]@users.noreply.github.com";
|
|
33457
33497
|
var FERN_BOT_LOGIN = "fern-api[bot]";
|
|
33458
33498
|
|
|
33499
|
+
// src/pipeline/steps/BaseStep.ts
|
|
33500
|
+
var BaseStep = class {
|
|
33501
|
+
constructor(outputDir, logger) {
|
|
33502
|
+
this.outputDir = outputDir;
|
|
33503
|
+
this.logger = logger;
|
|
33504
|
+
}
|
|
33505
|
+
outputDir;
|
|
33506
|
+
logger;
|
|
33507
|
+
};
|
|
33508
|
+
|
|
33509
|
+
// src/pipeline/steps/AutoVersionStep.ts
|
|
33510
|
+
var AutoVersionStep = class extends BaseStep {
|
|
33511
|
+
constructor(outputDir, logger, config) {
|
|
33512
|
+
super(outputDir, logger);
|
|
33513
|
+
this.config = config;
|
|
33514
|
+
}
|
|
33515
|
+
config;
|
|
33516
|
+
name = "autoVersion";
|
|
33517
|
+
async execute(_context) {
|
|
33518
|
+
this.logger.info(
|
|
33519
|
+
`AutoVersionStep: no-op scaffold for language=${this.config.language} (logic lands in FER-9980)`
|
|
33520
|
+
);
|
|
33521
|
+
return {
|
|
33522
|
+
executed: true,
|
|
33523
|
+
success: true
|
|
33524
|
+
};
|
|
33525
|
+
}
|
|
33526
|
+
};
|
|
33527
|
+
|
|
33459
33528
|
// src/pipeline/steps/GithubStep.ts
|
|
33460
33529
|
var import_promises3 = require("fs/promises");
|
|
33461
33530
|
var import_path7 = require("path");
|
|
@@ -33588,6 +33657,128 @@ ${prBody}`;
|
|
|
33588
33657
|
return { prTitle, prBody };
|
|
33589
33658
|
}
|
|
33590
33659
|
|
|
33660
|
+
// src/pipeline/github/pushSignedCommit.ts
|
|
33661
|
+
var MAX_CONCURRENT_PUSH_RETRIES = 3;
|
|
33662
|
+
async function pushSignedCommit({
|
|
33663
|
+
repository,
|
|
33664
|
+
octokit,
|
|
33665
|
+
owner,
|
|
33666
|
+
repo,
|
|
33667
|
+
branch,
|
|
33668
|
+
force,
|
|
33669
|
+
rebaseOnConflict = false,
|
|
33670
|
+
logger
|
|
33671
|
+
}) {
|
|
33672
|
+
const tempRef = `refs/temp/fern-${Date.now()}`;
|
|
33673
|
+
let tempRefPushed = false;
|
|
33674
|
+
try {
|
|
33675
|
+
let [localHeadSha, treeSha, message, parents] = await Promise.all([
|
|
33676
|
+
repository.getHeadSha(),
|
|
33677
|
+
repository.getHeadTreeHash(),
|
|
33678
|
+
repository.getHeadCommitMessage(),
|
|
33679
|
+
repository.getHeadParents()
|
|
33680
|
+
]);
|
|
33681
|
+
await repository.pushObjectToRef(localHeadSha, tempRef);
|
|
33682
|
+
tempRefPushed = true;
|
|
33683
|
+
for (let attempt2 = 0; attempt2 < MAX_CONCURRENT_PUSH_RETRIES; attempt2++) {
|
|
33684
|
+
const { data: signedCommit } = await octokit.git.createCommit({
|
|
33685
|
+
owner,
|
|
33686
|
+
repo,
|
|
33687
|
+
message,
|
|
33688
|
+
tree: treeSha,
|
|
33689
|
+
parents
|
|
33690
|
+
});
|
|
33691
|
+
const signedSha = signedCommit.sha;
|
|
33692
|
+
try {
|
|
33693
|
+
await upsertBranchRef({ octokit, owner, repo, branch, sha: signedSha, force });
|
|
33694
|
+
logger.debug(`Updated refs/heads/${branch} to signed commit ${signedSha}`);
|
|
33695
|
+
await syncLocalToSignedCommit({ repository, branch, signedSha });
|
|
33696
|
+
return signedSha;
|
|
33697
|
+
} catch (err) {
|
|
33698
|
+
if (!isNonFastForwardError(err) || force || attempt2 >= MAX_CONCURRENT_PUSH_RETRIES - 1) {
|
|
33699
|
+
throw err;
|
|
33700
|
+
}
|
|
33701
|
+
if (rebaseOnConflict) {
|
|
33702
|
+
logger.warn(
|
|
33703
|
+
`Non-fast-forward on refs/heads/${branch} (attempt ${attempt2 + 1}/${MAX_CONCURRENT_PUSH_RETRIES}); rebasing locally and retrying.`
|
|
33704
|
+
);
|
|
33705
|
+
await repository.pullWithRebase(branch);
|
|
33706
|
+
[localHeadSha, treeSha, message, parents] = await Promise.all([
|
|
33707
|
+
repository.getHeadSha(),
|
|
33708
|
+
repository.getHeadTreeHash(),
|
|
33709
|
+
repository.getHeadCommitMessage(),
|
|
33710
|
+
repository.getHeadParents()
|
|
33711
|
+
]);
|
|
33712
|
+
await repository.pushObjectToRef(localHeadSha, tempRef, { force: true });
|
|
33713
|
+
} else {
|
|
33714
|
+
throw err;
|
|
33715
|
+
}
|
|
33716
|
+
}
|
|
33717
|
+
}
|
|
33718
|
+
throw new Error(
|
|
33719
|
+
`Failed to push signed commit to refs/heads/${branch} after ${MAX_CONCURRENT_PUSH_RETRIES} attempts`
|
|
33720
|
+
);
|
|
33721
|
+
} finally {
|
|
33722
|
+
if (tempRefPushed) {
|
|
33723
|
+
try {
|
|
33724
|
+
await octokit.git.deleteRef({ owner, repo, ref: tempRef.replace(/^refs\//, "") });
|
|
33725
|
+
} catch (err) {
|
|
33726
|
+
logger.debug(`Failed to delete temp ref ${tempRef}: ${extractErrorMessage(err)}`);
|
|
33727
|
+
}
|
|
33728
|
+
}
|
|
33729
|
+
}
|
|
33730
|
+
}
|
|
33731
|
+
async function upsertBranchRef({
|
|
33732
|
+
octokit,
|
|
33733
|
+
owner,
|
|
33734
|
+
repo,
|
|
33735
|
+
branch,
|
|
33736
|
+
sha,
|
|
33737
|
+
force
|
|
33738
|
+
}) {
|
|
33739
|
+
try {
|
|
33740
|
+
await octokit.git.updateRef({
|
|
33741
|
+
owner,
|
|
33742
|
+
repo,
|
|
33743
|
+
ref: `heads/${branch}`,
|
|
33744
|
+
sha,
|
|
33745
|
+
force
|
|
33746
|
+
});
|
|
33747
|
+
} catch (err) {
|
|
33748
|
+
if (isNotFoundError(err)) {
|
|
33749
|
+
await octokit.git.createRef({
|
|
33750
|
+
owner,
|
|
33751
|
+
repo,
|
|
33752
|
+
ref: `refs/heads/${branch}`,
|
|
33753
|
+
sha
|
|
33754
|
+
});
|
|
33755
|
+
return;
|
|
33756
|
+
}
|
|
33757
|
+
throw err;
|
|
33758
|
+
}
|
|
33759
|
+
}
|
|
33760
|
+
async function syncLocalToSignedCommit({
|
|
33761
|
+
repository,
|
|
33762
|
+
branch,
|
|
33763
|
+
signedSha
|
|
33764
|
+
}) {
|
|
33765
|
+
await repository.fetch(["origin", branch]);
|
|
33766
|
+
await repository.resetHardToSha(signedSha);
|
|
33767
|
+
}
|
|
33768
|
+
function hasNumericStatus(err) {
|
|
33769
|
+
return typeof err === "object" && err != null && "status" in err && typeof err.status === "number";
|
|
33770
|
+
}
|
|
33771
|
+
function isNotFoundError(err) {
|
|
33772
|
+
return hasNumericStatus(err) && err.status === 404;
|
|
33773
|
+
}
|
|
33774
|
+
function isNonFastForwardError(err) {
|
|
33775
|
+
if (!hasNumericStatus(err) || err.status !== 422) {
|
|
33776
|
+
return false;
|
|
33777
|
+
}
|
|
33778
|
+
const message = extractErrorMessage(err).toLowerCase();
|
|
33779
|
+
return message.includes("not a fast forward") || message.includes("not a fast-forward") || message.includes("update is not a fast forward");
|
|
33780
|
+
}
|
|
33781
|
+
|
|
33591
33782
|
// src/pipeline/replay-summary.ts
|
|
33592
33783
|
function plural(n, word) {
|
|
33593
33784
|
return `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
@@ -33683,16 +33874,6 @@ function formatReplayPrBody(result, options) {
|
|
|
33683
33874
|
return parts.join("\n");
|
|
33684
33875
|
}
|
|
33685
33876
|
|
|
33686
|
-
// src/pipeline/steps/BaseStep.ts
|
|
33687
|
-
var BaseStep = class {
|
|
33688
|
-
constructor(outputDir, logger) {
|
|
33689
|
-
this.outputDir = outputDir;
|
|
33690
|
-
this.logger = logger;
|
|
33691
|
-
}
|
|
33692
|
-
outputDir;
|
|
33693
|
-
logger;
|
|
33694
|
-
};
|
|
33695
|
-
|
|
33696
33877
|
// src/pipeline/steps/GithubStep.ts
|
|
33697
33878
|
var GithubStep = class extends BaseStep {
|
|
33698
33879
|
constructor(outputDir, logger, config) {
|
|
@@ -33772,13 +33953,17 @@ var GithubStep = class extends BaseStep {
|
|
|
33772
33953
|
});
|
|
33773
33954
|
switch (branchAction) {
|
|
33774
33955
|
case "replay-branch":
|
|
33775
|
-
|
|
33776
|
-
|
|
33777
|
-
|
|
33778
|
-
|
|
33779
|
-
|
|
33780
|
-
|
|
33781
|
-
|
|
33956
|
+
if (shouldPushGenerationBaseTag(replayResult)) {
|
|
33957
|
+
generationBaseSha = await createReplayBranch(
|
|
33958
|
+
repository,
|
|
33959
|
+
prBranch,
|
|
33960
|
+
this.config.commitMessage,
|
|
33961
|
+
replayConflictInfo,
|
|
33962
|
+
this.logger
|
|
33963
|
+
);
|
|
33964
|
+
} else {
|
|
33965
|
+
await repository.createBranchFromHead(prBranch);
|
|
33966
|
+
}
|
|
33782
33967
|
break;
|
|
33783
33968
|
case "create-from-head":
|
|
33784
33969
|
await repository.createBranchFromHead(prBranch);
|
|
@@ -33798,7 +33983,7 @@ var GithubStep = class extends BaseStep {
|
|
|
33798
33983
|
await repository.commitAllChanges(finalCommitMessage2);
|
|
33799
33984
|
this.logger.debug(`Committed changes to local copy of GitHub repository at ${this.outputDir}`);
|
|
33800
33985
|
}
|
|
33801
|
-
if (this.config
|
|
33986
|
+
if (shouldCheckNoDiff(this.config)) {
|
|
33802
33987
|
const noDiff = await repository.treeHashEquals(`origin/${baseBranch}`);
|
|
33803
33988
|
if (noDiff) {
|
|
33804
33989
|
this.logger.info("No changes detected after generation \u2014 skipping PR creation");
|
|
@@ -33811,11 +33996,15 @@ var GithubStep = class extends BaseStep {
|
|
|
33811
33996
|
updatedExistingPr: isUpdatingExistingPR
|
|
33812
33997
|
};
|
|
33813
33998
|
if (!this.config.previewMode) {
|
|
33814
|
-
|
|
33815
|
-
|
|
33816
|
-
|
|
33817
|
-
|
|
33818
|
-
|
|
33999
|
+
await pushSignedCommit({
|
|
34000
|
+
repository,
|
|
34001
|
+
octokit,
|
|
34002
|
+
owner,
|
|
34003
|
+
repo,
|
|
34004
|
+
branch: prBranch,
|
|
34005
|
+
force: isUpdatingExistingPR,
|
|
34006
|
+
logger: this.logger
|
|
34007
|
+
});
|
|
33819
34008
|
const pushedBranch = await repository.getCurrentBranch();
|
|
33820
34009
|
result.branchUrl = `https://github.com/${this.config.uri}/tree/${pushedBranch}`;
|
|
33821
34010
|
this.logger.info(`Pushed branch: ${result.branchUrl}`);
|
|
@@ -33916,7 +34105,7 @@ var GithubStep = class extends BaseStep {
|
|
|
33916
34105
|
const finalCommitMessage = this.config.commitMessage ?? "SDK Generation";
|
|
33917
34106
|
await repository.commitAllChanges(finalCommitMessage);
|
|
33918
34107
|
this.logger.debug(`Committed changes to local copy of GitHub repository at ${this.outputDir}`);
|
|
33919
|
-
if (this.config
|
|
34108
|
+
if (shouldCheckNoDiff(this.config)) {
|
|
33920
34109
|
const noDiff = await repository.treeHashEquals(`origin/${baseBranch}`);
|
|
33921
34110
|
if (noDiff) {
|
|
33922
34111
|
this.logger.info("No changes detected after generation \u2014 skipping push");
|
|
@@ -33928,7 +34117,18 @@ var GithubStep = class extends BaseStep {
|
|
|
33928
34117
|
success: true
|
|
33929
34118
|
};
|
|
33930
34119
|
if (!this.config.previewMode) {
|
|
33931
|
-
|
|
34120
|
+
const octokit = new Octokit2({ auth: this.config.token });
|
|
34121
|
+
const { owner, repo } = parseRepository(this.config.uri);
|
|
34122
|
+
await pushSignedCommit({
|
|
34123
|
+
repository,
|
|
34124
|
+
octokit,
|
|
34125
|
+
owner,
|
|
34126
|
+
repo,
|
|
34127
|
+
branch: baseBranch,
|
|
34128
|
+
force: false,
|
|
34129
|
+
rebaseOnConflict: true,
|
|
34130
|
+
logger: this.logger
|
|
34131
|
+
});
|
|
33932
34132
|
const pushedBranch = await repository.getCurrentBranch();
|
|
33933
34133
|
result.branchUrl = `https://github.com/${this.config.uri}/tree/${pushedBranch}`;
|
|
33934
34134
|
this.logger.info(`Pushed branch: ${result.branchUrl}`);
|
|
@@ -33987,6 +34187,12 @@ _Fern Run ID: \`${config.runId}\`_`;
|
|
|
33987
34187
|
function shouldEnableAutomerge(config) {
|
|
33988
34188
|
return config.automationMode === true && config.autoMerge === true && config.hasBreakingChanges !== true;
|
|
33989
34189
|
}
|
|
34190
|
+
function shouldCheckNoDiff(config) {
|
|
34191
|
+
return config.skipIfNoDiff === true;
|
|
34192
|
+
}
|
|
34193
|
+
function shouldPushGenerationBaseTag(replayResult) {
|
|
34194
|
+
return (replayResult?.patchesWithConflicts ?? 0) > 0;
|
|
34195
|
+
}
|
|
33990
34196
|
function resolveBranchAction({
|
|
33991
34197
|
automationMode,
|
|
33992
34198
|
skipCommit,
|
|
@@ -34125,18 +34331,24 @@ async function replayRun(params) {
|
|
|
34125
34331
|
}
|
|
34126
34332
|
}
|
|
34127
34333
|
if (shouldSync && tagSha != null) {
|
|
34128
|
-
|
|
34129
|
-
|
|
34130
|
-
|
|
34131
|
-
|
|
34132
|
-
|
|
34133
|
-
|
|
34134
|
-
|
|
34135
|
-
|
|
34136
|
-
|
|
34137
|
-
|
|
34334
|
+
if (!isTagMergedIntoHead(outputDir, tagSha)) {
|
|
34335
|
+
logger?.warn(
|
|
34336
|
+
`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).`
|
|
34337
|
+
);
|
|
34338
|
+
} else {
|
|
34339
|
+
const syncService = new ReplayService(outputDir, { enabled: true });
|
|
34340
|
+
await syncService.syncFromDivergentMerge(tagSha, {
|
|
34341
|
+
cliVersion,
|
|
34342
|
+
generatorVersions,
|
|
34343
|
+
baseBranchHead: baseBranchHead ?? void 0
|
|
34344
|
+
});
|
|
34345
|
+
try {
|
|
34346
|
+
const freshLockManager = new LockfileManager(outputDir);
|
|
34347
|
+
if (freshLockManager.exists()) {
|
|
34348
|
+
previousGenerationSha = freshLockManager.read().current_generation;
|
|
34349
|
+
}
|
|
34350
|
+
} catch {
|
|
34138
34351
|
}
|
|
34139
|
-
} catch {
|
|
34140
34352
|
}
|
|
34141
34353
|
}
|
|
34142
34354
|
}
|
|
@@ -34186,6 +34398,30 @@ async function replayRun(params) {
|
|
|
34186
34398
|
baseBranchHead: resolvedBaseBranchHead
|
|
34187
34399
|
};
|
|
34188
34400
|
}
|
|
34401
|
+
function isTagMergedIntoHead(cwd2, tagSha) {
|
|
34402
|
+
try {
|
|
34403
|
+
(0, import_child_process3.execFileSync)("git", ["merge-base", "--is-ancestor", tagSha, "HEAD"], {
|
|
34404
|
+
cwd: cwd2,
|
|
34405
|
+
stdio: "pipe"
|
|
34406
|
+
});
|
|
34407
|
+
return true;
|
|
34408
|
+
} catch {
|
|
34409
|
+
}
|
|
34410
|
+
const tagTree = gitRevParse(cwd2, `${tagSha}^{tree}`);
|
|
34411
|
+
if (tagTree == null) {
|
|
34412
|
+
return false;
|
|
34413
|
+
}
|
|
34414
|
+
try {
|
|
34415
|
+
const trees = (0, import_child_process3.execFileSync)("git", ["log", "HEAD", "--format=%T", "--max-count=500"], {
|
|
34416
|
+
cwd: cwd2,
|
|
34417
|
+
encoding: "utf-8",
|
|
34418
|
+
stdio: "pipe"
|
|
34419
|
+
});
|
|
34420
|
+
return trees.split("\n").some((t2) => t2.trim() === tagTree);
|
|
34421
|
+
} catch {
|
|
34422
|
+
return false;
|
|
34423
|
+
}
|
|
34424
|
+
}
|
|
34189
34425
|
function gitRevParse(cwd2, rev) {
|
|
34190
34426
|
try {
|
|
34191
34427
|
return (0, import_child_process3.execFileSync)("git", ["rev-parse", "--verify", rev], {
|
|
@@ -34287,6 +34523,9 @@ var PostGenerationPipeline = class {
|
|
|
34287
34523
|
);
|
|
34288
34524
|
config.replay.enabled = false;
|
|
34289
34525
|
}
|
|
34526
|
+
if (config.autoVersion?.enabled) {
|
|
34527
|
+
this.steps.push(new AutoVersionStep(config.outputDir, this.logger, config.autoVersion));
|
|
34528
|
+
}
|
|
34290
34529
|
if (config.replay?.enabled) {
|
|
34291
34530
|
this.steps.push(
|
|
34292
34531
|
new ReplayStep(
|
|
@@ -34327,6 +34566,9 @@ var PostGenerationPipeline = class {
|
|
|
34327
34566
|
if (step.name === "replay") {
|
|
34328
34567
|
result.steps.replay = stepResult;
|
|
34329
34568
|
pipelineContext.previousStepResults.replay = stepResult;
|
|
34569
|
+
} else if (step.name === "autoVersion") {
|
|
34570
|
+
result.steps.autoVersion = stepResult;
|
|
34571
|
+
pipelineContext.previousStepResults.autoVersion = stepResult;
|
|
34330
34572
|
} else if (step.name === "fernignore") {
|
|
34331
34573
|
result.steps.fernignore = stepResult;
|
|
34332
34574
|
} else if (step.name === "github") {
|