create-better-t-stack 3.11.0-bun-compile-opentui.37fc326 → 3.11.0-bun-compile-opentui.a1fcb7b

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.11.0-bun-compile-opentui.37fc326",
3
+ "version": "3.11.0-bun-compile-opentui.a1fcb7b",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "keywords": [
6
6
  "better-auth",
@@ -68,13 +68,16 @@
68
68
  "prepublishOnly": "echo 'Build binaries separately before publishing'"
69
69
  },
70
70
  "dependencies": {
71
- "@better-t-stack/types": "3.11.0-bun-compile-opentui.37fc326",
71
+ "@better-t-stack/types": "3.11.0-bun-compile-opentui.a1fcb7b",
72
+ "@clack/prompts": "^1.0.0-alpha.8",
72
73
  "@opentui/core": "^0.1.62",
73
74
  "@opentui/react": "^0.1.62",
74
75
  "commander": "^14.0.2",
76
+ "consola": "^3.4.2",
75
77
  "fs-extra": "^11.3.3",
76
78
  "handlebars": "^4.7.8",
77
79
  "jsonc-parser": "^3.3.1",
80
+ "picocolors": "^1.1.1",
78
81
  "react": "^19.1.0",
79
82
  "tinyglobby": "^0.2.15",
80
83
  "ts-morph": "^27.0.2",
@@ -90,11 +93,11 @@
90
93
  "typescript": "^5.9.3"
91
94
  },
92
95
  "optionalDependencies": {
93
- "@better-t-stack/cli-darwin-arm64": "3.11.0-bun-compile-opentui.37fc326",
94
- "@better-t-stack/cli-darwin-x64": "3.11.0-bun-compile-opentui.37fc326",
95
- "@better-t-stack/cli-linux-arm64": "3.11.0-bun-compile-opentui.37fc326",
96
- "@better-t-stack/cli-linux-x64": "3.11.0-bun-compile-opentui.37fc326",
97
- "@better-t-stack/cli-windows-x64": "3.11.0-bun-compile-opentui.37fc326",
96
+ "@better-t-stack/cli-darwin-arm64": "3.11.0-bun-compile-opentui.a1fcb7b",
97
+ "@better-t-stack/cli-darwin-x64": "3.11.0-bun-compile-opentui.a1fcb7b",
98
+ "@better-t-stack/cli-linux-arm64": "3.11.0-bun-compile-opentui.a1fcb7b",
99
+ "@better-t-stack/cli-linux-x64": "3.11.0-bun-compile-opentui.a1fcb7b",
100
+ "@better-t-stack/cli-windows-x64": "3.11.0-bun-compile-opentui.a1fcb7b",
98
101
  "@opentui/core-darwin-arm64": "0.1.63",
99
102
  "@opentui/core-darwin-x64": "0.1.63",
100
103
  "@opentui/core-linux-arm64": "0.1.63",
@@ -1,5 +1,4 @@
1
1
  import path from "node:path";
2
- import { log } from "@clack/prompts";
3
2
  import fs from "fs-extra";
4
3
  import pc from "picocolors";
5
4
  import type { Frontend, ProjectConfig } from "../../types";
@@ -32,7 +31,7 @@ export async function setupAddons(config: ProjectConfig, isAddCommand = false) {
32
31
  });
33
32
 
34
33
  if (isAddCommand) {
35
- log.info(`${pc.yellow("Update your package.json scripts:")}
34
+ console.log(`ℹ ${pc.yellow("Update your package.json scripts:")}
36
35
 
37
36
  ${pc.dim("Replace:")} ${pc.yellow('"pnpm -r dev"')} ${pc.dim("→")} ${pc.green('"turbo dev"')}
38
37
  ${pc.dim("Replace:")} ${pc.yellow('"pnpm --filter web dev"')} ${pc.dim(
@@ -1,10 +1,10 @@
1
1
  import path from "node:path";
2
- import { spinner } from "@clack/prompts";
3
2
  import { $ } from "bun";
4
3
  import fs from "fs-extra";
5
4
  import type { PackageManager } from "../../types";
6
5
  import { addPackageDependency } from "../../utils/add-package-deps";
7
6
  import { getPackageExecutionCommand } from "../../utils/package-runner";
7
+ import { log } from "../../utils/logger";
8
8
 
9
9
  export async function setupOxlint(projectDir: string, packageManager: PackageManager) {
10
10
  await addPackageDependency({
@@ -24,13 +24,13 @@ export async function setupOxlint(projectDir: string, packageManager: PackageMan
24
24
  await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
25
25
  }
26
26
 
27
- const s = spinner();
27
+ log.step("Initializing oxlint and oxfmt...");
28
28
 
29
29
  const oxlintInitCommand = getPackageExecutionCommand(packageManager, "oxlint@latest --init");
30
- s.start("Initializing oxlint and oxfmt...");
31
30
  await $`${{ raw: oxlintInitCommand }}`.cwd(projectDir).env({ CI: "true" });
32
31
 
33
32
  const oxfmtInitCommand = getPackageExecutionCommand(packageManager, "oxfmt@latest --init");
34
33
  await $`${{ raw: oxfmtInitCommand }}`.cwd(projectDir).env({ CI: "true" });
35
- s.stop("oxlint and oxfmt initialized successfully!");
34
+
35
+ log.success("oxlint and oxfmt initialized successfully!");
36
36
  }
@@ -1,18 +1,17 @@
1
1
  import path from "node:path";
2
- import { spinner } from "@clack/prompts";
3
2
  import consola from "consola";
4
3
  import { $ } from "bun";
5
4
  import fs from "fs-extra";
6
5
  import pc from "picocolors";
7
6
  import type { ProjectConfig } from "../../types";
8
7
  import { getPackageExecutionCommand } from "../../utils/package-runner";
8
+ import { log } from "../../utils/logger";
9
9
 
10
10
  export async function setupStarlight(config: ProjectConfig) {
11
11
  const { packageManager, projectDir } = config;
12
- const s = spinner();
13
12
 
14
13
  try {
15
- s.start("Setting up Starlight docs...");
14
+ log.step("Setting up Starlight docs...");
16
15
 
17
16
  const starlightArgs = [
18
17
  "docs",
@@ -35,9 +34,9 @@ export async function setupStarlight(config: ProjectConfig) {
35
34
 
36
35
  await $`${{ raw: starlightInitCommand }}`.cwd(appsDir).env({ CI: "true" });
37
36
 
38
- s.stop("Starlight docs setup successfully!");
37
+ log.success("Starlight docs setup successfully!");
39
38
  } catch (error) {
40
- s.stop(pc.red("Failed to set up Starlight docs"));
39
+ log.error("Failed to set up Starlight docs");
41
40
  if (error instanceof Error) {
42
41
  consola.error(pc.red(error.message));
43
42
  }
@@ -1,5 +1,4 @@
1
1
  import path from "node:path";
2
- import { spinner } from "@clack/prompts";
3
2
  import { consola } from "consola";
4
3
  import { $ } from "bun";
5
4
  import fs from "fs-extra";
@@ -7,10 +6,10 @@ import pc from "picocolors";
7
6
  import type { ProjectConfig } from "../../types";
8
7
  import { addPackageDependency } from "../../utils/add-package-deps";
9
8
  import { getPackageExecutionCommand } from "../../utils/package-runner";
9
+ import { log } from "../../utils/logger";
10
10
 
11
11
  export async function setupTauri(config: ProjectConfig) {
12
12
  const { packageManager, frontend, projectDir } = config;
13
- const s = spinner();
14
13
  const clientPackageDir = path.join(projectDir, "apps/web");
15
14
 
16
15
  if (!(await fs.pathExists(clientPackageDir))) {
@@ -18,7 +17,7 @@ export async function setupTauri(config: ProjectConfig) {
18
17
  }
19
18
 
20
19
  try {
21
- s.start("Setting up Tauri desktop app support...");
20
+ log.step("Setting up Tauri desktop app support...");
22
21
 
23
22
  await addPackageDependency({
24
23
  devDependencies: ["@tauri-apps/cli"],
@@ -80,9 +79,9 @@ export async function setupTauri(config: ProjectConfig) {
80
79
 
81
80
  await $`${{ raw: tauriInitCommand }}`.cwd(clientPackageDir).env({ CI: "true" });
82
81
 
83
- s.stop("Tauri desktop app support configured successfully!");
82
+ log.success("Tauri desktop app support configured successfully!");
84
83
  } catch (error) {
85
- s.stop(pc.red("Failed to set up Tauri"));
84
+ log.error("Failed to set up Tauri");
86
85
  if (error instanceof Error) {
87
86
  consola.error(pc.red(error.message));
88
87
  }
@@ -1,5 +1,4 @@
1
1
  import path from "node:path";
2
- import { log } from "@clack/prompts";
3
2
  import pc from "picocolors";
4
3
  import type { AddInput, Addons, ProjectConfig } from "../../types";
5
4
  import { updateBtsConfig } from "../../utils/bts-config";
@@ -74,8 +73,8 @@ export async function addAddonsToProject(
74
73
  packageManager: config.packageManager,
75
74
  });
76
75
  } else if (!input.suppressInstallMessage) {
77
- log.info(
78
- pc.yellow(`Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`),
76
+ console.log(
77
+ pc.yellow(`ℹ Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`),
79
78
  );
80
79
  }
81
80
  } catch (error) {
@@ -1,5 +1,4 @@
1
1
  import path from "node:path";
2
- import { log } from "@clack/prompts";
3
2
  import pc from "picocolors";
4
3
  import type { AddInput, ProjectConfig, ServerDeploy, WebDeploy } from "../../types";
5
4
  import { updateBtsConfig } from "../../utils/bts-config";
@@ -67,13 +66,13 @@ export async function addDeploymentToProject(
67
66
  };
68
67
 
69
68
  if (input.webDeploy && input.webDeploy !== "none") {
70
- log.info(
71
- pc.green(`Adding ${input.webDeploy} web deployment to ${config.frontend.join("/")}`),
69
+ console.log(
70
+ pc.green(`ℹ Adding ${input.webDeploy} web deployment to ${config.frontend.join("/")}`),
72
71
  );
73
72
  }
74
73
 
75
74
  if (input.serverDeploy && input.serverDeploy !== "none") {
76
- log.info(pc.green(`Adding ${input.serverDeploy} server deployment`));
75
+ console.log(pc.green(`ℹ Adding ${input.serverDeploy} server deployment`));
77
76
  }
78
77
 
79
78
  await setupDeploymentTemplates(projectDir, config);
@@ -91,8 +90,8 @@ export async function addDeploymentToProject(
91
90
  packageManager: config.packageManager,
92
91
  });
93
92
  } else if (!input.suppressInstallMessage) {
94
- log.info(
95
- pc.yellow(`Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`),
93
+ console.log(
94
+ pc.yellow(`ℹ Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`),
96
95
  );
97
96
  }
98
97
  } catch (error) {
@@ -1,6 +1,6 @@
1
- import { log } from "@clack/prompts";
2
1
  import fs from "fs-extra";
3
2
  import type { ProjectConfig } from "../../types";
3
+ import { log } from "../../utils/logger";
4
4
  import { writeBtsConfig } from "../../utils/bts-config";
5
5
  import { exitWithError } from "../../utils/errors";
6
6
  import { setupCatalogs } from "../../utils/setup-catalogs";
@@ -1,4 +1,3 @@
1
- import { log } from "@clack/prompts";
2
1
  import { $ } from "bun";
3
2
  import pc from "picocolors";
4
3
 
@@ -8,7 +7,7 @@ export async function initializeGit(projectDir: string, useGit: boolean) {
8
7
  const gitVersionResult = await $`git --version`.cwd(projectDir).nothrow().quiet();
9
8
 
10
9
  if (gitVersionResult.exitCode !== 0) {
11
- log.warn(pc.yellow("Git is not installed"));
10
+ console.warn(pc.yellow("Git is not installed"));
12
11
  return;
13
12
  }
14
13
 
@@ -1,8 +1,8 @@
1
- import { spinner } from "@clack/prompts";
2
1
  import consola from "consola";
3
2
  import { $ } from "bun";
4
3
  import pc from "picocolors";
5
4
  import type { Addons, PackageManager } from "../../types";
5
+ import { log } from "../../utils/logger";
6
6
 
7
7
  export async function installDependencies({
8
8
  projectDir,
@@ -12,16 +12,14 @@ export async function installDependencies({
12
12
  packageManager: PackageManager;
13
13
  addons?: Addons[];
14
14
  }) {
15
- const s = spinner();
16
-
17
15
  try {
18
- s.start(`Running ${packageManager} install...`);
16
+ log.step(`Running ${packageManager} install...`);
19
17
 
20
18
  await $`${packageManager} install`.cwd(projectDir);
21
19
 
22
- s.stop("Dependencies installed successfully");
20
+ log.success("Dependencies installed successfully");
23
21
  } catch (error) {
24
- s.stop(pc.red("Failed to install dependencies"));
22
+ log.error("Failed to install dependencies");
25
23
  if (error instanceof Error) {
26
24
  consola.error(pc.red(`Installation error: ${error.message}`));
27
25
  }
@@ -1,5 +1,4 @@
1
1
  import path from "node:path";
2
- import { log } from "@clack/prompts";
3
2
  import { $ } from "bun";
4
3
  import fs from "fs-extra";
5
4
  import type { ProjectConfig } from "../../types";
@@ -96,7 +95,7 @@ async function updateRootPackageJson(projectDir: string, options: ProjectConfig)
96
95
  const stdout = await $`${packageManager} -v`.cwd(projectDir).text();
97
96
  packageJson.packageManager = `${packageManager}@${stdout.trim()}`;
98
97
  } catch {
99
- log.warn(`Could not determine ${packageManager} version.`);
98
+ console.warn(`⚠ Could not determine ${packageManager} version.`);
100
99
  }
101
100
 
102
101
  if (backend === "convex") {
package/src/tui/app.tsx CHANGED
@@ -5,6 +5,8 @@
5
5
  import { createCliRenderer } from "@opentui/core";
6
6
  import { createRoot, useKeyboard, useTerminalDimensions } from "@opentui/react";
7
7
  import { useState, useCallback, useEffect } from "react";
8
+ import { useLogger } from "./use-logger";
9
+ import type { LogEntry } from "../utils/logger";
8
10
  import type {
9
11
  ProjectConfig,
10
12
  Frontend,
@@ -558,6 +560,40 @@ function Spinner(props: { text: string }) {
558
560
  );
559
561
  }
560
562
 
563
+ // LogDisplay component - shows real-time logs during project creation
564
+ function LogDisplay() {
565
+ const logs = useLogger();
566
+
567
+ const getLogIcon = (level: LogEntry["level"]) => {
568
+ switch (level) {
569
+ case "success":
570
+ return { icon: "✓", color: theme.success };
571
+ case "error":
572
+ return { icon: "✗", color: theme.error };
573
+ case "warn":
574
+ return { icon: "⚠", color: "#f59e0b" };
575
+ case "step":
576
+ return { icon: "→", color: theme.primary };
577
+ default:
578
+ return { icon: "ℹ", color: theme.muted };
579
+ }
580
+ };
581
+
582
+ return (
583
+ <box style={{ flexDirection: "column" }}>
584
+ {logs.slice(-15).map((log) => {
585
+ const { icon, color } = getLogIcon(log.level);
586
+ return (
587
+ <text key={log.id}>
588
+ <span fg={color}>{icon}</span>
589
+ <span fg={theme.text}> {log.message}</span>
590
+ </text>
591
+ );
592
+ })}
593
+ </box>
594
+ );
595
+ }
596
+
561
597
  type Phase = "prompts" | "creating" | "done";
562
598
 
563
599
  function App(props: {
@@ -788,17 +824,20 @@ function App(props: {
788
824
  </>
789
825
  )}
790
826
 
791
- {/* Creating Phase - Show spinner */}
827
+ {/* Creating Phase - Show spinner and logs */}
792
828
  {phase === "creating" && (
793
- <box style={{ flexDirection: "column", marginTop: 2 }}>
829
+ <box style={{ flexDirection: "column", marginTop: 2, flexGrow: 1 }}>
794
830
  <Spinner text={creationStatus} />
795
- <box style={{ marginTop: 2 }}>
831
+ <box style={{ marginTop: 1 }}>
796
832
  <text>
797
833
  <span fg={theme.muted}>Creating </span>
798
834
  <span fg={theme.primary}>{config.projectName}</span>
799
835
  <span fg={theme.muted}>...</span>
800
836
  </text>
801
837
  </box>
838
+ <box style={{ marginTop: 2, flexGrow: 1, overflow: "scroll" }}>
839
+ <LogDisplay />
840
+ </box>
802
841
  </box>
803
842
  )}
804
843
 
@@ -0,0 +1,44 @@
1
+ /**
2
+ * React hook for subscribing to the global logger
3
+ * Updates component state when new logs are emitted
4
+ */
5
+ import { useState, useEffect } from "react";
6
+ import { logger, type LogEntry } from "../utils/logger";
7
+
8
+ export function useLogger() {
9
+ const [logs, setLogs] = useState<LogEntry[]>([]);
10
+
11
+ useEffect(() => {
12
+ // Get existing logs
13
+ setLogs(logger.getLogs());
14
+
15
+ // Subscribe to new logs
16
+ const unsubscribe = logger.subscribe((entry) => {
17
+ setLogs((prev) => [...prev, entry]);
18
+ });
19
+
20
+ return unsubscribe;
21
+ }, []);
22
+
23
+ return logs;
24
+ }
25
+
26
+ // Hook to get only the latest log entry
27
+ export function useLatestLog() {
28
+ const [latestLog, setLatestLog] = useState<LogEntry | null>(null);
29
+
30
+ useEffect(() => {
31
+ const existing = logger.getLogs();
32
+ if (existing.length > 0) {
33
+ setLatestLog(existing[existing.length - 1]);
34
+ }
35
+
36
+ const unsubscribe = logger.subscribe((entry) => {
37
+ setLatestLog(entry);
38
+ });
39
+
40
+ return unsubscribe;
41
+ }, []);
42
+
43
+ return latestLog;
44
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * TUI-integrated logging system
3
+ * Provides a global logger that emits events for the TUI to display in real-time
4
+ */
5
+
6
+ export type LogLevel = "info" | "success" | "warn" | "error" | "step";
7
+
8
+ export interface LogEntry {
9
+ id: number;
10
+ level: LogLevel;
11
+ message: string;
12
+ timestamp: Date;
13
+ }
14
+
15
+ type LogListener = (entry: LogEntry) => void;
16
+
17
+ class Logger {
18
+ private listeners: Set<LogListener> = new Set();
19
+ private logs: LogEntry[] = [];
20
+ private idCounter = 0;
21
+
22
+ subscribe(listener: LogListener): () => void {
23
+ this.listeners.add(listener);
24
+ return () => this.listeners.delete(listener);
25
+ }
26
+
27
+ private emit(level: LogLevel, message: string) {
28
+ const entry: LogEntry = {
29
+ id: this.idCounter++,
30
+ level,
31
+ message,
32
+ timestamp: new Date(),
33
+ };
34
+ this.logs.push(entry);
35
+ for (const listener of this.listeners) {
36
+ listener(entry);
37
+ }
38
+ }
39
+
40
+ info(message: string) {
41
+ this.emit("info", message);
42
+ }
43
+
44
+ success(message: string) {
45
+ this.emit("success", message);
46
+ }
47
+
48
+ warn(message: string) {
49
+ this.emit("warn", message);
50
+ }
51
+
52
+ error(message: string) {
53
+ this.emit("error", message);
54
+ }
55
+
56
+ step(message: string) {
57
+ this.emit("step", message);
58
+ }
59
+
60
+ getLogs(): LogEntry[] {
61
+ return [...this.logs];
62
+ }
63
+
64
+ clear() {
65
+ this.logs = [];
66
+ this.idCounter = 0;
67
+ }
68
+ }
69
+
70
+ // Global logger instance
71
+ export const logger = new Logger();
72
+
73
+ // Convenience exports for easy importing
74
+ export const log = {
75
+ info: (msg: string) => logger.info(msg),
76
+ success: (msg: string) => logger.success(msg),
77
+ warn: (msg: string) => logger.warn(msg),
78
+ error: (msg: string) => logger.error(msg),
79
+ step: (msg: string) => logger.step(msg),
80
+ };
@@ -1,4 +1,3 @@
1
- import { log } from "@clack/prompts";
2
1
  import { $ } from "bun";
3
2
 
4
3
  export async function openUrl(url: string) {
@@ -13,6 +12,6 @@ export async function openUrl(url: string) {
13
12
  await $`xdg-open ${url}`.quiet();
14
13
  }
15
14
  } catch {
16
- log.message(`Please open ${url} in your browser.`);
15
+ console.log(`Please open ${url} in your browser.`);
17
16
  }
18
17
  }