@supatype/cli 0.1.0-alpha.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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +7 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/bin/dev-entry.ts +2 -0
- package/bin/supatype.js +5 -0
- package/dist/app/framework.d.ts +44 -0
- package/dist/app/framework.d.ts.map +1 -0
- package/dist/app/framework.js +200 -0
- package/dist/app/framework.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +55 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/admin.d.ts +4 -0
- package/dist/commands/admin.d.ts.map +1 -0
- package/dist/commands/admin.js +270 -0
- package/dist/commands/admin.js.map +1 -0
- package/dist/commands/app.d.ts +3 -0
- package/dist/commands/app.d.ts.map +1 -0
- package/dist/commands/app.js +235 -0
- package/dist/commands/app.js.map +1 -0
- package/dist/commands/cloud.d.ts +3 -0
- package/dist/commands/cloud.d.ts.map +1 -0
- package/dist/commands/cloud.js +256 -0
- package/dist/commands/cloud.js.map +1 -0
- package/dist/commands/db.d.ts +8 -0
- package/dist/commands/db.d.ts.map +1 -0
- package/dist/commands/db.js +123 -0
- package/dist/commands/db.js.map +1 -0
- package/dist/commands/deploy-types.d.ts +14 -0
- package/dist/commands/deploy-types.d.ts.map +1 -0
- package/dist/commands/deploy-types.js +38 -0
- package/dist/commands/deploy-types.js.map +1 -0
- package/dist/commands/deploy.d.ts +14 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +295 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/dev.d.ts +3 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +428 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/diff.d.ts +3 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +39 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/engine.d.ts +9 -0
- package/dist/commands/engine.d.ts.map +1 -0
- package/dist/commands/engine.js +99 -0
- package/dist/commands/engine.js.map +1 -0
- package/dist/commands/functions.d.ts +3 -0
- package/dist/commands/functions.d.ts.map +1 -0
- package/dist/commands/functions.js +762 -0
- package/dist/commands/functions.js.map +1 -0
- package/dist/commands/generate.d.ts +3 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +28 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +515 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/keys.d.ts +4 -0
- package/dist/commands/keys.d.ts.map +1 -0
- package/dist/commands/keys.js +57 -0
- package/dist/commands/keys.js.map +1 -0
- package/dist/commands/logs.d.ts +6 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +52 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/migrate.d.ts +3 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +71 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/plugins.d.ts +3 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +431 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/pull.d.ts +3 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +73 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +3 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +87 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/seed.d.ts +3 -0
- package/dist/commands/seed.d.ts.map +1 -0
- package/dist/commands/seed.js +22 -0
- package/dist/commands/seed.js.map +1 -0
- package/dist/commands/self-host.d.ts +3 -0
- package/dist/commands/self-host.d.ts.map +1 -0
- package/dist/commands/self-host.js +796 -0
- package/dist/commands/self-host.js.map +1 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +69 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/config.d.ts +106 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +66 -0
- package/dist/config.js.map +1 -0
- package/dist/engine/cache.d.ts +37 -0
- package/dist/engine/cache.d.ts.map +1 -0
- package/dist/engine/cache.js +121 -0
- package/dist/engine/cache.js.map +1 -0
- package/dist/engine/download.d.ts +19 -0
- package/dist/engine/download.d.ts.map +1 -0
- package/dist/engine/download.js +108 -0
- package/dist/engine/download.js.map +1 -0
- package/dist/engine/platform.d.ts +24 -0
- package/dist/engine/platform.d.ts.map +1 -0
- package/dist/engine/platform.js +50 -0
- package/dist/engine/platform.js.map +1 -0
- package/dist/engine/resolve.d.ts +37 -0
- package/dist/engine/resolve.d.ts.map +1 -0
- package/dist/engine/resolve.js +133 -0
- package/dist/engine/resolve.js.map +1 -0
- package/dist/engine/update-notify.d.ts +11 -0
- package/dist/engine/update-notify.d.ts.map +1 -0
- package/dist/engine/update-notify.js +43 -0
- package/dist/engine/update-notify.js.map +1 -0
- package/dist/engine/verify.d.ts +50 -0
- package/dist/engine/verify.d.ts.map +1 -0
- package/dist/engine/verify.js +161 -0
- package/dist/engine/verify.js.map +1 -0
- package/dist/engine-version.d.ts +35 -0
- package/dist/engine-version.d.ts.map +1 -0
- package/dist/engine-version.js +35 -0
- package/dist/engine-version.js.map +1 -0
- package/dist/engine.d.ts +34 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +76 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt.d.ts +3 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +13 -0
- package/dist/jwt.js.map +1 -0
- package/dist/pull-utils.d.ts +16 -0
- package/dist/pull-utils.d.ts.map +1 -0
- package/dist/pull-utils.js +65 -0
- package/dist/pull-utils.js.map +1 -0
- package/dist/scripts/postinstall.d.ts +12 -0
- package/dist/scripts/postinstall.d.ts.map +1 -0
- package/dist/scripts/postinstall.js +31 -0
- package/dist/scripts/postinstall.js.map +1 -0
- package/dist/tsx-runner.d.ts +18 -0
- package/dist/tsx-runner.d.ts.map +1 -0
- package/dist/tsx-runner.js +62 -0
- package/dist/tsx-runner.js.map +1 -0
- package/package.json +36 -0
- package/src/app/framework.ts +249 -0
- package/src/cli.ts +58 -0
- package/src/commands/admin.ts +371 -0
- package/src/commands/app.ts +261 -0
- package/src/commands/cloud.ts +326 -0
- package/src/commands/db.ts +145 -0
- package/src/commands/deploy-types.ts +49 -0
- package/src/commands/deploy.ts +366 -0
- package/src/commands/dev.ts +477 -0
- package/src/commands/diff.ts +61 -0
- package/src/commands/engine.ts +133 -0
- package/src/commands/functions.ts +919 -0
- package/src/commands/generate.ts +31 -0
- package/src/commands/init.ts +532 -0
- package/src/commands/keys.ts +66 -0
- package/src/commands/logs.ts +58 -0
- package/src/commands/migrate.ts +83 -0
- package/src/commands/plugins.ts +508 -0
- package/src/commands/pull.ts +96 -0
- package/src/commands/push.ts +119 -0
- package/src/commands/seed.ts +26 -0
- package/src/commands/self-host.ts +932 -0
- package/src/commands/status.ts +83 -0
- package/src/config.ts +190 -0
- package/src/engine/cache.ts +135 -0
- package/src/engine/download.ts +143 -0
- package/src/engine/platform.ts +66 -0
- package/src/engine/resolve.ts +197 -0
- package/src/engine/update-notify.ts +50 -0
- package/src/engine/verify.ts +206 -0
- package/src/engine-version.ts +39 -0
- package/src/engine.ts +99 -0
- package/src/index.ts +19 -0
- package/src/jwt.ts +14 -0
- package/src/pull-utils.ts +57 -0
- package/src/scripts/postinstall.ts +40 -0
- package/src/tsx-runner.ts +79 -0
- package/tests/cli-help.test.ts +107 -0
- package/tests/config.test.ts +117 -0
- package/tests/engine-distribution.test.ts +418 -0
- package/tests/init.test.ts +184 -0
- package/tests/keys.test.ts +160 -0
- package/tests/pull-utils.test.ts +115 -0
- package/tests/tsx-runner.test.ts +66 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import type { Command } from "commander"
|
|
2
|
+
import { spawnSync, spawn, type ChildProcess } from "node:child_process"
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
4
|
+
import { resolve } from "node:path"
|
|
5
|
+
import { loadConfig } from "../config.js"
|
|
6
|
+
import { ensureEngine, invokeEngine } from "../engine.js"
|
|
7
|
+
|
|
8
|
+
const POSTGREST_URL = "http://localhost:3000"
|
|
9
|
+
const HEALTH_TIMEOUT_MS = 60_000
|
|
10
|
+
const HEALTH_POLL_MS = 2_000
|
|
11
|
+
|
|
12
|
+
export function registerDev(program: Command): void {
|
|
13
|
+
program
|
|
14
|
+
.command("dev")
|
|
15
|
+
.description(
|
|
16
|
+
"Start local Postgres, PostgREST, and Kong via Docker Compose, then watch for schema changes",
|
|
17
|
+
)
|
|
18
|
+
.option("--no-watch", "Start services but do not watch for schema changes")
|
|
19
|
+
.option("--local", "Run storage, realtime, and studio from source (monorepo dev)")
|
|
20
|
+
.action(async (opts: { watch: boolean; local: boolean }) => {
|
|
21
|
+
const cwd = process.cwd()
|
|
22
|
+
|
|
23
|
+
// Generate .env with local defaults if missing
|
|
24
|
+
ensureDevEnv(cwd)
|
|
25
|
+
|
|
26
|
+
if (opts.local) {
|
|
27
|
+
// Monorepo dev — generate an infra-only compose if needed, then start
|
|
28
|
+
const composePath = resolve(cwd, "docker-compose.yml")
|
|
29
|
+
if (!existsSync(composePath)) {
|
|
30
|
+
ensureInfraCompose(cwd)
|
|
31
|
+
}
|
|
32
|
+
console.log("Starting infra services...")
|
|
33
|
+
const up = spawnSync(
|
|
34
|
+
"docker",
|
|
35
|
+
["compose", "up", "-d", "--wait", "db", "pgbouncer", "gotrue", "postgrest", "minio", "kong"],
|
|
36
|
+
{ cwd, stdio: "inherit" },
|
|
37
|
+
)
|
|
38
|
+
if (up.status !== 0) {
|
|
39
|
+
console.error("docker compose up failed.")
|
|
40
|
+
process.exit(1)
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
if (!existsSync(resolve(cwd, "docker-compose.yml"))) {
|
|
44
|
+
console.error(
|
|
45
|
+
"docker-compose.yml not found. Run: supatype init",
|
|
46
|
+
)
|
|
47
|
+
process.exit(1)
|
|
48
|
+
}
|
|
49
|
+
console.log("Starting services...")
|
|
50
|
+
const up = spawnSync(
|
|
51
|
+
"docker",
|
|
52
|
+
["compose", "up", "-d", "--wait"],
|
|
53
|
+
{ cwd, stdio: "inherit" },
|
|
54
|
+
)
|
|
55
|
+
if (up.status !== 0) {
|
|
56
|
+
console.error("docker compose up failed.")
|
|
57
|
+
process.exit(1)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log("Waiting for PostgREST to be ready...")
|
|
62
|
+
await waitForPostgREST()
|
|
63
|
+
|
|
64
|
+
const children: ChildProcess[] = []
|
|
65
|
+
|
|
66
|
+
if (opts.local) {
|
|
67
|
+
console.log("\nStarting local services from source...")
|
|
68
|
+
children.push(
|
|
69
|
+
...startLocalServices(cwd),
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log("\nServices running:")
|
|
74
|
+
console.log(" Postgres postgresql://localhost:5432")
|
|
75
|
+
console.log(" PostgREST http://localhost:3000")
|
|
76
|
+
console.log(" Kong http://localhost:8000")
|
|
77
|
+
console.log(" REST API http://localhost:8000/rest/v1/")
|
|
78
|
+
console.log(" GraphQL http://localhost:8000/graphql/v1")
|
|
79
|
+
if (opts.local) {
|
|
80
|
+
console.log(" Storage http://localhost:5000 (from source)")
|
|
81
|
+
console.log(" Realtime http://localhost:4000 (from source)")
|
|
82
|
+
console.log(" Studio http://localhost:3002 (from source)")
|
|
83
|
+
}
|
|
84
|
+
console.log()
|
|
85
|
+
|
|
86
|
+
// Clean shutdown on Ctrl+C
|
|
87
|
+
const cleanup = () => {
|
|
88
|
+
for (const child of children) {
|
|
89
|
+
child.kill()
|
|
90
|
+
}
|
|
91
|
+
process.exit(0)
|
|
92
|
+
}
|
|
93
|
+
process.on("SIGINT", cleanup)
|
|
94
|
+
process.on("SIGTERM", cleanup)
|
|
95
|
+
|
|
96
|
+
if (opts.watch) {
|
|
97
|
+
await watchAndPush(cwd)
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function ensureInfraCompose(cwd: string): void {
|
|
103
|
+
const projectName = resolve(cwd).split(/[\\/]/).pop() ?? "supatype"
|
|
104
|
+
const content = `# Generated by supatype dev --local — infra services only
|
|
105
|
+
services:
|
|
106
|
+
db:
|
|
107
|
+
image: supatype/postgres:17-latest
|
|
108
|
+
environment:
|
|
109
|
+
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-postgres}
|
|
110
|
+
POSTGRES_DB: \${POSTGRES_DB:-${projectName}}
|
|
111
|
+
ports:
|
|
112
|
+
- "5432:5432"
|
|
113
|
+
volumes:
|
|
114
|
+
- db-data:/var/lib/postgresql/data
|
|
115
|
+
healthcheck:
|
|
116
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
117
|
+
interval: 5s
|
|
118
|
+
timeout: 5s
|
|
119
|
+
retries: 20
|
|
120
|
+
|
|
121
|
+
pgbouncer:
|
|
122
|
+
image: pgbouncer/pgbouncer:latest
|
|
123
|
+
volumes:
|
|
124
|
+
- ./.supatype/pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro
|
|
125
|
+
- ./.supatype/userlist.txt:/etc/pgbouncer/userlist.txt:ro
|
|
126
|
+
depends_on:
|
|
127
|
+
db:
|
|
128
|
+
condition: service_healthy
|
|
129
|
+
healthcheck:
|
|
130
|
+
test: ["CMD", "pg_isready", "-h", "localhost", "-p", "6432", "-U", "postgres"]
|
|
131
|
+
interval: 5s
|
|
132
|
+
timeout: 5s
|
|
133
|
+
retries: 10
|
|
134
|
+
|
|
135
|
+
gotrue:
|
|
136
|
+
image: supatype/auth:v1.0.0
|
|
137
|
+
environment:
|
|
138
|
+
GOTRUE_API_HOST: 0.0.0.0
|
|
139
|
+
GOTRUE_API_PORT: 9999
|
|
140
|
+
GOTRUE_DB_DRIVER: postgres
|
|
141
|
+
GOTRUE_DB_DATABASE_URL: "postgres://postgres:\${POSTGRES_PASSWORD:-postgres}@pgbouncer:6432/\${POSTGRES_DB:-${projectName}}?search_path=auth"
|
|
142
|
+
GOTRUE_SITE_URL: \${SITE_URL:-http://localhost:3000}
|
|
143
|
+
GOTRUE_JWT_SECRET: \${JWT_SECRET:-super-secret-jwt-token-change-in-production}
|
|
144
|
+
GOTRUE_JWT_EXP: 3600
|
|
145
|
+
GOTRUE_JWT_AUD: authenticated
|
|
146
|
+
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
|
|
147
|
+
GOTRUE_JWT_ADMIN_ROLES: service_role
|
|
148
|
+
GOTRUE_MAILER_AUTOCONFIRM: \${GOTRUE_MAILER_AUTOCONFIRM:-true}
|
|
149
|
+
GOTRUE_DISABLE_SIGNUP: \${DISABLE_SIGNUP:-false}
|
|
150
|
+
ports:
|
|
151
|
+
- "9999:9999"
|
|
152
|
+
depends_on:
|
|
153
|
+
pgbouncer:
|
|
154
|
+
condition: service_healthy
|
|
155
|
+
|
|
156
|
+
postgrest:
|
|
157
|
+
image: postgrest/postgrest:v12.2.8
|
|
158
|
+
environment:
|
|
159
|
+
PGRST_DB_URI: postgresql://authenticator:\${POSTGRES_PASSWORD:-postgres}@pgbouncer:6432/\${POSTGRES_DB:-${projectName}}
|
|
160
|
+
PGRST_DB_SCHEMA: public
|
|
161
|
+
PGRST_DB_ANON_ROLE: anon
|
|
162
|
+
PGRST_JWT_SECRET: \${JWT_SECRET:-super-secret-jwt-token-change-in-production}
|
|
163
|
+
PGRST_DB_EXTRA_SEARCH_PATH: public,extensions
|
|
164
|
+
PGRST_DB_POOL: 3
|
|
165
|
+
ports:
|
|
166
|
+
- "3000:3000"
|
|
167
|
+
depends_on:
|
|
168
|
+
pgbouncer:
|
|
169
|
+
condition: service_healthy
|
|
170
|
+
|
|
171
|
+
minio:
|
|
172
|
+
image: minio/minio:RELEASE.2024-11-07T00-52-20Z
|
|
173
|
+
command: server /data --console-address ":9001"
|
|
174
|
+
environment:
|
|
175
|
+
MINIO_ROOT_USER: supatype
|
|
176
|
+
MINIO_ROOT_PASSWORD: supatype-secret
|
|
177
|
+
ports:
|
|
178
|
+
- "9000:9000"
|
|
179
|
+
- "9001:9001"
|
|
180
|
+
volumes:
|
|
181
|
+
- minio-data:/data
|
|
182
|
+
healthcheck:
|
|
183
|
+
test: ["CMD", "mc", "ready", "local"]
|
|
184
|
+
interval: 5s
|
|
185
|
+
timeout: 5s
|
|
186
|
+
retries: 10
|
|
187
|
+
|
|
188
|
+
kong:
|
|
189
|
+
image: kong:3.6
|
|
190
|
+
environment:
|
|
191
|
+
KONG_DATABASE: "off"
|
|
192
|
+
KONG_DECLARATIVE_CONFIG: /etc/kong/kong.yml
|
|
193
|
+
KONG_PROXY_ACCESS_LOG: /dev/stdout
|
|
194
|
+
KONG_ADMIN_ACCESS_LOG: /dev/stdout
|
|
195
|
+
KONG_PROXY_ERROR_LOG: /dev/stderr
|
|
196
|
+
KONG_ADMIN_ERROR_LOG: /dev/stderr
|
|
197
|
+
volumes:
|
|
198
|
+
- ./.supatype/kong.yml:/etc/kong/kong.yml:ro
|
|
199
|
+
ports:
|
|
200
|
+
- "8000:8000"
|
|
201
|
+
depends_on:
|
|
202
|
+
- postgrest
|
|
203
|
+
- gotrue
|
|
204
|
+
|
|
205
|
+
volumes:
|
|
206
|
+
db-data:
|
|
207
|
+
minio-data:
|
|
208
|
+
`
|
|
209
|
+
writeFileSync(resolve(cwd, "docker-compose.yml"), content, "utf8")
|
|
210
|
+
console.log(" created docker-compose.yml (infra only)\n")
|
|
211
|
+
|
|
212
|
+
// Also ensure pgbouncer config exists
|
|
213
|
+
const supatypeDir = resolve(cwd, ".supatype")
|
|
214
|
+
mkdirSync(supatypeDir, { recursive: true })
|
|
215
|
+
|
|
216
|
+
if (!existsSync(resolve(supatypeDir, "pgbouncer.ini"))) {
|
|
217
|
+
writeFileSync(resolve(supatypeDir, "pgbouncer.ini"), `[databases]
|
|
218
|
+
* = host=db port=5432
|
|
219
|
+
|
|
220
|
+
[pgbouncer]
|
|
221
|
+
listen_addr = 0.0.0.0
|
|
222
|
+
listen_port = 6432
|
|
223
|
+
auth_type = trust
|
|
224
|
+
auth_file = /etc/pgbouncer/userlist.txt
|
|
225
|
+
pool_mode = transaction
|
|
226
|
+
default_pool_size = 20
|
|
227
|
+
max_db_connections = 60
|
|
228
|
+
max_client_conn = 100
|
|
229
|
+
server_reset_query = DEALLOCATE ALL
|
|
230
|
+
ignore_startup_parameters = extra_float_digits
|
|
231
|
+
`, "utf8")
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!existsSync(resolve(supatypeDir, "userlist.txt"))) {
|
|
235
|
+
writeFileSync(resolve(supatypeDir, "userlist.txt"), "", "utf8")
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!existsSync(resolve(supatypeDir, "kong.yml"))) {
|
|
239
|
+
writeFileSync(resolve(supatypeDir, "kong.yml"), `_format_version: "3.0"
|
|
240
|
+
|
|
241
|
+
services:
|
|
242
|
+
- name: rest-v1
|
|
243
|
+
url: http://postgrest:3000
|
|
244
|
+
routes:
|
|
245
|
+
- name: rest-v1-all
|
|
246
|
+
strip_path: true
|
|
247
|
+
paths:
|
|
248
|
+
- /rest/v1/
|
|
249
|
+
- name: auth-v1
|
|
250
|
+
url: http://gotrue:9999
|
|
251
|
+
routes:
|
|
252
|
+
- name: auth-v1-all
|
|
253
|
+
strip_path: true
|
|
254
|
+
paths:
|
|
255
|
+
- /auth/v1/
|
|
256
|
+
- name: storage-v1
|
|
257
|
+
url: http://host.docker.internal:5000
|
|
258
|
+
routes:
|
|
259
|
+
- name: storage-v1-all
|
|
260
|
+
strip_path: true
|
|
261
|
+
paths:
|
|
262
|
+
- /storage/v1/
|
|
263
|
+
- name: realtime-v1
|
|
264
|
+
url: http://host.docker.internal:4000
|
|
265
|
+
routes:
|
|
266
|
+
- name: realtime-v1-all
|
|
267
|
+
strip_path: true
|
|
268
|
+
paths:
|
|
269
|
+
- /realtime/v1/
|
|
270
|
+
protocols:
|
|
271
|
+
- http
|
|
272
|
+
- https
|
|
273
|
+
- ws
|
|
274
|
+
- wss
|
|
275
|
+
- name: functions-v1
|
|
276
|
+
url: http://host.docker.internal:54321
|
|
277
|
+
routes:
|
|
278
|
+
- name: functions-v1-all
|
|
279
|
+
strip_path: false
|
|
280
|
+
paths:
|
|
281
|
+
- /functions/v1/
|
|
282
|
+
- name: studio
|
|
283
|
+
url: http://host.docker.internal:3002
|
|
284
|
+
routes:
|
|
285
|
+
- name: studio-all
|
|
286
|
+
strip_path: true
|
|
287
|
+
paths:
|
|
288
|
+
- /studio/
|
|
289
|
+
`, "utf8")
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function ensureDevEnv(cwd: string): void {
|
|
294
|
+
const envPath = resolve(cwd, ".env")
|
|
295
|
+
if (existsSync(envPath)) return
|
|
296
|
+
|
|
297
|
+
const projectName = resolve(cwd).split(/[\\/]/).pop() ?? "supatype"
|
|
298
|
+
const content = `# Generated by supatype dev — all defaults for local development
|
|
299
|
+
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/${projectName}
|
|
300
|
+
POSTGRES_PASSWORD=postgres
|
|
301
|
+
POSTGRES_DB=${projectName}
|
|
302
|
+
|
|
303
|
+
JWT_SECRET=super-secret-jwt-token-change-in-production
|
|
304
|
+
ANON_KEY=
|
|
305
|
+
SERVICE_ROLE_KEY=
|
|
306
|
+
|
|
307
|
+
SITE_URL=http://localhost:3000
|
|
308
|
+
|
|
309
|
+
# Storage (MinIO)
|
|
310
|
+
S3_ENDPOINT=http://localhost:9000
|
|
311
|
+
S3_REGION=us-east-1
|
|
312
|
+
S3_ACCESS_KEY=supatype
|
|
313
|
+
S3_SECRET_KEY=supatype-secret
|
|
314
|
+
S3_FORCE_PATH_STYLE=true
|
|
315
|
+
|
|
316
|
+
# SMTP — leave empty for email autoconfirm in dev
|
|
317
|
+
SMTP_HOST=
|
|
318
|
+
SMTP_PORT=
|
|
319
|
+
SMTP_USER=
|
|
320
|
+
SMTP_PASS=
|
|
321
|
+
`
|
|
322
|
+
writeFileSync(envPath, content, "utf8")
|
|
323
|
+
console.log(" created .env (local dev defaults)\n")
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function loadDotEnv(cwd: string): Record<string, string> {
|
|
327
|
+
const envPath = resolve(cwd, ".env")
|
|
328
|
+
if (!existsSync(envPath)) return {}
|
|
329
|
+
const vars: Record<string, string> = {}
|
|
330
|
+
for (const line of readFileSync(envPath, "utf8").split("\n")) {
|
|
331
|
+
const trimmed = line.trim()
|
|
332
|
+
if (!trimmed || trimmed.startsWith("#")) continue
|
|
333
|
+
const eq = trimmed.indexOf("=")
|
|
334
|
+
if (eq === -1) continue
|
|
335
|
+
const key = trimmed.slice(0, eq)
|
|
336
|
+
const value = trimmed.slice(eq + 1)
|
|
337
|
+
vars[key] = value
|
|
338
|
+
}
|
|
339
|
+
return vars
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function localDevDefaults(cwd: string): Record<string, string> {
|
|
343
|
+
const projectName = resolve(cwd).split(/[\\/]/).pop() ?? "supatype"
|
|
344
|
+
// Known defaults that match docker-compose local dev setup
|
|
345
|
+
const defaults: Record<string, string> = {
|
|
346
|
+
DATABASE_URL: `postgresql://postgres:postgres@localhost:5432/${projectName}`,
|
|
347
|
+
POSTGRES_PASSWORD: "postgres",
|
|
348
|
+
POSTGRES_DB: projectName,
|
|
349
|
+
JWT_SECRET: "super-secret-jwt-token-change-in-production",
|
|
350
|
+
SITE_URL: "http://localhost:3000",
|
|
351
|
+
S3_ENDPOINT: "http://localhost:9000",
|
|
352
|
+
S3_REGION: "us-east-1",
|
|
353
|
+
S3_ACCESS_KEY: "supatype",
|
|
354
|
+
S3_SECRET_KEY: "supatype-secret",
|
|
355
|
+
S3_FORCE_PATH_STYLE: "true",
|
|
356
|
+
SLOT_NAME: "realtime_slot",
|
|
357
|
+
REPLICATION_POLL_INTERVAL: "100",
|
|
358
|
+
SECURE_CHANNELS: "true",
|
|
359
|
+
}
|
|
360
|
+
// .env file values override defaults
|
|
361
|
+
const dotEnv = loadDotEnv(cwd)
|
|
362
|
+
return { ...defaults, ...dotEnv }
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function startLocalServices(cwd: string): ChildProcess[] {
|
|
366
|
+
const children: ChildProcess[] = []
|
|
367
|
+
const devEnv = localDevDefaults(cwd)
|
|
368
|
+
|
|
369
|
+
const services = [
|
|
370
|
+
{ name: "storage", filter: "@supatype/storage", color: "\x1b[34m" },
|
|
371
|
+
{ name: "realtime", filter: "@supatype/realtime", color: "\x1b[35m" },
|
|
372
|
+
{ name: "studio", filter: "@supatype/studio", color: "\x1b[36m" },
|
|
373
|
+
]
|
|
374
|
+
|
|
375
|
+
for (const svc of services) {
|
|
376
|
+
const pkgDir = resolve(cwd, "..", "packages", svc.name)
|
|
377
|
+
|
|
378
|
+
if (!existsSync(resolve(pkgDir, "package.json"))) {
|
|
379
|
+
console.warn(` Skipping ${svc.name} — not found at ${pkgDir}`)
|
|
380
|
+
continue
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const reset = "\x1b[0m"
|
|
384
|
+
const prefix = `${svc.color}[${svc.name}]${reset}`
|
|
385
|
+
|
|
386
|
+
const child = spawn("pnpm", ["dev"], {
|
|
387
|
+
cwd: pkgDir,
|
|
388
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
389
|
+
shell: true,
|
|
390
|
+
env: {
|
|
391
|
+
...process.env,
|
|
392
|
+
...devEnv,
|
|
393
|
+
PORT: svc.name === "storage" ? "5000" : svc.name === "realtime" ? "4000" : "3002",
|
|
394
|
+
},
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
child.stdout?.on("data", (data: Buffer) => {
|
|
398
|
+
for (const line of data.toString().trimEnd().split("\n")) {
|
|
399
|
+
console.log(`${prefix} ${line}`)
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
child.stderr?.on("data", (data: Buffer) => {
|
|
403
|
+
for (const line of data.toString().trimEnd().split("\n")) {
|
|
404
|
+
console.error(`${prefix} ${line}`)
|
|
405
|
+
}
|
|
406
|
+
})
|
|
407
|
+
child.on("exit", (code) => {
|
|
408
|
+
if (code !== 0 && code !== null) {
|
|
409
|
+
console.error(`${prefix} exited with code ${code}`)
|
|
410
|
+
}
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
children.push(child)
|
|
414
|
+
console.log(` ${prefix} started (pnpm dev)`)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return children
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function waitForPostgREST(): Promise<void> {
|
|
421
|
+
const deadline = Date.now() + HEALTH_TIMEOUT_MS
|
|
422
|
+
while (Date.now() < deadline) {
|
|
423
|
+
try {
|
|
424
|
+
const res = await fetch(POSTGREST_URL, { signal: AbortSignal.timeout(2000) })
|
|
425
|
+
if (res.ok || res.status === 401) return // 401 = JWT required = server up
|
|
426
|
+
} catch {
|
|
427
|
+
// not ready yet
|
|
428
|
+
}
|
|
429
|
+
await sleep(HEALTH_POLL_MS)
|
|
430
|
+
}
|
|
431
|
+
throw new Error(
|
|
432
|
+
`PostgREST did not become healthy within ${HEALTH_TIMEOUT_MS / 1000}s.\n` +
|
|
433
|
+
"Check: docker compose logs postgrest",
|
|
434
|
+
)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function watchAndPush(cwd: string): Promise<void> {
|
|
438
|
+
const config = loadConfig(cwd)
|
|
439
|
+
const schemaDir = resolve(cwd, config.schema, "..")
|
|
440
|
+
|
|
441
|
+
console.log(`Watching ${schemaDir} for changes... (Ctrl+C to stop)\n`)
|
|
442
|
+
|
|
443
|
+
// Initial push on start
|
|
444
|
+
await runPush(cwd)
|
|
445
|
+
|
|
446
|
+
const { watch } = await import("node:fs")
|
|
447
|
+
watch(schemaDir, { recursive: true }, (eventType, filename) => {
|
|
448
|
+
if (!filename?.endsWith(".ts")) return
|
|
449
|
+
console.log(`\nChange detected in ${filename}, pushing...`)
|
|
450
|
+
runPush(cwd).catch((e: unknown) =>
|
|
451
|
+
console.error("Push failed:", (e as Error).message),
|
|
452
|
+
)
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
// Block forever
|
|
456
|
+
await new Promise<never>(() => undefined)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async function runPush(cwd: string): Promise<void> {
|
|
460
|
+
const { loadConfig, loadSchemaAst } = await import("../config.js")
|
|
461
|
+
const config = loadConfig(cwd)
|
|
462
|
+
const ast = loadSchemaAst(config.schema, cwd)
|
|
463
|
+
await ensureEngine()
|
|
464
|
+
const result = invokeEngine(
|
|
465
|
+
["migrate", "--connection", config.connection],
|
|
466
|
+
JSON.stringify(ast),
|
|
467
|
+
)
|
|
468
|
+
if (result.exitCode !== 0) {
|
|
469
|
+
console.error(result.stderr || result.stdout)
|
|
470
|
+
return
|
|
471
|
+
}
|
|
472
|
+
console.log(result.stdout || "Schema up to date.")
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function sleep(ms: number): Promise<void> {
|
|
476
|
+
return new Promise((r) => setTimeout(r, ms))
|
|
477
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Command } from "commander"
|
|
2
|
+
import { loadConfig, loadSchemaAst } from "../config.js"
|
|
3
|
+
import { ensureEngine, invokeEngine } from "../engine.js"
|
|
4
|
+
|
|
5
|
+
interface DiffResult {
|
|
6
|
+
operations: Array<{
|
|
7
|
+
kind: string
|
|
8
|
+
risk: "safe" | "cautious" | "destructive"
|
|
9
|
+
description: string
|
|
10
|
+
}>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function registerDiff(program: Command): void {
|
|
14
|
+
program
|
|
15
|
+
.command("diff")
|
|
16
|
+
.description("Show planned schema changes without applying them (dry run)")
|
|
17
|
+
.option("--connection <url>", "Database connection URL (overrides config)")
|
|
18
|
+
.action(async (opts: { connection?: string }) => {
|
|
19
|
+
const cwd = process.cwd()
|
|
20
|
+
const config = loadConfig(cwd)
|
|
21
|
+
const connection = opts.connection ?? config.connection
|
|
22
|
+
|
|
23
|
+
await ensureEngine()
|
|
24
|
+
|
|
25
|
+
console.log("Loading schema...")
|
|
26
|
+
const ast = loadSchemaAst(config.schema, cwd)
|
|
27
|
+
|
|
28
|
+
const result = invokeEngine(
|
|
29
|
+
["diff", "--connection", connection, "--format", "json"],
|
|
30
|
+
JSON.stringify(ast),
|
|
31
|
+
)
|
|
32
|
+
if (result.exitCode !== 0) {
|
|
33
|
+
console.error(result.stderr || result.stdout)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const diff = JSON.parse(result.stdout) as DiffResult
|
|
38
|
+
const ops = diff.operations ?? []
|
|
39
|
+
|
|
40
|
+
if (ops.length === 0) {
|
|
41
|
+
console.log("No changes.")
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const symbol = { safe: "+", cautious: "~", destructive: "!" }
|
|
46
|
+
const legend = { safe: "safe", cautious: "cautious", destructive: "DESTRUCTIVE" }
|
|
47
|
+
|
|
48
|
+
console.log(`\n${ops.length} change(s):\n`)
|
|
49
|
+
for (const op of ops) {
|
|
50
|
+
console.log(
|
|
51
|
+
` [${symbol[op.risk]}] ${op.description} (${legend[op.risk]})`,
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const destructive = ops.filter((o) => o.risk === "destructive").length
|
|
56
|
+
if (destructive > 0) {
|
|
57
|
+
console.log(`\n ${destructive} destructive operation(s). Run with --yes to skip confirmation.`)
|
|
58
|
+
}
|
|
59
|
+
console.log()
|
|
60
|
+
})
|
|
61
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine management commands:
|
|
3
|
+
* supatype engine version — show pinned, cached, and latest versions
|
|
4
|
+
* supatype engine update-check — check for newer engine versions
|
|
5
|
+
* supatype engine prune — remove old cached engine versions
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Command } from "commander"
|
|
9
|
+
import { ENGINE_VERSION } from "../engine-version.js"
|
|
10
|
+
import { detectPlatform } from "../engine/platform.js"
|
|
11
|
+
import {
|
|
12
|
+
hasCachedBinary,
|
|
13
|
+
listCachedVersions,
|
|
14
|
+
pruneCacheExcept,
|
|
15
|
+
saveUpdateCheck,
|
|
16
|
+
} from "../engine/cache.js"
|
|
17
|
+
import { checkLatestVersion } from "../engine/resolve.js"
|
|
18
|
+
|
|
19
|
+
export function registerEngine(program: Command): void {
|
|
20
|
+
const engine = program
|
|
21
|
+
.command("engine")
|
|
22
|
+
.description("Manage the Supatype engine binary")
|
|
23
|
+
|
|
24
|
+
// supatype engine version
|
|
25
|
+
engine
|
|
26
|
+
.command("version")
|
|
27
|
+
.description("Show engine version information")
|
|
28
|
+
.action(async () => {
|
|
29
|
+
const platform = detectPlatform()
|
|
30
|
+
const cached = hasCachedBinary(ENGINE_VERSION, platform)
|
|
31
|
+
const cachedVersions = listCachedVersions()
|
|
32
|
+
|
|
33
|
+
console.log(`Engine: v${ENGINE_VERSION} (pinned)`)
|
|
34
|
+
console.log(
|
|
35
|
+
`Cache: ${cached ? `v${ENGINE_VERSION} ✓` : "not downloaded"}`,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if (cachedVersions.length > 1) {
|
|
39
|
+
const others = cachedVersions.filter((v) => v !== ENGINE_VERSION)
|
|
40
|
+
console.log(`Other cached versions: ${others.join(", ")}`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check latest
|
|
44
|
+
try {
|
|
45
|
+
const latest = await checkLatestVersion()
|
|
46
|
+
if (latest) {
|
|
47
|
+
await saveUpdateCheck(latest.version)
|
|
48
|
+
if (latest.version !== ENGINE_VERSION) {
|
|
49
|
+
console.log(
|
|
50
|
+
`Latest: v${latest.version} — update available, run: npm update @supatype/cli`,
|
|
51
|
+
)
|
|
52
|
+
} else {
|
|
53
|
+
console.log(`Latest: v${latest.version} (up to date)`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
console.log("Latest: unable to check (offline?)")
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// supatype engine update-check
|
|
62
|
+
engine
|
|
63
|
+
.command("update-check")
|
|
64
|
+
.description("Check if a newer engine version is available")
|
|
65
|
+
.action(async () => {
|
|
66
|
+
const latest = await checkLatestVersion()
|
|
67
|
+
|
|
68
|
+
if (!latest) {
|
|
69
|
+
console.error(
|
|
70
|
+
"Could not check for updates. Check your internet connection.",
|
|
71
|
+
)
|
|
72
|
+
process.exitCode = 1
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await saveUpdateCheck(latest.version)
|
|
77
|
+
|
|
78
|
+
if (latest.version !== ENGINE_VERSION) {
|
|
79
|
+
console.log(
|
|
80
|
+
`Supatype engine v${latest.version} is available (current: v${ENGINE_VERSION}).`,
|
|
81
|
+
)
|
|
82
|
+
console.log(`Run: npm update @supatype/cli`)
|
|
83
|
+
} else {
|
|
84
|
+
console.log(`Engine v${ENGINE_VERSION} is up to date.`)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// supatype engine prune
|
|
89
|
+
engine
|
|
90
|
+
.command("prune")
|
|
91
|
+
.description("Remove all cached engine versions except the current one")
|
|
92
|
+
.action(() => {
|
|
93
|
+
const { removed, bytesFreed } = pruneCacheExcept(ENGINE_VERSION)
|
|
94
|
+
|
|
95
|
+
if (removed.length === 0) {
|
|
96
|
+
console.log("Nothing to prune — only the current version is cached.")
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const mb = (bytesFreed / (1024 * 1024)).toFixed(1)
|
|
101
|
+
console.log(`Removed ${removed.length} cached version(s): ${removed.join(", ")}`)
|
|
102
|
+
console.log(`Space reclaimed: ${mb}MB`)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// supatype engine versions (list all released versions)
|
|
106
|
+
engine
|
|
107
|
+
.command("versions")
|
|
108
|
+
.description("List all released engine versions")
|
|
109
|
+
.action(async () => {
|
|
110
|
+
const { fetchJson } = await import("../engine/download.js")
|
|
111
|
+
const { CDN_BASE_URL } = await import("../engine-version.js")
|
|
112
|
+
|
|
113
|
+
interface VersionEntry {
|
|
114
|
+
version: string
|
|
115
|
+
date: string
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const versions = await fetchJson<VersionEntry[]>(
|
|
119
|
+
`${CDN_BASE_URL}/versions.json`,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if (!versions || versions.length === 0) {
|
|
123
|
+
console.log("No released versions found.")
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log("Released engine versions:")
|
|
128
|
+
for (const v of versions) {
|
|
129
|
+
const current = v.version === ENGINE_VERSION ? " (current)" : ""
|
|
130
|
+
console.log(` v${v.version} ${v.date}${current}`)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
}
|