@supatype/cli 0.1.0 → 0.1.2
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 +121 -86
- package/.turbo/turbo-typecheck.log +2 -2
- package/dist/binary-cache.d.ts +2 -0
- package/dist/binary-cache.d.ts.map +1 -1
- package/dist/binary-cache.js +36 -0
- package/dist/binary-cache.js.map +1 -1
- package/dist/binary-download-lock.d.ts +12 -0
- package/dist/binary-download-lock.d.ts.map +1 -0
- package/dist/binary-download-lock.js +106 -0
- package/dist/binary-download-lock.js.map +1 -0
- package/dist/cli-package-version.d.ts +3 -0
- package/dist/cli-package-version.d.ts.map +1 -0
- package/dist/cli-package-version.js +15 -0
- package/dist/cli-package-version.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/add.js +25 -19
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/admin.d.ts +28 -1
- package/dist/commands/admin.d.ts.map +1 -1
- package/dist/commands/admin.js +290 -112
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/cache.d.ts +1 -1
- package/dist/commands/cache.d.ts.map +1 -1
- package/dist/commands/cache.js +98 -5
- package/dist/commands/cache.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +28 -22
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.d.ts +8 -4
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +278 -139
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/link-helpers.d.ts.map +1 -1
- package/dist/commands/link-helpers.js +26 -25
- package/dist/commands/link-helpers.js.map +1 -1
- package/dist/commands/push.js +3 -3
- package/dist/commands/push.js.map +1 -1
- package/dist/compose-local-server-image.d.ts +13 -0
- package/dist/compose-local-server-image.d.ts.map +1 -0
- package/dist/compose-local-server-image.js +57 -0
- package/dist/compose-local-server-image.js.map +1 -0
- package/dist/compose-rename.d.ts +10 -0
- package/dist/compose-rename.d.ts.map +1 -0
- package/dist/compose-rename.js +64 -0
- package/dist/compose-rename.js.map +1 -0
- package/dist/dev-compose.d.ts.map +1 -1
- package/dist/dev-compose.js +138 -97
- package/dist/dev-compose.js.map +1 -1
- package/dist/dev-log-bus.d.ts +5 -0
- package/dist/dev-log-bus.d.ts.map +1 -1
- package/dist/dev-log-bus.js +14 -0
- package/dist/dev-log-bus.js.map +1 -1
- package/dist/dev-log-filter.d.ts +4 -0
- package/dist/dev-log-filter.d.ts.map +1 -1
- package/dist/dev-log-filter.js +52 -0
- package/dist/dev-log-filter.js.map +1 -1
- package/dist/dev-ports.d.ts +27 -0
- package/dist/dev-ports.d.ts.map +1 -0
- package/dist/dev-ports.js +178 -0
- package/dist/dev-ports.js.map +1 -0
- package/dist/dev-ready-panel.d.ts +19 -0
- package/dist/dev-ready-panel.d.ts.map +1 -0
- package/dist/dev-ready-panel.js +49 -0
- package/dist/dev-ready-panel.js.map +1 -0
- package/dist/dev-session-lock.d.ts +25 -0
- package/dist/dev-session-lock.d.ts.map +1 -0
- package/dist/dev-session-lock.js +78 -0
- package/dist/dev-session-lock.js.map +1 -0
- package/dist/dev-session.d.ts +18 -5
- package/dist/dev-session.d.ts.map +1 -1
- package/dist/dev-session.js +91 -18
- package/dist/dev-session.js.map +1 -1
- package/dist/dev-shutdown.d.ts +20 -2
- package/dist/dev-shutdown.d.ts.map +1 -1
- package/dist/dev-shutdown.js +88 -10
- package/dist/dev-shutdown.js.map +1 -1
- package/dist/dev-task-colors.d.ts +3 -1
- package/dist/dev-task-colors.d.ts.map +1 -1
- package/dist/dev-task-colors.js +23 -3
- package/dist/dev-task-colors.js.map +1 -1
- package/dist/docker-runtime.js +1 -1
- package/dist/docker-runtime.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/ensure-binary.d.ts.map +1 -1
- package/dist/ensure-binary.js +14 -2
- package/dist/ensure-binary.js.map +1 -1
- package/dist/ensure-component-binaries.d.ts +17 -0
- package/dist/ensure-component-binaries.d.ts.map +1 -0
- package/dist/ensure-component-binaries.js +45 -0
- package/dist/ensure-component-binaries.js.map +1 -0
- package/dist/ensure-dev-api-config.d.ts +3 -0
- package/dist/ensure-dev-api-config.d.ts.map +1 -0
- package/dist/ensure-dev-api-config.js +17 -0
- package/dist/ensure-dev-api-config.js.map +1 -0
- package/dist/env-file.d.ts +5 -0
- package/dist/env-file.d.ts.map +1 -0
- package/dist/env-file.js +33 -0
- package/dist/env-file.js.map +1 -0
- package/dist/init-dependency-versions.d.ts +13 -0
- package/dist/init-dependency-versions.d.ts.map +1 -0
- package/dist/init-dependency-versions.js +53 -0
- package/dist/init-dependency-versions.js.map +1 -0
- package/dist/init-package-json.d.ts +21 -0
- package/dist/init-package-json.d.ts.map +1 -0
- package/dist/init-package-json.js +46 -0
- package/dist/init-package-json.js.map +1 -0
- package/dist/postgres-ctl.d.ts.map +1 -1
- package/dist/postgres-ctl.js +2 -1
- package/dist/postgres-ctl.js.map +1 -1
- package/dist/required-host-components.d.ts +11 -0
- package/dist/required-host-components.d.ts.map +1 -0
- package/dist/required-host-components.js +25 -0
- package/dist/required-host-components.js.map +1 -0
- package/dist/resolve-api-url.d.ts +6 -0
- package/dist/resolve-api-url.d.ts.map +1 -0
- package/dist/resolve-api-url.js +39 -0
- package/dist/resolve-api-url.js.map +1 -0
- package/dist/rest-cache-admin.d.ts +33 -0
- package/dist/rest-cache-admin.d.ts.map +1 -0
- package/dist/rest-cache-admin.js +96 -0
- package/dist/rest-cache-admin.js.map +1 -0
- package/dist/scripts/postinstall.d.ts +4 -6
- package/dist/scripts/postinstall.d.ts.map +1 -1
- package/dist/scripts/postinstall.js +8 -41
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/self-host-compose.d.ts.map +1 -1
- package/dist/self-host-compose.js +24 -13
- package/dist/self-host-compose.js.map +1 -1
- package/dist/studio-dev-server.d.ts +4 -2
- package/dist/studio-dev-server.d.ts.map +1 -1
- package/dist/studio-dev-server.js +3 -2
- package/dist/studio-dev-server.js.map +1 -1
- package/dist/ui/brand.d.ts +2 -5
- package/dist/ui/brand.d.ts.map +1 -1
- package/dist/ui/brand.js +2 -7
- package/dist/ui/brand.js.map +1 -1
- package/dist/ui/clack-api.d.ts +44 -0
- package/dist/ui/clack-api.d.ts.map +1 -0
- package/dist/ui/clack-api.js +76 -0
- package/dist/ui/clack-api.js.map +1 -0
- package/dist/ui/clack.d.ts +14 -0
- package/dist/ui/clack.d.ts.map +1 -0
- package/dist/ui/clack.js +147 -0
- package/dist/ui/clack.js.map +1 -0
- package/dist/ui/components/FlowSpinner.d.ts +5 -0
- package/dist/ui/components/FlowSpinner.d.ts.map +1 -0
- package/dist/ui/components/FlowSpinner.js +8 -0
- package/dist/ui/components/FlowSpinner.js.map +1 -0
- package/dist/ui/components/LogoWordmark.d.ts +9 -0
- package/dist/ui/components/LogoWordmark.d.ts.map +1 -0
- package/dist/ui/components/LogoWordmark.js +17 -0
- package/dist/ui/components/LogoWordmark.js.map +1 -0
- package/dist/ui/components/PromptPanel.d.ts +5 -0
- package/dist/ui/components/PromptPanel.d.ts.map +1 -0
- package/dist/ui/components/PromptPanel.js +7 -0
- package/dist/ui/components/PromptPanel.js.map +1 -0
- package/dist/ui/components/SelectField.d.ts +11 -0
- package/dist/ui/components/SelectField.d.ts.map +1 -0
- package/dist/ui/components/SelectField.js +19 -0
- package/dist/ui/components/SelectField.js.map +1 -0
- package/dist/ui/confirm.d.ts +2 -1
- package/dist/ui/confirm.d.ts.map +1 -1
- package/dist/ui/confirm.js +4 -4
- package/dist/ui/confirm.js.map +1 -1
- package/dist/ui/dev/DevDashboard.d.ts +8 -0
- package/dist/ui/dev/DevDashboard.d.ts.map +1 -0
- package/dist/ui/dev/DevDashboard.js +151 -0
- package/dist/ui/dev/DevDashboard.js.map +1 -0
- package/dist/ui/dev/DevReadyPanelView.d.ts +7 -0
- package/dist/ui/dev/DevReadyPanelView.d.ts.map +1 -0
- package/dist/ui/dev/DevReadyPanelView.js +29 -0
- package/dist/ui/dev/DevReadyPanelView.js.map +1 -0
- package/dist/ui/fatal.d.ts.map +1 -1
- package/dist/ui/fatal.js +5 -11
- package/dist/ui/fatal.js.map +1 -1
- package/dist/ui/flows/FlowApp.d.ts +8 -0
- package/dist/ui/flows/FlowApp.d.ts.map +1 -0
- package/dist/ui/flows/FlowApp.js +42 -0
- package/dist/ui/flows/FlowApp.js.map +1 -0
- package/dist/ui/flows/prompt-fields.d.ts +48 -0
- package/dist/ui/flows/prompt-fields.d.ts.map +1 -0
- package/dist/ui/flows/prompt-fields.js +88 -0
- package/dist/ui/flows/prompt-fields.js.map +1 -0
- package/dist/ui/flows/types.d.ts +41 -0
- package/dist/ui/flows/types.d.ts.map +1 -0
- package/dist/ui/flows/types.js +2 -0
- package/dist/ui/flows/types.js.map +1 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/ui/messages.d.ts +1 -0
- package/dist/ui/messages.d.ts.map +1 -1
- package/dist/ui/messages.js +48 -8
- package/dist/ui/messages.js.map +1 -1
- package/dist/ui/progress.d.ts +5 -1
- package/dist/ui/progress.d.ts.map +1 -1
- package/dist/ui/progress.js +12 -3
- package/dist/ui/progress.js.map +1 -1
- package/dist/ui/prompts.d.ts +7 -5
- package/dist/ui/prompts.d.ts.map +1 -1
- package/dist/ui/prompts.js +15 -8
- package/dist/ui/prompts.js.map +1 -1
- package/dist/ui/runtime/cancel.d.ts +4 -0
- package/dist/ui/runtime/cancel.d.ts.map +1 -0
- package/dist/ui/runtime/cancel.js +6 -0
- package/dist/ui/runtime/cancel.js.map +1 -0
- package/dist/ui/runtime/command-chrome.d.ts +17 -0
- package/dist/ui/runtime/command-chrome.d.ts.map +1 -0
- package/dist/ui/runtime/command-chrome.js +123 -0
- package/dist/ui/runtime/command-chrome.js.map +1 -0
- package/dist/ui/runtime/dev-prompt-queue.d.ts +30 -0
- package/dist/ui/runtime/dev-prompt-queue.d.ts.map +1 -0
- package/dist/ui/runtime/dev-prompt-queue.js +57 -0
- package/dist/ui/runtime/dev-prompt-queue.js.map +1 -0
- package/dist/ui/runtime/flow-session.d.ts +4 -0
- package/dist/ui/runtime/flow-session.d.ts.map +1 -0
- package/dist/ui/runtime/flow-session.js +8 -0
- package/dist/ui/runtime/flow-session.js.map +1 -0
- package/dist/ui/runtime/run-clack-flow.d.ts +5 -0
- package/dist/ui/runtime/run-clack-flow.d.ts.map +1 -0
- package/dist/ui/runtime/run-clack-flow.js +34 -0
- package/dist/ui/runtime/run-clack-flow.js.map +1 -0
- package/dist/ui/theme.d.ts +24 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +34 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/valkey-sidecar.d.ts +17 -0
- package/dist/valkey-sidecar.d.ts.map +1 -0
- package/dist/valkey-sidecar.js +94 -0
- package/dist/valkey-sidecar.js.map +1 -0
- package/package.json +11 -3
- package/src/binary-cache.ts +61 -0
- package/src/binary-download-lock.ts +124 -0
- package/src/cli-package-version.ts +15 -0
- package/src/cli.ts +6 -2
- package/src/commands/add.ts +26 -19
- package/src/commands/admin.ts +380 -137
- package/src/commands/cache.ts +105 -5
- package/src/commands/dev.ts +29 -21
- package/src/commands/init.ts +217 -46
- package/src/commands/link-helpers.ts +30 -29
- package/src/commands/push.ts +3 -3
- package/src/compose-local-server-image.ts +68 -0
- package/src/compose-rename.ts +73 -0
- package/src/dev-compose.ts +175 -96
- package/src/dev-log-bus.ts +18 -0
- package/src/dev-log-filter.ts +42 -0
- package/src/dev-ports.ts +223 -0
- package/src/dev-ready-panel.ts +63 -0
- package/src/dev-session-lock.ts +98 -0
- package/src/dev-session.ts +98 -18
- package/src/dev-shutdown.ts +114 -9
- package/src/dev-task-colors.ts +22 -3
- package/src/docker-runtime.ts +1 -1
- package/src/engine-push-output.ts +5 -1
- package/src/ensure-binary.ts +21 -1
- package/src/ensure-component-binaries.ts +71 -0
- package/src/ensure-dev-api-config.ts +18 -0
- package/src/env-file.ts +37 -0
- package/src/init-dependency-versions.ts +69 -0
- package/src/init-package-json.ts +63 -0
- package/src/postgres-ctl.ts +2 -1
- package/src/required-host-components.ts +37 -0
- package/src/resolve-api-url.ts +43 -0
- package/src/rest-cache-admin.ts +135 -0
- package/src/scripts/postinstall.ts +9 -46
- package/src/self-host-compose.ts +23 -12
- package/src/studio-dev-server.ts +5 -4
- package/src/ui/README.md +13 -9
- package/src/ui/brand.ts +2 -8
- package/src/ui/clack-api.ts +123 -0
- package/src/ui/clack.ts +153 -0
- package/src/ui/components/FlowSpinner.tsx +15 -0
- package/src/ui/components/LogoWordmark.tsx +36 -0
- package/src/ui/components/PromptPanel.tsx +17 -0
- package/src/ui/components/SelectField.tsx +58 -0
- package/src/ui/confirm.ts +5 -4
- package/src/ui/dev/DevDashboard.tsx +213 -0
- package/src/ui/dev/DevReadyPanelView.tsx +67 -0
- package/src/ui/fatal.ts +5 -10
- package/src/ui/flows/FlowApp.tsx +67 -0
- package/src/ui/flows/prompt-fields.tsx +192 -0
- package/src/ui/flows/types.ts +50 -0
- package/src/ui/index.ts +1 -1
- package/src/ui/messages.ts +50 -8
- package/src/ui/progress.ts +14 -3
- package/src/ui/prompts.ts +17 -9
- package/src/ui/runtime/cancel.ts +6 -0
- package/src/ui/runtime/command-chrome.ts +147 -0
- package/src/ui/runtime/dev-prompt-queue.ts +84 -0
- package/src/ui/runtime/flow-session.ts +11 -0
- package/src/ui/runtime/run-clack-flow.tsx +52 -0
- package/src/ui/theme.ts +41 -0
- package/src/valkey-sidecar.ts +126 -0
- package/tests/admin-ensure.test.ts +59 -0
- package/tests/binary-cache-cloud-overrides.test.ts +2 -2
- package/tests/binary-download-lock.test.ts +41 -0
- package/tests/cli-help.test.ts +7 -4
- package/tests/cli-package-version.test.ts +15 -0
- package/tests/command-chrome-coverage.test.ts +149 -0
- package/tests/command-chrome-wrap.test.ts +51 -0
- package/tests/config.test.ts +8 -4
- package/tests/dev-ports.test.ts +41 -0
- package/tests/dev-ready-panel.test.ts +20 -0
- package/tests/dev-session-lock.test.ts +54 -0
- package/tests/dev-ui.test.ts +66 -3
- package/tests/docker-runtime.test.ts +9 -20
- package/tests/ensure-dev-api-config.test.ts +22 -0
- package/tests/init-dependency-versions.test.ts +62 -0
- package/tests/init-package-json.test.ts +18 -0
- package/tests/init.test.ts +30 -3
- package/tests/required-host-components.test.ts +51 -0
- package/tests/resolve-api-url.test.ts +23 -0
- package/tests/rest-cache-admin.test.ts +28 -0
- package/tests/runtime-contract.test.ts +12 -3
- package/tests/tsconfig.json +8 -2
- package/tests/ui-confirm.test.ts +13 -9
- package/tests/ui-messages.test.ts +4 -18
- package/tsconfig.json +1 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +3 -0
- package/dist/dev-tui.d.ts +0 -24
- package/dist/dev-tui.d.ts.map +0 -1
- package/dist/dev-tui.js +0 -188
- package/dist/dev-tui.js.map +0 -1
- package/src/dev-tui.ts +0 -232
package/src/dev-ports.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kong / Postgres host port resolution for local Docker dev.
|
|
3
|
+
* Persists SUPATYPE_KONG_PORT in `.env` so re-runs are stable, but re-checks
|
|
4
|
+
* availability so multiple projects and port collisions are surfaced clearly.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { CLACK_CANCEL, isCancel, p } from "./ui/clack.js"
|
|
8
|
+
import { COMPOSE_DEV_KONG_PORT } from "./project-config.js"
|
|
9
|
+
import { isPortInUse } from "./postgres-ctl.js"
|
|
10
|
+
import { readEnvInt, upsertEnvFile } from "./env-file.js"
|
|
11
|
+
import { isInteractive } from "./ui/interactive.js"
|
|
12
|
+
import { fatalError } from "./ui/fatal.js"
|
|
13
|
+
|
|
14
|
+
const MIN_PORT = 1024
|
|
15
|
+
const MAX_PORT = 65535
|
|
16
|
+
|
|
17
|
+
export function isValidHostPort(port: number): boolean {
|
|
18
|
+
return Number.isInteger(port) && port >= MIN_PORT && port <= MAX_PORT
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function parseHostPortInput(raw: string): number | null {
|
|
22
|
+
const port = Number(raw.trim())
|
|
23
|
+
return isValidHostPort(port) ? port : null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Next free TCP port on 127.0.0.1 starting at `start`. */
|
|
27
|
+
export async function findNextFreePort(start: number): Promise<number> {
|
|
28
|
+
let port = Math.max(MIN_PORT, start)
|
|
29
|
+
while (port <= MAX_PORT && (await isPortInUse(port))) port++
|
|
30
|
+
if (port > MAX_PORT) {
|
|
31
|
+
throw new Error(`No free local port found in range ${start}–${MAX_PORT}.`)
|
|
32
|
+
}
|
|
33
|
+
return port
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function readPersistedKongPort(cwd: string): number | null {
|
|
37
|
+
return readEnvInt(cwd, "SUPATYPE_KONG_PORT")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function persistKongPort(cwd: string, port: number): void {
|
|
41
|
+
const apiUrl = `http://localhost:${port}`
|
|
42
|
+
upsertEnvFile(cwd, {
|
|
43
|
+
SUPATYPE_KONG_PORT: String(port),
|
|
44
|
+
PUBLIC_SUPATYPE_URL: apiUrl,
|
|
45
|
+
API_EXTERNAL_URL: apiUrl,
|
|
46
|
+
SITE_URL: apiUrl,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function promptPortConflict(
|
|
51
|
+
cwd: string,
|
|
52
|
+
blockedPort: number,
|
|
53
|
+
reason: "in_use" | "init",
|
|
54
|
+
): Promise<number> {
|
|
55
|
+
const port = await promptPortConflictWithoutPersist(blockedPort, reason)
|
|
56
|
+
persistKongPort(cwd, port)
|
|
57
|
+
return port
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface EnsureKongPortOptions {
|
|
61
|
+
/** When false, fail fast instead of prompting (CI / scripts). */
|
|
62
|
+
interactive?: boolean
|
|
63
|
+
/** init wizard — slightly different copy */
|
|
64
|
+
context?: "dev" | "init"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the Kong host port for this project directory.
|
|
69
|
+
* - Uses `.env` when set and available.
|
|
70
|
+
* - Prompts (or errors) when the configured port is taken.
|
|
71
|
+
* - Auto-picks the next free port on first run when unset.
|
|
72
|
+
*/
|
|
73
|
+
export async function ensureKongPort(
|
|
74
|
+
cwd: string,
|
|
75
|
+
opts: EnsureKongPortOptions = {},
|
|
76
|
+
): Promise<number> {
|
|
77
|
+
const interactive = opts.interactive ?? isInteractive()
|
|
78
|
+
const context = opts.context ?? "dev"
|
|
79
|
+
const persisted = readPersistedKongPort(cwd)
|
|
80
|
+
|
|
81
|
+
if (persisted !== null) {
|
|
82
|
+
if (!(await isPortInUse(persisted))) return persisted
|
|
83
|
+
|
|
84
|
+
if (!interactive) {
|
|
85
|
+
fatalError(`Port ${persisted} is already in use (SUPATYPE_KONG_PORT in .env).`, [
|
|
86
|
+
"Stop the other service or set a different SUPATYPE_KONG_PORT.",
|
|
87
|
+
"Run `supatype dev` in a terminal to pick a new port interactively.",
|
|
88
|
+
])
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return promptPortConflict(cwd, persisted, "in_use")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const preferred = COMPOSE_DEV_KONG_PORT
|
|
95
|
+
const port =
|
|
96
|
+
(await isPortInUse(preferred))
|
|
97
|
+
? interactive && context === "init"
|
|
98
|
+
? await promptPortConflict(cwd, preferred, "init")
|
|
99
|
+
: await findNextFreePort(preferred)
|
|
100
|
+
: preferred
|
|
101
|
+
|
|
102
|
+
return port
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Wizard / init — pick a Kong port without writing `.env` (scaffold writes it). */
|
|
106
|
+
export async function promptKongPortChoice(): Promise<number> {
|
|
107
|
+
const freeDefault = (await isPortInUse(COMPOSE_DEV_KONG_PORT))
|
|
108
|
+
? await findNextFreePort(COMPOSE_DEV_KONG_PORT)
|
|
109
|
+
: COMPOSE_DEV_KONG_PORT
|
|
110
|
+
|
|
111
|
+
const value = await p.text({
|
|
112
|
+
message: "Local API gateway port (Kong)",
|
|
113
|
+
defaultValue: String(freeDefault),
|
|
114
|
+
placeholder: String(COMPOSE_DEV_KONG_PORT),
|
|
115
|
+
validate: (raw) => {
|
|
116
|
+
const port = parseHostPortInput(raw ?? "")
|
|
117
|
+
if (!port) return `Enter a port between ${MIN_PORT} and ${MAX_PORT}.`
|
|
118
|
+
return undefined
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
if (isCancel(value)) {
|
|
123
|
+
p.cancel("Cancelled.")
|
|
124
|
+
process.exit(0)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const port = parseHostPortInput(value)!
|
|
128
|
+
if (await isPortInUse(port)) {
|
|
129
|
+
return promptPortConflictWithoutPersist(port, "init")
|
|
130
|
+
}
|
|
131
|
+
return port
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function promptPortConflictWithoutPersist(
|
|
135
|
+
blockedPort: number,
|
|
136
|
+
reason: "in_use" | "init",
|
|
137
|
+
): Promise<number> {
|
|
138
|
+
const suggested = await findNextFreePort(blockedPort + 1)
|
|
139
|
+
const headline =
|
|
140
|
+
reason === "init"
|
|
141
|
+
? `Port ${blockedPort} is already in use on this machine.`
|
|
142
|
+
: `Port ${blockedPort} is in use — another Supatype project or service may already be bound to it.`
|
|
143
|
+
|
|
144
|
+
const choice = await p.select<"suggested" | "custom" | "cancel">({
|
|
145
|
+
message: headline,
|
|
146
|
+
options: [
|
|
147
|
+
{
|
|
148
|
+
value: "suggested",
|
|
149
|
+
label: `Use ${suggested} instead`,
|
|
150
|
+
hint: "next available port",
|
|
151
|
+
},
|
|
152
|
+
{ value: "custom", label: "Enter a different port" },
|
|
153
|
+
{ value: "cancel", label: "Cancel" },
|
|
154
|
+
],
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
if (isCancel(choice) || choice === "cancel") {
|
|
158
|
+
p.cancel("Cancelled.")
|
|
159
|
+
process.exit(0)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (choice === "suggested") return suggested
|
|
163
|
+
|
|
164
|
+
const custom = await p.text({
|
|
165
|
+
message: "Local API gateway port (Kong)",
|
|
166
|
+
defaultValue: String(suggested),
|
|
167
|
+
validate: (value) => {
|
|
168
|
+
const port = parseHostPortInput(value ?? "")
|
|
169
|
+
if (!port) return `Enter a port between ${MIN_PORT} and ${MAX_PORT}.`
|
|
170
|
+
return undefined
|
|
171
|
+
},
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
if (isCancel(custom)) {
|
|
175
|
+
p.cancel("Cancelled.")
|
|
176
|
+
process.exit(0)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const port = parseHostPortInput(custom)
|
|
180
|
+
if (!port) {
|
|
181
|
+
p.cancel("Invalid port.")
|
|
182
|
+
process.exit(1)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (await isPortInUse(port)) {
|
|
186
|
+
fatalError(`Port ${port} is still in use.`, [
|
|
187
|
+
"Pick another port or stop the service using it.",
|
|
188
|
+
"Check Docker Desktop for other Supatype stacks.",
|
|
189
|
+
])
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return port
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const COMPOSE_DEV_DB_PORT = 54329
|
|
196
|
+
|
|
197
|
+
function devDbConnectionUrl(port: number): string {
|
|
198
|
+
return `postgresql://supatype_admin:postgres@localhost:${port}/supatype?sslmode=disable`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function ensureDevDbPort(cwd: string): Promise<number> {
|
|
202
|
+
const persisted = readEnvInt(cwd, "SUPATYPE_DEV_DB_PORT")
|
|
203
|
+
|
|
204
|
+
const persist = (port: number): number => {
|
|
205
|
+
upsertEnvFile(cwd, {
|
|
206
|
+
SUPATYPE_DEV_DB_PORT: String(port),
|
|
207
|
+
DATABASE_URL: devDbConnectionUrl(port),
|
|
208
|
+
})
|
|
209
|
+
return port
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (persisted !== null) {
|
|
213
|
+
if (!(await isPortInUse(persisted))) return persisted
|
|
214
|
+
const next = await findNextFreePort(persisted + 1)
|
|
215
|
+
return persist(next)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let port = COMPOSE_DEV_DB_PORT
|
|
219
|
+
if (await isPortInUse(port)) {
|
|
220
|
+
port = await findNextFreePort(port + 1)
|
|
221
|
+
}
|
|
222
|
+
return persist(port)
|
|
223
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pinned service URLs + dev keys for the Ink dashboard.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { appendStackOutput, getActiveDevSession } from "./dev-session.js"
|
|
6
|
+
|
|
7
|
+
export interface DevServiceLink {
|
|
8
|
+
label: string
|
|
9
|
+
url: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DevReadyPanel {
|
|
13
|
+
title: string
|
|
14
|
+
links: DevServiceLink[]
|
|
15
|
+
anonKey?: string
|
|
16
|
+
serviceRoleKey?: string
|
|
17
|
+
hints?: string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Ink row budget for DevReadyPanelView (border, title, links, hints, keys, margin). */
|
|
21
|
+
export function devReadyPanelRowCount(panel: DevReadyPanel): number {
|
|
22
|
+
let rows = 2 // round border top + bottom
|
|
23
|
+
rows += 1 // title
|
|
24
|
+
rows += panel.links.length
|
|
25
|
+
for (const hint of panel.hints ?? []) {
|
|
26
|
+
if (hint.trim()) rows += 1
|
|
27
|
+
}
|
|
28
|
+
if (panel.anonKey) {
|
|
29
|
+
rows += 1 // "API keys (local dev)"
|
|
30
|
+
rows += 1 // anon
|
|
31
|
+
if (panel.serviceRoleKey) rows += 1 // service_role
|
|
32
|
+
}
|
|
33
|
+
rows += 1 // marginBottom on panel box
|
|
34
|
+
return rows
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatStreamBlock(panel: DevReadyPanel): string {
|
|
38
|
+
const lines = [`[supatype] ${panel.title}`]
|
|
39
|
+
for (const link of panel.links) {
|
|
40
|
+
lines.push(` ${link.label.padEnd(16)} ${link.url}`)
|
|
41
|
+
}
|
|
42
|
+
for (const hint of panel.hints ?? []) {
|
|
43
|
+
lines.push(` ${hint}`)
|
|
44
|
+
}
|
|
45
|
+
if (panel.anonKey) {
|
|
46
|
+
lines.push(" API keys (local dev only):")
|
|
47
|
+
lines.push(` anon key ${panel.anonKey}`)
|
|
48
|
+
lines.push(` service_role ${panel.serviceRoleKey ?? ""}`)
|
|
49
|
+
}
|
|
50
|
+
lines.push(" Press Ctrl+C to stop.")
|
|
51
|
+
return lines.join("\n")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Pin links in the TUI or print the classic block in stream mode. */
|
|
55
|
+
export function publishDevReady(panel: DevReadyPanel): void {
|
|
56
|
+
const session = getActiveDevSession()
|
|
57
|
+
if (session?.isTui()) {
|
|
58
|
+
session.bus.setReadyPanel(panel)
|
|
59
|
+
appendStackOutput("Services running.", "log")
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
console.log(formatStreamBlock(panel))
|
|
63
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks an active `supatype dev` session so we can recover when the CLI exits
|
|
3
|
+
* without graceful shutdown (terminal closed, kill signal, etc.).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs"
|
|
7
|
+
import { join, resolve } from "node:path"
|
|
8
|
+
import { spawnSync } from "node:child_process"
|
|
9
|
+
import { runDockerCompose } from "./self-host-compose.js"
|
|
10
|
+
import { confirm as uiConfirm } from "./ui/confirm.js"
|
|
11
|
+
import { success, warn } from "./ui/messages.js"
|
|
12
|
+
import { isInteractive } from "./ui/interactive.js"
|
|
13
|
+
|
|
14
|
+
const LOCK_VERSION = 1 as const
|
|
15
|
+
|
|
16
|
+
export interface DevSessionLock {
|
|
17
|
+
version: typeof LOCK_VERSION
|
|
18
|
+
composeProject: string
|
|
19
|
+
projectRef: string
|
|
20
|
+
composePath: string
|
|
21
|
+
kongPort: number
|
|
22
|
+
startedAt: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function devSessionLockPath(cwd: string): string {
|
|
26
|
+
return resolve(cwd, ".supatype/dev-session.json")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function readDevSessionLock(cwd: string): DevSessionLock | null {
|
|
30
|
+
const path = devSessionLockPath(cwd)
|
|
31
|
+
if (!existsSync(path)) return null
|
|
32
|
+
try {
|
|
33
|
+
const data = JSON.parse(readFileSync(path, "utf8")) as DevSessionLock
|
|
34
|
+
if (data.version !== LOCK_VERSION) return null
|
|
35
|
+
return data
|
|
36
|
+
} catch {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function writeDevSessionLock(cwd: string, lock: Omit<DevSessionLock, "version">): void {
|
|
42
|
+
const dir = join(cwd, ".supatype")
|
|
43
|
+
mkdirSync(dir, { recursive: true })
|
|
44
|
+
const payload: DevSessionLock = { version: LOCK_VERSION, ...lock }
|
|
45
|
+
writeFileSync(devSessionLockPath(cwd), `${JSON.stringify(payload, null, 2)}\n`, "utf8")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function clearDevSessionLock(cwd: string): void {
|
|
49
|
+
const path = devSessionLockPath(cwd)
|
|
50
|
+
if (existsSync(path)) unlinkSync(path)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function composeStackHasContainers(composeProject: string): boolean {
|
|
54
|
+
const result = spawnSync(
|
|
55
|
+
"docker",
|
|
56
|
+
["ps", "-a", "--filter", `label=com.docker.compose.project=${composeProject}`, "--format", "{{.Names}}"],
|
|
57
|
+
{ encoding: "utf8", shell: process.platform === "win32" },
|
|
58
|
+
)
|
|
59
|
+
return Boolean(result.stdout?.trim())
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* When a previous dev session did not shut down cleanly, offer to stop its stack
|
|
64
|
+
* before starting a new one.
|
|
65
|
+
*/
|
|
66
|
+
export async function recoverStaleDevSession(cwd: string): Promise<void> {
|
|
67
|
+
const lock = readDevSessionLock(cwd)
|
|
68
|
+
if (!lock) return
|
|
69
|
+
if (!composeStackHasContainers(lock.composeProject)) {
|
|
70
|
+
clearDevSessionLock(cwd)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const message =
|
|
75
|
+
`Previous dev session for "${lock.projectRef}" may not have shut down cleanly ` +
|
|
76
|
+
`(stack "${lock.composeProject}" is still running).`
|
|
77
|
+
|
|
78
|
+
if (!isInteractive()) {
|
|
79
|
+
warn(message)
|
|
80
|
+
warn(`Stop it manually: docker compose -p ${lock.composeProject} down`)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const stop = await uiConfirm(`${message}\n\nStop the orphaned stack before starting?`, { default: true })
|
|
85
|
+
|
|
86
|
+
if (!stop) {
|
|
87
|
+
warn(`Leaving "${lock.composeProject}" running.`)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const status = runDockerCompose(lock.composePath, ["down"], cwd, lock.composeProject, { quiet: true })
|
|
92
|
+
if (status === 0) {
|
|
93
|
+
clearDevSessionLock(cwd)
|
|
94
|
+
success(`Stopped orphaned stack "${lock.composeProject}".`)
|
|
95
|
+
} else {
|
|
96
|
+
warn(`Could not stop "${lock.composeProject}" (exit ${status}).`)
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/dev-session.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `supatype dev` output session —
|
|
2
|
+
* `supatype dev` output session — Ink dashboard (default) or interleaved stream mode.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { render, type Instance } from "ink"
|
|
6
|
+
import React from "react"
|
|
5
7
|
import type { ProcessOptions } from "./process-manager.js"
|
|
6
|
-
import { filterDevSubprocessLine, formatConsoleArgs } from "./dev-log-filter.js"
|
|
8
|
+
import { filterDevSubprocessLine, filterStackLogLine, formatConsoleArgs } from "./dev-log-filter.js"
|
|
7
9
|
import { DevLogBus, type DevLogLevel } from "./dev-log-bus.js"
|
|
8
|
-
import {
|
|
10
|
+
import { DevDashboard } from "./ui/dev/DevDashboard.js"
|
|
11
|
+
import { ensureDevShutdownHooks } from "./dev-shutdown.js"
|
|
12
|
+
import { clearDevPromptQueue } from "./ui/runtime/dev-prompt-queue.js"
|
|
9
13
|
|
|
10
14
|
export type DevUiMode = "tui" | "stream"
|
|
11
15
|
|
|
@@ -18,26 +22,74 @@ export function resolveDevUiMode(streamFlag: boolean): DevUiMode {
|
|
|
18
22
|
let activeSession: DevSession | null = null
|
|
19
23
|
|
|
20
24
|
export function beginDevSession(mode: DevUiMode): DevSession {
|
|
25
|
+
ensureDevShutdownHooks()
|
|
21
26
|
activeSession?.stop()
|
|
22
27
|
activeSession = new DevSession(mode)
|
|
28
|
+
if (activeSession.isTui()) {
|
|
29
|
+
activeSession.enableConsoleCapture(false)
|
|
30
|
+
activeSession.startInk()
|
|
31
|
+
}
|
|
23
32
|
return activeSession
|
|
24
33
|
}
|
|
25
34
|
|
|
26
|
-
/**
|
|
35
|
+
/** @deprecated Ink starts in beginDevSession — kept for callers that invoke late. */
|
|
27
36
|
export function startDevSession(): void {
|
|
28
|
-
activeSession?.
|
|
37
|
+
activeSession?.startInk()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isDevTuiActive(): boolean {
|
|
41
|
+
return activeSession?.isInkMounted() ?? false
|
|
29
42
|
}
|
|
30
43
|
|
|
31
44
|
export function endDevSession(): void {
|
|
45
|
+
clearDevPromptQueue()
|
|
32
46
|
activeSession?.stop()
|
|
33
47
|
activeSession = null
|
|
34
48
|
}
|
|
35
49
|
|
|
50
|
+
/** @deprecated Dev prompts render inside the Ink dashboard — no suspend needed. */
|
|
51
|
+
export function suspendDevSessionForPrompt(): void {
|
|
52
|
+
prepareStdinForInteractivePrompt()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function prepareStdinForInteractivePrompt(): void {
|
|
56
|
+
if (!process.stdin.isTTY) return
|
|
57
|
+
process.stdin.setRawMode(false)
|
|
58
|
+
process.stdin.resume()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** @deprecated Dev prompts render inside the Ink dashboard — no resume needed. */
|
|
62
|
+
export function resumeDevSessionAfterPrompt(): void {
|
|
63
|
+
// no-op — prompts are Ink overlays
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** @deprecated Use Ink overlay prompts — runs fn without tearing down the dashboard. */
|
|
67
|
+
export async function withDevSessionSuspended<T>(fn: () => Promise<T>): Promise<T> {
|
|
68
|
+
return fn()
|
|
69
|
+
}
|
|
70
|
+
|
|
36
71
|
export function getActiveDevSession(): DevSession | null {
|
|
37
72
|
return activeSession
|
|
38
73
|
}
|
|
39
74
|
|
|
40
|
-
|
|
75
|
+
export function appendStackOutput(
|
|
76
|
+
text: string | null | undefined,
|
|
77
|
+
level: DevLogLevel = "log",
|
|
78
|
+
): void {
|
|
79
|
+
if (text == null || text.trim() === "") return
|
|
80
|
+
const session = getActiveDevSession()
|
|
81
|
+
for (const line of text.split(/\r?\n/)) {
|
|
82
|
+
if (line.trim() === "") continue
|
|
83
|
+
if (session?.isTui() && !filterStackLogLine(line)) continue
|
|
84
|
+
if (session?.isConsoleCaptured()) {
|
|
85
|
+
session.bus.append("stack", line, level)
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
const write = level === "warn" ? console.warn : level === "error" ? console.error : console.log
|
|
89
|
+
write(`[supatype] ${line}`)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
41
93
|
export function appendDevTaskLog(
|
|
42
94
|
taskId: string,
|
|
43
95
|
taskTitle: string,
|
|
@@ -58,8 +110,9 @@ export function appendDevTaskLog(
|
|
|
58
110
|
export class DevSession {
|
|
59
111
|
readonly bus = new DevLogBus()
|
|
60
112
|
readonly mode: DevUiMode
|
|
61
|
-
private
|
|
113
|
+
private ink: Instance | null = null
|
|
62
114
|
private restoreConsole: (() => void) | null = null
|
|
115
|
+
private consoleCaptured = false
|
|
63
116
|
|
|
64
117
|
constructor(mode: DevUiMode) {
|
|
65
118
|
this.mode = mode
|
|
@@ -69,18 +122,37 @@ export class DevSession {
|
|
|
69
122
|
return this.mode === "tui"
|
|
70
123
|
}
|
|
71
124
|
|
|
72
|
-
|
|
73
|
-
|
|
125
|
+
isInkMounted(): boolean {
|
|
126
|
+
return this.ink !== null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
isConsoleCaptured(): boolean {
|
|
130
|
+
return this.consoleCaptured
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
needsPromptSuspend(): boolean {
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
enableConsoleCapture(_tee: boolean): void {
|
|
138
|
+
if (!this.isTui() || this.consoleCaptured) return
|
|
74
139
|
this.restoreConsole = patchConsole(this.bus)
|
|
75
|
-
this.
|
|
76
|
-
|
|
140
|
+
this.consoleCaptured = true
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
startInk(): void {
|
|
144
|
+
if (!this.isTui() || this.ink) return
|
|
145
|
+
this.ink = render(React.createElement(DevDashboard, { bus: this.bus }))
|
|
77
146
|
}
|
|
78
147
|
|
|
79
148
|
stop(): void {
|
|
80
|
-
this.
|
|
81
|
-
this.
|
|
82
|
-
this.restoreConsole
|
|
83
|
-
|
|
149
|
+
this.ink?.unmount()
|
|
150
|
+
this.ink = null
|
|
151
|
+
if (this.restoreConsole) {
|
|
152
|
+
this.restoreConsole()
|
|
153
|
+
this.restoreConsole = null
|
|
154
|
+
this.consoleCaptured = false
|
|
155
|
+
}
|
|
84
156
|
}
|
|
85
157
|
}
|
|
86
158
|
|
|
@@ -105,6 +177,14 @@ export function enhanceProcessOptions(label: string, opts: ProcessOptions): Proc
|
|
|
105
177
|
}
|
|
106
178
|
}
|
|
107
179
|
|
|
180
|
+
function appendFilteredStackLine(bus: DevLogBus, line: string, level: DevLogLevel): void {
|
|
181
|
+
for (const part of line.split(/\r?\n/)) {
|
|
182
|
+
if (!part.trim()) continue
|
|
183
|
+
if (!filterStackLogLine(part)) continue
|
|
184
|
+
bus.append("stack", part, level)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
108
188
|
function patchConsole(bus: DevLogBus): () => void {
|
|
109
189
|
const original = {
|
|
110
190
|
log: console.log,
|
|
@@ -113,13 +193,13 @@ function patchConsole(bus: DevLogBus): () => void {
|
|
|
113
193
|
}
|
|
114
194
|
|
|
115
195
|
console.log = (...args: unknown[]) => {
|
|
116
|
-
bus
|
|
196
|
+
appendFilteredStackLine(bus, formatConsoleArgs(args), "log")
|
|
117
197
|
}
|
|
118
198
|
console.warn = (...args: unknown[]) => {
|
|
119
|
-
bus
|
|
199
|
+
appendFilteredStackLine(bus, formatConsoleArgs(args), "warn")
|
|
120
200
|
}
|
|
121
201
|
console.error = (...args: unknown[]) => {
|
|
122
|
-
bus
|
|
202
|
+
appendFilteredStackLine(bus, formatConsoleArgs(args), "error")
|
|
123
203
|
}
|
|
124
204
|
|
|
125
205
|
return () => {
|