sf-plugin-cms 0.3.1 → 0.5.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 (79) hide show
  1. package/README.md +334 -28
  2. package/lib/commands/cms/export/workspace.d.ts +6 -1
  3. package/lib/commands/cms/export/workspace.js +80 -3
  4. package/lib/commands/cms/export/workspace.js.map +1 -1
  5. package/lib/commands/cms/import/workspace.d.ts +14 -4
  6. package/lib/commands/cms/import/workspace.js +199 -24
  7. package/lib/commands/cms/import/workspace.js.map +1 -1
  8. package/lib/commands/cms/info.js +21 -2
  9. package/lib/commands/cms/info.js.map +1 -1
  10. package/lib/contracts/info.d.ts +7 -4
  11. package/lib/contracts/info.js +17 -8
  12. package/lib/contracts/info.js.map +1 -1
  13. package/lib/contracts/shared.d.ts +2 -1
  14. package/lib/contracts/shared.js +11 -3
  15. package/lib/contracts/shared.js.map +1 -1
  16. package/lib/contracts/workspace-export.d.ts +23 -3
  17. package/lib/contracts/workspace-export.js +118 -5
  18. package/lib/contracts/workspace-export.js.map +1 -1
  19. package/lib/contracts/workspace-import.d.ts +64 -0
  20. package/lib/contracts/workspace-import.js +119 -0
  21. package/lib/contracts/workspace-import.js.map +1 -1
  22. package/lib/index.d.ts +3 -1
  23. package/lib/index.js +2 -0
  24. package/lib/index.js.map +1 -1
  25. package/lib/services/bulk-export-workspaces.js +10 -4
  26. package/lib/services/bulk-export-workspaces.js.map +1 -1
  27. package/lib/services/editable-raw-html-export.d.ts +26 -0
  28. package/lib/services/editable-raw-html-export.js +100 -0
  29. package/lib/services/editable-raw-html-export.js.map +1 -0
  30. package/lib/services/editable-raw-html-import.d.ts +25 -0
  31. package/lib/services/editable-raw-html-import.js +107 -0
  32. package/lib/services/editable-raw-html-import.js.map +1 -0
  33. package/lib/services/editable-raw-html.d.ts +34 -0
  34. package/lib/services/editable-raw-html.js +83 -0
  35. package/lib/services/editable-raw-html.js.map +1 -0
  36. package/lib/services/email-fragment.d.ts +15 -0
  37. package/lib/services/email-fragment.js +199 -0
  38. package/lib/services/email-fragment.js.map +1 -0
  39. package/lib/services/export-references.js +3 -1
  40. package/lib/services/export-references.js.map +1 -1
  41. package/lib/services/export-workspace.d.ts +16 -1
  42. package/lib/services/export-workspace.js +222 -15
  43. package/lib/services/export-workspace.js.map +1 -1
  44. package/lib/services/image-import.d.ts +91 -0
  45. package/lib/services/image-import.js +579 -0
  46. package/lib/services/image-import.js.map +1 -0
  47. package/lib/services/import-identities.d.ts +33 -0
  48. package/lib/services/import-identities.js +287 -0
  49. package/lib/services/import-identities.js.map +1 -0
  50. package/lib/services/import-workspace.d.ts +50 -1
  51. package/lib/services/import-workspace.js +445 -21
  52. package/lib/services/import-workspace.js.map +1 -1
  53. package/lib/services/landing-page-template.d.ts +52 -0
  54. package/lib/services/landing-page-template.js +421 -0
  55. package/lib/services/landing-page-template.js.map +1 -0
  56. package/lib/services/landing-page.d.ts +43 -0
  57. package/lib/services/landing-page.js +117 -0
  58. package/lib/services/landing-page.js.map +1 -0
  59. package/lib/services/resolve-workspace.d.ts +6 -1
  60. package/lib/services/resolve-workspace.js +51 -4
  61. package/lib/services/resolve-workspace.js.map +1 -1
  62. package/lib/services/variant-identity.d.ts +13 -0
  63. package/lib/services/variant-identity.js +34 -0
  64. package/lib/services/variant-identity.js.map +1 -0
  65. package/lib/services/web-fragment.d.ts +41 -0
  66. package/lib/services/web-fragment.js +245 -0
  67. package/lib/services/web-fragment.js.map +1 -0
  68. package/lib/transport/experimental-media.d.ts +30 -0
  69. package/lib/transport/experimental-media.js +224 -0
  70. package/lib/transport/experimental-media.js.map +1 -0
  71. package/lib/transport/json-request.d.ts +9 -1
  72. package/lib/transport/json-request.js +30 -3
  73. package/lib/transport/json-request.js.map +1 -1
  74. package/lib/transport/multipart-image-create.d.ts +28 -0
  75. package/lib/transport/multipart-image-create.js +189 -0
  76. package/lib/transport/multipart-image-create.js.map +1 -0
  77. package/messages/cms.export.workspace.md +12 -2
  78. package/messages/cms.import.workspace.md +29 -3
  79. package/package.json +1 -1
package/README.md CHANGED
@@ -58,17 +58,17 @@ Machine consumers should run this command first and select a mutually supported
58
58
 
59
59
  ## Command reference
60
60
 
61
- | Command | Details |
62
- |---|---|
63
- | `sf cms info` | [Plugin information](#sf-cms-info) |
64
- | `sf cms list workspace` | [List workspaces](#sf-cms-list-workspace) |
65
- | `sf cms get workspace` | [Get a workspace](#sf-cms-get-workspace) |
66
- | `sf cms list channel` | [List workspace channels](#sf-cms-list-channel) |
67
- | `sf cms get channel` | [Get a channel](#sf-cms-get-channel) |
68
- | `sf cms get content` | [Get content](#sf-cms-get-content) |
69
- | `sf cms get variant` | [Get a variant](#sf-cms-get-variant) |
70
- | `sf cms export workspace` | [Export a workspace](#sf-cms-export-workspace) |
71
- | `sf cms import workspace` | [Import a workspace](#sf-cms-import-workspace) |
61
+ | Command | Details |
62
+ | ------------------------- | ----------------------------------------------- |
63
+ | `sf cms info` | [Plugin information](#sf-cms-info) |
64
+ | `sf cms list workspace` | [List workspaces](#sf-cms-list-workspace) |
65
+ | `sf cms get workspace` | [Get a workspace](#sf-cms-get-workspace) |
66
+ | `sf cms list channel` | [List workspace channels](#sf-cms-list-channel) |
67
+ | `sf cms get channel` | [Get a channel](#sf-cms-get-channel) |
68
+ | `sf cms get content` | [Get content](#sf-cms-get-content) |
69
+ | `sf cms get variant` | [Get a variant](#sf-cms-get-variant) |
70
+ | `sf cms export workspace` | [Export a workspace](#sf-cms-export-workspace) |
71
+ | `sf cms import workspace` | [Import a workspace](#sf-cms-import-workspace) |
72
72
 
73
73
  All org-backed commands require `--target-org <username-or-alias>` (short form `-o`). They use API version `67.0` by default; pass `--api-version <version>` only when you need to override it. Add `--json` for machine-readable Salesforce CLI output. Without `--json`, list commands print compact tables and get commands print formatted JSON records.
74
74
 
@@ -81,7 +81,7 @@ sf cms info --json
81
81
  sf cms info --contract-version 1 --json
82
82
  ```
83
83
 
84
- The result advertises command-result versions separately from compatible workspace-package manifest majors. It currently reports API `67.0` as both the default and sole tested version. Bulk export is implemented; external-reference correlation and import mapping remain experimental because only evidenced CMS reference kinds can be resolved. Installation alone does not prove org permissions or endpoint availability.
84
+ The result advertises command-result versions separately from compatible workspace-package manifest majors. It currently reports API `67.0` as both the default and sole tested version. Bulk export is implemented without media binaries; single-workspace media export is experimental and requires the explicit `--experimental-media` opt-in. The strict v2 image-create profile and the bounded v1 component-create profiles remain experimental. External-reference correlation and import mapping remain experimental because only evidenced CMS reference kinds and explicitly mapped prerequisites can be resolved. Installation alone does not prove org permissions or endpoint availability.
85
85
 
86
86
  `--contract-version <major>` defaults to `1`. Any unsupported major returns a `blocked` `sf-cms-info` envelope with diagnostic code `UNSUPPORTED_CONTRACT_VERSION` and exit `1`, without org access.
87
87
 
@@ -180,17 +180,18 @@ Runs an experimental, read-only, best-effort export of the variants currently ob
180
180
  ```sh
181
181
  sf cms export workspace --target-org my-org --workspace-name "Main Site"
182
182
  sf cms export workspace --target-org my-org --workspace-id 0Zu... --output-dir ./custom-export --json
183
+ sf cms export workspace --target-org my-org --workspace-id 0Zu... --output-dir ./custom-export --experimental-media --json
183
184
  sf cms export workspace --target-org my-org --all --workspace-type Marketing --output-dir ./cms --contract-version 1 --json
184
185
  ```
185
186
 
186
187
  Choose either single or bulk mode:
187
188
 
188
- - Single mode requires exactly one of `--workspace-id <id>` or `--workspace-name <name>`. Name matching is exact but case-insensitive. Case-only or Unicode-normalization-equivalent duplicates are ambiguous. The canonical fetched workspace name is always used for the default folder, preserving its casing.
189
- - Bulk mode uses `--all`, which is mutually exclusive with both single selectors. Optional `--workspace-type Marketing|Content` is valid only with `--all`; input casing is ignored and output is normalized to `Marketing` or `Content`.
189
+ - Single mode requires exactly one of `--workspace-id <id>` or `--workspace-name <name>`. Name matching is exact but case-insensitive. Case-only or Unicode-normalization-equivalent duplicates are ambiguous. The canonical fetched workspace name is always used for the default folder, preserving its casing. Image candidates are JSON-only and force an honest partial result by default. `--experimental-media` explicitly opts into the undocumented binary download transport and strict v2 media manifest; only then may Salesforce authorization follow the transport's narrowly validated redirect policy.
190
+ - Bulk mode uses `--all`, which is mutually exclusive with both single selectors. Optional `--workspace-type Marketing|Content` is valid only with `--all`; input casing is ignored and output is normalized to `Marketing` or `Content`. `--experimental-media` is incompatible with `--all`; bulk export never enables media binaries.
190
191
 
191
192
  In single mode, `--output-dir <path>` remains the exact new destination. When omitted, export writes to `./cms/<safe-canonical-workspace-name>`. In bulk mode, `--output-dir` is the parent directory and defaults to `./cms`; each workspace is written beneath it using the canonical fetched name. Safe segments preserve Unicode and case, replace path/control/Windows-invalid characters, and trim unsafe trailing dots or spaces.
192
193
 
193
- Bulk mode performs a strict global preflight before the first workspace export: it enumerates until an empty page, canonicalizes every workspace by ID, validates IDs, names, and actual types, applies the optional type filter, sorts by canonical ID, checks the output parent and every destination, and rejects collisions after sanitization, Unicode normalization, and case folding. Any preflight failure creates no destinations and makes no export calls.
194
+ Bulk mode performs a strict global preflight before the first workspace export: it enumerates until an empty page, canonicalizes every workspace by ID, validates IDs and names, resolves recognized canonical `spaceType` values, applies the optional type filter, sorts by canonical ID, checks the output parent and every destination, and rejects collisions after sanitization, Unicode normalization, and case folding. If a canonical detail response omits `spaceType`, preflight can use the list response's recognized type only for the same exact workspace ID. A present malformed, null, or unsupported canonical type is rejected, while an explicit contradictory canonical `Content` type is filtered out. Any preflight failure creates no destinations and makes no export calls.
194
195
 
195
196
  After preflight, each workspace export remains atomic. Execution continues after individual failures. Manifest warnings count as successful exports. JSON and human modes both return the complete deterministic aggregate with discovered, selected, succeeded, and failed counts plus per-workspace status, manifest, or redacted single-line error. A partial execution sets a nonzero process exit code only after the aggregate is emitted.
196
197
 
@@ -210,7 +211,7 @@ cms/
210
211
 
211
212
  ### `sf cms import workspace`
212
213
 
213
- Plans or applies a create-only import from an export package. Dry-run is the default: the command validates the complete local package before authenticating, resolves the destination org and workspace, requires an exact workspace ID plus nonempty `defaultLanguage` and `rootFolderId`, and checks every planned content key for conflicts without sending mutations.
214
+ Plans or applies a create-only import from an export package. Dry-run is the default: the command validates the complete local package before authenticating, resolves the destination org and workspace, and requires an exact workspace ID plus nonempty `defaultLanguage` and `rootFolderId`. The default profile checks every planned source content key for conflicts without sending mutations. The opt-in native-copy profile below instead requests server-generated keys.
214
215
 
215
216
  Safe dry run:
216
217
 
@@ -237,6 +238,308 @@ Safety flags:
237
238
  - `--report-dir <path>`: required with `--apply`; the destination must not exist. There is no overwrite mode.
238
239
  - `--allow-partial`: accept a package whose manifest records omissions or incomplete coverage. Without this flag, partial exports are rejected.
239
240
 
241
+ A successful default-profile dry-run is a read-only proposal, not a deploy-ready result. It preserves named content and checks destination content-key absence, but reports `SERVER_CONFLICT_CHECK_UNVERIFIED` and `APPLY_READINESS_UNVERIFIED` warnings. Named proposals additionally report `NAME_AVAILABILITY_UNVERIFIED`: destination API-name/URL-name availability is not established, and default-profile named apply remains blocked. Package-wide reference and media checks still apply to default-profile dry-runs; no target mappings are claimed as resolved.
242
+
243
+ #### Native raw-HTML copies (`--native-copy-map`)
244
+
245
+ `--native-copy-map <json-file>` opts into a bounded create-only profile for selected raw-HTML `sfdc_cms__email` and `sfdc_cms__emailTemplate` content. It is not general workspace restoration. Phase 1 users deliberately select content they believe is reference-free; a successful dry-run does not prove that embedded HTML has no dependencies. The file must contain a nonempty JSON array; each row has exactly these four string fields:
246
+
247
+ ```json
248
+ [
249
+ {
250
+ "sourceContentKey": "SOURCE_CONTENT_KEY",
251
+ "language": "en-US",
252
+ "apiName": "CopiedEmail",
253
+ "urlName": "copied-email"
254
+ }
255
+ ]
256
+ ```
257
+
258
+ Replace the example source key and language with an exact pair from the integrity-verified package. Each row selects exactly one existing variant, with only one language per distinct parent; selection may be a subset of the package, but the entire package must pass integrity validation. The selected language must equal the destination workspace's default language. `sourceContentKey`, `language`, and `apiName` use letters, digits, underscores, or hyphens without whitespace; `urlName` uses only lowercase letters, digits, or hyphens. API and URL names must be fresh relative to all source items and the other rows in this run.
259
+
260
+ Native selection excludes a `cms.relationship` descriptor from reference preflight only when it exactly matches exporter evidence and is unambiguously owned by an unselected variant. References owned by selected variants, or with ambiguous or unproven ownership, remain subject to blocking preflight checks. Full-package integrity validation still includes unselected items, and the original manifest, source hash, integrity counts, and complete reference reporting are retained; excluding a descriptor from preflight does not resolve it. Default-profile imports retain package-wide reference preflight.
261
+
262
+ Partial source packages still require explicit `--allow-partial`, including for native selection. The read-only selection diagnostic returned contract status `partial`, exit code `2`, an empty `errors` array, and a non-null import result while retaining the original unsupported references and readiness warnings. This demonstrates that the selected proposal passed planning/preflight, not apply authorization, apply readiness, or full-package restore proof.
263
+
264
+ Save the array outside the source package, for example as `./native-copy-map.json`. Preview first, then explicitly apply with a new report directory:
265
+
266
+ ```sh
267
+ sf cms import workspace --target-org my-org --workspace-name "Destination" --source-dir ./cms/Source --native-copy-map ./native-copy-map.json --contract-version 1 --json
268
+ sf cms import workspace --target-org my-org --workspace-name "Destination" --source-dir ./cms/Source --native-copy-map ./native-copy-map.json --apply --report-dir ./cms-native-copy-report --contract-version 1 --json
269
+ ```
270
+
271
+ The native profile omits `contentKey` from CREATE and uses the returned server-generated identity; source keys are not retained as target keys. It submits the selected fresh API/URL names and remaps the email body's `sfdc_cms:urlName`, without modifying source files or existing records. Destination API-name/URL-name availability is not prevalidated: an API-name collision can reject CREATE, while duplicate URLs can create distinct objects. A dry-run neither allocates target identities nor proves that a later apply will succeed.
272
+
273
+ The supported body requires nonempty `sfdc_cms:title`, `subjectLine`, `messagePurpose`, and `rawHtml`. Optional supported strings are `sfdc_cms:description`, `preheader`, `textContent`, and `backgroundColor`, plus the email body's URL name. Only empty provider/expression/attachment/variant arrays and the exact observed default background/brand settings are accepted. Unknown fields, non-null external-provider metadata, unsafe non-HTML metadata strings, and structured/package media or reference forms remain rejected. Temporarily through Phase 7, the retained raw-HTML danger scanner is bypassed and nonempty `rawHtml` is accepted as opaque literal content: embedded dependencies, media, references, dynamic syntax, and URLs are not discovered, resolved, rewritten, sanitized, or rejected. This is not dependency or safety validation; enabling the retained type-aware scanner is deferred to Phase 8. Encoded native GET HTML is decoded once for CREATE, while literal edited sidecar HTML is not decoded again. Successful apply reads the created content back and verifies generated identities, destination, language, type, names, Draft/unpublished state, and submitted body fields.
274
+
275
+ Historical native-copy evidence includes local compiled public-command execution with real flag parsing and org connections for owned email/template probes, including a separate cross-org native CREATE acceptance with independent readbacks. That evidence concerns the unedited native-copy path, not live installed editable HTML transfer. Separate packed CLI tests do not establish live installed-host acceptance against Salesforce. No general workspace migration, custom-key CREATE, general reference/media transport, non-default-language copying, or every allowed-field live profile is proven. The MCNext orchestrator is not wired to this native-copy flag; its integration remains a separate slice.
276
+
277
+ #### CMS images (`--image-map`)
278
+
279
+ Image transfer is an experimental single-workspace workflow built on strict workspace package v2. Export must use `--experimental-media`, which downloads the selected image binaries and records an exact bijection between each image variant, its media descriptor, and its packaged file. Import requires `--contract-version 2` and a nonempty `--image-map` JSON array. One row imports one image; multiple rows import many images sequentially in their deterministic typed-source order.
280
+
281
+ ```json
282
+ [
283
+ {
284
+ "source": {
285
+ "family": "cms",
286
+ "type": "image",
287
+ "apiName": "SourceLogo"
288
+ },
289
+ "contentKey": { "strategy": "preserve" },
290
+ "apiName": { "strategy": "fresh", "value": "TargetLogo" },
291
+ "title": { "strategy": "fresh", "value": "Target Logo" },
292
+ "urlName": { "strategy": "generated" }
293
+ }
294
+ ]
295
+ ```
296
+
297
+ ```sh
298
+ sf cms export workspace --target-org source-org --workspace-id 0ZuSOURCE --output-dir ./cms-images --experimental-media --json
299
+ sf cms import workspace --target-org destination-org --workspace-id 0ZuTARGET --source-dir ./cms-images --image-map ./image-map.json --contract-version 2 --json
300
+ sf cms import workspace --target-org destination-org --workspace-id 0ZuTARGET --source-dir ./cms-images --image-map ./image-map.json --contract-version 2 --apply --report-dir ./cms-image-report --json
301
+ ```
302
+
303
+ Every row selects exactly one integrity-verified `cms/image` item by exact API name, with optional source bindings such as workspace, language, server ID, title, or version. Each of the four identity fields has an explicit strategy: `preserve` submits the evidenced source value, `fresh` submits a different map-provided value, and `generated` omits the value for the server to assign. `title` supports only `preserve` or `fresh`. Content keys supplied through `preserve` or `fresh` must use the canonical `MC` plus 26 base32-character shape. Duplicate source selections and duplicate non-generated target identity tuples are rejected.
304
+
305
+ Dry-run validates the complete v2 package, verifies media bytes and hashes, resolves the exact destination workspace, and checks every selected destination identity before reporting the plan. Apply repeats destination checks immediately before each sequential multipart CREATE, records a pending operation before transport, and verifies returned identity, workspace, type, Draft state, authoring metadata, and canonical variant readback. Binary byte proof is reported separately because the authoring readback may not expose original bytes. Multipart filenames are normalized to the declared GIF, JPEG, PNG, or WebP MIME type; missing extensions are added and mismatches are rejected.
306
+
307
+ Image import is create-only. It has no update fallback, overwrite, publication, rollback, cleanup, or automatic retry. A transport or report-persistence ambiguity leaves ownership uncertain and must be reconciled from `workspace-import-run.json` before any new attempt.
308
+
309
+ #### Bounded email-fragment copies (`--email-fragment-map`)
310
+
311
+ `--email-fragment-map <json-file>` opts into the experimental first Phase 4 profile. It supports only dependency-free `sfdc_cms__emailFragment` content whose body exactly matches the evidenced root-content-block → one section → one empty column shape, including the exact accepted layout attributes and empty provider, expression, attachment, and variant arrays. It is not general reusable-block or workspace restoration support.
312
+
313
+ The mapping file is a nonempty JSON array with exact typed source selectors and fresh target identities:
314
+
315
+ ```json
316
+ [
317
+ {
318
+ "source": {
319
+ "family": "cms",
320
+ "type": "emailFragment",
321
+ "apiName": "source_fragment_api"
322
+ },
323
+ "target": {
324
+ "contentKey": "fresh-fragment-key",
325
+ "apiName": "fresh_fragment_api"
326
+ }
327
+ }
328
+ ]
329
+ ```
330
+
331
+ Each row must select exactly one integrity-verified `sfdc_cms__emailFragment` by API name. Missing, ambiguous, wrong-type, and duplicate selections are rejected; source content keys remain package provenance and are not selectors. The selected item language must equal the destination workspace's default language. Target `contentKey` and `apiName` must be fresh; the source title and URL name are preserved and therefore must also be fresh at the destination. Content-key and exact API-name absence are checked during preflight and rechecked immediately before CREATE, but dry-run still reports that broader server conflict behavior and apply readiness are unverified.
332
+
333
+ Preview first, then apply once with a new report directory:
334
+
335
+ ```sh
336
+ sf cms import workspace --target-org destination-org --workspace-id 0ZuTARGET --source-dir ./cms-baseline --email-fragment-map ./email-fragment-map.json --contract-version 1 --json
337
+ sf cms import workspace --target-org destination-org --workspace-id 0ZuTARGET --source-dir ./cms-baseline --email-fragment-map ./email-fragment-map.json --apply --report-dir ./cms-email-fragment-create-report --contract-version 1 --json
338
+ ```
339
+
340
+ Apply performs fresh create-only import under the destination root folder. It creates only the destination-default-language parent and leaves it Draft and unpublished. It preserves the source body, title, and URL name while replacing only the mapped `contentKey` and `apiName`; it does not modify the source package. No update, overwrite, publication, activation, send, child-language creation, or automatic retry is supported.
341
+
342
+ Bodies with nonempty components, references, media, data providers, expressions, attachments, variants, extra fields, or near-match layout definitions/attributes are rejected before mutation. Other email-fragment shapes remain unsupported. The separate `workspace.import.email-fragment-create` capability remains `experimental` because live acceptance covers only this narrow profile, not general email-fragment or workspace restoration.
343
+
344
+ #### Landing-page content blocks (`--web-fragment-map`)
345
+
346
+ Export one or many `sfdc_cms__webFragment` components by exact API name with a JSON string array. Every requested name must resolve exactly once in the selected workspace and type; otherwise export fails closed.
347
+
348
+ ```json
349
+ ["LandingHero", "LandingFooter"]
350
+ ```
351
+
352
+ ```sh
353
+ sf cms export workspace --target-org source-org --workspace-id 0ZuSOURCE --output-dir ./cms-web-fragments --web-fragment-map ./web-fragments.json --json
354
+ ```
355
+
356
+ Import uses a nonempty JSON array with exact typed source selection, fresh target identities, and one explicit row for every source Data Graph provider. Preserve names by repeating them, or map both the developer name and data-space developer name explicitly.
357
+
358
+ ```json
359
+ [
360
+ {
361
+ "source": {
362
+ "family": "cms",
363
+ "type": "webFragment",
364
+ "apiName": "LandingHero"
365
+ },
366
+ "target": {
367
+ "contentKey": "fresh_landing_hero_key",
368
+ "apiName": "LandingHeroTarget"
369
+ },
370
+ "dataGraphs": [
371
+ {
372
+ "sourceDeveloperName": "Marketing",
373
+ "sourceDataSpace": "default",
374
+ "targetDeveloperName": "Marketing",
375
+ "targetDataSpace": "default"
376
+ }
377
+ ]
378
+ }
379
+ ]
380
+ ```
381
+
382
+ ```sh
383
+ sf cms import workspace --target-org destination-org --workspace-id 0ZuTARGET --source-dir ./cms-web-fragments --web-fragment-map ./web-fragment-map.json --contract-version 1 --json
384
+ sf cms import workspace --target-org destination-org --workspace-id 0ZuTARGET --source-dir ./cms-web-fragments --web-fragment-map ./web-fragment-map.json --apply --report-dir ./cms-web-fragment-report --contract-version 1 --json
385
+ ```
386
+
387
+ Dry-run proves fresh content-key and API-name availability plus exact unique `DataGraph.DeveloperName` and `DataSpaceDevName` matches. Apply repeats API-name and Data Graph validation immediately before each CREATE, creates sequentially, and journals every prerequisite mapping and mutation in `workspace-import-run.json`. Missing, ambiguous, unqueryable, or mismatched prerequisites block creation; no default Data Graph substitution is allowed. The profile creates Draft content only: no update fallback, overwrite, publication, activation, or send is performed. Deploy referenced CMS images before dependent fragments. Live installed-host fragment CREATE/readback acceptance is still pending.
388
+
389
+ #### Landing-page templates (`--landing-page-template-map`)
390
+
391
+ Export one or many `sfdc_cms__landingPageTemplate` components by exact API name. The selection file is a nonempty JSON string array; each name must resolve exactly once in the selected workspace and exact content type.
392
+
393
+ ```json
394
+ ["MCBSUMZKCZFFEU3MZFHAZWVTWNGY"]
395
+ ```
396
+
397
+ ```sh
398
+ sf cms export workspace --target-org source-org --workspace-id 0ZuSOURCE --output-dir ./cms-landing-templates --landing-page-template-map ./landing-page-templates.json --json
399
+ ```
400
+
401
+ Import uses exact typed source selection, fresh create identities, explicit mappings for every captured CMS image/web-fragment reference, and explicit mappings for every Data Graph provider. `targetTitle` is optional and is used only when the exact target API-name lookup succeeds with zero matches; fallback remains exact, type-qualified, workspace-scoped, and must resolve uniquely.
402
+
403
+ ```json
404
+ [
405
+ {
406
+ "source": {
407
+ "family": "cms",
408
+ "type": "landingPageTemplate",
409
+ "apiName": "MCBSUMZKCZFFEU3MZFHAZWVTWNGY"
410
+ },
411
+ "target": {
412
+ "contentKey": "fresh_landing_template_key",
413
+ "apiName": "FreshLandingTemplate"
414
+ },
415
+ "cmsDependencies": [
416
+ {
417
+ "sourceContentKey": "MCX2CQNLBTIBHUTDNGOLKCRUZW2Q",
418
+ "sourceType": "image",
419
+ "targetApiName": "TargetLogoImage",
420
+ "targetTitle": "Logo Placeholder"
421
+ }
422
+ ],
423
+ "dataGraphs": [
424
+ {
425
+ "sourceDeveloperName": "Marketing",
426
+ "sourceDataSpace": "default",
427
+ "targetDeveloperName": "Marketing",
428
+ "targetDataSpace": "default"
429
+ }
430
+ ]
431
+ }
432
+ ]
433
+ ```
434
+
435
+ ```sh
436
+ sf cms import workspace --target-org destination-org --workspace-id 0ZuTARGET --source-dir ./cms-landing-templates --landing-page-template-map ./landing-page-template-map.json --contract-version 1 --json
437
+ sf cms import workspace --target-org destination-org --workspace-id 0ZuTARGET --source-dir ./cms-landing-templates --landing-page-template-map ./landing-page-template-map.json --apply --report-dir ./cms-landing-template-report --contract-version 1 --json
438
+ ```
439
+
440
+ Dry-run verifies the complete package, fresh destination content key/API name, each exact Data Graph identity, and each exact typed CMS prerequisite. Apply repeats all checks immediately before each sequential CREATE, rewrites only the mapped CMS content keys and bound media URLs, and records CMS/Data Graph prerequisite resolution plus mutation evidence in `workspace-import-run.json`. Any lookup error, missing/ambiguous API name, missing/ambiguous title fallback, wrong type/workspace, unsupported reference shape, or drift blocks creation. Templates remain Draft and unpublished; there is no update, overwrite, upsert, publication, activation, send, or landing-page creation. Deploy images and web fragments first. Live installed-host template CREATE/readback acceptance remains pending.
441
+
442
+ #### Landing pages (`--landing-page-map`)
443
+
444
+ Export one or many `sfdc_cms__landingPage` components by exact API name. The selection file is a nonempty JSON string array; each name must resolve exactly once within the selected workspace and exact content type.
445
+
446
+ ```json
447
+ ["MCJV5RXRKOMRF5HD3OD3OFQSO5C4"]
448
+ ```
449
+
450
+ ```sh
451
+ sf cms export workspace --target-org source-org --workspace-id 0ZuSOURCE --output-dir ./cms-landing-pages --landing-page-map ./landing-pages.json --json
452
+ ```
453
+
454
+ The bounded import profile supports only the captured landing-page shape: the exact top-level body fields, the `sfdc_cms__dataGraphDataProvider` with `dataGraphApiName` and `dataspace`, and image blocks whose `source.type` is `imageReference`, whose `source.ref.contentKey` identifies the image, and whose URL begins with `/cms/media/{contentKey}`. The available fixture does not evidence landing-page-template or web-fragment references, so this profile does not resolve or rewrite them and fails closed if an unsupported reference shape appears.
455
+
456
+ ```json
457
+ [
458
+ {
459
+ "source": {
460
+ "family": "cms",
461
+ "type": "landingPage",
462
+ "apiName": "MCJV5RXRKOMRF5HD3OD3OFQSO5C4"
463
+ },
464
+ "target": {
465
+ "contentKey": "fresh_landing_page_key",
466
+ "apiName": "FreshLandingPage"
467
+ },
468
+ "imageDependencies": [
469
+ {
470
+ "sourceContentKey": "MCX2CQNLBTIBHUTDNGOLKCRUZW2Q",
471
+ "targetApiName": "TargetLogoImage",
472
+ "targetTitle": "Logo Placeholder"
473
+ }
474
+ ],
475
+ "dataGraphs": [
476
+ {
477
+ "sourceDeveloperName": "Marketing",
478
+ "sourceDataSpace": "default",
479
+ "targetDeveloperName": "Marketing",
480
+ "targetDataSpace": "default"
481
+ }
482
+ ]
483
+ }
484
+ ]
485
+ ```
486
+
487
+ ```sh
488
+ sf cms import workspace --target-org destination-org --workspace-id 0ZuTARGET --source-dir ./cms-landing-pages --landing-page-map ./landing-page-map.json --contract-version 1 --json
489
+ sf cms import workspace --target-org destination-org --workspace-id 0ZuTARGET --source-dir ./cms-landing-pages --landing-page-map ./landing-page-map.json --apply --report-dir ./cms-landing-page-report --contract-version 1 --json
490
+ ```
491
+
492
+ Image resolution is exact API name first within the destination workspace and `sfdc_cms__image` type. `targetTitle` is considered only after that lookup completes successfully with zero matches, and the exact title fallback must resolve once. Dry-run performs no writes. Apply creates Draft pages sequentially, immediately rechecks content-key/API-name availability plus all image and Data Graph prerequisites before each CREATE, rewrites the resolved image content keys and their matching `/cms/media/{contentKey}` URLs, and durably journals prerequisite results, request hashes, and returned IDs in `workspace-import-run.json`. There is no update, overwrite, upsert, publish, activate, send, or implicit dependency deployment. Installed-host live landing-page CREATE/readback acceptance remains pending and was not performed for this slice.
493
+
494
+ #### Editable HTML companion (`--editable-dir`)
495
+
496
+ For local HTML editing, keep two separate directories: the unchanged workspace export is the integrity/provenance baseline, and an opt-in companion contains literal HTML beside its variant metadata. Export with both explicit destinations:
497
+
498
+ ```sh
499
+ sf cms export workspace --target-org source-org --workspace-id 0ZuSOURCE --output-dir ./cms-baseline --editable-dir ./cms-editable --json
500
+ ```
501
+
502
+ Replace the example org aliases and workspace IDs with your own. `--editable-dir` is single-workspace only, cannot be combined with `--all`, and requires explicit `--output-dir`. Both destinations must be new, disjoint directories: neither may contain the other. Unsafe paths and symlink/junction routes are rejected before org access. The baseline is published first; if companion creation fails (including when there are no eligible variants), the command fails with a **baseline retained / editable output unavailable** message. Keep that baseline: this is not a two-directory transaction, and a successful baseline is not deleted on companion failure.
503
+
504
+ ```text
505
+ cms-baseline/
506
+ ├── manifest.json
507
+ └── items/<variant-id>.json
508
+ cms-editable/
509
+ ├── editable.json
510
+ └── items/
511
+ ├── <variant-id>.json
512
+ └── <variant-id>.html
513
+ ```
514
+
515
+ The companion includes only `sfdc_cms__email` / `sfdc_cms__emailTemplate` variants with string `contentBody.rawHtml` and no `sfdc_cms:block`. Other content remains solely in the baseline. Distinct variant IDs keep languages and parents separate. Export eligibility is a raw-shape test, not proof that the HTML is dependency-free or safe. Builder/block-based content, arbitrary metadata editing, and structured media/reference transport are not supported; embedded HTML dependencies and dynamic syntax remain opaque while the retained scanner is bypassed until Phase 8.
516
+
517
+ 1. Open `./cms-editable/items/<variant-id>.html` in your editor and change only the HTML. It is literal UTF-8, not a JSON-escaped document; save without a BOM. Do not edit the baseline, `editable.json`, or the adjacent metadata JSON, and do not add or rename files. The metadata retains the original variant fields except `contentBody.rawHtml`.
518
+ 2. Save `./native-copy-map.json` outside both directories using the four-field array shown above. Select the exact original content key and language, with fresh API/URL names. Only one destination-default-language variant per parent can be selected; every selected variant must have a companion HTML file.
519
+ 3. Preview the reconstructed HTML with the default dry-run:
520
+
521
+ ```sh
522
+ sf cms import workspace --target-org destination-org --workspace-id 0ZuTARGET --source-dir ./cms-baseline --editable-dir ./cms-editable --native-copy-map ./native-copy-map.json --contract-version 1 --json
523
+ ```
524
+
525
+ 4. Review the result, then explicitly request fresh CREATE with a new report directory outside both input directories:
526
+
527
+ ```sh
528
+ sf cms import workspace --target-org destination-org --workspace-id 0ZuTARGET --source-dir ./cms-baseline --editable-dir ./cms-editable --native-copy-map ./native-copy-map.json --apply --report-dir ./cms-editable-create-report --contract-version 1 --json
529
+ ```
530
+
531
+ For a partial baseline, add `--allow-partial` to both import commands only after accepting its recorded omissions. A `partial` result can exit `2` with usable data; inspect the result and diagnostics rather than treating dry-run as apply readiness. No `--dry-run` flag is needed. Import `--editable-dir` requires `--native-copy-map`, and `--source-dir` always points to the original baseline, never the companion. CREATE generates new target keys; this is not UPDATE or publication, and destination name availability is not guaranteed by dry-run.
532
+
533
+ Import verifies the entire baseline, including unselected items, before checking the companion against metadata and hashes derived from that baseline. Only declared HTML contents may differ; recalculating a descriptor hash cannot authorize a metadata edit. No manual checksum updates or repacking are needed. Selected literal HTML replaces only `rawHtml` and is not decoded as a GET response again. Structured/package reference checks and all non-HTML metadata guards still apply, but the opaque HTML itself is not scanned. Existing explicit native identity/URL mapping still applies; neither input directory is rewritten.
534
+
535
+ `EDITABLE_HTML_INPUT` appears on successful dry-run/apply results even if no HTML changed. It separates selected modifications planned/applied from changes across **all** companion entries. Original source-manifest hashes, integrity counts, and reference inventory continue to describe only the unchanged baseline, not the edited payload; HTML edits do not establish CMS reference rewriting. Before the first editable CREATE, the initial durable `workspace-import-run.json` includes `editableSource` with the companion contract, original `sourceManifestSha256`, and every companion variant's `originalHtmlSha256`, `currentHtmlSha256`, and `changed` value. Its pending operations also bind the exact reconstructed CREATE payloads through `requestSha256`. All-companion evidence does not mean all entries were selected or created; use the operations and returned identities to determine what happened.
536
+
537
+ The companion is **not** an `sf-cms-workspace-export` package and must not be supplied as MCNext migration artifact evidence. Default exports, workspace-package v1, and existing CLI JSON result shapes remain unchanged. MCNext does not consume this editable directory or gain editable-transfer support from its presence.
538
+
539
+ Controlled-transport installed CLI tests cover export, HTML editing, dry-run, fresh CREATE, readback, and journal evidence for email/template content. **Live installed editable transfer is still unproven:** the 2026-09-16 attempt stopped on Salesforce session-refresh maintenance before export or mutation. Historical native-copy acceptance is separate evidence, not proof of edited HTML transfer.
540
+
541
+ #### Run reports and recovery
542
+
240
543
  A successful apply writes:
241
544
 
242
545
  ```text
@@ -244,15 +547,15 @@ cms-import-report/
244
547
  └── workspace-import-run.json
245
548
  ```
246
549
 
247
- The run report binds the run ID to the destination org ID, destination workspace ID, canonical source directory, and source-manifest SHA-256. Before each parent or child mutation it atomically records a `pending` operation containing the exact content key, language, operation kind, destination identity, deterministic request identity, and SHA-256. A response is then recorded as `succeeded` with returned IDs, or a thrown mutation is recorded as `failed` when report storage remains available. Immediate created-parent records remain available for recovery. The run itself is marked `applying`, `completed`, `failed`, or `ownership-uncertain`.
550
+ The run report binds the run ID to the destination org ID, destination workspace ID, canonical source directory, and source-manifest SHA-256. Before each parent or child mutation it atomically records a `pending` operation containing the content key, language, operation kind, destination identity, deterministic request identity, and SHA-256. For native copies, that pre-request content key is the source key, not a predicted target key. Returned native identities are durably recorded as `contentKey`, `contentId`, and `primaryVariantId` before response semantic checks and readback verification. Native POST errors, including `DUPLICATE_VALUE`, leave the operation pending and the run ownership-uncertain; there is no automatic retry or update fallback. In the default profile, a response is recorded as `succeeded` with returned IDs, or a thrown mutation is recorded as `failed` when report storage remains available. Immediate created-parent records remain available for recovery. The run itself is marked `applying`, `completed`, `failed`, or `ownership-uncertain`.
248
551
 
249
552
  Filesystem report replacement is atomic, but the remote mutation and local report update are not a single atomic transaction. If a mutation returns successfully and its result cannot be durably written, the command raises a distinct ownership-uncertain error and keeps that operation `pending`; the report must not be interpreted as proving that no remote object was created. An existing report directory is never reused, so unresolved operations cannot be treated as resumable progress.
250
553
 
251
554
  Import limitations and conflicts:
252
555
 
253
- - Import is create-only. Any existing destination content key aborts the entire preflight before mutation; there is no overwrite, update, merge, checkpoint/resume, or cleanup command.
254
- - Every content group must contain exactly one variant matching the destination workspace's `defaultLanguage`. There is no primary-language fallback.
255
- - Child variants are created sequentially after their primary parent. There is no concurrency.
556
+ - Import is create-only. In the default profile, any existing destination content key aborts the entire preflight before mutation. Native copies use server-generated keys instead. Neither profile supports overwrite, update, merge, checkpoint/resume, or a cleanup command.
557
+ - Every content group must contain exactly one variant matching the destination workspace's `defaultLanguage`. There is no primary-language fallback; native copies select only that language and create no additional-language children.
558
+ - Default-profile child variants are created sequentially after their primary parent. There is no concurrency, automatic rollback, or full-package transaction; a later failure can leave earlier created drafts.
256
559
  - Media-specific migration is not supported.
257
560
  - The command does not change publication state; newly created records are expected to remain drafts.
258
561
  - `--allow-partial` accepts known source omissions but does not make the missing records recoverable.
@@ -260,7 +563,7 @@ Import limitations and conflicts:
260
563
  Recovery guidance:
261
564
 
262
565
  1. Preserve `workspace-import-run.json` if an apply fails; it is the authoritative local journal for that run.
263
- 2. Reconcile every unresolved `pending` operation first, using its exact destination org/workspace, content key, language, operation kind, and request hash. A pending entry means the mutation may have happened even when no returned ID is recorded.
566
+ 2. Reconcile every unresolved `pending` operation first, using its exact destination org/workspace, content key, language, operation kind, and request hash. For native copies, distinguish the recorded source key from any returned generated target identity. A pending entry means the mutation may have happened even when no returned ID is recorded; neither a POST error nor a failed readback proves that no draft exists. Do not retry blindly.
264
567
  3. Inspect and verify each `succeeded` operation and immediate created-parent record in the exact destination org and workspace before taking action.
265
568
  4. Remove only records proven to belong to that run, beginning with recorded variants. Do not infer IDs or delete by broad search, workspace, title, or content-key pattern.
266
569
  5. If ownership or deletion is uncertain, stop and record the leftovers for manual review. Never modify workspace/channel configuration or publish content as recovery.
@@ -285,13 +588,13 @@ sf cms get content --help
285
588
 
286
589
  ### Integration boundary and ownership
287
590
 
288
- The supported v0.3.1 integration boundary is the Salesforce CLI subprocess. A consumer such as an MCN orchestrator should:
591
+ The supported v0.5.0 integration boundary is the Salesforce CLI subprocess. A consumer such as an MCN orchestrator should:
289
592
 
290
593
  1. Run `sf cms info --json`, require the needed capability, and choose a mutually supported command-result major.
291
594
  2. Run bulk export or workspace import with `--contract-version 1 --json`.
292
595
  3. Consume the returned CMS mappings and rewrite only fields owned by that consumer.
293
596
 
294
- CMS owns CMS artifact identity, source-to-target CMS mappings, import ordering, and CMS-internal reference rewriting. Dependency discovery and closure are unavailable in v0.3.1: exports preserve only evidenced opaque identity inventory and do not expose dependency edges. Consumers must not inspect package payloads to reconstruct CMS identity, infer dependencies or mappings, or rewrite CMS-owned references. No public JavaScript API is part of v0.3.1; the CLI JSON boundary is sufficient and avoids a second integration surface.
597
+ CMS owns CMS artifact identity, source-to-target CMS mappings, import ordering, and CMS-internal reference rewriting. General dependency discovery and closure are unavailable in v0.5.0: default exports preserve only evidenced opaque identity inventory and do not expose a general dependency graph. The bounded web-fragment, landing-page-template, and landing-page profiles resolve only the exact CMS and Data Graph prerequisites declared in their maps and evidenced by their supported shapes. Consumers must not inspect package payloads to reconstruct CMS identity, infer dependencies or mappings, or rewrite CMS-owned references. No public JavaScript API is part of v0.5.0; the CLI JSON boundary is sufficient and avoids a second integration surface.
295
598
 
296
599
  ### Authoritative envelope
297
600
 
@@ -304,14 +607,14 @@ CMS owns CMS artifact identity, source-to-target CMS mappings, import ordering,
304
607
  "status": "success",
305
608
  "metadata": {
306
609
  "operation": "<cms.info|workspace.export.bulk|workspace.import>",
307
- "plugin": { "name": "sf-plugin-cms", "version": "0.3.1" },
610
+ "plugin": { "name": "sf-plugin-cms", "version": "0.5.0" },
308
611
  "apiVersion": "67.0"
309
612
  },
310
613
  "diagnostics": { "warnings": [], "errors": [] },
311
614
  "provenance": {
312
615
  "producer": "sf-plugin-cms",
313
616
  "sourceOrgId": "<org-id-or-offline>",
314
- "pluginVersion": "0.3.1",
617
+ "pluginVersion": "0.5.0",
315
618
  "command": "sf cms <operation>",
316
619
  "generatedAt": "<ISO-8601>"
317
620
  },
@@ -328,8 +631,11 @@ The `--contract-version <major>` flag selects the command envelope/result major
328
631
  `sf-cms-info@1` reports plugin/API versions, supported command results, supported package manifests, result-to-manifest compatibility, and these capability IDs:
329
632
 
330
633
  - `workspace.export.bulk` — `implemented`, contract `sf-cms-workspace-export-set@1`.
331
- - `workspace.export.dependency-closure` — `unavailable`; v0.3.1 does not discover or traverse CMS relationships.
634
+ - `workspace.export.dependency-closure` — `unavailable`; v0.5.0 does not provide general relationship discovery or traversal.
332
635
  - `workspace.export.external-reference-correlation` — `experimental`, embedded contract `sf-cms-external-reference-correlations@1`.
636
+ - `workspace.export.experimental-media` — `experimental`, contract `sf-cms-workspace-export@2`; available only for a single workspace with explicit `--experimental-media`.
637
+ - `workspace.import.email-fragment-create` — `experimental`, contract `sf-cms-workspace-import@1`.
638
+ - `workspace.import.image-create` — `experimental`, contract `sf-cms-workspace-import@2`.
333
639
  - `workspace.import.mapping` — `experimental`, contract `sf-cms-workspace-import@1`.
334
640
 
335
641
  `sf-cms-workspace-export-set@1` contains `workspaceType`, a portable `outputDirectory`, selection and summary counts, `externalReferenceCorrelations`, and deterministic per-workspace entries. Workspace entries include canonical source identity, `success|partial|failed` status, relative artifact/manifest paths and hashes when finalized, and structured diagnostics. Any finalized omission or unresolved/unsupported reference makes that workspace and aggregate partial.
@@ -352,7 +658,7 @@ The correlation table exists only at `result.externalReferenceCorrelations`. Eac
352
658
 
353
659
  ### Package layout and integrity
354
660
 
355
- A v1 workspace export package contains `manifest.json` plus regular item files. `manifest.json` is the sole package control file and the sole regular file excluded from `items[]`. Every other regular file must occur exactly once in `items[]`; directories are excluded, while symlinks and other special filesystem entries are rejected. Paths are relative POSIX-style paths.
661
+ A workspace export package contains `manifest.json` plus regular item files. Packages without image candidates use manifest v1. Image candidates without `--experimental-media` remain JSON entries in a strict v2 manifest with an empty `media` array, no media binary files, a `MEDIA_EXPORT_FAILED` warning, and `partial` completeness. With explicit `--experimental-media`, successful image downloads use the same strict v2 manifest and add bijectively matched `media` descriptors and `cms.media` items. `manifest.json` is the sole package control file and the sole regular file excluded from `items[]`. Every other regular file must occur exactly once in `items[]`; directories are excluded, while symlinks and other special filesystem entries are rejected. Paths are relative POSIX-style paths.
356
662
 
357
663
  Each item records exactly `{ path, sha256, kind, referenceId? }`, with lowercase SHA-256 calculated over the finalized exact file bytes. Import independently validates the manifest major, enumerates the package, rejects missing, substituted, duplicate-path, or unlisted files, and verifies every item hash. Only after the listed set and hashes are verified does it hash the exact `manifest.json` bytes and use that hash as package identity.
358
664
 
@@ -386,7 +692,7 @@ The experimental export uses the v67 `GET /connect/cms/items/search` operation.
386
692
 
387
693
  String-array query values such as `contentSpaceOrFolderIds` and `languages` are serialized as repeated keys. Pagination reconstructs each request from the original filters instead of trusting `nextPageUri`, which was observed to remain present after an empty page. Delivery responses do not prove workspace ownership and exclude drafts. SOQL omitted a known record that remained readable through Connect REST, and CMS `9Pu` folders exposed no child-enumeration operation, so none of those alternatives establishes a complete inventory.
388
694
 
389
- Current shipped export guards include an explicit single-workspace mode and a strict bulk preflight. Workspace enumeration is zero-based with `pageSize=250`, deduplicates exact IDs, ignores advertised totals as a termination signal, and continues until an empty page. It fails closed on repeated pages, no progress, malformed IDs, case/Unicode-equivalent IDs, or the 1,000-page cap. Bulk preflight canonicalizes every workspace with a GET, validates canonical ID, name, and `spaceType`, filters by actual type, sorts by canonical ID, and rejects unsafe or colliding destinations before any export starts.
695
+ Current shipped export guards include an explicit single-workspace mode and a strict bulk preflight. Workspace enumeration is zero-based with `pageSize=250`, deduplicates exact IDs, rejects conflicting recognized types for the same ID during bulk preflight, ignores advertised totals as a termination signal, and continues until an empty page. It fails closed on repeated pages, no progress, malformed IDs, case/Unicode-equivalent IDs, or the 1,000-page cap. Bulk preflight canonicalizes every workspace with a GET, validates canonical ID and name, prefers recognized canonical `spaceType`, and uses recognized list type evidence only when the canonical detail type is absent and the exact ID matches. It rejects present malformed, null, or unsupported canonical type evidence, filters an explicit contradictory `Content` type, sorts by canonical ID, and rejects unsafe or colliding destinations before any export starts.
390
696
 
391
697
  Per-workspace variant export remains atomic and best-effort: it verifies `managedContentSpaceId` in search rows and `contentSpace.id` in variant details, records ownership rejections and detail failures, refuses an existing destination, writes stable JSON to a temporary sibling directory, removes staging after failure, and renames staging only after all output is written. Bulk execution continues across workspace failures and emits a deterministic complete aggregate in both human and JSON modes.
392
698
 
@@ -5,7 +5,7 @@ import { CmsCommand } from '../../../command-base.js';
5
5
  type WorkspaceExportCommandResult = CmsEnvelope<WorkspaceExportSetResult> | ExportWorkspaceResult;
6
6
  export default class ExportWorkspace extends CmsCommand<WorkspaceExportCommandResult> {
7
7
  static readonly summary = "Experimentally export one or all CMS workspaces using a read-only, best-effort process.";
8
- static readonly description = "Experimentally exports one Marketing Cloud CMS workspace selected by exact ID or case-insensitive exact name, or preflights and exports all workspaces under a parent directory. Canonical fetched names and casing are preserved. Bulk preflight is global and strict; execution then continues across individual failures and returns a complete aggregate. This is not a complete or guaranteed backup and does not mutate org data.";
8
+ static readonly description = "Experimentally exports one Marketing Cloud CMS workspace or preflights and exports all workspaces. Image candidates remain partial JSON-only records by default. Single-workspace --experimental-media explicitly opts into an undocumented read-only binary transport that forwards Salesforce authorization only to a narrowly validated Salesforce media host class. Bulk media export is unsupported. For a single workspace, --editable-dir creates an HTML-only companion beside an explicit unchanged baseline. The paths must be new and disjoint; the baseline is retained if companion creation fails. Eligible HTML is not proven dependency-free or safe and is not resolved, rewritten, sanitized, or scanned through Phase 7. This is not a complete or guaranteed backup and does not mutate org data.";
9
9
  static readonly examples: string[];
10
10
  static readonly flags: {
11
11
  'target-org': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -15,6 +15,11 @@ export default class ExportWorkspace extends CmsCommand<WorkspaceExportCommandRe
15
15
  'workspace-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
16
  'workspace-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
17
  'workspace-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ 'web-fragment-map': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ 'landing-page-template-map': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ 'landing-page-map': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ 'experimental-media': import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
+ 'editable-dir': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
23
  'output-dir': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
24
  };
20
25
  run(): Promise<WorkspaceExportCommandResult>;