Skip to content

Commit

Permalink
Apply suggestions after review
Browse files Browse the repository at this point in the history
  • Loading branch information
mszostok committed Jul 28, 2024
1 parent d226af8 commit 255d2e7
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 4,124 deletions.
37 changes: 0 additions & 37 deletions .github/scripts/maintainers/.eslintrc.yml

This file was deleted.

1 change: 0 additions & 1 deletion .github/scripts/maintainers/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
/node_modules
github.api.cache.json
2 changes: 0 additions & 2 deletions .github/scripts/maintainers/.prettierignore

This file was deleted.

11 changes: 5 additions & 6 deletions .github/scripts/maintainers/cache.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const fs = require("fs");
const core = require("@actions/core");

module.exports = {
fetchWithCache,
Expand All @@ -8,26 +7,26 @@ module.exports = {
printAPICallsStats,
};

const CODEOWNERS_CACHE_PATH = "github.api.cache.json";
const CODEOWNERS_CACHE_PATH = "./.github/scripts/maintainers/github.api.cache.json";

let cacheEntries = {};

let numberOfFullFetches = 0;
let numberOfCacheHits = 0;

async function loadCache() {
function loadCache(core) {
try {
cacheEntries = JSON.parse(fs.readFileSync(CODEOWNERS_CACHE_PATH, "utf8"));
} catch (error) {
core.warning(`Cache was not restored: ${error}`);
}
}

async function saveCache() {
function saveCache() {
fs.writeFileSync(CODEOWNERS_CACHE_PATH, JSON.stringify(cacheEntries));
}

async function fetchWithCache(cacheKey, fetchFn) {
async function fetchWithCache(cacheKey, fetchFn, core) {
const cachedResp = cacheEntries[cacheKey];

try {
Expand Down Expand Up @@ -55,7 +54,7 @@ async function fetchWithCache(cacheKey, fetchFn) {
}
}

function printAPICallsStats() {
function printAPICallsStats(core) {
core.startGroup("API calls statistic");
core.info(
`Number of API calls count against rate limit: ${numberOfFullFetches}`,
Expand Down
37 changes: 19 additions & 18 deletions .github/scripts/maintainers/gh_calls.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
const core = require("@actions/core");
const { fetchWithCache } = require("./cache");

module.exports = { getGitHubProfile, getAllCodeownersFiles, getRepositories };

async function getRepositories(github, owner, ignoredRepos) {
async function getRepositories(github, owner, ignoredRepos, core) {
core.startGroup(
`Getting list of all public, non-archived repositories owned by ${owner}`,
);

const query = `
query repos($cursor: String, $owner: String!) {
organization(login: $owner) {
repositories(first: 100, after: $cursor, visibility: PUBLIC, isArchived: false) {
nodes {
name
}
pageInfo {
hasNextPage
endCursor
}
query repos($cursor: String, $owner: String!) {
organization(login: $owner) {
repositories(first: 100 after: $cursor visibility: PUBLIC isArchived: false orderBy: {field: CREATED_AT, direction: ASC} ) {
nodes {
name
}
pageInfo {
hasNextPage
endCursor
}
}
}`;
}
}`;

const repos = [];
let cursor = null;
Expand All @@ -41,7 +40,7 @@ async function getRepositories(github, owner, ignoredRepos) {
return repos.filter((repo) => !ignoredRepos.includes(repo.name));
}

async function getGitHubProfile(github, login) {
async function getGitHubProfile(github, login, core) {
try {
const profile = await fetchWithCache(
`profile:${login}`,
Expand All @@ -51,15 +50,16 @@ async function getGitHubProfile(github, login) {
headers,
});
},
core,
);

return removeNulls({
name: profile.name ?? login,
github: login,
twitter: profile.twitter_username,
availableForHire: profile.hireable,
isTscMember: false,
repos: [],
githubID: profile.id,
});
} catch (error) {
if (error.status === 404) {
Expand All @@ -73,7 +73,7 @@ async function getGitHubProfile(github, login) {
// https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-file-location
//
// Detect the repository default branch automatically.
async function getCodeownersFile(github, owner, repo) {
async function getCodeownersFile(github, owner, repo, core) {
const paths = ["CODEOWNERS", "docs/CODEOWNERS", ".github/CODEOWNERS"];

for (const path of paths) {
Expand All @@ -94,6 +94,7 @@ async function getCodeownersFile(github, owner, repo) {
},
});
},
core,
);
} catch (error) {
core.warning(
Expand All @@ -108,11 +109,11 @@ async function getCodeownersFile(github, owner, repo) {
return null;
}

async function getAllCodeownersFiles(github, owner, repos) {
async function getAllCodeownersFiles(github, owner, repos, core) {
core.startGroup(`Fetching CODEOWNERS files for ${repos.length} repositories`);
const files = [];
for (const repo of repos) {
const data = await getCodeownersFile(github, owner, repo.name);
const data = await getCodeownersFile(github, owner, repo.name, core);
if (!data) {
continue;
}
Expand Down
119 changes: 62 additions & 57 deletions .github/scripts/maintainers/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const core = require("@actions/core");
const { getOctokit, context } = require("@actions/github");
const yaml = require("js-yaml");
const fs = require("fs");
const { saveCache, loadCache, printAPICallsStats } = require("./cache");
Expand All @@ -10,6 +8,15 @@ const {
getRepositories,
} = require("./gh_calls");

module.exports = async ({ github, context, core }) => {
try {
await run(github, context, core);
} catch (error) {
console.log(error);
core.setFailed(`An error occurred: ${error}`);
}
};

const config = {
ghToken: process.env.GH_TOKEN,
ignoredRepos: getCommaSeparatedInputList(process.env.IGNORED_REPOSITORIES),
Expand All @@ -26,7 +33,7 @@ function getCommaSeparatedInputList(list) {
);
}

function extractGitHubUsernames(codeownersContent) {
function extractGitHubUsernames(codeownersContent, core) {
if (!codeownersContent) {
return [];
}
Expand Down Expand Up @@ -55,12 +62,12 @@ function extractGitHubUsernames(codeownersContent) {
return uniqueOwners;
}

async function collectCurrentMaintainers(codeownersFiles, github) {
async function collectCurrentMaintainers(codeownersFiles, github, core) {
core.startGroup(`Fetching GitHub profile information for each codeowner`);

const currentMaintainers = {};
for (const codeowners of codeownersFiles) {
const owners = extractGitHubUsernames(codeowners.content);
const owners = extractGitHubUsernames(codeowners.content, core);

for (const owner of owners) {
if (config.ignoredUsers.includes(owner)) {
Expand All @@ -72,7 +79,7 @@ async function collectCurrentMaintainers(codeownersFiles, github) {
const key = owner.toLowerCase();
if (!currentMaintainers[key]) {
// Fetching GitHub profile is useful to ensure that all maintainers are valid (e.g., their GitHub accounts haven't been deleted).
const profile = await getGitHubProfile(github, owner);
const profile = await getGitHubProfile(github, owner, core);
if (!profile) {
core.warning(
`[repo: ${codeowners.repo}]: GitHub profile not found for ${owner}.`,
Expand All @@ -91,7 +98,11 @@ async function collectCurrentMaintainers(codeownersFiles, github) {
return currentMaintainers;
}

function refreshPreviousMaintainers(previousMaintainers, currentMaintainers) {
function refreshPreviousMaintainers(
previousMaintainers,
currentMaintainers,
core,
) {
core.startGroup(`Refreshing previous maintainers list`);

const updatedMaintainers = [];
Expand All @@ -113,6 +124,7 @@ function refreshPreviousMaintainers(previousMaintainers, currentMaintainers) {
updatedMaintainers.push({
...previousEntry,
repos: currentMaintainer.repos,
githubID: currentMaintainer.githubID,
});
}

Expand All @@ -124,55 +136,48 @@ function refreshPreviousMaintainers(previousMaintainers, currentMaintainers) {
return updatedMaintainers;
}

async function run() {
try {
if (!config.maintainersFilePath) {
core.setFailed("The MAINTAINERS_FILE_PATH is not defined");
return;
}
await loadCache();

const github = getOctokit(config.ghToken);

const repos = await getRepositories(
github,
context.repo.owner,
config.ignoredRepos,
);
const codeownersFiles = await getAllCodeownersFiles(
github,
context.repo.owner,
repos,
);

const previousMaintainers = yaml.load(
fs.readFileSync(config.maintainersFilePath, "utf8"),
);

// 1. Collect new maintainers from all current CODEOWNERS files found across all repositories.
const currentMaintainers = await collectCurrentMaintainers(
codeownersFiles,
github,
);

// 2. Refresh the repository list for existing maintainers and add any new maintainers to the list.
const refreshedMaintainers = refreshPreviousMaintainers(
previousMaintainers,
currentMaintainers,
);

fs.writeFileSync(
config.maintainersFilePath,
yaml.dump(refreshedMaintainers),
);

printAPICallsStats();

await summarizeChanges(previousMaintainers, refreshedMaintainers);
await saveCache();
} catch (error) {
core.setFailed(`An error occurred: ${error}`);
async function run(github, context, core) {
if (!config.maintainersFilePath) {
core.setFailed("The MAINTAINERS_FILE_PATH is not defined");
return;
}
}
loadCache(core);

run();
const repos = await getRepositories(
github,
context.repo.owner,
config.ignoredRepos,
core,
);
const codeownersFiles = await getAllCodeownersFiles(
github,
context.repo.owner,
repos,
core,
);

const previousMaintainers = yaml.load(
fs.readFileSync(config.maintainersFilePath, "utf8"),
);

// 1. Collect new maintainers from all current CODEOWNERS files found across all repositories.
const currentMaintainers = await collectCurrentMaintainers(
codeownersFiles,
github,
core,
);

// 2. Refresh the repository list for existing maintainers and add any new maintainers to the list.
const refreshedMaintainers = refreshPreviousMaintainers(
previousMaintainers,
currentMaintainers,
core,
);

fs.writeFileSync(config.maintainersFilePath, yaml.dump(refreshedMaintainers));

printAPICallsStats(core);

await summarizeChanges(previousMaintainers, refreshedMaintainers, core);
saveCache();
}
Loading

0 comments on commit 255d2e7

Please sign in to comment.