mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
use spaces instead of commas in flags
This commit is contained in:
5
.changeset/wide-buses-carry.md
Normal file
5
.changeset/wide-buses-carry.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"create-better-t-stack": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
use spaces instead of commas in flags
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Create Better-T-Stack CLI
|
# Create Better-T-Stack CLI
|
||||||
|
|
||||||
A CLI tool for scaffolding type-safe full-stack apps with Hono/Elysia backends, React web frontends, and Expo native apps, all connected through tRPC.
|
A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@@ -49,10 +49,10 @@ Options:
|
|||||||
--orm <type> ORM type (none, drizzle, prisma)
|
--orm <type> ORM type (none, drizzle, prisma)
|
||||||
--auth Include authentication
|
--auth Include authentication
|
||||||
--no-auth Exclude authentication
|
--no-auth Exclude authentication
|
||||||
--frontend <types> Frontend types (web,native or both)
|
--frontend <types...> Frontend types (web, native, none)
|
||||||
--addons <types> Additional addons (pwa,tauri,biome,husky)
|
--addons <types...> Additional addons (pwa, tauri, biome, husky)
|
||||||
--no-addons Skip all additional addons
|
--no-addons Skip all additional addons
|
||||||
--examples <types> Examples to include (todo,ai)
|
--examples <types...> Examples to include (todo, ai)
|
||||||
--no-examples Skip all examples
|
--no-examples Skip all examples
|
||||||
--git Initialize git repository
|
--git Initialize git repository
|
||||||
--no-git Skip git initialization
|
--no-git Skip git initialization
|
||||||
@@ -75,7 +75,7 @@ npx create-better-t-stack my-app -y
|
|||||||
|
|
||||||
Create a project with specific options:
|
Create a project with specific options:
|
||||||
```bash
|
```bash
|
||||||
npx create-better-t-stack my-app --database postgres --orm drizzle --auth --addons pwa,biome
|
npx create-better-t-stack my-app --database postgres --orm drizzle --auth --addons pwa biome
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a project with Elysia and Node.js runtime:
|
Create a project with Elysia and Node.js runtime:
|
||||||
@@ -85,10 +85,10 @@ npx create-better-t-stack my-app --backend elysia --runtime node
|
|||||||
|
|
||||||
Create a project with specific frontend options:
|
Create a project with specific frontend options:
|
||||||
```bash
|
```bash
|
||||||
npx create-better-t-stack my-app --frontend web,native
|
npx create-better-t-stack my-app --frontend web native
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a project with examples:
|
Create a project with examples:
|
||||||
```bash
|
```bash
|
||||||
npx create-better-t-stack my-app --examples todo,ai
|
npx create-better-t-stack my-app --examples todo ai
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -7,10 +7,7 @@
|
|||||||
"bin": {
|
"bin": {
|
||||||
"create-better-t-stack": "dist/index.js"
|
"create-better-t-stack": "dist/index.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": ["dist", "template"],
|
||||||
"dist",
|
|
||||||
"template"
|
|
||||||
],
|
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { createReadme } from "./create-readme";
|
|||||||
import { setupDatabase } from "./db-setup";
|
import { setupDatabase } from "./db-setup";
|
||||||
import { setupEnvironmentVariables } from "./env-setup";
|
import { setupEnvironmentVariables } from "./env-setup";
|
||||||
import { setupExamples } from "./examples-setup";
|
import { setupExamples } from "./examples-setup";
|
||||||
|
import { installDependencies } from "./install-dependencies";
|
||||||
import { displayPostInstallInstructions } from "./post-installation";
|
import { displayPostInstallInstructions } from "./post-installation";
|
||||||
import { initializeGit, updatePackageConfigurations } from "./project-config";
|
import { initializeGit, updatePackageConfigurations } from "./project-config";
|
||||||
import { setupRuntime } from "./runtime-setup";
|
import { setupRuntime } from "./runtime-setup";
|
||||||
@@ -91,6 +92,14 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
|||||||
await updatePackageConfigurations(projectDir, options);
|
await updatePackageConfigurations(projectDir, options);
|
||||||
await createReadme(projectDir, options);
|
await createReadme(projectDir, options);
|
||||||
|
|
||||||
|
if (!options.noInstall) {
|
||||||
|
await installDependencies({
|
||||||
|
projectDir,
|
||||||
|
packageManager: options.packageManager,
|
||||||
|
addons: options.addons,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
displayPostInstallInstructions(
|
displayPostInstallInstructions(
|
||||||
options.database,
|
options.database,
|
||||||
options.projectName,
|
options.projectName,
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ app.post("/ai", async (c) => {
|
|||||||
return stream(c, (stream) => stream.pipe(result.toDataStream()));
|
return stream(c, (stream) => stream.pipe(result.toDataStream()));
|
||||||
});`;
|
});`;
|
||||||
|
|
||||||
// Add the import section
|
|
||||||
if (indexContent.includes("import {")) {
|
if (indexContent.includes("import {")) {
|
||||||
const lastImportIndex = indexContent.lastIndexOf("import");
|
const lastImportIndex = indexContent.lastIndexOf("import");
|
||||||
const endOfLastImport = indexContent.indexOf("\n", lastImportIndex);
|
const endOfLastImport = indexContent.indexOf("\n", lastImportIndex);
|
||||||
@@ -94,7 +93,6 @@ ${indexContent.substring(endOfLastImport + 1)}`;
|
|||||||
${indexContent}`;
|
${indexContent}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the route handler
|
|
||||||
const trpcHandlerIndex =
|
const trpcHandlerIndex =
|
||||||
indexContent.indexOf('app.use("/trpc"') ||
|
indexContent.indexOf('app.use("/trpc"') ||
|
||||||
indexContent.indexOf("app.use(trpc(");
|
indexContent.indexOf("app.use(trpc(");
|
||||||
@@ -103,7 +101,6 @@ ${indexContent}`;
|
|||||||
|
|
||||||
${indexContent.substring(trpcHandlerIndex)}`;
|
${indexContent.substring(trpcHandlerIndex)}`;
|
||||||
} else {
|
} else {
|
||||||
// Add it near the end before export
|
|
||||||
const exportIndex = indexContent.indexOf("export default");
|
const exportIndex = indexContent.indexOf("export default");
|
||||||
if (exportIndex !== -1) {
|
if (exportIndex !== -1) {
|
||||||
indexContent = `${indexContent.substring(0, exportIndex)}${aiRouteHandler}
|
indexContent = `${indexContent.substring(0, exportIndex)}${aiRouteHandler}
|
||||||
|
|||||||
@@ -3,13 +3,18 @@ import { Command } from "commander";
|
|||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
import { DEFAULT_CONFIG } from "./constants";
|
import { DEFAULT_CONFIG } from "./constants";
|
||||||
import { createProject } from "./helpers/create-project";
|
import { createProject } from "./helpers/create-project";
|
||||||
import { installDependencies } from "./helpers/install-dependencies";
|
|
||||||
import { gatherConfig } from "./prompts/config-prompts";
|
import { gatherConfig } from "./prompts/config-prompts";
|
||||||
import type {
|
import type {
|
||||||
|
CLIOptions,
|
||||||
ProjectAddons,
|
ProjectAddons,
|
||||||
|
ProjectBackend,
|
||||||
ProjectConfig,
|
ProjectConfig,
|
||||||
|
ProjectDatabase,
|
||||||
ProjectExamples,
|
ProjectExamples,
|
||||||
ProjectFrontend,
|
ProjectFrontend,
|
||||||
|
ProjectOrm,
|
||||||
|
ProjectPackageManager,
|
||||||
|
ProjectRuntime,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { displayConfig } from "./utils/display-config";
|
import { displayConfig } from "./utils/display-config";
|
||||||
import { generateReproducibleCommand } from "./utils/generate-reproducible-command";
|
import { generateReproducibleCommand } from "./utils/generate-reproducible-command";
|
||||||
@@ -36,22 +41,13 @@ async function main() {
|
|||||||
.option("--orm <type>", "ORM type (none, drizzle, prisma)")
|
.option("--orm <type>", "ORM type (none, drizzle, prisma)")
|
||||||
.option("--auth", "Include authentication")
|
.option("--auth", "Include authentication")
|
||||||
.option("--no-auth", "Exclude authentication")
|
.option("--no-auth", "Exclude authentication")
|
||||||
|
.option("--frontend <types...>", "Frontend types (web, native, none)")
|
||||||
.option(
|
.option(
|
||||||
"--frontend <types>",
|
"--addons <types...>",
|
||||||
"Frontend types (web,native or both)",
|
"Additional addons (pwa, tauri, biome, husky)",
|
||||||
(val) => val.split(",") as ProjectFrontend[],
|
|
||||||
)
|
|
||||||
.option(
|
|
||||||
"--addons <types>",
|
|
||||||
"Additional addons (pwa,tauri,biome,husky)",
|
|
||||||
(val) => val.split(",") as ProjectAddons[],
|
|
||||||
)
|
)
|
||||||
.option("--no-addons", "Skip all additional addons")
|
.option("--no-addons", "Skip all additional addons")
|
||||||
.option(
|
.option("--examples <types...>", "Examples to include (todo, ai)")
|
||||||
"--examples <types>",
|
|
||||||
"Examples to include (todo,ai)",
|
|
||||||
(val) => val.split(",") as ProjectExamples[],
|
|
||||||
)
|
|
||||||
.option("--no-examples", "Skip all examples")
|
.option("--no-examples", "Skip all examples")
|
||||||
.option("--git", "Initialize git repository")
|
.option("--git", "Initialize git repository")
|
||||||
.option("--no-git", "Skip git initialization")
|
.option("--no-git", "Skip git initialization")
|
||||||
@@ -70,93 +66,12 @@ async function main() {
|
|||||||
renderTitle();
|
renderTitle();
|
||||||
intro(pc.magenta("Creating a new Better-T-Stack project"));
|
intro(pc.magenta("Creating a new Better-T-Stack project"));
|
||||||
|
|
||||||
const options = program.opts();
|
const options = program.opts() as CLIOptions;
|
||||||
const projectDirectory = program.args[0];
|
const projectDirectory = program.args[0];
|
||||||
|
|
||||||
if (
|
validateOptions(options);
|
||||||
options.database &&
|
|
||||||
!["none", "sqlite", "postgres"].includes(options.database)
|
|
||||||
) {
|
|
||||||
cancel(
|
|
||||||
pc.red(
|
|
||||||
`Invalid database type: ${options.database}. Must be none, sqlite, or postgres.`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.orm && !["none", "drizzle", "prisma"].includes(options.orm)) {
|
const flagConfig = processFlags(options, projectDirectory);
|
||||||
cancel(
|
|
||||||
pc.red(
|
|
||||||
`Invalid ORM type: ${options.orm}. Must be none, drizzle, or prisma.`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
options.packageManager &&
|
|
||||||
!["npm", "pnpm", "bun"].includes(options.packageManager)
|
|
||||||
) {
|
|
||||||
cancel(
|
|
||||||
pc.red(
|
|
||||||
`Invalid package manager: ${options.packageManager}. Must be npm, pnpm, or bun.`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.backend && !["hono", "elysia"].includes(options.backend)) {
|
|
||||||
cancel(
|
|
||||||
pc.red(
|
|
||||||
`Invalid backend framework: ${options.backend}. Must be hono or elysia.`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.runtime && !["bun", "node"].includes(options.runtime)) {
|
|
||||||
cancel(
|
|
||||||
pc.red(`Invalid runtime: ${options.runtime}. Must be bun or node.`),
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.examples && options.examples.length > 0) {
|
|
||||||
const validExamples = ["todo", "ai"];
|
|
||||||
const invalidExamples = options.examples.filter(
|
|
||||||
(example: ProjectExamples) => !validExamples.includes(example),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (invalidExamples.length > 0) {
|
|
||||||
cancel(
|
|
||||||
pc.red(
|
|
||||||
`Invalid example(s): ${invalidExamples.join(", ")}. Valid options are: ${validExamples.join(", ")}.`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const flagConfig: Partial<ProjectConfig> = {
|
|
||||||
...(projectDirectory && { projectName: projectDirectory }),
|
|
||||||
...(options.database && { database: options.database }),
|
|
||||||
...(options.orm && { orm: options.orm }),
|
|
||||||
...("auth" in options && { auth: options.auth }),
|
|
||||||
...(options.packageManager && { packageManager: options.packageManager }),
|
|
||||||
...("git" in options && { git: options.git }),
|
|
||||||
...("install" in options && { noInstall: !options.install }),
|
|
||||||
...("turso" in options && { turso: options.turso }),
|
|
||||||
...(options.backend && { backend: options.backend }),
|
|
||||||
...(options.runtime && { runtime: options.runtime }),
|
|
||||||
...(options.frontend && { frontend: options.frontend }),
|
|
||||||
...((options.addons || options.addons === false) && {
|
|
||||||
addons: options.addons === false ? [] : options.addons,
|
|
||||||
}),
|
|
||||||
...((options.examples || options.examples === false) && {
|
|
||||||
examples: options.examples === false ? [] : options.examples,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!options.yes && Object.keys(flagConfig).length > 0) {
|
if (!options.yes && Object.keys(flagConfig).length > 0) {
|
||||||
log.info(pc.yellow("Using these pre-selected options:"));
|
log.info(pc.yellow("Using these pre-selected options:"));
|
||||||
@@ -178,15 +93,7 @@ async function main() {
|
|||||||
log.message("");
|
log.message("");
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await createProject(config);
|
await createProject(config);
|
||||||
|
|
||||||
if (!config.noInstall) {
|
|
||||||
await installDependencies({
|
|
||||||
projectDir,
|
|
||||||
packageManager: config.packageManager,
|
|
||||||
addons: config.addons,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
log.success(
|
log.success(
|
||||||
pc.blue(
|
pc.blue(
|
||||||
@@ -211,6 +118,322 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateOptions(options: CLIOptions): void {
|
||||||
|
if (
|
||||||
|
options.database &&
|
||||||
|
!["none", "sqlite", "postgres"].includes(options.database)
|
||||||
|
) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
`Invalid database type: ${options.database}. Must be none, sqlite, or postgres.`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.orm && !["none", "drizzle", "prisma"].includes(options.orm)) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
`Invalid ORM type: ${options.orm}. Must be none, drizzle, or prisma.`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.database === "none") {
|
||||||
|
if (options.auth === true) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
"Authentication requires a database. Cannot use --auth with --database none.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.orm && options.orm !== "none") {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
`Cannot use ORM with no database. Cannot use --orm ${options.orm} with --database none.`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("turso" in options && options.turso === true) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
"Turso setup requires a SQLite database. Cannot use --turso with --database none.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
"turso" in options &&
|
||||||
|
options.turso === true &&
|
||||||
|
options.database &&
|
||||||
|
options.database !== "sqlite"
|
||||||
|
) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
`Turso setup requires a SQLite database. Cannot use --turso with --database ${options.database}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
"turso" in options &&
|
||||||
|
options.turso === true &&
|
||||||
|
options.orm === "prisma"
|
||||||
|
) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
"Turso setup is not compatible with Prisma. Cannot use --turso with --orm prisma",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
options.packageManager &&
|
||||||
|
!["npm", "pnpm", "bun"].includes(options.packageManager)
|
||||||
|
) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
`Invalid package manager: ${options.packageManager}. Must be npm, pnpm, or bun.`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.backend && !["hono", "elysia"].includes(options.backend)) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
`Invalid backend framework: ${options.backend}. Must be hono or elysia.`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.runtime && !["bun", "node"].includes(options.runtime)) {
|
||||||
|
cancel(pc.red(`Invalid runtime: ${options.runtime}. Must be bun or node.`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
options.examples &&
|
||||||
|
Array.isArray(options.examples) &&
|
||||||
|
options.examples.length > 0
|
||||||
|
) {
|
||||||
|
const validExamples = ["todo", "ai"];
|
||||||
|
const invalidExamples = options.examples.filter(
|
||||||
|
(example: string) => !validExamples.includes(example),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (invalidExamples.length > 0) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
`Invalid example(s): ${invalidExamples.join(", ")}. Valid options are: ${validExamples.join(", ")}.`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.examples.includes("ai") && options.backend === "elysia") {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
"AI example is only compatible with Hono backend. Cannot use --examples ai with --backend elysia",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
options.frontend &&
|
||||||
|
!options.frontend.includes("web") &&
|
||||||
|
!options.frontend.includes("none")
|
||||||
|
) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
"Examples require a web frontend. Cannot use --examples with --frontend native only",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.frontend && options.frontend.length > 0) {
|
||||||
|
const validFrontends = ["web", "native", "none"];
|
||||||
|
const invalidFrontends = options.frontend.filter(
|
||||||
|
(frontend: string) => !validFrontends.includes(frontend),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (invalidFrontends.length > 0) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
`Invalid frontend(s): ${invalidFrontends.join(", ")}. Valid options are: ${validFrontends.join(", ")}.`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.frontend.includes("none") && options.frontend.length > 1) {
|
||||||
|
cancel(pc.red(`Cannot combine 'none' with other frontend options.`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.addons && options.addons.length > 0) {
|
||||||
|
const validAddons = ["pwa", "tauri", "biome", "husky"];
|
||||||
|
const invalidAddons = options.addons.filter(
|
||||||
|
(addon: string) => !validAddons.includes(addon),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (invalidAddons.length > 0) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
`Invalid addon(s): ${invalidAddons.join(", ")}. Valid options are: ${validAddons.join(", ")}.`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const webSpecificAddons = ["pwa", "tauri"];
|
||||||
|
const hasWebSpecificAddons = options.addons.some((addon) =>
|
||||||
|
webSpecificAddons.includes(addon),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
hasWebSpecificAddons &&
|
||||||
|
options.frontend &&
|
||||||
|
!options.frontend.includes("web") &&
|
||||||
|
!options.frontend.includes("none")
|
||||||
|
) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
`PWA and Tauri addons require a web frontend. Cannot use --addons ${options.addons
|
||||||
|
.filter((a) => webSpecificAddons.includes(a))
|
||||||
|
.join(", ")} with --frontend native only`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processFlags(
|
||||||
|
options: CLIOptions,
|
||||||
|
projectDirectory?: string,
|
||||||
|
): Partial<ProjectConfig> {
|
||||||
|
let frontend: ProjectFrontend[] | undefined = undefined;
|
||||||
|
if (options.frontend) {
|
||||||
|
if (options.frontend.includes("none")) {
|
||||||
|
frontend = [];
|
||||||
|
} else {
|
||||||
|
frontend = options.frontend.filter(
|
||||||
|
(f): f is ProjectFrontend => f === "web" || f === "native",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const database = options.database as ProjectDatabase | undefined;
|
||||||
|
let orm: ProjectOrm | undefined;
|
||||||
|
if (options.orm) {
|
||||||
|
orm = options.orm as ProjectOrm;
|
||||||
|
}
|
||||||
|
|
||||||
|
let auth: boolean | undefined = "auth" in options ? options.auth : undefined;
|
||||||
|
let tursoOption: boolean | undefined =
|
||||||
|
"turso" in options ? options.turso : undefined;
|
||||||
|
|
||||||
|
if (database === "none") {
|
||||||
|
orm = "none";
|
||||||
|
auth = false;
|
||||||
|
tursoOption = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let examples: ProjectExamples[] | undefined;
|
||||||
|
if ("examples" in options) {
|
||||||
|
if (options.examples === false) {
|
||||||
|
examples = [];
|
||||||
|
} else if (Array.isArray(options.examples)) {
|
||||||
|
examples = options.examples.filter(
|
||||||
|
(ex): ex is ProjectExamples => ex === "todo" || ex === "ai",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (frontend && frontend.length > 0 && !frontend.includes("web")) {
|
||||||
|
examples = [];
|
||||||
|
log.warn(
|
||||||
|
pc.yellow("Examples require web frontend - ignoring examples flag"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (examples.includes("ai") && options.backend === "elysia") {
|
||||||
|
examples = examples.filter((ex) => ex !== "ai");
|
||||||
|
log.warn(
|
||||||
|
pc.yellow(
|
||||||
|
"AI example is not compatible with Elysia - removing AI example",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let addons: ProjectAddons[] | undefined;
|
||||||
|
if ("addons" in options) {
|
||||||
|
if (options.addons === undefined) {
|
||||||
|
addons = [];
|
||||||
|
} else if (options.addons) {
|
||||||
|
addons = options.addons.filter(
|
||||||
|
(addon): addon is ProjectAddons =>
|
||||||
|
addon === "pwa" ||
|
||||||
|
addon === "tauri" ||
|
||||||
|
addon === "biome" ||
|
||||||
|
addon === "husky",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (frontend && frontend.length > 0 && !frontend.includes("web")) {
|
||||||
|
addons = addons.filter((addon) => !["pwa", "tauri"].includes(addon));
|
||||||
|
if (addons.length !== options.addons.length) {
|
||||||
|
log.warn(
|
||||||
|
pc.yellow(
|
||||||
|
"PWA and Tauri addons require web frontend - removing these addons",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addons.includes("husky") && !addons.includes("biome")) {
|
||||||
|
addons.push("biome");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const backend = options.backend as ProjectBackend | undefined;
|
||||||
|
const runtime = options.runtime as ProjectRuntime | undefined;
|
||||||
|
const packageManager = options.packageManager as
|
||||||
|
| ProjectPackageManager
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(projectDirectory && { projectName: projectDirectory }),
|
||||||
|
...(database !== undefined && { database }),
|
||||||
|
...(orm !== undefined && { orm }),
|
||||||
|
...(auth !== undefined && { auth }),
|
||||||
|
...(packageManager && { packageManager }),
|
||||||
|
...("git" in options && { git: options.git }),
|
||||||
|
...("install" in options && { noInstall: !options.install }),
|
||||||
|
...(tursoOption !== undefined && { turso: tursoOption }),
|
||||||
|
...(backend && { backend }),
|
||||||
|
...(runtime && { runtime }),
|
||||||
|
...(frontend !== undefined && { frontend }),
|
||||||
|
...(addons !== undefined && { addons }),
|
||||||
|
...(examples !== undefined && { examples }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
main().catch((err) => {
|
main().catch((err) => {
|
||||||
log.error("Aborting installation...");
|
log.error("Aborting installation...");
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
|
|||||||
@@ -22,3 +22,19 @@ export interface ProjectConfig {
|
|||||||
turso?: boolean;
|
turso?: boolean;
|
||||||
frontend: ProjectFrontend[];
|
frontend: ProjectFrontend[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CLIOptions = {
|
||||||
|
yes?: boolean;
|
||||||
|
database?: string;
|
||||||
|
orm?: string;
|
||||||
|
auth?: boolean;
|
||||||
|
frontend?: string[];
|
||||||
|
addons?: string[];
|
||||||
|
examples?: string[] | boolean;
|
||||||
|
git?: boolean;
|
||||||
|
packageManager?: string;
|
||||||
|
install?: boolean;
|
||||||
|
turso?: boolean;
|
||||||
|
backend?: string;
|
||||||
|
runtime?: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -32,17 +32,17 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.frontend && config.frontend.length > 0) {
|
if (config.frontend && config.frontend.length > 0) {
|
||||||
flags.push(`--frontend ${config.frontend.join(",")}`);
|
flags.push(`--frontend ${config.frontend.join(" ")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.addons && config.addons.length > 0) {
|
if (config.addons && config.addons.length > 0) {
|
||||||
flags.push(`--addons ${config.addons.join(",")}`);
|
flags.push(`--addons ${config.addons.join(" ")}`);
|
||||||
} else {
|
} else {
|
||||||
flags.push("--no-addons");
|
flags.push("--no-addons");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.examples && config.examples.length > 0) {
|
if (config.examples && config.examples.length > 0) {
|
||||||
flags.push(`--examples ${config.examples.join(",")}`);
|
flags.push(`--examples ${config.examples.join(" ")}`);
|
||||||
} else {
|
} else {
|
||||||
flags.push("--no-examples");
|
flags.push("--no-examples");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
export const NAV_THEME = {
|
export const NAV_THEME = {
|
||||||
light: {
|
light: {
|
||||||
background: "hsl(0 0% 100%)", // background
|
background: "hsl(0 0% 100%)",
|
||||||
border: "hsl(240 5.9% 90%)", // border
|
border: "hsl(240 5.9% 90%)",
|
||||||
card: "hsl(0 0% 100%)", // card
|
card: "hsl(0 0% 100%)",
|
||||||
notification: "hsl(0 84.2% 60.2%)", // destructive
|
notification: "hsl(0 84.2% 60.2%)",
|
||||||
primary: "hsl(240 5.9% 10%)", // primary
|
primary: "hsl(240 5.9% 10%)",
|
||||||
text: "hsl(240 10% 3.9%)", // foreground
|
text: "hsl(240 10% 3.9%)",
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
background: "hsl(240 10% 3.9%)", // background
|
background: "hsl(240 10% 3.9%)",
|
||||||
border: "hsl(240 3.7% 15.9%)", // border
|
border: "hsl(240 3.7% 15.9%)",
|
||||||
card: "hsl(240 10% 3.9%)", // card
|
card: "hsl(240 10% 3.9%)",
|
||||||
notification: "hsl(0 72% 51%)", // destructive
|
notification: "hsl(0 72% 51%)",
|
||||||
primary: "hsl(0 0% 98%)", // primary
|
primary: "hsl(0 0% 98%)",
|
||||||
text: "hsl(0 0% 98%)", // foreground
|
text: "hsl(0 0% 98%)",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,9 +19,8 @@ const app = new Elysia()
|
|||||||
const { request } = context;
|
const { request } = context;
|
||||||
if (["POST", "GET"].includes(request.method)) {
|
if (["POST", "GET"].includes(request.method)) {
|
||||||
return auth.handler(request);
|
return auth.handler(request);
|
||||||
} else {
|
|
||||||
context.error(405);
|
|
||||||
}
|
}
|
||||||
|
context.error(405);
|
||||||
})
|
})
|
||||||
.all("/trpc/*", async (context) => {
|
.all("/trpc/*", async (context) => {
|
||||||
const res = await fetchRequestHandler({
|
const res = await fetchRequestHandler({
|
||||||
@@ -34,5 +33,5 @@ const app = new Elysia()
|
|||||||
})
|
})
|
||||||
.get("/", () => "OK")
|
.get("/", () => "OK")
|
||||||
.listen(3000, () => {
|
.listen(3000, () => {
|
||||||
console.log(`Server is running on http://localhost:3000`);
|
console.log("Server is running on http://localhost:3000");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -393,72 +393,58 @@ const StackArchitect = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const projectName = stackState.projectName || "my-better-t-app";
|
const projectName = stackState.projectName || "my-better-t-app";
|
||||||
const flags: string[] = ["--yes"]; // Start with yes flag
|
const flags: string[] = ["--yes"];
|
||||||
|
|
||||||
// Only add flags that differ from defaults
|
|
||||||
|
|
||||||
// Frontend (default is web)
|
|
||||||
if (stackState.frontend.length === 1 && stackState.frontend[0] === "none") {
|
if (stackState.frontend.length === 1 && stackState.frontend[0] === "none") {
|
||||||
flags.push("--frontend none");
|
flags.push("--frontend none");
|
||||||
} else if (
|
} else if (
|
||||||
!(stackState.frontend.length === 1 && stackState.frontend[0] === "web")
|
!(stackState.frontend.length === 1 && stackState.frontend[0] === "web")
|
||||||
) {
|
) {
|
||||||
flags.push(`--frontend ${stackState.frontend.join(",")}`);
|
flags.push(`--frontend ${stackState.frontend.join(" ")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database (default is sqlite)
|
|
||||||
if (stackState.database !== "sqlite") {
|
if (stackState.database !== "sqlite") {
|
||||||
flags.push(`--database ${stackState.database}`);
|
flags.push(`--database ${stackState.database}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ORM (default is drizzle)
|
|
||||||
if (stackState.database !== "none" && stackState.orm !== "drizzle") {
|
if (stackState.database !== "none" && stackState.orm !== "drizzle") {
|
||||||
flags.push(`--orm ${stackState.orm}`);
|
flags.push(`--orm ${stackState.orm}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth (default is true)
|
|
||||||
if (stackState.auth === "false") {
|
if (stackState.auth === "false") {
|
||||||
flags.push("--no-auth");
|
flags.push("--no-auth");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turso (default is false)
|
|
||||||
if (stackState.turso === "true") {
|
if (stackState.turso === "true") {
|
||||||
flags.push("--turso");
|
flags.push("--turso");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backend (default is hono)
|
|
||||||
if (stackState.backendFramework !== "hono") {
|
if (stackState.backendFramework !== "hono") {
|
||||||
flags.push(`--backend ${stackState.backendFramework}`);
|
flags.push(`--backend ${stackState.backendFramework}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runtime (default is bun)
|
|
||||||
if (stackState.runtime !== "bun") {
|
if (stackState.runtime !== "bun") {
|
||||||
flags.push(`--runtime ${stackState.runtime}`);
|
flags.push(`--runtime ${stackState.runtime}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Package manager (default is bun)
|
|
||||||
if (stackState.packageManager !== "bun") {
|
if (stackState.packageManager !== "bun") {
|
||||||
flags.push(`--package-manager ${stackState.packageManager}`);
|
flags.push(`--package-manager ${stackState.packageManager}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Git (default is true)
|
|
||||||
if (stackState.git === "false") {
|
if (stackState.git === "false") {
|
||||||
flags.push("--no-git");
|
flags.push("--no-git");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install (default is true)
|
|
||||||
if (stackState.install === "false") {
|
if (stackState.install === "false") {
|
||||||
flags.push("--no-install");
|
flags.push("--no-install");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Addons (default is none)
|
|
||||||
if (stackState.addons.length > 0) {
|
if (stackState.addons.length > 0) {
|
||||||
flags.push(`--addons ${stackState.addons.join(",")}`);
|
flags.push(`--addons ${stackState.addons.join(" ")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Examples (default is none)
|
|
||||||
if (stackState.examples.length > 0) {
|
if (stackState.examples.length > 0) {
|
||||||
flags.push(`--examples ${stackState.examples.join(",")}`);
|
flags.push(`--examples ${stackState.examples.join(" ")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${base} ${projectName} ${flags.join(" ")}`;
|
return `${base} ${projectName} ${flags.join(" ")}`;
|
||||||
@@ -486,7 +472,6 @@ const StackArchitect = () => {
|
|||||||
|
|
||||||
if (currentSelection.includes(techId)) {
|
if (currentSelection.includes(techId)) {
|
||||||
if (currentSelection.length === 1) {
|
if (currentSelection.length === 1) {
|
||||||
// Don't remove the last frontend option
|
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,9 +497,7 @@ const StackArchitect = () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding a frontend option
|
|
||||||
if (currentSelection.includes("none")) {
|
if (currentSelection.includes("none")) {
|
||||||
// Replace "none" with the selected option
|
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
frontend: [techId],
|
frontend: [techId],
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ export default function Testimonials() {
|
|||||||
return () => window.removeEventListener("resize", handleResize);
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Get visible tweets
|
|
||||||
const getVisibleTweets = () => {
|
const getVisibleTweets = () => {
|
||||||
const visible = [];
|
const visible = [];
|
||||||
for (let i = 0; i < tweetsPerPage; i++) {
|
for (let i = 0; i < tweetsPerPage; i++) {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
|
|||||||
*/
|
*/
|
||||||
export const baseOptions: BaseLayoutProps = {
|
export const baseOptions: BaseLayoutProps = {
|
||||||
nav: {
|
nav: {
|
||||||
// can be JSX too!
|
|
||||||
title: "Better-T-Stack",
|
title: "Better-T-Stack",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"typescript": "5.7.3"
|
"typescript": "5.7.3"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*": ["biome check --write ."]
|
"*": ["bun biome check --write ."]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
|
|||||||
Reference in New Issue
Block a user