@take-out/cli 0.0.39
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/LICENSE +21 -0
- package/README.md +274 -0
- package/cli.mjs +3 -0
- package/dist/cjs/cli.cjs +71 -0
- package/dist/cjs/cli.js +70 -0
- package/dist/cjs/cli.js.map +6 -0
- package/dist/cjs/cli.native.js +79 -0
- package/dist/cjs/cli.native.js.map +6 -0
- package/dist/cjs/commands/changed.cjs +212 -0
- package/dist/cjs/commands/changed.js +214 -0
- package/dist/cjs/commands/changed.js.map +6 -0
- package/dist/cjs/commands/changed.native.js +289 -0
- package/dist/cjs/commands/changed.native.js.map +6 -0
- package/dist/cjs/commands/docs.cjs +388 -0
- package/dist/cjs/commands/docs.js +313 -0
- package/dist/cjs/commands/docs.js.map +6 -0
- package/dist/cjs/commands/docs.native.js +476 -0
- package/dist/cjs/commands/docs.native.js.map +6 -0
- package/dist/cjs/commands/env-setup.cjs +90 -0
- package/dist/cjs/commands/env-setup.js +78 -0
- package/dist/cjs/commands/env-setup.js.map +6 -0
- package/dist/cjs/commands/env-setup.native.js +85 -0
- package/dist/cjs/commands/env-setup.native.js.map +6 -0
- package/dist/cjs/commands/onboard.cjs +479 -0
- package/dist/cjs/commands/onboard.js +631 -0
- package/dist/cjs/commands/onboard.js.map +6 -0
- package/dist/cjs/commands/onboard.native.js +608 -0
- package/dist/cjs/commands/onboard.native.js.map +6 -0
- package/dist/cjs/commands/run.cjs +148 -0
- package/dist/cjs/commands/run.js +116 -0
- package/dist/cjs/commands/run.js.map +6 -0
- package/dist/cjs/commands/run.native.js +140 -0
- package/dist/cjs/commands/run.native.js.map +6 -0
- package/dist/cjs/commands/script.cjs +379 -0
- package/dist/cjs/commands/script.js +339 -0
- package/dist/cjs/commands/script.js.map +6 -0
- package/dist/cjs/commands/script.native.js +449 -0
- package/dist/cjs/commands/script.native.js.map +6 -0
- package/dist/cjs/commands/sync.cjs +190 -0
- package/dist/cjs/commands/sync.js +168 -0
- package/dist/cjs/commands/sync.js.map +6 -0
- package/dist/cjs/commands/sync.native.js +211 -0
- package/dist/cjs/commands/sync.native.js.map +6 -0
- package/dist/cjs/constants/ascii.cjs +36 -0
- package/dist/cjs/constants/ascii.js +30 -0
- package/dist/cjs/constants/ascii.js.map +6 -0
- package/dist/cjs/constants/ascii.native.js +36 -0
- package/dist/cjs/constants/ascii.native.js.map +6 -0
- package/dist/cjs/index.cjs +64 -0
- package/dist/cjs/index.js +55 -0
- package/dist/cjs/index.js.map +6 -0
- package/dist/cjs/index.native.js +94 -0
- package/dist/cjs/index.native.js.map +6 -0
- package/dist/cjs/types.cjs +16 -0
- package/dist/cjs/types.js +14 -0
- package/dist/cjs/types.js.map +6 -0
- package/dist/cjs/types.native.js +15 -0
- package/dist/cjs/types.native.js.map +6 -0
- package/dist/cjs/utils/env-categories.cjs +272 -0
- package/dist/cjs/utils/env-categories.js +296 -0
- package/dist/cjs/utils/env-categories.js.map +6 -0
- package/dist/cjs/utils/env-categories.native.js +317 -0
- package/dist/cjs/utils/env-categories.native.js.map +6 -0
- package/dist/cjs/utils/env-setup.cjs +181 -0
- package/dist/cjs/utils/env-setup.js +190 -0
- package/dist/cjs/utils/env-setup.js.map +6 -0
- package/dist/cjs/utils/env-setup.native.js +264 -0
- package/dist/cjs/utils/env-setup.native.js.map +6 -0
- package/dist/cjs/utils/env.cjs +118 -0
- package/dist/cjs/utils/env.js +97 -0
- package/dist/cjs/utils/env.js.map +6 -0
- package/dist/cjs/utils/env.native.js +128 -0
- package/dist/cjs/utils/env.native.js.map +6 -0
- package/dist/cjs/utils/files.cjs +215 -0
- package/dist/cjs/utils/files.js +164 -0
- package/dist/cjs/utils/files.js.map +6 -0
- package/dist/cjs/utils/files.native.js +266 -0
- package/dist/cjs/utils/files.native.js.map +6 -0
- package/dist/cjs/utils/parallel-runner.cjs +99 -0
- package/dist/cjs/utils/parallel-runner.js +84 -0
- package/dist/cjs/utils/parallel-runner.js.map +6 -0
- package/dist/cjs/utils/parallel-runner.native.js +123 -0
- package/dist/cjs/utils/parallel-runner.native.js.map +6 -0
- package/dist/cjs/utils/ports.cjs +101 -0
- package/dist/cjs/utils/ports.js +81 -0
- package/dist/cjs/utils/ports.js.map +6 -0
- package/dist/cjs/utils/ports.native.js +130 -0
- package/dist/cjs/utils/ports.native.js.map +6 -0
- package/dist/cjs/utils/prerequisites.cjs +119 -0
- package/dist/cjs/utils/prerequisites.js +107 -0
- package/dist/cjs/utils/prerequisites.js.map +6 -0
- package/dist/cjs/utils/prerequisites.native.js +127 -0
- package/dist/cjs/utils/prerequisites.native.js.map +6 -0
- package/dist/cjs/utils/prompts.cjs +161 -0
- package/dist/cjs/utils/prompts.js +162 -0
- package/dist/cjs/utils/prompts.js.map +6 -0
- package/dist/cjs/utils/prompts.native.js +179 -0
- package/dist/cjs/utils/prompts.native.js.map +6 -0
- package/dist/cjs/utils/script-listing.cjs +113 -0
- package/dist/cjs/utils/script-listing.js +108 -0
- package/dist/cjs/utils/script-listing.js.map +6 -0
- package/dist/cjs/utils/script-listing.native.js +174 -0
- package/dist/cjs/utils/script-listing.native.js.map +6 -0
- package/dist/cjs/utils/sync.cjs +85 -0
- package/dist/cjs/utils/sync.js +70 -0
- package/dist/cjs/utils/sync.js.map +6 -0
- package/dist/cjs/utils/sync.native.js +84 -0
- package/dist/cjs/utils/sync.native.js.map +6 -0
- package/dist/cjs/utils/welcome.cjs +50 -0
- package/dist/cjs/utils/welcome.js +42 -0
- package/dist/cjs/utils/welcome.js.map +6 -0
- package/dist/cjs/utils/welcome.native.js +47 -0
- package/dist/cjs/utils/welcome.native.js.map +6 -0
- package/dist/esm/cli.js +79 -0
- package/dist/esm/cli.js.map +6 -0
- package/dist/esm/cli.mjs +71 -0
- package/dist/esm/cli.mjs.map +1 -0
- package/dist/esm/cli.native.js +69 -0
- package/dist/esm/cli.native.js.map +1 -0
- package/dist/esm/commands/changed.js +194 -0
- package/dist/esm/commands/changed.js.map +6 -0
- package/dist/esm/commands/changed.mjs +178 -0
- package/dist/esm/commands/changed.mjs.map +1 -0
- package/dist/esm/commands/changed.native.js +273 -0
- package/dist/esm/commands/changed.native.js.map +1 -0
- package/dist/esm/commands/docs.js +306 -0
- package/dist/esm/commands/docs.js.map +6 -0
- package/dist/esm/commands/docs.mjs +353 -0
- package/dist/esm/commands/docs.mjs.map +1 -0
- package/dist/esm/commands/docs.native.js +516 -0
- package/dist/esm/commands/docs.native.js.map +1 -0
- package/dist/esm/commands/env-setup.js +56 -0
- package/dist/esm/commands/env-setup.js.map +6 -0
- package/dist/esm/commands/env-setup.mjs +56 -0
- package/dist/esm/commands/env-setup.mjs.map +1 -0
- package/dist/esm/commands/env-setup.native.js +59 -0
- package/dist/esm/commands/env-setup.native.js.map +1 -0
- package/dist/esm/commands/onboard.js +645 -0
- package/dist/esm/commands/onboard.js.map +6 -0
- package/dist/esm/commands/onboard.mjs +445 -0
- package/dist/esm/commands/onboard.mjs.map +1 -0
- package/dist/esm/commands/onboard.native.js +584 -0
- package/dist/esm/commands/onboard.native.js.map +1 -0
- package/dist/esm/commands/run.js +95 -0
- package/dist/esm/commands/run.js.map +6 -0
- package/dist/esm/commands/run.mjs +114 -0
- package/dist/esm/commands/run.mjs.map +1 -0
- package/dist/esm/commands/run.native.js +133 -0
- package/dist/esm/commands/run.native.js.map +1 -0
- package/dist/esm/commands/script.js +338 -0
- package/dist/esm/commands/script.js.map +6 -0
- package/dist/esm/commands/script.mjs +336 -0
- package/dist/esm/commands/script.mjs.map +1 -0
- package/dist/esm/commands/script.native.js +445 -0
- package/dist/esm/commands/script.native.js.map +1 -0
- package/dist/esm/commands/sync.js +158 -0
- package/dist/esm/commands/sync.js.map +6 -0
- package/dist/esm/commands/sync.mjs +155 -0
- package/dist/esm/commands/sync.mjs.map +1 -0
- package/dist/esm/commands/sync.native.js +173 -0
- package/dist/esm/commands/sync.native.js.map +1 -0
- package/dist/esm/constants/ascii.js +14 -0
- package/dist/esm/constants/ascii.js.map +6 -0
- package/dist/esm/constants/ascii.mjs +12 -0
- package/dist/esm/constants/ascii.mjs.map +1 -0
- package/dist/esm/constants/ascii.native.js +12 -0
- package/dist/esm/constants/ascii.native.js.map +1 -0
- package/dist/esm/index.js +83 -0
- package/dist/esm/index.js.map +6 -0
- package/dist/esm/index.mjs +7 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +7 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/types.js.map +6 -0
- package/dist/esm/types.mjs +2 -0
- package/dist/esm/types.mjs.map +1 -0
- package/dist/esm/types.native.js +2 -0
- package/dist/esm/types.native.js.map +1 -0
- package/dist/esm/utils/env-categories.js +272 -0
- package/dist/esm/utils/env-categories.js.map +6 -0
- package/dist/esm/utils/env-categories.mjs +233 -0
- package/dist/esm/utils/env-categories.mjs.map +1 -0
- package/dist/esm/utils/env-categories.native.js +246 -0
- package/dist/esm/utils/env-categories.native.js.map +1 -0
- package/dist/esm/utils/env-setup.js +173 -0
- package/dist/esm/utils/env-setup.js.map +6 -0
- package/dist/esm/utils/env-setup.mjs +146 -0
- package/dist/esm/utils/env-setup.mjs.map +1 -0
- package/dist/esm/utils/env-setup.native.js +243 -0
- package/dist/esm/utils/env-setup.native.js.map +1 -0
- package/dist/esm/utils/env.js +83 -0
- package/dist/esm/utils/env.js.map +6 -0
- package/dist/esm/utils/env.mjs +90 -0
- package/dist/esm/utils/env.mjs.map +1 -0
- package/dist/esm/utils/env.native.js +99 -0
- package/dist/esm/utils/env.native.js.map +1 -0
- package/dist/esm/utils/files.js +150 -0
- package/dist/esm/utils/files.js.map +6 -0
- package/dist/esm/utils/files.mjs +187 -0
- package/dist/esm/utils/files.mjs.map +1 -0
- package/dist/esm/utils/files.native.js +247 -0
- package/dist/esm/utils/files.native.js.map +1 -0
- package/dist/esm/utils/parallel-runner.js +69 -0
- package/dist/esm/utils/parallel-runner.js.map +6 -0
- package/dist/esm/utils/parallel-runner.mjs +76 -0
- package/dist/esm/utils/parallel-runner.mjs.map +1 -0
- package/dist/esm/utils/parallel-runner.native.js +109 -0
- package/dist/esm/utils/parallel-runner.native.js.map +1 -0
- package/dist/esm/utils/ports.js +65 -0
- package/dist/esm/utils/ports.js.map +6 -0
- package/dist/esm/utils/ports.mjs +74 -0
- package/dist/esm/utils/ports.mjs.map +1 -0
- package/dist/esm/utils/ports.native.js +93 -0
- package/dist/esm/utils/ports.native.js.map +1 -0
- package/dist/esm/utils/prerequisites.js +91 -0
- package/dist/esm/utils/prerequisites.js.map +6 -0
- package/dist/esm/utils/prerequisites.mjs +91 -0
- package/dist/esm/utils/prerequisites.mjs.map +1 -0
- package/dist/esm/utils/prerequisites.native.js +97 -0
- package/dist/esm/utils/prerequisites.native.js.map +1 -0
- package/dist/esm/utils/prompts.js +139 -0
- package/dist/esm/utils/prompts.js.map +6 -0
- package/dist/esm/utils/prompts.mjs +112 -0
- package/dist/esm/utils/prompts.mjs.map +1 -0
- package/dist/esm/utils/prompts.native.js +115 -0
- package/dist/esm/utils/prompts.native.js.map +1 -0
- package/dist/esm/utils/script-listing.js +91 -0
- package/dist/esm/utils/script-listing.js.map +6 -0
- package/dist/esm/utils/script-listing.mjs +76 -0
- package/dist/esm/utils/script-listing.mjs.map +1 -0
- package/dist/esm/utils/script-listing.native.js +151 -0
- package/dist/esm/utils/script-listing.native.js.map +1 -0
- package/dist/esm/utils/sync.js +50 -0
- package/dist/esm/utils/sync.js.map +6 -0
- package/dist/esm/utils/sync.mjs +48 -0
- package/dist/esm/utils/sync.mjs.map +1 -0
- package/dist/esm/utils/sync.native.js +53 -0
- package/dist/esm/utils/sync.native.js.map +1 -0
- package/dist/esm/utils/welcome.js +21 -0
- package/dist/esm/utils/welcome.js.map +6 -0
- package/dist/esm/utils/welcome.mjs +15 -0
- package/dist/esm/utils/welcome.mjs.map +1 -0
- package/dist/esm/utils/welcome.native.js +18 -0
- package/dist/esm/utils/welcome.native.js.map +1 -0
- package/docs/aggregates.md +579 -0
- package/docs/cloudflare-dev-tunnel.md +41 -0
- package/docs/database.md +203 -0
- package/docs/docs.md +8 -0
- package/docs/emitters.md +562 -0
- package/docs/hot-updater.md +223 -0
- package/docs/native-hot-update.md +252 -0
- package/docs/one-components.md +234 -0
- package/docs/one-hooks.md +570 -0
- package/docs/one-routes.md +660 -0
- package/docs/package-json.md +115 -0
- package/docs/react-native-navigation-flow.md +184 -0
- package/docs/scripts.md +147 -0
- package/docs/sync-prompt.md +208 -0
- package/docs/tamagui.md +478 -0
- package/docs/testing-integration.md +564 -0
- package/docs/triggers.md +450 -0
- package/docs/zero.md +719 -0
- package/package.json +76 -0
- package/scripts/seed.ts +209 -0
- package/src/cli.ts +147 -0
- package/src/commands/changed.ts +313 -0
- package/src/commands/docs.ts +582 -0
- package/src/commands/env-setup.ts +69 -0
- package/src/commands/onboard.ts +1391 -0
- package/src/commands/run.ts +173 -0
- package/src/commands/script.ts +587 -0
- package/src/commands/sync.ts +305 -0
- package/src/constants/ascii.ts +17 -0
- package/src/index.ts +63 -0
- package/src/types.ts +59 -0
- package/src/utils/env-categories.ts +245 -0
- package/src/utils/env-setup.ts +338 -0
- package/src/utils/env.ts +127 -0
- package/src/utils/files.ts +302 -0
- package/src/utils/parallel-runner.ts +129 -0
- package/src/utils/ports.ts +77 -0
- package/src/utils/prerequisites.ts +137 -0
- package/src/utils/prompts.ts +197 -0
- package/src/utils/script-listing.ts +214 -0
- package/src/utils/sync.ts +101 -0
- package/src/withOpSqliteStatic.cjs +51 -0
- package/types/cli.d.ts +7 -0
- package/types/cli.d.ts.map +1 -0
- package/types/commands/changed.d.ts +14 -0
- package/types/commands/changed.d.ts.map +1 -0
- package/types/commands/docs.d.ts +5 -0
- package/types/commands/docs.d.ts.map +1 -0
- package/types/commands/env-setup.d.ts +25 -0
- package/types/commands/env-setup.d.ts.map +1 -0
- package/types/commands/onboard.d.ts +16 -0
- package/types/commands/onboard.d.ts.map +1 -0
- package/types/commands/run.d.ts +8 -0
- package/types/commands/run.d.ts.map +1 -0
- package/types/commands/script.d.ts +28 -0
- package/types/commands/script.d.ts.map +1 -0
- package/types/commands/sync.d.ts +5 -0
- package/types/commands/sync.d.ts.map +1 -0
- package/types/constants/ascii.d.ts +6 -0
- package/types/constants/ascii.d.ts.map +1 -0
- package/types/index.d.ts +12 -0
- package/types/index.d.ts.map +1 -0
- package/types/types.d.ts +54 -0
- package/types/types.d.ts.map +1 -0
- package/types/utils/env-categories.d.ts +8 -0
- package/types/utils/env-categories.d.ts.map +1 -0
- package/types/utils/env-setup.d.ts +10 -0
- package/types/utils/env-setup.d.ts.map +1 -0
- package/types/utils/env.d.ts +19 -0
- package/types/utils/env.d.ts.map +1 -0
- package/types/utils/files.d.ts +47 -0
- package/types/utils/files.d.ts.map +1 -0
- package/types/utils/parallel-runner.d.ts +15 -0
- package/types/utils/parallel-runner.d.ts.map +1 -0
- package/types/utils/ports.d.ts +16 -0
- package/types/utils/ports.d.ts.map +1 -0
- package/types/utils/prerequisites.d.ts +11 -0
- package/types/utils/prerequisites.d.ts.map +1 -0
- package/types/utils/prompts.d.ts +30 -0
- package/types/utils/prompts.d.ts.map +1 -0
- package/types/utils/script-listing.d.ts +7 -0
- package/types/utils/script-listing.d.ts.map +1 -0
- package/types/utils/sync.d.ts +16 -0
- package/types/utils/sync.d.ts.map +1 -0
- package/types/utils/welcome.d.ts +6 -0
- package/types/utils/welcome.d.ts.map +1 -0
|
@@ -0,0 +1,1391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboard command - interactive setup for Takeout starter kit
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { execSync } from 'node:child_process'
|
|
6
|
+
import { randomBytes } from 'node:crypto'
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
8
|
+
import { homedir } from 'node:os'
|
|
9
|
+
import { resolve } from 'node:path'
|
|
10
|
+
|
|
11
|
+
import { defineCommand } from 'citty'
|
|
12
|
+
import pc from 'picocolors'
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
copyEnvFile,
|
|
16
|
+
createEnvLocal,
|
|
17
|
+
envFileExists,
|
|
18
|
+
updateEnvVariable,
|
|
19
|
+
} from '../utils/env'
|
|
20
|
+
import { setupProductionEnv } from '../utils/env-setup'
|
|
21
|
+
import {
|
|
22
|
+
ejectFromMonorepo,
|
|
23
|
+
markOnboarded,
|
|
24
|
+
updateAppConfig,
|
|
25
|
+
updatePackageJson,
|
|
26
|
+
updatePackageJsonEnv,
|
|
27
|
+
} from '../utils/files'
|
|
28
|
+
import { checkAllPorts, getConflictingPorts } from '../utils/ports'
|
|
29
|
+
import { checkAllPrerequisites, hasRequiredPrerequisites } from '../utils/prerequisites'
|
|
30
|
+
import {
|
|
31
|
+
confirmContinue,
|
|
32
|
+
displayOutro,
|
|
33
|
+
displayPortConflicts,
|
|
34
|
+
displayPrerequisites,
|
|
35
|
+
displayWelcome,
|
|
36
|
+
promptText,
|
|
37
|
+
promptSelect,
|
|
38
|
+
promptStartStep,
|
|
39
|
+
showError,
|
|
40
|
+
showInfo,
|
|
41
|
+
showSpinner,
|
|
42
|
+
showStep,
|
|
43
|
+
showSuccess,
|
|
44
|
+
showWarning,
|
|
45
|
+
} from '../utils/prompts'
|
|
46
|
+
|
|
47
|
+
export const onboardCommand = defineCommand({
|
|
48
|
+
meta: {
|
|
49
|
+
name: 'onboard',
|
|
50
|
+
description: 'Interactive onboarding for Takeout starter kit',
|
|
51
|
+
},
|
|
52
|
+
args: {
|
|
53
|
+
skip: {
|
|
54
|
+
type: 'boolean',
|
|
55
|
+
description: 'Skip onboarding entirely',
|
|
56
|
+
default: false,
|
|
57
|
+
},
|
|
58
|
+
defaults: {
|
|
59
|
+
type: 'boolean',
|
|
60
|
+
description: 'Run with defaults (non-interactive)',
|
|
61
|
+
default: false,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
async run({ args }) {
|
|
65
|
+
const cwd = process.cwd()
|
|
66
|
+
|
|
67
|
+
if (args.skip) {
|
|
68
|
+
showInfo('Skipping onboarding (--skip flag)')
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// non-interactive defaults mode
|
|
73
|
+
if (args.defaults) {
|
|
74
|
+
showInfo('Running onboarding with defaults (--defaults flag)')
|
|
75
|
+
console.info()
|
|
76
|
+
|
|
77
|
+
// setup environment
|
|
78
|
+
const hasEnv = envFileExists(cwd, '.env')
|
|
79
|
+
if (!hasEnv) {
|
|
80
|
+
const copyResult = copyEnvFile(cwd, '.env.development', '.env')
|
|
81
|
+
if (copyResult.success) {
|
|
82
|
+
showSuccess('Created .env from .env.development')
|
|
83
|
+
} else {
|
|
84
|
+
showError(`Failed to create .env: ${copyResult.error}`)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
createEnvLocal(cwd)
|
|
88
|
+
showSuccess('Created .env.local for personal overrides')
|
|
89
|
+
} else {
|
|
90
|
+
showInfo('.env already exists, skipping')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
markOnboarded(cwd)
|
|
94
|
+
showSuccess('Onboarding complete with defaults')
|
|
95
|
+
console.info()
|
|
96
|
+
showInfo('Next steps:')
|
|
97
|
+
console.info(' bun backend # start docker services')
|
|
98
|
+
console.info(' bun dev # start web dev server')
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Phase 0: Check for saved state
|
|
103
|
+
displayWelcome()
|
|
104
|
+
|
|
105
|
+
const savedState = loadOnboardState(cwd)
|
|
106
|
+
let startStep:
|
|
107
|
+
| 'full'
|
|
108
|
+
| 'prerequisites'
|
|
109
|
+
| 'identity'
|
|
110
|
+
| 'ports'
|
|
111
|
+
| 'eject'
|
|
112
|
+
| 'ci-runners'
|
|
113
|
+
| 'production'
|
|
114
|
+
| 'cancel'
|
|
115
|
+
|
|
116
|
+
if (savedState) {
|
|
117
|
+
console.info()
|
|
118
|
+
showInfo(`Found incomplete setup from previous session (${savedState.step})`)
|
|
119
|
+
const shouldResume = await confirmContinue('Resume from where you left off?', true)
|
|
120
|
+
|
|
121
|
+
if (shouldResume) {
|
|
122
|
+
startStep = savedState.step
|
|
123
|
+
} else {
|
|
124
|
+
clearOnboardState(cwd)
|
|
125
|
+
startStep = await promptStartStep()
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
startStep = await promptStartStep()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (startStep === 'cancel') {
|
|
132
|
+
displayOutro('Setup cancelled')
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Direct eject - skip to eject step
|
|
137
|
+
if (startStep === 'eject') {
|
|
138
|
+
showStep('Monorepo Ejection')
|
|
139
|
+
console.info()
|
|
140
|
+
|
|
141
|
+
console.info(
|
|
142
|
+
pc.gray(
|
|
143
|
+
"We've included a variety of packages we found useful building apps with this stack:"
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
console.info(pc.gray(' • @take-out/cli - CLI tools and onboarding'))
|
|
147
|
+
console.info(pc.gray(' • @take-out/helpers - Utility functions'))
|
|
148
|
+
console.info(pc.gray(' • @take-out/hooks - React hooks'))
|
|
149
|
+
console.info(pc.gray(' • @take-out/postgres - Database utilities'))
|
|
150
|
+
console.info(pc.gray(' • @take-out/scripts - Build and dev scripts'))
|
|
151
|
+
console.info(pc.gray(' • @take-out/better-auth-utils - Auth helpers'))
|
|
152
|
+
console.info()
|
|
153
|
+
|
|
154
|
+
const shouldEject = await confirmContinue(
|
|
155
|
+
'Eject from monorepo setup? (removes ./packages, uses published versions)',
|
|
156
|
+
true
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if (shouldEject) {
|
|
160
|
+
// first run a dry-run to show what will happen
|
|
161
|
+
const dryResult = await ejectFromMonorepo(cwd, { dryRun: true })
|
|
162
|
+
|
|
163
|
+
if (!dryResult.success) {
|
|
164
|
+
showError(`Cannot eject: ${dryResult.error}`)
|
|
165
|
+
clearOnboardState(cwd)
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.info()
|
|
170
|
+
showInfo(`Found ${dryResult.packages?.length} packages to convert:`)
|
|
171
|
+
for (const pkg of dryResult.packages || []) {
|
|
172
|
+
console.info(pc.gray(` • ${pkg.name}@${pkg.version}`))
|
|
173
|
+
}
|
|
174
|
+
if (dryResult.warnings?.length) {
|
|
175
|
+
for (const warn of dryResult.warnings) {
|
|
176
|
+
showWarning(warn)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
console.info()
|
|
180
|
+
|
|
181
|
+
const confirmEject = await confirmContinue('Proceed with ejection?', true)
|
|
182
|
+
if (!confirmEject) {
|
|
183
|
+
showInfo('Eject cancelled')
|
|
184
|
+
clearOnboardState(cwd)
|
|
185
|
+
displayOutro('Done!')
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const spinner = showSpinner('Ejecting from monorepo...')
|
|
190
|
+
|
|
191
|
+
const result = await ejectFromMonorepo(cwd)
|
|
192
|
+
|
|
193
|
+
if (result.success) {
|
|
194
|
+
spinner.stop('Ejected from monorepo')
|
|
195
|
+
showSuccess('✓ Removed ./packages directory')
|
|
196
|
+
showSuccess('✓ Updated package.json to use published versions')
|
|
197
|
+
showSuccess('✓ Installed published packages')
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
execSync('git add -A && git commit -m "ejected from monorepo"', {
|
|
201
|
+
cwd,
|
|
202
|
+
stdio: 'ignore',
|
|
203
|
+
})
|
|
204
|
+
showSuccess('✓ Committed eject changes')
|
|
205
|
+
} catch {
|
|
206
|
+
showWarning('Git commit skipped (not a git repo or no changes)')
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.info()
|
|
210
|
+
showInfo('You can now upgrade packages with: bun up takeout')
|
|
211
|
+
} else {
|
|
212
|
+
spinner.stop('Ejection failed')
|
|
213
|
+
showError(`Failed to eject: ${result.error}`)
|
|
214
|
+
if (result.error?.includes('install failed')) {
|
|
215
|
+
showInfo('You may be able to fix this by running "bun install" manually')
|
|
216
|
+
showInfo('Or restore the repo from git with "git checkout ."')
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
showInfo('Eject cancelled')
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
clearOnboardState(cwd)
|
|
224
|
+
displayOutro('Done!')
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Phase 1: Prerequisites
|
|
229
|
+
if (startStep === 'prerequisites' || startStep === 'full') {
|
|
230
|
+
saveOnboardState(cwd, { step: 'prerequisites', timestamp: Date.now() })
|
|
231
|
+
showStep('Checking prerequisites...')
|
|
232
|
+
console.info()
|
|
233
|
+
|
|
234
|
+
const checks = checkAllPrerequisites()
|
|
235
|
+
displayPrerequisites(checks)
|
|
236
|
+
|
|
237
|
+
const hasRequired = hasRequiredPrerequisites(checks)
|
|
238
|
+
|
|
239
|
+
if (!hasRequired) {
|
|
240
|
+
showWarning(
|
|
241
|
+
'Some required prerequisites are missing. You can continue, but setup may fail.'
|
|
242
|
+
)
|
|
243
|
+
const shouldContinue = await confirmContinue('Continue anyway?', false)
|
|
244
|
+
if (!shouldContinue) {
|
|
245
|
+
displayOutro('Setup cancelled. Install prerequisites and try again.')
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.info()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Phase 2: Project Identity
|
|
254
|
+
if (
|
|
255
|
+
startStep === 'prerequisites' ||
|
|
256
|
+
startStep === 'identity' ||
|
|
257
|
+
startStep === 'full'
|
|
258
|
+
) {
|
|
259
|
+
saveOnboardState(cwd, { step: 'identity', timestamp: Date.now() })
|
|
260
|
+
showStep('Configuring project identity...')
|
|
261
|
+
console.info()
|
|
262
|
+
|
|
263
|
+
const shouldCustomize = await confirmContinue(
|
|
264
|
+
'Customize project name and bundle identifier?',
|
|
265
|
+
false
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if (shouldCustomize) {
|
|
269
|
+
await customizeProject(cwd)
|
|
270
|
+
} else {
|
|
271
|
+
showInfo('Keeping default project configuration')
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
console.info()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Phase 3: Web Port Configuration
|
|
278
|
+
if (
|
|
279
|
+
startStep === 'prerequisites' ||
|
|
280
|
+
startStep === 'identity' ||
|
|
281
|
+
startStep === 'ports' ||
|
|
282
|
+
startStep === 'full'
|
|
283
|
+
) {
|
|
284
|
+
saveOnboardState(cwd, { step: 'ports', timestamp: Date.now() })
|
|
285
|
+
showStep('Configuring web server port...')
|
|
286
|
+
console.info()
|
|
287
|
+
|
|
288
|
+
showInfo('Default web port: 8081 (TAMA in T9)')
|
|
289
|
+
const shouldCustomizePort = await confirmContinue(
|
|
290
|
+
'Customize web server port?',
|
|
291
|
+
false
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if (shouldCustomizePort) {
|
|
295
|
+
const newPort = await promptText(
|
|
296
|
+
'Web server port:',
|
|
297
|
+
'8081',
|
|
298
|
+
'3000, 8080, 8081, etc.'
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
if (newPort && newPort !== '8081') {
|
|
302
|
+
await replacePortInProject(cwd, '8081', newPort)
|
|
303
|
+
showSuccess(`✓ Updated web server port to ${newPort}`)
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
showInfo('Keeping default port 8081')
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
console.info()
|
|
310
|
+
showStep('Checking service ports...')
|
|
311
|
+
console.info()
|
|
312
|
+
|
|
313
|
+
const portChecks = checkAllPorts()
|
|
314
|
+
const conflicts = getConflictingPorts(portChecks)
|
|
315
|
+
|
|
316
|
+
if (conflicts.length > 0) {
|
|
317
|
+
displayPortConflicts(conflicts)
|
|
318
|
+
showWarning('Some ports are already in use. You may need to stop other services.')
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
console.info()
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Phase 4: CI/CD Runner Configuration
|
|
325
|
+
if (
|
|
326
|
+
startStep === 'prerequisites' ||
|
|
327
|
+
startStep === 'identity' ||
|
|
328
|
+
startStep === 'ports' ||
|
|
329
|
+
startStep === 'full'
|
|
330
|
+
) {
|
|
331
|
+
saveOnboardState(cwd, { step: 'ci-runners', timestamp: Date.now() })
|
|
332
|
+
showStep('Configuring CI/CD runners...')
|
|
333
|
+
console.info()
|
|
334
|
+
|
|
335
|
+
const shouldConfigureCI = await confirmContinue(
|
|
336
|
+
'Configure GitHub Actions CI runners?',
|
|
337
|
+
true
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
if (shouldConfigureCI) {
|
|
341
|
+
await configureCIRunners(cwd)
|
|
342
|
+
} else {
|
|
343
|
+
showInfo('Skipping CI runner configuration')
|
|
344
|
+
showWarning(
|
|
345
|
+
'Default CI uses ARM64 Docker builds. If not using ARM runners, update scripts/web/build-docker.ts'
|
|
346
|
+
)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
console.info()
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Phase 6: Development Complete
|
|
353
|
+
if (startStep !== 'production') {
|
|
354
|
+
showStep('Development setup complete!')
|
|
355
|
+
console.info()
|
|
356
|
+
|
|
357
|
+
showSuccess('✓ Environment configured')
|
|
358
|
+
showSuccess('✓ Project ready for development')
|
|
359
|
+
|
|
360
|
+
markOnboarded(cwd)
|
|
361
|
+
|
|
362
|
+
console.info()
|
|
363
|
+
showInfo('Next steps (run in separate terminals):')
|
|
364
|
+
console.info()
|
|
365
|
+
console.info(' bun backend # start docker services first')
|
|
366
|
+
console.info(' bun dev # start web dev server')
|
|
367
|
+
console.info()
|
|
368
|
+
console.info(' bun ios # build iOS dev app')
|
|
369
|
+
console.info(' bun android # build Android dev app')
|
|
370
|
+
console.info(' bun tko docs list # view Takeout docs')
|
|
371
|
+
console.info()
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Phase 6.5: Monorepo Ejection (Optional)
|
|
375
|
+
if (startStep !== 'production') {
|
|
376
|
+
console.info()
|
|
377
|
+
showStep('Monorepo Setup')
|
|
378
|
+
console.info()
|
|
379
|
+
|
|
380
|
+
console.info(
|
|
381
|
+
pc.gray(
|
|
382
|
+
"We've included a variety of packages we found useful building apps with this stack:"
|
|
383
|
+
)
|
|
384
|
+
)
|
|
385
|
+
console.info(pc.gray(' • @take-out/cli - CLI tools and onboarding'))
|
|
386
|
+
console.info(pc.gray(' • @take-out/helpers - Utility functions'))
|
|
387
|
+
console.info(pc.gray(' • @take-out/hooks - React hooks'))
|
|
388
|
+
console.info(pc.gray(' • @take-out/postgres - Database utilities'))
|
|
389
|
+
console.info(pc.gray(' • @take-out/scripts - Build and dev scripts'))
|
|
390
|
+
console.info(pc.gray(' • @take-out/better-auth-utils - Auth helpers'))
|
|
391
|
+
console.info()
|
|
392
|
+
console.info(pc.gray('These packages are included locally for two reasons:'))
|
|
393
|
+
console.info(
|
|
394
|
+
pc.gray(' 1. You can see their source and decide if you want to keep them')
|
|
395
|
+
)
|
|
396
|
+
console.info(
|
|
397
|
+
pc.gray(' 2. Anyone can easily submit fixes or improvements back to our repo')
|
|
398
|
+
)
|
|
399
|
+
console.info()
|
|
400
|
+
console.info(
|
|
401
|
+
pc.gray(
|
|
402
|
+
"Over time we'll publish new versions. You can sync with 'bun tko sync' (monorepo)"
|
|
403
|
+
)
|
|
404
|
+
)
|
|
405
|
+
console.info(pc.gray("or if you eject, use 'bun up takeout' for package updates."))
|
|
406
|
+
console.info()
|
|
407
|
+
|
|
408
|
+
const shouldEject = await confirmContinue(
|
|
409
|
+
'Eject from monorepo setup? (removes ./packages, uses published versions)',
|
|
410
|
+
false
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
if (shouldEject) {
|
|
414
|
+
// first run a dry-run to show what will happen
|
|
415
|
+
const dryResult = await ejectFromMonorepo(cwd, { dryRun: true })
|
|
416
|
+
|
|
417
|
+
if (!dryResult.success) {
|
|
418
|
+
showError(`Cannot eject: ${dryResult.error}`)
|
|
419
|
+
} else {
|
|
420
|
+
console.info()
|
|
421
|
+
showInfo(`Found ${dryResult.packages?.length} packages to convert:`)
|
|
422
|
+
for (const pkg of dryResult.packages || []) {
|
|
423
|
+
console.info(pc.gray(` • ${pkg.name}@${pkg.version}`))
|
|
424
|
+
}
|
|
425
|
+
if (dryResult.warnings?.length) {
|
|
426
|
+
for (const warn of dryResult.warnings) {
|
|
427
|
+
showWarning(warn)
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
console.info()
|
|
431
|
+
|
|
432
|
+
const confirmEject = await confirmContinue('Proceed with ejection?', true)
|
|
433
|
+
if (confirmEject) {
|
|
434
|
+
const spinner = showSpinner('Ejecting from monorepo...')
|
|
435
|
+
|
|
436
|
+
const result = await ejectFromMonorepo(cwd)
|
|
437
|
+
|
|
438
|
+
if (result.success) {
|
|
439
|
+
spinner.stop('Ejected from monorepo')
|
|
440
|
+
showSuccess('✓ Removed ./packages directory')
|
|
441
|
+
showSuccess('✓ Updated package.json to use published versions')
|
|
442
|
+
showSuccess('✓ Installed published packages')
|
|
443
|
+
|
|
444
|
+
// commit the eject changes
|
|
445
|
+
try {
|
|
446
|
+
execSync('git add -A && git commit -m "ejected from monorepo"', {
|
|
447
|
+
cwd,
|
|
448
|
+
stdio: 'ignore',
|
|
449
|
+
})
|
|
450
|
+
showSuccess('✓ Committed eject changes')
|
|
451
|
+
} catch {
|
|
452
|
+
showWarning('Git commit skipped (not a git repo or no changes)')
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
console.info()
|
|
456
|
+
showInfo('You can now upgrade packages with: bun up takeout')
|
|
457
|
+
} else {
|
|
458
|
+
spinner.stop('Ejection failed')
|
|
459
|
+
showError(`Failed to eject: ${result.error}`)
|
|
460
|
+
if (result.error?.includes('install failed')) {
|
|
461
|
+
showInfo('You may be able to fix this by running "bun install" manually')
|
|
462
|
+
showInfo('Or restore the repo from git with "git checkout ."')
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
showInfo('Keeping monorepo setup - you can customize packages locally')
|
|
467
|
+
showInfo('Run "bun tko sync" to sync with upstream Takeout updates later')
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
showInfo('Keeping monorepo setup - you can customize packages locally')
|
|
472
|
+
showInfo('Run "bun tko sync" to sync with upstream Takeout updates later')
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
console.info()
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Phase 7: Production Setup (Optional or direct)
|
|
479
|
+
if (startStep === 'full' || startStep === 'production') {
|
|
480
|
+
saveOnboardState(cwd, { step: 'production', timestamp: Date.now() })
|
|
481
|
+
console.info()
|
|
482
|
+
showStep('Production deployment setup')
|
|
483
|
+
console.info()
|
|
484
|
+
|
|
485
|
+
// Only ask to confirm if doing full setup, skip if directly chose production
|
|
486
|
+
let setupProd = startStep === 'production'
|
|
487
|
+
if (startStep === 'full') {
|
|
488
|
+
setupProd = await confirmContinue('Set up production deployment?', false)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (setupProd) {
|
|
492
|
+
await setupProductionDeployment(cwd)
|
|
493
|
+
} else {
|
|
494
|
+
showInfo('Skipping production setup')
|
|
495
|
+
showInfo('You can set up production later with: bun tko onboard --production')
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Clear state on successful completion
|
|
500
|
+
clearOnboardState(cwd)
|
|
501
|
+
|
|
502
|
+
console.info()
|
|
503
|
+
displayOutro('Happy coding! 🚀')
|
|
504
|
+
},
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
async function customizeProject(cwd: string): Promise<void> {
|
|
508
|
+
const projectName = await promptText('Project name:', 'takeout', 'my-awesome-app')
|
|
509
|
+
|
|
510
|
+
const slug = await promptText(
|
|
511
|
+
'Project slug (URL-friendly):',
|
|
512
|
+
projectName.toLowerCase().replace(/\s+/g, '-'),
|
|
513
|
+
'my-awesome-app'
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
const bundleId = await promptText(
|
|
517
|
+
'Bundle identifier:',
|
|
518
|
+
`com.${slug}.app`,
|
|
519
|
+
'com.example.app'
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
const domain = await promptText(
|
|
523
|
+
'Development domain:',
|
|
524
|
+
'localhost:8081',
|
|
525
|
+
'localhost:8081'
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
// Update package.json
|
|
529
|
+
const pkgResult = updatePackageJson(cwd, {
|
|
530
|
+
name: projectName,
|
|
531
|
+
description: `${projectName} - Built with Takeout starter kit`,
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
if (pkgResult.success) {
|
|
535
|
+
showSuccess('Updated package.json')
|
|
536
|
+
} else {
|
|
537
|
+
showError(`Failed to update package.json: ${pkgResult.error}`)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Update app.config.ts
|
|
541
|
+
const configResult = updateAppConfig(cwd, {
|
|
542
|
+
name: projectName,
|
|
543
|
+
slug,
|
|
544
|
+
bundleId,
|
|
545
|
+
})
|
|
546
|
+
|
|
547
|
+
if (configResult.success) {
|
|
548
|
+
showSuccess('Updated app.config.ts')
|
|
549
|
+
} else {
|
|
550
|
+
showError(`Failed to update app.config.ts: ${configResult.error}`)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Update .env URLs
|
|
554
|
+
const serverUrl = `http://${domain}`
|
|
555
|
+
updateEnvVariable(cwd, 'BETTER_AUTH_URL', serverUrl)
|
|
556
|
+
updateEnvVariable(cwd, 'ONE_SERVER_URL', serverUrl)
|
|
557
|
+
showSuccess('Updated environment URLs')
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async function setupProductionDeployment(cwd: string): Promise<void> {
|
|
561
|
+
// Choose platform
|
|
562
|
+
const platform = await promptSelect<'uncloud' | 'sst'>('Choose deployment platform:', [
|
|
563
|
+
{
|
|
564
|
+
value: 'uncloud',
|
|
565
|
+
label: 'Uncloud',
|
|
566
|
+
hint: 'Simpler and quicker (~10 minutes setup)',
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
value: 'sst',
|
|
570
|
+
label: 'AWS SST',
|
|
571
|
+
hint: 'Proven and reliable (~1 hour setup)',
|
|
572
|
+
},
|
|
573
|
+
])
|
|
574
|
+
|
|
575
|
+
if (platform === 'cancel') {
|
|
576
|
+
showInfo('Skipping production setup')
|
|
577
|
+
return
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
console.info()
|
|
581
|
+
|
|
582
|
+
if (platform === 'uncloud') {
|
|
583
|
+
await setupUncloudDeployment(cwd)
|
|
584
|
+
} else {
|
|
585
|
+
await setupSSTDeployment(cwd)
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
async function setupUncloudDeployment(cwd: string): Promise<void> {
|
|
590
|
+
showInfo('Setting up Uncloud deployment')
|
|
591
|
+
console.info()
|
|
592
|
+
console.info(
|
|
593
|
+
pc.gray(
|
|
594
|
+
'Uncloud provides:\n • Managed PostgreSQL with logical replication\n • Free subdomain (your-app.uncld.dev)\n • Automatic SSL certificates\n • Easy scaling'
|
|
595
|
+
)
|
|
596
|
+
)
|
|
597
|
+
console.info()
|
|
598
|
+
|
|
599
|
+
// ask about server architecture
|
|
600
|
+
console.info()
|
|
601
|
+
showInfo('Server Architecture')
|
|
602
|
+
console.info(pc.gray('Docker images must match your server CPU architecture'))
|
|
603
|
+
console.info()
|
|
604
|
+
|
|
605
|
+
const architecture = await promptSelect<'amd64' | 'arm64'>(
|
|
606
|
+
'What CPU architecture is your deployment server?',
|
|
607
|
+
[
|
|
608
|
+
{
|
|
609
|
+
value: 'amd64',
|
|
610
|
+
label: 'AMD64/x86_64 (Intel/AMD)',
|
|
611
|
+
hint: 'Most VPS providers (DigitalOcean, Linode, Vultr)',
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
value: 'arm64',
|
|
615
|
+
label: 'ARM64 (Apple Silicon)',
|
|
616
|
+
hint: 'Hetzner ARM, Oracle ARM, Bare metal ARM servers',
|
|
617
|
+
},
|
|
618
|
+
]
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
if (architecture === 'cancel') {
|
|
622
|
+
showInfo('Skipping production setup')
|
|
623
|
+
return
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const deploymentArch = architecture === 'arm64' ? 'linux/arm64' : 'linux/amd64'
|
|
627
|
+
console.info()
|
|
628
|
+
showInfo(`Will build Docker images for: ${deploymentArch}`)
|
|
629
|
+
console.info()
|
|
630
|
+
|
|
631
|
+
// Read app name from constants
|
|
632
|
+
const appConstantsPath = resolve(cwd, 'src/constants/app.ts')
|
|
633
|
+
let defaultAppName = 'my-app'
|
|
634
|
+
try {
|
|
635
|
+
const appConstants = readFileSync(appConstantsPath, 'utf-8')
|
|
636
|
+
const appNameMatch = appConstants.match(/APP_NAME_LOWERCASE\s*=\s*['"](.+?)['"]/)
|
|
637
|
+
if (appNameMatch?.[1]) {
|
|
638
|
+
defaultAppName = appNameMatch[1]
|
|
639
|
+
}
|
|
640
|
+
} catch {
|
|
641
|
+
// use default if can't read file
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Domain choice
|
|
645
|
+
const useFreeSubdomain = await confirmContinue('Use free Uncloud subdomain?', true)
|
|
646
|
+
|
|
647
|
+
let domain: string
|
|
648
|
+
let zeroUrl: string
|
|
649
|
+
|
|
650
|
+
if (useFreeSubdomain) {
|
|
651
|
+
const appName = await promptText(
|
|
652
|
+
'App name for subdomain:',
|
|
653
|
+
defaultAppName,
|
|
654
|
+
defaultAppName
|
|
655
|
+
)
|
|
656
|
+
domain = `https://${appName}.uncld.dev`
|
|
657
|
+
zeroUrl = `https://zero-${appName}.uncld.dev`
|
|
658
|
+
} else {
|
|
659
|
+
domain = await promptText(
|
|
660
|
+
'Enter your custom domain:',
|
|
661
|
+
undefined,
|
|
662
|
+
'https://yourapp.com'
|
|
663
|
+
)
|
|
664
|
+
zeroUrl = await promptText(
|
|
665
|
+
'Enter your Zero sync domain:',
|
|
666
|
+
undefined,
|
|
667
|
+
'https://zero.yourapp.com'
|
|
668
|
+
)
|
|
669
|
+
console.info()
|
|
670
|
+
showWarning('Custom domain setup requires DNS configuration after deployment')
|
|
671
|
+
console.info(pc.gray('See: https://uncloud.run/docs/domains'))
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
console.info()
|
|
675
|
+
|
|
676
|
+
// Database setup
|
|
677
|
+
showInfo('Database Configuration')
|
|
678
|
+
console.info()
|
|
679
|
+
console.info(pc.gray('PostgreSQL database with logical replication enabled'))
|
|
680
|
+
console.info(pc.gray('Zero sync requires 3 databases on the same host:'))
|
|
681
|
+
console.info(pc.gray(' • Main database (your app data)'))
|
|
682
|
+
console.info(pc.gray(' • Two Zero databases (for sync infrastructure)'))
|
|
683
|
+
console.info()
|
|
684
|
+
console.info(pc.gray('Use a managed database (DigitalOcean, Render, Supabase, etc.)'))
|
|
685
|
+
console.info(pc.gray('The deployment will automatically create the Zero databases'))
|
|
686
|
+
console.info()
|
|
687
|
+
|
|
688
|
+
const dbUser = await promptText('Database username:', undefined, 'postgres')
|
|
689
|
+
const dbPassword = await promptText('Database password:', undefined, '')
|
|
690
|
+
const dbHost = await promptText(
|
|
691
|
+
'Database host (e.g., db-xxx.ondigitalocean.com):',
|
|
692
|
+
undefined,
|
|
693
|
+
'localhost'
|
|
694
|
+
)
|
|
695
|
+
const dbPort = await promptText('Database port:', '5432', '5432')
|
|
696
|
+
const dbName = await promptText(
|
|
697
|
+
'Main database name (will derive Zero databases from this):',
|
|
698
|
+
'postgres',
|
|
699
|
+
'postgres'
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
// Construct database URL (deployment script will derive Zero databases from this)
|
|
703
|
+
const dbUrl = `postgresql://${dbUser}:${dbPassword}@${dbHost}:${dbPort}/${dbName}`
|
|
704
|
+
|
|
705
|
+
// Generate auth secret
|
|
706
|
+
console.info()
|
|
707
|
+
const authSecret = randomBytes(32).toString('hex')
|
|
708
|
+
|
|
709
|
+
// Save to .env.production
|
|
710
|
+
const envFile = '.env.production'
|
|
711
|
+
updateEnvVariable(cwd, 'DEPLOYMENT_PLATFORM', 'uncloud', envFile)
|
|
712
|
+
updateEnvVariable(cwd, 'DEPLOYMENT_ARCH', deploymentArch, envFile)
|
|
713
|
+
updateEnvVariable(cwd, 'DEPLOY_DB', dbUrl, envFile)
|
|
714
|
+
updateEnvVariable(cwd, 'BETTER_AUTH_SECRET', authSecret, envFile)
|
|
715
|
+
updateEnvVariable(cwd, 'BETTER_AUTH_URL', domain, envFile)
|
|
716
|
+
updateEnvVariable(cwd, 'ONE_SERVER_URL', domain, envFile)
|
|
717
|
+
updateEnvVariable(cwd, 'VITE_PUBLIC_ZERO_SERVER', zeroUrl, envFile)
|
|
718
|
+
|
|
719
|
+
// derive zero database URLs from the main database URL
|
|
720
|
+
const dbBase = dbUrl.split('/').slice(0, -1).join('/')
|
|
721
|
+
const zeroCvrDb = `${dbBase}/zero_cvr`
|
|
722
|
+
const zeroChangeDb = `${dbBase}/zero_cdb`
|
|
723
|
+
|
|
724
|
+
// set zero database vars in .env.production (for production deployment)
|
|
725
|
+
updateEnvVariable(cwd, 'ZERO_UPSTREAM_DB', dbUrl, envFile)
|
|
726
|
+
updateEnvVariable(cwd, 'ZERO_CVR_DB', zeroCvrDb, envFile)
|
|
727
|
+
updateEnvVariable(cwd, 'ZERO_CHANGE_DB', zeroChangeDb, envFile)
|
|
728
|
+
|
|
729
|
+
// also set in .env for local dev
|
|
730
|
+
updateEnvVariable(cwd, 'ZERO_UPSTREAM_DB', dbUrl, '.env')
|
|
731
|
+
updateEnvVariable(cwd, 'ZERO_CVR_DB', zeroCvrDb, '.env')
|
|
732
|
+
updateEnvVariable(cwd, 'ZERO_CHANGE_DB', zeroChangeDb, '.env')
|
|
733
|
+
|
|
734
|
+
// Extract host from domain URL and save for deployment
|
|
735
|
+
const deployHost = new URL(domain).hostname
|
|
736
|
+
updateEnvVariable(cwd, 'DEPLOY_HOST', deployHost, envFile)
|
|
737
|
+
updateEnvVariable(cwd, 'DEPLOY_USER', 'root', envFile)
|
|
738
|
+
|
|
739
|
+
// SSH key setup
|
|
740
|
+
console.info()
|
|
741
|
+
showInfo('SSH Key Configuration')
|
|
742
|
+
console.info(pc.gray('Deployment requires SSH access to your server'))
|
|
743
|
+
console.info()
|
|
744
|
+
|
|
745
|
+
const sshKeyPath = await promptText(
|
|
746
|
+
'Path to SSH private key:',
|
|
747
|
+
`${homedir()}/.ssh/id_rsa`,
|
|
748
|
+
`${homedir()}/.ssh/id_rsa`
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
if (sshKeyPath) {
|
|
752
|
+
// verify the key exists
|
|
753
|
+
if (existsSync(sshKeyPath)) {
|
|
754
|
+
updateEnvVariable(cwd, 'DEPLOY_SSH_KEY', sshKeyPath, envFile)
|
|
755
|
+
showSuccess(`✓ SSH key configured: ${sshKeyPath}`)
|
|
756
|
+
console.info()
|
|
757
|
+
showInfo(
|
|
758
|
+
pc.gray(
|
|
759
|
+
"For CI/CD, you'll need to add the SSH private key content as a GitHub secret"
|
|
760
|
+
)
|
|
761
|
+
)
|
|
762
|
+
showInfo(pc.gray('The sync script will help with this'))
|
|
763
|
+
} else {
|
|
764
|
+
showWarning(`SSH key not found at: ${sshKeyPath}`)
|
|
765
|
+
showInfo('You can add DEPLOY_SSH_KEY to .env.production manually later')
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
console.info()
|
|
770
|
+
showSuccess(`✓ Saved configuration to ${envFile}`)
|
|
771
|
+
|
|
772
|
+
// Custom domain setup (optional)
|
|
773
|
+
console.info()
|
|
774
|
+
showInfo('Custom Domain Setup (Optional)')
|
|
775
|
+
console.info(pc.gray('By default, your app will use:'))
|
|
776
|
+
console.info(pc.gray(` ${deployHost} (cluster subdomain from Uncloud DNS)`))
|
|
777
|
+
console.info()
|
|
778
|
+
console.info(
|
|
779
|
+
pc.gray('You can optionally configure custom domains (e.g., app.yourdomain.com) by:')
|
|
780
|
+
)
|
|
781
|
+
console.info(pc.gray(' 1. Adding CNAME records in your DNS provider'))
|
|
782
|
+
console.info(pc.gray(` 2. Pointing to your cluster subdomain`))
|
|
783
|
+
console.info(pc.gray(' 3. Setting WEB_DOMAIN and ZERO_DOMAIN in .env.production'))
|
|
784
|
+
console.info()
|
|
785
|
+
|
|
786
|
+
const configureCustomDomain = await confirmContinue(
|
|
787
|
+
'Configure custom domain now?',
|
|
788
|
+
false
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
if (configureCustomDomain) {
|
|
792
|
+
console.info()
|
|
793
|
+
console.info(pc.gray('First, run: uc dns reserve'))
|
|
794
|
+
console.info(
|
|
795
|
+
pc.gray('This will give you a cluster subdomain like: abc123.cluster.uncld.dev')
|
|
796
|
+
)
|
|
797
|
+
console.info()
|
|
798
|
+
|
|
799
|
+
const clusterSubdomain = await promptText(
|
|
800
|
+
'Enter your cluster subdomain from uc dns show:',
|
|
801
|
+
'',
|
|
802
|
+
''
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
if (clusterSubdomain) {
|
|
806
|
+
console.info()
|
|
807
|
+
showInfo('DNS Setup Instructions:')
|
|
808
|
+
console.info(
|
|
809
|
+
pc.gray('Add these CNAME records in your DNS provider (e.g., Cloudflare):')
|
|
810
|
+
)
|
|
811
|
+
console.info()
|
|
812
|
+
|
|
813
|
+
const webDomain = await promptText(
|
|
814
|
+
'Custom domain for web app (e.g., app.yourdomain.com):',
|
|
815
|
+
'',
|
|
816
|
+
''
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
const zeroDomain = await promptText(
|
|
820
|
+
'Custom domain for zero sync (e.g., zero.yourdomain.com):',
|
|
821
|
+
'',
|
|
822
|
+
''
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
if (webDomain) {
|
|
826
|
+
console.info()
|
|
827
|
+
console.info(pc.cyan(` CNAME: ${webDomain} → ${clusterSubdomain}`))
|
|
828
|
+
if (zeroDomain) {
|
|
829
|
+
console.info(pc.cyan(` CNAME: ${zeroDomain} → ${clusterSubdomain}`))
|
|
830
|
+
}
|
|
831
|
+
console.info()
|
|
832
|
+
console.info(pc.yellow('⚠️ Set DNS to "DNS only" mode (gray cloud), not proxied'))
|
|
833
|
+
console.info()
|
|
834
|
+
|
|
835
|
+
updateEnvVariable(cwd, 'WEB_DOMAIN', webDomain, envFile)
|
|
836
|
+
updateEnvVariable(cwd, 'BETTER_AUTH_URL', `https://${webDomain}`, envFile)
|
|
837
|
+
updateEnvVariable(cwd, 'ONE_SERVER_URL', `https://${webDomain}`, envFile)
|
|
838
|
+
|
|
839
|
+
if (zeroDomain) {
|
|
840
|
+
updateEnvVariable(cwd, 'ZERO_DOMAIN', zeroDomain, envFile)
|
|
841
|
+
updateEnvVariable(
|
|
842
|
+
cwd,
|
|
843
|
+
'VITE_PUBLIC_ZERO_SERVER',
|
|
844
|
+
`https://${zeroDomain}`,
|
|
845
|
+
envFile
|
|
846
|
+
)
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
showSuccess('✓ Custom domains configured')
|
|
850
|
+
console.info()
|
|
851
|
+
showInfo(pc.gray('DNS propagation typically takes 5-30 minutes'))
|
|
852
|
+
|
|
853
|
+
// offer cloudflare origin ca option
|
|
854
|
+
console.info()
|
|
855
|
+
showInfo('SSL Certificate Options')
|
|
856
|
+
console.info(
|
|
857
|
+
pc.gray("By default, Caddy will use Let's Encrypt for SSL certificates.")
|
|
858
|
+
)
|
|
859
|
+
console.info(pc.gray('If using Cloudflare, you can use Origin CA instead to:'))
|
|
860
|
+
console.info(pc.gray(" • Bypass Let's Encrypt rate limits"))
|
|
861
|
+
console.info(pc.gray(' • Enable Cloudflare proxy (DDoS protection, caching)'))
|
|
862
|
+
console.info()
|
|
863
|
+
|
|
864
|
+
const useOriginCA = await confirmContinue(
|
|
865
|
+
'Use Cloudflare Origin CA? (requires Cloudflare account)',
|
|
866
|
+
false
|
|
867
|
+
)
|
|
868
|
+
|
|
869
|
+
if (useOriginCA) {
|
|
870
|
+
console.info()
|
|
871
|
+
showInfo('Cloudflare Origin CA Setup')
|
|
872
|
+
console.info(pc.gray('1. Go to Cloudflare Dashboard → SSL/TLS → Origin Server'))
|
|
873
|
+
console.info(pc.gray('2. Click "Create Certificate"'))
|
|
874
|
+
console.info(
|
|
875
|
+
pc.gray(
|
|
876
|
+
`3. Add hostnames: ${webDomain}${zeroDomain ? `, ${zeroDomain}` : ''}`
|
|
877
|
+
)
|
|
878
|
+
)
|
|
879
|
+
console.info(pc.gray('4. Choose 15 year validity'))
|
|
880
|
+
console.info(pc.gray('5. Save certificate to: certs/origin.pem'))
|
|
881
|
+
console.info(pc.gray('6. Save private key to: certs/origin.key'))
|
|
882
|
+
console.info()
|
|
883
|
+
|
|
884
|
+
const certPath = await promptText(
|
|
885
|
+
'Path to Origin CA certificate:',
|
|
886
|
+
'certs/origin.pem',
|
|
887
|
+
'certs/origin.pem'
|
|
888
|
+
)
|
|
889
|
+
|
|
890
|
+
const keyPath = await promptText(
|
|
891
|
+
'Path to Origin CA private key:',
|
|
892
|
+
'certs/origin.key',
|
|
893
|
+
'certs/origin.key'
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
if (certPath && keyPath) {
|
|
897
|
+
updateEnvVariable(cwd, 'ORIGIN_CA_CERT', certPath, envFile)
|
|
898
|
+
updateEnvVariable(cwd, 'ORIGIN_CA_KEY', keyPath, envFile)
|
|
899
|
+
|
|
900
|
+
showSuccess('✓ Origin CA configured')
|
|
901
|
+
console.info(pc.gray(' Caddyfile will be auto-generated during deploy'))
|
|
902
|
+
console.info()
|
|
903
|
+
console.info(pc.yellow('Important: In Cloudflare Dashboard:'))
|
|
904
|
+
console.info(pc.yellow(' 1. Enable proxy (orange cloud) for your domains'))
|
|
905
|
+
console.info(pc.yellow(' 2. Set SSL mode to "Full (strict)"'))
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// update package.json env section to remove sst-specific vars
|
|
913
|
+
console.info()
|
|
914
|
+
showInfo('Updating package.json env section for Uncloud deployment...')
|
|
915
|
+
const envUpdateResult = updatePackageJsonEnv(cwd, 'uncloud')
|
|
916
|
+
if (envUpdateResult.success) {
|
|
917
|
+
showSuccess('✓ Removed SST-specific environment variables from package.json')
|
|
918
|
+
} else {
|
|
919
|
+
showWarning(`Failed to update package.json env: ${envUpdateResult.error}`)
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// run env:update to sync to GitHub workflow
|
|
923
|
+
console.info()
|
|
924
|
+
showInfo('Updating GitHub workflow with environment variables...')
|
|
925
|
+
try {
|
|
926
|
+
execSync('bun env:update', { cwd, stdio: 'ignore' })
|
|
927
|
+
showSuccess('✓ GitHub workflow updated')
|
|
928
|
+
} catch (error) {
|
|
929
|
+
showWarning('Failed to update GitHub workflow (non-critical)')
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// offer to sync to github
|
|
933
|
+
console.info()
|
|
934
|
+
const syncToGitHub = await confirmContinue(
|
|
935
|
+
'Sync environment to GitHub secrets for CI/CD?',
|
|
936
|
+
true
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
if (syncToGitHub) {
|
|
940
|
+
try {
|
|
941
|
+
execSync('bun scripts/env/sync-to-github.ts --yes', { cwd, stdio: 'inherit' })
|
|
942
|
+
} catch (error) {
|
|
943
|
+
showWarning('Failed to sync to GitHub (you can do this later)')
|
|
944
|
+
showInfo('Run manually: bun scripts/env/sync-to-github.ts')
|
|
945
|
+
}
|
|
946
|
+
} else {
|
|
947
|
+
showInfo('You can sync later with: bun scripts/env/sync-to-github.ts')
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Next steps
|
|
951
|
+
console.info()
|
|
952
|
+
showStep('Ready to deploy!')
|
|
953
|
+
console.info()
|
|
954
|
+
console.info(pc.bold('Next steps:'))
|
|
955
|
+
console.info()
|
|
956
|
+
console.info(pc.cyan('1. Install Uncloud CLI (if not already installed):'))
|
|
957
|
+
console.info(pc.gray(' npm install -g uncloud-cli'))
|
|
958
|
+
console.info()
|
|
959
|
+
console.info(pc.cyan('2. Login to Uncloud:'))
|
|
960
|
+
console.info(pc.gray(' uncloud login'))
|
|
961
|
+
console.info()
|
|
962
|
+
console.info(pc.cyan('3. Deploy your app:'))
|
|
963
|
+
console.info(pc.gray(' bun tko uncloud deploy-prod'))
|
|
964
|
+
console.info()
|
|
965
|
+
console.info(pc.cyan('Or push to main branch for automatic CI/CD deployment:'))
|
|
966
|
+
console.info(pc.gray(' git push origin main'))
|
|
967
|
+
console.info()
|
|
968
|
+
console.info(pc.bold('scaling to multiple machines:'))
|
|
969
|
+
console.info(pc.gray(' uc machine add --name server-2 root@IP'))
|
|
970
|
+
console.info(pc.gray(' uc scale web 3 # run 3 instances'))
|
|
971
|
+
console.info()
|
|
972
|
+
console.info(pc.gray('view detailed guide: bun tko docs read deployment-uncloud'))
|
|
973
|
+
console.info(pc.gray('or see: docs/deployment-uncloud.md'))
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
async function setupSSTDeployment(cwd: string): Promise<void> {
|
|
977
|
+
showInfo('Setting up AWS SST deployment')
|
|
978
|
+
console.info()
|
|
979
|
+
showWarning('AWS setup takes approximately 30 minutes')
|
|
980
|
+
console.info()
|
|
981
|
+
console.info(
|
|
982
|
+
pc.gray(
|
|
983
|
+
'SST provides:\n • AWS infrastructure (ECS, Aurora, ALB)\n • Auto-scaling\n • Full control over resources\n • Higher setup complexity'
|
|
984
|
+
)
|
|
985
|
+
)
|
|
986
|
+
console.info()
|
|
987
|
+
|
|
988
|
+
// ask about architecture (ARM is cheaper for AWS)
|
|
989
|
+
console.info()
|
|
990
|
+
showInfo('AWS ECS Architecture')
|
|
991
|
+
console.info(pc.gray('AWS Graviton (ARM64) is ~40% cheaper than x86_64'))
|
|
992
|
+
console.info(
|
|
993
|
+
pc.gray('Both have excellent performance, ARM recommended for cost savings')
|
|
994
|
+
)
|
|
995
|
+
console.info()
|
|
996
|
+
|
|
997
|
+
const architecture = await promptSelect<'amd64' | 'arm64'>(
|
|
998
|
+
'What CPU architecture for AWS ECS?',
|
|
999
|
+
[
|
|
1000
|
+
{
|
|
1001
|
+
value: 'arm64',
|
|
1002
|
+
label: 'ARM64 (Graviton) - Recommended',
|
|
1003
|
+
hint: 'Significantly cheaper, excellent performance',
|
|
1004
|
+
},
|
|
1005
|
+
{
|
|
1006
|
+
value: 'amd64',
|
|
1007
|
+
label: 'AMD64/x86_64 (Intel/AMD)',
|
|
1008
|
+
hint: 'Standard option if you need specific x86 dependencies',
|
|
1009
|
+
},
|
|
1010
|
+
]
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
if (architecture === 'cancel') {
|
|
1014
|
+
showInfo('Skipping AWS setup')
|
|
1015
|
+
return
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
const deploymentArch = architecture === 'arm64' ? 'linux/arm64' : 'linux/amd64'
|
|
1019
|
+
console.info()
|
|
1020
|
+
showInfo(`Will build Docker images for: ${deploymentArch}`)
|
|
1021
|
+
console.info()
|
|
1022
|
+
|
|
1023
|
+
const shouldContinue = await confirmContinue(
|
|
1024
|
+
'Continue with AWS SST setup? (requires AWS account)',
|
|
1025
|
+
false
|
|
1026
|
+
)
|
|
1027
|
+
|
|
1028
|
+
if (!shouldContinue) {
|
|
1029
|
+
showInfo('Skipping AWS setup')
|
|
1030
|
+
showInfo('You can set up AWS later with: bun tko env:setup --category aws')
|
|
1031
|
+
return
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// Run the existing production env setup with AWS category
|
|
1035
|
+
await setupProductionEnv(cwd, {
|
|
1036
|
+
onlyCategory: 'aws',
|
|
1037
|
+
envFile: '.env.production',
|
|
1038
|
+
interactive: true,
|
|
1039
|
+
})
|
|
1040
|
+
|
|
1041
|
+
// Save deployment platform
|
|
1042
|
+
const envFile = '.env.production'
|
|
1043
|
+
updateEnvVariable(cwd, 'DEPLOYMENT_PLATFORM', 'sst', envFile)
|
|
1044
|
+
updateEnvVariable(cwd, 'DEPLOYMENT_ARCH', deploymentArch, envFile)
|
|
1045
|
+
|
|
1046
|
+
// update sst.config.ts architecture
|
|
1047
|
+
console.info()
|
|
1048
|
+
showInfo('Updating sst.config.ts architecture...')
|
|
1049
|
+
try {
|
|
1050
|
+
const sstConfigPath = resolve(cwd, 'sst.config.ts')
|
|
1051
|
+
let sstConfig = readFileSync(sstConfigPath, 'utf-8')
|
|
1052
|
+
|
|
1053
|
+
// replace architecture in all services
|
|
1054
|
+
const archValue = architecture === 'arm64' ? 'arm64' : 'x86_64'
|
|
1055
|
+
sstConfig = sstConfig.replace(
|
|
1056
|
+
/architecture:\s*['"]arm64['"]/g,
|
|
1057
|
+
`architecture: '${archValue}'`
|
|
1058
|
+
)
|
|
1059
|
+
sstConfig = sstConfig.replace(
|
|
1060
|
+
/architecture:\s*['"]x86_64['"]/g,
|
|
1061
|
+
`architecture: '${archValue}'`
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
writeFileSync(sstConfigPath, sstConfig)
|
|
1065
|
+
showSuccess(`✓ Updated sst.config.ts to use ${archValue}`)
|
|
1066
|
+
} catch (error) {
|
|
1067
|
+
showWarning('Could not update sst.config.ts (you can update manually)')
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// update package.json env section to remove uncloud-specific vars
|
|
1071
|
+
console.info()
|
|
1072
|
+
showInfo('Updating package.json env section for SST deployment...')
|
|
1073
|
+
const envUpdateResult = updatePackageJsonEnv(cwd, 'sst')
|
|
1074
|
+
if (envUpdateResult.success) {
|
|
1075
|
+
showSuccess('✓ Removed Uncloud-specific environment variables from package.json')
|
|
1076
|
+
} else {
|
|
1077
|
+
showWarning(`Failed to update package.json env: ${envUpdateResult.error}`)
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// run env:update to sync to GitHub workflow
|
|
1081
|
+
console.info()
|
|
1082
|
+
showInfo('Updating GitHub workflow with environment variables...')
|
|
1083
|
+
try {
|
|
1084
|
+
execSync('bun env:update', { cwd, stdio: 'ignore' })
|
|
1085
|
+
showSuccess('✓ GitHub workflow updated')
|
|
1086
|
+
} catch (error) {
|
|
1087
|
+
showWarning('Failed to update GitHub workflow (non-critical)')
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// offer to sync to github
|
|
1091
|
+
console.info()
|
|
1092
|
+
const syncToGitHub = await confirmContinue(
|
|
1093
|
+
'Sync environment to GitHub secrets for CI/CD?',
|
|
1094
|
+
true
|
|
1095
|
+
)
|
|
1096
|
+
|
|
1097
|
+
if (syncToGitHub) {
|
|
1098
|
+
try {
|
|
1099
|
+
execSync('bun scripts/env/sync-to-github.ts --yes', { cwd, stdio: 'inherit' })
|
|
1100
|
+
} catch (error) {
|
|
1101
|
+
showWarning('Failed to sync to GitHub (you can do this later)')
|
|
1102
|
+
showInfo('Run manually: bun scripts/env/sync-to-github.ts')
|
|
1103
|
+
}
|
|
1104
|
+
} else {
|
|
1105
|
+
showInfo('You can sync later with: bun scripts/env/sync-to-github.ts')
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
console.info()
|
|
1109
|
+
showInfo(
|
|
1110
|
+
'For complete AWS deployment guide, see: https://docs.yourapp.com/deployment/sst'
|
|
1111
|
+
)
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
async function configureCIRunners(cwd: string): Promise<void> {
|
|
1115
|
+
showInfo('GitHub Actions CI/CD Runner Configuration')
|
|
1116
|
+
console.info()
|
|
1117
|
+
console.info(
|
|
1118
|
+
pc.gray(
|
|
1119
|
+
'Your project uses ARM64 (Apple Silicon) architecture for Docker builds.\n' +
|
|
1120
|
+
'GitHub Actions requires compatible runners for CI/CD to work properly.'
|
|
1121
|
+
)
|
|
1122
|
+
)
|
|
1123
|
+
console.info()
|
|
1124
|
+
|
|
1125
|
+
const runnerChoice = await promptSelect<'warp' | 'github-arm' | 'github-x64' | 'skip'>(
|
|
1126
|
+
'Choose your CI runner configuration:',
|
|
1127
|
+
[
|
|
1128
|
+
{
|
|
1129
|
+
value: 'warp',
|
|
1130
|
+
label: 'Warp Runners (Recommended)',
|
|
1131
|
+
hint: 'Fast ARM64 runners, cheaper than GitHub ($0.005/min)',
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
value: 'github-arm',
|
|
1135
|
+
label: 'GitHub ARM Runners',
|
|
1136
|
+
hint: 'Native ARM64, requires GitHub Teams/Enterprise ($0.16/min)',
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
value: 'github-x64',
|
|
1140
|
+
label: 'GitHub x64 Runners (Free)',
|
|
1141
|
+
hint: 'Requires changing Docker builds to x64 architecture',
|
|
1142
|
+
},
|
|
1143
|
+
{
|
|
1144
|
+
value: 'skip',
|
|
1145
|
+
label: 'Configure Later',
|
|
1146
|
+
hint: 'Skip for now (CI will fail until configured)',
|
|
1147
|
+
},
|
|
1148
|
+
]
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
if (runnerChoice === 'cancel' || runnerChoice === 'skip') {
|
|
1152
|
+
showInfo('Skipping CI runner configuration')
|
|
1153
|
+
showWarning(
|
|
1154
|
+
'CI/CD will fail until you configure runners. Update .github/workflows/ci.yml'
|
|
1155
|
+
)
|
|
1156
|
+
return
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
const ciConfigPath = resolve(cwd, '.github/workflows/ci.yml')
|
|
1160
|
+
const dockerBuildPath = resolve(cwd, 'scripts/web/build-docker.ts')
|
|
1161
|
+
|
|
1162
|
+
try {
|
|
1163
|
+
let ciContent = readFileSync(ciConfigPath, 'utf-8')
|
|
1164
|
+
let dockerContent = readFileSync(dockerBuildPath, 'utf-8')
|
|
1165
|
+
|
|
1166
|
+
if (runnerChoice === 'warp') {
|
|
1167
|
+
console.info()
|
|
1168
|
+
showStep('Setting up Warp runners')
|
|
1169
|
+
console.info()
|
|
1170
|
+
console.info(pc.cyan('1. Sign up for Warp Build (if not already):'))
|
|
1171
|
+
console.info(pc.gray(' https://www.warpbuild.com'))
|
|
1172
|
+
console.info()
|
|
1173
|
+
console.info(pc.cyan('2. Install Warp GitHub App:'))
|
|
1174
|
+
console.info(pc.gray(' https://github.com/apps/warp-build'))
|
|
1175
|
+
console.info()
|
|
1176
|
+
console.info(pc.cyan('3. Grant access to your repository'))
|
|
1177
|
+
console.info()
|
|
1178
|
+
|
|
1179
|
+
// CI config is already set for Warp, just confirm
|
|
1180
|
+
showSuccess('✓ CI configuration already set for Warp runners')
|
|
1181
|
+
showInfo('Warp uses ARM64 runners matching your local architecture')
|
|
1182
|
+
} else if (runnerChoice === 'github-arm') {
|
|
1183
|
+
// Update CI to use GitHub ARM runners
|
|
1184
|
+
ciContent = ciContent.replace(
|
|
1185
|
+
/runs-on:.*warp-ubuntu-latest-arm64.*/,
|
|
1186
|
+
'runs-on: ubuntu-24.04-arm'
|
|
1187
|
+
)
|
|
1188
|
+
writeFileSync(ciConfigPath, ciContent)
|
|
1189
|
+
|
|
1190
|
+
console.info()
|
|
1191
|
+
showSuccess('✓ Updated CI to use GitHub ARM runners')
|
|
1192
|
+
showWarning('Note: GitHub ARM runners require Teams or Enterprise plan')
|
|
1193
|
+
showInfo('Pricing: $0.16/minute for ARM runners')
|
|
1194
|
+
} else if (runnerChoice === 'github-x64') {
|
|
1195
|
+
// Update CI to use standard GitHub runners
|
|
1196
|
+
ciContent = ciContent.replace(
|
|
1197
|
+
/runs-on:.*warp-ubuntu-latest-arm64.*/,
|
|
1198
|
+
'runs-on: ubuntu-latest'
|
|
1199
|
+
)
|
|
1200
|
+
writeFileSync(ciConfigPath, ciContent)
|
|
1201
|
+
|
|
1202
|
+
// Update Docker build script to use x64
|
|
1203
|
+
dockerContent = dockerContent.replace('linux/arm64', 'linux/amd64')
|
|
1204
|
+
writeFileSync(dockerBuildPath, dockerContent)
|
|
1205
|
+
|
|
1206
|
+
console.info()
|
|
1207
|
+
showSuccess('✓ Updated CI to use free GitHub x64 runners')
|
|
1208
|
+
showSuccess('✓ Updated Docker builds to x64 architecture')
|
|
1209
|
+
showWarning(
|
|
1210
|
+
"Note: Docker images built on x64 won't run on ARM64 machines without emulation"
|
|
1211
|
+
)
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
console.info()
|
|
1215
|
+
showInfo('CI runner configuration complete')
|
|
1216
|
+
} catch (error) {
|
|
1217
|
+
showError('Failed to update CI configuration')
|
|
1218
|
+
showInfo(
|
|
1219
|
+
'Please manually update .github/workflows/ci.yml and scripts/web/build-docker.ts'
|
|
1220
|
+
)
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
async function startServices(cwd: string): Promise<void> {
|
|
1225
|
+
const spinner = showSpinner('Starting Docker services...')
|
|
1226
|
+
|
|
1227
|
+
try {
|
|
1228
|
+
// Start services in background
|
|
1229
|
+
execSync('bun backend', {
|
|
1230
|
+
stdio: 'ignore',
|
|
1231
|
+
cwd,
|
|
1232
|
+
})
|
|
1233
|
+
|
|
1234
|
+
// Wait a bit for services to start
|
|
1235
|
+
await new Promise((resolve) => setTimeout(resolve, 3000))
|
|
1236
|
+
|
|
1237
|
+
spinner.stop('Docker services started')
|
|
1238
|
+
showSuccess('✓ PostgreSQL running on port 5432')
|
|
1239
|
+
showSuccess('✓ Zero sync running on port 4848')
|
|
1240
|
+
showSuccess('✓ MinIO (S3) running on port 9090')
|
|
1241
|
+
|
|
1242
|
+
// Run migrations
|
|
1243
|
+
const shouldMigrate = await confirmContinue('Run database migrations?', true)
|
|
1244
|
+
|
|
1245
|
+
if (shouldMigrate) {
|
|
1246
|
+
const migrateSpinner = showSpinner('Running migrations...')
|
|
1247
|
+
try {
|
|
1248
|
+
execSync('bun migrate', { stdio: 'ignore', cwd })
|
|
1249
|
+
migrateSpinner.stop('Database migrated')
|
|
1250
|
+
showSuccess('✓ Database migrations complete')
|
|
1251
|
+
} catch {
|
|
1252
|
+
migrateSpinner.stop('Migration failed')
|
|
1253
|
+
showError('Failed to run migrations')
|
|
1254
|
+
showInfo("Try running 'bun migrate' manually")
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
} catch (error) {
|
|
1258
|
+
spinner.stop('Failed to start services')
|
|
1259
|
+
showError(error instanceof Error ? error.message : 'Unknown error')
|
|
1260
|
+
showInfo("Try running 'bun backend' manually")
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* Replace port throughout project files (cross-platform)
|
|
1266
|
+
*/
|
|
1267
|
+
async function replacePortInProject(
|
|
1268
|
+
cwd: string,
|
|
1269
|
+
oldPort: string,
|
|
1270
|
+
newPort: string
|
|
1271
|
+
): Promise<void> {
|
|
1272
|
+
const spinner = showSpinner(
|
|
1273
|
+
`Replacing port ${oldPort} with ${newPort} throughout project...`
|
|
1274
|
+
)
|
|
1275
|
+
|
|
1276
|
+
// directories to skip
|
|
1277
|
+
const excludeDirs = new Set([
|
|
1278
|
+
'node_modules',
|
|
1279
|
+
'.git',
|
|
1280
|
+
'dist',
|
|
1281
|
+
'build',
|
|
1282
|
+
'.next',
|
|
1283
|
+
'.turbo',
|
|
1284
|
+
'types',
|
|
1285
|
+
])
|
|
1286
|
+
|
|
1287
|
+
// file extensions to process
|
|
1288
|
+
const includeExts = new Set([
|
|
1289
|
+
'.ts',
|
|
1290
|
+
'.tsx',
|
|
1291
|
+
'.js',
|
|
1292
|
+
'.jsx',
|
|
1293
|
+
'.json',
|
|
1294
|
+
'.md',
|
|
1295
|
+
'.yml',
|
|
1296
|
+
'.yaml',
|
|
1297
|
+
])
|
|
1298
|
+
|
|
1299
|
+
// recursively find and replace in files
|
|
1300
|
+
async function processDir(dir: string): Promise<void> {
|
|
1301
|
+
const { readdirSync, statSync } = await import('node:fs')
|
|
1302
|
+
const { join, extname, basename } = await import('node:path')
|
|
1303
|
+
|
|
1304
|
+
const entries = readdirSync(dir)
|
|
1305
|
+
|
|
1306
|
+
for (const entry of entries) {
|
|
1307
|
+
const fullPath = join(dir, entry)
|
|
1308
|
+
|
|
1309
|
+
try {
|
|
1310
|
+
const stat = statSync(fullPath)
|
|
1311
|
+
|
|
1312
|
+
if (stat.isDirectory()) {
|
|
1313
|
+
if (!excludeDirs.has(entry)) {
|
|
1314
|
+
await processDir(fullPath)
|
|
1315
|
+
}
|
|
1316
|
+
} else if (stat.isFile()) {
|
|
1317
|
+
const ext = extname(entry)
|
|
1318
|
+
const name = basename(entry)
|
|
1319
|
+
// check extension or .env* pattern
|
|
1320
|
+
if (includeExts.has(ext) || name.startsWith('.env')) {
|
|
1321
|
+
const content = readFileSync(fullPath, 'utf-8')
|
|
1322
|
+
if (content.includes(oldPort)) {
|
|
1323
|
+
const newContent = content.split(oldPort).join(newPort)
|
|
1324
|
+
writeFileSync(fullPath, newContent, 'utf-8')
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
} catch {
|
|
1329
|
+
// skip files we can't read
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
try {
|
|
1335
|
+
await processDir(cwd)
|
|
1336
|
+
spinner.stop(`Port updated from ${oldPort} to ${newPort}`)
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
spinner.stop('Port replacement failed')
|
|
1339
|
+
showError('Failed to replace port. You may need to update manually.')
|
|
1340
|
+
throw error
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// State management helpers
|
|
1345
|
+
interface OnboardState {
|
|
1346
|
+
step: 'prerequisites' | 'identity' | 'ports' | 'eject' | 'ci-runners' | 'production'
|
|
1347
|
+
platform?: 'uncloud' | 'sst'
|
|
1348
|
+
timestamp: number
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function getStatePath(cwd: string): string {
|
|
1352
|
+
return resolve(cwd, 'node_modules/.takeout/onboard-state.json')
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function saveOnboardState(cwd: string, state: OnboardState): void {
|
|
1356
|
+
const statePath = getStatePath(cwd)
|
|
1357
|
+
const stateDir = resolve(cwd, 'node_modules/.takeout')
|
|
1358
|
+
|
|
1359
|
+
if (!existsSync(stateDir)) {
|
|
1360
|
+
mkdirSync(stateDir, { recursive: true })
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2))
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function loadOnboardState(cwd: string): OnboardState | null {
|
|
1367
|
+
const statePath = getStatePath(cwd)
|
|
1368
|
+
|
|
1369
|
+
if (!existsSync(statePath)) {
|
|
1370
|
+
return null
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
try {
|
|
1374
|
+
const content = readFileSync(statePath, 'utf-8')
|
|
1375
|
+
return JSON.parse(content)
|
|
1376
|
+
} catch {
|
|
1377
|
+
return null
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
function clearOnboardState(cwd: string): void {
|
|
1382
|
+
const statePath = getStatePath(cwd)
|
|
1383
|
+
|
|
1384
|
+
if (existsSync(statePath)) {
|
|
1385
|
+
try {
|
|
1386
|
+
unlinkSync(statePath)
|
|
1387
|
+
} catch {
|
|
1388
|
+
// ignore errors
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|