UNPKG

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.54 kB
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("warning", 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("success", 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("success", 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("log", 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