UNPKG

sb-mig

Version:

CLI to rule the world. (and handle stuff related to Storyblok CMS)

272 lines (271 loc) 10.5 kB
import path from "path"; import chalk from "chalk"; import { discoverMigrationConfig, discoverStories, LOOKUP_TYPE, SCOPE, } from "../../cli/utils/discover.js"; import storyblokConfig from "../../config/config.js"; import { createAndSaveToFile } from "../../utils/files.js"; import Logger from "../../utils/logger.js"; import { getFilesContentWithRequire, isObjectEmpty } from "../../utils/main.js"; import { modifyOrCreateAppliedMigrationsFile } from "../../utils/migrations.js"; import { managementApi } from "../managementApi.js"; function replaceComponentData({ parent, key, components, mapper, depth, maxDepth, sumOfReplacing, }) { let currentMaxDepth = depth; if (storyblokConfig.debug) { Logger.warning(`Current max depth: ${depth}`); } if (typeof parent[key] === "object") { if (parent[key]?.component && components.includes(parent[key].component)) { const { ...rest } = parent[key]; const { data: dataToReplace, wasReplaced } = mapper[parent[key].component](parent[key]); parent[key] = { ...rest, ...dataToReplace }; if (storyblokConfig.debug) { console.log(chalk.yellow(`______________ In __________________________________________`)); console.log(" "); console.log(` Data from ${chalk.blue(dataToReplace.component)} component,\n with _uid: ${chalk.blue(dataToReplace._uid)} `); console.log("Was it replaced? ", wasReplaced); console.log(chalk.yellow(`____________________________________________________________`)); console.log(" "); } if (wasReplaced) { sumOfReplacing[dataToReplace.component] = sumOfReplacing[dataToReplace.component] ? sumOfReplacing[dataToReplace.component] + 1 : 1; console.log("Sum of replacing: "); console.log(sumOfReplacing); } } if (Array.isArray(parent[key])) { for (let i = 0; i < parent[key].length; i++) { const childMaxDepth = replaceComponentData({ parent: parent[key], key: i, components, mapper, depth: depth + 1, maxDepth, sumOfReplacing, }); currentMaxDepth = Math.max(currentMaxDepth, childMaxDepth); } } else { for (const subKey in parent[key]) { const childMaxDepth = replaceComponentData({ parent: parent[key], key: subKey, components, mapper, depth: depth + 1, maxDepth, sumOfReplacing, }); currentMaxDepth = Math.max(currentMaxDepth, childMaxDepth); } } } return currentMaxDepth; } export const prepareStoriesFromLocalFile = ({ from }) => { // Get all stories to be migrated const allLocalStories = discoverStories({ scope: SCOPE.local, type: LOOKUP_TYPE.fileName, fileNames: [from], }); // Get content of all stories to be migrated const storiesFileContent = getFilesContentWithRequire({ files: allLocalStories, })[0]; if (!storiesFileContent) { throw new Error(`Couldn't receive data from provided stories filename: ${chalk.red(from)}`); } return storiesFileContent; }; export const prepareMigrationConfig = ({ migrationConfig }) => { // Get migration config file with migration definition const migrationConfigFiles = discoverMigrationConfig({ scope: SCOPE.local, type: LOOKUP_TYPE.fileName, fileNames: [migrationConfig], }); // Get content of migration config file const migrationConfigFileContent = getFilesContentWithRequire({ files: migrationConfigFiles, })[0]; if (isObjectEmpty(migrationConfigFileContent)) { throw new Error("Migration config file is empty. Please provide default exported config object with components map to migrate"); } if (!migrationConfigFileContent) { throw new Error("Migration config probably doesnt exist. Create one"); } Logger.success(`Migration config loaded.`); return migrationConfigFileContent; }; export const migrateAllComponentsDataInStories = async ({ itemType, migrationConfig, migrateFrom, from, to, }, config) => { Logger.warning(`Trying to migrate all ${itemType} from ${migrateFrom}, ${from} to ${to}...`); const migrationConfigFileContent = prepareMigrationConfig({ migrationConfig, }); // Taking every component defined in const config = {} in migration config file const componentsToMigrate = Object.keys(migrationConfigFileContent); if (storyblokConfig.debug) { Logger.warning("_________ Components in stories to migrate ___________"); console.log(componentsToMigrate); } await migrateProvidedComponentsDataInStories({ itemType, migrationConfig, migrateFrom, from, to, componentsToMigrate, }, config); }; export const doTheMigration = async ({ itemType = "story", from, itemsToMigrate, componentsToMigrate, migrationConfigFileContent, migrationConfig, to, }, config) => { const arrayOfMaxDepths = []; const migratedItems = itemsToMigrate.map((item, index) => { const sumOfReplacing = {}; if (storyblokConfig.debug) { Logger.success(`# ${index} #`); } const json = itemType === "story" ? item[itemType].content : item[itemType]; const maxDepth = replaceComponentData({ parent: { root: json }, key: "root", components: componentsToMigrate, mapper: migrationConfigFileContent, depth: 0, maxDepth: 0, sumOfReplacing, }); arrayOfMaxDepths.push(maxDepth); if (Object.keys(sumOfReplacing).length > 0) { console.log(" "); console.log(`Migration in ${chalk.magenta(itemType === "story" ? item[itemType].full_slug : item[itemType].name)} page: `); componentsToMigrate.forEach((component) => { if (sumOfReplacing[component]) { console.log(`${chalk.blue(component)} component data was replaced: ${sumOfReplacing[component]} times.`); } }); } if (Object.keys(sumOfReplacing).length > 0) { return { ...item, [itemType]: itemType === "story" ? { ...item[itemType], content: json, } : { ...json, }, }; } else { return null; } }); const maxDepth = Math.max(...arrayOfMaxDepths); if (storyblokConfig.debug) { console.log(" "); if (maxDepth > 30) { Logger.error(`Max depth: ${maxDepth}`); } else { Logger.success(`Max depth: ${maxDepth}`); } console.log(" "); } const isListEmpty = (list) => list.filter((item) => item).length; if (!isListEmpty(migratedItems)) { console.log("# No Stories to update #"); } else { console.log(`${migratedItems.length} stories to migrate`); } const notNullMigratedItems = migratedItems.filter((item) => item); // Saving result with migrated version of stories into file await createAndSaveToFile({ datestamp: true, ext: "json", filename: `${from}---${itemType}-to-migrate`, folder: "migrations", res: notNullMigratedItems, }, config); await modifyOrCreateAppliedMigrationsFile(migrationConfig, itemType); if (itemType === "story") { await managementApi.stories.updateStories({ stories: notNullMigratedItems, spaceId: to, options: { publish: false }, }, config); } else if (itemType === "preset") { await managementApi.presets.updatePresets({ presets: notNullMigratedItems, spaceId: to, options: {}, }, config); } }; const saveBackupToFile = async ({ itemType, res, folder, filename }, config) => { await createAndSaveToFile({ ext: "json", datestamp: true, suffix: itemType === "story" ? ".sb.stories" : ".sb.presets", filename, folder, res: res, }, config); }; export const migrateProvidedComponentsDataInStories = async ({ itemType, migrationConfig, migrateFrom, from, to, componentsToMigrate, }, config) => { const migrationConfigFileContent = prepareMigrationConfig({ migrationConfig, }); if (migrateFrom === "file") { Logger.log("Migrating using file...."); // Get all stories to be migrated from file const itemsToMigrate = prepareStoriesFromLocalFile({ from }); await doTheMigration({ itemsToMigrate, componentsToMigrate, migrationConfigFileContent, to, }, config); } else if (migrateFrom === "space") { // Get all stories to be migrated from storyblok space let itemsToMigrate = []; if (itemType === "story") { itemsToMigrate = await managementApi.stories.getAllStories({}, { ...config, spaceId: from, }); } else if (itemType === "preset") { itemsToMigrate = await managementApi.presets.getAllPresets({ ...config, spaceId: from, }); } const backupFolder = path.join("backup", itemType); // save stories to file as backup await saveBackupToFile({ itemType, filename: `before__${migrationConfig}__${from}`, folder: backupFolder, res: itemsToMigrate, }, config); await doTheMigration({ itemType, itemsToMigrate, componentsToMigrate, migrationConfigFileContent, migrationConfig, from, to, }, config); } };