appwrite-utils-cli 1.7.8 → 1.8.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.
Files changed (111) hide show
  1. package/CHANGELOG.md +14 -199
  2. package/README.md +87 -30
  3. package/dist/adapters/AdapterFactory.js +5 -25
  4. package/dist/adapters/DatabaseAdapter.d.ts +17 -2
  5. package/dist/adapters/LegacyAdapter.d.ts +2 -1
  6. package/dist/adapters/LegacyAdapter.js +212 -16
  7. package/dist/adapters/TablesDBAdapter.d.ts +2 -12
  8. package/dist/adapters/TablesDBAdapter.js +261 -57
  9. package/dist/cli/commands/databaseCommands.js +10 -10
  10. package/dist/cli/commands/functionCommands.js +17 -8
  11. package/dist/collections/attributes.js +447 -125
  12. package/dist/collections/methods.js +197 -186
  13. package/dist/collections/tableOperations.d.ts +86 -0
  14. package/dist/collections/tableOperations.js +434 -0
  15. package/dist/collections/transferOperations.d.ts +3 -2
  16. package/dist/collections/transferOperations.js +93 -12
  17. package/dist/config/services/ConfigLoaderService.d.ts +7 -0
  18. package/dist/config/services/ConfigLoaderService.js +47 -1
  19. package/dist/config/yamlConfig.d.ts +221 -88
  20. package/dist/examples/yamlTerminologyExample.d.ts +1 -1
  21. package/dist/examples/yamlTerminologyExample.js +6 -3
  22. package/dist/functions/deployments.js +5 -23
  23. package/dist/functions/fnConfigDiscovery.d.ts +3 -0
  24. package/dist/functions/fnConfigDiscovery.js +108 -0
  25. package/dist/functions/methods.js +4 -2
  26. package/dist/functions/pathResolution.d.ts +37 -0
  27. package/dist/functions/pathResolution.js +185 -0
  28. package/dist/functions/templates/count-docs-in-collection/README.md +54 -0
  29. package/dist/functions/templates/count-docs-in-collection/package.json +25 -0
  30. package/dist/functions/templates/count-docs-in-collection/src/main.ts +159 -0
  31. package/dist/functions/templates/count-docs-in-collection/src/request.ts +9 -0
  32. package/dist/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
  33. package/dist/functions/templates/hono-typescript/README.md +286 -0
  34. package/dist/functions/templates/hono-typescript/package.json +26 -0
  35. package/dist/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
  36. package/dist/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
  37. package/dist/functions/templates/hono-typescript/src/app.ts +180 -0
  38. package/dist/functions/templates/hono-typescript/src/context.ts +103 -0
  39. package/dist/functions/templates/hono-typescript/src/index.ts +54 -0
  40. package/dist/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
  41. package/dist/functions/templates/hono-typescript/tsconfig.json +20 -0
  42. package/dist/functions/templates/typescript-node/README.md +32 -0
  43. package/dist/functions/templates/typescript-node/package.json +25 -0
  44. package/dist/functions/templates/typescript-node/src/context.ts +103 -0
  45. package/dist/functions/templates/typescript-node/src/index.ts +29 -0
  46. package/dist/functions/templates/typescript-node/tsconfig.json +28 -0
  47. package/dist/functions/templates/uv/README.md +31 -0
  48. package/dist/functions/templates/uv/pyproject.toml +30 -0
  49. package/dist/functions/templates/uv/src/__init__.py +0 -0
  50. package/dist/functions/templates/uv/src/context.py +125 -0
  51. package/dist/functions/templates/uv/src/index.py +46 -0
  52. package/dist/interactiveCLI.js +18 -15
  53. package/dist/main.js +219 -81
  54. package/dist/migrations/appwriteToX.d.ts +88 -23
  55. package/dist/migrations/comprehensiveTransfer.d.ts +2 -0
  56. package/dist/migrations/comprehensiveTransfer.js +83 -6
  57. package/dist/migrations/dataLoader.d.ts +227 -69
  58. package/dist/migrations/dataLoader.js +3 -3
  59. package/dist/migrations/importController.js +3 -3
  60. package/dist/migrations/relationships.d.ts +8 -2
  61. package/dist/migrations/services/ImportOrchestrator.js +3 -3
  62. package/dist/migrations/transfer.js +159 -37
  63. package/dist/shared/attributeMapper.d.ts +20 -0
  64. package/dist/shared/attributeMapper.js +203 -0
  65. package/dist/shared/selectionDialogs.d.ts +1 -1
  66. package/dist/shared/selectionDialogs.js +39 -11
  67. package/dist/storage/schemas.d.ts +354 -92
  68. package/dist/utils/configDiscovery.js +4 -3
  69. package/dist/utils/versionDetection.d.ts +0 -4
  70. package/dist/utils/versionDetection.js +41 -173
  71. package/dist/utils/yamlConverter.js +89 -16
  72. package/dist/utils/yamlLoader.d.ts +1 -1
  73. package/dist/utils/yamlLoader.js +6 -2
  74. package/dist/utilsController.d.ts +2 -1
  75. package/dist/utilsController.js +151 -22
  76. package/package.json +7 -5
  77. package/scripts/copy-templates.ts +23 -0
  78. package/src/adapters/AdapterFactory.ts +119 -143
  79. package/src/adapters/DatabaseAdapter.ts +18 -3
  80. package/src/adapters/LegacyAdapter.ts +236 -105
  81. package/src/adapters/TablesDBAdapter.ts +773 -643
  82. package/src/cli/commands/databaseCommands.ts +19 -19
  83. package/src/cli/commands/functionCommands.ts +23 -14
  84. package/src/collections/attributes.ts +2054 -1611
  85. package/src/collections/methods.ts +208 -293
  86. package/src/collections/tableOperations.ts +506 -0
  87. package/src/collections/transferOperations.ts +218 -144
  88. package/src/config/services/ConfigLoaderService.ts +62 -1
  89. package/src/examples/yamlTerminologyExample.ts +10 -5
  90. package/src/functions/deployments.ts +10 -35
  91. package/src/functions/fnConfigDiscovery.ts +103 -0
  92. package/src/functions/methods.ts +4 -2
  93. package/src/functions/pathResolution.ts +227 -0
  94. package/src/interactiveCLI.ts +25 -20
  95. package/src/main.ts +557 -202
  96. package/src/migrations/comprehensiveTransfer.ts +126 -50
  97. package/src/migrations/dataLoader.ts +3 -3
  98. package/src/migrations/importController.ts +3 -3
  99. package/src/migrations/services/ImportOrchestrator.ts +3 -3
  100. package/src/migrations/transfer.ts +148 -131
  101. package/src/shared/attributeMapper.ts +229 -0
  102. package/src/shared/selectionDialogs.ts +65 -32
  103. package/src/utils/configDiscovery.ts +9 -3
  104. package/src/utils/versionDetection.ts +74 -228
  105. package/src/utils/yamlConverter.ts +94 -17
  106. package/src/utils/yamlLoader.ts +11 -4
  107. package/src/utilsController.ts +202 -36
  108. package/dist/utils/schemaStrings.d.ts +0 -14
  109. package/dist/utils/schemaStrings.js +0 -428
  110. package/dist/utils/sessionPreservationExample.d.ts +0 -1666
  111. package/dist/utils/sessionPreservationExample.js +0 -101
@@ -0,0 +1,103 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import yaml from 'js-yaml';
4
+ import { homedir } from 'node:os';
5
+ import { AppwriteFunctionSchema, type AppwriteFunction } from 'appwrite-utils';
6
+ import { shouldIgnoreDirectory } from '../utils/directoryUtils.js';
7
+ import { MessageFormatter } from '../shared/messageFormatter.js';
8
+
9
+ function findGitRoot(startDir: string): string {
10
+ let dir = path.resolve(startDir);
11
+ while (dir !== path.parse(dir).root) {
12
+ if (fs.existsSync(path.join(dir, '.git'))) return dir;
13
+ const parent = path.dirname(dir);
14
+ if (parent === dir) break;
15
+ dir = parent;
16
+ }
17
+ return path.resolve(startDir);
18
+ }
19
+
20
+ function expandTilde(p: string): string {
21
+ if (!p) return p;
22
+ if (p === '~' || p.startsWith('~/')) return p.replace(/^~(?=$|\/|\\)/, homedir());
23
+ return p;
24
+ }
25
+
26
+ export function discoverFnConfigs(startDir: string): AppwriteFunction[] {
27
+ const root = findGitRoot(startDir);
28
+ const results: AppwriteFunction[] = [];
29
+
30
+ const visit = (dir: string, depth = 0) => {
31
+ if (depth > 5) return; // cap depth
32
+ const base = path.basename(dir);
33
+ if (shouldIgnoreDirectory(base)) return;
34
+ let entries: fs.Dirent[] = [];
35
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
36
+
37
+ // Check for .fnconfig.yaml / .fnconfig.yml
38
+ for (const fname of ['.fnconfig.yaml', '.fnconfig.yml']) {
39
+ const cfgPath = path.join(dir, fname);
40
+ if (fs.existsSync(cfgPath)) {
41
+ try {
42
+ const raw = fs.readFileSync(cfgPath, 'utf8');
43
+ const data = yaml.load(raw) as any;
44
+ const parsed = AppwriteFunctionSchema.parse({
45
+ $id: data.id || data.$id,
46
+ name: data.name,
47
+ runtime: data.runtime,
48
+ execute: data.execute || [],
49
+ events: data.events || [],
50
+ schedule: data.schedule,
51
+ timeout: data.timeout,
52
+ enabled: data.enabled,
53
+ logging: data.logging,
54
+ entrypoint: data.entrypoint,
55
+ commands: data.commands,
56
+ scopes: data.scopes,
57
+ installationId: data.installationId,
58
+ providerRepositoryId: data.providerRepositoryId,
59
+ providerBranch: data.providerBranch,
60
+ providerSilentMode: data.providerSilentMode,
61
+ providerRootDirectory: data.providerRootDirectory,
62
+ templateRepository: data.templateRepository,
63
+ templateOwner: data.templateOwner,
64
+ templateRootDirectory: data.templateRootDirectory,
65
+ templateVersion: data.templateVersion,
66
+ specification: data.specification,
67
+ dirPath: data.dirPath,
68
+ predeployCommands: data.predeployCommands,
69
+ deployDir: data.deployDir,
70
+ ignore: data.ignore,
71
+ });
72
+
73
+ // Resolve dirPath relative to the config file directory
74
+ let dirPath = parsed.dirPath || '.';
75
+ dirPath = expandTilde(dirPath);
76
+ if (!path.isAbsolute(dirPath)) dirPath = path.resolve(path.dirname(cfgPath), dirPath);
77
+ const merged: AppwriteFunction = { ...parsed, dirPath };
78
+ results.push(merged);
79
+ } catch (e) {
80
+ MessageFormatter.warning(`Failed to parse ${cfgPath}: ${e instanceof Error ? e.message : String(e)}`, { prefix: 'Functions' });
81
+ }
82
+ }
83
+ }
84
+
85
+ for (const entry of entries) {
86
+ if (entry.isDirectory()) visit(path.join(dir, entry.name), depth + 1);
87
+ }
88
+ };
89
+
90
+ visit(root, 0);
91
+ return results;
92
+ }
93
+
94
+ export function mergeDiscoveredFunctions(
95
+ central: AppwriteFunction[] = [],
96
+ discovered: AppwriteFunction[] = []
97
+ ): AppwriteFunction[] {
98
+ const map = new Map<string, AppwriteFunction>();
99
+ for (const f of central) if (f?.$id) map.set(f.$id, f);
100
+ for (const f of discovered) if (f?.$id) map.set(f.$id, f); // discovered overrides
101
+ return Array.from(map.values());
102
+ }
103
+
@@ -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
+ }
@@ -10,6 +10,7 @@ import {
10
10
  Compression,
11
11
  Query,
12
12
  Functions,
13
+ DatabaseType,
13
14
  } from "node-appwrite";
14
15
  import {
15
16
  PermissionToAppwritePermission,
@@ -232,10 +233,10 @@ export class InteractiveCLI {
232
233
  const configDatabases = this.getLocalDatabases();
233
234
  const allDatabases = [...databases, ...configDatabases]
234
235
  .reduce((acc, db) => {
235
- // Local config takes precedence - if a database with same name exists, use local version
236
- const existingIndex = acc.findIndex((d) => d.name === db.name);
236
+ // Local config takes precedence - if a database with same name or ID exists, use local version
237
+ const existingIndex = acc.findIndex((d) => d.name === db.name || d.$id === db.$id);
237
238
  if (existingIndex >= 0) {
238
- if (configDatabases.some((cdb) => cdb.name === db.name)) {
239
+ if (configDatabases.some((cdb) => cdb.name === db.name || cdb.$id === db.$id)) {
239
240
  acc[existingIndex] = db; // Replace with local version
240
241
  }
241
242
  } else {
@@ -246,10 +247,10 @@ export class InteractiveCLI {
246
247
 
247
248
  const hasLocalAndRemote =
248
249
  allDatabases.some((db) =>
249
- configDatabases.some((c) => c.name === db.name)
250
+ configDatabases.some((c) => c.name === db.name || c.$id === db.$id)
250
251
  ) &&
251
252
  allDatabases.some(
252
- (db) => !configDatabases.some((c) => c.name === db.name)
253
+ (db) => !configDatabases.some((c) => c.name === db.name || c.$id === db.$id)
253
254
  );
254
255
 
255
256
  const choices = allDatabases
@@ -258,7 +259,7 @@ export class InteractiveCLI {
258
259
  name:
259
260
  db.name +
260
261
  (hasLocalAndRemote
261
- ? configDatabases.some((c) => c.name === db.name)
262
+ ? configDatabases.some((c) => c.name === db.name || c.$id === db.$id)
262
263
  ? " (Local)"
263
264
  : " (Remote)"
264
265
  : ""),
@@ -311,7 +312,7 @@ export class InteractiveCLI {
311
312
  let allCollections = preferLocal
312
313
  ? remoteCollections.reduce(
313
314
  (acc, remoteCollection) => {
314
- if (!acc.some((c) => c.name === remoteCollection.name)) {
315
+ if (!acc.some((c) => c.name === remoteCollection.name || c.$id === remoteCollection.$id)) {
315
316
  acc.push(remoteCollection);
316
317
  }
317
318
  return acc;
@@ -321,7 +322,7 @@ export class InteractiveCLI {
321
322
  : [
322
323
  ...remoteCollections,
323
324
  ...configCollections.filter(
324
- (c) => !remoteCollections.some((rc) => rc.name === c.name)
325
+ (c) => !remoteCollections.some((rc) => rc.name === c.name || rc.$id === c.$id)
325
326
  ),
326
327
  ];
327
328
 
@@ -329,7 +330,7 @@ export class InteractiveCLI {
329
330
  // Show collections that EITHER exist in the remote database OR have matching local databaseId metadata
330
331
  allCollections = allCollections.filter((c: any) => {
331
332
  // Include if it exists remotely in this database
332
- const existsInRemoteDb = remoteCollections.some((rc) => rc.name === c.name);
333
+ const existsInRemoteDb = remoteCollections.some((rc) => rc.name === c.name || rc.$id === c.$id);
333
334
 
334
335
  // Include if local metadata claims it belongs to this database
335
336
  const hasMatchingLocalMetadata = c.databaseId === database.$id;
@@ -345,10 +346,10 @@ export class InteractiveCLI {
345
346
 
346
347
  const hasLocalAndRemote =
347
348
  allCollections.some((coll) =>
348
- configCollections.some((c) => c.name === coll.name)
349
+ configCollections.some((c) => c.name === coll.name || c.$id === coll.$id)
349
350
  ) &&
350
351
  allCollections.some(
351
- (coll) => !configCollections.some((c) => c.name === coll.name)
352
+ (coll) => !configCollections.some((c) => c.name === coll.name || c.$id === coll.$id)
352
353
  );
353
354
 
354
355
  // Enhanced choice display with type indicators
@@ -365,7 +366,7 @@ export class InteractiveCLI {
365
366
  return a.name.localeCompare(b.name);
366
367
  })
367
368
  .map((collection) => {
368
- const localCollection = configCollections.find((c) => c.name === collection.name);
369
+ const localCollection = configCollections.find((c) => c.name === collection.name || c.$id === collection.$id);
369
370
  const isLocal = !!localCollection;
370
371
  const isTable = localCollection?._isFromTablesDir || (collection as any)._isFromTablesDir || false;
371
372
  const sourceFolder = localCollection?._sourceFolder || (collection as any)._sourceFolder || 'collections';
@@ -447,12 +448,15 @@ export class InteractiveCLI {
447
448
  MessageFormatter.info(`📊 ${tablesCount} tables available from tables/ folder`, { prefix: "Collections" });
448
449
  }
449
450
 
450
- // Ask user if they want to filter by database or show all
451
- const { filterChoice } = await inquirer.prompt([
452
- {
453
- type: "list",
454
- name: "filterChoice",
455
- message: chalk.blue("How would you like to view collections/tables?"),
451
+ // Show current database context clearly before view mode selection
452
+ MessageFormatter.info(`DB: ${database.name}`, { prefix: "Collections" });
453
+
454
+ // Ask user if they want to filter by database or show all
455
+ const { filterChoice } = await inquirer.prompt([
456
+ {
457
+ type: "list",
458
+ name: "filterChoice",
459
+ message: chalk.blue("How would you like to view collections/tables?"),
456
460
  choices: [
457
461
  {
458
462
  name: `Show all available collections/tables (${totalCount} total) - You can push any collection to any database`,
@@ -629,7 +633,7 @@ export class InteractiveCLI {
629
633
  const allFunctions = [
630
634
  ...localFunctions,
631
635
  ...remoteFunctions.functions.filter(
632
- (rf: any) => !localFunctions.some((lf) => lf.name === rf.name)
636
+ (rf: any) => !localFunctions.some((lf) => lf.name === rf.name || lf.$id === rf.$id)
633
637
  ),
634
638
  ];
635
639
 
@@ -640,7 +644,7 @@ export class InteractiveCLI {
640
644
  message,
641
645
  choices: allFunctions.map((f) => ({
642
646
  name: `${f.name} (${f.$id})${
643
- localFunctions.some((lf) => lf.name === f.name)
647
+ localFunctions.some((lf) => lf.name === f.name || lf.$id === f.$id)
644
648
  ? " (Local)"
645
649
  : " (Remote)"
646
650
  }`,
@@ -980,6 +984,7 @@ export class InteractiveCLI {
980
984
  $updatedAt: DateTime.now().toISO(),
981
985
  name: db.name,
982
986
  enabled: true,
987
+ type: "tablesdb" as DatabaseType,
983
988
  }));
984
989
  }
985
990