storyblok-backup 0.1.2 → 0.3.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/.env.example +2 -2
- package/README.md +34 -3
- package/bin/storyblok-backup.mjs +130 -119
- package/bin/storyblok-restore.mjs +54 -7
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
STORYBLOK_OAUTH_TOKEN=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
|
|
2
|
-
STORYBLOK_SPACE_ID=123456
|
|
1
|
+
STORYBLOK_OAUTH_TOKEN=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
|
|
2
|
+
STORYBLOK_SPACE_ID=123456
|
|
3
3
|
STORYBLOK_REGION=eu
|
package/README.md
CHANGED
|
@@ -34,7 +34,6 @@ The backup script will fetch the following resources of a Storyblok space using
|
|
|
34
34
|
The restore script is able to individually restore the resources from the backup files (via update or create) with the following exceptions:
|
|
35
35
|
|
|
36
36
|
- Assets: Only updating asset-resource-data is supported. Creating assets and updating asset-files is not supported.
|
|
37
|
-
- Tasks: Currently not supported due to missing fields returned from management API.
|
|
38
37
|
- Field types: Currently not supported
|
|
39
38
|
- Workflow stage changes: No update possible.
|
|
40
39
|
- Access Tokens: Creating access tokens from backup makes no sense, since it will result in a new token-string.
|
|
@@ -81,6 +80,29 @@ Call `npx storyblok-backup` with the following options:
|
|
|
81
80
|
- 'ca': Canada
|
|
82
81
|
- 'cn': China
|
|
83
82
|
Alternatively, you can set the STORYBLOK_REGION environment variable.
|
|
83
|
+
--types <types> Comma separated list of resource-types to backup. Defaults to all.
|
|
84
|
+
Possible values are:
|
|
85
|
+
- 'stories'
|
|
86
|
+
- 'collaborators'
|
|
87
|
+
- 'components'
|
|
88
|
+
- 'component-groups'
|
|
89
|
+
- 'assets'
|
|
90
|
+
- 'asset-folders'
|
|
91
|
+
- 'internal-tags'
|
|
92
|
+
- 'datasources'
|
|
93
|
+
- 'space'
|
|
94
|
+
- 'space-roles'
|
|
95
|
+
- 'tasks'
|
|
96
|
+
- 'activities'
|
|
97
|
+
- 'presets'
|
|
98
|
+
- 'field-types'
|
|
99
|
+
- 'webhooks'
|
|
100
|
+
- 'workflow-stages'
|
|
101
|
+
- 'workflow-stage-changes'
|
|
102
|
+
- 'workflows'
|
|
103
|
+
- 'releases'
|
|
104
|
+
- 'pipeline-branches'
|
|
105
|
+
- 'access-tokens'
|
|
84
106
|
--with-asset-files Downloads all files (assets) of the space. Defaults to false.
|
|
85
107
|
--output-dir <dir> Directory to write the backup to. Defaults to ./.output
|
|
86
108
|
(ATTENTION: Will fail if the directory already exists!)
|
|
@@ -108,7 +130,8 @@ This will create the folder `./.output/backup` and fetch all resources sorted in
|
|
|
108
130
|
npx storyblok-backup \
|
|
109
131
|
--token 1234567890abcdef \
|
|
110
132
|
--space 12345 \
|
|
111
|
-
--region ap
|
|
133
|
+
--region ap \
|
|
134
|
+
--types "stories,components" \
|
|
112
135
|
--with-asset-files \
|
|
113
136
|
--output-dir ./my-dir \
|
|
114
137
|
--force \
|
|
@@ -170,6 +193,8 @@ jobs:
|
|
|
170
193
|
with:
|
|
171
194
|
name: weekly-backup
|
|
172
195
|
path: .output
|
|
196
|
+
include-hidden-files: true
|
|
197
|
+
if-no-files-found: error
|
|
173
198
|
```
|
|
174
199
|
|
|
175
200
|
Make sure, to set the secrets `STORYBLOK_OAUTH_TOKEN` and `STORYBLOK_SPACE_ID` in your repository settings.
|
|
@@ -214,17 +239,22 @@ Call `npx storyblok-restore` with the following options:
|
|
|
214
239
|
- 'datasource-entries'
|
|
215
240
|
- 'space'
|
|
216
241
|
- 'space-role'
|
|
242
|
+
- 'task'
|
|
217
243
|
- 'preset'
|
|
218
244
|
- 'webhook'
|
|
219
245
|
- 'workflow'
|
|
220
246
|
- 'workflow-stage'
|
|
221
247
|
- 'release'
|
|
222
248
|
- 'pipeline-branch'
|
|
223
|
-
- 'access-token
|
|
249
|
+
- 'access-token'
|
|
224
250
|
--file <file> (required) File of resource to restore.
|
|
225
251
|
--publish Perform a publish after restore of a story (default=false).
|
|
226
252
|
--create Create a new resource instead of updating (default=false).
|
|
227
253
|
Not supported for assets.
|
|
254
|
+
--propagate Propagate new story UUID to referencing stories (default=false).
|
|
255
|
+
Usable with create and stories. A create results in a new ID and UUID.
|
|
256
|
+
This option will update all stories referencing the old
|
|
257
|
+
UUID (as stated in the backup-json) with the new one.
|
|
228
258
|
--id <file> (required if type=datasource-entries and create is set)
|
|
229
259
|
ID of datasource the entries belong to.
|
|
230
260
|
--verbose Will show detailed result of the restore process.
|
|
@@ -250,6 +280,7 @@ npx storyblok-restore \
|
|
|
250
280
|
--file ./.output/backup/123456789.json \
|
|
251
281
|
--publish \
|
|
252
282
|
--create \
|
|
283
|
+
--propagate \
|
|
253
284
|
--verbose
|
|
254
285
|
```
|
|
255
286
|
|
package/bin/storyblok-backup.mjs
CHANGED
|
@@ -13,6 +13,30 @@ const startTime = performance.now()
|
|
|
13
13
|
|
|
14
14
|
dotenvx.config({ quiet: true })
|
|
15
15
|
|
|
16
|
+
let resourceTypes = [
|
|
17
|
+
'stories',
|
|
18
|
+
'collaborators',
|
|
19
|
+
'components',
|
|
20
|
+
'component-groups',
|
|
21
|
+
'assets',
|
|
22
|
+
'asset-folders',
|
|
23
|
+
'internal-tags',
|
|
24
|
+
'datasources',
|
|
25
|
+
'space',
|
|
26
|
+
'space-roles',
|
|
27
|
+
'tasks',
|
|
28
|
+
'activities',
|
|
29
|
+
'presets',
|
|
30
|
+
'field-types',
|
|
31
|
+
'webhooks',
|
|
32
|
+
'workflow-stages',
|
|
33
|
+
'workflow-stage-changes',
|
|
34
|
+
'workflows',
|
|
35
|
+
'releases',
|
|
36
|
+
'pipeline-branches',
|
|
37
|
+
'access-tokens',
|
|
38
|
+
]
|
|
39
|
+
|
|
16
40
|
const args = minimist(process.argv.slice(2))
|
|
17
41
|
|
|
18
42
|
if ('help' in args) {
|
|
@@ -33,12 +57,15 @@ OPTIONS
|
|
|
33
57
|
- 'ca': Canada
|
|
34
58
|
- 'cn': China
|
|
35
59
|
Alternatively, you can set the STORYBLOK_REGION environment variable.
|
|
36
|
-
--
|
|
37
|
-
|
|
60
|
+
--types <types> Comma separated list of resource-types to backup (default=all).
|
|
61
|
+
Possible values are:
|
|
62
|
+
- '${resourceTypes.join("'\n - '")}'
|
|
63
|
+
--with-asset-files Downloads all files (assets) of the space (default=false).
|
|
64
|
+
--output-dir <dir> Directory to write the backup to (default=./.output)
|
|
38
65
|
(ATTENTION: Will fail if the directory already exists!)
|
|
39
66
|
--force Force deletion and recreation of existing output directory.
|
|
40
|
-
--create-zip Create a zip file of the backup
|
|
41
|
-
--zip-prefix <dir> Prefix for the zip file.
|
|
67
|
+
--create-zip Create a zip file of the backup (default=false).
|
|
68
|
+
--zip-prefix <dir> Prefix for the zip file. (default='backup').
|
|
42
69
|
(The suffix will automatically be the current date.)
|
|
43
70
|
--verbose Will show detailed output for every file written.
|
|
44
71
|
--help Show this help
|
|
@@ -51,6 +78,7 @@ MAXIMAL EXAMPLE
|
|
|
51
78
|
--token 1234567890abcdef \\
|
|
52
79
|
--space 12345 \\
|
|
53
80
|
--region ap \\
|
|
81
|
+
--types "stories,components" \\
|
|
54
82
|
--with-asset-files \\
|
|
55
83
|
--output-dir ./my-dir \\
|
|
56
84
|
--force \\
|
|
@@ -87,6 +115,19 @@ if ('region' in args || process.env.STORYBLOK_REGION) {
|
|
|
87
115
|
}
|
|
88
116
|
}
|
|
89
117
|
|
|
118
|
+
if ('types' in args) {
|
|
119
|
+
const typesToBackup = args.types.split(',')
|
|
120
|
+
for (const type of typesToBackup) {
|
|
121
|
+
if (!resourceTypes.includes(type)) {
|
|
122
|
+
console.log(
|
|
123
|
+
`Error: Invalid type parameter stated. "${type}" is not a valid resource type. Use --help to find out more.`
|
|
124
|
+
)
|
|
125
|
+
process.exit(1)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
resourceTypes = typesToBackup
|
|
129
|
+
}
|
|
130
|
+
|
|
90
131
|
const verbose = 'verbose' in args
|
|
91
132
|
|
|
92
133
|
const outputDir = args['output-dir'] || './.output'
|
|
@@ -132,51 +173,30 @@ if (fs.existsSync(outputDir)) {
|
|
|
132
173
|
fs.rmSync(outputDir, { recursive: true, force: true })
|
|
133
174
|
}
|
|
134
175
|
|
|
135
|
-
// Create output directories
|
|
176
|
+
// Create output directories (except for space, since this json will be saved in the root)
|
|
136
177
|
fs.mkdirSync(backupDir, { recursive: true })
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
'collaborators',
|
|
141
|
-
'components',
|
|
142
|
-
'component-groups',
|
|
143
|
-
'assets',
|
|
144
|
-
'asset-folders',
|
|
145
|
-
'internal-tags',
|
|
146
|
-
'datasources',
|
|
147
|
-
'space-roles',
|
|
148
|
-
'tasks',
|
|
149
|
-
'activities',
|
|
150
|
-
'presets',
|
|
151
|
-
'field-types',
|
|
152
|
-
'webhooks',
|
|
153
|
-
'workflow-stages',
|
|
154
|
-
'workflow-stage-changes',
|
|
155
|
-
'workflows',
|
|
156
|
-
'releases',
|
|
157
|
-
'pipeline-branches',
|
|
158
|
-
'access-tokens',
|
|
159
|
-
]
|
|
160
|
-
resources.forEach((resource) => fs.mkdirSync(`${backupDir}/${resource}`))
|
|
178
|
+
resourceTypes.forEach(
|
|
179
|
+
(resource) => resource === 'space' || fs.mkdirSync(`${backupDir}/${resource}`)
|
|
180
|
+
)
|
|
161
181
|
|
|
162
182
|
// Function to perform a default fetch
|
|
163
|
-
const defaultFetch = async (
|
|
164
|
-
|
|
165
|
-
.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
183
|
+
const defaultFetch = async (endpoint, type, fileField, fileFieldObject) => {
|
|
184
|
+
if (resourceTypes.includes(type)) {
|
|
185
|
+
console.log(`Fetching ${type}`)
|
|
186
|
+
await StoryblokMAPI.getAll(`spaces/${spaceId}/${endpoint}`)
|
|
187
|
+
.then((items) => {
|
|
188
|
+
items.forEach((item) =>
|
|
189
|
+
writeJson(
|
|
190
|
+
type,
|
|
191
|
+
fileFieldObject ? item[fileFieldObject][fileField] : item[fileField],
|
|
192
|
+
item
|
|
193
|
+
)
|
|
174
194
|
)
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
195
|
+
})
|
|
196
|
+
.catch((error) => {
|
|
197
|
+
throw error
|
|
198
|
+
})
|
|
199
|
+
}
|
|
180
200
|
}
|
|
181
201
|
|
|
182
202
|
// Function to write a file
|
|
@@ -204,138 +224,129 @@ const downloadFile = async (type, name, url) => {
|
|
|
204
224
|
}
|
|
205
225
|
|
|
206
226
|
// Fetch space info
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
227
|
+
if (resourceTypes.includes('space')) {
|
|
228
|
+
console.log(`Fetching space`)
|
|
229
|
+
await StoryblokMAPI.get(`spaces/${spaceId}/`)
|
|
230
|
+
.then((space) => {
|
|
231
|
+
writeJson(null, `space-${spaceId}`, space.data.space)
|
|
232
|
+
})
|
|
233
|
+
.catch((error) => {
|
|
234
|
+
throw error
|
|
235
|
+
})
|
|
236
|
+
}
|
|
215
237
|
|
|
216
238
|
// Fetch all stories
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
239
|
+
if (resourceTypes.includes('stories')) {
|
|
240
|
+
console.log(`Fetching stories`)
|
|
241
|
+
await StoryblokMAPI.getAll(`spaces/${spaceId}/stories`)
|
|
242
|
+
.then(async (stories) => {
|
|
243
|
+
for (const story of stories) {
|
|
244
|
+
await StoryblokMAPI.get(`spaces/${spaceId}/stories/${story.id}`)
|
|
245
|
+
.then((response) => {
|
|
246
|
+
delete response.data.story.preview_token
|
|
247
|
+
writeJson('stories', story.id, response.data.story)
|
|
248
|
+
})
|
|
249
|
+
.catch((error) => {
|
|
250
|
+
throw error
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
.catch((error) => {
|
|
255
|
+
throw error
|
|
256
|
+
})
|
|
257
|
+
}
|
|
234
258
|
|
|
235
259
|
// Fetch all collaborators
|
|
236
|
-
console.log(`Fetching collaborators`)
|
|
237
260
|
await defaultFetch('collaborators', 'collaborators', 'user_id')
|
|
238
261
|
|
|
239
262
|
// Fetch all components
|
|
240
|
-
console.log(`Fetching components`)
|
|
241
263
|
await defaultFetch('components', 'components', 'name')
|
|
242
264
|
|
|
243
265
|
// Fetch all component-groups
|
|
244
|
-
console.log(`Fetching component-groups`)
|
|
245
266
|
await defaultFetch('component_groups', 'component-groups', 'id')
|
|
246
267
|
|
|
247
268
|
// Fetch all assets (including files)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
.
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
269
|
+
if (resourceTypes.includes('assets')) {
|
|
270
|
+
console.log(`Fetching assets`)
|
|
271
|
+
await StoryblokMAPI.getAll(`spaces/${spaceId}/assets`)
|
|
272
|
+
.then(async (assets) => {
|
|
273
|
+
for (const asset of assets) {
|
|
274
|
+
writeJson('assets', asset.id, asset)
|
|
275
|
+
if ('with-asset-files' in args) {
|
|
276
|
+
const fileExtension = asset.filename.split('.').at(-1)
|
|
277
|
+
const fileName = asset.id + '.' + fileExtension
|
|
278
|
+
await downloadFile('assets', fileName, asset.filename)
|
|
279
|
+
}
|
|
257
280
|
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
281
|
+
})
|
|
282
|
+
.catch((error) => {
|
|
283
|
+
throw error
|
|
284
|
+
})
|
|
285
|
+
}
|
|
263
286
|
|
|
264
287
|
// Fetch all asset-folders
|
|
265
|
-
console.log(`Fetching asset-folders`)
|
|
266
288
|
await defaultFetch('asset_folders', 'asset-folders', 'id')
|
|
267
289
|
|
|
268
290
|
// Fetch all internal-tags
|
|
269
|
-
console.log(`Fetching internal-tags`)
|
|
270
291
|
await defaultFetch('internal_tags', 'internal-tags', 'id')
|
|
271
292
|
|
|
272
293
|
// Fetch all datasources (including entries)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
.then((dateSourceEntries) =>
|
|
282
|
-
writeJson('datasources', datasource.id + '_entries', dateSourceEntries)
|
|
283
|
-
)
|
|
284
|
-
.catch((error) => {
|
|
285
|
-
throw error
|
|
294
|
+
if (resourceTypes.includes('datasources')) {
|
|
295
|
+
console.log(`Fetching datasources`)
|
|
296
|
+
await StoryblokMAPI.getAll(`spaces/${spaceId}/datasources`)
|
|
297
|
+
.then(async (datasources) => {
|
|
298
|
+
for (const datasource of datasources) {
|
|
299
|
+
writeJson('datasources', datasource.id, datasource)
|
|
300
|
+
await StoryblokMAPI.getAll(`spaces/${spaceId}/datasource_entries`, {
|
|
301
|
+
datasource_id: datasource.id,
|
|
286
302
|
})
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
303
|
+
.then((dateSourceEntries) =>
|
|
304
|
+
writeJson('datasources', datasource.id + '_entries', dateSourceEntries)
|
|
305
|
+
)
|
|
306
|
+
.catch((error) => {
|
|
307
|
+
throw error
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
.catch((error) => {
|
|
312
|
+
throw error
|
|
313
|
+
})
|
|
314
|
+
}
|
|
292
315
|
|
|
293
316
|
// Fetch all space roles
|
|
294
|
-
console.log(`Fetching space roles`)
|
|
295
317
|
await defaultFetch('space_roles', 'space-roles', 'id')
|
|
296
318
|
|
|
297
319
|
// Fetch all tasks
|
|
298
|
-
console.log(`Fetching tasks`)
|
|
299
320
|
await defaultFetch('tasks', 'tasks', 'id')
|
|
300
321
|
|
|
301
322
|
// Fetch all activities
|
|
302
|
-
console.log(`Fetching activities`)
|
|
303
323
|
await defaultFetch('activities', 'activities', 'id', 'activity')
|
|
304
324
|
|
|
305
325
|
// Fetch all presets
|
|
306
|
-
console.log(`Fetching presets`)
|
|
307
326
|
await defaultFetch('presets', 'presets', 'id')
|
|
308
327
|
|
|
309
328
|
// Fetch all field-types
|
|
310
|
-
console.log(`Fetching field-types`)
|
|
311
329
|
await defaultFetch('field_types', 'field-types', 'name')
|
|
312
330
|
|
|
313
331
|
// Fetch all webhooks
|
|
314
|
-
console.log(`Fetching webhooks`)
|
|
315
332
|
await defaultFetch('webhook_endpoints', 'webhooks', 'id')
|
|
316
333
|
|
|
317
334
|
// Fetch all workflow-stages
|
|
318
|
-
console.log(`Fetching workflow-stages`)
|
|
319
335
|
await defaultFetch('workflow_stages', 'workflow-stages', 'id')
|
|
320
336
|
|
|
321
337
|
// Fetch all workflow-stage-changes
|
|
322
|
-
console.log(`Fetching workflow-stage-changes`)
|
|
323
338
|
await defaultFetch('workflow_stage_changes', 'workflow-stage-changes', 'id')
|
|
324
339
|
|
|
325
340
|
// Fetch all workflows
|
|
326
|
-
console.log(`Fetching workflows`)
|
|
327
341
|
await defaultFetch('workflows', 'workflows', 'id')
|
|
328
342
|
|
|
329
343
|
// Fetch all releases
|
|
330
|
-
console.log(`Fetching releases`)
|
|
331
344
|
await defaultFetch('releases', 'releases', 'id')
|
|
332
345
|
|
|
333
346
|
// Fetch all pipeline branches
|
|
334
|
-
console.log(`Fetching pipeline branches`)
|
|
335
347
|
await defaultFetch('branches', 'pipeline-branches', 'id')
|
|
336
348
|
|
|
337
349
|
// Fetch all access tokens
|
|
338
|
-
console.log(`Fetching access tokens`)
|
|
339
350
|
await defaultFetch('api_keys', 'access-tokens', 'id')
|
|
340
351
|
|
|
341
352
|
// Create zip file
|
|
@@ -22,7 +22,7 @@ const resourceTypes = [
|
|
|
22
22
|
'datasource-entries',
|
|
23
23
|
'space',
|
|
24
24
|
'space-role',
|
|
25
|
-
|
|
25
|
+
'task',
|
|
26
26
|
'preset',
|
|
27
27
|
// 'field-type',
|
|
28
28
|
'webhook',
|
|
@@ -54,11 +54,15 @@ OPTIONS
|
|
|
54
54
|
- 'cn': China
|
|
55
55
|
Alternatively, you can set the STORYBLOK_REGION environment variable.
|
|
56
56
|
--type <type> (required) Type of resource to restore. Possible values are:
|
|
57
|
-
- '${resourceTypes.join("'\n - '")}
|
|
57
|
+
- '${resourceTypes.join("'\n - '")}'
|
|
58
58
|
--file <file> (required) File of resource to restore.
|
|
59
59
|
--publish Perform a publish after restore of a story (default=false).
|
|
60
60
|
--create Create a new resource instead of updating (default=false).
|
|
61
61
|
Not supported for assets.
|
|
62
|
+
--propagate Propagate new story UUID to referencing stories (default=false).
|
|
63
|
+
Usable with create and stories. A create results in a new ID and UUID.
|
|
64
|
+
This option will update all stories referencing the old
|
|
65
|
+
UUID (as stated in the backup-json) with the new one.
|
|
62
66
|
--id <file> (required if type=datasource-entries and create is set)
|
|
63
67
|
ID of datasource the entries belong to.
|
|
64
68
|
--verbose Will show detailed result of the restore process.
|
|
@@ -76,6 +80,7 @@ MAXIMAL EXAMPLE
|
|
|
76
80
|
--file ./.output/backup/123456789.json \\
|
|
77
81
|
--publish \\
|
|
78
82
|
--create \\
|
|
83
|
+
--propagate \\
|
|
79
84
|
--verbose
|
|
80
85
|
`)
|
|
81
86
|
process.exit(0)
|
|
@@ -137,6 +142,18 @@ const publish = 'publish' in args
|
|
|
137
142
|
|
|
138
143
|
const create = 'create' in args
|
|
139
144
|
|
|
145
|
+
const propagate = 'propagate' in args
|
|
146
|
+
if (propagate && !create) {
|
|
147
|
+
console.log('Error: Propagate is only usable with create. Use --help to find out more.')
|
|
148
|
+
process.exit(1)
|
|
149
|
+
}
|
|
150
|
+
if (propagate && args.type !== 'story') {
|
|
151
|
+
console.log(
|
|
152
|
+
'Error: Propagate is only usable with story resources. Use --help to find out more.'
|
|
153
|
+
)
|
|
154
|
+
process.exit(1)
|
|
155
|
+
}
|
|
156
|
+
|
|
140
157
|
// Init Management API
|
|
141
158
|
const StoryblokMAPI = new StoryblokClient({
|
|
142
159
|
oauthToken: oauthToken,
|
|
@@ -144,7 +161,7 @@ const StoryblokMAPI = new StoryblokClient({
|
|
|
144
161
|
})
|
|
145
162
|
|
|
146
163
|
// Function to perform a default single resource restore
|
|
147
|
-
const defaultSingleRestore = async (type, id, params) => {
|
|
164
|
+
const defaultSingleRestore = async (type, id, params, forceUpdate) => {
|
|
148
165
|
if (publish) {
|
|
149
166
|
params.publish = 1
|
|
150
167
|
}
|
|
@@ -158,14 +175,15 @@ const defaultSingleRestore = async (type, id, params) => {
|
|
|
158
175
|
url = `${url}/${type}`
|
|
159
176
|
}
|
|
160
177
|
|
|
161
|
-
if (create) {
|
|
162
|
-
await StoryblokMAPI.post(url, params)
|
|
178
|
+
if (create && !forceUpdate) {
|
|
179
|
+
return await StoryblokMAPI.post(url, params)
|
|
163
180
|
.then((response) => {
|
|
164
181
|
console.log(`Created "${type}" resource.`)
|
|
165
182
|
if (verbose) {
|
|
166
183
|
console.log('Result:')
|
|
167
184
|
console.log(response.data)
|
|
168
185
|
}
|
|
186
|
+
return response.data
|
|
169
187
|
})
|
|
170
188
|
.catch((error) => {
|
|
171
189
|
throw error
|
|
@@ -174,13 +192,14 @@ const defaultSingleRestore = async (type, id, params) => {
|
|
|
174
192
|
if (id) {
|
|
175
193
|
url = `${url}/${id}`
|
|
176
194
|
}
|
|
177
|
-
await StoryblokMAPI.put(url, params)
|
|
195
|
+
return await StoryblokMAPI.put(url, params)
|
|
178
196
|
.then((response) => {
|
|
179
197
|
console.log(`Updated "${type}" resource with id "${id}".`)
|
|
180
198
|
if (verbose) {
|
|
181
199
|
console.log('Result:')
|
|
182
200
|
console.log(response.data)
|
|
183
201
|
}
|
|
202
|
+
return response.data
|
|
184
203
|
})
|
|
185
204
|
.catch((error) => {
|
|
186
205
|
throw error
|
|
@@ -193,7 +212,35 @@ const resource = JSON.parse(fs.readFileSync(args.file, 'utf8'))
|
|
|
193
212
|
switch (args.type) {
|
|
194
213
|
case 'story':
|
|
195
214
|
delete resource.updated_at
|
|
196
|
-
await defaultSingleRestore('stories', resource.id, { story: resource })
|
|
215
|
+
const result = await defaultSingleRestore('stories', resource.id, { story: resource })
|
|
216
|
+
if (create && propagate) {
|
|
217
|
+
const oldUuid = resource.uuid
|
|
218
|
+
const newUuid = result.story.uuid
|
|
219
|
+
const newId = result.story.id
|
|
220
|
+
console.log(`Propagating UUID change from "${oldUuid}" to "${newUuid}":`)
|
|
221
|
+
const referencingStories = await StoryblokMAPI.getAll(`spaces/${spaceId}/stories`, {
|
|
222
|
+
reference_search: oldUuid,
|
|
223
|
+
excluding_ids: [newId],
|
|
224
|
+
})
|
|
225
|
+
for (const referencingStory of referencingStories) {
|
|
226
|
+
const fullReferencingStoryResult = await StoryblokMAPI.get(
|
|
227
|
+
`spaces/${spaceId}/stories/${referencingStory.id}`
|
|
228
|
+
)
|
|
229
|
+
await defaultSingleRestore(
|
|
230
|
+
'stories',
|
|
231
|
+
referencingStory.id,
|
|
232
|
+
{
|
|
233
|
+
story: JSON.parse(
|
|
234
|
+
JSON.stringify(fullReferencingStoryResult.data.story).replaceAll(
|
|
235
|
+
oldUuid,
|
|
236
|
+
newUuid
|
|
237
|
+
)
|
|
238
|
+
),
|
|
239
|
+
},
|
|
240
|
+
true
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
197
244
|
break
|
|
198
245
|
case 'collaborator':
|
|
199
246
|
await defaultSingleRestore(
|
package/package.json
CHANGED