storyblok 3.19.1 → 3.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -2
- package/package.json +1 -1
- package/src/cli.js +22 -4
- package/src/tasks/sync-commands/components.js +9 -0
- package/src/tasks/sync.js +9 -2
- package/src/utils/build-filter-query.js +23 -0
- package/src/utils/index.js +2 -1
package/README.md
CHANGED
|
@@ -37,7 +37,10 @@ $ storyblok login
|
|
|
37
37
|
* `password`: your user's password
|
|
38
38
|
|
|
39
39
|
##### Options for Login with token (Recomended to SSO user's but works with all user accounts)
|
|
40
|
-
* `token`: your access token
|
|
40
|
+
* `token`: your personal access token
|
|
41
|
+
|
|
42
|
+
**Get your personal access token**
|
|
43
|
+
* Go to [https://app.storyblok.com/#/me/account?tab=token](https://app.storyblok.com/#/me/account?tab=token) and click on Generate new token.
|
|
41
44
|
|
|
42
45
|
**For Both login options you nedd to pass the region**
|
|
43
46
|
|
|
@@ -47,7 +50,7 @@ $ storyblok login
|
|
|
47
50
|
You can also add the token directly from the login’s command, like the example below:
|
|
48
51
|
|
|
49
52
|
```sh
|
|
50
|
-
$ storyblok login --token <
|
|
53
|
+
$ storyblok login --token <PERSONAL_ACCESS_TOKEN> --region eu
|
|
51
54
|
```
|
|
52
55
|
|
|
53
56
|
### logout
|
|
@@ -225,6 +228,10 @@ $ storyblok sync --type <COMMAND> --source <SPACE_ID> --target <SPACE_ID>
|
|
|
225
228
|
* `type`: describe the command type to execute. Can be: `folders`, `components`, `stories`, `datasources` or `roles`. It's possible pass multiple types separated by comma (`,`).
|
|
226
229
|
* `source`: the source space to use to sync
|
|
227
230
|
* `target`: the target space to use to sync
|
|
231
|
+
* `starts-with`: sync only stories that starts with the given string
|
|
232
|
+
* `filter`: sync stories based on the given filter. Required Options: Required options: `--keys`, `--operations`, `--values`
|
|
233
|
+
* `keys`: Multiple keys should be separated by comma. Example: `--keys key1,key2`, `--keys key1`
|
|
234
|
+
* `operations`: Operations to be used for filtering. Can be: `is`, `in`, `not_in`, `like`, `not_like`, `any_in_array`, `all_in_array`, `gt_date`, `lt_date`, `gt_int`, `lt_int`, `gt_float`, `lt_float`. Multiple operations should be separated by comma.
|
|
228
235
|
|
|
229
236
|
#### Examples
|
|
230
237
|
|
|
@@ -234,6 +241,15 @@ $ storyblok sync --type components --source 00001 --target 00002
|
|
|
234
241
|
|
|
235
242
|
# Sync components and stories from `00001` space to `00002` space
|
|
236
243
|
$ storyblok sync --type components,stories --source 00001 --target 00002
|
|
244
|
+
|
|
245
|
+
# Sync only stories that starts with `myStartsWithString` from `00001` space to `00002` space
|
|
246
|
+
$ storyblok sync --type stories --source 00001 --target 00002 --starts-with myStartsWithString
|
|
247
|
+
|
|
248
|
+
# Sync only stories with a category field like `reference` from `00001` space to `00002` space
|
|
249
|
+
$ storyblok sync --type stories --source 00001 --target 00002 --filter --keys category --operations like --values reference
|
|
250
|
+
|
|
251
|
+
# Sync only stories with a category field like `reference` and a name field not like `demo` from `00001` space to `00002` space
|
|
252
|
+
$ storyblok sync --type stories --source 00001 --target 00002 --filter --keys category,name --operations like,not_like --values reference,demo
|
|
237
253
|
```
|
|
238
254
|
|
|
239
255
|
### quickstart
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ const updateNotifier = require('update-notifier')
|
|
|
12
12
|
const pkg = require('../package.json')
|
|
13
13
|
|
|
14
14
|
const tasks = require('./tasks')
|
|
15
|
-
const { getQuestions, lastStep, api, creds } = require('./utils')
|
|
15
|
+
const { getQuestions, lastStep, api, creds, buildFilterQuery } = require('./utils')
|
|
16
16
|
const { SYNC_TYPES, COMMANDS } = require('./constants')
|
|
17
17
|
|
|
18
18
|
clear()
|
|
@@ -268,6 +268,12 @@ program
|
|
|
268
268
|
.requiredOption('--type <TYPE>', 'Define what will be sync. Can be components, folders, stories, datasources or roles')
|
|
269
269
|
.requiredOption('--source <SPACE_ID>', 'Source space id')
|
|
270
270
|
.requiredOption('--target <SPACE_ID>', 'Target space id')
|
|
271
|
+
.option('--starts-with <STARTS_WITH>', 'Sync only stories that starts with the given string')
|
|
272
|
+
.option('--filter', 'Enable filter options to sync only stories that match the given filter. Required options: --keys; --operations; --values')
|
|
273
|
+
.option('--keys <KEYS>', 'Field names in your story object which should be used for filtering. Multiple keys should separated by comma.')
|
|
274
|
+
.option('--operations <OPERATIONS>', 'Operations to be used for filtering. Can be: is, in, not_in, like, not_like, any_in_array, all_in_array, gt_date, lt_date, gt_int, lt_int, gt_float, lt_float. Multiple operations should be separated by comma.')
|
|
275
|
+
.option('--values <VALUES>', 'Values to be used for filtering. Any string or number. If you want to use multiple values, separate them with a comma. Multiple values should be separated by comma.')
|
|
276
|
+
.option('--components-groups <UUIDs>', 'Synchronize components based on their group UUIDs separated by commas')
|
|
271
277
|
.action(async (options) => {
|
|
272
278
|
console.log(`${chalk.blue('-')} Sync data between spaces\n`)
|
|
273
279
|
|
|
@@ -279,9 +285,17 @@ program
|
|
|
279
285
|
const {
|
|
280
286
|
type,
|
|
281
287
|
source,
|
|
282
|
-
target
|
|
288
|
+
target,
|
|
289
|
+
startsWith,
|
|
290
|
+
filter,
|
|
291
|
+
keys,
|
|
292
|
+
operations,
|
|
293
|
+
values,
|
|
294
|
+
componentsGroups
|
|
283
295
|
} = options
|
|
284
296
|
|
|
297
|
+
const _componentsGroups = componentsGroups ? componentsGroups.split(',') : null
|
|
298
|
+
|
|
285
299
|
const _types = type.split(',') || []
|
|
286
300
|
_types.forEach(_type => {
|
|
287
301
|
if (!SYNC_TYPES.includes(_type)) {
|
|
@@ -289,13 +303,17 @@ program
|
|
|
289
303
|
}
|
|
290
304
|
})
|
|
291
305
|
|
|
292
|
-
const
|
|
306
|
+
const filterQuery = filter ? buildFilterQuery(keys, operations, values) : undefined
|
|
293
307
|
|
|
308
|
+
const token = creds.get().token || null
|
|
294
309
|
await tasks.sync(_types, {
|
|
295
310
|
api,
|
|
296
311
|
token,
|
|
297
312
|
source,
|
|
298
|
-
target
|
|
313
|
+
target,
|
|
314
|
+
startsWith,
|
|
315
|
+
filterQuery,
|
|
316
|
+
_componentsGroups
|
|
299
317
|
})
|
|
300
318
|
|
|
301
319
|
console.log('\n' + chalk.green('✓') + ' Sync data between spaces successfully completed')
|
|
@@ -18,6 +18,7 @@ class SyncComponents {
|
|
|
18
18
|
this.oauthToken = options.oauthToken
|
|
19
19
|
this.client = api.getClient()
|
|
20
20
|
this.presetsLib = new PresetsLib({ oauthToken: options.oauthToken, targetSpaceId: this.targetSpaceId })
|
|
21
|
+
this.componentsGroups = options.componentsGroups
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
async sync () {
|
|
@@ -70,6 +71,14 @@ class SyncComponents {
|
|
|
70
71
|
|
|
71
72
|
const sourceGroupUuid = component.component_group_uuid
|
|
72
73
|
|
|
74
|
+
if (this.componentsGroups && !this.componentsGroups.includes(sourceGroupUuid)) {
|
|
75
|
+
console.log(
|
|
76
|
+
chalk.yellow("-") +
|
|
77
|
+
` Component ${component.name} does not belong to the ${this.componentsGroups} group(s).`
|
|
78
|
+
);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
// if the component belongs to a group
|
|
74
83
|
if (sourceGroupUuid) {
|
|
75
84
|
const sourceGroup = findByProperty(
|
package/src/tasks/sync.js
CHANGED
|
@@ -3,6 +3,7 @@ const chalk = require('chalk')
|
|
|
3
3
|
const SyncComponents = require('./sync-commands/components')
|
|
4
4
|
const SyncDatasources = require('./sync-commands/datasources')
|
|
5
5
|
const { capitalize } = require('../utils')
|
|
6
|
+
const { startsWith } = require('lodash/string')
|
|
6
7
|
|
|
7
8
|
const SyncSpaces = {
|
|
8
9
|
targetComponents: [],
|
|
@@ -14,7 +15,10 @@ const SyncSpaces = {
|
|
|
14
15
|
this.sourceSpaceId = options.source
|
|
15
16
|
this.targetSpaceId = options.target
|
|
16
17
|
this.oauthToken = options.token
|
|
18
|
+
this.startsWith = options.startsWith
|
|
19
|
+
this.filterQuery = options.filterQuery
|
|
17
20
|
this.client = api.getClient()
|
|
21
|
+
this.componentsGroups = options._componentsGroups
|
|
18
22
|
},
|
|
19
23
|
|
|
20
24
|
async getStoryWithTranslatedSlugs (sourceStory, targetStory) {
|
|
@@ -56,7 +60,9 @@ const SyncSpaces = {
|
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
const all = await this.client.getAll(`spaces/${this.sourceSpaceId}/stories`, {
|
|
59
|
-
story_only: 1
|
|
63
|
+
story_only: 1,
|
|
64
|
+
...(this.startsWith ? { starts_with: this.startsWith } : {}),
|
|
65
|
+
...(this.filterQuery ? { filter_query: this.filterQuery } : {})
|
|
60
66
|
})
|
|
61
67
|
|
|
62
68
|
for (let i = 0; i < all.length; i++) {
|
|
@@ -226,7 +232,8 @@ const SyncSpaces = {
|
|
|
226
232
|
const syncComponentsInstance = new SyncComponents({
|
|
227
233
|
sourceSpaceId: this.sourceSpaceId,
|
|
228
234
|
targetSpaceId: this.targetSpaceId,
|
|
229
|
-
oauthToken: this.oauthToken
|
|
235
|
+
oauthToken: this.oauthToken,
|
|
236
|
+
componentsGroups: this.componentsGroups
|
|
230
237
|
})
|
|
231
238
|
|
|
232
239
|
try {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const buildFilterQuery = (keys, operations, values) => {
|
|
2
|
+
const operators = ['is', 'in', 'not_in', 'like', 'not_like', 'any_in_array', 'all_in_array', 'gt_date', 'lt_date', 'gt_int', 'lt_int', 'gt_float', 'lt_float']
|
|
3
|
+
if (!keys || !operations || !values) {
|
|
4
|
+
throw new Error('Filter options are required: --keys; --operations; --values')
|
|
5
|
+
}
|
|
6
|
+
const _keys = keys.split(',')
|
|
7
|
+
const _operations = operations.split(',')
|
|
8
|
+
const _values = values.split(',')
|
|
9
|
+
if (_keys.length !== _operations.length || _keys.length !== _values.length) {
|
|
10
|
+
throw new Error('The number of keys, operations and values must be the same')
|
|
11
|
+
}
|
|
12
|
+
const invalidOperators = _operations.filter((o) => !operators.includes(o))
|
|
13
|
+
if (invalidOperators.length) {
|
|
14
|
+
throw new Error('Invalid operator(s) applied for filter: ' + invalidOperators.join(' '))
|
|
15
|
+
}
|
|
16
|
+
const filterQuery = {}
|
|
17
|
+
_keys.forEach((key, index) => {
|
|
18
|
+
filterQuery[key] = { [_operations[index]]: _values[index] }
|
|
19
|
+
})
|
|
20
|
+
return filterQuery
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = buildFilterQuery
|
package/src/utils/index.js
CHANGED