shsu 0.0.5 → 0.0.6
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 +31 -4
- package/bin/shsu.mjs +123 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**S**elf-**H**osted **S**upabase **U**tilities
|
|
4
4
|
|
|
5
|
-
Deploy and manage Supabase Edge Functions on Coolify-hosted Supabase.
|
|
5
|
+
Deploy and manage Supabase Edge Functions and migrations on Coolify-hosted Supabase.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -27,17 +27,22 @@ This adds config to your `package.json`:
|
|
|
27
27
|
"shsu": {
|
|
28
28
|
"server": "root@your-coolify-server",
|
|
29
29
|
"remotePath": "/data/coolify/services/YOUR_SERVICE_ID/volumes/functions",
|
|
30
|
-
"url": "https://your-supabase.example.com"
|
|
30
|
+
"url": "https://your-supabase.example.com",
|
|
31
|
+
"edgeContainer": "edge",
|
|
32
|
+
"dbContainer": "postgres"
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
```
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
### Finding Configuration Values
|
|
36
38
|
|
|
39
|
+
**remotePath** - SSH to your server and run:
|
|
37
40
|
```bash
|
|
38
41
|
docker inspect $(docker ps -q --filter 'name=edge') | grep -A 5 "Mounts"
|
|
39
42
|
```
|
|
40
43
|
|
|
44
|
+
**Container names** - SSH to your server and run `docker ps` to list containers. Coolify names containers using the pattern `<service>-<uuid>` (e.g., `abc123-supabase-edge-functions-1`). The filter does substring matching, so `edge` matches any container with "edge" in its name.
|
|
45
|
+
|
|
41
46
|
## Usage
|
|
42
47
|
|
|
43
48
|
```bash
|
|
@@ -56,6 +61,9 @@ shsu deploy hello-world
|
|
|
56
61
|
# Deploy without restarting edge-runtime
|
|
57
62
|
shsu deploy hello-world --no-restart
|
|
58
63
|
|
|
64
|
+
# Run database migrations
|
|
65
|
+
shsu migrate
|
|
66
|
+
|
|
59
67
|
# Stream logs
|
|
60
68
|
shsu logs
|
|
61
69
|
|
|
@@ -75,6 +83,21 @@ shsu new my-function
|
|
|
75
83
|
shsu restart
|
|
76
84
|
```
|
|
77
85
|
|
|
86
|
+
## Migrations
|
|
87
|
+
|
|
88
|
+
Place SQL files in `./supabase/migrations/` (or your configured `migrationsPath`). Files are sorted alphabetically and executed in order.
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
supabase/migrations/
|
|
92
|
+
001_create_users.sql
|
|
93
|
+
002_add_email_index.sql
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Run with:
|
|
97
|
+
```bash
|
|
98
|
+
shsu migrate
|
|
99
|
+
```
|
|
100
|
+
|
|
78
101
|
## Configuration
|
|
79
102
|
|
|
80
103
|
Config is read from `package.json` "shsu" key. Environment variables override package.json values.
|
|
@@ -85,6 +108,9 @@ Config is read from `package.json` "shsu" key. Environment variables override pa
|
|
|
85
108
|
| `remotePath` / `SHSU_REMOTE_PATH` | Yes | Remote path to functions directory |
|
|
86
109
|
| `url` / `SHSU_URL` | For `invoke` | Supabase URL |
|
|
87
110
|
| `localPath` / `SHSU_LOCAL_PATH` | No | Local functions path (default: `./supabase/functions`) |
|
|
111
|
+
| `migrationsPath` / `SHSU_MIGRATIONS_PATH` | No | Local migrations path (default: `./supabase/migrations`) |
|
|
112
|
+
| `edgeContainer` / `SHSU_EDGE_CONTAINER` | No | Edge runtime container filter (default: `edge`) |
|
|
113
|
+
| `dbContainer` / `SHSU_DB_CONTAINER` | No | Database container filter (default: `postgres`) |
|
|
88
114
|
|
|
89
115
|
## MCP Server
|
|
90
116
|
|
|
@@ -136,9 +162,10 @@ Add to `.cursor/mcp.json` in your project:
|
|
|
136
162
|
}
|
|
137
163
|
```
|
|
138
164
|
|
|
139
|
-
### Available Tools
|
|
165
|
+
### Available MCP Tools
|
|
140
166
|
|
|
141
167
|
- `deploy` - Deploy edge functions
|
|
168
|
+
- `migrate` - Run database migrations
|
|
142
169
|
- `list` - List local and remote functions
|
|
143
170
|
- `invoke` - Invoke a function
|
|
144
171
|
- `restart` - Restart edge-runtime
|
package/bin/shsu.mjs
CHANGED
|
@@ -27,7 +27,10 @@ function loadConfig() {
|
|
|
27
27
|
server: process.env.SHSU_SERVER || pkgConfig.server,
|
|
28
28
|
remotePath: process.env.SHSU_REMOTE_PATH || pkgConfig.remotePath,
|
|
29
29
|
localPath: process.env.SHSU_LOCAL_PATH || pkgConfig.localPath || './supabase/functions',
|
|
30
|
+
migrationsPath: process.env.SHSU_MIGRATIONS_PATH || pkgConfig.migrationsPath || './supabase/migrations',
|
|
30
31
|
url: process.env.SHSU_URL || pkgConfig.url,
|
|
32
|
+
edgeContainer: process.env.SHSU_EDGE_CONTAINER || pkgConfig.edgeContainer || 'edge',
|
|
33
|
+
dbContainer: process.env.SHSU_DB_CONTAINER || pkgConfig.dbContainer || 'postgres',
|
|
31
34
|
};
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -85,7 +88,7 @@ function runSync(cmd) {
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
function getEdgeContainer() {
|
|
88
|
-
return runSync(`ssh ${config.server} "docker ps -q --filter 'name
|
|
91
|
+
return runSync(`ssh ${config.server} "docker ps -q --filter 'name=${config.edgeContainer}'"`);
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -121,7 +124,7 @@ async function cmdDeploy(funcName, noRestart = false) {
|
|
|
121
124
|
info('Restarting edge-runtime...');
|
|
122
125
|
await run('ssh', [
|
|
123
126
|
config.server,
|
|
124
|
-
`docker restart $(docker ps -q --filter 'name
|
|
127
|
+
`docker restart $(docker ps -q --filter 'name=${config.edgeContainer}')`,
|
|
125
128
|
], { stdio: ['inherit', 'pipe', 'inherit'] });
|
|
126
129
|
success(`Deployed${funcName ? ` ${funcName}` : ''}`);
|
|
127
130
|
} else {
|
|
@@ -136,7 +139,7 @@ async function cmdLogs(filter, lines = 100) {
|
|
|
136
139
|
|
|
137
140
|
const sshArgs = [
|
|
138
141
|
config.server,
|
|
139
|
-
`docker logs -f $(docker ps -q --filter 'name
|
|
142
|
+
`docker logs -f $(docker ps -q --filter 'name=${config.edgeContainer}') --tail ${lines} 2>&1`,
|
|
140
143
|
];
|
|
141
144
|
|
|
142
145
|
if (filter) {
|
|
@@ -196,11 +199,62 @@ async function cmdRestart() {
|
|
|
196
199
|
info('Restarting edge-runtime...');
|
|
197
200
|
await run('ssh', [
|
|
198
201
|
config.server,
|
|
199
|
-
`docker restart $(docker ps -q --filter 'name
|
|
202
|
+
`docker restart $(docker ps -q --filter 'name=${config.edgeContainer}')`,
|
|
200
203
|
], { stdio: ['inherit', 'pipe', 'inherit'] });
|
|
201
204
|
success('Restarted');
|
|
202
205
|
}
|
|
203
206
|
|
|
207
|
+
async function cmdMigrate() {
|
|
208
|
+
requireServer();
|
|
209
|
+
|
|
210
|
+
if (!existsSync(config.migrationsPath)) {
|
|
211
|
+
error(`Migrations folder not found: ${config.migrationsPath}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Get list of migration files
|
|
215
|
+
const migrations = readdirSync(config.migrationsPath)
|
|
216
|
+
.filter((f) => f.endsWith('.sql'))
|
|
217
|
+
.sort();
|
|
218
|
+
|
|
219
|
+
if (migrations.length === 0) {
|
|
220
|
+
info('No migration files found.');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
info(`Found ${migrations.length} migration(s): ${migrations.join(', ')}`);
|
|
225
|
+
|
|
226
|
+
// Copy migrations to server
|
|
227
|
+
const remoteMigrationsPath = '/tmp/shsu-migrations';
|
|
228
|
+
info('Syncing migrations to server...');
|
|
229
|
+
await run('rsync', [
|
|
230
|
+
'-avz', '--delete',
|
|
231
|
+
`${config.migrationsPath}/`,
|
|
232
|
+
`${config.server}:${remoteMigrationsPath}/`,
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
// Find the database container
|
|
236
|
+
const dbContainer = runSync(`ssh ${config.server} "docker ps -q --filter 'name=${config.dbContainer}'"`);
|
|
237
|
+
if (!dbContainer) {
|
|
238
|
+
error(`Database container not found (filter: ${config.dbContainer})`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Run each migration
|
|
242
|
+
for (const migration of migrations) {
|
|
243
|
+
info(`Running ${migration}...`);
|
|
244
|
+
try {
|
|
245
|
+
await run('ssh', [
|
|
246
|
+
config.server,
|
|
247
|
+
`docker exec -i ${dbContainer} psql -U postgres -d postgres -f /tmp/shsu-migrations/${migration}`,
|
|
248
|
+
]);
|
|
249
|
+
success(`Applied ${migration}`);
|
|
250
|
+
} catch (e) {
|
|
251
|
+
error(`Failed to apply ${migration}: ${e.message}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
success('All migrations applied');
|
|
256
|
+
}
|
|
257
|
+
|
|
204
258
|
async function cmdNew(funcName) {
|
|
205
259
|
if (!funcName) {
|
|
206
260
|
error('Usage: shsu new <function-name>');
|
|
@@ -239,23 +293,26 @@ function cmdEnv() {
|
|
|
239
293
|
console.log(`
|
|
240
294
|
${c.yellow('Configuration (package.json "shsu" key or environment variables):')}
|
|
241
295
|
|
|
242
|
-
server
|
|
243
|
-
remotePath
|
|
244
|
-
url
|
|
245
|
-
localPath
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
SHSU_SERVER, SHSU_REMOTE_PATH, SHSU_URL, SHSU_LOCAL_PATH
|
|
296
|
+
server SSH host for your Coolify server
|
|
297
|
+
remotePath Remote path to functions directory
|
|
298
|
+
url Supabase URL (for invoke command)
|
|
299
|
+
localPath Local functions path (default: ./supabase/functions)
|
|
300
|
+
migrationsPath Local migrations path (default: ./supabase/migrations)
|
|
301
|
+
edgeContainer Edge runtime container filter (default: edge)
|
|
302
|
+
dbContainer Database container filter (default: postgres)
|
|
250
303
|
|
|
251
304
|
${c.yellow('Current values:')}
|
|
252
305
|
|
|
253
|
-
server
|
|
254
|
-
remotePath
|
|
255
|
-
url
|
|
256
|
-
localPath
|
|
306
|
+
server = ${config.server || c.dim('(not set)')}
|
|
307
|
+
remotePath = ${config.remotePath || c.dim('(not set)')}
|
|
308
|
+
url = ${config.url || c.dim('(not set)')}
|
|
309
|
+
localPath = ${config.localPath}
|
|
310
|
+
migrationsPath = ${config.migrationsPath}
|
|
311
|
+
edgeContainer = ${config.edgeContainer}
|
|
312
|
+
dbContainer = ${config.dbContainer}
|
|
257
313
|
|
|
258
314
|
${c.dim('Run "shsu init" to configure via prompts.')}
|
|
315
|
+
${c.dim('Find container names in Coolify: Services → Your Service → look for container name prefix')}
|
|
259
316
|
`);
|
|
260
317
|
}
|
|
261
318
|
|
|
@@ -285,6 +342,8 @@ async function cmdInit() {
|
|
|
285
342
|
const remotePath = await ask('Remote path to functions', config.remotePath);
|
|
286
343
|
const url = await ask('Supabase URL', config.url);
|
|
287
344
|
const localPath = await ask('Local functions path', config.localPath || './supabase/functions');
|
|
345
|
+
const edgeContainer = await ask('Edge container name filter', config.edgeContainer || 'edge');
|
|
346
|
+
const dbContainer = await ask('Database container name filter', config.dbContainer || 'postgres');
|
|
288
347
|
|
|
289
348
|
rl.close();
|
|
290
349
|
|
|
@@ -295,6 +354,8 @@ async function cmdInit() {
|
|
|
295
354
|
remotePath: remotePath || undefined,
|
|
296
355
|
url: url || undefined,
|
|
297
356
|
localPath: localPath !== './supabase/functions' ? localPath : undefined,
|
|
357
|
+
edgeContainer: edgeContainer !== 'edge' ? edgeContainer : undefined,
|
|
358
|
+
dbContainer: dbContainer !== 'postgres' ? dbContainer : undefined,
|
|
298
359
|
};
|
|
299
360
|
|
|
300
361
|
// Remove undefined values
|
|
@@ -362,6 +423,11 @@ async function cmdMcp() {
|
|
|
362
423
|
description: 'Get current shsu configuration.',
|
|
363
424
|
inputSchema: { type: 'object', properties: {} },
|
|
364
425
|
},
|
|
426
|
+
{
|
|
427
|
+
name: 'migrate',
|
|
428
|
+
description: 'Run SQL migrations on the database. Syncs migration files via rsync and executes them via psql.',
|
|
429
|
+
inputSchema: { type: 'object', properties: {} },
|
|
430
|
+
},
|
|
365
431
|
];
|
|
366
432
|
|
|
367
433
|
const serverInfo = {
|
|
@@ -411,7 +477,7 @@ async function cmdMcp() {
|
|
|
411
477
|
}
|
|
412
478
|
|
|
413
479
|
if (!noRestart) {
|
|
414
|
-
output += '\n' + captureExec(`ssh ${config.server} "docker restart \\$(docker ps -q --filter 'name
|
|
480
|
+
output += '\n' + captureExec(`ssh ${config.server} "docker restart \\$(docker ps -q --filter 'name=${config.edgeContainer}')"`);
|
|
415
481
|
}
|
|
416
482
|
|
|
417
483
|
return { content: [{ type: 'text', text: `Deployed${funcName ? ` ${funcName}` : ' all functions'}${noRestart ? ' (no restart)' : ''}\n\n${output}` }] };
|
|
@@ -448,7 +514,7 @@ async function cmdMcp() {
|
|
|
448
514
|
if (!config.server) {
|
|
449
515
|
return { content: [{ type: 'text', text: 'Error: server must be configured.' }] };
|
|
450
516
|
}
|
|
451
|
-
const output = captureExec(`ssh ${config.server} "docker restart \\$(docker ps -q --filter 'name
|
|
517
|
+
const output = captureExec(`ssh ${config.server} "docker restart \\$(docker ps -q --filter 'name=${config.edgeContainer}')"`);
|
|
452
518
|
return { content: [{ type: 'text', text: `Restarted edge-runtime\n\n${output}` }] };
|
|
453
519
|
}
|
|
454
520
|
|
|
@@ -487,11 +553,41 @@ async function cmdMcp() {
|
|
|
487
553
|
return {
|
|
488
554
|
content: [{
|
|
489
555
|
type: 'text',
|
|
490
|
-
text: `Current configuration:\n server: ${config.server || '(not set)'}\n remotePath: ${config.remotePath || '(not set)'}\n url: ${config.url || '(not set)'}\n localPath: ${config.localPath}`,
|
|
556
|
+
text: `Current configuration:\n server: ${config.server || '(not set)'}\n remotePath: ${config.remotePath || '(not set)'}\n url: ${config.url || '(not set)'}\n localPath: ${config.localPath}\n migrationsPath: ${config.migrationsPath}\n edgeContainer: ${config.edgeContainer}\n dbContainer: ${config.dbContainer}`,
|
|
491
557
|
}],
|
|
492
558
|
};
|
|
493
559
|
}
|
|
494
560
|
|
|
561
|
+
case 'migrate': {
|
|
562
|
+
if (!config.server) {
|
|
563
|
+
return { content: [{ type: 'text', text: 'Error: server must be configured.' }] };
|
|
564
|
+
}
|
|
565
|
+
if (!existsSync(config.migrationsPath)) {
|
|
566
|
+
return { content: [{ type: 'text', text: `Error: Migrations folder not found: ${config.migrationsPath}` }] };
|
|
567
|
+
}
|
|
568
|
+
const migrations = readdirSync(config.migrationsPath)
|
|
569
|
+
.filter((f) => f.endsWith('.sql'))
|
|
570
|
+
.sort();
|
|
571
|
+
if (migrations.length === 0) {
|
|
572
|
+
return { content: [{ type: 'text', text: 'No migration files found.' }] };
|
|
573
|
+
}
|
|
574
|
+
let output = `Found ${migrations.length} migration(s): ${migrations.join(', ')}\n\n`;
|
|
575
|
+
// Sync migrations
|
|
576
|
+
output += captureExec(`rsync -avz --delete "${config.migrationsPath}/" "${config.server}:/tmp/shsu-migrations/"`) + '\n';
|
|
577
|
+
// Find db container
|
|
578
|
+
const dbContainer = captureExec(`ssh ${config.server} "docker ps -q --filter 'name=${config.dbContainer}'"`);
|
|
579
|
+
if (!dbContainer) {
|
|
580
|
+
return { content: [{ type: 'text', text: `Error: Database container not found (filter: ${config.dbContainer})` }] };
|
|
581
|
+
}
|
|
582
|
+
// Run migrations
|
|
583
|
+
for (const migration of migrations) {
|
|
584
|
+
output += `\nRunning ${migration}...\n`;
|
|
585
|
+
output += captureExec(`ssh ${config.server} "docker exec -i ${dbContainer} psql -U postgres -d postgres -f /tmp/shsu-migrations/${migration}"`) + '\n';
|
|
586
|
+
}
|
|
587
|
+
output += '\nAll migrations applied.';
|
|
588
|
+
return { content: [{ type: 'text', text: output }] };
|
|
589
|
+
}
|
|
590
|
+
|
|
495
591
|
default:
|
|
496
592
|
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
497
593
|
}
|
|
@@ -559,6 +655,8 @@ ${c.yellow('Commands:')}
|
|
|
559
655
|
- With name: deploy single function
|
|
560
656
|
Options: --no-restart
|
|
561
657
|
|
|
658
|
+
migrate Run SQL migrations on database
|
|
659
|
+
|
|
562
660
|
logs [filter] Stream edge-runtime logs
|
|
563
661
|
- Optional filter string
|
|
564
662
|
|
|
@@ -578,6 +676,7 @@ ${c.yellow('Examples:')}
|
|
|
578
676
|
shsu init
|
|
579
677
|
shsu deploy
|
|
580
678
|
shsu deploy hello-world --no-restart
|
|
679
|
+
shsu migrate
|
|
581
680
|
shsu logs hello-world
|
|
582
681
|
shsu invoke hello-world '{"name":"Stefan"}'
|
|
583
682
|
shsu new my-function
|
|
@@ -617,6 +716,11 @@ async function main() {
|
|
|
617
716
|
case 'restart':
|
|
618
717
|
await cmdRestart();
|
|
619
718
|
break;
|
|
719
|
+
case 'migrate':
|
|
720
|
+
case 'migration':
|
|
721
|
+
case 'migrations':
|
|
722
|
+
await cmdMigrate();
|
|
723
|
+
break;
|
|
620
724
|
case 'new':
|
|
621
725
|
case 'create':
|
|
622
726
|
await cmdNew(args[1]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shsu",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "CLI for deploying and managing Supabase Edge Functions on self-hosted Supabase (Coolify, Docker Compose). Sync functions via rsync, stream logs, and invoke endpoints.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1"
|