@njdamstra/appwrite-utils-cli 1.10.1 → 1.11.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.
@@ -0,0 +1,195 @@
1
+ import { z } from "zod";
2
+ export declare const MigrationTargetType: z.ZodEnum<{
3
+ varchar: "varchar";
4
+ text: "text";
5
+ mediumtext: "mediumtext";
6
+ longtext: "longtext";
7
+ }>;
8
+ export type MigrationTargetType = z.infer<typeof MigrationTargetType>;
9
+ export declare const MigrationAction: z.ZodEnum<{
10
+ skip: "skip";
11
+ migrate: "migrate";
12
+ }>;
13
+ export type MigrationAction = z.infer<typeof MigrationAction>;
14
+ export declare const MigrationPlanEntrySchema: z.ZodObject<{
15
+ databaseId: z.ZodString;
16
+ databaseName: z.ZodString;
17
+ collectionId: z.ZodString;
18
+ collectionName: z.ZodString;
19
+ attributeKey: z.ZodString;
20
+ currentType: z.ZodDefault<z.ZodString>;
21
+ currentSize: z.ZodNumber;
22
+ isRequired: z.ZodDefault<z.ZodBoolean>;
23
+ isArray: z.ZodDefault<z.ZodBoolean>;
24
+ isEncrypted: z.ZodDefault<z.ZodBoolean>;
25
+ hasDefault: z.ZodDefault<z.ZodBoolean>;
26
+ defaultValue: z.ZodOptional<z.ZodAny>;
27
+ suggestedType: z.ZodEnum<{
28
+ varchar: "varchar";
29
+ text: "text";
30
+ mediumtext: "mediumtext";
31
+ longtext: "longtext";
32
+ }>;
33
+ targetType: z.ZodEnum<{
34
+ varchar: "varchar";
35
+ text: "text";
36
+ mediumtext: "mediumtext";
37
+ longtext: "longtext";
38
+ }>;
39
+ targetSize: z.ZodOptional<z.ZodNumber>;
40
+ action: z.ZodEnum<{
41
+ skip: "skip";
42
+ migrate: "migrate";
43
+ }>;
44
+ skipReason: z.ZodOptional<z.ZodString>;
45
+ indexesAffected: z.ZodDefault<z.ZodArray<z.ZodString>>;
46
+ }, z.core.$strip>;
47
+ export type MigrationPlanEntry = z.infer<typeof MigrationPlanEntrySchema>;
48
+ export declare const MigrationPlanSchema: z.ZodObject<{
49
+ version: z.ZodDefault<z.ZodNumber>;
50
+ generatedAt: z.ZodString;
51
+ appwriteEndpoint: z.ZodOptional<z.ZodString>;
52
+ appwriteProject: z.ZodOptional<z.ZodString>;
53
+ summary: z.ZodObject<{
54
+ totalStringAttributes: z.ZodNumber;
55
+ toMigrate: z.ZodNumber;
56
+ toSkip: z.ZodNumber;
57
+ databaseCount: z.ZodNumber;
58
+ collectionCount: z.ZodNumber;
59
+ }, z.core.$strip>;
60
+ entries: z.ZodArray<z.ZodObject<{
61
+ databaseId: z.ZodString;
62
+ databaseName: z.ZodString;
63
+ collectionId: z.ZodString;
64
+ collectionName: z.ZodString;
65
+ attributeKey: z.ZodString;
66
+ currentType: z.ZodDefault<z.ZodString>;
67
+ currentSize: z.ZodNumber;
68
+ isRequired: z.ZodDefault<z.ZodBoolean>;
69
+ isArray: z.ZodDefault<z.ZodBoolean>;
70
+ isEncrypted: z.ZodDefault<z.ZodBoolean>;
71
+ hasDefault: z.ZodDefault<z.ZodBoolean>;
72
+ defaultValue: z.ZodOptional<z.ZodAny>;
73
+ suggestedType: z.ZodEnum<{
74
+ varchar: "varchar";
75
+ text: "text";
76
+ mediumtext: "mediumtext";
77
+ longtext: "longtext";
78
+ }>;
79
+ targetType: z.ZodEnum<{
80
+ varchar: "varchar";
81
+ text: "text";
82
+ mediumtext: "mediumtext";
83
+ longtext: "longtext";
84
+ }>;
85
+ targetSize: z.ZodOptional<z.ZodNumber>;
86
+ action: z.ZodEnum<{
87
+ skip: "skip";
88
+ migrate: "migrate";
89
+ }>;
90
+ skipReason: z.ZodOptional<z.ZodString>;
91
+ indexesAffected: z.ZodDefault<z.ZodArray<z.ZodString>>;
92
+ }, z.core.$strip>>;
93
+ }, z.core.$strip>;
94
+ export type MigrationPlan = z.infer<typeof MigrationPlanSchema>;
95
+ export declare const CheckpointPhase: z.ZodEnum<{
96
+ pending: "pending";
97
+ completed: "completed";
98
+ failed: "failed";
99
+ backup_created: "backup_created";
100
+ data_copied_to_backup: "data_copied_to_backup";
101
+ data_verified_backup: "data_verified_backup";
102
+ original_deleted: "original_deleted";
103
+ new_attr_created: "new_attr_created";
104
+ data_copied_back: "data_copied_back";
105
+ data_verified_final: "data_verified_final";
106
+ backup_deleted: "backup_deleted";
107
+ }>;
108
+ export type CheckpointPhase = z.infer<typeof CheckpointPhase>;
109
+ export declare const CheckpointEntrySchema: z.ZodObject<{
110
+ databaseId: z.ZodString;
111
+ collectionId: z.ZodString;
112
+ attributeKey: z.ZodString;
113
+ backupKey: z.ZodString;
114
+ phase: z.ZodEnum<{
115
+ pending: "pending";
116
+ completed: "completed";
117
+ failed: "failed";
118
+ backup_created: "backup_created";
119
+ data_copied_to_backup: "data_copied_to_backup";
120
+ data_verified_backup: "data_verified_backup";
121
+ original_deleted: "original_deleted";
122
+ new_attr_created: "new_attr_created";
123
+ data_copied_back: "data_copied_back";
124
+ data_verified_final: "data_verified_final";
125
+ backup_deleted: "backup_deleted";
126
+ }>;
127
+ targetType: z.ZodEnum<{
128
+ varchar: "varchar";
129
+ text: "text";
130
+ mediumtext: "mediumtext";
131
+ longtext: "longtext";
132
+ }>;
133
+ targetSize: z.ZodOptional<z.ZodNumber>;
134
+ error: z.ZodOptional<z.ZodString>;
135
+ storedIndexes: z.ZodDefault<z.ZodArray<z.ZodObject<{
136
+ key: z.ZodString;
137
+ type: z.ZodString;
138
+ attributes: z.ZodArray<z.ZodString>;
139
+ orders: z.ZodOptional<z.ZodArray<z.ZodString>>;
140
+ }, z.core.$strip>>>;
141
+ }, z.core.$strip>;
142
+ export type CheckpointEntry = z.infer<typeof CheckpointEntrySchema>;
143
+ export declare const MigrationCheckpointSchema: z.ZodObject<{
144
+ planFile: z.ZodString;
145
+ startedAt: z.ZodString;
146
+ lastUpdatedAt: z.ZodString;
147
+ entries: z.ZodArray<z.ZodObject<{
148
+ databaseId: z.ZodString;
149
+ collectionId: z.ZodString;
150
+ attributeKey: z.ZodString;
151
+ backupKey: z.ZodString;
152
+ phase: z.ZodEnum<{
153
+ pending: "pending";
154
+ completed: "completed";
155
+ failed: "failed";
156
+ backup_created: "backup_created";
157
+ data_copied_to_backup: "data_copied_to_backup";
158
+ data_verified_backup: "data_verified_backup";
159
+ original_deleted: "original_deleted";
160
+ new_attr_created: "new_attr_created";
161
+ data_copied_back: "data_copied_back";
162
+ data_verified_final: "data_verified_final";
163
+ backup_deleted: "backup_deleted";
164
+ }>;
165
+ targetType: z.ZodEnum<{
166
+ varchar: "varchar";
167
+ text: "text";
168
+ mediumtext: "mediumtext";
169
+ longtext: "longtext";
170
+ }>;
171
+ targetSize: z.ZodOptional<z.ZodNumber>;
172
+ error: z.ZodOptional<z.ZodString>;
173
+ storedIndexes: z.ZodDefault<z.ZodArray<z.ZodObject<{
174
+ key: z.ZodString;
175
+ type: z.ZodString;
176
+ attributes: z.ZodArray<z.ZodString>;
177
+ orders: z.ZodOptional<z.ZodArray<z.ZodString>>;
178
+ }, z.core.$strip>>>;
179
+ }, z.core.$strip>>;
180
+ }, z.core.$strip>;
181
+ export type MigrationCheckpoint = z.infer<typeof MigrationCheckpointSchema>;
182
+ export interface AnalyzeOptions {
183
+ outputPath?: string;
184
+ verbose?: boolean;
185
+ }
186
+ export interface ExecuteOptions {
187
+ planPath: string;
188
+ keepBackups?: boolean;
189
+ dryRun?: boolean;
190
+ batchSize?: number;
191
+ batchDelayMs?: number;
192
+ checkpointPath?: string;
193
+ }
194
+ export declare function suggestTargetType(size: number, hasIndex: boolean): MigrationTargetType;
195
+ export declare function generateBackupKey(originalKey: string): string;
@@ -0,0 +1,117 @@
1
+ import { z } from "zod";
2
+ // ── Target types for string attribute migration ──
3
+ export const MigrationTargetType = z.enum([
4
+ "varchar",
5
+ "text",
6
+ "mediumtext",
7
+ "longtext",
8
+ ]);
9
+ export const MigrationAction = z.enum(["migrate", "skip"]);
10
+ // ── Plan entry: one per attribute to migrate ──
11
+ export const MigrationPlanEntrySchema = z.object({
12
+ databaseId: z.string(),
13
+ databaseName: z.string(),
14
+ collectionId: z.string(),
15
+ collectionName: z.string(),
16
+ attributeKey: z.string(),
17
+ currentType: z.string().default("string"),
18
+ currentSize: z.number(),
19
+ isRequired: z.boolean().default(false),
20
+ isArray: z.boolean().default(false),
21
+ isEncrypted: z.boolean().default(false),
22
+ hasDefault: z.boolean().default(false),
23
+ defaultValue: z.any().optional(),
24
+ suggestedType: MigrationTargetType,
25
+ targetType: MigrationTargetType,
26
+ targetSize: z.number().optional(), // varchar only
27
+ action: MigrationAction,
28
+ skipReason: z.string().optional(),
29
+ indexesAffected: z.array(z.string()).default([]),
30
+ });
31
+ // ── Plan: full migration plan (user-editable YAML) ──
32
+ export const MigrationPlanSchema = z.object({
33
+ version: z.number().default(1),
34
+ generatedAt: z.string(),
35
+ appwriteEndpoint: z.string().optional(),
36
+ appwriteProject: z.string().optional(),
37
+ summary: z.object({
38
+ totalStringAttributes: z.number(),
39
+ toMigrate: z.number(),
40
+ toSkip: z.number(),
41
+ databaseCount: z.number(),
42
+ collectionCount: z.number(),
43
+ }),
44
+ entries: z.array(MigrationPlanEntrySchema),
45
+ });
46
+ // ── Checkpoint: tracks progress during execution ──
47
+ export const CheckpointPhase = z.enum([
48
+ "pending",
49
+ "backup_created",
50
+ "data_copied_to_backup",
51
+ "data_verified_backup",
52
+ "original_deleted",
53
+ "new_attr_created",
54
+ "data_copied_back",
55
+ "data_verified_final",
56
+ "backup_deleted",
57
+ "completed",
58
+ "failed",
59
+ ]);
60
+ export const CheckpointEntrySchema = z.object({
61
+ databaseId: z.string(),
62
+ collectionId: z.string(),
63
+ attributeKey: z.string(),
64
+ backupKey: z.string(),
65
+ phase: CheckpointPhase,
66
+ targetType: MigrationTargetType,
67
+ targetSize: z.number().optional(),
68
+ error: z.string().optional(),
69
+ // Store index definitions for recreation after attribute delete
70
+ storedIndexes: z
71
+ .array(z.object({
72
+ key: z.string(),
73
+ type: z.string(),
74
+ attributes: z.array(z.string()),
75
+ orders: z.array(z.string()).optional(),
76
+ }))
77
+ .default([]),
78
+ });
79
+ export const MigrationCheckpointSchema = z.object({
80
+ planFile: z.string(),
81
+ startedAt: z.string(),
82
+ lastUpdatedAt: z.string(),
83
+ entries: z.array(CheckpointEntrySchema),
84
+ });
85
+ // ── Helper: suggest target type from size + index presence ──
86
+ export function suggestTargetType(size, hasIndex) {
87
+ // varchar supports up to 16,383 bytes and allows full indexing
88
+ if (size <= 768)
89
+ return "varchar";
90
+ if (hasIndex)
91
+ return "varchar"; // indexes require varchar
92
+ if (size <= 16_383)
93
+ return "text";
94
+ if (size <= 4_000_000)
95
+ return "mediumtext";
96
+ return "longtext";
97
+ }
98
+ // ── Helper: generate backup key with length limits ──
99
+ const MAX_KEY_LENGTH = 36;
100
+ const BACKUP_PREFIX = "_mig_";
101
+ export function generateBackupKey(originalKey) {
102
+ const candidate = `${BACKUP_PREFIX}${originalKey}`;
103
+ if (candidate.length <= MAX_KEY_LENGTH) {
104
+ return candidate;
105
+ }
106
+ // Truncate + 4-char hash for uniqueness
107
+ const hash = simpleHash(originalKey);
108
+ const maxOrigLen = MAX_KEY_LENGTH - BACKUP_PREFIX.length - 1 - 4; // _mig_ + _ + hash4
109
+ return `_m_${originalKey.slice(0, maxOrigLen)}_${hash}`;
110
+ }
111
+ function simpleHash(str) {
112
+ let h = 0;
113
+ for (let i = 0; i < str.length; i++) {
114
+ h = ((h << 5) - h + str.charCodeAt(i)) | 0;
115
+ }
116
+ return Math.abs(h).toString(36).slice(0, 4).padStart(4, "0");
117
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@njdamstra/appwrite-utils-cli",
3
3
  "description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
4
- "version": "1.10.1",
4
+ "version": "1.11.0",
5
5
  "main": "dist/main.js",
6
6
  "type": "module",
7
7
  "repository": {
@@ -0,0 +1,157 @@
1
+ import inquirer from "inquirer";
2
+ import path from "node:path";
3
+ import { MessageFormatter } from "@njdamstra/appwrite-utils-helpers";
4
+ import type { InteractiveCLI } from "../../interactiveCLI.js";
5
+ import {
6
+ analyzeStringAttributes,
7
+ executeMigrationPlan,
8
+ } from "../../migrations/migrateStrings.js";
9
+ import type { AnalyzeOptions, ExecuteOptions } from "../../migrations/migrateStringsTypes.js";
10
+
11
+ export const migrateCommands = {
12
+ async migrateStrings(cli: InteractiveCLI): Promise<void> {
13
+ const { phase } = await inquirer.prompt([
14
+ {
15
+ type: "list",
16
+ name: "phase",
17
+ message: "String attribute migration:",
18
+ choices: [
19
+ {
20
+ name: "Analyze — scan local configs, generate migration plan (YAML)",
21
+ value: "analyze",
22
+ },
23
+ {
24
+ name: "Execute — run a migration plan against Appwrite server",
25
+ value: "execute",
26
+ },
27
+ { name: "Back", value: "back" },
28
+ ],
29
+ },
30
+ ]);
31
+
32
+ if (phase === "back") return;
33
+ if (phase === "analyze") {
34
+ await migrateCommands.analyzePhase(cli);
35
+ } else {
36
+ await migrateCommands.executePhase(cli);
37
+ }
38
+ },
39
+
40
+ async analyzePhase(cli: InteractiveCLI): Promise<void> {
41
+ const controller = (cli as any).controller;
42
+ if (!controller?.config) {
43
+ MessageFormatter.error(
44
+ "No configuration loaded. Make sure you have a valid .appwrite config.",
45
+ undefined,
46
+ { prefix: "Analyze" }
47
+ );
48
+ return;
49
+ }
50
+
51
+ const config = controller.config;
52
+ const collections = [
53
+ ...(config.collections || []),
54
+ ...(config.tables || []),
55
+ ];
56
+
57
+ if (collections.length === 0) {
58
+ MessageFormatter.warning(
59
+ "No collections/tables found in local config. Nothing to analyze.",
60
+ { prefix: "Analyze" }
61
+ );
62
+ return;
63
+ }
64
+
65
+ // Prompt for output path
66
+ const { outputPath } = await inquirer.prompt([
67
+ {
68
+ type: "input",
69
+ name: "outputPath",
70
+ message: "Output path for migration plan:",
71
+ default: path.join(process.cwd(), "migrate-strings-plan.yaml"),
72
+ },
73
+ ]);
74
+
75
+ const options: AnalyzeOptions = { outputPath };
76
+
77
+ try {
78
+ analyzeStringAttributes(config, options);
79
+ MessageFormatter.success(
80
+ "Analysis complete. Review the YAML plan, edit targetType/action as needed, then run Execute.",
81
+ { prefix: "Analyze" }
82
+ );
83
+ } catch (err: any) {
84
+ MessageFormatter.error(
85
+ `Analysis failed: ${err.message}`,
86
+ undefined,
87
+ { prefix: "Analyze" }
88
+ );
89
+ }
90
+ },
91
+
92
+ async executePhase(cli: InteractiveCLI): Promise<void> {
93
+ const controller = (cli as any).controller;
94
+ if (!controller?.adapter) {
95
+ MessageFormatter.error(
96
+ "No database adapter available. Ensure a server connection is established.",
97
+ undefined,
98
+ { prefix: "Execute" }
99
+ );
100
+ return;
101
+ }
102
+
103
+ // Prompt for plan path
104
+ const { planPath } = await inquirer.prompt([
105
+ {
106
+ type: "input",
107
+ name: "planPath",
108
+ message: "Path to migration plan YAML:",
109
+ default: path.join(process.cwd(), "migrate-strings-plan.yaml"),
110
+ },
111
+ ]);
112
+
113
+ const { keepBackups } = await inquirer.prompt([
114
+ {
115
+ type: "confirm",
116
+ name: "keepBackups",
117
+ message: "Keep backup attributes after migration? (safer, uses more attribute slots)",
118
+ default: true,
119
+ },
120
+ ]);
121
+
122
+ const { dryRun } = await inquirer.prompt([
123
+ {
124
+ type: "confirm",
125
+ name: "dryRun",
126
+ message: "Dry run? (no actual changes)",
127
+ default: false,
128
+ },
129
+ ]);
130
+
131
+ const options: ExecuteOptions = {
132
+ planPath,
133
+ keepBackups,
134
+ dryRun,
135
+ };
136
+
137
+ try {
138
+ const results = await executeMigrationPlan(controller.adapter, options);
139
+ if (results.failed > 0) {
140
+ MessageFormatter.warning(
141
+ `Migration completed with ${results.failed} failure(s). Check checkpoint file to resume.`,
142
+ { prefix: "Execute" }
143
+ );
144
+ } else {
145
+ MessageFormatter.success("Migration completed successfully.", {
146
+ prefix: "Execute",
147
+ });
148
+ }
149
+ } catch (err: any) {
150
+ MessageFormatter.error(
151
+ `Execution failed: ${err.message}`,
152
+ undefined,
153
+ { prefix: "Execute" }
154
+ );
155
+ }
156
+ },
157
+ };
@@ -46,6 +46,7 @@ import { storageCommands } from "./cli/commands/storageCommands.js";
46
46
  import { transferCommands } from "./cli/commands/transferCommands.js";
47
47
  import { schemaCommands } from "./cli/commands/schemaCommands.js";
48
48
  import { importFileCommands } from "./cli/commands/importFileCommands.js";
49
+ import { migrateCommands } from "./cli/commands/migrateCommands.js";
49
50
 
50
51
  enum CHOICES {
51
52
  MIGRATE_CONFIG = "🔄 Migrate TypeScript config to YAML (.appwrite structure)",
@@ -71,6 +72,7 @@ enum CHOICES {
71
72
  RELOAD_CONFIG = "🔄 Reload configuration files",
72
73
  UPDATE_FUNCTION_SPEC = "⚙️ Update function specifications",
73
74
  MANAGE_BUCKETS = "🪣 Manage storage buckets",
75
+ MIGRATE_STRINGS = "🔄 Migrate string attributes to varchar/text types",
74
76
  EXIT = "👋 Exit",
75
77
  }
76
78
 
@@ -203,6 +205,10 @@ export class InteractiveCLI {
203
205
  case CHOICES.MANAGE_BUCKETS:
204
206
  await this.manageBuckets();
205
207
  break;
208
+ case CHOICES.MIGRATE_STRINGS:
209
+ await this.initControllerIfNeeded();
210
+ await migrateCommands.migrateStrings(this);
211
+ break;
206
212
  case CHOICES.EXIT:
207
213
  MessageFormatter.success("Goodbye!");
208
214
  process.exit(0);
package/src/main.ts CHANGED
@@ -82,6 +82,12 @@ interface CliOptions {
82
82
  importFile?: string;
83
83
  targetDb?: string;
84
84
  targetTable?: string;
85
+ // String attribute migration
86
+ migrateStringsAnalyze?: boolean;
87
+ migrateStringsExecute?: string;
88
+ migrateStringsOutput?: string;
89
+ migrateStringsKeepBackups?: boolean;
90
+ migrateStringsDryRun?: boolean;
85
91
  }
86
92
 
87
93
  type ParsedArgv = ArgumentsCamelCase<CliOptions>;
@@ -621,6 +627,32 @@ const argv = yargs(hideBin(process.argv))
621
627
  type: "string",
622
628
  description: "Target table ID for --importFile (prompted if omitted)",
623
629
  })
630
+ .option("migrateStringsAnalyze", {
631
+ alias: ["migrate-strings-analyze"],
632
+ type: "boolean",
633
+ description: "Analyze local configs and generate a string-to-varchar/text migration plan (YAML)",
634
+ })
635
+ .option("migrateStringsExecute", {
636
+ alias: ["migrate-strings-execute"],
637
+ type: "string",
638
+ description: "Execute a string migration plan from the given YAML path",
639
+ })
640
+ .option("migrateStringsOutput", {
641
+ alias: ["migrate-strings-output"],
642
+ type: "string",
643
+ description: "Output path for the migration plan (default: ./migrate-strings-plan.yaml)",
644
+ })
645
+ .option("migrateStringsKeepBackups", {
646
+ alias: ["migrate-strings-keep-backups"],
647
+ type: "boolean",
648
+ default: true,
649
+ description: "Keep backup attributes after migration (default: true)",
650
+ })
651
+ .option("migrateStringsDryRun", {
652
+ alias: ["migrate-strings-dry-run"],
653
+ type: "boolean",
654
+ description: "Dry run — show what would happen without making changes",
655
+ })
624
656
  .parse() as ParsedArgv;
625
657
 
626
658
  async function main() {
@@ -842,6 +874,43 @@ async function main() {
842
874
  return;
843
875
  }
844
876
 
877
+ // String attribute migration (analyze or execute)
878
+ if (argv.migrateStringsAnalyze || argv.migrateStringsExecute) {
879
+ const { analyzeStringAttributes, executeMigrationPlan } = await import(
880
+ "./migrations/migrateStrings.js"
881
+ );
882
+
883
+ if (argv.migrateStringsAnalyze) {
884
+ if (!controller.config) {
885
+ MessageFormatter.error("No Appwrite configuration found", undefined, {
886
+ prefix: "Migration",
887
+ });
888
+ return;
889
+ }
890
+ analyzeStringAttributes(controller.config, {
891
+ outputPath: argv.migrateStringsOutput,
892
+ });
893
+ } else if (argv.migrateStringsExecute) {
894
+ if (!controller.adapter) {
895
+ MessageFormatter.error(
896
+ "No database adapter available. Ensure config has valid credentials.",
897
+ undefined,
898
+ { prefix: "Migration" }
899
+ );
900
+ return;
901
+ }
902
+ const results = await executeMigrationPlan(controller.adapter, {
903
+ planPath: argv.migrateStringsExecute,
904
+ keepBackups: argv.migrateStringsKeepBackups ?? true,
905
+ dryRun: argv.migrateStringsDryRun ?? false,
906
+ });
907
+ if (results.failed > 0) {
908
+ process.exit(1);
909
+ }
910
+ }
911
+ return;
912
+ }
913
+
845
914
  // List backups if requested
846
915
  if (parsedArgv.listBackups) {
847
916
  const { AdapterFactory } = await import("@njdamstra/appwrite-utils-helpers");