storyblok 4.3.1 → 4.3.3

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
@@ -9,6 +9,7 @@ import { Spinner } from '@topcli/spinner';
9
9
  import { select, password, input, confirm } from '@inquirer/prompts';
10
10
  import fs, { mkdir, writeFile, readFile as readFile$1, access, readdir } from 'node:fs/promises';
11
11
  import path, { join, parse, resolve } from 'node:path';
12
+ import filenamify from 'filenamify';
12
13
  import { exec, spawn } from 'node:child_process';
13
14
  import { promisify } from 'node:util';
14
15
  import { getRegion } from '@storyblok/region-helper';
@@ -614,6 +615,11 @@ const resolvePath = (path, folder) => {
614
615
  const getComponentNameFromFilename = (filename) => {
615
616
  return filename.replace(/\.js$/, "");
616
617
  };
618
+ const sanitizeFilename = (filename) => {
619
+ return filenamify(filename, {
620
+ replacement: "_"
621
+ });
622
+ };
617
623
  async function readJsonFile(filePath) {
618
624
  try {
619
625
  const content = (await readFile(filePath)).toString();
@@ -1246,11 +1252,12 @@ const saveComponentsToFiles = async (space, spaceData, options) => {
1246
1252
  try {
1247
1253
  if (separateFiles) {
1248
1254
  for (const component of components) {
1249
- const componentFilePath = join(resolvedPath, suffix ? `${component.name}.${suffix}.json` : `${component.name}.json`);
1255
+ const sanitizedName = sanitizeFilename(component.name);
1256
+ const componentFilePath = join(resolvedPath, suffix ? `${sanitizedName}.${suffix}.json` : `${sanitizedName}.json`);
1250
1257
  await saveToFile(componentFilePath, JSON.stringify(component, null, 2));
1251
1258
  const componentPresets = presets.filter((preset) => preset.component_id === component.id);
1252
1259
  if (componentPresets.length > 0) {
1253
- const presetsFilePath = join(resolvedPath, suffix ? `${component.name}.presets.${suffix}.json` : `${component.name}.presets.json`);
1260
+ const presetsFilePath = join(resolvedPath, suffix ? `${sanitizedName}.presets.${suffix}.json` : `${sanitizedName}.presets.json`);
1254
1261
  await saveToFile(presetsFilePath, JSON.stringify(componentPresets, null, 2));
1255
1262
  }
1256
1263
  const groupsFilePath = join(resolvedPath, suffix ? `groups.${suffix}.json` : `groups.json`);
@@ -1562,7 +1569,7 @@ componentsCommand.command("pull [componentName]").option("-f, --filename <filena
1562
1569
  spinnerComponents.succeed(`${chalk.hex(colorPalette.COMPONENTS)("Components")} - Completed in ${spinnerComponents.elapsedTime.toFixed(2)}ms`);
1563
1570
  await saveComponentsToFiles(
1564
1571
  space,
1565
- { components, groups: groups || [], presets: presets || [], internalTags: internalTags || [] },
1572
+ { components, groups: groups || [], presets: presets || [], internalTags: internalTags || [], datasources: [] },
1566
1573
  { ...options, path, separateFiles: separateFiles || !!componentName }
1567
1574
  );
1568
1575
  konsola.br();
@@ -1592,153 +1599,6 @@ componentsCommand.command("pull [componentName]").option("-f, --filename <filena
1592
1599
  }
1593
1600
  });
1594
1601
 
1595
- const pushDatasource = async (space, datasource) => {
1596
- try {
1597
- const client = mapiClient();
1598
- const { data } = await client.post(`spaces/${space}/datasources`, {
1599
- body: JSON.stringify(datasource)
1600
- });
1601
- return data.datasource;
1602
- } catch (error) {
1603
- handleAPIError("push_datasource", error, `Failed to push datasource ${datasource.name}`);
1604
- }
1605
- };
1606
- const updateDatasource = async (space, datasourceId, datasource) => {
1607
- try {
1608
- const client = mapiClient();
1609
- const { data } = await client.put(`spaces/${space}/datasources/${datasourceId}`, {
1610
- body: JSON.stringify(datasource)
1611
- });
1612
- return data.datasource;
1613
- } catch (error) {
1614
- handleAPIError("update_datasource", error, `Failed to update datasource ${datasource.name}`);
1615
- }
1616
- };
1617
- const upsertDatasource = async (space, datasource, existingId) => {
1618
- if (existingId) {
1619
- return await updateDatasource(space, existingId, datasource);
1620
- } else {
1621
- return await pushDatasource(space, datasource);
1622
- }
1623
- };
1624
- const pushDatasourceEntry = async (space, datasourceId, entry) => {
1625
- try {
1626
- const client = mapiClient();
1627
- const { data } = await client.post(`spaces/${space}/datasource_entries`, {
1628
- body: JSON.stringify({
1629
- datasource_entry: {
1630
- ...entry,
1631
- datasource_id: datasourceId
1632
- }
1633
- })
1634
- });
1635
- return data.datasource_entry;
1636
- } catch (error) {
1637
- handleAPIError("push_datasource", error, `Failed to push datasource entry ${entry.name}`);
1638
- }
1639
- };
1640
- const updateDatasourceEntry = async (space, entryId, entry) => {
1641
- try {
1642
- const client = mapiClient();
1643
- await client.put(`spaces/${space}/datasource_entries/${entryId}`, {
1644
- body: JSON.stringify({
1645
- datasource_entry: entry
1646
- })
1647
- });
1648
- } catch (error) {
1649
- handleAPIError("update_datasource", error, `Failed to update datasource entry ${entry.name}`);
1650
- }
1651
- };
1652
- const upsertDatasourceEntry = async (space, datasourceId, entry, existingId) => {
1653
- if (existingId) {
1654
- await updateDatasourceEntry(space, existingId, entry);
1655
- return void 0;
1656
- } else {
1657
- return await pushDatasourceEntry(space, datasourceId, entry);
1658
- }
1659
- };
1660
- const readDatasourcesFiles = async (options) => {
1661
- const { from, path, separateFiles = false, suffix, space } = options;
1662
- const resolvedPath = resolvePath(path, `datasources/${from}`);
1663
- try {
1664
- await readdir(resolvedPath);
1665
- } catch (error) {
1666
- const message = `No local datasources found for space ${chalk.bold(from)}. To push datasources, you need to pull them first:
1667
-
1668
- 1. Pull the datasources from your source space:
1669
- ${chalk.cyan(`storyblok datasources pull --space ${from}`)}
1670
-
1671
- 2. Then try pushing again:
1672
- ${chalk.cyan(`storyblok datasources push --space ${space} --from ${from}`)}`;
1673
- throw new FileSystemError(
1674
- "file_not_found",
1675
- "read",
1676
- error,
1677
- message
1678
- );
1679
- }
1680
- if (separateFiles) {
1681
- return await readSeparateFiles(resolvedPath, suffix);
1682
- }
1683
- return await readConsolidatedFiles(resolvedPath, suffix);
1684
- };
1685
- async function readSeparateFiles(resolvedPath, suffix) {
1686
- const files = await readdir(resolvedPath);
1687
- const datasources = [];
1688
- const filteredFiles = files.filter((file) => {
1689
- if (suffix) {
1690
- return file.endsWith(`.${suffix}.json`);
1691
- } else {
1692
- return !/\.\w+\.json$/.test(file);
1693
- }
1694
- });
1695
- for (const file of filteredFiles) {
1696
- const filePath = join(resolvedPath, file);
1697
- if (file.endsWith(".json") || file.endsWith(`${suffix}.json`)) {
1698
- if (file === "datasources.json" || /^datasources\.\w+\.json$/.test(file)) {
1699
- continue;
1700
- }
1701
- const result = await readJsonFile(filePath);
1702
- if (result.error) {
1703
- handleFileSystemError("read", result.error);
1704
- continue;
1705
- }
1706
- datasources.push(...result.data);
1707
- }
1708
- }
1709
- return {
1710
- datasources
1711
- };
1712
- }
1713
- async function readConsolidatedFiles(resolvedPath, suffix) {
1714
- const datasourcesPath = join(resolvedPath, suffix ? `datasources.${suffix}.json` : "datasources.json");
1715
- const datasourcesResult = await readJsonFile(datasourcesPath);
1716
- if (datasourcesResult.error || !datasourcesResult.data.length) {
1717
- throw new FileSystemError(
1718
- "file_not_found",
1719
- "read",
1720
- datasourcesResult.error || new Error("Datasources file is empty"),
1721
- `No datasources found in ${datasourcesPath}. Please make sure you have pulled the datasources first.`
1722
- );
1723
- }
1724
- return {
1725
- datasources: datasourcesResult.data
1726
- };
1727
- }
1728
-
1729
- function createStubDatasource(name) {
1730
- return {
1731
- id: 0,
1732
- // Will be set by API
1733
- name,
1734
- slug: name,
1735
- dimensions: [],
1736
- entries: [],
1737
- // Empty entries for stub
1738
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
1739
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
1740
- };
1741
- }
1742
1602
  function buildDependencyGraph(context) {
1743
1603
  const { spaceState } = context;
1744
1604
  const graph = { nodes: /* @__PURE__ */ new Map() };
@@ -1750,22 +1610,6 @@ function buildDependencyGraph(context) {
1750
1610
  dependency.dependents.add(dependentId);
1751
1611
  }
1752
1612
  }
1753
- const referencedDatasources = /* @__PURE__ */ new Set();
1754
- spaceState.local.components.forEach((component) => {
1755
- if (component.schema) {
1756
- const dependencies = collectWhitelistDependencies(component.schema);
1757
- dependencies.datasourceNames.forEach((datasourceName) => {
1758
- referencedDatasources.add(datasourceName);
1759
- });
1760
- }
1761
- });
1762
- referencedDatasources.forEach((datasourceName) => {
1763
- const nodeId = `datasource:${datasourceName}`;
1764
- const targetDatasource = spaceState.target.datasources?.get(datasourceName);
1765
- const stubDatasource = createStubDatasource(datasourceName);
1766
- const node = new DatasourceNode(nodeId, stubDatasource, targetDatasource);
1767
- graph.nodes.set(nodeId, node);
1768
- });
1769
1613
  spaceState.local.internalTags.forEach((tag) => {
1770
1614
  const nodeId = `tag:${tag.id}`;
1771
1615
  const targetTag = spaceState.target.tags.get(tag.name);
@@ -1841,10 +1685,6 @@ function buildDependencyGraph(context) {
1841
1685
  const dependencyId = `component:${componentName}`;
1842
1686
  addDependency(componentId, dependencyId);
1843
1687
  });
1844
- dependencies.datasourceNames.forEach((datasourceName) => {
1845
- const datasourceId = `datasource:${datasourceName}`;
1846
- addDependency(componentId, datasourceId);
1847
- });
1848
1688
  }
1849
1689
  });
1850
1690
  spaceState.local.presets.forEach((preset) => {
@@ -2227,15 +2067,6 @@ class ComponentNode extends GraphNode {
2227
2067
  });
2228
2068
  }
2229
2069
  }
2230
- if ((resolvedField.type === "option" || resolvedField.type === "options") && resolvedField.source === "internal") {
2231
- if (resolvedField.datasource_slug && typeof resolvedField.datasource_slug === "string") {
2232
- const datasourceNodeId = `datasource:${resolvedField.datasource_slug}`;
2233
- const datasourceNode = graph.nodes.get(datasourceNodeId);
2234
- if (datasourceNode?.targetData) {
2235
- resolvedField.datasource_slug = datasourceNode.targetData.resource.slug;
2236
- }
2237
- }
2238
- }
2239
2070
  Object.keys(resolvedField).forEach((key) => {
2240
2071
  if (typeof resolvedField[key] === "object" && resolvedField[key] !== null) {
2241
2072
  resolvedField[key] = resolveField(resolvedField[key]);
@@ -2307,23 +2138,6 @@ class PresetNode {
2307
2138
  };
2308
2139
  }
2309
2140
  }
2310
- class DatasourceNode extends GraphNode {
2311
- constructor(id, data, targetDatasource) {
2312
- super(id, "datasource", data.name, data, targetDatasource);
2313
- }
2314
- resolveReferences(_graph) {
2315
- }
2316
- async upsert(space) {
2317
- const existingDatasource = this.targetData?.resource;
2318
- const existingId = existingDatasource?.id;
2319
- const { entries, ...datasourceDefinition } = this.sourceData;
2320
- const result = await upsertDatasource(space, datasourceDefinition, existingId);
2321
- if (!result) {
2322
- throw new Error(`Failed to upsert datasource ${this.name}`);
2323
- }
2324
- return result;
2325
- }
2326
- }
2327
2141
 
2328
2142
  function collectAllDependencies(components, allComponents, allGroups, allTags) {
2329
2143
  const requiredComponents = /* @__PURE__ */ new Set();
@@ -2392,7 +2206,8 @@ function filterSpaceDataByComponent(spaceData, componentName) {
2392
2206
  components: [],
2393
2207
  groups: [],
2394
2208
  internalTags: [],
2395
- presets: []
2209
+ presets: [],
2210
+ datasources: []
2396
2211
  };
2397
2212
  }
2398
2213
  const { filteredComponents, filteredGroups, filteredTags } = collectAllDependencies(
@@ -2409,7 +2224,8 @@ function filterSpaceDataByComponent(spaceData, componentName) {
2409
2224
  components: filteredComponents,
2410
2225
  groups: filteredGroups,
2411
2226
  internalTags: filteredTags,
2412
- presets: filteredPresets
2227
+ presets: filteredPresets,
2228
+ datasources: []
2413
2229
  };
2414
2230
  }
2415
2231
  function filterSpaceDataByPattern(spaceData, pattern) {
@@ -2421,7 +2237,8 @@ function filterSpaceDataByPattern(spaceData, pattern) {
2421
2237
  components: [],
2422
2238
  groups: [],
2423
2239
  internalTags: [],
2424
- presets: []
2240
+ presets: [],
2241
+ datasources: []
2425
2242
  };
2426
2243
  }
2427
2244
  const { filteredComponents, filteredGroups, filteredTags } = collectAllDependencies(
@@ -2438,7 +2255,8 @@ function filterSpaceDataByPattern(spaceData, pattern) {
2438
2255
  components: filteredComponents,
2439
2256
  groups: filteredGroups,
2440
2257
  internalTags: filteredTags,
2441
- presets: filteredPresets
2258
+ presets: filteredPresets,
2259
+ datasources: []
2442
2260
  };
2443
2261
  }
2444
2262
 
@@ -2724,63 +2542,6 @@ async function pushWithDependencyGraph(space, spaceState, maxConcurrency = 5) {
2724
2542
  return results;
2725
2543
  }
2726
2544
 
2727
- const fetchDatasourceEntries = async (space, datasourceId) => {
2728
- try {
2729
- const client = mapiClient();
2730
- const { data } = await client.get(`spaces/${space}/datasource_entries?datasource_id=${datasourceId}`);
2731
- return data.datasource_entries;
2732
- } catch (error) {
2733
- handleAPIError("pull_datasources", error);
2734
- }
2735
- };
2736
- const fetchDatasources = async (space) => {
2737
- try {
2738
- const client = mapiClient();
2739
- const { data } = await client.get(`spaces/${space}/datasources`);
2740
- const datasources = data.datasources;
2741
- const datasourcesWithEntries = await Promise.all(
2742
- datasources.map(async (ds) => {
2743
- const entries = await fetchDatasourceEntries(space, ds.id);
2744
- return { ...ds, entries };
2745
- })
2746
- );
2747
- return datasourcesWithEntries;
2748
- } catch (error) {
2749
- handleAPIError("pull_datasources", error);
2750
- }
2751
- };
2752
- const fetchDatasource = async (space, datasourceName) => {
2753
- try {
2754
- const client = mapiClient();
2755
- const { data } = await client.get(`spaces/${space}/datasources?search=${encodeURIComponent(datasourceName)}`);
2756
- const found = data.datasources?.find((d) => d.name === datasourceName);
2757
- if (!found) {
2758
- return void 0;
2759
- }
2760
- const entries = await fetchDatasourceEntries(space, found.id);
2761
- return { ...found, entries };
2762
- } catch (error) {
2763
- handleAPIError("pull_datasources", error, `Failed to fetch datasource ${datasourceName}`);
2764
- }
2765
- };
2766
- const saveDatasourcesToFiles = async (space, datasources, options) => {
2767
- const { filename = "datasources", suffix, path, separateFiles } = options;
2768
- const resolvedPath = path ? resolve(process.cwd(), path, "datasources", space) : resolvePath(path, `datasources/${space}`);
2769
- try {
2770
- if (separateFiles) {
2771
- for (const datasource of datasources) {
2772
- const datasourceFilePath = join(resolvedPath, suffix ? `${datasource.name}.${suffix}.json` : `${datasource.name}.json`);
2773
- await saveToFile(datasourceFilePath, JSON.stringify(datasource, null, 2));
2774
- }
2775
- return;
2776
- }
2777
- const datasourcesFilePath = join(resolvedPath, suffix ? `${filename}.${suffix}.json` : `${filename}.json`);
2778
- await saveToFile(datasourcesFilePath, JSON.stringify(datasources, null, 2));
2779
- } catch (error) {
2780
- handleFileSystemError("write", error);
2781
- }
2782
- };
2783
-
2784
2545
  const program$c = getProgram();
2785
2546
  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").option("--su, --suffix <suffix>", "Suffix to add to the component name").action(async (componentName, options) => {
2786
2547
  konsola.title(`${commands.COMPONENTS}`, colorPalette.COMPONENTS, componentName ? `Pushing component ${componentName}...` : "Pushing components...");
@@ -2834,10 +2595,9 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2834
2595
  fetchComponents(space),
2835
2596
  fetchComponentGroups(space),
2836
2597
  fetchComponentPresets(space),
2837
- fetchComponentInternalTags(space),
2838
- fetchDatasources(space)
2598
+ fetchComponentInternalTags(space)
2839
2599
  ];
2840
- const [components, groups, presets, internalTags, datasources] = await Promise.all(promises);
2600
+ const [components, groups, presets, internalTags] = await Promise.all(promises);
2841
2601
  if (components) {
2842
2602
  components.forEach((component) => {
2843
2603
  spaceState.target.components.set(component.name, component);
@@ -2862,11 +2622,6 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2862
2622
  spaceState.target.tags.set(tag.name, tag);
2863
2623
  });
2864
2624
  }
2865
- if (datasources) {
2866
- datasources.forEach((datasource) => {
2867
- spaceState.target.datasources.set(datasource.name, datasource);
2868
- });
2869
- }
2870
2625
  if (componentName) {
2871
2626
  spaceState.local = filterSpaceDataByComponent(spaceState.local, componentName);
2872
2627
  if (!spaceState.local.components.length) {
@@ -2904,6 +2659,26 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2904
2659
  }
2905
2660
  }
2906
2661
  console.log(`${requestCount} requests made`);
2662
+ const referencedDatasources = /* @__PURE__ */ new Set();
2663
+ spaceState.local.components.forEach((component) => {
2664
+ if (component.schema) {
2665
+ const fields = JSON.stringify(component.schema);
2666
+ const datasourceMatches = fields.match(/"datasource_slug"\s*:\s*"([^"]+)"/g);
2667
+ if (datasourceMatches) {
2668
+ datasourceMatches.forEach((match) => {
2669
+ const slug = match.match(/"([^"]+)"$/)?.[1];
2670
+ if (slug) {
2671
+ referencedDatasources.add(slug);
2672
+ }
2673
+ });
2674
+ }
2675
+ }
2676
+ });
2677
+ if (referencedDatasources.size > 0) {
2678
+ konsola.br();
2679
+ konsola.info(`Components reference datasources: ${chalk.yellow(Array.from(referencedDatasources).join(", "))}`);
2680
+ konsola.info(`To manage datasources, use: ${chalk.cyan("storyblok datasources push")}`);
2681
+ }
2907
2682
  } catch (error) {
2908
2683
  handleError(error, verbose);
2909
2684
  }
@@ -3591,7 +3366,6 @@ migrationsCommand.command("rollback [migrationFile]").description("Rollback a mi
3591
3366
  handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
3592
3367
  return;
3593
3368
  }
3594
- const { password, region } = state;
3595
3369
  try {
3596
3370
  const rollbackData = await readRollbackFile({
3597
3371
  space,
@@ -3601,7 +3375,7 @@ migrationsCommand.command("rollback [migrationFile]").description("Rollback a mi
3601
3375
  for (const story of rollbackData.stories) {
3602
3376
  const spinner = new Spinner({ verbose }).start(`Restoring story ${chalk.hex(colorPalette.PRIMARY)(story.name || story.storyId)}...`);
3603
3377
  try {
3604
- await updateStory(space, password, region, story.storyId, {
3378
+ await updateStory(space, story.storyId, {
3605
3379
  story: {
3606
3380
  content: story.content,
3607
3381
  id: story.storyId,
@@ -4176,6 +3950,15 @@ const getComponentPropertiesTypeAnnotations = async (component, options, spaceDa
4176
3950
  );
4177
3951
  propertyTypeAnnotation[key].tsType = componentsInGroupWhitelist.length > 0 ? `(${componentsInGroupWhitelist.join(" | ")})[]` : `never[]`;
4178
3952
  }
3953
+ } else if (value.restrict_type === "tags") {
3954
+ if (Array.isArray(value.component_tag_whitelist) && value.component_tag_whitelist.length > 0) {
3955
+ const componentsWithTags = spaceData.components.filter(
3956
+ (component2) => component2.internal_tag_ids && component2.internal_tag_ids.some(
3957
+ (tagId) => value.component_tag_whitelist.includes(Number(tagId))
3958
+ )
3959
+ );
3960
+ propertyTypeAnnotation[key].tsType = componentsWithTags.length > 0 ? `(${componentsWithTags.map((component2) => getComponentType(component2.name, options)).join(" | ")})[]` : `never[]`;
3961
+ }
4179
3962
  } else {
4180
3963
  if (Array.isArray(value.component_whitelist) && value.component_whitelist.length > 0) {
4181
3964
  propertyTypeAnnotation[key].tsType = `(${value.component_whitelist.map((name) => getComponentType(name, options)).join(" | ")})[]`;
@@ -4366,6 +4149,64 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
4366
4149
  const program$4 = getProgram();
4367
4150
  const datasourcesCommand = program$4.command(commands.DATASOURCES).alias("ds").description(`Manage your space's datasources`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/datasources").hook("preAction", resolveRegion);
4368
4151
 
4152
+ const fetchDatasourceEntries = async (space, datasourceId) => {
4153
+ try {
4154
+ const client = mapiClient();
4155
+ const { data } = await client.get(`spaces/${space}/datasource_entries?datasource_id=${datasourceId}`);
4156
+ return data.datasource_entries;
4157
+ } catch (error) {
4158
+ handleAPIError("pull_datasources", error);
4159
+ }
4160
+ };
4161
+ const fetchDatasources = async (space) => {
4162
+ try {
4163
+ const client = mapiClient();
4164
+ const { data } = await client.get(`spaces/${space}/datasources`);
4165
+ const datasources = data.datasources;
4166
+ const datasourcesWithEntries = await Promise.all(
4167
+ datasources.map(async (ds) => {
4168
+ const entries = await fetchDatasourceEntries(space, ds.id);
4169
+ return { ...ds, entries };
4170
+ })
4171
+ );
4172
+ return datasourcesWithEntries;
4173
+ } catch (error) {
4174
+ handleAPIError("pull_datasources", error);
4175
+ }
4176
+ };
4177
+ const fetchDatasource = async (space, datasourceName) => {
4178
+ try {
4179
+ const client = mapiClient();
4180
+ const { data } = await client.get(`spaces/${space}/datasources?search=${encodeURIComponent(datasourceName)}`);
4181
+ const found = data.datasources?.find((d) => d.name === datasourceName);
4182
+ if (!found) {
4183
+ return void 0;
4184
+ }
4185
+ const entries = await fetchDatasourceEntries(space, found.id);
4186
+ return { ...found, entries };
4187
+ } catch (error) {
4188
+ handleAPIError("pull_datasources", error, `Failed to fetch datasource ${datasourceName}`);
4189
+ }
4190
+ };
4191
+ const saveDatasourcesToFiles = async (space, datasources, options) => {
4192
+ const { filename = "datasources", suffix, path, separateFiles } = options;
4193
+ const resolvedPath = path ? resolve(process.cwd(), path, "datasources", space) : resolvePath(path, `datasources/${space}`);
4194
+ try {
4195
+ if (separateFiles) {
4196
+ for (const datasource of datasources) {
4197
+ const sanitizedName = sanitizeFilename(datasource.name);
4198
+ const datasourceFilePath = join(resolvedPath, suffix ? `${sanitizedName}.${suffix}.json` : `${sanitizedName}.json`);
4199
+ await saveToFile(datasourceFilePath, JSON.stringify(datasource, null, 2));
4200
+ }
4201
+ return;
4202
+ }
4203
+ const datasourcesFilePath = join(resolvedPath, suffix ? `${filename}.${suffix}.json` : `${filename}.json`);
4204
+ await saveToFile(datasourcesFilePath, JSON.stringify(datasources, null, 2));
4205
+ } catch (error) {
4206
+ handleFileSystemError("write", error);
4207
+ }
4208
+ };
4209
+
4369
4210
  const program$3 = getProgram();
4370
4211
  datasourcesCommand.command("pull [datasourceName]").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 datasource").option("--su, --suffix <suffix>", "suffix to add to the file name (e.g. datasources.<suffix>.json)").description("Pull datasources from your space").action(async (datasourceName, options) => {
4371
4212
  konsola.title(`${commands.DATASOURCES}`, colorPalette.DATASOURCES, datasourceName ? `Pulling datasource ${datasourceName}...` : "Pulling datasources...");
@@ -4436,6 +4277,140 @@ datasourcesCommand.command("pull [datasourceName]").option("-f, --filename <file
4436
4277
  }
4437
4278
  });
4438
4279
 
4280
+ const pushDatasource = async (space, datasource) => {
4281
+ try {
4282
+ const client = mapiClient();
4283
+ const { data } = await client.post(`spaces/${space}/datasources`, {
4284
+ body: JSON.stringify(datasource)
4285
+ });
4286
+ return data.datasource;
4287
+ } catch (error) {
4288
+ handleAPIError("push_datasource", error, `Failed to push datasource ${datasource.name}`);
4289
+ }
4290
+ };
4291
+ const updateDatasource = async (space, datasourceId, datasource) => {
4292
+ try {
4293
+ const client = mapiClient();
4294
+ const { data } = await client.put(`spaces/${space}/datasources/${datasourceId}`, {
4295
+ body: JSON.stringify(datasource)
4296
+ });
4297
+ return data.datasource;
4298
+ } catch (error) {
4299
+ handleAPIError("update_datasource", error, `Failed to update datasource ${datasource.name}`);
4300
+ }
4301
+ };
4302
+ const upsertDatasource = async (space, datasource, existingId) => {
4303
+ if (existingId) {
4304
+ return await updateDatasource(space, existingId, datasource);
4305
+ } else {
4306
+ return await pushDatasource(space, datasource);
4307
+ }
4308
+ };
4309
+ const pushDatasourceEntry = async (space, datasourceId, entry) => {
4310
+ try {
4311
+ const client = mapiClient();
4312
+ const { data } = await client.post(`spaces/${space}/datasource_entries`, {
4313
+ body: JSON.stringify({
4314
+ datasource_entry: {
4315
+ ...entry,
4316
+ datasource_id: datasourceId
4317
+ }
4318
+ })
4319
+ });
4320
+ return data.datasource_entry;
4321
+ } catch (error) {
4322
+ handleAPIError("push_datasource", error, `Failed to push datasource entry ${entry.name}`);
4323
+ }
4324
+ };
4325
+ const updateDatasourceEntry = async (space, entryId, entry) => {
4326
+ try {
4327
+ const client = mapiClient();
4328
+ await client.put(`spaces/${space}/datasource_entries/${entryId}`, {
4329
+ body: JSON.stringify({
4330
+ datasource_entry: entry
4331
+ })
4332
+ });
4333
+ } catch (error) {
4334
+ handleAPIError("update_datasource", error, `Failed to update datasource entry ${entry.name}`);
4335
+ }
4336
+ };
4337
+ const upsertDatasourceEntry = async (space, datasourceId, entry, existingId) => {
4338
+ if (existingId) {
4339
+ await updateDatasourceEntry(space, existingId, entry);
4340
+ return void 0;
4341
+ } else {
4342
+ return await pushDatasourceEntry(space, datasourceId, entry);
4343
+ }
4344
+ };
4345
+ const readDatasourcesFiles = async (options) => {
4346
+ const { from, path, separateFiles = false, suffix, space } = options;
4347
+ const resolvedPath = resolvePath(path, `datasources/${from}`);
4348
+ try {
4349
+ await readdir(resolvedPath);
4350
+ } catch (error) {
4351
+ const message = `No local datasources found for space ${chalk.bold(from)}. To push datasources, you need to pull them first:
4352
+
4353
+ 1. Pull the datasources from your source space:
4354
+ ${chalk.cyan(`storyblok datasources pull --space ${from}`)}
4355
+
4356
+ 2. Then try pushing again:
4357
+ ${chalk.cyan(`storyblok datasources push --space ${space} --from ${from}`)}`;
4358
+ throw new FileSystemError(
4359
+ "file_not_found",
4360
+ "read",
4361
+ error,
4362
+ message
4363
+ );
4364
+ }
4365
+ if (separateFiles) {
4366
+ return await readSeparateFiles(resolvedPath, suffix);
4367
+ }
4368
+ return await readConsolidatedFiles(resolvedPath, suffix);
4369
+ };
4370
+ async function readSeparateFiles(resolvedPath, suffix) {
4371
+ const files = await readdir(resolvedPath);
4372
+ const datasources = [];
4373
+ const filteredFiles = files.filter((file) => {
4374
+ if (suffix) {
4375
+ return file.endsWith(`.${suffix}.json`);
4376
+ } else {
4377
+ return !/\.\w+\.json$/.test(file);
4378
+ }
4379
+ });
4380
+ for (const file of filteredFiles) {
4381
+ const filePath = join(resolvedPath, file);
4382
+ if (file.endsWith(".json") || file.endsWith(`${suffix}.json`)) {
4383
+ if (file === "datasources.json" || /^datasources\.\w+\.json$/.test(file)) {
4384
+ continue;
4385
+ }
4386
+ const result = await readJsonFile(filePath);
4387
+ if (result.error) {
4388
+ handleFileSystemError("read", result.error);
4389
+ continue;
4390
+ }
4391
+ datasources.push(...result.data);
4392
+ }
4393
+ }
4394
+ return {
4395
+ datasources
4396
+ };
4397
+ }
4398
+ async function readConsolidatedFiles(resolvedPath, suffix) {
4399
+ const datasourcesPath = join(resolvedPath, suffix ? `datasources.${suffix}.json` : "datasources.json");
4400
+ const datasourcesResult = await readJsonFile(datasourcesPath);
4401
+ if (datasourcesResult.error || !datasourcesResult.data.length) {
4402
+ throw new FileSystemError(
4403
+ "file_not_found",
4404
+ "read",
4405
+ datasourcesResult.error || new Error("Datasources file is empty"),
4406
+ `No datasources found in ${datasourcesPath}. Please make sure you have pulled the datasources first.`
4407
+ );
4408
+ }
4409
+ return {
4410
+ datasources: datasourcesResult.data
4411
+ };
4412
+ }
4413
+
4439
4414
  const program$2 = getProgram();
4440
4415
  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").action(async (datasourceName, options) => {
4441
4416
  konsola.title(`${commands.DATASOURCES}`, colorPalette.DATASOURCES, datasourceName ? `Pushing datasource ${datasourceName}...` : "Pushing datasources...");
@@ -4885,7 +4860,7 @@ program$1.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
4885
4860
  konsola.br();
4886
4861
  });
4887
4862
 
4888
- const version = "4.3.1";
4863
+ const version = "4.3.3";
4889
4864
  const pkg = {
4890
4865
  version: version};
4891
4866