From afc6ab5442b9b0a51c4dac8a280f3eefca479616 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 28 Aug 2019 00:39:13 +0300 Subject: [PATCH] test(docs-infra): modify `verify-guide-codeownership.js` to also check API docs examples (#32360) This script can be used for manually checking that all docs guides/examples/images have owners in `.github/CODEOWNERS`. This commit adds support for also checking codeownership of API docs examples (in `packages/examples/*`). The script is also renamed to the more generic (and accurate) `verify-docs-codeownership.js`. PR Close #32360 --- aio/scripts/verify-docs-codeownership.js | 126 ++++++++++++++++++++++ aio/scripts/verify-guide-codeownership.js | 87 --------------- 2 files changed, 126 insertions(+), 87 deletions(-) create mode 100644 aio/scripts/verify-docs-codeownership.js delete mode 100644 aio/scripts/verify-guide-codeownership.js diff --git a/aio/scripts/verify-docs-codeownership.js b/aio/scripts/verify-docs-codeownership.js new file mode 100644 index 0000000000..4b36b3b136 --- /dev/null +++ b/aio/scripts/verify-docs-codeownership.js @@ -0,0 +1,126 @@ +'use strict'; + +// Imports +const fs = require('fs'); +const path = require('path'); + +// Constants +const PROJECT_ROOT_DIR = path.resolve(__dirname, '../..'); +const CODEOWNERS_PATH = path.resolve(PROJECT_ROOT_DIR, '.github/CODEOWNERS'); +const PKG_EXAMPLES_DIR = path.resolve(PROJECT_ROOT_DIR, 'packages/examples'); +const AIO_CONTENT_DIR = path.resolve(PROJECT_ROOT_DIR, 'aio/content'); +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_EXAMPLES_DIR = path.resolve(AIO_CONTENT_DIR, 'examples'); + +// Run +_main(); + +// Functions - Definitions +function _main() { + const {examples: pkgExamplePaths} = getPathsFromPkgExamples(); + const {guides: aioGuidePaths, images: aioGuideImagesPaths, examples: aioExamplePaths} = getPathsFromAioContent(); + const { + aioGuides: coAioGuidePaths, + aioImages: coAioGuideImagesPaths, + aioExamples: coAioExamplePaths, + pkgExamples: coPkgExamplePaths, + } = getPathsFromCodeowners(); + + const aioGuidesDiff = arrayDiff(aioGuidePaths, coAioGuidePaths); + const aioImagesDiff = arrayDiff(aioGuideImagesPaths, coAioGuideImagesPaths); + const aioExamplesDiff = arrayDiff(aioExamplePaths, coAioExamplePaths); + const pkgExamplesDiff = arrayDiff(pkgExamplePaths, coPkgExamplePaths); + const hasDiff = (aioGuidesDiff.diffCount > 0) || (aioImagesDiff.diffCount> 0) || (aioExamplesDiff.diffCount > 0) || + (pkgExamplesDiff.diffCount > 0); + + if (hasDiff) { + const expectedAioGuidesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDES_DIR); + const expectedAioImagesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDE_IMAGES_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); + + reportDiff(aioGuidesDiff, expectedAioGuidesSrc, actualSrc); + reportDiff(aioImagesDiff, expectedAioImagesSrc, actualSrc); + reportDiff(aioExamplesDiff, expectedAioExamplesSrc, actualSrc); + reportDiff(pkgExamplesDiff, expectedPkgExamplesSrc, actualSrc); + } + + process.exit(hasDiff ? 1 : 0); +} + +function arrayDiff(expected, actual) { + const missing = expected.filter(x => !actual.includes(x)).sort(); + const extra = actual.filter(x => !expected.includes(x)).sort(); + + return {missing, extra, diffCount: missing.length + extra.length}; +} + +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()), + }; +} + +function getPathsFromCodeowners() { + // 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 aioGuidesOrImagesPathRe = /^\/aio\/content\/(?:(images\/)?guide|(examples))\/([^\s\*/]+)/; + const pkgExamplesPathRe = /^\/packages\/examples\/([^\s\*/]+)/; + + const aioGuides = []; + const aioImages = []; + const aioExamples = []; + const pkgExamples = []; + + // Read `CODEOWNERS` and split into lines. + const lines = fs. + readFileSync(CODEOWNERS_PATH, 'utf8'). + split('\n'). + map(l => l.trim()); + + // Collect `aio/` guides/images/examples. + lines. + map(l => l.match(aioGuidesOrImagesPathRe)). + filter(m => m). + forEach(([, isImage, isExample, path]) => { + const list = isExample ? aioExamples : + isImage ? aioImages : + aioGuides; + list.push(path); + }); + + // Collect API docs examples (`packages/examples/`). + lines. + map(l => l.match(pkgExamplesPathRe)). + filter(m => m). + forEach(([, path]) => pkgExamples.push(path)); + + return {aioGuides, aioImages, aioExamples, pkgExamples}; +} + +function getPathsFromPkgExamples() { + return { + examples: fs.readdirSync(PKG_EXAMPLES_DIR). + filter(name => fs.statSync(`${PKG_EXAMPLES_DIR}/${name}`).isDirectory()), + }; +} + + +function reportDiff(diff, expectedSrc, actualSrc) { + if (diff.missing.length) { + console.error( + `\nEntries in '${expectedSrc}' but not in '${actualSrc}':\n` + + diff.missing.map(x => ` - ${x}`).join('\n')); + } + + if (diff.extra.length) { + console.error( + `\nEntries in '${actualSrc}' but not in '${expectedSrc}':\n` + + diff.extra.map(x => ` - ${x}`).join('\n')); + } +} diff --git a/aio/scripts/verify-guide-codeownership.js b/aio/scripts/verify-guide-codeownership.js deleted file mode 100644 index 1632072970..0000000000 --- a/aio/scripts/verify-guide-codeownership.js +++ /dev/null @@ -1,87 +0,0 @@ -'use strict'; - -// Imports -const fs = require('fs'); -const path = require('path'); - -// Constants -const PROJECT_ROOT_DIR = path.resolve(__dirname, '../..'); -const CODEOWNERS_PATH = path.resolve(PROJECT_ROOT_DIR, '.github/CODEOWNERS'); -const AIO_CONTENT_DIR = path.resolve(PROJECT_ROOT_DIR, 'aio/content'); -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_EXAMPLES_DIR = path.resolve(AIO_CONTENT_DIR, 'examples'); - -// Run -_main(); - -// Functions - Definitions -function _main() { - const {guides: acGuidePaths, images: acGuideImagesPaths, examples: acExamplePaths} = getPathsFromAioContent(); - const {guides: coGuidePaths, images: coGuideImagesPaths, examples: coExamplePaths} = getPathsFromCodeowners(); - - const guidesDiff = arrayDiff(acGuidePaths, coGuidePaths); - const imagesDiff = arrayDiff(acGuideImagesPaths, coGuideImagesPaths); - const examplesDiff = arrayDiff(acExamplePaths, coExamplePaths); - const hasDiff = !!(guidesDiff.diffCount || imagesDiff.diffCount || examplesDiff.diffCount); - - if (hasDiff) { - const expectedGuidesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDES_DIR); - const expectedImagesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDE_IMAGES_DIR); - const expectedExamplesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDE_EXAMPLES_DIR); - const actualSrc = path.relative(PROJECT_ROOT_DIR, CODEOWNERS_PATH); - - reportDiff(guidesDiff, expectedGuidesSrc, actualSrc); - reportDiff(imagesDiff, expectedImagesSrc, actualSrc); - reportDiff(examplesDiff, expectedExamplesSrc, actualSrc); - } - - process.exit(hasDiff ? 1 : 0); -} - -function arrayDiff(expected, actual) { - const missing = expected.filter(x => !actual.includes(x)).sort(); - const extra = actual.filter(x => !expected.includes(x)).sort(); - - return {missing, extra, diffCount: missing.length + extra.length}; -} - -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()), - }; -} - -function getPathsFromCodeowners() { - const guidesOrImagesPathRe = /^\/aio\/content\/(?:(images\/)?guide|(examples))\/([^\s/]+)/; - - return fs. - readFileSync(CODEOWNERS_PATH, 'utf8'). - split('\n'). - map(l => l.trim().match(guidesOrImagesPathRe)). - filter(m => m). - reduce((aggr, [, isImage, isExample, path]) => { - const list = isExample ? aggr.examples : - isImage ? aggr.images : - aggr.guides; - list.push(path); - return aggr; - }, {guides: [], images: [], examples: []}); -} - -function reportDiff(diff, expectedSrc, actualSrc) { - if (diff.missing.length) { - console.error( - `\nEntries in '${expectedSrc}' but not in '${actualSrc}':\n` + - diff.missing.map(x => ` - ${x}`).join('\n')); - } - - if (diff.extra.length) { - console.error( - `\nEntries in '${actualSrc}' but not in '${expectedSrc}':\n` + - diff.extra.map(x => ` - ${x}`).join('\n')); - } -}