storyblok 4.0.0-beta.2 → 4.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -107,7 +107,11 @@ async function customFetch(url, options = {}) {
107
107
  data
108
108
  });
109
109
  }
110
- return data;
110
+ return {
111
+ ...data,
112
+ perPage: Number(response.headers.get("Per-Page")),
113
+ total: Number(response.headers.get("Total"))
114
+ };
111
115
  } catch (error) {
112
116
  if (error instanceof FetchError) {
113
117
  throw error;
@@ -315,6 +319,22 @@ class FileSystemError extends Error {
315
319
  }
316
320
  }
317
321
 
322
+ function handleVerboseError(error) {
323
+ if (error instanceof CommandError || error instanceof APIError || error instanceof FileSystemError) {
324
+ const errorDetails = "getInfo" in error ? error.getInfo() : {};
325
+ if (error instanceof CommandError) {
326
+ konsola.error(`Command Error: ${error.getInfo().message}`, errorDetails);
327
+ } else if (error instanceof APIError) {
328
+ konsola.error(`API Error: ${error.getInfo().cause}`, errorDetails);
329
+ } else if (error instanceof FileSystemError) {
330
+ konsola.error(`File System Error: ${error.getInfo().cause}`, errorDetails);
331
+ } else {
332
+ konsola.error(`Unexpected Error: ${error}`, errorDetails);
333
+ }
334
+ } else {
335
+ konsola.error(`Unexpected Error`, error);
336
+ }
337
+ }
318
338
  function handleError(error, verbose = false) {
319
339
  if (error instanceof APIError || error instanceof FileSystemError) {
320
340
  const messageStack = error.messageStack;
@@ -329,17 +349,8 @@ function handleError(error, verbose = false) {
329
349
  header: true
330
350
  });
331
351
  }
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
- }
352
+ if (verbose) {
353
+ handleVerboseError(error);
343
354
  } else {
344
355
  konsola.br();
345
356
  konsola.info("For more information about the error, run the command with the `--verbose` flag");
@@ -353,7 +364,10 @@ const toPascalCase = (str) => {
353
364
  return str.replace(/(?:^|_)(\w)/g, (_, char) => char.toUpperCase());
354
365
  };
355
366
  const toCamelCase = (str) => {
356
- return str.replace(/(?:^|_)(\w)/g, (_, char) => char.toUpperCase()).replace(/_/g, "").replace(/^[A-Z]/, (char) => char.toLowerCase());
367
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()).replace(/_/g, "").replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()).replace(/[^a-z0-9]([a-z])/gi, (_, letter) => letter.toUpperCase()).replace(/[^a-z0-9]/gi, "");
368
+ };
369
+ const capitalize = (str) => {
370
+ return str.charAt(0).toUpperCase() + str.slice(1);
357
371
  };
358
372
  function maskToken(token) {
359
373
  if (token.length <= 4) {
@@ -563,14 +577,18 @@ const getCredentials = async (filePath = join(getStoryblokGlobalPath(), "credent
563
577
  try {
564
578
  await access(filePath);
565
579
  const content = await readFile(filePath);
566
- return JSON.parse(content);
580
+ const parsedContent = JSON.parse(content);
581
+ if (Object.keys(parsedContent).length === 0) {
582
+ return null;
583
+ }
584
+ return parsedContent;
567
585
  } catch (error) {
568
586
  if (error.code === "ENOENT") {
569
587
  await saveToFile(filePath, JSON.stringify({}, null, 2), { mode: 384 });
570
- return {};
588
+ return null;
571
589
  }
572
590
  handleFileSystemError("read", error);
573
- return {};
591
+ return null;
574
592
  }
575
593
  };
576
594
  const addCredentials = async ({
@@ -604,7 +622,7 @@ function createSession() {
604
622
  const state = {
605
623
  isLoggedIn: false
606
624
  };
607
- async function initializeSession(region = "eu") {
625
+ async function initializeSession() {
608
626
  const envCredentials = getEnvCredentials();
609
627
  if (envCredentials) {
610
628
  state.isLoggedIn = true;
@@ -614,9 +632,9 @@ function createSession() {
614
632
  state.envLogin = true;
615
633
  return;
616
634
  }
617
- const machines = await getCredentials();
618
- const creds = machines[regionsDomain[region] || "api.storyblok.com"];
619
- if (creds) {
635
+ const credentials = await getCredentials();
636
+ if (credentials) {
637
+ const creds = Object.values(credentials)[0];
620
638
  state.isLoggedIn = true;
621
639
  state.login = creds.login;
622
640
  state.password = creds.password;
@@ -1191,16 +1209,18 @@ async function readJsonFile(filePath) {
1191
1209
  }
1192
1210
  }
1193
1211
  const readComponentsFiles = async (options) => {
1194
- const { from, path, separateFiles = false, suffix } = options;
1212
+ const { from, path, separateFiles = false, suffix, space } = options;
1195
1213
  const resolvedPath = resolvePath(path, `components/${from}`);
1196
1214
  try {
1197
1215
  await readdir(resolvedPath);
1198
1216
  } catch (error) {
1199
- const message = `No directory found for space "${from}". Please make sure you have pulled the components first by running:
1217
+ const message = `No local components found for space ${chalk.bold(from)}. To push components, you need to pull them first:
1200
1218
 
1201
- storyblok components pull --space ${from}
1219
+ 1. Pull the components from your source space:
1220
+ ${chalk.cyan(`storyblok components pull --space ${from}`)}
1202
1221
 
1203
- `;
1222
+ 2. Then try pushing again:
1223
+ ${chalk.cyan(`storyblok components push --space ${space} --from ${from}`)}`;
1204
1224
  throw new FileSystemError(
1205
1225
  "file_not_found",
1206
1226
  "read",
@@ -1380,7 +1400,7 @@ componentsCommand.command("pull [componentName]").option("-f, --filename <filena
1380
1400
  }
1381
1401
  });
1382
1402
 
1383
- function findRelatedResources(components, spaceData) {
1403
+ function findRelatedResources(components, spaceData, visitedComponents = /* @__PURE__ */ new Set()) {
1384
1404
  const componentIds = new Set(components.map((c) => c.id));
1385
1405
  const whitelistedComponentNames = /* @__PURE__ */ new Set();
1386
1406
  const componentGroupUuids = /* @__PURE__ */ new Set();
@@ -1465,7 +1485,11 @@ function findRelatedResources(components, spaceData) {
1465
1485
  whitelistedComponents: []
1466
1486
  };
1467
1487
  if (whitelistedComponents.length > 0) {
1468
- additionalResources = findRelatedResources(whitelistedComponents, spaceData);
1488
+ const newComponents = whitelistedComponents.filter((component) => !visitedComponents.has(component.name));
1489
+ if (newComponents.length > 0) {
1490
+ components.forEach((component) => visitedComponents.add(component.name));
1491
+ additionalResources = findRelatedResources(newComponents, spaceData, visitedComponents);
1492
+ }
1469
1493
  }
1470
1494
  const result = {
1471
1495
  groups: Array.from(/* @__PURE__ */ new Set([...Array.from(relatedGroups), ...additionalResources.groups])),
@@ -1759,11 +1783,6 @@ async function handleWhitelists(space, password, region, spaceData) {
1759
1783
  return;
1760
1784
  }
1761
1785
  if (visited.has(componentName)) {
1762
- failedComponents.add(componentName);
1763
- results.failed.push({
1764
- name: componentName,
1765
- error: new Error(`Circular dependency detected for component ${componentName}`)
1766
- });
1767
1786
  return;
1768
1787
  }
1769
1788
  visited.add(componentName);
@@ -2022,11 +2041,14 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2022
2041
  if (!from) {
2023
2042
  options.from = space;
2024
2043
  }
2044
+ 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
+ konsola.br();
2025
2046
  const { password, region } = state;
2026
2047
  try {
2027
2048
  let spaceData = await readComponentsFiles({
2028
2049
  ...options,
2029
- path
2050
+ path,
2051
+ space
2030
2052
  });
2031
2053
  if (componentName) {
2032
2054
  spaceData = filterSpaceDataByComponent(spaceData, componentName);
@@ -2241,16 +2263,28 @@ migrationsCommand.command("generate [componentName]").description("Generate a mi
2241
2263
  const fetchStories = async (space, token, region, params) => {
2242
2264
  try {
2243
2265
  const url = getStoryblokUrl(region);
2244
- const { filter_query, ...restParams } = params || {};
2245
- const regularParams = new URLSearchParams(objectToStringParams(restParams)).toString();
2246
- const queryString = filter_query ? `${regularParams ? `${regularParams}&` : ""}${filter_query}` : regularParams;
2247
- const endpoint = `${url}/spaces/${space}/stories${queryString ? `?${queryString}` : ""}`;
2248
- const response = await customFetch(endpoint, {
2249
- headers: {
2250
- Authorization: token
2251
- }
2252
- });
2253
- return response.stories;
2266
+ const allStories = [];
2267
+ let currentPage = 1;
2268
+ let hasMorePages = true;
2269
+ while (hasMorePages) {
2270
+ const { filter_query, ...restParams } = params || {};
2271
+ const regularParams = new URLSearchParams({
2272
+ ...objectToStringParams({ ...restParams, per_page: 100 }),
2273
+ ...currentPage > 1 && { page: currentPage.toString() }
2274
+ }).toString();
2275
+ const queryString = filter_query ? `${regularParams ? `${regularParams}&` : ""}${filter_query}` : regularParams;
2276
+ const endpoint = `${url}/spaces/${space}/stories${queryString ? `?${queryString}` : ""}`;
2277
+ const response = await customFetch(endpoint, {
2278
+ headers: {
2279
+ Authorization: token
2280
+ }
2281
+ });
2282
+ allStories.push(...response.stories);
2283
+ const totalPages = Math.ceil(response.total / response.perPage);
2284
+ hasMorePages = currentPage < totalPages;
2285
+ currentPage++;
2286
+ }
2287
+ return allStories;
2254
2288
  } catch (error) {
2255
2289
  handleAPIError("pull_stories", error);
2256
2290
  }
@@ -3223,18 +3257,67 @@ const storyblokSchemas = /* @__PURE__ */ new Map([
3223
3257
  ["richtext", getRichtextJSONSchema]
3224
3258
  ]);
3225
3259
 
3260
+ const STORY_TYPE = "ISbStoryData";
3226
3261
  const DEFAULT_TYPEDEFS_HEADER = [
3227
3262
  "// This file was generated by the storyblok CLI.",
3228
3263
  "// DO NOT MODIFY THIS FILE BY HAND."
3229
3264
  ];
3230
- const getPropertyTypeAnnotation = (property) => {
3265
+ const getPropertyTypeAnnotation = (property, prefix) => {
3231
3266
  if (Array.from(storyblokSchemas.keys()).includes(property.type)) {
3232
3267
  return { type: property.type };
3233
3268
  }
3269
+ let type = "unknown";
3234
3270
  const options = property.options && property.options.length > 0 ? property.options.map((item) => item.value) : [];
3235
3271
  if (options.length > 0 && property.exclude_empty_option !== true) {
3236
3272
  options.unshift("");
3237
3273
  }
3274
+ if (property.source === "internal_stories") {
3275
+ if (property.filter_content_type) {
3276
+ if (typeof property.filter_content_type === "string") {
3277
+ return {
3278
+ tsType: `(${getStoryType(property.filter_content_type, prefix)} | string )${property.type === "options" ? "[]" : ""}`
3279
+ };
3280
+ }
3281
+ return {
3282
+ tsType: `(${property.filter_content_type.map((type2) => getStoryType(type2, prefix)).join(" | ")} | string )${property.type === "options" ? "[]" : ""}`
3283
+ };
3284
+ }
3285
+ }
3286
+ if (
3287
+ // If there is no `source` and there are options, the data source is the component itself
3288
+ // TODO: check if this is an old behaviour (shouldn't this be handled as an "internal" source?)
3289
+ options.length > 0 && !property.source || property.source === "internal_languages" || property.source === "external"
3290
+ ) {
3291
+ type = "string";
3292
+ }
3293
+ if (property.source === "internal") {
3294
+ type = ["number", "string"];
3295
+ }
3296
+ if (property.type === "option") {
3297
+ if (options.length > 0) {
3298
+ return {
3299
+ type,
3300
+ enum: options
3301
+ };
3302
+ }
3303
+ return {
3304
+ type
3305
+ };
3306
+ }
3307
+ if (property.type === "options") {
3308
+ if (options.length > 0) {
3309
+ return {
3310
+ type: "array",
3311
+ items: {
3312
+ enum: options
3313
+ }
3314
+ };
3315
+ }
3316
+ return {
3317
+ type: "array",
3318
+ items: { type }
3319
+ };
3320
+ }
3238
3321
  switch (property.type) {
3239
3322
  case "bloks":
3240
3323
  return { type: "array" };
@@ -3251,6 +3334,9 @@ const getPropertyTypeAnnotation = (property) => {
3251
3334
  return { type: "any" };
3252
3335
  }
3253
3336
  };
3337
+ function getStoryType(property, prefix) {
3338
+ return `${STORY_TYPE}<${prefix ?? ""}${capitalize(toCamelCase(property))}>`;
3339
+ }
3254
3340
  const getComponentType = (componentName, options) => {
3255
3341
  const prefix = options.typePrefix ?? "";
3256
3342
  const sanitizedName = componentName.replace(/[^a-z0-9]/gi, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
@@ -3266,7 +3352,7 @@ const getComponentPropertiesTypeAnnotations = async (component, options, spaceDa
3266
3352
  }
3267
3353
  const propertyType = value.type;
3268
3354
  const propertyTypeAnnotation = {
3269
- [key]: getPropertyTypeAnnotation(value)
3355
+ [key]: getPropertyTypeAnnotation(value, options.typePrefix)
3270
3356
  };
3271
3357
  if (propertyType === "custom" && customFieldsParser) {
3272
3358
  const customField = typeof customFieldsParser === "function" ? customFieldsParser(key, value) : {};
@@ -3354,7 +3440,7 @@ const generateTypes = async (spaceData, options = {
3354
3440
  const schemas = await Promise.all(spaceData.components.map(async (component) => {
3355
3441
  const type = getComponentType(component.name, options);
3356
3442
  const componentPropertiesTypeAnnotations = await getComponentPropertiesTypeAnnotations(component, options, spaceData, customFieldsParser);
3357
- const requiredFields = Object.entries(component.schema).reduce(
3443
+ const requiredFields = Object.entries(component?.schema || {}).reduce(
3358
3444
  (acc, [key, value]) => {
3359
3445
  if (value.required) {
3360
3446
  return [...acc, key];
@@ -3424,45 +3510,13 @@ const saveTypesToFile = async (space, typedefString, options) => {
3424
3510
  const generateStoryblokTypes = async (options = {}) => {
3425
3511
  const { filename = "storyblok", path } = options;
3426
3512
  try {
3427
- const storyblokTypesPath = resolve(process.cwd(), "src", "types", "storyblok.ts");
3513
+ const storyblokTypesPath = resolve(__dirname, "./index.d.ts");
3428
3514
  const storyblokTypesContent = readFileSync(storyblokTypesPath, "utf-8");
3429
- const lines = storyblokTypesContent.split("\n");
3430
- const typeDefinitions = [];
3431
- let isCollecting = false;
3432
- let bracketCount = 0;
3433
- let currentType = "";
3434
- for (let i = 0; i < lines.length; i++) {
3435
- const line = lines[i];
3436
- if (line.includes("export type StoryblokPropertyType") || line.includes("export interface Storyblok")) {
3437
- if (isCollecting) {
3438
- typeDefinitions.push("");
3439
- }
3440
- isCollecting = true;
3441
- typeDefinitions.push(line);
3442
- currentType = line.includes("type") ? "type" : "interface";
3443
- bracketCount += (line.match(/\{/g) || []).length;
3444
- bracketCount -= (line.match(/\}/g) || []).length;
3445
- if (currentType === "type") {
3446
- isCollecting = false;
3447
- continue;
3448
- }
3449
- let j = i + 1;
3450
- while (j < lines.length && bracketCount > 0) {
3451
- const nextLine = lines[j];
3452
- bracketCount += (nextLine.match(/\{/g) || []).length;
3453
- bracketCount -= (nextLine.match(/\}/g) || []).length;
3454
- typeDefinitions.push(nextLine);
3455
- j++;
3456
- }
3457
- i = j - 1;
3458
- isCollecting = false;
3459
- }
3460
- }
3461
3515
  const typeDefs = [
3462
- "// This file was generated by the storyblok CLI.",
3516
+ "// This file was generated by the Storyblok CLI.",
3463
3517
  "// DO NOT MODIFY THIS FILE BY HAND.",
3464
- "",
3465
- ...typeDefinitions
3518
+ `import type { ${STORY_TYPE} } from '@storyblok/js';`,
3519
+ storyblokTypesContent
3466
3520
  ].join("\n");
3467
3521
  const resolvedPath = path ? resolve(process.cwd(), path, "types") : resolvePath(path, "types");
3468
3522
  await saveToFile(join(resolvedPath, `${filename}.d.ts`), typeDefs);
@@ -3516,7 +3570,7 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
3516
3570
  }
3517
3571
  });
3518
3572
 
3519
- const version = "4.0.0-beta.2";
3573
+ const version = "4.0.0-beta.4";
3520
3574
  const pkg = {
3521
3575
  version: version};
3522
3576
 
@@ -3533,6 +3587,19 @@ program.on("command:*", () => {
3533
3587
  konsola.br();
3534
3588
  program.help();
3535
3589
  });
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
+ });
3536
3603
  try {
3537
3604
  program.parse(process.argv);
3538
3605
  } catch (error) {