config-disassembler 2.1.0 → 2.2.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.
Files changed (4) hide show
  1. package/CHANGELOG.md +7 -761
  2. package/README.md +70 -316
  3. package/index.js +52 -52
  4. package/package.json +11 -15
package/README.md CHANGED
@@ -1,81 +1,14 @@
1
- # config-disassembler-node
1
+ # config-disassembler
2
2
 
3
3
  [![NPM](https://img.shields.io/npm/v/config-disassembler.svg?label=config-disassembler)](https://www.npmjs.com/package/config-disassembler)
4
4
  [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://raw.githubusercontent.com/mcarvin8/config-disassembler-node/main/LICENSE.md)
5
5
  [![Downloads/week](https://img.shields.io/npm/dw/config-disassembler.svg)](https://npmjs.org/package/config-disassembler)
6
6
 
7
- Node.js bindings for the Rust [`config-disassembler`](https://crates.io/crates/config-disassembler) crate.
7
+ Node.js bindings for the Rust [`config-disassembler`](https://crates.io/crates/config-disassembler) crate via napi-rs.
8
8
 
9
- Disassemble configuration files (XML, JSON, JSON5, JSONC, YAML, TOON, TOML, INI) into smaller, version-control–friendly pieces — and reassemble them on demand.
9
+ Use it to disassemble large configuration files into smaller, version-control–friendly files and reassemble them later.
10
10
 
11
- > **Native Rust:** All work is done in [`config-disassembler`](https://crates.io/crates/config-disassembler); this package provides Node.js bindings via [napi-rs](https://napi.rs).
12
-
13
- ---
14
-
15
- ## Table of contents
16
-
17
- - [Quick start](#quick-start)
18
- - [Install](#install)
19
- - [Supported formats](#supported-formats)
20
- - [XML API](#xml-api)
21
- - [Disassembling XML](#disassembling-xml)
22
- - [Disassembly strategies](#disassembly-strategies)
23
- - [Split tags (splitTags)](#split-tags-splittags)
24
- - [Multi-level disassembly](#multi-level-disassembly)
25
- - [Reassembling XML](#reassembling-xml)
26
- - [Value-format API (JSON / JSON5 / JSONC / YAML / TOON / TOML / INI)](#value-format-api)
27
- - [Disassembling config files](#disassembling-config-files)
28
- - [Reassembling config files](#reassembling-config-files)
29
- - [TOML and INI isolation](#toml-and-ini-isolation)
30
- - [Ignore file](#ignore-file)
31
- - [Logging](#logging)
32
- - [Implementation](#implementation)
33
- - [Use case](#use-case)
34
- - [License](#license)
35
-
36
- ---
37
-
38
- ## Quick start
39
-
40
- ```typescript
41
- import {
42
- DisassembleXMLFileHandler,
43
- ReassembleXMLFileHandler,
44
- DisassembleConfigFileHandler,
45
- ReassembleConfigFileHandler,
46
- } from "config-disassembler";
47
-
48
- // XML: disassemble one XML -> many small files
49
- const disassembleXml = new DisassembleXMLFileHandler();
50
- disassembleXml.disassemble({
51
- filePath: "path/to/YourFile.permissionset-meta.xml",
52
- uniqueIdElements:
53
- "application,apexClass,name,flow,object,recordType,tab,field",
54
- format: "json",
55
- strategy: "unique-id",
56
- });
57
-
58
- const reassembleXml = new ReassembleXMLFileHandler();
59
- reassembleXml.reassemble({
60
- filePath: "path/to/YourFile",
61
- fileExtension: "permissionset-meta.xml",
62
- });
63
-
64
- // JSON / YAML / TOML / INI / etc.
65
- const disassembleCfg = new DisassembleConfigFileHandler();
66
- const outDir = disassembleCfg.disassemble({
67
- input: "config.json",
68
- outputFormat: "yaml",
69
- uniqueId: "id",
70
- });
71
-
72
- const reassembleCfg = new ReassembleConfigFileHandler();
73
- reassembleCfg.reassemble({
74
- inputDir: outDir,
75
- output: "config.rebuilt.json",
76
- outputFormat: "json",
77
- });
78
- ```
11
+ The Node bindings closely mirror the Rust crate APIs and behavior. For complete documentation and behavior details, see the Rust crate [documentation](https://github.com/mcarvin8/config-disassembler).
79
12
 
80
13
  ---
81
14
 
@@ -87,309 +20,130 @@ npm install config-disassembler
87
20
 
88
21
  ---
89
22
 
90
- ## Supported formats
91
-
92
- | Format | Disassemble | Reassemble | Cross-format conversions |
93
- | ------ | ----------- | ---------- | --------------------------------------------------------------------------------- |
94
- | XML | yes | yes | XML ↔ XML / JSON / JSON5 / YAML |
95
- | JSON | yes | yes | within JSON / JSON5 / JSONC / YAML / TOON |
96
- | JSON5 | yes | yes | within JSON / JSON5 / JSONC / YAML / TOON |
97
- | JSONC | yes | yes | within JSON / JSON5 / JSONC / YAML / TOON (comments preserved on JSONC <-> JSONC) |
98
- | YAML | yes | yes | within JSON / JSON5 / JSONC / YAML / TOON |
99
- | TOON | yes | yes | within JSON / JSON5 / JSONC / YAML / TOON |
100
- | TOML | yes | yes | TOML <-> TOML only |
101
- | INI | yes | yes | INI <-> INI only |
102
-
103
- See [TOML and INI isolation](#toml-and-ini-isolation) for the rationale on the same-format-only restriction.
104
-
105
- ---
23
+ ## XML API Examples
106
24
 
107
- ## XML API
25
+ > Use this to disassemble a large XML into smaller files (XML, JSON, JSON5, YAML) and reassemble the XML.
108
26
 
109
- Mirrors the standalone [`xml-disassembler`](https://github.com/mcarvin8/xml-disassembler-rust) crate (now hosted as the `xml` subcommand of `config-disassembler`). The on-disk layout, defaults, and option semantics are identical.
110
-
111
- ### Disassembling XML
27
+ ```ts
28
+ import {
29
+ DisassembleXMLFileHandler,
30
+ ReassembleXMLFileHandler,
31
+ } from "config-disassembler";
112
32
 
113
- ```typescript
114
- import { DisassembleXMLFileHandler } from "config-disassembler";
33
+ const disassemble = new DisassembleXMLFileHandler();
115
34
 
116
- const handler = new DisassembleXMLFileHandler();
117
- handler.disassemble({
118
- filePath: "test/baselines/general",
35
+ /* Disassemble XML Options
36
+ * filePath = required. Path to the XML file or directory to disassemble.
37
+ * strategy = optional (default: unique-id). Disassembly strategy (unique-id or grouped-by-tag).
38
+ * uniqueIdElements = optional for unique-id strategy. Comma-separated element names used to derive filenames for nested elements.
39
+ * multiLevel = optional for unique-id strategy. One or more multi-level specs: file_pattern:root_to_strip:unique_id_elements. Pass a string (single rule) or a string[] for several rules; semicolon-separated strings are also accepted.
40
+ * splitTags = optional for grouped-by-tag strategy. split or group nested tags.
41
+ * prePurge = optional (default: false). Delete any pre-existing disassembled files before disassembly.
42
+ * postPurge = optional (default: false). Delete the XML file after successfully disassembling it.
43
+ * format = optional (default: xml). Output format: xml, json, json5, yaml.
44
+ * ignorePath = optional (default: .cdignore). Path to a .gitignore-like file to skip files while disassembling.
45
+ */
46
+
47
+ // Disassemble using unique-ID strategy
48
+ disassemble.disassemble({
49
+ filePath: "My.permissionset-meta.xml",
119
50
  uniqueIdElements:
120
- "application,apexClass,name,externalDataSource,flow,object,apexPage,recordType,tab,field",
51
+ "application,apexClass,name,flow,object,recordType,tab,field",
52
+ strategy: "unique-id",
53
+ format: "json",
121
54
  prePurge: true,
122
55
  postPurge: true,
123
56
  ignorePath: ".cdignore",
124
- format: "json",
125
- strategy: "unique-id",
126
57
  });
127
- ```
128
-
129
- | Option | Description |
130
- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
131
- | `filePath` | Path to the XML file or directory to disassemble. |
132
- | `uniqueIdElements` | Comma-separated element names used to derive filenames for nested elements. |
133
- | `multiLevel` | Optional. One or more multi-level specs: `file_pattern:root_to_strip:unique_id_elements`. Pass a `string` (single rule) or a `string[]` for several rules; semicolon-separated strings are also accepted. See [Multi-level disassembly](#multi-level-disassembly). |
134
- | `splitTags` | Optional. With `strategy: "grouped-by-tag"`: split or group nested tags. See [Split tags](#split-tags-splittags). |
135
- | `prePurge` | Remove existing disassembly output before running (default: `false`). |
136
- | `postPurge` | Remove the source XML after disassembly (default: `false`). |
137
- | `ignorePath` | Path to the ignore file (default: `.cdignore`). |
138
- | `format` | Output format: `xml`, `json`, `json5`, `yaml`. |
139
- | `strategy` | `unique-id` or `grouped-by-tag`. |
140
-
141
- ### Disassembly strategies
142
-
143
- #### `unique-id` (default)
144
-
145
- Each nested element is written to its own file, named by a unique identifier (or an 8-character SHA-256 hash if no UID is available). Leaf content stays in a file named after the original XML.
146
-
147
- **Compound keys (`+`)** – Each comma-separated *UID candidate* in the list may be a `+`-joined compound (e.g. `actionName+pageOrSobjectType+formFactor+profile`). A compound matches only when every sub-field is present and non-empty at the same level, in which case the resolved values are joined with `__` to form the filename. Useful for metadata whose natural unique key is multi-field, like Salesforce `<profileActionOverrides>` (`actionName + pageOrSobjectType + formFactor + profile [+ recordType]`); without compounds, every sibling sharing an `actionName` would collapse to one filename. List both wide and narrow forms (e.g. `A+B+C+D, A+B+C, A`) for graceful fallback when items only carry some keys.
148
58
 
149
- **Filename safety** – Resolved unique-id values are sanitized before being used as a path segment: path separators (`/`, `\`), Windows-reserved chars (`:`, `*`, `?`, `"`, `<`, `>`, `|`), and ASCII control bytes are each replaced with `_`; trailing dots and spaces are stripped. So a Salesforce `EntitlementProcess` milestone named `TrustFile Transaction Sync/Import Complete` produces the shard `TrustFile Transaction Sync_Import Complete.milestones-meta.xml` on every platform instead of the `/` being interpreted as a directory separator. After sanitization, any remaining sibling collisions (because `uniqueIdElements` is too narrow, or because sanitization folded distinct values into the same form) are detected automatically: every sibling in the colliding group falls back to a per-element 8-character SHA-256 hash so no row is silently overwritten on disk. Both behaviors require no configuration.
150
-
151
- Best for fine-grained diffs and version control.
152
-
153
- #### `grouped-by-tag`
154
-
155
- All nested elements with the same tag go into one file per tag. Leaf content stays in the base file named after the original XML.
156
-
157
- Best for fewer files and quick inspection.
158
-
159
- ```typescript
160
- handler.disassemble({
161
- filePath: "my.xml",
59
+ // Or, disassemble using grouped-by-tag strategy
60
+ disassemble.disassemble({
61
+ filePath: "My.permissionset-meta.xml",
162
62
  strategy: "grouped-by-tag",
163
- format: "yaml",
63
+ format: "json",
164
64
  });
165
- ```
166
-
167
- ### Split tags (`splitTags`)
168
65
 
169
- With `strategy: "grouped-by-tag"`, you can optionally split or group specific nested tags into subdirectories instead of a single file per tag. Useful for permission sets and similar metadata: e.g. one file per `objectPermissions` under `objectPermissions/`, and `fieldPermissions` grouped by object under `fieldPermissions/`.
170
-
171
- **Spec:** Comma-separated rules. Each rule is `tag:mode:field` or `tag:path:mode:field` (path defaults to tag). `mode` is `split` (one file per array item, filename from `field`) or `group` (group array items by `field`, one file per group).
172
-
173
- ```typescript
174
- handler.disassemble({
175
- filePath: "fixtures/split-tags/HR_Admin.permissionset-meta.xml",
66
+ // Or, disassemble using grouped-by-tag strategy with split-tags
67
+ disassemble.disassemble({
68
+ filePath: "My.permissionset-meta.xml",
176
69
  strategy: "grouped-by-tag",
177
70
  splitTags: "objectPermissions:split:object,fieldPermissions:group:field",
178
- format: "xml",
179
71
  });
180
- ```
181
-
182
- ### Multi-level disassembly
183
-
184
- For XML with nested repeatable blocks (e.g. `programProcesses` inside `LoyaltyProgramSetup`), you can disassemble in one call and reassemble in one call. Pass a **multi-level spec** so the tool further splits matching files and later merges them in the right order.
185
-
186
- **Spec format:** `file_pattern:root_to_strip:unique_id_elements`
187
-
188
- ```typescript
189
- import {
190
- DisassembleXMLFileHandler,
191
- ReassembleXMLFileHandler,
192
- } from "config-disassembler";
193
72
 
194
- const disassemble = new DisassembleXMLFileHandler();
73
+ // Or, disassemble an XML over multiple-levels with unique-id strategy
195
74
  disassemble.disassemble({
196
75
  filePath: "Cloud_Kicks_Inner_Circle.loyaltyProgramSetup-meta.xml",
76
+ strategy: "unique-id",
197
77
  uniqueIdElements: "fullName,name,processName",
198
78
  multiLevel: "programProcesses:programProcesses:parameterName,ruleName",
199
79
  postPurge: true,
200
80
  });
201
81
 
202
82
  const reassemble = new ReassembleXMLFileHandler();
203
- reassemble.reassemble({
204
- filePath: "Cloud_Kicks_Inner_Circle",
205
- fileExtension: "loyaltyProgramSetup-meta.xml",
206
- postPurge: true,
207
- });
208
- ```
209
-
210
- A `.multi_level.json` config is written in the disassembly root so reassembly knows how to merge inner levels first, then the top level. No extra options are needed for reassembly.
211
-
212
- **Caveat:** Multi-level reassembly removes disassembled directories after reassembling each level, even when you do not pass `postPurge`. This is required so the next level can merge the reassembled XML files.
213
-
214
- #### Multiple multi-level rules
215
-
216
- If a single XML file has more than one deeply-nested repeatable block, pass several specs in one `disassemble` call. Each rule is applied independently and persisted to `.multi_level.json` so reassembly replays them all in order. Use a `string[]` (preferred for clarity) or a single `;`-separated string:
217
-
218
- ```typescript
219
- disassemble.disassemble({
220
- filePath: "MyType.bigFile-meta.xml",
221
- uniqueIdElements: "fullName,name,id",
222
- multiLevel: ["sectionA:sectionA:id", "sectionB:sectionB:name"],
223
- postPurge: true,
224
- });
225
83
 
226
- disassemble.disassemble({
227
- filePath: "MyType.bigFile-meta.xml",
228
- uniqueIdElements: "fullName,name,id",
229
- multiLevel: "sectionA:sectionA:id;sectionB:sectionB:name",
230
- postPurge: true,
231
- });
232
- ```
233
-
234
- > Sequential `disassemble` calls (one per rule, with `postPurge: false` to preserve prior output) are **not** equivalent — each call rewrites `.multi_level.json` and reorganises the on-disk wrappers, so only the last rule survives. Always pass every rule for a given file in a single call.
84
+ /* Reassemble XML Options
85
+ * filePath = required. Folder containing disassembled files to reassemble into 1 XML.
86
+ * fileExtension = optional (default: `.xml`). Set explicit file extension.
87
+ * postPurge = optional (default: false). Delete disassembled files after reassembly.
88
+ */
235
89
 
236
- ### Reassembling XML
237
-
238
- ```typescript
239
- import { ReassembleXMLFileHandler } from "config-disassembler";
240
-
241
- const handler = new ReassembleXMLFileHandler();
242
- handler.reassemble({
243
- filePath: "test/baselines/general/HR_Admin",
90
+ reassemble.reassemble({
91
+ filePath: "My",
244
92
  fileExtension: "permissionset-meta.xml",
245
93
  postPurge: true,
246
94
  });
247
95
  ```
248
96
 
249
- | Option | Description |
250
- | --------------- | --------------------------------------------------------------------------------- |
251
- | `filePath` | Directory that contains the disassembled files (e.g. `HR_Admin/`). |
252
- | `fileExtension` | Suffix for the rebuilt XML file (e.g. `permissionset-meta.xml`). Default: `.xml`. |
253
- | `postPurge` | Remove disassembled files after a successful reassembly (default: `false`). |
254
-
255
97
  ---
256
98
 
257
- ## Value-format API
99
+ ## Value-Format API Examples
258
100
 
259
- Use these classes for JSON, JSON5, JSONC, YAML, TOON, TOML, and INI files. Both methods are synchronous and return the relevant output path.
101
+ > Use this for JSON, JSON5, JSONC, YAML, TOML, TOON, and INI configs.
260
102
 
261
- ### Disassembling config files
262
-
263
- ```typescript
264
- import { DisassembleConfigFileHandler } from "config-disassembler";
265
-
266
- const handler = new DisassembleConfigFileHandler();
103
+ ```ts
104
+ import {
105
+ DisassembleConfigFileHandler,
106
+ ReassembleConfigFileHandler,
107
+ } from "config-disassembler";
267
108
 
268
- // File input: writes split files into ./config/ next to config.json
269
- const outDir = handler.disassemble({
270
- input: "config.json",
271
- });
109
+ // Disassemble config
110
+ const disassemble = new DisassembleConfigFileHandler();
272
111
 
273
- // Cross-format: split JSON into per-key YAML files
274
- handler.disassemble({
112
+ const outputDir = disassemble.disassemble({
275
113
  input: "config.json",
276
114
  outputFormat: "yaml",
277
- outputDir: "config-split",
278
- });
279
-
280
- // Array root: name files by a field on each element
281
- handler.disassemble({
282
- input: "users.json",
283
115
  uniqueId: "id",
284
116
  });
285
117
 
286
- // Directory input: walks the directory and disassembles every matching
287
- // file in place, using `.cdignore` (or `ignorePath`) to filter the walk.
288
- handler.disassemble({
289
- input: "envs/",
290
- inputFormat: "yaml",
291
- });
292
- ```
293
-
294
- | Option | Description |
295
- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
296
- | `input` | Path to a file or directory to disassemble. |
297
- | `inputFormat` | Override input format. Defaults to detecting from the file extension (or each file's extension when `input` is a directory). |
298
- | `outputFormat` | Format used for the split files. Defaults to `inputFormat`. Restricted to the input's compatible family. |
299
- | `outputDir` | Directory to write split files to (single-file input only). Defaults to `<stem>` next to the input. Rejected for directory inputs. |
300
- | `uniqueId` | For array roots, name files by this field on each element. Falls back to a zero-padded index when missing. |
301
- | `prePurge` | Remove the output directory before writing (default: `false`). |
302
- | `postPurge` | Delete the input file (or input directory, when empty after disassembly) after a successful run (default: `false`). |
303
- | `ignorePath` | `.gitignore`-style ignore file for directory walks. Defaults to `.cdignore` in the input directory. |
304
-
305
- The handler returns the path of the directory containing the split files (single-file input) or the input directory itself (directory input).
306
-
307
- A `.config-disassembler.json` sidecar is written into the output directory recording the original key order, root type, source format, and the format the split files were written in. Reassembly uses this metadata to rebuild the original document deterministically.
308
-
309
- ### Reassembling config files
310
-
311
- ```typescript
312
- import { ReassembleConfigFileHandler } from "config-disassembler";
313
-
314
- const handler = new ReassembleConfigFileHandler();
118
+ // Reassemble config
119
+ const reassemble = new ReassembleConfigFileHandler();
315
120
 
316
- // Default: rebuild using the original source format from the sidecar
317
- handler.reassemble({
318
- inputDir: "config",
319
- });
320
-
321
- // Cross-format: rebuild a YAML-split tree as JSON
322
- handler.reassemble({
323
- inputDir: "config",
121
+ reassemble.reassemble({
122
+ inputDir: outputDir,
324
123
  output: "config.rebuilt.json",
325
124
  outputFormat: "json",
326
125
  });
327
126
  ```
328
127
 
329
- | Option | Description |
330
- | -------------- | --------------------------------------------------------------------------------------------------------------------- |
331
- | `inputDir` | Directory containing the split files and `.config-disassembler.json` sidecar. |
332
- | `output` | Output file path. Defaults to the original source filename recorded in the metadata, or `<dirname>.<ext>` next to it. |
333
- | `outputFormat` | Format to write the rebuilt file in. Defaults to the source format recorded in the metadata. |
334
- | `postPurge` | Remove the disassembled directory after a successful reassembly (default: `false`). |
335
-
336
- ### TOML and INI isolation
337
-
338
- TOML and INI can only be disassembled and reassembled within the same format. TOML cannot represent `null`, forbids array roots, and forces bare keys to precede tables (which would reorder values on round-trip through other formats). INI is even narrower: section values are strings (or valueless keys) and arrays / nested objects cannot be represented without inventing a custom encoding.
339
-
340
- Trying to mix formats with TOML or INI throws a clear error:
341
-
342
- ```
343
- TOML can only be converted to and from TOML; got input=json, output=toml
344
- INI can only be converted to and from INI; got input=json, output=ini
345
- ```
346
-
347
- To keep every split file valid for table-style formats, TOML and INI wrap each per-key split file under its parent key (e.g. `dependencies.toml` contains `[dependencies]` headers, `settings.ini` contains `[settings]`). Reassembly unwraps them automatically using the metadata sidecar.
348
-
349
128
  ---
350
129
 
351
- ## Ignore file
352
-
353
- Exclude files or directories from disassembly using an ignore file (default: `.cdignore` in the input directory). Syntax is the same as [`.gitignore`](https://git-scm.com/docs/gitignore).
354
-
355
- ```
356
- **/secret.json
357
- **/generated/
358
- ```
359
-
360
- For backward compatibility, the XML API still falls back to `.xmldisassemblerignore` if `.cdignore` is missing — rename the file or pass `ignorePath` explicitly to silence the deprecation warning.
361
-
362
- ---
363
-
364
- ## Logging
365
-
366
- The Rust crate uses [env_logger](https://docs.rs/env_logger). Set `RUST_LOG` to control verbosity (e.g. `RUST_LOG=debug`).
367
-
368
- ---
369
-
370
- ## Implementation
371
-
372
- The core logic is implemented in Rust ([config-disassembler](https://crates.io/crates/config-disassembler)) and exposed to Node.js via [napi-rs](https://napi.rs). Building from source requires Rust and Node.js.
373
-
374
- Prebuilt native binaries are published as platform-scoped optional dependencies (e.g. `config-disassembler-darwin-arm64`, `config-disassembler-linux-x64-gnu`); npm/yarn/pnpm will install only the one matching the consumer's `os`/`cpu`/`libc`.
375
-
376
- This package ships with native binaries for these platforms and architectures:
130
+ ## Supported Platforms
377
131
 
378
- | Platform | Architectures |
379
- | ----------- | ---------------------------------------------------------- |
380
- | **macOS** | x64 (Intel), arm64 (Apple Silicon) |
381
- | **Linux** | x64 (gnu + musl), arm64 (gnu + musl) |
382
- | **Windows** | x64, arm64, ia32 |
132
+ This package ships with prebuilt native binaries as platform-specific optional npm packages — your package manager installs only the one matching your `os` / `cpu` / `libc`:
383
133
 
384
- The Linux musl builds (`config-disassembler-linux-x64-musl`, `config-disassembler-linux-arm64-musl`) cover Alpine-based images and other musl libc distributions commonly used for slim CI containers.
134
+ | Platform | Architectures |
135
+ | ----------- | ------------------------------------ |
136
+ | **macOS** | x64 (Intel), arm64 (Apple Silicon) |
137
+ | **Linux** | x64 (gnu + musl), arm64 (gnu + musl) |
138
+ | **Windows** | x64, arm64, ia32 |
385
139
 
386
- Adding a new target is as simple as appending its Rust triple to the `napi.targets` array in `package.json` and adding the matching entry to the build matrix in `.github/workflows/release.yml`. If other platforms or architectures require support, please open an [issue](https://github.com/mcarvin8/config-disassembler-node/issues).
140
+ If your platform or architecture isn't listed, please open an [issue](https://github.com/mcarvin8/config-disassembler-node/issues).
387
141
 
388
142
  ---
389
143
 
390
- ## Use case
144
+ ## Use Case
391
145
 
392
- For a Salesforce CLI integration example using the XML API, see [sf-decomposer](https://github.com/mcarvin8/sf-decomposer).
146
+ For a use-case using the XML API, see [sf-decomposer](https://github.com/mcarvin8/sf-decomposer).
393
147
 
394
148
  ---
395
149