@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.
Files changed (200) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +7 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/bin/dev-entry.ts +2 -0
  5. package/bin/supatype.js +5 -0
  6. package/dist/app/framework.d.ts +44 -0
  7. package/dist/app/framework.d.ts.map +1 -0
  8. package/dist/app/framework.js +200 -0
  9. package/dist/app/framework.js.map +1 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +55 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/commands/admin.d.ts +4 -0
  15. package/dist/commands/admin.d.ts.map +1 -0
  16. package/dist/commands/admin.js +270 -0
  17. package/dist/commands/admin.js.map +1 -0
  18. package/dist/commands/app.d.ts +3 -0
  19. package/dist/commands/app.d.ts.map +1 -0
  20. package/dist/commands/app.js +235 -0
  21. package/dist/commands/app.js.map +1 -0
  22. package/dist/commands/cloud.d.ts +3 -0
  23. package/dist/commands/cloud.d.ts.map +1 -0
  24. package/dist/commands/cloud.js +256 -0
  25. package/dist/commands/cloud.js.map +1 -0
  26. package/dist/commands/db.d.ts +8 -0
  27. package/dist/commands/db.d.ts.map +1 -0
  28. package/dist/commands/db.js +123 -0
  29. package/dist/commands/db.js.map +1 -0
  30. package/dist/commands/deploy-types.d.ts +14 -0
  31. package/dist/commands/deploy-types.d.ts.map +1 -0
  32. package/dist/commands/deploy-types.js +38 -0
  33. package/dist/commands/deploy-types.js.map +1 -0
  34. package/dist/commands/deploy.d.ts +14 -0
  35. package/dist/commands/deploy.d.ts.map +1 -0
  36. package/dist/commands/deploy.js +295 -0
  37. package/dist/commands/deploy.js.map +1 -0
  38. package/dist/commands/dev.d.ts +3 -0
  39. package/dist/commands/dev.d.ts.map +1 -0
  40. package/dist/commands/dev.js +428 -0
  41. package/dist/commands/dev.js.map +1 -0
  42. package/dist/commands/diff.d.ts +3 -0
  43. package/dist/commands/diff.d.ts.map +1 -0
  44. package/dist/commands/diff.js +39 -0
  45. package/dist/commands/diff.js.map +1 -0
  46. package/dist/commands/engine.d.ts +9 -0
  47. package/dist/commands/engine.d.ts.map +1 -0
  48. package/dist/commands/engine.js +99 -0
  49. package/dist/commands/engine.js.map +1 -0
  50. package/dist/commands/functions.d.ts +3 -0
  51. package/dist/commands/functions.d.ts.map +1 -0
  52. package/dist/commands/functions.js +762 -0
  53. package/dist/commands/functions.js.map +1 -0
  54. package/dist/commands/generate.d.ts +3 -0
  55. package/dist/commands/generate.d.ts.map +1 -0
  56. package/dist/commands/generate.js +28 -0
  57. package/dist/commands/generate.js.map +1 -0
  58. package/dist/commands/init.d.ts +7 -0
  59. package/dist/commands/init.d.ts.map +1 -0
  60. package/dist/commands/init.js +515 -0
  61. package/dist/commands/init.js.map +1 -0
  62. package/dist/commands/keys.d.ts +4 -0
  63. package/dist/commands/keys.d.ts.map +1 -0
  64. package/dist/commands/keys.js +57 -0
  65. package/dist/commands/keys.js.map +1 -0
  66. package/dist/commands/logs.d.ts +6 -0
  67. package/dist/commands/logs.d.ts.map +1 -0
  68. package/dist/commands/logs.js +52 -0
  69. package/dist/commands/logs.js.map +1 -0
  70. package/dist/commands/migrate.d.ts +3 -0
  71. package/dist/commands/migrate.d.ts.map +1 -0
  72. package/dist/commands/migrate.js +71 -0
  73. package/dist/commands/migrate.js.map +1 -0
  74. package/dist/commands/plugins.d.ts +3 -0
  75. package/dist/commands/plugins.d.ts.map +1 -0
  76. package/dist/commands/plugins.js +431 -0
  77. package/dist/commands/plugins.js.map +1 -0
  78. package/dist/commands/pull.d.ts +3 -0
  79. package/dist/commands/pull.d.ts.map +1 -0
  80. package/dist/commands/pull.js +73 -0
  81. package/dist/commands/pull.js.map +1 -0
  82. package/dist/commands/push.d.ts +3 -0
  83. package/dist/commands/push.d.ts.map +1 -0
  84. package/dist/commands/push.js +87 -0
  85. package/dist/commands/push.js.map +1 -0
  86. package/dist/commands/seed.d.ts +3 -0
  87. package/dist/commands/seed.d.ts.map +1 -0
  88. package/dist/commands/seed.js +22 -0
  89. package/dist/commands/seed.js.map +1 -0
  90. package/dist/commands/self-host.d.ts +3 -0
  91. package/dist/commands/self-host.d.ts.map +1 -0
  92. package/dist/commands/self-host.js +796 -0
  93. package/dist/commands/self-host.js.map +1 -0
  94. package/dist/commands/status.d.ts +6 -0
  95. package/dist/commands/status.d.ts.map +1 -0
  96. package/dist/commands/status.js +69 -0
  97. package/dist/commands/status.js.map +1 -0
  98. package/dist/config.d.ts +106 -0
  99. package/dist/config.d.ts.map +1 -0
  100. package/dist/config.js +66 -0
  101. package/dist/config.js.map +1 -0
  102. package/dist/engine/cache.d.ts +37 -0
  103. package/dist/engine/cache.d.ts.map +1 -0
  104. package/dist/engine/cache.js +121 -0
  105. package/dist/engine/cache.js.map +1 -0
  106. package/dist/engine/download.d.ts +19 -0
  107. package/dist/engine/download.d.ts.map +1 -0
  108. package/dist/engine/download.js +108 -0
  109. package/dist/engine/download.js.map +1 -0
  110. package/dist/engine/platform.d.ts +24 -0
  111. package/dist/engine/platform.d.ts.map +1 -0
  112. package/dist/engine/platform.js +50 -0
  113. package/dist/engine/platform.js.map +1 -0
  114. package/dist/engine/resolve.d.ts +37 -0
  115. package/dist/engine/resolve.d.ts.map +1 -0
  116. package/dist/engine/resolve.js +133 -0
  117. package/dist/engine/resolve.js.map +1 -0
  118. package/dist/engine/update-notify.d.ts +11 -0
  119. package/dist/engine/update-notify.d.ts.map +1 -0
  120. package/dist/engine/update-notify.js +43 -0
  121. package/dist/engine/update-notify.js.map +1 -0
  122. package/dist/engine/verify.d.ts +50 -0
  123. package/dist/engine/verify.d.ts.map +1 -0
  124. package/dist/engine/verify.js +161 -0
  125. package/dist/engine/verify.js.map +1 -0
  126. package/dist/engine-version.d.ts +35 -0
  127. package/dist/engine-version.d.ts.map +1 -0
  128. package/dist/engine-version.js +35 -0
  129. package/dist/engine-version.js.map +1 -0
  130. package/dist/engine.d.ts +34 -0
  131. package/dist/engine.d.ts.map +1 -0
  132. package/dist/engine.js +76 -0
  133. package/dist/engine.js.map +1 -0
  134. package/dist/index.d.ts +12 -0
  135. package/dist/index.d.ts.map +1 -0
  136. package/dist/index.js +10 -0
  137. package/dist/index.js.map +1 -0
  138. package/dist/jwt.d.ts +3 -0
  139. package/dist/jwt.d.ts.map +1 -0
  140. package/dist/jwt.js +13 -0
  141. package/dist/jwt.js.map +1 -0
  142. package/dist/pull-utils.d.ts +16 -0
  143. package/dist/pull-utils.d.ts.map +1 -0
  144. package/dist/pull-utils.js +65 -0
  145. package/dist/pull-utils.js.map +1 -0
  146. package/dist/scripts/postinstall.d.ts +12 -0
  147. package/dist/scripts/postinstall.d.ts.map +1 -0
  148. package/dist/scripts/postinstall.js +31 -0
  149. package/dist/scripts/postinstall.js.map +1 -0
  150. package/dist/tsx-runner.d.ts +18 -0
  151. package/dist/tsx-runner.d.ts.map +1 -0
  152. package/dist/tsx-runner.js +62 -0
  153. package/dist/tsx-runner.js.map +1 -0
  154. package/package.json +36 -0
  155. package/src/app/framework.ts +249 -0
  156. package/src/cli.ts +58 -0
  157. package/src/commands/admin.ts +371 -0
  158. package/src/commands/app.ts +261 -0
  159. package/src/commands/cloud.ts +326 -0
  160. package/src/commands/db.ts +145 -0
  161. package/src/commands/deploy-types.ts +49 -0
  162. package/src/commands/deploy.ts +366 -0
  163. package/src/commands/dev.ts +477 -0
  164. package/src/commands/diff.ts +61 -0
  165. package/src/commands/engine.ts +133 -0
  166. package/src/commands/functions.ts +919 -0
  167. package/src/commands/generate.ts +31 -0
  168. package/src/commands/init.ts +532 -0
  169. package/src/commands/keys.ts +66 -0
  170. package/src/commands/logs.ts +58 -0
  171. package/src/commands/migrate.ts +83 -0
  172. package/src/commands/plugins.ts +508 -0
  173. package/src/commands/pull.ts +96 -0
  174. package/src/commands/push.ts +119 -0
  175. package/src/commands/seed.ts +26 -0
  176. package/src/commands/self-host.ts +932 -0
  177. package/src/commands/status.ts +83 -0
  178. package/src/config.ts +190 -0
  179. package/src/engine/cache.ts +135 -0
  180. package/src/engine/download.ts +143 -0
  181. package/src/engine/platform.ts +66 -0
  182. package/src/engine/resolve.ts +197 -0
  183. package/src/engine/update-notify.ts +50 -0
  184. package/src/engine/verify.ts +206 -0
  185. package/src/engine-version.ts +39 -0
  186. package/src/engine.ts +99 -0
  187. package/src/index.ts +19 -0
  188. package/src/jwt.ts +14 -0
  189. package/src/pull-utils.ts +57 -0
  190. package/src/scripts/postinstall.ts +40 -0
  191. package/src/tsx-runner.ts +79 -0
  192. package/tests/cli-help.test.ts +107 -0
  193. package/tests/config.test.ts +117 -0
  194. package/tests/engine-distribution.test.ts +418 -0
  195. package/tests/init.test.ts +184 -0
  196. package/tests/keys.test.ts +160 -0
  197. package/tests/pull-utils.test.ts +115 -0
  198. package/tests/tsx-runner.test.ts +66 -0
  199. package/tsconfig.json +10 -0
  200. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,31 @@
1
+ import type { Command } from "commander"
2
+ import { loadConfig, loadSchemaAst } from "../config.js"
3
+ import { ensureEngine, invokeEngine } from "../engine.js"
4
+
5
+ export function registerGenerate(program: Command): void {
6
+ program
7
+ .command("generate")
8
+ .description("Regenerate TypeScript types without running a migration")
9
+ .option("--connection <url>", "Database connection URL (overrides config)")
10
+ .action(async (opts: { connection?: string }) => {
11
+ const cwd = process.cwd()
12
+ const config = loadConfig(cwd)
13
+ const connection = opts.connection ?? config.connection
14
+
15
+ await ensureEngine()
16
+ console.log("Loading schema...")
17
+ const ast = loadSchemaAst(config.schema, cwd)
18
+
19
+ const args = ["generate", "--connection", connection]
20
+ if (config.output?.types) args.push("--types", config.output.types)
21
+ if (config.output?.client) args.push("--client", config.output.client)
22
+
23
+ const result = invokeEngine(args, JSON.stringify(ast))
24
+ if (result.exitCode !== 0) {
25
+ console.error(result.stderr || result.stdout)
26
+ process.exit(1)
27
+ }
28
+
29
+ console.log(result.stdout || "Types generated.")
30
+ })
31
+ }
@@ -0,0 +1,532 @@
1
+ import type { Command } from "commander"
2
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs"
3
+ import { resolve, join } from "node:path"
4
+
5
+ export { scaffold }
6
+
7
+ // ─── Markers used by `supatype app add / remove` ─────────────────────────────
8
+ export const APP_COMPOSE_MARKER = " # ─── App service (run: supatype app add) ───"
9
+ export const KONG_APP_MARKER = " # ─── App fallback route (run: supatype app add) ───"
10
+
11
+ export function registerInit(program: Command): void {
12
+ program
13
+ .command("init [name]")
14
+ .description("Scaffold a new Supatype project")
15
+ .action((name?: string) => {
16
+ const projectName = name ?? "my-project"
17
+ const dir = name ? resolve(process.cwd(), name) : process.cwd()
18
+
19
+ if (name && existsSync(dir)) {
20
+ console.error(`Directory already exists: ${dir}`)
21
+ process.exit(1)
22
+ }
23
+
24
+ if (name) mkdirSync(dir, { recursive: true })
25
+
26
+ scaffold(dir, projectName)
27
+
28
+ console.log(`\nSupatype project ready${name ? ` in ${name}/` : ""}.\n`)
29
+ console.log("Next steps:")
30
+ if (name) console.log(` cd ${name}`)
31
+ console.log(" pnpm install")
32
+ console.log(" supatype keys # generate ANON_KEY + SERVICE_ROLE_KEY, add to .env")
33
+ console.log(" supatype dev # start local Postgres + PgBouncer + GoTrue + PostgREST")
34
+ console.log(" supatype push # apply schema + generate types\n")
35
+ })
36
+ }
37
+
38
+ function scaffold(dir: string, projectName: string): void {
39
+ const write = (rel: string, content: string) => {
40
+ const full = join(dir, rel)
41
+ mkdirSync(resolve(full, ".."), { recursive: true })
42
+ writeFileSync(full, content, "utf8")
43
+ console.log(` created ${rel}`)
44
+ }
45
+
46
+ write("supatype.config.ts", configTemplate(projectName))
47
+ write("schema/index.ts", schemaTemplate())
48
+ write(".env", envTemplate(projectName))
49
+ write("docker-compose.yml", dockerComposeTemplate(projectName))
50
+ write(".supatype/kong.yml", kongTemplate())
51
+ write(".supatype/pgbouncer.ini", pgbouncerIniTemplate())
52
+ write(".supatype/userlist.txt", pgbouncerUserlistTemplate())
53
+ write("seed.ts", seedTemplate(projectName))
54
+ write(".gitignore", gitignoreTemplate())
55
+ }
56
+
57
+ // ─── Templates ───────────────────────────────────────────────────────────────
58
+
59
+ function configTemplate(projectName: string): string {
60
+ return `import { defineConfig } from "@supatype/cli"
61
+
62
+ export default defineConfig({
63
+ connection:
64
+ process.env["DATABASE_URL"] ??
65
+ "postgresql://postgres:postgres@localhost:5432/${projectName}",
66
+ schema: "./schema/index.ts",
67
+ output: {
68
+ types: "./src/types/supatype.d.ts",
69
+ client: "./src/lib/supatype.ts",
70
+ },
71
+ // Self-hosted production deployment (run: supatype self-host setup)
72
+ // selfHost: {
73
+ // domain: "${projectName}.example.com",
74
+ // app: {
75
+ // dockerfile: "./Dockerfile",
76
+ // port: 3000,
77
+ // },
78
+ // ssl: {
79
+ // provider: "caddy",
80
+ // email: "you@example.com",
81
+ // },
82
+ // },
83
+ })
84
+ `
85
+ }
86
+
87
+ function schemaTemplate(): string {
88
+ return `import { model, field, access } from "@supatype/schema"
89
+
90
+ export const User = model("user", {
91
+ fields: {
92
+ id: field.uuid({ required: true, default: { kind: "genRandomUuid" } }),
93
+ email: field.email({ required: true, unique: true }),
94
+ name: field.text({ required: true }),
95
+ },
96
+ access: {
97
+ read: access.public(),
98
+ create: access.public(),
99
+ update: access.owner("id"),
100
+ delete: access.role("admin"),
101
+ },
102
+ options: { timestamps: true },
103
+ })
104
+ `
105
+ }
106
+
107
+ function envTemplate(projectName: string): string {
108
+ return `DATABASE_URL=postgresql://postgres:postgres@localhost:5432/${projectName}
109
+ POSTGRES_PASSWORD=postgres
110
+ POSTGRES_DB=${projectName}
111
+
112
+ # JWT — run \`supatype keys\` to generate ANON_KEY and SERVICE_ROLE_KEY
113
+ JWT_SECRET=super-secret-jwt-token-change-in-production
114
+ ANON_KEY=
115
+ SERVICE_ROLE_KEY=
116
+
117
+ # Site URL (used by GoTrue for email redirects)
118
+ SITE_URL=http://localhost:3000
119
+
120
+ # SMTP — leave empty to use email autoconfirm in dev (no emails sent)
121
+ SMTP_HOST=
122
+ SMTP_PORT=
123
+ SMTP_USER=
124
+ SMTP_PASS=
125
+ SMTP_SENDER_NAME=${projectName}
126
+
127
+ # Storage (MinIO for local dev)
128
+ S3_ENDPOINT=http://localhost:9000
129
+ S3_ACCESS_KEY=supatype
130
+ S3_SECRET_KEY=supatype-secret
131
+ `
132
+ }
133
+
134
+ function dockerComposeTemplate(projectName: string): string {
135
+ return `services:
136
+ db:
137
+ image: supatype/postgres:17-latest
138
+ environment:
139
+ POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-postgres}
140
+ POSTGRES_DB: \${POSTGRES_DB:-${projectName}}
141
+ ports:
142
+ - "5432:5432"
143
+ volumes:
144
+ - db-data:/var/lib/postgresql/data
145
+ healthcheck:
146
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
147
+ interval: 5s
148
+ timeout: 5s
149
+ retries: 20
150
+
151
+ pgbouncer:
152
+ image: pgbouncer/pgbouncer:latest
153
+ volumes:
154
+ - ./.supatype/pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro
155
+ - ./.supatype/userlist.txt:/etc/pgbouncer/userlist.txt:ro
156
+ depends_on:
157
+ db:
158
+ condition: service_healthy
159
+ healthcheck:
160
+ test: ["CMD", "pg_isready", "-h", "localhost", "-p", "6432", "-U", "postgres"]
161
+ interval: 5s
162
+ timeout: 5s
163
+ retries: 10
164
+
165
+ gotrue:
166
+ image: supatype/auth:v1.0.0
167
+ environment:
168
+ GOTRUE_API_HOST: 0.0.0.0
169
+ GOTRUE_API_PORT: 9999
170
+ GOTRUE_DB_DRIVER: postgres
171
+ GOTRUE_DB_DATABASE_URL: "postgres://postgres:\${POSTGRES_PASSWORD:-postgres}@pgbouncer:6432/\${POSTGRES_DB:-${projectName}}?search_path=auth"
172
+ GOTRUE_SITE_URL: \${SITE_URL:-http://localhost:3000}
173
+ GOTRUE_JWT_SECRET: \${JWT_SECRET:-super-secret-jwt-token-change-in-production}
174
+ GOTRUE_JWT_EXP: 3600
175
+ GOTRUE_JWT_AUD: authenticated
176
+ GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
177
+ GOTRUE_JWT_ADMIN_ROLES: service_role
178
+ # Email autoconfirm — set to false and configure SMTP for production
179
+ GOTRUE_MAILER_AUTOCONFIRM: \${GOTRUE_MAILER_AUTOCONFIRM:-true}
180
+ GOTRUE_SMTP_HOST: \${SMTP_HOST:-}
181
+ GOTRUE_SMTP_PORT: \${SMTP_PORT:-587}
182
+ GOTRUE_SMTP_USER: \${SMTP_USER:-}
183
+ GOTRUE_SMTP_PASS: \${SMTP_PASS:-}
184
+ GOTRUE_SMTP_SENDER_NAME: \${SMTP_SENDER_NAME:-${projectName}}
185
+ GOTRUE_MAILER_URLPATHS_CONFIRMATION: /auth/v1/verify
186
+ GOTRUE_MAILER_URLPATHS_RECOVERY: /auth/v1/verify
187
+ GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: /auth/v1/verify
188
+ GOTRUE_MAILER_URLPATHS_INVITE: /auth/v1/verify
189
+ GOTRUE_DISABLE_SIGNUP: \${DISABLE_SIGNUP:-false}
190
+ ports:
191
+ - "9999:9999"
192
+ depends_on:
193
+ pgbouncer:
194
+ condition: service_healthy
195
+
196
+ postgrest:
197
+ image: postgrest/postgrest:v12.2.8
198
+ environment:
199
+ PGRST_DB_URI: postgresql://authenticator:\${POSTGRES_PASSWORD:-postgres}@pgbouncer:6432/\${POSTGRES_DB:-${projectName}}
200
+ PGRST_DB_SCHEMA: public
201
+ PGRST_DB_ANON_ROLE: anon
202
+ PGRST_JWT_SECRET: \${JWT_SECRET:-super-secret-jwt-token-change-in-production}
203
+ PGRST_DB_EXTRA_SEARCH_PATH: public,extensions
204
+ PGRST_DB_POOL: 3
205
+ ports:
206
+ - "3000:3000"
207
+ depends_on:
208
+ pgbouncer:
209
+ condition: service_healthy
210
+
211
+ minio:
212
+ image: minio/minio:RELEASE.2024-11-07T00-52-20Z
213
+ command: server /data --console-address ":9001"
214
+ environment:
215
+ MINIO_ROOT_USER: supatype
216
+ MINIO_ROOT_PASSWORD: supatype-secret
217
+ ports:
218
+ - "9000:9000"
219
+ - "9001:9001"
220
+ volumes:
221
+ - minio-data:/data
222
+ healthcheck:
223
+ test: ["CMD", "mc", "ready", "local"]
224
+ interval: 5s
225
+ timeout: 5s
226
+ retries: 10
227
+
228
+ storage:
229
+ image: supatype/storage:latest
230
+ environment:
231
+ PORT: 5000
232
+ DATABASE_URL: "postgresql://postgres:\${POSTGRES_PASSWORD:-postgres}@pgbouncer:6432/\${POSTGRES_DB:-${projectName}}"
233
+ JWT_SECRET: \${JWT_SECRET:-super-secret-jwt-token-change-in-production}
234
+ S3_ENDPOINT: http://minio:9000
235
+ S3_REGION: us-east-1
236
+ S3_ACCESS_KEY: supatype
237
+ S3_SECRET_KEY: supatype-secret
238
+ S3_FORCE_PATH_STYLE: "true"
239
+ ports:
240
+ - "5000:5000"
241
+ depends_on:
242
+ pgbouncer:
243
+ condition: service_healthy
244
+ minio:
245
+ condition: service_healthy
246
+
247
+ realtime:
248
+ image: supatype/realtime:latest
249
+ environment:
250
+ PORT: 4000
251
+ DATABASE_URL: "postgresql://postgres:\${POSTGRES_PASSWORD:-postgres}@pgbouncer:6432/\${POSTGRES_DB:-${projectName}}"
252
+ JWT_SECRET: \${JWT_SECRET:-super-secret-jwt-token-change-in-production}
253
+ REPLICATION_MODE: RLS
254
+ REPLICATION_POLL_INTERVAL: 100
255
+ SECURE_CHANNELS: "true"
256
+ SLOT_NAME: realtime_slot
257
+ ports:
258
+ - "4000:4000"
259
+ depends_on:
260
+ pgbouncer:
261
+ condition: service_healthy
262
+
263
+ studio:
264
+ image: supatype/studio:latest
265
+ ports:
266
+ - "3002:3002"
267
+
268
+ kong:
269
+ image: kong:3.6
270
+ environment:
271
+ KONG_DATABASE: "off"
272
+ KONG_DECLARATIVE_CONFIG: /etc/kong/kong.yml
273
+ KONG_PROXY_ACCESS_LOG: /dev/stdout
274
+ KONG_ADMIN_ACCESS_LOG: /dev/stdout
275
+ KONG_PROXY_ERROR_LOG: /dev/stderr
276
+ KONG_ADMIN_ERROR_LOG: /dev/stderr
277
+ volumes:
278
+ - ./.supatype/kong.yml:/etc/kong/kong.yml:ro
279
+ ports:
280
+ - "8000:8000"
281
+ depends_on:
282
+ - postgrest
283
+ - gotrue
284
+ - storage
285
+ - realtime
286
+ - studio
287
+
288
+ ${APP_COMPOSE_MARKER}
289
+ # app:
290
+ # build:
291
+ # context: .
292
+ # dockerfile: ./Dockerfile
293
+ # ports:
294
+ # - "3000:3000"
295
+ # environment:
296
+ # SUPATYPE_URL: http://kong:8000
297
+ # SUPATYPE_ANON_KEY: \${ANON_KEY}
298
+ # SUPATYPE_SERVICE_ROLE_KEY: \${SERVICE_ROLE_KEY}
299
+ # volumes:
300
+ # - .:/app
301
+ # - /app/node_modules
302
+ # depends_on:
303
+ # kong:
304
+ # condition: service_healthy
305
+
306
+ volumes:
307
+ db-data:
308
+ minio-data:
309
+ `
310
+ }
311
+
312
+ function kongTemplate(): string {
313
+ return `_format_version: "3.0"
314
+
315
+ services:
316
+ - name: rest-v1
317
+ url: http://postgrest:3000
318
+ routes:
319
+ - name: rest-v1-all
320
+ strip_path: true
321
+ paths:
322
+ - /rest/v1/
323
+ plugins:
324
+ - name: cors
325
+ config:
326
+ origins:
327
+ - "*"
328
+ methods:
329
+ - GET
330
+ - POST
331
+ - PATCH
332
+ - DELETE
333
+ - OPTIONS
334
+ headers:
335
+ - Authorization
336
+ - Content-Type
337
+ - apikey
338
+ - Prefer
339
+ credentials: true
340
+
341
+ - name: graphql-v1
342
+ url: http://postgrest:3000/rpc/graphql
343
+ routes:
344
+ - name: graphql-v1-all
345
+ strip_path: true
346
+ paths:
347
+ - /graphql/v1
348
+ plugins:
349
+ - name: cors
350
+ config:
351
+ origins:
352
+ - "*"
353
+ methods:
354
+ - GET
355
+ - POST
356
+ - OPTIONS
357
+ headers:
358
+ - Authorization
359
+ - Content-Type
360
+ - apikey
361
+ credentials: true
362
+
363
+ - name: auth-v1
364
+ url: http://gotrue:9999
365
+ routes:
366
+ - name: auth-v1-all
367
+ strip_path: true
368
+ paths:
369
+ - /auth/v1/
370
+ plugins:
371
+ - name: cors
372
+ config:
373
+ origins:
374
+ - "*"
375
+ methods:
376
+ - GET
377
+ - POST
378
+ - PUT
379
+ - DELETE
380
+ - OPTIONS
381
+ headers:
382
+ - Authorization
383
+ - Content-Type
384
+ - apikey
385
+ credentials: true
386
+
387
+ - name: storage-v1
388
+ url: http://storage:5000
389
+ routes:
390
+ - name: storage-v1-all
391
+ strip_path: true
392
+ paths:
393
+ - /storage/v1/
394
+ plugins:
395
+ - name: cors
396
+ config:
397
+ origins:
398
+ - "*"
399
+ methods:
400
+ - GET
401
+ - POST
402
+ - PUT
403
+ - DELETE
404
+ - OPTIONS
405
+ headers:
406
+ - Authorization
407
+ - Content-Type
408
+ - apikey
409
+ - x-upsert
410
+ credentials: true
411
+
412
+ - name: realtime-v1
413
+ url: http://realtime:4000
414
+ routes:
415
+ - name: realtime-v1-all
416
+ strip_path: true
417
+ paths:
418
+ - /realtime/v1/
419
+ protocols:
420
+ - http
421
+ - https
422
+ - ws
423
+ - wss
424
+ plugins:
425
+ - name: cors
426
+ config:
427
+ origins:
428
+ - "*"
429
+ methods:
430
+ - GET
431
+ - OPTIONS
432
+ headers:
433
+ - Authorization
434
+ - Content-Type
435
+ - apikey
436
+ credentials: true
437
+
438
+ - name: studio
439
+ url: http://studio:3002
440
+ routes:
441
+ - name: studio-all
442
+ strip_path: true
443
+ paths:
444
+ - /studio/
445
+ plugins:
446
+ - name: cors
447
+ config:
448
+ origins:
449
+ - "*"
450
+ methods:
451
+ - GET
452
+ - POST
453
+ - OPTIONS
454
+ headers:
455
+ - Authorization
456
+ - Content-Type
457
+ credentials: true
458
+
459
+ ${KONG_APP_MARKER}
460
+ # - name: app
461
+ # url: http://app:3000
462
+ # routes:
463
+ # - name: app-root
464
+ # paths:
465
+ # - /
466
+ # strip_path: false
467
+ `
468
+ }
469
+
470
+ function pgbouncerIniTemplate(): string {
471
+ return `[databases]
472
+ # Forward all databases to Postgres (Docker service name: db)
473
+ * = host=db port=5432
474
+
475
+ [pgbouncer]
476
+ listen_addr = 0.0.0.0
477
+ listen_port = 6432
478
+ # trust = accept any client on the internal Docker network (safe for local dev)
479
+ auth_type = trust
480
+ auth_file = /etc/pgbouncer/userlist.txt
481
+ pool_mode = transaction
482
+ default_pool_size = 20
483
+ max_db_connections = 60
484
+ max_client_conn = 100
485
+ # Required for PostgREST compatibility
486
+ server_reset_query = DEALLOCATE ALL
487
+ ignore_startup_parameters = extra_float_digits
488
+ `
489
+ }
490
+
491
+ function pgbouncerUserlistTemplate(): string {
492
+ return `# PgBouncer userlist — auth_type = trust for local dev, so passwords here are unused.
493
+ # For production, this file is regenerated by: supatype self-host setup
494
+ `
495
+ }
496
+
497
+ function seedTemplate(projectName: string): string {
498
+ return `import { sql } from "@supatype/cli/seed"
499
+
500
+ // Connect using DATABASE_URL from environment
501
+ const db = sql(
502
+ process.env["DATABASE_URL"] ??
503
+ "postgresql://postgres:postgres@localhost:5432/${projectName}",
504
+ )
505
+
506
+ async function seed() {
507
+ console.log("Seeding ${projectName}...")
508
+
509
+ // TODO: insert seed data
510
+ // await db\`INSERT INTO users (email, name) VALUES ('admin@example.com', 'Admin')\`
511
+
512
+ await db.end()
513
+ console.log("Done.")
514
+ }
515
+
516
+ seed().catch((e) => {
517
+ console.error(e)
518
+ process.exit(1)
519
+ })
520
+ `
521
+ }
522
+
523
+ function gitignoreTemplate(): string {
524
+ return `.env
525
+ node_modules/
526
+ dist/
527
+ .supatype/engine/
528
+ # Generated by supatype push
529
+ src/types/supatype.d.ts
530
+ src/lib/supatype.ts
531
+ `
532
+ }
@@ -0,0 +1,66 @@
1
+ import type { Command } from "commander"
2
+ import { readFileSync, existsSync } from "node:fs"
3
+ import { resolve } from "node:path"
4
+ import { signJwt } from "../jwt.js"
5
+
6
+ export function registerKeys(program: Command): void {
7
+ program
8
+ .command("keys")
9
+ .description("Generate ANON_KEY and SERVICE_ROLE_KEY JWTs from your JWT_SECRET")
10
+ .option("--secret <secret>", "JWT secret (defaults to JWT_SECRET env var or value in .env)")
11
+ .option("--exp-years <years>", "Token expiry in years (default: 10)", "10")
12
+ .action((opts: { secret?: string; expYears: string }) => {
13
+ const secret = opts.secret ?? resolveSecret()
14
+ if (!secret) {
15
+ console.error(
16
+ "Error: JWT_SECRET not found. Set it in .env or pass --secret <value>",
17
+ )
18
+ process.exit(1)
19
+ }
20
+
21
+ const expYears = parseInt(opts.expYears, 10)
22
+ if (isNaN(expYears) || expYears < 1) {
23
+ console.error("Error: --exp-years must be a positive integer")
24
+ process.exit(1)
25
+ }
26
+
27
+ const now = Math.floor(Date.now() / 1000)
28
+ const exp = now + expYears * 365 * 24 * 60 * 60
29
+
30
+ const anonKey = signJwt({ iss: "supatype", role: "anon", iat: now, exp }, secret)
31
+ const serviceKey = signJwt({ iss: "supatype", role: "service_role", iat: now, exp }, secret)
32
+
33
+ console.log("\nGenerated keys (valid for", expYears, "years):\n")
34
+ console.log("ANON_KEY=" + anonKey)
35
+ console.log("SERVICE_ROLE_KEY=" + serviceKey)
36
+ console.log(
37
+ "\nAdd these to your .env file. Do not commit .env to source control.",
38
+ )
39
+ })
40
+ }
41
+
42
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
43
+
44
+ export function resolveSecret(): string | undefined {
45
+ // 1. Check environment variable
46
+ const fromEnv = process.env["JWT_SECRET"]
47
+ if (fromEnv) return fromEnv
48
+
49
+ // 2. Parse .env file in cwd
50
+ const envPath = resolve(process.cwd(), ".env")
51
+ if (!existsSync(envPath)) return undefined
52
+
53
+ try {
54
+ const contents = readFileSync(envPath, "utf8")
55
+ for (const line of contents.split("\n")) {
56
+ const trimmed = line.trim()
57
+ if (trimmed.startsWith("JWT_SECRET=")) {
58
+ const value = trimmed.slice("JWT_SECRET=".length).trim()
59
+ if (value && !value.startsWith("#")) return value
60
+ }
61
+ }
62
+ } catch {
63
+ // ignore read errors
64
+ }
65
+ return undefined
66
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * supatype logs — tail aggregated logs from local Docker containers.
3
+ */
4
+ import type { Command } from "commander"
5
+ import { spawn } from "node:child_process"
6
+
7
+ const SERVICES = ["postgres", "postgrest", "gotrue", "kong", "minio", "realtime", "studio"]
8
+
9
+ export function registerLogs(program: Command): void {
10
+ program
11
+ .command("logs")
12
+ .description("Tail aggregated logs from local dev services")
13
+ .option("--service <name>", "Filter to a specific service")
14
+ .option("--since <duration>", "Show logs since duration (e.g., 5m, 1h)", "5m")
15
+ .option("-f, --follow", "Follow log output", true)
16
+ .action((opts: { service?: string; since?: string; follow?: boolean }) => {
17
+ const services = opts.service
18
+ ? [`supatype-${opts.service}`]
19
+ : SERVICES.map((s) => `supatype-${s}`)
20
+
21
+ if (opts.service && !SERVICES.includes(opts.service)) {
22
+ console.error(`Unknown service: ${opts.service}`)
23
+ console.error(`Available: ${SERVICES.join(", ")}`)
24
+ process.exit(1)
25
+ }
26
+
27
+ const args = ["compose", "logs"]
28
+ if (opts.follow) args.push("-f")
29
+ if (opts.since) args.push("--since", opts.since)
30
+ args.push("--tail", "100")
31
+
32
+ // If filtering by service, use docker logs for that container
33
+ if (opts.service) {
34
+ const containerArgs = ["logs"]
35
+ if (opts.follow) containerArgs.push("-f")
36
+ if (opts.since) containerArgs.push("--since", opts.since)
37
+ containerArgs.push("--tail", "100")
38
+ containerArgs.push(`supatype-${opts.service}`)
39
+
40
+ const child = spawn("docker", containerArgs, { stdio: "inherit" })
41
+ child.on("error", () => {
42
+ console.error("Docker not found. Ensure Docker is installed and running.")
43
+ process.exit(1)
44
+ })
45
+ return
46
+ }
47
+
48
+ // Aggregated logs via docker compose
49
+ const child = spawn("docker", args, {
50
+ stdio: "inherit",
51
+ cwd: process.cwd(),
52
+ })
53
+ child.on("error", () => {
54
+ console.error("Docker not found. Ensure Docker is installed and running.")
55
+ process.exit(1)
56
+ })
57
+ })
58
+ }