@supatype/cli 0.1.0-alpha.13 → 0.1.0-alpha.15
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 +209 -92
- package/.turbo/turbo-typecheck.log +1 -1
- package/dist/app-config.d.ts +10 -0
- package/dist/app-config.d.ts.map +1 -1
- package/dist/app-config.js +72 -0
- package/dist/app-config.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/add.d.ts +3 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +83 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/app.js +2 -2
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/init.d.ts +29 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +569 -90
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/keys.d.ts +15 -1
- package/dist/commands/keys.d.ts.map +1 -1
- package/dist/commands/keys.js +39 -4
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/self-host.d.ts.map +1 -1
- package/dist/commands/self-host.js +5 -5
- package/dist/commands/self-host.js.map +1 -1
- package/dist/kong-config.d.ts +9 -0
- package/dist/kong-config.d.ts.map +1 -1
- package/dist/kong-config.js +18 -1
- package/dist/kong-config.js.map +1 -1
- package/dist/project-config.d.ts +16 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +34 -0
- package/dist/project-config.js.map +1 -1
- package/dist/prompts.d.ts +8 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +20 -0
- package/dist/prompts.js.map +1 -0
- package/dist/self-host-compose.d.ts.map +1 -1
- package/dist/self-host-compose.js +62 -17
- package/dist/self-host-compose.js.map +1 -1
- package/package.json +2 -1
- package/src/app-config.ts +80 -0
- package/src/cli.ts +2 -0
- package/src/commands/add.ts +94 -0
- package/src/commands/app.ts +2 -2
- package/src/commands/init.ts +738 -88
- package/src/commands/keys.ts +49 -4
- package/src/commands/self-host.ts +25 -5
- package/src/kong-config.ts +24 -1
- package/src/project-config.ts +45 -0
- package/src/prompts.ts +21 -0
- package/src/self-host-compose.ts +62 -17
- package/tests/config.test.ts +26 -0
- package/tests/init.test.ts +128 -15
- package/tests/runtime-contract.test.ts +111 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/commands/keys.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Command } from "commander"
|
|
2
|
-
import { readFileSync, existsSync } from "node:fs"
|
|
2
|
+
import { readFileSync, existsSync, writeFileSync } from "node:fs"
|
|
3
3
|
import { resolve } from "node:path"
|
|
4
4
|
import { signJwt } from "../jwt.js"
|
|
5
5
|
|
|
@@ -41,13 +41,58 @@ export function registerKeys(program: Command): void {
|
|
|
41
41
|
|
|
42
42
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
/** Mint a long-lived anon + service_role JWT pair from a secret. */
|
|
45
|
+
export function signKeyPair(
|
|
46
|
+
secret: string,
|
|
47
|
+
expYears = 10,
|
|
48
|
+
): { anonKey: string; serviceKey: string } {
|
|
49
|
+
const now = Math.floor(Date.now() / 1000)
|
|
50
|
+
const exp = now + expYears * 365 * 24 * 60 * 60
|
|
51
|
+
return {
|
|
52
|
+
anonKey: signJwt({ iss: "supatype", role: "anon", iat: now, exp }, secret),
|
|
53
|
+
serviceKey: signJwt({ iss: "supatype", role: "service_role", iat: now, exp }, secret),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generate keys from the JWT_SECRET found in `dir`'s .env (or env var) and
|
|
59
|
+
* rewrite the ANON_KEY / SERVICE_ROLE_KEY lines in that .env file in place.
|
|
60
|
+
* Returns the minted pair, or null if no secret could be resolved.
|
|
61
|
+
*/
|
|
62
|
+
export function generateAndWriteKeys(
|
|
63
|
+
dir: string,
|
|
64
|
+
expYears = 10,
|
|
65
|
+
): { anonKey: string; serviceKey: string } | null {
|
|
66
|
+
const secret = resolveSecret(dir)
|
|
67
|
+
if (!secret) return null
|
|
68
|
+
|
|
69
|
+
const { anonKey, serviceKey } = signKeyPair(secret, expYears)
|
|
70
|
+
|
|
71
|
+
const envPath = resolve(dir, ".env")
|
|
72
|
+
if (existsSync(envPath)) {
|
|
73
|
+
let content = readFileSync(envPath, "utf8")
|
|
74
|
+
content = upsertEnvVar(content, "ANON_KEY", anonKey)
|
|
75
|
+
content = upsertEnvVar(content, "SERVICE_ROLE_KEY", serviceKey)
|
|
76
|
+
writeFileSync(envPath, content, "utf8")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { anonKey, serviceKey }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function upsertEnvVar(content: string, key: string, value: string): string {
|
|
83
|
+
const re = new RegExp(`^${key}=.*$`, "m")
|
|
84
|
+
if (re.test(content)) return content.replace(re, `${key}=${value}`)
|
|
85
|
+
const sep = content.endsWith("\n") || content.length === 0 ? "" : "\n"
|
|
86
|
+
return `${content}${sep}${key}=${value}\n`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function resolveSecret(dir: string = process.cwd()): string | undefined {
|
|
45
90
|
// 1. Check environment variable
|
|
46
91
|
const fromEnv = process.env["JWT_SECRET"]
|
|
47
92
|
if (fromEnv) return fromEnv
|
|
48
93
|
|
|
49
|
-
// 2. Parse .env file in
|
|
50
|
-
const envPath = resolve(
|
|
94
|
+
// 2. Parse .env file in the target directory
|
|
95
|
+
const envPath = resolve(dir, ".env")
|
|
51
96
|
if (!existsSync(envPath)) return undefined
|
|
52
97
|
|
|
53
98
|
try {
|
|
@@ -17,7 +17,7 @@ import { resolveBinary } from "../binary-cache.js"
|
|
|
17
17
|
import { generateUnits } from "../systemd.js"
|
|
18
18
|
import { readPid } from "../process-manager.js"
|
|
19
19
|
import { localStorageEnv } from "../local-storage.js"
|
|
20
|
-
import { runDockerCompose, writeSelfHostCompose } from "../self-host-compose.js"
|
|
20
|
+
import { composeProjectName, runDockerCompose, writeSelfHostCompose } from "../self-host-compose.js"
|
|
21
21
|
|
|
22
22
|
export function registerSelfHost(program: Command): void {
|
|
23
23
|
const selfHostCmd = program
|
|
@@ -47,7 +47,12 @@ export function registerSelfHost(program: Command): void {
|
|
|
47
47
|
const cwd = process.cwd()
|
|
48
48
|
const config = loadConfig(cwd)
|
|
49
49
|
const out = writeSelfHostCompose(cwd, config)
|
|
50
|
-
const status = runDockerCompose(
|
|
50
|
+
const status = runDockerCompose(
|
|
51
|
+
out.composePath,
|
|
52
|
+
opts.detach ? ["up", "-d"] : ["up"],
|
|
53
|
+
cwd,
|
|
54
|
+
composeProjectName(config.project.name),
|
|
55
|
+
)
|
|
51
56
|
process.exitCode = status
|
|
52
57
|
})
|
|
53
58
|
|
|
@@ -58,7 +63,12 @@ export function registerSelfHost(program: Command): void {
|
|
|
58
63
|
const cwd = process.cwd()
|
|
59
64
|
const config = loadConfig(cwd)
|
|
60
65
|
const out = writeSelfHostCompose(cwd, config)
|
|
61
|
-
process.exitCode = runDockerCompose(
|
|
66
|
+
process.exitCode = runDockerCompose(
|
|
67
|
+
out.composePath,
|
|
68
|
+
["down"],
|
|
69
|
+
cwd,
|
|
70
|
+
composeProjectName(config.project.name),
|
|
71
|
+
)
|
|
62
72
|
})
|
|
63
73
|
|
|
64
74
|
composeCmd
|
|
@@ -68,7 +78,12 @@ export function registerSelfHost(program: Command): void {
|
|
|
68
78
|
const cwd = process.cwd()
|
|
69
79
|
const config = loadConfig(cwd)
|
|
70
80
|
const out = writeSelfHostCompose(cwd, config)
|
|
71
|
-
process.exitCode = runDockerCompose(
|
|
81
|
+
process.exitCode = runDockerCompose(
|
|
82
|
+
out.composePath,
|
|
83
|
+
["ps"],
|
|
84
|
+
cwd,
|
|
85
|
+
composeProjectName(config.project.name),
|
|
86
|
+
)
|
|
72
87
|
})
|
|
73
88
|
|
|
74
89
|
composeCmd
|
|
@@ -83,7 +98,12 @@ export function registerSelfHost(program: Command): void {
|
|
|
83
98
|
const args = ["logs"]
|
|
84
99
|
if (opts.follow) args.push("-f")
|
|
85
100
|
if (opts.service) args.push(opts.service)
|
|
86
|
-
process.exitCode = runDockerCompose(
|
|
101
|
+
process.exitCode = runDockerCompose(
|
|
102
|
+
out.composePath,
|
|
103
|
+
args,
|
|
104
|
+
cwd,
|
|
105
|
+
composeProjectName(config.project.name),
|
|
106
|
+
)
|
|
87
107
|
})
|
|
88
108
|
|
|
89
109
|
// ── Legacy native/systemd helpers (hidden; use compose for self-host) ─────
|
package/src/kong-config.ts
CHANGED
|
@@ -18,6 +18,11 @@ export interface KongDeclarativeOptions {
|
|
|
18
18
|
studioServiceUrl?: string | undefined
|
|
19
19
|
/** See {@link RuntimeRouteOptions.studioStripPath}. */
|
|
20
20
|
studioStripPath?: boolean | undefined
|
|
21
|
+
/**
|
|
22
|
+
* When set, append a global Kong `acme` plugin (Let's Encrypt) with Redis/Valkey
|
|
23
|
+
* storage so the self-host gateway provisions and renews TLS automatically.
|
|
24
|
+
*/
|
|
25
|
+
acme?: { email: string; domain: string; redisHost: string } | undefined
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
/** Escape a string for use inside YAML double quotes. */
|
|
@@ -85,9 +90,27 @@ ${route.paths.map((path) => ` - ${path}`).join("\n")}
|
|
|
85
90
|
${protocols}${routePlugins}${graphqlPlugins}`
|
|
86
91
|
}).join("\n")
|
|
87
92
|
|
|
93
|
+
const acme = opts.acme
|
|
94
|
+
const pluginsBlock = acme
|
|
95
|
+
? `
|
|
96
|
+
plugins:
|
|
97
|
+
- name: acme
|
|
98
|
+
config:
|
|
99
|
+
account_email: ${yamlQuotedString(acme.email)}
|
|
100
|
+
tos_accepted: true
|
|
101
|
+
domains:
|
|
102
|
+
- ${yamlQuotedString(acme.domain)}
|
|
103
|
+
storage: redis
|
|
104
|
+
storage_config:
|
|
105
|
+
redis:
|
|
106
|
+
host: ${yamlQuotedString(acme.redisHost)}
|
|
107
|
+
port: 6379
|
|
108
|
+
`
|
|
109
|
+
: ""
|
|
110
|
+
|
|
88
111
|
return `_format_version: "3.0"
|
|
89
112
|
${consumersBlock}
|
|
90
113
|
services:
|
|
91
114
|
${servicesBlock}
|
|
92
|
-
`
|
|
115
|
+
${pluginsBlock}`
|
|
93
116
|
}
|
package/src/project-config.ts
CHANGED
|
@@ -59,6 +59,16 @@ export interface SupatypeProjectConfig {
|
|
|
59
59
|
postgrestPort?: number
|
|
60
60
|
/** Domain for ACME TLS certificate (mode=standalone). */
|
|
61
61
|
domain?: string
|
|
62
|
+
/**
|
|
63
|
+
* TLS for self-host HTTPS (Kong ACME / Let's Encrypt).
|
|
64
|
+
* Requires `mode: "standalone"` and a non-empty `domain`.
|
|
65
|
+
*/
|
|
66
|
+
tls?: {
|
|
67
|
+
/** ACME contact email for Let's Encrypt (required to enable HTTPS). */
|
|
68
|
+
email?: string
|
|
69
|
+
/** "kong" (default) = Kong acme plugin; "none" = stay HTTP even with a domain. */
|
|
70
|
+
provider?: "kong" | "none"
|
|
71
|
+
}
|
|
62
72
|
}
|
|
63
73
|
app: {
|
|
64
74
|
/**
|
|
@@ -298,6 +308,23 @@ export function mergeProjectConfig(
|
|
|
298
308
|
...(base.admin !== undefined || override.admin !== undefined
|
|
299
309
|
? { admin: { ...base.admin, ...override.admin } as NonNullable<SupatypeProjectConfig["admin"]> }
|
|
300
310
|
: {}),
|
|
311
|
+
...(base.environments !== undefined || override.environments !== undefined
|
|
312
|
+
? (() => {
|
|
313
|
+
const b = base.environments
|
|
314
|
+
const o = override.environments
|
|
315
|
+
const mergedBranchDefaults =
|
|
316
|
+
b?.branchDefaults !== undefined || o?.branchDefaults !== undefined
|
|
317
|
+
? { ...(b?.branchDefaults ?? {}), ...(o?.branchDefaults ?? {}) }
|
|
318
|
+
: undefined
|
|
319
|
+
return {
|
|
320
|
+
environments: {
|
|
321
|
+
...b,
|
|
322
|
+
...o,
|
|
323
|
+
...(mergedBranchDefaults !== undefined ? { branchDefaults: mergedBranchDefaults } : {}),
|
|
324
|
+
} as NonNullable<SupatypeProjectConfig["environments"]>,
|
|
325
|
+
}
|
|
326
|
+
})()
|
|
327
|
+
: {}),
|
|
301
328
|
}
|
|
302
329
|
}
|
|
303
330
|
|
|
@@ -373,6 +400,24 @@ export function serverBaseUrl(cfg: SupatypeProjectConfig): string | undefined {
|
|
|
373
400
|
}
|
|
374
401
|
}
|
|
375
402
|
|
|
403
|
+
/**
|
|
404
|
+
* True when `supatype self-host compose` should render Kong ACME TLS (Let's Encrypt).
|
|
405
|
+
* Gated on a real self-host render (not `supatype dev`), standalone mode, a non-empty
|
|
406
|
+
* domain, an ACME contact email, and `tls.provider !== "none"`.
|
|
407
|
+
*/
|
|
408
|
+
export function selfHostTlsEnabled(
|
|
409
|
+
cfg: SupatypeProjectConfig,
|
|
410
|
+
devLocal = false,
|
|
411
|
+
): boolean {
|
|
412
|
+
if (devLocal) return false
|
|
413
|
+
if (cfg.server.mode !== "standalone") return false
|
|
414
|
+
const domain = cfg.server.domain?.trim()
|
|
415
|
+
if (!domain) return false
|
|
416
|
+
const tls = cfg.server.tls
|
|
417
|
+
if (!tls || tls.provider === "none") return false
|
|
418
|
+
return Boolean(tls.email?.trim())
|
|
419
|
+
}
|
|
420
|
+
|
|
376
421
|
/** Resolved runtime provider (`config.provider` ?? `database.provider` ?? native). */
|
|
377
422
|
export function resolveRuntimeProvider(cfg: SupatypeProjectConfig): "native" | "docker" {
|
|
378
423
|
return cfg.provider ?? cfg.database.provider ?? "native"
|
package/src/prompts.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as p from "@clack/prompts"
|
|
2
|
+
import { SUPATYPE_ASCII_LOGO_WORDMARK, colorLogoLines } from "./dev-logo.js"
|
|
3
|
+
|
|
4
|
+
/** Print the coloured Supatype ASCII wordmark at the top of an interactive command. */
|
|
5
|
+
export function printLogo(): void {
|
|
6
|
+
console.log()
|
|
7
|
+
console.log(colorLogoLines([...SUPATYPE_ASCII_LOGO_WORDMARK]).join("\n"))
|
|
8
|
+
console.log()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Unwrap a clack prompt result, exiting cleanly when the user cancels (Ctrl-C).
|
|
13
|
+
* Shared by all interactive commands so cancellation behaves consistently.
|
|
14
|
+
*/
|
|
15
|
+
export function ensureNotCancelled<T>(value: T | symbol, cancelMessage = "Cancelled."): T {
|
|
16
|
+
if (p.isCancel(value)) {
|
|
17
|
+
p.cancel(cancelMessage)
|
|
18
|
+
process.exit(0)
|
|
19
|
+
}
|
|
20
|
+
return value as T
|
|
21
|
+
}
|
package/src/self-host-compose.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
2
2
|
import { dirname, join, relative, resolve } from "node:path"
|
|
3
3
|
import { spawnSync } from "node:child_process"
|
|
4
|
-
import { preferredFunctionsPathFromProject, type SupatypeProjectConfig } from "./project-config.js"
|
|
4
|
+
import { preferredFunctionsPathFromProject, selfHostTlsEnabled, type SupatypeProjectConfig } from "./project-config.js"
|
|
5
5
|
import { hasEngineOverride, hasStudioOverride, pinnedVersion, fetchLatestVersion, VERSION_PIN_LOCAL } from "./binary-cache.js"
|
|
6
6
|
import { buildKongDeclarative } from "./kong-config.js"
|
|
7
7
|
|
|
@@ -228,6 +228,11 @@ export function renderSelfHostCompose(
|
|
|
228
228
|
const projectMount = projectMountPath(cwd)
|
|
229
229
|
const kongMount = kongMountPath(cwd)
|
|
230
230
|
const devLocal = options?.devLocal === true
|
|
231
|
+
const tlsEnabled = selfHostTlsEnabled(config, devLocal)
|
|
232
|
+
const domain = config.server.domain?.trim() ?? ""
|
|
233
|
+
// When TLS is on, default external URLs to https://<domain> so auth links/redirects use HTTPS.
|
|
234
|
+
const externalUrlFallback = tlsEnabled ? `https://${domain}` : "http://localhost:18473"
|
|
235
|
+
const siteUrlFallback = tlsEnabled ? `https://${domain}` : "http://localhost:3000"
|
|
231
236
|
const studioHostDev = devLocal && hasStudioOverride(config)
|
|
232
237
|
const appEnv = serverAppEnvForCompose(config, devLocal)
|
|
233
238
|
const staticDir = staticDirForCompose(config) ?? "./dist"
|
|
@@ -239,7 +244,7 @@ export function renderSelfHostCompose(
|
|
|
239
244
|
studio:
|
|
240
245
|
${studioService}
|
|
241
246
|
environment:
|
|
242
|
-
SUPATYPE_CLOUD_JSON: '{"url":"\${API_EXTERNAL_URL
|
|
247
|
+
SUPATYPE_CLOUD_JSON: '{"url":"\${API_EXTERNAL_URL:-${externalUrlFallback}}","anonKey":"\${ANON_KEY:-}"}'
|
|
243
248
|
expose:
|
|
244
249
|
- "3002"
|
|
245
250
|
`
|
|
@@ -270,6 +275,44 @@ ${studioService}
|
|
|
270
275
|
- "9000:9000"
|
|
271
276
|
- "9001:9001"
|
|
272
277
|
`
|
|
278
|
+
const kongTlsEnv = tlsEnabled
|
|
279
|
+
? ` KONG_PROXY_LISTEN: "0.0.0.0:8000, 0.0.0.0:8443 ssl"
|
|
280
|
+
KONG_LUA_SSL_TRUSTED_CERTIFICATE: system
|
|
281
|
+
KONG_LUA_SSL_VERIFY_DEPTH: "2"
|
|
282
|
+
`
|
|
283
|
+
: ""
|
|
284
|
+
const kongPorts = tlsEnabled
|
|
285
|
+
? ` - "80:8000"
|
|
286
|
+
- "443:8443"`
|
|
287
|
+
: ` - "\${SUPATYPE_KONG_PORT:-18473}:8000"`
|
|
288
|
+
const kongTlsDependsOn = tlsEnabled ? "\n - valkey" : ""
|
|
289
|
+
const valkeyBlock = tlsEnabled
|
|
290
|
+
? `
|
|
291
|
+
valkey:
|
|
292
|
+
image: \${SUPATYPE_VALKEY_IMAGE:-valkey/valkey:8-alpine}
|
|
293
|
+
command: ["valkey-server", "--appendonly", "yes"]
|
|
294
|
+
expose:
|
|
295
|
+
- "6379"
|
|
296
|
+
volumes:
|
|
297
|
+
- valkey-data:/data
|
|
298
|
+
`
|
|
299
|
+
: ""
|
|
300
|
+
const tlsHintComment = tlsEnabled
|
|
301
|
+
? ""
|
|
302
|
+
: ` # HTTPS is off. To enable automatic TLS (Let's Encrypt) for production, set in supatype.config.ts:
|
|
303
|
+
# server: { mode: "standalone", domain: "your.domain", tls: { email: "you@example.com" } }
|
|
304
|
+
# then re-run \`supatype self-host compose up -d\`. Kong publishes :80/:443 and provisions certs automatically.
|
|
305
|
+
`
|
|
306
|
+
const volumesBlock = tlsEnabled
|
|
307
|
+
? `volumes:
|
|
308
|
+
db-data:
|
|
309
|
+
minio-data:
|
|
310
|
+
valkey-data:
|
|
311
|
+
`
|
|
312
|
+
: `volumes:
|
|
313
|
+
db-data:
|
|
314
|
+
minio-data:
|
|
315
|
+
`
|
|
273
316
|
|
|
274
317
|
return `# Generated by supatype self-host compose
|
|
275
318
|
# Kong → supatype-server (unified gateway) → internal PostgREST / storage / etc.
|
|
@@ -330,12 +373,12 @@ ${dbPorts} volumes:
|
|
|
330
373
|
SUPATYPE_FUNCTIONS_ROOT: /project/functions
|
|
331
374
|
SUPATYPE_DENO_FUNCTIONS_DIR: /project/functions
|
|
332
375
|
PORT: "8001"
|
|
333
|
-
SUPATYPE_URL: \${API_EXTERNAL_URL
|
|
376
|
+
SUPATYPE_URL: \${API_EXTERNAL_URL:-${externalUrlFallback}}
|
|
334
377
|
SUPATYPE_ANON_KEY: \${ANON_KEY:-}
|
|
335
378
|
SUPATYPE_SERVICE_ROLE_KEY: \${SERVICE_ROLE_KEY:-}
|
|
336
379
|
STRIPE_SECRET_KEY: \${STRIPE_SECRET_KEY:-}
|
|
337
380
|
STRIPE_WEBHOOK_SECRET: \${STRIPE_WEBHOOK_SECRET:-}
|
|
338
|
-
SITE_URL: \${SITE_URL:-\${API_EXTERNAL_URL
|
|
381
|
+
SITE_URL: \${SITE_URL:-\${API_EXTERNAL_URL:-${externalUrlFallback}}}
|
|
339
382
|
depends_on:
|
|
340
383
|
db:
|
|
341
384
|
condition: service_healthy
|
|
@@ -374,7 +417,7 @@ ${serverPorts} volumes:
|
|
|
374
417
|
SUPATYPE_POSTGREST_URL: http://postgrest:3000
|
|
375
418
|
SUPATYPE_GRAPHQL_URL: http://postgrest:3000
|
|
376
419
|
SUPATYPE_STORAGE_URL: http://storage:5000
|
|
377
|
-
SUPATYPE_URL: \${API_EXTERNAL_URL
|
|
420
|
+
SUPATYPE_URL: \${API_EXTERNAL_URL:-${externalUrlFallback}}
|
|
378
421
|
SUPATYPE_ANON_KEY: \${ANON_KEY:-}
|
|
379
422
|
SUPATYPE_SERVICE_ROLE_KEY: \${SERVICE_ROLE_KEY:-}
|
|
380
423
|
SUPATYPE_SQL_DATABASE_URL: "postgresql://\${POSTGRES_USER:-supatype_admin}:\${POSTGRES_PASSWORD:-postgres}@db:5432/\${POSTGRES_DB:-supatype}"
|
|
@@ -384,11 +427,11 @@ ${serverPorts} volumes:
|
|
|
384
427
|
${appEnv}
|
|
385
428
|
GOTRUE_API_HOST: 0.0.0.0
|
|
386
429
|
GOTRUE_API_PORT: 9999
|
|
387
|
-
API_EXTERNAL_URL: \${API_EXTERNAL_URL
|
|
388
|
-
GOTRUE_API_EXTERNAL_URL: \${API_EXTERNAL_URL
|
|
430
|
+
API_EXTERNAL_URL: \${API_EXTERNAL_URL:-${externalUrlFallback}}
|
|
431
|
+
GOTRUE_API_EXTERNAL_URL: \${API_EXTERNAL_URL:-${externalUrlFallback}}
|
|
389
432
|
GOTRUE_DB_DRIVER: postgres
|
|
390
433
|
GOTRUE_DB_DATABASE_URL: "postgres://\${POSTGRES_USER:-supatype_admin}:\${POSTGRES_PASSWORD:-postgres}@db:5432/\${POSTGRES_DB:-supatype}?search_path=auth"
|
|
391
|
-
GOTRUE_SITE_URL: \${SITE_URL
|
|
434
|
+
GOTRUE_SITE_URL: \${SITE_URL:-${siteUrlFallback}}
|
|
392
435
|
GOTRUE_JWT_SECRET: \${JWT_SECRET:-super-secret-jwt-token-change-in-production}
|
|
393
436
|
GOTRUE_JWT_EXP: 3600
|
|
394
437
|
GOTRUE_JWT_AUD: authenticated
|
|
@@ -428,8 +471,7 @@ ${minioPorts} volumes:
|
|
|
428
471
|
depends_on:
|
|
429
472
|
db:
|
|
430
473
|
condition: service_healthy
|
|
431
|
-
${studioBlock}
|
|
432
|
-
kong:
|
|
474
|
+
${studioBlock}${valkeyBlock}${tlsHintComment} kong:
|
|
433
475
|
image: kong:3.6
|
|
434
476
|
environment:
|
|
435
477
|
KONG_DATABASE: "off"
|
|
@@ -438,17 +480,14 @@ ${studioBlock}
|
|
|
438
480
|
KONG_ADMIN_ACCESS_LOG: /dev/stdout
|
|
439
481
|
KONG_PROXY_ERROR_LOG: /dev/stderr
|
|
440
482
|
KONG_ADMIN_ERROR_LOG: /dev/stderr
|
|
441
|
-
volumes:
|
|
483
|
+
${kongTlsEnv} volumes:
|
|
442
484
|
- ${kongMount}:/etc/kong/kong.yml:ro
|
|
443
485
|
ports:
|
|
444
|
-
|
|
486
|
+
${kongPorts}
|
|
445
487
|
depends_on:
|
|
446
|
-
${kongDependsOn}
|
|
488
|
+
${kongDependsOn}${kongTlsDependsOn}
|
|
447
489
|
|
|
448
|
-
|
|
449
|
-
db-data:
|
|
450
|
-
minio-data:
|
|
451
|
-
`
|
|
490
|
+
${volumesBlock}`
|
|
452
491
|
}
|
|
453
492
|
|
|
454
493
|
function ensureComposeManifest(cwd: string): void {
|
|
@@ -480,6 +519,9 @@ export function writeSelfHostCompose(
|
|
|
480
519
|
ensureComposeManifest(cwd)
|
|
481
520
|
writeFileSync(paths.composePath, renderSelfHostCompose(config, cwd, options), "utf8")
|
|
482
521
|
const studioHostDev = options?.devLocal === true && hasStudioOverride(config)
|
|
522
|
+
const tlsEnabled = selfHostTlsEnabled(config, options?.devLocal === true)
|
|
523
|
+
const domain = config.server.domain?.trim()
|
|
524
|
+
const acmeEmail = config.server.tls?.email?.trim()
|
|
483
525
|
writeFileSync(
|
|
484
526
|
paths.kongPath,
|
|
485
527
|
buildKongDeclarative({
|
|
@@ -488,6 +530,9 @@ export function writeSelfHostCompose(
|
|
|
488
530
|
studioServiceUrl: COMPOSE_STUDIO_HOST_URL,
|
|
489
531
|
studioStripPath: false,
|
|
490
532
|
}),
|
|
533
|
+
...(tlsEnabled && domain && acmeEmail
|
|
534
|
+
? { acme: { email: acmeEmail, domain, redisHost: "valkey" } }
|
|
535
|
+
: {}),
|
|
491
536
|
}),
|
|
492
537
|
"utf8",
|
|
493
538
|
)
|
package/tests/config.test.ts
CHANGED
|
@@ -249,4 +249,30 @@ describe("mergeProjectConfig()", () => {
|
|
|
249
249
|
expect(merged.app.start).toBe("dev:site")
|
|
250
250
|
expect(merged.app.static_dir).toBe("./dist")
|
|
251
251
|
})
|
|
252
|
+
|
|
253
|
+
it("preserves base environments when a local override sets only server.mode", () => {
|
|
254
|
+
const base = defineConfig({
|
|
255
|
+
...minimalProject("p"),
|
|
256
|
+
server: { mode: "standalone", domain: "api.example.com" },
|
|
257
|
+
environments: { default: "production" },
|
|
258
|
+
})
|
|
259
|
+
const merged = mergeProjectConfig(base, { server: { mode: "dev" } })
|
|
260
|
+
expect(merged.server.mode).toBe("dev")
|
|
261
|
+
expect(merged.environments?.default).toBe("production")
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it("deep-merges environments.branchDefaults across base and override", () => {
|
|
265
|
+
const base = defineConfig({
|
|
266
|
+
...minimalProject("p"),
|
|
267
|
+
environments: { default: "production", branchDefaults: { main: "production" } },
|
|
268
|
+
})
|
|
269
|
+
const merged = mergeProjectConfig(base, {
|
|
270
|
+
environments: { branchDefaults: { staging: "preview" } },
|
|
271
|
+
})
|
|
272
|
+
expect(merged.environments?.default).toBe("production")
|
|
273
|
+
expect(merged.environments?.branchDefaults).toEqual({
|
|
274
|
+
main: "production",
|
|
275
|
+
staging: "preview",
|
|
276
|
+
})
|
|
277
|
+
})
|
|
252
278
|
})
|