@blinkk/root-cms 1.4.3 → 1.4.5
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 +8 -3
- package/dist/cli.js +15 -0
- package/dist/{client-98Le1XxF.d.ts → client-c_QAurxr.d.ts} +209 -2
- package/dist/client.d.ts +1 -1
- package/dist/client.js +397 -22
- package/dist/core.d.ts +3 -3
- package/dist/core.js +405 -27
- package/dist/functions.js +401 -26
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +401 -26
- package/dist/project.d.ts +1 -1
- package/dist/{schema--Xq_zQlv.d.ts → schema-hlZc-G1f.d.ts} +11 -0
- package/dist/ui/ui.js +44 -44
- package/package.json +5 -3
package/dist/plugin.js
CHANGED
|
@@ -447,10 +447,361 @@ var ChatClient = class {
|
|
|
447
447
|
|
|
448
448
|
// core/client.ts
|
|
449
449
|
import crypto2 from "node:crypto";
|
|
450
|
+
import {
|
|
451
|
+
FieldValue as FieldValue2,
|
|
452
|
+
Timestamp as Timestamp3
|
|
453
|
+
} from "firebase-admin/firestore";
|
|
454
|
+
|
|
455
|
+
// core/translations-manager.ts
|
|
450
456
|
import {
|
|
451
457
|
FieldValue,
|
|
452
458
|
Timestamp as Timestamp2
|
|
453
459
|
} from "firebase-admin/firestore";
|
|
460
|
+
|
|
461
|
+
// shared/strings.ts
|
|
462
|
+
import fnv from "fnv-plus";
|
|
463
|
+
function normalizeStr(str) {
|
|
464
|
+
const lines = str.trim().split("\n").map((line) => removeTrailingWhitespace(line));
|
|
465
|
+
return lines.join("\n");
|
|
466
|
+
}
|
|
467
|
+
function removeTrailingWhitespace(str) {
|
|
468
|
+
return str.trimEnd().replace(/ $/, "");
|
|
469
|
+
}
|
|
470
|
+
function hashStr(str) {
|
|
471
|
+
if (!str || typeof str !== "string") {
|
|
472
|
+
throw new Error("input string is invalid");
|
|
473
|
+
}
|
|
474
|
+
return fnv.fast1a52hex(normalizeStr(str));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// core/translations-manager.ts
|
|
478
|
+
var TRANSLATIONS_DB_PATH_FORMAT = "/Projects/{project}/TranslationsManager/{mode}/Translations";
|
|
479
|
+
var TRANSLATIONS_LOCALE_DOC_DB_PATH_FORMAT = `${TRANSLATIONS_DB_PATH_FORMAT}/{id}:{locale}`;
|
|
480
|
+
var TranslationsManager = class {
|
|
481
|
+
constructor(cmsClient) {
|
|
482
|
+
this.cmsClient = cmsClient;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Saves draft translations for a translations doc id.
|
|
486
|
+
*
|
|
487
|
+
* Example:
|
|
488
|
+
* ```
|
|
489
|
+
* const strings = {
|
|
490
|
+
* 'one': {es: 'uno', fr: 'un'},
|
|
491
|
+
* 'two': {es: 'dos', fr: 'deux'},
|
|
492
|
+
* };
|
|
493
|
+
* await tm.saveTranslations('Pages/index', strings);
|
|
494
|
+
* ```
|
|
495
|
+
*/
|
|
496
|
+
async saveTranslations(id, strings, options) {
|
|
497
|
+
const mode = "draft";
|
|
498
|
+
const localesSet = /* @__PURE__ */ new Set();
|
|
499
|
+
Object.values(strings).forEach((entry) => {
|
|
500
|
+
Object.keys(entry).forEach((locale) => {
|
|
501
|
+
if (locale !== "source") {
|
|
502
|
+
localesSet.add(locale);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
const batch = this.cmsClient.db.batch();
|
|
507
|
+
const locales = Array.from(localesSet);
|
|
508
|
+
locales.forEach((locale) => {
|
|
509
|
+
const updates = {
|
|
510
|
+
id,
|
|
511
|
+
locale,
|
|
512
|
+
sys: {
|
|
513
|
+
modifiedAt: Timestamp2.now(),
|
|
514
|
+
modifiedBy: (options == null ? void 0 : options.modifiedBy) || "root-cms-client"
|
|
515
|
+
},
|
|
516
|
+
strings: {}
|
|
517
|
+
};
|
|
518
|
+
if ((options == null ? void 0 : options.tags) && options.tags.length > 0) {
|
|
519
|
+
updates.tags = FieldValue.arrayUnion(...options.tags);
|
|
520
|
+
}
|
|
521
|
+
let numUpdates = 0;
|
|
522
|
+
const hashMap = this.toLocaleDocHashMap(strings, locale);
|
|
523
|
+
Object.entries(hashMap).forEach(([hash, translations]) => {
|
|
524
|
+
Object.entries(translations).forEach(([locale2, translation]) => {
|
|
525
|
+
if (translation) {
|
|
526
|
+
updates.strings[hash] ??= {};
|
|
527
|
+
updates.strings[hash][locale2] = translation;
|
|
528
|
+
numUpdates += 1;
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
if (numUpdates > 0) {
|
|
533
|
+
const localeDocPath = buildTranslationsLocaleDocDbPath({
|
|
534
|
+
project: this.cmsClient.projectId,
|
|
535
|
+
mode,
|
|
536
|
+
id,
|
|
537
|
+
locale
|
|
538
|
+
});
|
|
539
|
+
const localeDocRef = this.cmsClient.db.doc(localeDocPath);
|
|
540
|
+
batch.set(localeDocRef, updates, { merge: true });
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
await batch.commit();
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Publishes a translations doc.
|
|
547
|
+
*/
|
|
548
|
+
async publishTranslations(id, options) {
|
|
549
|
+
const db = this.cmsClient.db;
|
|
550
|
+
const project = this.cmsClient.projectId;
|
|
551
|
+
const draftPath = buildTranslationsDbPath({ project, mode: "draft" });
|
|
552
|
+
const query = db.collection(draftPath).where("id", "==", id);
|
|
553
|
+
const res = await query.get();
|
|
554
|
+
if (res.size === 0) {
|
|
555
|
+
console.warn(`no translations to publish for ${id}`);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const batch = (options == null ? void 0 : options.batch) || db.batch();
|
|
559
|
+
res.docs.forEach((doc) => {
|
|
560
|
+
const translationsLocaleDoc = doc.data();
|
|
561
|
+
const sys = {
|
|
562
|
+
...translationsLocaleDoc.sys,
|
|
563
|
+
publishedAt: Timestamp2.now(),
|
|
564
|
+
publishedBy: (options == null ? void 0 : options.publishedBy) || "root-cms-client"
|
|
565
|
+
};
|
|
566
|
+
batch.update(doc.ref, { sys });
|
|
567
|
+
const publishedDocPath = buildTranslationsLocaleDocDbPath({
|
|
568
|
+
project,
|
|
569
|
+
mode: "published",
|
|
570
|
+
id: translationsLocaleDoc.id,
|
|
571
|
+
locale: translationsLocaleDoc.locale
|
|
572
|
+
});
|
|
573
|
+
const publishedDocRef = db.doc(publishedDocPath);
|
|
574
|
+
batch.set(publishedDocRef, { ...translationsLocaleDoc, sys });
|
|
575
|
+
});
|
|
576
|
+
const shouldCommitBatch = !(options == null ? void 0 : options.batch);
|
|
577
|
+
if (shouldCommitBatch) {
|
|
578
|
+
await batch.commit();
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Fetches translations from one or more translations docs in the translations
|
|
583
|
+
* manager.
|
|
584
|
+
*
|
|
585
|
+
* Example:
|
|
586
|
+
* ```
|
|
587
|
+
* await tm.loadTranslations();
|
|
588
|
+
* // =>
|
|
589
|
+
* // {
|
|
590
|
+
* // "one": {"es": "uno", "fr": "un"},
|
|
591
|
+
* // "two": {"es": "dos", "fr": "deux"}
|
|
592
|
+
* // }
|
|
593
|
+
* ```
|
|
594
|
+
*
|
|
595
|
+
* To load a specific set of translations docs by id:
|
|
596
|
+
* ```
|
|
597
|
+
* const translationsToLoad = ['Global/strings', 'Global/header', 'Global/footer', 'Pages/index'];
|
|
598
|
+
* await tm.loadTranslations({ids: translationsToLoad});
|
|
599
|
+
* // =>
|
|
600
|
+
* // {
|
|
601
|
+
* // "one": {"es": "uno", "fr": "un"},
|
|
602
|
+
* // "two": {"es": "dos", "fr": "deux"}
|
|
603
|
+
* // }
|
|
604
|
+
* ```
|
|
605
|
+
*
|
|
606
|
+
* To load a subset of locales (more performant):
|
|
607
|
+
* ```
|
|
608
|
+
* await tm.loadTranslations({locales: ['es']});
|
|
609
|
+
* // =>
|
|
610
|
+
* // {
|
|
611
|
+
* // "one": {"es": "uno"},
|
|
612
|
+
* // "two": {"es": "dos"}
|
|
613
|
+
* // }
|
|
614
|
+
* ```
|
|
615
|
+
*/
|
|
616
|
+
async loadTranslations(options) {
|
|
617
|
+
const mode = (options == null ? void 0 : options.mode) || "published";
|
|
618
|
+
const dbPath = buildTranslationsDbPath({
|
|
619
|
+
project: this.cmsClient.projectId,
|
|
620
|
+
mode
|
|
621
|
+
});
|
|
622
|
+
let query = this.cmsClient.db.collection(dbPath);
|
|
623
|
+
if ((options == null ? void 0 : options.ids) && options.ids.length > 0) {
|
|
624
|
+
query = query.where("id", "in", options.ids);
|
|
625
|
+
}
|
|
626
|
+
if ((options == null ? void 0 : options.tags) && options.tags.length > 0) {
|
|
627
|
+
query = query.where("tags", "array-contains", options.tags);
|
|
628
|
+
}
|
|
629
|
+
if ((options == null ? void 0 : options.locales) && options.locales.length > 0) {
|
|
630
|
+
query = query.where("locale", "in", options.locales);
|
|
631
|
+
}
|
|
632
|
+
const results = await query.get();
|
|
633
|
+
const strings = {};
|
|
634
|
+
results.forEach((result) => {
|
|
635
|
+
const localeDoc = result.data();
|
|
636
|
+
Object.values(localeDoc.strings || {}).forEach((item) => {
|
|
637
|
+
strings[item.source] ??= { source: item.source };
|
|
638
|
+
strings[item.source][localeDoc.locale] = item.translation;
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
return strings;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Fetches translations for a given locale, with optional fallbacks.
|
|
645
|
+
* The return value is a map of source string to translated string.
|
|
646
|
+
*
|
|
647
|
+
* Example:
|
|
648
|
+
* ```
|
|
649
|
+
* await translationsDoc.loadTranslationsForLocale('es');
|
|
650
|
+
* // =>
|
|
651
|
+
* // {
|
|
652
|
+
* // "one": "uno",
|
|
653
|
+
* // "two": "dos",
|
|
654
|
+
* // }
|
|
655
|
+
* ```
|
|
656
|
+
*/
|
|
657
|
+
async loadTranslationsForLocale(locale, options) {
|
|
658
|
+
const localeSet = /* @__PURE__ */ new Set([
|
|
659
|
+
locale,
|
|
660
|
+
...(options == null ? void 0 : options.fallbackLocales) || []
|
|
661
|
+
]);
|
|
662
|
+
const fallbackLocales = Array.from(localeSet);
|
|
663
|
+
const multiLocaleStrings = await this.loadTranslations({
|
|
664
|
+
mode: options == null ? void 0 : options.mode,
|
|
665
|
+
locales: fallbackLocales
|
|
666
|
+
});
|
|
667
|
+
return this.toSingleLocaleMap(multiLocaleStrings, fallbackLocales);
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Converts a multi-locale translations map to a flat single-locale map,
|
|
671
|
+
* with optional support for fallback locales.
|
|
672
|
+
*
|
|
673
|
+
* ```
|
|
674
|
+
* const multiLocaleStrings = {
|
|
675
|
+
* 'one': {es: 'uno', fr: 'un'},
|
|
676
|
+
* 'two': {es: 'dos', fr: 'deux'}
|
|
677
|
+
* };
|
|
678
|
+
* translationsDoc.toSingleLocaleMap(multiLocaleStrings, ['es']);
|
|
679
|
+
* // =>
|
|
680
|
+
* // {
|
|
681
|
+
* // "one": "uno",
|
|
682
|
+
* // "two": "dos",
|
|
683
|
+
* // }
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
toSingleLocaleMap(multiLocaleStrings, fallbackLocales) {
|
|
687
|
+
const singleLocaleStrings = {};
|
|
688
|
+
Object.entries(multiLocaleStrings).forEach(([source, translations]) => {
|
|
689
|
+
let translation = source;
|
|
690
|
+
for (const locale of fallbackLocales) {
|
|
691
|
+
if (translations[locale]) {
|
|
692
|
+
translation = translations[locale];
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
singleLocaleStrings[source] = translation;
|
|
697
|
+
});
|
|
698
|
+
return singleLocaleStrings;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Converts a multi-locale translations map to a single-locale hashed version,
|
|
702
|
+
* used for storage in in the DB.
|
|
703
|
+
*
|
|
704
|
+
* ```
|
|
705
|
+
* const multiLocaleStrings = {
|
|
706
|
+
* 'one': {es: 'uno', fr: 'un'},
|
|
707
|
+
* 'two': {es: 'dos', fr: 'deux'}
|
|
708
|
+
* };
|
|
709
|
+
* translationsDoc.toLocaleDocHashMap(multiLocaleStrings, 'es');
|
|
710
|
+
* // =>
|
|
711
|
+
* // {
|
|
712
|
+
* // "<hash1>": {"source": "one", "translation": "uno"},
|
|
713
|
+
* // "<hash2>": {"source": "two", "translation": "dos"},
|
|
714
|
+
* // }
|
|
715
|
+
* ```
|
|
716
|
+
*
|
|
717
|
+
* One reason for using hashes is because the DB has limits on the number of
|
|
718
|
+
* chars that can be used as the "key" in a object map.
|
|
719
|
+
*/
|
|
720
|
+
toLocaleDocHashMap(multiLocaleStrings, locale) {
|
|
721
|
+
const hashMap = {};
|
|
722
|
+
Object.entries(multiLocaleStrings).forEach(([source, translations]) => {
|
|
723
|
+
const translation = translations[locale];
|
|
724
|
+
if (translation) {
|
|
725
|
+
const hash = hashStr(source);
|
|
726
|
+
hashMap[hash] = { source, translation };
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
return hashMap;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Import translations from the v1 system to the TranslationsManager.
|
|
733
|
+
*/
|
|
734
|
+
async importTranslationsFromV1() {
|
|
735
|
+
var _a;
|
|
736
|
+
const projectId = this.cmsClient.projectId;
|
|
737
|
+
const db = this.cmsClient.db;
|
|
738
|
+
const dbPath = `Projects/${projectId}/Translations`;
|
|
739
|
+
const query = db.collection(dbPath);
|
|
740
|
+
const querySnapshot = await query.get();
|
|
741
|
+
if (querySnapshot.size === 0) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
console.log(
|
|
745
|
+
"[root cms] importing v1 Translations to v2 TranslationsManager"
|
|
746
|
+
);
|
|
747
|
+
const translationsDocs = {};
|
|
748
|
+
querySnapshot.forEach((doc) => {
|
|
749
|
+
const translation = doc.data();
|
|
750
|
+
const source = this.cmsClient.normalizeString(translation.source);
|
|
751
|
+
delete translation.source;
|
|
752
|
+
const tags = translation.tags || [];
|
|
753
|
+
delete translation.tags;
|
|
754
|
+
for (const tag of tags) {
|
|
755
|
+
if (tag.includes("/")) {
|
|
756
|
+
const translationsId = tag;
|
|
757
|
+
translationsDocs[translationsId] ??= {
|
|
758
|
+
id: translationsId,
|
|
759
|
+
tags,
|
|
760
|
+
strings: {}
|
|
761
|
+
};
|
|
762
|
+
translationsDocs[translationsId].strings[source] = translation;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
if (Object.keys(translationsDocs).length === 0) {
|
|
767
|
+
console.log("[root cms] no v1 translations to save");
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
for (const docId in translationsDocs) {
|
|
771
|
+
const [collection, slug] = docId.split("/");
|
|
772
|
+
if (collection && slug) {
|
|
773
|
+
const doc = await this.cmsClient.getDoc(collection, slug, {
|
|
774
|
+
mode: "draft"
|
|
775
|
+
});
|
|
776
|
+
const linkedSheet = (_a = doc == null ? void 0 : doc.sys) == null ? void 0 : _a.l10nSheet;
|
|
777
|
+
if (linkedSheet) {
|
|
778
|
+
translationsDocs[docId].sys.linkedSheet = linkedSheet;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
Object.entries(translationsDocs).forEach(([translationsId, data]) => {
|
|
783
|
+
const len = Object.keys(data.strings).length;
|
|
784
|
+
console.log(`[root cms] saving ${len} string(s) to ${translationsId}...`);
|
|
785
|
+
this.saveTranslations(translationsId, data.strings, {
|
|
786
|
+
tags: data.tags || [translationsId]
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
function buildTranslationsDbPath(options) {
|
|
792
|
+
return TRANSLATIONS_DB_PATH_FORMAT.replace(
|
|
793
|
+
"{project}",
|
|
794
|
+
options.project
|
|
795
|
+
).replace("{mode}", options.mode);
|
|
796
|
+
}
|
|
797
|
+
function buildTranslationsLocaleDocDbPath(options) {
|
|
798
|
+
return TRANSLATIONS_LOCALE_DOC_DB_PATH_FORMAT.replace(
|
|
799
|
+
"{project}",
|
|
800
|
+
options.project
|
|
801
|
+
).replace("{mode}", options.mode).replace("{id}", options.id.replaceAll("/", "--")).replace("{locale}", options.locale);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// core/client.ts
|
|
454
805
|
var RootCMSClient = class {
|
|
455
806
|
constructor(rootConfig) {
|
|
456
807
|
this.rootConfig = rootConfig;
|
|
@@ -504,9 +855,9 @@ var RootCMSClient = class {
|
|
|
504
855
|
slug,
|
|
505
856
|
sys: {
|
|
506
857
|
...draftSys,
|
|
507
|
-
createdAt: draftSys.createdAt ??
|
|
858
|
+
createdAt: draftSys.createdAt ?? Timestamp3.now(),
|
|
508
859
|
createdBy: draftSys.createdBy ?? modifiedBy,
|
|
509
|
-
modifiedAt:
|
|
860
|
+
modifiedAt: Timestamp3.now(),
|
|
510
861
|
modifiedBy,
|
|
511
862
|
locales: (options == null ? void 0 : options.locales) ?? draftSys.locales ?? ["en"]
|
|
512
863
|
},
|
|
@@ -614,7 +965,7 @@ var RootCMSClient = class {
|
|
|
614
965
|
const publishedRef = this.db.doc(
|
|
615
966
|
`${projectCollectionsPath}/${collection}/Published/${slug}`
|
|
616
967
|
);
|
|
617
|
-
const firstPublishedAt = sys.firstPublishedAt ??
|
|
968
|
+
const firstPublishedAt = sys.firstPublishedAt ?? FieldValue2.serverTimestamp();
|
|
618
969
|
const firstPublishedBy = sys.firstPublishedBy ?? publishedBy;
|
|
619
970
|
batch.set(publishedRef, {
|
|
620
971
|
id,
|
|
@@ -625,7 +976,7 @@ var RootCMSClient = class {
|
|
|
625
976
|
...sys,
|
|
626
977
|
firstPublishedAt,
|
|
627
978
|
firstPublishedBy,
|
|
628
|
-
publishedAt:
|
|
979
|
+
publishedAt: FieldValue2.serverTimestamp(),
|
|
629
980
|
publishedBy
|
|
630
981
|
}
|
|
631
982
|
});
|
|
@@ -633,11 +984,11 @@ var RootCMSClient = class {
|
|
|
633
984
|
batch.delete(scheduledRef);
|
|
634
985
|
batchCount += 1;
|
|
635
986
|
batch.update(draftRef, {
|
|
636
|
-
"sys.scheduledAt":
|
|
637
|
-
"sys.scheduledBy":
|
|
987
|
+
"sys.scheduledAt": FieldValue2.delete(),
|
|
988
|
+
"sys.scheduledBy": FieldValue2.delete(),
|
|
638
989
|
"sys.firstPublishedAt": firstPublishedAt,
|
|
639
990
|
"sys.firstPublishedBy": firstPublishedBy,
|
|
640
|
-
"sys.publishedAt":
|
|
991
|
+
"sys.publishedAt": FieldValue2.serverTimestamp(),
|
|
641
992
|
"sys.publishedBy": publishedBy
|
|
642
993
|
});
|
|
643
994
|
batchCount += 1;
|
|
@@ -711,7 +1062,7 @@ var RootCMSClient = class {
|
|
|
711
1062
|
...sys,
|
|
712
1063
|
firstPublishedAt,
|
|
713
1064
|
firstPublishedBy,
|
|
714
|
-
publishedAt:
|
|
1065
|
+
publishedAt: FieldValue2.serverTimestamp(),
|
|
715
1066
|
publishedBy: scheduledBy || ""
|
|
716
1067
|
}
|
|
717
1068
|
});
|
|
@@ -719,11 +1070,11 @@ var RootCMSClient = class {
|
|
|
719
1070
|
batch.delete(scheduledRef);
|
|
720
1071
|
batchCount += 1;
|
|
721
1072
|
batch.update(draftRef, {
|
|
722
|
-
"sys.scheduledAt":
|
|
723
|
-
"sys.scheduledBy":
|
|
1073
|
+
"sys.scheduledAt": FieldValue2.delete(),
|
|
1074
|
+
"sys.scheduledBy": FieldValue2.delete(),
|
|
724
1075
|
"sys.firstPublishedAt": firstPublishedAt,
|
|
725
1076
|
"sys.firstPublishedBy": firstPublishedBy,
|
|
726
|
-
"sys.publishedAt":
|
|
1077
|
+
"sys.publishedAt": FieldValue2.serverTimestamp(),
|
|
727
1078
|
"sys.publishedBy": scheduledBy || "root-cms-client"
|
|
728
1079
|
});
|
|
729
1080
|
batchCount += 1;
|
|
@@ -746,17 +1097,17 @@ var RootCMSClient = class {
|
|
|
746
1097
|
async publishScheduledReleases() {
|
|
747
1098
|
const releasesPath = `Projects/${this.projectId}/Releases`;
|
|
748
1099
|
const now = Math.ceil((/* @__PURE__ */ new Date()).getTime());
|
|
749
|
-
const query = this.db.collection(releasesPath).where("scheduledAt", "<=",
|
|
1100
|
+
const query = this.db.collection(releasesPath).where("scheduledAt", "<=", Timestamp3.fromMillis(now));
|
|
750
1101
|
const querySnapshot = await query.get();
|
|
751
1102
|
for (const snapshot of querySnapshot.docs) {
|
|
752
1103
|
const release = snapshot.data();
|
|
753
1104
|
const batch = this.db.batch();
|
|
754
1105
|
const publishedBy = release.scheduledBy || "root-cms-client";
|
|
755
1106
|
batch.update(snapshot.ref, {
|
|
756
|
-
publishedAt:
|
|
1107
|
+
publishedAt: Timestamp3.now(),
|
|
757
1108
|
publishedBy,
|
|
758
|
-
scheduledAt:
|
|
759
|
-
scheduledBy:
|
|
1109
|
+
scheduledAt: FieldValue2.delete(),
|
|
1110
|
+
scheduledBy: FieldValue2.delete()
|
|
760
1111
|
});
|
|
761
1112
|
await this.publishDocs(release.docIds || [], { publishedBy, batch });
|
|
762
1113
|
}
|
|
@@ -768,7 +1119,7 @@ var RootCMSClient = class {
|
|
|
768
1119
|
var _a;
|
|
769
1120
|
if ((_a = doc.sys) == null ? void 0 : _a.publishingLocked) {
|
|
770
1121
|
if (doc.sys.publishingLocked.until) {
|
|
771
|
-
const now =
|
|
1122
|
+
const now = Timestamp3.now().toMillis();
|
|
772
1123
|
const until = doc.sys.publishingLocked.until.toMillis();
|
|
773
1124
|
return now < until;
|
|
774
1125
|
}
|
|
@@ -776,6 +1127,30 @@ var RootCMSClient = class {
|
|
|
776
1127
|
}
|
|
777
1128
|
return false;
|
|
778
1129
|
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Returns a `TranslationsManager` object for managing translations.
|
|
1132
|
+
*
|
|
1133
|
+
* To get translations:
|
|
1134
|
+
* ```
|
|
1135
|
+
* await tm.loadTranslations({
|
|
1136
|
+
* ids: ['Global/strings', 'Pages/index'],
|
|
1137
|
+
* locales: ['es'],
|
|
1138
|
+
* });
|
|
1139
|
+
* ```
|
|
1140
|
+
*
|
|
1141
|
+
* NOTE: The `TranslationsManager` is a v2 feature that will eventually
|
|
1142
|
+
* replace the v1 translations system.
|
|
1143
|
+
*/
|
|
1144
|
+
getTranslationsManager() {
|
|
1145
|
+
var _a;
|
|
1146
|
+
const cmsPluginOptions = this.cmsPlugin.getConfig();
|
|
1147
|
+
if ((_a = cmsPluginOptions.experiments) == null ? void 0 : _a.v2TranslationsManager) {
|
|
1148
|
+
throw new Error(
|
|
1149
|
+
"`v2TranslationsManager` is not enabled. update root.config.ts and add: `{experiments: {v2TranslationsManager: true}}`"
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
return new TranslationsManager(this);
|
|
1153
|
+
}
|
|
779
1154
|
/**
|
|
780
1155
|
* Loads translations saved in the translations collection, optionally
|
|
781
1156
|
* filtered by tag.
|
|
@@ -821,7 +1196,7 @@ var RootCMSClient = class {
|
|
|
821
1196
|
source: this.normalizeString(source)
|
|
822
1197
|
};
|
|
823
1198
|
if (tags) {
|
|
824
|
-
data.tags = tags;
|
|
1199
|
+
data.tags = FieldValue2.arrayUnion(...tags);
|
|
825
1200
|
}
|
|
826
1201
|
batch.set(translationRef, data, { merge: true });
|
|
827
1202
|
batchCount += 1;
|
|
@@ -892,7 +1267,7 @@ var RootCMSClient = class {
|
|
|
892
1267
|
const syncedBy = (options == null ? void 0 : options.syncedBy) || "root-cms-client";
|
|
893
1268
|
const updatedDataSource = {
|
|
894
1269
|
...dataSource,
|
|
895
|
-
syncedAt:
|
|
1270
|
+
syncedAt: Timestamp3.now(),
|
|
896
1271
|
syncedBy
|
|
897
1272
|
};
|
|
898
1273
|
const batch = this.db.batch();
|
|
@@ -901,7 +1276,7 @@ var RootCMSClient = class {
|
|
|
901
1276
|
data
|
|
902
1277
|
});
|
|
903
1278
|
batch.update(dataSourceDocRef, {
|
|
904
|
-
syncedAt:
|
|
1279
|
+
syncedAt: Timestamp3.now(),
|
|
905
1280
|
syncedBy
|
|
906
1281
|
});
|
|
907
1282
|
await batch.commit();
|
|
@@ -926,7 +1301,7 @@ var RootCMSClient = class {
|
|
|
926
1301
|
const publishedBy = (options == null ? void 0 : options.publishedBy) || "root-cms-client";
|
|
927
1302
|
const updatedDataSource = {
|
|
928
1303
|
...dataSource,
|
|
929
|
-
publishedAt:
|
|
1304
|
+
publishedAt: Timestamp3.now(),
|
|
930
1305
|
publishedBy
|
|
931
1306
|
};
|
|
932
1307
|
const batch = this.db.batch();
|
|
@@ -938,7 +1313,7 @@ var RootCMSClient = class {
|
|
|
938
1313
|
dataSource: updatedDataSource
|
|
939
1314
|
});
|
|
940
1315
|
batch.update(dataSourceDocRef, {
|
|
941
|
-
publishedAt:
|
|
1316
|
+
publishedAt: Timestamp3.now(),
|
|
942
1317
|
publishedBy
|
|
943
1318
|
});
|
|
944
1319
|
await batch.commit();
|
|
@@ -1045,7 +1420,7 @@ var RootCMSClient = class {
|
|
|
1045
1420
|
}
|
|
1046
1421
|
const data = {
|
|
1047
1422
|
action,
|
|
1048
|
-
timestamp:
|
|
1423
|
+
timestamp: Timestamp3.now(),
|
|
1049
1424
|
by: (options == null ? void 0 : options.by) || "system",
|
|
1050
1425
|
metadata: (options == null ? void 0 : options.metadata) || {}
|
|
1051
1426
|
};
|
|
@@ -1199,7 +1574,7 @@ function parseDocId(docId) {
|
|
|
1199
1574
|
|
|
1200
1575
|
// core/versions.ts
|
|
1201
1576
|
import path from "node:path";
|
|
1202
|
-
import { Timestamp as
|
|
1577
|
+
import { Timestamp as Timestamp4 } from "firebase-admin/firestore";
|
|
1203
1578
|
import glob from "tiny-glob";
|
|
1204
1579
|
var DOCUMENT_SAVE_OFFSET = 5 * 60 * 1e3;
|
|
1205
1580
|
var VersionsService = class {
|
|
@@ -1218,7 +1593,7 @@ var VersionsService = class {
|
|
|
1218
1593
|
const lastRun = await this.getLastRun();
|
|
1219
1594
|
const lastRunWithOffset = lastRun === 0 ? lastRun : lastRun - DOCUMENT_SAVE_OFFSET;
|
|
1220
1595
|
const changedDocs = await this.getDocsModifiedAfter(lastRunWithOffset);
|
|
1221
|
-
const now =
|
|
1596
|
+
const now = Timestamp4.now().toMillis();
|
|
1222
1597
|
const versions = changedDocs.filter((doc) => {
|
|
1223
1598
|
const modifiedAt = doc.sys.modifiedAt.toMillis();
|
|
1224
1599
|
return modifiedAt <= now - DOCUMENT_SAVE_OFFSET;
|
|
@@ -1264,7 +1639,7 @@ var VersionsService = class {
|
|
|
1264
1639
|
* Saves {versionLastRun: <timestamp>} to the Projects/<projectId> doc.
|
|
1265
1640
|
*/
|
|
1266
1641
|
async saveLastRun(millis) {
|
|
1267
|
-
const ts =
|
|
1642
|
+
const ts = Timestamp4.fromMillis(millis);
|
|
1268
1643
|
const projectDocRef = this.db.collection("Projects").doc(this.projectId);
|
|
1269
1644
|
await projectDocRef.set({ versionsLastRun: ts }, { merge: true });
|
|
1270
1645
|
}
|
|
@@ -1272,7 +1647,7 @@ var VersionsService = class {
|
|
|
1272
1647
|
* Returns a list of all docs that were edited after a certain time.
|
|
1273
1648
|
*/
|
|
1274
1649
|
async getDocsModifiedAfter(millis) {
|
|
1275
|
-
const ts =
|
|
1650
|
+
const ts = Timestamp4.fromMillis(millis);
|
|
1276
1651
|
const results = [];
|
|
1277
1652
|
const collectionIds = await this.listCollections();
|
|
1278
1653
|
for (const collectionId of collectionIds) {
|
package/dist/project.d.ts
CHANGED
|
@@ -197,6 +197,17 @@ type Collection = Schema & {
|
|
|
197
197
|
src: string;
|
|
198
198
|
};
|
|
199
199
|
};
|
|
200
|
+
/**
|
|
201
|
+
* Regular expression used to validate document slugs. Should be provided as a
|
|
202
|
+
* string so it can be serialized to the CMS UI.
|
|
203
|
+
*/
|
|
204
|
+
slugRegex?: string;
|
|
205
|
+
/**
|
|
206
|
+
* Automatically add a publishing lock whenever the doc is edited.
|
|
207
|
+
*/
|
|
208
|
+
autolock?: boolean;
|
|
209
|
+
/** Reason for the automatic publishing lock. */
|
|
210
|
+
autolockReason?: string;
|
|
200
211
|
};
|
|
201
212
|
declare function defineCollection(collection: Omit<Collection, 'id'>): Omit<Collection, 'id'>;
|
|
202
213
|
declare const collection: typeof defineCollection;
|