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
+
+ 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
+
+
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+
+ PC-{0000}
+
+ 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
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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
+
+ true
+ false
+ Picklist
+
+ true
+
+ false
+
+ List Price
+ false
+
+
+
+ Surcharge
+ false
+
+
+
+ Discount
+ false
+
+
+
+
+
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
+
+
+ PPC-{0000}
+
+ 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__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__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 that is sold or available for sale.
+
+
+ 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
+
+ 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__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
+
+ 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
+
+ Represents a line item on a sales order.
+
+ SOL-{0000}
+
+ 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
+
+ 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__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__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
+
+ 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
+
+ true
+ false
+ Picklist
+
+ true
+
+ false
+
+ Charge
+ false
+
+
+
+ Discount
+ false
+
+
+
+
+
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
+
+ Custom object for tracking sales orders.
+
+ SO-{0000}
+
+ 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
+
+ Represents a speaker at an event.
+
+ SPEAK-{0000}
+
+ 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
+
+ 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__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
+
+ 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
+
+
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+
+ PC-{0000}
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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
+
+ true
+ false
+ Picklist
+
+ true
+
+ false
+
+ List Price
+ false
+
+
+
+ Surcharge
+ false
+
+
+
+ Discount
+ false
+
+
+
+
+
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
+
+
+ PPC-{0000}
+
+ 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__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__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 that is sold or available for sale.
+
+
+ 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__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
+
+ 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/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 f08d0499..faf3fd59 100644
--- a/src/core/markdown/__test__/generating-custom-object-docs.spec.ts
+++ b/src/core/markdown/__test__/generating-custom-object-docs.spec.ts
@@ -1,12 +1,7 @@
import { extendExpect } from './expect-extensions';
-import {
- customField,
- customObjectGenerator,
- generateDocs,
- unparsedFieldBundleFromRawString,
- unparsedObjectBundleFromRawString,
-} from './test-helpers';
+import { 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(() => {
@@ -54,7 +49,6 @@ describe('Generates Custom Object documentation', () => {
});
const customFieldBundle = unparsedFieldBundleFromRawString({
- rawContent: customField,
filePath: 'src/object/TestField__c.field-meta.xml',
parentName: 'TestObject__c',
});
@@ -82,7 +76,6 @@ describe('Generates Custom Object documentation', () => {
});
const customFieldBundle = unparsedFieldBundleFromRawString({
- rawContent: customField,
filePath: 'src/object/TestField__c.field-meta.xml',
parentName: 'TestObject__c',
});
@@ -99,7 +92,6 @@ describe('Generates Custom Object documentation', () => {
});
const customFieldBundle = unparsedFieldBundleFromRawString({
- rawContent: customField,
filePath: 'src/object/TestField__c.field-meta.xml',
parentName: 'TestObject__c',
});
@@ -116,7 +108,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 d884958f..eb7e8fb3 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',
@@ -58,29 +39,3 @@ export function generateDocs(apexBundles: UnparsedSourceBundle[], config?: Parti
...config,
});
}
-
-export function customObjectGenerator(
- config: { deploymentStatus: string; visibility: string } = { deploymentStatus: 'Deployed', visibility: 'Public' },
-) {
- return `
-
-
- ${config.deploymentStatus}
- test object for testing
-
- MyFirstObjects
- ${config.visibility}
- `;
-}
-
-export const customField = `
-
-
- PhotoUrl__c
- false
-
- false
- false
- Url
- A URL that points to a photo
-`;
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 286a09b9..94e94b93 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
+
+ MyFirstObjects
+ ${config.visibility}
+ `;
+}
+
+export const customField = `
+
+
+ PhotoUrl__c
+ false
+
+ 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',
+ );
+}