storyblok 4.17.2 → 4.17.4
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 +253 -201
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { readPackageUp } from 'read-package-up';
|
|
|
10
10
|
import { Command } from 'commander';
|
|
11
11
|
import { MultiBar, Presets } from 'cli-progress';
|
|
12
12
|
import { Spinner } from '@topcli/spinner';
|
|
13
|
-
import fs, { mkdir, writeFile,
|
|
13
|
+
import fs, { mkdir, writeFile, readdir, readFile as readFile$1, appendFile, access, constants, unlink } from 'node:fs/promises';
|
|
14
14
|
import filenamify from 'filenamify';
|
|
15
15
|
import { createManagementApiClient, normalizeAssetUrl } from '@storyblok/management-api-client';
|
|
16
16
|
import { select, password, input, confirm } from '@inquirer/prompts';
|
|
@@ -148,6 +148,18 @@ const directories = {
|
|
|
148
148
|
stories: "stories"
|
|
149
149
|
};
|
|
150
150
|
|
|
151
|
+
const chunk = (items, size) => {
|
|
152
|
+
const all = Array.from(items);
|
|
153
|
+
if (all.length === 0) {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
const chunks = [];
|
|
157
|
+
for (let i = 0; i < all.length; i += size) {
|
|
158
|
+
chunks.push(all.slice(i, i + size));
|
|
159
|
+
}
|
|
160
|
+
return chunks;
|
|
161
|
+
};
|
|
162
|
+
|
|
151
163
|
function isPlainObject(value) {
|
|
152
164
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
153
165
|
}
|
|
@@ -1382,14 +1394,16 @@ async function fileExists(path) {
|
|
|
1382
1394
|
return false;
|
|
1383
1395
|
}
|
|
1384
1396
|
}
|
|
1385
|
-
function
|
|
1386
|
-
return
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1397
|
+
function filterJsonBySuffix(files, suffix) {
|
|
1398
|
+
return files.filter((file) => {
|
|
1399
|
+
if (!file.endsWith(".json")) {
|
|
1400
|
+
return false;
|
|
1401
|
+
}
|
|
1402
|
+
if (suffix) {
|
|
1403
|
+
return file.endsWith(`.${suffix}.json`);
|
|
1404
|
+
}
|
|
1405
|
+
return true;
|
|
1406
|
+
});
|
|
1393
1407
|
}
|
|
1394
1408
|
|
|
1395
1409
|
const REPORT_STATUS = {
|
|
@@ -2274,6 +2288,75 @@ program$b.command(commands.USER).description("Get the current user").action(asyn
|
|
|
2274
2288
|
const program$a = getProgram();
|
|
2275
2289
|
const componentsCommand = program$a.command(commands.COMPONENTS).alias("comp").description(`Manage your space's block schema`);
|
|
2276
2290
|
|
|
2291
|
+
function isComponent(item) {
|
|
2292
|
+
return "schema" in item;
|
|
2293
|
+
}
|
|
2294
|
+
function isPreset(item) {
|
|
2295
|
+
return "component_id" in item && "preset" in item;
|
|
2296
|
+
}
|
|
2297
|
+
function isInternalTag(item) {
|
|
2298
|
+
return "object_type" in item;
|
|
2299
|
+
}
|
|
2300
|
+
function isComponentGroup(item) {
|
|
2301
|
+
return "uuid" in item && !("schema" in item);
|
|
2302
|
+
}
|
|
2303
|
+
async function loadComponents(directoryPath, options) {
|
|
2304
|
+
const files = await readDirectory(directoryPath);
|
|
2305
|
+
const { suffix } = options ?? {};
|
|
2306
|
+
const componentMap = /* @__PURE__ */ new Map();
|
|
2307
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
2308
|
+
const tagMap = /* @__PURE__ */ new Map();
|
|
2309
|
+
const presets = [];
|
|
2310
|
+
const duplicates = [];
|
|
2311
|
+
for (const file of filterJsonBySuffix(files, suffix)) {
|
|
2312
|
+
const { data, error } = await readJsonFile(join(directoryPath, file));
|
|
2313
|
+
if (error) {
|
|
2314
|
+
handleFileSystemError("read", error);
|
|
2315
|
+
continue;
|
|
2316
|
+
}
|
|
2317
|
+
for (const item of data) {
|
|
2318
|
+
if (isPreset(item)) {
|
|
2319
|
+
presets.push(item);
|
|
2320
|
+
} else if (isInternalTag(item)) {
|
|
2321
|
+
if (!item.id) {
|
|
2322
|
+
throw new Error('Internal tag is missing "id"!');
|
|
2323
|
+
}
|
|
2324
|
+
tagMap.set(item.id, item);
|
|
2325
|
+
} else if (isComponent(item)) {
|
|
2326
|
+
const existing = componentMap.get(item.name);
|
|
2327
|
+
if (existing) {
|
|
2328
|
+
duplicates.push(`Component "${item.name}" found in both "${existing.file}" and "${file}"`);
|
|
2329
|
+
}
|
|
2330
|
+
componentMap.set(item.name, { component: item, file });
|
|
2331
|
+
} else if (isComponentGroup(item)) {
|
|
2332
|
+
groupMap.set(item.id, item);
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
if (duplicates.length) {
|
|
2337
|
+
throw new FileSystemError(
|
|
2338
|
+
"invalid_argument",
|
|
2339
|
+
"read",
|
|
2340
|
+
new Error("Duplicate components detected"),
|
|
2341
|
+
`Duplicate components found in ${directoryPath}:
|
|
2342
|
+
|
|
2343
|
+
${duplicates.join("\n")}
|
|
2344
|
+
|
|
2345
|
+
This can happen when multiple environment snapshots (e.g. components.json and components.dev.json) or mixed formats coexist in the same directory.
|
|
2346
|
+
|
|
2347
|
+
To fix this, either:
|
|
2348
|
+
- Use --suffix <env> to target a specific environment (e.g. --suffix dev)
|
|
2349
|
+
- Clean up the directory and pull components again in the format you intend`
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
return {
|
|
2353
|
+
components: [...componentMap.values()].map(({ component }) => component),
|
|
2354
|
+
groups: [...groupMap.values()],
|
|
2355
|
+
presets,
|
|
2356
|
+
internalTags: [...tagMap.values()]
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2277
2360
|
const DEFAULT_COMPONENTS_FILENAME = "components";
|
|
2278
2361
|
const DEFAULT_GROUPS_FILENAME = "groups";
|
|
2279
2362
|
const DEFAULT_PRESETS_FILENAME = "presets";
|
|
@@ -2596,11 +2679,15 @@ const upsertComponentInternalTag = async (space, tag, existingId) => {
|
|
|
2596
2679
|
}
|
|
2597
2680
|
};
|
|
2598
2681
|
const readComponentsFiles = async (options) => {
|
|
2599
|
-
const { from, path,
|
|
2682
|
+
const { from, path, suffix } = options;
|
|
2600
2683
|
const resolvedPath = resolvePath(path, `components/${from}`);
|
|
2684
|
+
let result;
|
|
2601
2685
|
try {
|
|
2602
|
-
await
|
|
2686
|
+
result = await loadComponents(resolvedPath, { suffix });
|
|
2603
2687
|
} catch (error) {
|
|
2688
|
+
if (error instanceof FileSystemError && error.code !== "ENOENT") {
|
|
2689
|
+
throw error;
|
|
2690
|
+
}
|
|
2604
2691
|
const message = `No local components found for space ${chalk.bold(from)}. To push components, you need to pull them first:
|
|
2605
2692
|
|
|
2606
2693
|
1. Pull the components from your source space:
|
|
@@ -2615,89 +2702,16 @@ const readComponentsFiles = async (options) => {
|
|
|
2615
2702
|
message
|
|
2616
2703
|
);
|
|
2617
2704
|
}
|
|
2618
|
-
if (
|
|
2619
|
-
return await readSeparateFiles$1(resolvedPath, suffix);
|
|
2620
|
-
}
|
|
2621
|
-
return await readConsolidatedFiles$1(resolvedPath, suffix);
|
|
2622
|
-
};
|
|
2623
|
-
async function readSeparateFiles$1(resolvedPath, suffix) {
|
|
2624
|
-
const files = await readdir(resolvedPath);
|
|
2625
|
-
const components = [];
|
|
2626
|
-
const presets = [];
|
|
2627
|
-
let groups = [];
|
|
2628
|
-
let internalTags = [];
|
|
2629
|
-
const filteredFiles = files.filter((file) => {
|
|
2630
|
-
if (suffix) {
|
|
2631
|
-
return file.endsWith(`.${suffix}.json`);
|
|
2632
|
-
} else {
|
|
2633
|
-
return !/\.\w+\.json$/.test(file) || file.endsWith(".presets.json");
|
|
2634
|
-
}
|
|
2635
|
-
});
|
|
2636
|
-
for (const file of filteredFiles) {
|
|
2637
|
-
const filePath = join(resolvedPath, file);
|
|
2638
|
-
if (file === `${DEFAULT_GROUPS_FILENAME}.json` || file === `${DEFAULT_GROUPS_FILENAME}.${suffix}.json`) {
|
|
2639
|
-
const result = await readJsonFile(filePath);
|
|
2640
|
-
if (result.error) {
|
|
2641
|
-
handleFileSystemError("read", result.error);
|
|
2642
|
-
continue;
|
|
2643
|
-
}
|
|
2644
|
-
groups = result.data;
|
|
2645
|
-
} else if (file === `${DEFAULT_TAGS_FILENAME}.json` || file === `${DEFAULT_TAGS_FILENAME}.${suffix}.json`) {
|
|
2646
|
-
const result = await readJsonFile(filePath);
|
|
2647
|
-
if (result.error) {
|
|
2648
|
-
handleFileSystemError("read", result.error);
|
|
2649
|
-
continue;
|
|
2650
|
-
}
|
|
2651
|
-
internalTags = result.data;
|
|
2652
|
-
} else if (file.endsWith(`.${DEFAULT_PRESETS_FILENAME}.json`) || file.endsWith(`.${DEFAULT_PRESETS_FILENAME}.${suffix}.json`)) {
|
|
2653
|
-
const result = await readJsonFile(filePath);
|
|
2654
|
-
if (result.error) {
|
|
2655
|
-
handleFileSystemError("read", result.error);
|
|
2656
|
-
continue;
|
|
2657
|
-
}
|
|
2658
|
-
presets.push(...result.data);
|
|
2659
|
-
} else if (file.endsWith(".json") || file.endsWith(`${suffix}.json`)) {
|
|
2660
|
-
if (file === `${DEFAULT_COMPONENTS_FILENAME}.json` || file === `${DEFAULT_COMPONENTS_FILENAME}.${suffix}.json`) {
|
|
2661
|
-
continue;
|
|
2662
|
-
}
|
|
2663
|
-
const result = await readJsonFile(filePath);
|
|
2664
|
-
if (result.error) {
|
|
2665
|
-
handleFileSystemError("read", result.error);
|
|
2666
|
-
continue;
|
|
2667
|
-
}
|
|
2668
|
-
components.push(...result.data);
|
|
2669
|
-
}
|
|
2670
|
-
}
|
|
2671
|
-
return {
|
|
2672
|
-
components,
|
|
2673
|
-
groups,
|
|
2674
|
-
presets,
|
|
2675
|
-
internalTags
|
|
2676
|
-
};
|
|
2677
|
-
}
|
|
2678
|
-
async function readConsolidatedFiles$1(resolvedPath, suffix) {
|
|
2679
|
-
const componentsPath = join(resolvedPath, suffix ? `${DEFAULT_COMPONENTS_FILENAME}.${suffix}.json` : `${DEFAULT_COMPONENTS_FILENAME}.json`);
|
|
2680
|
-
const componentsResult = await readJsonFile(componentsPath);
|
|
2681
|
-
if (componentsResult.error || !componentsResult.data.length) {
|
|
2705
|
+
if (!result.components.length) {
|
|
2682
2706
|
throw new FileSystemError(
|
|
2683
2707
|
"file_not_found",
|
|
2684
2708
|
"read",
|
|
2685
|
-
|
|
2686
|
-
`No components found in ${
|
|
2709
|
+
new Error("No component data found"),
|
|
2710
|
+
`No components found in ${resolvedPath}. Please make sure you have pulled the components first.`
|
|
2687
2711
|
);
|
|
2688
2712
|
}
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
readJsonFile(join(resolvedPath, suffix ? `${DEFAULT_PRESETS_FILENAME}.${suffix}.json` : `${DEFAULT_PRESETS_FILENAME}.json`)),
|
|
2692
|
-
readJsonFile(join(resolvedPath, suffix ? `${DEFAULT_TAGS_FILENAME}.${suffix}.json` : `${DEFAULT_TAGS_FILENAME}.json`))
|
|
2693
|
-
]);
|
|
2694
|
-
return {
|
|
2695
|
-
components: componentsResult.data,
|
|
2696
|
-
groups: groupsResult.data,
|
|
2697
|
-
presets: presetsResult.data,
|
|
2698
|
-
internalTags: tagsResult.data
|
|
2699
|
-
};
|
|
2700
|
-
}
|
|
2713
|
+
return result;
|
|
2714
|
+
};
|
|
2701
2715
|
|
|
2702
2716
|
const pullCmd$4 = componentsCommand.command("pull [componentName]").option("-f, --filename <filename>", "custom name to be used in file(s) name instead of space id").option("--sf, --separate-files", "Argument to create a single file for each component").option("--su, --suffix <suffix>", "suffix to add to the file name (e.g. components.<suffix>.json)").option("-s, --space <space>", "space ID").description(`Download your space's components schema as json. Optionally specify a component name to pull a single component.`);
|
|
2703
2717
|
pullCmd$4.action(async (componentName, options, command) => {
|
|
@@ -3728,7 +3742,7 @@ async function pushWithDependencyGraph(space, spaceState, maxConcurrency = getAc
|
|
|
3728
3742
|
return results;
|
|
3729
3743
|
}
|
|
3730
3744
|
|
|
3731
|
-
const pushCmd$3 = componentsCommand.command("push [componentName]").description(`Push your space's components schema as json`).option("-f, --from <from>", "source space id").option("--fi, --filter <filter>", "glob filter to apply to the components before pushing").option("--sf, --separate-files", "Read from separate files instead of consolidated files", false).option("--su, --suffix <suffix>", "
|
|
3745
|
+
const pushCmd$3 = componentsCommand.command("push [componentName]").description(`Push your space's components schema as json`).option("-f, --from <from>", "source space id").option("--fi, --filter <filter>", "glob filter to apply to the components before pushing").option("--sf, --separate-files", "Read from separate files instead of consolidated files", false).option("--su, --suffix <suffix>", "Load only files matching *.<suffix>.json (e.g. components.dev.json)").option("-s, --space <space>", "space ID");
|
|
3732
3746
|
pushCmd$3.action(async (componentName, options, command) => {
|
|
3733
3747
|
const ui = getUI();
|
|
3734
3748
|
const logger = getLogger();
|
|
@@ -4160,40 +4174,84 @@ const updateStory = async (spaceId, storyId, payload) => {
|
|
|
4160
4174
|
handleAPIError("update_story", error);
|
|
4161
4175
|
}
|
|
4162
4176
|
};
|
|
4163
|
-
const
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4177
|
+
const PREFETCH_CHUNK_SIZE = 100;
|
|
4178
|
+
const PREFETCH_PER_PAGE = 100;
|
|
4179
|
+
const addRef = (result, story) => {
|
|
4180
|
+
const ref = { id: story.id, uuid: story.uuid, is_folder: story.is_folder };
|
|
4181
|
+
if (story.full_slug) {
|
|
4182
|
+
const key = normalizeFullSlug(story.full_slug);
|
|
4183
|
+
const existing = result.bySlug.get(key);
|
|
4184
|
+
if (existing) {
|
|
4185
|
+
if (!existing.some((r) => r.id === ref.id)) {
|
|
4186
|
+
existing.push(ref);
|
|
4187
|
+
}
|
|
4188
|
+
} else {
|
|
4189
|
+
result.bySlug.set(key, [ref]);
|
|
4190
|
+
}
|
|
4191
|
+
}
|
|
4192
|
+
result.byId.set(story.id, ref);
|
|
4193
|
+
};
|
|
4194
|
+
const fetchChunkAllPages = async (spaceId, params, onPageStories) => {
|
|
4168
4195
|
let page = 1;
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
const response = await fetchStories(spaceId, { page, per_page: 100 });
|
|
4196
|
+
while (true) {
|
|
4197
|
+
const response = await fetchStories(spaceId, { ...params, page, per_page: PREFETCH_PER_PAGE });
|
|
4172
4198
|
if (!response) {
|
|
4173
|
-
|
|
4199
|
+
return;
|
|
4174
4200
|
}
|
|
4201
|
+
onPageStories(response.stories);
|
|
4175
4202
|
const total = Number(response.headers.get("Total"));
|
|
4176
|
-
const perPage = Number(response.headers.get("Per-Page"));
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
options?.onTotal?.(total);
|
|
4180
|
-
}
|
|
4181
|
-
for (const story of response.stories) {
|
|
4182
|
-
const ref = { id: story.id, uuid: story.uuid, is_folder: story.is_folder };
|
|
4183
|
-
if (story.full_slug) {
|
|
4184
|
-
const key = normalizeFullSlug(story.full_slug);
|
|
4185
|
-
const existing = result.bySlug.get(key);
|
|
4186
|
-
if (existing) {
|
|
4187
|
-
existing.push(ref);
|
|
4188
|
-
} else {
|
|
4189
|
-
result.bySlug.set(key, [ref]);
|
|
4190
|
-
}
|
|
4191
|
-
}
|
|
4192
|
-
result.byId.set(story.id, ref);
|
|
4203
|
+
const perPage = Number(response.headers.get("Per-Page")) || PREFETCH_PER_PAGE;
|
|
4204
|
+
if (!Number.isFinite(total) || total <= page * perPage) {
|
|
4205
|
+
return;
|
|
4193
4206
|
}
|
|
4194
|
-
options?.onIncrement?.(response.stories.length);
|
|
4195
4207
|
page++;
|
|
4196
4208
|
}
|
|
4209
|
+
};
|
|
4210
|
+
const prefetchTargetStoriesByKeys = async (spaceId, keys, options) => {
|
|
4211
|
+
const result = {
|
|
4212
|
+
bySlug: /* @__PURE__ */ new Map(),
|
|
4213
|
+
byId: /* @__PURE__ */ new Map()
|
|
4214
|
+
};
|
|
4215
|
+
const slugSet = /* @__PURE__ */ new Set();
|
|
4216
|
+
for (const slug of keys.slugs) {
|
|
4217
|
+
if (slug) {
|
|
4218
|
+
slugSet.add(normalizeFullSlug(slug));
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
const idSet = /* @__PURE__ */ new Set();
|
|
4222
|
+
for (const id of keys.ids) {
|
|
4223
|
+
if (typeof id === "number" && Number.isFinite(id)) {
|
|
4224
|
+
idSet.add(id);
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
options?.onTotal?.(slugSet.size + idSet.size);
|
|
4228
|
+
if (slugSet.size === 0 && idSet.size === 0) {
|
|
4229
|
+
return result;
|
|
4230
|
+
}
|
|
4231
|
+
const slugChunks = chunk(slugSet, PREFETCH_CHUNK_SIZE);
|
|
4232
|
+
const idChunks = chunk(idSet, PREFETCH_CHUNK_SIZE);
|
|
4233
|
+
const requests = [];
|
|
4234
|
+
for (const slugs of slugChunks) {
|
|
4235
|
+
requests.push((async () => {
|
|
4236
|
+
await fetchChunkAllPages(spaceId, { by_slugs: slugs.join(",") }, (stories) => {
|
|
4237
|
+
for (const story of stories) {
|
|
4238
|
+
addRef(result, story);
|
|
4239
|
+
}
|
|
4240
|
+
});
|
|
4241
|
+
options?.onIncrement?.(slugs.length);
|
|
4242
|
+
})());
|
|
4243
|
+
}
|
|
4244
|
+
for (const ids of idChunks) {
|
|
4245
|
+
requests.push((async () => {
|
|
4246
|
+
await fetchChunkAllPages(spaceId, { by_ids: ids.join(",") }, (stories) => {
|
|
4247
|
+
for (const story of stories) {
|
|
4248
|
+
addRef(result, story);
|
|
4249
|
+
}
|
|
4250
|
+
});
|
|
4251
|
+
options?.onIncrement?.(ids.length);
|
|
4252
|
+
})());
|
|
4253
|
+
}
|
|
4254
|
+
await Promise.all(requests);
|
|
4197
4255
|
return result;
|
|
4198
4256
|
};
|
|
4199
4257
|
|
|
@@ -4650,45 +4708,26 @@ const isStoryPublishedWithoutChanges = (story) => {
|
|
|
4650
4708
|
const isStoryWithUnpublishedChanges = (story) => {
|
|
4651
4709
|
return story.published && story.unpublished_changes;
|
|
4652
4710
|
};
|
|
4653
|
-
const toComponent = (maybeComponent) => {
|
|
4654
|
-
if (maybeComponent.component_group_uuid === void 0) {
|
|
4655
|
-
return null;
|
|
4656
|
-
}
|
|
4657
|
-
return maybeComponent;
|
|
4658
|
-
};
|
|
4659
4711
|
const findComponentSchemas = async (directoryPath) => {
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4712
|
+
try {
|
|
4713
|
+
const { components } = await loadComponents(directoryPath);
|
|
4714
|
+
const schemas = {};
|
|
4715
|
+
for (const component of components) {
|
|
4716
|
+
schemas[component.name] = component.schema;
|
|
4663
4717
|
}
|
|
4664
|
-
|
|
4665
|
-
})
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
const fileContent = readFileSync(filePath, "utf-8");
|
|
4669
|
-
return JSON.parse(fileContent);
|
|
4670
|
-
});
|
|
4671
|
-
const components = [];
|
|
4672
|
-
for (const content of fileContents) {
|
|
4673
|
-
if (Array.isArray(content)) {
|
|
4674
|
-
for (const maybeComponent of content) {
|
|
4675
|
-
const component2 = toComponent(maybeComponent);
|
|
4676
|
-
if (component2) {
|
|
4677
|
-
components.push(component2);
|
|
4678
|
-
}
|
|
4679
|
-
}
|
|
4680
|
-
continue;
|
|
4718
|
+
return schemas;
|
|
4719
|
+
} catch (error) {
|
|
4720
|
+
if (error.code === "ENOENT") {
|
|
4721
|
+
return {};
|
|
4681
4722
|
}
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4723
|
+
if (error instanceof FileSystemError) {
|
|
4724
|
+
error.message = `Failed to load component schemas for content validation.
|
|
4725
|
+
|
|
4726
|
+
${error.message}`;
|
|
4727
|
+
throw error;
|
|
4685
4728
|
}
|
|
4729
|
+
throw error;
|
|
4686
4730
|
}
|
|
4687
|
-
const schemas = {};
|
|
4688
|
-
for (const component of components) {
|
|
4689
|
-
schemas[component.name] = component.schema;
|
|
4690
|
-
}
|
|
4691
|
-
return schemas;
|
|
4692
4731
|
};
|
|
4693
4732
|
const getStoryFilename = (story) => {
|
|
4694
4733
|
return `${story.slug}_${story.uuid}.json`;
|
|
@@ -5922,8 +5961,6 @@ const generateStoryblokTypes = async (options = {}) => {
|
|
|
5922
5961
|
}
|
|
5923
5962
|
};
|
|
5924
5963
|
|
|
5925
|
-
const DEFAULT_DATASOURCES_FILENAME = "datasources";
|
|
5926
|
-
|
|
5927
5964
|
const pushDatasource = async (spaceId, datasource) => {
|
|
5928
5965
|
try {
|
|
5929
5966
|
const client = getMapiClient();
|
|
@@ -6025,11 +6062,15 @@ const deleteDatasourceEntry = async (spaceId, entryId) => {
|
|
|
6025
6062
|
handleAPIError("delete_datasource_entry", error, `Failed to delete datasource entry ${entryId}`);
|
|
6026
6063
|
}
|
|
6027
6064
|
};
|
|
6065
|
+
function isDatasource(item) {
|
|
6066
|
+
return typeof item === "object" && item !== null && "slug" in item && typeof item.slug === "string";
|
|
6067
|
+
}
|
|
6028
6068
|
const readDatasourcesFiles = async (options) => {
|
|
6029
|
-
const { from, path,
|
|
6069
|
+
const { from, path, suffix } = options;
|
|
6030
6070
|
const resolvedPath = resolvePath(path, `datasources/${from}`);
|
|
6071
|
+
let files;
|
|
6031
6072
|
try {
|
|
6032
|
-
await readdir(resolvedPath);
|
|
6073
|
+
files = await readdir(resolvedPath);
|
|
6033
6074
|
} catch (error) {
|
|
6034
6075
|
const message = `No local datasources found for space ${chalk.bold(from)}. To push datasources, you need to pull them first:
|
|
6035
6076
|
|
|
@@ -6045,54 +6086,51 @@ const readDatasourcesFiles = async (options) => {
|
|
|
6045
6086
|
message
|
|
6046
6087
|
);
|
|
6047
6088
|
}
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
const datasources = [];
|
|
6056
|
-
const filteredFiles = files.filter((file) => {
|
|
6057
|
-
if (suffix) {
|
|
6058
|
-
return file.endsWith(`.${suffix}.json`);
|
|
6059
|
-
} else {
|
|
6060
|
-
return !/\.\w+\.json$/.test(file);
|
|
6089
|
+
const datasourceMap = /* @__PURE__ */ new Map();
|
|
6090
|
+
const duplicates = [];
|
|
6091
|
+
for (const file of filterJsonBySuffix(files, suffix)) {
|
|
6092
|
+
const { data, error } = await readJsonFile(join(resolvedPath, file));
|
|
6093
|
+
if (error) {
|
|
6094
|
+
handleFileSystemError("read", error);
|
|
6095
|
+
continue;
|
|
6061
6096
|
}
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
const result = await readJsonFile(filePath);
|
|
6070
|
-
if (result.error) {
|
|
6071
|
-
handleFileSystemError("read", result.error);
|
|
6072
|
-
continue;
|
|
6097
|
+
for (const item of data) {
|
|
6098
|
+
if (isDatasource(item)) {
|
|
6099
|
+
const existing = datasourceMap.get(item.slug);
|
|
6100
|
+
if (existing) {
|
|
6101
|
+
duplicates.push(`Datasource "${item.slug}" found in both "${existing.file}" and "${file}"`);
|
|
6102
|
+
}
|
|
6103
|
+
datasourceMap.set(item.slug, { datasource: item, file });
|
|
6073
6104
|
}
|
|
6074
|
-
datasources.push(...result.data);
|
|
6075
6105
|
}
|
|
6076
6106
|
}
|
|
6077
|
-
|
|
6078
|
-
datasources
|
|
6079
|
-
};
|
|
6080
|
-
}
|
|
6081
|
-
async function readConsolidatedFiles(resolvedPath, suffix) {
|
|
6082
|
-
const datasourcesPath = join(resolvedPath, suffix ? `${DEFAULT_DATASOURCES_FILENAME}.${suffix}.json` : `${DEFAULT_DATASOURCES_FILENAME}.json`);
|
|
6083
|
-
const datasourcesResult = await readJsonFile(datasourcesPath);
|
|
6084
|
-
if (datasourcesResult.error || !datasourcesResult.data.length) {
|
|
6107
|
+
if (duplicates.length) {
|
|
6085
6108
|
throw new FileSystemError(
|
|
6086
6109
|
"file_not_found",
|
|
6087
6110
|
"read",
|
|
6088
|
-
|
|
6089
|
-
`
|
|
6111
|
+
new Error("Duplicate datasources detected"),
|
|
6112
|
+
`Duplicate datasources found in ${resolvedPath}:
|
|
6113
|
+
|
|
6114
|
+
${duplicates.join("\n")}
|
|
6115
|
+
|
|
6116
|
+
This can happen when multiple environment snapshots (e.g. datasources.json and datasources.dev.json) or mixed formats coexist in the same directory.
|
|
6117
|
+
|
|
6118
|
+
To fix this, either:
|
|
6119
|
+
- Use --suffix <env> to target a specific environment (e.g. --suffix dev)
|
|
6120
|
+
- Clean up the directory and pull datasources again in the format you intend`
|
|
6090
6121
|
);
|
|
6091
6122
|
}
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6123
|
+
const datasources = [...datasourceMap.values()].map(({ datasource }) => datasource);
|
|
6124
|
+
if (!datasources.length) {
|
|
6125
|
+
throw new FileSystemError(
|
|
6126
|
+
"file_not_found",
|
|
6127
|
+
"read",
|
|
6128
|
+
new Error("No datasource data found"),
|
|
6129
|
+
`No datasources found in ${resolvedPath}. Please make sure you have pulled the datasources first.`
|
|
6130
|
+
);
|
|
6131
|
+
}
|
|
6132
|
+
return { datasources };
|
|
6133
|
+
};
|
|
6096
6134
|
|
|
6097
6135
|
const generateCmd = typesCommand.command("generate").description("Generate types d.ts for your component schemas").option(
|
|
6098
6136
|
"--filename <name>",
|
|
@@ -6160,6 +6198,8 @@ generateCmd.action(async (options, command) => {
|
|
|
6160
6198
|
const program$6 = getProgram();
|
|
6161
6199
|
const datasourcesCommand = program$6.command(commands.DATASOURCES).alias("ds").description(`Manage your space's datasources`);
|
|
6162
6200
|
|
|
6201
|
+
const DEFAULT_DATASOURCES_FILENAME = "datasources";
|
|
6202
|
+
|
|
6163
6203
|
const fetchDatasourceEntries = async (spaceId, datasourceId) => {
|
|
6164
6204
|
try {
|
|
6165
6205
|
const client = getMapiClient();
|
|
@@ -6328,7 +6368,7 @@ pullCmd$2.action(async (datasourceName, options, command) => {
|
|
|
6328
6368
|
}
|
|
6329
6369
|
});
|
|
6330
6370
|
|
|
6331
|
-
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>", "
|
|
6371
|
+
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");
|
|
6332
6372
|
pushCmd$2.action(async (datasourceName, options, command) => {
|
|
6333
6373
|
konsola.title(`${commands.DATASOURCES}`, colorPalette.DATASOURCES, datasourceName ? `Pushing datasource ${datasourceName}...` : "Pushing datasources...");
|
|
6334
6374
|
const { space, path, verbose } = command.optsWithGlobals();
|
|
@@ -9190,12 +9230,6 @@ pushCmd.action(async (options, command) => {
|
|
|
9190
9230
|
};
|
|
9191
9231
|
visit(story.content);
|
|
9192
9232
|
};
|
|
9193
|
-
const fetchProgress = ui.createProgressBar({ title: "Matching Stories...".padEnd(21) });
|
|
9194
|
-
const existingTargetStories = await prefetchTargetStories(space, {
|
|
9195
|
-
onTotal: (total) => fetchProgress.setTotal(total),
|
|
9196
|
-
onIncrement: (count) => fetchProgress.increment(count)
|
|
9197
|
-
});
|
|
9198
|
-
fetchProgress.stop();
|
|
9199
9233
|
const scanProgress = ui.createProgressBar({ title: "Scanning Stories...".padEnd(21) });
|
|
9200
9234
|
const storyIndex = await scanLocalStoryIndex({
|
|
9201
9235
|
directoryPath: storiesDirectoryPath,
|
|
@@ -9214,6 +9248,24 @@ pushCmd.action(async (options, command) => {
|
|
|
9214
9248
|
});
|
|
9215
9249
|
const levels = groupStoriesByDepth(storyIndex);
|
|
9216
9250
|
scanProgress.stop();
|
|
9251
|
+
const localSlugs = storyIndex.map((entry) => entry.full_slug).filter(Boolean);
|
|
9252
|
+
const localIdSet = new Set(storyIndex.map((entry) => entry.id));
|
|
9253
|
+
const manifestIds = [];
|
|
9254
|
+
for (const [key, value] of maps.stories.entries()) {
|
|
9255
|
+
if (typeof key === "number" && localIdSet.has(key) && typeof value === "number") {
|
|
9256
|
+
manifestIds.push(value);
|
|
9257
|
+
}
|
|
9258
|
+
}
|
|
9259
|
+
const fetchProgress = ui.createProgressBar({ title: "Matching Stories...".padEnd(21) });
|
|
9260
|
+
const existingTargetStories = await prefetchTargetStoriesByKeys(
|
|
9261
|
+
space,
|
|
9262
|
+
{ slugs: localSlugs, ids: manifestIds },
|
|
9263
|
+
{
|
|
9264
|
+
onTotal: (total) => fetchProgress.setTotal(total),
|
|
9265
|
+
onIncrement: (count) => fetchProgress.increment(count)
|
|
9266
|
+
}
|
|
9267
|
+
);
|
|
9268
|
+
fetchProgress.stop();
|
|
9217
9269
|
const creationProgress = ui.createProgressBar({ title: "Creating Stories...".padEnd(21) });
|
|
9218
9270
|
const processProgress = ui.createProgressBar({ title: "Processing Stories...".padEnd(21) });
|
|
9219
9271
|
const updateProgress = ui.createProgressBar({ title: "Updating Stories...".padEnd(21) });
|