sf-decomposer 6.17.0 → 6.19.0
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.
- package/CHANGELOG.md +15 -0
- package/HANDBOOK.md +8 -5
- package/README.md +47 -84
- package/lib/metadata/uniqueIdElements.d.ts +9 -0
- package/lib/metadata/uniqueIdElements.js +46 -2
- package/lib/metadata/uniqueIdElements.js.map +1 -1
- package/oclif.manifest.json +1 -1
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@
|
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
7
7
|
|
|
8
|
+
## [6.19.0](https://github.com/mcarvin8/sf-decomposer/compare/v6.18.0...v6.19.0) (2026-05-05)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **metadata:** expand uniqueIdElements coverage from audit pass ([#439](https://github.com/mcarvin8/sf-decomposer/issues/439)) ([a4c6080](https://github.com/mcarvin8/sf-decomposer/commit/a4c6080fbafa0840ff9eb797351dd92ab1b0ecae))
|
|
14
|
+
|
|
15
|
+
## [6.18.0](https://github.com/mcarvin8/sf-decomposer/compare/v6.17.0...v6.18.0) (2026-05-05)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **decompose:** bump config-disassembler to 1.2.1 and use compound keys for app ([#433](https://github.com/mcarvin8/sf-decomposer/issues/433)) ([c087664](https://github.com/mcarvin8/sf-decomposer/commit/c087664a44d73116bc375333275cb6f722662013))
|
|
21
|
+
* **deps:** bump config-disassembler to 1.3.0 (sanitize + collision detection) ([#436](https://github.com/mcarvin8/sf-decomposer/issues/436)) ([d53878b](https://github.com/mcarvin8/sf-decomposer/commit/d53878b8c67b4ca42a35695b605b2c489bed5f6c))
|
|
22
|
+
|
|
8
23
|
## [6.17.0](https://github.com/mcarvin8/sf-decomposer/compare/v6.16.0...v6.17.0) (2026-05-04)
|
|
9
24
|
|
|
10
25
|
|
package/HANDBOOK.md
CHANGED
|
@@ -126,9 +126,9 @@ Each dialog still gets its own folder, but steps live as flat `*.botSteps-meta.x
|
|
|
126
126
|
>
|
|
127
127
|
> The default still applies to every other bot.
|
|
128
128
|
|
|
129
|
-
> **Agentforce vs Einstein.** Both share the `bot` suffix
|
|
130
|
-
|
|
131
|
-
> **Sibling order inside `botSteps`.** Recompose orders `<botSteps>` siblings alphabetically by
|
|
129
|
+
> **Agentforce vs Einstein.** Both share the `bot` suffix and are covered by a single override. Their structural differences (Agentforce: `botFlowInvocation`, `genAi*`; Einstein: `botNavigation`, `mlIntents`) are invisible to the recipe — `multiLevel` only targets the repeating sections that exist on each side.
|
|
130
|
+
>
|
|
131
|
+
> **Sibling order inside `botSteps`.** Recompose orders `<botSteps>` siblings alphabetically by on-disk filename, not by document position. Salesforce ignores step order at the XML level, so deploys are unaffected — but a freshly-recomposed file may show steps shuffled compared to the originally-retrieved source. The committed fixtures in this repo are baked from the recompose output for that reason; `sf decomposer verify` treats the baked output as the source of truth.
|
|
132
132
|
|
|
133
133
|
## Flexipages (Lightning App / Record / Home pages)
|
|
134
134
|
|
|
@@ -280,7 +280,7 @@ sf decomposer verify -t bot --config
|
|
|
280
280
|
You probably ran two `decompose` invocations back-to-back, one rule each. Don't. The disassembler rewrites `.multi_level.json` on every run, so each call replaces the prior one. Pass every rule for a given component in **one** override entry, in array form.
|
|
281
281
|
|
|
282
282
|
**2. "My `multiLevel` rule is correct but recompose produces a smaller file."**
|
|
283
|
-
|
|
283
|
+
On `config-disassembler` Rust ≥ 0.5.0 / Node ≥ 1.3.0 this should not happen — sibling collisions are written to per-element SHA-256 shards and surfaced as a `WARN` (see pitfall #5), not silently overwritten. If you do see a shrunken recomposed file on a current build, treat it as a regression worth capturing as a fixture.
|
|
284
284
|
|
|
285
285
|
**3. "Component-scope override fields look ignored."**
|
|
286
286
|
Component-scope wins over type-scope, but only for fields **the component override explicitly sets**. Fields it leaves out fall through to the type-scope value, then to the run-wide default. If you set `decomposedFormat: "yaml"` on a type and `strategy: "grouped-by-tag"` on the component, the component still gets `decomposedFormat: "yaml"` from the type override.
|
|
@@ -289,7 +289,10 @@ Component-scope wins over type-scope, but only for fields **the component overri
|
|
|
289
289
|
That's by design for `multiLevel` types only. Multi-level recompose has to clean up inner-level directories so the next level can merge their reassembled XML. If you want the decomposed tree preserved for inspection, copy it before running `recompose`.
|
|
290
290
|
|
|
291
291
|
**5. "Decompose succeeded but my decomposed files all have hash names."**
|
|
292
|
-
|
|
292
|
+
There are two distinct causes; run the decompose under `RUST_LOG=warn` to tell them apart:
|
|
293
|
+
|
|
294
|
+
- **No `WARN` line.** Your `unique_id_elements` (or the rule's third part) didn't resolve to a non-empty value on those items. Check the source XML for the elements you listed — names are case-sensitive and live at the immediate child level of each repeating item. The plugin only walks one level deep when picking a UID.
|
|
295
|
+
- **A `WARN` line of the form `uniqueIdElements collision: <parentTag> id "X" matched N sibling elements`.** The configured key is too narrow — multiple siblings legitimately share the same value, so the collision detector falls back to per-element SHA-256 hashes for that group rather than overwrite. Add a tiebreaker to `unique_id_elements` (e.g. a compound like `name+recordType`) and re-decompose with `prePurge: true`.
|
|
293
296
|
|
|
294
297
|
---
|
|
295
298
|
|
package/README.md
CHANGED
|
@@ -14,29 +14,17 @@ A Salesforce CLI plugin that **decomposes** large metadata XML files into smalle
|
|
|
14
14
|
<summary>Table of Contents</summary>
|
|
15
15
|
|
|
16
16
|
- [Quick Start](#quick-start)
|
|
17
|
+
- [Requirements](#requirements)
|
|
17
18
|
- [Why sf-decomposer?](#why-sf-decomposer)
|
|
18
19
|
- [Commands](#commands)
|
|
19
|
-
- [sf decomposer decompose](#sf-decomposer-decompose)
|
|
20
|
-
- [sf decomposer recompose](#sf-decomposer-recompose)
|
|
21
|
-
- [sf decomposer verify](#sf-decomposer-verify)
|
|
22
20
|
- [Manifest-scoped runs](#manifest-scoped-runs)
|
|
23
21
|
- [Decompose Strategies](#decompose-strategies)
|
|
24
|
-
- [Custom Labels](#custom-labels-decomposition)
|
|
25
|
-
- [Permission Sets (grouped-by-tag)](#additional-permission-set-decomposition)
|
|
26
|
-
- [Loyalty Program Setup](#loyalty-program-setup-decomposition)
|
|
27
22
|
- [Supported Metadata](#supported-metadata)
|
|
28
|
-
- [Exceptions](#exceptions)
|
|
29
23
|
- [Troubleshooting](#troubleshooting)
|
|
30
24
|
- [Hooks](#hooks)
|
|
31
25
|
- [Per-Type & Per-Component Overrides](#per-type--per-component-overrides)
|
|
32
|
-
- [splitTags grammar](#splittags-grammar)
|
|
33
|
-
- [multiLevel grammar](#multilevel-grammar)
|
|
34
26
|
- [Ignore Files](#ignore-files)
|
|
35
|
-
- [.forceignore](#forceignore)
|
|
36
|
-
- [.sfdecomposerignore](#sfdecomposerignore)
|
|
37
|
-
- [.gitignore](#gitignore)
|
|
38
27
|
- [Issues](#issues)
|
|
39
|
-
- [Requirements](#requirements)
|
|
40
28
|
- [Built With](#built-with)
|
|
41
29
|
- [Contributing](#contributing)
|
|
42
30
|
- [License](#license)
|
|
@@ -74,14 +62,7 @@ A Salesforce CLI plugin that **decomposes** large metadata XML files into smalle
|
|
|
74
62
|
sf project deploy start
|
|
75
63
|
```
|
|
76
64
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
sf decomposer recompose -x "manifest/package.xml"
|
|
81
|
-
sf project deploy start -x "manifest/package.xml"
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Or run the deploy command directly after configuring the [hooks](#hooks) to run the recompose automatically before deploying.
|
|
65
|
+
Pass `-x manifest/package.xml` to both commands to scope the run to the components in a deploy manifest. Configuring the [hooks](#hooks) automates this step entirely.
|
|
85
66
|
|
|
86
67
|
---
|
|
87
68
|
|
|
@@ -101,21 +82,14 @@ If other platforms or architectures require support, please open an issue in [co
|
|
|
101
82
|
|
|
102
83
|
## Why sf-decomposer?
|
|
103
84
|
|
|
104
|
-
Salesforce
|
|
105
|
-
|
|
106
|
-
### Benefits
|
|
85
|
+
Salesforce's built-in decomposition is limited. sf-decomposer gives admins and developers more control, flexibility, and better versioning.
|
|
107
86
|
|
|
108
|
-
- **Broader metadata support**
|
|
109
|
-
- **
|
|
110
|
-
- **
|
|
111
|
-
- **
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
- **Full decomposition** – Fully decompose types that Salesforce only partially supports (e.g. permission sets).
|
|
115
|
-
- **Stable ordering** – Elements are sorted consistently to reduce noisy diffs.
|
|
116
|
-
- **Multiple formats** – Output as XML, JSON, JSON5, or YAML.
|
|
117
|
-
- **CI/CD hooks** – Auto decompose after retrieve and recompose before deploy via [.sfdecomposer.config.json](#hooks).
|
|
118
|
-
- **Better reviews** – Smaller, structured files mean clearer pull requests and fewer merge conflicts.
|
|
87
|
+
- **Broader metadata support** — works with most Metadata API types, not just the subset Salesforce decomposes.
|
|
88
|
+
- **Two [strategies](#decompose-strategies)** — `unique-id` (one file per nested element) or `grouped-by-tag` (one file per tag).
|
|
89
|
+
- **Multiple formats** — XML, JSON, JSON5, or YAML.
|
|
90
|
+
- **Manifest-scoped runs** — pass `-x package.xml` to scope a run to just the components in a deploy manifest, the same way `sf project deploy start -x` does.
|
|
91
|
+
- **CI/CD hooks** — auto-decompose after retrieve and auto-recompose before deploy via [.sfdecomposer.config.json](#hooks).
|
|
92
|
+
- **Stable ordering and smaller files** — clearer pull requests, fewer merge conflicts.
|
|
119
93
|
|
|
120
94
|
---
|
|
121
95
|
|
|
@@ -238,22 +212,18 @@ sf decomposer verify -m "permissionset" -s "grouped-by-tag" -p
|
|
|
238
212
|
sf decomposer verify -x "manifest/package.xml" --config
|
|
239
213
|
```
|
|
240
214
|
|
|
241
|
-
Files
|
|
215
|
+
Files whose **only** delta is sibling or attribute ordering are reported as informational notices, not drift. Salesforce treats metadata as order-agnostic, so the deploy is safe — the notice just warns that committing the post-recompose output will show a git diff even though the metadata is functionally identical.
|
|
242
216
|
|
|
243
217
|
---
|
|
244
218
|
|
|
245
219
|
## Manifest-scoped runs
|
|
246
220
|
|
|
247
|
-
|
|
221
|
+
`-x` / `--manifest` is supported by every `sf decomposer` command and accepts the same `package.xml` you pass to `sf project deploy start -x`. Only the listed components are decomposed/recomposed; everything else is left alone.
|
|
248
222
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
-
|
|
252
|
-
-
|
|
253
|
-
- Only those files are decomposed/recomposed; everything else on disk is left untouched.
|
|
254
|
-
- Wildcards (`<members>*</members>`) expand against your local source. Folder-typed members (e.g. `MyFolder/MyReport`) are resolved by walking the folder.
|
|
255
|
-
- Types in the manifest that the plugin does not support (e.g. `CustomObject`, `ApexClass`) are skipped with a warning instead of failing the run, so a single manifest can drive both deploys and decomposer runs.
|
|
256
|
-
- If both `--metadata-type` and `--manifest` are provided, the run is scoped to the intersection (only types present in both).
|
|
223
|
+
- Wildcards (`<members>*</members>`) expand against your local source.
|
|
224
|
+
- Folder members (e.g. `MyFolder/MyReport`) resolve by walking the folder.
|
|
225
|
+
- Types the plugin does not support (e.g. `CustomObject`, `ApexClass`) are skipped with a warning, so the same manifest can drive both deploys and decomposer runs.
|
|
226
|
+
- If both `--metadata-type` and `--manifest` are supplied, the run is scoped to the intersection.
|
|
257
227
|
|
|
258
228
|
Example manifest:
|
|
259
229
|
|
|
@@ -329,9 +299,18 @@ permissionsets/
|
|
|
329
299
|
└── userPermissions.xml
|
|
330
300
|
```
|
|
331
301
|
|
|
302
|
+
### Filename safety (unique-id)
|
|
303
|
+
|
|
304
|
+
Two safety nets apply automatically to every shard filename emitted by the **unique-id** strategy. Neither requires configuration:
|
|
305
|
+
|
|
306
|
+
- **Path-segment sanitization (silent).** Characters illegal or reserved on at least one supported filesystem — path separators (`/`, `\`), Windows-reserved chars (`:`, `*`, `?`, `"`, `<`, `>`, `|`), and ASCII control bytes — are replaced with `_`; trailing `.` and spaces are stripped. Sanitized filenames are byte-stable across platforms.
|
|
307
|
+
- **Sibling-collision fallback (emits `WARN`).** When two or more siblings of the same parent tag would resolve to the same filename (the configured unique-id elements are too narrow, or sanitization folded two distinct values together), every sibling in the colliding group is written to its own per-element SHA-256 shard instead. No row is silently overwritten.
|
|
308
|
+
|
|
309
|
+
If you see a hash-named shard and want to know whether it came from a collision (vs. simply a missing UID), set `RUST_LOG=warn` and rerun — see [Rust crate logging](#xml-disassemble-output-rust-crate).
|
|
310
|
+
|
|
332
311
|
### Custom Labels Decomposition
|
|
333
312
|
|
|
334
|
-
Custom labels
|
|
313
|
+
Custom labels are always decomposed with `unique-id` (grouped-by-tag would be a no-op since every element shares the same tag). Each label is written to its own file:
|
|
335
314
|
|
|
336
315
|
```
|
|
337
316
|
labels/
|
|
@@ -373,12 +352,9 @@ permissionsets/
|
|
|
373
352
|
|
|
374
353
|
### Loyalty Program Setup Decomposition
|
|
375
354
|
|
|
376
|
-
`loyaltyProgramSetup`
|
|
377
|
-
|
|
378
|
-
- Each `<programProcesses>` element → its own file.
|
|
379
|
-
- Each `<parameters>` and `<rules>` child → its own file.
|
|
355
|
+
`loyaltyProgramSetup` is always decomposed with `unique-id`, with a built-in `multiLevel` default that splits `<programProcesses>` into per-process folders containing per-`<parameters>` / per-`<rules>` files.
|
|
380
356
|
|
|
381
|
-
>
|
|
357
|
+
> Recompose for `loyaltyProgramSetup` always removes the decomposed tree, with or without `--postpurge`. Rely on version control if you need to inspect it after a deploy.
|
|
382
358
|
|
|
383
359
|
```
|
|
384
360
|
loyaltyProgramSetups/
|
|
@@ -461,32 +437,31 @@ For example, if you attempt to decompose Custom Labels but none of your package
|
|
|
461
437
|
|
|
462
438
|
### XML disassemble output (Rust crate)
|
|
463
439
|
|
|
464
|
-
The
|
|
440
|
+
The underlying Rust crate logs through [env_logger](https://docs.rs/env_logger). Set `RUST_LOG` to opt into more verbosity:
|
|
465
441
|
|
|
466
|
-
|
|
442
|
+
| Level | What it covers |
|
|
443
|
+
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
444
|
+
| `RUST_LOG=error` | Default. Parse errors and skipped files (leaf-only XML — primitives only, nothing to decompose). |
|
|
445
|
+
| `RUST_LOG=warn` | Adds [sibling-collision fallback](#filename-safety-unique-id) signals — one line per colliding group (parent tag, collided id, sibling count). **Recommended in CI** when shipping overrides. |
|
|
467
446
|
|
|
468
|
-
Example
|
|
447
|
+
Example `WARN` (CustomApplication where four `actionOverrides` siblings shared the action name `View`):
|
|
469
448
|
|
|
470
449
|
```
|
|
471
|
-
[2026-
|
|
450
|
+
[2026-05-04T15:21:09Z WARN config_disassembler::xml::builders::build_disassembled_files]
|
|
451
|
+
uniqueIdElements collision: <actionOverrides> id "View" matched 4 sibling elements;
|
|
452
|
+
falling back to SHA-256 content hashes for the colliding group.
|
|
453
|
+
Consider adding more discriminating fields to uniqueIdElements for this metadata type.
|
|
472
454
|
```
|
|
473
455
|
|
|
474
|
-
### Files with only leaf elements
|
|
475
|
-
|
|
476
|
-
If a metadata file has only leaf elements (primitives, no nested structure), there is nothing to decompose. The Rust crate skips the file and logs an ERROR like the example above.
|
|
477
|
-
|
|
478
456
|
---
|
|
479
457
|
|
|
480
458
|
## Hooks
|
|
481
459
|
|
|
482
|
-
|
|
460
|
+
Put **.sfdecomposer.config.json** in the project root to auto-decompose after `sf project retrieve start` and auto-recompose before `sf project deploy start` / `validate`.
|
|
483
461
|
|
|
484
|
-
|
|
462
|
+
> Configure [.forceignore](#forceignore) first — the Salesforce CLI must ignore decomposed files or `sf` commands can fail.
|
|
485
463
|
|
|
486
|
-
-
|
|
487
|
-
- **Before** `sf project deploy start` / `sf project deploy validate`: recompose.
|
|
488
|
-
|
|
489
|
-
Copy and customize the [sample config](https://raw.githubusercontent.com/mcarvin8/sf-decomposer/main/examples/.sfdecomposer.config.json), or the [sample config with overrides](https://raw.githubusercontent.com/mcarvin8/sf-decomposer/main/examples/.sfdecomposer.config.overrides.json) to vary format/strategy/etc. by metadata type or by individual component.
|
|
464
|
+
Copy and customize the [sample config](https://raw.githubusercontent.com/mcarvin8/sf-decomposer/main/examples/.sfdecomposer.config.json), or the [sample with overrides](https://raw.githubusercontent.com/mcarvin8/sf-decomposer/main/examples/.sfdecomposer.config.overrides.json) to vary format/strategy per metadata type or component.
|
|
490
465
|
|
|
491
466
|
| Option | Required | Description |
|
|
492
467
|
| ---------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -584,7 +559,7 @@ For each component, each option is resolved independently in this order (highest
|
|
|
584
559
|
|
|
585
560
|
Each `<tag>` may appear at most once in a spec. The plugin validates the grammar at config-load time. Deeper checks (e.g. unknown tag names for the metadata type) are surfaced by the underlying disassembler crate at runtime.
|
|
586
561
|
|
|
587
|
-
|
|
562
|
+
**Examples:**
|
|
588
563
|
|
|
589
564
|
```json
|
|
590
565
|
"overrides": [
|
|
@@ -597,21 +572,13 @@ Each `<tag>` may appear at most once in a spec. The plugin validates the grammar
|
|
|
597
572
|
"metadataTypes": ["profile"],
|
|
598
573
|
"strategy": "grouped-by-tag",
|
|
599
574
|
"splitTags": "objectPermissions:split:object,fieldPermissions:group:field,layoutAssignments:group:layout"
|
|
600
|
-
},
|
|
601
|
-
{
|
|
602
|
-
"metadataTypes": ["flow"],
|
|
603
|
-
"strategy": "grouped-by-tag",
|
|
604
|
-
"splitTags": "actionCalls:split:name,decisions:split:name,assignments:split:name"
|
|
605
|
-
},
|
|
606
|
-
{
|
|
607
|
-
"metadataTypes": ["workflow"],
|
|
608
|
-
"strategy": "grouped-by-tag",
|
|
609
|
-
"splitTags": "rules:split:fullName,alerts:split:fullName,fieldUpdates:split:fullName,tasks:split:fullName"
|
|
610
575
|
}
|
|
611
576
|
]
|
|
612
577
|
```
|
|
613
578
|
|
|
614
|
-
> **Caveat:**
|
|
579
|
+
> **Caveat:** With `mode: split`, the chosen `<field>` must produce a unique value across every array item — otherwise two items map to the same filename. If items can share a field value, use `mode: group` instead.
|
|
580
|
+
|
|
581
|
+
See the [admin handbook](https://github.com/mcarvin8/sf-decomposer/blob/main/HANDBOOK.md) for additional `splitTags` and `multiLevel` recipes (flows, workflows, layouts, flexipages, bots).
|
|
615
582
|
|
|
616
583
|
### multiLevel grammar
|
|
617
584
|
|
|
@@ -651,13 +618,9 @@ Within one scope, the `(file_pattern, root_to_strip)` pair must be unique across
|
|
|
651
618
|
]
|
|
652
619
|
```
|
|
653
620
|
|
|
654
|
-
>
|
|
655
|
-
|
|
656
|
-
> **
|
|
657
|
-
|
|
658
|
-
> **Tip:** Use [`sf decomposer verify`](#sf-decomposer-verify) to non-destructively confirm a new override config still round-trips before committing it.
|
|
659
|
-
|
|
660
|
-
> **Tip:** See the [admin handbook](https://github.com/mcarvin8/sf-decomposer/blob/main/HANDBOOK.md) for end-to-end recipes for Bots, Flexipages, Layouts, and other deeply-nested metadata.
|
|
621
|
+
> **Built-in defaults.** `bot` and `loyaltyProgramSetup` ship with built-in `multiLevel` rules, so you do not need an override to get the canonical layout — supply your own only to replace the default. Full registry: [`src/metadata/multiLevelDefaults.ts`](https://github.com/mcarvin8/sf-decomposer/blob/main/src/metadata/multiLevelDefaults.ts).
|
|
622
|
+
>
|
|
623
|
+
> **Pass all rules at once.** Sequential single-rule decomposes rewrite `.multi_level.json` and only the last rule survives — bundle every rule for a given component into one override. Use [`sf decomposer verify`](#sf-decomposer-verify) to confirm a new config round-trips before committing it.
|
|
661
624
|
|
|
662
625
|
### Opting in from the CLI
|
|
663
626
|
|
|
@@ -62,6 +62,15 @@ declare const _default: {
|
|
|
62
62
|
reportType: {
|
|
63
63
|
uniqueIdElements: string[];
|
|
64
64
|
};
|
|
65
|
+
serviceChannel: {
|
|
66
|
+
uniqueIdElements: string[];
|
|
67
|
+
};
|
|
68
|
+
genAiPlugin: {
|
|
69
|
+
uniqueIdElements: string[];
|
|
70
|
+
};
|
|
71
|
+
app: {
|
|
72
|
+
uniqueIdElements: string[];
|
|
73
|
+
};
|
|
65
74
|
mutingpermissionset: {
|
|
66
75
|
uniqueIdElements: string[];
|
|
67
76
|
};
|
|
@@ -144,9 +144,53 @@ export default [
|
|
|
144
144
|
uniqueIdElements: ['sobjectType'],
|
|
145
145
|
},
|
|
146
146
|
reportType: {
|
|
147
|
-
// `<sections>` items use `<masterLabel>` as their natural key
|
|
147
|
+
// `<sections>` items use `<masterLabel>` as their natural key — same
|
|
148
148
|
// pattern as `globalValueSetTranslation`/`standardValueSetTranslation`.
|
|
149
|
-
|
|
149
|
+
// The singleton `<join>` element is keyed by `<relationship>` purely for
|
|
150
|
+
// readability: without it every reportType that joins a child object
|
|
151
|
+
// produces a hash-named shard.
|
|
152
|
+
uniqueIdElements: ['masterLabel', 'relationship'],
|
|
153
|
+
},
|
|
154
|
+
serviceChannel: {
|
|
155
|
+
// Each `<serviceChannelStatusFieldMappings>` row carries `<type>` (a
|
|
156
|
+
// status category like `COMPLETED` / `IN_PROGRESS`) and `<value>` (the
|
|
157
|
+
// human-readable status name). `<type>` collides massively (most rows
|
|
158
|
+
// are `COMPLETED`) and `<value>` alone is usually unique within a
|
|
159
|
+
// channel, but we observed status names repeated across distinct types
|
|
160
|
+
// in production data — the compound `type+value` is the only fully
|
|
161
|
+
// stable id, with `value` as a single-field fallback for any row that
|
|
162
|
+
// happens to lack `<type>`.
|
|
163
|
+
uniqueIdElements: ['type+value', 'value'],
|
|
164
|
+
},
|
|
165
|
+
genAiPlugin: {
|
|
166
|
+
// `<genAiFunctions>` items are keyed by `<functionName>`;
|
|
167
|
+
// `<genAiPluginInstructions>` items by `<developerName>`. Each
|
|
168
|
+
// repeating child only carries one of these fields, so first-match
|
|
169
|
+
// wins picks the right one without a compound.
|
|
170
|
+
uniqueIdElements: ['functionName', 'developerName'],
|
|
171
|
+
},
|
|
172
|
+
app: {
|
|
173
|
+
// CustomApplication's `<profileActionOverrides>` and `<actionOverrides>`
|
|
174
|
+
// have a *compound* natural unique key: any single field (e.g. just
|
|
175
|
+
// `<actionName>`) collides for hundreds of siblings sharing
|
|
176
|
+
// `<actionName>View</actionName>`, silently merging on disassembly.
|
|
177
|
+
// Compound keys (config-disassembler >= 0.4.5) join the resolved values
|
|
178
|
+
// with `__` to form a stable, readable, collision-free filename.
|
|
179
|
+
//
|
|
180
|
+
// Fallback chain, widest first:
|
|
181
|
+
// 1. profileActionOverrides with recordType
|
|
182
|
+
// 2. profileActionOverrides without recordType
|
|
183
|
+
// 3. actionOverrides with recordType (no profile)
|
|
184
|
+
// 4. actionOverrides without recordType (no profile)
|
|
185
|
+
// Items missing `pageOrSobjectType` or `formFactor` (very rare) fall
|
|
186
|
+
// through to the SHA-256 outer-element hash, which is correct since
|
|
187
|
+
// we can't safely name them without those keys.
|
|
188
|
+
uniqueIdElements: [
|
|
189
|
+
'actionName+pageOrSobjectType+formFactor+profile+recordType',
|
|
190
|
+
'actionName+pageOrSobjectType+formFactor+profile',
|
|
191
|
+
'actionName+pageOrSobjectType+formFactor+recordType',
|
|
192
|
+
'actionName+pageOrSobjectType+formFactor',
|
|
193
|
+
],
|
|
150
194
|
},
|
|
151
195
|
mutingpermissionset: {
|
|
152
196
|
uniqueIdElements: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uniqueIdElements.js","sourceRoot":"","sources":["../../src/metadata/uniqueIdElements.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"uniqueIdElements.js","sourceRoot":"","sources":["../../src/metadata/uniqueIdElements.ts"],"names":[],"mappings":"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"}
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sf-decomposer",
|
|
3
3
|
"description": "Split large Salesforce metadata files into version-control-friendly pieces and rebuild deployment-ready files.",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.19.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@oclif/core": "^4",
|
|
7
7
|
"@salesforce/core": "^8.26.3",
|
|
8
8
|
"@salesforce/sf-plugins-core": "^12.2.6",
|
|
9
9
|
"@salesforce/source-deploy-retrieve": "^12.35.0",
|
|
10
|
-
"config-disassembler": "^1.
|
|
10
|
+
"config-disassembler": "^1.3.0",
|
|
11
11
|
"fast-xml-parser": "^5.7.2",
|
|
12
12
|
"p-limit": "^7.3.0"
|
|
13
13
|
},
|
|
@@ -95,6 +95,9 @@
|
|
|
95
95
|
"test:only": "wireit",
|
|
96
96
|
"test:perf": "vitest run --config ./vitest.perf.config.ts",
|
|
97
97
|
"test:perf:gen": "node --import ts-node/esm scripts/gen-perf-fixtures.ts",
|
|
98
|
+
"audit": "node --loader ts-node/esm --no-warnings=ExperimentalWarning scripts/audit/audit.ts",
|
|
99
|
+
"audit:sweep": "node --loader ts-node/esm --no-warnings=ExperimentalWarning scripts/audit/sweep.ts",
|
|
100
|
+
"audit:roundtrip": "node --loader ts-node/esm --no-warnings=ExperimentalWarning scripts/audit/roundtrip.ts",
|
|
98
101
|
"version": "oclif readme"
|
|
99
102
|
},
|
|
100
103
|
"publishConfig": {
|