diff --git a/aio/tools/transforms/cli-docs-package/processors/processCliCommands.js b/aio/tools/transforms/cli-docs-package/processors/processCliCommands.js index 0de9909d7b..5b6e5448b1 100644 --- a/aio/tools/transforms/cli-docs-package/processors/processCliCommands.js +++ b/aio/tools/transforms/cli-docs-package/processors/processCliCommands.js @@ -4,11 +4,9 @@ module.exports = function processCliCommands(createDocMessage) { $runBefore: ['rendering-docs'], $process(docs) { const navigationDoc = docs.find(doc => doc.docType === 'navigation-json'); - const navigationNode = navigationDoc && - navigationDoc.data['SideNav'].find( - node => node.children && node.children.length && node.children[0].url === 'cli'); + const cliCommandsNode = navigationDoc && findCliCommandsNode(navigationDoc.data['SideNav']); - if (!navigationNode) { + if (!cliCommandsNode) { throw new Error(createDocMessage( 'Missing `cli` url - CLI Commands must include a first child node with url set at `cli`', navigationDoc)); @@ -24,13 +22,41 @@ module.exports = function processCliCommands(createDocMessage) { doc.optionKeywords = Array.from(optionKeywords).join(' '); // Add to navigation doc - navigationNode.children.push({url: doc.path, title: `ng ${doc.name}`}); + cliCommandsNode.children.push({url: doc.path, title: `ng ${doc.name}`}); } }); } }; }; +// Look for the `CLI Commands` navigation node. It is the node whose first child has `url: 'cli'`. +// (NOTE: Using the URL instead of the title, because it is more robust.) +function findCliCommandsNode(nodes) { + // We will "recursively" check all navigation nodes and their children (in breadth-first order), + // until we find the `CLI Commands` node. Keep a list of nodes lists to check. + // (NOTE: Each item in the list is a LIST of nodes.) + const nodesList = [nodes]; + + while (nodesList.length > 0) { + // Get the first item from the list of nodes lists. + const currentNodes = nodesList.shift(); + const cliCommandsNode = currentNodes.find(isCliCommandsNode); + + // One of the nodes in `currentNodes` was the `CLI Commands` node. Return it. + if (cliCommandsNode) return cliCommandsNode; + + // The `CLI Commands` node is not in `currentNodes`. Check each node's children (if any). + currentNodes.forEach(node => node.children && nodesList.push(node.children)); + } + + // We checked all navigation nodes and their children and did not find the `CLI Commands` node. + return undefined; +} + +function isCliCommandsNode(node) { + return node.children && node.children.length && node.children[0].url === 'cli'; +} + function processOptions(container, options, optionKeywords) { container.positionalOptions = []; container.namedOptions = []; diff --git a/aio/tools/transforms/cli-docs-package/processors/processCliCommands.spec.js b/aio/tools/transforms/cli-docs-package/processors/processCliCommands.spec.js index 97413d647e..79e0dbb0ec 100644 --- a/aio/tools/transforms/cli-docs-package/processors/processCliCommands.spec.js +++ b/aio/tools/transforms/cli-docs-package/processors/processCliCommands.spec.js @@ -258,14 +258,15 @@ describe('processCliCommands processor', () => { docType: 'navigation-json', data: { SideNav: [ - {url: 'some/page', title: 'Some Page'}, { + {url: 'some/page', title: 'Some Page'}, + { title: 'CLI Commands', tooltip: 'Angular CLI command reference', - children: [{'title': 'Overview', 'url': 'cli'}] + children: [{'title': 'Overview', 'url': 'cli'}], }, - {url: 'other/page', title: 'Other Page'} - ] - } + {url: 'other/page', title: 'Other Page'}, + ], + }, }; processor.$process([command, navigation]); expect(navigation.data.SideNav[1].title).toEqual('CLI Commands'); @@ -275,6 +276,54 @@ describe('processCliCommands processor', () => { ]); }); + it('should detect the CLI node if it is nested in another node (as long as there is a first child node with a `cli` url', + () => { + const command = { + docType: 'cli-command', + name: 'command1', + commandAliases: ['alias1', 'alias2'], + options: [], + path: 'cli/command1', + }; + const navigation = { + docType: 'navigation-json', + data: { + SideNav: [ + {url: 'some/page', title: 'Some Page'}, + { + title: 'CLI Commands Grandparent', + children: [ + {url: 'some/nested/page', title: 'Some Nested Page'}, + { + title: 'CLI Commands Parent', + children: [ + {url: 'some/more/nested/page', title: 'Some More Nested Page'}, + { + title: 'CLI Commands', + tooltip: 'Angular CLI command reference', + children: [{'title': 'Overview', 'url': 'cli'}], + }, + {url: 'other/more/nested/page', title: 'Other More Nested Page'}, + ], + }, + {url: 'other/nested/page', title: 'Other Nested Page'}, + ], + }, + {url: 'other/page', title: 'Other Page'}, + ], + }, + }; + + processor.$process([command, navigation]); + + const cliCommandsNode = navigation.data.SideNav[1].children[1].children[1]; + expect(cliCommandsNode.title).toEqual('CLI Commands'); + expect(cliCommandsNode.children).toEqual([ + {url: 'cli', title: 'Overview'}, + {url: 'cli/command1', title: 'ng command1'}, + ]); + }); + it('should complain if there is no child with `cli` url', () => { const command = { docType: 'cli-command',