@supatype/cli 0.1.0-alpha.9 → 0.1.1
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 +2 -2
- package/.turbo/turbo-test.log +285 -69
- package/.turbo/turbo-typecheck.log +1 -1
- package/assets/supatype-logo-wordmark.ascii.txt +6 -0
- package/bin/dev-entry.ts +2 -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 +110 -6
- package/dist/app/proxy-dev-app.js.map +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/assets/supatype-logo-wordmark.ascii.txt +6 -0
- package/dist/binary-cache.d.ts +19 -7
- package/dist/binary-cache.d.ts.map +1 -1
- package/dist/binary-cache.js +92 -46
- package/dist/binary-cache.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +17 -2
- 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 +86 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/admin.d.ts +28 -1
- package/dist/commands/admin.d.ts.map +1 -1
- package/dist/commands/admin.js +297 -149
- package/dist/commands/admin.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 +55 -0
- package/dist/commands/adopt.js.map +1 -0
- package/dist/commands/app.d.ts.map +1 -1
- package/dist/commands/app.js +20 -17
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/cache.d.ts.map +1 -1
- package/dist/commands/cache.js +11 -10
- package/dist/commands/cache.js.map +1 -1
- package/dist/commands/cloud.d.ts +4 -9
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +75 -125
- package/dist/commands/cloud.js.map +1 -1
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +37 -58
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +140 -96
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +74 -39
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +39 -39
- 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 +78 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/engine.d.ts.map +1 -1
- package/dist/commands/engine.js +5 -4
- package/dist/commands/engine.js.map +1 -1
- package/dist/commands/functions.d.ts.map +1 -1
- package/dist/commands/functions.js +172 -119
- package/dist/commands/functions.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +5 -4
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.d.ts +35 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +883 -107
- 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 +35 -0
- package/dist/commands/introspect.js.map +1 -0
- package/dist/commands/keys.d.ts +15 -1
- package/dist/commands/keys.d.ts.map +1 -1
- package/dist/commands/keys.js +46 -10
- package/dist/commands/keys.js.map +1 -1
- 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 +225 -0
- package/dist/commands/link-helpers.js.map +1 -0
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +5 -4
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/migrate-from-v1.d.ts.map +1 -1
- package/dist/commands/migrate-from-v1.js +3 -2
- package/dist/commands/migrate-from-v1.js.map +1 -1
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +119 -26
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/pg.d.ts.map +1 -1
- package/dist/commands/pg.js +11 -12
- package/dist/commands/pg.js.map +1 -1
- package/dist/commands/plugins.d.ts.map +1 -1
- package/dist/commands/plugins.js +55 -46
- package/dist/commands/plugins.js.map +1 -1
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +33 -5
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +111 -138
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/seed.d.ts.map +1 -1
- package/dist/commands/seed.js +4 -3
- package/dist/commands/seed.js.map +1 -1
- package/dist/commands/self-host.d.ts +2 -2
- package/dist/commands/self-host.d.ts.map +1 -1
- package/dist/commands/self-host.js +65 -50
- package/dist/commands/self-host.js.map +1 -1
- package/dist/commands/self-update.d.ts.map +1 -1
- package/dist/commands/self-update.js +3 -2
- package/dist/commands/self-update.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 +95 -29
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js +3 -2
- package/dist/commands/types.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +54 -21
- package/dist/commands/update.js.map +1 -1
- package/dist/compose-rename.d.ts +10 -0
- package/dist/compose-rename.d.ts.map +1 -0
- package/dist/compose-rename.js +67 -0
- package/dist/compose-rename.js.map +1 -0
- 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 +26 -0
- package/dist/dev-compose.d.ts.map +1 -1
- package/dist/dev-compose.js +357 -79
- package/dist/dev-compose.js.map +1 -1
- package/dist/dev-log-bus.d.ts +30 -0
- package/dist/dev-log-bus.d.ts.map +1 -0
- package/dist/dev-log-bus.js +87 -0
- package/dist/dev-log-bus.js.map +1 -0
- package/dist/dev-log-filter.d.ts +10 -0
- package/dist/dev-log-filter.d.ts.map +1 -0
- package/dist/dev-log-filter.js +36 -0
- package/dist/dev-log-filter.js.map +1 -0
- package/dist/dev-logo.d.ts +12 -0
- package/dist/dev-logo.d.ts.map +1 -0
- package/dist/dev-logo.js +56 -0
- package/dist/dev-logo.js.map +1 -0
- package/dist/dev-ports.d.ts +27 -0
- package/dist/dev-ports.d.ts.map +1 -0
- package/dist/dev-ports.js +171 -0
- package/dist/dev-ports.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 +81 -0
- package/dist/dev-session-lock.js.map +1 -0
- package/dist/dev-session.d.ts +26 -0
- package/dist/dev-session.d.ts.map +1 -0
- package/dist/dev-session.js +106 -0
- package/dist/dev-session.js.map +1 -0
- package/dist/dev-shutdown.d.ts +25 -0
- package/dist/dev-shutdown.d.ts.map +1 -0
- package/dist/dev-shutdown.js +114 -0
- package/dist/dev-shutdown.js.map +1 -0
- package/dist/dev-task-colors.d.ts +13 -0
- package/dist/dev-task-colors.d.ts.map +1 -0
- package/dist/dev-task-colors.js +43 -0
- package/dist/dev-task-colors.js.map +1 -0
- package/dist/dev-tui.d.ts +24 -0
- package/dist/dev-tui.d.ts.map +1 -0
- package/dist/dev-tui.js +188 -0
- package/dist/dev-tui.js.map +1 -0
- 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/docker-runtime.d.ts +30 -0
- package/dist/docker-runtime.d.ts.map +1 -0
- package/dist/docker-runtime.js +118 -0
- package/dist/docker-runtime.js.map +1 -0
- package/dist/engine-client.d.ts +10 -1
- package/dist/engine-client.d.ts.map +1 -1
- package/dist/engine-client.js +76 -17
- package/dist/engine-client.js.map +1 -1
- package/dist/engine-push-output.d.ts +17 -0
- package/dist/engine-push-output.d.ts.map +1 -0
- package/dist/engine-push-output.js +64 -0
- package/dist/engine-push-output.js.map +1 -0
- package/dist/ensure-binary.js +2 -2
- package/dist/ensure-binary.js.map +1 -1
- 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/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/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/link.d.ts +66 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +160 -0
- package/dist/link.js.map +1 -0
- package/dist/process-manager.d.ts +8 -0
- package/dist/process-manager.d.ts.map +1 -1
- package/dist/process-manager.js +53 -9
- package/dist/process-manager.js.map +1 -1
- package/dist/project-config.d.ts +30 -3
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +37 -4
- package/dist/project-config.js.map +1 -1
- package/dist/prompts.d.ts +3 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +3 -0
- package/dist/prompts.js.map +1 -0
- 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/restore-system-relation-targets.d.ts +3 -0
- package/dist/restore-system-relation-targets.d.ts.map +1 -0
- package/dist/restore-system-relation-targets.js +45 -0
- package/dist/restore-system-relation-targets.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/scripts/postinstall.js +5 -1
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/self-host-compose.d.ts +37 -1
- package/dist/self-host-compose.d.ts.map +1 -1
- package/dist/self-host-compose.js +234 -43
- 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/supatype-eval-1781522769253.d.mts +2 -0
- package/dist/supatype-eval-1781522769253.d.mts.map +1 -0
- package/dist/supatype-eval-1781522769253.mjs +3 -0
- package/dist/supatype-eval-1781522769253.mjs.map +1 -0
- package/dist/systemd.js +2 -2
- package/dist/systemd.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/dist/ui/brand.d.ts +9 -0
- package/dist/ui/brand.d.ts.map +1 -0
- package/dist/ui/brand.js +11 -0
- package/dist/ui/brand.js.map +1 -0
- package/dist/ui/confirm.d.ts +12 -0
- package/dist/ui/confirm.d.ts.map +1 -0
- package/dist/ui/confirm.js +28 -0
- package/dist/ui/confirm.js.map +1 -0
- package/dist/ui/fatal.d.ts +10 -0
- package/dist/ui/fatal.d.ts.map +1 -0
- package/dist/ui/fatal.js +34 -0
- package/dist/ui/fatal.js.map +1 -0
- package/dist/ui/index.d.ts +9 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +9 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/interactive.d.ts +3 -0
- package/dist/ui/interactive.d.ts.map +1 -0
- package/dist/ui/interactive.js +5 -0
- package/dist/ui/interactive.js.map +1 -0
- package/dist/ui/messages.d.ts +10 -0
- package/dist/ui/messages.d.ts.map +1 -0
- package/dist/ui/messages.js +35 -0
- package/dist/ui/messages.js.map +1 -0
- package/dist/ui/next-steps.d.ts +3 -0
- package/dist/ui/next-steps.d.ts.map +1 -0
- package/dist/ui/next-steps.js +10 -0
- package/dist/ui/next-steps.js.map +1 -0
- package/dist/ui/progress.d.ts +5 -0
- package/dist/ui/progress.d.ts.map +1 -0
- package/dist/ui/progress.js +24 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/prompts.d.ts +14 -0
- package/dist/ui/prompts.d.ts.map +1 -0
- package/dist/ui/prompts.js +34 -0
- package/dist/ui/prompts.js.map +1 -0
- package/package.json +5 -2
- package/src/app/framework.ts +1 -3
- package/src/app/proxy-dev-app.ts +114 -6
- package/src/app-config.ts +80 -0
- package/src/binary-cache.ts +102 -52
- package/src/cli.ts +16 -2
- package/src/commands/add.ts +97 -0
- package/src/commands/admin.ts +381 -190
- package/src/commands/adopt.ts +82 -0
- package/src/commands/app.ts +20 -17
- package/src/commands/cache.ts +11 -10
- package/src/commands/cloud.ts +91 -142
- package/src/commands/db.ts +40 -63
- package/src/commands/deploy.ts +186 -126
- package/src/commands/dev.ts +98 -55
- package/src/commands/diff.ts +52 -43
- package/src/commands/doctor.ts +103 -0
- package/src/commands/engine.ts +5 -4
- package/src/commands/functions.ts +187 -123
- package/src/commands/generate.ts +5 -4
- package/src/commands/init.ts +1087 -104
- package/src/commands/introspect.ts +48 -0
- package/src/commands/keys.ts +56 -14
- package/src/commands/link-helpers.ts +273 -0
- package/src/commands/logs.ts +5 -4
- package/src/commands/migrate-from-v1.ts +3 -2
- package/src/commands/migrate.ts +167 -27
- package/src/commands/pg.ts +13 -18
- package/src/commands/plugins.ts +55 -46
- package/src/commands/pull.ts +38 -9
- package/src/commands/push.ts +148 -175
- package/src/commands/seed.ts +5 -4
- package/src/commands/self-host.ts +85 -54
- package/src/commands/self-update.ts +3 -2
- package/src/commands/status.ts +102 -33
- package/src/commands/types.ts +3 -2
- package/src/commands/update.ts +59 -23
- package/src/compose-rename.ts +76 -0
- package/src/config.ts +2 -1
- package/src/dev-compose.ts +462 -76
- package/src/dev-log-bus.ts +101 -0
- package/src/dev-log-filter.ts +32 -0
- package/src/dev-logo.ts +61 -0
- package/src/dev-ports.ts +212 -0
- package/src/dev-session-lock.ts +101 -0
- package/src/dev-session.ts +130 -0
- package/src/dev-shutdown.ts +147 -0
- package/src/dev-task-colors.ts +47 -0
- package/src/dev-tui.ts +232 -0
- package/src/diff-output.ts +79 -1
- package/src/docker-runtime.ts +151 -0
- package/src/engine-client.ts +81 -17
- package/src/engine-push-output.ts +75 -0
- package/src/ensure-binary.ts +2 -2
- package/src/env-file.ts +37 -0
- package/src/gitignore.ts +48 -0
- package/src/kong-config.ts +24 -1
- package/src/link.ts +243 -0
- package/src/process-manager.ts +66 -10
- package/src/project-config.ts +62 -7
- package/src/prompts.ts +2 -0
- package/src/pull-utils.ts +217 -23
- package/src/resolve-target.ts +419 -0
- package/src/restore-system-relation-targets.ts +45 -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/scripts/postinstall.ts +7 -1
- package/src/self-host-compose.ts +262 -46
- package/src/storage-provision.ts +33 -1
- package/src/supatype-eval-1781522769253.mts +1 -0
- package/src/systemd.ts +2 -2
- package/src/target-client.ts +40 -0
- package/src/type-extractor.ts +124 -11
- package/src/ui/README.md +17 -0
- package/src/ui/brand.ts +12 -0
- package/src/ui/confirm.ts +38 -0
- package/src/ui/fatal.ts +43 -0
- package/src/ui/index.ts +8 -0
- package/src/ui/interactive.ts +4 -0
- package/src/ui/messages.ts +43 -0
- package/src/ui/next-steps.ts +10 -0
- package/src/ui/progress.ts +28 -0
- package/src/ui/prompts.ts +40 -0
- package/tests/admin-ensure.test.ts +59 -0
- package/tests/cli-help.test.ts +27 -2
- package/tests/config.test.ts +29 -2
- package/tests/dev-ports.test.ts +41 -0
- package/tests/dev-session-lock.test.ts +54 -0
- package/tests/dev-ui.test.ts +162 -0
- package/tests/docker-runtime.test.ts +236 -0
- package/tests/engine-push-output.test.ts +67 -0
- package/tests/init.test.ts +197 -18
- package/tests/link.test.ts +148 -0
- package/tests/minisign.test.ts +102 -0
- package/tests/proxy-dev-app.test.ts +45 -1
- package/tests/pull-utils.test.ts +5 -4
- package/tests/runtime-contract.test.ts +186 -2
- package/tests/schema-sources.test.ts +119 -0
- package/tests/storage-provision.test.ts +100 -0
- package/tests/ui-confirm.test.ts +41 -0
- package/tests/ui-messages.test.ts +66 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graceful shutdown for `supatype dev` — SIGINT, TUI Ctrl+C, terminal close,
|
|
3
|
+
* and a synchronous compose-down fallback on process exit.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync } from "node:fs"
|
|
7
|
+
import { endDevSession } from "./dev-session.js"
|
|
8
|
+
import { clearDevSessionLock } from "./dev-session-lock.js"
|
|
9
|
+
import { runDockerCompose } from "./self-host-compose.js"
|
|
10
|
+
|
|
11
|
+
export interface DevComposeShutdownFallback {
|
|
12
|
+
cwd: string
|
|
13
|
+
composePath: string
|
|
14
|
+
composeProject: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let shutdownWork: (() => Promise<void>) | null = null
|
|
18
|
+
let composeFallback: DevComposeShutdownFallback | null = null
|
|
19
|
+
let shutdownCwd: string | null = null
|
|
20
|
+
let shuttingDown = false
|
|
21
|
+
let shutdownCompleted = false
|
|
22
|
+
let forceQuitRequested = false
|
|
23
|
+
let hooksRegistered = false
|
|
24
|
+
|
|
25
|
+
function onSignal(): void {
|
|
26
|
+
void runDevShutdown()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function onStdinClose(): void {
|
|
30
|
+
if (!process.stdin.isTTY) return
|
|
31
|
+
void runDevShutdown()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function syncComposeDownFallback(): void {
|
|
35
|
+
if (shutdownCompleted || !composeFallback) return
|
|
36
|
+
try {
|
|
37
|
+
runDockerCompose(
|
|
38
|
+
composeFallback.composePath,
|
|
39
|
+
["down"],
|
|
40
|
+
composeFallback.cwd,
|
|
41
|
+
composeFallback.composeProject,
|
|
42
|
+
{ quiet: true },
|
|
43
|
+
)
|
|
44
|
+
} catch {
|
|
45
|
+
// best-effort — process is exiting
|
|
46
|
+
}
|
|
47
|
+
if (shutdownCwd) clearDevSessionLock(shutdownCwd)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function onProcessExit(): void {
|
|
51
|
+
syncComposeDownFallback()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface RegisterDevShutdownOptions {
|
|
55
|
+
/** Sync `docker compose down` when async teardown cannot finish (terminal close, kill). */
|
|
56
|
+
compose?: DevComposeShutdownFallback
|
|
57
|
+
/** Project root — clears `.supatype/dev-session.json` after shutdown. */
|
|
58
|
+
cwd?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Register async teardown (stop children, compose down, etc.). Call once per dev session. */
|
|
62
|
+
export function registerDevShutdown(
|
|
63
|
+
work: () => Promise<void>,
|
|
64
|
+
opts?: RegisterDevShutdownOptions,
|
|
65
|
+
): void {
|
|
66
|
+
shutdownWork = work
|
|
67
|
+
composeFallback = opts?.compose ?? null
|
|
68
|
+
shutdownCwd = opts?.cwd ?? opts?.compose?.cwd ?? null
|
|
69
|
+
|
|
70
|
+
if (hooksRegistered) return
|
|
71
|
+
hooksRegistered = true
|
|
72
|
+
|
|
73
|
+
process.on("SIGINT", onSignal)
|
|
74
|
+
process.on("SIGTERM", onSignal)
|
|
75
|
+
if (process.platform === "win32") {
|
|
76
|
+
process.on("SIGBREAK", onSignal)
|
|
77
|
+
}
|
|
78
|
+
process.on("exit", onProcessExit)
|
|
79
|
+
|
|
80
|
+
if (process.stdin.isTTY) {
|
|
81
|
+
process.stdin.on("end", onStdinClose)
|
|
82
|
+
process.stdin.on("close", onStdinClose)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** TUI Ctrl+C — do not re-emit SIGINT (avoids double-fire on Windows raw mode). */
|
|
87
|
+
export function requestDevShutdown(): void {
|
|
88
|
+
void runDevShutdown()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function isDevShuttingDown(): boolean {
|
|
92
|
+
return shuttingDown
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** @internal Tests — reset module state between cases. */
|
|
96
|
+
export function resetDevShutdownForTests(): void {
|
|
97
|
+
shutdownWork = null
|
|
98
|
+
composeFallback = null
|
|
99
|
+
shutdownCwd = null
|
|
100
|
+
shuttingDown = false
|
|
101
|
+
shutdownCompleted = false
|
|
102
|
+
forceQuitRequested = false
|
|
103
|
+
hooksRegistered = false
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function runDevShutdown(): Promise<void> {
|
|
107
|
+
if (shuttingDown) {
|
|
108
|
+
if (!forceQuitRequested) {
|
|
109
|
+
forceQuitRequested = true
|
|
110
|
+
process.stderr.write(
|
|
111
|
+
"\n[supatype] Still shutting down (stopping Docker)… press Ctrl+C again to force quit.\n",
|
|
112
|
+
)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
endDevSession()
|
|
117
|
+
} catch {
|
|
118
|
+
// best-effort terminal restore
|
|
119
|
+
}
|
|
120
|
+
process.stderr.write("\n[supatype] Forced quit — Docker containers may still be running.\n")
|
|
121
|
+
process.stdout.write("\n")
|
|
122
|
+
process.exit(130)
|
|
123
|
+
}
|
|
124
|
+
shuttingDown = true
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
endDevSession()
|
|
128
|
+
process.stdout.write("\n")
|
|
129
|
+
await shutdownWork?.()
|
|
130
|
+
shutdownCompleted = true
|
|
131
|
+
if (shutdownCwd) clearDevSessionLock(shutdownCwd)
|
|
132
|
+
process.exit(0)
|
|
133
|
+
} catch (err) {
|
|
134
|
+
process.stderr.write(`[supatype] Shutdown failed: ${(err as Error).message}\n`)
|
|
135
|
+
process.stderr.write(
|
|
136
|
+
"[supatype] Docker containers may still be running — try: supatype self-host compose down\n",
|
|
137
|
+
)
|
|
138
|
+
syncComposeDownFallback()
|
|
139
|
+
shutdownCompleted = true
|
|
140
|
+
process.exit(1)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Whether a compose fallback was registered (tests). */
|
|
145
|
+
export function hasComposeShutdownFallback(): boolean {
|
|
146
|
+
return composeFallback !== null && existsSync(composeFallback.composePath)
|
|
147
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANSI colours for `supatype dev` TUI task list and log panes.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BOLD, DIM, RESET } from "./ui/brand.js"
|
|
6
|
+
|
|
7
|
+
export { RESET, DIM, BOLD }
|
|
8
|
+
|
|
9
|
+
/** Purple for orchestrator logs, green for Vite/app, etc. */
|
|
10
|
+
export function taskColor(taskId: string): string {
|
|
11
|
+
switch (taskId) {
|
|
12
|
+
case "stack":
|
|
13
|
+
return "\x1b[35m"
|
|
14
|
+
case "app":
|
|
15
|
+
return "\x1b[32m"
|
|
16
|
+
case "studio":
|
|
17
|
+
return "\x1b[96m"
|
|
18
|
+
case "server":
|
|
19
|
+
return "\x1b[32m"
|
|
20
|
+
case "postgrest":
|
|
21
|
+
return "\x1b[36m"
|
|
22
|
+
default:
|
|
23
|
+
return "\x1b[37m"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function levelColor(line: string): string | null {
|
|
28
|
+
if (line.startsWith("✗ ")) return "\x1b[31m"
|
|
29
|
+
if (line.startsWith("⚠ ")) return "\x1b[33m"
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Drop redundant prefix — task pane is already labelled supatype. */
|
|
34
|
+
export function normalizeStackLogLine(line: string): string {
|
|
35
|
+
return line.replace(/^\[supatype\]\s*/, "")
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function colorizeLogLine(taskId: string, line: string): string {
|
|
39
|
+
const level = levelColor(line)
|
|
40
|
+
const base = level ?? taskColor(taskId)
|
|
41
|
+
return `${base}${line}${RESET}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function colorizeTaskLabel(taskId: string, label: string, focused: boolean): string {
|
|
45
|
+
const style = focused ? BOLD + taskColor(taskId) : taskColor(taskId)
|
|
46
|
+
return `${style}${label}${RESET}`
|
|
47
|
+
}
|
package/src/dev-tui.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive terminal UI for `supatype dev` — task list + focused log pane.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { stripAnsi } from "./dev-log-filter.js"
|
|
6
|
+
import type { DevLogBus } from "./dev-log-bus.js"
|
|
7
|
+
import {
|
|
8
|
+
colorizeLogLine,
|
|
9
|
+
colorizeTaskLabel,
|
|
10
|
+
DIM,
|
|
11
|
+
RESET,
|
|
12
|
+
taskColor,
|
|
13
|
+
} from "./dev-task-colors.js"
|
|
14
|
+
import {
|
|
15
|
+
colorLogoLines,
|
|
16
|
+
layoutLogoBlock,
|
|
17
|
+
logoRowCount,
|
|
18
|
+
pickLogoLines,
|
|
19
|
+
} from "./dev-logo.js"
|
|
20
|
+
import { requestDevShutdown } from "./dev-shutdown.js"
|
|
21
|
+
|
|
22
|
+
const ENTER_ALT_SCREEN = "\x1b[?1049h"
|
|
23
|
+
const LEAVE_ALT_SCREEN = "\x1b[?1049l"
|
|
24
|
+
const HIDE_CURSOR = "\x1b[?25l"
|
|
25
|
+
const SHOW_CURSOR = "\x1b[?25h"
|
|
26
|
+
const CLEAR_SCREEN = "\x1b[2J\x1b[H"
|
|
27
|
+
|
|
28
|
+
const TASK_COL_WIDTH = 22
|
|
29
|
+
const MIN_WIDTH = 60
|
|
30
|
+
const MIN_HEIGHT = 14
|
|
31
|
+
|
|
32
|
+
export class DevTui {
|
|
33
|
+
private active = false
|
|
34
|
+
private scrollFromBottom = 0
|
|
35
|
+
private renderPending = false
|
|
36
|
+
private unsubscribeBus: (() => void) | null = null
|
|
37
|
+
private readonly onResize = (): void => this.scheduleRender()
|
|
38
|
+
private readonly onData = (chunk: Buffer): void => this.handleInput(chunk)
|
|
39
|
+
|
|
40
|
+
constructor(private readonly bus: DevLogBus) {}
|
|
41
|
+
|
|
42
|
+
start(): void {
|
|
43
|
+
if (this.active) return
|
|
44
|
+
this.active = true
|
|
45
|
+
this.scrollFromBottom = 0
|
|
46
|
+
|
|
47
|
+
if (process.stdin.isTTY) {
|
|
48
|
+
process.stdin.setRawMode(true)
|
|
49
|
+
process.stdin.resume()
|
|
50
|
+
process.stdin.on("data", this.onData)
|
|
51
|
+
}
|
|
52
|
+
process.stdout.on("resize", this.onResize)
|
|
53
|
+
|
|
54
|
+
process.stdout.write(ENTER_ALT_SCREEN + HIDE_CURSOR)
|
|
55
|
+
this.unsubscribeBus = this.bus.onUpdate(() => this.scheduleRender())
|
|
56
|
+
this.scheduleRender()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
stop(): void {
|
|
60
|
+
if (!this.active) return
|
|
61
|
+
this.active = false
|
|
62
|
+
|
|
63
|
+
this.unsubscribeBus?.()
|
|
64
|
+
this.unsubscribeBus = null
|
|
65
|
+
|
|
66
|
+
process.stdout.off("resize", this.onResize)
|
|
67
|
+
if (process.stdin.isTTY) {
|
|
68
|
+
process.stdin.off("data", this.onData)
|
|
69
|
+
process.stdin.setRawMode(false)
|
|
70
|
+
process.stdin.pause()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
process.stdout.write(SHOW_CURSOR + LEAVE_ALT_SCREEN)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private scheduleRender(): void {
|
|
77
|
+
if (!this.active || this.renderPending) return
|
|
78
|
+
this.renderPending = true
|
|
79
|
+
setImmediate(() => {
|
|
80
|
+
this.renderPending = false
|
|
81
|
+
if (this.active) this.render()
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private handleInput(chunk: Buffer): void {
|
|
86
|
+
const key = chunk.toString()
|
|
87
|
+
|
|
88
|
+
if (key === "\x03") {
|
|
89
|
+
requestDevShutdown()
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
switch (key) {
|
|
94
|
+
case "j":
|
|
95
|
+
case "\x1b[B":
|
|
96
|
+
this.bus.focusNext()
|
|
97
|
+
this.scrollFromBottom = 0
|
|
98
|
+
break
|
|
99
|
+
case "k":
|
|
100
|
+
case "\x1b[A":
|
|
101
|
+
this.bus.focusPrevious()
|
|
102
|
+
this.scrollFromBottom = 0
|
|
103
|
+
break
|
|
104
|
+
case "u":
|
|
105
|
+
this.scrollFromBottom = Math.min(
|
|
106
|
+
this.scrollFromBottom + 3,
|
|
107
|
+
this.maxScroll(this.bus.getFocusedTaskId()),
|
|
108
|
+
)
|
|
109
|
+
break
|
|
110
|
+
case "d":
|
|
111
|
+
this.scrollFromBottom = Math.max(this.scrollFromBottom - 3, 0)
|
|
112
|
+
break
|
|
113
|
+
case "g":
|
|
114
|
+
this.scrollFromBottom = this.maxScroll(this.bus.getFocusedTaskId())
|
|
115
|
+
break
|
|
116
|
+
case "G":
|
|
117
|
+
this.scrollFromBottom = 0
|
|
118
|
+
break
|
|
119
|
+
default:
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
this.scheduleRender()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private maxScroll(taskId: string): number {
|
|
126
|
+
const task = this.bus.getTask(taskId)
|
|
127
|
+
if (!task) return 0
|
|
128
|
+
const logHeight = this.splitPaneHeight()
|
|
129
|
+
return Math.max(0, task.lines.length - logHeight)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private chromeRowCount(): number {
|
|
133
|
+
// logo + separator + keybind hint + footer
|
|
134
|
+
return logoRowCount() + 3
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private splitPaneHeight(): number {
|
|
138
|
+
const rows = Math.max(MIN_HEIGHT, process.stdout.rows ?? 24)
|
|
139
|
+
return Math.max(4, rows - this.chromeRowCount())
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private render(): void {
|
|
143
|
+
const cols = Math.max(MIN_WIDTH, process.stdout.columns ?? 80)
|
|
144
|
+
const logWidth = cols - TASK_COL_WIDTH - 1
|
|
145
|
+
const logHeight = this.splitPaneHeight()
|
|
146
|
+
|
|
147
|
+
const lines: string[] = []
|
|
148
|
+
|
|
149
|
+
const logoPlain = layoutLogoBlock(pickLogoLines())
|
|
150
|
+
for (const colored of colorLogoLines(logoPlain)) {
|
|
151
|
+
lines.push(formatLogoRow(colored, cols))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
lines.push(`${DIM}${"─".repeat(cols)}${RESET}`)
|
|
155
|
+
lines.push(
|
|
156
|
+
`${DIM}${truncate(
|
|
157
|
+
" ↑/k ↓/j task u/d scroll g/G top/bottom Ctrl+C quit",
|
|
158
|
+
cols,
|
|
159
|
+
)}${RESET}`,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const taskIds = this.bus.getTaskOrder()
|
|
163
|
+
const focusedId = this.bus.getFocusedTaskId()
|
|
164
|
+
|
|
165
|
+
for (let row = 0; row < logHeight; row++) {
|
|
166
|
+
let left = " ".repeat(TASK_COL_WIDTH)
|
|
167
|
+
const taskId = taskIds[row]
|
|
168
|
+
if (taskId) {
|
|
169
|
+
const task = this.bus.getTask(taskId)
|
|
170
|
+
if (task) {
|
|
171
|
+
const marker = taskId === focusedId ? "▶" : " "
|
|
172
|
+
const unread = task.unread ? " •" : " "
|
|
173
|
+
const label = truncate(`${marker} ${task.title}${unread}`, TASK_COL_WIDTH - 1)
|
|
174
|
+
left = padVisible(
|
|
175
|
+
colorizeTaskLabel(taskId, label, taskId === focusedId),
|
|
176
|
+
TASK_COL_WIDTH,
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const logLine = this.logLineAt(focusedId, row, logHeight, logWidth)
|
|
182
|
+
lines.push(left + `${DIM}│${RESET}` + logLine)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const focusedTitle = this.bus.getTask(focusedId)?.title ?? focusedId
|
|
186
|
+
lines.push(`${DIM} focused:${RESET} ${colorizeTaskLabel(focusedId, focusedTitle, true)}`)
|
|
187
|
+
|
|
188
|
+
process.stdout.write(CLEAR_SCREEN + lines.join("\n"))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private logLineAt(taskId: string, row: number, logHeight: number, width: number): string {
|
|
192
|
+
const task = this.bus.getTask(taskId)
|
|
193
|
+
if (!task || task.lines.length === 0) {
|
|
194
|
+
return " ".repeat(width)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const total = task.lines.length
|
|
198
|
+
const end = total - this.scrollFromBottom
|
|
199
|
+
const start = Math.max(0, end - logHeight)
|
|
200
|
+
const index = start + row
|
|
201
|
+
if (index >= end || index >= total) {
|
|
202
|
+
return " ".repeat(width)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const raw = stripAnsi(task.lines[index] ?? "")
|
|
206
|
+
const clipped = truncate(raw, width)
|
|
207
|
+
return padVisible(colorizeLogLine(taskId, clipped), width)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function truncate(text: string, width: number): string {
|
|
212
|
+
if (width <= 0) return ""
|
|
213
|
+
if (text.length <= width) return text
|
|
214
|
+
if (width <= 1) return text.slice(0, width)
|
|
215
|
+
return `${text.slice(0, width - 1)}…`
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Pad to visible width (ignores ANSI sequences already stripped from input). */
|
|
219
|
+
function padVisible(text: string, width: number): string {
|
|
220
|
+
const visible = stripAnsi(text)
|
|
221
|
+
if (visible.length >= width) return text
|
|
222
|
+
return text + " ".repeat(width - visible.length)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function formatLogoRow(colored: string, cols: number): string {
|
|
226
|
+
const plain = stripAnsi(colored)
|
|
227
|
+
if (plain.length <= cols) {
|
|
228
|
+
return plain.length < cols ? `${colored}${" ".repeat(cols - plain.length)}` : colored
|
|
229
|
+
}
|
|
230
|
+
const purple = taskColor("stack")
|
|
231
|
+
return `${purple}\x1b[1m${plain.slice(0, cols)}${RESET}`
|
|
232
|
+
}
|
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
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker daemon availability — used before `docker compose` and other docker CLI calls.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { spawnSync } from "node:child_process"
|
|
6
|
+
import { platform } from "node:os"
|
|
7
|
+
import { endDevSession, getActiveDevSession } from "./dev-session.js"
|
|
8
|
+
import { isInteractive } from "./ui/interactive.js"
|
|
9
|
+
import { error, plain } from "./ui/messages.js"
|
|
10
|
+
import { clack as p, printLogo } from "./ui/prompts.js"
|
|
11
|
+
|
|
12
|
+
export type DockerDaemonProbe =
|
|
13
|
+
| { ok: true }
|
|
14
|
+
| { ok: false; reason: "cli_missing" | "daemon_unavailable"; detail?: string }
|
|
15
|
+
|
|
16
|
+
export type DockerBrandOptions = {
|
|
17
|
+
intro: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type DockerReportOptions = {
|
|
21
|
+
/** Logo + Clack intro before the error (e.g. `supatype dev` entry). */
|
|
22
|
+
brand?: DockerBrandOptions
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function dockerSpawn(args: string[]) {
|
|
26
|
+
return spawnSync("docker", args, {
|
|
27
|
+
encoding: "utf8",
|
|
28
|
+
stdio: "pipe",
|
|
29
|
+
shell: process.platform === "win32",
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Returns whether the Docker CLI is on PATH and the daemon accepts connections. */
|
|
34
|
+
export function probeDockerDaemon(): DockerDaemonProbe {
|
|
35
|
+
const version = dockerSpawn(["version", "--format", "{{.Client.Version}}"])
|
|
36
|
+
if (version.error && "code" in version.error && version.error.code === "ENOENT") {
|
|
37
|
+
return { ok: false, reason: "cli_missing" }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const clientVersion = (version.stdout ?? "").trim()
|
|
41
|
+
const versionStderr = (version.stderr ?? "").trim()
|
|
42
|
+
const versionDetail = `${versionStderr}${version.stdout ?? ""}`.trim()
|
|
43
|
+
|
|
44
|
+
// No client version — CLI missing or broken.
|
|
45
|
+
if (!clientVersion) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
reason: "cli_missing",
|
|
49
|
+
...(versionDetail ? { detail: versionDetail } : {}),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Client is present but daemon refused (paused/stopped). `docker info` can hang
|
|
54
|
+
// while paused on Docker Desktop — use the version stderr and skip info.
|
|
55
|
+
if (version.status !== 0 && versionStderr) {
|
|
56
|
+
return { ok: false, reason: "daemon_unavailable", detail: versionStderr }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const info = dockerSpawn(["info"])
|
|
60
|
+
if (info.status === 0) return { ok: true }
|
|
61
|
+
|
|
62
|
+
const infoDetail = `${info.stderr ?? ""}${info.stdout ?? ""}`.trim()
|
|
63
|
+
const detail = infoDetail || versionDetail
|
|
64
|
+
return { ok: false, reason: "daemon_unavailable", ...(detail ? { detail } : {}) }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function shouldShowDockerDetail(probe: Extract<DockerDaemonProbe, { ok: false }>): boolean {
|
|
68
|
+
if (!probe.detail) return false
|
|
69
|
+
const lower = probe.detail.toLowerCase()
|
|
70
|
+
if (probe.reason !== "daemon_unavailable") return true
|
|
71
|
+
// Skip raw daemon stderr when we already print a clearer hint.
|
|
72
|
+
return !lower.includes("paused") && !lower.includes("cannot connect")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function dockerUnavailableHeadline(probe: Extract<DockerDaemonProbe, { ok: false }>): string {
|
|
76
|
+
if (probe.reason === "cli_missing") {
|
|
77
|
+
return "Docker is not installed or not on your PATH."
|
|
78
|
+
}
|
|
79
|
+
return "Docker is installed but the daemon is not running."
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function dockerUnavailableHints(probe: Extract<DockerDaemonProbe, { ok: false }>): string[] {
|
|
83
|
+
const hints: string[] = []
|
|
84
|
+
|
|
85
|
+
if (probe.reason === "cli_missing") {
|
|
86
|
+
hints.push(
|
|
87
|
+
"Install Docker Desktop (https://www.docker.com/products/docker-desktop/) or add the docker CLI to PATH.",
|
|
88
|
+
)
|
|
89
|
+
} else if (probe.detail?.toLowerCase().includes("paused")) {
|
|
90
|
+
hints.push("Unpause Docker Desktop from the whale menu or Dashboard, then try again.")
|
|
91
|
+
} else if (platform() === "win32" || platform() === "darwin") {
|
|
92
|
+
hints.push("Start Docker Desktop, then try again.")
|
|
93
|
+
} else {
|
|
94
|
+
hints.push("Start the Docker daemon (e.g. sudo systemctl start docker), then try again.")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
hints.push('To develop without Docker, set provider: "native" in supatype.config.ts.')
|
|
98
|
+
|
|
99
|
+
if (shouldShowDockerDetail(probe)) {
|
|
100
|
+
const firstLine = probe.detail!.split(/\r?\n/).find((line) => line.trim().length > 0)
|
|
101
|
+
if (firstLine) hints.push(`docker: ${firstLine.trim()}`)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return hints
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** @deprecated Tests only — use `reportDockerUnavailable`. */
|
|
108
|
+
export function formatDockerUnavailableMessage(
|
|
109
|
+
probe: Extract<DockerDaemonProbe, { ok: false }>,
|
|
110
|
+
): string {
|
|
111
|
+
const lines: string[] = [dockerUnavailableHeadline(probe), ...dockerUnavailableHints(probe)]
|
|
112
|
+
return lines.join("\n")
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Print Docker-unavailable guidance via the shared CLI message layer. */
|
|
116
|
+
export function reportDockerUnavailable(
|
|
117
|
+
probe: Extract<DockerDaemonProbe, { ok: false }>,
|
|
118
|
+
opts?: DockerReportOptions,
|
|
119
|
+
): void {
|
|
120
|
+
if (opts?.brand && isInteractive()) {
|
|
121
|
+
printLogo()
|
|
122
|
+
p.intro(opts.brand.intro)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const headline = dockerUnavailableHeadline(probe)
|
|
126
|
+
const hints = dockerUnavailableHints(probe)
|
|
127
|
+
|
|
128
|
+
error(headline)
|
|
129
|
+
|
|
130
|
+
if (isInteractive()) {
|
|
131
|
+
p.note(hints.join("\n\n"))
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const hint of hints) {
|
|
136
|
+
plain()
|
|
137
|
+
plain(` ${hint}`)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Exit 1 with a friendly message when Docker is not usable. */
|
|
142
|
+
export function requireDockerDaemon(opts?: DockerReportOptions): void {
|
|
143
|
+
const probe = probeDockerDaemon()
|
|
144
|
+
if (probe.ok) return
|
|
145
|
+
// Dev TUI patches console — restore stderr before printing a fatal message.
|
|
146
|
+
if (getActiveDevSession()) {
|
|
147
|
+
endDevSession()
|
|
148
|
+
}
|
|
149
|
+
reportDockerUnavailable(probe, opts)
|
|
150
|
+
process.exit(1)
|
|
151
|
+
}
|