feat: add command (#337)

This commit is contained in:
Aman Varshney
2025-06-22 03:20:05 +05:30
committed by GitHub
parent 198d0e7434
commit 9c7a0f0110
29 changed files with 1015 additions and 255 deletions

View File

@@ -8,7 +8,8 @@
"start": "next start",
"check": "biome check --write .",
"postinstall": "fumadocs-mdx",
"generate-analytics": "bun scripts/generate-analytics.ts"
"generate-analytics": "bun scripts/generate-analytics.ts",
"generate-schema": "bun scripts/generate-schema.ts"
},
"dependencies": {
"babel-plugin-react-compiler": "^19.1.0-rc.2",
@@ -19,7 +20,7 @@
"fumadocs-mdx": "11.6.7",
"fumadocs-ui": "15.5.1",
"lucide-react": "^0.513.0",
"motion": "^12.16.0",
"motion": "^12.18.1",
"next": "15.3.3",
"next-themes": "^0.4.6",
"nuqs": "^2.4.3",
@@ -28,21 +29,21 @@
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-tweet": "^3.2.2",
"recharts": "^2.15.3",
"recharts": "^2.15.4",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.0"
"tailwind-merge": "^3.3.1"
},
"devDependencies": {
"@types/papaparse": "^5.3.16",
"@tailwindcss/postcss": "^4.1.8",
"@tailwindcss/postcss": "^4.1.10",
"@types/mdx": "^2.0.13",
"@types/node": "24.0.0",
"@types/react": "^19.1.7",
"@types/papaparse": "^5.3.16",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"eslint": "^9.28.0",
"eslint": "^9.29.0",
"eslint-config-next": "15.3.3",
"postcss": "^8.5.4",
"tailwindcss": "^4.1.8",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.10",
"tw-animate-css": "^1.3.4",
"typescript": "^5.8.3"
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://better-t-stack.dev/schema.json",
// Better-T-Stack configuration file
// This file was automatically generated when the project was created
// It contains the stack configuration used to generate this project
"version": "2.21.0",
"createdAt": "2024-01-15T10:30:00.000Z",
"database": "sqlite",
"orm": "drizzle",
"backend": "hono",
"runtime": "bun",
"frontend": ["tanstack-router"],
"addons": ["turborepo", "biome"],
"examples": ["todo"],
"auth": true,
"packageManager": "bun",
"dbSetup": "turso",
"api": "trpc"
}

137
apps/web/public/schema.json Normal file
View File

@@ -0,0 +1,137 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://better-t-stack.dev/schema.json",
"title": "Better-T-Stack Configuration",
"description": "Configuration file for Better-T-Stack projects",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "JSON Schema reference for validation"
},
"version": {
"type": "string",
"description": "CLI version used to create this project",
"pattern": "^\\d+\\.\\d+\\.\\d+$"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the project was created"
},
"database": {
"type": "string",
"enum": ["none", "sqlite", "postgres", "mysql", "mongodb"],
"description": "Database type"
},
"orm": {
"type": "string",
"enum": ["drizzle", "prisma", "mongoose", "none"],
"description": "ORM type"
},
"backend": {
"type": "string",
"enum": [
"hono",
"express",
"fastify",
"next",
"elysia",
"convex",
"none"
],
"description": "Backend framework"
},
"runtime": {
"type": "string",
"enum": ["bun", "node", "workers", "none"],
"description": "Runtime environment (workers only available with hono backend and drizzle orm)"
},
"frontend": {
"type": "array",
"items": {
"type": "string",
"enum": [
"tanstack-router",
"react-router",
"tanstack-start",
"next",
"nuxt",
"native-nativewind",
"native-unistyles",
"svelte",
"solid",
"none"
]
},
"description": "Frontend framework"
},
"addons": {
"type": "array",
"items": {
"type": "string",
"enum": [
"pwa",
"tauri",
"starlight",
"biome",
"husky",
"turborepo",
"none"
]
},
"description": "Additional addons"
},
"examples": {
"type": "array",
"items": {
"type": "string",
"enum": ["todo", "ai", "none"]
},
"description": "Example templates to include"
},
"auth": {
"type": "boolean",
"description": "Whether authentication is enabled"
},
"packageManager": {
"type": "string",
"enum": ["npm", "pnpm", "bun"],
"description": "Package manager"
},
"dbSetup": {
"type": "string",
"enum": [
"turso",
"neon",
"prisma-postgres",
"mongodb-atlas",
"supabase",
"d1",
"none"
],
"description": "Database hosting setup"
},
"api": {
"type": "string",
"enum": ["trpc", "orpc", "none"],
"description": "API type"
}
},
"required": [
"version",
"createdAt",
"database",
"orm",
"backend",
"runtime",
"frontend",
"addons",
"examples",
"auth",
"packageManager",
"dbSetup",
"api"
],
"additionalProperties": false
}

View File

@@ -0,0 +1,137 @@
import path from "node:path";
import fs from "fs-extra";
import {
AddonsSchema,
APISchema,
BackendSchema,
DatabaseSchema,
DatabaseSetupSchema,
ExamplesSchema,
FrontendSchema,
ORMSchema,
PackageManagerSchema,
RuntimeSchema,
} from "../../cli/src/types";
const DATABASE_VALUES = DatabaseSchema.options;
const ORM_VALUES = ORMSchema.options;
const BACKEND_VALUES = BackendSchema.options;
const RUNTIME_VALUES = RuntimeSchema.options;
const FRONTEND_VALUES = FrontendSchema.options;
const ADDONS_VALUES = AddonsSchema.options;
const EXAMPLES_VALUES = ExamplesSchema.options;
const PACKAGE_MANAGER_VALUES = PackageManagerSchema.options;
const DATABASE_SETUP_VALUES = DatabaseSetupSchema.options;
const API_VALUES = APISchema.options;
const schema = {
$schema: "http://json-schema.org/draft-07/schema#",
$id: "https://better-t-stack.dev/schema.json",
title: "Better-T-Stack Configuration",
description: "Configuration file for Better-T-Stack projects",
type: "object" as const,
properties: {
$schema: {
type: "string" as const,
description: "JSON Schema reference for validation",
},
version: {
type: "string" as const,
description: "CLI version used to create this project",
pattern: "^\\d+\\.\\d+\\.\\d+$",
},
createdAt: {
type: "string" as const,
format: "date-time" as const,
description: "Timestamp when the project was created",
},
database: {
type: "string" as const,
enum: DATABASE_VALUES,
description: DatabaseSchema.description,
},
orm: {
type: "string" as const,
enum: ORM_VALUES,
description: ORMSchema.description,
},
backend: {
type: "string" as const,
enum: BACKEND_VALUES,
description: BackendSchema.description,
},
runtime: {
type: "string" as const,
enum: RUNTIME_VALUES,
description: RuntimeSchema.description,
},
frontend: {
type: "array" as const,
items: {
type: "string" as const,
enum: FRONTEND_VALUES,
},
description: FrontendSchema.description,
},
addons: {
type: "array" as const,
items: {
type: "string" as const,
enum: ADDONS_VALUES,
},
description: AddonsSchema.description,
},
examples: {
type: "array" as const,
items: {
type: "string" as const,
enum: EXAMPLES_VALUES,
},
description: ExamplesSchema.description,
},
auth: {
type: "boolean" as const,
description: "Whether authentication is enabled",
},
packageManager: {
type: "string" as const,
enum: PACKAGE_MANAGER_VALUES,
description: PackageManagerSchema.description,
},
dbSetup: {
type: "string" as const,
enum: DATABASE_SETUP_VALUES,
description: DatabaseSetupSchema.description,
},
api: {
type: "string" as const,
enum: API_VALUES,
description: APISchema.description,
},
},
required: [
"version",
"createdAt",
"database",
"orm",
"backend",
"runtime",
"frontend",
"addons",
"examples",
"auth",
"packageManager",
"dbSetup",
"api",
],
additionalProperties: false,
};
async function generateSchema() {
const schemaPath = path.join(process.cwd(), "public", "schema.json");
await fs.ensureDir(path.dirname(schemaPath));
await fs.writeFile(schemaPath, JSON.stringify(schema, null, 2), "utf-8");
console.log("✅ Generated schema.json from shared types package");
}
generateSchema().catch(console.error);

View File

@@ -1465,7 +1465,6 @@ const StackBuilder = () => {
</button>
)}
<button
id="save-stack-button"
type="button"
onClick={saveCurrentStack}
className="flex items-center gap-1 rounded border border-border bg-background px-2 py-1 text-muted-foreground text-xs transition-colors hover:bg-muted"

View File

@@ -1,7 +1,6 @@
"use client";
import { ExternalLink, File, Github, Monitor } from "lucide-react";
import { motion } from "motion/react";
import Image from "next/image";
import Link from "next/link";
@@ -24,26 +23,10 @@ export default function ShowcaseItem({
tags,
index = 0,
}: ShowcaseItemProps) {
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.5,
ease: "easeOut",
},
},
};
const projectId = `PROJECT_${String(index + 1).padStart(3, "0")}`;
return (
<motion.div
variants={itemVariants}
className="terminal-block-hover flex h-full flex-col overflow-hidden rounded border border-border bg-background"
style={{ animationDelay: `${index * 100}ms` }}
>
<div className="terminal-block-hover flex h-full flex-col overflow-hidden rounded border border-border bg-background">
<div className="border-border border-b bg-muted/20 px-3 py-2">
<div className="flex items-center gap-2">
<File className="h-3 w-3 text-primary" />
@@ -137,6 +120,6 @@ export default function ShowcaseItem({
</div>
</div>
</div>
</motion.div>
</div>
);
}

View File

@@ -1,7 +1,6 @@
"use client";
import { FolderOpen, Terminal } from "lucide-react";
import { motion } from "motion/react";
import Navbar from "../_components/navbar";
import ShowcaseItem from "./_components/ShowcaseItem";
@@ -17,40 +16,12 @@ const showcaseProjects = [
];
export default function ShowcasePage() {
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.5,
ease: "easeOut",
},
},
};
return (
<>
<Navbar />
<main className="flex min-h-svh flex-col items-center bg-background px-4 pt-24 pb-10 sm:px-6 md:px-8 md:pt-28 lg:pt-32">
<motion.div
className="mx-auto w-full max-w-6xl"
initial="hidden"
animate="visible"
variants={containerVariants}
>
<motion.div className="mb-8" variants={itemVariants}>
<div className="mx-auto w-full max-w-6xl">
<div className="mb-8">
<div className="mb-6 flex items-center gap-2">
<Terminal className="h-4 w-4 text-primary" />
<span className="font-bold font-mono text-lg">
@@ -92,18 +63,15 @@ export default function ShowcasePage() {
</div>
</div>
</div>
</motion.div>
</div>
<motion.div
className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3"
variants={containerVariants}
>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
{showcaseProjects.map((project, index) => (
<ShowcaseItem key={project.title} {...project} index={index} />
))}
</motion.div>
</div>
<motion.div className="mt-8" variants={itemVariants}>
<div className="mt-8">
<div className="terminal-block-hover rounded border border-border bg-muted/20 p-4">
<div className="flex items-center gap-2 text-sm">
<span className="text-primary">$</span>
@@ -119,8 +87,8 @@ export default function ShowcasePage() {
</span>
</div>
</div>
</motion.div>
</motion.div>
</div>
</div>
</main>
</>
);