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
127 lines (126 loc) • 6.5 kB
JavaScript
import c from 'chalk';
import { UtilsAi } from "../aiProvider/utils.js";
import { uxLog } from "../utils/index.js";
import { AiProvider } from '../aiProvider/index.js';
import { XMLParser } from 'fast-xml-parser';
import fs from 'fs-extra';
import path from 'path';
import { getMetaHideLines, includeFromFile } from './docUtils.js';
import { CONSTANTS } from '../../config/index.js';
export class DocBuilderRoot {
docType;
promptKey;
placeholder;
xmlRootKey;
docsSection;
metadataName;
metadataXml = "";
outputFile;
additionalVariables;
markdownDoc;
parsedXmlObject;
constructor(metadataName, metadataXml, outputFile, additionalVariables = {}) {
this.metadataName = metadataName;
this.metadataXml = metadataXml;
this.outputFile = outputFile;
this.additionalVariables = additionalVariables;
}
// This method must be overridden
async buildInitialMarkdownLines() {
return [];
}
async generateMarkdownFileFromXml() {
if (this.xmlRootKey === 'json') {
this.parsedXmlObject = this.metadataXml;
}
else if (this.xmlRootKey) {
this.parsedXmlObject = new XMLParser().parse(this.metadataXml)?.[this.xmlRootKey] || {};
}
const mdLines = [
'<!-- This file is auto-generated. if you do not want it to be overwritten, set TRUE in the line below -->',
'<!-- DO_NOT_OVERWRITE_DOC=FALSE -->',
''
];
// Main lines generated by overridden method
const initialMdLines = await this.buildInitialMarkdownLines();
mdLines.push(...initialMdLines);
// Footer
mdLines.push("");
mdLines.push(`_Documentation generated with [sfdx-hardis](${CONSTANTS.DOC_URL_ROOT}), by [Cloudity](https://www.cloudity.com/) & [friends](https://github.com/hardisgroupcom/sfdx-hardis/graphs/contributors)_`);
this.markdownDoc = mdLines.join("\n") + "\n";
this.markdownDoc = await this.completeDocWithAiDescription();
await fs.ensureDir(path.dirname(this.outputFile));
let overwriteDoc = true;
if (fs.existsSync(this.outputFile)) {
const fileContent = await fs.readFile(this.outputFile, "utf8");
if (fileContent.includes("DO_NOT_OVERWRITE_DOC=TRUE")) {
uxLog(this, c.yellow(`The file ${this.outputFile} is marked as DO_NOT_OVERWRITE_DOC=TRUE. Skipping generation.`));
overwriteDoc = false;
}
}
if (overwriteDoc) {
await fs.writeFile(this.outputFile, getMetaHideLines() + this.markdownDoc);
uxLog(this, c.green(`Successfully generated ${this.metadataName} documentation into ${this.outputFile}`));
}
const jsonTree = await this.generateJsonTree();
if (jsonTree) {
const jsonFile = `./docs/json/${this.docsSection}-${this.metadataName}.json`;
await fs.ensureDir(path.dirname(jsonFile));
await fs.writeFile(jsonFile, JSON.stringify(jsonTree, null, 2));
uxLog(this, c.green(`Successfully generated ${this.metadataName} JSON into ${jsonFile}`));
// Recovery to save git repos: Kill existing file if it has been created with forbidden characters
if (this.docsSection === "packages") {
const jsonFileBad = `./docs/json/${this.docsSection}-${this.metadataName}.json`;
if (jsonFileBad !== jsonFile && fs.existsSync(jsonFileBad)) {
await fs.remove(jsonFileBad);
}
}
}
return this.outputFile;
}
async completeDocWithAiDescription() {
const xmlStripped = await this.stripXmlForAi();
const aiCache = await UtilsAi.findAiCache(this.promptKey, [xmlStripped], this.metadataName);
if (aiCache.success === true) {
uxLog(this, c.grey(`Used AI cache for ${this.docType.toLowerCase()} description (set IGNORE_AI_CACHE=true to force call to AI)`));
const replaceText = `## AI-Generated Description\n\n${includeFromFile(aiCache.aiCacheDirFile, aiCache.cacheText || "")}`;
this.markdownDoc = this.markdownDoc.replace(this.placeholder, replaceText);
return this.markdownDoc;
}
if (AiProvider.isAiAvailable()) {
const defaultVariables = { [`${this.docType.toUpperCase()}_NAME`]: this.metadataName, [`${this.docType.toUpperCase()}_XML`]: xmlStripped };
const variables = Object.assign(defaultVariables, this.additionalVariables);
const prompt = AiProvider.buildPrompt(this.promptKey, variables);
/* jscpd:ignore-start */
const aiResponse = await AiProvider.promptAi(prompt, this.promptKey);
if (aiResponse?.success) {
let responseText = aiResponse.promptResponse || "No AI description available";
if (responseText.startsWith("##")) {
responseText = responseText.split("\n").slice(1).join("\n");
}
await UtilsAi.writeAiCache(this.promptKey, [xmlStripped], this.metadataName, responseText);
const replaceText = `## AI-Generated Description\n\n${includeFromFile(aiCache.aiCacheDirFile, responseText)}`;
this.markdownDoc = this.markdownDoc.replace(this.placeholder, replaceText);
return this.markdownDoc;
}
/* jscpd:ignore-end */
else if (aiResponse?.forcedTimeout) {
const forcedTimeoutText = `CI job reached maximum time allowed for allowed calls to AI. You can either:
- Run command locally then commit + push
- Increase using variable \`AI_MAX_TIMEOUT_MINUTES\` in your CI config (ex: AI_MAX_TIMEOUT_MINUTES=120) after making sure than your CI job timeout can handle it :)`;
const replaceText = `## AI-Generated Description\n\n${includeFromFile(aiCache.aiCacheDirFile, forcedTimeoutText)}`;
this.markdownDoc = this.markdownDoc.replace(this.placeholder, replaceText);
}
}
return this.markdownDoc;
}
// Override this method if you need to make a smaller XML to fit in the number of prompt tokens
async stripXmlForAi() {
return this.metadataXml;
}
// Override this method if you need to generate a JSON tree for the doc
async generateJsonTree() {
return null;
}
}
//# sourceMappingURL=docBuilderRoot.js.map