sb-mig
Version:
CLI to rule the world. (and handle stuff related to Storyblok CMS)
269 lines (268 loc) • 10.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.syncComponentsData = syncComponentsData;
const array_utils_js_1 = require("../../utils/array-utils.js");
const logger_js_1 = __importDefault(require("../../utils/logger.js"));
const object_utils_js_1 = require("../../utils/object-utils.js");
/**
* Default progress callback that logs to console
*/
const defaultProgress = (event) => {
if (event.type === "start") {
logger_js_1.default.log(`Starting sync of ${event.total} components...`);
}
else if (event.type === "progress" && event.name) {
const status = event.action === "creating"
? "Creating"
: event.action === "updating"
? "Updating"
: event.action === "created"
? "✓ Created"
: event.action === "updated"
? "✓ Updated"
: event.action === "skipped"
? "⏭ Skipped"
: "✘ Error";
logger_js_1.default.log(`[${event.current}/${event.total}] ${status}: ${event.name}`);
}
else if (event.type === "complete") {
logger_js_1.default.success(`Sync complete: ${event.message ?? "done"}`);
}
};
const components_js_1 = require("./components.js");
function getErrorMessage(error) {
return error instanceof Error ? error.message : String(error);
}
async function ensureComponentGroupsExist(groupNames, config, options = {}) {
try {
const existing = await (0, components_js_1.getAllComponentsGroups)(config);
const existingNames = new Set((existing ?? []).map((g) => g.name));
for (const groupName of groupNames) {
if (!existingNames.has(groupName)) {
if (options.dryRun) {
logger_js_1.default.warning(`[dry-run] Would create component group '${groupName}'.`);
continue;
}
await (0, components_js_1.createComponentsGroup)(groupName, config);
}
}
}
catch (error) {
// Log but don't fail - component groups are optional
logger_js_1.default.warning(`Could not fetch component groups: ${error instanceof Error ? error.message : String(error)}`);
}
}
function resolveGroupUuid(component, remoteGroups) {
if (!component.component_group_name) {
return { ...component, component_group_uuid: null };
}
const match = remoteGroups.find((g) => g.name === component.component_group_name);
if (!match)
return { ...component, component_group_uuid: null };
return { ...component, component_group_uuid: match.uuid };
}
async function syncComponentsData(args, config) {
const { components, presets, ssot, dryRun, onProgress } = args;
const progress = onProgress ?? defaultProgress;
const result = {
created: [],
updated: [],
skipped: [],
errors: [],
};
if (dryRun) {
logger_js_1.default.warning("[dry-run] Component sync will only read remote data and report planned changes.");
}
if (ssot) {
const existingComponents = await (0, components_js_1.getAllComponents)(config);
const existingGroups = await (0, components_js_1.getAllComponentsGroups)(config);
if (dryRun) {
for (const component of existingComponents ?? []) {
logger_js_1.default.warning(`[dry-run] Would remove component '${component.name}'.`);
}
for (const group of existingGroups ?? []) {
logger_js_1.default.warning(`[dry-run] Would remove component group '${group.name}'.`);
}
}
else {
const removalTargets = [
...(existingComponents ?? []).map((component) => ({
type: "component",
name: String(component?.name ?? component?.id ?? "unknown"),
remove: () => (0, components_js_1.removeComponent)(component, config),
})),
...(existingGroups ?? []).map((group) => ({
type: "component group",
name: String(group?.name ?? group?.id ?? "unknown"),
remove: () => (0, components_js_1.removeComponentGroup)(group, config),
})),
];
const removalResults = await Promise.allSettled(removalTargets.map((target) => target.remove()));
removalResults.forEach((removalResult, index) => {
if (removalResult.status === "fulfilled")
return;
const target = removalTargets[index];
if (!target)
return;
const name = `${target.type} '${target.name}'`;
const message = getErrorMessage(removalResult.reason);
result.skipped.push(name);
result.errors.push({
name,
message: `SSOT removal failed: ${message}`,
});
logger_js_1.default.warning(`Could not remove ${name} during SSOT sync: ${message}`);
});
}
}
const nonEmptyComponents = components.filter((c) => !(0, object_utils_js_1.isObjectEmpty)(c));
const groupsToCheck = (0, array_utils_js_1.uniqueValuesFrom)(nonEmptyComponents
.filter((c) => c.component_group_name)
.map((c) => c.component_group_name));
await ensureComponentGroupsExist(groupsToCheck, config, { dryRun });
let remoteComponents = [];
let remoteGroups = [];
try {
remoteComponents =
ssot && dryRun ? [] : ((await (0, components_js_1.getAllComponents)(config)) ?? []);
}
catch (error) {
logger_js_1.default.warning(`Could not fetch remote components: ${error instanceof Error ? error.message : String(error)}`);
}
try {
remoteGroups = (await (0, components_js_1.getAllComponentsGroups)(config)) ?? [];
}
catch (error) {
logger_js_1.default.warning(`Could not fetch remote groups: ${error instanceof Error ? error.message : String(error)}`);
}
const componentsToUpdate = [];
const componentsToCreate = [];
for (const component of nonEmptyComponents) {
if (!component?.name) {
result.skipped.push("unknown");
continue;
}
const remote = remoteComponents.find((rc) => rc.name === component.name);
if (remote) {
componentsToUpdate.push({ id: remote.id, ...component });
}
else {
componentsToCreate.push(component);
}
}
// Resolve group uuids after ensureComponentGroupsExist
const updatePayloads = componentsToUpdate.map((c) => resolveGroupUuid(c, remoteGroups));
const createPayloads = componentsToCreate.map((c) => resolveGroupUuid(c, remoteGroups));
const totalComponents = updatePayloads.length + createPayloads.length;
let currentIndex = 0;
// Report start
progress({ type: "start", total: totalComponents });
// Process updates sequentially for progress reporting
for (const component of updatePayloads) {
const name = String(component?.name ?? "unknown");
currentIndex++;
progress({
type: "progress",
current: currentIndex,
total: totalComponents,
name,
action: "updating",
});
try {
if (dryRun) {
logger_js_1.default.warning(`[dry-run] Would update component '${name}'.`);
result.updated.push(name);
progress({
type: "progress",
current: currentIndex,
total: totalComponents,
name,
action: "updated",
});
continue;
}
await (0, components_js_1.updateComponent)(component, presets, config);
result.updated.push(name);
progress({
type: "progress",
current: currentIndex,
total: totalComponents,
name,
action: "updated",
});
}
catch (error) {
result.errors.push({
name,
message: getErrorMessage(error),
});
progress({
type: "progress",
current: currentIndex,
total: totalComponents,
name,
action: "error",
message: getErrorMessage(error),
});
}
}
// Process creates sequentially for progress reporting
for (const component of createPayloads) {
const name = String(component?.name ?? "unknown");
currentIndex++;
progress({
type: "progress",
current: currentIndex,
total: totalComponents,
name,
action: "creating",
});
try {
if (dryRun) {
logger_js_1.default.warning(`[dry-run] Would create component '${name}'.`);
result.created.push(name);
progress({
type: "progress",
current: currentIndex,
total: totalComponents,
name,
action: "created",
});
continue;
}
await (0, components_js_1.createComponent)(component, presets, config);
result.created.push(name);
progress({
type: "progress",
current: currentIndex,
total: totalComponents,
name,
action: "created",
});
}
catch (error) {
result.errors.push({
name,
message: getErrorMessage(error),
});
progress({
type: "progress",
current: currentIndex,
total: totalComponents,
name,
action: "error",
message: getErrorMessage(error),
});
}
}
// Report completion
progress({
type: "complete",
total: totalComponents,
message: `${result.created.length} created, ${result.updated.length} updated, ${result.errors.length} errors`,
});
return result;
}