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
@@ -0,0 +1,307 @@
1
+ import { merge, cloneDeep, isPlainObject } from "es-toolkit";
2
+ /**
3
+ * Service responsible for merging configuration from multiple sources with proper priority handling.
4
+ *
5
+ * @description
6
+ * Handles configuration merging with the following priority order (highest to lowest):
7
+ * 1. CLI arguments/overrides (--endpoint, --apiKey, etc.)
8
+ * 2. Session cookie from ~/.appwrite/prefs.json
9
+ * 3. Config file values (YAML/TS/JSON)
10
+ * 4. Environment variables (APPWRITE_ENDPOINT, APPWRITE_PROJECT, etc.)
11
+ *
12
+ * The service ensures proper deep merging of nested objects while preserving array order
13
+ * and preventing undefined/null values from overwriting existing values.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const mergeService = new ConfigMergeService();
18
+ *
19
+ * // Merge session into config
20
+ * const configWithSession = mergeService.mergeSession(baseConfig, sessionInfo);
21
+ *
22
+ * // Apply CLI overrides
23
+ * const finalConfig = mergeService.applyOverrides(configWithSession, {
24
+ * appwriteEndpoint: 'https://custom-endpoint.com/v1',
25
+ * appwriteKey: 'custom-api-key'
26
+ * });
27
+ * ```
28
+ */
29
+ export class ConfigMergeService {
30
+ /**
31
+ * Merges session authentication information into an existing configuration.
32
+ *
33
+ * @description
34
+ * Adds session authentication details to the configuration, setting:
35
+ * - sessionCookie: The authentication cookie from the session
36
+ * - authMethod: Set to "session" to indicate session-based auth
37
+ *
38
+ * The session endpoint and projectId are used for validation but not merged
39
+ * since they should match the config's values.
40
+ *
41
+ * @param config - Base configuration to merge session into
42
+ * @param session - Session authentication information
43
+ * @returns New configuration object with session merged (input config is not mutated)
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const configWithSession = mergeService.mergeSession(config, {
48
+ * projectId: "my-project",
49
+ * endpoint: "https://cloud.appwrite.io/v1",
50
+ * sessionCookie: "eyJhbGc...",
51
+ * email: "user@example.com"
52
+ * });
53
+ * ```
54
+ */
55
+ mergeSession(config, session) {
56
+ // Clone config to avoid mutation
57
+ const merged = cloneDeep(config);
58
+ // Add session authentication (map 'cookie' to 'sessionCookie')
59
+ merged.sessionCookie = session.cookie;
60
+ merged.authMethod = "session";
61
+ // Add session metadata if available
62
+ if (session.email || session.expiresAt) {
63
+ merged.sessionMetadata = {
64
+ email: session.email,
65
+ expiresAt: session.expiresAt,
66
+ };
67
+ }
68
+ return merged;
69
+ }
70
+ /**
71
+ * Applies configuration overrides from CLI arguments or programmatic sources.
72
+ *
73
+ * @description
74
+ * Applies high-priority overrides that take precedence over all other config sources.
75
+ * Only applies values that are explicitly set (not undefined or null).
76
+ *
77
+ * This method is typically used to apply CLI arguments like:
78
+ * - --endpoint
79
+ * - --apiKey
80
+ * - --project
81
+ *
82
+ * @param config - Base configuration to apply overrides to
83
+ * @param overrides - Override values to apply
84
+ * @returns New configuration object with overrides applied (input config is not mutated)
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * const overriddenConfig = mergeService.applyOverrides(config, {
89
+ * appwriteEndpoint: 'https://custom.appwrite.io/v1',
90
+ * appwriteKey: 'my-api-key',
91
+ * authMethod: 'apikey'
92
+ * });
93
+ * ```
94
+ */
95
+ applyOverrides(config, overrides) {
96
+ // Clone config to avoid mutation
97
+ const merged = cloneDeep(config);
98
+ // Apply each override only if it has a value (not undefined/null)
99
+ if (overrides.appwriteEndpoint !== undefined && overrides.appwriteEndpoint !== null) {
100
+ merged.appwriteEndpoint = overrides.appwriteEndpoint;
101
+ }
102
+ if (overrides.appwriteProject !== undefined && overrides.appwriteProject !== null) {
103
+ merged.appwriteProject = overrides.appwriteProject;
104
+ }
105
+ if (overrides.appwriteKey !== undefined && overrides.appwriteKey !== null) {
106
+ merged.appwriteKey = overrides.appwriteKey;
107
+ }
108
+ if (overrides.sessionCookie !== undefined && overrides.sessionCookie !== null) {
109
+ merged.sessionCookie = overrides.sessionCookie;
110
+ }
111
+ if (overrides.authMethod !== undefined && overrides.authMethod !== null) {
112
+ merged.authMethod = overrides.authMethod;
113
+ }
114
+ return merged;
115
+ }
116
+ /**
117
+ * Merges environment variables into the configuration.
118
+ *
119
+ * @description
120
+ * Reads environment variables and merges them into the config with the lowest priority.
121
+ * Only applies environment variables that are set and have non-empty values.
122
+ *
123
+ * Supported environment variables:
124
+ * - APPWRITE_ENDPOINT: Appwrite API endpoint URL
125
+ * - APPWRITE_PROJECT: Appwrite project ID
126
+ * - APPWRITE_API_KEY: API key for authentication
127
+ * - APPWRITE_SESSION_COOKIE: Session cookie for authentication
128
+ *
129
+ * Environment variables are only applied if the corresponding config value is not already set.
130
+ *
131
+ * @param config - Base configuration to merge environment variables into
132
+ * @returns New configuration object with environment variables merged (input config is not mutated)
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * // With env vars: APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
137
+ * const configWithEnv = mergeService.mergeEnvironmentVariables(config);
138
+ * // configWithEnv.appwriteEndpoint will be set from env var if not already set
139
+ * ```
140
+ */
141
+ mergeEnvironmentVariables(config) {
142
+ // Clone config to avoid mutation
143
+ const merged = cloneDeep(config);
144
+ // Read environment variables
145
+ const envEndpoint = process.env.APPWRITE_ENDPOINT;
146
+ const envProject = process.env.APPWRITE_PROJECT;
147
+ const envApiKey = process.env.APPWRITE_API_KEY;
148
+ const envSessionCookie = process.env.APPWRITE_SESSION_COOKIE;
149
+ // Apply environment variables only if not already set in config
150
+ // Environment variables have the lowest priority
151
+ if (envEndpoint && !merged.appwriteEndpoint) {
152
+ merged.appwriteEndpoint = envEndpoint;
153
+ }
154
+ if (envProject && !merged.appwriteProject) {
155
+ merged.appwriteProject = envProject;
156
+ }
157
+ if (envApiKey && !merged.appwriteKey) {
158
+ merged.appwriteKey = envApiKey;
159
+ }
160
+ if (envSessionCookie && !merged.sessionCookie) {
161
+ merged.sessionCookie = envSessionCookie;
162
+ }
163
+ return merged;
164
+ }
165
+ /**
166
+ * Performs a deep merge of multiple partial configuration sources into a complete configuration.
167
+ *
168
+ * @description
169
+ * Merges multiple configuration sources using a deep merge strategy that:
170
+ * - Recursively merges nested objects
171
+ * - Preserves array order (arrays are replaced, not merged)
172
+ * - Skips undefined and null values (they don't overwrite existing values)
173
+ * - Later sources in the array take precedence over earlier ones
174
+ *
175
+ * This method is useful when combining:
176
+ * - Base defaults with file-based config
177
+ * - Multiple configuration files
178
+ * - Config fragments from different sources
179
+ *
180
+ * @param sources - Array of partial configurations to merge (order matters: later sources override earlier ones)
181
+ * @returns Complete merged configuration object
182
+ *
183
+ * @throws {Error} If no valid configuration can be produced from the sources
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * const merged = mergeService.mergeSources([
188
+ * envConfig, // Lowest priority
189
+ * fileConfig, // Medium priority
190
+ * sessionConfig, // Higher priority
191
+ * overrideConfig // Highest priority
192
+ * ]);
193
+ * ```
194
+ */
195
+ mergeSources(sources) {
196
+ // Filter out undefined/null sources
197
+ const validSources = sources.filter((source) => source !== undefined && source !== null);
198
+ if (validSources.length === 0) {
199
+ throw new Error("No valid configuration sources provided for merging");
200
+ }
201
+ // Start with an empty base object
202
+ let merged = {};
203
+ // Merge each source in order (later sources override earlier ones)
204
+ for (const source of validSources) {
205
+ merged = this.deepMergeConfigs(merged, source);
206
+ }
207
+ // Validate that we have the minimum required fields
208
+ if (!merged.appwriteEndpoint || !merged.appwriteProject) {
209
+ throw new Error("Merged configuration is missing required fields: appwriteEndpoint and/or appwriteProject");
210
+ }
211
+ return merged;
212
+ }
213
+ /**
214
+ * Internal helper for deep merging two configuration objects.
215
+ *
216
+ * @description
217
+ * Performs a deep merge of two objects with special handling for:
218
+ * - Plain objects: Recursively merged
219
+ * - Arrays: Target array is replaced by source array (not merged)
220
+ * - Undefined/null values in source: Skipped (don't overwrite target)
221
+ * - Primitive values: Source overwrites target
222
+ *
223
+ * This is used internally by mergeSources but can also be used directly
224
+ * for specific merge operations.
225
+ *
226
+ * @param target - Base configuration object
227
+ * @param source - Configuration to merge into target
228
+ * @returns New deeply merged configuration object
229
+ *
230
+ * @private
231
+ */
232
+ deepMergeConfigs(target, source) {
233
+ // Clone target to avoid mutation
234
+ const result = cloneDeep(target);
235
+ // Iterate through source properties
236
+ for (const key in source) {
237
+ if (!Object.prototype.hasOwnProperty.call(source, key)) {
238
+ continue;
239
+ }
240
+ const sourceValue = source[key];
241
+ const targetValue = result[key];
242
+ // Skip undefined and null values from source
243
+ if (sourceValue === undefined || sourceValue === null) {
244
+ continue;
245
+ }
246
+ // Handle arrays: Replace target array with source array (don't merge)
247
+ if (Array.isArray(sourceValue)) {
248
+ result[key] = cloneDeep(sourceValue);
249
+ continue;
250
+ }
251
+ // Handle plain objects: Recursively merge
252
+ if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
253
+ result[key] = this.deepMergeConfigs(targetValue, sourceValue);
254
+ continue;
255
+ }
256
+ // For all other cases (primitives, etc.), source overwrites target
257
+ result[key] = cloneDeep(sourceValue);
258
+ }
259
+ return result;
260
+ }
261
+ /**
262
+ * Creates a configuration by merging all sources in the correct priority order.
263
+ *
264
+ * @description
265
+ * Convenience method that applies the full configuration merge pipeline:
266
+ * 1. Start with base config (from file)
267
+ * 2. Merge environment variables (lowest priority)
268
+ * 3. Merge session information (if provided)
269
+ * 4. Apply CLI/programmatic overrides (highest priority)
270
+ *
271
+ * This is the recommended way to build a final configuration from all sources.
272
+ *
273
+ * @param baseConfig - Configuration loaded from file (YAML/TS/JSON)
274
+ * @param options - Optional merge options
275
+ * @param options.session - Session authentication to merge
276
+ * @param options.overrides - CLI/programmatic overrides to apply
277
+ * @param options.includeEnv - Whether to include environment variables (default: true)
278
+ * @returns Final merged configuration with all sources applied in priority order
279
+ *
280
+ * @example
281
+ * ```typescript
282
+ * const finalConfig = mergeService.mergeAllSources(fileConfig, {
283
+ * session: sessionInfo,
284
+ * overrides: cliOverrides,
285
+ * includeEnv: true
286
+ * });
287
+ * ```
288
+ */
289
+ mergeAllSources(baseConfig, options = {}) {
290
+ const { session, overrides, includeEnv = true } = options;
291
+ // Start with base config
292
+ let config = cloneDeep(baseConfig);
293
+ // Step 1: Merge environment variables (lowest priority)
294
+ if (includeEnv) {
295
+ config = this.mergeEnvironmentVariables(config);
296
+ }
297
+ // Step 2: Merge session (medium-high priority)
298
+ if (session) {
299
+ config = this.mergeSession(config, session);
300
+ }
301
+ // Step 3: Apply overrides (highest priority)
302
+ if (overrides) {
303
+ config = this.applyOverrides(config, overrides);
304
+ }
305
+ return config;
306
+ }
307
+ }
@@ -0,0 +1,214 @@
1
+ import type { AppwriteConfig } from "appwrite-utils";
2
+ import { type ValidationError as BaseValidationError } from "../configValidation.js";
3
+ /**
4
+ * Re-export types from configValidation for convenience
5
+ */
6
+ export type ValidationError = BaseValidationError;
7
+ /**
8
+ * Warning-level validation issue (non-blocking)
9
+ */
10
+ export interface ValidationWarning extends BaseValidationError {
11
+ severity: "warning";
12
+ }
13
+ /**
14
+ * Complete validation result including errors, warnings, and suggestions
15
+ */
16
+ export interface ValidationResult {
17
+ isValid: boolean;
18
+ errors: ValidationError[];
19
+ warnings: ValidationWarning[];
20
+ suggestions?: ValidationError[];
21
+ }
22
+ /**
23
+ * Options for validation reporting
24
+ */
25
+ export interface ValidationReportOptions {
26
+ /**
27
+ * Include detailed information in the report
28
+ * @default false
29
+ */
30
+ verbose?: boolean;
31
+ /**
32
+ * Suppress console output (only log to file)
33
+ * @default false
34
+ */
35
+ silent?: boolean;
36
+ /**
37
+ * Exit process if validation fails
38
+ * @default false
39
+ */
40
+ exitOnError?: boolean;
41
+ }
42
+ /**
43
+ * Service for validating Appwrite configuration with support for multiple validation modes
44
+ *
45
+ * This service provides centralized configuration validation with:
46
+ * - Standard validation (warnings allowed)
47
+ * - Strict validation (warnings treated as errors)
48
+ * - Detailed error reporting with suggestions
49
+ * - Configurable output verbosity
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const validationService = new ConfigValidationService();
54
+ *
55
+ * // Standard validation
56
+ * const result = validationService.validate(config);
57
+ * if (!result.isValid) {
58
+ * validationService.reportResults(result, { verbose: true });
59
+ * }
60
+ *
61
+ * // Strict validation (warnings become errors)
62
+ * const strictResult = validationService.validateStrict(config);
63
+ * if (!strictResult.isValid) {
64
+ * throw new Error("Configuration validation failed in strict mode");
65
+ * }
66
+ * ```
67
+ */
68
+ export declare class ConfigValidationService {
69
+ /**
70
+ * Validate configuration with standard rules
71
+ *
72
+ * Standard validation allows warnings - the configuration is considered valid
73
+ * even if warnings are present. Only errors cause validation to fail.
74
+ *
75
+ * Validation checks include:
76
+ * - Basic structure validation (required fields, array structure)
77
+ * - Naming conflict detection (collections vs tables)
78
+ * - Database reference validation
79
+ * - Schema consistency validation
80
+ * - Duplicate definition detection
81
+ *
82
+ * @param config - Appwrite configuration to validate
83
+ * @returns Validation result with errors, warnings, and suggestions
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * const result = validationService.validate(config);
88
+ *
89
+ * if (result.isValid) {
90
+ * console.log("Configuration is valid");
91
+ * if (result.warnings.length > 0) {
92
+ * console.log(`Found ${result.warnings.length} warnings`);
93
+ * }
94
+ * } else {
95
+ * console.error(`Configuration has ${result.errors.length} errors`);
96
+ * }
97
+ * ```
98
+ */
99
+ validate(config: AppwriteConfig): ValidationResult;
100
+ /**
101
+ * Validate configuration with strict rules
102
+ *
103
+ * Strict validation treats all warnings as errors. This is useful for:
104
+ * - CI/CD pipelines (fail builds on any issues)
105
+ * - Production deployments (ensure highest quality)
106
+ * - Configuration audits (enforce best practices)
107
+ *
108
+ * All warnings are promoted to errors, so the configuration is only
109
+ * considered valid if there are zero warnings and zero errors.
110
+ *
111
+ * @param config - Appwrite configuration to validate
112
+ * @returns Validation result with promoted warnings as errors
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * const result = validationService.validateStrict(config);
117
+ *
118
+ * if (!result.isValid) {
119
+ * console.error("Configuration failed strict validation");
120
+ * result.errors.forEach(err => {
121
+ * console.error(` - ${err.message}`);
122
+ * });
123
+ * process.exit(1);
124
+ * }
125
+ * ```
126
+ */
127
+ validateStrict(config: AppwriteConfig): ValidationResult;
128
+ /**
129
+ * Report validation results to console with formatted output
130
+ *
131
+ * Provides user-friendly formatting of validation results:
132
+ * - Color-coded output (errors in red, warnings in yellow, etc.)
133
+ * - Detailed error descriptions with suggestions
134
+ * - Affected items listing
135
+ * - Optional verbose mode for additional details
136
+ *
137
+ * @param validation - Validation result to report
138
+ * @param options - Reporting options (verbose, silent, exitOnError)
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const result = validationService.validate(config);
143
+ *
144
+ * // Basic reporting
145
+ * validationService.reportResults(result);
146
+ *
147
+ * // Verbose reporting with all details
148
+ * validationService.reportResults(result, { verbose: true });
149
+ *
150
+ * // Report and exit on error (useful for scripts)
151
+ * validationService.reportResults(result, { exitOnError: true });
152
+ * ```
153
+ */
154
+ reportResults(validation: ValidationResult, options?: ValidationReportOptions): void;
155
+ /**
156
+ * Validate and report in a single call
157
+ *
158
+ * Convenience method that combines validation and reporting.
159
+ * Useful for quick validation checks in CLI commands.
160
+ *
161
+ * @param config - Appwrite configuration to validate
162
+ * @param strict - Use strict validation mode
163
+ * @param reportOptions - Reporting options
164
+ * @returns Validation result
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * // Quick validation with reporting
169
+ * const result = validationService.validateAndReport(config, false, {
170
+ * verbose: true,
171
+ * exitOnError: true
172
+ * });
173
+ * ```
174
+ */
175
+ validateAndReport(config: AppwriteConfig, strict?: boolean, reportOptions?: ValidationReportOptions): ValidationResult;
176
+ /**
177
+ * Check if configuration is valid without detailed reporting
178
+ *
179
+ * Quick validation check that returns a simple boolean.
180
+ * Useful for conditional logic where you don't need detailed error information.
181
+ *
182
+ * @param config - Appwrite configuration to validate
183
+ * @param strict - Use strict validation mode
184
+ * @returns true if configuration is valid, false otherwise
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * if (validationService.isValid(config)) {
189
+ * // Proceed with configuration
190
+ * } else {
191
+ * // Show error and prompt for correction
192
+ * }
193
+ * ```
194
+ */
195
+ isValid(config: AppwriteConfig, strict?: boolean): boolean;
196
+ /**
197
+ * Get a summary of validation results as a formatted string
198
+ *
199
+ * Returns a human-readable summary of validation results suitable for
200
+ * logging or display in UIs.
201
+ *
202
+ * @param validation - Validation result to summarize
203
+ * @returns Formatted summary string
204
+ *
205
+ * @example
206
+ * ```typescript
207
+ * const result = validationService.validate(config);
208
+ * const summary = validationService.getSummary(result);
209
+ * console.log(summary);
210
+ * // Output: "Configuration valid with 2 warnings, 1 suggestion"
211
+ * ```
212
+ */
213
+ getSummary(validation: ValidationResult): string;
214
+ }