sf-git-merge-driver 1.0.0-dev-3.13704714225-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +74 -0
  3. package/bin/dev.cmd +3 -0
  4. package/bin/dev.js +7 -0
  5. package/bin/run.cmd +3 -0
  6. package/bin/run.js +7 -0
  7. package/lib/commands/git/merge/driver/install.d.ts +8 -0
  8. package/lib/commands/git/merge/driver/install.js +21 -0
  9. package/lib/commands/git/merge/driver/install.js.map +1 -0
  10. package/lib/commands/git/merge/driver/uninstall.d.ts +8 -0
  11. package/lib/commands/git/merge/driver/uninstall.js +15 -0
  12. package/lib/commands/git/merge/driver/uninstall.js.map +1 -0
  13. package/lib/constant/driverConstant.d.ts +1 -0
  14. package/lib/constant/driverConstant.js +2 -0
  15. package/lib/constant/driverConstant.js.map +1 -0
  16. package/lib/driver/MergeDriver.d.ts +3 -0
  17. package/lib/driver/MergeDriver.js +17 -0
  18. package/lib/driver/MergeDriver.js.map +1 -0
  19. package/lib/index.d.ts +2 -0
  20. package/lib/index.js +18 -0
  21. package/lib/index.js.map +1 -0
  22. package/lib/merger/JsonMerger.d.ts +14 -0
  23. package/lib/merger/JsonMerger.js +90 -0
  24. package/lib/merger/JsonMerger.js.map +1 -0
  25. package/lib/merger/XmlMerger.d.ts +3 -0
  26. package/lib/merger/XmlMerger.js +32 -0
  27. package/lib/merger/XmlMerger.js.map +1 -0
  28. package/lib/service/installService.d.ts +3 -0
  29. package/lib/service/installService.js +24 -0
  30. package/lib/service/installService.js.map +1 -0
  31. package/lib/service/uninstallService.d.ts +3 -0
  32. package/lib/service/uninstallService.js +29 -0
  33. package/lib/service/uninstallService.js.map +1 -0
  34. package/messages/install.md +13 -0
  35. package/messages/uninstall.md +13 -0
  36. package/npm-shrinkwrap.json +14069 -0
  37. package/oclif.manifest.json +145 -0
  38. package/package.json +255 -0
package/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sebastien Colladon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # Salesforce Metadata Git Merge Driver
2
+
3
+ A custom Git merge driver designed specifically for Salesforce metadata files. This tool helps resolve merge conflicts in Salesforce XML metadata files by understanding their structure and intelligently merging changes.
4
+
5
+ ## Features
6
+
7
+ - Intelligent merging of Salesforce XML metadata files
8
+ - Handles complex metadata structures like arrays with unique identifiers
9
+ - Supports both local and global installation
10
+ - Easy to use with SFDX CLI plugin commands
11
+
12
+ ## Installation
13
+
14
+ ### As SFDX Plugin
15
+
16
+ ```bash
17
+ sf plugins install sf-git-merge-driver
18
+ ```
19
+
20
+ ### Local Repository Installation
21
+
22
+ ```bash
23
+ sf git merge driver install
24
+ ```
25
+
26
+ This will:
27
+ - Configure your Git settings to use the merge driver
28
+ - Add appropriate entries to your `.gitattributes` file
29
+ - Set up the driver to handle XML files
30
+
31
+ ## Uninstallation
32
+
33
+ ```bash
34
+ sf git merge driver uninstall
35
+ ```
36
+
37
+ ## How It Works
38
+
39
+ The merge driver works by:
40
+ 1. Converting XML to JSON for easier processing
41
+ 2. Using a specialized three-way merge algorithm that understands Salesforce metadata structures
42
+ 3. Intelligently resolving conflicts based on metadata type
43
+ 4. Converting the merged result back to properly formatted XML
44
+
45
+ ## Configuration
46
+
47
+ The driver is configured to work with `.xml` files by default. The installation adds the following to your `.gitattributes` file:
48
+
49
+ ```
50
+ *.xml merge=salesforce-source
51
+ ```
52
+
53
+ ## Changelog
54
+
55
+ [changelog.md](CHANGELOG.md) is available for consultation.
56
+
57
+ ## Versioning
58
+
59
+ Versioning follows [SemVer](http://semver.org/) specification.
60
+
61
+ ## Authors
62
+
63
+ - **Kevin Gossent** - [yohanim](https://github.com/yohanim)
64
+ - **Sebastien Colladon** - [scolladon](https://github.com/scolladon)
65
+
66
+ ## Contributing
67
+
68
+ Contributions are what make the trailblazer community such an amazing place. I regard this component as a way to inspire and learn from others. Any contributions you make are **appreciated**.
69
+
70
+ See [contributing.md](CONTRIBUTING.md) for sgd contribution principles.
71
+
72
+ ## License
73
+
74
+ This project license is MIT - see the [LICENSE.md](LICENSE.md) file for details
package/bin/dev.cmd ADDED
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node --loader ts-node/esm --no-warnings=ExperimentalWarning "%~dp0\dev" %*
package/bin/dev.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env -S NODE_OPTIONS="--no-warnings=ExperimentalWarning" npx ts-node --project tsconfig.json --esm
2
+ async function main() {
3
+ const { execute } = await import('@oclif/core')
4
+ await execute({ development: true, dir: import.meta.url })
5
+ }
6
+
7
+ await main()
package/bin/run.cmd ADDED
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node "%~dp0\run" %*
package/bin/run.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ async function main() {
3
+ const { execute } = await import('@oclif/core')
4
+ await execute({ dir: import.meta.url })
5
+ }
6
+
7
+ await main()
@@ -0,0 +1,8 @@
1
+ import { SfCommand } from '@salesforce/sf-plugins-core';
2
+ export default class Install extends SfCommand<void> {
3
+ static readonly summary: string;
4
+ static readonly description: string;
5
+ static readonly examples: string[];
6
+ static readonly flags: {};
7
+ run(): Promise<void>;
8
+ }
@@ -0,0 +1,21 @@
1
+ import { Messages } from '@salesforce/core';
2
+ import { SfCommand } from '@salesforce/sf-plugins-core';
3
+ import { InstallService } from '../../../../service/installService.js';
4
+ import { UninstallService } from '../../../../service/uninstallService.js';
5
+ Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
6
+ const messages = Messages.loadMessages('sf-git-merge-driver', 'install');
7
+ export default class Install extends SfCommand {
8
+ static summary = messages.getMessage('summary');
9
+ static description = messages.getMessage('description');
10
+ static examples = messages.getMessages('examples');
11
+ static flags = {};
12
+ async run() {
13
+ try {
14
+ await new UninstallService().uninstallMergeDriver();
15
+ // biome-ignore lint/suspicious/noEmptyBlockStatements: <explanation>
16
+ }
17
+ catch { }
18
+ await new InstallService().installMergeDriver();
19
+ }
20
+ }
21
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../../../src/commands/git/merge/driver/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAA;AAE1E,QAAQ,CAAC,kCAAkC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC5D,MAAM,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAC,qBAAqB,EAAE,SAAS,CAAC,CAAA;AAExE,MAAM,CAAC,OAAO,OAAO,OAAQ,SAAQ,SAAe;IAC3C,MAAM,CAAmB,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;IACjE,MAAM,CAAmB,WAAW,GACzC,QAAQ,CAAC,UAAU,CAAC,aAAa,CAAC,CAAA;IAC7B,MAAM,CAAmB,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,UAAU,CAAC,CAAA;IAEpE,MAAM,CAAmB,KAAK,GAAG,EAAE,CAAA;IAEnC,KAAK,CAAC,GAAG;QACd,IAAI,CAAC;YACH,MAAM,IAAI,gBAAgB,EAAE,CAAC,oBAAoB,EAAE,CAAA;YACnD,qEAAqE;QACvE,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QACV,MAAM,IAAI,cAAc,EAAE,CAAC,kBAAkB,EAAE,CAAA;IACjD,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { SfCommand } from '@salesforce/sf-plugins-core';
2
+ export default class Uninstall extends SfCommand<void> {
3
+ static readonly summary: string;
4
+ static readonly description: string;
5
+ static readonly examples: string[];
6
+ static readonly flags: {};
7
+ run(): Promise<void>;
8
+ }
@@ -0,0 +1,15 @@
1
+ import { Messages } from '@salesforce/core';
2
+ import { SfCommand } from '@salesforce/sf-plugins-core';
3
+ import { UninstallService } from '../../../../service/uninstallService.js';
4
+ Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
5
+ const messages = Messages.loadMessages('sf-git-merge-driver', 'uninstall');
6
+ export default class Uninstall extends SfCommand {
7
+ static summary = messages.getMessage('summary');
8
+ static description = messages.getMessage('description');
9
+ static examples = messages.getMessages('examples');
10
+ static flags = {};
11
+ async run() {
12
+ await new UninstallService().uninstallMergeDriver();
13
+ }
14
+ }
15
+ //# sourceMappingURL=uninstall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstall.js","sourceRoot":"","sources":["../../../../../src/commands/git/merge/driver/uninstall.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAA;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAA;AAE1E,QAAQ,CAAC,kCAAkC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC5D,MAAM,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAC,qBAAqB,EAAE,WAAW,CAAC,CAAA;AAE1E,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,SAAe;IAC7C,MAAM,CAAmB,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;IACjE,MAAM,CAAmB,WAAW,GACzC,QAAQ,CAAC,UAAU,CAAC,aAAa,CAAC,CAAA;IAC7B,MAAM,CAAmB,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,UAAU,CAAC,CAAA;IAEpE,MAAM,CAAmB,KAAK,GAAG,EAAE,CAAA;IAEnC,KAAK,CAAC,GAAG;QACd,MAAM,IAAI,gBAAgB,EAAE,CAAC,oBAAoB,EAAE,CAAA;IACrD,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const DRIVER_NAME = "salesforce-source";
@@ -0,0 +1,2 @@
1
+ export const DRIVER_NAME = 'salesforce-source';
2
+ //# sourceMappingURL=driverConstant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driverConstant.js","sourceRoot":"","sources":["../../src/constant/driverConstant.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,mBAAmB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export declare class MergeDriver {
2
+ mergeFiles(ancestorFile: any, ourFile: any, theirFile: any, outputFile: any): Promise<void>;
3
+ }
@@ -0,0 +1,17 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { XmlMerger } from '../merger/XmlMerger.js';
3
+ export class MergeDriver {
4
+ async mergeFiles(ancestorFile, ourFile, theirFile, outputFile) {
5
+ // Read all three versions
6
+ const [ancestorContent, ourContent, theirContent] = await Promise.all([
7
+ readFile(ancestorFile, 'utf8'),
8
+ readFile(ourFile, 'utf8'),
9
+ readFile(theirFile, 'utf8'),
10
+ ]);
11
+ const xmlMerger = new XmlMerger();
12
+ const mergedContent = await xmlMerger.tripartXmlMerge(ancestorContent, ourContent, theirContent);
13
+ // Write the merged content to the output file
14
+ await writeFile(outputFile, mergedContent);
15
+ }
16
+ }
17
+ //# sourceMappingURL=MergeDriver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MergeDriver.js","sourceRoot":"","sources":["../../src/driver/MergeDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAElD,MAAM,OAAO,WAAW;IACtB,KAAK,CAAC,UAAU,CAAC,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU;QAC3D,0BAA0B;QAC1B,MAAM,CAAC,eAAe,EAAE,UAAU,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACpE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;YAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;YACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;SAC5B,CAAC,CAAA;QAEF,MAAM,SAAS,GAAG,IAAI,SAAS,EAAE,CAAA;QAEjC,MAAM,aAAa,GAAG,MAAM,SAAS,CAAC,eAAe,CACnD,eAAe,EACf,UAAU,EACV,YAAY,CACb,CAAA;QAED,8CAA8C;QAC9C,MAAM,SAAS,CAAC,UAAU,EAAE,aAAa,CAAC,CAAA;IAC5C,CAAC;CACF"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env -S NODE_OPTIONS="--no-warnings=ExperimentalWarning" npx ts-node --project tsconfig.json --esm
2
+ export {};
package/lib/index.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env -S NODE_OPTIONS="--no-warnings=ExperimentalWarning" npx ts-node --project tsconfig.json --esm
2
+ import { MergeDriver } from './driver/MergeDriver.js';
3
+ if (process.argv.length >= 6) {
4
+ const [, , ancestorFile, ourFile, theirFile, outputFile] = process.argv;
5
+ const mergeDriver = new MergeDriver();
6
+ mergeDriver
7
+ .mergeFiles(ancestorFile, ourFile, theirFile, outputFile)
8
+ .then(() => process.exit(0));
9
+ }
10
+ else {
11
+ console.error('Usage: sf-git-merge-driver %O %A %B %P');
12
+ console.error(' %O: ancestor file');
13
+ console.error(' %A: our file');
14
+ console.error(' %B: their file');
15
+ console.error(' %P: output file path');
16
+ process.exit(1);
17
+ }
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAErD,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,AAAD,EAAG,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAA;IACvE,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAA;IACrC,WAAW;SACR,UAAU,CAAC,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC;SACxD,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;AAChC,CAAC;KAAM,CAAC;IACN,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAA;IACvD,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAA;IACpC,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAA;IAC/B,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAA;IACjC,OAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAA;IACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC"}
@@ -0,0 +1,14 @@
1
+ type JsonValue = string | number | boolean | null | JsonObject | JsonArray;
2
+ interface JsonObject {
3
+ [key: string]: JsonValue;
4
+ }
5
+ interface JsonArray extends Array<JsonValue> {
6
+ }
7
+ export declare class JsonMerger {
8
+ private readonly idFields;
9
+ mergeObjects(ancestor: JsonValue | undefined, ours: JsonValue, theirs: JsonValue): JsonValue;
10
+ private mergeArrays;
11
+ private hasIdField;
12
+ private createIdMap;
13
+ }
14
+ export {};
@@ -0,0 +1,90 @@
1
+ export class JsonMerger {
2
+ idFields = [
3
+ 'fullName',
4
+ 'name',
5
+ 'field',
6
+ 'label',
7
+ 'id',
8
+ '@_name',
9
+ ];
10
+ mergeObjects(ancestor, ours, theirs) {
11
+ // Handle null/undefined cases
12
+ if (ours === null || theirs === null)
13
+ return ours ?? theirs;
14
+ if (typeof ours !== typeof theirs)
15
+ return ours;
16
+ // Handle arrays (special case for Salesforce metadata)
17
+ if (Array.isArray(ours) && Array.isArray(theirs)) {
18
+ return this.mergeArrays(Array.isArray(ancestor) ? ancestor : undefined, ours, theirs);
19
+ }
20
+ // Handle objects
21
+ if (typeof ours === 'object' && typeof theirs === 'object') {
22
+ const result = { ...ours };
23
+ const ancestorObj = (ancestor ?? {});
24
+ const theirsObj = theirs;
25
+ // Process all keys from both objects
26
+ const allKeys = new Set([...Object.keys(ours), ...Object.keys(theirs)]);
27
+ for (const key of allKeys) {
28
+ if (!(key in theirsObj))
29
+ continue; // Keep our version
30
+ if (!(key in result)) {
31
+ result[key] = theirsObj[key]; // Take their version
32
+ continue;
33
+ }
34
+ // Recursively merge when both have the key
35
+ result[key] = this.mergeObjects(ancestorObj[key], result[key], theirsObj[key]);
36
+ }
37
+ return result;
38
+ }
39
+ // For primitive values, use their changes if we didn't modify from ancestor
40
+ if (theirs !== ancestor && ours === ancestor)
41
+ return theirs;
42
+ return ours; // Default to our version
43
+ }
44
+ mergeArrays(ancestor, ours, theirs) {
45
+ // Find the first matching ID field that exists in both arrays
46
+ const idField = this.idFields.find(field => ours.some(item => this.hasIdField(item, field)) &&
47
+ theirs.some(item => this.hasIdField(item, field)));
48
+ if (!idField)
49
+ return ours; // No common identifier, keep our version
50
+ // Create lookup maps
51
+ const ourMap = this.createIdMap(ours, idField);
52
+ const theirMap = this.createIdMap(theirs, idField);
53
+ const ancestorMap = ancestor
54
+ ? this.createIdMap(ancestor, idField)
55
+ : new Map();
56
+ const result = [...ours];
57
+ const processed = new Set();
58
+ // Process all items from both arrays
59
+ for (const [id, ourItem] of ourMap) {
60
+ const theirItem = theirMap.get(id);
61
+ if (theirItem) {
62
+ // Item exists in both versions, merge them
63
+ const index = result.findIndex(item => this.hasIdField(item, idField) && item[idField] === id);
64
+ if (index !== -1) {
65
+ result[index] = this.mergeObjects(ancestorMap.get(id), ourItem, theirItem);
66
+ }
67
+ }
68
+ processed.add(id);
69
+ }
70
+ // Add items that only exist in their version
71
+ for (const [id, theirItem] of theirMap) {
72
+ if (!processed.has(id)) {
73
+ result.push(theirItem);
74
+ }
75
+ }
76
+ return result;
77
+ }
78
+ hasIdField(item, field) {
79
+ return (item !== null &&
80
+ typeof item === 'object' &&
81
+ !Array.isArray(item) &&
82
+ field in item);
83
+ }
84
+ createIdMap(arr, idField) {
85
+ return new Map(arr
86
+ .filter(item => this.hasIdField(item, idField))
87
+ .map(item => [String(item[idField]), item]));
88
+ }
89
+ }
90
+ //# sourceMappingURL=JsonMerger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JsonMerger.js","sourceRoot":"","sources":["../../src/merger/JsonMerger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare class XmlMerger {
2
+ tripartXmlMerge(ancestorContent: string, ourContent: string, theirContent: string): Promise<any>;
3
+ }
@@ -0,0 +1,32 @@
1
+ import { XMLBuilder, XMLParser } from 'fast-xml-parser';
2
+ import { JsonMerger } from './JsonMerger.js';
3
+ const options = {
4
+ attributeNamePrefix: '@_',
5
+ commentPropName: '#comment',
6
+ format: true,
7
+ ignoreAttributes: false,
8
+ ignoreNameSpace: false,
9
+ indentBy: ' ',
10
+ parseAttributeValue: false,
11
+ parseNodeValue: false,
12
+ parseTagValue: false,
13
+ processEntities: false,
14
+ suppressEmptyNode: false,
15
+ trimValues: true,
16
+ };
17
+ export class XmlMerger {
18
+ async tripartXmlMerge(ancestorContent, ourContent, theirContent) {
19
+ const parser = new XMLParser(options);
20
+ const ancestorObj = parser.parse(ancestorContent);
21
+ const ourObj = parser.parse(ourContent);
22
+ const theirObj = parser.parse(theirContent);
23
+ // Perform deep merge of XML objects
24
+ const jsonMerger = new JsonMerger();
25
+ const mergedObj = jsonMerger.mergeObjects(ancestorObj, ourObj, theirObj);
26
+ // Convert back to XML and format
27
+ const builder = new XMLBuilder(options);
28
+ const mergedXml = builder.build(mergedObj);
29
+ return mergedXml;
30
+ }
31
+ }
32
+ //# sourceMappingURL=XmlMerger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"XmlMerger.js","sourceRoot":"","sources":["../../src/merger/XmlMerger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5C,MAAM,OAAO,GAAG;IACd,mBAAmB,EAAE,IAAI;IACzB,eAAe,EAAE,UAAU;IAC3B,MAAM,EAAE,IAAI;IACZ,gBAAgB,EAAE,KAAK;IACvB,eAAe,EAAE,KAAK;IACtB,QAAQ,EAAE,MAAM;IAChB,mBAAmB,EAAE,KAAK;IAC1B,cAAc,EAAE,KAAK;IACrB,aAAa,EAAE,KAAK;IACpB,eAAe,EAAE,KAAK;IACtB,iBAAiB,EAAE,KAAK;IACxB,UAAU,EAAE,IAAI;CACjB,CAAA;AAED,MAAM,OAAO,SAAS;IACpB,KAAK,CAAC,eAAe,CACnB,eAAuB,EACvB,UAAkB,EAClB,YAAoB;QAEpB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,OAAO,CAAC,CAAA;QAErC,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;QACjD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;QACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;QAE3C,oCAAoC;QAEpC,MAAM,UAAU,GAAG,IAAI,UAAU,EAAE,CAAA;QACnC,MAAM,SAAS,GAAG,UAAU,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAA;QAExE,iCAAiC;QACjC,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,CAAA;QACvC,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC1C,OAAO,SAAS,CAAA;IAClB,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ export declare class InstallService {
2
+ installMergeDriver(): Promise<void>;
3
+ }
@@ -0,0 +1,24 @@
1
+ import { appendFile, chmod, copyFile, mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { simpleGit } from 'simple-git';
5
+ import { DRIVER_NAME } from '../constant/driverConstant.js';
6
+ const currentDir = fileURLToPath(new URL('.', import.meta.url));
7
+ const libIndexPath = join(currentDir, '../../lib/index.js');
8
+ const binaryPath = 'node_modules/.bin';
9
+ const localBinPath = `${binaryPath}/sf-git-merge-driver`;
10
+ export class InstallService {
11
+ async installMergeDriver() {
12
+ await mkdir(binaryPath, { recursive: true });
13
+ await copyFile(libIndexPath, localBinPath);
14
+ await chmod(localBinPath, 0o755);
15
+ const git = simpleGit();
16
+ await git.addConfig(`merge.${DRIVER_NAME}.name`, 'Salesforce source merge driver');
17
+ await git.addConfig(`merge.${DRIVER_NAME}.driver`, `${localBinPath} %O %A %B %P`);
18
+ await git.addConfig(`merge.${DRIVER_NAME}.recursive`, 'true');
19
+ const content = ['*.xml'].map(pattern => `${pattern} merge=${DRIVER_NAME}`).join('\n') +
20
+ '\n';
21
+ await appendFile('.gitattributes', content, { flag: 'a' });
22
+ }
23
+ }
24
+ //# sourceMappingURL=installService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installService.js","sourceRoot":"","sources":["../../src/service/installService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACrE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAE3D,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAC/D,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAA;AAC3D,MAAM,UAAU,GAAG,mBAAmB,CAAA;AACtC,MAAM,YAAY,GAAG,GAAG,UAAU,sBAAsB,CAAA;AAExD,MAAM,OAAO,cAAc;IAClB,KAAK,CAAC,kBAAkB;QAC7B,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5C,MAAM,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC,CAAA;QAC1C,MAAM,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,CAAA;QAEhC,MAAM,GAAG,GAAG,SAAS,EAAE,CAAA;QACvB,MAAM,GAAG,CAAC,SAAS,CACjB,SAAS,WAAW,OAAO,EAC3B,gCAAgC,CACjC,CAAA;QACD,MAAM,GAAG,CAAC,SAAS,CACjB,SAAS,WAAW,SAAS,EAC7B,GAAG,YAAY,cAAc,CAC9B,CAAA;QACD,MAAM,GAAG,CAAC,SAAS,CAAC,SAAS,WAAW,YAAY,EAAE,MAAM,CAAC,CAAA;QAE7D,MAAM,OAAO,GACX,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,OAAO,UAAU,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YACtE,IAAI,CAAA;QAEN,MAAM,UAAU,CAAC,gBAAgB,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAA;IAC5D,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ export declare class UninstallService {
2
+ uninstallMergeDriver(): Promise<void>;
3
+ }
@@ -0,0 +1,29 @@
1
+ import { readFile, unlink, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { simpleGit } from 'simple-git';
4
+ import { DRIVER_NAME } from '../constant/driverConstant.js';
5
+ const MERGE_DRIVER_CONFIG = new RegExp(`.* merge\\s*=\\s*${DRIVER_NAME}$`);
6
+ const localBinPath = join(process.cwd(), 'node_modules/.bin/sf-git-merge-driver');
7
+ export class UninstallService {
8
+ async uninstallMergeDriver() {
9
+ try {
10
+ await unlink(localBinPath);
11
+ // biome-ignore lint/suspicious/noEmptyBlockStatements: <explanation>
12
+ }
13
+ catch { }
14
+ const git = simpleGit();
15
+ try {
16
+ await git.raw(['config', '--remove-section', `merge.${DRIVER_NAME}`]);
17
+ // biome-ignore lint/suspicious/noEmptyBlockStatements: <explanation>
18
+ }
19
+ catch { }
20
+ const gitAttributes = await readFile('.gitattributes', {
21
+ encoding: 'utf8',
22
+ });
23
+ const filteredAttributes = gitAttributes
24
+ .split('\n')
25
+ .filter(line => !MERGE_DRIVER_CONFIG.test(line));
26
+ await writeFile('.gitattributes', filteredAttributes.join('\n'));
27
+ }
28
+ }
29
+ //# sourceMappingURL=uninstallService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstallService.js","sourceRoot":"","sources":["../../src/service/uninstallService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAE3D,MAAM,mBAAmB,GAAG,IAAI,MAAM,CAAC,oBAAoB,WAAW,GAAG,CAAC,CAAA;AAC1E,MAAM,YAAY,GAAG,IAAI,CACvB,OAAO,CAAC,GAAG,EAAE,EACb,uCAAuC,CACxC,CAAA;AAED,MAAM,OAAO,gBAAgB;IACpB,KAAK,CAAC,oBAAoB;QAC/B,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,YAAY,CAAC,CAAA;YAC1B,qEAAqE;QACvE,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QAEV,MAAM,GAAG,GAAG,SAAS,EAAE,CAAA;QACvB,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,kBAAkB,EAAE,SAAS,WAAW,EAAE,CAAC,CAAC,CAAA;YACrE,qEAAqE;QACvE,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QAEV,MAAM,aAAa,GAAG,MAAM,QAAQ,CAAC,gBAAgB,EAAE;YACrD,QAAQ,EAAE,MAAM;SACjB,CAAC,CAAA;QACF,MAAM,kBAAkB,GAAG,aAAa;aACrC,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;QAClD,MAAM,SAAS,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IAClE,CAAC;CACF"}
@@ -0,0 +1,13 @@
1
+ # summary
2
+
3
+ Installs a local git merge driver for the given org and branch.
4
+
5
+ # description
6
+
7
+ Installs a local git merge driver for the given org and branch, by updating the `.gitattributes` files in the project and creating a new merge driver configuration file in the `.git/config` of the project.
8
+
9
+ # examples
10
+
11
+ - Install the driver for a given project:
12
+
13
+ <%= config.bin %> <%= command.id %>
@@ -0,0 +1,13 @@
1
+ # summary
2
+
3
+ Uninstalls the local git merge driver for the given org and branch.
4
+
5
+ # description
6
+
7
+ Uninstalls the local git merge driver for the given org and branch, by removing the merge driver content in the `.gitattributes` files in the project and deleting the merge driver configuration file in the `.git/config` of the project.
8
+
9
+ # examples
10
+
11
+ - Uninstall the driver for a given project:
12
+
13
+ <%= config.bin %> <%= command.id %>