storyblok 4.6.13 → 4.7.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
@@ -4041,6 +4041,7 @@ const DEFAULT_TYPEDEFS_HEADER = [
4041
4041
  "// This file was generated by the storyblok CLI.",
4042
4042
  "// DO NOT MODIFY THIS FILE BY HAND."
4043
4043
  ];
4044
+ const getDatasourceTypeTitle = (slug) => `${toPascalCase(slug)}DataSource`;
4044
4045
  const getPropertyTypeAnnotation = (property, prefix, suffix) => {
4045
4046
  if (Array.from(storyblokSchemas.keys()).includes(property.type)) {
4046
4047
  return { type: property.type };
@@ -4149,6 +4150,13 @@ const getComponentPropertiesTypeAnnotations = async (component, options, spaceDa
4149
4150
  const componentType = toPascalCase(propertyType);
4150
4151
  propertyTypeAnnotation[key].tsType = `Storyblok${componentType}`;
4151
4152
  }
4153
+ if (spaceData.datasources.length > 0 && schema.source === "internal" && schema?.datasource_slug) {
4154
+ const datasourceExists = spaceData.datasources.some((ds) => ds.slug === schema.datasource_slug);
4155
+ if (datasourceExists) {
4156
+ const type = getDatasourceTypeTitle(schema.datasource_slug);
4157
+ propertyTypeAnnotation[key].tsType = propertyType === "options" ? `${type}[]` : type;
4158
+ }
4159
+ }
4152
4160
  if (propertyType === "multilink") {
4153
4161
  const excludedLinktypes = [
4154
4162
  ...!schema.email_link_type ? ['{ linktype?: "email" }'] : [],
@@ -4222,6 +4230,7 @@ const generateTypes = async (spaceData, options = {
4222
4230
  try {
4223
4231
  const typeDefs = [...DEFAULT_TYPEDEFS_HEADER];
4224
4232
  const storyblokPropertyTypes = /* @__PURE__ */ new Set();
4233
+ const contentTypeBloks = /* @__PURE__ */ new Set();
4225
4234
  let customFieldsParser;
4226
4235
  let compilerOptions;
4227
4236
  if (options.customFieldsParser) {
@@ -4230,8 +4239,11 @@ const generateTypes = async (spaceData, options = {
4230
4239
  if (options.compilerOptions) {
4231
4240
  compilerOptions = await loadCompilerOptions(options.compilerOptions);
4232
4241
  }
4233
- const schemas = await Promise.all(spaceData.components.map(async (component) => {
4242
+ const componentsSchema = spaceData.components.map(async (component) => {
4234
4243
  const type = getComponentType(component.name, options);
4244
+ if (component.is_root) {
4245
+ contentTypeBloks.add(type);
4246
+ }
4235
4247
  const componentPropertiesTypeAnnotations = await getComponentPropertiesTypeAnnotations(component, options, spaceData, customFieldsParser);
4236
4248
  const requiredFields = Object.entries(component?.schema || {}).reduce(
4237
4249
  (acc, [key, value]) => {
@@ -4270,7 +4282,35 @@ const generateTypes = async (spaceData, options = {
4270
4282
  }
4271
4283
  };
4272
4284
  return componentSchema;
4273
- }));
4285
+ });
4286
+ const resolvedComponentsSchema = await Promise.all(componentsSchema);
4287
+ const datasourcesSchema = spaceData.datasources.map(async (datasource) => {
4288
+ const allComponentTypes = resolvedComponentsSchema.map((schema) => schema.title);
4289
+ const enumValues = datasource.entries?.filter((d) => d.value).map((d) => d.value);
4290
+ const type = getDatasourceTypeTitle(datasource.slug);
4291
+ if (allComponentTypes.includes(type)) {
4292
+ console.warn(`Warning: Datasource type "${type}" conflicts with existing component type`);
4293
+ }
4294
+ const datasourceSchema = {
4295
+ $id: `#/${datasource.slug}`,
4296
+ title: type,
4297
+ type: "string",
4298
+ enum: enumValues
4299
+ };
4300
+ return datasourceSchema;
4301
+ });
4302
+ const resolvedDatasourcesSchema = await Promise.all(datasourcesSchema);
4303
+ const contentTypeSchema = {
4304
+ $id: `#/ContentType`,
4305
+ title: "ContentType",
4306
+ type: "string",
4307
+ tsType: contentTypeBloks.size > 0 ? `${Array.from(contentTypeBloks).join(" | ")}` : "never"
4308
+ };
4309
+ const schemas = [
4310
+ ...resolvedComponentsSchema,
4311
+ ...resolvedDatasourcesSchema,
4312
+ contentTypeSchema
4313
+ ];
4274
4314
  const result = await Promise.all(schemas.map(async (schema) => {
4275
4315
  return await compile(schema, schema.title || schema.$id.replace("#/", ""), {
4276
4316
  additionalProperties: !options.strict,
@@ -4328,6 +4368,160 @@ const generateStoryblokTypes = async (options = {}) => {
4328
4368
  }
4329
4369
  };
4330
4370
 
4371
+ const pushDatasource = async (spaceId, datasource) => {
4372
+ try {
4373
+ const client = mapiClient();
4374
+ const { data } = await client.datasources.create({
4375
+ path: {
4376
+ space_id: spaceId
4377
+ },
4378
+ body: { datasource },
4379
+ throwOnError: true
4380
+ });
4381
+ return data.datasource;
4382
+ } catch (error) {
4383
+ handleAPIError("push_datasource", error, `Failed to push datasource ${datasource.name}`);
4384
+ }
4385
+ };
4386
+ const updateDatasource = async (spaceId, datasourceId, datasource) => {
4387
+ try {
4388
+ const client = mapiClient();
4389
+ const { data } = await client.datasources.update({
4390
+ path: {
4391
+ space_id: spaceId,
4392
+ datasource_id: datasourceId
4393
+ },
4394
+ body: {
4395
+ datasource
4396
+ },
4397
+ throwOnError: true
4398
+ });
4399
+ return data.datasource;
4400
+ } catch (error) {
4401
+ handleAPIError("update_datasource", error, `Failed to update datasource ${datasource.name}`);
4402
+ }
4403
+ };
4404
+ const upsertDatasource = async (space, datasource, existingId) => {
4405
+ if (existingId) {
4406
+ return await updateDatasource(space, existingId, datasource);
4407
+ } else {
4408
+ return await pushDatasource(space, datasource);
4409
+ }
4410
+ };
4411
+ const pushDatasourceEntry = async (spaceId, datasourceId, entry) => {
4412
+ try {
4413
+ const client = mapiClient();
4414
+ const { data } = await client.datasourceEntries.create({
4415
+ path: {
4416
+ space_id: spaceId
4417
+ },
4418
+ body: {
4419
+ datasource_entry: {
4420
+ ...entry,
4421
+ datasource_id: datasourceId
4422
+ }
4423
+ },
4424
+ throwOnError: true
4425
+ });
4426
+ return data.datasource_entry;
4427
+ } catch (error) {
4428
+ handleAPIError("push_datasource", error, `Failed to push datasource entry ${entry.name}`);
4429
+ }
4430
+ };
4431
+ const updateDatasourceEntry = async (spaceId, entryId, entry) => {
4432
+ try {
4433
+ const client = mapiClient();
4434
+ await client.datasourceEntries.updateDatasourceEntry({
4435
+ path: {
4436
+ space_id: spaceId,
4437
+ datasource_entry_id: entryId
4438
+ },
4439
+ body: {
4440
+ datasource_entry: entry
4441
+ },
4442
+ throwOnError: true
4443
+ });
4444
+ } catch (error) {
4445
+ handleAPIError("update_datasource", error, `Failed to update datasource entry ${entry.name}`);
4446
+ }
4447
+ };
4448
+ const upsertDatasourceEntry = async (space, datasourceId, entry, existingId) => {
4449
+ if (existingId) {
4450
+ await updateDatasourceEntry(space, existingId, entry);
4451
+ return void 0;
4452
+ } else {
4453
+ return await pushDatasourceEntry(space, datasourceId, entry);
4454
+ }
4455
+ };
4456
+ const readDatasourcesFiles = async (options) => {
4457
+ const { from, path, separateFiles = false, suffix, space } = options;
4458
+ const resolvedPath = resolvePath(path, `datasources/${from}`);
4459
+ try {
4460
+ await readdir(resolvedPath);
4461
+ } catch (error) {
4462
+ const message = `No local datasources found for space ${chalk.bold(from)}. To push datasources, you need to pull them first:
4463
+
4464
+ 1. Pull the datasources from your source space:
4465
+ ${chalk.cyan(`storyblok datasources pull --space ${from}`)}
4466
+
4467
+ 2. Then try pushing again:
4468
+ ${chalk.cyan(`storyblok datasources push --space ${space} --from ${from}`)}`;
4469
+ throw new FileSystemError(
4470
+ "file_not_found",
4471
+ "read",
4472
+ error,
4473
+ message
4474
+ );
4475
+ }
4476
+ if (separateFiles) {
4477
+ return await readSeparateFiles(resolvedPath, suffix);
4478
+ }
4479
+ return await readConsolidatedFiles(resolvedPath, suffix);
4480
+ };
4481
+ async function readSeparateFiles(resolvedPath, suffix) {
4482
+ const files = await readdir(resolvedPath);
4483
+ const datasources = [];
4484
+ const filteredFiles = files.filter((file) => {
4485
+ if (suffix) {
4486
+ return file.endsWith(`.${suffix}.json`);
4487
+ } else {
4488
+ return !/\.\w+\.json$/.test(file);
4489
+ }
4490
+ });
4491
+ for (const file of filteredFiles) {
4492
+ const filePath = join(resolvedPath, file);
4493
+ if (file.endsWith(".json") || file.endsWith(`${suffix}.json`)) {
4494
+ if (file === "datasources.json" || /^datasources\.\w+\.json$/.test(file)) {
4495
+ continue;
4496
+ }
4497
+ const result = await readJsonFile(filePath);
4498
+ if (result.error) {
4499
+ handleFileSystemError("read", result.error);
4500
+ continue;
4501
+ }
4502
+ datasources.push(...result.data);
4503
+ }
4504
+ }
4505
+ return {
4506
+ datasources
4507
+ };
4508
+ }
4509
+ async function readConsolidatedFiles(resolvedPath, suffix) {
4510
+ const datasourcesPath = join(resolvedPath, suffix ? `datasources.${suffix}.json` : "datasources.json");
4511
+ const datasourcesResult = await readJsonFile(datasourcesPath);
4512
+ if (datasourcesResult.error || !datasourcesResult.data.length) {
4513
+ throw new FileSystemError(
4514
+ "file_not_found",
4515
+ "read",
4516
+ datasourcesResult.error || new Error("Datasources file is empty"),
4517
+ `No datasources found in ${datasourcesPath}. Please make sure you have pulled the datasources first.`
4518
+ );
4519
+ }
4520
+ return {
4521
+ datasources: datasourcesResult.data
4522
+ };
4523
+ }
4524
+
4331
4525
  const program$5 = getProgram();
4332
4526
  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(
4333
4527
  "--filename <name>",
@@ -4341,19 +4535,33 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
4341
4535
  });
4342
4536
  try {
4343
4537
  spinner.start(`Generating types...`);
4344
- const spaceData = await readComponentsFiles({
4538
+ const componentsData = await readComponentsFiles({
4345
4539
  ...options,
4346
4540
  from: space,
4347
4541
  path
4348
4542
  });
4543
+ let dataSourceData;
4544
+ try {
4545
+ dataSourceData = await readDatasourcesFiles({
4546
+ ...options,
4547
+ from: space,
4548
+ path
4549
+ });
4550
+ } catch (error) {
4551
+ if (error instanceof FileSystemError && error.errorId === "file_not_found") {
4552
+ dataSourceData = { datasources: [] };
4553
+ } else {
4554
+ throw error;
4555
+ }
4556
+ }
4349
4557
  await generateStoryblokTypes({
4350
4558
  path
4351
4559
  });
4352
- const spaceDataWithDatasources = {
4353
- ...spaceData,
4354
- datasources: []
4560
+ const spaceDataWithComponentsAndDatasources = {
4561
+ ...componentsData,
4562
+ ...dataSourceData
4355
4563
  };
4356
- const typedefString = await generateTypes(spaceDataWithDatasources, {
4564
+ const typedefString = await generateTypes(spaceDataWithComponentsAndDatasources, {
4357
4565
  ...options,
4358
4566
  path
4359
4567
  });
@@ -4376,19 +4584,36 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
4376
4584
  const program$4 = getProgram();
4377
4585
  const datasourcesCommand = program$4.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");
4378
4586
 
4587
+ async function fetchAllPages(fetchFunction, extractDataFunction, page = 1, collectedItems = []) {
4588
+ const { data, response } = await fetchFunction(page);
4589
+ const totalHeader = response.headers.get("total");
4590
+ const total = Number(totalHeader);
4591
+ const fetchedItems = extractDataFunction(data);
4592
+ const allItems = [...collectedItems, ...fetchedItems];
4593
+ if (!totalHeader || Number.isNaN(total)) {
4594
+ return allItems;
4595
+ }
4596
+ if (allItems.length < total && fetchedItems.length > 0) {
4597
+ return fetchAllPages(fetchFunction, extractDataFunction, page + 1, allItems);
4598
+ }
4599
+ return allItems;
4600
+ }
4379
4601
  const fetchDatasourceEntries = async (spaceId, datasourceId) => {
4380
4602
  try {
4381
4603
  const client = mapiClient();
4382
- const { data } = await client.datasourceEntries.list({
4383
- path: {
4384
- space_id: spaceId
4385
- },
4386
- query: {
4387
- datasource_id: datasourceId
4388
- },
4389
- throwOnError: true
4390
- });
4391
- return data?.datasource_entries;
4604
+ return await fetchAllPages(
4605
+ (page) => client.datasourceEntries.list({
4606
+ path: {
4607
+ space_id: spaceId
4608
+ },
4609
+ query: {
4610
+ datasource_id: datasourceId,
4611
+ page
4612
+ },
4613
+ throwOnError: true
4614
+ }),
4615
+ (data) => data?.datasource_entries || []
4616
+ );
4392
4617
  } catch (error) {
4393
4618
  handleAPIError("pull_datasources", error);
4394
4619
  }
@@ -4396,21 +4621,26 @@ const fetchDatasourceEntries = async (spaceId, datasourceId) => {
4396
4621
  const fetchDatasources = async (spaceId) => {
4397
4622
  try {
4398
4623
  const client = mapiClient();
4399
- const { data } = await client.datasources.list({
4400
- path: {
4401
- space_id: spaceId
4402
- },
4403
- throwOnError: true
4404
- });
4405
- const datasources = data?.datasources;
4624
+ const datasources = await fetchAllPages(
4625
+ (page) => client.datasources.list({
4626
+ path: {
4627
+ space_id: spaceId
4628
+ },
4629
+ query: {
4630
+ page
4631
+ },
4632
+ throwOnError: true
4633
+ }),
4634
+ (data) => data?.datasources || []
4635
+ );
4406
4636
  const datasourcesWithEntries = await Promise.all(
4407
- datasources?.map(async (ds) => {
4637
+ datasources.map(async (ds) => {
4408
4638
  if (!ds.id) {
4409
4639
  return { ...ds, entries: [] };
4410
4640
  }
4411
4641
  const entries = await fetchDatasourceEntries(spaceId, ds.id);
4412
4642
  return { ...ds, entries };
4413
- }) || []
4643
+ })
4414
4644
  );
4415
4645
  return datasourcesWithEntries;
4416
4646
  } catch (error) {
@@ -4530,160 +4760,6 @@ datasourcesCommand.command("pull [datasourceName]").option("-f, --filename <file
4530
4760
  }
4531
4761
  });
4532
4762
 
4533
- const pushDatasource = async (spaceId, datasource) => {
4534
- try {
4535
- const client = mapiClient();
4536
- const { data } = await client.datasources.create({
4537
- path: {
4538
- space_id: spaceId
4539
- },
4540
- body: { datasource },
4541
- throwOnError: true
4542
- });
4543
- return data.datasource;
4544
- } catch (error) {
4545
- handleAPIError("push_datasource", error, `Failed to push datasource ${datasource.name}`);
4546
- }
4547
- };
4548
- const updateDatasource = async (spaceId, datasourceId, datasource) => {
4549
- try {
4550
- const client = mapiClient();
4551
- const { data } = await client.datasources.update({
4552
- path: {
4553
- space_id: spaceId,
4554
- datasource_id: datasourceId
4555
- },
4556
- body: {
4557
- datasource
4558
- },
4559
- throwOnError: true
4560
- });
4561
- return data.datasource;
4562
- } catch (error) {
4563
- handleAPIError("update_datasource", error, `Failed to update datasource ${datasource.name}`);
4564
- }
4565
- };
4566
- const upsertDatasource = async (space, datasource, existingId) => {
4567
- if (existingId) {
4568
- return await updateDatasource(space, existingId, datasource);
4569
- } else {
4570
- return await pushDatasource(space, datasource);
4571
- }
4572
- };
4573
- const pushDatasourceEntry = async (spaceId, datasourceId, entry) => {
4574
- try {
4575
- const client = mapiClient();
4576
- const { data } = await client.datasourceEntries.create({
4577
- path: {
4578
- space_id: spaceId
4579
- },
4580
- body: {
4581
- datasource_entry: {
4582
- ...entry,
4583
- datasource_id: datasourceId
4584
- }
4585
- },
4586
- throwOnError: true
4587
- });
4588
- return data.datasource_entry;
4589
- } catch (error) {
4590
- handleAPIError("push_datasource", error, `Failed to push datasource entry ${entry.name}`);
4591
- }
4592
- };
4593
- const updateDatasourceEntry = async (spaceId, entryId, entry) => {
4594
- try {
4595
- const client = mapiClient();
4596
- await client.datasourceEntries.updateDatasourceEntry({
4597
- path: {
4598
- space_id: spaceId,
4599
- datasource_entry_id: entryId
4600
- },
4601
- body: {
4602
- datasource_entry: entry
4603
- },
4604
- throwOnError: true
4605
- });
4606
- } catch (error) {
4607
- handleAPIError("update_datasource", error, `Failed to update datasource entry ${entry.name}`);
4608
- }
4609
- };
4610
- const upsertDatasourceEntry = async (space, datasourceId, entry, existingId) => {
4611
- if (existingId) {
4612
- await updateDatasourceEntry(space, existingId, entry);
4613
- return void 0;
4614
- } else {
4615
- return await pushDatasourceEntry(space, datasourceId, entry);
4616
- }
4617
- };
4618
- const readDatasourcesFiles = async (options) => {
4619
- const { from, path, separateFiles = false, suffix, space } = options;
4620
- const resolvedPath = resolvePath(path, `datasources/${from}`);
4621
- try {
4622
- await readdir(resolvedPath);
4623
- } catch (error) {
4624
- const message = `No local datasources found for space ${chalk.bold(from)}. To push datasources, you need to pull them first:
4625
-
4626
- 1. Pull the datasources from your source space:
4627
- ${chalk.cyan(`storyblok datasources pull --space ${from}`)}
4628
-
4629
- 2. Then try pushing again:
4630
- ${chalk.cyan(`storyblok datasources push --space ${space} --from ${from}`)}`;
4631
- throw new FileSystemError(
4632
- "file_not_found",
4633
- "read",
4634
- error,
4635
- message
4636
- );
4637
- }
4638
- if (separateFiles) {
4639
- return await readSeparateFiles(resolvedPath, suffix);
4640
- }
4641
- return await readConsolidatedFiles(resolvedPath, suffix);
4642
- };
4643
- async function readSeparateFiles(resolvedPath, suffix) {
4644
- const files = await readdir(resolvedPath);
4645
- const datasources = [];
4646
- const filteredFiles = files.filter((file) => {
4647
- if (suffix) {
4648
- return file.endsWith(`.${suffix}.json`);
4649
- } else {
4650
- return !/\.\w+\.json$/.test(file);
4651
- }
4652
- });
4653
- for (const file of filteredFiles) {
4654
- const filePath = join(resolvedPath, file);
4655
- if (file.endsWith(".json") || file.endsWith(`${suffix}.json`)) {
4656
- if (file === "datasources.json" || /^datasources\.\w+\.json$/.test(file)) {
4657
- continue;
4658
- }
4659
- const result = await readJsonFile(filePath);
4660
- if (result.error) {
4661
- handleFileSystemError("read", result.error);
4662
- continue;
4663
- }
4664
- datasources.push(...result.data);
4665
- }
4666
- }
4667
- return {
4668
- datasources
4669
- };
4670
- }
4671
- async function readConsolidatedFiles(resolvedPath, suffix) {
4672
- const datasourcesPath = join(resolvedPath, suffix ? `datasources.${suffix}.json` : "datasources.json");
4673
- const datasourcesResult = await readJsonFile(datasourcesPath);
4674
- if (datasourcesResult.error || !datasourcesResult.data.length) {
4675
- throw new FileSystemError(
4676
- "file_not_found",
4677
- "read",
4678
- datasourcesResult.error || new Error("Datasources file is empty"),
4679
- `No datasources found in ${datasourcesPath}. Please make sure you have pulled the datasources first.`
4680
- );
4681
- }
4682
- return {
4683
- datasources: datasourcesResult.data
4684
- };
4685
- }
4686
-
4687
4763
  const program$2 = getProgram();
4688
4764
  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) => {
4689
4765
  konsola.title(`${commands.DATASOURCES}`, colorPalette.DATASOURCES, datasourceName ? `Pushing datasource ${datasourceName}...` : "Pushing datasources...");
@@ -5195,7 +5271,7 @@ program$1.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
5195
5271
  konsola.br();
5196
5272
  });
5197
5273
 
5198
- const version = "4.6.13";
5274
+ const version = "4.7.0";
5199
5275
  const pkg = {
5200
5276
  version: version};
5201
5277