diff --git a/src/commands/doc/generate/atlas.ts b/src/commands/doc/generate/atlas.ts index d52f105..24eb9b7 100644 --- a/src/commands/doc/generate/atlas.ts +++ b/src/commands/doc/generate/atlas.ts @@ -1,9 +1,11 @@ import { dirname } from 'node:path'; +import { readdir } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; -import { SfProject, Messages } from '@salesforce/core'; +import { SfProject, Messages, NamedPackageDir } from '@salesforce/core'; +import { ExcelWriter } from '../../../shared/xlsx/ExcelWriter.js'; -import { Atlas } from '../../../shared/Atlas.js'; +import { Atlas } from '../../../shared/metadata/Atlas.js'; Messages.importMessagesDirectory(dirname(fileURLToPath(import.meta.url))); const messages = Messages.loadMessages('plugin-documentation-atlas', 'doc.generate.atlas'); @@ -26,16 +28,28 @@ export default class DocGenerateAtlas extends SfCommand }), }; + // TODO - add flags and remove this exception + // eslint-disable-next-line class-methods-use-this public async run(): Promise { // const { flags } = await this.parse(DocGenerateAtlas); - this.spinner.start('Generating documentation atlas'); - const atlas = new Atlas(SfProject.getInstance().getPath()); - await atlas.initialize(this.spinner); - const xlsxFilename = await atlas.writeXlsx(); - this.spinner.stop('Written atlas file: ' + xlsxFilename); + const projectPath = SfProject.getInstance().getPath(); + const allProjectFiles = await getAllProjectFiles(projectPath); + const atlas = new Atlas(allProjectFiles); + const xlWriter = new ExcelWriter(atlas.album, projectPath); + const xlsxFilename = await xlWriter.writeXlsx(); return { path: xlsxFilename, }; } } + +async function getAllProjectFiles(projectPath: string): Promise { + const metadata: string[] = []; + const packageDirectories: NamedPackageDir[] = SfProject.getInstance(projectPath).getUniquePackageDirectories(); + for await (const thisPackageDirectory of packageDirectories) { + const items = await readdir(thisPackageDirectory.fullPath, { recursive: true }); + metadata.push(...items.map((item) => thisPackageDirectory.fullPath + item)); + } + return metadata; +} diff --git a/src/shared/ExcelWriter.ts b/src/shared/ExcelWriter.ts deleted file mode 100644 index e21742e..0000000 --- a/src/shared/ExcelWriter.ts +++ /dev/null @@ -1,44 +0,0 @@ -// import * as ExcelJS from 'exceljs'; - -// const NAME: ExcelJS.Column = { header: 'Name', key: 'name', width: 64 }; -// const API_VERSION: ExcelJS.Column = { header: 'API Version', key: 'apiVersion', width: 16 }; -// const LABEL: ExcelJS.Column = { header: 'Label', key: 'label', width: 64 }; -// const DESCRIPTION: ExcelJS.Column = { header: 'Description', key: 'description', width: 128 }; -// const ACTIVE: ExcelJS.Column = { header: 'Active', key: 'active', width: 64 }; -// const TRIGGER_TYPE: ExcelJS.Column = { header: 'Trigger Type', key: 'triggerType', width: 64 }; - -// type TabDefinition = { -// name: string; -// columns: Array; -// rows: Array; -// } - -// export class ExcelWriter { - -// private workbook: ExcelJS.Workbook; - -// public constructor() { -// this.workbook = new ExcelJS.Workbook(); -// } - -// public write(rows: Array): void { - -// } - -// /* -// if (this.apexClasses.length > 0) { -// const apexClassWorksheet = workbook.addWorksheet('Apex Classes'); -// apexClassWorksheet.columns = [ -// { header: 'Name', key: 'name', width: 64 }, -// { header: 'API Version', key: 'apiVersion', width: 16 }, -// ]; -// apexClassWorksheet.getRow(1).font = { bold: true }; -// for (const thisApexClass of this.apexClasses) { -// apexClassWorksheet.addRow({ -// name: thisApexClass.name, -// apiVersion: thisApexClass.apiVersion, -// }); -// } -// }/* - -// } diff --git a/src/shared/XmlParser.ts b/src/shared/XmlParser.ts deleted file mode 100644 index 07228b3..0000000 --- a/src/shared/XmlParser.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* eslint-disable @typescript-eslint/member-ordering */ -import * as xml2js from 'xml2js'; -import * as Metadata from './metadata/types/metadata.js'; -import { array } from './array.js'; -import { Extended, ExtendedMetadata, Album, Definition } from './metadata/file/index.js'; - -const nameRegEx = new RegExp('.+/([^.]*)'); -const parserOptions: xml2js.ParserOptions = { - explicitArray: false, - mergeAttrs: true, - valueProcessors: [ - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - xml2js.processors.parseNumbers, - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - xml2js.processors.parseBooleans, - ], -}; - -export class XmlParser { - public static getMetadata(xml: string, fileName: string, definition: Definition): Album { - const album: Album = {}; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - xml2js.parseString(xml, parserOptions, (err, result: Record) => { - const theRecord = result[definition.name]; - if (!definition.container) { - XmlParser.treatRecord(theRecord, definition, fileName); - album[definition.list] = [theRecord]; - } - if (definition.children) { - for (const listName of definition.children.keys()) { - const thisDefinition = definition.children.get(listName) as Definition; - album[thisDefinition.list] = []; - const records = array(theRecord[listName]) as Array>; - for (const thisRecord of records) { - XmlParser.treatRecord(thisRecord, thisDefinition, fileName); - album[thisDefinition.list].push(thisRecord as T); - } - } - } - }); - return album; - } - - private static treatRecord( - record: Extended, - metadata: Definition, - fileName: string - ): void { - record.fileName = fileName; - if (metadata.setName) { - record.name = metadata.setName(fileName); - } - if (metadata.setObjectname) { - record.objectName = metadata.setObjectname(fileName); - } - if (metadata.setFullName) { - record.fullName = metadata.setFullName(fileName); - } - if (metadata.transform) { - metadata.transform(record); - } - } - - public static getWorkflowRules(xml: string, fileName: string): Array> { - const workflowRules: Array> = []; - const objectName = nameRegEx.exec(fileName)?.[1] as string; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - xml2js.parseString(xml, parserOptions, (err, result: { Workflow: Metadata.Workflow }) => { - for (const thisRule of array(result.Workflow.rules) as Array>) { - thisRule.objectName = objectName; - workflowRules.push(thisRule); - } - }); - return workflowRules; - } -} diff --git a/src/shared/metadata/Atlas.ts b/src/shared/metadata/Atlas.ts new file mode 100644 index 0000000..9869b7e --- /dev/null +++ b/src/shared/metadata/Atlas.ts @@ -0,0 +1,96 @@ +import * as fs from 'node:fs'; +import * as xml2js from 'xml2js'; +import { array } from '../array.js'; +import { Extended, ExtendedMetadata, Album, Definition, ALL_DEFINITIONS, getExtension } from './file/index.js'; + +export class Atlas { + public album: Album = {}; + private projectFilenames: string[]; + private metadataFilenames: string[]; + private fileDefinitionsByExtension: Map; + private metadataExtensions: Set; + + public constructor(projectFilenames: string[]) { + this.projectFilenames = projectFilenames; + this.fileDefinitionsByExtension = new Map(); + + for (const thisFileDefinition of ALL_DEFINITIONS) { + if (thisFileDefinition.extension) { + this.fileDefinitionsByExtension.set(thisFileDefinition.extension, thisFileDefinition); + } + } + this.metadataExtensions = new Set(this.fileDefinitionsByExtension.keys()); + + this.metadataFilenames = this.projectFilenames.filter((theFile) => this.isMetadataFile(theFile)); + + for (const thisFile of this.metadataFilenames) { + const thisDefinition: Definition = this.fileDefinitionsByExtension.get(getExtension(thisFile))!; + const xml = fs.readFileSync(thisFile, 'utf-8'); + this.absorb(getMetadata>(xml, thisFile, thisDefinition)); + } + } + + private absorb(album: Album): void { + for (const thisList of Object.keys(album)) { + if (!this.album[thisList]) { + this.album[thisList] = []; + } + this.album[thisList].push(...album[thisList]); + } + } + + private isMetadataFile(thisFile: string): boolean { + return this.metadataExtensions.has(getExtension(thisFile)); + } +} + +const parserOptions: xml2js.ParserOptions = { + explicitArray: false, + mergeAttrs: true, + valueProcessors: [ + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + xml2js.processors.parseNumbers, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + xml2js.processors.parseBooleans, + ], +}; + +function getMetadata(xml: string, fileName: string, definition: Definition): Album { + const album: Album = {}; + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + xml2js.parseString(xml, parserOptions, (err, result: Record) => { + const theRecord = result[definition.name]; + if (!definition.container) { + treatRecord(theRecord, definition, fileName); + album[definition.list] = [theRecord]; + } + if (definition.children) { + for (const listName of Object.keys(definition.children)) { + const thisDefinition = definition.children[listName]; + album[thisDefinition.list] = []; + const records = array(theRecord[listName]) as Array>; + for (const thisRecord of records) { + treatRecord(thisRecord, thisDefinition, fileName); + album[thisDefinition.list].push(thisRecord as T); + } + } + } + }); + return album; +} + +function treatRecord(record: Extended, metadata: Definition, fileName: string): void { + record.fileName = fileName; + if (metadata.setName) { + record.name = metadata.setName(record); + } + if (metadata.setObjectname) { + record.objectName = metadata.setObjectname(record); + } + if (metadata.setFullName) { + record.fullName = metadata.setFullName(record); + } + if (metadata.transform) { + metadata.transform(record); + } +} diff --git a/src/shared/metadata/file/classes/Definition.ts b/src/shared/metadata/file/classes/Definition.ts index baf845a..30eddbc 100644 --- a/src/shared/metadata/file/classes/Definition.ts +++ b/src/shared/metadata/file/classes/Definition.ts @@ -7,10 +7,10 @@ export interface Definition { readonly extension?: string; readonly metadataType: Metadata; readonly container?: boolean; - readonly setName?: (parameter: string) => string; - readonly setObjectname?: (parameter: string) => string; - readonly setFullName?: (parameter: string) => string; + readonly setName?: (record: Extended) => string; + readonly setObjectname?: (record: Extended) => string; + readonly setFullName?: (record: Extended) => string; readonly transform?: (record: Extended) => void; readonly process?: Array<(record: Extended) => Array>>; - readonly children?: Map; + readonly children?: Record; } diff --git a/src/shared/metadata/file/definitions/CompactLayout.ts b/src/shared/metadata/file/definitions/CompactLayout.ts index 70ae7fb..327b14a 100644 --- a/src/shared/metadata/file/definitions/CompactLayout.ts +++ b/src/shared/metadata/file/definitions/CompactLayout.ts @@ -3,7 +3,7 @@ import { Definition, getBasenameWithoutExtension, getNameOfSecondToLastFolderLevel, - getFullNameForObjectComponent, + concatenateObjectNameAndName, } from '../index.js'; export const COMPACT_LAYOUT: Definition = { @@ -13,5 +13,5 @@ export const COMPACT_LAYOUT: Definition = { metadataType: {} as CompactLayout, setName: getBasenameWithoutExtension, setObjectname: getNameOfSecondToLastFolderLevel, - setFullName: getFullNameForObjectComponent, + setFullName: concatenateObjectNameAndName, }; diff --git a/src/shared/metadata/file/definitions/CustomField.ts b/src/shared/metadata/file/definitions/CustomField.ts index fe5653f..9fbd9ec 100644 --- a/src/shared/metadata/file/definitions/CustomField.ts +++ b/src/shared/metadata/file/definitions/CustomField.ts @@ -3,7 +3,7 @@ import { Definition, getBasenameWithoutExtension, getNameOfSecondToLastFolderLevel, - getFullNameForObjectComponent, + concatenateObjectNameAndName, } from '../index.js'; export const CUSTOM_FIELD: Definition = { @@ -13,5 +13,5 @@ export const CUSTOM_FIELD: Definition = { metadataType: {} as CustomField, setName: getBasenameWithoutExtension, setObjectname: getNameOfSecondToLastFolderLevel, - setFullName: getFullNameForObjectComponent, + setFullName: concatenateObjectNameAndName, }; diff --git a/src/shared/metadata/file/definitions/FieldSet.ts b/src/shared/metadata/file/definitions/FieldSet.ts index 0e5a0b9..8b3de8f 100644 --- a/src/shared/metadata/file/definitions/FieldSet.ts +++ b/src/shared/metadata/file/definitions/FieldSet.ts @@ -3,7 +3,7 @@ import { Definition, getBasenameWithoutExtension, getNameOfSecondToLastFolderLevel, - getFullNameForObjectComponent, + concatenateObjectNameAndName, } from '../index.js'; export const FIELD_SET: Definition = { @@ -13,5 +13,5 @@ export const FIELD_SET: Definition = { metadataType: {} as FieldSet, setName: getBasenameWithoutExtension, setObjectname: getNameOfSecondToLastFolderLevel, - setFullName: getFullNameForObjectComponent, + setFullName: concatenateObjectNameAndName, }; diff --git a/src/shared/metadata/file/definitions/ListView.ts b/src/shared/metadata/file/definitions/ListView.ts index 284f84d..6a62e82 100644 --- a/src/shared/metadata/file/definitions/ListView.ts +++ b/src/shared/metadata/file/definitions/ListView.ts @@ -3,7 +3,7 @@ import { Definition, getBasenameWithoutExtension, getNameOfSecondToLastFolderLevel, - getFullNameForObjectComponent, + concatenateObjectNameAndName, } from '../index.js'; export const LIST_VIEW: Definition = { @@ -13,5 +13,5 @@ export const LIST_VIEW: Definition = { metadataType: {} as ListView, setName: getBasenameWithoutExtension, setObjectname: getNameOfSecondToLastFolderLevel, - setFullName: getFullNameForObjectComponent, + setFullName: concatenateObjectNameAndName, }; diff --git a/src/shared/metadata/file/definitions/RecordType.ts b/src/shared/metadata/file/definitions/RecordType.ts index c1df1f2..45a8ab1 100644 --- a/src/shared/metadata/file/definitions/RecordType.ts +++ b/src/shared/metadata/file/definitions/RecordType.ts @@ -3,7 +3,7 @@ import { Definition, getBasenameWithoutExtension, getNameOfSecondToLastFolderLevel, - getFullNameForObjectComponent, + concatenateObjectNameAndName, } from '../index.js'; export const RECORD_TYPE: Definition = { @@ -13,5 +13,5 @@ export const RECORD_TYPE: Definition = { metadataType: {} as RecordType, setName: getBasenameWithoutExtension, setObjectname: getNameOfSecondToLastFolderLevel, - setFullName: getFullNameForObjectComponent, + setFullName: concatenateObjectNameAndName, }; diff --git a/src/shared/metadata/file/definitions/ValidationRule.ts b/src/shared/metadata/file/definitions/ValidationRule.ts index 22654ee..4ee8f85 100644 --- a/src/shared/metadata/file/definitions/ValidationRule.ts +++ b/src/shared/metadata/file/definitions/ValidationRule.ts @@ -3,7 +3,7 @@ import { Definition, getBasenameWithoutExtension, getNameOfSecondToLastFolderLevel, - getFullNameForObjectComponent, + concatenateObjectNameAndName, } from '../index.js'; export const VALIDATION_RULE: Definition = { @@ -13,5 +13,5 @@ export const VALIDATION_RULE: Definition = { metadataType: {} as ValidationRule, setName: getBasenameWithoutExtension, setObjectname: getNameOfSecondToLastFolderLevel, - setFullName: getFullNameForObjectComponent, + setFullName: concatenateObjectNameAndName, }; diff --git a/src/shared/metadata/file/definitions/WebLink.ts b/src/shared/metadata/file/definitions/WebLink.ts index 1884013..ecc164c 100644 --- a/src/shared/metadata/file/definitions/WebLink.ts +++ b/src/shared/metadata/file/definitions/WebLink.ts @@ -3,7 +3,7 @@ import { Definition, getBasenameWithoutExtension, getNameOfSecondToLastFolderLevel, - getFullNameForObjectComponent, + concatenateObjectNameAndName, } from '../index.js'; export const WEBLINK: Definition = { @@ -13,5 +13,5 @@ export const WEBLINK: Definition = { metadataType: {} as WebLink, setName: getBasenameWithoutExtension, setObjectname: getNameOfSecondToLastFolderLevel, - setFullName: getFullNameForObjectComponent, + setFullName: concatenateObjectNameAndName, }; diff --git a/src/shared/metadata/file/definitions/Workflow.ts b/src/shared/metadata/file/definitions/Workflow.ts index 55b6ec7..997b68a 100644 --- a/src/shared/metadata/file/definitions/Workflow.ts +++ b/src/shared/metadata/file/definitions/Workflow.ts @@ -1,13 +1,20 @@ -// import * as Metadata from '../../types/metadata.js'; -// import * as MetadataFile from '../index.js'; +import { Workflow, WorkflowRule } from '../../types/metadata.js'; +import { Definition, getFullNameValue, getBasenameWithoutExtension, concatenateObjectNameAndName } from '../index.js'; -// export const WORKFLOW: MetadataFile.Definition = { -// name: 'Workflow', -// list: 'workflows', -// extension: '.workflow-meta.xml', -// container: true, -// metadataType: {} as Metadata.Workflow, -// children: new Map([ -// ['rules', MetadataFile.WORKFLOW_RULE] -// ]), -// } +export const WORKFLOW: Definition = { + name: 'Workflow', + list: 'workflows', + extension: '.workflow-meta.xml', + container: true, + metadataType: {} as Workflow, + children: { + rules: { + name: 'WorkflowRule', + list: 'workflowRules', + metadataType: {} as WorkflowRule, + setName: getFullNameValue, + setObjectname: getBasenameWithoutExtension, + setFullName: concatenateObjectNameAndName, + }, + }, +}; diff --git a/src/shared/metadata/file/definitions/WorkflowRule.ts b/src/shared/metadata/file/definitions/WorkflowRule.ts deleted file mode 100644 index c1234bc..0000000 --- a/src/shared/metadata/file/definitions/WorkflowRule.ts +++ /dev/null @@ -1,11 +0,0 @@ -// import * as Metadata from '../../types/metadata.js'; -// import * as MetadataFile from '../index.js'; - -// export const WORKFLOW_RULE: MetadataFile.Definition = { -// name: 'WorkflowRule', -// list: 'workflowRules', -// metadataType: {} as Metadata.WorkflowRule, -// setName: MetadataFile.getBasenameWithoutExtension, -// setObjectname: MetadataFile.getNameOfSecondToLastFolderLevel, -// setFullName: MetadataFile.getFullNameForObjectComponent, -// } diff --git a/src/shared/metadata/file/definitions/index.ts b/src/shared/metadata/file/definitions/index.ts index aa83156..e001181 100644 --- a/src/shared/metadata/file/definitions/index.ts +++ b/src/shared/metadata/file/definitions/index.ts @@ -19,5 +19,4 @@ export * from './QuickAction.js'; export * from './RecordType.js'; export * from './ValidationRule.js'; export * from './WebLink.js'; -// export * from './Workflow.js' -// export * from './WorkflowRule.js'; +export * from './Workflow.js'; diff --git a/src/shared/metadata/file/functions/concatenateObjectNameAndName.ts b/src/shared/metadata/file/functions/concatenateObjectNameAndName.ts new file mode 100644 index 0000000..102ab29 --- /dev/null +++ b/src/shared/metadata/file/functions/concatenateObjectNameAndName.ts @@ -0,0 +1,6 @@ +import { Metadata } from '../../types/metadata.js'; +import { Extended } from '../index.js'; + +export function concatenateObjectNameAndName(record: Extended): string { + return record.objectName + '.' + record.name; +} diff --git a/src/shared/metadata/file/functions/getBasenameWithoutExtension.ts b/src/shared/metadata/file/functions/getBasenameWithoutExtension.ts index d9bef60..fccd285 100644 --- a/src/shared/metadata/file/functions/getBasenameWithoutExtension.ts +++ b/src/shared/metadata/file/functions/getBasenameWithoutExtension.ts @@ -1,7 +1,8 @@ import * as path from 'node:path'; -import { getExtension } from '../index.js'; +import { Extended, getExtension } from '../index.js'; +import { Metadata } from '../../types/metadata.js'; -export function getBasenameWithoutExtension(fullPath: string): string { - const extension = getExtension(fullPath); - return path.basename(fullPath).replace(extension, ''); +export function getBasenameWithoutExtension(record: Extended): string { + const extension = getExtension(record.fileName); + return path.basename(record.fileName).replace(extension, ''); } diff --git a/src/shared/metadata/file/functions/getFullNameForObjectComponent.ts b/src/shared/metadata/file/functions/getFullNameForObjectComponent.ts deleted file mode 100644 index 246b7b4..0000000 --- a/src/shared/metadata/file/functions/getFullNameForObjectComponent.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { getBasenameWithoutExtension, getNameOfSecondToLastFolderLevel } from '../index.js'; - -export function getFullNameForObjectComponent(fullPath: string): string { - const objectName = getNameOfSecondToLastFolderLevel(fullPath); - const componentName = getBasenameWithoutExtension(fullPath); - return objectName + '.' + componentName; -} diff --git a/src/shared/metadata/file/functions/getFullNameValue.ts b/src/shared/metadata/file/functions/getFullNameValue.ts new file mode 100644 index 0000000..c02bed4 --- /dev/null +++ b/src/shared/metadata/file/functions/getFullNameValue.ts @@ -0,0 +1,6 @@ +import { Extended } from '../index.js'; +import { Metadata } from '../../types/metadata.js'; + +export function getFullNameValue(record: Extended): string { + return record.fullName || ''; +} diff --git a/src/shared/metadata/file/functions/getNameOfSecondToLastFolderLevel.ts b/src/shared/metadata/file/functions/getNameOfSecondToLastFolderLevel.ts index dc577a0..17a712f 100644 --- a/src/shared/metadata/file/functions/getNameOfSecondToLastFolderLevel.ts +++ b/src/shared/metadata/file/functions/getNameOfSecondToLastFolderLevel.ts @@ -1,5 +1,7 @@ import * as path from 'node:path'; +import { Extended } from '../index.js'; +import { Metadata } from '../../types/metadata.js'; -export function getNameOfSecondToLastFolderLevel(fullPath: string): string { - return fullPath.split(path.sep).reverse()[2]; +export function getNameOfSecondToLastFolderLevel(record: Extended): string { + return record.fileName.split(path.sep).reverse()[2]; } diff --git a/src/shared/metadata/file/functions/index.ts b/src/shared/metadata/file/functions/index.ts index 477faa4..36bc5a3 100644 --- a/src/shared/metadata/file/functions/index.ts +++ b/src/shared/metadata/file/functions/index.ts @@ -1,6 +1,7 @@ export * from './getBasenameWithoutExtension.js'; export * from './getExtension.js'; -export * from './getFullNameForObjectComponent.js'; +export * from './concatenateObjectNameAndName.js'; +export * from './getFullNameValue.js'; export * from './getNameOfSecondToLastFolderLevel.js'; export * from './splitBasename.js'; export * from './transformCustomTab.js'; diff --git a/src/shared/metadata/file/functions/processWorkflowRules.ts b/src/shared/metadata/file/functions/processWorkflowRules.ts index 3bdf529..946b5ee 100644 --- a/src/shared/metadata/file/functions/processWorkflowRules.ts +++ b/src/shared/metadata/file/functions/processWorkflowRules.ts @@ -5,7 +5,7 @@ import { array } from '../../../array.js'; export function processWorkflowRules(record: Metadata.Workflow): Array> { const workflowRules: Array> = []; for (const thisRule of array(record.rules) as Array>) { - thisRule.objectName = getNameOfSecondToLastFolderLevel(thisRule.fileName); + thisRule.objectName = getNameOfSecondToLastFolderLevel(thisRule); workflowRules.push(thisRule); } return workflowRules; diff --git a/src/shared/metadata/file/functions/splitBasename.ts b/src/shared/metadata/file/functions/splitBasename.ts index 430f5ea..27e8abf 100644 --- a/src/shared/metadata/file/functions/splitBasename.ts +++ b/src/shared/metadata/file/functions/splitBasename.ts @@ -1,21 +1,22 @@ -import { getBasenameWithoutExtension } from '../index.js'; +import { Metadata } from '../../types/metadata.js'; +import { Extended, getBasenameWithoutExtension } from '../index.js'; -export function getFirstHalfOfBasenameSplitByPeriod(fullPath: string): string { - const basename = getBasenameWithoutExtension(fullPath); +export function getFirstHalfOfBasenameSplitByPeriod(record: Extended): string { + const basename = getBasenameWithoutExtension(record); return basename.split('.')[0]; } -export function getSecondHalfOfBasenameSplitByPeriod(fullPath: string): string { - const basename = getBasenameWithoutExtension(fullPath); +export function getSecondHalfOfBasenameSplitByPeriod(record: Extended): string { + const basename = getBasenameWithoutExtension(record); return basename.split('.')[1]; } -export function getFirstHalfOfBasenameSplitByDash(fullPath: string): string { - const basename = getBasenameWithoutExtension(fullPath); +export function getFirstHalfOfBasenameSplitByDash(record: Extended): string { + const basename = getBasenameWithoutExtension(record); return basename.split('-')[0]; } -export function getSecondHalfOfBasenameSplitByDash(fullPath: string): string { - const basename = getBasenameWithoutExtension(fullPath); +export function getSecondHalfOfBasenameSplitByDash(record: Extended): string { + const basename = getBasenameWithoutExtension(record); return basename.split('-')[1]; } diff --git a/src/shared/metadata/file/index.ts b/src/shared/metadata/file/index.ts index 22df8aa..5d6af80 100644 --- a/src/shared/metadata/file/index.ts +++ b/src/shared/metadata/file/index.ts @@ -26,6 +26,5 @@ export const ALL_DEFINITIONS = [ Definition.RECORD_TYPE, Definition.VALIDATION_RULE, Definition.WEBLINK, - // Definition.WORKFLOW, - // Definition.WORKFLOW_RULE, + Definition.WORKFLOW, ]; diff --git a/src/shared/Atlas.ts b/src/shared/xlsx/ExcelWriter.ts similarity index 88% rename from src/shared/Atlas.ts rename to src/shared/xlsx/ExcelWriter.ts index 2554da1..54be038 100644 --- a/src/shared/Atlas.ts +++ b/src/shared/xlsx/ExcelWriter.ts @@ -1,73 +1,24 @@ /* eslint-disable complexity */ -import * as fs from 'node:fs'; -import { readdir, mkdir } from 'node:fs/promises'; -import { SfProject, NamedPackageDir } from '@salesforce/core'; -import * as ExcelJS from 'exceljs'; -import { Spinner } from '@salesforce/sf-plugins-core'; -import * as Metadata from './metadata/types/metadata.js'; -import { XmlParser } from './XmlParser.js'; -import { Extended, Album, Definition, ALL_DEFINITIONS, getExtension } from './metadata/file/index.js'; -// TO DO -// - flexipages / lightning pages -// - flows -// - global value sets -// - lightning components - -export class Atlas { - public workflowRules: Array> = []; - - public projectPath: string; - public album: Album = {}; - private fileDefinitionsByExtension: Map; - private metadataExtensions: Set; - - public constructor(projectPath: string) { - this.projectPath = projectPath; - this.fileDefinitionsByExtension = new Map(); - for (const thisFileDefinition of ALL_DEFINITIONS) { - this.album[thisFileDefinition.list] = []; - if (thisFileDefinition.extension) { - this.fileDefinitionsByExtension.set(thisFileDefinition.extension, thisFileDefinition); - } - } - this.metadataExtensions = new Set(this.fileDefinitionsByExtension.keys()); - } - public async initialize(spinner: Spinner): Promise { - const allFiles = await getAllProjectFiles(this.projectPath); - const allMetadataFiles = allFiles.filter((theFile) => this.isMetadataFile(theFile)); - - for (const thisFile of allMetadataFiles) { - spinner.status = thisFile; - const thisDefinition: Definition = this.fileDefinitionsByExtension.get(getExtension(thisFile))!; - const xml = fs.readFileSync(thisFile, 'utf-8'); - this.absorb(XmlParser.getMetadata>(xml, thisFile, thisDefinition)); - } - - for (const thisFile of allFiles) { - spinner.status = thisFile; +import { mkdir } from 'node:fs/promises'; +import * as ExcelJS from 'exceljs'; +import { Extended, Album } from '../metadata/file/index.js'; +import * as Metadata from '../metadata/types/metadata.js'; - if (thisFile.endsWith('.workflow-meta.xml')) { - const xml = fs.readFileSync(thisFile, 'utf-8'); - this.workflowRules.push(...XmlParser.getWorkflowRules(xml, thisFile)); - } - } - } +export class ExcelWriter { + private album: Album; + private projectPath: string; - private absorb(album: Album): void { - for (const thisList of Object.keys(album)) { - if (!this.album[thisList]) { - this.album[thisList] = []; - } - this.album[thisList].push(...album[thisList]); - } + public constructor(album: Album, projectPath: string) { + this.album = album; + this.projectPath = projectPath; } // eslint-disable-next-line @typescript-eslint/member-ordering public async writeXlsx(): Promise { const workbook = new ExcelJS.default.Workbook(); - if (this.album.objects.length > 0) { + if (this.album.objects?.length > 0) { const objectWorksheet = workbook.addWorksheet('Objects'); objectWorksheet.columns = [ { header: 'Name', key: 'name', width: 64 }, @@ -114,7 +65,7 @@ export class Atlas { } } - if (this.album.fields.length > 0) { + if (this.album.fields?.length > 0) { const fieldWorksheet = workbook.addWorksheet('Fields'); fieldWorksheet.columns = [ { header: 'Object', key: 'objectName', width: 64 }, @@ -223,7 +174,7 @@ export class Atlas { } } - if (this.album.fieldSets.length > 0) { + if (this.album.fieldSets?.length > 0) { const fieldSetWorksheet = workbook.addWorksheet('Field Sets'); fieldSetWorksheet.columns = [ { header: 'Object', key: 'objectName', width: 64 }, @@ -246,7 +197,7 @@ export class Atlas { } } - if (this.album.listViews.length > 0) { + if (this.album.listViews?.length > 0) { const listViewWorksheet = workbook.addWorksheet('List Views'); listViewWorksheet.columns = [ { header: 'Object', key: 'objectName', width: 64 }, @@ -281,7 +232,7 @@ export class Atlas { } } - if (this.album.recordTypes.length > 0) { + if (this.album.recordTypes?.length > 0) { const recordTypeWorksheet = workbook.addWorksheet('Record Types'); recordTypeWorksheet.columns = [ { header: 'Object', key: 'objectName', width: 64 }, @@ -308,7 +259,7 @@ export class Atlas { } } - if (this.album.validationRules.length > 0) { + if (this.album.validationRules?.length > 0) { const validationRuleWorksheet = workbook.addWorksheet('Validation Rules'); validationRuleWorksheet.columns = [ { header: 'Object', key: 'objectName', width: 64 }, @@ -335,7 +286,7 @@ export class Atlas { } } - if (this.album.compactLayouts.length > 0) { + if (this.album.compactLayouts?.length > 0) { const compactLayoutWorksheet = workbook.addWorksheet('Compact Layouts'); compactLayoutWorksheet.columns = [ { header: 'Object', key: 'objectName', width: 64 }, @@ -356,7 +307,7 @@ export class Atlas { } } - if (this.album.webLinks.length > 0) { + if (this.album.webLinks?.length > 0) { const webLinkWorksheet = workbook.addWorksheet('Web Links'); webLinkWorksheet.columns = [ { header: 'Object', key: 'objectName', width: 64 }, @@ -415,7 +366,7 @@ export class Atlas { } } - if (this.album.apexClasses.length > 0) { + if (this.album.apexClasses?.length > 0) { const apexClassWorksheet = workbook.addWorksheet('Apex Classes'); apexClassWorksheet.columns = [ { header: 'Name', key: 'name', width: 64 }, @@ -430,7 +381,7 @@ export class Atlas { } } - if (this.album.apexTriggers.length > 0) { + if (this.album.apexTriggers?.length > 0) { const apexTriggerWorksheet = workbook.addWorksheet('Apex Triggers'); apexTriggerWorksheet.columns = [ { header: 'Name', key: 'name', width: 64 }, @@ -445,7 +396,7 @@ export class Atlas { } } - if (this.album.apexPages.length > 0) { + if (this.album.apexPages?.length > 0) { const visualforcePageWorksheet = workbook.addWorksheet('Visualforce Pages'); visualforcePageWorksheet.columns = [ { header: 'Name', key: 'name', width: 64 }, @@ -464,7 +415,7 @@ export class Atlas { } } - if (this.album.apexComponents.length > 0) { + if (this.album.apexComponents?.length > 0) { const visualforceComponentWorksheet = workbook.addWorksheet('Visualforce Components'); visualforceComponentWorksheet.columns = [ { header: 'Name', key: 'name', width: 64 }, @@ -483,7 +434,7 @@ export class Atlas { } } - if (this.album.auraComponents.length > 0) { + if (this.album.auraComponents?.length > 0) { const auraComponentWorksheet = workbook.addWorksheet('Aura Components'); auraComponentWorksheet.columns = [ { header: 'Name', key: 'name', width: 64 }, @@ -500,7 +451,7 @@ export class Atlas { } } - if (this.album.lightningWebComponents.length > 0) { + if (this.album.lightningWebComponents?.length > 0) { const lightningWebComponentWorksheet = workbook.addWorksheet('Lightning Web Components'); lightningWebComponentWorksheet.columns = [ { header: 'Name', key: 'name', width: 64 }, @@ -519,7 +470,7 @@ export class Atlas { } } - if (this.album.permissionSets.length > 0) { + if (this.album.permissionSets?.length > 0) { const permissionSetWorksheet = workbook.addWorksheet('Permission Sets'); permissionSetWorksheet.columns = [ { header: 'Name', key: 'name', width: 64 }, @@ -536,7 +487,7 @@ export class Atlas { } } - if (this.album.permissionSetGroups.length > 0) { + if (this.album.permissionSetGroups?.length > 0) { const permissionSetGroupWorksheet = workbook.addWorksheet('Permission Set Groups'); permissionSetGroupWorksheet.columns = [ { header: 'Name', key: 'name', width: 64 }, @@ -555,7 +506,7 @@ export class Atlas { } } - if (this.workflowRules.length > 0) { + if (this.album.workflowRules?.length > 0) { const workflowRuleWorksheet = workbook.addWorksheet('Workflow Rules'); workflowRuleWorksheet.columns = [ { header: 'Name', key: 'fullName', width: 64 }, @@ -565,7 +516,7 @@ export class Atlas { { header: 'Description', key: 'description', width: 128 }, ]; workflowRuleWorksheet.getRow(1).font = { bold: true }; - for (const thisWorkflowRule of this.workflowRules) { + for (const thisWorkflowRule of this.album.workflowRules as Array>) { workflowRuleWorksheet.addRow({ fullName: thisWorkflowRule.fullName, objectName: thisWorkflowRule.objectName, @@ -576,7 +527,7 @@ export class Atlas { } } - if (this.album.quickActions.length > 0) { + if (this.album.quickActions?.length > 0) { const quickActionWorksheet = workbook.addWorksheet('Quick Actions'); quickActionWorksheet.columns = [ { header: 'Full Name', key: 'fullName', width: 64 }, @@ -611,7 +562,7 @@ export class Atlas { } } - if (this.album.tabs.length > 0) { + if (this.album.tabs?.length > 0) { const tabWorksheet = workbook.addWorksheet('Tabs'); tabWorksheet.columns = [ { header: 'Full Name', key: 'fullName', width: 64 }, @@ -650,7 +601,7 @@ export class Atlas { } } - if (this.album.layouts.length > 0) { + if (this.album.layouts?.length > 0) { const layoutWorksheet = workbook.addWorksheet('Layouts'); layoutWorksheet.columns = [ { header: 'Full Name', key: 'fullName', width: 64 }, @@ -667,7 +618,7 @@ export class Atlas { } } - if (this.album.flexipages.length > 0) { + if (this.album.flexipages?.length > 0) { const flexipageWorksheet = workbook.addWorksheet('Flexipages'); flexipageWorksheet.columns = [ { header: 'Name', key: 'name', width: 64 }, @@ -690,7 +641,7 @@ export class Atlas { } } - if (this.album.flows.length > 0) { + if (this.album.flows?.length > 0) { const flowWorksheet = workbook.addWorksheet('Flows'); flowWorksheet.columns = [ { header: 'Name', key: 'name', width: 64 }, @@ -731,18 +682,4 @@ export class Atlas { await workbook.xlsx.writeFile(fileName); return fileName; } - - private isMetadataFile(thisFile: string): boolean { - return this.metadataExtensions.has(getExtension(thisFile)); - } -} - -async function getAllProjectFiles(projectPath: string): Promise { - const metadata: string[] = []; - const packageDirectories: NamedPackageDir[] = SfProject.getInstance(projectPath).getUniquePackageDirectories(); - for await (const thisPackageDirectory of packageDirectories) { - const items = await readdir(thisPackageDirectory.fullPath, { recursive: true }); - metadata.push(...items.map((item) => thisPackageDirectory.fullPath + item)); - } - return metadata; }