appwrite-utils-cli 1.7.7 → 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.
- package/SELECTION_DIALOGS.md +146 -0
- package/dist/cli/commands/databaseCommands.js +89 -23
- package/dist/config/services/ConfigLoaderService.d.ts +7 -0
- package/dist/config/services/ConfigLoaderService.js +47 -1
- package/dist/functions/deployments.js +5 -23
- package/dist/functions/methods.js +4 -2
- package/dist/functions/pathResolution.d.ts +37 -0
- package/dist/functions/pathResolution.js +185 -0
- package/dist/functions/templates/count-docs-in-collection/README.md +54 -0
- package/dist/functions/templates/count-docs-in-collection/package.json +25 -0
- package/dist/functions/templates/count-docs-in-collection/src/main.ts +159 -0
- package/dist/functions/templates/count-docs-in-collection/src/request.ts +9 -0
- package/dist/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
- package/dist/functions/templates/hono-typescript/README.md +286 -0
- package/dist/functions/templates/hono-typescript/package.json +26 -0
- package/dist/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
- package/dist/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
- package/dist/functions/templates/hono-typescript/src/app.ts +180 -0
- package/dist/functions/templates/hono-typescript/src/context.ts +103 -0
- package/dist/functions/templates/hono-typescript/src/index.ts +54 -0
- package/dist/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
- package/dist/functions/templates/hono-typescript/tsconfig.json +20 -0
- package/dist/functions/templates/typescript-node/README.md +32 -0
- package/dist/functions/templates/typescript-node/package.json +25 -0
- package/dist/functions/templates/typescript-node/src/context.ts +103 -0
- package/dist/functions/templates/typescript-node/src/index.ts +29 -0
- package/dist/functions/templates/typescript-node/tsconfig.json +28 -0
- package/dist/functions/templates/uv/README.md +31 -0
- package/dist/functions/templates/uv/pyproject.toml +30 -0
- package/dist/functions/templates/uv/src/__init__.py +0 -0
- package/dist/functions/templates/uv/src/context.py +125 -0
- package/dist/functions/templates/uv/src/index.py +46 -0
- package/dist/main.js +175 -4
- package/dist/migrations/appwriteToX.d.ts +27 -2
- package/dist/migrations/appwriteToX.js +293 -69
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
- package/dist/migrations/yaml/generateImportSchemas.js +23 -8
- package/dist/shared/schemaGenerator.js +25 -12
- package/dist/shared/selectionDialogs.d.ts +214 -0
- package/dist/shared/selectionDialogs.js +540 -0
- package/dist/utils/configDiscovery.d.ts +4 -4
- package/dist/utils/configDiscovery.js +66 -30
- package/dist/utils/yamlConverter.d.ts +1 -0
- package/dist/utils/yamlConverter.js +26 -3
- package/dist/utilsController.d.ts +7 -1
- package/dist/utilsController.js +198 -17
- package/package.json +4 -2
- package/scripts/copy-templates.ts +23 -0
- package/src/cli/commands/databaseCommands.ts +133 -34
- package/src/config/services/ConfigLoaderService.ts +62 -1
- package/src/functions/deployments.ts +10 -35
- package/src/functions/methods.ts +4 -2
- package/src/functions/pathResolution.ts +227 -0
- package/src/main.ts +276 -34
- package/src/migrations/appwriteToX.ts +385 -90
- package/src/migrations/yaml/generateImportSchemas.ts +26 -8
- package/src/shared/schemaGenerator.ts +29 -12
- package/src/shared/selectionDialogs.ts +745 -0
- package/src/utils/configDiscovery.ts +83 -39
- package/src/utils/yamlConverter.ts +29 -3
- package/src/utilsController.ts +250 -22
- package/dist/utils/schemaStrings.d.ts +0 -14
- package/dist/utils/schemaStrings.js +0 -428
- package/dist/utils/sessionPreservationExample.d.ts +0 -1666
- package/dist/utils/sessionPreservationExample.js +0 -101
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Selection Dialogs System
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `SelectionDialogs` class provides a comprehensive interactive selection system for the enhanced sync flow in the Appwrite Utils CLI. It enables users to select databases, tables/collections, and storage buckets with visual indicators for configured vs new items.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Visual Indicators**: ✅ for configured items, ○ for new items
|
|
10
|
+
- **Multi-Selection Support**: Checkbox-style selection with "Select All" functionality
|
|
11
|
+
- **Configuration Awareness**: Detects and highlights existing configurations
|
|
12
|
+
- **Grouped Display**: Organizes buckets by database for better context
|
|
13
|
+
- **Comprehensive Confirmation**: Shows detailed summary before sync execution
|
|
14
|
+
- **Graceful Error Handling**: Proper error messages and cancellation support
|
|
15
|
+
- **Type Safety**: Full TypeScript support with proper interfaces
|
|
16
|
+
|
|
17
|
+
## Main Functions
|
|
18
|
+
|
|
19
|
+
### `promptForExistingConfig(configuredItems: any[])`
|
|
20
|
+
Prompts user about existing configuration with options to:
|
|
21
|
+
- Sync existing configured items
|
|
22
|
+
- Add/remove items from configuration
|
|
23
|
+
|
|
24
|
+
### `selectDatabases(availableDatabases, configuredDatabases, options?)`
|
|
25
|
+
Interactive database selection with:
|
|
26
|
+
- Visual indicators for configured vs new databases
|
|
27
|
+
- "Select All" functionality
|
|
28
|
+
- Filtering options (new only, default selections)
|
|
29
|
+
|
|
30
|
+
### `selectTablesForDatabase(databaseId, databaseName, availableTables, configuredTables, options?)`
|
|
31
|
+
Table/collection selection for a specific database with:
|
|
32
|
+
- Database context display
|
|
33
|
+
- Table selection with indicators
|
|
34
|
+
- Multi-selection support
|
|
35
|
+
|
|
36
|
+
### `selectBucketsForDatabases(selectedDatabaseIds, availableBuckets, configuredBuckets, options?)`
|
|
37
|
+
Storage bucket selection with:
|
|
38
|
+
- Grouping by database
|
|
39
|
+
- Relevance filtering (only buckets for selected databases)
|
|
40
|
+
- Ungrouped/general storage support
|
|
41
|
+
|
|
42
|
+
### `confirmSyncSelection(selectionSummary: SyncSelectionSummary)`
|
|
43
|
+
Final confirmation dialog showing:
|
|
44
|
+
- Complete selection summary
|
|
45
|
+
- Statistics (total, new, existing items)
|
|
46
|
+
- Detailed breakdown by category
|
|
47
|
+
|
|
48
|
+
## Usage Example
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { SelectionDialogs } from './shared/selectionDialogs.js';
|
|
52
|
+
import type { Models } from 'node-appwrite';
|
|
53
|
+
|
|
54
|
+
// 1. Check for existing configuration
|
|
55
|
+
const { syncExisting, modifyConfiguration } = await SelectionDialogs.promptForExistingConfig(configuredDatabases);
|
|
56
|
+
|
|
57
|
+
if (modifyConfiguration) {
|
|
58
|
+
// 2. Select databases
|
|
59
|
+
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
|
|
60
|
+
availableDatabases,
|
|
61
|
+
configuredDatabases,
|
|
62
|
+
{ showSelectAll: true }
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// 3. Select tables for each database
|
|
66
|
+
const tableSelectionsMap = new Map<string, string[]>();
|
|
67
|
+
for (const databaseId of selectedDatabaseIds) {
|
|
68
|
+
const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
69
|
+
databaseId,
|
|
70
|
+
databaseName,
|
|
71
|
+
availableTables,
|
|
72
|
+
configuredTables
|
|
73
|
+
);
|
|
74
|
+
tableSelectionsMap.set(databaseId, selectedTableIds);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 4. Select buckets
|
|
78
|
+
const selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
|
|
79
|
+
selectedDatabaseIds,
|
|
80
|
+
availableBuckets,
|
|
81
|
+
configuredBuckets
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// 5. Create selection summary and confirm
|
|
85
|
+
const selectionSummary = SelectionDialogs.createSyncSelectionSummary(
|
|
86
|
+
databaseSelections,
|
|
87
|
+
bucketSelections
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
|
|
91
|
+
|
|
92
|
+
if (confirmed) {
|
|
93
|
+
// Proceed with sync operation
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Configuration Options
|
|
99
|
+
|
|
100
|
+
### Database Selection Options
|
|
101
|
+
- `showSelectAll`: Show "Select All" option (default: true)
|
|
102
|
+
- `allowNewOnly`: Only show new/unconfigured databases (default: false)
|
|
103
|
+
- `defaultSelected`: Array of database IDs to pre-select
|
|
104
|
+
|
|
105
|
+
### Table Selection Options
|
|
106
|
+
- `showSelectAll`: Show "Select All" option (default: true)
|
|
107
|
+
- `allowNewOnly`: Only show new/unconfigured tables (default: false)
|
|
108
|
+
- `defaultSelected`: Array of table IDs to pre-select
|
|
109
|
+
- `showDatabaseContext`: Show database name in header (default: true)
|
|
110
|
+
|
|
111
|
+
### Bucket Selection Options
|
|
112
|
+
- `showSelectAll`: Show "Select All" option (default: true)
|
|
113
|
+
- `allowNewOnly`: Only show new/unconfigured buckets (default: false)
|
|
114
|
+
- `defaultSelected`: Array of bucket IDs to pre-select
|
|
115
|
+
- `groupByDatabase`: Group buckets by database (default: true)
|
|
116
|
+
|
|
117
|
+
## Interfaces
|
|
118
|
+
|
|
119
|
+
### `SyncSelectionSummary`
|
|
120
|
+
Contains complete selection information:
|
|
121
|
+
- `databases`: Array of selected databases with their tables
|
|
122
|
+
- `buckets`: Array of selected buckets
|
|
123
|
+
- `totalDatabases/Tables/Buckets`: Count of selected items
|
|
124
|
+
- `newItems/existingItems`: Breakdown of new vs existing configurations
|
|
125
|
+
|
|
126
|
+
### `DatabaseSelection`
|
|
127
|
+
Represents a selected database:
|
|
128
|
+
- `databaseId/databaseName`: Database identification
|
|
129
|
+
- `tableIds/tableNames`: Selected tables for this database
|
|
130
|
+
- `isNew`: Whether this is a new configuration
|
|
131
|
+
|
|
132
|
+
### `BucketSelection`
|
|
133
|
+
Represents a selected bucket:
|
|
134
|
+
- `bucketId/bucketName`: Bucket identification
|
|
135
|
+
- `databaseId/databaseName`: Associated database (if applicable)
|
|
136
|
+
- `isNew`: Whether this is a new configuration
|
|
137
|
+
|
|
138
|
+
## Integration
|
|
139
|
+
|
|
140
|
+
The selection dialogs are designed to integrate seamlessly with the existing CLI infrastructure:
|
|
141
|
+
|
|
142
|
+
- Uses `MessageFormatter` for consistent styling
|
|
143
|
+
- Integrates with existing logging system
|
|
144
|
+
- Follows established error handling patterns
|
|
145
|
+
- Compatible with existing configuration management
|
|
146
|
+
- Uses inquirer.js for interactive prompts
|
|
@@ -3,32 +3,94 @@ import chalk from "chalk";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { MessageFormatter } from "../../shared/messageFormatter.js";
|
|
5
5
|
import { ConfirmationDialogs } from "../../shared/confirmationDialogs.js";
|
|
6
|
+
import { SelectionDialogs } from "../../shared/selectionDialogs.js";
|
|
7
|
+
import { logger } from "../../shared/logging.js";
|
|
6
8
|
import { fetchAllDatabases } from "../../databases/methods.js";
|
|
7
9
|
import { listBuckets } from "../../storage/methods.js";
|
|
8
10
|
import { getFunction, downloadLatestFunctionDeployment } from "../../functions/methods.js";
|
|
9
11
|
export const databaseCommands = {
|
|
10
12
|
async syncDb(cli) {
|
|
11
13
|
MessageFormatter.progress("Pushing local configuration to Appwrite...", { prefix: "Database" });
|
|
12
|
-
const databases = await cli.selectDatabases(cli.getLocalDatabases(), chalk.blue("Select local databases to push:"), true);
|
|
13
|
-
if (!databases.length) {
|
|
14
|
-
MessageFormatter.warning("No databases selected. Skipping database sync.", { prefix: "Database" });
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
14
|
try {
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
// Initialize controller
|
|
16
|
+
await cli.controller.init();
|
|
17
|
+
// Get available and configured databases
|
|
18
|
+
const availableDatabases = await fetchAllDatabases(cli.controller.database);
|
|
19
|
+
const configuredDatabases = cli.controller.config?.databases || [];
|
|
20
|
+
// Get local collections for selection
|
|
21
|
+
const localCollections = cli.getLocalCollections();
|
|
22
|
+
// Push operations always use local configuration as source of truth
|
|
23
|
+
// Select databases
|
|
24
|
+
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(availableDatabases, configuredDatabases, { showSelectAll: true, allowNewOnly: false });
|
|
25
|
+
if (selectedDatabaseIds.length === 0) {
|
|
26
|
+
MessageFormatter.warning("No databases selected. Skipping database sync.", { prefix: "Database" });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Select tables/collections for each database using the existing method
|
|
30
|
+
const tableSelectionsMap = new Map();
|
|
31
|
+
const availableTablesMap = new Map();
|
|
32
|
+
for (const databaseId of selectedDatabaseIds) {
|
|
33
|
+
const database = availableDatabases.find(db => db.$id === databaseId);
|
|
34
|
+
// Use the existing selectCollectionsAndTables method
|
|
35
|
+
const selectedCollections = await cli.selectCollectionsAndTables(database, cli.controller.database, chalk.blue(`Select collections/tables to push to "${database.name}":`), true, // multiSelect
|
|
22
36
|
true // prefer local
|
|
23
37
|
);
|
|
24
|
-
|
|
38
|
+
// Map selected collections to table IDs
|
|
39
|
+
const selectedTableIds = selectedCollections.map((c) => c.$id || c.id);
|
|
40
|
+
// Store selections
|
|
41
|
+
tableSelectionsMap.set(databaseId, selectedTableIds);
|
|
42
|
+
availableTablesMap.set(databaseId, selectedCollections);
|
|
43
|
+
if (selectedCollections.length === 0) {
|
|
25
44
|
MessageFormatter.warning(`No collections selected for database "${database.name}". Skipping.`, { prefix: "Database" });
|
|
26
45
|
continue;
|
|
27
46
|
}
|
|
28
|
-
// Push selected collections to this specific database
|
|
29
|
-
await cli.controller.syncDb([database], collections);
|
|
30
|
-
MessageFormatter.success(`Pushed ${collections.length} collection(s) to database "${database.name}"`, { prefix: "Database" });
|
|
31
47
|
}
|
|
48
|
+
// Ask if user wants to select buckets
|
|
49
|
+
const { selectBuckets } = await inquirer.prompt([
|
|
50
|
+
{
|
|
51
|
+
type: "confirm",
|
|
52
|
+
name: "selectBuckets",
|
|
53
|
+
message: "Do you want to select storage buckets to sync as well?",
|
|
54
|
+
default: false,
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
let bucketSelections = [];
|
|
58
|
+
if (selectBuckets) {
|
|
59
|
+
// Get available and configured buckets
|
|
60
|
+
try {
|
|
61
|
+
const availableBucketsResponse = await listBuckets(cli.controller.storage);
|
|
62
|
+
const availableBuckets = availableBucketsResponse.buckets || [];
|
|
63
|
+
const configuredBuckets = cli.controller.config?.buckets || [];
|
|
64
|
+
if (availableBuckets.length === 0) {
|
|
65
|
+
MessageFormatter.warning("No storage buckets available in remote instance.", { prefix: "Database" });
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Select buckets using SelectionDialogs
|
|
69
|
+
const selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(selectedDatabaseIds, availableBuckets, configuredBuckets, { showSelectAll: true, groupByDatabase: true });
|
|
70
|
+
if (selectedBucketIds.length > 0) {
|
|
71
|
+
// Create BucketSelection objects
|
|
72
|
+
bucketSelections = SelectionDialogs.createBucketSelection(selectedBucketIds, availableBuckets, configuredBuckets, availableDatabases);
|
|
73
|
+
MessageFormatter.info(`Selected ${bucketSelections.length} storage bucket(s)`, { prefix: "Database" });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
MessageFormatter.warning("Failed to fetch storage buckets. Continuing with databases only.", { prefix: "Database" });
|
|
79
|
+
logger.warn("Storage bucket fetch failed during syncDb", { error: error instanceof Error ? error.message : String(error) });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Create DatabaseSelection objects
|
|
83
|
+
const databaseSelections = SelectionDialogs.createDatabaseSelection(selectedDatabaseIds, availableDatabases, tableSelectionsMap, configuredDatabases, availableTablesMap);
|
|
84
|
+
// Show confirmation summary
|
|
85
|
+
const selectionSummary = SelectionDialogs.createSyncSelectionSummary(databaseSelections, bucketSelections);
|
|
86
|
+
const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary, 'push');
|
|
87
|
+
if (!confirmed) {
|
|
88
|
+
MessageFormatter.info("Push operation cancelled by user", { prefix: "Database" });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Perform selective push using the controller
|
|
92
|
+
MessageFormatter.progress("Starting selective push...", { prefix: "Database" });
|
|
93
|
+
await cli.controller.selectivePush(databaseSelections, bucketSelections);
|
|
32
94
|
MessageFormatter.success("\n✅ All database configurations pushed successfully!", { prefix: "Database" });
|
|
33
95
|
// Then handle functions if requested
|
|
34
96
|
const { syncFunctions } = await inquirer.prompt([
|
|
@@ -73,19 +135,23 @@ export const databaseCommands = {
|
|
|
73
135
|
]);
|
|
74
136
|
if (syncDatabases) {
|
|
75
137
|
const remoteDatabases = await fetchAllDatabases(cli.controller.database);
|
|
76
|
-
//
|
|
77
|
-
MessageFormatter.progress("Pulling collections and generating collection files...", { prefix: "Collections" });
|
|
78
|
-
await cli.controller.synchronizeConfigurations(remoteDatabases);
|
|
79
|
-
// Also configure buckets for any new databases
|
|
138
|
+
// First, prepare the combined database list for bucket configuration
|
|
80
139
|
const localDatabases = cli.controller.config?.databases || [];
|
|
81
|
-
const
|
|
140
|
+
const allDatabases = [
|
|
141
|
+
...localDatabases,
|
|
142
|
+
...remoteDatabases.filter((rd) => !localDatabases.some((ld) => ld.name === rd.name)),
|
|
143
|
+
];
|
|
144
|
+
// Configure buckets FIRST to get user selections before writing config
|
|
145
|
+
MessageFormatter.progress("Configuring storage buckets...", { prefix: "Buckets" });
|
|
146
|
+
const configWithBuckets = await cli.configureBuckets({
|
|
82
147
|
...cli.controller.config,
|
|
83
|
-
databases:
|
|
84
|
-
...localDatabases,
|
|
85
|
-
...remoteDatabases.filter((rd) => !localDatabases.some((ld) => ld.name === rd.name)),
|
|
86
|
-
],
|
|
148
|
+
databases: allDatabases,
|
|
87
149
|
});
|
|
88
|
-
|
|
150
|
+
// Update controller config with bucket selections
|
|
151
|
+
cli.controller.config = configWithBuckets;
|
|
152
|
+
// Now synchronize configurations with the updated config that includes bucket selections
|
|
153
|
+
MessageFormatter.progress("Pulling collections and generating collection files...", { prefix: "Collections" });
|
|
154
|
+
await cli.controller.synchronizeConfigurations(remoteDatabases, configWithBuckets);
|
|
89
155
|
}
|
|
90
156
|
// Then sync functions
|
|
91
157
|
const { syncFunctions } = await inquirer.prompt([
|
|
@@ -30,6 +30,13 @@ export interface CollectionLoadOptions {
|
|
|
30
30
|
* - Validates and normalizes configuration data
|
|
31
31
|
*/
|
|
32
32
|
export declare class ConfigLoaderService {
|
|
33
|
+
/**
|
|
34
|
+
* Normalizes function dirPath to absolute path
|
|
35
|
+
* @param func Function configuration object
|
|
36
|
+
* @param configDir Directory containing the config file
|
|
37
|
+
* @returns Function with normalized dirPath
|
|
38
|
+
*/
|
|
39
|
+
private normalizeFunctionPath;
|
|
33
40
|
/**
|
|
34
41
|
* Loads configuration from a discovered path, auto-detecting the type
|
|
35
42
|
* @param configPath Path to the configuration file
|
|
@@ -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";
|
|
@@ -8,6 +9,7 @@ import { normalizeYamlData } from "../../utils/yamlConverter.js";
|
|
|
8
9
|
import { loadYamlConfig } from "../yamlConfig.js";
|
|
9
10
|
import { loadAppwriteProjectConfig, projectConfigToAppwriteConfig, getCollectionsFromProject } from "../../utils/projectConfig.js";
|
|
10
11
|
import { loadYamlCollection, loadYamlTable, } from "../../utils/configDiscovery.js";
|
|
12
|
+
import { expandTildePath } from "../../functions/pathResolution.js";
|
|
11
13
|
/**
|
|
12
14
|
* Service for loading and parsing Appwrite configuration files.
|
|
13
15
|
*
|
|
@@ -25,6 +27,31 @@ import { loadYamlCollection, loadYamlTable, } from "../../utils/configDiscovery.
|
|
|
25
27
|
* - Validates and normalizes configuration data
|
|
26
28
|
*/
|
|
27
29
|
export class ConfigLoaderService {
|
|
30
|
+
/**
|
|
31
|
+
* Normalizes function dirPath to absolute path
|
|
32
|
+
* @param func Function configuration object
|
|
33
|
+
* @param configDir Directory containing the config file
|
|
34
|
+
* @returns Function with normalized dirPath
|
|
35
|
+
*/
|
|
36
|
+
normalizeFunctionPath(func, configDir) {
|
|
37
|
+
if (!func.dirPath) {
|
|
38
|
+
return func;
|
|
39
|
+
}
|
|
40
|
+
// Expand tilde first
|
|
41
|
+
const expandedPath = expandTildePath(func.dirPath);
|
|
42
|
+
// If already absolute, return as-is
|
|
43
|
+
if (isAbsolute(expandedPath)) {
|
|
44
|
+
return {
|
|
45
|
+
...func,
|
|
46
|
+
dirPath: expandedPath
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Resolve relative to config directory
|
|
50
|
+
return {
|
|
51
|
+
...func,
|
|
52
|
+
dirPath: resolvePath(configDir, expandedPath)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
28
55
|
/**
|
|
29
56
|
* Loads configuration from a discovered path, auto-detecting the type
|
|
30
57
|
* @param configPath Path to the configuration file
|
|
@@ -46,6 +73,11 @@ export class ConfigLoaderService {
|
|
|
46
73
|
if (!partialConfig.appwriteEndpoint || !partialConfig.appwriteProject) {
|
|
47
74
|
throw new Error("JSON project config must contain at minimum 'endpoint' and 'projectId' fields");
|
|
48
75
|
}
|
|
76
|
+
const configDir = path.dirname(configPath);
|
|
77
|
+
// Normalize function paths
|
|
78
|
+
const normalizedFunctions = partialConfig.functions
|
|
79
|
+
? partialConfig.functions.map(func => this.normalizeFunctionPath(func, configDir))
|
|
80
|
+
: [];
|
|
49
81
|
return {
|
|
50
82
|
appwriteEndpoint: partialConfig.appwriteEndpoint,
|
|
51
83
|
appwriteProject: partialConfig.appwriteProject,
|
|
@@ -73,7 +105,7 @@ export class ConfigLoaderService {
|
|
|
73
105
|
},
|
|
74
106
|
databases: partialConfig.databases || [],
|
|
75
107
|
buckets: partialConfig.buckets || [],
|
|
76
|
-
functions:
|
|
108
|
+
functions: normalizedFunctions,
|
|
77
109
|
collections: partialConfig.collections || [],
|
|
78
110
|
sessionCookie: partialConfig.sessionCookie,
|
|
79
111
|
authMethod: partialConfig.authMethod || "auto",
|
|
@@ -114,6 +146,10 @@ export class ConfigLoaderService {
|
|
|
114
146
|
}
|
|
115
147
|
// Load collections and tables from their respective directories
|
|
116
148
|
const configDir = path.dirname(yamlPath);
|
|
149
|
+
// Normalize function paths
|
|
150
|
+
if (config.functions) {
|
|
151
|
+
config.functions = config.functions.map(func => this.normalizeFunctionPath(func, configDir));
|
|
152
|
+
}
|
|
117
153
|
const collectionsDir = path.join(configDir, config.schemaConfig?.collectionsDirectory || "collections");
|
|
118
154
|
const tablesDir = path.join(configDir, config.schemaConfig?.tablesDirectory || "tables");
|
|
119
155
|
// Detect API mode to determine priority order
|
|
@@ -175,6 +211,11 @@ export class ConfigLoaderService {
|
|
|
175
211
|
if (!config) {
|
|
176
212
|
throw new Error(`Failed to load TypeScript config from: ${tsPath}`);
|
|
177
213
|
}
|
|
214
|
+
// Normalize function paths
|
|
215
|
+
const configDir = path.dirname(tsPath);
|
|
216
|
+
if (config.functions) {
|
|
217
|
+
config.functions = config.functions.map(func => this.normalizeFunctionPath(func, configDir));
|
|
218
|
+
}
|
|
178
219
|
MessageFormatter.success(`Loaded TypeScript config from: ${tsPath}`, {
|
|
179
220
|
prefix: "Config",
|
|
180
221
|
});
|
|
@@ -210,6 +251,11 @@ export class ConfigLoaderService {
|
|
|
210
251
|
if (collections.length > 0) {
|
|
211
252
|
appwriteConfig.collections = collections;
|
|
212
253
|
}
|
|
254
|
+
// Normalize function paths
|
|
255
|
+
const configDir = path.dirname(jsonPath);
|
|
256
|
+
if (appwriteConfig.functions) {
|
|
257
|
+
appwriteConfig.functions = appwriteConfig.functions.map(func => this.normalizeFunctionPath(func, configDir));
|
|
258
|
+
}
|
|
213
259
|
MessageFormatter.success(`Loaded project config from: ${jsonPath}`, {
|
|
214
260
|
prefix: "Config",
|
|
215
261
|
});
|
|
@@ -11,23 +11,7 @@ import { execSync } from "child_process";
|
|
|
11
11
|
import { createFunction, getFunction, updateFunction, updateFunctionSpecifications, } from "./methods.js";
|
|
12
12
|
import ignore from "ignore";
|
|
13
13
|
import { MessageFormatter } from "../shared/messageFormatter.js";
|
|
14
|
-
|
|
15
|
-
const normalizedName = functionName.toLowerCase().replace(/\s+/g, "-");
|
|
16
|
-
const dirs = fs.readdirSync(basePath, { withFileTypes: true });
|
|
17
|
-
for (const dir of dirs) {
|
|
18
|
-
if (dir.isDirectory()) {
|
|
19
|
-
const fullPath = join(basePath, dir.name);
|
|
20
|
-
if (dir.name.toLowerCase() === normalizedName) {
|
|
21
|
-
return fullPath;
|
|
22
|
-
}
|
|
23
|
-
const nestedResult = findFunctionDirectory(fullPath, functionName);
|
|
24
|
-
if (nestedResult) {
|
|
25
|
-
return nestedResult;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return undefined;
|
|
30
|
-
};
|
|
14
|
+
import { resolveFunctionDirectory, validateFunctionDirectory } from './pathResolution.js';
|
|
31
15
|
export const deployFunction = async (client, functionId, codePath, activate = true, entrypoint = "index.js", commands = "npm install", ignored = [
|
|
32
16
|
"node_modules",
|
|
33
17
|
".git",
|
|
@@ -123,12 +107,10 @@ export const deployLocalFunction = async (client, functionName, functionConfig,
|
|
|
123
107
|
catch (error) {
|
|
124
108
|
functionExists = false;
|
|
125
109
|
}
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
131
|
-
throw new Error(`Function directory not found at ${resolvedPath}`);
|
|
110
|
+
const configDirPath = process.cwd(); // TODO: This should be passed from caller
|
|
111
|
+
const resolvedPath = resolveFunctionDirectory(functionName, configDirPath, functionConfig.dirPath, functionPath);
|
|
112
|
+
if (!validateFunctionDirectory(resolvedPath)) {
|
|
113
|
+
throw new Error(`Function directory is invalid or missing required files: ${resolvedPath}`);
|
|
132
114
|
}
|
|
133
115
|
if (functionConfig.predeployCommands?.length) {
|
|
134
116
|
MessageFormatter.processing("Executing predeploy commands...", { prefix: "Deployment" });
|
|
@@ -6,6 +6,7 @@ import {} from "appwrite-utils";
|
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { extract as extractTar } from "tar";
|
|
8
8
|
import { MessageFormatter } from "../shared/messageFormatter.js";
|
|
9
|
+
import { expandTildePath, normalizeFunctionName } from "./pathResolution.js";
|
|
9
10
|
/**
|
|
10
11
|
* Validates and filters events array for Appwrite functions
|
|
11
12
|
* - Filters out empty/invalid strings
|
|
@@ -41,7 +42,7 @@ export const downloadLatestFunctionDeployment = async (client, functionId, baseP
|
|
|
41
42
|
const latestDeployment = functionDeployments.deployments[0];
|
|
42
43
|
const deploymentData = await functions.getDeploymentDownload(functionId, latestDeployment.$id);
|
|
43
44
|
// Create function directory using provided basePath
|
|
44
|
-
const functionDir = join(basePath, functionInfo.name
|
|
45
|
+
const functionDir = join(basePath, normalizeFunctionName(functionInfo.name));
|
|
45
46
|
await fs.promises.mkdir(functionDir, { recursive: true });
|
|
46
47
|
// Create temporary file for tar extraction
|
|
47
48
|
const tarPath = join(functionDir, "temp.tar.gz");
|
|
@@ -119,7 +120,8 @@ export const updateFunction = async (client, functionConfig) => {
|
|
|
119
120
|
return functionResponse;
|
|
120
121
|
};
|
|
121
122
|
export const createFunctionTemplate = async (templateType, functionName, basePath = "./functions") => {
|
|
122
|
-
const
|
|
123
|
+
const expandedBasePath = expandTildePath(basePath);
|
|
124
|
+
const functionPath = join(expandedBasePath, functionName);
|
|
123
125
|
const currentFileUrl = import.meta.url;
|
|
124
126
|
const currentDir = dirname(fileURLToPath(currentFileUrl));
|
|
125
127
|
const templatesPath = join(currentDir, "templates", templateType);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expands tilde (~) in paths to the user's home directory
|
|
3
|
+
* @param pathStr - Path string that may contain ~
|
|
4
|
+
* @returns Expanded path with home directory
|
|
5
|
+
*/
|
|
6
|
+
export declare function expandTildePath(pathStr: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Normalizes function name to standard format (lowercase, dashes instead of spaces)
|
|
9
|
+
* @param name - Function name to normalize
|
|
10
|
+
* @returns Normalized function name
|
|
11
|
+
*/
|
|
12
|
+
export declare function normalizeFunctionName(name: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Validates that a directory exists and contains function markers
|
|
15
|
+
* @param dirPath - Directory path to validate
|
|
16
|
+
* @returns True if directory is a valid function directory
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateFunctionDirectory(dirPath: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Helper function to search for function in standard locations
|
|
21
|
+
* @param configDirPath - Directory where config file is located
|
|
22
|
+
* @param normalizedName - Normalized function name
|
|
23
|
+
* @returns First valid function directory path or undefined
|
|
24
|
+
*/
|
|
25
|
+
export declare function findFunctionInStandardLocations(configDirPath: string, normalizedName: string): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Resolves the absolute path to a function directory
|
|
28
|
+
* Handles multiple resolution strategies with proper priority
|
|
29
|
+
*
|
|
30
|
+
* @param functionName - Name of the function
|
|
31
|
+
* @param configDirPath - Directory where config file is located
|
|
32
|
+
* @param dirPath - Optional explicit dirPath from config
|
|
33
|
+
* @param explicitPath - Optional path passed as parameter (highest priority)
|
|
34
|
+
* @returns Absolute path to the function directory
|
|
35
|
+
* @throws Error if function directory cannot be found or is invalid
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveFunctionDirectory(functionName: string, configDirPath: string, dirPath?: string, explicitPath?: string): string;
|