mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 23:18:47 +00:00
Compare commits
9 Commits
dylan/pyth
...
riskymh/bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b5d549262 | ||
|
|
2bcdb7c74f | ||
|
|
93458a2f51 | ||
|
|
8d0024ca62 | ||
|
|
8d402a5b32 | ||
|
|
8a0dbc5eee | ||
|
|
f68bfd49ea | ||
|
|
bd2119d440 | ||
|
|
5695def739 |
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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]/"
|
||||
|
||||
Reference in New Issue
Block a user