Skip to content

Commit d43d09c

Browse files
committed
[wrangler] Add PR metadata to preview deployments
1 parent 5fd6127 commit d43d09c

4 files changed

Lines changed: 370 additions & 5 deletions

File tree

packages/deploy-helpers/src/preview/api.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ export interface DeploymentResource {
7979
limits?: CfUserLimits;
8080
placement?: CfPlacement;
8181
cache?: CacheOptions;
82+
annotations?: {
83+
"workers/message"?: string;
84+
"workers/pull_request_number"?: string;
85+
"workers/pull_request_url"?: string;
86+
"workers/repository_url"?: string;
87+
"workers/tag"?: string;
88+
};
8289
env?: EnvBindings;
8390
created_on: string;
8491
}
@@ -102,6 +109,9 @@ export type CreatePreviewDeploymentRequestParams = {
102109
compatibility_flags?: string[];
103110
annotations?: {
104111
"workers/message"?: string;
112+
"workers/pull_request_number"?: string;
113+
"workers/pull_request_url"?: string;
114+
"workers/repository_url"?: string;
105115
"workers/tag"?: string;
106116
};
107117
migrations?: CfWorkerInit["migrations"];

packages/deploy-helpers/src/preview/preview.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ import {
2727
getBranchName,
2828
getHeadCommitMessage,
2929
getHeadCommitRef,
30+
getPullRequestMetadata,
31+
getRepositoryUrl,
3032
resolveWorkerName,
3133
shouldUseCIMetadataFallback,
3234
} from "./shared";
35+
import type { PullRequestMetadata } from "./shared";
3336
import type { WorkerBuildResult } from "../shared/types";
3437
import type {
3538
Binding,
@@ -81,6 +84,10 @@ type MergedVersionLevel = {
8184
value: Config["cache"];
8285
fromConfig: boolean;
8386
};
87+
pull_request?: {
88+
value: string;
89+
fromConfig: false;
90+
};
8491
assets?: {
8592
value: {
8693
directory?: string;
@@ -240,6 +247,8 @@ async function assemblePreviewDeploymentSettings(
240247
options: {
241248
message?: string;
242249
tag?: string;
250+
repositoryUrl?: string;
251+
pullRequest?: PullRequestMetadata;
243252
assetsOptions?: PreviewAssetsOptions;
244253
}
245254
): Promise<CreatePreviewDeploymentRequestParams> {
@@ -276,9 +285,16 @@ async function assemblePreviewDeploymentSettings(
276285
if (config.compatibility_flags && config.compatibility_flags.length > 0) {
277286
request.compatibility_flags = config.compatibility_flags;
278287
}
279-
if (options.message || options.tag) {
288+
const repositoryUrl = options.repositoryUrl;
289+
const pullRequest = options.pullRequest;
290+
if (options.message || options.tag || repositoryUrl || pullRequest) {
280291
request.annotations = {
281292
...(options.message && { "workers/message": options.message }),
293+
...(pullRequest?.number && {
294+
"workers/pull_request_number": pullRequest.number,
295+
}),
296+
...(pullRequest?.url && { "workers/pull_request_url": pullRequest.url }),
297+
...(repositoryUrl && { "workers/repository_url": repositoryUrl }),
282298
...(options.tag && { "workers/tag": options.tag }),
283299
};
284300
}
@@ -374,7 +390,9 @@ function buildMergedScriptLevel(
374390

375391
function buildMergedVersionLevel(
376392
config: Config,
377-
deployment: DeploymentResource
393+
deployment: DeploymentResource,
394+
repositoryUrl?: string,
395+
pullRequest?: PullRequestMetadata
378396
): MergedVersionLevel {
379397
const previews = config.previews as PreviewsConfig | undefined;
380398
const configBindingNames = new Set(
@@ -429,6 +447,16 @@ function buildMergedVersionLevel(
429447
fromConfig: previews?.cache !== undefined || config.cache !== undefined,
430448
};
431449
}
450+
const deploymentPullRequestUrl =
451+
deployment.annotations?.["workers/pull_request_url"] ?? pullRequest?.url;
452+
const deploymentPullRequestNumber =
453+
deployment.annotations?.["workers/pull_request_number"] ?? pullRequest?.number;
454+
if (deploymentPullRequestUrl || deploymentPullRequestNumber) {
455+
result.pull_request = {
456+
value: deploymentPullRequestUrl ?? `#${deploymentPullRequestNumber}`,
457+
fromConfig: false,
458+
};
459+
}
432460
if (config.assets) {
433461
result.assets = {
434462
value: {
@@ -565,6 +593,13 @@ function formatDeploymentResource(
565593
versionLevel.cache.fromConfig,
566594
]);
567595
}
596+
if (versionLevel.pull_request !== undefined) {
597+
settingsRows.push([
598+
"pull_request",
599+
versionLevel.pull_request.value,
600+
versionLevel.pull_request.fromConfig,
601+
]);
602+
}
568603
if (settingsRows.length > 0) {
569604
lines.push("");
570605
lines.push(...formatAlignedRows(settingsRows));
@@ -682,6 +717,8 @@ export async function preview(
682717
!args.message && shouldUseCIMetadataFallback()
683718
? getHeadCommitMessage()
684719
: undefined;
720+
const repositoryUrl = getRepositoryUrl();
721+
const pullRequest = getPullRequestMetadata();
685722

686723
let existingPreview: PreviewResource | null = null;
687724
try {
@@ -732,6 +769,8 @@ export async function preview(
732769
{
733770
message: args.message ?? fallbackMessage,
734771
tag: args.tag ?? fallbackTag,
772+
repositoryUrl,
773+
pullRequest,
735774
assetsOptions,
736775
}
737776
);
@@ -750,7 +789,12 @@ export async function preview(
750789
);
751790
} else {
752791
const scriptLevel = buildMergedScriptLevel(config, previewResource);
753-
const versionLevel = buildMergedVersionLevel(config, deployment);
792+
const versionLevel = buildMergedVersionLevel(
793+
config,
794+
deployment,
795+
repositoryUrl,
796+
pullRequest
797+
);
754798
const configName = configFileName(config.configPath);
755799
logger.log(
756800
formatPreviewResource(

packages/deploy-helpers/src/preview/shared.ts

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { execSync } from "node:child_process";
2+
import { readFileSync } from "node:fs";
23
import {
34
configFileName,
45
getWorkersCIBranchName,
@@ -55,6 +56,165 @@ export function getHeadCommitMessage(): string | undefined {
5556
}
5657
}
5758

59+
function normalizeRepositoryUrl(repositoryUrl: string): string | undefined {
60+
const trimmed = repositoryUrl.trim();
61+
if (!trimmed) {
62+
return undefined;
63+
}
64+
65+
const scpLikeSshMatch = trimmed.match(/^git@([^:]+):(.+)$/);
66+
if (scpLikeSshMatch) {
67+
const [, host, pathname] = scpLikeSshMatch;
68+
return `https://${host}/${pathname.replace(/\.git$/, "")}`;
69+
}
70+
71+
try {
72+
const url = new URL(trimmed);
73+
if (url.protocol === "ssh:" && url.username === "git") {
74+
return `https://${url.host}${url.pathname.replace(/\.git$/, "")}`;
75+
}
76+
77+
if (url.protocol !== "https:" && url.protocol !== "http:") {
78+
return undefined;
79+
}
80+
81+
url.username = "";
82+
url.password = "";
83+
url.search = "";
84+
url.hash = "";
85+
url.pathname = url.pathname.replace(/\.git$/, "");
86+
return url.toString().replace(/\/$/, "");
87+
} catch {
88+
return undefined;
89+
}
90+
}
91+
92+
export function getRepositoryUrl(): string | undefined {
93+
const repositoryUrl =
94+
process.env.CI_PROJECT_URL ??
95+
process.env.CI_REPOSITORY_URL ??
96+
process.env.CIRCLE_REPOSITORY_URL ??
97+
process.env.BUILDKITE_REPO ??
98+
process.env.BITBUCKET_GIT_HTTP_ORIGIN ??
99+
process.env.BITBUCKET_GIT_SSH_ORIGIN ??
100+
process.env.REPOSITORY_URL;
101+
if (repositoryUrl) {
102+
return normalizeRepositoryUrl(repositoryUrl);
103+
}
104+
105+
if (process.env.GITHUB_REPOSITORY) {
106+
const githubServerUrl = process.env.GITHUB_SERVER_URL ?? "https://github.com";
107+
return normalizeRepositoryUrl(
108+
`${githubServerUrl.replace(/\/$/, "")}/${process.env.GITHUB_REPOSITORY}`
109+
);
110+
}
111+
112+
try {
113+
execSync(`git rev-parse --is-inside-work-tree`, { stdio: "ignore" });
114+
return normalizeRepositoryUrl(
115+
execSync(`git config --get remote.origin.url`).toString()
116+
);
117+
} catch {
118+
return undefined;
119+
}
120+
}
121+
122+
export type PullRequestMetadata = {
123+
number?: string;
124+
url?: string;
125+
};
126+
127+
function normalizePullRequestNumber(number: string | number | undefined) {
128+
if (number === undefined) {
129+
return undefined;
130+
}
131+
132+
const normalizedNumber = String(number).trim();
133+
return normalizedNumber ? normalizedNumber : undefined;
134+
}
135+
136+
function getGitHubPullRequestMetadata(): PullRequestMetadata | undefined {
137+
if (process.env.GITHUB_EVENT_PATH) {
138+
try {
139+
const event = JSON.parse(
140+
readFileSync(process.env.GITHUB_EVENT_PATH, "utf8")
141+
) as {
142+
pull_request?: { html_url?: string; number?: number };
143+
};
144+
const number = normalizePullRequestNumber(event.pull_request?.number);
145+
const url = event.pull_request?.html_url
146+
? normalizeRepositoryUrl(event.pull_request.html_url)
147+
: undefined;
148+
if (number || url) {
149+
return { number, url };
150+
}
151+
} catch {
152+
// Fall back to environment-derived metadata below.
153+
}
154+
}
155+
156+
const refPullRequestNumber = process.env.GITHUB_REF?.match(
157+
/^refs\/pull\/(\d+)\//
158+
)?.[1];
159+
const number = normalizePullRequestNumber(refPullRequestNumber);
160+
if (!number || !process.env.GITHUB_REPOSITORY) {
161+
return undefined;
162+
}
163+
164+
const githubServerUrl = process.env.GITHUB_SERVER_URL ?? "https://github.com";
165+
return {
166+
number,
167+
url: normalizeRepositoryUrl(
168+
`${githubServerUrl.replace(/\/$/, "")}/${process.env.GITHUB_REPOSITORY}/pull/${number}`
169+
),
170+
};
171+
}
172+
173+
function getGitLabPullRequestMetadata(): PullRequestMetadata | undefined {
174+
const number = normalizePullRequestNumber(process.env.CI_MERGE_REQUEST_IID);
175+
const projectUrl =
176+
process.env.CI_MERGE_REQUEST_PROJECT_URL ?? process.env.CI_PROJECT_URL;
177+
if (!number || !projectUrl) {
178+
return undefined;
179+
}
180+
181+
const normalizedProjectUrl = projectUrl.replace(/\.git$/, "").replace(/\/$/, "");
182+
return {
183+
number,
184+
url: normalizeRepositoryUrl(
185+
`${normalizedProjectUrl}/-/merge_requests/${number}`
186+
),
187+
};
188+
}
189+
190+
function getDirectPullRequestMetadata(): PullRequestMetadata | undefined {
191+
const directUrl =
192+
process.env.PULL_REQUEST_URL ??
193+
process.env.PR_URL ??
194+
process.env.CHANGE_URL ??
195+
process.env.CIRCLE_PULL_REQUEST;
196+
const number = normalizePullRequestNumber(
197+
process.env.PULL_REQUEST_NUMBER ??
198+
process.env.PR_NUMBER ??
199+
process.env.CHANGE_ID
200+
);
201+
const url = directUrl ? normalizeRepositoryUrl(directUrl) : undefined;
202+
203+
if (number || url) {
204+
return { number, url };
205+
}
206+
207+
return undefined;
208+
}
209+
210+
export function getPullRequestMetadata(): PullRequestMetadata | undefined {
211+
return (
212+
getDirectPullRequestMetadata() ??
213+
getGitHubPullRequestMetadata() ??
214+
getGitLabPullRequestMetadata()
215+
);
216+
}
217+
58218
export function resolveWorkerName(
59219
args: { workerName?: string; "worker-name"?: string },
60220
config: Config

0 commit comments

Comments
 (0)