forked from w3c/webref
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add workflow that checks patch issues/PRs (w3c#255)
* Add workflow that checks patch issues/PRs This creates an additional workflow that runs once per week, extracts the list of GitHub issues and PR referenced by patches, checks the status of these issues and PRs, and creates a PR that drops patches that target now closed issues and PRs. Fixes w3c#215.
- Loading branch information
Showing
4 changed files
with
178 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
name: Clean patches when issues/PR are closed | ||
|
||
on: | ||
schedule: | ||
- cron: '30 3 * * 3' | ||
workflow_dispatch: | ||
jobs: | ||
clean: | ||
runs-on: ubuntu-18.04 | ||
steps: | ||
- name: Checkout webref | ||
uses: actions/checkout@master | ||
|
||
- name: Setup node.js | ||
uses: actions/setup-node@master | ||
with: | ||
node-version: 14.x | ||
|
||
- name: Install dependencies | ||
run: | | ||
npm install @actions/core | ||
npm install @octokit/rest | ||
- name: Drop patches locally when related issues/PR are closed | ||
run: node tools/clean-patches.js | ||
|
||
- name: Create PR to drop patches from repo if needed | ||
uses: peter-evans/create-pull-request@v3 | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
with: | ||
title: Drop patches that are no longer needed | ||
commit-message: "Drop patches that are no longer needed" | ||
body: ${{ env.dropped_patches }} | ||
assignees: tidoust, dontcallmedom | ||
branch: clean-patches | ||
branch-suffix: timestamp |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/** | ||
* Check GitHub issues and pull requests referenced by patches and create | ||
* a pull request to drop patches that should no longer be needed. | ||
*/ | ||
|
||
const core = require('@actions/core'); | ||
const { Octokit } = require("@octokit/rest"); | ||
const fs = require("fs"); | ||
const path = require("path"); | ||
|
||
|
||
/** | ||
* Check GitHub issues and PR referenced by patch files and drop patch files | ||
* that only reference closed issues and PR. | ||
* | ||
* @function | ||
* @return {String} A GitHub flavored markdown string that describes what | ||
* patches got dropped and why. To be used in a possible PR. Returns an | ||
* empty string when there are no patches to drop. | ||
*/ | ||
async function dropPatchesWhenPossible() { | ||
const rootDir = path.join(__dirname, "..", "ed"); | ||
|
||
console.log("Gather patch files"); | ||
let patches = []; | ||
const subDirs = fs.readdirSync(rootDir); | ||
for (const subDir of subDirs) { | ||
if (subDir.endsWith("patches")) { | ||
const files = fs.readdirSync(path.join(rootDir, subDir)); | ||
for (const file of files) { | ||
if (file.endsWith(".patch")) { | ||
const patch = path.join(subDir, file); | ||
console.log(`- add "${patch}"`); | ||
patches.push({ name: patch }); | ||
} | ||
} | ||
} | ||
} | ||
|
||
console.log(); | ||
console.log("Extract list of issues"); | ||
const diffStart = /^---$/m; | ||
const issueUrl = /(?:^|\s)https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/(issues|pull)\/(\d+)(?:\s|$)/g; | ||
for (const patch of patches) { | ||
const contents = fs.readFileSync(path.join(rootDir, patch.name), "utf8"); | ||
const desc = contents.substring(0, contents.match(diffStart)?.index); | ||
const patchIssues = [...desc.matchAll(issueUrl)]; | ||
for (patchIssue of patchIssues) { | ||
if (!patch.issues) { | ||
patch.issues = []; | ||
} | ||
const issue = { | ||
owner: patchIssue[1], | ||
repo: patchIssue[2], | ||
number: parseInt(patchIssue[4], 10), | ||
url: `https://github.com/${patchIssue[1]}/${patchIssue[2]}/${patchIssue[3]}/${patchIssue[4]}` | ||
} | ||
console.log(`- "${patch.name}" linked to ${issue.url}`); | ||
patch.issues.push(issue); | ||
} | ||
|
||
if (patchIssues.length === 0) { | ||
console.log(`- "${patch.name}" not linked to any issue`); | ||
} | ||
} | ||
patches = patches.filter(patch => patch.issues); | ||
|
||
console.log(); | ||
console.log("Check status of GitHub issues/PR"); | ||
for (const patch of patches) { | ||
for (const issue of patch.issues) { | ||
const response = await octokit.issues.get({ | ||
owner: issue.owner, | ||
repo: issue.repo, | ||
issue_number: issue.number | ||
}); | ||
issue.state = response?.data?.state ?? "unknown"; | ||
console.log(`- [${issue.state}] ${issue.url}`); | ||
} | ||
} | ||
|
||
console.log(); | ||
console.log("Drop patches when possible"); | ||
patches = patches.filter(patch => patch.issues.every(issue => issue.state === "closed")); | ||
if (patches.length > 0) { | ||
const res = []; | ||
for (const patch of patches) { | ||
console.log(`- drop "${patch.name}"`); | ||
fs.unlinkSync(path.join(rootDir, patch.name)); | ||
res.push(`- \`${patch.name}\` was linked to now closed: ` + | ||
patch.issues.map(issue => `[${issue.owner}/${issue.repo}#${issue.number}](${issue.url})`).join(", ")); | ||
} | ||
return res.join("\n"); | ||
} | ||
else { | ||
console.log("- No patch to drop at this time"); | ||
return ""; | ||
} | ||
} | ||
|
||
|
||
/******************************************************************************* | ||
Retrieve GH_TOKEN from environment, prepare Octokit and kick things off | ||
*******************************************************************************/ | ||
const GH_TOKEN = (() => { | ||
try { | ||
return require("../config.json").GH_TOKEN; | ||
} catch { | ||
return process.env.GH_TOKEN; | ||
} | ||
})(); | ||
if (!GH_TOKEN) { | ||
console.error("GH_TOKEN must be set to some personal access token as an env variable or in a config.json file"); | ||
process.exit(1); | ||
} | ||
|
||
const octokit = new Octokit({ | ||
auth: GH_TOKEN, | ||
//log: console | ||
}); | ||
|
||
|
||
dropPatchesWhenPossible() | ||
.then(res => { | ||
core.exportVariable("dropped_patches", res); | ||
console.log(); | ||
console.log("Set dropped_variables env variable"); | ||
console.log(res); | ||
console.log("== The end =="); | ||
}) | ||
.catch(err => { | ||
console.error(err); | ||
process.exit(1); | ||
}); |