@supatype/cli 0.1.2 → 0.1.4

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 (71) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +111 -108
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/dist/commands/admin.d.ts +12 -0
  5. package/dist/commands/admin.d.ts.map +1 -1
  6. package/dist/commands/admin.js +97 -23
  7. package/dist/commands/admin.js.map +1 -1
  8. package/dist/commands/init.d.ts.map +1 -1
  9. package/dist/commands/init.js +287 -182
  10. package/dist/commands/init.js.map +1 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +12 -1
  13. package/dist/config.js.map +1 -1
  14. package/dist/dev-compose.js +3 -3
  15. package/dist/dev-compose.js.map +1 -1
  16. package/dist/dev-log-bus.d.ts +1 -1
  17. package/dist/dev-log-bus.d.ts.map +1 -1
  18. package/dist/dev-log-bus.js +5 -3
  19. package/dist/dev-log-bus.js.map +1 -1
  20. package/dist/dev-ready-panel.d.ts +2 -0
  21. package/dist/dev-ready-panel.d.ts.map +1 -1
  22. package/dist/dev-ready-panel.js +18 -0
  23. package/dist/dev-ready-panel.js.map +1 -1
  24. package/dist/init-project-detect.d.ts +14 -0
  25. package/dist/init-project-detect.d.ts.map +1 -0
  26. package/dist/init-project-detect.js +89 -0
  27. package/dist/init-project-detect.js.map +1 -0
  28. package/dist/type-extractor.js +44 -0
  29. package/dist/type-extractor.js.map +1 -1
  30. package/dist/ui/dev/DevDashboard.d.ts.map +1 -1
  31. package/dist/ui/dev/DevDashboard.js +11 -4
  32. package/dist/ui/dev/DevDashboard.js.map +1 -1
  33. package/dist/ui/dev/DevReadyPanelView.d.ts +2 -1
  34. package/dist/ui/dev/DevReadyPanelView.d.ts.map +1 -1
  35. package/dist/ui/dev/DevReadyPanelView.js +21 -2
  36. package/dist/ui/dev/DevReadyPanelView.js.map +1 -1
  37. package/dist/ui/flows/FlowApp.d.ts.map +1 -1
  38. package/dist/ui/flows/FlowApp.js +3 -1
  39. package/dist/ui/flows/FlowApp.js.map +1 -1
  40. package/dist/ui/flows/prompt-fields.d.ts.map +1 -1
  41. package/dist/ui/flows/prompt-fields.js +9 -5
  42. package/dist/ui/flows/prompt-fields.js.map +1 -1
  43. package/dist/ui/runtime/run-clack-flow.d.ts.map +1 -1
  44. package/dist/ui/runtime/run-clack-flow.js +15 -3
  45. package/dist/ui/runtime/run-clack-flow.js.map +1 -1
  46. package/dist/ui/runtime/stdin-after-ink.d.ts +3 -0
  47. package/dist/ui/runtime/stdin-after-ink.d.ts.map +1 -0
  48. package/dist/ui/runtime/stdin-after-ink.js +16 -0
  49. package/dist/ui/runtime/stdin-after-ink.js.map +1 -0
  50. package/package.json +1 -1
  51. package/src/commands/admin.ts +135 -26
  52. package/src/commands/init.ts +360 -213
  53. package/src/config.ts +13 -1
  54. package/src/dev-compose.ts +4 -4
  55. package/src/dev-log-bus.ts +5 -3
  56. package/src/dev-ready-panel.ts +17 -0
  57. package/src/init-project-detect.ts +99 -0
  58. package/src/type-extractor.ts +57 -0
  59. package/src/ui/dev/DevDashboard.tsx +14 -4
  60. package/src/ui/dev/DevReadyPanelView.tsx +76 -9
  61. package/src/ui/flows/FlowApp.tsx +3 -1
  62. package/src/ui/flows/prompt-fields.tsx +13 -7
  63. package/src/ui/runtime/run-clack-flow.tsx +16 -3
  64. package/src/ui/runtime/stdin-after-ink.ts +18 -0
  65. package/tests/admin-ensure.test.ts +80 -0
  66. package/tests/config.test.ts +19 -0
  67. package/tests/dev-ready-panel.test.ts +18 -1
  68. package/tests/init-project-detect.test.ts +53 -0
  69. package/tests/stdin-after-ink.test.ts +8 -0
  70. package/tests/type-extractor.test.ts +101 -0
  71. package/tsconfig.tsbuildinfo +1 -1
@@ -4,6 +4,7 @@ import { render } from "ink";
4
4
  import { isInteractive } from "../interactive.js";
5
5
  import { createClackApi } from "../clack-api.js";
6
6
  import { FlowApp } from "../flows/FlowApp.js";
7
+ import { restoreStdinAfterInk } from "./stdin-after-ink.js";
7
8
  function RunClackFlowRoot({ run, onComplete, onError }) {
8
9
  const started = useRef(false);
9
10
  return (_jsx(FlowApp, { bind: (controller) => {
@@ -22,13 +23,24 @@ export async function runClackFlow(run) {
22
23
  }
23
24
  return new Promise((resolve, reject) => {
24
25
  const instance = render(_jsx(RunClackFlowRoot, { run: run, onComplete: (value) => {
25
- instance.unmount();
26
+ teardownInk(instance);
26
27
  resolve(value);
27
28
  }, onError: (err) => {
28
- instance.unmount();
29
+ teardownInk(instance);
29
30
  reject(err);
30
- } }));
31
+ } }), { patchConsole: false });
31
32
  });
32
33
  }
34
+ function teardownInk(instance) {
35
+ try {
36
+ instance.clear();
37
+ }
38
+ catch {
39
+ // ignore — terminal may already be reset
40
+ }
41
+ instance.unmount();
42
+ instance.cleanup();
43
+ restoreStdinAfterInk();
44
+ }
33
45
  export { CLACK_CANCEL } from "./cancel.js";
34
46
  //# sourceMappingURL=run-clack-flow.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"run-clack-flow.js","sourceRoot":"","sources":["../../../src/ui/runtime/run-clack-flow.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,cAAc,EAAiB,MAAM,iBAAiB,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAQ7C,SAAS,gBAAgB,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,EAAyB;IAC3E,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAE7B,OAAO,CACL,KAAC,OAAO,IACN,IAAI,EAAE,CAAC,UAAU,EAAE,EAAE;YACnB,IAAI,OAAO,CAAC,OAAO;gBAAE,OAAM;YAC3B,OAAO,CAAC,OAAO,GAAG,IAAI,CAAA;YACtB,KAAK,GAAG,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;iBACjC,IAAI,CAAC,UAAU,CAAC;iBAChB,KAAK,CAAC,OAAO,CAAC,CAAA;QACnB,CAAC,GACD,CACH,CAAA;AACH,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,YAAY,CAAI,GAAkC;IACtE,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAA;IACzD,CAAC;IAED,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,QAAQ,GAAG,MAAM,CACrB,KAAC,gBAAgB,IACf,GAAG,EAAE,GAA0C,EAC/C,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;gBACpB,QAAQ,CAAC,OAAO,EAAE,CAAA;gBAClB,OAAO,CAAC,KAAU,CAAC,CAAA;YACrB,CAAC,EACD,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACf,QAAQ,CAAC,OAAO,EAAE,CAAA;gBAClB,MAAM,CAAC,GAAG,CAAC,CAAA;YACb,CAAC,GACD,CACH,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"run-clack-flow.js","sourceRoot":"","sources":["../../../src/ui/runtime/run-clack-flow.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AACrC,OAAO,EAAE,MAAM,EAAiB,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,cAAc,EAAiB,MAAM,iBAAiB,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAQ3D,SAAS,gBAAgB,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,EAAyB;IAC3E,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAE7B,OAAO,CACL,KAAC,OAAO,IACN,IAAI,EAAE,CAAC,UAAU,EAAE,EAAE;YACnB,IAAI,OAAO,CAAC,OAAO;gBAAE,OAAM;YAC3B,OAAO,CAAC,OAAO,GAAG,IAAI,CAAA;YACtB,KAAK,GAAG,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;iBACjC,IAAI,CAAC,UAAU,CAAC;iBAChB,KAAK,CAAC,OAAO,CAAC,CAAA;QACnB,CAAC,GACD,CACH,CAAA;AACH,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,YAAY,CAAI,GAAkC;IACtE,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAA;IACzD,CAAC;IAED,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,QAAQ,GAAG,MAAM,CACrB,KAAC,gBAAgB,IACf,GAAG,EAAE,GAA0C,EAC/C,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;gBACpB,WAAW,CAAC,QAAQ,CAAC,CAAA;gBACrB,OAAO,CAAC,KAAU,CAAC,CAAA;YACrB,CAAC,EACD,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACf,WAAW,CAAC,QAAQ,CAAC,CAAA;gBACrB,MAAM,CAAC,GAAG,CAAC,CAAA;YACb,CAAC,GACD,EACF,EAAE,YAAY,EAAE,KAAK,EAAE,CACxB,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,QAAkB;IACrC,IAAI,CAAC;QACH,QAAQ,CAAC,KAAK,EAAE,CAAA;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,yCAAyC;IAC3C,CAAC;IACD,QAAQ,CAAC,OAAO,EAAE,CAAA;IAClB,QAAQ,CAAC,OAAO,EAAE,CAAA;IAClB,oBAAoB,EAAE,CAAA;AACxB,CAAC;AAED,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,3 @@
1
+ /** Restore stdin after Ink unmount so one-shot commands (init, link) can exit cleanly. */
2
+ export declare function restoreStdinAfterInk(): void;
3
+ //# sourceMappingURL=stdin-after-ink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdin-after-ink.d.ts","sourceRoot":"","sources":["../../../src/ui/runtime/stdin-after-ink.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,wBAAgB,oBAAoB,IAAI,IAAI,CAgB3C"}
@@ -0,0 +1,16 @@
1
+ /** Restore stdin after Ink unmount so one-shot commands (init, link) can exit cleanly. */
2
+ export function restoreStdinAfterInk() {
3
+ if (!process.stdin.isTTY)
4
+ return;
5
+ const stdin = process.stdin;
6
+ try {
7
+ if (stdin.isRaw)
8
+ stdin.setRawMode?.(false);
9
+ }
10
+ catch {
11
+ // ignore — stdin may already be restored
12
+ }
13
+ stdin.resume();
14
+ process.stdout.write("\n");
15
+ }
16
+ //# sourceMappingURL=stdin-after-ink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdin-after-ink.js","sourceRoot":"","sources":["../../../src/ui/runtime/stdin-after-ink.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,MAAM,UAAU,oBAAoB;IAClC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK;QAAE,OAAM;IAEhC,MAAM,KAAK,GAAG,OAAO,CAAC,KAGrB,CAAA;IAED,IAAI,CAAC;QACH,IAAI,KAAK,CAAC,KAAK;YAAE,KAAK,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAA;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,yCAAyC;IAC3C,CAAC;IAED,KAAK,CAAC,MAAM,EAAE,CAAA;IACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;AAC5B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supatype/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Supatype CLI — schema push, introspect, generate",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,6 +17,8 @@ import {
17
17
  } from "../project-config.js"
18
18
  import { readEnvValue, upsertEnvFile } from "../env-file.js"
19
19
  import { hasEngineOverride } from "../binary-cache.js"
20
+ import { readDevSessionLock } from "../dev-session-lock.js"
21
+ import { composeProjectName } from "../self-host-compose.js"
20
22
  import { confirm as uiConfirm } from "../ui/confirm.js"
21
23
  import { error, info, plain } from "../ui/messages.js"
22
24
  import { promptPassword, promptText } from "../ui/prompts.js"
@@ -30,6 +32,34 @@ export const ADMIN_PASSWORD_ENV = "SUPATYPE_ADMIN_PASSWORD"
30
32
 
31
33
  const BCRYPT_ROUNDS = 10
32
34
 
35
+ /** GoTrue scopes users to the nil instance id in single-tenant/self-host mode. */
36
+ export const GOTRUE_NIL_INSTANCE_ID = "00000000-0000-0000-0000-000000000000"
37
+
38
+ /** Audience claim used by GoTrue when looking up users (matches GOTRUE_JWT_AUD in compose). */
39
+ export function gotrueJwtAud(cwd: string): string {
40
+ return readEnvValue(cwd, "GOTRUE_JWT_AUD", "authenticated")
41
+ }
42
+
43
+ type DbQuery = (sql: string, params?: unknown[]) => Promise<QueryResult>
44
+ type AuthConfirmedColumn = "email_confirmed_at" | "confirmed_at"
45
+
46
+ /** Prefer GoTrue's column when migrated; fall back to postgres init `confirmed_at`. */
47
+ export async function resolveAuthConfirmedAtColumn(query: DbQuery): Promise<AuthConfirmedColumn> {
48
+ const result = await query(
49
+ `SELECT column_name
50
+ FROM information_schema.columns
51
+ WHERE table_schema = 'auth'
52
+ AND table_name = 'users'
53
+ AND column_name IN ('email_confirmed_at', 'confirmed_at')
54
+ ORDER BY CASE column_name WHEN 'email_confirmed_at' THEN 0 ELSE 1 END
55
+ LIMIT 1`,
56
+ )
57
+ const row = result.rows[0] as { column_name?: string; value?: string } | undefined
58
+ const name = row?.column_name ?? row?.value
59
+ if (name === "email_confirmed_at" || name === "confirmed_at") return name
60
+ return "confirmed_at"
61
+ }
62
+
33
63
  export interface EnsureFirstAdminOptions {
34
64
  email?: string
35
65
  password?: string
@@ -39,8 +69,6 @@ export interface EnsureFirstAdminOptions {
39
69
  compose?: { project: string; composePath: string }
40
70
  }
41
71
 
42
- type DbQuery = (sql: string, params?: unknown[]) => Promise<QueryResult>
43
-
44
72
  export function registerAdmin(program: Command): void {
45
73
  const adminCmd = program
46
74
  .command("admin")
@@ -81,20 +109,33 @@ export function registerAdmin(program: Command): void {
81
109
 
82
110
  info(`Creating admin user: ${email} (role: ${role})...`)
83
111
 
84
- const pg = await importPg()
85
- const pool = new pg.Pool({ connectionString: connection, max: 2 })
112
+ const compose =
113
+ opts.connection === undefined ? resolveDockerComposeContext(cwd, config) : null
86
114
 
87
115
  try {
88
- await ensureAuthUsersTable(pool)
89
- await createAdminUser(pool, email, password, role)
116
+ if (compose) {
117
+ await createAdminUser(
118
+ (sql, params) => composeExecQuery(cwd, compose, sql, params),
119
+ email,
120
+ password,
121
+ role,
122
+ { cwd },
123
+ )
124
+ } else {
125
+ const pg = await importPg()
126
+ const pool = new pg.Pool({ connectionString: connection, max: 2 })
127
+ try {
128
+ await ensureAuthUsersTable(pool)
129
+ await createAdminUser(pool, email, password, role, { cwd })
130
+ } finally {
131
+ await pool.end()
132
+ }
133
+ }
90
134
  info("This user can now log in to the admin panel at /admin")
91
135
  } catch (err) {
92
- const message =
93
- err instanceof Error ? err.message : "Unknown error"
136
+ const message = err instanceof Error ? err.message : "Unknown error"
94
137
  error(`Failed to create admin user: ${message}`)
95
138
  process.exit(1)
96
- } finally {
97
- await pool.end()
98
139
  }
99
140
  },
100
141
  )
@@ -252,8 +293,9 @@ export async function ensureFirstAdminUserForProject(
252
293
  (sql, params) => composeExecQuery(root, merged.compose!, sql, params),
253
294
  merged,
254
295
  )
255
- } catch {
256
- // Non-fatal
296
+ } catch (err) {
297
+ const message = err instanceof Error ? err.message : String(err)
298
+ error(`Could not ensure first admin user: ${message}`)
257
299
  }
258
300
  return
259
301
  }
@@ -288,7 +330,10 @@ async function ensureFirstAdminWithQuery(
288
330
 
289
331
  const role = options.role ?? "admin"
290
332
  try {
291
- await createAdminUser(query, credentials.email, credentials.password, role, { quiet: true })
333
+ await createAdminUser(query, credentials.email, credentials.password, role, {
334
+ quiet: true,
335
+ cwd,
336
+ })
292
337
  } catch (err) {
293
338
  const message = err instanceof Error ? err.message : String(err)
294
339
  error(`Failed to create admin user: ${message}`)
@@ -365,7 +410,7 @@ async function createAdminUser(
365
410
  email: string,
366
411
  password: string,
367
412
  role: string,
368
- opts: { quiet?: boolean } = {},
413
+ opts: { quiet?: boolean; aud?: string; cwd?: string } = {},
369
414
  ): Promise<{ id: string; email: string }> {
370
415
  const query: DbQuery =
371
416
  typeof (db as Pool).query === "function"
@@ -373,9 +418,15 @@ async function createAdminUser(
373
418
  : (db as DbQuery)
374
419
 
375
420
  const normalized = email.toLowerCase()
376
- const existing = await query(`SELECT id FROM auth.users WHERE email = $1`, [
377
- normalized,
378
- ])
421
+ const aud = opts.aud ?? (opts.cwd ? gotrueJwtAud(opts.cwd) : "authenticated")
422
+ const existing = await query(
423
+ `SELECT id FROM auth.users
424
+ WHERE instance_id = $1::uuid
425
+ AND LOWER(email) = $2
426
+ AND aud = $3
427
+ AND is_sso_user = false`,
428
+ [GOTRUE_NIL_INSTANCE_ID, normalized, aud],
429
+ )
379
430
  if (existing.rows.length > 0) {
380
431
  throw new Error(`User with email "${email}" already exists.`)
381
432
  }
@@ -387,18 +438,27 @@ async function createAdminUser(
387
438
  providers: ["email"],
388
439
  })
389
440
  const userMetadata = JSON.stringify({})
441
+ const confirmedCol = await resolveAuthConfirmedAtColumn(query)
390
442
 
391
443
  const result = await query(
392
444
  `INSERT INTO auth.users (
393
- id, email, encrypted_password, role, aud,
445
+ instance_id, id, aud, role, email, encrypted_password,
394
446
  raw_app_meta_data, raw_user_meta_data,
395
- email_confirmed_at, created_at, updated_at
447
+ ${confirmedCol},
448
+ confirmation_token, recovery_token,
449
+ email_change_token_new, email_change, email_change_token_current,
450
+ phone_change, phone_change_token, reauthentication_token,
451
+ is_sso_user, is_anonymous,
452
+ created_at, updated_at
396
453
  ) VALUES (
397
- gen_random_uuid(), $1, $2, 'authenticated', 'authenticated',
398
- $3::jsonb, $4::jsonb,
399
- now(), now(), now()
454
+ $1::uuid, gen_random_uuid(), $2, 'authenticated', $3, $4,
455
+ $5::jsonb, $6::jsonb,
456
+ now(),
457
+ '', '', '', '', '', '', '', '',
458
+ false, false,
459
+ now(), now()
400
460
  ) RETURNING id, email`,
401
- [normalized, passwordHash, appMetadata, userMetadata],
461
+ [GOTRUE_NIL_INSTANCE_ID, aud, normalized, passwordHash, appMetadata, userMetadata],
402
462
  )
403
463
 
404
464
  const user = result.rows[0] as { id: string; email: string }
@@ -417,7 +477,7 @@ async function ensureAuthUsersTable(pool: Pool): Promise<void> {
417
477
 
418
478
  CREATE TABLE IF NOT EXISTS auth.users (
419
479
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
420
- instance_id UUID,
480
+ instance_id UUID DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
421
481
  aud TEXT DEFAULT 'authenticated',
422
482
  role TEXT DEFAULT 'authenticated',
423
483
  email TEXT UNIQUE,
@@ -430,7 +490,13 @@ async function ensureAuthUsersTable(pool: Pool): Promise<void> {
430
490
  confirmation_token TEXT DEFAULT '',
431
491
  recovery_token TEXT DEFAULT '',
432
492
  email_change_token_new TEXT DEFAULT '',
433
- email_change TEXT DEFAULT ''
493
+ email_change_token_current TEXT DEFAULT '',
494
+ email_change TEXT DEFAULT '',
495
+ phone_change TEXT DEFAULT '',
496
+ phone_change_token TEXT DEFAULT '',
497
+ reauthentication_token TEXT DEFAULT '',
498
+ is_sso_user BOOLEAN NOT NULL DEFAULT false,
499
+ is_anonymous BOOLEAN NOT NULL DEFAULT false
434
500
  );
435
501
  `)
436
502
  }
@@ -455,6 +521,33 @@ async function hasAdminUsers(query: DbQuery): Promise<boolean> {
455
521
  return count > 0
456
522
  }
457
523
 
524
+ /** Postgres password for compose `exec psql` (db is not published to the host). */
525
+ export function composePostgresPassword(cwd: string): string {
526
+ return readEnvValue(cwd, "POSTGRES_PASSWORD", "postgres")
527
+ }
528
+
529
+ function resolveDockerComposeContext(
530
+ cwd: string,
531
+ config: SupatypeProjectConfig,
532
+ ): { project: string; composePath: string } | null {
533
+ if (resolveRuntimeProvider(config) !== "docker" || hasEngineOverride(config)) {
534
+ return null
535
+ }
536
+
537
+ const session = readDevSessionLock(cwd)
538
+ if (session?.composeProject && session.composePath && existsSync(session.composePath)) {
539
+ return { project: session.composeProject, composePath: session.composePath }
540
+ }
541
+
542
+ const composePath = resolve(cwd, ".supatype/self-host/docker-compose.yml")
543
+ if (!existsSync(composePath)) return null
544
+
545
+ return {
546
+ project: composeProjectName(config.project?.name ?? "project"),
547
+ composePath,
548
+ }
549
+ }
550
+
458
551
  function composeExecQuery(
459
552
  cwd: string,
460
553
  compose: { project: string; composePath: string },
@@ -463,6 +556,7 @@ function composeExecQuery(
463
556
  ): Promise<QueryResult> {
464
557
  const db = readEnvValue(cwd, "POSTGRES_DB", "supatype")
465
558
  const user = readEnvValue(cwd, "POSTGRES_USER", "supatype_admin")
559
+ const pgPassword = composePostgresPassword(cwd)
466
560
  const envFile = join(cwd, ".env")
467
561
  const composeDir = dirname(compose.composePath)
468
562
  const args = [
@@ -477,7 +571,20 @@ function composeExecQuery(
477
571
  if (existsSync(envFile)) args.push("--env-file", envFile)
478
572
 
479
573
  if (params.length === 0) {
480
- args.push("exec", "-T", "db", "psql", "-U", user, "-d", db, "-tAc", sql)
574
+ args.push(
575
+ "exec",
576
+ "-T",
577
+ "-e",
578
+ `PGPASSWORD=${pgPassword}`,
579
+ "db",
580
+ "psql",
581
+ "-U",
582
+ user,
583
+ "-d",
584
+ db,
585
+ "-tAc",
586
+ sql,
587
+ )
481
588
  const result = spawnSync("docker", args, { cwd: composeDir, encoding: "utf8" })
482
589
  if (result.status !== 0) {
483
590
  throw new Error((result.stderr ?? result.stdout ?? "compose psql failed").trim())
@@ -504,6 +611,8 @@ function composeExecQuery(
504
611
  args.push(
505
612
  "exec",
506
613
  "-T",
614
+ "-e",
615
+ `PGPASSWORD=${pgPassword}`,
507
616
  "db",
508
617
  "psql",
509
618
  "-U",