UNPKG

sb-mig

Version:

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

286 lines (285 loc) 12.6 kB
import { apiConfig } from "../cli/api-config.js"; import { compare, discover, discoverMany, discoverManyByPackageName, discoverStories, LOOKUP_TYPE, SCOPE, } from "../cli/utils/discover.js"; import { dumpToFile, getFileContentWithRequire, getFilesContentWithRequire, } from "../utils/files.js"; import Logger from "../utils/logger.js"; import { getAllAssets, migrateAsset } from "./assets/assets.js"; import { syncComponentsData } from "./components/components.sync.js"; import { contentHubApi } from "./contentHubApi.js"; import { managementApi } from "./managementApi.js"; import { backupStories } from "./stories/backup.js"; import { getAllStories } from "./stories/stories.js"; import { createTree, traverseAndCreate } from "./stories/tree.js"; import { resolveGlobalTransformations } from "./utils/resolverTransformations.js"; const _checkAndPrepareGroups = async (groupsToCheck, config) => { const componentsGroups = await managementApi.components.getAllComponentsGroups(config); const groupExist = (groupName) => componentsGroups.find((group) => group.name === groupName); for (const groupName of groupsToCheck) { if (!groupExist(groupName)) { await managementApi.components.createComponentsGroup(groupName, config); } } }; export const removeAllComponents = async (config) => { const components = await managementApi.components.getAllComponents(config); const component_groups = await managementApi.components.getAllComponentsGroups(config); return Promise.all([ ...components.map(async (component) => { await managementApi.components.removeComponent(component, config); }), ...component_groups.map(async (componentGroup) => { await managementApi.components.removeComponentGroup(componentGroup, config); }), ]); }; export const removeSpecifiedComponents = async (components, config) => { const remoteComponents = await managementApi.components.getAllComponents(config); const componentsToRemove = []; components.map((component) => { const shouldBeRemoved = remoteComponents.find((remoteComponent) => component === remoteComponent.name); shouldBeRemoved && componentsToRemove.push(shouldBeRemoved); }); return (componentsToRemove.length > 0 && Promise.all(componentsToRemove.map((component) => { return managementApi.components.removeComponent(component, config); }))); }; const _resolveGroups = async (component, existedGroups, remoteComponentsGroups) => { if (!component.component_group_name) { return { ...component, component_group_uuid: null }; } const componentsGroup = existedGroups.find((group) => component.component_group_name === group); if (componentsGroup) { const component_group_uuid = remoteComponentsGroups.find((remoteComponentsGroup) => remoteComponentsGroup.name === componentsGroup).uuid; return { ...component, component_group_uuid }; } }; export const syncComponents = async (specifiedComponents, presets, config, options = {}) => { Logger.log("sync2Components: "); let specifiedComponentsContent = await Promise.all(specifiedComponents.map((component) => { return getFileContentWithRequire({ file: component.p }); })); /** * Resolve all the global transformations you find * - storyblok.config file resolvers * - .sb.resolvers.ts files resolvers */ specifiedComponentsContent = await resolveGlobalTransformations(specifiedComponentsContent); await syncComponentsData({ components: specifiedComponentsContent, presets, ssot: options.ssot, dryRun: options.dryRun, }, config); }; export const syncAllComponents = async (presets, config, options = {}) => { // #1: discover all external .sb.js files const allLocalSbComponentsSchemaFiles = await discover({ scope: SCOPE.local, type: LOOKUP_TYPE.fileName, }); // #2: discover all local .sb.js files const allExternalSbComponentsSchemaFiles = await discover({ scope: SCOPE.external, type: LOOKUP_TYPE.fileName, }); // // #3: compare results, prefare local ones (so we have to create final external paths array and local array of things to sync from where) const { local, external } = compare({ local: allLocalSbComponentsSchemaFiles, external: allExternalSbComponentsSchemaFiles, }); // #4: sync - do all stuff already done (groups resolving, and so on) return await syncComponents([...local, ...external], presets, config, { dryRun: options.dryRun, ssot: options.ssot, }); }; export const syncProvidedComponents = async (presets, components, packageName, config, options = {}) => { if (!packageName) { // #1: discover all external .sb.js files const allLocalSbComponentsSchemaFiles = await discoverMany({ scope: SCOPE.local, type: LOOKUP_TYPE.fileName, fileNames: components, }); // #2: discover all local .sb.js files const allExternalSbComponentsSchemaFiles = await discoverMany({ scope: SCOPE.external, type: LOOKUP_TYPE.fileName, fileNames: components, }); // #3: compare results, prefer local ones (so we have to create final external paths array and local array of things to sync from where) const { local, external } = compare({ local: allLocalSbComponentsSchemaFiles, external: allExternalSbComponentsSchemaFiles, }); // #4: sync - do all stuff already done (groups resolving, and so on) return await syncComponents([...local, ...external], presets, config, { dryRun: options.dryRun, }); } else { // implement discovering and syncrhonizing with packageName // #1: discover all external .sb.js files const allLocalSbComponentsSchemaFiles = discoverManyByPackageName({ scope: SCOPE.local, packageNames: components, }); // #2: discover all local .sb.js files const allExternalSbComponentsSchemaFiles = discoverManyByPackageName({ scope: SCOPE.external, packageNames: components, }); // #3: compare results, prefer local ones (so we have to create final external paths array and local array of things to sync from where) const { local, external } = compare({ local: allLocalSbComponentsSchemaFiles, external: allExternalSbComponentsSchemaFiles, }); // #4: sync - do all stuff already done (groups resolving, and so on) return syncComponents([...local, ...external], presets, config, { dryRun: options.dryRun, }); } }; export const syncAssets = async ({ transmission: { from, to }, syncDirection, dryRun }, config) => { Logger.log(`We would try to migrate Assets data from: ${from} to: ${to}`); const allAssets = await getAllAssets({ spaceId: from }, config); const assets = Array.isArray(allAssets?.assets) ? allAssets.assets : []; if (dryRun) { Logger.warning(`[dry-run] Would sync ${assets.length} assets from ${from} to ${to}.`); return true; } await Promise.all(assets.map((asset) => { const { id, created_at, updated_at, ...newAssetPayload } = asset; return migrateAsset({ migrateTo: to, payload: newAssetPayload, syncDirection, }, config); })); return true; }; const syncStories = async ({ transmission: { from, to }, stories, toSpaceId, dryRun }, config) => { Logger.log(`We would try to migrate Stories data from: ${from} to: ${to}`); const storiesToPass = stories .map((item) => item.story) .map((item) => item.parent_id === 0 ? { ...item, parent_id: null } : item); Logger.warning(`Amount of all stories to migrate: ${storiesToPass.length}`); if (dryRun) { Logger.warning(`[dry-run] Would sync ${storiesToPass.length} stories from ${from} to ${to}.`); return true; } const storiesToPassJson = JSON.stringify(storiesToPass, null, 2); if (config.debug) { dumpToFile("storiesToPass.json", storiesToPassJson); } const tree = createTree(storiesToPass); const jsonString = JSON.stringify(tree, null, 2); if (config.debug) { dumpToFile("tree.json", jsonString); } await traverseAndCreate({ tree, realParentId: null, spaceId: toSpaceId }, config); return true; }; export const syncContent = async ({ type, transmission, syncDirection, filename, dryRun }, config) => { if (dryRun) { Logger.warning("[dry-run] Content sync will only read source data and report planned changes."); } if (type === "stories") { if (syncDirection === "fromSpaceToFile") { if (dryRun) { const stories = await getAllStories({}, { ...config, spaceId: transmission.from, sbApi: config.sbApi, }); Logger.warning(`[dry-run] Would back up ${stories.length} stories from ${transmission.from} to '${transmission.to}'.`); return true; } await backupStories({ filename: transmission.to, suffix: ".sb.stories", spaceId: transmission.from, }, config); } if (syncDirection === "fromSpaceToSpace") { const stories = await getAllStories({}, { ...config, spaceId: transmission.from, sbApi: config.sbApi, }); await syncStories({ transmission, stories, toSpaceId: transmission.to, dryRun, }, config); } if (syncDirection === "fromFileToSpace") { const allLocalStories = discoverStories({ scope: SCOPE.local, type: LOOKUP_TYPE.fileName, fileNames: [transmission.from], }); const storiesFileContent = getFilesContentWithRequire({ files: allLocalStories, }); await syncStories({ transmission, stories: storiesFileContent[0], toSpaceId: transmission.to, dryRun, }, config); } if (syncDirection === "fromAWSToSpace") { const data = await contentHubApi.getAllStories({ spaceId: transmission.from, storiesFilename: filename }, apiConfig); await syncStories({ transmission, stories: data.stories, toSpaceId: transmission.to, dryRun, }, config); } return true; } else if (type === "assets") { if (syncDirection === "fromFileToSpace") { Logger.warning(`${syncDirection} with ${type} This is not implemented yet!`); } else if (syncDirection === "fromSpaceToSpace" || syncDirection === "fromSpaceToFile") { await syncAssets({ transmission, syncDirection, dryRun }, config); } else { Logger.warning(`${syncDirection} with ${type} This is not implemented yet!`); } return true; } else { throw Error("This should never happen!"); } }; const setToDefaultPreset = (allPresets) => { return allPresets.find((preset) => preset.name === "Default"); }; export const setComponentDefaultPreset = async ({ presets, componentsToSync, apiConfig, }) => { if (presets) { Logger.warning("Setting default presets for components..."); const remoteComponents = await managementApi.components.getAllComponents(apiConfig); const filteredRemoteComponents = componentsToSync ? remoteComponents.filter((component) => componentsToSync.includes(component.name)) : remoteComponents; const finalRemoteComponents = filteredRemoteComponents.map((component) => { return { ...component, preset_id: setToDefaultPreset(component.all_presets)?.id, }; }); Logger.warning("Updating componet with default presets..."); return Promise.all(finalRemoteComponents.map((component) => { return managementApi.components.updateComponent(component, false, apiConfig); })); } else { return false; } };