Compare commits

...

9 Commits

Author SHA1 Message Date
RiskyMH
1b5d549262 fix tests 2025-02-12 05:59:26 +00:00
RiskyMH
2bcdb7c74f be better 2025-02-12 05:23:54 +00:00
RiskyMH
93458a2f51 init 2025-02-12 05:19:46 +00:00
RiskyMH
8d0024ca62 init to actually create stuff 2025-02-12 05:19:30 +00:00
RiskyMH
8d402a5b32 . 2025-02-11 15:52:31 +00:00
RiskyMH
8a0dbc5eee . 2025-02-11 15:45:31 +00:00
RiskyMH
f68bfd49ea . 2025-02-11 15:35:16 +00:00
RiskyMH
bd2119d440 more 2025-02-11 15:33:33 +00:00
RiskyMH
5695def739 some 2025-02-11 15:22:35 +00:00
7 changed files with 445 additions and 63 deletions

View File

@@ -1679,7 +1679,7 @@ pub const CreateCommand = struct {
pub fn onAnalyze(this: *@This(), result: *bun.bundle_v2.BundleV2.DependenciesScanner.Result) anyerror!void {
this.node.end();
try SourceFileProjectGenerator.generate(this.ctx, this.example_tag, this.entry_point, result);
try SourceFileProjectGenerator.generate(this.entry_point, result);
}
};

View File

@@ -24,6 +24,60 @@ const JSPrinter = bun.js_printer;
const exists = bun.sys.exists;
const existsZ = bun.sys.existsZ;
// make terminal input raw
fn setRawInput(b: bool) !void {
if (comptime Environment.isWindows) {
const ENABLE_ECHO_INPUT: u32 = 0x0004;
const ENABLE_LINE_INPUT: u32 = 0x0002;
const handle = std.io.getStdIn().handle;
var flags: u32 = undefined;
if (std.os.windows.kernel32.GetConsoleMode(handle, &flags) == 0) return error.NotATerminal;
if (b) {
flags &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT);
} else {
flags |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT;
}
std.debug.assert(std.os.windows.kernel32.SetConsoleMode(handle, flags) != 0);
} else {
var t: std.posix.termios = try std.posix.tcgetattr(std.posix.STDIN_FILENO);
t.lflag.ECHO = !b;
t.lflag.ICANON = !b;
t.lflag.FLUSHO = true;
try std.posix.tcsetattr(std.posix.STDIN_FILENO, .NOW, t);
}
}
// Convert string to PascalCase
fn toPascalCase(allocator: std.mem.Allocator, input: []const u8) ![]const u8 {
var result = try allocator.alloc(u8, input.len);
var output_index: usize = 0;
var capitalize_next = true;
for (input) |c| {
if (c == '-' or c == '_' or c == ' ') {
capitalize_next = true;
continue;
}
if (capitalize_next) {
if (c >= 'a' and c <= 'z') {
result[output_index] = c - 32;
} else {
result[output_index] = c;
}
capitalize_next = false;
} else {
result[output_index] = c;
}
output_index += 1;
}
return result[0..output_index];
}
pub const InitCommand = struct {
pub fn prompt(
alloc: std.mem.Allocator,
@@ -59,6 +113,148 @@ pub const InitCommand = struct {
}
}
pub fn promptBool(
_: std.mem.Allocator,
comptime label: []const u8,
default: bool,
) !bool {
Output.prettyln(label, .{});
Output.flush();
const original_mode: if (Environment.isWindows) ?bun.windows.DWORD else void = if (comptime Environment.isWindows)
bun.win32.unsetStdioModeFlags(0, bun.windows.ENABLE_VIRTUAL_TERMINAL_INPUT) catch null;
defer if (comptime Environment.isWindows) {
if (original_mode) |mode| {
_ = bun.windows.SetConsoleMode(bun.win32.STDIN_FD.cast(), mode);
}
};
Output.flush();
try setRawInput(true);
defer setRawInput(false) catch unreachable;
const reader = std.io.getStdIn().reader();
const val: bool = brk: {
var selected = default;
while (true) {
Output.pretty("\r\x1B[K", .{});
if (selected) {
Output.pretty("<r><green>●<r> Yes <d>/ ○ No<r>", .{});
} else {
Output.pretty("<r><d>○ Yes / <r><green>●<r> No<r>", .{});
}
Output.pretty("\x1B", .{});
Output.flush();
const byte = try reader.readByte();
switch (byte) {
'y', 'Y' => break :brk true,
'n', 'N' => break :brk false,
'\r', '\n' => break :brk selected,
'\x1b' => {
const seq_byte = try reader.readByte();
if (seq_byte != '[') continue;
const arrow = try reader.readByte();
switch (arrow) {
'C' => selected = false, // left arrow
'D' => selected = true, // right arrow
else => {},
}
},
else => continue,
}
}
};
Output.pretty("\r\x1B[A\r\x1B[K", .{});
Output.pretty(label, .{});
Output.pretty(" {s}\n", .{if (val) "yes" else "no"});
Output.flush();
return val;
}
pub fn promptSelect(
comptime label: []const u8,
opts: []const []const u8,
default: usize,
) !usize {
Output.prettyln(label, .{});
for (0..opts.len) |_| {
Output.prettyln("", .{});
}
Output.flush();
const original_mode: if (Environment.isWindows) ?bun.windows.DWORD else void = if (comptime Environment.isWindows)
bun.win32.unsetStdioModeFlags(0, bun.windows.ENABLE_VIRTUAL_TERMINAL_INPUT) catch null;
defer if (comptime Environment.isWindows) {
if (original_mode) |mode| {
_ = bun.windows.SetConsoleMode(bun.win32.STDIN_FD.cast(), mode);
}
};
Output.flush();
try setRawInput(true);
defer setRawInput(false) catch unreachable;
const reader = std.io.getStdIn().reader();
const val: usize = brk: {
var selected = default;
while (true) {
for (0..opts.len) |_| {
Output.pretty("\x1B[A", .{});
}
for (opts, 0..) |option, i| {
if (i == selected) {
Output.prettyln("<r><green>●<r> {s}", .{option});
} else {
Output.prettyln("<r><d>○ {s}<r>", .{option});
}
}
Output.pretty("\x1B[J", .{});
Output.flush();
const byte = try reader.readByte();
switch (byte) {
'\r', '\n' => break :brk selected,
'\x1b' => {
const seq_byte = try reader.readByte();
if (seq_byte != '[') continue;
const arrow = try reader.readByte();
switch (arrow) {
'A' => { // Up arrow
if (selected > 0) selected -= 1;
},
'B' => { // Down arrow
if (selected < opts.len - 1) selected += 1;
},
else => continue,
}
},
else => continue,
}
}
};
for (0..opts.len + 1) |_| {
Output.pretty("\x1B[A", .{});
}
Output.pretty(label, .{});
Output.prettyln(" {s}", .{opts[val]});
Output.prettyln("\x1B[J\x1B[A", .{});
Output.prettyln("\x1B[J\x1B[A", .{});
Output.flush();
return val;
}
const Assets = struct {
// "known" assets
const @".gitignore" = @embedFile("init/gitignore.default");
@@ -300,19 +496,61 @@ pub const InitCommand = struct {
fields.name = try normalizePackageName(alloc, name);
fields.entry_point = prompt(
alloc,
"<r><cyan>entry point<r> ",
fields.entry_point,
) catch |err| {
const template_options = [_][]const u8{
"TypeScript (default)",
"React + TypeScript",
"React + TypeScript + Tailwind + shadcn/ui",
};
const selected = promptSelect("<r><cyan>template:<r>", &template_options, 0) catch |err| {
if (err == error.EndOfStream) return;
return err;
};
const FullstackTemplate = enum {
base_typescript,
react_typescript,
react_typescript_tailwind_shadcn,
};
// // Set appropriate entry point based on selection
const fullstack_template: FullstackTemplate = switch (selected) {
0 => .base_typescript,
1 => .react_typescript,
2 => .react_typescript_tailwind_shadcn,
else => unreachable,
};
if (fullstack_template == .base_typescript) {
fields.entry_point = prompt(
alloc,
"<r><cyan>entry point<r> ",
fields.entry_point,
) catch |err| {
if (err == error.EndOfStream) return;
return err;
};
} else {
fields.entry_point = "src/App.tsx";
var dependencies = SourceFileProjectGenerator.Dependencies.init(alloc);
defer dependencies.deinit();
try dependencies.dev_deps.append("@types/bun@latest");
try dependencies.dev_deps.append("typescript@^5.0.0");
try SourceFileProjectGenerator.generateFromOptions(.{
.tailwind = true,
.shadcn = if (fullstack_template == .react_typescript_tailwind_shadcn) bun.StringSet.init(alloc) else null,
.dependencies = dependencies,
}, fields.entry_point, "App");
return;
}
try Output.writer().writeAll("\n");
Output.flush();
} else {
Output.prettyln("A package.json was found here. Would you like to configure", .{});
Output.prettyln("A package.json was found here.", .{});
}
}
@@ -490,3 +728,5 @@ pub const InitCommand = struct {
}
}
};
const SourceFileProjectGenerator = @import("../create/SourceFileProjectGenerator.zig");

View File

@@ -1,5 +1,29 @@
// Generate project files based on the entry point and dependencies
pub fn generate(_: Command.Context, _: Example.Tag, entry_point: string, result: *BundleV2.DependenciesScanner.Result) !void {
pub const Dependencies = struct {
deps: std.ArrayList([]const u8),
dev_deps: std.ArrayList([]const u8),
pub fn init(allocator: std.mem.Allocator) Dependencies {
return .{
.deps = std.ArrayList([]const u8).init(allocator),
.dev_deps = std.ArrayList([]const u8).init(allocator),
};
}
pub fn deinit(self: *Dependencies) void {
self.deps.deinit();
self.dev_deps.deinit();
}
};
pub const GenerateOptions = struct {
tailwind: bool = false,
shadcn: ?bun.StringSet = null,
dependencies: ?Dependencies = null,
};
// Original function that analyzes an existing entry point
pub fn generate(entry_point: string, result: *BundleV2.DependenciesScanner.Result) !void {
const react_component_export = findReactComponentExport(result.bundle_v2) orelse {
Output.errGeneric("No component export found in <b>{s}<r>", .{bun.fmt.quote(entry_point)});
Output.flush();
@@ -28,49 +52,77 @@ pub fn generate(_: Command.Context, _: Example.Tag, entry_point: string, result:
// Get any shadcn components used in the project
const shadcn = if (enable_shadcn_ui) try getShadcnComponents(result.bundle_v2, result.reachable_files) else bun.StringSet.init(default_allocator);
const needs_to_inject_shadcn_ui = shadcn.keys().len > 0;
// Convert dependencies to new format
var dependencies = Dependencies.init(default_allocator);
try dependencies.deps.appendSlice(result.dependencies.keys());
try generateFromOptions(.{
.tailwind = has_tailwind_in_dependencies or needs_to_inject_tailwind,
.shadcn = if (shadcn.keys().len > 0) shadcn else null,
.dependencies = dependencies,
}, entry_point, react_component_export);
}
// New function for generating without an existing entry point
pub fn generateFromOptions(options: GenerateOptions, entry_point: string, react_component_export: ?[]const u8) !void {
var dependencies = options.dependencies orelse Dependencies.init(default_allocator);
const needs_to_inject_shadcn_ui = options.shadcn != null;
const uses_tailwind = options.tailwind;
// Add Tailwind dependencies if needed
if (needs_to_inject_tailwind) {
try result.dependencies.insert("tailwindcss");
try result.dependencies.insert("bun-plugin-tailwind");
if (uses_tailwind) {
try dependencies.deps.append("tailwindcss");
try dependencies.deps.append("bun-plugin-tailwind");
}
// Add shadcn-ui dependencies if needed
if (needs_to_inject_shadcn_ui) {
// https://ui.shadcn.com/docs/installation/manual
// This will probably be tricky to keep updated.
// but hopefully the dependency scanning will just handle it for us.
try result.dependencies.insert("tailwindcss-animate");
try result.dependencies.insert("class-variance-authority");
try result.dependencies.insert("clsx");
try result.dependencies.insert("tailwind-merge");
try result.dependencies.insert("lucide-react");
try dependencies.deps.append("tailwindcss-animate");
try dependencies.deps.append("class-variance-authority");
try dependencies.deps.append("clsx");
try dependencies.deps.append("tailwind-merge");
try dependencies.deps.append("lucide-react");
}
const uses_tailwind = has_tailwind_in_dependencies or needs_to_inject_tailwind;
// We are JSX-only for now.
// The versions of react & react-dom need to match up, and it's SO easy to mess that up.
// So we have to be a little opinionated here.
if (needs_to_inject_shadcn_ui) {
// Use react 18 instead of 19 if shadcn is in use.
_ = result.dependencies.swapRemove("react");
_ = result.dependencies.swapRemove("react-dom");
try result.dependencies.insert("react@^18");
try result.dependencies.insert("react-dom@^18");
// Remove any existing react dependencies
for (dependencies.deps.items, 0..) |dep, i| {
if (strings.eqlComptime(dep, "react") or strings.eqlComptime(dep, "react-dom")) {
_ = dependencies.deps.orderedRemove(i);
}
}
try dependencies.deps.append("react@^18");
try dependencies.deps.append("react-dom@^18");
try dependencies.dev_deps.append("@types/react@^18");
try dependencies.dev_deps.append("@types/react-dom@^18");
} else {
// Add react-dom if react is used
_ = result.dependencies.swapRemove("react");
_ = result.dependencies.swapRemove("react-dom");
try result.dependencies.insert("react-dom@19");
try result.dependencies.insert("react@19");
// Remove any existing react dependencies
for (dependencies.deps.items, 0..) |dep, i| {
if (strings.eqlComptime(dep, "react") or strings.eqlComptime(dep, "react-dom")) {
_ = dependencies.deps.orderedRemove(i);
}
}
try dependencies.deps.append("react-dom@19");
try dependencies.deps.append("react@19");
try dependencies.dev_deps.append("@types/react@19");
try dependencies.dev_deps.append("@types/react-dom@19");
}
// Add dev dependencies
try dependencies.dev_deps.append("@types/bun@latest");
try dependencies.dev_deps.append("typescript@^5.0.0");
// Choose template based on dependencies and example type
const template: Template = brk: {
if (needs_to_inject_shadcn_ui) {
break :brk .{ .ReactShadcnSpa = .{ .components = shadcn } };
break :brk .{ .ReactShadcnSpa = .{ .components = options.shadcn orelse bun.StringSet.init(default_allocator) } };
} else if (uses_tailwind) {
break :brk .ReactTailwindSpa;
} else {
@@ -79,7 +131,7 @@ pub fn generate(_: Command.Context, _: Example.Tag, entry_point: string, result:
};
// Generate project files from template
try generateFiles(default_allocator, entry_point, result, template, react_component_export);
try generateFiles(default_allocator, entry_point, dependencies, template, react_component_export);
Global.exit(0);
}
@@ -168,7 +220,7 @@ fn stringWithReplacements(original_input: []const u8, basename: []const u8, rela
}
// Generate all project files from template
fn generateFiles(allocator: std.mem.Allocator, entry_point: string, result: *BundleV2.DependenciesScanner.Result, template: Template, react_component_export: []const u8) !void {
fn generateFiles(allocator: std.mem.Allocator, entry_point: string, dependencies: Dependencies, template: Template, react_component_export: ?[]const u8) !void {
var log = template.logger();
var basename = std.fs.path.basename(entry_point);
const extension = std.fs.path.extension(basename);
@@ -200,9 +252,15 @@ fn generateFiles(allocator: std.mem.Allocator, entry_point: string, result: *Bun
// Create all template files
inline for (0..files.len) |index| {
const file = &files[index];
const file_name = try stringWithReplacements(file.name, basename, normalized_name, react_component_export, allocator);
var file_name = try stringWithReplacements(file.name, basename, normalized_name, react_component_export orelse "App", allocator);
if (strings.eqlComptime(file.name, "REPLACE_ME_WITH_YOUR_APP_FILE_NAME.tsx") and !strings.eqlComptime(extension, ".tsx")) {
// replace the extension with the extension of the file
file_name = try std.fmt.allocPrint(allocator, "{s}{s}", .{ file_name[0 .. file_name.len - extension.len], extension });
}
if (file.overwrite or !bun.sys.exists(file_name)) {
switch (createFile(file_name, try stringWithReplacements(file.content, basename, normalized_name, react_component_export, default_allocator))) {
switch (createFile(file_name, try stringWithReplacements(file.content, basename, normalized_name, react_component_export orelse "", default_allocator))) {
.result => |new| {
if (new) {
created_files[index] = true;
@@ -231,11 +289,14 @@ fn generateFiles(allocator: std.mem.Allocator, entry_point: string, result: *Bun
try argv.append("bun");
try argv.append("--only-missing");
try argv.append("install");
try argv.appendSlice(result.dependencies.keys());
try argv.appendSlice(dependencies.deps.items);
// TODO: add this to --dev flag
try argv.appendSlice(dependencies.dev_deps.items);
if (log.has_written_initial_message) {
Output.print("\n", .{});
}
Output.pretty("<r>📦 <b>Auto-installing {d} detected dependencies<r>\n", .{result.dependencies.keys().len});
Output.pretty("<r>📦 <b>Auto-installing {d} detected dependencies<r>\n", .{dependencies.deps.items.len + dependencies.dev_deps.items.len});
// print "bun" but use bun.selfExePath()
Output.commandOut(argv.items);
@@ -341,14 +402,12 @@ fn generateFiles(allocator: std.mem.Allocator, entry_point: string, result: *Bun
}
Output.print("\n", .{});
log.ifNew();
}
},
.ReactSpa, .ReactTailwindSpa => {
log.ifNew();
},
.ReactSpa, .ReactTailwindSpa => {},
}
const normalized_name_with_ext = try std.fmt.allocPrint(allocator, "./{s}{s}", .{ normalized_name, extension });
log.ifNew(normalized_name_with_ext);
Output.flush();
@@ -671,6 +730,12 @@ const Reason = enum {
// Template for React + Tailwind project
const ReactTailwindSpa = struct {
pub const files = &[_]TemplateFile{
.{
.name = "REPLACE_ME_WITH_YOUR_APP_FILE_NAME.tsx",
.content = shared_app_tsx,
.reason = .bun,
.overwrite = false,
},
.{
.name = "REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts",
.content = shared_build_ts,
@@ -708,6 +773,7 @@ const ReactTailwindSpa = struct {
const shared_build_ts = @embedFile("projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts");
const shared_client_tsx = @embedFile("projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx");
const shared_app_tsx = @embedFile("projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.tsx");
const shared_html = @embedFile("projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html");
const shared_package_json = @embedFile("projects/react-shadcn-spa/package.json");
const shared_bunfig_toml = @embedFile("projects/react-shadcn-spa/bunfig.toml");
@@ -715,6 +781,12 @@ const shared_bunfig_toml = @embedFile("projects/react-shadcn-spa/bunfig.toml");
// Template for basic React project
const ReactSpa = struct {
pub const files = &[_]TemplateFile{
.{
.name = "REPLACE_ME_WITH_YOUR_APP_FILE_NAME.tsx",
.content = shared_app_tsx,
.reason = .bun,
.overwrite = false,
},
.{
.name = "REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts",
.content = shared_build_ts,
@@ -749,6 +821,12 @@ const ReactSpa = struct {
// Template for React + Shadcn project
const ReactShadcnSpa = struct {
pub const files = &[_]TemplateFile{
.{
.name = "REPLACE_ME_WITH_YOUR_APP_FILE_NAME.tsx",
.content = shared_app_tsx,
.reason = .bun,
.overwrite = false,
},
.{
.name = "lib/utils.ts",
.content = @embedFile("projects/react-shadcn-spa/lib/utils.ts"),
@@ -857,8 +935,8 @@ const Template = union(Tag) {
Output.prettyln(" <d>{s}<r>", .{@tagName(template_file.reason)});
}
pub fn ifNew(this: *Logger) void {
if (!this.has_written_initial_message) return;
pub fn ifNew(this: *Logger, main_file_name: []const u8) void {
// if (!this.has_written_initial_message) return;
Output.prettyln(
\\<r><d>--------------------------------<r>
@@ -872,8 +950,14 @@ const Template = union(Tag) {
\\
\\ <green><b>bun run build<r>
\\
\\<blue>Happy bunning! 🐇<r>
, .{this.template.label()});
\\<b><orange>Component<r><d> - your main react component<r>
\\
\\ <orange><b>{s}<r>
\\
\\ <blue>Happy bunning! 🐇<r>
\\
\\
, .{ this.template.label(), main_file_name });
}
};
};

View File

@@ -0,0 +1,13 @@
"use client";
import { useState } from "react";
export function REPLACE_ME_WITH_YOUR_REACT_COMPONENT_EXPORT() {
const [count, setCount] = useState(0);
return (
<div className="container">
<h1>Hello from Bun!</h1>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
</div>
);
};

View File

@@ -5,5 +5,9 @@
"scripts": {
"dev": "bun './**/*.html'",
"build": "bun 'REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts'"
},
"devDependencies": {
"@types/bun": "latest",
"typescript": "latest"
}
}

View File

@@ -861,6 +861,7 @@ pub const color_map = ComptimeStringMap(string, .{
&.{ "red", CSI ++ "31m" },
&.{ "green", CSI ++ "32m" },
&.{ "yellow", CSI ++ "33m" },
&.{ "orange", CSI ++ "33m" ++ CSI ++ "38;5;208m" },
&.{ "blue", CSI ++ "34m" },
&.{ "magenta", CSI ++ "35m" },
&.{ "cyan", CSI ++ "36m" },

View File

@@ -18,19 +18,27 @@ create index.css css
create index.html html
create index.client.tsx bun
create package.json npm
📦 Auto-installing 3 detected dependencies
$ bun --only-missing install classnames react-dom@19 react@19
📦 Auto-installing 7 detected dependencies
$ bun --only-missing install classnames react-dom@19 react@19 @types/react@19 @types/react-dom@19 @types/bun@latest typescript@^5.0.0
bun add v*.*.*
installed classnames@*.*.*
installed react-dom@*.*.*
installed react@*.*.*
4 packages installed [*ms]
installed @types/react@*.*.*
installed @types/react-dom@*.*.*
installed @types/bun@*.*.*
installed typescript@*.*.* with binaries:
- tsc
- tsserver
13 packages installed [*ms]
--------------------------------
✨ React project configured
Development - frontend dev server with hot reload
bun dev
Production - build optimized assets
bun run build
Component - your main react component
./index.jsx
Happy bunning! 🐇
Bun v*.*.* dev server ready in *.** ms
url: http://[SERVER_URL]/"
@@ -55,20 +63,26 @@ create index.html html
create index.client.tsx bun
create bunfig.toml bun
create package.json npm
📦 Auto-installing 4 detected dependencies
$ bun --only-missing install tailwindcss bun-plugin-tailwind react-dom@19 react@19
📦 Auto-installing 8 detected dependencies
$ bun --only-missing install tailwindcss bun-plugin-tailwind react-dom@19 react@19 @types/react@19 @types/react-dom@19 @types/bun@latest typescript@^5.0.0
bun add v*.*.*
+ @types/bun@*.*.*
+ typescript@*.*.*
installed tailwindcss@*.*.*
installed bun-plugin-tailwind@*.*.*
installed react-dom@*.*.*
installed react@*.*.*
7 packages installed [*ms]
installed @types/react@*.*.*
installed @types/react-dom@*.*.*
15 packages installed [*ms]
--------------------------------
✨ React + Tailwind project configured
Development - frontend dev server with hot reload
bun dev
Production - build optimized assets
bun run build
Component - your main react component
./index.tsx
Happy bunning! 🐇
Bun v*.*.* dev server ready in *.** ms
url: http://[SERVER_URL]/"
@@ -86,9 +100,11 @@ create bunfig.toml bun
create package.json npm
create tsconfig.json tsc
create components.json shadcn
📦 Auto-installing 9 detected dependencies
$ bun --only-missing install lucide-react tailwindcss bun-plugin-tailwind tailwindcss-animate class-variance-authority clsx tailwind-merge react@^18 react-dom@^18
📦 Auto-installing 14 detected dependencies
$ bun --only-missing install lucide-react tailwindcss bun-plugin-tailwind tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react react@^18 react-dom@^18 @types/react@^18 @types/react-dom@^18 @types/bun@latest typescript@^5.0.0
bun add v*.*.*
+ @types/bun@*.*.*
+ typescript@*.*.*
installed lucide-react@*.*.*
installed tailwindcss@*.*.*
installed bun-plugin-tailwind@*.*.*
@@ -98,7 +114,9 @@ installed clsx@*.*.*
installed tailwind-merge@*.*.*
installed react@*.*.*
installed react-dom@*.*.*
14 packages installed [*ms]
installed @types/react@*.*.*
installed @types/react-dom@*.*.*
23 packages installed [*ms]
😎 Setting up shadcn/ui components
$ bun x shadcn@canary add -y button badge card
- components/ui/button.tsx
@@ -110,6 +128,8 @@ Development - frontend dev server with hot reload
bun dev
Production - build optimized assets
bun run build
Component - your main react component
./index.tsx
Happy bunning! 🐇
Bun v*.*.* dev server ready in *.** ms
url: http://[SERVER_URL]/"
@@ -133,19 +153,27 @@ create index.css css
create index.html html
create index.client.tsx bun
create package.json npm
📦 Auto-installing 3 detected dependencies
$ bun --only-missing install classnames react-dom@19 react@19
📦 Auto-installing 7 detected dependencies
$ bun --only-missing install classnames react-dom@19 react@19 @types/react@19 @types/react-dom@19 @types/bun@latest typescript@^5.0.0
bun add v*.*.*
installed classnames@*.*.*
installed react-dom@*.*.*
installed react@*.*.*
4 packages installed [*ms]
installed @types/react@*.*.*
installed @types/react-dom@*.*.*
installed @types/bun@*.*.*
installed typescript@*.*.* with binaries:
- tsc
- tsserver
13 packages installed [*ms]
--------------------------------
✨ React project configured
Development - frontend dev server with hot reload
bun dev
Production - build optimized assets
bun run build
Component - your main react component
./index.jsx
Happy bunning! 🐇
Bun v*.*.* ready in *.** ms
url: http://[SERVER_URL]/"
@@ -170,20 +198,26 @@ create index.html html
create index.client.tsx bun
create bunfig.toml bun
create package.json npm
📦 Auto-installing 4 detected dependencies
$ bun --only-missing install tailwindcss bun-plugin-tailwind react-dom@19 react@19
📦 Auto-installing 8 detected dependencies
$ bun --only-missing install tailwindcss bun-plugin-tailwind react-dom@19 react@19 @types/react@19 @types/react-dom@19 @types/bun@latest typescript@^5.0.0
bun add v*.*.*
+ @types/bun@*.*.*
+ typescript@*.*.*
installed tailwindcss@*.*.*
installed bun-plugin-tailwind@*.*.*
installed react-dom@*.*.*
installed react@*.*.*
7 packages installed [*ms]
installed @types/react@*.*.*
installed @types/react-dom@*.*.*
15 packages installed [*ms]
--------------------------------
✨ React + Tailwind project configured
Development - frontend dev server with hot reload
bun dev
Production - build optimized assets
bun run build
Component - your main react component
./index.tsx
Happy bunning! 🐇
Bun v*.*.* ready in *.** ms
url: http://[SERVER_URL]/"
@@ -201,9 +235,11 @@ create bunfig.toml bun
create package.json npm
create tsconfig.json tsc
create components.json shadcn
📦 Auto-installing 9 detected dependencies
$ bun --only-missing install lucide-react tailwindcss bun-plugin-tailwind tailwindcss-animate class-variance-authority clsx tailwind-merge react@^18 react-dom@^18
📦 Auto-installing 14 detected dependencies
$ bun --only-missing install lucide-react tailwindcss bun-plugin-tailwind tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react react@^18 react-dom@^18 @types/react@^18 @types/react-dom@^18 @types/bun@latest typescript@^5.0.0
bun add v*.*.*
+ @types/bun@*.*.*
+ typescript@*.*.*
installed lucide-react@*.*.*
installed tailwindcss@*.*.*
installed bun-plugin-tailwind@*.*.*
@@ -213,7 +249,9 @@ installed clsx@*.*.*
installed tailwind-merge@*.*.*
installed react@*.*.*
installed react-dom@*.*.*
14 packages installed [*ms]
installed @types/react@*.*.*
installed @types/react-dom@*.*.*
23 packages installed [*ms]
😎 Setting up shadcn/ui components
$ bun x shadcn@canary add -y button badge card
- components/ui/button.tsx
@@ -225,6 +263,8 @@ Development - frontend dev server with hot reload
bun dev
Production - build optimized assets
bun run build
Component - your main react component
./index.tsx
Happy bunning! 🐇
Bun v*.*.* ready in *.** ms
url: http://[SERVER_URL]/"