mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-24 07:46:39 +00:00
4760866c77
Change-type: patch
169 lines
5.3 KiB
TypeScript
169 lines
5.3 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2019 Balena Ltd.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import * as path from 'path';
|
|
import { MarkdownFileParser } from './utils';
|
|
import { GlobSync } from 'glob';
|
|
|
|
/**
|
|
* This is the skeleton of CLI documentation/reference web page at:
|
|
* https://www.balena.io/docs/reference/cli/
|
|
*
|
|
* The `getCapitanoDoc` function in this module parses README.md and adds
|
|
* some content to this object.
|
|
*
|
|
* IMPORTANT
|
|
*
|
|
* All commands need to be stored under a folder in src/commands to maintain uniformity
|
|
* Generating docs will error out if directive not followed
|
|
* To add a custom heading for command docs, add the heading next to the folder name
|
|
* in the `commandHeadings` dictionary.
|
|
*
|
|
* This dictionary is the source of truth that creates the docs config which is used
|
|
* to generate the CLI documentation. By default, the folder name will be used.
|
|
*
|
|
* Resources with plural names needs to have 2 sections if they have commands like:
|
|
* "fleet, fleets" or "device, devices" or "tag, tags"
|
|
*
|
|
*/
|
|
|
|
interface Category {
|
|
title: string;
|
|
files: string[];
|
|
}
|
|
|
|
interface Documentation {
|
|
title: string;
|
|
introduction: string;
|
|
categories: Category[];
|
|
}
|
|
|
|
// Mapping folders names to custom headings in the docs
|
|
const commandHeadings: { [key: string]: string } = {
|
|
'api-key': 'API Keys',
|
|
'api-keys': 'API Keys',
|
|
login: 'Authentication',
|
|
whoami: 'Authentication',
|
|
logout: 'Authentication',
|
|
env: 'Environment Variables',
|
|
envs: 'Environment Variables',
|
|
help: 'Help and Version',
|
|
key: 'SSH Keys',
|
|
keys: 'SSH Keys',
|
|
orgs: 'Organizations',
|
|
os: 'OS',
|
|
util: 'Utilities',
|
|
ssh: 'Network',
|
|
scan: 'Network',
|
|
tunnel: 'Network',
|
|
build: 'Deploy',
|
|
join: 'Platform',
|
|
leave: 'Platform',
|
|
app: 'Apps',
|
|
block: 'Blocks',
|
|
device: 'Devices',
|
|
fleet: 'Fleets',
|
|
release: 'Releases',
|
|
tag: 'Tags',
|
|
};
|
|
|
|
// Fetch all available commands
|
|
const allCommandsPaths = new GlobSync('build/commands/**/*.js', {
|
|
ignore: 'build/commands/internal/**',
|
|
}).found;
|
|
|
|
// Throw error if any commands found outside of command directories
|
|
const illegalCommandPaths = allCommandsPaths.filter((commandPath: string) =>
|
|
/^build\/commands\/[^/]+\.js$/.test(commandPath),
|
|
);
|
|
|
|
if (illegalCommandPaths.length !== 0) {
|
|
throw new Error(
|
|
`Found the following commands without a command directory: ${illegalCommandPaths}\n
|
|
To resolve this error, move the respective commands to their resource directories or create new ones.\n
|
|
Refer to the automation/capitanodoc/capitanodoc.ts file for more information.`,
|
|
);
|
|
}
|
|
|
|
// Docs config template
|
|
const capitanoDoc: Documentation = {
|
|
title: 'balena CLI Documentation',
|
|
introduction: '',
|
|
categories: [],
|
|
};
|
|
|
|
// Helper function to capitalize each word of directory name
|
|
function formatTitle(dir: string): string {
|
|
return dir.replace(/(^\w|\s\w)/g, (word) => word.toUpperCase());
|
|
}
|
|
|
|
// Create a map to track the categories for faster lookup
|
|
const categoriesMap: { [key: string]: Category } = {};
|
|
|
|
for (const commandPath of allCommandsPaths) {
|
|
const commandDir = path.basename(path.dirname(commandPath));
|
|
const heading = commandHeadings[commandDir] || formatTitle(commandDir);
|
|
|
|
if (!categoriesMap[heading]) {
|
|
categoriesMap[heading] = { title: heading, files: [] };
|
|
capitanoDoc.categories.push(categoriesMap[heading]);
|
|
}
|
|
|
|
categoriesMap[heading].files.push(commandPath);
|
|
}
|
|
|
|
// Sort Category titles alphabetically
|
|
capitanoDoc.categories = capitanoDoc.categories.sort((a, b) =>
|
|
a.title.localeCompare(b.title),
|
|
);
|
|
|
|
// Sort Category file paths alphabetically
|
|
capitanoDoc.categories.forEach((category) => {
|
|
category.files.sort((a, b) => a.localeCompare(b));
|
|
});
|
|
|
|
/**
|
|
* Modify and return the `capitanoDoc` object above in order to generate the
|
|
* CLI documentation at docs/balena-cli.md
|
|
*
|
|
* This function parses the README.md file to extract relevant sections
|
|
* for the documentation web page.
|
|
*/
|
|
export async function getCapitanoDoc(): Promise<typeof capitanoDoc> {
|
|
const readmePath = path.join(__dirname, '..', '..', 'README.md');
|
|
const mdParser = new MarkdownFileParser(readmePath);
|
|
const sections: string[] = await Promise.all([
|
|
mdParser.getSectionOfTitle('About').then((sectionLines: string) => {
|
|
// delete the title of the 'About' section for the web page
|
|
const match = /^(#+)\s+.+?\n\s*([^]*)/.exec(sectionLines);
|
|
if (!match || match.length < 3) {
|
|
throw new Error(`Error parsing section title`);
|
|
}
|
|
// match[1] has the title, match[2] has the rest
|
|
return match && match[2];
|
|
}),
|
|
mdParser.getSectionOfTitle('Installation'),
|
|
mdParser.getSectionOfTitle('Choosing a shell (command prompt/terminal)'),
|
|
mdParser.getSectionOfTitle('Logging in'),
|
|
mdParser.getSectionOfTitle('Proxy support'),
|
|
mdParser.getSectionOfTitle('Support, FAQ and troubleshooting'),
|
|
mdParser.getSectionOfTitle('Deprecation policy'),
|
|
]);
|
|
capitanoDoc.introduction = sections.join('\n');
|
|
return capitanoDoc;
|
|
}
|