diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 9683d0f5..0a86bf5d 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,9 @@ 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
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/README.md b/README.md
index 7f0ed99e..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
@@ -309,6 +311,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.
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/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",
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/examples/vitepress/docs/.vitepress/cache/deps/_metadata.json b/examples/vitepress/docs/.vitepress/cache/deps/_metadata.json
index 3c6ae1c5..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": "f2216e85",
+ "hash": "38118198",
"configHash": "7f7b0dad",
- "lockfileHash": "76121266",
- "browserHash": "441a8d6a",
+ "lockfileHash": "09651dfc",
+ "browserHash": "1bd3b4f7",
"optimized": {
"vue": {
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
- "fileHash": "885cbaa9",
+ "fileHash": "12c2c40e",
"needsInterop": false
},
"vitepress > @vue/devtools-api": {
"src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
"file": "vitepress___@vue_devtools-api.js",
- "fileHash": "ff3ba36c",
+ "fileHash": "7b21a64c",
"needsInterop": false
},
"vitepress > @vueuse/core": {
"src": "../../../../node_modules/@vueuse/core/index.mjs",
"file": "vitepress___@vueuse_core.js",
- "fileHash": "b6cc6d79",
+ "fileHash": "5d565a7a",
"needsInterop": false
},
"@theme/index": {
"src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js",
"file": "@theme_index.js",
- "fileHash": "6b17bcd7",
+ "fileHash": "e4373905",
"needsInterop": false
}
},
diff --git a/examples/vitepress/docs/.vitepress/sidebar.json b/examples/vitepress/docs/.vitepress/sidebar.json
index 6f2444a3..0fc24fcc 100644
--- a/examples/vitepress/docs/.vitepress/sidebar.json
+++ b/examples/vitepress/docs/.vitepress/sidebar.json
@@ -93,6 +93,10 @@
{
"text": "Speaker__c",
"link": "custom-objects/Speaker__c.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..915a7aae 100644
--- a/examples/vitepress/docs/changelog.md
+++ b/examples/vitepress/docs/changelog.md
@@ -79,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/docs/custom-objects/VisibleCMT__mdt.md b/examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md
new file mode 100644
index 00000000..edc0ffa8
--- /dev/null
+++ b/examples/vitepress/docs/custom-objects/VisibleCMT__mdt.md
@@ -0,0 +1,29 @@
+---
+title: VisibleCMT__mdt
+---
+
+# VisibleCMT
+
+## API Name
+`VisibleCMT__mdt`
+
+## Fields
+### Field1
+**Required**
+
+**API Name**
+
+`apexdocs__Field1__c`
+
+**Type**
+
+*Text*
+
+## Records
+### Some Record 1
+
+`Protected`
+
+**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 6d8ce3bb..ca295921 100644
--- a/examples/vitepress/docs/index.md
+++ b/examples/vitepress/docs/index.md
@@ -49,6 +49,8 @@ Represents a line item on a sales order.
Represents a speaker at an event.
+### [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/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",
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/package-lock.json b/package-lock.json
index 8ac4c68c..fb3c014a 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": "^5.7.3",
+ "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",
@@ -3986,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",
@@ -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",
@@ -7644,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"
@@ -8119,23 +8155,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 +8638,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 +8775,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",
@@ -9513,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"
@@ -9725,6 +9711,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 d4c505b3..4acf912b 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",
@@ -19,14 +19,46 @@
"apexdocs": "./dist/cli/generate.js"
},
"scripts": {
- "test": "npm run build && jest",
+ "test": "wireit",
"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"
+ ],
+ "output": []
+ },
+ "build": {
+ "command": "tsc --noEmit --pretty && pkgroll",
+ "dependencies": [
+ "lint"
+ ],
+ "files": [
+ "src/**/*.ts",
+ "tsconfig.json"
+ ],
+ "output": [
+ "dist"
+ ]
+ },
+ "test": {
+ "command": "jest",
+ "dependencies": [
+ "build"
+ ],
+ "files": [
+ "src/**/*.ts"
+ ],
+ "output": []
+ }
+ },
"author": "Cesar Parra",
"license": "MIT",
"repository": {
@@ -45,10 +77,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": "^5.7.3",
+ "typescript-eslint": "^7.16.0",
+ "wireit": "^0.14.10"
},
"husky": {
"hooks": {
diff --git a/src/application/Apexdocs.ts b/src/application/Apexdocs.ts
index e317b345..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,10 +52,9 @@ async function processMarkdown(config: UserDefinedMarkdownConfig) {
return pipe(
E.tryCatch(
() =>
- readFiles(['ApexClass', 'CustomObject', 'CustomField'], { includeMetadata: config.includeMetadata })(
- config.sourceDir,
- config.exclude,
- ),
+ readFiles(allComponentTypes, {
+ includeMetadata: config.includeMetadata,
+ })(config.sourceDir, config.exclude),
(e) => new FileReadingError('An error occurred while reading files.', e),
),
TE.fromEither,
@@ -73,8 +72,8 @@ 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(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 a2a81ae6..96bcaaf2 100644
--- a/src/application/source-code-file-reader.ts
+++ b/src/application/source-code-file-reader.ts
@@ -1,10 +1,16 @@
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';
+export type ComponentTypes = 'ApexClass' | 'CustomObject' | 'CustomField' | 'CustomMetadata';
+export const allComponentTypes: ComponentTypes[] = ['ApexClass', 'CustomObject', 'CustomField', 'CustomMetadata'];
/**
* Simplified representation of a source component, with only
@@ -43,6 +49,14 @@ type CustomFieldSourceComponent = {
parentName: string;
};
+type CustomMetadataSourceComponent = {
+ type: 'CustomMetadata';
+ apiName: string;
+ name: string;
+ contentPath: string;
+ parentName: string;
+};
+
function getApexSourceComponents(
includeMetadata: boolean,
sourceComponents: SourceComponentAdapter[],
@@ -125,6 +139,41 @@ 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) => ({
+ apiName: component.name,
+ 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) => ({
+ apiName: component.apiName,
+ 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 +186,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 +202,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/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/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 {
it('has no new types when both the old and new versions are empty', () => {
const oldVersion = { types: [] };
@@ -668,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',
+ },
+ ],
+ },
+ ]);
+ });
+ });
});
diff --git a/src/core/changelog/generate-change-log.ts b/src/core/changelog/generate-change-log.ts
index a11d7daa..7cd8487b 100644
--- a/src/core/changelog/generate-change-log.ts
+++ b/src/core/changelog/generate-change-log.ts
@@ -18,13 +18,14 @@ 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';
+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';
+import { CustomMetadataMetadata } from '../reflection/sobject/reflect-custom-metadata-source';
type Config = Omit;
@@ -70,7 +71,10 @@ function reflect(bundles: UnparsedSourceBundle[], config: Omit {
return pipe(
- reflectCustomFieldsAndObjects(filterCustomObjectsAndFields(bundles)),
+ reflectCustomFieldsAndObjectsAndMetadataRecords(
+ filterCustomObjectsFieldsAndMetadataRecords(bundles),
+ config.customObjectVisibility,
+ ),
TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]),
);
}),
@@ -81,7 +85,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 +96,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..64aeab26 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 =
@@ -18,7 +19,9 @@ type ModificationTypes =
| 'NewProperty'
| 'RemovedProperty'
| 'NewField'
- | 'RemovedField';
+ | 'RemovedField'
+ | 'NewCustomMetadataRecord'
+ | 'RemovedCustomMetadataRecord';
export type MemberModificationType = {
__typename: ModificationTypes;
@@ -105,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),
);
}
@@ -179,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 0b72ed1c..825feccb 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;
@@ -43,11 +44,12 @@ export type RenderableChangelog = {
newCustomObjects: NewTypeSection<'customobject'> | null;
removedCustomObjects: RemovedTypeSection | null;
newOrRemovedCustomFields: NewOrModifiedMembersSection | null;
+ newOrRemovedCustomMetadataTypeRecords: NewOrModifiedMembersSection | null;
};
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())!,
@@ -59,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:
@@ -121,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,
};
@@ -179,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/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__/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 2f126de6..e71b998c 100644
--- a/src/core/markdown/__test__/test-helpers.ts
+++ b/src/core/markdown/__test__/test-helpers.ts
@@ -15,19 +15,21 @@ 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'],
+ customObjectVisibility: ['public'],
defaultGroupName: 'Miscellaneous',
customObjectsGroupName: 'Custom Objects',
sortAlphabetically: false,
diff --git a/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts b/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts
index b69fdd7b..28ac3a5b 100644
--- a/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts
+++ b/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts
@@ -11,6 +11,7 @@ function linkGenerator(type: string): string {
const defaultMarkdownGeneratorConfig: MarkdownGeneratorConfig = {
targetDir: '',
scope: ['global', 'public'],
+ customObjectVisibility: ['public'],
namespace: '',
defaultGroupName: 'Miscellaneous',
customObjectsGroupName: 'Custom Objects',
diff --git a/src/core/markdown/adapters/type-to-renderable.ts b/src/core/markdown/adapters/type-to-renderable.ts
index 46226a0f..39220d51 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
@@ -266,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)),
+ },
};
}
@@ -292,6 +300,17 @@ function fieldMetadataToRenderable(
};
}
+function customMetadataToRenderable(metadata: CustomMetadataMetadata, headingLevel: number): RenderableCustomMetadata {
+ return {
+ type: 'metadata',
+ headingLevel: headingLevel,
+ heading: metadata.label ?? metadata.name,
+ 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 fbffc05a..dc16aa5e 100644
--- a/src/core/markdown/generate-docs.ts
+++ b/src/core/markdown/generate-docs.ts
@@ -32,8 +32,8 @@ 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, filterCustomObjectsFieldsAndMetadataRecords } from '#utils/source-bundle-utils';
export type MarkdownGeneratorConfig = Omit<
UserDefinedMarkdownConfig,
@@ -52,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',
);
}
@@ -62,11 +63,14 @@ export function generateDocs(unparsedBundles: UnparsedSourceBundle[], config: Ma
generateForApex(filterApexSourceFiles(unparsedBundles), config),
TE.chain((parsedApexFiles) => {
return pipe(
- reflectCustomFieldsAndObjects(filterCustomObjectsAndFields(unparsedBundles)),
+ reflectCustomFieldsAndObjectsAndMetadataRecords(
+ filterCustomObjectsFieldsAndMetadataRecords(unparsedBundles),
+ config.customObjectVisibility,
+ ),
TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]),
);
}),
- TE.map((parsedFiles) => sort(filterOutCustomFields(parsedFiles))),
+ TE.map((parsedFiles) => sort(filterOutCustomFieldsAndMetadata(parsedFiles))),
TE.bindTo('parsedFiles'),
TE.bind('references', ({ parsedFiles }) =>
TE.right(
@@ -75,7 +79,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..f33022fd 100644
--- a/src/core/markdown/templates/custom-object-template.ts
+++ b/src/core/markdown/templates/custom-object-template.ts
@@ -39,4 +39,25 @@ export const customObjectTemplate = `
{{/each}}
{{/if}}
+{{#if hasRecords}}
+{{ heading metadataRecords.headingLevel metadataRecords.heading }}
+{{#each metadataRecords.value}}
+{{ heading headingLevel heading }}
+
+{{#if protected}}
+\`Protected\`
+{{/if}}
+
+{{#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
new file mode 100644
index 00000000..7ed4f1c9
--- /dev/null
+++ b/src/core/reflection/sobject/reflect-custom-metadata-source.ts
@@ -0,0 +1,86 @@
+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;
+ apiName: string;
+ name: string;
+ label?: string | null;
+ parentName: string;
+};
+
+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) => 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)])),
+ 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: Partial = {
+ label: null,
+ };
+
+ return {
+ ...defaultValues,
+ ...customMetadata,
+ type_name: 'custommetadata',
+ } as CustomMetadataMetadata;
+}
+
+function addNames(metadata: CustomMetadataMetadata, name: string, apiName: string): CustomMetadataMetadata {
+ return { ...metadata, name, apiName };
+}
+
+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 55%
rename from src/core/reflection/sobject/reflectCustomFieldsAndObjects.ts
rename to src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts
index 083865a4..6bf759a9 100644
--- a/src/core/reflection/sobject/reflectCustomFieldsAndObjects.ts
+++ b/src/core/reflection/sobject/reflectCustomFieldsAndObjectsAndMetadataRecords.ts
@@ -1,20 +1,39 @@
-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)[],
+ 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');
+ /**
+ * 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(
@@ -25,37 +44,52 @@ 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,
TE.map(filterNonPublished),
- TE.map(filterNonPublic),
- TE.bindTo('objects'),
+ TE.map(filter),
+ TE.bindTo('filterResult'),
TE.bind('fields', () => generateForFields(customFields)),
- TE.map(({ objects, fields }) => {
- return [...mapFieldsToObjects(objects, fields), ...mapExtensionFields(objects, fields)];
+ TE.bind('metadata', () => generateForMetadata(customMetadata)),
+ TE.map(({ filterResult, fields, metadata }) => {
+ return [...mapFieldsAndMetadata(filterResult[0], fields, metadata), ...mapExtensionFields(filterResult, 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);
+ 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)],
},
};
});
@@ -64,11 +98,16 @@ function mapFieldsToObjects(
// "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(
@@ -97,6 +136,7 @@ function mapExtensionFields(
name: key,
description: null,
fields: fields,
+ metadataRecords: [],
},
};
});
diff --git a/src/core/renderables/types.d.ts b/src/core/renderables/types.d.ts
index f54c5fad..5f1bd03a 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,15 @@ export type RenderableCustomField = {
required: boolean;
};
+export type RenderableCustomMetadata = {
+ headingLevel: number;
+ heading: string;
+ apiName: string;
+ type: 'metadata';
+ label: string;
+ protected: boolean;
+};
+
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 0e3157db..35c6d22b 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 } from '../reflection/sobject/reflect-custom-field-source';
+import { CustomMetadataMetadata } from '../reflection/sobject/reflect-custom-metadata-source';
export type Generators = 'markdown' | 'openapi' | 'changelog';
@@ -19,6 +20,7 @@ export type CliConfigurableMarkdownConfig = {
sourceDir: string;
targetDir: string;
scope: string[];
+ customObjectVisibility: string[];
namespace?: string;
defaultGroupName: string;
customObjectsGroupName: string;
@@ -29,7 +31,7 @@ export type CliConfigurableMarkdownConfig = {
};
export type UserDefinedMarkdownConfig = {
- targetGenerator: 'markdown' /** Glob patterns to exclude files from the documentation. */;
+ targetGenerator: 'markdown';
excludeTags: string[];
exclude: string[];
} & CliConfigurableMarkdownConfig &
@@ -53,13 +55,18 @@ export type UserDefinedChangelogConfig = {
targetDir: string;
fileName: string;
scope: string[];
+ customObjectVisibility: string[];
exclude: string[];
skipIfNoChanges: boolean;
} & Partial;
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 +83,15 @@ export type UnparsedCustomFieldBundle = {
parentName: string;
};
+export type UnparsedCustomMetadataBundle = {
+ type: 'custommetadata';
+ apiName: string;
+ name: string;
+ filePath: string;
+ content: string;
+ parentName: string;
+};
+
export type UnparsedApexBundle = {
type: 'apex';
name: string;
@@ -84,7 +100,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 +120,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/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,
+ };
+}
diff --git a/src/defaults.ts b/src/defaults.ts
index 8510cf7a..73f12d68 100644
--- a/src/defaults.ts
+++ b/src/defaults.ts
@@ -2,9 +2,15 @@ const commonDefaults = {
targetDir: './docs/',
};
-export const markdownDefaults = {
+const markdownAndChangelogDefaults = {
...commonDefaults,
scope: ['global'],
+ customObjectVisibility: ['public'],
+ exclude: [],
+};
+
+export const markdownDefaults = {
+ ...markdownAndChangelogDefaults,
defaultGroupName: 'Miscellaneous',
customObjectsGroupName: 'Custom Objects',
includeMetadata: false,
@@ -12,7 +18,6 @@ export const markdownDefaults = {
linkingStrategy: 'relative' as const,
referenceGuideTitle: 'Reference Guide',
excludeTags: [],
- exclude: [],
};
export const openApiDefaults = {
@@ -24,9 +29,7 @@ export const openApiDefaults = {
};
export const changeLogDefaults = {
- ...commonDefaults,
+ ...markdownAndChangelogDefaults,
fileName: 'changelog',
- scope: ['global'],
- exclude: [],
skipIfNoChanges: true,
};
diff --git a/src/util/source-bundle-utils.ts b/src/util/source-bundle-utils.ts
index 39fcb650..dc63676c 100644
--- a/src/util/source-bundle-utils.ts
+++ b/src/util/source-bundle-utils.ts
@@ -1,6 +1,7 @@
import {
UnparsedApexBundle,
UnparsedCustomFieldBundle,
+ UnparsedCustomMetadataBundle,
UnparsedCustomObjectBundle,
UnparsedSourceBundle,
} from '../core/shared/types';
@@ -9,11 +10,11 @@ export function filterApexSourceFiles(sourceFiles: UnparsedSourceBundle[]): Unpa
return sourceFiles.filter((sourceFile): sourceFile is UnparsedApexBundle => sourceFile.type === 'apex');
}
-export function filterCustomObjectsAndFields(
+export function filterCustomObjectsFieldsAndMetadataRecords(
sourceFiles: UnparsedSourceBundle[],
-): (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle)[] {
+): (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle | UnparsedCustomMetadataBundle)[] {
return sourceFiles.filter(
(sourceFile): sourceFile is UnparsedCustomObjectBundle =>
- sourceFile.type === 'customobject' || sourceFile.type === 'customfield',
+ sourceFile.type === 'customobject' || sourceFile.type === 'customfield' || sourceFile.type === 'custommetadata',
);
}