storyblok 4.17.2 → 4.17.5

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 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, access, constants, readFile as readFile$1, appendFile, readdir, unlink } from 'node:fs/promises';
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 consolidatedFilename(defaultFilename, suffix) {
1386
- return suffix ? `${defaultFilename}.${suffix}.json` : `${defaultFilename}.json`;
1387
- }
1388
- async function shouldUseSeparateFiles(resolvedPath, defaultFilename, separateFiles, suffix) {
1389
- if (separateFiles !== void 0) {
1390
- return separateFiles;
1391
- }
1392
- return !await fileExists(join(resolvedPath, consolidatedFilename(defaultFilename, suffix)));
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, separateFiles, suffix } = options;
2682
+ const { from, path, suffix } = options;
2600
2683
  const resolvedPath = resolvePath(path, `components/${from}`);
2684
+ let result;
2601
2685
  try {
2602
- await readdir(resolvedPath);
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 (await shouldUseSeparateFiles(resolvedPath, DEFAULT_COMPONENTS_FILENAME, separateFiles, suffix)) {
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
- componentsResult.error || new Error("Components file is empty"),
2686
- `No components found in ${componentsPath}. Please make sure you have pulled the components first.`
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
- const [groupsResult, presetsResult, tagsResult] = await Promise.all([
2690
- readJsonFile(join(resolvedPath, suffix ? `${DEFAULT_GROUPS_FILENAME}.${suffix}.json` : `${DEFAULT_GROUPS_FILENAME}.json`)),
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>", "Suffix to add to the component name").option("-s, --space <space>", "space ID");
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 prefetchTargetStories = async (spaceId, options) => {
4164
- const result = {
4165
- bySlug: /* @__PURE__ */ new Map(),
4166
- byId: /* @__PURE__ */ new Map()
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
- let totalPages = 1;
4170
- while (page <= totalPages) {
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
- break;
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
- totalPages = Math.ceil(total / perPage);
4178
- if (page === 1) {
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
- const files = await readdir(directoryPath).catch((error) => {
4661
- if (error.code === "ENOENT") {
4662
- return [];
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
- throw error;
4665
- });
4666
- const fileContents = files.filter((f) => extname(f) === ".json").map((f) => {
4667
- const filePath = join(directoryPath, f);
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
- const component = toComponent(content);
4683
- if (component) {
4684
- components.push(component);
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, separateFiles, suffix } = options;
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
- if (await shouldUseSeparateFiles(resolvedPath, DEFAULT_DATASOURCES_FILENAME, separateFiles, suffix)) {
6049
- return await readSeparateFiles(resolvedPath, suffix);
6050
- }
6051
- return await readConsolidatedFiles(resolvedPath, suffix);
6052
- };
6053
- async function readSeparateFiles(resolvedPath, suffix) {
6054
- const files = await readdir(resolvedPath);
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
- for (const file of filteredFiles) {
6064
- const filePath = join(resolvedPath, file);
6065
- if (file.endsWith(".json") || file.endsWith(`${suffix}.json`)) {
6066
- if (file === `${DEFAULT_DATASOURCES_FILENAME}.json` || new RegExp(`^${DEFAULT_DATASOURCES_FILENAME}\\.\\w+\\.json$`).test(file)) {
6067
- continue;
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
- return {
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
- datasourcesResult.error || new Error("Datasources file is empty"),
6089
- `No datasources found in ${datasourcesPath}. Please make sure you have pulled the datasources first.`
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
- return {
6093
- datasources: datasourcesResult.data
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>", "Suffix to add to the datasource name").option("-s, --space <space>", "space ID");
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) });