import chalk from 'chalk'; import {readdirSync, readFileSync, statSync} from 'fs'; import {IMinimatch, Minimatch} from 'minimatch'; import {join} from 'path'; /** * Script that lints the CODEOWNERS file and makes sure that all files have an owner. */ /** Path for the Github owners file. */ const ownersFilePath = '.github/CODEOWNERS'; /** Path for the .gitignore file. */ const gitIgnorePath = '.gitignore'; let errors = 0; const ownedPaths = readFileSync(ownersFilePath, 'utf8').split('\n') // Trim lines. .map(line => line.trim()) // Remove empty lines and comments. .filter(line => line && !line.startsWith('#')) // Split off just the path glob. .map(line => line.split(/\s+/)[0]) // Turn paths into Minimatch objects. .map(path => new Minimatch(path, {dot: true, matchBase: true})); const ignoredPaths = readFileSync(gitIgnorePath, 'utf8').split('\n') // Trim lines. .map(line => line.trim()) // Remove empty lines and comments. .filter(line => line && !line.startsWith('#')) // Turn paths into Minimatch objects. .map(path => new Minimatch(path, {dot: true, matchBase: true})); for (let paths = getChildPaths('.'); paths.length;) { paths = Array.prototype.concat(...paths // Remove ignored paths .filter(path => !ignoredPaths.reduce( (isIgnored, ignoredPath) => isIgnored || ignoredPath.match('/' + path), false)) // Remove paths that match an owned path. .filter(path => !ownedPaths.reduce( (isOwned, ownedPath) => isOwned || isOwnedBy(path, ownedPath), false)) // Report an error for any files that didn't match any owned paths. .filter(path => { if (statSync(path).isFile()) { console.log(chalk.red(`No code owner found for "${path}".`)); errors++; return false; } return true; }) // Get the next level of children for any directories. .map(path => getChildPaths(path))); } if (errors) { throw Error(`Found ${errors} files with no owner. Code owners for the files ` + `should be added in the CODEOWNERS file.`); } /** Check if the given path is owned by the given owned path matcher. */ function isOwnedBy(path: string, ownedPath: IMinimatch) { // If the owned path ends with `**` its safe to eliminate whole directories. if (ownedPath.pattern.endsWith('**') || statSync(path).isFile()) { return ownedPath.match('/' + path); } return false; } /** Get the immediate child paths of the given path. */ function getChildPaths(path: string) { return readdirSync(path).map(child => join(path, child)); }