Skip to content

Commit

Permalink
mkdocs-to-salesforce v0
Browse files Browse the repository at this point in the history
  • Loading branch information
nvuillam committed Jan 4, 2025
1 parent 80b8812 commit 3731fa0
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 4 deletions.
183 changes: 183 additions & 0 deletions src/commands/hardis/doc/mkdocs-to-salesforce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/* jscpd:ignore-start */
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import fs, { ensureDir } from 'fs-extra';
import c from "chalk";
import * as path from "path";
import { Messages, SfError } from '@salesforce/core';
import { AnyJson } from '@salesforce/ts-types';
import { createTempDir, execCommand, uxLog } from '../../../common/utils/index.js';
import { createBlankSfdxProject } from '../../../common/utils/projectUtils.js';
import { isProductionOrg } from '../../../common/utils/orgUtils.js';


Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('sfdx-hardis', 'org');

export default class MkDocsToSalesforce extends SfCommand<any> {
public static title = 'MkDocs to Salesforce';

public static description = `Generates MkDocs HTML pages and upload them to Salesforce as a static resource
`;

public static examples = [
'$ sf hardis:doc:mkdocs-to-salesforce',
];

public static flags: any = {
type: Flags.string({
char: 't',
options: ["CICD", "Monitoring"],
default: "CICD",
description: 'Type of the documentation to generate. Default is "all"',
}),
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
public static requiresProject = true;

protected debugMode = false;
/* jscpd:ignore-end */

public async run(): Promise<AnyJson> {
const { flags } = await this.parse(MkDocsToSalesforce);
const type = flags.type || "CICD";
const targetUsername = flags['target-org'].getUsername();
const conn = flags['target-org'].getConnection();
this.debugMode = flags.debug || false;

// Check if the project is a MkDocs project
const mkdocsYmlPath = path.join(process.cwd(), "mkdocs.yml");
if (!fs.existsSync(mkdocsYmlPath)) {
throw new SfError('This command needs a mkdocs.yml config file. Generate one using "sf hardis:doc:project2markdown --with-history"');
}

// Install Python dependencies
const mkdocsLocalOk = await this.installMkDocs();

await this.generateMkDocsHTML(mkdocsLocalOk);

// Create temp sfdx project
const tmpDirForSfdxProject = await createTempDir();
const tmpProjectPath = await createBlankSfdxProject(tmpDirForSfdxProject);
const defaultProjectPath = path.join(tmpProjectPath, "force-app", "main", "default");

// Create static resource folder
const resName = `SfdxHardisMkDocsSite-${type}`;
const { mkDocsResourcePath, vfPageMetaFile, tabMetaFile } = await this.createDocMetadatas(resName, defaultProjectPath, type);

// Upload resource to remote org
const deployRes = await this.uploadDocMetadatas(resName, targetUsername, conn, tmpProjectPath, mkDocsResourcePath, vfPageMetaFile, tabMetaFile);

return { success: deployRes.status === 0 };
}

private async installMkDocs() {
uxLog(this, c.cyan("Managing mkdocs-material local installation..."));
let mkdocsLocalOk = false;
const installMkDocsRes = await execCommand("pip install mkdocs-material mdx_truly_sane_lists", this, { fail: false, output: true, debug: this.debugMode });
if (installMkDocsRes.status !== 0) {
mkdocsLocalOk = true;
}
return mkdocsLocalOk;
}

private async generateMkDocsHTML(mkdocsLocalOk: boolean) {
if (mkdocsLocalOk) {
// Generate MkDocs HTML pages with local MkDocs
uxLog(this, c.cyan("Generating HTML pages with mkdocs..."));
const mkdocsBuildRes = await execCommand("mkdocs build", this, { fail: false, output: true, debug: this.debugMode });
if (mkdocsBuildRes.status !== 0) {
throw new SfError('MkDocs build failed:\n' + mkdocsBuildRes.stderr + "\n" + mkdocsBuildRes.stdout);
}
}
}

private async createDocMetadatas(resName: string, defaultProjectPath: string, type: any) {
uxLog(this, c.cyan(`Creating Static Resource ${resName} metadata...`));
const staticResourcePath = path.join(defaultProjectPath, "staticresources");
const mkDocsResourcePath = path.join(staticResourcePath, resName);
await ensureDir(mkDocsResourcePath);
await fs.move(path.join(process.cwd(), "site"), mkDocsResourcePath, { overwrite: true });

// Create Static resource metadata
uxLog(this, c.cyan(`Creating Static Resource ${resName} metadata...`));
const mkDocsResourceFileName = path.join(staticResourcePath, `${resName}.resource-meta.xml`);
const mkDocsResourceMeta = `<?xml version="1.0" encoding="UTF-8"?>
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
<cacheControl>Private</cacheControl>
<contentType>application/x-zip-compressed</contentType>
</StaticResource>
`;
await fs.writeFile(mkDocsResourceFileName, mkDocsResourceMeta);

// Create visual force page
uxLog(this, c.cyan(`Creating VisualForce page ${resName} metadata...`));
const vfPagesPath = path.join(defaultProjectPath, "pages");
const vfPageFileName = path.join(vfPagesPath, `${resName}.page`);
const vfPageCode = `<apex:page >
<iframe src="/resource/${resName}/index.html" width="100%" height="1000px" frameborder="0"></iframe>
</apex:page>
`;
await fs.writeFile(vfPageFileName, vfPageCode);

// Create visual force page metadata
const vfPageMetaFile = path.join(vfPagesPath, `${resName}.page-meta.xml`);
const vfPageMeta = `<?xml version="1.0" encoding="UTF-8"?>
<ApexPage xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<availableInTouch>false</availableInTouch>
<confirmationTokenRequired>false</confirmationTokenRequired>
<label>${resName}</label>
</ApexPage>
`;
await fs.writeFile(vfPageMetaFile, vfPageMeta);

// Create custom tab metadata
const tabsPath = path.join(defaultProjectPath, "tabs");
const tabMetaFile = path.join(tabsPath, `${resName}.tab-meta.xml`);
const tabMeta = `<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<label>${type === 'CICD' ? 'Sfdx-Hardis DOC (from CI/CD)' : 'Sfdx-Hardis DOC (from Monitoring)'}</label>
<motif>Custom46: Postage</motif>
<page>${resName}</page>
</CustomTab>
`;
await fs.writeFile(tabMetaFile, tabMeta);
return { mkDocsResourcePath, vfPageMetaFile, tabMetaFile };
}

private async uploadDocMetadatas(resName: string, targetUsername: any, conn: any, tmpProjectPath: string, mkDocsResourcePath: string, vfPageMetaFile: string, tabMetaFile: string) {
let deployCommand = `sf project deploy start -m StaticResource:${resName} -m ApexPage:${resName} -m CustomTab:${resName}`;
const isProdOrg = await isProductionOrg(targetUsername, { conn: conn });
if (isProdOrg) {
deployCommand += " --test-level RunLocalTests";
}
else {
deployCommand += " --test-level NoTestRun";
}
const deployRes = await execCommand(deployCommand, this, { cwd: tmpProjectPath, fail: false, output: true, debug: this.debugMode });

if (deployRes.status !== 0) {
uxLog(this, c.red(`Deployment failed:\n${deployRes.stderr + "\n" + deployRes.stdout}`));
uxLog(this, c.yellow(`You can manually deploy the Static Resource ${resName},the VisualForce page ${resName} and the custom tab ${resName} to your org`));
uxLog(this, c.yellow(`- Static Resource: ${mkDocsResourcePath} (If you upload using UI, zip the folder and make sure to have index.html at the zip root)`));
uxLog(this, c.yellow(`- VisualForce page: ${vfPageMetaFile}`));
uxLog(this, c.yellow(`- Custom tab: ${tabMetaFile}`));
}
else {
uxLog(this, c.green(`SFDX Project documentation uploaded to salesforce and available in Custom Tab ${resName}`));
}
return deployRes;
}
}
2 changes: 1 addition & 1 deletion src/common/utils/flowVisualiser/flowParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ function getTemplatesMd(textTemplates: any[]): string {
function getMermaidClasses(): string {
let classStr = "";
for (const property in NODE_CONFIG) {
classStr += "classDef " + property + " fill:" + (<any>NODE_CONFIG)[property].background + ",color:" + (<any>NODE_CONFIG)[property].color + ",max-height:100px\n";
classStr += "classDef " + property + " fill:" + (<any>NODE_CONFIG)[property].background + ",color:" + (<any>NODE_CONFIG)[property].color + ",text-decoration:none,max-height:100px\n";
}
return classStr;
}
6 changes: 3 additions & 3 deletions src/common/utils/mermaidUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ export async function generateMarkdownFileWithMermaidCli(outputFlowMdFileIn: str
}

export function getMermaidExtraClasses() {
const added = 'fill:green,color:white,stroke-width:4px,max-height:100px';
const removed = 'fill:red,color:white,stroke-width:4px,max-height:100px';
const changed = 'fill:orange,color:white,stroke-width:4px,max-height:100px';
const added = 'fill:green,color:white,stroke-width:4px,text-decoration:none,max-height:100px';
const removed = 'fill:red,color:white,stroke-width:4px,text-decoration:none,max-height:100px';
const changed = 'fill:orange,color:white,stroke-width:4px,text-decoration:none,max-height:100px';

const addedClasses = [
'actionCallsAdded',
Expand Down

0 comments on commit 3731fa0

Please sign in to comment.