storyblok 3.18.0 → 3.19.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/README.md CHANGED
@@ -43,6 +43,13 @@ $ storyblok login
43
43
 
44
44
  * `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.
45
45
 
46
+ #### Login with token flag
47
+ You can also add the token directly from the login’s command, like the example below:
48
+
49
+ ```sh
50
+ $ storyblok login --token <YOUR_OUTH_TOKEN> --region eu
51
+ ```
52
+
46
53
  ### logout
47
54
 
48
55
  Logout from the Storyblok cli
@@ -81,27 +88,33 @@ $ storyblok pull-languages --space <SPACE_ID>
81
88
 
82
89
  ### pull-components
83
90
 
84
- Download your space's components schema as json. This command will download 2 files: 1 for the components and 1 for the presets.
91
+ Download your space's components schema as json. By default this command will download 2 files: 1 for the components and 1 for the presets; But if you pass a flag `--separate-files or --sf` the command will create file for each component and presets. And also you could pass a path `--path or -p` to save your components and presets.
92
+
93
+ ```sh
94
+ $ storyblok pull-components --space <SPACE_ID> # Will save files like components-1234.json
95
+ ```
85
96
 
86
97
  ```sh
87
- $ storyblok pull-components --space <SPACE_ID> --region <REGION>
98
+ $ storyblok pull-components --space <SPACE_ID> --separate-files # Will save files like feature-1234.json grid-1234.json
88
99
  ```
89
100
 
90
101
  #### Options
91
102
 
92
103
  * `space`: your space id
104
+ * `separate-files`: boolean flag to save components and presets in single files instead a file with all
105
+ * `path`: the path to save your components and preset files
93
106
 
94
107
  ### push-components
95
108
 
96
109
  Push your components file to your/another space
97
110
 
98
111
  ```sh
99
- $ storyblok push-components <SOURCE> --space <SPACE_ID> --region <REGION> --presets-source <PRESETS_SOURCE>
112
+ $ storyblok push-components <SOURCE> --space <SPACE_ID> --presets-source <PRESETS_SOURCE>
100
113
  ```
101
114
 
102
115
  #### Parameters
103
116
 
104
- * `source`: can be a URL or path to JSON file.
117
+ * `source`: can be a URL or path to JSON file, the path to a json file could be to a single or multiple files separated by comma, like `./pages-1234.json,../User/components/grid-1234.json`
105
118
 
106
119
  Using an **URL**
107
120
 
@@ -109,12 +122,18 @@ Using an **URL**
109
122
  $ storyblok push-components https://raw.githubusercontent.com/storyblok/nuxtdoc/master/seed.components.json --space 67819
110
123
  ```
111
124
 
112
- Using a **path** to file
125
+ Using a **path** to a single file
113
126
 
114
127
  ```sh
115
128
  $ storyblok push-components ./components.json --space 67819
116
129
  ```
117
130
 
131
+ Using a **path** to a multiple files
132
+
133
+ ```sh
134
+ $ storyblok push-components ./page.json,../grid.json,./feature.json --space 67819
135
+ ```
136
+
118
137
  #### Options
119
138
 
120
139
  * `space`: your space id
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storyblok",
3
- "version": "3.18.0",
3
+ "version": "3.19.1",
4
4
  "description": "A simple CLI to start Storyblok from your command line.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,7 +25,7 @@
25
25
  "author": "Dominik Angerer <dominikangerer1@gmail.com>, Alexander Feiglstorfer <delooks@gmail.com>",
26
26
  "license": "MIT",
27
27
  "dependencies": {
28
- "axios": "^0.21.1",
28
+ "axios": "^0.27.2",
29
29
  "chalk": "^4.1.0",
30
30
  "clear": "0.1.0",
31
31
  "commander": "^5.1.0",
package/src/cli.js CHANGED
@@ -39,14 +39,18 @@ program
39
39
  program
40
40
  .command(COMMANDS.LOGIN)
41
41
  .description('Login to the Storyblok cli')
42
+ .option('-t, --token <token>', 'Token to login directly without questions, like for CI enviroments')
43
+ .option('-r, --region <region>', 'Region of the user')
42
44
  .action(async (options) => {
45
+ const { token, region } = options
46
+
43
47
  if (api.isAuthorized()) {
44
48
  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
49
  return
46
50
  }
47
51
 
48
52
  try {
49
- await api.processLogin()
53
+ await api.processLogin(token, region)
50
54
  process.exit(0)
51
55
  } catch (e) {
52
56
  console.log(chalk.red('X') + ' An error occurred when logging the user: ' + e.message)
@@ -115,10 +119,13 @@ program
115
119
  // pull-components
116
120
  program
117
121
  .command(COMMANDS.PULL_COMPONENTS)
122
+ .option('--sf, --separate-files [value]', 'Argument to create a single file for each component')
123
+ .option('-p, --path <path>', 'Path to save the component files')
118
124
  .description("Download your space's components schema as json")
119
- .action(async () => {
125
+ .action(async (options) => {
120
126
  console.log(`${chalk.blue('-')} Executing pull-components task`)
121
127
  const space = program.space
128
+ const { separateFiles, path } = options
122
129
  if (!space) {
123
130
  console.log(chalk.red('X') + ' Please provide the space as argument --space YOUR_SPACE_ID.')
124
131
  process.exit(0)
@@ -130,7 +137,7 @@ program
130
137
  }
131
138
 
132
139
  api.setSpaceId(space)
133
- await tasks.pullComponents(api, { space })
140
+ await tasks.pullComponents(api, { space, separateFiles, path })
134
141
  } catch (e) {
135
142
  errorHandler(e, COMMANDS.PULL_COMPONENTS)
136
143
  }
@@ -1,5 +1,5 @@
1
- const fs = require('fs')
2
1
  const chalk = require('chalk')
2
+ const saveFileFactory = require('../utils/save-file-factory')
3
3
 
4
4
  /**
5
5
  * @method getNameFromComponentGroups
@@ -20,11 +20,11 @@ const getNameFromComponentGroups = (groups, uuid) => {
20
20
  /**
21
21
  * @method pullComponents
22
22
  * @param {Object} api
23
- * @param {Object} options { space: Number }
23
+ * @param {Object} options { space: Number, separateFiles: Boolean, path: String }
24
24
  * @return {Promise<Object>}
25
25
  */
26
26
  const pullComponents = async (api, options) => {
27
- const { space } = options
27
+ const { space, separateFiles, path } = options
28
28
 
29
29
  try {
30
30
  const componentGroups = await api.getComponentGroups()
@@ -41,33 +41,40 @@ const pullComponents = async (api, options) => {
41
41
  }
42
42
  })
43
43
 
44
+ if (separateFiles) {
45
+ for (const comp in components) {
46
+ const compFileName = `${components[comp].name}-${space}.json`
47
+ const data = JSON.stringify(components[comp], null, 2)
48
+ saveFileFactory(compFileName, data, path)
49
+ }
50
+ console.log(`${chalk.green('✓')} We've saved your components in files with the names of each component`)
51
+
52
+ if (presets.length === 0) return
53
+
54
+ for (const preset in presets) {
55
+ const presetFileName = `${presets[preset].name}-${space}.json`
56
+ const data = JSON.stringify(presets[preset], null, 2)
57
+ saveFileFactory(presetFileName, data, path)
58
+ }
59
+ console.log(`${chalk.green('✓')} We've saved your presets in files with the names of each preset`)
60
+ return
61
+ }
62
+
44
63
  const file = `components.${space}.json`
45
64
  const data = JSON.stringify({ components }, null, 2)
46
65
 
47
66
  console.log(`${chalk.green('✓')} We've saved your components in the file: ${file}`)
48
67
 
49
- fs.writeFile(`./${file}`, data, err => {
50
- if (err) {
51
- Promise.reject(err)
52
- return
53
- }
68
+ saveFileFactory(file, data, path)
54
69
 
55
- Promise.resolve(file)
56
- })
70
+ if (presets.length === 0) return
57
71
 
58
72
  const presetsFile = `presets.${space}.json`
59
73
  const presetsData = JSON.stringify({ presets }, null, 2)
60
74
 
61
75
  console.log(`${chalk.green('✓')} We've saved your presets in the file: ${presetsFile}`)
62
76
 
63
- fs.writeFile(`./${presetsFile}`, presetsData, err => {
64
- if (err) {
65
- Promise.reject(err)
66
- return
67
- }
68
-
69
- Promise.resolve(presetsFile)
70
- })
77
+ saveFileFactory(presetsFile, presetsData, path)
71
78
  } catch (e) {
72
79
  console.error(`${chalk.red('X')} An error ocurred in pull-components task when load components data`)
73
80
  return Promise.reject(new Error(e))
@@ -21,30 +21,53 @@ const getGroupByUuid = (groups, uuid) => {
21
21
 
22
22
  /**
23
23
  * Get the data from a local or remote JSON file
24
- * @param {string} source the local path or remote url of the file
24
+ * @param {string} path the local path or remote url of the file
25
25
  * @returns {Promise<Object>} return the data from the source or an error
26
26
  */
27
- const getDataFromSource = async (source) => {
28
- if (!source) {
27
+ const getDataFromPath = async (path) => {
28
+ if (!path) {
29
29
  return {}
30
30
  }
31
+ const sources = path.split(',')
32
+ const isList = sources.length > 1
31
33
 
32
34
  try {
33
- if (isUrl(source)) {
34
- return (await axios.get(source)).data
35
+ if (isUrl(path)) {
36
+ return (await axios.get(path)).data
35
37
  } else {
36
- return JSON.parse(fs.readFileSync(source, 'utf8'))
38
+ if (!isList) return JSON.parse(fs.readFileSync(sources[0], 'utf8'))
39
+
40
+ const data = []
41
+ sources.forEach((source) => {
42
+ data.push(JSON.parse(fs.readFileSync(source, 'utf8')))
43
+ })
44
+ return data
37
45
  }
38
46
  } catch (err) {
39
- console.error(`${chalk.red('X')} Can not load json file from ${source}`)
47
+ console.error(`${chalk.red('X')} Can not load json file from ${path}`)
40
48
  return Promise.reject(err)
41
49
  }
42
50
  }
43
51
 
52
+ /**
53
+ * Creat an array based in the content parameter and the key provided
54
+ * @param {object} content the data to create a list
55
+ * @param {string} key key to serch in the content
56
+ * @returns {Array} return the data from the source or an error
57
+ */
58
+ const createContentList = (content, key) => {
59
+ if (content[key]) return content[key]
60
+ else if (Array.isArray(content)) return [...content]
61
+ else return [content]
62
+ }
63
+
44
64
  module.exports = async (api, { source, presetsSource }) => {
45
65
  try {
46
- const components = (await getDataFromSource(source)).components || []
47
- const presets = (await getDataFromSource(presetsSource)).presets || []
66
+ const rawComponents = await getDataFromPath(source)
67
+ const components = createContentList(rawComponents, 'components')
68
+ const rawPresets = await getDataFromPath(presetsSource)
69
+ const presets = createContentList(rawPresets, 'presets')
70
+
48
71
  return push(api, components, presets)
49
72
  } catch (err) {
50
73
  console.error(`${chalk.red('X')} Can not push invalid json - please provide a valid json file`)
package/src/utils/api.js CHANGED
@@ -103,8 +103,14 @@ module.exports = {
103
103
  return Promise.reject(new Error('The code could not be authenticated.'))
104
104
  },
105
105
 
106
- async processLogin () {
106
+ async processLogin (token = null, region = null) {
107
107
  try {
108
+ if (token && region) {
109
+ await this.loginWithToken({ token, region })
110
+ console.log(chalk.green('✓') + ' Log in successfully! Token has been added to .netrc file.')
111
+ return Promise.resolve({ token, region })
112
+ }
113
+
108
114
  let content = {}
109
115
  await inquirer
110
116
  .prompt(getQuestions('login-strategy'))
@@ -5,5 +5,6 @@ module.exports = {
5
5
  creds: require('./creds'),
6
6
  capitalize: require('./capitalize'),
7
7
  findByProperty: require('./find-by-property'),
8
- parseError: require('./parse-error')
8
+ parseError: require('./parse-error'),
9
+ saveFileFactory: require('./save-file-factory')
9
10
  }
@@ -0,0 +1,16 @@
1
+ const fs = require('fs')
2
+
3
+ const saveFileFactory = async (fileName, content, path = './') => {
4
+ return new Promise((resolve, reject) => {
5
+ fs.writeFile(`${path}${fileName}`, content, err => {
6
+ if (err) {
7
+ Promise.reject(err)
8
+ return
9
+ }
10
+
11
+ Promise.resolve(true)
12
+ })
13
+ })
14
+ }
15
+
16
+ module.exports = saveFileFactory
@@ -3,7 +3,7 @@ const PASSWORD_TEST = 'test'
3
3
  const TOKEN_TEST = 'storyblok1234'
4
4
  const REGION_TEST = 'eu'
5
5
 
6
- // use functions to always returns "new" data
6
+ // use functions to always returns 'new' data
7
7
  const FAKE_COMPONENTS = () => [
8
8
  {
9
9
  name: 'teaser',
@@ -136,7 +136,7 @@ const FAKE_COMPONENTS = () => [
136
136
  }
137
137
  ]
138
138
 
139
- // use functions to always returns "new" data
139
+ // use functions to always returns 'new' data
140
140
  const FAKE_STORIES = () => [
141
141
  {
142
142
  name: 'About',
@@ -249,6 +249,46 @@ const FAKE_SPACE_OPTIONS = () => ({
249
249
  use_translated_stories: false
250
250
  })
251
251
 
252
+ const FAKE_PRESET = () => ({
253
+ id: 123,
254
+ name: 'page_preset',
255
+ preset: {
256
+ _uid: '7dce995b-07ed-4e5b-a4bb-5d22447252d8',
257
+ body: [
258
+ {
259
+ _uid: '995e84c1-a08d-45cd-b121-e4db45e9cf50',
260
+ headline: 'Hello world!',
261
+ component: 'teaser'
262
+ },
263
+ {
264
+ _uid: 'a6e118ec-1a57-4f0f-b1e9-1ed625a82751',
265
+ columns: [
266
+ {
267
+ _uid: '9b0a9bed-e891-4edc-8f5e-bc29e7ec785c',
268
+ name: 'Feature 1',
269
+ component: 'feature'
270
+ },
271
+ {
272
+ _uid: '07863609-7518-48b9-8d28-b1e8037818e2',
273
+ name: 'Feature 2',
274
+ component: 'feature'
275
+ }
276
+ ],
277
+ component: 'grid'
278
+ }
279
+ ],
280
+ component: 'page'
281
+ },
282
+ component_id: 3481284,
283
+ space_id: 200378,
284
+ created_at: '2023-02-24T16:49:14.723Z',
285
+ updated_at: '2023-02-24T16:49:14.723Z',
286
+ image: '',
287
+ color: '',
288
+ icon: '',
289
+ description: 'page preset'
290
+ })
291
+
252
292
  module.exports = {
253
293
  EMAIL_TEST,
254
294
  TOKEN_TEST,
@@ -257,5 +297,6 @@ module.exports = {
257
297
  FAKE_COMPONENTS,
258
298
  FAKE_SPACES,
259
299
  FAKE_SPACE_OPTIONS,
260
- REGION_TEST
300
+ REGION_TEST,
301
+ FAKE_PRESET
261
302
  }
@@ -1,6 +1,6 @@
1
1
  const fs = require('fs')
2
2
  const pullComponents = require('../../src/tasks/pull-components')
3
- const { FAKE_COMPONENTS } = require('../constants')
3
+ const { FAKE_COMPONENTS, FAKE_PRESET } = require('../constants')
4
4
 
5
5
  jest.mock('fs')
6
6
 
@@ -24,36 +24,12 @@ describe('testing pullComponents', () => {
24
24
  })
25
25
  })
26
26
 
27
- it('api.getComponents() should be call fs.writeFile correctly', async () => {
27
+ it('pull components should be call fs.writeFile correctly and generate component file', async () => {
28
28
  const SPACE = 12345
29
- const BODY = {
30
- components: [
31
- {
32
- name: 'teaser',
33
- display_name: null,
34
- created_at: '2019-10-15T17:00:32.212Z',
35
- id: 581153,
36
- schema: {
37
- headline: {
38
- type: 'text'
39
- }
40
- },
41
- image: null,
42
- preview_field: null,
43
- is_root: false,
44
- preview_tmpl: null,
45
- is_nestable: true,
46
- all_presets: [],
47
- preset_id: null,
48
- real_name: 'teaser',
49
- component_group_uuid: null
50
- }
51
- ]
52
- }
53
29
 
54
30
  const api = {
55
31
  getComponents () {
56
- return Promise.resolve(BODY.components)
32
+ return Promise.resolve([FAKE_COMPONENTS()[0]])
57
33
  },
58
34
  getComponentGroups () {
59
35
  return Promise.resolve([])
@@ -73,9 +49,85 @@ describe('testing pullComponents', () => {
73
49
  .then(_ => {
74
50
  const [path, data] = fs.writeFile.mock.calls[0]
75
51
 
76
- expect(fs.writeFile.mock.calls.length).toBe(2)
52
+ expect(fs.writeFile.mock.calls.length).toBe(1)
77
53
  expect(path).toBe(`./${expectFileName}`)
78
- expect(JSON.parse(data)).toEqual(BODY)
54
+ expect(JSON.parse(data)).toEqual({ components: [FAKE_COMPONENTS()[0]] })
55
+ })
56
+ })
57
+
58
+ it('pull components should be call fs.writeFile correctly and generate a component and preset files', async () => {
59
+ const SPACE = 12345
60
+
61
+ const api = {
62
+ getComponents () {
63
+ return Promise.resolve([FAKE_COMPONENTS()[0]])
64
+ },
65
+ getComponentGroups () {
66
+ return Promise.resolve([])
67
+ },
68
+ getPresets () {
69
+ return Promise.resolve(FAKE_PRESET())
70
+ }
71
+ }
72
+
73
+ const options = {
74
+ space: SPACE
75
+ }
76
+
77
+ const expectComponentFileName = `components.${SPACE}.json`
78
+ const expectPresetFileName = `presets.${SPACE}.json`
79
+
80
+ return pullComponents(api, options)
81
+ .then(_ => {
82
+ const [compPath, compData] = fs.writeFile.mock.calls[0]
83
+ const [presetPath, presetData] = fs.writeFile.mock.calls[1]
84
+
85
+ expect(fs.writeFile.mock.calls.length).toBe(2)
86
+
87
+ expect(compPath).toBe(`./${expectComponentFileName}`)
88
+ expect(JSON.parse(compData)).toEqual({ components: [FAKE_COMPONENTS()[0]] })
89
+
90
+ expect(presetPath).toBe(`./${expectPresetFileName}`)
91
+ expect(JSON.parse(presetData)).toEqual({ presets: FAKE_PRESET() })
92
+ })
93
+ })
94
+
95
+ it('pull components should be call fs.writeFile and generate a single file for each component', async () => {
96
+ const SPACE = 12345
97
+
98
+ const api = {
99
+ getComponents () {
100
+ return Promise.resolve(FAKE_COMPONENTS())
101
+ },
102
+ getComponentGroups () {
103
+ return Promise.resolve([])
104
+ },
105
+ getPresets () {
106
+ return Promise.resolve([])
107
+ }
108
+ }
109
+
110
+ const options = {
111
+ space: SPACE,
112
+ separateFiles: true
113
+ }
114
+
115
+ return pullComponents(api, options)
116
+ .then(_ => {
117
+ expect(fs.writeFile.mock.calls.length).toBe(FAKE_COMPONENTS().length)
118
+
119
+ for (const comp in FAKE_COMPONENTS()) {
120
+ const compFileName = `${FAKE_COMPONENTS()[comp].name}-${SPACE}.json`
121
+ let data = FAKE_COMPONENTS()[comp]
122
+ const [compPath, compData] = fs.writeFile.mock.calls[comp]
123
+
124
+ if (FAKE_COMPONENTS()[comp].name === 'logo') {
125
+ data = { ...FAKE_COMPONENTS()[comp], component_group_name: '' }
126
+ }
127
+
128
+ expect(compPath).toBe(`./${compFileName}`)
129
+ expect(JSON.parse(compData)).toEqual(data)
130
+ }
79
131
  })
80
132
  })
81
133
 
@@ -1,6 +1,7 @@
1
1
  const sync = require('../../src/tasks/sync')
2
2
  const PresetsLib = jest.requireActual('../../src/utils/presets-lib')
3
- const { TOKEN_TEST, FAKE_COMPONENTS } = require('../constants')
3
+ const { TOKEN_TEST, EMAIL_TEST, REGION_TEST, FAKE_COMPONENTS } = require('../constants')
4
+ const creds = require('../../src/utils/creds')
4
5
 
5
6
  const FAKE_COMPONENTS_TO_TEST = {
6
7
  '001': {
@@ -217,6 +218,7 @@ const TARGET_SPACE_TEST = '002'
217
218
 
218
219
  describe('testing syncComponents', () => {
219
220
  beforeAll(() => {
221
+ creds.set(EMAIL_TEST, TOKEN_TEST, REGION_TEST)
220
222
  // we need to execute once this function to test it
221
223
  const _types = ['components']
222
224