@supatype/cli 0.1.0-alpha.6 → 0.1.0-alpha.7
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 +203 -1
- package/.turbo/turbo-typecheck.log +1 -1
- 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 +89 -0
- package/dist/binary-cache.d.ts.map +1 -0
- package/dist/binary-cache.js +656 -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 +12 -0
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +36 -46
- 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 +92 -51
- 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 +751 -384
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +20 -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 +124 -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 +99 -39
- 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 +77 -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/docker-postgres.d.ts +39 -0
- package/dist/docker-postgres.d.ts.map +1 -0
- package/dist/docker-postgres.js +96 -0
- package/dist/docker-postgres.js.map +1 -0
- package/dist/engine-client.d.ts +67 -0
- package/dist/engine-client.d.ts.map +1 -0
- package/dist/engine-client.js +156 -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 +21 -0
- package/dist/kong-config.d.ts.map +1 -0
- package/dist/kong-config.js +60 -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 +41 -0
- package/dist/process-manager.d.ts.map +1 -0
- package/dist/process-manager.js +120 -0
- package/dist/process-manager.js.map +1 -0
- package/dist/project-config.d.ts +215 -0
- package/dist/project-config.d.ts.map +1 -0
- package/dist/project-config.js +145 -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 +25 -0
- package/dist/runtime-routes.d.ts.map +1 -0
- package/dist/runtime-routes.js +189 -0
- package/dist/runtime-routes.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 +14 -0
- package/dist/self-host-compose.d.ts.map +1 -0
- package/dist/self-host-compose.js +236 -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/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 +31 -0
- package/dist/type-extractor.d.ts.map +1 -0
- package/dist/type-extractor.js +876 -0
- package/dist/type-extractor.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/app-config.ts +128 -0
- package/src/augmentation-generator.ts +126 -0
- package/src/binary-cache.ts +802 -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 +46 -57
- package/src/commands/db.ts +54 -63
- package/src/commands/deploy.ts +110 -61
- package/src/commands/dev.ts +930 -405
- package/src/commands/diff.ts +21 -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 +136 -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 +128 -59
- 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 +92 -0
- package/src/components.ts +6 -0
- package/src/config.ts +127 -94
- package/src/docker-postgres.ts +138 -0
- package/src/engine-client.ts +231 -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 +78 -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 +151 -0
- package/src/project-config.ts +353 -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 +216 -0
- package/src/scripts/postinstall.ts +36 -25
- package/src/self-host-compose.ts +257 -0
- package/src/storage-provision.ts +58 -0
- package/src/systemd.ts +137 -0
- package/src/tsx-runner.ts +11 -1
- package/src/type-extractor.ts +1016 -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 +140 -37
- 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/pg-spawn-env.test.ts +18 -0
- package/tests/postgres-archive-tag.test.ts +9 -0
- package/tests/pull-utils.test.ts +36 -1
- package/tests/release-pins.test.ts +28 -0
- package/tests/runtime-contract.test.ts +236 -0
- package/tests/seed-discover.test.ts +31 -0
- package/tests/tsconfig.json +9 -0
- package/tests/type-extractor.test.ts +401 -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,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* supatype self-update — Phase 10.6C8.
|
|
3
|
+
*
|
|
4
|
+
* npm-installed CLI: instruct to use `npm update -g`.
|
|
5
|
+
* Standalone / future curl|sh installs: CDN-published CLI binary replacement is not wired yet.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Command } from "commander"
|
|
9
|
+
import { sep } from "node:path"
|
|
10
|
+
|
|
11
|
+
function looksLikeNpmOrWorkspaceCLI(): boolean {
|
|
12
|
+
const main = process.argv[1] ?? ""
|
|
13
|
+
return (
|
|
14
|
+
main.includes("node_modules") ||
|
|
15
|
+
main.includes(`${sep}dist${sep}cli`) ||
|
|
16
|
+
main.includes(`${sep}bin${sep}supatype.js`) ||
|
|
17
|
+
Boolean(process.env["npm_execpath"]) ||
|
|
18
|
+
Boolean(process.env["npm_lifecycle_event"])
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function registerSelfUpdate(program: Command): void {
|
|
23
|
+
program
|
|
24
|
+
.command("self-update")
|
|
25
|
+
.description(
|
|
26
|
+
"Update the supatype CLI (npm: use npm update; standalone binary swap from CDN is not available yet)",
|
|
27
|
+
)
|
|
28
|
+
.action(() => {
|
|
29
|
+
if (looksLikeNpmOrWorkspaceCLI()) {
|
|
30
|
+
console.log(
|
|
31
|
+
"This CLI was installed via npm (or runs from the package workspace).\n" +
|
|
32
|
+
"Update with:\n\n npm update -g @supatype/cli\n\n" +
|
|
33
|
+
"To bump engine/server/postgres/deno pinned in supatype.config.ts, use:\n\n supatype update\n",
|
|
34
|
+
)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.error(
|
|
39
|
+
"Standalone CLI self-update (replace binary from CDN) is not published yet.\n\n" +
|
|
40
|
+
"Install or upgrade via npm:\n npm install -g @supatype/cli\n\n" +
|
|
41
|
+
"Component binaries (engine, server, postgres, deno):\n supatype update\n",
|
|
42
|
+
)
|
|
43
|
+
process.exit(1)
|
|
44
|
+
})
|
|
45
|
+
}
|
package/src/commands/status.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { Command } from "commander"
|
|
5
5
|
import { spawnSync } from "node:child_process"
|
|
6
|
+
import { LOCAL_KONG_HOST_PORT, localKongBaseUrl } from "../local-gateway.js"
|
|
6
7
|
|
|
7
8
|
interface ServiceStatus {
|
|
8
9
|
name: string
|
|
@@ -21,7 +22,7 @@ export function registerStatus(program: Command): void {
|
|
|
21
22
|
{ name: "Postgres", container: "supatype-postgres", port: 5432 },
|
|
22
23
|
{ name: "PostgREST", container: "supatype-postgrest", port: 3000 },
|
|
23
24
|
{ name: "GoTrue", container: "supatype-gotrue", port: 9999 },
|
|
24
|
-
{ name: "Kong", container: "supatype-kong", port:
|
|
25
|
+
{ name: "Kong", container: "supatype-kong", port: LOCAL_KONG_HOST_PORT },
|
|
25
26
|
{ name: "MinIO", container: "supatype-minio", port: 9000 },
|
|
26
27
|
{ name: "Realtime", container: "supatype-realtime", port: 4000 },
|
|
27
28
|
{ name: "Studio", container: "supatype-studio", port: 3100 },
|
|
@@ -46,9 +47,9 @@ export function registerStatus(program: Command): void {
|
|
|
46
47
|
console.log(`\n${running.length}/${services.length} services running`)
|
|
47
48
|
|
|
48
49
|
if (running.length > 0) {
|
|
49
|
-
console.log(`\nAPI URL:
|
|
50
|
+
console.log(`\nAPI URL: ${localKongBaseUrl()}`)
|
|
50
51
|
console.log(`Studio: http://localhost:3100`)
|
|
51
|
-
console.log(`Database: postgresql://
|
|
52
|
+
console.log(`Database: postgresql://supatype_admin:postgres@localhost:5432/postgres`)
|
|
52
53
|
}
|
|
53
54
|
})
|
|
54
55
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Command } from "commander"
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
3
|
+
import { resolve, relative } from "node:path"
|
|
4
|
+
import ts from "typescript"
|
|
5
|
+
import { loadConfig } from "../config.js"
|
|
6
|
+
|
|
7
|
+
export function registerTypes(program: Command): void {
|
|
8
|
+
const types = program.command("types").description("Type generation and validation utilities")
|
|
9
|
+
|
|
10
|
+
types
|
|
11
|
+
.command("check")
|
|
12
|
+
.description("Validate generated client augmentation wiring")
|
|
13
|
+
.action(() => {
|
|
14
|
+
const cwd = process.cwd()
|
|
15
|
+
const config = loadConfig(cwd)
|
|
16
|
+
const augmentationPath = resolve(cwd, config.output?.client ?? "supatype/generated/index.d.ts")
|
|
17
|
+
|
|
18
|
+
const errors: string[] = []
|
|
19
|
+
|
|
20
|
+
if (!existsSync(augmentationPath)) {
|
|
21
|
+
errors.push(
|
|
22
|
+
`Missing generated augmentation file: ${relative(cwd, augmentationPath)}.\nRun: supatype generate`,
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const tsconfigPath = resolve(cwd, "tsconfig.json")
|
|
27
|
+
if (!existsSync(tsconfigPath)) {
|
|
28
|
+
errors.push("Missing tsconfig.json in project root.")
|
|
29
|
+
} else {
|
|
30
|
+
const tsconfigRaw = readFileSync(tsconfigPath, "utf8")
|
|
31
|
+
const parsed = ts.parseConfigFileTextToJson(tsconfigPath, tsconfigRaw)
|
|
32
|
+
const cfg = (parsed.config ?? {}) as { include?: unknown; files?: unknown }
|
|
33
|
+
const include = Array.isArray(cfg.include) ? cfg.include.map(String) : []
|
|
34
|
+
const files = Array.isArray(cfg.files) ? cfg.files.map(String) : []
|
|
35
|
+
const relAug = toPosix(relative(cwd, augmentationPath))
|
|
36
|
+
const coveredByFiles = files.includes(relAug)
|
|
37
|
+
const coveredByInclude = include.some((entry) => {
|
|
38
|
+
if (entry.includes("**")) return relAug.startsWith(entry.split("**")[0] ?? "")
|
|
39
|
+
if (entry.endsWith("*.ts") || entry.endsWith("*.d.ts")) {
|
|
40
|
+
return relAug.startsWith(entry.replace(/\*\.d?ts$/, ""))
|
|
41
|
+
}
|
|
42
|
+
return relAug === entry || relAug.startsWith(entry.replace(/\/$/, "") + "/")
|
|
43
|
+
})
|
|
44
|
+
if (!coveredByFiles && !coveredByInclude) {
|
|
45
|
+
errors.push(
|
|
46
|
+
`tsconfig.json does not include ${relAug}. Add it to "include" or "files" so module augmentation is visible to TypeScript.`,
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const packageJsonPath = resolve(cwd, "package.json")
|
|
52
|
+
if (existsSync(packageJsonPath)) {
|
|
53
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
|
|
54
|
+
dependencies?: Record<string, string>
|
|
55
|
+
devDependencies?: Record<string, string>
|
|
56
|
+
}
|
|
57
|
+
const hasClient =
|
|
58
|
+
(pkg.dependencies && "@supatype/client" in pkg.dependencies) ||
|
|
59
|
+
(pkg.devDependencies && "@supatype/client" in pkg.devDependencies)
|
|
60
|
+
if (!hasClient) {
|
|
61
|
+
errors.push('package.json is missing "@supatype/client" dependency.')
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (errors.length > 0) {
|
|
66
|
+
for (const err of errors) console.error(`- ${err}`)
|
|
67
|
+
process.exit(1)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log("types check passed")
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function toPosix(path: string): string {
|
|
75
|
+
return path.replace(/\\/g, "/")
|
|
76
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* supatype update — bump component versions in supatype.config.ts
|
|
3
|
+
* and download the new binaries.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Command } from "commander"
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs"
|
|
8
|
+
import { basename, resolve } from "node:path"
|
|
9
|
+
import { loadConfig } from "../config.js"
|
|
10
|
+
import { download, currentPlatform, fetchAllLatestVersions, type Component } from "../binary-cache.js"
|
|
11
|
+
|
|
12
|
+
const CONFIG_CANDIDATES = ["supatype.config.ts", "supatype.config.js", "supatype.config.mjs"]
|
|
13
|
+
|
|
14
|
+
function resolveConfigFile(cwd: string): string | null {
|
|
15
|
+
for (const name of CONFIG_CANDIDATES) {
|
|
16
|
+
const p = resolve(cwd, name)
|
|
17
|
+
if (existsSync(p)) return p
|
|
18
|
+
}
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function registerUpdate(program: Command): void {
|
|
23
|
+
program
|
|
24
|
+
.command("update")
|
|
25
|
+
.description(
|
|
26
|
+
"Download the latest component binaries and update versions in supatype.config.ts",
|
|
27
|
+
)
|
|
28
|
+
.option("--check", "Print available updates without downloading", false)
|
|
29
|
+
.action(async (opts: { check: boolean }) => {
|
|
30
|
+
const cwd = process.cwd()
|
|
31
|
+
const config = loadConfig(cwd)
|
|
32
|
+
const platform = currentPlatform()
|
|
33
|
+
|
|
34
|
+
const components: Component[] = ["engine", "server", "postgres", "deno"]
|
|
35
|
+
const updates: Array<{ component: Component; from: string; to: string }> = []
|
|
36
|
+
|
|
37
|
+
console.log("Fetching latest component versions from CDN...")
|
|
38
|
+
const latestVersions = await fetchAllLatestVersions()
|
|
39
|
+
|
|
40
|
+
for (const component of components) {
|
|
41
|
+
const current = config.versions[component]
|
|
42
|
+
if (current === "local") continue
|
|
43
|
+
const latest = latestVersions[component]
|
|
44
|
+
if (current !== latest) {
|
|
45
|
+
updates.push({ component, from: current, to: latest })
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (updates.length === 0) {
|
|
50
|
+
console.log("All components are up to date.")
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log("Available updates:")
|
|
55
|
+
for (const { component, from, to } of updates) {
|
|
56
|
+
console.log(` ${component} ${from} → ${to}`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (opts.check) return
|
|
60
|
+
|
|
61
|
+
// Download updated binaries.
|
|
62
|
+
for (const { component, to } of updates) {
|
|
63
|
+
console.log(`\nDownloading ${component} v${to}...`)
|
|
64
|
+
try {
|
|
65
|
+
await download(component, to, platform)
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.error(` Failed: ${(err as Error).message}`)
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
console.log(` ${component} v${to} downloaded.`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const configPath = resolveConfigFile(cwd)
|
|
74
|
+
if (configPath === null) {
|
|
75
|
+
console.error("No supatype.config.ts (or .js/.mjs) found to patch versions.")
|
|
76
|
+
process.exit(1)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let text = readFileSync(configPath, "utf8")
|
|
80
|
+
for (const { component, to } of updates) {
|
|
81
|
+
const key = component
|
|
82
|
+
// engine: "0.4.1" or engine: '0.4.1'
|
|
83
|
+
text = text.replace(
|
|
84
|
+
new RegExp(`(${key}\\s*:\\s*['"])[^'"]*(['"])`),
|
|
85
|
+
`$1${to}$2`,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
writeFileSync(configPath, text, "utf8")
|
|
90
|
+
console.log(`\n${basename(configPath)} updated.`)
|
|
91
|
+
})
|
|
92
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** CDN-cached binaries resolved by the CLI (`engine`, `server`, `postgres`, `deno`). */
|
|
2
|
+
export const BINARY_COMPONENTS = ["engine", "server", "postgres", "deno"] as const
|
|
3
|
+
|
|
4
|
+
export type Component = (typeof BINARY_COMPONENTS)[number]
|
|
5
|
+
|
|
6
|
+
export type ComponentVersions = Record<Component, string>
|
package/src/config.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
|
-
import { existsSync } from "node:fs"
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs"
|
|
2
2
|
import { resolve } from "node:path"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
3
5
|
import { evalTsSnippet } from "./tsx-runner.js"
|
|
6
|
+
import {
|
|
7
|
+
mergeProjectConfig,
|
|
8
|
+
validateProjectConfig,
|
|
9
|
+
type SupatypeProjectConfig,
|
|
10
|
+
} from "./project-config.js"
|
|
11
|
+
import { extractSchemaAstFromTypes } from "./type-extractor.js"
|
|
12
|
+
|
|
13
|
+
export type { SupatypeProjectConfig } from "./project-config.js"
|
|
14
|
+
|
|
15
|
+
/** Canonical project configuration shape (alias for clarity at call sites). */
|
|
16
|
+
export type SupatypeConfig = SupatypeProjectConfig
|
|
4
17
|
|
|
5
18
|
export interface ServiceVersionPin {
|
|
6
19
|
/** Docker image tag to pin this service to (e.g. "v1.2.3"). When set, `self-host upgrade` skips this service. */
|
|
@@ -27,12 +40,6 @@ export interface SelfHostConfig {
|
|
|
27
40
|
* Pin specific services to fixed Docker image versions.
|
|
28
41
|
* When a service is pinned, `self-host upgrade` will skip it.
|
|
29
42
|
* Omit a service or set to `undefined` to allow automatic upgrades.
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* services: {
|
|
33
|
-
* db: { version: "17-latest" },
|
|
34
|
-
* postgrest: { version: "v12.2.8" },
|
|
35
|
-
* }
|
|
36
43
|
*/
|
|
37
44
|
services?: {
|
|
38
45
|
db?: ServiceVersionPin
|
|
@@ -63,97 +70,136 @@ export interface AppConfig {
|
|
|
63
70
|
headers?: Record<string, string>
|
|
64
71
|
}
|
|
65
72
|
|
|
66
|
-
export interface SupatypeConfig {
|
|
67
|
-
/** Database connection string. */
|
|
68
|
-
connection: string
|
|
69
|
-
/**
|
|
70
|
-
* Path (or glob) to the schema entry point.
|
|
71
|
-
* Must export model definitions as named exports.
|
|
72
|
-
* @example "./schema/index.ts"
|
|
73
|
-
*/
|
|
74
|
-
schema: string
|
|
75
|
-
output?: {
|
|
76
|
-
/** Path for generated TypeScript types. */
|
|
77
|
-
types?: string
|
|
78
|
-
/** Path for generated client helpers. */
|
|
79
|
-
client?: string
|
|
80
|
-
}
|
|
81
|
-
/** Self-hosted production deployment configuration. */
|
|
82
|
-
selfHost?: SelfHostConfig
|
|
83
|
-
/** Cloud project reference (set by `supatype link --project <ref>`). */
|
|
84
|
-
projectRef?: string
|
|
85
|
-
/** Cloud API URL override. */
|
|
86
|
-
apiUrl?: string
|
|
87
|
-
/** Cloud access token (prefer SUPATYPE_ACCESS_TOKEN env var). */
|
|
88
|
-
accessToken?: string
|
|
89
|
-
/** CORS configuration. */
|
|
90
|
-
cors?: {
|
|
91
|
-
/** Allowed origins. Defaults to ['*'] in development. */
|
|
92
|
-
allowedOrigins?: string[]
|
|
93
|
-
}
|
|
94
|
-
/** Static site hosting configuration. */
|
|
95
|
-
app?: AppConfig
|
|
96
|
-
/** Registered plugins (provider, field, composite, widget). */
|
|
97
|
-
plugins?: Array<unknown> | undefined
|
|
98
|
-
/** Admin panel configuration (see Gap Appendices tasks 47–50). */
|
|
99
|
-
admin?: {
|
|
100
|
-
/**
|
|
101
|
-
* Roles from {ref}_auth.users that grant admin panel access.
|
|
102
|
-
* Checked against `app_metadata.role` in the project JWT.
|
|
103
|
-
* @default ["admin"]
|
|
104
|
-
*/
|
|
105
|
-
roles?: string[]
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
73
|
/** Identity helper — provides type inference for config files. */
|
|
110
|
-
export function defineConfig(config:
|
|
74
|
+
export function defineConfig(config: SupatypeProjectConfig): SupatypeProjectConfig {
|
|
111
75
|
return config
|
|
112
76
|
}
|
|
113
77
|
|
|
114
|
-
const
|
|
78
|
+
const LEGACY_TOML = "supatype.config.toml"
|
|
79
|
+
|
|
80
|
+
const MAIN_CONFIG_CANDIDATES = [
|
|
115
81
|
"supatype.config.ts",
|
|
116
82
|
"supatype.config.js",
|
|
117
83
|
"supatype.config.mjs",
|
|
118
84
|
]
|
|
119
85
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
86
|
+
const LOCAL_CONFIG_CANDIDATES = [
|
|
87
|
+
"supatype.local.config.ts",
|
|
88
|
+
"supatype.local.config.js",
|
|
89
|
+
"supatype.local.config.mjs",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Normalize a config object parsed from JSON (e.g. shorthand `schema: "./x"`).
|
|
94
|
+
*/
|
|
95
|
+
function normalizeProjectJson(raw: unknown): unknown {
|
|
96
|
+
if (typeof raw !== "object" || raw === null) return raw
|
|
97
|
+
const o = { ...(raw as Record<string, unknown>) }
|
|
98
|
+
const sch = o["schema"]
|
|
99
|
+
if (typeof sch === "string") {
|
|
100
|
+
o["schema"] = {
|
|
101
|
+
path: sch,
|
|
102
|
+
pg_schema: typeof o["pg_schema"] === "string" ? o["pg_schema"] : "public",
|
|
103
|
+
}
|
|
104
|
+
delete o["pg_schema"]
|
|
105
|
+
}
|
|
106
|
+
return o
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Load project config from `supatype.config.ts` (or .js/.mjs), merged with
|
|
111
|
+
* optional `supatype.local.config.*` (gitignored overrides).
|
|
112
|
+
*/
|
|
113
|
+
export function loadConfig(cwd: string = process.cwd()): SupatypeProjectConfig {
|
|
114
|
+
if (existsSync(resolve(cwd, LEGACY_TOML))) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Found ${LEGACY_TOML}, which is no longer supported.\n` +
|
|
117
|
+
"Move settings into supatype.config.ts (export default { project, database, server, app, versions, … }).\n" +
|
|
118
|
+
"Run `supatype init` in a new folder for a fresh template.",
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const baseRaw = loadFirstTsConfig(cwd, MAIN_CONFIG_CANDIDATES)
|
|
123
|
+
if (baseRaw === null) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
"No supatype.config.ts (or .js/.mjs) found in the current directory.\n" +
|
|
126
|
+
"Run: supatype init",
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const baseNorm = normalizeProjectJson(baseRaw)
|
|
131
|
+
const base = validateProjectConfig(baseNorm, "supatype.config.ts")
|
|
132
|
+
|
|
133
|
+
const localRaw = loadFirstTsConfig(cwd, LOCAL_CONFIG_CANDIDATES)
|
|
134
|
+
if (localRaw === null) return base
|
|
135
|
+
|
|
136
|
+
const localNorm = normalizeProjectJson(localRaw) as Partial<SupatypeProjectConfig>
|
|
137
|
+
return mergeProjectConfig(base, localNorm)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function loadFirstTsConfig(
|
|
141
|
+
cwd: string,
|
|
142
|
+
candidates: string[],
|
|
143
|
+
): Record<string, unknown> | null {
|
|
144
|
+
for (const candidate of candidates) {
|
|
123
145
|
const configPath = resolve(cwd, candidate)
|
|
124
146
|
if (!existsSync(configPath)) continue
|
|
125
147
|
|
|
126
148
|
const urlPath = "file:///" + configPath.replace(/\\/g, "/")
|
|
127
|
-
|
|
128
|
-
// Use dynamic import so we can always access .default —
|
|
129
|
-
// files without a parent package.json are treated as CJS by tsx,
|
|
130
|
-
// meaning export default becomes module.exports.default rather than
|
|
131
|
-
// the namespace default. Dynamic import + fallback handles both.
|
|
132
149
|
const snippet = `
|
|
133
150
|
const mod = await import(${JSON.stringify(urlPath)})
|
|
134
151
|
const config = mod.default ?? mod
|
|
135
152
|
process.stdout.write(JSON.stringify(config))
|
|
136
153
|
`
|
|
137
154
|
const result = evalTsSnippet(snippet, { cwd })
|
|
138
|
-
if (result.exitCode
|
|
139
|
-
|
|
140
|
-
`Failed to load ${candidate}:\n${result.stderr || result.stdout}`,
|
|
141
|
-
)
|
|
155
|
+
if (result.exitCode === 0) {
|
|
156
|
+
return JSON.parse(result.stdout) as Record<string, unknown>
|
|
142
157
|
}
|
|
143
158
|
|
|
144
|
-
const
|
|
145
|
-
if (!
|
|
146
|
-
throw new Error(
|
|
147
|
-
`${candidate} must export { connection, schema } via defineConfig()`,
|
|
148
|
-
)
|
|
159
|
+
const failure = result.stderr || result.stdout
|
|
160
|
+
if (!failure.includes("ERR_PACKAGE_PATH_NOT_EXPORTED")) {
|
|
161
|
+
throw new Error(`Failed to load ${candidate}:\n${failure}`)
|
|
149
162
|
}
|
|
150
|
-
|
|
163
|
+
|
|
164
|
+
const fallback = loadTsConfigWithoutCliImport(configPath, cwd)
|
|
165
|
+
if (fallback !== null) return fallback
|
|
166
|
+
throw new Error(`Failed to load ${candidate}:\n${failure}`)
|
|
151
167
|
}
|
|
168
|
+
return null
|
|
169
|
+
}
|
|
152
170
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
171
|
+
function loadTsConfigWithoutCliImport(
|
|
172
|
+
configPath: string,
|
|
173
|
+
cwd: string,
|
|
174
|
+
): Record<string, unknown> | null {
|
|
175
|
+
const original = readFileSync(configPath, "utf8")
|
|
176
|
+
const patched = original
|
|
177
|
+
.replace(/^import\s+type\s+\{[^}]*\}\s+from\s+["']@supatype\/cli["'];?\s*$/gm, "")
|
|
178
|
+
.replace(/^import\s+\{[^}]*defineConfig[^}]*\}\s+from\s+["']@supatype\/cli["'];?\s*$/gm, "")
|
|
179
|
+
|
|
180
|
+
// If the file didn't import from @supatype/cli, this fallback won't help.
|
|
181
|
+
if (patched === original) return null
|
|
182
|
+
|
|
183
|
+
const tmpPath = join(tmpdir(), `supatype-config-fallback-${Date.now()}.mts`)
|
|
184
|
+
const wrapper = `const defineConfig = (config) => config\n${patched}`
|
|
185
|
+
writeFileSync(tmpPath, wrapper, "utf8")
|
|
186
|
+
try {
|
|
187
|
+
const urlPath = "file:///" + tmpPath.replace(/\\/g, "/")
|
|
188
|
+
const snippet = `
|
|
189
|
+
const mod = await import(${JSON.stringify(urlPath)})
|
|
190
|
+
const config = mod.default ?? mod
|
|
191
|
+
process.stdout.write(JSON.stringify(config))
|
|
192
|
+
`
|
|
193
|
+
const result = evalTsSnippet(snippet, { cwd })
|
|
194
|
+
if (result.exitCode !== 0) return null
|
|
195
|
+
return JSON.parse(result.stdout) as Record<string, unknown>
|
|
196
|
+
} finally {
|
|
197
|
+
try {
|
|
198
|
+
unlinkSync(tmpPath)
|
|
199
|
+
} catch {
|
|
200
|
+
// ignore cleanup errors
|
|
201
|
+
}
|
|
202
|
+
}
|
|
157
203
|
}
|
|
158
204
|
|
|
159
205
|
/** Load schema AST by evaluating the user's schema entry point via tsx. */
|
|
@@ -161,30 +207,17 @@ export function loadSchemaAst(
|
|
|
161
207
|
schemaPath: string,
|
|
162
208
|
cwd: string = process.cwd(),
|
|
163
209
|
): unknown {
|
|
210
|
+
const extracted = extractSchemaAstFromTypes(schemaPath, cwd)
|
|
211
|
+
if (extracted !== null) return extracted
|
|
212
|
+
|
|
164
213
|
const absPath = resolve(cwd, schemaPath)
|
|
165
214
|
if (!existsSync(absPath)) {
|
|
166
215
|
throw new Error(`Schema file not found: ${absPath}`)
|
|
167
216
|
}
|
|
168
217
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const mod = await import(${JSON.stringify(urlPath)})
|
|
174
|
-
const { default: _default, ...named } = mod
|
|
175
|
-
const models = Object.fromEntries(
|
|
176
|
-
Object.entries(named).filter(([, v]) =>
|
|
177
|
-
v != null && typeof v === "object" && "__modelMeta" in (v as object)
|
|
218
|
+
throw new Error(
|
|
219
|
+
"Runtime model() schemas are no longer supported.\n" +
|
|
220
|
+
`Could not extract type-based models from: ${absPath}\n` +
|
|
221
|
+
"Migrate this file to @supatype/types Model<> definitions (or run `supatype migrate-from-v1`).",
|
|
178
222
|
)
|
|
179
|
-
)
|
|
180
|
-
process.stdout.write(JSON.stringify(serialiseSchema(models)))
|
|
181
|
-
`
|
|
182
|
-
const result = evalTsSnippet(snippet, { cwd })
|
|
183
|
-
if (result.exitCode !== 0) {
|
|
184
|
-
throw new Error(
|
|
185
|
-
`Failed to load schema from ${absPath}:\n${result.stderr || result.stdout}`,
|
|
186
|
-
)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return JSON.parse(result.stdout)
|
|
190
223
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
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:17-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
|
+
|
|
24
|
+
/** Derived container name for a project. */
|
|
25
|
+
export function containerName(projectName: string): string {
|
|
26
|
+
return `supatype-${projectName}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Start the supatype/postgres Docker container.
|
|
31
|
+
* Removes any stopped container with the same name before starting.
|
|
32
|
+
* Throws if `docker run` exits non-zero.
|
|
33
|
+
*/
|
|
34
|
+
export function dockerPgStart(opts: DockerPgOptions): void {
|
|
35
|
+
const { image, projectName, port, password = "postgres" } = opts
|
|
36
|
+
const name = containerName(projectName)
|
|
37
|
+
const volume = `${name}-data`
|
|
38
|
+
|
|
39
|
+
// Remove any stopped container from a previous session.
|
|
40
|
+
spawnSync("docker", ["rm", "-f", name], { encoding: "utf8" })
|
|
41
|
+
|
|
42
|
+
const result = spawnSync(
|
|
43
|
+
"docker",
|
|
44
|
+
[
|
|
45
|
+
"run", "-d",
|
|
46
|
+
"--name", name,
|
|
47
|
+
"-e", `POSTGRES_USER=${PG_USER}`,
|
|
48
|
+
"-e", `POSTGRES_PASSWORD=${password}`,
|
|
49
|
+
"-e", `POSTGRES_DB=${projectName}`,
|
|
50
|
+
"-p", `${port}:5432`,
|
|
51
|
+
"-v", `${volume}:/var/lib/postgresql/data`,
|
|
52
|
+
image,
|
|
53
|
+
],
|
|
54
|
+
{ encoding: "utf8", stdio: "pipe" },
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if (result.status !== 0) {
|
|
58
|
+
const detail = (result.stderr ?? result.stdout ?? "").trim()
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Failed to start Docker container "${name}".\n` +
|
|
61
|
+
(detail ? ` docker: ${detail}\n` : "") +
|
|
62
|
+
` Is Docker running? docker info`,
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Stop the container (fast — does not remove it or the data volume).
|
|
69
|
+
* Safe to call even if the container is not running.
|
|
70
|
+
*/
|
|
71
|
+
export function dockerPgStop(projectName: string): void {
|
|
72
|
+
spawnSync("docker", ["stop", containerName(projectName)], { encoding: "utf8" })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Poll until Postgres accepts connections and the image entrypoint init has
|
|
77
|
+
* finished (anon/authenticated/service_role come from supatype-postgres
|
|
78
|
+
* migrations/db/init-scripts/00000000000000-initial-schema.sql via migrate.sh).
|
|
79
|
+
*/
|
|
80
|
+
export async function dockerPgWaitReady(
|
|
81
|
+
projectName: string,
|
|
82
|
+
timeoutMs = 30_000,
|
|
83
|
+
): Promise<void> {
|
|
84
|
+
const name = containerName(projectName)
|
|
85
|
+
const deadline = Date.now() + timeoutMs
|
|
86
|
+
|
|
87
|
+
while (Date.now() < deadline) {
|
|
88
|
+
const ready = spawnSync(
|
|
89
|
+
"docker",
|
|
90
|
+
["exec", name, "pg_isready", "-U", PG_USER, "-q"],
|
|
91
|
+
{ encoding: "utf8" },
|
|
92
|
+
)
|
|
93
|
+
if (ready.status === 0) {
|
|
94
|
+
const initDone = spawnSync(
|
|
95
|
+
"docker",
|
|
96
|
+
[
|
|
97
|
+
"exec", name,
|
|
98
|
+
"psql", "-U", PG_USER, "-d", projectName, "-tAc",
|
|
99
|
+
"SELECT EXISTS (SELECT FROM pg_roles WHERE rolname = 'anon')",
|
|
100
|
+
],
|
|
101
|
+
{ encoding: "utf8", stdio: "pipe" },
|
|
102
|
+
)
|
|
103
|
+
if (initDone.status === 0 && initDone.stdout?.trim() === "t") return
|
|
104
|
+
}
|
|
105
|
+
await sleep(300)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const logs = spawnSync("docker", ["logs", "--tail", "30", name], {
|
|
109
|
+
encoding: "utf8",
|
|
110
|
+
})
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Docker Postgres "${name}" did not finish image init within ${timeoutMs}ms.\n` +
|
|
113
|
+
" API roles (anon, authenticated, service_role) are created by the supatype/postgres\n" +
|
|
114
|
+
" entrypoint (99-supatype-migrate.sh), not by the CLI.\n" +
|
|
115
|
+
" If you upgraded the image, remove the stale volume:\n" +
|
|
116
|
+
` docker volume rm ${name}-data\n` +
|
|
117
|
+
(logs.stdout ? ` stdout:\n${indent(logs.stdout)}\n` : "") +
|
|
118
|
+
(logs.stderr ? ` stderr:\n${indent(logs.stderr)}\n` : ""),
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Connection string for the Docker container (local dev credentials). */
|
|
123
|
+
export function dockerDbUrl(projectName: string, port: number): string {
|
|
124
|
+
// Local image has no TLS; sqlx/libpq default "prefer" can mis-handle the SSLRequest on some hosts.
|
|
125
|
+
return `postgres://${PG_USER}:postgres@127.0.0.1:${port}/${projectName}?sslmode=disable`
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function sleep(ms: number): Promise<void> {
|
|
129
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function indent(s: string): string {
|
|
133
|
+
return s
|
|
134
|
+
.trimEnd()
|
|
135
|
+
.split("\n")
|
|
136
|
+
.map((l) => ` ${l}`)
|
|
137
|
+
.join("\n")
|
|
138
|
+
}
|