storyblok 4.20.0-alpha.0 → 4.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +702 -86
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -698,6 +698,8 @@ const API_ACTIONS = {
|
|
|
698
698
|
pull_component_internal_tags: "Failed to pull component internal tags",
|
|
699
699
|
push_component: "Failed to push component",
|
|
700
700
|
push_component_group: "Failed to push component group",
|
|
701
|
+
push_component_folder: "Failed to create component folder",
|
|
702
|
+
delete_component_folder: "Failed to delete component folder",
|
|
701
703
|
push_component_preset: "Failed to push component preset",
|
|
702
704
|
push_component_internal_tag: "Failed to push component internal tag",
|
|
703
705
|
update_component: "Failed to update component",
|
|
@@ -806,7 +808,11 @@ class APIError extends Error {
|
|
|
806
808
|
if (this.code === 422) {
|
|
807
809
|
const responseData = this.response?.data;
|
|
808
810
|
if (responseData?.name?.[0] === "has already been taken") {
|
|
809
|
-
|
|
811
|
+
if (action === "push_component_folder") {
|
|
812
|
+
this.message = "A component folder with this name already exists";
|
|
813
|
+
} else if (action === "push_component" || action === "update_component") {
|
|
814
|
+
this.message = "A component with this name already exists";
|
|
815
|
+
}
|
|
810
816
|
}
|
|
811
817
|
Object.entries(responseData || {}).forEach(([key, errors]) => {
|
|
812
818
|
if (Array.isArray(errors)) {
|
|
@@ -5826,10 +5832,8 @@ const getPropertyTypeAnnotation = (property, prefix, suffix) => {
|
|
|
5826
5832
|
return { type: property.type };
|
|
5827
5833
|
}
|
|
5828
5834
|
let type = "unknown";
|
|
5829
|
-
const
|
|
5830
|
-
|
|
5831
|
-
options.unshift("");
|
|
5832
|
-
}
|
|
5835
|
+
const optionValues = property.options && property.options.length > 0 ? property.options.map((item) => item.value).filter((value) => value !== "") : [];
|
|
5836
|
+
const options = optionValues.length > 0 && !property.required ? ["", ...optionValues] : optionValues;
|
|
5833
5837
|
if (property.source === "internal_stories") {
|
|
5834
5838
|
if (property.filter_content_type) {
|
|
5835
5839
|
return {
|
|
@@ -6182,11 +6186,18 @@ const generateStoryblokTypes = async (options = {}) => {
|
|
|
6182
6186
|
const pushDatasource = async (spaceId, datasource) => {
|
|
6183
6187
|
try {
|
|
6184
6188
|
const client = getMapiClient();
|
|
6189
|
+
const { dimensions, ...datasourceFields } = datasource;
|
|
6190
|
+
const dimensionsAttributes = (dimensions ?? []).filter((dimension) => dimension.entry_value).map((dimension) => ({ name: dimension.name ?? dimension.entry_value ?? "", entry_value: dimension.entry_value ?? "" }));
|
|
6185
6191
|
const { data } = await client.datasources.create({
|
|
6186
6192
|
path: {
|
|
6187
6193
|
space_id: Number(spaceId)
|
|
6188
6194
|
},
|
|
6189
|
-
body: {
|
|
6195
|
+
body: {
|
|
6196
|
+
datasource: {
|
|
6197
|
+
...datasourceFields,
|
|
6198
|
+
...dimensionsAttributes.length > 0 && { dimensions_attributes: dimensionsAttributes }
|
|
6199
|
+
}
|
|
6200
|
+
},
|
|
6190
6201
|
throwOnError: true
|
|
6191
6202
|
});
|
|
6192
6203
|
return data.datasource;
|
|
@@ -6221,13 +6232,14 @@ const upsertDatasource = async (space, datasource, existingId) => {
|
|
|
6221
6232
|
const pushDatasourceEntry = async (spaceId, datasourceId, entry, position) => {
|
|
6222
6233
|
try {
|
|
6223
6234
|
const client = getMapiClient();
|
|
6235
|
+
const { dimension_values, ...entryFields } = entry;
|
|
6224
6236
|
const { data } = await client.datasourceEntries.create({
|
|
6225
6237
|
path: {
|
|
6226
6238
|
space_id: Number(spaceId)
|
|
6227
6239
|
},
|
|
6228
6240
|
body: {
|
|
6229
6241
|
datasource_entry: {
|
|
6230
|
-
...
|
|
6242
|
+
...entryFields,
|
|
6231
6243
|
value: entry.value ?? "",
|
|
6232
6244
|
datasource_id: datasourceId,
|
|
6233
6245
|
...position != null && { position }
|
|
@@ -6243,13 +6255,14 @@ const pushDatasourceEntry = async (spaceId, datasourceId, entry, position) => {
|
|
|
6243
6255
|
const updateDatasourceEntry = async (spaceId, entryId, entry, position) => {
|
|
6244
6256
|
try {
|
|
6245
6257
|
const client = getMapiClient();
|
|
6258
|
+
const { dimension_values, ...entryFields } = entry;
|
|
6246
6259
|
await client.datasourceEntries.update(entryId, {
|
|
6247
6260
|
path: {
|
|
6248
6261
|
space_id: Number(spaceId)
|
|
6249
6262
|
},
|
|
6250
6263
|
body: {
|
|
6251
6264
|
datasource_entry: {
|
|
6252
|
-
...
|
|
6265
|
+
...entryFields,
|
|
6253
6266
|
...position != null && { position }
|
|
6254
6267
|
}
|
|
6255
6268
|
},
|
|
@@ -6259,6 +6272,27 @@ const updateDatasourceEntry = async (spaceId, entryId, entry, position) => {
|
|
|
6259
6272
|
handleAPIError("update_datasource", error, `Failed to update datasource entry ${entry.name}`);
|
|
6260
6273
|
}
|
|
6261
6274
|
};
|
|
6275
|
+
const updateDatasourceEntryDimension = async (spaceId, entryId, entry, dimensionId, dimensionValue) => {
|
|
6276
|
+
try {
|
|
6277
|
+
const client = getMapiClient();
|
|
6278
|
+
await client.datasourceEntries.update(entryId, {
|
|
6279
|
+
path: {
|
|
6280
|
+
space_id: Number(spaceId)
|
|
6281
|
+
},
|
|
6282
|
+
query: { dimension_id: dimensionId },
|
|
6283
|
+
body: {
|
|
6284
|
+
datasource_entry: {
|
|
6285
|
+
name: entry.name,
|
|
6286
|
+
value: entry.value ?? "",
|
|
6287
|
+
dimension_value: dimensionValue
|
|
6288
|
+
}
|
|
6289
|
+
},
|
|
6290
|
+
throwOnError: true
|
|
6291
|
+
});
|
|
6292
|
+
} catch (error) {
|
|
6293
|
+
handleAPIError("update_datasource", error, `Failed to update datasource entry ${entry.name} for dimension ${dimensionId}`);
|
|
6294
|
+
}
|
|
6295
|
+
};
|
|
6262
6296
|
const upsertDatasourceEntry = async (space, datasourceId, entry, existingId, position) => {
|
|
6263
6297
|
if (existingId) {
|
|
6264
6298
|
await updateDatasourceEntry(space, existingId, entry, position);
|
|
@@ -6418,7 +6452,7 @@ const datasourcesCommand = program$7.command(commands.DATASOURCES).alias("ds").d
|
|
|
6418
6452
|
|
|
6419
6453
|
const DEFAULT_DATASOURCES_FILENAME = "datasources";
|
|
6420
6454
|
|
|
6421
|
-
const fetchDatasourceEntries = async (spaceId, datasourceId) => {
|
|
6455
|
+
const fetchDatasourceEntries = async (spaceId, datasourceId, dimensionId) => {
|
|
6422
6456
|
try {
|
|
6423
6457
|
const client = getMapiClient();
|
|
6424
6458
|
return await fetchAllPages(
|
|
@@ -6428,7 +6462,9 @@ const fetchDatasourceEntries = async (spaceId, datasourceId) => {
|
|
|
6428
6462
|
},
|
|
6429
6463
|
query: {
|
|
6430
6464
|
datasource_id: datasourceId,
|
|
6431
|
-
page
|
|
6465
|
+
page,
|
|
6466
|
+
// MAPI matches `dimension` against the numeric dimension id.
|
|
6467
|
+
...dimensionId != null && { dimension: dimensionId }
|
|
6432
6468
|
},
|
|
6433
6469
|
throwOnError: true
|
|
6434
6470
|
}),
|
|
@@ -6438,6 +6474,34 @@ const fetchDatasourceEntries = async (spaceId, datasourceId) => {
|
|
|
6438
6474
|
handleAPIError("pull_datasources", error);
|
|
6439
6475
|
}
|
|
6440
6476
|
};
|
|
6477
|
+
const fetchDatasourceEntriesWithDimensions = async (spaceId, datasource) => {
|
|
6478
|
+
const defaultEntries = await fetchDatasourceEntries(spaceId, datasource.id) ?? [];
|
|
6479
|
+
const entries = defaultEntries.map(({ dimension_value, ...rest }) => rest);
|
|
6480
|
+
const dimensions = datasource.dimensions ?? [];
|
|
6481
|
+
if (!dimensions.length) {
|
|
6482
|
+
return entries;
|
|
6483
|
+
}
|
|
6484
|
+
const entriesById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
6485
|
+
for (const dimension of dimensions) {
|
|
6486
|
+
if (dimension.id == null || !dimension.entry_value) {
|
|
6487
|
+
continue;
|
|
6488
|
+
}
|
|
6489
|
+
const dimensionEntries = await fetchDatasourceEntries(spaceId, datasource.id, dimension.id) ?? [];
|
|
6490
|
+
for (const dimensionEntry of dimensionEntries) {
|
|
6491
|
+
const value = dimensionEntry.dimension_value;
|
|
6492
|
+
if (!value) {
|
|
6493
|
+
continue;
|
|
6494
|
+
}
|
|
6495
|
+
const entry = entriesById.get(dimensionEntry.id);
|
|
6496
|
+
if (!entry) {
|
|
6497
|
+
continue;
|
|
6498
|
+
}
|
|
6499
|
+
entry.dimension_values ??= {};
|
|
6500
|
+
entry.dimension_values[dimension.entry_value] = value;
|
|
6501
|
+
}
|
|
6502
|
+
}
|
|
6503
|
+
return entries;
|
|
6504
|
+
};
|
|
6441
6505
|
const fetchDatasources = async (spaceId) => {
|
|
6442
6506
|
try {
|
|
6443
6507
|
const client = getMapiClient();
|
|
@@ -6458,7 +6522,7 @@ const fetchDatasources = async (spaceId) => {
|
|
|
6458
6522
|
if (!ds.id) {
|
|
6459
6523
|
return { ...ds, entries: [] };
|
|
6460
6524
|
}
|
|
6461
|
-
const entries = await
|
|
6525
|
+
const entries = await fetchDatasourceEntriesWithDimensions(spaceId, ds);
|
|
6462
6526
|
return { ...ds, entries };
|
|
6463
6527
|
})
|
|
6464
6528
|
);
|
|
@@ -6487,8 +6551,8 @@ const fetchDatasource = async (spaceId, datasourceName) => {
|
|
|
6487
6551
|
if (!found) {
|
|
6488
6552
|
return void 0;
|
|
6489
6553
|
}
|
|
6490
|
-
const entries = await
|
|
6491
|
-
return { ...found, entries
|
|
6554
|
+
const entries = await fetchDatasourceEntriesWithDimensions(spaceId, found);
|
|
6555
|
+
return { ...found, entries };
|
|
6492
6556
|
} catch (error) {
|
|
6493
6557
|
handleAPIError("pull_datasources", error, `Failed to fetch datasource ${datasourceName}`);
|
|
6494
6558
|
}
|
|
@@ -6586,6 +6650,14 @@ pullCmd$2.action(async (datasourceName, options, command) => {
|
|
|
6586
6650
|
}
|
|
6587
6651
|
});
|
|
6588
6652
|
|
|
6653
|
+
function dimensionValuesEqual(a, b) {
|
|
6654
|
+
const aKeys = Object.keys(a ?? {});
|
|
6655
|
+
const bKeys = Object.keys(b ?? {});
|
|
6656
|
+
if (aKeys.length !== bKeys.length) {
|
|
6657
|
+
return false;
|
|
6658
|
+
}
|
|
6659
|
+
return aKeys.every((key) => a?.[key] === b?.[key]);
|
|
6660
|
+
}
|
|
6589
6661
|
const pushCmd$2 = datasourcesCommand.command("push [datasourceName]").description(`Push your space's datasources schema as json`).option("-f, --from <from>", "source space id").option("--fi, --filter <filter>", "glob filter to apply to the datasources before pushing").option("--sf, --separate-files", "Read from separate files instead of consolidated files").option("--su, --suffix <suffix>", "Load only files matching *.<suffix>.json (e.g. datasources.dev.json)").option("-s, --space <space>", "space ID");
|
|
6590
6662
|
pushCmd$2.action(async (datasourceName, options, command) => {
|
|
6591
6663
|
konsola.title(`${commands.DATASOURCES}`, colorPalette.DATASOURCES, datasourceName ? `Pushing datasource ${datasourceName}...` : "Pushing datasources...");
|
|
@@ -6657,19 +6729,39 @@ pushCmd$2.action(async (datasourceName, options, command) => {
|
|
|
6657
6729
|
results.successful.push(datasource.name);
|
|
6658
6730
|
const localEntries = entries ?? [];
|
|
6659
6731
|
const existingEntries = existingDatasource?.entries ?? [];
|
|
6732
|
+
const dimensionWarnings = [];
|
|
6660
6733
|
const existingEntryMap = new Map(existingEntries.map((e, idx) => [e.name, { entry: e, position: idx + 1 }]));
|
|
6661
6734
|
for (let i = 0; i < localEntries.length; i++) {
|
|
6662
6735
|
const entry = localEntries[i];
|
|
6663
6736
|
const existing = existingEntryMap.get(entry.name);
|
|
6664
6737
|
const existingEntryId = existing?.entry.id;
|
|
6665
6738
|
const targetPosition = i + 1;
|
|
6666
|
-
if (existing && existing.entry.value === entry.value && existing.entry.
|
|
6739
|
+
if (existing && existing.entry.value === entry.value && dimensionValuesEqual(existing.entry.dimension_values, entry.dimension_values) && existing.position === targetPosition) {
|
|
6667
6740
|
logger.info("Skipped datasource entry (unchanged)", { datasource: datasource.name, entry: entry.name, position: targetPosition });
|
|
6668
6741
|
continue;
|
|
6669
6742
|
}
|
|
6670
6743
|
try {
|
|
6671
|
-
await upsertDatasourceEntry(space, result.id, entry, existingEntryId, i + 1);
|
|
6744
|
+
const upserted = await upsertDatasourceEntry(space, result.id, entry, existingEntryId, i + 1);
|
|
6745
|
+
const entryId = existingEntryId ?? upserted?.id;
|
|
6672
6746
|
logger.info(existingEntryId ? "Updated datasource entry" : "Created datasource entry", { datasource: datasource.name, entry: entry.name, position: i + 1 });
|
|
6747
|
+
if (entryId != null && entry.dimension_values !== void 0) {
|
|
6748
|
+
const localDimensionValues = entry.dimension_values;
|
|
6749
|
+
const targetDimensionValues = existing?.entry.dimension_values ?? {};
|
|
6750
|
+
const dimensionCodes = /* @__PURE__ */ new Set([...Object.keys(localDimensionValues), ...Object.keys(targetDimensionValues)]);
|
|
6751
|
+
for (const code of dimensionCodes) {
|
|
6752
|
+
const localValue = localDimensionValues[code] ?? "";
|
|
6753
|
+
const targetValue = targetDimensionValues[code] ?? "";
|
|
6754
|
+
if (localValue === targetValue) {
|
|
6755
|
+
continue;
|
|
6756
|
+
}
|
|
6757
|
+
const dimension = result.dimensions?.find((d) => d.entry_value === code);
|
|
6758
|
+
if (!dimension?.id) {
|
|
6759
|
+
dimensionWarnings.push(`Skipping dimension "${code}" for entry "${entry.name}": no matching dimension in target space ${space}`);
|
|
6760
|
+
continue;
|
|
6761
|
+
}
|
|
6762
|
+
await updateDatasourceEntryDimension(space, entryId, entry, dimension.id, localValue);
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
6673
6765
|
} catch (entryError) {
|
|
6674
6766
|
results.failed.push({ name: datasource.name, error: entryError });
|
|
6675
6767
|
spinner.failed(`${chalk.hex(colorPalette.DATASOURCES)(datasource.name)} - Failed in ${spinner.elapsedTime.toFixed(2)}ms`);
|
|
@@ -6687,6 +6779,7 @@ pushCmd$2.action(async (datasourceName, options, command) => {
|
|
|
6687
6779
|
}
|
|
6688
6780
|
}
|
|
6689
6781
|
spinner.succeed(`${chalk.hex(colorPalette.DATASOURCES)(datasource.name)} - Completed in ${spinner.elapsedTime.toFixed(2)}ms`);
|
|
6782
|
+
dimensionWarnings.forEach((warning) => konsola.warn(warning));
|
|
6690
6783
|
} else {
|
|
6691
6784
|
results.failed.push({ name: datasource.name, error: result });
|
|
6692
6785
|
spinner.failed(`${chalk.hex(colorPalette.DATASOURCES)(datasource.name)} - Failed in ${spinner.elapsedTime.toFixed(2)}ms`);
|
|
@@ -7820,7 +7913,7 @@ const extractAssetSizeFromFilename = (filename) => {
|
|
|
7820
7913
|
}
|
|
7821
7914
|
try {
|
|
7822
7915
|
const segments = new URL(filename).pathname.split("/");
|
|
7823
|
-
return segments.find((segment) => /^\d+x\d
|
|
7916
|
+
return segments.find((segment) => /^\d+x\d+$/i.test(segment));
|
|
7824
7917
|
} catch {
|
|
7825
7918
|
return void 0;
|
|
7826
7919
|
}
|
|
@@ -10698,6 +10791,11 @@ function applyDefaults(entity, defaults) {
|
|
|
10698
10791
|
return result;
|
|
10699
10792
|
}
|
|
10700
10793
|
const INDENT = " ";
|
|
10794
|
+
class RawCode {
|
|
10795
|
+
constructor(code) {
|
|
10796
|
+
this.code = code;
|
|
10797
|
+
}
|
|
10798
|
+
}
|
|
10701
10799
|
function quoteString(value) {
|
|
10702
10800
|
const escaped = JSON.stringify(value).slice(1, -1).replace(/\\"/g, '"').replace(/'/g, "\\'");
|
|
10703
10801
|
return `'${escaped}'`;
|
|
@@ -10708,6 +10806,9 @@ function formatValue(value, depth) {
|
|
|
10708
10806
|
if (value === null || value === void 0) {
|
|
10709
10807
|
return String(value);
|
|
10710
10808
|
}
|
|
10809
|
+
if (value instanceof RawCode) {
|
|
10810
|
+
return value.code;
|
|
10811
|
+
}
|
|
10711
10812
|
if (typeof value === "string") {
|
|
10712
10813
|
return quoteString(value);
|
|
10713
10814
|
}
|
|
@@ -10753,10 +10854,69 @@ function stripKeys(obj, keysToStrip) {
|
|
|
10753
10854
|
return result;
|
|
10754
10855
|
}
|
|
10755
10856
|
|
|
10857
|
+
function buildGroupPathByUuid(folders) {
|
|
10858
|
+
const byUuid = new Map(folders.map((folder) => [folder.uuid, folder]));
|
|
10859
|
+
const cache = /* @__PURE__ */ new Map();
|
|
10860
|
+
function pathFor(uuid, visited) {
|
|
10861
|
+
if (!uuid) {
|
|
10862
|
+
return [];
|
|
10863
|
+
}
|
|
10864
|
+
const cached = cache.get(uuid);
|
|
10865
|
+
if (cached) {
|
|
10866
|
+
return cached;
|
|
10867
|
+
}
|
|
10868
|
+
const folder = byUuid.get(uuid);
|
|
10869
|
+
if (!folder) {
|
|
10870
|
+
return [];
|
|
10871
|
+
}
|
|
10872
|
+
if (visited.has(uuid)) {
|
|
10873
|
+
return [];
|
|
10874
|
+
}
|
|
10875
|
+
visited.add(uuid);
|
|
10876
|
+
const path = [...pathFor(folder.parent_uuid, visited), slugify(folder.name)];
|
|
10877
|
+
cache.set(uuid, path);
|
|
10878
|
+
return path;
|
|
10879
|
+
}
|
|
10880
|
+
for (const folder of folders) {
|
|
10881
|
+
pathFor(folder.uuid, /* @__PURE__ */ new Set());
|
|
10882
|
+
}
|
|
10883
|
+
return cache;
|
|
10884
|
+
}
|
|
10885
|
+
function slugifyPath(displayPath) {
|
|
10886
|
+
return displayPath.split("/").map((segment) => slugify(segment)).filter(Boolean).join("/");
|
|
10887
|
+
}
|
|
10888
|
+
function expandFolderPath(displayPath) {
|
|
10889
|
+
const segments = displayPath.split("/").map((segment) => ({ name: segment, slug: slugify(segment) })).filter((segment) => segment.slug !== "");
|
|
10890
|
+
const result = [];
|
|
10891
|
+
let parentPath = null;
|
|
10892
|
+
for (const { name, slug } of segments) {
|
|
10893
|
+
const path = parentPath ? `${parentPath}/${slug}` : slug;
|
|
10894
|
+
result.push({ name, path, parentPath });
|
|
10895
|
+
parentPath = path;
|
|
10896
|
+
}
|
|
10897
|
+
return result;
|
|
10898
|
+
}
|
|
10899
|
+
|
|
10756
10900
|
function mapFieldToWire(field) {
|
|
10757
10901
|
const { name, allow, datasource, ...rest } = field;
|
|
10758
10902
|
const value = { ...rest };
|
|
10759
|
-
if (allow !== void 0) {
|
|
10903
|
+
if (allow !== void 0 && Array.isArray(allow)) {
|
|
10904
|
+
const folderPaths = allow.filter((entry) => isRecord(entry) && typeof entry.folder === "string").map((entry) => slugifyPath(entry.folder));
|
|
10905
|
+
const blockNames = allow.filter((entry) => typeof entry === "string");
|
|
10906
|
+
if (folderPaths.length > 0) {
|
|
10907
|
+
value.component_group_whitelist = folderPaths;
|
|
10908
|
+
if (rest.type === "bloks" || rest.type === "richtext") {
|
|
10909
|
+
value.restrict_components = true;
|
|
10910
|
+
value.restrict_type = "groups";
|
|
10911
|
+
}
|
|
10912
|
+
} else {
|
|
10913
|
+
value.component_whitelist = blockNames;
|
|
10914
|
+
if (rest.type === "bloks") {
|
|
10915
|
+
value.restrict_components = true;
|
|
10916
|
+
value.restrict_type = "";
|
|
10917
|
+
}
|
|
10918
|
+
}
|
|
10919
|
+
} else if (allow !== void 0) {
|
|
10760
10920
|
value.component_whitelist = allow;
|
|
10761
10921
|
if (rest.type === "bloks") {
|
|
10762
10922
|
value.restrict_components = true;
|
|
@@ -10769,7 +10929,7 @@ function mapFieldToWire(field) {
|
|
|
10769
10929
|
return { name: typeof name === "string" ? name : "", value };
|
|
10770
10930
|
}
|
|
10771
10931
|
function mapBlockToWire(block) {
|
|
10772
|
-
const { fields, ...rest } = block;
|
|
10932
|
+
const { fields, folder, ...rest } = block;
|
|
10773
10933
|
const schema = {};
|
|
10774
10934
|
if (Array.isArray(fields)) {
|
|
10775
10935
|
for (const field of fields) {
|
|
@@ -10782,7 +10942,11 @@ function mapBlockToWire(block) {
|
|
|
10782
10942
|
}
|
|
10783
10943
|
}
|
|
10784
10944
|
}
|
|
10785
|
-
return {
|
|
10945
|
+
return {
|
|
10946
|
+
...rest,
|
|
10947
|
+
...folder !== void 0 && { folder: typeof folder === "string" ? slugifyPath(folder) : null },
|
|
10948
|
+
schema
|
|
10949
|
+
};
|
|
10786
10950
|
}
|
|
10787
10951
|
function mapDatasourceToWire(datasource) {
|
|
10788
10952
|
return datasource;
|
|
@@ -10794,23 +10958,88 @@ function isComponent(value) {
|
|
|
10794
10958
|
function isDatasource(value) {
|
|
10795
10959
|
return isRecord(value) && typeof value.name === "string" && typeof value.slug === "string" && !Array.isArray(value.fields);
|
|
10796
10960
|
}
|
|
10961
|
+
function isFolder(value) {
|
|
10962
|
+
return isRecord(value) && typeof value.name === "string" && typeof value.path === "string" && !Array.isArray(value.fields) && !("slug" in value);
|
|
10963
|
+
}
|
|
10964
|
+
function buildLocalFolders(registered, derived) {
|
|
10965
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
10966
|
+
const add = (displayPath, registeredLeaf) => {
|
|
10967
|
+
const expanded = expandFolderPath(displayPath);
|
|
10968
|
+
expanded.forEach((entry, i) => {
|
|
10969
|
+
const isLeaf = i === expanded.length - 1;
|
|
10970
|
+
const isRegisteredEntry = registeredLeaf && isLeaf;
|
|
10971
|
+
const existing = byPath.get(entry.path);
|
|
10972
|
+
if (!existing) {
|
|
10973
|
+
byPath.set(entry.path, { folder: entry, isRegistered: isRegisteredEntry });
|
|
10974
|
+
return;
|
|
10975
|
+
}
|
|
10976
|
+
if (isRegisteredEntry && existing.isRegistered && existing.folder.name !== entry.name) {
|
|
10977
|
+
throw new CommandError(`Conflicting folder names for path "${entry.path}": "${existing.folder.name}" vs "${entry.name}"`);
|
|
10978
|
+
}
|
|
10979
|
+
if (isRegisteredEntry && !existing.isRegistered) {
|
|
10980
|
+
byPath.set(entry.path, { folder: entry, isRegistered: true });
|
|
10981
|
+
}
|
|
10982
|
+
});
|
|
10983
|
+
};
|
|
10984
|
+
for (const path of registered) {
|
|
10985
|
+
add(path, true);
|
|
10986
|
+
}
|
|
10987
|
+
for (const path of derived) {
|
|
10988
|
+
add(path, false);
|
|
10989
|
+
}
|
|
10990
|
+
const folders = [...byPath.values()].map((v) => v.folder).sort((a, b) => a.path.split("/").length - b.path.split("/").length || a.path.localeCompare(b.path));
|
|
10991
|
+
const leafToPath = /* @__PURE__ */ new Map();
|
|
10992
|
+
for (const folder of folders) {
|
|
10993
|
+
const leaf = folder.path.split("/").pop() ?? folder.path;
|
|
10994
|
+
const existing = leafToPath.get(leaf);
|
|
10995
|
+
if (existing !== void 0 && existing !== folder.path) {
|
|
10996
|
+
throw new CommandError(
|
|
10997
|
+
`Duplicate folder name "${leaf}" (folders "${existing}" and "${folder.path}"): Storyblok group names must be unique per space, even under different parents.`
|
|
10998
|
+
);
|
|
10999
|
+
}
|
|
11000
|
+
leafToPath.set(leaf, folder.path);
|
|
11001
|
+
}
|
|
11002
|
+
return folders;
|
|
11003
|
+
}
|
|
10797
11004
|
function isSchemaObject(value) {
|
|
10798
|
-
return isRecord(value) && ("blocks" in value || "datasources" in value);
|
|
11005
|
+
return isRecord(value) && ("blocks" in value || "datasources" in value || "folders" in value);
|
|
10799
11006
|
}
|
|
10800
11007
|
function emptySchemaData() {
|
|
10801
|
-
return { components: [], datasources: [] };
|
|
11008
|
+
return { components: [], folders: [], datasources: [] };
|
|
10802
11009
|
}
|
|
10803
11010
|
function classifyExports(moduleExports) {
|
|
10804
11011
|
const data = emptySchemaData();
|
|
10805
11012
|
const seenComponents = /* @__PURE__ */ new Set();
|
|
10806
11013
|
const seenDatasources = /* @__PURE__ */ new Set();
|
|
11014
|
+
const registered = [];
|
|
11015
|
+
const derived = [];
|
|
11016
|
+
function harvestComponentPaths(value) {
|
|
11017
|
+
if (typeof value.folder === "string") {
|
|
11018
|
+
derived.push(value.folder);
|
|
11019
|
+
}
|
|
11020
|
+
if (Array.isArray(value.fields)) {
|
|
11021
|
+
for (const field of value.fields) {
|
|
11022
|
+
if (!isRecord(field) || !Array.isArray(field.allow)) {
|
|
11023
|
+
continue;
|
|
11024
|
+
}
|
|
11025
|
+
for (const entry of field.allow) {
|
|
11026
|
+
if (isRecord(entry) && typeof entry.folder === "string") {
|
|
11027
|
+
derived.push(entry.folder);
|
|
11028
|
+
}
|
|
11029
|
+
}
|
|
11030
|
+
}
|
|
11031
|
+
}
|
|
11032
|
+
}
|
|
10807
11033
|
function collect(value) {
|
|
10808
11034
|
if (isComponent(value)) {
|
|
11035
|
+
harvestComponentPaths(value);
|
|
10809
11036
|
if (seenComponents.has(value.name)) {
|
|
10810
11037
|
return;
|
|
10811
11038
|
}
|
|
10812
11039
|
seenComponents.add(value.name);
|
|
10813
11040
|
data.components.push(mapBlockToWire(value));
|
|
11041
|
+
} else if (isFolder(value)) {
|
|
11042
|
+
registered.push(value.path);
|
|
10814
11043
|
} else if (isDatasource(value)) {
|
|
10815
11044
|
if (seenDatasources.has(value.name)) {
|
|
10816
11045
|
return;
|
|
@@ -10832,6 +11061,7 @@ function classifyExports(moduleExports) {
|
|
|
10832
11061
|
collect(value);
|
|
10833
11062
|
}
|
|
10834
11063
|
}
|
|
11064
|
+
data.folders = buildLocalFolders(registered, derived);
|
|
10835
11065
|
return data;
|
|
10836
11066
|
}
|
|
10837
11067
|
async function loadSchema(entryPath) {
|
|
@@ -10859,6 +11089,7 @@ function sortSchemaByPos$1(schema) {
|
|
|
10859
11089
|
})
|
|
10860
11090
|
);
|
|
10861
11091
|
}
|
|
11092
|
+
const FOLDER_UNGROUPED = "__FOLDER_UNGROUPED__";
|
|
10862
11093
|
function serializeComponent(component, options = {}) {
|
|
10863
11094
|
const stripSet = options.includeGroupUuid ? new Set([...COMPONENT_STRIP_KEYS].filter((key) => key !== "component_group_uuid")) : COMPONENT_STRIP_KEYS;
|
|
10864
11095
|
const clean = stripKeys(component, stripSet);
|
|
@@ -10878,7 +11109,10 @@ function serializeComponent(component, options = {}) {
|
|
|
10878
11109
|
if (clean.is_nestable !== void 0) {
|
|
10879
11110
|
ordered.is_nestable = clean.is_nestable;
|
|
10880
11111
|
}
|
|
10881
|
-
|
|
11112
|
+
if ("folder" in component) {
|
|
11113
|
+
ordered.folder = component.folder === null ? FOLDER_UNGROUPED : component.folder;
|
|
11114
|
+
}
|
|
11115
|
+
const handled = /* @__PURE__ */ new Set(["name", "display_name", "is_root", "is_nestable", "folder", "schema"]);
|
|
10882
11116
|
for (const [key, value] of Object.entries(clean).sort(([a], [b]) => a.localeCompare(b))) {
|
|
10883
11117
|
if (!handled.has(key)) {
|
|
10884
11118
|
ordered[key] = value;
|
|
@@ -10887,7 +11121,7 @@ function serializeComponent(component, options = {}) {
|
|
|
10887
11121
|
if (clean.schema !== void 0) {
|
|
10888
11122
|
ordered.schema = clean.schema;
|
|
10889
11123
|
}
|
|
10890
|
-
return `defineBlock(${formatValue(ordered, 0)})
|
|
11124
|
+
return `defineBlock(${formatValue(ordered, 0)})`.replace(`folder: '${FOLDER_UNGROUPED}'`, "folder: null");
|
|
10891
11125
|
}
|
|
10892
11126
|
function serializeDatasource(datasource) {
|
|
10893
11127
|
const clean = stripKeys(datasource, DATASOURCE_STRIP_KEYS);
|
|
@@ -10912,6 +11146,23 @@ function serializeDatasource(datasource) {
|
|
|
10912
11146
|
return `defineDatasource(${formatValue(ordered, 0)})`;
|
|
10913
11147
|
}
|
|
10914
11148
|
|
|
11149
|
+
function translateGroupWhitelist(schema, uuidToPath) {
|
|
11150
|
+
if (!isRecord(schema)) {
|
|
11151
|
+
return schema;
|
|
11152
|
+
}
|
|
11153
|
+
const result = {};
|
|
11154
|
+
for (const [fieldName, field] of Object.entries(schema)) {
|
|
11155
|
+
if (isRecord(field) && Array.isArray(field.component_group_whitelist)) {
|
|
11156
|
+
result[fieldName] = {
|
|
11157
|
+
...field,
|
|
11158
|
+
component_group_whitelist: field.component_group_whitelist.map((entry) => typeof entry === "string" ? uuidToPath.get(entry) ?? entry : entry)
|
|
11159
|
+
};
|
|
11160
|
+
} else {
|
|
11161
|
+
result[fieldName] = field;
|
|
11162
|
+
}
|
|
11163
|
+
}
|
|
11164
|
+
return result;
|
|
11165
|
+
}
|
|
10915
11166
|
function diffEntity(type, name, localSerialized, remoteSerialized) {
|
|
10916
11167
|
if (!remoteSerialized && localSerialized) {
|
|
10917
11168
|
return { type, name, action: "create", diff: null, local: null, remote: null };
|
|
@@ -10934,13 +11185,46 @@ function diffEntity(type, name, localSerialized, remoteSerialized) {
|
|
|
10934
11185
|
}
|
|
10935
11186
|
function diffSchema(local, remote) {
|
|
10936
11187
|
const diffs = [];
|
|
11188
|
+
const groupPathByUuid = buildGroupPathByUuid([...remote.componentFolders.values()]);
|
|
11189
|
+
const uuidToPath = /* @__PURE__ */ new Map();
|
|
11190
|
+
for (const [uuid, segments] of groupPathByUuid) {
|
|
11191
|
+
uuidToPath.set(uuid, segments.join("/"));
|
|
11192
|
+
}
|
|
11193
|
+
const remoteFolderPaths = new Set(uuidToPath.values());
|
|
11194
|
+
const localFolderPaths = new Set(local.folders.map((f) => f.path));
|
|
11195
|
+
for (const folder of local.folders) {
|
|
11196
|
+
const action = remoteFolderPaths.has(folder.path) ? "unchanged" : "create";
|
|
11197
|
+
diffs.push({ type: "folder", name: folder.path, action, diff: null, local: null, remote: null });
|
|
11198
|
+
}
|
|
11199
|
+
for (const path of remoteFolderPaths) {
|
|
11200
|
+
if (!localFolderPaths.has(path)) {
|
|
11201
|
+
diffs.push({ type: "folder", name: path, action: "stale", diff: null, local: null, remote: null });
|
|
11202
|
+
}
|
|
11203
|
+
}
|
|
10937
11204
|
const processedComponentNames = /* @__PURE__ */ new Set();
|
|
10938
11205
|
for (const comp of local.components) {
|
|
10939
11206
|
processedComponentNames.add(comp.name);
|
|
10940
11207
|
const remoteComp = remote.components.get(comp.name);
|
|
10941
11208
|
const includeGroupUuid = typeof comp.component_group_uuid === "string";
|
|
10942
|
-
const
|
|
10943
|
-
const
|
|
11209
|
+
const localForDiff = { ...comp };
|
|
11210
|
+
const remoteForDiff = remoteComp ? { ...remoteComp } : void 0;
|
|
11211
|
+
if ("folder" in comp) {
|
|
11212
|
+
if (remoteForDiff) {
|
|
11213
|
+
const uuid = remoteForDiff.component_group_uuid;
|
|
11214
|
+
remoteForDiff.folder = typeof uuid === "string" && uuid ? uuidToPath.get(uuid) ?? null : null;
|
|
11215
|
+
}
|
|
11216
|
+
} else {
|
|
11217
|
+
delete localForDiff.folder;
|
|
11218
|
+
if (remoteForDiff) {
|
|
11219
|
+
delete remoteForDiff.folder;
|
|
11220
|
+
}
|
|
11221
|
+
}
|
|
11222
|
+
localForDiff.schema = translateGroupWhitelist(localForDiff.schema, uuidToPath);
|
|
11223
|
+
if (remoteForDiff) {
|
|
11224
|
+
remoteForDiff.schema = translateGroupWhitelist(remoteForDiff.schema, uuidToPath);
|
|
11225
|
+
}
|
|
11226
|
+
const localSerialized = serializeComponent(applyDefaults(localForDiff, COMPONENT_DEFAULTS), { includeGroupUuid });
|
|
11227
|
+
const remoteSerialized = remoteForDiff ? serializeComponent(applyDefaults(remoteForDiff, COMPONENT_DEFAULTS), { includeGroupUuid }) : null;
|
|
10944
11228
|
diffs.push(diffEntity("component", comp.name, localSerialized, remoteSerialized));
|
|
10945
11229
|
}
|
|
10946
11230
|
for (const [name] of remote.components) {
|
|
@@ -11023,7 +11307,10 @@ function buildComponentPayload(input) {
|
|
|
11023
11307
|
...isRecord(input.schema) && { schema: toSchemaRecord(input.schema) },
|
|
11024
11308
|
...typeof input.is_root === "boolean" && { is_root: input.is_root },
|
|
11025
11309
|
...typeof input.is_nestable === "boolean" && { is_nestable: input.is_nestable },
|
|
11026
|
-
|
|
11310
|
+
// Forward the group membership only when the key is present on input: a
|
|
11311
|
+
// string sets the group, an explicit `null` clears it. Unmanaged components
|
|
11312
|
+
// (key absent) omit it so their remote group is left untouched.
|
|
11313
|
+
..."component_group_uuid" in input && (typeof input.component_group_uuid === "string" || input.component_group_uuid === null) && { component_group_uuid: input.component_group_uuid }
|
|
11027
11314
|
};
|
|
11028
11315
|
}
|
|
11029
11316
|
function toComponentCreate(input) {
|
|
@@ -11066,11 +11353,54 @@ function toDatasourceUpdate(input, remote) {
|
|
|
11066
11353
|
return base;
|
|
11067
11354
|
}
|
|
11068
11355
|
|
|
11356
|
+
function resolveGroupRefs(comp, groupByPath) {
|
|
11357
|
+
const { folder, ...rest } = comp;
|
|
11358
|
+
const resolved = { ...rest };
|
|
11359
|
+
if (typeof folder === "string") {
|
|
11360
|
+
const group = groupByPath.get(folder);
|
|
11361
|
+
if (!group) {
|
|
11362
|
+
throw new CommandError(
|
|
11363
|
+
`Unknown folder path "${folder}" for component "${comp.name}": no matching component group exists. Folder creation runs before component push, so this indicates an internal inconsistency.`
|
|
11364
|
+
);
|
|
11365
|
+
}
|
|
11366
|
+
resolved.component_group_uuid = group.uuid;
|
|
11367
|
+
} else if (folder === null) {
|
|
11368
|
+
resolved.component_group_uuid = null;
|
|
11369
|
+
}
|
|
11370
|
+
if (isRecord(resolved.schema)) {
|
|
11371
|
+
const schema = {};
|
|
11372
|
+
for (const [key, field] of Object.entries(resolved.schema)) {
|
|
11373
|
+
if (isRecord(field) && Array.isArray(field.component_group_whitelist)) {
|
|
11374
|
+
schema[key] = {
|
|
11375
|
+
...field,
|
|
11376
|
+
component_group_whitelist: field.component_group_whitelist.map((path) => typeof path === "string" ? groupByPath.get(path)?.uuid ?? path : path)
|
|
11377
|
+
};
|
|
11378
|
+
} else {
|
|
11379
|
+
schema[key] = field;
|
|
11380
|
+
}
|
|
11381
|
+
}
|
|
11382
|
+
resolved.schema = schema;
|
|
11383
|
+
}
|
|
11384
|
+
return resolved;
|
|
11385
|
+
}
|
|
11386
|
+
function buildGroupByPath(remote) {
|
|
11387
|
+
const remoteFolders = [...remote.componentFolders.values()];
|
|
11388
|
+
const groupPathByUuid = buildGroupPathByUuid(remoteFolders);
|
|
11389
|
+
const groupByPath = /* @__PURE__ */ new Map();
|
|
11390
|
+
for (const folder of remoteFolders) {
|
|
11391
|
+
const segments = groupPathByUuid.get(folder.uuid);
|
|
11392
|
+
if (segments?.length) {
|
|
11393
|
+
groupByPath.set(segments.join("/"), { id: folder.id, uuid: folder.uuid });
|
|
11394
|
+
}
|
|
11395
|
+
}
|
|
11396
|
+
return groupByPath;
|
|
11397
|
+
}
|
|
11069
11398
|
function formatDiffOutput(result, options) {
|
|
11070
11399
|
const lines = [];
|
|
11071
11400
|
const byType = {
|
|
11072
11401
|
component: [],
|
|
11073
|
-
datasource: []
|
|
11402
|
+
datasource: [],
|
|
11403
|
+
folder: []
|
|
11074
11404
|
};
|
|
11075
11405
|
for (const diff of result.diffs) {
|
|
11076
11406
|
byType[diff.type].push(diff);
|
|
@@ -11083,6 +11413,7 @@ function formatDiffOutput(result, options) {
|
|
|
11083
11413
|
stale: chalk.red("-")
|
|
11084
11414
|
};
|
|
11085
11415
|
const sections = [
|
|
11416
|
+
["Folders", byType.folder],
|
|
11086
11417
|
["Components", byType.component],
|
|
11087
11418
|
["Datasources", byType.datasource]
|
|
11088
11419
|
];
|
|
@@ -11123,24 +11454,62 @@ async function executePush(spaceId, local, remote, diffResult, options) {
|
|
|
11123
11454
|
let created = 0;
|
|
11124
11455
|
let updated = 0;
|
|
11125
11456
|
let deleted = 0;
|
|
11457
|
+
const groupByPath = buildGroupByPath(remote);
|
|
11458
|
+
const foldersByPath = new Map(local.folders.map((f) => [f.path, f]));
|
|
11459
|
+
const folderCreates = diffResult.diffs.filter((d) => d.type === "folder" && d.action === "create").sort((a, b) => a.name.split("/").length - b.name.split("/").length);
|
|
11460
|
+
for (const diff of folderCreates) {
|
|
11461
|
+
const localFolder = foldersByPath.get(diff.name);
|
|
11462
|
+
if (!localFolder) {
|
|
11463
|
+
continue;
|
|
11464
|
+
}
|
|
11465
|
+
const parent = localFolder.parentPath ? groupByPath.get(localFolder.parentPath) : void 0;
|
|
11466
|
+
let createdGroup;
|
|
11467
|
+
try {
|
|
11468
|
+
const res = await client.componentFolders.create({
|
|
11469
|
+
path: { space_id: spaceIdNum },
|
|
11470
|
+
body: { component_group: { name: localFolder.name, parent_id: parent?.id ?? null } },
|
|
11471
|
+
throwOnError: true
|
|
11472
|
+
});
|
|
11473
|
+
createdGroup = res.data?.component_group;
|
|
11474
|
+
} catch (error) {
|
|
11475
|
+
handleAPIError("push_component_folder", error, `Failed to create folder ${localFolder.name}`);
|
|
11476
|
+
}
|
|
11477
|
+
if (createdGroup?.id == null || !createdGroup.uuid) {
|
|
11478
|
+
throw new CommandError(
|
|
11479
|
+
`Folder "${localFolder.name}" was created but the Management API response did not include an id and uuid; blocks that reference this folder cannot be resolved.`
|
|
11480
|
+
);
|
|
11481
|
+
}
|
|
11482
|
+
groupByPath.set(localFolder.path, { id: createdGroup.id, uuid: createdGroup.uuid });
|
|
11483
|
+
created++;
|
|
11484
|
+
}
|
|
11126
11485
|
const componentDiffs = diffResult.diffs.filter((d) => d.type === "component");
|
|
11486
|
+
const resolvedComponents = /* @__PURE__ */ new Map();
|
|
11487
|
+
for (const diff of componentDiffs) {
|
|
11488
|
+
if (diff.action !== "create" && diff.action !== "update") {
|
|
11489
|
+
continue;
|
|
11490
|
+
}
|
|
11491
|
+
const localComp = local.components.find((c) => c.name === diff.name);
|
|
11492
|
+
if (localComp) {
|
|
11493
|
+
resolvedComponents.set(diff.name, resolveGroupRefs(localComp, groupByPath));
|
|
11494
|
+
}
|
|
11495
|
+
}
|
|
11127
11496
|
const componentResults = await Promise.allSettled(
|
|
11128
11497
|
componentDiffs.map(async (diff) => {
|
|
11129
|
-
const
|
|
11130
|
-
if (diff.action === "create" &&
|
|
11498
|
+
const resolvedComp = resolvedComponents.get(diff.name);
|
|
11499
|
+
if (diff.action === "create" && resolvedComp) {
|
|
11131
11500
|
await client.components.create({
|
|
11132
11501
|
path: { space_id: spaceIdNum },
|
|
11133
|
-
body: { component: toComponentCreate(
|
|
11502
|
+
body: { component: toComponentCreate(resolvedComp) },
|
|
11134
11503
|
throwOnError: true
|
|
11135
11504
|
});
|
|
11136
11505
|
return "created";
|
|
11137
11506
|
}
|
|
11138
|
-
if (diff.action === "update" &&
|
|
11507
|
+
if (diff.action === "update" && resolvedComp) {
|
|
11139
11508
|
const existing = remote.components.get(diff.name);
|
|
11140
11509
|
if (existing?.id) {
|
|
11141
11510
|
await client.components.update(existing.id, {
|
|
11142
11511
|
path: { space_id: spaceIdNum },
|
|
11143
|
-
body: { component: toComponentUpdate(
|
|
11512
|
+
body: { component: toComponentUpdate(resolvedComp) },
|
|
11144
11513
|
throwOnError: true
|
|
11145
11514
|
});
|
|
11146
11515
|
return "updated";
|
|
@@ -11202,6 +11571,7 @@ async function executePush(spaceId, local, remote, diffResult, options) {
|
|
|
11202
11571
|
}
|
|
11203
11572
|
}
|
|
11204
11573
|
if (options.delete) {
|
|
11574
|
+
const deleteErrors = [];
|
|
11205
11575
|
const staleComponents = diffResult.diffs.filter((d) => d.type === "component" && d.action === "stale");
|
|
11206
11576
|
const deleteComponentResults = await Promise.allSettled(
|
|
11207
11577
|
staleComponents.map(async (diff) => {
|
|
@@ -11222,7 +11592,7 @@ async function executePush(spaceId, local, remote, diffResult, options) {
|
|
|
11222
11592
|
deleted++;
|
|
11223
11593
|
}
|
|
11224
11594
|
} else {
|
|
11225
|
-
|
|
11595
|
+
deleteErrors.push({ action: "delete_component", reason: result.reason, message: `Failed to delete component ${staleComponents[i].name}` });
|
|
11226
11596
|
}
|
|
11227
11597
|
}
|
|
11228
11598
|
const staleDatasources = diffResult.diffs.filter((d) => d.type === "datasource" && d.action === "stale");
|
|
@@ -11245,14 +11615,49 @@ async function executePush(spaceId, local, remote, diffResult, options) {
|
|
|
11245
11615
|
deleted++;
|
|
11246
11616
|
}
|
|
11247
11617
|
} else {
|
|
11248
|
-
|
|
11618
|
+
deleteErrors.push({ action: "delete_datasource", reason: result.reason, message: `Failed to delete datasource ${staleDatasources[i].name}` });
|
|
11249
11619
|
}
|
|
11250
11620
|
}
|
|
11621
|
+
const staleFolders = diffResult.diffs.filter((d) => d.type === "folder" && d.action === "stale").sort((a, b) => b.name.split("/").length - a.name.split("/").length);
|
|
11622
|
+
for (const diff of staleFolders) {
|
|
11623
|
+
const group = groupByPath.get(diff.name);
|
|
11624
|
+
if (!group) {
|
|
11625
|
+
deleteErrors.push({
|
|
11626
|
+
action: "delete_component_folder",
|
|
11627
|
+
reason: new Error(`No component group resolved for stale folder path "${diff.name}".`),
|
|
11628
|
+
message: `Failed to delete folder ${diff.name}`
|
|
11629
|
+
});
|
|
11630
|
+
continue;
|
|
11631
|
+
}
|
|
11632
|
+
try {
|
|
11633
|
+
await client.componentFolders.delete(group.id, {
|
|
11634
|
+
path: { space_id: spaceIdNum },
|
|
11635
|
+
throwOnError: true
|
|
11636
|
+
});
|
|
11637
|
+
deleted++;
|
|
11638
|
+
} catch (error) {
|
|
11639
|
+
deleteErrors.push({ action: "delete_component_folder", reason: error, message: `Failed to delete folder ${diff.name}` });
|
|
11640
|
+
}
|
|
11641
|
+
}
|
|
11642
|
+
if (deleteErrors.length > 0) {
|
|
11643
|
+
const first = deleteErrors[0];
|
|
11644
|
+
const suffix = deleteErrors.length > 1 ? ` (and ${deleteErrors.length - 1} more delete error(s))` : "";
|
|
11645
|
+
handleAPIError(first.action, first.reason, `${first.message}${suffix}`);
|
|
11646
|
+
}
|
|
11251
11647
|
}
|
|
11252
11648
|
return { created, updated, deleted };
|
|
11253
11649
|
}
|
|
11254
11650
|
function buildChangesetEntries(diffResult, local, remote, options) {
|
|
11255
11651
|
const changes = [];
|
|
11652
|
+
const remoteFolders = [...remote.componentFolders.values()];
|
|
11653
|
+
const folderPathByUuid = buildGroupPathByUuid(remoteFolders);
|
|
11654
|
+
const remoteFolderByPath = /* @__PURE__ */ new Map();
|
|
11655
|
+
for (const folder of remoteFolders) {
|
|
11656
|
+
const segments = folderPathByUuid.get(folder.uuid);
|
|
11657
|
+
if (segments?.length) {
|
|
11658
|
+
remoteFolderByPath.set(segments.join("/"), folder);
|
|
11659
|
+
}
|
|
11660
|
+
}
|
|
11256
11661
|
for (const diff of diffResult.diffs) {
|
|
11257
11662
|
if (diff.action === "unchanged") {
|
|
11258
11663
|
continue;
|
|
@@ -11269,6 +11674,9 @@ function buildChangesetEntries(diffResult, local, remote, options) {
|
|
|
11269
11674
|
} else if (diff.type === "datasource") {
|
|
11270
11675
|
remoteSrc = remote.datasources.get(diff.name);
|
|
11271
11676
|
localSrc = local.datasources.find((d) => d.name === diff.name);
|
|
11677
|
+
} else if (diff.type === "folder") {
|
|
11678
|
+
remoteSrc = remoteFolderByPath.get(diff.name);
|
|
11679
|
+
localSrc = local.folders.find((f) => f.path === diff.name);
|
|
11272
11680
|
}
|
|
11273
11681
|
changes.push({
|
|
11274
11682
|
type: diff.type,
|
|
@@ -11552,6 +11960,25 @@ async function writeMigrationFile(options) {
|
|
|
11552
11960
|
|
|
11553
11961
|
const DEFAULT_GROUPS_FILENAME = "groups.json";
|
|
11554
11962
|
const CONSOLIDATED_COMPONENTS_FILENAME = "components.json";
|
|
11963
|
+
function sanitizeForLocalWrite(component) {
|
|
11964
|
+
const { folder, ...rest } = component;
|
|
11965
|
+
if (isRecord(rest.schema)) {
|
|
11966
|
+
const schema = {};
|
|
11967
|
+
for (const [key, field] of Object.entries(rest.schema)) {
|
|
11968
|
+
if (isRecord(field) && "component_group_whitelist" in field) {
|
|
11969
|
+
const { component_group_whitelist, restrict_components, ...fieldRest } = field;
|
|
11970
|
+
if (fieldRest.restrict_type === "groups") {
|
|
11971
|
+
delete fieldRest.restrict_type;
|
|
11972
|
+
}
|
|
11973
|
+
schema[key] = fieldRest;
|
|
11974
|
+
} else {
|
|
11975
|
+
schema[key] = field;
|
|
11976
|
+
}
|
|
11977
|
+
}
|
|
11978
|
+
rest.schema = schema;
|
|
11979
|
+
}
|
|
11980
|
+
return rest;
|
|
11981
|
+
}
|
|
11555
11982
|
async function writeLocalComponents({
|
|
11556
11983
|
space,
|
|
11557
11984
|
basePath,
|
|
@@ -11570,7 +11997,7 @@ async function writeLocalComponents({
|
|
|
11570
11997
|
}
|
|
11571
11998
|
for (const component of resolved.components) {
|
|
11572
11999
|
const filePath = join(componentsDir, `${sanitizeFilename(component.name || "")}.json`);
|
|
11573
|
-
await saveToFile(filePath, JSON.stringify(component, null, 2));
|
|
12000
|
+
await saveToFile(filePath, JSON.stringify(sanitizeForLocalWrite(component), null, 2));
|
|
11574
12001
|
}
|
|
11575
12002
|
const groupsPath = join(componentsDir, DEFAULT_GROUPS_FILENAME);
|
|
11576
12003
|
if (await fileExists(groupsPath)) {
|
|
@@ -11631,10 +12058,10 @@ schemaCommand.command("push <entry-file>").description("Push local TypeScript sc
|
|
|
11631
12058
|
handleError(toError(maybeError), verbose);
|
|
11632
12059
|
return;
|
|
11633
12060
|
}
|
|
11634
|
-
loadSpinner.succeed(`Found: ${local.components.length} components, ${local.datasources.length} datasources`);
|
|
11635
|
-
const totalLocal = local.components.length + local.datasources.length;
|
|
12061
|
+
loadSpinner.succeed(`Found: ${local.components.length} components, ${local.folders.length} folders, ${local.datasources.length} datasources`);
|
|
12062
|
+
const totalLocal = local.components.length + local.datasources.length + local.folders.length;
|
|
11636
12063
|
if (totalLocal === 0) {
|
|
11637
|
-
ui.warn("No components or datasources found in the entry file. Verify the file exports schema definitions.");
|
|
12064
|
+
ui.warn("No components, folders, or datasources found in the entry file. Verify the file exports schema definitions.");
|
|
11638
12065
|
return;
|
|
11639
12066
|
}
|
|
11640
12067
|
const remoteSpinner = ui.createSpinner(`Fetching remote state from space ${space}...`);
|
|
@@ -11720,6 +12147,26 @@ schemaCommand.command("push <entry-file>").description("Push local TypeScript sc
|
|
|
11720
12147
|
for (const comp of deletedComponents) {
|
|
11721
12148
|
ui.warn(`Component '${comp.name}' will be deleted. Stories using it will have out-of-schema content.`);
|
|
11722
12149
|
}
|
|
12150
|
+
const staleFolders = diffResult.diffs.filter((d) => d.type === "folder" && d.action === "stale");
|
|
12151
|
+
if (staleFolders.length > 0) {
|
|
12152
|
+
const uuidByPath = /* @__PURE__ */ new Map();
|
|
12153
|
+
for (const [uuid, segments] of buildGroupPathByUuid([...remote.componentFolders.values()])) {
|
|
12154
|
+
uuidByPath.set(segments.join("/"), uuid);
|
|
12155
|
+
}
|
|
12156
|
+
const staleComponentNames = new Set(
|
|
12157
|
+
diffResult.diffs.filter((d) => d.type === "component" && d.action === "stale").map((d) => d.name)
|
|
12158
|
+
);
|
|
12159
|
+
for (const folder of staleFolders) {
|
|
12160
|
+
const uuid = uuidByPath.get(folder.name);
|
|
12161
|
+
if (!uuid) {
|
|
12162
|
+
continue;
|
|
12163
|
+
}
|
|
12164
|
+
const count = [...remote.components.values()].filter((c) => c.component_group_uuid === uuid && !staleComponentNames.has(c.name)).length;
|
|
12165
|
+
if (count > 0) {
|
|
12166
|
+
ui.warn(`Folder '${folder.name}' will be deleted; ${count} component(s) inside will be ungrouped.`);
|
|
12167
|
+
}
|
|
12168
|
+
}
|
|
12169
|
+
}
|
|
11723
12170
|
}
|
|
11724
12171
|
if (diffResult.stale > 0 && !options.delete) {
|
|
11725
12172
|
ui.warn(`${diffResult.stale} stale entity(s) exist remotely but not in schema. Use --delete to remove.`);
|
|
@@ -11780,35 +12227,6 @@ schemaCommand.command("push <entry-file>").description("Push local TypeScript sc
|
|
|
11780
12227
|
}
|
|
11781
12228
|
});
|
|
11782
12229
|
|
|
11783
|
-
function buildGroupPathByUuid(folders) {
|
|
11784
|
-
const byUuid = new Map(folders.map((folder) => [folder.uuid, folder]));
|
|
11785
|
-
const cache = /* @__PURE__ */ new Map();
|
|
11786
|
-
function pathFor(uuid, visited) {
|
|
11787
|
-
if (!uuid) {
|
|
11788
|
-
return [];
|
|
11789
|
-
}
|
|
11790
|
-
const cached = cache.get(uuid);
|
|
11791
|
-
if (cached) {
|
|
11792
|
-
return cached;
|
|
11793
|
-
}
|
|
11794
|
-
const folder = byUuid.get(uuid);
|
|
11795
|
-
if (!folder) {
|
|
11796
|
-
return [];
|
|
11797
|
-
}
|
|
11798
|
-
if (visited.has(uuid)) {
|
|
11799
|
-
return [];
|
|
11800
|
-
}
|
|
11801
|
-
visited.add(uuid);
|
|
11802
|
-
const path = [...pathFor(folder.parent_uuid, visited), slugify(folder.name)];
|
|
11803
|
-
cache.set(uuid, path);
|
|
11804
|
-
return path;
|
|
11805
|
-
}
|
|
11806
|
-
for (const folder of folders) {
|
|
11807
|
-
pathFor(folder.uuid, /* @__PURE__ */ new Set());
|
|
11808
|
-
}
|
|
11809
|
-
return cache;
|
|
11810
|
-
}
|
|
11811
|
-
|
|
11812
12230
|
const FIELD_STRIP_KEYS = /* @__PURE__ */ new Set(["id", "pos"]);
|
|
11813
12231
|
function toCamelCaseIdentifier(str) {
|
|
11814
12232
|
const camel = slugify(str).replace(/^[_-]+/, "").replace(/[_-]+(.)/g, (_, char) => char.toUpperCase());
|
|
@@ -11826,6 +12244,9 @@ function componentVarName(name) {
|
|
|
11826
12244
|
function datasourceVarName(name) {
|
|
11827
12245
|
return `${toCamelCaseIdentifier(name)}Datasource`;
|
|
11828
12246
|
}
|
|
12247
|
+
function folderVarName(name) {
|
|
12248
|
+
return `${toCamelCaseIdentifier(name)}Folder`;
|
|
12249
|
+
}
|
|
11829
12250
|
function resolveVarNames(rawNames, baseVarName) {
|
|
11830
12251
|
const used = /* @__PURE__ */ new Set();
|
|
11831
12252
|
return rawNames.map((raw) => {
|
|
@@ -11885,12 +12306,37 @@ function resolveDatasources(datasources) {
|
|
|
11885
12306
|
fileName: fileNames[i]
|
|
11886
12307
|
}));
|
|
11887
12308
|
}
|
|
11888
|
-
function
|
|
11889
|
-
const
|
|
12309
|
+
function resolveFolders(folders) {
|
|
12310
|
+
const pathByUuid = buildGroupPathByUuid(folders);
|
|
12311
|
+
const ordered = [...folders].sort((a, b) => (pathByUuid.get(a.uuid)?.length ?? 0) - (pathByUuid.get(b.uuid)?.length ?? 0));
|
|
12312
|
+
const varNames = resolveVarNames(ordered.map((f) => f.name), folderVarName);
|
|
12313
|
+
return ordered.map((folder, i) => ({
|
|
12314
|
+
folder,
|
|
12315
|
+
varName: varNames[i],
|
|
12316
|
+
segments: pathByUuid.get(folder.uuid) ?? []
|
|
12317
|
+
}));
|
|
12318
|
+
}
|
|
12319
|
+
function resolveGroupWhitelistRefs(whitelist, folderVarByUuid) {
|
|
12320
|
+
if (!folderVarByUuid || !Array.isArray(whitelist) || whitelist.length === 0) {
|
|
12321
|
+
return void 0;
|
|
12322
|
+
}
|
|
12323
|
+
const vars = whitelist.map((uuid) => typeof uuid === "string" ? folderVarByUuid.get(uuid) : void 0);
|
|
12324
|
+
if (!vars.every((v) => typeof v === "string")) {
|
|
12325
|
+
return void 0;
|
|
12326
|
+
}
|
|
12327
|
+
return vars.map((v) => new RawCode(v));
|
|
12328
|
+
}
|
|
12329
|
+
function toDslField(field, folderVarByUuid) {
|
|
12330
|
+
const { component_whitelist, component_group_whitelist, datasource_slug, restrict_components, restrict_type, ...rest } = field;
|
|
11890
12331
|
const out = { ...rest };
|
|
11891
|
-
|
|
12332
|
+
const groupRefs = resolveGroupWhitelistRefs(component_group_whitelist, folderVarByUuid);
|
|
12333
|
+
const hasBlockNames = Array.isArray(component_whitelist) && component_whitelist.length > 0;
|
|
12334
|
+
if (hasBlockNames) {
|
|
11892
12335
|
out.allow = component_whitelist;
|
|
11893
|
-
} else {
|
|
12336
|
+
} else if (groupRefs) {
|
|
12337
|
+
out.allow = groupRefs;
|
|
12338
|
+
} else if (component_group_whitelist !== void 0) {
|
|
12339
|
+
out.component_group_whitelist = component_group_whitelist;
|
|
11894
12340
|
if (restrict_components !== void 0) {
|
|
11895
12341
|
out.restrict_components = restrict_components;
|
|
11896
12342
|
}
|
|
@@ -11903,10 +12349,33 @@ function toDslField(field) {
|
|
|
11903
12349
|
}
|
|
11904
12350
|
return out;
|
|
11905
12351
|
}
|
|
11906
|
-
function
|
|
11907
|
-
const
|
|
12352
|
+
function omitEmptyArrays(obj) {
|
|
12353
|
+
const out = {};
|
|
12354
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
12355
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
12356
|
+
continue;
|
|
12357
|
+
}
|
|
12358
|
+
out[key] = value;
|
|
12359
|
+
}
|
|
12360
|
+
return out;
|
|
12361
|
+
}
|
|
12362
|
+
function generateFieldCode(fieldName, fieldData, depth, folderVarByUuid) {
|
|
12363
|
+
const clean = omitEmptyArrays(toDslField(stripKeys(fieldData, FIELD_STRIP_KEYS), folderVarByUuid));
|
|
11908
12364
|
return `defineField(${quoteString(fieldName)}, ${formatValue(clean, depth)})`;
|
|
11909
12365
|
}
|
|
12366
|
+
function collectWhitelistFolderVars(schema, folderVarByUuid) {
|
|
12367
|
+
const vars = /* @__PURE__ */ new Set();
|
|
12368
|
+
for (const field of Object.values(schema)) {
|
|
12369
|
+
if (!isRecord(field)) {
|
|
12370
|
+
continue;
|
|
12371
|
+
}
|
|
12372
|
+
const refs = resolveGroupWhitelistRefs(field.component_group_whitelist, folderVarByUuid);
|
|
12373
|
+
if (refs) {
|
|
12374
|
+
refs.forEach((ref) => vars.add(ref.code));
|
|
12375
|
+
}
|
|
12376
|
+
}
|
|
12377
|
+
return [...vars].sort();
|
|
12378
|
+
}
|
|
11910
12379
|
function sortSchemaByPos(schema) {
|
|
11911
12380
|
return Object.entries(schema).filter(([key]) => key !== "_uid" && key !== "component").sort(([, a], [, b]) => {
|
|
11912
12381
|
const posA = typeof a.pos === "number" ? a.pos : Infinity;
|
|
@@ -11914,16 +12383,42 @@ function sortSchemaByPos(schema) {
|
|
|
11914
12383
|
return posA - posB;
|
|
11915
12384
|
});
|
|
11916
12385
|
}
|
|
11917
|
-
function
|
|
12386
|
+
function generateFoldersFile(resolved) {
|
|
12387
|
+
const varByUuid = new Map(resolved.map((r) => [r.folder.uuid, r.varName]));
|
|
12388
|
+
const lines = ["import { defineFolder } from '@storyblok/schema';", ""];
|
|
12389
|
+
for (const { folder, varName } of resolved) {
|
|
12390
|
+
const parentVar = folder.parent_uuid ? varByUuid.get(folder.parent_uuid) : void 0;
|
|
12391
|
+
lines.push(`export const ${varName} = defineFolder({`);
|
|
12392
|
+
lines.push(`${INDENT}name: ${quoteString(folder.name)},`);
|
|
12393
|
+
if (parentVar) {
|
|
12394
|
+
lines.push(`${INDENT}parent: ${parentVar},`);
|
|
12395
|
+
}
|
|
12396
|
+
lines.push("});");
|
|
12397
|
+
lines.push("");
|
|
12398
|
+
}
|
|
12399
|
+
return lines.join("\n");
|
|
12400
|
+
}
|
|
12401
|
+
function generateComponentFile(component, varName, folderRef, folderVarByUuid) {
|
|
11918
12402
|
const lines = [];
|
|
11919
12403
|
lines.push("import {");
|
|
11920
12404
|
lines.push(" defineBlock,");
|
|
11921
12405
|
lines.push(" defineField,");
|
|
11922
12406
|
lines.push("} from '@storyblok/schema';");
|
|
11923
12407
|
lines.push("");
|
|
12408
|
+
const blockDepth = folderRef?.segments.length ?? 0;
|
|
12409
|
+
const schema = isRecord(component.schema) ? component.schema : {};
|
|
12410
|
+
const folderVars = [.../* @__PURE__ */ new Set([
|
|
12411
|
+
...folderRef ? [folderRef.varName] : [],
|
|
12412
|
+
...collectWhitelistFolderVars(schema, folderVarByUuid)
|
|
12413
|
+
])].sort();
|
|
12414
|
+
if (folderVars.length > 0) {
|
|
12415
|
+
const rel = "../".repeat(blockDepth + 1);
|
|
12416
|
+
lines.push(`import { ${folderVars.join(", ")} } from '${rel}folders';`);
|
|
12417
|
+
lines.push("");
|
|
12418
|
+
}
|
|
11924
12419
|
const resolvedVarName = varName ?? componentVarName(component.name);
|
|
11925
12420
|
lines.push(`export const ${resolvedVarName} = defineBlock({`);
|
|
11926
|
-
const clean = stripKeys(component, COMPONENT_STRIP_KEYS);
|
|
12421
|
+
const clean = omitEmptyArrays(stripKeys(component, COMPONENT_STRIP_KEYS));
|
|
11927
12422
|
delete clean.component_group_uuid;
|
|
11928
12423
|
const orderedKeys = [];
|
|
11929
12424
|
if (clean.name !== void 0) {
|
|
@@ -11938,22 +12433,29 @@ function generateComponentFile(component, varName) {
|
|
|
11938
12433
|
if (clean.is_nestable !== void 0) {
|
|
11939
12434
|
orderedKeys.push("is_nestable");
|
|
11940
12435
|
}
|
|
12436
|
+
const prefixKeyCount = orderedKeys.length;
|
|
11941
12437
|
const handled = /* @__PURE__ */ new Set(["name", "display_name", "is_root", "is_nestable", "schema"]);
|
|
11942
12438
|
for (const key of Object.keys(clean).sort()) {
|
|
11943
12439
|
if (!handled.has(key)) {
|
|
11944
12440
|
orderedKeys.push(key);
|
|
11945
12441
|
}
|
|
11946
12442
|
}
|
|
11947
|
-
|
|
12443
|
+
orderedKeys.forEach((key, i) => {
|
|
11948
12444
|
lines.push(`${INDENT}${key}: ${formatValue(clean[key], 1)},`);
|
|
12445
|
+
if (folderRef && i === prefixKeyCount - 1) {
|
|
12446
|
+
lines.push(`${INDENT}folder: ${folderRef.varName},`);
|
|
12447
|
+
}
|
|
12448
|
+
});
|
|
12449
|
+
if (folderRef && prefixKeyCount === 0) {
|
|
12450
|
+
lines.push(`${INDENT}folder: ${folderRef.varName},`);
|
|
11949
12451
|
}
|
|
11950
12452
|
if (clean.schema && typeof clean.schema === "object") {
|
|
11951
|
-
const
|
|
11952
|
-
const sortedFields = sortSchemaByPos(
|
|
12453
|
+
const schema2 = clean.schema;
|
|
12454
|
+
const sortedFields = sortSchemaByPos(schema2);
|
|
11953
12455
|
if (sortedFields.length > 0) {
|
|
11954
12456
|
lines.push(`${INDENT}fields: [`);
|
|
11955
12457
|
for (const [fieldName, fieldData] of sortedFields) {
|
|
11956
|
-
const fieldCode = generateFieldCode(fieldName, fieldData, 2);
|
|
12458
|
+
const fieldCode = generateFieldCode(fieldName, fieldData, 2, folderVarByUuid);
|
|
11957
12459
|
lines.push(`${INDENT}${INDENT}${fieldCode},`);
|
|
11958
12460
|
}
|
|
11959
12461
|
lines.push(`${INDENT}],`);
|
|
@@ -11971,7 +12473,7 @@ function generateDatasourceFile(datasource, varName) {
|
|
|
11971
12473
|
lines.push("");
|
|
11972
12474
|
const resolvedVarName = varName ?? datasourceVarName(datasource.name);
|
|
11973
12475
|
lines.push(`export const ${resolvedVarName} = defineDatasource({`);
|
|
11974
|
-
const clean = stripKeys(datasource, DATASOURCE_STRIP_KEYS);
|
|
12476
|
+
const clean = omitEmptyArrays(stripKeys(datasource, DATASOURCE_STRIP_KEYS));
|
|
11975
12477
|
if (clean.name !== void 0) {
|
|
11976
12478
|
lines.push(`${INDENT}name: ${formatValue(clean.name, 1)},`);
|
|
11977
12479
|
}
|
|
@@ -11988,7 +12490,7 @@ function generateDatasourceFile(datasource, varName) {
|
|
|
11988
12490
|
lines.push("");
|
|
11989
12491
|
return lines.join("\n");
|
|
11990
12492
|
}
|
|
11991
|
-
function generateSchemaFile(components, datasources) {
|
|
12493
|
+
function generateSchemaFile(components, datasources, folders = []) {
|
|
11992
12494
|
const lines = [];
|
|
11993
12495
|
lines.push("import { defineSchema } from '@storyblok/schema';");
|
|
11994
12496
|
lines.push("import type { Schema as InferSchema, Story as InferStory } from '@storyblok/schema';");
|
|
@@ -12001,6 +12503,9 @@ function generateSchemaFile(components, datasources) {
|
|
|
12001
12503
|
for (const { varName, fileName } of datasources) {
|
|
12002
12504
|
lines.push(`import { ${varName} } from './datasources/${fileName}';`);
|
|
12003
12505
|
}
|
|
12506
|
+
if (folders.length > 0) {
|
|
12507
|
+
lines.push(`import { ${folders.map((f) => f.varName).join(", ")} } from './folders';`);
|
|
12508
|
+
}
|
|
12004
12509
|
lines.push("");
|
|
12005
12510
|
lines.push("export const schema = defineSchema({");
|
|
12006
12511
|
if (components.length > 0) {
|
|
@@ -12017,6 +12522,13 @@ function generateSchemaFile(components, datasources) {
|
|
|
12017
12522
|
}
|
|
12018
12523
|
lines.push(" },");
|
|
12019
12524
|
}
|
|
12525
|
+
if (folders.length > 0) {
|
|
12526
|
+
lines.push(" folders: {");
|
|
12527
|
+
for (const { varName } of folders) {
|
|
12528
|
+
lines.push(` ${varName},`);
|
|
12529
|
+
}
|
|
12530
|
+
lines.push(" },");
|
|
12531
|
+
}
|
|
12020
12532
|
lines.push("});");
|
|
12021
12533
|
lines.push("");
|
|
12022
12534
|
lines.push("export type Schema = InferSchema<typeof schema>;");
|
|
@@ -12040,9 +12552,14 @@ async function writeSchemaFiles(targetPath, components, componentFolders, dataso
|
|
|
12040
12552
|
);
|
|
12041
12553
|
const resolvedComponents = resolveComponents(components, componentSegments);
|
|
12042
12554
|
const resolvedDatasources = resolveDatasources(datasources);
|
|
12555
|
+
const resolvedFolders = resolveFolders(componentFolders);
|
|
12556
|
+
const folderByUuid = new Map(resolvedFolders.map((r) => [r.folder.uuid, r]));
|
|
12557
|
+
const folderVarByUuid = new Map([...folderByUuid].map(([uuid, r]) => [uuid, r.varName]));
|
|
12043
12558
|
for (const { component, varName, fileName, segments } of resolvedComponents) {
|
|
12044
12559
|
const filePath = join(targetPath, "blocks", ...segments, `${fileName}.ts`);
|
|
12045
|
-
|
|
12560
|
+
const folder = component.component_group_uuid ? folderByUuid.get(component.component_group_uuid) : void 0;
|
|
12561
|
+
const folderRef = folder && { varName: folder.varName, segments };
|
|
12562
|
+
await writeFileWithDirs(filePath, generateComponentFile(component, varName, folderRef, folderVarByUuid));
|
|
12046
12563
|
writtenFiles.push(filePath);
|
|
12047
12564
|
}
|
|
12048
12565
|
for (const { datasource, varName, fileName } of resolvedDatasources) {
|
|
@@ -12050,8 +12567,13 @@ async function writeSchemaFiles(targetPath, components, componentFolders, dataso
|
|
|
12050
12567
|
await writeFileWithDirs(filePath, generateDatasourceFile(datasource, varName));
|
|
12051
12568
|
writtenFiles.push(filePath);
|
|
12052
12569
|
}
|
|
12570
|
+
if (resolvedFolders.length > 0) {
|
|
12571
|
+
const foldersPath = join(targetPath, "folders.ts");
|
|
12572
|
+
await writeFileWithDirs(foldersPath, generateFoldersFile(resolvedFolders));
|
|
12573
|
+
writtenFiles.push(foldersPath);
|
|
12574
|
+
}
|
|
12053
12575
|
const schemaPath = join(targetPath, "schema.ts");
|
|
12054
|
-
await writeFileWithDirs(schemaPath, generateSchemaFile(resolvedComponents, resolvedDatasources));
|
|
12576
|
+
await writeFileWithDirs(schemaPath, generateSchemaFile(resolvedComponents, resolvedDatasources, resolvedFolders));
|
|
12055
12577
|
writtenFiles.push(schemaPath);
|
|
12056
12578
|
return writtenFiles;
|
|
12057
12579
|
}
|
|
@@ -12181,6 +12703,7 @@ function rollbackAction(original) {
|
|
|
12181
12703
|
}
|
|
12182
12704
|
function formatRollbackOutput(changes) {
|
|
12183
12705
|
const byType = {
|
|
12706
|
+
folder: [],
|
|
12184
12707
|
component: [],
|
|
12185
12708
|
datasource: []
|
|
12186
12709
|
};
|
|
@@ -12194,6 +12717,7 @@ function formatRollbackOutput(changes) {
|
|
|
12194
12717
|
};
|
|
12195
12718
|
const lines = [];
|
|
12196
12719
|
const sections = [
|
|
12720
|
+
["Folders", byType.folder],
|
|
12197
12721
|
["Components", byType.component],
|
|
12198
12722
|
["Datasources", byType.datasource]
|
|
12199
12723
|
];
|
|
@@ -12213,9 +12737,11 @@ function formatRollbackOutput(changes) {
|
|
|
12213
12737
|
if (entry.type === "component") {
|
|
12214
12738
|
fromStr = serializeComponent(applyDefaults(entry.after, COMPONENT_DEFAULTS));
|
|
12215
12739
|
toStr = serializeComponent(applyDefaults(entry.before, COMPONENT_DEFAULTS));
|
|
12216
|
-
} else {
|
|
12740
|
+
} else if (entry.type === "datasource") {
|
|
12217
12741
|
fromStr = serializeDatasource(entry.after);
|
|
12218
12742
|
toStr = serializeDatasource(entry.before);
|
|
12743
|
+
} else {
|
|
12744
|
+
continue;
|
|
12219
12745
|
}
|
|
12220
12746
|
if (fromStr !== toStr) {
|
|
12221
12747
|
const patch = createTwoFilesPatch(
|
|
@@ -12240,6 +12766,46 @@ function formatRollbackOutput(changes) {
|
|
|
12240
12766
|
}
|
|
12241
12767
|
return lines.join("\n").trimEnd();
|
|
12242
12768
|
}
|
|
12769
|
+
function buildGroupRefByPath(remote) {
|
|
12770
|
+
const folders = [...remote.componentFolders.values()];
|
|
12771
|
+
const pathByUuid = buildGroupPathByUuid(folders);
|
|
12772
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
12773
|
+
for (const folder of folders) {
|
|
12774
|
+
const segments = pathByUuid.get(folder.uuid);
|
|
12775
|
+
if (segments?.length) {
|
|
12776
|
+
byPath.set(segments.join("/"), { id: folder.id, uuid: folder.uuid });
|
|
12777
|
+
}
|
|
12778
|
+
}
|
|
12779
|
+
return byPath;
|
|
12780
|
+
}
|
|
12781
|
+
function parentPathOf(path) {
|
|
12782
|
+
const segments = path.split("/");
|
|
12783
|
+
return segments.length > 1 ? segments.slice(0, -1).join("/") : null;
|
|
12784
|
+
}
|
|
12785
|
+
function remapGroupUuids(payload, uuidMap) {
|
|
12786
|
+
if (uuidMap.size === 0) {
|
|
12787
|
+
return payload;
|
|
12788
|
+
}
|
|
12789
|
+
const result = { ...payload };
|
|
12790
|
+
if (typeof result.component_group_uuid === "string") {
|
|
12791
|
+
result.component_group_uuid = uuidMap.get(result.component_group_uuid) ?? result.component_group_uuid;
|
|
12792
|
+
}
|
|
12793
|
+
if (isRecord(result.schema)) {
|
|
12794
|
+
const schema = {};
|
|
12795
|
+
for (const [key, field] of Object.entries(result.schema)) {
|
|
12796
|
+
if (isRecord(field) && Array.isArray(field.component_group_whitelist)) {
|
|
12797
|
+
schema[key] = {
|
|
12798
|
+
...field,
|
|
12799
|
+
component_group_whitelist: field.component_group_whitelist.map((uuid) => typeof uuid === "string" ? uuidMap.get(uuid) ?? uuid : uuid)
|
|
12800
|
+
};
|
|
12801
|
+
} else {
|
|
12802
|
+
schema[key] = field;
|
|
12803
|
+
}
|
|
12804
|
+
}
|
|
12805
|
+
result.schema = schema;
|
|
12806
|
+
}
|
|
12807
|
+
return result;
|
|
12808
|
+
}
|
|
12243
12809
|
async function executeRollback(spaceId, ops, remote) {
|
|
12244
12810
|
const client = getMapiClient();
|
|
12245
12811
|
const spaceIdNum = Number(spaceId);
|
|
@@ -12248,9 +12814,43 @@ async function executeRollback(spaceId, ops, remote) {
|
|
|
12248
12814
|
let deleted = 0;
|
|
12249
12815
|
const componentOps = ops.filter((op) => op.type === "component");
|
|
12250
12816
|
const datasourceOps = ops.filter((op) => op.type === "datasource");
|
|
12817
|
+
const folderOps = ops.filter((op) => op.type === "folder");
|
|
12818
|
+
const groupRefByPath = buildGroupRefByPath(remote);
|
|
12819
|
+
const groupUuidMap = /* @__PURE__ */ new Map();
|
|
12820
|
+
const folderCreates = folderOps.filter((o) => o.action === "create").sort((a, b) => a.name.split("/").length - b.name.split("/").length);
|
|
12821
|
+
for (const op of folderCreates) {
|
|
12822
|
+
const existing = groupRefByPath.get(op.name);
|
|
12823
|
+
const oldUuid = typeof op.payload.uuid === "string" ? op.payload.uuid : void 0;
|
|
12824
|
+
if (existing) {
|
|
12825
|
+
if (oldUuid) {
|
|
12826
|
+
groupUuidMap.set(oldUuid, existing.uuid);
|
|
12827
|
+
}
|
|
12828
|
+
continue;
|
|
12829
|
+
}
|
|
12830
|
+
const parentPath = parentPathOf(op.name);
|
|
12831
|
+
const parentId = parentPath ? groupRefByPath.get(parentPath)?.id ?? null : null;
|
|
12832
|
+
const name = typeof op.payload.name === "string" ? op.payload.name : op.name.split("/").pop() ?? op.name;
|
|
12833
|
+
try {
|
|
12834
|
+
const res = await client.componentFolders.create({
|
|
12835
|
+
path: { space_id: spaceIdNum },
|
|
12836
|
+
body: { component_group: { name, parent_id: parentId } },
|
|
12837
|
+
throwOnError: true
|
|
12838
|
+
});
|
|
12839
|
+
const group = res.data?.component_group;
|
|
12840
|
+
if (group?.id != null && group.uuid) {
|
|
12841
|
+
groupRefByPath.set(op.name, { id: group.id, uuid: group.uuid });
|
|
12842
|
+
if (oldUuid) {
|
|
12843
|
+
groupUuidMap.set(oldUuid, group.uuid);
|
|
12844
|
+
}
|
|
12845
|
+
created++;
|
|
12846
|
+
}
|
|
12847
|
+
} catch (error) {
|
|
12848
|
+
handleAPIError("push_component_folder", error, `Failed to recreate folder ${op.name}`);
|
|
12849
|
+
}
|
|
12850
|
+
}
|
|
12251
12851
|
for (const op of componentOps.filter((o) => o.action !== "delete")) {
|
|
12252
12852
|
if (op.action === "create") {
|
|
12253
|
-
const payload = toComponentCreate(stripApiFields(op.payload));
|
|
12853
|
+
const payload = toComponentCreate(remapGroupUuids(stripApiFields(op.payload), groupUuidMap));
|
|
12254
12854
|
try {
|
|
12255
12855
|
await client.components.create({
|
|
12256
12856
|
path: { space_id: spaceIdNum },
|
|
@@ -12264,7 +12864,7 @@ async function executeRollback(spaceId, ops, remote) {
|
|
|
12264
12864
|
} else if (op.action === "update") {
|
|
12265
12865
|
const existing = remote.components.get(op.name);
|
|
12266
12866
|
if (existing?.id) {
|
|
12267
|
-
const payload = toComponentUpdate(stripApiFields(op.payload));
|
|
12867
|
+
const payload = toComponentUpdate(remapGroupUuids(stripApiFields(op.payload), groupUuidMap));
|
|
12268
12868
|
try {
|
|
12269
12869
|
await client.components.update(existing.id, {
|
|
12270
12870
|
path: { space_id: spaceIdNum },
|
|
@@ -12336,6 +12936,22 @@ async function executeRollback(spaceId, ops, remote) {
|
|
|
12336
12936
|
}
|
|
12337
12937
|
}
|
|
12338
12938
|
}
|
|
12939
|
+
const folderDeletes = folderOps.filter((o) => o.action === "delete").sort((a, b) => b.name.split("/").length - a.name.split("/").length);
|
|
12940
|
+
for (const op of folderDeletes) {
|
|
12941
|
+
const ref = groupRefByPath.get(op.name);
|
|
12942
|
+
if (!ref) {
|
|
12943
|
+
continue;
|
|
12944
|
+
}
|
|
12945
|
+
try {
|
|
12946
|
+
await client.componentFolders.delete(ref.id, {
|
|
12947
|
+
path: { space_id: spaceIdNum },
|
|
12948
|
+
throwOnError: true
|
|
12949
|
+
});
|
|
12950
|
+
deleted++;
|
|
12951
|
+
} catch (error) {
|
|
12952
|
+
handleAPIError("delete_component_folder", error, `Failed to delete folder ${op.name}`);
|
|
12953
|
+
}
|
|
12954
|
+
}
|
|
12339
12955
|
return { created, updated, deleted };
|
|
12340
12956
|
}
|
|
12341
12957
|
|