appwrite-utils-cli 1.0.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/collections/attributes.d.ts +8 -0
- package/dist/collections/attributes.js +195 -0
- package/dist/collections/indexes.d.ts +8 -0
- package/dist/collections/indexes.js +150 -0
- package/dist/collections/methods.js +105 -53
- package/dist/interactiveCLI.js +134 -42
- package/dist/migrations/transfer.js +29 -40
- package/package.json +1 -1
- package/src/collections/attributes.ts +339 -0
- package/src/collections/indexes.ts +264 -0
- package/src/collections/methods.ts +175 -87
- package/src/interactiveCLI.ts +137 -42
- package/src/migrations/transfer.ts +48 -99
package/src/interactiveCLI.ts
CHANGED
@@ -2052,49 +2052,144 @@ export class InteractiveCLI {
|
|
2052
2052
|
MessageFormatter.info("Starting comprehensive transfer configuration...", { prefix: "Transfer" });
|
2053
2053
|
|
2054
2054
|
try {
|
2055
|
-
//
|
2056
|
-
const
|
2057
|
-
|
2058
|
-
|
2059
|
-
name: "sourceEndpoint",
|
2060
|
-
message: "Enter the source Appwrite endpoint:",
|
2061
|
-
validate: (input) => input.trim() !== "" || "Endpoint cannot be empty",
|
2062
|
-
},
|
2063
|
-
{
|
2064
|
-
type: "input",
|
2065
|
-
name: "sourceProject",
|
2066
|
-
message: "Enter the source project ID:",
|
2067
|
-
validate: (input) => input.trim() !== "" || "Project ID cannot be empty",
|
2068
|
-
},
|
2069
|
-
{
|
2070
|
-
type: "password",
|
2071
|
-
name: "sourceKey",
|
2072
|
-
message: "Enter the source API key:",
|
2073
|
-
validate: (input) => input.trim() !== "" || "API key cannot be empty",
|
2074
|
-
},
|
2075
|
-
]);
|
2055
|
+
// Check if user has an appwrite config for easier setup
|
2056
|
+
const hasAppwriteConfig = this.controller?.config?.appwriteEndpoint &&
|
2057
|
+
this.controller?.config?.appwriteProject &&
|
2058
|
+
this.controller?.config?.appwriteKey;
|
2076
2059
|
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
2080
|
-
|
2081
|
-
|
2082
|
-
|
2083
|
-
|
2084
|
-
|
2085
|
-
|
2086
|
-
|
2087
|
-
|
2088
|
-
|
2089
|
-
|
2090
|
-
|
2091
|
-
{
|
2092
|
-
|
2093
|
-
|
2094
|
-
|
2095
|
-
|
2096
|
-
|
2097
|
-
|
2060
|
+
let sourceConfig: any;
|
2061
|
+
let targetConfig: any;
|
2062
|
+
|
2063
|
+
if (hasAppwriteConfig) {
|
2064
|
+
// Offer to use existing config for source
|
2065
|
+
const { useConfigForSource } = await inquirer.prompt([
|
2066
|
+
{
|
2067
|
+
type: "confirm",
|
2068
|
+
name: "useConfigForSource",
|
2069
|
+
message: "Use your current appwriteConfig as the source?",
|
2070
|
+
default: true,
|
2071
|
+
},
|
2072
|
+
]);
|
2073
|
+
|
2074
|
+
if (useConfigForSource) {
|
2075
|
+
sourceConfig = {
|
2076
|
+
sourceEndpoint: this.controller!.config!.appwriteEndpoint,
|
2077
|
+
sourceProject: this.controller!.config!.appwriteProject,
|
2078
|
+
sourceKey: this.controller!.config!.appwriteKey,
|
2079
|
+
};
|
2080
|
+
MessageFormatter.info(`Using config source: ${sourceConfig.sourceEndpoint}`, { prefix: "Transfer" });
|
2081
|
+
} else {
|
2082
|
+
// Get source configuration manually
|
2083
|
+
sourceConfig = await inquirer.prompt([
|
2084
|
+
{
|
2085
|
+
type: "input",
|
2086
|
+
name: "sourceEndpoint",
|
2087
|
+
message: "Enter the source Appwrite endpoint:",
|
2088
|
+
validate: (input) => input.trim() !== "" || "Endpoint cannot be empty",
|
2089
|
+
},
|
2090
|
+
{
|
2091
|
+
type: "input",
|
2092
|
+
name: "sourceProject",
|
2093
|
+
message: "Enter the source project ID:",
|
2094
|
+
validate: (input) => input.trim() !== "" || "Project ID cannot be empty",
|
2095
|
+
},
|
2096
|
+
{
|
2097
|
+
type: "password",
|
2098
|
+
name: "sourceKey",
|
2099
|
+
message: "Enter the source API key:",
|
2100
|
+
validate: (input) => input.trim() !== "" || "API key cannot be empty",
|
2101
|
+
},
|
2102
|
+
]);
|
2103
|
+
}
|
2104
|
+
|
2105
|
+
// Offer to use existing config for target
|
2106
|
+
const { useConfigForTarget } = await inquirer.prompt([
|
2107
|
+
{
|
2108
|
+
type: "confirm",
|
2109
|
+
name: "useConfigForTarget",
|
2110
|
+
message: "Use your current appwriteConfig as the target?",
|
2111
|
+
default: false,
|
2112
|
+
},
|
2113
|
+
]);
|
2114
|
+
|
2115
|
+
if (useConfigForTarget) {
|
2116
|
+
targetConfig = {
|
2117
|
+
targetEndpoint: this.controller!.config!.appwriteEndpoint,
|
2118
|
+
targetProject: this.controller!.config!.appwriteProject,
|
2119
|
+
targetKey: this.controller!.config!.appwriteKey,
|
2120
|
+
};
|
2121
|
+
MessageFormatter.info(`Using config target: ${targetConfig.targetEndpoint}`, { prefix: "Transfer" });
|
2122
|
+
} else {
|
2123
|
+
// Get target configuration manually
|
2124
|
+
targetConfig = await inquirer.prompt([
|
2125
|
+
{
|
2126
|
+
type: "input",
|
2127
|
+
name: "targetEndpoint",
|
2128
|
+
message: "Enter the target Appwrite endpoint:",
|
2129
|
+
validate: (input) => input.trim() !== "" || "Endpoint cannot be empty",
|
2130
|
+
},
|
2131
|
+
{
|
2132
|
+
type: "input",
|
2133
|
+
name: "targetProject",
|
2134
|
+
message: "Enter the target project ID:",
|
2135
|
+
validate: (input) => input.trim() !== "" || "Project ID cannot be empty",
|
2136
|
+
},
|
2137
|
+
{
|
2138
|
+
type: "password",
|
2139
|
+
name: "targetKey",
|
2140
|
+
message: "Enter the target API key:",
|
2141
|
+
validate: (input) => input.trim() !== "" || "API key cannot be empty",
|
2142
|
+
},
|
2143
|
+
]);
|
2144
|
+
}
|
2145
|
+
} else {
|
2146
|
+
// No appwrite config found, get both configurations manually
|
2147
|
+
MessageFormatter.info("No appwriteConfig found, please enter source and target configurations manually", { prefix: "Transfer" });
|
2148
|
+
|
2149
|
+
// Get source configuration
|
2150
|
+
sourceConfig = await inquirer.prompt([
|
2151
|
+
{
|
2152
|
+
type: "input",
|
2153
|
+
name: "sourceEndpoint",
|
2154
|
+
message: "Enter the source Appwrite endpoint:",
|
2155
|
+
validate: (input) => input.trim() !== "" || "Endpoint cannot be empty",
|
2156
|
+
},
|
2157
|
+
{
|
2158
|
+
type: "input",
|
2159
|
+
name: "sourceProject",
|
2160
|
+
message: "Enter the source project ID:",
|
2161
|
+
validate: (input) => input.trim() !== "" || "Project ID cannot be empty",
|
2162
|
+
},
|
2163
|
+
{
|
2164
|
+
type: "password",
|
2165
|
+
name: "sourceKey",
|
2166
|
+
message: "Enter the source API key:",
|
2167
|
+
validate: (input) => input.trim() !== "" || "API key cannot be empty",
|
2168
|
+
},
|
2169
|
+
]);
|
2170
|
+
|
2171
|
+
// Get target configuration
|
2172
|
+
targetConfig = await inquirer.prompt([
|
2173
|
+
{
|
2174
|
+
type: "input",
|
2175
|
+
name: "targetEndpoint",
|
2176
|
+
message: "Enter the target Appwrite endpoint:",
|
2177
|
+
validate: (input) => input.trim() !== "" || "Endpoint cannot be empty",
|
2178
|
+
},
|
2179
|
+
{
|
2180
|
+
type: "input",
|
2181
|
+
name: "targetProject",
|
2182
|
+
message: "Enter the target project ID:",
|
2183
|
+
validate: (input) => input.trim() !== "" || "Project ID cannot be empty",
|
2184
|
+
},
|
2185
|
+
{
|
2186
|
+
type: "password",
|
2187
|
+
name: "targetKey",
|
2188
|
+
message: "Enter the target API key:",
|
2189
|
+
validate: (input) => input.trim() !== "" || "API key cannot be empty",
|
2190
|
+
},
|
2191
|
+
]);
|
2192
|
+
}
|
2098
2193
|
|
2099
2194
|
// Get transfer options
|
2100
2195
|
const transferOptions = await inquirer.prompt([
|
@@ -13,6 +13,7 @@ import { getAppwriteClient } from "../utils/helperFunctions.js";
|
|
13
13
|
import {
|
14
14
|
createOrUpdateAttribute,
|
15
15
|
createUpdateCollectionAttributes,
|
16
|
+
createUpdateCollectionAttributesWithStatusCheck,
|
16
17
|
} from "../collections/attributes.js";
|
17
18
|
import { parseAttribute } from "appwrite-utils";
|
18
19
|
import chalk from "chalk";
|
@@ -22,6 +23,7 @@ import { ProgressManager } from "../shared/progressManager.js";
|
|
22
23
|
import {
|
23
24
|
createOrUpdateIndex,
|
24
25
|
createOrUpdateIndexes,
|
26
|
+
createOrUpdateIndexesWithStatusCheck,
|
25
27
|
} from "../collections/indexes.js";
|
26
28
|
import { getClient } from "../utils/getClientFromConfig.js";
|
27
29
|
|
@@ -305,44 +307,23 @@ export const transferDatabaseLocalToLocal = async (
|
|
305
307
|
);
|
306
308
|
}
|
307
309
|
|
308
|
-
// Handle attributes
|
309
|
-
|
310
|
-
|
311
|
-
|
310
|
+
// Handle attributes with enhanced status checking
|
311
|
+
console.log(chalk.blue(`Creating attributes for collection ${collection.name} with enhanced monitoring...`));
|
312
|
+
|
313
|
+
const allAttributes = collection.attributes.map(attr => parseAttribute(attr as any));
|
314
|
+
const attributeSuccess = await createUpdateCollectionAttributesWithStatusCheck(
|
315
|
+
localDb,
|
316
|
+
targetDbId,
|
317
|
+
targetCollection,
|
318
|
+
allAttributes
|
312
319
|
);
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
(attr: any) => attr.key === parsedAttribute.key
|
318
|
-
);
|
319
|
-
|
320
|
-
if (!existingAttribute) {
|
321
|
-
await tryAwaitWithRetry(async () =>
|
322
|
-
createOrUpdateAttribute(
|
323
|
-
localDb,
|
324
|
-
targetDbId,
|
325
|
-
targetCollection,
|
326
|
-
parsedAttribute
|
327
|
-
)
|
328
|
-
);
|
329
|
-
console.log(chalk.green(`Attribute ${parsedAttribute.key} created`));
|
330
|
-
} else {
|
331
|
-
console.log(
|
332
|
-
chalk.blue(
|
333
|
-
`Attribute ${parsedAttribute.key} exists, checking for updates...`
|
334
|
-
)
|
335
|
-
);
|
336
|
-
await tryAwaitWithRetry(async () =>
|
337
|
-
createOrUpdateAttribute(
|
338
|
-
localDb,
|
339
|
-
targetDbId,
|
340
|
-
targetCollection,
|
341
|
-
parsedAttribute
|
342
|
-
)
|
343
|
-
);
|
344
|
-
}
|
320
|
+
|
321
|
+
if (!attributeSuccess) {
|
322
|
+
console.log(chalk.red(`❌ Failed to create all attributes for collection ${collection.name}, skipping to next collection`));
|
323
|
+
continue;
|
345
324
|
}
|
325
|
+
|
326
|
+
console.log(chalk.green(`✅ All attributes created successfully for collection ${collection.name}`));
|
346
327
|
|
347
328
|
// Handle indexes
|
348
329
|
const existingIndexes = await tryAwaitWithRetry(
|
@@ -474,73 +455,41 @@ export const transferDatabaseLocalToRemote = async (
|
|
474
455
|
);
|
475
456
|
}
|
476
457
|
|
477
|
-
// Handle attributes
|
478
|
-
|
479
|
-
|
458
|
+
// Handle attributes with enhanced status checking
|
459
|
+
console.log(chalk.blue(`Creating attributes for collection ${collection.name} with enhanced monitoring...`));
|
460
|
+
|
461
|
+
const attributesToCreate = collection.attributes.map(attr => parseAttribute(attr as any));
|
462
|
+
|
463
|
+
const attributesSuccess = await createUpdateCollectionAttributesWithStatusCheck(
|
464
|
+
remoteDb,
|
465
|
+
toDbId,
|
466
|
+
targetCollection,
|
467
|
+
attributesToCreate
|
480
468
|
);
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
);
|
487
|
-
|
488
|
-
if (!existingAttribute) {
|
489
|
-
await tryAwaitWithRetry(async () =>
|
490
|
-
createOrUpdateAttribute(
|
491
|
-
remoteDb,
|
492
|
-
toDbId,
|
493
|
-
targetCollection,
|
494
|
-
parsedAttribute
|
495
|
-
)
|
496
|
-
);
|
497
|
-
console.log(chalk.green(`Attribute ${parsedAttribute.key} created`));
|
498
|
-
} else {
|
499
|
-
console.log(
|
500
|
-
chalk.blue(
|
501
|
-
`Attribute ${parsedAttribute.key} exists, checking for updates...`
|
502
|
-
)
|
503
|
-
);
|
504
|
-
await tryAwaitWithRetry(async () =>
|
505
|
-
createOrUpdateAttribute(
|
506
|
-
remoteDb,
|
507
|
-
toDbId,
|
508
|
-
targetCollection,
|
509
|
-
parsedAttribute
|
510
|
-
)
|
511
|
-
);
|
512
|
-
}
|
469
|
+
|
470
|
+
if (!attributesSuccess) {
|
471
|
+
console.log(chalk.red(`Failed to create some attributes for collection ${collection.name}`));
|
472
|
+
// Continue with the transfer even if some attributes failed
|
473
|
+
} else {
|
474
|
+
console.log(chalk.green(`All attributes created successfully for collection ${collection.name}`));
|
513
475
|
}
|
514
476
|
|
515
|
-
// Handle indexes
|
516
|
-
|
517
|
-
|
477
|
+
// Handle indexes with enhanced status checking
|
478
|
+
console.log(chalk.blue(`Creating indexes for collection ${collection.name} with enhanced monitoring...`));
|
479
|
+
|
480
|
+
const indexesSuccess = await createOrUpdateIndexesWithStatusCheck(
|
481
|
+
toDbId,
|
482
|
+
remoteDb,
|
483
|
+
targetCollection.$id,
|
484
|
+
targetCollection,
|
485
|
+
collection.indexes as any
|
518
486
|
);
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
if (!existingIndex) {
|
526
|
-
await createOrUpdateIndex(
|
527
|
-
toDbId,
|
528
|
-
remoteDb,
|
529
|
-
targetCollection.$id,
|
530
|
-
index as any
|
531
|
-
);
|
532
|
-
console.log(chalk.green(`Index ${index.key} created`));
|
533
|
-
} else {
|
534
|
-
console.log(
|
535
|
-
chalk.blue(`Index ${index.key} exists, checking for updates...`)
|
536
|
-
);
|
537
|
-
await createOrUpdateIndex(
|
538
|
-
toDbId,
|
539
|
-
remoteDb,
|
540
|
-
targetCollection.$id,
|
541
|
-
index as any
|
542
|
-
);
|
543
|
-
}
|
487
|
+
|
488
|
+
if (!indexesSuccess) {
|
489
|
+
console.log(chalk.red(`Failed to create some indexes for collection ${collection.name}`));
|
490
|
+
// Continue with the transfer even if some indexes failed
|
491
|
+
} else {
|
492
|
+
console.log(chalk.green(`All indexes created successfully for collection ${collection.name}`));
|
544
493
|
}
|
545
494
|
|
546
495
|
// Transfer documents
|