storyblok-backup 0.0.1
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/.editorconfig +13 -0
- package/.eslintrc.cjs +11 -0
- package/.lintignore +2 -0
- package/.prettierrc.js +16 -0
- package/.vscode/extensions.json +3 -0
- package/.vscode/settings.json +10 -0
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/bin/storyblok-backup.mjs +294 -0
- package/package.json +48 -0
package/.editorconfig
ADDED
package/.eslintrc.cjs
ADDED
package/.lintignore
ADDED
package/.prettierrc.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
semi: false,
|
|
3
|
+
singleQuote: true,
|
|
4
|
+
useTabs: true,
|
|
5
|
+
tabWidth: 4,
|
|
6
|
+
printWidth: 100,
|
|
7
|
+
trailingComma: 'es5',
|
|
8
|
+
overrides: [
|
|
9
|
+
{
|
|
10
|
+
files: ['**/*.md', '**/*.yaml', '**/*.yml'],
|
|
11
|
+
options: {
|
|
12
|
+
tabWidth: 2,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 webflorist
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Storyblok Backup CLI
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/storyblok-backup)
|
|
4
|
+
[](https://github.com/webflorist/storyblok-backup/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
A npx CLI tool to create a full backup of a space of the [Storyblok CMS](https://www.storyblok.com).
|
|
7
|
+
|
|
8
|
+
The script will fetch the following resources of a Storyblok space using the Management API and archive them in a zip file:
|
|
9
|
+
|
|
10
|
+
- Stories
|
|
11
|
+
- Components
|
|
12
|
+
- Component groups
|
|
13
|
+
- Assets (optionally incl. original files)
|
|
14
|
+
- Asset folders
|
|
15
|
+
- Datasources (incl. entries)
|
|
16
|
+
- Space
|
|
17
|
+
- Space Roles
|
|
18
|
+
- Tasks
|
|
19
|
+
- Activities
|
|
20
|
+
- Presets
|
|
21
|
+
- Field types
|
|
22
|
+
- Workflow stages
|
|
23
|
+
- Workflow stage changes
|
|
24
|
+
- Custom workflows
|
|
25
|
+
- Releases
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```shell
|
|
30
|
+
# install globally
|
|
31
|
+
$ npm install -g storyblok-backup
|
|
32
|
+
|
|
33
|
+
# or simply run via npx
|
|
34
|
+
$ npx storyblok-backup
|
|
35
|
+
|
|
36
|
+
# or install for project using npm
|
|
37
|
+
$ npm install storyblok-backup
|
|
38
|
+
|
|
39
|
+
# or install for project using yarn
|
|
40
|
+
$ yarn add storyblok-backup
|
|
41
|
+
|
|
42
|
+
# or install for project using pnpm
|
|
43
|
+
$ pnpm add storyblok-backup
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
### Options
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
--token <token> (required) Personal OAuth access token created
|
|
52
|
+
in the account settings of a Stoyblok user.
|
|
53
|
+
(NOT the Access Token of a Space!)
|
|
54
|
+
--space <space_id> (required) ID of the space to backup
|
|
55
|
+
--with-asset-files Downloads all files (assets) of the space. Defaults to false.
|
|
56
|
+
--output-dir <dir> Directory to write the backup to. Defaults to ./.output
|
|
57
|
+
(ATTENTION: Will fail if the directory already exists!)
|
|
58
|
+
--force Force deletion and recreation of existing output directory.
|
|
59
|
+
--create-zip Create a zip file of the backup. Defaults to false.
|
|
60
|
+
--zip-prefix <dir> Prefix for the zip file. Defaults to 'backup'.
|
|
61
|
+
(The suffix will automatically be the current date.)
|
|
62
|
+
--verbose Will show detailed output for every file written.
|
|
63
|
+
--help Show this help
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Minimal example
|
|
67
|
+
|
|
68
|
+
```shell
|
|
69
|
+
npx storyblok-backup --token 1234567890abcdef --space 12345
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This will create the folder `./.output` and fetch all resources sorted into folders.
|
|
73
|
+
|
|
74
|
+
### Maximal example
|
|
75
|
+
|
|
76
|
+
```shell
|
|
77
|
+
npx storyblok-backup \
|
|
78
|
+
--token 1234567890abcdef \
|
|
79
|
+
--space 12345 \
|
|
80
|
+
--with-asset-files \
|
|
81
|
+
--output-dir ./my-dir \
|
|
82
|
+
--create-zip \
|
|
83
|
+
--zip-prefix daily \
|
|
84
|
+
--verbose
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
This will create the folder `./my-dir`, 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.
|
|
88
|
+
|
|
89
|
+
## Continuous Integration
|
|
90
|
+
|
|
91
|
+
You can e.g. use this script to create periodic backups of Storyblok spaces using GitHub Actions and artifacts.
|
|
92
|
+
|
|
93
|
+
Here would be an example for a weekly backup, that removes the artifacts/backups from previous runs and uploads a new one:
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
name: Weekly Storyblok Backup
|
|
97
|
+
|
|
98
|
+
on:
|
|
99
|
+
schedule:
|
|
100
|
+
- cron: '0 0 * * 0'
|
|
101
|
+
|
|
102
|
+
jobs:
|
|
103
|
+
build:
|
|
104
|
+
runs-on: ubuntu-latest
|
|
105
|
+
|
|
106
|
+
steps:
|
|
107
|
+
- name: Perform Backup
|
|
108
|
+
env:
|
|
109
|
+
STORYBLOK_OAUTH_TOKEN: ${{ secrets.STORYBLOK_OAUTH_TOKEN }}
|
|
110
|
+
STORYBLOK_SPACE_ID: ${{ secrets.STORYBLOK_SPACE_ID }}
|
|
111
|
+
run: npx storyblok-backup --token $STORYBLOK_OAUTH_TOKEN --space $STORYBLOK_SPACE_ID --create-zip
|
|
112
|
+
|
|
113
|
+
- name: Delete Old Artifacts
|
|
114
|
+
uses: actions/github-script@v6
|
|
115
|
+
id: artifact
|
|
116
|
+
with:
|
|
117
|
+
script: |
|
|
118
|
+
const res = await github.rest.actions.listArtifactsForRepo({
|
|
119
|
+
owner: context.repo.owner,
|
|
120
|
+
repo: context.repo.repo,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
res.data.artifacts
|
|
124
|
+
.filter(({ name }) => name === 'weekly-backup')
|
|
125
|
+
.forEach(({ id }) => {
|
|
126
|
+
github.rest.actions.deleteArtifact({
|
|
127
|
+
owner: context.repo.owner,
|
|
128
|
+
repo: context.repo.repo,
|
|
129
|
+
artifact_id: id,
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
- name: Copy Artifact
|
|
134
|
+
run: mkdir artifact && cp ./.output/*.zip artifact
|
|
135
|
+
|
|
136
|
+
- name: Upload Artifact
|
|
137
|
+
uses: actions/upload-artifact@v3
|
|
138
|
+
with:
|
|
139
|
+
name: weekly-backup
|
|
140
|
+
path: artifact
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Make sure, to set the secrets `STORYBLOK_OAUTH_TOKEN` and `STORYBLOK_SPACE_ID` in your repository settings.
|
|
144
|
+
|
|
145
|
+
If you create multiple workflows for daily, weekly and monthly backups, by changing the cron-schedule and the two occurrences of the artifact name `weekly-backup`, you will always have exactly one daily, weekly and monthly backup.
|
|
146
|
+
|
|
147
|
+
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)).
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
This package is open-sourced software licensed under the [MIT license](https://github.com/webflorist/storyblok-backup/blob/main/LICENSE.).
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import { Readable } from 'stream'
|
|
5
|
+
import { finished } from 'stream/promises'
|
|
6
|
+
import zipLib from 'zip-lib'
|
|
7
|
+
import minimist from 'minimist'
|
|
8
|
+
import StoryblokClient from 'storyblok-js-client'
|
|
9
|
+
import { performance } from 'perf_hooks'
|
|
10
|
+
|
|
11
|
+
const startTime = performance.now()
|
|
12
|
+
|
|
13
|
+
const args = minimist(process.argv.slice(2))
|
|
14
|
+
|
|
15
|
+
if ('help' in args) {
|
|
16
|
+
console.log(`USAGE
|
|
17
|
+
$ npx storyblok-backup
|
|
18
|
+
|
|
19
|
+
OPTIONS
|
|
20
|
+
--token <token> (required) Personal OAuth access token created
|
|
21
|
+
in the account settings of a Stoyblok user.
|
|
22
|
+
(NOT the Access Token of a Space!)
|
|
23
|
+
--space <space_id> (required) ID of the space to backup
|
|
24
|
+
--with-asset-files Downloads all files (assets) of the space. Defaults to false.
|
|
25
|
+
--output-dir <dir> Directory to write the backup to. Defaults to ./.output
|
|
26
|
+
(ATTENTION: Will fail if the directory already exists!)
|
|
27
|
+
--force Force deletion and recreation of existing output directory.
|
|
28
|
+
--create-zip Create a zip file of the backup. Defaults to false.
|
|
29
|
+
--zip-prefix <dir> Prefix for the zip file. Defaults to 'backup'.
|
|
30
|
+
(The suffix will automatically be the current date.)
|
|
31
|
+
--verbose Will show detailed output for every file written.
|
|
32
|
+
--help Show this help
|
|
33
|
+
|
|
34
|
+
MINIMAL EXAMPLE
|
|
35
|
+
$ npx storyblok-backup --token 1234567890abcdef --space 12345
|
|
36
|
+
|
|
37
|
+
MAXIMAL EXAMPLE
|
|
38
|
+
$ npx storyblok-backup \\
|
|
39
|
+
--token 1234567890abcdef \\
|
|
40
|
+
--space 12345 \\
|
|
41
|
+
--output-dir ./backup \\
|
|
42
|
+
--zip-prefix daily
|
|
43
|
+
--verbose
|
|
44
|
+
`)
|
|
45
|
+
process.exit(0)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!('token' in args)) {
|
|
49
|
+
console.log(
|
|
50
|
+
'Error: State your oauth token via the --token argument. Use --help to find out more.'
|
|
51
|
+
)
|
|
52
|
+
process.exit(1)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!('space' in args)) {
|
|
56
|
+
console.log('Error: State your space id via the --space argument. Use --help to find out more.')
|
|
57
|
+
process.exit(1)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const verbose = 'verbose' in args
|
|
61
|
+
|
|
62
|
+
const outputDir = args['output-dir'] || './.output'
|
|
63
|
+
|
|
64
|
+
if (fs.existsSync(outputDir) && !('force' in args)) {
|
|
65
|
+
console.log(
|
|
66
|
+
`Error: Output directory "${outputDir}" already exists. Use --force to delete and recreate it (POSSIBLY DANGEROUS!).`
|
|
67
|
+
)
|
|
68
|
+
process.exit(1)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const spaceId = args.space
|
|
72
|
+
|
|
73
|
+
const filePrefix = args['zip-prefix'] || 'backup'
|
|
74
|
+
|
|
75
|
+
const fileName =
|
|
76
|
+
[
|
|
77
|
+
filePrefix,
|
|
78
|
+
new Date().getFullYear(),
|
|
79
|
+
new Date().getMonth().toString().padStart(2, '0'),
|
|
80
|
+
new Date().getDay().toString().padStart(2, '0'),
|
|
81
|
+
new Date().getHours().toString().padStart(2, '0'),
|
|
82
|
+
new Date().getMinutes().toString().padStart(2, '0'),
|
|
83
|
+
new Date().getSeconds().toString().padStart(2, '0'),
|
|
84
|
+
].join('-') + '.zip'
|
|
85
|
+
|
|
86
|
+
const filePath = `${outputDir}/${fileName}`
|
|
87
|
+
|
|
88
|
+
console.log(`Creating backup for space ${spaceId}:`)
|
|
89
|
+
console.log(`Output dir: ${outputDir}`)
|
|
90
|
+
if ('create-zip' in args) {
|
|
91
|
+
console.log(`Output zip: ${filePath}`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Init Management API
|
|
95
|
+
const StoryblokMAPI = new StoryblokClient({
|
|
96
|
+
oauthToken: args.token,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// Remove existing output directory
|
|
100
|
+
if (fs.existsSync(outputDir)) {
|
|
101
|
+
fs.rmSync(outputDir, { recursive: true, force: true })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Create output directories
|
|
105
|
+
fs.mkdirSync(outputDir, { recursive: true })
|
|
106
|
+
|
|
107
|
+
const resources = [
|
|
108
|
+
'stories',
|
|
109
|
+
'components',
|
|
110
|
+
'component-groups',
|
|
111
|
+
'assets',
|
|
112
|
+
'asset-folders',
|
|
113
|
+
'datasources',
|
|
114
|
+
'space-roles',
|
|
115
|
+
'tasks',
|
|
116
|
+
'activities',
|
|
117
|
+
'presets',
|
|
118
|
+
'field-types',
|
|
119
|
+
'workflow-stages',
|
|
120
|
+
'workflow-stage-changes',
|
|
121
|
+
'workflows',
|
|
122
|
+
'releases',
|
|
123
|
+
]
|
|
124
|
+
resources.forEach((resource) => fs.mkdirSync(`${outputDir}/${resource}`))
|
|
125
|
+
|
|
126
|
+
// Function to perform a default fetch
|
|
127
|
+
const defaultFetch = async (type, folder, fileField) => {
|
|
128
|
+
await StoryblokMAPI.getAll(`spaces/${spaceId}/${type}`)
|
|
129
|
+
.then((items) => {
|
|
130
|
+
items.forEach((item) => writeJson(folder, item[fileField], item))
|
|
131
|
+
})
|
|
132
|
+
.catch((error) => {
|
|
133
|
+
throw error
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Function to write a file
|
|
138
|
+
const writeJson = (folder, file, content) => {
|
|
139
|
+
let outputFile = outputDir
|
|
140
|
+
if (folder !== null) {
|
|
141
|
+
outputFile += `/${folder}`
|
|
142
|
+
}
|
|
143
|
+
outputFile += `/${file}.json`
|
|
144
|
+
fs.writeFile(outputFile, JSON.stringify(content), (error) => {
|
|
145
|
+
if (error) {
|
|
146
|
+
throw error
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
if (verbose) console.log(`Written file ${outputFile}`)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Function to download a file
|
|
153
|
+
const downloadFile = async (type, name, url) => {
|
|
154
|
+
const res = await fetch(url)
|
|
155
|
+
const outputFile = `${outputDir}/${type}/${name}`
|
|
156
|
+
const fileStream = fs.createWriteStream(outputFile, { flags: 'wx' })
|
|
157
|
+
await finished(Readable.fromWeb(res.body).pipe(fileStream))
|
|
158
|
+
if (verbose) console.log(`Written file ${outputFile}`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Fetch space info
|
|
162
|
+
console.log(`Fetching space`)
|
|
163
|
+
await StoryblokMAPI.get(`spaces/${spaceId}/`)
|
|
164
|
+
.then((space) => {
|
|
165
|
+
writeJson(null, `space-${spaceId}`, space.data.space)
|
|
166
|
+
})
|
|
167
|
+
.catch((error) => {
|
|
168
|
+
throw error
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// Fetch all stories
|
|
172
|
+
console.log(`Fetching stories`)
|
|
173
|
+
await StoryblokMAPI.getAll(`spaces/${spaceId}/stories`)
|
|
174
|
+
.then(async (stories) => {
|
|
175
|
+
for (const story of stories) {
|
|
176
|
+
await StoryblokMAPI.get(`spaces/${spaceId}/stories/${story.id}`)
|
|
177
|
+
.then((response) => writeJson('stories', story.id, response.data.story))
|
|
178
|
+
.catch((error) => {
|
|
179
|
+
throw error
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
.catch((error) => {
|
|
184
|
+
throw error
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
// Fetch all components
|
|
188
|
+
console.log(`Fetching components`)
|
|
189
|
+
await defaultFetch('components', 'components', 'name')
|
|
190
|
+
|
|
191
|
+
// Fetch all component-groups
|
|
192
|
+
console.log(`Fetching component-groups`)
|
|
193
|
+
await defaultFetch('component_groups', 'component-groups', 'id')
|
|
194
|
+
|
|
195
|
+
// Fetch all assets (including files)
|
|
196
|
+
console.log(`Fetching assets`)
|
|
197
|
+
await StoryblokMAPI.getAll(`spaces/${spaceId}/assets`)
|
|
198
|
+
.then(async (assets) => {
|
|
199
|
+
for (const asset of assets) {
|
|
200
|
+
writeJson('assets', asset.id, asset)
|
|
201
|
+
if ('with-asset-files' in args) {
|
|
202
|
+
const fileExtension = asset.filename.split('.').at(-1)
|
|
203
|
+
const fileName = asset.id + '.' + fileExtension
|
|
204
|
+
await downloadFile('assets', fileName, asset.filename)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
.catch((error) => {
|
|
209
|
+
throw error
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// Fetch all asset-folders
|
|
213
|
+
console.log(`Fetching asset-folders`)
|
|
214
|
+
await defaultFetch('asset_folders', 'asset-folders', 'id')
|
|
215
|
+
|
|
216
|
+
// Fetch all datasources (including entries)
|
|
217
|
+
console.log(`Fetching datasources`)
|
|
218
|
+
await StoryblokMAPI.getAll(`spaces/${spaceId}/datasources`)
|
|
219
|
+
.then(async (datasources) => {
|
|
220
|
+
for (const datasource of datasources) {
|
|
221
|
+
writeJson('datasources', datasource.id, datasource)
|
|
222
|
+
await StoryblokMAPI.getAll(`spaces/${spaceId}/datasource_entries`, {
|
|
223
|
+
datasource_id: datasource.id,
|
|
224
|
+
})
|
|
225
|
+
.then((dateSourceEntries) =>
|
|
226
|
+
writeJson('datasources', datasource.id + '_entries', dateSourceEntries)
|
|
227
|
+
)
|
|
228
|
+
.catch((error) => {
|
|
229
|
+
throw error
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
.catch((error) => {
|
|
234
|
+
throw error
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
// Fetch all space roles
|
|
238
|
+
console.log(`Fetching space roles`)
|
|
239
|
+
await defaultFetch('space_roles', 'space-roles', 'id')
|
|
240
|
+
|
|
241
|
+
// Fetch all tasks
|
|
242
|
+
console.log(`Fetching tasks`)
|
|
243
|
+
await defaultFetch('tasks', 'tasks', 'id')
|
|
244
|
+
|
|
245
|
+
// Fetch all activities
|
|
246
|
+
console.log(`Fetching activities`)
|
|
247
|
+
await defaultFetch('activities', 'activities', 'id')
|
|
248
|
+
|
|
249
|
+
// Fetch all presets
|
|
250
|
+
console.log(`Fetching presets`)
|
|
251
|
+
await defaultFetch('presets', 'presets', 'id')
|
|
252
|
+
|
|
253
|
+
// Fetch all field-types
|
|
254
|
+
console.log(`Fetching field-types`)
|
|
255
|
+
await defaultFetch('field_types', 'field-types', 'name')
|
|
256
|
+
|
|
257
|
+
// Fetch all workflow-stages
|
|
258
|
+
console.log(`Fetching workflow-stages`)
|
|
259
|
+
await defaultFetch('workflow_stages', 'workflow-stages', 'id')
|
|
260
|
+
|
|
261
|
+
// Fetch all workflow-stage-changes
|
|
262
|
+
console.log(`Fetching workflow-stage-changes`)
|
|
263
|
+
await defaultFetch('workflow_stage_changes', 'workflow-stage-changes', 'id')
|
|
264
|
+
|
|
265
|
+
// Fetch all workflows
|
|
266
|
+
console.log(`Fetching workflows`)
|
|
267
|
+
await defaultFetch('workflows', 'workflows', 'id')
|
|
268
|
+
|
|
269
|
+
// Fetch all releases
|
|
270
|
+
console.log(`Fetching releases`)
|
|
271
|
+
await defaultFetch('releases', 'releases', 'id')
|
|
272
|
+
|
|
273
|
+
// Create zip file
|
|
274
|
+
if ('create-zip' in args) {
|
|
275
|
+
console.log(`Creating zip file`)
|
|
276
|
+
await zipLib
|
|
277
|
+
.archiveFolder(outputDir, filePath)
|
|
278
|
+
.then(
|
|
279
|
+
function () {
|
|
280
|
+
console.log(`Backup file '${filePath}' successfully created.`)
|
|
281
|
+
},
|
|
282
|
+
function (err) {
|
|
283
|
+
throw err
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
.catch((error) => {
|
|
287
|
+
throw error
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const endTime = performance.now()
|
|
292
|
+
|
|
293
|
+
console.log(`Backup successfully created in ${Math.round((endTime - startTime) / 1000)} seconds.`)
|
|
294
|
+
process.exit(0)
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "storyblok-backup",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "npx CLI tool to create a full backup of a Storyblok space",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"upgrade": "npx npm-check-updates -i -u && pnpm install",
|
|
7
|
+
"lint:js": "eslint --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .lintignore .",
|
|
8
|
+
"lintfix:js": "pnpm lint:js --fix",
|
|
9
|
+
"lint:prettier": "prettier --ignore-path ./.lintignore --check .",
|
|
10
|
+
"lintfix:prettier": "prettier --ignore-path ./.lintignore --write --list-different .",
|
|
11
|
+
"lint": "pnpm lint:js && pnpm lint:prettier",
|
|
12
|
+
"lintfix": "pnpm lintfix:js && pnpm lintfix:prettier"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"storyblok-backup": "bin/storyblok-backup.mjs"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/webflorist/storyblok-backup.git"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"storyblok",
|
|
23
|
+
"cms",
|
|
24
|
+
"backup",
|
|
25
|
+
"cli",
|
|
26
|
+
"node",
|
|
27
|
+
"script",
|
|
28
|
+
"npx"
|
|
29
|
+
],
|
|
30
|
+
"author": "Gerald Buttinger <gerald@code.florist>",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/webflorist/storyblok-backup/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/webflorist/storyblok-backup#readme",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"eslint": "^8.49.0",
|
|
38
|
+
"eslint-config-prettier": "^9.0.0",
|
|
39
|
+
"eslint-plugin-prettier": "^5.0.0",
|
|
40
|
+
"prettier": "^3.0.3"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"archiver": "^6.0.1",
|
|
44
|
+
"minimist": "^1.2.8",
|
|
45
|
+
"storyblok-js-client": "^6.0.0",
|
|
46
|
+
"zip-lib": "^0.7.3"
|
|
47
|
+
}
|
|
48
|
+
}
|