build(docs-infra): add path disambiguation (#41788)
When two documents have the same `outputPath`, only differing by letter casing, there can be problems on case-insensitive file-systems: Only one of each of the docs would end up being written. Moreover, the Webpack 5 bundler will error if it comes across files that have this kind of ambiguous paths. This commit adds a new docType: `disambiguator`, which will display a list of the docs that match an ambiguous path. Each of the ambiguous docs is then given a unique path and outputPath to ensure there are no collisions. PR Close #41788
This commit is contained in:
parent
2ede980fea
commit
538286df16
|
@ -37,6 +37,7 @@ module.exports = new Package('angular-base', [
|
||||||
.processor(require('./processors/renderLinkInfo'))
|
.processor(require('./processors/renderLinkInfo'))
|
||||||
.processor(require('./processors/checkContentRules'))
|
.processor(require('./processors/checkContentRules'))
|
||||||
.processor(require('./processors/splitDescription'))
|
.processor(require('./processors/splitDescription'))
|
||||||
|
.processor(require('./processors/disambiguateDocPaths'))
|
||||||
|
|
||||||
// overrides base packageInfo and returns the one for the 'angular/angular' repo.
|
// overrides base packageInfo and returns the one for the 'angular/angular' repo.
|
||||||
.factory('packageInfo', function() { return require(path.resolve(PROJECT_ROOT, 'package.json')); })
|
.factory('packageInfo', function() { return require(path.resolve(PROJECT_ROOT, 'package.json')); })
|
||||||
|
@ -66,7 +67,7 @@ module.exports = new Package('angular-base', [
|
||||||
collectExamples.exampleFolders = [];
|
collectExamples.exampleFolders = [];
|
||||||
|
|
||||||
generateKeywordsProcessor.ignoreWords = require(path.resolve(__dirname, 'ignore-words'))['en'];
|
generateKeywordsProcessor.ignoreWords = require(path.resolve(__dirname, 'ignore-words'))['en'];
|
||||||
generateKeywordsProcessor.docTypesToIgnore = [undefined, 'example-region', 'json-doc', 'api-list-data', 'api-list-data', 'contributors-json', 'navigation-json', 'announcements-json'];
|
generateKeywordsProcessor.docTypesToIgnore = [undefined, 'example-region', 'json-doc', 'api-list-data', 'api-list-data', 'contributors-json', 'navigation-json', 'announcements-json', 'disambiguator'];
|
||||||
generateKeywordsProcessor.propertiesToIgnore = ['basePath', 'renderedContent', 'docType', 'searchTitle'];
|
generateKeywordsProcessor.propertiesToIgnore = ['basePath', 'renderedContent', 'docType', 'searchTitle'];
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -167,5 +168,5 @@ module.exports = new Package('angular-base', [
|
||||||
})
|
})
|
||||||
|
|
||||||
.config(function(convertToJsonProcessor) {
|
.config(function(convertToJsonProcessor) {
|
||||||
convertToJsonProcessor.docTypes = [];
|
convertToJsonProcessor.docTypes = ['disambiguator'];
|
||||||
});
|
});
|
||||||
|
|
68
aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.js
vendored
Normal file
68
aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.js
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/**
|
||||||
|
* @dgProcessor disambiguateDocPathsProcessor
|
||||||
|
* @description
|
||||||
|
*
|
||||||
|
* Ensures that docs that have the same path, other than case changes,
|
||||||
|
* are disambiguated.
|
||||||
|
*
|
||||||
|
* For example in Angular there is the `ROUTES` const and a `Routes` type.
|
||||||
|
* In a case-sensitive file-system these would both be stored at the paths
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* aio/src/generated/router/Routes.json
|
||||||
|
* aio/src/generated/router/ROUTES.json
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* but in a case-insensitive file-system these two paths point to the same file!
|
||||||
|
*/
|
||||||
|
module.exports = function disambiguateDocPathsProcessor(log) {
|
||||||
|
return {
|
||||||
|
$runAfter: ['paths-computed'],
|
||||||
|
$runBefore: ['rendering-docs'],
|
||||||
|
$process(docs) {
|
||||||
|
// Collect all the ambiguous docs, whose outputPath is are only different by casing.
|
||||||
|
const ambiguousDocMap = new Map();
|
||||||
|
for (const doc of docs) {
|
||||||
|
if (!doc.outputPath) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const outputPath = doc.outputPath.toLowerCase();
|
||||||
|
if (!ambiguousDocMap.has(outputPath)) {
|
||||||
|
ambiguousDocMap.set(outputPath, []);
|
||||||
|
}
|
||||||
|
const ambiguousDocs = ambiguousDocMap.get(outputPath);
|
||||||
|
ambiguousDocs.push(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a disambiguator doc for each set of such ambiguous docs,
|
||||||
|
// and update the ambiguous docs to have unique `path` and `outputPath` properties.
|
||||||
|
for (const [outputPath, ambiguousDocs] of ambiguousDocMap) {
|
||||||
|
if (ambiguousDocs.length === 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug('Docs with ambiguous outputPath:' + ambiguousDocs.map((d, i) => `\n - ${d.id}: "${d.outputPath}" replaced with "${convertPath(d.outputPath, i)}".`));
|
||||||
|
|
||||||
|
const doc = ambiguousDocs[0];
|
||||||
|
const path = doc.path;
|
||||||
|
const id = `${doc.id.toLowerCase()}-disambiguator`;
|
||||||
|
const title = `${doc.id.toLowerCase()} (disambiguation)`;
|
||||||
|
const aliases = [id];
|
||||||
|
docs.push({ docType: 'disambiguator', id, title, aliases, path, outputPath, docs: ambiguousDocs });
|
||||||
|
|
||||||
|
// Update the paths
|
||||||
|
let count = 0;
|
||||||
|
for (const doc of ambiguousDocs) {
|
||||||
|
doc.path = convertPath(doc.path, count);
|
||||||
|
doc.outputPath = convertPath(doc.outputPath, count);
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function convertPath(path, count) {
|
||||||
|
// Add the counter before any extension
|
||||||
|
return path.replace(/(\.[^.]*)?$/, `-${count}$1`);
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
const testPackage = require('../../helpers/test-package');
|
||||||
|
const Dgeni = require('dgeni');
|
||||||
|
|
||||||
|
describe('disambiguateDocPaths processor', () => {
|
||||||
|
let dgeni, injector, processor, docs;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dgeni = new Dgeni([testPackage('angular-base-package')]);
|
||||||
|
injector = dgeni.configureInjector();
|
||||||
|
processor = injector.get('disambiguateDocPathsProcessor');
|
||||||
|
docs = [
|
||||||
|
{docType: 'test-doc', id: 'test-doc', path: 'test/doc', outputPath: 'test/doc.json'},
|
||||||
|
{docType: 'test-doc', id: 'TEST-DOC', path: 'TEST/DOC', outputPath: 'TEST/DOC.json'},
|
||||||
|
{docType: 'test-doc', id: 'test-Doc', path: 'test/Doc', outputPath: 'test/Doc.xml'},
|
||||||
|
{docType: 'test-doc', id: 'unique-doc', path: 'unique/doc', outputPath: 'unique/doc.json'},
|
||||||
|
{docType: 'test-doc', id: 'other-doc', path: 'other/doc', outputPath: 'other/doc.json'},
|
||||||
|
{docType: 'test-doc', id: 'other-DOC', path: 'other/DOC', outputPath: 'other/DOC.json'},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be part of the dgeni package', () => {
|
||||||
|
expect(processor).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create `disambiguator` documents for docs that have ambiguous outputPaths', () => {
|
||||||
|
const numDocs = docs.length;
|
||||||
|
processor.$process(docs);
|
||||||
|
expect(docs.length).toEqual(numDocs + 2);
|
||||||
|
expect(docs[docs.length - 2]).toEqual({
|
||||||
|
docType: 'disambiguator',
|
||||||
|
id: 'test-doc-disambiguator',
|
||||||
|
title: 'test-doc (disambiguation)',
|
||||||
|
aliases: ['test-doc-disambiguator'],
|
||||||
|
path: 'test/doc',
|
||||||
|
outputPath: 'test/doc.json',
|
||||||
|
docs: [docs[0], docs[1]],
|
||||||
|
});
|
||||||
|
expect(docs[docs.length - 1]).toEqual({
|
||||||
|
docType: 'disambiguator',
|
||||||
|
id: 'other-doc-disambiguator',
|
||||||
|
title: 'other-doc (disambiguation)',
|
||||||
|
aliases: ['other-doc-disambiguator'],
|
||||||
|
path: 'other/doc',
|
||||||
|
outputPath: 'other/doc.json',
|
||||||
|
docs: [docs[4], docs[5]],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the path and outputPath properties of each ambiguous doc', () => {
|
||||||
|
processor.$process(docs);
|
||||||
|
expect(docs[0].path).toEqual('test/doc-0');
|
||||||
|
expect(docs[0].outputPath).toEqual('test/doc-0.json');
|
||||||
|
expect(docs[1].path).toEqual('TEST/DOC-1');
|
||||||
|
expect(docs[1].outputPath).toEqual('TEST/DOC-1.json');
|
||||||
|
|
||||||
|
// The non-ambiguous docs are left alone
|
||||||
|
expect(docs[2].outputPath).toEqual('test/Doc.xml');
|
||||||
|
expect(docs[3].outputPath).toEqual('unique/doc.json');
|
||||||
|
|
||||||
|
expect(docs[4].path).toEqual('other/doc-0');
|
||||||
|
expect(docs[4].outputPath).toEqual('other/doc-0.json');
|
||||||
|
expect(docs[5].path).toEqual('other/DOC-1');
|
||||||
|
expect(docs[5].outputPath).toEqual('other/DOC-1.json');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% marked %}
|
||||||
|
# {$ doc.title $}
|
||||||
|
{% endmarked %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="content">
|
||||||
|
There are multiple pages that match this path:
|
||||||
|
<ul>
|
||||||
|
{% for ambiguousDoc in doc.docs %}<li>
|
||||||
|
<a href="{$ ambiguousDoc.path $}">{$ ambiguousDoc.id $}</a>
|
||||||
|
{$ ambiguousDoc.shortDescription | marked $}
|
||||||
|
</li>{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue