@supatype/cli 0.1.13 → 0.2.0

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 (96) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +151 -143
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/dist/cli-version-embedded.d.ts.map +1 -1
  5. package/dist/cli-version-embedded.js +1 -1
  6. package/dist/cli-version-embedded.js.map +1 -1
  7. package/dist/commands/admin.d.ts +5 -5
  8. package/dist/commands/admin.d.ts.map +1 -1
  9. package/dist/commands/admin.js +10 -10
  10. package/dist/commands/admin.js.map +1 -1
  11. package/dist/commands/dev.js +51 -51
  12. package/dist/commands/dev.js.map +1 -1
  13. package/dist/commands/init.js +1 -1
  14. package/dist/commands/logs.d.ts +14 -1
  15. package/dist/commands/logs.d.ts.map +1 -1
  16. package/dist/commands/logs.js +33 -37
  17. package/dist/commands/logs.js.map +1 -1
  18. package/dist/commands/push.d.ts.map +1 -1
  19. package/dist/commands/push.js +3 -0
  20. package/dist/commands/push.js.map +1 -1
  21. package/dist/commands/status.d.ts +1 -1
  22. package/dist/commands/status.d.ts.map +1 -1
  23. package/dist/commands/status.js +33 -64
  24. package/dist/commands/status.js.map +1 -1
  25. package/dist/compose-services.d.ts +59 -0
  26. package/dist/compose-services.d.ts.map +1 -0
  27. package/dist/compose-services.js +160 -0
  28. package/dist/compose-services.js.map +1 -0
  29. package/dist/config.d.ts +1 -1
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/dev-compose.d.ts.map +1 -1
  32. package/dist/dev-compose.js +123 -20
  33. package/dist/dev-compose.js.map +1 -1
  34. package/dist/dev-ports.d.ts +24 -0
  35. package/dist/dev-ports.d.ts.map +1 -1
  36. package/dist/dev-ports.js +37 -0
  37. package/dist/dev-ports.js.map +1 -1
  38. package/dist/docker-postgres.d.ts +1 -1
  39. package/dist/docker-postgres.d.ts.map +1 -1
  40. package/dist/docker-postgres.js +2 -2
  41. package/dist/docker-postgres.js.map +1 -1
  42. package/dist/engine-floor.d.ts +50 -0
  43. package/dist/engine-floor.d.ts.map +1 -0
  44. package/dist/engine-floor.js +80 -0
  45. package/dist/engine-floor.js.map +1 -0
  46. package/dist/link.d.ts +1 -1
  47. package/dist/link.d.ts.map +1 -1
  48. package/dist/pgbouncer-userlist.d.ts +1 -1
  49. package/dist/pgbouncer-userlist.d.ts.map +1 -1
  50. package/dist/pgbouncer-userlist.js +1 -1
  51. package/dist/pgbouncer-userlist.js.map +1 -1
  52. package/dist/postgres-ctl.d.ts +5 -1
  53. package/dist/postgres-ctl.d.ts.map +1 -1
  54. package/dist/postgres-ctl.js +43 -4
  55. package/dist/postgres-ctl.js.map +1 -1
  56. package/dist/project-config.d.ts +4 -4
  57. package/dist/resolve-target.d.ts +1 -1
  58. package/dist/resolve-target.d.ts.map +1 -1
  59. package/dist/self-host-compose.d.ts.map +1 -1
  60. package/dist/self-host-compose.js +41 -18
  61. package/dist/self-host-compose.js.map +1 -1
  62. package/dist/studio-dev-server.d.ts +8 -1
  63. package/dist/studio-dev-server.d.ts.map +1 -1
  64. package/dist/studio-dev-server.js +1 -2
  65. package/dist/studio-dev-server.js.map +1 -1
  66. package/package.json +2 -2
  67. package/src/cli-version-embedded.ts +1 -1
  68. package/src/commands/admin.ts +10 -10
  69. package/src/commands/dev.ts +51 -51
  70. package/src/commands/init.ts +1 -1
  71. package/src/commands/logs.ts +34 -37
  72. package/src/commands/push.ts +3 -0
  73. package/src/commands/status.ts +34 -70
  74. package/src/compose-services.ts +174 -0
  75. package/src/config.ts +1 -1
  76. package/src/dev-compose.ts +130 -20
  77. package/src/dev-ports.ts +48 -0
  78. package/src/docker-postgres.ts +2 -2
  79. package/src/engine-floor.ts +113 -0
  80. package/src/link.ts +1 -1
  81. package/src/pgbouncer-userlist.ts +1 -1
  82. package/src/postgres-ctl.ts +44 -4
  83. package/src/project-config.ts +4 -4
  84. package/src/resolve-target.ts +1 -1
  85. package/src/self-host-compose.ts +41 -18
  86. package/src/studio-dev-server.ts +9 -3
  87. package/tests/admin-ensure.test.ts +10 -10
  88. package/tests/compose-parsable-defaults.test.ts +60 -0
  89. package/tests/compose-services.test.ts +144 -0
  90. package/tests/dev-ports-owner.test.ts +40 -0
  91. package/tests/engine-floor.test.ts +102 -0
  92. package/tests/external-database-compose.test.ts +3 -3
  93. package/tests/port-in-use.test.ts +58 -0
  94. package/tests/readiness-poll.test.ts +84 -0
  95. package/tests/runtime-contract.test.ts +1 -1
  96. package/tsconfig.tsbuildinfo +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supatype/cli",
3
- "version": "0.1.13",
3
+ "version": "0.2.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Supatype CLI: schema push, introspect, generate",
6
6
  "type": "module",
@@ -41,7 +41,7 @@
41
41
  "@types/pg": "^8",
42
42
  "@types/react": "^18.3.18",
43
43
  "vitest": "^4.0.18",
44
- "@supatype/types": "0.1.13"
44
+ "@supatype/types": "0.2.0"
45
45
  },
46
46
  "scripts": {
47
47
  "build": "tsc --project tsconfig.json && node --input-type=module -e \"import{cpSync,mkdirSync}from'node:fs';const a='dist/assets';mkdirSync(a,{recursive:true});cpSync('assets/supatype-logo-wordmark.ascii.txt',a+'/supatype-logo-wordmark.ascii.txt')\"",
@@ -9,4 +9,4 @@
9
9
  // Annotated `string`, not left to inference: once a release stamps a value here, the
10
10
  // inferred literal type makes the `!== ""` test in cliPackageVersion() a compile error
11
11
  // (TS2367, no overlap), so the build would only fail during a release.
12
- export const EMBEDDED_CLI_VERSION: string = "0.1.13"
12
+ export const EMBEDDED_CLI_VERSION: string = "0.2.0"
@@ -52,18 +52,18 @@ function assertStudioRole(role: string): void {
52
52
  process.exit(1)
53
53
  }
54
54
 
55
- /** GoTrue scopes users to the nil instance id in single-tenant/self-host mode. */
56
- export const GOTRUE_NIL_INSTANCE_ID = "00000000-0000-0000-0000-000000000000"
55
+ /** The auth service scopes users to the nil instance id in single-tenant/self-host mode. */
56
+ export const SUPATYPE_NIL_INSTANCE_ID = "00000000-0000-0000-0000-000000000000"
57
57
 
58
- /** Audience claim used by GoTrue when looking up users (matches GOTRUE_JWT_AUD in compose). */
59
- export function gotrueJwtAud(cwd: string): string {
60
- return readEnvValue(cwd, "GOTRUE_JWT_AUD", "authenticated")
58
+ /** Audience claim used when looking up users (matches SUPATYPE_JWT_AUD in compose). */
59
+ export function authJwtAud(cwd: string): string {
60
+ return readEnvValue(cwd, "SUPATYPE_JWT_AUD", "authenticated")
61
61
  }
62
62
 
63
63
  type DbQuery = (sql: string, params?: unknown[]) => Promise<QueryResult>
64
64
  type AuthConfirmedColumn = "email_confirmed_at" | "confirmed_at"
65
65
 
66
- /** Prefer GoTrue's column when migrated; fall back to postgres init `confirmed_at`. */
66
+ /** Prefer the migrated column when present; fall back to postgres init `confirmed_at`. */
67
67
  export async function resolveAuthConfirmedAtColumn(query: DbQuery): Promise<AuthConfirmedColumn> {
68
68
  const result = await query(
69
69
  `SELECT column_name
@@ -464,14 +464,14 @@ async function createAdminUser(
464
464
  : (db as DbQuery)
465
465
 
466
466
  const normalized = email.toLowerCase()
467
- const aud = opts.aud ?? (opts.cwd ? gotrueJwtAud(opts.cwd) : "authenticated")
467
+ const aud = opts.aud ?? (opts.cwd ? authJwtAud(opts.cwd) : "authenticated")
468
468
  const existing = await query(
469
469
  `SELECT id FROM auth.users
470
470
  WHERE instance_id = $1::uuid
471
471
  AND LOWER(email) = $2
472
472
  AND aud = $3
473
473
  AND is_sso_user = false`,
474
- [GOTRUE_NIL_INSTANCE_ID, normalized, aud],
474
+ [SUPATYPE_NIL_INSTANCE_ID, normalized, aud],
475
475
  )
476
476
  if (existing.rows.length > 0) {
477
477
  throw new Error(`User with email "${email}" already exists.`)
@@ -507,7 +507,7 @@ async function createAdminUser(
507
507
  false, false,
508
508
  now(), now()
509
509
  ) RETURNING id, email`,
510
- [GOTRUE_NIL_INSTANCE_ID, aud, normalized, passwordHash, appMetadata, userMetadata],
510
+ [SUPATYPE_NIL_INSTANCE_ID, aud, normalized, passwordHash, appMetadata, userMetadata],
511
511
  )
512
512
 
513
513
  const user = result.rows[0] as { id: string; email: string }
@@ -601,7 +601,7 @@ async function hasAdminUsers(query: DbQuery): Promise<boolean> {
601
601
  if (!(await studioMembersTableExists(query))) return false
602
602
 
603
603
  // Inner JOIN on purpose, unlike `list-users`: a membership held by a cloud
604
- // account cannot log in to a self-hosted GoTrue, so a project exported from
604
+ // account cannot log in to a self-hosted auth service, so a project exported from
605
605
  // cloud with only those grants genuinely still needs a first admin.
606
606
  const adminCount = await query(
607
607
  `SELECT COUNT(*)::int as count
@@ -65,21 +65,21 @@ import {
65
65
  isPortInUse,
66
66
  pgSpawnEnv,
67
67
  } from "../postgres-ctl.js"
68
- /** Map `email.smtp` from supatype.config.ts into GOTRUE_SMTP_* for the embedded GoTrue process. */
69
- function gotrueSMTPFromEmailConfig(email: SupatypeProjectConfig["email"] | undefined): Record<string, string> {
68
+ /** Map `email.smtp` from supatype.config.ts into SUPATYPE_SMTP_* for the embedded auth service. */
69
+ function authSMTPFromEmailConfig(email: SupatypeProjectConfig["email"] | undefined): Record<string, string> {
70
70
  const s = email?.smtp
71
71
  if (!s) return {}
72
72
  const out: Record<string, string> = {}
73
73
  const host = s.host?.trim()
74
- if (host) out.GOTRUE_SMTP_HOST = host
75
- if (s.port !== undefined) out.GOTRUE_SMTP_PORT = String(s.port)
74
+ if (host) out.SUPATYPE_SMTP_HOST = host
75
+ if (s.port !== undefined) out.SUPATYPE_SMTP_PORT = String(s.port)
76
76
  const user = s.user?.trim()
77
- if (user) out.GOTRUE_SMTP_USER = user
78
- if (s.pass !== undefined && s.pass !== "") out.GOTRUE_SMTP_PASS = s.pass
77
+ if (user) out.SUPATYPE_SMTP_USER = user
78
+ if (s.pass !== undefined && s.pass !== "") out.SUPATYPE_SMTP_PASS = s.pass
79
79
  const admin = s.admin_email?.trim()
80
- if (admin) out.GOTRUE_SMTP_ADMIN_EMAIL = admin
80
+ if (admin) out.SUPATYPE_SMTP_ADMIN_EMAIL = admin
81
81
  const sender = s.sender_name?.trim()
82
- if (sender) out.GOTRUE_SMTP_SENDER_NAME = sender
82
+ if (sender) out.SUPATYPE_SMTP_SENDER_NAME = sender
83
83
  return out
84
84
  }
85
85
 
@@ -287,10 +287,10 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
287
287
  ? `${dbURL}&search_path=auth`
288
288
  : `${dbURL}?search_path=auth`
289
289
 
290
- // ── 8. GoTrue migrations (auth.users before engine studio SQL) ─────────
291
- console.log("[supatype] Running GoTrue migrations...")
292
- const migrateEnv = gotrueMigrateEnv(serverPort, dbURL, LOCAL_JWT_SECRET)
293
- runGotrueMigrations(serverBin, migrateEnv)
290
+ // ── 8. auth migrations (auth.users before engine studio SQL) ─────────
291
+ console.log("[supatype] Running auth migrations...")
292
+ const migrateEnv = authMigrateEnv(serverPort, dbURL, LOCAL_JWT_SECRET)
293
+ runAuthMigrations(serverBin, migrateEnv)
294
294
 
295
295
  // ── 9. Engine: apply schema ───────────────────────────────────────────
296
296
  const schemaPath = schemaPathFromProject(config, cwd)
@@ -348,7 +348,7 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
348
348
  }
349
349
  }
350
350
 
351
- // Matches GOTRUE_HOOK_SEND_EMAIL_SECRETS symmetric format (dev only). Override via .env.
351
+ // Matches SUPATYPE_HOOK_SEND_EMAIL_SECRETS symmetric format (dev only). Override via .env.
352
352
  const LOCAL_SEND_EMAIL_HOOK_SECRETS =
353
353
  "v1,whsec_abcdefghijklmnopqrstuvwxyz01234567"
354
354
  const now = Math.floor(Date.now() / 1000)
@@ -390,7 +390,7 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
390
390
 
391
391
 
392
392
  const emailProvider = config.email?.provider ?? "console"
393
- const gotrueMailerProvider =
393
+ const authMailerProvider =
394
394
  emailProvider === "console"
395
395
  ? "console"
396
396
  : emailProvider === "resend"
@@ -430,47 +430,47 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
430
430
  ...(config.app.vite_dev_url !== undefined && config.app.vite_dev_url.trim() !== ""
431
431
  ? { SUPATYPE_VITE_DEV_URL: config.app.vite_dev_url.trim() }
432
432
  : {}),
433
- // GoTrue required fields (sensible local-dev defaults)
434
- GOTRUE_DB_DATABASE_URL: authDbURL,
433
+ // Auth service required fields (sensible local-dev defaults)
434
+ SUPATYPE_DB_DATABASE_URL: authDbURL,
435
435
  DATABASE_URL: authDbURL,
436
436
  SUPATYPE_SQL_DATABASE_URL: dbURL,
437
437
  PGSSLMODE: "disable",
438
- GOTRUE_DB_NAMESPACE: "auth",
439
- GOTRUE_DB_DRIVER: "postgres",
440
- GOTRUE_JWT_SECRET: LOCAL_JWT_SECRET,
441
- GOTRUE_JWT_EXP: "3600",
442
- GOTRUE_JWT_AUD: "authenticated",
443
- GOTRUE_JWT_ADMIN_ROLES: "supatype_admin,service_role",
444
- API_EXTERNAL_URL: `http://localhost:${serverPort}/auth/v1`,
445
- GOTRUE_API_HOST: "localhost",
446
- GOTRUE_SITE_URL: `http://localhost:${serverPort}`,
447
- GOTRUE_MAILER_MAILER_PROVIDER: gotrueMailerProvider,
448
- GOTRUE_MAILER_AUTOCONFIRM: "true",
449
- GOTRUE_LOG_LEVEL: "info",
450
- GOTRUE_DISABLE_SIGNUP: "false",
438
+ SUPATYPE_DB_NAMESPACE: "auth",
439
+ SUPATYPE_DB_DRIVER: "postgres",
440
+ SUPATYPE_JWT_SECRET: LOCAL_JWT_SECRET,
441
+ SUPATYPE_JWT_EXP: "3600",
442
+ SUPATYPE_JWT_AUD: "authenticated",
443
+ SUPATYPE_JWT_ADMIN_ROLES: "supatype_admin,service_role",
444
+ SUPATYPE_API_EXTERNAL_URL: `http://localhost:${serverPort}/auth/v1`,
445
+ SUPATYPE_API_HOST: "localhost",
446
+ SUPATYPE_SITE_URL: `http://localhost:${serverPort}`,
447
+ SUPATYPE_MAILER_MAILER_PROVIDER: authMailerProvider,
448
+ SUPATYPE_MAILER_AUTOCONFIRM: "true",
449
+ SUPATYPE_LOG_LEVEL: "info",
450
+ SUPATYPE_DISABLE_SIGNUP: "false",
451
451
  ...(config.email?.resend_api_key !== undefined && config.email.resend_api_key !== ""
452
452
  ? { RESEND_API_KEY: config.email.resend_api_key }
453
453
  : {}),
454
- ...(gotrueMailerProvider === "resend" &&
454
+ ...(authMailerProvider === "resend" &&
455
455
  config.email?.resend_from !== undefined &&
456
456
  config.email.resend_from.trim() !== ""
457
457
  ? { RESEND_FROM: config.email.resend_from.trim() }
458
458
  : {}),
459
- ...(gotrueMailerProvider === "ses" &&
459
+ ...(authMailerProvider === "ses" &&
460
460
  config.email?.ses_from !== undefined &&
461
461
  config.email.ses_from.trim() !== ""
462
462
  ? { SES_FROM: config.email.ses_from.trim() }
463
463
  : {}),
464
- ...(gotrueMailerProvider === "smtp" ? gotrueSMTPFromEmailConfig(config.email) : {}),
464
+ ...(authMailerProvider === "smtp" ? authSMTPFromEmailConfig(config.email) : {}),
465
465
  ...(config.email?.send_email_hook === true
466
466
  ? {
467
- GOTRUE_HOOK_SEND_EMAIL_ENABLED: "true",
468
- GOTRUE_HOOK_SEND_EMAIL_URI:
467
+ SUPATYPE_HOOK_SEND_EMAIL_ENABLED: "true",
468
+ SUPATYPE_HOOK_SEND_EMAIL_URI:
469
469
  config.email?.send_email_hook_uri !== undefined &&
470
470
  config.email.send_email_hook_uri.trim() !== ""
471
471
  ? config.email.send_email_hook_uri.trim()
472
472
  : `http://127.0.0.1:${serverPort}/internal/v0hooks/send-email`,
473
- GOTRUE_HOOK_SEND_EMAIL_SECRETS:
473
+ SUPATYPE_HOOK_SEND_EMAIL_SECRETS:
474
474
  config.email?.send_email_hook_secrets !== undefined &&
475
475
  config.email.send_email_hook_secrets.trim() !== ""
476
476
  ? config.email.send_email_hook_secrets.trim()
@@ -526,7 +526,7 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
526
526
  PGRST_DB_ANON_ROLE: "anon",
527
527
  PGRST_SERVER_PORT: postgrestPort,
528
528
  PGRST_SERVER_HOST: "127.0.0.1",
529
- PGRST_JWT_SECRET: serverEnv["GOTRUE_JWT_SECRET"] ?? "",
529
+ PGRST_JWT_SECRET: serverEnv["SUPATYPE_JWT_SECRET"] ?? "",
530
530
  PGRST_LOG_LEVEL: "warn",
531
531
  // On Windows, PostgREST (MinGW/GHC binary) needs libpq.dll and
532
532
  // OpenSSL DLLs. Prepend a Postgres bin dir which bundles these
@@ -571,7 +571,7 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
571
571
  cwd,
572
572
  studioOverride,
573
573
  pidDir,
574
- serviceRoleKey,
574
+ anonKey,
575
575
  proxyTarget: `http://localhost:${serverPort}`,
576
576
  viteSupatypeUrl: `http://localhost:${studioPort}`,
577
577
  })
@@ -1079,30 +1079,30 @@ function adaptUnsupportedKinds(
1079
1079
  // .env loader
1080
1080
  // ---------------------------------------------------------------------------
1081
1081
 
1082
- /** Minimal GoTrue env for `migrate` (matches required fields in serverEnv below). */
1083
- function gotrueMigrateEnv(
1082
+ /** Minimal auth env for `migrate` (matches required fields in serverEnv below). */
1083
+ function authMigrateEnv(
1084
1084
  serverPort: string,
1085
1085
  sqlDbURL: string,
1086
1086
  jwtSecret: string,
1087
1087
  ): Record<string, string> {
1088
1088
  const base = `http://localhost:${serverPort}`
1089
1089
  return {
1090
- // envconfig: gotrue + DB.DATABASE_URL → GOTRUE_DB_DATABASE_URL
1091
- GOTRUE_DB_DATABASE_URL: sqlDbURL,
1090
+ // envconfig: the service prefix + DB.DATABASE_URL → SUPATYPE_DB_DATABASE_URL
1091
+ SUPATYPE_DB_DATABASE_URL: sqlDbURL,
1092
1092
  DATABASE_URL: sqlDbURL,
1093
- GOTRUE_DB_DRIVER: "postgres",
1094
- GOTRUE_DB_NAMESPACE: "auth",
1093
+ SUPATYPE_DB_DRIVER: "postgres",
1094
+ SUPATYPE_DB_NAMESPACE: "auth",
1095
1095
  PGSSLMODE: "disable",
1096
- GOTRUE_JWT_SECRET: jwtSecret,
1097
- API_EXTERNAL_URL: `${base}/auth/v1`,
1098
- GOTRUE_API_HOST: "localhost",
1099
- GOTRUE_SITE_URL: base,
1100
- GOTRUE_MAILER_AUTOCONFIRM: "true",
1096
+ SUPATYPE_JWT_SECRET: jwtSecret,
1097
+ SUPATYPE_API_EXTERNAL_URL: `${base}/auth/v1`,
1098
+ SUPATYPE_API_HOST: "localhost",
1099
+ SUPATYPE_SITE_URL: base,
1100
+ SUPATYPE_MAILER_AUTOCONFIRM: "true",
1101
1101
  }
1102
1102
  }
1103
1103
 
1104
- /** Apply GoTrue DDL (auth.users, etc.) before engine push references auth schema. */
1105
- function runGotrueMigrations(
1104
+ /** Apply auth DDL (auth.users, etc.) before engine push references auth schema. */
1105
+ function runAuthMigrations(
1106
1106
  serverBin: string,
1107
1107
  migrateEnv: Record<string, string>,
1108
1108
  ): void {
@@ -1117,7 +1117,7 @@ function runGotrueMigrations(
1117
1117
  if (result.status !== 0) {
1118
1118
  const detail = (result.stderr ?? result.stdout ?? "").trim()
1119
1119
  throw new Error(
1120
- `GoTrue migrations failed (exit ${result.status ?? "unknown"})` +
1120
+ `auth migrations failed (exit ${result.status ?? "unknown"})` +
1121
1121
  (detail ? `:\n${detail}` : ""),
1122
1122
  )
1123
1123
  }
@@ -1226,7 +1226,7 @@ JWT_SECRET=${randomBytes(48).toString("base64url")}
1226
1226
  ANON_KEY=
1227
1227
  SERVICE_ROLE_KEY=`)
1228
1228
 
1229
- sections.push(`# Site URL (used by GoTrue for email redirects)
1229
+ sections.push(`# Site URL (used for email redirects)
1230
1230
  SITE_URL=http://localhost:3000`)
1231
1231
 
1232
1232
  if (opts.provider === "docker" && opts.kongPort !== undefined) {
@@ -1,56 +1,53 @@
1
1
  /**
2
- * supatype logs: tail aggregated logs from local Docker containers.
2
+ * supatype logs: tail logs from the local stack.
3
3
  */
4
4
  import type { Command } from "commander"
5
5
  import { spawn } from "node:child_process"
6
- import { error } from "../ui/messages.js"
6
+ import { composeArgs, composeContext, composeServiceNames } from "../compose-services.js"
7
+ import { error, info } from "../ui/messages.js"
7
8
 
8
- const SERVICES = ["postgres", "postgrest", "gotrue", "kong", "minio", "realtime", "studio"]
9
+ /**
10
+ * logsArgs builds the compose invocation.
11
+ *
12
+ * Compose is asked for the logs rather than `docker logs <name>`, because the
13
+ * container names are Compose's to choose: the generated file sets no
14
+ * `container_name`, so a container is `<project>-<service>-<n>` and guessing at
15
+ * that name is what left this command tailing containers that never existed.
16
+ */
17
+ export function logsArgs(
18
+ base: string[],
19
+ opts: { service?: string; since?: string; follow?: boolean },
20
+ ): string[] {
21
+ const args = [...base, "logs", "--tail", "100"]
22
+ if (opts.follow) args.push("--follow")
23
+ if (opts.since) args.push("--since", opts.since)
24
+ if (opts.service) args.push(opts.service)
25
+ return args
26
+ }
9
27
 
10
28
  export function registerLogs(program: Command): void {
11
29
  program
12
30
  .command("logs")
13
- .description("Tail aggregated logs from local dev services")
14
- .option("--service <name>", "Filter to a specific service")
15
- .option("--since <duration>", "Show logs since duration (e.g., 5m, 1h)", "5m")
31
+ .description("Tail logs from the local stack")
32
+ .option("--service <name>", "Filter to a single service")
33
+ .option("--since <duration>", "Show logs since duration (e.g. 5m, 1h)", "5m")
16
34
  .option("-f, --follow", "Follow log output", true)
17
35
  .action((opts: { service?: string; since?: string; follow?: boolean }) => {
18
- const services = opts.service
19
- ? [`supatype-${opts.service}`]
20
- : SERVICES.map((s) => `supatype-${s}`)
21
-
22
- if (opts.service && !SERVICES.includes(opts.service)) {
23
- error(`Unknown service: ${opts.service}`)
24
- error(`Available: ${SERVICES.join(", ")}`)
36
+ const ctx = composeContext(process.cwd())
37
+ if (!ctx) {
38
+ error("No generated compose file here.")
39
+ info("Run `supatype self-host compose up` to create the stack.")
25
40
  process.exit(1)
26
41
  }
27
42
 
28
- const args = ["compose", "logs"]
29
- if (opts.follow) args.push("-f")
30
- if (opts.since) args.push("--since", opts.since)
31
- args.push("--tail", "100")
32
-
33
- // If filtering by service, use docker logs for that container
34
- if (opts.service) {
35
- const containerArgs = ["logs"]
36
- if (opts.follow) containerArgs.push("-f")
37
- if (opts.since) containerArgs.push("--since", opts.since)
38
- containerArgs.push("--tail", "100")
39
- containerArgs.push(`supatype-${opts.service}`)
40
-
41
- const child = spawn("docker", containerArgs, { stdio: "inherit" })
42
- child.on("error", () => {
43
- error("Docker not found. Ensure Docker is installed and running.")
44
- process.exit(1)
45
- })
46
- return
43
+ const available = composeServiceNames(ctx)
44
+ if (opts.service && available.length > 0 && !available.includes(opts.service)) {
45
+ error(`Unknown service: ${opts.service}`)
46
+ error(`Available: ${available.join(", ")}`)
47
+ process.exit(1)
47
48
  }
48
49
 
49
- // Aggregated logs via docker compose
50
- const child = spawn("docker", args, {
51
- stdio: "inherit",
52
- cwd: process.cwd(),
53
- })
50
+ const child = spawn("docker", logsArgs(composeArgs(ctx), opts), { stdio: "inherit" })
54
51
  child.on("error", () => {
55
52
  error("Docker not found. Ensure Docker is installed and running.")
56
53
  process.exit(1)
@@ -18,6 +18,8 @@ import {
18
18
  serverBaseUrl,
19
19
  } from "../project-config.js"
20
20
  import { ensureEngine, engineRequest, type DiffResult } from "../engine-client.js"
21
+ import { assertEngineSupportsSchema } from "../engine-floor.js"
22
+ import { pinnedVersion } from "../binary-cache.js"
21
23
  import { printDiffOperations, printDiffWarnings } from "../diff-output.js"
22
24
  import { signJwt } from "../jwt.js"
23
25
  import { provisionBucketsFromAst } from "../storage-provision.js"
@@ -72,6 +74,7 @@ export function registerPush(program: Command): void {
72
74
  const ast = loadSchemaAst(schemaPathFromProject(config, cwd), cwd)
73
75
  assertModelHooksResolve(cwd, config, ast)
74
76
  assertServiceRoleGrantsResolve(cwd, config)
77
+ assertEngineSupportsSchema(ast, pinnedVersion("engine", config))
75
78
 
76
79
  const linked = loadProjectLink(cwd)
77
80
  const useDirect = opts.direct || opts.local || Boolean(opts.connection)
@@ -1,27 +1,19 @@
1
1
  /**
2
- * supatype status: show linked target or local dev stack state.
2
+ * supatype status: show linked target or local stack state.
3
3
  */
4
4
  import type { Command } from "commander"
5
- import { spawnSync } from "node:child_process"
6
5
  import { existsSync } from "node:fs"
7
6
  import { resolve } from "node:path"
8
- import { LOCAL_KONG_HOST_PORT, localKongBaseUrl } from "../local-gateway.js"
7
+ import { composeContext, composeServices } from "../compose-services.js"
8
+ import { localKongBaseUrl } from "../local-gateway.js"
9
9
  import { loadLocalEnvironment, loadProjectLink } from "../link.js"
10
10
  import { resolveTarget, targetStatus } from "../resolve-target.js"
11
11
  import { error, info, plain, warn } from "../ui/messages.js"
12
12
 
13
- interface ServiceStatus {
14
- name: string
15
- container: string
16
- status: "running" | "stopped" | "error"
17
- port?: number
18
- uptime?: string
19
- }
20
-
21
13
  export function registerStatus(program: Command): void {
22
14
  program
23
15
  .command("status")
24
- .description("Show linked target status or local dev services")
16
+ .description("Show linked target status or local stack services")
25
17
  .option("--env <name>", "Target environment when linked")
26
18
  .action(async (opts: { env?: string }) => {
27
19
  const cwd = process.cwd()
@@ -73,47 +65,48 @@ async function printLinkedStatus(target: ReturnType<typeof resolveTarget>): Prom
73
65
  }
74
66
  }
75
67
 
68
+ /**
69
+ * printLocalStackStatus reports what the stack is actually running.
70
+ *
71
+ * It asks Docker rather than checking a list of container names held here. The
72
+ * list this replaced named containers the compose file does not create, and
73
+ * omitted every one it does, so the command described an eight-service stack
74
+ * that has not existed since those services moved in-process behind
75
+ * supatype-server.
76
+ */
76
77
  function printLocalStackStatus(cwd: string): void {
77
- const localEnv = loadLocalEnvironment(cwd)
78
- const kongPort = localEnv?.kongPort ?? LOCAL_KONG_HOST_PORT
78
+ const ctx = composeContext(cwd)
79
+ if (!ctx) {
80
+ plain("Supatype local stack\n")
81
+ info("No generated compose file here.")
82
+ info("Run `supatype self-host compose up` to create the stack, or `supatype dev`,")
83
+ info("which runs the development stack outside Docker.")
84
+ return
85
+ }
79
86
 
80
- const services: ServiceStatus[] = [
81
- { name: "Postgres", container: "supatype-postgres", port: 5432 },
82
- { name: "PostgREST", container: "supatype-postgrest", port: 3000 },
83
- { name: "GoTrue", container: "supatype-gotrue", port: 9999 },
84
- { name: "Kong", container: "supatype-kong", port: kongPort },
85
- { name: "Control plane", container: "supatype-control-plane" },
86
- { name: "MinIO", container: "supatype-minio", port: 9000 },
87
- { name: "Realtime", container: "supatype-realtime", port: 4000 },
88
- { name: "Studio", container: "supatype-studio", port: 3100 },
89
- ].map((svc) => {
90
- const status = getContainerStatus(svc.container)
91
- const uptime = getContainerUptime(svc.container)
92
- return { ...svc, status, ...(uptime !== undefined && { uptime }) }
93
- })
87
+ const services = composeServices(ctx)
88
+ plain(`Supatype local stack (${ctx.projectName})\n`)
94
89
 
95
- plain("Supatype Local Development Stack\n")
90
+ if (services.length === 0) {
91
+ info("No containers yet. Start the stack with `supatype self-host compose up`.")
92
+ return
93
+ }
96
94
 
97
- const maxName = Math.max(...services.map((s) => s.name.length))
95
+ const width = Math.max(...services.map((svc) => svc.service.length))
98
96
  for (const svc of services) {
99
- const icon = svc.status === "running" ? "●" : svc.status === "stopped" ? "○" : "✕"
100
- const status = svc.status.padEnd(8)
101
- const port = svc.port ? `:${svc.port}` : ""
102
- const uptime = svc.uptime ? ` (${svc.uptime})` : ""
103
- plain(` ${icon} ${svc.name.padEnd(maxName)} ${status} ${port}${uptime}`)
97
+ const icon = svc.state === "running" ? "●" : svc.state === "exited" ? "○" : "✕"
98
+ const ports = svc.ports ? ` ${svc.ports}` : ""
99
+ plain(` ${icon} ${svc.service.padEnd(width)} ${svc.state.padEnd(10)}${ports}`)
104
100
  }
105
101
 
106
- const running = services.filter((s) => s.status === "running")
102
+ const running = services.filter((svc) => svc.state === "running")
107
103
  plain(`\n${running.length}/${services.length} services running`)
108
104
 
109
105
  if (running.length > 0) {
110
- const apiUrl = localEnv?.apiUrl ?? localKongBaseUrl()
111
- plain(`\nAPI URL: ${apiUrl}`)
112
- plain(`Studio: http://localhost:3100`)
106
+ const localEnv = loadLocalEnvironment(cwd)
107
+ plain(`\nAPI URL: ${localEnv?.apiUrl ?? localKongBaseUrl()}`)
113
108
  if (localEnv?.databaseUrl) {
114
109
  plain(`Database: ${localEnv.databaseUrl}`)
115
- } else {
116
- plain(`Database: postgresql://supatype_admin:postgres@localhost:5432/postgres`)
117
110
  }
118
111
  }
119
112
 
@@ -122,32 +115,3 @@ function printLocalStackStatus(cwd: string): void {
122
115
  info("Link remote ops: supatype link --url <api> --token $SERVICE_ROLE_KEY")
123
116
  }
124
117
  }
125
-
126
- function getContainerStatus(name: string): "running" | "stopped" | "error" {
127
- const result = spawnSync("docker", ["inspect", "--format", "{{.State.Status}}", name], {
128
- timeout: 5000,
129
- })
130
- const status = result.stdout?.toString().trim()
131
- if (status === "running") return "running"
132
- if (result.status !== 0) return "stopped"
133
- return "error"
134
- }
135
-
136
- function getContainerUptime(name: string): string | undefined {
137
- const result = spawnSync("docker", ["inspect", "--format", "{{.State.StartedAt}}", name], {
138
- timeout: 5000,
139
- })
140
- if (result.status !== 0) return undefined
141
- const startedAt = result.stdout?.toString().trim()
142
- if (!startedAt) return undefined
143
-
144
- const started = new Date(startedAt)
145
- const now = new Date()
146
- const diffMs = now.getTime() - started.getTime()
147
- if (diffMs < 0) return undefined
148
-
149
- const hours = Math.floor(diffMs / (1000 * 60 * 60))
150
- const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60))
151
- if (hours > 0) return `${hours}h ${minutes}m`
152
- return `${minutes}m`
153
- }