Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/cli/init/README-tanstack.default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# {[name]s}

A TanStack Start project powered by Bun.

To install dependencies:

```bash
bun install
```

To start a development server:

```bash
bun dev
```

To build for production:

```bash
bun run build
```

## About TanStack Start

[TanStack Start](https://tanstack.com/start/latest) is a full-stack framework powered by TanStack Router for React and Solid that provides:

- File-based routing
- Server-side rendering (SSR)
- Server functions with `createServerFn`
- Built-in data loading with route loaders
- Hot module replacement (HMR)

This project was created using `bun init --react=tanstack` in bun v{[bunVersion]s}. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.

For more information, check out Bun's [TanStack Start guide](https://bun.com/guides/ecosystem/tanstack-start).
92 changes: 81 additions & 11 deletions src/cli/init_command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ pub const InitCommand = struct {
const @"tsconfig.json" = @embedFile("init/tsconfig.default.json");
const @"README.md" = @embedFile("init/README.default.md");
const @"README2.md" = @embedFile("init/README2.default.md");
const @"README-tanstack.md" = @embedFile("init/README-tanstack.default.md");

/// Create a new asset file, overriding anything that already exists. Known
/// assets will have their contents pre-populated; otherwise the file will be empty.
Expand Down Expand Up @@ -382,6 +383,10 @@ pub const InitCommand = struct {
template = .react_tailwind_shadcn;
prev_flag_was_react = false;
auto_yes = true;
} else if ((template == .react_blank and prev_flag_was_react and strings.eqlComptime(arg, "tanstack") or strings.eqlComptime(arg, "--react=tanstack")) or strings.eqlComptime(arg, "r=tanstack")) {
template = .react_tanstack;
prev_flag_was_react = false;
auto_yes = true;
} else {
prev_flag_was_react = false;
}
Expand Down Expand Up @@ -585,12 +590,14 @@ pub const InitCommand = struct {
default,
tailwind,
shadcn_tailwind,
tanstack,

pub fn fmt(self: @This()) []const u8 {
return switch (self) {
.default => "<blue>Default (blank)<r>",
.tailwind => "<magenta>TailwindCSS<r>",
.shadcn_tailwind => "<green>Shadcn + TailwindCSS<r>",
.tanstack => "<yellow>TanStack Start<r>",
};
}
});
Expand All @@ -599,6 +606,7 @@ pub const InitCommand = struct {
.default => .react_blank,
.tailwind => .react_tailwind,
.shadcn_tailwind => .react_tailwind_shadcn,
.tanstack => .react_tanstack,
};
},
.blank => template = .blank,
Expand All @@ -613,7 +621,7 @@ pub const InitCommand = struct {
}

switch (template) {
inline .react_blank, .react_tailwind, .react_tailwind_shadcn => |t| {
inline .react_blank, .react_tailwind, .react_tailwind_shadcn, .react_tanstack => |t| {
try t.@"write files and run `bun dev`"(alloc);
return;
},
Expand Down Expand Up @@ -791,7 +799,7 @@ pub const InitCommand = struct {

switch (template) {
.blank, .typescript_library => {
Template.createAgentRule();
Template.createAgentRule(template);

if (package_json_file != null and !did_load_package_json) {
Output.prettyln(" + <r><d>package.json<r>", .{});
Expand Down Expand Up @@ -910,13 +918,32 @@ const DependencyGroup = struct {
} ++ tailwind.dependencies[0..tailwind.dependencies.len].*,
.devDependencies = &[_]DependencyNeeded{} ++ tailwind.devDependencies[0..tailwind.devDependencies.len].*,
};

pub const tanstack = DependencyGroup{
.dependencies = &[_]DependencyNeeded{
.{ .name = "@tailwindcss/vite", .version = "^4.1.17" },
.{ .name = "@tanstack/react-router", .version = "^1.135.2" },
.{ .name = "@tanstack/react-start", .version = "^1.135.2" },
.{ .name = "react", .version = "^19.2.0" },
.{ .name = "react-dom", .version = "^19.2.0" },
.{ .name = "tailwindcss", .version = "^4.1.17" },
},
.devDependencies = &[_]DependencyNeeded{
.{ .name = "@types/react", .version = "^19.2.3" },
.{ .name = "@types/react-dom", .version = "^19.2.3" },
.{ .name = "@vitejs/plugin-react", .version = "^5.1.0" },
.{ .name = "vite", .version = "^7.2.2" },
.{ .name = "vite-tsconfig-paths", .version = "^5.1.4" },
} ++ blank.devDependencies[0..1].*,
};
Comment thread
lydiahallie marked this conversation as resolved.
};

const Template = enum {
blank,
react_blank,
react_tailwind,
react_tailwind_shadcn,
react_tanstack,
typescript_library,
const TemplateFile = struct {
path: [:0]const u8,
Expand All @@ -931,7 +958,7 @@ const Template = enum {
}
pub fn isReact(this: Template) bool {
return switch (this) {
.react_blank, .react_tailwind, .react_tailwind_shadcn => true,
.react_blank, .react_tailwind, .react_tailwind_shadcn, .react_tanstack => true,
else => false,
};
}
Expand Down Expand Up @@ -959,6 +986,7 @@ const Template = enum {
.react_blank => DependencyGroup.react,
.react_tailwind => DependencyGroup.tailwind,
.react_tailwind_shadcn => DependencyGroup.shadcn,
.react_tanstack => DependencyGroup.tanstack,
.typescript_library => DependencyGroup.blank,
};
}
Expand All @@ -969,6 +997,7 @@ const Template = enum {
.react_blank => "bun-react-template",
.react_tailwind => "bun-react-tailwind-template",
.react_tailwind_shadcn => "bun-react-tailwind-shadcn-template",
.react_tanstack => "bun-tanstack-start-template",
};
}
pub fn scripts(this: Template) []const []const u8 {
Expand All @@ -986,13 +1015,16 @@ const Template = enum {
"build",
"NODE_ENV=production bun .",
},
.react_tanstack => &.{ "dev", "bun --bun vite dev", "build", "bun --bun vite build", "serve", "bun --bun vite preview" },
};

return s;
}

const agent_rule = @embedFile("../init/rule.md");
const agent_rule_tanstack = @embedFile("../init/rule-tanstack.md");
const cursor_rule = TemplateFile{ .path = ".cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc", .contents = agent_rule };
const cursor_rule_tanstack = TemplateFile{ .path = ".cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc", .contents = agent_rule_tanstack };
const cursor_rule_path_to_claude_md = "../../CLAUDE.md";

fn isClaudeCodeInstalled() bool {
Expand All @@ -1012,12 +1044,12 @@ const Template = enum {
return bun.which(pathbuffer, bun.env_var.PATH.get() orelse return false, bun.fs.FileSystem.instance.top_level_dir, "claude") != null;
}

pub fn createAgentRule() void {
pub fn createAgentRule(this: Template) void {
var @"create CLAUDE.md" = Template.isClaudeCodeInstalled() and
// Never overwrite CLAUDE.md
!bun.sys.exists("CLAUDE.md");

if (Template.getCursorRule()) |template_file| {
if (this.getCursorRule()) |template_file| {
var did_create_agent_rule = false;

// If both Cursor & Claude is installed, make the cursor rule a
Expand Down Expand Up @@ -1053,9 +1085,13 @@ const Template = enum {
// If cursor is not installed but claude code is installed, then create the CLAUDE.md.
if (@"create CLAUDE.md") {
// In this case, the frontmatter from the cursor rule is not helpful so let's trim it out.
const end_of_frontmatter = if (bun.strings.lastIndexOf(agent_rule, "---\n")) |start| start + "---\n".len else 0;
const rule_to_use = switch (this) {
.react_tanstack => agent_rule_tanstack,
else => agent_rule,
};
const end_of_frontmatter = if (bun.strings.lastIndexOf(rule_to_use, "---\n")) |start| start + "---\n".len else 0;

InitCommand.Assets.createNew("CLAUDE.md", agent_rule[end_of_frontmatter..]) catch {};
InitCommand.Assets.createNew("CLAUDE.md", rule_to_use[end_of_frontmatter..]) catch {};
}
}

Expand Down Expand Up @@ -1092,9 +1128,12 @@ const Template = enum {

return false;
}
fn getCursorRule() ?*const TemplateFile {
fn getCursorRule(this: Template) ?*const TemplateFile {
if (isCursorInstalled()) {
return &cursor_rule;
return switch (this) {
.react_tanstack => &cursor_rule_tanstack,
else => &cursor_rule,
};
}

return null;
Expand Down Expand Up @@ -1168,17 +1207,36 @@ const Template = enum {
};
};

const ReactTanstack = struct {
const files: []const TemplateFile = &.{
.{ .path = "package.json", .contents = @embedFile("../init/react-tanstack/package.json") },
.{ .path = "tsconfig.json", .contents = @embedFile("../init/react-tanstack/tsconfig.json") },
.{ .path = "vite.config.ts", .contents = @embedFile("../init/react-tanstack/vite.config.ts") },
.{ .path = "styles.css", .contents = @embedFile("../init/react-tanstack/styles.css") },
.{ .path = "README.md", .contents = InitCommand.Assets.@"README-tanstack.md" },
.{ .path = ".gitignore", .contents = InitCommand.Assets.@".gitignore", .can_skip_if_exists = true },
.{ .path = "src/router.tsx", .contents = @embedFile("../init/react-tanstack/src/router.tsx") },
.{ .path = "src/routes/__root.tsx", .contents = @embedFile("../init/react-tanstack/src/routes/__root.tsx") },
.{ .path = "src/routes/index.tsx", .contents = @embedFile("../init/react-tanstack/src/routes/index.tsx") },
.{ .path = "src/routes/stats.tsx", .contents = @embedFile("../init/react-tanstack/src/routes/stats.tsx") },
.{ .path = "src/routeTree.gen.ts", .contents = @embedFile("../init/react-tanstack/src/routeTree.gen.ts") },
.{ .path = "public/header.webp", .contents = @embedFile("../init/react-tanstack/public/header.webp") },
.{ .path = "public/favicon.ico", .contents = @embedFile("../init/react-tanstack/public/favicon.ico") },
};
Comment thread
lydiahallie marked this conversation as resolved.
};

pub fn files(this: Template) []const TemplateFile {
return switch (this) {
.react_blank => ReactBlank.files,
.react_tailwind => ReactTailwind.files,
.react_tailwind_shadcn => ReactShadcn.files,
.react_tanstack => ReactTanstack.files,
else => &.{.{ &.{}, &.{} }},
};
}

pub fn @"write files and run `bun dev`"(comptime this: Template, allocator: std.mem.Allocator) !void {
Template.createAgentRule();
this.createAgentRule();

inline for (comptime this.files()) |file| {
const path = file.path;
Expand Down Expand Up @@ -1218,10 +1276,22 @@ const Template = enum {

_ = try install.spawnAndWait();

var cwd_buf: bun.PathBuffer = undefined;
const cwd_path = switch (bun.sys.getcwd(&cwd_buf)) {
.result => |p| p,
.err => |e| {
Output.err(e, "failed to get current working directory", .{});
Global.exit(1);
},
};
const dir_name = std.fs.path.basename(cwd_path);

Output.prettyln(
\\
\\✨ New project configured!
\\
\\<d>cd {s}<r>
\\
\\<b><cyan>Development<r><d> - full-stack dev server with hot reload<r>
\\
\\ <cyan><b>bun dev<r>
Expand All @@ -1236,7 +1306,7 @@ const Template = enum {
\\
\\<blue>Happy bunning! 🐇<r>
\\
, .{});
, .{dir_name});

Output.flush();
}
Expand Down
28 changes: 28 additions & 0 deletions src/init/react-tanstack/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "bun-tanstack-start-template",
"type": "module",
"scripts": {
"dev": "bun --bun vite dev",
"build": "bun --bun vite build",
"start": "bun --bun vite preview"
},
"devDependencies": {
"@types/bun": "^1.3.2",
"@types/react": "^19.2.3",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.0",
"vite": "^7.2.2",
"vite-tsconfig-paths": "^5.1.4"
},
"peerDependencies": {
"typescript": "^5.9.3"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.17",
"@tanstack/react-router": "^1.135.2",
"@tanstack/react-start": "^1.135.2",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"tailwindcss": "^4.1.17"
}
}
Binary file added src/init/react-tanstack/public/favicon.ico
Binary file not shown.
Binary file added src/init/react-tanstack/public/header.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading