diff --git a/README.md b/README.md
index 41e9c7c6..1a394375 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ create a documentation site that fits your needs, hosted in any static web hosti
ApexDocs generates Markdown files, which can be integrated into any Static Site Generation (SSG) engine,
(e.g. Jekyll, Vitepress, Hugo, Docosaurus, etc.) to create a documentation site that fits your needs.
-This gives you the flexibility to create beautiful leveraging your preferred SSG engine, which
+This gives you the flexibility to create beautiful sites by leveraging your preferred SSG engine, which
usually provides a wide range of themes, dark mode support, and other features out of the box.
diff --git a/examples/changelog/current/objects/Contact/fields/PhotoUrl__c.field-meta.xml b/examples/changelog/current/objects/Contact/fields/PhotoUrl__c.field-meta.xml
new file mode 100644
index 00000000..a9117781
--- /dev/null
+++ b/examples/changelog/current/objects/Contact/fields/PhotoUrl__c.field-meta.xml
@@ -0,0 +1,9 @@
+
+
+ PhotoUrl__c
+ false
+ PhotoUrl
+ false
+ false
+ Url
+
diff --git a/examples/changelog/current/objects/Event__c/Event__c.object-meta.xml b/examples/changelog/current/objects/Event__c/Event__c.object-meta.xml
new file mode 100644
index 00000000..d30dde5c
--- /dev/null
+++ b/examples/changelog/current/objects/Event__c/Event__c.object-meta.xml
@@ -0,0 +1,167 @@
+
+
+
+ Accept
+ Default
+
+
+ Accept
+ Large
+ Default
+
+
+ Accept
+ Small
+ Default
+
+
+ CancelEdit
+ Default
+
+
+ CancelEdit
+ Large
+ Default
+
+
+ CancelEdit
+ Small
+ Default
+
+
+ Clone
+ Default
+
+
+ Clone
+ Large
+ Default
+
+
+ Clone
+ Small
+ Default
+
+
+ Delete
+ Default
+
+
+ Delete
+ Large
+ Default
+
+
+ Delete
+ Small
+ Default
+
+
+ Edit
+ Default
+
+
+ Edit
+ Large
+ Default
+
+
+ Edit
+ Small
+ Default
+
+
+ List
+ Default
+
+
+ List
+ Large
+ Default
+
+
+ List
+ Small
+ Default
+
+
+ New
+ Default
+
+
+ New
+ Large
+ Default
+
+
+ New
+ Small
+ Default
+
+
+ SaveEdit
+ Default
+
+
+ SaveEdit
+ Large
+ Default
+
+
+ SaveEdit
+ Small
+ Default
+
+
+ Tab
+ Default
+
+
+ Tab
+ Large
+ Default
+
+
+ Tab
+ Small
+ Default
+
+
+ View
+ Default
+
+
+ View
+ Large
+ Default
+
+
+ View
+ Small
+ Default
+
+ false
+ SYSTEM
+ Deployed
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ Private
+ Event
+
+ Event Name
+ Text
+
+ Events
+ Represents an event that people can register for.
+
+ ReadWrite
+ Vowel
+ Public
+
diff --git a/examples/changelog/current/objects/Event__c/fields/Description__c.field-meta.xml b/examples/changelog/current/objects/Event__c/fields/Description__c.field-meta.xml
new file mode 100644
index 00000000..c1b682a4
--- /dev/null
+++ b/examples/changelog/current/objects/Event__c/fields/Description__c.field-meta.xml
@@ -0,0 +1,10 @@
+
+
+ Description__c
+ false
+ Description
+ 32768
+ false
+ LongTextArea
+ 10
+
diff --git a/examples/changelog/current/objects/Event__c/fields/End_Date__c.field-meta.xml b/examples/changelog/current/objects/Event__c/fields/End_Date__c.field-meta.xml
new file mode 100644
index 00000000..422a0003
--- /dev/null
+++ b/examples/changelog/current/objects/Event__c/fields/End_Date__c.field-meta.xml
@@ -0,0 +1,9 @@
+
+
+ End_Date__c
+ false
+ End Date
+ true
+ false
+ Date
+
diff --git a/examples/changelog/current/objects/Event__c/fields/Location__c.field-meta.xml b/examples/changelog/current/objects/Event__c/fields/Location__c.field-meta.xml
new file mode 100644
index 00000000..b8f32121
--- /dev/null
+++ b/examples/changelog/current/objects/Event__c/fields/Location__c.field-meta.xml
@@ -0,0 +1,11 @@
+
+
+ Location__c
+ false
+ false
+ Location
+ true
+ 3
+ false
+ Location
+
diff --git a/examples/changelog/current/objects/Event__c/fields/Start_Date__c.field-meta.xml b/examples/changelog/current/objects/Event__c/fields/Start_Date__c.field-meta.xml
new file mode 100644
index 00000000..81fb3f6d
--- /dev/null
+++ b/examples/changelog/current/objects/Event__c/fields/Start_Date__c.field-meta.xml
@@ -0,0 +1,9 @@
+
+
+ Start_Date__c
+ false
+ Start Date
+ true
+ false
+ Date
+
diff --git a/examples/changelog/current/objects/Event__c/fields/Tag_Line__c.field-meta.xml b/examples/changelog/current/objects/Event__c/fields/Tag_Line__c.field-meta.xml
new file mode 100644
index 00000000..652ee2e0
--- /dev/null
+++ b/examples/changelog/current/objects/Event__c/fields/Tag_Line__c.field-meta.xml
@@ -0,0 +1,11 @@
+
+
+ Tag_Line__c
+ false
+ Tag Line
+ 255
+ false
+ false
+ Text
+ false
+
diff --git a/examples/changelog/current/objects/Price_Component__c/Price_Component__c.object-meta.xml b/examples/changelog/current/objects/Price_Component__c/Price_Component__c.object-meta.xml
new file mode 100644
index 00000000..ae72fd0c
--- /dev/null
+++ b/examples/changelog/current/objects/Price_Component__c/Price_Component__c.object-meta.xml
@@ -0,0 +1,169 @@
+
+
+
+ Accept
+ Default
+
+
+ Accept
+ Large
+ Default
+
+
+ Accept
+ Small
+ Default
+
+
+ CancelEdit
+ Default
+
+
+ CancelEdit
+ Large
+ Default
+
+
+ CancelEdit
+ Small
+ Default
+
+
+ Clone
+ Default
+
+
+ Clone
+ Large
+ Default
+
+
+ Clone
+ Small
+ Default
+
+
+ Delete
+ Default
+
+
+ Delete
+ Large
+ Default
+
+
+ Delete
+ Small
+ Default
+
+
+ Edit
+ Default
+
+
+ Edit
+ Large
+ Default
+
+
+ Edit
+ Small
+ Default
+
+
+ List
+ Default
+
+
+ List
+ Large
+ Default
+
+
+ List
+ Small
+ Default
+
+
+ New
+ Default
+
+
+ New
+ Large
+ Default
+
+
+ New
+ Small
+ Default
+
+
+ SaveEdit
+ Default
+
+
+ SaveEdit
+ Large
+ Default
+
+
+ SaveEdit
+ Small
+ Default
+
+
+ Tab
+ Default
+
+
+ Tab
+ Large
+ Default
+
+
+ Tab
+ Small
+ Default
+
+
+ View
+ Action override created by Lightning App Builder during activation.
+ Price_Component_Record_Page
+ Large
+ false
+ Flexipage
+
+
+ View
+ Default
+
+
+ View
+ Small
+ Default
+
+ false
+ SYSTEM
+ Deployed
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ Private
+ Price Component
+
+ PC-{0000}
+ Price Component Name
+ AutoNumber
+
+ Price Components
+
+ ReadWrite
+ Public
+
diff --git a/examples/changelog/current/objects/Price_Component__c/fields/Description__c.field-meta.xml b/examples/changelog/current/objects/Price_Component__c/fields/Description__c.field-meta.xml
new file mode 100644
index 00000000..69050ca6
--- /dev/null
+++ b/examples/changelog/current/objects/Price_Component__c/fields/Description__c.field-meta.xml
@@ -0,0 +1,11 @@
+
+
+ Description__c
+ false
+ Description
+ 255
+ false
+ false
+ Text
+ false
+
diff --git a/examples/changelog/current/objects/Price_Component__c/fields/Expression__c.field-meta.xml b/examples/changelog/current/objects/Price_Component__c/fields/Expression__c.field-meta.xml
new file mode 100644
index 00000000..c0bf4e45
--- /dev/null
+++ b/examples/changelog/current/objects/Price_Component__c/fields/Expression__c.field-meta.xml
@@ -0,0 +1,12 @@
+
+
+ Expression__c
+ The Expression that determines if this price should take effect or not.
+ false
+ The Expression that determines if this price should take effect or not.
+ Expression
+ 131072
+ false
+ LongTextArea
+ 20
+
diff --git a/examples/changelog/current/objects/Price_Component__c/fields/Percent__c.field-meta.xml b/examples/changelog/current/objects/Price_Component__c/fields/Percent__c.field-meta.xml
new file mode 100644
index 00000000..9c303bc4
--- /dev/null
+++ b/examples/changelog/current/objects/Price_Component__c/fields/Percent__c.field-meta.xml
@@ -0,0 +1,13 @@
+
+
+ Percent__c
+ Use this field to calculate the price based on the list price's percentage instead of providing a flat price.
+ false
+ Use this field to calculate the price based on the list price's percentage instead of providing a flat price.
+ Percent
+ 18
+ false
+ 0
+ false
+ Percent
+
diff --git a/examples/changelog/current/objects/Price_Component__c/fields/Price__c.field-meta.xml b/examples/changelog/current/objects/Price_Component__c/fields/Price__c.field-meta.xml
new file mode 100644
index 00000000..84136dec
--- /dev/null
+++ b/examples/changelog/current/objects/Price_Component__c/fields/Price__c.field-meta.xml
@@ -0,0 +1,13 @@
+
+
+ Price__c
+ Use this when the Price Component represents a Flat Price. To represent a Percentage use the Percent field.
+ false
+ Use this when the Price Component represents a Flat Price. To represent a Percentage use the Percent field.
+ Price
+ 18
+ false
+ 2
+ false
+ Currency
+
diff --git a/examples/changelog/current/objects/Price_Component__c/fields/Type__c.field-meta.xml b/examples/changelog/current/objects/Price_Component__c/fields/Type__c.field-meta.xml
new file mode 100644
index 00000000..c430b305
--- /dev/null
+++ b/examples/changelog/current/objects/Price_Component__c/fields/Type__c.field-meta.xml
@@ -0,0 +1,30 @@
+
+
+ Type__c
+ false
+ Type
+ true
+ false
+ Picklist
+
+ true
+
+ false
+
+ List Price
+ false
+ List Price
+
+
+ Surcharge
+ false
+ Surcharge
+
+
+ Discount
+ false
+ Discount
+
+
+
+
diff --git a/examples/changelog/current/objects/Product_Price_Component__c/Product_Price_Component__c.object-meta.xml b/examples/changelog/current/objects/Product_Price_Component__c/Product_Price_Component__c.object-meta.xml
new file mode 100644
index 00000000..8a9a6348
--- /dev/null
+++ b/examples/changelog/current/objects/Product_Price_Component__c/Product_Price_Component__c.object-meta.xml
@@ -0,0 +1,166 @@
+
+
+
+ Accept
+ Default
+
+
+ Accept
+ Large
+ Default
+
+
+ Accept
+ Small
+ Default
+
+
+ CancelEdit
+ Default
+
+
+ CancelEdit
+ Large
+ Default
+
+
+ CancelEdit
+ Small
+ Default
+
+
+ Clone
+ Default
+
+
+ Clone
+ Large
+ Default
+
+
+ Clone
+ Small
+ Default
+
+
+ Delete
+ Default
+
+
+ Delete
+ Large
+ Default
+
+
+ Delete
+ Small
+ Default
+
+
+ Edit
+ Default
+
+
+ Edit
+ Large
+ Default
+
+
+ Edit
+ Small
+ Default
+
+
+ List
+ Default
+
+
+ List
+ Large
+ Default
+
+
+ List
+ Small
+ Default
+
+
+ New
+ Default
+
+
+ New
+ Large
+ Default
+
+
+ New
+ Small
+ Default
+
+
+ SaveEdit
+ Default
+
+
+ SaveEdit
+ Large
+ Default
+
+
+ SaveEdit
+ Small
+ Default
+
+
+ Tab
+ Default
+
+
+ Tab
+ Large
+ Default
+
+
+ Tab
+ Small
+ Default
+
+
+ View
+ Default
+
+
+ View
+ Large
+ Default
+
+
+ View
+ Small
+ Default
+
+ false
+ SYSTEM
+ Deployed
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ ControlledByParent
+ Product Price Component
+
+ PPC-{0000}
+ Product Price Component Name
+ AutoNumber
+
+ Product Price Components
+
+ ControlledByParent
+ Public
+
diff --git a/examples/changelog/current/objects/Product_Price_Component__c/fields/Price_Component__c.field-meta.xml b/examples/changelog/current/objects/Product_Price_Component__c/fields/Price_Component__c.field-meta.xml
new file mode 100644
index 00000000..f152ecb6
--- /dev/null
+++ b/examples/changelog/current/objects/Product_Price_Component__c/fields/Price_Component__c.field-meta.xml
@@ -0,0 +1,14 @@
+
+
+ Price_Component__c
+ false
+ Price Component
+ Price_Component__c
+ Product Price Components
+ Product_Price_Components
+ 1
+ false
+ false
+ MasterDetail
+ false
+
diff --git a/examples/changelog/current/objects/Product_Price_Component__c/fields/Product__c.field-meta.xml b/examples/changelog/current/objects/Product_Price_Component__c/fields/Product__c.field-meta.xml
new file mode 100644
index 00000000..16ec5b33
--- /dev/null
+++ b/examples/changelog/current/objects/Product_Price_Component__c/fields/Product__c.field-meta.xml
@@ -0,0 +1,14 @@
+
+
+ Product__c
+ false
+ Product
+ Product__c
+ Product Price Components
+ Product_Price_Components
+ 0
+ false
+ false
+ MasterDetail
+ false
+
diff --git a/examples/changelog/current/objects/Product__c/Product__c.object-meta.xml b/examples/changelog/current/objects/Product__c/Product__c.object-meta.xml
new file mode 100644
index 00000000..cdeb52a9
--- /dev/null
+++ b/examples/changelog/current/objects/Product__c/Product__c.object-meta.xml
@@ -0,0 +1,169 @@
+
+
+
+ Accept
+ Default
+
+
+ Accept
+ Large
+ Default
+
+
+ Accept
+ Small
+ Default
+
+
+ CancelEdit
+ Default
+
+
+ CancelEdit
+ Large
+ Default
+
+
+ CancelEdit
+ Small
+ Default
+
+
+ Clone
+ Default
+
+
+ Clone
+ Large
+ Default
+
+
+ Clone
+ Small
+ Default
+
+
+ Delete
+ Default
+
+
+ Delete
+ Large
+ Default
+
+
+ Delete
+ Small
+ Default
+
+
+ Edit
+ Default
+
+
+ Edit
+ Large
+ Default
+
+
+ Edit
+ Small
+ Default
+
+
+ List
+ Default
+
+
+ List
+ Large
+ Default
+
+
+ List
+ Small
+ Default
+
+
+ New
+ Default
+
+
+ New
+ Large
+ Default
+
+
+ New
+ Small
+ Default
+
+
+ SaveEdit
+ Default
+
+
+ SaveEdit
+ Large
+ Default
+
+
+ SaveEdit
+ Small
+ Default
+
+
+ Tab
+ Default
+
+
+ Tab
+ Large
+ Default
+
+
+ Tab
+ Small
+ Default
+
+
+ View
+ Action override created by Lightning App Builder during activation.
+ Product_Record_Page
+ Large
+ false
+ Flexipage
+
+
+ View
+ Default
+
+
+ View
+ Small
+ Default
+
+ false
+ SYSTEM
+ Deployed
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ Private
+ Product (Custom)
+ Product that is sold or available for sale.
+
+ Product Name
+ Text
+
+ Products
+
+ ReadWrite
+ Public
+
diff --git a/examples/changelog/current/objects/Product__c/fields/Description__c.field-meta.xml b/examples/changelog/current/objects/Product__c/fields/Description__c.field-meta.xml
new file mode 100644
index 00000000..69050ca6
--- /dev/null
+++ b/examples/changelog/current/objects/Product__c/fields/Description__c.field-meta.xml
@@ -0,0 +1,11 @@
+
+
+ Description__c
+ false
+ Description
+ 255
+ false
+ false
+ Text
+ false
+
diff --git a/examples/changelog/current/objects/Product__c/fields/Event__c.field-meta.xml b/examples/changelog/current/objects/Product__c/fields/Event__c.field-meta.xml
new file mode 100644
index 00000000..82947d0b
--- /dev/null
+++ b/examples/changelog/current/objects/Product__c/fields/Event__c.field-meta.xml
@@ -0,0 +1,12 @@
+
+
+ Event__c
+ Restrict
+ false
+ Event
+ Event__c
+ Products
+ true
+ false
+ Lookup
+
diff --git a/examples/changelog/current/objects/Product__c/fields/Features__c.field-meta.xml b/examples/changelog/current/objects/Product__c/fields/Features__c.field-meta.xml
new file mode 100644
index 00000000..6b67a859
--- /dev/null
+++ b/examples/changelog/current/objects/Product__c/fields/Features__c.field-meta.xml
@@ -0,0 +1,10 @@
+
+
+ Features__c
+ false
+ Features
+ 32768
+ false
+ LongTextArea
+ 10
+
diff --git a/examples/changelog/current/objects/Sales_Order_Line__c/Sales_Order_Line__c.object-meta.xml b/examples/changelog/current/objects/Sales_Order_Line__c/Sales_Order_Line__c.object-meta.xml
new file mode 100644
index 00000000..36e9348d
--- /dev/null
+++ b/examples/changelog/current/objects/Sales_Order_Line__c/Sales_Order_Line__c.object-meta.xml
@@ -0,0 +1,167 @@
+
+
+
+ Accept
+ Default
+
+
+ Accept
+ Large
+ Default
+
+
+ Accept
+ Small
+ Default
+
+
+ CancelEdit
+ Default
+
+
+ CancelEdit
+ Large
+ Default
+
+
+ CancelEdit
+ Small
+ Default
+
+
+ Clone
+ Default
+
+
+ Clone
+ Large
+ Default
+
+
+ Clone
+ Small
+ Default
+
+
+ Delete
+ Default
+
+
+ Delete
+ Large
+ Default
+
+
+ Delete
+ Small
+ Default
+
+
+ Edit
+ Default
+
+
+ Edit
+ Large
+ Default
+
+
+ Edit
+ Small
+ Default
+
+
+ List
+ Default
+
+
+ List
+ Large
+ Default
+
+
+ List
+ Small
+ Default
+
+
+ New
+ Default
+
+
+ New
+ Large
+ Default
+
+
+ New
+ Small
+ Default
+
+
+ SaveEdit
+ Default
+
+
+ SaveEdit
+ Large
+ Default
+
+
+ SaveEdit
+ Small
+ Default
+
+
+ Tab
+ Default
+
+
+ Tab
+ Large
+ Default
+
+
+ Tab
+ Small
+ Default
+
+
+ View
+ Default
+
+
+ View
+ Large
+ Default
+
+
+ View
+ Small
+ Default
+
+ false
+ SYSTEM
+ Deployed
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ ControlledByParent
+ Sales Order Line
+ Represents a line item on a sales order.
+
+ SOL-{0000}
+ Sales Order Line Name
+ AutoNumber
+
+ Sales Order Lines
+
+ ControlledByParent
+ Public
+
diff --git a/examples/changelog/current/objects/Sales_Order_Line__c/fields/Amount__c.field-meta.xml b/examples/changelog/current/objects/Sales_Order_Line__c/fields/Amount__c.field-meta.xml
new file mode 100644
index 00000000..3a464e2d
--- /dev/null
+++ b/examples/changelog/current/objects/Sales_Order_Line__c/fields/Amount__c.field-meta.xml
@@ -0,0 +1,11 @@
+
+
+ Amount__c
+ false
+ Amount
+ 18
+ true
+ 2
+ false
+ Currency
+
diff --git a/examples/changelog/current/objects/Sales_Order_Line__c/fields/Product__c.field-meta.xml b/examples/changelog/current/objects/Sales_Order_Line__c/fields/Product__c.field-meta.xml
new file mode 100644
index 00000000..b6b5369f
--- /dev/null
+++ b/examples/changelog/current/objects/Sales_Order_Line__c/fields/Product__c.field-meta.xml
@@ -0,0 +1,13 @@
+
+
+ Product__c
+ Restrict
+ false
+ Product
+ Product__c
+ Sales Order Lines
+ Sales_Order_Lines
+ true
+ false
+ Lookup
+
diff --git a/examples/changelog/current/objects/Sales_Order_Line__c/fields/Sales_Order__c.field-meta.xml b/examples/changelog/current/objects/Sales_Order_Line__c/fields/Sales_Order__c.field-meta.xml
new file mode 100644
index 00000000..c1d881c8
--- /dev/null
+++ b/examples/changelog/current/objects/Sales_Order_Line__c/fields/Sales_Order__c.field-meta.xml
@@ -0,0 +1,14 @@
+
+
+ Sales_Order__c
+ false
+ Sales Order
+ Sales_Order__c
+ Sales Order Lines
+ Sales_Order_Lines
+ 0
+ false
+ false
+ MasterDetail
+ false
+
diff --git a/examples/changelog/current/objects/Sales_Order_Line__c/fields/Source_Price_Component__c.field-meta.xml b/examples/changelog/current/objects/Sales_Order_Line__c/fields/Source_Price_Component__c.field-meta.xml
new file mode 100644
index 00000000..69817d96
--- /dev/null
+++ b/examples/changelog/current/objects/Sales_Order_Line__c/fields/Source_Price_Component__c.field-meta.xml
@@ -0,0 +1,13 @@
+
+
+ Source_Price_Component__c
+ SetNull
+ false
+ Source Price Component
+ Price_Component__c
+ Sales Order Lines
+ Sales_Order_Lines
+ false
+ false
+ Lookup
+
diff --git a/examples/changelog/current/objects/Sales_Order_Line__c/fields/Type__c.field-meta.xml b/examples/changelog/current/objects/Sales_Order_Line__c/fields/Type__c.field-meta.xml
new file mode 100644
index 00000000..328b5529
--- /dev/null
+++ b/examples/changelog/current/objects/Sales_Order_Line__c/fields/Type__c.field-meta.xml
@@ -0,0 +1,26 @@
+
+
+ Type__c
+ "Charge"
+ false
+ Type
+ true
+ false
+ Picklist
+
+ true
+
+ false
+
+ Charge
+ false
+ Charge
+
+
+ Discount
+ false
+ Discount
+
+
+
+
diff --git a/examples/changelog/current/objects/Sales_Order__c/Sales_Order__c.object-meta.xml b/examples/changelog/current/objects/Sales_Order__c/Sales_Order__c.object-meta.xml
new file mode 100644
index 00000000..2225e4f9
--- /dev/null
+++ b/examples/changelog/current/objects/Sales_Order__c/Sales_Order__c.object-meta.xml
@@ -0,0 +1,170 @@
+
+
+
+ Accept
+ Default
+
+
+ Accept
+ Large
+ Default
+
+
+ Accept
+ Small
+ Default
+
+
+ CancelEdit
+ Default
+
+
+ CancelEdit
+ Large
+ Default
+
+
+ CancelEdit
+ Small
+ Default
+
+
+ Clone
+ Default
+
+
+ Clone
+ Large
+ Default
+
+
+ Clone
+ Small
+ Default
+
+
+ Delete
+ Default
+
+
+ Delete
+ Large
+ Default
+
+
+ Delete
+ Small
+ Default
+
+
+ Edit
+ Default
+
+
+ Edit
+ Large
+ Default
+
+
+ Edit
+ Small
+ Default
+
+
+ List
+ Default
+
+
+ List
+ Large
+ Default
+
+
+ List
+ Small
+ Default
+
+
+ New
+ Default
+
+
+ New
+ Large
+ Default
+
+
+ New
+ Small
+ Default
+
+
+ SaveEdit
+ Default
+
+
+ SaveEdit
+ Large
+ Default
+
+
+ SaveEdit
+ Small
+ Default
+
+
+ Tab
+ Default
+
+
+ Tab
+ Large
+ Default
+
+
+ Tab
+ Small
+ Default
+
+
+ View
+ Action override created by Lightning App Builder during activation.
+ Sales_Order_Record_Page
+ Large
+ false
+ Flexipage
+
+
+ View
+ Default
+
+
+ View
+ Small
+ Default
+
+ false
+ SYSTEM
+ Deployed
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ Private
+ Sales Order
+ Custom object for tracking sales orders.
+
+ SO-{0000}
+ Sales Order Name
+ AutoNumber
+
+ Sales Orders
+
+ ReadWrite
+ Public
+
diff --git a/examples/changelog/current/objects/Speaker__c/Speaker__c.object-meta.xml b/examples/changelog/current/objects/Speaker__c/Speaker__c.object-meta.xml
new file mode 100644
index 00000000..6bdf2199
--- /dev/null
+++ b/examples/changelog/current/objects/Speaker__c/Speaker__c.object-meta.xml
@@ -0,0 +1,167 @@
+
+
+
+ Accept
+ Default
+
+
+ Accept
+ Large
+ Default
+
+
+ Accept
+ Small
+ Default
+
+
+ CancelEdit
+ Default
+
+
+ CancelEdit
+ Large
+ Default
+
+
+ CancelEdit
+ Small
+ Default
+
+
+ Clone
+ Default
+
+
+ Clone
+ Large
+ Default
+
+
+ Clone
+ Small
+ Default
+
+
+ Delete
+ Default
+
+
+ Delete
+ Large
+ Default
+
+
+ Delete
+ Small
+ Default
+
+
+ Edit
+ Default
+
+
+ Edit
+ Large
+ Default
+
+
+ Edit
+ Small
+ Default
+
+
+ List
+ Default
+
+
+ List
+ Large
+ Default
+
+
+ List
+ Small
+ Default
+
+
+ New
+ Default
+
+
+ New
+ Large
+ Default
+
+
+ New
+ Small
+ Default
+
+
+ SaveEdit
+ Default
+
+
+ SaveEdit
+ Large
+ Default
+
+
+ SaveEdit
+ Small
+ Default
+
+
+ Tab
+ Default
+
+
+ Tab
+ Large
+ Default
+
+
+ Tab
+ Small
+ Default
+
+
+ View
+ Default
+
+
+ View
+ Large
+ Default
+
+
+ View
+ Small
+ Default
+
+ false
+ SYSTEM
+ Deployed
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ ControlledByParent
+ Speaker
+ Represents a speaker at an event.
+
+ SPEAK-{0000}
+ Speaker Name
+ AutoNumber
+
+ Speakers
+
+ ControlledByParent
+ Public
+
diff --git a/examples/changelog/current/objects/Speaker__c/fields/About__c.field-meta.xml b/examples/changelog/current/objects/Speaker__c/fields/About__c.field-meta.xml
new file mode 100644
index 00000000..2fc71d94
--- /dev/null
+++ b/examples/changelog/current/objects/Speaker__c/fields/About__c.field-meta.xml
@@ -0,0 +1,10 @@
+
+
+ About__c
+ false
+ About
+ 32768
+ false
+ LongTextArea
+ 3
+
diff --git a/examples/changelog/current/objects/Speaker__c/fields/Event__c.field-meta.xml b/examples/changelog/current/objects/Speaker__c/fields/Event__c.field-meta.xml
new file mode 100644
index 00000000..cf6bfc63
--- /dev/null
+++ b/examples/changelog/current/objects/Speaker__c/fields/Event__c.field-meta.xml
@@ -0,0 +1,14 @@
+
+
+ Event__c
+ false
+ Event
+ Event__c
+ Speakers
+ Speakers
+ 0
+ false
+ false
+ MasterDetail
+ false
+
diff --git a/examples/changelog/current/objects/Speaker__c/fields/Person__c.field-meta.xml b/examples/changelog/current/objects/Speaker__c/fields/Person__c.field-meta.xml
new file mode 100644
index 00000000..b7ac07b1
--- /dev/null
+++ b/examples/changelog/current/objects/Speaker__c/fields/Person__c.field-meta.xml
@@ -0,0 +1,14 @@
+
+
+ Person__c
+ false
+ Person
+ Contact
+ Speakers
+ Speakers
+ 1
+ false
+ false
+ MasterDetail
+ false
+
diff --git a/examples/changelog/docs/changelog.md b/examples/changelog/docs/changelog.md
index f25caaf8..b567be19 100644
--- a/examples/changelog/docs/changelog.md
+++ b/examples/changelog/docs/changelog.md
@@ -22,6 +22,20 @@ These enums are new.
### PossibleValues
+## New Custom Objects
+
+These custom objects are new.
+
+### 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.
+
## Removed Types
These types have been removed.
@@ -35,4 +49,21 @@ These members have been added or modified.
### SolidService
- New Method: newMethod
-- Removed Method: deprecatedMethod
\ No newline at end of file
+- Removed Method: deprecatedMethod
+
+## New or Removed Fields in Existing Objects
+
+These custom fields have been added or removed.
+
+### Event__c
+
+- New Field: Description__c
+- New Field: Tag_Line__c
+
+### Price_Component__c
+
+- New Field: Description__c
+
+### Product__c
+
+- New Field: Description__c
\ No newline at end of file
diff --git a/examples/changelog/previous/OldImplementation.cls b/examples/changelog/previous/classes/OldImplementation.cls
similarity index 100%
rename from examples/changelog/previous/OldImplementation.cls
rename to examples/changelog/previous/classes/OldImplementation.cls
diff --git a/examples/changelog/previous/OldImplementation.cls-meta.xml b/examples/changelog/previous/classes/OldImplementation.cls-meta.xml
similarity index 100%
rename from examples/changelog/previous/OldImplementation.cls-meta.xml
rename to examples/changelog/previous/classes/OldImplementation.cls-meta.xml
diff --git a/examples/changelog/previous/SolidService.cls b/examples/changelog/previous/classes/SolidService.cls
similarity index 100%
rename from examples/changelog/previous/SolidService.cls
rename to examples/changelog/previous/classes/SolidService.cls
diff --git a/examples/changelog/previous/SolidService.cls-meta.xml b/examples/changelog/previous/classes/SolidService.cls-meta.xml
similarity index 100%
rename from examples/changelog/previous/SolidService.cls-meta.xml
rename to examples/changelog/previous/classes/SolidService.cls-meta.xml
diff --git a/examples/changelog/previous/objects/Event__c/Event__c.object-meta.xml b/examples/changelog/previous/objects/Event__c/Event__c.object-meta.xml
new file mode 100644
index 00000000..d30dde5c
--- /dev/null
+++ b/examples/changelog/previous/objects/Event__c/Event__c.object-meta.xml
@@ -0,0 +1,167 @@
+
+
+
+ Accept
+ Default
+
+
+ Accept
+ Large
+ Default
+
+
+ Accept
+ Small
+ Default
+
+
+ CancelEdit
+ Default
+
+
+ CancelEdit
+ Large
+ Default
+
+
+ CancelEdit
+ Small
+ Default
+
+
+ Clone
+ Default
+
+
+ Clone
+ Large
+ Default
+
+
+ Clone
+ Small
+ Default
+
+
+ Delete
+ Default
+
+
+ Delete
+ Large
+ Default
+
+
+ Delete
+ Small
+ Default
+
+
+ Edit
+ Default
+
+
+ Edit
+ Large
+ Default
+
+
+ Edit
+ Small
+ Default
+
+
+ List
+ Default
+
+
+ List
+ Large
+ Default
+
+
+ List
+ Small
+ Default
+
+
+ New
+ Default
+
+
+ New
+ Large
+ Default
+
+
+ New
+ Small
+ Default
+
+
+ SaveEdit
+ Default
+
+
+ SaveEdit
+ Large
+ Default
+
+
+ SaveEdit
+ Small
+ Default
+
+
+ Tab
+ Default
+
+
+ Tab
+ Large
+ Default
+
+
+ Tab
+ Small
+ Default
+
+
+ View
+ Default
+
+
+ View
+ Large
+ Default
+
+
+ View
+ Small
+ Default
+
+ false
+ SYSTEM
+ Deployed
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ Private
+ Event
+
+ Event Name
+ Text
+
+ Events
+ Represents an event that people can register for.
+
+ ReadWrite
+ Vowel
+ Public
+
diff --git a/examples/changelog/previous/objects/Event__c/fields/End_Date__c.field-meta.xml b/examples/changelog/previous/objects/Event__c/fields/End_Date__c.field-meta.xml
new file mode 100644
index 00000000..422a0003
--- /dev/null
+++ b/examples/changelog/previous/objects/Event__c/fields/End_Date__c.field-meta.xml
@@ -0,0 +1,9 @@
+
+
+ End_Date__c
+ false
+ End Date
+ true
+ false
+ Date
+
diff --git a/examples/changelog/previous/objects/Event__c/fields/Location__c.field-meta.xml b/examples/changelog/previous/objects/Event__c/fields/Location__c.field-meta.xml
new file mode 100644
index 00000000..b8f32121
--- /dev/null
+++ b/examples/changelog/previous/objects/Event__c/fields/Location__c.field-meta.xml
@@ -0,0 +1,11 @@
+
+
+ Location__c
+ false
+ false
+ Location
+ true
+ 3
+ false
+ Location
+
diff --git a/examples/changelog/previous/objects/Event__c/fields/Start_Date__c.field-meta.xml b/examples/changelog/previous/objects/Event__c/fields/Start_Date__c.field-meta.xml
new file mode 100644
index 00000000..81fb3f6d
--- /dev/null
+++ b/examples/changelog/previous/objects/Event__c/fields/Start_Date__c.field-meta.xml
@@ -0,0 +1,9 @@
+
+
+ Start_Date__c
+ false
+ Start Date
+ true
+ false
+ Date
+
diff --git a/examples/changelog/previous/objects/Price_Component__c/Price_Component__c.object-meta.xml b/examples/changelog/previous/objects/Price_Component__c/Price_Component__c.object-meta.xml
new file mode 100644
index 00000000..ae72fd0c
--- /dev/null
+++ b/examples/changelog/previous/objects/Price_Component__c/Price_Component__c.object-meta.xml
@@ -0,0 +1,169 @@
+
+
+
+ Accept
+ Default
+
+
+ Accept
+ Large
+ Default
+
+
+ Accept
+ Small
+ Default
+
+
+ CancelEdit
+ Default
+
+
+ CancelEdit
+ Large
+ Default
+
+
+ CancelEdit
+ Small
+ Default
+
+
+ Clone
+ Default
+
+
+ Clone
+ Large
+ Default
+
+
+ Clone
+ Small
+ Default
+
+
+ Delete
+ Default
+
+
+ Delete
+ Large
+ Default
+
+
+ Delete
+ Small
+ Default
+
+
+ Edit
+ Default
+
+
+ Edit
+ Large
+ Default
+
+
+ Edit
+ Small
+ Default
+
+
+ List
+ Default
+
+
+ List
+ Large
+ Default
+
+
+ List
+ Small
+ Default
+
+
+ New
+ Default
+
+
+ New
+ Large
+ Default
+
+
+ New
+ Small
+ Default
+
+
+ SaveEdit
+ Default
+
+
+ SaveEdit
+ Large
+ Default
+
+
+ SaveEdit
+ Small
+ Default
+
+
+ Tab
+ Default
+
+
+ Tab
+ Large
+ Default
+
+
+ Tab
+ Small
+ Default
+
+
+ View
+ Action override created by Lightning App Builder during activation.
+ Price_Component_Record_Page
+ Large
+ false
+ Flexipage
+
+
+ View
+ Default
+
+
+ View
+ Small
+ Default
+
+ false
+ SYSTEM
+ Deployed
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ Private
+ Price Component
+
+ PC-{0000}
+ Price Component Name
+ AutoNumber
+
+ Price Components
+
+ ReadWrite
+ Public
+
diff --git a/examples/changelog/previous/objects/Price_Component__c/fields/Expression__c.field-meta.xml b/examples/changelog/previous/objects/Price_Component__c/fields/Expression__c.field-meta.xml
new file mode 100644
index 00000000..c0bf4e45
--- /dev/null
+++ b/examples/changelog/previous/objects/Price_Component__c/fields/Expression__c.field-meta.xml
@@ -0,0 +1,12 @@
+
+
+ Expression__c
+ The Expression that determines if this price should take effect or not.
+ false
+ The Expression that determines if this price should take effect or not.
+ Expression
+ 131072
+ false
+ LongTextArea
+ 20
+
diff --git a/examples/changelog/previous/objects/Price_Component__c/fields/Percent__c.field-meta.xml b/examples/changelog/previous/objects/Price_Component__c/fields/Percent__c.field-meta.xml
new file mode 100644
index 00000000..9c303bc4
--- /dev/null
+++ b/examples/changelog/previous/objects/Price_Component__c/fields/Percent__c.field-meta.xml
@@ -0,0 +1,13 @@
+
+
+ Percent__c
+ Use this field to calculate the price based on the list price's percentage instead of providing a flat price.
+ false
+ Use this field to calculate the price based on the list price's percentage instead of providing a flat price.
+ Percent
+ 18
+ false
+ 0
+ false
+ Percent
+
diff --git a/examples/changelog/previous/objects/Price_Component__c/fields/Price__c.field-meta.xml b/examples/changelog/previous/objects/Price_Component__c/fields/Price__c.field-meta.xml
new file mode 100644
index 00000000..84136dec
--- /dev/null
+++ b/examples/changelog/previous/objects/Price_Component__c/fields/Price__c.field-meta.xml
@@ -0,0 +1,13 @@
+
+
+ Price__c
+ Use this when the Price Component represents a Flat Price. To represent a Percentage use the Percent field.
+ false
+ Use this when the Price Component represents a Flat Price. To represent a Percentage use the Percent field.
+ Price
+ 18
+ false
+ 2
+ false
+ Currency
+
diff --git a/examples/changelog/previous/objects/Price_Component__c/fields/Type__c.field-meta.xml b/examples/changelog/previous/objects/Price_Component__c/fields/Type__c.field-meta.xml
new file mode 100644
index 00000000..c430b305
--- /dev/null
+++ b/examples/changelog/previous/objects/Price_Component__c/fields/Type__c.field-meta.xml
@@ -0,0 +1,30 @@
+
+
+ Type__c
+ false
+ Type
+ true
+ false
+ Picklist
+
+ true
+
+ false
+
+ List Price
+ false
+ List Price
+
+
+ Surcharge
+ false
+ Surcharge
+
+
+ Discount
+ false
+ Discount
+
+
+
+
diff --git a/examples/changelog/previous/objects/Product_Price_Component__c/Product_Price_Component__c.object-meta.xml b/examples/changelog/previous/objects/Product_Price_Component__c/Product_Price_Component__c.object-meta.xml
new file mode 100644
index 00000000..8a9a6348
--- /dev/null
+++ b/examples/changelog/previous/objects/Product_Price_Component__c/Product_Price_Component__c.object-meta.xml
@@ -0,0 +1,166 @@
+
+
+
+ Accept
+ Default
+
+
+ Accept
+ Large
+ Default
+
+
+ Accept
+ Small
+ Default
+
+
+ CancelEdit
+ Default
+
+
+ CancelEdit
+ Large
+ Default
+
+
+ CancelEdit
+ Small
+ Default
+
+
+ Clone
+ Default
+
+
+ Clone
+ Large
+ Default
+
+
+ Clone
+ Small
+ Default
+
+
+ Delete
+ Default
+
+
+ Delete
+ Large
+ Default
+
+
+ Delete
+ Small
+ Default
+
+
+ Edit
+ Default
+
+
+ Edit
+ Large
+ Default
+
+
+ Edit
+ Small
+ Default
+
+
+ List
+ Default
+
+
+ List
+ Large
+ Default
+
+
+ List
+ Small
+ Default
+
+
+ New
+ Default
+
+
+ New
+ Large
+ Default
+
+
+ New
+ Small
+ Default
+
+
+ SaveEdit
+ Default
+
+
+ SaveEdit
+ Large
+ Default
+
+
+ SaveEdit
+ Small
+ Default
+
+
+ Tab
+ Default
+
+
+ Tab
+ Large
+ Default
+
+
+ Tab
+ Small
+ Default
+
+
+ View
+ Default
+
+
+ View
+ Large
+ Default
+
+
+ View
+ Small
+ Default
+
+ false
+ SYSTEM
+ Deployed
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ ControlledByParent
+ Product Price Component
+
+ PPC-{0000}
+ Product Price Component Name
+ AutoNumber
+
+ Product Price Components
+
+ ControlledByParent
+ Public
+
diff --git a/examples/changelog/previous/objects/Product_Price_Component__c/fields/Price_Component__c.field-meta.xml b/examples/changelog/previous/objects/Product_Price_Component__c/fields/Price_Component__c.field-meta.xml
new file mode 100644
index 00000000..f152ecb6
--- /dev/null
+++ b/examples/changelog/previous/objects/Product_Price_Component__c/fields/Price_Component__c.field-meta.xml
@@ -0,0 +1,14 @@
+
+
+ Price_Component__c
+ false
+ Price Component
+ Price_Component__c
+ Product Price Components
+ Product_Price_Components
+ 1
+ false
+ false
+ MasterDetail
+ false
+
diff --git a/examples/changelog/previous/objects/Product_Price_Component__c/fields/Product__c.field-meta.xml b/examples/changelog/previous/objects/Product_Price_Component__c/fields/Product__c.field-meta.xml
new file mode 100644
index 00000000..16ec5b33
--- /dev/null
+++ b/examples/changelog/previous/objects/Product_Price_Component__c/fields/Product__c.field-meta.xml
@@ -0,0 +1,14 @@
+
+
+ Product__c
+ false
+ Product
+ Product__c
+ Product Price Components
+ Product_Price_Components
+ 0
+ false
+ false
+ MasterDetail
+ false
+
diff --git a/examples/changelog/previous/objects/Product__c/Product__c.object-meta.xml b/examples/changelog/previous/objects/Product__c/Product__c.object-meta.xml
new file mode 100644
index 00000000..cdeb52a9
--- /dev/null
+++ b/examples/changelog/previous/objects/Product__c/Product__c.object-meta.xml
@@ -0,0 +1,169 @@
+
+
+
+ Accept
+ Default
+
+
+ Accept
+ Large
+ Default
+
+
+ Accept
+ Small
+ Default
+
+
+ CancelEdit
+ Default
+
+
+ CancelEdit
+ Large
+ Default
+
+
+ CancelEdit
+ Small
+ Default
+
+
+ Clone
+ Default
+
+
+ Clone
+ Large
+ Default
+
+
+ Clone
+ Small
+ Default
+
+
+ Delete
+ Default
+
+
+ Delete
+ Large
+ Default
+
+
+ Delete
+ Small
+ Default
+
+
+ Edit
+ Default
+
+
+ Edit
+ Large
+ Default
+
+
+ Edit
+ Small
+ Default
+
+
+ List
+ Default
+
+
+ List
+ Large
+ Default
+
+
+ List
+ Small
+ Default
+
+
+ New
+ Default
+
+
+ New
+ Large
+ Default
+
+
+ New
+ Small
+ Default
+
+
+ SaveEdit
+ Default
+
+
+ SaveEdit
+ Large
+ Default
+
+
+ SaveEdit
+ Small
+ Default
+
+
+ Tab
+ Default
+
+
+ Tab
+ Large
+ Default
+
+
+ Tab
+ Small
+ Default
+
+
+ View
+ Action override created by Lightning App Builder during activation.
+ Product_Record_Page
+ Large
+ false
+ Flexipage
+
+
+ View
+ Default
+
+
+ View
+ Small
+ Default
+
+ false
+ SYSTEM
+ Deployed
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ Private
+ Product (Custom)
+ Product that is sold or available for sale.
+
+ Product Name
+ Text
+
+ Products
+
+ ReadWrite
+ Public
+
diff --git a/examples/changelog/previous/objects/Product__c/fields/Event__c.field-meta.xml b/examples/changelog/previous/objects/Product__c/fields/Event__c.field-meta.xml
new file mode 100644
index 00000000..82947d0b
--- /dev/null
+++ b/examples/changelog/previous/objects/Product__c/fields/Event__c.field-meta.xml
@@ -0,0 +1,12 @@
+
+
+ Event__c
+ Restrict
+ false
+ Event
+ Event__c
+ Products
+ true
+ false
+ Lookup
+
diff --git a/examples/changelog/previous/objects/Product__c/fields/Features__c.field-meta.xml b/examples/changelog/previous/objects/Product__c/fields/Features__c.field-meta.xml
new file mode 100644
index 00000000..6b67a859
--- /dev/null
+++ b/examples/changelog/previous/objects/Product__c/fields/Features__c.field-meta.xml
@@ -0,0 +1,10 @@
+
+
+ Features__c
+ false
+ Features
+ 32768
+ false
+ LongTextArea
+ 10
+
diff --git a/examples/vitepress/docs/changelog.md b/examples/vitepress/docs/changelog.md
index 59bf6f89..fedd3cc0 100644
--- a/examples/vitepress/docs/changelog.md
+++ b/examples/vitepress/docs/changelog.md
@@ -40,4 +40,28 @@ These enums are new.
This is a sample enum. This references ReferencedEnum .
-This description has several lines
\ No newline at end of file
+This description has several lines
+
+## New Custom Objects
+
+These custom objects are new.
+
+### Event__c
+
+Represents an event that people can register for.
+### Price_Component__c
+
+### 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.
\ No newline at end of file
diff --git a/jest.config.js b/jest.config.js
index 81c35d21..8a93fadd 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,7 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
- modulePathIgnorePatterns: ['
/dist/'],
+ modulePathIgnorePatterns: ['/dist/', '/examples/'],
moduleNameMapper: {
'^chalk$': '/__mocks__/chalk.js',
'^log-update$': '/__mocks__/log-update.js',
diff --git a/package.json b/package.json
index 2c269126..8fa6595b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@cparra/apexdocs",
- "version": "3.4.2",
+ "version": "3.5.0",
"description": "Library with CLI capabilities to generate documentation for Salesforce Apex classes.",
"keywords": [
"apex",
diff --git a/src/application/Apexdocs.ts b/src/application/Apexdocs.ts
index e153bc73..e317b345 100644
--- a/src/application/Apexdocs.ts
+++ b/src/application/Apexdocs.ts
@@ -11,6 +11,7 @@ import { DefaultFileSystem } from './file-system';
import { Logger } from '#utils/logger';
import {
UnparsedApexBundle,
+ UnparsedSourceBundle,
UserDefinedChangelogConfig,
UserDefinedConfig,
UserDefinedMarkdownConfig,
@@ -70,10 +71,10 @@ async function processOpenApi(config: UserDefinedOpenApiConfig, logger: Logger)
}
async function processChangeLog(config: UserDefinedChangelogConfig) {
- function loadFiles(): [UnparsedApexBundle[], UnparsedApexBundle[]] {
+ function loadFiles(): [UnparsedSourceBundle[], UnparsedSourceBundle[]] {
return [
- readFiles(['ApexClass'])(config.previousVersionDir, config.exclude) as UnparsedApexBundle[],
- readFiles(['ApexClass'])(config.currentVersionDir, config.exclude) as UnparsedApexBundle[],
+ readFiles(['ApexClass', 'CustomObject', 'CustomField'])(config.previousVersionDir, config.exclude),
+ readFiles(['ApexClass', 'CustomObject', 'CustomField'])(config.currentVersionDir, config.exclude),
];
}
diff --git a/src/application/generators/changelog.ts b/src/application/generators/changelog.ts
index d72dd1f9..00899238 100644
--- a/src/application/generators/changelog.ts
+++ b/src/application/generators/changelog.ts
@@ -1,5 +1,5 @@
import { pipe } from 'fp-ts/function';
-import { PageData, Skip, UnparsedApexBundle, UserDefinedChangelogConfig } from '../../core/shared/types';
+import { PageData, Skip, UnparsedSourceBundle, UserDefinedChangelogConfig } from '../../core/shared/types';
import * as TE from 'fp-ts/TaskEither';
import { writeFiles } from '../file-writer';
import { ChangeLogPageData, generateChangeLog } from '../../core/changelog/generate-change-log';
@@ -7,8 +7,8 @@ import { FileWritingError } from '../errors';
import { isSkip } from '../../core/shared/utils';
export default function generate(
- oldBundles: UnparsedApexBundle[],
- newBundles: UnparsedApexBundle[],
+ oldBundles: UnparsedSourceBundle[],
+ newBundles: UnparsedSourceBundle[],
config: UserDefinedChangelogConfig,
) {
function handleFile(file: ChangeLogPageData | Skip) {
diff --git a/src/core/changelog/__test__/generating-change-log.spec.ts b/src/core/changelog/__test__/generating-change-log.spec.ts
index 28e9512e..fb97c418 100644
--- a/src/core/changelog/__test__/generating-change-log.spec.ts
+++ b/src/core/changelog/__test__/generating-change-log.spec.ts
@@ -1,7 +1,8 @@
-import { UnparsedApexBundle } from '../../shared/types';
+import { UnparsedApexBundle, UnparsedCustomObjectBundle, UnparsedSourceBundle } from '../../shared/types';
import { ChangeLogPageData, generateChangeLog } from '../generate-change-log';
import { assertEither } from '../../test-helpers/assert-either';
import { isSkip } from '../../shared/utils';
+import { customObjectGenerator, unparsedFieldBundleFromRawString } from '../../test-helpers/test-data-builders';
const config = {
fileName: 'changelog',
@@ -183,6 +184,21 @@ describe('when generating a changelog', () => {
});
});
+ describe('that include new custom objects', () => {
+ it('should include a section for new custom objects', async () => {
+ const newObjectSource = customObjectGenerator();
+
+ const oldBundle: UnparsedCustomObjectBundle[] = [];
+ const newBundle: UnparsedCustomObjectBundle[] = [
+ { type: 'customobject', name: 'MyTestObject', content: newObjectSource, filePath: 'MyTestObject.object' },
+ ];
+
+ const result = await generateChangeLog(oldBundle, newBundle, config)();
+
+ assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('## New Custom Objects'));
+ });
+ });
+
describe('that includes new types out of scope', () => {
it('should not include them', async () => {
const newClassSource = 'class Test {}';
@@ -226,6 +242,36 @@ describe('when generating a changelog', () => {
});
});
+ describe('that includes removed custom objects', () => {
+ it('should include a section for removed custom objects', async () => {
+ const oldObjectSource = customObjectGenerator();
+
+ const oldBundle: UnparsedCustomObjectBundle[] = [
+ { type: 'customobject', name: 'MyTestObject', content: oldObjectSource, filePath: 'MyTestObject.object' },
+ ];
+ const newBundle: UnparsedCustomObjectBundle[] = [];
+
+ const result = await generateChangeLog(oldBundle, newBundle, config)();
+
+ assertEither(result, (data) =>
+ expect((data as ChangeLogPageData).content).toContain('## Removed Custom Objects'),
+ );
+ });
+
+ it('should include the removed custom object name', async () => {
+ const oldObjectSource = customObjectGenerator();
+
+ const oldBundle: UnparsedCustomObjectBundle[] = [
+ { type: 'customobject', name: 'MyTestObject', content: oldObjectSource, filePath: 'MyTestObject.object' },
+ ];
+ const newBundle: UnparsedCustomObjectBundle[] = [];
+
+ const result = await generateChangeLog(oldBundle, newBundle, config)();
+
+ assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('- MyTestObject'));
+ });
+ });
+
describe('that includes modifications to existing members', () => {
it('should include a section for new or modified members', async () => {
const oldClassSource = 'class Test {}';
@@ -280,4 +326,70 @@ describe('when generating a changelog', () => {
assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('myMethod'));
});
});
+
+ describe('that includes modifications to custom fields', () => {
+ it('should include a section for new or removed custom fields', async () => {
+ const oldObjectSource = customObjectGenerator();
+ const newObjectSource = customObjectGenerator();
+
+ const oldBundle: UnparsedSourceBundle[] = [
+ { type: 'customobject', name: 'MyTestObject', content: oldObjectSource, filePath: 'MyTestObject.object' },
+ ];
+ const newBundle: UnparsedSourceBundle[] = [
+ { type: 'customobject', name: 'MyTestObject', content: newObjectSource, filePath: 'MyTestObject.object' },
+ unparsedFieldBundleFromRawString({
+ filePath: 'MyTestObject__c.field-meta.xml',
+ parentName: 'MyTestObject',
+ }),
+ ];
+
+ const result = await generateChangeLog(oldBundle, newBundle, config)();
+
+ assertEither(result, (data) =>
+ expect((data as ChangeLogPageData).content).toContain('## New or Removed Fields in Existing Objects'),
+ );
+ });
+
+ it('should include new custom field names', async () => {
+ const oldObjectSource = customObjectGenerator();
+ const newObjectSource = customObjectGenerator();
+
+ const oldBundle: UnparsedSourceBundle[] = [
+ { type: 'customobject', name: 'MyTestObject', content: oldObjectSource, filePath: 'MyTestObject.object' },
+ ];
+ const newBundle: UnparsedSourceBundle[] = [
+ { type: 'customobject', name: 'MyTestObject', content: newObjectSource, filePath: 'MyTestObject.object' },
+ unparsedFieldBundleFromRawString({
+ filePath: 'MyTestObject__c.field-meta.xml',
+ parentName: 'MyTestObject',
+ }),
+ ];
+
+ const result = await generateChangeLog(oldBundle, newBundle, config)();
+
+ assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('New Field: TestField__c'));
+ });
+
+ it('should include removed custom field names', async () => {
+ const oldObjectSource = customObjectGenerator();
+ const newObjectSource = customObjectGenerator();
+
+ const oldBundle: UnparsedSourceBundle[] = [
+ { type: 'customobject', name: 'MyTestObject', content: oldObjectSource, filePath: 'MyTestObject.object' },
+ unparsedFieldBundleFromRawString({
+ filePath: 'MyTestObject__c.field-meta.xml',
+ parentName: 'MyTestObject',
+ }),
+ ];
+ const newBundle: UnparsedSourceBundle[] = [
+ { type: 'customobject', name: 'MyTestObject', content: newObjectSource, filePath: 'MyTestObject.object' },
+ ];
+
+ const result = await generateChangeLog(oldBundle, newBundle, config)();
+
+ assertEither(result, (data) =>
+ expect((data as ChangeLogPageData).content).toContain('Removed Field: TestField__c'),
+ );
+ });
+ });
});
diff --git a/src/core/changelog/__test__/processing-changelog.spec.ts b/src/core/changelog/__test__/processing-changelog.spec.ts
index 9162f06a..9bb8a964 100644
--- a/src/core/changelog/__test__/processing-changelog.spec.ts
+++ b/src/core/changelog/__test__/processing-changelog.spec.ts
@@ -1,7 +1,9 @@
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';
-function typeFromRawString(raw: string): Type {
+function apexTypeFromRawString(raw: string): Type {
const result = reflect(raw);
if (result.error) {
throw new Error(result.error.message);
@@ -10,6 +12,46 @@ function typeFromRawString(raw: string): Type {
return result.typeMirror!;
}
+class CustomFieldMetadataBuilder {
+ build(): CustomFieldMetadata {
+ return {
+ type: 'Text',
+ type_name: 'customfield',
+ label: 'MyField',
+ name: 'MyField',
+ description: null,
+ parentName: 'MyObject',
+ };
+ }
+}
+
+class CustomObjectMetadataBuilder {
+ label: string = 'MyObject';
+ fields: CustomFieldMetadata[] = [];
+
+ withLabel(label: string): CustomObjectMetadataBuilder {
+ this.label = label;
+ return this;
+ }
+
+ 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,
+ };
+ }
+}
+
describe('when generating a changelog', () => {
it('has no new types when both the old and new versions are empty', () => {
const oldVersion = { types: [] };
@@ -17,7 +59,7 @@ describe('when generating a changelog', () => {
const changeLog = processChangelog(oldVersion, newVersion);
- expect(changeLog.newTypes).toEqual([]);
+ expect(changeLog.newApexTypes).toEqual([]);
});
it('has no removed types when the old and new versions are empty', () => {
@@ -26,414 +68,506 @@ describe('when generating a changelog', () => {
const changeLog = processChangelog(oldVersion, newVersion);
- expect(changeLog.removedTypes).toEqual([]);
- });
-
- it('has no new types when both the old and new versions are the same', () => {
- const anyClassBody = 'public class AnyClass {}';
- const anyClass = typeFromRawString(anyClassBody);
- const oldVersion = { types: [anyClass] };
- const newVersion = { types: [anyClass] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newTypes).toEqual([]);
- });
-
- it('has no removed types when both the old and new versions are the same', () => {
- const anyClassBody = 'public class AnyClass {}';
- const anyClass = typeFromRawString(anyClassBody);
- const oldVersion = { types: [anyClass] };
- const newVersion = { types: [anyClass] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.removedTypes).toEqual([]);
- });
-
- it('lists all new types', () => {
- const existingInBoth = 'public class ExistingInBoth {}';
- const existingClass = typeFromRawString(existingInBoth);
- const oldVersion = { types: [existingClass] };
- const newClassBody = 'public class NewClass {}';
- const newClass = typeFromRawString(newClassBody);
- const newVersion = { types: [existingClass, newClass] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newTypes).toEqual([newClass.name]);
- });
-
- it('lists all removed types', () => {
- const existingInBoth = 'public class ExistingInBoth {}';
- const existingClass = typeFromRawString(existingInBoth);
- const existingOnlyInOld = 'public class ExistingOnlyInOld {}';
- const existingOnlyInOldClass = typeFromRawString(existingOnlyInOld);
- const oldVersion = { types: [existingClass, existingOnlyInOldClass] };
- const newVersion = { types: [existingClass] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.removedTypes).toEqual([existingOnlyInOldClass.name]);
- });
-
- it('lists all new values of a modified enum', () => {
- const enumBefore = 'public enum MyEnum { VALUE1 }';
- const oldEnum = typeFromRawString(enumBefore);
- const enumAfter = 'public enum MyEnum { VALUE1, VALUE2 }';
- const newEnum = typeFromRawString(enumAfter);
-
- const oldVersion = { types: [oldEnum] };
- const newVersion = { types: [newEnum] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyEnum',
- modifications: [
- {
- __typename: 'NewEnumValue',
- name: 'VALUE2',
- },
- ],
- },
- ]);
- });
-
- it('list all removed values of a modified enum', () => {
- const enumBefore = 'public enum MyEnum { VALUE1, VALUE2 }';
- const oldEnum = typeFromRawString(enumBefore);
- const enumAfter = 'public enum MyEnum { VALUE1 }';
- const newEnum = typeFromRawString(enumAfter);
-
- const oldVersion = { types: [oldEnum] };
- const newVersion = { types: [newEnum] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyEnum',
- modifications: [
- {
- __typename: 'RemovedEnumValue',
- name: 'VALUE2',
- },
- ],
- },
- ]);
- });
-
- it('lists all new methods of an interface', () => {
- const interfaceBefore = 'public interface MyInterface {}';
- const oldInterface = typeFromRawString(interfaceBefore);
- const interfaceAfter = 'public interface MyInterface { void newMethod(); }';
- const newInterface = typeFromRawString(interfaceAfter);
-
- const oldVersion = { types: [oldInterface] };
- const newVersion = { types: [newInterface] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyInterface',
- modifications: [
- {
- __typename: 'NewMethod',
- name: 'newMethod',
- },
- ],
- },
- ]);
- });
-
- it('lists all new methods of a class', () => {
- const classBefore = 'public class MyClass { }';
- const oldClass = typeFromRawString(classBefore);
- const classAfter = 'public class MyClass { void newMethod() {} }';
- const newClass = typeFromRawString(classAfter);
-
- const oldVersion = { types: [oldClass] };
- const newVersion = { types: [newClass] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyClass',
- modifications: [
- {
- __typename: 'NewMethod',
- name: 'newMethod',
- },
- ],
- },
- ]);
- });
-
- it('lists all removed methods of an interface', () => {
- const interfaceBefore = 'public interface MyInterface { void oldMethod(); }';
- const oldInterface = typeFromRawString(interfaceBefore);
- const interfaceAfter = 'public interface MyInterface {}';
- const newInterface = typeFromRawString(interfaceAfter);
-
- const oldVersion = { types: [oldInterface] };
- const newVersion = { types: [newInterface] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyInterface',
- modifications: [
- {
- __typename: 'RemovedMethod',
- name: 'oldMethod',
- },
- ],
- },
- ]);
- });
-
- it('lists all new properties of a class', () => {
- const classBefore = 'public class MyClass { }';
- const oldClass = typeFromRawString(classBefore);
- const classAfter = 'public class MyClass { String newProperty { get; set; } }';
- const newClass = typeFromRawString(classAfter);
-
- const oldVersion = { types: [oldClass] };
- const newVersion = { types: [newClass] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyClass',
- modifications: [
- {
- __typename: 'NewProperty',
- name: 'newProperty',
- },
- ],
- },
- ]);
- });
-
- it('lists all removed properties of a class', () => {
- const classBefore = 'public class MyClass { String oldProperty { get; set; } }';
- const oldClass = typeFromRawString(classBefore);
- const classAfter = 'public class MyClass { }';
- const newClass = typeFromRawString(classAfter);
-
- const oldVersion = { types: [oldClass] };
- const newVersion = { types: [newClass] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyClass',
- modifications: [
- {
- __typename: 'RemovedProperty',
- name: 'oldProperty',
- },
- ],
- },
- ]);
- });
-
- it('lists all new fields of a class', () => {
- const classBefore = 'public class MyClass { }';
- const oldClass = typeFromRawString(classBefore);
- const classAfter = 'public class MyClass { String newField; }';
- const newClass = typeFromRawString(classAfter);
-
- const oldVersion = { types: [oldClass] };
- const newVersion = { types: [newClass] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyClass',
- modifications: [
- {
- __typename: 'NewField',
- name: 'newField',
- },
- ],
- },
- ]);
+ expect(changeLog.removedApexTypes).toEqual([]);
});
- it('lists all removed fields of a class', () => {
- const classBefore = 'public class MyClass { String oldField; }';
- const oldClass = typeFromRawString(classBefore);
- const classAfter = 'public class MyClass { }';
- const newClass = typeFromRawString(classAfter);
+ describe('with apex code', () => {
+ it('has no new types when both the old and new versions are the same', () => {
+ const anyClassBody = 'public class AnyClass {}';
+ const anyClass = apexTypeFromRawString(anyClassBody);
+ const oldVersion = { types: [anyClass] };
+ const newVersion = { types: [anyClass] };
- const oldVersion = { types: [oldClass] };
- const newVersion = { types: [newClass] };
+ const changeLog = processChangelog(oldVersion, newVersion);
- const changeLog = processChangelog(oldVersion, newVersion);
+ expect(changeLog.newApexTypes).toEqual([]);
+ });
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyClass',
- modifications: [
- {
- __typename: 'RemovedField',
- name: 'oldField',
- },
- ],
- },
- ]);
- });
+ it('has no removed types when both the old and new versions are the same', () => {
+ const anyClassBody = 'public class AnyClass {}';
+ const anyClass = apexTypeFromRawString(anyClassBody);
+ const oldVersion = { types: [anyClass] };
+ const newVersion = { types: [anyClass] };
- it('lists new inner classes of a class', () => {
- const classBefore = 'public class MyClass { }';
- const oldClass = typeFromRawString(classBefore);
- const classAfter = 'public class MyClass { class NewInnerClass { } }';
- const newClass = typeFromRawString(classAfter);
+ const changeLog = processChangelog(oldVersion, newVersion);
- const oldVersion = { types: [oldClass] };
- const newVersion = { types: [newClass] };
+ expect(changeLog.removedApexTypes).toEqual([]);
+ });
- const changeLog = processChangelog(oldVersion, newVersion);
+ it('lists all new types', () => {
+ const existingInBoth = 'public class ExistingInBoth {}';
+ const existingClass = apexTypeFromRawString(existingInBoth);
+ const oldVersion = { types: [existingClass] };
+ const newClassBody = 'public class NewClass {}';
+ const newClass = apexTypeFromRawString(newClassBody);
+ const newVersion = { types: [existingClass, newClass] };
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyClass',
- modifications: [
- {
- __typename: 'NewType',
- name: 'NewInnerClass',
- },
- ],
- },
- ]);
- });
+ const changeLog = processChangelog(oldVersion, newVersion);
- it('lists removed inner classes of a class', () => {
- const classBefore = 'public class MyClass { class OldInnerClass { } }';
- const oldClass = typeFromRawString(classBefore);
- const classAfter = 'public class MyClass { }';
- const newClass = typeFromRawString(classAfter);
+ expect(changeLog.newApexTypes).toEqual([newClass.name]);
+ });
- const oldVersion = { types: [oldClass] };
- const newVersion = { types: [newClass] };
+ it('lists all removed types', () => {
+ const existingInBoth = 'public class ExistingInBoth {}';
+ const existingClass = apexTypeFromRawString(existingInBoth);
+ const existingOnlyInOld = 'public class ExistingOnlyInOld {}';
+ const existingOnlyInOldClass = apexTypeFromRawString(existingOnlyInOld);
+ const oldVersion = { types: [existingClass, existingOnlyInOldClass] };
+ const newVersion = { types: [existingClass] };
- const changeLog = processChangelog(oldVersion, newVersion);
+ const changeLog = processChangelog(oldVersion, newVersion);
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyClass',
- modifications: [
- {
- __typename: 'RemovedType',
- name: 'OldInnerClass',
- },
- ],
- },
- ]);
+ expect(changeLog.removedApexTypes).toEqual([existingOnlyInOldClass.name]);
+ });
});
- it('lists new inner interfaces of a class', () => {
- const classBefore = 'public class MyClass { }';
- const oldClass = typeFromRawString(classBefore);
- const classAfter = 'public class MyClass { interface NewInterface { } }';
- const newClass = typeFromRawString(classAfter);
-
- const oldVersion = { types: [oldClass] };
- const newVersion = { types: [newClass] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyClass',
- modifications: [
- {
- __typename: 'NewType',
- name: 'NewInterface',
- },
- ],
- },
- ]);
+ describe('with custom object code', () => {
+ it('has no new objects when both the old and new versions are the same', () => {
+ const oldVersion = { types: [new CustomObjectMetadataBuilder().build()] };
+ const newVersion = { types: [new CustomObjectMetadataBuilder().build()] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newCustomObjects).toEqual([]);
+ });
+
+ it('has no removed objects when both the old and new versions are the same', () => {
+ const oldVersion = { types: [new CustomObjectMetadataBuilder().build()] };
+ const newVersion = { types: [new CustomObjectMetadataBuilder().build()] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.removedCustomObjects).toEqual([]);
+ });
+
+ it('lists all new custom objects', () => {
+ const oldVersion = { types: [] };
+ const newObject = new CustomObjectMetadataBuilder().build();
+ const newVersion = { types: [newObject] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newCustomObjects).toEqual([newObject.name]);
+ });
+
+ it('lists all removed custom objects', () => {
+ const oldObject = new CustomObjectMetadataBuilder().build();
+ const oldVersion = { types: [oldObject] };
+ const newVersion = { types: [] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.removedCustomObjects).toEqual([oldObject.name]);
+ });
+
+ it('lists all new fields of a custom object', () => {
+ const oldObject = new CustomObjectMetadataBuilder().build();
+ const newField = new CustomFieldMetadataBuilder().build();
+ const newObject = new CustomObjectMetadataBuilder().withField(newField).build();
+ const oldVersion = { types: [oldObject] };
+ const newVersion = { types: [newObject] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.customObjectModifications).toEqual([
+ {
+ typeName: newObject.name,
+ modifications: [
+ {
+ __typename: 'NewField',
+ name: newField.name,
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('lists all removed fields of a custom object', () => {
+ const oldField = new CustomFieldMetadataBuilder().build();
+ const oldObject = new CustomObjectMetadataBuilder().withField(oldField).build();
+ const newObject = new CustomObjectMetadataBuilder().build();
+ const oldVersion = { types: [oldObject] };
+ const newVersion = { types: [newObject] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.customObjectModifications).toEqual([
+ {
+ typeName: oldObject.name,
+ modifications: [
+ {
+ __typename: 'RemovedField',
+ name: oldField.name,
+ },
+ ],
+ },
+ ]);
+ });
});
- it('lists removed inner interfaces of a class', () => {
- const classBefore = 'public class MyClass { interface OldInterface { } }';
- const oldClass = typeFromRawString(classBefore);
- const classAfter = 'public class MyClass { }';
- const newClass = typeFromRawString(classAfter);
-
- const oldVersion = { types: [oldClass] };
- const newVersion = { types: [newClass] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyClass',
- modifications: [
- {
- __typename: 'RemovedType',
- name: 'OldInterface',
- },
- ],
- },
- ]);
+ describe('with enum code', () => {
+ it('lists all new values of a modified enum', () => {
+ const enumBefore = 'public enum MyEnum { VALUE1 }';
+ const oldEnum = apexTypeFromRawString(enumBefore);
+ const enumAfter = 'public enum MyEnum { VALUE1, VALUE2 }';
+ const newEnum = apexTypeFromRawString(enumAfter);
+
+ const oldVersion = { types: [oldEnum] };
+ const newVersion = { types: [newEnum] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyEnum',
+ modifications: [
+ {
+ __typename: 'NewEnumValue',
+ name: 'VALUE2',
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('list all removed values of a modified enum', () => {
+ const enumBefore = 'public enum MyEnum { VALUE1, VALUE2 }';
+ const oldEnum = apexTypeFromRawString(enumBefore);
+ const enumAfter = 'public enum MyEnum { VALUE1 }';
+ const newEnum = apexTypeFromRawString(enumAfter);
+
+ const oldVersion = { types: [oldEnum] };
+ const newVersion = { types: [newEnum] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyEnum',
+ modifications: [
+ {
+ __typename: 'RemovedEnumValue',
+ name: 'VALUE2',
+ },
+ ],
+ },
+ ]);
+ });
});
- it('lists new inner enums of a class', () => {
- const classBefore = 'public class MyClass { }';
- const oldClass = typeFromRawString(classBefore);
- const classAfter = 'public class MyClass { enum NewEnum { } }';
- const newClass = typeFromRawString(classAfter);
-
- const oldVersion = { types: [oldClass] };
- const newVersion = { types: [newClass] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyClass',
- modifications: [
- {
- __typename: 'NewType',
- name: 'NewEnum',
- },
- ],
- },
- ]);
+ describe('with interface code', () => {
+ it('lists all new methods of an interface', () => {
+ const interfaceBefore = 'public interface MyInterface {}';
+ const oldInterface = apexTypeFromRawString(interfaceBefore);
+ const interfaceAfter = 'public interface MyInterface { void newMethod(); }';
+ const newInterface = apexTypeFromRawString(interfaceAfter);
+
+ const oldVersion = { types: [oldInterface] };
+ const newVersion = { types: [newInterface] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyInterface',
+ modifications: [
+ {
+ __typename: 'NewMethod',
+ name: 'newMethod',
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('lists all removed methods of an interface', () => {
+ const interfaceBefore = 'public interface MyInterface { void oldMethod(); }';
+ const oldInterface = apexTypeFromRawString(interfaceBefore);
+ const interfaceAfter = 'public interface MyInterface {}';
+ const newInterface = apexTypeFromRawString(interfaceAfter);
+
+ const oldVersion = { types: [oldInterface] };
+ const newVersion = { types: [newInterface] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyInterface',
+ modifications: [
+ {
+ __typename: 'RemovedMethod',
+ name: 'oldMethod',
+ },
+ ],
+ },
+ ]);
+ });
});
- it('lists removed inner enums of a class', () => {
- const classBefore = 'public class MyClass { interface OldEnum { } }';
- const oldClass = typeFromRawString(classBefore);
- const classAfter = 'public class MyClass { }';
- const newClass = typeFromRawString(classAfter);
-
- const oldVersion = { types: [oldClass] };
- const newVersion = { types: [newClass] };
-
- const changeLog = processChangelog(oldVersion, newVersion);
-
- expect(changeLog.newOrModifiedMembers).toEqual([
- {
- typeName: 'MyClass',
- modifications: [
- {
- __typename: 'RemovedType',
- name: 'OldEnum',
- },
- ],
- },
- ]);
+ describe('with class code', () => {
+ it('lists all new methods of a class', () => {
+ const classBefore = 'public class MyClass { }';
+ const oldClass = apexTypeFromRawString(classBefore);
+ const classAfter = 'public class MyClass { void newMethod() {} }';
+ const newClass = apexTypeFromRawString(classAfter);
+
+ const oldVersion = { types: [oldClass] };
+ const newVersion = { types: [newClass] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyClass',
+ modifications: [
+ {
+ __typename: 'NewMethod',
+ name: 'newMethod',
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('lists all new properties of a class', () => {
+ const classBefore = 'public class MyClass { }';
+ const oldClass = apexTypeFromRawString(classBefore);
+ const classAfter = 'public class MyClass { String newProperty { get; set; } }';
+ const newClass = apexTypeFromRawString(classAfter);
+
+ const oldVersion = { types: [oldClass] };
+ const newVersion = { types: [newClass] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyClass',
+ modifications: [
+ {
+ __typename: 'NewProperty',
+ name: 'newProperty',
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('lists all removed properties of a class', () => {
+ const classBefore = 'public class MyClass { String oldProperty { get; set; } }';
+ const oldClass = apexTypeFromRawString(classBefore);
+ const classAfter = 'public class MyClass { }';
+ const newClass = apexTypeFromRawString(classAfter);
+
+ const oldVersion = { types: [oldClass] };
+ const newVersion = { types: [newClass] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyClass',
+ modifications: [
+ {
+ __typename: 'RemovedProperty',
+ name: 'oldProperty',
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('lists all new fields of a class', () => {
+ const classBefore = 'public class MyClass { }';
+ const oldClass = apexTypeFromRawString(classBefore);
+ const classAfter = 'public class MyClass { String newField; }';
+ const newClass = apexTypeFromRawString(classAfter);
+
+ const oldVersion = { types: [oldClass] };
+ const newVersion = { types: [newClass] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyClass',
+ modifications: [
+ {
+ __typename: 'NewField',
+ name: 'newField',
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('lists all removed fields of a class', () => {
+ const classBefore = 'public class MyClass { String oldField; }';
+ const oldClass = apexTypeFromRawString(classBefore);
+ const classAfter = 'public class MyClass { }';
+ const newClass = apexTypeFromRawString(classAfter);
+
+ const oldVersion = { types: [oldClass] };
+ const newVersion = { types: [newClass] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyClass',
+ modifications: [
+ {
+ __typename: 'RemovedField',
+ name: 'oldField',
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('lists new inner classes of a class', () => {
+ const classBefore = 'public class MyClass { }';
+ const oldClass = apexTypeFromRawString(classBefore);
+ const classAfter = 'public class MyClass { class NewInnerClass { } }';
+ const newClass = apexTypeFromRawString(classAfter);
+
+ const oldVersion = { types: [oldClass] };
+ const newVersion = { types: [newClass] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyClass',
+ modifications: [
+ {
+ __typename: 'NewType',
+ name: 'NewInnerClass',
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('lists removed inner classes of a class', () => {
+ const classBefore = 'public class MyClass { class OldInnerClass { } }';
+ const oldClass = apexTypeFromRawString(classBefore);
+ const classAfter = 'public class MyClass { }';
+ const newClass = apexTypeFromRawString(classAfter);
+
+ const oldVersion = { types: [oldClass] };
+ const newVersion = { types: [newClass] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyClass',
+ modifications: [
+ {
+ __typename: 'RemovedType',
+ name: 'OldInnerClass',
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('lists new inner interfaces of a class', () => {
+ const classBefore = 'public class MyClass { }';
+ const oldClass = apexTypeFromRawString(classBefore);
+ const classAfter = 'public class MyClass { interface NewInterface { } }';
+ const newClass = apexTypeFromRawString(classAfter);
+
+ const oldVersion = { types: [oldClass] };
+ const newVersion = { types: [newClass] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyClass',
+ modifications: [
+ {
+ __typename: 'NewType',
+ name: 'NewInterface',
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('lists removed inner interfaces of a class', () => {
+ const classBefore = 'public class MyClass { interface OldInterface { } }';
+ const oldClass = apexTypeFromRawString(classBefore);
+ const classAfter = 'public class MyClass { }';
+ const newClass = apexTypeFromRawString(classAfter);
+
+ const oldVersion = { types: [oldClass] };
+ const newVersion = { types: [newClass] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyClass',
+ modifications: [
+ {
+ __typename: 'RemovedType',
+ name: 'OldInterface',
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('lists new inner enums of a class', () => {
+ const classBefore = 'public class MyClass { }';
+ const oldClass = apexTypeFromRawString(classBefore);
+ const classAfter = 'public class MyClass { enum NewEnum { } }';
+ const newClass = apexTypeFromRawString(classAfter);
+
+ const oldVersion = { types: [oldClass] };
+ const newVersion = { types: [newClass] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyClass',
+ modifications: [
+ {
+ __typename: 'NewType',
+ name: 'NewEnum',
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('lists removed inner enums of a class', () => {
+ const classBefore = 'public class MyClass { interface OldEnum { } }';
+ const oldClass = apexTypeFromRawString(classBefore);
+ const classAfter = 'public class MyClass { }';
+ const newClass = apexTypeFromRawString(classAfter);
+
+ const oldVersion = { types: [oldClass] };
+ const newVersion = { types: [newClass] };
+
+ const changeLog = processChangelog(oldVersion, newVersion);
+
+ expect(changeLog.newOrModifiedApexMembers).toEqual([
+ {
+ typeName: 'MyClass',
+ modifications: [
+ {
+ __typename: 'RemovedType',
+ name: 'OldEnum',
+ },
+ ],
+ },
+ ]);
+ });
});
});
diff --git a/src/core/changelog/generate-change-log.ts b/src/core/changelog/generate-change-log.ts
index 9552308f..4071b6df 100644
--- a/src/core/changelog/generate-change-log.ts
+++ b/src/core/changelog/generate-change-log.ts
@@ -1,4 +1,10 @@
-import { ParsedFile, Skip, UnparsedApexBundle, UserDefinedChangelogConfig } from '../shared/types';
+import {
+ ParsedFile,
+ Skip,
+ UnparsedApexBundle,
+ UnparsedSourceBundle,
+ UserDefinedChangelogConfig,
+} from '../shared/types';
import { pipe } from 'fp-ts/function';
import * as TE from 'fp-ts/TaskEither';
import { reflectApexSource } from '../reflection/apex/reflect-apex-source';
@@ -9,7 +15,11 @@ import { changelogTemplate } from './templates/changelog-template';
import { ReflectionErrors } from '../errors/errors';
import { apply } from '#utils/fp';
import { filterScope } from '../reflection/apex/filter-scope';
-import { isApexType, skip } from '../shared/utils';
+import { skip } from '../shared/utils';
+import { reflectCustomFieldsAndObjects } from '../reflection/sobject/reflectCustomFieldsAndObjects';
+import { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources';
+import { Type } from '@cparra/apex-reflection';
+import { filterApexSourceFiles, filterCustomObjectsAndFields } from '#utils/source-bundle-utils';
export type ChangeLogPageData = {
content: string;
@@ -17,16 +27,10 @@ export type ChangeLogPageData = {
};
export function generateChangeLog(
- oldBundles: UnparsedApexBundle[],
- newBundles: UnparsedApexBundle[],
+ oldBundles: UnparsedSourceBundle[],
+ newBundles: UnparsedSourceBundle[],
config: Omit,
): TE.TaskEither {
- const filterOutOfScope = apply(filterScope, config.scope);
-
- function reflect(sourceFiles: UnparsedApexBundle[]) {
- return pipe(reflectApexSource(sourceFiles), TE.map(filterOutOfScope));
- }
-
const convertToPageData = apply(toPageData, config.fileName);
function handleConversion({ changelog, newManifest }: { changelog: Changelog; newManifest: VersionManifest }) {
@@ -37,9 +41,9 @@ export function generateChangeLog(
}
return pipe(
- reflect(oldBundles),
+ reflect(oldBundles, config),
TE.bindTo('oldVersion'),
- TE.bind('newVersion', () => reflect(newBundles)),
+ TE.bind('newVersion', () => reflect(newBundles, config)),
TE.map(toManifests),
TE.map(({ oldManifest, newManifest }) => ({
changelog: processChangelog(oldManifest, newManifest),
@@ -49,13 +53,34 @@ export function generateChangeLog(
);
}
-function toManifests({ oldVersion, newVersion }: { oldVersion: ParsedFile[]; newVersion: ParsedFile[] }) {
- function parsedFilesToManifest(parsedFiles: ParsedFile[]): VersionManifest {
+function reflect(bundles: UnparsedSourceBundle[], config: Omit) {
+ const filterOutOfScopeApex = apply(filterScope, config.scope);
+
+ function reflectApexFiles(sourceFiles: UnparsedApexBundle[]) {
+ return pipe(reflectApexSource(sourceFiles), TE.map(filterOutOfScopeApex));
+ }
+
+ return pipe(
+ reflectApexFiles(filterApexSourceFiles(bundles)),
+ TE.chain((parsedApexFiles) => {
+ return pipe(
+ reflectCustomFieldsAndObjects(filterCustomObjectsAndFields(bundles)),
+ TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]),
+ );
+ }),
+ );
+}
+
+function toManifests({
+ oldVersion,
+ newVersion,
+}: {
+ oldVersion: ParsedFile[];
+ newVersion: ParsedFile[];
+}) {
+ function parsedFilesToManifest(parsedFiles: ParsedFile[]): VersionManifest {
return {
- types: parsedFiles
- .map((parsedFile) => parsedFile.type)
- // Changelog does not currently support object types
- .filter((type) => isApexType(type)),
+ types: parsedFiles.map((parsedFile) => parsedFile.type),
};
}
diff --git a/src/core/changelog/process-changelog.ts b/src/core/changelog/process-changelog.ts
index 6ae1ecdc..b9d8695d 100644
--- a/src/core/changelog/process-changelog.ts
+++ b/src/core/changelog/process-changelog.ts
@@ -1,9 +1,10 @@
-import { ClassMirror, EnumMirror, MethodMirror, Type } from '@cparra/apex-reflection';
+import { ClassMirror, EnumMirror, InterfaceMirror, MethodMirror, Type } from '@cparra/apex-reflection';
import { pipe } from 'fp-ts/function';
import { areMethodsEqual } from './method-changes-checker';
+import { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources';
export type VersionManifest = {
- types: Type[];
+ types: (Type | CustomObjectMetadata)[];
};
type ModificationTypes =
@@ -29,40 +30,64 @@ export type NewOrModifiedMember = {
};
export type Changelog = {
- newTypes: string[];
- removedTypes: string[];
- newOrModifiedMembers: NewOrModifiedMember[];
+ newApexTypes: string[];
+ removedApexTypes: string[];
+ newOrModifiedApexMembers: NewOrModifiedMember[];
+ newCustomObjects: string[];
+ removedCustomObjects: string[];
+ customObjectModifications: NewOrModifiedMember[];
};
export function hasChanges(changelog: Changelog): boolean {
return (
- changelog.newTypes.length > 0 || changelog.removedTypes.length > 0 || changelog.newOrModifiedMembers.length > 0
+ changelog.newApexTypes.length > 0 ||
+ changelog.removedApexTypes.length > 0 ||
+ changelog.newOrModifiedApexMembers.length > 0
);
}
export function processChangelog(oldVersion: VersionManifest, newVersion: VersionManifest): Changelog {
return {
- newTypes: getNewTypes(oldVersion, newVersion),
- removedTypes: getRemovedTypes(oldVersion, newVersion),
- newOrModifiedMembers: getNewOrModifiedMembers(oldVersion, newVersion),
+ newApexTypes: getNewApexTypes(oldVersion, newVersion),
+ removedApexTypes: getRemovedApexTypes(oldVersion, newVersion),
+ newOrModifiedApexMembers: getNewOrModifiedApexMembers(oldVersion, newVersion),
+ newCustomObjects: getNewCustomObjects(oldVersion, newVersion),
+ removedCustomObjects: getRemovedCustomObjects(oldVersion, newVersion),
+ customObjectModifications: getCustomObjectModifications(oldVersion, newVersion),
};
}
-function getNewTypes(oldVersion: VersionManifest, newVersion: VersionManifest): string[] {
+function getNewApexTypes(oldVersion: VersionManifest, newVersion: VersionManifest): string[] {
return newVersion.types
+ .filter((newType): newType is Type => newType.type_name !== 'customobject')
.filter((newType) => !oldVersion.types.some((oldType) => oldType.name.toLowerCase() === newType.name.toLowerCase()))
.map((type) => type.name);
}
-function getRemovedTypes(oldVersion: VersionManifest, newVersion: VersionManifest): string[] {
+function getRemovedApexTypes(oldVersion: VersionManifest, newVersion: VersionManifest): string[] {
return oldVersion.types
+ .filter((newType): newType is Type => newType.type_name !== 'customobject')
.filter((oldType) => !newVersion.types.some((newType) => newType.name.toLowerCase() === oldType.name.toLowerCase()))
.map((type) => type.name);
}
-function getNewOrModifiedMembers(oldVersion: VersionManifest, newVersion: VersionManifest): NewOrModifiedMember[] {
+function getNewCustomObjects(oldVersion: VersionManifest, newVersion: VersionManifest): string[] {
+ return newVersion.types
+ .filter((newType): newType is CustomObjectMetadata => newType.type_name === 'customobject')
+ .filter((newType) => !oldVersion.types.some((oldType) => oldType.name.toLowerCase() === newType.name.toLowerCase()))
+ .map((type) => type.name);
+}
+
+function getRemovedCustomObjects(oldVersion: VersionManifest, newVersion: VersionManifest): string[] {
+ return oldVersion.types
+ .filter((newType): newType is CustomObjectMetadata => newType.type_name === 'customobject')
+ .filter((oldType) => !newVersion.types.some((newType) => newType.name.toLowerCase() === oldType.name.toLowerCase()))
+ .map((type) => type.name);
+}
+
+function getNewOrModifiedApexMembers(oldVersion: VersionManifest, newVersion: VersionManifest): NewOrModifiedMember[] {
return pipe(
- getTypesInBothVersions(oldVersion, newVersion),
+ getApexTypesInBothVersions(oldVersion, newVersion),
(typesInBoth) => [
...getNewOrModifiedEnumValues(typesInBoth),
...getNewOrModifiedMethods(typesInBoth),
@@ -72,13 +97,36 @@ function getNewOrModifiedMembers(oldVersion: VersionManifest, newVersion: Versio
);
}
-function getNewOrModifiedEnumValues(typesInBoth: TypeInBoth[]): NewOrModifiedMember[] {
+function getCustomObjectModifications(oldVersion: VersionManifest, newVersion: VersionManifest): NewOrModifiedMember[] {
+ return pipe(
+ getCustomObjectsInBothVersions(oldVersion, newVersion),
+ (customObjectsInBoth) => getNewOrRemovedCustomFields(customObjectsInBoth),
+ (customObjectModifications) => customObjectModifications.filter((member) => member.modifications.length > 0),
+ );
+}
+
+function getNewOrRemovedCustomFields(typesInBoth: TypeInBoth[]): NewOrModifiedMember[] {
+ return typesInBoth.map(({ oldType, newType }) => {
+ const oldCustomObject = oldType;
+ const newCustomObject = newType;
+
+ return {
+ typeName: newType.name,
+ modifications: [
+ ...getNewValues(oldCustomObject, newCustomObject, 'fields', 'NewField'),
+ ...getRemovedValues(oldCustomObject, newCustomObject, 'fields', 'RemovedField'),
+ ],
+ };
+ });
+}
+
+function getNewOrModifiedEnumValues(typesInBoth: TypeInBoth[]): NewOrModifiedMember[] {
return pipe(
- typesInBoth.filter((typeInBoth) => typeInBoth.oldType.type_name === 'enum'),
+ typesInBoth.filter((typeInBoth): typeInBoth is TypeInBoth => typeInBoth.oldType.type_name === 'enum'),
(enumsInBoth) =>
enumsInBoth.map(({ oldType, newType }) => {
- const oldEnum = oldType as EnumMirror;
- const newEnum = newType as EnumMirror;
+ const oldEnum = oldType;
+ const newEnum = newType;
return {
typeName: newType.name,
modifications: [
@@ -90,27 +138,28 @@ function getNewOrModifiedEnumValues(typesInBoth: TypeInBoth[]): NewOrModifiedMem
);
}
-function getNewOrModifiedMethods(typesInBoth: TypeInBoth[]): NewOrModifiedMember[] {
+function getNewOrModifiedMethods(typesInBoth: TypeInBoth[]): NewOrModifiedMember[] {
return pipe(
typesInBoth.filter(
- (typeInBoth) => typeInBoth.oldType.type_name === 'class' || typeInBoth.oldType.type_name === 'interface',
+ (typeInBoth): typeInBoth is TypeInBoth =>
+ typeInBoth.oldType.type_name === 'class' || typeInBoth.oldType.type_name === 'interface',
),
(typesInBoth) =>
typesInBoth.map(({ oldType, newType }) => {
- const oldMethodAware = oldType as MethodAware;
- const newMethodAware = newType as MethodAware;
+ const oldMethodAware = oldType;
+ const newMethodAware = newType;
return {
typeName: newType.name,
modifications: [
- ...getNewValues(
+ ...getNewValues(
oldMethodAware,
newMethodAware,
'methods',
'NewMethod',
areMethodsEqual,
),
- ...getRemovedValues(
+ ...getRemovedValues(
oldMethodAware,
newMethodAware,
'methods',
@@ -123,7 +172,7 @@ function getNewOrModifiedMethods(typesInBoth: TypeInBoth[]): NewOrModifiedMember
);
}
-function getNewOrModifiedClassMembers(typesInBoth: TypeInBoth[]): NewOrModifiedMember[] {
+function getNewOrModifiedClassMembers(typesInBoth: TypeInBoth[]): NewOrModifiedMember[] {
return pipe(
typesInBoth.filter((typeInBoth) => typeInBoth.oldType.type_name === 'class'),
(classesInBoth) =>
@@ -150,18 +199,32 @@ function getNewOrModifiedClassMembers(typesInBoth: TypeInBoth[]): NewOrModifiedM
);
}
-type TypeInBoth = {
- oldType: Type;
- newType: Type;
+type TypeInBoth = {
+ oldType: T;
+ newType: T;
};
-function getTypesInBothVersions(oldVersion: VersionManifest, newVersion: VersionManifest): TypeInBoth[] {
+function getApexTypesInBothVersions(oldVersion: VersionManifest, newVersion: VersionManifest): TypeInBoth[] {
+ return oldVersion.types
+ .filter((newType): newType is Type => newType.type_name !== 'customobject')
+ .map((oldType) => ({
+ oldType,
+ newType: newVersion.types.find((newType) => newType.name.toLowerCase() === oldType.name.toLowerCase()),
+ }))
+ .filter((type): type is TypeInBoth => type.newType !== undefined);
+}
+
+function getCustomObjectsInBothVersions(
+ oldVersion: VersionManifest,
+ newVersion: VersionManifest,
+): TypeInBoth[] {
return oldVersion.types
+ .filter((newType): newType is CustomObjectMetadata => newType.type_name === 'customobject')
.map((oldType) => ({
oldType,
newType: newVersion.types.find((newType) => newType.name.toLowerCase() === oldType.name.toLowerCase()),
}))
- .filter((type) => type.newType !== undefined) as TypeInBoth[];
+ .filter((type): type is TypeInBoth => type.newType !== undefined);
}
type NameAware = {
@@ -169,16 +232,17 @@ type NameAware = {
};
type AreEqualFn = (oldValue: T, newValue: T) => boolean;
+
function areEqualByName(oldValue: T, newValue: T): boolean {
return oldValue.name.toLowerCase() === newValue.name.toLowerCase();
}
-function getNewValues, K extends keyof T>(
+function getNewValues, K extends keyof T>(
oldPlaceToSearch: T,
newPlaceToSearch: T,
keyToSearch: K,
typeName: ModificationTypes,
- areEqualFn: AreEqualFn = areEqualByName,
+ areEqualFn: AreEqualFn = areEqualByName,
): MemberModificationType[] {
return newPlaceToSearch[keyToSearch]
.filter((newValue) => !oldPlaceToSearch[keyToSearch].some((oldValue) => areEqualFn(oldValue, newValue)))
@@ -198,7 +262,3 @@ function getRemovedValues,
.map((value) => value.name)
.map((name) => ({ __typename: typeName, name }));
}
-
-type MethodAware = {
- methods: MethodMirror[];
-};
diff --git a/src/core/changelog/renderable-changelog.ts b/src/core/changelog/renderable-changelog.ts
index 43aa4982..dead2477 100644
--- a/src/core/changelog/renderable-changelog.ts
+++ b/src/core/changelog/renderable-changelog.ts
@@ -1,14 +1,15 @@
import { Changelog, MemberModificationType, NewOrModifiedMember } from './process-changelog';
-import { Type } from '@cparra/apex-reflection';
+import { ClassMirror, EnumMirror, InterfaceMirror, Type } from '@cparra/apex-reflection';
import { RenderableContent } from '../renderables/types';
import { adaptDescribable } from '../renderables/documentables';
+import { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources';
type NewTypeRenderable = {
name: string;
description?: RenderableContent[];
};
-type NewTypeSection = {
+type NewTypeSection = {
__type: T;
heading: string;
description: string;
@@ -38,16 +39,25 @@ export type RenderableChangelog = {
newEnums: NewTypeSection<'enum'> | null;
removedTypes: RemovedTypeSection | null;
newOrModifiedMembers: NewOrModifiedMembersSection | null;
+ newCustomObjects: NewTypeSection<'customobject'> | null;
+ removedCustomObjects: RemovedTypeSection | null;
+ newOrRemovedCustomFields: NewOrModifiedMembersSection | null;
};
-export function convertToRenderableChangelog(changelog: Changelog, newManifest: Type[]): RenderableChangelog {
- const allNewTypes = changelog.newTypes.map(
+export function convertToRenderableChangelog(
+ changelog: Changelog,
+ newManifest: (Type | CustomObjectMetadata)[],
+): RenderableChangelog {
+ const allNewTypes = [...changelog.newApexTypes, ...changelog.newCustomObjects].map(
(newType) => newManifest.find((type) => type.name.toLowerCase() === newType.toLowerCase())!,
);
- const newClasses = allNewTypes.filter((type) => type.type_name === 'class');
- const newInterfaces = allNewTypes.filter((type) => type.type_name === 'interface');
- const newEnums = allNewTypes.filter((type) => type.type_name === 'enum');
+ const newClasses = allNewTypes.filter((type): type is ClassMirror => type.type_name === 'class');
+ const newInterfaces = allNewTypes.filter((type): type is InterfaceMirror => type.type_name === 'interface');
+ const newEnums = allNewTypes.filter((type): type is EnumMirror => type.type_name === 'enum');
+ const newCustomObjects = allNewTypes.filter(
+ (type): type is CustomObjectMetadata => type.type_name === 'customobject',
+ );
return {
newClasses:
@@ -78,15 +88,43 @@ export function convertToRenderableChangelog(changelog: Changelog, newManifest:
}
: null,
removedTypes:
- changelog.removedTypes.length > 0
- ? { heading: 'Removed Types', description: 'These types have been removed.', types: changelog.removedTypes }
+ changelog.removedApexTypes.length > 0
+ ? { heading: 'Removed Types', description: 'These types have been removed.', types: changelog.removedApexTypes }
: null,
newOrModifiedMembers:
- changelog.newOrModifiedMembers.length > 0
+ changelog.newOrModifiedApexMembers.length > 0
? {
heading: 'New or Modified Members in Existing Types',
description: 'These members have been added or modified.',
- modifications: changelog.newOrModifiedMembers.map(toRenderableModification),
+ modifications: changelog.newOrModifiedApexMembers.map(toRenderableModification),
+ }
+ : null,
+ newCustomObjects:
+ newCustomObjects.length > 0
+ ? {
+ __type: 'customobject',
+ heading: 'New Custom Objects',
+ description: 'These custom objects are new.',
+ types: newCustomObjects.map((type) => ({
+ name: type.name,
+ description: type.description ? [type.description] : undefined,
+ })),
+ }
+ : null,
+ removedCustomObjects:
+ changelog.removedCustomObjects.length > 0
+ ? {
+ heading: 'Removed Custom Objects',
+ description: 'These custom objects have been removed.',
+ types: changelog.removedCustomObjects,
+ }
+ : null,
+ newOrRemovedCustomFields:
+ changelog.customObjectModifications.length > 0
+ ? {
+ heading: 'New or Removed Fields in Existing Objects',
+ description: 'These custom fields have been added or removed.',
+ modifications: changelog.customObjectModifications.map(toRenderableModification),
}
: null,
};
diff --git a/src/core/changelog/templates/changelog-template.ts b/src/core/changelog/templates/changelog-template.ts
index 3d982da2..eb6f431b 100644
--- a/src/core/changelog/templates/changelog-template.ts
+++ b/src/core/changelog/templates/changelog-template.ts
@@ -37,8 +37,20 @@ export const changelogTemplate = `
{{/each}}
{{/if}}
+{{#if newCustomObjects}}
+## {{newCustomObjects.heading}}
+
+{{newCustomObjects.description}}
+
+{{#each newCustomObjects.types}}
+### {{this.name}}
+
+{{{renderContent this.description}}}
+{{/each}}
+{{/if}}
+
{{#if removedTypes}}
-## Removed Types
+## {{removedTypes.heading}}
{{removedTypes.description}}
@@ -47,6 +59,16 @@ export const changelogTemplate = `
{{/each}}
{{/if}}
+{{#if removedCustomObjects}}
+## {{removedCustomObjects.heading}}
+
+{{removedCustomObjects.description}}
+
+{{#each removedCustomObjects.types}}
+- {{this}}
+{{/each}}
+{{/if}}
+
{{#if newOrModifiedMembers}}
## {{newOrModifiedMembers.heading}}
@@ -58,6 +80,21 @@ export const changelogTemplate = `
{{#each this.modifications}}
- {{this}}
{{/each}}
+{{/each}}
+{{/if}}
+
+{{#if newOrRemovedCustomFields}}
+## {{newOrRemovedCustomFields.heading}}
+
+{{newOrRemovedCustomFields.description}}
+
+{{#each newOrRemovedCustomFields.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 e1af6d39..b5768434 100644
--- a/src/core/markdown/__test__/generating-custom-object-docs.spec.ts
+++ b/src/core/markdown/__test__/generating-custom-object-docs.spec.ts
@@ -1,13 +1,7 @@
import { extendExpect } from './expect-extensions';
-import {
- customField,
- customFieldPickListValues,
- customObjectGenerator,
- generateDocs,
- unparsedFieldBundleFromRawString,
- unparsedObjectBundleFromRawString,
-} from './test-helpers';
+import { customFieldPickListValues, generateDocs, unparsedObjectBundleFromRawString } from './test-helpers';
import { assertEither } from '../../test-helpers/assert-either';
+import { customObjectGenerator, unparsedFieldBundleFromRawString } from '../../test-helpers/test-data-builders';
describe('Generates Custom Object documentation', () => {
beforeAll(() => {
@@ -47,7 +41,7 @@ describe('Generates Custom Object documentation', () => {
expect(result).documentationBundleHasLength(1);
assertEither(result, (data) => expect(data).firstDocContains('`TestObject__c`'));
});
-
+
it('displays the Fields heading if fields are present', async () => {
const customObjectBundle = unparsedObjectBundleFromRawString({
rawContent: customObjectGenerator(),
@@ -55,7 +49,6 @@ describe('Generates Custom Object documentation', () => {
});
const customFieldBundle = unparsedFieldBundleFromRawString({
- rawContent: customField,
filePath: 'src/object/TestField__c.field-meta.xml',
parentName: 'TestObject__c',
});
@@ -102,7 +95,6 @@ describe('Generates Custom Object documentation', () => {
});
const customFieldBundle = unparsedFieldBundleFromRawString({
- rawContent: customField,
filePath: 'src/object/TestField__c.field-meta.xml',
parentName: 'TestObject__c',
});
@@ -119,7 +111,6 @@ describe('Generates Custom Object documentation', () => {
});
const customFieldBundle = unparsedFieldBundleFromRawString({
- rawContent: customField,
filePath: 'src/object/TestField__c.field-meta.xml',
parentName: 'TestObject__c',
});
@@ -136,7 +127,6 @@ describe('Generates Custom Object documentation', () => {
});
const customFieldBundle = unparsedFieldBundleFromRawString({
- rawContent: customField,
filePath: 'src/object/TestField__c.field-meta.xml',
parentName: 'TestObject__c',
});
diff --git a/src/core/markdown/__test__/generating-docs.spec.ts b/src/core/markdown/__test__/generating-docs.spec.ts
index e8f6fb32..fd22690a 100644
--- a/src/core/markdown/__test__/generating-docs.spec.ts
+++ b/src/core/markdown/__test__/generating-docs.spec.ts
@@ -1,12 +1,8 @@
import { DocPageData, PostHookDocumentationBundle } from '../../shared/types';
import { extendExpect } from './expect-extensions';
-import {
- unparsedApexBundleFromRawString,
- generateDocs,
- unparsedObjectBundleFromRawString,
- customObjectGenerator,
-} from './test-helpers';
+import { unparsedApexBundleFromRawString, generateDocs, unparsedObjectBundleFromRawString } from './test-helpers';
import { assertEither } from '../../test-helpers/assert-either';
+import { customObjectGenerator } from '../../test-helpers/test-data-builders';
function aSingleDoc(result: PostHookDocumentationBundle): DocPageData {
expect(result.docs).toHaveLength(1);
diff --git a/src/core/markdown/__test__/generating-reference-guide.spec.ts b/src/core/markdown/__test__/generating-reference-guide.spec.ts
index 510e9458..6650c71d 100644
--- a/src/core/markdown/__test__/generating-reference-guide.spec.ts
+++ b/src/core/markdown/__test__/generating-reference-guide.spec.ts
@@ -1,14 +1,10 @@
import { extendExpect } from './expect-extensions';
import { pipe } from 'fp-ts/function';
import * as E from 'fp-ts/Either';
-import {
- unparsedApexBundleFromRawString,
- generateDocs,
- customObjectGenerator,
- unparsedObjectBundleFromRawString,
-} from './test-helpers';
+import { unparsedApexBundleFromRawString, generateDocs, unparsedObjectBundleFromRawString } from './test-helpers';
import { ReferenceGuidePageData } from '../../shared/types';
import { assertEither } from '../../test-helpers/assert-either';
+import { customObjectGenerator } from '../../test-helpers/test-data-builders';
describe('When generating the Reference Guide', () => {
beforeAll(() => {
diff --git a/src/core/markdown/__test__/test-helpers.ts b/src/core/markdown/__test__/test-helpers.ts
index c711414d..2f126de6 100644
--- a/src/core/markdown/__test__/test-helpers.ts
+++ b/src/core/markdown/__test__/test-helpers.ts
@@ -1,9 +1,4 @@
-import {
- UnparsedApexBundle,
- UnparsedCustomFieldBundle,
- UnparsedCustomObjectBundle,
- UnparsedSourceBundle,
-} from '../../shared/types';
+import { UnparsedApexBundle, UnparsedCustomObjectBundle, UnparsedSourceBundle } from '../../shared/types';
import { generateDocs as gen, MarkdownGeneratorConfig } from '../generate-docs';
import { referenceGuideTemplate } from '../templates/reference-guide';
@@ -29,20 +24,6 @@ export function unparsedObjectBundleFromRawString(meta: {
};
}
-export function unparsedFieldBundleFromRawString(meta: {
- rawContent: string;
- filePath: string;
- parentName: string;
-}): UnparsedCustomFieldBundle {
- return {
- type: 'customfield',
- name: 'TestField__c',
- filePath: meta.filePath,
- content: meta.rawContent,
- parentName: meta.parentName,
- };
-}
-
export function generateDocs(apexBundles: UnparsedSourceBundle[], config?: Partial) {
return gen(apexBundles, {
targetDir: 'target',
@@ -59,32 +40,6 @@ export function generateDocs(apexBundles: UnparsedSourceBundle[], config?: Parti
});
}
-export function customObjectGenerator(
- config: { deploymentStatus: string; visibility: string } = { deploymentStatus: 'Deployed', visibility: 'Public' },
-) {
- return `
-
-
- ${config.deploymentStatus}
- test object for testing
- MyTestObject
- MyFirstObjects
- ${config.visibility}
- `;
-}
-
-export const customField = `
-
-
- PhotoUrl__c
- false
- PhotoUrl
- false
- false
- Url
- A URL that points to a photo
- `;
-
export const customFieldPickListValues = `
diff --git a/src/core/markdown/adapters/reference-guide.ts b/src/core/markdown/adapters/reference-guide.ts
index 81804530..f3a8381d 100644
--- a/src/core/markdown/adapters/reference-guide.ts
+++ b/src/core/markdown/adapters/reference-guide.ts
@@ -1,12 +1,12 @@
import { MarkdownGeneratorConfig } from '../generate-docs';
import { DocPageReference, ParsedFile } from '../../shared/types';
import { getTypeGroup } from '../../shared/utils';
-import { ObjectMetadata } from '../../reflection/sobject/reflect-custom-object-sources';
+import { CustomObjectMetadata } from '../../reflection/sobject/reflect-custom-object-sources';
import { Type } from '@cparra/apex-reflection';
export function parsedFilesToReferenceGuide(
config: MarkdownGeneratorConfig,
- parsedFiles: ParsedFile[],
+ parsedFiles: ParsedFile[],
): Record {
return parsedFiles.reduce>((acc, parsedFile) => {
acc[parsedFile.source.name] = parsedFileToDocPageReference(config, parsedFile);
@@ -16,7 +16,7 @@ export function parsedFilesToReferenceGuide(
function parsedFileToDocPageReference(
config: MarkdownGeneratorConfig,
- parsedFile: ParsedFile,
+ parsedFile: ParsedFile,
): DocPageReference {
const path = `${slugify(getTypeGroup(parsedFile.type, config))}/${parsedFile.source.name}.md`;
return {
diff --git a/src/core/markdown/adapters/renderable-bundle.ts b/src/core/markdown/adapters/renderable-bundle.ts
index af4cd633..9c6182a6 100644
--- a/src/core/markdown/adapters/renderable-bundle.ts
+++ b/src/core/markdown/adapters/renderable-bundle.ts
@@ -13,17 +13,17 @@ import { apply } from '#utils/fp';
import { generateLink } from './generate-link';
import { getTypeGroup } from '../../shared/utils';
import { Type } from '@cparra/apex-reflection';
-import { ObjectMetadata } from '../../reflection/sobject/reflect-custom-object-sources';
+import { CustomObjectMetadata } from '../../reflection/sobject/reflect-custom-object-sources';
export function parsedFilesToRenderableBundle(
config: MarkdownGeneratorConfig,
- parsedFiles: ParsedFile[],
+ parsedFiles: ParsedFile[],
references: Record,
): RenderableBundle {
const referenceFinder = apply(generateLink(config.linkingStrategy), references);
function toReferenceGuide(
- parsedFiles: ParsedFile[],
+ parsedFiles: ParsedFile[],
): Record {
return parsedFiles.reduce>(
addToReferenceGuide(apply(referenceFinder, '__base__'), config, references),
@@ -31,7 +31,7 @@ export function parsedFilesToRenderableBundle(
);
}
- function toRenderables(parsedFiles: ParsedFile[]): Renderable[] {
+ function toRenderables(parsedFiles: ParsedFile[]): Renderable[] {
return parsedFiles.reduce((acc, parsedFile) => {
const renderable = typeToRenderable(parsedFile, apply(referenceFinder, parsedFile.source.name), config);
acc.push(renderable);
@@ -50,7 +50,7 @@ function addToReferenceGuide(
config: MarkdownGeneratorConfig,
references: Record,
) {
- return (acc: Record, parsedFile: ParsedFile) => {
+ return (acc: Record, parsedFile: ParsedFile) => {
const group: string = getTypeGroup(parsedFile.type, config);
if (!acc[group]) {
acc[group] = [];
@@ -66,7 +66,7 @@ function addToReferenceGuide(
}
function getRenderableDescription(
- type: Type | ObjectMetadata,
+ type: Type | CustomObjectMetadata,
findLinkFromHome: (referenceName: string) => string | Link,
): RenderableContent[] | null {
switch (type.type_name) {
diff --git a/src/core/markdown/adapters/type-to-renderable.ts b/src/core/markdown/adapters/type-to-renderable.ts
index 638e9375..85936078 100644
--- a/src/core/markdown/adapters/type-to-renderable.ts
+++ b/src/core/markdown/adapters/type-to-renderable.ts
@@ -18,11 +18,11 @@ import { adaptConstructor, adaptMethod } from './methods-and-constructors';
import { adaptFieldOrProperty } from './fields-and-properties';
import { MarkdownGeneratorConfig } from '../generate-docs';
import { SourceFileMetadata } from '../../shared/types';
-import { ObjectMetadata } from '../../reflection/sobject/reflect-custom-object-sources';
+import { CustomObjectMetadata } from '../../reflection/sobject/reflect-custom-object-sources';
import { getTypeGroup } from '../../shared/utils';
import { CustomFieldMetadata } from '../../reflection/sobject/reflect-custom-field-source';
-type GetReturnRenderable = T extends InterfaceMirror
+type GetReturnRenderable = T extends InterfaceMirror
? RenderableInterface
: T extends ClassMirror
? RenderableClass
@@ -30,7 +30,7 @@ type GetReturnRenderable = T extends InterfaceM
? RenderableEnum
: RenderableCustomObject;
-export function typeToRenderable(
+export function typeToRenderable(
parsedFile: { source: SourceFileMetadata; type: T },
linkGenerator: GetRenderableContentByTypeName,
config: MarkdownGeneratorConfig,
@@ -45,7 +45,7 @@ export function typeToRenderable(
case 'class':
return classTypeToClassSource(type as ClassMirrorWithInheritanceChain, linkGenerator);
case 'customobject':
- return objectMetadataToRenderable(type as ObjectMetadata, config);
+ return objectMetadataToRenderable(type as CustomObjectMetadata, config);
}
}
@@ -247,7 +247,7 @@ function singleGroup(
}
function objectMetadataToRenderable(
- objectMetadata: ObjectMetadata,
+ objectMetadata: CustomObjectMetadata,
config: MarkdownGeneratorConfig,
): RenderableCustomObject {
return {
@@ -264,7 +264,7 @@ function objectMetadataToRenderable(
fields: {
headingLevel: 2,
heading: 'Fields',
- value: objectMetadata.fields.map((field) => fieldMetadataToRenderable(field.type, config, 3)),
+ value: objectMetadata.fields.map((field) => fieldMetadataToRenderable(field, config, 3)),
},
};
}
diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts
index 71a53ec9..a0464ac3 100644
--- a/src/core/markdown/generate-docs.ts
+++ b/src/core/markdown/generate-docs.ts
@@ -17,9 +17,7 @@ import {
DocPageReference,
TransformReference,
ParsedFile,
- UnparsedCustomObjectBundle,
UnparsedSourceBundle,
- UnparsedCustomFieldBundle,
} from '../shared/types';
import { parsedFilesToRenderableBundle } from './adapters/renderable-bundle';
import { reflectApexSource } from '../reflection/apex/reflect-apex-source';
@@ -33,10 +31,11 @@ import { sortTypesAndMembers } from '../reflection/sort-types-and-members';
import { isSkip } from '../shared/utils';
import { parsedFilesToReferenceGuide } from './adapters/reference-guide';
import { removeExcludedTags } from '../reflection/apex/remove-excluded-tags';
-import { HookError, ReflectionErrors } from '../errors/errors';
-import { ObjectMetadata, reflectCustomObjectSources } from '../reflection/sobject/reflect-custom-object-sources';
-import { CustomFieldMetadata, reflectCustomFieldSources } from '../reflection/sobject/reflect-custom-field-source';
+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';
export type MarkdownGeneratorConfig = Omit<
UserDefinedMarkdownConfig,
@@ -45,7 +44,7 @@ export type MarkdownGeneratorConfig = Omit<
referenceGuideTemplate: string;
};
-export function generateDocs(unparsedApexFiles: UnparsedSourceBundle[], config: MarkdownGeneratorConfig) {
+export function generateDocs(unparsedBundles: UnparsedSourceBundle[], config: MarkdownGeneratorConfig) {
const convertToReferences = apply(parsedFilesToReferenceGuide, config);
const convertToRenderableBundle = apply(parsedFilesToRenderableBundle, config);
const convertToDocumentationBundleForTemplate = apply(
@@ -55,30 +54,17 @@ export function generateDocs(unparsedApexFiles: UnparsedSourceBundle[], config:
);
const sort = apply(sortTypesAndMembers, config.sortAlphabetically);
- function filterApexSourceFiles(sourceFiles: UnparsedSourceBundle[]): UnparsedApexBundle[] {
- return sourceFiles.filter((sourceFile): sourceFile is UnparsedApexBundle => sourceFile.type === 'apex');
- }
-
- function filterCustomObjectsAndFields(
- sourceFiles: UnparsedSourceBundle[],
- ): (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle)[] {
- return sourceFiles.filter(
- (sourceFile): sourceFile is UnparsedCustomObjectBundle =>
- sourceFile.type === 'customobject' || sourceFile.type === 'customfield',
- );
- }
-
- function filterOutCustomFields(parsedFiles: ParsedFile[]): ParsedFile[] {
+ function filterOutCustomFields(parsedFiles: ParsedFile[]): ParsedFile[] {
return parsedFiles.filter(
- (parsedFile): parsedFile is ParsedFile => parsedFile.source.type !== 'customfield',
+ (parsedFile): parsedFile is ParsedFile => parsedFile.source.type !== 'customfield',
);
}
return pipe(
- generateForApex(filterApexSourceFiles(unparsedApexFiles), config),
+ generateForApex(filterApexSourceFiles(unparsedBundles), config),
TE.chain((parsedApexFiles) => {
return pipe(
- generateForObject(filterCustomObjectsAndFields(unparsedApexFiles)),
+ reflectCustomFieldsAndObjects(filterCustomObjectsAndFields(unparsedBundles)),
TE.map((parsedObjectFiles) => [...parsedApexFiles, ...parsedObjectFiles]),
);
}),
@@ -112,52 +98,6 @@ function generateForApex(apexBundles: UnparsedApexBundle[], config: MarkdownGene
);
}
-function generateForObject(objectBundles: (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle)[]) {
- 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');
- }
-
- const customObjects = objectBundles.filter(
- (object): object is UnparsedCustomObjectBundle => object.type === 'customobject',
- );
-
- const customFields = objectBundles.filter(
- (object): object is UnparsedCustomFieldBundle => object.type === 'customfield',
- );
-
- function generateForFields(
- fields: UnparsedCustomFieldBundle[],
- ): TE.TaskEither[]> {
- return pipe(fields, reflectCustomFieldSources);
- }
-
- return pipe(
- customObjects,
- reflectCustomObjectSources,
- TE.map(filterNonPublished),
- TE.map(filterNonPublic),
- TE.bindTo('objects'),
- TE.bind('fields', () => generateForFields(customFields)),
- // Locate the fields for each object by using the parentName property
- TE.map(({ objects, fields }) => {
- return objects.map((object) => {
- const objectFields = fields.filter((field) => field.type.parentName === object.type.name);
- return {
- ...object,
- type: {
- ...object.type,
- fields: objectFields,
- },
- } as ParsedFile;
- });
- }),
- );
-}
-
function transformReferenceHook(config: MarkdownGeneratorConfig) {
async function _execute(
references: Record,
diff --git a/src/core/reflection/sobject/reflect-custom-object-sources.ts b/src/core/reflection/sobject/reflect-custom-object-sources.ts
index 8ddfe459..f48ed6e9 100644
--- a/src/core/reflection/sobject/reflect-custom-object-sources.ts
+++ b/src/core/reflection/sobject/reflect-custom-object-sources.ts
@@ -9,30 +9,30 @@ import * as A from 'fp-ts/Array';
import * as E from 'fp-ts/Either';
import { CustomFieldMetadata } from './reflect-custom-field-source';
-export type ObjectMetadata = {
+export type CustomObjectMetadata = {
type_name: 'customobject';
deploymentStatus: string;
visibility: string;
label?: string | null;
name: string;
description: string | null;
- fields: ParsedFile[];
+ fields: CustomFieldMetadata[];
};
export function reflectCustomObjectSources(
- objectSources: UnparsedCustomObjectBundle[],
-): TE.TaskEither[]> {
+ objectBundles: UnparsedCustomObjectBundle[],
+): TE.TaskEither[]> {
const semiGroupReflectionError: Semigroup = {
concat: (x, y) => new ReflectionErrors([...x.errors, ...y.errors]),
};
const Ap = TE.getApplicativeTaskValidation(T.ApplyPar, semiGroupReflectionError);
- return pipe(objectSources, A.traverse(Ap)(reflectCustomObjectSource));
+ return pipe(objectBundles, A.traverse(Ap)(reflectCustomObjectSource));
}
function reflectCustomObjectSource(
objectSource: UnparsedCustomObjectBundle,
-): TE.TaskEither> {
+): TE.TaskEither> {
return pipe(
E.tryCatch(() => new XMLParser().parse(objectSource.content), E.toError),
E.flatMap(validate),
@@ -59,33 +59,33 @@ function validate(parseResult: unknown): E.Either[],
+ fields: [] as CustomFieldMetadata[],
};
- return { ...defaultValues, ...customObject } as ObjectMetadata;
+ return { ...defaultValues, ...customObject } as CustomObjectMetadata;
}
-function addName(objectMetadata: ObjectMetadata, name: string): ObjectMetadata {
+function addName(objectMetadata: CustomObjectMetadata, name: string): CustomObjectMetadata {
return {
...objectMetadata,
name,
};
}
-function addTypeName(objectMetadata: ObjectMetadata): ObjectMetadata {
+function addTypeName(objectMetadata: CustomObjectMetadata): CustomObjectMetadata {
return {
...objectMetadata,
type_name: 'customobject',
};
}
-function toParsedFile(filePath: string, typeMirror: ObjectMetadata): ParsedFile {
+function toParsedFile(filePath: string, typeMirror: CustomObjectMetadata): ParsedFile {
return {
source: {
filePath: filePath,
diff --git a/src/core/reflection/sobject/reflectCustomFieldsAndObjects.ts b/src/core/reflection/sobject/reflectCustomFieldsAndObjects.ts
new file mode 100644
index 00000000..9cfcec6c
--- /dev/null
+++ b/src/core/reflection/sobject/reflectCustomFieldsAndObjects.ts
@@ -0,0 +1,55 @@
+import { ParsedFile, UnparsedCustomFieldBundle, 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';
+
+export function reflectCustomFieldsAndObjects(
+ objectBundles: (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle)[],
+): 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');
+ }
+
+ const customObjects = objectBundles.filter(
+ (object): object is UnparsedCustomObjectBundle => object.type === 'customobject',
+ );
+
+ const customFields = objectBundles.filter(
+ (object): object is UnparsedCustomFieldBundle => object.type === 'customfield',
+ );
+
+ function generateForFields(
+ fields: UnparsedCustomFieldBundle[],
+ ): TE.TaskEither[]> {
+ return pipe(fields, reflectCustomFieldSources);
+ }
+
+ return pipe(
+ customObjects,
+ reflectCustomObjectSources,
+ TE.map(filterNonPublished),
+ TE.map(filterNonPublic),
+ TE.bindTo('objects'),
+ TE.bind('fields', () => generateForFields(customFields)),
+ // Locate the fields for each object by using the parentName property
+ TE.map(({ objects, fields }) => {
+ return objects.map((object) => {
+ const objectFields = fields.filter((field) => field.type.parentName === object.type.name);
+ return {
+ ...object,
+ type: {
+ ...object.type,
+ fields: objectFields.map((field) => field.type),
+ },
+ };
+ });
+ }),
+ );
+}
diff --git a/src/core/reflection/sort-types-and-members.ts b/src/core/reflection/sort-types-and-members.ts
index fde5f41e..df185177 100644
--- a/src/core/reflection/sort-types-and-members.ts
+++ b/src/core/reflection/sort-types-and-members.ts
@@ -1,15 +1,14 @@
import { ClassMirror, EnumMirror, InterfaceMirror, Type } from '@cparra/apex-reflection';
import { ParsedFile } from '../shared/types';
import { isApexType } from '../shared/utils';
-import { ObjectMetadata } from './sobject/reflect-custom-object-sources';
-import { CustomFieldMetadata } from './sobject/reflect-custom-field-source';
+import { CustomObjectMetadata } from './sobject/reflect-custom-object-sources';
type Named = { name: string };
export function sortTypesAndMembers(
shouldSort: boolean,
- parsedFiles: ParsedFile[],
-): ParsedFile[] {
+ parsedFiles: ParsedFile[],
+): ParsedFile[] {
return parsedFiles
.map((parsedFile) => ({
...parsedFile,
@@ -42,17 +41,13 @@ function sortTypeMember(type: Type, shouldSort: boolean): Type {
}
}
-function sortCustomObjectFields(type: ObjectMetadata, shouldSort: boolean): ObjectMetadata {
+function sortCustomObjectFields(type: CustomObjectMetadata, shouldSort: boolean): CustomObjectMetadata {
return {
...type,
- fields: sortFields(type.fields, shouldSort),
+ fields: sortNamed(shouldSort, type.fields),
};
}
-function sortFields(fields: ParsedFile[], shouldSort: boolean): ParsedFile[] {
- return fields.sort((a, b) => sortByNames(shouldSort, a.type, b.type));
-}
-
function sortEnumValues(shouldSort: boolean, enumType: EnumMirror): EnumMirror {
return {
...enumType,
diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts
index 2bc75ab1..e9ce8fd3 100644
--- a/src/core/shared/types.d.ts
+++ b/src/core/shared/types.d.ts
@@ -1,6 +1,6 @@
import { Type } from '@cparra/apex-reflection';
import { ChangeLogPageData } from '../changelog/generate-change-log';
-import { ObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources';
+import { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources';
import { CustomFieldMetadata } from '../reflection/sobject/reflect-custom-field-source';
export type Generators = 'markdown' | 'openapi' | 'changelog';
@@ -92,7 +92,7 @@ export type SourceFileMetadata = {
};
export type ParsedFile<
- T extends Type | ObjectMetadata | CustomFieldMetadata = Type | ObjectMetadata | CustomFieldMetadata,
+ T extends Type | CustomObjectMetadata | CustomFieldMetadata = Type | CustomObjectMetadata | CustomFieldMetadata,
> = {
source: SourceFileMetadata;
type: T;
diff --git a/src/core/shared/utils.ts b/src/core/shared/utils.ts
index 12982c74..3d11cebc 100644
--- a/src/core/shared/utils.ts
+++ b/src/core/shared/utils.ts
@@ -1,6 +1,6 @@
import { Skip } from './types';
import { Type } from '@cparra/apex-reflection';
-import { ObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources';
+import { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources';
import { MarkdownGeneratorConfig } from '../markdown/generate-docs';
import { CustomFieldMetadata } from '../reflection/sobject/reflect-custom-field-source';
@@ -17,15 +17,15 @@ export function isSkip(value: unknown): value is Skip {
return Object.prototype.hasOwnProperty.call(value, '_tag') && (value as Skip)._tag === 'Skip';
}
-export function isObjectType(type: Type | ObjectMetadata | CustomFieldMetadata): type is ObjectMetadata {
- return (type as ObjectMetadata).type_name === 'customobject';
+export function isObjectType(type: Type | CustomObjectMetadata | CustomFieldMetadata): type is CustomObjectMetadata {
+ return (type as CustomObjectMetadata).type_name === 'customobject';
}
-export function isApexType(type: Type | ObjectMetadata | CustomFieldMetadata): type is Type {
+export function isApexType(type: Type | CustomObjectMetadata | CustomFieldMetadata): type is Type {
return !isObjectType(type);
}
-export function getTypeGroup(type: Type | ObjectMetadata, config: MarkdownGeneratorConfig): string {
+export function getTypeGroup(type: Type | CustomObjectMetadata, config: MarkdownGeneratorConfig): string {
function getGroup(type: Type, config: MarkdownGeneratorConfig): string {
const groupAnnotation = type.docComment?.annotations.find(
(annotation) => annotation.name.toLowerCase() === 'group',
diff --git a/src/core/test-helpers/test-data-builders.ts b/src/core/test-helpers/test-data-builders.ts
new file mode 100644
index 00000000..be47b271
--- /dev/null
+++ b/src/core/test-helpers/test-data-builders.ts
@@ -0,0 +1,41 @@
+import { UnparsedCustomFieldBundle } from '../shared/types';
+
+export function customObjectGenerator(
+ config: { deploymentStatus: string; visibility: string } = { deploymentStatus: 'Deployed', visibility: 'Public' },
+) {
+ return `
+
+
+ ${config.deploymentStatus}
+ test object for testing
+ MyTestObject
+ MyFirstObjects
+ ${config.visibility}
+ `;
+}
+
+export const customField = `
+
+
+ PhotoUrl__c
+ false
+ PhotoUrl
+ false
+ false
+ Url
+ A URL that points to a photo
+ `;
+
+export function unparsedFieldBundleFromRawString(meta: {
+ rawContent?: string;
+ filePath: string;
+ parentName: string;
+}): UnparsedCustomFieldBundle {
+ return {
+ type: 'customfield',
+ name: 'TestField__c',
+ filePath: meta.filePath,
+ content: meta.rawContent ?? customField,
+ parentName: meta.parentName,
+ };
+}
diff --git a/src/util/source-bundle-utils.ts b/src/util/source-bundle-utils.ts
new file mode 100644
index 00000000..39fcb650
--- /dev/null
+++ b/src/util/source-bundle-utils.ts
@@ -0,0 +1,19 @@
+import {
+ UnparsedApexBundle,
+ UnparsedCustomFieldBundle,
+ UnparsedCustomObjectBundle,
+ UnparsedSourceBundle,
+} from '../core/shared/types';
+
+export function filterApexSourceFiles(sourceFiles: UnparsedSourceBundle[]): UnparsedApexBundle[] {
+ return sourceFiles.filter((sourceFile): sourceFile is UnparsedApexBundle => sourceFile.type === 'apex');
+}
+
+export function filterCustomObjectsAndFields(
+ sourceFiles: UnparsedSourceBundle[],
+): (UnparsedCustomObjectBundle | UnparsedCustomFieldBundle)[] {
+ return sourceFiles.filter(
+ (sourceFile): sourceFile is UnparsedCustomObjectBundle =>
+ sourceFile.type === 'customobject' || sourceFile.type === 'customfield',
+ );
+}