@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,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
|
+
}
|