From 8b405e9e92dfe46fe427d9024fa7f51327a13150 Mon Sep 17 00:00:00 2001 From: Cesar Parra Date: Thu, 10 Oct 2024 08:12:05 -0400 Subject: [PATCH 01/32] Cleaning up dist after every build (#193) * Cleaning up `dist` after every build * Cleaning up `dist` after every build --- package.json | 4 ++-- src/core/changelog/generate-change-log.ts | 2 +- src/core/shared/utils.ts | 9 +++++++++ src/index.ts | 10 +--------- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index d247aaac..2b7631ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cparra/apexdocs", - "version": "3.3.0", + "version": "3.3.1", "description": "Library with CLI capabilities to generate documentation for Salesforce Apex classes.", "keywords": [ "apex", @@ -21,7 +21,7 @@ "scripts": { "test": "npm run build && jest", "test:cov": "npm run build && jest --coverage", - "build": "rimraf ./lib && npm run lint && tsc --noEmit && pkgroll", + "build": "rimraf ./dist && npm run lint && tsc --noEmit && pkgroll", "lint": "eslint \"./src/**/*.{js,ts}\" --quiet --fix", "prepare": "npm run build", "version": "npm run format && git add -A src", diff --git a/src/core/changelog/generate-change-log.ts b/src/core/changelog/generate-change-log.ts index fbce605a..371073ca 100644 --- a/src/core/changelog/generate-change-log.ts +++ b/src/core/changelog/generate-change-log.ts @@ -9,7 +9,7 @@ import { changelogTemplate } from './templates/changelog-template'; import { ReflectionErrors } from '../errors/errors'; import { apply } from '#utils/fp'; import { filterScope } from '../reflection/filter-scope'; -import { skip } from '../../index'; +import { skip } from '../shared/utils'; export type ChangeLogPageData = { content: string; diff --git a/src/core/shared/utils.ts b/src/core/shared/utils.ts index 73ec83a8..233b0899 100644 --- a/src/core/shared/utils.ts +++ b/src/core/shared/utils.ts @@ -1,5 +1,14 @@ import { Skip } from './types'; +/** + * Represents a file to be skipped. + */ +export function skip(): Skip { + return { + _tag: 'Skip', + }; +} + export function isSkip(value: unknown): value is Skip { return Object.prototype.hasOwnProperty.call(value, '_tag') && (value as Skip)._tag === 'Skip'; } diff --git a/src/index.ts b/src/index.ts index 37b5ae82..dc7c2aa1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,7 @@ import type { UserDefinedOpenApiConfig, UserDefinedChangelogConfig, } from './core/shared/types'; +import { skip } from './core/shared/utils'; import { changeLogDefaults, markdownDefaults, openApiDefaults } from './defaults'; import { process } from './node/process'; @@ -59,15 +60,6 @@ function defineChangelogConfig(config: ConfigurableChangelogConfig): Partial Date: Mon, 14 Oct 2024 17:25:40 -0400 Subject: [PATCH 02/32] Initial commit --- src/application/Apexdocs.ts | 18 ++-- ...pec.ts => source-code-file-reader.spec.ts} | 31 +++++- src/application/apex-file-reader.ts | 63 ----------- src/application/generators/changelog.ts | 6 +- src/application/generators/markdown.ts | 6 +- src/application/generators/openapi.ts | 6 +- src/application/source-code-file-reader.ts | 100 +++++++++++++++++ .../__test__/generating-change-log.spec.ts | 102 +++++++++--------- src/core/changelog/generate-change-log.ts | 8 +- src/core/markdown/__test__/test-helpers.ts | 7 +- src/core/markdown/generate-docs.ts | 4 +- src/core/openapi/manifest-factory.ts | 4 +- src/core/openapi/parser.ts | 8 +- src/core/reflection/reflect-source.ts | 8 +- src/core/shared/types.d.ts | 12 ++- 15 files changed, 234 insertions(+), 149 deletions(-) rename src/application/__tests__/{apex-file-reader.spec.ts => source-code-file-reader.spec.ts} (85%) delete mode 100644 src/application/apex-file-reader.ts create mode 100644 src/application/source-code-file-reader.ts diff --git a/src/application/Apexdocs.ts b/src/application/Apexdocs.ts index 7ee33925..2f1ecd31 100644 --- a/src/application/Apexdocs.ts +++ b/src/application/Apexdocs.ts @@ -6,11 +6,11 @@ import markdown from './generators/markdown'; import openApi from './generators/openapi'; import changelog from './generators/changelog'; -import { processFiles } from './apex-file-reader'; +import { processApexFiles, processFiles } from './source-code-file-reader'; import { DefaultFileSystem } from './file-system'; import { Logger } from '#utils/logger'; import { - UnparsedSourceFile, + UnparsedApexFile, UserDefinedChangelogConfig, UserDefinedConfig, UserDefinedMarkdownConfig, @@ -51,25 +51,27 @@ const readFiles = apply(processFiles, new DefaultFileSystem()); async function processMarkdown(config: UserDefinedMarkdownConfig) { return pipe( TE.tryCatch( - () => readFiles(config.sourceDir, config.includeMetadata, config.exclude), + () => readFiles(config.sourceDir, config.exclude, [processApexFiles(config.includeMetadata)]), (e) => new FileReadingError('An error occurred while reading files.', e), ), - TE.flatMap((fileBodies) => markdown(fileBodies, config)), + TE.flatMap((fileBodies) => markdown(fileBodies as UnparsedApexFile[], config)), TE.map(() => '✔️ Documentation generated successfully!'), TE.mapLeft(toErrors), ); } async function processOpenApi(config: UserDefinedOpenApiConfig, logger: Logger) { - const fileBodies = await readFiles(config.sourceDir, false, config.exclude); + const fileBodies = (await readFiles(config.sourceDir, config.exclude, [ + processApexFiles(false), + ])) as UnparsedApexFile[]; return openApi(logger, fileBodies, config); } async function processChangeLog(config: UserDefinedChangelogConfig) { - async function loadFiles(): Promise<[UnparsedSourceFile[], UnparsedSourceFile[]]> { + async function loadFiles(): Promise<[UnparsedApexFile[], UnparsedApexFile[]]> { return [ - await readFiles(config.previousVersionDir, false, config.exclude), - await readFiles(config.currentVersionDir, false, config.exclude), + (await readFiles(config.previousVersionDir, config.exclude, [processApexFiles(false)])) as UnparsedApexFile[], + (await readFiles(config.currentVersionDir, config.exclude, [processApexFiles(false)])) as UnparsedApexFile[], ]; } diff --git a/src/application/__tests__/apex-file-reader.spec.ts b/src/application/__tests__/source-code-file-reader.spec.ts similarity index 85% rename from src/application/__tests__/apex-file-reader.spec.ts rename to src/application/__tests__/source-code-file-reader.spec.ts index 9b33fc5c..1c44bcea 100644 --- a/src/application/__tests__/apex-file-reader.spec.ts +++ b/src/application/__tests__/source-code-file-reader.spec.ts @@ -1,5 +1,5 @@ import { FileSystem } from '../file-system'; -import { processFiles } from '../apex-file-reader'; +import { processFiles } from '../source-code-file-reader'; type File = { type: 'file'; @@ -126,6 +126,35 @@ describe('File Reader', () => { expect(result[1].content).toBe('public class AnotherClass{}'); }); + it('returns the file contents of all Object files', async () => { + const objectContent = ` + + + Deployed + test object with one field for eclipse ide testing + + MyFirstObjects + `; + + const fileSystem = new TestFileSystem([ + { + type: 'directory', + path: '', + files: [ + { + type: 'file', + path: 'SomeObject__c.object-meta.xml\n', + content: objectContent, + }, + ], + }, + ]); + + const result = await processFiles(fileSystem, '', false, []); + expect(result.length).toBe(1); + expect(result[0].content).toBe(objectContent); + }); + it('skips files that match the excluded glob pattern', async () => { const fileSystem = new TestFileSystem([ { diff --git a/src/application/apex-file-reader.ts b/src/application/apex-file-reader.ts deleted file mode 100644 index 4ddd3697..00000000 --- a/src/application/apex-file-reader.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { FileSystem } from './file-system'; -import { UnparsedSourceFile } from '../core/shared/types'; -import { minimatch } from 'minimatch'; -import { pipe } from 'fp-ts/function'; -import { apply } from '#utils/fp'; - -const APEX_FILE_EXTENSION = '.cls'; - -/** - * Reads from .cls files and returns their raw body. - */ -export async function processFiles( - fileSystem: FileSystem, - rootPath: string, - includeMetadata: boolean, - exclude: string[], -): Promise { - const processSingleFile = apply(processFile, fileSystem, includeMetadata); - - return pipe( - await getFilePaths(fileSystem, rootPath), - (filePaths) => filePaths.filter((filePath) => !isExcluded(filePath, exclude)), - (filePaths) => filePaths.filter(isApexFile), - (filePaths) => Promise.all(filePaths.map(processSingleFile)), - ); -} - -async function getFilePaths(fileSystem: FileSystem, rootPath: string): Promise { - const directoryContents = await fileSystem.readDirectory(rootPath); - const paths: string[] = []; - for (const filePath of directoryContents) { - const currentPath = fileSystem.joinPath(rootPath, filePath); - if (await fileSystem.isDirectory(currentPath)) { - paths.push(...(await getFilePaths(fileSystem, currentPath))); - } else { - paths.push(currentPath); - } - } - return paths; -} - -function isExcluded(filePath: string, exclude: string[]): boolean { - return exclude.some((pattern) => minimatch(filePath, pattern)); -} - -async function processFile( - fileSystem: FileSystem, - includeMetadata: boolean, - filePath: string, -): Promise { - const rawTypeContent = await fileSystem.readFile(filePath); - const metadataPath = `${filePath}-meta.xml`; - let rawMetadataContent = null; - if (includeMetadata) { - rawMetadataContent = fileSystem.exists(metadataPath) ? await fileSystem.readFile(metadataPath) : null; - } - - return { filePath, content: rawTypeContent, metadataContent: rawMetadataContent }; -} - -function isApexFile(currentFile: string): boolean { - return currentFile.endsWith(APEX_FILE_EXTENSION); -} diff --git a/src/application/generators/changelog.ts b/src/application/generators/changelog.ts index 8556deb2..4b944705 100644 --- a/src/application/generators/changelog.ts +++ b/src/application/generators/changelog.ts @@ -1,5 +1,5 @@ import { pipe } from 'fp-ts/function'; -import { PageData, Skip, UnparsedSourceFile, UserDefinedChangelogConfig } from '../../core/shared/types'; +import { PageData, Skip, UnparsedApexFile, UserDefinedChangelogConfig } from '../../core/shared/types'; import * as TE from 'fp-ts/TaskEither'; import { writeFiles } from '../file-writer'; import { ChangeLogPageData, generateChangeLog } from '../../core/changelog/generate-change-log'; @@ -7,8 +7,8 @@ import { FileWritingError } from '../errors'; import { isSkip } from '../../core/shared/utils'; export default function generate( - oldBundles: UnparsedSourceFile[], - newBundles: UnparsedSourceFile[], + oldBundles: UnparsedApexFile[], + newBundles: UnparsedApexFile[], config: UserDefinedChangelogConfig, ) { function handleFile(file: ChangeLogPageData | Skip) { diff --git a/src/application/generators/markdown.ts b/src/application/generators/markdown.ts index 50fa766a..31300ef2 100644 --- a/src/application/generators/markdown.ts +++ b/src/application/generators/markdown.ts @@ -3,7 +3,7 @@ import { pipe } from 'fp-ts/function'; import { PageData, PostHookDocumentationBundle, - UnparsedSourceFile, + UnparsedApexFile, UserDefinedMarkdownConfig, } from '../../core/shared/types'; import { referenceGuideTemplate } from '../../core/markdown/templates/reference-guide'; @@ -12,14 +12,14 @@ import { isSkip } from '../../core/shared/utils'; import { writeFiles } from '../file-writer'; import { FileWritingError } from '../errors'; -export default function generate(bundles: UnparsedSourceFile[], config: UserDefinedMarkdownConfig) { +export default function generate(bundles: UnparsedApexFile[], config: UserDefinedMarkdownConfig) { return pipe( generateDocumentationBundle(bundles, config), TE.flatMap((files) => writeFilesToSystem(files, config.targetDir)), ); } -function generateDocumentationBundle(bundles: UnparsedSourceFile[], config: UserDefinedMarkdownConfig) { +function generateDocumentationBundle(bundles: UnparsedApexFile[], config: UserDefinedMarkdownConfig) { return generateDocs(bundles, { ...config, referenceGuideTemplate: referenceGuideTemplate, diff --git a/src/application/generators/openapi.ts b/src/application/generators/openapi.ts index 8a15d2cd..2b1e6670 100644 --- a/src/application/generators/openapi.ts +++ b/src/application/generators/openapi.ts @@ -6,7 +6,7 @@ import { Logger } from '#utils/logger'; import ErrorLogger from '#utils/error-logger'; import { reflect, ReflectionResult } from '@cparra/apex-reflection'; import Manifest from '../../core/manifest'; -import { PageData, UnparsedSourceFile, UserDefinedOpenApiConfig } from '../../core/shared/types'; +import { PageData, UnparsedApexFile, UserDefinedOpenApiConfig } from '../../core/shared/types'; import { OpenApiDocsProcessor } from '../../core/openapi/open-api-docs-processor'; import { writeFiles } from '../file-writer'; import { pipe } from 'fp-ts/function'; @@ -16,7 +16,7 @@ import { apply } from '#utils/fp'; export default async function openApi( logger: Logger, - fileBodies: UnparsedSourceFile[], + fileBodies: UnparsedApexFile[], config: UserDefinedOpenApiConfig, ) { OpenApiSettings.build({ @@ -46,7 +46,7 @@ export default async function openApi( ErrorLogger.logErrors(logger, filteredTypes); } -function reflectionWithLogger(logger: Logger, apexBundle: UnparsedSourceFile): ReflectionResult { +function reflectionWithLogger(logger: Logger, apexBundle: UnparsedApexFile): ReflectionResult { const result = reflect(apexBundle.content); if (result.error) { logger.error(`${apexBundle.filePath} - Parsing error ${result.error?.message}`); diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts new file mode 100644 index 00000000..d1643198 --- /dev/null +++ b/src/application/source-code-file-reader.ts @@ -0,0 +1,100 @@ +import { FileSystem } from './file-system'; +import { UnparsedApexFile, UnparsedSourceFile } from '../core/shared/types'; +import { minimatch } from 'minimatch'; +import { pipe } from 'fp-ts/function'; + +/** + * Reads from .cls files and returns their raw body. + */ +export async function processFiles( + fileSystem: FileSystem, + rootPath: string, + exclude: string[], + processors: FileProcessor[], +): Promise { + return pipe( + await getFilePaths(fileSystem, rootPath), + (filePaths) => filePaths.filter((filePath) => !isExcluded(filePath, exclude)), + (filteredFilePaths) => readFiles(fileSystem, filteredFilePaths, processors), + ); +} + +async function readFiles( + fileSystem: FileSystem, + filePaths: string[], + processors: FileProcessor[], +): Promise { + const files: UnparsedSourceFile[] = []; + for (const filePath of filePaths) { + const processor = processors.find((p) => p.isSupportedFile(filePath)); + if (processor) { + files.push(await processor.process(fileSystem, filePath)); + } + } + return files; +} + +async function getFilePaths(fileSystem: FileSystem, rootPath: string): Promise { + const directoryContents = await fileSystem.readDirectory(rootPath); + const paths: string[] = []; + for (const filePath of directoryContents) { + const currentPath = fileSystem.joinPath(rootPath, filePath); + if (await fileSystem.isDirectory(currentPath)) { + paths.push(...(await getFilePaths(fileSystem, currentPath))); + } else { + paths.push(currentPath); + } + } + return paths; +} + +function isExcluded(filePath: string, exclude: string[]): boolean { + return exclude.some((pattern) => minimatch(filePath, pattern)); +} + +interface FileProcessor { + isSupportedFile: (currentFile: string) => boolean; + process: (fileSystem: FileSystem, filePath: string) => Promise; +} + +export function processApexFiles(includeMetadata: boolean): FileProcessor { + return new ApexFileReader(includeMetadata); +} + +class ApexFileReader implements FileProcessor { + APEX_FILE_EXTENSION = '.cls'; + + constructor(public includeMetadata: boolean) {} + + isSupportedFile(currentFile: string): boolean { + return currentFile.endsWith(this.APEX_FILE_EXTENSION); + } + + async process(fileSystem: FileSystem, filePath: string): Promise { + const rawTypeContent = await fileSystem.readFile(filePath); + const metadataPath = `${filePath}-meta.xml`; + let rawMetadataContent = null; + if (this.includeMetadata) { + rawMetadataContent = fileSystem.exists(metadataPath) ? await fileSystem.readFile(metadataPath) : null; + } + + return { type: 'apex', filePath, content: rawTypeContent, metadataContent: rawMetadataContent }; + } +} + +export function processObjectFiles(): FileProcessor { + return new ObjectFileReader(); +} + +class ObjectFileReader implements FileProcessor { + OBJECT_FILE_EXTENSION = '.object-meta.xml'; + + isSupportedFile(currentFile: string): boolean { + return currentFile.endsWith(this.OBJECT_FILE_EXTENSION); + } + + async process(fileSystem: FileSystem, filePath: string): Promise { + const rawTypeContent = await fileSystem.readFile(filePath); + return { type: 'object', filePath, content: rawTypeContent }; + } +} diff --git a/src/core/changelog/__test__/generating-change-log.spec.ts b/src/core/changelog/__test__/generating-change-log.spec.ts index 55c9bf33..4924f586 100644 --- a/src/core/changelog/__test__/generating-change-log.spec.ts +++ b/src/core/changelog/__test__/generating-change-log.spec.ts @@ -1,4 +1,4 @@ -import { UnparsedSourceFile } from '../../shared/types'; +import { UnparsedApexFile } from '../../shared/types'; import { ChangeLogPageData, generateChangeLog } from '../generate-change-log'; import { assertEither } from '../../test-helpers/assert-either'; import { isSkip } from '../../shared/utils'; @@ -34,8 +34,8 @@ describe('when generating a changelog', () => { describe('that does not include new classes', () => { it('should not have a section for new classes', async () => { - const oldBundle: UnparsedSourceFile[] = []; - const newBundle: UnparsedSourceFile[] = []; + const oldBundle: UnparsedApexFile[] = []; + const newBundle: UnparsedApexFile[] = []; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -47,9 +47,9 @@ describe('when generating a changelog', () => { it('should include a section for new classes', async () => { const newClassSource = 'class Test {}'; - const oldBundle: UnparsedSourceFile[] = []; - const newBundle: UnparsedSourceFile[] = [ - { content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + const oldBundle: UnparsedApexFile[] = []; + const newBundle: UnparsedApexFile[] = [ + { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -60,9 +60,9 @@ describe('when generating a changelog', () => { it('should include the new class name', async () => { const newClassSource = 'class Test {}'; - const oldBundle: UnparsedSourceFile[] = []; - const newBundle: UnparsedSourceFile[] = [ - { content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + const oldBundle: UnparsedApexFile[] = []; + const newBundle: UnparsedApexFile[] = [ + { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -78,9 +78,9 @@ describe('when generating a changelog', () => { class Test {} `; - const oldBundle: UnparsedSourceFile[] = []; - const newBundle: UnparsedSourceFile[] = [ - { content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + const oldBundle: UnparsedApexFile[] = []; + const newBundle: UnparsedApexFile[] = [ + { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -93,9 +93,9 @@ describe('when generating a changelog', () => { it('should include a section for new interfaces', async () => { const newInterfaceSource = 'interface Test {}'; - const oldBundle: UnparsedSourceFile[] = []; - const newBundle: UnparsedSourceFile[] = [ - { content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, + const oldBundle: UnparsedApexFile[] = []; + const newBundle: UnparsedApexFile[] = [ + { type: 'apex', content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -106,9 +106,9 @@ describe('when generating a changelog', () => { it('should include the new interface name', async () => { const newInterfaceSource = 'interface Test {}'; - const oldBundle: UnparsedSourceFile[] = []; - const newBundle: UnparsedSourceFile[] = [ - { content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, + const oldBundle: UnparsedApexFile[] = []; + const newBundle: UnparsedApexFile[] = [ + { type: 'apex', content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -124,9 +124,9 @@ describe('when generating a changelog', () => { interface Test {} `; - const oldBundle: UnparsedSourceFile[] = []; - const newBundle: UnparsedSourceFile[] = [ - { content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, + const oldBundle: UnparsedApexFile[] = []; + const newBundle: UnparsedApexFile[] = [ + { type: 'apex', content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -141,8 +141,10 @@ describe('when generating a changelog', () => { it('should include a section for new enums', async () => { const newEnumSource = 'enum Test {}'; - const oldBundle: UnparsedSourceFile[] = []; - const newBundle: UnparsedSourceFile[] = [{ content: newEnumSource, filePath: 'Test.cls', metadataContent: null }]; + const oldBundle: UnparsedApexFile[] = []; + const newBundle: UnparsedApexFile[] = [ + { type: 'apex', content: newEnumSource, filePath: 'Test.cls', metadataContent: null }, + ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -152,8 +154,10 @@ describe('when generating a changelog', () => { it('should include the new enum name', async () => { const newEnumSource = 'enum Test {}'; - const oldBundle: UnparsedSourceFile[] = []; - const newBundle: UnparsedSourceFile[] = [{ content: newEnumSource, filePath: 'Test.cls', metadataContent: null }]; + const oldBundle: UnparsedApexFile[] = []; + const newBundle: UnparsedApexFile[] = [ + { type: 'apex', content: newEnumSource, filePath: 'Test.cls', metadataContent: null }, + ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -168,8 +172,10 @@ describe('when generating a changelog', () => { enum Test {} `; - const oldBundle: UnparsedSourceFile[] = []; - const newBundle: UnparsedSourceFile[] = [{ content: newEnumSource, filePath: 'Test.cls', metadataContent: null }]; + const oldBundle: UnparsedApexFile[] = []; + const newBundle: UnparsedApexFile[] = [ + { type: 'apex', content: newEnumSource, filePath: 'Test.cls', metadataContent: null }, + ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -181,9 +187,9 @@ describe('when generating a changelog', () => { it('should not include them', async () => { const newClassSource = 'class Test {}'; - const oldBundle: UnparsedSourceFile[] = []; - const newBundle: UnparsedSourceFile[] = [ - { content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + const oldBundle: UnparsedApexFile[] = []; + const newBundle: UnparsedApexFile[] = [ + { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, { ...config, scope: ['global'] })(); @@ -196,10 +202,10 @@ describe('when generating a changelog', () => { it('should include a section for removed types', async () => { const oldClassSource = 'class Test {}'; - const oldBundle: UnparsedSourceFile[] = [ - { content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, + const oldBundle: UnparsedApexFile[] = [ + { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; - const newBundle: UnparsedSourceFile[] = []; + const newBundle: UnparsedApexFile[] = []; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -209,10 +215,10 @@ describe('when generating a changelog', () => { it('should include the removed type name', async () => { const oldClassSource = 'class Test {}'; - const oldBundle: UnparsedSourceFile[] = [ - { content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, + const oldBundle: UnparsedApexFile[] = [ + { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; - const newBundle: UnparsedSourceFile[] = []; + const newBundle: UnparsedApexFile[] = []; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -225,12 +231,12 @@ describe('when generating a changelog', () => { const oldClassSource = 'class Test {}'; const newClassSource = 'class Test { void myMethod() {} }'; - const oldBundle: UnparsedSourceFile[] = [ - { content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, + const oldBundle: UnparsedApexFile[] = [ + { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; - const newBundle: UnparsedSourceFile[] = [ - { content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + const newBundle: UnparsedApexFile[] = [ + { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -244,12 +250,12 @@ describe('when generating a changelog', () => { const oldClassSource = 'class Test {}'; const newClassSource = 'class Test { void myMethod() {} }'; - const oldBundle: UnparsedSourceFile[] = [ - { content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, + const oldBundle: UnparsedApexFile[] = [ + { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; - const newBundle: UnparsedSourceFile[] = [ - { content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + const newBundle: UnparsedApexFile[] = [ + { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -261,12 +267,12 @@ describe('when generating a changelog', () => { const oldClassSource = 'class Test {}'; const newClassSource = 'class Test { void myMethod() {} }'; - const oldBundle: UnparsedSourceFile[] = [ - { content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, + const oldBundle: UnparsedApexFile[] = [ + { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; - const newBundle: UnparsedSourceFile[] = [ - { content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + const newBundle: UnparsedApexFile[] = [ + { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); diff --git a/src/core/changelog/generate-change-log.ts b/src/core/changelog/generate-change-log.ts index 371073ca..5729bd37 100644 --- a/src/core/changelog/generate-change-log.ts +++ b/src/core/changelog/generate-change-log.ts @@ -1,4 +1,4 @@ -import { ParsedFile, Skip, UnparsedSourceFile, UserDefinedChangelogConfig } from '../shared/types'; +import { ParsedFile, Skip, UnparsedApexFile, UserDefinedChangelogConfig } from '../shared/types'; import { pipe } from 'fp-ts/function'; import * as TE from 'fp-ts/TaskEither'; import { reflectBundles } from '../reflection/reflect-source'; @@ -17,13 +17,13 @@ export type ChangeLogPageData = { }; export function generateChangeLog( - oldBundles: UnparsedSourceFile[], - newBundles: UnparsedSourceFile[], + oldBundles: UnparsedApexFile[], + newBundles: UnparsedApexFile[], config: Omit, ): TE.TaskEither { const filterOutOfScope = apply(filterScope, config.scope); - function reflect(sourceFiles: UnparsedSourceFile[]) { + function reflect(sourceFiles: UnparsedApexFile[]) { return pipe(reflectBundles(sourceFiles), TE.map(filterOutOfScope)); } diff --git a/src/core/markdown/__test__/test-helpers.ts b/src/core/markdown/__test__/test-helpers.ts index a3cd95a8..1f1a512e 100644 --- a/src/core/markdown/__test__/test-helpers.ts +++ b/src/core/markdown/__test__/test-helpers.ts @@ -1,16 +1,17 @@ -import { UnparsedSourceFile } from '../../shared/types'; +import { UnparsedApexFile } from '../../shared/types'; import { generateDocs as gen, MarkdownGeneratorConfig } from '../generate-docs'; import { referenceGuideTemplate } from '../templates/reference-guide'; -export function apexBundleFromRawString(raw: string, rawMetadata?: string): UnparsedSourceFile { +export function apexBundleFromRawString(raw: string, rawMetadata?: string): UnparsedApexFile { return { + type: 'apex', filePath: 'test.cls', content: raw, metadataContent: rawMetadata ?? null, }; } -export function generateDocs(apexBundles: UnparsedSourceFile[], config?: Partial) { +export function generateDocs(apexBundles: UnparsedApexFile[], config?: Partial) { return gen(apexBundles, { targetDir: 'target', scope: ['global', 'public'], diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index 6bb0d92d..bd3f126f 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -9,7 +9,7 @@ import { Frontmatter, PostHookDocumentationBundle, ReferenceGuidePageData, - UnparsedSourceFile, + UnparsedApexFile, TransformDocPage, TransformDocs, TransformReferenceGuide, @@ -39,7 +39,7 @@ export type MarkdownGeneratorConfig = Omit< referenceGuideTemplate: string; }; -export function generateDocs(apexBundles: UnparsedSourceFile[], config: MarkdownGeneratorConfig) { +export function generateDocs(apexBundles: UnparsedApexFile[], config: MarkdownGeneratorConfig) { const filterOutOfScope = apply(filterScope, config.scope); const convertToReferences = apply(parsedFilesToReferenceGuide, config); const convertToRenderableBundle = apply(parsedFilesToRenderableBundle, config); diff --git a/src/core/openapi/manifest-factory.ts b/src/core/openapi/manifest-factory.ts index 9ba277a1..d299994b 100644 --- a/src/core/openapi/manifest-factory.ts +++ b/src/core/openapi/manifest-factory.ts @@ -1,7 +1,7 @@ import Manifest from '../manifest'; import { TypeParser } from './parser'; import { ReflectionResult } from '@cparra/apex-reflection'; -import { UnparsedSourceFile } from '../shared/types'; +import { UnparsedApexFile } from '../shared/types'; /** * Builds a new Manifest object, sourcing its types from the received TypeParser. @@ -10,7 +10,7 @@ import { UnparsedSourceFile } from '../shared/types'; */ export function createManifest( typeParser: TypeParser, - reflect: (apexBundle: UnparsedSourceFile) => ReflectionResult, + reflect: (apexBundle: UnparsedApexFile) => ReflectionResult, ): Manifest { return new Manifest(typeParser.parse(reflect)); } diff --git a/src/core/openapi/parser.ts b/src/core/openapi/parser.ts index 1ce9594b..22605743 100644 --- a/src/core/openapi/parser.ts +++ b/src/core/openapi/parser.ts @@ -1,9 +1,9 @@ import { ClassMirror, InterfaceMirror, ReflectionResult, Type } from '@cparra/apex-reflection'; import { Logger } from '#utils/logger'; -import { UnparsedSourceFile } from '../shared/types'; +import { UnparsedApexFile } from '../shared/types'; export interface TypeParser { - parse(reflect: (apexBundle: UnparsedSourceFile) => ReflectionResult): Type[]; + parse(reflect: (apexBundle: UnparsedApexFile) => ReflectionResult): Type[]; } type NameAware = { name: string }; @@ -11,10 +11,10 @@ type NameAware = { name: string }; export class RawBodyParser implements TypeParser { constructor( private logger: Logger, - public typeBundles: UnparsedSourceFile[], + public typeBundles: UnparsedApexFile[], ) {} - parse(reflect: (apexBundle: UnparsedSourceFile) => ReflectionResult): Type[] { + parse(reflect: (apexBundle: UnparsedApexFile) => ReflectionResult): Type[] { const types = this.typeBundles .map((currentBundle) => { this.logger.log(`Parsing file: ${currentBundle.filePath}`); diff --git a/src/core/reflection/reflect-source.ts b/src/core/reflection/reflect-source.ts index f83199ee..94d94feb 100644 --- a/src/core/reflection/reflect-source.ts +++ b/src/core/reflection/reflect-source.ts @@ -8,7 +8,7 @@ import * as O from 'fp-ts/Option'; import { ParsingError } from '@cparra/apex-reflection'; import { apply } from '#utils/fp'; import { Semigroup } from 'fp-ts/Semigroup'; -import { ParsedFile, UnparsedSourceFile } from '../shared/types'; +import { ParsedFile, UnparsedApexFile } from '../shared/types'; import { ReflectionError, ReflectionErrors } from '../errors/errors'; import { parseApexMetadata } from '../parse-apex-metadata'; @@ -25,7 +25,7 @@ async function reflectAsync(rawSource: string): Promise { }); } -export function reflectBundles(apexBundles: UnparsedSourceFile[]) { +export function reflectBundles(apexBundles: UnparsedApexFile[]) { const semiGroupReflectionError: Semigroup = { concat: (x, y) => new ReflectionErrors([...x.errors, ...y.errors]), }; @@ -34,14 +34,14 @@ export function reflectBundles(apexBundles: UnparsedSourceFile[]) { return pipe(apexBundles, A.traverse(Ap)(reflectBundle)); } -function reflectBundle(apexBundle: UnparsedSourceFile): TE.TaskEither { +function reflectBundle(apexBundle: UnparsedApexFile): TE.TaskEither { const convertToParsedFile: (typeMirror: Type) => ParsedFile = apply(toParsedFile, apexBundle.filePath); const withMetadata = apply(addMetadata, apexBundle.metadataContent); return pipe(apexBundle, reflectAsTask, TE.map(convertToParsedFile), TE.flatMap(withMetadata)); } -function reflectAsTask(apexBundle: UnparsedSourceFile): TE.TaskEither { +function reflectAsTask(apexBundle: UnparsedApexFile): TE.TaskEither { return TE.tryCatch( () => reflectAsync(apexBundle.content), (error) => diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index 74d31854..c953fcf8 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -26,6 +26,7 @@ export type UserDefinedMarkdownConfig = { linkingStrategy: LinkingStrategy; excludeTags: string[]; referenceGuideTitle: string; + /** Glob patterns to exclude files from the documentation. */ exclude: string[]; } & Partial; @@ -53,7 +54,16 @@ export type UserDefinedChangelogConfig = { export type UserDefinedConfig = UserDefinedMarkdownConfig | UserDefinedOpenApiConfig | UserDefinedChangelogConfig; -export type UnparsedSourceFile = { +export type UnparsedSourceFile = UnparsedObjectFile | UnparsedApexFile; + +export type UnparsedObjectFile = { + type: 'object'; + filePath: string; + content: string; +}; + +export type UnparsedApexFile = { + type: 'apex'; filePath: string; content: string; metadataContent: string | null; From cd4de3660e8bf87cd7b888fccdb82889cef170ec Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 15 Oct 2024 10:35:11 -0400 Subject: [PATCH 03/32] WIP --- .../__tests__/source-code-file-reader.spec.ts | 16 +++--- src/core/changelog/generate-change-log.ts | 4 +- src/core/markdown/generate-docs.ts | 43 +++++++++++---- src/core/reflection/reflect-object-source.ts | 53 +++++++++++++++++++ src/core/reflection/reflect-source.ts | 4 +- src/core/shared/types.d.ts | 5 +- 6 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 src/core/reflection/reflect-object-source.ts diff --git a/src/application/__tests__/source-code-file-reader.spec.ts b/src/application/__tests__/source-code-file-reader.spec.ts index 1c44bcea..84f122d3 100644 --- a/src/application/__tests__/source-code-file-reader.spec.ts +++ b/src/application/__tests__/source-code-file-reader.spec.ts @@ -1,5 +1,5 @@ import { FileSystem } from '../file-system'; -import { processFiles } from '../source-code-file-reader'; +import { processApexFiles, processFiles, processObjectFiles } from '../source-code-file-reader'; type File = { type: 'file'; @@ -70,7 +70,7 @@ describe('File Reader', () => { }, ]); - const result = await processFiles(fileSystem, '', false, []); + const result = await processFiles(fileSystem, '', [], [processApexFiles(false)]); expect(result.length).toBe(0); }); @@ -90,7 +90,7 @@ describe('File Reader', () => { }, ]); - const result = await processFiles(fileSystem, '', false, []); + const result = await processFiles(fileSystem, '', [], [processApexFiles(false)]); expect(result.length).toBe(0); }); @@ -120,7 +120,7 @@ describe('File Reader', () => { }, ]); - const result = await processFiles(fileSystem, '', false, []); + const result = await processFiles(fileSystem, '', [], [processApexFiles(false)]); expect(result.length).toBe(2); expect(result[0].content).toBe('public class MyClass{}'); expect(result[1].content).toBe('public class AnotherClass{}'); @@ -143,14 +143,14 @@ describe('File Reader', () => { files: [ { type: 'file', - path: 'SomeObject__c.object-meta.xml\n', + path: 'SomeObject__c.object-meta.xml', content: objectContent, }, ], }, ]); - const result = await processFiles(fileSystem, '', false, []); + const result = await processFiles(fileSystem, '', [], [processObjectFiles()]); expect(result.length).toBe(1); expect(result[0].content).toBe(objectContent); }); @@ -181,7 +181,7 @@ describe('File Reader', () => { }, ]); - const result = await processFiles(fileSystem, '', false, ['**/AnotherFile.cls']); + const result = await processFiles(fileSystem, '', ['**/AnotherFile.cls'], [processApexFiles(false)]); expect(result.length).toBe(1); expect(result[0].content).toBe('public class MyClass{}'); }); @@ -234,7 +234,7 @@ describe('File Reader', () => { }, ]); - const result = await processFiles(fileSystem, '', false, []); + const result = await processFiles(fileSystem, '', [], [processApexFiles(false)]); expect(result.length).toBe(4); }); }); diff --git a/src/core/changelog/generate-change-log.ts b/src/core/changelog/generate-change-log.ts index 5729bd37..7b8b8cec 100644 --- a/src/core/changelog/generate-change-log.ts +++ b/src/core/changelog/generate-change-log.ts @@ -1,7 +1,7 @@ import { ParsedFile, Skip, UnparsedApexFile, UserDefinedChangelogConfig } from '../shared/types'; import { pipe } from 'fp-ts/function'; import * as TE from 'fp-ts/TaskEither'; -import { reflectBundles } from '../reflection/reflect-source'; +import { reflectApexSource } from '../reflection/reflect-source'; import { Changelog, hasChanges, processChangelog, VersionManifest } from './process-changelog'; import { convertToRenderableChangelog, RenderableChangelog } from './renderable-changelog'; import { CompilationRequest, Template } from '../template'; @@ -24,7 +24,7 @@ export function generateChangeLog( const filterOutOfScope = apply(filterScope, config.scope); function reflect(sourceFiles: UnparsedApexFile[]) { - return pipe(reflectBundles(sourceFiles), TE.map(filterOutOfScope)); + return pipe(reflectApexSource(sourceFiles), TE.map(filterOutOfScope)); } const convertToPageData = apply(toPageData, config.fileName); diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index bd3f126f..2dcb2ac6 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -17,9 +17,11 @@ import { DocPageReference, TransformReference, ParsedFile, + UnparsedObjectFile, + UnparsedSourceFile, } from '../shared/types'; import { parsedFilesToRenderableBundle } from './adapters/renderable-bundle'; -import { reflectBundles } from '../reflection/reflect-source'; +import { reflectApexSource } from '../reflection/reflect-source'; import { addInheritanceChainToTypes } from '../reflection/inheritance-chain-expanion'; import { addInheritedMembersToTypes } from '../reflection/inherited-member-expansion'; import { convertToDocumentationBundle } from './adapters/renderable-to-page-data'; @@ -31,6 +33,7 @@ import { isSkip } from '../shared/utils'; import { parsedFilesToReferenceGuide } from './adapters/reference-guide'; import { removeExcludedTags } from '../reflection/remove-excluded-tags'; import { HookError } from '../errors/errors'; +import { reflectObjectSources } from '../reflection/reflect-object-source'; export type MarkdownGeneratorConfig = Omit< UserDefinedMarkdownConfig, @@ -39,8 +42,7 @@ export type MarkdownGeneratorConfig = Omit< referenceGuideTemplate: string; }; -export function generateDocs(apexBundles: UnparsedApexFile[], config: MarkdownGeneratorConfig) { - const filterOutOfScope = apply(filterScope, config.scope); +export function generateDocs(unparsedApexFiles: UnparsedSourceFile[], config: MarkdownGeneratorConfig) { const convertToReferences = apply(parsedFilesToReferenceGuide, config); const convertToRenderableBundle = apply(parsedFilesToRenderableBundle, config); const convertToDocumentationBundleForTemplate = apply( @@ -49,16 +51,19 @@ export function generateDocs(apexBundles: UnparsedApexFile[], config: MarkdownGe config.referenceGuideTemplate, ); const sort = apply(sortTypesAndMembers, config.sortAlphabetically); - const removeExcluded = apply(removeExcludedTags, config.excludeTags); + + function filterApexSourceFiles(sourceFiles: UnparsedSourceFile[]): UnparsedApexFile[] { + return sourceFiles.filter((sourceFile): sourceFile is UnparsedApexFile => sourceFile.type === 'apex'); + } + + function filterObjectSourceFiles(sourceFiles: UnparsedSourceFile[]): UnparsedObjectFile[] { + return sourceFiles.filter((sourceFile): sourceFile is UnparsedObjectFile => sourceFile.type === 'object'); + } return pipe( - apexBundles, - reflectBundles, - TE.map(filterOutOfScope), - TE.map(addInheritedMembersToTypes), - TE.map(addInheritanceChainToTypes), + generateForApex(filterApexSourceFiles(unparsedApexFiles), config), + TE.map((parsedApexFiles) => [...parsedApexFiles, ...generateForObject(filterObjectSourceFiles(unparsedApexFiles))]), TE.map(sort), - TE.map(removeExcluded), TE.bindTo('parsedFiles'), TE.bind('references', ({ parsedFiles }) => TE.right(convertToReferences(parsedFiles))), TE.flatMap(({ parsedFiles, references }) => transformReferenceHook(config)({ references, parsedFiles })), @@ -69,6 +74,24 @@ export function generateDocs(apexBundles: UnparsedApexFile[], config: MarkdownGe ); } +function generateForApex(apexBundles: UnparsedApexFile[], config: MarkdownGeneratorConfig) { + const filterOutOfScope = apply(filterScope, config.scope); + const removeExcluded = apply(removeExcludedTags, config.excludeTags); + + return pipe( + apexBundles, + reflectApexSource, + TE.map(filterOutOfScope), + TE.map(addInheritedMembersToTypes), + TE.map(addInheritanceChainToTypes), + TE.map(removeExcluded), + ); +} + +function generateForObject(objectBundles: UnparsedObjectFile[]) { + return pipe(objectBundles, reflectObjectSources); +} + function transformReferenceHook(config: MarkdownGeneratorConfig) { async function _execute( references: Record, diff --git a/src/core/reflection/reflect-object-source.ts b/src/core/reflection/reflect-object-source.ts new file mode 100644 index 00000000..9fe20df3 --- /dev/null +++ b/src/core/reflection/reflect-object-source.ts @@ -0,0 +1,53 @@ +import { ParsedFile, UnparsedObjectFile } from '../shared/types'; +import { XMLParser } from 'fast-xml-parser'; +import * as TE from 'fp-ts/TaskEither'; +import { ReflectionError, ReflectionErrors } from '../errors/errors'; +import { Semigroup } from 'fp-ts/Semigroup'; +import * as T from 'fp-ts/Task'; +import { pipe } from 'fp-ts/function'; +import * as A from 'fp-ts/Array'; + +export type ObjectMetadata = { + type_name: 'object'; + label: string; +}; + +export function reflectObjectSources(objectSources: UnparsedObjectFile[]) { + const semiGroupReflectionError: Semigroup = { + concat: (x, y) => new ReflectionErrors([...x.errors, ...y.errors]), + }; + const Ap = TE.getApplicativeTaskValidation(T.ApplyPar, semiGroupReflectionError); + + return pipe(objectSources, A.traverse(Ap)(reflectObjectSource)); +} + +function reflectObjectSource(objectSource: UnparsedObjectFile): TE.TaskEither { + return pipe( + TE.tryCatch( + () => new XMLParser().parse(objectSource.content), + (error) => new ReflectionErrors([new ReflectionError(objectSource.filePath, (error as Error).message)]), + ), + TE.map((metadata) => addTypeName(metadata as ObjectMetadata)), + TE.map((metadata) => toParsedFile(objectSource.filePath, metadata)), + ); +} + +function addTypeName(objectMetadata: ObjectMetadata): ObjectMetadata { + return { + ...objectMetadata, + type_name: 'object', + }; +} + +function toParsedFile(filePath: string, typeMirror: ObjectMetadata): ParsedFile { + return { + source: { + filePath: filePath, + name: typeMirror.label, + type: typeMirror.type_name, + }, + type: typeMirror, + }; +} + +// TODO: Filter out non public diff --git a/src/core/reflection/reflect-source.ts b/src/core/reflection/reflect-source.ts index 94d94feb..181ae6db 100644 --- a/src/core/reflection/reflect-source.ts +++ b/src/core/reflection/reflect-source.ts @@ -25,7 +25,7 @@ async function reflectAsync(rawSource: string): Promise { }); } -export function reflectBundles(apexBundles: UnparsedApexFile[]) { +export function reflectApexSource(apexBundles: UnparsedApexFile[]) { const semiGroupReflectionError: Semigroup = { concat: (x, y) => new ReflectionErrors([...x.errors, ...y.errors]), }; @@ -67,7 +67,7 @@ function addMetadata( return TE.fromEither( pipe( parsedFile.type, - (type) => addFileMetadataToTypeAnnotation(type, rawMetadataContent), + (type) => addFileMetadataToTypeAnnotation(type as Type, rawMetadataContent), E.map((type) => ({ ...parsedFile, type })), E.mapLeft((error) => errorToReflectionErrors(error, parsedFile.source.filePath)), ), diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index c953fcf8..981fbbca 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -1,5 +1,6 @@ import { Type } from '@cparra/apex-reflection'; import { ChangeLogPageData } from '../changelog/generate-change-log'; +import { ObjectMetadata } from '../reflection/reflect-object-source'; export type Generators = 'markdown' | 'openapi' | 'changelog'; @@ -72,12 +73,12 @@ export type UnparsedApexFile = { export type SourceFileMetadata = { filePath: string; name: string; - type: 'interface' | 'class' | 'enum'; + type: 'interface' | 'class' | 'enum' | 'object'; }; export type ParsedFile = { source: SourceFileMetadata; - type: Type; + type: Type | ObjectMetadata; }; export type DocPageReference = { From d0ff5f822f7fea9222cc1060aea3a784b5c9631f Mon Sep 17 00:00:00 2001 From: cesarParra Date: Wed, 16 Oct 2024 14:56:11 -0400 Subject: [PATCH 04/32] Introducing object parsing --- src/cli/args.ts | 3 -- src/core/changelog/generate-change-log.ts | 7 ++-- src/core/markdown/adapters/apex-types.ts | 17 ++++++---- src/core/markdown/adapters/reference-guide.ts | 13 +++----- .../markdown/adapters/renderable-bundle.ts | 32 ++++++++++++++----- src/core/markdown/generate-docs.ts | 8 ++++- src/core/markdown/utils.ts | 4 ++- src/core/reflection/__test__/helpers.ts | 4 +-- src/core/reflection/filter-scope.ts | 3 +- .../reflection/inheritance-chain-expanion.ts | 2 +- .../reflection/inherited-member-expansion.ts | 4 +-- src/core/reflection/reflect-object-source.ts | 17 ++++++++-- src/core/reflection/reflect-source.ts | 10 +++--- src/core/reflection/remove-excluded-tags.ts | 2 +- src/core/reflection/sort-types-and-members.ts | 3 +- src/core/shared/types.d.ts | 6 ++-- src/core/shared/utils.ts | 27 ++++++++++++++++ 17 files changed, 113 insertions(+), 49 deletions(-) diff --git a/src/cli/args.ts b/src/cli/args.ts index 52e38f74..9f6cedc5 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -122,7 +122,6 @@ function extractArgsForCommandsProvidedInConfig( return pipe( extractMultiCommandConfig(extractFromProcessFn, 'markdown', generatorConfig), E.map((cliArgs) => { - console.log('markdown', cliArgs); return cliArgs; }), E.map((cliArgs) => ({ ...configOnlyMarkdownDefaults, ...generatorConfig, ...cliArgs })), @@ -136,7 +135,6 @@ function extractArgsForCommandsProvidedInConfig( return pipe( extractMultiCommandConfig(extractFromProcessFn, 'changelog', generatorConfig), E.map((cliArgs) => { - console.log('changelog', cliArgs); return cliArgs; }), E.map((cliArgs) => ({ ...configOnlyChangelogDefaults, ...generatorConfig, ...cliArgs })), @@ -224,7 +222,6 @@ function extractMultiCommandConfig( } const options = getOptions(command); - console.log('config', config); return E.tryCatch(() => { return yargs(extractFromProcessFn()) .config(config) diff --git a/src/core/changelog/generate-change-log.ts b/src/core/changelog/generate-change-log.ts index 7b8b8cec..b7414c1f 100644 --- a/src/core/changelog/generate-change-log.ts +++ b/src/core/changelog/generate-change-log.ts @@ -9,7 +9,7 @@ import { changelogTemplate } from './templates/changelog-template'; import { ReflectionErrors } from '../errors/errors'; import { apply } from '#utils/fp'; import { filterScope } from '../reflection/filter-scope'; -import { skip } from '../shared/utils'; +import { isApexType, skip } from '../shared/utils'; export type ChangeLogPageData = { content: string; @@ -52,7 +52,10 @@ export function generateChangeLog( function toManifests({ oldVersion, newVersion }: { oldVersion: ParsedFile[]; newVersion: ParsedFile[] }) { function parsedFilesToManifest(parsedFiles: ParsedFile[]): VersionManifest { return { - types: parsedFiles.map((parsedFile) => parsedFile.type), + types: parsedFiles + .map((parsedFile) => parsedFile.type) + // Changelog does not currently support object types + .filter((type) => isApexType(type)), }; } diff --git a/src/core/markdown/adapters/apex-types.ts b/src/core/markdown/adapters/apex-types.ts index 35708c37..d6c7e682 100644 --- a/src/core/markdown/adapters/apex-types.ts +++ b/src/core/markdown/adapters/apex-types.ts @@ -16,14 +16,17 @@ import { adaptConstructor, adaptMethod } from './methods-and-constructors'; import { adaptFieldOrProperty } from './fields-and-properties'; import { MarkdownGeneratorConfig } from '../generate-docs'; import { SourceFileMetadata } from '../../shared/types'; +import { ObjectMetadata } from '../../reflection/reflect-object-source'; -type GetReturnRenderable = T extends InterfaceMirror +type GetReturnRenderable = T extends InterfaceMirror ? RenderableInterface : T extends ClassMirror ? RenderableClass - : RenderableEnum; + : T extends EnumMirror + ? RenderableEnum + : never; // TODO: Implement renderable object -export function typeToRenderable( +export function typeToRenderable( parsedFile: { source: SourceFileMetadata; type: T }, linkGenerator: GetRenderableContentByTypeName, config: MarkdownGeneratorConfig, @@ -32,11 +35,13 @@ export function typeToRenderable( const { type } = parsedFile; switch (type.type_name) { case 'enum': - return enumTypeToEnumSource(type as EnumMirror, linkGenerator) as RenderableEnum; + return enumTypeToEnumSource(type as EnumMirror, linkGenerator); case 'interface': - return interfaceTypeToInterfaceSource(type as InterfaceMirror, linkGenerator) as RenderableInterface; + return interfaceTypeToInterfaceSource(type as InterfaceMirror, linkGenerator); case 'class': - return classTypeToClassSource(type as ClassMirrorWithInheritanceChain, linkGenerator) as RenderableClass; + return classTypeToClassSource(type as ClassMirrorWithInheritanceChain, linkGenerator); + case 'object': + throw new Error('Not implemented'); } } diff --git a/src/core/markdown/adapters/reference-guide.ts b/src/core/markdown/adapters/reference-guide.ts index 5e645cd7..774ab0d6 100644 --- a/src/core/markdown/adapters/reference-guide.ts +++ b/src/core/markdown/adapters/reference-guide.ts @@ -1,32 +1,27 @@ import { MarkdownGeneratorConfig } from '../generate-docs'; import { DocPageReference, ParsedFile } from '../../shared/types'; -import { Type } from '@cparra/apex-reflection'; +import { getTypeGroup } from '../../shared/utils'; export function parsedFilesToReferenceGuide( config: MarkdownGeneratorConfig, parsedFiles: ParsedFile[], ): Record { return parsedFiles.reduce>((acc, parsedFile) => { - acc[parsedFile.type.name] = parsedFileToDocPageReference(config, parsedFile); + acc[parsedFile.source.name] = parsedFileToDocPageReference(config, parsedFile); return acc; }, {}); } function parsedFileToDocPageReference(config: MarkdownGeneratorConfig, parsedFile: ParsedFile): DocPageReference { - const path = `${slugify(getTypeGroup(parsedFile.type, config))}/${parsedFile.type.name}.md`; + const path = `${slugify(getTypeGroup(parsedFile.type, config))}/${parsedFile.source.name}.md`; return { source: parsedFile.source, - displayName: parsedFile.type.name, + displayName: parsedFile.source.name, outputDocPath: path, referencePath: path, }; } -function getTypeGroup(type: Type, config: MarkdownGeneratorConfig): string { - const groupAnnotation = type.docComment?.annotations.find((annotation) => annotation.name.toLowerCase() === 'group'); - return groupAnnotation?.body ?? config.defaultGroupName; -} - function slugify(text: string): string { return text .toLowerCase() diff --git a/src/core/markdown/adapters/renderable-bundle.ts b/src/core/markdown/adapters/renderable-bundle.ts index 2b0e580d..0067f2ce 100644 --- a/src/core/markdown/adapters/renderable-bundle.ts +++ b/src/core/markdown/adapters/renderable-bundle.ts @@ -1,11 +1,19 @@ import { DocPageReference, ParsedFile } from '../../shared/types'; -import { Link, ReferenceGuideReference, Renderable, RenderableBundle } from '../../renderables/types'; +import { + Link, + ReferenceGuideReference, + Renderable, + RenderableBundle, + RenderableContent, +} from '../../renderables/types'; import { typeToRenderable } from './apex-types'; import { adaptDescribable } from '../../renderables/documentables'; import { MarkdownGeneratorConfig } from '../generate-docs'; import { apply } from '#utils/fp'; -import { Type } from '@cparra/apex-reflection'; import { generateLink } from './generate-link'; +import { getTypeGroup } from '../../shared/utils'; +import { Type } from '@cparra/apex-reflection'; +import { ObjectMetadata } from '../../reflection/reflect-object-source'; export function parsedFilesToRenderableBundle( config: MarkdownGeneratorConfig, @@ -46,16 +54,24 @@ function addToReferenceGuide( acc[group] = []; } acc[group].push({ - reference: references[parsedFile.type.name], - title: findLinkFromHome(parsedFile.type.name) as Link, - description: adaptDescribable(parsedFile.type.docComment?.descriptionLines, findLinkFromHome).description ?? null, + reference: references[parsedFile.source.name], + title: findLinkFromHome(parsedFile.source.name) as Link, + description: getRenderableDescription(parsedFile.type, findLinkFromHome), }); return acc; }; } -function getTypeGroup(type: Type, config: MarkdownGeneratorConfig): string { - const groupAnnotation = type.docComment?.annotations.find((annotation) => annotation.name.toLowerCase() === 'group'); - return groupAnnotation?.body ?? config.defaultGroupName; +function getRenderableDescription( + type: Type | ObjectMetadata, + findLinkFromHome: (referenceName: string) => string | Link, +): RenderableContent[] | null { + switch (type.type_name) { + case 'object': + // TODO + return null; + default: + return adaptDescribable(type.docComment?.descriptionLines, findLinkFromHome).description ?? null; + } } diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index 2dcb2ac6..1c91a751 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -62,7 +62,12 @@ export function generateDocs(unparsedApexFiles: UnparsedSourceFile[], config: Ma return pipe( generateForApex(filterApexSourceFiles(unparsedApexFiles), config), - TE.map((parsedApexFiles) => [...parsedApexFiles, ...generateForObject(filterObjectSourceFiles(unparsedApexFiles))]), + TE.chain((parsedApexFiles) => { + return pipe( + generateForObject(filterObjectSourceFiles(unparsedApexFiles)), + TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]), + ); + }), TE.map(sort), TE.bindTo('parsedFiles'), TE.bind('references', ({ parsedFiles }) => TE.right(convertToReferences(parsedFiles))), @@ -89,6 +94,7 @@ function generateForApex(apexBundles: UnparsedApexFile[], config: MarkdownGenera } function generateForObject(objectBundles: UnparsedObjectFile[]) { + // TODO: Filter out non public return pipe(objectBundles, reflectObjectSources); } diff --git a/src/core/markdown/utils.ts b/src/core/markdown/utils.ts index 06f54064..39df2217 100644 --- a/src/core/markdown/utils.ts +++ b/src/core/markdown/utils.ts @@ -1,3 +1,5 @@ import { ParsedFile } from '../shared/types'; +import { Type } from '@cparra/apex-reflection'; -export const parsedFilesToTypes = (parsedFiles: ParsedFile[]) => parsedFiles.map((parsedFile) => parsedFile.type); +export const parsedFilesToTypes = (parsedFiles: ParsedFile[]) => + parsedFiles.map((parsedFile) => parsedFile.type); diff --git a/src/core/reflection/__test__/helpers.ts b/src/core/reflection/__test__/helpers.ts index 15ca7a53..854200ec 100644 --- a/src/core/reflection/__test__/helpers.ts +++ b/src/core/reflection/__test__/helpers.ts @@ -1,7 +1,7 @@ -import { reflect } from '@cparra/apex-reflection'; +import { reflect, Type } from '@cparra/apex-reflection'; import { ParsedFile } from '../../shared/types'; -export function parsedFileFromRawString(raw: string): ParsedFile { +export function parsedFileFromRawString(raw: string): ParsedFile { const { error, typeMirror } = reflect(raw); if (error) { throw new Error(error.message); diff --git a/src/core/reflection/filter-scope.ts b/src/core/reflection/filter-scope.ts index bb9c153e..716db669 100644 --- a/src/core/reflection/filter-scope.ts +++ b/src/core/reflection/filter-scope.ts @@ -1,7 +1,8 @@ import Manifest from '../manifest'; import { ParsedFile } from '../shared/types'; +import { Type } from '@cparra/apex-reflection'; -export function filterScope(scopes: string[], parsedFiles: ParsedFile[]): ParsedFile[] { +export function filterScope(scopes: string[], parsedFiles: ParsedFile[]): ParsedFile[] { return parsedFiles .filter(({ type }) => Manifest.shouldFilterType(type, scopes)) .map((parsedFile) => { diff --git a/src/core/reflection/inheritance-chain-expanion.ts b/src/core/reflection/inheritance-chain-expanion.ts index 108b84a8..f79a32a9 100644 --- a/src/core/reflection/inheritance-chain-expanion.ts +++ b/src/core/reflection/inheritance-chain-expanion.ts @@ -3,7 +3,7 @@ import { createInheritanceChain } from './inheritance-chain'; import { parsedFilesToTypes } from '../markdown/utils'; import { ParsedFile } from '../shared/types'; -export const addInheritanceChainToTypes = (parsedFiles: ParsedFile[]): ParsedFile[] => +export const addInheritanceChainToTypes = (parsedFiles: ParsedFile[]): ParsedFile[] => parsedFiles.map((parsedFile) => ({ ...parsedFile, type: addInheritanceChain(parsedFile.type, parsedFilesToTypes(parsedFiles)), diff --git a/src/core/reflection/inherited-member-expansion.ts b/src/core/reflection/inherited-member-expansion.ts index 0cfba92a..521aed2e 100644 --- a/src/core/reflection/inherited-member-expansion.ts +++ b/src/core/reflection/inherited-member-expansion.ts @@ -3,10 +3,10 @@ import { pipe } from 'fp-ts/function'; import { ParsedFile } from '../shared/types'; import { parsedFilesToTypes } from '../markdown/utils'; -export const addInheritedMembersToTypes = (parsedFiles: ParsedFile[]) => +export const addInheritedMembersToTypes = (parsedFiles: ParsedFile[]) => parsedFiles.map((parsedFile) => addInheritedMembers(parsedFilesToTypes(parsedFiles), parsedFile)); -export function addInheritedMembers(repository: Type[], parsedFile: ParsedFile): ParsedFile { +export function addInheritedMembers(repository: Type[], parsedFile: ParsedFile): ParsedFile { function addInheritedMembersToType(repository: Type[], current: T): T { if (current.type_name === 'enum') { return current; diff --git a/src/core/reflection/reflect-object-source.ts b/src/core/reflection/reflect-object-source.ts index 9fe20df3..56c14c7c 100644 --- a/src/core/reflection/reflect-object-source.ts +++ b/src/core/reflection/reflect-object-source.ts @@ -10,6 +10,7 @@ import * as A from 'fp-ts/Array'; export type ObjectMetadata = { type_name: 'object'; label: string; + name: string; }; export function reflectObjectSources(objectSources: UnparsedObjectFile[]) { @@ -27,11 +28,23 @@ function reflectObjectSource(objectSource: UnparsedObjectFile): TE.TaskEither new XMLParser().parse(objectSource.content), (error) => new ReflectionErrors([new ReflectionError(objectSource.filePath, (error as Error).message)]), ), - TE.map((metadata) => addTypeName(metadata as ObjectMetadata)), + TE.map((metadata) => addName(metadata as ObjectMetadata, objectSource.filePath)), + TE.map((metadata) => addTypeName(metadata)), TE.map((metadata) => toParsedFile(objectSource.filePath, metadata)), ); } +function extractNameFromFilePath(filePath: string): string { + return filePath.split('/').pop()!.split('.').shift()!; +} + +function addName(objectMetadata: ObjectMetadata, filePath: string): ObjectMetadata { + return { + ...objectMetadata, + name: extractNameFromFilePath(filePath), + }; +} + function addTypeName(objectMetadata: ObjectMetadata): ObjectMetadata { return { ...objectMetadata, @@ -49,5 +62,3 @@ function toParsedFile(filePath: string, typeMirror: ObjectMetadata): ParsedFile type: typeMirror, }; } - -// TODO: Filter out non public diff --git a/src/core/reflection/reflect-source.ts b/src/core/reflection/reflect-source.ts index 181ae6db..1b3643a7 100644 --- a/src/core/reflection/reflect-source.ts +++ b/src/core/reflection/reflect-source.ts @@ -34,8 +34,8 @@ export function reflectApexSource(apexBundles: UnparsedApexFile[]) { return pipe(apexBundles, A.traverse(Ap)(reflectBundle)); } -function reflectBundle(apexBundle: UnparsedApexFile): TE.TaskEither { - const convertToParsedFile: (typeMirror: Type) => ParsedFile = apply(toParsedFile, apexBundle.filePath); +function reflectBundle(apexBundle: UnparsedApexFile): TE.TaskEither> { + const convertToParsedFile: (typeMirror: Type) => ParsedFile = apply(toParsedFile, apexBundle.filePath); const withMetadata = apply(addMetadata, apexBundle.metadataContent); return pipe(apexBundle, reflectAsTask, TE.map(convertToParsedFile), TE.flatMap(withMetadata)); @@ -49,7 +49,7 @@ function reflectAsTask(apexBundle: UnparsedApexFile): TE.TaskEither { return { source: { filePath: filePath, @@ -62,8 +62,8 @@ function toParsedFile(filePath: string, typeMirror: Type): ParsedFile { function addMetadata( rawMetadataContent: string | null, - parsedFile: ParsedFile, -): TE.TaskEither { + parsedFile: ParsedFile, +): TE.TaskEither> { return TE.fromEither( pipe( parsedFile.type, diff --git a/src/core/reflection/remove-excluded-tags.ts b/src/core/reflection/remove-excluded-tags.ts index 9c6a4b1d..620679b3 100644 --- a/src/core/reflection/remove-excluded-tags.ts +++ b/src/core/reflection/remove-excluded-tags.ts @@ -9,7 +9,7 @@ type AppliedRemoveTagFn = (tagName: string, removeFn: RemoveTagFn) => DocComment type RemoveTagFn = (docComment: DocComment) => DocComment; type Documentable = { docComment?: DocComment }; -export const removeExcludedTags = (excludedTags: string[], parsedFiles: ParsedFile[]): ParsedFile[] => { +export const removeExcludedTags = (excludedTags: string[], parsedFiles: ParsedFile[]): ParsedFile[] => { return parsedFiles.map((parsedFile) => { return { ...parsedFile, diff --git a/src/core/reflection/sort-types-and-members.ts b/src/core/reflection/sort-types-and-members.ts index 9c2e3e8e..f8271452 100644 --- a/src/core/reflection/sort-types-and-members.ts +++ b/src/core/reflection/sort-types-and-members.ts @@ -1,5 +1,6 @@ import { ClassMirror, EnumMirror, InterfaceMirror, Type } from '@cparra/apex-reflection'; import { ParsedFile } from '../shared/types'; +import { isApexType } from '../shared/utils'; type Named = { name: string }; @@ -7,7 +8,7 @@ export function sortTypesAndMembers(shouldSort: boolean, parsedFiles: ParsedFile return parsedFiles .map((parsedFile) => ({ ...parsedFile, - type: sortTypeMember(parsedFile.type, shouldSort), + type: isApexType(parsedFile.type) ? sortTypeMember(parsedFile.type, shouldSort) : parsedFile.type, })) .sort((a, b) => sortByNames(shouldSort, a.type, b.type)); } diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index 981fbbca..948b5be8 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -76,9 +76,9 @@ export type SourceFileMetadata = { type: 'interface' | 'class' | 'enum' | 'object'; }; -export type ParsedFile = { +export type ParsedFile = { source: SourceFileMetadata; - type: Type | ObjectMetadata; + type: T; }; export type DocPageReference = { @@ -95,7 +95,7 @@ export type DocPageReference = { referencePath: string; }; -type Frontmatter = string | Record | null; +export type Frontmatter = string | Record | null; export type ReferenceGuidePageData = { frontmatter: Frontmatter; diff --git a/src/core/shared/utils.ts b/src/core/shared/utils.ts index 233b0899..d085f799 100644 --- a/src/core/shared/utils.ts +++ b/src/core/shared/utils.ts @@ -1,4 +1,7 @@ import { Skip } from './types'; +import { Type } from '@cparra/apex-reflection'; +import { ObjectMetadata } from '../reflection/reflect-object-source'; +import { MarkdownGeneratorConfig } from '../markdown/generate-docs'; /** * Represents a file to be skipped. @@ -12,3 +15,27 @@ export function skip(): Skip { export function isSkip(value: unknown): value is Skip { return Object.prototype.hasOwnProperty.call(value, '_tag') && (value as Skip)._tag === 'Skip'; } + +export function isObjectType(type: Type | ObjectMetadata): type is ObjectMetadata { + return (type as ObjectMetadata).type_name === 'object'; +} + +export function isApexType(type: Type | ObjectMetadata): type is Type { + return !isObjectType(type); +} + +export function getTypeGroup(type: Type | ObjectMetadata, config: MarkdownGeneratorConfig): string { + function getGroup(type: Type, config: MarkdownGeneratorConfig): string { + const groupAnnotation = type.docComment?.annotations.find( + (annotation) => annotation.name.toLowerCase() === 'group', + ); + return groupAnnotation?.body ?? config.defaultGroupName; + } + + switch (type.type_name) { + case 'object': + return 'Objects'; // TODO: Make configurable? + default: + return getGroup(type, config); + } +} From abc3f6cf1ca73636a85804489b7e149a70ae20d2 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Thu, 17 Oct 2024 07:03:10 -0400 Subject: [PATCH 05/32] Removing duplicate function declaration --- src/core/shared/utils.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/core/shared/utils.ts b/src/core/shared/utils.ts index 4df78baf..d085f799 100644 --- a/src/core/shared/utils.ts +++ b/src/core/shared/utils.ts @@ -12,15 +12,6 @@ export function skip(): Skip { }; } -/** - * Represents a file to be skipped. - */ -export function skip(): Skip { - return { - _tag: 'Skip', - }; -} - export function isSkip(value: unknown): value is Skip { return Object.prototype.hasOwnProperty.call(value, '_tag') && (value as Skip)._tag === 'Skip'; } From f68946c30bcb20e05f9a63787a3ee8969f6c5c77 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Thu, 17 Oct 2024 14:06:18 -0400 Subject: [PATCH 06/32] Refactoring into generics to avoid using "as" --- src/application/Apexdocs.ts | 16 +++---- .../__tests__/source-code-file-reader.spec.ts | 12 ++--- src/application/source-code-file-reader.ts | 45 +++++++++---------- src/core/shared/types.d.ts | 2 +- 4 files changed, 36 insertions(+), 39 deletions(-) diff --git a/src/application/Apexdocs.ts b/src/application/Apexdocs.ts index 2f1ecd31..f199bc1a 100644 --- a/src/application/Apexdocs.ts +++ b/src/application/Apexdocs.ts @@ -18,7 +18,6 @@ import { } from '../core/shared/types'; import { ReflectionError, ReflectionErrors, HookError } from '../core/errors/errors'; import { FileReadingError, FileWritingError } from './errors'; -import { apply } from '#utils/fp'; /** * Application entry-point to generate documentation out of Apex source files. @@ -46,32 +45,31 @@ export class Apexdocs { } } -const readFiles = apply(processFiles, new DefaultFileSystem()); +const readFiles = processFiles(new DefaultFileSystem()); async function processMarkdown(config: UserDefinedMarkdownConfig) { + // TODO: Also process Object files return pipe( TE.tryCatch( - () => readFiles(config.sourceDir, config.exclude, [processApexFiles(config.includeMetadata)]), + () => readFiles([processApexFiles(config.includeMetadata)])(config.sourceDir, config.exclude), (e) => new FileReadingError('An error occurred while reading files.', e), ), - TE.flatMap((fileBodies) => markdown(fileBodies as UnparsedApexFile[], config)), + TE.flatMap((fileBodies) => markdown(fileBodies, config)), TE.map(() => '✔️ Documentation generated successfully!'), TE.mapLeft(toErrors), ); } async function processOpenApi(config: UserDefinedOpenApiConfig, logger: Logger) { - const fileBodies = (await readFiles(config.sourceDir, config.exclude, [ - processApexFiles(false), - ])) as UnparsedApexFile[]; + const fileBodies = await readFiles([processApexFiles(false)])(config.sourceDir, config.exclude); return openApi(logger, fileBodies, config); } async function processChangeLog(config: UserDefinedChangelogConfig) { async function loadFiles(): Promise<[UnparsedApexFile[], UnparsedApexFile[]]> { return [ - (await readFiles(config.previousVersionDir, config.exclude, [processApexFiles(false)])) as UnparsedApexFile[], - (await readFiles(config.currentVersionDir, config.exclude, [processApexFiles(false)])) as UnparsedApexFile[], + await readFiles([processApexFiles(false)])(config.previousVersionDir, config.exclude), + await readFiles([processApexFiles(false)])(config.currentVersionDir, config.exclude), ]; } diff --git a/src/application/__tests__/source-code-file-reader.spec.ts b/src/application/__tests__/source-code-file-reader.spec.ts index 84f122d3..c4485c06 100644 --- a/src/application/__tests__/source-code-file-reader.spec.ts +++ b/src/application/__tests__/source-code-file-reader.spec.ts @@ -70,7 +70,7 @@ describe('File Reader', () => { }, ]); - const result = await processFiles(fileSystem, '', [], [processApexFiles(false)]); + const result = await processFiles(fileSystem)([processApexFiles(false)])('', []); expect(result.length).toBe(0); }); @@ -90,7 +90,7 @@ describe('File Reader', () => { }, ]); - const result = await processFiles(fileSystem, '', [], [processApexFiles(false)]); + const result = await processFiles(fileSystem)([processApexFiles(false)])('', []); expect(result.length).toBe(0); }); @@ -120,7 +120,7 @@ describe('File Reader', () => { }, ]); - const result = await processFiles(fileSystem, '', [], [processApexFiles(false)]); + const result = await processFiles(fileSystem)([processApexFiles(false)])('', []); expect(result.length).toBe(2); expect(result[0].content).toBe('public class MyClass{}'); expect(result[1].content).toBe('public class AnotherClass{}'); @@ -150,7 +150,7 @@ describe('File Reader', () => { }, ]); - const result = await processFiles(fileSystem, '', [], [processObjectFiles()]); + const result = await processFiles(fileSystem)([processObjectFiles()])('', []); expect(result.length).toBe(1); expect(result[0].content).toBe(objectContent); }); @@ -181,7 +181,7 @@ describe('File Reader', () => { }, ]); - const result = await processFiles(fileSystem, '', ['**/AnotherFile.cls'], [processApexFiles(false)]); + const result = await processFiles(fileSystem)([processApexFiles(false)])('', ['**/AnotherFile.cls']); expect(result.length).toBe(1); expect(result[0].content).toBe('public class MyClass{}'); }); @@ -234,7 +234,7 @@ describe('File Reader', () => { }, ]); - const result = await processFiles(fileSystem, '', [], [processApexFiles(false)]); + const result = await processFiles(fileSystem)([processApexFiles(false)])('', []); expect(result.length).toBe(4); }); }); diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts index d1643198..2bef185e 100644 --- a/src/application/source-code-file-reader.ts +++ b/src/application/source-code-file-reader.ts @@ -1,30 +1,29 @@ import { FileSystem } from './file-system'; -import { UnparsedApexFile, UnparsedSourceFile } from '../core/shared/types'; +import { UnparsedApexFile, UnparsedObjectFile } from '../core/shared/types'; import { minimatch } from 'minimatch'; import { pipe } from 'fp-ts/function'; /** * Reads from .cls files and returns their raw body. */ -export async function processFiles( - fileSystem: FileSystem, - rootPath: string, - exclude: string[], - processors: FileProcessor[], -): Promise { - return pipe( - await getFilePaths(fileSystem, rootPath), - (filePaths) => filePaths.filter((filePath) => !isExcluded(filePath, exclude)), - (filteredFilePaths) => readFiles(fileSystem, filteredFilePaths, processors), - ); +export function processFiles(fileSystem: FileSystem) { + return function (processors: FileProcessor[]) { + return async function (rootPath: string, exclude: string[]): Promise { + return pipe( + await getFilePaths(fileSystem, rootPath), + (filePaths) => filePaths.filter((filePath) => !isExcluded(filePath, exclude)), + (filteredFilePaths) => readFiles(fileSystem, filteredFilePaths, processors), + ); + }; + }; } -async function readFiles( +async function readFiles( fileSystem: FileSystem, filePaths: string[], - processors: FileProcessor[], -): Promise { - const files: UnparsedSourceFile[] = []; + processors: FileProcessor[], +): Promise { + const files: T[] = []; for (const filePath of filePaths) { const processor = processors.find((p) => p.isSupportedFile(filePath)); if (processor) { @@ -52,16 +51,16 @@ function isExcluded(filePath: string, exclude: string[]): boolean { return exclude.some((pattern) => minimatch(filePath, pattern)); } -interface FileProcessor { +interface FileProcessor { isSupportedFile: (currentFile: string) => boolean; - process: (fileSystem: FileSystem, filePath: string) => Promise; + process: (fileSystem: FileSystem, filePath: string) => Promise; } -export function processApexFiles(includeMetadata: boolean): FileProcessor { +export function processApexFiles(includeMetadata: boolean): FileProcessor { return new ApexFileReader(includeMetadata); } -class ApexFileReader implements FileProcessor { +class ApexFileReader implements FileProcessor { APEX_FILE_EXTENSION = '.cls'; constructor(public includeMetadata: boolean) {} @@ -82,18 +81,18 @@ class ApexFileReader implements FileProcessor { } } -export function processObjectFiles(): FileProcessor { +export function processObjectFiles(): FileProcessor { return new ObjectFileReader(); } -class ObjectFileReader implements FileProcessor { +class ObjectFileReader implements FileProcessor { OBJECT_FILE_EXTENSION = '.object-meta.xml'; isSupportedFile(currentFile: string): boolean { return currentFile.endsWith(this.OBJECT_FILE_EXTENSION); } - async process(fileSystem: FileSystem, filePath: string): Promise { + async process(fileSystem: FileSystem, filePath: string): Promise { const rawTypeContent = await fileSystem.readFile(filePath); return { type: 'object', filePath, content: rawTypeContent }; } diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index 948b5be8..acef98be 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -55,7 +55,7 @@ export type UserDefinedChangelogConfig = { export type UserDefinedConfig = UserDefinedMarkdownConfig | UserDefinedOpenApiConfig | UserDefinedChangelogConfig; -export type UnparsedSourceFile = UnparsedObjectFile | UnparsedApexFile; +export type UnparsedSourceFile = UnparsedApexFile | UnparsedObjectFile; export type UnparsedObjectFile = { type: 'object'; From 1c5906f310d235e49fc501b218b9702140cca51e Mon Sep 17 00:00:00 2001 From: cesarParra Date: Thu, 17 Oct 2024 14:08:57 -0400 Subject: [PATCH 07/32] Refactoring to "bundle" --- src/application/Apexdocs.ts | 4 +- src/application/generators/changelog.ts | 6 +- src/application/generators/markdown.ts | 6 +- src/application/generators/openapi.ts | 6 +- src/application/source-code-file-reader.ts | 20 +++--- .../__test__/generating-change-log.spec.ts | 66 +++++++++---------- src/core/changelog/generate-change-log.ts | 8 +-- .../__test__/generating-class-docs.spec.ts | 49 +++++++------- .../markdown/__test__/generating-docs.spec.ts | 63 ++++++++++-------- .../__test__/generating-enum-docs.spec.ts | 8 +-- .../generating-interface-docs.spec.ts | 25 +++---- .../generating-reference-guide.spec.ts | 31 ++++++--- src/core/markdown/__test__/test-helpers.ts | 6 +- src/core/markdown/generate-docs.ts | 20 +++--- src/core/openapi/manifest-factory.ts | 4 +- src/core/openapi/parser.ts | 8 +-- src/core/reflection/reflect-object-source.ts | 6 +- src/core/reflection/reflect-source.ts | 8 +-- src/core/shared/types.d.ts | 6 +- 19 files changed, 190 insertions(+), 160 deletions(-) diff --git a/src/application/Apexdocs.ts b/src/application/Apexdocs.ts index f199bc1a..30144791 100644 --- a/src/application/Apexdocs.ts +++ b/src/application/Apexdocs.ts @@ -10,7 +10,7 @@ import { processApexFiles, processFiles } from './source-code-file-reader'; import { DefaultFileSystem } from './file-system'; import { Logger } from '#utils/logger'; import { - UnparsedApexFile, + UnparsedApexBundle, UserDefinedChangelogConfig, UserDefinedConfig, UserDefinedMarkdownConfig, @@ -66,7 +66,7 @@ async function processOpenApi(config: UserDefinedOpenApiConfig, logger: Logger) } async function processChangeLog(config: UserDefinedChangelogConfig) { - async function loadFiles(): Promise<[UnparsedApexFile[], UnparsedApexFile[]]> { + async function loadFiles(): Promise<[UnparsedApexBundle[], UnparsedApexBundle[]]> { return [ await readFiles([processApexFiles(false)])(config.previousVersionDir, config.exclude), await readFiles([processApexFiles(false)])(config.currentVersionDir, config.exclude), diff --git a/src/application/generators/changelog.ts b/src/application/generators/changelog.ts index 4b944705..d72dd1f9 100644 --- a/src/application/generators/changelog.ts +++ b/src/application/generators/changelog.ts @@ -1,5 +1,5 @@ import { pipe } from 'fp-ts/function'; -import { PageData, Skip, UnparsedApexFile, UserDefinedChangelogConfig } from '../../core/shared/types'; +import { PageData, Skip, UnparsedApexBundle, UserDefinedChangelogConfig } from '../../core/shared/types'; import * as TE from 'fp-ts/TaskEither'; import { writeFiles } from '../file-writer'; import { ChangeLogPageData, generateChangeLog } from '../../core/changelog/generate-change-log'; @@ -7,8 +7,8 @@ import { FileWritingError } from '../errors'; import { isSkip } from '../../core/shared/utils'; export default function generate( - oldBundles: UnparsedApexFile[], - newBundles: UnparsedApexFile[], + oldBundles: UnparsedApexBundle[], + newBundles: UnparsedApexBundle[], config: UserDefinedChangelogConfig, ) { function handleFile(file: ChangeLogPageData | Skip) { diff --git a/src/application/generators/markdown.ts b/src/application/generators/markdown.ts index 31300ef2..0b048610 100644 --- a/src/application/generators/markdown.ts +++ b/src/application/generators/markdown.ts @@ -3,7 +3,7 @@ import { pipe } from 'fp-ts/function'; import { PageData, PostHookDocumentationBundle, - UnparsedApexFile, + UnparsedApexBundle, UserDefinedMarkdownConfig, } from '../../core/shared/types'; import { referenceGuideTemplate } from '../../core/markdown/templates/reference-guide'; @@ -12,14 +12,14 @@ import { isSkip } from '../../core/shared/utils'; import { writeFiles } from '../file-writer'; import { FileWritingError } from '../errors'; -export default function generate(bundles: UnparsedApexFile[], config: UserDefinedMarkdownConfig) { +export default function generate(bundles: UnparsedApexBundle[], config: UserDefinedMarkdownConfig) { return pipe( generateDocumentationBundle(bundles, config), TE.flatMap((files) => writeFilesToSystem(files, config.targetDir)), ); } -function generateDocumentationBundle(bundles: UnparsedApexFile[], config: UserDefinedMarkdownConfig) { +function generateDocumentationBundle(bundles: UnparsedApexBundle[], config: UserDefinedMarkdownConfig) { return generateDocs(bundles, { ...config, referenceGuideTemplate: referenceGuideTemplate, diff --git a/src/application/generators/openapi.ts b/src/application/generators/openapi.ts index 2b1e6670..0e145d02 100644 --- a/src/application/generators/openapi.ts +++ b/src/application/generators/openapi.ts @@ -6,7 +6,7 @@ import { Logger } from '#utils/logger'; import ErrorLogger from '#utils/error-logger'; import { reflect, ReflectionResult } from '@cparra/apex-reflection'; import Manifest from '../../core/manifest'; -import { PageData, UnparsedApexFile, UserDefinedOpenApiConfig } from '../../core/shared/types'; +import { PageData, UnparsedApexBundle, UserDefinedOpenApiConfig } from '../../core/shared/types'; import { OpenApiDocsProcessor } from '../../core/openapi/open-api-docs-processor'; import { writeFiles } from '../file-writer'; import { pipe } from 'fp-ts/function'; @@ -16,7 +16,7 @@ import { apply } from '#utils/fp'; export default async function openApi( logger: Logger, - fileBodies: UnparsedApexFile[], + fileBodies: UnparsedApexBundle[], config: UserDefinedOpenApiConfig, ) { OpenApiSettings.build({ @@ -46,7 +46,7 @@ export default async function openApi( ErrorLogger.logErrors(logger, filteredTypes); } -function reflectionWithLogger(logger: Logger, apexBundle: UnparsedApexFile): ReflectionResult { +function reflectionWithLogger(logger: Logger, apexBundle: UnparsedApexBundle): ReflectionResult { const result = reflect(apexBundle.content); if (result.error) { logger.error(`${apexBundle.filePath} - Parsing error ${result.error?.message}`); diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts index 2bef185e..51dc4bc1 100644 --- a/src/application/source-code-file-reader.ts +++ b/src/application/source-code-file-reader.ts @@ -1,5 +1,5 @@ import { FileSystem } from './file-system'; -import { UnparsedApexFile, UnparsedObjectFile } from '../core/shared/types'; +import { UnparsedApexBundle, UnparsedObjectBundle } from '../core/shared/types'; import { minimatch } from 'minimatch'; import { pipe } from 'fp-ts/function'; @@ -7,7 +7,7 @@ import { pipe } from 'fp-ts/function'; * Reads from .cls files and returns their raw body. */ export function processFiles(fileSystem: FileSystem) { - return function (processors: FileProcessor[]) { + return function (processors: FileProcessor[]) { return async function (rootPath: string, exclude: string[]): Promise { return pipe( await getFilePaths(fileSystem, rootPath), @@ -18,7 +18,7 @@ export function processFiles(fileSystem: FileSystem) { }; } -async function readFiles( +async function readFiles( fileSystem: FileSystem, filePaths: string[], processors: FileProcessor[], @@ -51,16 +51,16 @@ function isExcluded(filePath: string, exclude: string[]): boolean { return exclude.some((pattern) => minimatch(filePath, pattern)); } -interface FileProcessor { +interface FileProcessor { isSupportedFile: (currentFile: string) => boolean; process: (fileSystem: FileSystem, filePath: string) => Promise; } -export function processApexFiles(includeMetadata: boolean): FileProcessor { +export function processApexFiles(includeMetadata: boolean): FileProcessor { return new ApexFileReader(includeMetadata); } -class ApexFileReader implements FileProcessor { +class ApexFileReader implements FileProcessor { APEX_FILE_EXTENSION = '.cls'; constructor(public includeMetadata: boolean) {} @@ -69,7 +69,7 @@ class ApexFileReader implements FileProcessor { return currentFile.endsWith(this.APEX_FILE_EXTENSION); } - async process(fileSystem: FileSystem, filePath: string): Promise { + async process(fileSystem: FileSystem, filePath: string): Promise { const rawTypeContent = await fileSystem.readFile(filePath); const metadataPath = `${filePath}-meta.xml`; let rawMetadataContent = null; @@ -81,18 +81,18 @@ class ApexFileReader implements FileProcessor { } } -export function processObjectFiles(): FileProcessor { +export function processObjectFiles(): FileProcessor { return new ObjectFileReader(); } -class ObjectFileReader implements FileProcessor { +class ObjectFileReader implements FileProcessor { OBJECT_FILE_EXTENSION = '.object-meta.xml'; isSupportedFile(currentFile: string): boolean { return currentFile.endsWith(this.OBJECT_FILE_EXTENSION); } - async process(fileSystem: FileSystem, filePath: string): Promise { + async process(fileSystem: FileSystem, filePath: string): Promise { const rawTypeContent = await fileSystem.readFile(filePath); return { type: 'object', filePath, content: rawTypeContent }; } diff --git a/src/core/changelog/__test__/generating-change-log.spec.ts b/src/core/changelog/__test__/generating-change-log.spec.ts index 4924f586..b3ff6d05 100644 --- a/src/core/changelog/__test__/generating-change-log.spec.ts +++ b/src/core/changelog/__test__/generating-change-log.spec.ts @@ -1,4 +1,4 @@ -import { UnparsedApexFile } from '../../shared/types'; +import { UnparsedApexBundle } from '../../shared/types'; import { ChangeLogPageData, generateChangeLog } from '../generate-change-log'; import { assertEither } from '../../test-helpers/assert-either'; import { isSkip } from '../../shared/utils'; @@ -34,8 +34,8 @@ describe('when generating a changelog', () => { describe('that does not include new classes', () => { it('should not have a section for new classes', async () => { - const oldBundle: UnparsedApexFile[] = []; - const newBundle: UnparsedApexFile[] = []; + const oldBundle: UnparsedApexBundle[] = []; + const newBundle: UnparsedApexBundle[] = []; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -47,8 +47,8 @@ describe('when generating a changelog', () => { it('should include a section for new classes', async () => { const newClassSource = 'class Test {}'; - const oldBundle: UnparsedApexFile[] = []; - const newBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = []; + const newBundle: UnparsedApexBundle[] = [ { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; @@ -60,8 +60,8 @@ describe('when generating a changelog', () => { it('should include the new class name', async () => { const newClassSource = 'class Test {}'; - const oldBundle: UnparsedApexFile[] = []; - const newBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = []; + const newBundle: UnparsedApexBundle[] = [ { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; @@ -78,8 +78,8 @@ describe('when generating a changelog', () => { class Test {} `; - const oldBundle: UnparsedApexFile[] = []; - const newBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = []; + const newBundle: UnparsedApexBundle[] = [ { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; @@ -93,8 +93,8 @@ describe('when generating a changelog', () => { it('should include a section for new interfaces', async () => { const newInterfaceSource = 'interface Test {}'; - const oldBundle: UnparsedApexFile[] = []; - const newBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = []; + const newBundle: UnparsedApexBundle[] = [ { type: 'apex', content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, ]; @@ -106,8 +106,8 @@ describe('when generating a changelog', () => { it('should include the new interface name', async () => { const newInterfaceSource = 'interface Test {}'; - const oldBundle: UnparsedApexFile[] = []; - const newBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = []; + const newBundle: UnparsedApexBundle[] = [ { type: 'apex', content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, ]; @@ -124,8 +124,8 @@ describe('when generating a changelog', () => { interface Test {} `; - const oldBundle: UnparsedApexFile[] = []; - const newBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = []; + const newBundle: UnparsedApexBundle[] = [ { type: 'apex', content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, ]; @@ -141,8 +141,8 @@ describe('when generating a changelog', () => { it('should include a section for new enums', async () => { const newEnumSource = 'enum Test {}'; - const oldBundle: UnparsedApexFile[] = []; - const newBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = []; + const newBundle: UnparsedApexBundle[] = [ { type: 'apex', content: newEnumSource, filePath: 'Test.cls', metadataContent: null }, ]; @@ -154,8 +154,8 @@ describe('when generating a changelog', () => { it('should include the new enum name', async () => { const newEnumSource = 'enum Test {}'; - const oldBundle: UnparsedApexFile[] = []; - const newBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = []; + const newBundle: UnparsedApexBundle[] = [ { type: 'apex', content: newEnumSource, filePath: 'Test.cls', metadataContent: null }, ]; @@ -172,8 +172,8 @@ describe('when generating a changelog', () => { enum Test {} `; - const oldBundle: UnparsedApexFile[] = []; - const newBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = []; + const newBundle: UnparsedApexBundle[] = [ { type: 'apex', content: newEnumSource, filePath: 'Test.cls', metadataContent: null }, ]; @@ -187,8 +187,8 @@ describe('when generating a changelog', () => { it('should not include them', async () => { const newClassSource = 'class Test {}'; - const oldBundle: UnparsedApexFile[] = []; - const newBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = []; + const newBundle: UnparsedApexBundle[] = [ { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; @@ -202,10 +202,10 @@ describe('when generating a changelog', () => { it('should include a section for removed types', async () => { const oldClassSource = 'class Test {}'; - const oldBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = [ { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; - const newBundle: UnparsedApexFile[] = []; + const newBundle: UnparsedApexBundle[] = []; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -215,10 +215,10 @@ describe('when generating a changelog', () => { it('should include the removed type name', async () => { const oldClassSource = 'class Test {}'; - const oldBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = [ { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; - const newBundle: UnparsedApexFile[] = []; + const newBundle: UnparsedApexBundle[] = []; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -231,11 +231,11 @@ describe('when generating a changelog', () => { const oldClassSource = 'class Test {}'; const newClassSource = 'class Test { void myMethod() {} }'; - const oldBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = [ { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; - const newBundle: UnparsedApexFile[] = [ + const newBundle: UnparsedApexBundle[] = [ { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; @@ -250,11 +250,11 @@ describe('when generating a changelog', () => { const oldClassSource = 'class Test {}'; const newClassSource = 'class Test { void myMethod() {} }'; - const oldBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = [ { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; - const newBundle: UnparsedApexFile[] = [ + const newBundle: UnparsedApexBundle[] = [ { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; @@ -267,11 +267,11 @@ describe('when generating a changelog', () => { const oldClassSource = 'class Test {}'; const newClassSource = 'class Test { void myMethod() {} }'; - const oldBundle: UnparsedApexFile[] = [ + const oldBundle: UnparsedApexBundle[] = [ { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; - const newBundle: UnparsedApexFile[] = [ + const newBundle: UnparsedApexBundle[] = [ { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; diff --git a/src/core/changelog/generate-change-log.ts b/src/core/changelog/generate-change-log.ts index b7414c1f..b0bb05c6 100644 --- a/src/core/changelog/generate-change-log.ts +++ b/src/core/changelog/generate-change-log.ts @@ -1,4 +1,4 @@ -import { ParsedFile, Skip, UnparsedApexFile, UserDefinedChangelogConfig } from '../shared/types'; +import { ParsedFile, Skip, UnparsedApexBundle, UserDefinedChangelogConfig } from '../shared/types'; import { pipe } from 'fp-ts/function'; import * as TE from 'fp-ts/TaskEither'; import { reflectApexSource } from '../reflection/reflect-source'; @@ -17,13 +17,13 @@ export type ChangeLogPageData = { }; export function generateChangeLog( - oldBundles: UnparsedApexFile[], - newBundles: UnparsedApexFile[], + oldBundles: UnparsedApexBundle[], + newBundles: UnparsedApexBundle[], config: Omit, ): TE.TaskEither { const filterOutOfScope = apply(filterScope, config.scope); - function reflect(sourceFiles: UnparsedApexFile[]) { + function reflect(sourceFiles: UnparsedApexBundle[]) { return pipe(reflectApexSource(sourceFiles), TE.map(filterOutOfScope)); } diff --git a/src/core/markdown/__test__/generating-class-docs.spec.ts b/src/core/markdown/__test__/generating-class-docs.spec.ts index 9c4ab2c5..56ad9a36 100644 --- a/src/core/markdown/__test__/generating-class-docs.spec.ts +++ b/src/core/markdown/__test__/generating-class-docs.spec.ts @@ -1,5 +1,5 @@ import { extendExpect } from './expect-extensions'; -import { apexBundleFromRawString, generateDocs } from './test-helpers'; +import { unparsedApexBundleFromRawString, generateDocs } from './test-helpers'; import { assertEither } from '../../test-helpers/assert-either'; describe('When generating documentation for a class', () => { @@ -15,7 +15,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('## Methods')); }); @@ -28,7 +28,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: true })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: true })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { const aMethodIndex = data.docs[0].content.indexOf('aMethod'); @@ -45,7 +45,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: false })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: false })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { const aMethodIndex = data.docs[0].content.indexOf('aMethod'); @@ -61,7 +61,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('## Properties')); }); @@ -74,7 +74,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: true })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: true })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { const aPropertyIndex = data.docs[0].content.indexOf('aProperty'); @@ -91,7 +91,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: false })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: false })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { const aPropertyIndex = data.docs[0].content.indexOf('aProperty'); @@ -107,7 +107,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('## Fields')); }); @@ -120,7 +120,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: true })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: true })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { const aFieldIndex = data.docs[0].content.indexOf('aField'); @@ -137,7 +137,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: false })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: false })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { const aFieldIndex = data.docs[0].content.indexOf('aField'); @@ -153,7 +153,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('## Constructors')); }); @@ -165,7 +165,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('## Classes')); }); @@ -178,7 +178,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: true })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: true })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { const aInnerClassIndex = data.docs[0].content.indexOf('AInnerClass'); @@ -195,7 +195,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: false })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: false })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { const aInnerClassIndex = data.docs[0].content.indexOf('AInnerClass'); @@ -211,7 +211,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('## Interfaces')); }); @@ -224,7 +224,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: true })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: true })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { const aInnerInterfaceIndex = data.docs[0].content.indexOf('AInnerInterface'); @@ -241,7 +241,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: false })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: false })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { const aInnerInterfaceIndex = data.docs[0].content.indexOf('AInnerInterface'); @@ -257,7 +257,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('## Enums')); }); @@ -270,7 +270,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: true })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: true })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { const aInnerEnumIndex = data.docs[0].content.indexOf('AInnerEnum'); @@ -287,7 +287,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: false })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: false })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { const aInnerEnumIndex = data.docs[0].content.indexOf('AInnerEnum'); @@ -313,7 +313,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('```mermaid')); assertEither(result, (data) => expect(data).firstDocContains('graph TD')); @@ -334,7 +334,7 @@ describe('When generating documentation for a class', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('```apex')); assertEither(result, (data) => expect(data).firstDocContains('public class MyClass')); @@ -351,7 +351,10 @@ describe('When generating documentation for a class', () => { public class AnotherClass extends MyClass {} `; - const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])(); + const result = await generateDocs([ + unparsedApexBundleFromRawString(input1), + unparsedApexBundleFromRawString(input2), + ])(); expect(result).documentationBundleHasLength(2); assertEither(result, (data) => expect(data.docs.find((doc) => doc.source.name === 'AnotherClass')?.content).toContain('Inherited'), diff --git a/src/core/markdown/__test__/generating-docs.spec.ts b/src/core/markdown/__test__/generating-docs.spec.ts index 769300f4..f8f081cb 100644 --- a/src/core/markdown/__test__/generating-docs.spec.ts +++ b/src/core/markdown/__test__/generating-docs.spec.ts @@ -1,6 +1,6 @@ import { DocPageData, PostHookDocumentationBundle } from '../../shared/types'; import { extendExpect } from './expect-extensions'; -import { apexBundleFromRawString, generateDocs } from './test-helpers'; +import { unparsedApexBundleFromRawString, generateDocs } from './test-helpers'; import { assertEither } from '../../test-helpers/assert-either'; function aSingleDoc(result: PostHookDocumentationBundle): DocPageData { @@ -22,7 +22,7 @@ describe('When generating documentation', () => { ]; for (const [input, expected] of properties) { - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); assertEither(result, (data) => expect(aSingleDoc(data).outputDocPath).toContain(expected)); } }); @@ -35,7 +35,7 @@ describe('When generating documentation', () => { ]; for (const [input, expected] of properties) { - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); assertEither(result, (data) => expect(aSingleDoc(data).outputDocPath).toContain(expected)); } }); @@ -66,7 +66,7 @@ describe('When generating documentation', () => { ]; for (const [input, expected] of properties) { - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); assertEither(result, (data) => expect(aSingleDoc(data).outputDocPath).toContain(expected)); } }); @@ -81,7 +81,7 @@ describe('When generating documentation', () => { ]; for (const [input, expected] of properties) { - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); assertEither(result, (data) => expect(aSingleDoc(data).source.type).toBe(expected)); } }); @@ -90,9 +90,12 @@ describe('When generating documentation', () => { const input1 = 'global class MyClass {}'; const input2 = 'public class AnotherClass {}'; - const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)], { - scope: ['global'], - })(); + const result = await generateDocs( + [unparsedApexBundleFromRawString(input1), unparsedApexBundleFromRawString(input2)], + { + scope: ['global'], + }, + )(); expect(result).documentationBundleHasLength(1); }); @@ -103,7 +106,7 @@ describe('When generating documentation', () => { */ public class MyClass {}`; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(0); }); }); @@ -117,7 +120,7 @@ describe('When generating documentation', () => { ]; for (const [input, expected] of properties) { - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains(expected)); @@ -133,7 +136,7 @@ describe('When generating documentation', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('NAMESPACEACCESSIBLE')); @@ -150,7 +153,7 @@ describe('When generating documentation', () => { `; - const result = await generateDocs([apexBundleFromRawString(input, metadata)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input, metadata)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('APIVERSION')); @@ -165,7 +168,7 @@ describe('When generating documentation', () => { public class MyClass {} `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('This is a description')); @@ -178,7 +181,7 @@ describe('When generating documentation', () => { */ public class MyClass {}`; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('This is a description')); }); @@ -191,7 +194,7 @@ describe('When generating documentation', () => { public class MyClass {} `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('Custom Tag')); assertEither(result, (data) => expect(data).firstDocContains('My Value')); @@ -204,7 +207,7 @@ describe('When generating documentation', () => { */ public class MyClass {}`; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('Group')); assertEither(result, (data) => expect(data).firstDocContains('MyGroup')); @@ -217,7 +220,7 @@ describe('When generating documentation', () => { */ public class MyClass {}`; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('Author')); assertEither(result, (data) => expect(data).firstDocContains('John Doe')); @@ -230,7 +233,7 @@ describe('When generating documentation', () => { */ public class MyClass {}`; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('Date')); assertEither(result, (data) => expect(data).firstDocContains('2021-01-01')); @@ -246,7 +249,10 @@ describe('When generating documentation', () => { const input2 = 'public class ClassRef {}'; - const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])(); + const result = await generateDocs([ + unparsedApexBundleFromRawString(input1), + unparsedApexBundleFromRawString(input2), + ])(); expect(result).documentationBundleHasLength(2); assertEither(result, (data) => expect(data).firstDocContains('This is a description with a [ClassRef](ClassRef.md) reference'), @@ -261,7 +267,7 @@ describe('When generating documentation', () => { public class MyClass {} `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains( @@ -280,7 +286,10 @@ describe('When generating documentation', () => { const input2 = 'public class ClassRef {}'; - const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])(); + const result = await generateDocs([ + unparsedApexBundleFromRawString(input1), + unparsedApexBundleFromRawString(input2), + ])(); expect(result).documentationBundleHasLength(2); assertEither(result, (data) => expect(data).firstDocContains('See')); assertEither(result, (data) => expect(data).firstDocContains('[ClassRef](ClassRef.md)')); @@ -294,7 +303,7 @@ describe('When generating documentation', () => { public class MyClass {} `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('See')); @@ -304,7 +313,7 @@ describe('When generating documentation', () => { it('displays the namespace if present in the config', async () => { const input = 'public class MyClass {}'; - const result = await generateDocs([apexBundleFromRawString(input)], { namespace: 'MyNamespace' })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { namespace: 'MyNamespace' })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('## Namespace')); assertEither(result, (data) => expect(data).firstDocContains('MyNamespace')); @@ -313,7 +322,7 @@ describe('When generating documentation', () => { it('does not display the namespace if not present in the config', async () => { const input = 'public class MyClass {}'; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContainsNot('## Namespace')); }); @@ -333,7 +342,7 @@ describe('When generating documentation', () => { public class MyClass {} `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('```mermaid')); assertEither(result, (data) => expect(data).firstDocContains('graph TD')); @@ -353,7 +362,7 @@ describe('When generating documentation', () => { */ public class MyClass {}`; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('```apex')); @@ -368,7 +377,7 @@ describe('When generating documentation', () => { public class MyClass {} `; - const result = await generateDocs([apexBundleFromRawString(input)], { + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { excludeTags: ['see'], })(); diff --git a/src/core/markdown/__test__/generating-enum-docs.spec.ts b/src/core/markdown/__test__/generating-enum-docs.spec.ts index f0f7aa67..66bf94bb 100644 --- a/src/core/markdown/__test__/generating-enum-docs.spec.ts +++ b/src/core/markdown/__test__/generating-enum-docs.spec.ts @@ -1,5 +1,5 @@ import { extendExpect } from './expect-extensions'; -import { apexBundleFromRawString, generateDocs } from './test-helpers'; +import { unparsedApexBundleFromRawString, generateDocs } from './test-helpers'; import { assertEither } from '../../test-helpers/assert-either'; describe('Generates enum documentation', () => { @@ -16,7 +16,7 @@ describe('Generates enum documentation', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('## Values')); assertEither(result, (data) => expect(data).firstDocContains('VALUE1')); @@ -31,7 +31,7 @@ describe('Generates enum documentation', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: true })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: true })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('## Values')); assertEither(result, (data) => { @@ -49,7 +49,7 @@ describe('Generates enum documentation', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: false })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: false })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('## Values')); assertEither(result, (data) => { diff --git a/src/core/markdown/__test__/generating-interface-docs.spec.ts b/src/core/markdown/__test__/generating-interface-docs.spec.ts index c6d7e964..21afaf9c 100644 --- a/src/core/markdown/__test__/generating-interface-docs.spec.ts +++ b/src/core/markdown/__test__/generating-interface-docs.spec.ts @@ -1,5 +1,5 @@ import { extendExpect } from './expect-extensions'; -import { apexBundleFromRawString, generateDocs } from './test-helpers'; +import { unparsedApexBundleFromRawString, generateDocs } from './test-helpers'; import { assertEither } from '../../test-helpers/assert-either'; describe('Generates interface documentation', () => { @@ -16,7 +16,7 @@ describe('Generates interface documentation', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('## Methods')); }); @@ -29,7 +29,7 @@ describe('Generates interface documentation', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: true })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: true })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { expect(data.docs[0].content.indexOf('anotherMethod')).toBeLessThan(data.docs[0].content.indexOf('myMethod')); @@ -44,7 +44,7 @@ describe('Generates interface documentation', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)], { sortAlphabetically: false })(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { sortAlphabetically: false })(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => { expect(data.docs[0].content.indexOf('myMethod')).toBeLessThan(data.docs[0].content.indexOf('anotherMethod')); @@ -68,7 +68,7 @@ describe('Generates interface documentation', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('```mermaid')); assertEither(result, (data) => expect(data).firstDocContains('graph TD')); @@ -89,7 +89,7 @@ describe('Generates interface documentation', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('```apex')); assertEither(result, (data) => expect(data).firstDocContains('public class MyClass')); @@ -102,7 +102,7 @@ describe('Generates interface documentation', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('### Signature')); }); @@ -114,7 +114,7 @@ describe('Generates interface documentation', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('### Parameters')); }); @@ -126,7 +126,7 @@ describe('Generates interface documentation', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('### Return Type')); }); @@ -141,7 +141,7 @@ describe('Generates interface documentation', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('### Throws')); }); @@ -157,7 +157,10 @@ describe('Generates interface documentation', () => { public interface AnotherInterface extends MyInterface {} `; - const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])(); + const result = await generateDocs([ + unparsedApexBundleFromRawString(input1), + unparsedApexBundleFromRawString(input2), + ])(); expect(result).documentationBundleHasLength(2); assertEither(result, (data) => expect(data.docs.find((doc) => doc.outputDocPath.includes('AnotherInterface'))?.content).toContain( diff --git a/src/core/markdown/__test__/generating-reference-guide.spec.ts b/src/core/markdown/__test__/generating-reference-guide.spec.ts index ad64980c..e37d6ef5 100644 --- a/src/core/markdown/__test__/generating-reference-guide.spec.ts +++ b/src/core/markdown/__test__/generating-reference-guide.spec.ts @@ -1,7 +1,7 @@ import { extendExpect } from './expect-extensions'; import { pipe } from 'fp-ts/function'; import * as E from 'fp-ts/Either'; -import { apexBundleFromRawString, generateDocs } from './test-helpers'; +import { unparsedApexBundleFromRawString, generateDocs } from './test-helpers'; import { ReferenceGuidePageData } from '../../shared/types'; import { assertEither } from '../../test-helpers/assert-either'; @@ -38,7 +38,10 @@ describe('When generating the Reference Guide', () => { public class MyClass {} `; - const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])(); + const result = await generateDocs([ + unparsedApexBundleFromRawString(input1), + unparsedApexBundleFromRawString(input2), + ])(); expect(result).documentationBundleHasLength(2); assertEither(result, (data) => @@ -57,7 +60,7 @@ describe('When generating the Reference Guide', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('## Miscellaneous'), @@ -75,7 +78,7 @@ describe('When generating the Reference Guide', () => { } `; - const result = await generateDocs([apexBundleFromRawString(input)])(); + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('## MyGroup'), @@ -100,7 +103,10 @@ describe('When generating the Reference Guide', () => { public class MyClass {} `; - const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])(); + const result = await generateDocs([ + unparsedApexBundleFromRawString(input1), + unparsedApexBundleFromRawString(input2), + ])(); expect(result).documentationBundleHasLength(2); pipe( result, @@ -133,7 +139,10 @@ describe('When generating the Reference Guide', () => { public class MyClass {} `; - const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])(); + const result = await generateDocs([ + unparsedApexBundleFromRawString(input1), + unparsedApexBundleFromRawString(input2), + ])(); expect(result).documentationBundleHasLength(2); assertEither(result, (data) => expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('## Group1'), @@ -162,7 +171,10 @@ describe('When generating the Reference Guide', () => { public class MyClass {} `; - const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])(); + const result = await generateDocs([ + unparsedApexBundleFromRawString(input1), + unparsedApexBundleFromRawString(input2), + ])(); expect(result).documentationBundleHasLength(2); assertEither(result, (data) => expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('This is a description'), @@ -188,7 +200,10 @@ describe('When generating the Reference Guide', () => { public class MyClass {} `; - const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])(); + const result = await generateDocs([ + unparsedApexBundleFromRawString(input1), + unparsedApexBundleFromRawString(input2), + ])(); expect(result).documentationBundleHasLength(2); assertEither(result, (data) => expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('with a [MyClass](group2/MyClass.md)'), diff --git a/src/core/markdown/__test__/test-helpers.ts b/src/core/markdown/__test__/test-helpers.ts index 1f1a512e..403a690b 100644 --- a/src/core/markdown/__test__/test-helpers.ts +++ b/src/core/markdown/__test__/test-helpers.ts @@ -1,8 +1,8 @@ -import { UnparsedApexFile } from '../../shared/types'; +import { UnparsedApexBundle } from '../../shared/types'; import { generateDocs as gen, MarkdownGeneratorConfig } from '../generate-docs'; import { referenceGuideTemplate } from '../templates/reference-guide'; -export function apexBundleFromRawString(raw: string, rawMetadata?: string): UnparsedApexFile { +export function unparsedApexBundleFromRawString(raw: string, rawMetadata?: string): UnparsedApexBundle { return { type: 'apex', filePath: 'test.cls', @@ -11,7 +11,7 @@ export function apexBundleFromRawString(raw: string, rawMetadata?: string): Unpa }; } -export function generateDocs(apexBundles: UnparsedApexFile[], config?: Partial) { +export function generateDocs(apexBundles: UnparsedApexBundle[], config?: Partial) { return gen(apexBundles, { targetDir: 'target', scope: ['global', 'public'], diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index 1c91a751..7009a808 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -9,7 +9,7 @@ import { Frontmatter, PostHookDocumentationBundle, ReferenceGuidePageData, - UnparsedApexFile, + UnparsedApexBundle, TransformDocPage, TransformDocs, TransformReferenceGuide, @@ -17,8 +17,8 @@ import { DocPageReference, TransformReference, ParsedFile, - UnparsedObjectFile, - UnparsedSourceFile, + UnparsedObjectBundle, + UnparsedSourceBundle, } from '../shared/types'; import { parsedFilesToRenderableBundle } from './adapters/renderable-bundle'; import { reflectApexSource } from '../reflection/reflect-source'; @@ -42,7 +42,7 @@ export type MarkdownGeneratorConfig = Omit< referenceGuideTemplate: string; }; -export function generateDocs(unparsedApexFiles: UnparsedSourceFile[], config: MarkdownGeneratorConfig) { +export function generateDocs(unparsedApexFiles: UnparsedSourceBundle[], config: MarkdownGeneratorConfig) { const convertToReferences = apply(parsedFilesToReferenceGuide, config); const convertToRenderableBundle = apply(parsedFilesToRenderableBundle, config); const convertToDocumentationBundleForTemplate = apply( @@ -52,12 +52,12 @@ export function generateDocs(unparsedApexFiles: UnparsedSourceFile[], config: Ma ); const sort = apply(sortTypesAndMembers, config.sortAlphabetically); - function filterApexSourceFiles(sourceFiles: UnparsedSourceFile[]): UnparsedApexFile[] { - return sourceFiles.filter((sourceFile): sourceFile is UnparsedApexFile => sourceFile.type === 'apex'); + function filterApexSourceFiles(sourceFiles: UnparsedSourceBundle[]): UnparsedApexBundle[] { + return sourceFiles.filter((sourceFile): sourceFile is UnparsedApexBundle => sourceFile.type === 'apex'); } - function filterObjectSourceFiles(sourceFiles: UnparsedSourceFile[]): UnparsedObjectFile[] { - return sourceFiles.filter((sourceFile): sourceFile is UnparsedObjectFile => sourceFile.type === 'object'); + function filterObjectSourceFiles(sourceFiles: UnparsedSourceBundle[]): UnparsedObjectBundle[] { + return sourceFiles.filter((sourceFile): sourceFile is UnparsedObjectBundle => sourceFile.type === 'object'); } return pipe( @@ -79,7 +79,7 @@ export function generateDocs(unparsedApexFiles: UnparsedSourceFile[], config: Ma ); } -function generateForApex(apexBundles: UnparsedApexFile[], config: MarkdownGeneratorConfig) { +function generateForApex(apexBundles: UnparsedApexBundle[], config: MarkdownGeneratorConfig) { const filterOutOfScope = apply(filterScope, config.scope); const removeExcluded = apply(removeExcludedTags, config.excludeTags); @@ -93,7 +93,7 @@ function generateForApex(apexBundles: UnparsedApexFile[], config: MarkdownGenera ); } -function generateForObject(objectBundles: UnparsedObjectFile[]) { +function generateForObject(objectBundles: UnparsedObjectBundle[]) { // TODO: Filter out non public return pipe(objectBundles, reflectObjectSources); } diff --git a/src/core/openapi/manifest-factory.ts b/src/core/openapi/manifest-factory.ts index d299994b..19124275 100644 --- a/src/core/openapi/manifest-factory.ts +++ b/src/core/openapi/manifest-factory.ts @@ -1,7 +1,7 @@ import Manifest from '../manifest'; import { TypeParser } from './parser'; import { ReflectionResult } from '@cparra/apex-reflection'; -import { UnparsedApexFile } from '../shared/types'; +import { UnparsedApexBundle } from '../shared/types'; /** * Builds a new Manifest object, sourcing its types from the received TypeParser. @@ -10,7 +10,7 @@ import { UnparsedApexFile } from '../shared/types'; */ export function createManifest( typeParser: TypeParser, - reflect: (apexBundle: UnparsedApexFile) => ReflectionResult, + reflect: (apexBundle: UnparsedApexBundle) => ReflectionResult, ): Manifest { return new Manifest(typeParser.parse(reflect)); } diff --git a/src/core/openapi/parser.ts b/src/core/openapi/parser.ts index 22605743..ea34f47e 100644 --- a/src/core/openapi/parser.ts +++ b/src/core/openapi/parser.ts @@ -1,9 +1,9 @@ import { ClassMirror, InterfaceMirror, ReflectionResult, Type } from '@cparra/apex-reflection'; import { Logger } from '#utils/logger'; -import { UnparsedApexFile } from '../shared/types'; +import { UnparsedApexBundle } from '../shared/types'; export interface TypeParser { - parse(reflect: (apexBundle: UnparsedApexFile) => ReflectionResult): Type[]; + parse(reflect: (apexBundle: UnparsedApexBundle) => ReflectionResult): Type[]; } type NameAware = { name: string }; @@ -11,10 +11,10 @@ type NameAware = { name: string }; export class RawBodyParser implements TypeParser { constructor( private logger: Logger, - public typeBundles: UnparsedApexFile[], + public typeBundles: UnparsedApexBundle[], ) {} - parse(reflect: (apexBundle: UnparsedApexFile) => ReflectionResult): Type[] { + parse(reflect: (apexBundle: UnparsedApexBundle) => ReflectionResult): Type[] { const types = this.typeBundles .map((currentBundle) => { this.logger.log(`Parsing file: ${currentBundle.filePath}`); diff --git a/src/core/reflection/reflect-object-source.ts b/src/core/reflection/reflect-object-source.ts index 56c14c7c..1dbebde8 100644 --- a/src/core/reflection/reflect-object-source.ts +++ b/src/core/reflection/reflect-object-source.ts @@ -1,4 +1,4 @@ -import { ParsedFile, UnparsedObjectFile } from '../shared/types'; +import { ParsedFile, UnparsedObjectBundle } from '../shared/types'; import { XMLParser } from 'fast-xml-parser'; import * as TE from 'fp-ts/TaskEither'; import { ReflectionError, ReflectionErrors } from '../errors/errors'; @@ -13,7 +13,7 @@ export type ObjectMetadata = { name: string; }; -export function reflectObjectSources(objectSources: UnparsedObjectFile[]) { +export function reflectObjectSources(objectSources: UnparsedObjectBundle[]) { const semiGroupReflectionError: Semigroup = { concat: (x, y) => new ReflectionErrors([...x.errors, ...y.errors]), }; @@ -22,7 +22,7 @@ export function reflectObjectSources(objectSources: UnparsedObjectFile[]) { return pipe(objectSources, A.traverse(Ap)(reflectObjectSource)); } -function reflectObjectSource(objectSource: UnparsedObjectFile): TE.TaskEither { +function reflectObjectSource(objectSource: UnparsedObjectBundle): TE.TaskEither { return pipe( TE.tryCatch( () => new XMLParser().parse(objectSource.content), diff --git a/src/core/reflection/reflect-source.ts b/src/core/reflection/reflect-source.ts index 1b3643a7..6e6204e6 100644 --- a/src/core/reflection/reflect-source.ts +++ b/src/core/reflection/reflect-source.ts @@ -8,7 +8,7 @@ import * as O from 'fp-ts/Option'; import { ParsingError } from '@cparra/apex-reflection'; import { apply } from '#utils/fp'; import { Semigroup } from 'fp-ts/Semigroup'; -import { ParsedFile, UnparsedApexFile } from '../shared/types'; +import { ParsedFile, UnparsedApexBundle } from '../shared/types'; import { ReflectionError, ReflectionErrors } from '../errors/errors'; import { parseApexMetadata } from '../parse-apex-metadata'; @@ -25,7 +25,7 @@ async function reflectAsync(rawSource: string): Promise { }); } -export function reflectApexSource(apexBundles: UnparsedApexFile[]) { +export function reflectApexSource(apexBundles: UnparsedApexBundle[]) { const semiGroupReflectionError: Semigroup = { concat: (x, y) => new ReflectionErrors([...x.errors, ...y.errors]), }; @@ -34,14 +34,14 @@ export function reflectApexSource(apexBundles: UnparsedApexFile[]) { return pipe(apexBundles, A.traverse(Ap)(reflectBundle)); } -function reflectBundle(apexBundle: UnparsedApexFile): TE.TaskEither> { +function reflectBundle(apexBundle: UnparsedApexBundle): TE.TaskEither> { const convertToParsedFile: (typeMirror: Type) => ParsedFile = apply(toParsedFile, apexBundle.filePath); const withMetadata = apply(addMetadata, apexBundle.metadataContent); return pipe(apexBundle, reflectAsTask, TE.map(convertToParsedFile), TE.flatMap(withMetadata)); } -function reflectAsTask(apexBundle: UnparsedApexFile): TE.TaskEither { +function reflectAsTask(apexBundle: UnparsedApexBundle): TE.TaskEither { return TE.tryCatch( () => reflectAsync(apexBundle.content), (error) => diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index acef98be..21e5574c 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -55,15 +55,15 @@ export type UserDefinedChangelogConfig = { export type UserDefinedConfig = UserDefinedMarkdownConfig | UserDefinedOpenApiConfig | UserDefinedChangelogConfig; -export type UnparsedSourceFile = UnparsedApexFile | UnparsedObjectFile; +export type UnparsedSourceBundle = UnparsedApexBundle | UnparsedObjectBundle; -export type UnparsedObjectFile = { +export type UnparsedObjectBundle = { type: 'object'; filePath: string; content: string; }; -export type UnparsedApexFile = { +export type UnparsedApexBundle = { type: 'apex'; filePath: string; content: string; From 445a8aea0d911b7ebeefdf867f07ad8ce82b7795 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Thu, 17 Oct 2024 14:16:25 -0400 Subject: [PATCH 08/32] Moving reflection code --- src/application/source-code-file-reader.ts | 2 +- src/core/changelog/generate-change-log.ts | 4 ++-- src/core/markdown/__test__/inheritance-chain.test.ts | 2 +- src/core/markdown/generate-docs.ts | 10 +++++----- .../{ => apex}/__test__/filter-scope.spec.ts | 3 ++- src/core/reflection/{ => apex}/__test__/helpers.ts | 2 +- .../{ => apex}/__test__/remove-excluded-tags.spec.ts | 2 +- src/core/reflection/{ => apex}/filter-scope.ts | 4 ++-- .../{ => apex}/inheritance-chain-expanion.ts | 4 ++-- src/core/reflection/{ => apex}/inheritance-chain.ts | 0 .../{ => apex}/inherited-member-expansion.ts | 4 ++-- .../{reflect-source.ts => apex/reflect-apex-source.ts} | 6 +++--- src/core/reflection/{ => apex}/remove-excluded-tags.ts | 2 +- src/core/reflection/sort-types-and-members.ts | 1 + 14 files changed, 24 insertions(+), 22 deletions(-) rename src/core/reflection/{ => apex}/__test__/filter-scope.spec.ts (99%) rename src/core/reflection/{ => apex}/__test__/helpers.ts (88%) rename src/core/reflection/{ => apex}/__test__/remove-excluded-tags.spec.ts (100%) rename src/core/reflection/{ => apex}/filter-scope.ts (81%) rename src/core/reflection/{ => apex}/inheritance-chain-expanion.ts (86%) rename src/core/reflection/{ => apex}/inheritance-chain.ts (100%) rename src/core/reflection/{ => apex}/inherited-member-expansion.ts (97%) rename src/core/reflection/{reflect-source.ts => apex/reflect-apex-source.ts} (94%) rename src/core/reflection/{ => apex}/remove-excluded-tags.ts (99%) diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts index 51dc4bc1..ba57cd9d 100644 --- a/src/application/source-code-file-reader.ts +++ b/src/application/source-code-file-reader.ts @@ -4,7 +4,7 @@ import { minimatch } from 'minimatch'; import { pipe } from 'fp-ts/function'; /** - * Reads from .cls files and returns their raw body. + * Reads from source code files and returns their raw body. */ export function processFiles(fileSystem: FileSystem) { return function (processors: FileProcessor[]) { diff --git a/src/core/changelog/generate-change-log.ts b/src/core/changelog/generate-change-log.ts index b0bb05c6..9552308f 100644 --- a/src/core/changelog/generate-change-log.ts +++ b/src/core/changelog/generate-change-log.ts @@ -1,14 +1,14 @@ import { ParsedFile, Skip, UnparsedApexBundle, UserDefinedChangelogConfig } from '../shared/types'; import { pipe } from 'fp-ts/function'; import * as TE from 'fp-ts/TaskEither'; -import { reflectApexSource } from '../reflection/reflect-source'; +import { reflectApexSource } from '../reflection/apex/reflect-apex-source'; import { Changelog, hasChanges, processChangelog, VersionManifest } from './process-changelog'; import { convertToRenderableChangelog, RenderableChangelog } from './renderable-changelog'; import { CompilationRequest, Template } from '../template'; import { changelogTemplate } from './templates/changelog-template'; import { ReflectionErrors } from '../errors/errors'; import { apply } from '#utils/fp'; -import { filterScope } from '../reflection/filter-scope'; +import { filterScope } from '../reflection/apex/filter-scope'; import { isApexType, skip } from '../shared/utils'; export type ChangeLogPageData = { diff --git a/src/core/markdown/__test__/inheritance-chain.test.ts b/src/core/markdown/__test__/inheritance-chain.test.ts index 4b97fdaa..f6c76e59 100644 --- a/src/core/markdown/__test__/inheritance-chain.test.ts +++ b/src/core/markdown/__test__/inheritance-chain.test.ts @@ -1,5 +1,5 @@ import { ClassMirrorBuilder } from '../../../test-helpers/ClassMirrorBuilder'; -import { createInheritanceChain } from '../../reflection/inheritance-chain'; +import { createInheritanceChain } from '../../reflection/apex/inheritance-chain'; describe('inheritance chain for classes', () => { test('returns an empty list of the class does not extend any other class', () => { diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index 7009a808..8cc5892d 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -21,17 +21,17 @@ import { UnparsedSourceBundle, } from '../shared/types'; import { parsedFilesToRenderableBundle } from './adapters/renderable-bundle'; -import { reflectApexSource } from '../reflection/reflect-source'; -import { addInheritanceChainToTypes } from '../reflection/inheritance-chain-expanion'; -import { addInheritedMembersToTypes } from '../reflection/inherited-member-expansion'; +import { reflectApexSource } from '../reflection/apex/reflect-apex-source'; +import { addInheritanceChainToTypes } from '../reflection/apex/inheritance-chain-expanion'; +import { addInheritedMembersToTypes } from '../reflection/apex/inherited-member-expansion'; import { convertToDocumentationBundle } from './adapters/renderable-to-page-data'; -import { filterScope } from '../reflection/filter-scope'; +import { filterScope } from '../reflection/apex/filter-scope'; import { Template } from '../template'; import { hookableTemplate } from './templates/hookable'; import { sortTypesAndMembers } from '../reflection/sort-types-and-members'; import { isSkip } from '../shared/utils'; import { parsedFilesToReferenceGuide } from './adapters/reference-guide'; -import { removeExcludedTags } from '../reflection/remove-excluded-tags'; +import { removeExcludedTags } from '../reflection/apex/remove-excluded-tags'; import { HookError } from '../errors/errors'; import { reflectObjectSources } from '../reflection/reflect-object-source'; diff --git a/src/core/reflection/__test__/filter-scope.spec.ts b/src/core/reflection/apex/__test__/filter-scope.spec.ts similarity index 99% rename from src/core/reflection/__test__/filter-scope.spec.ts rename to src/core/reflection/apex/__test__/filter-scope.spec.ts index edf8f2b8..8eb5e782 100644 --- a/src/core/reflection/__test__/filter-scope.spec.ts +++ b/src/core/reflection/apex/__test__/filter-scope.spec.ts @@ -1,6 +1,7 @@ import { ClassMirror, EnumMirror, InterfaceMirror } from '@cparra/apex-reflection'; -import { filterScope } from '../filter-scope'; + import { parsedFileFromRawString } from './helpers'; +import { filterScope } from '../filter-scope'; describe('When filtering scope', () => { it('filters out files with the @ignore annotation', () => { diff --git a/src/core/reflection/__test__/helpers.ts b/src/core/reflection/apex/__test__/helpers.ts similarity index 88% rename from src/core/reflection/__test__/helpers.ts rename to src/core/reflection/apex/__test__/helpers.ts index 854200ec..f9d1a5a0 100644 --- a/src/core/reflection/__test__/helpers.ts +++ b/src/core/reflection/apex/__test__/helpers.ts @@ -1,5 +1,5 @@ import { reflect, Type } from '@cparra/apex-reflection'; -import { ParsedFile } from '../../shared/types'; +import { ParsedFile } from '../../../shared/types'; export function parsedFileFromRawString(raw: string): ParsedFile { const { error, typeMirror } = reflect(raw); diff --git a/src/core/reflection/__test__/remove-excluded-tags.spec.ts b/src/core/reflection/apex/__test__/remove-excluded-tags.spec.ts similarity index 100% rename from src/core/reflection/__test__/remove-excluded-tags.spec.ts rename to src/core/reflection/apex/__test__/remove-excluded-tags.spec.ts index 108e5c14..83daa879 100644 --- a/src/core/reflection/__test__/remove-excluded-tags.spec.ts +++ b/src/core/reflection/apex/__test__/remove-excluded-tags.spec.ts @@ -1,6 +1,6 @@ import { parsedFileFromRawString } from './helpers'; -import { removeExcludedTags } from '../remove-excluded-tags'; import { ClassMirror, InterfaceMirror } from '@cparra/apex-reflection'; +import { removeExcludedTags } from '../remove-excluded-tags'; describe('when removing excluded tags', () => { describe('from any type', () => { diff --git a/src/core/reflection/filter-scope.ts b/src/core/reflection/apex/filter-scope.ts similarity index 81% rename from src/core/reflection/filter-scope.ts rename to src/core/reflection/apex/filter-scope.ts index 716db669..2f0fa9b3 100644 --- a/src/core/reflection/filter-scope.ts +++ b/src/core/reflection/apex/filter-scope.ts @@ -1,5 +1,5 @@ -import Manifest from '../manifest'; -import { ParsedFile } from '../shared/types'; +import Manifest from '../../manifest'; +import { ParsedFile } from '../../shared/types'; import { Type } from '@cparra/apex-reflection'; export function filterScope(scopes: string[], parsedFiles: ParsedFile[]): ParsedFile[] { diff --git a/src/core/reflection/inheritance-chain-expanion.ts b/src/core/reflection/apex/inheritance-chain-expanion.ts similarity index 86% rename from src/core/reflection/inheritance-chain-expanion.ts rename to src/core/reflection/apex/inheritance-chain-expanion.ts index f79a32a9..77da29b0 100644 --- a/src/core/reflection/inheritance-chain-expanion.ts +++ b/src/core/reflection/apex/inheritance-chain-expanion.ts @@ -1,7 +1,7 @@ import { ClassMirror, Type } from '@cparra/apex-reflection'; import { createInheritanceChain } from './inheritance-chain'; -import { parsedFilesToTypes } from '../markdown/utils'; -import { ParsedFile } from '../shared/types'; +import { parsedFilesToTypes } from '../../markdown/utils'; +import { ParsedFile } from '../../shared/types'; export const addInheritanceChainToTypes = (parsedFiles: ParsedFile[]): ParsedFile[] => parsedFiles.map((parsedFile) => ({ diff --git a/src/core/reflection/inheritance-chain.ts b/src/core/reflection/apex/inheritance-chain.ts similarity index 100% rename from src/core/reflection/inheritance-chain.ts rename to src/core/reflection/apex/inheritance-chain.ts diff --git a/src/core/reflection/inherited-member-expansion.ts b/src/core/reflection/apex/inherited-member-expansion.ts similarity index 97% rename from src/core/reflection/inherited-member-expansion.ts rename to src/core/reflection/apex/inherited-member-expansion.ts index 521aed2e..0df9db3d 100644 --- a/src/core/reflection/inherited-member-expansion.ts +++ b/src/core/reflection/apex/inherited-member-expansion.ts @@ -1,7 +1,7 @@ import { ClassMirror, InterfaceMirror, Type } from '@cparra/apex-reflection'; import { pipe } from 'fp-ts/function'; -import { ParsedFile } from '../shared/types'; -import { parsedFilesToTypes } from '../markdown/utils'; +import { ParsedFile } from '../../shared/types'; +import { parsedFilesToTypes } from '../../markdown/utils'; export const addInheritedMembersToTypes = (parsedFiles: ParsedFile[]) => parsedFiles.map((parsedFile) => addInheritedMembers(parsedFilesToTypes(parsedFiles), parsedFile)); diff --git a/src/core/reflection/reflect-source.ts b/src/core/reflection/apex/reflect-apex-source.ts similarity index 94% rename from src/core/reflection/reflect-source.ts rename to src/core/reflection/apex/reflect-apex-source.ts index 6e6204e6..44cf16e8 100644 --- a/src/core/reflection/reflect-source.ts +++ b/src/core/reflection/apex/reflect-apex-source.ts @@ -8,9 +8,9 @@ import * as O from 'fp-ts/Option'; import { ParsingError } from '@cparra/apex-reflection'; import { apply } from '#utils/fp'; import { Semigroup } from 'fp-ts/Semigroup'; -import { ParsedFile, UnparsedApexBundle } from '../shared/types'; -import { ReflectionError, ReflectionErrors } from '../errors/errors'; -import { parseApexMetadata } from '../parse-apex-metadata'; +import { ParsedFile, UnparsedApexBundle } from '../../shared/types'; +import { ReflectionError, ReflectionErrors } from '../../errors/errors'; +import { parseApexMetadata } from '../../parse-apex-metadata'; async function reflectAsync(rawSource: string): Promise { return new Promise((resolve, reject) => { diff --git a/src/core/reflection/remove-excluded-tags.ts b/src/core/reflection/apex/remove-excluded-tags.ts similarity index 99% rename from src/core/reflection/remove-excluded-tags.ts rename to src/core/reflection/apex/remove-excluded-tags.ts index 620679b3..fa88c66e 100644 --- a/src/core/reflection/remove-excluded-tags.ts +++ b/src/core/reflection/apex/remove-excluded-tags.ts @@ -3,7 +3,7 @@ import { match } from 'fp-ts/boolean'; import { ClassMirror, DocComment, InterfaceMirror, Type } from '@cparra/apex-reflection'; import { pipe } from 'fp-ts/function'; import { apply } from '#utils/fp'; -import { ParsedFile } from '../shared/types'; +import { ParsedFile } from '../../shared/types'; type AppliedRemoveTagFn = (tagName: string, removeFn: RemoveTagFn) => DocComment; type RemoveTagFn = (docComment: DocComment) => DocComment; diff --git a/src/core/reflection/sort-types-and-members.ts b/src/core/reflection/sort-types-and-members.ts index f8271452..c4e7016e 100644 --- a/src/core/reflection/sort-types-and-members.ts +++ b/src/core/reflection/sort-types-and-members.ts @@ -8,6 +8,7 @@ export function sortTypesAndMembers(shouldSort: boolean, parsedFiles: ParsedFile return parsedFiles .map((parsedFile) => ({ ...parsedFile, + // TODO: Sort fields when they are added to the parser type: isApexType(parsedFile.type) ? sortTypeMember(parsedFile.type, shouldSort) : parsedFile.type, })) .sort((a, b) => sortByNames(shouldSort, a.type, b.type)); From cde832939e91d72753e8d3c4a378b577d9b3c025 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Thu, 17 Oct 2024 14:17:19 -0400 Subject: [PATCH 09/32] Moving reflection code --- src/application/source-code-file-reader.ts | 14 +++++++------- src/core/markdown/adapters/apex-types.ts | 2 +- src/core/markdown/adapters/renderable-bundle.ts | 2 +- src/core/markdown/generate-docs.ts | 12 ++++++------ .../reflect-sobject-source.ts} | 10 +++++----- src/core/shared/types.d.ts | 6 +++--- src/core/shared/utils.ts | 2 +- 7 files changed, 24 insertions(+), 24 deletions(-) rename src/core/reflection/{reflect-object-source.ts => sobject/reflect-sobject-source.ts} (80%) diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts index ba57cd9d..9f685f21 100644 --- a/src/application/source-code-file-reader.ts +++ b/src/application/source-code-file-reader.ts @@ -1,5 +1,5 @@ import { FileSystem } from './file-system'; -import { UnparsedApexBundle, UnparsedObjectBundle } from '../core/shared/types'; +import { UnparsedApexBundle, UnparsedSObjectBundle } from '../core/shared/types'; import { minimatch } from 'minimatch'; import { pipe } from 'fp-ts/function'; @@ -7,7 +7,7 @@ import { pipe } from 'fp-ts/function'; * Reads from source code files and returns their raw body. */ export function processFiles(fileSystem: FileSystem) { - return function (processors: FileProcessor[]) { + return function (processors: FileProcessor[]) { return async function (rootPath: string, exclude: string[]): Promise { return pipe( await getFilePaths(fileSystem, rootPath), @@ -18,7 +18,7 @@ export function processFiles(fileSystem: FileSystem) { }; } -async function readFiles( +async function readFiles( fileSystem: FileSystem, filePaths: string[], processors: FileProcessor[], @@ -51,7 +51,7 @@ function isExcluded(filePath: string, exclude: string[]): boolean { return exclude.some((pattern) => minimatch(filePath, pattern)); } -interface FileProcessor { +interface FileProcessor { isSupportedFile: (currentFile: string) => boolean; process: (fileSystem: FileSystem, filePath: string) => Promise; } @@ -81,18 +81,18 @@ class ApexFileReader implements FileProcessor { } } -export function processObjectFiles(): FileProcessor { +export function processObjectFiles(): FileProcessor { return new ObjectFileReader(); } -class ObjectFileReader implements FileProcessor { +class ObjectFileReader implements FileProcessor { OBJECT_FILE_EXTENSION = '.object-meta.xml'; isSupportedFile(currentFile: string): boolean { return currentFile.endsWith(this.OBJECT_FILE_EXTENSION); } - async process(fileSystem: FileSystem, filePath: string): Promise { + async process(fileSystem: FileSystem, filePath: string): Promise { const rawTypeContent = await fileSystem.readFile(filePath); return { type: 'object', filePath, content: rawTypeContent }; } diff --git a/src/core/markdown/adapters/apex-types.ts b/src/core/markdown/adapters/apex-types.ts index d6c7e682..d9513f8c 100644 --- a/src/core/markdown/adapters/apex-types.ts +++ b/src/core/markdown/adapters/apex-types.ts @@ -16,7 +16,7 @@ import { adaptConstructor, adaptMethod } from './methods-and-constructors'; import { adaptFieldOrProperty } from './fields-and-properties'; import { MarkdownGeneratorConfig } from '../generate-docs'; import { SourceFileMetadata } from '../../shared/types'; -import { ObjectMetadata } from '../../reflection/reflect-object-source'; +import { ObjectMetadata } from '../../reflection/sobject/reflect-sobject-source'; type GetReturnRenderable = T extends InterfaceMirror ? RenderableInterface diff --git a/src/core/markdown/adapters/renderable-bundle.ts b/src/core/markdown/adapters/renderable-bundle.ts index 0067f2ce..13db4a6a 100644 --- a/src/core/markdown/adapters/renderable-bundle.ts +++ b/src/core/markdown/adapters/renderable-bundle.ts @@ -13,7 +13,7 @@ import { apply } from '#utils/fp'; import { generateLink } from './generate-link'; import { getTypeGroup } from '../../shared/utils'; import { Type } from '@cparra/apex-reflection'; -import { ObjectMetadata } from '../../reflection/reflect-object-source'; +import { ObjectMetadata } from '../../reflection/sobject/reflect-sobject-source'; export function parsedFilesToRenderableBundle( config: MarkdownGeneratorConfig, diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index 8cc5892d..1a8a71f8 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -17,7 +17,7 @@ import { DocPageReference, TransformReference, ParsedFile, - UnparsedObjectBundle, + UnparsedSObjectBundle, UnparsedSourceBundle, } from '../shared/types'; import { parsedFilesToRenderableBundle } from './adapters/renderable-bundle'; @@ -33,7 +33,7 @@ import { isSkip } from '../shared/utils'; import { parsedFilesToReferenceGuide } from './adapters/reference-guide'; import { removeExcludedTags } from '../reflection/apex/remove-excluded-tags'; import { HookError } from '../errors/errors'; -import { reflectObjectSources } from '../reflection/reflect-object-source'; +import { reflectSObjectSources } from '../reflection/sobject/reflect-sobject-source'; export type MarkdownGeneratorConfig = Omit< UserDefinedMarkdownConfig, @@ -56,8 +56,8 @@ export function generateDocs(unparsedApexFiles: UnparsedSourceBundle[], config: return sourceFiles.filter((sourceFile): sourceFile is UnparsedApexBundle => sourceFile.type === 'apex'); } - function filterObjectSourceFiles(sourceFiles: UnparsedSourceBundle[]): UnparsedObjectBundle[] { - return sourceFiles.filter((sourceFile): sourceFile is UnparsedObjectBundle => sourceFile.type === 'object'); + function filterObjectSourceFiles(sourceFiles: UnparsedSourceBundle[]): UnparsedSObjectBundle[] { + return sourceFiles.filter((sourceFile): sourceFile is UnparsedSObjectBundle => sourceFile.type === 'object'); } return pipe( @@ -93,9 +93,9 @@ function generateForApex(apexBundles: UnparsedApexBundle[], config: MarkdownGene ); } -function generateForObject(objectBundles: UnparsedObjectBundle[]) { +function generateForObject(objectBundles: UnparsedSObjectBundle[]) { // TODO: Filter out non public - return pipe(objectBundles, reflectObjectSources); + return pipe(objectBundles, reflectSObjectSources); } function transformReferenceHook(config: MarkdownGeneratorConfig) { diff --git a/src/core/reflection/reflect-object-source.ts b/src/core/reflection/sobject/reflect-sobject-source.ts similarity index 80% rename from src/core/reflection/reflect-object-source.ts rename to src/core/reflection/sobject/reflect-sobject-source.ts index 1dbebde8..b480334e 100644 --- a/src/core/reflection/reflect-object-source.ts +++ b/src/core/reflection/sobject/reflect-sobject-source.ts @@ -1,7 +1,7 @@ -import { ParsedFile, UnparsedObjectBundle } from '../shared/types'; +import { ParsedFile, UnparsedSObjectBundle } from '../../shared/types'; import { XMLParser } from 'fast-xml-parser'; import * as TE from 'fp-ts/TaskEither'; -import { ReflectionError, ReflectionErrors } from '../errors/errors'; +import { ReflectionError, ReflectionErrors } from '../../errors/errors'; import { Semigroup } from 'fp-ts/Semigroup'; import * as T from 'fp-ts/Task'; import { pipe } from 'fp-ts/function'; @@ -13,16 +13,16 @@ export type ObjectMetadata = { name: string; }; -export function reflectObjectSources(objectSources: UnparsedObjectBundle[]) { +export function reflectSObjectSources(objectSources: UnparsedSObjectBundle[]) { const semiGroupReflectionError: Semigroup = { concat: (x, y) => new ReflectionErrors([...x.errors, ...y.errors]), }; const Ap = TE.getApplicativeTaskValidation(T.ApplyPar, semiGroupReflectionError); - return pipe(objectSources, A.traverse(Ap)(reflectObjectSource)); + return pipe(objectSources, A.traverse(Ap)(reflectSobjectSource)); } -function reflectObjectSource(objectSource: UnparsedObjectBundle): TE.TaskEither { +function reflectSobjectSource(objectSource: UnparsedSObjectBundle): TE.TaskEither { return pipe( TE.tryCatch( () => new XMLParser().parse(objectSource.content), diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index 21e5574c..0c581c52 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -1,6 +1,6 @@ import { Type } from '@cparra/apex-reflection'; import { ChangeLogPageData } from '../changelog/generate-change-log'; -import { ObjectMetadata } from '../reflection/reflect-object-source'; +import { ObjectMetadata } from '../reflection/sobject/reflect-sobject-source'; export type Generators = 'markdown' | 'openapi' | 'changelog'; @@ -55,9 +55,9 @@ export type UserDefinedChangelogConfig = { export type UserDefinedConfig = UserDefinedMarkdownConfig | UserDefinedOpenApiConfig | UserDefinedChangelogConfig; -export type UnparsedSourceBundle = UnparsedApexBundle | UnparsedObjectBundle; +export type UnparsedSourceBundle = UnparsedApexBundle | UnparsedSObjectBundle; -export type UnparsedObjectBundle = { +export type UnparsedSObjectBundle = { type: 'object'; filePath: string; content: string; diff --git a/src/core/shared/utils.ts b/src/core/shared/utils.ts index d085f799..c987e78f 100644 --- a/src/core/shared/utils.ts +++ b/src/core/shared/utils.ts @@ -1,6 +1,6 @@ import { Skip } from './types'; import { Type } from '@cparra/apex-reflection'; -import { ObjectMetadata } from '../reflection/reflect-object-source'; +import { ObjectMetadata } from '../reflection/sobject/reflect-sobject-source'; import { MarkdownGeneratorConfig } from '../markdown/generate-docs'; /** From a151cd9b01ac8faf68b8afa8875b88db1eb9e5c9 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Thu, 17 Oct 2024 14:26:01 -0400 Subject: [PATCH 10/32] Refactorings --- src/application/generators/openapi.ts | 2 +- src/core/openapi/__tests__/open-api-docs-processor.spec.ts | 2 +- src/core/openapi/open-api-docs-processor.ts | 2 +- src/core/{ => openapi}/openApiSettings.ts | 0 src/core/{ => reflection/apex}/parse-apex-metadata.ts | 0 src/core/reflection/apex/reflect-apex-source.ts | 2 +- src/core/reflection/sobject/reflect-sobject-source.ts | 4 ++-- src/test-helpers/SettingsBuilder.ts | 2 +- 8 files changed, 7 insertions(+), 7 deletions(-) rename src/core/{ => openapi}/openApiSettings.ts (100%) rename src/core/{ => reflection/apex}/parse-apex-metadata.ts (100%) diff --git a/src/application/generators/openapi.ts b/src/application/generators/openapi.ts index 0e145d02..79ecf7ad 100644 --- a/src/application/generators/openapi.ts +++ b/src/application/generators/openapi.ts @@ -11,7 +11,7 @@ import { OpenApiDocsProcessor } from '../../core/openapi/open-api-docs-processor import { writeFiles } from '../file-writer'; import { pipe } from 'fp-ts/function'; import * as TE from 'fp-ts/TaskEither'; -import { OpenApiSettings } from '../../core/openApiSettings'; +import { OpenApiSettings } from '../../core/openapi/openApiSettings'; import { apply } from '#utils/fp'; export default async function openApi( diff --git a/src/core/openapi/__tests__/open-api-docs-processor.spec.ts b/src/core/openapi/__tests__/open-api-docs-processor.spec.ts index 45817899..3951b896 100644 --- a/src/core/openapi/__tests__/open-api-docs-processor.spec.ts +++ b/src/core/openapi/__tests__/open-api-docs-processor.spec.ts @@ -1,5 +1,5 @@ import { OpenApiDocsProcessor } from '../open-api-docs-processor'; -import { OpenApiSettings } from '../../openApiSettings'; +import { OpenApiSettings } from '../openApiSettings'; import { SettingsBuilder } from '../../../test-helpers/SettingsBuilder'; import { DocCommentBuilder } from '../../../test-helpers/DocCommentBuilder'; import { AnnotationBuilder } from '../../../test-helpers/AnnotationBuilder'; diff --git a/src/core/openapi/open-api-docs-processor.ts b/src/core/openapi/open-api-docs-processor.ts index 5f52ae21..5f076348 100644 --- a/src/core/openapi/open-api-docs-processor.ts +++ b/src/core/openapi/open-api-docs-processor.ts @@ -2,7 +2,7 @@ import { FileContainer } from './file-container'; import { ClassMirror, Type } from '@cparra/apex-reflection'; import { Logger } from '#utils/logger'; import { OpenApi } from './open-api'; -import { OpenApiSettings } from '../openApiSettings'; +import { OpenApiSettings } from './openApiSettings'; import { MethodParser } from './parsers/MethodParser'; import { camel2title } from '#utils/string-utils'; import { createOpenApiFile } from './openapi-type-file'; diff --git a/src/core/openApiSettings.ts b/src/core/openapi/openApiSettings.ts similarity index 100% rename from src/core/openApiSettings.ts rename to src/core/openapi/openApiSettings.ts diff --git a/src/core/parse-apex-metadata.ts b/src/core/reflection/apex/parse-apex-metadata.ts similarity index 100% rename from src/core/parse-apex-metadata.ts rename to src/core/reflection/apex/parse-apex-metadata.ts diff --git a/src/core/reflection/apex/reflect-apex-source.ts b/src/core/reflection/apex/reflect-apex-source.ts index 44cf16e8..67206fcb 100644 --- a/src/core/reflection/apex/reflect-apex-source.ts +++ b/src/core/reflection/apex/reflect-apex-source.ts @@ -10,7 +10,7 @@ import { apply } from '#utils/fp'; import { Semigroup } from 'fp-ts/Semigroup'; import { ParsedFile, UnparsedApexBundle } from '../../shared/types'; import { ReflectionError, ReflectionErrors } from '../../errors/errors'; -import { parseApexMetadata } from '../../parse-apex-metadata'; +import { parseApexMetadata } from './parse-apex-metadata'; async function reflectAsync(rawSource: string): Promise { return new Promise((resolve, reject) => { diff --git a/src/core/reflection/sobject/reflect-sobject-source.ts b/src/core/reflection/sobject/reflect-sobject-source.ts index b480334e..6fcfff0b 100644 --- a/src/core/reflection/sobject/reflect-sobject-source.ts +++ b/src/core/reflection/sobject/reflect-sobject-source.ts @@ -19,10 +19,10 @@ export function reflectSObjectSources(objectSources: UnparsedSObjectBundle[]) { }; const Ap = TE.getApplicativeTaskValidation(T.ApplyPar, semiGroupReflectionError); - return pipe(objectSources, A.traverse(Ap)(reflectSobjectSource)); + return pipe(objectSources, A.traverse(Ap)(reflectSObjectSource)); } -function reflectSobjectSource(objectSource: UnparsedSObjectBundle): TE.TaskEither { +function reflectSObjectSource(objectSource: UnparsedSObjectBundle): TE.TaskEither { return pipe( TE.tryCatch( () => new XMLParser().parse(objectSource.content), diff --git a/src/test-helpers/SettingsBuilder.ts b/src/test-helpers/SettingsBuilder.ts index 72738598..64e992aa 100644 --- a/src/test-helpers/SettingsBuilder.ts +++ b/src/test-helpers/SettingsBuilder.ts @@ -1,4 +1,4 @@ -import { SettingsConfig } from '../core/openApiSettings'; +import { SettingsConfig } from '../core/openapi/openApiSettings'; /** * Builder class to create SettingsConfig objects. From ccd9c4eed5b5e9d1baf9c8109f6845a1798e6e45 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Thu, 17 Oct 2024 14:42:25 -0400 Subject: [PATCH 11/32] When parsing SObject metadata -> the resulting type is sobject --- .../__tests__/source-code-file-reader.spec.ts | 2 +- src/application/source-code-file-reader.ts | 2 +- src/core/markdown/adapters/apex-types.ts | 2 +- .../markdown/adapters/renderable-bundle.ts | 2 +- src/core/markdown/generate-docs.ts | 2 +- .../__test__/reflect-sobject-source.spec.ts | 29 +++++++++++++++++++ .../sobject/reflect-sobject-source.ts | 11 ++++--- src/core/shared/types.d.ts | 4 +-- src/core/shared/utils.ts | 6 ++-- 9 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts diff --git a/src/application/__tests__/source-code-file-reader.spec.ts b/src/application/__tests__/source-code-file-reader.spec.ts index c4485c06..ba187375 100644 --- a/src/application/__tests__/source-code-file-reader.spec.ts +++ b/src/application/__tests__/source-code-file-reader.spec.ts @@ -131,7 +131,7 @@ describe('File Reader', () => { Deployed - test object with one field for eclipse ide testing + test object for testing MyFirstObjects `; diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts index 9f685f21..d3882f10 100644 --- a/src/application/source-code-file-reader.ts +++ b/src/application/source-code-file-reader.ts @@ -94,6 +94,6 @@ class ObjectFileReader implements FileProcessor { async process(fileSystem: FileSystem, filePath: string): Promise { const rawTypeContent = await fileSystem.readFile(filePath); - return { type: 'object', filePath, content: rawTypeContent }; + return { type: 'sobject', filePath, content: rawTypeContent }; } } diff --git a/src/core/markdown/adapters/apex-types.ts b/src/core/markdown/adapters/apex-types.ts index d9513f8c..d4c482ab 100644 --- a/src/core/markdown/adapters/apex-types.ts +++ b/src/core/markdown/adapters/apex-types.ts @@ -40,7 +40,7 @@ export function typeToRenderable( return interfaceTypeToInterfaceSource(type as InterfaceMirror, linkGenerator); case 'class': return classTypeToClassSource(type as ClassMirrorWithInheritanceChain, linkGenerator); - case 'object': + case 'sobject': throw new Error('Not implemented'); } } diff --git a/src/core/markdown/adapters/renderable-bundle.ts b/src/core/markdown/adapters/renderable-bundle.ts index 13db4a6a..8133909b 100644 --- a/src/core/markdown/adapters/renderable-bundle.ts +++ b/src/core/markdown/adapters/renderable-bundle.ts @@ -68,7 +68,7 @@ function getRenderableDescription( findLinkFromHome: (referenceName: string) => string | Link, ): RenderableContent[] | null { switch (type.type_name) { - case 'object': + case 'sobject': // TODO return null; default: diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index 1a8a71f8..77ed7d35 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -57,7 +57,7 @@ export function generateDocs(unparsedApexFiles: UnparsedSourceBundle[], config: } function filterObjectSourceFiles(sourceFiles: UnparsedSourceBundle[]): UnparsedSObjectBundle[] { - return sourceFiles.filter((sourceFile): sourceFile is UnparsedSObjectBundle => sourceFile.type === 'object'); + return sourceFiles.filter((sourceFile): sourceFile is UnparsedSObjectBundle => sourceFile.type === 'sobject'); } return pipe( diff --git a/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts b/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts new file mode 100644 index 00000000..d9a7d575 --- /dev/null +++ b/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts @@ -0,0 +1,29 @@ +import { reflectSObjectSources } from '../reflect-sobject-source'; +import { UnparsedSObjectBundle } from '../../../shared/types'; +import { assertEither } from '../../../test-helpers/assert-either'; + +const sObjectContent = ` + + + Deployed + test object for testing + + MyFirstObjects + `; + +describe('when parsing SObject metadata', () => { + test('the resulting type is "sobject"', async () => { + const unparsed: UnparsedSObjectBundle = { + type: 'sobject', + filePath: 'src/object/MyFirstObject__c.object-meta.xml', + content: sObjectContent, + }; + + const result = await reflectSObjectSources([unparsed])(); + + assertEither(result, (data) => expect(data.length).toBe(1)); + assertEither(result, (data) => expect(data[0].source.type).toBe('sobject')); + }); +}); + +// TODO: Test that things are validated diff --git a/src/core/reflection/sobject/reflect-sobject-source.ts b/src/core/reflection/sobject/reflect-sobject-source.ts index 6fcfff0b..a9942ca7 100644 --- a/src/core/reflection/sobject/reflect-sobject-source.ts +++ b/src/core/reflection/sobject/reflect-sobject-source.ts @@ -6,9 +6,10 @@ import { Semigroup } from 'fp-ts/Semigroup'; import * as T from 'fp-ts/Task'; import { pipe } from 'fp-ts/function'; import * as A from 'fp-ts/Array'; +import * as E from 'fp-ts/Either'; export type ObjectMetadata = { - type_name: 'object'; + type_name: 'sobject'; label: string; name: string; }; @@ -24,13 +25,11 @@ export function reflectSObjectSources(objectSources: UnparsedSObjectBundle[]) { function reflectSObjectSource(objectSource: UnparsedSObjectBundle): TE.TaskEither { return pipe( - TE.tryCatch( - () => new XMLParser().parse(objectSource.content), - (error) => new ReflectionErrors([new ReflectionError(objectSource.filePath, (error as Error).message)]), - ), + TE.fromEither(E.tryCatch(() => new XMLParser().parse(objectSource.content), E.toError)), TE.map((metadata) => addName(metadata as ObjectMetadata, objectSource.filePath)), TE.map((metadata) => addTypeName(metadata)), TE.map((metadata) => toParsedFile(objectSource.filePath, metadata)), + TE.mapLeft((error) => new ReflectionErrors([new ReflectionError(objectSource.filePath, error.message)])), ); } @@ -48,7 +47,7 @@ function addName(objectMetadata: ObjectMetadata, filePath: string): ObjectMetada function addTypeName(objectMetadata: ObjectMetadata): ObjectMetadata { return { ...objectMetadata, - type_name: 'object', + type_name: 'sobject', }; } diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index 0c581c52..c3d87c82 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -58,7 +58,7 @@ export type UserDefinedConfig = UserDefinedMarkdownConfig | UserDefinedOpenApiCo export type UnparsedSourceBundle = UnparsedApexBundle | UnparsedSObjectBundle; export type UnparsedSObjectBundle = { - type: 'object'; + type: 'sobject'; filePath: string; content: string; }; @@ -73,7 +73,7 @@ export type UnparsedApexBundle = { export type SourceFileMetadata = { filePath: string; name: string; - type: 'interface' | 'class' | 'enum' | 'object'; + type: 'interface' | 'class' | 'enum' | 'sobject'; }; export type ParsedFile = { diff --git a/src/core/shared/utils.ts b/src/core/shared/utils.ts index c987e78f..88acc614 100644 --- a/src/core/shared/utils.ts +++ b/src/core/shared/utils.ts @@ -17,7 +17,7 @@ export function isSkip(value: unknown): value is Skip { } export function isObjectType(type: Type | ObjectMetadata): type is ObjectMetadata { - return (type as ObjectMetadata).type_name === 'object'; + return (type as ObjectMetadata).type_name === 'sobject'; } export function isApexType(type: Type | ObjectMetadata): type is Type { @@ -33,8 +33,8 @@ export function getTypeGroup(type: Type | ObjectMetadata, config: MarkdownGenera } switch (type.type_name) { - case 'object': - return 'Objects'; // TODO: Make configurable? + case 'sobject': + return 'SObjects'; // TODO: Make configurable? default: return getGroup(type, config); } From f118f5a6649cee9871b0f59cbec4770c8fd23f4b Mon Sep 17 00:00:00 2001 From: cesarParra Date: Thu, 17 Oct 2024 16:25:20 -0400 Subject: [PATCH 12/32] When parsing SObject metadata tests --- .../__test__/reflect-sobject-source.spec.ts | 115 ++++++++++++++++++ .../sobject/reflect-sobject-source.ts | 25 +++- 2 files changed, 135 insertions(+), 5 deletions(-) diff --git a/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts b/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts index d9a7d575..d1b803f0 100644 --- a/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts +++ b/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts @@ -9,6 +9,7 @@ const sObjectContent = ` test object for testing MyFirstObjects + Public `; describe('when parsing SObject metadata', () => { @@ -23,6 +24,120 @@ describe('when parsing SObject metadata', () => { assertEither(result, (data) => expect(data.length).toBe(1)); assertEither(result, (data) => expect(data[0].source.type).toBe('sobject')); + assertEither(result, (data) => expect(data[0].type.type_name).toBe('sobject')); + }); + + test('the resulting type contains the file path', async () => { + const unparsed: UnparsedSObjectBundle = { + type: 'sobject', + filePath: 'src/object/MyFirstObject__c.object-meta.xml', + content: sObjectContent, + }; + + const result = await reflectSObjectSources([unparsed])(); + + assertEither(result, (data) => expect(data[0].source.filePath).toBe('src/object/MyFirstObject__c.object-meta.xml')); + }); + + test('the resulting type contains the correct label', async () => { + const unparsed: UnparsedSObjectBundle = { + type: 'sobject', + filePath: 'src/object/MyFirstObject__c.object-meta.xml', + content: sObjectContent, + }; + + const result = await reflectSObjectSources([unparsed])(); + + assertEither(result, (data) => { + expect(data[0].type.label).toBe('MyFirstObject'); + }); + }); + + test('the resulting type contains the correct name', async () => { + const unparsed: UnparsedSObjectBundle = { + type: 'sobject', + filePath: 'src/object/MyFirstObject__c.object-meta.xml', + content: sObjectContent, + }; + + const result = await reflectSObjectSources([unparsed])(); + + assertEither(result, (data) => { + expect(data[0].type.name).toBe('MyFirstObject__c'); + }); + }); + + test('the resulting type contains the deployment status', async () => { + const unparsed: UnparsedSObjectBundle = { + type: 'sobject', + filePath: 'src/object/MyFirstObject__c.object-meta.xml', + content: sObjectContent, + }; + + const result = await reflectSObjectSources([unparsed])(); + + assertEither(result, (data) => { + expect(data[0].type.deploymentStatus).toBe('Deployed'); + }); + }); + + test('the deployment status is "Deployed" by default', async () => { + const sObjectContent = ` + + + test object for testing + + MyFirstObjects + `; + + const unparsed: UnparsedSObjectBundle = { + type: 'sobject', + filePath: 'src/object/MyFirstObject__c.object-meta.xml', + content: sObjectContent, + }; + + const result = await reflectSObjectSources([unparsed])(); + + assertEither(result, (data) => { + expect(data[0].type.deploymentStatus).toBe('Deployed'); + }); + }); + + test('the resulting type contains the visibility', async () => { + const unparsed: UnparsedSObjectBundle = { + type: 'sobject', + filePath: 'src/object/MyFirstObject__c.object-meta.xml', + content: sObjectContent, + }; + + const result = await reflectSObjectSources([unparsed])(); + + assertEither(result, (data) => { + expect(data[0].type.visibility).toBe('Public'); + }); + }); + + test('the visibility is "Public" by default', async () => { + const sObjectContent = ` + + + Deployed + test object for testing + + MyFirstObjects + `; + + const unparsed: UnparsedSObjectBundle = { + type: 'sobject', + filePath: 'src/object/MyFirstObject__c.object-meta.xml', + content: sObjectContent, + }; + + const result = await reflectSObjectSources([unparsed])(); + + assertEither(result, (data) => { + expect(data[0].type.visibility).toBe('Public'); + }); }); }); diff --git a/src/core/reflection/sobject/reflect-sobject-source.ts b/src/core/reflection/sobject/reflect-sobject-source.ts index a9942ca7..6bf9a296 100644 --- a/src/core/reflection/sobject/reflect-sobject-source.ts +++ b/src/core/reflection/sobject/reflect-sobject-source.ts @@ -10,11 +10,15 @@ import * as E from 'fp-ts/Either'; export type ObjectMetadata = { type_name: 'sobject'; + deploymentStatus: string; + visibility: string; label: string; name: string; }; -export function reflectSObjectSources(objectSources: UnparsedSObjectBundle[]) { +export function reflectSObjectSources( + objectSources: UnparsedSObjectBundle[], +): TE.TaskEither[]> { const semiGroupReflectionError: Semigroup = { concat: (x, y) => new ReflectionErrors([...x.errors, ...y.errors]), }; @@ -23,16 +27,27 @@ export function reflectSObjectSources(objectSources: UnparsedSObjectBundle[]) { return pipe(objectSources, A.traverse(Ap)(reflectSObjectSource)); } -function reflectSObjectSource(objectSource: UnparsedSObjectBundle): TE.TaskEither { +function reflectSObjectSource( + objectSource: UnparsedSObjectBundle, +): TE.TaskEither> { return pipe( TE.fromEither(E.tryCatch(() => new XMLParser().parse(objectSource.content), E.toError)), - TE.map((metadata) => addName(metadata as ObjectMetadata, objectSource.filePath)), + TE.map(toObjectMetadata), + TE.map((metadata) => addName(metadata, objectSource.filePath)), TE.map((metadata) => addTypeName(metadata)), TE.map((metadata) => toParsedFile(objectSource.filePath, metadata)), TE.mapLeft((error) => new ReflectionErrors([new ReflectionError(objectSource.filePath, error.message)])), ); } +function toObjectMetadata(parserResult: { CustomObject: object }): ObjectMetadata { + const defaultValues = { + deploymentStatus: 'Deployed', + visibility: 'Public', + }; + return { ...defaultValues, ...parserResult.CustomObject } as ObjectMetadata; +} + function extractNameFromFilePath(filePath: string): string { return filePath.split('/').pop()!.split('.').shift()!; } @@ -51,11 +66,11 @@ function addTypeName(objectMetadata: ObjectMetadata): ObjectMetadata { }; } -function toParsedFile(filePath: string, typeMirror: ObjectMetadata): ParsedFile { +function toParsedFile(filePath: string, typeMirror: ObjectMetadata): ParsedFile { return { source: { filePath: filePath, - name: typeMirror.label, + name: typeMirror.name, type: typeMirror.type_name, }, type: typeMirror, From f336f37179773a5bfd8dd8489e587e8d05f47ab4 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Fri, 18 Oct 2024 06:41:45 -0400 Subject: [PATCH 13/32] Validating SObject metadata --- .../__test__/reflect-sobject-source.spec.ts | 44 +++++++++++++++++++ .../sobject/reflect-sobject-source.ts | 36 ++++++++++++--- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts b/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts index d1b803f0..16993aa5 100644 --- a/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts +++ b/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts @@ -1,6 +1,7 @@ import { reflectSObjectSources } from '../reflect-sobject-source'; import { UnparsedSObjectBundle } from '../../../shared/types'; import { assertEither } from '../../../test-helpers/assert-either'; +import * as E from 'fp-ts/Either'; const sObjectContent = ` @@ -139,6 +140,49 @@ describe('when parsing SObject metadata', () => { expect(data[0].type.visibility).toBe('Public'); }); }); + + test('an error is thrown when the XML is in an invalid format', async () => { + const sObjectContent = ` + + + Deployed + test object for testing + + MyFirstObjects + Public + `; + + const unparsed: UnparsedSObjectBundle = { + type: 'sobject', + filePath: 'src/object/MyFirstObject__c.object-meta.xml', + content: sObjectContent, + }; + + const result = await reflectSObjectSources([unparsed])(); + + expect(E.isLeft(result)).toBe(true); + }); + + test('an error is thrown when the label is missing', async () => { + const sObjectContent = ` + + + Deployed + test object for testing + MyFirstObjects + Public + `; + + const unparsed: UnparsedSObjectBundle = { + type: 'sobject', + filePath: 'src/object/MyFirstObject__c.object-meta.xml', + content: sObjectContent, + }; + + const result = await reflectSObjectSources([unparsed])(); + + expect(E.isLeft(result)).toBe(true); + }); }); // TODO: Test that things are validated diff --git a/src/core/reflection/sobject/reflect-sobject-source.ts b/src/core/reflection/sobject/reflect-sobject-source.ts index 6bf9a296..d86cd562 100644 --- a/src/core/reflection/sobject/reflect-sobject-source.ts +++ b/src/core/reflection/sobject/reflect-sobject-source.ts @@ -31,15 +31,39 @@ function reflectSObjectSource( objectSource: UnparsedSObjectBundle, ): TE.TaskEither> { return pipe( - TE.fromEither(E.tryCatch(() => new XMLParser().parse(objectSource.content), E.toError)), - TE.map(toObjectMetadata), - TE.map((metadata) => addName(metadata, objectSource.filePath)), - TE.map((metadata) => addTypeName(metadata)), - TE.map((metadata) => toParsedFile(objectSource.filePath, metadata)), - TE.mapLeft((error) => new ReflectionErrors([new ReflectionError(objectSource.filePath, error.message)])), + E.tryCatch(() => new XMLParser().parse(objectSource.content), E.toError), + E.flatMap(validate), + E.map(toObjectMetadata), + E.map((metadata) => addName(metadata, objectSource.filePath)), + E.map(addTypeName), + E.map((metadata) => toParsedFile(objectSource.filePath, metadata)), + E.mapLeft((error) => new ReflectionErrors([new ReflectionError(objectSource.filePath, error.message)])), + TE.fromEither, ); } +function validate(parseResult: unknown): E.Either { + if (typeof parseResult !== 'object' || parseResult === null) { + return E.left(new Error('Invalid SObject metadata')); + } + + // Confirm that the object has a CustomObject property + if (!('CustomObject' in parseResult)) { + return E.left(new Error('Invalid SObject metadata')); + } + + // Confirm that the CustomObject property is an object that contains that "label" property + if (typeof (parseResult as { CustomObject: object }).CustomObject !== 'object') { + return E.left(new Error('Invalid SObject metadata')); + } + + if (!('label' in (parseResult as { CustomObject: object }).CustomObject)) { + return E.left(new Error('Invalid SObject metadata')); + } + + return E.right(parseResult as { CustomObject: object }); +} + function toObjectMetadata(parserResult: { CustomObject: object }): ObjectMetadata { const defaultValues = { deploymentStatus: 'Deployed', From 1d307880658933951254846e54bf97647bb9c1ea Mon Sep 17 00:00:00 2001 From: cesarParra Date: Fri, 18 Oct 2024 07:01:58 -0400 Subject: [PATCH 14/32] Refactoring validation into FP --- .../sobject/reflect-sobject-source.ts | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/core/reflection/sobject/reflect-sobject-source.ts b/src/core/reflection/sobject/reflect-sobject-source.ts index d86cd562..1f43c49b 100644 --- a/src/core/reflection/sobject/reflect-sobject-source.ts +++ b/src/core/reflection/sobject/reflect-sobject-source.ts @@ -43,25 +43,30 @@ function reflectSObjectSource( } function validate(parseResult: unknown): E.Either { - if (typeof parseResult !== 'object' || parseResult === null) { - return E.left(new Error('Invalid SObject metadata')); + const err = E.left(new Error('Invalid SObject metadata')); + function isObject(value: unknown) { + return typeof value === 'object' && value !== null ? E.right(value) : err; } - // Confirm that the object has a CustomObject property - if (!('CustomObject' in parseResult)) { - return E.left(new Error('Invalid SObject metadata')); + function hasTheCustomObjectKey(value: object) { + return 'CustomObject' in value ? E.right(value) : err; } - // Confirm that the CustomObject property is an object that contains that "label" property - if (typeof (parseResult as { CustomObject: object }).CustomObject !== 'object') { - return E.left(new Error('Invalid SObject metadata')); + function theCustomObjectKeyIsAnObject(value: Record<'CustomObject', unknown>) { + return typeof value.CustomObject === 'object' ? E.right(value as Record<'CustomObject', object>) : err; } - if (!('label' in (parseResult as { CustomObject: object }).CustomObject)) { - return E.left(new Error('Invalid SObject metadata')); + function theCustomObjectContainsTheLabelKey(value: Record<'CustomObject', object>) { + return 'label' in value.CustomObject ? E.right(value) : err; } - return E.right(parseResult as { CustomObject: object }); + return pipe( + parseResult, + isObject, + E.chain(hasTheCustomObjectKey), + E.chain(theCustomObjectKeyIsAnObject), + E.chain(theCustomObjectContainsTheLabelKey), + ); } function toObjectMetadata(parserResult: { CustomObject: object }): ObjectMetadata { From 6a7f2ae0885c92fe578493ec205726ee6f058f9f Mon Sep 17 00:00:00 2001 From: cesarParra Date: Fri, 18 Oct 2024 11:25:45 -0400 Subject: [PATCH 15/32] Introducing @salesforce/source-deploy-retrieve as a dependency --- examples/vitepress/apexdocs.config.ts | 1 + .../default/classes/BaseClass.cls-meta.xml | 5 + .../MultiInheritanceClass.cls-meta.xml | 5 + .../classes/ParentInterface.cls-meta.xml | 5 + .../classes/ReferencedEnum.cls-meta.xml | 5 + .../main/default/classes/Url.cls-meta.xml | 5 + .../feature-a/SampleClass.cls-meta.xml | 5 + .../classes/feature-a/SampleEnum.cls-meta.xml | 5 + .../feature-a/SampleException.cls-meta.xml | 5 + .../feature-a/SampleInterface.cls-meta.xml | 5 + .../Contact/fields/PhotoUrl__c.field-meta.xml | 9 + .../objects/Event__c/Event__c.object-meta.xml | 166 ++ .../fields/Description__c.field-meta.xml | 10 + .../fields/End_Date__c.field-meta.xml | 9 + .../fields/Location__c.field-meta.xml | 11 + .../fields/Start_Date__c.field-meta.xml | 9 + .../fields/Tag_Line__c.field-meta.xml | 11 + .../Event__c/listViews/All.listView-meta.xml | 6 + .../Price_Component__c.object-meta.xml | 169 ++ .../fields/Description__c.field-meta.xml | 11 + .../fields/Expression__c.field-meta.xml | 12 + .../fields/Percent__c.field-meta.xml | 13 + .../fields/Price__c.field-meta.xml | 13 + .../fields/Type__c.field-meta.xml | 30 + ...Product_Price_Component__c.object-meta.xml | 166 ++ .../fields/Price_Component__c.field-meta.xml | 14 + .../fields/Product__c.field-meta.xml | 14 + .../Product__c/Product__c.object-meta.xml | 168 ++ .../fields/Description__c.field-meta.xml | 11 + .../Product__c/fields/Event__c.field-meta.xml | 12 + .../fields/Features__c.field-meta.xml | 10 + .../Sales_Order_Line__c.object-meta.xml | 166 ++ .../fields/Amount__c.field-meta.xml | 11 + .../fields/Product__c.field-meta.xml | 13 + .../fields/Sales_Order__c.field-meta.xml | 14 + .../Source_Price_Component__c.field-meta.xml | 13 + .../fields/Type__c.field-meta.xml | 26 + .../Sales_Order__c.object-meta.xml | 169 ++ .../listViews/All.listView-meta.xml | 6 + .../Speaker__c/Speaker__c.object-meta.xml | 166 ++ .../Speaker__c/fields/About__c.field-meta.xml | 10 + .../Speaker__c/fields/Event__c.field-meta.xml | 14 + .../fields/Person__c.field-meta.xml | 14 + package-lock.json | 2085 ++++++++++++++++- package.json | 1 + src/application/Apexdocs.ts | 22 +- src/application/file-system.ts | 64 +- src/application/source-code-file-reader.ts | 179 +- 48 files changed, 3683 insertions(+), 210 deletions(-) create mode 100644 examples/vitepress/force-app/main/default/classes/BaseClass.cls-meta.xml create mode 100644 examples/vitepress/force-app/main/default/classes/MultiInheritanceClass.cls-meta.xml create mode 100644 examples/vitepress/force-app/main/default/classes/ParentInterface.cls-meta.xml create mode 100644 examples/vitepress/force-app/main/default/classes/ReferencedEnum.cls-meta.xml create mode 100644 examples/vitepress/force-app/main/default/classes/Url.cls-meta.xml create mode 100644 examples/vitepress/force-app/main/default/classes/feature-a/SampleClass.cls-meta.xml create mode 100644 examples/vitepress/force-app/main/default/classes/feature-a/SampleEnum.cls-meta.xml create mode 100644 examples/vitepress/force-app/main/default/classes/feature-a/SampleException.cls-meta.xml create mode 100644 examples/vitepress/force-app/main/default/classes/feature-a/SampleInterface.cls-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Contact/fields/PhotoUrl__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Event__c/Event__c.object-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Event__c/fields/Description__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Event__c/fields/End_Date__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Event__c/fields/Location__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Event__c/fields/Start_Date__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Event__c/fields/Tag_Line__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Event__c/listViews/All.listView-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Price_Component__c/Price_Component__c.object-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Description__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Expression__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Percent__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Price__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Type__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Product_Price_Component__c/Product_Price_Component__c.object-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Product_Price_Component__c/fields/Price_Component__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Product_Price_Component__c/fields/Product__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Product__c/Product__c.object-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Product__c/fields/Description__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Product__c/fields/Event__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Product__c/fields/Features__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/Sales_Order_Line__c.object-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Amount__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Product__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Sales_Order__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Source_Price_Component__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Type__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Sales_Order__c/Sales_Order__c.object-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Sales_Order__c/listViews/All.listView-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Speaker__c/Speaker__c.object-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Speaker__c/fields/About__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Speaker__c/fields/Event__c.field-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/Speaker__c/fields/Person__c.field-meta.xml diff --git a/examples/vitepress/apexdocs.config.ts b/examples/vitepress/apexdocs.config.ts index 4a76b64f..9cd727a9 100644 --- a/examples/vitepress/apexdocs.config.ts +++ b/examples/vitepress/apexdocs.config.ts @@ -33,6 +33,7 @@ export default { }), markdown: defineMarkdownConfig({ sourceDir: 'force-app', + includeMetadata: false, scope: ['global', 'public', 'protected', 'private', 'namespaceaccessible'], sortAlphabetically: true, namespace: 'apexdocs', diff --git a/examples/vitepress/force-app/main/default/classes/BaseClass.cls-meta.xml b/examples/vitepress/force-app/main/default/classes/BaseClass.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/vitepress/force-app/main/default/classes/BaseClass.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/vitepress/force-app/main/default/classes/MultiInheritanceClass.cls-meta.xml b/examples/vitepress/force-app/main/default/classes/MultiInheritanceClass.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/vitepress/force-app/main/default/classes/MultiInheritanceClass.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/vitepress/force-app/main/default/classes/ParentInterface.cls-meta.xml b/examples/vitepress/force-app/main/default/classes/ParentInterface.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/vitepress/force-app/main/default/classes/ParentInterface.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/vitepress/force-app/main/default/classes/ReferencedEnum.cls-meta.xml b/examples/vitepress/force-app/main/default/classes/ReferencedEnum.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/vitepress/force-app/main/default/classes/ReferencedEnum.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/vitepress/force-app/main/default/classes/Url.cls-meta.xml b/examples/vitepress/force-app/main/default/classes/Url.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/vitepress/force-app/main/default/classes/Url.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/vitepress/force-app/main/default/classes/feature-a/SampleClass.cls-meta.xml b/examples/vitepress/force-app/main/default/classes/feature-a/SampleClass.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/vitepress/force-app/main/default/classes/feature-a/SampleClass.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/vitepress/force-app/main/default/classes/feature-a/SampleEnum.cls-meta.xml b/examples/vitepress/force-app/main/default/classes/feature-a/SampleEnum.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/vitepress/force-app/main/default/classes/feature-a/SampleEnum.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/vitepress/force-app/main/default/classes/feature-a/SampleException.cls-meta.xml b/examples/vitepress/force-app/main/default/classes/feature-a/SampleException.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/vitepress/force-app/main/default/classes/feature-a/SampleException.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/vitepress/force-app/main/default/classes/feature-a/SampleInterface.cls-meta.xml b/examples/vitepress/force-app/main/default/classes/feature-a/SampleInterface.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/vitepress/force-app/main/default/classes/feature-a/SampleInterface.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/vitepress/force-app/main/default/objects/Contact/fields/PhotoUrl__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Contact/fields/PhotoUrl__c.field-meta.xml new file mode 100644 index 00000000..a9117781 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Contact/fields/PhotoUrl__c.field-meta.xml @@ -0,0 +1,9 @@ + + + PhotoUrl__c + false + + false + false + Url + diff --git a/examples/vitepress/force-app/main/default/objects/Event__c/Event__c.object-meta.xml b/examples/vitepress/force-app/main/default/objects/Event__c/Event__c.object-meta.xml new file mode 100644 index 00000000..7197afcb --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Event__c/Event__c.object-meta.xml @@ -0,0 +1,166 @@ + + + + Accept + Default + + + Accept + Large + Default + + + Accept + Small + Default + + + CancelEdit + Default + + + CancelEdit + Large + Default + + + CancelEdit + Small + Default + + + Clone + Default + + + Clone + Large + Default + + + Clone + Small + Default + + + Delete + Default + + + Delete + Large + Default + + + Delete + Small + Default + + + Edit + Default + + + Edit + Large + Default + + + Edit + Small + Default + + + List + Default + + + List + Large + Default + + + List + Small + Default + + + New + Default + + + New + Large + Default + + + New + Small + Default + + + SaveEdit + Default + + + SaveEdit + Large + Default + + + SaveEdit + Small + Default + + + Tab + Default + + + Tab + Large + Default + + + Tab + Small + Default + + + View + Default + + + View + Large + Default + + + View + Small + Default + + false + SYSTEM + Deployed + false + true + false + false + false + false + false + true + true + Private + + + + Text + + Events + + ReadWrite + Vowel + Public + diff --git a/examples/vitepress/force-app/main/default/objects/Event__c/fields/Description__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Event__c/fields/Description__c.field-meta.xml new file mode 100644 index 00000000..c1b682a4 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Event__c/fields/Description__c.field-meta.xml @@ -0,0 +1,10 @@ + + + Description__c + false + + 32768 + false + LongTextArea + 10 + diff --git a/examples/vitepress/force-app/main/default/objects/Event__c/fields/End_Date__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Event__c/fields/End_Date__c.field-meta.xml new file mode 100644 index 00000000..422a0003 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Event__c/fields/End_Date__c.field-meta.xml @@ -0,0 +1,9 @@ + + + End_Date__c + false + + true + false + Date + diff --git a/examples/vitepress/force-app/main/default/objects/Event__c/fields/Location__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Event__c/fields/Location__c.field-meta.xml new file mode 100644 index 00000000..b8f32121 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Event__c/fields/Location__c.field-meta.xml @@ -0,0 +1,11 @@ + + + Location__c + false + false + + true + 3 + false + Location + diff --git a/examples/vitepress/force-app/main/default/objects/Event__c/fields/Start_Date__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Event__c/fields/Start_Date__c.field-meta.xml new file mode 100644 index 00000000..81fb3f6d --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Event__c/fields/Start_Date__c.field-meta.xml @@ -0,0 +1,9 @@ + + + Start_Date__c + false + + true + false + Date + diff --git a/examples/vitepress/force-app/main/default/objects/Event__c/fields/Tag_Line__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Event__c/fields/Tag_Line__c.field-meta.xml new file mode 100644 index 00000000..652ee2e0 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Event__c/fields/Tag_Line__c.field-meta.xml @@ -0,0 +1,11 @@ + + + Tag_Line__c + false + + 255 + false + false + Text + false + diff --git a/examples/vitepress/force-app/main/default/objects/Event__c/listViews/All.listView-meta.xml b/examples/vitepress/force-app/main/default/objects/Event__c/listViews/All.listView-meta.xml new file mode 100644 index 00000000..d5058512 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Event__c/listViews/All.listView-meta.xml @@ -0,0 +1,6 @@ + + + All + Everything + + diff --git a/examples/vitepress/force-app/main/default/objects/Price_Component__c/Price_Component__c.object-meta.xml b/examples/vitepress/force-app/main/default/objects/Price_Component__c/Price_Component__c.object-meta.xml new file mode 100644 index 00000000..ae72fd0c --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Price_Component__c/Price_Component__c.object-meta.xml @@ -0,0 +1,169 @@ + + + + Accept + Default + + + Accept + Large + Default + + + Accept + Small + Default + + + CancelEdit + Default + + + CancelEdit + Large + Default + + + CancelEdit + Small + Default + + + Clone + Default + + + Clone + Large + Default + + + Clone + Small + Default + + + Delete + Default + + + Delete + Large + Default + + + Delete + Small + Default + + + Edit + Default + + + Edit + Large + Default + + + Edit + Small + Default + + + List + Default + + + List + Large + Default + + + List + Small + Default + + + New + Default + + + New + Large + Default + + + New + Small + Default + + + SaveEdit + Default + + + SaveEdit + Large + Default + + + SaveEdit + Small + Default + + + Tab + Default + + + Tab + Large + Default + + + Tab + Small + Default + + + View + Action override created by Lightning App Builder during activation. + Price_Component_Record_Page + Large + false + Flexipage + + + View + Default + + + View + Small + Default + + false + SYSTEM + Deployed + false + true + false + false + false + false + false + true + true + Private + + + PC-{0000} + + AutoNumber + + Price Components + + ReadWrite + Public + diff --git a/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Description__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Description__c.field-meta.xml new file mode 100644 index 00000000..69050ca6 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Description__c.field-meta.xml @@ -0,0 +1,11 @@ + + + Description__c + false + + 255 + false + false + Text + false + diff --git a/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Expression__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Expression__c.field-meta.xml new file mode 100644 index 00000000..c0bf4e45 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Expression__c.field-meta.xml @@ -0,0 +1,12 @@ + + + Expression__c + The Expression that determines if this price should take effect or not. + false + The Expression that determines if this price should take effect or not. + + 131072 + false + LongTextArea + 20 + diff --git a/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Percent__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Percent__c.field-meta.xml new file mode 100644 index 00000000..9c303bc4 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Percent__c.field-meta.xml @@ -0,0 +1,13 @@ + + + Percent__c + Use this field to calculate the price based on the list price's percentage instead of providing a flat price. + false + Use this field to calculate the price based on the list price's percentage instead of providing a flat price. + + 18 + false + 0 + false + Percent + diff --git a/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Price__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Price__c.field-meta.xml new file mode 100644 index 00000000..84136dec --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Price__c.field-meta.xml @@ -0,0 +1,13 @@ + + + Price__c + Use this when the Price Component represents a Flat Price. To represent a Percentage use the Percent field. + false + Use this when the Price Component represents a Flat Price. To represent a Percentage use the Percent field. + + 18 + false + 2 + false + Currency + diff --git a/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Type__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Type__c.field-meta.xml new file mode 100644 index 00000000..c430b305 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Price_Component__c/fields/Type__c.field-meta.xml @@ -0,0 +1,30 @@ + + + Type__c + false + + true + false + Picklist + + true + + false + + List Price + false + + + + Surcharge + false + + + + Discount + false + + + + + diff --git a/examples/vitepress/force-app/main/default/objects/Product_Price_Component__c/Product_Price_Component__c.object-meta.xml b/examples/vitepress/force-app/main/default/objects/Product_Price_Component__c/Product_Price_Component__c.object-meta.xml new file mode 100644 index 00000000..8a9a6348 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Product_Price_Component__c/Product_Price_Component__c.object-meta.xml @@ -0,0 +1,166 @@ + + + + Accept + Default + + + Accept + Large + Default + + + Accept + Small + Default + + + CancelEdit + Default + + + CancelEdit + Large + Default + + + CancelEdit + Small + Default + + + Clone + Default + + + Clone + Large + Default + + + Clone + Small + Default + + + Delete + Default + + + Delete + Large + Default + + + Delete + Small + Default + + + Edit + Default + + + Edit + Large + Default + + + Edit + Small + Default + + + List + Default + + + List + Large + Default + + + List + Small + Default + + + New + Default + + + New + Large + Default + + + New + Small + Default + + + SaveEdit + Default + + + SaveEdit + Large + Default + + + SaveEdit + Small + Default + + + Tab + Default + + + Tab + Large + Default + + + Tab + Small + Default + + + View + Default + + + View + Large + Default + + + View + Small + Default + + false + SYSTEM + Deployed + false + true + false + false + false + false + false + true + true + ControlledByParent + + + PPC-{0000} + + AutoNumber + + Product Price Components + + ControlledByParent + Public + diff --git a/examples/vitepress/force-app/main/default/objects/Product_Price_Component__c/fields/Price_Component__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Product_Price_Component__c/fields/Price_Component__c.field-meta.xml new file mode 100644 index 00000000..f152ecb6 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Product_Price_Component__c/fields/Price_Component__c.field-meta.xml @@ -0,0 +1,14 @@ + + + Price_Component__c + false + + Price_Component__c + Product Price Components + Product_Price_Components + 1 + false + false + MasterDetail + false + diff --git a/examples/vitepress/force-app/main/default/objects/Product_Price_Component__c/fields/Product__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Product_Price_Component__c/fields/Product__c.field-meta.xml new file mode 100644 index 00000000..16ec5b33 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Product_Price_Component__c/fields/Product__c.field-meta.xml @@ -0,0 +1,14 @@ + + + Product__c + false + + Product__c + Product Price Components + Product_Price_Components + 0 + false + false + MasterDetail + false + diff --git a/examples/vitepress/force-app/main/default/objects/Product__c/Product__c.object-meta.xml b/examples/vitepress/force-app/main/default/objects/Product__c/Product__c.object-meta.xml new file mode 100644 index 00000000..ea2d7802 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Product__c/Product__c.object-meta.xml @@ -0,0 +1,168 @@ + + + + Accept + Default + + + Accept + Large + Default + + + Accept + Small + Default + + + CancelEdit + Default + + + CancelEdit + Large + Default + + + CancelEdit + Small + Default + + + Clone + Default + + + Clone + Large + Default + + + Clone + Small + Default + + + Delete + Default + + + Delete + Large + Default + + + Delete + Small + Default + + + Edit + Default + + + Edit + Large + Default + + + Edit + Small + Default + + + List + Default + + + List + Large + Default + + + List + Small + Default + + + New + Default + + + New + Large + Default + + + New + Small + Default + + + SaveEdit + Default + + + SaveEdit + Large + Default + + + SaveEdit + Small + Default + + + Tab + Default + + + Tab + Large + Default + + + Tab + Small + Default + + + View + Action override created by Lightning App Builder during activation. + Product_Record_Page + Large + false + Flexipage + + + View + Default + + + View + Small + Default + + false + SYSTEM + Deployed + false + true + false + false + false + false + false + true + true + Private + + + + Text + + Products + + ReadWrite + Public + diff --git a/examples/vitepress/force-app/main/default/objects/Product__c/fields/Description__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Product__c/fields/Description__c.field-meta.xml new file mode 100644 index 00000000..69050ca6 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Product__c/fields/Description__c.field-meta.xml @@ -0,0 +1,11 @@ + + + Description__c + false + + 255 + false + false + Text + false + diff --git a/examples/vitepress/force-app/main/default/objects/Product__c/fields/Event__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Product__c/fields/Event__c.field-meta.xml new file mode 100644 index 00000000..82947d0b --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Product__c/fields/Event__c.field-meta.xml @@ -0,0 +1,12 @@ + + + Event__c + Restrict + false + + Event__c + Products + true + false + Lookup + diff --git a/examples/vitepress/force-app/main/default/objects/Product__c/fields/Features__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Product__c/fields/Features__c.field-meta.xml new file mode 100644 index 00000000..6b67a859 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Product__c/fields/Features__c.field-meta.xml @@ -0,0 +1,10 @@ + + + Features__c + false + + 32768 + false + LongTextArea + 10 + diff --git a/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/Sales_Order_Line__c.object-meta.xml b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/Sales_Order_Line__c.object-meta.xml new file mode 100644 index 00000000..ce565bf8 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/Sales_Order_Line__c.object-meta.xml @@ -0,0 +1,166 @@ + + + + Accept + Default + + + Accept + Large + Default + + + Accept + Small + Default + + + CancelEdit + Default + + + CancelEdit + Large + Default + + + CancelEdit + Small + Default + + + Clone + Default + + + Clone + Large + Default + + + Clone + Small + Default + + + Delete + Default + + + Delete + Large + Default + + + Delete + Small + Default + + + Edit + Default + + + Edit + Large + Default + + + Edit + Small + Default + + + List + Default + + + List + Large + Default + + + List + Small + Default + + + New + Default + + + New + Large + Default + + + New + Small + Default + + + SaveEdit + Default + + + SaveEdit + Large + Default + + + SaveEdit + Small + Default + + + Tab + Default + + + Tab + Large + Default + + + Tab + Small + Default + + + View + Default + + + View + Large + Default + + + View + Small + Default + + false + SYSTEM + Deployed + false + true + false + false + false + false + false + true + true + ControlledByParent + + + SOL-{0000} + + AutoNumber + + Sales Order Lines + + ControlledByParent + Public + diff --git a/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Amount__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Amount__c.field-meta.xml new file mode 100644 index 00000000..3a464e2d --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Amount__c.field-meta.xml @@ -0,0 +1,11 @@ + + + Amount__c + false + + 18 + true + 2 + false + Currency + diff --git a/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Product__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Product__c.field-meta.xml new file mode 100644 index 00000000..b6b5369f --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Product__c.field-meta.xml @@ -0,0 +1,13 @@ + + + Product__c + Restrict + false + + Product__c + Sales Order Lines + Sales_Order_Lines + true + false + Lookup + diff --git a/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Sales_Order__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Sales_Order__c.field-meta.xml new file mode 100644 index 00000000..c1d881c8 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Sales_Order__c.field-meta.xml @@ -0,0 +1,14 @@ + + + Sales_Order__c + false + + Sales_Order__c + Sales Order Lines + Sales_Order_Lines + 0 + false + false + MasterDetail + false + diff --git a/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Source_Price_Component__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Source_Price_Component__c.field-meta.xml new file mode 100644 index 00000000..69817d96 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Source_Price_Component__c.field-meta.xml @@ -0,0 +1,13 @@ + + + Source_Price_Component__c + SetNull + false + + Price_Component__c + Sales Order Lines + Sales_Order_Lines + false + false + Lookup + diff --git a/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Type__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Type__c.field-meta.xml new file mode 100644 index 00000000..328b5529 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/fields/Type__c.field-meta.xml @@ -0,0 +1,26 @@ + + + Type__c + "Charge" + false + + true + false + Picklist + + true + + false + + Charge + false + + + + Discount + false + + + + + diff --git a/examples/vitepress/force-app/main/default/objects/Sales_Order__c/Sales_Order__c.object-meta.xml b/examples/vitepress/force-app/main/default/objects/Sales_Order__c/Sales_Order__c.object-meta.xml new file mode 100644 index 00000000..08247ffc --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Sales_Order__c/Sales_Order__c.object-meta.xml @@ -0,0 +1,169 @@ + + + + Accept + Default + + + Accept + Large + Default + + + Accept + Small + Default + + + CancelEdit + Default + + + CancelEdit + Large + Default + + + CancelEdit + Small + Default + + + Clone + Default + + + Clone + Large + Default + + + Clone + Small + Default + + + Delete + Default + + + Delete + Large + Default + + + Delete + Small + Default + + + Edit + Default + + + Edit + Large + Default + + + Edit + Small + Default + + + List + Default + + + List + Large + Default + + + List + Small + Default + + + New + Default + + + New + Large + Default + + + New + Small + Default + + + SaveEdit + Default + + + SaveEdit + Large + Default + + + SaveEdit + Small + Default + + + Tab + Default + + + Tab + Large + Default + + + Tab + Small + Default + + + View + Action override created by Lightning App Builder during activation. + Sales_Order_Record_Page + Large + false + Flexipage + + + View + Default + + + View + Small + Default + + false + SYSTEM + Deployed + false + true + false + false + false + false + false + true + true + Private + + + SO-{0000} + + AutoNumber + + Sales Orders + + ReadWrite + Public + diff --git a/examples/vitepress/force-app/main/default/objects/Sales_Order__c/listViews/All.listView-meta.xml b/examples/vitepress/force-app/main/default/objects/Sales_Order__c/listViews/All.listView-meta.xml new file mode 100644 index 00000000..d5058512 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Sales_Order__c/listViews/All.listView-meta.xml @@ -0,0 +1,6 @@ + + + All + Everything + + diff --git a/examples/vitepress/force-app/main/default/objects/Speaker__c/Speaker__c.object-meta.xml b/examples/vitepress/force-app/main/default/objects/Speaker__c/Speaker__c.object-meta.xml new file mode 100644 index 00000000..5a77a80b --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Speaker__c/Speaker__c.object-meta.xml @@ -0,0 +1,166 @@ + + + + Accept + Default + + + Accept + Large + Default + + + Accept + Small + Default + + + CancelEdit + Default + + + CancelEdit + Large + Default + + + CancelEdit + Small + Default + + + Clone + Default + + + Clone + Large + Default + + + Clone + Small + Default + + + Delete + Default + + + Delete + Large + Default + + + Delete + Small + Default + + + Edit + Default + + + Edit + Large + Default + + + Edit + Small + Default + + + List + Default + + + List + Large + Default + + + List + Small + Default + + + New + Default + + + New + Large + Default + + + New + Small + Default + + + SaveEdit + Default + + + SaveEdit + Large + Default + + + SaveEdit + Small + Default + + + Tab + Default + + + Tab + Large + Default + + + Tab + Small + Default + + + View + Default + + + View + Large + Default + + + View + Small + Default + + false + SYSTEM + Deployed + false + true + false + false + false + false + false + true + true + ControlledByParent + + + SPEAK-{0000} + + AutoNumber + + Speakers + + ControlledByParent + Public + diff --git a/examples/vitepress/force-app/main/default/objects/Speaker__c/fields/About__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Speaker__c/fields/About__c.field-meta.xml new file mode 100644 index 00000000..2fc71d94 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Speaker__c/fields/About__c.field-meta.xml @@ -0,0 +1,10 @@ + + + About__c + false + + 32768 + false + LongTextArea + 3 + diff --git a/examples/vitepress/force-app/main/default/objects/Speaker__c/fields/Event__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Speaker__c/fields/Event__c.field-meta.xml new file mode 100644 index 00000000..cf6bfc63 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Speaker__c/fields/Event__c.field-meta.xml @@ -0,0 +1,14 @@ + + + Event__c + false + + Event__c + Speakers + Speakers + 0 + false + false + MasterDetail + false + diff --git a/examples/vitepress/force-app/main/default/objects/Speaker__c/fields/Person__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/Speaker__c/fields/Person__c.field-meta.xml new file mode 100644 index 00000000..b7ac07b1 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/Speaker__c/fields/Person__c.field-meta.xml @@ -0,0 +1,14 @@ + + + Person__c + false + + Contact + Speakers + Speakers + 1 + false + false + MasterDetail + false + diff --git a/package-lock.json b/package-lock.json index 6c811b16..4cb39f91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "@cparra/apexdocs", - "version": "3.2.2", + "version": "3.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cparra/apexdocs", - "version": "3.2.2", + "version": "3.3.1", "license": "MIT", "dependencies": { "@cparra/apex-reflection": "2.15.0", + "@salesforce/source-deploy-retrieve": "^12.8.1", "@types/js-yaml": "^4.0.9", "@types/yargs": "^17.0.32", "chalk": "^4.1.2", @@ -1950,6 +1951,28 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsforce/jsforce-node": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@jsforce/jsforce-node/-/jsforce-node-3.5.2.tgz", + "integrity": "sha512-WZZo7HVFQsTeHRfykMJBrEK3ACr9sQnWSm3EVLasAnAUxjh+hjU4SfJ7HB1UaPRliHjLVZcSCUwF+OoJqjbfoA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4", + "base64url": "^3.0.1", + "csv-parse": "^5.5.2", + "csv-stringify": "^6.4.4", + "faye": "^1.4.0", + "form-data": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "multistream": "^3.1.0", + "node-fetch": "^2.6.1", + "strip-ansi": "^6.0.0", + "xml2js": "^0.6.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2449,12 +2472,168 @@ "win32" ] }, + "node_modules/@salesforce/core": { + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/@salesforce/core/-/core-8.6.2.tgz", + "integrity": "sha512-LFzTLnavDeWsZBB7b2iuVz0F6yeuTcJzQxCy5n+rACY2/Lbw6UJDK/bOSt4wlss6fKrkyU1FTHNlUK5ZoBEveg==", + "license": "BSD-3-Clause", + "dependencies": { + "@jsforce/jsforce-node": "^3.4.1", + "@salesforce/kit": "^3.2.2", + "@salesforce/schemas": "^1.9.0", + "@salesforce/ts-types": "^2.0.10", + "ajv": "^8.17.1", + "change-case": "^4.1.2", + "fast-levenshtein": "^3.0.0", + "faye": "^1.4.0", + "form-data": "^4.0.0", + "js2xmlparser": "^4.0.1", + "jsonwebtoken": "9.0.2", + "jszip": "3.10.1", + "pino": "^9.4.0", + "pino-abstract-transport": "^1.2.0", + "pino-pretty": "^11.2.2", + "proper-lockfile": "^4.1.2", + "semver": "^7.6.3", + "ts-retry-promise": "^0.8.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@salesforce/core/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@salesforce/core/node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "license": "MIT", + "dependencies": { + "fastest-levenshtein": "^1.0.7" + } + }, + "node_modules/@salesforce/core/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@salesforce/core/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@salesforce/kit": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@salesforce/kit/-/kit-3.2.3.tgz", + "integrity": "sha512-X8rZouLt06dxRkn+uYTwywWDS/NqZ783AyomGqgtWdUxF61EOJvu0ehtcYeutx9Ng08uuZ+s6wNvWiDsdhUcPg==", + "license": "BSD-3-Clause", + "dependencies": { + "@salesforce/ts-types": "^2.0.12" + } + }, + "node_modules/@salesforce/schemas": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@salesforce/schemas/-/schemas-1.9.0.tgz", + "integrity": "sha512-LiN37zG5ODT6z70sL1fxF7BQwtCX9JOWofSU8iliSNIM+WDEeinnoFtVqPInRSNt8I0RiJxIKCrqstsmQRBNvA==", + "license": "ISC" + }, + "node_modules/@salesforce/source-deploy-retrieve": { + "version": "12.8.1", + "resolved": "https://registry.npmjs.org/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-12.8.1.tgz", + "integrity": "sha512-1wTP6Qa9aWuToY5VMMO0Xg8ea5Vtnaf79ZYJry4BTK9y2XyzhrmwSHTvvO7UuhDb//zx2REfKT53JltCysEADA==", + "license": "BSD-3-Clause", + "dependencies": { + "@salesforce/core": "^8.6.2", + "@salesforce/kit": "^3.2.2", + "@salesforce/ts-types": "^2.0.12", + "fast-levenshtein": "^3.0.0", + "fast-xml-parser": "^4.5.0", + "got": "^11.8.6", + "graceful-fs": "^4.2.11", + "ignore": "^5.3.2", + "isbinaryfile": "^5.0.2", + "jszip": "^3.10.1", + "mime": "2.6.0", + "minimatch": "^9.0.5", + "proxy-agent": "^6.4.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@salesforce/source-deploy-retrieve/node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "license": "MIT", + "dependencies": { + "fastest-levenshtein": "^1.0.7" + } + }, + "node_modules/@salesforce/source-deploy-retrieve/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@salesforce/ts-types": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@salesforce/ts-types/-/ts-types-2.0.12.tgz", + "integrity": "sha512-BIJyduJC18Kc8z+arUm5AZ9VkPRyw1KKAm+Tk+9LT99eOzhNilyfKzhZ4t+tG2lIGgnJpmytZfVDZ0e2kFul8g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -2473,6 +2652,24 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.1.18", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", @@ -2514,6 +2711,18 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, "node_modules/@types/eslint": { "version": "8.56.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", @@ -2551,6 +2760,12 @@ "@types/node": "*" } }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -2597,6 +2812,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "20.14.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", @@ -2612,6 +2836,15 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -2861,6 +3094,18 @@ "dev": true, "license": "ISC" }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -2884,6 +3129,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2976,6 +3233,39 @@ "node": ">=8" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -3020,6 +3310,44 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3093,6 +3421,36 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3111,6 +3469,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3119,6 +3519,16 @@ "node": ">=6" } }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -3148,6 +3558,17 @@ } ] }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3218,6 +3639,26 @@ "node": ">=8" } }, + "node_modules/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -3383,6 +3824,18 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -3416,9 +3869,20 @@ "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -3442,6 +3906,23 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -3519,12 +4000,53 @@ "node": ">= 8" } }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { + "node_modules/csprng": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/csprng/-/csprng-0.1.2.tgz", + "integrity": "sha512-D3WAbvvgUVIqSxUfdvLeGjuotsB32bvfVPd+AaaTWMtyUeC9zgCnw5xs94no89yFLVsafvY9dMZEhTwsY/ZecA==", + "license": "MIT", + "dependencies": { + "sequin": "*" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/csv-parse": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.6.tgz", + "integrity": "sha512-uNpm30m/AGSkLxxy7d9yRXpJQFrZzVWLFBkS+6ngPcZkw/5k3L/jjFuj7tVnEpRn+QgmiXr21nDlhCiUK4ij2A==", + "license": "MIT" + }, + "node_modules/csv-stringify": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.5.1.tgz", + "integrity": "sha512-+9lpZfwpLntpTIEpFbwQyWuW/hmI/eHuJZD1XzeZpfZTqkf1fyvBbBLXTJJMsBuuS11uTShMqPwzx4A6ffXgRQ==", + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { "ms": "2.1.2" }, "engines": { @@ -3536,6 +4058,33 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -3565,6 +4114,38 @@ "node": ">=0.10.0" } }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3609,6 +4190,16 @@ "node": ">=6.0.0" } }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3616,6 +4207,15 @@ "dev": true, "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.795", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.795.tgz", @@ -3639,6 +4239,15 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -3711,6 +4320,27 @@ "node": ">=0.8.0" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, "node_modules/eslint": { "version": "8.57.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", @@ -3965,7 +4595,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -4003,7 +4632,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -4018,12 +4646,20 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -4031,6 +4667,15 @@ "dev": true, "license": "MIT" }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -4079,11 +4724,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -4128,10 +4778,31 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "license": "BSD-3-Clause" + }, "node_modules/fast-xml-parser": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", - "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", "funding": [ { "type": "github", @@ -4150,6 +4821,15 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -4159,6 +4839,35 @@ "reusify": "^1.0.4" } }, + "node_modules/faye": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/faye/-/faye-1.4.0.tgz", + "integrity": "sha512-kRrIg4be8VNYhycS2PY//hpBJSzZPr/DBbcy9VWelhZMW3KhyLkQR0HL0k0MNpmVoNFF4EdfMFkNAWjTP65g6w==", + "license": "Apache-2.0", + "dependencies": { + "asap": "*", + "csprng": "*", + "faye-websocket": ">=0.9.1", + "safe-buffer": "*", + "tough-cookie": "*", + "tunnel-agent": "*" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/fb-watchman": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", @@ -4275,12 +4984,40 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fp-ts": { "version": "2.16.8", "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.8.tgz", "integrity": "sha512-nmDtNqmMZkOxu0M5hkrS9YA15/KPkYkILb6Axg9XBAoUoYEtzg+LFmVWqZrl9FNttsW0qIUpx9RCA9INbv+Bxw==", "license": "MIT" }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4361,6 +5098,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -4445,11 +5197,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", - "dev": true + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", @@ -4497,12 +5274,91 @@ "node": ">= 0.4" } }, + "node_modules/header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "license": "MIT", + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -4528,15 +5384,41 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", "engines": { "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -4593,8 +5475,26 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" }, "node_modules/is-arrayish": { "version": "0.2.1", @@ -4712,11 +5612,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.3.tgz", + "integrity": "sha512-VR4gNjFaDP8csJQvzInG20JvBj8MaHYLxNOMXysxRbGM7tcsHZwCjhch3FubFtZBkuDbN55i4dUukGeIrzF+6g==", + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", @@ -5987,6 +6905,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6003,6 +6930,21 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "license": "Apache-2.0", + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -6019,7 +6961,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -6052,11 +6993,89 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -6093,6 +7112,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", @@ -6467,6 +7495,42 @@ "node": ">=8" } }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -6479,6 +7543,30 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/lru-cache": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", @@ -6570,6 +7658,39 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -6579,6 +7700,15 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", @@ -6614,8 +7744,31 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multistream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/multistream/-/multistream-3.1.0.tgz", + "integrity": "sha512-zBgD3kn8izQAN/TaL1PCMv15vYpf+Vcrsfub06njuYVYlzUldzpopTlrEZ53pZVEbfn3Shtv7vRFoOv6LOV87Q==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^3.4.0" + } + }, + "node_modules/multistream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } }, "node_modules/natural-compare": { "version": "1.4.0", @@ -6628,6 +7781,45 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -6649,6 +7841,18 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -6661,11 +7865,19 @@ "node": ">=8" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "dependencies": { "wrappy": "1" } @@ -6702,6 +7914,15 @@ "node": ">= 0.8.0" } }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -6738,6 +7959,63 @@ "node": ">=6" } }, + "node_modules/pac-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", + "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.5", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", @@ -6745,6 +8023,22 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6773,6 +8067,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6862,37 +8176,168 @@ "node": ">=0.10" } }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true, - "engines": { - "node": ">= 6" + "node_modules/pino": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.5.0.tgz", + "integrity": "sha512-xSEmD4pLnV54t0NOUN16yCl7RIB1c5UUOse5HSyEXtBp+FgFQyPeDutc+Q2ZO7/22vImV7VfEjH/1zV2QuqvYw==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^4.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, + "node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "license": "MIT", "dependencies": { - "find-up": "^4.0.0" + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/pkgroll": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/pkgroll/-/pkgroll-2.4.2.tgz", - "integrity": "sha512-9seL/4BNQsE+eL+kefjfh5jSLqQPSKXQE/adw1L76k49KFw/XnOnyU8dRwuWpVtvMyIVyecaSBIpvFYrmnZq6A==", - "dev": true, + "node_modules/pino-abstract-transport/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", "dependencies": { - "@rollup/plugin-alias": "^5.1.0", - "@rollup/plugin-commonjs": "^26.0.1", - "@rollup/plugin-inject": "^5.0.5", + "safe-buffer": "~5.2.0" + } + }, + "node_modules/pino-pretty": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.3.0.tgz", + "integrity": "sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-pretty/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "node_modules/pino/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkgroll": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/pkgroll/-/pkgroll-2.4.2.tgz", + "integrity": "sha512-9seL/4BNQsE+eL+kefjfh5jSLqQPSKXQE/adw1L76k49KFw/XnOnyU8dRwuWpVtvMyIVyecaSBIpvFYrmnZq6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-alias": "^5.1.0", + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-replace": "^5.0.7", @@ -6970,6 +8415,27 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz", + "integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==", + "license": "MIT" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -6983,6 +8449,86 @@ "node": ">= 6" } }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -7029,12 +8575,60 @@ } ] }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7043,6 +8637,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -7060,6 +8663,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" + }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -7098,6 +8707,27 @@ "node": ">=10" } }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -7217,6 +8847,47 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -7226,6 +8897,32 @@ "semver": "bin/semver.js" } }, + "node_modules/sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/sequin": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/sequin/-/sequin-0.1.1.tgz", + "integrity": "sha512-hJWMZRwP75ocoBM+1/YaCsvS0j5MTPeBHJkS2/wruehl9xwtX30HlDF1Gt6UZ8HHHY8SJa2/IL+jo+JJCd59rA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7250,8 +8947,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/sisteransi": { "version": "1.0.5", @@ -7311,6 +9007,75 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7329,6 +9094,15 @@ "source-map": "^0.6.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -7356,6 +9130,21 @@ "node": ">=8" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -7455,7 +9244,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -7533,6 +9321,33 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tldts": { + "version": "6.1.52", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.52.tgz", + "integrity": "sha512-fgrDJXDjbAverY6XnIt0lNfv8A0cf7maTEaZxNykLGsLG7XP+5xhjBTrt/ieAsFjAlZ+G5nmXomLcZDkxXnDzw==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.52" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.52", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.52.tgz", + "integrity": "sha512-j4OxQI5rc1Ve/4m/9o2WhWSC4jGc4uVbCINdOEJRAraCi0YqTqgMcxUx7DbmuP0G3PCixoof/RZB0Q5Kh9tagw==", + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -7560,6 +9375,24 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -7632,6 +9465,33 @@ "node": ">=10" } }, + "node_modules/ts-retry-promise": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/ts-retry-promise/-/ts-retry-promise-0.8.1.tgz", + "integrity": "sha512-+AHPUmAhr5bSRRK5CurE9kNH8gZlEHnCgusZ0zy2bjfatUBDX0h6vGQjiT0YrGwSDwRZmU+bapeX6mj55FOPvg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7710,6 +9570,15 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", @@ -7740,6 +9609,24 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -7750,6 +9637,12 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/v8-to-istanbul": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", @@ -7779,6 +9672,45 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7951,8 +9883,35 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "license": "Apache-2.0" }, "node_modules/y18n": { "version": "5.0.8", diff --git a/package.json b/package.json index 2b7631ed..01e49836 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ }, "dependencies": { "@cparra/apex-reflection": "2.15.0", + "@salesforce/source-deploy-retrieve": "^12.8.1", "@types/js-yaml": "^4.0.9", "@types/yargs": "^17.0.32", "chalk": "^4.1.2", diff --git a/src/application/Apexdocs.ts b/src/application/Apexdocs.ts index 30144791..6bf021da 100644 --- a/src/application/Apexdocs.ts +++ b/src/application/Apexdocs.ts @@ -6,7 +6,7 @@ import markdown from './generators/markdown'; import openApi from './generators/openapi'; import changelog from './generators/changelog'; -import { processApexFiles, processFiles } from './source-code-file-reader'; +import { processFiles } from './source-code-file-reader'; import { DefaultFileSystem } from './file-system'; import { Logger } from '#utils/logger'; import { @@ -51,7 +51,14 @@ async function processMarkdown(config: UserDefinedMarkdownConfig) { // TODO: Also process Object files return pipe( TE.tryCatch( - () => readFiles([processApexFiles(config.includeMetadata)])(config.sourceDir, config.exclude), + // TODO: We do not need to deal with promises anymore (in this step) + () => + Promise.resolve( + readFiles(['ApexClass'], { includeMetadata: config.includeMetadata })( + config.sourceDir, + config.exclude, + ) as UnparsedApexBundle[], + ), (e) => new FileReadingError('An error occurred while reading files.', e), ), TE.flatMap((fileBodies) => markdown(fileBodies, config)), @@ -61,20 +68,21 @@ async function processMarkdown(config: UserDefinedMarkdownConfig) { } async function processOpenApi(config: UserDefinedOpenApiConfig, logger: Logger) { - const fileBodies = await readFiles([processApexFiles(false)])(config.sourceDir, config.exclude); + const fileBodies = readFiles(['ApexClass'])(config.sourceDir, config.exclude) as UnparsedApexBundle[]; return openApi(logger, fileBodies, config); } async function processChangeLog(config: UserDefinedChangelogConfig) { - async function loadFiles(): Promise<[UnparsedApexBundle[], UnparsedApexBundle[]]> { + function loadFiles(): [UnparsedApexBundle[], UnparsedApexBundle[]] { return [ - await readFiles([processApexFiles(false)])(config.previousVersionDir, config.exclude), - await readFiles([processApexFiles(false)])(config.currentVersionDir, config.exclude), + readFiles(['ApexClass'])(config.previousVersionDir, config.exclude) as UnparsedApexBundle[], + readFiles(['ApexClass'])(config.currentVersionDir, config.exclude) as UnparsedApexBundle[], ]; } return pipe( - TE.tryCatch(loadFiles, (e) => new FileReadingError('An error occurred while reading files.', e)), + E.tryCatch(loadFiles, (e) => new FileReadingError('An error occurred while reading files.', e)), + TE.fromEither, TE.flatMap(([previous, current]) => changelog(previous, current, config)), TE.mapLeft(toErrors), ); diff --git a/src/application/file-system.ts b/src/application/file-system.ts index 3626bf1d..dc50fd03 100644 --- a/src/application/file-system.ts +++ b/src/application/file-system.ts @@ -1,69 +1,11 @@ import * as fs from 'fs'; -import * as path from 'path'; export interface FileSystem { - isDirectory: (path: string) => Promise; - readDirectory: (sourceDirectory: string) => Promise; - readFile: (path: string) => Promise; - joinPath: (...paths: string[]) => string; - exists: (path: string) => boolean; -} - -function stat(path: string): Promise { - return new Promise((resolve, reject) => { - fs.stat(path, (err, stats) => { - if (err) { - reject(err); - } else { - resolve(stats); - } - }); - }); -} - -function readdir(path: string): Promise { - return new Promise((resolve, reject) => { - fs.readdir(path, (err, files) => { - if (err) { - reject(err); - } else { - resolve(files); - } - }); - }); -} - -function readFile(path: string): Promise { - return new Promise((resolve, reject) => { - fs.readFile(path, (err, data) => { - if (err) { - reject(err); - } else { - resolve(data.toString()); - } - }); - }); + readFile: (path: string) => string; } export class DefaultFileSystem implements FileSystem { - async isDirectory(pathToRead: string): Promise { - const stats = await stat(pathToRead); - return stats.isDirectory(); - } - - readDirectory(sourceDirectory: string): Promise { - return readdir(sourceDirectory); - } - - readFile(pathToRead: string): Promise { - return readFile(pathToRead); - } - - joinPath(...paths: string[]): string { - return path.join(...paths); - } - - exists(path: string): boolean { - return fs.existsSync(path); + readFile(pathToRead: string): string { + return fs.readFileSync(pathToRead, 'utf8'); } } diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts index d3882f10..b6b11333 100644 --- a/src/application/source-code-file-reader.ts +++ b/src/application/source-code-file-reader.ts @@ -1,99 +1,120 @@ +import { MetadataResolver, SourceComponent } from '@salesforce/source-deploy-retrieve'; import { FileSystem } from './file-system'; import { UnparsedApexBundle, UnparsedSObjectBundle } from '../core/shared/types'; import { minimatch } from 'minimatch'; -import { pipe } from 'fp-ts/function'; +import { flow, pipe } from 'fp-ts/function'; +import { apply } from '#utils/fp'; -/** - * Reads from source code files and returns their raw body. - */ -export function processFiles(fileSystem: FileSystem) { - return function (processors: FileProcessor[]) { - return async function (rootPath: string, exclude: string[]): Promise { - return pipe( - await getFilePaths(fileSystem, rootPath), - (filePaths) => filePaths.filter((filePath) => !isExcluded(filePath, exclude)), - (filteredFilePaths) => readFiles(fileSystem, filteredFilePaths, processors), - ); - }; - }; -} +type ComponentTypes = 'ApexClass' | 'CustomObject'; -async function readFiles( - fileSystem: FileSystem, - filePaths: string[], - processors: FileProcessor[], -): Promise { - const files: T[] = []; - for (const filePath of filePaths) { - const processor = processors.find((p) => p.isSupportedFile(filePath)); - if (processor) { - files.push(await processor.process(fileSystem, filePath)); - } - } - return files; -} +type ApexClassApexSourceComponent = { + type: 'ApexClass'; + name: string; + xmlPath?: string; + contentPath: string; +}; -async function getFilePaths(fileSystem: FileSystem, rootPath: string): Promise { - const directoryContents = await fileSystem.readDirectory(rootPath); - const paths: string[] = []; - for (const filePath of directoryContents) { - const currentPath = fileSystem.joinPath(rootPath, filePath); - if (await fileSystem.isDirectory(currentPath)) { - paths.push(...(await getFilePaths(fileSystem, currentPath))); - } else { - paths.push(currentPath); - } - } - return paths; -} +type CustomObjectSourceComponent = { + type: 'CustomObject'; + name: string; + contentPath: string; +}; -function isExcluded(filePath: string, exclude: string[]): boolean { - return exclude.some((pattern) => minimatch(filePath, pattern)); +function getMetadata(rootPath: string): SourceComponent[] { + return new MetadataResolver().getComponentsFromPath(rootPath); } -interface FileProcessor { - isSupportedFile: (currentFile: string) => boolean; - process: (fileSystem: FileSystem, filePath: string) => Promise; +function getApexSourceComponents( + includeMetadata: boolean, + sourceComponents: SourceComponent[], +): ApexClassApexSourceComponent[] { + return sourceComponents + .filter((component) => component.type.name === 'ApexClass') + .map((component) => ({ + type: 'ApexClass' as const, + name: component.name, + xmlPath: includeMetadata ? component.xml : undefined, + contentPath: component.content!, + })); } -export function processApexFiles(includeMetadata: boolean): FileProcessor { - return new ApexFileReader(includeMetadata); -} - -class ApexFileReader implements FileProcessor { - APEX_FILE_EXTENSION = '.cls'; - - constructor(public includeMetadata: boolean) {} - - isSupportedFile(currentFile: string): boolean { - return currentFile.endsWith(this.APEX_FILE_EXTENSION); - } +function toUnparsedApexBundle( + fileSystem: FileSystem, + apexSourceComponents: ApexClassApexSourceComponent[], +): UnparsedApexBundle[] { + return apexSourceComponents.map((component) => { + const apexComponentTuple: [string, string | null] = [ + fileSystem.readFile(component.contentPath), + component.xmlPath ? fileSystem.readFile(component.xmlPath) : null, + ]; - async process(fileSystem: FileSystem, filePath: string): Promise { - const rawTypeContent = await fileSystem.readFile(filePath); - const metadataPath = `${filePath}-meta.xml`; - let rawMetadataContent = null; - if (this.includeMetadata) { - rawMetadataContent = fileSystem.exists(metadataPath) ? await fileSystem.readFile(metadataPath) : null; - } + return { + type: 'apex', + filePath: component.contentPath, + content: apexComponentTuple[0], + metadataContent: apexComponentTuple[1], + }; + }); +} - return { type: 'apex', filePath, content: rawTypeContent, metadataContent: rawMetadataContent }; - } +function getCustomObjectSourceComponents(sourceComponents: SourceComponent[]): CustomObjectSourceComponent[] { + return sourceComponents + .filter((component) => component.type.name === 'CustomObject') + .map((component) => ({ + name: component.name, + type: 'CustomObject' as const, + contentPath: component.xml!, + })); } -export function processObjectFiles(): FileProcessor { - return new ObjectFileReader(); +function toUnparsedSObjectBundle( + fileSystem: FileSystem, + customObjectSourceComponents: CustomObjectSourceComponent[], +): UnparsedSObjectBundle[] { + return customObjectSourceComponents.map((component) => { + return { + type: 'sobject', + filePath: component.contentPath, + content: fileSystem.readFile(component.contentPath), + }; + }); } -class ObjectFileReader implements FileProcessor { - OBJECT_FILE_EXTENSION = '.object-meta.xml'; +/** + * Reads from source code files and returns their raw body. + */ +export function processFiles(fileSystem: FileSystem) { + return function ( + componentTypesToRetrieve: T, + options: { includeMetadata: boolean } = { includeMetadata: false }, + ) { + const converters: Record< + ComponentTypes, + (components: SourceComponent[]) => (UnparsedApexBundle | UnparsedSObjectBundle)[] + > = { + ApexClass: flow(apply(getApexSourceComponents, options.includeMetadata), (apexSourceComponents) => + toUnparsedApexBundle(fileSystem, apexSourceComponents), + ), + CustomObject: flow(getCustomObjectSourceComponents, (customObjectSourceComponents) => + toUnparsedSObjectBundle(fileSystem, customObjectSourceComponents), + ), + }; + + const convertersToUse = componentTypesToRetrieve.map((componentType) => converters[componentType]); - isSupportedFile(currentFile: string): boolean { - return currentFile.endsWith(this.OBJECT_FILE_EXTENSION); - } + return function (rootPath: string, exclude: string[]) { + return pipe( + getMetadata(rootPath), + (components) => components.filter((component) => !isExcluded(component.content!, exclude)), + (components) => convertersToUse.map((converter) => converter(components)), + (bundles) => { + return bundles.reduce((acc, bundle) => [...acc, ...bundle], []); + }, + ); + }; + }; +} - async process(fileSystem: FileSystem, filePath: string): Promise { - const rawTypeContent = await fileSystem.readFile(filePath); - return { type: 'sobject', filePath, content: rawTypeContent }; - } +function isExcluded(filePath: string, exclude: string[]): boolean { + return exclude.some((pattern) => minimatch(filePath, pattern)); } From a4a6f94f8bef06e5ce25209b3efc3c274636a943 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Fri, 18 Oct 2024 16:05:50 -0400 Subject: [PATCH 16/32] Test refactoring --- .../__tests__/source-code-file-reader.spec.ts | 266 +++++------------- src/application/file-system.ts | 7 + src/application/source-code-file-reader.ts | 31 +- 3 files changed, 104 insertions(+), 200 deletions(-) diff --git a/src/application/__tests__/source-code-file-reader.spec.ts b/src/application/__tests__/source-code-file-reader.spec.ts index ba187375..7a24e7f0 100644 --- a/src/application/__tests__/source-code-file-reader.spec.ts +++ b/src/application/__tests__/source-code-file-reader.spec.ts @@ -1,240 +1,130 @@ import { FileSystem } from '../file-system'; -import { processApexFiles, processFiles, processObjectFiles } from '../source-code-file-reader'; - -type File = { - type: 'file'; - path: string; - content: string; -}; - -type Directory = { - type: 'directory'; - path: string; - files: (File | Directory)[]; -}; - -type Path = File | Directory; +import { processFiles, SourceComponentAdapter } from '../source-code-file-reader'; class TestFileSystem implements FileSystem { - constructor(private readonly paths: Path[]) {} + constructor(private readonly sourceComponents: SourceComponentAdapter[]) {} - async isDirectory(path: string): Promise { - const directory = this.findPath(path); - return directory ? directory.type === 'directory' : false; - } - - joinPath(...paths: string[]): string { - return paths.join('/'); - } - - async readDirectory(sourceDirectory: string): Promise { - const directory = this.findPath(sourceDirectory); - if (!directory || directory.type !== 'directory') { - throw new Error('Directory not found'); - } - return directory.files.map((f) => f.path); - } - - async readFile(path: string): Promise { - const file = this.findPath(path); - if (!file || file.type !== 'file') { - throw new Error('File not found'); - } - return file.content; + getComponents(): SourceComponentAdapter[] { + return this.sourceComponents; } - exists(path: string): boolean { - return this.paths.some((p) => p.path === path); - } - - findPath(path: string): Path | undefined { - const splitPath = path.split('/'); - let currentPath = this.paths.find((p) => p.path === splitPath[0]); - for (let i = 1; i < splitPath.length; i++) { - if (!currentPath || currentPath.type !== 'directory') { - return undefined; - } - currentPath = currentPath.files.find((f) => f.path === splitPath[i]); + readFile(path: string): string { + switch (path) { + case 'Speaker.cls': + return 'public class Speaker{}'; + case 'AnotherSpeaker.cls': + return 'public class AnotherSpeaker{}'; + case 'SomeObject__c.object-meta.xml': + return ` + + + Deployed + test object for testing + + MyFirstObjects + `; + default: + return ''; } - return currentPath; } } describe('File Reader', () => { - it('returns an empty list when there are no files in the directory', async () => { - const fileSystem = new TestFileSystem([ - { - type: 'directory', - path: '', - files: [], - }, - ]); + it('returns an empty list when no source components are found', async () => { + const fileSystem = new TestFileSystem([]); - const result = await processFiles(fileSystem)([processApexFiles(false)])('', []); + const result = processFiles(fileSystem)(['ApexClass'])('', []); expect(result.length).toBe(0); }); - it('returns an empty list when there are no Apex files in the directory', async () => { + it('returns an empty list when reading Apex files and there are none', async () => { const fileSystem = new TestFileSystem([ { - type: 'directory', - path: '', - files: [ - { - type: 'file', - path: 'SomeFile.md', - content: '## Some Markdown', - }, - ], + name: 'Speaker__c', + type: { + id: 'customobject', + name: 'CustomObject', + }, + xml: 'force-app/main/default/objects/Speaker__c/Speaker__c.object-meta.xml', + content: 'force-app/main/default/objects/Speaker__c', }, ]); - const result = await processFiles(fileSystem)([processApexFiles(false)])('', []); + const result = processFiles(fileSystem)(['ApexClass'])('', []); expect(result.length).toBe(0); }); it('returns the file contents for all Apex files', async () => { const fileSystem = new TestFileSystem([ { - type: 'directory', - path: '', - files: [ - { - type: 'file', - path: 'SomeFile.cls', - content: 'public class MyClass{}', - }, - { - type: 'directory', - path: 'subdir', - files: [ - { - type: 'file', - path: 'AnotherFile.cls', - content: 'public class AnotherClass{}', - }, - ], - }, - ], + name: 'Speaker', + type: { + id: 'apexclass', + name: 'ApexClass', + }, + xml: 'Speaker.cls-meta.xml', + content: 'Speaker.cls', + }, + { + name: 'AnotherSpeaker', + type: { + id: 'apexclass', + name: 'ApexClass', + }, + xml: 'AnotherSpeaker.cls-meta.xml', + content: 'AnotherSpeaker.cls', }, ]); - const result = await processFiles(fileSystem)([processApexFiles(false)])('', []); + const result = processFiles(fileSystem)(['ApexClass'])('', []); expect(result.length).toBe(2); - expect(result[0].content).toBe('public class MyClass{}'); - expect(result[1].content).toBe('public class AnotherClass{}'); + expect(result[0].content).toBe('public class Speaker{}'); + expect(result[1].content).toBe('public class AnotherSpeaker{}'); }); it('returns the file contents of all Object files', async () => { - const objectContent = ` - - - Deployed - test object for testing - - MyFirstObjects - `; - const fileSystem = new TestFileSystem([ { - type: 'directory', - path: '', - files: [ - { - type: 'file', - path: 'SomeObject__c.object-meta.xml', - content: objectContent, - }, - ], + name: 'SomeObject__c', + type: { + id: 'customobject', + name: 'CustomObject', + }, + xml: 'SomeObject__c.object-meta.xml', + content: '', }, ]); - const result = await processFiles(fileSystem)([processObjectFiles()])('', []); + const result = processFiles(fileSystem)(['CustomObject'])('', []); expect(result.length).toBe(1); - expect(result[0].content).toBe(objectContent); + expect(result[0].content).toContain('test object for testing'); }); it('skips files that match the excluded glob pattern', async () => { const fileSystem = new TestFileSystem([ { - type: 'directory', - path: '', - files: [ - { - type: 'file', - path: 'SomeFile.cls', - content: 'public class MyClass{}', - }, - { - type: 'directory', - path: 'subdir', - files: [ - { - type: 'file', - path: 'AnotherFile.cls', - content: 'public class AnotherClass{}', - }, - ], - }, - ], + name: 'Speaker', + type: { + id: 'apexclass', + name: 'ApexClass', + }, + xml: 'Speaker.cls-meta.xml', + content: 'Speaker.cls', }, - ]); - - const result = await processFiles(fileSystem)([processApexFiles(false)])('', ['**/AnotherFile.cls']); - expect(result.length).toBe(1); - expect(result[0].content).toBe('public class MyClass{}'); - }); - - it('returns the file contents for all Apex when there are multiple directories', async () => { - const fileSystem = new TestFileSystem([ { - type: 'directory', - path: '', - files: [ - { - type: 'file', - path: 'SomeFile.cls', - content: 'public class MyClass{}', - }, - { - type: 'directory', - path: 'subdir', - files: [ - { - type: 'file', - path: 'AnotherFile.cls', - content: 'public class AnotherClass{}', - }, - ], - }, - { - type: 'directory', - path: 'subdir2', - files: [ - { - type: 'file', - path: 'SomeFile2.cls', - content: 'public class MyClass{}', - }, - { - type: 'directory', - path: 'subdir', - files: [ - { - type: 'file', - path: 'AnotherFile2.cls', - content: 'public class AnotherClass{}', - }, - ], - }, - ], - }, - ], + name: 'AnotherSpeaker', + type: { + id: 'apexclass', + name: 'ApexClass', + }, + xml: 'AnotherSpeaker.cls-meta.xml', + content: 'AnotherSpeaker.cls', }, ]); - const result = await processFiles(fileSystem)([processApexFiles(false)])('', []); - expect(result.length).toBe(4); + const result = processFiles(fileSystem)(['ApexClass'])('', ['**/Speaker.cls']); + expect(result.length).toBe(1); + expect(result[0].content).toBe('public class AnotherSpeaker{}'); }); }); diff --git a/src/application/file-system.ts b/src/application/file-system.ts index dc50fd03..9020b4d2 100644 --- a/src/application/file-system.ts +++ b/src/application/file-system.ts @@ -1,10 +1,17 @@ import * as fs from 'fs'; +import { MetadataResolver } from '@salesforce/source-deploy-retrieve'; +import { SourceComponentAdapter } from './source-code-file-reader'; export interface FileSystem { + getComponents(path: string): SourceComponentAdapter[]; readFile: (path: string) => string; } export class DefaultFileSystem implements FileSystem { + getComponents(path: string): SourceComponentAdapter[] { + return new MetadataResolver().getComponentsFromPath(path); + } + readFile(pathToRead: string): string { return fs.readFileSync(pathToRead, 'utf8'); } diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts index b6b11333..76b21220 100644 --- a/src/application/source-code-file-reader.ts +++ b/src/application/source-code-file-reader.ts @@ -1,4 +1,3 @@ -import { MetadataResolver, SourceComponent } from '@salesforce/source-deploy-retrieve'; import { FileSystem } from './file-system'; import { UnparsedApexBundle, UnparsedSObjectBundle } from '../core/shared/types'; import { minimatch } from 'minimatch'; @@ -7,6 +6,20 @@ import { apply } from '#utils/fp'; type ComponentTypes = 'ApexClass' | 'CustomObject'; +/** + * Simplified representation of a source component, with only + * the required information we need. + */ +export type SourceComponentAdapter = { + name: string; + type: { + id: string; + name: string; + }; + xml?: string; + content?: string; +}; + type ApexClassApexSourceComponent = { type: 'ApexClass'; name: string; @@ -20,13 +33,9 @@ type CustomObjectSourceComponent = { contentPath: string; }; -function getMetadata(rootPath: string): SourceComponent[] { - return new MetadataResolver().getComponentsFromPath(rootPath); -} - function getApexSourceComponents( includeMetadata: boolean, - sourceComponents: SourceComponent[], + sourceComponents: SourceComponentAdapter[], ): ApexClassApexSourceComponent[] { return sourceComponents .filter((component) => component.type.name === 'ApexClass') @@ -57,7 +66,7 @@ function toUnparsedApexBundle( }); } -function getCustomObjectSourceComponents(sourceComponents: SourceComponent[]): CustomObjectSourceComponent[] { +function getCustomObjectSourceComponents(sourceComponents: SourceComponentAdapter[]): CustomObjectSourceComponent[] { return sourceComponents .filter((component) => component.type.name === 'CustomObject') .map((component) => ({ @@ -90,7 +99,7 @@ export function processFiles(fileSystem: FileSystem) { ) { const converters: Record< ComponentTypes, - (components: SourceComponent[]) => (UnparsedApexBundle | UnparsedSObjectBundle)[] + (components: SourceComponentAdapter[]) => (UnparsedApexBundle | UnparsedSObjectBundle)[] > = { ApexClass: flow(apply(getApexSourceComponents, options.includeMetadata), (apexSourceComponents) => toUnparsedApexBundle(fileSystem, apexSourceComponents), @@ -104,12 +113,10 @@ export function processFiles(fileSystem: FileSystem) { return function (rootPath: string, exclude: string[]) { return pipe( - getMetadata(rootPath), + fileSystem.getComponents(rootPath), (components) => components.filter((component) => !isExcluded(component.content!, exclude)), (components) => convertersToUse.map((converter) => converter(components)), - (bundles) => { - return bundles.reduce((acc, bundle) => [...acc, ...bundle], []); - }, + (bundles) => bundles.flat(), ); }; }; From dbde850a9fa2b4faddbaced70e05801da87fc27a Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sat, 19 Oct 2024 07:21:01 -0400 Subject: [PATCH 17/32] SObject renderable implementation --- examples/vitepress/docs/index.md | 56 ---- .../vitepress/docs/miscellaneous/BaseClass.md | 20 -- .../miscellaneous/MultiInheritanceClass.md | 76 ----- .../docs/miscellaneous/ParentInterface.md | 19 -- .../docs/miscellaneous/ReferencedEnum.md | 15 - .../docs/miscellaneous/SampleException.md | 28 -- .../docs/miscellaneous/SampleInterface.md | 116 ------- examples/vitepress/docs/miscellaneous/Url.md | 317 ------------------ .../vitepress/docs/sample-enums/SampleEnum.md | 40 --- .../vitepress/docs/samplegroup/SampleClass.md | 174 ---------- src/application/Apexdocs.ts | 13 +- src/application/generators/markdown.ts | 6 +- .../__tests__/interface-adapter.spec.ts | 2 +- .../adapters/fields-and-properties.ts | 4 +- .../markdown/adapters/renderable-bundle.ts | 2 +- .../adapters/renderable-to-page-data.ts | 2 + .../{apex-types.ts => type-to-renderable.ts} | 19 +- .../sobject/reflect-sobject-source.ts | 1 + src/core/renderables/types.d.ts | 16 +- 19 files changed, 42 insertions(+), 884 deletions(-) delete mode 100644 examples/vitepress/docs/index.md delete mode 100644 examples/vitepress/docs/miscellaneous/BaseClass.md delete mode 100644 examples/vitepress/docs/miscellaneous/MultiInheritanceClass.md delete mode 100644 examples/vitepress/docs/miscellaneous/ParentInterface.md delete mode 100644 examples/vitepress/docs/miscellaneous/ReferencedEnum.md delete mode 100644 examples/vitepress/docs/miscellaneous/SampleException.md delete mode 100644 examples/vitepress/docs/miscellaneous/SampleInterface.md delete mode 100644 examples/vitepress/docs/miscellaneous/Url.md delete mode 100644 examples/vitepress/docs/sample-enums/SampleEnum.md delete mode 100644 examples/vitepress/docs/samplegroup/SampleClass.md rename src/core/markdown/adapters/{apex-types.ts => type-to-renderable.ts} (94%) diff --git a/examples/vitepress/docs/index.md b/examples/vitepress/docs/index.md deleted file mode 100644 index cdb614cd..00000000 --- a/examples/vitepress/docs/index.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -# https://vitepress.dev/reference/default-theme-home-page -layout: home - -hero: - name: "Apexdocs Vitepress Example" - text: "API Documentation" - tagline: My great project tagline - actions: - - theme: brand - text: Markdown Examples - link: /markdown-examples - - theme: alt - text: API Examples - link: /api-examples ---- - -# Apex Reference Guide - -## Miscellaneous - -### [BaseClass](miscellaneous/BaseClass) - -### [MultiInheritanceClass](miscellaneous/MultiInheritanceClass) - -### [ParentInterface](miscellaneous/ParentInterface) - -### [ReferencedEnum](miscellaneous/ReferencedEnum) - -### [SampleException](miscellaneous/SampleException) - -This is a sample exception. - -### [SampleInterface](miscellaneous/SampleInterface) - -This is a sample interface - -### [Url](miscellaneous/Url) - -Represents a uniform resource locator (URL) and provides access to parts of the URL. -Enables access to the base URL used to access your Salesforce org. - -## Sample Enums - -### [SampleEnum](sample-enums/SampleEnum) - -This is a sample enum. This references [ReferencedEnum](miscellaneous/ReferencedEnum) . - -This description has several lines - -## SampleGroup - -### [SampleClass](samplegroup/SampleClass) - -aliquip ex sunt officia ullamco anim deserunt magna aliquip nisi eiusmod in sit officia veniam ex -**deserunt** ea officia exercitation laboris enim in duis quis enim eiusmod eu amet cupidatat. \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/BaseClass.md b/examples/vitepress/docs/miscellaneous/BaseClass.md deleted file mode 100644 index 62d3cf76..00000000 --- a/examples/vitepress/docs/miscellaneous/BaseClass.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: BaseClass ---- - -# BaseClass Class -`abstract` - -## Namespace -apexdocs - -## Fields -### `sampleEnumFromBase` - -#### Signature -```apex -public sampleEnumFromBase -``` - -#### Type -[SampleEnum](../sample-enums/SampleEnum) \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/MultiInheritanceClass.md b/examples/vitepress/docs/miscellaneous/MultiInheritanceClass.md deleted file mode 100644 index f0470f9b..00000000 --- a/examples/vitepress/docs/miscellaneous/MultiInheritanceClass.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: MultiInheritanceClass ---- - -# MultiInheritanceClass Class - -## Namespace -apexdocs - -**Inheritance** - -[SampleClass](../samplegroup/SampleClass) < [BaseClass](BaseClass) - -## Fields -### `sampleEnumFromBase` - -*Inherited* - -#### Signature -```apex -public sampleEnumFromBase -``` - -#### Type -[SampleEnum](../sample-enums/SampleEnum) - -## Properties -### Group Name -#### `someProperty` - -*Inherited* - -##### Signature -```apex -public someProperty -``` - -##### Type -String - -## Methods -### Available Methods -#### `doSomething()` - -*Inherited* - -##### Signature -```apex -public void doSomething() -``` - -##### Return Type -**void** - -### Deprecated Methods -#### `sayHello()` - -*Inherited* - -`DEPRECATED` - -This is a sample method. - -##### Signature -```apex -public virtual String sayHello() -``` - -##### Return Type -**String** - -A string value. - -##### Example -SampleClass sample = new SampleClass(); -sample.doSomething(); \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/ParentInterface.md b/examples/vitepress/docs/miscellaneous/ParentInterface.md deleted file mode 100644 index 6bbe2741..00000000 --- a/examples/vitepress/docs/miscellaneous/ParentInterface.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: ParentInterface ---- - -# ParentInterface Interface - -## Namespace -apexdocs - -## Methods -### `sampleParentMethod()` - -#### Signature -```apex -public void sampleParentMethod() -``` - -#### Return Type -**void** \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/ReferencedEnum.md b/examples/vitepress/docs/miscellaneous/ReferencedEnum.md deleted file mode 100644 index 69e97a43..00000000 --- a/examples/vitepress/docs/miscellaneous/ReferencedEnum.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: ReferencedEnum ---- - -# ReferencedEnum Enum - -## Namespace -apexdocs - -## Values -| Value | Description | -|-------|-------------| -| VALUE1 | | -| VALUE2 | | -| VALUE3 | | \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/SampleException.md b/examples/vitepress/docs/miscellaneous/SampleException.md deleted file mode 100644 index cc919c74..00000000 --- a/examples/vitepress/docs/miscellaneous/SampleException.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: SampleException ---- - -# SampleException Class - -This is a sample exception. - -**Usage** - -You can use the exception the following way. -You can also take a look at [SampleClass](../samplegroup/SampleClass) to see how it is used. -This is a dangerous HTML tag: <script>alert('Hello');</script> - -```apex -try { - throw new SampleException(); -} catch (SampleException e) { - System.debug('Caught exception'); -} -``` - -## Namespace -apexdocs - -**Inheritance** - -Exception \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/SampleInterface.md b/examples/vitepress/docs/miscellaneous/SampleInterface.md deleted file mode 100644 index 2a719b54..00000000 --- a/examples/vitepress/docs/miscellaneous/SampleInterface.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -title: SampleInterface ---- - -# SampleInterface Interface - -`NAMESPACEACCESSIBLE` - -This is a sample interface - -**Mermaid** - -graph TD -A[SampleInterface] -->|extends| B[ParentInterface] -B -->|extends| C[GrandParentInterface] -C -->|extends| D[GreatGrandParentInterface] - -**Author** John Doe - -**Date** 2020-01-01 - -**See** [SampleEnum](../sample-enums/SampleEnum) - -**See** [ReferencedEnum](ReferencedEnum) - -## Namespace -apexdocs - -## Example -SampleInterface sampleInterface = new SampleInterface(); -sampleInterface.sampleMethod(); - -**Extends** -[ParentInterface](ParentInterface) - -## Methods -### `sampleMethod()` - -`NAMESPACEACCESSIBLE` - -This is a sample method - -**Custom Tag** - -This is a custom tag - -**Another Custom Tag** - -This is another custom tag - -**Mermaid** - -graph TD -A[SampleInterface] -->|extends| B[ParentInterface] -B -->|extends| C[GrandParentInterface] -C -->|extends| D[GreatGrandParentInterface] - -#### Signature -```apex -public String sampleMethod() -``` - -#### Return Type -**String** - -Some return value - -#### Throws -[SampleException](SampleException): This is a sample exception - -AnotherSampleException: This is another sample exception - -#### Example -SampleInterface sampleInterface = new SampleInterface(); -sampleInterface.sampleMethod(); - ---- - -### `sampleMethodWithParams(param1, param2, theEnum)` - -`NAMESPACEACCESSIBLE` -`DEPRECATED` - -This is a sample method with parameters -Sometimes it won't be possible to find a NonExistent link. - -#### Signature -```apex -public SampleEnum sampleMethodWithParams(String param1, Integer param2, SampleEnum theEnum) -``` - -#### Parameters -| Name | Type | Description | -|------|------|-------------| -| param1 | String | This is the first parameter | -| param2 | Integer | This is the second parameter | -| theEnum | [SampleEnum](../sample-enums/SampleEnum) | This is an enum parameter | - -#### Return Type -**[SampleEnum](../sample-enums/SampleEnum)** - -Some return value - ---- - -### `sampleParentMethod()` - -*Inherited* - -#### Signature -```apex -public void sampleParentMethod() -``` - -#### Return Type -**void** \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/Url.md b/examples/vitepress/docs/miscellaneous/Url.md deleted file mode 100644 index ff2038da..00000000 --- a/examples/vitepress/docs/miscellaneous/Url.md +++ /dev/null @@ -1,317 +0,0 @@ ---- -title: Url ---- - -# Url Class - -Represents a uniform resource locator (URL) and provides access to parts of the URL. -Enables access to the base URL used to access your Salesforce org. - -**Usage** - -Use the methods of the `System.URL` class to create links to objects in your organization. Such objects can be files, images, -logos, or records that you want to include in external emails, in activities, or in Chatter posts. For example, you can create -a link to a file uploaded as an attachment to a Chatter post by concatenating the Salesforce base URL with the file ID: - -```apex -// Get a file uploaded through Chatter. -ContentDocument doc = [SELECT Id FROM ContentDocument - WHERE Title = 'myfile']; -// Create a link to the file. -String fullFileURL = URL.getOrgDomainURL().toExternalForm() + - '/' + doc.id; -System.debug(fullFileURL); -``` - - -The following example creates a link to a Salesforce record. The full URL is created by concatenating the Salesforce base -URL with the record ID. - -```apex -Account acct = [SELECT Id FROM Account WHERE Name = 'Acme' LIMIT 1]; -String fullRecordURL = URL.getOrgDomainURL().toExternalForm() + '/' + acct.Id; -``` - -**Version Behavior Changes** - -In API version 41.0 and later, Apex URL objects are represented by the java.net.URI type, not the java.net.URL type. - -The API version in which the URL object was instantiated determines the behavior of subsequent method calls to the -specific instance. Salesforce strongly encourages you to use API 41.0 and later versions for fully RFC-compliant URL -parsing that includes proper handling of edge cases of complex URL structures. API 41.0 and later versions also enforce -that inputs are valid, RFC-compliant URL or URI strings. - -* [URL Constructors](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_url.htm#apex_System_URL_constructors) -* [URL Methods](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_url.htm#apex_System_URL_methods) - -**See Also** -* [URL Class](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_url.htm) - -## Namespace -apexdocs - -## Example -In this example, the base URL and the full request URL of the current Salesforce server instance are retrieved. Next, a URL -pointing to a specific account object is created. Finally, components of the base and full URL are obtained. This example -prints out all the results to the debug log output. - -```apex -// Create a new account called Acme that we will create a link for later. -Account myAccount = new Account(Name='Acme'); -insert myAccount; - -// Get the base URL. -String sfdcBaseURL = URL.getOrgDomainURL().toExternalForm(); -System.debug('Base URL: ' + sfdcBaseURL ); - -// Get the URL for the current request. -String currentRequestURL = URL.getCurrentRequestUrl().toExternalForm(); -System.debug('Current request URL: ' + currentRequestURL); - -// Create the account URL from the base URL. -String accountURL = URL.getOrgDomainURL().toExternalForm() + - '/' + myAccount.Id; -System.debug('URL of a particular account: ' + accountURL); - -// Get some parts of the base URL. -System.debug('Host: ' + URL.getOrgDomainURL().getHost()); -System.debug('Protocol: ' + URL.getOrgDomainURL().getProtocol()); - -// Get the query string of the current request. -System.debug('Query: ' + URL.getCurrentRequestUrl().getQuery()); -``` - -## Constructors -### `Url(spec)` - -Creates a new instance of the URL class using the specified string representation of the URL. - -#### Signature -```apex -global Url(String spec) -``` - -#### Parameters -| Name | Type | Description | -|------|------|-------------| -| spec | String | The string to parse as a URL. | - ---- - -### `Url(context, spec)` - -Creates a new instance of the URL class by parsing the specified spec within the specified context. - -**Usage** - -The new URL is created from the given context URL and the spec argument as described in RFC2396 "Uniform Resource Identifiers : Generic * Syntax" : -```xml -://?# -``` - - -For more information about the arguments of this constructor, see the corresponding URL(java.net.URL, java.lang.String) constructor for Java. - -#### Signature -```apex -global Url(Url context, String spec) -``` - -#### Parameters -| Name | Type | Description | -|------|------|-------------| -| context | [Url](Url) | The context in which to parse the specification. | -| spec | String | The string to parse as a URL. | - ---- - -### `Url(protocol, host, file)` - -Creates a new instance of the URL class using the specified protocol, host, and file on the host. The default port for the specified protocol is used. - -#### Signature -```apex -global Url(String protocol, String host, String file) -``` - -#### Parameters -| Name | Type | Description | -|------|------|-------------| -| protocol | String | The protocol name for this URL. | -| host | String | The host name for this URL. | -| file | String | The file name for this URL. | - ---- - -### `Url(protocol, host, port, file)` - -Creates a new instance of the URL class using the specified protocol, host, port number, and file on the host. - -#### Signature -```apex -global Url(String protocol, String host, Integer port, String file) -``` - -#### Parameters -| Name | Type | Description | -|------|------|-------------| -| protocol | String | The protocol name for this URL. | -| host | String | The host name for this URL. | -| port | Integer | The port number for this URL. | -| file | String | The file name for this URL. | - -## Methods -### `getAuthority()` - -Returns the authority portion of the current URL. - -#### Signature -```apex -global String getAuthority() -``` - -#### Return Type -**String** - -The authority portion of the current URL. - ---- - -### `getCurrentRequestUrl()` - -Returns the URL of an entire request on a Salesforce instance. - -**Usage** - -An example of a URL for an entire request is https://yourInstance.salesforce.com/apex/myVfPage.apexp. - -#### Signature -```apex -global static Url getCurrentRequestUrl() -``` - -#### Return Type -**[Url](Url)** - -The URL of the entire request. - ---- - -### `getDefPort()` - -Returns the default port number of the protocol associated with the current URL. - -**Usage** - -Returns -1 if the URL scheme or the stream protocol handler for the URL doesn't define a default port number. - -#### Signature -```apex -global Integer getDefPort() -``` - -#### Return Type -**Integer** - -The default port number of the protocol associated with the current URL. - ---- - -### `getFile()` - -Returns the file name of the current URL. - -#### Signature -```apex -global String getFile() -``` - -#### Return Type -**String** - -The file name of the current URL. - ---- - -### `getFileFieldURL(entityId, fieldName)` - -Returns the download URL for a file attachment. - -#### Signature -```apex -global static String getFileFieldURL(String entityId, String fieldName) -``` - -#### Parameters -| Name | Type | Description | -|------|------|-------------| -| entityId | String | Specifies the ID of the entity that holds the file data. | -| fieldName | String | Specifies the API name of a file field component, such as `AttachmentBody` . | - -#### Return Type -**String** - -The download URL for the file attachment. - -#### Example -String fileURL = -URL.getFileFieldURL( -'087000000000123' , -'AttachmentBody'); - ---- - -### `getHost()` - -Returns the host name of the current URL. - -#### Signature -```apex -global String getHost() -``` - -#### Return Type -**String** - -The host name of the current URL. - ---- - -### `getOrgDomainUrl()` - -Returns the canonical URL for your org. For example, https://MyDomainName.my.salesforce.com. - -**Usage** - -Use getOrgDomainUrl() to interact with Salesforce REST and SOAP APIs in Apex code. Get endpoints for User Interface API calls, for creating and customizing picklist value sets and custom fields, and more. - - `getOrgDomainUrl()` can access the domain URL only for the org in which the Apex code is running. - -You don't need a RemoteSiteSetting for your org to interact with the Salesforce APIs using domain URLs retrieved with this method. - -**See Also** - -* [Lightning Aura Components Developer Guide: Making API Calls from Apex](https://developer.salesforce.com/docs/atlas.en-us.250.0.lightning.meta/lightning/apex_api_calls.htm) - -#### Signature -```apex -global static Url getOrgDomainUrl() -``` - -#### Return Type -**[Url](Url)** - -getOrgDomainUrl() always returns the login URL for your org, regardless of context. Use that URL when making API calls to your org. - -#### Example -This example uses the Salesforce REST API to get organization limit values. For information on limits, see Limits in the REST API Developer Guide. - -```apex -Http h = new Http(); -HttpRequest req = new HttpRequest(); -req.setEndpoint(Url.getOrgDomainUrl().toExternalForm() - + '/services/data/v44.0/limits'); -req.setMethod('GET'); -req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId()); -HttpResponse res = h.send(req); -``` \ No newline at end of file diff --git a/examples/vitepress/docs/sample-enums/SampleEnum.md b/examples/vitepress/docs/sample-enums/SampleEnum.md deleted file mode 100644 index 539f01e0..00000000 --- a/examples/vitepress/docs/sample-enums/SampleEnum.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: SampleEnum ---- - -# SampleEnum Enum - -`NAMESPACEACCESSIBLE` - -This is a sample enum. This references [ReferencedEnum](../miscellaneous/ReferencedEnum) . - -This description has several lines - -**Some Custom** - -Test. I can also have a [ReferencedEnum](../miscellaneous/ReferencedEnum) here. -And it can be multiline. - -**Mermaid** - -graph TD -A[SampleEnum] -->|references| B[ReferencedEnum] -B -->|referenced by| A - -**Group** Sample Enums - -**Author** John Doe - -**Date** 2022-01-01 - -**See** [ReferencedEnum](../miscellaneous/ReferencedEnum) - -## Namespace -apexdocs - -## Values -| Value | Description | -|-------|-------------| -| VALUE1 | This is value 1 | -| VALUE2 | This is value 2 | -| VALUE3 | This is value 3 | \ No newline at end of file diff --git a/examples/vitepress/docs/samplegroup/SampleClass.md b/examples/vitepress/docs/samplegroup/SampleClass.md deleted file mode 100644 index 38de2d1f..00000000 --- a/examples/vitepress/docs/samplegroup/SampleClass.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -title: SampleClass ---- - -# SampleClass Class -`virtual` - -aliquip ex sunt officia ullamco anim deserunt magna aliquip nisi eiusmod in sit officia veniam ex -**deserunt** ea officia exercitation laboris enim in duis quis enim eiusmod eu amet cupidatat. - -**Group** SampleGroup - -## Namespace -apexdocs - -## Example -SampleClass sample = new SampleClass(); -sample.doSomething(); - -**Inheritance** - -[BaseClass](../miscellaneous/BaseClass) - -**Implements** - -[SampleInterface](../miscellaneous/SampleInterface), -[ParentInterface](../miscellaneous/ParentInterface) - -## Fields -### Group Name -#### `name` - -This is a sample field. - -##### Signature -```apex -private final name -``` - -##### Type -String - -### Other -#### `sampleEnumFromBase` - -*Inherited* - -##### Signature -```apex -public sampleEnumFromBase -``` - -##### Type -[SampleEnum](../sample-enums/SampleEnum) - -## Properties -### Group Name -#### `someProperty` - -##### Signature -```apex -public someProperty -``` - -##### Type -String - -## Constructors -### Other -#### `SampleClass()` - -This is a sample constructor. - -##### Signature -```apex -public SampleClass() -``` - -### Other Constructors -#### `SampleClass(name)` - -##### Signature -```apex -public SampleClass(String name) -``` - -##### Parameters -| Name | Type | Description | -|------|------|-------------| -| name | String | | - -## Methods -### Available Methods -#### `doSomething()` - -##### Signature -```apex -public void doSomething() -``` - -##### Return Type -**void** - -### Deprecated Methods -#### `sayHello()` - -`DEPRECATED` - -This is a sample method. - -##### Signature -```apex -public virtual String sayHello() -``` - -##### Return Type -**String** - -A string value. - -##### Example -SampleClass sample = new SampleClass(); -sample.doSomething(); - -## Classes -### SomeInnerClass Class - -#### Fields -##### `someInnerField` - -###### Signature -```apex -public someInnerField -``` - -###### Type -String - -#### Methods -##### `doSomething()` - -###### Signature -```apex -public void doSomething() -``` - -###### Return Type -**void** - -## Enums -### SomeEnum Enum - -This enum is used for foo and bar. - -#### Values -| Value | Description | -|-------|-------------| -| TEST_1 | This is a test. | -| TEST_2 | | -| TEST_3 | | - -## Interfaces -### SomeInterface Interface - -#### Methods -##### `doSomething()` - -###### Signature -```apex -public void doSomething() -``` - -###### Return Type -**void** \ No newline at end of file diff --git a/src/application/Apexdocs.ts b/src/application/Apexdocs.ts index 6bf021da..c0a3fc43 100644 --- a/src/application/Apexdocs.ts +++ b/src/application/Apexdocs.ts @@ -48,19 +48,16 @@ export class Apexdocs { const readFiles = processFiles(new DefaultFileSystem()); async function processMarkdown(config: UserDefinedMarkdownConfig) { - // TODO: Also process Object files return pipe( - TE.tryCatch( - // TODO: We do not need to deal with promises anymore (in this step) + E.tryCatch( () => - Promise.resolve( - readFiles(['ApexClass'], { includeMetadata: config.includeMetadata })( - config.sourceDir, - config.exclude, - ) as UnparsedApexBundle[], + readFiles(['ApexClass', 'CustomObject'], { includeMetadata: config.includeMetadata })( + config.sourceDir, + config.exclude, ), (e) => new FileReadingError('An error occurred while reading files.', e), ), + TE.fromEither, TE.flatMap((fileBodies) => markdown(fileBodies, config)), TE.map(() => '✔️ Documentation generated successfully!'), TE.mapLeft(toErrors), diff --git a/src/application/generators/markdown.ts b/src/application/generators/markdown.ts index 0b048610..56f2e9e1 100644 --- a/src/application/generators/markdown.ts +++ b/src/application/generators/markdown.ts @@ -3,7 +3,7 @@ import { pipe } from 'fp-ts/function'; import { PageData, PostHookDocumentationBundle, - UnparsedApexBundle, + UnparsedSourceBundle, UserDefinedMarkdownConfig, } from '../../core/shared/types'; import { referenceGuideTemplate } from '../../core/markdown/templates/reference-guide'; @@ -12,14 +12,14 @@ import { isSkip } from '../../core/shared/utils'; import { writeFiles } from '../file-writer'; import { FileWritingError } from '../errors'; -export default function generate(bundles: UnparsedApexBundle[], config: UserDefinedMarkdownConfig) { +export default function generate(bundles: UnparsedSourceBundle[], config: UserDefinedMarkdownConfig) { return pipe( generateDocumentationBundle(bundles, config), TE.flatMap((files) => writeFilesToSystem(files, config.targetDir)), ); } -function generateDocumentationBundle(bundles: UnparsedApexBundle[], config: UserDefinedMarkdownConfig) { +function generateDocumentationBundle(bundles: UnparsedSourceBundle[], config: UserDefinedMarkdownConfig) { return generateDocs(bundles, { ...config, referenceGuideTemplate: referenceGuideTemplate, diff --git a/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts b/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts index 6b818cea..34315f63 100644 --- a/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts +++ b/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts @@ -1,4 +1,4 @@ -import { typeToRenderable } from '../apex-types'; +import { typeToRenderable } from '../type-to-renderable'; import { InterfaceMirrorBuilder } from '../../../../test-helpers/InterfaceMirrorBuilder'; import { AnnotationBuilder } from '../../../../test-helpers/AnnotationBuilder'; import { MethodMirrorBuilder, ParameterBuilder } from '../../../../test-helpers/MethodMirrorBuilder'; diff --git a/src/core/markdown/adapters/fields-and-properties.ts b/src/core/markdown/adapters/fields-and-properties.ts index f8450ce1..500fe5cc 100644 --- a/src/core/markdown/adapters/fields-and-properties.ts +++ b/src/core/markdown/adapters/fields-and-properties.ts @@ -2,7 +2,7 @@ import { CodeBlock, FieldMirrorWithInheritance, PropertyMirrorWithInheritance, - RenderableField, + RenderableApexField, GetRenderableContentByTypeName, } from '../../renderables/types'; import { adaptDocumentable } from '../../renderables/documentables'; @@ -11,7 +11,7 @@ export function adaptFieldOrProperty( field: FieldMirrorWithInheritance | PropertyMirrorWithInheritance, linkGenerator: GetRenderableContentByTypeName, baseHeadingLevel: number, -): RenderableField { +): RenderableApexField { function buildSignature(): CodeBlock { const { access_modifier, name } = field; const memberModifiers = field.memberModifiers.join(' '); diff --git a/src/core/markdown/adapters/renderable-bundle.ts b/src/core/markdown/adapters/renderable-bundle.ts index 8133909b..7208c3c4 100644 --- a/src/core/markdown/adapters/renderable-bundle.ts +++ b/src/core/markdown/adapters/renderable-bundle.ts @@ -6,7 +6,7 @@ import { RenderableBundle, RenderableContent, } from '../../renderables/types'; -import { typeToRenderable } from './apex-types'; +import { typeToRenderable } from './type-to-renderable'; import { adaptDescribable } from '../../renderables/documentables'; import { MarkdownGeneratorConfig } from '../generate-docs'; import { apply } from '#utils/fp'; diff --git a/src/core/markdown/adapters/renderable-to-page-data.ts b/src/core/markdown/adapters/renderable-to-page-data.ts index cc291602..96bb06e2 100644 --- a/src/core/markdown/adapters/renderable-to-page-data.ts +++ b/src/core/markdown/adapters/renderable-to-page-data.ts @@ -77,6 +77,8 @@ function resolveApexTypeTemplate(renderable: Renderable): CompilationRequest { return interfaceMarkdownTemplate; case 'class': return classMarkdownTemplate; + case 'sobject': + throw new Error('SObject type not supported'); } } diff --git a/src/core/markdown/adapters/apex-types.ts b/src/core/markdown/adapters/type-to-renderable.ts similarity index 94% rename from src/core/markdown/adapters/apex-types.ts rename to src/core/markdown/adapters/type-to-renderable.ts index d4c482ab..5c9fa237 100644 --- a/src/core/markdown/adapters/apex-types.ts +++ b/src/core/markdown/adapters/type-to-renderable.ts @@ -10,6 +10,7 @@ import { FieldMirrorWithInheritance, PropertyMirrorWithInheritance, GetRenderableContentByTypeName, + RenderableSObject, } from '../../renderables/types'; import { adaptDescribable, adaptDocumentable } from '../../renderables/documentables'; import { adaptConstructor, adaptMethod } from './methods-and-constructors'; @@ -24,14 +25,14 @@ type GetReturnRenderable = T extends InterfaceM ? RenderableClass : T extends EnumMirror ? RenderableEnum - : never; // TODO: Implement renderable object + : RenderableSObject; export function typeToRenderable( parsedFile: { source: SourceFileMetadata; type: T }, linkGenerator: GetRenderableContentByTypeName, config: MarkdownGeneratorConfig, ): GetReturnRenderable & { filePath: string; namespace?: string } { - function getRenderable(): RenderableInterface | RenderableClass | RenderableEnum { + function getRenderable(): RenderableInterface | RenderableClass | RenderableEnum | RenderableSObject { const { type } = parsedFile; switch (type.type_name) { case 'enum': @@ -41,7 +42,7 @@ export function typeToRenderable( case 'class': return classTypeToClassSource(type as ClassMirrorWithInheritanceChain, linkGenerator); case 'sobject': - throw new Error('Not implemented'); + return objectMetadataToRenderable(type as ObjectMetadata); } } @@ -97,6 +98,18 @@ function enumTypeToEnumSource( }; } +function objectMetadataToRenderable(objectMetadata: ObjectMetadata): RenderableSObject { + return { + type: 'sobject', + headingLevel: 1, + heading: objectMetadata.name, + name: objectMetadata.label, + doc: { + description: objectMetadata.description ? [objectMetadata.description] : [], + }, + }; +} + function interfaceTypeToInterfaceSource( interfaceType: InterfaceMirror, linkGenerator: GetRenderableContentByTypeName, diff --git a/src/core/reflection/sobject/reflect-sobject-source.ts b/src/core/reflection/sobject/reflect-sobject-source.ts index 1f43c49b..c9607643 100644 --- a/src/core/reflection/sobject/reflect-sobject-source.ts +++ b/src/core/reflection/sobject/reflect-sobject-source.ts @@ -14,6 +14,7 @@ export type ObjectMetadata = { visibility: string; label: string; name: string; + description: string | null; }; export function reflectSObjectSources( diff --git a/src/core/renderables/types.d.ts b/src/core/renderables/types.d.ts index 878bc918..5dbdf7a8 100644 --- a/src/core/renderables/types.d.ts +++ b/src/core/renderables/types.d.ts @@ -82,7 +82,7 @@ type RenderableDocumentation = { annotations?: Annotation[]; description?: RenderableContent[]; customTags?: CustomTag[]; - example: RenderableSection; + example?: RenderableSection; group?: string; author?: string; date?: string; @@ -131,7 +131,7 @@ type RenderableMethod = { inherited?: boolean; }; -type RenderableField = { +type RenderableApexField = { headingLevel: number; heading: string; type: RenderableSection; @@ -159,8 +159,8 @@ export type RenderableClass = RenderableType & { isGrouped: boolean; }; methods: RenderableSection[]> & { isGrouped: boolean }; - fields: RenderableSection[]> & { isGrouped: boolean }; - properties: RenderableSection[]> & { isGrouped: boolean }; + fields: RenderableSection[]> & { isGrouped: boolean }; + properties: RenderableSection[]> & { isGrouped: boolean }; innerClasses: RenderableSection; innerEnums: RenderableSection; innerInterfaces: RenderableSection; @@ -177,4 +177,10 @@ export type RenderableEnum = RenderableType & { values: RenderableSection; }; -export type Renderable = (RenderableClass | RenderableInterface | RenderableEnum) & { filePath: string }; +export type RenderableSObject = Omit & { + type: 'sobject'; +}; + +export type Renderable = (RenderableClass | RenderableInterface | RenderableEnum | RenderableSObject) & { + filePath: string; +}; From b89f5c052c85eb4b8b72bdb6f9191f7be5adc4c6 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sat, 19 Oct 2024 08:35:47 -0400 Subject: [PATCH 18/32] SObject template implementation --- examples/vitepress/apexdocs.config.ts | 19 +- .../vitepress/docs/.vitepress/sidebar.json | 75 ++--- .../vitepress/docs/custom-objects/Event__c.md | 10 + .../docs/custom-objects/Price_Component__c.md | 8 + .../Product_Price_Component__c.md | 8 + .../docs/custom-objects/Product__c.md | 10 + .../custom-objects/Sales_Order_Line__c.md | 10 + .../docs/custom-objects/Sales_Order__c.md | 10 + .../docs/custom-objects/Speaker__c.md | 10 + examples/vitepress/docs/index.md | 82 +++++ .../vitepress/docs/miscellaneous/BaseClass.md | 20 ++ .../miscellaneous/MultiInheritanceClass.md | 76 +++++ .../docs/miscellaneous/ParentInterface.md | 19 ++ .../docs/miscellaneous/ReferencedEnum.md | 15 + .../docs/miscellaneous/SampleException.md | 28 ++ .../docs/miscellaneous/SampleInterface.md | 116 +++++++ examples/vitepress/docs/miscellaneous/Url.md | 317 ++++++++++++++++++ .../vitepress/docs/sample-enums/SampleEnum.md | 40 +++ .../vitepress/docs/samplegroup/SampleClass.md | 174 ++++++++++ .../objects/Event__c/Event__c.object-meta.xml | 1 + .../Event__c/listViews/All.listView-meta.xml | 6 - .../Product__c/Product__c.object-meta.xml | 1 + .../Sales_Order_Line__c.object-meta.xml | 1 + .../Sales_Order__c.object-meta.xml | 1 + .../listViews/All.listView-meta.xml | 6 - .../Speaker__c/Speaker__c.object-meta.xml | 1 + src/cli/commands/markdown.ts | 5 + src/core/markdown/__test__/test-helpers.ts | 1 + .../__tests__/interface-adapter.spec.ts | 1 + .../markdown/adapters/renderable-bundle.ts | 3 +- .../adapters/renderable-to-page-data.ts | 3 +- .../markdown/adapters/type-to-renderable.ts | 22 +- .../markdown/templates/sobject-template.ts | 9 + src/core/renderables/types.d.ts | 1 + src/core/shared/types.d.ts | 3 +- src/core/shared/utils.ts | 2 +- src/defaults.ts | 3 +- 37 files changed, 1028 insertions(+), 89 deletions(-) create mode 100644 examples/vitepress/docs/custom-objects/Event__c.md create mode 100644 examples/vitepress/docs/custom-objects/Price_Component__c.md create mode 100644 examples/vitepress/docs/custom-objects/Product_Price_Component__c.md create mode 100644 examples/vitepress/docs/custom-objects/Product__c.md create mode 100644 examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md create mode 100644 examples/vitepress/docs/custom-objects/Sales_Order__c.md create mode 100644 examples/vitepress/docs/custom-objects/Speaker__c.md create mode 100644 examples/vitepress/docs/index.md create mode 100644 examples/vitepress/docs/miscellaneous/BaseClass.md create mode 100644 examples/vitepress/docs/miscellaneous/MultiInheritanceClass.md create mode 100644 examples/vitepress/docs/miscellaneous/ParentInterface.md create mode 100644 examples/vitepress/docs/miscellaneous/ReferencedEnum.md create mode 100644 examples/vitepress/docs/miscellaneous/SampleException.md create mode 100644 examples/vitepress/docs/miscellaneous/SampleInterface.md create mode 100644 examples/vitepress/docs/miscellaneous/Url.md create mode 100644 examples/vitepress/docs/sample-enums/SampleEnum.md create mode 100644 examples/vitepress/docs/samplegroup/SampleClass.md delete mode 100644 examples/vitepress/force-app/main/default/objects/Event__c/listViews/All.listView-meta.xml delete mode 100644 examples/vitepress/force-app/main/default/objects/Sales_Order__c/listViews/All.listView-meta.xml create mode 100644 src/core/markdown/templates/sobject-template.ts diff --git a/examples/vitepress/apexdocs.config.ts b/examples/vitepress/apexdocs.config.ts index 9cd727a9..8584092f 100644 --- a/examples/vitepress/apexdocs.config.ts +++ b/examples/vitepress/apexdocs.config.ts @@ -57,24 +57,7 @@ export default { text: 'API Reference', items: [ { - text: 'Grouped By Type', - items: [ - { - text: 'Classes', - items: docs.filter((doc) => doc.source.type === 'class').map(toSidebarLink), - }, - { - text: 'Interfaces', - items: docs.filter((doc) => doc.source.type === 'interface').map(toSidebarLink), - }, - { - text: 'Enums', - items: docs.filter((doc) => doc.source.type === 'enum').map(toSidebarLink), - }, - ], - }, - { - text: 'Grouped by Group', + text: 'Groups', items: Array.from(extractGroups(docs)).map(([groupName, groupDocs]) => ({ text: groupName, items: groupDocs.map(toSidebarLink), diff --git a/examples/vitepress/docs/.vitepress/sidebar.json b/examples/vitepress/docs/.vitepress/sidebar.json index a4e2b837..6531c3de 100644 --- a/examples/vitepress/docs/.vitepress/sidebar.json +++ b/examples/vitepress/docs/.vitepress/sidebar.json @@ -3,10 +3,10 @@ "text": "API Reference", "items": [ { - "text": "Grouped By Type", + "text": "Groups", "items": [ { - "text": "Classes", + "text": "Miscellaneous", "items": [ { "text": "BaseClass", @@ -17,80 +17,57 @@ "link": "miscellaneous/MultiInheritanceClass.md" }, { - "text": "SampleClass", - "link": "samplegroup/SampleClass.md" + "text": "ParentInterface", + "link": "miscellaneous/ParentInterface.md" }, { - "text": "SampleException", - "link": "miscellaneous/SampleException.md" + "text": "ReferencedEnum", + "link": "miscellaneous/ReferencedEnum.md" }, { - "text": "Url", - "link": "miscellaneous/Url.md" - } - ] - }, - { - "text": "Interfaces", - "items": [ - { - "text": "ParentInterface", - "link": "miscellaneous/ParentInterface.md" + "text": "SampleException", + "link": "miscellaneous/SampleException.md" }, { "text": "SampleInterface", "link": "miscellaneous/SampleInterface.md" - } - ] - }, - { - "text": "Enums", - "items": [ - { - "text": "ReferencedEnum", - "link": "miscellaneous/ReferencedEnum.md" }, { - "text": "SampleEnum", - "link": "sample-enums/SampleEnum.md" + "text": "Url", + "link": "miscellaneous/Url.md" } ] - } - ] - }, - { - "text": "Grouped by Group", - "items": [ + }, { - "text": "Miscellaneous", + "text": "Custom Objects", "items": [ { - "text": "BaseClass", - "link": "miscellaneous/BaseClass.md" + "text": "Event__c", + "link": "custom-objects/Event__c.md" }, { - "text": "MultiInheritanceClass", - "link": "miscellaneous/MultiInheritanceClass.md" + "text": "Price_Component__c", + "link": "custom-objects/Price_Component__c.md" }, { - "text": "ParentInterface", - "link": "miscellaneous/ParentInterface.md" + "text": "Product__c", + "link": "custom-objects/Product__c.md" }, { - "text": "ReferencedEnum", - "link": "miscellaneous/ReferencedEnum.md" + "text": "Product_Price_Component__c", + "link": "custom-objects/Product_Price_Component__c.md" }, { - "text": "SampleException", - "link": "miscellaneous/SampleException.md" + "text": "Sales_Order__c", + "link": "custom-objects/Sales_Order__c.md" }, { - "text": "SampleInterface", - "link": "miscellaneous/SampleInterface.md" + "text": "Sales_Order_Line__c", + "link": "custom-objects/Sales_Order_Line__c.md" }, { - "text": "Url", - "link": "miscellaneous/Url.md" + "text": "Speaker__c", + "link": "custom-objects/Speaker__c.md" } ] }, diff --git a/examples/vitepress/docs/custom-objects/Event__c.md b/examples/vitepress/docs/custom-objects/Event__c.md new file mode 100644 index 00000000..88d204b2 --- /dev/null +++ b/examples/vitepress/docs/custom-objects/Event__c.md @@ -0,0 +1,10 @@ +--- +title: Event__c +--- + +# Event + +Represents an event that people can register for. + +## API Name +`apexdocs__Event__c` \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Price_Component__c.md b/examples/vitepress/docs/custom-objects/Price_Component__c.md new file mode 100644 index 00000000..c50fef14 --- /dev/null +++ b/examples/vitepress/docs/custom-objects/Price_Component__c.md @@ -0,0 +1,8 @@ +--- +title: Price_Component__c +--- + +# Price Component + +## API Name +`apexdocs__Price_Component__c` \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md b/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md new file mode 100644 index 00000000..aae375b9 --- /dev/null +++ b/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md @@ -0,0 +1,8 @@ +--- +title: Product_Price_Component__c +--- + +# Product Price Component + +## API Name +`apexdocs__Product_Price_Component__c` \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Product__c.md b/examples/vitepress/docs/custom-objects/Product__c.md new file mode 100644 index 00000000..8830ca4f --- /dev/null +++ b/examples/vitepress/docs/custom-objects/Product__c.md @@ -0,0 +1,10 @@ +--- +title: Product__c +--- + +# Product (Custom) + +Product that is sold or available for sale. + +## API Name +`apexdocs__Product__c` \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md b/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md new file mode 100644 index 00000000..245cd660 --- /dev/null +++ b/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md @@ -0,0 +1,10 @@ +--- +title: Sales_Order_Line__c +--- + +# Sales Order Line + +Represents a line item on a sales order. + +## API Name +`apexdocs__Sales_Order_Line__c` \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Sales_Order__c.md b/examples/vitepress/docs/custom-objects/Sales_Order__c.md new file mode 100644 index 00000000..220efa21 --- /dev/null +++ b/examples/vitepress/docs/custom-objects/Sales_Order__c.md @@ -0,0 +1,10 @@ +--- +title: Sales_Order__c +--- + +# Sales Order + +Custom object for tracking sales orders. + +## API Name +`apexdocs__Sales_Order__c` \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Speaker__c.md b/examples/vitepress/docs/custom-objects/Speaker__c.md new file mode 100644 index 00000000..349d6d70 --- /dev/null +++ b/examples/vitepress/docs/custom-objects/Speaker__c.md @@ -0,0 +1,10 @@ +--- +title: Speaker__c +--- + +# Speaker + +Represents a speaker at an event. + +## API Name +`apexdocs__Speaker__c` \ No newline at end of file diff --git a/examples/vitepress/docs/index.md b/examples/vitepress/docs/index.md new file mode 100644 index 00000000..e8629caa --- /dev/null +++ b/examples/vitepress/docs/index.md @@ -0,0 +1,82 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "Apexdocs Vitepress Example" + text: "API Documentation" + tagline: My great project tagline + actions: + - theme: brand + text: Markdown Examples + link: /markdown-examples + - theme: alt + text: API Examples + link: /api-examples +--- + +# Reference Guide + +## Custom Objects + +### [Event__c](custom-objects/Event__c) + +Represents an event that people can register for. + +### [Price_Component__c](custom-objects/Price_Component__c) + +### [Product__c](custom-objects/Product__c) + +Product that is sold or available for sale. + +### [Product_Price_Component__c](custom-objects/Product_Price_Component__c) + +### [Sales_Order__c](custom-objects/Sales_Order__c) + +Custom object for tracking sales orders. + +### [Sales_Order_Line__c](custom-objects/Sales_Order_Line__c) + +Represents a line item on a sales order. + +### [Speaker__c](custom-objects/Speaker__c) + +Represents a speaker at an event. + +## Miscellaneous + +### [BaseClass](miscellaneous/BaseClass) + +### [MultiInheritanceClass](miscellaneous/MultiInheritanceClass) + +### [ParentInterface](miscellaneous/ParentInterface) + +### [ReferencedEnum](miscellaneous/ReferencedEnum) + +### [SampleException](miscellaneous/SampleException) + +This is a sample exception. + +### [SampleInterface](miscellaneous/SampleInterface) + +This is a sample interface + +### [Url](miscellaneous/Url) + +Represents a uniform resource locator (URL) and provides access to parts of the URL. +Enables access to the base URL used to access your Salesforce org. + +## Sample Enums + +### [SampleEnum](sample-enums/SampleEnum) + +This is a sample enum. This references [ReferencedEnum](miscellaneous/ReferencedEnum) . + +This description has several lines + +## SampleGroup + +### [SampleClass](samplegroup/SampleClass) + +aliquip ex sunt officia ullamco anim deserunt magna aliquip nisi eiusmod in sit officia veniam ex +**deserunt** ea officia exercitation laboris enim in duis quis enim eiusmod eu amet cupidatat. \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/BaseClass.md b/examples/vitepress/docs/miscellaneous/BaseClass.md new file mode 100644 index 00000000..62d3cf76 --- /dev/null +++ b/examples/vitepress/docs/miscellaneous/BaseClass.md @@ -0,0 +1,20 @@ +--- +title: BaseClass +--- + +# BaseClass Class +`abstract` + +## Namespace +apexdocs + +## Fields +### `sampleEnumFromBase` + +#### Signature +```apex +public sampleEnumFromBase +``` + +#### Type +[SampleEnum](../sample-enums/SampleEnum) \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/MultiInheritanceClass.md b/examples/vitepress/docs/miscellaneous/MultiInheritanceClass.md new file mode 100644 index 00000000..f0470f9b --- /dev/null +++ b/examples/vitepress/docs/miscellaneous/MultiInheritanceClass.md @@ -0,0 +1,76 @@ +--- +title: MultiInheritanceClass +--- + +# MultiInheritanceClass Class + +## Namespace +apexdocs + +**Inheritance** + +[SampleClass](../samplegroup/SampleClass) < [BaseClass](BaseClass) + +## Fields +### `sampleEnumFromBase` + +*Inherited* + +#### Signature +```apex +public sampleEnumFromBase +``` + +#### Type +[SampleEnum](../sample-enums/SampleEnum) + +## Properties +### Group Name +#### `someProperty` + +*Inherited* + +##### Signature +```apex +public someProperty +``` + +##### Type +String + +## Methods +### Available Methods +#### `doSomething()` + +*Inherited* + +##### Signature +```apex +public void doSomething() +``` + +##### Return Type +**void** + +### Deprecated Methods +#### `sayHello()` + +*Inherited* + +`DEPRECATED` + +This is a sample method. + +##### Signature +```apex +public virtual String sayHello() +``` + +##### Return Type +**String** + +A string value. + +##### Example +SampleClass sample = new SampleClass(); +sample.doSomething(); \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/ParentInterface.md b/examples/vitepress/docs/miscellaneous/ParentInterface.md new file mode 100644 index 00000000..6bbe2741 --- /dev/null +++ b/examples/vitepress/docs/miscellaneous/ParentInterface.md @@ -0,0 +1,19 @@ +--- +title: ParentInterface +--- + +# ParentInterface Interface + +## Namespace +apexdocs + +## Methods +### `sampleParentMethod()` + +#### Signature +```apex +public void sampleParentMethod() +``` + +#### Return Type +**void** \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/ReferencedEnum.md b/examples/vitepress/docs/miscellaneous/ReferencedEnum.md new file mode 100644 index 00000000..69e97a43 --- /dev/null +++ b/examples/vitepress/docs/miscellaneous/ReferencedEnum.md @@ -0,0 +1,15 @@ +--- +title: ReferencedEnum +--- + +# ReferencedEnum Enum + +## Namespace +apexdocs + +## Values +| Value | Description | +|-------|-------------| +| VALUE1 | | +| VALUE2 | | +| VALUE3 | | \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/SampleException.md b/examples/vitepress/docs/miscellaneous/SampleException.md new file mode 100644 index 00000000..cc919c74 --- /dev/null +++ b/examples/vitepress/docs/miscellaneous/SampleException.md @@ -0,0 +1,28 @@ +--- +title: SampleException +--- + +# SampleException Class + +This is a sample exception. + +**Usage** + +You can use the exception the following way. +You can also take a look at [SampleClass](../samplegroup/SampleClass) to see how it is used. +This is a dangerous HTML tag: <script>alert('Hello');</script> + +```apex +try { + throw new SampleException(); +} catch (SampleException e) { + System.debug('Caught exception'); +} +``` + +## Namespace +apexdocs + +**Inheritance** + +Exception \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/SampleInterface.md b/examples/vitepress/docs/miscellaneous/SampleInterface.md new file mode 100644 index 00000000..2a719b54 --- /dev/null +++ b/examples/vitepress/docs/miscellaneous/SampleInterface.md @@ -0,0 +1,116 @@ +--- +title: SampleInterface +--- + +# SampleInterface Interface + +`NAMESPACEACCESSIBLE` + +This is a sample interface + +**Mermaid** + +graph TD +A[SampleInterface] -->|extends| B[ParentInterface] +B -->|extends| C[GrandParentInterface] +C -->|extends| D[GreatGrandParentInterface] + +**Author** John Doe + +**Date** 2020-01-01 + +**See** [SampleEnum](../sample-enums/SampleEnum) + +**See** [ReferencedEnum](ReferencedEnum) + +## Namespace +apexdocs + +## Example +SampleInterface sampleInterface = new SampleInterface(); +sampleInterface.sampleMethod(); + +**Extends** +[ParentInterface](ParentInterface) + +## Methods +### `sampleMethod()` + +`NAMESPACEACCESSIBLE` + +This is a sample method + +**Custom Tag** + +This is a custom tag + +**Another Custom Tag** + +This is another custom tag + +**Mermaid** + +graph TD +A[SampleInterface] -->|extends| B[ParentInterface] +B -->|extends| C[GrandParentInterface] +C -->|extends| D[GreatGrandParentInterface] + +#### Signature +```apex +public String sampleMethod() +``` + +#### Return Type +**String** + +Some return value + +#### Throws +[SampleException](SampleException): This is a sample exception + +AnotherSampleException: This is another sample exception + +#### Example +SampleInterface sampleInterface = new SampleInterface(); +sampleInterface.sampleMethod(); + +--- + +### `sampleMethodWithParams(param1, param2, theEnum)` + +`NAMESPACEACCESSIBLE` +`DEPRECATED` + +This is a sample method with parameters +Sometimes it won't be possible to find a NonExistent link. + +#### Signature +```apex +public SampleEnum sampleMethodWithParams(String param1, Integer param2, SampleEnum theEnum) +``` + +#### Parameters +| Name | Type | Description | +|------|------|-------------| +| param1 | String | This is the first parameter | +| param2 | Integer | This is the second parameter | +| theEnum | [SampleEnum](../sample-enums/SampleEnum) | This is an enum parameter | + +#### Return Type +**[SampleEnum](../sample-enums/SampleEnum)** + +Some return value + +--- + +### `sampleParentMethod()` + +*Inherited* + +#### Signature +```apex +public void sampleParentMethod() +``` + +#### Return Type +**void** \ No newline at end of file diff --git a/examples/vitepress/docs/miscellaneous/Url.md b/examples/vitepress/docs/miscellaneous/Url.md new file mode 100644 index 00000000..ff2038da --- /dev/null +++ b/examples/vitepress/docs/miscellaneous/Url.md @@ -0,0 +1,317 @@ +--- +title: Url +--- + +# Url Class + +Represents a uniform resource locator (URL) and provides access to parts of the URL. +Enables access to the base URL used to access your Salesforce org. + +**Usage** + +Use the methods of the `System.URL` class to create links to objects in your organization. Such objects can be files, images, +logos, or records that you want to include in external emails, in activities, or in Chatter posts. For example, you can create +a link to a file uploaded as an attachment to a Chatter post by concatenating the Salesforce base URL with the file ID: + +```apex +// Get a file uploaded through Chatter. +ContentDocument doc = [SELECT Id FROM ContentDocument + WHERE Title = 'myfile']; +// Create a link to the file. +String fullFileURL = URL.getOrgDomainURL().toExternalForm() + + '/' + doc.id; +System.debug(fullFileURL); +``` + + +The following example creates a link to a Salesforce record. The full URL is created by concatenating the Salesforce base +URL with the record ID. + +```apex +Account acct = [SELECT Id FROM Account WHERE Name = 'Acme' LIMIT 1]; +String fullRecordURL = URL.getOrgDomainURL().toExternalForm() + '/' + acct.Id; +``` + +**Version Behavior Changes** + +In API version 41.0 and later, Apex URL objects are represented by the java.net.URI type, not the java.net.URL type. + +The API version in which the URL object was instantiated determines the behavior of subsequent method calls to the +specific instance. Salesforce strongly encourages you to use API 41.0 and later versions for fully RFC-compliant URL +parsing that includes proper handling of edge cases of complex URL structures. API 41.0 and later versions also enforce +that inputs are valid, RFC-compliant URL or URI strings. + +* [URL Constructors](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_url.htm#apex_System_URL_constructors) +* [URL Methods](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_url.htm#apex_System_URL_methods) + +**See Also** +* [URL Class](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_url.htm) + +## Namespace +apexdocs + +## Example +In this example, the base URL and the full request URL of the current Salesforce server instance are retrieved. Next, a URL +pointing to a specific account object is created. Finally, components of the base and full URL are obtained. This example +prints out all the results to the debug log output. + +```apex +// Create a new account called Acme that we will create a link for later. +Account myAccount = new Account(Name='Acme'); +insert myAccount; + +// Get the base URL. +String sfdcBaseURL = URL.getOrgDomainURL().toExternalForm(); +System.debug('Base URL: ' + sfdcBaseURL ); + +// Get the URL for the current request. +String currentRequestURL = URL.getCurrentRequestUrl().toExternalForm(); +System.debug('Current request URL: ' + currentRequestURL); + +// Create the account URL from the base URL. +String accountURL = URL.getOrgDomainURL().toExternalForm() + + '/' + myAccount.Id; +System.debug('URL of a particular account: ' + accountURL); + +// Get some parts of the base URL. +System.debug('Host: ' + URL.getOrgDomainURL().getHost()); +System.debug('Protocol: ' + URL.getOrgDomainURL().getProtocol()); + +// Get the query string of the current request. +System.debug('Query: ' + URL.getCurrentRequestUrl().getQuery()); +``` + +## Constructors +### `Url(spec)` + +Creates a new instance of the URL class using the specified string representation of the URL. + +#### Signature +```apex +global Url(String spec) +``` + +#### Parameters +| Name | Type | Description | +|------|------|-------------| +| spec | String | The string to parse as a URL. | + +--- + +### `Url(context, spec)` + +Creates a new instance of the URL class by parsing the specified spec within the specified context. + +**Usage** + +The new URL is created from the given context URL and the spec argument as described in RFC2396 "Uniform Resource Identifiers : Generic * Syntax" : +```xml +://?# +``` + + +For more information about the arguments of this constructor, see the corresponding URL(java.net.URL, java.lang.String) constructor for Java. + +#### Signature +```apex +global Url(Url context, String spec) +``` + +#### Parameters +| Name | Type | Description | +|------|------|-------------| +| context | [Url](Url) | The context in which to parse the specification. | +| spec | String | The string to parse as a URL. | + +--- + +### `Url(protocol, host, file)` + +Creates a new instance of the URL class using the specified protocol, host, and file on the host. The default port for the specified protocol is used. + +#### Signature +```apex +global Url(String protocol, String host, String file) +``` + +#### Parameters +| Name | Type | Description | +|------|------|-------------| +| protocol | String | The protocol name for this URL. | +| host | String | The host name for this URL. | +| file | String | The file name for this URL. | + +--- + +### `Url(protocol, host, port, file)` + +Creates a new instance of the URL class using the specified protocol, host, port number, and file on the host. + +#### Signature +```apex +global Url(String protocol, String host, Integer port, String file) +``` + +#### Parameters +| Name | Type | Description | +|------|------|-------------| +| protocol | String | The protocol name for this URL. | +| host | String | The host name for this URL. | +| port | Integer | The port number for this URL. | +| file | String | The file name for this URL. | + +## Methods +### `getAuthority()` + +Returns the authority portion of the current URL. + +#### Signature +```apex +global String getAuthority() +``` + +#### Return Type +**String** + +The authority portion of the current URL. + +--- + +### `getCurrentRequestUrl()` + +Returns the URL of an entire request on a Salesforce instance. + +**Usage** + +An example of a URL for an entire request is https://yourInstance.salesforce.com/apex/myVfPage.apexp. + +#### Signature +```apex +global static Url getCurrentRequestUrl() +``` + +#### Return Type +**[Url](Url)** + +The URL of the entire request. + +--- + +### `getDefPort()` + +Returns the default port number of the protocol associated with the current URL. + +**Usage** + +Returns -1 if the URL scheme or the stream protocol handler for the URL doesn't define a default port number. + +#### Signature +```apex +global Integer getDefPort() +``` + +#### Return Type +**Integer** + +The default port number of the protocol associated with the current URL. + +--- + +### `getFile()` + +Returns the file name of the current URL. + +#### Signature +```apex +global String getFile() +``` + +#### Return Type +**String** + +The file name of the current URL. + +--- + +### `getFileFieldURL(entityId, fieldName)` + +Returns the download URL for a file attachment. + +#### Signature +```apex +global static String getFileFieldURL(String entityId, String fieldName) +``` + +#### Parameters +| Name | Type | Description | +|------|------|-------------| +| entityId | String | Specifies the ID of the entity that holds the file data. | +| fieldName | String | Specifies the API name of a file field component, such as `AttachmentBody` . | + +#### Return Type +**String** + +The download URL for the file attachment. + +#### Example +String fileURL = +URL.getFileFieldURL( +'087000000000123' , +'AttachmentBody'); + +--- + +### `getHost()` + +Returns the host name of the current URL. + +#### Signature +```apex +global String getHost() +``` + +#### Return Type +**String** + +The host name of the current URL. + +--- + +### `getOrgDomainUrl()` + +Returns the canonical URL for your org. For example, https://MyDomainName.my.salesforce.com. + +**Usage** + +Use getOrgDomainUrl() to interact with Salesforce REST and SOAP APIs in Apex code. Get endpoints for User Interface API calls, for creating and customizing picklist value sets and custom fields, and more. + + `getOrgDomainUrl()` can access the domain URL only for the org in which the Apex code is running. + +You don't need a RemoteSiteSetting for your org to interact with the Salesforce APIs using domain URLs retrieved with this method. + +**See Also** + +* [Lightning Aura Components Developer Guide: Making API Calls from Apex](https://developer.salesforce.com/docs/atlas.en-us.250.0.lightning.meta/lightning/apex_api_calls.htm) + +#### Signature +```apex +global static Url getOrgDomainUrl() +``` + +#### Return Type +**[Url](Url)** + +getOrgDomainUrl() always returns the login URL for your org, regardless of context. Use that URL when making API calls to your org. + +#### Example +This example uses the Salesforce REST API to get organization limit values. For information on limits, see Limits in the REST API Developer Guide. + +```apex +Http h = new Http(); +HttpRequest req = new HttpRequest(); +req.setEndpoint(Url.getOrgDomainUrl().toExternalForm() + + '/services/data/v44.0/limits'); +req.setMethod('GET'); +req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId()); +HttpResponse res = h.send(req); +``` \ No newline at end of file diff --git a/examples/vitepress/docs/sample-enums/SampleEnum.md b/examples/vitepress/docs/sample-enums/SampleEnum.md new file mode 100644 index 00000000..539f01e0 --- /dev/null +++ b/examples/vitepress/docs/sample-enums/SampleEnum.md @@ -0,0 +1,40 @@ +--- +title: SampleEnum +--- + +# SampleEnum Enum + +`NAMESPACEACCESSIBLE` + +This is a sample enum. This references [ReferencedEnum](../miscellaneous/ReferencedEnum) . + +This description has several lines + +**Some Custom** + +Test. I can also have a [ReferencedEnum](../miscellaneous/ReferencedEnum) here. +And it can be multiline. + +**Mermaid** + +graph TD +A[SampleEnum] -->|references| B[ReferencedEnum] +B -->|referenced by| A + +**Group** Sample Enums + +**Author** John Doe + +**Date** 2022-01-01 + +**See** [ReferencedEnum](../miscellaneous/ReferencedEnum) + +## Namespace +apexdocs + +## Values +| Value | Description | +|-------|-------------| +| VALUE1 | This is value 1 | +| VALUE2 | This is value 2 | +| VALUE3 | This is value 3 | \ No newline at end of file diff --git a/examples/vitepress/docs/samplegroup/SampleClass.md b/examples/vitepress/docs/samplegroup/SampleClass.md new file mode 100644 index 00000000..38de2d1f --- /dev/null +++ b/examples/vitepress/docs/samplegroup/SampleClass.md @@ -0,0 +1,174 @@ +--- +title: SampleClass +--- + +# SampleClass Class +`virtual` + +aliquip ex sunt officia ullamco anim deserunt magna aliquip nisi eiusmod in sit officia veniam ex +**deserunt** ea officia exercitation laboris enim in duis quis enim eiusmod eu amet cupidatat. + +**Group** SampleGroup + +## Namespace +apexdocs + +## Example +SampleClass sample = new SampleClass(); +sample.doSomething(); + +**Inheritance** + +[BaseClass](../miscellaneous/BaseClass) + +**Implements** + +[SampleInterface](../miscellaneous/SampleInterface), +[ParentInterface](../miscellaneous/ParentInterface) + +## Fields +### Group Name +#### `name` + +This is a sample field. + +##### Signature +```apex +private final name +``` + +##### Type +String + +### Other +#### `sampleEnumFromBase` + +*Inherited* + +##### Signature +```apex +public sampleEnumFromBase +``` + +##### Type +[SampleEnum](../sample-enums/SampleEnum) + +## Properties +### Group Name +#### `someProperty` + +##### Signature +```apex +public someProperty +``` + +##### Type +String + +## Constructors +### Other +#### `SampleClass()` + +This is a sample constructor. + +##### Signature +```apex +public SampleClass() +``` + +### Other Constructors +#### `SampleClass(name)` + +##### Signature +```apex +public SampleClass(String name) +``` + +##### Parameters +| Name | Type | Description | +|------|------|-------------| +| name | String | | + +## Methods +### Available Methods +#### `doSomething()` + +##### Signature +```apex +public void doSomething() +``` + +##### Return Type +**void** + +### Deprecated Methods +#### `sayHello()` + +`DEPRECATED` + +This is a sample method. + +##### Signature +```apex +public virtual String sayHello() +``` + +##### Return Type +**String** + +A string value. + +##### Example +SampleClass sample = new SampleClass(); +sample.doSomething(); + +## Classes +### SomeInnerClass Class + +#### Fields +##### `someInnerField` + +###### Signature +```apex +public someInnerField +``` + +###### Type +String + +#### Methods +##### `doSomething()` + +###### Signature +```apex +public void doSomething() +``` + +###### Return Type +**void** + +## Enums +### SomeEnum Enum + +This enum is used for foo and bar. + +#### Values +| Value | Description | +|-------|-------------| +| TEST_1 | This is a test. | +| TEST_2 | | +| TEST_3 | | + +## Interfaces +### SomeInterface Interface + +#### Methods +##### `doSomething()` + +###### Signature +```apex +public void doSomething() +``` + +###### Return Type +**void** \ No newline at end of file diff --git a/examples/vitepress/force-app/main/default/objects/Event__c/Event__c.object-meta.xml b/examples/vitepress/force-app/main/default/objects/Event__c/Event__c.object-meta.xml index 7197afcb..d30dde5c 100644 --- a/examples/vitepress/force-app/main/default/objects/Event__c/Event__c.object-meta.xml +++ b/examples/vitepress/force-app/main/default/objects/Event__c/Event__c.object-meta.xml @@ -159,6 +159,7 @@ Text Events + Represents an event that people can register for. ReadWrite Vowel diff --git a/examples/vitepress/force-app/main/default/objects/Event__c/listViews/All.listView-meta.xml b/examples/vitepress/force-app/main/default/objects/Event__c/listViews/All.listView-meta.xml deleted file mode 100644 index d5058512..00000000 --- a/examples/vitepress/force-app/main/default/objects/Event__c/listViews/All.listView-meta.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - All - Everything - - diff --git a/examples/vitepress/force-app/main/default/objects/Product__c/Product__c.object-meta.xml b/examples/vitepress/force-app/main/default/objects/Product__c/Product__c.object-meta.xml index ea2d7802..cdeb52a9 100644 --- a/examples/vitepress/force-app/main/default/objects/Product__c/Product__c.object-meta.xml +++ b/examples/vitepress/force-app/main/default/objects/Product__c/Product__c.object-meta.xml @@ -157,6 +157,7 @@ true Private + Product that is sold or available for sale. Text diff --git a/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/Sales_Order_Line__c.object-meta.xml b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/Sales_Order_Line__c.object-meta.xml index ce565bf8..36e9348d 100644 --- a/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/Sales_Order_Line__c.object-meta.xml +++ b/examples/vitepress/force-app/main/default/objects/Sales_Order_Line__c/Sales_Order_Line__c.object-meta.xml @@ -154,6 +154,7 @@ true ControlledByParent + Represents a line item on a sales order. SOL-{0000} diff --git a/examples/vitepress/force-app/main/default/objects/Sales_Order__c/Sales_Order__c.object-meta.xml b/examples/vitepress/force-app/main/default/objects/Sales_Order__c/Sales_Order__c.object-meta.xml index 08247ffc..2225e4f9 100644 --- a/examples/vitepress/force-app/main/default/objects/Sales_Order__c/Sales_Order__c.object-meta.xml +++ b/examples/vitepress/force-app/main/default/objects/Sales_Order__c/Sales_Order__c.object-meta.xml @@ -157,6 +157,7 @@ true Private + Custom object for tracking sales orders. SO-{0000} diff --git a/examples/vitepress/force-app/main/default/objects/Sales_Order__c/listViews/All.listView-meta.xml b/examples/vitepress/force-app/main/default/objects/Sales_Order__c/listViews/All.listView-meta.xml deleted file mode 100644 index d5058512..00000000 --- a/examples/vitepress/force-app/main/default/objects/Sales_Order__c/listViews/All.listView-meta.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - All - Everything - - diff --git a/examples/vitepress/force-app/main/default/objects/Speaker__c/Speaker__c.object-meta.xml b/examples/vitepress/force-app/main/default/objects/Speaker__c/Speaker__c.object-meta.xml index 5a77a80b..6bdf2199 100644 --- a/examples/vitepress/force-app/main/default/objects/Speaker__c/Speaker__c.object-meta.xml +++ b/examples/vitepress/force-app/main/default/objects/Speaker__c/Speaker__c.object-meta.xml @@ -154,6 +154,7 @@ true ControlledByParent + Represents a speaker at an event. SPEAK-{0000} diff --git a/src/cli/commands/markdown.ts b/src/cli/commands/markdown.ts index 46cfb46b..b8531a9f 100644 --- a/src/cli/commands/markdown.ts +++ b/src/cli/commands/markdown.ts @@ -28,6 +28,11 @@ export const markdownOptions: { [key: string]: Options } = { default: markdownDefaults.defaultGroupName, describe: 'Defines the @group name to be used when a file does not specify it.', }, + customObjectGroupName: { + type: 'string', + default: markdownDefaults.customObjectsGroupName, + describe: 'The name under which custom objects will be grouped in the Reference Guide', + }, namespace: { type: 'string', describe: 'The package namespace, if any. If provided, it will be added to the generated files.', diff --git a/src/core/markdown/__test__/test-helpers.ts b/src/core/markdown/__test__/test-helpers.ts index 403a690b..e35f7d79 100644 --- a/src/core/markdown/__test__/test-helpers.ts +++ b/src/core/markdown/__test__/test-helpers.ts @@ -16,6 +16,7 @@ export function generateDocs(apexBundles: UnparsedApexBundle[], config?: Partial targetDir: 'target', scope: ['global', 'public'], defaultGroupName: 'Miscellaneous', + customObjectsGroupName: 'Custom Objects', sortAlphabetically: false, referenceGuideTemplate: referenceGuideTemplate, linkingStrategy: 'relative', diff --git a/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts b/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts index 34315f63..b69fdd7b 100644 --- a/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts +++ b/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts @@ -13,6 +13,7 @@ const defaultMarkdownGeneratorConfig: MarkdownGeneratorConfig = { scope: ['global', 'public'], namespace: '', defaultGroupName: 'Miscellaneous', + customObjectsGroupName: 'Custom Objects', referenceGuideTemplate: '', sortAlphabetically: false, linkingStrategy: 'relative', diff --git a/src/core/markdown/adapters/renderable-bundle.ts b/src/core/markdown/adapters/renderable-bundle.ts index 7208c3c4..9ac2082e 100644 --- a/src/core/markdown/adapters/renderable-bundle.ts +++ b/src/core/markdown/adapters/renderable-bundle.ts @@ -69,8 +69,7 @@ function getRenderableDescription( ): RenderableContent[] | null { switch (type.type_name) { case 'sobject': - // TODO - return null; + return type.description ? [type.description] : null; default: return adaptDescribable(type.docComment?.descriptionLines, findLinkFromHome).description ?? null; } diff --git a/src/core/markdown/adapters/renderable-to-page-data.ts b/src/core/markdown/adapters/renderable-to-page-data.ts index 96bb06e2..5c906791 100644 --- a/src/core/markdown/adapters/renderable-to-page-data.ts +++ b/src/core/markdown/adapters/renderable-to-page-data.ts @@ -6,6 +6,7 @@ import { enumMarkdownTemplate } from '../templates/enum-template'; import { interfaceMarkdownTemplate } from '../templates/interface-template'; import { classMarkdownTemplate } from '../templates/class-template'; import { markdownDefaults } from '../../../defaults'; +import { sObjectTemplate } from '../templates/sobject-template'; export const convertToDocumentationBundle = ( referenceGuideTitle: string, @@ -78,7 +79,7 @@ function resolveApexTypeTemplate(renderable: Renderable): CompilationRequest { case 'class': return classMarkdownTemplate; case 'sobject': - throw new Error('SObject type not supported'); + return sObjectTemplate; } } diff --git a/src/core/markdown/adapters/type-to-renderable.ts b/src/core/markdown/adapters/type-to-renderable.ts index 5c9fa237..c6697ddb 100644 --- a/src/core/markdown/adapters/type-to-renderable.ts +++ b/src/core/markdown/adapters/type-to-renderable.ts @@ -18,6 +18,7 @@ import { adaptFieldOrProperty } from './fields-and-properties'; import { MarkdownGeneratorConfig } from '../generate-docs'; import { SourceFileMetadata } from '../../shared/types'; import { ObjectMetadata } from '../../reflection/sobject/reflect-sobject-source'; +import { getTypeGroup } from '../../shared/utils'; type GetReturnRenderable = T extends InterfaceMirror ? RenderableInterface @@ -42,13 +43,14 @@ export function typeToRenderable( case 'class': return classTypeToClassSource(type as ClassMirrorWithInheritanceChain, linkGenerator); case 'sobject': - return objectMetadataToRenderable(type as ObjectMetadata); + return objectMetadataToRenderable(type as ObjectMetadata, config); } } return { ...(getRenderable() as GetReturnRenderable), filePath: parsedFile.source.filePath, + // TODO: How to handle namespace for sobjects? Remember that only custom objects should get them (as opposed to standard) namespace: config.namespace, }; } @@ -98,14 +100,26 @@ function enumTypeToEnumSource( }; } -function objectMetadataToRenderable(objectMetadata: ObjectMetadata): RenderableSObject { +function objectMetadataToRenderable( + objectMetadata: ObjectMetadata, + config: MarkdownGeneratorConfig, +): RenderableSObject { + function getApiName() { + if (config.namespace) { + return `${config.namespace}__${objectMetadata.name}`; + } + return objectMetadata.name; + } + return { type: 'sobject', headingLevel: 1, - heading: objectMetadata.name, - name: objectMetadata.label, + apiName: getApiName(), + heading: objectMetadata.label, + name: objectMetadata.name, doc: { description: objectMetadata.description ? [objectMetadata.description] : [], + group: getTypeGroup(objectMetadata, config), }, }; } diff --git a/src/core/markdown/templates/sobject-template.ts b/src/core/markdown/templates/sobject-template.ts new file mode 100644 index 00000000..f77bf752 --- /dev/null +++ b/src/core/markdown/templates/sobject-template.ts @@ -0,0 +1,9 @@ +export const sObjectTemplate = ` +{{ heading headingLevel heading }} + +{{{renderContent doc.description}}} + +## API Name +\`{{apiName}}\` + +`.trim(); diff --git a/src/core/renderables/types.d.ts b/src/core/renderables/types.d.ts index 5dbdf7a8..e8616362 100644 --- a/src/core/renderables/types.d.ts +++ b/src/core/renderables/types.d.ts @@ -178,6 +178,7 @@ export type RenderableEnum = RenderableType & { }; export type RenderableSObject = Omit & { + apiName: string; type: 'sobject'; }; diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index c3d87c82..4aa4c2f2 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -21,7 +21,8 @@ export type UserDefinedMarkdownConfig = { targetDir: string; scope: string[]; namespace?: string; - defaultGroupName: string; + defaultGroupName: string; // TODO: Deprecate and rename + customObjectsGroupName: string; sortAlphabetically: boolean; includeMetadata: boolean; linkingStrategy: LinkingStrategy; diff --git a/src/core/shared/utils.ts b/src/core/shared/utils.ts index 88acc614..7a3d0226 100644 --- a/src/core/shared/utils.ts +++ b/src/core/shared/utils.ts @@ -34,7 +34,7 @@ export function getTypeGroup(type: Type | ObjectMetadata, config: MarkdownGenera switch (type.type_name) { case 'sobject': - return 'SObjects'; // TODO: Make configurable? + return config.customObjectsGroupName; default: return getGroup(type, config); } diff --git a/src/defaults.ts b/src/defaults.ts index 0182cb1f..8510cf7a 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -6,10 +6,11 @@ export const markdownDefaults = { ...commonDefaults, scope: ['global'], defaultGroupName: 'Miscellaneous', + customObjectsGroupName: 'Custom Objects', includeMetadata: false, sortAlphabetically: false, linkingStrategy: 'relative' as const, - referenceGuideTitle: 'Apex Reference Guide', + referenceGuideTitle: 'Reference Guide', excludeTags: [], exclude: [], }; From f86ced66bf4a17650976cdd9f46fd8f326c8ad3e Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sat, 19 Oct 2024 09:21:27 -0400 Subject: [PATCH 19/32] SObject template implementation --- .../markdown/__test__/generating-docs.spec.ts | 127 +++++++++++++++++- .../generating-reference-guide.spec.ts | 2 + src/core/markdown/__test__/test-helpers.ts | 15 ++- src/core/markdown/generate-docs.ts | 13 +- 4 files changed, 146 insertions(+), 11 deletions(-) diff --git a/src/core/markdown/__test__/generating-docs.spec.ts b/src/core/markdown/__test__/generating-docs.spec.ts index f8f081cb..6ada1b56 100644 --- a/src/core/markdown/__test__/generating-docs.spec.ts +++ b/src/core/markdown/__test__/generating-docs.spec.ts @@ -1,8 +1,22 @@ import { DocPageData, PostHookDocumentationBundle } from '../../shared/types'; import { extendExpect } from './expect-extensions'; -import { unparsedApexBundleFromRawString, generateDocs } from './test-helpers'; +import { unparsedApexBundleFromRawString, generateDocs, unparsedObjectBundleFromRawString } from './test-helpers'; import { assertEither } from '../../test-helpers/assert-either'; +function customObjectGenerator( + config: { deploymentStatus: string; visibility: string } = { deploymentStatus: 'Deployed', visibility: 'Public' }, +) { + return ` + + + ${config.deploymentStatus} + test object for testing + + MyFirstObjects + ${config.visibility} + `; +} + function aSingleDoc(result: PostHookDocumentationBundle): DocPageData { expect(result.docs).toHaveLength(1); return result.docs[0]; @@ -14,7 +28,7 @@ describe('When generating documentation', () => { }); describe('the resulting files', () => { - it('are named after the type', async () => { + it('Apex code is named after the type', async () => { const properties: [string, string][] = [ ['public class MyClass {}', 'MyClass.md'], ['public interface MyInterface {}', 'MyInterface.md'], @@ -27,7 +41,37 @@ describe('When generating documentation', () => { } }); - it('are placed in the miscellaneous folder if no group is provided', async () => { + it('SObject code is named after the path', async () => { + const properties: [ + { + rawContent: string; + filePath: string; + }, + string, + ][] = [ + [ + { + rawContent: customObjectGenerator(), + filePath: 'src/object/MyFirstObject__c.object-meta.xml', + }, + 'MyFirstObject__c.md', + ], + [ + { + rawContent: customObjectGenerator(), + filePath: 'src/object/MySecondObject__c.object-meta.xml', + }, + 'MySecondObject__c.md', + ], + ]; + + for (const [input, expected] of properties) { + const result = await generateDocs([unparsedObjectBundleFromRawString(input)])(); + assertEither(result, (data) => expect(aSingleDoc(data).outputDocPath).toContain(expected)); + } + }); + + it('Apex code is placed in the miscellaneous folder if no group is provided', async () => { const properties: [string, string][] = [ ['public class MyClass {}', 'miscellaneous'], ['public interface MyInterface {}', 'miscellaneous'], @@ -40,7 +84,37 @@ describe('When generating documentation', () => { } }); - it('are placed in the slugified group folder if a group is provided', async () => { + it('SObject code is placed in the custom objects folder', async () => { + const properties: [ + { + rawContent: string; + filePath: string; + }, + string, + ][] = [ + [ + { + rawContent: customObjectGenerator(), + filePath: 'src/object/MyFirstObject__c.object-meta.xml', + }, + 'custom-objects', + ], + [ + { + rawContent: customObjectGenerator(), + filePath: 'src/object/MySecondObject__c.object-meta.xml', + }, + 'custom-objects', + ], + ]; + + for (const [input, expected] of properties) { + const result = await generateDocs([unparsedObjectBundleFromRawString(input)])(); + assertEither(result, (data) => expect(aSingleDoc(data).outputDocPath).toContain(expected)); + } + }); + + it('Apex code is placed in the slugified group folder if a group is provided', async () => { const properties: [string, string][] = [ [ `/** @@ -73,7 +147,7 @@ describe('When generating documentation', () => { }); describe('the generated bundles', () => { - it('return the type', async () => { + it('Apex code returns the type', async () => { const properties: [string, string][] = [ ['public class MyClass {}', 'class'], ['public interface MyInterface {}', 'interface'], @@ -86,7 +160,30 @@ describe('When generating documentation', () => { } }); - it('do not return files out of scope', async () => { + it('SObjects return the type', async () => { + const properties: [ + { + rawContent: string; + filePath: string; + }, + string, + ][] = [ + [ + { + rawContent: customObjectGenerator(), + filePath: 'src/object/MyFirstObject__c.object-meta.xml', + }, + 'sobject', + ], + ]; + + for (const [input, expected] of properties) { + const result = await generateDocs([unparsedObjectBundleFromRawString(input)])(); + assertEither(result, (data) => expect(aSingleDoc(data).source.type).toBe(expected)); + } + }); + + it('do not return Apex files out of scope', async () => { const input1 = 'global class MyClass {}'; const input2 = 'public class AnotherClass {}'; @@ -99,6 +196,20 @@ describe('When generating documentation', () => { expect(result).documentationBundleHasLength(1); }); + it('does not return non-deployed custom objects', async () => { + const input = customObjectGenerator({ deploymentStatus: 'InDevelopment', visibility: 'Public' }); + + const result = await generateDocs([unparsedObjectBundleFromRawString({ rawContent: input, filePath: 'test' })])(); + expect(result).documentationBundleHasLength(0); + }); + + it('does not return non-public custom objects', async () => { + const input = customObjectGenerator({ deploymentStatus: 'Deployed', visibility: 'Protected' }); + + const result = await generateDocs([unparsedObjectBundleFromRawString({ rawContent: input, filePath: 'test' })])(); + expect(result).documentationBundleHasLength(0); + }); + it('do not return files that have an @ignore in the docs', async () => { const input = ` /** @@ -113,6 +224,8 @@ describe('When generating documentation', () => { describe('the documentation content', () => { it('includes a heading with the type name', async () => { + // TODO: A version of this for objects + const properties: [string, string][] = [ ['public class MyClass {}', 'MyClass Class'], ['public enum MyEnum {}', 'MyEnum Enum'], @@ -127,6 +240,8 @@ describe('When generating documentation', () => { } }); + // TODO: Everything below here is just Apex specific, so it should go to its own file. + it('displays type level annotations', async () => { const input = ` @NamespaceAccessible diff --git a/src/core/markdown/__test__/generating-reference-guide.spec.ts b/src/core/markdown/__test__/generating-reference-guide.spec.ts index e37d6ef5..7c8305c0 100644 --- a/src/core/markdown/__test__/generating-reference-guide.spec.ts +++ b/src/core/markdown/__test__/generating-reference-guide.spec.ts @@ -5,6 +5,8 @@ import { unparsedApexBundleFromRawString, generateDocs } from './test-helpers'; import { ReferenceGuidePageData } from '../../shared/types'; import { assertEither } from '../../test-helpers/assert-either'; +// TODO: Test that it contains the SObject content + describe('When generating the Reference Guide', () => { beforeAll(() => { extendExpect(); diff --git a/src/core/markdown/__test__/test-helpers.ts b/src/core/markdown/__test__/test-helpers.ts index e35f7d79..2aab60a8 100644 --- a/src/core/markdown/__test__/test-helpers.ts +++ b/src/core/markdown/__test__/test-helpers.ts @@ -1,4 +1,4 @@ -import { UnparsedApexBundle } from '../../shared/types'; +import { UnparsedApexBundle, UnparsedSObjectBundle, UnparsedSourceBundle } from '../../shared/types'; import { generateDocs as gen, MarkdownGeneratorConfig } from '../generate-docs'; import { referenceGuideTemplate } from '../templates/reference-guide'; @@ -11,7 +11,18 @@ export function unparsedApexBundleFromRawString(raw: string, rawMetadata?: strin }; } -export function generateDocs(apexBundles: UnparsedApexBundle[], config?: Partial) { +export function unparsedObjectBundleFromRawString(meta: { + rawContent: string; + filePath: string; +}): UnparsedSObjectBundle { + return { + type: 'sobject', + filePath: meta.filePath, + content: meta.rawContent, + }; +} + +export function generateDocs(apexBundles: UnparsedSourceBundle[], config?: Partial) { return gen(apexBundles, { targetDir: 'target', scope: ['global', 'public'], diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index 77ed7d35..9df1a5d1 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -33,7 +33,7 @@ import { isSkip } from '../shared/utils'; import { parsedFilesToReferenceGuide } from './adapters/reference-guide'; import { removeExcludedTags } from '../reflection/apex/remove-excluded-tags'; import { HookError } from '../errors/errors'; -import { reflectSObjectSources } from '../reflection/sobject/reflect-sobject-source'; +import { ObjectMetadata, reflectSObjectSources } from '../reflection/sobject/reflect-sobject-source'; export type MarkdownGeneratorConfig = Omit< UserDefinedMarkdownConfig, @@ -94,8 +94,15 @@ function generateForApex(apexBundles: UnparsedApexBundle[], config: MarkdownGene } function generateForObject(objectBundles: UnparsedSObjectBundle[]) { - // TODO: Filter out non public - return pipe(objectBundles, reflectSObjectSources); + function filterNonPublished(parsedFiles: ParsedFile[]): ParsedFile[] { + return parsedFiles.filter((parsedFile) => parsedFile.type.deploymentStatus === 'Deployed'); + } + + function filterNonPublic(parsedFiles: ParsedFile[]): ParsedFile[] { + return parsedFiles.filter((parsedFile) => parsedFile.type.visibility === 'Public'); + } + + return pipe(objectBundles, reflectSObjectSources, TE.map(filterNonPublished), TE.map(filterNonPublic)); } function transformReferenceHook(config: MarkdownGeneratorConfig) { From 642df800b03d8ba534d91997101ad52d586b6476 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sat, 19 Oct 2024 09:31:57 -0400 Subject: [PATCH 20/32] SObject template implementation --- .../__test__/generating-any-apex-doc.spec.ts | 289 ++++++++++++++++++ .../markdown/__test__/generating-docs.spec.ts | 266 +--------------- 2 files changed, 295 insertions(+), 260 deletions(-) create mode 100644 src/core/markdown/__test__/generating-any-apex-doc.spec.ts diff --git a/src/core/markdown/__test__/generating-any-apex-doc.spec.ts b/src/core/markdown/__test__/generating-any-apex-doc.spec.ts new file mode 100644 index 00000000..61b2a312 --- /dev/null +++ b/src/core/markdown/__test__/generating-any-apex-doc.spec.ts @@ -0,0 +1,289 @@ +import { DocPageData, PostHookDocumentationBundle } from '../../shared/types'; +import { extendExpect } from './expect-extensions'; +import { unparsedApexBundleFromRawString, generateDocs, unparsedObjectBundleFromRawString } from './test-helpers'; +import { assertEither } from '../../test-helpers/assert-either'; + +function customObjectGenerator( + config: { deploymentStatus: string; visibility: string } = { deploymentStatus: 'Deployed', visibility: 'Public' }, +) { + return ` + + + ${config.deploymentStatus} + test object for testing + + MyFirstObjects + ${config.visibility} + `; +} + +function aSingleDoc(result: PostHookDocumentationBundle): DocPageData { + expect(result.docs).toHaveLength(1); + return result.docs[0]; +} + +describe('When generating documentation', () => { + beforeAll(() => { + extendExpect(); + }); + + describe('the documentation content', () => { + it('displays type level annotations', async () => { + const input = ` + @NamespaceAccessible + public class MyClass { + @Deprecated + public void myMethod() {} + } + `; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); + + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('NAMESPACEACCESSIBLE')); + assertEither(result, (data) => expect(data).firstDocContains('DEPRECATED')); + }); + + it('displays metadata as annotations', async () => { + const input = 'public class MyClass {}'; + const metadata = ` + + + 59.0 + Active + + `; + + const result = await generateDocs([unparsedApexBundleFromRawString(input, metadata)])(); + + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('APIVERSION')); + assertEither(result, (data) => expect(data).firstDocContains('STATUS')); + }); + + it('displays the description when no @description tag is used', async () => { + const input = ` + /** + * This is a description + */ + public class MyClass {} + `; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); + + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('This is a description')); + }); + + it('displays the description when a @description tag is used', async () => { + const input = ` + /** + * @description This is a description + */ + public class MyClass {}`; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('This is a description')); + }); + + it('display custom documentation tags', async () => { + const input = ` + /** + * @custom-tag My Value + */ + public class MyClass {} + `; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('Custom Tag')); + assertEither(result, (data) => expect(data).firstDocContains('My Value')); + }); + + it('displays the group', async () => { + const input = ` + /** + * @group MyGroup + */ + public class MyClass {}`; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('Group')); + assertEither(result, (data) => expect(data).firstDocContains('MyGroup')); + }); + + it('displays the author', async () => { + const input = ` + /** + * @author John Doe + */ + public class MyClass {}`; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('Author')); + assertEither(result, (data) => expect(data).firstDocContains('John Doe')); + }); + + it('displays the date', async () => { + const input = ` + /** + * @date 2021-01-01 + */ + public class MyClass {}`; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('Date')); + assertEither(result, (data) => expect(data).firstDocContains('2021-01-01')); + }); + + it('displays descriptions with links', async () => { + const input1 = ` + /** + * @description This is a description with a {@link ClassRef} reference + */ + public enum MyClass {} + `; + + const input2 = 'public class ClassRef {}'; + + const result = await generateDocs([ + unparsedApexBundleFromRawString(input1), + unparsedApexBundleFromRawString(input2), + ])(); + expect(result).documentationBundleHasLength(2); + assertEither(result, (data) => + expect(data).firstDocContains('This is a description with a [ClassRef](ClassRef.md) reference'), + ); + }); + + it('displays descriptions with emails', async () => { + const input = ` + /** + * @description This is a description with an {@email test@testerson.com} email + */ + public class MyClass {} + `; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => + expect(data).firstDocContains( + 'This is a description with an [test@testerson.com](mailto:test@testerson.com) email', + ), + ); + }); + + it('displays @sees with accurately resolved links', async () => { + const input1 = ` + /** + * @see ClassRef + */ + public class MyClass {} + `; + + const input2 = 'public class ClassRef {}'; + + const result = await generateDocs([ + unparsedApexBundleFromRawString(input1), + unparsedApexBundleFromRawString(input2), + ])(); + expect(result).documentationBundleHasLength(2); + assertEither(result, (data) => expect(data).firstDocContains('See')); + assertEither(result, (data) => expect(data).firstDocContains('[ClassRef](ClassRef.md)')); + }); + + it('displays @sees without links when the reference is not found', async () => { + const input = ` + /** + * @see ClassRef + */ + public class MyClass {} + `; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); + + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('See')); + assertEither(result, (data) => expect(data).firstDocContains('ClassRef')); + }); + + it('displays the namespace if present in the config', async () => { + const input = 'public class MyClass {}'; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { namespace: 'MyNamespace' })(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('## Namespace')); + assertEither(result, (data) => expect(data).firstDocContains('MyNamespace')); + }); + + it('does not display the namespace if not present in the config', async () => { + const input = 'public class MyClass {}'; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContainsNot('## Namespace')); + }); + + it('displays a mermaid diagram', async () => { + const input = ` + /** + * @mermaid + * \`\`\`mermaid + * graph TD + * A[Square Rect] -- Link text --> B((Circle)) + * A --> C(Round Rect) + * B --> D{Rhombus} + * C --> D + * \`\`\` + */ + public class MyClass {} + `; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('```mermaid')); + assertEither(result, (data) => expect(data).firstDocContains('graph TD')); + }); + + it('displays an example code block', async () => { + const input = ` + /** + * @example + * \`\`\`apex + * public class MyClass { + * public void myMethod() { + * System.debug('Hello, World!'); + * } + * } + * \`\`\` + */ + public class MyClass {}`; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); + + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('```apex')); + assertEither(result, (data) => expect(data).firstDocContains('public class MyClass')); + }); + + it('does not display tags marked as excluded', async () => { + const input = ` + /** + * @see ClassRef + */ + public class MyClass {} + `; + + const result = await generateDocs([unparsedApexBundleFromRawString(input)], { + excludeTags: ['see'], + })(); + + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContainsNot('See')); + }); + }); +}); diff --git a/src/core/markdown/__test__/generating-docs.spec.ts b/src/core/markdown/__test__/generating-docs.spec.ts index 6ada1b56..4025a530 100644 --- a/src/core/markdown/__test__/generating-docs.spec.ts +++ b/src/core/markdown/__test__/generating-docs.spec.ts @@ -11,7 +11,7 @@ function customObjectGenerator( ${config.deploymentStatus} test object for testing - + MyFirstObjects ${config.visibility} `; @@ -223,9 +223,7 @@ describe('When generating documentation', () => { }); describe('the documentation content', () => { - it('includes a heading with the type name', async () => { - // TODO: A version of this for objects - + it('includes a heading with the type name for Apex code', async () => { const properties: [string, string][] = [ ['public class MyClass {}', 'MyClass Class'], ['public enum MyEnum {}', 'MyEnum Enum'], @@ -240,264 +238,12 @@ describe('When generating documentation', () => { } }); - // TODO: Everything below here is just Apex specific, so it should go to its own file. - - it('displays type level annotations', async () => { - const input = ` - @NamespaceAccessible - public class MyClass { - @Deprecated - public void myMethod() {} - } - `; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); - - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContains('NAMESPACEACCESSIBLE')); - assertEither(result, (data) => expect(data).firstDocContains('DEPRECATED')); - }); - - it('displays metadata as annotations', async () => { - const input = 'public class MyClass {}'; - const metadata = ` - - - 59.0 - Active - - `; - - const result = await generateDocs([unparsedApexBundleFromRawString(input, metadata)])(); - - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContains('APIVERSION')); - assertEither(result, (data) => expect(data).firstDocContains('STATUS')); - }); - - it('displays the description when no @description tag is used', async () => { - const input = ` - /** - * This is a description - */ - public class MyClass {} - `; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); - - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContains('This is a description')); - }); - - it('displays the description when a @description tag is used', async () => { - const input = ` - /** - * @description This is a description - */ - public class MyClass {}`; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContains('This is a description')); - }); - - it('display custom documentation tags', async () => { - const input = ` - /** - * @custom-tag My Value - */ - public class MyClass {} - `; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContains('Custom Tag')); - assertEither(result, (data) => expect(data).firstDocContains('My Value')); - }); - - it('displays the group', async () => { - const input = ` - /** - * @group MyGroup - */ - public class MyClass {}`; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContains('Group')); - assertEither(result, (data) => expect(data).firstDocContains('MyGroup')); - }); - - it('displays the author', async () => { - const input = ` - /** - * @author John Doe - */ - public class MyClass {}`; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContains('Author')); - assertEither(result, (data) => expect(data).firstDocContains('John Doe')); - }); - - it('displays the date', async () => { - const input = ` - /** - * @date 2021-01-01 - */ - public class MyClass {}`; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContains('Date')); - assertEither(result, (data) => expect(data).firstDocContains('2021-01-01')); - }); - - it('displays descriptions with links', async () => { - const input1 = ` - /** - * @description This is a description with a {@link ClassRef} reference - */ - public enum MyClass {} - `; - - const input2 = 'public class ClassRef {}'; - - const result = await generateDocs([ - unparsedApexBundleFromRawString(input1), - unparsedApexBundleFromRawString(input2), - ])(); - expect(result).documentationBundleHasLength(2); - assertEither(result, (data) => - expect(data).firstDocContains('This is a description with a [ClassRef](ClassRef.md) reference'), - ); - }); - - it('displays descriptions with emails', async () => { - const input = ` - /** - * @description This is a description with an {@email test@testerson.com} email - */ - public class MyClass {} - `; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => - expect(data).firstDocContains( - 'This is a description with an [test@testerson.com](mailto:test@testerson.com) email', - ), - ); - }); - - it('displays @sees with accurately resolved links', async () => { - const input1 = ` - /** - * @see ClassRef - */ - public class MyClass {} - `; - - const input2 = 'public class ClassRef {}'; - - const result = await generateDocs([ - unparsedApexBundleFromRawString(input1), - unparsedApexBundleFromRawString(input2), - ])(); - expect(result).documentationBundleHasLength(2); - assertEither(result, (data) => expect(data).firstDocContains('See')); - assertEither(result, (data) => expect(data).firstDocContains('[ClassRef](ClassRef.md)')); - }); - - it('displays @sees without links when the reference is not found', async () => { - const input = ` - /** - * @see ClassRef - */ - public class MyClass {} - `; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); - - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContains('See')); - assertEither(result, (data) => expect(data).firstDocContains('ClassRef')); - }); - - it('displays the namespace if present in the config', async () => { - const input = 'public class MyClass {}'; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)], { namespace: 'MyNamespace' })(); - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContains('## Namespace')); - assertEither(result, (data) => expect(data).firstDocContains('MyNamespace')); - }); - - it('does not display the namespace if not present in the config', async () => { - const input = 'public class MyClass {}'; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContainsNot('## Namespace')); - }); - - it('displays a mermaid diagram', async () => { - const input = ` - /** - * @mermaid - * \`\`\`mermaid - * graph TD - * A[Square Rect] -- Link text --> B((Circle)) - * A --> C(Round Rect) - * B --> D{Rhombus} - * C --> D - * \`\`\` - */ - public class MyClass {} - `; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContains('```mermaid')); - assertEither(result, (data) => expect(data).firstDocContains('graph TD')); - }); - - it('displays an example code block', async () => { - const input = ` - /** - * @example - * \`\`\`apex - * public class MyClass { - * public void myMethod() { - * System.debug('Hello, World!'); - * } - * } - * \`\`\` - */ - public class MyClass {}`; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)])(); - - expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContains('```apex')); - assertEither(result, (data) => expect(data).firstDocContains('public class MyClass')); - }); - - it('does not display tags marked as excluded', async () => { - const input = ` - /** - * @see ClassRef - */ - public class MyClass {} - `; - - const result = await generateDocs([unparsedApexBundleFromRawString(input)], { - excludeTags: ['see'], - })(); + it('includes a heading with the Custom Object label', async () => { + const input = customObjectGenerator(); + const result = await generateDocs([unparsedObjectBundleFromRawString({ rawContent: input, filePath: 'test' })])(); expect(result).documentationBundleHasLength(1); - assertEither(result, (data) => expect(data).firstDocContainsNot('See')); + assertEither(result, (data) => expect(data).firstDocContains('MyTestObject')); }); }); }); From 29561b40a85cb7c57685621d2dc1cf1b978c5f69 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sat, 19 Oct 2024 10:25:46 -0400 Subject: [PATCH 21/32] SObject template implementation --- .../markdown/__test__/generating-docs.spec.ts | 21 ++--- .../generating-reference-guide.spec.ts | 78 +++++++++++++++++-- src/core/markdown/__test__/test-helpers.ts | 15 ++++ .../__test__/reflect-sobject-source.spec.ts | 2 - 4 files changed, 91 insertions(+), 25 deletions(-) diff --git a/src/core/markdown/__test__/generating-docs.spec.ts b/src/core/markdown/__test__/generating-docs.spec.ts index 4025a530..0e11334d 100644 --- a/src/core/markdown/__test__/generating-docs.spec.ts +++ b/src/core/markdown/__test__/generating-docs.spec.ts @@ -1,22 +1,13 @@ import { DocPageData, PostHookDocumentationBundle } from '../../shared/types'; import { extendExpect } from './expect-extensions'; -import { unparsedApexBundleFromRawString, generateDocs, unparsedObjectBundleFromRawString } from './test-helpers'; +import { + unparsedApexBundleFromRawString, + generateDocs, + unparsedObjectBundleFromRawString, + customObjectGenerator, +} from './test-helpers'; import { assertEither } from '../../test-helpers/assert-either'; -function customObjectGenerator( - config: { deploymentStatus: string; visibility: string } = { deploymentStatus: 'Deployed', visibility: 'Public' }, -) { - return ` - - - ${config.deploymentStatus} - test object for testing - - MyFirstObjects - ${config.visibility} - `; -} - function aSingleDoc(result: PostHookDocumentationBundle): DocPageData { expect(result.docs).toHaveLength(1); return result.docs[0]; diff --git a/src/core/markdown/__test__/generating-reference-guide.spec.ts b/src/core/markdown/__test__/generating-reference-guide.spec.ts index 7c8305c0..4d498ba0 100644 --- a/src/core/markdown/__test__/generating-reference-guide.spec.ts +++ b/src/core/markdown/__test__/generating-reference-guide.spec.ts @@ -1,12 +1,15 @@ import { extendExpect } from './expect-extensions'; import { pipe } from 'fp-ts/function'; import * as E from 'fp-ts/Either'; -import { unparsedApexBundleFromRawString, generateDocs } from './test-helpers'; +import { + unparsedApexBundleFromRawString, + generateDocs, + customObjectGenerator, + unparsedObjectBundleFromRawString, +} from './test-helpers'; import { ReferenceGuidePageData } from '../../shared/types'; import { assertEither } from '../../test-helpers/assert-either'; -// TODO: Test that it contains the SObject content - describe('When generating the Reference Guide', () => { beforeAll(() => { extendExpect(); @@ -40,11 +43,17 @@ describe('When generating the Reference Guide', () => { public class MyClass {} `; + const input3 = { + rawContent: customObjectGenerator(), + filePath: 'src/object/TestObject__c.object-meta.xml', + }; + const result = await generateDocs([ unparsedApexBundleFromRawString(input1), unparsedApexBundleFromRawString(input2), + unparsedObjectBundleFromRawString(input3), ])(); - expect(result).documentationBundleHasLength(2); + expect(result).documentationBundleHasLength(3); assertEither(result, (data) => expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('[MyEnum](miscellaneous/MyEnum.md)'), @@ -52,9 +61,14 @@ describe('When generating the Reference Guide', () => { assertEither(result, (data) => expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('[MyClass](miscellaneous/MyClass.md)'), ); + assertEither(result, (data) => + expect((data.referenceGuide as ReferenceGuidePageData).content).toContain( + '[TestObject__c](custom-objects/TestObject__c.md)', + ), + ); }); - it('groups things under Miscellaneous if no group is provided', async () => { + it('groups Apex code under Miscellaneous if no group is provided', async () => { const input = ` public enum MyEnum { VALUE1, @@ -69,7 +83,7 @@ describe('When generating the Reference Guide', () => { ); }); - it('group things under the provided group', async () => { + it('group Apex code under the provided group', async () => { const input = ` /** * @group MyGroup @@ -87,6 +101,34 @@ describe('When generating the Reference Guide', () => { ); }); + it('group SObjects under the Custom Objects group by default', async () => { + const input = { + rawContent: customObjectGenerator(), + filePath: 'src/object/TestObject__c.object-meta.xml', + }; + + const result = await generateDocs([unparsedObjectBundleFromRawString(input)])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => + expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('## Custom Objects'), + ); + }); + + it('groups SObjects under the provided group', async () => { + const input = { + rawContent: customObjectGenerator(), + filePath: 'src/object/TestObject__c.object-meta.xml', + }; + + const result = await generateDocs([unparsedObjectBundleFromRawString(input)], { + customObjectsGroupName: 'MyCustomObjects', + })(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => + expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('## MyCustomObjects'), + ); + }); + it('displays groups in alphabetical order', async () => { const input1 = ` /** @@ -141,17 +183,27 @@ describe('When generating the Reference Guide', () => { public class MyClass {} `; + const input3 = { + rawContent: customObjectGenerator(), + filePath: 'src/object/ATestObject__c.object-meta.xml', + }; + const result = await generateDocs([ unparsedApexBundleFromRawString(input1), unparsedApexBundleFromRawString(input2), + unparsedObjectBundleFromRawString(input3), ])(); - expect(result).documentationBundleHasLength(2); + + expect(result).documentationBundleHasLength(3); assertEither(result, (data) => expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('## Group1'), ); assertEither(result, (data) => expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('MyClass'), ); + assertEither(result, (data) => + expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('ATestObject__c'), + ); assertEither(result, (data) => expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('MyEnum')); }); @@ -173,14 +225,24 @@ describe('When generating the Reference Guide', () => { public class MyClass {} `; + const input3 = { + rawContent: customObjectGenerator(), + filePath: 'src/object/ATestObject__c.object-meta.xml', + }; + const result = await generateDocs([ unparsedApexBundleFromRawString(input1), unparsedApexBundleFromRawString(input2), + unparsedObjectBundleFromRawString(input3), ])(); - expect(result).documentationBundleHasLength(2); + + expect(result).documentationBundleHasLength(3); assertEither(result, (data) => expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('This is a description'), ); + assertEither(result, (data) => + expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('test object for testing'), + ); }); it('returns a reference guide with descriptions with links to all other files', async () => { diff --git a/src/core/markdown/__test__/test-helpers.ts b/src/core/markdown/__test__/test-helpers.ts index 2aab60a8..c81200a5 100644 --- a/src/core/markdown/__test__/test-helpers.ts +++ b/src/core/markdown/__test__/test-helpers.ts @@ -37,3 +37,18 @@ export function generateDocs(apexBundles: UnparsedSourceBundle[], config?: Parti ...config, }); } + +export function customObjectGenerator( + config: { deploymentStatus: string; visibility: string } = { deploymentStatus: 'Deployed', visibility: 'Public' }, +) { + return ` + + + ${config.deploymentStatus} + test object for testing + + MyFirstObjects + ${config.visibility} + `; +} + diff --git a/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts b/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts index 16993aa5..d36d2c84 100644 --- a/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts +++ b/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts @@ -184,5 +184,3 @@ describe('when parsing SObject metadata', () => { expect(E.isLeft(result)).toBe(true); }); }); - -// TODO: Test that things are validated From 677d183d7e8feea35f239d357d649226e38264c2 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sat, 19 Oct 2024 10:26:36 -0400 Subject: [PATCH 22/32] SObject template implementation --- .../__test__/generating-any-apex-doc.spec.ts | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/core/markdown/__test__/generating-any-apex-doc.spec.ts b/src/core/markdown/__test__/generating-any-apex-doc.spec.ts index 61b2a312..44ce4404 100644 --- a/src/core/markdown/__test__/generating-any-apex-doc.spec.ts +++ b/src/core/markdown/__test__/generating-any-apex-doc.spec.ts @@ -1,27 +1,7 @@ -import { DocPageData, PostHookDocumentationBundle } from '../../shared/types'; import { extendExpect } from './expect-extensions'; -import { unparsedApexBundleFromRawString, generateDocs, unparsedObjectBundleFromRawString } from './test-helpers'; +import { unparsedApexBundleFromRawString, generateDocs } from './test-helpers'; import { assertEither } from '../../test-helpers/assert-either'; -function customObjectGenerator( - config: { deploymentStatus: string; visibility: string } = { deploymentStatus: 'Deployed', visibility: 'Public' }, -) { - return ` - - - ${config.deploymentStatus} - test object for testing - - MyFirstObjects - ${config.visibility} - `; -} - -function aSingleDoc(result: PostHookDocumentationBundle): DocPageData { - expect(result.docs).toHaveLength(1); - return result.docs[0]; -} - describe('When generating documentation', () => { beforeAll(() => { extendExpect(); From 7d487490609736a618d42df777be63a358f5595f Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sat, 19 Oct 2024 13:30:06 -0400 Subject: [PATCH 23/32] Retrieving custom fields. --- src/application/file-system.ts | 19 ++++++- src/application/source-code-file-reader.ts | 56 ++++++++++++++++--- .../__test__/generating-change-log.spec.ts | 36 ++++++------ .../markdown/__test__/generating-docs.spec.ts | 30 ---------- .../generating-reference-guide.spec.ts | 4 +- src/core/markdown/__test__/test-helpers.ts | 7 ++- src/core/markdown/generate-docs.ts | 8 +-- .../__test__/reflect-sobject-source.spec.ts | 32 +++++++---- .../sobject/reflect-sobject-source.ts | 17 +++--- src/core/shared/types.d.ts | 14 ++++- 10 files changed, 135 insertions(+), 88 deletions(-) diff --git a/src/application/file-system.ts b/src/application/file-system.ts index 9020b4d2..22f1c603 100644 --- a/src/application/file-system.ts +++ b/src/application/file-system.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import { MetadataResolver } from '@salesforce/source-deploy-retrieve'; import { SourceComponentAdapter } from './source-code-file-reader'; +import { pipe } from 'fp-ts/function'; export interface FileSystem { getComponents(path: string): SourceComponentAdapter[]; @@ -9,7 +10,23 @@ export interface FileSystem { export class DefaultFileSystem implements FileSystem { getComponents(path: string): SourceComponentAdapter[] { - return new MetadataResolver().getComponentsFromPath(path); + const components = new MetadataResolver().getComponentsFromPath(path); + + const fieldComponents = pipe( + components, + (components) => components.filter((component) => component.type.name === 'CustomObject'), + (components) => components.map((component) => component.content), + (contents) => contents.filter((content) => content !== undefined), + (contents) => contents.map((content) => `${content}/fields`), + (potentialFieldLocations) => + potentialFieldLocations.map((potentialFieldLocation) => + new MetadataResolver().getComponentsFromPath(potentialFieldLocation), + ), + (fieldComponents) => fieldComponents.flat(), + (fieldComponents) => fieldComponents.filter((fieldComponent) => fieldComponent.type.name === 'CustomField'), + ); + + return [...components, ...fieldComponents]; } readFile(pathToRead: string): string { diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts index 76b21220..8023b17e 100644 --- a/src/application/source-code-file-reader.ts +++ b/src/application/source-code-file-reader.ts @@ -1,10 +1,11 @@ import { FileSystem } from './file-system'; -import { UnparsedApexBundle, UnparsedSObjectBundle } from '../core/shared/types'; +import { UnparsedApexBundle, UnparsedCustomFieldBundle, UnparsedCustomObjectBundle } from '../core/shared/types'; import { minimatch } from 'minimatch'; import { flow, pipe } from 'fp-ts/function'; import { apply } from '#utils/fp'; -type ComponentTypes = 'ApexClass' | 'CustomObject'; +// TODO: If custom object is passed, then let's automatically also add custom field. +type ComponentTypes = 'ApexClass' | 'CustomObject' | 'CustomField'; /** * Simplified representation of a source component, with only @@ -18,6 +19,9 @@ export type SourceComponentAdapter = { }; xml?: string; content?: string; + parent?: { + name: string; + }; }; type ApexClassApexSourceComponent = { @@ -33,6 +37,13 @@ type CustomObjectSourceComponent = { contentPath: string; }; +type CustomFieldSourceComponent = { + type: 'CustomField'; + name: string; + contentPath: string; + parentName: string; +}; + function getApexSourceComponents( includeMetadata: boolean, sourceComponents: SourceComponentAdapter[], @@ -59,6 +70,7 @@ function toUnparsedApexBundle( return { type: 'apex', + name: component.name, filePath: component.contentPath, content: apexComponentTuple[0], metadataContent: apexComponentTuple[1], @@ -79,27 +91,54 @@ function getCustomObjectSourceComponents(sourceComponents: SourceComponentAdapte function toUnparsedSObjectBundle( fileSystem: FileSystem, customObjectSourceComponents: CustomObjectSourceComponent[], -): UnparsedSObjectBundle[] { +): UnparsedCustomObjectBundle[] { return customObjectSourceComponents.map((component) => { return { type: 'sobject', + name: component.name, filePath: component.contentPath, content: fileSystem.readFile(component.contentPath), }; }); } +function getCustomFieldSourceComponents(sourceComponents: SourceComponentAdapter[]): CustomFieldSourceComponent[] { + return sourceComponents + .filter((component) => component.type.name === 'CustomField') + .map((component) => ({ + name: component.name, + type: 'CustomField' as const, + contentPath: component.xml!, + parentName: component.parent!.name, + })); +} + +function toUnparsedCustomFieldBundle( + fileSystem: FileSystem, + customFieldSourceComponents: CustomFieldSourceComponent[], +): UnparsedCustomFieldBundle[] { + return customFieldSourceComponents.map((component) => ({ + type: 'customfield', + name: component.name, + filePath: component.contentPath, + content: fileSystem.readFile(component.contentPath), + parentName: component.parentName, + })); +} + /** * Reads from source code files and returns their raw body. */ export function processFiles(fileSystem: FileSystem) { - return function ( + return ( componentTypesToRetrieve: T, options: { includeMetadata: boolean } = { includeMetadata: false }, - ) { + ) => { const converters: Record< ComponentTypes, - (components: SourceComponentAdapter[]) => (UnparsedApexBundle | UnparsedSObjectBundle)[] + ( + components: SourceComponentAdapter[], + ) => (UnparsedApexBundle | UnparsedCustomObjectBundle | UnparsedCustomFieldBundle)[] > = { ApexClass: flow(apply(getApexSourceComponents, options.includeMetadata), (apexSourceComponents) => toUnparsedApexBundle(fileSystem, apexSourceComponents), @@ -107,11 +146,14 @@ export function processFiles(fileSystem: FileSystem) { CustomObject: flow(getCustomObjectSourceComponents, (customObjectSourceComponents) => toUnparsedSObjectBundle(fileSystem, customObjectSourceComponents), ), + CustomField: flow(getCustomFieldSourceComponents, (customFieldSourceComponents) => + toUnparsedCustomFieldBundle(fileSystem, customFieldSourceComponents), + ), }; const convertersToUse = componentTypesToRetrieve.map((componentType) => converters[componentType]); - return function (rootPath: string, exclude: string[]) { + return (rootPath: string, exclude: string[]) => { return pipe( fileSystem.getComponents(rootPath), (components) => components.filter((component) => !isExcluded(component.content!, exclude)), diff --git a/src/core/changelog/__test__/generating-change-log.spec.ts b/src/core/changelog/__test__/generating-change-log.spec.ts index b3ff6d05..28e9512e 100644 --- a/src/core/changelog/__test__/generating-change-log.spec.ts +++ b/src/core/changelog/__test__/generating-change-log.spec.ts @@ -49,7 +49,7 @@ describe('when generating a changelog', () => { const oldBundle: UnparsedApexBundle[] = []; const newBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -62,7 +62,7 @@ describe('when generating a changelog', () => { const oldBundle: UnparsedApexBundle[] = []; const newBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -80,7 +80,7 @@ describe('when generating a changelog', () => { const oldBundle: UnparsedApexBundle[] = []; const newBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -95,7 +95,7 @@ describe('when generating a changelog', () => { const oldBundle: UnparsedApexBundle[] = []; const newBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -108,7 +108,7 @@ describe('when generating a changelog', () => { const oldBundle: UnparsedApexBundle[] = []; const newBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -126,7 +126,7 @@ describe('when generating a changelog', () => { const oldBundle: UnparsedApexBundle[] = []; const newBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: newInterfaceSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -143,7 +143,7 @@ describe('when generating a changelog', () => { const oldBundle: UnparsedApexBundle[] = []; const newBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: newEnumSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: newEnumSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -156,7 +156,7 @@ describe('when generating a changelog', () => { const oldBundle: UnparsedApexBundle[] = []; const newBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: newEnumSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: newEnumSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -174,7 +174,7 @@ describe('when generating a changelog', () => { const oldBundle: UnparsedApexBundle[] = []; const newBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: newEnumSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: newEnumSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -189,7 +189,7 @@ describe('when generating a changelog', () => { const oldBundle: UnparsedApexBundle[] = []; const newBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, { ...config, scope: ['global'] })(); @@ -203,7 +203,7 @@ describe('when generating a changelog', () => { const oldClassSource = 'class Test {}'; const oldBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const newBundle: UnparsedApexBundle[] = []; @@ -216,7 +216,7 @@ describe('when generating a changelog', () => { const oldClassSource = 'class Test {}'; const oldBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const newBundle: UnparsedApexBundle[] = []; @@ -232,11 +232,11 @@ describe('when generating a changelog', () => { const newClassSource = 'class Test { void myMethod() {} }'; const oldBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const newBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -251,11 +251,11 @@ describe('when generating a changelog', () => { const newClassSource = 'class Test { void myMethod() {} }'; const oldBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const newBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); @@ -268,11 +268,11 @@ describe('when generating a changelog', () => { const newClassSource = 'class Test { void myMethod() {} }'; const oldBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: oldClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const newBundle: UnparsedApexBundle[] = [ - { type: 'apex', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, + { type: 'apex', name: 'Test', content: newClassSource, filePath: 'Test.cls', metadataContent: null }, ]; const result = await generateChangeLog(oldBundle, newBundle, config)(); diff --git a/src/core/markdown/__test__/generating-docs.spec.ts b/src/core/markdown/__test__/generating-docs.spec.ts index 0e11334d..239058d4 100644 --- a/src/core/markdown/__test__/generating-docs.spec.ts +++ b/src/core/markdown/__test__/generating-docs.spec.ts @@ -32,36 +32,6 @@ describe('When generating documentation', () => { } }); - it('SObject code is named after the path', async () => { - const properties: [ - { - rawContent: string; - filePath: string; - }, - string, - ][] = [ - [ - { - rawContent: customObjectGenerator(), - filePath: 'src/object/MyFirstObject__c.object-meta.xml', - }, - 'MyFirstObject__c.md', - ], - [ - { - rawContent: customObjectGenerator(), - filePath: 'src/object/MySecondObject__c.object-meta.xml', - }, - 'MySecondObject__c.md', - ], - ]; - - for (const [input, expected] of properties) { - const result = await generateDocs([unparsedObjectBundleFromRawString(input)])(); - assertEither(result, (data) => expect(aSingleDoc(data).outputDocPath).toContain(expected)); - } - }); - it('Apex code is placed in the miscellaneous folder if no group is provided', async () => { const properties: [string, string][] = [ ['public class MyClass {}', 'miscellaneous'], diff --git a/src/core/markdown/__test__/generating-reference-guide.spec.ts b/src/core/markdown/__test__/generating-reference-guide.spec.ts index 4d498ba0..510e9458 100644 --- a/src/core/markdown/__test__/generating-reference-guide.spec.ts +++ b/src/core/markdown/__test__/generating-reference-guide.spec.ts @@ -185,7 +185,7 @@ describe('When generating the Reference Guide', () => { const input3 = { rawContent: customObjectGenerator(), - filePath: 'src/object/ATestObject__c.object-meta.xml', + filePath: 'src/object/TestObject__c.object-meta.xml', }; const result = await generateDocs([ @@ -202,7 +202,7 @@ describe('When generating the Reference Guide', () => { expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('MyClass'), ); assertEither(result, (data) => - expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('ATestObject__c'), + expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('TestObject__c'), ); assertEither(result, (data) => expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('MyEnum')); }); diff --git a/src/core/markdown/__test__/test-helpers.ts b/src/core/markdown/__test__/test-helpers.ts index c81200a5..4c500543 100644 --- a/src/core/markdown/__test__/test-helpers.ts +++ b/src/core/markdown/__test__/test-helpers.ts @@ -1,10 +1,11 @@ -import { UnparsedApexBundle, UnparsedSObjectBundle, UnparsedSourceBundle } from '../../shared/types'; +import { UnparsedApexBundle, UnparsedCustomObjectBundle, UnparsedSourceBundle } from '../../shared/types'; import { generateDocs as gen, MarkdownGeneratorConfig } from '../generate-docs'; import { referenceGuideTemplate } from '../templates/reference-guide'; export function unparsedApexBundleFromRawString(raw: string, rawMetadata?: string): UnparsedApexBundle { return { type: 'apex', + name: 'Test', filePath: 'test.cls', content: raw, metadataContent: rawMetadata ?? null, @@ -14,9 +15,10 @@ export function unparsedApexBundleFromRawString(raw: string, rawMetadata?: strin export function unparsedObjectBundleFromRawString(meta: { rawContent: string; filePath: string; -}): UnparsedSObjectBundle { +}): UnparsedCustomObjectBundle { return { type: 'sobject', + name: 'TestObject__c', filePath: meta.filePath, content: meta.rawContent, }; @@ -51,4 +53,3 @@ export function customObjectGenerator( ${config.visibility} `; } - diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index 9df1a5d1..0464c8f1 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -17,7 +17,7 @@ import { DocPageReference, TransformReference, ParsedFile, - UnparsedSObjectBundle, + UnparsedCustomObjectBundle, UnparsedSourceBundle, } from '../shared/types'; import { parsedFilesToRenderableBundle } from './adapters/renderable-bundle'; @@ -56,8 +56,8 @@ export function generateDocs(unparsedApexFiles: UnparsedSourceBundle[], config: return sourceFiles.filter((sourceFile): sourceFile is UnparsedApexBundle => sourceFile.type === 'apex'); } - function filterObjectSourceFiles(sourceFiles: UnparsedSourceBundle[]): UnparsedSObjectBundle[] { - return sourceFiles.filter((sourceFile): sourceFile is UnparsedSObjectBundle => sourceFile.type === 'sobject'); + function filterObjectSourceFiles(sourceFiles: UnparsedSourceBundle[]): UnparsedCustomObjectBundle[] { + return sourceFiles.filter((sourceFile): sourceFile is UnparsedCustomObjectBundle => sourceFile.type === 'sobject'); } return pipe( @@ -93,7 +93,7 @@ function generateForApex(apexBundles: UnparsedApexBundle[], config: MarkdownGene ); } -function generateForObject(objectBundles: UnparsedSObjectBundle[]) { +function generateForObject(objectBundles: UnparsedCustomObjectBundle[]) { function filterNonPublished(parsedFiles: ParsedFile[]): ParsedFile[] { return parsedFiles.filter((parsedFile) => parsedFile.type.deploymentStatus === 'Deployed'); } diff --git a/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts b/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts index d36d2c84..9c2a74e0 100644 --- a/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts +++ b/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts @@ -1,5 +1,5 @@ import { reflectSObjectSources } from '../reflect-sobject-source'; -import { UnparsedSObjectBundle } from '../../../shared/types'; +import { UnparsedCustomObjectBundle } from '../../../shared/types'; import { assertEither } from '../../../test-helpers/assert-either'; import * as E from 'fp-ts/Either'; @@ -15,8 +15,9 @@ const sObjectContent = ` describe('when parsing SObject metadata', () => { test('the resulting type is "sobject"', async () => { - const unparsed: UnparsedSObjectBundle = { + const unparsed: UnparsedCustomObjectBundle = { type: 'sobject', + name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, }; @@ -29,8 +30,9 @@ describe('when parsing SObject metadata', () => { }); test('the resulting type contains the file path', async () => { - const unparsed: UnparsedSObjectBundle = { + const unparsed: UnparsedCustomObjectBundle = { type: 'sobject', + name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, }; @@ -41,8 +43,9 @@ describe('when parsing SObject metadata', () => { }); test('the resulting type contains the correct label', async () => { - const unparsed: UnparsedSObjectBundle = { + const unparsed: UnparsedCustomObjectBundle = { type: 'sobject', + name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, }; @@ -55,8 +58,9 @@ describe('when parsing SObject metadata', () => { }); test('the resulting type contains the correct name', async () => { - const unparsed: UnparsedSObjectBundle = { + const unparsed: UnparsedCustomObjectBundle = { type: 'sobject', + name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, }; @@ -69,8 +73,9 @@ describe('when parsing SObject metadata', () => { }); test('the resulting type contains the deployment status', async () => { - const unparsed: UnparsedSObjectBundle = { + const unparsed: UnparsedCustomObjectBundle = { type: 'sobject', + name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, }; @@ -91,8 +96,9 @@ describe('when parsing SObject metadata', () => { MyFirstObjects `; - const unparsed: UnparsedSObjectBundle = { + const unparsed: UnparsedCustomObjectBundle = { type: 'sobject', + name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, }; @@ -105,8 +111,9 @@ describe('when parsing SObject metadata', () => { }); test('the resulting type contains the visibility', async () => { - const unparsed: UnparsedSObjectBundle = { + const unparsed: UnparsedCustomObjectBundle = { type: 'sobject', + name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, }; @@ -128,8 +135,9 @@ describe('when parsing SObject metadata', () => { MyFirstObjects `; - const unparsed: UnparsedSObjectBundle = { + const unparsed: UnparsedCustomObjectBundle = { type: 'sobject', + name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, }; @@ -152,8 +160,9 @@ describe('when parsing SObject metadata', () => { Public `; - const unparsed: UnparsedSObjectBundle = { + const unparsed: UnparsedCustomObjectBundle = { type: 'sobject', + name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, }; @@ -173,8 +182,9 @@ describe('when parsing SObject metadata', () => { Public `; - const unparsed: UnparsedSObjectBundle = { + const unparsed: UnparsedCustomObjectBundle = { type: 'sobject', + name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, }; diff --git a/src/core/reflection/sobject/reflect-sobject-source.ts b/src/core/reflection/sobject/reflect-sobject-source.ts index c9607643..41bc9c5b 100644 --- a/src/core/reflection/sobject/reflect-sobject-source.ts +++ b/src/core/reflection/sobject/reflect-sobject-source.ts @@ -1,4 +1,4 @@ -import { ParsedFile, UnparsedSObjectBundle } from '../../shared/types'; +import { ParsedFile, UnparsedCustomObjectBundle } from '../../shared/types'; import { XMLParser } from 'fast-xml-parser'; import * as TE from 'fp-ts/TaskEither'; import { ReflectionError, ReflectionErrors } from '../../errors/errors'; @@ -18,7 +18,7 @@ export type ObjectMetadata = { }; export function reflectSObjectSources( - objectSources: UnparsedSObjectBundle[], + objectSources: UnparsedCustomObjectBundle[], ): TE.TaskEither[]> { const semiGroupReflectionError: Semigroup = { concat: (x, y) => new ReflectionErrors([...x.errors, ...y.errors]), @@ -29,13 +29,13 @@ export function reflectSObjectSources( } function reflectSObjectSource( - objectSource: UnparsedSObjectBundle, + objectSource: UnparsedCustomObjectBundle, ): TE.TaskEither> { return pipe( E.tryCatch(() => new XMLParser().parse(objectSource.content), E.toError), E.flatMap(validate), E.map(toObjectMetadata), - E.map((metadata) => addName(metadata, objectSource.filePath)), + E.map((metadata) => addName(metadata, objectSource.name)), E.map(addTypeName), E.map((metadata) => toParsedFile(objectSource.filePath, metadata)), E.mapLeft((error) => new ReflectionErrors([new ReflectionError(objectSource.filePath, error.message)])), @@ -45,6 +45,7 @@ function reflectSObjectSource( function validate(parseResult: unknown): E.Either { const err = E.left(new Error('Invalid SObject metadata')); + function isObject(value: unknown) { return typeof value === 'object' && value !== null ? E.right(value) : err; } @@ -78,14 +79,10 @@ function toObjectMetadata(parserResult: { CustomObject: object }): ObjectMetadat return { ...defaultValues, ...parserResult.CustomObject } as ObjectMetadata; } -function extractNameFromFilePath(filePath: string): string { - return filePath.split('/').pop()!.split('.').shift()!; -} - -function addName(objectMetadata: ObjectMetadata, filePath: string): ObjectMetadata { +function addName(objectMetadata: ObjectMetadata, name: string): ObjectMetadata { return { ...objectMetadata, - name: extractNameFromFilePath(filePath), + name, }; } diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index 4aa4c2f2..f71d3bfa 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -56,16 +56,26 @@ export type UserDefinedChangelogConfig = { export type UserDefinedConfig = UserDefinedMarkdownConfig | UserDefinedOpenApiConfig | UserDefinedChangelogConfig; -export type UnparsedSourceBundle = UnparsedApexBundle | UnparsedSObjectBundle; +export type UnparsedSourceBundle = UnparsedApexBundle | UnparsedCustomObjectBundle | UnparsedCustomFieldBundle; -export type UnparsedSObjectBundle = { +export type UnparsedCustomObjectBundle = { type: 'sobject'; + name: string; filePath: string; content: string; }; +export type UnparsedCustomFieldBundle = { + type: 'customfield'; + name: string; + filePath: string; + content: string; + parentName: string; +}; + export type UnparsedApexBundle = { type: 'apex'; + name: string; filePath: string; content: string; metadataContent: string | null; From d9b532300e2ce39a131027e39315631773143bea Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sat, 19 Oct 2024 14:16:23 -0400 Subject: [PATCH 24/32] Retrieving custom fields. --- src/application/Apexdocs.ts | 2 +- src/application/file-system.ts | 5 +- src/application/source-code-file-reader.ts | 1 - src/core/markdown/adapters/reference-guide.ts | 9 ++- .../markdown/adapters/renderable-bundle.ts | 10 ++- src/core/markdown/generate-docs.ts | 69 +++++++++++++++-- .../sobject/reflect-custom-field-source.ts | 74 +++++++++++++++++++ .../sobject/reflect-sobject-source.ts | 4 + src/core/shared/types.d.ts | 7 +- src/core/shared/utils.ts | 5 +- 10 files changed, 165 insertions(+), 21 deletions(-) create mode 100644 src/core/reflection/sobject/reflect-custom-field-source.ts diff --git a/src/application/Apexdocs.ts b/src/application/Apexdocs.ts index c0a3fc43..e153bc73 100644 --- a/src/application/Apexdocs.ts +++ b/src/application/Apexdocs.ts @@ -51,7 +51,7 @@ async function processMarkdown(config: UserDefinedMarkdownConfig) { return pipe( E.tryCatch( () => - readFiles(['ApexClass', 'CustomObject'], { includeMetadata: config.includeMetadata })( + readFiles(['ApexClass', 'CustomObject', 'CustomField'], { includeMetadata: config.includeMetadata })( config.sourceDir, config.exclude, ), diff --git a/src/application/file-system.ts b/src/application/file-system.ts index 22f1c603..e45b401c 100644 --- a/src/application/file-system.ts +++ b/src/application/file-system.ts @@ -2,6 +2,7 @@ import * as fs from 'fs'; import { MetadataResolver } from '@salesforce/source-deploy-retrieve'; import { SourceComponentAdapter } from './source-code-file-reader'; import { pipe } from 'fp-ts/function'; +import * as nodePath from 'path'; export interface FileSystem { getComponents(path: string): SourceComponentAdapter[]; @@ -17,7 +18,9 @@ export class DefaultFileSystem implements FileSystem { (components) => components.filter((component) => component.type.name === 'CustomObject'), (components) => components.map((component) => component.content), (contents) => contents.filter((content) => content !== undefined), - (contents) => contents.map((content) => `${content}/fields`), + (contents) => contents.map((content) => nodePath.join(content!, 'fields')), + (potentialFieldLocations) => + potentialFieldLocations.filter((potentialFieldLocation) => fs.existsSync(potentialFieldLocation)), (potentialFieldLocations) => potentialFieldLocations.map((potentialFieldLocation) => new MetadataResolver().getComponentsFromPath(potentialFieldLocation), diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts index 8023b17e..0e6d925c 100644 --- a/src/application/source-code-file-reader.ts +++ b/src/application/source-code-file-reader.ts @@ -4,7 +4,6 @@ import { minimatch } from 'minimatch'; import { flow, pipe } from 'fp-ts/function'; import { apply } from '#utils/fp'; -// TODO: If custom object is passed, then let's automatically also add custom field. type ComponentTypes = 'ApexClass' | 'CustomObject' | 'CustomField'; /** diff --git a/src/core/markdown/adapters/reference-guide.ts b/src/core/markdown/adapters/reference-guide.ts index 774ab0d6..cea15919 100644 --- a/src/core/markdown/adapters/reference-guide.ts +++ b/src/core/markdown/adapters/reference-guide.ts @@ -1,10 +1,12 @@ import { MarkdownGeneratorConfig } from '../generate-docs'; import { DocPageReference, ParsedFile } from '../../shared/types'; import { getTypeGroup } from '../../shared/utils'; +import { ObjectMetadata } from '../../reflection/sobject/reflect-sobject-source'; +import { Type } from '@cparra/apex-reflection'; export function parsedFilesToReferenceGuide( config: MarkdownGeneratorConfig, - parsedFiles: ParsedFile[], + parsedFiles: ParsedFile[], ): Record { return parsedFiles.reduce>((acc, parsedFile) => { acc[parsedFile.source.name] = parsedFileToDocPageReference(config, parsedFile); @@ -12,7 +14,10 @@ export function parsedFilesToReferenceGuide( }, {}); } -function parsedFileToDocPageReference(config: MarkdownGeneratorConfig, parsedFile: ParsedFile): DocPageReference { +function parsedFileToDocPageReference( + config: MarkdownGeneratorConfig, + parsedFile: ParsedFile, +): DocPageReference { const path = `${slugify(getTypeGroup(parsedFile.type, config))}/${parsedFile.source.name}.md`; return { source: parsedFile.source, diff --git a/src/core/markdown/adapters/renderable-bundle.ts b/src/core/markdown/adapters/renderable-bundle.ts index 9ac2082e..59506163 100644 --- a/src/core/markdown/adapters/renderable-bundle.ts +++ b/src/core/markdown/adapters/renderable-bundle.ts @@ -17,19 +17,21 @@ import { ObjectMetadata } from '../../reflection/sobject/reflect-sobject-source' export function parsedFilesToRenderableBundle( config: MarkdownGeneratorConfig, - parsedFiles: ParsedFile[], + parsedFiles: ParsedFile[], references: Record, ): RenderableBundle { const referenceFinder = apply(generateLink(config.linkingStrategy), references); - function toReferenceGuide(parsedFiles: ParsedFile[]): Record { + function toReferenceGuide( + parsedFiles: ParsedFile[], + ): Record { return parsedFiles.reduce>( addToReferenceGuide(apply(referenceFinder, '__base__'), config, references), {}, ); } - function toRenderables(parsedFiles: ParsedFile[]): Renderable[] { + function toRenderables(parsedFiles: ParsedFile[]): Renderable[] { return parsedFiles.reduce((acc, parsedFile) => { const renderable = typeToRenderable(parsedFile, apply(referenceFinder, parsedFile.source.name), config); acc.push(renderable); @@ -48,7 +50,7 @@ function addToReferenceGuide( config: MarkdownGeneratorConfig, references: Record, ) { - return (acc: Record, parsedFile: ParsedFile) => { + return (acc: Record, parsedFile: ParsedFile) => { const group: string = getTypeGroup(parsedFile.type, config); if (!acc[group]) { acc[group] = []; diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index 0464c8f1..44a850b9 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -19,6 +19,7 @@ import { ParsedFile, UnparsedCustomObjectBundle, UnparsedSourceBundle, + UnparsedCustomFieldBundle, } from '../shared/types'; import { parsedFilesToRenderableBundle } from './adapters/renderable-bundle'; import { reflectApexSource } from '../reflection/apex/reflect-apex-source'; @@ -32,8 +33,10 @@ import { sortTypesAndMembers } from '../reflection/sort-types-and-members'; import { isSkip } from '../shared/utils'; import { parsedFilesToReferenceGuide } from './adapters/reference-guide'; import { removeExcludedTags } from '../reflection/apex/remove-excluded-tags'; -import { HookError } from '../errors/errors'; +import { HookError, ReflectionErrors } from '../errors/errors'; import { ObjectMetadata, reflectSObjectSources } from '../reflection/sobject/reflect-sobject-source'; +import { CustomFieldMetadata, reflectCustomFieldSources } from '../reflection/sobject/reflect-custom-field-source'; +import { Type } from '@cparra/apex-reflection'; export type MarkdownGeneratorConfig = Omit< UserDefinedMarkdownConfig, @@ -56,23 +59,39 @@ export function generateDocs(unparsedApexFiles: UnparsedSourceBundle[], config: return sourceFiles.filter((sourceFile): sourceFile is UnparsedApexBundle => sourceFile.type === 'apex'); } - function filterObjectSourceFiles(sourceFiles: UnparsedSourceBundle[]): UnparsedCustomObjectBundle[] { - return sourceFiles.filter((sourceFile): sourceFile is UnparsedCustomObjectBundle => sourceFile.type === 'sobject'); + function filterCustomObjectsAndFields( + sourceFiles: UnparsedSourceBundle[], + ): (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle)[] { + return sourceFiles.filter( + (sourceFile): sourceFile is UnparsedCustomObjectBundle => + sourceFile.type === 'sobject' || sourceFile.type === 'customfield', + ); + } + + function filterOutCustomFields(parsedFiles: ParsedFile[]): ParsedFile[] { + return parsedFiles.filter( + (parsedFile): parsedFile is ParsedFile => parsedFile.source.type !== 'customfield', + ); } return pipe( generateForApex(filterApexSourceFiles(unparsedApexFiles), config), TE.chain((parsedApexFiles) => { return pipe( - generateForObject(filterObjectSourceFiles(unparsedApexFiles)), + generateForObject(filterCustomObjectsAndFields(unparsedApexFiles)), TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]), ); }), TE.map(sort), TE.bindTo('parsedFiles'), - TE.bind('references', ({ parsedFiles }) => TE.right(convertToReferences(parsedFiles))), + TE.bind('references', ({ parsedFiles }) => + TE.right( + // Custom fields should not show up in the reference guide + convertToReferences(filterOutCustomFields(parsedFiles)), + ), + ), TE.flatMap(({ parsedFiles, references }) => transformReferenceHook(config)({ references, parsedFiles })), - TE.map(({ parsedFiles, references }) => convertToRenderableBundle(parsedFiles, references)), + TE.map(({ parsedFiles, references }) => convertToRenderableBundle(filterOutCustomFields(parsedFiles), references)), TE.map(convertToDocumentationBundleForTemplate), TE.flatMap(transformDocumentationBundleHook(config)), TE.map(postHookCompile), @@ -93,7 +112,7 @@ function generateForApex(apexBundles: UnparsedApexBundle[], config: MarkdownGene ); } -function generateForObject(objectBundles: UnparsedCustomObjectBundle[]) { +function generateForObject(objectBundles: (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle)[]) { function filterNonPublished(parsedFiles: ParsedFile[]): ParsedFile[] { return parsedFiles.filter((parsedFile) => parsedFile.type.deploymentStatus === 'Deployed'); } @@ -102,7 +121,41 @@ function generateForObject(objectBundles: UnparsedCustomObjectBundle[]) { return parsedFiles.filter((parsedFile) => parsedFile.type.visibility === 'Public'); } - return pipe(objectBundles, reflectSObjectSources, TE.map(filterNonPublished), TE.map(filterNonPublic)); + const customObjects = objectBundles.filter( + (object): object is UnparsedCustomObjectBundle => object.type === 'sobject', + ); + + const customFields = objectBundles.filter( + (object): object is UnparsedCustomFieldBundle => object.type === 'customfield', + ); + + function generateForFields( + fields: UnparsedCustomFieldBundle[], + ): TE.TaskEither[]> { + return pipe(fields, reflectCustomFieldSources); + } + + return pipe( + customObjects, + reflectSObjectSources, + TE.map(filterNonPublished), + TE.map(filterNonPublic), + TE.bindTo('objects'), + TE.bind('fields', () => generateForFields(customFields)), + // Locate the fields for each object by using the parentName property + TE.map(({ objects, fields }) => { + return objects.map((object) => { + const objectFields = fields.filter((field) => field.type.parentName === object.type.name); + return { + ...object, + type: { + ...object.type, + fields: objectFields, + }, + } as ParsedFile; + }); + }), + ); } function transformReferenceHook(config: MarkdownGeneratorConfig) { diff --git a/src/core/reflection/sobject/reflect-custom-field-source.ts b/src/core/reflection/sobject/reflect-custom-field-source.ts new file mode 100644 index 00000000..13768483 --- /dev/null +++ b/src/core/reflection/sobject/reflect-custom-field-source.ts @@ -0,0 +1,74 @@ +import { ParsedFile, UnparsedCustomFieldBundle } from '../../shared/types'; +import { ReflectionError, ReflectionErrors } from '../../errors/errors'; +import { Semigroup } from 'fp-ts/Semigroup'; +import * as TE from 'fp-ts/TaskEither'; +import * as T from 'fp-ts/Task'; +import { pipe } from 'fp-ts/function'; +import * as A from 'fp-ts/Array'; +import { XMLParser } from 'fast-xml-parser'; +import * as E from 'fp-ts/Either'; + +export type CustomFieldMetadata = { + type_name: 'customfield'; + description: string | null; + name: string; + label: string; + type: string; + parentName: string; + + // TODO: Whatever else we want to put around fields + // TODO: E.g. if it is required, picklist values, etc. +}; + +export function reflectCustomFieldSources( + customFieldSources: UnparsedCustomFieldBundle[], +): TE.TaskEither[]> { + const semiGroupReflectionError: Semigroup = { + concat: (x, y) => new ReflectionErrors([...x.errors, ...y.errors]), + }; + const Ap = TE.getApplicativeTaskValidation(T.ApplyPar, semiGroupReflectionError); + + return pipe(customFieldSources, A.traverse(Ap)(reflectCustomFieldSource)); +} + +function reflectCustomFieldSource( + customFieldSource: UnparsedCustomFieldBundle, +): TE.TaskEither> { + return pipe( + E.tryCatch(() => new XMLParser().parse(customFieldSource.content), E.toError), + // TODO: Validate + E.map(toCustomFieldMetadata), + E.map((metadata) => addName(metadata, customFieldSource.name)), + E.map((metadata) => addParentName(metadata, customFieldSource.parentName)), + E.map((metadata) => toParsedFile(customFieldSource.filePath, metadata)), + E.mapLeft((error) => new ReflectionErrors([new ReflectionError(customFieldSource.filePath, error.message)])), + TE.fromEither, + ); +} + +function toCustomFieldMetadata(parserResult: { CustomField: object }): CustomFieldMetadata { + const defaultValues = { + description: null, + }; + + return { ...defaultValues, ...parserResult.CustomField, type_name: 'customfield' } as CustomFieldMetadata; +} + +function addName(metadata: CustomFieldMetadata, name: string): CustomFieldMetadata { + return { ...metadata, name }; +} + +function addParentName(metadata: CustomFieldMetadata, parentName: string): CustomFieldMetadata { + return { ...metadata, parentName }; +} + +function toParsedFile(filePath: string, typeMirror: CustomFieldMetadata): ParsedFile { + return { + source: { + filePath, + name: typeMirror.name, + type: typeMirror.type_name, + }, + type: typeMirror, + }; +} diff --git a/src/core/reflection/sobject/reflect-sobject-source.ts b/src/core/reflection/sobject/reflect-sobject-source.ts index 41bc9c5b..eea0d55e 100644 --- a/src/core/reflection/sobject/reflect-sobject-source.ts +++ b/src/core/reflection/sobject/reflect-sobject-source.ts @@ -7,6 +7,7 @@ import * as T from 'fp-ts/Task'; import { pipe } from 'fp-ts/function'; import * as A from 'fp-ts/Array'; import * as E from 'fp-ts/Either'; +import { CustomFieldMetadata } from './reflect-custom-field-source'; export type ObjectMetadata = { type_name: 'sobject'; @@ -15,6 +16,7 @@ export type ObjectMetadata = { label: string; name: string; description: string | null; + fields: ParsedFile[]; }; export function reflectSObjectSources( @@ -75,6 +77,8 @@ function toObjectMetadata(parserResult: { CustomObject: object }): ObjectMetadat const defaultValues = { deploymentStatus: 'Deployed', visibility: 'Public', + description: null, + fields: [] as ParsedFile[], }; return { ...defaultValues, ...parserResult.CustomObject } as ObjectMetadata; } diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index f71d3bfa..04d58091 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -1,6 +1,7 @@ import { Type } from '@cparra/apex-reflection'; import { ChangeLogPageData } from '../changelog/generate-change-log'; import { ObjectMetadata } from '../reflection/sobject/reflect-sobject-source'; +import { CustomFieldMetadata } from '../reflection/sobject/reflect-custom-field-source'; export type Generators = 'markdown' | 'openapi' | 'changelog'; @@ -84,10 +85,12 @@ export type UnparsedApexBundle = { export type SourceFileMetadata = { filePath: string; name: string; - type: 'interface' | 'class' | 'enum' | 'sobject'; + type: 'interface' | 'class' | 'enum' | 'sobject' | 'customfield'; }; -export type ParsedFile = { +export type ParsedFile< + T extends Type | ObjectMetadata | CustomFieldMetadata = Type | ObjectMetadata | CustomFieldMetadata, +> = { source: SourceFileMetadata; type: T; }; diff --git a/src/core/shared/utils.ts b/src/core/shared/utils.ts index 7a3d0226..fa381678 100644 --- a/src/core/shared/utils.ts +++ b/src/core/shared/utils.ts @@ -2,6 +2,7 @@ import { Skip } from './types'; import { Type } from '@cparra/apex-reflection'; import { ObjectMetadata } from '../reflection/sobject/reflect-sobject-source'; import { MarkdownGeneratorConfig } from '../markdown/generate-docs'; +import { CustomFieldMetadata } from '../reflection/sobject/reflect-custom-field-source'; /** * Represents a file to be skipped. @@ -16,11 +17,11 @@ export function isSkip(value: unknown): value is Skip { return Object.prototype.hasOwnProperty.call(value, '_tag') && (value as Skip)._tag === 'Skip'; } -export function isObjectType(type: Type | ObjectMetadata): type is ObjectMetadata { +export function isObjectType(type: Type | ObjectMetadata | CustomFieldMetadata): type is ObjectMetadata { return (type as ObjectMetadata).type_name === 'sobject'; } -export function isApexType(type: Type | ObjectMetadata): type is Type { +export function isApexType(type: Type | ObjectMetadata | CustomFieldMetadata): type is Type { return !isObjectType(type); } From 12503cd83257ce835235508d4d13ad4071c5b3c5 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sat, 19 Oct 2024 14:36:37 -0400 Subject: [PATCH 25/32] Rendering field data --- .../vitepress/docs/custom-objects/Event__c.md | 11 ++- .../docs/custom-objects/Price_Component__c.md | 11 ++- .../Product_Price_Component__c.md | 8 ++- .../docs/custom-objects/Product__c.md | 9 ++- .../custom-objects/Sales_Order_Line__c.md | 11 ++- .../docs/custom-objects/Speaker__c.md | 9 ++- .../adapters/renderable-to-page-data.ts | 4 +- .../markdown/adapters/type-to-renderable.ts | 71 ++++++++++++------- .../templates/custom-object-template.ts | 18 +++++ .../markdown/templates/sobject-template.ts | 9 --- src/core/renderables/types.d.ts | 15 +++- 11 files changed, 130 insertions(+), 46 deletions(-) create mode 100644 src/core/markdown/templates/custom-object-template.ts delete mode 100644 src/core/markdown/templates/sobject-template.ts diff --git a/examples/vitepress/docs/custom-objects/Event__c.md b/examples/vitepress/docs/custom-objects/Event__c.md index 88d204b2..f25dd6b1 100644 --- a/examples/vitepress/docs/custom-objects/Event__c.md +++ b/examples/vitepress/docs/custom-objects/Event__c.md @@ -7,4 +7,13 @@ title: Event__c Represents an event that people can register for. ## API Name -`apexdocs__Event__c` \ No newline at end of file +`apexdocs__Event__c` + +## Fields +| Field | Description | +|-------|-------------| +| **Description** | | +| **End Date** | | +| **Location** | | +| **Start Date** | | +| **Tag Line** | | \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Price_Component__c.md b/examples/vitepress/docs/custom-objects/Price_Component__c.md index c50fef14..263ec3fc 100644 --- a/examples/vitepress/docs/custom-objects/Price_Component__c.md +++ b/examples/vitepress/docs/custom-objects/Price_Component__c.md @@ -5,4 +5,13 @@ title: Price_Component__c # Price Component ## API Name -`apexdocs__Price_Component__c` \ No newline at end of file +`apexdocs__Price_Component__c` + +## Fields +| Field | Description | +|-------|-------------| +| **Description** | | +| **Expression** | The Expression that determines if this price should take effect or not. | +| **Percent** | Use this field to calculate the price based on the list price's percentage instead of providing a flat price. | +| **Price** | Use this when the Price Component represents a Flat Price. To represent a Percentage use the Percent field. | +| **Type** | | \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md b/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md index aae375b9..85efe8a3 100644 --- a/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md +++ b/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md @@ -5,4 +5,10 @@ title: Product_Price_Component__c # Product Price Component ## API Name -`apexdocs__Product_Price_Component__c` \ No newline at end of file +`apexdocs__Product_Price_Component__c` + +## Fields +| Field | Description | +|-------|-------------| +| **Price Component** | | +| **Product** | | \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Product__c.md b/examples/vitepress/docs/custom-objects/Product__c.md index 8830ca4f..c691207b 100644 --- a/examples/vitepress/docs/custom-objects/Product__c.md +++ b/examples/vitepress/docs/custom-objects/Product__c.md @@ -7,4 +7,11 @@ title: Product__c Product that is sold or available for sale. ## API Name -`apexdocs__Product__c` \ No newline at end of file +`apexdocs__Product__c` + +## Fields +| Field | Description | +|-------|-------------| +| **Description** | | +| **Event** | | +| **Features** | | \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md b/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md index 245cd660..3d312f25 100644 --- a/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md +++ b/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md @@ -7,4 +7,13 @@ title: Sales_Order_Line__c Represents a line item on a sales order. ## API Name -`apexdocs__Sales_Order_Line__c` \ No newline at end of file +`apexdocs__Sales_Order_Line__c` + +## Fields +| Field | Description | +|-------|-------------| +| **Amount** | | +| **Product** | | +| **Sales Order** | | +| **Source Price Component** | | +| **Type** | | \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Speaker__c.md b/examples/vitepress/docs/custom-objects/Speaker__c.md index 349d6d70..f9079e4d 100644 --- a/examples/vitepress/docs/custom-objects/Speaker__c.md +++ b/examples/vitepress/docs/custom-objects/Speaker__c.md @@ -7,4 +7,11 @@ title: Speaker__c Represents a speaker at an event. ## API Name -`apexdocs__Speaker__c` \ No newline at end of file +`apexdocs__Speaker__c` + +## Fields +| Field | Description | +|-------|-------------| +| **About** | | +| **Event** | | +| **Person** | | \ No newline at end of file diff --git a/src/core/markdown/adapters/renderable-to-page-data.ts b/src/core/markdown/adapters/renderable-to-page-data.ts index 5c906791..118e4f76 100644 --- a/src/core/markdown/adapters/renderable-to-page-data.ts +++ b/src/core/markdown/adapters/renderable-to-page-data.ts @@ -6,7 +6,7 @@ import { enumMarkdownTemplate } from '../templates/enum-template'; import { interfaceMarkdownTemplate } from '../templates/interface-template'; import { classMarkdownTemplate } from '../templates/class-template'; import { markdownDefaults } from '../../../defaults'; -import { sObjectTemplate } from '../templates/sobject-template'; +import { customObjectTemplate } from '../templates/custom-object-template'; export const convertToDocumentationBundle = ( referenceGuideTitle: string, @@ -79,7 +79,7 @@ function resolveApexTypeTemplate(renderable: Renderable): CompilationRequest { case 'class': return classMarkdownTemplate; case 'sobject': - return sObjectTemplate; + return customObjectTemplate; } } diff --git a/src/core/markdown/adapters/type-to-renderable.ts b/src/core/markdown/adapters/type-to-renderable.ts index c6697ddb..04b68b3e 100644 --- a/src/core/markdown/adapters/type-to-renderable.ts +++ b/src/core/markdown/adapters/type-to-renderable.ts @@ -10,7 +10,8 @@ import { FieldMirrorWithInheritance, PropertyMirrorWithInheritance, GetRenderableContentByTypeName, - RenderableSObject, + RenderableCustomObject, + RenderableCustomField, } from '../../renderables/types'; import { adaptDescribable, adaptDocumentable } from '../../renderables/documentables'; import { adaptConstructor, adaptMethod } from './methods-and-constructors'; @@ -19,6 +20,7 @@ import { MarkdownGeneratorConfig } from '../generate-docs'; import { SourceFileMetadata } from '../../shared/types'; import { ObjectMetadata } from '../../reflection/sobject/reflect-sobject-source'; import { getTypeGroup } from '../../shared/utils'; +import { CustomFieldMetadata } from '../../reflection/sobject/reflect-custom-field-source'; type GetReturnRenderable = T extends InterfaceMirror ? RenderableInterface @@ -26,14 +28,14 @@ type GetReturnRenderable = T extends InterfaceM ? RenderableClass : T extends EnumMirror ? RenderableEnum - : RenderableSObject; + : RenderableCustomObject; export function typeToRenderable( parsedFile: { source: SourceFileMetadata; type: T }, linkGenerator: GetRenderableContentByTypeName, config: MarkdownGeneratorConfig, ): GetReturnRenderable & { filePath: string; namespace?: string } { - function getRenderable(): RenderableInterface | RenderableClass | RenderableEnum | RenderableSObject { + function getRenderable(): RenderableInterface | RenderableClass | RenderableEnum | RenderableCustomObject { const { type } = parsedFile; switch (type.type_name) { case 'enum': @@ -100,30 +102,6 @@ function enumTypeToEnumSource( }; } -function objectMetadataToRenderable( - objectMetadata: ObjectMetadata, - config: MarkdownGeneratorConfig, -): RenderableSObject { - function getApiName() { - if (config.namespace) { - return `${config.namespace}__${objectMetadata.name}`; - } - return objectMetadata.name; - } - - return { - type: 'sobject', - headingLevel: 1, - apiName: getApiName(), - heading: objectMetadata.label, - name: objectMetadata.name, - doc: { - description: objectMetadata.description ? [objectMetadata.description] : [], - group: getTypeGroup(objectMetadata, config), - }, - }; -} - function interfaceTypeToInterfaceSource( interfaceType: InterfaceMirror, linkGenerator: GetRenderableContentByTypeName, @@ -268,3 +246,42 @@ function singleGroup( value: toFlat(members, adapter, linkGenerator, headingLevel + 1), }; } + +function objectMetadataToRenderable( + objectMetadata: ObjectMetadata, + config: MarkdownGeneratorConfig, +): RenderableCustomObject { + function getApiName() { + if (config.namespace) { + return `${config.namespace}__${objectMetadata.name}`; + } + return objectMetadata.name; + } + + return { + type: 'sobject', + headingLevel: 1, + apiName: getApiName(), + heading: objectMetadata.label, + name: objectMetadata.name, + doc: { + description: objectMetadata.description ? [objectMetadata.description] : [], + group: getTypeGroup(objectMetadata, config), + }, + hasFields: objectMetadata.fields.length > 0, + fields: { + headingLevel: 2, + heading: 'Fields', + value: objectMetadata.fields.map((field) => fieldMetadataToRenderable(field.type)), + }, + }; +} + +function fieldMetadataToRenderable(field: CustomFieldMetadata): RenderableCustomField { + return { + label: field.label, + description: field.description ? [field.description] : [], + apiName: field.name, + type: 'field', + }; +} diff --git a/src/core/markdown/templates/custom-object-template.ts b/src/core/markdown/templates/custom-object-template.ts new file mode 100644 index 00000000..3f838177 --- /dev/null +++ b/src/core/markdown/templates/custom-object-template.ts @@ -0,0 +1,18 @@ +export const customObjectTemplate = ` +{{ heading headingLevel heading }} + +{{{renderContent doc.description}}} + +## API Name +\`{{apiName}}\` + +{{#if hasFields}} +{{ heading fields.headingLevel fields.heading }} +| Field Name | Details | +|-------|-------------| +{{#each fields.value}} +| **{{ label }}** | {{{renderContent description}}} | +{{/each}} +{{/if}} + +`.trim(); diff --git a/src/core/markdown/templates/sobject-template.ts b/src/core/markdown/templates/sobject-template.ts deleted file mode 100644 index f77bf752..00000000 --- a/src/core/markdown/templates/sobject-template.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const sObjectTemplate = ` -{{ heading headingLevel heading }} - -{{{renderContent doc.description}}} - -## API Name -\`{{apiName}}\` - -`.trim(); diff --git a/src/core/renderables/types.d.ts b/src/core/renderables/types.d.ts index e8616362..f8996083 100644 --- a/src/core/renderables/types.d.ts +++ b/src/core/renderables/types.d.ts @@ -177,11 +177,22 @@ export type RenderableEnum = RenderableType & { values: RenderableSection; }; -export type RenderableSObject = Omit & { +export type RenderableCustomObject = Omit & { apiName: string; type: 'sobject'; + hasFields: boolean; + fields: RenderableSection; }; -export type Renderable = (RenderableClass | RenderableInterface | RenderableEnum | RenderableSObject) & { +export type RenderableCustomField = { + label: string; + apiName: string; + description: RenderableContent[]; + type: 'field'; + + // TODO: Whatever else we want to add to the field descriptions +}; + +export type Renderable = (RenderableClass | RenderableInterface | RenderableEnum | RenderableCustomObject) & { filePath: string; }; From 6c870639204c0940d211d183ecf0cee592915f1a Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sat, 19 Oct 2024 14:50:41 -0400 Subject: [PATCH 26/32] Refactoring from "sobject" to "customobject" --- examples/vitepress/apexdocs.config.ts | 13 +- .../vitepress/docs/.vitepress/sidebar.json | 149 +++++++++--------- .../vitepress/docs/custom-objects/Event__c.md | 2 +- .../docs/custom-objects/Price_Component__c.md | 2 +- .../Product_Price_Component__c.md | 2 +- .../docs/custom-objects/Product__c.md | 2 +- .../custom-objects/Sales_Order_Line__c.md | 2 +- .../docs/custom-objects/Speaker__c.md | 2 +- src/application/source-code-file-reader.ts | 2 +- .../markdown/__test__/generating-docs.spec.ts | 2 +- .../__test__/generating-enum-docs.spec.ts | 3 + src/core/markdown/__test__/test-helpers.ts | 2 +- .../markdown/adapters/renderable-bundle.ts | 2 +- .../adapters/renderable-to-page-data.ts | 3 +- .../markdown/adapters/type-to-renderable.ts | 4 +- src/core/markdown/generate-docs.ts | 4 +- .../__test__/reflect-sobject-source.spec.ts | 24 +-- .../sobject/reflect-sobject-source.ts | 4 +- src/core/renderables/types.d.ts | 2 +- src/core/shared/types.d.ts | 7 +- src/core/shared/utils.ts | 4 +- 21 files changed, 116 insertions(+), 121 deletions(-) diff --git a/examples/vitepress/apexdocs.config.ts b/examples/vitepress/apexdocs.config.ts index 8584092f..8b389762 100644 --- a/examples/vitepress/apexdocs.config.ts +++ b/examples/vitepress/apexdocs.config.ts @@ -55,15 +55,10 @@ export default { const sidebar = [ { text: 'API Reference', - items: [ - { - text: 'Groups', - items: Array.from(extractGroups(docs)).map(([groupName, groupDocs]) => ({ - text: groupName, - items: groupDocs.map(toSidebarLink), - })), - }, - ], + items: Array.from(extractGroups(docs)).map(([groupName, groupDocs]) => ({ + text: groupName, + items: groupDocs.map(toSidebarLink), + })), }, ]; await writeFileAsync('./docs/.vitepress/sidebar.json', JSON.stringify(sidebar, null, 2)); diff --git a/examples/vitepress/docs/.vitepress/sidebar.json b/examples/vitepress/docs/.vitepress/sidebar.json index 6531c3de..d4484518 100644 --- a/examples/vitepress/docs/.vitepress/sidebar.json +++ b/examples/vitepress/docs/.vitepress/sidebar.json @@ -3,91 +3,86 @@ "text": "API Reference", "items": [ { - "text": "Groups", + "text": "Miscellaneous", "items": [ { - "text": "Miscellaneous", - "items": [ - { - "text": "BaseClass", - "link": "miscellaneous/BaseClass.md" - }, - { - "text": "MultiInheritanceClass", - "link": "miscellaneous/MultiInheritanceClass.md" - }, - { - "text": "ParentInterface", - "link": "miscellaneous/ParentInterface.md" - }, - { - "text": "ReferencedEnum", - "link": "miscellaneous/ReferencedEnum.md" - }, - { - "text": "SampleException", - "link": "miscellaneous/SampleException.md" - }, - { - "text": "SampleInterface", - "link": "miscellaneous/SampleInterface.md" - }, - { - "text": "Url", - "link": "miscellaneous/Url.md" - } - ] + "text": "BaseClass", + "link": "miscellaneous/BaseClass.md" }, { - "text": "Custom Objects", - "items": [ - { - "text": "Event__c", - "link": "custom-objects/Event__c.md" - }, - { - "text": "Price_Component__c", - "link": "custom-objects/Price_Component__c.md" - }, - { - "text": "Product__c", - "link": "custom-objects/Product__c.md" - }, - { - "text": "Product_Price_Component__c", - "link": "custom-objects/Product_Price_Component__c.md" - }, - { - "text": "Sales_Order__c", - "link": "custom-objects/Sales_Order__c.md" - }, - { - "text": "Sales_Order_Line__c", - "link": "custom-objects/Sales_Order_Line__c.md" - }, - { - "text": "Speaker__c", - "link": "custom-objects/Speaker__c.md" - } - ] + "text": "MultiInheritanceClass", + "link": "miscellaneous/MultiInheritanceClass.md" }, { - "text": "SampleGroup", - "items": [ - { - "text": "SampleClass", - "link": "samplegroup/SampleClass.md" - } - ] + "text": "ParentInterface", + "link": "miscellaneous/ParentInterface.md" }, { - "text": "Sample Enums", - "items": [ - { - "text": "SampleEnum", - "link": "sample-enums/SampleEnum.md" - } - ] + "text": "ReferencedEnum", + "link": "miscellaneous/ReferencedEnum.md" + }, + { + "text": "SampleException", + "link": "miscellaneous/SampleException.md" + }, + { + "text": "SampleInterface", + "link": "miscellaneous/SampleInterface.md" + }, + { + "text": "Url", + "link": "miscellaneous/Url.md" + } + ] + }, + { + "text": "Custom Objects", + "items": [ + { + "text": "Event__c", + "link": "custom-objects/Event__c.md" + }, + { + "text": "Price_Component__c", + "link": "custom-objects/Price_Component__c.md" + }, + { + "text": "Product__c", + "link": "custom-objects/Product__c.md" + }, + { + "text": "Product_Price_Component__c", + "link": "custom-objects/Product_Price_Component__c.md" + }, + { + "text": "Sales_Order__c", + "link": "custom-objects/Sales_Order__c.md" + }, + { + "text": "Sales_Order_Line__c", + "link": "custom-objects/Sales_Order_Line__c.md" + }, + { + "text": "Speaker__c", + "link": "custom-objects/Speaker__c.md" + } + ] + }, + { + "text": "SampleGroup", + "items": [ + { + "text": "SampleClass", + "link": "samplegroup/SampleClass.md" + } + ] + }, + { + "text": "Sample Enums", + "items": [ + { + "text": "SampleEnum", + "link": "sample-enums/SampleEnum.md" } ] } diff --git a/examples/vitepress/docs/custom-objects/Event__c.md b/examples/vitepress/docs/custom-objects/Event__c.md index f25dd6b1..d4975611 100644 --- a/examples/vitepress/docs/custom-objects/Event__c.md +++ b/examples/vitepress/docs/custom-objects/Event__c.md @@ -10,7 +10,7 @@ Represents an event that people can register for. `apexdocs__Event__c` ## Fields -| Field | Description | +| Field Name | Details | |-------|-------------| | **Description** | | | **End Date** | | diff --git a/examples/vitepress/docs/custom-objects/Price_Component__c.md b/examples/vitepress/docs/custom-objects/Price_Component__c.md index 263ec3fc..2a5523a8 100644 --- a/examples/vitepress/docs/custom-objects/Price_Component__c.md +++ b/examples/vitepress/docs/custom-objects/Price_Component__c.md @@ -8,7 +8,7 @@ title: Price_Component__c `apexdocs__Price_Component__c` ## Fields -| Field | Description | +| Field Name | Details | |-------|-------------| | **Description** | | | **Expression** | The Expression that determines if this price should take effect or not. | diff --git a/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md b/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md index 85efe8a3..ab9b3c0f 100644 --- a/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md +++ b/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md @@ -8,7 +8,7 @@ title: Product_Price_Component__c `apexdocs__Product_Price_Component__c` ## Fields -| Field | Description | +| Field Name | Details | |-------|-------------| | **Price Component** | | | **Product** | | \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Product__c.md b/examples/vitepress/docs/custom-objects/Product__c.md index c691207b..4e38a604 100644 --- a/examples/vitepress/docs/custom-objects/Product__c.md +++ b/examples/vitepress/docs/custom-objects/Product__c.md @@ -10,7 +10,7 @@ Product that is sold or available for sale. `apexdocs__Product__c` ## Fields -| Field | Description | +| Field Name | Details | |-------|-------------| | **Description** | | | **Event** | | diff --git a/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md b/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md index 3d312f25..be2e7728 100644 --- a/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md +++ b/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md @@ -10,7 +10,7 @@ Represents a line item on a sales order. `apexdocs__Sales_Order_Line__c` ## Fields -| Field | Description | +| Field Name | Details | |-------|-------------| | **Amount** | | | **Product** | | diff --git a/examples/vitepress/docs/custom-objects/Speaker__c.md b/examples/vitepress/docs/custom-objects/Speaker__c.md index f9079e4d..b559bbf4 100644 --- a/examples/vitepress/docs/custom-objects/Speaker__c.md +++ b/examples/vitepress/docs/custom-objects/Speaker__c.md @@ -10,7 +10,7 @@ Represents a speaker at an event. `apexdocs__Speaker__c` ## Fields -| Field | Description | +| Field Name | Details | |-------|-------------| | **About** | | | **Event** | | diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts index 0e6d925c..7d9b5da3 100644 --- a/src/application/source-code-file-reader.ts +++ b/src/application/source-code-file-reader.ts @@ -93,7 +93,7 @@ function toUnparsedSObjectBundle( ): UnparsedCustomObjectBundle[] { return customObjectSourceComponents.map((component) => { return { - type: 'sobject', + type: 'customobject', name: component.name, filePath: component.contentPath, content: fileSystem.readFile(component.contentPath), diff --git a/src/core/markdown/__test__/generating-docs.spec.ts b/src/core/markdown/__test__/generating-docs.spec.ts index 239058d4..e8f6fb32 100644 --- a/src/core/markdown/__test__/generating-docs.spec.ts +++ b/src/core/markdown/__test__/generating-docs.spec.ts @@ -134,7 +134,7 @@ describe('When generating documentation', () => { rawContent: customObjectGenerator(), filePath: 'src/object/MyFirstObject__c.object-meta.xml', }, - 'sobject', + 'customobject', ], ]; diff --git a/src/core/markdown/__test__/generating-enum-docs.spec.ts b/src/core/markdown/__test__/generating-enum-docs.spec.ts index 66bf94bb..90f4954f 100644 --- a/src/core/markdown/__test__/generating-enum-docs.spec.ts +++ b/src/core/markdown/__test__/generating-enum-docs.spec.ts @@ -2,6 +2,9 @@ import { extendExpect } from './expect-extensions'; import { unparsedApexBundleFromRawString, generateDocs } from './test-helpers'; import { assertEither } from '../../test-helpers/assert-either'; +// TODO: Create a test similar to this. +// TODO: Make sure to test the output for every single type of field supported + describe('Generates enum documentation', () => { beforeAll(() => { extendExpect(); diff --git a/src/core/markdown/__test__/test-helpers.ts b/src/core/markdown/__test__/test-helpers.ts index 4c500543..dcd8268d 100644 --- a/src/core/markdown/__test__/test-helpers.ts +++ b/src/core/markdown/__test__/test-helpers.ts @@ -17,7 +17,7 @@ export function unparsedObjectBundleFromRawString(meta: { filePath: string; }): UnparsedCustomObjectBundle { return { - type: 'sobject', + type: 'customobject', name: 'TestObject__c', filePath: meta.filePath, content: meta.rawContent, diff --git a/src/core/markdown/adapters/renderable-bundle.ts b/src/core/markdown/adapters/renderable-bundle.ts index 59506163..46ac7ce2 100644 --- a/src/core/markdown/adapters/renderable-bundle.ts +++ b/src/core/markdown/adapters/renderable-bundle.ts @@ -70,7 +70,7 @@ function getRenderableDescription( findLinkFromHome: (referenceName: string) => string | Link, ): RenderableContent[] | null { switch (type.type_name) { - case 'sobject': + case 'customobject': return type.description ? [type.description] : null; default: return adaptDescribable(type.docComment?.descriptionLines, findLinkFromHome).description ?? null; diff --git a/src/core/markdown/adapters/renderable-to-page-data.ts b/src/core/markdown/adapters/renderable-to-page-data.ts index 118e4f76..f5d84776 100644 --- a/src/core/markdown/adapters/renderable-to-page-data.ts +++ b/src/core/markdown/adapters/renderable-to-page-data.ts @@ -63,6 +63,7 @@ function renderableToPageData(referenceGuideReference: ReferenceGuideReference[] frontmatter: null, content: docContents, group: renderable.doc.group ?? markdownDefaults.defaultGroupName, + type: renderable.type, }; } @@ -78,7 +79,7 @@ function resolveApexTypeTemplate(renderable: Renderable): CompilationRequest { return interfaceMarkdownTemplate; case 'class': return classMarkdownTemplate; - case 'sobject': + case 'customobject': return customObjectTemplate; } } diff --git a/src/core/markdown/adapters/type-to-renderable.ts b/src/core/markdown/adapters/type-to-renderable.ts index 04b68b3e..72779db6 100644 --- a/src/core/markdown/adapters/type-to-renderable.ts +++ b/src/core/markdown/adapters/type-to-renderable.ts @@ -44,7 +44,7 @@ export function typeToRenderable( return interfaceTypeToInterfaceSource(type as InterfaceMirror, linkGenerator); case 'class': return classTypeToClassSource(type as ClassMirrorWithInheritanceChain, linkGenerator); - case 'sobject': + case 'customobject': return objectMetadataToRenderable(type as ObjectMetadata, config); } } @@ -259,7 +259,7 @@ function objectMetadataToRenderable( } return { - type: 'sobject', + type: 'customobject', headingLevel: 1, apiName: getApiName(), heading: objectMetadata.label, diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index 44a850b9..72d2a9dc 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -64,7 +64,7 @@ export function generateDocs(unparsedApexFiles: UnparsedSourceBundle[], config: ): (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle)[] { return sourceFiles.filter( (sourceFile): sourceFile is UnparsedCustomObjectBundle => - sourceFile.type === 'sobject' || sourceFile.type === 'customfield', + sourceFile.type === 'customobject' || sourceFile.type === 'customfield', ); } @@ -122,7 +122,7 @@ function generateForObject(objectBundles: (UnparsedCustomObjectBundle | Unparsed } const customObjects = objectBundles.filter( - (object): object is UnparsedCustomObjectBundle => object.type === 'sobject', + (object): object is UnparsedCustomObjectBundle => object.type === 'customobject', ); const customFields = objectBundles.filter( diff --git a/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts b/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts index 9c2a74e0..2d59b5d6 100644 --- a/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts +++ b/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts @@ -16,7 +16,7 @@ const sObjectContent = ` describe('when parsing SObject metadata', () => { test('the resulting type is "sobject"', async () => { const unparsed: UnparsedCustomObjectBundle = { - type: 'sobject', + type: 'customobject', name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, @@ -25,13 +25,13 @@ describe('when parsing SObject metadata', () => { const result = await reflectSObjectSources([unparsed])(); assertEither(result, (data) => expect(data.length).toBe(1)); - assertEither(result, (data) => expect(data[0].source.type).toBe('sobject')); - assertEither(result, (data) => expect(data[0].type.type_name).toBe('sobject')); + assertEither(result, (data) => expect(data[0].source.type).toBe('customobject')); + assertEither(result, (data) => expect(data[0].type.type_name).toBe('customobject')); }); test('the resulting type contains the file path', async () => { const unparsed: UnparsedCustomObjectBundle = { - type: 'sobject', + type: 'customobject', name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, @@ -44,7 +44,7 @@ describe('when parsing SObject metadata', () => { test('the resulting type contains the correct label', async () => { const unparsed: UnparsedCustomObjectBundle = { - type: 'sobject', + type: 'customobject', name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, @@ -59,7 +59,7 @@ describe('when parsing SObject metadata', () => { test('the resulting type contains the correct name', async () => { const unparsed: UnparsedCustomObjectBundle = { - type: 'sobject', + type: 'customobject', name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, @@ -74,7 +74,7 @@ describe('when parsing SObject metadata', () => { test('the resulting type contains the deployment status', async () => { const unparsed: UnparsedCustomObjectBundle = { - type: 'sobject', + type: 'customobject', name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, @@ -97,7 +97,7 @@ describe('when parsing SObject metadata', () => { `; const unparsed: UnparsedCustomObjectBundle = { - type: 'sobject', + type: 'customobject', name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, @@ -112,7 +112,7 @@ describe('when parsing SObject metadata', () => { test('the resulting type contains the visibility', async () => { const unparsed: UnparsedCustomObjectBundle = { - type: 'sobject', + type: 'customobject', name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, @@ -136,7 +136,7 @@ describe('when parsing SObject metadata', () => { `; const unparsed: UnparsedCustomObjectBundle = { - type: 'sobject', + type: 'customobject', name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, @@ -161,7 +161,7 @@ describe('when parsing SObject metadata', () => { `; const unparsed: UnparsedCustomObjectBundle = { - type: 'sobject', + type: 'customobject', name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, @@ -183,7 +183,7 @@ describe('when parsing SObject metadata', () => { `; const unparsed: UnparsedCustomObjectBundle = { - type: 'sobject', + type: 'customobject', name: 'MyFirstObject__c', filePath: 'src/object/MyFirstObject__c.object-meta.xml', content: sObjectContent, diff --git a/src/core/reflection/sobject/reflect-sobject-source.ts b/src/core/reflection/sobject/reflect-sobject-source.ts index eea0d55e..22213354 100644 --- a/src/core/reflection/sobject/reflect-sobject-source.ts +++ b/src/core/reflection/sobject/reflect-sobject-source.ts @@ -10,7 +10,7 @@ import * as E from 'fp-ts/Either'; import { CustomFieldMetadata } from './reflect-custom-field-source'; export type ObjectMetadata = { - type_name: 'sobject'; + type_name: 'customobject'; deploymentStatus: string; visibility: string; label: string; @@ -93,7 +93,7 @@ function addName(objectMetadata: ObjectMetadata, name: string): ObjectMetadata { function addTypeName(objectMetadata: ObjectMetadata): ObjectMetadata { return { ...objectMetadata, - type_name: 'sobject', + type_name: 'customobject', }; } diff --git a/src/core/renderables/types.d.ts b/src/core/renderables/types.d.ts index f8996083..db06b174 100644 --- a/src/core/renderables/types.d.ts +++ b/src/core/renderables/types.d.ts @@ -179,7 +179,7 @@ export type RenderableEnum = RenderableType & { export type RenderableCustomObject = Omit & { apiName: string; - type: 'sobject'; + type: 'customobject'; hasFields: boolean; fields: RenderableSection; }; diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index 04d58091..13764a68 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -60,7 +60,7 @@ export type UserDefinedConfig = UserDefinedMarkdownConfig | UserDefinedOpenApiCo export type UnparsedSourceBundle = UnparsedApexBundle | UnparsedCustomObjectBundle | UnparsedCustomFieldBundle; export type UnparsedCustomObjectBundle = { - type: 'sobject'; + type: 'customobject'; name: string; filePath: string; content: string; @@ -85,7 +85,7 @@ export type UnparsedApexBundle = { export type SourceFileMetadata = { filePath: string; name: string; - type: 'interface' | 'class' | 'enum' | 'sobject' | 'customfield'; + type: 'interface' | 'class' | 'enum' | 'customobject' | 'customfield'; }; export type ParsedFile< @@ -123,9 +123,10 @@ export type DocPageData = { outputDocPath: string; frontmatter: Frontmatter; content: string; + type: 'class' | 'interface' | 'enum' | 'customobject'; }; -export type OpenApiPageData = Omit; +export type OpenApiPageData = Omit; export type PageData = DocPageData | OpenApiPageData | ReferenceGuidePageData | ChangeLogPageData; diff --git a/src/core/shared/utils.ts b/src/core/shared/utils.ts index fa381678..f66b58f4 100644 --- a/src/core/shared/utils.ts +++ b/src/core/shared/utils.ts @@ -18,7 +18,7 @@ export function isSkip(value: unknown): value is Skip { } export function isObjectType(type: Type | ObjectMetadata | CustomFieldMetadata): type is ObjectMetadata { - return (type as ObjectMetadata).type_name === 'sobject'; + return (type as ObjectMetadata).type_name === 'customobject'; } export function isApexType(type: Type | ObjectMetadata | CustomFieldMetadata): type is Type { @@ -34,7 +34,7 @@ export function getTypeGroup(type: Type | ObjectMetadata, config: MarkdownGenera } switch (type.type_name) { - case 'sobject': + case 'customobject': return config.customObjectsGroupName; default: return getGroup(type, config); From 2791c8ac6e212b77e7d844a65e29f1ae5e9764d5 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sat, 19 Oct 2024 14:53:58 -0400 Subject: [PATCH 27/32] Vitepress sidebar improvements --- examples/vitepress/apexdocs.config.ts | 9 ++- .../vitepress/docs/.vitepress/sidebar.json | 66 +++++++++---------- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/examples/vitepress/apexdocs.config.ts b/examples/vitepress/apexdocs.config.ts index 8b389762..15db7a60 100644 --- a/examples/vitepress/apexdocs.config.ts +++ b/examples/vitepress/apexdocs.config.ts @@ -51,15 +51,22 @@ export default { }, excludeTags: ['internal'], transformDocs: async (docs) => { + const apexOnlyDocs = docs.filter((doc) => doc.type !== 'customobject'); + const objectOnlyDocs = docs.filter((doc) => doc.type === 'customobject'); + // Update sidebar const sidebar = [ { text: 'API Reference', - items: Array.from(extractGroups(docs)).map(([groupName, groupDocs]) => ({ + items: Array.from(extractGroups(apexOnlyDocs)).map(([groupName, groupDocs]) => ({ text: groupName, items: groupDocs.map(toSidebarLink), })), }, + { + text: 'Object Reference', + items: objectOnlyDocs.map(toSidebarLink), + }, ]; await writeFileAsync('./docs/.vitepress/sidebar.json', JSON.stringify(sidebar, null, 2)); diff --git a/examples/vitepress/docs/.vitepress/sidebar.json b/examples/vitepress/docs/.vitepress/sidebar.json index d4484518..dd5afb72 100644 --- a/examples/vitepress/docs/.vitepress/sidebar.json +++ b/examples/vitepress/docs/.vitepress/sidebar.json @@ -35,39 +35,6 @@ } ] }, - { - "text": "Custom Objects", - "items": [ - { - "text": "Event__c", - "link": "custom-objects/Event__c.md" - }, - { - "text": "Price_Component__c", - "link": "custom-objects/Price_Component__c.md" - }, - { - "text": "Product__c", - "link": "custom-objects/Product__c.md" - }, - { - "text": "Product_Price_Component__c", - "link": "custom-objects/Product_Price_Component__c.md" - }, - { - "text": "Sales_Order__c", - "link": "custom-objects/Sales_Order__c.md" - }, - { - "text": "Sales_Order_Line__c", - "link": "custom-objects/Sales_Order_Line__c.md" - }, - { - "text": "Speaker__c", - "link": "custom-objects/Speaker__c.md" - } - ] - }, { "text": "SampleGroup", "items": [ @@ -87,5 +54,38 @@ ] } ] + }, + { + "text": "Object Reference", + "items": [ + { + "text": "Event__c", + "link": "custom-objects/Event__c.md" + }, + { + "text": "Price_Component__c", + "link": "custom-objects/Price_Component__c.md" + }, + { + "text": "Product__c", + "link": "custom-objects/Product__c.md" + }, + { + "text": "Product_Price_Component__c", + "link": "custom-objects/Product_Price_Component__c.md" + }, + { + "text": "Sales_Order__c", + "link": "custom-objects/Sales_Order__c.md" + }, + { + "text": "Sales_Order_Line__c", + "link": "custom-objects/Sales_Order_Line__c.md" + }, + { + "text": "Speaker__c", + "link": "custom-objects/Speaker__c.md" + } + ] } ] \ No newline at end of file From 964e2c6fde1f884c78a4f2737a326d1abdb01a70 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sat, 19 Oct 2024 15:23:06 -0400 Subject: [PATCH 28/32] Improvements to how fields are rendered --- .../vitepress/docs/custom-objects/Event__c.md | 41 +++++++++++++++---- .../docs/custom-objects/Price_Component__c.md | 41 +++++++++++++++---- .../Product_Price_Component__c.md | 23 +++++++++-- .../docs/custom-objects/Product__c.md | 29 ++++++++++--- .../custom-objects/Sales_Order_Line__c.md | 41 +++++++++++++++---- .../docs/custom-objects/Speaker__c.md | 29 ++++++++++--- .../markdown/adapters/type-to-renderable.ts | 14 +++++-- .../templates/custom-object-template.ts | 22 +++++++--- 8 files changed, 197 insertions(+), 43 deletions(-) diff --git a/examples/vitepress/docs/custom-objects/Event__c.md b/examples/vitepress/docs/custom-objects/Event__c.md index d4975611..63c847bf 100644 --- a/examples/vitepress/docs/custom-objects/Event__c.md +++ b/examples/vitepress/docs/custom-objects/Event__c.md @@ -10,10 +10,37 @@ Represents an event that people can register for. `apexdocs__Event__c` ## Fields -| Field Name | Details | -|-------|-------------| -| **Description** | | -| **End Date** | | -| **Location** | | -| **Start Date** | | -| **Tag Line** | | \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldAPI NameDetails
Descriptionapexdocs__Description__c
End Dateapexdocs__End_Date__c
Locationapexdocs__Location__c
Start Dateapexdocs__Start_Date__c
Tag Lineapexdocs__Tag_Line__c
\ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Price_Component__c.md b/examples/vitepress/docs/custom-objects/Price_Component__c.md index 2a5523a8..003bc307 100644 --- a/examples/vitepress/docs/custom-objects/Price_Component__c.md +++ b/examples/vitepress/docs/custom-objects/Price_Component__c.md @@ -8,10 +8,37 @@ title: Price_Component__c `apexdocs__Price_Component__c` ## Fields -| Field Name | Details | -|-------|-------------| -| **Description** | | -| **Expression** | The Expression that determines if this price should take effect or not. | -| **Percent** | Use this field to calculate the price based on the list price's percentage instead of providing a flat price. | -| **Price** | Use this when the Price Component represents a Flat Price. To represent a Percentage use the Percent field. | -| **Type** | | \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldAPI NameDetails
Descriptionapexdocs__Description__c
Expressionapexdocs__Expression__cThe Expression that determines if this price should take effect or not.
Percentapexdocs__Percent__cUse this field to calculate the price based on the list price's percentage instead of providing a flat price.
Priceapexdocs__Price__cUse this when the Price Component represents a Flat Price. To represent a Percentage use the Percent field.
Typeapexdocs__Type__c
\ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md b/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md index ab9b3c0f..e1538a61 100644 --- a/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md +++ b/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md @@ -8,7 +8,22 @@ title: Product_Price_Component__c `apexdocs__Product_Price_Component__c` ## Fields -| Field Name | Details | -|-------|-------------| -| **Price Component** | | -| **Product** | | \ No newline at end of file + + + + + + + + + + + + + + + + + + +
FieldAPI NameDetails
Price Componentapexdocs__Price_Component__c
Productapexdocs__Product__c
\ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Product__c.md b/examples/vitepress/docs/custom-objects/Product__c.md index 4e38a604..79b8bd5a 100644 --- a/examples/vitepress/docs/custom-objects/Product__c.md +++ b/examples/vitepress/docs/custom-objects/Product__c.md @@ -10,8 +10,27 @@ Product that is sold or available for sale. `apexdocs__Product__c` ## Fields -| Field Name | Details | -|-------|-------------| -| **Description** | | -| **Event** | | -| **Features** | | \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + +
FieldAPI NameDetails
Descriptionapexdocs__Description__c
Eventapexdocs__Event__c
Featuresapexdocs__Features__c
\ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md b/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md index be2e7728..5bfd9da1 100644 --- a/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md +++ b/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md @@ -10,10 +10,37 @@ Represents a line item on a sales order. `apexdocs__Sales_Order_Line__c` ## Fields -| Field Name | Details | -|-------|-------------| -| **Amount** | | -| **Product** | | -| **Sales Order** | | -| **Source Price Component** | | -| **Type** | | \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldAPI NameDetails
Amountapexdocs__Amount__c
Productapexdocs__Product__c
Sales Orderapexdocs__Sales_Order__c
Source Price Componentapexdocs__Source_Price_Component__c
Typeapexdocs__Type__c
\ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Speaker__c.md b/examples/vitepress/docs/custom-objects/Speaker__c.md index b559bbf4..5cfda86b 100644 --- a/examples/vitepress/docs/custom-objects/Speaker__c.md +++ b/examples/vitepress/docs/custom-objects/Speaker__c.md @@ -10,8 +10,27 @@ Represents a speaker at an event. `apexdocs__Speaker__c` ## Fields -| Field Name | Details | -|-------|-------------| -| **About** | | -| **Event** | | -| **Person** | | \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + +
FieldAPI NameDetails
Aboutapexdocs__About__c
Eventapexdocs__Event__c
Personapexdocs__Person__c
\ No newline at end of file diff --git a/src/core/markdown/adapters/type-to-renderable.ts b/src/core/markdown/adapters/type-to-renderable.ts index 72779db6..774e71e5 100644 --- a/src/core/markdown/adapters/type-to-renderable.ts +++ b/src/core/markdown/adapters/type-to-renderable.ts @@ -12,6 +12,7 @@ import { GetRenderableContentByTypeName, RenderableCustomObject, RenderableCustomField, + RenderableContent, } from '../../renderables/types'; import { adaptDescribable, adaptDocumentable } from '../../renderables/documentables'; import { adaptConstructor, adaptMethod } from './methods-and-constructors'; @@ -272,16 +273,23 @@ function objectMetadataToRenderable( fields: { headingLevel: 2, heading: 'Fields', - value: objectMetadata.fields.map((field) => fieldMetadataToRenderable(field.type)), + value: objectMetadata.fields.map((field) => fieldMetadataToRenderable(field.type, config)), }, }; } -function fieldMetadataToRenderable(field: CustomFieldMetadata): RenderableCustomField { +function fieldMetadataToRenderable(field: CustomFieldMetadata, config: MarkdownGeneratorConfig): RenderableCustomField { + function getApiName() { + if (config.namespace) { + return `${config.namespace}__${field.name}`; + } + return field.name; + } + return { label: field.label, description: field.description ? [field.description] : [], - apiName: field.name, + apiName: getApiName(), type: 'field', }; } diff --git a/src/core/markdown/templates/custom-object-template.ts b/src/core/markdown/templates/custom-object-template.ts index 3f838177..2a0121c0 100644 --- a/src/core/markdown/templates/custom-object-template.ts +++ b/src/core/markdown/templates/custom-object-template.ts @@ -8,11 +8,23 @@ export const customObjectTemplate = ` {{#if hasFields}} {{ heading fields.headingLevel fields.heading }} -| Field Name | Details | -|-------|-------------| -{{#each fields.value}} -| **{{ label }}** | {{{renderContent description}}} | -{{/each}} + + + + + + + + {{#each fields.value}} + + + + + + {{/each}} + +
FieldAPI NameDetails
{{label}}{{{apiName}}}{{{renderContent description}}}
+ {{/if}} `.trim(); From d602b3158adca2c445e3a57e06ac4567b33023ec Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sun, 20 Oct 2024 06:36:08 -0400 Subject: [PATCH 29/32] Validating CustomField metadata --- src/core/markdown/adapters/reference-guide.ts | 2 +- .../markdown/adapters/renderable-bundle.ts | 2 +- .../markdown/adapters/type-to-renderable.ts | 3 +- src/core/markdown/generate-docs.ts | 4 +- .../reflect-custom-field-sources.spec.ts | 202 ++++++++++++++++++ ... => reflect-custom-object-sources.spec.ts} | 35 +-- .../sobject/reflect-custom-field-source.ts | 39 +++- ...ce.ts => reflect-custom-object-sources.ts} | 6 +- src/core/shared/types.d.ts | 2 +- src/core/shared/utils.ts | 2 +- 10 files changed, 257 insertions(+), 40 deletions(-) create mode 100644 src/core/reflection/sobject/__test__/reflect-custom-field-sources.spec.ts rename src/core/reflection/sobject/__test__/{reflect-sobject-source.spec.ts => reflect-custom-object-sources.spec.ts} (82%) rename src/core/reflection/sobject/{reflect-sobject-source.ts => reflect-custom-object-sources.ts} (95%) diff --git a/src/core/markdown/adapters/reference-guide.ts b/src/core/markdown/adapters/reference-guide.ts index cea15919..81804530 100644 --- a/src/core/markdown/adapters/reference-guide.ts +++ b/src/core/markdown/adapters/reference-guide.ts @@ -1,7 +1,7 @@ import { MarkdownGeneratorConfig } from '../generate-docs'; import { DocPageReference, ParsedFile } from '../../shared/types'; import { getTypeGroup } from '../../shared/utils'; -import { ObjectMetadata } from '../../reflection/sobject/reflect-sobject-source'; +import { ObjectMetadata } from '../../reflection/sobject/reflect-custom-object-sources'; import { Type } from '@cparra/apex-reflection'; export function parsedFilesToReferenceGuide( diff --git a/src/core/markdown/adapters/renderable-bundle.ts b/src/core/markdown/adapters/renderable-bundle.ts index 46ac7ce2..af4cd633 100644 --- a/src/core/markdown/adapters/renderable-bundle.ts +++ b/src/core/markdown/adapters/renderable-bundle.ts @@ -13,7 +13,7 @@ import { apply } from '#utils/fp'; import { generateLink } from './generate-link'; import { getTypeGroup } from '../../shared/utils'; import { Type } from '@cparra/apex-reflection'; -import { ObjectMetadata } from '../../reflection/sobject/reflect-sobject-source'; +import { ObjectMetadata } from '../../reflection/sobject/reflect-custom-object-sources'; export function parsedFilesToRenderableBundle( config: MarkdownGeneratorConfig, diff --git a/src/core/markdown/adapters/type-to-renderable.ts b/src/core/markdown/adapters/type-to-renderable.ts index 774e71e5..5dcd9c9e 100644 --- a/src/core/markdown/adapters/type-to-renderable.ts +++ b/src/core/markdown/adapters/type-to-renderable.ts @@ -19,7 +19,7 @@ import { adaptConstructor, adaptMethod } from './methods-and-constructors'; import { adaptFieldOrProperty } from './fields-and-properties'; import { MarkdownGeneratorConfig } from '../generate-docs'; import { SourceFileMetadata } from '../../shared/types'; -import { ObjectMetadata } from '../../reflection/sobject/reflect-sobject-source'; +import { ObjectMetadata } from '../../reflection/sobject/reflect-custom-object-sources'; import { getTypeGroup } from '../../shared/utils'; import { CustomFieldMetadata } from '../../reflection/sobject/reflect-custom-field-source'; @@ -53,7 +53,6 @@ export function typeToRenderable( return { ...(getRenderable() as GetReturnRenderable), filePath: parsedFile.source.filePath, - // TODO: How to handle namespace for sobjects? Remember that only custom objects should get them (as opposed to standard) namespace: config.namespace, }; } diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index 72d2a9dc..7bad3852 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -34,7 +34,7 @@ import { isSkip } from '../shared/utils'; import { parsedFilesToReferenceGuide } from './adapters/reference-guide'; import { removeExcludedTags } from '../reflection/apex/remove-excluded-tags'; import { HookError, ReflectionErrors } from '../errors/errors'; -import { ObjectMetadata, reflectSObjectSources } from '../reflection/sobject/reflect-sobject-source'; +import { ObjectMetadata, reflectCustomObjectSources } from '../reflection/sobject/reflect-custom-object-sources'; import { CustomFieldMetadata, reflectCustomFieldSources } from '../reflection/sobject/reflect-custom-field-source'; import { Type } from '@cparra/apex-reflection'; @@ -137,7 +137,7 @@ function generateForObject(objectBundles: (UnparsedCustomObjectBundle | Unparsed return pipe( customObjects, - reflectSObjectSources, + reflectCustomObjectSources, TE.map(filterNonPublished), TE.map(filterNonPublic), TE.bindTo('objects'), diff --git a/src/core/reflection/sobject/__test__/reflect-custom-field-sources.spec.ts b/src/core/reflection/sobject/__test__/reflect-custom-field-sources.spec.ts new file mode 100644 index 00000000..0eba7e3c --- /dev/null +++ b/src/core/reflection/sobject/__test__/reflect-custom-field-sources.spec.ts @@ -0,0 +1,202 @@ +import { UnparsedCustomFieldBundle } from '../../../shared/types'; +import { reflectCustomFieldSources } from '../reflect-custom-field-source'; +import { assertEither } from '../../../test-helpers/assert-either'; +import * as E from 'fp-ts/Either'; + +const customFieldContent = ` + + + PhotoUrl__c + false + + false + false + Url + A Photo URL field +`; + +describe('when parsing custom field metadata', () => { + test('the resulting type contains the file path', async () => { + const unparsed: UnparsedCustomFieldBundle = { + type: 'customfield', + name: 'PhotoUrl__c', + parentName: 'MyFirstObject__c', + filePath: 'src/field/PhotoUrl__c.field-meta.xml', + content: customFieldContent, + }; + + const result = await reflectCustomFieldSources([unparsed])(); + + assertEither(result, (data) => expect(data[0].source.filePath).toBe('src/field/PhotoUrl__c.field-meta.xml')); + }); + + test('the resulting type contains the correct name', async () => { + const unparsed: UnparsedCustomFieldBundle = { + type: 'customfield', + name: 'PhotoUrl__c', + parentName: 'MyFirstObject__c', + filePath: 'src/field/PhotoUrl__c.field-meta.xml', + content: customFieldContent, + }; + + const result = await reflectCustomFieldSources([unparsed])(); + + assertEither(result, (data) => expect(data[0].type.name).toBe('PhotoUrl__c')); + }); + + test('the resulting type contains the correct parent name', async () => { + const unparsed: UnparsedCustomFieldBundle = { + type: 'customfield', + name: 'PhotoUrl__c', + parentName: 'MyFirstObject__c', + filePath: 'src/field/PhotoUrl__c.field-meta.xml', + content: customFieldContent, + }; + + const result = await reflectCustomFieldSources([unparsed])(); + + assertEither(result, (data) => expect(data[0].type.parentName).toBe('MyFirstObject__c')); + }); + + test('the resulting type contains the correct label', async () => { + const unparsed: UnparsedCustomFieldBundle = { + type: 'customfield', + name: 'PhotoUrl__c', + parentName: 'MyFirstObject__c', + filePath: 'src/field/PhotoUrl__c.field-meta.xml', + content: customFieldContent, + }; + + const result = await reflectCustomFieldSources([unparsed])(); + + assertEither(result, (data) => expect(data[0].type.label).toBe('PhotoUrl')); + }); + + test('the resulting type contains the correct type', async () => { + const unparsed: UnparsedCustomFieldBundle = { + type: 'customfield', + name: 'PhotoUrl__c', + parentName: 'MyFirstObject__c', + filePath: 'src/field/PhotoUrl__c.field-meta.xml', + content: customFieldContent, + }; + + const result = await reflectCustomFieldSources([unparsed])(); + + assertEither(result, (data) => expect(data[0].type.type).toBe('Url')); + }); + + test('the resulting type contains the correct description', async () => { + const unparsed: UnparsedCustomFieldBundle = { + type: 'customfield', + name: 'PhotoUrl__c', + parentName: 'MyFirstObject__c', + filePath: 'src/field/PhotoUrl__c.field-meta.xml', + content: customFieldContent, + }; + + const result = await reflectCustomFieldSources([unparsed])(); + + assertEither(result, (data) => expect(data[0].type.description).toBe('A Photo URL field')); + }); + + test('An error is returned when the XML is in an invalid format', async () => { + const unparsed: UnparsedCustomFieldBundle = { + type: 'customfield', + name: 'PhotoUrl__c', + parentName: 'MyFirstObject__c', + filePath: 'src/field/PhotoUrl__c.field-meta.xml', + content: 'invalid-xml', + }; + + const result = await reflectCustomFieldSources([unparsed])(); + + expect(E.isLeft(result)).toBe(true); + }); + + test('An error is returned when the XML is missing the CustomField key', async () => { + const unparsed: UnparsedCustomFieldBundle = { + type: 'customfield', + name: 'PhotoUrl__c', + parentName: 'MyFirstObject__c', + filePath: 'src/field/PhotoUrl__c.field-meta.xml', + content: ` + + + PhotoUrl__c + false + + false + false + Url + A Photo URL field + `, + }; + + const result = await reflectCustomFieldSources([unparsed])(); + + expect(E.isLeft(result)).toBe(true); + }); + + test('An error is returned when the CustomField key is not an object', async () => { + const unparsed: UnparsedCustomFieldBundle = { + type: 'customfield', + name: 'PhotoUrl__c', + parentName: 'MyFirstObject__c', + filePath: 'src/field/PhotoUrl__c.field-meta.xml', + content: ` + + invalid`, + }; + + const result = await reflectCustomFieldSources([unparsed])(); + + expect(E.isLeft(result)).toBe(true); + }); + + test('An error is returned when the CustomKey object does not contain the label key', async () => { + const unparsed: UnparsedCustomFieldBundle = { + type: 'customfield', + name: 'PhotoUrl__c', + parentName: 'MyFirstObject__c', + filePath: 'src/field/PhotoUrl__c.field-meta.xml', + content: ` + + + PhotoUrl__c + false + false + false + Url + A Photo URL field + `, + }; + + const result = await reflectCustomFieldSources([unparsed])(); + + expect(E.isLeft(result)).toBe(true); + }); + + test('An error is returned when the CustomKey object does not contain the type key', async () => { + const unparsed: UnparsedCustomFieldBundle = { + type: 'customfield', + name: 'PhotoUrl__c', + parentName: 'MyFirstObject__c', + filePath: 'src/field/PhotoUrl__c.field-meta.xml', + content: ` + + + PhotoUrl__c + false + + false + false + A Photo URL field + `, + }; + + const result = await reflectCustomFieldSources([unparsed])(); + + expect(E.isLeft(result)).toBe(true); + }); +}); diff --git a/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts b/src/core/reflection/sobject/__test__/reflect-custom-object-sources.spec.ts similarity index 82% rename from src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts rename to src/core/reflection/sobject/__test__/reflect-custom-object-sources.spec.ts index 2d59b5d6..f923feee 100644 --- a/src/core/reflection/sobject/__test__/reflect-sobject-source.spec.ts +++ b/src/core/reflection/sobject/__test__/reflect-custom-object-sources.spec.ts @@ -1,4 +1,4 @@ -import { reflectSObjectSources } from '../reflect-sobject-source'; +import { reflectCustomObjectSources } from '../reflect-custom-object-sources'; import { UnparsedCustomObjectBundle } from '../../../shared/types'; import { assertEither } from '../../../test-helpers/assert-either'; import * as E from 'fp-ts/Either'; @@ -14,21 +14,6 @@ const sObjectContent = ` `; describe('when parsing SObject metadata', () => { - test('the resulting type is "sobject"', async () => { - const unparsed: UnparsedCustomObjectBundle = { - type: 'customobject', - name: 'MyFirstObject__c', - filePath: 'src/object/MyFirstObject__c.object-meta.xml', - content: sObjectContent, - }; - - const result = await reflectSObjectSources([unparsed])(); - - assertEither(result, (data) => expect(data.length).toBe(1)); - assertEither(result, (data) => expect(data[0].source.type).toBe('customobject')); - assertEither(result, (data) => expect(data[0].type.type_name).toBe('customobject')); - }); - test('the resulting type contains the file path', async () => { const unparsed: UnparsedCustomObjectBundle = { type: 'customobject', @@ -37,7 +22,7 @@ describe('when parsing SObject metadata', () => { content: sObjectContent, }; - const result = await reflectSObjectSources([unparsed])(); + const result = await reflectCustomObjectSources([unparsed])(); assertEither(result, (data) => expect(data[0].source.filePath).toBe('src/object/MyFirstObject__c.object-meta.xml')); }); @@ -50,7 +35,7 @@ describe('when parsing SObject metadata', () => { content: sObjectContent, }; - const result = await reflectSObjectSources([unparsed])(); + const result = await reflectCustomObjectSources([unparsed])(); assertEither(result, (data) => { expect(data[0].type.label).toBe('MyFirstObject'); @@ -65,7 +50,7 @@ describe('when parsing SObject metadata', () => { content: sObjectContent, }; - const result = await reflectSObjectSources([unparsed])(); + const result = await reflectCustomObjectSources([unparsed])(); assertEither(result, (data) => { expect(data[0].type.name).toBe('MyFirstObject__c'); @@ -80,7 +65,7 @@ describe('when parsing SObject metadata', () => { content: sObjectContent, }; - const result = await reflectSObjectSources([unparsed])(); + const result = await reflectCustomObjectSources([unparsed])(); assertEither(result, (data) => { expect(data[0].type.deploymentStatus).toBe('Deployed'); @@ -103,7 +88,7 @@ describe('when parsing SObject metadata', () => { content: sObjectContent, }; - const result = await reflectSObjectSources([unparsed])(); + const result = await reflectCustomObjectSources([unparsed])(); assertEither(result, (data) => { expect(data[0].type.deploymentStatus).toBe('Deployed'); @@ -118,7 +103,7 @@ describe('when parsing SObject metadata', () => { content: sObjectContent, }; - const result = await reflectSObjectSources([unparsed])(); + const result = await reflectCustomObjectSources([unparsed])(); assertEither(result, (data) => { expect(data[0].type.visibility).toBe('Public'); @@ -142,7 +127,7 @@ describe('when parsing SObject metadata', () => { content: sObjectContent, }; - const result = await reflectSObjectSources([unparsed])(); + const result = await reflectCustomObjectSources([unparsed])(); assertEither(result, (data) => { expect(data[0].type.visibility).toBe('Public'); @@ -167,7 +152,7 @@ describe('when parsing SObject metadata', () => { content: sObjectContent, }; - const result = await reflectSObjectSources([unparsed])(); + const result = await reflectCustomObjectSources([unparsed])(); expect(E.isLeft(result)).toBe(true); }); @@ -189,7 +174,7 @@ describe('when parsing SObject metadata', () => { content: sObjectContent, }; - const result = await reflectSObjectSources([unparsed])(); + const result = await reflectCustomObjectSources([unparsed])(); expect(E.isLeft(result)).toBe(true); }); diff --git a/src/core/reflection/sobject/reflect-custom-field-source.ts b/src/core/reflection/sobject/reflect-custom-field-source.ts index 13768483..49f6bacb 100644 --- a/src/core/reflection/sobject/reflect-custom-field-source.ts +++ b/src/core/reflection/sobject/reflect-custom-field-source.ts @@ -13,11 +13,9 @@ export type CustomFieldMetadata = { description: string | null; name: string; label: string; + // TODO: Make sure the type is displayed in the end result type: string; parentName: string; - - // TODO: Whatever else we want to put around fields - // TODO: E.g. if it is required, picklist values, etc. }; export function reflectCustomFieldSources( @@ -36,7 +34,7 @@ function reflectCustomFieldSource( ): TE.TaskEither> { return pipe( E.tryCatch(() => new XMLParser().parse(customFieldSource.content), E.toError), - // TODO: Validate + E.flatMap(validate), E.map(toCustomFieldMetadata), E.map((metadata) => addName(metadata, customFieldSource.name)), E.map((metadata) => addParentName(metadata, customFieldSource.parentName)), @@ -46,6 +44,39 @@ function reflectCustomFieldSource( ); } +function validate(parsedResult: unknown): E.Either { + const err = E.left(new Error('Invalid custom field metadata')); + + function isObject(value: unknown) { + return typeof value === 'object' && value !== null ? E.right(value) : err; + } + + function hasTheCustomFieldKey(value: object) { + return 'CustomField' in value ? E.right(value) : err; + } + + function theCustomFieldKeyIsAnObject(value: Record<'CustomField', unknown>) { + return typeof value.CustomField === 'object' ? E.right(value as Record<'CustomField', object>) : err; + } + + function theCustomFieldObjectContainsTheLabelKey(value: Record<'CustomField', object>) { + return 'label' in value.CustomField ? E.right(value) : err; + } + + function theCustomFieldObjectContainsTheTypeKey(value: Record<'CustomField', object>) { + return 'type' in value.CustomField ? E.right(value) : err; + } + + return pipe( + parsedResult, + isObject, + E.chain(hasTheCustomFieldKey), + E.chain(theCustomFieldKeyIsAnObject), + E.chain(theCustomFieldObjectContainsTheLabelKey), + E.chain(theCustomFieldObjectContainsTheTypeKey), + ); +} + function toCustomFieldMetadata(parserResult: { CustomField: object }): CustomFieldMetadata { const defaultValues = { description: null, diff --git a/src/core/reflection/sobject/reflect-sobject-source.ts b/src/core/reflection/sobject/reflect-custom-object-sources.ts similarity index 95% rename from src/core/reflection/sobject/reflect-sobject-source.ts rename to src/core/reflection/sobject/reflect-custom-object-sources.ts index 22213354..e301c4c1 100644 --- a/src/core/reflection/sobject/reflect-sobject-source.ts +++ b/src/core/reflection/sobject/reflect-custom-object-sources.ts @@ -19,7 +19,7 @@ export type ObjectMetadata = { fields: ParsedFile[]; }; -export function reflectSObjectSources( +export function reflectCustomObjectSources( objectSources: UnparsedCustomObjectBundle[], ): TE.TaskEither[]> { const semiGroupReflectionError: Semigroup = { @@ -27,10 +27,10 @@ export function reflectSObjectSources( }; const Ap = TE.getApplicativeTaskValidation(T.ApplyPar, semiGroupReflectionError); - return pipe(objectSources, A.traverse(Ap)(reflectSObjectSource)); + return pipe(objectSources, A.traverse(Ap)(reflectCustomObjectSource)); } -function reflectSObjectSource( +function reflectCustomObjectSource( objectSource: UnparsedCustomObjectBundle, ): TE.TaskEither> { return pipe( diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index 13764a68..9fa50f5e 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -1,6 +1,6 @@ import { Type } from '@cparra/apex-reflection'; import { ChangeLogPageData } from '../changelog/generate-change-log'; -import { ObjectMetadata } from '../reflection/sobject/reflect-sobject-source'; +import { ObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources'; import { CustomFieldMetadata } from '../reflection/sobject/reflect-custom-field-source'; export type Generators = 'markdown' | 'openapi' | 'changelog'; diff --git a/src/core/shared/utils.ts b/src/core/shared/utils.ts index f66b58f4..12982c74 100644 --- a/src/core/shared/utils.ts +++ b/src/core/shared/utils.ts @@ -1,6 +1,6 @@ import { Skip } from './types'; import { Type } from '@cparra/apex-reflection'; -import { ObjectMetadata } from '../reflection/sobject/reflect-sobject-source'; +import { ObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources'; import { MarkdownGeneratorConfig } from '../markdown/generate-docs'; import { CustomFieldMetadata } from '../reflection/sobject/reflect-custom-field-source'; From 83fc58c9acbb0b172ef7974c2421d3ab7b2853c0 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sun, 20 Oct 2024 07:05:10 -0400 Subject: [PATCH 30/32] Displaying type in the end result --- .../vitepress/docs/custom-objects/Event__c.md | 87 ++++++++++------- .../docs/custom-objects/Price_Component__c.md | 93 ++++++++++++------- .../Product_Price_Component__c.md | 39 ++++---- .../docs/custom-objects/Product__c.md | 55 ++++++----- .../custom-objects/Sales_Order_Line__c.md | 87 ++++++++++------- .../docs/custom-objects/Speaker__c.md | 55 ++++++----- .../markdown/adapters/type-to-renderable.ts | 46 +++++---- .../templates/custom-object-template.ts | 32 +++---- .../sobject/reflect-custom-field-source.ts | 1 - src/core/renderables/types.d.ts | 6 +- 10 files changed, 292 insertions(+), 209 deletions(-) diff --git a/examples/vitepress/docs/custom-objects/Event__c.md b/examples/vitepress/docs/custom-objects/Event__c.md index 63c847bf..892379a4 100644 --- a/examples/vitepress/docs/custom-objects/Event__c.md +++ b/examples/vitepress/docs/custom-objects/Event__c.md @@ -10,37 +10,56 @@ Represents an event that people can register for. `apexdocs__Event__c` ## Fields - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldAPI NameDetails
Descriptionapexdocs__Description__c
End Dateapexdocs__End_Date__c
Locationapexdocs__Location__c
Start Dateapexdocs__Start_Date__c
Tag Lineapexdocs__Tag_Line__c
\ No newline at end of file +### Description + +**API Name** + +`apexdocs__Description__c` + +**Type** + +*LongTextArea* + +--- +### End Date + +**API Name** + +`apexdocs__End_Date__c` + +**Type** + +*Date* + +--- +### Location + +**API Name** + +`apexdocs__Location__c` + +**Type** + +*Location* + +--- +### Start Date + +**API Name** + +`apexdocs__Start_Date__c` + +**Type** + +*Date* + +--- +### Tag Line + +**API Name** + +`apexdocs__Tag_Line__c` + +**Type** + +*Text* \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Price_Component__c.md b/examples/vitepress/docs/custom-objects/Price_Component__c.md index 003bc307..053ccb3c 100644 --- a/examples/vitepress/docs/custom-objects/Price_Component__c.md +++ b/examples/vitepress/docs/custom-objects/Price_Component__c.md @@ -8,37 +8,62 @@ title: Price_Component__c `apexdocs__Price_Component__c` ## Fields - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldAPI NameDetails
Descriptionapexdocs__Description__c
Expressionapexdocs__Expression__cThe Expression that determines if this price should take effect or not.
Percentapexdocs__Percent__cUse this field to calculate the price based on the list price's percentage instead of providing a flat price.
Priceapexdocs__Price__cUse this when the Price Component represents a Flat Price. To represent a Percentage use the Percent field.
Typeapexdocs__Type__c
\ No newline at end of file +### Description + +**API Name** + +`apexdocs__Description__c` + +**Type** + +*Text* + +--- +### Expression + +The Expression that determines if this price should take effect or not. + +**API Name** + +`apexdocs__Expression__c` + +**Type** + +*LongTextArea* + +--- +### Percent + +Use this field to calculate the price based on the list price's percentage instead of providing a flat price. + +**API Name** + +`apexdocs__Percent__c` + +**Type** + +*Percent* + +--- +### Price + +Use this when the Price Component represents a Flat Price. To represent a Percentage use the Percent field. + +**API Name** + +`apexdocs__Price__c` + +**Type** + +*Currency* + +--- +### Type + +**API Name** + +`apexdocs__Type__c` + +**Type** + +*Picklist* \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md b/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md index e1538a61..cfe9dffa 100644 --- a/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md +++ b/examples/vitepress/docs/custom-objects/Product_Price_Component__c.md @@ -8,22 +8,23 @@ title: Product_Price_Component__c `apexdocs__Product_Price_Component__c` ## Fields - - - - - - - - - - - - - - - - - - -
FieldAPI NameDetails
Price Componentapexdocs__Price_Component__c
Productapexdocs__Product__c
\ No newline at end of file +### Price Component + +**API Name** + +`apexdocs__Price_Component__c` + +**Type** + +*MasterDetail* + +--- +### Product + +**API Name** + +`apexdocs__Product__c` + +**Type** + +*MasterDetail* \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Product__c.md b/examples/vitepress/docs/custom-objects/Product__c.md index 79b8bd5a..ba3e70d6 100644 --- a/examples/vitepress/docs/custom-objects/Product__c.md +++ b/examples/vitepress/docs/custom-objects/Product__c.md @@ -10,27 +10,34 @@ Product that is sold or available for sale. `apexdocs__Product__c` ## Fields - - - - - - - - - - - - - - - - - - - - - - - -
FieldAPI NameDetails
Descriptionapexdocs__Description__c
Eventapexdocs__Event__c
Featuresapexdocs__Features__c
\ No newline at end of file +### Description + +**API Name** + +`apexdocs__Description__c` + +**Type** + +*Text* + +--- +### Event + +**API Name** + +`apexdocs__Event__c` + +**Type** + +*Lookup* + +--- +### Features + +**API Name** + +`apexdocs__Features__c` + +**Type** + +*LongTextArea* \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md b/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md index 5bfd9da1..041e94ce 100644 --- a/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md +++ b/examples/vitepress/docs/custom-objects/Sales_Order_Line__c.md @@ -10,37 +10,56 @@ Represents a line item on a sales order. `apexdocs__Sales_Order_Line__c` ## Fields - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldAPI NameDetails
Amountapexdocs__Amount__c
Productapexdocs__Product__c
Sales Orderapexdocs__Sales_Order__c
Source Price Componentapexdocs__Source_Price_Component__c
Typeapexdocs__Type__c
\ No newline at end of file +### Amount + +**API Name** + +`apexdocs__Amount__c` + +**Type** + +*Currency* + +--- +### Product + +**API Name** + +`apexdocs__Product__c` + +**Type** + +*Lookup* + +--- +### Sales Order + +**API Name** + +`apexdocs__Sales_Order__c` + +**Type** + +*MasterDetail* + +--- +### Source Price Component + +**API Name** + +`apexdocs__Source_Price_Component__c` + +**Type** + +*Lookup* + +--- +### Type + +**API Name** + +`apexdocs__Type__c` + +**Type** + +*Picklist* \ No newline at end of file diff --git a/examples/vitepress/docs/custom-objects/Speaker__c.md b/examples/vitepress/docs/custom-objects/Speaker__c.md index 5cfda86b..093efbf4 100644 --- a/examples/vitepress/docs/custom-objects/Speaker__c.md +++ b/examples/vitepress/docs/custom-objects/Speaker__c.md @@ -10,27 +10,34 @@ Represents a speaker at an event. `apexdocs__Speaker__c` ## Fields - - - - - - - - - - - - - - - - - - - - - - - -
FieldAPI NameDetails
Aboutapexdocs__About__c
Eventapexdocs__Event__c
Personapexdocs__Person__c
\ No newline at end of file +### About + +**API Name** + +`apexdocs__About__c` + +**Type** + +*LongTextArea* + +--- +### Event + +**API Name** + +`apexdocs__Event__c` + +**Type** + +*MasterDetail* + +--- +### Person + +**API Name** + +`apexdocs__Person__c` + +**Type** + +*MasterDetail* \ No newline at end of file diff --git a/src/core/markdown/adapters/type-to-renderable.ts b/src/core/markdown/adapters/type-to-renderable.ts index 5dcd9c9e..dceedbfb 100644 --- a/src/core/markdown/adapters/type-to-renderable.ts +++ b/src/core/markdown/adapters/type-to-renderable.ts @@ -251,17 +251,10 @@ function objectMetadataToRenderable( objectMetadata: ObjectMetadata, config: MarkdownGeneratorConfig, ): RenderableCustomObject { - function getApiName() { - if (config.namespace) { - return `${config.namespace}__${objectMetadata.name}`; - } - return objectMetadata.name; - } - return { type: 'customobject', headingLevel: 1, - apiName: getApiName(), + apiName: getApiName(objectMetadata.name, config), heading: objectMetadata.label, name: objectMetadata.name, doc: { @@ -272,23 +265,36 @@ function objectMetadataToRenderable( fields: { headingLevel: 2, heading: 'Fields', - value: objectMetadata.fields.map((field) => fieldMetadataToRenderable(field.type, config)), + value: objectMetadata.fields.map((field) => fieldMetadataToRenderable(field.type, config, 3)), }, }; } -function fieldMetadataToRenderable(field: CustomFieldMetadata, config: MarkdownGeneratorConfig): RenderableCustomField { - function getApiName() { - if (config.namespace) { - return `${config.namespace}__${field.name}`; - } - return field.name; - } - +function fieldMetadataToRenderable( + field: CustomFieldMetadata, + config: MarkdownGeneratorConfig, + headingLevel: number, +): RenderableCustomField { return { - label: field.label, - description: field.description ? [field.description] : [], - apiName: getApiName(), type: 'field', + headingLevel: headingLevel, + heading: field.label, + description: field.description ? [field.description] : [], + apiName: getApiName(field.name, config), + fieldType: field.type, }; } + +function getApiName(currentName: string, config: MarkdownGeneratorConfig) { + if (config.namespace) { + // first remove any `__c` suffix + const name = currentName.replace(/__c$/, ''); + // if the name still has an __, it's already namespaced + if (name.includes('__')) { + return name; + } + + return `${config.namespace}__${currentName}`; + } + return currentName; +} diff --git a/src/core/markdown/templates/custom-object-template.ts b/src/core/markdown/templates/custom-object-template.ts index 2a0121c0..ff0af90b 100644 --- a/src/core/markdown/templates/custom-object-template.ts +++ b/src/core/markdown/templates/custom-object-template.ts @@ -8,23 +8,23 @@ export const customObjectTemplate = ` {{#if hasFields}} {{ heading fields.headingLevel fields.heading }} - - - - - - - - {{#each fields.value}} - - - - - - {{/each}} - -
FieldAPI NameDetails
{{label}}{{{apiName}}}{{{renderContent description}}}
+{{#each fields.value}} +{{ heading headingLevel heading }} + +{{#if description}} +{{{renderContent description}}} +{{/if}} + +**API Name** + +\`{{{apiName}}}\` + +**Type** + +*{{fieldType}}* +{{#unless @last}}---{{/unless}} +{{/each}} {{/if}} `.trim(); diff --git a/src/core/reflection/sobject/reflect-custom-field-source.ts b/src/core/reflection/sobject/reflect-custom-field-source.ts index 49f6bacb..a0affa80 100644 --- a/src/core/reflection/sobject/reflect-custom-field-source.ts +++ b/src/core/reflection/sobject/reflect-custom-field-source.ts @@ -13,7 +13,6 @@ export type CustomFieldMetadata = { description: string | null; name: string; label: string; - // TODO: Make sure the type is displayed in the end result type: string; parentName: string; }; diff --git a/src/core/renderables/types.d.ts b/src/core/renderables/types.d.ts index db06b174..ddf50728 100644 --- a/src/core/renderables/types.d.ts +++ b/src/core/renderables/types.d.ts @@ -185,12 +185,12 @@ export type RenderableCustomObject = Omit & { }; export type RenderableCustomField = { - label: string; + headingLevel: number; + heading: string; apiName: string; description: RenderableContent[]; type: 'field'; - - // TODO: Whatever else we want to add to the field descriptions + fieldType: string; }; export type Renderable = (RenderableClass | RenderableInterface | RenderableEnum | RenderableCustomObject) & { From ee61c03a04eb62cae38f1cb302bab5ef4a60b77b Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sun, 20 Oct 2024 07:18:17 -0400 Subject: [PATCH 31/32] Sorting custom object fields --- src/core/markdown/generate-docs.ts | 4 ++-- src/core/reflection/sort-types-and-members.ts | 23 ++++++++++++++++--- src/core/shared/types.d.ts | 2 +- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index 7bad3852..71a53ec9 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -82,12 +82,12 @@ export function generateDocs(unparsedApexFiles: UnparsedSourceBundle[], config: TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]), ); }), - TE.map(sort), + TE.map((parsedFiles) => sort(filterOutCustomFields(parsedFiles))), TE.bindTo('parsedFiles'), TE.bind('references', ({ parsedFiles }) => TE.right( // Custom fields should not show up in the reference guide - convertToReferences(filterOutCustomFields(parsedFiles)), + convertToReferences(parsedFiles), ), ), TE.flatMap(({ parsedFiles, references }) => transformReferenceHook(config)({ references, parsedFiles })), diff --git a/src/core/reflection/sort-types-and-members.ts b/src/core/reflection/sort-types-and-members.ts index c4e7016e..fde5f41e 100644 --- a/src/core/reflection/sort-types-and-members.ts +++ b/src/core/reflection/sort-types-and-members.ts @@ -1,15 +1,21 @@ import { ClassMirror, EnumMirror, InterfaceMirror, Type } from '@cparra/apex-reflection'; import { ParsedFile } from '../shared/types'; import { isApexType } from '../shared/utils'; +import { ObjectMetadata } from './sobject/reflect-custom-object-sources'; +import { CustomFieldMetadata } from './sobject/reflect-custom-field-source'; type Named = { name: string }; -export function sortTypesAndMembers(shouldSort: boolean, parsedFiles: ParsedFile[]): ParsedFile[] { +export function sortTypesAndMembers( + shouldSort: boolean, + parsedFiles: ParsedFile[], +): ParsedFile[] { return parsedFiles .map((parsedFile) => ({ ...parsedFile, - // TODO: Sort fields when they are added to the parser - type: isApexType(parsedFile.type) ? sortTypeMember(parsedFile.type, shouldSort) : parsedFile.type, + type: isApexType(parsedFile.type) + ? sortTypeMember(parsedFile.type, shouldSort) + : sortCustomObjectFields(parsedFile.type, shouldSort), })) .sort((a, b) => sortByNames(shouldSort, a.type, b.type)); } @@ -36,6 +42,17 @@ function sortTypeMember(type: Type, shouldSort: boolean): Type { } } +function sortCustomObjectFields(type: ObjectMetadata, shouldSort: boolean): ObjectMetadata { + return { + ...type, + fields: sortFields(type.fields, shouldSort), + }; +} + +function sortFields(fields: ParsedFile[], shouldSort: boolean): ParsedFile[] { + return fields.sort((a, b) => sortByNames(shouldSort, a.type, b.type)); +} + function sortEnumValues(shouldSort: boolean, enumType: EnumMirror): EnumMirror { return { ...enumType, diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index 9fa50f5e..a3c5cac1 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -22,7 +22,7 @@ export type UserDefinedMarkdownConfig = { targetDir: string; scope: string[]; namespace?: string; - defaultGroupName: string; // TODO: Deprecate and rename + defaultGroupName: string; customObjectsGroupName: string; sortAlphabetically: boolean; includeMetadata: boolean; From 4736c8e767f01228665fd56db7ac0dbbdda6ee81 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sun, 20 Oct 2024 07:36:38 -0400 Subject: [PATCH 32/32] Generating custom objects UTs --- .../generating-custom-object-docs.spec.ts | 129 ++++++++++++++++++ .../__test__/generating-enum-docs.spec.ts | 3 - src/core/markdown/__test__/test-helpers.ts | 33 ++++- .../markdown/adapters/type-to-renderable.ts | 1 - 4 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 src/core/markdown/__test__/generating-custom-object-docs.spec.ts diff --git a/src/core/markdown/__test__/generating-custom-object-docs.spec.ts b/src/core/markdown/__test__/generating-custom-object-docs.spec.ts new file mode 100644 index 00000000..f08d0499 --- /dev/null +++ b/src/core/markdown/__test__/generating-custom-object-docs.spec.ts @@ -0,0 +1,129 @@ +import { extendExpect } from './expect-extensions'; +import { + customField, + customObjectGenerator, + generateDocs, + unparsedFieldBundleFromRawString, + unparsedObjectBundleFromRawString, +} from './test-helpers'; +import { assertEither } from '../../test-helpers/assert-either'; + +describe('Generates Custom Object documentation', () => { + beforeAll(() => { + extendExpect(); + }); + + describe('documentation content', () => { + it('displays the object label as a heading', async () => { + const input = unparsedObjectBundleFromRawString({ + rawContent: customObjectGenerator(), + filePath: 'src/object/TestObject__c.object-meta.xml', + }); + + const result = await generateDocs([input])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('# MyTestObject')); + }); + + it('displays the object description', async () => { + const input = unparsedObjectBundleFromRawString({ + rawContent: customObjectGenerator(), + filePath: 'src/object/TestObject__c.object-meta.xml', + }); + + const result = await generateDocs([input])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('test object for testing')); + }); + + it('displays the object api name', async () => { + const input = unparsedObjectBundleFromRawString({ + rawContent: customObjectGenerator(), + filePath: 'src/object/TestObject__c.object-meta.xml', + }); + + const result = await generateDocs([input])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('`TestObject__c`')); + }); + + it('displays the Fields heading if fields are present', async () => { + const customObjectBundle = unparsedObjectBundleFromRawString({ + rawContent: customObjectGenerator(), + filePath: 'src/object/TestObject__c.object-meta.xml', + }); + + const customFieldBundle = unparsedFieldBundleFromRawString({ + rawContent: customField, + filePath: 'src/object/TestField__c.field-meta.xml', + parentName: 'TestObject__c', + }); + + const result = await generateDocs([customObjectBundle, customFieldBundle])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('## Fields')); + }); + + it('does not display the Fields heading if no fields are present', async () => { + const input = unparsedObjectBundleFromRawString({ + rawContent: customObjectGenerator(), + filePath: 'src/object/TestObject__c.object-meta.xml', + }); + + const result = await generateDocs([input])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).not.firstDocContains('## Fields')); + }); + + it('displays the field label as a heading', async () => { + const customObjectBundle = unparsedObjectBundleFromRawString({ + rawContent: customObjectGenerator(), + filePath: 'src/object/TestObject__c.object-meta.xml', + }); + + const customFieldBundle = unparsedFieldBundleFromRawString({ + rawContent: customField, + filePath: 'src/object/TestField__c.field-meta.xml', + parentName: 'TestObject__c', + }); + + const result = await generateDocs([customObjectBundle, customFieldBundle])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('## PhotoUrl')); + }); + + it('displays the field description', async () => { + const customObjectBundle = unparsedObjectBundleFromRawString({ + rawContent: customObjectGenerator(), + filePath: 'src/object/TestObject__c.object-meta.xml', + }); + + const customFieldBundle = unparsedFieldBundleFromRawString({ + rawContent: customField, + filePath: 'src/object/TestField__c.field-meta.xml', + parentName: 'TestObject__c', + }); + + const result = await generateDocs([customObjectBundle, customFieldBundle])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('A URL that points to a photo')); + }); + + it('displays the field api name', async () => { + const customObjectBundle = unparsedObjectBundleFromRawString({ + rawContent: customObjectGenerator(), + filePath: 'src/object/TestObject__c.object-meta.xml', + }); + + const customFieldBundle = unparsedFieldBundleFromRawString({ + rawContent: customField, + filePath: 'src/object/TestField__c.field-meta.xml', + parentName: 'TestObject__c', + }); + + const result = await generateDocs([customObjectBundle, customFieldBundle])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('`TestField__c`')); + }); + }); +}); diff --git a/src/core/markdown/__test__/generating-enum-docs.spec.ts b/src/core/markdown/__test__/generating-enum-docs.spec.ts index 90f4954f..66bf94bb 100644 --- a/src/core/markdown/__test__/generating-enum-docs.spec.ts +++ b/src/core/markdown/__test__/generating-enum-docs.spec.ts @@ -2,9 +2,6 @@ import { extendExpect } from './expect-extensions'; import { unparsedApexBundleFromRawString, generateDocs } from './test-helpers'; import { assertEither } from '../../test-helpers/assert-either'; -// TODO: Create a test similar to this. -// TODO: Make sure to test the output for every single type of field supported - describe('Generates enum documentation', () => { beforeAll(() => { extendExpect(); diff --git a/src/core/markdown/__test__/test-helpers.ts b/src/core/markdown/__test__/test-helpers.ts index dcd8268d..d884958f 100644 --- a/src/core/markdown/__test__/test-helpers.ts +++ b/src/core/markdown/__test__/test-helpers.ts @@ -1,4 +1,9 @@ -import { UnparsedApexBundle, UnparsedCustomObjectBundle, UnparsedSourceBundle } from '../../shared/types'; +import { + UnparsedApexBundle, + UnparsedCustomFieldBundle, + UnparsedCustomObjectBundle, + UnparsedSourceBundle, +} from '../../shared/types'; import { generateDocs as gen, MarkdownGeneratorConfig } from '../generate-docs'; import { referenceGuideTemplate } from '../templates/reference-guide'; @@ -24,6 +29,20 @@ export function unparsedObjectBundleFromRawString(meta: { }; } +export function unparsedFieldBundleFromRawString(meta: { + rawContent: string; + filePath: string; + parentName: string; +}): UnparsedCustomFieldBundle { + return { + type: 'customfield', + name: 'TestField__c', + filePath: meta.filePath, + content: meta.rawContent, + parentName: meta.parentName, + }; +} + export function generateDocs(apexBundles: UnparsedSourceBundle[], config?: Partial) { return gen(apexBundles, { targetDir: 'target', @@ -53,3 +72,15 @@ export function customObjectGenerator( ${config.visibility} `; } + +export const customField = ` + + + PhotoUrl__c + false + + false + false + Url + A URL that points to a photo +`; diff --git a/src/core/markdown/adapters/type-to-renderable.ts b/src/core/markdown/adapters/type-to-renderable.ts index dceedbfb..cd66d8ec 100644 --- a/src/core/markdown/adapters/type-to-renderable.ts +++ b/src/core/markdown/adapters/type-to-renderable.ts @@ -12,7 +12,6 @@ import { GetRenderableContentByTypeName, RenderableCustomObject, RenderableCustomField, - RenderableContent, } from '../../renderables/types'; import { adaptDescribable, adaptDocumentable } from '../../renderables/documentables'; import { adaptConstructor, adaptMethod } from './methods-and-constructors';