@supatype/cli 0.1.0-alpha.10 → 0.1.0-alpha.11
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 +104 -71
- package/.turbo/turbo-typecheck.log +1 -1
- package/dist/app/framework.js +1 -3
- package/dist/app/framework.js.map +1 -1
- package/dist/app/proxy-dev-app.d.ts +14 -0
- package/dist/app/proxy-dev-app.d.ts.map +1 -1
- package/dist/app/proxy-dev-app.js +109 -6
- package/dist/app/proxy-dev-app.js.map +1 -1
- package/dist/binary-cache.d.ts +1 -1
- package/dist/binary-cache.d.ts.map +1 -1
- package/dist/binary-cache.js +6 -1
- package/dist/binary-cache.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts +3 -0
- package/dist/commands/adopt.d.ts.map +1 -0
- package/dist/commands/adopt.js +58 -0
- package/dist/commands/adopt.js.map +1 -0
- package/dist/commands/cloud.d.ts +4 -9
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +49 -91
- package/dist/commands/cloud.js.map +1 -1
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +25 -47
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +117 -74
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +21 -3
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +37 -37
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +77 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/functions.d.ts.map +1 -1
- package/dist/commands/functions.js +80 -33
- package/dist/commands/functions.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +26 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/introspect.d.ts +3 -0
- package/dist/commands/introspect.d.ts.map +1 -0
- package/dist/commands/introspect.js +34 -0
- package/dist/commands/introspect.js.map +1 -0
- package/dist/commands/link-helpers.d.ts +15 -0
- package/dist/commands/link-helpers.d.ts.map +1 -0
- package/dist/commands/link-helpers.js +187 -0
- package/dist/commands/link-helpers.js.map +1 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +116 -14
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +32 -5
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +102 -129
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +93 -29
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +6 -2
- package/dist/commands/update.js.map +1 -1
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/dev-compose.d.ts +23 -0
- package/dist/dev-compose.d.ts.map +1 -1
- package/dist/dev-compose.js +183 -6
- package/dist/dev-compose.js.map +1 -1
- package/dist/diff-output.d.ts +5 -1
- package/dist/diff-output.d.ts.map +1 -1
- package/dist/diff-output.js +69 -0
- package/dist/diff-output.js.map +1 -1
- package/dist/engine-client.d.ts +10 -1
- package/dist/engine-client.d.ts.map +1 -1
- package/dist/engine-client.js +64 -13
- package/dist/engine-client.js.map +1 -1
- package/dist/engine-push-output.d.ts +1 -0
- package/dist/engine-push-output.d.ts.map +1 -1
- package/dist/engine-push-output.js +4 -1
- package/dist/engine-push-output.js.map +1 -1
- package/dist/gitignore.d.ts +8 -0
- package/dist/gitignore.d.ts.map +1 -0
- package/dist/gitignore.js +41 -0
- package/dist/gitignore.js.map +1 -0
- package/dist/link.d.ts +66 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +159 -0
- package/dist/link.js.map +1 -0
- package/dist/process-manager.d.ts +2 -0
- package/dist/process-manager.d.ts.map +1 -1
- package/dist/process-manager.js +2 -0
- package/dist/process-manager.js.map +1 -1
- package/dist/project-config.d.ts +8 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js.map +1 -1
- package/dist/pull-utils.d.ts +50 -14
- package/dist/pull-utils.d.ts.map +1 -1
- package/dist/pull-utils.js +152 -12
- package/dist/pull-utils.js.map +1 -1
- package/dist/resolve-target.d.ts +86 -0
- package/dist/resolve-target.d.ts.map +1 -0
- package/dist/resolve-target.js +291 -0
- package/dist/resolve-target.js.map +1 -0
- package/dist/runtime-routes.d.ts.map +1 -1
- package/dist/runtime-routes.js +7 -0
- package/dist/runtime-routes.js.map +1 -1
- package/dist/schema-ast-v2.d.ts +1 -1
- package/dist/schema-ast-v2.d.ts.map +1 -1
- package/dist/schema-ast-v2.js +2 -2
- package/dist/schema-ast-v2.js.map +1 -1
- package/dist/schema-sources.d.ts +40 -0
- package/dist/schema-sources.d.ts.map +1 -0
- package/dist/schema-sources.js +183 -0
- package/dist/schema-sources.js.map +1 -0
- package/dist/self-host-compose.d.ts +10 -0
- package/dist/self-host-compose.d.ts.map +1 -1
- package/dist/self-host-compose.js +85 -3
- package/dist/self-host-compose.js.map +1 -1
- package/dist/storage-provision.d.ts +4 -0
- package/dist/storage-provision.d.ts.map +1 -1
- package/dist/storage-provision.js +24 -2
- package/dist/storage-provision.js.map +1 -1
- package/dist/target-client.d.ts +10 -0
- package/dist/target-client.d.ts.map +1 -0
- package/dist/target-client.js +22 -0
- package/dist/target-client.js.map +1 -0
- package/dist/type-extractor.d.ts +11 -0
- package/dist/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor.js +95 -8
- package/dist/type-extractor.js.map +1 -1
- package/package.json +1 -1
- package/src/app/framework.ts +1 -3
- package/src/app/proxy-dev-app.ts +113 -6
- package/src/binary-cache.ts +6 -1
- package/src/cli.ts +6 -0
- package/src/commands/adopt.ts +83 -0
- package/src/commands/cloud.ts +66 -108
- package/src/commands/db.ts +28 -52
- package/src/commands/deploy.ts +162 -104
- package/src/commands/dev.ts +24 -10
- package/src/commands/diff.ts +40 -41
- package/src/commands/doctor.ts +102 -0
- package/src/commands/functions.ts +95 -37
- package/src/commands/init.ts +25 -4
- package/src/commands/introspect.ts +47 -0
- package/src/commands/link-helpers.ts +228 -0
- package/src/commands/migrate.ts +163 -15
- package/src/commands/pull.ts +37 -9
- package/src/commands/push.ts +132 -166
- package/src/commands/status.ts +100 -33
- package/src/commands/update.ts +6 -2
- package/src/config.ts +2 -1
- package/src/dev-compose.ts +240 -6
- package/src/diff-output.ts +79 -1
- package/src/engine-client.ts +70 -13
- package/src/engine-push-output.ts +7 -3
- package/src/gitignore.ts +48 -0
- package/src/link.ts +242 -0
- package/src/process-manager.ts +4 -0
- package/src/project-config.ts +8 -0
- package/src/pull-utils.ts +217 -23
- package/src/resolve-target.ts +419 -0
- package/src/runtime-routes.ts +7 -0
- package/src/schema-ast-v2.ts +2 -1
- package/src/schema-sources.ts +248 -0
- package/src/self-host-compose.ts +87 -3
- package/src/storage-provision.ts +33 -1
- package/src/target-client.ts +40 -0
- package/src/type-extractor.ts +124 -11
- package/tests/cli-help.test.ts +27 -2
- package/tests/init.test.ts +1 -1
- package/tests/link.test.ts +148 -0
- package/tests/proxy-dev-app.test.ts +45 -1
- package/tests/pull-utils.test.ts +5 -4
- package/tests/runtime-contract.test.ts +44 -1
- package/tests/schema-sources.test.ts +119 -0
- package/tests/storage-provision.test.ts +100 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/commands/status.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* supatype status — show
|
|
2
|
+
* supatype status — show linked target or local dev stack state.
|
|
3
3
|
*/
|
|
4
4
|
import type { Command } from "commander"
|
|
5
5
|
import { spawnSync } from "node:child_process"
|
|
6
|
+
import { existsSync } from "node:fs"
|
|
7
|
+
import { resolve } from "node:path"
|
|
6
8
|
import { LOCAL_KONG_HOST_PORT, localKongBaseUrl } from "../local-gateway.js"
|
|
9
|
+
import { loadLocalEnvironment, loadProjectLink } from "../link.js"
|
|
10
|
+
import { resolveTarget, targetStatus } from "../resolve-target.js"
|
|
7
11
|
|
|
8
12
|
interface ServiceStatus {
|
|
9
13
|
name: string
|
|
@@ -16,42 +20,105 @@ interface ServiceStatus {
|
|
|
16
20
|
export function registerStatus(program: Command): void {
|
|
17
21
|
program
|
|
18
22
|
.command("status")
|
|
19
|
-
.description("Show
|
|
20
|
-
.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
for (const svc of services) {
|
|
39
|
-
const icon = svc.status === "running" ? "●" : svc.status === "stopped" ? "○" : "✕"
|
|
40
|
-
const status = svc.status.padEnd(8)
|
|
41
|
-
const port = svc.port ? `:${svc.port}` : ""
|
|
42
|
-
const uptime = svc.uptime ? ` (${svc.uptime})` : ""
|
|
43
|
-
console.log(` ${icon} ${svc.name.padEnd(maxName)} ${status} ${port}${uptime}`)
|
|
23
|
+
.description("Show linked target status or local dev services")
|
|
24
|
+
.option("--env <name>", "Target environment when linked")
|
|
25
|
+
.action(async (opts: { env?: string }) => {
|
|
26
|
+
const cwd = process.cwd()
|
|
27
|
+
const link = loadProjectLink(cwd)
|
|
28
|
+
const localEnv = loadLocalEnvironment(cwd)
|
|
29
|
+
|
|
30
|
+
if (link || localEnv) {
|
|
31
|
+
try {
|
|
32
|
+
const target = resolveTarget(cwd, { env: opts.env })
|
|
33
|
+
if (target.mode !== "direct") {
|
|
34
|
+
await printLinkedStatus(target)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error((err as Error).message)
|
|
39
|
+
process.exitCode = 1
|
|
40
|
+
return
|
|
41
|
+
}
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
printLocalStackStatus(cwd)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function printLinkedStatus(target: ReturnType<typeof resolveTarget>): Promise<void> {
|
|
49
|
+
console.log(`Target: ${target.mode} (${target.environment})`)
|
|
50
|
+
console.log(`Project: ${target.projectRef}`)
|
|
51
|
+
console.log(`API: ${target.apiBaseUrl}${target.apiPrefix}\n`)
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
try {
|
|
54
|
+
const data = (await targetStatus(target)) as Record<string, unknown>
|
|
55
|
+
if (data.functions && Array.isArray(data.functions)) {
|
|
56
|
+
console.log(`Functions (${data.functions.length}):`)
|
|
57
|
+
for (const fn of data.functions as Array<{ name?: string } | string>) {
|
|
58
|
+
const name = typeof fn === "string" ? fn : fn.name
|
|
59
|
+
if (name) console.log(` • ${name}`)
|
|
53
60
|
}
|
|
54
|
-
|
|
61
|
+
console.log()
|
|
62
|
+
}
|
|
63
|
+
if (data.deploymentId) {
|
|
64
|
+
console.log(`Active deployment: ${data.deploymentId}`)
|
|
65
|
+
}
|
|
66
|
+
if (data.controlPlane) {
|
|
67
|
+
console.log(`Control plane: ${data.controlPlane}`)
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.warn(`Could not fetch remote status: ${(err as Error).message}`)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function printLocalStackStatus(cwd: string): void {
|
|
75
|
+
const localEnv = loadLocalEnvironment(cwd)
|
|
76
|
+
const kongPort = localEnv?.kongPort ?? LOCAL_KONG_HOST_PORT
|
|
77
|
+
|
|
78
|
+
const services: ServiceStatus[] = [
|
|
79
|
+
{ name: "Postgres", container: "supatype-postgres", port: 5432 },
|
|
80
|
+
{ name: "PostgREST", container: "supatype-postgrest", port: 3000 },
|
|
81
|
+
{ name: "GoTrue", container: "supatype-gotrue", port: 9999 },
|
|
82
|
+
{ name: "Kong", container: "supatype-kong", port: kongPort },
|
|
83
|
+
{ name: "Control plane", container: "supatype-control-plane" },
|
|
84
|
+
{ name: "MinIO", container: "supatype-minio", port: 9000 },
|
|
85
|
+
{ name: "Realtime", container: "supatype-realtime", port: 4000 },
|
|
86
|
+
{ name: "Studio", container: "supatype-studio", port: 3100 },
|
|
87
|
+
].map((svc) => {
|
|
88
|
+
const status = getContainerStatus(svc.container)
|
|
89
|
+
const uptime = getContainerUptime(svc.container)
|
|
90
|
+
return { ...svc, status, ...(uptime !== undefined && { uptime }) }
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
console.log("Supatype Local Development Stack\n")
|
|
94
|
+
|
|
95
|
+
const maxName = Math.max(...services.map((s) => s.name.length))
|
|
96
|
+
for (const svc of services) {
|
|
97
|
+
const icon = svc.status === "running" ? "●" : svc.status === "stopped" ? "○" : "✕"
|
|
98
|
+
const status = svc.status.padEnd(8)
|
|
99
|
+
const port = svc.port ? `:${svc.port}` : ""
|
|
100
|
+
const uptime = svc.uptime ? ` (${svc.uptime})` : ""
|
|
101
|
+
console.log(` ${icon} ${svc.name.padEnd(maxName)} ${status} ${port}${uptime}`)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const running = services.filter((s) => s.status === "running")
|
|
105
|
+
console.log(`\n${running.length}/${services.length} services running`)
|
|
106
|
+
|
|
107
|
+
if (running.length > 0) {
|
|
108
|
+
const apiUrl = localEnv?.apiUrl ?? localKongBaseUrl()
|
|
109
|
+
console.log(`\nAPI URL: ${apiUrl}`)
|
|
110
|
+
console.log(`Studio: http://localhost:3100`)
|
|
111
|
+
if (localEnv?.databaseUrl) {
|
|
112
|
+
console.log(`Database: ${localEnv.databaseUrl}`)
|
|
113
|
+
} else {
|
|
114
|
+
console.log(`Database: postgresql://supatype_admin:postgres@localhost:5432/postgres`)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (existsSync(resolve(cwd, ".supatype/environment.json"))) {
|
|
119
|
+
console.log("\nLocal environment file: .supatype/environment.json")
|
|
120
|
+
console.log("Link remote ops: supatype link --url <api> --token $SERVICE_ROLE_KEY")
|
|
121
|
+
}
|
|
55
122
|
}
|
|
56
123
|
|
|
57
124
|
function getContainerStatus(name: string): "running" | "stopped" | "error" {
|
package/src/commands/update.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs"
|
|
|
8
8
|
import { basename, resolve } from "node:path"
|
|
9
9
|
import { loadConfig } from "../config.js"
|
|
10
10
|
import { resolveRuntimeProvider } from "../project-config.js"
|
|
11
|
-
import { runDockerCompose, writeSelfHostCompose } from "../self-host-compose.js"
|
|
11
|
+
import { runDockerCompose, writeSelfHostCompose, composePullNeedsIgnoreFailures } from "../self-host-compose.js"
|
|
12
12
|
import { syncComposeImagePins } from "../dev-compose.js"
|
|
13
13
|
import { download, currentPlatform, fetchAllLatestVersions, pinnedVersion, type Component } from "../binary-cache.js"
|
|
14
14
|
|
|
@@ -42,7 +42,11 @@ export function registerUpdate(program: Command): void {
|
|
|
42
42
|
const paths = writeSelfHostCompose(cwd, config, { devLocal: true })
|
|
43
43
|
syncComposeImagePins(cwd, config)
|
|
44
44
|
console.log("Pulling self-host compose images...")
|
|
45
|
-
const
|
|
45
|
+
const pullArgs = ["pull"]
|
|
46
|
+
if (composePullNeedsIgnoreFailures(config, cwd)) {
|
|
47
|
+
pullArgs.push("--ignore-pull-failures")
|
|
48
|
+
}
|
|
49
|
+
const status = runDockerCompose(paths.composePath, pullArgs, cwd)
|
|
46
50
|
if (status !== 0) process.exit(status)
|
|
47
51
|
console.log("Compose images updated.")
|
|
48
52
|
return
|
package/src/config.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type SupatypeProjectConfig,
|
|
10
10
|
} from "./project-config.js"
|
|
11
11
|
import { extractSchemaAstFromTypes } from "./type-extractor.js"
|
|
12
|
+
import type { ExtractedSchemaAstV2 } from "./schema-ast-v2.js"
|
|
12
13
|
|
|
13
14
|
export type { SupatypeProjectConfig } from "./project-config.js"
|
|
14
15
|
|
|
@@ -206,7 +207,7 @@ process.stdout.write(JSON.stringify(config))
|
|
|
206
207
|
export function loadSchemaAst(
|
|
207
208
|
schemaPath: string,
|
|
208
209
|
cwd: string = process.cwd(),
|
|
209
|
-
):
|
|
210
|
+
): ExtractedSchemaAstV2 {
|
|
210
211
|
const extracted = extractSchemaAstFromTypes(schemaPath, cwd)
|
|
211
212
|
if (extracted !== null) return extracted
|
|
212
213
|
|
package/src/dev-compose.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { startProxyDevApp } from "./app/proxy-dev-app.js"
|
|
|
10
10
|
import { loadSchemaAst } from "./config.js"
|
|
11
11
|
import {
|
|
12
12
|
COMPOSE_DEV_KONG_PORT,
|
|
13
|
+
connectionString,
|
|
13
14
|
projectRootFromConfig,
|
|
14
15
|
resolveRuntimeProvider,
|
|
15
16
|
schemaPathFromProject,
|
|
@@ -28,16 +29,21 @@ import {
|
|
|
28
29
|
} from "./self-host-compose.js"
|
|
29
30
|
import { hasEngineOverride } from "./binary-cache.js"
|
|
30
31
|
import { startStudioViteDevServer } from "./studio-dev-server.js"
|
|
31
|
-
import { ensureEngine, engineRequest } from "./engine-client.js"
|
|
32
|
+
import { ensureEngine, engineRequest, type DiffResult } from "./engine-client.js"
|
|
33
|
+
import { writeSchemaSourcePushArtifacts, type SchemaSourcePushArtifacts } from "./schema-sources.js"
|
|
32
34
|
import { endDevSession } from "./dev-session.js"
|
|
35
|
+
import { writeLocalEnvironment } from "./link.js"
|
|
33
36
|
import { registerDevShutdown } from "./dev-shutdown.js"
|
|
34
37
|
import {
|
|
35
38
|
filterComposeNoise,
|
|
36
39
|
formatEnginePushMessage,
|
|
40
|
+
parseEngineJsonOutput,
|
|
37
41
|
parseEnginePushOutput,
|
|
38
42
|
} from "./engine-push-output.js"
|
|
39
43
|
import { withAdminRoles } from "./studio-admin-roles.js"
|
|
40
44
|
import { restoreSystemRelationTargets } from "./restore-system-relation-targets.js"
|
|
45
|
+
import { provisionBucketsFromAst } from "./storage-provision.js"
|
|
46
|
+
import type { ExtractedSchemaAstV2 } from "./schema-ast-v2.js"
|
|
41
47
|
|
|
42
48
|
const LOCAL_JWT_SECRET = "super-secret-jwt-token-with-at-least-32-characters-long"
|
|
43
49
|
|
|
@@ -94,6 +100,72 @@ function hostComposeDbUrl(cwd: string): string {
|
|
|
94
100
|
return `postgresql://${user}:${pass}@127.0.0.1:${port}/${db}?sslmode=disable`
|
|
95
101
|
}
|
|
96
102
|
|
|
103
|
+
/**
|
|
104
|
+
* When `provider: docker` and `overrides.engine` is set, ensure Postgres is published
|
|
105
|
+
* on the host (SUPATYPE_DEV_DB_PORT) so the local engine binary can connect.
|
|
106
|
+
*/
|
|
107
|
+
export async function ensureDockerDbPublishedForHostEngine(
|
|
108
|
+
cwd: string,
|
|
109
|
+
config: SupatypeProjectConfig,
|
|
110
|
+
): Promise<void> {
|
|
111
|
+
if (resolveRuntimeProvider(config) !== "docker") {
|
|
112
|
+
throw new Error("ensureDockerDbPublishedForHostEngine requires provider: docker")
|
|
113
|
+
}
|
|
114
|
+
if (!hasEngineOverride(config)) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
"Docker Postgres is not published to the host without overrides.engine. " +
|
|
117
|
+
"Set overrides.engine in supatype.local.config.ts or pass --connection.",
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const project = composeProjectName(config.project.name)
|
|
122
|
+
const kongPort = await resolveKongPort(cwd)
|
|
123
|
+
const devDbPort = await resolveDevDbPort(cwd)
|
|
124
|
+
|
|
125
|
+
const now = Math.floor(Date.now() / 1000)
|
|
126
|
+
const jwtBase = { iss: "supatype", iat: now, exp: now + 315_360_000 }
|
|
127
|
+
const anonKey = signJwt({ ...jwtBase, role: "anon" }, LOCAL_JWT_SECRET)
|
|
128
|
+
const serviceRoleKey = signJwt({ ...jwtBase, role: "service_role" }, LOCAL_JWT_SECRET)
|
|
129
|
+
ensureDevComposeEnv(cwd, config, anonKey, serviceRoleKey, kongPort, devDbPort)
|
|
130
|
+
|
|
131
|
+
const paths = writeSelfHostCompose(cwd, config, { devLocal: true })
|
|
132
|
+
const up = runDockerCompose(paths.composePath, ["up", "-d", "db"], cwd, project, { quiet: true })
|
|
133
|
+
if (up !== 0) process.exit(up)
|
|
134
|
+
await waitComposeHealthy(paths, cwd, 120_000, project)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* True when CLI should publish local Compose Postgres for the host-side engine
|
|
139
|
+
* (local dev with overrides.engine). False for remote DB URLs via config or --connection.
|
|
140
|
+
*/
|
|
141
|
+
export function usesLocalDockerEngineDb(
|
|
142
|
+
config: SupatypeProjectConfig,
|
|
143
|
+
explicitConnection?: string,
|
|
144
|
+
): boolean {
|
|
145
|
+
if (explicitConnection?.trim()) return false
|
|
146
|
+
if (config.connection?.trim()) return false
|
|
147
|
+
return resolveRuntimeProvider(config) === "docker" && hasEngineOverride(config)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Resolve a Postgres URL reachable from the host-side engine binary.
|
|
152
|
+
* Local docker + overrides.engine → SUPATYPE_DEV_DB_PORT on localhost.
|
|
153
|
+
* Remote self-host → set `connection` in config or pass `--connection`.
|
|
154
|
+
*/
|
|
155
|
+
export async function resolveHostEngineDatabaseUrl(
|
|
156
|
+
cwd: string,
|
|
157
|
+
config: SupatypeProjectConfig,
|
|
158
|
+
explicit?: string,
|
|
159
|
+
): Promise<string> {
|
|
160
|
+
if (explicit?.trim()) return explicit
|
|
161
|
+
if (config.connection?.trim()) return config.connection
|
|
162
|
+
if (usesLocalDockerEngineDb(config)) {
|
|
163
|
+
await ensureDockerDbPublishedForHostEngine(cwd, config)
|
|
164
|
+
return hostComposeDbUrl(cwd)
|
|
165
|
+
}
|
|
166
|
+
return connectionString(config)
|
|
167
|
+
}
|
|
168
|
+
|
|
97
169
|
async function resolveKongPort(cwd: string): Promise<number> {
|
|
98
170
|
const envPath = join(cwd, ".env")
|
|
99
171
|
if (existsSync(envPath)) {
|
|
@@ -151,6 +223,8 @@ function ensureDevComposeEnv(
|
|
|
151
223
|
}
|
|
152
224
|
if (devDbPort !== undefined) {
|
|
153
225
|
updates.SUPATYPE_DEV_DB_PORT = String(devDbPort)
|
|
226
|
+
updates.DATABASE_URL =
|
|
227
|
+
`postgresql://supatype_admin:postgres@localhost:${devDbPort}/supatype?sslmode=disable`
|
|
154
228
|
}
|
|
155
229
|
const removeImageKeys = COMPOSE_PINNED_IMAGE_ENV_KEYS.filter((key) => !(key in imagePins))
|
|
156
230
|
upsertEnvFile(cwd, updates, removeImageKeys)
|
|
@@ -189,6 +263,14 @@ async function waitKongReady(kongPort: number, maxSec: number): Promise<void> {
|
|
|
189
263
|
throw new Error(`Kong gateway at ${base} did not become ready within ${maxSec}s`)
|
|
190
264
|
}
|
|
191
265
|
|
|
266
|
+
async function provisionDockerStorageBuckets(
|
|
267
|
+
ast: ExtractedSchemaAstV2,
|
|
268
|
+
kongPort: number,
|
|
269
|
+
serviceRoleKey: string,
|
|
270
|
+
): Promise<void> {
|
|
271
|
+
await provisionBucketsFromAst(ast, `http://localhost:${kongPort}/storage/v1`, serviceRoleKey)
|
|
272
|
+
}
|
|
273
|
+
|
|
192
274
|
let _lastPushedAst: string | null = null
|
|
193
275
|
let _lastFailedAst: string | null = null
|
|
194
276
|
let _composePushInFlight = false
|
|
@@ -279,12 +361,19 @@ async function runComposeSchemaPush(
|
|
|
279
361
|
console.log("[supatype] Applying schema via local engine (overrides.engine)...")
|
|
280
362
|
await ensureEngine()
|
|
281
363
|
const pgSchema = config.schema?.pg_schema ?? "public"
|
|
364
|
+
const sources = writeSchemaSourcePushArtifacts(cwd)
|
|
282
365
|
try {
|
|
283
366
|
await engineRequest("/push", {
|
|
284
367
|
ast,
|
|
285
368
|
database_url: hostComposeDbUrl(cwd),
|
|
286
369
|
schema: pgSchema,
|
|
287
370
|
force: true,
|
|
371
|
+
...(sources
|
|
372
|
+
? {
|
|
373
|
+
schema_sources_gz_base64: sources.payload.dataBase64,
|
|
374
|
+
schema_sources_manifest: sources.payload.manifest,
|
|
375
|
+
}
|
|
376
|
+
: {}),
|
|
288
377
|
})
|
|
289
378
|
} catch (err) {
|
|
290
379
|
_lastFailedAst = astJson
|
|
@@ -300,11 +389,12 @@ async function runComposeSchemaPush(
|
|
|
300
389
|
}
|
|
301
390
|
|
|
302
391
|
console.log("[supatype] Applying schema via compose schema-engine...")
|
|
303
|
-
|
|
392
|
+
const sources = writeSchemaSourcePushArtifacts(cwd)
|
|
393
|
+
let push = await runComposeEnginePush(paths, cwd, composeProject, config, sources)
|
|
304
394
|
// Windows Docker bind mounts can lag briefly after the host write.
|
|
305
395
|
if (push.status !== 0) {
|
|
306
396
|
await new Promise((r) => setTimeout(r, 250))
|
|
307
|
-
push = await runComposeEnginePush(paths, cwd, composeProject, config)
|
|
397
|
+
push = await runComposeEnginePush(paths, cwd, composeProject, config, sources)
|
|
308
398
|
}
|
|
309
399
|
if (push.status !== 0) {
|
|
310
400
|
_lastFailedAst = astJson
|
|
@@ -347,6 +437,7 @@ async function runComposeEnginePush(
|
|
|
347
437
|
cwd: string,
|
|
348
438
|
composeProject: string,
|
|
349
439
|
config: SupatypeProjectConfig,
|
|
440
|
+
sources?: SchemaSourcePushArtifacts | null,
|
|
350
441
|
): Promise<{ status: number; output: string }> {
|
|
351
442
|
const envFile = resolve(cwd, ".env")
|
|
352
443
|
const composeArgs = ["compose", "--progress", "quiet"]
|
|
@@ -370,6 +461,14 @@ async function runComposeEnginePush(
|
|
|
370
461
|
"--force",
|
|
371
462
|
"--non-interactive",
|
|
372
463
|
)
|
|
464
|
+
if (sources) {
|
|
465
|
+
composeArgs.push(
|
|
466
|
+
"--schema-sources-gz",
|
|
467
|
+
sources.dockerGzPath,
|
|
468
|
+
"--schema-sources-manifest",
|
|
469
|
+
sources.dockerManifestPath,
|
|
470
|
+
)
|
|
471
|
+
}
|
|
373
472
|
const pushEnv: NodeJS.ProcessEnv = {
|
|
374
473
|
...process.env,
|
|
375
474
|
COMPOSE_PROGRESS: "quiet",
|
|
@@ -399,6 +498,117 @@ async function runComposeEnginePush(
|
|
|
399
498
|
return { status: exitStatus, output }
|
|
400
499
|
}
|
|
401
500
|
|
|
501
|
+
async function runComposeEngineDiff(
|
|
502
|
+
paths: SelfHostComposePaths,
|
|
503
|
+
cwd: string,
|
|
504
|
+
composeProject: string,
|
|
505
|
+
config: SupatypeProjectConfig,
|
|
506
|
+
pgSchema: string,
|
|
507
|
+
): Promise<{ status: number; output: string; diff: DiffResult | null }> {
|
|
508
|
+
const envFile = resolve(cwd, ".env")
|
|
509
|
+
const composeArgs = ["compose", "--progress", "quiet"]
|
|
510
|
+
if (composeProject) composeArgs.push("-p", composeProject)
|
|
511
|
+
composeArgs.push("--project-directory", cwd)
|
|
512
|
+
composeArgs.push("-f", paths.composePath)
|
|
513
|
+
if (existsSync(envFile)) {
|
|
514
|
+
composeArgs.push("--env-file", envFile)
|
|
515
|
+
}
|
|
516
|
+
composeArgs.push(
|
|
517
|
+
"--profile",
|
|
518
|
+
"tools",
|
|
519
|
+
"run",
|
|
520
|
+
"--rm",
|
|
521
|
+
"schema-engine",
|
|
522
|
+
"diff",
|
|
523
|
+
"-i",
|
|
524
|
+
"/project/.supatype/schema.ast.json",
|
|
525
|
+
"--database-url",
|
|
526
|
+
composeDbUrl(),
|
|
527
|
+
"--schema",
|
|
528
|
+
pgSchema,
|
|
529
|
+
)
|
|
530
|
+
const diffEnv: NodeJS.ProcessEnv = {
|
|
531
|
+
...process.env,
|
|
532
|
+
COMPOSE_PROGRESS: "quiet",
|
|
533
|
+
}
|
|
534
|
+
const engineImage = await schemaEngineImageForPush(config)
|
|
535
|
+
if (engineImage) {
|
|
536
|
+
diffEnv.SUPATYPE_ENGINE_IMAGE = engineImage
|
|
537
|
+
}
|
|
538
|
+
const result = spawnSync("docker", composeArgs, {
|
|
539
|
+
cwd,
|
|
540
|
+
encoding: "utf8",
|
|
541
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
542
|
+
env: diffEnv,
|
|
543
|
+
})
|
|
544
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim()
|
|
545
|
+
const exitStatus = result.status ?? 1
|
|
546
|
+
const diff = parseEngineJsonOutput<DiffResult>(output)
|
|
547
|
+
|
|
548
|
+
return { status: exitStatus, output, diff }
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* `supatype diff` when `provider: docker`. Uses in-compose schema-engine unless
|
|
553
|
+
* `overrides.engine` is set — then Postgres is published to the host and diff runs
|
|
554
|
+
* through the local engine binary.
|
|
555
|
+
*/
|
|
556
|
+
export async function diffSchemaDocker(cwd: string, config: SupatypeProjectConfig): Promise<DiffResult> {
|
|
557
|
+
if (resolveRuntimeProvider(config) !== "docker") {
|
|
558
|
+
throw new Error("diffSchemaDocker requires provider: docker")
|
|
559
|
+
}
|
|
560
|
+
const project = composeProjectName(config.project.name)
|
|
561
|
+
const pgSchema = config.schema?.pg_schema ?? "public"
|
|
562
|
+
|
|
563
|
+
if (hasEngineOverride(config)) {
|
|
564
|
+
await ensureDockerDbPublishedForHostEngine(cwd, config)
|
|
565
|
+
const schemaPath = schemaPathFromProject(config, cwd)
|
|
566
|
+
const ast = loadSchemaAst(schemaPath, cwd)
|
|
567
|
+
await ensureEngine()
|
|
568
|
+
return engineRequest<DiffResult>("/diff", {
|
|
569
|
+
ast,
|
|
570
|
+
database_url: hostComposeDbUrl(cwd),
|
|
571
|
+
schema: pgSchema,
|
|
572
|
+
})
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const kongPort = await resolveKongPort(cwd)
|
|
576
|
+
const now = Math.floor(Date.now() / 1000)
|
|
577
|
+
const jwtBase = { iss: "supatype", iat: now, exp: now + 315_360_000 }
|
|
578
|
+
const anonKey = signJwt({ ...jwtBase, role: "anon" }, LOCAL_JWT_SECRET)
|
|
579
|
+
const serviceRoleKey = signJwt({ ...jwtBase, role: "service_role" }, LOCAL_JWT_SECRET)
|
|
580
|
+
ensureDevComposeEnv(cwd, config, anonKey, serviceRoleKey, kongPort, undefined)
|
|
581
|
+
|
|
582
|
+
const paths = writeSelfHostCompose(cwd, config, { devLocal: true })
|
|
583
|
+
|
|
584
|
+
const up = runDockerCompose(paths.composePath, ["up", "-d", "db"], cwd, project, { quiet: true })
|
|
585
|
+
if (up !== 0) process.exit(up)
|
|
586
|
+
await waitComposeHealthy(paths, cwd, 120_000, project)
|
|
587
|
+
|
|
588
|
+
const schemaPath = schemaPathFromProject(config, cwd)
|
|
589
|
+
const ast = loadSchemaAst(schemaPath, cwd)
|
|
590
|
+
|
|
591
|
+
const supatypeDir = join(cwd, ".supatype")
|
|
592
|
+
mkdirSync(supatypeDir, { recursive: true })
|
|
593
|
+
const astPath = join(supatypeDir, "schema.ast.json")
|
|
594
|
+
writeFileSync(astPath, JSON.stringify(ast))
|
|
595
|
+
|
|
596
|
+
let result = await runComposeEngineDiff(paths, cwd, project, config, pgSchema)
|
|
597
|
+
// Windows Docker bind mounts can lag briefly after the host write.
|
|
598
|
+
if (result.status !== 0) {
|
|
599
|
+
await new Promise((r) => setTimeout(r, 250))
|
|
600
|
+
result = await runComposeEngineDiff(paths, cwd, project, config, pgSchema)
|
|
601
|
+
}
|
|
602
|
+
if (result.status !== 0) {
|
|
603
|
+
const detail = filterComposeNoise(result.output) || result.output
|
|
604
|
+
throw new Error(detail || `Engine schema diff failed (exit ${result.status})`)
|
|
605
|
+
}
|
|
606
|
+
if (!result.diff) {
|
|
607
|
+
throw new Error("Engine diff returned no result")
|
|
608
|
+
}
|
|
609
|
+
return result.diff
|
|
610
|
+
}
|
|
611
|
+
|
|
402
612
|
/**
|
|
403
613
|
* `supatype push` when `provider: docker`. Uses in-compose schema-engine unless
|
|
404
614
|
* `overrides.engine` is set — then Postgres is published to the host and push runs
|
|
@@ -426,7 +636,14 @@ export async function pushSchemaDocker(cwd: string, config: SupatypeProjectConfi
|
|
|
426
636
|
await waitComposeHealthy(paths, cwd, 120_000, project)
|
|
427
637
|
|
|
428
638
|
const schemaPath = schemaPathFromProject(config, cwd)
|
|
639
|
+
const ast = loadSchemaAst(schemaPath, cwd)
|
|
429
640
|
await runComposeSchemaPush(cwd, config, paths, schemaPath, project)
|
|
641
|
+
|
|
642
|
+
const upGateway = runDockerCompose(paths.composePath, ["up", "-d"], cwd, project, { quiet: true })
|
|
643
|
+
if (upGateway !== 0) process.exit(upGateway)
|
|
644
|
+
await waitKongReady(kongPort, 120)
|
|
645
|
+
await provisionDockerStorageBuckets(ast, kongPort, serviceRoleKey)
|
|
646
|
+
|
|
430
647
|
console.log("[supatype] Schema pushed.")
|
|
431
648
|
}
|
|
432
649
|
|
|
@@ -468,6 +685,18 @@ export async function runDevCompose(cwd: string, config: SupatypeProjectConfig,
|
|
|
468
685
|
console.log("[supatype] Waiting for API gateway...")
|
|
469
686
|
await waitKongReady(kongPort, 120)
|
|
470
687
|
|
|
688
|
+
writeLocalEnvironment(cwd, {
|
|
689
|
+
target: "local",
|
|
690
|
+
apiUrl: `http://localhost:${kongPort}`,
|
|
691
|
+
databaseUrl: hasEngineOverride(config) ? hostComposeDbUrl(cwd) : composeDbUrl(),
|
|
692
|
+
projectRef: config.project.name,
|
|
693
|
+
kongPort,
|
|
694
|
+
provider: "docker",
|
|
695
|
+
})
|
|
696
|
+
|
|
697
|
+
const ast = loadSchemaAst(schemaPath, cwd)
|
|
698
|
+
await provisionDockerStorageBuckets(ast, kongPort, serviceRoleKey)
|
|
699
|
+
|
|
471
700
|
const pidDir = join(homedir(), ".supatype", "projects", config.project.name, "pid")
|
|
472
701
|
mkdirSync(pidDir, { recursive: true })
|
|
473
702
|
|
|
@@ -538,9 +767,14 @@ export async function runDevCompose(cwd: string, config: SupatypeProjectConfig,
|
|
|
538
767
|
debounceTimer = setTimeout(() => {
|
|
539
768
|
debounceTimer = null
|
|
540
769
|
console.log(`\n[supatype] Change detected in ${filename}, pushing schema...`)
|
|
541
|
-
runComposeSchemaPushQueued(cwd, config, paths, schemaPath, project)
|
|
542
|
-
|
|
543
|
-
|
|
770
|
+
runComposeSchemaPushQueued(cwd, config, paths, schemaPath, project)
|
|
771
|
+
.then(async () => {
|
|
772
|
+
const updatedAst = loadSchemaAst(schemaPath, cwd)
|
|
773
|
+
await provisionDockerStorageBuckets(updatedAst, kongPort, serviceRoleKey)
|
|
774
|
+
})
|
|
775
|
+
.catch((e: unknown) =>
|
|
776
|
+
console.error("[supatype] Schema push failed:", (e as Error).message),
|
|
777
|
+
)
|
|
544
778
|
}, 300)
|
|
545
779
|
})
|
|
546
780
|
}
|
package/src/diff-output.ts
CHANGED
|
@@ -1,4 +1,44 @@
|
|
|
1
|
-
import type { DiffResult } from "./engine-client.js"
|
|
1
|
+
import type { DiffResult, Operation } from "./engine-client.js"
|
|
2
|
+
|
|
3
|
+
/** Human-readable label for a single schema operation. */
|
|
4
|
+
export function formatOperation(op: Operation): string {
|
|
5
|
+
if (typeof op.description === "string" && op.description.trim().length > 0) {
|
|
6
|
+
return op.description
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const kind = typeof op.type === "string" ? op.type : typeof op.kind === "string" ? op.kind : "operation"
|
|
10
|
+
const raw = op as unknown as Record<string, unknown>
|
|
11
|
+
const table = raw["table"]
|
|
12
|
+
const column = raw["column"]
|
|
13
|
+
const index = raw["index"]
|
|
14
|
+
const sql = typeof op.sql === "string" ? op.sql.trim() : ""
|
|
15
|
+
|
|
16
|
+
if (kind === "add_unique_constraint" || kind === "drop_unique_constraint") {
|
|
17
|
+
const constraint = typeof raw["constraint"] === "string" ? raw["constraint"] : null
|
|
18
|
+
if (typeof table === "string" && constraint) return `${kind} ${table}.${constraint}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (kind === "create_index" || kind === "drop_index" || kind === "add_index") {
|
|
22
|
+
const indexName = typeof index === "string" ? index : typeof raw["name"] === "string" ? raw["name"] : null
|
|
23
|
+
const fields = Array.isArray(raw["fields"]) ? raw["fields"].join(", ") : null
|
|
24
|
+
if (indexName && fields) return `${kind} ${table}.${indexName} (${fields})`
|
|
25
|
+
if (indexName) return `${kind} ${table}.${indexName}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof table === "string" && typeof column === "string") {
|
|
29
|
+
return `${kind} ${table}.${column}`
|
|
30
|
+
}
|
|
31
|
+
if (typeof table === "string") {
|
|
32
|
+
return `${kind} ${table}`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (sql) {
|
|
36
|
+
const oneLine = sql.replace(/\s+/g, " ").slice(0, 120)
|
|
37
|
+
return `${kind}: ${oneLine}${sql.length > 120 ? "…" : ""}`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return kind
|
|
41
|
+
}
|
|
2
42
|
|
|
3
43
|
/** Print engine diff warnings before the operation list. */
|
|
4
44
|
export function printDiffWarnings(diff: DiffResult): void {
|
|
@@ -10,3 +50,41 @@ export function printDiffWarnings(diff: DiffResult): void {
|
|
|
10
50
|
}
|
|
11
51
|
console.log()
|
|
12
52
|
}
|
|
53
|
+
|
|
54
|
+
const RISK_SYMBOL: Record<NonNullable<DiffResult["operations"][number]["risk"]>, string> = {
|
|
55
|
+
safe: "+",
|
|
56
|
+
warn: "~",
|
|
57
|
+
cautious: "~",
|
|
58
|
+
danger: "!",
|
|
59
|
+
destructive: "!",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const RISK_LEGEND: Record<NonNullable<DiffResult["operations"][number]["risk"]>, string> = {
|
|
63
|
+
safe: "safe",
|
|
64
|
+
warn: "caution",
|
|
65
|
+
cautious: "caution",
|
|
66
|
+
danger: "DANGER",
|
|
67
|
+
destructive: "DANGER",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Print planned schema operations from a diff result. */
|
|
71
|
+
export function printDiffOperations(diff: DiffResult): void {
|
|
72
|
+
const ops = diff.operations ?? []
|
|
73
|
+
if (ops.length === 0) {
|
|
74
|
+
console.log("No changes.")
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(`\n${ops.length} change(s):\n`)
|
|
79
|
+
for (const op of ops) {
|
|
80
|
+
const r = op.risk ?? "safe"
|
|
81
|
+
const label = op.warning ?? formatOperation(op)
|
|
82
|
+
console.log(` [${RISK_SYMBOL[r]}] ${label} (${RISK_LEGEND[r]})`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const dangerous = ops.filter((o) => o.risk === "danger").length
|
|
86
|
+
if (dangerous > 0) {
|
|
87
|
+
console.log(`\n ${dangerous} dangerous operation(s). Review before pushing.`)
|
|
88
|
+
}
|
|
89
|
+
console.log()
|
|
90
|
+
}
|