@take-out/cli 0.4.3 → 0.4.5

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.
Files changed (69) hide show
  1. package/dist/cjs/cli.cjs +53 -35
  2. package/dist/cjs/commands/changed.cjs +133 -95
  3. package/dist/cjs/commands/docs.cjs +276 -203
  4. package/dist/cjs/commands/env-setup.cjs +45 -33
  5. package/dist/cjs/commands/onboard.cjs +668 -224
  6. package/dist/cjs/commands/run-all.cjs +54 -45
  7. package/dist/cjs/commands/run.cjs +80 -65
  8. package/dist/cjs/commands/script.cjs +263 -187
  9. package/dist/cjs/commands/skills.cjs +174 -118
  10. package/dist/cjs/commands/sync.cjs +193 -93
  11. package/dist/cjs/constants/ascii.cjs +14 -12
  12. package/dist/cjs/index.cjs +12 -10
  13. package/dist/cjs/types.cjs +7 -5
  14. package/dist/cjs/utils/env-categories.cjs +53 -48
  15. package/dist/cjs/utils/env-setup.cjs +214 -106
  16. package/dist/cjs/utils/env.cjs +65 -44
  17. package/dist/cjs/utils/files.cjs +186 -113
  18. package/dist/cjs/utils/parallel-runner.cjs +125 -75
  19. package/dist/cjs/utils/ports.cjs +21 -21
  20. package/dist/cjs/utils/prerequisites.cjs +42 -34
  21. package/dist/cjs/utils/prompts.cjs +66 -41
  22. package/dist/cjs/utils/script-listing.cjs +130 -60
  23. package/dist/cjs/utils/script-utils.cjs +29 -19
  24. package/dist/cjs/utils/sync.cjs +67 -45
  25. package/dist/esm/cli.mjs +40 -24
  26. package/dist/esm/cli.mjs.map +1 -1
  27. package/dist/esm/commands/changed.mjs +104 -68
  28. package/dist/esm/commands/changed.mjs.map +1 -1
  29. package/dist/esm/commands/docs.mjs +245 -174
  30. package/dist/esm/commands/docs.mjs.map +1 -1
  31. package/dist/esm/commands/env-setup.mjs +18 -8
  32. package/dist/esm/commands/env-setup.mjs.map +1 -1
  33. package/dist/esm/commands/onboard.mjs +631 -189
  34. package/dist/esm/commands/onboard.mjs.map +1 -1
  35. package/dist/esm/commands/run-all.mjs +26 -19
  36. package/dist/esm/commands/run-all.mjs.map +1 -1
  37. package/dist/esm/commands/run.mjs +52 -39
  38. package/dist/esm/commands/run.mjs.map +1 -1
  39. package/dist/esm/commands/script.mjs +230 -156
  40. package/dist/esm/commands/script.mjs.map +1 -1
  41. package/dist/esm/commands/skills.mjs +143 -89
  42. package/dist/esm/commands/skills.mjs.map +1 -1
  43. package/dist/esm/commands/sync.mjs +160 -62
  44. package/dist/esm/commands/sync.mjs.map +1 -1
  45. package/dist/esm/constants/ascii.mjs +2 -2
  46. package/dist/esm/constants/ascii.mjs.map +1 -1
  47. package/dist/esm/utils/env-categories.mjs +29 -26
  48. package/dist/esm/utils/env-categories.mjs.map +1 -1
  49. package/dist/esm/utils/env-setup.mjs +184 -78
  50. package/dist/esm/utils/env-setup.mjs.map +1 -1
  51. package/dist/esm/utils/env.mjs +50 -31
  52. package/dist/esm/utils/env.mjs.map +1 -1
  53. package/dist/esm/utils/files.mjs +171 -100
  54. package/dist/esm/utils/files.mjs.map +1 -1
  55. package/dist/esm/utils/parallel-runner.mjs +111 -63
  56. package/dist/esm/utils/parallel-runner.mjs.map +1 -1
  57. package/dist/esm/utils/ports.mjs +9 -11
  58. package/dist/esm/utils/ports.mjs.map +1 -1
  59. package/dist/esm/utils/prerequisites.mjs +30 -24
  60. package/dist/esm/utils/prerequisites.mjs.map +1 -1
  61. package/dist/esm/utils/prompts.mjs +40 -17
  62. package/dist/esm/utils/prompts.mjs.map +1 -1
  63. package/dist/esm/utils/script-listing.mjs +101 -33
  64. package/dist/esm/utils/script-listing.mjs.map +1 -1
  65. package/dist/esm/utils/script-utils.mjs +14 -6
  66. package/dist/esm/utils/script-utils.mjs.map +1 -1
  67. package/dist/esm/utils/sync.mjs +38 -18
  68. package/dist/esm/utils/sync.mjs.map +1 -1
  69. package/package.json +5 -5
@@ -20,12 +20,12 @@ const onboardCommand = defineCommand({
20
20
  skip: {
21
21
  type: "boolean",
22
22
  description: "Skip onboarding entirely",
23
- default: !1
23
+ default: false
24
24
  },
25
25
  defaults: {
26
26
  type: "boolean",
27
27
  description: "Run with defaults (non-interactive)",
28
- default: !1
28
+ default: false
29
29
  }
30
30
  },
31
31
  async run({
@@ -37,147 +37,348 @@ const onboardCommand = defineCommand({
37
37
  return;
38
38
  }
39
39
  if (args.defaults) {
40
- if (showInfo("Running onboarding with defaults (--defaults flag)"), console.info(), envFileExists(cwd, ".env")) showInfo(".env already exists, skipping");else {
40
+ showInfo("Running onboarding with defaults (--defaults flag)");
41
+ console.info();
42
+ const hasEnv = envFileExists(cwd, ".env");
43
+ if (!hasEnv) {
41
44
  const copyResult = copyEnvFile(cwd, ".env.development", ".env");
42
- if (copyResult.success) showSuccess("Created .env from .env.development");else {
45
+ if (copyResult.success) {
46
+ showSuccess("Created .env from .env.development");
47
+ } else {
43
48
  showError(`Failed to create .env: ${copyResult.error}`);
44
49
  return;
45
50
  }
46
- createEnvLocal(cwd), showSuccess("Created .env.local for personal overrides");
51
+ createEnvLocal(cwd);
52
+ showSuccess("Created .env.local for personal overrides");
53
+ } else {
54
+ showInfo(".env already exists, skipping");
47
55
  }
48
- markOnboarded(cwd), showSuccess("Onboarding complete with defaults"), console.info(), showInfo("Next steps:"), console.info(" bun backend # start docker services"), console.info(" bun dev # start web dev server");
56
+ markOnboarded(cwd);
57
+ showSuccess("Onboarding complete with defaults");
58
+ console.info();
59
+ showInfo("Next steps:");
60
+ console.info(" bun backend # start docker services");
61
+ console.info(" bun dev # start web dev server");
49
62
  return;
50
63
  }
51
64
  displayWelcome();
52
65
  const savedState = loadOnboardState(cwd);
53
66
  let startStep;
54
- if (savedState ? (console.info(), showInfo(`Found incomplete setup from previous session (${savedState.step})`), (await confirmContinue("Resume from where you left off?", !0)) ? startStep = savedState.step : (clearOnboardState(cwd), startStep = await promptStartStep())) : startStep = await promptStartStep(), startStep === "cancel") {
67
+ if (savedState) {
68
+ console.info();
69
+ showInfo(`Found incomplete setup from previous session (${savedState.step})`);
70
+ const shouldResume = await confirmContinue("Resume from where you left off?", true);
71
+ if (shouldResume) {
72
+ startStep = savedState.step;
73
+ } else {
74
+ clearOnboardState(cwd);
75
+ startStep = await promptStartStep();
76
+ }
77
+ } else {
78
+ startStep = await promptStartStep();
79
+ }
80
+ if (startStep === "cancel") {
55
81
  displayOutro("Setup cancelled");
56
82
  return;
57
83
  }
58
84
  if (startStep === "eject") {
59
- if (showStep("Monorepo Ejection"), console.info(), console.info(pc.gray("We've included a variety of packages we found useful building apps with this stack:")), console.info(pc.gray(" \u2022 @take-out/cli - CLI tools and onboarding")), console.info(pc.gray(" \u2022 @take-out/helpers - Utility functions")), console.info(pc.gray(" \u2022 @take-out/hooks - React hooks")), console.info(pc.gray(" \u2022 @take-out/database - Database utilities")), console.info(pc.gray(" \u2022 @take-out/scripts - Build and dev scripts")), console.info(pc.gray(" \u2022 @take-out/better-auth-utils - Auth helpers")), console.info(), await confirmContinue("Eject from monorepo setup? (removes ./packages, uses published versions)", !0)) {
85
+ showStep("Monorepo Ejection");
86
+ console.info();
87
+ console.info(pc.gray("We've included a variety of packages we found useful building apps with this stack:"));
88
+ console.info(pc.gray(" \u2022 @take-out/cli - CLI tools and onboarding"));
89
+ console.info(pc.gray(" \u2022 @take-out/helpers - Utility functions"));
90
+ console.info(pc.gray(" \u2022 @take-out/hooks - React hooks"));
91
+ console.info(pc.gray(" \u2022 @take-out/database - Database utilities"));
92
+ console.info(pc.gray(" \u2022 @take-out/scripts - Build and dev scripts"));
93
+ console.info(pc.gray(" \u2022 @take-out/better-auth-utils - Auth helpers"));
94
+ console.info();
95
+ const shouldEject = await confirmContinue("Eject from monorepo setup? (removes ./packages, uses published versions)", true);
96
+ if (shouldEject) {
60
97
  const dryResult = await ejectFromMonorepo(cwd, {
61
- dryRun: !0
98
+ dryRun: true
62
99
  });
63
100
  if (!dryResult.success) {
64
- showError(`Cannot eject: ${dryResult.error}`), clearOnboardState(cwd);
101
+ showError(`Cannot eject: ${dryResult.error}`);
102
+ clearOnboardState(cwd);
65
103
  return;
66
104
  }
67
- console.info(), showInfo(`Found ${dryResult.packages?.length} packages to convert:`);
68
- for (const pkg of dryResult.packages || []) console.info(pc.gray(` \u2022 ${pkg.name}@${pkg.version}`));
69
- if (dryResult.warnings?.length) for (const warn of dryResult.warnings) showWarning(warn);
70
- if (console.info(), !(await confirmContinue("Proceed with ejection?", !0))) {
71
- showInfo("Eject cancelled"), clearOnboardState(cwd), displayOutro("Done!");
105
+ console.info();
106
+ showInfo(`Found ${dryResult.packages?.length} packages to convert:`);
107
+ for (const pkg of dryResult.packages || []) {
108
+ console.info(pc.gray(` \u2022 ${pkg.name}@${pkg.version}`));
109
+ }
110
+ if (dryResult.warnings?.length) {
111
+ for (const warn of dryResult.warnings) {
112
+ showWarning(warn);
113
+ }
114
+ }
115
+ console.info();
116
+ const confirmEject = await confirmContinue("Proceed with ejection?", true);
117
+ if (!confirmEject) {
118
+ showInfo("Eject cancelled");
119
+ clearOnboardState(cwd);
120
+ displayOutro("Done!");
72
121
  return;
73
122
  }
74
- const spinner = showSpinner("Ejecting from monorepo..."),
75
- result = await ejectFromMonorepo(cwd);
123
+ const spinner = showSpinner("Ejecting from monorepo...");
124
+ const result = await ejectFromMonorepo(cwd);
76
125
  if (result.success) {
77
- spinner.stop("Ejected from monorepo"), showSuccess("\u2713 Removed ./packages directory"), showSuccess("\u2713 Updated package.json to use published versions"), showSuccess("\u2713 Installed published packages");
126
+ spinner.stop("Ejected from monorepo");
127
+ showSuccess("\u2713 Removed ./packages directory");
128
+ showSuccess("\u2713 Updated package.json to use published versions");
129
+ showSuccess("\u2713 Installed published packages");
78
130
  try {
79
131
  execSync('git add -A && git commit -m "ejected from monorepo"', {
80
132
  cwd,
81
133
  stdio: "ignore"
82
- }), showSuccess("\u2713 Committed eject changes");
134
+ });
135
+ showSuccess("\u2713 Committed eject changes");
83
136
  } catch {
84
137
  showWarning("Git commit skipped (not a git repo or no changes)");
85
138
  }
86
- console.info(), showInfo("You can now upgrade packages with: bun up takeout");
87
- } else spinner.stop("Ejection failed"), showError(`Failed to eject: ${result.error}`), result.error?.includes("install failed") && (showInfo('You may be able to fix this by running "bun install" manually'), showInfo('Or restore the repo from git with "git checkout ."'));
88
- } else showInfo("Eject cancelled");
89
- clearOnboardState(cwd), displayOutro("Done!");
139
+ console.info();
140
+ showInfo("You can now upgrade packages with: bun up takeout");
141
+ } else {
142
+ spinner.stop("Ejection failed");
143
+ showError(`Failed to eject: ${result.error}`);
144
+ if (result.error?.includes("install failed")) {
145
+ showInfo('You may be able to fix this by running "bun install" manually');
146
+ showInfo('Or restore the repo from git with "git checkout ."');
147
+ }
148
+ }
149
+ } else {
150
+ showInfo("Eject cancelled");
151
+ }
152
+ clearOnboardState(cwd);
153
+ displayOutro("Done!");
90
154
  return;
91
155
  }
92
156
  if (startStep === "prerequisites" || startStep === "full") {
93
157
  saveOnboardState(cwd, {
94
158
  step: "prerequisites",
95
159
  timestamp: Date.now()
96
- }), showStep("Checking prerequisites..."), console.info();
160
+ });
161
+ showStep("Checking prerequisites...");
162
+ console.info();
97
163
  const checks = checkAllPrerequisites();
98
- if (displayPrerequisites(checks), !hasRequiredPrerequisites(checks) && (showWarning("Some required prerequisites are missing. You can continue, but setup may fail."), !(await confirmContinue("Continue anyway?", !1)))) {
99
- displayOutro("Setup cancelled. Install prerequisites and try again.");
100
- return;
164
+ displayPrerequisites(checks);
165
+ const hasRequired = hasRequiredPrerequisites(checks);
166
+ if (!hasRequired) {
167
+ showWarning("Some required prerequisites are missing. You can continue, but setup may fail.");
168
+ const shouldContinue = await confirmContinue("Continue anyway?", false);
169
+ if (!shouldContinue) {
170
+ displayOutro("Setup cancelled. Install prerequisites and try again.");
171
+ return;
172
+ }
173
+ }
174
+ console.info();
175
+ }
176
+ if (startStep === "prerequisites" || startStep === "identity" || startStep === "full") {
177
+ saveOnboardState(cwd, {
178
+ step: "identity",
179
+ timestamp: Date.now()
180
+ });
181
+ showStep("Configuring project identity...");
182
+ console.info();
183
+ const shouldCustomize = await confirmContinue("Customize project name and bundle identifier?", false);
184
+ if (shouldCustomize) {
185
+ await customizeProject(cwd);
186
+ } else {
187
+ showInfo("Keeping default project configuration");
101
188
  }
102
189
  console.info();
103
190
  }
104
- if ((startStep === "prerequisites" || startStep === "identity" || startStep === "full") && (saveOnboardState(cwd, {
105
- step: "identity",
106
- timestamp: Date.now()
107
- }), showStep("Configuring project identity..."), console.info(), (await confirmContinue("Customize project name and bundle identifier?", !1)) ? await customizeProject(cwd) : showInfo("Keeping default project configuration"), console.info()), startStep === "prerequisites" || startStep === "identity" || startStep === "ports" || startStep === "full") {
108
- if (saveOnboardState(cwd, {
191
+ if (startStep === "prerequisites" || startStep === "identity" || startStep === "ports" || startStep === "full") {
192
+ saveOnboardState(cwd, {
109
193
  step: "ports",
110
194
  timestamp: Date.now()
111
- }), showStep("Configuring web server port..."), console.info(), showInfo("Default web port: 8081 (TAMA in T9)"), await confirmContinue("Customize web server port?", !1)) {
195
+ });
196
+ showStep("Configuring web server port...");
197
+ console.info();
198
+ showInfo("Default web port: 8081 (TAMA in T9)");
199
+ const shouldCustomizePort = await confirmContinue("Customize web server port?", false);
200
+ if (shouldCustomizePort) {
112
201
  const newPort = await promptText("Web server port:", "8081", "3000, 8080, 8081, etc.");
113
- newPort && newPort !== "8081" && (await replacePortInProject(cwd, "8081", newPort), showSuccess(`\u2713 Updated web server port to ${newPort}`));
114
- } else showInfo("Keeping default port 8081");
115
- console.info(), showStep("Checking service ports..."), console.info();
116
- const portChecks = checkAllPorts(),
117
- conflicts = getConflictingPorts(portChecks);
118
- conflicts.length > 0 && (displayPortConflicts(conflicts), showWarning("Some ports are already in use. You may need to stop other services.")), console.info();
202
+ if (newPort && newPort !== "8081") {
203
+ await replacePortInProject(cwd, "8081", newPort);
204
+ showSuccess(`\u2713 Updated web server port to ${newPort}`);
205
+ }
206
+ } else {
207
+ showInfo("Keeping default port 8081");
208
+ }
209
+ console.info();
210
+ showStep("Checking service ports...");
211
+ console.info();
212
+ const portChecks = checkAllPorts();
213
+ const conflicts = getConflictingPorts(portChecks);
214
+ if (conflicts.length > 0) {
215
+ displayPortConflicts(conflicts);
216
+ showWarning("Some ports are already in use. You may need to stop other services.");
217
+ }
218
+ console.info();
219
+ }
220
+ if (startStep === "prerequisites" || startStep === "identity" || startStep === "ports" || startStep === "full") {
221
+ saveOnboardState(cwd, {
222
+ step: "ci-runners",
223
+ timestamp: Date.now()
224
+ });
225
+ showStep("Configuring CI/CD runners...");
226
+ console.info();
227
+ const shouldConfigureCI = await confirmContinue("Configure GitHub Actions CI runners?", true);
228
+ if (shouldConfigureCI) {
229
+ await configureCIRunners(cwd);
230
+ } else {
231
+ showInfo("Skipping CI runner configuration");
232
+ showWarning("Default CI uses ARM64 Docker builds. If not using ARM runners, update scripts/web/build-docker.ts");
233
+ }
234
+ console.info();
235
+ }
236
+ if (startStep !== "production") {
237
+ showStep("Development setup complete!");
238
+ console.info();
239
+ showSuccess("\u2713 Environment configured");
240
+ showSuccess("\u2713 Project ready for development");
241
+ markOnboarded(cwd);
242
+ console.info();
243
+ showInfo("Next steps (run in separate terminals):");
244
+ console.info();
245
+ console.info(" bun backend # start docker services first");
246
+ console.info(" bun dev # start web dev server");
247
+ console.info();
248
+ console.info(" bun ios # build iOS dev app");
249
+ console.info(" bun android # build Android dev app");
250
+ console.info(" bun tko docs list # view Takeout docs");
251
+ console.info();
119
252
  }
120
- if ((startStep === "prerequisites" || startStep === "identity" || startStep === "ports" || startStep === "full") && (saveOnboardState(cwd, {
121
- step: "ci-runners",
122
- timestamp: Date.now()
123
- }), showStep("Configuring CI/CD runners..."), console.info(), (await confirmContinue("Configure GitHub Actions CI runners?", !0)) ? await configureCIRunners(cwd) : (showInfo("Skipping CI runner configuration"), showWarning("Default CI uses ARM64 Docker builds. If not using ARM runners, update scripts/web/build-docker.ts")), console.info()), startStep !== "production" && (showStep("Development setup complete!"), console.info(), showSuccess("\u2713 Environment configured"), showSuccess("\u2713 Project ready for development"), markOnboarded(cwd), console.info(), showInfo("Next steps (run in separate terminals):"), console.info(), console.info(" bun backend # start docker services first"), console.info(" bun dev # start web dev server"), console.info(), console.info(" bun ios # build iOS dev app"), console.info(" bun android # build Android dev app"), console.info(" bun tko docs list # view Takeout docs"), console.info()), startStep !== "production") {
124
- if (console.info(), showStep("Monorepo Setup"), console.info(), console.info(pc.gray("We've included a variety of packages we found useful building apps with this stack:")), console.info(pc.gray(" \u2022 @take-out/cli - CLI tools and onboarding")), console.info(pc.gray(" \u2022 @take-out/helpers - Utility functions")), console.info(pc.gray(" \u2022 @take-out/hooks - React hooks")), console.info(pc.gray(" \u2022 @take-out/database - Database utilities")), console.info(pc.gray(" \u2022 @take-out/scripts - Build and dev scripts")), console.info(pc.gray(" \u2022 @take-out/better-auth-utils - Auth helpers")), console.info(), console.info(pc.gray("These packages are included locally for two reasons:")), console.info(pc.gray(" 1. You can see their source and decide if you want to keep them")), console.info(pc.gray(" 2. Anyone can easily submit fixes or improvements back to our repo")), console.info(), console.info(pc.gray("Over time we'll publish new versions. You can sync with 'bun tko sync' (monorepo)")), console.info(pc.gray("or if you eject, use 'bun up takeout' for package updates.")), console.info(), await confirmContinue("Eject from monorepo setup? (removes ./packages, uses published versions)", !1)) {
253
+ if (startStep !== "production") {
254
+ console.info();
255
+ showStep("Monorepo Setup");
256
+ console.info();
257
+ console.info(pc.gray("We've included a variety of packages we found useful building apps with this stack:"));
258
+ console.info(pc.gray(" \u2022 @take-out/cli - CLI tools and onboarding"));
259
+ console.info(pc.gray(" \u2022 @take-out/helpers - Utility functions"));
260
+ console.info(pc.gray(" \u2022 @take-out/hooks - React hooks"));
261
+ console.info(pc.gray(" \u2022 @take-out/database - Database utilities"));
262
+ console.info(pc.gray(" \u2022 @take-out/scripts - Build and dev scripts"));
263
+ console.info(pc.gray(" \u2022 @take-out/better-auth-utils - Auth helpers"));
264
+ console.info();
265
+ console.info(pc.gray("These packages are included locally for two reasons:"));
266
+ console.info(pc.gray(" 1. You can see their source and decide if you want to keep them"));
267
+ console.info(pc.gray(" 2. Anyone can easily submit fixes or improvements back to our repo"));
268
+ console.info();
269
+ console.info(pc.gray("Over time we'll publish new versions. You can sync with 'bun tko sync' (monorepo)"));
270
+ console.info(pc.gray("or if you eject, use 'bun up takeout' for package updates."));
271
+ console.info();
272
+ const shouldEject = await confirmContinue("Eject from monorepo setup? (removes ./packages, uses published versions)", false);
273
+ if (shouldEject) {
125
274
  const dryResult = await ejectFromMonorepo(cwd, {
126
- dryRun: !0
275
+ dryRun: true
127
276
  });
128
- if (!dryResult.success) showError(`Cannot eject: ${dryResult.error}`);else {
129
- console.info(), showInfo(`Found ${dryResult.packages?.length} packages to convert:`);
130
- for (const pkg of dryResult.packages || []) console.info(pc.gray(` \u2022 ${pkg.name}@${pkg.version}`));
131
- if (dryResult.warnings?.length) for (const warn of dryResult.warnings) showWarning(warn);
132
- if (console.info(), await confirmContinue("Proceed with ejection?", !0)) {
133
- const spinner = showSpinner("Ejecting from monorepo..."),
134
- result = await ejectFromMonorepo(cwd);
277
+ if (!dryResult.success) {
278
+ showError(`Cannot eject: ${dryResult.error}`);
279
+ } else {
280
+ console.info();
281
+ showInfo(`Found ${dryResult.packages?.length} packages to convert:`);
282
+ for (const pkg of dryResult.packages || []) {
283
+ console.info(pc.gray(` \u2022 ${pkg.name}@${pkg.version}`));
284
+ }
285
+ if (dryResult.warnings?.length) {
286
+ for (const warn of dryResult.warnings) {
287
+ showWarning(warn);
288
+ }
289
+ }
290
+ console.info();
291
+ const confirmEject = await confirmContinue("Proceed with ejection?", true);
292
+ if (confirmEject) {
293
+ const spinner = showSpinner("Ejecting from monorepo...");
294
+ const result = await ejectFromMonorepo(cwd);
135
295
  if (result.success) {
136
- spinner.stop("Ejected from monorepo"), showSuccess("\u2713 Removed ./packages directory"), showSuccess("\u2713 Updated package.json to use published versions"), showSuccess("\u2713 Installed published packages");
296
+ spinner.stop("Ejected from monorepo");
297
+ showSuccess("\u2713 Removed ./packages directory");
298
+ showSuccess("\u2713 Updated package.json to use published versions");
299
+ showSuccess("\u2713 Installed published packages");
137
300
  try {
138
301
  execSync('git add -A && git commit -m "ejected from monorepo"', {
139
302
  cwd,
140
303
  stdio: "ignore"
141
- }), showSuccess("\u2713 Committed eject changes");
304
+ });
305
+ showSuccess("\u2713 Committed eject changes");
142
306
  } catch {
143
307
  showWarning("Git commit skipped (not a git repo or no changes)");
144
308
  }
145
- console.info(), showInfo("You can now upgrade packages with: bun up takeout");
146
- } else spinner.stop("Ejection failed"), showError(`Failed to eject: ${result.error}`), result.error?.includes("install failed") && (showInfo('You may be able to fix this by running "bun install" manually'), showInfo('Or restore the repo from git with "git checkout ."'));
147
- } else showInfo("Keeping monorepo setup - you can customize packages locally"), showInfo('Run "bun tko sync" to sync with upstream Takeout updates later');
309
+ console.info();
310
+ showInfo("You can now upgrade packages with: bun up takeout");
311
+ } else {
312
+ spinner.stop("Ejection failed");
313
+ showError(`Failed to eject: ${result.error}`);
314
+ if (result.error?.includes("install failed")) {
315
+ showInfo('You may be able to fix this by running "bun install" manually');
316
+ showInfo('Or restore the repo from git with "git checkout ."');
317
+ }
318
+ }
319
+ } else {
320
+ showInfo("Keeping monorepo setup - you can customize packages locally");
321
+ showInfo('Run "bun tko sync" to sync with upstream Takeout updates later');
322
+ }
148
323
  }
149
- } else showInfo("Keeping monorepo setup - you can customize packages locally"), showInfo('Run "bun tko sync" to sync with upstream Takeout updates later');
324
+ } else {
325
+ showInfo("Keeping monorepo setup - you can customize packages locally");
326
+ showInfo('Run "bun tko sync" to sync with upstream Takeout updates later');
327
+ }
150
328
  console.info();
151
329
  }
152
330
  if (startStep === "full" || startStep === "production") {
153
331
  saveOnboardState(cwd, {
154
332
  step: "production",
155
333
  timestamp: Date.now()
156
- }), console.info(), showStep("Production deployment setup"), console.info();
334
+ });
335
+ console.info();
336
+ showStep("Production deployment setup");
337
+ console.info();
157
338
  let setupProd = startStep === "production";
158
- startStep === "full" && (setupProd = await confirmContinue("Set up production deployment?", !1)), setupProd ? await setupProductionDeployment(cwd) : (showInfo("Skipping production setup"), showInfo("You can set up production later with: bun tko onboard --production"));
339
+ if (startStep === "full") {
340
+ setupProd = await confirmContinue("Set up production deployment?", false);
341
+ }
342
+ if (setupProd) {
343
+ await setupProductionDeployment(cwd);
344
+ } else {
345
+ showInfo("Skipping production setup");
346
+ showInfo("You can set up production later with: bun tko onboard --production");
347
+ }
159
348
  }
160
- clearOnboardState(cwd), console.info(), displayOutro("Happy coding! \u{1F680}");
349
+ clearOnboardState(cwd);
350
+ console.info();
351
+ displayOutro("Happy coding! \u{1F680}");
161
352
  }
162
353
  });
163
354
  async function customizeProject(cwd) {
164
- const projectName = await promptText("Project name:", "takeout", "my-awesome-app"),
165
- slug = await promptText("Project slug (URL-friendly):", projectName.toLowerCase().replace(/\s+/g, "-"), "my-awesome-app"),
166
- bundleId = await promptText("Bundle identifier:", `com.${slug}.app`, "com.example.app"),
167
- domain = await promptText("Development domain:", "localhost:8081", "localhost:8081"),
168
- pkgResult = updatePackageJson(cwd, {
169
- name: projectName,
170
- description: `${projectName} - Built with Takeout starter kit`
171
- });
172
- pkgResult.success ? showSuccess("Updated package.json") : showError(`Failed to update package.json: ${pkgResult.error}`);
355
+ const projectName = await promptText("Project name:", "takeout", "my-awesome-app");
356
+ const slug = await promptText("Project slug (URL-friendly):", projectName.toLowerCase().replace(/\s+/g, "-"), "my-awesome-app");
357
+ const bundleId = await promptText("Bundle identifier:", `com.${slug}.app`, "com.example.app");
358
+ const domain = await promptText("Development domain:", "localhost:8081", "localhost:8081");
359
+ const pkgResult = updatePackageJson(cwd, {
360
+ name: projectName,
361
+ description: `${projectName} - Built with Takeout starter kit`
362
+ });
363
+ if (pkgResult.success) {
364
+ showSuccess("Updated package.json");
365
+ } else {
366
+ showError(`Failed to update package.json: ${pkgResult.error}`);
367
+ }
173
368
  const configResult = updateAppConfig(cwd, {
174
369
  name: projectName,
175
370
  slug,
176
371
  bundleId
177
372
  });
178
- configResult.success ? showSuccess("Updated app.config.ts") : showError(`Failed to update app.config.ts: ${configResult.error}`);
373
+ if (configResult.success) {
374
+ showSuccess("Updated app.config.ts");
375
+ } else {
376
+ showError(`Failed to update app.config.ts: ${configResult.error}`);
377
+ }
179
378
  const serverUrl = `http://${domain}`;
180
- updateEnvVariable(cwd, "BETTER_AUTH_URL", serverUrl), updateEnvVariable(cwd, "ONE_SERVER_URL", serverUrl), showSuccess("Updated environment URLs");
379
+ updateEnvVariable(cwd, "BETTER_AUTH_URL", serverUrl);
380
+ updateEnvVariable(cwd, "ONE_SERVER_URL", serverUrl);
381
+ showSuccess("Updated environment URLs");
181
382
  }
182
383
  async function setupProductionDeployment(cwd) {
183
384
  const platform = await promptSelect("Choose deployment platform:", [{
@@ -193,14 +394,22 @@ async function setupProductionDeployment(cwd) {
193
394
  showInfo("Skipping production setup");
194
395
  return;
195
396
  }
196
- console.info(), platform === "uncloud" ? await setupUncloudDeployment(cwd) : await setupSSTDeployment(cwd);
397
+ console.info();
398
+ if (platform === "uncloud") {
399
+ await setupUncloudDeployment(cwd);
400
+ } else {
401
+ await setupSSTDeployment(cwd);
402
+ }
197
403
  }
198
404
  async function setupUncloudDeployment(cwd) {
199
- showInfo("Setting up Uncloud deployment"), console.info(), console.info(pc.gray(`Uncloud provides:
200
- \u2022 Managed PostgreSQL with logical replication
201
- \u2022 Free subdomain (your-app.uncld.dev)
202
- \u2022 Automatic SSL certificates
203
- \u2022 Easy scaling`)), console.info(), console.info(), showInfo("Server Architecture"), console.info(pc.gray("Docker images must match your server CPU architecture")), console.info();
405
+ showInfo("Setting up Uncloud deployment");
406
+ console.info();
407
+ console.info(pc.gray("Uncloud provides:\n \u2022 Managed PostgreSQL with logical replication\n \u2022 Free subdomain (your-app.uncld.dev)\n \u2022 Automatic SSL certificates\n \u2022 Easy scaling"));
408
+ console.info();
409
+ console.info();
410
+ showInfo("Server Architecture");
411
+ console.info(pc.gray("Docker images must match your server CPU architecture"));
412
+ console.info();
204
413
  const architecture = await promptSelect("What CPU architecture is your deployment server?", [{
205
414
  value: "amd64",
206
415
  label: "AMD64/x86_64 (Intel/AMD)",
@@ -215,93 +424,249 @@ async function setupUncloudDeployment(cwd) {
215
424
  return;
216
425
  }
217
426
  const deploymentArch = architecture === "arm64" ? "linux/arm64" : "linux/amd64";
218
- console.info(), showInfo(`Will build Docker images for: ${deploymentArch}`), console.info();
427
+ console.info();
428
+ showInfo(`Will build Docker images for: ${deploymentArch}`);
429
+ console.info();
219
430
  const appConstantsPath = resolve(cwd, "src/constants/app.ts");
220
431
  let defaultAppName = "my-app";
221
432
  try {
222
- const appNameMatch = readFileSync(appConstantsPath, "utf-8").match(/APP_NAME_LOWERCASE\s*=\s*['"](.+?)['"]/);
223
- appNameMatch?.[1] && (defaultAppName = appNameMatch[1]);
433
+ const appConstants = readFileSync(appConstantsPath, "utf-8");
434
+ const appNameMatch = appConstants.match(/APP_NAME_LOWERCASE\s*=\s*['"](.+?)['"]/);
435
+ if (appNameMatch?.[1]) {
436
+ defaultAppName = appNameMatch[1];
437
+ }
224
438
  } catch {}
225
- const useFreeSubdomain = await confirmContinue("Use free Uncloud subdomain?", !0);
226
- let domain, zeroUrl;
439
+ const useFreeSubdomain = await confirmContinue("Use free Uncloud subdomain?", true);
440
+ let domain;
441
+ let zeroUrl;
227
442
  if (useFreeSubdomain) {
228
443
  const appName = await promptText("App name for subdomain:", defaultAppName, defaultAppName);
229
- domain = `https://${appName}.uncld.dev`, zeroUrl = `https://zero-${appName}.uncld.dev`;
230
- } else domain = await promptText("Enter your custom domain:", void 0, "https://yourapp.com"), zeroUrl = await promptText("Enter your Zero sync domain:", void 0, "https://zero.yourapp.com"), console.info(), showWarning("Custom domain setup requires DNS configuration after deployment"), console.info(pc.gray("See: https://uncloud.run/docs/domains"));
231
- console.info(), showInfo("Database Configuration"), console.info(), console.info(pc.gray("PostgreSQL database with logical replication enabled")), console.info(pc.gray("Zero sync requires 3 databases on the same host:")), console.info(pc.gray(" \u2022 Main database (your app data)")), console.info(pc.gray(" \u2022 Two Zero databases (for sync infrastructure)")), console.info(), console.info(pc.gray("Use a managed database (DigitalOcean, Render, Supabase, etc.)")), console.info(pc.gray("The deployment will automatically create the Zero databases")), console.info();
232
- const dbUser = await promptText("Database username:", "postgres", "postgres"),
233
- dbPassword = await promptText("Database password:", void 0, ""),
234
- dbHost = await promptText("Database host (e.g., db-xxx.ondigitalocean.com):", void 0, "localhost"),
235
- dbPort = await promptText("Database port:", "5432", "5432"),
236
- dbName = await promptText("Main database name (will derive Zero databases from this):", "postgres", "postgres"),
237
- dbUrl = `postgresql://${dbUser}:${dbPassword}@${dbHost}:${dbPort}/${dbName}`;
238
- console.info();
239
- const authSecret = randomBytes(32).toString("hex"),
240
- envFile = ".env.production";
444
+ domain = `https://${appName}.uncld.dev`;
445
+ zeroUrl = `https://zero-${appName}.uncld.dev`;
446
+ } else {
447
+ domain = await promptText("Enter your custom domain:", void 0, "https://yourapp.com");
448
+ zeroUrl = await promptText("Enter your Zero sync domain:", void 0, "https://zero.yourapp.com");
449
+ console.info();
450
+ showWarning("Custom domain setup requires DNS configuration after deployment");
451
+ console.info(pc.gray("See: https://uncloud.run/docs/domains"));
452
+ }
453
+ console.info();
454
+ showInfo("Database Configuration");
455
+ console.info();
456
+ console.info(pc.gray("PostgreSQL database with logical replication enabled"));
457
+ console.info(pc.gray("Zero sync requires 3 databases on the same host:"));
458
+ console.info(pc.gray(" \u2022 Main database (your app data)"));
459
+ console.info(pc.gray(" \u2022 Two Zero databases (for sync infrastructure)"));
460
+ console.info();
461
+ console.info(pc.gray("Use a managed database (DigitalOcean, Render, Supabase, etc.)"));
462
+ console.info(pc.gray("The deployment will automatically create the Zero databases"));
463
+ console.info();
464
+ const dbUser = await promptText("Database username:", "postgres", "postgres");
465
+ const dbPassword = await promptText("Database password:", void 0, "");
466
+ const dbHost = await promptText("Database host (e.g., db-xxx.ondigitalocean.com):", void 0, "localhost");
467
+ const dbPort = await promptText("Database port:", "5432", "5432");
468
+ const dbName = await promptText("Main database name (will derive Zero databases from this):", "postgres", "postgres");
469
+ const dbUrl = `postgresql://${dbUser}:${dbPassword}@${dbHost}:${dbPort}/${dbName}`;
470
+ console.info();
471
+ const authSecret = randomBytes(32).toString("hex");
472
+ const envFile = ".env.production";
241
473
  if (!envFileExists(cwd, envFile)) {
242
474
  const copyResult = copyEnvFile(cwd, ".env.production.example", envFile);
243
- if (copyResult.success) showSuccess(`Created ${envFile} from .env.production.example`);else {
475
+ if (copyResult.success) {
476
+ showSuccess(`Created ${envFile} from .env.production.example`);
477
+ } else {
244
478
  showError(`Failed to create ${envFile}: ${copyResult.error}`);
245
479
  return;
246
480
  }
247
481
  }
248
- updateEnvVariable(cwd, "DEPLOYMENT_PLATFORM", "uncloud", envFile), updateEnvVariable(cwd, "DEPLOYMENT_ARCH", deploymentArch, envFile), updateEnvVariable(cwd, "DEPLOY_DB", dbUrl, envFile), updateEnvVariable(cwd, "BETTER_AUTH_SECRET", authSecret, envFile), updateEnvVariable(cwd, "BETTER_AUTH_URL", domain, envFile), updateEnvVariable(cwd, "ONE_SERVER_URL", domain, envFile);
249
- const zeroHost = zeroUrl.replace(/^https?:\/\//, ""),
250
- webHost = domain.replace(/^https?:\/\//, "");
251
- updateEnvVariable(cwd, "VITE_ZERO_HOSTNAME", zeroHost, envFile), updateEnvVariable(cwd, "VITE_WEB_HOSTNAME", webHost, envFile);
252
- const dbBase = dbUrl.split("/").slice(0, -1).join("/"),
253
- zeroCvrDb = `${dbBase}/zero_cvr`,
254
- zeroChangeDb = `${dbBase}/zero_cdb`;
255
- updateEnvVariable(cwd, "ZERO_UPSTREAM_DB", dbUrl, envFile), updateEnvVariable(cwd, "ZERO_CVR_DB", zeroCvrDb, envFile), updateEnvVariable(cwd, "ZERO_CHANGE_DB", zeroChangeDb, envFile), updateEnvVariable(cwd, "ZERO_UPSTREAM_DB", dbUrl, ".env"), updateEnvVariable(cwd, "ZERO_CVR_DB", zeroCvrDb, ".env"), updateEnvVariable(cwd, "ZERO_CHANGE_DB", zeroChangeDb, ".env");
482
+ updateEnvVariable(cwd, "DEPLOYMENT_PLATFORM", "uncloud", envFile);
483
+ updateEnvVariable(cwd, "DEPLOYMENT_ARCH", deploymentArch, envFile);
484
+ updateEnvVariable(cwd, "DEPLOY_DB", dbUrl, envFile);
485
+ updateEnvVariable(cwd, "BETTER_AUTH_SECRET", authSecret, envFile);
486
+ updateEnvVariable(cwd, "BETTER_AUTH_URL", domain, envFile);
487
+ updateEnvVariable(cwd, "ONE_SERVER_URL", domain, envFile);
488
+ const zeroHost = zeroUrl.replace(/^https?:\/\//, "");
489
+ const webHost = domain.replace(/^https?:\/\//, "");
490
+ updateEnvVariable(cwd, "VITE_ZERO_HOSTNAME", zeroHost, envFile);
491
+ updateEnvVariable(cwd, "VITE_WEB_HOSTNAME", webHost, envFile);
492
+ const dbBase = dbUrl.split("/").slice(0, -1).join("/");
493
+ const zeroCvrDb = `${dbBase}/zero_cvr`;
494
+ const zeroChangeDb = `${dbBase}/zero_cdb`;
495
+ updateEnvVariable(cwd, "ZERO_UPSTREAM_DB", dbUrl, envFile);
496
+ updateEnvVariable(cwd, "ZERO_CVR_DB", zeroCvrDb, envFile);
497
+ updateEnvVariable(cwd, "ZERO_CHANGE_DB", zeroChangeDb, envFile);
498
+ updateEnvVariable(cwd, "ZERO_UPSTREAM_DB", dbUrl, ".env");
499
+ updateEnvVariable(cwd, "ZERO_CVR_DB", zeroCvrDb, ".env");
500
+ updateEnvVariable(cwd, "ZERO_CHANGE_DB", zeroChangeDb, ".env");
256
501
  const deployHost = new URL(domain).hostname;
257
- updateEnvVariable(cwd, "DEPLOY_HOST", deployHost, envFile), updateEnvVariable(cwd, "DEPLOY_USER", "root", envFile), console.info(), showInfo("SSH Key Configuration"), console.info(pc.gray("Deployment requires SSH access to your server")), console.info();
502
+ updateEnvVariable(cwd, "DEPLOY_HOST", deployHost, envFile);
503
+ updateEnvVariable(cwd, "DEPLOY_USER", "root", envFile);
504
+ console.info();
505
+ showInfo("SSH Key Configuration");
506
+ console.info(pc.gray("Deployment requires SSH access to your server"));
507
+ console.info();
258
508
  const sshKeyPath = await promptText("Path to SSH private key:", `${homedir()}/.ssh/id_rsa`, `${homedir()}/.ssh/id_rsa`);
259
- if (sshKeyPath && (existsSync(sshKeyPath) ? (updateEnvVariable(cwd, "DEPLOY_SSH_KEY", sshKeyPath, envFile), showSuccess(`\u2713 SSH key configured: ${sshKeyPath}`), console.info(), showInfo(pc.gray("For CI/CD, you'll need to add the SSH private key content as a GitHub secret")), showInfo(pc.gray("The sync script will help with this"))) : (showWarning(`SSH key not found at: ${sshKeyPath}`), showInfo("You can add DEPLOY_SSH_KEY to .env.production manually later"))), console.info(), showSuccess(`\u2713 Saved configuration to ${envFile}`), console.info(), showInfo("Custom Domain Setup (Optional)"), console.info(pc.gray("By default, your app will use:")), console.info(pc.gray(` ${deployHost} (cluster subdomain from Uncloud DNS)`)), console.info(), console.info(pc.gray("You can optionally configure custom domains (e.g., app.yourdomain.com) by:")), console.info(pc.gray(" 1. Adding CNAME records in your DNS provider")), console.info(pc.gray(" 2. Pointing to your cluster subdomain")), console.info(pc.gray(" 3. Setting VITE_WEB_HOSTNAME and VITE_ZERO_HOSTNAME in .env.production")), console.info(), await confirmContinue("Configure custom domain now?", !1)) {
260
- console.info(), console.info(pc.gray("First, run: uc dns reserve")), console.info(pc.gray("This will give you a cluster subdomain like: abc123.uncld.dev")), console.info();
509
+ if (sshKeyPath) {
510
+ if (existsSync(sshKeyPath)) {
511
+ updateEnvVariable(cwd, "DEPLOY_SSH_KEY", sshKeyPath, envFile);
512
+ showSuccess(`\u2713 SSH key configured: ${sshKeyPath}`);
513
+ console.info();
514
+ showInfo(pc.gray("For CI/CD, you'll need to add the SSH private key content as a GitHub secret"));
515
+ showInfo(pc.gray("The sync script will help with this"));
516
+ } else {
517
+ showWarning(`SSH key not found at: ${sshKeyPath}`);
518
+ showInfo("You can add DEPLOY_SSH_KEY to .env.production manually later");
519
+ }
520
+ }
521
+ console.info();
522
+ showSuccess(`\u2713 Saved configuration to ${envFile}`);
523
+ console.info();
524
+ showInfo("Custom Domain Setup (Optional)");
525
+ console.info(pc.gray("By default, your app will use:"));
526
+ console.info(pc.gray(` ${deployHost} (cluster subdomain from Uncloud DNS)`));
527
+ console.info();
528
+ console.info(pc.gray("You can optionally configure custom domains (e.g., app.yourdomain.com) by:"));
529
+ console.info(pc.gray(" 1. Adding CNAME records in your DNS provider"));
530
+ console.info(pc.gray(` 2. Pointing to your cluster subdomain`));
531
+ console.info(pc.gray(" 3. Setting VITE_WEB_HOSTNAME and VITE_ZERO_HOSTNAME in .env.production"));
532
+ console.info();
533
+ const configureCustomDomain = await confirmContinue("Configure custom domain now?", false);
534
+ if (configureCustomDomain) {
535
+ console.info();
536
+ console.info(pc.gray("First, run: uc dns reserve"));
537
+ console.info(pc.gray("This will give you a cluster subdomain like: abc123.uncld.dev"));
538
+ console.info();
261
539
  const clusterSubdomain = await promptText("Enter your cluster subdomain from uc dns show:", "", "");
262
540
  if (clusterSubdomain) {
263
- console.info(), showInfo("DNS Setup Instructions:"), console.info(pc.gray("Add these CNAME records in your DNS provider (e.g., Cloudflare):")), console.info();
264
- const webDomain = await promptText("Custom domain for web app (e.g., app.yourdomain.com):", "", ""),
265
- zeroDomain = await promptText("Custom domain for zero sync (e.g., zero.yourdomain.com):", "", "");
541
+ console.info();
542
+ showInfo("DNS Setup Instructions:");
543
+ console.info(pc.gray("Add these CNAME records in your DNS provider (e.g., Cloudflare):"));
544
+ console.info();
545
+ const webDomain = await promptText("Custom domain for web app (e.g., app.yourdomain.com):", "", "");
546
+ const zeroDomain = await promptText("Custom domain for zero sync (e.g., zero.yourdomain.com):", "", "");
266
547
  if (webDomain) {
267
- console.info(), console.info(pc.cyan(` CNAME: ${webDomain} \u2192 ${clusterSubdomain}`)), zeroDomain && console.info(pc.cyan(` CNAME: ${zeroDomain} \u2192 ${clusterSubdomain}`)), console.info(), console.info(pc.yellow('\u26A0\uFE0F Set DNS to "DNS only" mode (gray cloud), not proxied')), console.info();
548
+ console.info();
549
+ console.info(pc.cyan(` CNAME: ${webDomain} \u2192 ${clusterSubdomain}`));
550
+ if (zeroDomain) {
551
+ console.info(pc.cyan(` CNAME: ${zeroDomain} \u2192 ${clusterSubdomain}`));
552
+ }
553
+ console.info();
554
+ console.info(pc.yellow('\u26A0\uFE0F Set DNS to "DNS only" mode (gray cloud), not proxied'));
555
+ console.info();
268
556
  const webUrl = `https://${webDomain}`;
269
- if (updateEnvVariable(cwd, "VITE_WEB_HOSTNAME", webDomain, envFile), updateEnvVariable(cwd, "BETTER_AUTH_URL", webUrl, envFile), updateEnvVariable(cwd, "ONE_SERVER_URL", webUrl, envFile), zeroDomain && updateEnvVariable(cwd, "VITE_ZERO_HOSTNAME", zeroDomain, envFile), showSuccess("\u2713 Custom domains configured"), console.info(), showInfo(pc.gray("DNS propagation typically takes 5-30 minutes")), console.info(), showInfo("SSL Certificate Options"), console.info(pc.gray("By default, Caddy will use Let's Encrypt for SSL certificates.")), console.info(pc.gray("If using Cloudflare, you can use Origin CA instead to:")), console.info(pc.gray(" \u2022 Bypass Let's Encrypt rate limits")), console.info(pc.gray(" \u2022 Enable Cloudflare proxy (DDoS protection, caching)")), console.info(), await confirmContinue("Use Cloudflare Origin CA? (requires Cloudflare account)", !1)) {
270
- console.info(), showInfo("Cloudflare Origin CA Setup"), console.info(pc.gray("1. Go to Cloudflare Dashboard \u2192 SSL/TLS \u2192 Origin Server")), console.info(pc.gray('2. Click "Create Certificate"')), console.info(pc.gray(`3. Add hostnames: ${webDomain}${zeroDomain ? `, ${zeroDomain}` : ""}`)), console.info(pc.gray("4. Choose 15 year validity")), console.info(pc.gray("5. Save certificate to: certs/origin.pem")), console.info(pc.gray("6. Save private key to: certs/origin.key")), console.info();
271
- const certPath = await promptText("Path to Origin CA certificate:", "certs/origin.pem", "certs/origin.pem"),
272
- keyPath = await promptText("Path to Origin CA private key:", "certs/origin.key", "certs/origin.key");
273
- certPath && keyPath && (updateEnvVariable(cwd, "ORIGIN_CA_CERT", certPath, envFile), updateEnvVariable(cwd, "ORIGIN_CA_KEY", keyPath, envFile), showSuccess("\u2713 Origin CA configured"), console.info(pc.gray(" Caddyfile will be auto-generated during deploy")), console.info(), console.info(pc.yellow("Important: In Cloudflare Dashboard:")), console.info(pc.yellow(" 1. Enable proxy (orange cloud) for your domains")), console.info(pc.yellow(' 2. Set SSL mode to "Full (strict)"')));
557
+ updateEnvVariable(cwd, "VITE_WEB_HOSTNAME", webDomain, envFile);
558
+ updateEnvVariable(cwd, "BETTER_AUTH_URL", webUrl, envFile);
559
+ updateEnvVariable(cwd, "ONE_SERVER_URL", webUrl, envFile);
560
+ if (zeroDomain) {
561
+ updateEnvVariable(cwd, "VITE_ZERO_HOSTNAME", zeroDomain, envFile);
562
+ }
563
+ showSuccess("\u2713 Custom domains configured");
564
+ console.info();
565
+ showInfo(pc.gray("DNS propagation typically takes 5-30 minutes"));
566
+ console.info();
567
+ showInfo("SSL Certificate Options");
568
+ console.info(pc.gray("By default, Caddy will use Let's Encrypt for SSL certificates."));
569
+ console.info(pc.gray("If using Cloudflare, you can use Origin CA instead to:"));
570
+ console.info(pc.gray(" \u2022 Bypass Let's Encrypt rate limits"));
571
+ console.info(pc.gray(" \u2022 Enable Cloudflare proxy (DDoS protection, caching)"));
572
+ console.info();
573
+ const useOriginCA = await confirmContinue("Use Cloudflare Origin CA? (requires Cloudflare account)", false);
574
+ if (useOriginCA) {
575
+ console.info();
576
+ showInfo("Cloudflare Origin CA Setup");
577
+ console.info(pc.gray("1. Go to Cloudflare Dashboard \u2192 SSL/TLS \u2192 Origin Server"));
578
+ console.info(pc.gray('2. Click "Create Certificate"'));
579
+ console.info(pc.gray(`3. Add hostnames: ${webDomain}${zeroDomain ? `, ${zeroDomain}` : ""}`));
580
+ console.info(pc.gray("4. Choose 15 year validity"));
581
+ console.info(pc.gray("5. Save certificate to: certs/origin.pem"));
582
+ console.info(pc.gray("6. Save private key to: certs/origin.key"));
583
+ console.info();
584
+ const certPath = await promptText("Path to Origin CA certificate:", "certs/origin.pem", "certs/origin.pem");
585
+ const keyPath = await promptText("Path to Origin CA private key:", "certs/origin.key", "certs/origin.key");
586
+ if (certPath && keyPath) {
587
+ updateEnvVariable(cwd, "ORIGIN_CA_CERT", certPath, envFile);
588
+ updateEnvVariable(cwd, "ORIGIN_CA_KEY", keyPath, envFile);
589
+ showSuccess("\u2713 Origin CA configured");
590
+ console.info(pc.gray(" Caddyfile will be auto-generated during deploy"));
591
+ console.info();
592
+ console.info(pc.yellow("Important: In Cloudflare Dashboard:"));
593
+ console.info(pc.yellow(" 1. Enable proxy (orange cloud) for your domains"));
594
+ console.info(pc.yellow(' 2. Set SSL mode to "Full (strict)"'));
595
+ }
274
596
  }
275
597
  }
276
598
  }
277
599
  }
278
- console.info(), showInfo("Updating package.json env section for Uncloud deployment...");
600
+ console.info();
601
+ showInfo("Updating package.json env section for Uncloud deployment...");
279
602
  const envUpdateResult = updatePackageJsonEnv(cwd, "uncloud");
280
- envUpdateResult.success ? showSuccess("\u2713 Removed SST-specific environment variables from package.json") : showWarning(`Failed to update package.json env: ${envUpdateResult.error}`), console.info(), showInfo("Updating GitHub workflow with environment variables...");
603
+ if (envUpdateResult.success) {
604
+ showSuccess("\u2713 Removed SST-specific environment variables from package.json");
605
+ } else {
606
+ showWarning(`Failed to update package.json env: ${envUpdateResult.error}`);
607
+ }
608
+ console.info();
609
+ showInfo("Updating GitHub workflow with environment variables...");
281
610
  try {
282
611
  execSync("bun env:update", {
283
612
  cwd,
284
613
  stdio: "ignore"
285
- }), showSuccess("\u2713 GitHub workflow updated");
286
- } catch {
614
+ });
615
+ showSuccess("\u2713 GitHub workflow updated");
616
+ } catch (error) {
287
617
  showWarning("Failed to update GitHub workflow (non-critical)");
288
618
  }
289
- if (console.info(), await confirmContinue("Sync environment to GitHub secrets for CI/CD?", !0)) try {
290
- execSync("bun scripts/env/sync-to-github.ts --yes", {
291
- cwd,
292
- stdio: "inherit"
293
- });
294
- } catch {
295
- showWarning("Failed to sync to GitHub (you can do this later)"), showInfo("Run manually: bun scripts/env/sync-to-github.ts");
296
- } else showInfo("You can sync later with: bun scripts/env/sync-to-github.ts");
297
- console.info(), showStep("Ready to deploy!"), console.info(), console.info(pc.bold("Next steps:")), console.info(), console.info(pc.cyan("1. Install Uncloud CLI (if not already installed):")), console.info(pc.gray(" npm install -g uncloud-cli")), console.info(), console.info(pc.cyan("2. Login to Uncloud:")), console.info(pc.gray(" uncloud login")), console.info(), console.info(pc.cyan("3. Deploy your app:")), console.info(pc.gray(" bun tko uncloud deploy-prod")), console.info(), console.info(pc.cyan("Or push to main branch for automatic CI/CD deployment:")), console.info(pc.gray(" git push origin main")), console.info(), console.info(pc.bold("scaling to multiple machines:")), console.info(pc.gray(" uc machine add --name server-2 root@IP")), console.info(pc.gray(" uc scale web 3 # run 3 instances")), console.info(), console.info(pc.gray("view detailed guide: bun tko docs read deployment-uncloud")), console.info(pc.gray("or see: docs/deployment-uncloud.md"));
619
+ console.info();
620
+ const syncToGitHub = await confirmContinue("Sync environment to GitHub secrets for CI/CD?", true);
621
+ if (syncToGitHub) {
622
+ try {
623
+ execSync("bun scripts/env/sync-to-github.ts --yes", {
624
+ cwd,
625
+ stdio: "inherit"
626
+ });
627
+ } catch (error) {
628
+ showWarning("Failed to sync to GitHub (you can do this later)");
629
+ showInfo("Run manually: bun scripts/env/sync-to-github.ts");
630
+ }
631
+ } else {
632
+ showInfo("You can sync later with: bun scripts/env/sync-to-github.ts");
633
+ }
634
+ console.info();
635
+ showStep("Ready to deploy!");
636
+ console.info();
637
+ console.info(pc.bold("Next steps:"));
638
+ console.info();
639
+ console.info(pc.cyan("1. Install Uncloud CLI (if not already installed):"));
640
+ console.info(pc.gray(" npm install -g uncloud-cli"));
641
+ console.info();
642
+ console.info(pc.cyan("2. Login to Uncloud:"));
643
+ console.info(pc.gray(" uncloud login"));
644
+ console.info();
645
+ console.info(pc.cyan("3. Deploy your app:"));
646
+ console.info(pc.gray(" bun tko uncloud deploy-prod"));
647
+ console.info();
648
+ console.info(pc.cyan("Or push to main branch for automatic CI/CD deployment:"));
649
+ console.info(pc.gray(" git push origin main"));
650
+ console.info();
651
+ console.info(pc.bold("scaling to multiple machines:"));
652
+ console.info(pc.gray(" uc machine add --name server-2 root@IP"));
653
+ console.info(pc.gray(" uc scale web 3 # run 3 instances"));
654
+ console.info();
655
+ console.info(pc.gray("view detailed guide: bun tko docs read deployment-uncloud"));
656
+ console.info(pc.gray("or see: docs/deployment-uncloud.md"));
298
657
  }
299
658
  async function setupSSTDeployment(cwd) {
300
- showInfo("Setting up AWS SST deployment"), console.info(), showWarning("AWS setup takes approximately 30 minutes"), console.info(), console.info(pc.gray(`SST provides:
301
- \u2022 AWS infrastructure (ECS, Aurora, ALB)
302
- \u2022 Auto-scaling
303
- \u2022 Full control over resources
304
- \u2022 Higher setup complexity`)), console.info(), console.info(), showInfo("AWS ECS Architecture"), console.info(pc.gray("AWS Graviton (ARM64) is ~40% cheaper than x86_64")), console.info(pc.gray("Both have excellent performance, ARM recommended for cost savings")), console.info();
659
+ showInfo("Setting up AWS SST deployment");
660
+ console.info();
661
+ showWarning("AWS setup takes approximately 30 minutes");
662
+ console.info();
663
+ console.info(pc.gray("SST provides:\n \u2022 AWS infrastructure (ECS, Aurora, ALB)\n \u2022 Auto-scaling\n \u2022 Full control over resources\n \u2022 Higher setup complexity"));
664
+ console.info();
665
+ console.info();
666
+ showInfo("AWS ECS Architecture");
667
+ console.info(pc.gray("AWS Graviton (ARM64) is ~40% cheaper than x86_64"));
668
+ console.info(pc.gray("Both have excellent performance, ARM recommended for cost savings"));
669
+ console.info();
305
670
  const architecture = await promptSelect("What CPU architecture for AWS ECS?", [{
306
671
  value: "arm64",
307
672
  label: "ARM64 (Graviton) - Recommended",
@@ -316,49 +681,78 @@ async function setupSSTDeployment(cwd) {
316
681
  return;
317
682
  }
318
683
  const deploymentArch = architecture === "arm64" ? "linux/arm64" : "linux/amd64";
319
- if (console.info(), showInfo(`Will build Docker images for: ${deploymentArch}`), console.info(), !(await confirmContinue("Continue with AWS SST setup? (requires AWS account)", !1))) {
320
- showInfo("Skipping AWS setup"), showInfo("You can set up AWS later with: bun tko env:setup --category aws");
684
+ console.info();
685
+ showInfo(`Will build Docker images for: ${deploymentArch}`);
686
+ console.info();
687
+ const shouldContinue = await confirmContinue("Continue with AWS SST setup? (requires AWS account)", false);
688
+ if (!shouldContinue) {
689
+ showInfo("Skipping AWS setup");
690
+ showInfo("You can set up AWS later with: bun tko env:setup --category aws");
321
691
  return;
322
692
  }
323
693
  await setupProductionEnv(cwd, {
324
694
  onlyCategory: "aws",
325
695
  envFile: ".env.production",
326
- interactive: !0
696
+ interactive: true
327
697
  });
328
698
  const envFile = ".env.production";
329
- updateEnvVariable(cwd, "DEPLOYMENT_PLATFORM", "sst", envFile), updateEnvVariable(cwd, "DEPLOYMENT_ARCH", deploymentArch, envFile), console.info(), showInfo("Updating sst.config.ts architecture...");
699
+ updateEnvVariable(cwd, "DEPLOYMENT_PLATFORM", "sst", envFile);
700
+ updateEnvVariable(cwd, "DEPLOYMENT_ARCH", deploymentArch, envFile);
701
+ console.info();
702
+ showInfo("Updating sst.config.ts architecture...");
330
703
  try {
331
704
  const sstConfigPath = resolve(cwd, "sst.config.ts");
332
705
  let sstConfig = readFileSync(sstConfigPath, "utf-8");
333
706
  const archValue = architecture === "arm64" ? "arm64" : "x86_64";
334
- sstConfig = sstConfig.replace(/architecture:\s*['"]arm64['"]/g, `architecture: '${archValue}'`), sstConfig = sstConfig.replace(/architecture:\s*['"]x86_64['"]/g, `architecture: '${archValue}'`), writeFileSync(sstConfigPath, sstConfig), showSuccess(`\u2713 Updated sst.config.ts to use ${archValue}`);
335
- } catch {
707
+ sstConfig = sstConfig.replace(/architecture:\s*['"]arm64['"]/g, `architecture: '${archValue}'`);
708
+ sstConfig = sstConfig.replace(/architecture:\s*['"]x86_64['"]/g, `architecture: '${archValue}'`);
709
+ writeFileSync(sstConfigPath, sstConfig);
710
+ showSuccess(`\u2713 Updated sst.config.ts to use ${archValue}`);
711
+ } catch (error) {
336
712
  showWarning("Could not update sst.config.ts (you can update manually)");
337
713
  }
338
- console.info(), showInfo("Updating package.json env section for SST deployment...");
714
+ console.info();
715
+ showInfo("Updating package.json env section for SST deployment...");
339
716
  const envUpdateResult = updatePackageJsonEnv(cwd, "sst");
340
- envUpdateResult.success ? showSuccess("\u2713 Removed Uncloud-specific environment variables from package.json") : showWarning(`Failed to update package.json env: ${envUpdateResult.error}`), console.info(), showInfo("Updating GitHub workflow with environment variables...");
717
+ if (envUpdateResult.success) {
718
+ showSuccess("\u2713 Removed Uncloud-specific environment variables from package.json");
719
+ } else {
720
+ showWarning(`Failed to update package.json env: ${envUpdateResult.error}`);
721
+ }
722
+ console.info();
723
+ showInfo("Updating GitHub workflow with environment variables...");
341
724
  try {
342
725
  execSync("bun env:update", {
343
726
  cwd,
344
727
  stdio: "ignore"
345
- }), showSuccess("\u2713 GitHub workflow updated");
346
- } catch {
728
+ });
729
+ showSuccess("\u2713 GitHub workflow updated");
730
+ } catch (error) {
347
731
  showWarning("Failed to update GitHub workflow (non-critical)");
348
732
  }
349
- if (console.info(), await confirmContinue("Sync environment to GitHub secrets for CI/CD?", !0)) try {
350
- execSync("bun scripts/env/sync-to-github.ts --yes", {
351
- cwd,
352
- stdio: "inherit"
353
- });
354
- } catch {
355
- showWarning("Failed to sync to GitHub (you can do this later)"), showInfo("Run manually: bun scripts/env/sync-to-github.ts");
356
- } else showInfo("You can sync later with: bun scripts/env/sync-to-github.ts");
357
- console.info(), showInfo("For complete AWS deployment guide, see: https://docs.yourapp.com/deployment/sst");
733
+ console.info();
734
+ const syncToGitHub = await confirmContinue("Sync environment to GitHub secrets for CI/CD?", true);
735
+ if (syncToGitHub) {
736
+ try {
737
+ execSync("bun scripts/env/sync-to-github.ts --yes", {
738
+ cwd,
739
+ stdio: "inherit"
740
+ });
741
+ } catch (error) {
742
+ showWarning("Failed to sync to GitHub (you can do this later)");
743
+ showInfo("Run manually: bun scripts/env/sync-to-github.ts");
744
+ }
745
+ } else {
746
+ showInfo("You can sync later with: bun scripts/env/sync-to-github.ts");
747
+ }
748
+ console.info();
749
+ showInfo("For complete AWS deployment guide, see: https://docs.yourapp.com/deployment/sst");
358
750
  }
359
751
  async function configureCIRunners(cwd) {
360
- showInfo("GitHub Actions CI/CD Runner Configuration"), console.info(), console.info(pc.gray(`Your project uses ARM64 (Apple Silicon) architecture for Docker builds.
361
- GitHub Actions requires compatible runners for CI/CD to work properly.`)), console.info();
752
+ showInfo("GitHub Actions CI/CD Runner Configuration");
753
+ console.info();
754
+ console.info(pc.gray("Your project uses ARM64 (Apple Silicon) architecture for Docker builds.\nGitHub Actions requires compatible runners for CI/CD to work properly."));
755
+ console.info();
362
756
  const runnerChoice = await promptSelect("Choose your CI runner configuration:", [{
363
757
  value: "warp",
364
758
  label: "Warp Runners (Recommended)",
@@ -377,41 +771,79 @@ GitHub Actions requires compatible runners for CI/CD to work properly.`)), conso
377
771
  hint: "Skip for now (CI will fail until configured)"
378
772
  }]);
379
773
  if (runnerChoice === "cancel" || runnerChoice === "skip") {
380
- showInfo("Skipping CI runner configuration"), showWarning("CI/CD will fail until you configure runners. Update .github/workflows/ci.yml");
774
+ showInfo("Skipping CI runner configuration");
775
+ showWarning("CI/CD will fail until you configure runners. Update .github/workflows/ci.yml");
381
776
  return;
382
777
  }
383
- const ciConfigPath = resolve(cwd, ".github/workflows/ci.yml"),
384
- dockerBuildPath = resolve(cwd, "scripts/web/build-docker.ts");
778
+ const ciConfigPath = resolve(cwd, ".github/workflows/ci.yml");
779
+ const dockerBuildPath = resolve(cwd, "scripts/web/build-docker.ts");
385
780
  try {
386
- let ciContent = readFileSync(ciConfigPath, "utf-8"),
387
- dockerContent = readFileSync(dockerBuildPath, "utf-8");
388
- runnerChoice === "warp" ? (console.info(), showStep("Setting up Warp runners"), console.info(), console.info(pc.cyan("1. Sign up for Warp Build (if not already):")), console.info(pc.gray(" https://www.warpbuild.com")), console.info(), console.info(pc.cyan("2. Install Warp GitHub App:")), console.info(pc.gray(" https://github.com/apps/warp-build")), console.info(), console.info(pc.cyan("3. Grant access to your repository")), console.info(), showSuccess("\u2713 CI configuration already set for Warp runners"), showInfo("Warp uses ARM64 runners matching your local architecture")) : runnerChoice === "github-arm" ? (ciContent = ciContent.replace(/runs-on:.*warp-ubuntu-latest-arm64.*/, "runs-on: ubuntu-24.04-arm"), writeFileSync(ciConfigPath, ciContent), console.info(), showSuccess("\u2713 Updated CI to use GitHub ARM runners"), showWarning("Note: GitHub ARM runners require Teams or Enterprise plan"), showInfo("Pricing: $0.16/minute for ARM runners")) : runnerChoice === "github-x64" && (ciContent = ciContent.replace(/runs-on:.*warp-ubuntu-latest-arm64.*/, "runs-on: ubuntu-latest"), writeFileSync(ciConfigPath, ciContent), dockerContent = dockerContent.replace("linux/arm64", "linux/amd64"), writeFileSync(dockerBuildPath, dockerContent), console.info(), showSuccess("\u2713 Updated CI to use free GitHub x64 runners"), showSuccess("\u2713 Updated Docker builds to x64 architecture"), showWarning("Note: Docker images built on x64 won't run on ARM64 machines without emulation")), console.info(), showInfo("CI runner configuration complete");
389
- } catch {
390
- showError("Failed to update CI configuration"), showInfo("Please manually update .github/workflows/ci.yml and scripts/web/build-docker.ts");
781
+ let ciContent = readFileSync(ciConfigPath, "utf-8");
782
+ let dockerContent = readFileSync(dockerBuildPath, "utf-8");
783
+ if (runnerChoice === "warp") {
784
+ console.info();
785
+ showStep("Setting up Warp runners");
786
+ console.info();
787
+ console.info(pc.cyan("1. Sign up for Warp Build (if not already):"));
788
+ console.info(pc.gray(" https://www.warpbuild.com"));
789
+ console.info();
790
+ console.info(pc.cyan("2. Install Warp GitHub App:"));
791
+ console.info(pc.gray(" https://github.com/apps/warp-build"));
792
+ console.info();
793
+ console.info(pc.cyan("3. Grant access to your repository"));
794
+ console.info();
795
+ showSuccess("\u2713 CI configuration already set for Warp runners");
796
+ showInfo("Warp uses ARM64 runners matching your local architecture");
797
+ } else if (runnerChoice === "github-arm") {
798
+ ciContent = ciContent.replace(/runs-on:.*warp-ubuntu-latest-arm64.*/, "runs-on: ubuntu-24.04-arm");
799
+ writeFileSync(ciConfigPath, ciContent);
800
+ console.info();
801
+ showSuccess("\u2713 Updated CI to use GitHub ARM runners");
802
+ showWarning("Note: GitHub ARM runners require Teams or Enterprise plan");
803
+ showInfo("Pricing: $0.16/minute for ARM runners");
804
+ } else if (runnerChoice === "github-x64") {
805
+ ciContent = ciContent.replace(/runs-on:.*warp-ubuntu-latest-arm64.*/, "runs-on: ubuntu-latest");
806
+ writeFileSync(ciConfigPath, ciContent);
807
+ dockerContent = dockerContent.replace("linux/arm64", "linux/amd64");
808
+ writeFileSync(dockerBuildPath, dockerContent);
809
+ console.info();
810
+ showSuccess("\u2713 Updated CI to use free GitHub x64 runners");
811
+ showSuccess("\u2713 Updated Docker builds to x64 architecture");
812
+ showWarning("Note: Docker images built on x64 won't run on ARM64 machines without emulation");
813
+ }
814
+ console.info();
815
+ showInfo("CI runner configuration complete");
816
+ } catch (error) {
817
+ showError("Failed to update CI configuration");
818
+ showInfo("Please manually update .github/workflows/ci.yml and scripts/web/build-docker.ts");
391
819
  }
392
820
  }
393
821
  async function replacePortInProject(cwd, oldPort, newPort) {
394
- const spinner = showSpinner(`Replacing port ${oldPort} with ${newPort} throughout project...`),
395
- excludeDirs = /* @__PURE__ */new Set(["node_modules", ".git", "dist", "build", ".next", ".turbo", "types"]),
396
- includeExts = /* @__PURE__ */new Set([".ts", ".tsx", ".js", ".jsx", ".json", ".md", ".yml", ".yaml"]);
822
+ const spinner = showSpinner(`Replacing port ${oldPort} with ${newPort} throughout project...`);
823
+ const excludeDirs = /* @__PURE__ */new Set(["node_modules", ".git", "dist", "build", ".next", ".turbo", "types"]);
824
+ const includeExts = /* @__PURE__ */new Set([".ts", ".tsx", ".js", ".jsx", ".json", ".md", ".yml", ".yaml"]);
397
825
  async function processDir(dir) {
398
826
  const {
399
- readdirSync,
400
- statSync
401
- } = await import("node:fs"),
402
- {
403
- join,
404
- extname,
405
- basename
406
- } = await import("node:path"),
407
- entries = readdirSync(dir);
827
+ readdirSync,
828
+ statSync
829
+ } = await import("node:fs");
830
+ const {
831
+ join,
832
+ extname,
833
+ basename
834
+ } = await import("node:path");
835
+ const entries = readdirSync(dir);
408
836
  for (const entry of entries) {
409
837
  const fullPath = join(dir, entry);
410
838
  try {
411
839
  const stat = statSync(fullPath);
412
- if (stat.isDirectory()) excludeDirs.has(entry) || (await processDir(fullPath));else if (stat.isFile()) {
413
- const ext = extname(entry),
414
- name = basename(entry);
840
+ if (stat.isDirectory()) {
841
+ if (!excludeDirs.has(entry)) {
842
+ await processDir(fullPath);
843
+ }
844
+ } else if (stat.isFile()) {
845
+ const ext = extname(entry);
846
+ const name = basename(entry);
415
847
  if (includeExts.has(ext) || name.startsWith(".env")) {
416
848
  const content = readFileSync(fullPath, "utf-8");
417
849
  if (content.includes(oldPort)) {
@@ -424,24 +856,32 @@ async function replacePortInProject(cwd, oldPort, newPort) {
424
856
  }
425
857
  }
426
858
  try {
427
- await processDir(cwd), spinner.stop(`Port updated from ${oldPort} to ${newPort}`);
859
+ await processDir(cwd);
860
+ spinner.stop(`Port updated from ${oldPort} to ${newPort}`);
428
861
  } catch (error) {
429
- throw spinner.stop("Port replacement failed"), showError("Failed to replace port. You may need to update manually."), error;
862
+ spinner.stop("Port replacement failed");
863
+ showError("Failed to replace port. You may need to update manually.");
864
+ throw error;
430
865
  }
431
866
  }
432
867
  function getStatePath(cwd) {
433
868
  return resolve(cwd, "node_modules/.takeout/onboard-state.json");
434
869
  }
435
870
  function saveOnboardState(cwd, state) {
436
- const statePath = getStatePath(cwd),
437
- stateDir = resolve(cwd, "node_modules/.takeout");
438
- existsSync(stateDir) || mkdirSync(stateDir, {
439
- recursive: !0
440
- }), writeFileSync(statePath, JSON.stringify(state, null, 2));
871
+ const statePath = getStatePath(cwd);
872
+ const stateDir = resolve(cwd, "node_modules/.takeout");
873
+ if (!existsSync(stateDir)) {
874
+ mkdirSync(stateDir, {
875
+ recursive: true
876
+ });
877
+ }
878
+ writeFileSync(statePath, JSON.stringify(state, null, 2));
441
879
  }
442
880
  function loadOnboardState(cwd) {
443
881
  const statePath = getStatePath(cwd);
444
- if (!existsSync(statePath)) return null;
882
+ if (!existsSync(statePath)) {
883
+ return null;
884
+ }
445
885
  try {
446
886
  const content = readFileSync(statePath, "utf-8");
447
887
  return JSON.parse(content);
@@ -451,9 +891,11 @@ function loadOnboardState(cwd) {
451
891
  }
452
892
  function clearOnboardState(cwd) {
453
893
  const statePath = getStatePath(cwd);
454
- if (existsSync(statePath)) try {
455
- unlinkSync(statePath);
456
- } catch {}
894
+ if (existsSync(statePath)) {
895
+ try {
896
+ unlinkSync(statePath);
897
+ } catch {}
898
+ }
457
899
  }
458
900
  export { onboardCommand };
459
901
  //# sourceMappingURL=onboard.mjs.map