storyblok 4.0.0-beta.3 → 4.0.0-beta.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
@@ -9,6 +9,8 @@ import { Spinner } from '@topcli/spinner';
9
9
  import { select, password, input } from '@inquirer/prompts';
10
10
  import { mkdir, writeFile, readFile as readFile$1, access, readdir } from 'node:fs/promises';
11
11
  import { join, parse, resolve } from 'node:path';
12
+ import { exec } from 'node:child_process';
13
+ import { promisify } from 'node:util';
12
14
  import { hash } from 'ohash';
13
15
  import { compile } from 'json-schema-to-typescript';
14
16
  import { readFileSync } from 'node:fs';
@@ -16,6 +18,7 @@ import { readFileSync } from 'node:fs';
16
18
  const commands = {
17
19
  LOGIN: "login",
18
20
  LOGOUT: "logout",
21
+ SIGNUP: "signup",
19
22
  USER: "user",
20
23
  COMPONENTS: "Components",
21
24
  LANGUAGES: "languages",
@@ -26,6 +29,7 @@ const colorPalette = {
26
29
  PRIMARY: "#8d60ff",
27
30
  LOGIN: "#dad4ff",
28
31
  LOGOUT: "#6d6d6d",
32
+ SIGNUP: "#b6ff6d",
29
33
  USER: "#71d300",
30
34
  COMPONENTS: "#a185ff",
31
35
  LANGUAGES: "#f5c003",
@@ -107,7 +111,11 @@ async function customFetch(url, options = {}) {
107
111
  data
108
112
  });
109
113
  }
110
- return data;
114
+ return {
115
+ ...data,
116
+ perPage: Number(response.headers.get("Per-Page")),
117
+ total: Number(response.headers.get("Total"))
118
+ };
111
119
  } catch (error) {
112
120
  if (error instanceof FetchError) {
113
121
  throw error;
@@ -315,6 +323,22 @@ class FileSystemError extends Error {
315
323
  }
316
324
  }
317
325
 
326
+ function handleVerboseError(error) {
327
+ if (error instanceof CommandError || error instanceof APIError || error instanceof FileSystemError) {
328
+ const errorDetails = "getInfo" in error ? error.getInfo() : {};
329
+ if (error instanceof CommandError) {
330
+ konsola.error(`Command Error: ${error.getInfo().message}`, errorDetails);
331
+ } else if (error instanceof APIError) {
332
+ konsola.error(`API Error: ${error.getInfo().cause}`, errorDetails);
333
+ } else if (error instanceof FileSystemError) {
334
+ konsola.error(`File System Error: ${error.getInfo().cause}`, errorDetails);
335
+ } else {
336
+ konsola.error(`Unexpected Error: ${error}`, errorDetails);
337
+ }
338
+ } else {
339
+ konsola.error(`Unexpected Error`, error);
340
+ }
341
+ }
318
342
  function handleError(error, verbose = false) {
319
343
  if (error instanceof APIError || error instanceof FileSystemError) {
320
344
  const messageStack = error.messageStack;
@@ -329,17 +353,8 @@ function handleError(error, verbose = false) {
329
353
  header: true
330
354
  });
331
355
  }
332
- if (verbose && (error instanceof CommandError || error instanceof APIError || error instanceof FileSystemError)) {
333
- const errorDetails = "getInfo" in error ? error.getInfo() : {};
334
- if (error instanceof CommandError) {
335
- konsola.error(`Command Error: ${error.getInfo().message}`, errorDetails);
336
- } else if (error instanceof APIError) {
337
- konsola.error(`API Error: ${error.getInfo().cause}`, errorDetails);
338
- } else if (error instanceof FileSystemError) {
339
- konsola.error(`File System Error: ${error.getInfo().cause}`, errorDetails);
340
- } else {
341
- konsola.error(`Unexpected Error: ${error}`, errorDetails);
342
- }
356
+ if (verbose) {
357
+ handleVerboseError(error);
343
358
  } else {
344
359
  konsola.br();
345
360
  konsola.info("For more information about the error, run the command with the `--verbose` flag");
@@ -349,6 +364,17 @@ function handleError(error, verbose = false) {
349
364
  }
350
365
  }
351
366
 
367
+ function requireAuthentication(state, verbose = false) {
368
+ if (!state.isLoggedIn || !state.password || !state.region) {
369
+ handleError(
370
+ new CommandError(`You are currently not logged in. Please run ${chalk.hex(colorPalette.PRIMARY)("storyblok login")} to authenticate, or ${chalk.hex(colorPalette.PRIMARY)("storyblok signup")} to sign up.`),
371
+ verbose
372
+ );
373
+ return false;
374
+ }
375
+ return true;
376
+ }
377
+
352
378
  const toPascalCase = (str) => {
353
379
  return str.replace(/(?:^|_)(\w)/g, (_, char) => char.toUpperCase());
354
380
  };
@@ -688,7 +714,7 @@ function session() {
688
714
  return sessionInstance;
689
715
  }
690
716
 
691
- const program$d = getProgram();
717
+ const program$e = getProgram();
692
718
  const allRegionsText = Object.values(regions).join(",");
693
719
  const loginStrategy = {
694
720
  message: "How would you like to login?",
@@ -705,12 +731,12 @@ const loginStrategy = {
705
731
  }
706
732
  ]
707
733
  };
708
- program$d.command(commands.LOGIN).description("Login to the Storyblok CLI").option("-t, --token <token>", "Token to login directly without questions, like for CI environments").option(
734
+ program$e.command(commands.LOGIN).description("Login to the Storyblok CLI").option("-t, --token <token>", "Token to login directly without questions, like for CI environments").option(
709
735
  "-r, --region <region>",
710
736
  `The region you would like to work in. Please keep in mind that the region must match the region of your space. This region flag will be used for the other cli's commands. You can use the values: ${allRegionsText}.`
711
737
  ).action(async (options) => {
712
738
  konsola.title(` ${commands.LOGIN} `, colorPalette.LOGIN);
713
- const verbose = program$d.opts().verbose;
739
+ const verbose = program$e.opts().verbose;
714
740
  const { token, region } = options;
715
741
  const { state, updateSession, persistCredentials, initializeSession } = session();
716
742
  await initializeSession();
@@ -818,10 +844,10 @@ program$d.command(commands.LOGIN).description("Login to the Storyblok CLI").opti
818
844
  konsola.br();
819
845
  });
820
846
 
821
- const program$c = getProgram();
822
- program$c.command(commands.LOGOUT).description("Logout from the Storyblok CLI").action(async () => {
847
+ const program$d = getProgram();
848
+ program$d.command(commands.LOGOUT).description("Logout from the Storyblok CLI").action(async () => {
823
849
  konsola.title(` ${commands.LOGOUT} `, colorPalette.LOGOUT);
824
- const verbose = program$c.opts().verbose;
850
+ const verbose = program$d.opts().verbose;
825
851
  try {
826
852
  const { state, initializeSession } = session();
827
853
  await initializeSession();
@@ -839,6 +865,61 @@ program$c.command(commands.LOGOUT).description("Logout from the Storyblok CLI").
839
865
  konsola.br();
840
866
  });
841
867
 
868
+ const execAsync = promisify(exec);
869
+ function buildSignupUrl() {
870
+ const baseUrl = "https://app.storyblok.com";
871
+ const utmParams = new URLSearchParams({
872
+ utm_source: "storyblok-cli",
873
+ utm_medium: "cli",
874
+ utm_campaign: "signup"
875
+ });
876
+ return `${baseUrl}/#/signup?${utmParams.toString()}`;
877
+ }
878
+ async function openSignupInBrowser(url) {
879
+ let command;
880
+ switch (process.platform) {
881
+ case "darwin":
882
+ command = `open "${url}"`;
883
+ break;
884
+ case "win32":
885
+ command = `start "" "${url}"`;
886
+ break;
887
+ default:
888
+ command = `xdg-open "${url}"`;
889
+ break;
890
+ }
891
+ try {
892
+ await execAsync(command);
893
+ } catch (error) {
894
+ throw new Error(`Failed to open browser: ${error}`);
895
+ }
896
+ }
897
+
898
+ const program$c = getProgram();
899
+ program$c.command(commands.SIGNUP).description("Sign up for Storyblok").action(async () => {
900
+ konsola.title(` ${commands.SIGNUP} `, colorPalette.SIGNUP);
901
+ const verbose = program$c.opts().verbose;
902
+ const { state, initializeSession } = session();
903
+ await initializeSession();
904
+ if (state.isLoggedIn && !state.envLogin) {
905
+ konsola.ok(`You are already logged in. If you want to signup with a different account, please logout first.`);
906
+ return;
907
+ }
908
+ try {
909
+ const signupUrl = buildSignupUrl();
910
+ konsola.info(`Opening Storyblok signup page...`);
911
+ konsola.info(`URL: ${chalk.dim(signupUrl)}`);
912
+ await openSignupInBrowser(signupUrl);
913
+ konsola.ok(`Browser opened! Please complete the signup process.`);
914
+ konsola.br();
915
+ konsola.info(`Once you've completed signup, run ${chalk.hex(colorPalette.PRIMARY)("storyblok login")} to authenticate with the CLI.`);
916
+ } catch (error) {
917
+ konsola.br();
918
+ handleError(error, verbose);
919
+ }
920
+ konsola.br();
921
+ });
922
+
842
923
  const getUser = async (token, region) => {
843
924
  try {
844
925
  const url = getStoryblokUrl(region);
@@ -868,8 +949,7 @@ program$b.command(commands.USER).description("Get the current user").action(asyn
868
949
  konsola.title(` ${commands.USER} `, colorPalette.USER);
869
950
  const { state, initializeSession } = session();
870
951
  await initializeSession();
871
- if (!state.isLoggedIn) {
872
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`));
952
+ if (!requireAuthentication(state)) {
873
953
  return;
874
954
  }
875
955
  const spinner = new Spinner({
@@ -893,67 +973,174 @@ program$b.command(commands.USER).description("Get the current user").action(asyn
893
973
  const program$a = getProgram();
894
974
  const componentsCommand = program$a.command("components").alias("comp").description(`Manage your space's block schema`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/components");
895
975
 
896
- const fetchComponents = async (space, token, region) => {
897
- try {
898
- const url = getStoryblokUrl(region);
899
- const response = await customFetch(`${url}/spaces/${space}/components`, {
900
- headers: {
901
- Authorization: token
976
+ let instance = null;
977
+ const createMapiClient = (options) => {
978
+ const baseHeaders = {
979
+ "Content-Type": "application/json",
980
+ "Authorization": options.token
981
+ };
982
+ const state = {
983
+ uuid: `mapi-client-${Math.random().toString(36).substring(2, 15)}`,
984
+ baseHeaders,
985
+ url: options.url || getStoryblokUrl(options.region),
986
+ maxRetries: options.maxRetries ?? 6,
987
+ baseDelay: options.baseDelay ?? 500,
988
+ freeze: false
989
+ };
990
+ const request = async (path, fetchOptions, attempt = 0, isRateLimitOwner = false) => {
991
+ if (state.freeze && !isRateLimitOwner) {
992
+ if (options?.verbose) {
993
+ console.log(`\u23F3 ${path} - Waiting for rate limit to be resolved`);
994
+ }
995
+ await new Promise((resolve) => {
996
+ const checkFreeze = setInterval(() => {
997
+ if (!state.freeze) {
998
+ clearInterval(checkFreeze);
999
+ resolve();
1000
+ }
1001
+ }, 50);
1002
+ });
1003
+ await delay(100 + Math.random() * 400);
1004
+ return request(path, fetchOptions, attempt);
1005
+ }
1006
+ try {
1007
+ if (options?.verbose) {
1008
+ console.log(`${state.url}/${path} - Attempt ${attempt}`);
902
1009
  }
903
- });
904
- return response.components;
1010
+ const res = await fetch(`${state.url}/${path}`, {
1011
+ headers: {
1012
+ ...state.baseHeaders,
1013
+ ...fetchOptions?.headers
1014
+ },
1015
+ ...fetchOptions
1016
+ });
1017
+ let data;
1018
+ try {
1019
+ data = await res.json();
1020
+ } catch {
1021
+ throw new FetchError("Non-JSON response", {
1022
+ status: res.status,
1023
+ statusText: res.statusText,
1024
+ data: null
1025
+ });
1026
+ }
1027
+ if (res.ok) {
1028
+ if (options?.verbose) {
1029
+ console.log(`\u2705 ${path}`);
1030
+ }
1031
+ return {
1032
+ data,
1033
+ attempt
1034
+ };
1035
+ } else {
1036
+ throw new FetchError("Request failed", {
1037
+ status: res.status,
1038
+ statusText: res.statusText,
1039
+ data
1040
+ });
1041
+ }
1042
+ } catch (error) {
1043
+ if (error instanceof FetchError) {
1044
+ if (error.response.status === 429 && attempt < state.maxRetries) {
1045
+ if (options?.verbose) {
1046
+ console.log(`\u274C ${path} - Rate limit exceeded`);
1047
+ }
1048
+ let isOwner = isRateLimitOwner;
1049
+ if (!state.freeze) {
1050
+ state.freeze = true;
1051
+ isOwner = true;
1052
+ }
1053
+ const waitTime = state.baseDelay * 2 ** attempt + Math.random() * 100;
1054
+ await delay(waitTime);
1055
+ try {
1056
+ const result = await request(path, fetchOptions, attempt + 1, isOwner);
1057
+ return result;
1058
+ } finally {
1059
+ if (isOwner && state.freeze) {
1060
+ state.freeze = false;
1061
+ }
1062
+ }
1063
+ }
1064
+ throw error;
1065
+ }
1066
+ if (state.freeze && isRateLimitOwner) {
1067
+ state.freeze = false;
1068
+ }
1069
+ throw new FetchError(error instanceof Error ? error.message : String(error), {
1070
+ status: 0,
1071
+ statusText: "Network Error",
1072
+ data: null
1073
+ });
1074
+ }
1075
+ };
1076
+ const get = async (path, fetchOptions) => {
1077
+ return request(path, fetchOptions);
1078
+ };
1079
+ const post = async (path, fetchOptions) => {
1080
+ return request(path, { ...fetchOptions, method: "POST" });
1081
+ };
1082
+ const put = async (path, fetchOptions) => {
1083
+ return request(path, { ...fetchOptions, method: "PUT" });
1084
+ };
1085
+ instance = {
1086
+ uuid: state.uuid,
1087
+ get,
1088
+ post,
1089
+ put,
1090
+ dispose: () => {
1091
+ instance = null;
1092
+ }
1093
+ };
1094
+ return instance;
1095
+ };
1096
+ function mapiClient(options) {
1097
+ if (!instance) {
1098
+ instance = createMapiClient(options ?? {});
1099
+ }
1100
+ return instance;
1101
+ }
1102
+
1103
+ const fetchComponents = async (space) => {
1104
+ try {
1105
+ const client = mapiClient();
1106
+ const { data } = await client.get(`spaces/${space}/components`, {});
1107
+ return data.components;
905
1108
  } catch (error) {
906
1109
  handleAPIError("pull_components", error);
907
1110
  }
908
1111
  };
909
- const fetchComponent = async (space, componentName, token, region) => {
1112
+ const fetchComponent = async (space, componentName) => {
910
1113
  try {
911
- const url = getStoryblokUrl(region);
912
- const response = await customFetch(`${url}/spaces/${space}/components?search=${encodeURIComponent(componentName)}`, {
913
- headers: {
914
- Authorization: token
915
- }
916
- });
917
- return response.components?.find((c) => c.name === componentName);
1114
+ const client = mapiClient();
1115
+ const { data } = await client.get(`spaces/${space}/components?search=${encodeURIComponent(componentName)}`, {});
1116
+ return data.components?.find((c) => c.name === componentName);
918
1117
  } catch (error) {
919
1118
  handleAPIError("pull_components", error, `Failed to fetch component ${componentName}`);
920
1119
  }
921
1120
  };
922
- const fetchComponentGroups = async (space, token, region) => {
1121
+ const fetchComponentGroups = async (space) => {
923
1122
  try {
924
- const url = getStoryblokUrl(region);
925
- const response = await customFetch(`${url}/spaces/${space}/component_groups`, {
926
- headers: {
927
- Authorization: token
928
- }
929
- });
930
- return response.component_groups;
1123
+ const client = mapiClient();
1124
+ const { data } = await client.get(`spaces/${space}/component_groups`);
1125
+ return data.component_groups;
931
1126
  } catch (error) {
932
1127
  handleAPIError("pull_component_groups", error);
933
1128
  }
934
1129
  };
935
- const fetchComponentPresets = async (space, token, region) => {
1130
+ const fetchComponentPresets = async (space) => {
936
1131
  try {
937
- const url = getStoryblokUrl(region);
938
- const response = await customFetch(`${url}/spaces/${space}/presets`, {
939
- headers: {
940
- Authorization: token
941
- }
942
- });
943
- return response.presets;
1132
+ const client = mapiClient();
1133
+ const { data } = await client.get(`spaces/${space}/presets`);
1134
+ return data.presets;
944
1135
  } catch (error) {
945
1136
  handleAPIError("pull_component_presets", error);
946
1137
  }
947
1138
  };
948
- const fetchComponentInternalTags = async (space, token, region) => {
1139
+ const fetchComponentInternalTags = async (space) => {
949
1140
  try {
950
- const url = getStoryblokUrl(region);
951
- const response = await customFetch(`${url}/spaces/${space}/internal_tags`, {
952
- headers: {
953
- Authorization: token
954
- }
955
- });
956
- return response.internal_tags.filter((tag) => tag.object_type === "component");
1141
+ const client = mapiClient();
1142
+ const { data } = await client.get(`spaces/${space}/internal_tags`, {});
1143
+ return data.internal_tags.filter((tag) => tag.object_type === "component");
957
1144
  } catch (error) {
958
1145
  handleAPIError("pull_component_internal_tags", error);
959
1146
  }
@@ -998,187 +1185,157 @@ const saveComponentsToFiles = async (space, spaceData, options) => {
998
1185
  }
999
1186
  };
1000
1187
 
1001
- const pushComponent = async (space, component, token, region) => {
1188
+ const pushComponent = async (space, component) => {
1002
1189
  try {
1003
- const url = getStoryblokUrl(region);
1004
- const response = await customFetch(`${url}/spaces/${space}/components`, {
1005
- method: "POST",
1006
- headers: {
1007
- Authorization: token
1008
- },
1190
+ const client = mapiClient();
1191
+ const { data } = await client.post(`spaces/${space}/components`, {
1009
1192
  body: JSON.stringify(component)
1010
1193
  });
1011
- return response.component;
1194
+ return data.component;
1012
1195
  } catch (error) {
1013
1196
  handleAPIError("push_component", error, `Failed to push component ${component.name}`);
1014
1197
  }
1015
1198
  };
1016
- const updateComponent = async (space, componentId, component, token, region) => {
1199
+ const updateComponent = async (space, componentId, component) => {
1017
1200
  try {
1018
- const url = getStoryblokUrl(region);
1019
- const response = await customFetch(`${url}/spaces/${space}/components/${componentId}`, {
1020
- method: "PUT",
1021
- headers: {
1022
- Authorization: token
1023
- },
1201
+ const client = mapiClient();
1202
+ const { data } = await client.put(`spaces/${space}/components/${componentId}`, {
1024
1203
  body: JSON.stringify(component)
1025
1204
  });
1026
- return response.component;
1205
+ return data.component;
1027
1206
  } catch (error) {
1028
1207
  handleAPIError("update_component", error, `Failed to update component ${component.name}`);
1029
1208
  }
1030
1209
  };
1031
- const upsertComponent = async (space, component, token, region) => {
1210
+ const upsertComponent = async (space, component) => {
1032
1211
  try {
1033
- return await pushComponent(space, component, token, region);
1212
+ return await pushComponent(space, component);
1034
1213
  } catch (error) {
1035
1214
  if (error instanceof APIError && error.code === 422) {
1036
1215
  const responseData = error.response?.data;
1037
1216
  if (responseData?.name?.[0] === "has already been taken") {
1038
- const existingComponent = await fetchComponent(space, component.name, token, region);
1217
+ const existingComponent = await fetchComponent(space, component.name);
1039
1218
  if (existingComponent) {
1040
- return await updateComponent(space, existingComponent.id, component, token, region);
1219
+ return await updateComponent(space, existingComponent.id, component);
1041
1220
  }
1042
1221
  }
1043
1222
  }
1044
1223
  throw error;
1045
1224
  }
1046
1225
  };
1047
- const pushComponentGroup = async (space, componentGroup, token, region) => {
1226
+ const pushComponentGroup = async (space, componentGroup) => {
1048
1227
  try {
1049
- const url = getStoryblokUrl(region);
1050
- const response = await customFetch(`${url}/spaces/${space}/component_groups`, {
1051
- method: "POST",
1052
- headers: {
1053
- Authorization: token
1054
- },
1228
+ const client = mapiClient();
1229
+ const { data } = await client.post(`spaces/${space}/component_groups`, {
1055
1230
  body: JSON.stringify(componentGroup)
1056
1231
  });
1057
- return response.component_group;
1232
+ return data.component_group;
1058
1233
  } catch (error) {
1059
1234
  handleAPIError("push_component_group", error, `Failed to push component group ${componentGroup.name}`);
1060
1235
  }
1061
1236
  };
1062
- const updateComponentGroup = async (space, groupId, componentGroup, token, region) => {
1237
+ const updateComponentGroup = async (space, groupId, componentGroup) => {
1063
1238
  try {
1064
- const url = getStoryblokUrl(region);
1065
- const response = await customFetch(`${url}/spaces/${space}/component_groups/${groupId}`, {
1066
- method: "PUT",
1067
- headers: {
1068
- Authorization: token
1069
- },
1239
+ const client = mapiClient();
1240
+ const { data } = await client.put(`spaces/${space}/component_groups/${groupId}`, {
1070
1241
  body: JSON.stringify(componentGroup)
1071
1242
  });
1072
- return response.component_group;
1243
+ return data.component_group;
1073
1244
  } catch (error) {
1074
1245
  handleAPIError("update_component_group", error, `Failed to update component group ${componentGroup.name}`);
1075
1246
  }
1076
1247
  };
1077
- const upsertComponentGroup = async (space, group, token, region) => {
1248
+ const upsertComponentGroup = async (space, group) => {
1078
1249
  try {
1079
- return await pushComponentGroup(space, group, token, region);
1250
+ return await pushComponentGroup(space, group);
1080
1251
  } catch (error) {
1081
1252
  if (error instanceof APIError && error.code === 422) {
1082
1253
  const responseData = error.response?.data;
1083
1254
  if (responseData?.name?.[0] === "has already been taken") {
1084
- const existingGroups = await fetchComponentGroups(space, token, region);
1255
+ const existingGroups = await fetchComponentGroups(space);
1085
1256
  const existingGroup = existingGroups?.find((g) => g.name === group.name);
1086
1257
  if (existingGroup) {
1087
- return await updateComponentGroup(space, existingGroup.id, group, token, region);
1258
+ return await updateComponentGroup(space, existingGroup.id, group);
1088
1259
  }
1089
1260
  }
1090
1261
  }
1091
1262
  throw error;
1092
1263
  }
1093
1264
  };
1094
- const pushComponentPreset = async (space, componentPreset, token, region) => {
1265
+ const pushComponentPreset = async (space, componentPreset) => {
1095
1266
  try {
1096
- const url = getStoryblokUrl(region);
1097
- const response = await customFetch(`${url}/spaces/${space}/presets`, {
1098
- method: "POST",
1099
- headers: {
1100
- Authorization: token
1101
- },
1267
+ const client = mapiClient();
1268
+ const { data } = await client.post(`spaces/${space}/presets`, {
1102
1269
  body: JSON.stringify(componentPreset)
1103
1270
  });
1104
- return response.preset;
1271
+ return data.preset;
1105
1272
  } catch (error) {
1106
1273
  handleAPIError("push_component_preset", error, `Failed to push component preset ${componentPreset.preset.name}`);
1107
1274
  }
1108
1275
  };
1109
- const updateComponentPreset = async (space, presetId, componentPreset, token, region) => {
1276
+ const updateComponentPreset = async (space, presetId, componentPreset) => {
1110
1277
  try {
1111
- const url = getStoryblokUrl(region);
1112
- const response = await customFetch(`${url}/spaces/${space}/presets/${presetId}`, {
1113
- method: "PUT",
1114
- headers: {
1115
- Authorization: token
1116
- },
1278
+ const client = mapiClient();
1279
+ const { data } = await client.put(`spaces/${space}/presets/${presetId}`, {
1117
1280
  body: JSON.stringify(componentPreset)
1118
1281
  });
1119
- return response.preset;
1282
+ return data.preset;
1120
1283
  } catch (error) {
1121
1284
  handleAPIError("update_component_preset", error, `Failed to update component preset ${componentPreset.preset.name}`);
1122
1285
  }
1123
1286
  };
1124
- const upsertComponentPreset = async (space, preset, token, region) => {
1287
+ const upsertComponentPreset = async (space, preset) => {
1125
1288
  try {
1126
- return await pushComponentPreset(space, { preset }, token, region);
1289
+ return await pushComponentPreset(space, { preset });
1127
1290
  } catch (error) {
1128
1291
  if (error instanceof APIError && error.code === 422) {
1129
1292
  const responseData = error.response?.data;
1130
1293
  if (responseData?.name?.[0] === "has already been taken") {
1131
- const existingPresets = await fetchComponentPresets(space, token, region);
1294
+ const existingPresets = await fetchComponentPresets(space);
1132
1295
  const existingPreset = existingPresets?.find((p) => p.name === preset.name && p.component_id === preset.component_id);
1133
1296
  if (existingPreset) {
1134
- return await updateComponentPreset(space, existingPreset.id, { preset }, token, region);
1297
+ return await updateComponentPreset(space, existingPreset.id, { preset });
1135
1298
  }
1136
1299
  }
1137
1300
  }
1138
1301
  throw error;
1139
1302
  }
1140
1303
  };
1141
- const pushComponentInternalTag = async (space, componentInternalTag, token, region) => {
1304
+ const pushComponentInternalTag = async (space, componentInternalTag) => {
1142
1305
  try {
1143
- const url = getStoryblokUrl(region);
1144
- const response = await customFetch(`${url}/spaces/${space}/internal_tags`, {
1306
+ const client = mapiClient();
1307
+ const { data } = await client.post(`spaces/${space}/internal_tags`, {
1145
1308
  method: "POST",
1146
- headers: {
1147
- Authorization: token
1148
- },
1149
1309
  body: JSON.stringify(componentInternalTag)
1150
1310
  });
1151
- return response.internal_tag;
1311
+ return data.internal_tag;
1152
1312
  } catch (error) {
1153
1313
  handleAPIError("push_component_internal_tag", error, `Failed to push component internal tag ${componentInternalTag.name}`);
1154
1314
  }
1155
1315
  };
1156
- const updateComponentInternalTag = async (space, tagId, componentInternalTag, token, region) => {
1316
+ const updateComponentInternalTag = async (space, tagId, componentInternalTag) => {
1157
1317
  try {
1158
- const url = getStoryblokUrl(region);
1159
- const response = await customFetch(`${url}/spaces/${space}/internal_tags/${tagId}`, {
1318
+ const client = mapiClient();
1319
+ const { data } = await client.put(`spaces/${space}/internal_tags/${tagId}`, {
1160
1320
  method: "PUT",
1161
- headers: {
1162
- Authorization: token
1163
- },
1164
1321
  body: JSON.stringify(componentInternalTag)
1165
1322
  });
1166
- return response.internal_tag;
1323
+ return data.internal_tag;
1167
1324
  } catch (error) {
1168
1325
  handleAPIError("update_component_internal_tag", error, `Failed to update component internal tag ${componentInternalTag.name}`);
1169
1326
  }
1170
1327
  };
1171
- const upsertComponentInternalTag = async (space, tag, token, region) => {
1328
+ const upsertComponentInternalTag = async (space, tag) => {
1172
1329
  try {
1173
- return await pushComponentInternalTag(space, tag, token, region);
1330
+ return await pushComponentInternalTag(space, tag);
1174
1331
  } catch (error) {
1175
1332
  if (error instanceof APIError && error.code === 422) {
1176
1333
  const responseData = error.response?.data;
1177
1334
  if (responseData?.name?.[0] === "has already been taken") {
1178
- const existingTags = await fetchComponentInternalTags(space, token, region);
1335
+ const existingTags = await fetchComponentInternalTags(space);
1179
1336
  const existingTag = existingTags?.find((t) => t.name === tag.name);
1180
1337
  if (existingTag) {
1181
- return await updateComponentInternalTag(space, existingTag.id, tag, token, region);
1338
+ return await updateComponentInternalTag(space, existingTag.id, tag);
1182
1339
  }
1183
1340
  }
1184
1341
  }
@@ -1198,16 +1355,18 @@ async function readJsonFile(filePath) {
1198
1355
  }
1199
1356
  }
1200
1357
  const readComponentsFiles = async (options) => {
1201
- const { from, path, separateFiles = false, suffix } = options;
1358
+ const { from, path, separateFiles = false, suffix, space } = options;
1202
1359
  const resolvedPath = resolvePath(path, `components/${from}`);
1203
1360
  try {
1204
1361
  await readdir(resolvedPath);
1205
1362
  } catch (error) {
1206
- const message = `No directory found for space "${from}". Please make sure you have pulled the components first by running:
1363
+ const message = `No local components found for space ${chalk.bold(from)}. To push components, you need to pull them first:
1207
1364
 
1208
- storyblok components pull --space ${from}
1365
+ 1. Pull the components from your source space:
1366
+ ${chalk.cyan(`storyblok components pull --space ${from}`)}
1209
1367
 
1210
- `;
1368
+ 2. Then try pushing again:
1369
+ ${chalk.cyan(`storyblok components push --space ${space} --from ${from}`)}`;
1211
1370
  throw new FileSystemError(
1212
1371
  "file_not_found",
1213
1372
  "read",
@@ -1307,8 +1466,7 @@ componentsCommand.command("pull [componentName]").option("-f, --filename <filena
1307
1466
  const { separateFiles, suffix, filename = "components" } = options;
1308
1467
  const { state, initializeSession } = session();
1309
1468
  await initializeSession();
1310
- if (!state.isLoggedIn || !state.password || !state.region) {
1311
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
1469
+ if (!requireAuthentication(state, verbose)) {
1312
1470
  return;
1313
1471
  }
1314
1472
  if (!space) {
@@ -1316,6 +1474,10 @@ componentsCommand.command("pull [componentName]").option("-f, --filename <filena
1316
1474
  return;
1317
1475
  }
1318
1476
  const { password, region } = state;
1477
+ mapiClient({
1478
+ token: password,
1479
+ region
1480
+ });
1319
1481
  const spinnerGroups = new Spinner({
1320
1482
  verbose: !isVitest
1321
1483
  });
@@ -1330,25 +1492,25 @@ componentsCommand.command("pull [componentName]").option("-f, --filename <filena
1330
1492
  });
1331
1493
  try {
1332
1494
  spinnerGroups.start(`Fetching ${chalk.hex(colorPalette.COMPONENTS)("components groups")}`);
1333
- const groups = await fetchComponentGroups(space, password, region);
1495
+ const groups = await fetchComponentGroups(space);
1334
1496
  spinnerGroups.succeed(`${chalk.hex(colorPalette.COMPONENTS)("Groups")} - Completed in ${spinnerGroups.elapsedTime.toFixed(2)}ms`);
1335
1497
  spinnerPresets.start(`Fetching ${chalk.hex(colorPalette.COMPONENTS)("components presets")}`);
1336
- const presets = await fetchComponentPresets(space, password, region);
1498
+ const presets = await fetchComponentPresets(space);
1337
1499
  spinnerPresets.succeed(`${chalk.hex(colorPalette.COMPONENTS)("Presets")} - Completed in ${spinnerPresets.elapsedTime.toFixed(2)}ms`);
1338
1500
  spinnerInternalTags.start(`Fetching ${chalk.hex(colorPalette.COMPONENTS)("components internal tags")}`);
1339
- const internalTags = await fetchComponentInternalTags(space, password, region);
1501
+ const internalTags = await fetchComponentInternalTags(space);
1340
1502
  spinnerInternalTags.succeed(`${chalk.hex(colorPalette.COMPONENTS)("Tags")} - Completed in ${spinnerInternalTags.elapsedTime.toFixed(2)}ms`);
1341
1503
  let components;
1342
1504
  spinnerComponents.start(`Fetching ${chalk.hex(colorPalette.COMPONENTS)("components")}`);
1343
1505
  if (componentName) {
1344
- const component = await fetchComponent(space, componentName, password, region);
1506
+ const component = await fetchComponent(space, componentName);
1345
1507
  if (!component) {
1346
1508
  konsola.warn(`No component found with name "${componentName}"`);
1347
1509
  return;
1348
1510
  }
1349
1511
  components = [component];
1350
1512
  } else {
1351
- components = await fetchComponents(space, password, region);
1513
+ components = await fetchComponents(space);
1352
1514
  if (!components || components.length === 0) {
1353
1515
  konsola.warn(`No components found in the space ${space}`);
1354
1516
  return;
@@ -1387,7 +1549,7 @@ componentsCommand.command("pull [componentName]").option("-f, --filename <filena
1387
1549
  }
1388
1550
  });
1389
1551
 
1390
- function findRelatedResources(components, spaceData) {
1552
+ function findRelatedResources(components, spaceData, visitedComponents = /* @__PURE__ */ new Set()) {
1391
1553
  const componentIds = new Set(components.map((c) => c.id));
1392
1554
  const whitelistedComponentNames = /* @__PURE__ */ new Set();
1393
1555
  const componentGroupUuids = /* @__PURE__ */ new Set();
@@ -1472,7 +1634,11 @@ function findRelatedResources(components, spaceData) {
1472
1634
  whitelistedComponents: []
1473
1635
  };
1474
1636
  if (whitelistedComponents.length > 0) {
1475
- additionalResources = findRelatedResources(whitelistedComponents, spaceData);
1637
+ const newComponents = whitelistedComponents.filter((component) => !visitedComponents.has(component.name));
1638
+ if (newComponents.length > 0) {
1639
+ components.forEach((component) => visitedComponents.add(component.name));
1640
+ additionalResources = findRelatedResources(newComponents, spaceData, visitedComponents);
1641
+ }
1476
1642
  }
1477
1643
  const result = {
1478
1644
  groups: Array.from(/* @__PURE__ */ new Set([...Array.from(relatedGroups), ...additionalResources.groups])),
@@ -1519,7 +1685,7 @@ function filterSpaceDataByComponent(spaceData, componentName) {
1519
1685
  internalTags: relatedResources.internalTags
1520
1686
  };
1521
1687
  }
1522
- async function handleTags(space, password, region, spaceData, skipIds) {
1688
+ async function handleTags(space, spaceData, skipIds) {
1523
1689
  const results = {
1524
1690
  successful: [],
1525
1691
  failed: [],
@@ -1532,7 +1698,7 @@ async function handleTags(space, password, region, spaceData, skipIds) {
1532
1698
  });
1533
1699
  consolidatedSpinner.start("Upserting tags...");
1534
1700
  try {
1535
- const updatedTag = await upsertComponentInternalTag(space, tag, password, region);
1701
+ const updatedTag = await upsertComponentInternalTag(space, tag);
1536
1702
  if (updatedTag) {
1537
1703
  results.idMap.set(tag.id, updatedTag.id);
1538
1704
  results.successful.push(tag.name);
@@ -1545,7 +1711,7 @@ async function handleTags(space, password, region, spaceData, skipIds) {
1545
1711
  }));
1546
1712
  return results;
1547
1713
  }
1548
- async function handleComponentGroups(space, password, region, spaceData, skipUuids) {
1714
+ async function handleComponentGroups(space, spaceData, skipUuids) {
1549
1715
  const results = {
1550
1716
  successful: [],
1551
1717
  failed: [],
@@ -1562,7 +1728,7 @@ async function handleComponentGroups(space, password, region, spaceData, skipUui
1562
1728
  });
1563
1729
  spinner.start(`Upserting root group ${group.name}...`);
1564
1730
  try {
1565
- const updatedGroup = await upsertComponentGroup(space, group, password, region);
1731
+ const updatedGroup = await upsertComponentGroup(space, group);
1566
1732
  if (updatedGroup) {
1567
1733
  results.uuidMap.set(group.uuid, updatedGroup.uuid);
1568
1734
  results.idMap.set(group.id, updatedGroup.id);
@@ -1598,7 +1764,7 @@ async function handleComponentGroups(space, password, region, spaceData, skipUui
1598
1764
  parent_uuid: newParentUuid,
1599
1765
  parent_id: newParentId
1600
1766
  };
1601
- const updatedGroup = await upsertComponentGroup(space, groupToUpdate, password, region);
1767
+ const updatedGroup = await upsertComponentGroup(space, groupToUpdate);
1602
1768
  if (updatedGroup) {
1603
1769
  results.uuidMap.set(group.uuid, updatedGroup.uuid);
1604
1770
  results.idMap.set(group.id, updatedGroup.id);
@@ -1693,7 +1859,7 @@ function getGroupHierarchy(group, allGroups) {
1693
1859
  }
1694
1860
  return hierarchy;
1695
1861
  }
1696
- async function handleWhitelists(space, password, region, spaceData) {
1862
+ async function handleWhitelists(space, spaceData) {
1697
1863
  const results = {
1698
1864
  successful: [],
1699
1865
  failed: [],
@@ -1723,7 +1889,7 @@ async function handleWhitelists(space, password, region, spaceData) {
1723
1889
  verbose: !isVitest
1724
1890
  });
1725
1891
  spinner.start("Processing whitelist tags...");
1726
- const tagResults = await handleTags(space, password, region, whitelistTags);
1892
+ const tagResults = await handleTags(space, whitelistTags);
1727
1893
  results.successful.push(...tagResults.successful);
1728
1894
  results.failed.push(...tagResults.failed);
1729
1895
  tagResults.idMap.forEach((newId, oldId) => {
@@ -1744,7 +1910,7 @@ async function handleWhitelists(space, password, region, spaceData) {
1744
1910
  verbose: !isVitest
1745
1911
  });
1746
1912
  spinner.start("Processing whitelist groups...");
1747
- const groupResults = await handleComponentGroups(space, password, region, whitelistGroups);
1913
+ const groupResults = await handleComponentGroups(space, whitelistGroups);
1748
1914
  results.successful.push(...groupResults.successful);
1749
1915
  results.failed.push(...groupResults.failed);
1750
1916
  groupResults.uuidMap.forEach((newUuid, oldUuid) => {
@@ -1766,11 +1932,6 @@ async function handleWhitelists(space, password, region, spaceData) {
1766
1932
  return;
1767
1933
  }
1768
1934
  if (visited.has(componentName)) {
1769
- failedComponents.add(componentName);
1770
- results.failed.push({
1771
- name: componentName,
1772
- error: new Error(`Circular dependency detected for component ${componentName}`)
1773
- });
1774
1935
  return;
1775
1936
  }
1776
1937
  visited.add(componentName);
@@ -1834,7 +1995,7 @@ async function handleWhitelists(space, password, region, spaceData) {
1834
1995
  results.componentNameMap
1835
1996
  );
1836
1997
  }
1837
- const updatedComponent = await upsertComponent(space, componentToUpdate, password, region);
1998
+ const updatedComponent = await upsertComponent(space, componentToUpdate);
1838
1999
  if (updatedComponent) {
1839
2000
  results.successful.push(component.name);
1840
2001
  results.componentNameMap.set(component.name, updatedComponent.name);
@@ -1858,8 +2019,6 @@ async function handleWhitelists(space, password, region, spaceData) {
1858
2019
  async function handleComponents(options) {
1859
2020
  const {
1860
2021
  space,
1861
- password,
1862
- region,
1863
2022
  spaceData: { components, internalTags, presets },
1864
2023
  groupsUuidMap,
1865
2024
  tagsIdMaps,
@@ -1938,7 +2097,7 @@ async function handleComponents(options) {
1938
2097
  componentNameMap
1939
2098
  );
1940
2099
  }
1941
- const updatedComponent = await upsertComponent(space, componentToUpdate, password, region);
2100
+ const updatedComponent = await upsertComponent(space, componentToUpdate);
1942
2101
  if (updatedComponent) {
1943
2102
  results.successful.push(component.name);
1944
2103
  results.componentIdMap.set(component.id, updatedComponent.id);
@@ -1972,7 +2131,7 @@ async function handleComponents(options) {
1972
2131
  tagsIdMaps,
1973
2132
  componentNameMap
1974
2133
  );
1975
- await upsertComponent(space, componentToUpdate, password, region);
2134
+ await upsertComponent(space, componentToUpdate);
1976
2135
  spinner.succeed(`Component whitelists-> ${chalk.hex(colorPalette.COMPONENTS)(component.name)} - Completed in ${spinner.elapsedTime.toFixed(2)}ms`);
1977
2136
  } catch (error) {
1978
2137
  spinner.failed(`Component whitelists-> ${chalk.hex(colorPalette.COMPONENTS)(component.name)} - Failed`);
@@ -1998,7 +2157,7 @@ async function handleComponents(options) {
1998
2157
  preset: preset.preset,
1999
2158
  component_id: newComponentId
2000
2159
  };
2001
- await upsertComponentPreset(space, presetToUpdate, password, region);
2160
+ await upsertComponentPreset(space, presetToUpdate);
2002
2161
  presetSpinner.succeed(`Preset-> ${chalk.hex(colorPalette.COMPONENTS)(preset.name)} - Completed in ${presetSpinner.elapsedTime.toFixed(2)}ms`);
2003
2162
  } catch (error) {
2004
2163
  presetSpinner.failed(`Preset-> ${chalk.hex(colorPalette.COMPONENTS)(preset.name)} - Failed`);
@@ -2018,8 +2177,7 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2018
2177
  const { from, filter } = options;
2019
2178
  const { state, initializeSession } = session();
2020
2179
  await initializeSession();
2021
- if (!state.isLoggedIn || !state.password || !state.region) {
2022
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
2180
+ if (!requireAuthentication(state, verbose)) {
2023
2181
  return;
2024
2182
  }
2025
2183
  if (!space) {
@@ -2029,11 +2187,18 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2029
2187
  if (!from) {
2030
2188
  options.from = space;
2031
2189
  }
2190
+ konsola.info(`Attempting to push components ${chalk.bold("from")} space ${chalk.hex(colorPalette.COMPONENTS)(options.from)} ${chalk.bold("to")} ${chalk.hex(colorPalette.COMPONENTS)(space)}`);
2191
+ konsola.br();
2032
2192
  const { password, region } = state;
2193
+ mapiClient({
2194
+ token: password,
2195
+ region
2196
+ });
2033
2197
  try {
2034
2198
  let spaceData = await readComponentsFiles({
2035
2199
  ...options,
2036
- path
2200
+ path,
2201
+ space
2037
2202
  });
2038
2203
  if (componentName) {
2039
2204
  spaceData = filterSpaceDataByComponent(spaceData, componentName);
@@ -2057,13 +2222,13 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2057
2222
  successful: [],
2058
2223
  failed: []
2059
2224
  };
2060
- const whitelistResults = await handleWhitelists(space, password, region, spaceData);
2225
+ const whitelistResults = await handleWhitelists(space, spaceData);
2061
2226
  results.successful.push(...whitelistResults.successful);
2062
2227
  results.failed.push(...whitelistResults.failed);
2063
- const tagsResults = await handleTags(space, password, region, spaceData.internalTags, whitelistResults.processedTagIds);
2228
+ const tagsResults = await handleTags(space, spaceData.internalTags, whitelistResults.processedTagIds);
2064
2229
  results.successful.push(...tagsResults.successful);
2065
2230
  results.failed.push(...tagsResults.failed);
2066
- const groupsResults = await handleComponentGroups(space, password, region, spaceData.groups, whitelistResults.processedGroupUuids);
2231
+ const groupsResults = await handleComponentGroups(space, spaceData.groups, whitelistResults.processedGroupUuids);
2067
2232
  results.successful.push(...groupsResults.successful);
2068
2233
  results.failed.push(...groupsResults.failed);
2069
2234
  const remainingComponents = spaceData.components.filter(
@@ -2071,8 +2236,6 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2071
2236
  );
2072
2237
  const componentsResults = await handleComponents({
2073
2238
  space,
2074
- password,
2075
- region,
2076
2239
  spaceData: {
2077
2240
  ...spaceData,
2078
2241
  components: remainingComponents
@@ -2138,20 +2301,20 @@ languagesCommand.command("pull").description(`Download your space's languages sc
2138
2301
  const { filename = "languages", suffix = options.space } = options;
2139
2302
  const { state, initializeSession } = session();
2140
2303
  await initializeSession();
2141
- if (!state.isLoggedIn || !state.password || !state.region) {
2142
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
2304
+ if (!requireAuthentication(state, verbose)) {
2143
2305
  return;
2144
2306
  }
2145
2307
  if (!space) {
2146
2308
  handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
2147
2309
  return;
2148
2310
  }
2311
+ const { password, region } = state;
2149
2312
  const spinner = new Spinner({
2150
2313
  verbose: !isVitest
2151
2314
  });
2152
2315
  try {
2153
2316
  spinner.start(`Fetching ${chalk.hex(colorPalette.LANGUAGES)("languages")}`);
2154
- const internationalization = await fetchLanguages(space, state.password, state.region);
2317
+ const internationalization = await fetchLanguages(space, password, region);
2155
2318
  if (!internationalization || internationalization.languages?.length === 0) {
2156
2319
  spinner.failed();
2157
2320
  konsola.warn(`No languages found in the space ${space}`, true);
@@ -2215,8 +2378,7 @@ migrationsCommand.command("generate [componentName]").description("Generate a mi
2215
2378
  }
2216
2379
  const { state, initializeSession } = session();
2217
2380
  await initializeSession();
2218
- if (!state.isLoggedIn || !state.password || !state.region) {
2219
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
2381
+ if (!requireAuthentication(state, verbose)) {
2220
2382
  return;
2221
2383
  }
2222
2384
  if (!space) {
@@ -2224,11 +2386,15 @@ migrationsCommand.command("generate [componentName]").description("Generate a mi
2224
2386
  return;
2225
2387
  }
2226
2388
  const { password, region } = state;
2389
+ mapiClient({
2390
+ token: password,
2391
+ region
2392
+ });
2227
2393
  const spinner = new Spinner({
2228
2394
  verbose: !isVitest
2229
2395
  }).start(`Generating migration for component ${componentName}...`);
2230
2396
  try {
2231
- const component = await fetchComponent(space, componentName, password, region);
2397
+ const component = await fetchComponent(space, componentName);
2232
2398
  if (!component) {
2233
2399
  spinner.failed(`Failed to fetch component ${componentName}. Make sure the component exists in your space.`);
2234
2400
  handleError(new CommandError(`No component found with name "${componentName}"`), verbose);
@@ -2248,16 +2414,28 @@ migrationsCommand.command("generate [componentName]").description("Generate a mi
2248
2414
  const fetchStories = async (space, token, region, params) => {
2249
2415
  try {
2250
2416
  const url = getStoryblokUrl(region);
2251
- const { filter_query, ...restParams } = params || {};
2252
- const regularParams = new URLSearchParams(objectToStringParams(restParams)).toString();
2253
- const queryString = filter_query ? `${regularParams ? `${regularParams}&` : ""}${filter_query}` : regularParams;
2254
- const endpoint = `${url}/spaces/${space}/stories${queryString ? `?${queryString}` : ""}`;
2255
- const response = await customFetch(endpoint, {
2256
- headers: {
2257
- Authorization: token
2258
- }
2259
- });
2260
- return response.stories;
2417
+ const allStories = [];
2418
+ let currentPage = 1;
2419
+ let hasMorePages = true;
2420
+ while (hasMorePages) {
2421
+ const { filter_query, ...restParams } = params || {};
2422
+ const regularParams = new URLSearchParams({
2423
+ ...objectToStringParams({ ...restParams, per_page: 100 }),
2424
+ ...currentPage > 1 && { page: currentPage.toString() }
2425
+ }).toString();
2426
+ const queryString = filter_query ? `${regularParams ? `${regularParams}&` : ""}${filter_query}` : regularParams;
2427
+ const endpoint = `${url}/spaces/${space}/stories${queryString ? `?${queryString}` : ""}`;
2428
+ const response = await customFetch(endpoint, {
2429
+ headers: {
2430
+ Authorization: token
2431
+ }
2432
+ });
2433
+ allStories.push(...response.stories);
2434
+ const totalPages = Math.ceil(response.total / response.perPage);
2435
+ hasMorePages = currentPage < totalPages;
2436
+ currentPage++;
2437
+ }
2438
+ return allStories;
2261
2439
  } catch (error) {
2262
2440
  handleAPIError("pull_stories", error);
2263
2441
  }
@@ -2615,8 +2793,7 @@ migrationsCommand.command("run [componentName]").description("Run migrations").o
2615
2793
  const { space, path } = migrationsCommand.opts();
2616
2794
  const { state, initializeSession } = session();
2617
2795
  await initializeSession();
2618
- if (!state.isLoggedIn || !state.password || !state.region) {
2619
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
2796
+ if (!requireAuthentication(state, verbose)) {
2620
2797
  return;
2621
2798
  }
2622
2799
  if (!space) {
@@ -2772,8 +2949,7 @@ migrationsCommand.command("rollback [migrationFile]").description("Rollback a mi
2772
2949
  const { space, path } = migrationsCommand.opts();
2773
2950
  const { state, initializeSession } = session();
2774
2951
  await initializeSession();
2775
- if (!state.isLoggedIn || !state.password || !state.region) {
2776
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
2952
+ if (!requireAuthentication(state, verbose)) {
2777
2953
  return;
2778
2954
  }
2779
2955
  if (!space) {
@@ -3386,7 +3562,7 @@ const loadCustomFieldsParser = async (path) => {
3386
3562
  return customFieldsParser.default;
3387
3563
  } catch (error) {
3388
3564
  handleError(error);
3389
- return null;
3565
+ return void 0;
3390
3566
  }
3391
3567
  };
3392
3568
  async function loadCompilerOptions(path) {
@@ -3427,6 +3603,9 @@ const generateTypes = async (spaceData, options = {
3427
3603
  if (property.type && Array.from(storyblokSchemas.keys()).includes(property.type)) {
3428
3604
  storyblokPropertyTypes.add(property.type);
3429
3605
  }
3606
+ if (property.tsType && property.tsType.includes(STORY_TYPE)) {
3607
+ storyblokPropertyTypes.add(STORY_TYPE);
3608
+ }
3430
3609
  });
3431
3610
  }
3432
3611
  const componentSchema = {
@@ -3456,6 +3635,11 @@ const generateTypes = async (spaceData, options = {
3456
3635
  });
3457
3636
  }));
3458
3637
  const imports = [];
3638
+ const needsISbStoryData = storyblokPropertyTypes.has(STORY_TYPE);
3639
+ if (needsISbStoryData) {
3640
+ imports.push(`import type { ${STORY_TYPE} } from '@storyblok/js';`);
3641
+ storyblokPropertyTypes.delete(STORY_TYPE);
3642
+ }
3459
3643
  if (storyblokPropertyTypes.size > 0) {
3460
3644
  const typeImports = Array.from(storyblokPropertyTypes).map((type) => {
3461
3645
  const pascalType = toPascalCase(type);
@@ -3488,6 +3672,7 @@ const generateStoryblokTypes = async (options = {}) => {
3488
3672
  const typeDefs = [
3489
3673
  "// This file was generated by the Storyblok CLI.",
3490
3674
  "// DO NOT MODIFY THIS FILE BY HAND.",
3675
+ `import type { ${STORY_TYPE} } from '@storyblok/js';`,
3491
3676
  storyblokTypesContent
3492
3677
  ].join("\n");
3493
3678
  const resolvedPath = path ? resolve(process.cwd(), path, "types") : resolvePath(path, "types");
@@ -3506,8 +3691,7 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
3506
3691
  const { space, path } = typesCommand.opts();
3507
3692
  const { state, initializeSession } = session();
3508
3693
  await initializeSession();
3509
- if (!state.isLoggedIn || !state.password || !state.region) {
3510
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
3694
+ if (!requireAuthentication(state, verbose)) {
3511
3695
  return;
3512
3696
  }
3513
3697
  if (!space) {
@@ -3542,7 +3726,7 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
3542
3726
  }
3543
3727
  });
3544
3728
 
3545
- const version = "4.0.0-beta.3";
3729
+ const version = "4.0.0-beta.5";
3546
3730
  const pkg = {
3547
3731
  version: version};
3548
3732