appwrite-utils-cli 1.8.8 → 1.9.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.
- package/CHANGELOG.md +8 -0
- package/dist/adapters/DatabaseAdapter.d.ts +9 -0
- package/dist/adapters/LegacyAdapter.js +1 -1
- package/dist/adapters/TablesDBAdapter.js +29 -4
- package/dist/cli/commands/databaseCommands.d.ts +1 -0
- package/dist/cli/commands/databaseCommands.js +90 -0
- package/dist/cli/commands/functionCommands.js +53 -48
- package/dist/config/ConfigManager.d.ts +5 -0
- package/dist/config/ConfigManager.js +1 -1
- package/dist/config/services/ConfigDiscoveryService.d.ts +43 -47
- package/dist/config/services/ConfigDiscoveryService.js +155 -207
- package/dist/config/services/ConfigLoaderService.js +2 -7
- package/dist/config/yamlConfig.d.ts +2 -2
- package/dist/main.js +9 -1
- package/dist/migrations/appwriteToX.d.ts +1 -1
- package/dist/migrations/appwriteToX.js +14 -2
- package/dist/migrations/dataLoader.d.ts +3 -3
- package/dist/storage/schemas.d.ts +4 -4
- package/dist/utils/projectConfig.d.ts +4 -1
- package/dist/utils/projectConfig.js +41 -6
- package/dist/utilsController.d.ts +1 -0
- package/dist/utilsController.js +2 -1
- package/package.json +2 -1
- package/src/adapters/DatabaseAdapter.ts +12 -0
- package/src/adapters/LegacyAdapter.ts +28 -28
- package/src/adapters/TablesDBAdapter.ts +46 -4
- package/src/cli/commands/databaseCommands.ts +141 -11
- package/src/cli/commands/functionCommands.ts +72 -67
- package/src/config/ConfigManager.ts +10 -1
- package/src/config/services/ConfigDiscoveryService.ts +180 -233
- package/src/config/services/ConfigLoaderService.ts +2 -10
- package/src/main.ts +213 -204
- package/src/migrations/appwriteToX.ts +34 -22
- package/src/utils/projectConfig.ts +57 -16
- package/src/utilsController.ts +73 -72
package/CHANGELOG.md
CHANGED
|
@@ -25,3 +25,11 @@ All notable changes to this project will be documented in this file.
|
|
|
25
25
|
- Enhancement: Interactive selection adds “Use same selection as before” when choosing tables per database, speeding up multi-DB workflows.
|
|
26
26
|
- Behavior: Non-interactive `--push` with both `--dbIds` and `--collectionIds` is fully non-interactive. The provided IDs are applied as-is to every selected database and the confirmation summary is skipped.
|
|
27
27
|
- Improvement: Selective push reloads local config from disk before pushing to ensure the latest YAML/TS changes are used.
|
|
28
|
+
- Fix: Sync-from-Appwrite now captures function `scopes` and writes them to config; deployments use `scopes` from either central config or `.fnconfig.yaml`.
|
|
29
|
+
- Change: Removed global prompt to choose between central `config.yaml` and per-function `.fnconfig.yaml` during function deployments. The tool now prompts per function only when both sources exist for that function (with an option to merge where `.fnconfig` overrides).
|
|
30
|
+
|
|
31
|
+
## 1.8.9 - 2025-10-29
|
|
32
|
+
|
|
33
|
+
- Fix: Functions sync now fetches full details via `getFunction` per function to reliably capture `scopes` (prevents empty scopes in some environments).
|
|
34
|
+
- Confirmed schema: YAML `appwrite-config.schema.json` supports `functions[].dirPath`; writers preserve `dirPath` when present.
|
|
35
|
+
- Polish: Per-function config source selection is now applied at deploy time when both central and `.fnconfig.yaml` are present.
|
|
@@ -105,6 +105,15 @@ export interface CreateAttributeParams {
|
|
|
105
105
|
default?: any;
|
|
106
106
|
array?: boolean;
|
|
107
107
|
encrypt?: boolean;
|
|
108
|
+
min?: number;
|
|
109
|
+
max?: number;
|
|
110
|
+
elements?: string[];
|
|
111
|
+
relatedCollection?: string;
|
|
112
|
+
relationType?: 'oneToOne' | 'manyToOne' | 'oneToMany' | 'manyToMany';
|
|
113
|
+
twoWay?: boolean;
|
|
114
|
+
twoWayKey?: string;
|
|
115
|
+
onDelete?: 'setNull' | 'cascade' | 'restrict';
|
|
116
|
+
side?: 'parent' | 'child';
|
|
108
117
|
[key: string]: any;
|
|
109
118
|
}
|
|
110
119
|
export interface UpdateAttributeParams {
|
|
@@ -316,7 +316,7 @@ export class LegacyAdapter extends BaseAdapter {
|
|
|
316
316
|
relatedCollectionId: params.relatedCollection || '',
|
|
317
317
|
type: (params.type || 'oneToOne'),
|
|
318
318
|
twoWay: params.twoWay ?? false,
|
|
319
|
-
onDelete: params.onDelete || 'restrict'
|
|
319
|
+
onDelete: (params.onDelete || 'restrict')
|
|
320
320
|
});
|
|
321
321
|
break;
|
|
322
322
|
default:
|
|
@@ -308,10 +308,10 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
|
308
308
|
tableId: params.tableId,
|
|
309
309
|
key: params.key,
|
|
310
310
|
relatedTableId: params.relatedCollection || '',
|
|
311
|
-
type: (params.
|
|
311
|
+
type: (params.relationType || 'oneToOne'),
|
|
312
312
|
twoWay: params.twoWay ?? false,
|
|
313
313
|
twoWayKey: params.twoWayKey,
|
|
314
|
-
onDelete: params.onDelete || 'restrict'
|
|
314
|
+
onDelete: (params.onDelete || 'restrict')
|
|
315
315
|
});
|
|
316
316
|
break;
|
|
317
317
|
default:
|
|
@@ -505,7 +505,21 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
|
505
505
|
let queries;
|
|
506
506
|
// Wipe mode: use Query.limit for deleting without fetching
|
|
507
507
|
if (params.rowIds.length === 0) {
|
|
508
|
-
const batchSize = params.batchSize ||
|
|
508
|
+
const batchSize = params.batchSize || 1000;
|
|
509
|
+
// Validation: Ensure table exists before wiping
|
|
510
|
+
try {
|
|
511
|
+
await this.getTable({
|
|
512
|
+
databaseId: params.databaseId,
|
|
513
|
+
tableId: params.tableId
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
const errorMessage = error?.message || String(error);
|
|
518
|
+
if (errorMessage.toLowerCase().includes('not found') || error?.code === 404) {
|
|
519
|
+
throw new AdapterError(`Table '${params.tableId}' not found in database '${params.databaseId}'`, 'TABLE_NOT_FOUND', error);
|
|
520
|
+
}
|
|
521
|
+
throw error; // Re-throw other errors
|
|
522
|
+
}
|
|
509
523
|
queries = [Query.limit(batchSize)];
|
|
510
524
|
}
|
|
511
525
|
// Specific IDs mode: chunk into batches of 80-90 to stay within Appwrite limits
|
|
@@ -526,7 +540,18 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
|
526
540
|
};
|
|
527
541
|
}
|
|
528
542
|
catch (error) {
|
|
529
|
-
|
|
543
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
544
|
+
// Enhanced error categorization
|
|
545
|
+
if (errorMessage.toLowerCase().includes('not found') || error?.code === 404) {
|
|
546
|
+
throw new AdapterError(`Table or rows not found: ${errorMessage}`, 'NOT_FOUND', error instanceof Error ? error : undefined);
|
|
547
|
+
}
|
|
548
|
+
else if (errorMessage.toLowerCase().includes('permission') || errorMessage.toLowerCase().includes('unauthorized') || error?.code === 403) {
|
|
549
|
+
throw new AdapterError(`Permission denied for bulk delete: ${errorMessage}`, 'PERMISSION_DENIED', error instanceof Error ? error : undefined);
|
|
550
|
+
}
|
|
551
|
+
else if (errorMessage.toLowerCase().includes('rate limit') || error?.code === 429) {
|
|
552
|
+
throw new AdapterError(`Rate limit exceeded during bulk delete: ${errorMessage}`, 'RATE_LIMIT', error instanceof Error ? error : undefined);
|
|
553
|
+
}
|
|
554
|
+
throw new AdapterError(`Failed to bulk delete rows: ${errorMessage}`, 'BULK_DELETE_ROWS_FAILED', error instanceof Error ? error : undefined);
|
|
530
555
|
}
|
|
531
556
|
}
|
|
532
557
|
// Metadata and Capabilities
|
|
@@ -10,4 +10,5 @@ export declare const databaseCommands: {
|
|
|
10
10
|
executeUnifiedBackup(cli: InteractiveCLI, trackingDatabaseId: string, scope: any): Promise<void>;
|
|
11
11
|
wipeDatabase(cli: InteractiveCLI): Promise<void>;
|
|
12
12
|
wipeCollections(cli: InteractiveCLI): Promise<void>;
|
|
13
|
+
wipeTablesData(cli: InteractiveCLI): Promise<void>;
|
|
13
14
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import inquirer from "inquirer";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { Query } from "node-appwrite";
|
|
4
5
|
import { MessageFormatter } from "../../shared/messageFormatter.js";
|
|
5
6
|
import { ConfirmationDialogs } from "../../shared/confirmationDialogs.js";
|
|
6
7
|
import { SelectionDialogs } from "../../shared/selectionDialogs.js";
|
|
@@ -8,6 +9,7 @@ import { logger } from "../../shared/logging.js";
|
|
|
8
9
|
import { fetchAllDatabases } from "../../databases/methods.js";
|
|
9
10
|
import { listBuckets } from "../../storage/methods.js";
|
|
10
11
|
import { getFunction, downloadLatestFunctionDeployment } from "../../functions/methods.js";
|
|
12
|
+
import { wipeTableRows } from "../../collections/wipeOperations.js";
|
|
11
13
|
export const databaseCommands = {
|
|
12
14
|
async syncDb(cli) {
|
|
13
15
|
MessageFormatter.progress("Pushing local configuration to Appwrite...", { prefix: "Database" });
|
|
@@ -550,5 +552,93 @@ export const databaseCommands = {
|
|
|
550
552
|
}
|
|
551
553
|
}
|
|
552
554
|
MessageFormatter.success("Wipe collections operation completed", { prefix: "Wipe" });
|
|
555
|
+
},
|
|
556
|
+
async wipeTablesData(cli) {
|
|
557
|
+
const controller = cli.controller;
|
|
558
|
+
if (!controller?.adapter) {
|
|
559
|
+
throw new Error("Database adapter is not initialized. TablesDB operations require adapter support.");
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
// Step 1: Select database (single selection for clearer UX)
|
|
563
|
+
const databases = await fetchAllDatabases(controller.database);
|
|
564
|
+
if (!databases || databases.length === 0) {
|
|
565
|
+
MessageFormatter.warning("No databases found", { prefix: "Wipe" });
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const { selectedDatabase } = await inquirer.prompt([
|
|
569
|
+
{
|
|
570
|
+
type: "list",
|
|
571
|
+
name: "selectedDatabase",
|
|
572
|
+
message: "Select database containing tables to wipe:",
|
|
573
|
+
choices: databases.map((db) => ({
|
|
574
|
+
name: `${db.name} (${db.$id})`,
|
|
575
|
+
value: db
|
|
576
|
+
}))
|
|
577
|
+
}
|
|
578
|
+
]);
|
|
579
|
+
const database = selectedDatabase;
|
|
580
|
+
// Step 2: Get available tables
|
|
581
|
+
const adapter = controller.adapter;
|
|
582
|
+
const tablesResponse = await adapter.listTables({
|
|
583
|
+
databaseId: database.$id,
|
|
584
|
+
queries: [Query.limit(500)]
|
|
585
|
+
});
|
|
586
|
+
const availableTables = tablesResponse.tables || [];
|
|
587
|
+
if (availableTables.length === 0) {
|
|
588
|
+
MessageFormatter.warning(`No tables found in database: ${database.name}`, { prefix: "Wipe" });
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
// Step 3: Select tables using existing SelectionDialogs
|
|
592
|
+
const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(database.$id, database.name, availableTables, [], // No configured tables context needed for wipe
|
|
593
|
+
{
|
|
594
|
+
showSelectAll: true,
|
|
595
|
+
allowNewOnly: false,
|
|
596
|
+
defaultSelected: []
|
|
597
|
+
});
|
|
598
|
+
if (selectedTableIds.length === 0) {
|
|
599
|
+
MessageFormatter.warning("No tables selected. Operation cancelled.", { prefix: "Wipe" });
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
// Step 4: Show confirmation with table details
|
|
603
|
+
const selectedTables = availableTables.filter((t) => selectedTableIds.includes(t.$id));
|
|
604
|
+
const tableNames = selectedTables.map((t) => t.name);
|
|
605
|
+
console.log(chalk.yellow.bold("\n⚠️ WARNING: Table Row Wipe Operation"));
|
|
606
|
+
console.log(chalk.yellow("This will delete ALL ROWS from the selected tables."));
|
|
607
|
+
console.log(chalk.yellow("The table structures will remain intact.\n"));
|
|
608
|
+
console.log(chalk.cyan("Database:"), chalk.white(database.name));
|
|
609
|
+
console.log(chalk.cyan("Tables to wipe:"));
|
|
610
|
+
tableNames.forEach((name) => console.log(chalk.white(` • ${name}`)));
|
|
611
|
+
console.log();
|
|
612
|
+
const { confirmed } = await inquirer.prompt([
|
|
613
|
+
{
|
|
614
|
+
type: "confirm",
|
|
615
|
+
name: "confirmed",
|
|
616
|
+
message: chalk.red.bold("Are you ABSOLUTELY SURE you want to wipe these table rows?"),
|
|
617
|
+
default: false
|
|
618
|
+
}
|
|
619
|
+
]);
|
|
620
|
+
if (!confirmed) {
|
|
621
|
+
MessageFormatter.info("Wipe operation cancelled by user", { prefix: "Wipe" });
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
// Step 5: Execute wipe using existing wipeTableRows function
|
|
625
|
+
MessageFormatter.progress("Starting table row wipe operation...", { prefix: "Wipe" });
|
|
626
|
+
for (const table of selectedTables) {
|
|
627
|
+
try {
|
|
628
|
+
MessageFormatter.info(`Wiping rows from table: ${table.name}`, { prefix: "Wipe" });
|
|
629
|
+
// Use existing wipeTableRows from wipeOperations.ts
|
|
630
|
+
await wipeTableRows(adapter, database.$id, table.$id);
|
|
631
|
+
MessageFormatter.success(`Successfully wiped rows from table: ${table.name}`, { prefix: "Wipe" });
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
MessageFormatter.error(`Failed to wipe table ${table.name}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Wipe" });
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
MessageFormatter.success(`Wipe operation completed for ${selectedTables.length} table(s)`, { prefix: "Wipe" });
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
MessageFormatter.error("Table wipe operation failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Wipe" });
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
553
643
|
}
|
|
554
644
|
};
|
|
@@ -111,38 +111,14 @@ export const functionCommands = {
|
|
|
111
111
|
MessageFormatter.error("Failed to initialize controller or load config", undefined, { prefix: "Functions" });
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
|
-
//
|
|
115
|
-
|
|
114
|
+
// Discover per-function .fnconfig.yaml definitions and merge with central list for selection
|
|
115
|
+
// No global prompt; we'll handle conflicts per-function if both exist.
|
|
116
|
+
let discovered = [];
|
|
117
|
+
let central = cli.controller.config.functions || [];
|
|
116
118
|
try {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
name: 'source',
|
|
121
|
-
message: 'Select function config source:',
|
|
122
|
-
choices: [
|
|
123
|
-
{ name: 'config.yaml functions (central)', value: 'central' },
|
|
124
|
-
{ name: '.fnconfig.yaml (discovered per-function)', value: 'fnconfig' },
|
|
125
|
-
{ name: 'Both (merge; .fnconfig overrides)', value: 'both' },
|
|
126
|
-
],
|
|
127
|
-
default: 'both'
|
|
128
|
-
}
|
|
129
|
-
]);
|
|
130
|
-
sourceChoice = answer.source;
|
|
131
|
-
}
|
|
132
|
-
catch { }
|
|
133
|
-
try {
|
|
134
|
-
const discovered = discoverFnConfigs(cli.currentDir);
|
|
135
|
-
const central = cli.controller.config.functions || [];
|
|
136
|
-
if (sourceChoice === 'central') {
|
|
137
|
-
cli.controller.config.functions = central;
|
|
138
|
-
}
|
|
139
|
-
else if (sourceChoice === 'fnconfig') {
|
|
140
|
-
cli.controller.config.functions = discovered;
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
const merged = mergeDiscoveredFunctions(central, discovered);
|
|
144
|
-
cli.controller.config.functions = merged;
|
|
145
|
-
}
|
|
119
|
+
discovered = discoverFnConfigs(cli.currentDir);
|
|
120
|
+
const merged = mergeDiscoveredFunctions(central, discovered);
|
|
121
|
+
cli.controller.config.functions = merged;
|
|
146
122
|
}
|
|
147
123
|
catch { }
|
|
148
124
|
const functions = await cli.selectFunctions("Select function(s) to deploy:", true, true);
|
|
@@ -155,22 +131,51 @@ export const functionCommands = {
|
|
|
155
131
|
MessageFormatter.error("Invalid function configuration", undefined, { prefix: "Functions" });
|
|
156
132
|
return;
|
|
157
133
|
}
|
|
134
|
+
// Resolve effective config for this function (prefer per-function choice if both sources exist)
|
|
135
|
+
const byIdOrName = (arr) => arr.find((f) => f?.$id === functionConfig.$id || f?.name === functionConfig.name);
|
|
136
|
+
const centralDef = byIdOrName(central);
|
|
137
|
+
const discoveredDef = byIdOrName(discovered);
|
|
138
|
+
let effectiveConfig = functionConfig;
|
|
139
|
+
if (centralDef && discoveredDef) {
|
|
140
|
+
try {
|
|
141
|
+
const answer = await inquirer.prompt([
|
|
142
|
+
{
|
|
143
|
+
type: 'list',
|
|
144
|
+
name: 'cfgChoice',
|
|
145
|
+
message: `Multiple configs found for '${functionConfig.name}'. Which to use?`,
|
|
146
|
+
choices: [
|
|
147
|
+
{ name: 'config.yaml (central)', value: 'central' },
|
|
148
|
+
{ name: '.fnconfig.yaml (local file)', value: 'fnconfig' },
|
|
149
|
+
{ name: 'Merge (.fnconfig overrides central)', value: 'merge' },
|
|
150
|
+
],
|
|
151
|
+
default: 'fnconfig'
|
|
152
|
+
}
|
|
153
|
+
]);
|
|
154
|
+
if (answer.cfgChoice === 'central')
|
|
155
|
+
effectiveConfig = centralDef;
|
|
156
|
+
else if (answer.cfgChoice === 'fnconfig')
|
|
157
|
+
effectiveConfig = discoveredDef;
|
|
158
|
+
else
|
|
159
|
+
effectiveConfig = { ...centralDef, ...discoveredDef };
|
|
160
|
+
}
|
|
161
|
+
catch { }
|
|
162
|
+
}
|
|
158
163
|
// Ensure functions array exists
|
|
159
164
|
if (!cli.controller.config.functions) {
|
|
160
165
|
cli.controller.config.functions = [];
|
|
161
166
|
}
|
|
162
|
-
const functionNameLower =
|
|
167
|
+
const functionNameLower = effectiveConfig.name
|
|
163
168
|
.toLowerCase()
|
|
164
169
|
.replace(/\s+/g, "-");
|
|
165
170
|
// Debug logging
|
|
166
171
|
MessageFormatter.info(`🔍 Function deployment debug:`, { prefix: "Functions" });
|
|
167
|
-
MessageFormatter.info(` Function name: ${
|
|
168
|
-
MessageFormatter.info(` Function ID: ${
|
|
169
|
-
MessageFormatter.info(` Config dirPath: ${
|
|
170
|
-
if (
|
|
171
|
-
const expandedPath =
|
|
172
|
-
?
|
|
173
|
-
:
|
|
172
|
+
MessageFormatter.info(` Function name: ${effectiveConfig.name}`, { prefix: "Functions" });
|
|
173
|
+
MessageFormatter.info(` Function ID: ${effectiveConfig.$id}`, { prefix: "Functions" });
|
|
174
|
+
MessageFormatter.info(` Config dirPath: ${effectiveConfig.dirPath || 'undefined'}`, { prefix: "Functions" });
|
|
175
|
+
if (effectiveConfig.dirPath) {
|
|
176
|
+
const expandedPath = effectiveConfig.dirPath.startsWith('~/')
|
|
177
|
+
? effectiveConfig.dirPath.replace('~', os.homedir())
|
|
178
|
+
: effectiveConfig.dirPath;
|
|
174
179
|
MessageFormatter.info(` Expanded dirPath: ${expandedPath}`, { prefix: "Functions" });
|
|
175
180
|
}
|
|
176
181
|
MessageFormatter.info(` Appwrite folder: ${cli.controller.getAppwriteFolderPath()}`, { prefix: "Functions" });
|
|
@@ -182,10 +187,10 @@ export const functionCommands = {
|
|
|
182
187
|
// Check locations in priority order:
|
|
183
188
|
const priorityLocations = [
|
|
184
189
|
// 1. Config dirPath if specified (with tilde expansion)
|
|
185
|
-
|
|
186
|
-
? (require('node:path').isAbsolute(expandTildePath(
|
|
187
|
-
? expandTildePath(
|
|
188
|
-
: require('node:path').resolve(yamlBaseDir, expandTildePath(
|
|
190
|
+
effectiveConfig.dirPath
|
|
191
|
+
? (require('node:path').isAbsolute(expandTildePath(effectiveConfig.dirPath))
|
|
192
|
+
? expandTildePath(effectiveConfig.dirPath)
|
|
193
|
+
: require('node:path').resolve(yamlBaseDir, expandTildePath(effectiveConfig.dirPath)))
|
|
189
194
|
: undefined,
|
|
190
195
|
// 2. Appwrite config folder/functions/name
|
|
191
196
|
join(cli.controller.getAppwriteFolderPath(), "functions", functionNameLower),
|
|
@@ -226,10 +231,10 @@ export const functionCommands = {
|
|
|
226
231
|
if (shouldDownload) {
|
|
227
232
|
try {
|
|
228
233
|
MessageFormatter.progress("Downloading latest deployment...", { prefix: "Functions" });
|
|
229
|
-
const { path: downloadedPath, function: remoteFunction } = await downloadLatestFunctionDeployment(cli.controller.appwriteServer,
|
|
234
|
+
const { path: downloadedPath, function: remoteFunction } = await downloadLatestFunctionDeployment(cli.controller.appwriteServer, effectiveConfig.$id, join(cli.controller.getAppwriteFolderPath(), "functions"));
|
|
230
235
|
MessageFormatter.success(`✨ Function downloaded to ${downloadedPath}`, { prefix: "Functions" });
|
|
231
236
|
functionPath = downloadedPath;
|
|
232
|
-
|
|
237
|
+
effectiveConfig.dirPath = downloadedPath;
|
|
233
238
|
const existingIndex = cli.controller.config.functions.findIndex((f) => f?.$id === remoteFunction.$id);
|
|
234
239
|
if (existingIndex >= 0) {
|
|
235
240
|
cli.controller.config.functions[existingIndex].dirPath =
|
|
@@ -243,7 +248,7 @@ export const functionCommands = {
|
|
|
243
248
|
}
|
|
244
249
|
}
|
|
245
250
|
else {
|
|
246
|
-
MessageFormatter.error(`Function ${
|
|
251
|
+
MessageFormatter.error(`Function ${effectiveConfig.name} not found locally. Cannot deploy.`, undefined, { prefix: "Functions" });
|
|
247
252
|
return;
|
|
248
253
|
}
|
|
249
254
|
}
|
|
@@ -252,8 +257,8 @@ export const functionCommands = {
|
|
|
252
257
|
return;
|
|
253
258
|
}
|
|
254
259
|
try {
|
|
255
|
-
await deployLocalFunction(cli.controller.appwriteServer,
|
|
256
|
-
...
|
|
260
|
+
await deployLocalFunction(cli.controller.appwriteServer, effectiveConfig.name, {
|
|
261
|
+
...effectiveConfig,
|
|
257
262
|
dirPath: functionPath,
|
|
258
263
|
}, functionPath);
|
|
259
264
|
MessageFormatter.success("Function deployed successfully!", { prefix: "Functions" });
|
|
@@ -54,6 +54,11 @@ export interface ConfigLoadOptions {
|
|
|
54
54
|
* Override session authentication (used for preserving session on reload)
|
|
55
55
|
*/
|
|
56
56
|
sessionOverride?: SessionAuthInfo;
|
|
57
|
+
/**
|
|
58
|
+
* Prefer loading from appwrite.config.json over config.yaml
|
|
59
|
+
* @default false
|
|
60
|
+
*/
|
|
61
|
+
preferJson?: boolean;
|
|
57
62
|
}
|
|
58
63
|
/**
|
|
59
64
|
* Filter function for collections (accepts both Collection and CollectionCreate)
|
|
@@ -139,7 +139,7 @@ export class ConfigManager {
|
|
|
139
139
|
}
|
|
140
140
|
logger.debug("Loading config from file", { prefix: "ConfigManager", options });
|
|
141
141
|
// 2. Discover config file
|
|
142
|
-
const configPath = this.discoveryService.findConfig(options.configDir || process.cwd());
|
|
142
|
+
const configPath = await this.discoveryService.findConfig(options.configDir || process.cwd(), options.preferJson || false);
|
|
143
143
|
if (!configPath) {
|
|
144
144
|
const searchDir = options.configDir || process.cwd();
|
|
145
145
|
throw new Error(`No Appwrite configuration found in "${searchDir}".\n` +
|
|
@@ -10,74 +10,76 @@ export interface DiscoveryResult {
|
|
|
10
10
|
/**
|
|
11
11
|
* Service for discovering Appwrite configuration files and collection/table definitions.
|
|
12
12
|
*
|
|
13
|
+
* Uses find-up for intelligent searching with git repository boundary detection:
|
|
14
|
+
* 1. Finds .git directory to establish repo root boundary
|
|
15
|
+
* 2. Searches UP from current directory to repo root
|
|
16
|
+
* 3. Searches DOWN recursively within repo root
|
|
17
|
+
*
|
|
13
18
|
* Search Priority:
|
|
14
19
|
* 1. YAML configs (.appwrite/config.yaml, .appwrite/config.yml, etc.)
|
|
15
|
-
* 2.
|
|
16
|
-
* 3.
|
|
17
|
-
*
|
|
18
|
-
* Features:
|
|
19
|
-
* - Searches up directory tree (max 5 levels)
|
|
20
|
-
* - Ignores common directories (node_modules, .git, etc.)
|
|
21
|
-
* - Discovers both collections/ and tables/ directories
|
|
22
|
-
* - Recursive subdirectory scanning for .appwrite folders
|
|
20
|
+
* 2. JSON configs (appwrite.config.json, appwrite.json)
|
|
21
|
+
* 3. TypeScript configs (appwriteConfig.ts)
|
|
23
22
|
*/
|
|
24
23
|
export declare class ConfigDiscoveryService {
|
|
25
24
|
/**
|
|
26
25
|
* YAML configuration file names to search for
|
|
27
26
|
*/
|
|
28
27
|
private readonly YAML_FILENAMES;
|
|
28
|
+
/**
|
|
29
|
+
* JSON configuration file names to search for
|
|
30
|
+
*/
|
|
31
|
+
private readonly JSON_FILENAMES;
|
|
29
32
|
/**
|
|
30
33
|
* TypeScript configuration file names to search for
|
|
31
34
|
*/
|
|
32
35
|
private readonly TS_FILENAMES;
|
|
33
36
|
/**
|
|
34
|
-
*
|
|
37
|
+
* Finds the git repository root directory
|
|
38
|
+
* @param startDir The directory to start searching from
|
|
39
|
+
* @returns Path to the repository root, or startDir if no .git found
|
|
35
40
|
*/
|
|
36
|
-
private
|
|
41
|
+
private findRepoRoot;
|
|
37
42
|
/**
|
|
38
|
-
*
|
|
43
|
+
* Recursively searches downward for files matching patterns
|
|
44
|
+
* @param dir Directory to search in
|
|
45
|
+
* @param patterns File patterns to match
|
|
46
|
+
* @param maxDepth Maximum depth to search
|
|
47
|
+
* @param currentDepth Current recursion depth
|
|
48
|
+
* @returns First matching file path or null
|
|
39
49
|
*/
|
|
40
|
-
private
|
|
50
|
+
private searchDownward;
|
|
41
51
|
/**
|
|
42
|
-
* Finds any configuration file with priority
|
|
52
|
+
* Finds any configuration file with configurable priority
|
|
43
53
|
* @param startDir The directory to start searching from
|
|
54
|
+
* @param preferJson If true, prioritizes appwrite.config.json over YAML (default: false)
|
|
44
55
|
* @returns Path to the configuration file or null if not found
|
|
56
|
+
*
|
|
57
|
+
* Default priority: YAML → JSON → TypeScript
|
|
58
|
+
* With preferJson=true: JSON → YAML → TypeScript
|
|
45
59
|
*/
|
|
46
|
-
findConfig(startDir: string): string | null
|
|
60
|
+
findConfig(startDir: string, preferJson?: boolean): Promise<string | null>;
|
|
47
61
|
/**
|
|
48
62
|
* Finds YAML configuration files
|
|
49
|
-
* Searches
|
|
63
|
+
* Searches UP to repo root, then DOWN from repo root
|
|
50
64
|
* @param startDir The directory to start searching from
|
|
65
|
+
* @param repoRoot The repository root boundary
|
|
51
66
|
* @returns Path to the YAML config file or null if not found
|
|
52
67
|
*/
|
|
53
|
-
findYamlConfig(startDir: string): string | null
|
|
68
|
+
findYamlConfig(startDir: string, repoRoot?: string): Promise<string | null>;
|
|
54
69
|
/**
|
|
55
|
-
*
|
|
56
|
-
* @param
|
|
57
|
-
* @param
|
|
58
|
-
* @returns Path to
|
|
70
|
+
* Finds JSON project configuration files (appwrite.config.json, appwrite.json)
|
|
71
|
+
* @param startDir The directory to start searching from
|
|
72
|
+
* @param repoRoot The repository root boundary
|
|
73
|
+
* @returns Path to the JSON config file or null if not found
|
|
59
74
|
*/
|
|
60
|
-
|
|
75
|
+
findProjectConfig(startDir: string, repoRoot?: string): Promise<string | null>;
|
|
61
76
|
/**
|
|
62
77
|
* Finds TypeScript configuration files (appwriteConfig.ts)
|
|
63
78
|
* @param startDir The directory to start searching from
|
|
79
|
+
* @param repoRoot The repository root boundary
|
|
64
80
|
* @returns Path to the TypeScript config file or null if not found
|
|
65
81
|
*/
|
|
66
|
-
findTypeScriptConfig(startDir: string): string | null
|
|
67
|
-
/**
|
|
68
|
-
* Recursively searches for TypeScript configuration files
|
|
69
|
-
* @param dir The directory to search
|
|
70
|
-
* @param depth Current search depth
|
|
71
|
-
* @returns Path to TypeScript config or null
|
|
72
|
-
*/
|
|
73
|
-
private findTypeScriptConfigRecursive;
|
|
74
|
-
/**
|
|
75
|
-
* Finds project configuration JSON files (appwrite.json, appwrite.config.json)
|
|
76
|
-
* Searches up to 5 levels up the directory tree
|
|
77
|
-
* @param startDir The directory to start searching from
|
|
78
|
-
* @returns Path to the JSON config file or null if not found
|
|
79
|
-
*/
|
|
80
|
-
findProjectConfig(startDir?: string): string | null;
|
|
82
|
+
findTypeScriptConfig(startDir: string, repoRoot?: string): Promise<string | null>;
|
|
81
83
|
/**
|
|
82
84
|
* Discovers collection YAML files in a collections/ directory
|
|
83
85
|
* @param collectionsDir Path to the collections directory
|
|
@@ -95,32 +97,26 @@ export declare class ConfigDiscoveryService {
|
|
|
95
97
|
* @param startDir The directory to start searching from
|
|
96
98
|
* @returns Path to .appwrite directory or null if not found
|
|
97
99
|
*/
|
|
98
|
-
findAppwriteDirectory(startDir: string): string | null
|
|
100
|
+
findAppwriteDirectory(startDir: string): Promise<string | null>;
|
|
99
101
|
/**
|
|
100
102
|
* Finds the functions directory
|
|
101
103
|
* @param startDir The directory to start searching from
|
|
102
104
|
* @returns Path to functions directory or null if not found
|
|
103
105
|
*/
|
|
104
|
-
findFunctionsDirectory(startDir: string): string | null
|
|
105
|
-
/**
|
|
106
|
-
* Recursively searches for the functions directory
|
|
107
|
-
* @param dir The directory to search
|
|
108
|
-
* @param depth Current search depth
|
|
109
|
-
* @returns Path to functions directory or null
|
|
110
|
-
*/
|
|
111
|
-
private findFunctionsDirectoryRecursive;
|
|
106
|
+
findFunctionsDirectory(startDir: string): Promise<string | null>;
|
|
112
107
|
/**
|
|
113
108
|
* Gets a summary of all discoverable configuration files
|
|
114
109
|
* Useful for debugging configuration issues
|
|
115
110
|
* @param startDir The directory to start searching from
|
|
116
111
|
* @returns Object containing paths to all discovered config types
|
|
117
112
|
*/
|
|
118
|
-
getConfigurationSummary(startDir: string): {
|
|
113
|
+
getConfigurationSummary(startDir: string): Promise<{
|
|
119
114
|
yaml: string | null;
|
|
120
115
|
typescript: string | null;
|
|
121
116
|
json: string | null;
|
|
122
117
|
appwriteDirectory: string | null;
|
|
123
118
|
functionsDirectory: string | null;
|
|
124
119
|
selectedConfig: string | null;
|
|
125
|
-
|
|
120
|
+
repoRoot: string;
|
|
121
|
+
}>;
|
|
126
122
|
}
|