From 7c67215ac40e27fde11b123ffb07dd0c1d48f50b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 16:53:06 +0000 Subject: [PATCH 01/20] Bump cross-spawn from 7.0.3 to 7.0.6 in /examples/changelog Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6. - [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md) - [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6) --- updated-dependencies: - dependency-name: cross-spawn dependency-type: indirect ... Signed-off-by: dependabot[bot] --- examples/changelog/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/changelog/package-lock.json b/examples/changelog/package-lock.json index 9b36fb9c..4ada25e7 100644 --- a/examples/changelog/package-lock.json +++ b/examples/changelog/package-lock.json @@ -217,9 +217,9 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", From 73097a95098b5addfa9bb97ef11b7db31ffd3eb2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 16:53:19 +0000 Subject: [PATCH 02/20] Bump vite from 5.4.6 to 5.4.14 in /examples/vitepress Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.6 to 5.4.14. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.4.14/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.4.14/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: indirect ... Signed-off-by: dependabot[bot] --- examples/vitepress/package-lock.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/vitepress/package-lock.json b/examples/vitepress/package-lock.json index 75eb8588..6908b653 100644 --- a/examples/vitepress/package-lock.json +++ b/examples/vitepress/package-lock.json @@ -2314,10 +2314,11 @@ "license": "MIT" }, "node_modules/vite": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", - "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", From a65618e5ae9f0ae753a3672c09d0de94a069a494 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 4 Feb 2025 08:24:59 -0400 Subject: [PATCH 03/20] Custom metadata records are picked up for documentation --- .../docs/.vitepress/cache/deps/_metadata.json | 14 +-- .../vitepress/docs/.vitepress/sidebar.json | 40 +++++++++ examples/vitepress/docs/changelog.md | 25 ++++++ .../docs/custom-objects/VisibleCMT__mdt.md | 20 +++++ examples/vitepress/docs/index.md | 32 +++++++ .../VisibleCMT.Some_Record_1.md-meta.xml | 9 ++ .../SameNamespaceMDT__mdt.object-meta.xml | 6 ++ .../VisibleCMT__mdt.object-meta.xml | 6 ++ .../fields/Field1__c.field-meta.xml | 11 +++ src/application/Apexdocs.ts | 7 +- src/application/source-code-file-reader.ts | 59 ++++++++++++- src/core/changelog/generate-change-log.ts | 4 +- .../markdown/adapters/type-to-renderable.ts | 2 + src/core/markdown/generate-docs.ts | 11 ++- .../sobject/reflect-custom-metadata-source.ts | 88 +++++++++++++++++++ .../sobject/reflect-custom-object-sources.ts | 5 +- ...stomFieldsAndObjectsAndMetadataRecords.ts} | 49 +++++++++-- src/core/shared/types.d.ts | 26 ++++-- src/util/source-bundle-utils.ts | 12 ++- 19 files changed, 395 insertions(+), 31 deletions(-) create mode 100644 examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md create mode 100644 examples/vitepress/force-app/main/default/customMetadata/VisibleCMT.Some_Record_1.md-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/SameNamespaceMDT__mdt/SameNamespaceMDT__mdt.object-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/VisibleCMT__mdt/VisibleCMT__mdt.object-meta.xml create mode 100644 examples/vitepress/force-app/main/default/objects/VisibleCMT__mdt/fields/Field1__c.field-meta.xml create mode 100644 src/core/reflection/sobject/reflect-custom-metadata-source.ts rename src/core/reflection/sobject/{reflectCustomFieldsAndObjects.ts => reflectCustomFieldsAndObjectsAndMetadataRecords.ts} (68%) diff --git a/examples/vitepress/docs/.vitepress/cache/deps/_metadata.json b/examples/vitepress/docs/.vitepress/cache/deps/_metadata.json index 3c6ae1c5..e710f560 100644 --- a/examples/vitepress/docs/.vitepress/cache/deps/_metadata.json +++ b/examples/vitepress/docs/.vitepress/cache/deps/_metadata.json @@ -1,31 +1,31 @@ { - "hash": "f2216e85", + "hash": "05eb2d4f", "configHash": "7f7b0dad", - "lockfileHash": "76121266", - "browserHash": "441a8d6a", + "lockfileHash": "3a9c2374", + "browserHash": "a831d6e7", "optimized": { "vue": { "src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js", "file": "vue.js", - "fileHash": "885cbaa9", + "fileHash": "1632d62a", "needsInterop": false }, "vitepress > @vue/devtools-api": { "src": "../../../../node_modules/@vue/devtools-api/dist/index.js", "file": "vitepress___@vue_devtools-api.js", - "fileHash": "ff3ba36c", + "fileHash": "dc8fec00", "needsInterop": false }, "vitepress > @vueuse/core": { "src": "../../../../node_modules/@vueuse/core/index.mjs", "file": "vitepress___@vueuse_core.js", - "fileHash": "b6cc6d79", + "fileHash": "3d02446b", "needsInterop": false }, "@theme/index": { "src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js", "file": "@theme_index.js", - "fileHash": "6b17bcd7", + "fileHash": "3d2d1de3", "needsInterop": false } }, diff --git a/examples/vitepress/docs/.vitepress/sidebar.json b/examples/vitepress/docs/.vitepress/sidebar.json index 6f2444a3..521440e0 100644 --- a/examples/vitepress/docs/.vitepress/sidebar.json +++ b/examples/vitepress/docs/.vitepress/sidebar.json @@ -66,10 +66,22 @@ "text": "Event__c", "link": "custom-objects/Event__c.md" }, + { + "text": "Event__c", + "link": "custom-objects/Event__c.md" + }, { "text": "Price_Component__c", "link": "custom-objects/Price_Component__c.md" }, + { + "text": "Price_Component__c", + "link": "custom-objects/Price_Component__c.md" + }, + { + "text": "Product__c", + "link": "custom-objects/Product__c.md" + }, { "text": "Product__c", "link": "custom-objects/Product__c.md" @@ -78,6 +90,14 @@ "text": "Product_Inline_Fields__c", "link": "custom-objects/Product_Inline_Fields__c.md" }, + { + "text": "Product_Inline_Fields__c", + "link": "custom-objects/Product_Inline_Fields__c.md" + }, + { + "text": "Product_Price_Component__c", + "link": "custom-objects/Product_Price_Component__c.md" + }, { "text": "Product_Price_Component__c", "link": "custom-objects/Product_Price_Component__c.md" @@ -86,6 +106,14 @@ "text": "Sales_Order__c", "link": "custom-objects/Sales_Order__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": "Sales_Order_Line__c", "link": "custom-objects/Sales_Order_Line__c.md" @@ -93,6 +121,18 @@ { "text": "Speaker__c", "link": "custom-objects/Speaker__c.md" + }, + { + "text": "Speaker__c", + "link": "custom-objects/Speaker__c.md" + }, + { + "text": "VisibleCMT__mdt", + "link": "custom-objects/VisibleCMT__mdt.md" + }, + { + "text": "VisibleCMT__mdt", + "link": "custom-objects/VisibleCMT__mdt.md" } ] } diff --git a/examples/vitepress/docs/changelog.md b/examples/vitepress/docs/changelog.md index 3cc6f539..f9a2c330 100644 --- a/examples/vitepress/docs/changelog.md +++ b/examples/vitepress/docs/changelog.md @@ -72,6 +72,31 @@ Custom object for tracking sales orders. ### Speaker__c Represents a speaker at an event. +### VisibleCMT__mdt + +### Event__c + +Represents an event that people can register for. +### Price_Component__c + +### Product_Inline_Fields__c + +Products +### Product_Price_Component__c + +### Product__c + +Product that is sold or available for sale. +### Sales_Order_Line__c + +Represents a line item on a sales order. +### Sales_Order__c + +Custom object for tracking sales orders. +### Speaker__c + +Represents a speaker at an event. +### VisibleCMT__mdt ## New or Removed Fields to Custom Objects or Standard Objects diff --git a/examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md b/examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md new file mode 100644 index 00000000..ae1a3ebb --- /dev/null +++ b/examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md @@ -0,0 +1,20 @@ +--- +title: VisibleCMT__mdt +--- + +# VisibleCMT + +## API Name +`VisibleCMT__mdt` + +## Fields +### Field1 +**Required** + +**API Name** + +`apexdocs__Field1__c` + +**Type** + +*Text* \ No newline at end of file diff --git a/examples/vitepress/docs/index.md b/examples/vitepress/docs/index.md index 6d8ce3bb..626090a2 100644 --- a/examples/vitepress/docs/index.md +++ b/examples/vitepress/docs/index.md @@ -25,22 +25,46 @@ hero: Represents an event that people can register for. +### [Event__c](custom-objects/Event__c) + +Represents an event that people can register for. + +### [Price_Component__c](custom-objects/Price_Component__c) + ### [Price_Component__c](custom-objects/Price_Component__c) ### [Product__c](custom-objects/Product__c) Product that is sold or available for sale. +### [Product__c](custom-objects/Product__c) + +Product that is sold or available for sale. + ### [Product_Inline_Fields__c](custom-objects/Product_Inline_Fields__c) Products +### [Product_Inline_Fields__c](custom-objects/Product_Inline_Fields__c) + +Products + +### [Product_Price_Component__c](custom-objects/Product_Price_Component__c) + ### [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__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. + ### [Sales_Order_Line__c](custom-objects/Sales_Order_Line__c) Represents a line item on a sales order. @@ -49,6 +73,14 @@ Represents a line item on a sales order. Represents a speaker at an event. +### [Speaker__c](custom-objects/Speaker__c) + +Represents a speaker at an event. + +### [VisibleCMT__mdt](custom-objects/VisibleCMT__mdt) + +### [VisibleCMT__mdt](custom-objects/VisibleCMT__mdt) + ## Miscellaneous ### [BaseClass](miscellaneous/BaseClass) diff --git a/examples/vitepress/force-app/main/default/customMetadata/VisibleCMT.Some_Record_1.md-meta.xml b/examples/vitepress/force-app/main/default/customMetadata/VisibleCMT.Some_Record_1.md-meta.xml new file mode 100644 index 00000000..13c352d4 --- /dev/null +++ b/examples/vitepress/force-app/main/default/customMetadata/VisibleCMT.Some_Record_1.md-meta.xml @@ -0,0 +1,9 @@ + + + + true + + Field1__c + Sample Value + + diff --git a/examples/vitepress/force-app/main/default/objects/SameNamespaceMDT__mdt/SameNamespaceMDT__mdt.object-meta.xml b/examples/vitepress/force-app/main/default/objects/SameNamespaceMDT__mdt/SameNamespaceMDT__mdt.object-meta.xml new file mode 100644 index 00000000..a5d81fa8 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/SameNamespaceMDT__mdt/SameNamespaceMDT__mdt.object-meta.xml @@ -0,0 +1,6 @@ + + + + SameNamespaceMDTs + Protected + diff --git a/examples/vitepress/force-app/main/default/objects/VisibleCMT__mdt/VisibleCMT__mdt.object-meta.xml b/examples/vitepress/force-app/main/default/objects/VisibleCMT__mdt/VisibleCMT__mdt.object-meta.xml new file mode 100644 index 00000000..e4b702db --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/VisibleCMT__mdt/VisibleCMT__mdt.object-meta.xml @@ -0,0 +1,6 @@ + + + + VisibleCMTs + Public + diff --git a/examples/vitepress/force-app/main/default/objects/VisibleCMT__mdt/fields/Field1__c.field-meta.xml b/examples/vitepress/force-app/main/default/objects/VisibleCMT__mdt/fields/Field1__c.field-meta.xml new file mode 100644 index 00000000..8363be54 --- /dev/null +++ b/examples/vitepress/force-app/main/default/objects/VisibleCMT__mdt/fields/Field1__c.field-meta.xml @@ -0,0 +1,11 @@ + + + Field1__c + false + DeveloperControlled + + 255 + true + Text + false + diff --git a/src/application/Apexdocs.ts b/src/application/Apexdocs.ts index e317b345..56550feb 100644 --- a/src/application/Apexdocs.ts +++ b/src/application/Apexdocs.ts @@ -52,10 +52,9 @@ async function processMarkdown(config: UserDefinedMarkdownConfig) { return pipe( E.tryCatch( () => - readFiles(['ApexClass', 'CustomObject', 'CustomField'], { includeMetadata: config.includeMetadata })( - config.sourceDir, - config.exclude, - ), + readFiles(['ApexClass', 'CustomObject', 'CustomField', 'CustomMetadata'], { + includeMetadata: config.includeMetadata, + })(config.sourceDir, config.exclude), (e) => new FileReadingError('An error occurred while reading files.', e), ), TE.fromEither, diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts index a2a81ae6..a16d2429 100644 --- a/src/application/source-code-file-reader.ts +++ b/src/application/source-code-file-reader.ts @@ -1,10 +1,15 @@ import { FileSystem } from './file-system'; -import { UnparsedApexBundle, UnparsedCustomFieldBundle, UnparsedCustomObjectBundle } from '../core/shared/types'; +import { + UnparsedApexBundle, + UnparsedCustomFieldBundle, + UnparsedCustomMetadataBundle, + 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' | 'CustomField'; +type ComponentTypes = 'ApexClass' | 'CustomObject' | 'CustomField' | 'CustomMetadata'; /** * Simplified representation of a source component, with only @@ -43,6 +48,13 @@ type CustomFieldSourceComponent = { parentName: string; }; +type CustomMetadataSourceComponent = { + type: 'CustomMetadata'; + name: string; + contentPath: string; + parentName: string; +}; + function getApexSourceComponents( includeMetadata: boolean, sourceComponents: SourceComponentAdapter[], @@ -125,6 +137,39 @@ function toUnparsedCustomFieldBundle( })); } +function getCustomMetadataSourceComponents( + sourceComponents: SourceComponentAdapter[], +): CustomMetadataSourceComponent[] { + function getParentAndNamePair(component: SourceComponentAdapter): [string, string] { + // Custom metadata take the format [Namespace].[ParentName].[MetadataName], where namespace is optional. + // Here we split the strig and return the last 2 elements, representing the parent and the metadata name. + const [parentName, name] = component.name.split('.').slice(-2); + return [parentName, name]; + } + + return sourceComponents + .filter((component) => component.type.name === 'CustomMetadata') + .map((component) => ({ + name: getParentAndNamePair(component)[1], + type: 'CustomMetadata' as const, + contentPath: component.xml!, + parentName: getParentAndNamePair(component)[0], + })); +} + +function toUnparsedCustomMetadataBundle( + fileSystem: FileSystem, + customMetadataSourceComponents: CustomMetadataSourceComponent[], +): UnparsedCustomMetadataBundle[] { + return customMetadataSourceComponents.map((component) => ({ + type: 'custommetadata', + name: component.name, + filePath: component.contentPath, + content: fileSystem.readFile(component.contentPath), + parentName: component.parentName, + })); +} + /** * Reads from source code files and returns their raw body. */ @@ -137,7 +182,12 @@ export function processFiles(fileSystem: FileSystem) { ComponentTypes, ( components: SourceComponentAdapter[], - ) => (UnparsedApexBundle | UnparsedCustomObjectBundle | UnparsedCustomFieldBundle)[] + ) => ( + | UnparsedApexBundle + | UnparsedCustomObjectBundle + | UnparsedCustomFieldBundle + | UnparsedCustomMetadataBundle + )[] > = { ApexClass: flow(apply(getApexSourceComponents, options.includeMetadata), (apexSourceComponents) => toUnparsedApexBundle(fileSystem, apexSourceComponents), @@ -148,6 +198,9 @@ export function processFiles(fileSystem: FileSystem) { CustomField: flow(getCustomFieldSourceComponents, (customFieldSourceComponents) => toUnparsedCustomFieldBundle(fileSystem, customFieldSourceComponents), ), + CustomMetadata: flow(getCustomMetadataSourceComponents, (customMetadataSourceComponents) => + toUnparsedCustomMetadataBundle(fileSystem, customMetadataSourceComponents), + ), }; const convertersToUse = componentTypesToRetrieve.map((componentType) => converters[componentType]); diff --git a/src/core/changelog/generate-change-log.ts b/src/core/changelog/generate-change-log.ts index a11d7daa..0a79b6e4 100644 --- a/src/core/changelog/generate-change-log.ts +++ b/src/core/changelog/generate-change-log.ts @@ -18,7 +18,7 @@ import { HookError, ReflectionErrors } from '../errors/errors'; import { apply } from '#utils/fp'; import { filterScope } from '../reflection/apex/filter-scope'; import { isInSource, isSkip, passThroughHook, skip, toFrontmatterString } from '../shared/utils'; -import { reflectCustomFieldsAndObjects } from '../reflection/sobject/reflectCustomFieldsAndObjects'; +import { reflectCustomFieldsAndObjectsAndMetadataRecords } from '../reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords'; import { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources'; import { Type } from '@cparra/apex-reflection'; import { filterApexSourceFiles, filterCustomObjectsAndFields } from '#utils/source-bundle-utils'; @@ -70,7 +70,7 @@ function reflect(bundles: UnparsedSourceBundle[], config: Omit { return pipe( - reflectCustomFieldsAndObjects(filterCustomObjectsAndFields(bundles)), + reflectCustomFieldsAndObjectsAndMetadataRecords(filterCustomObjectsAndFields(bundles)), TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]), ); }), diff --git a/src/core/markdown/adapters/type-to-renderable.ts b/src/core/markdown/adapters/type-to-renderable.ts index 46226a0f..0eb0bad8 100644 --- a/src/core/markdown/adapters/type-to-renderable.ts +++ b/src/core/markdown/adapters/type-to-renderable.ts @@ -250,6 +250,8 @@ function objectMetadataToRenderable( objectMetadata: CustomObjectMetadata, config: MarkdownGeneratorConfig, ): RenderableCustomObject { + console.log(JSON.stringify(objectMetadata, null, 2)); + return { type: 'customobject', headingLevel: 1, diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index fbffc05a..b99878d1 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -32,8 +32,12 @@ import { removeExcludedTags } from '../reflection/apex/remove-excluded-tags'; import { HookError } from '../errors/errors'; import { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources'; import { Type } from '@cparra/apex-reflection'; -import { reflectCustomFieldsAndObjects } from '../reflection/sobject/reflectCustomFieldsAndObjects'; -import { filterApexSourceFiles, filterCustomObjectsAndFields } from '#utils/source-bundle-utils'; +import { reflectCustomFieldsAndObjectsAndMetadataRecords } from '../reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords'; +import { + filterApexSourceFiles, + filterCustomObjectsAndFields, + filterCustomObjectsFieldsAndMetadataRecords, +} from '#utils/source-bundle-utils'; export type MarkdownGeneratorConfig = Omit< UserDefinedMarkdownConfig, @@ -62,10 +66,11 @@ export function generateDocs(unparsedBundles: UnparsedSourceBundle[], config: Ma generateForApex(filterApexSourceFiles(unparsedBundles), config), TE.chain((parsedApexFiles) => { return pipe( - reflectCustomFieldsAndObjects(filterCustomObjectsAndFields(unparsedBundles)), + reflectCustomFieldsAndObjectsAndMetadataRecords(filterCustomObjectsFieldsAndMetadataRecords(unparsedBundles)), TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]), ); }), + // TODO: Sort out custom metadata whenever is necessary TE.map((parsedFiles) => sort(filterOutCustomFields(parsedFiles))), TE.bindTo('parsedFiles'), TE.bind('references', ({ parsedFiles }) => diff --git a/src/core/reflection/sobject/reflect-custom-metadata-source.ts b/src/core/reflection/sobject/reflect-custom-metadata-source.ts new file mode 100644 index 00000000..4b8e179c --- /dev/null +++ b/src/core/reflection/sobject/reflect-custom-metadata-source.ts @@ -0,0 +1,88 @@ +import { ParsedFile, UnparsedCustomMetadataBundle } from '../../shared/types'; +import * as TE from 'fp-ts/TaskEither'; +import { ReflectionError, ReflectionErrors } from '../../errors/errors'; +import { pipe } from 'fp-ts/function'; +import * as A from 'fp-ts/Array'; +import * as E from 'fp-ts/Either'; +import { XMLParser } from 'fast-xml-parser'; + +export type CustomMetadataMetadata = { + type_name: 'custommetadata'; + protected: boolean; + name: string; + label?: string | null; + description: string | null; + parentName: string; + // TODO: Reflect values +}; + +export function reflectCustomMetadataSources( + customMetadataSources: UnparsedCustomMetadataBundle[], +): TE.TaskEither[]> { + return pipe(customMetadataSources, A.traverse(TE.ApplicativePar)(reflectCustomMetadataSource)); +} + +function reflectCustomMetadataSource( + customMetadataSource: UnparsedCustomMetadataBundle, +): TE.TaskEither> { + return pipe( + E.tryCatch(() => new XMLParser().parse(customMetadataSource.content), E.toError), + E.flatMap(validate), + E.map(toCustomMetadataMetadata), + E.map((metadata) => addName(metadata, customMetadataSource.name)), + E.map((metadata) => addParentName(metadata, customMetadataSource.parentName)), + E.map((metadata) => toParsedFile(customMetadataSource.filePath, metadata)), + E.mapLeft((error) => new ReflectionErrors([new ReflectionError(customMetadataSource.filePath, error.message)])), + TE.fromEither, + ); +} + +function validate(parsedResult: unknown): E.Either { + const err = E.left(new Error('Invalid custom metadata')); + + function isObject(value: unknown) { + return typeof value === 'object' && value !== null ? E.right(value) : err; + } + + function hasTheCustomMetadataKey(value: object) { + return 'CustomMetadata' in value ? E.right(value) : err; + } + + return pipe(parsedResult, isObject, E.chain(hasTheCustomMetadataKey)); +} + +function toCustomMetadataMetadata(parserResult: { CustomMetadata: unknown }): CustomMetadataMetadata { + const customMetadata = + parserResult?.CustomMetadata != null && typeof parserResult.CustomMetadata === 'object' + ? parserResult.CustomMetadata + : {}; + const defaultValues = { + label: null, + description: null, + }; + + return { + ...defaultValues, + ...customMetadata, + type_name: 'custommetadata', + } as CustomMetadataMetadata; +} + +function addName(metadata: CustomMetadataMetadata, name: string): CustomMetadataMetadata { + return { ...metadata, name }; +} + +function addParentName(metadata: CustomMetadataMetadata, parentName: string): CustomMetadataMetadata { + return { ...metadata, parentName }; +} + +function toParsedFile(filePath: string, typeMirror: CustomMetadataMetadata): ParsedFile { + return { + source: { + filePath, + name: typeMirror.name, + type: typeMirror.type_name, + }, + type: typeMirror, + }; +} diff --git a/src/core/reflection/sobject/reflect-custom-object-sources.ts b/src/core/reflection/sobject/reflect-custom-object-sources.ts index 6f22780d..5654d6e8 100644 --- a/src/core/reflection/sobject/reflect-custom-object-sources.ts +++ b/src/core/reflection/sobject/reflect-custom-object-sources.ts @@ -9,6 +9,7 @@ import * as A from 'fp-ts/Array'; import * as E from 'fp-ts/Either'; import { CustomFieldMetadata } from './reflect-custom-field-source'; import { getPickListValues } from './parse-picklist-values'; +import { CustomMetadataMetadata } from './reflect-custom-metadata-source'; export type CustomObjectMetadata = { type_name: 'customobject'; @@ -18,6 +19,7 @@ export type CustomObjectMetadata = { name: string; description: string | null; fields: CustomFieldMetadata[]; + metadataRecords: CustomMetadataMetadata[]; }; export function reflectCustomObjectSources( @@ -64,11 +66,12 @@ function validate(parseResult: unknown): E.Either = { deploymentStatus: 'Deployed', visibility: 'Public', description: null, fields: [] as CustomFieldMetadata[], + metadataRecords: [] as CustomMetadataMetadata[], }; return { ...defaultValues, ...customObject } as CustomObjectMetadata; } diff --git a/src/core/reflection/sobject/reflectCustomFieldsAndObjects.ts b/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts similarity index 68% rename from src/core/reflection/sobject/reflectCustomFieldsAndObjects.ts rename to src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts index 083865a4..3121c94c 100644 --- a/src/core/reflection/sobject/reflectCustomFieldsAndObjects.ts +++ b/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts @@ -1,13 +1,19 @@ -import { ParsedFile, UnparsedCustomFieldBundle, UnparsedCustomObjectBundle } from '../../shared/types'; +import { + ParsedFile, + UnparsedCustomFieldBundle, + UnparsedCustomMetadataBundle, + UnparsedCustomObjectBundle, +} from '../../shared/types'; import { CustomObjectMetadata, reflectCustomObjectSources } from './reflect-custom-object-sources'; import * as TE from 'fp-ts/TaskEither'; import { ReflectionErrors } from '../../errors/errors'; import { CustomFieldMetadata, reflectCustomFieldSources } from './reflect-custom-field-source'; import { pipe } from 'fp-ts/function'; import { TaskEither } from 'fp-ts/TaskEither'; +import { CustomMetadataMetadata, reflectCustomMetadataSources } from './reflect-custom-metadata-source'; -export function reflectCustomFieldsAndObjects( - objectBundles: (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle)[], +export function reflectCustomFieldsAndObjectsAndMetadataRecords( + objectBundles: (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle | UnparsedCustomMetadataBundle)[], ): TaskEither[]> { function filterNonPublished(parsedFiles: ParsedFile[]): ParsedFile[] { return parsedFiles.filter((parsedFile) => parsedFile.type.deploymentStatus === 'Deployed'); @@ -25,12 +31,22 @@ export function reflectCustomFieldsAndObjects( (object): object is UnparsedCustomFieldBundle => object.type === 'customfield', ); + const customMetadata = objectBundles.filter( + (object): object is UnparsedCustomMetadataBundle => object.type === 'custommetadata', + ); + function generateForFields( fields: UnparsedCustomFieldBundle[], ): TE.TaskEither[]> { return pipe(fields, reflectCustomFieldSources); } + function generateForMetadata( + metadata: UnparsedCustomMetadataBundle[], + ): TE.TaskEither[]> { + return pipe(metadata, reflectCustomMetadataSources); + } + return pipe( customObjects, reflectCustomObjectSources, @@ -38,8 +54,13 @@ export function reflectCustomFieldsAndObjects( TE.map(filterNonPublic), TE.bindTo('objects'), TE.bind('fields', () => generateForFields(customFields)), - TE.map(({ objects, fields }) => { - return [...mapFieldsToObjects(objects, fields), ...mapExtensionFields(objects, fields)]; + TE.bind('metadata', () => generateForMetadata(customMetadata)), + TE.map(({ objects, fields, metadata }) => { + return [ + ...mapFieldsToObjects(objects, fields), + ...mapCustomMetadataToObjects(objects, metadata), + ...mapExtensionFields(objects, fields), + ]; }), ); } @@ -61,6 +82,23 @@ function mapFieldsToObjects( }); } +function mapCustomMetadataToObjects( + objects: ParsedFile[], + metadata: ParsedFile[], +): ParsedFile[] { + // Locate the metadata for each object by using the parentName property + return objects.map((object) => { + const objectMetadata = metadata.filter((meta) => `${meta.type.parentName}__mdt` === object.type.name); + return { + ...object, + type: { + ...object.type, + metadataRecords: [...object.type.metadataRecords, ...objectMetadata.map((meta) => meta.type)], + }, + }; + }); +} + // "Extension" fields are fields that are in the source code without the corresponding object-meta.xml file. // These are fields that either extend a standard Salesforce object, or an object in a different package. function mapExtensionFields( @@ -97,6 +135,7 @@ function mapExtensionFields( name: key, description: null, fields: fields, + metadataRecords: [], }, }; }); diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index 0e3157db..2dd5ccf4 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 { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources'; -import { CustomFieldMetadata } from '../reflection/sobject/reflect-custom-field-source'; +import { CustomFieldMetadata, CustomMetadataMetadata } from '../reflection/sobject/reflect-custom-field-source'; export type Generators = 'markdown' | 'openapi' | 'changelog'; @@ -29,7 +29,7 @@ export type CliConfigurableMarkdownConfig = { }; export type UserDefinedMarkdownConfig = { - targetGenerator: 'markdown' /** Glob patterns to exclude files from the documentation. */; + targetGenerator: 'markdown'; excludeTags: string[]; exclude: string[]; } & CliConfigurableMarkdownConfig & @@ -59,7 +59,11 @@ export type UserDefinedChangelogConfig = { export type UserDefinedConfig = UserDefinedMarkdownConfig | UserDefinedOpenApiConfig | UserDefinedChangelogConfig; -export type UnparsedSourceBundle = UnparsedApexBundle | UnparsedCustomObjectBundle | UnparsedCustomFieldBundle; +export type UnparsedSourceBundle = + | UnparsedApexBundle + | UnparsedCustomObjectBundle + | UnparsedCustomFieldBundle + | UnparsedCustomMetadataBundle; export type UnparsedCustomObjectBundle = { type: 'customobject'; @@ -76,6 +80,14 @@ export type UnparsedCustomFieldBundle = { parentName: string; }; +export type UnparsedCustomMetadataBundle = { + type: 'custommetadata'; + name: string; + filePath: string; + content: string; + parentName: string; +}; + export type UnparsedApexBundle = { type: 'apex'; name: string; @@ -84,7 +96,7 @@ export type UnparsedApexBundle = { metadataContent: string | null; }; -type MetadataTypes = 'interface' | 'class' | 'enum' | 'customobject' | 'customfield'; +type MetadataTypes = 'interface' | 'class' | 'enum' | 'customobject' | 'customfield' | 'custommetadata'; export type SourceFileMetadata = { filePath: string; @@ -104,7 +116,11 @@ export type ExternalMetadata = { }; export type ParsedFile< - T extends Type | CustomObjectMetadata | CustomFieldMetadata = Type | CustomObjectMetadata | CustomFieldMetadata, + T extends Type | CustomObjectMetadata | CustomFieldMetadata | CustomMetadataMetadata = + | Type + | CustomObjectMetadata + | CustomFieldMetadata + | CustomMetadataMetadata, > = { source: SourceFileMetadata | ExternalMetadata; type: T; diff --git a/src/util/source-bundle-utils.ts b/src/util/source-bundle-utils.ts index 39fcb650..9eae0ede 100644 --- a/src/util/source-bundle-utils.ts +++ b/src/util/source-bundle-utils.ts @@ -1,6 +1,6 @@ import { UnparsedApexBundle, - UnparsedCustomFieldBundle, + UnparsedCustomFieldBundle, UnparsedCustomMetadataBundle, UnparsedCustomObjectBundle, UnparsedSourceBundle, } from '../core/shared/types'; @@ -9,6 +9,7 @@ export function filterApexSourceFiles(sourceFiles: UnparsedSourceBundle[]): Unpa return sourceFiles.filter((sourceFile): sourceFile is UnparsedApexBundle => sourceFile.type === 'apex'); } +// TODO: The changelog still uses this export function filterCustomObjectsAndFields( sourceFiles: UnparsedSourceBundle[], ): (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle)[] { @@ -17,3 +18,12 @@ export function filterCustomObjectsAndFields( sourceFile.type === 'customobject' || sourceFile.type === 'customfield', ); } + +export function filterCustomObjectsFieldsAndMetadataRecords( + sourceFiles: UnparsedSourceBundle[], +): (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle | UnparsedCustomMetadataBundle)[] { + return sourceFiles.filter( + (sourceFile): sourceFile is UnparsedCustomObjectBundle => + sourceFile.type === 'customobject' || sourceFile.type === 'customfield' || sourceFile.type === 'custommetadata', + ); +} From 03875434ca7eb940128643071c0bb89452b4fe9e Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 4 Feb 2025 10:41:17 -0400 Subject: [PATCH 04/20] Preventing objects to appear twice in the reference guide --- .../vitepress/docs/.vitepress/sidebar.json | 36 ------------------- examples/vitepress/docs/changelog.md | 24 ------------- .../docs/custom-objects/VisibleCMT__mdt.md | 9 ++++- examples/vitepress/docs/index.md | 30 ---------------- src/application/source-code-file-reader.ts | 3 ++ src/core/changelog/generate-change-log.ts | 8 +++-- src/core/changelog/process-changelog.ts | 3 +- src/core/changelog/renderable-changelog.ts | 3 +- .../markdown/adapters/type-to-renderable.ts | 22 ++++++++++-- src/core/markdown/generate-docs.ts | 18 +++++----- .../templates/custom-object-template.ts | 17 +++++++++ .../sobject/reflect-custom-metadata-source.ts | 7 ++-- ...ustomFieldsAndObjectsAndMetadataRecords.ts | 27 +++----------- src/core/renderables/types.d.ts | 13 +++++++ src/core/shared/types.d.ts | 4 ++- 15 files changed, 91 insertions(+), 133 deletions(-) diff --git a/examples/vitepress/docs/.vitepress/sidebar.json b/examples/vitepress/docs/.vitepress/sidebar.json index 521440e0..0fc24fcc 100644 --- a/examples/vitepress/docs/.vitepress/sidebar.json +++ b/examples/vitepress/docs/.vitepress/sidebar.json @@ -66,22 +66,10 @@ "text": "Event__c", "link": "custom-objects/Event__c.md" }, - { - "text": "Event__c", - "link": "custom-objects/Event__c.md" - }, { "text": "Price_Component__c", "link": "custom-objects/Price_Component__c.md" }, - { - "text": "Price_Component__c", - "link": "custom-objects/Price_Component__c.md" - }, - { - "text": "Product__c", - "link": "custom-objects/Product__c.md" - }, { "text": "Product__c", "link": "custom-objects/Product__c.md" @@ -90,14 +78,6 @@ "text": "Product_Inline_Fields__c", "link": "custom-objects/Product_Inline_Fields__c.md" }, - { - "text": "Product_Inline_Fields__c", - "link": "custom-objects/Product_Inline_Fields__c.md" - }, - { - "text": "Product_Price_Component__c", - "link": "custom-objects/Product_Price_Component__c.md" - }, { "text": "Product_Price_Component__c", "link": "custom-objects/Product_Price_Component__c.md" @@ -106,14 +86,6 @@ "text": "Sales_Order__c", "link": "custom-objects/Sales_Order__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": "Sales_Order_Line__c", "link": "custom-objects/Sales_Order_Line__c.md" @@ -122,14 +94,6 @@ "text": "Speaker__c", "link": "custom-objects/Speaker__c.md" }, - { - "text": "Speaker__c", - "link": "custom-objects/Speaker__c.md" - }, - { - "text": "VisibleCMT__mdt", - "link": "custom-objects/VisibleCMT__mdt.md" - }, { "text": "VisibleCMT__mdt", "link": "custom-objects/VisibleCMT__mdt.md" diff --git a/examples/vitepress/docs/changelog.md b/examples/vitepress/docs/changelog.md index f9a2c330..e20ac3a4 100644 --- a/examples/vitepress/docs/changelog.md +++ b/examples/vitepress/docs/changelog.md @@ -74,30 +74,6 @@ Custom object for tracking sales orders. Represents a speaker at an event. ### VisibleCMT__mdt -### Event__c - -Represents an event that people can register for. -### Price_Component__c - -### Product_Inline_Fields__c - -Products -### Product_Price_Component__c - -### Product__c - -Product that is sold or available for sale. -### Sales_Order_Line__c - -Represents a line item on a sales order. -### Sales_Order__c - -Custom object for tracking sales orders. -### Speaker__c - -Represents a speaker at an event. -### VisibleCMT__mdt - ## New or Removed Fields to Custom Objects or Standard Objects These custom fields have been added or removed. diff --git a/examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md b/examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md index ae1a3ebb..ca747a07 100644 --- a/examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md +++ b/examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md @@ -17,4 +17,11 @@ title: VisibleCMT__mdt **Type** -*Text* \ No newline at end of file +*Text* + +## Records +### Some Record 1 + +**API Name** + +`VisibleCMT.Some_Record_1` \ No newline at end of file diff --git a/examples/vitepress/docs/index.md b/examples/vitepress/docs/index.md index 626090a2..ca295921 100644 --- a/examples/vitepress/docs/index.md +++ b/examples/vitepress/docs/index.md @@ -25,46 +25,22 @@ hero: Represents an event that people can register for. -### [Event__c](custom-objects/Event__c) - -Represents an event that people can register for. - -### [Price_Component__c](custom-objects/Price_Component__c) - ### [Price_Component__c](custom-objects/Price_Component__c) ### [Product__c](custom-objects/Product__c) Product that is sold or available for sale. -### [Product__c](custom-objects/Product__c) - -Product that is sold or available for sale. - ### [Product_Inline_Fields__c](custom-objects/Product_Inline_Fields__c) Products -### [Product_Inline_Fields__c](custom-objects/Product_Inline_Fields__c) - -Products - -### [Product_Price_Component__c](custom-objects/Product_Price_Component__c) - ### [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__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. - ### [Sales_Order_Line__c](custom-objects/Sales_Order_Line__c) Represents a line item on a sales order. @@ -73,12 +49,6 @@ Represents a line item on a sales order. Represents a speaker at an event. -### [Speaker__c](custom-objects/Speaker__c) - -Represents a speaker at an event. - -### [VisibleCMT__mdt](custom-objects/VisibleCMT__mdt) - ### [VisibleCMT__mdt](custom-objects/VisibleCMT__mdt) ## Miscellaneous diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts index a16d2429..b01f796f 100644 --- a/src/application/source-code-file-reader.ts +++ b/src/application/source-code-file-reader.ts @@ -50,6 +50,7 @@ type CustomFieldSourceComponent = { type CustomMetadataSourceComponent = { type: 'CustomMetadata'; + apiName: string; name: string; contentPath: string; parentName: string; @@ -150,6 +151,7 @@ function getCustomMetadataSourceComponents( return sourceComponents .filter((component) => component.type.name === 'CustomMetadata') .map((component) => ({ + apiName: component.name, name: getParentAndNamePair(component)[1], type: 'CustomMetadata' as const, contentPath: component.xml!, @@ -162,6 +164,7 @@ function toUnparsedCustomMetadataBundle( customMetadataSourceComponents: CustomMetadataSourceComponent[], ): UnparsedCustomMetadataBundle[] { return customMetadataSourceComponents.map((component) => ({ + apiName: component.apiName, type: 'custommetadata', name: component.name, filePath: component.contentPath, diff --git a/src/core/changelog/generate-change-log.ts b/src/core/changelog/generate-change-log.ts index 0a79b6e4..d48c9c0a 100644 --- a/src/core/changelog/generate-change-log.ts +++ b/src/core/changelog/generate-change-log.ts @@ -25,6 +25,7 @@ import { filterApexSourceFiles, filterCustomObjectsAndFields } from '#utils/sour import { CustomFieldMetadata } from '../reflection/sobject/reflect-custom-field-source'; import { hookableTemplate } from '../markdown/templates/hookable'; import changelogToSourceChangelog from './helpers/changelog-to-source-changelog'; +import { CustomMetadataMetadata } from '../reflection/sobject/reflect-custom-metadata-source'; type Config = Omit; @@ -81,7 +82,10 @@ function toManifests({ oldVersion, newVersion }: { oldVersion: ParsedFile[]; new function parsedFilesToManifest(parsedFiles: ParsedFile[]): VersionManifest { return { types: parsedFiles.reduce( - (previousValue: (Type | CustomObjectMetadata | CustomFieldMetadata)[], parsedFile: ParsedFile) => { + ( + previousValue: (Type | CustomObjectMetadata | CustomFieldMetadata | CustomMetadataMetadata)[], + parsedFile: ParsedFile, + ) => { if (!isInSource(parsedFile.source) && parsedFile.type.type_name === 'customobject') { // When we are dealing with a custom object that was not in the source (for extension fields), we return all // of its fields. @@ -89,7 +93,7 @@ function toManifests({ oldVersion, newVersion }: { oldVersion: ParsedFile[]; new } return [...previousValue, parsedFile.type]; }, - [] as (Type | CustomObjectMetadata | CustomFieldMetadata)[], + [] as (Type | CustomObjectMetadata | CustomFieldMetadata | CustomMetadataMetadata)[], ), }; } diff --git a/src/core/changelog/process-changelog.ts b/src/core/changelog/process-changelog.ts index d689db40..086584b7 100644 --- a/src/core/changelog/process-changelog.ts +++ b/src/core/changelog/process-changelog.ts @@ -3,9 +3,10 @@ import { pipe } from 'fp-ts/function'; import { areMethodsEqual } from './method-changes-checker'; import { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources'; import { CustomFieldMetadata } from '../reflection/sobject/reflect-custom-field-source'; +import { CustomMetadataMetadata } from '../reflection/sobject/reflect-custom-metadata-source'; export type VersionManifest = { - types: (Type | CustomObjectMetadata | CustomFieldMetadata)[]; + types: (Type | CustomObjectMetadata | CustomFieldMetadata | CustomMetadataMetadata)[]; }; type ModificationTypes = diff --git a/src/core/changelog/renderable-changelog.ts b/src/core/changelog/renderable-changelog.ts index 0b72ed1c..2ec86c49 100644 --- a/src/core/changelog/renderable-changelog.ts +++ b/src/core/changelog/renderable-changelog.ts @@ -4,6 +4,7 @@ import { RenderableContent } from '../renderables/types'; import { adaptDescribable } from '../renderables/documentables'; import { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources'; import { CustomFieldMetadata } from '../reflection/sobject/reflect-custom-field-source'; +import { CustomMetadataMetadata } from '../reflection/sobject/reflect-custom-metadata-source'; type NewTypeRenderable = { name: string; @@ -47,7 +48,7 @@ export type RenderableChangelog = { export function convertToRenderableChangelog( changelog: Changelog, - newManifest: (Type | CustomObjectMetadata | CustomFieldMetadata)[], + newManifest: (Type | CustomObjectMetadata | CustomFieldMetadata | CustomMetadataMetadata)[], ): RenderableChangelog { const allNewTypes = [...changelog.newApexTypes, ...changelog.newCustomObjects].map( (newType) => newManifest.find((type) => type.name.toLowerCase() === newType.toLowerCase())!, diff --git a/src/core/markdown/adapters/type-to-renderable.ts b/src/core/markdown/adapters/type-to-renderable.ts index 0eb0bad8..647c6037 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, + RenderableCustomMetadata, } from '../../renderables/types'; import { adaptDescribable, adaptDocumentable } from '../../renderables/documentables'; import { adaptConstructor, adaptMethod } from './methods-and-constructors'; @@ -21,6 +22,7 @@ import { ExternalMetadata, SourceFileMetadata } from '../../shared/types'; import { CustomObjectMetadata } from '../../reflection/sobject/reflect-custom-object-sources'; import { getTypeGroup, isInSource } from '../../shared/utils'; import { CustomFieldMetadata } from '../../reflection/sobject/reflect-custom-field-source'; +import { CustomMetadataMetadata } from '../../reflection/sobject/reflect-custom-metadata-source'; type GetReturnRenderable = T extends InterfaceMirror ? RenderableInterface @@ -250,8 +252,6 @@ function objectMetadataToRenderable( objectMetadata: CustomObjectMetadata, config: MarkdownGeneratorConfig, ): RenderableCustomObject { - console.log(JSON.stringify(objectMetadata, null, 2)); - return { type: 'customobject', headingLevel: 1, @@ -268,6 +268,12 @@ function objectMetadataToRenderable( heading: 'Fields', value: objectMetadata.fields.map((field) => fieldMetadataToRenderable(field, config, 3)), }, + hasRecords: objectMetadata.metadataRecords.length > 0, + metadataRecords: { + headingLevel: 2, + heading: 'Records', + value: objectMetadata.metadataRecords.map((metadata) => customMetadataToRenderable(metadata, 3)), + }, }; } @@ -294,6 +300,18 @@ function fieldMetadataToRenderable( }; } +function customMetadataToRenderable(metadata: CustomMetadataMetadata, headingLevel: number): RenderableCustomMetadata { + return { + type: 'metadata', + headingLevel: headingLevel, + heading: metadata.label ?? metadata.name, + description: metadata.description ? [metadata.description] : [], + apiName: metadata.apiName, + label: metadata.label ?? metadata.name, + protected: metadata.protected, + }; +} + function getApiName(currentName: string, config: MarkdownGeneratorConfig) { if (config.namespace) { // first remove any `__c` suffix diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index b99878d1..ef7f6e42 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -33,11 +33,7 @@ import { HookError } from '../errors/errors'; import { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources'; import { Type } from '@cparra/apex-reflection'; import { reflectCustomFieldsAndObjectsAndMetadataRecords } from '../reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords'; -import { - filterApexSourceFiles, - filterCustomObjectsAndFields, - filterCustomObjectsFieldsAndMetadataRecords, -} from '#utils/source-bundle-utils'; +import { filterApexSourceFiles, filterCustomObjectsFieldsAndMetadataRecords } from '#utils/source-bundle-utils'; export type MarkdownGeneratorConfig = Omit< UserDefinedMarkdownConfig, @@ -56,9 +52,10 @@ export function generateDocs(unparsedBundles: UnparsedSourceBundle[], config: Ma ); const sort = apply(sortTypesAndMembers, config.sortAlphabetically); - function filterOutCustomFields(parsedFiles: ParsedFile[]): ParsedFile[] { + function filterOutCustomFieldsAndMetadata(parsedFiles: ParsedFile[]): ParsedFile[] { return parsedFiles.filter( - (parsedFile): parsedFile is ParsedFile => parsedFile.source.type !== 'customfield', + (parsedFile): parsedFile is ParsedFile => + parsedFile.source.type !== 'customfield' && parsedFile.source.type !== 'custommetadata', ); } @@ -70,8 +67,7 @@ export function generateDocs(unparsedBundles: UnparsedSourceBundle[], config: Ma TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]), ); }), - // TODO: Sort out custom metadata whenever is necessary - TE.map((parsedFiles) => sort(filterOutCustomFields(parsedFiles))), + TE.map((parsedFiles) => sort(filterOutCustomFieldsAndMetadata(parsedFiles))), TE.bindTo('parsedFiles'), TE.bind('references', ({ parsedFiles }) => TE.right( @@ -80,7 +76,9 @@ export function generateDocs(unparsedBundles: UnparsedSourceBundle[], config: Ma ), ), TE.flatMap(({ parsedFiles, references }) => transformReferenceHook(config)({ references, parsedFiles })), - TE.map(({ parsedFiles, references }) => convertToRenderableBundle(filterOutCustomFields(parsedFiles), references)), + TE.map(({ parsedFiles, references }) => + convertToRenderableBundle(filterOutCustomFieldsAndMetadata(parsedFiles), references), + ), TE.map(convertToDocumentationBundleForTemplate), TE.flatMap(transformDocumentationBundleHook(config)), TE.map(postHookCompile), diff --git a/src/core/markdown/templates/custom-object-template.ts b/src/core/markdown/templates/custom-object-template.ts index 16d2f99c..261a159f 100644 --- a/src/core/markdown/templates/custom-object-template.ts +++ b/src/core/markdown/templates/custom-object-template.ts @@ -39,4 +39,21 @@ export const customObjectTemplate = ` {{/each}} {{/if}} +{{#if hasRecords}} +{{ heading metadataRecords.headingLevel metadataRecords.heading }} +{{#each metadataRecords.value}} +{{ heading headingLevel heading }} + +{{#if description}} +{{{renderContent description}}} +{{/if}} + +**API Name** + +\`{{{apiName}}}\` + +{{#unless @last}}---{{/unless}} +{{/each}} +{{/if}} + `.trim(); diff --git a/src/core/reflection/sobject/reflect-custom-metadata-source.ts b/src/core/reflection/sobject/reflect-custom-metadata-source.ts index 4b8e179c..e6d72e6b 100644 --- a/src/core/reflection/sobject/reflect-custom-metadata-source.ts +++ b/src/core/reflection/sobject/reflect-custom-metadata-source.ts @@ -9,6 +9,7 @@ import { XMLParser } from 'fast-xml-parser'; export type CustomMetadataMetadata = { type_name: 'custommetadata'; protected: boolean; + apiName: string; name: string; label?: string | null; description: string | null; @@ -29,7 +30,7 @@ function reflectCustomMetadataSource( E.tryCatch(() => new XMLParser().parse(customMetadataSource.content), E.toError), E.flatMap(validate), E.map(toCustomMetadataMetadata), - E.map((metadata) => addName(metadata, customMetadataSource.name)), + E.map((metadata) => addNames(metadata, customMetadataSource.name, customMetadataSource.apiName)), E.map((metadata) => addParentName(metadata, customMetadataSource.parentName)), E.map((metadata) => toParsedFile(customMetadataSource.filePath, metadata)), E.mapLeft((error) => new ReflectionErrors([new ReflectionError(customMetadataSource.filePath, error.message)])), @@ -68,8 +69,8 @@ function toCustomMetadataMetadata(parserResult: { CustomMetadata: unknown }): Cu } as CustomMetadataMetadata; } -function addName(metadata: CustomMetadataMetadata, name: string): CustomMetadataMetadata { - return { ...metadata, name }; +function addNames(metadata: CustomMetadataMetadata, name: string, apiName: string): CustomMetadataMetadata { + return { ...metadata, name, apiName }; } function addParentName(metadata: CustomMetadataMetadata, parentName: string): CustomMetadataMetadata { diff --git a/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts b/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts index 3121c94c..157f8354 100644 --- a/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts +++ b/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts @@ -56,43 +56,26 @@ export function reflectCustomFieldsAndObjectsAndMetadataRecords( TE.bind('fields', () => generateForFields(customFields)), TE.bind('metadata', () => generateForMetadata(customMetadata)), TE.map(({ objects, fields, metadata }) => { - return [ - ...mapFieldsToObjects(objects, fields), - ...mapCustomMetadataToObjects(objects, metadata), - ...mapExtensionFields(objects, fields), - ]; + return [...mapFieldsAndMetadata(objects, fields, metadata), ...mapExtensionFields(objects, fields)]; }), ); } -function mapFieldsToObjects( +function mapFieldsAndMetadata( objects: ParsedFile[], fields: ParsedFile[], + metadata: ParsedFile[], ): ParsedFile[] { // Locate the fields for each object by using the parentName property return objects.map((object) => { const objectFields = fields.filter((field) => field.type.parentName === object.type.name); - return { - ...object, - type: { - ...object.type, - fields: [...object.type.fields, ...objectFields.map((field) => field.type)], - }, - }; - }); -} - -function mapCustomMetadataToObjects( - objects: ParsedFile[], - metadata: ParsedFile[], -): ParsedFile[] { - // Locate the metadata for each object by using the parentName property - return objects.map((object) => { const objectMetadata = metadata.filter((meta) => `${meta.type.parentName}__mdt` === object.type.name); + return { ...object, type: { ...object.type, + fields: [...object.type.fields, ...objectFields.map((field) => field.type)], metadataRecords: [...object.type.metadataRecords, ...objectMetadata.map((meta) => meta.type)], }, }; diff --git a/src/core/renderables/types.d.ts b/src/core/renderables/types.d.ts index f54c5fad..cface860 100644 --- a/src/core/renderables/types.d.ts +++ b/src/core/renderables/types.d.ts @@ -181,7 +181,9 @@ export type RenderableCustomObject = Omit & { apiName: string; type: 'customobject'; hasFields: boolean; + hasRecords: boolean; fields: RenderableSection; + metadataRecords: RenderableSection; }; export type RenderableCustomField = { @@ -195,6 +197,17 @@ export type RenderableCustomField = { required: boolean; }; +export type RenderableCustomMetadata = { + headingLevel: number; + heading: string; + apiName: string; + description: RenderableContent[]; + type: 'metadata'; + label: string; + protected: boolean; + // TODO: Add values? +}; + export type Renderable = (RenderableClass | RenderableInterface | RenderableEnum | RenderableCustomObject) & { filePath: string | undefined; }; diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index 2dd5ccf4..e2fdff5c 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 { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources'; -import { CustomFieldMetadata, CustomMetadataMetadata } from '../reflection/sobject/reflect-custom-field-source'; +import { CustomFieldMetadata } from '../reflection/sobject/reflect-custom-field-source'; +import { CustomMetadataMetadata } from '../reflection/sobject/reflect-custom-metadata-source'; export type Generators = 'markdown' | 'openapi' | 'changelog'; @@ -82,6 +83,7 @@ export type UnparsedCustomFieldBundle = { export type UnparsedCustomMetadataBundle = { type: 'custommetadata'; + apiName: string; name: string; filePath: string; content: string; From b760ebdbceb54807f2a3fbc5e0afae625b3f2d2a Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 4 Feb 2025 10:44:30 -0400 Subject: [PATCH 05/20] Records describe if they are protected or not --- examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md | 2 ++ src/core/markdown/templates/custom-object-template.ts | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md b/examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md index ca747a07..edc0ffa8 100644 --- a/examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md +++ b/examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md @@ -22,6 +22,8 @@ title: VisibleCMT__mdt ## Records ### Some Record 1 +`Protected` + **API Name** `VisibleCMT.Some_Record_1` \ No newline at end of file diff --git a/src/core/markdown/templates/custom-object-template.ts b/src/core/markdown/templates/custom-object-template.ts index 261a159f..f33022fd 100644 --- a/src/core/markdown/templates/custom-object-template.ts +++ b/src/core/markdown/templates/custom-object-template.ts @@ -44,6 +44,10 @@ export const customObjectTemplate = ` {{#each metadataRecords.value}} {{ heading headingLevel heading }} +{{#if protected}} +\`Protected\` +{{/if}} + {{#if description}} {{{renderContent description}}} {{/if}} From b162278f8017039fce411dba8b6cedd06ecc5d85 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 4 Feb 2025 11:09:02 -0400 Subject: [PATCH 06/20] Custom Metadata rendering tests --- .../__test__/processing-changelog.spec.ts | 1 + .../generating-custom-object-docs.spec.ts | 73 ++++++++++++++++++- src/core/markdown/__test__/test-helpers.ts | 7 +- .../markdown/adapters/type-to-renderable.ts | 1 - .../sobject/reflect-custom-metadata-source.ts | 5 +- src/core/renderables/types.d.ts | 2 - src/core/test-helpers/test-data-builders.ts | 30 +++++++- 7 files changed, 107 insertions(+), 12 deletions(-) diff --git a/src/core/changelog/__test__/processing-changelog.spec.ts b/src/core/changelog/__test__/processing-changelog.spec.ts index ad8ea5a1..04c00d47 100644 --- a/src/core/changelog/__test__/processing-changelog.spec.ts +++ b/src/core/changelog/__test__/processing-changelog.spec.ts @@ -57,6 +57,7 @@ class CustomObjectMetadataBuilder { name: 'MyObject', description: null, fields: this.fields, + metadataRecords: [], }; } } diff --git a/src/core/markdown/__test__/generating-custom-object-docs.spec.ts b/src/core/markdown/__test__/generating-custom-object-docs.spec.ts index feef912e..572c2901 100644 --- a/src/core/markdown/__test__/generating-custom-object-docs.spec.ts +++ b/src/core/markdown/__test__/generating-custom-object-docs.spec.ts @@ -1,7 +1,10 @@ import { extendExpect } from './expect-extensions'; import { customFieldPickListValues, generateDocs, unparsedObjectBundleFromRawString } from './test-helpers'; import { assertEither } from '../../test-helpers/assert-either'; -import { unparsedFieldBundleFromRawString } from '../../test-helpers/test-data-builders'; +import { + unparsedCustomMetadataFromRawString, + unparsedFieldBundleFromRawString, +} from '../../test-helpers/test-data-builders'; import { CustomObjectXmlBuilder } from '../../test-helpers/test-data-builders/custom-object-xml-builder'; describe('Generates Custom Object documentation', () => { @@ -136,5 +139,73 @@ describe('Generates Custom Object documentation', () => { expect(result).documentationBundleHasLength(1); assertEither(result, (data) => expect(data).firstDocContains('`TestField__c`')); }); + + describe('when documenting Custom Metadata Types', () => { + it('displays the Records heading if fields are present', async () => { + const customObjectBundle = unparsedObjectBundleFromRawString({ + name: 'TestObject__mdt', + rawContent: new CustomObjectXmlBuilder().build(), + filePath: 'src/object/TestObject__mdt.object-meta.xml', + }); + + const customMetadataBundle = unparsedCustomMetadataFromRawString({ + filePath: 'src/customMetadata/TestField__c.field-meta.xml', + parentName: 'TestObject', + apiName: 'TestObject.TestField__c', + }); + + const result = await generateDocs([customObjectBundle, customMetadataBundle])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('## Records')); + }); + + it('does not display the Records heading if no records are present', async () => { + const input = unparsedObjectBundleFromRawString({ + name: 'TestObject__mdt', + rawContent: new CustomObjectXmlBuilder().build(), + filePath: 'src/object/TestObject__c.object-meta.xml', + }); + + const result = await generateDocs([input])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).not.firstDocContains('## Records')); + }); + + it('displays the record label as a heading', async () => { + const customObjectBundle = unparsedObjectBundleFromRawString({ + name: 'TestObject__mdt', + rawContent: new CustomObjectXmlBuilder().build(), + filePath: 'src/object/TestObject__mdt.object-meta.xml', + }); + + const customMetadataBundle = unparsedCustomMetadataFromRawString({ + filePath: 'src/customMetadata/TestField__c.field-meta.xml', + parentName: 'TestObject', + apiName: 'TestObject.TestField__c', + }); + + const result = await generateDocs([customObjectBundle, customMetadataBundle])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('## Test Metadata')); + }); + + it('displays the record api name', async () => { + const customObjectBundle = unparsedObjectBundleFromRawString({ + name: 'TestObject__mdt', + rawContent: new CustomObjectXmlBuilder().build(), + filePath: 'src/object/TestObject__mdt.object-meta.xml', + }); + + const customMetadataBundle = unparsedCustomMetadataFromRawString({ + filePath: 'src/customMetadata/TestField__c.field-meta.xml', + parentName: 'TestObject', + apiName: 'TestObject.TestField__c', + }); + + const result = await generateDocs([customObjectBundle, customMetadataBundle])(); + expect(result).documentationBundleHasLength(1); + assertEither(result, (data) => expect(data).firstDocContains('TestObject.TestField__c')); + }); + }); }); }); diff --git a/src/core/markdown/__test__/test-helpers.ts b/src/core/markdown/__test__/test-helpers.ts index 2f126de6..0ed98827 100644 --- a/src/core/markdown/__test__/test-helpers.ts +++ b/src/core/markdown/__test__/test-helpers.ts @@ -15,17 +15,18 @@ export function unparsedApexBundleFromRawString(raw: string, rawMetadata?: strin export function unparsedObjectBundleFromRawString(meta: { rawContent: string; filePath: string; + name?: string; }): UnparsedCustomObjectBundle { return { type: 'customobject', - name: 'TestObject__c', + name: meta.name ?? 'TestObject__c', filePath: meta.filePath, content: meta.rawContent, }; } -export function generateDocs(apexBundles: UnparsedSourceBundle[], config?: Partial) { - return gen(apexBundles, { +export function generateDocs(bundles: UnparsedSourceBundle[], config?: Partial) { + return gen(bundles, { targetDir: 'target', scope: ['global', 'public'], defaultGroupName: 'Miscellaneous', diff --git a/src/core/markdown/adapters/type-to-renderable.ts b/src/core/markdown/adapters/type-to-renderable.ts index 647c6037..39220d51 100644 --- a/src/core/markdown/adapters/type-to-renderable.ts +++ b/src/core/markdown/adapters/type-to-renderable.ts @@ -305,7 +305,6 @@ function customMetadataToRenderable(metadata: CustomMetadataMetadata, headingLev type: 'metadata', headingLevel: headingLevel, heading: metadata.label ?? metadata.name, - description: metadata.description ? [metadata.description] : [], apiName: metadata.apiName, label: metadata.label ?? metadata.name, protected: metadata.protected, diff --git a/src/core/reflection/sobject/reflect-custom-metadata-source.ts b/src/core/reflection/sobject/reflect-custom-metadata-source.ts index e6d72e6b..7ed4f1c9 100644 --- a/src/core/reflection/sobject/reflect-custom-metadata-source.ts +++ b/src/core/reflection/sobject/reflect-custom-metadata-source.ts @@ -12,9 +12,7 @@ export type CustomMetadataMetadata = { apiName: string; name: string; label?: string | null; - description: string | null; parentName: string; - // TODO: Reflect values }; export function reflectCustomMetadataSources( @@ -57,9 +55,8 @@ function toCustomMetadataMetadata(parserResult: { CustomMetadata: unknown }): Cu parserResult?.CustomMetadata != null && typeof parserResult.CustomMetadata === 'object' ? parserResult.CustomMetadata : {}; - const defaultValues = { + const defaultValues: Partial = { label: null, - description: null, }; return { diff --git a/src/core/renderables/types.d.ts b/src/core/renderables/types.d.ts index cface860..5f1bd03a 100644 --- a/src/core/renderables/types.d.ts +++ b/src/core/renderables/types.d.ts @@ -201,11 +201,9 @@ export type RenderableCustomMetadata = { headingLevel: number; heading: string; apiName: string; - description: RenderableContent[]; type: 'metadata'; label: string; protected: boolean; - // TODO: Add values? }; export type Renderable = (RenderableClass | RenderableInterface | RenderableEnum | RenderableCustomObject) & { diff --git a/src/core/test-helpers/test-data-builders.ts b/src/core/test-helpers/test-data-builders.ts index 4bd4a20e..9c0bed2e 100644 --- a/src/core/test-helpers/test-data-builders.ts +++ b/src/core/test-helpers/test-data-builders.ts @@ -1,4 +1,4 @@ -import { UnparsedCustomFieldBundle } from '../shared/types'; +import { UnparsedCustomFieldBundle, UnparsedCustomMetadataBundle } from '../shared/types'; export const customField = ` @@ -25,3 +25,31 @@ export function unparsedFieldBundleFromRawString(meta: { parentName: meta.parentName, }; } + +export const customMetadata = ` + + + + true + + Field1__c + Sample Value + + +`; + +export function unparsedCustomMetadataFromRawString(meta: { + rawContent?: string; + filePath: string; + apiName: string; + parentName: string; +}): UnparsedCustomMetadataBundle { + return { + type: 'custommetadata', + name: meta.apiName, + filePath: meta.filePath, + content: meta.rawContent ?? customMetadata, + apiName: meta.apiName, + parentName: meta.parentName, + }; +} From 411b134e30444f8b734fd30459c5d5fa538522d6 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 4 Feb 2025 14:34:50 -0400 Subject: [PATCH 07/20] Custom Metadata Type records appear in the changelog --- examples/vitepress/docs/changelog.md | 11 ++++++-- .../VisibleCMT__mdt.object-meta.xml | 6 +++++ .../fields/Field1__c.field-meta.xml | 11 ++++++++ src/application/Apexdocs.ts | 10 +++++-- src/core/changelog/generate-change-log.ts | 4 +-- src/core/changelog/process-changelog.ts | 24 +++++++++++++++-- src/core/changelog/renderable-changelog.ts | 27 +++++++++++++++++-- .../changelog/templates/changelog-template.ts | 15 +++++++++++ src/util/source-bundle-utils.ts | 13 ++------- 9 files changed, 100 insertions(+), 21 deletions(-) create mode 100644 examples/vitepress/previous/force-app/main/default/objects/VisibleCMT__mdt/VisibleCMT__mdt.object-meta.xml create mode 100644 examples/vitepress/previous/force-app/main/default/objects/VisibleCMT__mdt/fields/Field1__c.field-meta.xml diff --git a/examples/vitepress/docs/changelog.md b/examples/vitepress/docs/changelog.md index e20ac3a4..915a7aae 100644 --- a/examples/vitepress/docs/changelog.md +++ b/examples/vitepress/docs/changelog.md @@ -72,7 +72,6 @@ Custom object for tracking sales orders. ### Speaker__c Represents a speaker at an event. -### VisibleCMT__mdt ## New or Removed Fields to Custom Objects or Standard Objects @@ -80,4 +79,12 @@ These custom fields have been added or removed. ### Contact -- New Field: PhotoUrl__c. URL of the contact's photo \ No newline at end of file +- New Field: PhotoUrl__c. URL of the contact's photo + +## New or Removed Custom Metadata Type Records + +These custom metadata type records have been added or removed. + +### VisibleCMT__mdt + +- New Custom Metadata Record: Some_Record_1 \ No newline at end of file diff --git a/examples/vitepress/previous/force-app/main/default/objects/VisibleCMT__mdt/VisibleCMT__mdt.object-meta.xml b/examples/vitepress/previous/force-app/main/default/objects/VisibleCMT__mdt/VisibleCMT__mdt.object-meta.xml new file mode 100644 index 00000000..e4b702db --- /dev/null +++ b/examples/vitepress/previous/force-app/main/default/objects/VisibleCMT__mdt/VisibleCMT__mdt.object-meta.xml @@ -0,0 +1,6 @@ + + + + VisibleCMTs + Public + diff --git a/examples/vitepress/previous/force-app/main/default/objects/VisibleCMT__mdt/fields/Field1__c.field-meta.xml b/examples/vitepress/previous/force-app/main/default/objects/VisibleCMT__mdt/fields/Field1__c.field-meta.xml new file mode 100644 index 00000000..8363be54 --- /dev/null +++ b/examples/vitepress/previous/force-app/main/default/objects/VisibleCMT__mdt/fields/Field1__c.field-meta.xml @@ -0,0 +1,11 @@ + + + Field1__c + false + DeveloperControlled + + 255 + true + Text + false + diff --git a/src/application/Apexdocs.ts b/src/application/Apexdocs.ts index 56550feb..a397c309 100644 --- a/src/application/Apexdocs.ts +++ b/src/application/Apexdocs.ts @@ -72,8 +72,14 @@ async function processOpenApi(config: UserDefinedOpenApiConfig, logger: Logger) async function processChangeLog(config: UserDefinedChangelogConfig) { function loadFiles(): [UnparsedSourceBundle[], UnparsedSourceBundle[]] { return [ - readFiles(['ApexClass', 'CustomObject', 'CustomField'])(config.previousVersionDir, config.exclude), - readFiles(['ApexClass', 'CustomObject', 'CustomField'])(config.currentVersionDir, config.exclude), + readFiles(['ApexClass', 'CustomObject', 'CustomField', 'CustomMetadata'])( + config.previousVersionDir, + config.exclude, + ), + readFiles(['ApexClass', 'CustomObject', 'CustomField', 'CustomMetadata'])( + config.currentVersionDir, + config.exclude, + ), ]; } diff --git a/src/core/changelog/generate-change-log.ts b/src/core/changelog/generate-change-log.ts index d48c9c0a..380f443b 100644 --- a/src/core/changelog/generate-change-log.ts +++ b/src/core/changelog/generate-change-log.ts @@ -21,7 +21,7 @@ import { isInSource, isSkip, passThroughHook, skip, toFrontmatterString } from ' import { reflectCustomFieldsAndObjectsAndMetadataRecords } from '../reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords'; import { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources'; import { Type } from '@cparra/apex-reflection'; -import { filterApexSourceFiles, filterCustomObjectsAndFields } from '#utils/source-bundle-utils'; +import { filterApexSourceFiles, filterCustomObjectsFieldsAndMetadataRecords } from '#utils/source-bundle-utils'; import { CustomFieldMetadata } from '../reflection/sobject/reflect-custom-field-source'; import { hookableTemplate } from '../markdown/templates/hookable'; import changelogToSourceChangelog from './helpers/changelog-to-source-changelog'; @@ -71,7 +71,7 @@ function reflect(bundles: UnparsedSourceBundle[], config: Omit { return pipe( - reflectCustomFieldsAndObjectsAndMetadataRecords(filterCustomObjectsAndFields(bundles)), + reflectCustomFieldsAndObjectsAndMetadataRecords(filterCustomObjectsFieldsAndMetadataRecords(bundles)), TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]), ); }), diff --git a/src/core/changelog/process-changelog.ts b/src/core/changelog/process-changelog.ts index 086584b7..64aeab26 100644 --- a/src/core/changelog/process-changelog.ts +++ b/src/core/changelog/process-changelog.ts @@ -19,7 +19,9 @@ type ModificationTypes = | 'NewProperty' | 'RemovedProperty' | 'NewField' - | 'RemovedField'; + | 'RemovedField' + | 'NewCustomMetadataRecord' + | 'RemovedCustomMetadataRecord'; export type MemberModificationType = { __typename: ModificationTypes; @@ -106,7 +108,10 @@ function getNewOrModifiedApexMembers(oldVersion: VersionManifest, newVersion: Ve function getCustomObjectModifications(oldVersion: VersionManifest, newVersion: VersionManifest): NewOrModifiedMember[] { return pipe( getCustomObjectsInBothVersions(oldVersion, newVersion), - (customObjectsInBoth) => getNewOrRemovedCustomFields(customObjectsInBoth), + (customObjectsInBoth) => [ + ...getNewOrRemovedCustomFields(customObjectsInBoth), + ...getNewOrRemovedCustomMetadataRecords(customObjectsInBoth), + ], (customObjectModifications) => customObjectModifications.filter((member) => member.modifications.length > 0), ); } @@ -180,6 +185,21 @@ function getNewOrRemovedCustomFields(typesInBoth: TypeInBoth[]): NewOrModifiedMember[] { + return typesInBoth.map(({ oldType, newType }) => { + const oldCustomObject = oldType; + const newCustomObject = newType; + + return { + typeName: newType.name, + modifications: [ + ...getNewValues(oldCustomObject, newCustomObject, 'metadataRecords', 'NewCustomMetadataRecord'), + ...getRemovedValues(oldCustomObject, newCustomObject, 'metadataRecords', 'RemovedCustomMetadataRecord'), + ], + }; + }); +} + function getNewOrModifiedEnumValues(typesInBoth: TypeInBoth[]): NewOrModifiedMember[] { return pipe( typesInBoth.filter((typeInBoth): typeInBoth is TypeInBoth => typeInBoth.oldType.type_name === 'enum'), diff --git a/src/core/changelog/renderable-changelog.ts b/src/core/changelog/renderable-changelog.ts index 2ec86c49..825feccb 100644 --- a/src/core/changelog/renderable-changelog.ts +++ b/src/core/changelog/renderable-changelog.ts @@ -44,6 +44,7 @@ export type RenderableChangelog = { newCustomObjects: NewTypeSection<'customobject'> | null; removedCustomObjects: RemovedTypeSection | null; newOrRemovedCustomFields: NewOrModifiedMembersSection | null; + newOrRemovedCustomMetadataTypeRecords: NewOrModifiedMembersSection | null; }; export function convertToRenderableChangelog( @@ -60,6 +61,16 @@ export function convertToRenderableChangelog( const newCustomObjects = allNewTypes.filter( (type): type is CustomObjectMetadata => type.type_name === 'customobject', ); + const newOrModifiedCustomFields = changelog.customObjectModifications.filter( + (modification): modification is NewOrModifiedMember => + modification.modifications.some((mod) => mod.__typename === 'NewField' || mod.__typename === 'RemovedField'), + ); + const newOrModifiedCustomMetadataTypeRecords = changelog.customObjectModifications.filter( + (modification): modification is NewOrModifiedMember => + modification.modifications.some( + (mod) => mod.__typename === 'NewCustomMetadataRecord' || mod.__typename === 'RemovedCustomMetadataRecord', + ), + ); return { newClasses: @@ -122,11 +133,19 @@ export function convertToRenderableChangelog( } : null, newOrRemovedCustomFields: - changelog.customObjectModifications.length > 0 + newOrModifiedCustomFields.length > 0 ? { heading: 'New or Removed Fields to Custom Objects or Standard Objects', description: 'These custom fields have been added or removed.', - modifications: changelog.customObjectModifications.map(toRenderableModification), + modifications: newOrModifiedCustomFields.map(toRenderableModification), + } + : null, + newOrRemovedCustomMetadataTypeRecords: + newOrModifiedCustomMetadataTypeRecords.length > 0 + ? { + heading: 'New or Removed Custom Metadata Type Records', + description: 'These custom metadata type records have been added or removed.', + modifications: newOrModifiedCustomMetadataTypeRecords.map(toRenderableModification), } : null, }; @@ -180,5 +199,9 @@ function toRenderableModificationDescription(memberModificationType: MemberModif return `New Type: ${withDescription(memberModificationType)}`; case 'RemovedType': return `Removed Type: ${memberModificationType.name}`; + case 'NewCustomMetadataRecord': + return `New Custom Metadata Record: ${withDescription(memberModificationType)}`; + case 'RemovedCustomMetadataRecord': + return `Removed Custom Metadata Record: ${memberModificationType.name}`; } } diff --git a/src/core/changelog/templates/changelog-template.ts b/src/core/changelog/templates/changelog-template.ts index eb6f431b..26ec2d79 100644 --- a/src/core/changelog/templates/changelog-template.ts +++ b/src/core/changelog/templates/changelog-template.ts @@ -95,6 +95,21 @@ export const changelogTemplate = ` - {{this}} {{/each}} +{{/each}} +{{/if}} + +{{#if newOrRemovedCustomMetadataTypeRecords}} +## {{newOrRemovedCustomMetadataTypeRecords.heading}} + +{{newOrRemovedCustomMetadataTypeRecords.description}} + +{{#each newOrRemovedCustomMetadataTypeRecords.modifications}} +### {{this.typeName}} + +{{#each this.modifications}} +- {{this}} +{{/each}} + {{/each}} {{/if}} `.trim(); diff --git a/src/util/source-bundle-utils.ts b/src/util/source-bundle-utils.ts index 9eae0ede..dc63676c 100644 --- a/src/util/source-bundle-utils.ts +++ b/src/util/source-bundle-utils.ts @@ -1,6 +1,7 @@ import { UnparsedApexBundle, - UnparsedCustomFieldBundle, UnparsedCustomMetadataBundle, + UnparsedCustomFieldBundle, + UnparsedCustomMetadataBundle, UnparsedCustomObjectBundle, UnparsedSourceBundle, } from '../core/shared/types'; @@ -9,16 +10,6 @@ export function filterApexSourceFiles(sourceFiles: UnparsedSourceBundle[]): Unpa return sourceFiles.filter((sourceFile): sourceFile is UnparsedApexBundle => sourceFile.type === 'apex'); } -// TODO: The changelog still uses this -export function filterCustomObjectsAndFields( - sourceFiles: UnparsedSourceBundle[], -): (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle)[] { - return sourceFiles.filter( - (sourceFile): sourceFile is UnparsedCustomObjectBundle => - sourceFile.type === 'customobject' || sourceFile.type === 'customfield', - ); -} - export function filterCustomObjectsFieldsAndMetadataRecords( sourceFiles: UnparsedSourceBundle[], ): (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle | UnparsedCustomMetadataBundle)[] { From 6515faa2aa884d1de98eef975b9c356f3338dd17 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 4 Feb 2025 16:44:12 -0400 Subject: [PATCH 08/20] Changelog unit tests --- .../helpers/custom-field-metadata-builder.ts | 28 ++++ .../custom-metadata-metadata-builder.ts | 22 ++++ .../helpers/custom-object-metadata-builder.ts | 32 +++++ .../__test__/processing-changelog.spec.ts | 122 ++++++++++-------- 4 files changed, 152 insertions(+), 52 deletions(-) create mode 100644 src/core/changelog/__test__/helpers/custom-field-metadata-builder.ts create mode 100644 src/core/changelog/__test__/helpers/custom-metadata-metadata-builder.ts create mode 100644 src/core/changelog/__test__/helpers/custom-object-metadata-builder.ts diff --git a/src/core/changelog/__test__/helpers/custom-field-metadata-builder.ts b/src/core/changelog/__test__/helpers/custom-field-metadata-builder.ts new file mode 100644 index 00000000..6bdc8e5d --- /dev/null +++ b/src/core/changelog/__test__/helpers/custom-field-metadata-builder.ts @@ -0,0 +1,28 @@ +import { CustomFieldMetadata } from '../../../reflection/sobject/reflect-custom-field-source'; + +export default class CustomFieldMetadataBuilder { + name: string = 'MyField'; + description: string | null = null; + + withName(name: string): CustomFieldMetadataBuilder { + this.name = name; + return this; + } + + withDescription(testDescription: string) { + this.description = testDescription; + return this; + } + + build(): CustomFieldMetadata { + return { + type: 'Text', + type_name: 'customfield', + label: 'MyField', + name: this.name, + description: this.description, + parentName: 'MyObject', + required: false, + }; + } +} diff --git a/src/core/changelog/__test__/helpers/custom-metadata-metadata-builder.ts b/src/core/changelog/__test__/helpers/custom-metadata-metadata-builder.ts new file mode 100644 index 00000000..62241fb5 --- /dev/null +++ b/src/core/changelog/__test__/helpers/custom-metadata-metadata-builder.ts @@ -0,0 +1,22 @@ +import { CustomMetadataMetadata } from '../../../reflection/sobject/reflect-custom-metadata-source'; + +export default class CustomMetadataMetadataBuilder { + parentName: string = 'MyObject'; + name: string = 'FieldName__c'; + + withName(name: string): CustomMetadataMetadataBuilder { + this.name = name; + return this; + } + + build(): CustomMetadataMetadata { + return { + type_name: 'custommetadata', + apiName: `${this.parentName}.${this.name}`, + protected: false, + label: 'MyMetadata', + name: this.name, + parentName: this.parentName, + }; + } +} diff --git a/src/core/changelog/__test__/helpers/custom-object-metadata-builder.ts b/src/core/changelog/__test__/helpers/custom-object-metadata-builder.ts new file mode 100644 index 00000000..62c6a61a --- /dev/null +++ b/src/core/changelog/__test__/helpers/custom-object-metadata-builder.ts @@ -0,0 +1,32 @@ +import { CustomFieldMetadata } from '../../../reflection/sobject/reflect-custom-field-source'; +import { CustomObjectMetadata } from '../../../reflection/sobject/reflect-custom-object-sources'; +import { CustomMetadataMetadata } from '../../../reflection/sobject/reflect-custom-metadata-source'; + +export default class CustomObjectMetadataBuilder { + label: string = 'MyObject'; + fields: CustomFieldMetadata[] = []; + metadataRecords: CustomMetadataMetadata[] = []; + + withField(field: CustomFieldMetadata): CustomObjectMetadataBuilder { + this.fields.push(field); + return this; + } + + withMetadataRecord(metadataRecord: CustomMetadataMetadata): CustomObjectMetadataBuilder { + this.metadataRecords.push(metadataRecord); + return this; + } + + build(): CustomObjectMetadata { + return { + type_name: 'customobject', + deploymentStatus: 'Deployed', + visibility: 'Public', + label: this.label, + name: 'MyObject', + description: null, + fields: this.fields, + metadataRecords: this.metadataRecords, + }; + } +} diff --git a/src/core/changelog/__test__/processing-changelog.spec.ts b/src/core/changelog/__test__/processing-changelog.spec.ts index 04c00d47..6eccbb8b 100644 --- a/src/core/changelog/__test__/processing-changelog.spec.ts +++ b/src/core/changelog/__test__/processing-changelog.spec.ts @@ -1,7 +1,8 @@ import { processChangelog } from '../process-changelog'; import { reflect, Type } from '@cparra/apex-reflection'; -import { CustomObjectMetadata } from '../../reflection/sobject/reflect-custom-object-sources'; -import { CustomFieldMetadata } from '../../reflection/sobject/reflect-custom-field-source'; +import CustomFieldMetadataBuilder from './helpers/custom-field-metadata-builder'; +import CustomObjectMetadataBuilder from './helpers/custom-object-metadata-builder'; +import CustomMetadataMetadataBuilder from './helpers/custom-metadata-metadata-builder'; function apexTypeFromRawString(raw: string): Type { const result = reflect(raw); @@ -12,56 +13,6 @@ function apexTypeFromRawString(raw: string): Type { return result.typeMirror!; } -class CustomFieldMetadataBuilder { - name: string = 'MyField'; - description: string | null = null; - - withName(name: string): CustomFieldMetadataBuilder { - this.name = name; - return this; - } - - withDescription(testDescription: string) { - this.description = testDescription; - return this; - } - - build(): CustomFieldMetadata { - return { - type: 'Text', - type_name: 'customfield', - label: 'MyField', - name: this.name, - description: this.description, - parentName: 'MyObject', - required: false, - }; - } -} - -class CustomObjectMetadataBuilder { - label: string = 'MyObject'; - fields: CustomFieldMetadata[] = []; - - withField(field: CustomFieldMetadata): CustomObjectMetadataBuilder { - this.fields.push(field); - return this; - } - - build(): CustomObjectMetadata { - return { - type_name: 'customobject', - deploymentStatus: 'Deployed', - visibility: 'Public', - label: this.label, - name: 'MyObject', - description: null, - fields: this.fields, - metadataRecords: [], - }; - } -} - describe('when generating a changelog', () => { it('has no new types when both the old and new versions are empty', () => { const oldVersion = { types: [] }; @@ -669,4 +620,71 @@ describe('when generating a changelog', () => { ]); }); }); + + describe('with custom metadata records', () => { + it('does not list custom metadata records that are the same in both versions', () => { + // The record uniqueness is determined by its api name. + + const oldCustomMetadata = new CustomMetadataMetadataBuilder().build(); + const newCustomMetadata = new CustomMetadataMetadataBuilder().build(); + + const oldManifest = { types: [oldCustomMetadata] }; + const newManifest = { types: [newCustomMetadata] }; + + const changeLog = processChangelog(oldManifest, newManifest); + + expect(changeLog.customObjectModifications).toEqual([]); + }); + + it('lists new records of a custom object', () => { + const oldObject = new CustomObjectMetadataBuilder() + .withMetadataRecord(new CustomMetadataMetadataBuilder().build()) + .build(); + const newObject = new CustomObjectMetadataBuilder() + .withMetadataRecord(new CustomMetadataMetadataBuilder().build()) + .withMetadataRecord(new CustomMetadataMetadataBuilder().withName('NewField__c').build()) + .build(); + + const oldManifest = { types: [oldObject] }; + const newManifest = { types: [newObject] }; + + const changeLog = processChangelog(oldManifest, newManifest); + + expect(changeLog.customObjectModifications).toEqual([ + { + typeName: newObject.name, + modifications: [ + { + __typename: 'NewCustomMetadataRecord', + name: 'NewField__c', + }, + ], + }, + ]); + }); + + it('lists removed records of a custom object', () => { + const oldObject = new CustomObjectMetadataBuilder() + .withMetadataRecord(new CustomMetadataMetadataBuilder().withName('OldField__c').build()) + .build(); + const newObject = new CustomObjectMetadataBuilder().build(); + + const oldManifest = { types: [oldObject] }; + const newManifest = { types: [newObject] }; + + const changeLog = processChangelog(oldManifest, newManifest); + + expect(changeLog.customObjectModifications).toEqual([ + { + typeName: oldObject.name, + modifications: [ + { + __typename: 'RemovedCustomMetadataRecord', + name: 'OldField__c', + }, + ], + }, + ]); + }); + }); }); From 3fe9d70ad70c0d50d366626be88e6718422607f9 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 4 Feb 2025 16:51:24 -0400 Subject: [PATCH 09/20] Doc updates --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 7f0ed99e..dd96e039 100644 --- a/README.md +++ b/README.md @@ -309,6 +309,15 @@ export default defineMarkdownConfig({ }); ``` +You can also leverage the `exclude` property to indirectly modify things like custom metadata records you do +not want included in the custom metadata type object documentation. + +```typescript +//... +exclude: ['**/*.md-meta.xml'] +//... +``` + ### Excluding Tags from Appearing in the Documentation Note: Only works for Markdown documentation. From 5f9f0fd2846ac8f8e35d851e18cc44bd5c8bbca3 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 4 Feb 2025 16:57:41 -0400 Subject: [PATCH 10/20] Refactorings --- src/application/Apexdocs.ts | 14 ++++---------- src/application/source-code-file-reader.ts | 3 ++- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/application/Apexdocs.ts b/src/application/Apexdocs.ts index a397c309..0369760a 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 { processFiles } from './source-code-file-reader'; +import { allComponentTypes, processFiles } from './source-code-file-reader'; import { DefaultFileSystem } from './file-system'; import { Logger } from '#utils/logger'; import { @@ -52,7 +52,7 @@ async function processMarkdown(config: UserDefinedMarkdownConfig) { return pipe( E.tryCatch( () => - readFiles(['ApexClass', 'CustomObject', 'CustomField', 'CustomMetadata'], { + readFiles(allComponentTypes, { includeMetadata: config.includeMetadata, })(config.sourceDir, config.exclude), (e) => new FileReadingError('An error occurred while reading files.', e), @@ -72,14 +72,8 @@ async function processOpenApi(config: UserDefinedOpenApiConfig, logger: Logger) async function processChangeLog(config: UserDefinedChangelogConfig) { function loadFiles(): [UnparsedSourceBundle[], UnparsedSourceBundle[]] { return [ - readFiles(['ApexClass', 'CustomObject', 'CustomField', 'CustomMetadata'])( - config.previousVersionDir, - config.exclude, - ), - readFiles(['ApexClass', 'CustomObject', 'CustomField', 'CustomMetadata'])( - config.currentVersionDir, - config.exclude, - ), + readFiles(allComponentTypes)(config.previousVersionDir, config.exclude), + readFiles(allComponentTypes)(config.currentVersionDir, config.exclude), ]; } diff --git a/src/application/source-code-file-reader.ts b/src/application/source-code-file-reader.ts index b01f796f..96bcaaf2 100644 --- a/src/application/source-code-file-reader.ts +++ b/src/application/source-code-file-reader.ts @@ -9,7 +9,8 @@ import { minimatch } from 'minimatch'; import { flow, pipe } from 'fp-ts/function'; import { apply } from '#utils/fp'; -type ComponentTypes = 'ApexClass' | 'CustomObject' | 'CustomField' | 'CustomMetadata'; +export type ComponentTypes = 'ApexClass' | 'CustomObject' | 'CustomField' | 'CustomMetadata'; +export const allComponentTypes: ComponentTypes[] = ['ApexClass', 'CustomObject', 'CustomField', 'CustomMetadata']; /** * Simplified representation of a source component, with only From 98521ab42b80985c1c52bf4a3719f396300ac807 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 4 Feb 2025 17:00:48 -0400 Subject: [PATCH 11/20] 3.8 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d4c505b3..bbae05bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cparra/apexdocs", - "version": "3.7.3", + "version": "3.8.0", "description": "Library with CLI capabilities to generate documentation for Salesforce Apex classes.", "keywords": [ "apex", From 7a8fe4a69db7b23e1eb6b00c26c51b37a97cae5c Mon Sep 17 00:00:00 2001 From: cesarParra Date: Thu, 6 Feb 2025 10:58:08 -0400 Subject: [PATCH 12/20] Setting up wireit --- package-lock.json | 220 ++++++++++++++++++++++++++-------------------- package.json | 31 ++++++- 2 files changed, 153 insertions(+), 98 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ac4c68c..2059b125 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cparra/apexdocs", - "version": "3.7.2", + "version": "3.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cparra/apexdocs", - "version": "3.7.2", + "version": "3.8.0", "license": "MIT", "dependencies": { "@cparra/apex-reflection": "2.16.1", @@ -38,10 +38,10 @@ "lint-staged": "^15.2.7", "pkgroll": "^2.4.2", "prettier": "^3.3.2", - "rimraf": "^6.0.1", "ts-jest": "^29.2.0", "typescript": "^5.5.3", - "typescript-eslint": "^7.16.0" + "typescript-eslint": "^7.16.0", + "wireit": "^0.14.10" } }, "node_modules/@ampproject/remapping": { @@ -3347,6 +3347,18 @@ "node": ">=10.0.0" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3667,6 +3679,42 @@ "node": ">=10" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -5500,6 +5548,18 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-builtin-module": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", @@ -5722,25 +5782,6 @@ "node": ">=8" } }, - "node_modules/jackspeak": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", - "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -6992,6 +7033,12 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -7566,16 +7613,6 @@ "node": ">=8" } }, - "node_modules/lru-cache": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", - "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/magic-string": { "version": "0.30.10", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", @@ -8119,23 +8156,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -8619,6 +8639,18 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/real-require": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", @@ -8744,50 +8776,6 @@ "dev": true, "license": "MIT" }, - "node_modules/rimraf": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", - "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^11.0.0", - "package-json-from-dist": "^1.0.0" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", - "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { "version": "4.22.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", @@ -9725,6 +9713,50 @@ "node": ">= 8" } }, + "node_modules/wireit": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/wireit/-/wireit-0.14.10.tgz", + "integrity": "sha512-Y9wiNU92PcyfTcXRYzqjmilKl4Yfg30Jk/dwTN0e64JCkzoIP2QVo6gc8fjYK0gpL0/pq2IW+iMlknHLmLV+MQ==", + "dev": true, + "workspaces": [ + "vscode-extension", + "website" + ], + "dependencies": { + "brace-expansion": "^4.0.0", + "chokidar": "^3.5.3", + "fast-glob": "^3.2.11", + "jsonc-parser": "^3.0.0", + "proper-lockfile": "^4.1.2" + }, + "bin": { + "wireit": "bin/wireit.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/wireit/node_modules/balanced-match": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-3.0.1.tgz", + "integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/wireit/node_modules/brace-expansion": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-4.0.0.tgz", + "integrity": "sha512-l/mOwLWs7BQIgOKrL46dIAbyCKvPV7YJPDspkuc88rHsZRlg3hptUGdU7Trv0VFP4d3xnSGBQrKu5ZvGB7UeIw==", + "dev": true, + "dependencies": { + "balanced-match": "^3.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index bbae05bd..2e469d81 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,35 @@ "scripts": { "test": "npm run build && jest", "test:cov": "npm run build && jest --coverage", - "build": "rimraf ./dist && npm run lint && tsc --noEmit && pkgroll", - "lint": "eslint \"./src/**/*.{js,ts}\" --quiet --fix", + "build": "wireit", + "lint": "wireit", "prepare": "npm run build", "version": "npm run format && git add -A src", "postversion": "git push && git push --tags" }, + "wireit": { + "lint": { + "command": "eslint \"./src/**/*.{js,ts}\" --quiet --fix", + "files": [ + "src/**/*.ts", + "src/**/*.js" + ], + "output": [] + }, + "build": { + "command": "tsc --noEmit --pretty && pkgroll", + "dependencies": [ + "lint" + ], + "files": [ + "src/**/*.ts", + "tsconfig.json" + ], + "output": [ + "dist" + ] + } + }, "author": "Cesar Parra", "license": "MIT", "repository": { @@ -45,10 +68,10 @@ "lint-staged": "^15.2.7", "pkgroll": "^2.4.2", "prettier": "^3.3.2", - "rimraf": "^6.0.1", "ts-jest": "^29.2.0", "typescript": "^5.5.3", - "typescript-eslint": "^7.16.0" + "typescript-eslint": "^7.16.0", + "wireit": "^0.14.10" }, "husky": { "hooks": { From 7f3cafc870dc6c9bf754bc838d70e1e33291fd44 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Thu, 6 Feb 2025 11:02:50 -0400 Subject: [PATCH 13/20] Setting up wireit --- .gitignore | 3 ++- package.json | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 18a056fc..3ef6256b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ npm-debug.log /dist/ /lib/ .sfdx/ +.wireit/ # Added by Illuminated Cloud .localdev/ @@ -16,4 +17,4 @@ npm-debug.log target/ /.illuminatedCloud/ **/tsconfig*.json -**/*.tsbuildinfo \ No newline at end of file +**/*.tsbuildinfo diff --git a/package.json b/package.json index 2e469d81..840a563c 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "apexdocs": "./dist/cli/generate.js" }, "scripts": { - "test": "npm run build && jest", + "test": "wireit", "test:cov": "npm run build && jest --coverage", "build": "wireit", "lint": "wireit", @@ -31,8 +31,7 @@ "lint": { "command": "eslint \"./src/**/*.{js,ts}\" --quiet --fix", "files": [ - "src/**/*.ts", - "src/**/*.js" + "src/**/*.ts" ], "output": [] }, @@ -48,6 +47,16 @@ "output": [ "dist" ] + }, + "test": { + "command": "jest", + "dependencies": [ + "build" + ], + "files": [ + "src/**/*.ts" + ], + "output": [] } }, "author": "Cesar Parra", From 5fb596453c78a49b4d3a498281cee85b59b9c6d4 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Thu, 6 Feb 2025 11:04:16 -0400 Subject: [PATCH 14/20] Setting up caching for the GH Action --- .github/workflows/ci.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9683d0f5..1385ddc9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,9 +2,9 @@ name: CI on: push: - branches: ["master", "develop"] + branches: [ "master", "develop" ] pull_request: - branches: ["main", "master", "develop"] + branches: [ "main", "master", "develop" ] workflow_dispatch: jobs: @@ -17,6 +17,10 @@ jobs: with: node-version: "20" cache: "npm" + + # Set up GitHub Actions caching for Wireit. + - uses: google/wireit@setup-github-actions-caching/v2 + - run: npm ci - run: npm run build --if-present - run: npm test From 93bce7e7e456576368d349a4801b1eb98f2fc1e9 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Thu, 6 Feb 2025 11:04:39 -0400 Subject: [PATCH 15/20] Setting up caching for the GH Action --- .github/workflows/ci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1385ddc9..0a86bf5d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,5 +22,4 @@ jobs: - uses: google/wireit@setup-github-actions-caching/v2 - run: npm ci - - run: npm run build --if-present - run: npm test From 697b79b3ebee2a3ff1c2a8db36d5aeb796bc336b Mon Sep 17 00:00:00 2001 From: cesarParra Date: Thu, 6 Feb 2025 11:16:19 -0400 Subject: [PATCH 16/20] Upgrading the Typescript version --- package-lock.json | 22 ++++++++++------------ package.json | 2 +- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2059b125..fb3c014a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "pkgroll": "^2.4.2", "prettier": "^3.3.2", "ts-jest": "^29.2.0", - "typescript": "^5.5.3", + "typescript": "^5.7.3", "typescript-eslint": "^7.16.0", "wireit": "^0.14.10" } @@ -4034,9 +4034,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -7681,11 +7681,10 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -9501,10 +9500,9 @@ } }, "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", - "license": "Apache-2.0", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 840a563c..4acf912b 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "pkgroll": "^2.4.2", "prettier": "^3.3.2", "ts-jest": "^29.2.0", - "typescript": "^5.5.3", + "typescript": "^5.7.3", "typescript-eslint": "^7.16.0", "wireit": "^0.14.10" }, From c756c675f4e8965d8671023bf4974d1cad4b2449 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 11 Feb 2025 07:15:39 -0400 Subject: [PATCH 17/20] Configurable custom object visibility --- src/cli/commands/markdown.ts | 8 +++++++ src/core/changelog/generate-change-log.ts | 4 +++- .../markdown/__test__/generating-docs.spec.ts | 24 +++++++++++++++---- src/core/markdown/__test__/test-helpers.ts | 1 + .../__tests__/interface-adapter.spec.ts | 1 + src/core/markdown/generate-docs.ts | 5 +++- ...ustomFieldsAndObjectsAndMetadataRecords.ts | 5 +++- src/core/shared/types.d.ts | 1 + src/defaults.ts | 1 + 9 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/cli/commands/markdown.ts b/src/cli/commands/markdown.ts index e029d3b8..34c50637 100644 --- a/src/cli/commands/markdown.ts +++ b/src/cli/commands/markdown.ts @@ -24,6 +24,14 @@ export const markdownOptions: Record { return pipe( - reflectCustomFieldsAndObjectsAndMetadataRecords(filterCustomObjectsFieldsAndMetadataRecords(bundles)), + reflectCustomFieldsAndObjectsAndMetadataRecords(filterCustomObjectsFieldsAndMetadataRecords(bundles), [ + 'public', // TODO: Do not hardcode + ]), TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]), ); }), diff --git a/src/core/markdown/__test__/generating-docs.spec.ts b/src/core/markdown/__test__/generating-docs.spec.ts index bce18c9e..2ed1df82 100644 --- a/src/core/markdown/__test__/generating-docs.spec.ts +++ b/src/core/markdown/__test__/generating-docs.spec.ts @@ -160,11 +160,25 @@ describe('When generating documentation', () => { expect(result).documentationBundleHasLength(0); }); - it('does not return non-public custom objects', async () => { - const input = new CustomObjectXmlBuilder().withVisibility('Protected').build(); - - const result = await generateDocs([unparsedObjectBundleFromRawString({ rawContent: input, filePath: 'test' })])(); - expect(result).documentationBundleHasLength(0); + describe('and the custom object visibility', () => { + it('is not set, it does not return non-public custom objects', async () => { + const input = new CustomObjectXmlBuilder().withVisibility('Protected').build(); + + const result = await generateDocs([ + unparsedObjectBundleFromRawString({ rawContent: input, filePath: 'test' }), + ])(); + expect(result).documentationBundleHasLength(0); + }); + + it('is configured, it respects the configured visibility', async () => { + const input = new CustomObjectXmlBuilder().withVisibility('Protected').build(); + + const result = await generateDocs( + [unparsedObjectBundleFromRawString({ rawContent: input, filePath: 'test' })], + { customObjectVisibility: ['protected'] }, + )(); + expect(result).documentationBundleHasLength(1); + }); }); it('do not return files that have an @ignore in the docs', async () => { diff --git a/src/core/markdown/__test__/test-helpers.ts b/src/core/markdown/__test__/test-helpers.ts index 0ed98827..e71b998c 100644 --- a/src/core/markdown/__test__/test-helpers.ts +++ b/src/core/markdown/__test__/test-helpers.ts @@ -29,6 +29,7 @@ export function generateDocs(bundles: UnparsedSourceBundle[], config?: Partial { return pipe( - reflectCustomFieldsAndObjectsAndMetadataRecords(filterCustomObjectsFieldsAndMetadataRecords(unparsedBundles)), + reflectCustomFieldsAndObjectsAndMetadataRecords( + filterCustomObjectsFieldsAndMetadataRecords(unparsedBundles), + config.customObjectVisibility, + ), TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]), ); }), diff --git a/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts b/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts index 157f8354..8505fa3f 100644 --- a/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts +++ b/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts @@ -14,13 +14,16 @@ import { CustomMetadataMetadata, reflectCustomMetadataSources } from './reflect- export function reflectCustomFieldsAndObjectsAndMetadataRecords( objectBundles: (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle | UnparsedCustomMetadataBundle)[], + visibilitiesToDocument: string[], ): TaskEither[]> { 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 parsedFiles.filter((parsedFile) => + visibilitiesToDocument.includes(parsedFile.type.visibility.toLowerCase()), + ); } const customObjects = objectBundles.filter( diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index e2fdff5c..457474d8 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -20,6 +20,7 @@ export type CliConfigurableMarkdownConfig = { sourceDir: string; targetDir: string; scope: string[]; + customObjectVisibility: string[]; namespace?: string; defaultGroupName: string; customObjectsGroupName: string; diff --git a/src/defaults.ts b/src/defaults.ts index 8510cf7a..399988d0 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -3,6 +3,7 @@ const commonDefaults = { }; export const markdownDefaults = { + customObjectVisibility: ['public'], ...commonDefaults, scope: ['global'], defaultGroupName: 'Miscellaneous', From 486996054f9daa718d2b85413ebb41653bb652f1 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 11 Feb 2025 07:38:09 -0400 Subject: [PATCH 18/20] Configurable custom object visibility --- .../docs/.vitepress/cache/deps/_metadata.json | 14 ++++---- ...ustomFieldsAndObjectsAndMetadataRecords.ts | 35 +++++++++++++------ 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/examples/vitepress/docs/.vitepress/cache/deps/_metadata.json b/examples/vitepress/docs/.vitepress/cache/deps/_metadata.json index e710f560..3a5d46ac 100644 --- a/examples/vitepress/docs/.vitepress/cache/deps/_metadata.json +++ b/examples/vitepress/docs/.vitepress/cache/deps/_metadata.json @@ -1,31 +1,31 @@ { - "hash": "05eb2d4f", + "hash": "38118198", "configHash": "7f7b0dad", - "lockfileHash": "3a9c2374", - "browserHash": "a831d6e7", + "lockfileHash": "09651dfc", + "browserHash": "1bd3b4f7", "optimized": { "vue": { "src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js", "file": "vue.js", - "fileHash": "1632d62a", + "fileHash": "12c2c40e", "needsInterop": false }, "vitepress > @vue/devtools-api": { "src": "../../../../node_modules/@vue/devtools-api/dist/index.js", "file": "vitepress___@vue_devtools-api.js", - "fileHash": "dc8fec00", + "fileHash": "7b21a64c", "needsInterop": false }, "vitepress > @vueuse/core": { "src": "../../../../node_modules/@vueuse/core/index.mjs", "file": "vitepress___@vueuse_core.js", - "fileHash": "3d02446b", + "fileHash": "5d565a7a", "needsInterop": false }, "@theme/index": { "src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js", "file": "@theme_index.js", - "fileHash": "3d2d1de3", + "fileHash": "e4373905", "needsInterop": false } }, diff --git a/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts b/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts index 8505fa3f..6bf759a9 100644 --- a/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts +++ b/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts @@ -20,10 +20,20 @@ export function reflectCustomFieldsAndObjectsAndMetadataRecords( return parsedFiles.filter((parsedFile) => parsedFile.type.deploymentStatus === 'Deployed'); } - function filterNonPublic(parsedFiles: ParsedFile[]): ParsedFile[] { - return parsedFiles.filter((parsedFile) => - visibilitiesToDocument.includes(parsedFile.type.visibility.toLowerCase()), - ); + /** + * Returns a tuple of parsed objects to document and the names of the objects that should be actively ignored. + * @param parsedFiles + */ + function filter(parsedFiles: ParsedFile[]): [ParsedFile[], string[]] { + function shouldBeDocumented(parsedFile: ParsedFile): boolean { + return visibilitiesToDocument.includes(parsedFile.type.visibility.toLowerCase()); + } + + const objectsToDocument = parsedFiles.filter(shouldBeDocumented); + const objectsToIgnore = parsedFiles + .filter((parsedFile) => !shouldBeDocumented(parsedFile)) + .map((parsedFile) => parsedFile.type.name); + return [objectsToDocument, objectsToIgnore]; } const customObjects = objectBundles.filter( @@ -54,12 +64,12 @@ export function reflectCustomFieldsAndObjectsAndMetadataRecords( customObjects, reflectCustomObjectSources, TE.map(filterNonPublished), - TE.map(filterNonPublic), - TE.bindTo('objects'), + TE.map(filter), + TE.bindTo('filterResult'), TE.bind('fields', () => generateForFields(customFields)), TE.bind('metadata', () => generateForMetadata(customMetadata)), - TE.map(({ objects, fields, metadata }) => { - return [...mapFieldsAndMetadata(objects, fields, metadata), ...mapExtensionFields(objects, fields)]; + TE.map(({ filterResult, fields, metadata }) => { + return [...mapFieldsAndMetadata(filterResult[0], fields, metadata), ...mapExtensionFields(filterResult, fields)]; }), ); } @@ -88,11 +98,16 @@ function mapFieldsAndMetadata( // "Extension" fields are fields that are in the source code without the corresponding object-meta.xml file. // These are fields that either extend a standard Salesforce object, or an object in a different package. function mapExtensionFields( - objects: ParsedFile[], + filterResult: [ParsedFile[], string[]], fields: ParsedFile[], ): ParsedFile[] { + const objects = filterResult[0]; + const ignoredObjectNames = filterResult[1]; + const extensionFields = fields.filter( - (field) => !objects.some((object) => object.type.name === field.type.parentName), + (field) => + !objects.some((object) => object.type.name.toLowerCase() === field.type.parentName.toLowerCase()) && + !ignoredObjectNames.map((name) => name.toLowerCase()).includes(field.type.parentName.toLowerCase()), ); // There might be many objects for the same parent name, so we need to group the fields by parent name const extensionFieldsByParent = extensionFields.reduce( From 8ffaa516b617d9443c5f740e8c6849a6f07ed0fd Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 11 Feb 2025 08:06:03 -0400 Subject: [PATCH 19/20] Configurable custom object visibility - changelog support --- examples/changelog/docs/changelog.md | 8 +- .../markdown/docs/custom-objects/Event__c.md | 3 + .../docs/custom-objects/Price_Component__c.md | 8 +- .../docs/custom-objects/Product__c.md | 1 + .../custom-objects/Sales_Order_Line__c.md | 9 +- examples/open-api/docs/openapi.json | 572 +++++++++++++++++- src/cli/commands/changelog.ts | 8 + .../__test__/generating-change-log.spec.ts | 1 + src/core/changelog/generate-change-log.ts | 7 +- src/core/shared/types.d.ts | 1 + src/defaults.ts | 14 +- 11 files changed, 617 insertions(+), 15 deletions(-) diff --git a/examples/changelog/docs/changelog.md b/examples/changelog/docs/changelog.md index b567be19..362fafba 100644 --- a/examples/changelog/docs/changelog.md +++ b/examples/changelog/docs/changelog.md @@ -51,7 +51,7 @@ These members have been added or modified. - New Method: newMethod - Removed Method: deprecatedMethod -## New or Removed Fields in Existing Objects +## New or Removed Fields to Custom Objects or Standard Objects These custom fields have been added or removed. @@ -66,4 +66,8 @@ These custom fields have been added or removed. ### Product__c -- New Field: Description__c \ No newline at end of file +- New Field: Description__c + +### Contact + +- New Field: PhotoUrl__c \ No newline at end of file diff --git a/examples/markdown/docs/custom-objects/Event__c.md b/examples/markdown/docs/custom-objects/Event__c.md index 111bcb28..023f2a04 100644 --- a/examples/markdown/docs/custom-objects/Event__c.md +++ b/examples/markdown/docs/custom-objects/Event__c.md @@ -18,6 +18,7 @@ Represents an event that people can register for. --- ### End Date +**Required** **API Name** @@ -29,6 +30,7 @@ Represents an event that people can register for. --- ### Location +**Required** **API Name** @@ -40,6 +42,7 @@ Represents an event that people can register for. --- ### Start Date +**Required** **API Name** diff --git a/examples/markdown/docs/custom-objects/Price_Component__c.md b/examples/markdown/docs/custom-objects/Price_Component__c.md index e2305371..e86e2002 100644 --- a/examples/markdown/docs/custom-objects/Price_Component__c.md +++ b/examples/markdown/docs/custom-objects/Price_Component__c.md @@ -55,6 +55,7 @@ Use this when the Price Component represents a Flat Price. To represent a Percen --- ### Type +**Required** **API Name** @@ -62,4 +63,9 @@ Use this when the Price Component represents a Flat Price. To represent a Percen **Type** -*Picklist* \ No newline at end of file +*Picklist* + +#### Possible values are +* List Price +* Surcharge +* Discount \ No newline at end of file diff --git a/examples/markdown/docs/custom-objects/Product__c.md b/examples/markdown/docs/custom-objects/Product__c.md index 795d2ee1..95bfa4c6 100644 --- a/examples/markdown/docs/custom-objects/Product__c.md +++ b/examples/markdown/docs/custom-objects/Product__c.md @@ -18,6 +18,7 @@ Product that is sold or available for sale. --- ### Event +**Required** **API Name** diff --git a/examples/markdown/docs/custom-objects/Sales_Order_Line__c.md b/examples/markdown/docs/custom-objects/Sales_Order_Line__c.md index da0fb5bf..d99afa40 100644 --- a/examples/markdown/docs/custom-objects/Sales_Order_Line__c.md +++ b/examples/markdown/docs/custom-objects/Sales_Order_Line__c.md @@ -7,6 +7,7 @@ Represents a line item on a sales order. ## Fields ### Amount +**Required** **API Name** @@ -18,6 +19,7 @@ Represents a line item on a sales order. --- ### Product +**Required** **API Name** @@ -51,6 +53,7 @@ Represents a line item on a sales order. --- ### Type +**Required** **API Name** @@ -58,4 +61,8 @@ Represents a line item on a sales order. **Type** -*Picklist* \ No newline at end of file +*Picklist* + +#### Possible values are +* Charge +* Discount \ No newline at end of file diff --git a/examples/open-api/docs/openapi.json b/examples/open-api/docs/openapi.json index 2bec9b4e..f2e60a56 100644 --- a/examples/open-api/docs/openapi.json +++ b/examples/open-api/docs/openapi.json @@ -9,6 +9,574 @@ "url": "/services/apexrest/openapi/" } ], - "paths": {}, - "tags": [] + "paths": { + "/AccountService/": { + "description": "Account related operations", + "get": { + "tags": [ + "Account Service" + ], + "description": "This is a sample HTTP Get method", + "parameters": [ + { + "name": "limit", + "in": "query", + "required": true, + "description": "Limits the number of items on a page", + "schema": { + "type": "integer" + } + }, + { + "name": "complex", + "in": "cookie", + "description": "A more complex schema", + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + } + ], + "responses": { + "100": { + "description": "Status code 100", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "anotherObject": { + "description": "An object inside of an object", + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "somethingElse": { + "type": "number" + } + } + } + } + } + } + } + }, + "200": { + "description": "Status code 200", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The super Id." + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string", + "format": "byte" + } + } + } + } + } + }, + "304": { + "description": "Status code 304", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Status code 400", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + } + } + }, + "500": { + "description": "Status code 500", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "post": { + "tags": [ + "Account Service" + ], + "description": "This is a sample HTTP Post method", + "summary": "Posts an Account 2", + "requestBody": { + "description": "This is an example of a request body", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "limit", + "in": "query", + "required": true, + "description": "Limits the number of items on a page", + "schema": { + "type": "integer" + } + }, + { + "name": "complex", + "in": "cookie", + "description": "A more complex schema", + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + } + ], + "responses": { + "200": { + "description": "Status code 200", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The super Id." + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string", + "format": "byte" + } + } + } + } + } + }, + "304": { + "description": "Status code 304", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Status code 400", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + } + } + }, + "500": { + "description": "Status code 500", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Account Service" + ], + "description": "Sample HTTP Delete method with references to other types.", + "parameters": [ + { + "name": "limit", + "in": "header", + "required": true, + "description": "My sample description.", + "schema": { + "$ref": "#/components/schemas/SampleClass" + } + } + ], + "responses": { + "200": { + "description": "Status code 200", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SampleClass" + } + } + } + }, + "304": { + "description": "Status code 304", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChildClass" + } + } + } + }, + "305": { + "description": "Status code 305", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Reference1" + } + } + } + }, + "306": { + "description": "Status code 306", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Reference1_array" + } + } + } + }, + "307": { + "description": "Status code 307", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Reference7_Reference7[untypedObject:Reference2]" + } + } + } + }, + "500": { + "description": "Status code 500", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SampleClass" + } + } + } + } + } + } + }, + "/Contact/": { + "description": "Contact related operations", + "get": { + "tags": [ + "Contact" + ], + "description": "This is a sample HTTP Get method", + "responses": { + "200": { + "description": "Status code 200", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SampleRestResourceWithInnerClass.InnerClass" + } + } + } + } + } + } + }, + "/Order/": { + "description": "Order related operations", + "get": { + "tags": [ + "Order" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "param1": { + "type": "string" + }, + "param2": { + "$ref": "#/components/schemas/Reference1" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Status code 200", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Order" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "param1": { + "type": "string" + }, + "param2": { + "$ref": "#/components/schemas/Reference1" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Status code 200", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "tags": [ + { + "name": "Account Service", + "description": "Account related operations" + }, + { + "name": "Contact", + "description": "Contact related operations" + }, + { + "name": "Order", + "description": "Order related operations" + } + ], + "components": { + "schemas": { + "SampleClass": { + "type": "object", + "properties": { + "MyProp": { + "type": "string", + "description": "This is a String property." + }, + "AnotherProp": { + "type": "number", + "description": "This is a Decimal property." + }, + "listOfStrings": { + "type": "array", + "items": { + "type": "string" + } + }, + "someVariable": { + "type": "string" + }, + "somePrivateStuff": { + "type": "string" + } + } + }, + "ChildClass": { + "type": "object", + "properties": { + "privateStringFromChild": { + "type": "string" + }, + "aPrivateString": { + "type": "string" + } + } + }, + "Reference1": { + "type": "object", + "properties": { + "reference2Member": { + "$ref": "#/components/schemas/Reference2", + "description": "This is a reference 2 member. Lorem." + }, + "reference3Member": { + "$ref": "#/components/schemas/Reference3" + }, + "reference4Collection": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Reference4" + } + }, + "reference5Member": { + "$ref": "#/components/schemas/Reference5" + } + } + }, + "Reference2": { + "type": "object", + "properties": { + "stringMember": { + "type": "string" + }, + "objectReference": { + "$ref": "#/components/schemas/Reference3_array", + "description": "This is an object reference." + } + } + }, + "Reference3_array": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Reference3" + } + }, + "Reference3": { + "type": "object", + "properties": { + "someBoolean": { + "type": "boolean" + } + } + }, + "Reference4": { + "type": "object", + "properties": { + "someString": { + "type": "string" + } + } + }, + "Reference5": { + "type": "object", + "properties": { + "reference6Member": { + "$ref": "#/components/schemas/Reference6" + } + } + }, + "Reference6": { + "type": "object", + "properties": { + "grandChildString": { + "type": "string", + "description": "This is the grandchild description." + } + } + }, + "Reference1_array": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Reference1" + } + }, + "Reference7_Reference7[untypedObject:Reference2]": { + "type": "object", + "properties": { + "untypedObject": { + "$ref": "#/components/schemas/Reference2" + } + } + }, + "SampleRestResourceWithInnerClass.InnerClass": { + "type": "object", + "properties": { + "stringMember": { + "type": "string" + } + } + } + } + } } \ No newline at end of file diff --git a/src/cli/commands/changelog.ts b/src/cli/commands/changelog.ts index 58dcd507..b3ecaafb 100644 --- a/src/cli/commands/changelog.ts +++ b/src/cli/commands/changelog.ts @@ -35,6 +35,14 @@ export const changeLogOptions: { [key: string]: Options } = { 'Values should be separated by a space, e.g --scope global public namespaceaccessible. ' + 'Annotations are supported and should be passed lowercased and without the @ symbol, e.g. namespaceaccessible auraenabled.', }, + customObjectVisibility: { + type: 'string', + array: true, + alias: 'v', + default: changeLogDefaults.customObjectVisibility, + choices: ['public', 'protected', 'packageprotected'], + describe: 'Controls which custom objects are documented. Values should be separated by a space.', + }, skipIfNoChanges: { type: 'boolean', default: changeLogDefaults.skipIfNoChanges, diff --git a/src/core/changelog/__test__/generating-change-log.spec.ts b/src/core/changelog/__test__/generating-change-log.spec.ts index 8dbbefa4..a322e738 100644 --- a/src/core/changelog/__test__/generating-change-log.spec.ts +++ b/src/core/changelog/__test__/generating-change-log.spec.ts @@ -14,6 +14,7 @@ import { CustomFieldXmlBuilder } from '../../test-helpers/test-data-builders/cus const config = { fileName: 'changelog', scope: ['global', 'public', 'private'], + customObjectVisibility: ['public'], targetDir: '', currentVersionDir: '', previousVersionDir: '', diff --git a/src/core/changelog/generate-change-log.ts b/src/core/changelog/generate-change-log.ts index 87f24922..7cd8487b 100644 --- a/src/core/changelog/generate-change-log.ts +++ b/src/core/changelog/generate-change-log.ts @@ -71,9 +71,10 @@ function reflect(bundles: UnparsedSourceBundle[], config: Omit { return pipe( - reflectCustomFieldsAndObjectsAndMetadataRecords(filterCustomObjectsFieldsAndMetadataRecords(bundles), [ - 'public', // TODO: Do not hardcode - ]), + reflectCustomFieldsAndObjectsAndMetadataRecords( + filterCustomObjectsFieldsAndMetadataRecords(bundles), + config.customObjectVisibility, + ), TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]), ); }), diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index 457474d8..35c6d22b 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -55,6 +55,7 @@ export type UserDefinedChangelogConfig = { targetDir: string; fileName: string; scope: string[]; + customObjectVisibility: string[]; exclude: string[]; skipIfNoChanges: boolean; } & Partial; diff --git a/src/defaults.ts b/src/defaults.ts index 399988d0..73f12d68 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -2,10 +2,15 @@ const commonDefaults = { targetDir: './docs/', }; -export const markdownDefaults = { - customObjectVisibility: ['public'], +const markdownAndChangelogDefaults = { ...commonDefaults, scope: ['global'], + customObjectVisibility: ['public'], + exclude: [], +}; + +export const markdownDefaults = { + ...markdownAndChangelogDefaults, defaultGroupName: 'Miscellaneous', customObjectsGroupName: 'Custom Objects', includeMetadata: false, @@ -13,7 +18,6 @@ export const markdownDefaults = { linkingStrategy: 'relative' as const, referenceGuideTitle: 'Reference Guide', excludeTags: [], - exclude: [], }; export const openApiDefaults = { @@ -25,9 +29,7 @@ export const openApiDefaults = { }; export const changeLogDefaults = { - ...commonDefaults, + ...markdownAndChangelogDefaults, fileName: 'changelog', - scope: ['global'], - exclude: [], skipIfNoChanges: true, }; From 8f5b418f1749f75623477e9f5eea470ca3c141c4 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 11 Feb 2025 08:10:39 -0400 Subject: [PATCH 20/20] READMe updates --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index dd96e039..24c27518 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,8 @@ apexdocs changelog --previousVersionDir force-app-previous --currentVersionDir f |----------------------------|-------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|----------| | `--sourceDir` | `-s` | The directory where the source files are located. | N/A | Yes | | `--targetDir` | `-t` | The directory where the generated files will be placed. | `docs` | No | -| `--scope` | `-p` | A list of scopes to document. Values should be separated by a space, e.g --scope global public namespaceaccessible. | `global` | No | +| `--scope` | `-p` | A list of scopes to document. Values should be separated by a space, e.g --scope global public namespaceaccessible. | `[global]` | No | +| `--customObjectVisibility` | `-v` | Controls which custom objects are documented. Values should be separated by a space. | `[public]` | No | | `--defaultGroupName` | N/A | The default group name to use when a group is not specified. | `Miscellaneous` | No | | `--namespace` | N/A | The package namespace, if any. If provided, it will be added to the generated files. | N/A | No | | `--sortAlphabetically` | N/A | Sorts files appearing in the Reference Guide alphabetically, as well as the members of a class, interface or enum alphabetically. If false, the members will be displayed in the same order as the code. | `false` | No | @@ -184,14 +185,15 @@ apexdocs openapi -s force-app -t docs -n MyNamespace --title "My Custom OpenApi #### Flags -| Flag | Alias | Description | Default | Required | -|------------------------|-------|--------------------------------------------------------------------|-------------|----------| -| `--previousVersionDir` | `-p` | The directory location of the previous version of the source code. | N/A | Yes | -| `--currentVersionDir` | `-t` | The directory location of the current version of the source code. | N/A | Yes | -| `--targetDir` | `-t` | The directory location where the changelog file will be generated. | `./docs/` | No | -| `--fileName` | N/A | The name of the changelog file to be generated. | `changelog` | No | -| `--scope` | N/A | The list of scope to respect when generating the changelog. | ['global'] | No | -| `--skipIfNoChanges` | N/A | Whether to skip generating the changelog if there are no changes. | `true` | No | +| Flag | Alias | Description | Default | Required | +|----------------------------|-------|--------------------------------------------------------------------------------------|-------------|----------| +| `--previousVersionDir` | `-p` | The directory location of the previous version of the source code. | N/A | Yes | +| `--currentVersionDir` | `-t` | The directory location of the current version of the source code. | N/A | Yes | +| `--targetDir` | `-t` | The directory location where the changelog file will be generated. | `./docs/` | No | +| `--fileName` | N/A | The name of the changelog file to be generated. | `changelog` | No | +| `--scope` | N/A | The list of scope to respect when generating the changelog. | ['global'] | No | +| `--customObjectVisibility` | `-v` | Controls which custom objects are documented. Values should be separated by a space. | ['public'] | No | +| `--skipIfNoChanges` | N/A | Whether to skip generating the changelog if there are no changes. | `true` | No | #### Sample Usage