chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Extract release information from either release event or workflow_dispatch input
|
||||
*/
|
||||
function extractReleaseInfo(context) {
|
||||
let releaseVersion;
|
||||
let releaseTag;
|
||||
|
||||
if (context.eventName === "workflow_dispatch") {
|
||||
// Manual trigger with version parameter
|
||||
releaseVersion = context.payload.inputs?.release_version;
|
||||
if (!releaseVersion) {
|
||||
throw new Error("release_version input is required for workflow_dispatch");
|
||||
}
|
||||
releaseTag = releaseVersion.startsWith("v") ? releaseVersion : `v${releaseVersion}`;
|
||||
releaseVersion = releaseVersion.replace(/^v/, ""); // Remove 'v' prefix if present
|
||||
console.log(`Processing manual workflow for release: ${releaseTag} (${releaseVersion})`);
|
||||
} else {
|
||||
// Automatic trigger from release event
|
||||
const release = context.payload.release;
|
||||
if (!release) {
|
||||
throw new Error("Release information not found in payload");
|
||||
}
|
||||
releaseTag = release.tag_name;
|
||||
releaseVersion = releaseTag.replace(/^v/, ""); // Remove 'v' prefix if present
|
||||
console.log(`Processing release event: ${releaseTag} (${releaseVersion})`);
|
||||
}
|
||||
|
||||
const versionMatch = releaseVersion.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
||||
if (!versionMatch) {
|
||||
console.log(`Skipping invalid release: ${releaseVersion}`);
|
||||
throw new Error(`Invalid version format: ${releaseVersion}`);
|
||||
}
|
||||
|
||||
const [, major, minor, patch] = versionMatch;
|
||||
const nextPatchVersion = `${major}.${minor}.${parseInt(patch) + 1}`;
|
||||
const releaseLabel = `v${releaseVersion}`;
|
||||
const nextPatchLabel = `v${nextPatchVersion}`;
|
||||
|
||||
// Get release branch name (e.g., branch-3.1 for v3.1.4)
|
||||
const releaseBranch = `branch-${major}.${minor}`;
|
||||
|
||||
console.log(`Release label: ${releaseLabel}`);
|
||||
console.log(`Next patch label: ${nextPatchLabel}`);
|
||||
console.log(`Release branch: ${releaseBranch}`);
|
||||
|
||||
return {
|
||||
releaseVersion,
|
||||
releaseTag,
|
||||
releaseLabel,
|
||||
nextPatchLabel,
|
||||
releaseBranch,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to extract PR number from commit message
|
||||
*/
|
||||
function extractPRNumberFromCommitMessage(commitMessage) {
|
||||
const prRegex = /\(#(\d+)\)$/;
|
||||
const lines = commitMessage.split("\n");
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.trim().match(prRegex);
|
||||
if (match) {
|
||||
return parseInt(match[1], 10);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract PR numbers from release branch commits
|
||||
*/
|
||||
async function extractPRNumbersFromBranch(github, context, releaseBranch) {
|
||||
const releasePRNumbers = new Set();
|
||||
|
||||
try {
|
||||
const commits = await github.paginate(github.rest.repos.listCommits, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: releaseBranch,
|
||||
since: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(), // Last 30 days
|
||||
});
|
||||
|
||||
for (const commit of commits) {
|
||||
const prNumber = extractPRNumberFromCommitMessage(commit.commit.message);
|
||||
if (prNumber) {
|
||||
releasePRNumbers.add(prNumber);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Found ${releasePRNumbers.size} PR numbers from ${releaseBranch} commits`);
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
console.log(
|
||||
`Release branch '${releaseBranch}' not found. This may be expected for new releases.`
|
||||
);
|
||||
console.log("Skipping commit analysis - will update all PRs with the release label.");
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return releasePRNumbers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all merged PRs with a specific label
|
||||
*/
|
||||
async function fetchPRsWithLabel(github, context, releaseLabel) {
|
||||
const allIssues = await github.paginate(github.rest.issues.listForRepo, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: releaseLabel,
|
||||
state: "all",
|
||||
});
|
||||
|
||||
const prsWithReleaseLabel = allIssues.filter((item) => {
|
||||
if (!item.pull_request) return false;
|
||||
if (item.state === "open") return true;
|
||||
if (item.state === "closed" && item.pull_request.merged_at) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
console.log(`Found ${prsWithReleaseLabel.length} PRs with label ${releaseLabel}`);
|
||||
return prsWithReleaseLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update PR labels for PRs not included in release
|
||||
*/
|
||||
async function updatePRLabels(
|
||||
github,
|
||||
context,
|
||||
prsWithReleaseLabel,
|
||||
releasePRNumbers,
|
||||
releaseLabel,
|
||||
nextPatchLabel
|
||||
) {
|
||||
const pullRequests = prsWithReleaseLabel.filter((item) => item.pull_request);
|
||||
console.log(
|
||||
`Processing ${pullRequests.length} PRs (filtered out ${
|
||||
prsWithReleaseLabel.length - pullRequests.length
|
||||
} issues)`
|
||||
);
|
||||
|
||||
const prsToUpdate = [];
|
||||
|
||||
for (const pr of pullRequests) {
|
||||
if (releasePRNumbers.has(pr.number)) continue;
|
||||
prsToUpdate.push(pr.number);
|
||||
}
|
||||
|
||||
console.log(`Found ${prsToUpdate.length} PRs that need label updates: ${prsToUpdate.join(", ")}`);
|
||||
|
||||
for (const prNumber of prsToUpdate) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
name: releaseLabel,
|
||||
});
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
labels: [nextPatchLabel],
|
||||
});
|
||||
|
||||
console.log(`Updated PR #${prNumber}: ${releaseLabel} → ${nextPatchLabel}`);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.log(`Warning: Failed to update labels for PR #${prNumber}: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to update release labels
|
||||
*
|
||||
* This script checks all PRs labeled with a release version and updates
|
||||
* their labels to the next patch version if they weren't actually included
|
||||
* in the release (handles cherry-picked commits properly).
|
||||
*/
|
||||
async function updateReleaseLabels({ github, context }) {
|
||||
try {
|
||||
const releaseInfo = extractReleaseInfo(context);
|
||||
|
||||
const releasePRNumbers = await extractPRNumbersFromBranch(
|
||||
github,
|
||||
context,
|
||||
releaseInfo.releaseBranch
|
||||
);
|
||||
|
||||
const prsWithReleaseLabel = await fetchPRsWithLabel(github, context, releaseInfo.releaseLabel);
|
||||
|
||||
await updatePRLabels(
|
||||
github,
|
||||
context,
|
||||
prsWithReleaseLabel,
|
||||
releasePRNumbers,
|
||||
releaseInfo.releaseLabel,
|
||||
releaseInfo.nextPatchLabel
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.error(`Error updating release labels: ${errorMessage}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { updateReleaseLabels };
|
||||
Reference in New Issue
Block a user