contentful-export 8.3.0 → 8.4.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/README.md +17 -0
- package/dist/index.js +29 -0
- package/dist/parseOptions.js +1 -0
- package/dist/tasks/get-space-data.js +88 -0
- package/dist/usageParams.js +4 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -389,6 +389,23 @@ If the source space has no ExO entities, or lacks the `exoM1` entitlement, each
|
|
|
389
389
|
|
|
390
390
|
Requires the `exoM1` entitlement on the source space's organization. [contentful-cli](https://github.com/contentful/contentful-cli)'s `space export` command doesn't expose this option at all yet, so ExO export isn't reachable through that separate CLI regardless of default.
|
|
391
391
|
|
|
392
|
+
### Optimization Variants
|
|
393
|
+
|
|
394
|
+
Experiences and Experience Fragments each support **Optimization Variants** — alternate versions used for personalization. Variants are a separate opt-in on top of `includeExperienceOrchestration`, defaulting to `false`:
|
|
395
|
+
|
|
396
|
+
```javascript
|
|
397
|
+
const options = {
|
|
398
|
+
spaceId: '<space_id>',
|
|
399
|
+
managementToken: '<content_management_api_key>',
|
|
400
|
+
includeExperienceOrchestration: true,
|
|
401
|
+
includeExoVariants: true
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
await contentfulExport(options)
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Unlike the six entity types above, variants are **not** exported as a seventh top-level array — they're nested onto their parent as `experience.optimizationVariants` / `experienceFragment.optimizationVariants`, because a variant has no globally-unique `sys.id` of its own (the API's variant response reuses the parent's `sys.id`; the variant is identified by `sys.variant`/`sys.variantType`/`sys.variantDimension` instead). When `includeExoVariants` is omitted or `false`, the `optimizationVariants` field is absent entirely — not an empty array — so default export output is unaffected. See [`docs/exo-export.md`](./docs/exo-export.md#optimization-variants) for the full shape, the ADR behind the nested-storage decision, and the corresponding `contentful-import` behavior.
|
|
408
|
+
|
|
392
409
|
### Round-tripping into `contentful-import`
|
|
393
410
|
|
|
394
411
|
The ExO entities exported here are designed to be fed directly into [`contentful-import`](https://github.com/contentful/contentful-import), which preserves source IDs, applies dependency ordering (a topological sort for Components and Experience Fragments, since either can reference others of the same type), and upgrades entities from older, pre-rename export files automatically. See `contentful-import`'s README "Experience Orchestration (ExO) entities" section for the import-side details.
|
package/dist/index.js
CHANGED
|
@@ -82,6 +82,7 @@ function runContentfulExport(params) {
|
|
|
82
82
|
skipTags: options.skipTags,
|
|
83
83
|
stripTags: options.stripTags,
|
|
84
84
|
includeExperienceOrchestration: options.includeExperienceOrchestration,
|
|
85
|
+
includeExoVariants: options.includeExoVariants,
|
|
85
86
|
listrOptions,
|
|
86
87
|
queryEntries: options.queryEntries,
|
|
87
88
|
queryAssets: options.queryAssets
|
|
@@ -131,6 +132,7 @@ function runContentfulExport(params) {
|
|
|
131
132
|
}).then(ctx => {
|
|
132
133
|
const resultTypes = Object.keys(ctx.data);
|
|
133
134
|
if (resultTypes.length) {
|
|
135
|
+
var _ctx$data$experiences, _ctx$optimizationVari, _ctx$data$experienceF, _ctx$optimizationVari2;
|
|
134
136
|
const resultTable = new _cliTable.default(tableOptions);
|
|
135
137
|
resultTable.push([{
|
|
136
138
|
colSpan: 2,
|
|
@@ -139,6 +141,33 @@ function runContentfulExport(params) {
|
|
|
139
141
|
resultTypes.forEach(type => {
|
|
140
142
|
resultTable.push([(0, _lodash.default)(type), ctx.data[type].length]);
|
|
141
143
|
});
|
|
144
|
+
|
|
145
|
+
// Optimization Variants are nested onto their parent Experience/Experience
|
|
146
|
+
// Fragment rather than exported as their own top-level field, so their count
|
|
147
|
+
// isn't free from resultTypes above and is computed here instead. A per-parent
|
|
148
|
+
// fetch failure falls back to an empty optimizationVariants array, which is
|
|
149
|
+
// indistinguishable from a parent that genuinely has none -- so failures are
|
|
150
|
+
// surfaced as their own row rather than folded silently into the count.
|
|
151
|
+
if ((_ctx$data$experiences = ctx.data.experiences) !== null && _ctx$data$experiences !== void 0 && _ctx$data$experiences.some(experience => experience.optimizationVariants)) {
|
|
152
|
+
const count = ctx.data.experiences.reduce((sum, experience) => {
|
|
153
|
+
var _experience$optimizat, _experience$optimizat2;
|
|
154
|
+
return sum + ((_experience$optimizat = (_experience$optimizat2 = experience.optimizationVariants) === null || _experience$optimizat2 === void 0 ? void 0 : _experience$optimizat2.length) !== null && _experience$optimizat !== void 0 ? _experience$optimizat : 0);
|
|
155
|
+
}, 0);
|
|
156
|
+
resultTable.push(['Experience Optimization Variants', count]);
|
|
157
|
+
}
|
|
158
|
+
if ((_ctx$optimizationVari = ctx.optimizationVariantFailures) !== null && _ctx$optimizationVari !== void 0 && _ctx$optimizationVari.experiences) {
|
|
159
|
+
resultTable.push(['Experience Optimization Variant Fetch Failures', ctx.optimizationVariantFailures.experiences]);
|
|
160
|
+
}
|
|
161
|
+
if ((_ctx$data$experienceF = ctx.data.experienceFragments) !== null && _ctx$data$experienceF !== void 0 && _ctx$data$experienceF.some(experienceFragment => experienceFragment.optimizationVariants)) {
|
|
162
|
+
const count = ctx.data.experienceFragments.reduce((sum, experienceFragment) => {
|
|
163
|
+
var _experienceFragment$o, _experienceFragment$o2;
|
|
164
|
+
return sum + ((_experienceFragment$o = (_experienceFragment$o2 = experienceFragment.optimizationVariants) === null || _experienceFragment$o2 === void 0 ? void 0 : _experienceFragment$o2.length) !== null && _experienceFragment$o !== void 0 ? _experienceFragment$o : 0);
|
|
165
|
+
}, 0);
|
|
166
|
+
resultTable.push(['Experience Fragment Optimization Variants', count]);
|
|
167
|
+
}
|
|
168
|
+
if ((_ctx$optimizationVari2 = ctx.optimizationVariantFailures) !== null && _ctx$optimizationVari2 !== void 0 && _ctx$optimizationVari2.experienceFragments) {
|
|
169
|
+
resultTable.push(['Experience Fragment Optimization Variant Fetch Failures', ctx.optimizationVariantFailures.experienceFragments]);
|
|
170
|
+
}
|
|
142
171
|
console.log(resultTable.toString());
|
|
143
172
|
} else {
|
|
144
173
|
console.log('No data was exported');
|
package/dist/parseOptions.js
CHANGED
|
@@ -38,6 +38,7 @@ function getFullSourceSpace({
|
|
|
38
38
|
includeArchived,
|
|
39
39
|
maxAllowedLimit,
|
|
40
40
|
includeExperienceOrchestration,
|
|
41
|
+
includeExoVariants,
|
|
41
42
|
listrOptions,
|
|
42
43
|
queryEntries,
|
|
43
44
|
queryAssets
|
|
@@ -260,6 +261,40 @@ function getFullSourceSpace({
|
|
|
260
261
|
}
|
|
261
262
|
}),
|
|
262
263
|
skip: () => !includeExperienceOrchestration
|
|
264
|
+
}, {
|
|
265
|
+
title: 'Fetching Experience Optimization Variants data',
|
|
266
|
+
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
267
|
+
const experienceFailureCount = await attachOptimizationVariants({
|
|
268
|
+
parents: ctx.data.experiences,
|
|
269
|
+
fetchFn: (parentId, query) => client.experienceVariant.getMany({
|
|
270
|
+
spaceId,
|
|
271
|
+
environmentId,
|
|
272
|
+
experienceId: parentId,
|
|
273
|
+
query
|
|
274
|
+
}),
|
|
275
|
+
entityLabel: 'Experience Optimization Variants'
|
|
276
|
+
});
|
|
277
|
+
ctx.optimizationVariantFailures = ctx.optimizationVariantFailures || {};
|
|
278
|
+
ctx.optimizationVariantFailures.experiences = experienceFailureCount;
|
|
279
|
+
}),
|
|
280
|
+
skip: () => !includeExperienceOrchestration || !includeExoVariants
|
|
281
|
+
}, {
|
|
282
|
+
title: 'Fetching Experience Fragment Optimization Variants data',
|
|
283
|
+
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
284
|
+
const experienceFragmentFailureCount = await attachOptimizationVariants({
|
|
285
|
+
parents: ctx.data.experienceFragments,
|
|
286
|
+
fetchFn: (parentId, query) => client.experienceFragmentVariant.getMany({
|
|
287
|
+
spaceId,
|
|
288
|
+
environmentId,
|
|
289
|
+
experienceFragmentId: parentId,
|
|
290
|
+
query
|
|
291
|
+
}),
|
|
292
|
+
entityLabel: 'Experience Fragment Optimization Variants'
|
|
293
|
+
});
|
|
294
|
+
ctx.optimizationVariantFailures = ctx.optimizationVariantFailures || {};
|
|
295
|
+
ctx.optimizationVariantFailures.experienceFragments = experienceFragmentFailureCount;
|
|
296
|
+
}),
|
|
297
|
+
skip: () => !includeExperienceOrchestration || !includeExoVariants
|
|
263
298
|
}], listrOptions);
|
|
264
299
|
}
|
|
265
300
|
function getEditorInterfaces(client, spaceId, environmentId, contentTypes) {
|
|
@@ -305,6 +340,59 @@ async function cursorPagedGet(fetchFn, entityLabel) {
|
|
|
305
340
|
return allItems;
|
|
306
341
|
}
|
|
307
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Fetches and attaches Optimization Variants onto each of a parent entity type's
|
|
345
|
+
* items (Experience or ExperienceFragment), mutating them in place as
|
|
346
|
+
* `parent.optimizationVariants`. Variants are nested onto their parent rather than
|
|
347
|
+
* exported as their own top-level field because a variant's `sys.id` is borrowed
|
|
348
|
+
* from its parent (not unique to the variant), so a flat array would collide the
|
|
349
|
+
* way none of the other ExO entity types do.
|
|
350
|
+
*
|
|
351
|
+
* Unlike the top-level ExO entities, the optimization_variants list endpoint is NOT
|
|
352
|
+
* cursor-paginated — it takes no limit/pageNext query params and its response has no
|
|
353
|
+
* total/pages fields, just { sys: { type: 'Array' }, items }. So this is a single
|
|
354
|
+
* request per parent, not a paging loop.
|
|
355
|
+
*
|
|
356
|
+
* The API always leads the list with a `sys.variantType: 'default'` item representing
|
|
357
|
+
* the parent's own base view (either a real record, or the baseline Experience/Fragment
|
|
358
|
+
* itself if none exists) — not a real personalization variant. That entity is already
|
|
359
|
+
* exported as its own top-level Experience/ExperienceFragment, so it's filtered out here
|
|
360
|
+
* to avoid double-counting it as a variant.
|
|
361
|
+
*
|
|
362
|
+
* Runs with bounded concurrency (mirrors getEditorInterfaces' pattern) since this
|
|
363
|
+
* issues one request per parent entity (N+1).
|
|
364
|
+
*
|
|
365
|
+
* Returns the count of parents whose variant fetch failed, so callers can
|
|
366
|
+
* surface that distinctly from a parent that genuinely has zero variants
|
|
367
|
+
* (both look identical as an empty `optimizationVariants` array otherwise).
|
|
368
|
+
*/
|
|
369
|
+
async function attachOptimizationVariants({
|
|
370
|
+
parents,
|
|
371
|
+
fetchFn,
|
|
372
|
+
entityLabel
|
|
373
|
+
}) {
|
|
374
|
+
if (!(parents !== null && parents !== void 0 && parents.length)) {
|
|
375
|
+
return 0;
|
|
376
|
+
}
|
|
377
|
+
let totalFetched = 0;
|
|
378
|
+
let failureCount = 0;
|
|
379
|
+
await _bluebird.default.map(parents, async parent => {
|
|
380
|
+
try {
|
|
381
|
+
const response = await fetchFn(parent.sys.id, {});
|
|
382
|
+
parent.optimizationVariants = response.items.filter(item => item.sys.variantType !== 'default');
|
|
383
|
+
totalFetched += parent.optimizationVariants.length;
|
|
384
|
+
_contentfulBatchLibs.logEmitter.emit('info', `Fetched ${totalFetched} ${entityLabel} items`);
|
|
385
|
+
} catch (err) {
|
|
386
|
+
_contentfulBatchLibs.logEmitter.emit('warning', `Skipping ${entityLabel} export for ${parent.sys.id}: ${err.message}`);
|
|
387
|
+
parent.optimizationVariants = [];
|
|
388
|
+
failureCount++;
|
|
389
|
+
}
|
|
390
|
+
}, {
|
|
391
|
+
concurrency: 6
|
|
392
|
+
});
|
|
393
|
+
return failureCount;
|
|
394
|
+
}
|
|
395
|
+
|
|
308
396
|
/**
|
|
309
397
|
* Gets all roles. Roles are scheduled to switch from skip/limit to
|
|
310
398
|
* cursor-based (pages.next/pagePrev) pagination on Feb 15, 2027:
|
package/dist/usageParams.js
CHANGED
|
@@ -111,6 +111,10 @@ var _default = exports.default = _yargs.default.version(_package.default.version
|
|
|
111
111
|
describe: 'Include Experience Orchestration entities (Components, Experience Templates, Data Assemblies, Experience Fragments, Experiences, Design Tokens). Requires exo_m1 entitlement on the source space.',
|
|
112
112
|
type: 'boolean',
|
|
113
113
|
default: true
|
|
114
|
+
}).option('include-exo-variants', {
|
|
115
|
+
describe: 'Include Optimization Variants, nested onto their parent Experience/Experience Fragment as `optimizationVariants`. Requires include-experience-orchestration. Only has an effect if the Contentful Personalization app is installed on the source space.',
|
|
116
|
+
type: 'boolean',
|
|
117
|
+
default: false
|
|
114
118
|
}).option('header', {
|
|
115
119
|
alias: 'H',
|
|
116
120
|
type: 'string',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "contentful-export",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.4.0",
|
|
4
4
|
"description": "this tool allows you to export a space to a JSON dump",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "types.d.ts",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"cli-table3": "^0.6.0",
|
|
53
53
|
"contentful": "^11.5.10",
|
|
54
54
|
"contentful-batch-libs": "^12.0.0",
|
|
55
|
-
"contentful-management": "^12.
|
|
55
|
+
"contentful-management": "^12.17.0",
|
|
56
56
|
"date-fns": "^4.1.0",
|
|
57
57
|
"figures": "^3.2.0",
|
|
58
58
|
"jsonwebtoken": "^9.0.0",
|