storyblok 4.0.0-beta.4 → 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",
@@ -360,6 +364,17 @@ function handleError(error, verbose = false) {
360
364
  }
361
365
  }
362
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
+
363
378
  const toPascalCase = (str) => {
364
379
  return str.replace(/(?:^|_)(\w)/g, (_, char) => char.toUpperCase());
365
380
  };
@@ -699,7 +714,7 @@ function session() {
699
714
  return sessionInstance;
700
715
  }
701
716
 
702
- const program$d = getProgram();
717
+ const program$e = getProgram();
703
718
  const allRegionsText = Object.values(regions).join(",");
704
719
  const loginStrategy = {
705
720
  message: "How would you like to login?",
@@ -716,12 +731,12 @@ const loginStrategy = {
716
731
  }
717
732
  ]
718
733
  };
719
- 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(
720
735
  "-r, --region <region>",
721
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}.`
722
737
  ).action(async (options) => {
723
738
  konsola.title(` ${commands.LOGIN} `, colorPalette.LOGIN);
724
- const verbose = program$d.opts().verbose;
739
+ const verbose = program$e.opts().verbose;
725
740
  const { token, region } = options;
726
741
  const { state, updateSession, persistCredentials, initializeSession } = session();
727
742
  await initializeSession();
@@ -829,10 +844,10 @@ program$d.command(commands.LOGIN).description("Login to the Storyblok CLI").opti
829
844
  konsola.br();
830
845
  });
831
846
 
832
- const program$c = getProgram();
833
- 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 () => {
834
849
  konsola.title(` ${commands.LOGOUT} `, colorPalette.LOGOUT);
835
- const verbose = program$c.opts().verbose;
850
+ const verbose = program$d.opts().verbose;
836
851
  try {
837
852
  const { state, initializeSession } = session();
838
853
  await initializeSession();
@@ -850,6 +865,61 @@ program$c.command(commands.LOGOUT).description("Logout from the Storyblok CLI").
850
865
  konsola.br();
851
866
  });
852
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
+
853
923
  const getUser = async (token, region) => {
854
924
  try {
855
925
  const url = getStoryblokUrl(region);
@@ -879,8 +949,7 @@ program$b.command(commands.USER).description("Get the current user").action(asyn
879
949
  konsola.title(` ${commands.USER} `, colorPalette.USER);
880
950
  const { state, initializeSession } = session();
881
951
  await initializeSession();
882
- if (!state.isLoggedIn) {
883
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`));
952
+ if (!requireAuthentication(state)) {
884
953
  return;
885
954
  }
886
955
  const spinner = new Spinner({
@@ -904,67 +973,174 @@ program$b.command(commands.USER).description("Get the current user").action(asyn
904
973
  const program$a = getProgram();
905
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");
906
975
 
907
- const fetchComponents = async (space, token, region) => {
908
- try {
909
- const url = getStoryblokUrl(region);
910
- const response = await customFetch(`${url}/spaces/${space}/components`, {
911
- headers: {
912
- 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}`);
913
1009
  }
914
- });
915
- 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;
916
1108
  } catch (error) {
917
1109
  handleAPIError("pull_components", error);
918
1110
  }
919
1111
  };
920
- const fetchComponent = async (space, componentName, token, region) => {
1112
+ const fetchComponent = async (space, componentName) => {
921
1113
  try {
922
- const url = getStoryblokUrl(region);
923
- const response = await customFetch(`${url}/spaces/${space}/components?search=${encodeURIComponent(componentName)}`, {
924
- headers: {
925
- Authorization: token
926
- }
927
- });
928
- 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);
929
1117
  } catch (error) {
930
1118
  handleAPIError("pull_components", error, `Failed to fetch component ${componentName}`);
931
1119
  }
932
1120
  };
933
- const fetchComponentGroups = async (space, token, region) => {
1121
+ const fetchComponentGroups = async (space) => {
934
1122
  try {
935
- const url = getStoryblokUrl(region);
936
- const response = await customFetch(`${url}/spaces/${space}/component_groups`, {
937
- headers: {
938
- Authorization: token
939
- }
940
- });
941
- return response.component_groups;
1123
+ const client = mapiClient();
1124
+ const { data } = await client.get(`spaces/${space}/component_groups`);
1125
+ return data.component_groups;
942
1126
  } catch (error) {
943
1127
  handleAPIError("pull_component_groups", error);
944
1128
  }
945
1129
  };
946
- const fetchComponentPresets = async (space, token, region) => {
1130
+ const fetchComponentPresets = async (space) => {
947
1131
  try {
948
- const url = getStoryblokUrl(region);
949
- const response = await customFetch(`${url}/spaces/${space}/presets`, {
950
- headers: {
951
- Authorization: token
952
- }
953
- });
954
- return response.presets;
1132
+ const client = mapiClient();
1133
+ const { data } = await client.get(`spaces/${space}/presets`);
1134
+ return data.presets;
955
1135
  } catch (error) {
956
1136
  handleAPIError("pull_component_presets", error);
957
1137
  }
958
1138
  };
959
- const fetchComponentInternalTags = async (space, token, region) => {
1139
+ const fetchComponentInternalTags = async (space) => {
960
1140
  try {
961
- const url = getStoryblokUrl(region);
962
- const response = await customFetch(`${url}/spaces/${space}/internal_tags`, {
963
- headers: {
964
- Authorization: token
965
- }
966
- });
967
- 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");
968
1144
  } catch (error) {
969
1145
  handleAPIError("pull_component_internal_tags", error);
970
1146
  }
@@ -1009,187 +1185,157 @@ const saveComponentsToFiles = async (space, spaceData, options) => {
1009
1185
  }
1010
1186
  };
1011
1187
 
1012
- const pushComponent = async (space, component, token, region) => {
1188
+ const pushComponent = async (space, component) => {
1013
1189
  try {
1014
- const url = getStoryblokUrl(region);
1015
- const response = await customFetch(`${url}/spaces/${space}/components`, {
1016
- method: "POST",
1017
- headers: {
1018
- Authorization: token
1019
- },
1190
+ const client = mapiClient();
1191
+ const { data } = await client.post(`spaces/${space}/components`, {
1020
1192
  body: JSON.stringify(component)
1021
1193
  });
1022
- return response.component;
1194
+ return data.component;
1023
1195
  } catch (error) {
1024
1196
  handleAPIError("push_component", error, `Failed to push component ${component.name}`);
1025
1197
  }
1026
1198
  };
1027
- const updateComponent = async (space, componentId, component, token, region) => {
1199
+ const updateComponent = async (space, componentId, component) => {
1028
1200
  try {
1029
- const url = getStoryblokUrl(region);
1030
- const response = await customFetch(`${url}/spaces/${space}/components/${componentId}`, {
1031
- method: "PUT",
1032
- headers: {
1033
- Authorization: token
1034
- },
1201
+ const client = mapiClient();
1202
+ const { data } = await client.put(`spaces/${space}/components/${componentId}`, {
1035
1203
  body: JSON.stringify(component)
1036
1204
  });
1037
- return response.component;
1205
+ return data.component;
1038
1206
  } catch (error) {
1039
1207
  handleAPIError("update_component", error, `Failed to update component ${component.name}`);
1040
1208
  }
1041
1209
  };
1042
- const upsertComponent = async (space, component, token, region) => {
1210
+ const upsertComponent = async (space, component) => {
1043
1211
  try {
1044
- return await pushComponent(space, component, token, region);
1212
+ return await pushComponent(space, component);
1045
1213
  } catch (error) {
1046
1214
  if (error instanceof APIError && error.code === 422) {
1047
1215
  const responseData = error.response?.data;
1048
1216
  if (responseData?.name?.[0] === "has already been taken") {
1049
- const existingComponent = await fetchComponent(space, component.name, token, region);
1217
+ const existingComponent = await fetchComponent(space, component.name);
1050
1218
  if (existingComponent) {
1051
- return await updateComponent(space, existingComponent.id, component, token, region);
1219
+ return await updateComponent(space, existingComponent.id, component);
1052
1220
  }
1053
1221
  }
1054
1222
  }
1055
1223
  throw error;
1056
1224
  }
1057
1225
  };
1058
- const pushComponentGroup = async (space, componentGroup, token, region) => {
1226
+ const pushComponentGroup = async (space, componentGroup) => {
1059
1227
  try {
1060
- const url = getStoryblokUrl(region);
1061
- const response = await customFetch(`${url}/spaces/${space}/component_groups`, {
1062
- method: "POST",
1063
- headers: {
1064
- Authorization: token
1065
- },
1228
+ const client = mapiClient();
1229
+ const { data } = await client.post(`spaces/${space}/component_groups`, {
1066
1230
  body: JSON.stringify(componentGroup)
1067
1231
  });
1068
- return response.component_group;
1232
+ return data.component_group;
1069
1233
  } catch (error) {
1070
1234
  handleAPIError("push_component_group", error, `Failed to push component group ${componentGroup.name}`);
1071
1235
  }
1072
1236
  };
1073
- const updateComponentGroup = async (space, groupId, componentGroup, token, region) => {
1237
+ const updateComponentGroup = async (space, groupId, componentGroup) => {
1074
1238
  try {
1075
- const url = getStoryblokUrl(region);
1076
- const response = await customFetch(`${url}/spaces/${space}/component_groups/${groupId}`, {
1077
- method: "PUT",
1078
- headers: {
1079
- Authorization: token
1080
- },
1239
+ const client = mapiClient();
1240
+ const { data } = await client.put(`spaces/${space}/component_groups/${groupId}`, {
1081
1241
  body: JSON.stringify(componentGroup)
1082
1242
  });
1083
- return response.component_group;
1243
+ return data.component_group;
1084
1244
  } catch (error) {
1085
1245
  handleAPIError("update_component_group", error, `Failed to update component group ${componentGroup.name}`);
1086
1246
  }
1087
1247
  };
1088
- const upsertComponentGroup = async (space, group, token, region) => {
1248
+ const upsertComponentGroup = async (space, group) => {
1089
1249
  try {
1090
- return await pushComponentGroup(space, group, token, region);
1250
+ return await pushComponentGroup(space, group);
1091
1251
  } catch (error) {
1092
1252
  if (error instanceof APIError && error.code === 422) {
1093
1253
  const responseData = error.response?.data;
1094
1254
  if (responseData?.name?.[0] === "has already been taken") {
1095
- const existingGroups = await fetchComponentGroups(space, token, region);
1255
+ const existingGroups = await fetchComponentGroups(space);
1096
1256
  const existingGroup = existingGroups?.find((g) => g.name === group.name);
1097
1257
  if (existingGroup) {
1098
- return await updateComponentGroup(space, existingGroup.id, group, token, region);
1258
+ return await updateComponentGroup(space, existingGroup.id, group);
1099
1259
  }
1100
1260
  }
1101
1261
  }
1102
1262
  throw error;
1103
1263
  }
1104
1264
  };
1105
- const pushComponentPreset = async (space, componentPreset, token, region) => {
1265
+ const pushComponentPreset = async (space, componentPreset) => {
1106
1266
  try {
1107
- const url = getStoryblokUrl(region);
1108
- const response = await customFetch(`${url}/spaces/${space}/presets`, {
1109
- method: "POST",
1110
- headers: {
1111
- Authorization: token
1112
- },
1267
+ const client = mapiClient();
1268
+ const { data } = await client.post(`spaces/${space}/presets`, {
1113
1269
  body: JSON.stringify(componentPreset)
1114
1270
  });
1115
- return response.preset;
1271
+ return data.preset;
1116
1272
  } catch (error) {
1117
1273
  handleAPIError("push_component_preset", error, `Failed to push component preset ${componentPreset.preset.name}`);
1118
1274
  }
1119
1275
  };
1120
- const updateComponentPreset = async (space, presetId, componentPreset, token, region) => {
1276
+ const updateComponentPreset = async (space, presetId, componentPreset) => {
1121
1277
  try {
1122
- const url = getStoryblokUrl(region);
1123
- const response = await customFetch(`${url}/spaces/${space}/presets/${presetId}`, {
1124
- method: "PUT",
1125
- headers: {
1126
- Authorization: token
1127
- },
1278
+ const client = mapiClient();
1279
+ const { data } = await client.put(`spaces/${space}/presets/${presetId}`, {
1128
1280
  body: JSON.stringify(componentPreset)
1129
1281
  });
1130
- return response.preset;
1282
+ return data.preset;
1131
1283
  } catch (error) {
1132
1284
  handleAPIError("update_component_preset", error, `Failed to update component preset ${componentPreset.preset.name}`);
1133
1285
  }
1134
1286
  };
1135
- const upsertComponentPreset = async (space, preset, token, region) => {
1287
+ const upsertComponentPreset = async (space, preset) => {
1136
1288
  try {
1137
- return await pushComponentPreset(space, { preset }, token, region);
1289
+ return await pushComponentPreset(space, { preset });
1138
1290
  } catch (error) {
1139
1291
  if (error instanceof APIError && error.code === 422) {
1140
1292
  const responseData = error.response?.data;
1141
1293
  if (responseData?.name?.[0] === "has already been taken") {
1142
- const existingPresets = await fetchComponentPresets(space, token, region);
1294
+ const existingPresets = await fetchComponentPresets(space);
1143
1295
  const existingPreset = existingPresets?.find((p) => p.name === preset.name && p.component_id === preset.component_id);
1144
1296
  if (existingPreset) {
1145
- return await updateComponentPreset(space, existingPreset.id, { preset }, token, region);
1297
+ return await updateComponentPreset(space, existingPreset.id, { preset });
1146
1298
  }
1147
1299
  }
1148
1300
  }
1149
1301
  throw error;
1150
1302
  }
1151
1303
  };
1152
- const pushComponentInternalTag = async (space, componentInternalTag, token, region) => {
1304
+ const pushComponentInternalTag = async (space, componentInternalTag) => {
1153
1305
  try {
1154
- const url = getStoryblokUrl(region);
1155
- const response = await customFetch(`${url}/spaces/${space}/internal_tags`, {
1306
+ const client = mapiClient();
1307
+ const { data } = await client.post(`spaces/${space}/internal_tags`, {
1156
1308
  method: "POST",
1157
- headers: {
1158
- Authorization: token
1159
- },
1160
1309
  body: JSON.stringify(componentInternalTag)
1161
1310
  });
1162
- return response.internal_tag;
1311
+ return data.internal_tag;
1163
1312
  } catch (error) {
1164
1313
  handleAPIError("push_component_internal_tag", error, `Failed to push component internal tag ${componentInternalTag.name}`);
1165
1314
  }
1166
1315
  };
1167
- const updateComponentInternalTag = async (space, tagId, componentInternalTag, token, region) => {
1316
+ const updateComponentInternalTag = async (space, tagId, componentInternalTag) => {
1168
1317
  try {
1169
- const url = getStoryblokUrl(region);
1170
- 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}`, {
1171
1320
  method: "PUT",
1172
- headers: {
1173
- Authorization: token
1174
- },
1175
1321
  body: JSON.stringify(componentInternalTag)
1176
1322
  });
1177
- return response.internal_tag;
1323
+ return data.internal_tag;
1178
1324
  } catch (error) {
1179
1325
  handleAPIError("update_component_internal_tag", error, `Failed to update component internal tag ${componentInternalTag.name}`);
1180
1326
  }
1181
1327
  };
1182
- const upsertComponentInternalTag = async (space, tag, token, region) => {
1328
+ const upsertComponentInternalTag = async (space, tag) => {
1183
1329
  try {
1184
- return await pushComponentInternalTag(space, tag, token, region);
1330
+ return await pushComponentInternalTag(space, tag);
1185
1331
  } catch (error) {
1186
1332
  if (error instanceof APIError && error.code === 422) {
1187
1333
  const responseData = error.response?.data;
1188
1334
  if (responseData?.name?.[0] === "has already been taken") {
1189
- const existingTags = await fetchComponentInternalTags(space, token, region);
1335
+ const existingTags = await fetchComponentInternalTags(space);
1190
1336
  const existingTag = existingTags?.find((t) => t.name === tag.name);
1191
1337
  if (existingTag) {
1192
- return await updateComponentInternalTag(space, existingTag.id, tag, token, region);
1338
+ return await updateComponentInternalTag(space, existingTag.id, tag);
1193
1339
  }
1194
1340
  }
1195
1341
  }
@@ -1320,8 +1466,7 @@ componentsCommand.command("pull [componentName]").option("-f, --filename <filena
1320
1466
  const { separateFiles, suffix, filename = "components" } = options;
1321
1467
  const { state, initializeSession } = session();
1322
1468
  await initializeSession();
1323
- if (!state.isLoggedIn || !state.password || !state.region) {
1324
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
1469
+ if (!requireAuthentication(state, verbose)) {
1325
1470
  return;
1326
1471
  }
1327
1472
  if (!space) {
@@ -1329,6 +1474,10 @@ componentsCommand.command("pull [componentName]").option("-f, --filename <filena
1329
1474
  return;
1330
1475
  }
1331
1476
  const { password, region } = state;
1477
+ mapiClient({
1478
+ token: password,
1479
+ region
1480
+ });
1332
1481
  const spinnerGroups = new Spinner({
1333
1482
  verbose: !isVitest
1334
1483
  });
@@ -1343,25 +1492,25 @@ componentsCommand.command("pull [componentName]").option("-f, --filename <filena
1343
1492
  });
1344
1493
  try {
1345
1494
  spinnerGroups.start(`Fetching ${chalk.hex(colorPalette.COMPONENTS)("components groups")}`);
1346
- const groups = await fetchComponentGroups(space, password, region);
1495
+ const groups = await fetchComponentGroups(space);
1347
1496
  spinnerGroups.succeed(`${chalk.hex(colorPalette.COMPONENTS)("Groups")} - Completed in ${spinnerGroups.elapsedTime.toFixed(2)}ms`);
1348
1497
  spinnerPresets.start(`Fetching ${chalk.hex(colorPalette.COMPONENTS)("components presets")}`);
1349
- const presets = await fetchComponentPresets(space, password, region);
1498
+ const presets = await fetchComponentPresets(space);
1350
1499
  spinnerPresets.succeed(`${chalk.hex(colorPalette.COMPONENTS)("Presets")} - Completed in ${spinnerPresets.elapsedTime.toFixed(2)}ms`);
1351
1500
  spinnerInternalTags.start(`Fetching ${chalk.hex(colorPalette.COMPONENTS)("components internal tags")}`);
1352
- const internalTags = await fetchComponentInternalTags(space, password, region);
1501
+ const internalTags = await fetchComponentInternalTags(space);
1353
1502
  spinnerInternalTags.succeed(`${chalk.hex(colorPalette.COMPONENTS)("Tags")} - Completed in ${spinnerInternalTags.elapsedTime.toFixed(2)}ms`);
1354
1503
  let components;
1355
1504
  spinnerComponents.start(`Fetching ${chalk.hex(colorPalette.COMPONENTS)("components")}`);
1356
1505
  if (componentName) {
1357
- const component = await fetchComponent(space, componentName, password, region);
1506
+ const component = await fetchComponent(space, componentName);
1358
1507
  if (!component) {
1359
1508
  konsola.warn(`No component found with name "${componentName}"`);
1360
1509
  return;
1361
1510
  }
1362
1511
  components = [component];
1363
1512
  } else {
1364
- components = await fetchComponents(space, password, region);
1513
+ components = await fetchComponents(space);
1365
1514
  if (!components || components.length === 0) {
1366
1515
  konsola.warn(`No components found in the space ${space}`);
1367
1516
  return;
@@ -1536,7 +1685,7 @@ function filterSpaceDataByComponent(spaceData, componentName) {
1536
1685
  internalTags: relatedResources.internalTags
1537
1686
  };
1538
1687
  }
1539
- async function handleTags(space, password, region, spaceData, skipIds) {
1688
+ async function handleTags(space, spaceData, skipIds) {
1540
1689
  const results = {
1541
1690
  successful: [],
1542
1691
  failed: [],
@@ -1549,7 +1698,7 @@ async function handleTags(space, password, region, spaceData, skipIds) {
1549
1698
  });
1550
1699
  consolidatedSpinner.start("Upserting tags...");
1551
1700
  try {
1552
- const updatedTag = await upsertComponentInternalTag(space, tag, password, region);
1701
+ const updatedTag = await upsertComponentInternalTag(space, tag);
1553
1702
  if (updatedTag) {
1554
1703
  results.idMap.set(tag.id, updatedTag.id);
1555
1704
  results.successful.push(tag.name);
@@ -1562,7 +1711,7 @@ async function handleTags(space, password, region, spaceData, skipIds) {
1562
1711
  }));
1563
1712
  return results;
1564
1713
  }
1565
- async function handleComponentGroups(space, password, region, spaceData, skipUuids) {
1714
+ async function handleComponentGroups(space, spaceData, skipUuids) {
1566
1715
  const results = {
1567
1716
  successful: [],
1568
1717
  failed: [],
@@ -1579,7 +1728,7 @@ async function handleComponentGroups(space, password, region, spaceData, skipUui
1579
1728
  });
1580
1729
  spinner.start(`Upserting root group ${group.name}...`);
1581
1730
  try {
1582
- const updatedGroup = await upsertComponentGroup(space, group, password, region);
1731
+ const updatedGroup = await upsertComponentGroup(space, group);
1583
1732
  if (updatedGroup) {
1584
1733
  results.uuidMap.set(group.uuid, updatedGroup.uuid);
1585
1734
  results.idMap.set(group.id, updatedGroup.id);
@@ -1615,7 +1764,7 @@ async function handleComponentGroups(space, password, region, spaceData, skipUui
1615
1764
  parent_uuid: newParentUuid,
1616
1765
  parent_id: newParentId
1617
1766
  };
1618
- const updatedGroup = await upsertComponentGroup(space, groupToUpdate, password, region);
1767
+ const updatedGroup = await upsertComponentGroup(space, groupToUpdate);
1619
1768
  if (updatedGroup) {
1620
1769
  results.uuidMap.set(group.uuid, updatedGroup.uuid);
1621
1770
  results.idMap.set(group.id, updatedGroup.id);
@@ -1710,7 +1859,7 @@ function getGroupHierarchy(group, allGroups) {
1710
1859
  }
1711
1860
  return hierarchy;
1712
1861
  }
1713
- async function handleWhitelists(space, password, region, spaceData) {
1862
+ async function handleWhitelists(space, spaceData) {
1714
1863
  const results = {
1715
1864
  successful: [],
1716
1865
  failed: [],
@@ -1740,7 +1889,7 @@ async function handleWhitelists(space, password, region, spaceData) {
1740
1889
  verbose: !isVitest
1741
1890
  });
1742
1891
  spinner.start("Processing whitelist tags...");
1743
- const tagResults = await handleTags(space, password, region, whitelistTags);
1892
+ const tagResults = await handleTags(space, whitelistTags);
1744
1893
  results.successful.push(...tagResults.successful);
1745
1894
  results.failed.push(...tagResults.failed);
1746
1895
  tagResults.idMap.forEach((newId, oldId) => {
@@ -1761,7 +1910,7 @@ async function handleWhitelists(space, password, region, spaceData) {
1761
1910
  verbose: !isVitest
1762
1911
  });
1763
1912
  spinner.start("Processing whitelist groups...");
1764
- const groupResults = await handleComponentGroups(space, password, region, whitelistGroups);
1913
+ const groupResults = await handleComponentGroups(space, whitelistGroups);
1765
1914
  results.successful.push(...groupResults.successful);
1766
1915
  results.failed.push(...groupResults.failed);
1767
1916
  groupResults.uuidMap.forEach((newUuid, oldUuid) => {
@@ -1846,7 +1995,7 @@ async function handleWhitelists(space, password, region, spaceData) {
1846
1995
  results.componentNameMap
1847
1996
  );
1848
1997
  }
1849
- const updatedComponent = await upsertComponent(space, componentToUpdate, password, region);
1998
+ const updatedComponent = await upsertComponent(space, componentToUpdate);
1850
1999
  if (updatedComponent) {
1851
2000
  results.successful.push(component.name);
1852
2001
  results.componentNameMap.set(component.name, updatedComponent.name);
@@ -1870,8 +2019,6 @@ async function handleWhitelists(space, password, region, spaceData) {
1870
2019
  async function handleComponents(options) {
1871
2020
  const {
1872
2021
  space,
1873
- password,
1874
- region,
1875
2022
  spaceData: { components, internalTags, presets },
1876
2023
  groupsUuidMap,
1877
2024
  tagsIdMaps,
@@ -1950,7 +2097,7 @@ async function handleComponents(options) {
1950
2097
  componentNameMap
1951
2098
  );
1952
2099
  }
1953
- const updatedComponent = await upsertComponent(space, componentToUpdate, password, region);
2100
+ const updatedComponent = await upsertComponent(space, componentToUpdate);
1954
2101
  if (updatedComponent) {
1955
2102
  results.successful.push(component.name);
1956
2103
  results.componentIdMap.set(component.id, updatedComponent.id);
@@ -1984,7 +2131,7 @@ async function handleComponents(options) {
1984
2131
  tagsIdMaps,
1985
2132
  componentNameMap
1986
2133
  );
1987
- await upsertComponent(space, componentToUpdate, password, region);
2134
+ await upsertComponent(space, componentToUpdate);
1988
2135
  spinner.succeed(`Component whitelists-> ${chalk.hex(colorPalette.COMPONENTS)(component.name)} - Completed in ${spinner.elapsedTime.toFixed(2)}ms`);
1989
2136
  } catch (error) {
1990
2137
  spinner.failed(`Component whitelists-> ${chalk.hex(colorPalette.COMPONENTS)(component.name)} - Failed`);
@@ -2010,7 +2157,7 @@ async function handleComponents(options) {
2010
2157
  preset: preset.preset,
2011
2158
  component_id: newComponentId
2012
2159
  };
2013
- await upsertComponentPreset(space, presetToUpdate, password, region);
2160
+ await upsertComponentPreset(space, presetToUpdate);
2014
2161
  presetSpinner.succeed(`Preset-> ${chalk.hex(colorPalette.COMPONENTS)(preset.name)} - Completed in ${presetSpinner.elapsedTime.toFixed(2)}ms`);
2015
2162
  } catch (error) {
2016
2163
  presetSpinner.failed(`Preset-> ${chalk.hex(colorPalette.COMPONENTS)(preset.name)} - Failed`);
@@ -2030,8 +2177,7 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2030
2177
  const { from, filter } = options;
2031
2178
  const { state, initializeSession } = session();
2032
2179
  await initializeSession();
2033
- if (!state.isLoggedIn || !state.password || !state.region) {
2034
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
2180
+ if (!requireAuthentication(state, verbose)) {
2035
2181
  return;
2036
2182
  }
2037
2183
  if (!space) {
@@ -2044,6 +2190,10 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2044
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)}`);
2045
2191
  konsola.br();
2046
2192
  const { password, region } = state;
2193
+ mapiClient({
2194
+ token: password,
2195
+ region
2196
+ });
2047
2197
  try {
2048
2198
  let spaceData = await readComponentsFiles({
2049
2199
  ...options,
@@ -2072,13 +2222,13 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2072
2222
  successful: [],
2073
2223
  failed: []
2074
2224
  };
2075
- const whitelistResults = await handleWhitelists(space, password, region, spaceData);
2225
+ const whitelistResults = await handleWhitelists(space, spaceData);
2076
2226
  results.successful.push(...whitelistResults.successful);
2077
2227
  results.failed.push(...whitelistResults.failed);
2078
- const tagsResults = await handleTags(space, password, region, spaceData.internalTags, whitelistResults.processedTagIds);
2228
+ const tagsResults = await handleTags(space, spaceData.internalTags, whitelistResults.processedTagIds);
2079
2229
  results.successful.push(...tagsResults.successful);
2080
2230
  results.failed.push(...tagsResults.failed);
2081
- const groupsResults = await handleComponentGroups(space, password, region, spaceData.groups, whitelistResults.processedGroupUuids);
2231
+ const groupsResults = await handleComponentGroups(space, spaceData.groups, whitelistResults.processedGroupUuids);
2082
2232
  results.successful.push(...groupsResults.successful);
2083
2233
  results.failed.push(...groupsResults.failed);
2084
2234
  const remainingComponents = spaceData.components.filter(
@@ -2086,8 +2236,6 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2086
2236
  );
2087
2237
  const componentsResults = await handleComponents({
2088
2238
  space,
2089
- password,
2090
- region,
2091
2239
  spaceData: {
2092
2240
  ...spaceData,
2093
2241
  components: remainingComponents
@@ -2153,20 +2301,20 @@ languagesCommand.command("pull").description(`Download your space's languages sc
2153
2301
  const { filename = "languages", suffix = options.space } = options;
2154
2302
  const { state, initializeSession } = session();
2155
2303
  await initializeSession();
2156
- if (!state.isLoggedIn || !state.password || !state.region) {
2157
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
2304
+ if (!requireAuthentication(state, verbose)) {
2158
2305
  return;
2159
2306
  }
2160
2307
  if (!space) {
2161
2308
  handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
2162
2309
  return;
2163
2310
  }
2311
+ const { password, region } = state;
2164
2312
  const spinner = new Spinner({
2165
2313
  verbose: !isVitest
2166
2314
  });
2167
2315
  try {
2168
2316
  spinner.start(`Fetching ${chalk.hex(colorPalette.LANGUAGES)("languages")}`);
2169
- const internationalization = await fetchLanguages(space, state.password, state.region);
2317
+ const internationalization = await fetchLanguages(space, password, region);
2170
2318
  if (!internationalization || internationalization.languages?.length === 0) {
2171
2319
  spinner.failed();
2172
2320
  konsola.warn(`No languages found in the space ${space}`, true);
@@ -2230,8 +2378,7 @@ migrationsCommand.command("generate [componentName]").description("Generate a mi
2230
2378
  }
2231
2379
  const { state, initializeSession } = session();
2232
2380
  await initializeSession();
2233
- if (!state.isLoggedIn || !state.password || !state.region) {
2234
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
2381
+ if (!requireAuthentication(state, verbose)) {
2235
2382
  return;
2236
2383
  }
2237
2384
  if (!space) {
@@ -2239,11 +2386,15 @@ migrationsCommand.command("generate [componentName]").description("Generate a mi
2239
2386
  return;
2240
2387
  }
2241
2388
  const { password, region } = state;
2389
+ mapiClient({
2390
+ token: password,
2391
+ region
2392
+ });
2242
2393
  const spinner = new Spinner({
2243
2394
  verbose: !isVitest
2244
2395
  }).start(`Generating migration for component ${componentName}...`);
2245
2396
  try {
2246
- const component = await fetchComponent(space, componentName, password, region);
2397
+ const component = await fetchComponent(space, componentName);
2247
2398
  if (!component) {
2248
2399
  spinner.failed(`Failed to fetch component ${componentName}. Make sure the component exists in your space.`);
2249
2400
  handleError(new CommandError(`No component found with name "${componentName}"`), verbose);
@@ -2642,8 +2793,7 @@ migrationsCommand.command("run [componentName]").description("Run migrations").o
2642
2793
  const { space, path } = migrationsCommand.opts();
2643
2794
  const { state, initializeSession } = session();
2644
2795
  await initializeSession();
2645
- if (!state.isLoggedIn || !state.password || !state.region) {
2646
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
2796
+ if (!requireAuthentication(state, verbose)) {
2647
2797
  return;
2648
2798
  }
2649
2799
  if (!space) {
@@ -2799,8 +2949,7 @@ migrationsCommand.command("rollback [migrationFile]").description("Rollback a mi
2799
2949
  const { space, path } = migrationsCommand.opts();
2800
2950
  const { state, initializeSession } = session();
2801
2951
  await initializeSession();
2802
- if (!state.isLoggedIn || !state.password || !state.region) {
2803
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
2952
+ if (!requireAuthentication(state, verbose)) {
2804
2953
  return;
2805
2954
  }
2806
2955
  if (!space) {
@@ -3413,7 +3562,7 @@ const loadCustomFieldsParser = async (path) => {
3413
3562
  return customFieldsParser.default;
3414
3563
  } catch (error) {
3415
3564
  handleError(error);
3416
- return null;
3565
+ return void 0;
3417
3566
  }
3418
3567
  };
3419
3568
  async function loadCompilerOptions(path) {
@@ -3454,6 +3603,9 @@ const generateTypes = async (spaceData, options = {
3454
3603
  if (property.type && Array.from(storyblokSchemas.keys()).includes(property.type)) {
3455
3604
  storyblokPropertyTypes.add(property.type);
3456
3605
  }
3606
+ if (property.tsType && property.tsType.includes(STORY_TYPE)) {
3607
+ storyblokPropertyTypes.add(STORY_TYPE);
3608
+ }
3457
3609
  });
3458
3610
  }
3459
3611
  const componentSchema = {
@@ -3483,6 +3635,11 @@ const generateTypes = async (spaceData, options = {
3483
3635
  });
3484
3636
  }));
3485
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
+ }
3486
3643
  if (storyblokPropertyTypes.size > 0) {
3487
3644
  const typeImports = Array.from(storyblokPropertyTypes).map((type) => {
3488
3645
  const pascalType = toPascalCase(type);
@@ -3534,8 +3691,7 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
3534
3691
  const { space, path } = typesCommand.opts();
3535
3692
  const { state, initializeSession } = session();
3536
3693
  await initializeSession();
3537
- if (!state.isLoggedIn || !state.password || !state.region) {
3538
- handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose);
3694
+ if (!requireAuthentication(state, verbose)) {
3539
3695
  return;
3540
3696
  }
3541
3697
  if (!space) {
@@ -3570,7 +3726,7 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
3570
3726
  }
3571
3727
  });
3572
3728
 
3573
- const version = "4.0.0-beta.4";
3729
+ const version = "4.0.0-beta.5";
3574
3730
  const pkg = {
3575
3731
  version: version};
3576
3732
 
@@ -3587,19 +3743,6 @@ program.on("command:*", () => {
3587
3743
  konsola.br();
3588
3744
  program.help();
3589
3745
  });
3590
- program.command("test").description("Test the CLI").action(async () => {
3591
- const { state, initializeSession } = session();
3592
- await initializeSession();
3593
- const { password, region } = state;
3594
- try {
3595
- const result = await fetchStories("85047", password, region, {
3596
- per_page: 100
3597
- });
3598
- console.log(result?.length);
3599
- } catch (error) {
3600
- console.error(error);
3601
- }
3602
- });
3603
3746
  try {
3604
3747
  program.parse(process.argv);
3605
3748
  } catch (error) {