diff --git a/.circleci/config.yml b/.circleci/config.yml index 3d103c64a8..bfa50ff2db 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -234,6 +234,7 @@ jobs: (echo -e "\n.bzl files have lint errors. Please run ''yarn bazel:lint-fix''"; exit 1)' - run: yarn gulp lint + - run: node tools/verify-codeownership test: <<: *job_defaults diff --git a/aio/scripts/verify-codeownership.js b/tools/verify-codeownership.js similarity index 66% rename from aio/scripts/verify-codeownership.js rename to tools/verify-codeownership.js index 4c035bd484..00d5b46142 100644 --- a/aio/scripts/verify-codeownership.js +++ b/tools/verify-codeownership.js @@ -1,17 +1,24 @@ -#!/usr/bin/env node -'use strict'; +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ /** * **Usage:** * ``` - * node aio/scripts/verify-codeownership + * node tools/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). + * 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. + * 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/`. @@ -20,13 +27,15 @@ * - **Guide images**: Top-level directories in `aio/content/images/guide/`. * - **Guide examples**: Top-level directories in `aio/content/examples/`. */ +'use strict'; // Imports +const chalk = require('chalk'); const fs = require('fs'); const path = require('path'); // 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 PKG_DIR = path.resolve(PROJECT_ROOT_DIR, 'packages'); const PKG_EXAMPLES_DIR = path.resolve(PKG_DIR, 'examples'); @@ -45,7 +54,11 @@ _main(); // Functions - Definitions function _main() { const {packages: pkgPackagePaths, examples: pkgExamplePaths} = getPathsFromPkg(); - const {guides: aioGuidePaths, images: aioGuideImagesPaths, examples: aioExamplePaths} = getPathsFromAioContent(); + const { + guides: aioGuidePaths, + images: aioGuideImagesPaths, + examples: aioExamplePaths, + } = getPathsFromAioContent(); const { pkgPackages: coPkgPackagePaths, pkgExamples: coPkgExamplePaths, @@ -60,7 +73,8 @@ function _main() { const aioImagesDiff = arrayDiff(aioGuideImagesPaths, coAioGuideImagesPaths); const aioExamplesDiff = arrayDiff(aioExamplePaths, coAioExamplePaths); const hasDiff = (pkgPackagesDiff.diffCount > 0) || (pkgExamplesDiff.diffCount > 0) || - (aioGuidesDiff.diffCount > 0) || (aioImagesDiff.diffCount> 0) || (aioExamplesDiff.diffCount > 0); + (aioGuidesDiff.diffCount > 0) || (aioImagesDiff.diffCount > 0) || + (aioExamplesDiff.diffCount > 0); if (hasDiff) { const expectedPkgPackagesSrc = path.relative(PROJECT_ROOT_DIR, PKG_DIR); @@ -75,6 +89,16 @@ function _main() { reportDiff(aioGuidesDiff, expectedAioGuidesSrc, actualSrc); reportDiff(aioImagesDiff, expectedAioImagesSrc, actualSrc); reportDiff(aioExamplesDiff, expectedAioExamplesSrc, actualSrc); + + // tslint:disable-next-line: no-console + console.log(chalk.red( + '\nCode-ownership verification failed.\n' + + 'Please update \'.github/CODEOWNERS\' to ensure that all necessary files/directories ' + + 'have code-owners and all patterns that appear in the file correspond to actual ' + + 'files/directories in the repo.')); + } else { + // tslint:disable-next-line: no-console + console.log(chalk.green('\nCode-ownership verification succeeded!')); } process.exit(hasDiff ? 1 : 0); @@ -88,16 +112,16 @@ function arrayDiff(expected, actual) { } function findDirectories(parentDir) { - return fs.readdirSync(parentDir). - filter(name => fs.statSync(`${parentDir}/${name}`).isDirectory()); + return fs.readdirSync(parentDir).filter( + name => fs.statSync(`${parentDir}/${name}`).isDirectory()); } function getPathsFromAioContent() { return { guides: fs.readdirSync(AIO_GUIDES_DIR), images: fs.readdirSync(AIO_GUIDE_IMAGES_DIR), - examples: fs.readdirSync(AIO_GUIDE_EXAMPLES_DIR). - filter(name => fs.statSync(`${AIO_GUIDE_EXAMPLES_DIR}/${name}`).isDirectory()), + examples: fs.readdirSync(AIO_GUIDE_EXAMPLES_DIR) + .filter(name => fs.statSync(`${AIO_GUIDE_EXAMPLES_DIR}/${name}`).isDirectory()), }; } @@ -106,7 +130,8 @@ function getPathsFromCodeowners() { const pkgExamplesPathRe = /^\/packages\/examples\/([^\s\*/]+)/; // 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). - const aioGuidesImagesExamplesPathRe = /^\/aio\/content\/(?:(images\/)?guide|(examples))\/([^\s\*/]+)/; + const aioGuidesImagesExamplesPathRe = + /^\/aio\/content\/(?:(images\/)?guide|(examples))\/([^\s\*/]+)/; const manualGlobExpansions = { // `CODEOWNERS` has a glob to match all `testing/` directories, so no specific glob for // `packages/examples/testing/` is necessary. @@ -120,10 +145,7 @@ function getPathsFromCodeowners() { const aioExamples = []; // Read `CODEOWNERS` and split into lines. - const lines = fs. - readFileSync(CODEOWNERS_PATH, 'utf8'). - split('\n'). - map(l => l.trim()); + const lines = fs.readFileSync(CODEOWNERS_PATH, 'utf8').split('\n').map(l => l.trim()); // Manually expand globs to known matching patterns. for (const [glob, expansions] of Object.entries(manualGlobExpansions)) { @@ -134,27 +156,22 @@ function getPathsFromCodeowners() { } // Collect packages (`packages/`). - lines. - map(l => l.match(pkgPackagesPathRe)). - filter(m => m). - forEach(([, path]) => pkgPackages.push(path)); + 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)); + lines.map(l => l.match(pkgExamplesPathRe)).filter(m => m).forEach(([ + , path + ]) => pkgExamples.push(path)); // Collect `aio/` guides/images/examples. - lines. - map(l => l.match(aioGuidesImagesExamplesPathRe)). - filter(m => m). - forEach(([, isImage, isExample, path]) => { - const list = isExample ? aioExamples : - isImage ? aioImages : - aioGuides; - list.push(path); - }); + lines.map(l => l.match(aioGuidesImagesExamplesPathRe)) + .filter(m => m) + .forEach(([, isImage, isExample, path]) => { + const list = isExample ? aioExamples : isImage ? aioImages : aioGuides; + list.push(path); + }); return {pkgPackages, pkgExamples, aioGuides, aioImages, aioExamples}; }