16
loading...
This website collects cookies to deliver better user experience
This guide is complementary to the official Ghost documentation: How to migrate data from Ghost to Ghost and Imports and exports in Ghost: Access your content and data - FAQ.
If your are on the latest Ghost version (currently 4.5), the Labs options menu is no longer in the main menu, it is now hidden in the Settings (⚙️) option menu!
https://
), you will also have to edit them also if you want to delete your previous Ghost instance... 🥴/content/images
). 😉https://digitalpress.fra1.cdn.digitaloceanspaces.com/rec0035/
.import escapeStringRegExp from 'escape-string-regexp'
import fetch from 'node-fetch';
import fs from 'fs/promises';
import path from 'path'
// Configuration
const oldServerURL = "https://digitalpress.fra1.cdn.digitaloceanspaces.com/rec0035/"
const newServerURL = '/content/images/'
const oldGhostContentFile = 'ma-maison-sherby.ghost.2021-05-15-17-33-42.json'
const newGhostContentFile = oldGhostContentFile.replace('.json', '-output.json')
const imageDirectory = 'images/'
// Regexes
const filenameRegex = /(.*?\.(?:avif|ico|gif|jpe?g|png))/
const serverURLRegex = new RegExp(escapeStringRegExp(oldServerURL), 'gi')
const imageRegex = new RegExp(serverURLRegex.source + filenameRegex.source, 'gi')
// Utility functions
const stringify = object => JSON.stringify(object)
const parse = object => JSON.parse(object)
const returnBoolean = bool => () => bool
// Utility filesystem functions
const fileExists = async filePath => await fs.access(filePath, fs.F_OK)
.then(returnBoolean(true))
.catch(returnBoolean(false))
console.log(`Loading the Ghost content into memory...`)
const content = await fs.readFile(oldGhostContentFile, 'utf8')
const extractInformationsFromImageMatch = match => ({
absoluteURL: match[0],
directory: imageDirectory + path.dirname(match[1]),
fileName: path.basename(match[1]),
filePath: imageDirectory + match[1],
})
console.log(`Finding all unique images...`)
let images = [...content.matchAll(imageRegex)].map(extractInformationsFromImageMatch);
images = new Set(images.map(stringify))
images = Array.from(images).map(parse)
console.log(`${images.length} images found!`)
// Download an image inside a match object
const download = async (matchObject) => {
// Check if we have already download the image on our disk
const skipDownload = await fileExists(matchObject.filePath)
if (skipDownload) {
return
}
console.log(`Downloading ${matchObject.filePath}...`)
// Download the image
const response = await fetch(matchObject.absoluteURL);
// Check if everything is ok
if (!response.ok) {
throw new Error(`Unexpected response ${response.statusText}`);
}
// Transform the response to buffer
const buffer = await response.buffer();
// Make sure the directory of the file exists and create it otherwise
await fs.mkdir(matchObject.directory, {recursive: true})
// Write the image on disk
await fs.writeFile(matchObject.filePath, buffer)
console.log(`Download finished for ${matchObject.filePath}!`)
}
console.log(`Downloading all images found...`)
for (let i = 0; i < images.length; ++i) {
await download(images[i])
}
// Replace all occurrences of the old server to the new one
const newContent = content.replaceAll(serverURLRegex, newServerURL)
// Write the output inside a new file that can be imported
await fs.writeFile(newGhostContentFile, newContent)
download.mjs
file, to download all images and create a new JSON file.{
"dependencies": {
"escape-string-regexp": "^5.0.0",
"node-fetch": "^2.6.1"
}
}
package.json
file, with only two dependencies.node download.mjs
command and the images should normally be downloaded into the images
folder.scp -r images IP_ADDRESS:/var/opt/ghost/mamaisonsherby
scp
(secure copy) to upload all the content under the images
directory to my remote server.Import content
button in Settings
→ Labs
.Settings
→ Code injection
→ Site header
) to visually mark images that need more attention.img[src*="digitaloceanspaces"] {
border: 5px dashed darkred;
}