Skip to content

feat(cli): add ultracite, oxlint, fumadocs addons #427

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jul 28, 2025
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
9 changes: 9 additions & 0 deletions .changeset/sad-months-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"create-better-t-stack": minor
---

Added addons: fumadocs, ultracite, oxlint

Added bunfig.toml with isolated linker

Grouped addon prompts
13 changes: 9 additions & 4 deletions apps/cli/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import type { Frontend, ProjectConfig } from "./types";
import type { Addons, Frontend, ProjectConfig } from "./types";
import { getUserPkgManager } from "./utils/get-package-manager";

const __filename = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -55,10 +55,12 @@ export const dependencyVersionMap = {

"@tauri-apps/cli": "^2.4.0",

"@biomejs/biome": "^2.0.0",
"@biomejs/biome": "^2.1.2",
oxlint: "^1.8.0",
ultracite: "5.1.1",

husky: "^9.1.7",
"lint-staged": "^15.5.0",
"lint-staged": "^16.1.2",

tsx: "^4.19.2",
"@types/node": "^22.13.11",
Expand Down Expand Up @@ -119,13 +121,16 @@ export const dependencyVersionMap = {

export type AvailableDependencies = keyof typeof dependencyVersionMap;

export const ADDON_COMPATIBILITY = {
export const ADDON_COMPATIBILITY: Record<Addons, readonly Frontend[]> = {
pwa: ["tanstack-router", "react-router", "solid", "next"],
tauri: ["tanstack-router", "react-router", "nuxt", "svelte", "solid"],
biome: [],
husky: [],
turborepo: [],
starlight: [],
ultracite: [],
oxlint: [],
fumadocs: [],
none: [],
} as const;

Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/helpers/database-providers/d1-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
type EnvVariable,
} from "../project-generation/env-setup";

export async function setupCloudflareD1(config: ProjectConfig): Promise<void> {
export async function setupCloudflareD1(config: ProjectConfig) {
const { projectDir } = config;

const envPath = path.join(projectDir, "apps/server", ".env");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
type EnvVariable,
} from "../project-generation/env-setup";

export async function setupDockerCompose(config: ProjectConfig): Promise<void> {
export async function setupDockerCompose(config: ProjectConfig) {
const { database, projectDir, projectName } = config;

if (database === "none" || database === "sqlite") {
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/helpers/database-providers/neon-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ function displayManualSetupInstructions() {
DATABASE_URL="your_connection_string"`);
}

export async function setupNeonPostgres(config: ProjectConfig): Promise<void> {
export async function setupNeonPostgres(config: ProjectConfig) {
const { packageManager, projectDir } = config;

try {
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/helpers/database-providers/turso-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ DATABASE_URL=your_database_url
DATABASE_AUTH_TOKEN=your_auth_token`);
}

export async function setupTurso(config: ProjectConfig): Promise<void> {
export async function setupTurso(config: ProjectConfig) {
const { orm, projectDir } = config;
const _isDrizzle = orm === "drizzle";
const setupSpinner = spinner();
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/helpers/project-generation/add-addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function exitWithError(message: string): never {

export async function addAddonsToProject(
input: AddInput & { addons: Addons[]; suppressInstallMessage?: boolean },
): Promise<void> {
) {
try {
const projectDir = input.projectDir || process.cwd();

Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/helpers/project-generation/add-deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function exitWithError(message: string): never {

export async function addDeploymentToProject(
input: AddInput & { webDeploy: WebDeploy; suppressInstallMessage?: boolean },
): Promise<void> {
) {
try {
const projectDir = input.projectDir || process.cwd();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export async function createProjectHandler(
}
}

export async function addAddonsHandler(input: AddInput): Promise<void> {
export async function addAddonsHandler(input: AddInput) {
try {
const projectDir = input.projectDir || process.cwd();
const detectedConfig = await detectProjectConfig(projectDir);
Expand Down
6 changes: 2 additions & 4 deletions apps/cli/src/helpers/project-generation/env-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface EnvVariable {
export async function addEnvVariablesToFile(
filePath: string,
variables: EnvVariable[],
): Promise<void> {
) {
await fs.ensureDir(path.dirname(filePath));

let envContent = "";
Expand Down Expand Up @@ -84,9 +84,7 @@ export async function addEnvVariablesToFile(
}
}

export async function setupEnvironmentVariables(
config: ProjectConfig,
): Promise<void> {
export async function setupEnvironmentVariables(config: ProjectConfig) {
const { backend, frontend, database, auth, examples, dbSetup, projectDir } =
config;

Expand Down
5 changes: 1 addition & 4 deletions apps/cli/src/helpers/project-generation/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import { log } from "@clack/prompts";
import { $ } from "execa";
import pc from "picocolors";

export async function initializeGit(
projectDir: string,
useGit: boolean,
): Promise<void> {
export async function initializeGit(projectDir: string, useGit: boolean) {
if (!useGit) return;

const gitVersionResult = await $({
Expand Down
4 changes: 4 additions & 0 deletions apps/cli/src/helpers/project-generation/post-installation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ export async function displayPostInstallInstructions(
output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
}

if (addons?.includes("fumadocs")) {
output += `${pc.cyan("•")} Fumadocs: http://localhost:4000\n`;
}

if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
if (databaseInstructions) output += `\n${databaseInstructions.trim()}\n`;
if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
Expand Down
20 changes: 4 additions & 16 deletions apps/cli/src/helpers/project-generation/project-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { ProjectConfig } from "../../types";
export async function updatePackageConfigurations(
projectDir: string,
options: ProjectConfig,
): Promise<void> {
) {
await updateRootPackageJson(projectDir, options);
if (options.backend !== "convex") {
await updateServerPackageJson(projectDir, options);
Expand All @@ -19,7 +19,7 @@ export async function updatePackageConfigurations(
async function updateRootPackageJson(
projectDir: string,
options: ProjectConfig,
): Promise<void> {
) {
const rootPackageJsonPath = path.join(projectDir, "package.json");
if (!(await fs.pathExists(rootPackageJsonPath))) return;

Expand Down Expand Up @@ -185,18 +185,6 @@ async function updateRootPackageJson(
}
}

if (options.addons.includes("biome")) {
scripts.check = "biome check --write .";
}
if (options.addons.includes("husky")) {
scripts.prepare = "husky";
packageJson["lint-staged"] = {
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
"biome check --write .",
],
};
}

try {
const { stdout } = await execa(options.packageManager, ["-v"], {
cwd: projectDir,
Expand Down Expand Up @@ -235,7 +223,7 @@ async function updateRootPackageJson(
async function updateServerPackageJson(
projectDir: string,
options: ProjectConfig,
): Promise<void> {
) {
const serverPackageJsonPath = path.join(
projectDir,
"apps/server/package.json",
Expand Down Expand Up @@ -287,7 +275,7 @@ async function updateServerPackageJson(
async function updateConvexPackageJson(
projectDir: string,
options: ProjectConfig,
): Promise<void> {
) {
const convexPackageJsonPath = path.join(
projectDir,
"packages/backend/package.json",
Expand Down
33 changes: 19 additions & 14 deletions apps/cli/src/helpers/project-generation/template-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ async function processAndCopyFiles(
destDir: string,
context: ProjectConfig,
overwrite = true,
): Promise<void> {
) {
const sourceFiles = await globby(sourcePattern, {
cwd: baseSourceDir,
dot: true,
Expand Down Expand Up @@ -54,15 +54,15 @@ async function processAndCopyFiles(
export async function copyBaseTemplate(
projectDir: string,
context: ProjectConfig,
): Promise<void> {
) {
const templateDir = path.join(PKG_ROOT, "templates/base");
await processAndCopyFiles(["**/*"], templateDir, projectDir, context);
}

export async function setupFrontendTemplates(
projectDir: string,
context: ProjectConfig,
): Promise<void> {
) {
const hasReactWeb = context.frontend.some((f) =>
["tanstack-router", "react-router", "tanstack-start", "next"].includes(f),
);
Expand Down Expand Up @@ -241,7 +241,7 @@ export async function setupFrontendTemplates(
export async function setupBackendFramework(
projectDir: string,
context: ProjectConfig,
): Promise<void> {
) {
if (context.backend === "none") {
return;
}
Expand Down Expand Up @@ -332,7 +332,7 @@ export async function setupBackendFramework(
export async function setupDbOrmTemplates(
projectDir: string,
context: ProjectConfig,
): Promise<void> {
) {
if (
context.backend === "convex" ||
context.orm === "none" ||
Expand All @@ -357,7 +357,7 @@ export async function setupDbOrmTemplates(
export async function setupAuthTemplate(
projectDir: string,
context: ProjectConfig,
): Promise<void> {
) {
if (context.backend === "convex" || !context.auth) return;

const serverAppDir = path.join(projectDir, "apps/server");
Expand Down Expand Up @@ -529,7 +529,7 @@ export async function setupAuthTemplate(
export async function setupAddonsTemplate(
projectDir: string,
context: ProjectConfig,
): Promise<void> {
) {
if (!context.addons || context.addons.length === 0) return;

for (const addon of context.addons) {
Expand Down Expand Up @@ -567,7 +567,7 @@ export async function setupAddonsTemplate(
export async function setupExamplesTemplate(
projectDir: string,
context: ProjectConfig,
): Promise<void> {
) {
if (
!context.examples ||
context.examples.length === 0 ||
Expand Down Expand Up @@ -773,10 +773,7 @@ export async function setupExamplesTemplate(
}
}

export async function handleExtras(
projectDir: string,
context: ProjectConfig,
): Promise<void> {
export async function handleExtras(projectDir: string, context: ProjectConfig) {
const extrasDir = path.join(PKG_ROOT, "templates/extras");
const hasNativeWind = context.frontend.includes("native-nativewind");
const hasUnistyles = context.frontend.includes("native-unistyles");
Expand All @@ -790,6 +787,14 @@ export async function handleExtras(
}
}

if (context.packageManager === "bun") {
const bunfigSrc = path.join(extrasDir, "bunfig.toml");
const bunfigDest = path.join(projectDir, "bunfig.toml");
if (await fs.pathExists(bunfigSrc)) {
await fs.copy(bunfigSrc, bunfigDest);
}
}

if (
context.packageManager === "pnpm" &&
(hasNative || context.frontend.includes("nuxt"))
Expand Down Expand Up @@ -818,7 +823,7 @@ export async function handleExtras(
export async function setupDockerComposeTemplates(
projectDir: string,
context: ProjectConfig,
): Promise<void> {
) {
if (context.dbSetup !== "docker" || context.database === "none") {
return;
}
Expand All @@ -838,7 +843,7 @@ export async function setupDockerComposeTemplates(
export async function setupDeploymentTemplates(
projectDir: string,
context: ProjectConfig,
): Promise<void> {
) {
if (context.webDeploy === "none") {
return;
}
Expand Down
Loading