contentful-export 8.2.2 → 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 +30 -4
- package/dist/parseOptions.js +1 -0
- package/dist/tasks/get-space-data.js +244 -102
- package/dist/tasks/init-client.js +5 -9
- 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
|
@@ -18,9 +18,8 @@ var _formatDistance = require("date-fns/formatDistance");
|
|
|
18
18
|
var _contentfulBatchLibs = require("contentful-batch-libs");
|
|
19
19
|
var _downloadAssets = _interopRequireDefault(require("./tasks/download-assets"));
|
|
20
20
|
var _getSpaceData = _interopRequireDefault(require("./tasks/get-space-data"));
|
|
21
|
-
var _initClient =
|
|
21
|
+
var _initClient = _interopRequireDefault(require("./tasks/init-client"));
|
|
22
22
|
var _parseOptions = _interopRequireDefault(require("./parseOptions"));
|
|
23
|
-
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
24
23
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
25
24
|
const accessP = _bluebird.default.promisify(_fs.access);
|
|
26
25
|
const tableOptions = {
|
|
@@ -54,7 +53,6 @@ function runContentfulExport(params) {
|
|
|
54
53
|
try {
|
|
55
54
|
// CMA client
|
|
56
55
|
ctx.client = (0, _initClient.default)(options);
|
|
57
|
-
ctx.plainClient = (0, _initClient.initPlainClient)(options);
|
|
58
56
|
if (options.deliveryToken && !options.includeDrafts) {
|
|
59
57
|
// CDA client for fetching only public entries
|
|
60
58
|
ctx.cdaClient = (0, _initClient.default)(options, true);
|
|
@@ -69,7 +67,6 @@ function runContentfulExport(params) {
|
|
|
69
67
|
task: ctx => {
|
|
70
68
|
return (0, _getSpaceData.default)({
|
|
71
69
|
client: ctx.client,
|
|
72
|
-
plainClient: ctx.plainClient,
|
|
73
70
|
cdaClient: ctx.cdaClient,
|
|
74
71
|
spaceId: options.spaceId,
|
|
75
72
|
environmentId: options.environmentId,
|
|
@@ -85,6 +82,7 @@ function runContentfulExport(params) {
|
|
|
85
82
|
skipTags: options.skipTags,
|
|
86
83
|
stripTags: options.stripTags,
|
|
87
84
|
includeExperienceOrchestration: options.includeExperienceOrchestration,
|
|
85
|
+
includeExoVariants: options.includeExoVariants,
|
|
88
86
|
listrOptions,
|
|
89
87
|
queryEntries: options.queryEntries,
|
|
90
88
|
queryAssets: options.queryAssets
|
|
@@ -134,6 +132,7 @@ function runContentfulExport(params) {
|
|
|
134
132
|
}).then(ctx => {
|
|
135
133
|
const resultTypes = Object.keys(ctx.data);
|
|
136
134
|
if (resultTypes.length) {
|
|
135
|
+
var _ctx$data$experiences, _ctx$optimizationVari, _ctx$data$experienceF, _ctx$optimizationVari2;
|
|
137
136
|
const resultTable = new _cliTable.default(tableOptions);
|
|
138
137
|
resultTable.push([{
|
|
139
138
|
colSpan: 2,
|
|
@@ -142,6 +141,33 @@ function runContentfulExport(params) {
|
|
|
142
141
|
resultTypes.forEach(type => {
|
|
143
142
|
resultTable.push([(0, _lodash.default)(type), ctx.data[type].length]);
|
|
144
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
|
+
}
|
|
145
171
|
console.log(resultTable.toString());
|
|
146
172
|
} else {
|
|
147
173
|
console.log('No data was exported');
|
package/dist/parseOptions.js
CHANGED
|
@@ -9,6 +9,11 @@ var _contentfulBatchLibs = require("contentful-batch-libs");
|
|
|
9
9
|
var _listr = _interopRequireDefault(require("listr"));
|
|
10
10
|
var _listrVerboseRenderer = _interopRequireDefault(require("listr-verbose-renderer"));
|
|
11
11
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
13
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
14
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
15
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
16
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
12
17
|
const MAX_ALLOWED_LIMIT = 1000;
|
|
13
18
|
let pageLimit = MAX_ALLOWED_LIMIT;
|
|
14
19
|
|
|
@@ -18,7 +23,6 @@ let pageLimit = MAX_ALLOWED_LIMIT;
|
|
|
18
23
|
*/
|
|
19
24
|
function getFullSourceSpace({
|
|
20
25
|
client,
|
|
21
|
-
plainClient,
|
|
22
26
|
cdaClient,
|
|
23
27
|
spaceId,
|
|
24
28
|
environmentId = 'master',
|
|
@@ -34,6 +38,7 @@ function getFullSourceSpace({
|
|
|
34
38
|
includeArchived,
|
|
35
39
|
maxAllowedLimit,
|
|
36
40
|
includeExperienceOrchestration,
|
|
41
|
+
includeExoVariants,
|
|
37
42
|
listrOptions,
|
|
38
43
|
queryEntries,
|
|
39
44
|
queryAssets
|
|
@@ -43,22 +48,32 @@ function getFullSourceSpace({
|
|
|
43
48
|
renderer: _listrVerboseRenderer.default
|
|
44
49
|
};
|
|
45
50
|
return new _listr.default([{
|
|
46
|
-
title: 'Connecting to space',
|
|
47
|
-
task: (0, _contentfulBatchLibs.wrapTask)(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
title: 'Connecting to space/environment',
|
|
52
|
+
task: (0, _contentfulBatchLibs.wrapTask)(async () => {
|
|
53
|
+
try {
|
|
54
|
+
await client.space.get({
|
|
55
|
+
spaceId
|
|
56
|
+
});
|
|
57
|
+
} catch (err) {
|
|
58
|
+
throw new Error(`Unable to retrieve space ${spaceId}, please ensure the space exists and the token is valid. (${err.message})`);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
await client.environment.get({
|
|
62
|
+
spaceId,
|
|
63
|
+
environmentId
|
|
64
|
+
});
|
|
65
|
+
} catch (err) {
|
|
66
|
+
throw new Error(`Unable to retrieve environment ${environmentId}, please ensure the environment exists and the token is valid. (${err.message})`);
|
|
67
|
+
}
|
|
54
68
|
})
|
|
55
69
|
}, {
|
|
56
70
|
title: 'Fetching content types data',
|
|
57
71
|
task: (0, _contentfulBatchLibs.wrapTask)(ctx => {
|
|
58
|
-
return pagedGet({
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
return pagedGet(query => client.contentType.getMany({
|
|
73
|
+
spaceId,
|
|
74
|
+
environmentId,
|
|
75
|
+
query
|
|
76
|
+
})).then(extractItems).then(items => {
|
|
62
77
|
ctx.data.contentTypes = items;
|
|
63
78
|
});
|
|
64
79
|
}),
|
|
@@ -66,10 +81,11 @@ function getFullSourceSpace({
|
|
|
66
81
|
}, {
|
|
67
82
|
title: 'Fetching tags data',
|
|
68
83
|
task: (0, _contentfulBatchLibs.wrapTask)(ctx => {
|
|
69
|
-
return pagedGet({
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
84
|
+
return pagedGet(query => client.tag.getMany({
|
|
85
|
+
spaceId,
|
|
86
|
+
environmentId,
|
|
87
|
+
query
|
|
88
|
+
})).then(extractItems).then(items => {
|
|
73
89
|
ctx.data.tags = items;
|
|
74
90
|
}).catch(err => {
|
|
75
91
|
_contentfulBatchLibs.logEmitter.emit('error', new Error(`Fetching tags failed: ${err.message}`, {
|
|
@@ -82,7 +98,7 @@ function getFullSourceSpace({
|
|
|
82
98
|
}, {
|
|
83
99
|
title: 'Fetching editor interfaces data',
|
|
84
100
|
task: (0, _contentfulBatchLibs.wrapTask)(ctx => {
|
|
85
|
-
return getEditorInterfaces(ctx.data.contentTypes).then(editorInterfaces => {
|
|
101
|
+
return getEditorInterfaces(client, spaceId, environmentId, ctx.data.contentTypes).then(editorInterfaces => {
|
|
86
102
|
ctx.data.editorInterfaces = editorInterfaces.filter(editorInterface => {
|
|
87
103
|
return editorInterface !== null;
|
|
88
104
|
});
|
|
@@ -92,17 +108,17 @@ function getFullSourceSpace({
|
|
|
92
108
|
}, {
|
|
93
109
|
title: 'Fetching content entries data',
|
|
94
110
|
task: (0, _contentfulBatchLibs.wrapTask)(ctx => {
|
|
95
|
-
const source = (cdaClient === null || cdaClient === void 0 ? void 0 : cdaClient.withAllLocales) || ctx.environment;
|
|
96
111
|
if (cdaClient) {
|
|
97
112
|
// let's not fetch children when using Content Delivery API
|
|
98
113
|
queryEntries = queryEntries || {};
|
|
99
114
|
queryEntries.include = 0;
|
|
100
115
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
query
|
|
105
|
-
})
|
|
116
|
+
const fetchFn = cdaClient ? query => cdaClient.withAllLocales.getEntries(query) : query => client.entry.getMany({
|
|
117
|
+
spaceId,
|
|
118
|
+
environmentId,
|
|
119
|
+
query
|
|
120
|
+
});
|
|
121
|
+
return pagedGet(fetchFn, queryEntries).then(extractItems).then(items => filterDrafts(items, includeDrafts, cdaClient)).then(items => filterArchived(items, includeArchived)).then(items => removeTags(items, stripTags)).then(items => {
|
|
106
122
|
ctx.data.entries = items;
|
|
107
123
|
});
|
|
108
124
|
}),
|
|
@@ -110,13 +126,13 @@ function getFullSourceSpace({
|
|
|
110
126
|
}, {
|
|
111
127
|
title: 'Fetching assets data',
|
|
112
128
|
task: (0, _contentfulBatchLibs.wrapTask)(ctx => {
|
|
113
|
-
const source = (cdaClient === null || cdaClient === void 0 ? void 0 : cdaClient.withAllLocales) || ctx.environment;
|
|
114
129
|
queryAssets = queryAssets || {};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
query
|
|
119
|
-
})
|
|
130
|
+
const fetchFn = cdaClient ? query => cdaClient.withAllLocales.getAssets(query) : query => client.asset.getMany({
|
|
131
|
+
spaceId,
|
|
132
|
+
environmentId,
|
|
133
|
+
query
|
|
134
|
+
});
|
|
135
|
+
return pagedGet(fetchFn, queryAssets).then(extractItems).then(items => filterDrafts(items, includeDrafts, cdaClient)).then(items => filterArchived(items, includeArchived)).then(items => removeTags(items, stripTags)).then(items => {
|
|
120
136
|
ctx.data.assets = items;
|
|
121
137
|
});
|
|
122
138
|
}),
|
|
@@ -124,33 +140,34 @@ function getFullSourceSpace({
|
|
|
124
140
|
}, {
|
|
125
141
|
title: 'Fetching locales data',
|
|
126
142
|
task: (0, _contentfulBatchLibs.wrapTask)(ctx => {
|
|
127
|
-
return pagedGet({
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
143
|
+
return pagedGet(query => client.locale.getMany({
|
|
144
|
+
spaceId,
|
|
145
|
+
environmentId,
|
|
146
|
+
query
|
|
147
|
+
})).then(extractItems).then(items => {
|
|
131
148
|
ctx.data.locales = items;
|
|
132
149
|
});
|
|
133
150
|
}),
|
|
134
151
|
skip: () => skipContentModel
|
|
135
152
|
}, {
|
|
136
153
|
title: 'Fetching webhooks data',
|
|
137
|
-
task: (0, _contentfulBatchLibs.wrapTask)(ctx => {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
154
|
+
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
155
|
+
const webhooksResponse = await client.webhook.getMany({
|
|
156
|
+
query: {
|
|
157
|
+
// webhooks are capped to 100 per space
|
|
158
|
+
limit: 100
|
|
159
|
+
},
|
|
160
|
+
spaceId
|
|
143
161
|
});
|
|
162
|
+
ctx.data.webhooks = webhooksResponse.items;
|
|
144
163
|
}),
|
|
145
164
|
skip: () => skipWebhooks || environmentId !== 'master' && 'Webhooks can only be exported from master environment'
|
|
146
165
|
}, {
|
|
147
166
|
title: 'Fetching roles data',
|
|
148
|
-
task: (0, _contentfulBatchLibs.wrapTask)(ctx => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}).then(extractItems).then(items => {
|
|
153
|
-
ctx.data.roles = items;
|
|
167
|
+
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
168
|
+
ctx.data.roles = await pagedRolesGet({
|
|
169
|
+
client,
|
|
170
|
+
spaceId
|
|
154
171
|
});
|
|
155
172
|
}),
|
|
156
173
|
skip: () => skipRoles || environmentId !== 'master' && 'Roles can only be exported from master environment'
|
|
@@ -158,12 +175,11 @@ function getFullSourceSpace({
|
|
|
158
175
|
title: 'Fetching Design Tokens data',
|
|
159
176
|
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
160
177
|
try {
|
|
161
|
-
ctx.data.designTokens = await cursorPagedGet({
|
|
162
|
-
client: plainClient,
|
|
178
|
+
ctx.data.designTokens = await cursorPagedGet(query => client.designToken.getMany({
|
|
163
179
|
spaceId,
|
|
164
180
|
environmentId,
|
|
165
|
-
|
|
166
|
-
});
|
|
181
|
+
query
|
|
182
|
+
}), 'designToken');
|
|
167
183
|
} catch (err) {
|
|
168
184
|
_contentfulBatchLibs.logEmitter.emit('warning', `Skipping Design Tokens export: ${err.message}`);
|
|
169
185
|
ctx.data.designTokens = [];
|
|
@@ -174,12 +190,11 @@ function getFullSourceSpace({
|
|
|
174
190
|
title: 'Fetching Components data',
|
|
175
191
|
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
176
192
|
try {
|
|
177
|
-
ctx.data.components = await cursorPagedGet({
|
|
178
|
-
client: plainClient,
|
|
193
|
+
ctx.data.components = await cursorPagedGet(query => client.component.getMany({
|
|
179
194
|
spaceId,
|
|
180
195
|
environmentId,
|
|
181
|
-
|
|
182
|
-
});
|
|
196
|
+
query
|
|
197
|
+
}), 'component');
|
|
183
198
|
} catch (err) {
|
|
184
199
|
_contentfulBatchLibs.logEmitter.emit('warning', `Skipping Components export: ${err.message}`);
|
|
185
200
|
ctx.data.components = [];
|
|
@@ -190,12 +205,11 @@ function getFullSourceSpace({
|
|
|
190
205
|
title: 'Fetching Experience Templates data',
|
|
191
206
|
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
192
207
|
try {
|
|
193
|
-
ctx.data.experienceTemplates = await cursorPagedGet({
|
|
194
|
-
client: plainClient,
|
|
208
|
+
ctx.data.experienceTemplates = await cursorPagedGet(query => client.experienceTemplate.getMany({
|
|
195
209
|
spaceId,
|
|
196
210
|
environmentId,
|
|
197
|
-
|
|
198
|
-
});
|
|
211
|
+
query
|
|
212
|
+
}), 'experienceTemplate');
|
|
199
213
|
} catch (err) {
|
|
200
214
|
_contentfulBatchLibs.logEmitter.emit('warning', `Skipping Experience Templates export: ${err.message}`);
|
|
201
215
|
ctx.data.experienceTemplates = [];
|
|
@@ -206,12 +220,11 @@ function getFullSourceSpace({
|
|
|
206
220
|
title: 'Fetching Data Assemblies data',
|
|
207
221
|
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
208
222
|
try {
|
|
209
|
-
ctx.data.dataAssemblies = await cursorPagedGet({
|
|
210
|
-
client: plainClient,
|
|
223
|
+
ctx.data.dataAssemblies = await cursorPagedGet(query => client.dataAssembly.getMany({
|
|
211
224
|
spaceId,
|
|
212
225
|
environmentId,
|
|
213
|
-
|
|
214
|
-
});
|
|
226
|
+
query
|
|
227
|
+
}), 'dataAssembly');
|
|
215
228
|
} catch (err) {
|
|
216
229
|
_contentfulBatchLibs.logEmitter.emit('warning', `Skipping Data Assemblies export: ${err.message}`);
|
|
217
230
|
ctx.data.dataAssemblies = [];
|
|
@@ -222,12 +235,11 @@ function getFullSourceSpace({
|
|
|
222
235
|
title: 'Fetching Experience Fragments data',
|
|
223
236
|
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
224
237
|
try {
|
|
225
|
-
ctx.data.experienceFragments = await cursorPagedGet({
|
|
226
|
-
client: plainClient,
|
|
238
|
+
ctx.data.experienceFragments = await cursorPagedGet(query => client.experienceFragment.getMany({
|
|
227
239
|
spaceId,
|
|
228
240
|
environmentId,
|
|
229
|
-
|
|
230
|
-
});
|
|
241
|
+
query
|
|
242
|
+
}), 'experienceFragment');
|
|
231
243
|
} catch (err) {
|
|
232
244
|
_contentfulBatchLibs.logEmitter.emit('warning', `Skipping Experience Fragments export: ${err.message}`);
|
|
233
245
|
ctx.data.experienceFragments = [];
|
|
@@ -238,23 +250,60 @@ function getFullSourceSpace({
|
|
|
238
250
|
title: 'Fetching Experiences data',
|
|
239
251
|
task: (0, _contentfulBatchLibs.wrapTask)(async ctx => {
|
|
240
252
|
try {
|
|
241
|
-
ctx.data.experiences = await cursorPagedGet({
|
|
242
|
-
client: plainClient,
|
|
253
|
+
ctx.data.experiences = await cursorPagedGet(query => client.experience.getMany({
|
|
243
254
|
spaceId,
|
|
244
255
|
environmentId,
|
|
245
|
-
|
|
246
|
-
});
|
|
256
|
+
query
|
|
257
|
+
}), 'experience');
|
|
247
258
|
} catch (err) {
|
|
248
259
|
_contentfulBatchLibs.logEmitter.emit('warning', `Skipping Experiences export: ${err.message}`);
|
|
249
260
|
ctx.data.experiences = [];
|
|
250
261
|
}
|
|
251
262
|
}),
|
|
252
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
|
|
253
298
|
}], listrOptions);
|
|
254
299
|
}
|
|
255
|
-
function getEditorInterfaces(contentTypes) {
|
|
300
|
+
function getEditorInterfaces(client, spaceId, environmentId, contentTypes) {
|
|
256
301
|
return _bluebird.default.map(contentTypes, contentType => {
|
|
257
|
-
return
|
|
302
|
+
return client.editorInterface.get({
|
|
303
|
+
spaceId,
|
|
304
|
+
environmentId,
|
|
305
|
+
contentTypeId: contentType.sys.id
|
|
306
|
+
}).then(editorInterface => {
|
|
258
307
|
_contentfulBatchLibs.logEmitter.emit('info', `Fetched editor interface for ${contentType.name}`);
|
|
259
308
|
return editorInterface;
|
|
260
309
|
}).catch(() => {
|
|
@@ -272,55 +321,154 @@ function getEditorInterfaces(contentTypes) {
|
|
|
272
321
|
* Gets all ExO entities using cursor-based pagination (pageNext/pagePrev tokens).
|
|
273
322
|
* ExO list endpoints do not support skip-based pagination or the order param.
|
|
274
323
|
*/
|
|
275
|
-
async function cursorPagedGet({
|
|
276
|
-
client,
|
|
277
|
-
spaceId,
|
|
278
|
-
environmentId,
|
|
279
|
-
method
|
|
280
|
-
}) {
|
|
281
|
-
const [entity, operation] = method.split('.');
|
|
324
|
+
async function cursorPagedGet(fetchFn, entityLabel) {
|
|
282
325
|
const allItems = [];
|
|
283
326
|
let pageNext = null;
|
|
284
327
|
do {
|
|
285
328
|
var _response$pages$next, _response$pages;
|
|
286
329
|
const query = {
|
|
287
|
-
spaceId,
|
|
288
|
-
environmentId,
|
|
289
330
|
limit: pageLimit
|
|
290
331
|
};
|
|
291
332
|
if (pageNext) {
|
|
292
333
|
query.pageNext = pageNext;
|
|
293
334
|
}
|
|
294
|
-
const response = await
|
|
335
|
+
const response = await fetchFn(query);
|
|
295
336
|
allItems.push(...response.items);
|
|
296
|
-
_contentfulBatchLibs.logEmitter.emit('info', `Fetched ${allItems.length} ${
|
|
337
|
+
_contentfulBatchLibs.logEmitter.emit('info', `Fetched ${allItems.length} ${entityLabel} items`);
|
|
297
338
|
pageNext = (_response$pages$next = (_response$pages = response.pages) === null || _response$pages === void 0 ? void 0 : _response$pages.next) !== null && _response$pages$next !== void 0 ? _response$pages$next : null;
|
|
298
339
|
} while (pageNext);
|
|
299
340
|
return allItems;
|
|
300
341
|
}
|
|
301
342
|
|
|
302
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
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Gets all roles. Roles are scheduled to switch from skip/limit to
|
|
398
|
+
* cursor-based (pages.next/pagePrev) pagination on Feb 15, 2027:
|
|
399
|
+
* https://www.contentful.com/developers/api-changes/space-roles-collection-endpoints-update/
|
|
400
|
+
* Handles both shapes so this keeps working across that migration.
|
|
401
|
+
*/
|
|
402
|
+
async function pagedRolesGet({
|
|
403
|
+
client,
|
|
404
|
+
spaceId
|
|
405
|
+
}) {
|
|
406
|
+
const allItems = [];
|
|
407
|
+
const order = 'sys.createdAt,sys.id';
|
|
408
|
+
let response = await client.role.getMany({
|
|
409
|
+
spaceId,
|
|
410
|
+
query: {
|
|
411
|
+
limit: pageLimit,
|
|
412
|
+
order
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
allItems.push(...response.items);
|
|
416
|
+
_contentfulBatchLibs.logEmitter.emit('info', `Fetched ${allItems.length} roles`);
|
|
417
|
+
if (response.pages !== undefined) {
|
|
418
|
+
var _response$pages$next2, _response$pages2;
|
|
419
|
+
let pageNext = (_response$pages$next2 = (_response$pages2 = response.pages) === null || _response$pages2 === void 0 ? void 0 : _response$pages2.next) !== null && _response$pages$next2 !== void 0 ? _response$pages$next2 : null;
|
|
420
|
+
while (pageNext) {
|
|
421
|
+
var _response$pages$next3, _response$pages3;
|
|
422
|
+
response = await client.role.getMany({
|
|
423
|
+
spaceId,
|
|
424
|
+
query: {
|
|
425
|
+
limit: pageLimit,
|
|
426
|
+
order,
|
|
427
|
+
pageNext
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
allItems.push(...response.items);
|
|
431
|
+
_contentfulBatchLibs.logEmitter.emit('info', `Fetched ${allItems.length} roles`);
|
|
432
|
+
pageNext = (_response$pages$next3 = (_response$pages3 = response.pages) === null || _response$pages3 === void 0 ? void 0 : _response$pages3.next) !== null && _response$pages$next3 !== void 0 ? _response$pages$next3 : null;
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
let skip = allItems.length;
|
|
436
|
+
while (allItems.length < response.total && response.items.length > 0) {
|
|
437
|
+
response = await client.role.getMany({
|
|
438
|
+
spaceId,
|
|
439
|
+
query: {
|
|
440
|
+
limit: pageLimit,
|
|
441
|
+
order,
|
|
442
|
+
skip
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
allItems.push(...response.items);
|
|
446
|
+
_contentfulBatchLibs.logEmitter.emit('info', `Fetched ${allItems.length} roles`);
|
|
447
|
+
skip += response.items.length;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return allItems;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Recursively fetches all pages of a offset-based paginated CMA collection and returns
|
|
455
|
+
* them merged into a single response shaped like `{ items: [...], total, ... }`
|
|
456
|
+
*
|
|
303
457
|
* Gets all the existing entities based on pagination parameters.
|
|
304
458
|
* The first call will have no aggregated response. Subsequent calls will
|
|
305
459
|
* concatenate the new responses to the original one.
|
|
306
460
|
*/
|
|
307
|
-
function pagedGet({
|
|
308
|
-
|
|
309
|
-
method,
|
|
310
|
-
skip = 0,
|
|
311
|
-
aggregatedResponse = null,
|
|
312
|
-
query = null
|
|
313
|
-
}) {
|
|
314
|
-
const userQueryLimit = query && query.limit;
|
|
461
|
+
function pagedGet(fetchFn, userQuery = null, skip = 0, aggregatedResponse = null) {
|
|
462
|
+
const userQueryLimit = userQuery && userQuery.limit;
|
|
315
463
|
const fetchedTotal = aggregatedResponse && aggregatedResponse.items.length;
|
|
316
464
|
const limit = userQueryLimit ? Math.min(pageLimit, userQueryLimit - fetchedTotal) : pageLimit;
|
|
317
|
-
const
|
|
465
|
+
const query = _objectSpread(_objectSpread({
|
|
318
466
|
skip,
|
|
319
467
|
order: 'sys.createdAt,sys.id'
|
|
320
|
-
},
|
|
468
|
+
}, userQuery), {}, {
|
|
321
469
|
limit
|
|
322
470
|
});
|
|
323
|
-
return
|
|
471
|
+
return fetchFn(query).then(response => {
|
|
324
472
|
if (!aggregatedResponse) {
|
|
325
473
|
aggregatedResponse = response;
|
|
326
474
|
} else {
|
|
@@ -328,20 +476,14 @@ function pagedGet({
|
|
|
328
476
|
}
|
|
329
477
|
const totalItemsLength = aggregatedResponse.items.length;
|
|
330
478
|
const total = response.total;
|
|
331
|
-
logPagingStatus(response,
|
|
479
|
+
logPagingStatus(response, query, userQueryLimit);
|
|
332
480
|
const gotAllQueryLimitedItems = userQueryLimit && totalItemsLength >= userQueryLimit;
|
|
333
481
|
const gotAllItems = totalItemsLength >= total;
|
|
334
482
|
const gotNoItems = totalItemsLength <= 0;
|
|
335
483
|
if (gotAllQueryLimitedItems || gotAllItems || gotNoItems) {
|
|
336
484
|
return aggregatedResponse;
|
|
337
485
|
}
|
|
338
|
-
return pagedGet(
|
|
339
|
-
source,
|
|
340
|
-
method,
|
|
341
|
-
skip: skip + response.items.length,
|
|
342
|
-
aggregatedResponse,
|
|
343
|
-
query
|
|
344
|
-
});
|
|
486
|
+
return pagedGet(fetchFn, userQuery, skip + response.items.length, aggregatedResponse);
|
|
345
487
|
});
|
|
346
488
|
}
|
|
347
489
|
function logPagingStatus(response, requestQuery, userLimit) {
|
|
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = initClient;
|
|
7
|
-
exports.initPlainClient = initPlainClient;
|
|
8
7
|
var _contentful = require("contentful");
|
|
9
8
|
var _contentfulBatchLibs = require("contentful-batch-libs");
|
|
10
9
|
var _contentfulManagement = require("contentful-management");
|
|
@@ -32,13 +31,10 @@ function initClient(opts, useCda = false) {
|
|
|
32
31
|
return (0, _contentful.createClient)(cdaConfig).withoutLinkResolution;
|
|
33
32
|
}
|
|
34
33
|
return (0, _contentfulManagement.createClient)(config, {
|
|
35
|
-
|
|
34
|
+
defaults: {
|
|
35
|
+
spaceId: config.spaceId,
|
|
36
|
+
environmentId: config.environmentId
|
|
37
|
+
}
|
|
36
38
|
});
|
|
37
39
|
}
|
|
38
|
-
|
|
39
|
-
return (0, _contentfulManagement.createClient)({
|
|
40
|
-
accessToken: opts.managementToken,
|
|
41
|
-
host: opts.host,
|
|
42
|
-
logHandler
|
|
43
|
-
});
|
|
44
|
-
}
|
|
40
|
+
module.exports = exports.default;
|
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",
|