Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(votes): use GitHub handles in the JSON summary #1586

Merged
merged 10 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 11 additions & 40 deletions .github/workflows/closeVote.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ jobs:
run:
curl -L https://raw.githubusercontent.com/nodejs/node/main/.mailmap >>
.mailmap
- name: Configure git
run: |
git config --global user.email "github-bot@iojs.org"
git config --global user.name "Node.js GitHub Bot"
- name: Load vote branch
run: |
git fetch origin '${{ steps.branch.outputs.head }}'
Expand All @@ -68,49 +64,24 @@ jobs:
- name: Attempt closing the vote
id: vote-summary
run: |
{
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "markdown<<$EOF"
./votes/initiateNewVote/decryptPrivateKeyAndCloseVote.mjs \
--remote origin --branch "${{ steps.branch.outputs.head }}" \
--fromCommit "FETCH_HEAD~${{ steps.nb-of-commits.outputs.minusOne }}" \
--toCommit "FETCH_HEAD" \
--prURL "${{ steps.pr-url.outputs.URL }}" \
--save-markdown-summary summaryComment.md \
--comments "$COMMENTS" --commit-json-summary
echo "$EOF"
} >> "$GITHUB_OUTPUT"
./votes/initiateNewVote/decryptPrivateKeyAndCloseVote.mjs \
--remote origin --branch "${{ steps.branch.outputs.head }}" \
--fromCommit "FETCH_HEAD~${{ steps.nb-of-commits.outputs.minusOne }}" \
--toCommit "FETCH_HEAD" \
--prURL "${{ steps.pr-url.outputs.URL }}" \
--save-markdown-summary summaryComment.md \
--prepare-json-summary-graphql-query createCommitOnBranch.gql --prepare-json-summary-dirname ./votes \
--comments "$COMMENTS"
env:
COMMENTS: ${{ steps.comments.outputs.comments }}
- name: Install ghcommit
run: go install github.com/planetscale/ghcommit@8c6d9af75a7814768ce871cde246224d45bd8c04
- name: Push to the PR branch
run: |
GH_COMMIT_PATH="$(go env GOPATH)/bin/ghcommit" COMMIT_MESSAGE="$(
git log -1 HEAD --pretty=format:%B
)" SHA="$(
git rev-parse HEAD^
)" DELETED_FILES="$(
git show HEAD --name-only --diff-filter=D --pretty=format:
)" ADDED_FILES="$(
git show HEAD --name-only --diff-filter=d --pretty=format:
)" node --input-type=module <<'EOF'
import { spawnSync } from "node:child_process";
const {GH_COMMIT_PATH, COMMIT_MESSAGE, SHA, DELETED_FILES, ADDED_FILES} = process.env;
spawnSync(GH_COMMIT_PATH, [
'-r', ${{ toJSON(github.repository) }},
'-b', ${{ toJSON(steps.branch.outputs.head) }},
'-m', COMMIT_MESSAGE,
'--sha', SHA,
...DELETED_FILES.split('\n').filter(Boolean).flatMap(file => ['--delete', file]),
...ADDED_FILES.split('\n').filter(Boolean).flatMap(file => ['--add', file]),
], { stdio: 'inherit' });
EOF
cat createCommitOnBranch.gql
gh api graphql -F repo="$GITHUB_REPOSITORY" -F commit_title="close vote and aggregate results" -f query="$(< createCommitOnBranch.gql)"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish vote summary comment
run: |
gh pr comment "${{ steps.pr-url.outputs.URL }}" --body-file summaryComment.md
env:
GH_TOKEN: ${{ github.token }}
SUMMARY: ${{ steps.vote-summary.outputs.markdown }}
173 changes: 153 additions & 20 deletions votes/initiateNewVote/decryptPrivateKeyAndCloseVote.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
#!/usr/bin/env node

import { spawn } from "node:child_process";
import { writeFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { open, writeFile } from "node:fs/promises";
import path from "node:path";
import { createInterface as readLines } from "node:readline";
import { Readable } from "node:stream";
import { fileURLToPath } from "node:url";
import { parseArgs } from "node:util";

import countFromGit from "@node-core/caritat/countBallotsFromGit";
import readReadme from "./extractInfoFromReadme.mjs";

const { values: parsedArgs } = parseArgs({
options: {
Expand Down Expand Up @@ -43,37 +47,56 @@ const { values: parsedArgs } = parseArgs({
describe: "Commit JSON summary",
type: "boolean",
},
"prepare-json-summary-dirname": {
describe: "Directory where to create the JSON summary when using ",
type: "string",
},
"prepare-json-summary-graphql-query": {
describe: "Path to store mutation request to commit JSON summary",
type: "string",
},
"save-markdown-summary": {
describe: "Write the markdown to a file (use - for stdout)",
type: "string",
},
"nodejs-repository-path": {
type: "string",
},
},
});

const keyParts = [...new Set(JSON.parse(parsedArgs.comments)
.map(
(txt) =>
/-----BEGIN SHAMIR KEY PART-----(.+)-----END SHAMIR KEY PART-----/s.exec(
txt
)?.[1]
)
.filter(Boolean))];
const keyParts = [
...new Set(
JSON.parse(parsedArgs.comments || "[]")
.map(
(txt) =>
/-----BEGIN SHAMIR KEY PART-----(.+)-----END SHAMIR KEY PART-----/s.exec(
txt,
)?.[1],
)
.filter(Boolean),
),
];
aduh95 marked this conversation as resolved.
Show resolved Hide resolved

const firstCommitRef = parsedArgs.fromCommit;
const voteFileCanonicalName = "vote.yml";

const subPath = await new Promise(async (resolve, reject) => {
const cp = spawn("git", [
"--no-pager",
"show",
firstCommitRef,
"--name-only",
]);
cp.on("error", reject);
for await (const line of readLines(cp.stdout)) {
if (line === voteFileCanonicalName) return resolve("./");
if (line.endsWith(`/${voteFileCanonicalName}`))
return resolve(line.slice(0, -voteFileCanonicalName.length));
try {
const cp = spawn("git", [
"--no-pager",
"show",
firstCommitRef,
"--name-only",
]);
cp.on("error", reject);
for await (const line of readLines(cp.stdout)) {
if (line === voteFileCanonicalName) return resolve("./");
if (line.endsWith(`/${voteFileCanonicalName}`))
return resolve(line.slice(0, -voteFileCanonicalName.length));
}
} catch (err) {
reject(err);
}
});

Expand All @@ -94,6 +117,116 @@ const { result, privateKey } = await countFromGit({
: null,
});

async function generateSummaryFilePath() {
const date = new Date().toISOString().slice(0, 10);
for (let i = 0; i < Number.MAX_SAFE_INTEGER; i++) {
const filepath = path.join(
parsedArgs["prepare-json-summary-dirname"],
`${date}-${i}.json`,
);
if (!existsSync(filepath)) {
return filepath;
}
}

throw new Error("Could not find a path for the summary file");
}

async function getSHA(ref) {
return new Promise(async (resolve, reject) => {
const cp = spawn("git", [
"--no-pager",
"rev-parse",
ref,
]);
cp.on("error", reject);
resolve(cp.stdout.toArray().then((b) => Buffer.concat(b).toString("utf-8").trim()));
});
}

if (parsedArgs["prepare-json-summary-graphql-query"]) {
let input, crlfDelay;
if (parsedArgs["nodejs-repository-path"] == null) {
input = await fetch(
"https://raw.githubusercontent.com/nodejs/node/HEAD/README.md",
).then((res) => {
if (!res.ok) {
throw new Error("Wrong status code", { cause: res });
} else {
return Readable.fromWeb(res.body);
}
});
} else {
const fh = await open(
join(resolve(argv["nodejs-repository-path"]), "README.md"),
"r",
);
input = fh.createReadStream();
crlfDelay = Infinity;
}

const summary = result.toJSON();
for await (const { handle, name, email } of readReadme(
readLines({ input, crlfDelay }),
)) {
const authorName = `${name} <${email}>`;
if (authorName in summary.votes) {
summary.votes[handle] = summary.votes[authorName];
delete summary.votes[authorName];
}
}

summary.refs = parsedArgs.prURL;

const lastCommitSHA = await getSHA(parsedArgs.toCommit);
await writeFile(
parsedArgs["prepare-json-summary-graphql-query"],
`mutation ($repo: String!, $commit_title: String!, $commit_body: String) {
createCommitOnBranch(input: {
branch: {
repositoryNameWithOwner: $repo,
branchName: ${JSON.stringify(parsedArgs.branch)}
},
message: {
headline: $commit_title,
body: $commit_body
},
expectedHeadOid: ${JSON.stringify(lastCommitSHA)},
fileChanges: {
additions: [{
path: ${JSON.stringify(await generateSummaryFilePath())},
contents: ${JSON.stringify(Buffer.from(JSON.stringify(summary, undefined, 2)).toString("base64"))},
}],
deletions: [${
await new Promise(async (resolve, reject) => {
try {
const cp = spawn("git", [
"--no-pager",
"diff",
`${firstCommitRef}^..${lastCommitSHA}`,
"--name-only",
]);
cp.on("error", reject);
const files = [];
for await (const path of readLines(cp.stdout)) {
files.push(`\n { path: ${JSON.stringify(path)} }`);
}
resolve(files.join(','));
} catch (err) {
reject(err);
}
})
}
]
}}) {
commit {
url
}
}
}\n`,
);
}

if (parsedArgs["save-markdown-summary"]) {
function* toArmoredMessage(str, chunkSize = 64) {
yield "-----BEGIN PRIVATE KEY-----";
Expand Down