Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ npx create-better-t-stack@latest
- Databases: SQLite, PostgreSQL, MySQL, MongoDB (or none)
- ORMs: Drizzle, Prisma, Mongoose (or none)
- Auth: Better Auth or Clerk (optional)
- Addons: Turborepo, Nx, PWA, Tauri, Electrobun, Biome, Lefthook, Husky, Starlight, Fumadocs, Ultracite, Oxlint, MCP, OpenTUI, WXT, Skills
- Addons: Turborepo, Nx, PWA, Tauri, Electrobun, Biome, Lefthook, Husky, Starlight, Fumadocs, Ultracite, Oxc, MCP, OpenTUI, WXT, Skills
- Examples: Todo, AI
- DB Setup: Turso, Neon, Supabase, Prisma PostgreSQL, MongoDB Atlas, Cloudflare D1, Docker
- Web Deploy: Cloudflare Workers
Expand Down
4 changes: 2 additions & 2 deletions apps/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Follow the prompts to configure your project or use the `--yes` flag for default
| **Database Setup** | • Turso (SQLite)<br>• Cloudflare D1 (SQLite)<br>• Neon (PostgreSQL)<br>• Supabase (PostgreSQL)<br>• Prisma Postgres<br>• MongoDB Atlas<br>• None (manual setup) |
| **Authentication** | • Better Auth<br>• Clerk |
| **Styling** | Tailwind CSS with a shared shadcn/ui package for React web apps |
| **Addons** | • PWA support<br>• Tauri (desktop applications)<br>• Electrobun (lightweight desktop shell)<br>• Starlight and Fumadocs (documentation sites)<br>• Biome, Oxlint, Ultracite (linting and formatting)<br>• Lefthook, Husky (Git hooks)<br>• MCP, Skills (agent tooling)<br>• OpenTUI, WXT (platform extensions)<br>• Turborepo or Nx (monorepo orchestration) |
| **Addons** | • PWA support<br>• Tauri (desktop applications)<br>• Electrobun (lightweight desktop shell)<br>• Starlight and Fumadocs (documentation sites)<br>• Biome, Oxc, Ultracite (linting and formatting)<br>• Lefthook, Husky (Git hooks)<br>• MCP, Skills (agent tooling)<br>• OpenTUI, WXT (platform extensions)<br>• Turborepo or Nx (monorepo orchestration) |
| **Examples** | • Todo app<br>• AI Chat interface (using Vercel AI SDK) |
| **Developer Experience** | • Automatic Git initialization<br>• Package manager choice (npm, pnpm, bun)<br>• Automatic dependency installation |

Expand All @@ -60,7 +60,7 @@ Options:
--auth <provider> Authentication (better-auth, clerk, none)
--payments <provider> Payments provider (polar, none)
--frontend <types...> Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, astro, native-bare, native-uniwind, native-unistyles, none)
--addons <types...> Additional addons (pwa, tauri, electrobun, starlight, biome, lefthook, husky, mcp, turborepo, nx, fumadocs, ultracite, oxlint, opentui, wxt, skills, none)
--addons <types...> Additional addons (pwa, tauri, electrobun, starlight, biome, lefthook, husky, mcp, turborepo, nx, fumadocs, ultracite, oxc, opentui, wxt, skills, none)
--examples <types...> Examples to include (todo, ai, none)
--git Initialize git repository
--no-git Skip git initialization
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const ADDON_COMPATIBILITY = {
starlight: [],
ultracite: [],
mcp: [],
oxlint: [],
oxc: [],
fumadocs: [],
opentui: [],
wxt: [],
Expand Down
18 changes: 9 additions & 9 deletions apps/cli/src/helpers/addons/addons-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { AddonSetupError, UserCancelledError } from "../../utils/errors";
import { cliConsola } from "../../utils/terminal-output";
import { setupFumadocs } from "./fumadocs-setup";
import { setupMcp } from "./mcp-setup";
import { setupOxlint } from "./oxlint-setup";
import { setupOxc } from "./oxc-setup";
import { setupSkills } from "./skills-setup";
import { setupStarlight } from "./starlight-setup";
import { setupTauri } from "./tauri-setup";
Expand Down Expand Up @@ -63,7 +63,7 @@ export async function setupAddons(config: ProjectConfig) {
const hasBiome = addons.includes("biome");
const hasHusky = addons.includes("husky");
const hasLefthook = addons.includes("lefthook");
const hasOxlint = addons.includes("oxlint");
const hasOxc = addons.includes("oxc");

if (hasUltracite) {
const gitHooks: string[] = [];
Expand All @@ -75,14 +75,14 @@ export async function setupAddons(config: ProjectConfig) {
await runAddonStep("biome", () => setupBiome(projectDir));
}

if (hasOxlint) {
await runSetup(() => setupOxlint(projectDir, config.packageManager));
if (hasOxc) {
await runSetup(() => setupOxc(projectDir, config.packageManager));
}

if (hasHusky || hasLefthook) {
let linter: "biome" | "oxlint" | undefined;
if (hasOxlint) {
linter = "oxlint";
let linter: "biome" | "oxc" | undefined;
if (hasOxc) {
linter = "oxc";
} else if (hasBiome) {
linter = "biome";
}
Expand Down Expand Up @@ -139,7 +139,7 @@ export async function setupBiome(projectDir: string) {
}
}

export async function setupHusky(projectDir: string, linter?: "biome" | "oxlint") {
export async function setupHusky(projectDir: string, linter?: "biome" | "oxc") {
await addPackageDependency({
devDependencies: ["husky", "lint-staged"],
projectDir,
Expand All @@ -154,7 +154,7 @@ export async function setupHusky(projectDir: string, linter?: "biome" | "oxlint"
prepare: "husky",
};

if (linter === "oxlint") {
if (linter === "oxc") {
packageJson["lint-staged"] = {
"*": ["oxlint", "oxfmt --write"],
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { shouldSkipExternalCommands } from "../../utils/external-commands";
import { getPackageExecutionArgs } from "../../utils/package-runner";
import { createSpinner } from "../../utils/terminal-output";

export async function setupOxlint(
export async function setupOxc(
projectDir: string,
packageManager: PackageManager,
): Promise<Result<void, AddonSetupError>> {
Expand Down Expand Up @@ -41,24 +41,24 @@ export async function setupOxlint(
const s = createSpinner();

try {
s.start("Initializing oxlint and oxfmt...");
s.start("Initializing oxc...");
Comment thread
zcyc marked this conversation as resolved.

const oxlintArgs = getPackageExecutionArgs(packageManager, "oxlint@latest --init");
await $({ cwd: projectDir, env: { CI: "true" } })`${oxlintArgs}`;

const oxfmtArgs = getPackageExecutionArgs(packageManager, "oxfmt@latest --init");
await $({ cwd: projectDir, env: { CI: "true" } })`${oxfmtArgs}`;

s.stop("oxlint and oxfmt initialized successfully!");
s.stop("oxc initialized successfully!");
} catch (error) {
s.stop("Failed to initialize oxlint and oxfmt");
s.stop("Failed to initialize oxc");
throw error;
}
},
catch: (error) =>
new AddonSetupError({
addon: "oxlint",
message: `Failed to set up oxlint: ${error instanceof Error ? error.message : String(error)}`,
addon: "oxc",
message: `Failed to set up oxc: ${error instanceof Error ? error.message : String(error)}`,
cause: error,
}),
});
Expand Down
4 changes: 2 additions & 2 deletions apps/cli/src/helpers/addons/ultracite-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { shouldSkipExternalCommands } from "../../utils/external-commands";
import { getPackageRunnerPrefix } from "../../utils/package-runner";
import { cliLog, createSpinner } from "../../utils/terminal-output";

type UltraciteLinter = "biome" | "eslint" | "oxlint";
type UltraciteLinter = "biome" | "eslint" | "oxc";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Does Ultracite CLI accept "oxc" as a --linter option?

💡 Result:

No, Ultracite CLI does not accept "oxc" as a --linter option. The supported linter options are "biome", "eslint", and "oxlint" (part of the Oxc ecosystem). "oxc" refers to the broader Oxc project, but the specific linter is oxlint, used with commands like npx ultracite init --linter oxlint.

Citations:


Revert type change: "oxc" is not a valid Ultracite CLI linter option.

The change from "oxlint" to "oxc" in the UltraciteLinter type is incorrect. Ultracite CLI only accepts "biome", "eslint", and "oxlint" as valid --linter options. "oxc" refers to the broader Oxc project; the specific linter within that ecosystem is "oxlint". This applies to the LINTERS map as well (line 68). Revert both the type and mapping back to "oxlint".


type UltraciteEditor =
| "vscode"
Expand Down Expand Up @@ -65,7 +65,7 @@ type UltraciteInitArgsInput = {
const LINTERS = {
biome: { label: "Biome", hint: "Fast formatter and linter" },
eslint: { label: "ESLint", hint: "Traditional JavaScript linter" },
oxlint: { label: "Oxlint", hint: "Oxidation compiler linter" },
oxc: { label: "Oxc", hint: "Oxidation compiler linter" },
} as const;

const EDITORS = {
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/helpers/core/post-installation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export async function displayPostInstallInstructions(
addons?.includes("husky") ||
addons?.includes("biome") ||
addons?.includes("lefthook") ||
addons?.includes("oxlint");
addons?.includes("oxc");

const databaseInstructions =
!isConvex && database !== "none"
Expand Down
6 changes: 3 additions & 3 deletions apps/cli/src/prompts/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ function getAddonDisplay(addon: Addons): { label: string; hint: string } {
label = "Biome";
hint = "Format, lint, and more";
break;
case "oxlint":
label = "Oxlint";
case "oxc":
label = "Oxc";
hint = "Oxlint + Oxfmt (linting & formatting)";
break;
case "ultracite":
Expand Down Expand Up @@ -89,7 +89,7 @@ function getAddonDisplay(addon: Addons): { label: string; hint: string } {

const ADDON_GROUPS = {
"Monorepo & Tasks": ["turborepo", "nx"],
"Code Quality": ["biome", "oxlint", "ultracite", "husky", "lefthook"],
"Code Quality": ["biome", "oxc", "ultracite", "husky", "lefthook"],
Documentation: ["starlight", "fumadocs"],
"Platform Extensions": ["pwa", "tauri", "electrobun", "opentui", "wxt"],
"AI & Agent Tools": ["skills", "mcp"],
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/test/addons.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ describe("Addon Configurations", () => {
"husky",
"turborepo",
"nx",
"oxlint",
"oxc",
// Note: starlight, ultracite, fumadocs are prompt-controlled only
];

Expand Down
10 changes: 5 additions & 5 deletions apps/cli/test/external-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, expect, it } from "bun:test";
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";

import { setupOxlint } from "../src/helpers/addons/oxlint-setup";
import { setupOxc } from "../src/helpers/addons/oxc-setup";
import { installDependencies } from "../src/helpers/core/install-dependencies";
import { getPackageExecutionArgs } from "../src/utils/package-runner";
import { SMOKE_DIR } from "./setup";
Expand Down Expand Up @@ -34,16 +34,16 @@ describe("External Command Guards", () => {
expect(result.isOk()).toBe(true);
});

it("should update package.json without running oxlint init in test mode", async () => {
const projectDir = join(SMOKE_DIR, "oxlint-skip");
it("should update package.json without running oxc init in test mode", async () => {
const projectDir = join(SMOKE_DIR, "oxc-skip");
await mkdir(projectDir, { recursive: true });

const pkgJsonPath = join(projectDir, "package.json");
await writeFile(
pkgJsonPath,
JSON.stringify(
{
name: "oxlint-skip",
name: "oxc-skip",
version: "0.0.0",
scripts: {},
devDependencies: {},
Expand All @@ -53,7 +53,7 @@ describe("External Command Guards", () => {
),
);

const result = await setupOxlint(projectDir, "bun");
const result = await setupOxc(projectDir, "bun");
expect(result.isOk()).toBe(true);

const updated = await Bun.file(pkgJsonPath).json();
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe("Integration Tests - Real World Scenarios", () => {
auth: "better-auth",
api: "orpc",
frontend: ["svelte"],
addons: ["turborepo", "oxlint"],
addons: ["turborepo", "oxc"],
examples: ["todo"], // Todo works with Svelte
dbSetup: "none",
webDeploy: "none",
Expand Down
6 changes: 3 additions & 3 deletions apps/cli/test/silent-create-output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ async function runSilentCreate(testCase: SilentCreateCase) {
describe("silent create output", () => {
const cases: SilentCreateCase[] = [
{
name: "stays quiet for oxlint addon setup",
projectName: "silent-addon-oxlint",
name: "stays quiet for oxc addon setup",
projectName: "silent-addon-oxc",
options: {
frontend: ["next"],
backend: "hono",
Expand All @@ -60,7 +60,7 @@ describe("silent create output", () => {
api: "none",
auth: "none",
payments: "none",
addons: ["nx", "oxlint"],
addons: ["nx", "oxc"],
examples: [],
git: true,
packageManager: "pnpm",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/content/docs/cli/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ Additional features to include:
- `turborepo`: Turborepo monorepo setup
- `nx`: Nx monorepo setup
- `ultracite`: Ultracite configuration
- `oxlint`: Oxlint + Oxfmt (linting & formatting)
- `oxc`: Oxlint + Oxfmt (linting & formatting)
- `mcp`: Install MCP servers, including Better T Stack itself, with add-mcp
- `opentui`: OpenTUI components
- `wxt`: WXT browser extension framework
Expand Down
2 changes: 1 addition & 1 deletion apps/web/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ See the full list in the [CLI Reference](/docs/cli). Key flags:
- `--api`: trpc, orpc, none
- `--auth`: better-auth, clerk, none
- `--payments`: polar, none
- `--addons`: turborepo, nx, pwa, tauri, electrobun, biome, lefthook, husky, starlight, fumadocs, ultracite, oxlint, mcp, opentui, wxt, skills, none
- `--addons`: turborepo, nx, pwa, tauri, electrobun, biome, lefthook, husky, starlight, fumadocs, ultracite, oxc, mcp, opentui, wxt, skills, none
- `--examples`: todo, ai, none

## Next Steps
Expand Down
2 changes: 1 addition & 1 deletion apps/web/content/docs/project-structure.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ Electrobun adds an `apps/desktop` workspace with its own `package.json`, `electr
"backend": "<none|hono|express|fastify|elysia|convex|self>",
"runtime": "<bun|node|workers|none>",
"frontend": ["<tanstack-router|react-router|tanstack-start|next|nuxt|svelte|solid|astro|native-bare|native-uniwind|native-unistyles|none>"] ,
"addons": ["<pwa|tauri|electrobun|starlight|fumadocs|biome|lefthook|husky|mcp|turborepo|nx|ultracite|oxlint|opentui|wxt|skills|none>"] ,
"addons": ["<pwa|tauri|electrobun|starlight|fumadocs|biome|lefthook|husky|mcp|turborepo|nx|ultracite|oxc|opentui|wxt|skills|none>"] ,
"examples": ["<ai|todo|none>"] ,
"auth": <"better-auth"|"clerk"|"none">,
"packageManager": "<bun|pnpm|npm>",
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/lib/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,8 @@ export const TECH_OPTIONS: Record<
default: false,
},
{
id: "oxlint",
name: "Oxlint",
id: "oxc",
name: "Oxc",
description: "Oxlint + Oxfmt (linting & formatting)",
icon: `${ICON_BASE_URL}/oxc.svg`,
color: "from-orange-500 to-orange-700",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/stack-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export function generateStackCommand(stack: StackState) {
"nx",
"ultracite",
"fumadocs",
"oxlint",
"oxc",
"opentui",
"wxt",
"skills",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ function generateFeaturesList(
tauri: "- **Tauri** - Build native desktop applications",
electrobun: "- **Electrobun** - Lightweight desktop shell for web frontends",
biome: "- **Biome** - Linting and formatting",
oxlint: "- **Oxlint** - Oxlint + Oxfmt (linting & formatting)",
oxc: "- **Oxc** - Oxlint + Oxfmt (linting & formatting)",
husky: "- **Husky** - Git hooks for code quality",
starlight: "- **Starlight** - Documentation site with Astro",
turborepo: "- **Turborepo** - Optimized monorepo build system",
Expand Down Expand Up @@ -686,7 +686,7 @@ function generateScriptsList(
scripts += `\n- \`${packageManagerRunCmd} check\`: Run Biome formatting and linting`;
}

if (addons.includes("oxlint")) {
if (addons.includes("oxc")) {
scripts += `\n- \`${packageManagerRunCmd} check\`: Run Oxlint and Oxfmt`;
}

Expand Down Expand Up @@ -769,7 +769,7 @@ function generateGitHooksSection(
addons: ProjectConfig["addons"],
): string {
const hasHusky = addons.includes("husky");
const hasLinting = addons.includes("biome") || addons.includes("oxlint");
const hasLinting = addons.includes("biome") || addons.includes("oxc");

if (!hasHusky && !hasLinting) {
return "";
Expand Down
2 changes: 1 addition & 1 deletion packages/template-generator/src/templates.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ pre-commit:
glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}"
run: {{packageManager}} biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {staged_files}
stage_fixed: true
{{else if (includes addons "oxlint")}}
{{else if (includes addons "oxc")}}
- name: oxlint
run: {{packageManager}} oxlint --fix {staged_files}
stage_fixed: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pre-commit:
glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}"
run: {{packageManager}} biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {staged_files}
stage_fixed: true
{{else if (includes addons "oxlint")}}
{{else if (includes addons "oxc")}}
- name: oxlint
run: {{packageManager}} oxlint --fix {staged_files}
stage_fixed: true
Expand Down
4 changes: 2 additions & 2 deletions packages/types/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const AddonsSchema = z
"nx",
"fumadocs",
"ultracite",
"oxlint",
"oxc",
"opentui",
"wxt",
"skills",
Expand Down Expand Up @@ -224,7 +224,7 @@ export const SkillSelectionSchema = z.strictObject({
});

export const UltraciteLinterSchema = z
.enum(["biome", "eslint", "oxlint"])
.enum(["biome", "eslint", "oxc"])
.describe("Ultracite linter");

export const UltraciteEditorSchema = z
Expand Down