From e4028ae5c474ab1b240bced144746acc334146ff Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sat, 14 Nov 2020 13:02:40 +0000 Subject: [PATCH] build(docs-infra): compute previous version list (#39689) Previously we hand coded the list of previous major versions that are displayed in the left navigation. Now these are generated from the tags in GitHub. Closes #39688 PR Close #39689 --- aio/content/navigation.json | 30 --------- .../transforms/angular-base-package/index.js | 4 +- .../services/getPreviousMajorVersions.js | 54 ++++++++++++++++ .../services/getPreviousMajorVersions.spec.js | 63 +++++++++++++++++++ .../processors/processNavigationMap.js | 18 +++--- 5 files changed, 130 insertions(+), 39 deletions(-) create mode 100644 aio/tools/transforms/angular-base-package/services/getPreviousMajorVersions.js create mode 100644 aio/tools/transforms/angular-base-package/services/getPreviousMajorVersions.spec.js diff --git a/aio/content/navigation.json b/aio/content/navigation.json index 7a0cbf42f5..a082b064c7 100644 --- a/aio/content/navigation.json +++ b/aio/content/navigation.json @@ -1060,35 +1060,5 @@ } ] } - ], - "docVersions": [ - { - "title": "v9", - "url": "https://v9.angular.io/" - }, - { - "title": "v8", - "url": "https://v8.angular.io/" - }, - { - "title": "v7", - "url": "https://v7.angular.io/" - }, - { - "title": "v6", - "url": "https://v6.angular.io/" - }, - { - "title": "v5", - "url": "https://v5.angular.io/" - }, - { - "title": "v4", - "url": "https://v4.angular.io/" - }, - { - "title": "v2", - "url": "https://v2.angular.io/" - } ] } diff --git a/aio/tools/transforms/angular-base-package/index.js b/aio/tools/transforms/angular-base-package/index.js index 5ca5511e7e..5f1f6e5fc8 100644 --- a/aio/tools/transforms/angular-base-package/index.js +++ b/aio/tools/transforms/angular-base-package/index.js @@ -8,6 +8,7 @@ const path = require('path'); const Package = require('dgeni').Package; +const gitPackage = require('dgeni-packages/git'); const jsdocPackage = require('dgeni-packages/jsdoc'); const nunjucksPackage = require('dgeni-packages/nunjucks'); const linksPackage = require('../links-package'); @@ -19,7 +20,7 @@ const postProcessPackage = require('dgeni-packages/post-process-html'); const { PROJECT_ROOT, CONTENTS_PATH, OUTPUT_PATH, DOCS_OUTPUT_PATH, TEMPLATES_PATH, AIO_PATH, requireFolder } = require('../config'); module.exports = new Package('angular-base', [ - jsdocPackage, nunjucksPackage, linksPackage, examplesPackage, targetPackage, remarkPackage, postProcessPackage + gitPackage, jsdocPackage, nunjucksPackage, linksPackage, examplesPackage, targetPackage, remarkPackage, postProcessPackage ]) // Register the processors @@ -37,6 +38,7 @@ module.exports = new Package('angular-base', [ .factory(require('./readers/json')) .factory(require('./services/copyFolder')) .factory(require('./services/getImageDimensions')) + .factory(require('./services/getPreviousMajorVersions')) .factory(require('./services/auto-link-filters/filterPipes')) .factory(require('./services/auto-link-filters/filterAmbiguousDirectiveAliases')) .factory(require('./services/auto-link-filters/ignoreHttpInUrls')) diff --git a/aio/tools/transforms/angular-base-package/services/getPreviousMajorVersions.js b/aio/tools/transforms/angular-base-package/services/getPreviousMajorVersions.js new file mode 100644 index 0000000000..1632ab9aa0 --- /dev/null +++ b/aio/tools/transforms/angular-base-package/services/getPreviousMajorVersions.js @@ -0,0 +1,54 @@ +'use strict'; + +const child = require('child_process'); +const semver = require('semver'); +const versionMatcher = /refs\/tags\/(\d+.+)$/mg; + +/** + * Get a collection of all the previous "last major" versions sorted by semantic version. + * + * @param packageInfo injected from dgeni-packages/git + * @param versionInfo injected from dgeni-packages/git + * @returns an array of SemVer objects + */ +module.exports = function getPreviousMajorVersions(packageInfo, versionInfo) { + return () => { + // always use the remote tags as the local clone might not contain all commits when cloned with + // `git clone --depth=...` + const repoUrl = packageInfo.repository.url; + const tagResults = child.spawnSync('git', ['ls-remote', '--tags', repoUrl], {encoding: 'utf8'}); + + if (tagResults.status !== 0) { + return []; + } + + const majorVersions = {}; + tagResults.stdout.replace(versionMatcher, (_, tag) => { + const version = semver.parse(tag); + + // Not interested in tags that do not match semver format. + if (version === null) { + return; + } + + // Not interested in pre-release versions. + if (version.prerelease !== null && version.prerelease.length > 0) { + return; + } + + // Only interested in versions that are earlier than the current major. + if (version.major >= versionInfo.currentVersion.major) { + return; + } + + const currentMajor = majorVersions[version.major]; + if (currentMajor === undefined || semver.compare(version, currentMajor) === 1) { + // This version is newer than the currently captured version for this major. + majorVersions[version.major] = version; + } + }); + + // Sort them in descending order + return semver.sort(Object.values(majorVersions)).reverse(); + }; +}; diff --git a/aio/tools/transforms/angular-base-package/services/getPreviousMajorVersions.spec.js b/aio/tools/transforms/angular-base-package/services/getPreviousMajorVersions.spec.js new file mode 100644 index 0000000000..577f69d7f1 --- /dev/null +++ b/aio/tools/transforms/angular-base-package/services/getPreviousMajorVersions.spec.js @@ -0,0 +1,63 @@ +const child = require('child_process'); +const Dgeni = require('dgeni'); +const semver = require('semver'); + +const basePackage = require('../index'); + +describe('getPreviousMajorVersions', () => { + let getPreviousMajorVersions; + + beforeEach(() => { + const mockPackage = new Dgeni.Package('mock-package', [basePackage]) + .factory('versionInfo', mockVersionInfo) + .factory('packageInfo', mockPackageInfo); + const dgeni = new Dgeni([mockPackage]); + const injector = dgeni.configureInjector(); + getPreviousMajorVersions = injector.get('getPreviousMajorVersions'); + }); + + it('should spawn a child process to git', () => { + spyOn(child, 'spawnSync').and.returnValue({status: 0, stdout: ''}); + getPreviousMajorVersions(); + expect(child.spawnSync).toHaveBeenCalledWith('git', ['ls-remote', '--tags', 'SOME_GIT_URL'], { + encoding: 'utf8' + }); + }); + + it('should return an empty list for a failed git command', () => { + spyOn(child, 'spawnSync').and.returnValue({status: 1}); + expect(getPreviousMajorVersions()).toEqual([]); + }); + + it('should return an empty list for no tags', () => { + spyOn(child, 'spawnSync').and.returnValue({status: 0, stdout: ''}); + expect(getPreviousMajorVersions()).toEqual([]); + }); + + it('should return an array of latest major versions with major greater than current', () => { + spyOn(child, 'spawnSync').and.returnValue({ + status: 0, + stdout: ` + refs/pull/655 + refs/tags/some-tag + refs/tags/3.8.1 + refs/tags/4.2.9 + refs/tags/4.2.10 + refs/tags/5.6.1 + refs/tags/6.1.1 + ` + }); + expect(getPreviousMajorVersions()).toEqual([ + semver('4.2.10'), + semver('3.8.1'), + ]); + }); +}); + +function mockVersionInfo() { + return {currentVersion: new semver('5.1.0')}; +} + +function mockPackageInfo() { + return {repository: {url: 'SOME_GIT_URL'}}; +} diff --git a/aio/tools/transforms/angular.io-package/processors/processNavigationMap.js b/aio/tools/transforms/angular.io-package/processors/processNavigationMap.js index c744256749..41877db209 100644 --- a/aio/tools/transforms/angular.io-package/processors/processNavigationMap.js +++ b/aio/tools/transforms/angular.io-package/processors/processNavigationMap.js @@ -1,15 +1,14 @@ -module.exports = function processNavigationMap(versionInfo, log) { +module.exports = function processNavigationMap(versionInfo, getPreviousMajorVersions, log) { return { $runAfter: ['paths-computed'], $runBefore: ['rendering-docs'], $process: function(docs) { - const navigationDoc = docs.find(doc => doc.docType === 'navigation-json'); if (!navigationDoc) { throw new Error( - 'Missing navigation map document (docType="navigation-json").' + - 'Did you forget to add it to the readFileProcessor?'); + 'Missing navigation map document (docType="navigation-json").' + + 'Did you forget to add it to the readFileProcessor?'); } // Verify that all the navigation paths are to valid docs @@ -24,6 +23,9 @@ module.exports = function processNavigationMap(versionInfo, log) { throw new Error('processNavigationMap failed'); } + navigationDoc.data['docVersions'] = getPreviousMajorVersions().map( + v => ({title: `v${v.major}`, url: `https://v${v.major}.angular.io/`})); + // Add in the version data in a "secret" field to be extracted in the docs app navigationDoc.data['__versionInfo'] = versionInfo.currentVersion; } @@ -32,13 +34,13 @@ module.exports = function processNavigationMap(versionInfo, log) { function walk(node, map, path) { let errors = []; - for(const key in node) { + for (const key in node) { const child = node[key]; - if (child !== null) { // null is allowed + if (child !== null) { // null is allowed if (key === 'url') { - const url = child.replace(/#.*$/, ''); // strip hash + const url = child.replace(/#.*$/, ''); // strip hash if (isRelative(url) && !map[url]) { - errors.push({ path: path.join('.'), url }); + errors.push({path: path.join('.'), url}); } } else if (typeof child !== 'string') { errors = errors.concat(walk(child, map, path.concat([key])));