appwrite-utils-cli 0.10.85 → 1.0.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/.appwrite/.yaml_schemas/appwrite-config.schema.json +380 -0
- package/.appwrite/.yaml_schemas/collection.schema.json +255 -0
- package/.appwrite/collections/Categories.yaml +182 -0
- package/.appwrite/collections/ExampleCollection.yaml +36 -0
- package/.appwrite/collections/Posts.yaml +227 -0
- package/.appwrite/collections/Users.yaml +149 -0
- package/.appwrite/config.yaml +109 -0
- package/.appwrite/import/README.md +148 -0
- package/.appwrite/import/categories-import.yaml +129 -0
- package/.appwrite/import/posts-import.yaml +208 -0
- package/.appwrite/import/users-import.yaml +130 -0
- package/.appwrite/importData/categories.json +194 -0
- package/.appwrite/importData/posts.json +270 -0
- package/.appwrite/importData/users.json +220 -0
- package/.appwrite/schemas/categories.json +128 -0
- package/.appwrite/schemas/exampleCollection.json +52 -0
- package/.appwrite/schemas/posts.json +173 -0
- package/.appwrite/schemas/users.json +125 -0
- package/README.md +261 -33
- package/dist/collections/attributes.js +3 -2
- package/dist/collections/methods.js +56 -38
- package/dist/config/yamlConfig.d.ts +501 -0
- package/dist/config/yamlConfig.js +452 -0
- package/dist/databases/setup.d.ts +6 -0
- package/dist/databases/setup.js +119 -0
- package/dist/functions/methods.d.ts +1 -1
- package/dist/functions/methods.js +5 -2
- package/dist/functions/openapi.d.ts +4 -0
- package/dist/functions/openapi.js +60 -0
- package/dist/interactiveCLI.d.ts +5 -0
- package/dist/interactiveCLI.js +196 -52
- package/dist/main.js +91 -30
- package/dist/migrations/afterImportActions.js +2 -2
- package/dist/migrations/appwriteToX.d.ts +10 -0
- package/dist/migrations/appwriteToX.js +15 -4
- package/dist/migrations/backup.d.ts +16 -16
- package/dist/migrations/dataLoader.d.ts +83 -1
- package/dist/migrations/dataLoader.js +4 -4
- package/dist/migrations/importController.js +25 -18
- package/dist/migrations/importDataActions.js +2 -2
- package/dist/migrations/logging.d.ts +9 -1
- package/dist/migrations/logging.js +41 -22
- package/dist/migrations/migrationHelper.d.ts +4 -4
- package/dist/migrations/relationships.js +1 -1
- package/dist/migrations/services/DataTransformationService.d.ts +55 -0
- package/dist/migrations/services/DataTransformationService.js +158 -0
- package/dist/migrations/services/FileHandlerService.d.ts +75 -0
- package/dist/migrations/services/FileHandlerService.js +236 -0
- package/dist/migrations/services/ImportOrchestrator.d.ts +97 -0
- package/dist/migrations/services/ImportOrchestrator.js +488 -0
- package/dist/migrations/services/RateLimitManager.d.ts +138 -0
- package/dist/migrations/services/RateLimitManager.js +279 -0
- package/dist/migrations/services/RelationshipResolver.d.ts +120 -0
- package/dist/migrations/services/RelationshipResolver.js +332 -0
- package/dist/migrations/services/UserMappingService.d.ts +109 -0
- package/dist/migrations/services/UserMappingService.js +277 -0
- package/dist/migrations/services/ValidationService.d.ts +74 -0
- package/dist/migrations/services/ValidationService.js +260 -0
- package/dist/migrations/transfer.d.ts +0 -6
- package/dist/migrations/transfer.js +16 -132
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +384 -0
- package/dist/migrations/yaml/YamlImportConfigLoader.js +375 -0
- package/dist/migrations/yaml/YamlImportIntegration.d.ts +87 -0
- package/dist/migrations/yaml/YamlImportIntegration.js +330 -0
- package/dist/migrations/yaml/generateImportSchemas.d.ts +17 -0
- package/dist/migrations/yaml/generateImportSchemas.js +575 -0
- package/dist/schemas/authUser.d.ts +9 -9
- package/dist/shared/attributeManager.d.ts +17 -0
- package/dist/shared/attributeManager.js +273 -0
- package/dist/shared/confirmationDialogs.d.ts +75 -0
- package/dist/shared/confirmationDialogs.js +236 -0
- package/dist/shared/functionManager.d.ts +48 -0
- package/dist/shared/functionManager.js +322 -0
- package/dist/shared/indexManager.d.ts +24 -0
- package/dist/shared/indexManager.js +150 -0
- package/dist/shared/jsonSchemaGenerator.d.ts +51 -0
- package/dist/shared/jsonSchemaGenerator.js +313 -0
- package/dist/shared/logging.d.ts +10 -0
- package/dist/shared/logging.js +46 -0
- package/dist/shared/messageFormatter.d.ts +37 -0
- package/dist/shared/messageFormatter.js +152 -0
- package/dist/shared/migrationHelpers.d.ts +173 -0
- package/dist/shared/migrationHelpers.js +142 -0
- package/dist/shared/operationLogger.d.ts +3 -0
- package/dist/shared/operationLogger.js +25 -0
- package/dist/shared/operationQueue.d.ts +13 -0
- package/dist/shared/operationQueue.js +79 -0
- package/dist/shared/progressManager.d.ts +62 -0
- package/dist/shared/progressManager.js +215 -0
- package/dist/shared/schemaGenerator.d.ts +18 -0
- package/dist/shared/schemaGenerator.js +523 -0
- package/dist/storage/methods.d.ts +3 -1
- package/dist/storage/methods.js +144 -55
- package/dist/storage/schemas.d.ts +56 -16
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/users/methods.d.ts +16 -0
- package/dist/users/methods.js +276 -0
- package/dist/utils/configMigration.d.ts +1 -0
- package/dist/utils/configMigration.js +156 -0
- package/dist/utils/dataConverters.d.ts +46 -0
- package/dist/utils/dataConverters.js +139 -0
- package/dist/utils/loadConfigs.d.ts +15 -4
- package/dist/utils/loadConfigs.js +377 -51
- package/dist/utils/schemaStrings.js +2 -1
- package/dist/utils/setupFiles.d.ts +2 -1
- package/dist/utils/setupFiles.js +723 -28
- package/dist/utils/validationRules.d.ts +43 -0
- package/dist/utils/validationRules.js +42 -0
- package/dist/utils/yamlConverter.d.ts +48 -0
- package/dist/utils/yamlConverter.js +98 -0
- package/dist/utilsController.js +65 -43
- package/package.json +19 -15
- package/src/collections/attributes.ts +3 -2
- package/src/collections/methods.ts +85 -51
- package/src/config/yamlConfig.ts +488 -0
- package/src/{migrations/setupDatabase.ts → databases/setup.ts} +11 -5
- package/src/functions/methods.ts +8 -4
- package/src/functions/templates/count-docs-in-collection/package.json +25 -0
- package/src/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
- package/src/functions/templates/typescript-node/package.json +24 -0
- package/src/functions/templates/typescript-node/tsconfig.json +28 -0
- package/src/functions/templates/uv/README.md +31 -0
- package/src/functions/templates/uv/pyproject.toml +29 -0
- package/src/interactiveCLI.ts +230 -63
- package/src/main.ts +111 -37
- package/src/migrations/afterImportActions.ts +2 -2
- package/src/migrations/appwriteToX.ts +17 -4
- package/src/migrations/dataLoader.ts +4 -4
- package/src/migrations/importController.ts +30 -22
- package/src/migrations/importDataActions.ts +2 -2
- package/src/migrations/relationships.ts +1 -1
- package/src/migrations/services/DataTransformationService.ts +196 -0
- package/src/migrations/services/FileHandlerService.ts +311 -0
- package/src/migrations/services/ImportOrchestrator.ts +669 -0
- package/src/migrations/services/RateLimitManager.ts +363 -0
- package/src/migrations/services/RelationshipResolver.ts +461 -0
- package/src/migrations/services/UserMappingService.ts +345 -0
- package/src/migrations/services/ValidationService.ts +349 -0
- package/src/migrations/transfer.ts +22 -228
- package/src/migrations/yaml/YamlImportConfigLoader.ts +427 -0
- package/src/migrations/yaml/YamlImportIntegration.ts +419 -0
- package/src/migrations/yaml/generateImportSchemas.ts +589 -0
- package/src/shared/attributeManager.ts +429 -0
- package/src/shared/confirmationDialogs.ts +327 -0
- package/src/shared/functionManager.ts +515 -0
- package/src/shared/indexManager.ts +253 -0
- package/src/shared/jsonSchemaGenerator.ts +403 -0
- package/src/shared/logging.ts +74 -0
- package/src/shared/messageFormatter.ts +195 -0
- package/src/{migrations/migrationHelper.ts → shared/migrationHelpers.ts} +22 -4
- package/src/{migrations/helper.ts → shared/operationLogger.ts} +7 -2
- package/src/{migrations/queue.ts → shared/operationQueue.ts} +1 -1
- package/src/shared/progressManager.ts +278 -0
- package/src/{migrations/schemaStrings.ts → shared/schemaGenerator.ts} +71 -17
- package/src/storage/methods.ts +199 -78
- package/src/types.ts +2 -2
- package/src/{migrations/users.ts → users/methods.ts} +2 -2
- package/src/utils/configMigration.ts +212 -0
- package/src/utils/loadConfigs.ts +414 -52
- package/src/utils/schemaStrings.ts +2 -1
- package/src/utils/setupFiles.ts +742 -40
- package/src/{migrations → utils}/validationRules.ts +1 -1
- package/src/utils/yamlConverter.ts +131 -0
- package/src/utilsController.ts +75 -54
- package/src/functions/templates/poetry/README.md +0 -30
- package/src/functions/templates/poetry/pyproject.toml +0 -16
- package/src/migrations/attributes.ts +0 -561
- package/src/migrations/backup.ts +0 -205
- package/src/migrations/databases.ts +0 -39
- package/src/migrations/dbHelpers.ts +0 -92
- package/src/migrations/indexes.ts +0 -40
- package/src/migrations/logging.ts +0 -29
- package/src/migrations/storage.ts +0 -538
- /package/src/{migrations → functions}/openapi.ts +0 -0
- /package/src/functions/templates/{poetry → uv}/src/__init__.py +0 -0
- /package/src/functions/templates/{poetry → uv}/src/index.py +0 -0
- /package/src/{migrations/converters.ts → utils/dataConverters.ts} +0 -0
@@ -0,0 +1,452 @@
|
|
1
|
+
import { z } from "zod";
|
2
|
+
import yaml from "js-yaml";
|
3
|
+
import fs from "fs";
|
4
|
+
import path from "path";
|
5
|
+
import { AppwriteConfigSchema, RuntimeSchema, FunctionScopes, FunctionSpecifications, permissionsSchema, PermissionToAppwritePermission } from "appwrite-utils";
|
6
|
+
const YamlConfigSchema = z.object({
|
7
|
+
appwrite: z.object({
|
8
|
+
endpoint: z.string().default("https://cloud.appwrite.io/v1"),
|
9
|
+
project: z.string(),
|
10
|
+
key: z.string(),
|
11
|
+
}),
|
12
|
+
logging: z
|
13
|
+
.object({
|
14
|
+
enabled: z.boolean().default(false),
|
15
|
+
level: z.enum(["error", "warn", "info", "debug"]).default("info"),
|
16
|
+
directory: z.string().optional(),
|
17
|
+
console: z.boolean().default(false),
|
18
|
+
})
|
19
|
+
.optional()
|
20
|
+
.default({ enabled: false, level: "info", console: false }),
|
21
|
+
backups: z
|
22
|
+
.object({
|
23
|
+
enabled: z.boolean().default(true),
|
24
|
+
interval: z.number().default(3600),
|
25
|
+
retention: z.number().default(30),
|
26
|
+
cleanup: z.boolean().default(true),
|
27
|
+
})
|
28
|
+
.optional()
|
29
|
+
.default({ enabled: true, interval: 3600, retention: 30, cleanup: true }),
|
30
|
+
data: z
|
31
|
+
.object({
|
32
|
+
enableMockData: z.boolean().default(false),
|
33
|
+
documentBucketId: z.string().default("documents"),
|
34
|
+
usersCollectionName: z.string().default("Members"),
|
35
|
+
importDirectory: z.string().default("importData"),
|
36
|
+
})
|
37
|
+
.optional()
|
38
|
+
.default({
|
39
|
+
enableMockData: false,
|
40
|
+
documentBucketId: "documents",
|
41
|
+
usersCollectionName: "Members",
|
42
|
+
importDirectory: "importData",
|
43
|
+
}),
|
44
|
+
schemas: z
|
45
|
+
.object({
|
46
|
+
outputDirectory: z.string().default("schemas"),
|
47
|
+
yamlSchemaDirectory: z.string().default(".yaml_schemas"),
|
48
|
+
})
|
49
|
+
.optional()
|
50
|
+
.default({
|
51
|
+
outputDirectory: "schemas",
|
52
|
+
yamlSchemaDirectory: ".yaml_schemas",
|
53
|
+
}),
|
54
|
+
migrations: z
|
55
|
+
.object({
|
56
|
+
enabled: z.boolean().default(true),
|
57
|
+
})
|
58
|
+
.optional()
|
59
|
+
.default({
|
60
|
+
enabled: true,
|
61
|
+
}),
|
62
|
+
databases: z
|
63
|
+
.array(z.object({
|
64
|
+
id: z.string(),
|
65
|
+
name: z.string(),
|
66
|
+
bucket: z
|
67
|
+
.object({
|
68
|
+
id: z.string(),
|
69
|
+
name: z.string(),
|
70
|
+
permissions: permissionsSchema,
|
71
|
+
fileSecurity: z.boolean().optional(),
|
72
|
+
enabled: z.boolean().optional(),
|
73
|
+
maximumFileSize: z.number().optional(),
|
74
|
+
allowedFileExtensions: z.array(z.string()).optional(),
|
75
|
+
compression: z.enum(["none", "gzip", "zstd"]).optional(),
|
76
|
+
encryption: z.boolean().optional(),
|
77
|
+
antivirus: z.boolean().optional(),
|
78
|
+
})
|
79
|
+
.optional(),
|
80
|
+
}))
|
81
|
+
.optional()
|
82
|
+
.default([
|
83
|
+
{ id: "dev", name: "Development" },
|
84
|
+
{ id: "main", name: "Main" },
|
85
|
+
{ id: "staging", name: "Staging" },
|
86
|
+
]),
|
87
|
+
buckets: z
|
88
|
+
.array(z.object({
|
89
|
+
id: z.string(),
|
90
|
+
name: z.string(),
|
91
|
+
permissions: permissionsSchema,
|
92
|
+
fileSecurity: z.boolean().optional(),
|
93
|
+
enabled: z.boolean().optional(),
|
94
|
+
maximumFileSize: z.number().optional(),
|
95
|
+
allowedFileExtensions: z.array(z.string()).optional(),
|
96
|
+
compression: z.enum(["none", "gzip", "zstd"]).optional(),
|
97
|
+
encryption: z.boolean().optional(),
|
98
|
+
antivirus: z.boolean().optional(),
|
99
|
+
}))
|
100
|
+
.optional()
|
101
|
+
.default([]),
|
102
|
+
functions: z
|
103
|
+
.array(z.object({
|
104
|
+
id: z.string(),
|
105
|
+
name: z.string(),
|
106
|
+
runtime: RuntimeSchema,
|
107
|
+
execute: z.array(z.string()).optional(),
|
108
|
+
events: z.array(z.string()).optional(),
|
109
|
+
schedule: z.string().optional(),
|
110
|
+
timeout: z.number().optional(),
|
111
|
+
enabled: z.boolean().optional(),
|
112
|
+
logging: z.boolean().optional(),
|
113
|
+
entrypoint: z.string().optional(),
|
114
|
+
commands: z.string().optional(),
|
115
|
+
scopes: z.array(FunctionScopes).optional(),
|
116
|
+
installationId: z.string().optional(),
|
117
|
+
providerRepositoryId: z.string().optional(),
|
118
|
+
providerBranch: z.string().optional(),
|
119
|
+
providerSilentMode: z.boolean().optional(),
|
120
|
+
providerRootDirectory: z.string().optional(),
|
121
|
+
templateRepository: z.string().optional(),
|
122
|
+
templateOwner: z.string().optional(),
|
123
|
+
templateRootDirectory: z.string().optional(),
|
124
|
+
templateBranch: z.string().optional(),
|
125
|
+
specification: FunctionSpecifications.optional(),
|
126
|
+
}))
|
127
|
+
.optional()
|
128
|
+
.default([]),
|
129
|
+
});
|
130
|
+
export const convertYamlToAppwriteConfig = (yamlConfig) => {
|
131
|
+
const appwriteConfig = {
|
132
|
+
appwriteEndpoint: yamlConfig.appwrite.endpoint,
|
133
|
+
appwriteProject: yamlConfig.appwrite.project,
|
134
|
+
appwriteKey: yamlConfig.appwrite.key,
|
135
|
+
appwriteClient: null,
|
136
|
+
logging: {
|
137
|
+
enabled: yamlConfig.logging.enabled,
|
138
|
+
level: yamlConfig.logging.level,
|
139
|
+
logDirectory: yamlConfig.logging.directory,
|
140
|
+
console: yamlConfig.logging.console,
|
141
|
+
},
|
142
|
+
enableBackups: yamlConfig.backups.enabled,
|
143
|
+
backupInterval: yamlConfig.backups.interval,
|
144
|
+
backupRetention: yamlConfig.backups.retention,
|
145
|
+
enableBackupCleanup: yamlConfig.backups.cleanup,
|
146
|
+
enableMockData: yamlConfig.data.enableMockData,
|
147
|
+
documentBucketId: yamlConfig.data.documentBucketId,
|
148
|
+
usersCollectionName: yamlConfig.data.usersCollectionName,
|
149
|
+
useMigrations: yamlConfig.migrations.enabled,
|
150
|
+
schemaConfig: {
|
151
|
+
outputDirectory: yamlConfig.schemas.outputDirectory,
|
152
|
+
yamlSchemaDirectory: yamlConfig.schemas.yamlSchemaDirectory,
|
153
|
+
importDirectory: yamlConfig.data.importDirectory,
|
154
|
+
},
|
155
|
+
databases: yamlConfig.databases.map((db) => ({
|
156
|
+
$id: db.id,
|
157
|
+
name: db.name,
|
158
|
+
bucket: db.bucket
|
159
|
+
? {
|
160
|
+
$id: db.bucket.id,
|
161
|
+
name: db.bucket.name,
|
162
|
+
$permissions: PermissionToAppwritePermission(db.bucket.permissions),
|
163
|
+
fileSecurity: db.bucket.fileSecurity || false,
|
164
|
+
enabled: db.bucket.enabled || true,
|
165
|
+
maximumFileSize: db.bucket.maximumFileSize || 30000000,
|
166
|
+
allowedFileExtensions: db.bucket.allowedFileExtensions || [],
|
167
|
+
compression: db.bucket.compression || "none",
|
168
|
+
encryption: db.bucket.encryption || false,
|
169
|
+
antivirus: db.bucket.antivirus || false,
|
170
|
+
}
|
171
|
+
: undefined,
|
172
|
+
})),
|
173
|
+
buckets: yamlConfig.buckets.map((bucket) => ({
|
174
|
+
$id: bucket.id,
|
175
|
+
name: bucket.name,
|
176
|
+
$permissions: PermissionToAppwritePermission(bucket.permissions),
|
177
|
+
fileSecurity: bucket.fileSecurity || false,
|
178
|
+
enabled: bucket.enabled || true,
|
179
|
+
maximumFileSize: bucket.maximumFileSize || 30000000,
|
180
|
+
allowedFileExtensions: bucket.allowedFileExtensions || [],
|
181
|
+
compression: bucket.compression || "none",
|
182
|
+
encryption: bucket.encryption || false,
|
183
|
+
antivirus: bucket.antivirus || false,
|
184
|
+
})),
|
185
|
+
functions: yamlConfig.functions?.map((func) => ({
|
186
|
+
$id: func.id,
|
187
|
+
name: func.name,
|
188
|
+
runtime: func.runtime,
|
189
|
+
execute: func.execute || [],
|
190
|
+
events: func.events || [],
|
191
|
+
schedule: func.schedule || "",
|
192
|
+
timeout: func.timeout || 15,
|
193
|
+
enabled: func.enabled || true,
|
194
|
+
logging: func.logging || true,
|
195
|
+
entrypoint: func.entrypoint || "",
|
196
|
+
commands: func.commands || "",
|
197
|
+
scopes: func.scopes || [],
|
198
|
+
installationId: func.installationId || "",
|
199
|
+
providerRepositoryId: func.providerRepositoryId || "",
|
200
|
+
providerBranch: func.providerBranch || "",
|
201
|
+
providerSilentMode: func.providerSilentMode || false,
|
202
|
+
providerRootDirectory: func.providerRootDirectory || "",
|
203
|
+
templateRepository: func.templateRepository || "",
|
204
|
+
templateOwner: func.templateOwner || "",
|
205
|
+
templateRootDirectory: func.templateRootDirectory || "",
|
206
|
+
templateBranch: func.templateBranch || "",
|
207
|
+
specification: func.specification || "s-0.5vcpu-512mb",
|
208
|
+
})),
|
209
|
+
collections: [],
|
210
|
+
};
|
211
|
+
return appwriteConfig;
|
212
|
+
};
|
213
|
+
export const loadYamlConfig = async (configPath) => {
|
214
|
+
try {
|
215
|
+
const fileContent = fs.readFileSync(configPath, "utf8");
|
216
|
+
const yamlData = yaml.load(fileContent);
|
217
|
+
const yamlConfig = YamlConfigSchema.parse(yamlData);
|
218
|
+
return convertYamlToAppwriteConfig(yamlConfig);
|
219
|
+
}
|
220
|
+
catch (error) {
|
221
|
+
if (error instanceof z.ZodError) {
|
222
|
+
console.error("❌ YAML config validation failed:");
|
223
|
+
error.errors.forEach((err) => {
|
224
|
+
console.error(` ${err.path.join('.')} → ${err.message}`);
|
225
|
+
});
|
226
|
+
}
|
227
|
+
else {
|
228
|
+
console.error("❌ Error loading YAML config:", error instanceof Error ? error.message : error);
|
229
|
+
if (error instanceof Error && error.stack) {
|
230
|
+
console.error("Stack trace:", error.stack);
|
231
|
+
}
|
232
|
+
}
|
233
|
+
return null;
|
234
|
+
}
|
235
|
+
};
|
236
|
+
export const findYamlConfig = (startDir) => {
|
237
|
+
// First check current directory for YAML configs
|
238
|
+
const possiblePaths = [
|
239
|
+
path.join(startDir, ".appwrite", "appwriteConfig.yaml"),
|
240
|
+
path.join(startDir, ".appwrite", "appwriteConfig.yml"),
|
241
|
+
path.join(startDir, ".appwrite", "config.yaml"),
|
242
|
+
path.join(startDir, ".appwrite", "config.yml"),
|
243
|
+
path.join(startDir, "appwrite.yaml"),
|
244
|
+
path.join(startDir, "appwrite.yml"),
|
245
|
+
];
|
246
|
+
for (const configPath of possiblePaths) {
|
247
|
+
if (fs.existsSync(configPath)) {
|
248
|
+
return configPath;
|
249
|
+
}
|
250
|
+
}
|
251
|
+
// Recursively search subdirectories for .appwrite folders
|
252
|
+
const yamlConfigInSubdirs = findYamlConfigRecursive(startDir);
|
253
|
+
if (yamlConfigInSubdirs) {
|
254
|
+
return yamlConfigInSubdirs;
|
255
|
+
}
|
256
|
+
// Only check one level up to avoid infinite traversal
|
257
|
+
const parentDir = path.dirname(startDir);
|
258
|
+
if (parentDir !== startDir && path.basename(parentDir) !== 'node_modules') {
|
259
|
+
const parentPossiblePaths = [
|
260
|
+
path.join(parentDir, ".appwrite", "appwriteConfig.yaml"),
|
261
|
+
path.join(parentDir, ".appwrite", "appwriteConfig.yml"),
|
262
|
+
path.join(parentDir, "appwrite.yaml"),
|
263
|
+
path.join(parentDir, "appwrite.yml"),
|
264
|
+
];
|
265
|
+
for (const configPath of parentPossiblePaths) {
|
266
|
+
if (fs.existsSync(configPath)) {
|
267
|
+
return configPath;
|
268
|
+
}
|
269
|
+
}
|
270
|
+
}
|
271
|
+
return null;
|
272
|
+
};
|
273
|
+
const shouldIgnoreDirectory = (dirName) => {
|
274
|
+
const ignoredDirs = [
|
275
|
+
'node_modules',
|
276
|
+
'dist',
|
277
|
+
'build',
|
278
|
+
'coverage',
|
279
|
+
'.next',
|
280
|
+
'.nuxt',
|
281
|
+
'.cache',
|
282
|
+
'.git',
|
283
|
+
'.svn',
|
284
|
+
'.hg',
|
285
|
+
'__pycache__',
|
286
|
+
'.pytest_cache',
|
287
|
+
'.mypy_cache',
|
288
|
+
'venv',
|
289
|
+
'.venv',
|
290
|
+
'env',
|
291
|
+
'.env',
|
292
|
+
'target',
|
293
|
+
'out',
|
294
|
+
'bin',
|
295
|
+
'obj',
|
296
|
+
'.vs',
|
297
|
+
'.vscode',
|
298
|
+
'.idea',
|
299
|
+
'temp',
|
300
|
+
'tmp',
|
301
|
+
'.tmp',
|
302
|
+
'logs',
|
303
|
+
'log',
|
304
|
+
'.DS_Store',
|
305
|
+
'Thumbs.db'
|
306
|
+
];
|
307
|
+
return ignoredDirs.includes(dirName) ||
|
308
|
+
dirName.startsWith('.git') ||
|
309
|
+
dirName.startsWith('node_modules') ||
|
310
|
+
dirName.startsWith('.');
|
311
|
+
};
|
312
|
+
const findYamlConfigRecursive = (dir, depth = 0) => {
|
313
|
+
// Limit search depth to prevent infinite recursion
|
314
|
+
if (depth > 5) {
|
315
|
+
return null;
|
316
|
+
}
|
317
|
+
if (shouldIgnoreDirectory(path.basename(dir))) {
|
318
|
+
return null;
|
319
|
+
}
|
320
|
+
try {
|
321
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
322
|
+
for (const entry of entries) {
|
323
|
+
if (entry.isDirectory() && !shouldIgnoreDirectory(entry.name)) {
|
324
|
+
const fullPath = path.join(dir, entry.name);
|
325
|
+
// Check if this is an .appwrite directory
|
326
|
+
if (entry.name === ".appwrite") {
|
327
|
+
const configPaths = [
|
328
|
+
path.join(fullPath, "appwriteConfig.yaml"),
|
329
|
+
path.join(fullPath, "appwriteConfig.yml"),
|
330
|
+
path.join(fullPath, "config.yaml"),
|
331
|
+
path.join(fullPath, "config.yml"),
|
332
|
+
];
|
333
|
+
for (const configPath of configPaths) {
|
334
|
+
if (fs.existsSync(configPath)) {
|
335
|
+
return configPath;
|
336
|
+
}
|
337
|
+
}
|
338
|
+
}
|
339
|
+
// Recurse into other directories with increased depth
|
340
|
+
const result = findYamlConfigRecursive(fullPath, depth + 1);
|
341
|
+
if (result)
|
342
|
+
return result;
|
343
|
+
}
|
344
|
+
}
|
345
|
+
}
|
346
|
+
catch (error) {
|
347
|
+
// Ignore directory access errors
|
348
|
+
}
|
349
|
+
return null;
|
350
|
+
};
|
351
|
+
export const generateYamlConfigTemplate = (outputPath) => {
|
352
|
+
const template = {
|
353
|
+
appwrite: {
|
354
|
+
endpoint: "https://cloud.appwrite.io/v1",
|
355
|
+
project: "YOUR_PROJECT_ID",
|
356
|
+
key: "YOUR_API_KEY",
|
357
|
+
},
|
358
|
+
logging: {
|
359
|
+
enabled: false,
|
360
|
+
level: "info",
|
361
|
+
console: false,
|
362
|
+
},
|
363
|
+
backups: {
|
364
|
+
enabled: true,
|
365
|
+
interval: 3600,
|
366
|
+
retention: 30,
|
367
|
+
cleanup: true,
|
368
|
+
},
|
369
|
+
data: {
|
370
|
+
enableMockData: false,
|
371
|
+
documentBucketId: "documents",
|
372
|
+
usersCollectionName: "Members",
|
373
|
+
importDirectory: "importData",
|
374
|
+
},
|
375
|
+
schemas: {
|
376
|
+
outputDirectory: "schemas",
|
377
|
+
yamlSchemaDirectory: ".yaml_schemas",
|
378
|
+
},
|
379
|
+
migrations: {
|
380
|
+
enabled: true,
|
381
|
+
},
|
382
|
+
databases: [
|
383
|
+
{ id: "dev", name: "Development" },
|
384
|
+
{ id: "main", name: "Main" },
|
385
|
+
{ id: "staging", name: "Staging" },
|
386
|
+
],
|
387
|
+
buckets: [],
|
388
|
+
functions: [],
|
389
|
+
};
|
390
|
+
const yamlContent = yaml.dump(template, {
|
391
|
+
indent: 2,
|
392
|
+
lineWidth: 120,
|
393
|
+
sortKeys: false,
|
394
|
+
});
|
395
|
+
// Add schema reference header
|
396
|
+
const schemaReference = "# yaml-language-server: $schema=./.yaml_schemas/appwrite-config.schema.json\n";
|
397
|
+
const finalContent = schemaReference + "# Appwrite Project Configuration\n" + yamlContent;
|
398
|
+
fs.writeFileSync(outputPath, finalContent, "utf8");
|
399
|
+
};
|
400
|
+
/**
|
401
|
+
* Adds a new function to the YAML config file
|
402
|
+
* @param configPath Path to the YAML config file
|
403
|
+
* @param newFunction The function configuration to add
|
404
|
+
*/
|
405
|
+
export const addFunctionToYamlConfig = async (configPath, newFunction) => {
|
406
|
+
try {
|
407
|
+
// Read current config
|
408
|
+
const fileContent = fs.readFileSync(configPath, "utf8");
|
409
|
+
const yamlData = yaml.load(fileContent);
|
410
|
+
// Initialize functions array if it doesn't exist
|
411
|
+
if (!yamlData.functions) {
|
412
|
+
yamlData.functions = [];
|
413
|
+
}
|
414
|
+
// Convert AppwriteFunction to YAML format
|
415
|
+
const yamlFunction = {
|
416
|
+
id: newFunction.$id,
|
417
|
+
name: newFunction.name,
|
418
|
+
runtime: newFunction.runtime,
|
419
|
+
execute: newFunction.execute || ["any"],
|
420
|
+
events: newFunction.events || [],
|
421
|
+
schedule: newFunction.schedule || "",
|
422
|
+
timeout: newFunction.timeout || 15,
|
423
|
+
enabled: newFunction.enabled !== false,
|
424
|
+
logging: newFunction.logging !== false,
|
425
|
+
entrypoint: newFunction.entrypoint || "",
|
426
|
+
commands: newFunction.commands || "",
|
427
|
+
scopes: newFunction.scopes || [],
|
428
|
+
specification: newFunction.specification || "s-0.5vcpu-512mb"
|
429
|
+
};
|
430
|
+
// Add new function
|
431
|
+
yamlData.functions.push(yamlFunction);
|
432
|
+
// Write back to file
|
433
|
+
const updatedYamlContent = yaml.dump(yamlData, {
|
434
|
+
indent: 2,
|
435
|
+
lineWidth: 120,
|
436
|
+
sortKeys: false,
|
437
|
+
});
|
438
|
+
// Preserve schema reference if it exists
|
439
|
+
const lines = fileContent.split('\n');
|
440
|
+
const schemaLine = lines.find(line => line.startsWith('# yaml-language-server:'));
|
441
|
+
let finalContent = updatedYamlContent;
|
442
|
+
if (schemaLine) {
|
443
|
+
finalContent = schemaLine + '\n' + updatedYamlContent;
|
444
|
+
}
|
445
|
+
fs.writeFileSync(configPath, finalContent, "utf8");
|
446
|
+
console.log(`✅ Added function "${newFunction.name}" to YAML config`);
|
447
|
+
}
|
448
|
+
catch (error) {
|
449
|
+
console.error("❌ Error adding function to YAML config:", error instanceof Error ? error.message : error);
|
450
|
+
throw error;
|
451
|
+
}
|
452
|
+
};
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import { Databases, type Models } from "node-appwrite";
|
2
|
+
import { type AppwriteConfig } from "appwrite-utils";
|
3
|
+
export declare const setupMigrationDatabase: (config: AppwriteConfig) => Promise<void>;
|
4
|
+
export declare const ensureDatabasesExist: (config: AppwriteConfig, databasesToEnsure?: Models.Database[]) => Promise<void>;
|
5
|
+
export declare const wipeOtherDatabases: (database: Databases, databasesToKeep: Models.Database[], useMigrations?: boolean) => Promise<void>;
|
6
|
+
export declare const ensureCollectionsExist: (config: AppwriteConfig, database: Models.Database, collectionsToEnsure?: Models.Collection[]) => Promise<void>;
|
@@ -0,0 +1,119 @@
|
|
1
|
+
import { Databases, Query } from "node-appwrite";
|
2
|
+
import { createOrUpdateAttribute } from "../collections/attributes.js";
|
3
|
+
import { getMigrationCollectionSchemas } from "../storage/schemas.js";
|
4
|
+
import { areCollectionNamesSame, delay, toCamelCase, tryAwaitWithRetry, } from "../utils/index.js";
|
5
|
+
import {} from "appwrite-utils";
|
6
|
+
import { ulid } from "ulidx";
|
7
|
+
export const setupMigrationDatabase = async (config) => {
|
8
|
+
if (!config.useMigrations) {
|
9
|
+
console.log("Migrations database disabled, skipping setup");
|
10
|
+
return;
|
11
|
+
}
|
12
|
+
console.log("---------------------------------");
|
13
|
+
console.log("Starting Migrations Setup");
|
14
|
+
console.log("---------------------------------");
|
15
|
+
const database = new Databases(config.appwriteClient);
|
16
|
+
if (!config.appwriteClient) {
|
17
|
+
throw new Error("Appwrite client is not initialized in the config");
|
18
|
+
}
|
19
|
+
let db;
|
20
|
+
const migrationCollectionsSetup = getMigrationCollectionSchemas();
|
21
|
+
try {
|
22
|
+
db = await tryAwaitWithRetry(async () => await database.get("migrations"), undefined, true);
|
23
|
+
console.log("Migrations database found");
|
24
|
+
}
|
25
|
+
catch (e) {
|
26
|
+
db = await tryAwaitWithRetry(async () => await database.create("migrations", "Migrations", true));
|
27
|
+
console.log("Migrations database created");
|
28
|
+
}
|
29
|
+
if (!db) {
|
30
|
+
console.error("Failed to create or retrieve the migrations database");
|
31
|
+
return;
|
32
|
+
}
|
33
|
+
for (const [collectionName, { collection, attributes }] of Object.entries(migrationCollectionsSetup)) {
|
34
|
+
const collectionId = toCamelCase(collectionName);
|
35
|
+
let collectionFound;
|
36
|
+
try {
|
37
|
+
collectionFound = await tryAwaitWithRetry(async () => await database.getCollection(db.$id, collectionId), undefined, true);
|
38
|
+
console.log(`Collection found: ${collectionId}`);
|
39
|
+
}
|
40
|
+
catch (e) {
|
41
|
+
console.log(`Collection not found: ${collectionId}`);
|
42
|
+
try {
|
43
|
+
collectionFound = await tryAwaitWithRetry(async () => await database.createCollection(db.$id, collectionId, collectionName, undefined, collection.documentSecurity, collection.enabled), undefined, true);
|
44
|
+
console.log(`Collection created: ${collectionId}`);
|
45
|
+
}
|
46
|
+
catch (createError) {
|
47
|
+
console.error(`Failed to create collection: ${collectionId}`, createError);
|
48
|
+
continue;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
if (!collectionFound) {
|
52
|
+
console.error(`Failed to create or retrieve collection: ${collectionId}`);
|
53
|
+
continue;
|
54
|
+
}
|
55
|
+
for (const attribute of attributes) {
|
56
|
+
try {
|
57
|
+
await createOrUpdateAttribute(database, db.$id, collectionFound, attribute);
|
58
|
+
await delay(100);
|
59
|
+
console.log(`Attribute created/updated: ${attribute.key}`);
|
60
|
+
}
|
61
|
+
catch (attrError) {
|
62
|
+
console.error(`Failed to create/update attribute: ${attribute.key}`, attrError);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
console.log("---------------------------------");
|
67
|
+
console.log("Migrations Setup Complete");
|
68
|
+
console.log("---------------------------------");
|
69
|
+
};
|
70
|
+
export const ensureDatabasesExist = async (config, databasesToEnsure) => {
|
71
|
+
if (!config.appwriteClient) {
|
72
|
+
throw new Error("Appwrite client is not initialized in the config");
|
73
|
+
}
|
74
|
+
const database = new Databases(config.appwriteClient);
|
75
|
+
const databasesToCreate = databasesToEnsure || config.databases || [];
|
76
|
+
if (!databasesToCreate.length) {
|
77
|
+
console.log("No databases to create");
|
78
|
+
return;
|
79
|
+
}
|
80
|
+
const existingDatabases = await tryAwaitWithRetry(async () => await database.list([Query.limit(500)]));
|
81
|
+
const migrationsDatabase = existingDatabases.databases.find((d) => d.name.toLowerCase().trim().replace(" ", "") === "migrations");
|
82
|
+
if (config.useMigrations && existingDatabases.databases.length !== 0 && migrationsDatabase) {
|
83
|
+
console.log("Creating all databases including migrations");
|
84
|
+
databasesToCreate.push(migrationsDatabase);
|
85
|
+
}
|
86
|
+
for (const db of databasesToCreate) {
|
87
|
+
if (!existingDatabases.databases.some((d) => d.name === db.name)) {
|
88
|
+
await tryAwaitWithRetry(async () => await database.create(db.$id || ulid(), db.name, true));
|
89
|
+
console.log(`${db.name} database created`);
|
90
|
+
}
|
91
|
+
}
|
92
|
+
};
|
93
|
+
export const wipeOtherDatabases = async (database, databasesToKeep, useMigrations = true) => {
|
94
|
+
console.log(`Databases to keep: ${databasesToKeep.map(db => db.name).join(", ")}`);
|
95
|
+
const allDatabases = await tryAwaitWithRetry(async () => await database.list([Query.limit(500)]));
|
96
|
+
const migrationsDatabase = allDatabases.databases.find((d) => d.name.toLowerCase().trim().replace(" ", "") === "migrations");
|
97
|
+
if (useMigrations && allDatabases.databases.length !== 0 && migrationsDatabase) {
|
98
|
+
console.log("Wiping all databases except migrations");
|
99
|
+
databasesToKeep.push(migrationsDatabase);
|
100
|
+
}
|
101
|
+
for (const db of allDatabases.databases) {
|
102
|
+
if (!databasesToKeep.some((d) => d.name === db.name)) {
|
103
|
+
await tryAwaitWithRetry(async () => await database.delete(db.$id));
|
104
|
+
console.log(`Deleted database: ${db.name}`);
|
105
|
+
}
|
106
|
+
}
|
107
|
+
};
|
108
|
+
export const ensureCollectionsExist = async (config, database, collectionsToEnsure) => {
|
109
|
+
const databaseClient = new Databases(config.appwriteClient);
|
110
|
+
const collectionsToCreate = collectionsToEnsure ||
|
111
|
+
(config.collections ? config.collections : []);
|
112
|
+
const existingCollections = await tryAwaitWithRetry(async () => await databaseClient.listCollections(database.$id, [Query.limit(500)]));
|
113
|
+
for (const collection of collectionsToCreate) {
|
114
|
+
if (!existingCollections.collections.some((c) => c.name === collection.name)) {
|
115
|
+
await tryAwaitWithRetry(async () => await databaseClient.createCollection(database.$id, ulid(), collection.name, undefined, true, true));
|
116
|
+
console.log(`${collection.name} collection created in ${database.name}`);
|
117
|
+
}
|
118
|
+
}
|
119
|
+
};
|
@@ -12,4 +12,4 @@ export declare const createFunction: (client: Client, functionConfig: AppwriteFu
|
|
12
12
|
export declare const updateFunctionSpecifications: (client: Client, functionId: string, specification: Specification) => Promise<import("node-appwrite").Models.Function | undefined>;
|
13
13
|
export declare const listSpecifications: (client: Client) => Promise<import("node-appwrite").Models.SpecificationList>;
|
14
14
|
export declare const updateFunction: (client: Client, functionConfig: AppwriteFunction) => Promise<import("node-appwrite").Models.Function>;
|
15
|
-
export declare const createFunctionTemplate: (templateType: "typescript-node" | "
|
15
|
+
export declare const createFunctionTemplate: (templateType: "typescript-node" | "uv" | "count-docs-in-collection", functionName: string, basePath?: string) => Promise<string>;
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { AppwriteException, Client, Functions, Query, Runtime, } from "node-appwrite";
|
2
|
-
import { join } from "node:path";
|
2
|
+
import { join, dirname } from "node:path";
|
3
|
+
import { fileURLToPath } from "node:url";
|
3
4
|
import fs from "node:fs";
|
4
5
|
import {} from "appwrite-utils";
|
5
6
|
import chalk from "chalk";
|
@@ -100,7 +101,9 @@ export const updateFunction = async (client, functionConfig) => {
|
|
100
101
|
};
|
101
102
|
export const createFunctionTemplate = async (templateType, functionName, basePath = "./functions") => {
|
102
103
|
const functionPath = join(basePath, functionName);
|
103
|
-
const
|
104
|
+
const currentFileUrl = import.meta.url;
|
105
|
+
const currentDir = dirname(fileURLToPath(currentFileUrl));
|
106
|
+
const templatesPath = join(currentDir, "templates", templateType);
|
104
107
|
// Create function directory
|
105
108
|
await fs.promises.mkdir(functionPath, { recursive: true });
|
106
109
|
// Copy template files recursively
|
@@ -0,0 +1,4 @@
|
|
1
|
+
import { type AppwriteConfig } from "appwrite-utils";
|
2
|
+
import { z } from "zod";
|
3
|
+
export declare const generateOpenApi: (config: AppwriteConfig) => Promise<void>;
|
4
|
+
export declare function transformTypeToOpenApi<T extends z.ZodTypeAny>(schema: T, description?: string | Record<string, any> | null | undefined): T;
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import { OpenAPIRegistry, OpenApiGeneratorV3, OpenApiGeneratorV31, } from "@asteasolutions/zod-to-openapi";
|
2
|
+
import { attributeSchema, CollectionSchema, } from "appwrite-utils";
|
3
|
+
import { z } from "zod";
|
4
|
+
import { writeFileSync } from "fs";
|
5
|
+
const registry = new OpenAPIRegistry();
|
6
|
+
export const generateOpenApi = async (config) => {
|
7
|
+
if (!config.collections) {
|
8
|
+
return;
|
9
|
+
}
|
10
|
+
for (const collection of config.collections) {
|
11
|
+
// Transform and register each attribute schema
|
12
|
+
const attributeSchemas = collection.attributes.map((attribute) => {
|
13
|
+
return transformTypeToOpenApi(attributeSchema, attribute.description);
|
14
|
+
});
|
15
|
+
// Create and register the collection schema with descriptions
|
16
|
+
const updatedCollectionSchema = CollectionSchema.extend({
|
17
|
+
// @ts-ignore
|
18
|
+
attributes: attributeSchemas,
|
19
|
+
}).openapi(collection.description ?? "No description");
|
20
|
+
// Register the updated collection schema under the collection name
|
21
|
+
registry.register(collection.name, updatedCollectionSchema);
|
22
|
+
}
|
23
|
+
// Convert the registry to OpenAPI JSON
|
24
|
+
const generator = new OpenApiGeneratorV31(registry.definitions);
|
25
|
+
const openApiSpec = generator.generateComponents();
|
26
|
+
// Output the OpenAPI spec to a file
|
27
|
+
writeFileSync("./appwrite/openapi/openapi.json", JSON.stringify(openApiSpec, null, 2));
|
28
|
+
};
|
29
|
+
export function transformTypeToOpenApi(schema, description) {
|
30
|
+
// Check if description is an object (OpenAPI properties) or a string
|
31
|
+
let updatedSchema;
|
32
|
+
if (!description) {
|
33
|
+
return schema;
|
34
|
+
}
|
35
|
+
if (typeof description === "string") {
|
36
|
+
updatedSchema = schema.openapi(description);
|
37
|
+
}
|
38
|
+
else if (typeof description === "object") {
|
39
|
+
updatedSchema = schema.openapi(description);
|
40
|
+
}
|
41
|
+
else {
|
42
|
+
updatedSchema = schema;
|
43
|
+
}
|
44
|
+
// Check and transform attributes if they exist
|
45
|
+
if (schema._def && schema._def.shape) {
|
46
|
+
const shape = schema._def.shape();
|
47
|
+
for (const key in shape) {
|
48
|
+
const attributeDesc = shape[key].description;
|
49
|
+
if (attributeDesc) {
|
50
|
+
if (typeof attributeDesc === "string") {
|
51
|
+
shape[key] = shape[key].openapi(attributeDesc);
|
52
|
+
}
|
53
|
+
else if (typeof attributeDesc === "object") {
|
54
|
+
shape[key] = shape[key].openapi(attributeDesc);
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
return updatedSchema;
|
60
|
+
}
|