storyblok 4.9.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();
@@ -2847,10 +2966,10 @@ 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
2974
  const { filter } = options;
2856
2975
  const fromSpace = options.from || space;
@@ -3044,11 +3163,11 @@ const saveLanguagesToFile = async (space, internationalizationOptions, options)
3044
3163
  }
3045
3164
  };
3046
3165
 
3047
- const program$9 = getProgram();
3048
- 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");
3049
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) => {
3050
3169
  konsola.title(`${commands.LANGUAGES}`, colorPalette.LANGUAGES);
3051
- const verbose = program$9.opts().verbose;
3170
+ const verbose = program$a.opts().verbose;
3052
3171
  const { space, path } = languagesCommand.opts();
3053
3172
  const { filename = "languages", suffix = options.space } = options;
3054
3173
  const { state, initializeSession } = session();
@@ -3095,8 +3214,8 @@ languagesCommand.command("pull").description(`Download your space's languages sc
3095
3214
  konsola.br();
3096
3215
  });
3097
3216
 
3098
- const program$8 = getProgram();
3099
- 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");
3100
3219
 
3101
3220
  const getMigrationTemplate = () => {
3102
3221
  return `export default function (block) {
@@ -3815,6 +3934,7 @@ migrationsCommand.command("run [componentName]").description("Run migrations").o
3815
3934
  const program = getProgram();
3816
3935
  const ui = getUI();
3817
3936
  const logger = getLogger();
3937
+ const reporter = getReporter();
3818
3938
  ui.title(`${commands.MIGRATIONS}`, colorPalette.MIGRATIONS, componentName ? `Running migrations for component ${componentName}...` : "Running migrations...");
3819
3939
  logger.info("Migration started");
3820
3940
  if (options.dryRun) {
@@ -3917,21 +4037,26 @@ migrationsCommand.command("run [componentName]").description("Run migrations").o
3917
4037
  ui.info(migrationSummary);
3918
4038
  const updateSummary = updateStream.getSummary();
3919
4039
  ui.info(updateSummary);
3920
- const migrationResults = migrationStream.getResults();
3921
- 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
+ };
3922
4053
  logger.info("Migration finished", {
3923
- migrationResults: {
3924
- total: migrationResults.totalProcessed,
3925
- succeeded: migrationResults.successful.length,
3926
- skipped: migrationResults.skipped.length,
3927
- failed: migrationResults.failed.length
3928
- },
3929
- updateResults: {
3930
- total: updateResults.totalProcessed,
3931
- succeeded: updateResults.successful.length,
3932
- failed: updateResults.failed.length
3933
- }
4054
+ migrationResults,
4055
+ updateResults
3934
4056
  });
4057
+ reporter.addSummary("migrationResults", migrationResults);
4058
+ reporter.addSummary("updateResults", updateResults);
4059
+ reporter.finalize();
3935
4060
  } catch (error) {
3936
4061
  handleError(error, verbose);
3937
4062
  }
@@ -4026,8 +4151,8 @@ migrationsCommand.command("rollback [migrationFile]").description("Rollback a mi
4026
4151
  }
4027
4152
  });
4028
4153
 
4029
- const program$7 = getProgram();
4030
- 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");
4031
4156
 
4032
4157
  const getAssetJSONSchema = (title) => ({
4033
4158
  $id: "#/asset",
@@ -4935,13 +5060,13 @@ async function readConsolidatedFiles(resolvedPath, suffix) {
4935
5060
  };
4936
5061
  }
4937
5062
 
4938
- const program$6 = getProgram();
5063
+ const program$7 = getProgram();
4939
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(
4940
5065
  "--filename <name>",
4941
5066
  "Base file name for all component types when generating a single declarations file (e.g. components.d.ts). Ignored when using --separate-files."
4942
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) => {
4943
5068
  konsola.title(`${commands.TYPES}`, colorPalette.TYPES, "Generating types...");
4944
- const verbose = program$6.opts().verbose;
5069
+ const verbose = program$7.opts().verbose;
4945
5070
  const { space, path } = typesCommand.opts();
4946
5071
  const spinner = new Spinner({
4947
5072
  verbose: !isVitest
@@ -4994,8 +5119,8 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
4994
5119
  }
4995
5120
  });
4996
5121
 
4997
- const program$5 = getProgram();
4998
- 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");
4999
5124
 
5000
5125
  async function fetchAllPages(fetchFunction, extractDataFunction, page = 1, collectedItems = []) {
5001
5126
  const { data, response } = await fetchFunction(page);
@@ -5101,10 +5226,10 @@ const saveDatasourcesToFiles = async (space, datasources, options) => {
5101
5226
  }
5102
5227
  };
5103
5228
 
5104
- const program$4 = getProgram();
5229
+ const program$5 = getProgram();
5105
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) => {
5106
5231
  konsola.title(`${commands.DATASOURCES}`, colorPalette.DATASOURCES, datasourceName ? `Pulling datasource ${datasourceName}...` : "Pulling datasources...");
5107
- const verbose = program$4.opts().verbose;
5232
+ const verbose = program$5.opts().verbose;
5108
5233
  const { space, path } = datasourcesCommand.opts();
5109
5234
  const { separateFiles, suffix, filename = "datasources" } = options;
5110
5235
  const { state, initializeSession } = session();
@@ -5173,10 +5298,10 @@ datasourcesCommand.command("pull [datasourceName]").option("-f, --filename <file
5173
5298
  }
5174
5299
  });
5175
5300
 
5176
- const program$3 = getProgram();
5301
+ const program$4 = getProgram();
5177
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) => {
5178
5303
  konsola.title(`${commands.DATASOURCES}`, colorPalette.DATASOURCES, datasourceName ? `Pushing datasource ${datasourceName}...` : "Pushing datasources...");
5179
- const verbose = program$3.opts().verbose;
5304
+ const verbose = program$4.opts().verbose;
5180
5305
  const { space, path } = datasourcesCommand.opts();
5181
5306
  const { filter } = options;
5182
5307
  const fromSpace = options.from || space;
@@ -5513,10 +5638,10 @@ async function handleEnvFileCreation(resolvedPath, token) {
5513
5638
  return false;
5514
5639
  }
5515
5640
  }
5516
- const program$2 = getProgram();
5517
- 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").option("--token <token>", "Storyblok access token (skip space creation and use this token)").action(async (projectPath, options) => {
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) => {
5518
5643
  konsola.title(`${commands.CREATE}`, colorPalette.CREATE);
5519
- const verbose = program$2.opts().verbose;
5644
+ const verbose = program$3.opts().verbose;
5520
5645
  const { template, blueprint, token } = options;
5521
5646
  let selectedTemplate = template;
5522
5647
  if (blueprint && !template) {
@@ -5697,13 +5822,13 @@ program$2.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
5697
5822
  konsola.br();
5698
5823
  });
5699
5824
 
5700
- const program$1 = getProgram();
5701
- 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'.");
5702
5827
 
5703
5828
  logsCommand.command("list").description("List logs").action(async () => {
5704
5829
  const { space, path } = logsCommand.opts();
5705
5830
  const ui = getUI();
5706
- const logsPath = getLogsPath(directories.log, space, path);
5831
+ const logsPath = resolveCommandPath(directories.log, space, path);
5707
5832
  const logFiles = FileTransport.listLogFiles(logsPath);
5708
5833
  if (logFiles.length === 0) {
5709
5834
  ui.info(`No logs found for space "${space}".`);
@@ -5716,23 +5841,40 @@ logsCommand.command("list").description("List logs").action(async () => {
5716
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 }) => {
5717
5842
  const { space, path } = logsCommand.opts();
5718
5843
  const ui = getUI();
5719
- const logsPath = getLogsPath(directories.log, space, path);
5844
+ const logsPath = resolveCommandPath(directories.log, space, path);
5720
5845
  const deletedFilesCount = FileTransport.pruneLogFiles(logsPath, keep);
5721
5846
  ui.info(`Deleted ${deletedFilesCount} log file${deletedFilesCount === 1 ? "" : "s"}`);
5722
5847
  });
5723
5848
 
5724
- const version = "4.9.0";
5725
- const pkg = {
5726
- 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
+ });
5727
5872
 
5728
5873
  dotenv.config();
5729
5874
  const program = getProgram();
5730
5875
  konsola.br();
5731
5876
  konsola.br();
5732
5877
  konsola.title(` Storyblok CLI `, colorPalette.PRIMARY);
5733
- program.option("--verbose", "Enable verbose output");
5734
- program.version(pkg.version, "-v, --vers", "Output the current version");
5735
- program.helpOption("-h, --help", "Display help for command");
5736
5878
  program.on("command:*", () => {
5737
5879
  console.error(`Invalid command: ${program.args.join(" ")}`);
5738
5880
  konsola.br();