appwrite-utils-cli 1.5.2 → 1.6.1
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 +479 -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 +209 -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 +274 -1563
- 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
package/src/utils/loadConfigs.ts
CHANGED
@@ -1,124 +1,95 @@
|
|
1
1
|
import path from "path";
|
2
2
|
import fs from "fs";
|
3
|
-
import { type AppwriteConfig, type Collection, type CollectionCreate } from "appwrite-utils";
|
3
|
+
import { type AppwriteConfig, type Collection, type CollectionCreate, type Table, type TableCreate } 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";
|
8
|
-
import { detectAppwriteVersionCached, fetchServerVersion, isVersionAtLeast } from "./versionDetection.js";
|
9
|
-
import yaml from "js-yaml";
|
10
|
-
import { z } from "zod";
|
7
|
+
import { findYamlConfig, loadYamlConfig, loadYamlConfigWithSession, extractSessionOptionsFromConfig, type YamlSessionOptions } from "../config/yamlConfig.js";
|
8
|
+
import { detectAppwriteVersionCached, fetchServerVersion, isVersionAtLeast } from "./versionDetection.js";
|
11
9
|
import { MessageFormatter } from "../shared/messageFormatter.js";
|
10
|
+
import { validateCollectionsTablesConfig, reportValidationResults, type ValidationResult } from "../config/configValidation.js";
|
11
|
+
import { resolveCollectionsDir, resolveTablesDir } from "./pathResolvers.js";
|
12
|
+
import {
|
13
|
+
findAppwriteConfig,
|
14
|
+
findAppwriteConfigTS,
|
15
|
+
findFunctionsDir,
|
16
|
+
discoverCollections,
|
17
|
+
discoverTables,
|
18
|
+
discoverLegacyDirectory
|
19
|
+
} from "./configDiscovery.js";
|
12
20
|
|
13
21
|
/**
|
14
|
-
*
|
15
|
-
* Priority: 1) YAML configs in .appwrite directories, 2) appwriteConfig.ts files in subdirectories
|
16
|
-
* @param dir The directory to start the search from.
|
17
|
-
* @returns The directory path where the config was found, suitable for passing to loadConfig().
|
22
|
+
* Session authentication preservation options for config loading
|
18
23
|
*/
|
19
|
-
export
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
}
|
26
|
-
|
27
|
-
// Fall back to TypeScript config search
|
28
|
-
const tsConfigPath = findAppwriteConfigTS(dir);
|
29
|
-
if (tsConfigPath) {
|
30
|
-
return path.dirname(tsConfigPath);
|
31
|
-
}
|
32
|
-
|
33
|
-
return null;
|
34
|
-
};
|
35
|
-
|
36
|
-
const shouldIgnoreDirectory = (dirName: string): boolean => {
|
37
|
-
const ignoredDirs = [
|
38
|
-
'node_modules',
|
39
|
-
'dist',
|
40
|
-
'build',
|
41
|
-
'coverage',
|
42
|
-
'.next',
|
43
|
-
'.nuxt',
|
44
|
-
'.cache',
|
45
|
-
'.git',
|
46
|
-
'.svn',
|
47
|
-
'.hg',
|
48
|
-
'__pycache__',
|
49
|
-
'.pytest_cache',
|
50
|
-
'.mypy_cache',
|
51
|
-
'venv',
|
52
|
-
'.venv',
|
53
|
-
'env',
|
54
|
-
'.env',
|
55
|
-
'target',
|
56
|
-
'out',
|
57
|
-
'bin',
|
58
|
-
'obj',
|
59
|
-
'.vs',
|
60
|
-
'.vscode',
|
61
|
-
'.idea',
|
62
|
-
'temp',
|
63
|
-
'tmp',
|
64
|
-
'.tmp',
|
65
|
-
'logs',
|
66
|
-
'log',
|
67
|
-
'.DS_Store',
|
68
|
-
'Thumbs.db'
|
69
|
-
];
|
70
|
-
|
71
|
-
return ignoredDirs.includes(dirName) ||
|
72
|
-
dirName.startsWith('.git') ||
|
73
|
-
dirName.startsWith('node_modules') ||
|
74
|
-
(dirName.startsWith('.') && dirName !== '.appwrite');
|
75
|
-
};
|
24
|
+
export interface SessionPreservationOptions {
|
25
|
+
sessionCookie?: string;
|
26
|
+
authMethod?: "session" | "apikey" | "auto";
|
27
|
+
sessionMetadata?: {
|
28
|
+
email?: string;
|
29
|
+
expiresAt?: string;
|
30
|
+
};
|
31
|
+
}
|
76
32
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
try {
|
88
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
89
|
-
|
90
|
-
// First check current directory for appwriteConfig.ts
|
91
|
-
for (const entry of entries) {
|
92
|
-
if (entry.isFile() && entry.name === "appwriteConfig.ts") {
|
93
|
-
return path.join(dir, entry.name);
|
94
|
-
}
|
95
|
-
}
|
33
|
+
/**
|
34
|
+
* Configuration loading options
|
35
|
+
*/
|
36
|
+
export interface ConfigLoadingOptions {
|
37
|
+
validate?: boolean;
|
38
|
+
strictMode?: boolean;
|
39
|
+
reportValidation?: boolean;
|
40
|
+
preserveAuth?: SessionPreservationOptions;
|
41
|
+
}
|
96
42
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
43
|
+
/**
|
44
|
+
* Helper function to create session preservation options from session data
|
45
|
+
* @param sessionCookie The session cookie string
|
46
|
+
* @param email Optional email associated with the session
|
47
|
+
* @param expiresAt Optional expiration timestamp
|
48
|
+
* @returns SessionPreservationOptions object
|
49
|
+
*/
|
50
|
+
export function createSessionPreservation(
|
51
|
+
sessionCookie: string,
|
52
|
+
email?: string,
|
53
|
+
expiresAt?: string
|
54
|
+
): SessionPreservationOptions {
|
55
|
+
return {
|
56
|
+
sessionCookie,
|
57
|
+
authMethod: "session",
|
58
|
+
sessionMetadata: {
|
59
|
+
...(email && { email }),
|
60
|
+
...(expiresAt && { expiresAt })
|
103
61
|
}
|
104
|
-
}
|
105
|
-
|
106
|
-
}
|
62
|
+
};
|
63
|
+
}
|
107
64
|
|
108
|
-
|
109
|
-
};
|
65
|
+
// Re-export config discovery functions for backward compatibility
|
66
|
+
export { findAppwriteConfig, findFunctionsDir } from "./configDiscovery.js";
|
110
67
|
|
111
68
|
/**
|
112
69
|
* Loads the Appwrite configuration and returns both config and the path where it was found.
|
113
70
|
* @param configDir The directory to search for config files.
|
114
|
-
* @
|
71
|
+
* @param options Loading options including validation settings and session preservation.
|
72
|
+
* @returns Object containing the config, path, and validation results.
|
115
73
|
*/
|
116
74
|
export const loadConfigWithPath = async (
|
117
|
-
configDir: string
|
118
|
-
|
75
|
+
configDir: string,
|
76
|
+
options: ConfigLoadingOptions = {}
|
77
|
+
): Promise<{
|
78
|
+
config: AppwriteConfig;
|
79
|
+
actualConfigPath: string;
|
80
|
+
validation?: ValidationResult;
|
81
|
+
}> => {
|
82
|
+
const { validate = true, strictMode = false, reportValidation = true } = options;
|
119
83
|
let config: AppwriteConfig | null = null;
|
120
84
|
let actualConfigPath: string | null = null;
|
121
85
|
|
86
|
+
// Convert session preservation options to YAML format
|
87
|
+
const yamlSessionOptions: YamlSessionOptions | undefined = options.preserveAuth ? {
|
88
|
+
sessionCookie: options.preserveAuth.sessionCookie,
|
89
|
+
authMethod: options.preserveAuth.authMethod,
|
90
|
+
sessionMetadata: options.preserveAuth.sessionMetadata,
|
91
|
+
} : undefined;
|
92
|
+
|
122
93
|
// Check if we're given the .appwrite directory directly
|
123
94
|
if (configDir.endsWith('.appwrite')) {
|
124
95
|
// Look for config files directly in this directory
|
@@ -126,7 +97,9 @@ export const loadConfigWithPath = async (
|
|
126
97
|
for (const fileName of possibleYamlFiles) {
|
127
98
|
const yamlPath = path.join(configDir, fileName);
|
128
99
|
if (fs.existsSync(yamlPath)) {
|
129
|
-
config =
|
100
|
+
config = yamlSessionOptions
|
101
|
+
? await loadYamlConfigWithSession(yamlPath, yamlSessionOptions)
|
102
|
+
: await loadYamlConfig(yamlPath);
|
130
103
|
actualConfigPath = yamlPath;
|
131
104
|
break;
|
132
105
|
}
|
@@ -135,7 +108,9 @@ export const loadConfigWithPath = async (
|
|
135
108
|
// Original logic: search for .appwrite directories
|
136
109
|
const yamlConfigPath = findYamlConfig(configDir);
|
137
110
|
if (yamlConfigPath) {
|
138
|
-
config =
|
111
|
+
config = yamlSessionOptions
|
112
|
+
? await loadYamlConfigWithSession(yamlConfigPath, yamlSessionOptions)
|
113
|
+
: await loadYamlConfig(yamlConfigPath);
|
139
114
|
actualConfigPath = yamlConfigPath;
|
140
115
|
}
|
141
116
|
}
|
@@ -166,90 +141,158 @@ export const loadConfigWithPath = async (
|
|
166
141
|
throw new Error("No valid configuration found");
|
167
142
|
}
|
168
143
|
|
169
|
-
//
|
170
|
-
|
171
|
-
|
172
|
-
const
|
173
|
-
if (det.apiMode === 'tablesdb' || isVersionAtLeast(det.serverVersion, '1.8.0')) {
|
174
|
-
dirName = 'tables';
|
175
|
-
} else {
|
176
|
-
// Try health version if not provided
|
177
|
-
const ver = await fetchServerVersion(config.appwriteEndpoint);
|
178
|
-
if (isVersionAtLeast(ver || undefined, '1.8.0')) dirName = 'tables';
|
179
|
-
}
|
180
|
-
} catch {}
|
181
|
-
|
182
|
-
// Determine collections directory based on actual config file location and dirName
|
183
|
-
let collectionsDir: string;
|
184
|
-
const configFileDir = path.dirname(actualConfigPath);
|
185
|
-
collectionsDir = path.join(configFileDir, dirName);
|
186
|
-
// Fallback if not found
|
187
|
-
if (!fs.existsSync(collectionsDir)) {
|
188
|
-
const fallback = path.join(configFileDir, dirName === 'tables' ? 'collections' : 'tables');
|
189
|
-
if (fs.existsSync(fallback)) collectionsDir = fallback;
|
190
|
-
}
|
191
|
-
|
192
|
-
// Load collections if they exist
|
193
|
-
if (fs.existsSync(collectionsDir)) {
|
194
|
-
const unregister = register(); // Register tsx for collections
|
144
|
+
// Preserve session authentication if provided
|
145
|
+
// This allows maintaining session context when config is reloaded during CLI operations
|
146
|
+
if (options.preserveAuth) {
|
147
|
+
const { sessionCookie, authMethod, sessionMetadata } = options.preserveAuth;
|
195
148
|
|
196
|
-
|
197
|
-
|
198
|
-
config.
|
149
|
+
// Inject session cookie into the loaded config
|
150
|
+
if (sessionCookie) {
|
151
|
+
config.sessionCookie = sessionCookie;
|
152
|
+
}
|
199
153
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
154
|
+
// Set or override authentication method preference
|
155
|
+
if (authMethod) {
|
156
|
+
config.authMethod = authMethod;
|
157
|
+
}
|
158
|
+
|
159
|
+
// Merge session metadata (email, expiration, etc.) with existing metadata
|
160
|
+
if (sessionMetadata) {
|
161
|
+
config.sessionMetadata = {
|
162
|
+
...config.sessionMetadata,
|
163
|
+
...sessionMetadata
|
164
|
+
};
|
165
|
+
}
|
166
|
+
|
167
|
+
// Auto-detect authentication method if not explicitly provided
|
168
|
+
// If we have a session cookie but no auth method specified, prefer session auth
|
169
|
+
if (!authMethod && sessionCookie) {
|
170
|
+
config.authMethod = "session";
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
// Enhanced dual folder support: Load from BOTH collections/ AND tables/ directories
|
175
|
+
const configFileDir = path.dirname(actualConfigPath);
|
176
|
+
// Look for collections/tables directories in the same directory as the config file
|
177
|
+
const collectionsDir = resolveCollectionsDir(configFileDir);
|
178
|
+
const tablesDir = resolveTablesDir(configFileDir);
|
179
|
+
|
180
|
+
// Initialize collections array
|
181
|
+
config.collections = [];
|
182
|
+
|
183
|
+
// Load from collections/ directory first (higher priority)
|
184
|
+
const collectionsResult = await discoverCollections(collectionsDir);
|
185
|
+
config.collections.push(...collectionsResult.collections);
|
186
|
+
|
187
|
+
// Load from tables/ directory second (lower priority, check for conflicts)
|
188
|
+
const tablesResult = await discoverTables(tablesDir, collectionsResult.loadedNames);
|
189
|
+
config.collections.push(...tablesResult.tables);
|
190
|
+
|
191
|
+
// Combine conflicts from both discovery operations
|
192
|
+
const allConflicts = [...collectionsResult.conflicts, ...tablesResult.conflicts];
|
193
|
+
|
194
|
+
// Report conflicts if any
|
195
|
+
if (allConflicts.length > 0) {
|
196
|
+
MessageFormatter.warning(`Found ${allConflicts.length} naming conflicts between collections/ and tables/`, { prefix: "Config" });
|
197
|
+
allConflicts.forEach(conflict => {
|
198
|
+
MessageFormatter.info(` - '${conflict.name}': ${conflict.source1} (used) vs ${conflict.source2} (skipped)`, { prefix: "Config" });
|
199
|
+
});
|
200
|
+
}
|
201
|
+
|
202
|
+
// Fallback: If neither directory exists, try legacy single-directory detection
|
203
|
+
if (!fs.existsSync(collectionsDir) && !fs.existsSync(tablesDir)) {
|
204
|
+
// Determine directory (collections or tables) based on server version / API mode
|
205
|
+
let dirName: 'collections' | 'tables' = "collections";
|
206
|
+
try {
|
207
|
+
const det = await detectAppwriteVersionCached(config.appwriteEndpoint, config.appwriteProject, config.appwriteKey);
|
208
|
+
if (det.apiMode === 'tablesdb' || isVersionAtLeast(det.serverVersion, '1.8.0')) {
|
209
|
+
dirName = 'tables';
|
210
|
+
} else {
|
211
|
+
// Try health version if not provided
|
212
|
+
const ver = await fetchServerVersion(config.appwriteEndpoint);
|
213
|
+
if (isVersionAtLeast(ver || undefined, '1.8.0')) dirName = 'tables';
|
228
214
|
}
|
229
|
-
}
|
230
|
-
|
215
|
+
} catch {}
|
216
|
+
|
217
|
+
const legacyItems = await discoverLegacyDirectory(configFileDir, dirName);
|
218
|
+
config.collections.push(...legacyItems);
|
219
|
+
}
|
220
|
+
|
221
|
+
// Ensure array exists even if empty
|
222
|
+
config.collections = config.collections || [];
|
223
|
+
|
224
|
+
// Log the final result
|
225
|
+
const allCollections = config.collections || [];
|
226
|
+
const fromCollectionsDir = allCollections.filter((c: any) => !c._isFromTablesDir).length;
|
227
|
+
const fromTablesDir = allCollections.filter((c: any) => c._isFromTablesDir).length;
|
228
|
+
const totalLoaded = allCollections.length;
|
229
|
+
|
230
|
+
if (totalLoaded > 0) {
|
231
|
+
if (fromTablesDir > 0) {
|
232
|
+
MessageFormatter.success(`Successfully loaded ${totalLoaded} items total: ${fromCollectionsDir} from collections/ and ${fromTablesDir} from tables/`, { prefix: "Config" });
|
233
|
+
} else {
|
234
|
+
MessageFormatter.success(`Successfully loaded ${totalLoaded} collections from collections/`, { prefix: "Config" });
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
238
|
+
// Validate configuration if requested
|
239
|
+
let validation: ValidationResult | undefined;
|
240
|
+
if (validate) {
|
241
|
+
validation = validateCollectionsTablesConfig(config);
|
242
|
+
|
243
|
+
// In strict mode, treat warnings as errors
|
244
|
+
if (strictMode && validation.warnings.length > 0) {
|
245
|
+
const strictValidation = {
|
246
|
+
...validation,
|
247
|
+
isValid: false,
|
248
|
+
errors: [...validation.errors, ...validation.warnings.map(w => ({ ...w, severity: "error" as const }))],
|
249
|
+
warnings: []
|
250
|
+
};
|
251
|
+
validation = strictValidation;
|
252
|
+
}
|
253
|
+
|
254
|
+
// Report validation results if requested
|
255
|
+
if (reportValidation) {
|
256
|
+
reportValidationResults(validation, { verbose: true });
|
257
|
+
}
|
258
|
+
|
259
|
+
// Throw error if validation fails in strict mode
|
260
|
+
if (strictMode && !validation.isValid) {
|
261
|
+
throw new Error(`Configuration validation failed in strict mode. Found ${validation.errors.length} validation errors.`);
|
231
262
|
}
|
232
263
|
}
|
233
264
|
|
234
|
-
return { config, actualConfigPath };
|
265
|
+
return { config, actualConfigPath, validation };
|
235
266
|
};
|
236
267
|
|
237
268
|
/**
|
238
269
|
* Loads the Appwrite configuration and all collection configurations from a specified directory.
|
239
270
|
* Supports both YAML and TypeScript config formats with backward compatibility.
|
240
271
|
* @param configDir The directory containing the config file and collections folder.
|
272
|
+
* @param options Loading options including validation settings and session preservation.
|
241
273
|
* @returns The loaded Appwrite configuration including collections.
|
242
274
|
*/
|
243
275
|
export const loadConfig = async (
|
244
|
-
configDir: string
|
276
|
+
configDir: string,
|
277
|
+
options: ConfigLoadingOptions = {}
|
245
278
|
): Promise<AppwriteConfig> => {
|
279
|
+
const { validate = false, strictMode = false, reportValidation = false } = options;
|
246
280
|
let config: AppwriteConfig | null = null;
|
247
281
|
let actualConfigPath: string | null = null;
|
248
282
|
|
283
|
+
// Convert session preservation options to YAML format
|
284
|
+
const yamlSessionOptions: YamlSessionOptions | undefined = options.preserveAuth ? {
|
285
|
+
sessionCookie: options.preserveAuth.sessionCookie,
|
286
|
+
authMethod: options.preserveAuth.authMethod,
|
287
|
+
sessionMetadata: options.preserveAuth.sessionMetadata,
|
288
|
+
} : undefined;
|
289
|
+
|
249
290
|
// First try to find and load YAML config
|
250
291
|
const yamlConfigPath = findYamlConfig(configDir);
|
251
292
|
if (yamlConfigPath) {
|
252
|
-
config =
|
293
|
+
config = yamlSessionOptions
|
294
|
+
? await loadYamlConfigWithSession(yamlConfigPath, yamlSessionOptions)
|
295
|
+
: await loadYamlConfig(yamlConfigPath);
|
253
296
|
actualConfigPath = yamlConfigPath;
|
254
297
|
}
|
255
298
|
|
@@ -279,203 +322,128 @@ export const loadConfig = async (
|
|
279
322
|
throw new Error("No valid configuration found");
|
280
323
|
}
|
281
324
|
|
282
|
-
//
|
283
|
-
|
284
|
-
|
285
|
-
const
|
286
|
-
if (det.apiMode === 'tablesdb' || isVersionAtLeast(det.serverVersion, '1.8.0')) {
|
287
|
-
dirName2 = 'tables';
|
288
|
-
} else {
|
289
|
-
const ver = await fetchServerVersion(config.appwriteEndpoint);
|
290
|
-
if (isVersionAtLeast(ver || undefined, '1.8.0')) dirName2 = 'tables';
|
291
|
-
}
|
292
|
-
} catch {}
|
293
|
-
|
294
|
-
let collectionsDir: string;
|
295
|
-
if (actualConfigPath) {
|
296
|
-
const configFileDir = path.dirname(actualConfigPath);
|
297
|
-
collectionsDir = path.join(configFileDir, dirName2);
|
298
|
-
} else {
|
299
|
-
collectionsDir = path.join(configDir, dirName2);
|
300
|
-
}
|
301
|
-
if (!fs.existsSync(collectionsDir)) {
|
302
|
-
const fallback = path.join(path.dirname(actualConfigPath || configDir), dirName2 === 'tables' ? 'collections' : 'tables');
|
303
|
-
if (fs.existsSync(fallback)) collectionsDir = fallback;
|
304
|
-
}
|
305
|
-
|
306
|
-
// Load collections if they exist
|
307
|
-
if (fs.existsSync(collectionsDir)) {
|
308
|
-
const unregister = register(); // Register tsx for collections
|
325
|
+
// Preserve session authentication if provided
|
326
|
+
// This allows maintaining session context when config is reloaded during CLI operations
|
327
|
+
if (options.preserveAuth) {
|
328
|
+
const { sessionCookie, authMethod, sessionMetadata } = options.preserveAuth;
|
309
329
|
|
310
|
-
|
311
|
-
|
312
|
-
config.
|
330
|
+
// Inject session cookie into the loaded config
|
331
|
+
if (sessionCookie) {
|
332
|
+
config.sessionCookie = sessionCookie;
|
333
|
+
}
|
313
334
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
}
|
318
|
-
const filePath = path.join(collectionsDir, file);
|
319
|
-
|
320
|
-
// Handle YAML collections
|
321
|
-
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
322
|
-
const collection = loadYamlCollection(filePath);
|
323
|
-
if (collection) {
|
324
|
-
config.collections.push(collection);
|
325
|
-
}
|
326
|
-
continue;
|
327
|
-
}
|
328
|
-
|
329
|
-
// Handle TypeScript collections
|
330
|
-
if (file.endsWith('.ts')) {
|
331
|
-
const fileUrl = pathToFileURL(filePath).href;
|
332
|
-
const collectionModule = (await import(fileUrl));
|
333
|
-
const collection: Collection | undefined = collectionModule.default?.default || collectionModule.default || collectionModule;
|
334
|
-
if (collection) {
|
335
|
-
// Ensure importDefs are properly loaded
|
336
|
-
if (collectionModule.importDefs || collection.importDefs) {
|
337
|
-
collection.importDefs = collectionModule.importDefs || collection.importDefs;
|
338
|
-
}
|
339
|
-
config.collections.push(collection);
|
340
|
-
}
|
341
|
-
}
|
342
|
-
}
|
343
|
-
} finally {
|
344
|
-
unregister(); // Unregister tsx when done
|
335
|
+
// Set or override authentication method preference
|
336
|
+
if (authMethod) {
|
337
|
+
config.authMethod = authMethod;
|
345
338
|
}
|
346
|
-
} else {
|
347
|
-
config.collections = config.collections || [];
|
348
|
-
}
|
349
339
|
|
350
|
-
|
351
|
-
|
352
|
-
|
340
|
+
// Merge session metadata (email, expiration, etc.) with existing metadata
|
341
|
+
if (sessionMetadata) {
|
342
|
+
config.sessionMetadata = {
|
343
|
+
...config.sessionMetadata,
|
344
|
+
...sessionMetadata
|
345
|
+
};
|
346
|
+
}
|
347
|
+
|
348
|
+
// Auto-detect authentication method if not explicitly provided
|
349
|
+
// If we have a session cookie but no auth method specified, prefer session auth
|
350
|
+
if (!authMethod && sessionCookie) {
|
351
|
+
config.authMethod = "session";
|
352
|
+
}
|
353
353
|
}
|
354
354
|
|
355
|
-
|
356
|
-
|
355
|
+
// Enhanced dual folder support: Load from BOTH collections/ AND tables/ directories
|
356
|
+
const configFileDir = actualConfigPath ? path.dirname(actualConfigPath) : configDir;
|
357
|
+
// Look for collections/tables directories in the same directory as the config file
|
358
|
+
const collectionsDir = resolveCollectionsDir(configFileDir);
|
359
|
+
const tablesDir = resolveTablesDir(configFileDir);
|
357
360
|
|
358
|
-
|
359
|
-
|
360
|
-
if (depth > 5) {
|
361
|
-
return null;
|
362
|
-
}
|
361
|
+
// Initialize collections array
|
362
|
+
config.collections = [];
|
363
363
|
|
364
|
-
|
365
|
-
|
364
|
+
// Load from collections/ directory first (higher priority)
|
365
|
+
const collectionsResult = await discoverCollections(collectionsDir);
|
366
|
+
config.collections.push(...collectionsResult.collections);
|
367
|
+
|
368
|
+
// Load from tables/ directory second (lower priority, check for conflicts)
|
369
|
+
const tablesResult = await discoverTables(tablesDir, collectionsResult.loadedNames);
|
370
|
+
config.collections.push(...tablesResult.tables);
|
371
|
+
|
372
|
+
// Combine conflicts from both discovery operations
|
373
|
+
const allConflicts = [...collectionsResult.conflicts, ...tablesResult.conflicts];
|
374
|
+
|
375
|
+
// Report conflicts if any
|
376
|
+
if (allConflicts.length > 0) {
|
377
|
+
MessageFormatter.warning(`Found ${allConflicts.length} naming conflicts between collections/ and tables/`, { prefix: "Config" });
|
378
|
+
allConflicts.forEach(conflict => {
|
379
|
+
MessageFormatter.info(` - '${conflict.name}': ${conflict.source1} (used) vs ${conflict.source2} (skipped)`, { prefix: "Config" });
|
380
|
+
});
|
366
381
|
}
|
367
|
-
|
368
|
-
try {
|
369
|
-
const files = fs.readdirSync(dir, { withFileTypes: true });
|
370
382
|
|
371
|
-
|
372
|
-
|
373
|
-
|
383
|
+
// Fallback: If neither directory exists, try legacy single-directory detection
|
384
|
+
if (!fs.existsSync(collectionsDir) && !fs.existsSync(tablesDir)) {
|
385
|
+
// Determine directory (collections or tables) based on server version / API mode
|
386
|
+
let dirName: 'collections' | 'tables' = "collections";
|
387
|
+
try {
|
388
|
+
const det = await detectAppwriteVersionCached(config.appwriteEndpoint, config.appwriteProject, config.appwriteKey);
|
389
|
+
if (det.apiMode === 'tablesdb' || isVersionAtLeast(det.serverVersion, '1.8.0')) {
|
390
|
+
dirName = 'tables';
|
391
|
+
} else {
|
392
|
+
const ver = await fetchServerVersion(config.appwriteEndpoint);
|
393
|
+
if (isVersionAtLeast(ver || undefined, '1.8.0')) dirName = 'tables';
|
374
394
|
}
|
395
|
+
} catch {}
|
375
396
|
|
376
|
-
|
377
|
-
|
378
|
-
|
397
|
+
const legacyItems = await discoverLegacyDirectory(configFileDir, dirName);
|
398
|
+
config.collections.push(...legacyItems);
|
399
|
+
}
|
379
400
|
|
380
|
-
|
381
|
-
|
401
|
+
// Ensure array exists even if empty
|
402
|
+
config.collections = config.collections || [];
|
403
|
+
|
404
|
+
// Log the final result
|
405
|
+
const allCollections = config.collections || [];
|
406
|
+
const fromCollectionsDir = allCollections.filter((c: any) => !c._isFromTablesDir).length;
|
407
|
+
const fromTablesDir = allCollections.filter((c: any) => c._isFromTablesDir).length;
|
408
|
+
const totalLoaded = allCollections.length;
|
409
|
+
|
410
|
+
if (totalLoaded > 0) {
|
411
|
+
if (fromTablesDir > 0) {
|
412
|
+
MessageFormatter.success(`Successfully loaded ${totalLoaded} items total: ${fromCollectionsDir} from collections/ and ${fromTablesDir} from tables/`, { prefix: "Config" });
|
413
|
+
} else {
|
414
|
+
MessageFormatter.success(`Successfully loaded ${totalLoaded} collections from collections/`, { prefix: "Config" });
|
382
415
|
}
|
383
|
-
} catch (error) {
|
384
|
-
// Ignore directory access errors
|
385
416
|
}
|
386
417
|
|
387
|
-
|
388
|
-
|
418
|
+
// Log successful config loading
|
419
|
+
if (actualConfigPath) {
|
420
|
+
MessageFormatter.success(`Loaded config from: ${actualConfigPath}`, { prefix: "Config" });
|
421
|
+
}
|
389
422
|
|
390
|
-
//
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
relatedCollection: z.string().optional(),
|
414
|
-
relationType: z.string().optional(),
|
415
|
-
twoWay: z.boolean().optional(),
|
416
|
-
twoWayKey: z.string().optional(),
|
417
|
-
onDelete: z.string().optional(),
|
418
|
-
side: z.string().optional()
|
419
|
-
})
|
420
|
-
).optional().default([]),
|
421
|
-
indexes: z.array(
|
422
|
-
z.object({
|
423
|
-
key: z.string(),
|
424
|
-
type: z.string(),
|
425
|
-
attributes: z.array(z.string()),
|
426
|
-
orders: z.array(z.string()).optional()
|
427
|
-
})
|
428
|
-
).optional().default([]),
|
429
|
-
importDefs: z.array(z.any()).optional().default([])
|
430
|
-
});
|
431
|
-
|
432
|
-
type YamlCollection = z.infer<typeof YamlCollectionSchema>;
|
433
|
-
|
434
|
-
const loadYamlCollection = (filePath: string): CollectionCreate | null => {
|
435
|
-
try {
|
436
|
-
const fileContent = fs.readFileSync(filePath, "utf8");
|
437
|
-
const yamlData = yaml.load(fileContent) as unknown;
|
438
|
-
const parsedCollection = YamlCollectionSchema.parse(yamlData);
|
439
|
-
|
440
|
-
// Convert YAML collection to CollectionCreate format
|
441
|
-
const collection: CollectionCreate = {
|
442
|
-
name: parsedCollection.name,
|
443
|
-
$id: parsedCollection.id || parsedCollection.name.toLowerCase().replace(/\s+/g, '_'),
|
444
|
-
documentSecurity: parsedCollection.documentSecurity,
|
445
|
-
enabled: parsedCollection.enabled,
|
446
|
-
$permissions: parsedCollection.permissions.map(p => ({
|
447
|
-
permission: p.permission as any,
|
448
|
-
target: p.target
|
449
|
-
})),
|
450
|
-
attributes: parsedCollection.attributes.map(attr => ({
|
451
|
-
key: attr.key,
|
452
|
-
type: attr.type as any,
|
453
|
-
size: attr.size,
|
454
|
-
required: attr.required,
|
455
|
-
array: attr.array,
|
456
|
-
xdefault: attr.default,
|
457
|
-
min: attr.min,
|
458
|
-
max: attr.max,
|
459
|
-
elements: attr.elements,
|
460
|
-
relatedCollection: attr.relatedCollection,
|
461
|
-
relationType: attr.relationType as any,
|
462
|
-
twoWay: attr.twoWay,
|
463
|
-
twoWayKey: attr.twoWayKey,
|
464
|
-
onDelete: attr.onDelete as any,
|
465
|
-
side: attr.side as any
|
466
|
-
})),
|
467
|
-
indexes: parsedCollection.indexes.map(idx => ({
|
468
|
-
key: idx.key,
|
469
|
-
type: idx.type as any,
|
470
|
-
attributes: idx.attributes,
|
471
|
-
orders: idx.orders as any
|
472
|
-
})),
|
473
|
-
importDefs: parsedCollection.importDefs
|
474
|
-
};
|
475
|
-
|
476
|
-
return collection;
|
477
|
-
} catch (error) {
|
478
|
-
console.error(`Error loading YAML collection from ${filePath}:`, error);
|
479
|
-
return null;
|
423
|
+
// Validate configuration if requested
|
424
|
+
if (validate) {
|
425
|
+
let validation = validateCollectionsTablesConfig(config);
|
426
|
+
|
427
|
+
// In strict mode, treat warnings as errors
|
428
|
+
if (strictMode && validation.warnings.length > 0) {
|
429
|
+
validation = {
|
430
|
+
...validation,
|
431
|
+
isValid: false,
|
432
|
+
errors: [...validation.errors, ...validation.warnings.map(w => ({ ...w, severity: "error" as const }))],
|
433
|
+
warnings: []
|
434
|
+
};
|
435
|
+
}
|
436
|
+
|
437
|
+
// Report validation results if requested
|
438
|
+
if (reportValidation) {
|
439
|
+
reportValidationResults(validation, { verbose: true });
|
440
|
+
}
|
441
|
+
|
442
|
+
// Throw error if validation fails in strict mode
|
443
|
+
if (strictMode && !validation.isValid) {
|
444
|
+
throw new Error(`Configuration validation failed in strict mode. Found ${validation.errors.length} validation errors.`);
|
445
|
+
}
|
480
446
|
}
|
481
|
-
|
447
|
+
|
448
|
+
return config;
|
449
|
+
};
|