mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 17:59:08 +00:00
fix(ci): defer proposals for merged release commits
This commit is contained in:
1 parent
d74549e495
commit
5eef239dfd
10 files changed
+304
-68
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { valid } from "semver";
|
||||
import { z } from "zod";
|
||||
import { readMergedReleasePullRequest } from "./release-pull-request.mjs";
|
||||
import {
|
||||
releaseInventorySchema,
|
||||
verifyInventoryHistory,
|
||||
@@ -8,17 +9,6 @@ import {
|
||||
|
||||
const sha = z.string().regex(/^[a-f0-9]{40}$/u);
|
||||
const repository = z.string().regex(/^[\w.-]+\/[\w.-]+$/u);
|
||||
const pullRequest = z.object({
|
||||
number: z.number().int().positive(),
|
||||
body: z.string(),
|
||||
merged_at: z.string().nullable(),
|
||||
merge_commit_sha: sha.nullable(),
|
||||
head: z.object({
|
||||
ref: z.string(),
|
||||
repo: z.object({ full_name: z.string() }).nullable(),
|
||||
}),
|
||||
labels: z.array(z.object({ name: z.string() })),
|
||||
});
|
||||
|
||||
/** Read the reviewed Git snapshot and live PR before Release Please can mutate tags. */
|
||||
export async function verifyNotesPublication(run, options) {
|
||||
@@ -39,41 +29,13 @@ export async function verifyNotesPublication(run, options) {
|
||||
.parse(JSON.parse(await read("package.json")));
|
||||
if (valid(version) !== version || version.includes("+"))
|
||||
throw new Error("Invalid reviewed package version");
|
||||
let candidates;
|
||||
if (options.pr !== undefined) {
|
||||
const number = z.coerce.number().int().positive().parse(options.pr);
|
||||
candidates = [
|
||||
pullRequest.parse(
|
||||
JSON.parse(await run("gh", ["api", `repos/${repo}/pulls/${number}`])),
|
||||
),
|
||||
];
|
||||
} else {
|
||||
candidates = z
|
||||
.array(z.array(pullRequest))
|
||||
.parse(
|
||||
JSON.parse(
|
||||
await run("gh", [
|
||||
"api",
|
||||
"--paginate",
|
||||
"--slurp",
|
||||
`repos/${repo}/commits/${source}/pulls?per_page=100`,
|
||||
]),
|
||||
),
|
||||
)
|
||||
.flat();
|
||||
}
|
||||
const matching = candidates.filter(
|
||||
(pr) =>
|
||||
pr.merged_at !== null &&
|
||||
pr.merge_commit_sha === source &&
|
||||
pr.head.repo?.full_name.toLowerCase() === repo.toLowerCase() &&
|
||||
pr.head.ref.startsWith("release-please--branches--"),
|
||||
);
|
||||
if (matching.length !== 1)
|
||||
const pr = await readMergedReleasePullRequest(run, { ...options, source });
|
||||
if (pr === undefined)
|
||||
throw new Error(
|
||||
"Expected exactly one merged Release Please PR for the reviewed source",
|
||||
);
|
||||
const pr = matching[0];
|
||||
if (pr.body === null)
|
||||
throw new Error("The merged release PR has no release notes body");
|
||||
const path = `docs/releases/${version}.contributions.json`;
|
||||
const present =
|
||||
(
|
||||
|
||||
@@ -6,3 +6,19 @@ export function releaseProposalSearchDepth(
|
||||
run: (command: string, args: string[]) => Promise<string>,
|
||||
sourceSha: string,
|
||||
): Promise<number>;
|
||||
|
||||
export function createReleaseProposal<T>(
|
||||
run: (command: string, args: string[]) => Promise<string>,
|
||||
context: {
|
||||
repository: string;
|
||||
sourceSha: string;
|
||||
targetBranch: string;
|
||||
eventName: string | undefined;
|
||||
},
|
||||
loadManifest: (depth: number) => Promise<{
|
||||
createPullRequests(): Promise<Array<T | undefined>>;
|
||||
}>,
|
||||
): Promise<
|
||||
| { kind: "release-merge"; pullRequest: number }
|
||||
| { kind: "proposal"; pullRequests: T[] }
|
||||
>;
|
||||
@@ -5,6 +5,7 @@ import { dirname, relative, sep } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { requireCompleteReleaseHistory } from "./release-notes.mjs";
|
||||
import { readMergedReleasePullRequest } from "./release-pull-request.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
const require = createRequire(import.meta.url);
|
||||
@@ -74,3 +75,22 @@ export async function releaseProposalSearchDepth(run, sourceSha) {
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/** The merged-release workflow owns tag creation; its push needs no proposal. */
|
||||
export async function createReleaseProposal(run, context, loadManifest) {
|
||||
if (context.eventName === "push" && context.targetBranch === "main") {
|
||||
const release = await readMergedReleasePullRequest(run, {
|
||||
repository: context.repository,
|
||||
source: context.sourceSha,
|
||||
targetBranch: context.targetBranch,
|
||||
});
|
||||
if (release !== undefined)
|
||||
return { kind: "release-merge", pullRequest: release.number };
|
||||
}
|
||||
const depth = await releaseProposalSearchDepth(run, context.sourceSha);
|
||||
const manifest = await loadManifest(depth);
|
||||
return {
|
||||
kind: "proposal",
|
||||
pullRequests: (await manifest.createPullRequests()).filter(Boolean),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const sha = z.string().regex(/^[a-f0-9]{40}$/u);
|
||||
const repository = z.string().regex(/^[\w.-]+\/[\w.-]+$/u);
|
||||
const pullRequest = z.object({
|
||||
number: z.number().int().positive(),
|
||||
body: z.string().nullable(),
|
||||
merged_at: z.string().nullable(),
|
||||
merge_commit_sha: sha.nullable(),
|
||||
base: z.object({ ref: z.string() }),
|
||||
head: z.object({
|
||||
ref: z.string(),
|
||||
repo: z.object({ full_name: z.string() }).nullable(),
|
||||
}),
|
||||
labels: z.array(z.object({ name: z.string() })),
|
||||
});
|
||||
|
||||
/** Own REST release-PR identity for proposal admission and publication checks. */
|
||||
export async function readMergedReleasePullRequest(run, options) {
|
||||
const repo = repository.parse(options.repository);
|
||||
const source = sha.parse(options.source);
|
||||
let candidates;
|
||||
if (options.pr !== undefined) {
|
||||
const number = z.coerce.number().int().positive().parse(options.pr);
|
||||
candidates = [
|
||||
pullRequest.parse(
|
||||
JSON.parse(await run("gh", ["api", `repos/${repo}/pulls/${number}`])),
|
||||
),
|
||||
];
|
||||
} else {
|
||||
candidates = z
|
||||
.array(z.array(pullRequest))
|
||||
.parse(
|
||||
JSON.parse(
|
||||
await run("gh", [
|
||||
"api",
|
||||
"--paginate",
|
||||
"--slurp",
|
||||
`repos/${repo}/commits/${source}/pulls?per_page=100`,
|
||||
]),
|
||||
),
|
||||
)
|
||||
.flat();
|
||||
}
|
||||
const matching = candidates.filter(
|
||||
(pr) =>
|
||||
pr.merged_at !== null &&
|
||||
pr.merge_commit_sha === source &&
|
||||
pr.head.repo?.full_name.toLowerCase() === repo.toLowerCase() &&
|
||||
pr.head.ref.startsWith("release-please--branches--") &&
|
||||
(options.targetBranch === undefined ||
|
||||
(pr.base.ref === options.targetBranch &&
|
||||
pr.head.ref.startsWith(
|
||||
`release-please--branches--${options.targetBranch}--`,
|
||||
))),
|
||||
);
|
||||
if (matching.length > 1)
|
||||
throw new Error("Multiple merged Release Please PRs match the source");
|
||||
return matching[0];
|
||||
}
|
||||
Reference in new issue
Block a user