@elek-io/core 0.16.2 → 0.17.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/README.md +67 -6
- package/dist/astro/index.astro.d.mts +2 -1
- package/dist/astro/index.astro.mjs +1492 -298
- package/dist/astro/index.astro.mjs.map +1 -1
- package/dist/browser/index.browser.d.ts +1488 -1566
- package/dist/browser/index.browser.js +1 -1
- package/dist/browser/index.browser.js.map +1 -1
- package/dist/cli/index.cli.mjs +1470 -284
- package/dist/node/index.node.d.mts +1520 -1598
- package/dist/node/index.node.mjs +1470 -287
- package/dist/node/index.node.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.cli.mjs
CHANGED
|
@@ -24,6 +24,7 @@ import PQueue from "p-queue";
|
|
|
24
24
|
import { createLogger, format, transports } from "winston";
|
|
25
25
|
import DailyRotateFile from "winston-daily-rotate-file";
|
|
26
26
|
import Semver from "semver";
|
|
27
|
+
import { isDeepStrictEqual } from "node:util";
|
|
27
28
|
|
|
28
29
|
//#region \0rolldown/runtime.js
|
|
29
30
|
var __defProp = Object.defineProperty;
|
|
@@ -45,7 +46,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
45
46
|
//#region package.json
|
|
46
47
|
var package_default = {
|
|
47
48
|
name: "@elek-io/core",
|
|
48
|
-
version: "0.
|
|
49
|
+
version: "0.17.0",
|
|
49
50
|
description: "Handles core functionality of elek.io Projects like file IO and version control.",
|
|
50
51
|
homepage: "https://elek.io",
|
|
51
52
|
repository: "https://github.com/elek-io/core",
|
|
@@ -165,7 +166,7 @@ async function exportProjectNested({ projectToExport }) {
|
|
|
165
166
|
};
|
|
166
167
|
collectionContent = {
|
|
167
168
|
...collectionContent,
|
|
168
|
-
[collection.
|
|
169
|
+
[collection.slug.plural]: {
|
|
169
170
|
...collection,
|
|
170
171
|
entries: entryContent
|
|
171
172
|
}
|
|
@@ -618,11 +619,7 @@ const supportedLanguageSchema = z.enum([
|
|
|
618
619
|
"sv",
|
|
619
620
|
"zh"
|
|
620
621
|
]);
|
|
621
|
-
const supportedIconSchema = z.enum([
|
|
622
|
-
"home",
|
|
623
|
-
"plus",
|
|
624
|
-
"foobar"
|
|
625
|
-
]);
|
|
622
|
+
const supportedIconSchema = z.enum(["home", "plus"]);
|
|
626
623
|
const objectTypeSchema = z.enum([
|
|
627
624
|
"project",
|
|
628
625
|
"asset",
|
|
@@ -637,7 +634,7 @@ const logLevelSchema = z.enum([
|
|
|
637
634
|
"info",
|
|
638
635
|
"debug"
|
|
639
636
|
]);
|
|
640
|
-
const versionSchema = z.string();
|
|
637
|
+
const versionSchema = z.string().refine((version) => /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/.test(version), "String must follow the Semantic Versioning format (https://semver.org/)");
|
|
641
638
|
const uuidSchema = z.uuid();
|
|
642
639
|
/**
|
|
643
640
|
* A record that can be used to translate a string value into all supported languages
|
|
@@ -654,6 +651,41 @@ const translatableBooleanSchema = z.partialRecord(supportedLanguageSchema, z.boo
|
|
|
654
651
|
function translatableArrayOf(schema) {
|
|
655
652
|
return z.partialRecord(supportedLanguageSchema, z.array(schema));
|
|
656
653
|
}
|
|
654
|
+
const reservedSlugs = new Set([
|
|
655
|
+
"index",
|
|
656
|
+
"new",
|
|
657
|
+
"create",
|
|
658
|
+
"update",
|
|
659
|
+
"delete",
|
|
660
|
+
"edit",
|
|
661
|
+
"list",
|
|
662
|
+
"count",
|
|
663
|
+
"api",
|
|
664
|
+
"admin",
|
|
665
|
+
"collection",
|
|
666
|
+
"collections",
|
|
667
|
+
"entry",
|
|
668
|
+
"entries",
|
|
669
|
+
"asset",
|
|
670
|
+
"assets",
|
|
671
|
+
"project",
|
|
672
|
+
"projects",
|
|
673
|
+
"null",
|
|
674
|
+
"undefined",
|
|
675
|
+
"true",
|
|
676
|
+
"false",
|
|
677
|
+
"constructor",
|
|
678
|
+
"__proto__",
|
|
679
|
+
"prototype",
|
|
680
|
+
"toString",
|
|
681
|
+
"valueOf",
|
|
682
|
+
"login",
|
|
683
|
+
"logout",
|
|
684
|
+
"auth",
|
|
685
|
+
"settings",
|
|
686
|
+
"config"
|
|
687
|
+
]);
|
|
688
|
+
const slugSchema = z.string().min(1).max(128).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).refine((slug) => !reservedSlugs.has(slug), { message: "This slug is reserved and cannot be used" });
|
|
657
689
|
|
|
658
690
|
//#endregion
|
|
659
691
|
//#region src/schema/fileSchema.ts
|
|
@@ -663,91 +695,36 @@ function translatableArrayOf(schema) {
|
|
|
663
695
|
const baseFileSchema = z.object({
|
|
664
696
|
objectType: objectTypeSchema.readonly(),
|
|
665
697
|
id: uuidSchema.readonly(),
|
|
698
|
+
coreVersion: versionSchema.readonly(),
|
|
666
699
|
created: z.string().datetime().readonly(),
|
|
667
|
-
updated: z.string().datetime().nullable()
|
|
700
|
+
updated: z.string().datetime().nullable().readonly()
|
|
668
701
|
});
|
|
669
702
|
const fileReferenceSchema = z.object({
|
|
670
703
|
id: uuidSchema,
|
|
671
704
|
extension: z.string().optional()
|
|
672
705
|
});
|
|
673
|
-
|
|
674
|
-
//#endregion
|
|
675
|
-
//#region src/schema/gitSchema.ts
|
|
676
706
|
/**
|
|
677
|
-
*
|
|
707
|
+
* Schema for the collection index file (collections/index.json).
|
|
708
|
+
* Maps collection UUIDs to their slug.plural values.
|
|
709
|
+
* This is a local performance cache, not git-tracked.
|
|
678
710
|
*/
|
|
679
|
-
const
|
|
680
|
-
name: z.string(),
|
|
681
|
-
email: z.string().email()
|
|
682
|
-
});
|
|
683
|
-
const gitMessageSchema = z.object({
|
|
684
|
-
method: z.enum([
|
|
685
|
-
"create",
|
|
686
|
-
"update",
|
|
687
|
-
"delete",
|
|
688
|
-
"upgrade"
|
|
689
|
-
]),
|
|
690
|
-
reference: z.object({
|
|
691
|
-
objectType: objectTypeSchema,
|
|
692
|
-
id: uuidSchema,
|
|
693
|
-
collectionId: uuidSchema.optional()
|
|
694
|
-
})
|
|
695
|
-
});
|
|
696
|
-
const gitTagSchema = z.object({
|
|
697
|
-
id: uuidSchema,
|
|
698
|
-
message: z.string(),
|
|
699
|
-
author: gitSignatureSchema,
|
|
700
|
-
datetime: z.string().datetime()
|
|
701
|
-
});
|
|
702
|
-
const gitCommitSchema = z.object({
|
|
703
|
-
hash: z.string(),
|
|
704
|
-
message: gitMessageSchema,
|
|
705
|
-
author: gitSignatureSchema,
|
|
706
|
-
datetime: z.string().datetime(),
|
|
707
|
-
tag: gitTagSchema.nullable()
|
|
708
|
-
});
|
|
709
|
-
const gitInitOptionsSchema = z.object({ initialBranch: z.string() });
|
|
710
|
-
const gitCloneOptionsSchema = z.object({
|
|
711
|
-
depth: z.number(),
|
|
712
|
-
singleBranch: z.boolean(),
|
|
713
|
-
branch: z.string(),
|
|
714
|
-
bare: z.boolean()
|
|
715
|
-
});
|
|
716
|
-
const gitMergeOptionsSchema = z.object({ squash: z.boolean() });
|
|
717
|
-
const gitSwitchOptionsSchema = z.object({ isNew: z.boolean().optional() });
|
|
718
|
-
const gitLogOptionsSchema = z.object({
|
|
719
|
-
limit: z.number().optional(),
|
|
720
|
-
between: z.object({
|
|
721
|
-
from: z.string(),
|
|
722
|
-
to: z.string().optional()
|
|
723
|
-
}),
|
|
724
|
-
filePath: z.string().optional()
|
|
725
|
-
});
|
|
726
|
-
const createGitTagSchema = gitTagSchema.pick({ message: true }).extend({
|
|
727
|
-
path: z.string(),
|
|
728
|
-
hash: z.string().optional()
|
|
729
|
-
});
|
|
730
|
-
const readGitTagSchema = z.object({
|
|
731
|
-
path: z.string(),
|
|
732
|
-
id: uuidSchema.readonly()
|
|
733
|
-
});
|
|
734
|
-
const deleteGitTagSchema = readGitTagSchema.extend({});
|
|
735
|
-
const countGitTagsSchema = z.object({ path: z.string() });
|
|
711
|
+
const collectionIndexSchema = z.record(uuidSchema, z.string());
|
|
736
712
|
|
|
737
713
|
//#endregion
|
|
738
714
|
//#region src/schema/assetSchema.ts
|
|
739
715
|
const assetFileSchema = baseFileSchema.extend({
|
|
740
716
|
objectType: z.literal(objectTypeSchema.enum.asset).readonly(),
|
|
741
|
-
name: z.string(),
|
|
742
|
-
description: z.string(),
|
|
717
|
+
name: z.string().trim().min(1),
|
|
718
|
+
description: z.string().trim().min(1),
|
|
743
719
|
extension: z.string().readonly(),
|
|
744
720
|
mimeType: z.string().readonly(),
|
|
745
721
|
size: z.number().readonly()
|
|
746
722
|
});
|
|
747
|
-
const assetSchema = assetFileSchema.extend({
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
723
|
+
const assetSchema = assetFileSchema.extend({ absolutePath: z.string().readonly() }).openapi("Asset");
|
|
724
|
+
const assetHistorySchema = z.object({
|
|
725
|
+
id: uuidSchema.readonly(),
|
|
726
|
+
projectId: uuidSchema.readonly()
|
|
727
|
+
});
|
|
751
728
|
const assetExportSchema = assetSchema.extend({});
|
|
752
729
|
const createAssetSchema = assetFileSchema.pick({
|
|
753
730
|
name: true,
|
|
@@ -777,11 +754,15 @@ const deleteAssetSchema = assetFileSchema.pick({
|
|
|
777
754
|
id: true,
|
|
778
755
|
extension: true
|
|
779
756
|
}).extend({ projectId: uuidSchema.readonly() });
|
|
757
|
+
const migrateAssetSchema = z.looseObject(assetFileSchema.pick({
|
|
758
|
+
id: true,
|
|
759
|
+
coreVersion: true
|
|
760
|
+
}).shape);
|
|
780
761
|
const countAssetsSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
781
762
|
|
|
782
763
|
//#endregion
|
|
783
764
|
//#region src/schema/valueSchema.ts
|
|
784
|
-
const
|
|
765
|
+
const valueTypeSchema = z.enum([
|
|
785
766
|
"string",
|
|
786
767
|
"number",
|
|
787
768
|
"boolean",
|
|
@@ -796,20 +777,17 @@ const valueContentReferenceSchema = z.union([
|
|
|
796
777
|
valueContentReferenceToCollectionSchema,
|
|
797
778
|
valueContentReferenceToEntrySchema
|
|
798
779
|
]);
|
|
799
|
-
const directValueBaseSchema = z.object({
|
|
800
|
-
objectType: z.literal(objectTypeSchema.enum.value).readonly(),
|
|
801
|
-
fieldDefinitionId: uuidSchema.readonly()
|
|
802
|
-
});
|
|
780
|
+
const directValueBaseSchema = z.object({ objectType: z.literal(objectTypeSchema.enum.value).readonly() });
|
|
803
781
|
const directStringValueSchema = directValueBaseSchema.extend({
|
|
804
|
-
valueType: z.literal(
|
|
782
|
+
valueType: z.literal(valueTypeSchema.enum.string).readonly(),
|
|
805
783
|
content: translatableStringSchema
|
|
806
784
|
});
|
|
807
785
|
const directNumberValueSchema = directValueBaseSchema.extend({
|
|
808
|
-
valueType: z.literal(
|
|
786
|
+
valueType: z.literal(valueTypeSchema.enum.number).readonly(),
|
|
809
787
|
content: translatableNumberSchema
|
|
810
788
|
});
|
|
811
789
|
const directBooleanValueSchema = directValueBaseSchema.extend({
|
|
812
|
-
valueType: z.literal(
|
|
790
|
+
valueType: z.literal(valueTypeSchema.enum.boolean).readonly(),
|
|
813
791
|
content: translatableBooleanSchema
|
|
814
792
|
});
|
|
815
793
|
const directValueSchema = z.union([
|
|
@@ -819,8 +797,7 @@ const directValueSchema = z.union([
|
|
|
819
797
|
]);
|
|
820
798
|
const referencedValueSchema = z.object({
|
|
821
799
|
objectType: z.literal(objectTypeSchema.enum.value).readonly(),
|
|
822
|
-
|
|
823
|
-
valueType: z.literal(ValueTypeSchema.enum.reference).readonly(),
|
|
800
|
+
valueType: z.literal(valueTypeSchema.enum.reference).readonly(),
|
|
824
801
|
content: translatableArrayOf(valueContentReferenceSchema)
|
|
825
802
|
});
|
|
826
803
|
const valueSchema = z.union([directValueSchema, referencedValueSchema]);
|
|
@@ -835,19 +812,25 @@ const valueSchema = z.union([directValueSchema, referencedValueSchema]);
|
|
|
835
812
|
//#region src/schema/entrySchema.ts
|
|
836
813
|
const entryFileSchema = baseFileSchema.extend({
|
|
837
814
|
objectType: z.literal(objectTypeSchema.enum.entry).readonly(),
|
|
838
|
-
values: z.
|
|
815
|
+
values: z.record(slugSchema, valueSchema)
|
|
816
|
+
});
|
|
817
|
+
const entrySchema = entryFileSchema.openapi("Entry");
|
|
818
|
+
const entryHistorySchema = z.object({
|
|
819
|
+
id: uuidSchema.readonly(),
|
|
820
|
+
projectId: uuidSchema.readonly(),
|
|
821
|
+
collectionId: uuidSchema.readonly()
|
|
839
822
|
});
|
|
840
|
-
const entrySchema = entryFileSchema.extend({ history: z.array(gitCommitSchema) }).openapi("Entry");
|
|
841
823
|
const entryExportSchema = entrySchema.extend({});
|
|
842
824
|
const createEntrySchema = entryFileSchema.omit({
|
|
843
825
|
id: true,
|
|
844
826
|
objectType: true,
|
|
827
|
+
coreVersion: true,
|
|
845
828
|
created: true,
|
|
846
829
|
updated: true
|
|
847
830
|
}).extend({
|
|
848
831
|
projectId: uuidSchema.readonly(),
|
|
849
832
|
collectionId: uuidSchema.readonly(),
|
|
850
|
-
values: z.
|
|
833
|
+
values: z.record(slugSchema, valueSchema)
|
|
851
834
|
});
|
|
852
835
|
const readEntrySchema = z.object({
|
|
853
836
|
id: uuidSchema.readonly(),
|
|
@@ -857,6 +840,7 @@ const readEntrySchema = z.object({
|
|
|
857
840
|
});
|
|
858
841
|
const updateEntrySchema = entryFileSchema.omit({
|
|
859
842
|
objectType: true,
|
|
843
|
+
coreVersion: true,
|
|
860
844
|
created: true,
|
|
861
845
|
updated: true
|
|
862
846
|
}).extend({
|
|
@@ -864,6 +848,10 @@ const updateEntrySchema = entryFileSchema.omit({
|
|
|
864
848
|
collectionId: uuidSchema.readonly()
|
|
865
849
|
});
|
|
866
850
|
const deleteEntrySchema = readEntrySchema.extend({});
|
|
851
|
+
const migrateEntrySchema = z.looseObject(entryFileSchema.pick({
|
|
852
|
+
id: true,
|
|
853
|
+
coreVersion: true
|
|
854
|
+
}).shape);
|
|
867
855
|
const countEntriesSchema = z.object({
|
|
868
856
|
projectId: uuidSchema.readonly(),
|
|
869
857
|
collectionId: uuidSchema.readonly()
|
|
@@ -871,7 +859,7 @@ const countEntriesSchema = z.object({
|
|
|
871
859
|
|
|
872
860
|
//#endregion
|
|
873
861
|
//#region src/schema/fieldSchema.ts
|
|
874
|
-
const
|
|
862
|
+
const fieldTypeSchema = z.enum([
|
|
875
863
|
"text",
|
|
876
864
|
"textarea",
|
|
877
865
|
"email",
|
|
@@ -887,64 +875,65 @@ const FieldTypeSchema = z.enum([
|
|
|
887
875
|
"asset",
|
|
888
876
|
"entry"
|
|
889
877
|
]);
|
|
890
|
-
const
|
|
878
|
+
const fieldWidthSchema = z.enum([
|
|
891
879
|
"12",
|
|
892
880
|
"6",
|
|
893
881
|
"4",
|
|
894
882
|
"3"
|
|
895
883
|
]);
|
|
896
|
-
const
|
|
884
|
+
const fieldDefinitionBaseSchema = z.object({
|
|
897
885
|
id: uuidSchema.readonly(),
|
|
886
|
+
slug: slugSchema,
|
|
898
887
|
label: translatableStringSchema,
|
|
899
888
|
description: translatableStringSchema.nullable(),
|
|
900
889
|
isRequired: z.boolean(),
|
|
901
890
|
isDisabled: z.boolean(),
|
|
902
891
|
isUnique: z.boolean(),
|
|
903
|
-
inputWidth:
|
|
892
|
+
inputWidth: fieldWidthSchema
|
|
904
893
|
});
|
|
905
894
|
/**
|
|
906
895
|
* String based Field definitions
|
|
907
896
|
*/
|
|
908
|
-
const
|
|
909
|
-
valueType: z.literal(
|
|
897
|
+
const stringFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
|
|
898
|
+
valueType: z.literal(valueTypeSchema.enum.string),
|
|
910
899
|
defaultValue: z.string().nullable()
|
|
911
900
|
});
|
|
912
|
-
const textFieldDefinitionSchema =
|
|
913
|
-
fieldType: z.literal(
|
|
901
|
+
const textFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
902
|
+
fieldType: z.literal(fieldTypeSchema.enum.text),
|
|
914
903
|
min: z.number().nullable(),
|
|
915
904
|
max: z.number().nullable()
|
|
916
905
|
});
|
|
917
|
-
const textareaFieldDefinitionSchema =
|
|
918
|
-
fieldType: z.literal(
|
|
906
|
+
const textareaFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
907
|
+
fieldType: z.literal(fieldTypeSchema.enum.textarea),
|
|
919
908
|
min: z.number().nullable(),
|
|
920
909
|
max: z.number().nullable()
|
|
921
910
|
});
|
|
922
|
-
const emailFieldDefinitionSchema =
|
|
923
|
-
fieldType: z.literal(
|
|
911
|
+
const emailFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
912
|
+
fieldType: z.literal(fieldTypeSchema.enum.email),
|
|
924
913
|
defaultValue: z.email().nullable()
|
|
925
914
|
});
|
|
926
|
-
const urlFieldDefinitionSchema =
|
|
927
|
-
fieldType: z.literal(
|
|
915
|
+
const urlFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
916
|
+
fieldType: z.literal(fieldTypeSchema.enum.url),
|
|
928
917
|
defaultValue: z.url().nullable()
|
|
929
918
|
});
|
|
930
|
-
const ipv4FieldDefinitionSchema =
|
|
931
|
-
fieldType: z.literal(
|
|
919
|
+
const ipv4FieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
920
|
+
fieldType: z.literal(fieldTypeSchema.enum.ipv4),
|
|
932
921
|
defaultValue: z.ipv4().nullable()
|
|
933
922
|
});
|
|
934
|
-
const dateFieldDefinitionSchema =
|
|
935
|
-
fieldType: z.literal(
|
|
923
|
+
const dateFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
924
|
+
fieldType: z.literal(fieldTypeSchema.enum.date),
|
|
936
925
|
defaultValue: z.iso.date().nullable()
|
|
937
926
|
});
|
|
938
|
-
const timeFieldDefinitionSchema =
|
|
939
|
-
fieldType: z.literal(
|
|
927
|
+
const timeFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
928
|
+
fieldType: z.literal(fieldTypeSchema.enum.time),
|
|
940
929
|
defaultValue: z.iso.time().nullable()
|
|
941
930
|
});
|
|
942
|
-
const datetimeFieldDefinitionSchema =
|
|
943
|
-
fieldType: z.literal(
|
|
931
|
+
const datetimeFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
932
|
+
fieldType: z.literal(fieldTypeSchema.enum.datetime),
|
|
944
933
|
defaultValue: z.iso.datetime().nullable()
|
|
945
934
|
});
|
|
946
|
-
const telephoneFieldDefinitionSchema =
|
|
947
|
-
fieldType: z.literal(
|
|
935
|
+
const telephoneFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
936
|
+
fieldType: z.literal(fieldTypeSchema.enum.telephone),
|
|
948
937
|
defaultValue: z.e164().nullable()
|
|
949
938
|
});
|
|
950
939
|
const stringFieldDefinitionSchema = z.union([
|
|
@@ -961,16 +950,16 @@ const stringFieldDefinitionSchema = z.union([
|
|
|
961
950
|
/**
|
|
962
951
|
* Number based Field definitions
|
|
963
952
|
*/
|
|
964
|
-
const
|
|
965
|
-
valueType: z.literal(
|
|
953
|
+
const numberFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
|
|
954
|
+
valueType: z.literal(valueTypeSchema.enum.number),
|
|
966
955
|
min: z.number().nullable(),
|
|
967
956
|
max: z.number().nullable(),
|
|
968
957
|
isUnique: z.literal(false),
|
|
969
958
|
defaultValue: z.number().nullable()
|
|
970
959
|
});
|
|
971
|
-
const numberFieldDefinitionSchema =
|
|
972
|
-
const rangeFieldDefinitionSchema =
|
|
973
|
-
fieldType: z.literal(
|
|
960
|
+
const numberFieldDefinitionSchema = numberFieldDefinitionBaseSchema.extend({ fieldType: z.literal(fieldTypeSchema.enum.number) });
|
|
961
|
+
const rangeFieldDefinitionSchema = numberFieldDefinitionBaseSchema.extend({
|
|
962
|
+
fieldType: z.literal(fieldTypeSchema.enum.range),
|
|
974
963
|
isRequired: z.literal(true),
|
|
975
964
|
min: z.number(),
|
|
976
965
|
max: z.number(),
|
|
@@ -979,24 +968,27 @@ const rangeFieldDefinitionSchema = NumberFieldDefinitionBaseSchema.extend({
|
|
|
979
968
|
/**
|
|
980
969
|
* Boolean based Field definitions
|
|
981
970
|
*/
|
|
982
|
-
const
|
|
983
|
-
valueType: z.literal(
|
|
971
|
+
const booleanFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
|
|
972
|
+
valueType: z.literal(valueTypeSchema.enum.boolean),
|
|
984
973
|
isRequired: z.literal(true),
|
|
985
974
|
defaultValue: z.boolean(),
|
|
986
975
|
isUnique: z.literal(false)
|
|
987
976
|
});
|
|
988
|
-
const toggleFieldDefinitionSchema =
|
|
977
|
+
const toggleFieldDefinitionSchema = booleanFieldDefinitionBaseSchema.extend({ fieldType: z.literal(fieldTypeSchema.enum.toggle) });
|
|
989
978
|
/**
|
|
990
979
|
* Reference based Field definitions
|
|
991
980
|
*/
|
|
992
|
-
const
|
|
993
|
-
|
|
994
|
-
|
|
981
|
+
const referenceFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
|
|
982
|
+
valueType: z.literal(valueTypeSchema.enum.reference),
|
|
983
|
+
isUnique: z.literal(false)
|
|
984
|
+
});
|
|
985
|
+
const assetFieldDefinitionSchema = referenceFieldDefinitionBaseSchema.extend({
|
|
986
|
+
fieldType: z.literal(fieldTypeSchema.enum.asset),
|
|
995
987
|
min: z.number().nullable(),
|
|
996
988
|
max: z.number().nullable()
|
|
997
989
|
});
|
|
998
|
-
const entryFieldDefinitionSchema =
|
|
999
|
-
fieldType: z.literal(
|
|
990
|
+
const entryFieldDefinitionSchema = referenceFieldDefinitionBaseSchema.extend({
|
|
991
|
+
fieldType: z.literal(fieldTypeSchema.enum.entry),
|
|
1000
992
|
ofCollections: z.array(uuidSchema),
|
|
1001
993
|
min: z.number().nullable(),
|
|
1002
994
|
max: z.number().nullable()
|
|
@@ -1019,18 +1011,23 @@ const collectionFileSchema = baseFileSchema.extend({
|
|
|
1019
1011
|
plural: translatableStringSchema
|
|
1020
1012
|
}),
|
|
1021
1013
|
slug: z.object({
|
|
1022
|
-
singular:
|
|
1023
|
-
plural:
|
|
1014
|
+
singular: slugSchema,
|
|
1015
|
+
plural: slugSchema
|
|
1024
1016
|
}),
|
|
1025
1017
|
description: translatableStringSchema,
|
|
1026
1018
|
icon: supportedIconSchema,
|
|
1027
1019
|
fieldDefinitions: z.array(fieldDefinitionSchema)
|
|
1028
1020
|
});
|
|
1029
|
-
const collectionSchema = collectionFileSchema.
|
|
1021
|
+
const collectionSchema = collectionFileSchema.openapi("Collection");
|
|
1022
|
+
const collectionHistorySchema = z.object({
|
|
1023
|
+
id: uuidSchema.readonly(),
|
|
1024
|
+
projectId: uuidSchema.readonly()
|
|
1025
|
+
});
|
|
1030
1026
|
const collectionExportSchema = collectionSchema.extend({ entries: z.array(entryExportSchema) });
|
|
1031
1027
|
const createCollectionSchema = collectionFileSchema.omit({
|
|
1032
1028
|
id: true,
|
|
1033
1029
|
objectType: true,
|
|
1030
|
+
coreVersion: true,
|
|
1034
1031
|
created: true,
|
|
1035
1032
|
updated: true
|
|
1036
1033
|
}).extend({ projectId: uuidSchema.readonly() });
|
|
@@ -1039,6 +1036,11 @@ const readCollectionSchema = z.object({
|
|
|
1039
1036
|
projectId: uuidSchema.readonly(),
|
|
1040
1037
|
commitHash: z.string().optional().readonly()
|
|
1041
1038
|
});
|
|
1039
|
+
const readBySlugCollectionSchema = z.object({
|
|
1040
|
+
slug: slugSchema,
|
|
1041
|
+
projectId: uuidSchema.readonly(),
|
|
1042
|
+
commitHash: z.string().optional().readonly()
|
|
1043
|
+
});
|
|
1042
1044
|
const updateCollectionSchema = collectionFileSchema.pick({
|
|
1043
1045
|
id: true,
|
|
1044
1046
|
name: true,
|
|
@@ -1049,6 +1051,14 @@ const updateCollectionSchema = collectionFileSchema.pick({
|
|
|
1049
1051
|
}).extend({ projectId: uuidSchema.readonly() });
|
|
1050
1052
|
const deleteCollectionSchema = readCollectionSchema.extend({});
|
|
1051
1053
|
const countCollectionsSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
1054
|
+
const migrateCollectionSchema = z.looseObject(collectionFileSchema.pick({
|
|
1055
|
+
id: true,
|
|
1056
|
+
coreVersion: true
|
|
1057
|
+
}).shape);
|
|
1058
|
+
const resolveCollectionIdSchema = z.object({
|
|
1059
|
+
projectId: uuidSchema.readonly(),
|
|
1060
|
+
idOrSlug: z.string()
|
|
1061
|
+
});
|
|
1052
1062
|
|
|
1053
1063
|
//#endregion
|
|
1054
1064
|
//#region src/schema/coreSchema.ts
|
|
@@ -1065,15 +1075,88 @@ const constructorElekIoCoreSchema = elekIoCoreOptionsSchema.partial({
|
|
|
1065
1075
|
}).optional();
|
|
1066
1076
|
|
|
1067
1077
|
//#endregion
|
|
1068
|
-
//#region src/schema/
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1078
|
+
//#region src/schema/gitSchema.ts
|
|
1079
|
+
/**
|
|
1080
|
+
* Signature git uses to identify users
|
|
1081
|
+
*/
|
|
1082
|
+
const gitSignatureSchema = z.object({
|
|
1083
|
+
name: z.string().regex(/^[^|]+$/, "Name must not contain pipe characters"),
|
|
1084
|
+
email: z.string().email()
|
|
1085
|
+
});
|
|
1086
|
+
const gitMessageSchema = z.object({
|
|
1087
|
+
method: z.enum([
|
|
1088
|
+
"create",
|
|
1089
|
+
"update",
|
|
1090
|
+
"delete",
|
|
1091
|
+
"upgrade",
|
|
1092
|
+
"release"
|
|
1093
|
+
]),
|
|
1094
|
+
reference: z.object({
|
|
1095
|
+
objectType: objectTypeSchema,
|
|
1096
|
+
id: uuidSchema,
|
|
1097
|
+
collectionId: uuidSchema.optional()
|
|
1098
|
+
})
|
|
1099
|
+
});
|
|
1100
|
+
const gitTagMessageSchema = z.discriminatedUnion("type", [
|
|
1101
|
+
z.object({
|
|
1102
|
+
type: z.literal("release"),
|
|
1103
|
+
version: versionSchema
|
|
1104
|
+
}),
|
|
1105
|
+
z.object({
|
|
1106
|
+
type: z.literal("preview"),
|
|
1107
|
+
version: versionSchema
|
|
1108
|
+
}),
|
|
1109
|
+
z.object({
|
|
1110
|
+
type: z.literal("upgrade"),
|
|
1111
|
+
coreVersion: versionSchema
|
|
1112
|
+
})
|
|
1073
1113
|
]);
|
|
1114
|
+
const gitTagSchema = z.object({
|
|
1115
|
+
id: uuidSchema,
|
|
1116
|
+
message: gitTagMessageSchema,
|
|
1117
|
+
author: gitSignatureSchema,
|
|
1118
|
+
datetime: z.iso.datetime()
|
|
1119
|
+
});
|
|
1120
|
+
const gitCommitSchema = z.object({
|
|
1121
|
+
hash: z.hash("sha1"),
|
|
1122
|
+
message: gitMessageSchema,
|
|
1123
|
+
author: gitSignatureSchema,
|
|
1124
|
+
datetime: z.iso.datetime(),
|
|
1125
|
+
tag: gitTagSchema.nullable()
|
|
1126
|
+
});
|
|
1127
|
+
const gitInitOptionsSchema = z.object({ initialBranch: z.string() });
|
|
1128
|
+
const gitCloneOptionsSchema = z.object({
|
|
1129
|
+
depth: z.number(),
|
|
1130
|
+
singleBranch: z.boolean(),
|
|
1131
|
+
branch: z.string(),
|
|
1132
|
+
bare: z.boolean()
|
|
1133
|
+
});
|
|
1134
|
+
const gitMergeOptionsSchema = z.object({ squash: z.boolean() });
|
|
1135
|
+
const gitSwitchOptionsSchema = z.object({ isNew: z.boolean().optional() });
|
|
1136
|
+
const gitLogOptionsSchema = z.object({
|
|
1137
|
+
limit: z.number().optional(),
|
|
1138
|
+
between: z.object({
|
|
1139
|
+
from: z.string(),
|
|
1140
|
+
to: z.string().optional()
|
|
1141
|
+
}),
|
|
1142
|
+
filePath: z.string().optional()
|
|
1143
|
+
});
|
|
1144
|
+
const createGitTagSchema = gitTagSchema.pick({ message: true }).extend({
|
|
1145
|
+
path: z.string(),
|
|
1146
|
+
hash: z.string().optional()
|
|
1147
|
+
});
|
|
1148
|
+
const readGitTagSchema = z.object({
|
|
1149
|
+
path: z.string(),
|
|
1150
|
+
id: uuidSchema.readonly()
|
|
1151
|
+
});
|
|
1152
|
+
const deleteGitTagSchema = readGitTagSchema.extend({});
|
|
1153
|
+
const countGitTagsSchema = z.object({ path: z.string() });
|
|
1154
|
+
|
|
1155
|
+
//#endregion
|
|
1156
|
+
//#region src/schema/projectSchema.ts
|
|
1074
1157
|
const projectSettingsSchema = z.object({ language: z.object({
|
|
1075
1158
|
default: supportedLanguageSchema,
|
|
1076
|
-
supported: z.array(supportedLanguageSchema)
|
|
1159
|
+
supported: z.array(supportedLanguageSchema).refine((langs) => new Set(langs).size === langs.length, { message: "Supported languages must not contain duplicates" })
|
|
1077
1160
|
}) });
|
|
1078
1161
|
const projectFolderSchema = z.enum([
|
|
1079
1162
|
"assets",
|
|
@@ -1084,22 +1167,21 @@ const projectFolderSchema = z.enum([
|
|
|
1084
1167
|
const projectBranchSchema = z.enum(["production", "work"]);
|
|
1085
1168
|
const projectFileSchema = baseFileSchema.extend({
|
|
1086
1169
|
objectType: z.literal(objectTypeSchema.enum.project).readonly(),
|
|
1087
|
-
coreVersion: versionSchema,
|
|
1088
1170
|
name: z.string().trim().min(1),
|
|
1089
1171
|
description: z.string().trim().min(1),
|
|
1090
1172
|
version: versionSchema,
|
|
1091
|
-
status: projectStatusSchema,
|
|
1092
1173
|
settings: projectSettingsSchema
|
|
1093
1174
|
});
|
|
1094
|
-
const projectSchema = projectFileSchema.extend({
|
|
1095
|
-
|
|
1175
|
+
const projectSchema = projectFileSchema.extend({ remoteOriginUrl: z.string().nullable().openapi({ description: "URL of the remote Git repository" }) }).openapi("Project");
|
|
1176
|
+
const projectHistorySchema = z.object({ id: uuidSchema.readonly() });
|
|
1177
|
+
const projectHistoryResultSchema = z.object({
|
|
1096
1178
|
history: z.array(gitCommitSchema).openapi({ description: "Commit history of this Project" }),
|
|
1097
1179
|
fullHistory: z.array(gitCommitSchema).openapi({ description: "Full commit history of this Project including all Assets, Collections, Entries and other files" })
|
|
1098
|
-
})
|
|
1099
|
-
const migrateProjectSchema = projectFileSchema.pick({
|
|
1180
|
+
});
|
|
1181
|
+
const migrateProjectSchema = z.looseObject(projectFileSchema.pick({
|
|
1100
1182
|
id: true,
|
|
1101
1183
|
coreVersion: true
|
|
1102
|
-
}).
|
|
1184
|
+
}).shape);
|
|
1103
1185
|
const projectExportSchema = projectSchema.extend({
|
|
1104
1186
|
assets: z.array(assetExportSchema),
|
|
1105
1187
|
collections: z.array(collectionExportSchema)
|
|
@@ -1124,13 +1206,6 @@ const upgradeProjectSchema = z.object({
|
|
|
1124
1206
|
force: z.boolean().optional()
|
|
1125
1207
|
});
|
|
1126
1208
|
const deleteProjectSchema = readProjectSchema.extend({ force: z.boolean().optional() });
|
|
1127
|
-
const projectUpgradeSchema = z.object({
|
|
1128
|
-
to: versionSchema.readonly(),
|
|
1129
|
-
run: z.function({
|
|
1130
|
-
input: [projectFileSchema],
|
|
1131
|
-
output: z.promise(z.void())
|
|
1132
|
-
})
|
|
1133
|
-
});
|
|
1134
1209
|
const cloneProjectSchema = z.object({ url: z.string() });
|
|
1135
1210
|
const listBranchesProjectSchema = z.object({ id: uuidSchema.readonly() });
|
|
1136
1211
|
const currentBranchProjectSchema = z.object({ id: uuidSchema.readonly() });
|
|
@@ -1184,29 +1259,29 @@ function getNumberValueContentSchemaFromFieldDefinition(fieldDefinition) {
|
|
|
1184
1259
|
function getStringValueContentSchemaFromFieldDefinition(fieldDefinition) {
|
|
1185
1260
|
let schema = null;
|
|
1186
1261
|
switch (fieldDefinition.fieldType) {
|
|
1187
|
-
case
|
|
1262
|
+
case fieldTypeSchema.enum.email:
|
|
1188
1263
|
schema = z.email();
|
|
1189
1264
|
break;
|
|
1190
|
-
case
|
|
1265
|
+
case fieldTypeSchema.enum.url:
|
|
1191
1266
|
schema = z.url();
|
|
1192
1267
|
break;
|
|
1193
|
-
case
|
|
1268
|
+
case fieldTypeSchema.enum.ipv4:
|
|
1194
1269
|
schema = z.ipv4();
|
|
1195
1270
|
break;
|
|
1196
|
-
case
|
|
1271
|
+
case fieldTypeSchema.enum.date:
|
|
1197
1272
|
schema = z.iso.date();
|
|
1198
1273
|
break;
|
|
1199
|
-
case
|
|
1274
|
+
case fieldTypeSchema.enum.time:
|
|
1200
1275
|
schema = z.iso.time();
|
|
1201
1276
|
break;
|
|
1202
|
-
case
|
|
1277
|
+
case fieldTypeSchema.enum.datetime:
|
|
1203
1278
|
schema = z.iso.datetime();
|
|
1204
1279
|
break;
|
|
1205
|
-
case
|
|
1280
|
+
case fieldTypeSchema.enum.telephone:
|
|
1206
1281
|
schema = z.e164();
|
|
1207
1282
|
break;
|
|
1208
|
-
case
|
|
1209
|
-
case
|
|
1283
|
+
case fieldTypeSchema.enum.text:
|
|
1284
|
+
case fieldTypeSchema.enum.textarea:
|
|
1210
1285
|
schema = z.string().trim();
|
|
1211
1286
|
break;
|
|
1212
1287
|
}
|
|
@@ -1222,10 +1297,10 @@ function getStringValueContentSchemaFromFieldDefinition(fieldDefinition) {
|
|
|
1222
1297
|
function getReferenceValueContentSchemaFromFieldDefinition(fieldDefinition) {
|
|
1223
1298
|
let schema;
|
|
1224
1299
|
switch (fieldDefinition.fieldType) {
|
|
1225
|
-
case
|
|
1300
|
+
case fieldTypeSchema.enum.asset:
|
|
1226
1301
|
schema = z.array(valueContentReferenceToAssetSchema);
|
|
1227
1302
|
break;
|
|
1228
|
-
case
|
|
1303
|
+
case fieldTypeSchema.enum.entry:
|
|
1229
1304
|
schema = z.array(valueContentReferenceToEntrySchema);
|
|
1230
1305
|
break;
|
|
1231
1306
|
}
|
|
@@ -1251,35 +1326,37 @@ function getTranslatableReferenceValueContentSchemaFromFieldDefinition(fieldDefi
|
|
|
1251
1326
|
*/
|
|
1252
1327
|
function getValueSchemaFromFieldDefinition(fieldDefinition) {
|
|
1253
1328
|
switch (fieldDefinition.valueType) {
|
|
1254
|
-
case
|
|
1255
|
-
case
|
|
1256
|
-
case
|
|
1257
|
-
case
|
|
1329
|
+
case valueTypeSchema.enum.boolean: return directBooleanValueSchema.extend({ content: getTranslatableBooleanValueContentSchemaFromFieldDefinition() });
|
|
1330
|
+
case valueTypeSchema.enum.number: return directNumberValueSchema.extend({ content: getTranslatableNumberValueContentSchemaFromFieldDefinition(fieldDefinition) });
|
|
1331
|
+
case valueTypeSchema.enum.string: return directStringValueSchema.extend({ content: getTranslatableStringValueContentSchemaFromFieldDefinition(fieldDefinition) });
|
|
1332
|
+
case valueTypeSchema.enum.reference: return referencedValueSchema.extend({ content: getTranslatableReferenceValueContentSchemaFromFieldDefinition(fieldDefinition) });
|
|
1258
1333
|
default: throw new Error(`Error generating schema for unsupported ValueType "${fieldDefinition.valueType}"`);
|
|
1259
1334
|
}
|
|
1260
1335
|
}
|
|
1261
1336
|
/**
|
|
1337
|
+
* Builds a z.object shape from field definitions, keyed by slug
|
|
1338
|
+
*/
|
|
1339
|
+
function getValuesShapeFromFieldDefinitions(fieldDefinitions) {
|
|
1340
|
+
const shape = {};
|
|
1341
|
+
for (const fieldDef of fieldDefinitions) shape[fieldDef.slug] = getValueSchemaFromFieldDefinition(fieldDef);
|
|
1342
|
+
return shape;
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1262
1345
|
* Generates a schema for creating a new Entry based on the given Field definitions and Values
|
|
1263
1346
|
*/
|
|
1264
1347
|
function getCreateEntrySchemaFromFieldDefinitions(fieldDefinitions) {
|
|
1265
|
-
const valueSchemas = fieldDefinitions.map((fieldDefinition) => {
|
|
1266
|
-
return getValueSchemaFromFieldDefinition(fieldDefinition);
|
|
1267
|
-
});
|
|
1268
1348
|
return z.object({
|
|
1269
1349
|
...createEntrySchema.shape,
|
|
1270
|
-
values: z.
|
|
1350
|
+
values: z.object(getValuesShapeFromFieldDefinitions(fieldDefinitions))
|
|
1271
1351
|
});
|
|
1272
1352
|
}
|
|
1273
1353
|
/**
|
|
1274
1354
|
* Generates a schema for updating an existing Entry based on the given Field definitions and Values
|
|
1275
1355
|
*/
|
|
1276
1356
|
function getUpdateEntrySchemaFromFieldDefinitions(fieldDefinitions) {
|
|
1277
|
-
const valueSchemas = fieldDefinitions.map((fieldDefinition) => {
|
|
1278
|
-
return getValueSchemaFromFieldDefinition(fieldDefinition);
|
|
1279
|
-
});
|
|
1280
1357
|
return z.object({
|
|
1281
1358
|
...updateEntrySchema.shape,
|
|
1282
|
-
values: z.
|
|
1359
|
+
values: z.object(getValuesShapeFromFieldDefinitions(fieldDefinitions))
|
|
1283
1360
|
});
|
|
1284
1361
|
}
|
|
1285
1362
|
|
|
@@ -1295,7 +1372,8 @@ const serviceTypeSchema = z.enum([
|
|
|
1295
1372
|
"Search",
|
|
1296
1373
|
"Collection",
|
|
1297
1374
|
"Entry",
|
|
1298
|
-
"Value"
|
|
1375
|
+
"Value",
|
|
1376
|
+
"Release"
|
|
1299
1377
|
]);
|
|
1300
1378
|
function paginatedListOf(schema) {
|
|
1301
1379
|
return z.object({
|
|
@@ -1318,18 +1396,18 @@ const listGitTagsSchema = z.object({ path: z.string() });
|
|
|
1318
1396
|
|
|
1319
1397
|
//#endregion
|
|
1320
1398
|
//#region src/schema/userSchema.ts
|
|
1321
|
-
const
|
|
1399
|
+
const userTypeSchema = z.enum(["local", "cloud"]);
|
|
1322
1400
|
const baseUserSchema = gitSignatureSchema.extend({
|
|
1323
|
-
userType:
|
|
1401
|
+
userType: userTypeSchema,
|
|
1324
1402
|
language: supportedLanguageSchema,
|
|
1325
1403
|
localApi: z.object({
|
|
1326
1404
|
isEnabled: z.boolean(),
|
|
1327
1405
|
port: z.number()
|
|
1328
1406
|
})
|
|
1329
1407
|
});
|
|
1330
|
-
const localUserSchema = baseUserSchema.extend({ userType: z.literal(
|
|
1408
|
+
const localUserSchema = baseUserSchema.extend({ userType: z.literal(userTypeSchema.enum.local) });
|
|
1331
1409
|
const cloudUserSchema = baseUserSchema.extend({
|
|
1332
|
-
userType: z.literal(
|
|
1410
|
+
userType: z.literal(userTypeSchema.enum.cloud),
|
|
1333
1411
|
id: uuidSchema
|
|
1334
1412
|
});
|
|
1335
1413
|
const userFileSchema = z.union([localUserSchema, cloudUserSchema]);
|
|
@@ -1403,6 +1481,97 @@ const logConsoleTransportSchema = logSchema.extend({
|
|
|
1403
1481
|
level: z.string()
|
|
1404
1482
|
});
|
|
1405
1483
|
|
|
1484
|
+
//#endregion
|
|
1485
|
+
//#region src/schema/releaseSchema.ts
|
|
1486
|
+
const semverBumpSchema = z.enum([
|
|
1487
|
+
"major",
|
|
1488
|
+
"minor",
|
|
1489
|
+
"patch"
|
|
1490
|
+
]);
|
|
1491
|
+
const fieldChangeTypeSchema = z.enum([
|
|
1492
|
+
"added",
|
|
1493
|
+
"deleted",
|
|
1494
|
+
"valueTypeChanged",
|
|
1495
|
+
"fieldTypeChanged",
|
|
1496
|
+
"slugChanged",
|
|
1497
|
+
"minMaxTightened",
|
|
1498
|
+
"isRequiredToNotRequired",
|
|
1499
|
+
"isUniqueToNotUnique",
|
|
1500
|
+
"ofCollectionsChanged",
|
|
1501
|
+
"isNotRequiredToRequired",
|
|
1502
|
+
"isNotUniqueToUnique",
|
|
1503
|
+
"labelChanged",
|
|
1504
|
+
"descriptionChanged",
|
|
1505
|
+
"defaultValueChanged",
|
|
1506
|
+
"inputWidthChanged",
|
|
1507
|
+
"isDisabledChanged",
|
|
1508
|
+
"minMaxLoosened"
|
|
1509
|
+
]);
|
|
1510
|
+
const fieldChangeSchema = z.object({
|
|
1511
|
+
collectionId: uuidSchema,
|
|
1512
|
+
fieldId: uuidSchema,
|
|
1513
|
+
fieldSlug: z.string(),
|
|
1514
|
+
changeType: fieldChangeTypeSchema,
|
|
1515
|
+
bump: semverBumpSchema
|
|
1516
|
+
});
|
|
1517
|
+
const collectionChangeTypeSchema = z.enum(["added", "deleted"]);
|
|
1518
|
+
const collectionChangeSchema = z.object({
|
|
1519
|
+
collectionId: uuidSchema,
|
|
1520
|
+
changeType: collectionChangeTypeSchema,
|
|
1521
|
+
bump: semverBumpSchema
|
|
1522
|
+
});
|
|
1523
|
+
const projectChangeTypeSchema = z.enum([
|
|
1524
|
+
"nameChanged",
|
|
1525
|
+
"descriptionChanged",
|
|
1526
|
+
"defaultLanguageChanged",
|
|
1527
|
+
"supportedLanguageAdded",
|
|
1528
|
+
"supportedLanguageRemoved"
|
|
1529
|
+
]);
|
|
1530
|
+
const projectChangeSchema = z.object({
|
|
1531
|
+
changeType: projectChangeTypeSchema,
|
|
1532
|
+
bump: semverBumpSchema
|
|
1533
|
+
});
|
|
1534
|
+
const assetChangeTypeSchema = z.enum([
|
|
1535
|
+
"added",
|
|
1536
|
+
"deleted",
|
|
1537
|
+
"metadataChanged",
|
|
1538
|
+
"binaryChanged"
|
|
1539
|
+
]);
|
|
1540
|
+
const assetChangeSchema = z.object({
|
|
1541
|
+
assetId: uuidSchema,
|
|
1542
|
+
changeType: assetChangeTypeSchema,
|
|
1543
|
+
bump: semverBumpSchema
|
|
1544
|
+
});
|
|
1545
|
+
const entryChangeTypeSchema = z.enum([
|
|
1546
|
+
"added",
|
|
1547
|
+
"deleted",
|
|
1548
|
+
"modified"
|
|
1549
|
+
]);
|
|
1550
|
+
const entryChangeSchema = z.object({
|
|
1551
|
+
collectionId: uuidSchema,
|
|
1552
|
+
entryId: uuidSchema,
|
|
1553
|
+
changeType: entryChangeTypeSchema,
|
|
1554
|
+
bump: semverBumpSchema
|
|
1555
|
+
});
|
|
1556
|
+
const releaseDiffSchema = z.object({
|
|
1557
|
+
project: projectSchema,
|
|
1558
|
+
bump: semverBumpSchema.nullable(),
|
|
1559
|
+
currentVersion: versionSchema,
|
|
1560
|
+
nextVersion: versionSchema.nullable(),
|
|
1561
|
+
projectChanges: z.array(projectChangeSchema),
|
|
1562
|
+
collectionChanges: z.array(collectionChangeSchema),
|
|
1563
|
+
fieldChanges: z.array(fieldChangeSchema),
|
|
1564
|
+
assetChanges: z.array(assetChangeSchema),
|
|
1565
|
+
entryChanges: z.array(entryChangeSchema)
|
|
1566
|
+
});
|
|
1567
|
+
const prepareReleaseSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
1568
|
+
const createReleaseSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
1569
|
+
const createPreviewReleaseSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
1570
|
+
const releaseResultSchema = z.object({
|
|
1571
|
+
version: versionSchema,
|
|
1572
|
+
diff: releaseDiffSchema
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1406
1575
|
//#endregion
|
|
1407
1576
|
//#region src/api/routes/content/v1/projects.ts
|
|
1408
1577
|
const tags$3 = ["Content API v1"];
|
|
@@ -1524,29 +1693,36 @@ const router$5 = createRouter().openapi(createRoute({
|
|
|
1524
1693
|
return c.json(count, 200);
|
|
1525
1694
|
}).openapi(createRoute({
|
|
1526
1695
|
summary: "Get one Collection",
|
|
1527
|
-
description: "Retrieve a Collection by
|
|
1696
|
+
description: "Retrieve a Collection by UUID or slug",
|
|
1528
1697
|
method: "get",
|
|
1529
|
-
path: "/{projectId}/collections/{
|
|
1698
|
+
path: "/{projectId}/collections/{collectionIdOrSlug}",
|
|
1530
1699
|
tags: tags$2,
|
|
1531
1700
|
request: { params: z.object({
|
|
1532
1701
|
projectId: uuidSchema.openapi({ param: {
|
|
1533
1702
|
name: "projectId",
|
|
1534
1703
|
in: "path"
|
|
1535
1704
|
} }),
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1705
|
+
collectionIdOrSlug: z.string().openapi({
|
|
1706
|
+
param: {
|
|
1707
|
+
name: "collectionIdOrSlug",
|
|
1708
|
+
in: "path"
|
|
1709
|
+
},
|
|
1710
|
+
description: "Collection UUID or slug"
|
|
1711
|
+
})
|
|
1540
1712
|
}) },
|
|
1541
1713
|
responses: { [200]: {
|
|
1542
1714
|
content: { "application/json": { schema: collectionSchema } },
|
|
1543
1715
|
description: "The requested Collection"
|
|
1544
1716
|
} }
|
|
1545
1717
|
}), async (c) => {
|
|
1546
|
-
const { projectId,
|
|
1718
|
+
const { projectId, collectionIdOrSlug } = c.req.valid("param");
|
|
1719
|
+
const resolvedId = await c.var.collectionService.resolveCollectionId({
|
|
1720
|
+
projectId,
|
|
1721
|
+
idOrSlug: collectionIdOrSlug
|
|
1722
|
+
});
|
|
1547
1723
|
const collection = await c.var.collectionService.read({
|
|
1548
1724
|
projectId,
|
|
1549
|
-
id:
|
|
1725
|
+
id: resolvedId
|
|
1550
1726
|
});
|
|
1551
1727
|
return c.json(collection, 200);
|
|
1552
1728
|
});
|
|
@@ -1558,7 +1734,7 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1558
1734
|
summary: "List Entries",
|
|
1559
1735
|
description: "Lists all Entries of the given Projects Collection",
|
|
1560
1736
|
method: "get",
|
|
1561
|
-
path: "/{projectId}/collections/{
|
|
1737
|
+
path: "/{projectId}/collections/{collectionIdOrSlug}/entries",
|
|
1562
1738
|
tags: tags$1,
|
|
1563
1739
|
request: {
|
|
1564
1740
|
params: z.object({
|
|
@@ -1566,10 +1742,13 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1566
1742
|
name: "projectId",
|
|
1567
1743
|
in: "path"
|
|
1568
1744
|
} }),
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1745
|
+
collectionIdOrSlug: z.string().openapi({
|
|
1746
|
+
param: {
|
|
1747
|
+
name: "collectionIdOrSlug",
|
|
1748
|
+
in: "path"
|
|
1749
|
+
},
|
|
1750
|
+
description: "Collection UUID or slug"
|
|
1751
|
+
})
|
|
1573
1752
|
}),
|
|
1574
1753
|
query: z.object({
|
|
1575
1754
|
limit: z.string().pipe(z.coerce.number()).optional().openapi({
|
|
@@ -1587,8 +1766,12 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1587
1766
|
description: "A list of Entries for the given Projects Collection"
|
|
1588
1767
|
} }
|
|
1589
1768
|
}), async (c) => {
|
|
1590
|
-
const { projectId,
|
|
1769
|
+
const { projectId, collectionIdOrSlug } = c.req.valid("param");
|
|
1591
1770
|
const { limit, offset } = c.req.valid("query");
|
|
1771
|
+
const collectionId = await c.var.collectionService.resolveCollectionId({
|
|
1772
|
+
projectId,
|
|
1773
|
+
idOrSlug: collectionIdOrSlug
|
|
1774
|
+
});
|
|
1592
1775
|
const entries = await c.var.entryService.list({
|
|
1593
1776
|
projectId,
|
|
1594
1777
|
collectionId,
|
|
@@ -1600,24 +1783,31 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1600
1783
|
summary: "Count Entries",
|
|
1601
1784
|
description: "Counts all Entries of the given Projects Collection",
|
|
1602
1785
|
method: "get",
|
|
1603
|
-
path: "/{projectId}/collections/{
|
|
1786
|
+
path: "/{projectId}/collections/{collectionIdOrSlug}/entries/count",
|
|
1604
1787
|
tags: tags$1,
|
|
1605
1788
|
request: { params: z.object({
|
|
1606
1789
|
projectId: uuidSchema.openapi({ param: {
|
|
1607
1790
|
name: "projectId",
|
|
1608
1791
|
in: "path"
|
|
1609
1792
|
} }),
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1793
|
+
collectionIdOrSlug: z.string().openapi({
|
|
1794
|
+
param: {
|
|
1795
|
+
name: "collectionIdOrSlug",
|
|
1796
|
+
in: "path"
|
|
1797
|
+
},
|
|
1798
|
+
description: "Collection UUID or slug"
|
|
1799
|
+
})
|
|
1614
1800
|
}) },
|
|
1615
1801
|
responses: { [200]: {
|
|
1616
1802
|
content: { "application/json": { schema: z.number() } },
|
|
1617
1803
|
description: "The number of Entries of the given Projects Collection"
|
|
1618
1804
|
} }
|
|
1619
1805
|
}), async (c) => {
|
|
1620
|
-
const { projectId,
|
|
1806
|
+
const { projectId, collectionIdOrSlug } = c.req.valid("param");
|
|
1807
|
+
const collectionId = await c.var.collectionService.resolveCollectionId({
|
|
1808
|
+
projectId,
|
|
1809
|
+
idOrSlug: collectionIdOrSlug
|
|
1810
|
+
});
|
|
1621
1811
|
const count = await c.var.entryService.count({
|
|
1622
1812
|
projectId,
|
|
1623
1813
|
collectionId
|
|
@@ -1627,17 +1817,20 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1627
1817
|
summary: "Get one Entry",
|
|
1628
1818
|
description: "Retrieve an Entry by ID",
|
|
1629
1819
|
method: "get",
|
|
1630
|
-
path: "/{projectId}/collections/{
|
|
1820
|
+
path: "/{projectId}/collections/{collectionIdOrSlug}/entries/{entryId}",
|
|
1631
1821
|
tags: tags$1,
|
|
1632
1822
|
request: { params: z.object({
|
|
1633
1823
|
projectId: uuidSchema.openapi({ param: {
|
|
1634
1824
|
name: "projectId",
|
|
1635
1825
|
in: "path"
|
|
1636
1826
|
} }),
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1827
|
+
collectionIdOrSlug: z.string().openapi({
|
|
1828
|
+
param: {
|
|
1829
|
+
name: "collectionIdOrSlug",
|
|
1830
|
+
in: "path"
|
|
1831
|
+
},
|
|
1832
|
+
description: "Collection UUID or slug"
|
|
1833
|
+
}),
|
|
1641
1834
|
entryId: uuidSchema.openapi({ param: {
|
|
1642
1835
|
name: "entryId",
|
|
1643
1836
|
in: "path"
|
|
@@ -1648,7 +1841,11 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1648
1841
|
description: "The requested Entry"
|
|
1649
1842
|
} }
|
|
1650
1843
|
}), async (c) => {
|
|
1651
|
-
const { projectId,
|
|
1844
|
+
const { projectId, collectionIdOrSlug, entryId } = c.req.valid("param");
|
|
1845
|
+
const collectionId = await c.var.collectionService.resolveCollectionId({
|
|
1846
|
+
projectId,
|
|
1847
|
+
idOrSlug: collectionIdOrSlug
|
|
1848
|
+
});
|
|
1652
1849
|
const entry = await c.var.entryService.read({
|
|
1653
1850
|
projectId,
|
|
1654
1851
|
collectionId,
|
|
@@ -1915,6 +2112,9 @@ const pathTo = {
|
|
|
1915
2112
|
collectionFile: (projectId, id) => {
|
|
1916
2113
|
return Path.join(pathTo.collection(projectId, id), "collection.json");
|
|
1917
2114
|
},
|
|
2115
|
+
collectionIndex: (projectId) => {
|
|
2116
|
+
return Path.join(pathTo.collections(projectId), "index.json");
|
|
2117
|
+
},
|
|
1918
2118
|
entries: (projectId, collectionId) => {
|
|
1919
2119
|
return Path.join(pathTo.collection(projectId, collectionId));
|
|
1920
2120
|
},
|
|
@@ -2137,6 +2337,38 @@ var AbstractCrudService = class {
|
|
|
2137
2337
|
}
|
|
2138
2338
|
};
|
|
2139
2339
|
|
|
2340
|
+
//#endregion
|
|
2341
|
+
//#region src/service/migrations/applyMigrations.ts
|
|
2342
|
+
function applyMigrations(data, migrations, targetVersion) {
|
|
2343
|
+
let current = structuredClone(data);
|
|
2344
|
+
while (current["coreVersion"] !== targetVersion) {
|
|
2345
|
+
const migration = migrations.find((m) => m.from === current["coreVersion"]);
|
|
2346
|
+
if (!migration) {
|
|
2347
|
+
current["coreVersion"] = targetVersion;
|
|
2348
|
+
break;
|
|
2349
|
+
}
|
|
2350
|
+
current = migration.run(current);
|
|
2351
|
+
current["coreVersion"] = migration.to;
|
|
2352
|
+
}
|
|
2353
|
+
return current;
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
//#endregion
|
|
2357
|
+
//#region src/service/migrations/assetMigrations.ts
|
|
2358
|
+
const assetMigrations = [];
|
|
2359
|
+
|
|
2360
|
+
//#endregion
|
|
2361
|
+
//#region src/service/migrations/collectionMigrations.ts
|
|
2362
|
+
const collectionMigrations = [];
|
|
2363
|
+
|
|
2364
|
+
//#endregion
|
|
2365
|
+
//#region src/service/migrations/entryMigrations.ts
|
|
2366
|
+
const entryMigrations = [];
|
|
2367
|
+
|
|
2368
|
+
//#endregion
|
|
2369
|
+
//#region src/service/migrations/projectMigrations.ts
|
|
2370
|
+
const projectMigrations = [];
|
|
2371
|
+
|
|
2140
2372
|
//#endregion
|
|
2141
2373
|
//#region src/util/shared.ts
|
|
2142
2374
|
/**
|
|
@@ -2179,10 +2411,12 @@ function slug(string) {
|
|
|
2179
2411
|
* Service that manages CRUD functionality for Asset files on disk
|
|
2180
2412
|
*/
|
|
2181
2413
|
var AssetService = class extends AbstractCrudService {
|
|
2414
|
+
coreVersion;
|
|
2182
2415
|
jsonFileService;
|
|
2183
2416
|
gitService;
|
|
2184
|
-
constructor(options, logService, jsonFileService, gitService) {
|
|
2417
|
+
constructor(coreVersion, options, logService, jsonFileService, gitService) {
|
|
2185
2418
|
super(serviceTypeSchema.enum.Asset, options, logService);
|
|
2419
|
+
this.coreVersion = coreVersion;
|
|
2186
2420
|
this.jsonFileService = jsonFileService;
|
|
2187
2421
|
this.gitService = gitService;
|
|
2188
2422
|
}
|
|
@@ -2202,6 +2436,7 @@ var AssetService = class extends AbstractCrudService {
|
|
|
2202
2436
|
name: slug(props.name),
|
|
2203
2437
|
objectType: "asset",
|
|
2204
2438
|
id,
|
|
2439
|
+
coreVersion: this.coreVersion,
|
|
2205
2440
|
created: datetime(),
|
|
2206
2441
|
updated: null,
|
|
2207
2442
|
extension: fileType.extension,
|
|
@@ -2246,6 +2481,13 @@ var AssetService = class extends AbstractCrudService {
|
|
|
2246
2481
|
}
|
|
2247
2482
|
}
|
|
2248
2483
|
/**
|
|
2484
|
+
* Returns the commit history of an Asset
|
|
2485
|
+
*/
|
|
2486
|
+
async history(props) {
|
|
2487
|
+
assetHistorySchema.parse(props);
|
|
2488
|
+
return this.gitService.log(pathTo.project(props.projectId), { filePath: pathTo.assetFile(props.projectId, props.id) });
|
|
2489
|
+
}
|
|
2490
|
+
/**
|
|
2249
2491
|
* Copies an Asset to given file path on disk
|
|
2250
2492
|
*/
|
|
2251
2493
|
async save(props) {
|
|
@@ -2354,13 +2596,11 @@ var AssetService = class extends AbstractCrudService {
|
|
|
2354
2596
|
* @param projectId The project's ID
|
|
2355
2597
|
* @param assetFile The AssetFile to convert
|
|
2356
2598
|
*/
|
|
2357
|
-
|
|
2599
|
+
toAsset(projectId, assetFile, commitHash) {
|
|
2358
2600
|
const assetPath = commitHash ? pathTo.tmpAsset(assetFile.id, commitHash, assetFile.extension) : pathTo.asset(projectId, assetFile.id, assetFile.extension);
|
|
2359
|
-
const history = await this.gitService.log(pathTo.project(projectId), { filePath: pathTo.assetFile(projectId, assetFile.id) });
|
|
2360
2601
|
return {
|
|
2361
2602
|
...assetFile,
|
|
2362
|
-
absolutePath: assetPath
|
|
2363
|
-
history
|
|
2603
|
+
absolutePath: assetPath
|
|
2364
2604
|
};
|
|
2365
2605
|
}
|
|
2366
2606
|
/**
|
|
@@ -2383,7 +2623,8 @@ var AssetService = class extends AbstractCrudService {
|
|
|
2383
2623
|
* Migrates an potentially outdated Asset file to the current schema
|
|
2384
2624
|
*/
|
|
2385
2625
|
migrate(potentiallyOutdatedAssetFile) {
|
|
2386
|
-
|
|
2626
|
+
const migrated = applyMigrations(migrateAssetSchema.parse(potentiallyOutdatedAssetFile), assetMigrations, this.coreVersion);
|
|
2627
|
+
return assetFileSchema.parse(migrated);
|
|
2387
2628
|
}
|
|
2388
2629
|
};
|
|
2389
2630
|
|
|
@@ -2393,29 +2634,59 @@ var AssetService = class extends AbstractCrudService {
|
|
|
2393
2634
|
* Service that manages CRUD functionality for Collection files on disk
|
|
2394
2635
|
*/
|
|
2395
2636
|
var CollectionService = class extends AbstractCrudService {
|
|
2637
|
+
coreVersion;
|
|
2396
2638
|
jsonFileService;
|
|
2397
2639
|
gitService;
|
|
2398
|
-
|
|
2640
|
+
/** In-memory cache for collection indices, keyed by projectId */
|
|
2641
|
+
cachedIndex = /* @__PURE__ */ new Map();
|
|
2642
|
+
/** Promise deduplication for concurrent rebuilds, keyed by projectId */
|
|
2643
|
+
rebuildPromise = /* @__PURE__ */ new Map();
|
|
2644
|
+
constructor(coreVersion, options, logService, jsonFileService, gitService) {
|
|
2399
2645
|
super(serviceTypeSchema.enum.Collection, options, logService);
|
|
2646
|
+
this.coreVersion = coreVersion;
|
|
2400
2647
|
this.jsonFileService = jsonFileService;
|
|
2401
2648
|
this.gitService = gitService;
|
|
2402
2649
|
}
|
|
2403
2650
|
/**
|
|
2651
|
+
* Resolves a UUID-or-slug string to a collection UUID.
|
|
2652
|
+
*
|
|
2653
|
+
* If the input matches UUID format, verifies the folder exists on disk first.
|
|
2654
|
+
* If the folder doesn't exist, falls back to slug lookup.
|
|
2655
|
+
* Otherwise, looks up via the index.
|
|
2656
|
+
*/
|
|
2657
|
+
async resolveCollectionId(props) {
|
|
2658
|
+
if (uuidSchema.safeParse(props.idOrSlug).success) {
|
|
2659
|
+
const collectionPath = pathTo.collection(props.projectId, props.idOrSlug);
|
|
2660
|
+
if (await Fs.pathExists(collectionPath)) return props.idOrSlug;
|
|
2661
|
+
}
|
|
2662
|
+
const index = await this.getIndex(props.projectId);
|
|
2663
|
+
for (const [uuid, slugValue] of Object.entries(index)) if (slugValue === props.idOrSlug) return uuid;
|
|
2664
|
+
this.cachedIndex.delete(props.projectId);
|
|
2665
|
+
const freshIndex = await this.getIndex(props.projectId);
|
|
2666
|
+
for (const [uuid, slugValue] of Object.entries(freshIndex)) if (slugValue === props.idOrSlug) return uuid;
|
|
2667
|
+
throw new Error(`Collection not found: "${props.idOrSlug}" does not match any collection UUID or slug`);
|
|
2668
|
+
}
|
|
2669
|
+
/**
|
|
2404
2670
|
* Creates a new Collection
|
|
2405
2671
|
*/
|
|
2406
2672
|
async create(props) {
|
|
2407
2673
|
createCollectionSchema.parse(props);
|
|
2674
|
+
this.validateFieldDefinitionSlugUniqueness(props.fieldDefinitions);
|
|
2408
2675
|
const id = uuid();
|
|
2409
2676
|
const projectPath = pathTo.project(props.projectId);
|
|
2410
2677
|
const collectionPath = pathTo.collection(props.projectId, id);
|
|
2411
2678
|
const collectionFilePath = pathTo.collectionFile(props.projectId, id);
|
|
2679
|
+
const slugPlural = slug(props.slug.plural);
|
|
2680
|
+
const index = await this.getIndex(props.projectId);
|
|
2681
|
+
if (Object.values(index).includes(slugPlural)) throw new Error(`Collection slug "${slugPlural}" is already in use by another collection`);
|
|
2412
2682
|
const collectionFile = {
|
|
2413
2683
|
...props,
|
|
2414
2684
|
objectType: "collection",
|
|
2415
2685
|
id,
|
|
2686
|
+
coreVersion: this.coreVersion,
|
|
2416
2687
|
slug: {
|
|
2417
2688
|
singular: slug(props.slug.singular),
|
|
2418
|
-
plural:
|
|
2689
|
+
plural: slugPlural
|
|
2419
2690
|
},
|
|
2420
2691
|
created: datetime(),
|
|
2421
2692
|
updated: null
|
|
@@ -2430,7 +2701,9 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2430
2701
|
id
|
|
2431
2702
|
}
|
|
2432
2703
|
});
|
|
2433
|
-
|
|
2704
|
+
index[id] = slugPlural;
|
|
2705
|
+
await this.writeIndex(props.projectId, index);
|
|
2706
|
+
return this.toCollection(collectionFile);
|
|
2434
2707
|
}
|
|
2435
2708
|
/**
|
|
2436
2709
|
* Returns a Collection by ID
|
|
@@ -2441,33 +2714,103 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2441
2714
|
readCollectionSchema.parse(props);
|
|
2442
2715
|
if (!props.commitHash) {
|
|
2443
2716
|
const collectionFile = await this.jsonFileService.read(pathTo.collectionFile(props.projectId, props.id), collectionFileSchema);
|
|
2444
|
-
return this.toCollection(
|
|
2717
|
+
return this.toCollection(collectionFile);
|
|
2445
2718
|
} else {
|
|
2446
2719
|
const collectionFile = this.migrate(JSON.parse(await this.gitService.getFileContentAtCommit(pathTo.project(props.projectId), pathTo.collectionFile(props.projectId, props.id), props.commitHash)));
|
|
2447
|
-
return this.toCollection(
|
|
2720
|
+
return this.toCollection(collectionFile);
|
|
2448
2721
|
}
|
|
2449
2722
|
}
|
|
2450
2723
|
/**
|
|
2724
|
+
* Reads a Collection by its slug
|
|
2725
|
+
*/
|
|
2726
|
+
async readBySlug(props) {
|
|
2727
|
+
const id = await this.resolveCollectionId({
|
|
2728
|
+
projectId: props.projectId,
|
|
2729
|
+
idOrSlug: props.slug
|
|
2730
|
+
});
|
|
2731
|
+
return this.read({
|
|
2732
|
+
projectId: props.projectId,
|
|
2733
|
+
id,
|
|
2734
|
+
commitHash: props.commitHash
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Returns the commit history of a Collection
|
|
2739
|
+
*/
|
|
2740
|
+
async history(props) {
|
|
2741
|
+
collectionHistorySchema.parse(props);
|
|
2742
|
+
return this.gitService.log(pathTo.project(props.projectId), { filePath: pathTo.collectionFile(props.projectId, props.id) });
|
|
2743
|
+
}
|
|
2744
|
+
/**
|
|
2451
2745
|
* Updates given Collection
|
|
2452
2746
|
*
|
|
2453
|
-
*
|
|
2454
|
-
*
|
|
2455
|
-
* @param projectId Project ID of the collection to update
|
|
2456
|
-
* @param collection Collection to write to disk
|
|
2457
|
-
* @returns An object containing information about the actions needed to be taken,
|
|
2458
|
-
* before given update can be executed or void if the update was executed successfully
|
|
2747
|
+
* Handles fieldDefinition slug rename cascade and collection slug uniqueness.
|
|
2459
2748
|
*/
|
|
2460
2749
|
async update(props) {
|
|
2461
2750
|
updateCollectionSchema.parse(props);
|
|
2751
|
+
this.validateFieldDefinitionSlugUniqueness(props.fieldDefinitions);
|
|
2462
2752
|
const projectPath = pathTo.project(props.projectId);
|
|
2463
2753
|
const collectionFilePath = pathTo.collectionFile(props.projectId, props.id);
|
|
2754
|
+
const prevCollectionFile = await this.read(props);
|
|
2464
2755
|
const collectionFile = {
|
|
2465
|
-
...
|
|
2756
|
+
...prevCollectionFile,
|
|
2466
2757
|
...props,
|
|
2467
2758
|
updated: datetime()
|
|
2468
2759
|
};
|
|
2760
|
+
const oldFieldDefs = prevCollectionFile.fieldDefinitions;
|
|
2761
|
+
const newFieldDefs = props.fieldDefinitions;
|
|
2762
|
+
const slugRenames = [];
|
|
2763
|
+
const oldByUuid = new Map(oldFieldDefs.map((fd) => [fd.id, fd]));
|
|
2764
|
+
for (const newFd of newFieldDefs) {
|
|
2765
|
+
const oldFd = oldByUuid.get(newFd.id);
|
|
2766
|
+
if (oldFd && oldFd.slug !== newFd.slug) slugRenames.push({
|
|
2767
|
+
oldSlug: oldFd.slug,
|
|
2768
|
+
newSlug: newFd.slug
|
|
2769
|
+
});
|
|
2770
|
+
}
|
|
2771
|
+
const filesToGitAdd = [collectionFilePath];
|
|
2772
|
+
if (slugRenames.length > 0) {
|
|
2773
|
+
const entriesPath = pathTo.entries(props.projectId, props.id);
|
|
2774
|
+
if (await Fs.pathExists(entriesPath)) {
|
|
2775
|
+
const entryFiles = (await Fs.readdir(entriesPath)).filter((f) => f.endsWith(".json") && f !== "collection.json");
|
|
2776
|
+
for (const entryFileName of entryFiles) {
|
|
2777
|
+
const entryFilePath = pathTo.entryFile(props.projectId, props.id, entryFileName.replace(".json", ""));
|
|
2778
|
+
try {
|
|
2779
|
+
const entryFile = await this.jsonFileService.read(entryFilePath, entryFileSchema);
|
|
2780
|
+
let changed = false;
|
|
2781
|
+
const newValues = { ...entryFile.values };
|
|
2782
|
+
for (const { oldSlug, newSlug } of slugRenames) if (oldSlug in newValues) {
|
|
2783
|
+
newValues[newSlug] = newValues[oldSlug];
|
|
2784
|
+
delete newValues[oldSlug];
|
|
2785
|
+
changed = true;
|
|
2786
|
+
}
|
|
2787
|
+
if (changed) {
|
|
2788
|
+
const updatedEntryFile = {
|
|
2789
|
+
...entryFile,
|
|
2790
|
+
values: newValues
|
|
2791
|
+
};
|
|
2792
|
+
await this.jsonFileService.update(updatedEntryFile, entryFilePath, entryFileSchema);
|
|
2793
|
+
filesToGitAdd.push(entryFilePath);
|
|
2794
|
+
}
|
|
2795
|
+
} catch (error) {
|
|
2796
|
+
this.logService.warn({
|
|
2797
|
+
source: "core",
|
|
2798
|
+
message: `Failed to update entry "${entryFileName}" during slug rename cascade: ${error instanceof Error ? error.message : String(error)}`
|
|
2799
|
+
});
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
const newSlugPlural = slug(props.slug.plural);
|
|
2805
|
+
if (prevCollectionFile.slug.plural !== newSlugPlural) {
|
|
2806
|
+
const index = await this.getIndex(props.projectId);
|
|
2807
|
+
const existingUuid = Object.entries(index).find(([, s]) => s === newSlugPlural);
|
|
2808
|
+
if (existingUuid && existingUuid[0] !== props.id) throw new Error(`Collection slug "${newSlugPlural}" is already in use by another collection`);
|
|
2809
|
+
index[props.id] = newSlugPlural;
|
|
2810
|
+
await this.writeIndex(props.projectId, index);
|
|
2811
|
+
}
|
|
2469
2812
|
await this.jsonFileService.update(collectionFile, collectionFilePath, collectionFileSchema);
|
|
2470
|
-
await this.gitService.add(projectPath,
|
|
2813
|
+
await this.gitService.add(projectPath, filesToGitAdd);
|
|
2471
2814
|
await this.gitService.commit(projectPath, {
|
|
2472
2815
|
method: "update",
|
|
2473
2816
|
reference: {
|
|
@@ -2475,10 +2818,10 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2475
2818
|
id: collectionFile.id
|
|
2476
2819
|
}
|
|
2477
2820
|
});
|
|
2478
|
-
return this.toCollection(
|
|
2821
|
+
return this.toCollection(collectionFile);
|
|
2479
2822
|
}
|
|
2480
2823
|
/**
|
|
2481
|
-
* Deletes given Collection (folder), including it's
|
|
2824
|
+
* Deletes given Collection (folder), including it's Entries
|
|
2482
2825
|
*
|
|
2483
2826
|
* The Fields that Collection used are not deleted.
|
|
2484
2827
|
*/
|
|
@@ -2495,6 +2838,9 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2495
2838
|
id: props.id
|
|
2496
2839
|
}
|
|
2497
2840
|
});
|
|
2841
|
+
const index = await this.getIndex(props.projectId);
|
|
2842
|
+
delete index[props.id];
|
|
2843
|
+
await this.writeIndex(props.projectId, index);
|
|
2498
2844
|
}
|
|
2499
2845
|
async list(props) {
|
|
2500
2846
|
listCollectionsSchema.parse(props);
|
|
@@ -2529,7 +2875,8 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2529
2875
|
* Migrates an potentially outdated Collection file to the current schema
|
|
2530
2876
|
*/
|
|
2531
2877
|
migrate(potentiallyOutdatedCollectionFile) {
|
|
2532
|
-
|
|
2878
|
+
const migrated = applyMigrations(migrateCollectionSchema.parse(potentiallyOutdatedCollectionFile), collectionMigrations, this.coreVersion);
|
|
2879
|
+
return collectionFileSchema.parse(migrated);
|
|
2533
2880
|
}
|
|
2534
2881
|
/**
|
|
2535
2882
|
* Creates an Collection from given CollectionFile
|
|
@@ -2537,26 +2884,83 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2537
2884
|
* @param projectId The project's ID
|
|
2538
2885
|
* @param collectionFile The CollectionFile to convert
|
|
2539
2886
|
*/
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
return {
|
|
2543
|
-
...collectionFile,
|
|
2544
|
-
history
|
|
2545
|
-
};
|
|
2887
|
+
toCollection(collectionFile) {
|
|
2888
|
+
return { ...collectionFile };
|
|
2546
2889
|
}
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2890
|
+
/**
|
|
2891
|
+
* Gets the collection index, rebuilding from disk if not cached
|
|
2892
|
+
*/
|
|
2893
|
+
async getIndex(projectId) {
|
|
2894
|
+
const cached = this.cachedIndex.get(projectId);
|
|
2895
|
+
if (cached) return cached;
|
|
2896
|
+
const pending = this.rebuildPromise.get(projectId);
|
|
2897
|
+
if (pending) return pending;
|
|
2898
|
+
const promise = this.rebuildIndex(projectId);
|
|
2899
|
+
this.rebuildPromise.set(projectId, promise);
|
|
2900
|
+
const result = await promise;
|
|
2901
|
+
this.cachedIndex.set(projectId, result);
|
|
2902
|
+
this.rebuildPromise.delete(projectId);
|
|
2903
|
+
return result;
|
|
2904
|
+
}
|
|
2905
|
+
/**
|
|
2906
|
+
* Writes the index file atomically and updates cache
|
|
2907
|
+
*/
|
|
2908
|
+
async writeIndex(projectId, index) {
|
|
2909
|
+
const indexPath = pathTo.collectionIndex(projectId);
|
|
2910
|
+
await Fs.writeFile(indexPath, JSON.stringify(index, null, 2), { encoding: "utf8" });
|
|
2911
|
+
this.cachedIndex.set(projectId, index);
|
|
2912
|
+
}
|
|
2913
|
+
/**
|
|
2914
|
+
* Rebuilds the index by scanning all collection folders
|
|
2915
|
+
*/
|
|
2916
|
+
async rebuildIndex(projectId) {
|
|
2917
|
+
this.logService.info({
|
|
2918
|
+
source: "core",
|
|
2919
|
+
message: `Rebuilding Collection index for Project "${projectId}"`
|
|
2920
|
+
});
|
|
2921
|
+
const index = {};
|
|
2922
|
+
const collectionFolders = await folders(pathTo.collections(projectId));
|
|
2923
|
+
for (const folder of collectionFolders) {
|
|
2924
|
+
if (!uuidSchema.safeParse(folder.name).success) continue;
|
|
2925
|
+
try {
|
|
2926
|
+
const collectionFilePath = pathTo.collectionFile(projectId, folder.name);
|
|
2927
|
+
const collectionFile = await this.jsonFileService.read(collectionFilePath, collectionFileSchema);
|
|
2928
|
+
index[collectionFile.id] = collectionFile.slug.plural;
|
|
2929
|
+
} catch (error) {
|
|
2930
|
+
this.logService.warn({
|
|
2931
|
+
source: "core",
|
|
2932
|
+
message: `Skipping collection folder "${folder.name}" during index rebuild: ${error instanceof Error ? error.message : String(error)}`
|
|
2933
|
+
});
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
await this.writeIndex(projectId, index);
|
|
2937
|
+
return index;
|
|
2938
|
+
}
|
|
2939
|
+
/**
|
|
2940
|
+
* Validates that no two fieldDefinitions share the same slug
|
|
2941
|
+
*/
|
|
2942
|
+
validateFieldDefinitionSlugUniqueness(fieldDefinitions) {
|
|
2943
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2944
|
+
for (const fd of fieldDefinitions) {
|
|
2945
|
+
if (seen.has(fd.slug)) throw new Error(`Duplicate fieldDefinition slug "${fd.slug}": each fieldDefinition within a collection must have a unique slug`);
|
|
2946
|
+
seen.add(fd.slug);
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
};
|
|
2950
|
+
|
|
2951
|
+
//#endregion
|
|
2550
2952
|
//#region src/service/EntryService.ts
|
|
2551
2953
|
/**
|
|
2552
2954
|
* Service that manages CRUD functionality for Entry files on disk
|
|
2553
2955
|
*/
|
|
2554
2956
|
var EntryService = class extends AbstractCrudService {
|
|
2957
|
+
coreVersion;
|
|
2555
2958
|
jsonFileService;
|
|
2556
2959
|
gitService;
|
|
2557
2960
|
collectionService;
|
|
2558
|
-
constructor(options, logService, jsonFileService, gitService, collectionService) {
|
|
2961
|
+
constructor(coreVersion, options, logService, jsonFileService, gitService, collectionService) {
|
|
2559
2962
|
super(serviceTypeSchema.enum.Entry, options, logService);
|
|
2963
|
+
this.coreVersion = coreVersion;
|
|
2560
2964
|
this.jsonFileService = jsonFileService;
|
|
2561
2965
|
this.gitService = gitService;
|
|
2562
2966
|
this.collectionService = collectionService;
|
|
@@ -2576,11 +2980,12 @@ var EntryService = class extends AbstractCrudService {
|
|
|
2576
2980
|
const entryFile = {
|
|
2577
2981
|
objectType: "entry",
|
|
2578
2982
|
id,
|
|
2983
|
+
coreVersion: this.coreVersion,
|
|
2579
2984
|
values: props.values,
|
|
2580
2985
|
created: datetime(),
|
|
2581
2986
|
updated: null
|
|
2582
2987
|
};
|
|
2583
|
-
const entry =
|
|
2988
|
+
const entry = this.toEntry(entryFile);
|
|
2584
2989
|
getCreateEntrySchemaFromFieldDefinitions(collection.fieldDefinitions).parse(props);
|
|
2585
2990
|
await this.jsonFileService.create(entryFile, entryFilePath, entryFileSchema);
|
|
2586
2991
|
await this.gitService.add(projectPath, [entryFilePath]);
|
|
@@ -2603,13 +3008,20 @@ var EntryService = class extends AbstractCrudService {
|
|
|
2603
3008
|
readEntrySchema.parse(props);
|
|
2604
3009
|
if (!props.commitHash) {
|
|
2605
3010
|
const entryFile = await this.jsonFileService.read(pathTo.entryFile(props.projectId, props.collectionId, props.id), entryFileSchema);
|
|
2606
|
-
return this.toEntry(
|
|
3011
|
+
return this.toEntry(entryFile);
|
|
2607
3012
|
} else {
|
|
2608
3013
|
const entryFile = this.migrate(JSON.parse(await this.gitService.getFileContentAtCommit(pathTo.project(props.projectId), pathTo.entryFile(props.projectId, props.collectionId, props.id), props.commitHash)));
|
|
2609
|
-
return this.toEntry(
|
|
3014
|
+
return this.toEntry(entryFile);
|
|
2610
3015
|
}
|
|
2611
3016
|
}
|
|
2612
3017
|
/**
|
|
3018
|
+
* Returns the commit history of an Entry
|
|
3019
|
+
*/
|
|
3020
|
+
async history(props) {
|
|
3021
|
+
entryHistorySchema.parse(props);
|
|
3022
|
+
return this.gitService.log(pathTo.project(props.projectId), { filePath: pathTo.entryFile(props.projectId, props.collectionId, props.id) });
|
|
3023
|
+
}
|
|
3024
|
+
/**
|
|
2613
3025
|
* Updates an Entry of given Collection with new Values and shared Values
|
|
2614
3026
|
*/
|
|
2615
3027
|
async update(props) {
|
|
@@ -2629,7 +3041,7 @@ var EntryService = class extends AbstractCrudService {
|
|
|
2629
3041
|
values: props.values,
|
|
2630
3042
|
updated: datetime()
|
|
2631
3043
|
};
|
|
2632
|
-
const entry =
|
|
3044
|
+
const entry = this.toEntry(entryFile);
|
|
2633
3045
|
getUpdateEntrySchemaFromFieldDefinitions(collection.fieldDefinitions).parse(props);
|
|
2634
3046
|
await this.jsonFileService.update(entryFile, entryFilePath, entryFileSchema);
|
|
2635
3047
|
await this.gitService.add(projectPath, [entryFilePath]);
|
|
@@ -2695,17 +3107,14 @@ var EntryService = class extends AbstractCrudService {
|
|
|
2695
3107
|
* Migrates an potentially outdated Entry file to the current schema
|
|
2696
3108
|
*/
|
|
2697
3109
|
migrate(potentiallyOutdatedEntryFile) {
|
|
2698
|
-
|
|
3110
|
+
const migrated = applyMigrations(migrateEntrySchema.parse(potentiallyOutdatedEntryFile), entryMigrations, this.coreVersion);
|
|
3111
|
+
return entryFileSchema.parse(migrated);
|
|
2699
3112
|
}
|
|
2700
3113
|
/**
|
|
2701
3114
|
* Creates an Entry from given EntryFile by resolving it's Values
|
|
2702
3115
|
*/
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
return {
|
|
2706
|
-
...entryFile,
|
|
2707
|
-
history
|
|
2708
|
-
};
|
|
3116
|
+
toEntry(entryFile) {
|
|
3117
|
+
return { ...entryFile };
|
|
2709
3118
|
}
|
|
2710
3119
|
};
|
|
2711
3120
|
|
|
@@ -2734,10 +3143,11 @@ var GitTagService = class extends AbstractCrudService {
|
|
|
2734
3143
|
id
|
|
2735
3144
|
];
|
|
2736
3145
|
if (props.hash) args = [...args, props.hash];
|
|
3146
|
+
const fullMessage = `${this.serializeTagMessage(props.message)}\n\n${this.tagMessageToTrailers(props.message).join("\n")}`;
|
|
2737
3147
|
args = [
|
|
2738
3148
|
...args,
|
|
2739
3149
|
"-m",
|
|
2740
|
-
|
|
3150
|
+
fullMessage
|
|
2741
3151
|
];
|
|
2742
3152
|
await this.git(props.path, args);
|
|
2743
3153
|
return await this.read({
|
|
@@ -2796,27 +3206,33 @@ var GitTagService = class extends AbstractCrudService {
|
|
|
2796
3206
|
async list(props) {
|
|
2797
3207
|
listGitTagsSchema.parse(props);
|
|
2798
3208
|
let args = ["tag", "--list"];
|
|
3209
|
+
const format = [
|
|
3210
|
+
"%(refname:short)",
|
|
3211
|
+
"%(trailers:key=Type,valueonly)",
|
|
3212
|
+
"%(trailers:key=Version,valueonly)",
|
|
3213
|
+
"%(trailers:key=Core-Version,valueonly)",
|
|
3214
|
+
"%(*authorname)",
|
|
3215
|
+
"%(*authoremail)",
|
|
3216
|
+
"%(*authordate:iso-strict)"
|
|
3217
|
+
].join("|");
|
|
2799
3218
|
args = [
|
|
2800
3219
|
...args,
|
|
2801
3220
|
"--sort=-*authordate",
|
|
2802
|
-
|
|
3221
|
+
`--format=${format}`
|
|
2803
3222
|
];
|
|
2804
|
-
const gitTags = (await this.git(props.path, args)).stdout.split("\n").filter((line) => {
|
|
3223
|
+
const gitTags = (await this.git(props.path, args)).stdout.replace(/\n\|/g, "|").split("\n").filter((line) => {
|
|
2805
3224
|
return line.trim() !== "";
|
|
2806
3225
|
}).map((line) => {
|
|
2807
3226
|
const lineArray = line.split("|");
|
|
2808
|
-
if (lineArray[
|
|
2809
|
-
lineArray[3] = lineArray[3].slice(1, -1);
|
|
2810
|
-
lineArray[3] = lineArray[3].slice(0, -1);
|
|
2811
|
-
}
|
|
3227
|
+
if (lineArray[5]?.startsWith("<") && lineArray[5]?.endsWith(">")) lineArray[5] = lineArray[5].slice(1, -1);
|
|
2812
3228
|
return {
|
|
2813
3229
|
id: lineArray[0],
|
|
2814
|
-
message: lineArray[1],
|
|
3230
|
+
message: this.parseTagTrailers(lineArray[1]?.trim(), lineArray[2]?.trim(), lineArray[3]?.trim()),
|
|
2815
3231
|
author: {
|
|
2816
|
-
name: lineArray[
|
|
2817
|
-
email: lineArray[
|
|
3232
|
+
name: lineArray[4],
|
|
3233
|
+
email: lineArray[5]
|
|
2818
3234
|
},
|
|
2819
|
-
datetime: datetime(lineArray[
|
|
3235
|
+
datetime: datetime(lineArray[6])
|
|
2820
3236
|
};
|
|
2821
3237
|
}).filter(this.isGitTag.bind(this));
|
|
2822
3238
|
return {
|
|
@@ -2839,6 +3255,43 @@ var GitTagService = class extends AbstractCrudService {
|
|
|
2839
3255
|
return (await this.list({ path: props.path })).total;
|
|
2840
3256
|
}
|
|
2841
3257
|
/**
|
|
3258
|
+
* Serializes a GitTagMessage into a human-readable subject line
|
|
3259
|
+
*/
|
|
3260
|
+
serializeTagMessage(message) {
|
|
3261
|
+
return `${message.type.charAt(0).toUpperCase() + message.type.slice(1)} ${message.type === "upgrade" ? message.coreVersion : message.version}`;
|
|
3262
|
+
}
|
|
3263
|
+
/**
|
|
3264
|
+
* Converts a GitTagMessage into git trailer lines
|
|
3265
|
+
*/
|
|
3266
|
+
tagMessageToTrailers(message) {
|
|
3267
|
+
const trailers = [`Type: ${message.type}`];
|
|
3268
|
+
if (message.type === "upgrade") trailers.push(`Core-Version: ${message.coreVersion}`);
|
|
3269
|
+
else trailers.push(`Version: ${message.version}`);
|
|
3270
|
+
return trailers;
|
|
3271
|
+
}
|
|
3272
|
+
/**
|
|
3273
|
+
* Parses git trailer values back into a GitTagMessage
|
|
3274
|
+
*/
|
|
3275
|
+
parseTagTrailers(type, version, coreVersion) {
|
|
3276
|
+
switch (type) {
|
|
3277
|
+
case "upgrade": return gitTagMessageSchema.parse({
|
|
3278
|
+
type,
|
|
3279
|
+
coreVersion
|
|
3280
|
+
});
|
|
3281
|
+
case "release":
|
|
3282
|
+
case "preview": return gitTagMessageSchema.parse({
|
|
3283
|
+
type,
|
|
3284
|
+
version
|
|
3285
|
+
});
|
|
3286
|
+
default:
|
|
3287
|
+
this.logService.warn({
|
|
3288
|
+
source: "core",
|
|
3289
|
+
message: `Tag with ID "${type}" has an invalid or missing Type trailer and will be ignored`
|
|
3290
|
+
});
|
|
3291
|
+
return null;
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
/**
|
|
2842
3295
|
* Type guard for GitTag
|
|
2843
3296
|
*
|
|
2844
3297
|
* @param obj The object to check
|
|
@@ -3128,9 +3581,16 @@ var GitService = class {
|
|
|
3128
3581
|
gitMessageSchema.parse(message);
|
|
3129
3582
|
const user = await this.userService.get();
|
|
3130
3583
|
if (!user) throw new NoCurrentUserError();
|
|
3584
|
+
const subject = `${message.method.charAt(0).toUpperCase() + message.method.slice(1)} ${message.reference.objectType} ${message.reference.id}`;
|
|
3585
|
+
const trailers = [
|
|
3586
|
+
`Method: ${message.method}`,
|
|
3587
|
+
`Object-Type: ${message.reference.objectType}`,
|
|
3588
|
+
`Object-Id: ${message.reference.id}`
|
|
3589
|
+
];
|
|
3590
|
+
if (message.reference.collectionId) trailers.push(`Collection-Id: ${message.reference.collectionId}`);
|
|
3131
3591
|
const args = [
|
|
3132
3592
|
"commit",
|
|
3133
|
-
`--message=${
|
|
3593
|
+
`--message=${`${subject}\n\n${trailers.join("\n")}`}`,
|
|
3134
3594
|
`--author=${user.name} <${user.email}>`
|
|
3135
3595
|
];
|
|
3136
3596
|
await this.git(path, args);
|
|
@@ -3150,33 +3610,52 @@ var GitService = class {
|
|
|
3150
3610
|
let args = ["log"];
|
|
3151
3611
|
if (options?.between?.from) args = [...args, `${options.between.from}..${options.between.to || "HEAD"}`];
|
|
3152
3612
|
if (options?.limit) args = [...args, `--max-count=${options.limit}`];
|
|
3153
|
-
|
|
3613
|
+
const format = [
|
|
3614
|
+
"%H",
|
|
3615
|
+
"%(trailers:key=Method,valueonly)",
|
|
3616
|
+
"%(trailers:key=Object-Type,valueonly)",
|
|
3617
|
+
"%(trailers:key=Object-Id,valueonly)",
|
|
3618
|
+
"%(trailers:key=Collection-Id,valueonly)",
|
|
3619
|
+
"%an",
|
|
3620
|
+
"%ae",
|
|
3621
|
+
"%aI",
|
|
3622
|
+
"%D"
|
|
3623
|
+
].join("|");
|
|
3624
|
+
args = [...args, `--format=${format}`];
|
|
3154
3625
|
if (options?.filePath) args = [
|
|
3155
3626
|
...args,
|
|
3156
3627
|
"--",
|
|
3157
3628
|
options.filePath
|
|
3158
3629
|
];
|
|
3159
|
-
const noEmptyLinesArr = (await this.git(path, args)).stdout.split("\n").filter((line) => {
|
|
3630
|
+
const noEmptyLinesArr = (await this.git(path, args)).stdout.replace(/\n\|/g, "|").split("\n").filter((line) => {
|
|
3160
3631
|
return line.trim() !== "";
|
|
3161
3632
|
});
|
|
3162
3633
|
return (await Promise.all(noEmptyLinesArr.map(async (line) => {
|
|
3163
3634
|
const lineArray = line.split("|");
|
|
3164
|
-
const tagId = this.refNameToTagName(lineArray[
|
|
3635
|
+
const tagId = this.refNameToTagName(lineArray[8]?.trim() || "");
|
|
3165
3636
|
const tag = tagId ? await this.tags.read({
|
|
3166
3637
|
path,
|
|
3167
3638
|
id: tagId
|
|
3168
3639
|
}) : null;
|
|
3640
|
+
const collectionId = lineArray[4]?.trim();
|
|
3169
3641
|
return {
|
|
3170
3642
|
hash: lineArray[0],
|
|
3171
|
-
message:
|
|
3643
|
+
message: {
|
|
3644
|
+
method: lineArray[1]?.trim(),
|
|
3645
|
+
reference: {
|
|
3646
|
+
objectType: lineArray[2]?.trim(),
|
|
3647
|
+
id: lineArray[3]?.trim(),
|
|
3648
|
+
...collectionId ? { collectionId } : {}
|
|
3649
|
+
}
|
|
3650
|
+
},
|
|
3172
3651
|
author: {
|
|
3173
|
-
name: lineArray[
|
|
3174
|
-
email: lineArray[
|
|
3652
|
+
name: lineArray[5],
|
|
3653
|
+
email: lineArray[6]
|
|
3175
3654
|
},
|
|
3176
|
-
datetime: datetime(lineArray[
|
|
3655
|
+
datetime: datetime(lineArray[7]),
|
|
3177
3656
|
tag
|
|
3178
3657
|
};
|
|
3179
|
-
}))).filter(this.isGitCommit
|
|
3658
|
+
}))).filter((obj) => this.isGitCommit(obj));
|
|
3180
3659
|
}
|
|
3181
3660
|
/**
|
|
3182
3661
|
* Retrieves the content of a file at a specific commit
|
|
@@ -3190,6 +3669,35 @@ var GitService = class {
|
|
|
3190
3669
|
};
|
|
3191
3670
|
return (await this.git(path, args, { processCallback: setEncoding })).stdout;
|
|
3192
3671
|
}
|
|
3672
|
+
/**
|
|
3673
|
+
* Lists directory entries at a specific commit
|
|
3674
|
+
*
|
|
3675
|
+
* Useful for discovering what files/folders existed at a past commit,
|
|
3676
|
+
* e.g. to detect deleted collections when comparing branches.
|
|
3677
|
+
*
|
|
3678
|
+
* @see https://git-scm.com/docs/git-ls-tree
|
|
3679
|
+
*
|
|
3680
|
+
* @param path Path to the repository
|
|
3681
|
+
* @param treePath Relative path within the repository to list
|
|
3682
|
+
* @param commitRef Commit hash, branch name, or other git ref
|
|
3683
|
+
*/
|
|
3684
|
+
async listTreeAtCommit(path, treePath, commitRef) {
|
|
3685
|
+
const args = [
|
|
3686
|
+
"ls-tree",
|
|
3687
|
+
"--name-only",
|
|
3688
|
+
commitRef,
|
|
3689
|
+
`${treePath.replace(`${path}${Path.sep}`, "").split("\\").join("/")}/`
|
|
3690
|
+
];
|
|
3691
|
+
try {
|
|
3692
|
+
return (await this.git(path, args)).stdout.split("\n").map((line) => line.trim()).filter((line) => line !== "").map((entry) => {
|
|
3693
|
+
const parts = entry.split("/");
|
|
3694
|
+
return parts[parts.length - 1] || entry;
|
|
3695
|
+
});
|
|
3696
|
+
} catch (error) {
|
|
3697
|
+
if (error instanceof GitError) return [];
|
|
3698
|
+
throw error;
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3193
3701
|
refNameToTagName(refName) {
|
|
3194
3702
|
const tagName = refName.replace("tag: ", "").trim();
|
|
3195
3703
|
if (tagName === "" || uuidSchema.safeParse(tagName).success === false) return null;
|
|
@@ -3558,7 +4066,6 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3558
4066
|
created: datetime(),
|
|
3559
4067
|
updated: null,
|
|
3560
4068
|
coreVersion: this.coreVersion,
|
|
3561
|
-
status: "todo",
|
|
3562
4069
|
version: "0.0.1"
|
|
3563
4070
|
};
|
|
3564
4071
|
const projectPath = pathTo.project(id);
|
|
@@ -3612,11 +4119,23 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3612
4119
|
const projectFile = await this.jsonFileService.read(pathTo.projectFile(props.id), projectFileSchema);
|
|
3613
4120
|
return await this.toProject(projectFile);
|
|
3614
4121
|
} else {
|
|
3615
|
-
const projectFile = this.migrate(
|
|
4122
|
+
const projectFile = this.migrate(JSON.parse(await this.gitService.getFileContentAtCommit(pathTo.project(props.id), pathTo.projectFile(props.id), props.commitHash)));
|
|
3616
4123
|
return await this.toProject(projectFile);
|
|
3617
4124
|
}
|
|
3618
4125
|
}
|
|
3619
4126
|
/**
|
|
4127
|
+
* Returns the commit history of a Project
|
|
4128
|
+
*/
|
|
4129
|
+
async history(props) {
|
|
4130
|
+
projectHistorySchema.parse(props);
|
|
4131
|
+
const projectPath = pathTo.project(props.id);
|
|
4132
|
+
const fullHistory = await this.gitService.log(projectPath);
|
|
4133
|
+
return {
|
|
4134
|
+
history: await this.gitService.log(projectPath, { filePath: pathTo.projectFile(props.id) }),
|
|
4135
|
+
fullHistory
|
|
4136
|
+
};
|
|
4137
|
+
}
|
|
4138
|
+
/**
|
|
3620
4139
|
* Updates given Project
|
|
3621
4140
|
*/
|
|
3622
4141
|
async update(props) {
|
|
@@ -3649,7 +4168,7 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3649
4168
|
const projectPath = pathTo.project(props.id);
|
|
3650
4169
|
const projectFilePath = pathTo.projectFile(props.id);
|
|
3651
4170
|
if (await this.gitService.branches.current(projectPath) !== projectBranchSchema.enum.work) await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work);
|
|
3652
|
-
const currentProjectFile =
|
|
4171
|
+
const currentProjectFile = await this.jsonFileService.unsafeRead(projectFilePath);
|
|
3653
4172
|
if (Semver.gt(currentProjectFile.coreVersion, this.coreVersion)) throw new ProjectUpgradeError(`The Projects Core version "${currentProjectFile.coreVersion}" is higher than the current Core version "${this.coreVersion}".`);
|
|
3654
4173
|
if (Semver.eq(currentProjectFile.coreVersion, this.coreVersion) && props.force !== true) throw new ProjectUpgradeError(`The Projects Core version "${currentProjectFile.coreVersion}" is already up to date.`);
|
|
3655
4174
|
const assetReferences = await this.listReferences("asset", props.id);
|
|
@@ -3686,7 +4205,10 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3686
4205
|
});
|
|
3687
4206
|
await this.gitService.tags.create({
|
|
3688
4207
|
path: projectPath,
|
|
3689
|
-
message:
|
|
4208
|
+
message: {
|
|
4209
|
+
type: "upgrade",
|
|
4210
|
+
coreVersion: migratedProjectFile.coreVersion
|
|
4211
|
+
}
|
|
3690
4212
|
});
|
|
3691
4213
|
await this.gitService.branches.delete(projectPath, upgradeBranchName, true);
|
|
3692
4214
|
this.logService.info({
|
|
@@ -3792,7 +4314,7 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3792
4314
|
return (await Promise.all(projectReferences.map(async (reference) => {
|
|
3793
4315
|
const json = await this.jsonFileService.unsafeRead(pathTo.projectFile(reference.id));
|
|
3794
4316
|
const projectFile = migrateProjectSchema.parse(json);
|
|
3795
|
-
if (projectFile.coreVersion !== this.coreVersion) return projectFile;
|
|
4317
|
+
if (projectFile.coreVersion !== this.coreVersion) return this.migrate(projectFile);
|
|
3796
4318
|
return null;
|
|
3797
4319
|
}))).filter(isNotEmpty);
|
|
3798
4320
|
}
|
|
@@ -3824,9 +4346,9 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3824
4346
|
/**
|
|
3825
4347
|
* Migrates an potentially outdated Project file to the current schema
|
|
3826
4348
|
*/
|
|
3827
|
-
migrate(
|
|
3828
|
-
|
|
3829
|
-
return projectFileSchema.parse(
|
|
4349
|
+
migrate(potentiallyOutdatedFile) {
|
|
4350
|
+
const migrated = applyMigrations(migrateProjectSchema.parse(potentiallyOutdatedFile), projectMigrations, this.coreVersion);
|
|
4351
|
+
return projectFileSchema.parse(migrated);
|
|
3830
4352
|
}
|
|
3831
4353
|
/**
|
|
3832
4354
|
* Creates a Project from given ProjectFile
|
|
@@ -3835,13 +4357,9 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3835
4357
|
const projectPath = pathTo.project(projectFile.id);
|
|
3836
4358
|
let remoteOriginUrl = null;
|
|
3837
4359
|
if (await this.gitService.remotes.hasOrigin(projectPath)) remoteOriginUrl = await this.gitService.remotes.getOriginUrl(projectPath);
|
|
3838
|
-
const fullHistory = await this.gitService.log(pathTo.project(projectFile.id));
|
|
3839
|
-
const history = await this.gitService.log(pathTo.project(projectFile.id), { filePath: pathTo.projectFile(projectFile.id) });
|
|
3840
4360
|
return {
|
|
3841
4361
|
...projectFile,
|
|
3842
|
-
remoteOriginUrl
|
|
3843
|
-
history,
|
|
3844
|
-
fullHistory
|
|
4362
|
+
remoteOriginUrl
|
|
3845
4363
|
};
|
|
3846
4364
|
}
|
|
3847
4365
|
/**
|
|
@@ -3871,7 +4389,8 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3871
4389
|
"!/.gitattributes",
|
|
3872
4390
|
"!/**/.gitkeep",
|
|
3873
4391
|
"",
|
|
3874
|
-
"# elek.io related ignores"
|
|
4392
|
+
"# elek.io related ignores",
|
|
4393
|
+
"collections/index.json"
|
|
3875
4394
|
].join(Os.EOL));
|
|
3876
4395
|
}
|
|
3877
4396
|
async upgradeObjectFile(projectId, objectType, reference, collectionId) {
|
|
@@ -3937,6 +4456,665 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3937
4456
|
}
|
|
3938
4457
|
};
|
|
3939
4458
|
|
|
4459
|
+
//#endregion
|
|
4460
|
+
//#region src/service/ReleaseService.ts
|
|
4461
|
+
/**
|
|
4462
|
+
* Service that manages Release functionality
|
|
4463
|
+
*
|
|
4464
|
+
* A release diffs the current `work` branch against the `production` branch
|
|
4465
|
+
* to determine what changed, computes a semver bump, and merges work into production.
|
|
4466
|
+
*/
|
|
4467
|
+
var ReleaseService = class extends AbstractCrudService {
|
|
4468
|
+
gitService;
|
|
4469
|
+
jsonFileService;
|
|
4470
|
+
projectService;
|
|
4471
|
+
constructor(options, logService, gitService, jsonFileService, projectService) {
|
|
4472
|
+
super(serviceTypeSchema.enum.Release, options, logService);
|
|
4473
|
+
this.gitService = gitService;
|
|
4474
|
+
this.jsonFileService = jsonFileService;
|
|
4475
|
+
this.projectService = projectService;
|
|
4476
|
+
}
|
|
4477
|
+
/**
|
|
4478
|
+
* Prepares a release by diffing the current `work` branch against `production`.
|
|
4479
|
+
*
|
|
4480
|
+
* Returns a read-only summary of all changes and the computed next version.
|
|
4481
|
+
* If there are no changes, the next version and bump will be null.
|
|
4482
|
+
*/
|
|
4483
|
+
async prepare(props) {
|
|
4484
|
+
prepareReleaseSchema.parse(props);
|
|
4485
|
+
const projectPath = pathTo.project(props.projectId);
|
|
4486
|
+
const currentBranch = await this.gitService.branches.current(projectPath);
|
|
4487
|
+
if (currentBranch !== projectBranchSchema.enum.work) throw new Error(`Not on work branch (currently on "${currentBranch}")`);
|
|
4488
|
+
const project = await this.projectService.read({ id: props.projectId });
|
|
4489
|
+
const currentVersion = project.version;
|
|
4490
|
+
const productionRef = projectBranchSchema.enum.production;
|
|
4491
|
+
const productionProject = await this.getProjectAtRef(props.projectId, projectPath, productionRef);
|
|
4492
|
+
const projectDiff = this.diffProject(project, productionProject);
|
|
4493
|
+
const currentCollections = await this.getCollectionsAtRef(props.projectId, projectPath, projectBranchSchema.enum.work);
|
|
4494
|
+
const productionCollections = await this.getCollectionsAtRef(props.projectId, projectPath, productionRef);
|
|
4495
|
+
const collectionDiff = this.diffCollections(currentCollections, productionCollections);
|
|
4496
|
+
const currentAssets = await this.getAssetsAtRef(props.projectId, projectPath, projectBranchSchema.enum.work);
|
|
4497
|
+
const productionAssets = await this.getAssetsAtRef(props.projectId, projectPath, productionRef);
|
|
4498
|
+
const assetDiff = this.diffAssets(currentAssets, productionAssets);
|
|
4499
|
+
const allCollectionIds = new Set([...currentCollections.map((c) => c.id), ...productionCollections.map((c) => c.id)]);
|
|
4500
|
+
const entryDiff = await this.diffEntries(props.projectId, projectPath, allCollectionIds, productionRef);
|
|
4501
|
+
let finalBump = null;
|
|
4502
|
+
for (const bump of [
|
|
4503
|
+
projectDiff.bump,
|
|
4504
|
+
collectionDiff.bump,
|
|
4505
|
+
assetDiff.bump,
|
|
4506
|
+
entryDiff.bump
|
|
4507
|
+
]) if (bump) finalBump = finalBump ? this.higherBump(finalBump, bump) : bump;
|
|
4508
|
+
if (!finalBump) {
|
|
4509
|
+
if (await this.hasCommitsBetween(projectPath, productionRef, projectBranchSchema.enum.work)) finalBump = "patch";
|
|
4510
|
+
}
|
|
4511
|
+
const nextVersion = finalBump ? Semver.inc(currentVersion, finalBump) : null;
|
|
4512
|
+
return {
|
|
4513
|
+
project,
|
|
4514
|
+
bump: finalBump,
|
|
4515
|
+
currentVersion,
|
|
4516
|
+
nextVersion,
|
|
4517
|
+
projectChanges: projectDiff.projectChanges,
|
|
4518
|
+
collectionChanges: collectionDiff.collectionChanges,
|
|
4519
|
+
fieldChanges: collectionDiff.fieldChanges,
|
|
4520
|
+
assetChanges: assetDiff.assetChanges,
|
|
4521
|
+
entryChanges: entryDiff.entryChanges
|
|
4522
|
+
};
|
|
4523
|
+
}
|
|
4524
|
+
/**
|
|
4525
|
+
* Creates a release by:
|
|
4526
|
+
* 1. Recomputing the diff (stateless)
|
|
4527
|
+
* 2. Merging `work` into `production`
|
|
4528
|
+
* 3. Updating the project version on `production`
|
|
4529
|
+
* 4. Tagging on `production`
|
|
4530
|
+
* 5. Merging `production` back into `work` (fast-forward to sync the version commit)
|
|
4531
|
+
* 6. Switching back to `work`
|
|
4532
|
+
*/
|
|
4533
|
+
async create(props) {
|
|
4534
|
+
createReleaseSchema.parse(props);
|
|
4535
|
+
const projectPath = pathTo.project(props.projectId);
|
|
4536
|
+
const projectFilePath = pathTo.projectFile(props.projectId);
|
|
4537
|
+
const diff = await this.prepare(props);
|
|
4538
|
+
if (!diff.bump || !diff.nextVersion) throw new Error("Cannot create a release: no changes detected since the last full release");
|
|
4539
|
+
const nextVersion = diff.nextVersion;
|
|
4540
|
+
try {
|
|
4541
|
+
await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.production);
|
|
4542
|
+
await this.gitService.merge(projectPath, projectBranchSchema.enum.work);
|
|
4543
|
+
const updatedProjectFile = {
|
|
4544
|
+
...diff.project,
|
|
4545
|
+
version: nextVersion,
|
|
4546
|
+
updated: datetime()
|
|
4547
|
+
};
|
|
4548
|
+
await this.jsonFileService.update(updatedProjectFile, projectFilePath, projectFileSchema);
|
|
4549
|
+
await this.gitService.add(projectPath, [projectFilePath]);
|
|
4550
|
+
await this.gitService.commit(projectPath, {
|
|
4551
|
+
method: "release",
|
|
4552
|
+
reference: {
|
|
4553
|
+
objectType: "project",
|
|
4554
|
+
id: props.projectId
|
|
4555
|
+
}
|
|
4556
|
+
});
|
|
4557
|
+
await this.gitService.tags.create({
|
|
4558
|
+
path: projectPath,
|
|
4559
|
+
message: {
|
|
4560
|
+
type: "release",
|
|
4561
|
+
version: nextVersion
|
|
4562
|
+
}
|
|
4563
|
+
});
|
|
4564
|
+
await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work);
|
|
4565
|
+
await this.gitService.merge(projectPath, projectBranchSchema.enum.production);
|
|
4566
|
+
} catch (error) {
|
|
4567
|
+
await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work).catch(() => {});
|
|
4568
|
+
throw error;
|
|
4569
|
+
}
|
|
4570
|
+
this.logService.info({
|
|
4571
|
+
source: "core",
|
|
4572
|
+
message: `Released version ${nextVersion} (${diff.bump} bump)`
|
|
4573
|
+
});
|
|
4574
|
+
return {
|
|
4575
|
+
version: nextVersion,
|
|
4576
|
+
diff
|
|
4577
|
+
};
|
|
4578
|
+
}
|
|
4579
|
+
/**
|
|
4580
|
+
* Creates a preview release by:
|
|
4581
|
+
* 1. Recomputing the diff (stateless)
|
|
4582
|
+
* 2. Computing the preview version (e.g. 1.1.0-preview.3)
|
|
4583
|
+
* 3. Updating the project version on `work`
|
|
4584
|
+
* 4. Tagging on `work` (no merge into production)
|
|
4585
|
+
*
|
|
4586
|
+
* Preview releases are snapshots of the current work state.
|
|
4587
|
+
* They don't promote to production — only full releases do.
|
|
4588
|
+
*/
|
|
4589
|
+
async createPreview(props) {
|
|
4590
|
+
createPreviewReleaseSchema.parse(props);
|
|
4591
|
+
const projectPath = pathTo.project(props.projectId);
|
|
4592
|
+
const projectFilePath = pathTo.projectFile(props.projectId);
|
|
4593
|
+
const diff = await this.prepare(props);
|
|
4594
|
+
if (!diff.bump || !diff.nextVersion) throw new Error("Cannot create a preview release: no changes detected since the last full release");
|
|
4595
|
+
const previewNumber = await this.countPreviewsSinceLastRelease(projectPath, diff.nextVersion);
|
|
4596
|
+
const previewVersion = `${diff.nextVersion}-preview.${previewNumber + 1}`;
|
|
4597
|
+
try {
|
|
4598
|
+
const updatedProjectFile = {
|
|
4599
|
+
...diff.project,
|
|
4600
|
+
version: previewVersion,
|
|
4601
|
+
updated: datetime()
|
|
4602
|
+
};
|
|
4603
|
+
await this.jsonFileService.update(updatedProjectFile, projectFilePath, projectFileSchema);
|
|
4604
|
+
await this.gitService.add(projectPath, [projectFilePath]);
|
|
4605
|
+
await this.gitService.commit(projectPath, {
|
|
4606
|
+
method: "release",
|
|
4607
|
+
reference: {
|
|
4608
|
+
objectType: "project",
|
|
4609
|
+
id: props.projectId
|
|
4610
|
+
}
|
|
4611
|
+
});
|
|
4612
|
+
await this.gitService.tags.create({
|
|
4613
|
+
path: projectPath,
|
|
4614
|
+
message: {
|
|
4615
|
+
type: "preview",
|
|
4616
|
+
version: previewVersion
|
|
4617
|
+
}
|
|
4618
|
+
});
|
|
4619
|
+
} catch (error) {
|
|
4620
|
+
await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work).catch(() => {});
|
|
4621
|
+
throw error;
|
|
4622
|
+
}
|
|
4623
|
+
this.logService.info({
|
|
4624
|
+
source: "core",
|
|
4625
|
+
message: `Preview released version ${previewVersion} (${diff.bump} bump)`
|
|
4626
|
+
});
|
|
4627
|
+
return {
|
|
4628
|
+
version: previewVersion,
|
|
4629
|
+
diff
|
|
4630
|
+
};
|
|
4631
|
+
}
|
|
4632
|
+
/**
|
|
4633
|
+
* Reads the project file as it exists at a given git ref
|
|
4634
|
+
*/
|
|
4635
|
+
async getProjectAtRef(projectId, projectPath, ref) {
|
|
4636
|
+
try {
|
|
4637
|
+
const content = await this.gitService.getFileContentAtCommit(projectPath, pathTo.projectFile(projectId), ref);
|
|
4638
|
+
return projectFileSchema.parse(JSON.parse(content));
|
|
4639
|
+
} catch {
|
|
4640
|
+
return null;
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
/**
|
|
4644
|
+
* Reads asset metadata files as they exist at a given git ref
|
|
4645
|
+
*/
|
|
4646
|
+
async getAssetsAtRef(projectId, projectPath, ref) {
|
|
4647
|
+
const assetsPath = pathTo.assets(projectId);
|
|
4648
|
+
const fileNames = await this.gitService.listTreeAtCommit(projectPath, assetsPath, ref);
|
|
4649
|
+
const assets = [];
|
|
4650
|
+
for (const fileName of fileNames) {
|
|
4651
|
+
if (!fileName.endsWith(".json")) continue;
|
|
4652
|
+
const assetId = fileName.replace(".json", "");
|
|
4653
|
+
const assetFilePath = pathTo.assetFile(projectId, assetId);
|
|
4654
|
+
try {
|
|
4655
|
+
const content = await this.gitService.getFileContentAtCommit(projectPath, assetFilePath, ref);
|
|
4656
|
+
const assetFile = assetFileSchema.parse(JSON.parse(content));
|
|
4657
|
+
assets.push(assetFile);
|
|
4658
|
+
} catch {
|
|
4659
|
+
this.logService.debug({
|
|
4660
|
+
source: "core",
|
|
4661
|
+
message: `Skipping asset "${fileName}" at ref "${ref}" during release diff`
|
|
4662
|
+
});
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
return assets;
|
|
4666
|
+
}
|
|
4667
|
+
/**
|
|
4668
|
+
* Reads entry files for a single collection as they exist at a given git ref
|
|
4669
|
+
*/
|
|
4670
|
+
async getEntriesAtRef(projectId, projectPath, collectionId, ref) {
|
|
4671
|
+
const entriesPath = pathTo.entries(projectId, collectionId);
|
|
4672
|
+
const fileNames = await this.gitService.listTreeAtCommit(projectPath, entriesPath, ref);
|
|
4673
|
+
const entries = [];
|
|
4674
|
+
for (const fileName of fileNames) {
|
|
4675
|
+
if (!fileName.endsWith(".json") || fileName === "collection.json") continue;
|
|
4676
|
+
const entryId = fileName.replace(".json", "");
|
|
4677
|
+
const entryFilePath = pathTo.entryFile(projectId, collectionId, entryId);
|
|
4678
|
+
try {
|
|
4679
|
+
const content = await this.gitService.getFileContentAtCommit(projectPath, entryFilePath, ref);
|
|
4680
|
+
const entryFile = entryFileSchema.parse(JSON.parse(content));
|
|
4681
|
+
entries.push(entryFile);
|
|
4682
|
+
} catch {
|
|
4683
|
+
this.logService.debug({
|
|
4684
|
+
source: "core",
|
|
4685
|
+
message: `Skipping entry "${fileName}" in collection "${collectionId}" at ref "${ref}" during release diff`
|
|
4686
|
+
});
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
return entries;
|
|
4690
|
+
}
|
|
4691
|
+
/**
|
|
4692
|
+
* Reads collections as they exist at a given git ref (branch or commit)
|
|
4693
|
+
*/
|
|
4694
|
+
async getCollectionsAtRef(projectId, projectPath, ref) {
|
|
4695
|
+
const collectionsPath = pathTo.collections(projectId);
|
|
4696
|
+
const folderNames = await this.gitService.listTreeAtCommit(projectPath, collectionsPath, ref);
|
|
4697
|
+
const collections = [];
|
|
4698
|
+
for (const folderName of folderNames) {
|
|
4699
|
+
const collectionFilePath = pathTo.collectionFile(projectId, folderName);
|
|
4700
|
+
try {
|
|
4701
|
+
const content = await this.gitService.getFileContentAtCommit(projectPath, collectionFilePath, ref);
|
|
4702
|
+
const collectionFile = collectionFileSchema.parse(JSON.parse(content));
|
|
4703
|
+
collections.push(collectionFile);
|
|
4704
|
+
} catch {
|
|
4705
|
+
this.logService.debug({
|
|
4706
|
+
source: "core",
|
|
4707
|
+
message: `Skipping folder "${folderName}" at ref "${ref}" during release diff`
|
|
4708
|
+
});
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
return collections;
|
|
4712
|
+
}
|
|
4713
|
+
/**
|
|
4714
|
+
* Checks if there are any commits between two refs
|
|
4715
|
+
*/
|
|
4716
|
+
async hasCommitsBetween(projectPath, from, to) {
|
|
4717
|
+
try {
|
|
4718
|
+
return (await this.gitService.log(projectPath, { between: {
|
|
4719
|
+
from,
|
|
4720
|
+
to
|
|
4721
|
+
} })).length > 0;
|
|
4722
|
+
} catch {
|
|
4723
|
+
return true;
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
/**
|
|
4727
|
+
* Diffs two sets of collections and returns all changes with the computed bump level.
|
|
4728
|
+
*
|
|
4729
|
+
* Always collects all changes so they can be displayed to the user.
|
|
4730
|
+
*/
|
|
4731
|
+
diffCollections(currentCollections, productionCollections) {
|
|
4732
|
+
const collectionChanges = [];
|
|
4733
|
+
const fieldChanges = [];
|
|
4734
|
+
let highestBump = null;
|
|
4735
|
+
const currentById = new Map(currentCollections.map((c) => [c.id, c]));
|
|
4736
|
+
const productionById = new Map(productionCollections.map((c) => [c.id, c]));
|
|
4737
|
+
for (const [id] of productionById) if (!currentById.has(id)) {
|
|
4738
|
+
collectionChanges.push({
|
|
4739
|
+
collectionId: id,
|
|
4740
|
+
changeType: "deleted",
|
|
4741
|
+
bump: "major"
|
|
4742
|
+
});
|
|
4743
|
+
highestBump = "major";
|
|
4744
|
+
}
|
|
4745
|
+
for (const [id] of currentById) if (!productionById.has(id)) {
|
|
4746
|
+
collectionChanges.push({
|
|
4747
|
+
collectionId: id,
|
|
4748
|
+
changeType: "added",
|
|
4749
|
+
bump: "minor"
|
|
4750
|
+
});
|
|
4751
|
+
highestBump = this.higherBump(highestBump, "minor");
|
|
4752
|
+
}
|
|
4753
|
+
for (const [id, currentCollection] of currentById) {
|
|
4754
|
+
const productionCollection = productionById.get(id);
|
|
4755
|
+
if (!productionCollection) continue;
|
|
4756
|
+
const changes = this.diffFieldDefinitions(id, currentCollection.fieldDefinitions, productionCollection.fieldDefinitions);
|
|
4757
|
+
fieldChanges.push(...changes);
|
|
4758
|
+
for (const change of changes) highestBump = this.higherBump(highestBump, change.bump);
|
|
4759
|
+
}
|
|
4760
|
+
return {
|
|
4761
|
+
bump: highestBump,
|
|
4762
|
+
collectionChanges,
|
|
4763
|
+
fieldChanges
|
|
4764
|
+
};
|
|
4765
|
+
}
|
|
4766
|
+
/**
|
|
4767
|
+
* Diffs the project file between current and production.
|
|
4768
|
+
*
|
|
4769
|
+
* Skips immutable/system-managed fields (id, objectType, created, updated, version, coreVersion).
|
|
4770
|
+
*/
|
|
4771
|
+
diffProject(current, production) {
|
|
4772
|
+
const projectChanges = [];
|
|
4773
|
+
if (!production) return {
|
|
4774
|
+
bump: null,
|
|
4775
|
+
projectChanges
|
|
4776
|
+
};
|
|
4777
|
+
let highestBump = null;
|
|
4778
|
+
if (current.settings.language.default !== production.settings.language.default) {
|
|
4779
|
+
projectChanges.push({
|
|
4780
|
+
changeType: "defaultLanguageChanged",
|
|
4781
|
+
bump: "major"
|
|
4782
|
+
});
|
|
4783
|
+
highestBump = "major";
|
|
4784
|
+
}
|
|
4785
|
+
const currentSupported = new Set(current.settings.language.supported);
|
|
4786
|
+
const productionSupported = new Set(production.settings.language.supported);
|
|
4787
|
+
for (const lang of productionSupported) if (!currentSupported.has(lang)) {
|
|
4788
|
+
projectChanges.push({
|
|
4789
|
+
changeType: "supportedLanguageRemoved",
|
|
4790
|
+
bump: "major"
|
|
4791
|
+
});
|
|
4792
|
+
highestBump = "major";
|
|
4793
|
+
break;
|
|
4794
|
+
}
|
|
4795
|
+
for (const lang of currentSupported) if (!productionSupported.has(lang)) {
|
|
4796
|
+
projectChanges.push({
|
|
4797
|
+
changeType: "supportedLanguageAdded",
|
|
4798
|
+
bump: "minor"
|
|
4799
|
+
});
|
|
4800
|
+
highestBump = this.higherBump(highestBump, "minor");
|
|
4801
|
+
break;
|
|
4802
|
+
}
|
|
4803
|
+
if (current.name !== production.name) {
|
|
4804
|
+
projectChanges.push({
|
|
4805
|
+
changeType: "nameChanged",
|
|
4806
|
+
bump: "patch"
|
|
4807
|
+
});
|
|
4808
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4809
|
+
}
|
|
4810
|
+
if (current.description !== production.description) {
|
|
4811
|
+
projectChanges.push({
|
|
4812
|
+
changeType: "descriptionChanged",
|
|
4813
|
+
bump: "patch"
|
|
4814
|
+
});
|
|
4815
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4816
|
+
}
|
|
4817
|
+
return {
|
|
4818
|
+
bump: highestBump,
|
|
4819
|
+
projectChanges
|
|
4820
|
+
};
|
|
4821
|
+
}
|
|
4822
|
+
/**
|
|
4823
|
+
* Diffs two sets of assets and returns all changes with the computed bump level.
|
|
4824
|
+
*/
|
|
4825
|
+
diffAssets(currentAssets, productionAssets) {
|
|
4826
|
+
const assetChanges = [];
|
|
4827
|
+
let highestBump = null;
|
|
4828
|
+
const currentById = new Map(currentAssets.map((a) => [a.id, a]));
|
|
4829
|
+
const productionById = new Map(productionAssets.map((a) => [a.id, a]));
|
|
4830
|
+
for (const [id] of productionById) if (!currentById.has(id)) {
|
|
4831
|
+
assetChanges.push({
|
|
4832
|
+
assetId: id,
|
|
4833
|
+
changeType: "deleted",
|
|
4834
|
+
bump: "major"
|
|
4835
|
+
});
|
|
4836
|
+
highestBump = "major";
|
|
4837
|
+
}
|
|
4838
|
+
for (const [id] of currentById) if (!productionById.has(id)) {
|
|
4839
|
+
assetChanges.push({
|
|
4840
|
+
assetId: id,
|
|
4841
|
+
changeType: "added",
|
|
4842
|
+
bump: "minor"
|
|
4843
|
+
});
|
|
4844
|
+
highestBump = this.higherBump(highestBump, "minor");
|
|
4845
|
+
}
|
|
4846
|
+
for (const [id, current] of currentById) {
|
|
4847
|
+
const production = productionById.get(id);
|
|
4848
|
+
if (!production) continue;
|
|
4849
|
+
if (current.extension !== production.extension || current.mimeType !== production.mimeType || current.size !== production.size) {
|
|
4850
|
+
assetChanges.push({
|
|
4851
|
+
assetId: id,
|
|
4852
|
+
changeType: "binaryChanged",
|
|
4853
|
+
bump: "patch"
|
|
4854
|
+
});
|
|
4855
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4856
|
+
}
|
|
4857
|
+
if (current.name !== production.name || current.description !== production.description) {
|
|
4858
|
+
assetChanges.push({
|
|
4859
|
+
assetId: id,
|
|
4860
|
+
changeType: "metadataChanged",
|
|
4861
|
+
bump: "patch"
|
|
4862
|
+
});
|
|
4863
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
return {
|
|
4867
|
+
bump: highestBump,
|
|
4868
|
+
assetChanges
|
|
4869
|
+
};
|
|
4870
|
+
}
|
|
4871
|
+
/**
|
|
4872
|
+
* Diffs entries across all collections between current and production.
|
|
4873
|
+
*/
|
|
4874
|
+
async diffEntries(projectId, projectPath, allCollectionIds, productionRef) {
|
|
4875
|
+
const entryChanges = [];
|
|
4876
|
+
let highestBump = null;
|
|
4877
|
+
for (const collectionId of allCollectionIds) {
|
|
4878
|
+
const currentEntries = await this.getEntriesAtRef(projectId, projectPath, collectionId, projectBranchSchema.enum.work);
|
|
4879
|
+
const productionEntries = await this.getEntriesAtRef(projectId, projectPath, collectionId, productionRef);
|
|
4880
|
+
const currentById = new Map(currentEntries.map((e) => [e.id, e]));
|
|
4881
|
+
const productionById = new Map(productionEntries.map((e) => [e.id, e]));
|
|
4882
|
+
for (const [id] of productionById) if (!currentById.has(id)) {
|
|
4883
|
+
entryChanges.push({
|
|
4884
|
+
collectionId,
|
|
4885
|
+
entryId: id,
|
|
4886
|
+
changeType: "deleted",
|
|
4887
|
+
bump: "major"
|
|
4888
|
+
});
|
|
4889
|
+
highestBump = "major";
|
|
4890
|
+
}
|
|
4891
|
+
for (const [id] of currentById) if (!productionById.has(id)) {
|
|
4892
|
+
entryChanges.push({
|
|
4893
|
+
collectionId,
|
|
4894
|
+
entryId: id,
|
|
4895
|
+
changeType: "added",
|
|
4896
|
+
bump: "minor"
|
|
4897
|
+
});
|
|
4898
|
+
highestBump = this.higherBump(highestBump, "minor");
|
|
4899
|
+
}
|
|
4900
|
+
for (const [id, current] of currentById) {
|
|
4901
|
+
const production = productionById.get(id);
|
|
4902
|
+
if (!production) continue;
|
|
4903
|
+
if (!isDeepStrictEqual(current.values, production.values)) {
|
|
4904
|
+
entryChanges.push({
|
|
4905
|
+
collectionId,
|
|
4906
|
+
entryId: id,
|
|
4907
|
+
changeType: "modified",
|
|
4908
|
+
bump: "patch"
|
|
4909
|
+
});
|
|
4910
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
return {
|
|
4915
|
+
bump: highestBump,
|
|
4916
|
+
entryChanges
|
|
4917
|
+
};
|
|
4918
|
+
}
|
|
4919
|
+
/**
|
|
4920
|
+
* Diffs field definitions of a single collection.
|
|
4921
|
+
*
|
|
4922
|
+
* Matches fields by UUID and classifies each change.
|
|
4923
|
+
* Always collects all changes so they can be displayed to the user.
|
|
4924
|
+
*/
|
|
4925
|
+
diffFieldDefinitions(collectionId, currentFields, productionFields) {
|
|
4926
|
+
const changes = [];
|
|
4927
|
+
const currentById = new Map(currentFields.map((f) => [f.id, f]));
|
|
4928
|
+
const productionById = new Map(productionFields.map((f) => [f.id, f]));
|
|
4929
|
+
for (const [id, field] of productionById) if (!currentById.has(id)) changes.push({
|
|
4930
|
+
collectionId,
|
|
4931
|
+
fieldId: id,
|
|
4932
|
+
fieldSlug: field.slug,
|
|
4933
|
+
changeType: "deleted",
|
|
4934
|
+
bump: "major"
|
|
4935
|
+
});
|
|
4936
|
+
for (const [id, field] of currentById) if (!productionById.has(id)) changes.push({
|
|
4937
|
+
collectionId,
|
|
4938
|
+
fieldId: id,
|
|
4939
|
+
fieldSlug: field.slug,
|
|
4940
|
+
changeType: "added",
|
|
4941
|
+
bump: "minor"
|
|
4942
|
+
});
|
|
4943
|
+
for (const [id, currentField] of currentById) {
|
|
4944
|
+
const productionField = productionById.get(id);
|
|
4945
|
+
if (!productionField) continue;
|
|
4946
|
+
const fieldChanges = this.diffSingleField(collectionId, currentField, productionField);
|
|
4947
|
+
changes.push(...fieldChanges);
|
|
4948
|
+
}
|
|
4949
|
+
return changes;
|
|
4950
|
+
}
|
|
4951
|
+
/**
|
|
4952
|
+
* Compares two versions of the same field definition and returns all detected changes.
|
|
4953
|
+
*
|
|
4954
|
+
* Collects every change on the field so the full diff can be shown to the user.
|
|
4955
|
+
*/
|
|
4956
|
+
diffSingleField(collectionId, current, production) {
|
|
4957
|
+
const changes = [];
|
|
4958
|
+
const base = {
|
|
4959
|
+
collectionId,
|
|
4960
|
+
fieldId: current.id,
|
|
4961
|
+
fieldSlug: current.slug
|
|
4962
|
+
};
|
|
4963
|
+
if (current.valueType !== production.valueType) changes.push({
|
|
4964
|
+
...base,
|
|
4965
|
+
changeType: "valueTypeChanged",
|
|
4966
|
+
bump: "major"
|
|
4967
|
+
});
|
|
4968
|
+
if (current.fieldType !== production.fieldType) changes.push({
|
|
4969
|
+
...base,
|
|
4970
|
+
changeType: "fieldTypeChanged",
|
|
4971
|
+
bump: "major"
|
|
4972
|
+
});
|
|
4973
|
+
if (current.slug !== production.slug) changes.push({
|
|
4974
|
+
...base,
|
|
4975
|
+
changeType: "slugChanged",
|
|
4976
|
+
bump: "major"
|
|
4977
|
+
});
|
|
4978
|
+
if (this.isMinMaxTightened(current, production)) changes.push({
|
|
4979
|
+
...base,
|
|
4980
|
+
changeType: "minMaxTightened",
|
|
4981
|
+
bump: "major"
|
|
4982
|
+
});
|
|
4983
|
+
if (production.isRequired === true && current.isRequired === false) changes.push({
|
|
4984
|
+
...base,
|
|
4985
|
+
changeType: "isRequiredToNotRequired",
|
|
4986
|
+
bump: "major"
|
|
4987
|
+
});
|
|
4988
|
+
if (production.isUnique === true && current.isUnique === false) changes.push({
|
|
4989
|
+
...base,
|
|
4990
|
+
changeType: "isUniqueToNotUnique",
|
|
4991
|
+
bump: "major"
|
|
4992
|
+
});
|
|
4993
|
+
if (current.fieldType === "entry" && production.fieldType === "entry") {
|
|
4994
|
+
if (!isDeepStrictEqual([...current.ofCollections].sort(), [...production.ofCollections].sort())) changes.push({
|
|
4995
|
+
...base,
|
|
4996
|
+
changeType: "ofCollectionsChanged",
|
|
4997
|
+
bump: "major"
|
|
4998
|
+
});
|
|
4999
|
+
}
|
|
5000
|
+
if (production.isRequired === false && current.isRequired === true) changes.push({
|
|
5001
|
+
...base,
|
|
5002
|
+
changeType: "isNotRequiredToRequired",
|
|
5003
|
+
bump: "minor"
|
|
5004
|
+
});
|
|
5005
|
+
if (production.isUnique === false && current.isUnique === true) changes.push({
|
|
5006
|
+
...base,
|
|
5007
|
+
changeType: "isNotUniqueToUnique",
|
|
5008
|
+
bump: "minor"
|
|
5009
|
+
});
|
|
5010
|
+
if (this.isMinMaxLoosened(current, production)) changes.push({
|
|
5011
|
+
...base,
|
|
5012
|
+
changeType: "minMaxLoosened",
|
|
5013
|
+
bump: "patch"
|
|
5014
|
+
});
|
|
5015
|
+
if (!isDeepStrictEqual(current.label, production.label)) changes.push({
|
|
5016
|
+
...base,
|
|
5017
|
+
changeType: "labelChanged",
|
|
5018
|
+
bump: "patch"
|
|
5019
|
+
});
|
|
5020
|
+
if (!isDeepStrictEqual(current.description, production.description)) changes.push({
|
|
5021
|
+
...base,
|
|
5022
|
+
changeType: "descriptionChanged",
|
|
5023
|
+
bump: "patch"
|
|
5024
|
+
});
|
|
5025
|
+
if ("defaultValue" in current && "defaultValue" in production && !isDeepStrictEqual(current.defaultValue, production.defaultValue)) changes.push({
|
|
5026
|
+
...base,
|
|
5027
|
+
changeType: "defaultValueChanged",
|
|
5028
|
+
bump: "patch"
|
|
5029
|
+
});
|
|
5030
|
+
if (current.inputWidth !== production.inputWidth) changes.push({
|
|
5031
|
+
...base,
|
|
5032
|
+
changeType: "inputWidthChanged",
|
|
5033
|
+
bump: "patch"
|
|
5034
|
+
});
|
|
5035
|
+
if (current.isDisabled !== production.isDisabled) changes.push({
|
|
5036
|
+
...base,
|
|
5037
|
+
changeType: "isDisabledChanged",
|
|
5038
|
+
bump: "patch"
|
|
5039
|
+
});
|
|
5040
|
+
return changes;
|
|
5041
|
+
}
|
|
5042
|
+
/**
|
|
5043
|
+
* Checks if min/max constraints have been tightened.
|
|
5044
|
+
*
|
|
5045
|
+
* Tightening means: new min > old min, or new max < old max.
|
|
5046
|
+
* A null value means no constraint (unbounded).
|
|
5047
|
+
*/
|
|
5048
|
+
isMinMaxTightened(current, production) {
|
|
5049
|
+
const currentMin = this.getMinMax(current, "min");
|
|
5050
|
+
const productionMin = this.getMinMax(production, "min");
|
|
5051
|
+
const currentMax = this.getMinMax(current, "max");
|
|
5052
|
+
const productionMax = this.getMinMax(production, "max");
|
|
5053
|
+
if (currentMin !== null && productionMin === null) return true;
|
|
5054
|
+
if (currentMin !== null && productionMin !== null && currentMin > productionMin) return true;
|
|
5055
|
+
if (currentMax !== null && productionMax === null) return true;
|
|
5056
|
+
if (currentMax !== null && productionMax !== null && currentMax < productionMax) return true;
|
|
5057
|
+
return false;
|
|
5058
|
+
}
|
|
5059
|
+
/**
|
|
5060
|
+
* Checks if min/max constraints have been loosened.
|
|
5061
|
+
*
|
|
5062
|
+
* Loosening means: new min < old min, or new max > old max.
|
|
5063
|
+
*/
|
|
5064
|
+
isMinMaxLoosened(current, production) {
|
|
5065
|
+
const currentMin = this.getMinMax(current, "min");
|
|
5066
|
+
const productionMin = this.getMinMax(production, "min");
|
|
5067
|
+
const currentMax = this.getMinMax(current, "max");
|
|
5068
|
+
const productionMax = this.getMinMax(production, "max");
|
|
5069
|
+
if (currentMin === null && productionMin !== null) return true;
|
|
5070
|
+
if (currentMin !== null && productionMin !== null && currentMin < productionMin) return true;
|
|
5071
|
+
if (currentMax === null && productionMax !== null) return true;
|
|
5072
|
+
if (currentMax !== null && productionMax !== null && currentMax > productionMax) return true;
|
|
5073
|
+
return false;
|
|
5074
|
+
}
|
|
5075
|
+
/**
|
|
5076
|
+
* Safely extracts min or max from a field definition (not all types have it)
|
|
5077
|
+
*/
|
|
5078
|
+
getMinMax(field, prop) {
|
|
5079
|
+
switch (field.fieldType) {
|
|
5080
|
+
case "text":
|
|
5081
|
+
case "textarea":
|
|
5082
|
+
case "number":
|
|
5083
|
+
case "range":
|
|
5084
|
+
case "asset":
|
|
5085
|
+
case "entry": return field[prop];
|
|
5086
|
+
default: return null;
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
/**
|
|
5090
|
+
* Counts existing preview tags for a given base version since the last full release.
|
|
5091
|
+
*/
|
|
5092
|
+
async countPreviewsSinceLastRelease(projectPath, baseVersion) {
|
|
5093
|
+
const tags = await this.gitService.tags.list({ path: projectPath });
|
|
5094
|
+
let count = 0;
|
|
5095
|
+
for (const tag of tags.list) {
|
|
5096
|
+
if (tag.message.type === "upgrade") continue;
|
|
5097
|
+
if (tag.message.type === "release") break;
|
|
5098
|
+
if (tag.message.type === "preview") {
|
|
5099
|
+
if (tag.message.version.split("-")[0] === baseVersion) count++;
|
|
5100
|
+
}
|
|
5101
|
+
}
|
|
5102
|
+
return count;
|
|
5103
|
+
}
|
|
5104
|
+
/**
|
|
5105
|
+
* Returns the higher of two bumps (major > minor > patch)
|
|
5106
|
+
*/
|
|
5107
|
+
higherBump(a, b) {
|
|
5108
|
+
const order = {
|
|
5109
|
+
patch: 0,
|
|
5110
|
+
minor: 1,
|
|
5111
|
+
major: 2
|
|
5112
|
+
};
|
|
5113
|
+
if (a === null) return b;
|
|
5114
|
+
return order[a] >= order[b] ? a : b;
|
|
5115
|
+
}
|
|
5116
|
+
};
|
|
5117
|
+
|
|
3940
5118
|
//#endregion
|
|
3941
5119
|
//#region src/service/UserService.ts
|
|
3942
5120
|
/**
|
|
@@ -3972,7 +5150,7 @@ var UserService = class {
|
|
|
3972
5150
|
setUserSchema.parse(props);
|
|
3973
5151
|
const userFilePath = pathTo.userFile;
|
|
3974
5152
|
const userFile = { ...props };
|
|
3975
|
-
if (userFile.userType ===
|
|
5153
|
+
if (userFile.userType === userTypeSchema.enum.cloud) {}
|
|
3976
5154
|
await this.jsonFileService.update(userFile, userFilePath, userFileSchema);
|
|
3977
5155
|
this.logService.debug({
|
|
3978
5156
|
source: "core",
|
|
@@ -4000,6 +5178,7 @@ var ElekIoCore = class {
|
|
|
4000
5178
|
projectService;
|
|
4001
5179
|
collectionService;
|
|
4002
5180
|
entryService;
|
|
5181
|
+
releaseService;
|
|
4003
5182
|
localApi;
|
|
4004
5183
|
constructor(props) {
|
|
4005
5184
|
this.coreVersion = package_default.version;
|
|
@@ -4012,10 +5191,11 @@ var ElekIoCore = class {
|
|
|
4012
5191
|
this.jsonFileService = new JsonFileService(this.options, this.logService);
|
|
4013
5192
|
this.userService = new UserService(this.logService, this.jsonFileService);
|
|
4014
5193
|
this.gitService = new GitService(this.options, this.logService, this.userService);
|
|
4015
|
-
this.assetService = new AssetService(this.options, this.logService, this.jsonFileService, this.gitService);
|
|
4016
|
-
this.collectionService = new CollectionService(this.options, this.logService, this.jsonFileService, this.gitService);
|
|
4017
|
-
this.entryService = new EntryService(this.options, this.logService, this.jsonFileService, this.gitService, this.collectionService);
|
|
5194
|
+
this.assetService = new AssetService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService);
|
|
5195
|
+
this.collectionService = new CollectionService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService);
|
|
5196
|
+
this.entryService = new EntryService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService, this.collectionService);
|
|
4018
5197
|
this.projectService = new ProjectService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService, this.assetService, this.collectionService, this.entryService);
|
|
5198
|
+
this.releaseService = new ReleaseService(this.options, this.logService, this.gitService, this.jsonFileService, this.projectService);
|
|
4019
5199
|
this.localApi = new LocalApi(this.logService, this.projectService, this.collectionService, this.entryService, this.assetService);
|
|
4020
5200
|
this.logService.info({
|
|
4021
5201
|
source: "core",
|
|
@@ -4075,6 +5255,12 @@ var ElekIoCore = class {
|
|
|
4075
5255
|
return this.entryService;
|
|
4076
5256
|
}
|
|
4077
5257
|
/**
|
|
5258
|
+
* Prepare and create releases
|
|
5259
|
+
*/
|
|
5260
|
+
get releases() {
|
|
5261
|
+
return this.releaseService;
|
|
5262
|
+
}
|
|
5263
|
+
/**
|
|
4078
5264
|
* Allows starting and stopping a REST API
|
|
4079
5265
|
* to allow developers to read local Project data
|
|
4080
5266
|
*/
|