contentful-export 8.3.0 → 8.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.
- package/README.md +39 -1
- package/dist/index.js +30 -0
- package/dist/parseOptions.js +3 -0
- package/dist/tasks/get-space-data.js +112 -0
- package/dist/usageParams.js +8 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -186,7 +186,7 @@ Content Delivery API.
|
|
|
186
186
|
|
|
187
187
|
#### `includeArchived` [boolean] [default: false]
|
|
188
188
|
|
|
189
|
-
Include archived entries in the exported
|
|
189
|
+
Include archived entries and assets in the exported data. This option does not include archived Releases.
|
|
190
190
|
|
|
191
191
|
#### `skipContentModel` [boolean] [default: false]
|
|
192
192
|
|
|
@@ -212,6 +212,10 @@ Skip exporting tags
|
|
|
212
212
|
|
|
213
213
|
Skip exporting webhooks
|
|
214
214
|
|
|
215
|
+
#### `skipReleases` [boolean] [default: false]
|
|
216
|
+
|
|
217
|
+
Skip exporting [Releases](https://www.contentful.com/help/releases/). See the "Releases" section below for what's exported when this is on.
|
|
218
|
+
|
|
215
219
|
#### `stripTags` [boolean] [default: false]
|
|
216
220
|
|
|
217
221
|
Untag assets and entries
|
|
@@ -339,6 +343,7 @@ This is an overview of the exported data:
|
|
|
339
343
|
"webhooks": [],
|
|
340
344
|
"roles": [],
|
|
341
345
|
"editorInterfaces": [],
|
|
346
|
+
"releases": [],
|
|
342
347
|
"designTokens": [],
|
|
343
348
|
"components": [],
|
|
344
349
|
"experienceTemplates": [],
|
|
@@ -365,6 +370,8 @@ _Note:_ Tags feature is not available for all users. If you do not have access t
|
|
|
365
370
|
|
|
366
371
|
_Note:_ `designTokens`, `components`, `experienceTemplates`, `dataAssemblies`, `experienceFragments`, and `experiences` are Experience Orchestration (ExO) entities — present by default; absent only if you explicitly set `includeExperienceOrchestration: false` — see the "Experience Orchestration (ExO) entities" section below.
|
|
367
372
|
|
|
373
|
+
_Note:_ `releases` is present by default; absent only if you explicitly set `skipReleases: true` — see the "Releases" section below. It's a separate, GA Contentful feature, not part of ExO.
|
|
374
|
+
|
|
368
375
|
## :test_tube: Experience Orchestration (ExO) entities
|
|
369
376
|
|
|
370
377
|
Experience Orchestration (ExO) is Contentful's system for composing and rendering structured page experiences. It sits above the traditional entry/content-type layer and provides six dedicated entity types — Design Tokens, Components, Experience Templates, Data Assemblies, Experience Fragments, and Experiences — that together describe how content is fetched, assembled, and laid out.
|
|
@@ -389,10 +396,41 @@ If the source space has no ExO entities, or lacks the `exoM1` entitlement, each
|
|
|
389
396
|
|
|
390
397
|
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
398
|
|
|
399
|
+
### Optimization Variants
|
|
400
|
+
|
|
401
|
+
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`:
|
|
402
|
+
|
|
403
|
+
```javascript
|
|
404
|
+
const options = {
|
|
405
|
+
spaceId: '<space_id>',
|
|
406
|
+
managementToken: '<content_management_api_key>',
|
|
407
|
+
includeExperienceOrchestration: true,
|
|
408
|
+
includeExoVariants: true
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
await contentfulExport(options)
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
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.
|
|
415
|
+
|
|
392
416
|
### Round-tripping into `contentful-import`
|
|
393
417
|
|
|
394
418
|
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.
|
|
395
419
|
|
|
420
|
+
## :package: Releases
|
|
421
|
+
|
|
422
|
+
[Releases](https://www.contentful.com/help/releases/) (Timeline) is a separate, GA Contentful feature — not part of Experience Orchestration, and gated by its own organization entitlement rather than `exoM1`.
|
|
423
|
+
|
|
424
|
+
Releases export is on by default (`skipReleases: false`) — for the CLI and the module API alike. Pass `skipReleases: true` (`--skip-releases` on the CLI) to opt out.
|
|
425
|
+
|
|
426
|
+
Only releases with `sys.schemaVersion: "Release.v2"` ("Releases") are fetched. `Release.v1` ("Launch") releases are excluded by the export query itself, since [`contentful-import`](https://github.com/contentful/contentful-import) only supports `Release.v2` on the import side.
|
|
427
|
+
|
|
428
|
+
Only active Releases are exported. Archived Releases are omitted, regardless of `includeArchived`, because that option currently applies only to entries and assets.
|
|
429
|
+
|
|
430
|
+
If the source space's organization lacks the Releases entitlement, or the fetch otherwise fails, a `Skipping Releases export` warning is logged and `releases` exports as an empty array — it does not fail the export.
|
|
431
|
+
|
|
432
|
+
Round-trips into [`contentful-import`](https://github.com/contentful/contentful-import), which imports each release found in the exported data. See `contentful-import`'s README "Releases" section for the import-side details, including a real limitation worth knowing before you rely on this for repeated imports: Releases have no ID-preserving create, so re-importing the same export creates additional releases rather than updating existing ones.
|
|
433
|
+
|
|
396
434
|
## :warning: Limitations
|
|
397
435
|
|
|
398
436
|
- This tool currently does **not** support the export of space memberships.
|
package/dist/index.js
CHANGED
|
@@ -80,8 +80,10 @@ function runContentfulExport(params) {
|
|
|
80
80
|
skipWebhooks: options.skipWebhooks,
|
|
81
81
|
skipRoles: options.skipRoles,
|
|
82
82
|
skipTags: options.skipTags,
|
|
83
|
+
skipReleases: options.skipReleases,
|
|
83
84
|
stripTags: options.stripTags,
|
|
84
85
|
includeExperienceOrchestration: options.includeExperienceOrchestration,
|
|
86
|
+
includeExoVariants: options.includeExoVariants,
|
|
85
87
|
listrOptions,
|
|
86
88
|
queryEntries: options.queryEntries,
|
|
87
89
|
queryAssets: options.queryAssets
|
|
@@ -131,6 +133,7 @@ function runContentfulExport(params) {
|
|
|
131
133
|
}).then(ctx => {
|
|
132
134
|
const resultTypes = Object.keys(ctx.data);
|
|
133
135
|
if (resultTypes.length) {
|
|
136
|
+
var _ctx$data$experiences, _ctx$optimizationVari, _ctx$data$experienceF, _ctx$optimizationVari2;
|
|
134
137
|
const resultTable = new _cliTable.default(tableOptions);
|
|
135
138
|
resultTable.push([{
|
|
136
139
|
colSpan: 2,
|
|
@@ -139,6 +142,33 @@ function runContentfulExport(params) {
|
|
|
139
142
|
resultTypes.forEach(type => {
|
|
140
143
|
resultTable.push([(0, _lodash.default)(type), ctx.data[type].length]);
|
|
141
144
|
});
|
|
145
|
+
|
|
146
|
+
// Optimization Variants are nested onto their parent Experience/Experience
|
|
147
|
+
// Fragment rather than exported as their own top-level field, so their count
|
|
148
|
+
// isn't free from resultTypes above and is computed here instead. A per-parent
|
|
149
|
+
// fetch failure falls back to an empty optimizationVariants array, which is
|
|
150
|
+
// indistinguishable from a parent that genuinely has none -- so failures are
|
|
151
|
+
// surfaced as their own row rather than folded silently into the count.
|
|
152
|
+
if ((_ctx$data$experiences = ctx.data.experiences) !== null && _ctx$data$experiences !== void 0 && _ctx$data$experiences.some(experience => experience.optimizationVariants)) {
|
|
153
|
+
const count = ctx.data.experiences.reduce((sum, experience) => {
|
|
154
|
+
var _experience$optimizat, _experience$optimizat2;
|
|
155
|
+
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);
|
|
156
|
+
}, 0);
|
|
157
|
+
resultTable.push(['Experience Optimization Variants', count]);
|
|
158
|
+
}
|
|
159
|
+
if ((_ctx$optimizationVari = ctx.optimizationVariantFailures) !== null && _ctx$optimizationVari !== void 0 && _ctx$optimizationVari.experiences) {
|
|
160
|
+
resultTable.push(['Experience Optimization Variant Fetch Failures', ctx.optimizationVariantFailures.experiences]);
|
|
161
|
+
}
|
|
162
|
+
if ((_ctx$data$experienceF = ctx.data.experienceFragments) !== null && _ctx$data$experienceF !== void 0 && _ctx$data$experienceF.some(experienceFragment => experienceFragment.optimizationVariants)) {
|
|
163
|
+
const count = ctx.data.experienceFragments.reduce((sum, experienceFragment) => {
|
|
164
|
+
var _experienceFragment$o, _experienceFragment$o2;
|
|
165
|
+
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);
|
|
166
|
+
}, 0);
|
|
167
|
+
resultTable.push(['Experience Fragment Optimization Variants', count]);
|
|
168
|
+
}
|
|
169
|
+
if ((_ctx$optimizationVari2 = ctx.optimizationVariantFailures) !== null && _ctx$optimizationVari2 !== void 0 && _ctx$optimizationVari2.experienceFragments) {
|
|
170
|
+
resultTable.push(['Experience Fragment Optimization Variant Fetch Failures', ctx.optimizationVariantFailures.experienceFragments]);
|
|
171
|
+
}
|
|
142
172
|
console.log(resultTable.toString());
|
|
143
173
|
} else {
|
|
144
174
|
console.log('No data was exported');
|
package/dist/parseOptions.js
CHANGED
|
@@ -29,9 +29,11 @@ function parseOptions(params) {
|
|
|
29
29
|
skipAssets: false,
|
|
30
30
|
skipWebhooks: false,
|
|
31
31
|
skipTags: false,
|
|
32
|
+
skipReleases: false,
|
|
32
33
|
stripTags: false,
|
|
33
34
|
maxAllowedLimit: 1000,
|
|
34
35
|
includeExperienceOrchestration: true,
|
|
36
|
+
includeExoVariants: false,
|
|
35
37
|
saveFile: true,
|
|
36
38
|
useVerboseRenderer: false,
|
|
37
39
|
rawProxy: false
|
|
@@ -87,6 +89,7 @@ function parseOptions(params) {
|
|
|
87
89
|
options.skipRoles = true;
|
|
88
90
|
options.skipContentModel = true;
|
|
89
91
|
options.skipWebhooks = true;
|
|
92
|
+
options.skipReleases = true;
|
|
90
93
|
}
|
|
91
94
|
options.application = options.managementApplication || `contentful.export/${_package.version}`;
|
|
92
95
|
options.feature = options.managementFeature || 'library-export';
|
|
@@ -33,11 +33,13 @@ function getFullSourceSpace({
|
|
|
33
33
|
skipRoles,
|
|
34
34
|
skipEditorInterfaces,
|
|
35
35
|
skipTags,
|
|
36
|
+
skipReleases,
|
|
36
37
|
stripTags,
|
|
37
38
|
includeDrafts,
|
|
38
39
|
includeArchived,
|
|
39
40
|
maxAllowedLimit,
|
|
40
41
|
includeExperienceOrchestration,
|
|
42
|
+
includeExoVariants,
|
|
41
43
|
listrOptions,
|
|
42
44
|
queryEntries,
|
|
43
45
|
queryAssets
|
|
@@ -170,6 +172,29 @@ function getFullSourceSpace({
|
|
|
170
172
|
});
|
|
171
173
|
}),
|
|
172
174
|
skip: () => skipRoles || environmentId !== 'master' && 'Roles can only be exported from master environment'
|
|
175
|
+
}, {
|
|
176
|
+
title: 'Fetching Releases data',
|
|
177
|
+
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
178
|
+
try {
|
|
179
|
+
// Excludes releases created internally by other Contentful features (rather than by a
|
|
180
|
+
// user directly), so they don't leak into a customer's export as if they were
|
|
181
|
+
// user-authored Releases.
|
|
182
|
+
const releases = await cursorPagedGet(query => client.release.query({
|
|
183
|
+
environmentId,
|
|
184
|
+
spaceId,
|
|
185
|
+
query: _objectSpread({
|
|
186
|
+
'metadata.annotations.Contentful:Timeline.type[nin]': 'Staging,Hidden',
|
|
187
|
+
'sys.schemaVersion': 'Release.v2',
|
|
188
|
+
'sys.status[in]': 'active'
|
|
189
|
+
}, query)
|
|
190
|
+
}));
|
|
191
|
+
ctx.data.releases = releases;
|
|
192
|
+
} catch (err) {
|
|
193
|
+
_contentfulBatchLibs.logEmitter.emit('warning', `Skipping Releases export: ${err.message}`);
|
|
194
|
+
ctx.data.releases = [];
|
|
195
|
+
}
|
|
196
|
+
}),
|
|
197
|
+
skip: () => skipReleases
|
|
173
198
|
}, {
|
|
174
199
|
title: 'Fetching Design Tokens data',
|
|
175
200
|
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
@@ -260,6 +285,40 @@ function getFullSourceSpace({
|
|
|
260
285
|
}
|
|
261
286
|
}),
|
|
262
287
|
skip: () => !includeExperienceOrchestration
|
|
288
|
+
}, {
|
|
289
|
+
title: 'Fetching Experience Optimization Variants data',
|
|
290
|
+
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
291
|
+
const experienceFailureCount = await attachOptimizationVariants({
|
|
292
|
+
parents: ctx.data.experiences,
|
|
293
|
+
fetchFn: (parentId, query) => client.experienceVariant.getMany({
|
|
294
|
+
spaceId,
|
|
295
|
+
environmentId,
|
|
296
|
+
experienceId: parentId,
|
|
297
|
+
query
|
|
298
|
+
}),
|
|
299
|
+
entityLabel: 'Experience Optimization Variants'
|
|
300
|
+
});
|
|
301
|
+
ctx.optimizationVariantFailures = ctx.optimizationVariantFailures || {};
|
|
302
|
+
ctx.optimizationVariantFailures.experiences = experienceFailureCount;
|
|
303
|
+
}),
|
|
304
|
+
skip: () => !includeExperienceOrchestration || !includeExoVariants
|
|
305
|
+
}, {
|
|
306
|
+
title: 'Fetching Experience Fragment Optimization Variants data',
|
|
307
|
+
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
308
|
+
const experienceFragmentFailureCount = await attachOptimizationVariants({
|
|
309
|
+
parents: ctx.data.experienceFragments,
|
|
310
|
+
fetchFn: (parentId, query) => client.experienceFragmentVariant.getMany({
|
|
311
|
+
spaceId,
|
|
312
|
+
environmentId,
|
|
313
|
+
experienceFragmentId: parentId,
|
|
314
|
+
query
|
|
315
|
+
}),
|
|
316
|
+
entityLabel: 'Experience Fragment Optimization Variants'
|
|
317
|
+
});
|
|
318
|
+
ctx.optimizationVariantFailures = ctx.optimizationVariantFailures || {};
|
|
319
|
+
ctx.optimizationVariantFailures.experienceFragments = experienceFragmentFailureCount;
|
|
320
|
+
}),
|
|
321
|
+
skip: () => !includeExperienceOrchestration || !includeExoVariants
|
|
263
322
|
}], listrOptions);
|
|
264
323
|
}
|
|
265
324
|
function getEditorInterfaces(client, spaceId, environmentId, contentTypes) {
|
|
@@ -305,6 +364,59 @@ async function cursorPagedGet(fetchFn, entityLabel) {
|
|
|
305
364
|
return allItems;
|
|
306
365
|
}
|
|
307
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Fetches and attaches Optimization Variants onto each of a parent entity type's
|
|
369
|
+
* items (Experience or ExperienceFragment), mutating them in place as
|
|
370
|
+
* `parent.optimizationVariants`. Variants are nested onto their parent rather than
|
|
371
|
+
* exported as their own top-level field because a variant's `sys.id` is borrowed
|
|
372
|
+
* from its parent (not unique to the variant), so a flat array would collide the
|
|
373
|
+
* way none of the other ExO entity types do.
|
|
374
|
+
*
|
|
375
|
+
* Unlike the top-level ExO entities, the optimization_variants list endpoint is NOT
|
|
376
|
+
* cursor-paginated — it takes no limit/pageNext query params and its response has no
|
|
377
|
+
* total/pages fields, just { sys: { type: 'Array' }, items }. So this is a single
|
|
378
|
+
* request per parent, not a paging loop.
|
|
379
|
+
*
|
|
380
|
+
* The API always leads the list with a `sys.variantType: 'default'` item representing
|
|
381
|
+
* the parent's own base view (either a real record, or the baseline Experience/Fragment
|
|
382
|
+
* itself if none exists) — not a real personalization variant. That entity is already
|
|
383
|
+
* exported as its own top-level Experience/ExperienceFragment, so it's filtered out here
|
|
384
|
+
* to avoid double-counting it as a variant.
|
|
385
|
+
*
|
|
386
|
+
* Runs with bounded concurrency (mirrors getEditorInterfaces' pattern) since this
|
|
387
|
+
* issues one request per parent entity (N+1).
|
|
388
|
+
*
|
|
389
|
+
* Returns the count of parents whose variant fetch failed, so callers can
|
|
390
|
+
* surface that distinctly from a parent that genuinely has zero variants
|
|
391
|
+
* (both look identical as an empty `optimizationVariants` array otherwise).
|
|
392
|
+
*/
|
|
393
|
+
async function attachOptimizationVariants({
|
|
394
|
+
parents,
|
|
395
|
+
fetchFn,
|
|
396
|
+
entityLabel
|
|
397
|
+
}) {
|
|
398
|
+
if (!(parents !== null && parents !== void 0 && parents.length)) {
|
|
399
|
+
return 0;
|
|
400
|
+
}
|
|
401
|
+
let totalFetched = 0;
|
|
402
|
+
let failureCount = 0;
|
|
403
|
+
await _bluebird.default.map(parents, async parent => {
|
|
404
|
+
try {
|
|
405
|
+
const response = await fetchFn(parent.sys.id, {});
|
|
406
|
+
parent.optimizationVariants = response.items.filter(item => item.sys.variantType !== 'default');
|
|
407
|
+
totalFetched += parent.optimizationVariants.length;
|
|
408
|
+
_contentfulBatchLibs.logEmitter.emit('info', `Fetched ${totalFetched} ${entityLabel} items`);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
_contentfulBatchLibs.logEmitter.emit('warning', `Skipping ${entityLabel} export for ${parent.sys.id}: ${err.message}`);
|
|
411
|
+
parent.optimizationVariants = [];
|
|
412
|
+
failureCount++;
|
|
413
|
+
}
|
|
414
|
+
}, {
|
|
415
|
+
concurrency: 6
|
|
416
|
+
});
|
|
417
|
+
return failureCount;
|
|
418
|
+
}
|
|
419
|
+
|
|
308
420
|
/**
|
|
309
421
|
* Gets all roles. Roles are scheduled to switch from skip/limit to
|
|
310
422
|
* cursor-based (pages.next/pagePrev) pagination on Feb 15, 2027:
|
package/dist/usageParams.js
CHANGED
|
@@ -57,6 +57,10 @@ var _default = exports.default = _yargs.default.version(_package.default.version
|
|
|
57
57
|
describe: 'Skip exporting webhooks',
|
|
58
58
|
type: 'boolean',
|
|
59
59
|
default: false
|
|
60
|
+
}).option('skip-releases', {
|
|
61
|
+
describe: 'Skip exporting Releases',
|
|
62
|
+
type: 'boolean',
|
|
63
|
+
default: false
|
|
60
64
|
}).options('strip-tags', {
|
|
61
65
|
describe: 'Untag assets and entries',
|
|
62
66
|
type: 'boolean',
|
|
@@ -111,6 +115,10 @@ var _default = exports.default = _yargs.default.version(_package.default.version
|
|
|
111
115
|
describe: 'Include Experience Orchestration entities (Components, Experience Templates, Data Assemblies, Experience Fragments, Experiences, Design Tokens). Requires exo_m1 entitlement on the source space.',
|
|
112
116
|
type: 'boolean',
|
|
113
117
|
default: true
|
|
118
|
+
}).option('include-exo-variants', {
|
|
119
|
+
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.',
|
|
120
|
+
type: 'boolean',
|
|
121
|
+
default: false
|
|
114
122
|
}).option('header', {
|
|
115
123
|
alias: 'H',
|
|
116
124
|
type: 'string',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "contentful-export",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.5.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",
|