appwrite-utils-cli 0.9.992 → 0.9.994
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/README.md +2 -2
- package/dist/collections/methods.js +6 -4
- package/dist/functions/deployments.d.ts +4 -0
- package/dist/functions/deployments.js +114 -0
- package/dist/functions/methods.d.ts +13 -8
- package/dist/functions/methods.js +75 -21
- package/dist/functions/templates/count-docs-in-collection/src/main.d.ts +21 -0
- package/dist/functions/templates/count-docs-in-collection/src/main.js +114 -0
- package/dist/functions/templates/count-docs-in-collection/src/request.d.ts +15 -0
- package/dist/functions/templates/count-docs-in-collection/src/request.js +6 -0
- package/dist/functions/templates/typescript-node/src/index.d.ts +11 -0
- package/dist/functions/templates/typescript-node/src/index.js +11 -0
- package/dist/interactiveCLI.d.ts +6 -0
- package/dist/interactiveCLI.js +437 -32
- package/dist/main.js +0 -0
- package/dist/migrations/appwriteToX.js +32 -1
- package/dist/migrations/schemaStrings.d.ts +1 -0
- package/dist/migrations/schemaStrings.js +74 -2
- package/dist/migrations/transfer.js +112 -123
- package/dist/utils/loadConfigs.d.ts +1 -0
- package/dist/utils/loadConfigs.js +19 -0
- package/dist/utils/schemaStrings.js +27 -0
- package/dist/utilsController.d.ts +5 -1
- package/dist/utilsController.js +54 -2
- package/package.json +57 -55
- package/src/collections/methods.ts +29 -10
- package/src/functions/deployments.ts +190 -0
- package/src/functions/methods.ts +295 -235
- package/src/functions/templates/count-docs-in-collection/README.md +54 -0
- package/src/functions/templates/count-docs-in-collection/src/main.ts +159 -0
- package/src/functions/templates/count-docs-in-collection/src/request.ts +9 -0
- package/src/functions/templates/poetry/README.md +30 -0
- package/src/functions/templates/poetry/pyproject.toml +16 -0
- package/src/functions/templates/poetry/src/__init__.py +0 -0
- package/src/functions/templates/poetry/src/index.py +16 -0
- package/src/functions/templates/typescript-node/README.md +32 -0
- package/src/functions/templates/typescript-node/src/index.ts +23 -0
- package/src/interactiveCLI.ts +606 -47
- package/src/migrations/appwriteToX.ts +44 -1
- package/src/migrations/schemaStrings.ts +83 -2
- package/src/migrations/transfer.ts +280 -207
- package/src/setupController.ts +41 -41
- package/src/utils/loadConfigs.ts +24 -0
- package/src/utils/schemaStrings.ts +27 -0
- package/src/utilsController.ts +63 -3
- package/tsconfig.json +8 -1
@@ -4,6 +4,7 @@ import fs from "fs";
|
|
4
4
|
import path from "path";
|
5
5
|
import { dump } from "js-yaml";
|
6
6
|
import { getDatabaseFromConfig } from "./afterImportActions.js";
|
7
|
+
import { ulid } from "ulidx";
|
7
8
|
export class SchemaGenerator {
|
8
9
|
relationshipMap = new Map();
|
9
10
|
config;
|
@@ -15,7 +16,9 @@ export class SchemaGenerator {
|
|
15
16
|
}
|
16
17
|
updateTsSchemas() {
|
17
18
|
const collections = this.config.collections;
|
19
|
+
const functions = this.config.functions || [];
|
18
20
|
delete this.config.collections;
|
21
|
+
delete this.config.functions;
|
19
22
|
const configPath = path.join(this.appwriteFolderPath, "appwriteConfig.ts");
|
20
23
|
const configContent = `import { type AppwriteConfig } from "appwrite-utils";
|
21
24
|
|
@@ -30,8 +33,33 @@ export class SchemaGenerator {
|
|
30
33
|
enableMockData: ${this.config.enableMockData},
|
31
34
|
documentBucketId: "${this.config.documentBucketId}",
|
32
35
|
usersCollectionName: "${this.config.usersCollectionName}",
|
33
|
-
databases: ${JSON.stringify(this.config.databases)},
|
34
|
-
buckets: ${JSON.stringify(this.config.buckets)}
|
36
|
+
databases: ${JSON.stringify(this.config.databases, null, 4)},
|
37
|
+
buckets: ${JSON.stringify(this.config.buckets, null, 4)},
|
38
|
+
functions: ${JSON.stringify(functions.map((func) => ({
|
39
|
+
functionId: func.$id || ulid(),
|
40
|
+
name: func.name,
|
41
|
+
runtime: func.runtime,
|
42
|
+
path: func.dirPath || `functions/${func.name}`,
|
43
|
+
entrypoint: func.entrypoint || "src/index.ts",
|
44
|
+
execute: func.execute,
|
45
|
+
events: func.events || [],
|
46
|
+
schedule: func.schedule || "",
|
47
|
+
timeout: func.timeout || 15,
|
48
|
+
enabled: func.enabled !== false,
|
49
|
+
logging: func.logging !== false,
|
50
|
+
commands: func.commands || "npm install",
|
51
|
+
scopes: func.scopes || [],
|
52
|
+
installationId: func.installationId,
|
53
|
+
providerRepositoryId: func.providerRepositoryId,
|
54
|
+
providerBranch: func.providerBranch,
|
55
|
+
providerSilentMode: func.providerSilentMode,
|
56
|
+
providerRootDirectory: func.providerRootDirectory,
|
57
|
+
specification: func.specification,
|
58
|
+
...(func.predeployCommands
|
59
|
+
? { predeployCommands: func.predeployCommands }
|
60
|
+
: {}),
|
61
|
+
...(func.deployDir ? { deployDir: func.deployDir } : {}),
|
62
|
+
})), null, 4)}
|
35
63
|
};
|
36
64
|
|
37
65
|
export default appwriteConfig;
|
@@ -106,6 +134,50 @@ export class SchemaGenerator {
|
|
106
134
|
console.log(`Collection schema written to ${collectionFilePath}`);
|
107
135
|
});
|
108
136
|
}
|
137
|
+
updateConfig(config) {
|
138
|
+
const configPath = path.join(this.appwriteFolderPath, "appwriteConfig.ts");
|
139
|
+
const configContent = `import { type AppwriteConfig } from "appwrite-utils";
|
140
|
+
|
141
|
+
const appwriteConfig: AppwriteConfig = {
|
142
|
+
appwriteEndpoint: "${config.appwriteEndpoint}",
|
143
|
+
appwriteProject: "${config.appwriteProject}",
|
144
|
+
appwriteKey: "${config.appwriteKey}",
|
145
|
+
enableBackups: ${config.enableBackups},
|
146
|
+
backupInterval: ${config.backupInterval},
|
147
|
+
backupRetention: ${config.backupRetention},
|
148
|
+
enableBackupCleanup: ${config.enableBackupCleanup},
|
149
|
+
enableMockData: ${config.enableMockData},
|
150
|
+
documentBucketId: "${config.documentBucketId}",
|
151
|
+
usersCollectionName: "${config.usersCollectionName}",
|
152
|
+
databases: ${JSON.stringify(config.databases, null, 4)},
|
153
|
+
buckets: ${JSON.stringify(config.buckets, null, 4)},
|
154
|
+
functions: ${JSON.stringify(config.functions?.map((func) => ({
|
155
|
+
$id: func.$id || ulid(),
|
156
|
+
name: func.name,
|
157
|
+
runtime: func.runtime,
|
158
|
+
dirPath: func.dirPath || `functions/${func.name}`,
|
159
|
+
entrypoint: func.entrypoint || "src/index.ts",
|
160
|
+
execute: func.execute || [],
|
161
|
+
events: func.events || [],
|
162
|
+
schedule: func.schedule || "",
|
163
|
+
timeout: func.timeout || 15,
|
164
|
+
enabled: func.enabled !== false,
|
165
|
+
logging: func.logging !== false,
|
166
|
+
commands: func.commands || "npm install",
|
167
|
+
scopes: func.scopes || [],
|
168
|
+
installationId: func.installationId,
|
169
|
+
providerRepositoryId: func.providerRepositoryId,
|
170
|
+
providerBranch: func.providerBranch,
|
171
|
+
providerSilentMode: func.providerSilentMode,
|
172
|
+
providerRootDirectory: func.providerRootDirectory,
|
173
|
+
specification: func.specification,
|
174
|
+
})), null, 4)}
|
175
|
+
};
|
176
|
+
|
177
|
+
export default appwriteConfig;
|
178
|
+
`;
|
179
|
+
fs.writeFileSync(configPath, configContent, { encoding: "utf-8" });
|
180
|
+
}
|
109
181
|
extractRelationships() {
|
110
182
|
if (!this.config.collections) {
|
111
183
|
return;
|
@@ -2,8 +2,11 @@ import { tryAwaitWithRetry } from "appwrite-utils";
|
|
2
2
|
import { Client, Databases, IndexType, Query, Storage, } from "node-appwrite";
|
3
3
|
import { InputFile } from "node-appwrite/file";
|
4
4
|
import { getAppwriteClient } from "../utils/helperFunctions.js";
|
5
|
-
import { createOrUpdateAttribute } from "../collections/attributes.js";
|
5
|
+
import { createOrUpdateAttribute, createUpdateCollectionAttributes, } from "../collections/attributes.js";
|
6
6
|
import { parseAttribute } from "appwrite-utils";
|
7
|
+
import chalk from "chalk";
|
8
|
+
import { fetchAllCollections } from "src/collections/methods.js";
|
9
|
+
import { createOrUpdateIndex, createOrUpdateIndexes, } from "src/collections/indexes.js";
|
7
10
|
export const transferStorageLocalToLocal = async (storage, fromBucketId, toBucketId) => {
|
8
11
|
console.log(`Transferring files from ${fromBucketId} to ${toBucketId}`);
|
9
12
|
let lastFileId;
|
@@ -70,6 +73,7 @@ export const transferStorageLocalToLocal = async (storage, fromBucketId, toBucke
|
|
70
73
|
}
|
71
74
|
catch (error) {
|
72
75
|
// File already exists, so we can skip it
|
76
|
+
console.log(chalk.yellow(`File ${file.$id} already exists, skipping...`));
|
73
77
|
continue;
|
74
78
|
}
|
75
79
|
numberOfFiles++;
|
@@ -107,6 +111,7 @@ export const transferStorageLocalToRemote = async (localStorage, endpoint, proje
|
|
107
111
|
}
|
108
112
|
catch (error) {
|
109
113
|
// File already exists, so we can skip it
|
114
|
+
console.log(chalk.yellow(`File ${file.$id} already exists, skipping...`));
|
110
115
|
continue;
|
111
116
|
}
|
112
117
|
numberOfFiles++;
|
@@ -244,146 +249,130 @@ export const transferDocumentsBetweenDbsLocalToRemote = async (localDb, endpoint
|
|
244
249
|
* @return {Promise<void>} A promise that resolves when the transfer is complete.
|
245
250
|
*/
|
246
251
|
export const transferDatabaseLocalToLocal = async (localDb, fromDbId, targetDbId) => {
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
let toCollections = await tryAwaitWithRetry(async () => await localDb.listCollections(targetDbId, [Query.limit(50)]));
|
271
|
-
const allToCollections = toCollections.collections;
|
272
|
-
if (toCollections.collections.length < 50) {
|
273
|
-
}
|
274
|
-
else {
|
275
|
-
lastCollectionId =
|
276
|
-
toCollections.collections[toCollections.collections.length - 1].$id;
|
277
|
-
while (lastCollectionId) {
|
278
|
-
const collections = await localDb.listCollections(targetDbId, [
|
279
|
-
Query.limit(50),
|
280
|
-
Query.cursorAfter(lastCollectionId),
|
281
|
-
]);
|
282
|
-
allToCollections.push(...collections.collections);
|
283
|
-
if (collections.collections.length < 50) {
|
284
|
-
lastCollectionId = undefined;
|
252
|
+
// Get all collections from source database
|
253
|
+
const sourceCollections = await fetchAllCollections(fromDbId, localDb);
|
254
|
+
console.log(chalk.blue(`Found ${sourceCollections.length} collections in source database`));
|
255
|
+
// Process each collection
|
256
|
+
for (const collection of sourceCollections) {
|
257
|
+
console.log(chalk.yellow(`Processing collection: ${collection.name} (${collection.$id})`));
|
258
|
+
try {
|
259
|
+
// Create or update collection in target
|
260
|
+
let targetCollection;
|
261
|
+
const existingCollection = await tryAwaitWithRetry(async () => localDb.listCollections(targetDbId, [
|
262
|
+
Query.equal("$id", collection.$id),
|
263
|
+
]));
|
264
|
+
if (existingCollection.collections.length > 0) {
|
265
|
+
targetCollection = existingCollection.collections[0];
|
266
|
+
console.log(chalk.green(`Collection ${collection.name} exists in target database`));
|
267
|
+
// Update collection if needed
|
268
|
+
if (targetCollection.name !== collection.name ||
|
269
|
+
targetCollection.$permissions !== collection.$permissions ||
|
270
|
+
targetCollection.documentSecurity !== collection.documentSecurity ||
|
271
|
+
targetCollection.enabled !== collection.enabled) {
|
272
|
+
targetCollection = await tryAwaitWithRetry(async () => localDb.updateCollection(targetDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
|
273
|
+
console.log(chalk.green(`Collection ${collection.name} updated`));
|
274
|
+
}
|
285
275
|
}
|
286
276
|
else {
|
287
|
-
|
288
|
-
|
277
|
+
console.log(chalk.yellow(`Creating collection ${collection.name} in target database...`));
|
278
|
+
targetCollection = await tryAwaitWithRetry(async () => localDb.createCollection(targetDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
|
289
279
|
}
|
290
|
-
|
291
|
-
|
292
|
-
for (const collection of allFromCollections) {
|
293
|
-
const toCollection = allToCollections.find((c) => c.$id === collection.$id);
|
294
|
-
if (toCollection) {
|
295
|
-
await transferDocumentsBetweenDbsLocalToLocal(localDb, fromDbId, targetDbId, collection.$id, toCollection.$id);
|
296
|
-
}
|
297
|
-
else {
|
298
|
-
console.log(`Collection ${collection.name} not found in destination database, creating...`);
|
299
|
-
const newCollection = await tryAwaitWithRetry(async () => await localDb.createCollection(targetDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
|
300
|
-
console.log(`Collection ${newCollection.name} created`);
|
280
|
+
// Handle attributes
|
281
|
+
const existingAttributes = await tryAwaitWithRetry(async () => await localDb.listAttributes(targetDbId, targetCollection.$id));
|
301
282
|
for (const attribute of collection.attributes) {
|
302
|
-
|
283
|
+
const parsedAttribute = parseAttribute(attribute);
|
284
|
+
const existingAttribute = existingAttributes.attributes.find((attr) => attr.key === parsedAttribute.key);
|
285
|
+
if (!existingAttribute) {
|
286
|
+
await tryAwaitWithRetry(async () => createOrUpdateAttribute(localDb, targetDbId, targetCollection, parsedAttribute));
|
287
|
+
console.log(chalk.green(`Attribute ${parsedAttribute.key} created`));
|
288
|
+
}
|
289
|
+
else {
|
290
|
+
console.log(chalk.blue(`Attribute ${parsedAttribute.key} exists, checking for updates...`));
|
291
|
+
await tryAwaitWithRetry(async () => createOrUpdateAttribute(localDb, targetDbId, targetCollection, parsedAttribute));
|
292
|
+
}
|
303
293
|
}
|
294
|
+
// Handle indexes
|
295
|
+
const existingIndexes = await tryAwaitWithRetry(async () => await localDb.listIndexes(targetDbId, targetCollection.$id));
|
304
296
|
for (const index of collection.indexes) {
|
305
|
-
|
297
|
+
const existingIndex = existingIndexes.indexes.find((idx) => idx.key === index.key);
|
298
|
+
if (!existingIndex) {
|
299
|
+
await createOrUpdateIndex(targetDbId, localDb, targetCollection.$id, index);
|
300
|
+
console.log(chalk.green(`Index ${index.key} created`));
|
301
|
+
}
|
302
|
+
else {
|
303
|
+
console.log(chalk.blue(`Index ${index.key} exists, checking for updates...`));
|
304
|
+
await createOrUpdateIndex(targetDbId, localDb, targetCollection.$id, index);
|
305
|
+
}
|
306
306
|
}
|
307
|
-
|
307
|
+
// Transfer documents
|
308
|
+
await transferDocumentsBetweenDbsLocalToLocal(localDb, fromDbId, targetDbId, collection.$id, targetCollection.$id);
|
309
|
+
}
|
310
|
+
catch (error) {
|
311
|
+
console.error(chalk.red(`Error processing collection ${collection.name}:`), error);
|
308
312
|
}
|
309
313
|
}
|
310
314
|
};
|
311
315
|
export const transferDatabaseLocalToRemote = async (localDb, endpoint, projectId, apiKey, fromDbId, toDbId) => {
|
312
316
|
const client = getAppwriteClient(endpoint, projectId, apiKey);
|
313
317
|
const remoteDb = new Databases(client);
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
const toCollectionExists = await tryAwaitWithRetry(async () => await remoteDb.listCollections(toDbId, [
|
336
|
-
Query.equal("$id", collection.$id),
|
337
|
-
]));
|
338
|
-
if (toCollectionExists.collections.length > 0) {
|
339
|
-
console.log(`Collection ${collection.name} already exists. Updating...`);
|
340
|
-
toCollection = toCollectionExists.collections[0];
|
341
|
-
// Update collection if needed
|
342
|
-
if (toCollection.name !== collection.name ||
|
343
|
-
toCollection.$permissions !== collection.$permissions ||
|
344
|
-
toCollection.documentSecurity !== collection.documentSecurity ||
|
345
|
-
toCollection.enabled !== collection.enabled) {
|
346
|
-
toCollection = await tryAwaitWithRetry(async () => await remoteDb.updateCollection(toDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
|
347
|
-
console.log(`Collection ${toCollection.name} updated`);
|
348
|
-
}
|
349
|
-
}
|
350
|
-
else {
|
351
|
-
toCollection = await tryAwaitWithRetry(async () => await remoteDb.createCollection(toDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
|
352
|
-
console.log(`Collection ${toCollection.name} created`);
|
353
|
-
}
|
354
|
-
// Check and update attributes
|
355
|
-
const existingAttributes = await tryAwaitWithRetry(async () => await remoteDb.listAttributes(toDbId, toCollection.$id));
|
356
|
-
for (const attribute of collection.attributes) {
|
357
|
-
const parsedAttribute = parseAttribute(attribute);
|
358
|
-
const existingAttribute = existingAttributes.attributes.find(
|
359
|
-
// @ts-expect-error
|
360
|
-
(attr) => attr.key === parsedAttribute.key);
|
361
|
-
if (!existingAttribute) {
|
362
|
-
await tryAwaitWithRetry(async () => await createOrUpdateAttribute(remoteDb, toDbId, toCollection, parsedAttribute));
|
363
|
-
console.log(`Attribute ${parsedAttribute.key} created`);
|
318
|
+
// Get all collections from source database
|
319
|
+
const sourceCollections = await fetchAllCollections(fromDbId, localDb);
|
320
|
+
console.log(chalk.blue(`Found ${sourceCollections.length} collections in source database`));
|
321
|
+
// Process each collection
|
322
|
+
for (const collection of sourceCollections) {
|
323
|
+
console.log(chalk.yellow(`Processing collection: ${collection.name} (${collection.$id})`));
|
324
|
+
try {
|
325
|
+
// Create or update collection in target
|
326
|
+
let targetCollection;
|
327
|
+
const existingCollection = await tryAwaitWithRetry(async () => remoteDb.listCollections(toDbId, [Query.equal("$id", collection.$id)]));
|
328
|
+
if (existingCollection.collections.length > 0) {
|
329
|
+
targetCollection = existingCollection.collections[0];
|
330
|
+
console.log(chalk.green(`Collection ${collection.name} exists in remote database`));
|
331
|
+
// Update collection if needed
|
332
|
+
if (targetCollection.name !== collection.name ||
|
333
|
+
targetCollection.$permissions !== collection.$permissions ||
|
334
|
+
targetCollection.documentSecurity !== collection.documentSecurity ||
|
335
|
+
targetCollection.enabled !== collection.enabled) {
|
336
|
+
targetCollection = await tryAwaitWithRetry(async () => remoteDb.updateCollection(toDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
|
337
|
+
console.log(chalk.green(`Collection ${collection.name} updated`));
|
338
|
+
}
|
364
339
|
}
|
365
340
|
else {
|
366
|
-
|
367
|
-
|
368
|
-
// You might need to delete and recreate the attribute if significant changes are needed
|
369
|
-
console.log(`Attribute ${parsedAttribute.key} already exists`);
|
341
|
+
console.log(chalk.yellow(`Creating collection ${collection.name} in remote database...`));
|
342
|
+
targetCollection = await tryAwaitWithRetry(async () => remoteDb.createCollection(toDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
|
370
343
|
}
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
344
|
+
// Handle attributes
|
345
|
+
const existingAttributes = await tryAwaitWithRetry(async () => await remoteDb.listAttributes(toDbId, targetCollection.$id));
|
346
|
+
for (const attribute of collection.attributes) {
|
347
|
+
const parsedAttribute = parseAttribute(attribute);
|
348
|
+
const existingAttribute = existingAttributes.attributes.find((attr) => attr.key === parsedAttribute.key);
|
349
|
+
if (!existingAttribute) {
|
350
|
+
await tryAwaitWithRetry(async () => createOrUpdateAttribute(remoteDb, toDbId, targetCollection, parsedAttribute));
|
351
|
+
console.log(chalk.green(`Attribute ${parsedAttribute.key} created`));
|
352
|
+
}
|
353
|
+
else {
|
354
|
+
console.log(chalk.blue(`Attribute ${parsedAttribute.key} exists, checking for updates...`));
|
355
|
+
await tryAwaitWithRetry(async () => createOrUpdateAttribute(remoteDb, toDbId, targetCollection, parsedAttribute));
|
356
|
+
}
|
379
357
|
}
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
358
|
+
// Handle indexes
|
359
|
+
const existingIndexes = await tryAwaitWithRetry(async () => await remoteDb.listIndexes(toDbId, targetCollection.$id));
|
360
|
+
for (const index of collection.indexes) {
|
361
|
+
const existingIndex = existingIndexes.indexes.find((idx) => idx.key === index.key);
|
362
|
+
if (!existingIndex) {
|
363
|
+
await createOrUpdateIndex(toDbId, remoteDb, targetCollection.$id, index);
|
364
|
+
console.log(chalk.green(`Index ${index.key} created`));
|
365
|
+
}
|
366
|
+
else {
|
367
|
+
console.log(chalk.blue(`Index ${index.key} exists, checking for updates...`));
|
368
|
+
await createOrUpdateIndex(toDbId, remoteDb, targetCollection.$id, index);
|
369
|
+
}
|
385
370
|
}
|
371
|
+
// Transfer documents
|
372
|
+
await transferDocumentsBetweenDbsLocalToRemote(localDb, endpoint, projectId, apiKey, fromDbId, toDbId, collection.$id, targetCollection.$id);
|
373
|
+
}
|
374
|
+
catch (error) {
|
375
|
+
console.error(chalk.red(`Error processing collection ${collection.name}:`), error);
|
386
376
|
}
|
387
|
-
await transferDocumentsBetweenDbsLocalToRemote(localDb, endpoint, projectId, apiKey, fromDbId, toDbId, collection.$id, toCollection.$id);
|
388
377
|
}
|
389
378
|
};
|
@@ -11,3 +11,4 @@ export declare const findAppwriteConfig: (dir: string) => string | null;
|
|
11
11
|
* @returns The loaded Appwrite configuration including collections.
|
12
12
|
*/
|
13
13
|
export declare const loadConfig: (configDir: string) => Promise<AppwriteConfig>;
|
14
|
+
export declare const findFunctionsDir: (dir: string) => string | null;
|
@@ -3,6 +3,7 @@ import fs from "fs";
|
|
3
3
|
import {} from "appwrite-utils";
|
4
4
|
import { register } from "tsx/esm/api"; // Import the register function
|
5
5
|
import { pathToFileURL } from "node:url";
|
6
|
+
import chalk from "chalk";
|
6
7
|
/**
|
7
8
|
* Recursively searches for a file named 'appwriteConfig.ts' starting from the given directory.
|
8
9
|
* @param dir The directory to start the search from.
|
@@ -66,3 +67,21 @@ export const loadConfig = async (configDir) => {
|
|
66
67
|
unregister(); // Unregister tsx when done
|
67
68
|
}
|
68
69
|
};
|
70
|
+
export const findFunctionsDir = (dir) => {
|
71
|
+
if (dir === "node_modules") {
|
72
|
+
return null;
|
73
|
+
}
|
74
|
+
const files = fs.readdirSync(dir, { withFileTypes: true });
|
75
|
+
for (const entry of files) {
|
76
|
+
if (!entry.isDirectory() || entry.name === "node_modules") {
|
77
|
+
continue;
|
78
|
+
}
|
79
|
+
if (entry.name === "functions") {
|
80
|
+
return path.join(dir, entry.name);
|
81
|
+
}
|
82
|
+
const result = findFunctionsDir(path.join(dir, entry.name));
|
83
|
+
if (result)
|
84
|
+
return result;
|
85
|
+
}
|
86
|
+
return null;
|
87
|
+
};
|
@@ -4,6 +4,8 @@ import { z } from "zod";
|
|
4
4
|
import fs from "fs";
|
5
5
|
import path from "path";
|
6
6
|
import { dump } from "js-yaml";
|
7
|
+
import { findFunctionsDir } from "./loadConfigs.js";
|
8
|
+
import { ulid } from "ulidx";
|
7
9
|
export class SchemaGenerator {
|
8
10
|
relationshipMap = new Map();
|
9
11
|
config;
|
@@ -15,7 +17,9 @@ export class SchemaGenerator {
|
|
15
17
|
}
|
16
18
|
updateTsSchemas() {
|
17
19
|
const collections = this.config.collections;
|
20
|
+
const functions = this.config.functions || [];
|
18
21
|
delete this.config.collections;
|
22
|
+
delete this.config.functions;
|
19
23
|
const configPath = path.join(this.appwriteFolderPath, "appwriteConfig.ts");
|
20
24
|
const configContent = `import { type AppwriteConfig } from "appwrite-utils";
|
21
25
|
|
@@ -32,6 +36,29 @@ export class SchemaGenerator {
|
|
32
36
|
usersCollectionName: "${this.config.usersCollectionName}",
|
33
37
|
databases: ${JSON.stringify(this.config.databases)},
|
34
38
|
buckets: ${JSON.stringify(this.config.buckets)},
|
39
|
+
functions: ${JSON.stringify(functions.map(func => ({
|
40
|
+
functionId: func.$id || ulid(),
|
41
|
+
name: func.name,
|
42
|
+
runtime: func.runtime,
|
43
|
+
path: func.dirPath || `functions/${func.name}`,
|
44
|
+
entrypoint: func.entrypoint || 'src/index.ts',
|
45
|
+
execute: func.execute,
|
46
|
+
events: func.events || [],
|
47
|
+
schedule: func.schedule || '',
|
48
|
+
timeout: func.timeout || 15,
|
49
|
+
enabled: func.enabled !== false,
|
50
|
+
logging: func.logging !== false,
|
51
|
+
commands: func.commands || 'npm install',
|
52
|
+
scopes: func.scopes || [],
|
53
|
+
installationId: func.installationId,
|
54
|
+
providerRepositoryId: func.providerRepositoryId,
|
55
|
+
providerBranch: func.providerBranch,
|
56
|
+
providerSilentMode: func.providerSilentMode,
|
57
|
+
providerRootDirectory: func.providerRootDirectory,
|
58
|
+
specification: func.specification,
|
59
|
+
...(func.predeployCommands ? { predeployCommands: func.predeployCommands } : {}),
|
60
|
+
...(func.deployDir ? { deployDir: func.deployDir } : {})
|
61
|
+
})), null, 2)}
|
35
62
|
};
|
36
63
|
|
37
64
|
export default appwriteConfig;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Client, Databases, Storage, type Models } from "node-appwrite";
|
2
|
-
import { type AppwriteConfig, type Specification } from "appwrite-utils";
|
2
|
+
import { type AppwriteConfig, type AppwriteFunction, type Specification } from "appwrite-utils";
|
3
3
|
import { type AfterImportActions, type ConverterFunctions, type ValidationRules } from "appwrite-utils";
|
4
4
|
import { type TransferOptions } from "./migrations/transfer.js";
|
5
5
|
export interface SetupOptions {
|
@@ -36,6 +36,10 @@ export declare class UtilsController {
|
|
36
36
|
wipeOtherDatabases(databasesToKeep: Models.Database[]): Promise<void>;
|
37
37
|
wipeUsers(): Promise<void>;
|
38
38
|
backupDatabase(database: Models.Database): Promise<void>;
|
39
|
+
listAllFunctions(): Promise<Models.Function[]>;
|
40
|
+
findFunctionDirectories(): Promise<Map<any, any>>;
|
41
|
+
deployFunction(functionName: string, functionPath?: string, functionConfig?: AppwriteFunction): Promise<void>;
|
42
|
+
syncFunctions(): Promise<void>;
|
39
43
|
wipeDatabase(database: Models.Database, wipeBucket?: boolean): Promise<void>;
|
40
44
|
wipeBucketFromDatabase(database: Models.Database): Promise<void>;
|
41
45
|
wipeCollection(database: Models.Database, collection: Models.Collection): Promise<void>;
|
package/dist/utilsController.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Client, Databases, Query, Storage } from "node-appwrite";
|
2
2
|
import {} from "appwrite-utils";
|
3
|
-
import { loadConfig, findAppwriteConfig } from "./utils/loadConfigs.js";
|
3
|
+
import { loadConfig, findAppwriteConfig, findFunctionsDir } from "./utils/loadConfigs.js";
|
4
4
|
import { UsersController } from "./migrations/users.js";
|
5
5
|
import { AppwriteToX } from "./migrations/appwriteToX.js";
|
6
6
|
import { ImportController } from "./migrations/importController.js";
|
@@ -14,8 +14,10 @@ import { afterImportActions } from "./migrations/afterImportActions.js";
|
|
14
14
|
import { transferDatabaseLocalToLocal, transferDatabaseLocalToRemote, transferStorageLocalToLocal, transferStorageLocalToRemote, } from "./migrations/transfer.js";
|
15
15
|
import { getClient } from "./utils/getClientFromConfig.js";
|
16
16
|
import { fetchAllDatabases } from "./migrations/databases.js";
|
17
|
-
import { updateFunctionSpecifications } from "./functions/methods.js";
|
17
|
+
import { listFunctions, updateFunctionSpecifications } from "./functions/methods.js";
|
18
18
|
import chalk from "chalk";
|
19
|
+
import { deployLocalFunction } from "./functions/deployments.js";
|
20
|
+
import fs from "node:fs";
|
19
21
|
export class UtilsController {
|
20
22
|
appwriteFolderPath;
|
21
23
|
appwriteConfigPath;
|
@@ -125,6 +127,56 @@ export class UtilsController {
|
|
125
127
|
throw new Error("Database, storage, or config not initialized");
|
126
128
|
await backupDatabase(this.config, this.database, database.$id, this.storage);
|
127
129
|
}
|
130
|
+
async listAllFunctions() {
|
131
|
+
await this.init();
|
132
|
+
if (!this.appwriteServer)
|
133
|
+
throw new Error("Appwrite server not initialized");
|
134
|
+
const { functions } = await listFunctions(this.appwriteServer, [Query.limit(1000)]);
|
135
|
+
return functions;
|
136
|
+
}
|
137
|
+
async findFunctionDirectories() {
|
138
|
+
const functionsDir = findFunctionsDir(this.appwriteFolderPath);
|
139
|
+
if (!functionsDir)
|
140
|
+
return new Map();
|
141
|
+
const functionDirMap = new Map();
|
142
|
+
const entries = fs.readdirSync(functionsDir, { withFileTypes: true });
|
143
|
+
for (const entry of entries) {
|
144
|
+
if (entry.isDirectory()) {
|
145
|
+
const functionPath = path.join(functionsDir, entry.name);
|
146
|
+
// Match with config functions by name
|
147
|
+
if (this.config?.functions) {
|
148
|
+
const matchingFunc = this.config.functions.find(f => f.name.toLowerCase() === entry.name.toLowerCase());
|
149
|
+
if (matchingFunc) {
|
150
|
+
functionDirMap.set(matchingFunc.name, functionPath);
|
151
|
+
}
|
152
|
+
}
|
153
|
+
}
|
154
|
+
}
|
155
|
+
return functionDirMap;
|
156
|
+
}
|
157
|
+
async deployFunction(functionName, functionPath, functionConfig) {
|
158
|
+
await this.init();
|
159
|
+
if (!this.appwriteServer)
|
160
|
+
throw new Error("Appwrite server not initialized");
|
161
|
+
if (!functionConfig) {
|
162
|
+
functionConfig = this.config?.functions?.find(f => f.name === functionName);
|
163
|
+
}
|
164
|
+
if (!functionConfig)
|
165
|
+
throw new Error(`Function ${functionName} not found in config`);
|
166
|
+
await deployLocalFunction(this.appwriteServer, functionName, functionConfig, functionPath);
|
167
|
+
}
|
168
|
+
async syncFunctions() {
|
169
|
+
await this.init();
|
170
|
+
if (!this.appwriteServer)
|
171
|
+
throw new Error("Appwrite server not initialized");
|
172
|
+
const localFunctions = this.config?.functions || [];
|
173
|
+
const remoteFunctions = await listFunctions(this.appwriteServer, [Query.limit(1000)]);
|
174
|
+
for (const localFunction of localFunctions) {
|
175
|
+
console.log(chalk.blue(`Syncing function ${localFunction.name}...`));
|
176
|
+
await this.deployFunction(localFunction.name);
|
177
|
+
}
|
178
|
+
console.log(chalk.green("✨ All functions synchronized successfully!"));
|
179
|
+
}
|
128
180
|
async wipeDatabase(database, wipeBucket = false) {
|
129
181
|
await this.init();
|
130
182
|
if (!this.database)
|