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
219 lines (213 loc) • 11.7 kB
JavaScript
/* jscpd:ignore-start */
import { SfCommand, Flags, requiredOrgFlagWithDeprecations } 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 { createTempDir, execCommand, isCI, uxLog } from '../../../common/utils/index.js';
import { createBlankSfdxProject } from '../../../common/utils/projectUtils.js';
import { initPermissionSetAssignments, isProductionOrg } from '../../../common/utils/orgUtils.js';
import { CONSTANTS } from '../../../config/index.js';
import { generateMkDocsHTML, readMkDocsFile, writeMkDocsFile } from '../../../common/docBuilder/docUtils.js';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('sfdx-hardis', 'org');
export default class MkDocsToSalesforce extends SfCommand {
static title = 'MkDocs to Salesforce';
static description = `Generates MkDocs HTML pages and upload them to Salesforce as a static resource
This command performs the following operations:
- Generates MkDocs HTML pages (using locally installed mkdocs-material, or using mkdocs docker image)
- Creates a Static Resource, a VisualForce page and a Custom Tab metadata
- Upload the metadatas to the default org
- Opens the Custom Tab in the default browser (only if not in CI context)
Note: the documentation must have been previously generated using "sf hardis:doc:project2markdown --with-history"
You can:
- Specify the type of documentation to generate (CICD or Monitoring) using the --type flag. Default is CICD.
- Override default styles by customizing mkdocs.yml
More info on [Documentation section](${CONSTANTS.DOC_URL_ROOT}/salesforce-project-documentation/)
`;
static examples = [
'$ sf hardis:doc:mkdocs-to-salesforce',
];
static flags = {
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',
}),
'target-org': requiredOrgFlagWithDeprecations,
};
// Set this to true if your command requires a project workspace; 'requiresProject' is false by default
static requiresProject = true;
debugMode = false;
/* jscpd:ignore-end */
async run() {
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 mkdocsYmlFile = path.join(process.cwd(), "mkdocs.yml");
if (!fs.existsSync(mkdocsYmlFile)) {
throw new SfError('This command needs a mkdocs.yml config file. Generate one using "sf hardis:doc:project2markdown --with-history"');
}
// Update mkdocs.yml so it is compliant with being hosted in a static resource
const mkdocsYml = readMkDocsFile(mkdocsYmlFile);
const mkdocsYmlCopy = Object.assign({}, mkdocsYml);
mkdocsYmlCopy.use_directory_urls = false;
await writeMkDocsFile(mkdocsYmlFile, mkdocsYmlCopy);
try {
// Generate HTML pages
await generateMkDocsHTML();
// 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 = `SfdxHardis_MkDocsSite_${type}`;
const { mkDocsResourcePath, vfPageMetaFile, tabMetaFile, permissionSetFile } = await this.createDocMetadatas(resName, defaultProjectPath, type);
// Upload resource to remote org
const deployRes = await this.uploadDocMetadatas(resName, targetUsername, conn, tmpProjectPath, mkDocsResourcePath, vfPageMetaFile, tabMetaFile, permissionSetFile);
const success = deployRes.status === 0;
if (success) {
// Assign current user to newly created permission set
await initPermissionSetAssignments([resName], targetUsername);
}
if (success && !isCI) {
uxLog(this, c.cyan(`Opening the Custom Tab ${c.green(resName)} in your default browser...`));
uxLog(this, c.yellow(`If you have an access issue, make sure the tab ${resName} is not hidden on your Profile...`));
const sfPath = `lightning/n/${resName}`;
await execCommand(`sf org open --path ${sfPath} --target-org ${targetUsername}`, this, { fail: false, output: true, debug: this.debugMode });
}
// Restore previous mkdocs.yml version
await writeMkDocsFile(mkdocsYmlFile, mkdocsYml);
return { success: success };
}
catch (e) {
// Restore previous mkdocs.yml version
await writeMkDocsFile(mkdocsYmlFile, mkdocsYml);
throw e;
}
}
async createDocMetadatas(resName, defaultProjectPath, type) {
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");
await ensureDir(vfPagesPath);
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");
await ensureDir(tabsPath);
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);
// Create Permission Set metadata
const permissionSetsPath = path.join(defaultProjectPath, "permissionsets");
await ensureDir(permissionSetsPath);
const permissionSetFile = path.join(tabsPath, `${resName}.permissionset-meta.xml`);
const permissionSetMeta = `<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
<description>Permissions to Visualize Project Documentation, including Flow history, generated with sfdx-hardis from Git</description>
<hasActivationRequired>true</hasActivationRequired>
<label>${resName}</label>
<pageAccesses>
<apexPage>${resName}</apexPage>
<enabled>true</enabled>
</pageAccesses>
</PermissionSet>
`;
await fs.writeFile(permissionSetFile, permissionSetMeta);
return { mkDocsResourcePath, vfPageMetaFile, tabMetaFile, permissionSetFile };
}
async uploadDocMetadatas(resName, targetUsername, conn, tmpProjectPath, mkDocsResourcePath, vfPageMetaFile, tabMetaFile, permissionSetFile) {
uxLog(this, c.cyan(`Deploying Static Resource ${resName}, VisualForce page ${resName}, Custom Tab ${resName} and Permission Set ${resName} to org ${targetUsername}...`));
let deployCommand = `sf project deploy start -m StaticResource:${resName} -m ApexPage:${resName} -m CustomTab:${resName} -m PermissionSet:${resName} --ignore-conflicts --ignore-warnings --target-org ${targetUsername}`;
const isProdOrg = await isProductionOrg(targetUsername, { conn: conn });
if (isProdOrg) {
deployCommand += " --test-level RunLocalTests";
}
else {
deployCommand += " --test-level NoTestRun";
}
let deployRes = { status: 1, stdout: "", stderr: "" };
try {
deployRes = await execCommand(deployCommand, this, { cwd: tmpProjectPath, fail: false, output: true, debug: this.debugMode });
}
catch (e) {
deployRes.status = e.code;
deployRes.stderr = e.stderr;
deployRes.stdout = e.stdout;
}
if (deployRes.status !== 0) {
uxLog(this, c.red(`Deployment failed:\n${deployRes.stderr + "\n" + deployRes.stdout}`));
if ((deployRes.stderr + deployRes.stdout).includes("static resource cannot exceed")) {
uxLog(this, c.red(`Documentation is too big to be hosted in a Static Resource.`));
uxLog(this, c.yellow(`Cloudity can help you to host it somewhere else :)`));
uxLog(this, c.yellow(`If you are interested, contact us on ${c.green(c.bold(CONSTANTS.CONTACT_URL))}`));
}
else {
uxLog(this, c.yellow(`You can manually deploy the Static Resource ${resName},the VisualForce page ${resName} and the custom tab ${resName} to your org
- Static Resource: ${mkDocsResourcePath} (If you upload using UI, zip the folder and make sure to have index.html at the zip root)
- VisualForce page: ${vfPageMetaFile}
- Custom tab: ${tabMetaFile}
- Permission Set: ${permissionSetFile}
You can also run the documentation locally using "mkdocs serve -v || python -m mkdocs serve -v || py -m mkdocs serve -v"
`));
}
}
else {
uxLog(this, c.green(`SFDX Project documentation uploaded to salesforce and available in Custom Tab ${resName}`));
}
return deployRes;
}
}
//# sourceMappingURL=mkdocs-to-salesforce.js.map