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.
Files changed (55) hide show
  1. package/CONFIG_TODO.md +1189 -0
  2. package/SERVICE_IMPLEMENTATION_REPORT.md +462 -0
  3. package/dist/cli/commands/configCommands.js +7 -1
  4. package/dist/collections/attributes.js +102 -30
  5. package/dist/config/ConfigManager.d.ts +445 -0
  6. package/dist/config/ConfigManager.js +625 -0
  7. package/dist/config/index.d.ts +8 -0
  8. package/dist/config/index.js +7 -0
  9. package/dist/config/services/ConfigDiscoveryService.d.ts +126 -0
  10. package/dist/config/services/ConfigDiscoveryService.js +374 -0
  11. package/dist/config/services/ConfigLoaderService.d.ts +105 -0
  12. package/dist/config/services/ConfigLoaderService.js +410 -0
  13. package/dist/config/services/ConfigMergeService.d.ts +208 -0
  14. package/dist/config/services/ConfigMergeService.js +307 -0
  15. package/dist/config/services/ConfigValidationService.d.ts +214 -0
  16. package/dist/config/services/ConfigValidationService.js +310 -0
  17. package/dist/config/services/SessionAuthService.d.ts +225 -0
  18. package/dist/config/services/SessionAuthService.js +456 -0
  19. package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +1 -0
  20. package/dist/config/services/__tests__/ConfigMergeService.test.js +271 -0
  21. package/dist/config/services/index.d.ts +13 -0
  22. package/dist/config/services/index.js +10 -0
  23. package/dist/interactiveCLI.js +8 -6
  24. package/dist/main.js +2 -2
  25. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
  26. package/dist/shared/operationQueue.js +1 -1
  27. package/dist/utils/ClientFactory.d.ts +87 -0
  28. package/dist/utils/ClientFactory.js +164 -0
  29. package/dist/utils/getClientFromConfig.js +4 -3
  30. package/dist/utils/helperFunctions.d.ts +1 -0
  31. package/dist/utils/helperFunctions.js +21 -5
  32. package/dist/utils/yamlConverter.d.ts +2 -2
  33. package/dist/utils/yamlConverter.js +2 -2
  34. package/dist/utilsController.d.ts +18 -15
  35. package/dist/utilsController.js +83 -131
  36. package/package.json +1 -1
  37. package/src/cli/commands/configCommands.ts +8 -1
  38. package/src/collections/attributes.ts +118 -31
  39. package/src/config/ConfigManager.ts +808 -0
  40. package/src/config/index.ts +10 -0
  41. package/src/config/services/ConfigDiscoveryService.ts +463 -0
  42. package/src/config/services/ConfigLoaderService.ts +560 -0
  43. package/src/config/services/ConfigMergeService.ts +386 -0
  44. package/src/config/services/ConfigValidationService.ts +394 -0
  45. package/src/config/services/SessionAuthService.ts +565 -0
  46. package/src/config/services/__tests__/ConfigMergeService.test.ts +351 -0
  47. package/src/config/services/index.ts +29 -0
  48. package/src/interactiveCLI.ts +9 -7
  49. package/src/main.ts +2 -2
  50. package/src/shared/operationQueue.ts +1 -1
  51. package/src/utils/ClientFactory.ts +186 -0
  52. package/src/utils/getClientFromConfig.ts +4 -3
  53. package/src/utils/helperFunctions.ts +27 -7
  54. package/src/utils/yamlConverter.ts +4 -4
  55. 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
- if (!hasMinMaxProperties(attribute)) {
146
- return attribute;
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
- const { min, max } = normalizeMinMaxValues(attribute);
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
- const attributesToCheck = [
776
- "key",
777
- "type",
778
- "array",
779
- "encrypted",
780
- "required",
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
- return Boolean(dbValue) === Boolean(configValue);
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
- return Number(dbValue) === Number(configValue);
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
- return dbValue === configValue;
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
- return dbValue === false; // missing in config equals false in db
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
- return dbValue === undefined || dbValue === null;
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
- return configValue === false; // missing in db equals false in config
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 configValue === undefined || configValue === null;
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 {