storyblok-backup 0.0.3 → 0.1.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 ADDED
@@ -0,0 +1,3 @@
1
+ STORYBLOK_OAUTH_TOKEN=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
2
+ STORYBLOK_SPACE_ID=123456
3
+ STORYBLOK_REGION=eu
package/README.md CHANGED
@@ -5,13 +5,17 @@
5
5
 
6
6
  A npx CLI tool to create a full backup of a space of the [Storyblok CMS](https://www.storyblok.com).
7
7
 
8
- The script will fetch the following resources of a Storyblok space using the Management API and archive them in a zip file:
8
+ A restore tool to restore (create or update) resources is also included.
9
+
10
+ The backup script will fetch the following resources of a Storyblok space using the Management API and archive them in a zip file:
9
11
 
10
12
  - Stories
13
+ - Collaborators
11
14
  - Components
12
15
  - Component groups
13
16
  - Assets (optionally incl. original files)
14
17
  - Asset folders
18
+ - Internal Tags
15
19
  - Datasources (incl. entries)
16
20
  - Space
17
21
  - Space Roles
@@ -19,17 +23,31 @@ The script will fetch the following resources of a Storyblok space using the Man
19
23
  - Activities
20
24
  - Presets
21
25
  - Field types
26
+ - Webhooks
27
+ - Workflows
22
28
  - Workflow stages
23
29
  - Workflow stage changes
24
- - Custom workflows
25
30
  - Releases
31
+ - Pipeline Branches
32
+ - Access Tokens
33
+
34
+ The restore script is able to individually restore the the resources from the backup files (via update or create) with the following exceptions:
35
+
36
+ - Assets: Creating assets is not supported
37
+ - Tasks: Currently not supported due to missing fields returned from management API
38
+ - Field types: Currently not supported
39
+ - Workflow stage changes: No update possible
40
+ - Access Tokens: Creating access tokens from backup makes no sense, since it will result in a new token-string.
26
41
 
27
42
  ## Installation
28
43
 
29
44
  ```shell
30
45
 
31
46
  # simply auto-download and run via npx
47
+ ## for backup:
32
48
  $ npx storyblok-backup
49
+ ## for restore:
50
+ $ npx -p storyblok-backup storyblok-restore
33
51
 
34
52
  # or install globally
35
53
  $ npm install -g storyblok-backup
@@ -46,15 +64,26 @@ $ pnpm add storyblok-backup
46
64
 
47
65
  ## Usage
48
66
 
67
+ ### Backup
68
+
49
69
  Call `npx storyblok-backup` with the following options:
50
70
 
51
- ### Options
71
+ #### Backup options
52
72
 
53
73
  ```text
54
74
  --token <token> (required) Personal OAuth access token created
55
75
  in the account settings of a Stoyblok user.
56
76
  (NOT the Access Token of a Space!)
77
+ Alternatively, you can set the STORYBLOK_OAUTH_TOKEN environment variable.
57
78
  --space <space_id> (required) ID of the space to backup
79
+ Alternatively, you can set the STORYBLOK_SPACE_ID environment variable.
80
+ --region <region> Region of the space. Possible values are:
81
+ - 'eu' (default): EU
82
+ - 'us': US
83
+ - 'ap': Australia
84
+ - 'ca': Canada
85
+ - 'cn': China
86
+ Alternatively, you can set the STORYBLOK_REGION environment variable.
58
87
  --with-asset-files Downloads all files (assets) of the space. Defaults to false.
59
88
  --output-dir <dir> Directory to write the backup to. Defaults to ./.output
60
89
  (ATTENTION: Will fail if the directory already exists!)
@@ -66,7 +95,9 @@ Call `npx storyblok-backup` with the following options:
66
95
  --help Show this help
67
96
  ```
68
97
 
69
- ### Minimal example
98
+ OAuth token, space-id and region can be set via environment variables. You can also use a `.env` file in your project root for this (see `.env.example`).
99
+
100
+ #### Minimal backup example
70
101
 
71
102
  ```shell
72
103
  npx storyblok-backup --token 1234567890abcdef --space 12345
@@ -74,12 +105,13 @@ npx storyblok-backup --token 1234567890abcdef --space 12345
74
105
 
75
106
  This will create the folder `./.output/backup` and fetch all resources sorted into folders.
76
107
 
77
- ### Maximal example
108
+ #### Maximal backup example
78
109
 
79
110
  ```shell
80
111
  npx storyblok-backup \
81
112
  --token 1234567890abcdef \
82
113
  --space 12345 \
114
+ --region ap \\
83
115
  --with-asset-files \
84
116
  --output-dir ./my-dir \
85
117
  --force \
@@ -90,7 +122,7 @@ npx storyblok-backup \
90
122
 
91
123
  This will create the folder `./my-dir/backup`, fetch all resources (incl. the original file assets) sorted into folders, zip them to `./my-dir/daily-Y-m-d-H-i-s.zip`, and log every written file to console.
92
124
 
93
- ## Continuous Integration
125
+ #### Continuous backup integration
94
126
 
95
127
  You can e.g. use this script to create periodic backups of Storyblok spaces using GitHub Actions and artifacts.
96
128
 
@@ -151,6 +183,79 @@ If you create multiple workflows for daily, weekly and monthly backups, by chang
151
183
 
152
184
  Also keep in mind, that there is a limit on artifact storage and runner minutes ([see GitHub docs](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#included-storage-and-minutes)).
153
185
 
186
+ ### Restore
187
+
188
+ Call `npx -p storyblok-backup storyblok-restore` with the following options:
189
+
190
+ #### Restore options
191
+
192
+ ```text
193
+ --token <token> (required) Personal OAuth access token created
194
+ in the account settings of a Stoyblok user.
195
+ (NOT the Access Token of a Space!)
196
+ Alternatively, you can set the STORYBLOK_OAUTH_TOKEN environment variable.
197
+ --space <space_id> (required) ID of the space to backup
198
+ Alternatively, you can set the STORYBLOK_SPACE_ID environment variable.
199
+ --region <region> Region of the space. Possible values are:
200
+ - 'eu' (default): EU
201
+ - 'us': US
202
+ - 'ap': Australia
203
+ - 'ca': Canada
204
+ - 'cn': China
205
+ Alternatively, you can set the STORYBLOK_REGION environment variable.
206
+ --type <type> (required) Type of resource to restore. Possible values are:
207
+ - 'story'
208
+ - 'collaborator'
209
+ - 'component'
210
+ - 'component-group'
211
+ - 'asset'
212
+ - 'asset-folder'
213
+ - 'internal-tag'
214
+ - 'datasource'
215
+ - 'datasource-entries'
216
+ - 'space'
217
+ - 'space-role'
218
+ - 'preset'
219
+ - 'webhook'
220
+ - 'workflow'
221
+ - 'workflow-stage'
222
+ - 'release'
223
+ - 'pipeline-branch'
224
+ - 'access-token
225
+ --file <file> (required) File of resource to restore.
226
+ --publish Perform a publish after restore of a story (default=false).
227
+ --create Create a new resource instead of updating (default=false).
228
+ Not supported for assets.
229
+ --id <file> (required if type=datasource-entries and create is set)
230
+ ID of datasource the entries belong to.
231
+ --verbose Will show detailed result of the restore process.
232
+ --help Show this help
233
+ ```
234
+
235
+ #### Minimal restore example
236
+
237
+ ```shell
238
+ npx -p storyblok-backup storyblok-restore --token 1234567890abcdef --space 12345 --type story --file ./.output/backup/123456789.json
239
+ ```
240
+
241
+ This will restore the story from the stated file by updating it.
242
+
243
+ #### Maximal restore example
244
+
245
+ ```shell
246
+ npx -p storyblok-backup storyblok-restore \
247
+ --token 1234567890abcdef \
248
+ --space 12345 \
249
+ --region ap \
250
+ --type story \
251
+ --file ./.output/backup/123456789.json \
252
+ --publish \
253
+ --create \
254
+ --verbose
255
+ ```
256
+
257
+ This will restore the story by creating a new story, immediately publish it, and log the API result to console.
258
+
154
259
  ## License
155
260
 
156
261
  This package is open-sourced software licensed under the [MIT license](https://github.com/webflorist/storyblok-backup/blob/main/LICENSE.).
@@ -7,9 +7,12 @@ import zipLib from 'zip-lib'
7
7
  import minimist from 'minimist'
8
8
  import StoryblokClient from 'storyblok-js-client'
9
9
  import { performance } from 'perf_hooks'
10
+ import dotenvx from '@dotenvx/dotenvx'
10
11
 
11
12
  const startTime = performance.now()
12
13
 
14
+ dotenvx.config({ quiet: true })
15
+
13
16
  const args = minimist(process.argv.slice(2))
14
17
 
15
18
  if ('help' in args) {
@@ -20,7 +23,16 @@ OPTIONS
20
23
  --token <token> (required) Personal OAuth access token created
21
24
  in the account settings of a Stoyblok user.
22
25
  (NOT the Access Token of a Space!)
26
+ Alternatively, you can set the STORYBLOK_OAUTH_TOKEN environment variable.
23
27
  --space <space_id> (required) ID of the space to backup
28
+ Alternatively, you can set the STORYBLOK_SPACE_ID environment variable.
29
+ --region <region> Region of the space. Possible values are:
30
+ - 'eu' (default): EU
31
+ - 'us': US
32
+ - 'ap': Australia
33
+ - 'ca': Canada
34
+ - 'cn': China
35
+ Alternatively, you can set the STORYBLOK_REGION environment variable.
24
36
  --with-asset-files Downloads all files (assets) of the space. Defaults to false.
25
37
  --output-dir <dir> Directory to write the backup to. Defaults to ./.output
26
38
  (ATTENTION: Will fail if the directory already exists!)
@@ -38,8 +50,9 @@ MAXIMAL EXAMPLE
38
50
  $ npx storyblok-backup \\
39
51
  --token 1234567890abcdef \\
40
52
  --space 12345 \\
53
+ --region ap \\
41
54
  --with-asset-files \\
42
- --output-dir ./backup \\
55
+ --output-dir ./my-dir \\
43
56
  --force \\
44
57
  --create-zip \\
45
58
  --zip-prefix daily \\
@@ -48,17 +61,31 @@ MAXIMAL EXAMPLE
48
61
  process.exit(0)
49
62
  }
50
63
 
51
- if (!('token' in args)) {
64
+ if (!('token' in args) && !process.env.STORYBLOK_OAUTH_TOKEN) {
52
65
  console.log(
53
- 'Error: State your oauth token via the --token argument. Use --help to find out more.'
66
+ 'Error: State your oauth token via the --token argument or the environment variable STORYBLOK_OAUTH_TOKEN. Use --help to find out more.'
54
67
  )
55
68
  process.exit(1)
56
69
  }
70
+ const oauthToken = args.token || process.env.STORYBLOK_OAUTH_TOKEN
57
71
 
58
- if (!('space' in args)) {
59
- console.log('Error: State your space id via the --space argument. Use --help to find out more.')
72
+ if (!('space' in args) && !process.env.STORYBLOK_SPACE_ID) {
73
+ console.log(
74
+ 'Error: State your space id via the --space argument or the environment variable STORYBLOK_SPACE_ID. Use --help to find out more.'
75
+ )
60
76
  process.exit(1)
61
77
  }
78
+ const spaceId = args.space || process.env.STORYBLOK_SPACE_ID
79
+
80
+ let region = 'eu'
81
+ if ('region' in args || process.env.STORYBLOK_REGION) {
82
+ region = args.region || process.env.STORYBLOK_REGION
83
+
84
+ if (!['eu', 'us', 'ap', 'ca', 'cn'].includes(region)) {
85
+ console.log('Error: Invalid region parameter stated. Use --help to find out more.')
86
+ process.exit(1)
87
+ }
88
+ }
62
89
 
63
90
  const verbose = 'verbose' in args
64
91
 
@@ -73,8 +100,6 @@ if (fs.existsSync(outputDir) && !('force' in args)) {
73
100
  process.exit(1)
74
101
  }
75
102
 
76
- const spaceId = args.space
77
-
78
103
  const filePrefix = args['zip-prefix'] || 'backup'
79
104
 
80
105
  const fileName =
@@ -98,7 +123,8 @@ if ('create-zip' in args) {
98
123
 
99
124
  // Init Management API
100
125
  const StoryblokMAPI = new StoryblokClient({
101
- oauthToken: args.token,
126
+ oauthToken: oauthToken,
127
+ region: region,
102
128
  })
103
129
 
104
130
  // Remove existing output directory
@@ -111,20 +137,25 @@ fs.mkdirSync(backupDir, { recursive: true })
111
137
 
112
138
  const resources = [
113
139
  'stories',
140
+ 'collaborators',
114
141
  'components',
115
142
  'component-groups',
116
143
  'assets',
117
144
  'asset-folders',
145
+ 'internal-tags',
118
146
  'datasources',
119
147
  'space-roles',
120
148
  'tasks',
121
149
  'activities',
122
150
  'presets',
123
151
  'field-types',
152
+ 'webhooks',
124
153
  'workflow-stages',
125
154
  'workflow-stage-changes',
126
155
  'workflows',
127
156
  'releases',
157
+ 'pipeline-branches',
158
+ 'access-tokens',
128
159
  ]
129
160
  resources.forEach((resource) => fs.mkdirSync(`${backupDir}/${resource}`))
130
161
 
@@ -155,7 +186,7 @@ const writeJson = (folder, file, content) => {
155
186
  outputFile += `/${folder}`
156
187
  }
157
188
  outputFile += `/${file}.json`
158
- fs.writeFile(outputFile, JSON.stringify(content), (error) => {
189
+ fs.writeFileSync(outputFile, JSON.stringify(content, null, 2), (error) => {
159
190
  if (error) {
160
191
  throw error
161
192
  }
@@ -188,7 +219,10 @@ await StoryblokMAPI.getAll(`spaces/${spaceId}/stories`)
188
219
  .then(async (stories) => {
189
220
  for (const story of stories) {
190
221
  await StoryblokMAPI.get(`spaces/${spaceId}/stories/${story.id}`)
191
- .then((response) => writeJson('stories', story.id, response.data.story))
222
+ .then((response) => {
223
+ delete response.data.story.preview_token
224
+ writeJson('stories', story.id, response.data.story)
225
+ })
192
226
  .catch((error) => {
193
227
  throw error
194
228
  })
@@ -198,6 +232,10 @@ await StoryblokMAPI.getAll(`spaces/${spaceId}/stories`)
198
232
  throw error
199
233
  })
200
234
 
235
+ // Fetch all collaborators
236
+ console.log(`Fetching collaborators`)
237
+ await defaultFetch('collaborators', 'collaborators', 'user_id')
238
+
201
239
  // Fetch all components
202
240
  console.log(`Fetching components`)
203
241
  await defaultFetch('components', 'components', 'name')
@@ -227,6 +265,10 @@ await StoryblokMAPI.getAll(`spaces/${spaceId}/assets`)
227
265
  console.log(`Fetching asset-folders`)
228
266
  await defaultFetch('asset_folders', 'asset-folders', 'id')
229
267
 
268
+ // Fetch all internal-tags
269
+ console.log(`Fetching internal-tags`)
270
+ await defaultFetch('internal_tags', 'internal-tags', 'id')
271
+
230
272
  // Fetch all datasources (including entries)
231
273
  console.log(`Fetching datasources`)
232
274
  await StoryblokMAPI.getAll(`spaces/${spaceId}/datasources`)
@@ -268,6 +310,10 @@ await defaultFetch('presets', 'presets', 'id')
268
310
  console.log(`Fetching field-types`)
269
311
  await defaultFetch('field_types', 'field-types', 'name')
270
312
 
313
+ // Fetch all webhooks
314
+ console.log(`Fetching webhooks`)
315
+ await defaultFetch('webhook_endpoints', 'webhooks', 'id')
316
+
271
317
  // Fetch all workflow-stages
272
318
  console.log(`Fetching workflow-stages`)
273
319
  await defaultFetch('workflow_stages', 'workflow-stages', 'id')
@@ -284,6 +330,14 @@ await defaultFetch('workflows', 'workflows', 'id')
284
330
  console.log(`Fetching releases`)
285
331
  await defaultFetch('releases', 'releases', 'id')
286
332
 
333
+ // Fetch all pipeline branches
334
+ console.log(`Fetching pipeline branches`)
335
+ await defaultFetch('branches', 'pipeline-branches', 'id')
336
+
337
+ // Fetch all access tokens
338
+ console.log(`Fetching access tokens`)
339
+ await defaultFetch('api_keys', 'access-tokens', 'id')
340
+
287
341
  // Create zip file
288
342
  if ('create-zip' in args) {
289
343
  console.log(`Creating zip file`)
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ import fs from 'fs'
4
+ import minimist from 'minimist'
5
+ import StoryblokClient from 'storyblok-js-client'
6
+ import { performance } from 'perf_hooks'
7
+ import dotenvx from '@dotenvx/dotenvx'
8
+
9
+ const startTime = performance.now()
10
+
11
+ dotenvx.config({ quiet: true })
12
+
13
+ const resourceTypes = [
14
+ 'story',
15
+ 'collaborator',
16
+ 'component',
17
+ 'component-group',
18
+ 'asset',
19
+ 'asset-folder',
20
+ 'internal-tag',
21
+ 'datasource',
22
+ 'datasource-entries',
23
+ 'space',
24
+ 'space-role',
25
+ //'task', // Currently not supported due to missing fields returned from management API
26
+ 'preset',
27
+ // 'field-type',
28
+ 'webhook',
29
+ 'workflow',
30
+ 'workflow-stage',
31
+ 'release',
32
+ 'pipeline-branch',
33
+ 'access-token',
34
+ ]
35
+
36
+ const args = minimist(process.argv.slice(2))
37
+
38
+ if ('help' in args) {
39
+ console.log(`USAGE
40
+ $ npx -p storyblok-backup storyblok-restore
41
+
42
+ OPTIONS
43
+ --token <token> (required) Personal OAuth access token created
44
+ in the account settings of a Stoyblok user.
45
+ (NOT the Access Token of a Space!)
46
+ Alternatively, you can set the STORYBLOK_OAUTH_TOKEN environment variable.
47
+ --space <space_id> (required) ID of the space to backup
48
+ Alternatively, you can set the STORYBLOK_SPACE_ID environment variable.
49
+ --region <region> Region of the space. Possible values are:
50
+ - 'eu' (default): EU
51
+ - 'us': US
52
+ - 'ap': Australia
53
+ - 'ca': Canada
54
+ - 'cn': China
55
+ Alternatively, you can set the STORYBLOK_REGION environment variable.
56
+ --type <type> (required) Type of resource to restore. Possible values are:
57
+ - '${resourceTypes.join("'\n - '")}
58
+ --file <file> (required) File of resource to restore.
59
+ --publish Perform a publish after restore of a story (default=false).
60
+ --create Create a new resource instead of updating (default=false).
61
+ Not supported for assets.
62
+ --id <file> (required if type=datasource-entries and create is set)
63
+ ID of datasource the entries belong to.
64
+ --verbose Will show detailed result of the restore process.
65
+ --help Show this help
66
+
67
+ MINIMAL EXAMPLE
68
+ $ npx -p storyblok-backup storyblok-restore --token 1234567890abcdef --space 12345 --type story --file ./.output/backup/123456789.json
69
+
70
+ MAXIMAL EXAMPLE
71
+ $ npx -p storyblok-backup storyblok-restore \\
72
+ --token 1234567890abcdef \\
73
+ --space 12345 \\
74
+ --region ap \\
75
+ --type story \\
76
+ --file ./.output/backup/123456789.json \\
77
+ --publish \\
78
+ --create \\
79
+ --verbose
80
+ `)
81
+ process.exit(0)
82
+ }
83
+
84
+ if (!('token' in args) && !process.env.STORYBLOK_OAUTH_TOKEN) {
85
+ console.log(
86
+ 'Error: State your oauth token via the --token argument or the environment variable STORYBLOK_OAUTH_TOKEN. Use --help to find out more.'
87
+ )
88
+ process.exit(1)
89
+ }
90
+ const oauthToken = args.token || process.env.STORYBLOK_OAUTH_TOKEN
91
+
92
+ if (!('space' in args) && !process.env.STORYBLOK_SPACE_ID) {
93
+ console.log(
94
+ 'Error: State your space id via the --space argument or the environment variable STORYBLOK_SPACE_ID. Use --help to find out more.'
95
+ )
96
+ process.exit(1)
97
+ }
98
+ const spaceId = args.space || process.env.STORYBLOK_SPACE_ID
99
+
100
+ let region = 'eu'
101
+ if ('region' in args || process.env.STORYBLOK_REGION) {
102
+ region = args.region || process.env.STORYBLOK_REGION
103
+
104
+ if (!['eu', 'us', 'ap', 'ca', 'cn'].includes(region)) {
105
+ console.log('Error: Invalid region parameter stated. Use --help to find out more.')
106
+ process.exit(1)
107
+ }
108
+ }
109
+
110
+ if (!('type' in args)) {
111
+ console.log(
112
+ 'Error: State the resource type to restore via the --type argument. Use --help to find out more.'
113
+ )
114
+ process.exit(1)
115
+ }
116
+
117
+ if (!resourceTypes.includes(args.type)) {
118
+ console.log(`Error: Invalid resource type "${args.type}". Use --help to find out more.`)
119
+ process.exit(1)
120
+ }
121
+
122
+ if (!('file' in args)) {
123
+ console.log(
124
+ 'Error: State the resource file to restore via the --file argument. Use --help to find out more.'
125
+ )
126
+ process.exit(1)
127
+ }
128
+
129
+ if (!fs.existsSync(args.file)) {
130
+ console.log(`Error: Stated file "${args.file}" does not exist.`)
131
+ process.exit(1)
132
+ }
133
+
134
+ const verbose = 'verbose' in args
135
+
136
+ const publish = 'publish' in args
137
+
138
+ const create = 'create' in args
139
+
140
+ // Init Management API
141
+ const StoryblokMAPI = new StoryblokClient({
142
+ oauthToken: oauthToken,
143
+ region: region,
144
+ })
145
+
146
+ // Function to perform a default single resource restore
147
+ const defaultSingleRestore = async (type, id, params) => {
148
+ if (publish) {
149
+ params.publish = 1
150
+ }
151
+ let url = `spaces`
152
+
153
+ if (!(create && args.type === 'space')) {
154
+ url = `${url}/${spaceId}`
155
+ }
156
+
157
+ if (type) {
158
+ url = `${url}/${type}`
159
+ }
160
+
161
+ if (create) {
162
+ await StoryblokMAPI.post(url, params)
163
+ .then((response) => {
164
+ console.log(`Created "${type}" resource.`)
165
+ if (verbose) {
166
+ console.log('Result:')
167
+ console.log(response.data)
168
+ }
169
+ })
170
+ .catch((error) => {
171
+ throw error
172
+ })
173
+ } else {
174
+ if (id) {
175
+ url = `${url}/${id}`
176
+ }
177
+ await StoryblokMAPI.put(url, params)
178
+ .then((response) => {
179
+ console.log(`Updated "${type}" resource with id "${id}".`)
180
+ if (verbose) {
181
+ console.log('Result:')
182
+ console.log(response.data)
183
+ }
184
+ })
185
+ .catch((error) => {
186
+ throw error
187
+ })
188
+ }
189
+ }
190
+
191
+ const resource = JSON.parse(fs.readFileSync(args.file, 'utf8'))
192
+
193
+ switch (args.type) {
194
+ case 'story':
195
+ delete resource.updated_at
196
+ await defaultSingleRestore('stories', resource.id, { story: resource })
197
+ break
198
+ case 'collaborator':
199
+ await defaultSingleRestore(
200
+ 'collaborators',
201
+ resource.id,
202
+ create
203
+ ? {
204
+ email: resource.user.userid,
205
+ role: resource.role,
206
+ space_id: resource.space_id,
207
+ permissions: resource.permissions,
208
+ space_role_ids: resource.space_role_ids,
209
+ allow_multiple_roles_creation: resource.role === 'multi',
210
+ }
211
+ : { collaborator: resource }
212
+ )
213
+ break
214
+ case 'component':
215
+ await defaultSingleRestore('components', resource.id, { component: resource })
216
+ break
217
+ case 'component-group':
218
+ await defaultSingleRestore('component_groups', resource.id, { component_group: resource })
219
+ break
220
+ case 'asset':
221
+ if (create) {
222
+ console.log('Error: Creating assets is not supported.')
223
+ process.exit(1)
224
+ }
225
+ await defaultSingleRestore('assets', resource.id, { asset: resource })
226
+ break
227
+ case 'asset-folder':
228
+ await defaultSingleRestore('asset_folders', resource.id, { asset_folder: resource })
229
+ break
230
+ case 'internal-tag':
231
+ await defaultSingleRestore('internal_tags', resource.id, { internal_tag: resource })
232
+ break
233
+ case 'datasource':
234
+ await defaultSingleRestore('datasources', resource.id, { datasource: resource })
235
+ break
236
+ case 'datasource-entries':
237
+ if (create && !('id' in args)) {
238
+ console.log(
239
+ 'Error: State the datasource ID via the --id argument. Use --help to find out more.'
240
+ )
241
+ process.exit(1)
242
+ }
243
+ for (const entry of resource) {
244
+ if (create) {
245
+ entry.datasource_id = args.id
246
+ }
247
+ await defaultSingleRestore('datasource_entries', entry.id, {
248
+ datasource_entry: entry,
249
+ })
250
+ }
251
+ break
252
+ case 'space':
253
+ await defaultSingleRestore(null, null, { space: resource })
254
+ break
255
+ case 'space-role':
256
+ await defaultSingleRestore('space_roles', resource.id, { space_role: resource })
257
+ break
258
+ case 'task':
259
+ await defaultSingleRestore('tasks', resource.id, { task: resource })
260
+ break
261
+ case 'preset':
262
+ await defaultSingleRestore('presets', resource.id, { preset: resource })
263
+ break
264
+ case 'webhook':
265
+ await defaultSingleRestore('webhook_endpoints', resource.id, { webhook_endpoint: resource })
266
+ break
267
+ case 'workflow':
268
+ await defaultSingleRestore('workflows', resource.id, { workflow: resource })
269
+ break
270
+ case 'workflow-stage':
271
+ await defaultSingleRestore('workflow_stages', resource.id, { workflow_stage: resource })
272
+ break
273
+ case 'release':
274
+ await defaultSingleRestore('releases', resource.id, { release: resource })
275
+ break
276
+ case 'pipeline-branch':
277
+ await defaultSingleRestore('branches', resource.id, { branch: resource })
278
+ break
279
+ case 'access-token':
280
+ if (create) {
281
+ console.log(
282
+ 'Error: Creating access-tokens from backup is not possible, since it will result in a new token.'
283
+ )
284
+ process.exit(1)
285
+ }
286
+ await defaultSingleRestore('api_keys', resource.id, { api_key: resource })
287
+ break
288
+ }
289
+
290
+ const endTime = performance.now()
291
+
292
+ console.log(`Restore successful in ${Math.round((endTime - startTime) / 1000)} seconds.`)
293
+ process.exit(0)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "storyblok-backup",
3
- "version": "0.0.3",
4
- "description": "npx CLI tool to create a full backup of a Storyblok space",
3
+ "version": "0.1.0",
4
+ "description": "npx CLI tool to create a full backup of a Storyblok space and restore single resources from it.",
5
5
  "scripts": {
6
6
  "upgrade": "npx npm-check-updates -i -u && pnpm install",
7
7
  "lint:js": "eslint --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .lintignore .",
@@ -12,7 +12,8 @@
12
12
  "lintfix": "pnpm lintfix:js && pnpm lintfix:prettier"
13
13
  },
14
14
  "bin": {
15
- "storyblok-backup": "bin/storyblok-backup.mjs"
15
+ "storyblok-backup": "bin/storyblok-backup.mjs",
16
+ "storyblok-restore": "bin/storyblok-restore.mjs"
16
17
  },
17
18
  "repository": {
18
19
  "type": "git",
@@ -35,14 +36,14 @@
35
36
  "homepage": "https://github.com/webflorist/storyblok-backup#readme",
36
37
  "devDependencies": {
37
38
  "eslint": "^8.49.0",
38
- "eslint-config-prettier": "^9.0.0",
39
- "eslint-plugin-prettier": "^5.0.0",
40
- "prettier": "^3.0.3"
39
+ "eslint-config-prettier": "^9.1.0",
40
+ "eslint-plugin-prettier": "^5.1.3",
41
+ "prettier": "^3.3.2"
41
42
  },
42
43
  "dependencies": {
43
- "archiver": "^6.0.1",
44
+ "@dotenvx/dotenvx": "^1.6.2",
44
45
  "minimist": "^1.2.8",
45
- "storyblok-js-client": "^6.0.0",
46
- "zip-lib": "^0.7.3"
46
+ "storyblok-js-client": "^6.7.3",
47
+ "zip-lib": "^1.0.4"
47
48
  }
48
49
  }