diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a8fc61b6..ded93ab3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -20,11 +20,11 @@ packages/typescriptlang-org/src/copy/pt/**/*.ts @khaosdoctor @danilofuchs @orta packages/documentation/copy/pt/**/*.ts @khaosdoctor @danilofuchs @orta # Collaborators for Spanish Translation of the Website -packages/playground-examples/copy/es/**/*.md @KingDarBoja -packages/playground-examples/copy/es/**/*.ts @KingDarBoja -packages/tsconfig-reference/copy/es/**/*.md @KingDarBoja -packages/typescriptlang-org/src/copy/es/**/*.ts @KingDarBoja -packages/documentation/copy/es/**/*.ts @KingDarBoja +packages/playground-examples/copy/es/**/*.md @KingDarBoja [translate] [es] +packages/playground-examples/copy/es/**/*.ts @KingDarBoja [translate] [es] +packages/tsconfig-reference/copy/es/**/*.md @KingDarBoja [translate] [es] +packages/typescriptlang-org/src/copy/es/**/*.ts @KingDarBoja [translate] [es] +packages/documentation/copy/es/**/*.ts @KingDarBoja [translate] [es] # Collaborators for Chinese Translation of the Website packages/playground-examples/copy/zh/**/*.md @Kingwl diff --git a/README.md b/README.md index d2ab6a29..6711ecb7 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,19 @@ Then you should be good to go. We force the use of [`pull_request_target`](https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/) as a workflow event to ensure that someone cannot change the CODEOWNER files at the same time as having that change be used to validate if they can merge. +### Extras + +You can use this label to set labels for specific sections of the codebase, by having square brackets to indicate labels to make: `[label]` + +```sh +# Collaborators for Spanish Translation of the Website +packages/playground-examples/copy/es/**/*.md @KingDarBoja [translate] [es] +packages/playground-examples/copy/es/**/*.ts @KingDarBoja [translate] [es] +packages/tsconfig-reference/copy/es/**/*.md @KingDarBoja [translate] [es] +packages/typescriptlang-org/src/copy/es/**/*.ts @KingDarBoja [translate] [es] +packages/documentation/copy/es/**/*.ts @KingDarBoja [translate] [es] +``` + ### Dev Use `npx jest --watch` to run tests. diff --git a/index.js b/index.js index fb22457b..d056f9ba 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,7 @@ const {readFileSync} = require("fs") // Effectively the main function async function run() { - core.info("Running version 1.3.1") + core.info("Running version 1.3.2") // Tell folks they can merge if (context.eventName === "pull_request_target") { @@ -40,7 +40,8 @@ async function commentOnMergablePRs() { core.info(`Changed files: \n - ${changedFiles.join("\n - ")}`) const codeowners = findCodeOwnersForChangedFiles(changedFiles, cwd) - core.info(`Code-owners: \n - ${codeowners.join("\n - ")}`) + core.info(`Code-owners: \n - ${codeowners.users.join("\n - ")}`) + core.info(`Labels: \n - ${codeowners.labels.join("\n - ")}`) if (!codeowners.length) { console.log("This PR does not have any code-owners") @@ -49,7 +50,7 @@ async function commentOnMergablePRs() { // Determine who has access to merge every file in this PR const ownersWhoHaveAccessToAllFilesInPR = [] - codeowners.forEach(owner => { + codeowners.users.forEach(owner => { const filesWhichArentOwned = getFilesNotOwnedByCodeOwner(owner, changedFiles, cwd) if (filesWhichArentOwned.length === 0) ownersWhoHaveAccessToAllFilesInPR.push(owner) }) @@ -75,6 +76,12 @@ This section of the codebase is owned by ${owners} - if they write a comment say ${ourSignature}` await octokit.issues.createComment({ ...thisRepo, issue_number: pr.number, body: message }); + + // Add labels + for (const label of codeowners.labels) { + const labelConfig = { name: label, color: Math.random().toString(16).slice(2, 8) } + await createOrAddLabel(octokit, { ...thisRepo, id: pr.number }, labelConfig) + } } async function mergeIfLGTMAndHasAccess() { @@ -108,8 +115,12 @@ async function mergeIfLGTMAndHasAccess() { } core.info(`Creating comments and merging`) - await octokit.issues.createComment({ ...thisRepo, issue_number: issue.number, body: `Merging because @${sender} is a code-owner of all the changes - thanks!` }); - await octokit.pulls.merge({ ...thisRepo, pull_number: issue.number }); + try { + await octokit.pulls.merge({ ...thisRepo, pull_number: issue.number }); + await octokit.issues.createComment({ ...thisRepo, issue_number: issue.number, body: `Merging because @${sender} is a code-owner of all the changes - thanks!` }); + } catch (error) { + await octokit.issues.createComment({ ...thisRepo, issue_number: issue.number, body: `Looks good to merge, thanks ${sender}.` }); + } } function getFilesNotOwnedByCodeOwner(owner, files, cwd) { @@ -141,15 +152,22 @@ function listFilesWithOwners(files, cwd) { function findCodeOwnersForChangedFiles(changedFiles, cwd) { const owners = new Set() + const labels = new Set() const codeowners = new Codeowners(cwd); for (const file of changedFiles) { const relative = file.startsWith("/") ? file.slice(1) : file const filesOwners = codeowners.getOwner(relative); - filesOwners.forEach(o => owners.add(o)) + filesOwners.forEach(o => { + if (o.startsWith("@")) owners.add(o) + if (o.startsWith("[")) labels.add(o.slice(1, o.length-1)) + }) } - return Array.from(owners) + return { + users: Array.from(owners), + labels: Array.from(labels) + } } async function getPRChangedFiles(octokit, repoDeets, prNumber) { @@ -162,6 +180,32 @@ async function getPRChangedFiles(octokit, repoDeets, prNumber) { return fileStrings } + + +async function createOrAddLabel(octokit, repoDeets, labelConfig) { + let label = null + const existingLabels = await octokit.paginate('GET /repos/:owner/:repo/labels', { owner: repoDeets.owner, repo: repoDeets.repo }) + label = existingLabels.find(l => l.name == labelConfig.name) + + // Create the label if it doesn't exist yet + if (!label) { + await octokit.issues.createLabel({ + owner: repoDeets.owner, + repo: repoDeets.repo, + name: labelConfig.name, + color: labelConfig.color, + description: labelConfig.description, + }) + } + + await octokit.issues.addLabels({ + owner: repoDeets.owner, + repo: repoDeets.repo, + issue_number: repoDeets.id, + labels: [labelConfig.name], + }) +} + process.on('uncaughtException', function (error) { core.setFailed(error.message) }) diff --git a/index.test.js b/index.test.js index fbe213f7..7883e1a3 100644 --- a/index.test.js +++ b/index.test.js @@ -2,22 +2,29 @@ const { getFilesNotOwnedByCodeOwner, findCodeOwnersForChangedFiles } = require(" test("determine who owns a set of files", () => { const noFiles = findCodeOwnersForChangedFiles(["root-codeowners/one.two.js"], "./test-code-owners-repo"); - expect(noFiles).toEqual(["@two"]); + expect(noFiles.users).toEqual(["@two"]); const filesNotInCodeowners = findCodeOwnersForChangedFiles(["root-codeowners/one.two.ts"], "./test-code-owners-repo"); - expect(filesNotInCodeowners).toEqual([]); + expect(filesNotInCodeowners.users).toEqual([]); }); test("real world", () => { const changed = ["/packages/tsconfig-reference/copy/pt/options/files.md"]; const filesNotInCodeowners = findCodeOwnersForChangedFiles(changed, "."); - expect(filesNotInCodeowners).toEqual(["@khaosdoctor", "@danilofuchs", "@orta"]); + expect(filesNotInCodeowners.users).toEqual(["@khaosdoctor", "@danilofuchs", "@orta"]); }); test("real world 2", () => { const changed = ["/packages/typescriptlang-org/src/copy/pt/index.ts", "/packages/typescriptlang-org/src/copy/pt/nav.ts"]; const filesNotInCodeowners = findCodeOwnersForChangedFiles(changed, "."); - expect(filesNotInCodeowners).toEqual(["@khaosdoctor", "@danilofuchs", "@orta"]); + expect(filesNotInCodeowners.users).toEqual(["@khaosdoctor", "@danilofuchs", "@orta"]); +}); + +test("real world with labels", () => { + // spanish has [] labels in the CODEOWNERS + const changed = ["/packages/typescriptlang-org/src/copy/es/index.ts", "/packages/typescriptlang-org/src/copy/es/nav.ts"]; + const filesNotInCodeowners = findCodeOwnersForChangedFiles(changed, "."); + expect(filesNotInCodeowners.labels).toEqual(["translate", "es"]); }); test("deciding if someone has access to merge", () => { @@ -28,3 +35,4 @@ test("deciding if someone has access to merge", () => { expect(filesNotInCodeowners).toEqual(["random-path/file.ts"]); }); +