@blinkk/root-cms 2.5.1 → 2.5.3

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/app.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  getServerVersion
3
- } from "./chunk-KTUENARU.js";
3
+ } from "./chunk-WVOTD7EW.js";
4
4
  import {
5
5
  getCollectionSchema,
6
6
  getProjectSchemas
7
- } from "./chunk-RNDSZKAW.js";
7
+ } from "./chunk-MSJGHSR6.js";
8
8
  import "./chunk-MLKGABMK.js";
9
9
 
10
10
  // core/app.tsx
@@ -97,6 +97,7 @@ async function renderApp(req, res, options) {
97
97
  rootConfig: {
98
98
  projectId: cmsConfig.id || "default",
99
99
  projectName: cmsConfig.name || cmsConfig.id || "",
100
+ minimalBranding: cmsConfig.minimalBranding ?? false,
100
101
  domain: rootConfig.domain || "https://example.com",
101
102
  base: rootConfig.base || "/",
102
103
  gci,
@@ -13,7 +13,7 @@ async function generateTypes() {
13
13
  rootConfig,
14
14
  modulePath
15
15
  );
16
- const schemas = await project.getProjectSchemas();
16
+ const schemas = project.getProjectSchemas();
17
17
  const outputPath = path.resolve(rootDir, "root-cms.d.ts");
18
18
  await generateSchemaDts(outputPath, schemas);
19
19
  console.log("saved root-cms.d.ts!");
@@ -227,11 +227,11 @@ function fieldType(field, options) {
227
227
  if (field.types && Array.isArray(field.types)) {
228
228
  const unionTypes = [];
229
229
  field.types.forEach((schema) => {
230
- let typeName;
230
+ let typeName = "";
231
231
  if (typeof schema === "string") {
232
232
  typeName = schema;
233
233
  return;
234
- } else {
234
+ } else if (schema?.name) {
235
235
  typeName = schema.name;
236
236
  }
237
237
  if (!typeName) {
@@ -8,7 +8,7 @@ var SCHEMA_MODULES = import.meta.glob(
8
8
  ],
9
9
  { eager: true }
10
10
  );
11
- async function getProjectSchemas() {
11
+ function getProjectSchemas() {
12
12
  const schemas = {};
13
13
  for (const fileId in SCHEMA_MODULES) {
14
14
  const schemaModule = SCHEMA_MODULES[fileId];
@@ -37,7 +37,7 @@ function resolveOneOfPatterns(schemaObj) {
37
37
  (clone.fields || []).forEach(handleField);
38
38
  return clone;
39
39
  }
40
- async function getCollectionSchema(collectionId) {
40
+ function getCollectionSchema(collectionId) {
41
41
  if (!testValidCollectionId(collectionId)) {
42
42
  throw new Error(`invalid collection id: ${collectionId}`);
43
43
  }
@@ -167,5 +167,6 @@ function convertOneOfTypes(collection) {
167
167
  export {
168
168
  SCHEMA_MODULES,
169
169
  getProjectSchemas,
170
+ resolveOneOfPatterns,
170
171
  getCollectionSchema
171
172
  };
@@ -1,7 +1,7 @@
1
1
  // package.json
2
2
  var package_default = {
3
3
  name: "@blinkk/root-cms",
4
- version: "2.5.1",
4
+ version: "2.5.3",
5
5
  author: "s@blinkk.com",
6
6
  license: "MIT",
7
7
  engines: {
@@ -166,7 +166,7 @@ var package_default = {
166
166
  yjs: "13.6.27"
167
167
  },
168
168
  peerDependencies: {
169
- "@blinkk/root": "2.5.1",
169
+ "@blinkk/root": "2.5.3",
170
170
  "firebase-admin": ">=11",
171
171
  "firebase-functions": ">=4",
172
172
  preact: ">=10",
@@ -5,6 +5,11 @@ import {
5
5
  Timestamp as Timestamp2
6
6
  } from "firebase-admin/firestore";
7
7
 
8
+ // shared/slug.ts
9
+ function normalizeSlug(slug) {
10
+ return slug.replace(/^[\s/]*/g, "").replace(/[\s/]*$/g, "").replace(/^\/+|\/+$/g, "").replaceAll("/", "--");
11
+ }
12
+
8
13
  // core/translations-manager.ts
9
14
  import {
10
15
  FieldValue,
@@ -350,7 +355,7 @@ function buildTranslationsLocaleDocDbPath(options) {
350
355
  return TRANSLATIONS_LOCALE_DOC_DB_PATH_FORMAT.replace(
351
356
  "{project}",
352
357
  options.project
353
- ).replace("{mode}", options.mode).replace("{id}", options.id.replaceAll("/", "--")).replace("{locale}", options.locale);
358
+ ).replace("{mode}", options.mode).replace("{id}", normalizeSlug(options.id)).replace("{locale}", options.locale);
354
359
  }
355
360
 
356
361
  // core/validation.ts
@@ -675,6 +680,12 @@ var RootCMSClient = class {
675
680
  this.app = this.cmsPlugin.getFirebaseApp();
676
681
  this.db = this.cmsPlugin.getFirestore();
677
682
  }
683
+ /**
684
+ * Converts a doc mode to the Firestore collection name.
685
+ */
686
+ getModeCollection(mode) {
687
+ return mode === "draft" ? "Drafts" : "Published";
688
+ }
678
689
  /**
679
690
  * Retrieves doc data from Root.js CMS.
680
691
  */
@@ -690,9 +701,14 @@ var RootCMSClient = class {
690
701
  * what you are doing.
691
702
  */
692
703
  async getRawDoc(collectionId, slug, options) {
693
- const mode = options.mode;
694
- const modeCollection = mode === "draft" ? "Drafts" : "Published";
695
- slug = slug.replaceAll("/", "--");
704
+ if (!collectionId) {
705
+ throw new Error("collectionId is required");
706
+ }
707
+ if (!slug) {
708
+ throw new Error("slug is required");
709
+ }
710
+ const modeCollection = this.getModeCollection(options.mode);
711
+ slug = normalizeSlug(slug);
696
712
  const dbPath = `Projects/${this.projectId}/Collections/${collectionId}/${modeCollection}/${slug}`;
697
713
  const docRef = this.db.doc(dbPath);
698
714
  const doc = await docRef.get();
@@ -720,7 +736,7 @@ var RootCMSClient = class {
720
736
  */
721
737
  dbDocPath(collectionId, slug, options) {
722
738
  const collectionDocsPath = this.dbCollectionDocsPath(collectionId, options);
723
- const normalizedSlug = slug.replaceAll("/", "--");
739
+ const normalizedSlug = normalizeSlug(slug);
724
740
  return `${collectionDocsPath}/${normalizedSlug}`;
725
741
  }
726
742
  /**
@@ -801,12 +817,98 @@ ${errorMessages}`);
801
817
  });
802
818
  }
803
819
  /**
804
- * Prefer `saveDraftData('Pages/foo', data)`. Only use this if you know what
805
- * you're doing.
820
+ * Sets the raw document data directly in Firestore.
821
+ *
822
+ * CAUTION Prefer using `saveDraftData('Pages/foo', data)` in most cases.
823
+ * Only use this method if you need to manipulate system-level (sys) fields
824
+ * directly or if you're implementing low-level data operations.
825
+ *
826
+ * ## Validation & Normalization
827
+ *
828
+ * This method automatically validates and normalizes `sys` fields to prevent
829
+ * data integrity issues that can cause runtime errors like
830
+ * "e.toMillis is not a function".
831
+ *
832
+ * ### Timestamp Fields (auto-converted)
833
+ * The following fields accept multiple formats and are automatically converted
834
+ * to Firestore Timestamp objects:
835
+ * - `sys.createdAt`
836
+ * - `sys.modifiedAt`
837
+ * - `sys.publishedAt`
838
+ * - `sys.firstPublishedAt`
839
+ *
840
+ * Accepted formats:
841
+ * - Firestore `Timestamp` object (unchanged)
842
+ * - `number` - Interpreted as milliseconds since epoch, converted to Timestamp
843
+ * - `Date` object - Converted to Timestamp
844
+ *
845
+ * ### Required Fields (auto-populated with defaults if missing)
846
+ * - `sys.createdAt` - Defaults to current time if not provided
847
+ * - `sys.modifiedAt` - Defaults to current time if not provided
848
+ * - `sys.createdBy` - Defaults to 'root-cms-client' if not provided
849
+ * - `sys.modifiedBy` - Defaults to 'root-cms-client' if not provided
850
+ * - `sys.locales` - Defaults to ['en'] if not provided
851
+ *
852
+ * ### Optional Fields (validated if present)
853
+ * - `sys.publishedBy` - String identifier
854
+ * - `sys.firstPublishedBy` - String identifier
855
+ * - `sys.publishingLocked` - Object with optional `until` Timestamp
856
+ *
857
+ * ### Document Identity
858
+ * The `id`, `collection`, and `slug` fields are always set to match the
859
+ * provided parameters, overwriting any existing values to prevent data
860
+ * inconsistencies.
861
+ *
862
+ * @param collectionId - The collection ID (e.g., 'Pages')
863
+ * @param slug - The document slug (e.g., 'home')
864
+ * @param data - The complete document data including sys and fields
865
+ * @param options - Options specifying mode ('draft' or 'published')
866
+ *
867
+ * @throws {Error} If sys fields are invalid or missing required fields
868
+ * @throws {Error} If timestamp fields have invalid types
869
+ *
870
+ * @example
871
+ * ```typescript
872
+ * // Minimal example - sys fields use defaults
873
+ * await client.setRawDoc('Pages', 'home', {
874
+ * sys: {}, // All sys fields will be auto-populated with defaults
875
+ * fields: {
876
+ * title: 'Home Page'
877
+ * }
878
+ * }, { mode: 'draft' });
879
+ *
880
+ * // Full example - with number timestamps (auto-converted)
881
+ * await client.setRawDoc('Pages', 'home', {
882
+ * id: 'Pages/home',
883
+ * collection: 'Pages',
884
+ * slug: 'home',
885
+ * sys: {
886
+ * createdAt: Date.now(), // Auto-converted to Timestamp.
887
+ * createdBy: 'user@example.com',
888
+ * modifiedAt: Date.now(), // Auto-converted to Timestamp.
889
+ * modifiedBy: 'user@example.com',
890
+ * locales: ['en', 'es']
891
+ * },
892
+ * fields: {
893
+ * title: 'Home Page'
894
+ * }
895
+ * }, { mode: 'draft' });
896
+ * ```
806
897
  */
807
898
  async setRawDoc(collectionId, slug, data, options) {
808
- const mode = options.mode;
809
- const modeCollection = mode === "draft" ? "Drafts" : "Published";
899
+ if (!collectionId) {
900
+ throw new Error("collectionId is required");
901
+ }
902
+ if (!slug) {
903
+ throw new Error("slug is required");
904
+ }
905
+ slug = normalizeSlug(slug);
906
+ const expectedId = `${collectionId}/${slug}`;
907
+ data.id = expectedId;
908
+ data.collection = collectionId;
909
+ data.slug = slug;
910
+ data.sys = validateSysFields(data.sys || {});
911
+ const modeCollection = this.getModeCollection(options.mode);
810
912
  const dbPath = `Projects/${this.projectId}/Collections/${collectionId}/${modeCollection}/${slug}`;
811
913
  const docRef = this.db.doc(dbPath);
812
914
  await docRef.set(data);
@@ -815,8 +917,10 @@ ${errorMessages}`);
815
917
  * Lists docs from a Root.js CMS collection.
816
918
  */
817
919
  async listDocs(collectionId, options) {
818
- const mode = options.mode;
819
- const modeCollection = mode === "draft" ? "Drafts" : "Published";
920
+ if (!collectionId) {
921
+ throw new Error("collectionId is required");
922
+ }
923
+ const modeCollection = this.getModeCollection(options.mode);
820
924
  const dbPath = `Projects/${this.projectId}/Collections/${collectionId}/${modeCollection}`;
821
925
  let query = this.db.collection(dbPath);
822
926
  if (options.limit) {
@@ -848,8 +952,10 @@ ${errorMessages}`);
848
952
  * Returns the number of docs in a Root.js CMS collection.
849
953
  */
850
954
  async getDocsCount(collectionId, options) {
851
- const mode = options.mode;
852
- const modeCollection = mode === "draft" ? "Drafts" : "Published";
955
+ if (!collectionId) {
956
+ throw new Error("collectionId is required");
957
+ }
958
+ const modeCollection = this.getModeCollection(options.mode);
853
959
  const dbPath = `Projects/${this.projectId}/Collections/${collectionId}/${modeCollection}`;
854
960
  let query = this.db.collection(dbPath);
855
961
  if (options.query) {
@@ -1295,7 +1401,7 @@ ${errorMessages}`);
1295
1401
  if (!(mode === "draft" || mode === "published")) {
1296
1402
  throw new Error(`invalid mode: ${mode}`);
1297
1403
  }
1298
- const slug = translationsId.replaceAll("/", "--");
1404
+ const slug = normalizeSlug(translationsId);
1299
1405
  const dbPath = `Projects/${this.projectId}/TranslationsManager/${mode}/Translations/${slug}`;
1300
1406
  return dbPath;
1301
1407
  }
@@ -1639,6 +1745,87 @@ function unmarshalData(data) {
1639
1745
  }
1640
1746
  return result;
1641
1747
  }
1748
+ function isDef(value) {
1749
+ return value !== void 0 && value !== null;
1750
+ }
1751
+ function convertToTimestamp(value, fieldName) {
1752
+ if (value && typeof value === "object" && typeof value.toMillis === "function") {
1753
+ return value;
1754
+ }
1755
+ if (typeof value === "number") {
1756
+ return Timestamp2.fromMillis(value);
1757
+ }
1758
+ if (value instanceof Date) {
1759
+ return Timestamp2.fromDate(value);
1760
+ }
1761
+ throw new Error(
1762
+ `Invalid timestamp for ${fieldName}: expected Timestamp, number, or Date, got ${typeof value}`
1763
+ );
1764
+ }
1765
+ function validateSysFields(sys) {
1766
+ if (!sys || typeof sys !== "object") {
1767
+ throw new Error("sys must be an object");
1768
+ }
1769
+ const result = { ...sys };
1770
+ const now = Timestamp2.now();
1771
+ if (isDef(result.createdAt)) {
1772
+ result.createdAt = convertToTimestamp(result.createdAt, "sys.createdAt");
1773
+ } else {
1774
+ result.createdAt = now;
1775
+ }
1776
+ if (isDef(result.modifiedAt)) {
1777
+ result.modifiedAt = convertToTimestamp(result.modifiedAt, "sys.modifiedAt");
1778
+ } else {
1779
+ result.modifiedAt = now;
1780
+ }
1781
+ const optionalTimestampFields = ["firstPublishedAt", "publishedAt"];
1782
+ for (const field of optionalTimestampFields) {
1783
+ if (isDef(sys[field])) {
1784
+ result[field] = convertToTimestamp(sys[field], `sys.${field}`);
1785
+ }
1786
+ }
1787
+ if (!result.createdBy || typeof result.createdBy !== "string") {
1788
+ result.createdBy = "root-cms-client";
1789
+ }
1790
+ if (!result.modifiedBy || typeof result.modifiedBy !== "string") {
1791
+ result.modifiedBy = "root-cms-client";
1792
+ }
1793
+ const optionalStringFields = ["firstPublishedBy", "publishedBy"];
1794
+ for (const field of optionalStringFields) {
1795
+ if (isDef(sys[field])) {
1796
+ if (typeof sys[field] !== "string") {
1797
+ throw new Error(
1798
+ `Invalid sys.${field}: expected string, got ${typeof sys[field]}`
1799
+ );
1800
+ }
1801
+ }
1802
+ }
1803
+ if (isDef(sys.publishingLocked)) {
1804
+ if (typeof sys.publishingLocked !== "object") {
1805
+ throw new Error("Invalid sys.publishingLocked: expected object or null");
1806
+ }
1807
+ if (isDef(sys.publishingLocked.until)) {
1808
+ result.publishingLocked = {
1809
+ ...sys.publishingLocked,
1810
+ until: convertToTimestamp(
1811
+ sys.publishingLocked.until,
1812
+ "sys.publishingLocked.until"
1813
+ )
1814
+ };
1815
+ }
1816
+ }
1817
+ if (isDef(result.locales)) {
1818
+ if (!Array.isArray(result.locales)) {
1819
+ throw new Error("Invalid sys.locales: expected array");
1820
+ }
1821
+ if (!result.locales.every((locale) => typeof locale === "string")) {
1822
+ throw new Error("Invalid sys.locales: all items must be strings");
1823
+ }
1824
+ } else {
1825
+ result.locales = ["en"];
1826
+ }
1827
+ return result;
1828
+ }
1642
1829
  function normalizeData(data) {
1643
1830
  return unmarshalData(data);
1644
1831
  }
@@ -1713,7 +1900,7 @@ function parseDocId(docId) {
1713
1900
  throw new Error(`invalid doc id: ${docId}`);
1714
1901
  }
1715
1902
  const collection = docId.slice(0, sepIndex);
1716
- const slug = docId.slice(sepIndex + 1).replaceAll("/", "--");
1903
+ const slug = normalizeSlug(docId.slice(sepIndex + 1));
1717
1904
  if (!collection || !slug) {
1718
1905
  throw new Error(`invalid doc id: ${docId}`);
1719
1906
  }
@@ -1925,6 +2112,7 @@ var BatchResponse = class {
1925
2112
  };
1926
2113
 
1927
2114
  export {
2115
+ normalizeSlug,
1928
2116
  TranslationsManager,
1929
2117
  buildTranslationsDbPath,
1930
2118
  buildTranslationsLocaleDocDbPath,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  RootCMSClient,
3
3
  getCmsPlugin
4
- } from "./chunk-NUUABQRN.js";
4
+ } from "./chunk-XMR3KFP5.js";
5
5
 
6
6
  // core/versions.ts
7
7
  import path from "path";
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  generateTypes
3
- } from "./chunk-RYF3UTHQ.js";
3
+ } from "./chunk-KDXHFMIH.js";
4
4
  import {
5
5
  getCmsPlugin
6
- } from "./chunk-NUUABQRN.js";
6
+ } from "./chunk-XMR3KFP5.js";
7
7
  import "./chunk-MLKGABMK.js";
8
8
 
9
9
  // cli/cli.ts
@@ -128,6 +128,11 @@ type CMSPluginOptions = {
128
128
  * URL for a custom favicon used by the CMS UI.
129
129
  */
130
130
  favicon?: string;
131
+ /**
132
+ * Enables minimal branding mode in the CMS header. This hides the Root CMS
133
+ * logo and displays the project name on the top-right.
134
+ */
135
+ minimalBranding?: boolean;
131
136
  /**
132
137
  * Callback when an action occurs.
133
138
  */
@@ -539,6 +544,10 @@ declare class RootCMSClient {
539
544
  readonly app: App;
540
545
  readonly db: Firestore;
541
546
  constructor(rootConfig: RootConfig);
547
+ /**
548
+ * Converts a doc mode to the Firestore collection name.
549
+ */
550
+ private getModeCollection;
542
551
  /**
543
552
  * Retrieves doc data from Root.js CMS.
544
553
  */
@@ -591,8 +600,83 @@ declare class RootCMSClient {
591
600
  */
592
601
  updateDraftData(docId: string, path: string, fieldValue: any, options?: UpdateDraftOptions): Promise<void>;
593
602
  /**
594
- * Prefer `saveDraftData('Pages/foo', data)`. Only use this if you know what
595
- * you're doing.
603
+ * Sets the raw document data directly in Firestore.
604
+ *
605
+ * CAUTION Prefer using `saveDraftData('Pages/foo', data)` in most cases.
606
+ * Only use this method if you need to manipulate system-level (sys) fields
607
+ * directly or if you're implementing low-level data operations.
608
+ *
609
+ * ## Validation & Normalization
610
+ *
611
+ * This method automatically validates and normalizes `sys` fields to prevent
612
+ * data integrity issues that can cause runtime errors like
613
+ * "e.toMillis is not a function".
614
+ *
615
+ * ### Timestamp Fields (auto-converted)
616
+ * The following fields accept multiple formats and are automatically converted
617
+ * to Firestore Timestamp objects:
618
+ * - `sys.createdAt`
619
+ * - `sys.modifiedAt`
620
+ * - `sys.publishedAt`
621
+ * - `sys.firstPublishedAt`
622
+ *
623
+ * Accepted formats:
624
+ * - Firestore `Timestamp` object (unchanged)
625
+ * - `number` - Interpreted as milliseconds since epoch, converted to Timestamp
626
+ * - `Date` object - Converted to Timestamp
627
+ *
628
+ * ### Required Fields (auto-populated with defaults if missing)
629
+ * - `sys.createdAt` - Defaults to current time if not provided
630
+ * - `sys.modifiedAt` - Defaults to current time if not provided
631
+ * - `sys.createdBy` - Defaults to 'root-cms-client' if not provided
632
+ * - `sys.modifiedBy` - Defaults to 'root-cms-client' if not provided
633
+ * - `sys.locales` - Defaults to ['en'] if not provided
634
+ *
635
+ * ### Optional Fields (validated if present)
636
+ * - `sys.publishedBy` - String identifier
637
+ * - `sys.firstPublishedBy` - String identifier
638
+ * - `sys.publishingLocked` - Object with optional `until` Timestamp
639
+ *
640
+ * ### Document Identity
641
+ * The `id`, `collection`, and `slug` fields are always set to match the
642
+ * provided parameters, overwriting any existing values to prevent data
643
+ * inconsistencies.
644
+ *
645
+ * @param collectionId - The collection ID (e.g., 'Pages')
646
+ * @param slug - The document slug (e.g., 'home')
647
+ * @param data - The complete document data including sys and fields
648
+ * @param options - Options specifying mode ('draft' or 'published')
649
+ *
650
+ * @throws {Error} If sys fields are invalid or missing required fields
651
+ * @throws {Error} If timestamp fields have invalid types
652
+ *
653
+ * @example
654
+ * ```typescript
655
+ * // Minimal example - sys fields use defaults
656
+ * await client.setRawDoc('Pages', 'home', {
657
+ * sys: {}, // All sys fields will be auto-populated with defaults
658
+ * fields: {
659
+ * title: 'Home Page'
660
+ * }
661
+ * }, { mode: 'draft' });
662
+ *
663
+ * // Full example - with number timestamps (auto-converted)
664
+ * await client.setRawDoc('Pages', 'home', {
665
+ * id: 'Pages/home',
666
+ * collection: 'Pages',
667
+ * slug: 'home',
668
+ * sys: {
669
+ * createdAt: Date.now(), // Auto-converted to Timestamp.
670
+ * createdBy: 'user@example.com',
671
+ * modifiedAt: Date.now(), // Auto-converted to Timestamp.
672
+ * modifiedBy: 'user@example.com',
673
+ * locales: ['en', 'es']
674
+ * },
675
+ * fields: {
676
+ * title: 'Home Page'
677
+ * }
678
+ * }, { mode: 'draft' });
679
+ * ```
596
680
  */
597
681
  setRawDoc(collectionId: string, slug: string, data: any, options: SetDocOptions): Promise<void>;
598
682
  /**
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import '@blinkk/root';
2
2
  import 'firebase-admin/app';
3
3
  import 'firebase-admin/firestore';
4
- export { A as Action, q as ArrayObject, C as BatchRequest, B as BatchRequestOptions, x as BatchRequestQuery, y as BatchRequestQueryOptions, E as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode, b as Doc, D as DocMode, i as GetCountOptions, G as GetDocOptions, H as HttpMethod, l as ListActionsOptions, h as ListDocsOptions, L as LoadTranslationsOptions, a as LocaleTranslations, k as Release, R as RootCMSClient, f as SaveDraftOptions, S as SetDocOptions, j as Translation, z as TranslationsDoc, T as TranslationsMap, g as UpdateDraftOptions, U as UserRole, n as getCmsPlugin, m as isRichTextData, r as marshalArray, o as marshalData, p as normalizeData, w as parseDocId, t as toArrayObject, v as translationsForLocale, s as unmarshalArray, u as unmarshalData } from './client-ROwBDNeR.js';
4
+ export { A as Action, q as ArrayObject, C as BatchRequest, B as BatchRequestOptions, x as BatchRequestQuery, y as BatchRequestQueryOptions, E as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode, b as Doc, D as DocMode, i as GetCountOptions, G as GetDocOptions, H as HttpMethod, l as ListActionsOptions, h as ListDocsOptions, L as LoadTranslationsOptions, a as LocaleTranslations, k as Release, R as RootCMSClient, f as SaveDraftOptions, S as SetDocOptions, j as Translation, z as TranslationsDoc, T as TranslationsMap, g as UpdateDraftOptions, U as UserRole, n as getCmsPlugin, m as isRichTextData, r as marshalArray, o as marshalData, p as normalizeData, w as parseDocId, t as toArrayObject, v as translationsForLocale, s as unmarshalArray, u as unmarshalData } from './client-C1pZQL7M.js';
5
5
  import './schema-BKfPP_s9.js';
package/dist/client.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  translationsForLocale,
13
13
  unmarshalArray,
14
14
  unmarshalData
15
- } from "./chunk-NUUABQRN.js";
15
+ } from "./chunk-XMR3KFP5.js";
16
16
  import "./chunk-MLKGABMK.js";
17
17
  export {
18
18
  BatchRequest,
package/dist/core.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RootCMSClient, D as DocMode, L as LoadTranslationsOptions, T as TranslationsMap, a as LocaleTranslations } from './client-ROwBDNeR.js';
2
- export { A as Action, q as ArrayObject, C as BatchRequest, B as BatchRequestOptions, x as BatchRequestQuery, y as BatchRequestQueryOptions, E as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode, b as Doc, i as GetCountOptions, G as GetDocOptions, H as HttpMethod, l as ListActionsOptions, h as ListDocsOptions, F as Locale, O as MultiLocaleTranslationsMap, k as Release, f as SaveDraftOptions, S as SetDocOptions, P as SingleLocaleTranslationsMap, I as SourceString, J as TranslatedString, j as Translation, z as TranslationsDoc, K as TranslationsDocMode, N as TranslationsLocaleDocEntry, M as TranslationsLocaleDocHashMap, Q as TranslationsManager, g as UpdateDraftOptions, U as UserRole, V as buildTranslationsDbPath, W as buildTranslationsLocaleDocDbPath, n as getCmsPlugin, m as isRichTextData, r as marshalArray, o as marshalData, p as normalizeData, w as parseDocId, t as toArrayObject, v as translationsForLocale, s as unmarshalArray, u as unmarshalData } from './client-ROwBDNeR.js';
1
+ import { R as RootCMSClient, D as DocMode, L as LoadTranslationsOptions, T as TranslationsMap, a as LocaleTranslations } from './client-C1pZQL7M.js';
2
+ export { A as Action, q as ArrayObject, C as BatchRequest, B as BatchRequestOptions, x as BatchRequestQuery, y as BatchRequestQueryOptions, E as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode, b as Doc, i as GetCountOptions, G as GetDocOptions, H as HttpMethod, l as ListActionsOptions, h as ListDocsOptions, F as Locale, O as MultiLocaleTranslationsMap, k as Release, f as SaveDraftOptions, S as SetDocOptions, P as SingleLocaleTranslationsMap, I as SourceString, J as TranslatedString, j as Translation, z as TranslationsDoc, K as TranslationsDocMode, N as TranslationsLocaleDocEntry, M as TranslationsLocaleDocHashMap, Q as TranslationsManager, g as UpdateDraftOptions, U as UserRole, V as buildTranslationsDbPath, W as buildTranslationsLocaleDocDbPath, n as getCmsPlugin, m as isRichTextData, r as marshalArray, o as marshalData, p as normalizeData, w as parseDocId, t as toArrayObject, v as translationsForLocale, s as unmarshalArray, u as unmarshalData } from './client-C1pZQL7M.js';
3
3
  import { Request, RootConfig, Response, RouteParams, GetStaticProps, GetStaticPaths } from '@blinkk/root';
4
4
  import { Query } from 'firebase-admin/firestore';
5
5
  export { s as schema } from './schema-BKfPP_s9.js';
package/dist/core.js CHANGED
@@ -10,12 +10,13 @@ import {
10
10
  marshalArray,
11
11
  marshalData,
12
12
  normalizeData,
13
+ normalizeSlug,
13
14
  parseDocId,
14
15
  toArrayObject,
15
16
  translationsForLocale,
16
17
  unmarshalArray,
17
18
  unmarshalData
18
- } from "./chunk-NUUABQRN.js";
19
+ } from "./chunk-XMR3KFP5.js";
19
20
  import {
20
21
  __export
21
22
  } from "./chunk-MLKGABMK.js";
@@ -48,7 +49,7 @@ function createRoute(options) {
48
49
  const { slug, mode } = routeContext;
49
50
  const translationsTags = [
50
51
  "common",
51
- `${options.collection}/${slug.replaceAll("/", "--")}`
52
+ `${options.collection}/${normalizeSlug(slug)}`
52
53
  ];
53
54
  if (options.translations) {
54
55
  const tags = options.translations(routeContext)?.tags || [];
@@ -102,7 +103,7 @@ function createRoute(options) {
102
103
  };
103
104
  const translationsTags = [
104
105
  "common",
105
- `${options.collection}/${slug.replaceAll("/", "--")}`
106
+ `${options.collection}/${normalizeSlug(slug)}`
106
107
  ];
107
108
  if (options.translations) {
108
109
  const tags = options.translations(routeContext)?.tags || [];
@@ -311,7 +312,7 @@ async function getDoc(rootConfig, collectionId, slug, options) {
311
312
  const mode = options.mode;
312
313
  const modeCollection = mode === "draft" ? "Drafts" : "Published";
313
314
  const db = cmsPlugin.getFirestore();
314
- slug = slug.replaceAll("/", "--");
315
+ slug = normalizeSlug(slug);
315
316
  const dbPath = `Projects/${projectId}/Collections/${collectionId}/${modeCollection}/${slug}`;
316
317
  const docRef = db.doc(dbPath);
317
318
  const doc = await docRef.get();
package/dist/functions.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  runCronJobs
3
- } from "./chunk-RIJF2AHU.js";
4
- import "./chunk-NUUABQRN.js";
3
+ } from "./chunk-YAQCD6SS.js";
4
+ import "./chunk-XMR3KFP5.js";
5
5
  import "./chunk-MLKGABMK.js";
6
6
 
7
7
  // core/functions.ts
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  generateSchemaDts,
3
3
  generateTypes
4
- } from "./chunk-RYF3UTHQ.js";
4
+ } from "./chunk-KDXHFMIH.js";
5
5
  import "./chunk-MLKGABMK.js";
6
6
  export {
7
7
  generateSchemaDts,
package/dist/plugin.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import '@blinkk/root';
2
2
  import 'firebase-admin/app';
3
3
  import 'firebase-admin/firestore';
4
- export { Z as CMSAIConfig, X as CMSBuiltInSidebarTool, a0 as CMSPlugin, $ as CMSPluginOptions, _ as CMSSidebarTool, Y as CMSUser, a1 as cmsPlugin } from './client-ROwBDNeR.js';
4
+ export { Z as CMSAIConfig, X as CMSBuiltInSidebarTool, a0 as CMSPlugin, $ as CMSPluginOptions, _ as CMSSidebarTool, Y as CMSUser, a1 as cmsPlugin } from './client-C1pZQL7M.js';
5
5
  import './schema-BKfPP_s9.js';
package/dist/plugin.js CHANGED
@@ -4,15 +4,15 @@ import {
4
4
  } from "./chunk-T5UK2H24.js";
5
5
  import {
6
6
  getServerVersion
7
- } from "./chunk-KTUENARU.js";
7
+ } from "./chunk-WVOTD7EW.js";
8
8
  import {
9
9
  runCronJobs
10
- } from "./chunk-RIJF2AHU.js";
10
+ } from "./chunk-YAQCD6SS.js";
11
11
  import {
12
12
  RootCMSClient,
13
13
  parseDocId,
14
14
  unmarshalData
15
- } from "./chunk-NUUABQRN.js";
15
+ } from "./chunk-XMR3KFP5.js";
16
16
  import "./chunk-MLKGABMK.js";
17
17
 
18
18
  // core/plugin.ts
@@ -892,7 +892,7 @@ function cmsPlugin(options) {
892
892
  if (process.env.NODE_ENV === "development" && options.watch !== false) {
893
893
  plugin.onFileChange = (eventName, filepath) => {
894
894
  if (filepath.endsWith(".schema.ts")) {
895
- import("./generate-types-TQBCE2SG.js").then((generateTypesModule) => {
895
+ import("./generate-types-MHWSSOWV.js").then((generateTypesModule) => {
896
896
  const generateTypes = generateTypesModule.generateTypes;
897
897
  generateTypes();
898
898
  });