test: expand the `verify-codeownership.js` script to also check packages (#32577)

The `aio/scripts/verify-codeownership.js` script (formerly
`verify-docs-codeownership.js`) can be used to verify whether there are
directories in the codebase that don't have a codeowner (in
`.github/CODEOWNERS`) and vice versa. It does not aim to cover all files
and directories, but do a coarse check on some important (or frequently
changing) directories.

Previously, the script only checked for API docs examples (in
`packages/examples/`) and guides (and related images and example) (in
`aio/content/`).

This commit expands the script to also check for packages (i.e.
top-level directories in `packages/`). It also renames the script from
`verify-docs-codeownership.js` to `verify-codeownership.js`, to better
reflect its new behavior.

PR Close #32577
This commit is contained in:
George Kalpakas 2019-09-10 13:47:36 +03:00 committed by Matias Niemelä
parent 79b673a17f
commit d0dd69f177
1 changed files with 65 additions and 22 deletions

View File

@ -1,5 +1,26 @@
#!/usr/bin/env node
'use strict'; 'use strict';
/**
* **Usage:**
* ```
* node aio/scripts/verify-codeownership
* ```
*
* Verify whether there are directories in the codebase that don't have a codeowner (in `.github/CODEOWNERS`) and vice
* versa (that there are no patterns in `CODEOWNERS` that do not correspond to actual directories).
*
* The script does not aim to be exhaustive and highly accurate, checking all files and directories (since that would be
* too complicated). Instead, it does a coarse check on some important (or frequently changing) directories.
*
* Currently, it checks the following:
* - **Packages**: Top-level directories in `packages/`.
* - **API docs examples**: Top-level directories in `packages/examples/`.
* - **Guides**: Top-level files in `aio/content/guide/`.
* - **Guide images**: Top-level directories in `aio/content/images/guide/`.
* - **Guide examples**: Top-level directories in `aio/content/examples/`.
*/
// Imports // Imports
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
@ -7,44 +28,53 @@ const path = require('path');
// Constants // Constants
const PROJECT_ROOT_DIR = path.resolve(__dirname, '../..'); const PROJECT_ROOT_DIR = path.resolve(__dirname, '../..');
const CODEOWNERS_PATH = path.resolve(PROJECT_ROOT_DIR, '.github/CODEOWNERS'); const CODEOWNERS_PATH = path.resolve(PROJECT_ROOT_DIR, '.github/CODEOWNERS');
const PKG_EXAMPLES_DIR = path.resolve(PROJECT_ROOT_DIR, 'packages/examples'); const PKG_DIR = path.resolve(PROJECT_ROOT_DIR, 'packages');
const PKG_EXAMPLES_DIR = path.resolve(PKG_DIR, 'examples');
const AIO_CONTENT_DIR = path.resolve(PROJECT_ROOT_DIR, 'aio/content'); const AIO_CONTENT_DIR = path.resolve(PROJECT_ROOT_DIR, 'aio/content');
const AIO_GUIDES_DIR = path.resolve(AIO_CONTENT_DIR, 'guide'); const AIO_GUIDES_DIR = path.resolve(AIO_CONTENT_DIR, 'guide');
const AIO_GUIDE_IMAGES_DIR = path.resolve(AIO_CONTENT_DIR, 'images/guide'); const AIO_GUIDE_IMAGES_DIR = path.resolve(AIO_CONTENT_DIR, 'images/guide');
const AIO_GUIDE_EXAMPLES_DIR = path.resolve(AIO_CONTENT_DIR, 'examples'); const AIO_GUIDE_EXAMPLES_DIR = path.resolve(AIO_CONTENT_DIR, 'examples');
const IGNORED_PKG_DIRS = new Set([
// Examples are checked separately.
'examples',
]);
// Run // Run
_main(); _main();
// Functions - Definitions // Functions - Definitions
function _main() { function _main() {
const {examples: pkgExamplePaths} = getPathsFromPkgExamples(); const {packages: pkgPackagePaths, examples: pkgExamplePaths} = getPathsFromPkg();
const {guides: aioGuidePaths, images: aioGuideImagesPaths, examples: aioExamplePaths} = getPathsFromAioContent(); const {guides: aioGuidePaths, images: aioGuideImagesPaths, examples: aioExamplePaths} = getPathsFromAioContent();
const { const {
pkgPackages: coPkgPackagePaths,
pkgExamples: coPkgExamplePaths,
aioGuides: coAioGuidePaths, aioGuides: coAioGuidePaths,
aioImages: coAioGuideImagesPaths, aioImages: coAioGuideImagesPaths,
aioExamples: coAioExamplePaths, aioExamples: coAioExamplePaths,
pkgExamples: coPkgExamplePaths,
} = getPathsFromCodeowners(); } = getPathsFromCodeowners();
const pkgPackagesDiff = arrayDiff(pkgPackagePaths, coPkgPackagePaths);
const pkgExamplesDiff = arrayDiff(pkgExamplePaths, coPkgExamplePaths);
const aioGuidesDiff = arrayDiff(aioGuidePaths, coAioGuidePaths); const aioGuidesDiff = arrayDiff(aioGuidePaths, coAioGuidePaths);
const aioImagesDiff = arrayDiff(aioGuideImagesPaths, coAioGuideImagesPaths); const aioImagesDiff = arrayDiff(aioGuideImagesPaths, coAioGuideImagesPaths);
const aioExamplesDiff = arrayDiff(aioExamplePaths, coAioExamplePaths); const aioExamplesDiff = arrayDiff(aioExamplePaths, coAioExamplePaths);
const pkgExamplesDiff = arrayDiff(pkgExamplePaths, coPkgExamplePaths); const hasDiff = (pkgPackagesDiff.diffCount > 0) || (pkgExamplesDiff.diffCount > 0) ||
const hasDiff = (aioGuidesDiff.diffCount > 0) || (aioImagesDiff.diffCount> 0) || (aioExamplesDiff.diffCount > 0) || (aioGuidesDiff.diffCount > 0) || (aioImagesDiff.diffCount> 0) || (aioExamplesDiff.diffCount > 0);
(pkgExamplesDiff.diffCount > 0);
if (hasDiff) { if (hasDiff) {
const expectedPkgPackagesSrc = path.relative(PROJECT_ROOT_DIR, PKG_DIR);
const expectedPkgExamplesSrc = path.relative(PROJECT_ROOT_DIR, PKG_EXAMPLES_DIR);
const expectedAioGuidesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDES_DIR); const expectedAioGuidesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDES_DIR);
const expectedAioImagesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDE_IMAGES_DIR); const expectedAioImagesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDE_IMAGES_DIR);
const expectedAioExamplesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDE_EXAMPLES_DIR); const expectedAioExamplesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDE_EXAMPLES_DIR);
const expectedPkgExamplesSrc = path.relative(PROJECT_ROOT_DIR, PKG_EXAMPLES_DIR);
const actualSrc = path.relative(PROJECT_ROOT_DIR, CODEOWNERS_PATH); const actualSrc = path.relative(PROJECT_ROOT_DIR, CODEOWNERS_PATH);
reportDiff(pkgPackagesDiff, expectedPkgPackagesSrc, actualSrc);
reportDiff(pkgExamplesDiff, expectedPkgExamplesSrc, actualSrc);
reportDiff(aioGuidesDiff, expectedAioGuidesSrc, actualSrc); reportDiff(aioGuidesDiff, expectedAioGuidesSrc, actualSrc);
reportDiff(aioImagesDiff, expectedAioImagesSrc, actualSrc); reportDiff(aioImagesDiff, expectedAioImagesSrc, actualSrc);
reportDiff(aioExamplesDiff, expectedAioExamplesSrc, actualSrc); reportDiff(aioExamplesDiff, expectedAioExamplesSrc, actualSrc);
reportDiff(pkgExamplesDiff, expectedPkgExamplesSrc, actualSrc);
} }
process.exit(hasDiff ? 1 : 0); process.exit(hasDiff ? 1 : 0);
@ -57,6 +87,11 @@ function arrayDiff(expected, actual) {
return {missing, extra, diffCount: missing.length + extra.length}; return {missing, extra, diffCount: missing.length + extra.length};
} }
function findDirectories(parentDir) {
return fs.readdirSync(parentDir).
filter(name => fs.statSync(`${parentDir}/${name}`).isDirectory());
}
function getPathsFromAioContent() { function getPathsFromAioContent() {
return { return {
guides: fs.readdirSync(AIO_GUIDES_DIR), guides: fs.readdirSync(AIO_GUIDES_DIR),
@ -67,20 +102,22 @@ function getPathsFromAioContent() {
} }
function getPathsFromCodeowners() { function getPathsFromCodeowners() {
const pkgPackagesPathRe = /^\/packages\/([^\s\*/]+)\/\*?\*\s/;
const pkgExamplesPathRe = /^\/packages\/examples\/([^\s\*/]+)/;
// Use capturing groups for `images/` and `examples` to be able to differentiate between the // Use capturing groups for `images/` and `examples` to be able to differentiate between the
// different kinds of matches (guide, image, example) later (see `isImage`/`isExample` below). // different kinds of matches (guide, image, example) later (see `isImage`/`isExample` below).
const aioGuidesOrImagesPathRe = /^\/aio\/content\/(?:(images\/)?guide|(examples))\/([^\s\*/]+)/; const aioGuidesImagesExamplesPathRe = /^\/aio\/content\/(?:(images\/)?guide|(examples))\/([^\s\*/]+)/;
const pkgExamplesPathRe = /^\/packages\/examples\/([^\s\*/]+)/;
const manualGlobExpansions = { const manualGlobExpansions = {
// `CODEOWNERS` has a glob to match all `testing/` directories, so no specific glob for // `CODEOWNERS` has a glob to match all `testing/` directories, so no specific glob for
// `packages/examples/testing/` is necessary. // `packages/examples/testing/` is necessary.
'testing/**': ['/packages/examples/testing/**'], 'testing/**': ['/packages/examples/testing/**'],
}; };
const pkgPackages = [];
const pkgExamples = [];
const aioGuides = []; const aioGuides = [];
const aioImages = []; const aioImages = [];
const aioExamples = []; const aioExamples = [];
const pkgExamples = [];
// Read `CODEOWNERS` and split into lines. // Read `CODEOWNERS` and split into lines.
const lines = fs. const lines = fs.
@ -96,9 +133,21 @@ function getPathsFromCodeowners() {
} }
} }
// Collect packages (`packages/`).
lines.
map(l => l.match(pkgPackagesPathRe)).
filter(m => m).
forEach(([, path]) => pkgPackages.push(path));
// Collect API docs examples (`packages/examples/`).
lines.
map(l => l.match(pkgExamplesPathRe)).
filter(m => m).
forEach(([, path]) => pkgExamples.push(path));
// Collect `aio/` guides/images/examples. // Collect `aio/` guides/images/examples.
lines. lines.
map(l => l.match(aioGuidesOrImagesPathRe)). map(l => l.match(aioGuidesImagesExamplesPathRe)).
filter(m => m). filter(m => m).
forEach(([, isImage, isExample, path]) => { forEach(([, isImage, isExample, path]) => {
const list = isExample ? aioExamples : const list = isExample ? aioExamples :
@ -107,19 +156,13 @@ function getPathsFromCodeowners() {
list.push(path); list.push(path);
}); });
// Collect API docs examples (`packages/examples/`). return {pkgPackages, pkgExamples, aioGuides, aioImages, aioExamples};
lines.
map(l => l.match(pkgExamplesPathRe)).
filter(m => m).
forEach(([, path]) => pkgExamples.push(path));
return {aioGuides, aioImages, aioExamples, pkgExamples};
} }
function getPathsFromPkgExamples() { function getPathsFromPkg() {
return { return {
examples: fs.readdirSync(PKG_EXAMPLES_DIR). packages: findDirectories(PKG_DIR).filter(name => !IGNORED_PKG_DIRS.has(name)),
filter(name => fs.statSync(`${PKG_EXAMPLES_DIR}/${name}`).isDirectory()), examples: findDirectories(PKG_EXAMPLES_DIR),
}; };
} }