@supatype/cli 0.1.0-alpha.9 → 0.1.0
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 +270 -65
- 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.map +1 -1
- package/dist/commands/admin.js +39 -53
- 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 +72 -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 +30 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +814 -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 +110 -137
- 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/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 +328 -34
- 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-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 +9 -0
- package/dist/dev-shutdown.d.ts.map +1 -0
- package/dist/dev-shutdown.js +50 -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/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 +233 -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 +3 -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 +39 -73
- 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 +95 -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 +996 -105
- 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 +147 -174
- 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/config.ts +2 -1
- package/src/dev-compose.ts +426 -34
- 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-session.ts +130 -0
- package/src/dev-shutdown.ts +54 -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/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 +261 -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/cli-help.test.ts +27 -2
- package/tests/config.test.ts +29 -2
- package/tests/dev-ui.test.ts +139 -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
package/src/commands/init.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { Command } from "commander"
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
3
|
-
import { resolve, join, dirname } from "node:path"
|
|
3
|
+
import { resolve, join, dirname, basename } from "node:path"
|
|
4
4
|
import { fileURLToPath } from "node:url"
|
|
5
|
-
import {
|
|
5
|
+
import { spawnSync } from "node:child_process"
|
|
6
|
+
import * as p from "@clack/prompts"
|
|
7
|
+
import { ensureNotCancelled, printLogo } from "../ui/prompts.js"
|
|
8
|
+
import { generateAndWriteKeys } from "./keys.js"
|
|
9
|
+
import { file, error, info, plain, warn } from "../ui/messages.js"
|
|
10
|
+
import { nextSteps } from "../ui/next-steps.js"
|
|
11
|
+
import { probeDockerDaemon, reportDockerUnavailable } from "../docker-runtime.js"
|
|
6
12
|
|
|
7
13
|
export { scaffold }
|
|
8
14
|
|
|
@@ -26,158 +32,852 @@ function cliPackageVersion(): string {
|
|
|
26
32
|
}
|
|
27
33
|
}
|
|
28
34
|
|
|
35
|
+
// ─── Options model ─────────────────────────────────────────────────────────--
|
|
36
|
+
|
|
37
|
+
type PackageManager = "npm" | "pnpm" | "yarn" | "bun"
|
|
38
|
+
|
|
39
|
+
/** Where the project runs in production (drives committed config + local override). */
|
|
40
|
+
type ProductionTarget = "cloud" | "self-host" | "later"
|
|
41
|
+
|
|
42
|
+
export interface ScaffoldAppOptions {
|
|
43
|
+
mode: "none" | "static" | "proxy"
|
|
44
|
+
staticDir?: string
|
|
45
|
+
upstream?: string
|
|
46
|
+
start?: string
|
|
47
|
+
viteDevUrl?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** File-affecting answers that drive what `scaffold()` writes. */
|
|
51
|
+
export interface ScaffoldOptions {
|
|
52
|
+
projectName: string
|
|
53
|
+
/** Local development runtime (docker recommended). */
|
|
54
|
+
provider: "docker" | "native"
|
|
55
|
+
productionTarget: ProductionTarget
|
|
56
|
+
domain?: string
|
|
57
|
+
/** ACME contact email for Let's Encrypt HTTPS (self-host + domain). */
|
|
58
|
+
tlsEmail?: string
|
|
59
|
+
schemaPath: string
|
|
60
|
+
app: ScaffoldAppOptions
|
|
61
|
+
email: "console" | "smtp" | "resend" | "ses"
|
|
62
|
+
/** Object storage while developing locally (`supatype dev`). */
|
|
63
|
+
storageLocal: "local" | "s3"
|
|
64
|
+
/** Object storage when deployed to production. */
|
|
65
|
+
storageProduction: "local" | "s3"
|
|
66
|
+
helloFunction: boolean
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type StorageProvider = ScaffoldOptions["storageLocal"]
|
|
70
|
+
|
|
71
|
+
const STORAGE_PROVIDER_OPTIONS: {
|
|
72
|
+
value: StorageProvider
|
|
73
|
+
label: string
|
|
74
|
+
hint: string
|
|
75
|
+
}[] = [
|
|
76
|
+
{ value: "local", label: "Local", hint: "storage you host yourself (MinIO)" },
|
|
77
|
+
{ value: "s3", label: "S3", hint: "external bucket (AWS S3 or compatible)" },
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
/** Wizard result = scaffold options plus runtime actions (install / keys). */
|
|
81
|
+
interface WizardResult extends ScaffoldOptions {
|
|
82
|
+
packageManager: PackageManager
|
|
83
|
+
install: boolean
|
|
84
|
+
generateKeys: boolean
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** `--mode dev|standalone` is mapped onto a production target for back-compat. */
|
|
88
|
+
function productionTargetFromMode(mode: string): ProductionTarget {
|
|
89
|
+
return mode === "standalone" ? "self-host" : "later"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** supatype-server mode written to the committed config for a production target. */
|
|
93
|
+
function serverModeForTarget(target: ProductionTarget): "dev" | "standalone" | "managed" {
|
|
94
|
+
switch (target) {
|
|
95
|
+
case "cloud":
|
|
96
|
+
return "managed"
|
|
97
|
+
case "self-host":
|
|
98
|
+
return "standalone"
|
|
99
|
+
case "later":
|
|
100
|
+
return "dev"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function defaultScaffoldOptions(
|
|
105
|
+
projectName: string,
|
|
106
|
+
productionTarget: ProductionTarget = "later",
|
|
107
|
+
): ScaffoldOptions {
|
|
108
|
+
return {
|
|
109
|
+
projectName,
|
|
110
|
+
provider: "docker",
|
|
111
|
+
productionTarget,
|
|
112
|
+
...(productionTarget === "self-host" ? { domain: "" } : {}),
|
|
113
|
+
schemaPath: "schema/index.ts",
|
|
114
|
+
app: { mode: "none" },
|
|
115
|
+
email: "console",
|
|
116
|
+
storageLocal: "local",
|
|
117
|
+
storageProduction: "local",
|
|
118
|
+
helloFunction: false,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ─── Registration ──────────────────────────────────────────────────────────--
|
|
123
|
+
|
|
124
|
+
interface InitCliOptions {
|
|
125
|
+
mode: string
|
|
126
|
+
defaults?: boolean
|
|
127
|
+
install: boolean
|
|
128
|
+
keys: boolean
|
|
129
|
+
}
|
|
130
|
+
|
|
29
131
|
export function registerInit(program: Command): void {
|
|
30
132
|
program
|
|
31
133
|
.command("init [name]")
|
|
32
134
|
.description("Scaffold a new Supatype project")
|
|
33
135
|
.option(
|
|
34
136
|
"--mode <mode>",
|
|
35
|
-
"
|
|
137
|
+
"Back-compat: dev (default, local only) | standalone (self-host production target)",
|
|
36
138
|
"dev",
|
|
37
139
|
)
|
|
38
|
-
.
|
|
39
|
-
|
|
140
|
+
.option("-y, --defaults", "Skip all prompts and use sensible defaults")
|
|
141
|
+
.option("--no-install", "Do not run the package manager install step")
|
|
142
|
+
.option("--no-keys", "Do not generate ANON_KEY / SERVICE_ROLE_KEY")
|
|
143
|
+
.action(async (name: string | undefined, opts: InitCliOptions) => {
|
|
40
144
|
const dir = name ? resolve(process.cwd(), name) : process.cwd()
|
|
41
145
|
|
|
42
146
|
if (name && existsSync(dir)) {
|
|
43
|
-
|
|
147
|
+
error(`Directory already exists: ${dir}`)
|
|
44
148
|
process.exit(1)
|
|
45
149
|
}
|
|
46
150
|
|
|
47
|
-
|
|
151
|
+
const defaultName = name ?? basename(dir) ?? "my-project"
|
|
152
|
+
const interactive = !opts.defaults && Boolean(process.stdin.isTTY)
|
|
48
153
|
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
console.log("Fetching latest component versions from CDN...")
|
|
52
|
-
versions = await fetchAllLatestVersions()
|
|
53
|
-
} catch {
|
|
54
|
-
// Non-fatal: scaffold with placeholder versions; user can run `supatype update`.
|
|
55
|
-
}
|
|
154
|
+
const modeTarget = productionTargetFromMode(opts.mode)
|
|
56
155
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
console.log(" npm run build # write files into public/")
|
|
69
|
-
console.log(" supatype self-host compose up -d")
|
|
70
|
-
if (opts.mode === "standalone") {
|
|
71
|
-
console.log("\nStandalone (native TLS with ACME):")
|
|
72
|
-
console.log(" Edit supatype.config.ts — set server.domain")
|
|
73
|
-
console.log(" supatype dev # or run supatype-server with your TLS setup")
|
|
156
|
+
let result: WizardResult
|
|
157
|
+
if (interactive) {
|
|
158
|
+
printLogo()
|
|
159
|
+
result = await runWizard(defaultName, modeTarget)
|
|
160
|
+
} else {
|
|
161
|
+
result = {
|
|
162
|
+
...defaultScaffoldOptions(defaultName, modeTarget),
|
|
163
|
+
packageManager: detectInvokingPackageManager(),
|
|
164
|
+
install: true,
|
|
165
|
+
generateKeys: true,
|
|
166
|
+
}
|
|
74
167
|
}
|
|
75
|
-
|
|
168
|
+
|
|
169
|
+
// CLI flags override wizard / default action choices.
|
|
170
|
+
const doInstall = opts.install !== false && result.install
|
|
171
|
+
const doKeys = opts.keys !== false && result.generateKeys
|
|
172
|
+
|
|
173
|
+
if (name) mkdirSync(dir, { recursive: true })
|
|
174
|
+
|
|
175
|
+
scaffold(dir, result)
|
|
176
|
+
|
|
177
|
+
if (doInstall) runInstall(dir, result.packageManager)
|
|
178
|
+
const keysGenerated = doKeys ? writeKeys(dir) : false
|
|
179
|
+
|
|
180
|
+
warnDockerUnavailableForProvider(result.provider)
|
|
181
|
+
|
|
182
|
+
printNextSteps({
|
|
183
|
+
name,
|
|
184
|
+
result,
|
|
185
|
+
installed: doInstall,
|
|
186
|
+
keysGenerated,
|
|
187
|
+
})
|
|
76
188
|
})
|
|
77
189
|
}
|
|
78
190
|
|
|
79
|
-
|
|
191
|
+
// ─── Wizard ──────────────────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
async function runWizard(
|
|
194
|
+
defaultName: string,
|
|
195
|
+
defaultTarget: ProductionTarget,
|
|
196
|
+
): Promise<WizardResult> {
|
|
197
|
+
p.intro("Create a new Supatype project")
|
|
198
|
+
|
|
199
|
+
const projectName = ensureNotCancelled(
|
|
200
|
+
await p.text({
|
|
201
|
+
message: "Project name",
|
|
202
|
+
defaultValue: defaultName,
|
|
203
|
+
placeholder: defaultName,
|
|
204
|
+
}),
|
|
205
|
+
).trim() || defaultName
|
|
206
|
+
|
|
207
|
+
const packageManager = ensureNotCancelled(
|
|
208
|
+
await p.select<PackageManager>({
|
|
209
|
+
message: "Package manager",
|
|
210
|
+
initialValue: detectInvokingPackageManager(),
|
|
211
|
+
options: [
|
|
212
|
+
{ value: "npm", label: "npm" },
|
|
213
|
+
{ value: "pnpm", label: "pnpm" },
|
|
214
|
+
{ value: "yarn", label: "yarn" },
|
|
215
|
+
{ value: "bun", label: "bun" },
|
|
216
|
+
],
|
|
217
|
+
}),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
const productionTarget = ensureNotCancelled(
|
|
221
|
+
await p.select<ProductionTarget>({
|
|
222
|
+
message: "Where will this run in production?",
|
|
223
|
+
initialValue: defaultTarget,
|
|
224
|
+
options: [
|
|
225
|
+
{ value: "cloud", label: "Supatype Cloud", hint: "managed; deploy via supatype link" },
|
|
226
|
+
{ value: "self-host", label: "Self-host", hint: "your own server with TLS" },
|
|
227
|
+
{ value: "later", label: "Decide later", hint: "local development only for now" },
|
|
228
|
+
],
|
|
229
|
+
}),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
let domain: string | undefined
|
|
233
|
+
let tlsEmail: string | undefined
|
|
234
|
+
if (productionTarget === "self-host") {
|
|
235
|
+
domain = ensureNotCancelled(
|
|
236
|
+
await p.text({
|
|
237
|
+
message: "Production domain for ACME TLS (optional, can set later)",
|
|
238
|
+
placeholder: "api.example.com",
|
|
239
|
+
defaultValue: "",
|
|
240
|
+
}),
|
|
241
|
+
).trim()
|
|
242
|
+
if (domain) {
|
|
243
|
+
tlsEmail =
|
|
244
|
+
ensureNotCancelled(
|
|
245
|
+
await p.text({
|
|
246
|
+
message: "Email for Let's Encrypt (HTTPS) certificates",
|
|
247
|
+
placeholder: "you@example.com",
|
|
248
|
+
defaultValue: "",
|
|
249
|
+
}),
|
|
250
|
+
).trim() || undefined
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const provider = ensureNotCancelled(
|
|
255
|
+
await p.select<ScaffoldOptions["provider"]>({
|
|
256
|
+
message: "How should Postgres and the server run for local development?",
|
|
257
|
+
initialValue: "docker",
|
|
258
|
+
options: [
|
|
259
|
+
{ value: "docker", label: "Docker", hint: "Docker Compose stack (recommended)" },
|
|
260
|
+
{ value: "native", label: "Native", hint: "host Postgres + server binaries, no Docker" },
|
|
261
|
+
],
|
|
262
|
+
}),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
const schemaPath = ensureNotCancelled(
|
|
266
|
+
await p.text({
|
|
267
|
+
message: "Where should your schema live?",
|
|
268
|
+
defaultValue: "schema/index.ts",
|
|
269
|
+
placeholder: "schema/index.ts",
|
|
270
|
+
}),
|
|
271
|
+
).trim() || "schema/index.ts"
|
|
272
|
+
|
|
273
|
+
const app = await promptApp()
|
|
274
|
+
|
|
275
|
+
const email = ensureNotCancelled(
|
|
276
|
+
await p.select<ScaffoldOptions["email"]>({
|
|
277
|
+
message: "Email provider",
|
|
278
|
+
initialValue: "console",
|
|
279
|
+
options: [
|
|
280
|
+
{ value: "console", label: "console", hint: "log emails to the terminal (dev)" },
|
|
281
|
+
{ value: "smtp", label: "SMTP" },
|
|
282
|
+
{ value: "resend", label: "Resend" },
|
|
283
|
+
{ value: "ses", label: "Amazon SES" },
|
|
284
|
+
],
|
|
285
|
+
}),
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
const storageLocal = ensureNotCancelled(
|
|
289
|
+
await p.select<StorageProvider>({
|
|
290
|
+
message: "Local storage (for development)?",
|
|
291
|
+
initialValue: "local",
|
|
292
|
+
options: STORAGE_PROVIDER_OPTIONS,
|
|
293
|
+
}),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
const storageProduction = ensureNotCancelled(
|
|
297
|
+
await p.select<StorageProvider>({
|
|
298
|
+
message: "Production storage?",
|
|
299
|
+
initialValue: "local",
|
|
300
|
+
options: STORAGE_PROVIDER_OPTIONS,
|
|
301
|
+
}),
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
const helloFunction = ensureNotCancelled(
|
|
305
|
+
await p.confirm({
|
|
306
|
+
message: "Create a hello-world edge function?",
|
|
307
|
+
initialValue: false,
|
|
308
|
+
}),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
const install = ensureNotCancelled(
|
|
312
|
+
await p.confirm({
|
|
313
|
+
message: `Install dependencies with ${packageManager} now?`,
|
|
314
|
+
initialValue: true,
|
|
315
|
+
}),
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
const generateKeys = ensureNotCancelled(
|
|
319
|
+
await p.confirm({
|
|
320
|
+
message: "Generate ANON_KEY and SERVICE_ROLE_KEY now?",
|
|
321
|
+
initialValue: true,
|
|
322
|
+
}),
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
p.outro("Setting up your project...")
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
projectName,
|
|
329
|
+
provider,
|
|
330
|
+
productionTarget,
|
|
331
|
+
...(domain !== undefined ? { domain } : {}),
|
|
332
|
+
...(tlsEmail !== undefined ? { tlsEmail } : {}),
|
|
333
|
+
schemaPath,
|
|
334
|
+
app,
|
|
335
|
+
email,
|
|
336
|
+
storageLocal,
|
|
337
|
+
storageProduction,
|
|
338
|
+
helloFunction,
|
|
339
|
+
packageManager,
|
|
340
|
+
install,
|
|
341
|
+
generateKeys,
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function promptApp(): Promise<ScaffoldAppOptions> {
|
|
346
|
+
const mode = ensureNotCancelled(
|
|
347
|
+
await p.select<ScaffoldAppOptions["mode"]>({
|
|
348
|
+
message: "Host a frontend app at /?",
|
|
349
|
+
initialValue: "none",
|
|
350
|
+
options: [
|
|
351
|
+
{ value: "none", label: "No app", hint: "API only" },
|
|
352
|
+
{ value: "static", label: "Static site", hint: "serve a built directory" },
|
|
353
|
+
{ value: "proxy", label: "Local dev server", hint: "forward requests to a dev server you run" },
|
|
354
|
+
],
|
|
355
|
+
}),
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
if (mode === "static") {
|
|
359
|
+
const staticDir = ensureNotCancelled(
|
|
360
|
+
await p.text({
|
|
361
|
+
message: "Directory to serve",
|
|
362
|
+
defaultValue: "./public",
|
|
363
|
+
placeholder: "./public",
|
|
364
|
+
}),
|
|
365
|
+
).trim() || "./public"
|
|
366
|
+
const viteDevUrl = await promptViteDevUrl()
|
|
367
|
+
return { mode, staticDir, ...(viteDevUrl ? { viteDevUrl } : {}) }
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (mode === "proxy") {
|
|
371
|
+
const upstream = ensureNotCancelled(
|
|
372
|
+
await p.text({
|
|
373
|
+
message: "URL of your running dev server",
|
|
374
|
+
defaultValue: "http://localhost:3000",
|
|
375
|
+
placeholder: "http://localhost:3000",
|
|
376
|
+
}),
|
|
377
|
+
).trim() || "http://localhost:3000"
|
|
378
|
+
const start = ensureNotCancelled(
|
|
379
|
+
await p.text({
|
|
380
|
+
message: "package.json script that starts your dev server",
|
|
381
|
+
defaultValue: "dev",
|
|
382
|
+
placeholder: "dev",
|
|
383
|
+
}),
|
|
384
|
+
).trim() || "dev"
|
|
385
|
+
const viteDevUrl = await promptViteDevUrl()
|
|
386
|
+
return { mode, upstream, start, ...(viteDevUrl ? { viteDevUrl } : {}) }
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return { mode: "none" }
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function promptViteDevUrl(): Promise<string | undefined> {
|
|
393
|
+
const useVite = ensureNotCancelled(
|
|
394
|
+
await p.confirm({
|
|
395
|
+
message: "Enable live reload from a separate Vite dev server?",
|
|
396
|
+
initialValue: false,
|
|
397
|
+
}),
|
|
398
|
+
)
|
|
399
|
+
if (!useVite) return undefined
|
|
400
|
+
return (
|
|
401
|
+
ensureNotCancelled(
|
|
402
|
+
await p.text({
|
|
403
|
+
message: "Vite dev server URL",
|
|
404
|
+
defaultValue: "http://127.0.0.1:5173",
|
|
405
|
+
placeholder: "http://127.0.0.1:5173",
|
|
406
|
+
}),
|
|
407
|
+
).trim() || "http://127.0.0.1:5173"
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ─── Package manager ───────────────────────────────────────────────────────--
|
|
412
|
+
|
|
413
|
+
function detectInvokingPackageManager(): PackageManager {
|
|
414
|
+
const ua = process.env["npm_config_user_agent"] ?? ""
|
|
415
|
+
if (ua.startsWith("pnpm")) return "pnpm"
|
|
416
|
+
if (ua.startsWith("yarn")) return "yarn"
|
|
417
|
+
if (ua.startsWith("bun")) return "bun"
|
|
418
|
+
return "npm"
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function runInstall(dir: string, pm: PackageManager): void {
|
|
422
|
+
info(`Installing dependencies with ${pm}...`)
|
|
423
|
+
const res = spawnSync(pm, ["install"], {
|
|
424
|
+
cwd: dir,
|
|
425
|
+
stdio: "inherit",
|
|
426
|
+
shell: process.platform === "win32",
|
|
427
|
+
})
|
|
428
|
+
if (res.status !== 0 || res.error) {
|
|
429
|
+
warn(`Dependency install did not complete (run "${pm} install" manually).`)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function writeKeys(dir: string): boolean {
|
|
434
|
+
const keys = generateAndWriteKeys(dir)
|
|
435
|
+
if (!keys) {
|
|
436
|
+
warn("Could not generate keys (JWT_SECRET missing). Run `supatype keys` manually.")
|
|
437
|
+
return false
|
|
438
|
+
}
|
|
439
|
+
return true
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ─── Scaffold ──────────────────────────────────────────────────────────────--
|
|
443
|
+
|
|
444
|
+
function scaffold(dir: string, optsOrName: ScaffoldOptions | string): void {
|
|
445
|
+
const opts =
|
|
446
|
+
typeof optsOrName === "string" ? defaultScaffoldOptions(optsOrName) : optsOrName
|
|
80
447
|
const write = (rel: string, content: string) => {
|
|
81
448
|
const full = join(dir, rel)
|
|
82
449
|
mkdirSync(resolve(full, ".."), { recursive: true })
|
|
83
450
|
writeFileSync(full, content, "utf8")
|
|
84
|
-
|
|
451
|
+
file("created", rel)
|
|
85
452
|
}
|
|
86
453
|
|
|
87
454
|
const pkgPath = join(dir, "package.json")
|
|
88
455
|
if (!existsSync(pkgPath)) {
|
|
89
|
-
write("package.json", packageJsonTemplate(
|
|
456
|
+
write("package.json", packageJsonTemplate(opts, cliPackageVersion()))
|
|
90
457
|
} else {
|
|
91
|
-
|
|
458
|
+
file("skipped", "package.json (already exists)")
|
|
92
459
|
}
|
|
93
460
|
|
|
94
|
-
write("supatype.config.ts", tsConfigTemplate(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
461
|
+
write("supatype.config.ts", tsConfigTemplate(opts))
|
|
462
|
+
if (opts.productionTarget !== "later") {
|
|
463
|
+
write("supatype.local.config.ts", localConfigTemplate(opts.app))
|
|
464
|
+
}
|
|
465
|
+
write(opts.schemaPath, schemaTemplate())
|
|
466
|
+
write(".env", envTemplate(opts))
|
|
467
|
+
write("seed.ts", seedTemplate(opts.projectName))
|
|
98
468
|
write("seeds/.gitkeep", "")
|
|
469
|
+
scaffoldAppAssets(opts, write)
|
|
470
|
+
|
|
471
|
+
if (opts.helloFunction) scaffoldHelloFunction(dir, write)
|
|
472
|
+
|
|
473
|
+
const gitignorePath = join(dir, ".gitignore")
|
|
474
|
+
if (existsSync(gitignorePath)) {
|
|
475
|
+
const merged = mergeGitignoreTemplate(readFileSync(gitignorePath, "utf8"))
|
|
476
|
+
if (merged !== readFileSync(gitignorePath, "utf8")) {
|
|
477
|
+
writeFileSync(gitignorePath, merged, "utf8")
|
|
478
|
+
file("updated", ".gitignore (added .supatype/)")
|
|
479
|
+
} else {
|
|
480
|
+
file("skipped", ".gitignore (already exists)")
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
write(".gitignore", gitignoreTemplate())
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function staticDirRelative(staticDir?: string): string {
|
|
488
|
+
const raw = (staticDir ?? "./public").trim()
|
|
489
|
+
return raw.replace(/^\.\//, "").replace(/\/+$/, "") || "public"
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function scaffoldAppAssets(
|
|
493
|
+
opts: ScaffoldOptions,
|
|
494
|
+
write: (rel: string, content: string) => void,
|
|
495
|
+
): void {
|
|
496
|
+
const holding = holdingPageTemplate(opts.projectName)
|
|
497
|
+
const hasVite = Boolean(opts.app.viteDevUrl)
|
|
498
|
+
|
|
499
|
+
if (opts.app.mode === "static") {
|
|
500
|
+
const staticRel = staticDirRelative(opts.app.staticDir)
|
|
501
|
+
write(`${staticRel}/index.html`, holding)
|
|
502
|
+
if (hasVite) scaffoldVite(opts, write, holding)
|
|
503
|
+
return
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (opts.app.mode === "proxy" && opts.productionTarget !== "later") {
|
|
507
|
+
write("dist/index.html", holding)
|
|
508
|
+
if (hasVite) scaffoldVite(opts, write, holding)
|
|
509
|
+
return
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (opts.app.mode === "proxy" && hasVite) {
|
|
513
|
+
scaffoldVite(opts, write, holding)
|
|
514
|
+
return
|
|
515
|
+
}
|
|
516
|
+
|
|
99
517
|
write("public/.gitkeep", "")
|
|
100
|
-
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function scaffoldVite(
|
|
521
|
+
opts: ScaffoldOptions,
|
|
522
|
+
write: (rel: string, content: string) => void,
|
|
523
|
+
holding: string,
|
|
524
|
+
): void {
|
|
525
|
+
if (!opts.app.viteDevUrl) return
|
|
526
|
+
write("index.html", holding)
|
|
527
|
+
write("vite.config.ts", viteConfigTemplate(opts.app.viteDevUrl))
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function scaffoldHelloFunction(
|
|
531
|
+
dir: string,
|
|
532
|
+
write: (rel: string, content: string) => void,
|
|
533
|
+
): void {
|
|
534
|
+
write("functions/hello/index.ts", helloFunctionTemplate())
|
|
535
|
+
if (!existsSync(join(dir, "functions/_shared/README.md"))) {
|
|
536
|
+
write("functions/_shared/README.md", sharedFunctionsReadme())
|
|
537
|
+
}
|
|
538
|
+
if (!existsSync(join(dir, "functions/.env.local"))) {
|
|
539
|
+
write("functions/.env.local", functionsEnvLocalTemplate())
|
|
540
|
+
}
|
|
101
541
|
}
|
|
102
542
|
|
|
103
543
|
// ─── Templates ───────────────────────────────────────────────────────────────
|
|
104
544
|
|
|
105
|
-
function packageJsonTemplate(
|
|
545
|
+
function packageJsonTemplate(opts: ScaffoldOptions, cliVersion: string): string {
|
|
546
|
+
const scripts: string[] = [
|
|
547
|
+
` "dev": "supatype dev"`,
|
|
548
|
+
` "push": "supatype push"`,
|
|
549
|
+
` "seed": "tsx seed.ts"`,
|
|
550
|
+
]
|
|
551
|
+
if (opts.app.viteDevUrl) {
|
|
552
|
+
scripts.push(` "vite": "vite"`)
|
|
553
|
+
}
|
|
554
|
+
if (opts.helloFunction) {
|
|
555
|
+
scripts.push(` "functions": "supatype functions serve"`)
|
|
556
|
+
}
|
|
557
|
+
const devDeps = [` "tsx": "^4.19.2"`, ` "typescript": "^5"`]
|
|
558
|
+
if (opts.app.viteDevUrl) {
|
|
559
|
+
devDeps.push(` "vite": "^6"`)
|
|
560
|
+
}
|
|
106
561
|
return `{
|
|
107
|
-
"name": "${projectName}",
|
|
562
|
+
"name": "${opts.projectName}",
|
|
108
563
|
"private": true,
|
|
109
564
|
"type": "module",
|
|
110
565
|
"scripts": {
|
|
111
|
-
|
|
112
|
-
"push": "supatype push",
|
|
113
|
-
"seed": "tsx seed.ts"
|
|
566
|
+
${scripts.join(",\n")}
|
|
114
567
|
},
|
|
115
568
|
"dependencies": {
|
|
116
569
|
"@supatype/cli": "^${cliVersion}",
|
|
117
570
|
"@supatype/types": "^${cliVersion}"
|
|
118
571
|
},
|
|
119
572
|
"devDependencies": {
|
|
120
|
-
|
|
121
|
-
"typescript": "^5"
|
|
573
|
+
${devDeps.join(",\n")}
|
|
122
574
|
}
|
|
123
575
|
}
|
|
124
576
|
`
|
|
125
577
|
}
|
|
126
578
|
|
|
127
|
-
function tsConfigTemplate(
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
579
|
+
function tsConfigTemplate(opts: ScaffoldOptions): string {
|
|
580
|
+
const serverMode = serverModeForTarget(opts.productionTarget)
|
|
581
|
+
const hasLocalOverride = opts.productionTarget !== "later"
|
|
582
|
+
const lines: string[] = []
|
|
583
|
+
lines.push(`import { defineConfig } from "@supatype/cli"`)
|
|
584
|
+
lines.push("")
|
|
585
|
+
if (hasLocalOverride) {
|
|
586
|
+
lines.push(`// Committed config = ${opts.productionTarget} production target.`)
|
|
587
|
+
lines.push(`// Local development overrides live in supatype.local.config.ts (gitignored).`)
|
|
588
|
+
}
|
|
589
|
+
lines.push(`export default defineConfig({`)
|
|
590
|
+
lines.push(` project: { name: "${opts.projectName}" },`)
|
|
591
|
+
lines.push(` provider: "${opts.provider}",`)
|
|
592
|
+
if (opts.provider === "docker") {
|
|
593
|
+
lines.push(` // provider: "native" // host Postgres + supatype-server binaries (no Docker)`)
|
|
594
|
+
}
|
|
595
|
+
lines.push(` database: {`)
|
|
596
|
+
lines.push(` provider: "${opts.provider}",`)
|
|
597
|
+
lines.push(` },`)
|
|
598
|
+
lines.push(` server: {`)
|
|
599
|
+
lines.push(` mode: "${serverMode}",`)
|
|
600
|
+
lines.push(` port: 54321,`)
|
|
601
|
+
if (serverMode === "standalone") {
|
|
602
|
+
lines.push(` domain: "${opts.domain ?? ""}", // e.g. "api.example.com" for ACME TLS`)
|
|
603
|
+
if (opts.tlsEmail) {
|
|
604
|
+
lines.push(` tls: { email: "${opts.tlsEmail}" }, // automatic HTTPS via Let's Encrypt`)
|
|
605
|
+
} else {
|
|
606
|
+
lines.push(` // tls: { email: "you@example.com" }, // set to enable automatic HTTPS (Let's Encrypt)`)
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
lines.push(` },`)
|
|
610
|
+
lines.push(...appConfigLines(opts.app, opts.productionTarget))
|
|
611
|
+
if (opts.productionTarget !== "later") {
|
|
612
|
+
lines.push(` environments: { default: "production" }, // supatype link --env production ...`)
|
|
613
|
+
}
|
|
614
|
+
lines.push(
|
|
615
|
+
` // Optional: pin component versions (native cache + Docker images synced to .env on dev/push)`,
|
|
616
|
+
)
|
|
617
|
+
lines.push(` // versions: { engine: "0.1.2", server: "1.0.5", postgres: "17.2", deno: "2.2.0" },`)
|
|
618
|
+
lines.push(` email: { provider: "${opts.email}" },`)
|
|
619
|
+
lines.push(...storageConfigLines(opts.storageLocal, opts.storageProduction))
|
|
620
|
+
lines.push(` schema: { path: "${opts.schemaPath}", pg_schema: "public" },`)
|
|
621
|
+
lines.push(
|
|
622
|
+
` // Self-host production: supatype self-host compose (Docker only). Standalone + domain = native ACME dev.`,
|
|
623
|
+
)
|
|
624
|
+
lines.push(`})`)
|
|
625
|
+
return lines.join("\n") + "\n"
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function localConfigTemplate(app: ScaffoldAppOptions): string {
|
|
629
|
+
const lines: string[] = [
|
|
630
|
+
`import type { SupatypeConfig } from "@supatype/cli"`,
|
|
631
|
+
``,
|
|
632
|
+
`// Local development overrides — gitignored, deep-merged over supatype.config.ts.`,
|
|
633
|
+
`// Keeps \`supatype dev\` in local mode while the committed config targets production.`,
|
|
634
|
+
`const localConfig: Partial<SupatypeConfig> = {`,
|
|
635
|
+
` server: { mode: "dev" },`,
|
|
636
|
+
]
|
|
637
|
+
if (app.mode === "proxy") {
|
|
638
|
+
lines.push(` app: {`)
|
|
639
|
+
lines.push(` mode: "proxy",`)
|
|
640
|
+
lines.push(` upstream: "${app.upstream ?? "http://localhost:3000"}",`)
|
|
641
|
+
lines.push(` start: "${app.start ?? "dev"}",`)
|
|
642
|
+
if (app.viteDevUrl) lines.push(` vite_dev_url: "${app.viteDevUrl}",`)
|
|
643
|
+
lines.push(` },`)
|
|
644
|
+
}
|
|
645
|
+
lines.push(`}`, ``, `export default localConfig`, ``)
|
|
646
|
+
return lines.join("\n")
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function appConfigLines(app: ScaffoldAppOptions, productionTarget: ProductionTarget): string[] {
|
|
650
|
+
if (app.mode === "static") {
|
|
651
|
+
const out = [
|
|
652
|
+
` app: {`,
|
|
653
|
+
` mode: "static",`,
|
|
654
|
+
` static_dir: "${app.staticDir ?? "./public"}",`,
|
|
655
|
+
]
|
|
656
|
+
if (app.viteDevUrl) out.push(` vite_dev_url: "${app.viteDevUrl}",`)
|
|
657
|
+
out.push(` },`)
|
|
658
|
+
return out
|
|
659
|
+
}
|
|
660
|
+
if (app.mode === "proxy") {
|
|
661
|
+
if (productionTarget !== "later") {
|
|
662
|
+
return [
|
|
663
|
+
` app: {`,
|
|
664
|
+
` mode: "static",`,
|
|
665
|
+
` static_dir: "./dist", // production build output`,
|
|
666
|
+
` },`,
|
|
667
|
+
]
|
|
668
|
+
}
|
|
669
|
+
const out = [
|
|
670
|
+
` app: {`,
|
|
671
|
+
` mode: "proxy",`,
|
|
672
|
+
` upstream: "${app.upstream ?? "http://localhost:3000"}",`,
|
|
673
|
+
` start: "${app.start ?? "dev"}",`,
|
|
674
|
+
]
|
|
675
|
+
if (app.viteDevUrl) out.push(` vite_dev_url: "${app.viteDevUrl}",`)
|
|
676
|
+
out.push(` },`)
|
|
677
|
+
return out
|
|
678
|
+
}
|
|
679
|
+
return [
|
|
680
|
+
` app: {`,
|
|
681
|
+
` mode: "none",`,
|
|
682
|
+
` // mode: "static", static_dir: "./public", // supatype app add --static ./public`,
|
|
683
|
+
` // mode: "proxy", upstream: "http://localhost:3000", start: "dev",`,
|
|
684
|
+
` // vite_dev_url: "http://127.0.0.1:5173", // live reload from a separate Vite dev server`,
|
|
685
|
+
` },`,
|
|
686
|
+
]
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const HOLDING_PAGE_LOGO_URL = "https://supatype.github.io/supatype/supatype.svg"
|
|
690
|
+
const HOLDING_PAGE_DOCS_URL = "https://supatype.github.io/supatype/"
|
|
691
|
+
const HOLDING_PAGE_GITHUB_URL = "https://github.com/supatype"
|
|
692
|
+
const HOLDING_PAGE_DISCORD_URL = "https://discord.gg/yaQrjQD4"
|
|
693
|
+
|
|
694
|
+
function escapeHtml(text: string): string {
|
|
695
|
+
return text
|
|
696
|
+
.replace(/&/g, "&")
|
|
697
|
+
.replace(/</g, "<")
|
|
698
|
+
.replace(/>/g, ">")
|
|
699
|
+
.replace(/"/g, """)
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function holdingPageTemplate(projectName: string): string {
|
|
703
|
+
const name = escapeHtml(projectName)
|
|
704
|
+
return `<!doctype html>
|
|
705
|
+
<html lang="en">
|
|
706
|
+
<head>
|
|
707
|
+
<meta charset="UTF-8" />
|
|
708
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
709
|
+
<title>${name} — Supatype</title>
|
|
710
|
+
<meta name="description" content="A Supatype project. Define your types — we generate your backend." />
|
|
711
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
712
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
713
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
714
|
+
<style>
|
|
715
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
716
|
+
:root {
|
|
717
|
+
--bg: #0a0a0f;
|
|
718
|
+
--border: rgba(255, 255, 255, 0.08);
|
|
719
|
+
--text: #e8e8f0;
|
|
720
|
+
--text-muted: #8888a8;
|
|
721
|
+
--purple: #7c3aed;
|
|
722
|
+
--purple-light: #a855f7;
|
|
723
|
+
}
|
|
724
|
+
body {
|
|
725
|
+
min-height: 100vh;
|
|
726
|
+
font-family: Inter, system-ui, sans-serif;
|
|
727
|
+
background: var(--bg);
|
|
728
|
+
color: var(--text);
|
|
729
|
+
line-height: 1.6;
|
|
730
|
+
display: flex;
|
|
731
|
+
align-items: center;
|
|
732
|
+
justify-content: center;
|
|
733
|
+
padding: 2rem;
|
|
734
|
+
}
|
|
735
|
+
body::before {
|
|
736
|
+
content: "";
|
|
737
|
+
position: fixed;
|
|
738
|
+
inset: 0;
|
|
739
|
+
background: radial-gradient(ellipse 80% 50% at 50% -20%, rgba(124, 58, 237, 0.18), transparent);
|
|
740
|
+
pointer-events: none;
|
|
741
|
+
}
|
|
742
|
+
main {
|
|
743
|
+
position: relative;
|
|
744
|
+
max-width: 32rem;
|
|
745
|
+
width: 100%;
|
|
746
|
+
text-align: center;
|
|
747
|
+
}
|
|
748
|
+
.logo {
|
|
749
|
+
display: block;
|
|
750
|
+
height: 2rem;
|
|
751
|
+
width: auto;
|
|
752
|
+
margin: 0 auto 2rem;
|
|
753
|
+
}
|
|
754
|
+
h1 {
|
|
755
|
+
font-size: 1.5rem;
|
|
756
|
+
font-weight: 700;
|
|
757
|
+
letter-spacing: -0.02em;
|
|
758
|
+
margin-bottom: 0.75rem;
|
|
759
|
+
}
|
|
760
|
+
.tagline {
|
|
761
|
+
color: var(--text-muted);
|
|
762
|
+
font-size: 1rem;
|
|
763
|
+
margin-bottom: 2rem;
|
|
764
|
+
}
|
|
765
|
+
.links {
|
|
766
|
+
display: flex;
|
|
767
|
+
flex-wrap: wrap;
|
|
768
|
+
gap: 0.75rem;
|
|
769
|
+
justify-content: center;
|
|
770
|
+
margin-bottom: 2.5rem;
|
|
771
|
+
}
|
|
772
|
+
.links a {
|
|
773
|
+
display: inline-flex;
|
|
774
|
+
align-items: center;
|
|
775
|
+
padding: 0.6rem 1.1rem;
|
|
776
|
+
border-radius: 10px;
|
|
777
|
+
border: 1px solid var(--border);
|
|
778
|
+
color: var(--text);
|
|
779
|
+
text-decoration: none;
|
|
780
|
+
font-size: 0.9rem;
|
|
781
|
+
font-weight: 500;
|
|
782
|
+
transition: border-color 0.15s, background 0.15s;
|
|
783
|
+
}
|
|
784
|
+
.links a:hover {
|
|
785
|
+
border-color: rgba(168, 85, 247, 0.45);
|
|
786
|
+
background: rgba(124, 58, 237, 0.08);
|
|
787
|
+
}
|
|
788
|
+
.links a.primary {
|
|
789
|
+
background: linear-gradient(135deg, var(--purple), var(--purple-light));
|
|
790
|
+
border-color: transparent;
|
|
791
|
+
color: #fff;
|
|
792
|
+
}
|
|
793
|
+
.links a.primary:hover {
|
|
794
|
+
opacity: 0.92;
|
|
795
|
+
}
|
|
796
|
+
.hint {
|
|
797
|
+
font-size: 0.85rem;
|
|
798
|
+
color: var(--text-muted);
|
|
799
|
+
}
|
|
800
|
+
.hint code {
|
|
801
|
+
font-family: ui-monospace, "JetBrains Mono", monospace;
|
|
802
|
+
font-size: 0.8rem;
|
|
803
|
+
background: rgba(255, 255, 255, 0.06);
|
|
804
|
+
padding: 0.15rem 0.4rem;
|
|
805
|
+
border-radius: 4px;
|
|
806
|
+
}
|
|
807
|
+
</style>
|
|
808
|
+
</head>
|
|
809
|
+
<body>
|
|
810
|
+
<main>
|
|
811
|
+
<img class="logo" src="${HOLDING_PAGE_LOGO_URL}" alt="Supatype" width="160" height="30" />
|
|
812
|
+
<h1>${name}</h1>
|
|
813
|
+
<p class="tagline">Your Supatype project is ready. Replace this page when you build your app.</p>
|
|
814
|
+
<nav class="links" aria-label="Supatype resources">
|
|
815
|
+
<a class="primary" href="${HOLDING_PAGE_DOCS_URL}" target="_blank" rel="noopener noreferrer">Documentation</a>
|
|
816
|
+
<a href="${HOLDING_PAGE_GITHUB_URL}" target="_blank" rel="noopener noreferrer">GitHub</a>
|
|
817
|
+
<a href="${HOLDING_PAGE_DISCORD_URL}" target="_blank" rel="noopener noreferrer">Discord</a>
|
|
818
|
+
</nav>
|
|
819
|
+
<p class="hint">Run <code>supatype dev</code> then open <code>http://localhost:18473/</code></p>
|
|
820
|
+
</main>
|
|
821
|
+
</body>
|
|
822
|
+
</html>
|
|
823
|
+
`
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function vitePortFromDevUrl(viteDevUrl: string): number {
|
|
827
|
+
try {
|
|
828
|
+
const url = new URL(viteDevUrl)
|
|
829
|
+
if (url.port) return Number.parseInt(url.port, 10)
|
|
830
|
+
return url.protocol === "https:" ? 443 : 80
|
|
831
|
+
} catch {
|
|
832
|
+
return 5173
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function viteConfigTemplate(viteDevUrl: string): string {
|
|
837
|
+
const port = vitePortFromDevUrl(viteDevUrl)
|
|
838
|
+
return `import { defineConfig } from "vite"
|
|
134
839
|
|
|
135
840
|
export default defineConfig({
|
|
136
|
-
project: { name: "${projectName}" },
|
|
137
|
-
provider: "native",
|
|
138
|
-
// provider: "docker" // full self-host Compose stack (Kong :18473)
|
|
139
|
-
database: {
|
|
140
|
-
provider: "native",
|
|
141
|
-
},
|
|
142
841
|
server: {
|
|
143
|
-
|
|
144
|
-
port:
|
|
145
|
-
|
|
146
|
-
app: {
|
|
147
|
-
mode: "none",
|
|
148
|
-
// mode: "static", static_dir: "./public", // supatype app add --static ./public
|
|
149
|
-
// mode: "proxy", upstream: "http://localhost:3000", start: "dev",
|
|
150
|
-
// vite_dev_url: "http://127.0.0.1:5173", // dev HMR at /_vite (when using a separate Vite server)
|
|
151
|
-
},
|
|
152
|
-
versions: {
|
|
153
|
-
engine: "${v("engine", "latest")}",
|
|
154
|
-
server: "${v("server", "latest")}",
|
|
155
|
-
postgres: "${v("postgres", "latest")}",
|
|
156
|
-
deno: "${v("deno", "latest")}",
|
|
842
|
+
host: "127.0.0.1",
|
|
843
|
+
port: ${port},
|
|
844
|
+
strictPort: true,
|
|
157
845
|
},
|
|
158
|
-
email: { provider: "console" },
|
|
159
|
-
storage: { provider: "local", local_path: ".supatype/storage" },
|
|
160
|
-
schema: { path: "schema/index.ts", pg_schema: "public" },
|
|
161
|
-
// Self-host production: supatype self-host compose (Docker only). Standalone + domain = native ACME dev.
|
|
162
846
|
})
|
|
163
847
|
`
|
|
164
848
|
}
|
|
165
849
|
|
|
850
|
+
function storageConfigLines(
|
|
851
|
+
storageLocal: StorageProvider,
|
|
852
|
+
storageProduction: StorageProvider,
|
|
853
|
+
): string[] {
|
|
854
|
+
const lines: string[] = []
|
|
855
|
+
if (storageLocal === "s3") {
|
|
856
|
+
lines.push(` storage: { provider: "s3" }, // dev — configure S3_* in .env`)
|
|
857
|
+
} else {
|
|
858
|
+
lines.push(` storage: { provider: "local", local_path: ".supatype/storage" },`)
|
|
859
|
+
}
|
|
860
|
+
if (storageProduction === "s3" && storageLocal !== "s3") {
|
|
861
|
+
lines.push(` // Production storage: external S3 bucket — set production S3_* in .env`)
|
|
862
|
+
} else if (storageProduction === "local" && storageLocal === "s3") {
|
|
863
|
+
lines.push(` // Production storage: MinIO on your server (included in self-host compose)`)
|
|
864
|
+
}
|
|
865
|
+
return lines
|
|
866
|
+
}
|
|
867
|
+
|
|
166
868
|
function schemaTemplate(): string {
|
|
167
|
-
return `import type { Model,
|
|
869
|
+
return `import type { Model, LoggedIn, Owner, Public, Role, SupatypeAuthUserId, UUID } from "@supatype/types"
|
|
168
870
|
|
|
169
|
-
|
|
871
|
+
/** App profile for a signed-in user. \`id\` matches the Supatype auth user id. */
|
|
872
|
+
export type Profile = Model<{
|
|
170
873
|
id: SupatypeAuthUserId
|
|
171
|
-
|
|
172
|
-
name: string
|
|
173
|
-
created_at: string
|
|
174
|
-
updated_at: string
|
|
874
|
+
display_name: string
|
|
175
875
|
}, {
|
|
176
876
|
access: {
|
|
177
|
-
read:
|
|
178
|
-
create:
|
|
877
|
+
read: LoggedIn
|
|
878
|
+
create: Owner<"id">
|
|
179
879
|
update: Owner<"id">
|
|
180
|
-
delete:
|
|
880
|
+
delete: Owner<"id">
|
|
181
881
|
}
|
|
182
882
|
}>
|
|
183
883
|
|
|
@@ -195,34 +895,112 @@ export type SiteSettings = Model<{
|
|
|
195
895
|
`
|
|
196
896
|
}
|
|
197
897
|
|
|
198
|
-
function envTemplate(
|
|
199
|
-
|
|
898
|
+
function envTemplate(opts: ScaffoldOptions): string {
|
|
899
|
+
const sections: string[] = []
|
|
900
|
+
sections.push(`DATABASE_URL=postgresql://supatype_admin:postgres@localhost:5432/${opts.projectName}
|
|
200
901
|
POSTGRES_USER=supatype_admin
|
|
201
902
|
POSTGRES_PASSWORD=postgres
|
|
202
|
-
POSTGRES_DB=${projectName}
|
|
903
|
+
POSTGRES_DB=${opts.projectName}`)
|
|
203
904
|
|
|
204
|
-
|
|
905
|
+
sections.push(`# JWT — run \`supatype keys\` to generate ANON_KEY and SERVICE_ROLE_KEY
|
|
205
906
|
JWT_SECRET=super-secret-jwt-token-change-in-production
|
|
206
907
|
ANON_KEY=
|
|
207
|
-
SERVICE_ROLE_KEY
|
|
908
|
+
SERVICE_ROLE_KEY=`)
|
|
208
909
|
|
|
209
|
-
|
|
210
|
-
SITE_URL=http://localhost:3000
|
|
910
|
+
sections.push(`# Site URL (used by GoTrue for email redirects)
|
|
911
|
+
SITE_URL=http://localhost:3000`)
|
|
211
912
|
|
|
212
|
-
|
|
913
|
+
sections.push(emailEnvSection(opts.email, opts.projectName))
|
|
914
|
+
sections.push(storageEnvSections(opts.storageLocal, opts.storageProduction))
|
|
915
|
+
|
|
916
|
+
sections.push(
|
|
917
|
+
`# Self-host compose uses the same DATABASE_URL when Postgres is published on localhost:5432`,
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
return sections.join("\n\n") + "\n"
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function emailEnvSection(email: ScaffoldOptions["email"], projectName: string): string {
|
|
924
|
+
switch (email) {
|
|
925
|
+
case "resend":
|
|
926
|
+
return `# Email (Resend)
|
|
927
|
+
RESEND_API_KEY=
|
|
928
|
+
RESEND_FROM=onboarding@resend.dev`
|
|
929
|
+
case "ses":
|
|
930
|
+
return `# Email (Amazon SES)
|
|
931
|
+
SES_FROM=
|
|
932
|
+
AWS_REGION=us-east-1
|
|
933
|
+
AWS_ACCESS_KEY_ID=
|
|
934
|
+
AWS_SECRET_ACCESS_KEY=`
|
|
935
|
+
case "smtp":
|
|
936
|
+
return `# Email (SMTP)
|
|
937
|
+
SMTP_HOST=
|
|
938
|
+
SMTP_PORT=587
|
|
939
|
+
SMTP_USER=
|
|
940
|
+
SMTP_PASS=
|
|
941
|
+
SMTP_SENDER_NAME=${projectName}`
|
|
942
|
+
case "console":
|
|
943
|
+
default:
|
|
944
|
+
return `# SMTP — leave empty to use email autoconfirm in dev (no emails sent)
|
|
213
945
|
SMTP_HOST=
|
|
214
946
|
SMTP_PORT=
|
|
215
947
|
SMTP_USER=
|
|
216
948
|
SMTP_PASS=
|
|
217
|
-
SMTP_SENDER_NAME=${projectName}
|
|
949
|
+
SMTP_SENDER_NAME=${projectName}`
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function storageEnvSections(
|
|
954
|
+
storageLocal: StorageProvider,
|
|
955
|
+
storageProduction: StorageProvider,
|
|
956
|
+
): string {
|
|
957
|
+
if (storageLocal === storageProduction) {
|
|
958
|
+
if (storageLocal === "s3") {
|
|
959
|
+
return `# Storage (local development and production — external bucket)
|
|
960
|
+
# Use separate buckets for dev and production in your provider.
|
|
961
|
+
S3_ENDPOINT=
|
|
962
|
+
S3_REGION=us-east-1
|
|
963
|
+
S3_BUCKET=
|
|
964
|
+
S3_ACCESS_KEY=
|
|
965
|
+
S3_SECRET_KEY=`
|
|
966
|
+
}
|
|
967
|
+
return `${localStorageEnvSection("local")}
|
|
968
|
+
|
|
969
|
+
# Production storage (MinIO on your server)
|
|
970
|
+
# Included in the self-host compose stack — no extra configuration needed.`
|
|
971
|
+
}
|
|
218
972
|
|
|
219
|
-
|
|
973
|
+
return [localStorageEnvSection(storageLocal), productionStorageEnvSection(storageProduction)].join(
|
|
974
|
+
"\n\n",
|
|
975
|
+
)
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function localStorageEnvSection(storage: StorageProvider): string {
|
|
979
|
+
if (storage === "s3") {
|
|
980
|
+
return `# Storage (local development — external bucket)
|
|
981
|
+
S3_ENDPOINT=
|
|
982
|
+
S3_REGION=us-east-1
|
|
983
|
+
S3_BUCKET=
|
|
984
|
+
S3_ACCESS_KEY=
|
|
985
|
+
S3_SECRET_KEY=`
|
|
986
|
+
}
|
|
987
|
+
return `# Storage (local development — MinIO)
|
|
220
988
|
S3_ENDPOINT=http://localhost:9000
|
|
221
989
|
S3_ACCESS_KEY=supatype
|
|
222
|
-
S3_SECRET_KEY=supatype-secret
|
|
990
|
+
S3_SECRET_KEY=supatype-secret`
|
|
991
|
+
}
|
|
223
992
|
|
|
224
|
-
|
|
225
|
-
|
|
993
|
+
function productionStorageEnvSection(storage: StorageProvider): string {
|
|
994
|
+
if (storage === "s3") {
|
|
995
|
+
return `# Storage (production — external bucket)
|
|
996
|
+
S3_ENDPOINT=
|
|
997
|
+
S3_REGION=us-east-1
|
|
998
|
+
S3_BUCKET=
|
|
999
|
+
S3_ACCESS_KEY=
|
|
1000
|
+
S3_SECRET_KEY=`
|
|
1001
|
+
}
|
|
1002
|
+
return `# Storage (production — MinIO on your server)
|
|
1003
|
+
# Included in the self-host compose stack — no extra configuration needed.`
|
|
226
1004
|
}
|
|
227
1005
|
|
|
228
1006
|
function seedTemplate(projectName: string): string {
|
|
@@ -238,7 +1016,7 @@ async function seed() {
|
|
|
238
1016
|
console.log("Seeding ${projectName}...")
|
|
239
1017
|
|
|
240
1018
|
// TODO: insert seed data
|
|
241
|
-
// await db\`INSERT INTO
|
|
1019
|
+
// await db\`INSERT INTO profile (id, display_name) VALUES ('...', 'Admin')\`
|
|
242
1020
|
|
|
243
1021
|
await db.end()
|
|
244
1022
|
console.log("Done.")
|
|
@@ -251,17 +1029,130 @@ seed().catch((e) => {
|
|
|
251
1029
|
`
|
|
252
1030
|
}
|
|
253
1031
|
|
|
1032
|
+
function helloFunctionTemplate(): string {
|
|
1033
|
+
return `// hello — Supatype Edge Function
|
|
1034
|
+
// Docs: https://supatype.com/docs/edge-functions
|
|
1035
|
+
|
|
1036
|
+
export default async function handler(req: Request): Promise<Response> {
|
|
1037
|
+
const { method } = req
|
|
1038
|
+
|
|
1039
|
+
if (method === "POST") {
|
|
1040
|
+
const body = await req.json()
|
|
1041
|
+
return new Response(JSON.stringify({ message: "Hello from hello!", received: body }), {
|
|
1042
|
+
status: 200,
|
|
1043
|
+
headers: { "Content-Type": "application/json" },
|
|
1044
|
+
})
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
return new Response(JSON.stringify({ message: "Hello from hello!" }), {
|
|
1048
|
+
status: 200,
|
|
1049
|
+
headers: { "Content-Type": "application/json" },
|
|
1050
|
+
})
|
|
1051
|
+
}
|
|
1052
|
+
`
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function sharedFunctionsReadme(): string {
|
|
1056
|
+
return "# Shared Code\n\nFiles in `_shared/` are available to all functions via relative imports.\nThis directory is not deployed as a function.\n\nExample: `import { sendEmail } from '../_shared/email.ts'`\n"
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function functionsEnvLocalTemplate(): string {
|
|
1060
|
+
return "# Local environment variables for edge functions\n# These are NOT committed to git\n# Set production env vars via: npx supatype functions env set KEY=value\n"
|
|
1061
|
+
}
|
|
1062
|
+
|
|
254
1063
|
function gitignoreTemplate(): string {
|
|
255
1064
|
return `.env
|
|
256
1065
|
node_modules/
|
|
257
1066
|
dist/
|
|
258
|
-
.supatype/
|
|
259
|
-
# Local overrides — never commit
|
|
1067
|
+
.supatype/
|
|
260
1068
|
supatype.local.config.ts
|
|
261
1069
|
supatype.local.config.js
|
|
262
1070
|
supatype.local.config.mjs
|
|
263
|
-
# Generated by supatype push
|
|
1071
|
+
# Generated by supatype push (legacy paths — prefer output.types in config)
|
|
264
1072
|
src/types/supatype.d.ts
|
|
265
1073
|
src/lib/supatype.ts
|
|
266
1074
|
`
|
|
267
1075
|
}
|
|
1076
|
+
|
|
1077
|
+
export function mergeGitignoreTemplate(existingContent: string): string {
|
|
1078
|
+
if (existingContent.includes(".supatype/") || existingContent.includes(".supatype\n")) {
|
|
1079
|
+
return existingContent
|
|
1080
|
+
}
|
|
1081
|
+
const block = `
|
|
1082
|
+
# Supatype — local runtime (contains secrets in link.json)
|
|
1083
|
+
.supatype/
|
|
1084
|
+
`
|
|
1085
|
+
return existingContent.endsWith("\n") ? `${existingContent}${block}` : `${existingContent}\n${block}`
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// ─── Next steps ────────────────────────────────────────────────────────────--
|
|
1089
|
+
|
|
1090
|
+
function warnDockerUnavailableForProvider(provider: ScaffoldOptions["provider"]): void {
|
|
1091
|
+
if (provider !== "docker") return
|
|
1092
|
+
const probe = probeDockerDaemon()
|
|
1093
|
+
if (probe.ok) return
|
|
1094
|
+
reportDockerUnavailable(probe)
|
|
1095
|
+
plain()
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function printNextSteps(args: {
|
|
1099
|
+
name: string | undefined
|
|
1100
|
+
result: WizardResult
|
|
1101
|
+
installed: boolean
|
|
1102
|
+
keysGenerated: boolean
|
|
1103
|
+
}): void {
|
|
1104
|
+
const { name, result, installed, keysGenerated } = args
|
|
1105
|
+
const steps: string[] = []
|
|
1106
|
+
if (name) steps.push(`cd ${name}`)
|
|
1107
|
+
if (!installed) steps.push(`${result.packageManager} install`)
|
|
1108
|
+
if (!keysGenerated) steps.push("supatype keys")
|
|
1109
|
+
steps.push("supatype dev # Docker Compose stack (Kong :18473)")
|
|
1110
|
+
steps.push("supatype push # apply schema + generate types")
|
|
1111
|
+
if (result.helloFunction) {
|
|
1112
|
+
steps.push("supatype functions serve # run edge functions locally")
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
info(`Supatype project ready${name ? ` in ${name}/` : ""}.`)
|
|
1116
|
+
nextSteps("Next steps:", steps)
|
|
1117
|
+
|
|
1118
|
+
if (result.app.mode === "none") {
|
|
1119
|
+
nextSteps("Static frontend (self-host):", [
|
|
1120
|
+
"supatype app add --static ./public",
|
|
1121
|
+
"npm run build # write files into public/",
|
|
1122
|
+
"supatype self-host compose up -d",
|
|
1123
|
+
])
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
if (result.productionTarget === "cloud") {
|
|
1127
|
+
nextSteps("Deploy to Supatype Cloud:", [
|
|
1128
|
+
"supatype login",
|
|
1129
|
+
"supatype link --env production --project <ref>",
|
|
1130
|
+
"supatype push --env production",
|
|
1131
|
+
])
|
|
1132
|
+
info("supatype.local.config.ts keeps `supatype dev` local while the committed config targets cloud.")
|
|
1133
|
+
} else if (result.productionTarget === "self-host") {
|
|
1134
|
+
const selfHostSteps: string[] = []
|
|
1135
|
+
const domain = result.domain?.trim()
|
|
1136
|
+
if (domain) {
|
|
1137
|
+
selfHostSteps.push(`1. Point DNS: an A record for ${domain} -> your server's public IP`)
|
|
1138
|
+
selfHostSteps.push("2. Open ports 80 and 443 on the server firewall")
|
|
1139
|
+
if (!result.tlsEmail) {
|
|
1140
|
+
selfHostSteps.push("3. Set server.tls.email in supatype.config.ts (required for HTTPS)")
|
|
1141
|
+
}
|
|
1142
|
+
selfHostSteps.push("supatype self-host compose up -d # Kong provisions HTTPS automatically")
|
|
1143
|
+
selfHostSteps.push(`Your Supatype platform goes live at https://${domain}`)
|
|
1144
|
+
selfHostSteps.push(
|
|
1145
|
+
"Your app, REST, Auth, Storage, Realtime, Functions, and Studio — all behind one HTTPS domain (certs persist in valkey-data)",
|
|
1146
|
+
)
|
|
1147
|
+
} else {
|
|
1148
|
+
selfHostSteps.push(
|
|
1149
|
+
"Set server.domain + server.tls.email in supatype.config.ts to enable automatic HTTPS",
|
|
1150
|
+
)
|
|
1151
|
+
selfHostSteps.push("supatype self-host compose up -d # Docker stack")
|
|
1152
|
+
}
|
|
1153
|
+
selfHostSteps.push("supatype link --env production ... # then: supatype push --env production")
|
|
1154
|
+
nextSteps("Self-host production (your own server):", selfHostSteps)
|
|
1155
|
+
info("supatype.local.config.ts keeps `supatype dev` local while the committed config targets self-host.")
|
|
1156
|
+
}
|
|
1157
|
+
plain()
|
|
1158
|
+
}
|