Skip to content

Commit 8289f20

Browse files
committed
feat: add get clone assets diff script
add a script which compares all assets from source and target spaces and lists the name of assets not copied during clone-assets
1 parent 513ae6a commit 8289f20

File tree

5 files changed

+249
-0
lines changed

5 files changed

+249
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Each example contains a `README.md` with an explanation about the tool.
2626
| **[Clean Up Field Script ](https://github.com/storyblok/tool-examples/tree/main/clean-up-field)** <br/> A tool to remove a specific field from all stories | [Alexander Feiglstorfer](https://github.com/onefriendaday) |
2727
| **[Clone Assets ](https://github.com/storyblok/tool-examples/tree/main/clone-assets)** <br/> A tool to clone assets from a space to its clone | [Christian Zoppi](https://github.com/christianzoppi) |
2828
| **[Find Unused Assets ](https://github.com/storyblok/tool-examples/tree/main/find-unused-assets)** <br/> A tool to find all unused assets in a space | [Bogdan Selenginskiy](https://github.com/bseleng) |
29+
| **[Get Cloned Assets Diff ](https://github.com/storyblok/tool-examples/tree/main/get-cloned-assets-diff)** <br/> A tool to find all unused assets in a space | [Bogdan Selenginskiy](https://github.com/bseleng) |
2930
| **[](https://github.com/storyblok/tool-examples/tree/main/private-assets-demo)** <br/> undefined | undefined |
3031
| **[Storyblok Assets Backup ](https://github.com/storyblok/tool-examples/tree/main/storyblok-assets-backup)** <br/> Tool for differential backups of the assets of any Storyblok space | [Christian Zoppi](https://github.com/christianzoppi), [Gerrit Plehn](https://github.com/GerritPlehn) |
3132

get-cloned-assets-diff/index.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Migration from './src/index.js'
2+
import inquirer from 'inquirer'
3+
4+
const questions = [
5+
{
6+
type: 'input',
7+
name: 'oauth',
8+
message: "Please enter your Personal Access Token (get one at http://app.storyblok.com/#!/me/account)",
9+
},
10+
{
11+
type: 'input',
12+
name: 'source_space_id',
13+
message: "Please enter the Source Space Id",
14+
},
15+
{
16+
type: 'input',
17+
name: 'target_space_id',
18+
message: "Please enter the Target Space Id",
19+
},
20+
{
21+
type: 'input',
22+
name: 'simultaneous_uploads',
23+
message: "Simultaneous Uploads",
24+
default: 20
25+
},
26+
{
27+
type: 'input',
28+
name: 'region',
29+
message: "Please enter the Region code. Leave empty for default EU region",
30+
default: null
31+
},
32+
]
33+
34+
inquirer.prompt(questions).then((answers) => {
35+
const migration = new Migration(answers.oauth, answers.source_space_id, answers.target_space_id, answers.simultaneous_uploads, answers.region)
36+
migration.start()
37+
})
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "get-cloned-assets-dif",
3+
"version": "0.1.1",
4+
"description": "Get all assets from two spaces and list the names of assets which were not сloned to the target space",
5+
"main": "index.js",
6+
"type": "module",
7+
"engines": {
8+
"node": ">=16"
9+
},
10+
"scripts": {
11+
"start": "node index"
12+
},
13+
"author": "Christian Zoppi",
14+
"contributors": [
15+
{
16+
"name": "Bogdan Selenginskiy",
17+
"email": "[email protected]"
18+
}
19+
],
20+
"license": "ISC",
21+
"dependencies": {
22+
"async": "^3.2.0",
23+
"chalk": "^4.1.0",
24+
"form-data": "^3.0.0",
25+
"inquirer": "^7.3.3",
26+
"storyblok-js-client": "^5.12.1"
27+
}
28+
}

get-cloned-assets-diff/readme.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Get Clone Assets Diff
2+
3+
This script can be used to get names of all assets, which were not copied by `clone-assets`. Usually those are all non-images: .mp4, .zip, .pdf and so on.
4+
5+
Name | Description | Author
6+
------------ | ------------- | -------------
7+
Get Cloned Assets Diff | A tool to find all unused assets in a space | [Bogdan Selenginskiy](https://github.com/bseleng)
8+
9+
10+
## How to use
11+
12+
Run `npm i` to install and then `npm run start`.
13+
14+
You'll have to provide a Personal Access Token from your account, the id of the target space and the number of the max simultaneous uploads. The default for the max simultaneous uploads is 20 but you can increase it slightly to make the upload faster if your computer and connection can handle it or you can decrease it if you want to use less bandwidth and memory.
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import chalk from 'chalk'
2+
import StoryblokClient from 'storyblok-js-client'
3+
4+
// Throttling
5+
export default class Migration {
6+
constructor(oauth, source_space_id, target_space_id, simultaneous_uploads, region) {
7+
this.source_space_id = source_space_id
8+
this.target_space_id = target_space_id
9+
this.source_space_type = "SOURCE"
10+
this.target_space_type = "TARGET"
11+
this.oauth = oauth
12+
this.simultaneous_uploads = simultaneous_uploads
13+
this.region = region
14+
this.assets_retries = {}
15+
this.retries_limit = 4
16+
}
17+
18+
/**
19+
* Migration error callback
20+
*/
21+
migrationError(err) {
22+
throw new Error(err)
23+
}
24+
25+
/**
26+
* Print a message of the current step
27+
*/
28+
stepMessage(index, text, append_text) {
29+
process.stdout.clearLine()
30+
process.stdout.cursorTo(0)
31+
process.stdout.write(`${chalk.white.bgBlue(` ${index}/4 `)} ${text} ${append_text ? chalk.black.bgYellow(` ${append_text} `) : ''}`)
32+
}
33+
34+
/**
35+
* Print a message of the completed step
36+
*/
37+
stepMessageEnd(index, text) {
38+
process.stdout.clearLine()
39+
process.stdout.cursorTo(0)
40+
process.stdout.write(`${chalk.black.bgGreen(` ${index}/4 `)} ${text}\n`)
41+
}
42+
43+
/**
44+
* Start the migration
45+
*/
46+
async start() {
47+
try {
48+
await this.getTargetSpaceToken()
49+
await this.getAssets(this.source_space_id, this.source_space_type)
50+
await this.getAssets(this.target_space_id, this.target_space_type)
51+
await this.getMissingAssets()
52+
} catch (err) {
53+
console.log(`${chalk.white.bgRed(` ⚠ Migration Error `)} ${chalk.red(err.toString().replace('Error: ', ''))}`)
54+
}
55+
}
56+
57+
/**
58+
* Get the target space token and setup the Storyblok js client
59+
*/
60+
async getTargetSpaceToken() {
61+
try {
62+
this.storyblok = new StoryblokClient({
63+
oauthToken: this.oauth,
64+
region: this.region
65+
})
66+
const space_request = await this.storyblok.get(`spaces/${this.target_space_id}`)
67+
this.target_space_token = space_request.data.space.first_token
68+
this.storyblok = new StoryblokClient({
69+
accessToken: this.target_space_token,
70+
region: this.region,
71+
oauthToken: this.oauth,
72+
rateLimit: 3
73+
})
74+
this.stepMessageEnd('1', `Personal access token is valid. New StoryblokClient is created.`)
75+
} catch (err) {
76+
this.migrationError('Error trying to retrieve the space token. Please double check the target space id and the OAUTH token.')
77+
}
78+
}
79+
80+
81+
/**
82+
* Get the Assets list from the source space
83+
*/
84+
85+
async getAssets(spaceId, spaceType) {
86+
switch (spaceType) {
87+
case this.source_space_type:
88+
this.stepMessage('2', `Fetching assets from ${chalk.bgBlueBright(this.source_space_type)} space.`)
89+
break
90+
case this.target_space_type:
91+
this.stepMessage('3', `Fetching assets from ${chalk.bgMagentaBright(this.target_space_type)} space.`)
92+
break
93+
}
94+
95+
try {
96+
const assets_page_request = await this.storyblok.get(`spaces/${spaceId}/assets`, {
97+
per_page: 100,
98+
page: 1
99+
})
100+
const pages_total = Math.ceil(assets_page_request.headers.total / 100)
101+
const assets_requests = []
102+
for (let i = 1; i <= pages_total; i++) {
103+
assets_requests.push(
104+
this.storyblok.get(`spaces/${spaceId}/assets`, {
105+
per_page: 100,
106+
page: i
107+
})
108+
)
109+
}
110+
const assets_responses = await Promise.all(assets_requests)
111+
112+
switch (spaceType) {
113+
case this.source_space_type:
114+
this.source_assets_list = assets_responses.map(r => r.data.assets).flat().map((asset) => asset.filename)
115+
this.stepMessageEnd('2', `Fetched assets from ${chalk.bgBlueBright(this.source_space_type)} space. Total: ${chalk.bgBlueBright(this.source_assets_list.length)}`)
116+
break
117+
118+
case this.target_space_type:
119+
this.target_assets_list = assets_responses.map(r => r.data.assets).flat().map((asset) => asset.filename)
120+
this.stepMessageEnd('3', `Fetched assets from ${chalk.bgMagentaBright(this.target_space_type)} space. Total: ${chalk.bgMagentaBright(this.target_assets_list.length)}`)
121+
break
122+
123+
}
124+
} catch (err) {
125+
this.migrationError('Error fetching the assets. Please double check the space ids.')
126+
}
127+
}
128+
129+
async getMissingAssets() {
130+
131+
this.stepMessage('4', `Finding ${chalk.bgMagentaBright(" " + this.source_assets_list.length - this.target_assets_list.length + " ")} missing assets.`)
132+
133+
const targetAssets = {}
134+
this.target_assets_list.map(targetAsset => {
135+
targetAssets[this.getAssetResolutionAndName(targetAsset)] = true
136+
})
137+
138+
let number = 1
139+
this.source_assets_list.map(sourceAsset => {
140+
let sourceAssetResolutionAndName = this.getAssetResolutionAndName(sourceAsset)
141+
if (!targetAssets[sourceAssetResolutionAndName]) {
142+
process.stdout.clearLine()
143+
process.stdout.cursorTo(0)
144+
if (sourceAssetResolutionAndName.substring(0, 2) === "x-") {
145+
sourceAssetResolutionAndName = sourceAssetResolutionAndName.substring(2)
146+
}
147+
process.stdout.write(`${chalk.dim(` ${number}.`)} ${sourceAssetResolutionAndName}\n`)
148+
number++
149+
}
150+
151+
})
152+
153+
154+
155+
this.stepMessageEnd('4', `Target space ${chalk.dim("#" + this.target_space_id)}. Total: ${chalk.bgMagentaBright(" " + this.source_assets_list.length - this.target_assets_list.length + " ")} assets are missing.`)
156+
157+
}
158+
159+
160+
161+
getAssetResolutionAndName(asset) {
162+
//asset eg. "https://s3.amazonaws.com/a.storyblok.com/f/262399/1575x990/1553787291/webscrap-card-image2.png"
163+
const assetBySlash = asset.split("/")
164+
165+
// eg. "1575x990" + "-" + "webscrap-card-image2.png
166+
return assetBySlash[assetBySlash.length - 3] + "-" + assetBySlash[assetBySlash.length - 1]
167+
}
168+
169+
}

0 commit comments

Comments
 (0)