sfdx-hardis
Version:
Swiss-army-knife Toolbox for Salesforce. Allows you to define a complete CD/CD Pipeline. Orchestrate base commands and assist users with interactive wizards
211 lines (207 loc) • 9.96 kB
JavaScript
/* jscpd:ignore-start */
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import c from 'chalk';
import fs from 'fs-extra';
import * as path from 'path';
import sortArray from 'sort-array';
import set from 'set-value';
import * as yaml from 'js-yaml';
import { uxLog } from '../../../../common/utils/index.js';
import { PACKAGE_ROOT_DIR } from '../../../../settings.js';
import { Config } from '@oclif/core';
import { readMkDocsFile, writeMkDocsFile } from '../../../../common/docBuilder/docUtils.js';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('sfdx-hardis', 'org');
export default class DocPluginGenerate extends SfCommand {
static title = 'Generate SF Cli Plugin Documentation';
static description = `Generate Markdown documentation ready for HTML conversion with mkdocs
After the first run, you need to update manually:
- mkdocs.yml
- .github/workflows/build-deploy-docs.yml
- docs/javascripts/gtag.js , if you want Google Analytics tracking
Then, activate Github pages, with "gh_pages" as target branch
At each merge into master/main branch, the GitHub Action build-deploy-docs will rebuild documentation and publish it in GitHub pages
`;
static examples = ['$ sf hardis:doc:plugin:generate'];
// public static args = [{name: 'file'}];
static flags = {
debug: Flags.boolean({
char: 'd',
default: false,
description: messages.getMessage('debugMode'),
}),
websocket: Flags.string({
description: messages.getMessage('websocket'),
}),
skipauth: Flags.boolean({
description: 'Skip authentication check when a default username is required',
}),
};
// Set this to true if your command requires a project workspace; 'requiresProject' is false by default
static requiresProject = false;
debugMode = false;
/* jscpd:ignore-end */
async run() {
const { flags } = await this.parse(DocPluginGenerate);
this.debugMode = flags.debug || false;
// Load plugin configuration
const cwd = process.cwd();
const config = await Config.load({ root: cwd, devPlugins: false, userPlugins: false });
// Generate commands markdowns
const commandsNav = { 'All commands': 'commands.md' };
const commandsLinks = {};
for (const command of config.commands) {
await this.generateCommandDoc(command);
const commandsSplit = command.id.split(':');
const commandName = commandsSplit.pop();
const commandMdPath = commandsSplit.join('/') + `/${commandName}.md`;
const navItem = {};
navItem[commandName || ''] = commandMdPath;
set(commandsNav, commandsSplit.join('.'), navItem, { preservePaths: true, merge: true });
commandsLinks[command.id] = commandMdPath;
}
uxLog(this, yaml.dump(commandsNav));
// Generate index.md
await this.generateIndexDoc(config, commandsLinks);
// Copy default files (mkdocs.yml and other files can be updated by the SF Cli plugin developer later)
const mkdocsYmlFile = path.join(process.cwd(), 'mkdocs.yml');
const mkdocsYmlFileExists = fs.existsSync(mkdocsYmlFile);
await fs.copy(path.join(PACKAGE_ROOT_DIR, 'defaults/mkdocs', '.'), process.cwd(), { overwrite: false });
if (!mkdocsYmlFileExists) {
uxLog(this, c.blue('Base mkdocs files copied in your SF Cli plugin folder'));
uxLog(this, c.yellow('You should probably manually update mkdocs.yml and build-deploy-docs.yml with your repo & plugin information'));
}
// Remove changelog if not existing
if (!fs.existsSync(path.join(process.cwd(), 'CHANGELOG.md')) &&
fs.existsSync(path.join(process.cwd(), 'docs', 'CHANGELOG.md'))) {
await fs.remove(path.join(process.cwd(), 'docs', 'CHANGELOG.md'));
}
// Remove license if not existing
if (!fs.existsSync(path.join(process.cwd(), 'LICENSE')) &&
fs.existsSync(path.join(process.cwd(), 'docs', 'license.md'))) {
await fs.remove(path.join(process.cwd(), 'docs', 'license.md'));
}
// Update mkdocs nav items
const mkdocsYml = readMkDocsFile(mkdocsYmlFile);
mkdocsYml.nav = mkdocsYml.nav.map((navItem) => {
if (navItem['Commands']) {
navItem['Commands'] = commandsNav;
}
return navItem;
});
await writeMkDocsFile(mkdocsYmlFile, mkdocsYml);
// Return an object to be displayed with --json
return { outputString: `Generated documentation` };
}
// Generate index file
async generateIndexDoc(config, commandsLinks) {
const lines = [
"<!-- This file has been generated with command 'sf hardis:doc:plugin:generate'. Please do not update it manually or it may be overwritten -->",
];
const readme = await fs.readFile(path.join(process.cwd(), 'README.md'), 'utf8');
let reusableReadmePartFound = false;
// Try to find README content until auto-generated commands
const limitStrings = ['## Commands', '## COMMANDS', '# Commands', '<!-- commands -->'];
for (const limitString of limitStrings) {
if (readme.indexOf(limitString) > 0) {
lines.push(...readme.substring(0, readme.indexOf(limitString)).split(/\r?\n/));
reusableReadmePartFound = true;
break;
}
}
// Default index.md
if (reusableReadmePartFound === false) {
lines.push(...[
'',
`# ${config.pjson.name}`,
'',
'## Description',
'',
config.pjson.description.split('\n').join('<br/>'),
'',
]);
}
// Build commands (for index.md and commands.md)
const cmdLines = [];
lines.push(...['', '## Commands']);
cmdLines.push('# Commands');
let currentSection = '';
for (const command of sortArray(config.commands, { by: ['id'], order: ['asc'] })) {
const section = command.id.split(':')[0] + ':' + command.id.split(':')[1];
if (section !== currentSection) {
lines.push(...['', `### ${section}`, '', '|Command|Title|', '|:------|:----------|']);
cmdLines.push(...['', `## ${section}`, '', '|Command|Title|', '|:------|:----------|']);
currentSection = section;
}
const commandInstance = command.load();
const title = commandInstance.title
? commandInstance.title
: commandInstance.description
? commandInstance.description.split('\n')[0]
: '';
lines.push(...[`|[**${command.id}**](${commandsLinks[command.id]})|${title}|`]);
cmdLines.push(...[`|[**${command.id}**](${commandsLinks[command.id]})|${title}|`]);
}
// Create docs dir if not existing yet
await fs.ensureDir(path.join(process.cwd(), 'docs'));
// write in index.md
const indexMdFile = path.join(process.cwd(), 'docs', 'index.md');
const indexMdString = lines.join('\n') + '\n';
await fs.writeFile(indexMdFile, indexMdString);
// write in commands.md
const commandsMdFile = path.join(process.cwd(), 'docs', 'commands.md');
const commandsMdString = cmdLines.join('\n') + '\n';
await fs.writeFile(commandsMdFile, commandsMdString);
}
// Generate markdown doc for a single command
async generateCommandDoc(command) {
const lines = [
"<!-- This file has been generated with command 'sf hardis:doc:plugin:generate'. Please do not update it manually or it may be overwritten -->",
];
// Title
const titleLines = [`# ${command.id}`, ''];
lines.push(...titleLines);
// Description
const descriptionLines = [`## Description`, '', ...(command.description || '').split('\n'), ''];
lines.push(...descriptionLines);
// Flags
const flagLines = [
`## Parameters`,
'',
'|Name|Type|Description|Default|Required|Options|',
'|:---|:--:|:----------|:-----:|:------:|:-----:|',
...Object.keys(command.flags || {})
.sort()
.map((flagKey) => {
const flag = command.flags[flagKey];
const optionsUnique = [];
for (const option of flag.options || []) {
if (optionsUnique.filter((o) => o.toLowerCase() === option.toLowerCase()).length === 0) {
optionsUnique.push(option);
}
}
return `|${flag.name + (flag.char ? `<br/>-${flag.char}` : '')}|${flag.type}|${flag.description}|${flag.default || ''}|${flag.required ? '' : ''}|${optionsUnique.join('<br/>')}|`;
}),
'',
];
lines.push(...flagLines);
// Examples
const exampleLines = [
`## Examples`,
'',
...(command.examples || [])
.map((example) => ['```shell', ...(example || '').split('\n'), '```', ''])
.flat(),
'',
];
lines.push(...exampleLines);
// Write to file
const mdFileName = path.join(process.cwd(), 'docs', path.sep, command.id.replace(/:/g, '/') + '.md');
await fs.ensureDir(path.dirname(mdFileName));
const yamlString = lines.join('\n') + '\n';
await fs.writeFile(mdFileName, yamlString);
uxLog(this, c.grey('Generated file ' + c.bold(mdFileName)));
}
}
//# sourceMappingURL=generate.js.map