appwrite-utils-cli 1.6.3 → 1.6.4
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/CONFIG_TODO.md +1189 -0
- package/SERVICE_IMPLEMENTATION_REPORT.md +462 -0
- package/dist/cli/commands/configCommands.js +7 -1
- package/dist/collections/attributes.js +102 -30
- package/dist/config/ConfigManager.d.ts +445 -0
- package/dist/config/ConfigManager.js +625 -0
- package/dist/config/index.d.ts +8 -0
- package/dist/config/index.js +7 -0
- package/dist/config/services/ConfigDiscoveryService.d.ts +126 -0
- package/dist/config/services/ConfigDiscoveryService.js +374 -0
- package/dist/config/services/ConfigLoaderService.d.ts +105 -0
- package/dist/config/services/ConfigLoaderService.js +410 -0
- package/dist/config/services/ConfigMergeService.d.ts +208 -0
- package/dist/config/services/ConfigMergeService.js +307 -0
- package/dist/config/services/ConfigValidationService.d.ts +214 -0
- package/dist/config/services/ConfigValidationService.js +310 -0
- package/dist/config/services/SessionAuthService.d.ts +225 -0
- package/dist/config/services/SessionAuthService.js +456 -0
- package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +1 -0
- package/dist/config/services/__tests__/ConfigMergeService.test.js +271 -0
- package/dist/config/services/index.d.ts +13 -0
- package/dist/config/services/index.js +10 -0
- package/dist/interactiveCLI.js +8 -6
- package/dist/main.js +2 -2
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
- package/dist/shared/operationQueue.js +1 -1
- package/dist/utils/ClientFactory.d.ts +87 -0
- package/dist/utils/ClientFactory.js +164 -0
- package/dist/utils/getClientFromConfig.js +4 -3
- package/dist/utils/helperFunctions.d.ts +1 -0
- package/dist/utils/helperFunctions.js +21 -5
- package/dist/utils/yamlConverter.d.ts +2 -2
- package/dist/utils/yamlConverter.js +2 -2
- package/dist/utilsController.d.ts +18 -15
- package/dist/utilsController.js +83 -131
- package/package.json +1 -1
- package/src/cli/commands/configCommands.ts +8 -1
- package/src/collections/attributes.ts +118 -31
- package/src/config/ConfigManager.ts +808 -0
- package/src/config/index.ts +10 -0
- package/src/config/services/ConfigDiscoveryService.ts +463 -0
- package/src/config/services/ConfigLoaderService.ts +560 -0
- package/src/config/services/ConfigMergeService.ts +386 -0
- package/src/config/services/ConfigValidationService.ts +394 -0
- package/src/config/services/SessionAuthService.ts +565 -0
- package/src/config/services/__tests__/ConfigMergeService.test.ts +351 -0
- package/src/config/services/index.ts +29 -0
- package/src/interactiveCLI.ts +9 -7
- package/src/main.ts +2 -2
- package/src/shared/operationQueue.ts +1 -1
- package/src/utils/ClientFactory.ts +186 -0
- package/src/utils/getClientFromConfig.ts +4 -3
- package/src/utils/helperFunctions.ts +27 -7
- package/src/utils/yamlConverter.ts +4 -4
- package/src/utilsController.ts +99 -187
@@ -142,12 +142,22 @@ const normalizeMinMaxValues = (attribute: Attribute): { min?: number; max?: numb
|
|
142
142
|
* This is used when comparing database attributes with config attributes
|
143
143
|
*/
|
144
144
|
const normalizeAttributeForComparison = (attribute: Attribute): Attribute => {
|
145
|
-
|
146
|
-
|
145
|
+
const normalized: any = { ...attribute };
|
146
|
+
|
147
|
+
// Normalize min/max for numeric types
|
148
|
+
if (hasMinMaxProperties(attribute)) {
|
149
|
+
const { min, max } = normalizeMinMaxValues(attribute);
|
150
|
+
normalized.min = min;
|
151
|
+
normalized.max = max;
|
152
|
+
}
|
153
|
+
|
154
|
+
// Remove xdefault if null/undefined to ensure consistent comparison
|
155
|
+
// Appwrite sets xdefault: null for required attributes, but config files omit it
|
156
|
+
if ('xdefault' in normalized && (normalized.xdefault === null || normalized.xdefault === undefined)) {
|
157
|
+
delete normalized.xdefault;
|
147
158
|
}
|
148
159
|
|
149
|
-
|
150
|
-
return { ...(attribute as any), min, max };
|
160
|
+
return normalized;
|
151
161
|
};
|
152
162
|
|
153
163
|
/**
|
@@ -764,6 +774,45 @@ const deleteAndRecreateCollection = async (
|
|
764
774
|
}
|
765
775
|
};
|
766
776
|
|
777
|
+
/**
|
778
|
+
* Get the fields that should be compared for a specific attribute type
|
779
|
+
* Only returns fields that are valid for the given type to avoid false positives
|
780
|
+
*/
|
781
|
+
const getComparableFields = (type: string): string[] => {
|
782
|
+
const baseFields = ["key", "type", "array", "required", "xdefault"];
|
783
|
+
|
784
|
+
switch (type) {
|
785
|
+
case "string":
|
786
|
+
return [...baseFields, "size", "encrypted"];
|
787
|
+
|
788
|
+
case "integer":
|
789
|
+
case "double":
|
790
|
+
case "float":
|
791
|
+
return [...baseFields, "min", "max"];
|
792
|
+
|
793
|
+
case "enum":
|
794
|
+
return [...baseFields, "elements"];
|
795
|
+
|
796
|
+
case "relationship":
|
797
|
+
return [...baseFields, "relationType", "twoWay", "twoWayKey", "onDelete", "relatedCollection"];
|
798
|
+
|
799
|
+
case "boolean":
|
800
|
+
case "datetime":
|
801
|
+
case "email":
|
802
|
+
case "ip":
|
803
|
+
case "url":
|
804
|
+
return baseFields;
|
805
|
+
|
806
|
+
default:
|
807
|
+
// Fallback to all fields for unknown types
|
808
|
+
return [
|
809
|
+
"key", "type", "array", "encrypted", "required", "size",
|
810
|
+
"min", "max", "xdefault", "elements", "relationType",
|
811
|
+
"twoWay", "twoWayKey", "onDelete", "relatedCollection"
|
812
|
+
];
|
813
|
+
}
|
814
|
+
};
|
815
|
+
|
767
816
|
const attributesSame = (
|
768
817
|
databaseAttribute: Attribute,
|
769
818
|
configAttribute: Attribute
|
@@ -772,25 +821,12 @@ const attributesSame = (
|
|
772
821
|
const normalizedDbAttr = normalizeAttributeForComparison(databaseAttribute);
|
773
822
|
const normalizedConfigAttr = normalizeAttributeForComparison(configAttribute);
|
774
823
|
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
"size",
|
782
|
-
"min",
|
783
|
-
"max",
|
784
|
-
"xdefault",
|
785
|
-
"elements",
|
786
|
-
"relationType",
|
787
|
-
"twoWay",
|
788
|
-
"twoWayKey",
|
789
|
-
"onDelete",
|
790
|
-
"relatedCollection",
|
791
|
-
];
|
792
|
-
|
793
|
-
return attributesToCheck.every((attr) => {
|
824
|
+
// Use type-specific field list to avoid false positives from irrelevant fields
|
825
|
+
const attributesToCheck = getComparableFields(normalizedConfigAttr.type);
|
826
|
+
|
827
|
+
const differences: string[] = [];
|
828
|
+
|
829
|
+
const result = attributesToCheck.every((attr) => {
|
794
830
|
// Check if both objects have the attribute
|
795
831
|
const dbHasAttr = attr in normalizedDbAttr;
|
796
832
|
const configHasAttr = attr in normalizedConfigAttr;
|
@@ -810,16 +846,40 @@ const attributesSame = (
|
|
810
846
|
|
811
847
|
// Normalize booleans: treat undefined and false as equivalent
|
812
848
|
if (typeof dbValue === "boolean" || typeof configValue === "boolean") {
|
813
|
-
|
849
|
+
const boolMatch = Boolean(dbValue) === Boolean(configValue);
|
850
|
+
if (!boolMatch) {
|
851
|
+
differences.push(`${attr}: db=${dbValue} config=${configValue}`);
|
852
|
+
}
|
853
|
+
return boolMatch;
|
814
854
|
}
|
815
855
|
// For numeric comparisons, compare numbers if both are numeric-like
|
816
856
|
if (
|
817
857
|
(typeof dbValue === "number" || (typeof dbValue === "string" && dbValue !== "" && !isNaN(Number(dbValue)))) &&
|
818
858
|
(typeof configValue === "number" || (typeof configValue === "string" && configValue !== "" && !isNaN(Number(configValue))))
|
819
859
|
) {
|
820
|
-
|
860
|
+
const numMatch = Number(dbValue) === Number(configValue);
|
861
|
+
if (!numMatch) {
|
862
|
+
differences.push(`${attr}: db=${dbValue} config=${configValue}`);
|
863
|
+
}
|
864
|
+
return numMatch;
|
865
|
+
}
|
866
|
+
|
867
|
+
// For array comparisons (e.g., enum elements), use order-independent equality
|
868
|
+
if (Array.isArray(dbValue) && Array.isArray(configValue)) {
|
869
|
+
const arrayMatch =
|
870
|
+
dbValue.length === configValue.length &&
|
871
|
+
dbValue.every((val) => configValue.includes(val));
|
872
|
+
if (!arrayMatch) {
|
873
|
+
differences.push(`${attr}: db=${JSON.stringify(dbValue)} config=${JSON.stringify(configValue)}`);
|
874
|
+
}
|
875
|
+
return arrayMatch;
|
821
876
|
}
|
822
|
-
|
877
|
+
|
878
|
+
const match = dbValue === configValue;
|
879
|
+
if (!match) {
|
880
|
+
differences.push(`${attr}: db=${JSON.stringify(dbValue)} config=${JSON.stringify(configValue)}`);
|
881
|
+
}
|
882
|
+
return match;
|
823
883
|
}
|
824
884
|
|
825
885
|
// If neither has the attribute, consider it the same
|
@@ -832,23 +892,50 @@ const attributesSame = (
|
|
832
892
|
const dbValue = normalizedDbAttr[attr as keyof typeof normalizedDbAttr];
|
833
893
|
// Consider default-false booleans as equal to missing in config
|
834
894
|
if (typeof dbValue === "boolean") {
|
835
|
-
|
895
|
+
const match = dbValue === false; // missing in config equals false in db
|
896
|
+
if (!match) {
|
897
|
+
differences.push(`${attr}: db=${dbValue} config=<missing>`);
|
898
|
+
}
|
899
|
+
return match;
|
836
900
|
}
|
837
|
-
|
901
|
+
const match = dbValue === undefined || dbValue === null;
|
902
|
+
if (!match) {
|
903
|
+
differences.push(`${attr}: db=${JSON.stringify(dbValue)} config=<missing>`);
|
904
|
+
}
|
905
|
+
return match;
|
838
906
|
}
|
839
907
|
|
840
908
|
if (!dbHasAttr && configHasAttr) {
|
841
909
|
const configValue = normalizedConfigAttr[attr as keyof typeof normalizedConfigAttr];
|
842
910
|
// Consider default-false booleans as equal to missing in db
|
843
911
|
if (typeof configValue === "boolean") {
|
844
|
-
|
912
|
+
const match = configValue === false; // missing in db equals false in config
|
913
|
+
if (!match) {
|
914
|
+
differences.push(`${attr}: db=<missing> config=${configValue}`);
|
915
|
+
}
|
916
|
+
return match;
|
917
|
+
}
|
918
|
+
const match = configValue === undefined || configValue === null;
|
919
|
+
if (!match) {
|
920
|
+
differences.push(`${attr}: db=<missing> config=${JSON.stringify(configValue)}`);
|
845
921
|
}
|
846
|
-
return
|
922
|
+
return match;
|
847
923
|
}
|
848
924
|
|
849
925
|
// If we reach here, the attributes are different
|
926
|
+
differences.push(`${attr}: unexpected comparison state`);
|
850
927
|
return false;
|
851
928
|
});
|
929
|
+
|
930
|
+
// Log differences if any were found
|
931
|
+
if (differences.length > 0) {
|
932
|
+
logger.debug(`Attribute '${normalizedDbAttr.key}' comparison found differences:`, {
|
933
|
+
differences,
|
934
|
+
operation: 'attributesSame'
|
935
|
+
});
|
936
|
+
}
|
937
|
+
|
938
|
+
return result;
|
852
939
|
};
|
853
940
|
|
854
941
|
/**
|
@@ -1318,7 +1405,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1318
1405
|
return true;
|
1319
1406
|
}
|
1320
1407
|
|
1321
|
-
const needsUpdate = !attributesSame(existing, attribute);
|
1408
|
+
const needsUpdate = !attributesSame(existing, parseAttribute(attribute));
|
1322
1409
|
if (needsUpdate) {
|
1323
1410
|
MessageFormatter.info(`🔄 ${attribute.key}`);
|
1324
1411
|
} else {
|