Skip to content
This repository was archived by the owner on Jan 19, 2026. It is now read-only.

Commit dc8cef8

Browse files
author
lisilinhart
authored
feat(sync): add support for datasources
Feature/sync datasources
2 parents ad56d00 + 74cd20e commit dc8cef8

5 files changed

Lines changed: 198 additions & 4 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,15 @@ $ storyblok push-components ./components.json --space 67819
7070

7171
### sync
7272

73-
Sync components, folder, roles or stories between spaces
73+
Sync components, folder, roles, datasources or stories between spaces
7474

7575
```sh
7676
$ storyblok sync --type <COMMAND> --source <SPACE_ID> --target <SPACE_ID>
7777
```
7878

7979
#### Options
8080

81-
* `type`: describe the command type to execute. Can be: `folders`, `components`, `stories` or `roles`. It's possible pass multiple types separated by comma (`,`).
81+
* `type`: describe the command type to execute. Can be: `folders`, `components`, `stories`, `datasources` or `roles`. It's possible pass multiple types separated by comma (`,`).
8282
* `source`: the source space to use to sync
8383
* `target`: the target space to use to sync
8484

src/cli.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ program
165165
program
166166
.command('sync')
167167
.description('Sync schemas, roles, folders and stories between spaces')
168-
.requiredOption('--type <TYPE>', 'Define what will be sync. Can be components, folders, stories or roles')
168+
.requiredOption('--type <TYPE>', 'Define what will be sync. Can be components, folders, stories, datasources or roles')
169169
.requiredOption('--source <SPACE_ID>', 'Source space id')
170170
.requiredOption('--target <SPACE_ID>', 'Target space id')
171171
.action(async (options) => {

src/constants.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ const SYNC_TYPES = [
66
'folders',
77
'components',
88
'roles',
9-
'stories'
9+
'stories',
10+
'datasources'
1011
]
1112

1213
module.exports = {
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
const chalk = require('chalk')
2+
const StoryblokClient = require('storyblok-js-client')
3+
4+
class SyncDatasources {
5+
/**
6+
* @param {{ sourceSpaceId: string, targetSpaceId: string, oauthToken: string }} options
7+
*/
8+
constructor (options) {
9+
this.targetDatasources = []
10+
this.sourceDatasources = []
11+
this.sourceSpaceId = options.sourceSpaceId
12+
this.targetSpaceId = options.targetSpaceId
13+
this.oauthToken = options.oauthToken
14+
this.client = new StoryblokClient({
15+
oauthToken: options.oauthToken
16+
})
17+
}
18+
19+
async sync () {
20+
try {
21+
this.targetDatasources = await this.client.getAll(`spaces/${this.targetSpaceId}/datasources`)
22+
this.sourceDatasources = await this.client.getAll(`spaces/${this.sourceSpaceId}/datasources`)
23+
24+
console.log(
25+
`${chalk.blue('-')} In source space #${this.sourceSpaceId}: `
26+
)
27+
console.log(` - ${this.sourceDatasources.length} datasources`)
28+
29+
console.log(
30+
`${chalk.blue('-')} In target space #${this.targetSpaceId}: `
31+
)
32+
console.log(` - ${this.targetDatasources.length} datasources`)
33+
} catch (err) {
34+
console.error(`An error ocurred when loading the datasources: ${err.message}`)
35+
36+
return Promise.reject(err)
37+
}
38+
39+
console.log(chalk.green('-') + ' Syncing datasources...')
40+
await this.addDatasources()
41+
await this.updateDatasources()
42+
}
43+
44+
async getDatasourceEntries (spaceId, datasourceId) {
45+
try {
46+
const entriesFirstPage = await this.client.get(`spaces/${spaceId}/datasource_entries/?datasource_id=${datasourceId}`)
47+
const entriesRequets = []
48+
for (let i = 1; i < Math.ceil(entriesFirstPage.total / 25); i++) {
49+
entriesRequets.push(this.client.get(`spaces/${spaceId}/datasource_entries/?datasource_id=${datasourceId}`, { page: i }))
50+
}
51+
return entriesFirstPage.data.datasource_entries.concat((await Promise.all(entriesRequets)).map(r => r.data.datasource_entries))
52+
} catch (err) {
53+
console.error(`An error ocurred when loading the entries of the datasource #${datasourceId}: ${err.message}`)
54+
55+
return Promise.reject(err)
56+
}
57+
}
58+
59+
async addDatasourceEntry (entry, datasourceId) {
60+
try {
61+
return this.client.post(`spaces/${this.targetSpaceId}/datasource_entries/`, {
62+
datasource_entry: {
63+
name: entry.name,
64+
value: entry.value,
65+
datasource_id: datasourceId
66+
}
67+
})
68+
} catch (err) {
69+
console.error(`An error ocurred when creating the datasource entry ${entry.name}: ${err.message}`)
70+
71+
return Promise.reject(err)
72+
}
73+
}
74+
75+
async updateDatasourceEntry (entry, newData, datasourceId) {
76+
try {
77+
return this.client.put(`spaces/${this.targetSpaceId}/datasource_entries/${entry.id}`, {
78+
datasource_entry: {
79+
name: newData.name,
80+
value: newData.value,
81+
datasource_id: datasourceId
82+
}
83+
})
84+
} catch (err) {
85+
console.error(`An error ocurred when updating the datasource entry ${entry.name}: ${err.message}`)
86+
87+
return Promise.reject(err)
88+
}
89+
}
90+
91+
async syncDatasourceEntries (sourceId, targetId) {
92+
try {
93+
const sourceEntries = await this.getDatasourceEntries(this.sourceSpaceId, sourceId)
94+
const targetEntries = await this.getDatasourceEntries(this.targetSpaceId, targetId)
95+
const updateEntries = targetEntries.filter(e => sourceEntries.map(se => se.name).includes(e.name))
96+
const addEntries = sourceEntries.filter(e => !targetEntries.map(te => te.name).includes(e.name))
97+
98+
/* Update entries */
99+
const entriesUpdateRequests = []
100+
for (let j = 0; j < updateEntries.length; j++) {
101+
const sourceEntry = sourceEntries.find(d => d.name === updateEntries[j].name)
102+
entriesUpdateRequests.push(this.updateDatasourceEntry(updateEntries[j], sourceEntry, targetId))
103+
}
104+
await Promise.all(entriesUpdateRequests)
105+
106+
/* Add entries */
107+
const entriesCreationRequests = []
108+
for (let j = 0; j < addEntries.length; j++) {
109+
entriesCreationRequests.push(this.addDatasourceEntry(addEntries[j], targetId))
110+
}
111+
await Promise.all(entriesCreationRequests)
112+
} catch (err) {
113+
console.error(`An error ocurred when syncing the datasource entries: ${err.message}`)
114+
115+
return Promise.reject(err)
116+
}
117+
}
118+
119+
async addDatasources () {
120+
const datasourcesToAdd = this.sourceDatasources.filter(d => !this.targetDatasources.map(td => td.slug).includes(d.slug))
121+
if (datasourcesToAdd.length) {
122+
console.log(
123+
`${chalk.green('-')} Adding new datasources to target space #${this.targetSpaceId}...`
124+
)
125+
}
126+
127+
for (let i = 0; i < datasourcesToAdd.length; i++) {
128+
try {
129+
/* Create the datasource */
130+
const newDatasource = await this.client.post(`spaces/${this.targetSpaceId}/datasources`, {
131+
name: datasourcesToAdd[i].name,
132+
slug: datasourcesToAdd[i].slug
133+
})
134+
135+
await this.syncDatasourceEntries(datasourcesToAdd[i].id, newDatasource.data.datasource.id)
136+
console.log(chalk.green('✓') + ' Created datasource ' + datasourcesToAdd[i].name)
137+
} catch (err) {
138+
console.error(
139+
`${chalk.red('X')} Datasource ${datasourcesToAdd[i].name} creation failed: ${err.message}`
140+
)
141+
}
142+
}
143+
}
144+
145+
async updateDatasources () {
146+
const datasourcesToUpdate = this.targetDatasources.filter(d => this.sourceDatasources.map(sd => sd.slug).includes(d.slug))
147+
if (datasourcesToUpdate.length) {
148+
console.log(
149+
`${chalk.green('-')} Updating datasources In target space #${this.targetSpaceId}...`
150+
)
151+
}
152+
153+
for (let i = 0; i < datasourcesToUpdate.length; i++) {
154+
try {
155+
/* Update the datasource */
156+
const sourceDatasource = this.sourceDatasources.find(d => d.slug === datasourcesToUpdate[i].slug)
157+
await this.client.put(`spaces/${this.targetSpaceId}/datasources/${datasourcesToUpdate[i].id}`, {
158+
name: sourceDatasource.name,
159+
slug: sourceDatasource.slug
160+
})
161+
162+
await this.syncDatasourceEntries(sourceDatasource.id, datasourcesToUpdate[i].id)
163+
console.log(chalk.green('✓') + ' Updated datasource ' + datasourcesToUpdate[i].name)
164+
} catch (err) {
165+
console.error(
166+
`${chalk.red('X')} Datasource ${datasourcesToUpdate[i].name} update failed: ${err.message}`
167+
)
168+
}
169+
}
170+
}
171+
}
172+
173+
module.exports = SyncDatasources

src/tasks/sync.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const pSeries = require('p-series')
22
const chalk = require('chalk')
33
const StoryblokClient = require('storyblok-js-client')
44
const SyncComponents = require('./sync-commands/components')
5+
const SyncDatasources = require('./sync-commands/datasources')
56
const { capitalize } = require('../utils')
67

78
const SyncSpaces = {
@@ -188,6 +189,25 @@ const SyncSpaces = {
188189
)
189190
console.log(e)
190191

192+
return Promise.reject(new Error(e))
193+
}
194+
},
195+
196+
async syncDatasources () {
197+
const syncDatasourcesInstance = new SyncDatasources({
198+
sourceSpaceId: this.sourceSpaceId,
199+
targetSpaceId: this.targetSpaceId,
200+
oauthToken: this.oauthToken
201+
})
202+
203+
try {
204+
await syncDatasourcesInstance.sync()
205+
} catch (e) {
206+
console.error(
207+
chalk.red('X') + ` Datasources Sync failed: ${e.message}`
208+
)
209+
console.log(e)
210+
191211
return Promise.reject(new Error(e))
192212
}
193213
}

0 commit comments

Comments
 (0)