@supatype/cli 0.1.0-alpha.6 → 0.1.0-alpha.8
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 +1 -1
- package/.turbo/turbo-test.log +208 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/dist/app/proxy-dev-app.d.ts +13 -0
- package/dist/app/proxy-dev-app.d.ts.map +1 -0
- package/dist/app/proxy-dev-app.js +53 -0
- package/dist/app/proxy-dev-app.js.map +1 -0
- package/dist/app-config.d.ts +7 -0
- package/dist/app-config.d.ts.map +1 -0
- package/dist/app-config.js +113 -0
- package/dist/app-config.js.map +1 -0
- package/dist/augmentation-generator.d.ts +2 -0
- package/dist/augmentation-generator.d.ts.map +1 -0
- package/dist/augmentation-generator.js +111 -0
- package/dist/augmentation-generator.js.map +1 -0
- package/dist/binary-cache.d.ts +94 -0
- package/dist/binary-cache.d.ts.map +1 -0
- package/dist/binary-cache.js +669 -0
- package/dist/binary-cache.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +13 -7
- package/dist/cli.js.map +1 -1
- package/dist/commands/admin.d.ts.map +1 -1
- package/dist/commands/admin.js +4 -3
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/app.d.ts.map +1 -1
- package/dist/commands/app.js +56 -209
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/cache.d.ts +6 -0
- package/dist/commands/cache.d.ts.map +1 -0
- package/dist/commands/cache.js +105 -0
- package/dist/commands/cache.js.map +1 -0
- package/dist/commands/cloud.d.ts +20 -0
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +50 -52
- package/dist/commands/cloud.js.map +1 -1
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +47 -54
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts +2 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +79 -52
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +11 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +759 -385
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +30 -15
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/engine.d.ts +1 -3
- package/dist/commands/engine.d.ts.map +1 -1
- package/dist/commands/engine.js +13 -85
- package/dist/commands/engine.js.map +1 -1
- package/dist/commands/functions.d.ts.map +1 -1
- package/dist/commands/functions.js +92 -105
- package/dist/commands/functions.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +22 -12
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +137 -410
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/migrate-from-v1.d.ts +5 -0
- package/dist/commands/migrate-from-v1.d.ts.map +1 -0
- package/dist/commands/migrate-from-v1.js +125 -0
- package/dist/commands/migrate-from-v1.js.map +1 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +27 -23
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/pg.d.ts +8 -0
- package/dist/commands/pg.d.ts.map +1 -0
- package/dist/commands/pg.js +102 -0
- package/dist/commands/pg.js.map +1 -0
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +5 -66
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +128 -38
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/seed.d.ts +2 -0
- package/dist/commands/seed.d.ts.map +1 -1
- package/dist/commands/seed.js +44 -11
- package/dist/commands/seed.js.map +1 -1
- package/dist/commands/self-host.d.ts +7 -1
- package/dist/commands/self-host.d.ts.map +1 -1
- package/dist/commands/self-host.js +272 -758
- package/dist/commands/self-host.js.map +1 -1
- package/dist/commands/self-update.d.ts +9 -0
- package/dist/commands/self-update.d.ts.map +1 -0
- package/dist/commands/self-update.js +33 -0
- package/dist/commands/self-update.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +4 -3
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/types.d.ts +3 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +62 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +93 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/components.d.ts +5 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +3 -0
- package/dist/components.js.map +1 -0
- package/dist/config.d.ts +10 -51
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +101 -33
- package/dist/config.js.map +1 -1
- package/dist/dev-compose.d.ts +17 -0
- package/dist/dev-compose.d.ts.map +1 -0
- package/dist/dev-compose.js +374 -0
- package/dist/dev-compose.js.map +1 -0
- package/dist/diff-output.d.ts +4 -0
- package/dist/diff-output.d.ts.map +1 -0
- package/dist/diff-output.js +12 -0
- package/dist/diff-output.js.map +1 -0
- package/dist/docker-postgres.d.ts +57 -0
- package/dist/docker-postgres.d.ts.map +1 -0
- package/dist/docker-postgres.js +208 -0
- package/dist/docker-postgres.js.map +1 -0
- package/dist/engine-client.d.ts +69 -0
- package/dist/engine-client.d.ts.map +1 -0
- package/dist/engine-client.js +157 -0
- package/dist/engine-client.js.map +1 -0
- package/dist/ensure-binary.d.ts +7 -0
- package/dist/ensure-binary.d.ts.map +1 -0
- package/dist/ensure-binary.js +17 -0
- package/dist/ensure-binary.js.map +1 -0
- package/dist/functions-router-gen.d.ts +14 -0
- package/dist/functions-router-gen.d.ts.map +1 -0
- package/dist/functions-router-gen.js +199 -0
- package/dist/functions-router-gen.js.map +1 -0
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/kong-config.d.ts +25 -0
- package/dist/kong-config.d.ts.map +1 -0
- package/dist/kong-config.js +71 -0
- package/dist/kong-config.js.map +1 -0
- package/dist/local-gateway.d.ts +7 -0
- package/dist/local-gateway.d.ts.map +1 -0
- package/dist/local-gateway.js +9 -0
- package/dist/local-gateway.js.map +1 -0
- package/dist/local-storage.d.ts +8 -0
- package/dist/local-storage.d.ts.map +1 -0
- package/dist/local-storage.js +14 -0
- package/dist/local-storage.js.map +1 -0
- package/dist/pgbouncer-userlist.d.ts +5 -0
- package/dist/pgbouncer-userlist.d.ts.map +1 -0
- package/dist/pgbouncer-userlist.js +14 -0
- package/dist/pgbouncer-userlist.js.map +1 -0
- package/dist/postgres-ctl.d.ts +44 -0
- package/dist/postgres-ctl.d.ts.map +1 -0
- package/dist/postgres-ctl.js +137 -0
- package/dist/postgres-ctl.js.map +1 -0
- package/dist/process-manager.d.ts +43 -0
- package/dist/process-manager.d.ts.map +1 -0
- package/dist/process-manager.js +135 -0
- package/dist/process-manager.js.map +1 -0
- package/dist/project-config.d.ts +235 -0
- package/dist/project-config.d.ts.map +1 -0
- package/dist/project-config.js +160 -0
- package/dist/project-config.js.map +1 -0
- package/dist/pull-utils.d.ts +15 -0
- package/dist/pull-utils.d.ts.map +1 -1
- package/dist/pull-utils.js +12 -0
- package/dist/pull-utils.js.map +1 -1
- package/dist/release-pins.d.ts +7 -0
- package/dist/release-pins.d.ts.map +1 -0
- package/dist/release-pins.js +27 -0
- package/dist/release-pins.js.map +1 -0
- package/dist/release-public-key.d.ts +8 -0
- package/dist/release-public-key.d.ts.map +1 -0
- package/dist/release-public-key.js +13 -0
- package/dist/release-public-key.js.map +1 -0
- package/dist/runtime-routes.d.ts +34 -0
- package/dist/runtime-routes.d.ts.map +1 -0
- package/dist/runtime-routes.js +252 -0
- package/dist/runtime-routes.js.map +1 -0
- package/dist/schema-ast-v2.d.ts +127 -0
- package/dist/schema-ast-v2.d.ts.map +1 -0
- package/dist/schema-ast-v2.js +226 -0
- package/dist/schema-ast-v2.js.map +1 -0
- package/dist/scripts/postinstall.d.ts +5 -6
- package/dist/scripts/postinstall.d.ts.map +1 -1
- package/dist/scripts/postinstall.js +36 -20
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/self-host-compose.d.ts +22 -0
- package/dist/self-host-compose.d.ts.map +1 -0
- package/dist/self-host-compose.js +347 -0
- package/dist/self-host-compose.js.map +1 -0
- package/dist/storage-provision.d.ts +24 -0
- package/dist/storage-provision.d.ts.map +1 -0
- package/dist/storage-provision.js +44 -0
- package/dist/storage-provision.js.map +1 -0
- package/dist/studio-admin-roles.d.ts +7 -0
- package/dist/studio-admin-roles.d.ts.map +1 -0
- package/dist/studio-admin-roles.js +14 -0
- package/dist/studio-admin-roles.js.map +1 -0
- package/dist/studio-dev-server.d.ts +22 -0
- package/dist/studio-dev-server.d.ts.map +1 -0
- package/dist/studio-dev-server.js +28 -0
- package/dist/studio-dev-server.js.map +1 -0
- package/dist/systemd.d.ts +26 -0
- package/dist/systemd.d.ts.map +1 -0
- package/dist/systemd.js +102 -0
- package/dist/systemd.js.map +1 -0
- package/dist/tsx-runner.d.ts.map +1 -1
- package/dist/tsx-runner.js +9 -2
- package/dist/tsx-runner.js.map +1 -1
- package/dist/type-extractor.d.ts +4 -0
- package/dist/type-extractor.d.ts.map +1 -0
- package/dist/type-extractor.js +1213 -0
- package/dist/type-extractor.js.map +1 -0
- package/dist/type-resolver.d.ts +33 -0
- package/dist/type-resolver.d.ts.map +1 -0
- package/dist/type-resolver.js +338 -0
- package/dist/type-resolver.js.map +1 -0
- package/package.json +4 -3
- package/releases/deno/VERSION +1 -0
- package/scripts/mirror-deno-release.sh +76 -0
- package/src/TYPE-RESOLUTION.md +294 -0
- package/src/app/proxy-dev-app.ts +67 -0
- package/src/app-config.ts +128 -0
- package/src/augmentation-generator.ts +126 -0
- package/src/binary-cache.ts +822 -0
- package/src/cli.ts +13 -8
- package/src/commands/admin.ts +4 -3
- package/src/commands/app.ts +67 -231
- package/src/commands/cache.ts +117 -0
- package/src/commands/cloud.ts +63 -64
- package/src/commands/db.ts +54 -63
- package/src/commands/deploy.ts +96 -62
- package/src/commands/dev.ts +933 -405
- package/src/commands/diff.ts +31 -29
- package/src/commands/engine.ts +13 -116
- package/src/commands/functions.ts +97 -115
- package/src/commands/generate.ts +23 -10
- package/src/commands/init.ts +149 -414
- package/src/commands/migrate-from-v1.ts +131 -0
- package/src/commands/migrate.ts +27 -23
- package/src/commands/pg.ts +133 -0
- package/src/commands/pull.ts +6 -85
- package/src/commands/push.ts +161 -56
- package/src/commands/seed.ts +54 -12
- package/src/commands/self-host.ts +312 -880
- package/src/commands/self-update.ts +45 -0
- package/src/commands/status.ts +4 -3
- package/src/commands/types.ts +76 -0
- package/src/commands/update.ts +109 -0
- package/src/components.ts +6 -0
- package/src/config.ts +127 -94
- package/src/dev-compose.ts +455 -0
- package/src/diff-output.ts +12 -0
- package/src/docker-postgres.ts +295 -0
- package/src/engine-client.ts +236 -0
- package/src/ensure-binary.ts +28 -0
- package/src/functions-router-gen.ts +224 -0
- package/src/index.ts +4 -12
- package/src/kong-config.ts +93 -0
- package/src/local-gateway.ts +9 -0
- package/src/local-storage.ts +14 -0
- package/src/pgbouncer-userlist.ts +15 -0
- package/src/postgres-ctl.ts +171 -0
- package/src/process-manager.ts +168 -0
- package/src/project-config.ts +386 -0
- package/src/pull-utils.ts +24 -0
- package/src/release-pins.ts +31 -0
- package/src/release-public-key.ts +12 -0
- package/src/runtime-routes.ts +291 -0
- package/src/schema-ast-v2.ts +324 -0
- package/src/scripts/postinstall.ts +36 -25
- package/src/self-host-compose.ts +389 -0
- package/src/storage-provision.ts +58 -0
- package/src/studio-admin-roles.ts +16 -0
- package/src/studio-dev-server.ts +53 -0
- package/src/systemd.ts +137 -0
- package/src/tsx-runner.ts +11 -1
- package/src/type-extractor.ts +1479 -0
- package/src/type-resolver.ts +457 -0
- package/tests/app-command.test.ts +54 -0
- package/tests/augmentation-generator.test.ts +59 -0
- package/tests/binary-cache-cloud-overrides.test.ts +123 -0
- package/tests/cached-artifact-format.test.ts +84 -0
- package/tests/cli-help.test.ts +40 -14
- package/tests/config.test.ts +171 -37
- package/tests/docker-postgres.test.ts +39 -0
- package/tests/engine-distribution.test.ts +3 -3
- package/tests/ensure-binary.test.ts +59 -0
- package/tests/init.test.ts +28 -86
- package/tests/migrate-from-v1.test.ts +29 -0
- package/tests/normalize-admin-config.test.ts +48 -0
- package/tests/pg-spawn-env.test.ts +18 -0
- package/tests/postgres-archive-tag.test.ts +9 -0
- package/tests/proxy-dev-app.test.ts +33 -0
- package/tests/pull-utils.test.ts +36 -1
- package/tests/release-pins.test.ts +28 -0
- package/tests/runtime-contract.test.ts +351 -0
- package/tests/seed-discover.test.ts +31 -0
- package/tests/studio-admin-roles.test.ts +27 -0
- package/tests/tsconfig.json +9 -0
- package/tests/type-extractor.test.ts +985 -0
- package/tests/type-resolver.test.ts +59 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +12 -0
- package/dist/engine/cache.d.ts +0 -37
- package/dist/engine/cache.d.ts.map +0 -1
- package/dist/engine/cache.js +0 -121
- package/dist/engine/cache.js.map +0 -1
- package/dist/engine/download.d.ts +0 -19
- package/dist/engine/download.d.ts.map +0 -1
- package/dist/engine/download.js +0 -108
- package/dist/engine/download.js.map +0 -1
- package/dist/engine/platform.d.ts +0 -24
- package/dist/engine/platform.d.ts.map +0 -1
- package/dist/engine/platform.js +0 -50
- package/dist/engine/platform.js.map +0 -1
- package/dist/engine/resolve.d.ts +0 -37
- package/dist/engine/resolve.d.ts.map +0 -1
- package/dist/engine/resolve.js +0 -133
- package/dist/engine/resolve.js.map +0 -1
- package/dist/engine/update-notify.d.ts +0 -11
- package/dist/engine/update-notify.d.ts.map +0 -1
- package/dist/engine/update-notify.js +0 -43
- package/dist/engine/update-notify.js.map +0 -1
- package/dist/engine/verify.d.ts +0 -50
- package/dist/engine/verify.d.ts.map +0 -1
- package/dist/engine/verify.js +0 -161
- package/dist/engine/verify.js.map +0 -1
- package/dist/engine-version.d.ts +0 -35
- package/dist/engine-version.d.ts.map +0 -1
- package/dist/engine-version.js +0 -35
- package/dist/engine-version.js.map +0 -1
- package/dist/engine.d.ts +0 -34
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js +0 -76
- package/dist/engine.js.map +0 -1
- package/src/engine/cache.ts +0 -135
- package/src/engine/download.ts +0 -143
- package/src/engine/platform.ts +0 -66
- package/src/engine/resolve.ts +0 -197
- package/src/engine/update-notify.ts +0 -50
- package/src/engine/verify.ts +0 -206
- package/src/engine-version.ts +0 -39
- package/src/engine.ts +0 -99
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* docker-postgres — manage a supatype/postgres Docker container for local dev.
|
|
3
|
+
*
|
|
4
|
+
* Used by `supatype dev` when database.provider = "docker".
|
|
5
|
+
* The container is named supatype-{projectName} and persists data in a
|
|
6
|
+
* named Docker volume (supatype-{projectName}-data) across restarts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { spawnSync } from "node:child_process"
|
|
10
|
+
|
|
11
|
+
export interface DockerPgOptions {
|
|
12
|
+
/** Docker image to run. Defaults to supatype/postgres:latest. */
|
|
13
|
+
image: string
|
|
14
|
+
/** Project name — used to derive the container and volume names. */
|
|
15
|
+
projectName: string
|
|
16
|
+
/** Host port to bind to container's 5432. */
|
|
17
|
+
port: number
|
|
18
|
+
/** Superuser password (dev only). Defaults to "postgres". */
|
|
19
|
+
password?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const PG_USER = "supatype_admin"
|
|
23
|
+
/** Must match `dockerPgStart` default `POSTGRES_PASSWORD`. */
|
|
24
|
+
const DEFAULT_DEV_PASSWORD = "postgres"
|
|
25
|
+
|
|
26
|
+
function dockerPgPsql(
|
|
27
|
+
name: string,
|
|
28
|
+
db: string,
|
|
29
|
+
sql: string,
|
|
30
|
+
password = DEFAULT_DEV_PASSWORD,
|
|
31
|
+
) {
|
|
32
|
+
return spawnSync(
|
|
33
|
+
"docker",
|
|
34
|
+
[
|
|
35
|
+
"exec",
|
|
36
|
+
"-e", `PGPASSWORD=${password}`,
|
|
37
|
+
name,
|
|
38
|
+
"psql", "--no-password", "-U", PG_USER, "-d", db, "-tAc", sql,
|
|
39
|
+
],
|
|
40
|
+
{ encoding: "utf8", stdio: "pipe" },
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Derived container name for a project. */
|
|
45
|
+
export function containerName(projectName: string): string {
|
|
46
|
+
return `supatype-${projectName}`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Start the supatype/postgres Docker container.
|
|
51
|
+
* Removes any stopped container with the same name before starting.
|
|
52
|
+
* Throws if `docker run` exits non-zero.
|
|
53
|
+
*/
|
|
54
|
+
export function dockerPgStart(opts: DockerPgOptions): void {
|
|
55
|
+
const { image, projectName, port, password = "postgres" } = opts
|
|
56
|
+
const name = containerName(projectName)
|
|
57
|
+
const volume = `${name}-data`
|
|
58
|
+
|
|
59
|
+
// Remove any stopped container from a previous session.
|
|
60
|
+
spawnSync("docker", ["rm", "-f", name], { encoding: "utf8" })
|
|
61
|
+
|
|
62
|
+
const result = spawnSync(
|
|
63
|
+
"docker",
|
|
64
|
+
[
|
|
65
|
+
"run", "-d",
|
|
66
|
+
"--name", name,
|
|
67
|
+
"-e", `POSTGRES_USER=${PG_USER}`,
|
|
68
|
+
"-e", `POSTGRES_PASSWORD=${password}`,
|
|
69
|
+
"-e", `POSTGRES_DB=${projectName}`,
|
|
70
|
+
"-p", `${port}:5432`,
|
|
71
|
+
"-v", `${volume}:/var/lib/postgresql/data`,
|
|
72
|
+
image,
|
|
73
|
+
],
|
|
74
|
+
{ encoding: "utf8", stdio: "pipe" },
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if (result.status !== 0) {
|
|
78
|
+
const detail = (result.stderr ?? result.stdout ?? "").trim()
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Failed to start Docker container "${name}".\n` +
|
|
81
|
+
(detail ? ` docker: ${detail}\n` : "") +
|
|
82
|
+
` Is Docker running? docker info`,
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Stop the container (fast — does not remove it or the data volume).
|
|
89
|
+
* Safe to call even if the container is not running.
|
|
90
|
+
*/
|
|
91
|
+
export function dockerPgStop(projectName: string): void {
|
|
92
|
+
spawnSync("docker", ["stop", containerName(projectName)], { encoding: "utf8" })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function dockerPgLogsTail(name: string, tail = 120): string {
|
|
96
|
+
const logs = spawnSync(
|
|
97
|
+
"docker",
|
|
98
|
+
["logs", "--tail", String(tail), name],
|
|
99
|
+
{ encoding: "utf8" },
|
|
100
|
+
)
|
|
101
|
+
return `${logs.stdout ?? ""}${logs.stderr ?? ""}`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function dockerPgHealthStatus(name: string): string | undefined {
|
|
105
|
+
const inspect = spawnSync(
|
|
106
|
+
"docker",
|
|
107
|
+
["inspect", "-f", "{{if .State.Health}}{{.State.Health.Status}}{{end}}", name],
|
|
108
|
+
{ encoding: "utf8", stdio: "pipe" },
|
|
109
|
+
)
|
|
110
|
+
if (inspect.status !== 0) return undefined
|
|
111
|
+
const status = inspect.stdout?.trim()
|
|
112
|
+
return status === "" ? undefined : status
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** True when the final post-init Postgres process is accepting connections. */
|
|
116
|
+
export function dockerPgPostInitServing(logs: string): boolean {
|
|
117
|
+
const ready = "database system is ready to accept connections"
|
|
118
|
+
const lastReady = logs.lastIndexOf(ready)
|
|
119
|
+
if (lastReady === -1) return false
|
|
120
|
+
|
|
121
|
+
const initDone = logs.lastIndexOf("PostgreSQL init process complete")
|
|
122
|
+
if (initDone === -1) {
|
|
123
|
+
// Reused data volume — this run did not re-run entrypoint init.
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
return initDone < lastReady
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function migrateStillRunning(logs: string): boolean {
|
|
130
|
+
return /99-supatype-migrate\.sh: running .+\.sql/.test(logs)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function psqlTruthy(stdout: string | undefined): boolean {
|
|
134
|
+
const v = (stdout ?? "").replace(/\r/g, "").trim().toLowerCase()
|
|
135
|
+
return v === "t" || v === "true"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const ANON_ROLE_SQL = "SELECT EXISTS (SELECT FROM pg_roles WHERE rolname = 'anon')"
|
|
139
|
+
|
|
140
|
+
function dockerPgHasAnonRole(
|
|
141
|
+
name: string,
|
|
142
|
+
projectName: string,
|
|
143
|
+
password = DEFAULT_DEV_PASSWORD,
|
|
144
|
+
): boolean {
|
|
145
|
+
for (const db of [projectName, "postgres"]) {
|
|
146
|
+
const result = dockerPgPsql(name, db, ANON_ROLE_SQL, password)
|
|
147
|
+
if (result.status === 0 && psqlTruthy(result.stdout)) return true
|
|
148
|
+
}
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function dockerPgExecReady(name: string): boolean {
|
|
153
|
+
const ready = spawnSync(
|
|
154
|
+
"docker",
|
|
155
|
+
["exec", name, "pg_isready", "-U", PG_USER, "-q"],
|
|
156
|
+
{ encoding: "utf8", stdio: "pipe" },
|
|
157
|
+
)
|
|
158
|
+
return ready.status === 0
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Poll until Postgres accepts connections and the image entrypoint init has
|
|
163
|
+
* finished (anon/authenticated/service_role come from supatype-postgres
|
|
164
|
+
* migrations/db/init-scripts/00000000000000-initial-schema.sql via migrate.sh).
|
|
165
|
+
*/
|
|
166
|
+
export async function dockerPgWaitReady(
|
|
167
|
+
projectName: string,
|
|
168
|
+
timeoutMs = 180_000,
|
|
169
|
+
password = DEFAULT_DEV_PASSWORD,
|
|
170
|
+
): Promise<void> {
|
|
171
|
+
const name = containerName(projectName)
|
|
172
|
+
const deadline = Date.now() + timeoutMs
|
|
173
|
+
let servingWithoutAnonMs = 0
|
|
174
|
+
let lastPsqlDetail = ""
|
|
175
|
+
|
|
176
|
+
while (Date.now() < deadline) {
|
|
177
|
+
const health = dockerPgHealthStatus(name)
|
|
178
|
+
const logs = dockerPgLogsTail(name)
|
|
179
|
+
const serving =
|
|
180
|
+
health === "healthy" ||
|
|
181
|
+
dockerPgPostInitServing(logs) ||
|
|
182
|
+
dockerPgExecReady(name)
|
|
183
|
+
|
|
184
|
+
if (serving && !migrateStillRunning(logs)) {
|
|
185
|
+
if (dockerPgHasAnonRole(name, projectName, password)) return
|
|
186
|
+
|
|
187
|
+
const probe = dockerPgPsql(name, projectName, ANON_ROLE_SQL, password)
|
|
188
|
+
lastPsqlDetail = [
|
|
189
|
+
probe.status !== 0 ? `psql exit ${probe.status}` : "",
|
|
190
|
+
probe.stderr?.trim(),
|
|
191
|
+
probe.stdout?.trim() ? `stdout=${probe.stdout.trim()}` : "",
|
|
192
|
+
]
|
|
193
|
+
.filter(Boolean)
|
|
194
|
+
.join("; ")
|
|
195
|
+
|
|
196
|
+
const reusedVolume =
|
|
197
|
+
logs.includes("database system is ready to accept connections") &&
|
|
198
|
+
!logs.includes("PostgreSQL init process complete")
|
|
199
|
+
|
|
200
|
+
if (reusedVolume) {
|
|
201
|
+
servingWithoutAnonMs += 500
|
|
202
|
+
if (servingWithoutAnonMs >= 5_000) throw staleVolumeError(name)
|
|
203
|
+
} else {
|
|
204
|
+
servingWithoutAnonMs = 0
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
servingWithoutAnonMs = 0
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
await sleep(500)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const logs = dockerPgLogsTail(name, 80)
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Docker Postgres "${name}" did not finish image init within ${timeoutMs}ms.\n` +
|
|
216
|
+
" API roles (anon, authenticated, service_role) are created by the supatype/postgres\n" +
|
|
217
|
+
" entrypoint (99-supatype-migrate.sh), not by the CLI.\n" +
|
|
218
|
+
" If you upgraded the image, remove the stale volume:\n" +
|
|
219
|
+
` docker volume rm ${name}-data\n` +
|
|
220
|
+
(lastPsqlDetail ? ` Last anon probe: ${lastPsqlDetail}\n` : "") +
|
|
221
|
+
(logs ? ` logs (tail):\n${indent(logs)}\n` : ""),
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function staleVolumeError(name: string): Error {
|
|
226
|
+
return new Error(
|
|
227
|
+
`Docker Postgres "${name}" is up but API roles are missing.\n` +
|
|
228
|
+
" The data volume was initialised without supatype/postgres migrations (stale or wrong image).\n" +
|
|
229
|
+
" Remove the volume so first-boot 99-supatype-migrate.sh runs again:\n" +
|
|
230
|
+
` docker volume rm ${name}-data`,
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Connection string for the Docker container (local dev credentials). */
|
|
235
|
+
export function dockerDbUrl(projectName: string, port: number, password = DEFAULT_DEV_PASSWORD): string {
|
|
236
|
+
// Host → published port. sqlx/libpq "prefer" can mis-handle SSL on some hosts (e.g. Docker Desktop on Windows).
|
|
237
|
+
return `postgres://${PG_USER}:${password}@127.0.0.1:${port}/${projectName}?sslmode=disable`
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* DB URL for processes sharing the Postgres container network namespace
|
|
242
|
+
* (supatype-server migrate in a one-shot container). Uses loopback inside the
|
|
243
|
+
* container where pg_hba grants trust — avoids host-published-port SCRAM/SSL issues.
|
|
244
|
+
*/
|
|
245
|
+
export function dockerPgLoopbackDbUrl(projectName: string, password = DEFAULT_DEV_PASSWORD): string {
|
|
246
|
+
return `postgres://${PG_USER}:${password}@127.0.0.1:5432/${projectName}?sslmode=disable`
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Published Hub tag for local dev when CDN server version is not on Docker Hub yet.
|
|
251
|
+
* Keep in sync with tests/integration/scripts/compose-smoke.sh.
|
|
252
|
+
*/
|
|
253
|
+
export const DEFAULT_SERVER_DOCKER_IMAGE = "supatype/server:latest"
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Run `supatype-server migrate` on the Postgres container network (loopback trust).
|
|
257
|
+
* Used on Windows + database.provider docker — host-published :5432 breaks libpq TLS there.
|
|
258
|
+
*/
|
|
259
|
+
export function runGotrueMigrationsViaDocker(
|
|
260
|
+
pgContainerName: string,
|
|
261
|
+
serverImage: string,
|
|
262
|
+
migrateEnv: Record<string, string>,
|
|
263
|
+
): void {
|
|
264
|
+
const envArgs = Object.entries(migrateEnv).flatMap(([k, v]) => ["-e", `${k}=${v}`])
|
|
265
|
+
const result = spawnSync(
|
|
266
|
+
"docker",
|
|
267
|
+
[
|
|
268
|
+
"run", "--rm",
|
|
269
|
+
"--network", `container:${pgContainerName}`,
|
|
270
|
+
...envArgs,
|
|
271
|
+
serverImage,
|
|
272
|
+
"migrate",
|
|
273
|
+
],
|
|
274
|
+
{ encoding: "utf8", stdio: "pipe" },
|
|
275
|
+
)
|
|
276
|
+
if (result.status !== 0) {
|
|
277
|
+
const detail = (result.stderr ?? result.stdout ?? "").trim()
|
|
278
|
+
throw new Error(
|
|
279
|
+
`GoTrue migrations failed in Docker (exit ${result.status ?? "unknown"})` +
|
|
280
|
+
(detail ? `:\n${detail}` : ""),
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function sleep(ms: number): Promise<void> {
|
|
286
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function indent(s: string): string {
|
|
290
|
+
return s
|
|
291
|
+
.trimEnd()
|
|
292
|
+
.split("\n")
|
|
293
|
+
.map((l) => ` ${l}`)
|
|
294
|
+
.join("\n")
|
|
295
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine-client.ts — subprocess-based engine invocation.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the former HTTP-based engine client (Docker container API).
|
|
5
|
+
* All callers use the same interface; only the transport changed.
|
|
6
|
+
*
|
|
7
|
+
* The engine binary reads a request JSON file passed via --request-file and
|
|
8
|
+
* writes a response JSON to stdout.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawnSync } from "node:child_process"
|
|
12
|
+
import { mkdirSync, writeFileSync, unlinkSync, existsSync, readdirSync } from "node:fs"
|
|
13
|
+
import { tmpdir, homedir } from "node:os"
|
|
14
|
+
import { join } from "node:path"
|
|
15
|
+
import { loadConfig } from "./config.js"
|
|
16
|
+
import { resolveBinary, currentPlatform, cachePath } from "./binary-cache.js"
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Types (kept for backward compatibility with existing callers)
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
export interface Operation {
|
|
23
|
+
kind: "create_table" | "alter_table" | "drop_table" | "create_index" | "drop_index" |
|
|
24
|
+
"create_policy" | "drop_policy" | "add_column" | "drop_column" | "alter_column" |
|
|
25
|
+
"recreate_column"
|
|
26
|
+
type?: string
|
|
27
|
+
description?: string
|
|
28
|
+
risk?: "safe" | "warn" | "danger" | "cautious" | "destructive"
|
|
29
|
+
warning?: string
|
|
30
|
+
sql?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface DiffResult {
|
|
34
|
+
operations: Operation[]
|
|
35
|
+
warnings?: string[]
|
|
36
|
+
summary?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface IntrospectResult {
|
|
40
|
+
models: Array<{
|
|
41
|
+
name: string
|
|
42
|
+
table: string
|
|
43
|
+
columns: Array<{
|
|
44
|
+
name: string
|
|
45
|
+
type: string
|
|
46
|
+
nullable: boolean
|
|
47
|
+
default?: string
|
|
48
|
+
primaryKey?: boolean
|
|
49
|
+
unique?: boolean
|
|
50
|
+
references?: { table: string; column: string }
|
|
51
|
+
}>
|
|
52
|
+
}>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface EngineResult<T = unknown> {
|
|
56
|
+
ok: boolean
|
|
57
|
+
data: T
|
|
58
|
+
message?: string
|
|
59
|
+
error?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class EngineError extends Error {
|
|
63
|
+
constructor(
|
|
64
|
+
message: string,
|
|
65
|
+
public readonly endpoint: string,
|
|
66
|
+
public readonly exitCode: number | null,
|
|
67
|
+
) {
|
|
68
|
+
super(message)
|
|
69
|
+
this.name = "EngineError"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Engine binary resolution
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
let _engineBin: string | null = null
|
|
78
|
+
|
|
79
|
+
async function getEngineBin(): Promise<string> {
|
|
80
|
+
if (_engineBin) return _engineBin
|
|
81
|
+
|
|
82
|
+
const cwd = process.cwd()
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const config = loadConfig(cwd)
|
|
86
|
+
_engineBin = await resolveBinary("engine", config)
|
|
87
|
+
return _engineBin
|
|
88
|
+
} catch {
|
|
89
|
+
// No valid project config — fall through to default cache path.
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// No config found — scan the cache for any available engine binary.
|
|
93
|
+
const platform = currentPlatform()
|
|
94
|
+
const engineCacheDir = join(homedir(), ".supatype", "cache", "engine")
|
|
95
|
+
try {
|
|
96
|
+
const cachedVersions = readdirSync(engineCacheDir).sort()
|
|
97
|
+
for (const version of cachedVersions.reverse()) {
|
|
98
|
+
const bin = join(cachePath("engine", version), `supatype-engine-${platform.os}-${platform.arch}`)
|
|
99
|
+
if (existsSync(bin)) {
|
|
100
|
+
_engineBin = bin
|
|
101
|
+
return _engineBin
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch { /* cache dir doesn't exist */ }
|
|
105
|
+
|
|
106
|
+
throw new Error(
|
|
107
|
+
"Engine binary not found. Run: supatype update",
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Public API
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
/** Verify the engine binary is accessible. Throws if not found. */
|
|
116
|
+
export async function ensureEngine(): Promise<void> {
|
|
117
|
+
await getEngineBin()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Check if the engine can be invoked. Returns true/false. */
|
|
121
|
+
export async function engineHealth(): Promise<boolean> {
|
|
122
|
+
try {
|
|
123
|
+
const bin = await getEngineBin()
|
|
124
|
+
const result = spawnSync(bin, ["--version"], { encoding: "utf8" })
|
|
125
|
+
return result.status === 0
|
|
126
|
+
} catch {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Invoke the engine and return a typed result.
|
|
133
|
+
*
|
|
134
|
+
* The endpoint maps to a subcommand:
|
|
135
|
+
* /diff → engine diff
|
|
136
|
+
* /push → engine push
|
|
137
|
+
* /generate → engine generate
|
|
138
|
+
* /migrations → engine migrations
|
|
139
|
+
* /introspect → engine introspect
|
|
140
|
+
* /validate → engine validate
|
|
141
|
+
* /admin → engine admin (admin-config JSON on stdout)
|
|
142
|
+
*/
|
|
143
|
+
export async function engineRequest<T = unknown>(
|
|
144
|
+
endpoint: string,
|
|
145
|
+
body: Record<string, unknown>,
|
|
146
|
+
): Promise<T> {
|
|
147
|
+
const bin = await getEngineBin()
|
|
148
|
+
|
|
149
|
+
// Write request to a temp file.
|
|
150
|
+
// For CLI-mode endpoints the engine reads the input file as a raw SchemaAst,
|
|
151
|
+
// so we extract the `ast` field when present, otherwise write the full body.
|
|
152
|
+
const tmpDir = join(tmpdir(), "supatype-engine")
|
|
153
|
+
mkdirSync(tmpDir, { recursive: true })
|
|
154
|
+
const reqFile = join(tmpDir, `req-${Date.now()}.json`)
|
|
155
|
+
const inputPayload = body["ast"] !== undefined ? body["ast"] : body
|
|
156
|
+
writeFileSync(reqFile, JSON.stringify(inputPayload))
|
|
157
|
+
|
|
158
|
+
const args = endpointToArgs(endpoint, body, reqFile)
|
|
159
|
+
|
|
160
|
+
const result = spawnSync(bin, args, {
|
|
161
|
+
encoding: "utf8",
|
|
162
|
+
cwd: process.cwd(),
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// Clean up temp file.
|
|
166
|
+
try { unlinkSync(reqFile) } catch { /* ignore */ }
|
|
167
|
+
|
|
168
|
+
if (result.status !== 0) {
|
|
169
|
+
const stderr = result.stderr?.trim() || "(no output)"
|
|
170
|
+
throw new EngineError(
|
|
171
|
+
`Engine ${endpoint} failed (exit ${result.status}): ${stderr}`,
|
|
172
|
+
endpoint,
|
|
173
|
+
result.status,
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!result.stdout?.trim()) {
|
|
178
|
+
// Some subcommands print nothing on success.
|
|
179
|
+
return {} as T
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
return JSON.parse(result.stdout) as T
|
|
184
|
+
} catch {
|
|
185
|
+
// Non-JSON stdout — return as message.
|
|
186
|
+
return { message: result.stdout.trim() } as T
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Endpoint → CLI args mapping
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
function endpointToArgs(
|
|
195
|
+
endpoint: string,
|
|
196
|
+
body: Record<string, unknown>,
|
|
197
|
+
reqFile: string,
|
|
198
|
+
): string[] {
|
|
199
|
+
const dbUrl = (body["database_url"] as string | undefined) ?? ""
|
|
200
|
+
const schema = (body["schema"] as string | undefined) ?? "public"
|
|
201
|
+
const force = body["force"] ? ["--force"] : []
|
|
202
|
+
const nonInteractive =
|
|
203
|
+
body["non_interactive"] === true || body["force"] === true ? ["--non-interactive"] : []
|
|
204
|
+
|
|
205
|
+
switch (endpoint) {
|
|
206
|
+
case "/diff":
|
|
207
|
+
return ["diff", "--input", reqFile, "--database-url", dbUrl, "--schema", schema]
|
|
208
|
+
|
|
209
|
+
case "/push":
|
|
210
|
+
return ["push", "--input", reqFile, "--database-url", dbUrl, "--schema", schema, ...force, ...nonInteractive]
|
|
211
|
+
|
|
212
|
+
case "/parse":
|
|
213
|
+
return ["parse", "--input", reqFile]
|
|
214
|
+
|
|
215
|
+
case "/generate": {
|
|
216
|
+
const lang = (body["lang"] as string | undefined) ?? "typescript"
|
|
217
|
+
return ["generate", "--input", reqFile, "--lang", lang]
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
case "/introspect":
|
|
221
|
+
return ["introspect", "--database-url", dbUrl, "--schema", schema]
|
|
222
|
+
|
|
223
|
+
case "/validate":
|
|
224
|
+
return ["validate", "--input", reqFile]
|
|
225
|
+
|
|
226
|
+
case "/admin":
|
|
227
|
+
return ["admin", "--input", reqFile]
|
|
228
|
+
|
|
229
|
+
default:
|
|
230
|
+
if (endpoint.startsWith("/migrations")) {
|
|
231
|
+
const action = (body["action"] as string | undefined) ?? "list"
|
|
232
|
+
return ["migrations", action, "--database-url", dbUrl]
|
|
233
|
+
}
|
|
234
|
+
return [endpoint.replace(/^\//, ""), "--input", reqFile]
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve component binaries, downloading from the CDN when not cached.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
resolveBinary,
|
|
7
|
+
download,
|
|
8
|
+
currentPlatform,
|
|
9
|
+
versionFor,
|
|
10
|
+
type Component,
|
|
11
|
+
} from "./binary-cache.js"
|
|
12
|
+
import type { SupatypeProjectConfig } from "./project-config.js"
|
|
13
|
+
|
|
14
|
+
export async function ensureBinary(
|
|
15
|
+
component: Component,
|
|
16
|
+
config: SupatypeProjectConfig,
|
|
17
|
+
): Promise<string> {
|
|
18
|
+
try {
|
|
19
|
+
return await resolveBinary(component, config)
|
|
20
|
+
} catch (err) {
|
|
21
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
22
|
+
if (!message.includes("not found in cache")) {
|
|
23
|
+
throw err
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return download(component, versionFor(component, config), currentPlatform())
|
|
28
|
+
}
|