appwrite-utils-cli 1.5.2 → 1.6.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/CHANGELOG.md +199 -0
- package/README.md +251 -29
- package/dist/adapters/AdapterFactory.d.ts +10 -3
- package/dist/adapters/AdapterFactory.js +213 -17
- package/dist/adapters/TablesDBAdapter.js +60 -17
- package/dist/backups/operations/bucketBackup.d.ts +19 -0
- package/dist/backups/operations/bucketBackup.js +197 -0
- package/dist/backups/operations/collectionBackup.d.ts +30 -0
- package/dist/backups/operations/collectionBackup.js +201 -0
- package/dist/backups/operations/comprehensiveBackup.d.ts +25 -0
- package/dist/backups/operations/comprehensiveBackup.js +238 -0
- package/dist/backups/schemas/bucketManifest.d.ts +93 -0
- package/dist/backups/schemas/bucketManifest.js +33 -0
- package/dist/backups/schemas/comprehensiveManifest.d.ts +108 -0
- package/dist/backups/schemas/comprehensiveManifest.js +32 -0
- package/dist/backups/tracking/centralizedTracking.d.ts +34 -0
- package/dist/backups/tracking/centralizedTracking.js +274 -0
- package/dist/cli/commands/configCommands.d.ts +8 -0
- package/dist/cli/commands/configCommands.js +160 -0
- package/dist/cli/commands/databaseCommands.d.ts +13 -0
- package/dist/cli/commands/databaseCommands.js +478 -0
- package/dist/cli/commands/functionCommands.d.ts +7 -0
- package/dist/cli/commands/functionCommands.js +289 -0
- package/dist/cli/commands/schemaCommands.d.ts +7 -0
- package/dist/cli/commands/schemaCommands.js +134 -0
- package/dist/cli/commands/transferCommands.d.ts +5 -0
- package/dist/cli/commands/transferCommands.js +384 -0
- package/dist/collections/attributes.d.ts +5 -4
- package/dist/collections/attributes.js +539 -246
- package/dist/collections/indexes.js +39 -37
- package/dist/collections/methods.d.ts +2 -16
- package/dist/collections/methods.js +90 -538
- package/dist/collections/transferOperations.d.ts +7 -0
- package/dist/collections/transferOperations.js +331 -0
- package/dist/collections/wipeOperations.d.ts +16 -0
- package/dist/collections/wipeOperations.js +328 -0
- package/dist/config/configMigration.d.ts +87 -0
- package/dist/config/configMigration.js +390 -0
- package/dist/config/configValidation.d.ts +66 -0
- package/dist/config/configValidation.js +358 -0
- package/dist/config/yamlConfig.d.ts +455 -1
- package/dist/config/yamlConfig.js +145 -52
- package/dist/databases/methods.js +3 -2
- package/dist/databases/setup.d.ts +1 -2
- package/dist/databases/setup.js +9 -87
- package/dist/examples/yamlTerminologyExample.d.ts +42 -0
- package/dist/examples/yamlTerminologyExample.js +269 -0
- package/dist/functions/deployments.js +11 -10
- package/dist/functions/methods.d.ts +1 -1
- package/dist/functions/methods.js +5 -4
- package/dist/init.js +9 -9
- package/dist/interactiveCLI.d.ts +8 -17
- package/dist/interactiveCLI.js +181 -1172
- package/dist/main.js +364 -21
- package/dist/migrations/afterImportActions.js +22 -30
- package/dist/migrations/appwriteToX.js +71 -25
- package/dist/migrations/dataLoader.js +35 -26
- package/dist/migrations/importController.js +29 -30
- package/dist/migrations/relationships.js +13 -12
- package/dist/migrations/services/ImportOrchestrator.js +16 -19
- package/dist/migrations/transfer.js +46 -46
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +3 -1
- package/dist/migrations/yaml/YamlImportConfigLoader.js +6 -3
- package/dist/migrations/yaml/YamlImportIntegration.d.ts +9 -3
- package/dist/migrations/yaml/YamlImportIntegration.js +22 -11
- package/dist/migrations/yaml/generateImportSchemas.d.ts +14 -1
- package/dist/migrations/yaml/generateImportSchemas.js +736 -7
- package/dist/schemas/authUser.d.ts +1 -1
- package/dist/setupController.js +3 -2
- package/dist/shared/backupMetadataSchema.d.ts +94 -0
- package/dist/shared/backupMetadataSchema.js +38 -0
- package/dist/shared/backupTracking.d.ts +18 -0
- package/dist/shared/backupTracking.js +176 -0
- package/dist/shared/confirmationDialogs.js +15 -15
- package/dist/shared/errorUtils.d.ts +54 -0
- package/dist/shared/errorUtils.js +95 -0
- package/dist/shared/functionManager.js +20 -19
- package/dist/shared/indexManager.js +12 -11
- package/dist/shared/jsonSchemaGenerator.js +10 -26
- package/dist/shared/logging.d.ts +51 -0
- package/dist/shared/logging.js +70 -0
- package/dist/shared/messageFormatter.d.ts +2 -0
- package/dist/shared/messageFormatter.js +10 -0
- package/dist/shared/migrationHelpers.d.ts +6 -16
- package/dist/shared/migrationHelpers.js +24 -21
- package/dist/shared/operationLogger.d.ts +8 -1
- package/dist/shared/operationLogger.js +11 -24
- package/dist/shared/operationQueue.d.ts +28 -1
- package/dist/shared/operationQueue.js +268 -66
- package/dist/shared/operationsTable.d.ts +26 -0
- package/dist/shared/operationsTable.js +286 -0
- package/dist/shared/operationsTableSchema.d.ts +48 -0
- package/dist/shared/operationsTableSchema.js +35 -0
- package/dist/shared/relationshipExtractor.d.ts +56 -0
- package/dist/shared/relationshipExtractor.js +138 -0
- package/dist/shared/schemaGenerator.d.ts +19 -1
- package/dist/shared/schemaGenerator.js +56 -75
- package/dist/storage/backupCompression.d.ts +20 -0
- package/dist/storage/backupCompression.js +67 -0
- package/dist/storage/methods.d.ts +16 -2
- package/dist/storage/methods.js +98 -14
- package/dist/users/methods.js +9 -8
- package/dist/utils/configDiscovery.d.ts +78 -0
- package/dist/utils/configDiscovery.js +430 -0
- package/dist/utils/directoryUtils.d.ts +22 -0
- package/dist/utils/directoryUtils.js +59 -0
- package/dist/utils/getClientFromConfig.d.ts +17 -8
- package/dist/utils/getClientFromConfig.js +162 -17
- package/dist/utils/helperFunctions.d.ts +16 -2
- package/dist/utils/helperFunctions.js +19 -5
- package/dist/utils/loadConfigs.d.ts +34 -9
- package/dist/utils/loadConfigs.js +236 -316
- package/dist/utils/pathResolvers.d.ts +53 -0
- package/dist/utils/pathResolvers.js +72 -0
- package/dist/utils/projectConfig.d.ts +119 -0
- package/dist/utils/projectConfig.js +171 -0
- package/dist/utils/retryFailedPromises.js +4 -2
- package/dist/utils/sessionAuth.d.ts +48 -0
- package/dist/utils/sessionAuth.js +164 -0
- package/dist/utils/sessionPreservationExample.d.ts +1666 -0
- package/dist/utils/sessionPreservationExample.js +101 -0
- package/dist/utils/setupFiles.js +301 -41
- package/dist/utils/typeGuards.d.ts +35 -0
- package/dist/utils/typeGuards.js +57 -0
- package/dist/utils/versionDetection.js +145 -9
- package/dist/utils/yamlConverter.d.ts +53 -3
- package/dist/utils/yamlConverter.js +232 -13
- package/dist/utils/yamlLoader.d.ts +70 -0
- package/dist/utils/yamlLoader.js +263 -0
- package/dist/utilsController.d.ts +36 -3
- package/dist/utilsController.js +186 -56
- package/package.json +12 -2
- package/src/adapters/AdapterFactory.ts +263 -35
- package/src/adapters/TablesDBAdapter.ts +225 -36
- package/src/backups/operations/bucketBackup.ts +277 -0
- package/src/backups/operations/collectionBackup.ts +310 -0
- package/src/backups/operations/comprehensiveBackup.ts +342 -0
- package/src/backups/schemas/bucketManifest.ts +78 -0
- package/src/backups/schemas/comprehensiveManifest.ts +76 -0
- package/src/backups/tracking/centralizedTracking.ts +352 -0
- package/src/cli/commands/configCommands.ts +194 -0
- package/src/cli/commands/databaseCommands.ts +635 -0
- package/src/cli/commands/functionCommands.ts +379 -0
- package/src/cli/commands/schemaCommands.ts +163 -0
- package/src/cli/commands/transferCommands.ts +457 -0
- package/src/collections/attributes.ts +900 -621
- package/src/collections/attributes.ts.backup +1555 -0
- package/src/collections/indexes.ts +116 -114
- package/src/collections/methods.ts +295 -968
- package/src/collections/transferOperations.ts +516 -0
- package/src/collections/wipeOperations.ts +501 -0
- package/src/config/README.md +274 -0
- package/src/config/configMigration.ts +575 -0
- package/src/config/configValidation.ts +445 -0
- package/src/config/yamlConfig.ts +168 -55
- package/src/databases/methods.ts +3 -2
- package/src/databases/setup.ts +11 -138
- package/src/examples/yamlTerminologyExample.ts +341 -0
- package/src/functions/deployments.ts +14 -12
- package/src/functions/methods.ts +11 -11
- package/src/functions/templates/hono-typescript/README.md +286 -0
- package/src/functions/templates/hono-typescript/package.json +26 -0
- package/src/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
- package/src/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
- package/src/functions/templates/hono-typescript/src/app.ts +180 -0
- package/src/functions/templates/hono-typescript/src/context.ts +103 -0
- package/src/functions/templates/hono-typescript/src/index.ts +54 -0
- package/src/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
- package/src/functions/templates/hono-typescript/tsconfig.json +20 -0
- package/src/functions/templates/typescript-node/package.json +2 -1
- package/src/functions/templates/typescript-node/src/context.ts +103 -0
- package/src/functions/templates/typescript-node/src/index.ts +18 -12
- package/src/functions/templates/uv/pyproject.toml +1 -0
- package/src/functions/templates/uv/src/context.py +125 -0
- package/src/functions/templates/uv/src/index.py +35 -5
- package/src/init.ts +9 -11
- package/src/interactiveCLI.ts +278 -1596
- package/src/main.ts +418 -24
- package/src/migrations/afterImportActions.ts +71 -44
- package/src/migrations/appwriteToX.ts +100 -34
- package/src/migrations/dataLoader.ts +48 -34
- package/src/migrations/importController.ts +44 -39
- package/src/migrations/relationships.ts +28 -18
- package/src/migrations/services/ImportOrchestrator.ts +24 -27
- package/src/migrations/transfer.ts +159 -121
- package/src/migrations/yaml/YamlImportConfigLoader.ts +11 -4
- package/src/migrations/yaml/YamlImportIntegration.ts +47 -20
- package/src/migrations/yaml/generateImportSchemas.ts +751 -12
- package/src/setupController.ts +3 -2
- package/src/shared/backupMetadataSchema.ts +93 -0
- package/src/shared/backupTracking.ts +211 -0
- package/src/shared/confirmationDialogs.ts +19 -19
- package/src/shared/errorUtils.ts +110 -0
- package/src/shared/functionManager.ts +21 -20
- package/src/shared/indexManager.ts +12 -11
- package/src/shared/jsonSchemaGenerator.ts +38 -52
- package/src/shared/logging.ts +75 -0
- package/src/shared/messageFormatter.ts +14 -1
- package/src/shared/migrationHelpers.ts +45 -38
- package/src/shared/operationLogger.ts +11 -36
- package/src/shared/operationQueue.ts +322 -93
- package/src/shared/operationsTable.ts +338 -0
- package/src/shared/operationsTableSchema.ts +60 -0
- package/src/shared/relationshipExtractor.ts +214 -0
- package/src/shared/schemaGenerator.ts +179 -219
- package/src/storage/backupCompression.ts +88 -0
- package/src/storage/methods.ts +131 -34
- package/src/users/methods.ts +11 -9
- package/src/utils/configDiscovery.ts +502 -0
- package/src/utils/directoryUtils.ts +61 -0
- package/src/utils/getClientFromConfig.ts +205 -22
- package/src/utils/helperFunctions.ts +23 -5
- package/src/utils/loadConfigs.ts +313 -345
- package/src/utils/pathResolvers.ts +81 -0
- package/src/utils/projectConfig.ts +299 -0
- package/src/utils/retryFailedPromises.ts +4 -2
- package/src/utils/sessionAuth.ts +230 -0
- package/src/utils/setupFiles.ts +322 -54
- package/src/utils/typeGuards.ts +65 -0
- package/src/utils/versionDetection.ts +218 -64
- package/src/utils/yamlConverter.ts +296 -13
- package/src/utils/yamlLoader.ts +364 -0
- package/src/utilsController.ts +314 -110
- package/tests/README.md +497 -0
- package/tests/adapters/AdapterFactory.test.ts +277 -0
- package/tests/integration/syncOperations.test.ts +463 -0
- package/tests/jest.config.js +25 -0
- package/tests/migration/configMigration.test.ts +546 -0
- package/tests/setup.ts +62 -0
- package/tests/testUtils.ts +340 -0
- package/tests/utils/loadConfigs.test.ts +350 -0
- package/tests/validation/configValidation.test.ts +412 -0
- package/src/utils/schemaStrings.ts +0 -517
@@ -4,108 +4,47 @@ import {} from "appwrite-utils";
|
|
4
4
|
import { register } from "tsx/esm/api"; // Import the register function
|
5
5
|
import { pathToFileURL } from "node:url";
|
6
6
|
import chalk from "chalk";
|
7
|
-
import { findYamlConfig, loadYamlConfig } from "../config/yamlConfig.js";
|
7
|
+
import { findYamlConfig, loadYamlConfig, loadYamlConfigWithSession, extractSessionOptionsFromConfig } from "../config/yamlConfig.js";
|
8
8
|
import { detectAppwriteVersionCached, fetchServerVersion, isVersionAtLeast } from "./versionDetection.js";
|
9
|
-
import yaml from "js-yaml";
|
10
|
-
import { z } from "zod";
|
11
9
|
import { MessageFormatter } from "../shared/messageFormatter.js";
|
10
|
+
import { validateCollectionsTablesConfig, reportValidationResults } from "../config/configValidation.js";
|
11
|
+
import { resolveCollectionsDir, resolveTablesDir } from "./pathResolvers.js";
|
12
|
+
import { findAppwriteConfig, findAppwriteConfigTS, findFunctionsDir, discoverCollections, discoverTables, discoverLegacyDirectory } from "./configDiscovery.js";
|
12
13
|
/**
|
13
|
-
*
|
14
|
-
*
|
15
|
-
* @param
|
16
|
-
* @
|
14
|
+
* Helper function to create session preservation options from session data
|
15
|
+
* @param sessionCookie The session cookie string
|
16
|
+
* @param email Optional email associated with the session
|
17
|
+
* @param expiresAt Optional expiration timestamp
|
18
|
+
* @returns SessionPreservationOptions object
|
17
19
|
*/
|
18
|
-
export
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
// Fall back to TypeScript config search
|
26
|
-
const tsConfigPath = findAppwriteConfigTS(dir);
|
27
|
-
if (tsConfigPath) {
|
28
|
-
return path.dirname(tsConfigPath);
|
29
|
-
}
|
30
|
-
return null;
|
31
|
-
};
|
32
|
-
const shouldIgnoreDirectory = (dirName) => {
|
33
|
-
const ignoredDirs = [
|
34
|
-
'node_modules',
|
35
|
-
'dist',
|
36
|
-
'build',
|
37
|
-
'coverage',
|
38
|
-
'.next',
|
39
|
-
'.nuxt',
|
40
|
-
'.cache',
|
41
|
-
'.git',
|
42
|
-
'.svn',
|
43
|
-
'.hg',
|
44
|
-
'__pycache__',
|
45
|
-
'.pytest_cache',
|
46
|
-
'.mypy_cache',
|
47
|
-
'venv',
|
48
|
-
'.venv',
|
49
|
-
'env',
|
50
|
-
'.env',
|
51
|
-
'target',
|
52
|
-
'out',
|
53
|
-
'bin',
|
54
|
-
'obj',
|
55
|
-
'.vs',
|
56
|
-
'.vscode',
|
57
|
-
'.idea',
|
58
|
-
'temp',
|
59
|
-
'tmp',
|
60
|
-
'.tmp',
|
61
|
-
'logs',
|
62
|
-
'log',
|
63
|
-
'.DS_Store',
|
64
|
-
'Thumbs.db'
|
65
|
-
];
|
66
|
-
return ignoredDirs.includes(dirName) ||
|
67
|
-
dirName.startsWith('.git') ||
|
68
|
-
dirName.startsWith('node_modules') ||
|
69
|
-
(dirName.startsWith('.') && dirName !== '.appwrite');
|
70
|
-
};
|
71
|
-
const findAppwriteConfigTS = (dir, depth = 0) => {
|
72
|
-
// Limit search depth to prevent infinite recursion
|
73
|
-
if (depth > 10) {
|
74
|
-
return null;
|
75
|
-
}
|
76
|
-
if (shouldIgnoreDirectory(path.basename(dir))) {
|
77
|
-
return null;
|
78
|
-
}
|
79
|
-
try {
|
80
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
81
|
-
// First check current directory for appwriteConfig.ts
|
82
|
-
for (const entry of entries) {
|
83
|
-
if (entry.isFile() && entry.name === "appwriteConfig.ts") {
|
84
|
-
return path.join(dir, entry.name);
|
85
|
-
}
|
20
|
+
export function createSessionPreservation(sessionCookie, email, expiresAt) {
|
21
|
+
return {
|
22
|
+
sessionCookie,
|
23
|
+
authMethod: "session",
|
24
|
+
sessionMetadata: {
|
25
|
+
...(email && { email }),
|
26
|
+
...(expiresAt && { expiresAt })
|
86
27
|
}
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
if (result)
|
92
|
-
return result;
|
93
|
-
}
|
94
|
-
}
|
95
|
-
}
|
96
|
-
catch (error) {
|
97
|
-
// Ignore directory access errors
|
98
|
-
}
|
99
|
-
return null;
|
100
|
-
};
|
28
|
+
};
|
29
|
+
}
|
30
|
+
// Re-export config discovery functions for backward compatibility
|
31
|
+
export { findAppwriteConfig, findFunctionsDir } from "./configDiscovery.js";
|
101
32
|
/**
|
102
33
|
* Loads the Appwrite configuration and returns both config and the path where it was found.
|
103
34
|
* @param configDir The directory to search for config files.
|
104
|
-
* @
|
35
|
+
* @param options Loading options including validation settings and session preservation.
|
36
|
+
* @returns Object containing the config, path, and validation results.
|
105
37
|
*/
|
106
|
-
export const loadConfigWithPath = async (configDir) => {
|
38
|
+
export const loadConfigWithPath = async (configDir, options = {}) => {
|
39
|
+
const { validate = true, strictMode = false, reportValidation = true } = options;
|
107
40
|
let config = null;
|
108
41
|
let actualConfigPath = null;
|
42
|
+
// Convert session preservation options to YAML format
|
43
|
+
const yamlSessionOptions = options.preserveAuth ? {
|
44
|
+
sessionCookie: options.preserveAuth.sessionCookie,
|
45
|
+
authMethod: options.preserveAuth.authMethod,
|
46
|
+
sessionMetadata: options.preserveAuth.sessionMetadata,
|
47
|
+
} : undefined;
|
109
48
|
// Check if we're given the .appwrite directory directly
|
110
49
|
if (configDir.endsWith('.appwrite')) {
|
111
50
|
// Look for config files directly in this directory
|
@@ -113,7 +52,9 @@ export const loadConfigWithPath = async (configDir) => {
|
|
113
52
|
for (const fileName of possibleYamlFiles) {
|
114
53
|
const yamlPath = path.join(configDir, fileName);
|
115
54
|
if (fs.existsSync(yamlPath)) {
|
116
|
-
config =
|
55
|
+
config = yamlSessionOptions
|
56
|
+
? await loadYamlConfigWithSession(yamlPath, yamlSessionOptions)
|
57
|
+
: await loadYamlConfig(yamlPath);
|
117
58
|
actualConfigPath = yamlPath;
|
118
59
|
break;
|
119
60
|
}
|
@@ -123,7 +64,9 @@ export const loadConfigWithPath = async (configDir) => {
|
|
123
64
|
// Original logic: search for .appwrite directories
|
124
65
|
const yamlConfigPath = findYamlConfig(configDir);
|
125
66
|
if (yamlConfigPath) {
|
126
|
-
config =
|
67
|
+
config = yamlSessionOptions
|
68
|
+
? await loadYamlConfigWithSession(yamlConfigPath, yamlSessionOptions)
|
69
|
+
: await loadYamlConfig(yamlConfigPath);
|
127
70
|
actualConfigPath = yamlConfigPath;
|
128
71
|
}
|
129
72
|
}
|
@@ -150,84 +93,136 @@ export const loadConfigWithPath = async (configDir) => {
|
|
150
93
|
if (!config || !actualConfigPath) {
|
151
94
|
throw new Error("No valid configuration found");
|
152
95
|
}
|
153
|
-
//
|
154
|
-
|
155
|
-
|
156
|
-
const
|
157
|
-
|
158
|
-
|
96
|
+
// Preserve session authentication if provided
|
97
|
+
// This allows maintaining session context when config is reloaded during CLI operations
|
98
|
+
if (options.preserveAuth) {
|
99
|
+
const { sessionCookie, authMethod, sessionMetadata } = options.preserveAuth;
|
100
|
+
// Inject session cookie into the loaded config
|
101
|
+
if (sessionCookie) {
|
102
|
+
config.sessionCookie = sessionCookie;
|
159
103
|
}
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
104
|
+
// Set or override authentication method preference
|
105
|
+
if (authMethod) {
|
106
|
+
config.authMethod = authMethod;
|
107
|
+
}
|
108
|
+
// Merge session metadata (email, expiration, etc.) with existing metadata
|
109
|
+
if (sessionMetadata) {
|
110
|
+
config.sessionMetadata = {
|
111
|
+
...config.sessionMetadata,
|
112
|
+
...sessionMetadata
|
113
|
+
};
|
114
|
+
}
|
115
|
+
// Auto-detect authentication method if not explicitly provided
|
116
|
+
// If we have a session cookie but no auth method specified, prefer session auth
|
117
|
+
if (!authMethod && sessionCookie) {
|
118
|
+
config.authMethod = "session";
|
165
119
|
}
|
166
120
|
}
|
167
|
-
|
168
|
-
// Determine collections directory based on actual config file location and dirName
|
169
|
-
let collectionsDir;
|
121
|
+
// Enhanced dual folder support: Load from BOTH collections/ AND tables/ directories
|
170
122
|
const configFileDir = path.dirname(actualConfigPath);
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
123
|
+
// Look for collections/tables directories in the same directory as the config file
|
124
|
+
const collectionsDir = resolveCollectionsDir(configFileDir);
|
125
|
+
const tablesDir = resolveTablesDir(configFileDir);
|
126
|
+
// Initialize collections array
|
127
|
+
config.collections = [];
|
128
|
+
// Load from collections/ directory first (higher priority)
|
129
|
+
const collectionsResult = await discoverCollections(collectionsDir);
|
130
|
+
config.collections.push(...collectionsResult.collections);
|
131
|
+
// Load from tables/ directory second (lower priority, check for conflicts)
|
132
|
+
const tablesResult = await discoverTables(tablesDir, collectionsResult.loadedNames);
|
133
|
+
config.collections.push(...tablesResult.tables);
|
134
|
+
// Combine conflicts from both discovery operations
|
135
|
+
const allConflicts = [...collectionsResult.conflicts, ...tablesResult.conflicts];
|
136
|
+
// Report conflicts if any
|
137
|
+
if (allConflicts.length > 0) {
|
138
|
+
MessageFormatter.warning(`Found ${allConflicts.length} naming conflicts between collections/ and tables/`, { prefix: "Config" });
|
139
|
+
allConflicts.forEach(conflict => {
|
140
|
+
MessageFormatter.info(` - '${conflict.name}': ${conflict.source1} (used) vs ${conflict.source2} (skipped)`, { prefix: "Config" });
|
141
|
+
});
|
177
142
|
}
|
178
|
-
//
|
179
|
-
if (fs.existsSync(collectionsDir)) {
|
180
|
-
|
143
|
+
// Fallback: If neither directory exists, try legacy single-directory detection
|
144
|
+
if (!fs.existsSync(collectionsDir) && !fs.existsSync(tablesDir)) {
|
145
|
+
// Determine directory (collections or tables) based on server version / API mode
|
146
|
+
let dirName = "collections";
|
181
147
|
try {
|
182
|
-
const
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
const
|
189
|
-
|
190
|
-
|
191
|
-
const collection = loadYamlCollection(filePath);
|
192
|
-
if (collection) {
|
193
|
-
config.collections.push(collection);
|
194
|
-
}
|
195
|
-
continue;
|
196
|
-
}
|
197
|
-
// Handle TypeScript collections
|
198
|
-
if (file.endsWith('.ts')) {
|
199
|
-
const fileUrl = pathToFileURL(filePath).href;
|
200
|
-
const collectionModule = (await import(fileUrl));
|
201
|
-
const collection = collectionModule.default?.default || collectionModule.default || collectionModule;
|
202
|
-
if (collection) {
|
203
|
-
// Ensure importDefs are properly loaded
|
204
|
-
if (collectionModule.importDefs || collection.importDefs) {
|
205
|
-
collection.importDefs = collectionModule.importDefs || collection.importDefs;
|
206
|
-
}
|
207
|
-
config.collections.push(collection);
|
208
|
-
}
|
209
|
-
}
|
148
|
+
const det = await detectAppwriteVersionCached(config.appwriteEndpoint, config.appwriteProject, config.appwriteKey);
|
149
|
+
if (det.apiMode === 'tablesdb' || isVersionAtLeast(det.serverVersion, '1.8.0')) {
|
150
|
+
dirName = 'tables';
|
151
|
+
}
|
152
|
+
else {
|
153
|
+
// Try health version if not provided
|
154
|
+
const ver = await fetchServerVersion(config.appwriteEndpoint);
|
155
|
+
if (isVersionAtLeast(ver || undefined, '1.8.0'))
|
156
|
+
dirName = 'tables';
|
210
157
|
}
|
211
158
|
}
|
212
|
-
|
213
|
-
|
159
|
+
catch { }
|
160
|
+
const legacyItems = await discoverLegacyDirectory(configFileDir, dirName);
|
161
|
+
config.collections.push(...legacyItems);
|
162
|
+
}
|
163
|
+
// Ensure array exists even if empty
|
164
|
+
config.collections = config.collections || [];
|
165
|
+
// Log the final result
|
166
|
+
const allCollections = config.collections || [];
|
167
|
+
const fromCollectionsDir = allCollections.filter((c) => !c._isFromTablesDir).length;
|
168
|
+
const fromTablesDir = allCollections.filter((c) => c._isFromTablesDir).length;
|
169
|
+
const totalLoaded = allCollections.length;
|
170
|
+
if (totalLoaded > 0) {
|
171
|
+
if (fromTablesDir > 0) {
|
172
|
+
MessageFormatter.success(`Successfully loaded ${totalLoaded} items total: ${fromCollectionsDir} from collections/ and ${fromTablesDir} from tables/`, { prefix: "Config" });
|
173
|
+
}
|
174
|
+
else {
|
175
|
+
MessageFormatter.success(`Successfully loaded ${totalLoaded} collections from collections/`, { prefix: "Config" });
|
214
176
|
}
|
215
177
|
}
|
216
|
-
|
178
|
+
// Validate configuration if requested
|
179
|
+
let validation;
|
180
|
+
if (validate) {
|
181
|
+
validation = validateCollectionsTablesConfig(config);
|
182
|
+
// In strict mode, treat warnings as errors
|
183
|
+
if (strictMode && validation.warnings.length > 0) {
|
184
|
+
const strictValidation = {
|
185
|
+
...validation,
|
186
|
+
isValid: false,
|
187
|
+
errors: [...validation.errors, ...validation.warnings.map(w => ({ ...w, severity: "error" }))],
|
188
|
+
warnings: []
|
189
|
+
};
|
190
|
+
validation = strictValidation;
|
191
|
+
}
|
192
|
+
// Report validation results if requested
|
193
|
+
if (reportValidation) {
|
194
|
+
reportValidationResults(validation, { verbose: true });
|
195
|
+
}
|
196
|
+
// Throw error if validation fails in strict mode
|
197
|
+
if (strictMode && !validation.isValid) {
|
198
|
+
throw new Error(`Configuration validation failed in strict mode. Found ${validation.errors.length} validation errors.`);
|
199
|
+
}
|
200
|
+
}
|
201
|
+
return { config, actualConfigPath, validation };
|
217
202
|
};
|
218
203
|
/**
|
219
204
|
* Loads the Appwrite configuration and all collection configurations from a specified directory.
|
220
205
|
* Supports both YAML and TypeScript config formats with backward compatibility.
|
221
206
|
* @param configDir The directory containing the config file and collections folder.
|
207
|
+
* @param options Loading options including validation settings and session preservation.
|
222
208
|
* @returns The loaded Appwrite configuration including collections.
|
223
209
|
*/
|
224
|
-
export const loadConfig = async (configDir) => {
|
210
|
+
export const loadConfig = async (configDir, options = {}) => {
|
211
|
+
const { validate = false, strictMode = false, reportValidation = false } = options;
|
225
212
|
let config = null;
|
226
213
|
let actualConfigPath = null;
|
214
|
+
// Convert session preservation options to YAML format
|
215
|
+
const yamlSessionOptions = options.preserveAuth ? {
|
216
|
+
sessionCookie: options.preserveAuth.sessionCookie,
|
217
|
+
authMethod: options.preserveAuth.authMethod,
|
218
|
+
sessionMetadata: options.preserveAuth.sessionMetadata,
|
219
|
+
} : undefined;
|
227
220
|
// First try to find and load YAML config
|
228
221
|
const yamlConfigPath = findYamlConfig(configDir);
|
229
222
|
if (yamlConfigPath) {
|
230
|
-
config =
|
223
|
+
config = yamlSessionOptions
|
224
|
+
? await loadYamlConfigWithSession(yamlConfigPath, yamlSessionOptions)
|
225
|
+
: await loadYamlConfig(yamlConfigPath);
|
231
226
|
actualConfigPath = yamlConfigPath;
|
232
227
|
}
|
233
228
|
// Fall back to TypeScript config if YAML not found or failed to load
|
@@ -253,186 +248,111 @@ export const loadConfig = async (configDir) => {
|
|
253
248
|
if (!config) {
|
254
249
|
throw new Error("No valid configuration found");
|
255
250
|
}
|
256
|
-
//
|
257
|
-
|
258
|
-
|
259
|
-
const
|
260
|
-
|
261
|
-
|
251
|
+
// Preserve session authentication if provided
|
252
|
+
// This allows maintaining session context when config is reloaded during CLI operations
|
253
|
+
if (options.preserveAuth) {
|
254
|
+
const { sessionCookie, authMethod, sessionMetadata } = options.preserveAuth;
|
255
|
+
// Inject session cookie into the loaded config
|
256
|
+
if (sessionCookie) {
|
257
|
+
config.sessionCookie = sessionCookie;
|
262
258
|
}
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
259
|
+
// Set or override authentication method preference
|
260
|
+
if (authMethod) {
|
261
|
+
config.authMethod = authMethod;
|
262
|
+
}
|
263
|
+
// Merge session metadata (email, expiration, etc.) with existing metadata
|
264
|
+
if (sessionMetadata) {
|
265
|
+
config.sessionMetadata = {
|
266
|
+
...config.sessionMetadata,
|
267
|
+
...sessionMetadata
|
268
|
+
};
|
269
|
+
}
|
270
|
+
// Auto-detect authentication method if not explicitly provided
|
271
|
+
// If we have a session cookie but no auth method specified, prefer session auth
|
272
|
+
if (!authMethod && sessionCookie) {
|
273
|
+
config.authMethod = "session";
|
267
274
|
}
|
268
275
|
}
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
276
|
+
// Enhanced dual folder support: Load from BOTH collections/ AND tables/ directories
|
277
|
+
const configFileDir = actualConfigPath ? path.dirname(actualConfigPath) : configDir;
|
278
|
+
// Look for collections/tables directories in the same directory as the config file
|
279
|
+
const collectionsDir = resolveCollectionsDir(configFileDir);
|
280
|
+
const tablesDir = resolveTablesDir(configFileDir);
|
281
|
+
// Initialize collections array
|
282
|
+
config.collections = [];
|
283
|
+
// Load from collections/ directory first (higher priority)
|
284
|
+
const collectionsResult = await discoverCollections(collectionsDir);
|
285
|
+
config.collections.push(...collectionsResult.collections);
|
286
|
+
// Load from tables/ directory second (lower priority, check for conflicts)
|
287
|
+
const tablesResult = await discoverTables(tablesDir, collectionsResult.loadedNames);
|
288
|
+
config.collections.push(...tablesResult.tables);
|
289
|
+
// Combine conflicts from both discovery operations
|
290
|
+
const allConflicts = [...collectionsResult.conflicts, ...tablesResult.conflicts];
|
291
|
+
// Report conflicts if any
|
292
|
+
if (allConflicts.length > 0) {
|
293
|
+
MessageFormatter.warning(`Found ${allConflicts.length} naming conflicts between collections/ and tables/`, { prefix: "Config" });
|
294
|
+
allConflicts.forEach(conflict => {
|
295
|
+
MessageFormatter.info(` - '${conflict.name}': ${conflict.source1} (used) vs ${conflict.source2} (skipped)`, { prefix: "Config" });
|
296
|
+
});
|
282
297
|
}
|
283
|
-
//
|
284
|
-
if (fs.existsSync(collectionsDir)) {
|
285
|
-
|
298
|
+
// Fallback: If neither directory exists, try legacy single-directory detection
|
299
|
+
if (!fs.existsSync(collectionsDir) && !fs.existsSync(tablesDir)) {
|
300
|
+
// Determine directory (collections or tables) based on server version / API mode
|
301
|
+
let dirName = "collections";
|
286
302
|
try {
|
287
|
-
const
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
296
|
-
const collection = loadYamlCollection(filePath);
|
297
|
-
if (collection) {
|
298
|
-
config.collections.push(collection);
|
299
|
-
}
|
300
|
-
continue;
|
301
|
-
}
|
302
|
-
// Handle TypeScript collections
|
303
|
-
if (file.endsWith('.ts')) {
|
304
|
-
const fileUrl = pathToFileURL(filePath).href;
|
305
|
-
const collectionModule = (await import(fileUrl));
|
306
|
-
const collection = collectionModule.default?.default || collectionModule.default || collectionModule;
|
307
|
-
if (collection) {
|
308
|
-
// Ensure importDefs are properly loaded
|
309
|
-
if (collectionModule.importDefs || collection.importDefs) {
|
310
|
-
collection.importDefs = collectionModule.importDefs || collection.importDefs;
|
311
|
-
}
|
312
|
-
config.collections.push(collection);
|
313
|
-
}
|
314
|
-
}
|
303
|
+
const det = await detectAppwriteVersionCached(config.appwriteEndpoint, config.appwriteProject, config.appwriteKey);
|
304
|
+
if (det.apiMode === 'tablesdb' || isVersionAtLeast(det.serverVersion, '1.8.0')) {
|
305
|
+
dirName = 'tables';
|
306
|
+
}
|
307
|
+
else {
|
308
|
+
const ver = await fetchServerVersion(config.appwriteEndpoint);
|
309
|
+
if (isVersionAtLeast(ver || undefined, '1.8.0'))
|
310
|
+
dirName = 'tables';
|
315
311
|
}
|
316
312
|
}
|
317
|
-
|
318
|
-
|
319
|
-
|
313
|
+
catch { }
|
314
|
+
const legacyItems = await discoverLegacyDirectory(configFileDir, dirName);
|
315
|
+
config.collections.push(...legacyItems);
|
320
316
|
}
|
321
|
-
|
322
|
-
|
317
|
+
// Ensure array exists even if empty
|
318
|
+
config.collections = config.collections || [];
|
319
|
+
// Log the final result
|
320
|
+
const allCollections = config.collections || [];
|
321
|
+
const fromCollectionsDir = allCollections.filter((c) => !c._isFromTablesDir).length;
|
322
|
+
const fromTablesDir = allCollections.filter((c) => c._isFromTablesDir).length;
|
323
|
+
const totalLoaded = allCollections.length;
|
324
|
+
if (totalLoaded > 0) {
|
325
|
+
if (fromTablesDir > 0) {
|
326
|
+
MessageFormatter.success(`Successfully loaded ${totalLoaded} items total: ${fromCollectionsDir} from collections/ and ${fromTablesDir} from tables/`, { prefix: "Config" });
|
327
|
+
}
|
328
|
+
else {
|
329
|
+
MessageFormatter.success(`Successfully loaded ${totalLoaded} collections from collections/`, { prefix: "Config" });
|
330
|
+
}
|
323
331
|
}
|
324
332
|
// Log successful config loading
|
325
333
|
if (actualConfigPath) {
|
326
334
|
MessageFormatter.success(`Loaded config from: ${actualConfigPath}`, { prefix: "Config" });
|
327
335
|
}
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
}
|
347
|
-
const result = findFunctionsDir(path.join(dir, entry.name), depth + 1);
|
348
|
-
if (result)
|
349
|
-
return result;
|
336
|
+
// Validate configuration if requested
|
337
|
+
if (validate) {
|
338
|
+
let validation = validateCollectionsTablesConfig(config);
|
339
|
+
// In strict mode, treat warnings as errors
|
340
|
+
if (strictMode && validation.warnings.length > 0) {
|
341
|
+
validation = {
|
342
|
+
...validation,
|
343
|
+
isValid: false,
|
344
|
+
errors: [...validation.errors, ...validation.warnings.map(w => ({ ...w, severity: "error" }))],
|
345
|
+
warnings: []
|
346
|
+
};
|
347
|
+
}
|
348
|
+
// Report validation results if requested
|
349
|
+
if (reportValidation) {
|
350
|
+
reportValidationResults(validation, { verbose: true });
|
351
|
+
}
|
352
|
+
// Throw error if validation fails in strict mode
|
353
|
+
if (strictMode && !validation.isValid) {
|
354
|
+
throw new Error(`Configuration validation failed in strict mode. Found ${validation.errors.length} validation errors.`);
|
350
355
|
}
|
351
356
|
}
|
352
|
-
|
353
|
-
// Ignore directory access errors
|
354
|
-
}
|
355
|
-
return null;
|
356
|
-
};
|
357
|
-
// YAML Collection Schema
|
358
|
-
const YamlCollectionSchema = z.object({
|
359
|
-
name: z.string(),
|
360
|
-
id: z.string().optional(),
|
361
|
-
documentSecurity: z.boolean().default(false),
|
362
|
-
enabled: z.boolean().default(true),
|
363
|
-
permissions: z.array(z.object({
|
364
|
-
permission: z.string(),
|
365
|
-
target: z.string()
|
366
|
-
})).optional().default([]),
|
367
|
-
attributes: z.array(z.object({
|
368
|
-
key: z.string(),
|
369
|
-
type: z.string(),
|
370
|
-
size: z.number().optional(),
|
371
|
-
required: z.boolean().default(false),
|
372
|
-
array: z.boolean().optional(),
|
373
|
-
default: z.any().optional(),
|
374
|
-
min: z.number().optional(),
|
375
|
-
max: z.number().optional(),
|
376
|
-
elements: z.array(z.string()).optional(),
|
377
|
-
relatedCollection: z.string().optional(),
|
378
|
-
relationType: z.string().optional(),
|
379
|
-
twoWay: z.boolean().optional(),
|
380
|
-
twoWayKey: z.string().optional(),
|
381
|
-
onDelete: z.string().optional(),
|
382
|
-
side: z.string().optional()
|
383
|
-
})).optional().default([]),
|
384
|
-
indexes: z.array(z.object({
|
385
|
-
key: z.string(),
|
386
|
-
type: z.string(),
|
387
|
-
attributes: z.array(z.string()),
|
388
|
-
orders: z.array(z.string()).optional()
|
389
|
-
})).optional().default([]),
|
390
|
-
importDefs: z.array(z.any()).optional().default([])
|
391
|
-
});
|
392
|
-
const loadYamlCollection = (filePath) => {
|
393
|
-
try {
|
394
|
-
const fileContent = fs.readFileSync(filePath, "utf8");
|
395
|
-
const yamlData = yaml.load(fileContent);
|
396
|
-
const parsedCollection = YamlCollectionSchema.parse(yamlData);
|
397
|
-
// Convert YAML collection to CollectionCreate format
|
398
|
-
const collection = {
|
399
|
-
name: parsedCollection.name,
|
400
|
-
$id: parsedCollection.id || parsedCollection.name.toLowerCase().replace(/\s+/g, '_'),
|
401
|
-
documentSecurity: parsedCollection.documentSecurity,
|
402
|
-
enabled: parsedCollection.enabled,
|
403
|
-
$permissions: parsedCollection.permissions.map(p => ({
|
404
|
-
permission: p.permission,
|
405
|
-
target: p.target
|
406
|
-
})),
|
407
|
-
attributes: parsedCollection.attributes.map(attr => ({
|
408
|
-
key: attr.key,
|
409
|
-
type: attr.type,
|
410
|
-
size: attr.size,
|
411
|
-
required: attr.required,
|
412
|
-
array: attr.array,
|
413
|
-
xdefault: attr.default,
|
414
|
-
min: attr.min,
|
415
|
-
max: attr.max,
|
416
|
-
elements: attr.elements,
|
417
|
-
relatedCollection: attr.relatedCollection,
|
418
|
-
relationType: attr.relationType,
|
419
|
-
twoWay: attr.twoWay,
|
420
|
-
twoWayKey: attr.twoWayKey,
|
421
|
-
onDelete: attr.onDelete,
|
422
|
-
side: attr.side
|
423
|
-
})),
|
424
|
-
indexes: parsedCollection.indexes.map(idx => ({
|
425
|
-
key: idx.key,
|
426
|
-
type: idx.type,
|
427
|
-
attributes: idx.attributes,
|
428
|
-
orders: idx.orders
|
429
|
-
})),
|
430
|
-
importDefs: parsedCollection.importDefs
|
431
|
-
};
|
432
|
-
return collection;
|
433
|
-
}
|
434
|
-
catch (error) {
|
435
|
-
console.error(`Error loading YAML collection from ${filePath}:`, error);
|
436
|
-
return null;
|
437
|
-
}
|
357
|
+
return config;
|
438
358
|
};
|