mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(ci): filter finalized release proposals before pagination
This commit is contained in:
1 parent
4d88e4f07e
commit
e389d6ea4e
2 files changed
+192
-2
No files matched your search
@@ -47,8 +47,35 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
run: |
|
||||
finalized=$(gh api --paginate "repos/${GH_REPO}/pulls?state=open&base=main&per_page=100" \
|
||||
--jq '.[] | select(.head.repo.full_name == .base.repo.full_name) | select(.head.ref | startswith("release-please--branches--main--")) | select(any(.labels[]; .name == "rea:release-notes-finalized")) | .number')
|
||||
# Filter at the repository connection, without a search-index dependency.
|
||||
pages=$(gh api graphql --paginate --slurp \
|
||||
-f owner="${GH_REPO%/*}" -f name="${GH_REPO#*/}" \
|
||||
-f query='query($owner:String!,$name:String!,$endCursor:String) {
|
||||
repository(owner:$owner,name:$name) {
|
||||
nameWithOwner
|
||||
pullRequests(first:100,after:$endCursor,states:OPEN,baseRefName:"main",labels:["rea:release-notes-finalized"]) {
|
||||
nodes { number headRefName headRepository { nameWithOwner } }
|
||||
pageInfo { hasNextPage endCursor }
|
||||
}
|
||||
}
|
||||
}')
|
||||
finalized=$(jq -er '
|
||||
if type != "array" or length == 0 then error("Missing GitHub pages") else . end |
|
||||
if .[-1].data.repository.pullRequests.pageInfo.hasNextPage != false
|
||||
then error("Incomplete GitHub pagination") else . end |
|
||||
map(
|
||||
if (.errors // [] | length) != 0 then error("GitHub query failed") else . end |
|
||||
.data.repository |
|
||||
if (.nameWithOwner | type) != "string" or (.pullRequests.nodes | type) != "array" or
|
||||
(.pullRequests.pageInfo.hasNextPage | type) != "boolean"
|
||||
then error("Invalid GitHub repository connection") else . end |
|
||||
.nameWithOwner as $repo | .pullRequests.nodes[] |
|
||||
if (.number | type) != "number" or (.headRefName | type) != "string" or
|
||||
(.headRepository != null and (.headRepository.nameWithOwner | type) != "string")
|
||||
then error("Invalid GitHub pull request") else . end |
|
||||
select(.headRepository.nameWithOwner == $repo) |
|
||||
select(.headRefName | startswith("release-please--branches--main--")) | .number
|
||||
) | map(tostring) | join(" ")' <<< "$pages")
|
||||
if [[ -n "${finalized}" ]]; then
|
||||
echo "Finalized release notes on PR ${finalized}; automatic proposal refresh is paused." >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "frozen=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -138,6 +138,169 @@ it("keeps main pushes proposal-only and gates merged-release automation", async
|
||||
);
|
||||
});
|
||||
|
||||
const finalizedRelease = {
|
||||
number: 1399,
|
||||
headRefName: "release-please--branches--main--components--rea-agents",
|
||||
headRepository: { nameWithOwner: "fixture/rea" },
|
||||
};
|
||||
|
||||
function editorialPage(nodes: unknown[], hasNextPage = false) {
|
||||
return {
|
||||
data: {
|
||||
repository: {
|
||||
nameWithOwner: "fixture/rea",
|
||||
pullRequests: {
|
||||
nodes,
|
||||
pageInfo: { hasNextPage, endCursor: hasNextPage ? "next" : null },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "finalized release",
|
||||
pages: [editorialPage([finalizedRelease])],
|
||||
frozen: true,
|
||||
},
|
||||
{ name: "no finalized PR", pages: [editorialPage([])], frozen: false },
|
||||
{
|
||||
name: "fork and unrelated head branch",
|
||||
pages: [
|
||||
editorialPage([
|
||||
{ ...finalizedRelease, headRepository: { nameWithOwner: "fork/rea" } },
|
||||
{
|
||||
...finalizedRelease,
|
||||
headRefName: "release-please--branches--other--component",
|
||||
},
|
||||
{ ...finalizedRelease, headRepository: null },
|
||||
]),
|
||||
],
|
||||
frozen: false,
|
||||
},
|
||||
{
|
||||
name: "finalized release on a later page",
|
||||
pages: [editorialPage([], true), editorialPage([finalizedRelease])],
|
||||
frozen: true,
|
||||
},
|
||||
])("preserves the proposal freeze for $name", async ({ pages, frozen }) => {
|
||||
const step = (await readReleaseWorkflow()).jobs["release-proposal"].steps[0];
|
||||
const directory = await createTestTempDirectory("rea-release-editorial-");
|
||||
const output = join(directory, "output");
|
||||
const summary = join(directory, "summary");
|
||||
const args = join(directory, "args");
|
||||
await writeFile(output, "");
|
||||
await writeFile(summary, "");
|
||||
await writeFile(join(directory, "pages"), JSON.stringify(pages));
|
||||
await writeFile(
|
||||
join(directory, "gh"),
|
||||
'#!/bin/sh\nprintf "%s\n" "$@" > "$GH_ARGS"\ncat "$GH_PAGES"\n',
|
||||
{ mode: 0o755 },
|
||||
);
|
||||
await execFileAsync(
|
||||
"bash",
|
||||
["-e", "-o", "pipefail", "-c", z.string().parse(step?.run)],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `${directory}${delimiter}${process.env.PATH ?? ""}`,
|
||||
GH_REPO: "fixture/rea",
|
||||
GH_ARGS: args,
|
||||
GH_PAGES: join(directory, "pages"),
|
||||
GITHUB_OUTPUT: output,
|
||||
GITHUB_STEP_SUMMARY: summary,
|
||||
},
|
||||
},
|
||||
);
|
||||
const request = await readFile(args, "utf8");
|
||||
expect(request).toContain("api\ngraphql\n--paginate\n--slurp");
|
||||
expect(request).toContain("owner=fixture");
|
||||
expect(request).toContain("name=rea");
|
||||
expect(request).toContain(
|
||||
'states:OPEN,baseRefName:"main",labels:["rea:release-notes-finalized"]',
|
||||
);
|
||||
expect(request).toContain("after:$endCursor");
|
||||
expect(await readFile(output, "utf8")).toBe(frozen ? "frozen=true\n" : "");
|
||||
expect(await readFile(summary, "utf8")).toBe(
|
||||
frozen
|
||||
? "Finalized release notes on PR 1399; automatic proposal refresh is paused.\n"
|
||||
: "",
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ name: "transport quota failure", pages: [], status: 1 },
|
||||
{
|
||||
name: "partial GraphQL error",
|
||||
pages: [{ ...editorialPage([]), errors: [{ message: "quota exceeded" }] }],
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
name: "unavailable repository",
|
||||
pages: [{ data: { repository: null } }],
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
name: "missing nodes",
|
||||
pages: [
|
||||
{
|
||||
data: {
|
||||
repository: { nameWithOwner: "fixture/rea", pullRequests: {} },
|
||||
},
|
||||
},
|
||||
],
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
name: "malformed PR",
|
||||
pages: [editorialPage([{ ...finalizedRelease, headRefName: null }])],
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
name: "incomplete pagination",
|
||||
pages: [editorialPage([], true)],
|
||||
status: 0,
|
||||
},
|
||||
{ name: "empty response", pages: [], status: 0 },
|
||||
])("fails closed before release-please on $name", async ({ pages, status }) => {
|
||||
const step = (await readReleaseWorkflow()).jobs["release-proposal"].steps[0];
|
||||
const directory = await createTestTempDirectory(
|
||||
"rea-release-editorial-error-",
|
||||
);
|
||||
const output = join(directory, "output");
|
||||
await writeFile(output, "");
|
||||
await writeFile(join(directory, "pages"), JSON.stringify(pages));
|
||||
await writeFile(
|
||||
join(directory, "gh"),
|
||||
`#!/bin/sh\ncat "$GH_PAGES"\nexit ${status}\n`,
|
||||
{ mode: 0o755 },
|
||||
);
|
||||
await expect(
|
||||
execFileAsync(
|
||||
"bash",
|
||||
[
|
||||
"-e",
|
||||
"-o",
|
||||
"pipefail",
|
||||
"-c",
|
||||
`${z.string().parse(step?.run)}\nprintf 'action-ran\n' >> "$GITHUB_OUTPUT"`,
|
||||
],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `${directory}${delimiter}${process.env.PATH ?? ""}`,
|
||||
GH_REPO: "fixture/rea",
|
||||
GH_PAGES: join(directory, "pages"),
|
||||
GITHUB_OUTPUT: output,
|
||||
GITHUB_STEP_SUMMARY: join(directory, "summary"),
|
||||
},
|
||||
},
|
||||
),
|
||||
).rejects.toMatchObject({ code: expect.any(Number) });
|
||||
expect(await readFile(output, "utf8")).toBe("");
|
||||
});
|
||||
|
||||
it("validates the reviewed merge before creating its release", async () => {
|
||||
const workflow = await readReleaseWorkflow();
|
||||
const steps = workflow.jobs["merged-release"].steps;
|
||||
|
||||
Reference in new issue
Block a user