nx/scripts/check-codeowners.ts
Jack Hsu e0cae539d6
fix(js): update tinyglobby to speed up shallow file matching (#30415)
`tinyglobby` at `0.2.10` (what we use now) is slow on shallow files, but
the latest version `0.2.12` is fast due to this PR
https://github.com/SuperchupuDev/tinyglobby/pull/69/files.

This PR updates both the js and esbuild plugins to use the newest
versions, but also adds `tinyglobby@^0.2.12` to our root `package.json`
so we get the speed increase right away. I removed `fast-glob` in our
repo scripts and replaced it with `tinyglobby`.

## Current Behavior
Asset handling is slow for shallow files like `LICENSE` but is fine for
scoped patterns like `src/**/*.ts`.

## Expected Behavior
Asset handling should be fast for shallow files.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2025-03-19 09:20:18 -04:00

127 lines
3.3 KiB
TypeScript

import * as globby from 'tinyglobby';
import * as path from 'path';
import * as fs from 'fs';
import * as octokit from 'octokit';
import { output } from '@nx/devkit';
import { Octokit } from 'octokit';
async function main() {
const codeowners = fs.readFileSync(
path.join(__dirname, '../CODEOWNERS'),
'utf-8'
);
const codeownersLines = codeowners
.split('\n')
.filter((line) => line.trim().length > 0 && !line.startsWith('#'));
const mismatchedPatterns: string[] = [];
const mismatchedOwners: string[] = [];
const gh = new octokit.Octokit({
auth: process.env.GITHUB_TOKEN,
}).rest;
const cache: Map<string, boolean> = new Map();
for (const line of codeownersLines) {
// This is perhaps a bit naive, but it should
// work for all paths and patterns that do not
// contain spaces.
const [specifiedPattern, ...owners] = line.split(' ');
let foundMatchingFiles = false;
const patternsToCheck = specifiedPattern.startsWith('/')
? [`.${specifiedPattern}`]
: [`./${specifiedPattern}`, `**/${specifiedPattern}`];
for (const pattern of patternsToCheck) {
foundMatchingFiles ||=
globby.globSync(pattern, {
ignore: ['node_modules', 'dist', 'build', '.git'],
cwd: path.join(__dirname, '..'),
onlyFiles: false,
}).length > 0;
}
if (!foundMatchingFiles) {
mismatchedPatterns.push(specifiedPattern);
}
if (process.env.GITHUB_TOKEN) {
for (let owner of owners) {
owner = owner.substring(1); // Remove the @
if (owner.includes('/')) {
// Its a team.
const [org, team] = owner.split('/');
let res = cache.get(owner);
if (res === undefined) {
res = await validateTeam(gh, org, team);
cache.set(owner, res);
}
if (res === false) {
mismatchedOwners.push(`${specifiedPattern}: ${owner}`);
}
} else {
let res = cache.get(owner);
if (res === undefined) {
res = await validateUser(gh, owner);
cache.set(owner, res);
}
if (res === false) {
mismatchedOwners.push(`${specifiedPattern}: ${owner}`);
}
}
}
} else {
output.warn({
title: `Skipping owner validation because GITHUB_TOKEN is not set.`,
});
}
}
if (mismatchedPatterns.length > 0) {
output.error({
title: `The following patterns in CODEOWNERS do not match any files:`,
bodyLines: mismatchedPatterns.map((e) => `- ${e}`),
});
}
if (mismatchedOwners.length > 0) {
output.error({
title: `The following owners in CODEOWNERS do not exist:`,
bodyLines: mismatchedOwners.map((e) => `- ${e}`),
});
}
process.exit(mismatchedPatterns.length + mismatchedOwners.length > 0 ? 1 : 0);
}
main();
async function validateTeam(
gh: Octokit['rest'],
org: string,
team: string
): Promise<boolean> {
try {
await gh.teams.getByName({
org,
team_slug: team,
});
return true;
} catch {
return false;
}
}
async function validateUser(
gh: Octokit['rest'],
username: string
): Promise<boolean> {
try {
await gh.users.getByUsername({
username,
});
return true;
} catch {
return false;
}
}