-
Notifications
You must be signed in to change notification settings - Fork 332
Expand file tree
/
Copy pathreplay-submit-init.ts
More file actions
111 lines (99 loc) · 3.7 KB
/
Copy pathreplay-submit-init.ts
File metadata and controls
111 lines (99 loc) · 3.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import type { ReplayInitResult } from "./replay-init.js";
export interface SubmitReplayInitParams {
/** Fiddle API origin (e.g. "https://fiddle.buildwithfern.com") */
fiddleOrigin: string;
/** Fern auth token */
fernToken: string;
/** GitHub repository (e.g. "owner/repo" or full URL) */
githubRepo: string;
/** Result from replayInit() */
initResult: ReplayInitResult;
}
export interface SubmitReplayInitResult {
/** URL of the PR created by Fiddle */
prUrl: string;
}
/**
* Submits a Replay init lockfile to the Fiddle API for server-side PR creation.
*
* Extracts owner/repo from the GitHub repository string, posts the lockfile
* and related metadata to the Fiddle remote-gen endpoint, and returns the
* resulting PR URL.
*
* This function is shared between CLI v1 and CLI v2 to avoid duplicating
* the Fiddle submission logic.
*/
export async function submitReplayInit(params: SubmitReplayInitParams): Promise<SubmitReplayInitResult> {
const { fiddleOrigin, fernToken, githubRepo, initResult } = params;
const { owner, repo } = parseOwnerRepo(githubRepo);
const response = await fetch(`${fiddleOrigin}/api/remote-gen/replay/init`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${fernToken}`
},
body: JSON.stringify({
owner,
repo,
lockfileContents: initResult.lockfileContent,
fernignoreEntries: initResult.fernignoreEntries,
gitattributesEntries: initResult.gitattributesEntries,
prBody: initResult.prBody
})
});
if (!response.ok) {
if (response.status === 404) {
throw new ReplaySubmitError(
"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.",
response.status
);
}
const body = await response.text();
throw new ReplaySubmitError(`Failed to create PR via Fern: ${body}`, response.status);
}
const data: unknown = await response.json();
if (!isSubmitResponse(data)) {
throw new ReplaySubmitError("Unexpected response from Fern: missing prUrl field.");
}
return { prUrl: data.prUrl };
}
/**
* Parses a GitHub repository string into owner and repo components.
* Accepts formats: "owner/repo", "github.com/owner/repo",
* "https://github.com/owner/repo", "https://github.com/owner/repo.git"
*/
export function parseOwnerRepo(githubRepo: string): { owner: string; repo: string } {
const cleaned = githubRepo
.replace(/^https?:\/\//, "")
.replace(/\.git$/, "")
.replace(/^github\.com\//, "");
const parts = cleaned.split("/");
const owner = parts[parts.length - 2];
const repo = parts[parts.length - 1];
if (!owner || !repo) {
throw new Error(`Could not parse owner/repo from: ${githubRepo}`);
}
return { owner, repo };
}
/**
* Error thrown when submitting a Replay init to Fiddle fails.
* Consumers can inspect `statusCode` to distinguish between
* different failure modes (e.g. 404 = GitHub App not installed).
*/
export class ReplaySubmitError extends Error {
public readonly statusCode: number | undefined;
constructor(message: string, statusCode?: number) {
super(message);
this.name = "ReplaySubmitError";
this.statusCode = statusCode;
}
}
function isSubmitResponse(data: unknown): data is { prUrl: string } {
return (
typeof data === "object" &&
data != null &&
"prUrl" in data &&
typeof (data as Record<string, unknown>).prUrl === "string"
);
}