@supatype/cli 0.1.0-alpha.7 → 0.1.0-alpha.9

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 (128) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +66 -61
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/dist/app/proxy-dev-app.d.ts +13 -0
  5. package/dist/app/proxy-dev-app.d.ts.map +1 -0
  6. package/dist/app/proxy-dev-app.js +53 -0
  7. package/dist/app/proxy-dev-app.js.map +1 -0
  8. package/dist/binary-cache.d.ts +5 -0
  9. package/dist/binary-cache.d.ts.map +1 -1
  10. package/dist/binary-cache.js +13 -0
  11. package/dist/binary-cache.js.map +1 -1
  12. package/dist/commands/cloud.d.ts +11 -3
  13. package/dist/commands/cloud.d.ts.map +1 -1
  14. package/dist/commands/cloud.js +33 -25
  15. package/dist/commands/cloud.js.map +1 -1
  16. package/dist/commands/deploy.d.ts.map +1 -1
  17. package/dist/commands/deploy.js +3 -17
  18. package/dist/commands/deploy.js.map +1 -1
  19. package/dist/commands/dev.d.ts +3 -3
  20. package/dist/commands/dev.d.ts.map +1 -1
  21. package/dist/commands/dev.js +66 -59
  22. package/dist/commands/dev.js.map +1 -1
  23. package/dist/commands/diff.d.ts.map +1 -1
  24. package/dist/commands/diff.js +11 -1
  25. package/dist/commands/diff.js.map +1 -1
  26. package/dist/commands/init.js +16 -3
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/commands/push.d.ts.map +1 -1
  29. package/dist/commands/push.js +42 -12
  30. package/dist/commands/push.js.map +1 -1
  31. package/dist/commands/update.d.ts.map +1 -1
  32. package/dist/commands/update.js +16 -0
  33. package/dist/commands/update.js.map +1 -1
  34. package/dist/dev-compose.d.ts +17 -0
  35. package/dist/dev-compose.d.ts.map +1 -0
  36. package/dist/dev-compose.js +374 -0
  37. package/dist/dev-compose.js.map +1 -0
  38. package/dist/diff-output.d.ts +4 -0
  39. package/dist/diff-output.d.ts.map +1 -0
  40. package/dist/diff-output.js +12 -0
  41. package/dist/diff-output.js.map +1 -0
  42. package/dist/docker-postgres.d.ts +21 -3
  43. package/dist/docker-postgres.d.ts.map +1 -1
  44. package/dist/docker-postgres.js +130 -18
  45. package/dist/docker-postgres.js.map +1 -1
  46. package/dist/engine-client.d.ts +5 -3
  47. package/dist/engine-client.d.ts.map +1 -1
  48. package/dist/engine-client.js +2 -1
  49. package/dist/engine-client.js.map +1 -1
  50. package/dist/kong-config.d.ts +4 -0
  51. package/dist/kong-config.d.ts.map +1 -1
  52. package/dist/kong-config.js +12 -1
  53. package/dist/kong-config.js.map +1 -1
  54. package/dist/process-manager.d.ts +2 -0
  55. package/dist/process-manager.d.ts.map +1 -1
  56. package/dist/process-manager.js +16 -1
  57. package/dist/process-manager.js.map +1 -1
  58. package/dist/project-config.d.ts +21 -1
  59. package/dist/project-config.d.ts.map +1 -1
  60. package/dist/project-config.js +15 -0
  61. package/dist/project-config.js.map +1 -1
  62. package/dist/runtime-routes.d.ts +9 -0
  63. package/dist/runtime-routes.d.ts.map +1 -1
  64. package/dist/runtime-routes.js +75 -12
  65. package/dist/runtime-routes.js.map +1 -1
  66. package/dist/schema-ast-v2.d.ts +127 -0
  67. package/dist/schema-ast-v2.d.ts.map +1 -0
  68. package/dist/schema-ast-v2.js +226 -0
  69. package/dist/schema-ast-v2.js.map +1 -0
  70. package/dist/seed.d.ts +8 -0
  71. package/dist/seed.d.ts.map +1 -0
  72. package/dist/seed.js +32 -0
  73. package/dist/seed.js.map +1 -0
  74. package/dist/self-host-compose.d.ts +12 -4
  75. package/dist/self-host-compose.d.ts.map +1 -1
  76. package/dist/self-host-compose.js +146 -35
  77. package/dist/self-host-compose.js.map +1 -1
  78. package/dist/studio-admin-roles.d.ts +7 -0
  79. package/dist/studio-admin-roles.d.ts.map +1 -0
  80. package/dist/studio-admin-roles.js +14 -0
  81. package/dist/studio-admin-roles.js.map +1 -0
  82. package/dist/studio-dev-server.d.ts +22 -0
  83. package/dist/studio-dev-server.d.ts.map +1 -0
  84. package/dist/studio-dev-server.js +28 -0
  85. package/dist/studio-dev-server.js.map +1 -0
  86. package/dist/type-extractor.d.ts +3 -30
  87. package/dist/type-extractor.d.ts.map +1 -1
  88. package/dist/type-extractor.js +485 -148
  89. package/dist/type-extractor.js.map +1 -1
  90. package/dist/type-resolver.d.ts +33 -0
  91. package/dist/type-resolver.d.ts.map +1 -0
  92. package/dist/type-resolver.js +338 -0
  93. package/dist/type-resolver.js.map +1 -0
  94. package/package.json +7 -3
  95. package/src/TYPE-RESOLUTION.md +294 -0
  96. package/src/app/proxy-dev-app.ts +67 -0
  97. package/src/binary-cache.ts +20 -0
  98. package/src/commands/cloud.ts +40 -30
  99. package/src/commands/deploy.ts +3 -18
  100. package/src/commands/dev.ts +72 -69
  101. package/src/commands/diff.ts +11 -1
  102. package/src/commands/init.ts +16 -3
  103. package/src/commands/push.ts +49 -13
  104. package/src/commands/update.ts +17 -0
  105. package/src/dev-compose.ts +455 -0
  106. package/src/diff-output.ts +12 -0
  107. package/src/docker-postgres.ts +184 -27
  108. package/src/engine-client.ts +9 -4
  109. package/src/kong-config.ts +16 -1
  110. package/src/process-manager.ts +18 -1
  111. package/src/project-config.ts +34 -1
  112. package/src/runtime-routes.ts +87 -12
  113. package/src/schema-ast-v2.ts +324 -0
  114. package/src/seed.ts +43 -0
  115. package/src/self-host-compose.ts +168 -36
  116. package/src/studio-admin-roles.ts +16 -0
  117. package/src/studio-dev-server.ts +53 -0
  118. package/src/type-extractor.ts +649 -186
  119. package/src/type-resolver.ts +457 -0
  120. package/tests/config.test.ts +34 -3
  121. package/tests/docker-postgres.test.ts +39 -0
  122. package/tests/normalize-admin-config.test.ts +48 -0
  123. package/tests/proxy-dev-app.test.ts +33 -0
  124. package/tests/runtime-contract.test.ts +119 -4
  125. package/tests/studio-admin-roles.test.ts +27 -0
  126. package/tests/type-extractor.test.ts +607 -23
  127. package/tests/type-resolver.test.ts +59 -0
  128. package/tsconfig.tsbuildinfo +1 -1
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * supatype dev — start local Postgres, apply schema, run supatype-server.
3
3
  *
4
- * Supports two database providers (set in supatype.config.ts):
5
- * provider = "native"manages a native Postgres binary from the supatype cache (default when omitted)
6
- * provider = "docker"runs supatype/postgres via Docker (includes all extensions)
4
+ * Runtime provider (top-level `provider` or legacy `database.provider`):
5
+ * native — host Postgres + host server + host engine (default)
6
+ * docker — full self-host Compose stack (Kong :18473); see dev-compose.ts
7
7
  *
8
8
  * Edge functions (when a functions/ dir exists): Deno is resolved from the CDN cache
9
9
  * (auto-download on miss). Self-host/cloud Docker stacks use supatype-server in-container;
@@ -18,6 +18,7 @@ import { isAbsolute, join, relative, resolve } from "node:path"
18
18
  import { loadConfig } from "../config.js"
19
19
  import {
20
20
  functionsPathCandidatesFromProject,
21
+ resolveRuntimeProvider,
21
22
  schemaPathFromProject,
22
23
  type SupatypeProjectConfig,
23
24
  } from "../project-config.js"
@@ -32,7 +33,9 @@ import {
32
33
  postgresArchiveTag,
33
34
  } from "../binary-cache.js"
34
35
  import { ensureBinary } from "../ensure-binary.js"
36
+ import { startProxyDevApp } from "../app/proxy-dev-app.js"
35
37
  import { ProcessManager } from "../process-manager.js"
38
+ import { startStudioViteDevServer } from "../studio-dev-server.js"
36
39
  import { localStorageEnv } from "../local-storage.js"
37
40
  import {
38
41
  initdb,
@@ -42,15 +45,6 @@ import {
42
45
  isPortInUse,
43
46
  pgSpawnEnv,
44
47
  } from "../postgres-ctl.js"
45
- import {
46
- dockerPgStart,
47
- dockerPgStop,
48
- dockerPgWaitReady,
49
- dockerDbUrl,
50
- } from "../docker-postgres.js"
51
-
52
- const DEFAULT_DOCKER_IMAGE = "supatype/postgres:17-latest"
53
-
54
48
  /** Map `email.smtp` from supatype.config.ts into GOTRUE_SMTP_* for the embedded GoTrue process. */
55
49
  function gotrueSMTPFromEmailConfig(email: SupatypeProjectConfig["email"] | undefined): Record<string, string> {
56
50
  const s = email?.smtp
@@ -90,7 +84,13 @@ export function registerDev(program: Command): void {
90
84
  const projectName = config.project.name
91
85
  const serverPort = opts.port ?? String(config.server.port ?? 54321)
92
86
  const postgrestPort = String(config.server.postgrestPort ?? 3001)
93
- const provider = config.database.provider ?? "native"
87
+ const provider = resolveRuntimeProvider(config)
88
+
89
+ if (provider === "docker") {
90
+ const { runDevCompose } = await import("../dev-compose.js")
91
+ await runDevCompose(cwd, config, { watch: opts.watch !== false })
92
+ return
93
+ }
94
94
 
95
95
  // ── 2. Resolve engine + server binaries ──────────────────────────────
96
96
  console.log(`[supatype] Resolving component binaries for "${projectName}"...`)
@@ -136,22 +136,12 @@ export function registerDev(program: Command): void {
136
136
  // ── 5–7. Start Postgres ───────────────────────────────────────────────
137
137
  let dbURL: string
138
138
  let stopPostgres: () => void | Promise<void>
139
+ const pgPassword = "postgres"
139
140
  // pgBinDir is set on the native path and used to add DLL search path for
140
141
  // PostgREST on Windows (PostgREST links against libpq + SSL from MinGW).
141
142
  let pgBinDir: string | null = null
142
143
 
143
- if (provider === "docker") {
144
- console.log(
145
- "[supatype] database.provider \"docker\" — Postgres runs in Docker; engine and supatype-server stay native.",
146
- )
147
- const image = config.database.image ?? DEFAULT_DOCKER_IMAGE
148
- console.log(`[supatype] Starting Postgres via Docker (${image})...`)
149
- dockerPgStart({ image, projectName, port: pgPort })
150
- await dockerPgWaitReady(projectName, 90_000)
151
- console.log("[supatype] Postgres is ready.")
152
- dbURL = dockerDbUrl(projectName, pgPort)
153
- stopPostgres = () => dockerPgStop(projectName)
154
- } else {
144
+ {
155
145
  // native — resolve pg bin dir and manage with pg_ctl
156
146
  pgBinDir = await resolvePgBinDir(config)
157
147
  const dataDir = config.database.data_dir ?? join(stateRoot, "data")
@@ -167,7 +157,7 @@ export function registerDev(program: Command): void {
167
157
  dbURL = `postgres://postgres:postgres@127.0.0.1:${pgPort}/${projectName}?sslmode=disable`
168
158
  stopPostgres = () => pgStop(pgOpts)
169
159
 
170
- // Create project database if it doesn't exist.
160
+ // Create project database if it doesn't exist (native only).
171
161
  const psqlBin = join(pgBinDir, process.platform === "win32" ? "psql.exe" : "psql")
172
162
  const createdbBin = join(pgBinDir, process.platform === "win32" ? "createdb.exe" : "createdb")
173
163
  const pgConnArgs = ["-h", "127.0.0.1", "-p", String(pgPort), "-U", "postgres"]
@@ -225,7 +215,8 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
225
215
 
226
216
  // ── 8. GoTrue migrations (auth.users before engine studio SQL) ─────────
227
217
  console.log("[supatype] Running GoTrue migrations...")
228
- runGotrueMigrations(serverBin, authDbURL, LOCAL_JWT_SECRET)
218
+ const migrateEnv = gotrueMigrateEnv(serverPort, dbURL, LOCAL_JWT_SECRET)
219
+ runGotrueMigrations(serverBin, migrateEnv)
229
220
 
230
221
  // ── 9. Engine: apply schema ───────────────────────────────────────────
231
222
  const schemaPath = schemaPathFromProject(config, cwd)
@@ -236,9 +227,9 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
236
227
 
237
228
  const localStoragePath = config.storage?.provider !== "s3" ? join(stateRoot, "storage") : undefined
238
229
  // Native Postgres builds don't include PostGIS — skip geo fields rather than failing.
239
- const skipFieldKinds: ReadonlySet<string> = provider === "native" ? new Set(["geo", "vector"]) : new Set()
230
+ const skipFieldKinds: ReadonlySet<string> = new Set(["geo", "vector"])
240
231
 
241
- await runSchemaPush(cwd, engineBin, schemaPath, dbURL, manifestPath, adminConfigPath, localStoragePath, skipFieldKinds).catch(
232
+ await runSchemaPush(cwd, engineBin, schemaPath, dbURL, manifestPath, adminConfigPath, localStoragePath, skipFieldKinds, config).catch(
242
233
  (e: unknown) => console.error("[supatype] Initial schema push failed:", (e as Error).message),
243
234
  )
244
235
 
@@ -325,8 +316,11 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
325
316
  ? { SUPATYPE_VITE_DEV_URL: config.app.vite_dev_url.trim() }
326
317
  : {}),
327
318
  // GoTrue required fields (sensible local-dev defaults)
319
+ GOTRUE_DB_DATABASE_URL: authDbURL,
328
320
  DATABASE_URL: authDbURL,
329
321
  SUPATYPE_SQL_DATABASE_URL: dbURL,
322
+ PGSSLMODE: "disable",
323
+ GOTRUE_DB_NAMESPACE: "auth",
330
324
  GOTRUE_DB_DRIVER: "postgres",
331
325
  GOTRUE_JWT_SECRET: LOCAL_JWT_SECRET,
332
326
  GOTRUE_JWT_EXP: "3600",
@@ -400,7 +394,7 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
400
394
 
401
395
  const postgrestEnv: Record<string, string> = {
402
396
  PGRST_DB_URI: dbURL,
403
- PGRST_DB_SCHEMA: "public, supatype",
397
+ PGRST_DB_SCHEMA: "public, supatype, graphql_public",
404
398
  PGRST_DB_ANON_ROLE: "anon",
405
399
  PGRST_SERVER_PORT: postgrestPort,
406
400
  PGRST_SERVER_HOST: "127.0.0.1",
@@ -445,39 +439,19 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
445
439
 
446
440
  const studioOverride = config.overrides?.studio
447
441
  if (studioOverride) {
448
- const studioDir = resolve(cwd, studioOverride)
449
- // Run vite's JS entry directly via node — avoids .cmd/.sh wrapper spawn issues on Windows.
450
- const viteJs = join(studioDir, "node_modules", "vite", "bin", "vite.js")
451
- if (existsSync(viteJs)) {
452
- studioProc = new ProcessManager(
453
- process.execPath,
454
- [viteJs, "--port", String(studioPort), "--strictPort"],
455
- {
456
- label: "studio",
457
- pidDir,
458
- cwd: studioDir,
459
- colour: "\x1b[35m",
460
- env: {
461
- // Point the studio at the Vite dev server (same origin as the
462
- // browser) so all API requests are same-origin — CORS never fires.
463
- // Vite's dev proxy (configured via SUPATYPE_PROXY_TARGET) then
464
- // forwards those requests server-side to the actual backend.
465
- VITE_SUPATYPE_URL: `http://localhost:${studioPort}`,
466
- SUPATYPE_PROXY_TARGET: `http://localhost:${serverPort}`,
467
- // Studio is a developer tool — use service_role key to bypass
468
- // RLS so all tables and rows are visible regardless of policies.
469
- VITE_SUPATYPE_ANON_KEY: serviceRoleKey,
470
- VITE_SUPATYPE_SERVICE_ROLE_KEY: serviceRoleKey,
471
- VITE_BASE_PATH: "/",
472
- },
473
- },
474
- )
475
- studioProc.start()
476
- } else {
477
- console.warn(`[supatype] ⚠ Studio override set but vite not found at ${viteJs}. Run: pnpm install`)
478
- }
442
+ studioProc = startStudioViteDevServer({
443
+ cwd,
444
+ studioOverride,
445
+ pidDir,
446
+ serviceRoleKey,
447
+ proxyTarget: `http://localhost:${serverPort}`,
448
+ viteSupatypeUrl: `http://localhost:${studioPort}`,
449
+ })
450
+ studioProc?.start()
479
451
  }
480
452
 
453
+ const appProc = startProxyDevApp(cwd, config, pidDir)
454
+
481
455
  // ── Print status ──────────────────────────────────────────────────────
482
456
  console.log(`
483
457
  [supatype] Services running:
@@ -505,6 +479,7 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
505
479
  serverProc.stop(),
506
480
  postgrestProc?.stop(),
507
481
  studioProc?.stop(),
482
+ appProc?.stop(),
508
483
  ])
509
484
  await stopPostgres()
510
485
  process.exit(0)
@@ -527,7 +502,7 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
527
502
  debounceTimer = setTimeout(() => {
528
503
  debounceTimer = null
529
504
  console.log(`\n[supatype] Change detected in ${filename}, checking schema...`)
530
- runSchemaPush(cwd, engineBin, schemaPath, dbURL, manifestPath, adminConfigPath, localStoragePath, skipFieldKinds).catch((e: unknown) =>
505
+ runSchemaPush(cwd, engineBin, schemaPath, dbURL, manifestPath, adminConfigPath, localStoragePath, skipFieldKinds, config).catch((e: unknown) =>
531
506
  console.error("[supatype] Schema push failed:", (e as Error).message),
532
507
  )
533
508
  }, 300)
@@ -557,6 +532,7 @@ async function runSchemaPush(
557
532
  adminConfigPath?: string,
558
533
  storagePath?: string,
559
534
  skipFieldKinds?: ReadonlySet<string>,
535
+ config?: import("../project-config.js").SupatypeProjectConfig,
560
536
  ): Promise<void> {
561
537
  // Build AST JSON from schema file.
562
538
  const { loadSchemaAst } = await import("../config.js")
@@ -590,7 +566,7 @@ async function runSchemaPush(
590
566
  console.log("[supatype] Applying schema...")
591
567
  const pushResult = spawnSync(
592
568
  engineBin,
593
- ["push", "-i", astPath, "--database-url", dbURL, "--force"],
569
+ ["push", "-i", astPath, "--database-url", dbURL, "--force", "--non-interactive"],
594
570
  { cwd, stdio: "inherit", encoding: "utf8" },
595
571
  )
596
572
  if (pushResult.status !== 0) {
@@ -640,7 +616,15 @@ async function runSchemaPush(
640
616
  { cwd, stdio: "pipe", encoding: "utf8" },
641
617
  )
642
618
  if (adminResult.status === 0 && adminResult.stdout) {
643
- writeFileSync(adminConfigPath, adminResult.stdout)
619
+ const { withAdminRoles } = await import("../studio-admin-roles.js")
620
+ let admin: unknown
621
+ try {
622
+ admin = JSON.parse(adminResult.stdout) as unknown
623
+ } catch {
624
+ admin = adminResult.stdout
625
+ }
626
+ const merged = config ? withAdminRoles(admin, config) : admin
627
+ writeFileSync(adminConfigPath, `${JSON.stringify(merged, null, 2)}\n`)
644
628
  }
645
629
  }
646
630
 
@@ -960,20 +944,39 @@ function adaptUnsupportedKinds(
960
944
  // .env loader
961
945
  // ---------------------------------------------------------------------------
962
946
 
947
+ /** Minimal GoTrue env for `migrate` (matches required fields in serverEnv below). */
948
+ function gotrueMigrateEnv(
949
+ serverPort: string,
950
+ sqlDbURL: string,
951
+ jwtSecret: string,
952
+ ): Record<string, string> {
953
+ const base = `http://localhost:${serverPort}`
954
+ return {
955
+ // envconfig: gotrue + DB.DATABASE_URL → GOTRUE_DB_DATABASE_URL
956
+ GOTRUE_DB_DATABASE_URL: sqlDbURL,
957
+ DATABASE_URL: sqlDbURL,
958
+ GOTRUE_DB_DRIVER: "postgres",
959
+ GOTRUE_DB_NAMESPACE: "auth",
960
+ PGSSLMODE: "disable",
961
+ GOTRUE_JWT_SECRET: jwtSecret,
962
+ API_EXTERNAL_URL: `${base}/auth/v1`,
963
+ GOTRUE_API_HOST: "localhost",
964
+ GOTRUE_SITE_URL: base,
965
+ GOTRUE_MAILER_AUTOCONFIRM: "true",
966
+ }
967
+ }
968
+
963
969
  /** Apply GoTrue DDL (auth.users, etc.) before engine push references auth schema. */
964
970
  function runGotrueMigrations(
965
971
  serverBin: string,
966
- authDbURL: string,
967
- jwtSecret: string,
972
+ migrateEnv: Record<string, string>,
968
973
  ): void {
969
974
  const result = spawnSync(serverBin, ["migrate"], {
970
975
  stdio: "pipe",
971
976
  encoding: "utf8",
972
977
  env: {
973
978
  ...process.env,
974
- DATABASE_URL: authDbURL,
975
- GOTRUE_DB_DRIVER: "postgres",
976
- GOTRUE_JWT_SECRET: jwtSecret,
979
+ ...migrateEnv,
977
980
  },
978
981
  })
979
982
  if (result.status !== 0) {
@@ -2,6 +2,7 @@ import type { Command } from "commander"
2
2
  import { loadConfig, loadSchemaAst } from "../config.js"
3
3
  import { connectionString, schemaPathFromProject } from "../project-config.js"
4
4
  import { ensureEngine, engineRequest, type DiffResult } from "../engine-client.js"
5
+ import { printDiffWarnings } from "../diff-output.js"
5
6
 
6
7
  export function registerDiff(program: Command): void {
7
8
  program
@@ -25,6 +26,7 @@ export function registerDiff(program: Command): void {
25
26
  })
26
27
 
27
28
  const ops = diff.operations ?? []
29
+ printDiffWarnings(diff)
28
30
 
29
31
  if (ops.length === 0) {
30
32
  console.log("No changes.")
@@ -34,9 +36,17 @@ export function registerDiff(program: Command): void {
34
36
  const symbol: Record<NonNullable<DiffResult["operations"][number]["risk"]>, string> = {
35
37
  safe: "+",
36
38
  warn: "~",
39
+ cautious: "~",
37
40
  danger: "!",
41
+ destructive: "!",
42
+ }
43
+ const legend: typeof symbol = {
44
+ safe: "safe",
45
+ warn: "caution",
46
+ cautious: "caution",
47
+ danger: "DANGER",
48
+ destructive: "DANGER",
38
49
  }
39
- const legend: typeof symbol = { safe: "safe", warn: "caution", danger: "DANGER" }
40
50
 
41
51
  console.log(`\n${ops.length} change(s):\n`)
42
52
  for (const op of ops) {
@@ -134,9 +134,10 @@ function tsConfigTemplate(projectName: string, mode: "dev" | "standalone", versi
134
134
 
135
135
  export default defineConfig({
136
136
  project: { name: "${projectName}" },
137
+ provider: "native",
138
+ // provider: "docker" // full self-host Compose stack (Kong :18473)
137
139
  database: {
138
140
  provider: "native",
139
- // provider: "docker", image: "supatype/postgres:17-latest" // full extensions stack via Docker
140
141
  },
141
142
  server: {
142
143
  mode: "${mode}",
@@ -145,7 +146,7 @@ ${domainField} },
145
146
  app: {
146
147
  mode: "none",
147
148
  // mode: "static", static_dir: "./public", // supatype app add --static ./public
148
- // mode: "proxy", upstream: "http://localhost:3000",
149
+ // mode: "proxy", upstream: "http://localhost:3000", start: "dev",
149
150
  // vite_dev_url: "http://127.0.0.1:5173", // dev HMR at /_vite (when using a separate Vite server)
150
151
  },
151
152
  versions: {
@@ -163,7 +164,7 @@ ${domainField} },
163
164
  }
164
165
 
165
166
  function schemaTemplate(): string {
166
- return `import type { Model, Public, Owner, Role, SupatypeAuthUserId, Unique, Email } from "@supatype/types"
167
+ return `import type { Model, Public, Owner, Role, SupatypeAuthUserId, Unique, Email, UUID } from "@supatype/types"
167
168
 
168
169
  export type User = Model<{
169
170
  id: SupatypeAuthUserId
@@ -179,6 +180,18 @@ export type User = Model<{
179
180
  delete: Role<"admin">
180
181
  }
181
182
  }>
183
+
184
+ /** Example singleton global — editable in Studio under Settings. */
185
+ export type SiteSettings = Model<{
186
+ id: UUID
187
+ site_name: string
188
+ }, {
189
+ singleton: true
190
+ access: {
191
+ read: Public
192
+ update: Role<"admin">
193
+ }
194
+ }>
182
195
  `
183
196
  }
184
197
 
@@ -3,11 +3,15 @@ import { mkdirSync, writeFileSync } from "node:fs"
3
3
  import { createInterface } from "node:readline"
4
4
  import { join } from "node:path"
5
5
  import { loadConfig, loadSchemaAst } from "../config.js"
6
- import { connectionString, schemaPathFromProject, serverBaseUrl } from "../project-config.js"
6
+ import { connectionString, resolveRuntimeProvider, schemaPathFromProject, serverBaseUrl } from "../project-config.js"
7
+ import { isCloudLinked, pushSchemaToLinkedProject } from "./cloud.js"
7
8
  import { ensureEngine, engineRequest, type DiffResult, type Operation } from "../engine-client.js"
9
+ import { printDiffWarnings } from "../diff-output.js"
8
10
  import { signJwt } from "../jwt.js"
9
11
  import { provisionBuckets } from "../storage-provision.js"
10
12
  import { promptFirstAdminUser } from "./admin.js"
13
+ import { withAdminRoles } from "../studio-admin-roles.js"
14
+ import type { SupatypeProjectConfig } from "../project-config.js"
11
15
 
12
16
  const DEV_JWT_SECRET = "super-secret-jwt-token-with-at-least-32-characters-long"
13
17
 
@@ -21,7 +25,27 @@ export function registerPush(program: Command): void {
21
25
  .option("--connection <url>", "Database connection URL (overrides config)")
22
26
  .action(async (opts: { yes?: boolean; connection?: string }) => {
23
27
  const cwd = process.cwd()
28
+
29
+ if (isCloudLinked(cwd)) {
30
+ if (opts.connection) {
31
+ console.error("--connection is not allowed when linked to a cloud project (credentials stay server-side).")
32
+ process.exit(1)
33
+ }
34
+ await pushSchemaToLinkedProject(cwd, { force: opts.yes ?? true })
35
+ return
36
+ }
37
+
24
38
  const config = loadConfig(cwd)
39
+
40
+ // Docker provider: the compose Postgres isn't published to the host, so
41
+ // apply the schema through the in-compose schema-engine (unless the user
42
+ // gave an explicit --connection to a reachable database).
43
+ if (!opts.connection && resolveRuntimeProvider(config) === "docker") {
44
+ const { pushSchemaDocker } = await import("../dev-compose.js")
45
+ await pushSchemaDocker(cwd, config)
46
+ return
47
+ }
48
+
25
49
  const connection = opts.connection ?? connectionString(config)
26
50
 
27
51
  await ensureEngine()
@@ -37,18 +61,21 @@ export function registerPush(program: Command): void {
37
61
  })
38
62
 
39
63
  const ops = diff.operations ?? []
64
+ printDiffWarnings(diff)
40
65
 
41
66
  if (ops.length === 0) {
42
67
  console.log(
43
- "Schema matches the database (no DDL). Refreshing admin metadata and local Studio config...",
68
+ "Schema matches the database (no DDL). Syncing Studio metadata...",
44
69
  )
45
70
  } else {
46
71
  printDiff(ops)
47
72
 
48
- const destructive = ops.filter((o) => o.risk === "danger")
49
- if (destructive.length > 0 && !opts.yes) {
73
+ const risky = ops.filter(
74
+ (o) => o.risk === "cautious" || o.risk === "destructive" || o.risk === "warn" || o.risk === "danger",
75
+ )
76
+ if (risky.length > 0 && !opts.yes) {
50
77
  const confirmed = await confirm(
51
- `\n${destructive.length} destructive operation(s) above. Proceed? [y/N] `,
78
+ `\n${risky.length} risky operation(s) above (type changes or data loss). Proceed? [y/N] `,
52
79
  )
53
80
  if (!confirmed) {
54
81
  console.log("Aborted.")
@@ -71,14 +98,14 @@ export function registerPush(program: Command): void {
71
98
  if (pushResult.status === "up_to_date") {
72
99
  console.log(
73
100
  pushResult.admin_refreshed
74
- ? "Admin config updated on the latest migration record (no SQL applied)."
101
+ ? "Database schema unchanged Studio metadata synced."
75
102
  : "Schema is up to date.",
76
103
  )
77
104
  } else {
78
105
  console.log(pushResult.message ?? "Migration applied.")
79
106
  }
80
107
 
81
- await writeLocalAdminConfig(ast)
108
+ await writeLocalAdminConfig(ast, config)
82
109
 
83
110
  // After a DDL migration, check if this is the first push and offer to create an
84
111
  // admin user if none exist (Gap Appendices task 48).
@@ -130,20 +157,29 @@ export function registerPush(program: Command): void {
130
157
  console.log(genResult.message ?? "Types generated.")
131
158
  }
132
159
 
133
- console.log("\nDone.")
160
+ const studioBase = baseUrl?.replace(/\/$/, "") ?? ""
161
+ if (studioBase) {
162
+ console.log(`\nStudio: ${studioBase}/studio/ — sign in with the admin user you created.`)
163
+ } else {
164
+ console.log("\nDone.")
165
+ }
134
166
  })
135
167
  }
136
168
 
137
169
  function printDiff(ops: Operation[]): void {
138
- const symbol: Record<NonNullable<Operation["risk"]>, string> = {
170
+ const symbol: Record<string, string> = {
139
171
  safe: "+",
140
172
  warn: "~",
173
+ cautious: "~",
141
174
  danger: "!",
175
+ destructive: "!",
142
176
  }
143
177
  console.log(`\n${ops.length} change(s) planned:\n`)
144
178
  for (const op of ops) {
145
- const s = op.risk ? symbol[op.risk] : "?"
146
- console.log(` [${s}] ${formatOperation(op)}`)
179
+ const riskKey = op.risk ?? "safe"
180
+ const s = symbol[riskKey] ?? "?"
181
+ const label = op.warning ?? op.description ?? formatOperation(op)
182
+ console.log(` [${s}] ${label}`)
147
183
  }
148
184
  }
149
185
 
@@ -179,10 +215,10 @@ async function confirm(prompt: string): Promise<boolean> {
179
215
  }
180
216
 
181
217
  /** Write `.supatype/admin-config.json` for local Studio (same layout as `supatype dev`). */
182
- async function writeLocalAdminConfig(ast: unknown): Promise<void> {
218
+ async function writeLocalAdminConfig(ast: unknown, config: SupatypeProjectConfig): Promise<void> {
183
219
  const cwd = process.cwd()
184
220
  const dir = join(cwd, ".supatype")
185
221
  mkdirSync(dir, { recursive: true })
186
- const admin = await engineRequest<unknown>("/admin", { ast })
222
+ const admin = withAdminRoles(await engineRequest<unknown>("/admin", { ast }), config)
187
223
  writeFileSync(join(dir, "admin-config.json"), `${JSON.stringify(admin, null, 2)}\n`)
188
224
  }
@@ -7,6 +7,8 @@ import type { Command } from "commander"
7
7
  import { existsSync, readFileSync, writeFileSync } from "node:fs"
8
8
  import { basename, resolve } from "node:path"
9
9
  import { loadConfig } from "../config.js"
10
+ import { resolveRuntimeProvider } from "../project-config.js"
11
+ import { runDockerCompose, writeSelfHostCompose } from "../self-host-compose.js"
10
12
  import { download, currentPlatform, fetchAllLatestVersions, type Component } from "../binary-cache.js"
11
13
 
12
14
  const CONFIG_CANDIDATES = ["supatype.config.ts", "supatype.config.js", "supatype.config.mjs"]
@@ -29,6 +31,21 @@ export function registerUpdate(program: Command): void {
29
31
  .action(async (opts: { check: boolean }) => {
30
32
  const cwd = process.cwd()
31
33
  const config = loadConfig(cwd)
34
+ const provider = resolveRuntimeProvider(config)
35
+
36
+ if (provider === "docker") {
37
+ if (opts.check) {
38
+ console.log("Docker provider: run without --check to pull compose images (supatype self-host compose pull).")
39
+ return
40
+ }
41
+ const paths = writeSelfHostCompose(cwd, config, { devLocal: true })
42
+ console.log("Pulling self-host compose images...")
43
+ const status = runDockerCompose(paths.composePath, ["pull"], cwd)
44
+ if (status !== 0) process.exit(status)
45
+ console.log("Compose images updated.")
46
+ return
47
+ }
48
+
32
49
  const platform = currentPlatform()
33
50
 
34
51
  const components: Component[] = ["engine", "server", "postgres", "deno"]