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', + ); +}