storyblok 4.15.0 → 4.15.2

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
@@ -1,17 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import 'dotenv/config';
3
3
  import { fileURLToPath, pathToFileURL } from 'node:url';
4
- import { resolve, dirname, isAbsolute, relative as relative$1, join as join$1 } from 'pathe';
4
+ import { resolve, dirname, join, parse, extname, relative, isAbsolute, basename } from 'pathe';
5
5
  import { existsSync, mkdirSync, appendFileSync, writeFileSync, readdirSync, unlinkSync, readFileSync } from 'node:fs';
6
6
  import { homedir } from 'node:os';
7
7
  import { loadConfig as loadConfig$1, SUPPORTED_EXTENSIONS } from 'c12';
8
8
  import chalk from 'chalk';
9
9
  import { readPackageUp } from 'read-package-up';
10
10
  import { Command } from 'commander';
11
- import path, { join, resolve as resolve$1, parse, dirname as dirname$1, extname, relative, basename } from 'node:path';
12
11
  import { MultiBar, Presets } from 'cli-progress';
13
12
  import { Spinner } from '@topcli/spinner';
14
- import fs, { mkdir, writeFile, readFile as readFile$1, appendFile, access, constants, readdir, unlink, stat } from 'node:fs/promises';
13
+ import fs, { mkdir, writeFile, readFile as readFile$1, appendFile, access, constants, readdir, unlink } from 'node:fs/promises';
15
14
  import filenamify from 'filenamify';
16
15
  import { ManagementApiClient } from '@storyblok/management-api-client';
17
16
  import { RateLimit, Sema } from 'async-sema';
@@ -103,6 +102,44 @@ const regionNames = {
103
102
  ({
104
103
  SB_Agent_Version: process.env.npm_package_version || "4.x"
105
104
  });
105
+ const SUPPORTED_ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
106
+ // Images: image/png, image/x-png, image/gif, image/jpeg, image/avif, image/svg+xml, image/webp
107
+ ".jpg",
108
+ ".jpeg",
109
+ ".png",
110
+ ".gif",
111
+ ".webp",
112
+ ".avif",
113
+ ".svg",
114
+ // Video: video/*, application/mp4, application/x-mpegurl, application/vnd.apple.mpegurl
115
+ ".mp4",
116
+ ".mov",
117
+ ".avi",
118
+ ".webm",
119
+ ".wmv",
120
+ ".mkv",
121
+ ".flv",
122
+ ".ogv",
123
+ ".3gp",
124
+ ".m4v",
125
+ ".mpg",
126
+ ".mpeg",
127
+ ".m3u8",
128
+ // Audio: audio/*
129
+ ".mp3",
130
+ ".wav",
131
+ ".ogg",
132
+ ".aac",
133
+ ".flac",
134
+ ".wma",
135
+ ".m4a",
136
+ ".opus",
137
+ // Documents: application/msword, text/plain, application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document
138
+ ".pdf",
139
+ ".doc",
140
+ ".docx",
141
+ ".txt"
142
+ ]);
106
143
  const directories = {
107
144
  assets: "assets",
108
145
  components: "components",
@@ -651,25 +688,38 @@ const API_ACTIONS = {
651
688
  const API_ERRORS = {
652
689
  unauthorized: "The user is not authorized to access the API",
653
690
  network_error: "No response from server, please check if you are correctly connected to internet",
691
+ server_error: "The server returned an error",
654
692
  invalid_credentials: "The provided credentials are invalid",
655
693
  timeout: "The API request timed out",
656
694
  generic: "Error fetching data from the API",
657
695
  not_found: "The requested resource was not found",
658
696
  unprocessable_entity: "The request was well-formed but was unable to be followed due to semantic errors"
659
697
  };
698
+ function getErrorId(status) {
699
+ switch (status) {
700
+ case 401:
701
+ return "unauthorized";
702
+ case 404:
703
+ return "not_found";
704
+ case 422:
705
+ return "unprocessable_entity";
706
+ default:
707
+ return status >= 500 ? "server_error" : "generic";
708
+ }
709
+ }
660
710
  function handleAPIError(action, error, customMessage) {
661
711
  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
- }
712
+ const errorId = getErrorId(error.response.status);
713
+ throw new APIError(errorId, action, error, customMessage);
714
+ }
715
+ const response = error?.response;
716
+ if (response?.status) {
717
+ const wrappedError = new FetchError(
718
+ response.statusText ?? error.message,
719
+ { status: response.status, statusText: response.statusText ?? "", data: response.data }
720
+ );
721
+ const errorId = getErrorId(response.status);
722
+ throw new APIError(errorId, action, wrappedError, customMessage);
673
723
  }
674
724
  throw new APIError("generic", action, error, customMessage);
675
725
  }
@@ -1258,7 +1308,7 @@ const deduplicateManifest = async (manifestFile) => {
1258
1308
  };
1259
1309
  const resolvePath = (path, folder) => {
1260
1310
  const basePath = path ?? DEFAULT_STORAGE_DIR;
1261
- return resolve$1(process.cwd(), basePath, folder);
1311
+ return resolve(process.cwd(), basePath, folder);
1262
1312
  };
1263
1313
  function resolveCommandPath(commandPath, space, baseDir) {
1264
1314
  if (space) {
@@ -1370,7 +1420,7 @@ class Reporter {
1370
1420
  if (this.maxFiles === void 0) {
1371
1421
  return;
1372
1422
  }
1373
- const dir = dirname$1(this.filePath);
1423
+ const dir = dirname(this.filePath);
1374
1424
  const ext = extname(this.filePath);
1375
1425
  Reporter.pruneReportFiles(dir, this.maxFiles, ext);
1376
1426
  }
@@ -1428,7 +1478,7 @@ class FileTransport {
1428
1478
  if (this.maxFiles === void 0) {
1429
1479
  return;
1430
1480
  }
1431
- const dir = dirname$1(this.filePath);
1481
+ const dir = dirname(this.filePath);
1432
1482
  const ext = extname(this.filePath);
1433
1483
  FileTransport.pruneLogFiles(dir, this.maxFiles, ext);
1434
1484
  }
@@ -1827,7 +1877,7 @@ function getProgram() {
1827
1877
  options.path
1828
1878
  );
1829
1879
  const logFilename = `${commandPieces.join("-")}-${runId}.jsonl`;
1830
- logFilePath = path.join(logsPath, logFilename);
1880
+ logFilePath = join(logsPath, logFilename);
1831
1881
  transports.push(
1832
1882
  new FileTransport({
1833
1883
  filePath: logFilePath,
@@ -1851,7 +1901,7 @@ function getProgram() {
1851
1901
  options.path
1852
1902
  );
1853
1903
  const reportFilename = `${commandPieces.join("-")}-${runId}.json`;
1854
- const reportFilePath = path.join(reportPath, reportFilename);
1904
+ const reportFilePath = join(reportPath, reportFilename);
1855
1905
  const reporter = getReporter({
1856
1906
  enabled: true,
1857
1907
  filePath: reportFilePath,
@@ -1888,34 +1938,10 @@ const getUser = async (token, region) => {
1888
1938
  });
1889
1939
  return data?.user;
1890
1940
  } 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);
1941
+ const status = error?.response?.status;
1942
+ const customMessage = status === 401 ? `The token provided ${chalk.bold(maskToken(token))} is invalid.
1943
+ Please make sure you are using the correct token and try again.` : void 0;
1944
+ handleAPIError("get_user", error, customMessage);
1919
1945
  }
1920
1946
  };
1921
1947
 
@@ -1926,17 +1952,7 @@ const loginWithToken = async (token, region) => {
1926
1952
  if (error instanceof APIError) {
1927
1953
  throw error;
1928
1954
  }
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");
1955
+ handleAPIError("login_with_token", error);
1940
1956
  }
1941
1957
  };
1942
1958
  const loginWithEmailAndPassword = async (email, password, region) => {
@@ -2322,7 +2338,7 @@ const fetchComponentInternalTags = async (spaceId) => {
2322
2338
  const saveComponentsToFiles = async (space, spaceData, options) => {
2323
2339
  const { components = [], groups = [], presets = [], internalTags = [] } = spaceData;
2324
2340
  const { filename = DEFAULT_COMPONENTS_FILENAME, suffix, path, separateFiles } = options;
2325
- const resolvedPath = path ? resolve$1(process.cwd(), path, "components", space) : resolvePath(path, `components/${space}`);
2341
+ const resolvedPath = path ? resolve(process.cwd(), path, "components", space) : resolvePath(path, `components/${space}`);
2326
2342
  try {
2327
2343
  if (separateFiles) {
2328
2344
  for (const component of components) {
@@ -2696,17 +2712,17 @@ pullCmd$4.action(async (componentName, options, command) => {
2696
2712
  konsola.warn(`The --filename option is ignored when using --separate-files`);
2697
2713
  }
2698
2714
  const filePath = `${componentsOutputDir}/`;
2699
- const displayPath = path && isAbsolute(path) ? filePath : `${relative$1(process.cwd(), componentsOutputDir)}/`;
2715
+ const displayPath = path && isAbsolute(path) ? filePath : `${relative(process.cwd(), componentsOutputDir)}/`;
2700
2716
  konsola.ok(`Components downloaded successfully to ${chalk.hex(colorPalette.PRIMARY)(displayPath)}`);
2701
2717
  } else if (componentName) {
2702
2718
  const fileName = suffix ? `${actualFilename}.${suffix}.json` : `${componentName}.json`;
2703
- const filePath = join$1(componentsOutputDir, fileName);
2704
- const displayPath = path && isAbsolute(path) ? filePath : relative$1(process.cwd(), filePath);
2719
+ const filePath = join(componentsOutputDir, fileName);
2720
+ const displayPath = path && isAbsolute(path) ? filePath : relative(process.cwd(), filePath);
2705
2721
  konsola.ok(`Component ${chalk.hex(colorPalette.PRIMARY)(componentName)} downloaded successfully in ${chalk.hex(colorPalette.PRIMARY)(displayPath)}`);
2706
2722
  } else {
2707
2723
  const fileName = suffix ? `${actualFilename}.${suffix}.json` : `${actualFilename}.json`;
2708
- const filePath = join$1(componentsOutputDir, fileName);
2709
- const displayPath = path && isAbsolute(path) ? filePath : relative$1(process.cwd(), filePath);
2724
+ const filePath = join(componentsOutputDir, fileName);
2725
+ const displayPath = path && isAbsolute(path) ? filePath : relative(process.cwd(), filePath);
2710
2726
  konsola.ok(`Components downloaded successfully to ${chalk.hex(colorPalette.PRIMARY)(displayPath)}`);
2711
2727
  }
2712
2728
  konsola.br();
@@ -3889,8 +3905,8 @@ pullCmd$3.action(async (options, command) => {
3889
3905
  });
3890
3906
  const languagesOutputDir = resolveCommandPath("languages", space, path);
3891
3907
  const fileName = suffix ? `${filename}.${suffix}.json` : `${filename}.json`;
3892
- const filePath = join$1(languagesOutputDir, fileName);
3893
- const displayPath = path && isAbsolute(path) ? filePath : relative$1(process.cwd(), filePath);
3908
+ const filePath = join(languagesOutputDir, fileName);
3909
+ const displayPath = path && isAbsolute(path) ? filePath : relative(process.cwd(), filePath);
3894
3910
  spinner.succeed();
3895
3911
  konsola.ok(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(displayPath)}`, true);
3896
3912
  } catch (error) {
@@ -3920,7 +3936,7 @@ const getMigrationTemplate = () => {
3920
3936
  `;
3921
3937
  };
3922
3938
  const generateMigration = async (space, path, component, suffix) => {
3923
- const resolvedPath = path ? resolve$1(process.cwd(), path, "migrations", space) : resolvePath(path, `migrations/${space}`);
3939
+ const resolvedPath = path ? resolve(process.cwd(), path, "migrations", space) : resolvePath(path, `migrations/${space}`);
3924
3940
  const fileName = suffix ? `${component.name}.${suffix}.js` : `${component.name}.js`;
3925
3941
  const migrationPath = join(resolvedPath, fileName);
3926
3942
  try {
@@ -4025,13 +4041,13 @@ const createStory = async (spaceId, payload) => {
4025
4041
  },
4026
4042
  body: {
4027
4043
  story: payload.story,
4028
- publish: payload.publish
4044
+ ...payload.publish ? { publish: payload.publish } : {}
4029
4045
  },
4030
4046
  throwOnError: true
4031
4047
  });
4032
4048
  return data?.story;
4033
- } catch (maybeError) {
4034
- handleAPIError("create_story", toError(maybeError));
4049
+ } catch (error) {
4050
+ handleAPIError("create_story", error);
4035
4051
  }
4036
4052
  };
4037
4053
  const updateStory = async (spaceId, storyId, payload) => {
@@ -4045,18 +4061,20 @@ const updateStory = async (spaceId, storyId, payload) => {
4045
4061
  body: {
4046
4062
  story: payload.story,
4047
4063
  force_update: payload.force_update === "1" ? "1" : "0",
4048
- publish: payload.publish
4064
+ ...payload.publish ? { publish: payload.publish } : {}
4049
4065
  },
4050
4066
  throwOnError: true
4051
4067
  });
4052
- const { story } = data;
4068
+ const story = data?.story;
4053
4069
  if (!story) {
4054
4070
  throw new Error("Failed to update story");
4055
4071
  }
4056
4072
  return story;
4057
- } catch (maybeError) {
4058
- handleAPIError("update_story", toError(maybeError));
4059
- throw maybeError;
4073
+ } catch (error) {
4074
+ if (error instanceof Error && error.message === "Failed to update story") {
4075
+ throw error;
4076
+ }
4077
+ handleAPIError("update_story", error);
4060
4078
  }
4061
4079
  };
4062
4080
 
@@ -4526,8 +4544,8 @@ const findComponentSchemas = async (directoryPath) => {
4526
4544
  }
4527
4545
  throw error;
4528
4546
  });
4529
- const fileContents = files.filter((f) => path.extname(f) === ".json").map((f) => {
4530
- const filePath = path.join(directoryPath, f);
4547
+ const fileContents = files.filter((f) => extname(f) === ".json").map((f) => {
4548
+ const filePath = join(directoryPath, f);
4531
4549
  const fileContent = readFileSync(filePath, "utf-8");
4532
4550
  return JSON.parse(fileContent);
4533
4551
  });
@@ -5592,7 +5610,7 @@ const getComponentPropertiesTypeAnnotations = async (component, options, spaceDa
5592
5610
  };
5593
5611
  const loadCustomFieldsParser = async (path) => {
5594
5612
  try {
5595
- const customFieldsParser = await import(resolve$1(path));
5613
+ const customFieldsParser = await import(pathToFileURL(resolve(path)).href);
5596
5614
  return customFieldsParser.default;
5597
5615
  } catch (error) {
5598
5616
  handleError(error);
@@ -5601,7 +5619,7 @@ const loadCustomFieldsParser = async (path) => {
5601
5619
  };
5602
5620
  async function loadCompilerOptions(path) {
5603
5621
  if (path) {
5604
- const compilerOptions = await import(resolve$1(path));
5622
+ const compilerOptions = await import(pathToFileURL(resolve(path)).href);
5605
5623
  return compilerOptions.default;
5606
5624
  }
5607
5625
  return {};
@@ -5751,7 +5769,7 @@ const generateTypes = async (spaceData, options = {
5751
5769
  };
5752
5770
  const saveTypesToComponentsFile = async (space, typedefData, options) => {
5753
5771
  const { filename = DEFAULT_COMPONENT_FILENAME, path, separateFiles } = options;
5754
- const resolvedPath = path ? resolve$1(process.cwd(), path, "types", space) : resolvePath(path, `types/${space}`);
5772
+ const resolvedPath = path ? resolve(process.cwd(), path, "types", space) : resolvePath(path, `types/${space}`);
5755
5773
  try {
5756
5774
  if (separateFiles && Array.isArray(typedefData)) {
5757
5775
  for (const { name, content } of typedefData) {
@@ -5767,7 +5785,7 @@ const saveTypesToComponentsFile = async (space, typedefData, options) => {
5767
5785
  const generateStoryblokTypes = async (options = {}) => {
5768
5786
  const { path } = options;
5769
5787
  try {
5770
- const storyblokTypesPath = resolve$1(__dirname, "./index.d.ts");
5788
+ const storyblokTypesPath = resolve(__dirname, "./index.d.ts");
5771
5789
  const storyblokTypesContent = readFileSync(storyblokTypesPath, "utf-8");
5772
5790
  const typeDefs = [
5773
5791
  "// This file was generated by the Storyblok CLI.",
@@ -5775,7 +5793,7 @@ const generateStoryblokTypes = async (options = {}) => {
5775
5793
  `import type { ${STORY_TYPE} } from '@storyblok/js';`,
5776
5794
  storyblokTypesContent
5777
5795
  ].join("\n");
5778
- const resolvedPath = path ? resolve$1(process.cwd(), path, "types") : resolvePath(path, "types");
5796
+ const resolvedPath = path ? resolve(process.cwd(), path, "types") : resolvePath(path, "types");
5779
5797
  await saveToFile(join(resolvedPath, `storyblok.d.ts`), typeDefs);
5780
5798
  return true;
5781
5799
  } catch (error) {
@@ -6094,7 +6112,7 @@ const fetchDatasource = async (spaceId, datasourceName) => {
6094
6112
  };
6095
6113
  const saveDatasourcesToFiles = async (space, datasources, options) => {
6096
6114
  const { filename = DEFAULT_DATASOURCES_FILENAME, suffix, path, separateFiles } = options;
6097
- const resolvedPath = path ? resolve$1(process.cwd(), path, "datasources", space) : resolvePath(path, `datasources/${space}`);
6115
+ const resolvedPath = path ? resolve(process.cwd(), path, "datasources", space) : resolvePath(path, `datasources/${space}`);
6098
6116
  try {
6099
6117
  if (separateFiles) {
6100
6118
  for (const datasource of datasources) {
@@ -6162,17 +6180,17 @@ pullCmd$2.action(async (datasourceName, options, command) => {
6162
6180
  konsola.warn(`The --filename option is ignored when using --separate-files`);
6163
6181
  }
6164
6182
  const filePath = `${datasourcesOutputDir}/`;
6165
- const displayPath = path && isAbsolute(path) ? filePath : `${relative$1(process.cwd(), datasourcesOutputDir)}/`;
6183
+ const displayPath = path && isAbsolute(path) ? filePath : `${relative(process.cwd(), datasourcesOutputDir)}/`;
6166
6184
  konsola.ok(`Datasources downloaded successfully to ${chalk.hex(colorPalette.PRIMARY)(displayPath)}`);
6167
6185
  } else if (datasourceName) {
6168
6186
  const fileName = suffix ? `${actualFilename}.${suffix}.json` : `${datasourceName}.json`;
6169
- const filePath = join$1(datasourcesOutputDir, fileName);
6170
- const displayPath = path && isAbsolute(path) ? filePath : relative$1(process.cwd(), filePath);
6187
+ const filePath = join(datasourcesOutputDir, fileName);
6188
+ const displayPath = path && isAbsolute(path) ? filePath : relative(process.cwd(), filePath);
6171
6189
  konsola.ok(`Datasource ${chalk.hex(colorPalette.PRIMARY)(datasourceName)} downloaded successfully in ${chalk.hex(colorPalette.PRIMARY)(displayPath)}`);
6172
6190
  } else {
6173
6191
  const fileName = suffix ? `${actualFilename}.${suffix}.json` : `${actualFilename}.json`;
6174
- const filePath = join$1(datasourcesOutputDir, fileName);
6175
- const displayPath = path && isAbsolute(path) ? filePath : relative$1(process.cwd(), filePath);
6192
+ const filePath = join(datasourcesOutputDir, fileName);
6193
+ const displayPath = path && isAbsolute(path) ? filePath : relative(process.cwd(), filePath);
6176
6194
  konsola.ok(`Datasources downloaded successfully to ${chalk.hex(colorPalette.PRIMARY)(displayPath)}`);
6177
6195
  }
6178
6196
  konsola.br();
@@ -6381,7 +6399,7 @@ const createOctokit = (token) => {
6381
6399
 
6382
6400
  const generateProject = async (blueprint, projectName, targetPath = process.cwd()) => {
6383
6401
  try {
6384
- const projectPath = path.join(targetPath, projectName);
6402
+ const projectPath = join(targetPath, projectName);
6385
6403
  const templateRepo = `storyblok/blueprint-core-${blueprint}`;
6386
6404
  try {
6387
6405
  await fs.access(projectPath);
@@ -6418,7 +6436,7 @@ const generateProject = async (blueprint, projectName, targetPath = process.cwd(
6418
6436
  };
6419
6437
  const createEnvFile = async (projectPath, storyblokVars, additionalVars) => {
6420
6438
  try {
6421
- const envPath = path.join(projectPath, ".env");
6439
+ const envPath = join(projectPath, ".env");
6422
6440
  let envContent = `# Storyblok Configuration
6423
6441
  ${Object.entries(storyblokVars).map(([key, value]) => `${key}=${value}`).join("\n")}
6424
6442
  `;
@@ -6636,7 +6654,7 @@ program$5.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
6636
6654
  if (!value.trim()) {
6637
6655
  return "Project path is required";
6638
6656
  }
6639
- const projectName2 = path.basename(value);
6657
+ const projectName2 = basename(value);
6640
6658
  if (!/^[\w-]+$/.test(projectName2)) {
6641
6659
  return "Project name (last part of the path) can only contain letters, numbers, hyphens, and underscores";
6642
6660
  }
@@ -6644,9 +6662,9 @@ program$5.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
6644
6662
  }
6645
6663
  });
6646
6664
  }
6647
- const resolvedPath = path.resolve(finalProjectPath);
6648
- const targetDirectory = path.dirname(resolvedPath);
6649
- const projectName = path.basename(resolvedPath);
6665
+ const resolvedPath = resolve(finalProjectPath);
6666
+ const targetDirectory = dirname(resolvedPath);
6667
+ const projectName = basename(resolvedPath);
6650
6668
  konsola.br();
6651
6669
  konsola.info(`Scaffolding your project using the ${chalk.hex(colorPalette.CREATE)(technologyTemplate)} template...`);
6652
6670
  await generateProject(technologyTemplate, projectName, targetDirectory);
@@ -7106,7 +7124,7 @@ const parseAssetData = (raw) => {
7106
7124
  }
7107
7125
  };
7108
7126
  const getSidecarFilename = (assetBinaryPath) => {
7109
- return join(dirname$1(assetBinaryPath), `${basename(assetBinaryPath, extname(assetBinaryPath))}.json`);
7127
+ return join(dirname(assetBinaryPath), `${basename(assetBinaryPath, extname(assetBinaryPath))}.json`);
7110
7128
  };
7111
7129
  const loadSidecarAssetData = async (assetBinaryPath) => {
7112
7130
  const sidecarPath = getSidecarFilename(assetBinaryPath);
@@ -7460,31 +7478,25 @@ const readLocalAssetsStream = ({
7460
7478
  const iterator = async function* readAssets() {
7461
7479
  try {
7462
7480
  const files = await readdir(directoryPath);
7463
- const metadataFiles = files.filter((file) => file.endsWith(".json") && file !== "manifest.jsonl");
7464
- setTotalAssets?.(metadataFiles.length);
7465
- for (const file of metadataFiles) {
7466
- const filePath = join(directoryPath, file);
7481
+ const binaryFiles = files.filter((f) => SUPPORTED_ASSET_EXTENSIONS.has(extname(f).toLowerCase()));
7482
+ setTotalAssets?.(binaryFiles.length);
7483
+ for (const file of binaryFiles) {
7484
+ const binaryFilePath = join(directoryPath, file);
7467
7485
  try {
7468
- const statResult = await stat(filePath);
7469
- if (!statResult.isFile()) {
7470
- continue;
7471
- }
7472
- const metadataContent = await readFile$1(filePath, "utf8");
7473
- const assetRaw = JSON.parse(metadataContent);
7486
+ const sidecar = await loadSidecarAssetData(binaryFilePath);
7487
+ const shortFilename = sidecar.short_filename || (sidecar.filename ? basename(sidecar.filename) : void 0) || file;
7474
7488
  const asset = {
7475
- ...assetRaw,
7476
- short_filename: assetRaw.short_filename || basename(assetRaw.filename)
7489
+ ...sidecar,
7490
+ short_filename: shortFilename
7477
7491
  };
7478
- const baseName = parse(file).name;
7479
- const extFromMetadata = extname(asset.short_filename || asset.filename) || "";
7480
- const assetBinaryPath = join(directoryPath, `${baseName}${extFromMetadata}`);
7481
- const fileBuffer = await readFile$1(assetBinaryPath);
7492
+ const fileBuffer = await readFile$1(binaryFilePath);
7493
+ const sidecarPath = getSidecarFilename(binaryFilePath);
7482
7494
  yield {
7483
7495
  asset,
7484
7496
  context: {
7485
7497
  fileBuffer,
7486
- assetBinaryPath,
7487
- assetPath: filePath
7498
+ assetBinaryPath: binaryFilePath,
7499
+ assetPath: sidecarPath
7488
7500
  }
7489
7501
  };
7490
7502
  } catch (maybeError) {
@@ -7868,6 +7880,9 @@ const traverseAndMapBySchema = (data, {
7868
7880
  processedFields,
7869
7881
  missingSchemas
7870
7882
  }) => {
7883
+ if (!data?.component) {
7884
+ return data ?? {};
7885
+ }
7871
7886
  const schema = schemas[data.component];
7872
7887
  if (!schema) {
7873
7888
  missingSchemas.add(data.component);
@@ -7911,7 +7926,7 @@ const traverseAndMapRichtextDoc = (data, {
7911
7926
  }));
7912
7927
  }
7913
7928
  if (data && typeof data === "object") {
7914
- if (data.type === "link" && data.attrs.linktype === "story") {
7929
+ if (data.type === "link" && data.attrs?.linktype === "story") {
7915
7930
  return {
7916
7931
  ...data,
7917
7932
  attrs: {
@@ -7925,7 +7940,7 @@ const traverseAndMapRichtextDoc = (data, {
7925
7940
  ...data,
7926
7941
  attrs: {
7927
7942
  ...data.attrs,
7928
- body: data.attrs.body.map((d) => traverseAndMapBySchema(d, {
7943
+ body: (data.attrs?.body ?? []).map((d) => traverseAndMapBySchema(d, {
7929
7944
  schemas,
7930
7945
  maps,
7931
7946
  fieldRefMappers: fieldRefMappers2,
@@ -7967,7 +7982,7 @@ const multilinkFieldRefMapper = (data, { maps }) => {
7967
7982
  };
7968
7983
  const bloksFieldRefMapper = (data, { schemas, maps, fieldRefMappers: fieldRefMappers2, processedFields, missingSchemas }) => {
7969
7984
  if (!Array.isArray(data)) {
7970
- throw new TypeError("Invalid data!");
7985
+ 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", ... }]).`);
7971
7986
  }
7972
7987
  return data.map((d) => traverseAndMapBySchema(d, {
7973
7988
  schemas,
@@ -7986,7 +8001,7 @@ const assetFieldRefMapper = (data, { maps }) => {
7986
8001
  };
7987
8002
  const multiassetFieldRefMapper = (data, options) => {
7988
8003
  if (!Array.isArray(data)) {
7989
- throw new TypeError("Invalid data!");
8004
+ 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 }]).`);
7990
8005
  }
7991
8006
  return data.map((d) => assetFieldRefMapper(d, options));
7992
8007
  };
@@ -8009,23 +8024,23 @@ const storyRefMapper = (story, { schemas, maps }) => {
8009
8024
  const missingSchemas = /* @__PURE__ */ new Set();
8010
8025
  const alternates = story.alternates ? story.alternates.map((a) => ({
8011
8026
  ...a,
8012
- id: maps.stories?.get(a.id) || a.id,
8013
- parent_id: maps.stories?.get(a.parent_id) || a.parent_id
8027
+ id: maps.stories?.get(a.id) ?? a.id,
8028
+ parent_id: maps.stories?.get(a.parent_id) ?? a.parent_id
8014
8029
  })) : story.alternates;
8015
- const parentId = maps.stories?.get(story.parent_id) || story.parent_id;
8030
+ const parentId = maps.stories?.get(story.parent_id) ?? story.parent_id;
8016
8031
  const mappedStory = {
8017
8032
  ...story,
8018
- content: traverseAndMapBySchema(story.content, {
8033
+ content: story.content?.component ? traverseAndMapBySchema(story.content, {
8019
8034
  schemas,
8020
8035
  maps,
8021
8036
  fieldRefMappers,
8022
8037
  processedFields,
8023
8038
  missingSchemas
8024
- }),
8025
- id: Number(maps.stories?.get(story.id) || story.id),
8026
- uuid: String(maps.stories?.get(story.uuid) || story.uuid),
8039
+ }) : story.content,
8040
+ id: Number(maps.stories?.get(story.id) ?? story.id),
8041
+ uuid: String(maps.stories?.get(story.uuid) ?? story.uuid),
8027
8042
  // @ts-expect-error Our types are wrong.
8028
- parent_id: parentId ? Number(parentId) : null,
8043
+ parent_id: parentId != null ? Number(parentId) : null,
8029
8044
  alternates
8030
8045
  };
8031
8046
  return {
@@ -8187,14 +8202,14 @@ const getRemoteStory = async ({ spaceId, storyId }) => {
8187
8202
  return data?.story;
8188
8203
  };
8189
8204
  const makeCreateStoryAPITransport = ({ spaceId }) => async (localStory) => {
8190
- const { id: _id, uuid: _uuid, content, parent_id: _p, ...newStoryData } = localStory;
8205
+ const { id: _id, uuid: _uuid, parent_id: _parentId, content, ...newStoryData } = localStory;
8206
+ if (!localStory.is_folder && !content?.component) {
8207
+ throw new Error(`Story "${localStory.slug}" is missing a content type (content.component). Every story must define a content field with a valid component.`);
8208
+ }
8191
8209
  const remoteStory = await createStory(spaceId, {
8192
8210
  story: {
8193
8211
  ...newStoryData,
8194
- content: {
8195
- _uid: "",
8196
- component: "__tmp__"
8197
- }
8212
+ ...content?.component ? { content: { _uid: "", component: "__migration_artifact__" } } : {}
8198
8213
  },
8199
8214
  publish: 0
8200
8215
  });
@@ -8265,12 +8280,12 @@ const createStoryPlaceholderStream = ({
8265
8280
  });
8266
8281
  };
8267
8282
  const makeWriteStoryFSTransport = ({ directoryPath }) => async (story) => {
8268
- await saveToFile(resolve$1(directoryPath, getStoryFilename(story)), JSON.stringify(story, null, 2));
8283
+ await saveToFile(resolve(directoryPath, getStoryFilename(story)), JSON.stringify(story, null, 2));
8269
8284
  return story;
8270
8285
  };
8271
8286
  const makeWriteStoryAPITransport = ({ spaceId, publish }) => (mappedLocalStory) => updateStory(spaceId, mappedLocalStory.id, {
8272
8287
  story: mappedLocalStory,
8273
- publish: publish ?? (mappedLocalStory.published ? 1 : 0)
8288
+ publish: publish ?? (isStoryPublishedWithoutChanges(mappedLocalStory) ? 1 : 0)
8274
8289
  });
8275
8290
  const makeCleanupStoryFSTransport = ({ directoryPath, maps }) => async (mappedStory) => {
8276
8291
  const mapEntry = maps.stories?.entries().find(([_, v]) => v === mappedStory.uuid);
@@ -8279,7 +8294,7 @@ const makeCleanupStoryFSTransport = ({ directoryPath, maps }) => async (mappedSt
8279
8294
  slug: mappedStory.slug,
8280
8295
  uuid: originalUuid
8281
8296
  });
8282
- const storyFilePath = resolve$1(directoryPath, storyFilename);
8297
+ const storyFilePath = resolve(directoryPath, storyFilename);
8283
8298
  await unlink(storyFilePath);
8284
8299
  };
8285
8300
  const writeStoryStream = ({
@@ -8896,14 +8911,14 @@ pushCmd.action(async (options, command) => {
8896
8911
  processProgress.setTotal(total);
8897
8912
  updateProgress.setTotal(total);
8898
8913
  },
8899
- onStoryError(error) {
8914
+ onStoryError(error, filename) {
8900
8915
  summary.creationResults.failed += 1;
8901
8916
  summary.processResults.total -= 1;
8902
8917
  summary.updateResults.total -= 1;
8903
8918
  processProgress.setTotal(summary.processResults.total);
8904
8919
  updateProgress.setTotal(summary.updateResults.total);
8905
8920
  creationProgress.increment();
8906
- handleError(error, verbose);
8921
+ handleError(error, verbose, { storyFile: filename });
8907
8922
  }
8908
8923
  }),
8909
8924
  // Create remote stories.
@@ -8912,7 +8927,6 @@ pushCmd.action(async (options, command) => {
8912
8927
  spaceId: space,
8913
8928
  transports: {
8914
8929
  createStory: options.dryRun ? async (story) => story : makeCreateStoryAPITransport({
8915
- maps,
8916
8930
  spaceId: space
8917
8931
  }),
8918
8932
  appendStoryManifest: options.dryRun ? () => Promise.resolve() : makeAppendToManifestFSTransport({
@@ -8937,13 +8951,13 @@ pushCmd.action(async (options, command) => {
8937
8951
  logger.info("Skipped creating story", { storyId: localStory.uuid });
8938
8952
  summary.creationResults.skipped += 1;
8939
8953
  },
8940
- onStoryError(error) {
8954
+ onStoryError(error, localStory) {
8941
8955
  summary.creationResults.failed += 1;
8942
8956
  summary.processResults.total -= 1;
8943
8957
  summary.updateResults.total -= 1;
8944
8958
  processProgress.setTotal(summary.processResults.total);
8945
8959
  updateProgress.setTotal(summary.updateResults.total);
8946
- handleError(error, verbose);
8960
+ handleError(error, verbose, { storyId: localStory?.uuid });
8947
8961
  },
8948
8962
  onIncrement() {
8949
8963
  creationProgress.increment();
@@ -8963,13 +8977,13 @@ pushCmd.action(async (options, command) => {
8963
8977
  processProgress.setTotal(total);
8964
8978
  updateProgress.setTotal(total);
8965
8979
  },
8966
- onStoryError(error) {
8980
+ onStoryError(error, filename) {
8967
8981
  summary.creationResults.failed += 1;
8968
8982
  summary.processResults.total -= 1;
8969
8983
  summary.updateResults.total -= 1;
8970
8984
  processProgress.setTotal(summary.processResults.total);
8971
8985
  updateProgress.setTotal(summary.updateResults.total);
8972
- handleError(error, verbose);
8986
+ handleError(error, verbose, { storyFile: filename });
8973
8987
  }
8974
8988
  }),
8975
8989
  // Map all references to numeric ids and uuids.