storyblok 4.8.0 → 4.10.0

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
@@ -5,12 +5,12 @@ import { dirname } from 'pathe';
5
5
  import chalk from 'chalk';
6
6
  import { Command } from 'commander';
7
7
  import { readPackageUp } from 'read-package-up';
8
- import path, { join, resolve, parse, dirname as dirname$1, extname } from 'node:path';
8
+ import path, { join, resolve, parse, dirname as dirname$1, extname, relative } from 'node:path';
9
9
  import { MultiBar, Presets } from 'cli-progress';
10
10
  import { Spinner } from '@topcli/spinner';
11
11
  import fs, { mkdir, writeFile, readFile as readFile$1, appendFile, access, readdir } from 'node:fs/promises';
12
12
  import filenamify from 'filenamify';
13
- import { mkdirSync, appendFileSync, existsSync, readdirSync, unlinkSync, readFileSync } from 'node:fs';
13
+ import { mkdirSync, appendFileSync, writeFileSync, existsSync, readdirSync, unlinkSync, readFileSync } from 'node:fs';
14
14
  import { select, password, input, confirm } from '@inquirer/prompts';
15
15
  import { ManagementApiClient } from '@storyblok/management-api-client';
16
16
  import { RateLimit, Sema } from 'async-sema';
@@ -34,7 +34,8 @@ const commands = {
34
34
  TYPES: "types",
35
35
  DATASOURCES: "datasources",
36
36
  CREATE: "create",
37
- LOGS: "logs"
37
+ LOGS: "logs",
38
+ REPORTS: "reports"
38
39
  };
39
40
  const colorPalette = {
40
41
  PRIMARY: "#8d60ff",
@@ -51,7 +52,8 @@ const colorPalette = {
51
52
  TAGS: "#fbbf24",
52
53
  PRESETS: "#a855f7",
53
54
  DATASOURCES: "#4ade80",
54
- LOGS: "#4ade80"
55
+ LOGS: "#4ade80",
56
+ REPORTS: "#4ade80"
55
57
  };
56
58
  const regions = {
57
59
  EU: "eu",
@@ -92,7 +94,8 @@ const regionNames = {
92
94
  SB_Agent_Version: process.env.npm_package_version || "4.x"
93
95
  });
94
96
  const directories = {
95
- log: "logs"
97
+ log: "logs",
98
+ report: "reports"
96
99
  };
97
100
 
98
101
  class FetchError extends Error {
@@ -701,6 +704,22 @@ const saveToFile = async (filePath, data, options) => {
701
704
  handleFileSystemError("write", writeError);
702
705
  }
703
706
  };
707
+ const saveToFileSync = (filePath, data, options) => {
708
+ const resolvedPath = parse(filePath).dir;
709
+ if (resolvedPath) {
710
+ try {
711
+ mkdirSync(resolvedPath, { recursive: true });
712
+ } catch (mkdirError) {
713
+ handleFileSystemError("mkdir", mkdirError);
714
+ return;
715
+ }
716
+ }
717
+ try {
718
+ writeFileSync(filePath, data, options);
719
+ } catch (writeError) {
720
+ handleFileSystemError("write", writeError);
721
+ }
722
+ };
704
723
  const appendToFile = async (filePath, data, options) => {
705
724
  const resolvedPath = parse(filePath).dir;
706
725
  try {
@@ -747,6 +766,12 @@ const resolvePath = (path, folder) => {
747
766
  }
748
767
  return resolve(resolve(process.cwd(), ".storyblok"), folder);
749
768
  };
769
+ function resolveCommandPath(commandPath, space, baseDir) {
770
+ if (space) {
771
+ return resolvePath(baseDir, join(commandPath, space));
772
+ }
773
+ return resolvePath(baseDir, commandPath);
774
+ }
750
775
  const getComponentNameFromFilename = (filename) => {
751
776
  return filename.replace(/\.js$/, "");
752
777
  };
@@ -770,11 +795,101 @@ async function readJsonFile(filePath) {
770
795
  function importModule(filePath) {
771
796
  return import(`file://${filePath}`);
772
797
  }
773
- function getLogsPath(logFileDir, space, baseDir) {
774
- if (space) {
775
- return resolvePath(baseDir, join(logFileDir, space));
798
+
799
+ const REPORT_STATUS = {
800
+ unfinished: "UNFINISHED",
801
+ success: "SUCCESS",
802
+ partialSuccess: "PARTIAL_SUCCESS",
803
+ failure: "FAILURE"
804
+ };
805
+ class Reporter {
806
+ filePath;
807
+ enabled;
808
+ startedAt = /* @__PURE__ */ new Date();
809
+ maxFiles;
810
+ report = {
811
+ status: REPORT_STATUS.unfinished,
812
+ meta: {
813
+ startedAt: this.startedAt.toISOString()
814
+ },
815
+ summary: {}
816
+ };
817
+ constructor(options) {
818
+ this.enabled = options?.enabled || false;
819
+ this.filePath = options?.filePath ?? `./${Date.now()}.json`;
820
+ this.maxFiles = options?.maxFiles;
821
+ }
822
+ addMeta(key, value) {
823
+ this.report.meta[key] = value;
824
+ return this;
825
+ }
826
+ addSummary(key, value) {
827
+ this.report.summary[key] = value;
828
+ return this;
829
+ }
830
+ finalize() {
831
+ if (!this.enabled) {
832
+ return;
833
+ }
834
+ const endedAt = /* @__PURE__ */ new Date();
835
+ this.report.meta.endedAt = endedAt.toISOString();
836
+ this.report.meta.durationMs = endedAt.getTime() - this.startedAt.getTime();
837
+ this.updateStatus();
838
+ saveToFileSync(this.filePath, JSON.stringify(this.report, null, 2));
839
+ if (this.maxFiles !== void 0) {
840
+ this.pruneOldFiles();
841
+ }
842
+ }
843
+ updateStatus() {
844
+ let succeededTotal = 0;
845
+ let failedTotal = 0;
846
+ for (const item of Object.values(this.report.summary)) {
847
+ succeededTotal += item.succeeded;
848
+ failedTotal += item.failed;
849
+ }
850
+ if (failedTotal === 0) {
851
+ this.report.status = REPORT_STATUS.success;
852
+ } else if (succeededTotal > 0) {
853
+ this.report.status = REPORT_STATUS.partialSuccess;
854
+ } else {
855
+ this.report.status = REPORT_STATUS.failure;
856
+ }
857
+ }
858
+ pruneOldFiles() {
859
+ if (this.maxFiles === void 0) {
860
+ return;
861
+ }
862
+ const dir = dirname$1(this.filePath);
863
+ const ext = extname(this.filePath);
864
+ Reporter.pruneReportFiles(dir, this.maxFiles, ext);
865
+ }
866
+ static pruneReportFiles(directory, keep, extension = ".json") {
867
+ if (!existsSync(directory)) {
868
+ return 0;
869
+ }
870
+ const files = readdirSync(directory).filter((file) => extname(file) === extension).sort();
871
+ const filesToDelete = files.length - keep;
872
+ if (filesToDelete <= 0) {
873
+ return 0;
874
+ }
875
+ for (const file of files.slice(0, filesToDelete)) {
876
+ unlinkSync(join(directory, file));
877
+ }
878
+ return filesToDelete;
879
+ }
880
+ static listReportFiles(directory, extension = ".json") {
881
+ if (!existsSync(directory)) {
882
+ return [];
883
+ }
884
+ return readdirSync(directory).filter((file) => extname(file) === extension).map((f) => relative(process.cwd(), join(directory, f))).sort();
885
+ }
886
+ }
887
+ let reporterInstance = null;
888
+ function getReporter(options) {
889
+ if (!reporterInstance) {
890
+ reporterInstance = new Reporter(options);
776
891
  }
777
- return resolvePath(baseDir, logFileDir);
892
+ return reporterInstance;
778
893
  }
779
894
 
780
895
  class FileTransport {
@@ -896,7 +1011,7 @@ let programInstance = null;
896
1011
  function getProgram() {
897
1012
  if (!programInstance) {
898
1013
  programInstance = new Command();
899
- programInstance.name(packageJson.name).description(packageJson.description || "").version(packageJson.version).hook("preAction", (_, actionCmd) => {
1014
+ programInstance.name(packageJson.name).description(packageJson.description || "").version(packageJson.version, "-v, --vers", "Output the current version").helpOption("-h, --help", "Display help for command").option("--verbose", "Enable verbose output").hook("preAction", (_, actionCmd) => {
900
1015
  const options = actionCmd.optsWithGlobals();
901
1016
  const commandPieces = [];
902
1017
  for (let c = actionCmd; c; c = c.parent) {
@@ -905,7 +1020,7 @@ function getProgram() {
905
1020
  const command = commandPieces.join(" ");
906
1021
  const runId = Date.now();
907
1022
  const transports = [];
908
- const logsPath = getLogsPath(directories.log, options.space, options.path);
1023
+ const logsPath = resolveCommandPath(directories.log, options.space, options.path);
909
1024
  const logFilename = `${commandPieces.join("-")}-${runId}.jsonl`;
910
1025
  const filePath = path.join(logsPath, logFilename);
911
1026
  transports.push(new FileTransport({
@@ -917,6 +1032,10 @@ function getProgram() {
917
1032
  transports
918
1033
  });
919
1034
  getUI({ enabled: true });
1035
+ const reportPath = resolveCommandPath(directories.report, options.space, options.path);
1036
+ const reportFilename = `${commandPieces.join("-")}-${runId}.jsonl`;
1037
+ const reportFilePath = path.join(reportPath, reportFilename);
1038
+ getReporter({ enabled: true, filePath: reportFilePath }).addMeta("command", command).addMeta("cliVersion", packageJson.version).addMeta("runId", String(runId)).addMeta("logPath", filePath).addMeta("config", options);
920
1039
  });
921
1040
  programInstance.configureOutput({
922
1041
  writeErr: (str) => handleError(new Error(str))
@@ -1172,7 +1291,7 @@ function session() {
1172
1291
  return sessionInstance;
1173
1292
  }
1174
1293
 
1175
- const program$g = getProgram();
1294
+ const program$h = getProgram();
1176
1295
  const allRegionsText = Object.values(regions).join(",");
1177
1296
  const loginStrategy = {
1178
1297
  message: "How would you like to login?",
@@ -1189,12 +1308,12 @@ const loginStrategy = {
1189
1308
  }
1190
1309
  ]
1191
1310
  };
1192
- program$g.command(commands.LOGIN).description("Login to the Storyblok CLI").option("-t, --token <token>", "Token to login directly without questions, like for CI environments").option(
1311
+ program$h.command(commands.LOGIN).description("Login to the Storyblok CLI").option("-t, --token <token>", "Token to login directly without questions, like for CI environments").option(
1193
1312
  "-r, --region <region>",
1194
1313
  `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}.`
1195
1314
  ).action(async (options) => {
1196
1315
  konsola.title(`${commands.LOGIN}`, colorPalette.LOGIN);
1197
- const verbose = program$g.opts().verbose;
1316
+ const verbose = program$h.opts().verbose;
1198
1317
  const { token, region } = options;
1199
1318
  const { state, updateSession, persistCredentials, initializeSession } = session();
1200
1319
  await initializeSession();
@@ -1322,10 +1441,10 @@ program$g.command(commands.LOGIN).description("Login to the Storyblok CLI").opti
1322
1441
  konsola.br();
1323
1442
  });
1324
1443
 
1325
- const program$f = getProgram();
1326
- program$f.command(commands.LOGOUT).description("Logout from the Storyblok CLI").action(async () => {
1444
+ const program$g = getProgram();
1445
+ program$g.command(commands.LOGOUT).description("Logout from the Storyblok CLI").action(async () => {
1327
1446
  konsola.title(`${commands.LOGOUT}`, colorPalette.LOGOUT);
1328
- const verbose = program$f.opts().verbose;
1447
+ const verbose = program$g.opts().verbose;
1329
1448
  try {
1330
1449
  const { state, initializeSession } = session();
1331
1450
  await initializeSession();
@@ -1373,10 +1492,10 @@ async function openSignupInBrowser(url) {
1373
1492
  }
1374
1493
  }
1375
1494
 
1376
- const program$e = getProgram();
1377
- program$e.command(commands.SIGNUP).description("Sign up for Storyblok").action(async () => {
1495
+ const program$f = getProgram();
1496
+ program$f.command(commands.SIGNUP).description("Sign up for Storyblok").action(async () => {
1378
1497
  konsola.title(`${commands.SIGNUP}`, colorPalette.SIGNUP);
1379
- const verbose = program$e.opts().verbose;
1498
+ const verbose = program$f.opts().verbose;
1380
1499
  const { state, initializeSession } = session();
1381
1500
  await initializeSession();
1382
1501
  if (state.isLoggedIn && !state.envLogin) {
@@ -1398,10 +1517,10 @@ program$e.command(commands.SIGNUP).description("Sign up for Storyblok").action(a
1398
1517
  konsola.br();
1399
1518
  });
1400
1519
 
1401
- const program$d = getProgram();
1402
- program$d.command(commands.USER).description("Get the current user").action(async () => {
1520
+ const program$e = getProgram();
1521
+ program$e.command(commands.USER).description("Get the current user").action(async () => {
1403
1522
  konsola.title(`${commands.USER}`, colorPalette.USER);
1404
- const verbose = program$d.opts().verbose;
1523
+ const verbose = program$e.opts().verbose;
1405
1524
  const { state, initializeSession } = session();
1406
1525
  await initializeSession();
1407
1526
  if (!requireAuthentication(state)) {
@@ -1430,8 +1549,8 @@ program$d.command(commands.USER).description("Get the current user").action(asyn
1430
1549
  konsola.br();
1431
1550
  });
1432
1551
 
1433
- const program$c = getProgram();
1434
- const componentsCommand = program$c.command(commands.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");
1552
+ const program$d = getProgram();
1553
+ const componentsCommand = program$d.command(commands.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");
1435
1554
 
1436
1555
  const fetchComponents = async (spaceId) => {
1437
1556
  try {
@@ -1811,10 +1930,10 @@ async function readConsolidatedFiles$1(resolvedPath, suffix) {
1811
1930
  };
1812
1931
  }
1813
1932
 
1814
- const program$b = getProgram();
1933
+ const program$c = getProgram();
1815
1934
  componentsCommand.command("pull [componentName]").option("-f, --filename <filename>", "custom name to be used in file(s) name instead of space id").option("--sf, --separate-files", "Argument to create a single file for each component").option("--su, --suffix <suffix>", "suffix to add to the file name (e.g. components.<suffix>.json)").description(`Download your space's components schema as json. Optionally specify a component name to pull a single component.`).action(async (componentName, options) => {
1816
1935
  konsola.title(`${commands.COMPONENTS}`, colorPalette.COMPONENTS, componentName ? `Pulling component ${componentName}...` : "Pulling components...");
1817
- const verbose = program$b.opts().verbose;
1936
+ const verbose = program$c.opts().verbose;
1818
1937
  const { space, path } = componentsCommand.opts();
1819
1938
  const { separateFiles, suffix, filename = "components" } = options;
1820
1939
  const { state, initializeSession } = session();
@@ -1952,7 +2071,7 @@ function buildDependencyGraph(context) {
1952
2071
  graph.nodes.set(nodeId, node);
1953
2072
  });
1954
2073
  spaceState.local.groups.forEach((group) => {
1955
- if (group.parent_uuid) {
2074
+ if (group.parent_uuid && group.parent_id && group.parent_uuid !== group.uuid) {
1956
2075
  const childId = `group:${group.uuid}`;
1957
2076
  const parentId = `group:${group.parent_uuid}`;
1958
2077
  addDependency(childId, parentId);
@@ -2847,12 +2966,13 @@ async function pushWithDependencyGraph(space, spaceState, maxConcurrency = 5) {
2847
2966
  return results;
2848
2967
  }
2849
2968
 
2850
- const program$a = getProgram();
2969
+ const program$b = getProgram();
2851
2970
  componentsCommand.command("push [componentName]").description(`Push your space's components schema as json`).option("-f, --from <from>", "source space id").option("--fi, --filter <filter>", "glob filter to apply to the components before pushing").option("--sf, --separate-files", "Read from separate files instead of consolidated files").option("--su, --suffix <suffix>", "Suffix to add to the component name").action(async (componentName, options) => {
2852
2971
  konsola.title(`${commands.COMPONENTS}`, colorPalette.COMPONENTS, componentName ? `Pushing component ${componentName}...` : "Pushing components...");
2853
- const verbose = program$a.opts().verbose;
2972
+ const verbose = program$b.opts().verbose;
2854
2973
  const { space, path } = componentsCommand.opts();
2855
- const { from, filter } = options;
2974
+ const { filter } = options;
2975
+ const fromSpace = options.from || space;
2856
2976
  const { state, initializeSession } = session();
2857
2977
  await initializeSession();
2858
2978
  if (!requireAuthentication(state, verbose)) {
@@ -2862,10 +2982,7 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2862
2982
  handleError(new CommandError(`Please provide the target space as argument --space TARGET_SPACE_ID.`), verbose);
2863
2983
  return;
2864
2984
  }
2865
- if (!from) {
2866
- options.from = space;
2867
- }
2868
- konsola.info(`Attempting to push components ${chalk.bold("from")} space ${chalk.hex(colorPalette.COMPONENTS)(options.from)} ${chalk.bold("to")} ${chalk.hex(colorPalette.COMPONENTS)(space)}`);
2985
+ konsola.info(`Attempting to push components ${chalk.bold("from")} space ${chalk.hex(colorPalette.COMPONENTS)(fromSpace)} ${chalk.bold("to")} ${chalk.hex(colorPalette.COMPONENTS)(space)}`);
2869
2986
  konsola.br();
2870
2987
  const { password, region } = state;
2871
2988
  let requestCount = 0;
@@ -2883,7 +3000,7 @@ componentsCommand.command("push [componentName]").description(`Push your space's
2883
3000
  const componentsData = await readComponentsFiles({
2884
3001
  ...options,
2885
3002
  path,
2886
- space
3003
+ from: fromSpace
2887
3004
  });
2888
3005
  const localData = {
2889
3006
  ...componentsData,
@@ -3046,11 +3163,11 @@ const saveLanguagesToFile = async (space, internationalizationOptions, options)
3046
3163
  }
3047
3164
  };
3048
3165
 
3049
- const program$9 = getProgram();
3050
- const languagesCommand = program$9.command(commands.LANGUAGES).alias("lang").description(`Manage your space's languages`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/languages");
3166
+ const program$a = getProgram();
3167
+ const languagesCommand = program$a.command(commands.LANGUAGES).alias("lang").description(`Manage your space's languages`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/languages");
3051
3168
  languagesCommand.command("pull").description(`Download your space's languages schema as json`).option("-f, --filename <filename>", "filename to save the file as <filename>.<suffix>.json").option("--su, --suffix <suffix>", "suffix to add to the file name (e.g. languages.<suffix>.json). By default, the space ID is used.").action(async (options) => {
3052
3169
  konsola.title(`${commands.LANGUAGES}`, colorPalette.LANGUAGES);
3053
- const verbose = program$9.opts().verbose;
3170
+ const verbose = program$a.opts().verbose;
3054
3171
  const { space, path } = languagesCommand.opts();
3055
3172
  const { filename = "languages", suffix = options.space } = options;
3056
3173
  const { state, initializeSession } = session();
@@ -3097,8 +3214,8 @@ languagesCommand.command("pull").description(`Download your space's languages sc
3097
3214
  konsola.br();
3098
3215
  });
3099
3216
 
3100
- const program$8 = getProgram();
3101
- const migrationsCommand = program$8.command(commands.MIGRATIONS).alias("mig").description(`Manage your space's migrations`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/migrations");
3217
+ const program$9 = getProgram();
3218
+ const migrationsCommand = program$9.command(commands.MIGRATIONS).alias("mig").description(`Manage your space's migrations`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/migrations");
3102
3219
 
3103
3220
  const getMigrationTemplate = () => {
3104
3221
  return `export default function (block) {
@@ -3817,6 +3934,7 @@ migrationsCommand.command("run [componentName]").description("Run migrations").o
3817
3934
  const program = getProgram();
3818
3935
  const ui = getUI();
3819
3936
  const logger = getLogger();
3937
+ const reporter = getReporter();
3820
3938
  ui.title(`${commands.MIGRATIONS}`, colorPalette.MIGRATIONS, componentName ? `Running migrations for component ${componentName}...` : "Running migrations...");
3821
3939
  logger.info("Migration started");
3822
3940
  if (options.dryRun) {
@@ -3919,21 +4037,26 @@ migrationsCommand.command("run [componentName]").description("Run migrations").o
3919
4037
  ui.info(migrationSummary);
3920
4038
  const updateSummary = updateStream.getSummary();
3921
4039
  ui.info(updateSummary);
3922
- const migrationResults = migrationStream.getResults();
3923
- const updateResults = updateStream.getResults();
4040
+ const migrationStreamResults = migrationStream.getResults();
4041
+ const migrationResults = {
4042
+ total: migrationStreamResults.totalProcessed,
4043
+ succeeded: migrationStreamResults.successful.length,
4044
+ skipped: migrationStreamResults.skipped.length,
4045
+ failed: migrationStreamResults.failed.length
4046
+ };
4047
+ const updateStreamResults = updateStream.getResults();
4048
+ const updateResults = {
4049
+ total: updateStreamResults.totalProcessed,
4050
+ succeeded: updateStreamResults.successful.length,
4051
+ failed: updateStreamResults.failed.length
4052
+ };
3924
4053
  logger.info("Migration finished", {
3925
- migrationResults: {
3926
- total: migrationResults.totalProcessed,
3927
- succeeded: migrationResults.successful.length,
3928
- skipped: migrationResults.skipped.length,
3929
- failed: migrationResults.failed.length
3930
- },
3931
- updateResults: {
3932
- total: updateResults.totalProcessed,
3933
- succeeded: updateResults.successful.length,
3934
- failed: updateResults.failed.length
3935
- }
4054
+ migrationResults,
4055
+ updateResults
3936
4056
  });
4057
+ reporter.addSummary("migrationResults", migrationResults);
4058
+ reporter.addSummary("updateResults", updateResults);
4059
+ reporter.finalize();
3937
4060
  } catch (error) {
3938
4061
  handleError(error, verbose);
3939
4062
  }
@@ -4028,8 +4151,8 @@ migrationsCommand.command("rollback [migrationFile]").description("Rollback a mi
4028
4151
  }
4029
4152
  });
4030
4153
 
4031
- const program$7 = getProgram();
4032
- const typesCommand = program$7.command(commands.TYPES).alias("ts").description(`Generate types d.ts for your component schemas`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/types");
4154
+ const program$8 = getProgram();
4155
+ const typesCommand = program$8.command(commands.TYPES).alias("ts").description(`Generate types d.ts for your component schemas`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/types");
4033
4156
 
4034
4157
  const getAssetJSONSchema = (title) => ({
4035
4158
  $id: "#/asset",
@@ -4869,7 +4992,7 @@ const upsertDatasourceEntry = async (space, datasourceId, entry, existingId) =>
4869
4992
  }
4870
4993
  };
4871
4994
  const readDatasourcesFiles = async (options) => {
4872
- const { from, path, separateFiles = false, suffix, space } = options;
4995
+ const { from, path, separateFiles = false, suffix } = options;
4873
4996
  const resolvedPath = resolvePath(path, `datasources/${from}`);
4874
4997
  try {
4875
4998
  await readdir(resolvedPath);
@@ -4880,7 +5003,7 @@ const readDatasourcesFiles = async (options) => {
4880
5003
  ${chalk.cyan(`storyblok datasources pull --space ${from}`)}
4881
5004
 
4882
5005
  2. Then try pushing again:
4883
- ${chalk.cyan(`storyblok datasources push --space ${space} --from ${from}`)}`;
5006
+ ${chalk.cyan(`storyblok datasources push --space <target_space> --from ${from}`)}`;
4884
5007
  throw new FileSystemError(
4885
5008
  "file_not_found",
4886
5009
  "read",
@@ -4937,13 +5060,13 @@ async function readConsolidatedFiles(resolvedPath, suffix) {
4937
5060
  };
4938
5061
  }
4939
5062
 
4940
- const program$6 = getProgram();
5063
+ const program$7 = getProgram();
4941
5064
  typesCommand.command("generate").description("Generate types d.ts for your component schemas").option("--sf, --separate-files", "Generate one .d.ts file per component instead of a single combined file").option(
4942
5065
  "--filename <name>",
4943
5066
  "Base file name for all component types when generating a single declarations file (e.g. components.d.ts). Ignored when using --separate-files."
4944
5067
  ).option("--strict", "strict mode, no loose typing").option("--type-prefix <prefix>", "prefix to be prepended to all generated component type names").option("--type-suffix <suffix>", "suffix to be appended to all generated component type names").option("--suffix <suffix>", "Components suffix").option("--custom-fields-parser <path>", "Path to the parser file for Custom Field Types").option("--compiler-options <options>", "path to the compiler options from json-schema-to-typescript").action(async (options) => {
4945
5068
  konsola.title(`${commands.TYPES}`, colorPalette.TYPES, "Generating types...");
4946
- const verbose = program$6.opts().verbose;
5069
+ const verbose = program$7.opts().verbose;
4947
5070
  const { space, path } = typesCommand.opts();
4948
5071
  const spinner = new Spinner({
4949
5072
  verbose: !isVitest
@@ -4996,8 +5119,8 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
4996
5119
  }
4997
5120
  });
4998
5121
 
4999
- const program$5 = getProgram();
5000
- const datasourcesCommand = program$5.command(commands.DATASOURCES).alias("ds").description(`Manage your space's datasources`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/datasources");
5122
+ const program$6 = getProgram();
5123
+ const datasourcesCommand = program$6.command(commands.DATASOURCES).alias("ds").description(`Manage your space's datasources`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/datasources");
5001
5124
 
5002
5125
  async function fetchAllPages(fetchFunction, extractDataFunction, page = 1, collectedItems = []) {
5003
5126
  const { data, response } = await fetchFunction(page);
@@ -5103,10 +5226,10 @@ const saveDatasourcesToFiles = async (space, datasources, options) => {
5103
5226
  }
5104
5227
  };
5105
5228
 
5106
- const program$4 = getProgram();
5229
+ const program$5 = getProgram();
5107
5230
  datasourcesCommand.command("pull [datasourceName]").option("-f, --filename <filename>", "custom name to be used in file(s) name instead of space id").option("--sf, --separate-files", "Argument to create a single file for each datasource").option("--su, --suffix <suffix>", "suffix to add to the file name (e.g. datasources.<suffix>.json)").description("Pull datasources from your space").action(async (datasourceName, options) => {
5108
5231
  konsola.title(`${commands.DATASOURCES}`, colorPalette.DATASOURCES, datasourceName ? `Pulling datasource ${datasourceName}...` : "Pulling datasources...");
5109
- const verbose = program$4.opts().verbose;
5232
+ const verbose = program$5.opts().verbose;
5110
5233
  const { space, path } = datasourcesCommand.opts();
5111
5234
  const { separateFiles, suffix, filename = "datasources" } = options;
5112
5235
  const { state, initializeSession } = session();
@@ -5175,12 +5298,13 @@ datasourcesCommand.command("pull [datasourceName]").option("-f, --filename <file
5175
5298
  }
5176
5299
  });
5177
5300
 
5178
- const program$3 = getProgram();
5301
+ const program$4 = getProgram();
5179
5302
  datasourcesCommand.command("push [datasourceName]").description(`Push your space's datasources schema as json`).option("-f, --from <from>", "source space id").option("--fi, --filter <filter>", "glob filter to apply to the datasources before pushing").option("--sf, --separate-files", "Read from separate files instead of consolidated files").option("--su, --suffix <suffix>", "Suffix to add to the datasource name").action(async (datasourceName, options) => {
5180
5303
  konsola.title(`${commands.DATASOURCES}`, colorPalette.DATASOURCES, datasourceName ? `Pushing datasource ${datasourceName}...` : "Pushing datasources...");
5181
- const verbose = program$3.opts().verbose;
5304
+ const verbose = program$4.opts().verbose;
5182
5305
  const { space, path } = datasourcesCommand.opts();
5183
- const { from, filter } = options;
5306
+ const { filter } = options;
5307
+ const fromSpace = options.from || space;
5184
5308
  const { state, initializeSession } = session();
5185
5309
  await initializeSession();
5186
5310
  if (!requireAuthentication(state, verbose)) {
@@ -5190,10 +5314,7 @@ datasourcesCommand.command("push [datasourceName]").description(`Push your space
5190
5314
  handleError(new CommandError(`Please provide the target space as argument --space TARGET_SPACE_ID.`), verbose);
5191
5315
  return;
5192
5316
  }
5193
- if (!from) {
5194
- options.from = space;
5195
- }
5196
- konsola.info(`Attempting to push datasources ${chalk.bold("from")} space ${chalk.hex(colorPalette.DATASOURCES)(options.from || space)} ${chalk.bold("to")} ${chalk.hex(colorPalette.DATASOURCES)(space)}`);
5317
+ konsola.info(`Attempting to push datasources ${chalk.bold("from")} space ${chalk.hex(colorPalette.DATASOURCES)(fromSpace)} ${chalk.bold("to")} ${chalk.hex(colorPalette.DATASOURCES)(space)}`);
5197
5318
  konsola.br();
5198
5319
  const { password, region } = state;
5199
5320
  mapiClient({
@@ -5207,7 +5328,7 @@ datasourcesCommand.command("push [datasourceName]").description(`Push your space
5207
5328
  local: await readDatasourcesFiles({
5208
5329
  ...options,
5209
5330
  path,
5210
- space
5331
+ from: fromSpace
5211
5332
  }),
5212
5333
  target: {
5213
5334
  datasources: /* @__PURE__ */ new Map()
@@ -5495,11 +5616,33 @@ const fetchBlueprintRepositories = async () => {
5495
5616
  }
5496
5617
  };
5497
5618
 
5498
- const program$2 = getProgram();
5499
- program$2.command(`${commands.CREATE} [project-path]`).alias("c").description(`Scaffold a new project using Storyblok`).option("-t, --template <template>", "technology starter template").option("-b, --blueprint <blueprint>", "[DEPRECATED] use --template instead").option("--skip-space", "skip space creation").action(async (projectPath, options) => {
5619
+ function showNextSteps(technologyTemplate, finalProjectPath) {
5620
+ konsola.br();
5621
+ konsola.ok(`Your ${chalk.hex(colorPalette.PRIMARY)(technologyTemplate)} project is ready \u{1F389} !`);
5622
+ konsola.br();
5623
+ konsola.info(`Next steps:
5624
+ cd ${finalProjectPath}
5625
+ npm install
5626
+ npm run dev
5627
+ `);
5628
+ konsola.info(`Or check the dedicated guide at: ${chalk.hex(colorPalette.PRIMARY)(`https://www.storyblok.com/docs/guides/${technologyTemplate}`)}`);
5629
+ }
5630
+ async function handleEnvFileCreation(resolvedPath, token) {
5631
+ try {
5632
+ await createEnvFile(resolvedPath, token);
5633
+ konsola.ok(`Created .env file with Storyblok access token`, true);
5634
+ return true;
5635
+ } catch (error) {
5636
+ konsola.warn(`Failed to create .env file: ${error.message}`);
5637
+ konsola.info(`You can manually add this token to your .env file: ${token}`);
5638
+ return false;
5639
+ }
5640
+ }
5641
+ const program$3 = getProgram();
5642
+ program$3.command(`${commands.CREATE} [project-path]`).alias("c").description(`Scaffold a new project using Storyblok`).option("-t, --template <template>", "technology starter template").option("-b, --blueprint <blueprint>", "[DEPRECATED] use --template instead").option("--skip-space", "skip space creation").option("--token <token>", "Storyblok access token (skip space creation and use this token)").action(async (projectPath, options) => {
5500
5643
  konsola.title(`${commands.CREATE}`, colorPalette.CREATE);
5501
- const verbose = program$2.opts().verbose;
5502
- const { template, blueprint } = options;
5644
+ const verbose = program$3.opts().verbose;
5645
+ const { template, blueprint, token } = options;
5503
5646
  let selectedTemplate = template;
5504
5647
  if (blueprint && !template) {
5505
5648
  konsola.warn(`The --blueprint flag is deprecated. Please use --template instead.`);
@@ -5525,18 +5668,6 @@ program$2.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
5525
5668
  const spinnerSpace = new Spinner({
5526
5669
  verbose: !isVitest
5527
5670
  });
5528
- let userData;
5529
- try {
5530
- const user = await getUser(password, region);
5531
- if (!user) {
5532
- throw new Error("User data is undefined");
5533
- }
5534
- userData = user;
5535
- } catch (error) {
5536
- konsola.error("Failed to fetch user info. Please login again.", error);
5537
- konsola.br();
5538
- return;
5539
- }
5540
5671
  try {
5541
5672
  spinnerBlueprints.start("Fetching starter templates...");
5542
5673
  const templates = await fetchBlueprintRepositories();
@@ -5592,91 +5723,96 @@ program$2.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
5592
5723
  await generateProject(technologyTemplate, projectName, targetDirectory);
5593
5724
  konsola.ok(`Project ${chalk.hex(colorPalette.PRIMARY)(projectName)} created successfully in ${chalk.hex(colorPalette.PRIMARY)(finalProjectPath)}`, true);
5594
5725
  let createdSpace;
5595
- const choices = [
5596
- { name: "My personal account", value: "personal" }
5597
- ];
5598
- if (userData.has_org) {
5599
- choices.push({ name: `Organization (${userData?.org?.name})`, value: "org" });
5600
- }
5601
- if (userData.has_partner) {
5602
- choices.push({ name: "Partner Portal", value: "partner" });
5603
- }
5726
+ let userData;
5604
5727
  let whereToCreateSpace = "personal";
5605
- if (region === "eu" && (userData.has_partner || userData.has_org)) {
5606
- whereToCreateSpace = await select({
5607
- message: `Where would you like to create this space?`,
5608
- choices
5609
- });
5610
- }
5611
- if (region !== "eu" && userData.has_org) {
5612
- whereToCreateSpace = "org";
5728
+ if (token) {
5729
+ await handleEnvFileCreation(resolvedPath, token);
5730
+ showNextSteps(technologyTemplate, finalProjectPath);
5731
+ return;
5613
5732
  }
5614
- if (region !== "eu" && !userData.has_org) {
5615
- konsola.warn(`Space creation in this region is limited to Enterprise accounts. If you're part of an organization, please ensure you have the required permissions. For more information about Enterprise access, contact our Sales Team.`);
5616
- konsola.br();
5733
+ if (options.skipSpace) {
5734
+ showNextSteps(technologyTemplate, finalProjectPath);
5617
5735
  return;
5618
5736
  }
5619
- if (!options.skipSpace) {
5737
+ try {
5620
5738
  try {
5621
- spinnerSpace.start(`Creating space "${toHumanReadable(projectName)}"`);
5622
- const selectedBlueprint = templates.find((bp) => bp.value === technologyTemplate);
5623
- const blueprintDomain = selectedBlueprint?.location || "https://localhost:3000/";
5624
- const spaceToCreate = {
5625
- name: toHumanReadable(projectName),
5626
- domain: blueprintDomain
5627
- };
5628
- if (whereToCreateSpace === "org") {
5629
- spaceToCreate.org = userData.org;
5630
- spaceToCreate.in_org = true;
5631
- } else if (whereToCreateSpace === "partner") {
5632
- spaceToCreate.assign_partner = true;
5739
+ const user = await getUser(password, region);
5740
+ if (!user) {
5741
+ throw new Error("User data is undefined");
5633
5742
  }
5634
- createdSpace = await createSpace(spaceToCreate);
5635
- spinnerSpace.succeed(`Space "${chalk.hex(colorPalette.PRIMARY)(toHumanReadable(projectName))}" created successfully`);
5743
+ userData = user;
5636
5744
  } catch (error) {
5637
- spinnerSpace.failed();
5745
+ konsola.error("Failed to fetch user info. Please login again.", error);
5638
5746
  konsola.br();
5639
- handleError(error, verbose);
5640
5747
  return;
5641
5748
  }
5642
- }
5643
- if (createdSpace?.first_token) {
5644
- try {
5645
- await createEnvFile(resolvedPath, createdSpace.first_token);
5646
- konsola.ok(`Created .env file with Storyblok access token`, true);
5647
- } catch (error) {
5648
- konsola.warn(`Failed to create .env file: ${error.message}`);
5649
- konsola.info(`You can manually add this token to your .env file: ${createdSpace.first_token}`);
5749
+ const choices = [
5750
+ { name: "My personal account", value: "personal" }
5751
+ ];
5752
+ if (userData.has_org) {
5753
+ choices.push({ name: `Organization (${userData?.org?.name})`, value: "org" });
5650
5754
  }
5651
- }
5652
- if (createdSpace?.id) {
5653
- try {
5654
- await openSpaceInBrowser(createdSpace.id, region);
5655
- konsola.info(`Opened space in your browser`);
5656
- } catch (error) {
5657
- konsola.warn(`Failed to open browser: ${error.message}`);
5658
- const spaceUrl = generateSpaceUrl(createdSpace.id, region);
5659
- konsola.info(`You can manually open your space at: ${chalk.hex(colorPalette.PRIMARY)(spaceUrl)}`);
5755
+ if (userData.has_partner) {
5756
+ choices.push({ name: "Partner Portal", value: "partner" });
5660
5757
  }
5661
- }
5662
- konsola.br();
5663
- konsola.ok(`Your ${chalk.hex(colorPalette.PRIMARY)(technologyTemplate)} project is ready \u{1F389} !`);
5664
- if (createdSpace?.first_token) {
5758
+ if (region === regions.EU && (userData.has_partner || userData.has_org)) {
5759
+ whereToCreateSpace = await select({
5760
+ message: `Where would you like to create this space?`,
5761
+ choices
5762
+ });
5763
+ }
5764
+ if (region !== regions.EU && userData.has_org) {
5765
+ whereToCreateSpace = "org";
5766
+ }
5767
+ if (region !== regions.EU && !userData.has_org) {
5768
+ konsola.warn(`Space creation in this region is limited to Enterprise accounts. If you're part of an organization, please ensure you have the required permissions. For more information about Enterprise access, contact our Sales Team.`);
5769
+ konsola.br();
5770
+ return;
5771
+ }
5772
+ spinnerSpace.start(`Creating space "${toHumanReadable(projectName)}"`);
5773
+ const selectedBlueprint = templates.find((bp) => bp.value === technologyTemplate);
5774
+ const blueprintDomain = selectedBlueprint?.location || "https://localhost:3000/";
5775
+ const spaceToCreate = {
5776
+ name: toHumanReadable(projectName),
5777
+ domain: blueprintDomain
5778
+ };
5665
5779
  if (whereToCreateSpace === "org") {
5666
- konsola.ok(`Storyblok space created in organization ${chalk.hex(colorPalette.PRIMARY)(userData?.org?.name)}, preview url and .env configured automatically. You can now open your space in the browser at ${chalk.hex(colorPalette.PRIMARY)(generateSpaceUrl(createdSpace.id, region))}`);
5780
+ spaceToCreate.org = userData.org;
5781
+ spaceToCreate.in_org = true;
5667
5782
  } else if (whereToCreateSpace === "partner") {
5668
- konsola.ok(`Storyblok space created in partner portal, preview url and .env configured automatically. You can now open your space in the browser at ${chalk.hex(colorPalette.PRIMARY)(generateSpaceUrl(createdSpace.id, region))}`);
5669
- } else {
5670
- konsola.ok(`Storyblok space created, preview url and .env configured automatically. You can now open your space in the browser at ${chalk.hex(colorPalette.PRIMARY)(generateSpaceUrl(createdSpace.id, region))}`);
5783
+ spaceToCreate.assign_partner = true;
5784
+ }
5785
+ createdSpace = await createSpace(spaceToCreate);
5786
+ spinnerSpace.succeed(`Space "${chalk.hex(colorPalette.PRIMARY)(toHumanReadable(projectName))}" created successfully`);
5787
+ if (createdSpace?.first_token) {
5788
+ await handleEnvFileCreation(resolvedPath, createdSpace.first_token);
5789
+ }
5790
+ if (createdSpace?.id) {
5791
+ try {
5792
+ await openSpaceInBrowser(createdSpace.id, region);
5793
+ konsola.info(`Opened space in your browser`);
5794
+ } catch (error) {
5795
+ konsola.warn(`Failed to open browser: ${error.message}`);
5796
+ const spaceUrl = generateSpaceUrl(createdSpace.id, region);
5797
+ konsola.info(`You can manually open your space at: ${chalk.hex(colorPalette.PRIMARY)(spaceUrl)}`);
5798
+ }
5799
+ }
5800
+ showNextSteps(technologyTemplate, finalProjectPath);
5801
+ if (createdSpace?.first_token) {
5802
+ if (whereToCreateSpace === "org") {
5803
+ konsola.ok(`Storyblok space created in organization ${chalk.hex(colorPalette.PRIMARY)(userData?.org?.name)}, preview url and .env configured automatically. You can now open your space in the browser at ${chalk.hex(colorPalette.PRIMARY)(generateSpaceUrl(createdSpace.id, region))}`);
5804
+ } else if (whereToCreateSpace === "partner") {
5805
+ konsola.ok(`Storyblok space created in partner portal, preview url and .env configured automatically. You can now open your space in the browser at ${chalk.hex(colorPalette.PRIMARY)(generateSpaceUrl(createdSpace.id, region))}`);
5806
+ } else {
5807
+ konsola.ok(`Storyblok space created, preview url and .env configured automatically. You can now open your space in the browser at ${chalk.hex(colorPalette.PRIMARY)(generateSpaceUrl(createdSpace.id, region))}`);
5808
+ }
5671
5809
  }
5810
+ } catch (error) {
5811
+ spinnerSpace.failed();
5812
+ konsola.br();
5813
+ handleError(error, verbose);
5814
+ return;
5672
5815
  }
5673
- konsola.br();
5674
- konsola.info(`Next steps:
5675
- cd ${finalProjectPath}
5676
- npm install
5677
- npm run dev
5678
- `);
5679
- konsola.info(`Or check the dedicated guide at: ${chalk.hex(colorPalette.PRIMARY)(`https://www.storyblok.com/docs/guides/${technologyTemplate}`)}`);
5680
5816
  } catch (error) {
5681
5817
  spinnerSpace.failed();
5682
5818
  spinnerBlueprints.failed();
@@ -5686,13 +5822,13 @@ program$2.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
5686
5822
  konsola.br();
5687
5823
  });
5688
5824
 
5689
- const program$1 = getProgram();
5690
- const logsCommand = program$1.command(commands.LOGS).alias("lg").description(`Inspect and manage logs.`).option("-s, --space <space>", "The space ID.").option("-p, --path <path>", "Path to the directory containing the logs directory. Defaults to '.storyblok'.");
5825
+ const program$2 = getProgram();
5826
+ const logsCommand = program$2.command(commands.LOGS).alias("lg").description(`Inspect and manage logs.`).option("-s, --space <space>", "The space ID.").option("-p, --path <path>", "Path to the directory containing the logs directory. Defaults to '.storyblok'.");
5691
5827
 
5692
5828
  logsCommand.command("list").description("List logs").action(async () => {
5693
5829
  const { space, path } = logsCommand.opts();
5694
5830
  const ui = getUI();
5695
- const logsPath = getLogsPath(directories.log, space, path);
5831
+ const logsPath = resolveCommandPath(directories.log, space, path);
5696
5832
  const logFiles = FileTransport.listLogFiles(logsPath);
5697
5833
  if (logFiles.length === 0) {
5698
5834
  ui.info(`No logs found for space "${space}".`);
@@ -5705,23 +5841,40 @@ logsCommand.command("list").description("List logs").action(async () => {
5705
5841
  logsCommand.command("prune").description("Prune logs").option("--keep <number>", "Max number of log files to keep (default `0`, meaning remove all)", Number.parseInt, 0).action(async ({ keep }) => {
5706
5842
  const { space, path } = logsCommand.opts();
5707
5843
  const ui = getUI();
5708
- const logsPath = getLogsPath(directories.log, space, path);
5844
+ const logsPath = resolveCommandPath(directories.log, space, path);
5709
5845
  const deletedFilesCount = FileTransport.pruneLogFiles(logsPath, keep);
5710
5846
  ui.info(`Deleted ${deletedFilesCount} log file${deletedFilesCount === 1 ? "" : "s"}`);
5711
5847
  });
5712
5848
 
5713
- const version = "4.8.0";
5714
- const pkg = {
5715
- version: version};
5849
+ const program$1 = getProgram();
5850
+ const reportsCommand = program$1.command(commands.REPORTS).alias("rp").description("Inspect and manage reports.").option("-s, --space <space>", "The space ID.").option("-p, --path <path>", "Path to the directory containing the reports directory. Defaults to '.storyblok'.");
5851
+
5852
+ reportsCommand.command("list").description("List reports").action(async () => {
5853
+ const { space, path } = reportsCommand.opts();
5854
+ const ui = getUI();
5855
+ const reportsPath = resolveCommandPath(directories.report, space, path);
5856
+ const reportFiles = Reporter.listReportFiles(reportsPath, ".jsonl");
5857
+ if (reportFiles.length === 0) {
5858
+ ui.info(`No reports found for space "${space}".`);
5859
+ return;
5860
+ }
5861
+ ui.info(`Found ${reportFiles.length} report file${reportFiles.length === 1 ? "" : "s"} for space "${space}":`);
5862
+ ui.list(reportFiles);
5863
+ });
5864
+
5865
+ reportsCommand.command("prune").description("Prune reports").option("--keep <number>", "Max number of report files to keep (default `0`, meaning remove all)", Number.parseInt, 0).action(async ({ keep }) => {
5866
+ const { space, path } = reportsCommand.opts();
5867
+ const ui = getUI();
5868
+ const reportsPath = resolveCommandPath(directories.report, space, path);
5869
+ const deletedFilesCount = Reporter.pruneReportFiles(reportsPath, keep, ".jsonl");
5870
+ ui.info(`Deleted ${deletedFilesCount} report file${deletedFilesCount === 1 ? "" : "s"}`);
5871
+ });
5716
5872
 
5717
5873
  dotenv.config();
5718
5874
  const program = getProgram();
5719
5875
  konsola.br();
5720
5876
  konsola.br();
5721
5877
  konsola.title(` Storyblok CLI `, colorPalette.PRIMARY);
5722
- program.option("--verbose", "Enable verbose output");
5723
- program.version(pkg.version, "-v, --vers", "Output the current version");
5724
- program.helpOption("-h, --help", "Display help for command");
5725
5878
  program.on("command:*", () => {
5726
5879
  console.error(`Invalid command: ${program.args.join(" ")}`);
5727
5880
  konsola.br();