appwrite-utils-cli 1.7.8 → 1.7.9

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.
Files changed (50) hide show
  1. package/dist/cli/commands/databaseCommands.js +7 -8
  2. package/dist/config/services/ConfigLoaderService.d.ts +7 -0
  3. package/dist/config/services/ConfigLoaderService.js +47 -1
  4. package/dist/functions/deployments.js +5 -23
  5. package/dist/functions/methods.js +4 -2
  6. package/dist/functions/pathResolution.d.ts +37 -0
  7. package/dist/functions/pathResolution.js +185 -0
  8. package/dist/functions/templates/count-docs-in-collection/README.md +54 -0
  9. package/dist/functions/templates/count-docs-in-collection/package.json +25 -0
  10. package/dist/functions/templates/count-docs-in-collection/src/main.ts +159 -0
  11. package/dist/functions/templates/count-docs-in-collection/src/request.ts +9 -0
  12. package/dist/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
  13. package/dist/functions/templates/hono-typescript/README.md +286 -0
  14. package/dist/functions/templates/hono-typescript/package.json +26 -0
  15. package/dist/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
  16. package/dist/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
  17. package/dist/functions/templates/hono-typescript/src/app.ts +180 -0
  18. package/dist/functions/templates/hono-typescript/src/context.ts +103 -0
  19. package/dist/functions/templates/hono-typescript/src/index.ts +54 -0
  20. package/dist/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
  21. package/dist/functions/templates/hono-typescript/tsconfig.json +20 -0
  22. package/dist/functions/templates/typescript-node/README.md +32 -0
  23. package/dist/functions/templates/typescript-node/package.json +25 -0
  24. package/dist/functions/templates/typescript-node/src/context.ts +103 -0
  25. package/dist/functions/templates/typescript-node/src/index.ts +29 -0
  26. package/dist/functions/templates/typescript-node/tsconfig.json +28 -0
  27. package/dist/functions/templates/uv/README.md +31 -0
  28. package/dist/functions/templates/uv/pyproject.toml +30 -0
  29. package/dist/functions/templates/uv/src/__init__.py +0 -0
  30. package/dist/functions/templates/uv/src/context.py +125 -0
  31. package/dist/functions/templates/uv/src/index.py +46 -0
  32. package/dist/main.js +8 -8
  33. package/dist/shared/selectionDialogs.d.ts +1 -1
  34. package/dist/shared/selectionDialogs.js +31 -7
  35. package/dist/utilsController.d.ts +2 -1
  36. package/dist/utilsController.js +111 -19
  37. package/package.json +4 -2
  38. package/scripts/copy-templates.ts +23 -0
  39. package/src/cli/commands/databaseCommands.ts +7 -8
  40. package/src/config/services/ConfigLoaderService.ts +62 -1
  41. package/src/functions/deployments.ts +10 -35
  42. package/src/functions/methods.ts +4 -2
  43. package/src/functions/pathResolution.ts +227 -0
  44. package/src/main.ts +8 -8
  45. package/src/shared/selectionDialogs.ts +36 -7
  46. package/src/utilsController.ts +138 -22
  47. package/dist/utils/schemaStrings.d.ts +0 -14
  48. package/dist/utils/schemaStrings.js +0 -428
  49. package/dist/utils/sessionPreservationExample.d.ts +0 -1666
  50. package/dist/utils/sessionPreservationExample.js +0 -101
@@ -1,5 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
+ import { resolve as resolvePath, dirname, isAbsolute } from "node:path";
3
4
  import yaml from "js-yaml";
4
5
  import { register } from "tsx/esm/api";
5
6
  import { pathToFileURL } from "node:url";
@@ -19,6 +20,7 @@ import {
19
20
  type CollectionDiscoveryResult,
20
21
  type TableDiscoveryResult,
21
22
  } from "../../utils/configDiscovery.js";
23
+ import { expandTildePath } from "../../functions/pathResolution.js";
22
24
 
23
25
  /**
24
26
  * Options for loading collections or tables
@@ -51,6 +53,35 @@ export interface CollectionLoadOptions {
51
53
  * - Validates and normalizes configuration data
52
54
  */
53
55
  export class ConfigLoaderService {
56
+ /**
57
+ * Normalizes function dirPath to absolute path
58
+ * @param func Function configuration object
59
+ * @param configDir Directory containing the config file
60
+ * @returns Function with normalized dirPath
61
+ */
62
+ private normalizeFunctionPath(func: any, configDir: string): any {
63
+ if (!func.dirPath) {
64
+ return func;
65
+ }
66
+
67
+ // Expand tilde first
68
+ const expandedPath = expandTildePath(func.dirPath);
69
+
70
+ // If already absolute, return as-is
71
+ if (isAbsolute(expandedPath)) {
72
+ return {
73
+ ...func,
74
+ dirPath: expandedPath
75
+ };
76
+ }
77
+
78
+ // Resolve relative to config directory
79
+ return {
80
+ ...func,
81
+ dirPath: resolvePath(configDir, expandedPath)
82
+ };
83
+ }
84
+
54
85
  /**
55
86
  * Loads configuration from a discovered path, auto-detecting the type
56
87
  * @param configPath Path to the configuration file
@@ -78,6 +109,13 @@ export class ConfigLoaderService {
78
109
  );
79
110
  }
80
111
 
112
+ const configDir = path.dirname(configPath);
113
+
114
+ // Normalize function paths
115
+ const normalizedFunctions = partialConfig.functions
116
+ ? partialConfig.functions.map(func => this.normalizeFunctionPath(func, configDir))
117
+ : [];
118
+
81
119
  return {
82
120
  appwriteEndpoint: partialConfig.appwriteEndpoint,
83
121
  appwriteProject: partialConfig.appwriteProject,
@@ -105,7 +143,7 @@ export class ConfigLoaderService {
105
143
  },
106
144
  databases: partialConfig.databases || [],
107
145
  buckets: partialConfig.buckets || [],
108
- functions: partialConfig.functions || [],
146
+ functions: normalizedFunctions,
109
147
  collections: partialConfig.collections || [],
110
148
  sessionCookie: partialConfig.sessionCookie,
111
149
  authMethod: partialConfig.authMethod || "auto",
@@ -155,6 +193,13 @@ export class ConfigLoaderService {
155
193
 
156
194
  // Load collections and tables from their respective directories
157
195
  const configDir = path.dirname(yamlPath);
196
+
197
+ // Normalize function paths
198
+ if (config.functions) {
199
+ config.functions = config.functions.map(func =>
200
+ this.normalizeFunctionPath(func, configDir)
201
+ );
202
+ }
158
203
  const collectionsDir = path.join(configDir, config.schemaConfig?.collectionsDirectory || "collections");
159
204
  const tablesDir = path.join(configDir, config.schemaConfig?.tablesDirectory || "tables");
160
205
 
@@ -248,6 +293,14 @@ export class ConfigLoaderService {
248
293
  throw new Error(`Failed to load TypeScript config from: ${tsPath}`);
249
294
  }
250
295
 
296
+ // Normalize function paths
297
+ const configDir = path.dirname(tsPath);
298
+ if (config.functions) {
299
+ config.functions = config.functions.map(func =>
300
+ this.normalizeFunctionPath(func, configDir)
301
+ );
302
+ }
303
+
251
304
  MessageFormatter.success(`Loaded TypeScript config from: ${tsPath}`, {
252
305
  prefix: "Config",
253
306
  });
@@ -292,6 +345,14 @@ export class ConfigLoaderService {
292
345
  appwriteConfig.collections = collections;
293
346
  }
294
347
 
348
+ // Normalize function paths
349
+ const configDir = path.dirname(jsonPath);
350
+ if (appwriteConfig.functions) {
351
+ appwriteConfig.functions = appwriteConfig.functions.map(func =>
352
+ this.normalizeFunctionPath(func, configDir)
353
+ );
354
+ }
355
+
295
356
  MessageFormatter.success(`Loaded project config from: ${jsonPath}`, {
296
357
  prefix: "Config",
297
358
  });
@@ -16,30 +16,7 @@ import {
16
16
  } from "./methods.js";
17
17
  import ignore from "ignore";
18
18
  import { MessageFormatter } from "../shared/messageFormatter.js";
19
-
20
- const findFunctionDirectory = (
21
- basePath: string,
22
- functionName: string
23
- ): string | undefined => {
24
- const normalizedName = functionName.toLowerCase().replace(/\s+/g, "-");
25
- const dirs = fs.readdirSync(basePath, { withFileTypes: true });
26
-
27
- for (const dir of dirs) {
28
- if (dir.isDirectory()) {
29
- const fullPath = join(basePath, dir.name);
30
- if (dir.name.toLowerCase() === normalizedName) {
31
- return fullPath;
32
- }
33
-
34
- const nestedResult = findFunctionDirectory(fullPath, functionName);
35
- if (nestedResult) {
36
- return nestedResult;
37
- }
38
- }
39
- }
40
-
41
- return undefined;
42
- };
19
+ import { resolveFunctionDirectory, validateFunctionDirectory } from './pathResolution.js';
43
20
 
44
21
  export const deployFunction = async (
45
22
  client: Client,
@@ -183,18 +160,16 @@ export const deployLocalFunction = async (
183
160
  functionExists = false;
184
161
  }
185
162
 
186
- const resolvedPath =
187
- functionPath ||
188
- functionConfig.dirPath ||
189
- findFunctionDirectory(process.cwd(), functionName) ||
190
- join(
191
- process.cwd(),
192
- "functions",
193
- functionName.toLowerCase().replace(/\s+/g, "-")
194
- );
163
+ const configDirPath = process.cwd(); // TODO: This should be passed from caller
164
+ const resolvedPath = resolveFunctionDirectory(
165
+ functionName,
166
+ configDirPath,
167
+ functionConfig.dirPath,
168
+ functionPath
169
+ );
195
170
 
196
- if (!fs.existsSync(resolvedPath)) {
197
- throw new Error(`Function directory not found at ${resolvedPath}`);
171
+ if (!validateFunctionDirectory(resolvedPath)) {
172
+ throw new Error(`Function directory is invalid or missing required files: ${resolvedPath}`);
198
173
  }
199
174
 
200
175
  if (functionConfig.predeployCommands?.length) {
@@ -17,6 +17,7 @@ import {
17
17
  import chalk from "chalk";
18
18
  import { extract as extractTar } from "tar";
19
19
  import { MessageFormatter } from "../shared/messageFormatter.js";
20
+ import { expandTildePath, normalizeFunctionName } from "./pathResolution.js";
20
21
 
21
22
  /**
22
23
  * Validates and filters events array for Appwrite functions
@@ -72,7 +73,7 @@ export const downloadLatestFunctionDeployment = async (
72
73
  // Create function directory using provided basePath
73
74
  const functionDir = join(
74
75
  basePath,
75
- functionInfo.name.toLowerCase().replace(/\s+/g, "-")
76
+ normalizeFunctionName(functionInfo.name)
76
77
  );
77
78
  await fs.promises.mkdir(functionDir, { recursive: true });
78
79
 
@@ -219,7 +220,8 @@ export const createFunctionTemplate = async (
219
220
  functionName: string,
220
221
  basePath: string = "./functions"
221
222
  ) => {
222
- const functionPath = join(basePath, functionName);
223
+ const expandedBasePath = expandTildePath(basePath);
224
+ const functionPath = join(expandedBasePath, functionName);
223
225
  const currentFileUrl = import.meta.url;
224
226
  const currentDir = dirname(fileURLToPath(currentFileUrl));
225
227
  const templatesPath = join(currentDir, "templates", templateType);
@@ -0,0 +1,227 @@
1
+ import { existsSync, statSync, readdirSync } from 'node:fs';
2
+ import { join, resolve, isAbsolute } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { MessageFormatter } from '../shared/messageFormatter.js';
5
+ import { logger } from '../shared/logging.js';
6
+
7
+ /**
8
+ * Expands tilde (~) in paths to the user's home directory
9
+ * @param pathStr - Path string that may contain ~
10
+ * @returns Expanded path with home directory
11
+ */
12
+ export function expandTildePath(pathStr: string): string {
13
+ if (!pathStr) return pathStr;
14
+
15
+ if (pathStr.startsWith('~/') || pathStr === '~') {
16
+ const expandedPath = pathStr.replace(/^~(?=$|\/|\\)/, homedir());
17
+ logger.debug('Expanded tilde path', { original: pathStr, expanded: expandedPath });
18
+ return expandedPath;
19
+ }
20
+
21
+ return pathStr;
22
+ }
23
+
24
+ /**
25
+ * Normalizes function name to standard format (lowercase, dashes instead of spaces)
26
+ * @param name - Function name to normalize
27
+ * @returns Normalized function name
28
+ */
29
+ export function normalizeFunctionName(name: string): string {
30
+ if (!name) return name;
31
+
32
+ const normalized = name.toLowerCase().replace(/\s+/g, '-');
33
+
34
+ if (normalized !== name) {
35
+ logger.debug('Normalized function name', { original: name, normalized });
36
+ }
37
+
38
+ return normalized;
39
+ }
40
+
41
+ /**
42
+ * Validates that a directory exists and contains function markers
43
+ * @param dirPath - Directory path to validate
44
+ * @returns True if directory is a valid function directory
45
+ */
46
+ export function validateFunctionDirectory(dirPath: string): boolean {
47
+ try {
48
+ // Check if directory exists
49
+ if (!existsSync(dirPath)) {
50
+ logger.debug('Directory does not exist', { dirPath });
51
+ return false;
52
+ }
53
+
54
+ // Check if it's actually a directory
55
+ const stats = statSync(dirPath);
56
+ if (!stats.isDirectory()) {
57
+ logger.debug('Path is not a directory', { dirPath });
58
+ return false;
59
+ }
60
+
61
+ // Check for function markers
62
+ const contents = readdirSync(dirPath);
63
+ const hasPackageJson = contents.includes('package.json');
64
+ const hasPyprojectToml = contents.includes('pyproject.toml');
65
+ const hasSrcDir = contents.includes('src');
66
+
67
+ const isValid = hasPackageJson || hasPyprojectToml || hasSrcDir;
68
+
69
+ logger.debug('Function directory validation', {
70
+ dirPath,
71
+ isValid,
72
+ markers: {
73
+ hasPackageJson,
74
+ hasPyprojectToml,
75
+ hasSrcDir
76
+ }
77
+ });
78
+
79
+ return isValid;
80
+ } catch (error) {
81
+ logger.debug('Error validating function directory', {
82
+ dirPath,
83
+ error: error instanceof Error ? error.message : String(error)
84
+ });
85
+ return false;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Helper function to search for function in standard locations
91
+ * @param configDirPath - Directory where config file is located
92
+ * @param normalizedName - Normalized function name
93
+ * @returns First valid function directory path or undefined
94
+ */
95
+ export function findFunctionInStandardLocations(
96
+ configDirPath: string,
97
+ normalizedName: string
98
+ ): string | undefined {
99
+ const searchPaths = [
100
+ // Same directory as config
101
+ join(configDirPath, 'functions', normalizedName),
102
+ // Parent directory of config
103
+ join(configDirPath, '..', 'functions', normalizedName),
104
+ // Current working directory
105
+ join(process.cwd(), 'functions', normalizedName),
106
+ ];
107
+
108
+ logger.debug('Searching for function in standard locations', {
109
+ normalizedName,
110
+ configDirPath,
111
+ searchPaths
112
+ });
113
+
114
+ for (const searchPath of searchPaths) {
115
+ const resolvedPath = resolve(searchPath);
116
+ logger.debug('Checking search path', { searchPath, resolvedPath });
117
+
118
+ if (validateFunctionDirectory(resolvedPath)) {
119
+ logger.debug('Found function in standard location', { resolvedPath });
120
+ return resolvedPath;
121
+ }
122
+ }
123
+
124
+ logger.debug('Function not found in any standard location', { normalizedName });
125
+ return undefined;
126
+ }
127
+
128
+ /**
129
+ * Resolves the absolute path to a function directory
130
+ * Handles multiple resolution strategies with proper priority
131
+ *
132
+ * @param functionName - Name of the function
133
+ * @param configDirPath - Directory where config file is located
134
+ * @param dirPath - Optional explicit dirPath from config
135
+ * @param explicitPath - Optional path passed as parameter (highest priority)
136
+ * @returns Absolute path to the function directory
137
+ * @throws Error if function directory cannot be found or is invalid
138
+ */
139
+ export function resolveFunctionDirectory(
140
+ functionName: string,
141
+ configDirPath: string,
142
+ dirPath?: string,
143
+ explicitPath?: string
144
+ ): string {
145
+ logger.debug('Resolving function directory', {
146
+ functionName,
147
+ configDirPath,
148
+ dirPath,
149
+ explicitPath
150
+ });
151
+
152
+ const normalizedName = normalizeFunctionName(functionName);
153
+
154
+ // Priority 1: Explicit path parameter (highest priority)
155
+ if (explicitPath) {
156
+ logger.debug('Using explicit path parameter');
157
+ const expandedPath = expandTildePath(explicitPath);
158
+ const resolvedPath = isAbsolute(expandedPath)
159
+ ? expandedPath
160
+ : resolve(process.cwd(), expandedPath);
161
+
162
+ if (!validateFunctionDirectory(resolvedPath)) {
163
+ const errorMsg = `Explicit path is not a valid function directory: ${resolvedPath}`;
164
+ logger.error(errorMsg);
165
+ MessageFormatter.error('Invalid function directory', errorMsg, { prefix: 'Path Resolution' });
166
+ throw new Error(errorMsg);
167
+ }
168
+
169
+ logger.debug('Resolved using explicit path', { resolvedPath });
170
+ MessageFormatter.debug(`Resolved function directory using explicit path: ${resolvedPath}`, undefined, { prefix: 'Path Resolution' });
171
+ return resolvedPath;
172
+ }
173
+
174
+ // Priority 2: dirPath from config (relative to config location)
175
+ if (dirPath) {
176
+ logger.debug('Using dirPath from config');
177
+ const expandedPath = expandTildePath(dirPath);
178
+ const resolvedPath = isAbsolute(expandedPath)
179
+ ? expandedPath
180
+ : resolve(configDirPath, expandedPath);
181
+
182
+ if (!validateFunctionDirectory(resolvedPath)) {
183
+ const errorMsg = `Config dirPath is not a valid function directory: ${resolvedPath}`;
184
+ logger.error(errorMsg);
185
+ MessageFormatter.error('Invalid function directory', errorMsg, { prefix: 'Path Resolution' });
186
+ throw new Error(errorMsg);
187
+ }
188
+
189
+ logger.debug('Resolved using config dirPath', { resolvedPath });
190
+ MessageFormatter.debug(`Resolved function directory using config dirPath: ${resolvedPath}`, undefined, { prefix: 'Path Resolution' });
191
+ return resolvedPath;
192
+ }
193
+
194
+ // Priority 3: Search standard locations
195
+ logger.debug('Searching standard locations for function');
196
+ const foundPath = findFunctionInStandardLocations(configDirPath, normalizedName);
197
+
198
+ if (foundPath) {
199
+ logger.debug('Resolved using standard location search', { foundPath });
200
+ MessageFormatter.debug(`Found function directory in standard location: ${foundPath}`, undefined, { prefix: 'Path Resolution' });
201
+ return foundPath;
202
+ }
203
+
204
+ // Priority 4: Not found - throw error
205
+ const searchedLocations = [
206
+ join(configDirPath, 'functions', normalizedName),
207
+ join(configDirPath, '..', 'functions', normalizedName),
208
+ join(process.cwd(), 'functions', normalizedName),
209
+ ];
210
+
211
+ const errorMsg = `Function directory not found for '${functionName}' (normalized: '${normalizedName}'). ` +
212
+ `Searched locations:\n${searchedLocations.map(p => ` - ${p}`).join('\n')}`;
213
+
214
+ logger.error('Function directory not found', {
215
+ functionName,
216
+ normalizedName,
217
+ searchedLocations
218
+ });
219
+
220
+ MessageFormatter.error(
221
+ 'Function directory not found',
222
+ errorMsg,
223
+ { prefix: 'Path Resolution' }
224
+ );
225
+
226
+ throw new Error(errorMsg);
227
+ }
package/src/main.ts CHANGED
@@ -149,14 +149,14 @@ async function performEnhancedSync(
149
149
  bucketSelections
150
150
  );
151
151
 
152
- const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
152
+ const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary, 'pull');
153
153
  if (!confirmed) {
154
- MessageFormatter.info("Sync operation cancelled by user", { prefix: "Sync" });
154
+ MessageFormatter.info("Pull operation cancelled by user", { prefix: "Sync" });
155
155
  return null;
156
156
  }
157
157
 
158
- // Perform sync with existing configuration
159
- await controller.selectiveSync(databaseSelections, bucketSelections);
158
+ // Perform sync with existing configuration (pull from remote)
159
+ await controller.selectivePull(databaseSelections, bucketSelections);
160
160
  return selectionSummary;
161
161
  }
162
162
  }
@@ -275,14 +275,14 @@ async function performEnhancedSync(
275
275
  bucketSelections
276
276
  );
277
277
 
278
- const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
278
+ const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary, 'pull');
279
279
  if (!confirmed) {
280
- MessageFormatter.info("Sync operation cancelled by user", { prefix: "Sync" });
280
+ MessageFormatter.info("Pull operation cancelled by user", { prefix: "Sync" });
281
281
  return null;
282
282
  }
283
283
 
284
- // Perform the selective sync
285
- await controller.selectiveSync(databaseSelections, bucketSelections);
284
+ // Perform the selective sync (pull from remote)
285
+ await controller.selectivePull(databaseSelections, bucketSelections);
286
286
 
287
287
  MessageFormatter.success("Enhanced sync completed successfully", { prefix: "Sync" });
288
288
  return selectionSummary;
@@ -520,8 +520,37 @@ export class SelectionDialogs {
520
520
  /**
521
521
  * Shows final confirmation dialog with sync selection summary
522
522
  */
523
- static async confirmSyncSelection(selectionSummary: SyncSelectionSummary): Promise<boolean> {
524
- MessageFormatter.banner("Sync Selection Summary", "Review your selections before proceeding");
523
+ static async confirmSyncSelection(
524
+ selectionSummary: SyncSelectionSummary,
525
+ operationType: 'push' | 'pull' | 'sync' = 'sync'
526
+ ): Promise<boolean> {
527
+ const labels = {
528
+ push: {
529
+ banner: "Push Selection Summary",
530
+ subtitle: "Review selections before pushing to Appwrite",
531
+ confirm: "Proceed with push operation?",
532
+ success: "Push operation confirmed.",
533
+ cancel: "Push operation cancelled."
534
+ },
535
+ pull: {
536
+ banner: "Pull Selection Summary",
537
+ subtitle: "Review selections before pulling from Appwrite",
538
+ confirm: "Proceed with pull operation?",
539
+ success: "Pull operation confirmed.",
540
+ cancel: "Pull operation cancelled."
541
+ },
542
+ sync: {
543
+ banner: "Sync Selection Summary",
544
+ subtitle: "Review your selections before proceeding",
545
+ confirm: "Proceed with sync operation?",
546
+ success: "Sync operation confirmed.",
547
+ cancel: "Sync operation cancelled."
548
+ }
549
+ };
550
+
551
+ const label = labels[operationType];
552
+
553
+ MessageFormatter.banner(label.banner, label.subtitle);
525
554
 
526
555
  // Database summary
527
556
  console.log(chalk.bold.cyan("\nšŸ“Š Databases:"));
@@ -563,20 +592,20 @@ export class SelectionDialogs {
563
592
  const { confirmed } = await inquirer.prompt([{
564
593
  type: 'confirm',
565
594
  name: 'confirmed',
566
- message: chalk.green.bold('Proceed with sync operation?'),
595
+ message: chalk.green.bold(label.confirm),
567
596
  default: true
568
597
  }]);
569
598
 
570
599
  if (confirmed) {
571
- MessageFormatter.success("Sync operation confirmed.", { skipLogging: true });
572
- logger.info("Sync selection confirmed", {
600
+ MessageFormatter.success(label.success, { skipLogging: true });
601
+ logger.info(`${operationType} selection confirmed`, {
573
602
  databases: selectionSummary.totalDatabases,
574
603
  tables: selectionSummary.totalTables,
575
604
  buckets: selectionSummary.totalBuckets
576
605
  });
577
606
  } else {
578
- MessageFormatter.warning("Sync operation cancelled.", { skipLogging: true });
579
- logger.info("Sync selection cancelled by user");
607
+ MessageFormatter.warning(label.cancel, { skipLogging: true });
608
+ logger.info(`${operationType} selection cancelled by user`);
580
609
  }
581
610
 
582
611
  return confirmed;