appwrite-ctl 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +288 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +236 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/lib/appwrite.d.ts +21 -0
- package/dist/lib/appwrite.js +72 -0
- package/dist/lib/cli.d.ts +28 -0
- package/dist/lib/cli.js +136 -0
- package/dist/lib/config.d.ts +12 -0
- package/dist/lib/config.js +48 -0
- package/dist/lib/diagram.d.ts +4 -0
- package/dist/lib/diagram.js +222 -0
- package/dist/lib/runner.d.ts +4 -0
- package/dist/lib/runner.js +183 -0
- package/dist/lib/schema.d.ts +6 -0
- package/dist/lib/schema.js +57 -0
- package/dist/types/index.d.ts +26 -0
- package/dist/types/index.js +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Appwrite Ctl
|
|
2
|
+
|
|
3
|
+
A Node.js (ESM) package to manage Appwrite infrastructure via Version Snapshots. Uses the **Appwrite CLI** for schema pull/push operations and the **Appwrite SDK** for data migration scripts.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Version Control for Appwrite Schema**: Manage your `appwrite.config.json` snapshots alongside your code.
|
|
8
|
+
- **CLI-based Snapshots**: Uses `appwrite-cli` pull/push for reliable schema synchronization.
|
|
9
|
+
- **Data Migrations**: Execute TypeScript or JavaScript migration scripts (`up` and `down`) using the Node.js SDK.
|
|
10
|
+
- **State Management**: Tracks applied migrations in a dedicated Appwrite collection (`system.migrations`).
|
|
11
|
+
- **Backup Hooks**: Supports executing external backup commands before migration.
|
|
12
|
+
- **Attribute Polling**: Ensures schema attributes are `available` before running data scripts.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g appwrite-ctl
|
|
18
|
+
# or
|
|
19
|
+
npm install --save-dev appwrite-ctl
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### From Repository
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install github:bfbechlin/appwrite-ctl
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Prerequisites
|
|
29
|
+
|
|
30
|
+
- **Node.js**: v18 or higher.
|
|
31
|
+
- **Appwrite CLI**: Installed globally (`npm install -g appwrite-cli`). The tool configures the CLI automatically using API key — no interactive login required.
|
|
32
|
+
- **Environment Variables**:
|
|
33
|
+
|
|
34
|
+
```env
|
|
35
|
+
APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
|
|
36
|
+
APPWRITE_PROJECT_ID=your_project_id
|
|
37
|
+
APPWRITE_API_KEY=your_api_key
|
|
38
|
+
BACKUP_COMMAND="docker exec appwrite-mariadb mysqldump ..." # Optional
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Architecture
|
|
42
|
+
|
|
43
|
+
The tool uses a clear separation of concerns:
|
|
44
|
+
|
|
45
|
+
| Operation | Tool | Why |
|
|
46
|
+
| :--------------------------- | :--------------- | :--------------------------------------------------------------------------- |
|
|
47
|
+
| Schema snapshots (pull/push) | **Appwrite CLI** | Has full serialization/deserialization of schemas via `appwrite.config.json` |
|
|
48
|
+
| Data migrations (up/down) | **Appwrite SDK** | Provides programmatic access to databases, documents, etc. |
|
|
49
|
+
| Migration tracking | **Appwrite SDK** | Creates/reads documents in the `system.migrations` collection |
|
|
50
|
+
|
|
51
|
+
## CLI Usage
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Default (uses .env)
|
|
55
|
+
npx appwrite-ctl migrations run
|
|
56
|
+
|
|
57
|
+
# Custom environment file
|
|
58
|
+
npx appwrite-ctl migrations run --env .env.prod
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
### 1. Initialize the Project
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx appwrite-ctl init
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Creates:
|
|
70
|
+
|
|
71
|
+
- `appwrite/migration/` directory
|
|
72
|
+
- `appwrite/migration/config.json` configuration file
|
|
73
|
+
|
|
74
|
+
### 2. Setup System Collection
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx appwrite-ctl migrations setup
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. Create a Migration
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx appwrite-ctl migrations create
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This command:
|
|
87
|
+
|
|
88
|
+
1. Creates `appwrite/migration/vN/` (auto-increments version).
|
|
89
|
+
2. Generates an `index.ts` file with a boilerplate migration script.
|
|
90
|
+
3. Copies the current `appwrite.config.json` from the project root (or pulls from Appwrite via CLI if no local snapshot exists).
|
|
91
|
+
|
|
92
|
+
**Folder Structure:**
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
/appwrite
|
|
96
|
+
schema.md <-- Generated by `docs` command
|
|
97
|
+
/migration
|
|
98
|
+
config.json
|
|
99
|
+
/v1
|
|
100
|
+
index.ts <-- Migration logic (SDK)
|
|
101
|
+
appwrite.config.json <-- Schema snapshot (CLI format)
|
|
102
|
+
schema.md <-- Auto-generated on create/update
|
|
103
|
+
/v2
|
|
104
|
+
index.ts
|
|
105
|
+
appwrite.config.json
|
|
106
|
+
schema.md
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 4. Edit Migration Logic
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { Migration } from 'appwrite-ctl';
|
|
113
|
+
|
|
114
|
+
const migration: Migration = {
|
|
115
|
+
id: 'uuid-generated-id',
|
|
116
|
+
description: 'Update finance schema',
|
|
117
|
+
requiresBackup: true,
|
|
118
|
+
|
|
119
|
+
up: async ({ client, databases, log }) => {
|
|
120
|
+
log('Seeding initial data...');
|
|
121
|
+
await databases.createDocument('db', 'users', 'unique()', {
|
|
122
|
+
name: 'Admin',
|
|
123
|
+
role: 'admin',
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
down: async ({ client, databases, log }) => {
|
|
128
|
+
// Logic to revert changes
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export default migration;
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 5. Update a Snapshot
|
|
136
|
+
|
|
137
|
+
After making schema changes in the Appwrite console, update a migration version's snapshot:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npx appwrite-ctl migrations update v1
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
This pulls the current state from Appwrite via CLI and saves it as the version's `appwrite.config.json`.
|
|
144
|
+
|
|
145
|
+
### 6. Run Migrations
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npx appwrite-ctl migrations run
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The runner performs these steps for each pending version:
|
|
152
|
+
|
|
153
|
+
1. **Configure CLI**: Sets endpoint, project-id, and API key on appwrite-cli.
|
|
154
|
+
2. **Backup**: Runs `BACKUP_COMMAND` if `requiresBackup` is true.
|
|
155
|
+
3. **Schema Push**: Pushes the version's `appwrite.config.json` via CLI (settings, tables, buckets, teams, topics).
|
|
156
|
+
4. **Polling**: Waits for all schema attributes to become `available` (via SDK).
|
|
157
|
+
5. **Execution**: Runs the `up` function defined in `index.ts` (via SDK).
|
|
158
|
+
6. **Finalization**: Records the migration as applied.
|
|
159
|
+
|
|
160
|
+
### 7. Check Status
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npx appwrite-ctl migrations status
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 8. Generate Schema Docs
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Generate from latest version → appwrite/schema.md
|
|
170
|
+
npx appwrite-ctl migrations docs
|
|
171
|
+
|
|
172
|
+
# Generate from a specific version
|
|
173
|
+
npx appwrite-ctl migrations docs v1
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Generates a Markdown file with:
|
|
177
|
+
|
|
178
|
+
- **ER diagrams** (Mermaid) for each database (system database excluded)
|
|
179
|
+
- **Collection details**: columns, types, defaults, indexes, permissions, relationships
|
|
180
|
+
- **Buckets**: storage configuration summary
|
|
181
|
+
|
|
182
|
+
> **Note:** Schema docs are also auto-generated inside the version folder (`vN/schema.md`) when running `migrations create` or `migrations update`.
|
|
183
|
+
|
|
184
|
+
## Configuration (`appwrite/migration/config.json`)
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"collection": "migrations",
|
|
189
|
+
"database": "system"
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## CI/CD & Automated Deployment
|
|
194
|
+
|
|
195
|
+
1. Install Appwrite CLI: `npm install -g appwrite-cli`
|
|
196
|
+
2. Set environment variables: `APPWRITE_ENDPOINT`, `APPWRITE_PROJECT_ID`, `APPWRITE_API_KEY`
|
|
197
|
+
3. The tool automatically configures the CLI via `appwrite client --key` — no login required.
|
|
198
|
+
|
|
199
|
+
**Required API Key Scopes:**
|
|
200
|
+
|
|
201
|
+
- `collections.read`, `collections.write`
|
|
202
|
+
- `documents.read`, `documents.write`
|
|
203
|
+
- `attributes.read`, `attributes.write`
|
|
204
|
+
- `indexes.read`, `indexes.write`
|
|
205
|
+
|
|
206
|
+
## CLI Commands
|
|
207
|
+
|
|
208
|
+
| Command | Description |
|
|
209
|
+
| :---------------------------- | :----------------------------------------------------------------------------------- |
|
|
210
|
+
| `init` | Initialize the project folder structure and config. |
|
|
211
|
+
| `migrations setup` | Create the `system` database and `migrations` collection. |
|
|
212
|
+
| `migrations create` | Create a new migration version pulling the latest snapshot from Appwrite via CLI. |
|
|
213
|
+
| `migrations update <version>` | Update a version's snapshot by pulling from Appwrite via CLI. |
|
|
214
|
+
| `migrations run` | Execute all pending migrations in order. |
|
|
215
|
+
| `migrations status` | List applied and pending migrations. |
|
|
216
|
+
| `migrations docs` | Pull current schema state from Appwrite and generate documentation with ER diagrams. |
|
|
217
|
+
|
|
218
|
+
# AI Rules
|
|
219
|
+
|
|
220
|
+
## Understanding the Data Models Layer
|
|
221
|
+
|
|
222
|
+
📌 `schema.md` — The Source of Truth
|
|
223
|
+
|
|
224
|
+
The most important file for understanding the application's **data model** is:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
appwrite/schema.md
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
This is an **auto-generated** Markdown file that documents the **current state** of every database, collection, attribute, relationship, index, and storage bucket in the Appwrite project. It is generated from the latest `appwrite.config.json` snapshot via the `migrations docs` command.
|
|
231
|
+
|
|
232
|
+
**When you need to understand the data model — always read `appwrite/schema.md` first.**
|
|
233
|
+
|
|
234
|
+
It contains:
|
|
235
|
+
|
|
236
|
+
- **ER Diagrams** (Mermaid) — visual representation of collection relationships per database.
|
|
237
|
+
- **Collections** — complete list of every collection with:
|
|
238
|
+
- Column names, types, required flags, defaults, and constraints.
|
|
239
|
+
- Relationships: type (`oneToMany`, `manyToOne`, etc.), related collection, on-delete behavior, and two-way configuration.
|
|
240
|
+
- Indexes: type (unique, key, fulltext), columns, and sort orders.
|
|
241
|
+
- Permissions: read/write/create/delete access rules.
|
|
242
|
+
- **Buckets** — storage buckets with max file size, extensions, compression, encryption, and antivirus settings.
|
|
243
|
+
|
|
244
|
+
## Migration Commands
|
|
245
|
+
|
|
246
|
+
This project uses `appwrite-ctl` to manage schema migrations. The available commands are:
|
|
247
|
+
|
|
248
|
+
| Command | Description |
|
|
249
|
+
| :----------------------------------------- | :------------------------------------------------------------------------------------ |
|
|
250
|
+
| `appwrite-ctl migrations create` | Create a new migration version pulling the latest snapshot from Appwrite via CLI. |
|
|
251
|
+
| `appwrite-ctl migrations update <version>` | Pull the current Appwrite state and update a version's snapshot. |
|
|
252
|
+
| `appwrite-ctl migrations run` | Execute all pending migrations in order (push schema → poll attributes → run script). |
|
|
253
|
+
| `appwrite-ctl migrations status` | List applied and pending migrations. |
|
|
254
|
+
| `appwrite-ctl migrations docs` | Pull current schema state from Appwrite and generate/regenerate `schema.md`. |
|
|
255
|
+
|
|
256
|
+
Each migration version lives in `appwrite/migration/vN/` and contains:
|
|
257
|
+
|
|
258
|
+
- **`appwrite.config.json`** — the schema snapshot (Appwrite CLI format).
|
|
259
|
+
- **`index.ts`** — the migration script with `up` (and optional `down`) functions.
|
|
260
|
+
- **`schema.md`** — auto-generated docs for that version's snapshot.
|
|
261
|
+
|
|
262
|
+
## How to Handle Data Model Changes
|
|
263
|
+
|
|
264
|
+
When a change to the data model is needed (e.g. adding a collection, modifying attributes, creating indexes), follow these steps:
|
|
265
|
+
|
|
266
|
+
1. **Create a new migration version:**
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
npx appwrite-ctl migrations create
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
This creates `appwrite/migration/vN/` and automatically pulls the current schema snapshot from Appwrite into `appwrite.config.json` via the CLI.
|
|
273
|
+
|
|
274
|
+
2. **Edit the snapshot (`appwrite.config.json`)** inside the new version folder. Apply the desired schema changes directly to this JSON file — add/remove/modify collections, attributes, indexes, relationships, or buckets.
|
|
275
|
+
|
|
276
|
+
3. **Write the migration script** in `appwrite/migration/vN/index.ts` if data manipulation is needed (e.g. seeding data, transforming existing documents). If the change is schema-only, the default empty `up` function is sufficient.
|
|
277
|
+
|
|
278
|
+
4. **Regenerate the schema docs:**
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
npx appwrite-ctl migrations docs
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
This updates both `appwrite/migration/vN/schema.md` and the root `appwrite/schema.md`.
|
|
285
|
+
|
|
286
|
+
5. **Verify** the updated `appwrite/schema.md` to confirm the changes are correct.
|
|
287
|
+
|
|
288
|
+
> ⚠️ **Never edit `schema.md` files manually** — they are auto-generated. Always modify the `appwrite.config.json` snapshot and run `migrations docs` to regenerate.
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { runMigrations } from '../lib/runner.js';
|
|
8
|
+
import { loadConfig } from '../lib/config.js';
|
|
9
|
+
import { createAppwriteClient, ensureMigrationCollection, getAppliedMigrations, } from '../lib/appwrite.js';
|
|
10
|
+
import { configureClient, pullSnapshot, getSnapshotFilename } from '../lib/cli.js';
|
|
11
|
+
import { generateSchemaDoc } from '../lib/diagram.js';
|
|
12
|
+
const program = new Command();
|
|
13
|
+
const generateDocs = (snapshotPath, version, outputDir) => {
|
|
14
|
+
if (!fs.existsSync(snapshotPath))
|
|
15
|
+
return;
|
|
16
|
+
const markdown = generateSchemaDoc(snapshotPath, version);
|
|
17
|
+
if (!fs.existsSync(outputDir)) {
|
|
18
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
const outputPath = path.join(outputDir, 'schema.md');
|
|
21
|
+
fs.writeFileSync(outputPath, markdown);
|
|
22
|
+
console.log(chalk.green(`Schema docs updated at ${outputPath}`));
|
|
23
|
+
};
|
|
24
|
+
program
|
|
25
|
+
.name('appwrite-ctl')
|
|
26
|
+
.description('Appwrite CLI for managing migrations and other operations');
|
|
27
|
+
program.option('-e, --env <path>', 'Path to environment file', '.env');
|
|
28
|
+
program
|
|
29
|
+
.command('init')
|
|
30
|
+
.description('Initialize the project structure')
|
|
31
|
+
.action(async () => {
|
|
32
|
+
const rootDir = process.cwd();
|
|
33
|
+
const appwriteDir = path.join(rootDir, 'appwrite');
|
|
34
|
+
const migrationDir = path.join(appwriteDir, 'migration');
|
|
35
|
+
const configPath = path.join(migrationDir, 'config.json');
|
|
36
|
+
if (!fs.existsSync(appwriteDir))
|
|
37
|
+
fs.mkdirSync(appwriteDir);
|
|
38
|
+
if (!fs.existsSync(migrationDir))
|
|
39
|
+
fs.mkdirSync(migrationDir);
|
|
40
|
+
if (!fs.existsSync(configPath)) {
|
|
41
|
+
const config = {
|
|
42
|
+
collection: 'migrations',
|
|
43
|
+
database: 'system',
|
|
44
|
+
};
|
|
45
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
46
|
+
console.log(chalk.green('Created appwrite/migration/config.json'));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.log(chalk.yellow('Config file already exists.'));
|
|
50
|
+
}
|
|
51
|
+
console.log(chalk.green('Initialization complete.'));
|
|
52
|
+
});
|
|
53
|
+
const migrations = program.command('migrations').description('Manage Appwrite migrations');
|
|
54
|
+
migrations
|
|
55
|
+
.command('setup')
|
|
56
|
+
.description('Create the system database and migrations collection in Appwrite')
|
|
57
|
+
.action(async () => {
|
|
58
|
+
try {
|
|
59
|
+
const options = program.opts();
|
|
60
|
+
const config = loadConfig(options.env);
|
|
61
|
+
const { databases } = createAppwriteClient(config);
|
|
62
|
+
await ensureMigrationCollection(databases, config);
|
|
63
|
+
console.log(chalk.green(`System database '${config.database}' and collection '${config.migrationCollectionId}' ensured.`));
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error(chalk.red('Setup failed:'), error.message);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
migrations
|
|
71
|
+
.command('create')
|
|
72
|
+
.description('Create a new migration version')
|
|
73
|
+
.action(async () => {
|
|
74
|
+
const migrationsDir = path.join(process.cwd(), 'appwrite', 'migration');
|
|
75
|
+
if (!fs.existsSync(migrationsDir)) {
|
|
76
|
+
fs.mkdirSync(migrationsDir, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
const snapshotFilename = getSnapshotFilename();
|
|
79
|
+
// Find next version number
|
|
80
|
+
const versionDirs = fs
|
|
81
|
+
.readdirSync(migrationsDir)
|
|
82
|
+
.filter((dir) => dir.startsWith('v') && fs.statSync(path.join(migrationsDir, dir)).isDirectory())
|
|
83
|
+
.map((d) => parseInt(d.substring(1)))
|
|
84
|
+
.sort((a, b) => a - b);
|
|
85
|
+
const nextVersion = (versionDirs.length > 0 ? versionDirs[versionDirs.length - 1] : 0) + 1;
|
|
86
|
+
const versionPath = path.join(migrationsDir, `v${nextVersion}`);
|
|
87
|
+
const name = `migration_v${nextVersion}`;
|
|
88
|
+
fs.mkdirSync(versionPath);
|
|
89
|
+
const indexContent = `import { Migration } from "appwrite-ctl";
|
|
90
|
+
|
|
91
|
+
const migration: Migration = {
|
|
92
|
+
id: "${uuidv4()}",
|
|
93
|
+
description: "${name}",
|
|
94
|
+
requiresBackup: false,
|
|
95
|
+
up: async ({ client, databases, log, error }) => {
|
|
96
|
+
log("Executing up migration for ${name}");
|
|
97
|
+
// Write your migration logic here
|
|
98
|
+
},
|
|
99
|
+
down: async ({ client, databases, log, error }) => {
|
|
100
|
+
log("Executing down migration for ${name}");
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export default migration;
|
|
105
|
+
`;
|
|
106
|
+
fs.writeFileSync(path.join(versionPath, 'index.ts'), indexContent);
|
|
107
|
+
// Snapshot logic: always pull from Appwrite via CLI for a new migration.
|
|
108
|
+
console.log(chalk.blue('Pulling latest schema from Appwrite via CLI...'));
|
|
109
|
+
try {
|
|
110
|
+
const options = program.opts();
|
|
111
|
+
const config = loadConfig(options.env);
|
|
112
|
+
await configureClient(config);
|
|
113
|
+
await pullSnapshot(versionPath);
|
|
114
|
+
console.log(chalk.green('Successfully pulled snapshot from Appwrite.'));
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error(chalk.red(`Failed to pull snapshot: ${error.message}`));
|
|
118
|
+
console.warn(chalk.yellow('Creating empty snapshot placeholder.'));
|
|
119
|
+
const emptySnapshot = {
|
|
120
|
+
projectId: '',
|
|
121
|
+
projectName: '',
|
|
122
|
+
settings: {},
|
|
123
|
+
tablesDB: [],
|
|
124
|
+
tables: [],
|
|
125
|
+
buckets: [],
|
|
126
|
+
teams: [],
|
|
127
|
+
topics: [],
|
|
128
|
+
};
|
|
129
|
+
fs.writeFileSync(path.join(versionPath, snapshotFilename), JSON.stringify(emptySnapshot, null, 2));
|
|
130
|
+
}
|
|
131
|
+
console.log(chalk.green(`Created migration v${nextVersion} at ${versionPath}`));
|
|
132
|
+
generateDocs(path.join(versionPath, snapshotFilename), `v${nextVersion}`, versionPath);
|
|
133
|
+
generateDocs(path.join(versionPath, snapshotFilename), `v${nextVersion}`, path.join(process.cwd(), 'appwrite'));
|
|
134
|
+
});
|
|
135
|
+
migrations
|
|
136
|
+
.command('update <version>')
|
|
137
|
+
.description('Update snapshot for a version by pulling current state from Appwrite via CLI')
|
|
138
|
+
.action(async (version) => {
|
|
139
|
+
const migrationsDir = path.join(process.cwd(), 'appwrite', 'migration');
|
|
140
|
+
const versionPath = path.join(migrationsDir, version);
|
|
141
|
+
if (!fs.existsSync(versionPath)) {
|
|
142
|
+
console.error(chalk.red(`Version directory ${version} not found.`));
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
console.log(chalk.blue(`Updating snapshot for ${version} via CLI pull...`));
|
|
146
|
+
try {
|
|
147
|
+
const options = program.opts();
|
|
148
|
+
const config = loadConfig(options.env);
|
|
149
|
+
await configureClient(config);
|
|
150
|
+
await pullSnapshot(versionPath);
|
|
151
|
+
console.log(chalk.green(`Successfully updated snapshot for ${version}`));
|
|
152
|
+
const snapshotFilename = getSnapshotFilename();
|
|
153
|
+
generateDocs(path.join(versionPath, snapshotFilename), version, versionPath);
|
|
154
|
+
console.log(chalk.green(`Successfully updated schema.md for ${version}`));
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.error(chalk.red(`Failed to update snapshot: ${error.message}`));
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
migrations
|
|
162
|
+
.command('run')
|
|
163
|
+
.description('Execute pending migrations')
|
|
164
|
+
.action(async () => {
|
|
165
|
+
try {
|
|
166
|
+
const options = program.opts();
|
|
167
|
+
await runMigrations(options.env);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error(chalk.red('Migration run failed:'), error.message);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
migrations
|
|
175
|
+
.command('status')
|
|
176
|
+
.description('List migration status')
|
|
177
|
+
.action(async () => {
|
|
178
|
+
try {
|
|
179
|
+
const options = program.opts();
|
|
180
|
+
const config = loadConfig(options.env);
|
|
181
|
+
const { databases } = createAppwriteClient(config);
|
|
182
|
+
const appliedIds = await getAppliedMigrations(databases, config);
|
|
183
|
+
const appliedSet = new Set(appliedIds);
|
|
184
|
+
const migrationsDir = path.join(process.cwd(), 'appwrite', 'migration');
|
|
185
|
+
if (!fs.existsSync(migrationsDir)) {
|
|
186
|
+
console.log('No migrations directory found.');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const versionDirs = fs
|
|
190
|
+
.readdirSync(migrationsDir)
|
|
191
|
+
.filter((dir) => dir.startsWith('v') && fs.statSync(path.join(migrationsDir, dir)).isDirectory())
|
|
192
|
+
.sort((a, b) => parseInt(a.substring(1)) - parseInt(b.substring(1)));
|
|
193
|
+
console.log(chalk.bold.underline('\nMigration Status:\n'));
|
|
194
|
+
for (const version of versionDirs) {
|
|
195
|
+
const indexPath = path.join(migrationsDir, version, 'index.ts');
|
|
196
|
+
let id = 'unknown';
|
|
197
|
+
if (fs.existsSync(indexPath)) {
|
|
198
|
+
const content = fs.readFileSync(indexPath, 'utf8');
|
|
199
|
+
const match = content.match(/id:\s*["']([^"']+)["']/);
|
|
200
|
+
if (match)
|
|
201
|
+
id = match[1];
|
|
202
|
+
}
|
|
203
|
+
const status = appliedSet.has(id) ? chalk.green('APPLIED') : chalk.yellow('PENDING');
|
|
204
|
+
console.log(`${version.padEnd(10)} [${id}] ${status}`);
|
|
205
|
+
}
|
|
206
|
+
console.log('');
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
console.error(chalk.red('Status check failed:'), error.message);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
migrations
|
|
214
|
+
.command('docs')
|
|
215
|
+
.description('Pull current state from Appwrite and generate schema documentation with ER diagrams')
|
|
216
|
+
.action(async () => {
|
|
217
|
+
try {
|
|
218
|
+
const options = program.opts();
|
|
219
|
+
const config = loadConfig(options.env);
|
|
220
|
+
console.log(chalk.blue(`Pulling latest schema from Appwrite to project root...`));
|
|
221
|
+
await configureClient(config);
|
|
222
|
+
const snapshotPath = await pullSnapshot();
|
|
223
|
+
console.log(chalk.blue('Generating documentation...'));
|
|
224
|
+
const appwriteDir = path.join(process.cwd(), 'appwrite');
|
|
225
|
+
generateDocs(snapshotPath, 'latest', appwriteDir);
|
|
226
|
+
// Cleanup the temporary snapshot pulled to root
|
|
227
|
+
if (fs.existsSync(snapshotPath)) {
|
|
228
|
+
fs.unlinkSync(snapshotPath);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
console.error(chalk.red('Docs generation failed:'), error.message);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
program.parse();
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Client, Databases } from 'node-appwrite';
|
|
2
|
+
import { AppConfig } from './config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create Appwrite Client and Databases instance.
|
|
5
|
+
*/
|
|
6
|
+
export declare const createAppwriteClient: (config: AppConfig) => {
|
|
7
|
+
client: Client;
|
|
8
|
+
databases: Databases;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Ensure the system database and migrations collection exist.
|
|
12
|
+
*/
|
|
13
|
+
export declare const ensureMigrationCollection: (databases: Databases, config: AppConfig) => Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Get list of applied migration IDs.
|
|
16
|
+
*/
|
|
17
|
+
export declare const getAppliedMigrations: (databases: Databases, config: AppConfig) => Promise<string[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Record a successfully applied migration.
|
|
20
|
+
*/
|
|
21
|
+
export declare const recordMigration: (databases: Databases, config: AppConfig, migrationId: string, name: string) => Promise<void>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Client, Databases, Query } from 'node-appwrite';
|
|
2
|
+
/**
|
|
3
|
+
* Create Appwrite Client and Databases instance.
|
|
4
|
+
*/
|
|
5
|
+
export const createAppwriteClient = (config) => {
|
|
6
|
+
const client = new Client()
|
|
7
|
+
.setEndpoint(config.endpoint)
|
|
8
|
+
.setProject(config.projectId)
|
|
9
|
+
.setKey(config.apiKey);
|
|
10
|
+
const databases = new Databases(client);
|
|
11
|
+
return { client, databases };
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Ensure the system database and migrations collection exist.
|
|
15
|
+
*/
|
|
16
|
+
export const ensureMigrationCollection = async (databases, config) => {
|
|
17
|
+
// Ensure the system database exists.
|
|
18
|
+
try {
|
|
19
|
+
await databases.get(config.database);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error.code === 404) {
|
|
23
|
+
console.log(`Creating system database '${config.database}'...`);
|
|
24
|
+
await databases.create(config.database, config.database);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Ensure the migration collection exists within the system database.
|
|
31
|
+
try {
|
|
32
|
+
await databases.getCollection(config.database, config.migrationCollectionId);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.code === 404) {
|
|
36
|
+
console.log(`Creating migration collection '${config.migrationCollectionId}'...`);
|
|
37
|
+
await databases.createCollection(config.database, config.migrationCollectionId, config.migrationCollectionId);
|
|
38
|
+
await databases.createStringAttribute(config.database, config.migrationCollectionId, 'name', 255, true);
|
|
39
|
+
await databases.createDatetimeAttribute(config.database, config.migrationCollectionId, 'appliedAt', true);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Get list of applied migration IDs.
|
|
48
|
+
*/
|
|
49
|
+
export const getAppliedMigrations = async (databases, config) => {
|
|
50
|
+
try {
|
|
51
|
+
const response = await databases.listDocuments(config.database, config.migrationCollectionId, [
|
|
52
|
+
Query.limit(5000),
|
|
53
|
+
]);
|
|
54
|
+
return response.documents.map((doc) => doc.$id);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error.code === 404) {
|
|
58
|
+
// If DB or Collection unavailable, no migrations applied.
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Record a successfully applied migration.
|
|
66
|
+
*/
|
|
67
|
+
export const recordMigration = async (databases, config, migrationId, name) => {
|
|
68
|
+
await databases.createDocument(config.database, config.migrationCollectionId, migrationId, {
|
|
69
|
+
name,
|
|
70
|
+
appliedAt: new Date().toISOString(),
|
|
71
|
+
});
|
|
72
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AppConfig } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Configure the Appwrite CLI client for non-interactive use via API key.
|
|
4
|
+
*/
|
|
5
|
+
export declare const configureClient: (config: AppConfig) => Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Pull a full snapshot from Appwrite into a target directory.
|
|
8
|
+
* Uses individual `appwrite pull <resource>` commands for non-interactive operation.
|
|
9
|
+
*
|
|
10
|
+
* The operation works in the project root (where appwrite.config.json lives).
|
|
11
|
+
* If a targetDir is provided, it copies the resulting file to the target directory and cleans up the root.
|
|
12
|
+
*/
|
|
13
|
+
export declare const pullSnapshot: (targetDir?: string) => Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Push a snapshot from a version directory to the Appwrite project.
|
|
16
|
+
* Copies the version's appwrite.config.json to the project root,
|
|
17
|
+
* then runs individual `appwrite push <resource> --all --force` commands.
|
|
18
|
+
*
|
|
19
|
+
* The `projectId` in the snapshot is rewritten to match the current config,
|
|
20
|
+
* allowing the same snapshot to be pushed to any environment.
|
|
21
|
+
*
|
|
22
|
+
* `--all` auto-selects all resources, `--force` auto-confirms changes.
|
|
23
|
+
*/
|
|
24
|
+
export declare const pushSnapshot: (snapshotPath: string, config: AppConfig) => Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Get the snapshot filename used for versioned snapshots.
|
|
27
|
+
*/
|
|
28
|
+
export declare const getSnapshotFilename: () => string;
|