storyblok 3.15.0 → 3.16.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/.github/ISSUE_TEMPLATE/bug_report.md +1 -8
- package/.github/workflows/release.yml +23 -0
- package/README.md +78 -9
- package/package.json +10 -1
- package/pull_request_template.md +29 -0
- package/src/cli.js +77 -26
- package/src/constants.js +2 -0
- package/src/tasks/delete-component.js +32 -0
- package/src/tasks/delete-components.js +92 -0
- package/src/tasks/index.js +3 -1
- package/src/tasks/migrations/run.js +1 -1
- package/src/tasks/migrations/utils.js +6 -5
- package/src/tasks/sync-commands/component-groups.js +2 -4
- package/src/tasks/sync-commands/components.js +2 -4
- package/src/utils/api.js +47 -18
- package/src/utils/creds.js +11 -7
- package/src/utils/get-questions.js +13 -0
- package/src/utils/presets-lib.js +2 -4
- package/tests/constants.js +3 -1
- package/tests/units/delete-component.spec.js +53 -0
- package/tests/units/delete-components.spec.js +93 -0
- package/tests/units/login.spec.js +4 -3
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -28
- package/.github/ISSUE_TEMPLATE/question.md +0 -10
- package/.github/ISSUE_TEMPLATE.md +0 -31
- package/.github/labeler.yml +0 -8
- package/.github/workflows/issue-label.yml +0 -14
- package/.travis.yml +0 -5
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Bug report
|
|
3
|
-
about: Create a report to help us improve
|
|
3
|
+
about: Create a report to help us improve the CLI
|
|
4
4
|
title: ''
|
|
5
5
|
labels: bug
|
|
6
6
|
assignees: ''
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
**The issue occurs while working with:** (check one with "x")
|
|
11
|
-
- [ ] *app.storyblok.com (CMS - Interface)*
|
|
12
|
-
- [ ] *api.storyblok.com (CMS - Content Delivery API)*
|
|
13
|
-
- [ ] *mapi.storyblok.com (CMS - Management API)*
|
|
14
|
-
- [ ] *a.storyblok.com (CMS - Image Service)*
|
|
15
|
-
- [ ] *storyblok.com (Website)*
|
|
16
|
-
- [ ] *Other:* <!-- => If you've got an issue with on of our boilerplates or themes - please create an issue in the specific repo -->
|
|
17
10
|
|
|
18
11
|
**Current behavior:**
|
|
19
12
|
<!-- Describe how the bug manifests. -->
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v3
|
|
13
|
+
- uses: actions/setup-node@v3
|
|
14
|
+
with:
|
|
15
|
+
node-version: 16
|
|
16
|
+
cache: 'yarn'
|
|
17
|
+
- name: Install dependencies
|
|
18
|
+
run: yarn
|
|
19
|
+
- name: Release
|
|
20
|
+
env:
|
|
21
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
22
|
+
NPM_TOKEN: ${{ secrets.NPM_PUBLISH }}
|
|
23
|
+
run: npx semantic-release
|
package/README.md
CHANGED
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
<p align="center">A simple CLI for scaffolding <a href="https://www.storyblok.com" target="_blank">Storyblok</a> projects and fieldtypes.</p>
|
|
4
4
|
</p>
|
|
5
5
|
|
|
6
|
-
You found an issue?<br>Tell us about it - <a href="https://github.com/storyblok/storyblok/issues/new">open an issue</a> or look if it was <a href="https://github.com/storyblok/storyblok/issues/">already reported</a>.
|
|
7
|
-
|
|
8
6
|
[](https://www.npmjs.com/package/storyblok)
|
|
9
7
|
[](ttps://img.shields.io/npm/dt/storyblok.svg)
|
|
10
8
|
[](https://github.com/storyblok/storyblok/issues?q=is%3Aopen+is%3Aissue)
|
|
11
9
|
[](https://github.com/storyblok/storyblok/issues?q=is%3Aissue+is%3Aclosed)
|
|
12
10
|
|
|
11
|
+
## BREAKING CHANGE
|
|
12
|
+
|
|
13
|
+
We added the `region` option upon login. If you are using the CLI, please `logout` and `login` again providing your user region.
|
|
14
|
+
|
|
13
15
|
## Installation
|
|
14
16
|
|
|
15
|
-
Make sure you
|
|
17
|
+
Make sure you have Node `>= 9.11.0` installed.
|
|
16
18
|
|
|
17
19
|
```sh
|
|
18
20
|
$ npm i storyblok -g
|
|
@@ -51,7 +53,6 @@ $ storyblok pull-components --space <SPACE_ID> --region <REGION>
|
|
|
51
53
|
#### Options
|
|
52
54
|
|
|
53
55
|
* `space`: your space id
|
|
54
|
-
* `region`: your space region (default: `eu`). If your space was created under US region, you should use `us` instead.
|
|
55
56
|
|
|
56
57
|
### push-components
|
|
57
58
|
|
|
@@ -64,7 +65,6 @@ $ storyblok push-components <SOURCE> --space <SPACE_ID> --region <REGION> --pres
|
|
|
64
65
|
#### Parameters
|
|
65
66
|
|
|
66
67
|
* `source`: can be a URL or path to JSON file.
|
|
67
|
-
* `region`: your space region (default: `eu`). If your space was created under US region, you should use `us` instead.
|
|
68
68
|
|
|
69
69
|
Using an **URL**
|
|
70
70
|
|
|
@@ -81,7 +81,6 @@ $ storyblok push-components ./components.json --space 67819
|
|
|
81
81
|
#### Options
|
|
82
82
|
|
|
83
83
|
* `space`: your space id
|
|
84
|
-
* `region`: your space region (default: `eu`). If your space was created under US region, you should use `us` instead.
|
|
85
84
|
* `presets-source` (optional): it can be a URL or path to JSON file with the presets
|
|
86
85
|
|
|
87
86
|
#### Examples
|
|
@@ -98,6 +97,65 @@ Using a **path** to file
|
|
|
98
97
|
$ storyblok push-components ./components.json --presets-source ./presets.json --space 67819
|
|
99
98
|
```
|
|
100
99
|
|
|
100
|
+
### delete-component
|
|
101
|
+
|
|
102
|
+
Delete a single component on your space.
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
storyblok delete-component <component> --space <SPACE_ID>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### Parameters
|
|
109
|
+
* `component`: The name or id of the component
|
|
110
|
+
|
|
111
|
+
#### Options
|
|
112
|
+
* `space_id`: the space where the command should be executed.
|
|
113
|
+
|
|
114
|
+
#### Examples
|
|
115
|
+
|
|
116
|
+
Delete a component on your space.
|
|
117
|
+
```sh
|
|
118
|
+
storyblok delete-component 111111 --space 67819
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
storyblok delete-component teaser --space 67819
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### delete-components
|
|
126
|
+
|
|
127
|
+
Delete all components from your Space that occur in your Local JSON.
|
|
128
|
+
Or delete those components on your Space that do not appear in the JSON. (`--reverse`)
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
storyblok delete-components <SOURCE> --space <SPACE_ID>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### Parameters
|
|
135
|
+
* `source`: can be a URL or path to JSON file.
|
|
136
|
+
|
|
137
|
+
#### Options
|
|
138
|
+
* `space_id`: the space where the command should be executed.
|
|
139
|
+
* `reverse`: When passed as an argument, deletes only those components on your space that do not appear in the JSON.
|
|
140
|
+
* `dryrun`: when passed as an argument, does not perform any changes on the given space.
|
|
141
|
+
|
|
142
|
+
#### Examples
|
|
143
|
+
|
|
144
|
+
Delete all components on a certain space that occur in your local JSON.
|
|
145
|
+
```sh
|
|
146
|
+
storyblok delete-components ./components.json --space 67819
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Delete only those components which do not occur in your local json from your space.
|
|
150
|
+
```sh
|
|
151
|
+
storyblok delete-components ./components.json --space 67819 --reverse
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
To see the result in your console output but to not perform the command on your space, use the `--dryrun` argument.
|
|
155
|
+
```sh
|
|
156
|
+
storyblok delete-components ./components.json --space 67819 --reverse --dryrun
|
|
157
|
+
```
|
|
158
|
+
|
|
101
159
|
### sync
|
|
102
160
|
|
|
103
161
|
Sync components, folder, roles, datasources or stories between spaces
|
|
@@ -111,7 +169,6 @@ $ storyblok sync --type <COMMAND> --source <SPACE_ID> --target <SPACE_ID>
|
|
|
111
169
|
* `type`: describe the command type to execute. Can be: `folders`, `components`, `stories`, `datasources` or `roles`. It's possible pass multiple types separated by comma (`,`).
|
|
112
170
|
* `source`: the source space to use to sync
|
|
113
171
|
* `target`: the target space to use to sync
|
|
114
|
-
* `region`: your space region (default: `eu`). If your space was created under US region, you should use `us` instead.
|
|
115
172
|
|
|
116
173
|
#### Examples
|
|
117
174
|
|
|
@@ -146,6 +203,19 @@ Login to the Storyblok cli
|
|
|
146
203
|
```sh
|
|
147
204
|
$ storyblok login
|
|
148
205
|
```
|
|
206
|
+
#### Options
|
|
207
|
+
|
|
208
|
+
* `email`: your user's email address
|
|
209
|
+
* `password`: your user's password
|
|
210
|
+
* `region`: your user's region (default: `eu`). You can use `us`, `cn` or `eu`. This region will be used for the other cli's commands.
|
|
211
|
+
|
|
212
|
+
### user
|
|
213
|
+
|
|
214
|
+
Get the currently logged in user
|
|
215
|
+
|
|
216
|
+
```sh
|
|
217
|
+
$ storyblok user
|
|
218
|
+
```
|
|
149
219
|
|
|
150
220
|
### generate-migration
|
|
151
221
|
|
|
@@ -161,7 +231,6 @@ It's important to note that the `component` and `field` parameters are required
|
|
|
161
231
|
* `space`: space where the component is
|
|
162
232
|
* `component`: component name. It needs to be a valid component
|
|
163
233
|
* `field`: name of field
|
|
164
|
-
* `region`: your space region (default: `eu`). If your space was created under US region, you should use `us` instead.
|
|
165
234
|
|
|
166
235
|
### run-migration
|
|
167
236
|
|
|
@@ -188,7 +257,6 @@ $ storyblok run-migration --publish published --space 1234 --component article -
|
|
|
188
257
|
* `published`: only publish stories that already are published and don't have unpublished changes
|
|
189
258
|
* `published-with-changes`: publish stories that are published and have unpublished changes
|
|
190
259
|
* `publish-languages` (optional): publish specific languages. You can publish more than one language at a time by separating the languages by `,`
|
|
191
|
-
* `region`: your space region (default: `eu`). If your space was created under US region, you should use `us` instead.
|
|
192
260
|
|
|
193
261
|
### rollback-migration
|
|
194
262
|
|
|
@@ -208,6 +276,7 @@ $ storyblok rollback-migration --space 1234 --component Product --field title
|
|
|
208
276
|
|
|
209
277
|
### spaces
|
|
210
278
|
|
|
279
|
+
|
|
211
280
|
List all spaces of the logged account
|
|
212
281
|
|
|
213
282
|
```sh
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "storyblok",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.16.1",
|
|
4
4
|
"description": "A simple CLI to start Storyblok from your command line.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/storyblok/storyblok-cli.git"
|
|
8
|
+
},
|
|
5
9
|
"keywords": [
|
|
6
10
|
"storyblok",
|
|
7
11
|
"cli",
|
|
@@ -55,5 +59,10 @@
|
|
|
55
59
|
"eslint-plugin-promise": "^4.2.1",
|
|
56
60
|
"eslint-plugin-standard": "^4.0.1",
|
|
57
61
|
"jest": "^26.1.0"
|
|
62
|
+
},
|
|
63
|
+
"release": {
|
|
64
|
+
"branches": [
|
|
65
|
+
"master"
|
|
66
|
+
]
|
|
58
67
|
}
|
|
59
68
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!--- Please provide a general summary of your changes in the title above -->
|
|
2
|
+
|
|
3
|
+
## Pull request type
|
|
4
|
+
|
|
5
|
+
Jira Link: [INT-](url)
|
|
6
|
+
|
|
7
|
+
<!-- Please try to limit your pull request to one type, submit multiple pull requests if needed.
|
|
8
|
+
|
|
9
|
+
Please check the type of change your PR introduces:-->
|
|
10
|
+
|
|
11
|
+
- [ ] Bugfix
|
|
12
|
+
- [ ] Feature
|
|
13
|
+
- [ ] Code style update (formatting, renaming)
|
|
14
|
+
- [ ] Refactoring (no functional changes, no api changes)
|
|
15
|
+
- [ ] Other (please describe):
|
|
16
|
+
|
|
17
|
+
## How to test this PR
|
|
18
|
+
|
|
19
|
+
<!-- Please provide the steps on how to test this PR. -->
|
|
20
|
+
|
|
21
|
+
## What is the new behavior?
|
|
22
|
+
|
|
23
|
+
<!-- Please describe the behavior or changes that are being added by this PR. -->
|
|
24
|
+
|
|
25
|
+
-
|
|
26
|
+
-
|
|
27
|
+
-
|
|
28
|
+
|
|
29
|
+
## Other information
|
package/src/cli.js
CHANGED
|
@@ -39,7 +39,7 @@ program
|
|
|
39
39
|
program
|
|
40
40
|
.command(COMMANDS.LOGIN)
|
|
41
41
|
.description('Login to the Storyblok cli')
|
|
42
|
-
.action(async () => {
|
|
42
|
+
.action(async (options) => {
|
|
43
43
|
if (api.isAuthorized()) {
|
|
44
44
|
console.log(chalk.green('✓') + ' The user has been already logged. If you want to change the logged user, you must logout and login again')
|
|
45
45
|
return
|
|
@@ -54,6 +54,24 @@ program
|
|
|
54
54
|
}
|
|
55
55
|
})
|
|
56
56
|
|
|
57
|
+
// getUser
|
|
58
|
+
program
|
|
59
|
+
.command('user')
|
|
60
|
+
.description('Get the currently logged in user')
|
|
61
|
+
.action(async () => {
|
|
62
|
+
if (api.isAuthorized()) {
|
|
63
|
+
try {
|
|
64
|
+
const user = await api.getUser()
|
|
65
|
+
console.log(chalk.green('✓') + ` Hi ${user.friendly_name}, you current logged in with: ${creds.get().email}`)
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.log(chalk.red('X') + ` Please check if your current region matches your user's region: ${e.message}.`)
|
|
68
|
+
} finally {
|
|
69
|
+
process.exit(0)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
console.log(chalk.red('X') + ' There is currently no user logged.')
|
|
73
|
+
})
|
|
74
|
+
|
|
57
75
|
// logout
|
|
58
76
|
program
|
|
59
77
|
.command(COMMANDS.LOGOUT)
|
|
@@ -97,9 +115,8 @@ program
|
|
|
97
115
|
// pull-components
|
|
98
116
|
program
|
|
99
117
|
.command(COMMANDS.PULL_COMPONENTS)
|
|
100
|
-
.option('-r, --region [value]', 'region', 'eu')
|
|
101
118
|
.description("Download your space's components schema as json")
|
|
102
|
-
.action(async (
|
|
119
|
+
.action(async () => {
|
|
103
120
|
console.log(`${chalk.blue('-')} Executing pull-components task`)
|
|
104
121
|
const space = program.space
|
|
105
122
|
if (!space) {
|
|
@@ -112,11 +129,6 @@ program
|
|
|
112
129
|
await api.processLogin()
|
|
113
130
|
}
|
|
114
131
|
|
|
115
|
-
const { region } = source
|
|
116
|
-
if (program.args.length > 0) {
|
|
117
|
-
api.setRegion(region)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
132
|
api.setSpaceId(space)
|
|
121
133
|
await tasks.pullComponents(api, { space })
|
|
122
134
|
} catch (e) {
|
|
@@ -128,7 +140,6 @@ program
|
|
|
128
140
|
program
|
|
129
141
|
.command(COMMANDS.PUSH_COMPONENTS + ' <source>')
|
|
130
142
|
.option('-p, --presets-source <presetsSource>', 'Path to presets file')
|
|
131
|
-
.option('-r, --region [value]', 'region', 'eu')
|
|
132
143
|
.description("Download your space's components schema as json. The source parameter can be a URL to your JSON file or a path to it")
|
|
133
144
|
.action(async (source, options) => {
|
|
134
145
|
console.log(`${chalk.blue('-')} Executing push-components task`)
|
|
@@ -145,11 +156,6 @@ program
|
|
|
145
156
|
await api.processLogin()
|
|
146
157
|
}
|
|
147
158
|
|
|
148
|
-
const { region } = options
|
|
149
|
-
if (program.args.length > 0) {
|
|
150
|
-
api.setRegion(region)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
159
|
api.setSpaceId(space)
|
|
154
160
|
await tasks.pushComponents(api, { source, presetsSource })
|
|
155
161
|
} catch (e) {
|
|
@@ -157,6 +163,56 @@ program
|
|
|
157
163
|
}
|
|
158
164
|
})
|
|
159
165
|
|
|
166
|
+
// delete-component
|
|
167
|
+
program
|
|
168
|
+
.command('delete-component <component>')
|
|
169
|
+
.description('Delete a single component on your space.')
|
|
170
|
+
.action(async (component) => {
|
|
171
|
+
console.log(`${chalk.blue('-')} Executing delete-component task`)
|
|
172
|
+
const space = program.space
|
|
173
|
+
if (!space) {
|
|
174
|
+
console.log(chalk.red('X') + ' Please provide the space as argument --space YOUR_SPACE_ID.')
|
|
175
|
+
process.exit(0)
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
if (!api.isAuthorized()) {
|
|
179
|
+
await api.processLogin()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
api.setSpaceId(space)
|
|
183
|
+
await tasks.deleteComponent(api, { comp: component })
|
|
184
|
+
} catch (e) {
|
|
185
|
+
console.log(chalk.red('X') + ' An error occurred when executing the delete-component task: ' + e.message)
|
|
186
|
+
process.exit(1)
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// delete-components
|
|
191
|
+
program
|
|
192
|
+
.command('delete-components <source>')
|
|
193
|
+
.description('Delete all components in your space that occur in your source file.')
|
|
194
|
+
.option('-r, --reverse', 'Delete all components in your space that do not appear in your source.', false)
|
|
195
|
+
.option('--dryrun', 'Does not perform any delete changes on your space.')
|
|
196
|
+
.action(async (source, options) => {
|
|
197
|
+
console.log(`${chalk.blue('-')} Executing delete-components task`)
|
|
198
|
+
const space = program.space
|
|
199
|
+
if (!space) {
|
|
200
|
+
console.log(chalk.red('X') + ' Please provide the space as argument --space YOUR_SPACE_ID.')
|
|
201
|
+
process.exit(0)
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
if (!api.isAuthorized()) {
|
|
205
|
+
await api.processLogin()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
api.setSpaceId(space)
|
|
209
|
+
await tasks.deleteComponents(api, { source, dryRun: !!options.dryrun, reversed: !!options.reverse })
|
|
210
|
+
} catch (e) {
|
|
211
|
+
console.log(chalk.red('X') + ' An error occurred when executing the delete-component task: ' + e.message)
|
|
212
|
+
process.exit(1)
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
|
|
160
216
|
// scaffold
|
|
161
217
|
program
|
|
162
218
|
.command(COMMANDS.SCAFFOLD + ' <name>')
|
|
@@ -205,7 +261,6 @@ program
|
|
|
205
261
|
.requiredOption('--type <TYPE>', 'Define what will be sync. Can be components, folders, stories, datasources or roles')
|
|
206
262
|
.requiredOption('--source <SPACE_ID>', 'Source space id')
|
|
207
263
|
.requiredOption('--target <SPACE_ID>', 'Target space id')
|
|
208
|
-
.option('-r, --region [value]', 'region', 'eu')
|
|
209
264
|
.action(async (options) => {
|
|
210
265
|
console.log(`${chalk.blue('-')} Sync data between spaces\n`)
|
|
211
266
|
|
|
@@ -217,12 +272,9 @@ program
|
|
|
217
272
|
const {
|
|
218
273
|
type,
|
|
219
274
|
source,
|
|
220
|
-
target
|
|
221
|
-
region
|
|
275
|
+
target
|
|
222
276
|
} = options
|
|
223
277
|
|
|
224
|
-
api.setRegion(region)
|
|
225
|
-
|
|
226
278
|
const _types = type.split(',') || []
|
|
227
279
|
_types.forEach(_type => {
|
|
228
280
|
if (!SYNC_TYPES.includes(_type)) {
|
|
@@ -251,6 +303,10 @@ program
|
|
|
251
303
|
.description('Start a project quickly')
|
|
252
304
|
.action(async () => {
|
|
253
305
|
try {
|
|
306
|
+
if (!api.isAuthorized()) {
|
|
307
|
+
await api.processLogin()
|
|
308
|
+
}
|
|
309
|
+
|
|
254
310
|
const space = program.space
|
|
255
311
|
const questions = getQuestions('quickstart', { space }, api)
|
|
256
312
|
const answers = await inquirer.prompt(questions)
|
|
@@ -264,7 +320,6 @@ program
|
|
|
264
320
|
program
|
|
265
321
|
.command(COMMANDS.GENERATE_MIGRATION)
|
|
266
322
|
.description('Generate a content migration file')
|
|
267
|
-
.option('-r, --region [value]', 'region', 'eu')
|
|
268
323
|
.requiredOption('-c, --component <COMPONENT_NAME>', 'Name of the component')
|
|
269
324
|
.requiredOption('-f, --field <FIELD_NAME>', 'Name of the component field')
|
|
270
325
|
.action(async (options) => {
|
|
@@ -284,9 +339,6 @@ program
|
|
|
284
339
|
await api.processLogin()
|
|
285
340
|
}
|
|
286
341
|
|
|
287
|
-
const { region } = options
|
|
288
|
-
api.setRegion(region)
|
|
289
|
-
|
|
290
342
|
api.setSpaceId(space)
|
|
291
343
|
await tasks.generateMigration(api, component, field)
|
|
292
344
|
} catch (e) {
|
|
@@ -300,7 +352,6 @@ program
|
|
|
300
352
|
.description('Run a migration file')
|
|
301
353
|
.requiredOption('-c, --component <COMPONENT_NAME>', 'Name of the component')
|
|
302
354
|
.requiredOption('-f, --field <FIELD_NAME>', 'Name of the component field')
|
|
303
|
-
.option('-r, --region [value]', 'region', 'eu')
|
|
304
355
|
.option('--dryrun', 'Do not update the story content')
|
|
305
356
|
.option('--publish <PUBLISH_OPTION>', 'Publish the content. It can be: all, published or published-with-changes')
|
|
306
357
|
.option('--publish-languages <LANGUAGES>', 'Publish specific languages')
|
|
@@ -387,7 +438,7 @@ program
|
|
|
387
438
|
|
|
388
439
|
await tasks.listSpaces(api)
|
|
389
440
|
} catch (e) {
|
|
390
|
-
console.log(chalk.red('X') + ' An error ocurred to listing
|
|
441
|
+
console.log(chalk.red('X') + ' An error ocurred to listing spaces: ' + e.message)
|
|
391
442
|
process.exit(1)
|
|
392
443
|
}
|
|
393
444
|
})
|
|
@@ -433,7 +484,7 @@ if (program.rawArgs.length <= 2) {
|
|
|
433
484
|
|
|
434
485
|
function errorHandler (e, command) {
|
|
435
486
|
if (/404/.test(e.message)) {
|
|
436
|
-
console.log(chalk.yellow('/!\\') + ' If your space was created under US region, you must provide the region
|
|
487
|
+
console.log(chalk.yellow('/!\\') + ' If your space was created under US or CN region, you must provide the region us or cn upon login.')
|
|
437
488
|
} else {
|
|
438
489
|
console.log(chalk.red('X') + ' An error occurred when executing the ' + command + ' task: ' + e || e.message)
|
|
439
490
|
}
|
package/src/constants.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const API_URL = 'https://api.storyblok.com/v1/'
|
|
2
2
|
const US_API_URL = 'https://api-us.storyblok.com/v1/'
|
|
3
|
+
const CN_API_URL = 'https://api.storyblokchina.cn/v1/'
|
|
3
4
|
const LOGIN_URL = `${API_URL}users/login`
|
|
4
5
|
const SIGNUP_URL = `${API_URL}users/signup`
|
|
5
6
|
|
|
@@ -33,5 +34,6 @@ module.exports = {
|
|
|
33
34
|
API_URL,
|
|
34
35
|
SYNC_TYPES,
|
|
35
36
|
US_API_URL,
|
|
37
|
+
CN_API_URL,
|
|
36
38
|
COMMANDS
|
|
37
39
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const chalk = require('chalk')
|
|
2
|
+
const { getComponentsFromName } = require('./migrations/utils')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param api {Object}
|
|
7
|
+
* @param comp {String | Number}
|
|
8
|
+
* @param dryrun {Boolean}
|
|
9
|
+
* @returns {Promise<void>}
|
|
10
|
+
*/
|
|
11
|
+
const deleteComponent = async (api, { comp, dryrun = false }) => {
|
|
12
|
+
try {
|
|
13
|
+
let component
|
|
14
|
+
if (!isNaN(comp)) {
|
|
15
|
+
const { data } = await api.get(`components/${comp}`)
|
|
16
|
+
component = data.component
|
|
17
|
+
} else {
|
|
18
|
+
component = await getComponentsFromName(api, comp)
|
|
19
|
+
}
|
|
20
|
+
if (Object.keys(component).length === 0) {
|
|
21
|
+
return Promise.reject(new Error(`Component ${comp} not found.`))
|
|
22
|
+
}
|
|
23
|
+
if (!dryrun) {
|
|
24
|
+
await api.delete(`components/${component.id}`)
|
|
25
|
+
}
|
|
26
|
+
console.log(chalk.green('✓') + ' Component ' + chalk.blue(component.name) + ' deleted.')
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.error(`${chalk.red('X')} An error occurred in delete-component task.`)
|
|
29
|
+
return Promise.reject(new Error(e))
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
module.exports = deleteComponent
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const chalk = require('chalk')
|
|
2
|
+
const axios = require('axios')
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const deleteComponent = require('./delete-component')
|
|
5
|
+
|
|
6
|
+
const isUrl = source => source.indexOf('http') === 0
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get the data from a local or remote JSON file
|
|
10
|
+
* @param {string} source the local path or remote url of the file
|
|
11
|
+
* @returns {Promise<Object>} return the data from the source or an error
|
|
12
|
+
*/
|
|
13
|
+
const getDataFromSource = async (source) => {
|
|
14
|
+
if (!source) {
|
|
15
|
+
return {}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
if (isUrl(source)) {
|
|
20
|
+
return (await axios.get(source)).data
|
|
21
|
+
} else {
|
|
22
|
+
return JSON.parse(fs.readFileSync(source, 'utf8'))
|
|
23
|
+
}
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error(`${chalk.red('X')} Can not load json file from ${source}`)
|
|
26
|
+
return Promise.reject(err)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Delete all components from your Space that occur in your Local JSON.
|
|
32
|
+
* @param api {Object}
|
|
33
|
+
* @param source {String}
|
|
34
|
+
* @param reversed {Boolean} Or delete those components on your Space that do not appear in the JSON.
|
|
35
|
+
* @param dryRun {Boolean}
|
|
36
|
+
* @returns {Promise<void>}
|
|
37
|
+
*/
|
|
38
|
+
const deleteComponents = async (api, { source, reversed = false, dryRun = false }) => {
|
|
39
|
+
try {
|
|
40
|
+
const sourceComponents = (await getDataFromSource(source)).components || []
|
|
41
|
+
if (!reversed) {
|
|
42
|
+
return deleteAllComponents(api, sourceComponents, dryRun)
|
|
43
|
+
}
|
|
44
|
+
const spaceComponents = await api.getComponents()
|
|
45
|
+
return deleteComponentsReversed(api, sourceComponents, spaceComponents, dryRun)
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.error(`${chalk.red('X')} Can not delete with invalid json - please provide a valid json file`)
|
|
48
|
+
return Promise.reject(new Error('Can not delete with invalid json - please provide a valid json file'))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Delete all given components
|
|
54
|
+
* @param api {Object}
|
|
55
|
+
* @param components {Object[]}
|
|
56
|
+
* @param dryrun {Boolean}
|
|
57
|
+
* @returns {Promise<void>}
|
|
58
|
+
*/
|
|
59
|
+
const deleteAllComponents = async (api, components, dryrun) => {
|
|
60
|
+
for (const c of components) {
|
|
61
|
+
await deleteComponentAndSkip(api, c, dryrun)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Delete all components which do not appear in components but in the space components
|
|
67
|
+
* @param api {Object}
|
|
68
|
+
* @param components {Object[]}
|
|
69
|
+
* @param spaceComponents {Object[]}
|
|
70
|
+
* @param dryrun {Boolean}
|
|
71
|
+
* @returns {Promise<void>}
|
|
72
|
+
*/
|
|
73
|
+
const deleteComponentsReversed = async (api, components, spaceComponents, dryrun) => {
|
|
74
|
+
const unifiedComps = components.concat([...spaceComponents])
|
|
75
|
+
const toDelete = unifiedComps
|
|
76
|
+
.filter((value, index, self) =>
|
|
77
|
+
self.findIndex((o, i) => o.id === value.id && i !== index) < 0)
|
|
78
|
+
console.log(chalk.blue('-') + ' Deleting all components which do not appear in the given source.')
|
|
79
|
+
for (const c of toDelete) {
|
|
80
|
+
await deleteComponentAndSkip(api, c, dryrun)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const deleteComponentAndSkip = async (api, c, dryrun) => {
|
|
85
|
+
try {
|
|
86
|
+
return await deleteComponent(api, { comp: c.id, dryrun: dryrun })
|
|
87
|
+
} catch (e) {
|
|
88
|
+
console.log(chalk.red('-') + ' Error deleting component ' + chalk.blue(c.name) + '! Skipped...')
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = deleteComponents
|
package/src/tasks/index.js
CHANGED
|
@@ -9,5 +9,7 @@ module.exports = {
|
|
|
9
9
|
runMigration: require('./migrations/run'),
|
|
10
10
|
rollbackMigration: require('./migrations/rollback'),
|
|
11
11
|
listSpaces: require('./list-spaces'),
|
|
12
|
-
importFiles: require('./import/import')
|
|
12
|
+
importFiles: require('./import/import'),
|
|
13
|
+
deleteComponent: require('./delete-component'),
|
|
14
|
+
deleteComponents: require('./delete-components')
|
|
13
15
|
}
|
|
@@ -91,7 +91,7 @@ const runMigration = async (api, component, field, options = {}) => {
|
|
|
91
91
|
const storyData = await api.getSingleStory(story.id)
|
|
92
92
|
const oldContent = cloneDeep(storyData.content)
|
|
93
93
|
|
|
94
|
-
await processMigration(storyData.content, component, migrationFn)
|
|
94
|
+
await processMigration(storyData.content, component, migrationFn, story.full_slug)
|
|
95
95
|
|
|
96
96
|
const isChangeContent = !isEqual(oldContent, storyData.content)
|
|
97
97
|
|
|
@@ -186,9 +186,10 @@ const showMigrationChanges = (path, value, oldValue) => {
|
|
|
186
186
|
* @param {Object} content component structure from Storyblok
|
|
187
187
|
* @param {String} component name of the component that is processing
|
|
188
188
|
* @param {Function} migrationFn the migration function defined by user
|
|
189
|
+
* @param {String} storyFullSlug the full slug of the containing story
|
|
189
190
|
* @return {Promise<Boolean>}
|
|
190
191
|
*/
|
|
191
|
-
const processMigration = async (content = {}, component = '', migrationFn) => {
|
|
192
|
+
const processMigration = async (content = {}, component = '', migrationFn, storyFullSlug) => {
|
|
192
193
|
// I'm processing the component that I want
|
|
193
194
|
if (content.component === component) {
|
|
194
195
|
const watchedContent = onChange(
|
|
@@ -196,7 +197,7 @@ const processMigration = async (content = {}, component = '', migrationFn) => {
|
|
|
196
197
|
showMigrationChanges
|
|
197
198
|
)
|
|
198
199
|
|
|
199
|
-
await migrationFn(watchedContent)
|
|
200
|
+
await migrationFn(watchedContent, storyFullSlug)
|
|
200
201
|
}
|
|
201
202
|
|
|
202
203
|
for (const key in content) {
|
|
@@ -205,7 +206,7 @@ const processMigration = async (content = {}, component = '', migrationFn) => {
|
|
|
205
206
|
if (isArray(value)) {
|
|
206
207
|
try {
|
|
207
208
|
await Promise.all(
|
|
208
|
-
value.map(_item => processMigration(_item, component, migrationFn))
|
|
209
|
+
value.map(_item => processMigration(_item, component, migrationFn, storyFullSlug))
|
|
209
210
|
)
|
|
210
211
|
} catch (e) {
|
|
211
212
|
console.error(e)
|
|
@@ -214,7 +215,7 @@ const processMigration = async (content = {}, component = '', migrationFn) => {
|
|
|
214
215
|
|
|
215
216
|
if (isPlainObject(value) && has(value, 'component')) {
|
|
216
217
|
try {
|
|
217
|
-
await processMigration(value, component, migrationFn)
|
|
218
|
+
await processMigration(value, component, migrationFn, storyFullSlug)
|
|
218
219
|
} catch (e) {
|
|
219
220
|
console.error(e)
|
|
220
221
|
}
|
|
@@ -223,7 +224,7 @@ const processMigration = async (content = {}, component = '', migrationFn) => {
|
|
|
223
224
|
if (isPlainObject(value) && value.type === 'doc' && value.content) {
|
|
224
225
|
value.content.filter(item => item.type === 'blok').forEach(async (item) => {
|
|
225
226
|
try {
|
|
226
|
-
await processMigration(item.attrs.body, component, migrationFn)
|
|
227
|
+
await processMigration(item.attrs.body, component, migrationFn, storyFullSlug)
|
|
227
228
|
} catch (e) {
|
|
228
229
|
console.error(e)
|
|
229
230
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const chalk = require('chalk')
|
|
2
|
-
const StoryblokClient = require('storyblok-js-client')
|
|
3
2
|
const { findByProperty } = require('../../utils')
|
|
3
|
+
const api = require('../../utils/api')
|
|
4
4
|
|
|
5
5
|
class SyncComponentGroups {
|
|
6
6
|
/**
|
|
@@ -14,9 +14,7 @@ class SyncComponentGroups {
|
|
|
14
14
|
this.sourceComponentGroups = []
|
|
15
15
|
this.targetComponentGroups = []
|
|
16
16
|
|
|
17
|
-
this.client =
|
|
18
|
-
oauthToken: options.oauthToken
|
|
19
|
-
})
|
|
17
|
+
this.client = api.getClient()
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
async init () {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const chalk = require('chalk')
|
|
2
|
-
const StoryblokClient = require('storyblok-js-client')
|
|
3
2
|
const { find } = require('lodash')
|
|
4
3
|
const SyncComponentGroups = require('./component-groups')
|
|
5
4
|
const { findByProperty } = require('../../utils')
|
|
6
5
|
const PresetsLib = require('../../utils/presets-lib')
|
|
6
|
+
const api = require('../../utils/api')
|
|
7
7
|
|
|
8
8
|
class SyncComponents {
|
|
9
9
|
/**
|
|
@@ -16,9 +16,7 @@ class SyncComponents {
|
|
|
16
16
|
this.sourceSpaceId = options.sourceSpaceId
|
|
17
17
|
this.targetSpaceId = options.targetSpaceId
|
|
18
18
|
this.oauthToken = options.oauthToken
|
|
19
|
-
this.client =
|
|
20
|
-
oauthToken: options.oauthToken
|
|
21
|
-
})
|
|
19
|
+
this.client = api.getClient()
|
|
22
20
|
this.presetsLib = new PresetsLib({ oauthToken: options.oauthToken, targetSpaceId: this.targetSpaceId })
|
|
23
21
|
}
|
|
24
22
|
|
package/src/utils/api.js
CHANGED
|
@@ -5,21 +5,22 @@ const inquirer = require('inquirer')
|
|
|
5
5
|
|
|
6
6
|
const creds = require('./creds')
|
|
7
7
|
const getQuestions = require('./get-questions')
|
|
8
|
-
const {
|
|
8
|
+
const { SIGNUP_URL, API_URL, US_API_URL, CN_API_URL } = require('../constants')
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
11
|
accessToken: '',
|
|
12
12
|
oauthToken: '',
|
|
13
13
|
spaceId: null,
|
|
14
|
-
region: '
|
|
14
|
+
region: '',
|
|
15
15
|
|
|
16
16
|
getClient () {
|
|
17
|
-
const
|
|
17
|
+
const { region } = creds.get()
|
|
18
|
+
|
|
18
19
|
return new Storyblok({
|
|
19
20
|
accessToken: this.accessToken,
|
|
20
21
|
oauthToken: this.oauthToken,
|
|
21
22
|
region: this.region
|
|
22
|
-
},
|
|
23
|
+
}, this.apiSwitcher(region))
|
|
23
24
|
},
|
|
24
25
|
|
|
25
26
|
getPath (path) {
|
|
@@ -30,9 +31,9 @@ module.exports = {
|
|
|
30
31
|
return path
|
|
31
32
|
},
|
|
32
33
|
|
|
33
|
-
async login (email, password) {
|
|
34
|
+
async login (email, password, region) {
|
|
34
35
|
try {
|
|
35
|
-
const response = await axios.post(
|
|
36
|
+
const response = await axios.post(`${this.apiSwitcher(region)}users/login`, {
|
|
36
37
|
email: email,
|
|
37
38
|
password: password
|
|
38
39
|
})
|
|
@@ -57,26 +58,41 @@ module.exports = {
|
|
|
57
58
|
|
|
58
59
|
const { otp_attempt: code } = await inquirer.prompt(questions)
|
|
59
60
|
|
|
60
|
-
const newResponse = await axios.post(
|
|
61
|
+
const newResponse = await axios.post(`${this.apiSwitcher(region)}users/login`, {
|
|
61
62
|
email: email,
|
|
62
63
|
password: password,
|
|
63
64
|
otp_attempt: code
|
|
64
65
|
})
|
|
65
66
|
|
|
66
|
-
return this.persistCredentials(email, newResponse.data || {})
|
|
67
|
+
return this.persistCredentials(email, newResponse.data || {}, region)
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
return this.persistCredentials(email, data)
|
|
70
|
+
return this.persistCredentials(email, data, region)
|
|
70
71
|
} catch (e) {
|
|
71
72
|
return Promise.reject(e)
|
|
72
73
|
}
|
|
73
74
|
},
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
async getUser () {
|
|
77
|
+
const { region } = creds.get()
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const { data } = await axios.get(`${this.apiSwitcher(this.region ? this.region : region)}users/me`, {
|
|
81
|
+
headers: {
|
|
82
|
+
Authorization: this.oauthToken
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
return data.user
|
|
86
|
+
} catch (e) {
|
|
87
|
+
return Promise.reject(e)
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
persistCredentials (email, data, region = 'eu') {
|
|
76
92
|
const token = this.extractToken(data)
|
|
77
93
|
if (token) {
|
|
78
94
|
this.oauthToken = token
|
|
79
|
-
creds.set(email, token)
|
|
95
|
+
creds.set(email, token, region)
|
|
80
96
|
|
|
81
97
|
return Promise.resolve(data)
|
|
82
98
|
}
|
|
@@ -86,9 +102,9 @@ module.exports = {
|
|
|
86
102
|
async processLogin () {
|
|
87
103
|
try {
|
|
88
104
|
const questions = getQuestions('login')
|
|
89
|
-
const { email, password } = await inquirer.prompt(questions)
|
|
105
|
+
const { email, password, region } = await inquirer.prompt(questions)
|
|
90
106
|
|
|
91
|
-
const data = await this.login(email, password)
|
|
107
|
+
const data = await this.login(email, password, region)
|
|
92
108
|
|
|
93
109
|
console.log(chalk.green('✓') + ' Log in successfully! Token has been added to .netrc file.')
|
|
94
110
|
|
|
@@ -109,19 +125,23 @@ module.exports = {
|
|
|
109
125
|
return data.access_token
|
|
110
126
|
},
|
|
111
127
|
|
|
112
|
-
logout () {
|
|
128
|
+
logout (unauthorized) {
|
|
129
|
+
if (creds.get().email && unauthorized) {
|
|
130
|
+
console.log(chalk.red('X') + ' Your login seems to be expired, we logged you out. Please log back in again.')
|
|
131
|
+
}
|
|
113
132
|
creds.set(null)
|
|
114
133
|
},
|
|
115
134
|
|
|
116
|
-
signup (email, password) {
|
|
135
|
+
signup (email, password, region = 'eu') {
|
|
117
136
|
return axios.post(SIGNUP_URL, {
|
|
118
137
|
email: email,
|
|
119
|
-
password: password
|
|
138
|
+
password: password,
|
|
139
|
+
region
|
|
120
140
|
})
|
|
121
141
|
.then(response => {
|
|
122
142
|
const token = this.extractToken(response)
|
|
123
143
|
this.oauthToken = token
|
|
124
|
-
creds.set(email, token)
|
|
144
|
+
creds.set(email, token, region)
|
|
125
145
|
|
|
126
146
|
return Promise.resolve(true)
|
|
127
147
|
})
|
|
@@ -130,7 +150,6 @@ module.exports = {
|
|
|
130
150
|
|
|
131
151
|
isAuthorized () {
|
|
132
152
|
const { token } = creds.get() || {}
|
|
133
|
-
|
|
134
153
|
if (token) {
|
|
135
154
|
this.oauthToken = token
|
|
136
155
|
return true
|
|
@@ -226,5 +245,15 @@ module.exports = {
|
|
|
226
245
|
.get('spaces/', {})
|
|
227
246
|
.then(res => res.data.spaces || [])
|
|
228
247
|
.catch(err => Promise.reject(err))
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
apiSwitcher (region) {
|
|
251
|
+
const apiList = {
|
|
252
|
+
us: US_API_URL,
|
|
253
|
+
cn: CN_API_URL,
|
|
254
|
+
eu: API_URL
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return region ? apiList[region] : apiList[this.region]
|
|
229
258
|
}
|
|
230
259
|
}
|
package/src/utils/creds.js
CHANGED
|
@@ -25,31 +25,34 @@ const getNrcFile = () => {
|
|
|
25
25
|
const get = function () {
|
|
26
26
|
const obj = getNrcFile()
|
|
27
27
|
|
|
28
|
-
if (process.env.STORYBLOK_LOGIN && process.env.STORYBLOK_TOKEN) {
|
|
28
|
+
if (process.env.STORYBLOK_LOGIN && process.env.STORYBLOK_TOKEN && process.env.STORYBLOK_REGION) {
|
|
29
29
|
return {
|
|
30
30
|
email: process.env.STORYBLOK_LOGIN,
|
|
31
|
-
token: process.env.STORYBLOK_TOKEN
|
|
31
|
+
token: process.env.STORYBLOK_TOKEN,
|
|
32
|
+
region: process.env.STORYBLOK_REGION
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
if (process.env.TRAVIS_STORYBLOK_LOGIN && process.env.TRAVIS_STORYBLOK_TOKEN) {
|
|
36
|
+
if (process.env.TRAVIS_STORYBLOK_LOGIN && process.env.TRAVIS_STORYBLOK_TOKEN && process.env.TRAVIS_STORYBLOK_REGION) {
|
|
36
37
|
return {
|
|
37
38
|
email: process.env.TRAVIS_STORYBLOK_LOGIN,
|
|
38
|
-
token: process.env.TRAVIS_STORYBLOK_TOKEN
|
|
39
|
+
token: process.env.TRAVIS_STORYBLOK_TOKEN,
|
|
40
|
+
region: process.env.TRAVIS_STORYBLOK_REGION
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
if (Object.hasOwnProperty.call(obj, host)) {
|
|
43
45
|
return {
|
|
44
46
|
email: obj[host].login,
|
|
45
|
-
token: obj[host].password
|
|
47
|
+
token: obj[host].password,
|
|
48
|
+
region: obj[host].region
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
return null
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
const set = function (email, token) {
|
|
55
|
+
const set = function (email, token, region) {
|
|
53
56
|
const file = getFile()
|
|
54
57
|
let obj = {}
|
|
55
58
|
|
|
@@ -66,7 +69,8 @@ const set = function (email, token) {
|
|
|
66
69
|
} else {
|
|
67
70
|
obj[host] = {
|
|
68
71
|
login: email,
|
|
69
|
-
password: token
|
|
72
|
+
password: token,
|
|
73
|
+
region
|
|
70
74
|
}
|
|
71
75
|
fs.writeFileSync(file, netrc.format(obj) + os.EOL)
|
|
72
76
|
return get()
|
|
@@ -128,6 +128,19 @@ const getOptions = (subCommand, argv = {}, api = {}) => {
|
|
|
128
128
|
|
|
129
129
|
return 'Please enter a valid password:'
|
|
130
130
|
}
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: 'input',
|
|
134
|
+
name: 'region',
|
|
135
|
+
message: 'Enter your user\'s region (us, eu or cn):',
|
|
136
|
+
validate: function (value) {
|
|
137
|
+
const flagList = ['us', 'cn', 'eu']
|
|
138
|
+
if (flagList.indexOf(value) > -1) {
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return 'Please enter a valid region: us, eu or cn'
|
|
143
|
+
}
|
|
131
144
|
}
|
|
132
145
|
]
|
|
133
146
|
}
|
package/src/utils/presets-lib.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const chalk = require('chalk')
|
|
2
|
-
const StoryblokClient = require('storyblok-js-client')
|
|
3
2
|
const FormData = require('form-data')
|
|
4
3
|
const axios = require('axios')
|
|
5
4
|
const { last } = require('lodash')
|
|
5
|
+
const api = require('./api')
|
|
6
6
|
|
|
7
7
|
class PresetsLib {
|
|
8
8
|
/**
|
|
@@ -10,9 +10,7 @@ class PresetsLib {
|
|
|
10
10
|
*/
|
|
11
11
|
constructor (options) {
|
|
12
12
|
this.oauthToken = options.oauthToken
|
|
13
|
-
this.client =
|
|
14
|
-
oauthToken: options.oauthToken
|
|
15
|
-
})
|
|
13
|
+
this.client = api.getClient()
|
|
16
14
|
this.targetSpaceId = options.targetSpaceId
|
|
17
15
|
}
|
|
18
16
|
|
package/tests/constants.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const EMAIL_TEST = 'test@storyblok.com'
|
|
2
2
|
const PASSWORD_TEST = 'test'
|
|
3
3
|
const TOKEN_TEST = 'storyblok1234'
|
|
4
|
+
const REGION_TEST = 'eu'
|
|
4
5
|
|
|
5
6
|
// use functions to always returns "new" data
|
|
6
7
|
const FAKE_COMPONENTS = () => [
|
|
@@ -255,5 +256,6 @@ module.exports = {
|
|
|
255
256
|
PASSWORD_TEST,
|
|
256
257
|
FAKE_COMPONENTS,
|
|
257
258
|
FAKE_SPACES,
|
|
258
|
-
FAKE_SPACE_OPTIONS
|
|
259
|
+
FAKE_SPACE_OPTIONS,
|
|
260
|
+
REGION_TEST
|
|
259
261
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const deleteComponent = require('../../src/tasks/delete-component')
|
|
2
|
+
const { FAKE_COMPONENTS } = require('../constants')
|
|
3
|
+
|
|
4
|
+
describe('testing deleteComponent', () => {
|
|
5
|
+
it('api.deleteComponent name', () => {
|
|
6
|
+
const api = {
|
|
7
|
+
getComponents: jest.fn(() => Promise.resolve(FAKE_COMPONENTS())),
|
|
8
|
+
delete: jest.fn(() => Promise.resolve())
|
|
9
|
+
}
|
|
10
|
+
return deleteComponent(api, { comp: 'teaser' }).then(() => {
|
|
11
|
+
expect(api.delete.mock.calls.length).toBe(1)
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
it('api.deleteComponent id', () => {
|
|
15
|
+
const comp = FAKE_COMPONENTS()[0]
|
|
16
|
+
const api = {
|
|
17
|
+
get: jest.fn(() => Promise.resolve({ data: { component: comp } })),
|
|
18
|
+
delete: jest.fn(() => Promise.resolve())
|
|
19
|
+
}
|
|
20
|
+
return deleteComponent(api, { comp: 0 }).then(() => {
|
|
21
|
+
expect(api.get.mock.calls.length).toBe(1)
|
|
22
|
+
expect(api.delete.mock.calls.length).toBe(1)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
it('api.deleteComponent name dryrun', () => {
|
|
26
|
+
const api = {
|
|
27
|
+
getComponents: jest.fn(() => Promise.resolve(FAKE_COMPONENTS())),
|
|
28
|
+
delete: jest.fn(() => Promise.resolve())
|
|
29
|
+
}
|
|
30
|
+
return deleteComponent(api, { comp: 'teaser', dryrun: true }).then(() => {
|
|
31
|
+
expect(api.delete.mock.calls.length).toBe(0)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
it('api.deleteComponent not found', () => {
|
|
35
|
+
const api = {
|
|
36
|
+
getComponents: jest.fn(() => Promise.resolve(FAKE_COMPONENTS())),
|
|
37
|
+
delete: jest.fn(() => Promise.resolve())
|
|
38
|
+
}
|
|
39
|
+
return expect(deleteComponent(api, { comp: 'not a fake component' }).then(() => {
|
|
40
|
+
expect(api.delete.mock.calls.length).toBe(0)
|
|
41
|
+
})).rejects.toThrow('Component not a fake component not found.')
|
|
42
|
+
})
|
|
43
|
+
it('api.deleteComponent not found by id', () => {
|
|
44
|
+
const api = {
|
|
45
|
+
get: jest.fn(() => Promise.reject(new Error('Not Found'))),
|
|
46
|
+
delete: jest.fn(() => Promise.resolve())
|
|
47
|
+
}
|
|
48
|
+
return expect(deleteComponent(api, { comp: 1 }).then(() => {
|
|
49
|
+
expect(api.get.mock.calls.length).toBe(1)
|
|
50
|
+
expect(api.delete.mock.calls.length).toBe(0)
|
|
51
|
+
})).rejects.toThrow('Not Found')
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const deleteComponents = require('../../src/tasks/delete-components')
|
|
2
|
+
const { FAKE_COMPONENTS } = require('../constants')
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
jest.mock('fs')
|
|
5
|
+
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
jest.clearAllMocks()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
describe('testing deleteComponents', () => {
|
|
11
|
+
it('api.deleteComponents', () => {
|
|
12
|
+
const source = 'components.js'
|
|
13
|
+
const components = FAKE_COMPONENTS()
|
|
14
|
+
const spy = jest.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({
|
|
15
|
+
components
|
|
16
|
+
}))
|
|
17
|
+
const api = {
|
|
18
|
+
get: jest.fn((path) => {
|
|
19
|
+
const id = path.split('/')[1]
|
|
20
|
+
return Promise.resolve({ data: { component: components[id] } })
|
|
21
|
+
}),
|
|
22
|
+
delete: jest.fn(() => Promise.resolve())
|
|
23
|
+
}
|
|
24
|
+
return deleteComponents(api, { source, reversed: false }).then(() => {
|
|
25
|
+
expect(spy.mock.calls.length).toBe(1)
|
|
26
|
+
expect(api.delete.mock.calls.length).toBe(components.length)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
it('api.deleteComponents reverse', () => {
|
|
30
|
+
const source = 'components.js'
|
|
31
|
+
const components = FAKE_COMPONENTS()
|
|
32
|
+
const spy = jest.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({
|
|
33
|
+
components
|
|
34
|
+
}))
|
|
35
|
+
const api = {
|
|
36
|
+
get: jest.fn((path) => {
|
|
37
|
+
const id = path.split('/')[1]
|
|
38
|
+
return Promise.resolve({ data: { component: components[id] } })
|
|
39
|
+
}),
|
|
40
|
+
getComponents: jest.fn(() => {
|
|
41
|
+
const copy = [...components]
|
|
42
|
+
copy.splice(3, 1)
|
|
43
|
+
return copy
|
|
44
|
+
}),
|
|
45
|
+
delete: jest.fn(() => Promise.resolve())
|
|
46
|
+
}
|
|
47
|
+
return deleteComponents(api, { source, reversed: true }).then(() => {
|
|
48
|
+
expect(spy.mock.calls.length).toBe(1)
|
|
49
|
+
expect(api.delete.mock.calls.length).toBe(1)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
it('api.deleteComponents --dryrun', () => {
|
|
53
|
+
const source = 'components.js'
|
|
54
|
+
const components = FAKE_COMPONENTS()
|
|
55
|
+
const spy = jest.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({
|
|
56
|
+
components
|
|
57
|
+
}))
|
|
58
|
+
const api = {
|
|
59
|
+
get: jest.fn((path) => {
|
|
60
|
+
const id = path.split('/')[1]
|
|
61
|
+
return Promise.resolve({ data: { component: components[id] } })
|
|
62
|
+
}),
|
|
63
|
+
delete: jest.fn(() => Promise.resolve())
|
|
64
|
+
}
|
|
65
|
+
return deleteComponents(api, { source, reversed: false, dryRun: true }).then(() => {
|
|
66
|
+
expect(spy.mock.calls.length).toBe(1)
|
|
67
|
+
expect(api.delete.mock.calls.length).toBe(0)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
it('api.deleteComponents reverse --dryrun', () => {
|
|
71
|
+
const source = 'components.js'
|
|
72
|
+
const components = FAKE_COMPONENTS()
|
|
73
|
+
const spy = jest.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({
|
|
74
|
+
components
|
|
75
|
+
}))
|
|
76
|
+
const api = {
|
|
77
|
+
get: jest.fn((path) => {
|
|
78
|
+
const id = path.split('/')[1]
|
|
79
|
+
return Promise.resolve({ data: { component: components[id] } })
|
|
80
|
+
}),
|
|
81
|
+
getComponents: jest.fn(() => {
|
|
82
|
+
const copy = [...components]
|
|
83
|
+
copy.splice(3, 1)
|
|
84
|
+
return copy
|
|
85
|
+
}),
|
|
86
|
+
delete: jest.fn(() => Promise.resolve())
|
|
87
|
+
}
|
|
88
|
+
return deleteComponents(api, { source, reversed: true, dryRun: true }).then(() => {
|
|
89
|
+
expect(spy.mock.calls.length).toBe(1)
|
|
90
|
+
expect(api.delete.mock.calls.length).toBe(0)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const api = require('../../src/utils/api')
|
|
2
2
|
const creds = require('../../src/utils/creds')
|
|
3
|
-
const { EMAIL_TEST, TOKEN_TEST, PASSWORD_TEST } = require('../constants')
|
|
3
|
+
const { EMAIL_TEST, TOKEN_TEST, PASSWORD_TEST, REGION_TEST } = require('../constants')
|
|
4
4
|
|
|
5
5
|
jest.mock('axios')
|
|
6
6
|
|
|
@@ -19,7 +19,8 @@ describe('api.login() method', () => {
|
|
|
19
19
|
|
|
20
20
|
expect(creds.get()).toEqual({
|
|
21
21
|
email: EMAIL_TEST,
|
|
22
|
-
token: TOKEN_TEST
|
|
22
|
+
token: TOKEN_TEST,
|
|
23
|
+
region: REGION_TEST
|
|
23
24
|
})
|
|
24
25
|
} catch (e) {
|
|
25
26
|
console.error(e)
|
|
@@ -28,7 +29,7 @@ describe('api.login() method', () => {
|
|
|
28
29
|
|
|
29
30
|
it('when login is incorrect, the .netrc file is not populated and throw a reject message', async () => {
|
|
30
31
|
try {
|
|
31
|
-
await api.login(EMAIL_TEST, '1234')
|
|
32
|
+
await api.login(EMAIL_TEST, '1234', REGION_TEST)
|
|
32
33
|
} catch (e) {
|
|
33
34
|
expect(e.message).toBe('Incorrect access')
|
|
34
35
|
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Feature request
|
|
3
|
-
about: Suggest an idea for Storyblok
|
|
4
|
-
title: ''
|
|
5
|
-
labels: enhancement
|
|
6
|
-
assignees: ''
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
**The feature would affect:** (check one with "x")
|
|
11
|
-
- [ ] *app.storyblok.com (CMS - Interface)*
|
|
12
|
-
- [ ] *api.storyblok.com (CMS - Content Delivery API)*
|
|
13
|
-
- [ ] *mapi.storyblok.com (CMS - Management API)*
|
|
14
|
-
- [ ] *a.storyblok.com (CMS - Image Service)*
|
|
15
|
-
- [ ] *Other* <!-- => If you've got an issue with on of our boilerplates or themes - please create an issue in the specific repo -->
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
**Is your feature request related to a problem? Please describe.**
|
|
19
|
-
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
20
|
-
|
|
21
|
-
**Describe the solution you'd like**
|
|
22
|
-
A clear and concise description of what you want to happen.
|
|
23
|
-
|
|
24
|
-
**Describe alternatives you've considered**
|
|
25
|
-
A clear and concise description of any alternative solutions or features you've considered.
|
|
26
|
-
|
|
27
|
-
**Additional context**
|
|
28
|
-
Add any other context or screenshots about the feature request here.
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
**The issue occurs while working with:** (check one with "x")
|
|
2
|
-
- [ ] *app.storyblok.com (CMS - Interface)*
|
|
3
|
-
- [ ] *api.storyblok.com (CMS - Content Delivery API)*
|
|
4
|
-
- [ ] *mapi.storyblok.com (CMS - Management API)*
|
|
5
|
-
- [ ] *capi.storyblok.com (Commerce - API)*
|
|
6
|
-
- [ ] *Commerce - Interface*
|
|
7
|
-
- [ ] *Other* <!-- => If you've got an issue with on of our boilerplates or themes - please create an issue in the specific repo -->
|
|
8
|
-
|
|
9
|
-
**I'm submitting a ...** (check one with "x")
|
|
10
|
-
- [ ] bug report
|
|
11
|
-
- [ ] feature request
|
|
12
|
-
- [ ] question
|
|
13
|
-
|
|
14
|
-
**Current behavior:**
|
|
15
|
-
<!-- Describe how the bug manifests. -->
|
|
16
|
-
|
|
17
|
-
**Expected behavior:**
|
|
18
|
-
<!-- Describe what the behavior would be without the bug. -->
|
|
19
|
-
|
|
20
|
-
**Steps to reproduce:**
|
|
21
|
-
<!-- If you are able to illustrate the bug or feature request with an example, please provide steps to reproduce and if possible also a demo.-->
|
|
22
|
-
|
|
23
|
-
**Related code:**
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
insert any relevant code here
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
**Other information:**
|
|
30
|
-
<!-- List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to fix, Stack Overflow links, forum links, etc. -->
|
|
31
|
-
|
package/.github/labeler.yml
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
name: "Issue Labeler"
|
|
2
|
-
on:
|
|
3
|
-
issues:
|
|
4
|
-
types: [opened]
|
|
5
|
-
|
|
6
|
-
jobs:
|
|
7
|
-
triage:
|
|
8
|
-
runs-on: ubuntu-latest
|
|
9
|
-
steps:
|
|
10
|
-
- uses: github/issue-labeler@v2.5
|
|
11
|
-
with:
|
|
12
|
-
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
|
13
|
-
configuration-path: .github/labeler.yml
|
|
14
|
-
enable-versioned-regex: 0
|