storyblok 4.14.3 → 4.15.1

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
@@ -11,7 +11,7 @@ import { Command } from 'commander';
11
11
  import path, { join, resolve as resolve$1, parse, dirname as dirname$1, extname, relative, basename } from 'node:path';
12
12
  import { MultiBar, Presets } from 'cli-progress';
13
13
  import { Spinner } from '@topcli/spinner';
14
- import fs, { mkdir, writeFile, readFile as readFile$1, appendFile, access, constants, readdir, unlink, stat } from 'node:fs/promises';
14
+ import fs, { mkdir, writeFile, readFile as readFile$1, appendFile, access, constants, readdir, unlink } from 'node:fs/promises';
15
15
  import filenamify from 'filenamify';
16
16
  import { ManagementApiClient } from '@storyblok/management-api-client';
17
17
  import { RateLimit, Sema } from 'async-sema';
@@ -103,6 +103,44 @@ const regionNames = {
103
103
  ({
104
104
  SB_Agent_Version: process.env.npm_package_version || "4.x"
105
105
  });
106
+ const SUPPORTED_ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
107
+ // Images: image/png, image/x-png, image/gif, image/jpeg, image/avif, image/svg+xml, image/webp
108
+ ".jpg",
109
+ ".jpeg",
110
+ ".png",
111
+ ".gif",
112
+ ".webp",
113
+ ".avif",
114
+ ".svg",
115
+ // Video: video/*, application/mp4, application/x-mpegurl, application/vnd.apple.mpegurl
116
+ ".mp4",
117
+ ".mov",
118
+ ".avi",
119
+ ".webm",
120
+ ".wmv",
121
+ ".mkv",
122
+ ".flv",
123
+ ".ogv",
124
+ ".3gp",
125
+ ".m4v",
126
+ ".mpg",
127
+ ".mpeg",
128
+ ".m3u8",
129
+ // Audio: audio/*
130
+ ".mp3",
131
+ ".wav",
132
+ ".ogg",
133
+ ".aac",
134
+ ".flac",
135
+ ".wma",
136
+ ".m4a",
137
+ ".opus",
138
+ // Documents: application/msword, text/plain, application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document
139
+ ".pdf",
140
+ ".doc",
141
+ ".docx",
142
+ ".txt"
143
+ ]);
106
144
  const directories = {
107
145
  assets: "assets",
108
146
  components: "components",
@@ -651,25 +689,38 @@ const API_ACTIONS = {
651
689
  const API_ERRORS = {
652
690
  unauthorized: "The user is not authorized to access the API",
653
691
  network_error: "No response from server, please check if you are correctly connected to internet",
692
+ server_error: "The server returned an error",
654
693
  invalid_credentials: "The provided credentials are invalid",
655
694
  timeout: "The API request timed out",
656
695
  generic: "Error fetching data from the API",
657
696
  not_found: "The requested resource was not found",
658
697
  unprocessable_entity: "The request was well-formed but was unable to be followed due to semantic errors"
659
698
  };
699
+ function getErrorId(status) {
700
+ switch (status) {
701
+ case 401:
702
+ return "unauthorized";
703
+ case 404:
704
+ return "not_found";
705
+ case 422:
706
+ return "unprocessable_entity";
707
+ default:
708
+ return status >= 500 ? "server_error" : "generic";
709
+ }
710
+ }
660
711
  function handleAPIError(action, error, customMessage) {
661
712
  if (error instanceof FetchError) {
662
- const status = error.response.status;
663
- switch (status) {
664
- case 401:
665
- throw new APIError("unauthorized", action, error, customMessage);
666
- case 404:
667
- throw new APIError("not_found", action, error, customMessage);
668
- case 422:
669
- throw new APIError("unprocessable_entity", action, error, customMessage);
670
- default:
671
- throw new APIError("network_error", action, error, customMessage);
672
- }
713
+ const errorId = getErrorId(error.response.status);
714
+ throw new APIError(errorId, action, error, customMessage);
715
+ }
716
+ const response = error?.response;
717
+ if (response?.status) {
718
+ const wrappedError = new FetchError(
719
+ response.statusText ?? error.message,
720
+ { status: response.status, statusText: response.statusText ?? "", data: response.data }
721
+ );
722
+ const errorId = getErrorId(response.status);
723
+ throw new APIError(errorId, action, wrappedError, customMessage);
673
724
  }
674
725
  throw new APIError("generic", action, error, customMessage);
675
726
  }
@@ -1888,34 +1939,10 @@ const getUser = async (token, region) => {
1888
1939
  });
1889
1940
  return data?.user;
1890
1941
  } catch (error) {
1891
- if (error instanceof FetchError) {
1892
- const status = error.response.status;
1893
- switch (status) {
1894
- case 401:
1895
- throw new APIError("unauthorized", "get_user", error, `The token provided ${chalk.bold(maskToken(token))} is invalid.
1896
- Please make sure you are using the correct token and try again.`);
1897
- default:
1898
- throw new APIError("network_error", "get_user", error);
1899
- }
1900
- }
1901
- if (typeof error === "string" && error === "Unauthorized") {
1902
- const mockFetchError = new FetchError("Non-JSON response", {
1903
- status: 401,
1904
- statusText: "Unauthorized",
1905
- data: null
1906
- });
1907
- throw new APIError("unauthorized", "get_user", mockFetchError, `The token provided ${chalk.bold(maskToken(token))} is invalid.
1908
- Please make sure you are using the correct token and try again.`);
1909
- }
1910
- if (typeof error === "object" && error !== null && Object.keys(error).length === 0) {
1911
- const mockFetchError = new FetchError("Network Error", {
1912
- status: 500,
1913
- statusText: "Internal Server Error",
1914
- data: null
1915
- });
1916
- throw new APIError("network_error", "get_user", mockFetchError);
1917
- }
1918
- throw new APIError("generic", "get_user", error);
1942
+ const status = error?.response?.status;
1943
+ const customMessage = status === 401 ? `The token provided ${chalk.bold(maskToken(token))} is invalid.
1944
+ Please make sure you are using the correct token and try again.` : void 0;
1945
+ handleAPIError("get_user", error, customMessage);
1919
1946
  }
1920
1947
  };
1921
1948
 
@@ -1926,17 +1953,7 @@ const loginWithToken = async (token, region) => {
1926
1953
  if (error instanceof APIError) {
1927
1954
  throw error;
1928
1955
  }
1929
- if (error instanceof FetchError) {
1930
- const status = error.response.status;
1931
- switch (status) {
1932
- case 401:
1933
- throw new APIError("unauthorized", "login_with_token", error, `The token provided ${chalk.bold(maskToken(token))} is invalid.
1934
- Please make sure you are using the correct token and try again.`);
1935
- default:
1936
- throw new APIError("network_error", "login_with_token", error);
1937
- }
1938
- }
1939
- throw new APIError("generic", "login_with_token", error, "The provided credentials are invalid");
1956
+ handleAPIError("login_with_token", error);
1940
1957
  }
1941
1958
  };
1942
1959
  const loginWithEmailAndPassword = async (email, password, region) => {
@@ -4025,13 +4042,13 @@ const createStory = async (spaceId, payload) => {
4025
4042
  },
4026
4043
  body: {
4027
4044
  story: payload.story,
4028
- publish: payload.publish
4045
+ ...payload.publish ? { publish: payload.publish } : {}
4029
4046
  },
4030
4047
  throwOnError: true
4031
4048
  });
4032
4049
  return data?.story;
4033
- } catch (maybeError) {
4034
- handleAPIError("create_story", toError(maybeError));
4050
+ } catch (error) {
4051
+ handleAPIError("create_story", error);
4035
4052
  }
4036
4053
  };
4037
4054
  const updateStory = async (spaceId, storyId, payload) => {
@@ -4045,18 +4062,20 @@ const updateStory = async (spaceId, storyId, payload) => {
4045
4062
  body: {
4046
4063
  story: payload.story,
4047
4064
  force_update: payload.force_update === "1" ? "1" : "0",
4048
- publish: payload.publish
4065
+ ...payload.publish ? { publish: payload.publish } : {}
4049
4066
  },
4050
4067
  throwOnError: true
4051
4068
  });
4052
- const { story } = data;
4069
+ const story = data?.story;
4053
4070
  if (!story) {
4054
4071
  throw new Error("Failed to update story");
4055
4072
  }
4056
4073
  return story;
4057
- } catch (maybeError) {
4058
- handleAPIError("update_story", toError(maybeError));
4059
- throw maybeError;
4074
+ } catch (error) {
4075
+ if (error instanceof Error && error.message === "Failed to update story") {
4076
+ throw error;
4077
+ }
4078
+ handleAPIError("update_story", error);
4060
4079
  }
4061
4080
  };
4062
4081
 
@@ -5299,6 +5318,121 @@ const storyblokSchemas = /* @__PURE__ */ new Map([
5299
5318
  ["richtext", getRichtextJSONSchema]
5300
5319
  ]);
5301
5320
 
5321
+ function generateStoryblokImports(storyblokPropertyTypes, STORY_TYPE) {
5322
+ const imports = [];
5323
+ const needsISbStoryData = storyblokPropertyTypes.has(STORY_TYPE);
5324
+ if (needsISbStoryData) {
5325
+ imports.push(`import type { ${STORY_TYPE} } from '@storyblok/js';`);
5326
+ storyblokPropertyTypes.delete(STORY_TYPE);
5327
+ }
5328
+ if (storyblokPropertyTypes.size > 0) {
5329
+ const typeImports = Array.from(storyblokPropertyTypes).map((type) => {
5330
+ const pascalType = toPascalCase(type);
5331
+ return `Storyblok${pascalType}`;
5332
+ });
5333
+ imports.push(`import type { ${typeImports.join(", ")} } from '../storyblok.d.ts';`);
5334
+ }
5335
+ return imports;
5336
+ }
5337
+ function escapeRegExp(value) {
5338
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5339
+ }
5340
+ function hasIdentifier(content, identifier) {
5341
+ const regex = new RegExp(`\\b${escapeRegExp(identifier)}\\b`, "g");
5342
+ return regex.test(content);
5343
+ }
5344
+ function detectUsedStoryblokTypes(content, storyblokPropertyTypes, STORY_TYPE) {
5345
+ const usedTypes = [];
5346
+ if (content.includes(STORY_TYPE)) {
5347
+ usedTypes.push(STORY_TYPE);
5348
+ }
5349
+ Array.from(storyblokPropertyTypes).forEach((type) => {
5350
+ const pascalType = toPascalCase(type);
5351
+ if (content.includes(`Storyblok${pascalType}`)) {
5352
+ usedTypes.push(type);
5353
+ }
5354
+ });
5355
+ return usedTypes;
5356
+ }
5357
+ function detectUsedDatasourceTypes(content, datasourceResults) {
5358
+ return datasourceResults.filter((ds) => hasIdentifier(content, ds.title)).map((ds) => ds.title);
5359
+ }
5360
+ function detectReferencedComponents(content, currentTitle, componentResults) {
5361
+ return componentResults.filter((c) => c.title !== currentTitle && hasIdentifier(content, c.title)).map((c) => c.title);
5362
+ }
5363
+ function generateComponentImports(componentContent, componentTitle, storyblokPropertyTypes, datasourceResults, componentResults, STORY_TYPE) {
5364
+ const imports = [];
5365
+ const usedStoryblokTypes = detectUsedStoryblokTypes(
5366
+ componentContent,
5367
+ storyblokPropertyTypes,
5368
+ STORY_TYPE
5369
+ );
5370
+ if (usedStoryblokTypes.length > 0) {
5371
+ const hasISbStoryData = usedStoryblokTypes.includes(STORY_TYPE);
5372
+ const otherTypes = usedStoryblokTypes.filter((t) => t !== STORY_TYPE);
5373
+ if (hasISbStoryData) {
5374
+ imports.push(`import type { ${STORY_TYPE} } from '@storyblok/js';`);
5375
+ }
5376
+ if (otherTypes.length > 0) {
5377
+ const typeImports = otherTypes.map((type) => {
5378
+ const pascalType = toPascalCase(type);
5379
+ return `Storyblok${pascalType}`;
5380
+ });
5381
+ imports.push(`import type { ${typeImports.join(", ")} } from '../storyblok.d.ts';`);
5382
+ }
5383
+ }
5384
+ const usedDatasourceTypes = detectUsedDatasourceTypes(componentContent, datasourceResults);
5385
+ if (usedDatasourceTypes.length > 0) {
5386
+ imports.push(`import type { ${usedDatasourceTypes.join(", ")} } from './datasource-types.d.ts';`);
5387
+ }
5388
+ const referencedComponents = detectReferencedComponents(
5389
+ componentContent,
5390
+ componentTitle,
5391
+ componentResults
5392
+ );
5393
+ if (referencedComponents.length > 0) {
5394
+ const componentImportsStr = referencedComponents.map((name) => `import type { ${name} } from './${name}.d.ts';`).join("\n");
5395
+ imports.push(componentImportsStr);
5396
+ }
5397
+ return imports;
5398
+ }
5399
+ function createDatasourcesFile(datasourceResults, typeDefs) {
5400
+ if (datasourceResults.length === 0) {
5401
+ return null;
5402
+ }
5403
+ const content = [
5404
+ ...typeDefs,
5405
+ ...datasourceResults.map((r) => r.content)
5406
+ ].join("\n");
5407
+ return { name: "datasource-types", content };
5408
+ }
5409
+ function createContentTypesFile(contentTypeBloks, typeDefs) {
5410
+ if (contentTypeBloks.size === 0) {
5411
+ return null;
5412
+ }
5413
+ const contentTypeNames = Array.from(contentTypeBloks);
5414
+ const imports = contentTypeNames.map((name) => `import type { ${name} } from './${name}.d.ts';`).join("\n");
5415
+ const typeUnion = contentTypeNames.length > 0 ? contentTypeNames.join("\n | ") : "never";
5416
+ const typeDefinition = `export type ContentType =
5417
+ | ${typeUnion};`;
5418
+ const content = [
5419
+ ...typeDefs,
5420
+ imports,
5421
+ typeDefinition
5422
+ ].join("\n");
5423
+ return { name: "content-types", content };
5424
+ }
5425
+ function createComponentFile(componentResult, typeDefs, componentImports) {
5426
+ return {
5427
+ name: componentResult.title,
5428
+ content: [
5429
+ ...typeDefs,
5430
+ ...componentImports,
5431
+ componentResult.content
5432
+ ].join("\n")
5433
+ };
5434
+ }
5435
+
5302
5436
  const STORY_TYPE = "ISbStoryData";
5303
5437
  const DEFAULT_COMPONENT_FILENAME = "storyblok-components";
5304
5438
  const DEFAULT_TYPEDEFS_HEADER = [
@@ -5390,6 +5524,9 @@ const getComponentType = (componentName, options) => {
5390
5524
  return isFirstCharacterNumber ? `_${componentType}` : componentType;
5391
5525
  };
5392
5526
  const getComponentPropertiesTypeAnnotations = async (component, options, spaceData, customFieldsParser) => {
5527
+ if (!component.schema || typeof component.schema !== "object") {
5528
+ return {};
5529
+ }
5393
5530
  return Object.entries(component.schema).reduce(async (accPromise, [key, value]) => {
5394
5531
  const acc = await accPromise;
5395
5532
  if (key.startsWith("tab-")) {
@@ -5551,6 +5688,9 @@ const generateTypes = async (spaceData, options = {
5551
5688
  const datasourcesSchema = spaceData.datasources.map(async (datasource) => {
5552
5689
  const allComponentTypes = resolvedComponentsSchema.map((schema) => schema.title);
5553
5690
  const enumValues = datasource.entries?.filter((d) => d.value).map((d) => d.value);
5691
+ if (!datasource.slug) {
5692
+ return null;
5693
+ }
5554
5694
  const type = getDatasourceTypeTitle(datasource.slug);
5555
5695
  if (allComponentTypes.includes(type)) {
5556
5696
  console.warn(`Warning: Datasource type "${type}" conflicts with existing component type`);
@@ -5563,7 +5703,10 @@ const generateTypes = async (spaceData, options = {
5563
5703
  };
5564
5704
  return datasourceSchema;
5565
5705
  });
5566
- const resolvedDatasourcesSchema = await Promise.all(datasourcesSchema);
5706
+ const resolvedDatasourcesSchemaWithNulls = await Promise.all(datasourcesSchema);
5707
+ const resolvedDatasourcesSchema = resolvedDatasourcesSchemaWithNulls.filter((s) => s !== null);
5708
+ const componentTitles = new Set(resolvedComponentsSchema.map((s) => s.title).filter(Boolean));
5709
+ const datasourceTitles = new Set(resolvedDatasourcesSchema.map((s) => s.title).filter(Boolean));
5567
5710
  const contentTypeSchema = {
5568
5711
  $id: `#/ContentType`,
5569
5712
  title: "ContentType",
@@ -5576,26 +5719,48 @@ const generateTypes = async (spaceData, options = {
5576
5719
  contentTypeSchema
5577
5720
  ];
5578
5721
  const result = await Promise.all(schemas.map(async (schema) => {
5579
- return await compile(schema, schema.title || schema.$id.replace("#/", ""), {
5580
- additionalProperties: !options.strict,
5581
- bannerComment: "",
5582
- ...compilerOptions
5583
- });
5722
+ const title = schema.title || schema.$id.replace("#/", "");
5723
+ return {
5724
+ title,
5725
+ content: await compile(schema, title, {
5726
+ additionalProperties: !options.strict,
5727
+ bannerComment: "",
5728
+ ...compilerOptions
5729
+ }),
5730
+ isComponent: componentTitles.has(title),
5731
+ isDatasource: datasourceTitles.has(title)
5732
+ };
5584
5733
  }));
5585
- const imports = [];
5586
- const needsISbStoryData = storyblokPropertyTypes.has(STORY_TYPE);
5587
- if (needsISbStoryData) {
5588
- imports.push(`import type { ${STORY_TYPE} } from '@storyblok/js';`);
5589
- storyblokPropertyTypes.delete(STORY_TYPE);
5590
- }
5591
- if (storyblokPropertyTypes.size > 0) {
5592
- const typeImports = Array.from(storyblokPropertyTypes).map((type) => {
5593
- const pascalType = toPascalCase(type);
5594
- return `Storyblok${pascalType}`;
5595
- });
5596
- imports.push(`import type { ${typeImports.join(", ")} } from '../storyblok.d.ts';`);
5734
+ const imports = generateStoryblokImports(storyblokPropertyTypes, STORY_TYPE);
5735
+ if (options.separateFiles) {
5736
+ const files = [];
5737
+ const datasourceResults = result.filter((r) => r.isDatasource);
5738
+ const componentResults = result.filter((r) => r.isComponent);
5739
+ const datasourcesFile = createDatasourcesFile(datasourceResults, typeDefs);
5740
+ if (datasourcesFile) {
5741
+ files.push(datasourcesFile);
5742
+ }
5743
+ const contentTypesFile = createContentTypesFile(
5744
+ contentTypeBloks,
5745
+ typeDefs
5746
+ );
5747
+ if (contentTypesFile) {
5748
+ files.push(contentTypesFile);
5749
+ }
5750
+ for (const componentResult of componentResults) {
5751
+ const componentImports = generateComponentImports(
5752
+ componentResult.content,
5753
+ componentResult.title,
5754
+ storyblokPropertyTypes,
5755
+ datasourceResults,
5756
+ componentResults,
5757
+ STORY_TYPE
5758
+ );
5759
+ files.push(createComponentFile(componentResult, typeDefs, componentImports));
5760
+ }
5761
+ return files;
5597
5762
  }
5598
- const finalTypeDef = [...typeDefs, ...imports, ...result];
5763
+ const finalTypeDef = [...typeDefs, ...imports, ...result.map((r) => r.content)];
5599
5764
  return [
5600
5765
  ...finalTypeDef
5601
5766
  ].join("\n");
@@ -5603,11 +5768,17 @@ const generateTypes = async (spaceData, options = {
5603
5768
  handleError(error);
5604
5769
  }
5605
5770
  };
5606
- const saveTypesToComponentsFile = async (space, typedefString, options) => {
5607
- const { filename = DEFAULT_COMPONENT_FILENAME, path } = options;
5771
+ const saveTypesToComponentsFile = async (space, typedefData, options) => {
5772
+ const { filename = DEFAULT_COMPONENT_FILENAME, path, separateFiles } = options;
5608
5773
  const resolvedPath = path ? resolve$1(process.cwd(), path, "types", space) : resolvePath(path, `types/${space}`);
5609
5774
  try {
5610
- await saveToFile(join(resolvedPath, `${filename}.d.ts`), typedefString);
5775
+ if (separateFiles && Array.isArray(typedefData)) {
5776
+ for (const { name, content } of typedefData) {
5777
+ await saveToFile(join(resolvedPath, `${name}.d.ts`), content);
5778
+ }
5779
+ } else if (typeof typedefData === "string") {
5780
+ await saveToFile(join(resolvedPath, `${filename}.d.ts`), typedefData);
5781
+ }
5611
5782
  } catch (error) {
5612
5783
  handleFileSystemError("write", error);
5613
5784
  }
@@ -5801,16 +5972,18 @@ generateCmd.action(async (options, command) => {
5801
5972
  try {
5802
5973
  spinner.start(`Generating types...`);
5803
5974
  const componentsData = await readComponentsFiles({
5804
- ...options,
5805
5975
  from: space,
5806
- path
5976
+ path,
5977
+ suffix: options.suffix,
5978
+ verbose
5807
5979
  });
5808
5980
  let dataSourceData;
5809
5981
  try {
5810
5982
  dataSourceData = await readDatasourcesFiles({
5811
- ...options,
5812
5983
  from: space,
5813
- path
5984
+ path,
5985
+ suffix: options.suffix,
5986
+ verbose
5814
5987
  });
5815
5988
  } catch (error) {
5816
5989
  if (error instanceof FileSystemError && error.errorId === "file_not_found") {
@@ -5826,17 +5999,21 @@ generateCmd.action(async (options, command) => {
5826
5999
  ...componentsData,
5827
6000
  ...dataSourceData
5828
6001
  };
5829
- const typedefString = await generateTypes(spaceDataWithComponentsAndDatasources, {
6002
+ const typedefData = await generateTypes(spaceDataWithComponentsAndDatasources, {
5830
6003
  ...options,
5831
6004
  path
5832
6005
  });
5833
- if (typedefString) {
5834
- await saveTypesToComponentsFile(space, typedefString, {
6006
+ if (typedefData) {
6007
+ await saveTypesToComponentsFile(space, typedefData, {
5835
6008
  filename: options.filename,
5836
- path
6009
+ path,
6010
+ separateFiles: options.separateFiles
5837
6011
  });
5838
6012
  }
5839
6013
  spinner.succeed();
6014
+ if (options.separateFiles && options.filename) {
6015
+ konsola.warn(`The --filename option is ignored when using --separate-files`);
6016
+ }
5840
6017
  konsola.ok(`Successfully generated types for space ${space}`, true);
5841
6018
  konsola.br();
5842
6019
  } catch (error) {
@@ -7302,31 +7479,25 @@ const readLocalAssetsStream = ({
7302
7479
  const iterator = async function* readAssets() {
7303
7480
  try {
7304
7481
  const files = await readdir(directoryPath);
7305
- const metadataFiles = files.filter((file) => file.endsWith(".json") && file !== "manifest.jsonl");
7306
- setTotalAssets?.(metadataFiles.length);
7307
- for (const file of metadataFiles) {
7308
- const filePath = join(directoryPath, file);
7482
+ const binaryFiles = files.filter((f) => SUPPORTED_ASSET_EXTENSIONS.has(extname(f).toLowerCase()));
7483
+ setTotalAssets?.(binaryFiles.length);
7484
+ for (const file of binaryFiles) {
7485
+ const binaryFilePath = join(directoryPath, file);
7309
7486
  try {
7310
- const statResult = await stat(filePath);
7311
- if (!statResult.isFile()) {
7312
- continue;
7313
- }
7314
- const metadataContent = await readFile$1(filePath, "utf8");
7315
- const assetRaw = JSON.parse(metadataContent);
7487
+ const sidecar = await loadSidecarAssetData(binaryFilePath);
7488
+ const shortFilename = sidecar.short_filename || (sidecar.filename ? basename(sidecar.filename) : void 0) || file;
7316
7489
  const asset = {
7317
- ...assetRaw,
7318
- short_filename: assetRaw.short_filename || basename(assetRaw.filename)
7490
+ ...sidecar,
7491
+ short_filename: shortFilename
7319
7492
  };
7320
- const baseName = parse(file).name;
7321
- const extFromMetadata = extname(asset.short_filename || asset.filename) || "";
7322
- const assetBinaryPath = join(directoryPath, `${baseName}${extFromMetadata}`);
7323
- const fileBuffer = await readFile$1(assetBinaryPath);
7493
+ const fileBuffer = await readFile$1(binaryFilePath);
7494
+ const sidecarPath = getSidecarFilename(binaryFilePath);
7324
7495
  yield {
7325
7496
  asset,
7326
7497
  context: {
7327
7498
  fileBuffer,
7328
- assetBinaryPath,
7329
- assetPath: filePath
7499
+ assetBinaryPath: binaryFilePath,
7500
+ assetPath: sidecarPath
7330
7501
  }
7331
7502
  };
7332
7503
  } catch (maybeError) {
@@ -7710,6 +7881,9 @@ const traverseAndMapBySchema = (data, {
7710
7881
  processedFields,
7711
7882
  missingSchemas
7712
7883
  }) => {
7884
+ if (!data?.component) {
7885
+ return data ?? {};
7886
+ }
7713
7887
  const schema = schemas[data.component];
7714
7888
  if (!schema) {
7715
7889
  missingSchemas.add(data.component);
@@ -7753,7 +7927,7 @@ const traverseAndMapRichtextDoc = (data, {
7753
7927
  }));
7754
7928
  }
7755
7929
  if (data && typeof data === "object") {
7756
- if (data.type === "link" && data.attrs.linktype === "story") {
7930
+ if (data.type === "link" && data.attrs?.linktype === "story") {
7757
7931
  return {
7758
7932
  ...data,
7759
7933
  attrs: {
@@ -7767,7 +7941,7 @@ const traverseAndMapRichtextDoc = (data, {
7767
7941
  ...data,
7768
7942
  attrs: {
7769
7943
  ...data.attrs,
7770
- body: data.attrs.body.map((d) => traverseAndMapBySchema(d, {
7944
+ body: (data.attrs?.body ?? []).map((d) => traverseAndMapBySchema(d, {
7771
7945
  schemas,
7772
7946
  maps,
7773
7947
  fieldRefMappers: fieldRefMappers2,
@@ -7809,7 +7983,7 @@ const multilinkFieldRefMapper = (data, { maps }) => {
7809
7983
  };
7810
7984
  const bloksFieldRefMapper = (data, { schemas, maps, fieldRefMappers: fieldRefMappers2, processedFields, missingSchemas }) => {
7811
7985
  if (!Array.isArray(data)) {
7812
- throw new TypeError("Invalid data!");
7986
+ throw new TypeError(`Invalid bloks field: expected an array, but received ${JSON.stringify(data)}. Please make sure your bloks field value is an array of components (e.g. [{ component: "my_blok", ... }]).`);
7813
7987
  }
7814
7988
  return data.map((d) => traverseAndMapBySchema(d, {
7815
7989
  schemas,
@@ -7828,7 +8002,7 @@ const assetFieldRefMapper = (data, { maps }) => {
7828
8002
  };
7829
8003
  const multiassetFieldRefMapper = (data, options) => {
7830
8004
  if (!Array.isArray(data)) {
7831
- throw new TypeError("Invalid data!");
8005
+ throw new TypeError(`Invalid multiasset field: expected an array, but received ${JSON.stringify(data)}. Please make sure your multiasset field value is an array of asset objects (e.g. [{ filename: "...", id: 123 }]).`);
7832
8006
  }
7833
8007
  return data.map((d) => assetFieldRefMapper(d, options));
7834
8008
  };
@@ -7851,23 +8025,23 @@ const storyRefMapper = (story, { schemas, maps }) => {
7851
8025
  const missingSchemas = /* @__PURE__ */ new Set();
7852
8026
  const alternates = story.alternates ? story.alternates.map((a) => ({
7853
8027
  ...a,
7854
- id: maps.stories?.get(a.id) || a.id,
7855
- parent_id: maps.stories?.get(a.parent_id) || a.parent_id
8028
+ id: maps.stories?.get(a.id) ?? a.id,
8029
+ parent_id: maps.stories?.get(a.parent_id) ?? a.parent_id
7856
8030
  })) : story.alternates;
7857
- const parentId = maps.stories?.get(story.parent_id) || story.parent_id;
8031
+ const parentId = maps.stories?.get(story.parent_id) ?? story.parent_id;
7858
8032
  const mappedStory = {
7859
8033
  ...story,
7860
- content: traverseAndMapBySchema(story.content, {
8034
+ content: story.content?.component ? traverseAndMapBySchema(story.content, {
7861
8035
  schemas,
7862
8036
  maps,
7863
8037
  fieldRefMappers,
7864
8038
  processedFields,
7865
8039
  missingSchemas
7866
- }),
7867
- id: Number(maps.stories?.get(story.id) || story.id),
7868
- uuid: String(maps.stories?.get(story.uuid) || story.uuid),
8040
+ }) : story.content,
8041
+ id: Number(maps.stories?.get(story.id) ?? story.id),
8042
+ uuid: String(maps.stories?.get(story.uuid) ?? story.uuid),
7869
8043
  // @ts-expect-error Our types are wrong.
7870
- parent_id: parentId ? Number(parentId) : null,
8044
+ parent_id: parentId != null ? Number(parentId) : null,
7871
8045
  alternates
7872
8046
  };
7873
8047
  return {
@@ -8029,14 +8203,14 @@ const getRemoteStory = async ({ spaceId, storyId }) => {
8029
8203
  return data?.story;
8030
8204
  };
8031
8205
  const makeCreateStoryAPITransport = ({ spaceId }) => async (localStory) => {
8032
- const { id: _id, uuid: _uuid, content, parent_id: _p, ...newStoryData } = localStory;
8206
+ const { id: _id, uuid: _uuid, parent_id: _parentId, content, ...newStoryData } = localStory;
8207
+ if (!localStory.is_folder && !content?.component) {
8208
+ throw new Error(`Story "${localStory.slug}" is missing a content type (content.component). Every story must define a content field with a valid component.`);
8209
+ }
8033
8210
  const remoteStory = await createStory(spaceId, {
8034
8211
  story: {
8035
8212
  ...newStoryData,
8036
- content: {
8037
- _uid: "",
8038
- component: "__tmp__"
8039
- }
8213
+ ...content?.component ? { content: { _uid: "", component: "__migration_artifact__" } } : {}
8040
8214
  },
8041
8215
  publish: 0
8042
8216
  });
@@ -8112,7 +8286,7 @@ const makeWriteStoryFSTransport = ({ directoryPath }) => async (story) => {
8112
8286
  };
8113
8287
  const makeWriteStoryAPITransport = ({ spaceId, publish }) => (mappedLocalStory) => updateStory(spaceId, mappedLocalStory.id, {
8114
8288
  story: mappedLocalStory,
8115
- publish: publish ?? (mappedLocalStory.published ? 1 : 0)
8289
+ publish: publish ?? (isStoryPublishedWithoutChanges(mappedLocalStory) ? 1 : 0)
8116
8290
  });
8117
8291
  const makeCleanupStoryFSTransport = ({ directoryPath, maps }) => async (mappedStory) => {
8118
8292
  const mapEntry = maps.stories?.entries().find(([_, v]) => v === mappedStory.uuid);
@@ -8738,14 +8912,14 @@ pushCmd.action(async (options, command) => {
8738
8912
  processProgress.setTotal(total);
8739
8913
  updateProgress.setTotal(total);
8740
8914
  },
8741
- onStoryError(error) {
8915
+ onStoryError(error, filename) {
8742
8916
  summary.creationResults.failed += 1;
8743
8917
  summary.processResults.total -= 1;
8744
8918
  summary.updateResults.total -= 1;
8745
8919
  processProgress.setTotal(summary.processResults.total);
8746
8920
  updateProgress.setTotal(summary.updateResults.total);
8747
8921
  creationProgress.increment();
8748
- handleError(error, verbose);
8922
+ handleError(error, verbose, { storyFile: filename });
8749
8923
  }
8750
8924
  }),
8751
8925
  // Create remote stories.
@@ -8754,7 +8928,6 @@ pushCmd.action(async (options, command) => {
8754
8928
  spaceId: space,
8755
8929
  transports: {
8756
8930
  createStory: options.dryRun ? async (story) => story : makeCreateStoryAPITransport({
8757
- maps,
8758
8931
  spaceId: space
8759
8932
  }),
8760
8933
  appendStoryManifest: options.dryRun ? () => Promise.resolve() : makeAppendToManifestFSTransport({
@@ -8779,13 +8952,13 @@ pushCmd.action(async (options, command) => {
8779
8952
  logger.info("Skipped creating story", { storyId: localStory.uuid });
8780
8953
  summary.creationResults.skipped += 1;
8781
8954
  },
8782
- onStoryError(error) {
8955
+ onStoryError(error, localStory) {
8783
8956
  summary.creationResults.failed += 1;
8784
8957
  summary.processResults.total -= 1;
8785
8958
  summary.updateResults.total -= 1;
8786
8959
  processProgress.setTotal(summary.processResults.total);
8787
8960
  updateProgress.setTotal(summary.updateResults.total);
8788
- handleError(error, verbose);
8961
+ handleError(error, verbose, { storyId: localStory?.uuid });
8789
8962
  },
8790
8963
  onIncrement() {
8791
8964
  creationProgress.increment();
@@ -8805,13 +8978,13 @@ pushCmd.action(async (options, command) => {
8805
8978
  processProgress.setTotal(total);
8806
8979
  updateProgress.setTotal(total);
8807
8980
  },
8808
- onStoryError(error) {
8981
+ onStoryError(error, filename) {
8809
8982
  summary.creationResults.failed += 1;
8810
8983
  summary.processResults.total -= 1;
8811
8984
  summary.updateResults.total -= 1;
8812
8985
  processProgress.setTotal(summary.processResults.total);
8813
8986
  updateProgress.setTotal(summary.updateResults.total);
8814
- handleError(error, verbose);
8987
+ handleError(error, verbose, { storyFile: filename });
8815
8988
  }
8816
8989
  }),
8817
8990
  // Map all references to numeric ids and uuids.